Merge branch 'main' into mdx

This commit is contained in:
Remco Haszing 2023-04-07 16:06:30 +02:00
commit ef3a98e0a2
No known key found for this signature in database
GPG key ID: 40D9F5FE9155FD3C
18 changed files with 4711 additions and 579 deletions

View file

@ -14,6 +14,8 @@ import { exit } from "process";
"yarn",
[
"tsc",
"--target",
"es6",
"--noEmit",
"--allowJs",
"--checkJs",

View file

@ -21,12 +21,24 @@ export interface IMonacoSetup {
monacoTypesUrl: string | undefined;
}
let loadMonacoPromise: Promise<typeof monaco> | undefined;
let loading = false;
let resolve: (value: typeof monaco) => void;
let reject: (error: unknown) => void;
let loadMonacoPromise = new Promise<typeof monaco>((res, rej) => {
resolve = res;
reject = rej;
});
export async function waitForLoadedMonaco(): Promise<typeof monaco> {
return loadMonacoPromise;
}
export async function loadMonaco(
setup: IMonacoSetup = prodMonacoSetup
): Promise<typeof monaco> {
if (!loadMonacoPromise) {
loadMonacoPromise = _loadMonaco(setup);
if (!loading) {
loading = true;
_loadMonaco(setup).then(resolve, reject);
}
return loadMonacoPromise;
}

View file

@ -0,0 +1,48 @@
// A fragment shader which lights textured geometry with point lights.
// Taken from the introduction of the WebGPU Shading Lnaguage Specification
// https://w3.org/TR/WGSL
// Lights from a storage buffer binding.
struct PointLight {
position : vec3f,
color : vec3f,
}
struct LightStorage {
pointCount : u32,
point : array<PointLight>,
}
@group(0) @binding(0) var<storage> lights : LightStorage;
// Texture and sampler.
@group(1) @binding(0) var baseColorSampler : sampler;
@group(1) @binding(1) var baseColorTexture : texture_2d<f32>;
// Function arguments are values from from vertex shader.
@fragment
fn fragmentMain(@location(0) worldPos : vec3f,
@location(1) normal : vec3f,
@location(2) uv : vec2f) -> @location(0) vec4f {
// Sample the base color of the surface from a texture.
let baseColor = textureSample(baseColorTexture, baseColorSampler, uv);
let N = normalize(normal);
var surfaceColor = vec3f(0);
// Loop over the scene point lights.
for (var i = 0u; i < lights.pointCount; i++) {
let worldToLight = lights.point[i].position - worldPos;
let dist = length(worldToLight);
let dir = normalize(worldToLight);
// Determine the contribution of this light to the surface color.
let radiance = lights.point[i].color * (1 / pow(dist, 2));
let nDotL = max(dot(N, dir), 0);
// Accumulate light contribution to the surface color.
surfaceColor += baseColor.rgb * radiance * nDotL;
}
// Return the accumulated surface color.
return vec4(surfaceColor, baseColor.a);
}

View file

@ -11,10 +11,12 @@ customElements.define(
const shadowRoot = this.attachShadow({ mode: "open" });
// Copy over editor styles
const style = document.querySelector(
"link[rel='stylesheet'][data-name='vs/editor/editor.main']"
const styles = document.querySelectorAll(
"link[rel='stylesheet'][data-name^='vs/']"
);
shadowRoot.appendChild(style.cloneNode(true));
for (const style of styles) {
shadowRoot.appendChild(style.cloneNode(true));
}
const template = /** @type HTMLTemplateElement */ (
document.getElementById("editor-template")

View file

@ -12,7 +12,11 @@ import {
reaction,
runInAction,
} from "mobx";
import { IMonacoSetup, loadMonaco } from "../../../monaco-loader";
import {
IMonacoSetup,
loadMonaco,
waitForLoadedMonaco,
} from "../../../monaco-loader";
import { IPlaygroundProject, IPreviewState } from "../../../shared";
import { monacoEditorVersion } from "../../monacoEditorVersion";
import { Debouncer } from "../../utils/Debouncer";
@ -56,12 +60,23 @@ export class PlaygroundModel {
public readonly serializer = new StateUrlSerializer(this);
reload(): void {
public reload(): void {
this.reloadKey++;
}
private readonly _previewHandlers = new Set<IPreviewHandler>();
private _wasEverNonFullScreen = false;
public get wasEverNonFullScreen() {
if (this._wasEverNonFullScreen) {
return true;
}
if (!this.settings.previewFullScreen) {
this._wasEverNonFullScreen = true;
}
return this._wasEverNonFullScreen;
}
@computed.struct
get monacoSetup(): IMonacoSetup {
const sourceOverride = this.serializer.sourceOverride;
@ -125,14 +140,31 @@ export class PlaygroundModel {
}
}
private readonly debouncer = new Debouncer(250);
private readonly debouncer = new Debouncer(700);
@observable
public isDirty = false;
constructor() {
let lastState = this.state;
this.dispose.track({
dispose: reaction(
() => ({ state: this.state }),
({ state }) => {
if (!this.settings.autoReload) {
if (
JSON.stringify(state.monacoSetup) ===
JSON.stringify(lastState.monacoSetup) &&
state.key === lastState.key
) {
this.isDirty = true;
return;
}
}
this.debouncer.run(() => {
this.isDirty = false;
lastState = state;
for (const handler of this._previewHandlers) {
handler.handlePreview(state);
}
@ -142,10 +174,10 @@ export class PlaygroundModel {
),
});
const observablePromise = new ObservablePromise(loadMonaco());
const observablePromise = new ObservablePromise(waitForLoadedMonaco());
let disposable: Disposable | undefined = undefined;
loadMonaco().then((m) => {
waitForLoadedMonaco().then((m) => {
const options =
monaco.languages.typescript.javascriptDefaults.getCompilerOptions();
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions(

View file

@ -18,7 +18,7 @@ import { PlaygroundModel } from "./PlaygroundModel";
import { Preview } from "./Preview";
import { SettingsDialog } from "./SettingsDialog";
import { Button, Col, Row, Stack } from "../../components/bootstrap";
import { ButtonGroup } from "react-bootstrap";
import { ButtonGroup, FormCheck } from "react-bootstrap";
@hotComponent(module)
@observer
@ -37,88 +37,122 @@ export class PlaygroundPageContent extends React.Component<
className="h-100 g-2"
style={{ flexWrap: "wrap-reverse" }}
>
<Col
md
className={
model.settings.previewFullScreen ? "d-none" : ""
}
>
<Vertical>
<div style={{ flex: 1 }}>
<LabeledEditor
label="JavaScript"
titleBarItems={
<div
className="hstack"
style={{ marginLeft: "auto" }}
>
<span
style={{ marginRight: 8 }}
{model.wasEverNonFullScreen && (
<Col
md
className={
model.settings.previewFullScreen
? "d-none"
: ""
}
>
<Vertical>
<div style={{ flex: 1 }}>
<LabeledEditor
label="JavaScript"
titleBarItems={
<div
className="hstack"
style={{
marginLeft: "auto",
}}
>
Example:
</span>
<Select<PlaygroundExample>
values={getPlaygroundExamples().map(
(e) => ({
groupTitle:
e.chapterTitle,
items: e.examples,
})
)}
value={ref(
model,
"selectedExample"
)}
getLabel={(i) => i.title}
/>
</div>
}
>
<Editor
language={"javascript"}
value={ref(model, "js")}
/>
</LabeledEditor>
</div>
<span
style={{
marginRight: 8,
}}
>
Example:
</span>
<Select<PlaygroundExample>
values={getPlaygroundExamples().map(
(e) => ({
groupTitle:
e.chapterTitle,
items: e.examples,
})
)}
value={ref(
model,
"selectedExample"
)}
getLabel={(i) =>
i.title
}
/>
</div>
}
>
<Editor
language={"javascript"}
value={ref(model, "js")}
/>
</LabeledEditor>
</div>
<div>
<LabeledEditor label="HTML">
<Editor
height={{
kind: "dynamic",
maxHeight: 200,
}}
language={"html"}
value={ref(model, "html")}
/>
</LabeledEditor>
</div>
<div>
<LabeledEditor label="HTML">
<Editor
height={{
kind: "dynamic",
maxHeight: 200,
}}
language={"html"}
value={ref(model, "html")}
/>
</LabeledEditor>
</div>
<div>
<LabeledEditor label="CSS">
<Editor
height={{
kind: "dynamic",
maxHeight: 200,
}}
language={"css"}
value={ref(model, "css")}
/>
</LabeledEditor>
</div>
</Vertical>
</Col>
<div>
<LabeledEditor label="CSS">
<Editor
height={{
kind: "dynamic",
maxHeight: 200,
}}
language={"css"}
value={ref(model, "css")}
/>
</LabeledEditor>
</div>
</Vertical>
</Col>
)}
<Col md>
<LabeledEditor
label="Preview"
titleBarItems={
<div
style={{ marginLeft: "auto" }}
className="d-flex gap-2"
className="d-flex gap-2 align-items-center"
>
{model.settings.previewFullScreen || (
<FormCheck
label="Auto-Reload"
className="text-nowrap"
checked={
model.settings.autoReload
}
onChange={(e) => {
model.settings.autoReload =
e.target.checked;
if (
e.target.checked &&
model.isDirty
) {
model.reload();
}
}}
/>
)}
<Button
type="button"
className="btn btn-light settings bi-arrow-clockwise"
className={
"btn settings bi-arrow-clockwise " +
(model.isDirty
? "btn-primary"
: "btn-light")
}
style={{
fontSize: 20,
padding: "0px 4px",

View file

@ -33,6 +33,14 @@ export class SettingsModel {
this.setSettings({ ...this._settings, previewFullScreen: value });
}
get autoReload() {
return this._settings.autoReload ?? true;
}
set autoReload(value: boolean) {
this.setSettings({ ...this._settings, autoReload: value });
}
constructor() {
const settingsStr = localStorage.getItem(this.settingsKey);
if (settingsStr) {
@ -70,6 +78,7 @@ export interface Settings {
customConfig: JsonString<IMonacoSetup>;
previewFullScreen: boolean;
autoReload: boolean | undefined;
}
export type JsonString<T> = string;
@ -167,6 +176,7 @@ export function getDefaultSettings(): Settings {
loaderPathsConfig: "",
}),
previewFullScreen: false,
autoReload: true,
};
return defaultSettings;
}