Merge branch 'main' into feature/json_docsym
commit
c38f07a36e
@ -1,103 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
const fs = require('fs');
|
||||
const cp = require('child_process');
|
||||
const packageJson = require('../../../package.json');
|
||||
|
||||
if (process.argv.length !== 4) {
|
||||
console.error(`usage: node computeState.js <"workflow_dispatch"|"schedule"> <"true"|"false">`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const EVENT_NAME = /** @type {'workflow_dispatch'|'schedule'} */ (process.argv[2]);
|
||||
const STR_NIGHTLY = /** @type {'true'|'false'|''} */ (process.argv[3]);
|
||||
|
||||
if (!/^((workflow_dispatch)|(schedule))$/.test(EVENT_NAME)) {
|
||||
console.error(`usage: node computeState.js <"workflow_dispatch"|"schedule"> <"true"|"false">`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
if (!/^((true)|(false)|())$/.test(STR_NIGHTLY)) {
|
||||
console.error(`usage: node computeState.js <"workflow_dispatch"|"schedule"> <"true"|"false">`);
|
||||
process.exit(3);
|
||||
}
|
||||
|
||||
const NIGHTLY = EVENT_NAME === 'schedule' || STR_NIGHTLY === 'true';
|
||||
|
||||
const distTag = NIGHTLY ? 'next' : 'latest';
|
||||
|
||||
const latestMonacoEditorVersion = npmGetLatestVersion('monaco-editor');
|
||||
const version = (() => {
|
||||
if (NIGHTLY) {
|
||||
const pieces = latestMonacoEditorVersion.split('.');
|
||||
const minor = parseInt(pieces[1], 10);
|
||||
const date = new Date();
|
||||
const yyyy = date.getUTCFullYear();
|
||||
const mm = String(date.getUTCMonth() + 1).padStart(2, '0');
|
||||
const dd = String(date.getUTCDate()).padStart(2, '0');
|
||||
return `0.${minor + 1}.0-dev.${yyyy}${mm}${dd}`;
|
||||
} else {
|
||||
return packageJson.version;
|
||||
}
|
||||
})();
|
||||
|
||||
const vscodeBranch = (() => {
|
||||
if (NIGHTLY) {
|
||||
return 'main';
|
||||
} else {
|
||||
return packageJson.vscode;
|
||||
}
|
||||
})();
|
||||
|
||||
const skipMonacoEditorCore = (() => {
|
||||
return /** @type {'true'|'false'} */ (String(npmExists('monaco-editor-core', version)));
|
||||
})();
|
||||
|
||||
const skipMonacoEditor = (() => {
|
||||
return /** @type {'true'|'false'} */ (String(npmExists('monaco-editor', version)));
|
||||
})();
|
||||
|
||||
console.log(`
|
||||
::set-output name=dist_tag::${distTag}
|
||||
::set-output name=version::${version}
|
||||
::set-output name=vscode_branch::${vscodeBranch}
|
||||
::set-output name=skip_monaco_editor_core::${skipMonacoEditorCore}
|
||||
::set-output name=skip_monaco_editor::${skipMonacoEditor}
|
||||
`);
|
||||
|
||||
/**
|
||||
* @param {string} packageName
|
||||
* @returns {string}
|
||||
*/
|
||||
function npmGetLatestVersion(packageName) {
|
||||
const output = cp.execSync(`npm show ${packageName} version`).toString();
|
||||
const version = output.split(/\r\n|\r|\n/g)[0];
|
||||
if (!/^0\.(\d+)\.(\d+)$/.test(version)) {
|
||||
console.error(`version ${version} does not match 0.x.y`);
|
||||
process.exit(1);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} packageName
|
||||
* @param {string} version
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function npmExists(packageName, version) {
|
||||
try {
|
||||
const output = cp.execSync(`npm show ${packageName}@${version} version`).toString();
|
||||
const result = output.split(/\r\n|\r|\n/g)[0];
|
||||
if (result.trim().length === 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,15 @@
|
||||
interface Env {
|
||||
VSCODE_REF: string;
|
||||
PRERELEASE_VERSION: string;
|
||||
}
|
||||
|
||||
export function getNightlyEnv(): Env {
|
||||
const env: Env = process.env as any;
|
||||
if (!env.PRERELEASE_VERSION) {
|
||||
throw new Error(`Missing PRERELEASE_VERSION in process.env`);
|
||||
}
|
||||
if (!env.VSCODE_REF) {
|
||||
throw new Error(`Missing VSCODE_REF in process.env`);
|
||||
}
|
||||
return env;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { registerLanguage } from '../_.contribution';
|
||||
|
||||
declare var AMD: any;
|
||||
declare var require: any;
|
||||
|
||||
registerLanguage({
|
||||
id: 'mdx',
|
||||
extensions: ['.mdx'],
|
||||
aliases: ['MDX', 'mdx'],
|
||||
loader: () => {
|
||||
if (AMD) {
|
||||
return new Promise((resolve, reject) => {
|
||||
require(['vs/basic-languages/mdx/mdx'], resolve, reject);
|
||||
});
|
||||
} else {
|
||||
return import('./mdx');
|
||||
}
|
||||
}
|
||||
});
|
@ -0,0 +1,171 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { testTokenization } from '../test/testRunner';
|
||||
|
||||
testTokenization(
|
||||
['mdx', 'yaml'],
|
||||
[
|
||||
// headers
|
||||
[
|
||||
{
|
||||
line: '# header 1',
|
||||
tokens: [{ startIndex: 0, type: 'keyword.mdx' }]
|
||||
},
|
||||
{
|
||||
line: '## header 2',
|
||||
tokens: [{ startIndex: 0, type: 'keyword.mdx' }]
|
||||
},
|
||||
{
|
||||
line: '### header 3',
|
||||
tokens: [{ startIndex: 0, type: 'keyword.mdx' }]
|
||||
},
|
||||
{
|
||||
line: '#### header 4',
|
||||
tokens: [{ startIndex: 0, type: 'keyword.mdx' }]
|
||||
},
|
||||
{
|
||||
line: '##### header 5',
|
||||
tokens: [{ startIndex: 0, type: 'keyword.mdx' }]
|
||||
},
|
||||
{
|
||||
line: '###### header 6',
|
||||
tokens: [{ startIndex: 0, type: 'keyword.mdx' }]
|
||||
}
|
||||
],
|
||||
|
||||
// Lists
|
||||
[
|
||||
{
|
||||
line: '- apple',
|
||||
tokens: [
|
||||
{ startIndex: 0, type: 'keyword.mdx' },
|
||||
{ startIndex: 1, type: 'white.mdx' },
|
||||
{ startIndex: 2, type: '' }
|
||||
]
|
||||
},
|
||||
{
|
||||
line: '* pear',
|
||||
tokens: [
|
||||
{ startIndex: 0, type: 'keyword.mdx' },
|
||||
{ startIndex: 1, type: 'white.mdx' },
|
||||
{ startIndex: 2, type: '' }
|
||||
]
|
||||
},
|
||||
{
|
||||
line: '+ pineapple',
|
||||
tokens: [
|
||||
{ startIndex: 0, type: 'keyword.mdx' },
|
||||
{ startIndex: 1, type: 'white.mdx' },
|
||||
{ startIndex: 2, type: '' }
|
||||
]
|
||||
},
|
||||
{
|
||||
line: '1. orange',
|
||||
tokens: [
|
||||
{ startIndex: 0, type: 'number.mdx' },
|
||||
{ startIndex: 2, type: 'white.mdx' },
|
||||
{ startIndex: 3, type: '' }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
// Frontmatter
|
||||
[
|
||||
{
|
||||
line: '---',
|
||||
tokens: [{ startIndex: 0, type: 'meta.content.mdx' }]
|
||||
},
|
||||
{
|
||||
line: 'frontmatter: yaml',
|
||||
tokens: [
|
||||
{ startIndex: 0, type: 'type.yaml' },
|
||||
{ startIndex: 11, type: 'operators.yaml' },
|
||||
{ startIndex: 12, type: 'white.yaml' },
|
||||
{ startIndex: 13, type: 'string.yaml' }
|
||||
]
|
||||
},
|
||||
{
|
||||
line: '---',
|
||||
tokens: [{ startIndex: 0, type: 'meta.content.mdx' }]
|
||||
}
|
||||
],
|
||||
|
||||
// links
|
||||
[
|
||||
{
|
||||
line: '[MDX](https://mdxjs.com)',
|
||||
tokens: [
|
||||
{ startIndex: 0, type: '' },
|
||||
{ startIndex: 1, type: 'type.identifier.mdx' },
|
||||
{ startIndex: 4, type: '' },
|
||||
{ startIndex: 6, type: 'string.link.mdx' },
|
||||
{ startIndex: 23, type: '' }
|
||||
]
|
||||
},
|
||||
{
|
||||
line: '[monaco][monaco]',
|
||||
tokens: [
|
||||
{ startIndex: 0, type: '' },
|
||||
{ startIndex: 1, type: 'type.identifier.mdx' },
|
||||
{ startIndex: 7, type: '' },
|
||||
{ startIndex: 9, type: 'type.identifier.mdx' },
|
||||
{ startIndex: 15, type: '' }
|
||||
]
|
||||
},
|
||||
{
|
||||
line: '[monaco][]',
|
||||
tokens: [
|
||||
{ startIndex: 0, type: '' },
|
||||
{ startIndex: 1, type: 'type.identifier.mdx' },
|
||||
{ startIndex: 9, type: '' }
|
||||
]
|
||||
},
|
||||
{
|
||||
line: '[monaco]',
|
||||
tokens: [
|
||||
{ startIndex: 0, type: '' },
|
||||
{ startIndex: 1, type: 'type.identifier.mdx' },
|
||||
{ startIndex: 7, type: '' }
|
||||
]
|
||||
},
|
||||
{
|
||||
line: '[monaco]: https://github.com/microsoft/monaco-editor',
|
||||
tokens: [
|
||||
{ startIndex: 0, type: '' },
|
||||
{ startIndex: 1, type: 'type.identifier.mdx' },
|
||||
{ startIndex: 7, type: '' },
|
||||
{ startIndex: 10, type: 'string.link.mdx' }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
// JSX
|
||||
[
|
||||
{
|
||||
line: '<div>**child**</div>',
|
||||
tokens: [
|
||||
{ startIndex: 0, type: 'type.identifier.mdx' },
|
||||
// This is incorrect. MDX children that start on the same line are JSX, not markdown
|
||||
{ startIndex: 5, type: 'strong.mdx' },
|
||||
{ startIndex: 14, type: 'type.identifier.mdx' }
|
||||
]
|
||||
},
|
||||
{
|
||||
line: '{console.log("This is JavaScript")}',
|
||||
tokens: [
|
||||
{ startIndex: 0, type: 'delimiter.bracket.mdx' },
|
||||
{ startIndex: 1, type: 'identifier.js' },
|
||||
{ startIndex: 8, type: 'delimiter.js' },
|
||||
{ startIndex: 9, type: 'identifier.js' },
|
||||
{ startIndex: 12, type: 'delimiter.parenthesis.js' },
|
||||
{ startIndex: 13, type: 'string.js' },
|
||||
{ startIndex: 33, type: 'delimiter.parenthesis.js' },
|
||||
{ startIndex: 34, type: 'delimiter.bracket.mdx' }
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
);
|
@ -0,0 +1,92 @@
|
||||
---
|
||||
frontmatter: data
|
||||
yaml: true
|
||||
---
|
||||
|
||||
[link](https://example.com)
|
||||
|
||||
~~~
|
||||
aasd
|
||||
asd
|
||||
asd
|
||||
~~~
|
||||
|
||||
# Hello MDX {1+2}
|
||||
|
||||
import { MyComponent } from './MyComponent'
|
||||
|
||||
This is **bold {'foo' + 1} text**
|
||||
|
||||
This is _emphasis {'foo' + 1} text_
|
||||
|
||||
This is *emphasis {'foo' + 1} text too*
|
||||
|
||||
This is an indented *code* block
|
||||
|
||||
export function foo() {
|
||||
console.log('asd', 1)
|
||||
if(true) {
|
||||
return 'yep'
|
||||
}
|
||||
return 'nope'
|
||||
}
|
||||
|
||||
|
||||
This is regular content
|
||||
|
||||
- this is a list
|
||||
|
||||
* this is also a list
|
||||
|
||||
+ me too!
|
||||
|
||||
1. pizza
|
||||
2. fries
|
||||
3. ice cream
|
||||
|
||||
----
|
||||
|
||||
_________
|
||||
|
||||
***\
|
||||
~~~css
|
||||
body {
|
||||
color: red;
|
||||
}
|
||||
~~~
|
||||
|
||||
> - this is a list
|
||||
>
|
||||
> * this is also a list
|
||||
>
|
||||
> + me too!
|
||||
>
|
||||
> 1. pizza
|
||||
> 2. fries
|
||||
> 3. ice cream
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> _________
|
||||
>
|
||||
> ***
|
||||
>
|
||||
> ```css
|
||||
> body {
|
||||
> color: red;
|
||||
> }
|
||||
> ```
|
||||
|
||||
> This is a blockquote
|
||||
>
|
||||
>> This is a nested {'blockquote'}
|
||||
|
||||
{'foo' + 1 + 2 + {} + 12}
|
||||
|
||||
{/* this is a comment */}
|
||||
|
||||
<MyComponent contenteditable className="text-green-700" id='foo' width={100 + 100}>
|
||||
|
||||
This is **also** markdown.
|
||||
|
||||
</MyComponent>
|
@ -0,0 +1,40 @@
|
||||
import * as React from "react";
|
||||
export class Loader<T> extends React.Component<
|
||||
{ children: (value: T) => React.ReactChild; loader: () => Promise<T> },
|
||||
{ value: T | undefined; hasValue: boolean }
|
||||
> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = { value: undefined, hasValue: false };
|
||||
if (!this.state.value) {
|
||||
this.props.loader().then((value) => {
|
||||
this.setState({
|
||||
hasValue: true,
|
||||
value,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.hasValue) {
|
||||
return null;
|
||||
}
|
||||
return this.props.children(this.state.value!);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a component so that it only gets mounted when monaco is loaded.
|
||||
*/
|
||||
export function withLoader(
|
||||
loader: () => Promise<void>
|
||||
): <TProps>(
|
||||
Component: React.FunctionComponent<TProps> | React.ComponentClass<TProps>
|
||||
) => any {
|
||||
return (Component) => {
|
||||
return (props: any) => (
|
||||
<Loader loader={loader}>{() => <Component {...props} />}</Loader>
|
||||
);
|
||||
};
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
const text = `function hello() {
|
||||
const value = /* set from `myEditor.getModel()`: */ `function hello() {
|
||||
alert('Hello world!');
|
||||
}`;
|
||||
|
||||
// Hover on each property to see its docs!
|
||||
monaco.editor.create(document.getElementById("container"), {
|
||||
value: text,
|
||||
const myEditor = monaco.editor.create(document.getElementById("container"), {
|
||||
value,
|
||||
language: "javascript",
|
||||
automaticLayout: true,
|
||||
});
|
||||
|
@ -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)
|
||||
];
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
import { monacoEditorVersion } from "../../monacoEditorVersion";
|
||||
import { getNpmVersionsSync } from "./getNpmVersionsSync";
|
||||
import { Settings } from "./SettingsModel";
|
||||
|
||||
export class Source {
|
||||
public static useLatestDev(sourceLanguagesStr?: string): Source {
|
||||
// Assume the versions are already loaded
|
||||
const versions = getNpmVersionsSync(undefined);
|
||||
const version = versions.find((v) => v.indexOf("-dev-") !== -1);
|
||||
return new Source(version, undefined, sourceLanguagesStr);
|
||||
}
|
||||
|
||||
public static useLatest(sourceLanguagesStr?: string): Source {
|
||||
return new Source(monacoEditorVersion, undefined, sourceLanguagesStr);
|
||||
}
|
||||
|
||||
public static parse(
|
||||
sourceStr: string | undefined,
|
||||
sourceLanguagesStr: string | undefined
|
||||
): Source {
|
||||
if (sourceStr === "latest-dev") {
|
||||
return Source.useLatestDev(sourceLanguagesStr);
|
||||
}
|
||||
if (sourceStr === "latest") {
|
||||
return Source.useLatest(sourceLanguagesStr);
|
||||
}
|
||||
|
||||
if (sourceStr && sourceStr.startsWith("v")) {
|
||||
return new Source(
|
||||
sourceStr.substring(1),
|
||||
undefined,
|
||||
sourceLanguagesStr
|
||||
);
|
||||
}
|
||||
return new Source(undefined, sourceStr, sourceLanguagesStr);
|
||||
}
|
||||
|
||||
public equals(other: Source | undefined): boolean {
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
return other.toString() === this.toString();
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly version: string | undefined,
|
||||
public readonly url: string | undefined,
|
||||
public readonly sourceLanguagesStr: string | undefined
|
||||
) {
|
||||
if (
|
||||
version === undefined &&
|
||||
url === undefined &&
|
||||
sourceLanguagesStr === undefined
|
||||
) {
|
||||
throw new Error("one parameter must be defined");
|
||||
}
|
||||
}
|
||||
|
||||
sourceToString(): string | undefined {
|
||||
if (this.url) {
|
||||
return this.url;
|
||||
}
|
||||
if (this.version) {
|
||||
return `v${this.version}`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
sourceLanguagesToString(): string | undefined {
|
||||
return this.sourceLanguagesStr;
|
||||
}
|
||||
|
||||
toString() {
|
||||
const sourceLangToStr = this.sourceLanguagesToString();
|
||||
return `${this.sourceToString()}${
|
||||
sourceLangToStr ? `;${sourceLangToStr}` : ""
|
||||
}`;
|
||||
}
|
||||
|
||||
public toPartialSettings(): Partial<Settings> {
|
||||
const languagesSettings: Partial<Settings> = {
|
||||
languagesSource:
|
||||
this.sourceLanguagesStr === undefined ? "latest" : "url",
|
||||
languagesUrl: this.sourceLanguagesStr,
|
||||
};
|
||||
|
||||
if (this.version) {
|
||||
return {
|
||||
monacoSource: "npm",
|
||||
npmVersion: this.version,
|
||||
};
|
||||
} else if (this.url) {
|
||||
return {
|
||||
monacoSource: "independent",
|
||||
coreSource: "url",
|
||||
coreUrl: this.url,
|
||||
...languagesSettings,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
monacoSource: "independent",
|
||||
coreSource: "latest",
|
||||
...languagesSettings,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { normalizeLineEnding } from "./utils";
|
||||
import { IPlaygroundProject } from "../../../shared";
|
||||
|
||||
export function findLastIndex<T>(
|
||||
array: T[],
|
||||
predicate: (value: T) => boolean
|
||||
): number {
|
||||
for (let i = array.length - 1; i >= 0; i--) {
|
||||
if (predicate(array[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
export function projectEquals(
|
||||
project1: IPlaygroundProject,
|
||||
project2: IPlaygroundProject
|
||||
): boolean {
|
||||
return (
|
||||
normalizeLineEnding(project1.css) ===
|
||||
normalizeLineEnding(project2.css) &&
|
||||
normalizeLineEnding(project1.html) ===
|
||||
normalizeLineEnding(project2.html) &&
|
||||
normalizeLineEnding(project1.js) === normalizeLineEnding(project2.js)
|
||||
);
|
||||
}
|
||||
export function normalizeLineEnding(str: string): string {
|
||||
return str.replace(/\r\n/g, "\n");
|
||||
}
|
Loading…
Reference in New Issue