Skip to content

Commit

Permalink
Improves playground
Browse files Browse the repository at this point in the history
  • Loading branch information
hediet committed Sep 6, 2023
1 parent e7d7a5b commit 51ba677
Show file tree
Hide file tree
Showing 12 changed files with 734 additions and 576 deletions.
2 changes: 1 addition & 1 deletion website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"mini-css-extract-plugin": "^2.6.1",
"mobx": "^5.15.4",
"mobx-react": "^6.2.2",
"monaco-editor": "^0.41.0",
"monaco-editor": "^0.42.0-dev-20230906",
"react": "^17.0.2",
"react-bootstrap": "^2.4.0",
"react-dom": "^17.0.2",
Expand Down
2 changes: 1 addition & 1 deletion website/src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ export interface IPlaygroundProject {
}

export interface IPreviewState extends IPlaygroundProject {
key: number;
reloadKey: number;
monacoSetup: IMonacoSetup;
}
161 changes: 161 additions & 0 deletions website/src/website/pages/playground/BisectModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { action, ObservableMap } from "mobx";
import {
getNpmVersions,
getNpmVersionsSync,
getVsCodeCommitId,
} from "./getNpmVersionsSync";
import { PlaygroundModel } from "./PlaygroundModel";
import { findLastIndex } from "./utils";

export class BisectModel {
private readonly map = new ObservableMap<string, boolean>();

constructor(private readonly model: PlaygroundModel) {}

public getState(version: string): boolean | undefined {
return this.map.get(version);
}

public get isActive() {
return [...this.map.values()].some((e) => e !== undefined);
}

public reset(): void {
this.map.clear();
}

public async toggleState(version: string, state: boolean): Promise<void> {
const currentState = this.getState(version);
await this.setState(
version,
currentState === state ? undefined : state
);
}

@action
public async setState(
version: string,
state: boolean | undefined
): Promise<void> {
if (state === undefined) {
this.map.delete(version);
} else {
this.map.set(version, state);
}

const nextVersion = await this.getNextVersion();
if (!nextVersion) {
return;
}
this.model.settings.setSettings({
...this.model.settings.settings,
npmVersion: nextVersion,
});
}

private get versions() {
return getNpmVersionsSync(undefined);
}

private get indexOfLastBadVersion() {
return findLastIndex(this.versions, (v) => this.map.get(v) === false);
}
private get indexOfFirstGoodVersion() {
return this.versions.findIndex((v) => this.map.get(v) === true);
}

public get steps() {
const indexOfFirstGoodVersion = this.indexOfFirstGoodVersion;
const indexOfLastBadVersion = this.indexOfLastBadVersion;

if (indexOfFirstGoodVersion === -1 && indexOfLastBadVersion === -1) {
return -1;
}
if (indexOfFirstGoodVersion === -1) {
return Math.ceil(
Math.log2(this.versions.length - indexOfLastBadVersion)
);
} else if (indexOfLastBadVersion === -1) {
return Math.ceil(Math.log2(indexOfFirstGoodVersion + 1));
} else {
return Math.ceil(
Math.log2(indexOfFirstGoodVersion - indexOfLastBadVersion)
);
}
}

public get isFinished() {
if (
this.indexOfFirstGoodVersion !== -1 &&
this.indexOfLastBadVersion + 1 === this.indexOfFirstGoodVersion
) {
return true;
}
return false;
}

public async openGithub() {
const versions = await getNpmVersions();
const indexOfFirstGoodVersion =
this.indexOfFirstGoodVersion === -1
? versions.length - 1
: this.indexOfFirstGoodVersion;
const indexOfLastBadVersion =
this.indexOfLastBadVersion === -1 ? 0 : this.indexOfLastBadVersion;
const goodCommitId = await getVsCodeCommitId(
versions[indexOfFirstGoodVersion]
);
const badCommitId = await getVsCodeCommitId(
versions[indexOfLastBadVersion]
);
window.open(
`https://github.com/microsoft/vscode/compare/${goodCommitId}...${badCommitId}`,
"_blank"
);
}

private async getNextVersion(): Promise<string | undefined> {
const versions = await getNpmVersions();

const indexOfFirstGoodVersion = this.indexOfFirstGoodVersion;
const indexOfLastBadVersion = this.indexOfLastBadVersion;

if (
indexOfFirstGoodVersion !== -1 &&
indexOfLastBadVersion + 1 === indexOfFirstGoodVersion
) {
// Finished
return;
}

if (indexOfLastBadVersion === -1 && indexOfFirstGoodVersion === -1) {
return versions[0];
}
if (indexOfLastBadVersion === -1) {
// try first (newest) version that hasn't been tested
const indexOfFirstUntestedVersion = versions.findIndex(
(v) => this.map.get(v) === undefined
);
if (indexOfFirstUntestedVersion === -1) {
return undefined;
}
return versions[indexOfFirstUntestedVersion];
}

if (indexOfFirstGoodVersion === -1) {
/*// exponential back off, might be good for recent regressions, but ruins step counter
const candidate = Math.min(
indexOfLastBadVersion * 2 + 1,
versions.length - 1
);*/
const candidate = Math.floor(
(indexOfLastBadVersion + versions.length) / 2
);
return versions[candidate];
}

return versions[
Math.floor((indexOfLastBadVersion + indexOfFirstGoodVersion) / 2)
];
}
}
211 changes: 211 additions & 0 deletions website/src/website/pages/playground/LocationModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import { action, observable } from "mobx";
import { IPlaygroundProject } from "../../../shared";
import { monacoEditorVersion } from "../../monacoEditorVersion";
import { LzmaCompressor } from "../../utils/lzmaCompressor";
import {
HistoryController,
IHistoryModel,
ILocation,
} from "../../utils/ObservableHistory";
import { debouncedComputed, Disposable } from "../../utils/utils";
import { getPlaygroundExamples, PlaygroundExample } from "./playgroundExamples";
import { Source } from "./Source";
import { PlaygroundModel } from "./PlaygroundModel";
import { projectEquals } from "./utils";

export class LocationModel implements IHistoryModel {
public readonly dispose = Disposable.fn();

private readonly compressor = new LzmaCompressor<IPlaygroundProject>();

private cachedState:
| { state: IPlaygroundProject; hash: string }
| undefined = undefined;

@observable private _sourceOverride: Source | undefined;
get sourceOverride(): Source | undefined {
return this._sourceOverride;
}

@observable private _compareWith: Source | undefined;
get compareWith(): Source | undefined {
return this._compareWith;
}

/**
* This is used to control replace/push state.
* Replace is used if the history id does not change.
*/
@observable historyId: number = 0;

constructor(private readonly model: PlaygroundModel) {
this.dispose.track(
new HistoryController((initialLocation) => {
this.updateLocation(initialLocation);
return this;
})
);
}

get location(): ILocation {
const source = this._sourceOverride || this.sourceFromSettings;
return {
hashValue: this.computedHashValue.value || this.cachedState?.hash,
searchParams: {
source: source?.sourceToString(),
sourceLanguages: source?.sourceLanguagesToString(),
compareWith: this._compareWith?.sourceToString(),
},
};
}

@action
updateLocation(currentLocation: ILocation): void {
const hashValue = currentLocation.hashValue;
const sourceStr = currentLocation.searchParams.source;
const sourceLanguages = currentLocation.searchParams.sourceLanguages;
const source =
sourceStr || sourceLanguages
? Source.parse(sourceStr, sourceLanguages)
: undefined;

if (this.sourceFromSettings?.equals(source)) {
this._sourceOverride = undefined;
} else {
this._sourceOverride = source;
}

const compareWithStr = currentLocation.searchParams.compareWith;
const compareWith = compareWithStr
? Source.parse(compareWithStr, undefined)
: undefined;
this._compareWith = compareWith;

function findExample(hashValue: string): PlaygroundExample | undefined {
if (hashValue.startsWith("example-")) {
hashValue = hashValue.substring("example-".length);
}
return getPlaygroundExamples()
.flatMap((e) => e.examples)
.find((e) => e.id === hashValue);
}

let example: PlaygroundExample | undefined;

if (!hashValue) {
this.model.selectedExample = getPlaygroundExamples()[0].examples[0];
} else if ((example = findExample(hashValue))) {
this.model.selectedExample = example;
} else {
let p: IPlaygroundProject | undefined = undefined;
if (this.cachedState?.hash === hashValue) {
p = this.cachedState.state;
}
if (!p) {
try {
p =
this.compressor.decodeData<IPlaygroundProject>(
hashValue
);
} catch (e) {
console.log("Could not deserialize from hash value", e);
}
}
if (p) {
this.cachedState = { state: p, hash: hashValue };
this.model.setState(p);
}
}
}

private readonly computedHashValue = debouncedComputed(
500,
() => ({
state: this.model.playgroundProject,
selectedExampleProject: this.model.selectedExampleProject,
}),
({ state, selectedExampleProject }) => {
if (
selectedExampleProject &&
projectEquals(state, selectedExampleProject.project)
) {
return "example-" + selectedExampleProject.example.id;
}
if (
this.cachedState &&
projectEquals(this.cachedState.state, state)
) {
return this.cachedState.hash;
}
return this.compressor.encodeData(state);
}
);

private get sourceFromSettings(): Source | undefined {
const settings = this.model.settings.settings;
if (settings.monacoSource === "npm") {
return new Source(settings.npmVersion, undefined, undefined);
} else if (
settings.monacoSource === "independent" &&
((settings.coreSource === "url" &&
(settings.languagesSource === "latest" ||
settings.languagesSource === "url")) ||
(settings.coreSource === "latest" &&
settings.languagesSource === "url"))
) {
return new Source(
undefined,
settings.coreSource === "url" ? settings.coreUrl : undefined,
settings.languagesSource === "latest"
? undefined
: settings.languagesUrl
);
} else if (settings.monacoSource === "latest") {
return new Source(monacoEditorVersion, undefined, undefined);
}
return undefined;
}

@action
exitCompare(): void {
this._compareWith = undefined;
this.historyId++;
}

@action
disableSourceOverride(): void {
this._sourceOverride = undefined;
this.historyId++;
}

@action
compareWithLatestDev(): void {
this._compareWith = Source.useLatestDev();
this.historyId++;
}

@action
saveCompareWith(): void {
if (this._compareWith) {
this.model.settings.setSettings({
...this.model.settings.settings,
...this._compareWith.toPartialSettings(),
});
this.historyId++;
this._compareWith = undefined;
this._sourceOverride = undefined;
}
}

@action
saveSourceOverride(): void {
if (this._sourceOverride) {
this.model.settings.setSettings({
...this.model.settings.settings,
...this._sourceOverride.toPartialSettings(),
});
this.historyId++;
this._sourceOverride = undefined;
}
}
}
Loading

0 comments on commit 51ba677

Please sign in to comment.