Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
692 changes: 268 additions & 424 deletions typescript/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions typescript/packages/common-os-ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<os-icon-button slot="toolbar-end" icon="info"></os-icon-button>
<os-icon-button slot="toolbar-end" icon="palette"></os-icon-button>
<os-sidebar-close-button slot="toolbar-end"></os-sidebar-close-button>
<os-code-editor></os-code-editor>
<os-charm-row-group>
<os-charm-row icon="mail" text="Mail"></os-charm-row>
<os-charm-row icon="calendar_month" text="Calendar"> </os-charm-row>
Expand Down
10 changes: 10 additions & 0 deletions typescript/packages/common-os-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,17 @@
"./lib/index.js"
],
"dependencies": {
"@codemirror/lang-css": "^6.3.0",
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-markdown": "^6.3.0",
"@codemirror/search": "^6.5.6",
"@codemirror/state": "^6.4.1",
"@codemirror/theme-one-dark": "^6.1.2",
"@commontools/common-os-ui": "file:",
"@floating-ui/dom": "^1.6.11",
"codemirror": "^6.0.1",
"lit": "^3.2.0",
"prosemirror-commands": "^1.6.0",
"prosemirror-history": "^1.4.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { EditorState } from "@codemirror/state";
import { EditorView } from "codemirror";

export const replaceSource = (state: EditorState, value: string) =>
state.update({
changes: {
from: 0,
to: state.doc.length,
insert: value,
},
});

/** Replace the source in this editor view, but only if it's different */
export const replaceSourceIfNeeded = (view: EditorView, value: string) => {
if (view.state.doc.toString() === value) return;
view.update([
view.state.update({
changes: {
from: 0,
to: view.state.doc.length,
insert: value,
},
}),
]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { css, html, render, ReactiveElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators.js";
import { basicSetup, EditorView } from "codemirror";
import { EditorState, Compartment, Extension } from "@codemirror/state";
import { LanguageSupport } from "@codemirror/language";
import { javascript as createJavaScript } from "@codemirror/lang-javascript";
import { markdown as createMarkdown } from "@codemirror/lang-markdown";
import { css as createCss } from "@codemirror/lang-css";
import { html as creatHtml } from "@codemirror/lang-html";
import { json as createJson } from "@codemirror/lang-json";
import { oneDark } from "@codemirror/theme-one-dark";
import { replaceSourceIfNeeded } from "./codemirror/utils.js";
import { createCancelGroup } from "../../shared/cancel.js";

const freeze = Object.freeze;

export const MimeType = freeze({
css: "text/css",
html: "text/html",
javascript: "text/javascript",
jsx: "text/x.jsx",
typescript: "text/x.typescript",
json: "application/json",
markdown: "text/markdown",
} as const);

export type MimeType = (typeof MimeType)[keyof typeof MimeType];

export const langRegistry = new Map<MimeType, LanguageSupport>();
const markdownLang = createMarkdown({
defaultCodeLanguage: createJavaScript({ jsx: true }),
});
const defaultLang = markdownLang;

langRegistry.set(MimeType.javascript, createJavaScript());
langRegistry.set(
MimeType.jsx,
createJavaScript({
jsx: true,
}),
);
langRegistry.set(
MimeType.typescript,
createJavaScript({
jsx: true,
typescript: true,
}),
);
langRegistry.set(MimeType.css, createCss());
langRegistry.set(MimeType.html, creatHtml());
langRegistry.set(MimeType.markdown, markdownLang);
langRegistry.set(MimeType.json, createJson());

export const getLangExtFromMimeType = (mime: MimeType) => {
return langRegistry.get(mime) ?? defaultLang;
};

export const createEditor = ({
element,
extensions = [],
}: {
element: HTMLElement;
extensions?: Array<Extension>;
}) => {
const state = EditorState.create({
extensions: [basicSetup, oneDark, ...extensions],
});

return new EditorView({
state,
parent: element,
});
};

@customElement("os-code-editor")
export class OsCodeEditor extends ReactiveElement {
static styles = [
css`
:host {
display: block;
}

.code-editor {
display: block;
}

.cm-editor.cm-focused {
outline: none;
}
`,
];

#editorView: EditorView | undefined = undefined;
#lang = new Compartment();
#tabSize = new Compartment();

destroy = createCancelGroup();

@property({ type: String })
source = "";

@property({ type: String })
lang = MimeType.markdown;

get editor(): EditorState | undefined {
return this.#editorView?.state;
}

set editor(state: EditorState) {
this.#editorView?.setState(state);
}

protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
// Set up skeleton
// - #editor is managed by ProseMirror
// - #reactive is rendered via Lit templates and driven by store updates
render(html`<div id="editor" class="code-editor"></div>`, this.renderRoot);
const editorRoot = this.renderRoot.querySelector("#editor") as HTMLElement;

this.#editorView = createEditor({
element: editorRoot,
extensions: [
this.#lang.of(defaultLang),
this.#tabSize.of(EditorState.tabSize.of(4)),
],
});
this.destroy.add(() => this.#editorView?.destroy());
}

protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("source")) {
replaceSourceIfNeeded(this.#editorView!, this.source);
}
if (changedProperties.has("lang")) {
const lang = getLangExtFromMimeType(this.lang);
this.#editorView?.dispatch({
effects: this.#lang.reconfigure(lang),
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
Store,
ValueMsg,
} from "../../shared/store.js";
import { createCleanupGroup } from "../../shared/cleanup.js";
import { createCancelGroup } from "../../shared/cancel.js";
import { TemplateResult } from "lit";
import { classes, on } from "../../shared/dom.js";
import { ClickCompletion } from "../os-floating-completions.js";
Expand Down Expand Up @@ -308,7 +308,7 @@ export class OsRichTextEditor extends HTMLElement {
`,
];

#destroy = createCleanupGroup();
destroy = createCancelGroup();
#store: Store<Model, Msg>;
#editorView: EditorView;
#reactiveRoot: HTMLElement;
Expand Down Expand Up @@ -343,7 +343,7 @@ export class OsRichTextEditor extends HTMLElement {
element: editorRoot,
send: (msg: Msg) => this.#store.send(msg),
});
this.#destroy.add(() => {
this.destroy.add(() => {
this.#editorView.destroy();
});

Expand All @@ -352,7 +352,7 @@ export class OsRichTextEditor extends HTMLElement {
const event = new EditorStateChangeEvent(this.#editorView.state);
this.dispatchEvent(event);
});
this.#destroy.add(offInput);
this.destroy.add(offInput);

// Create fx driver
const fx = createFx({
Expand All @@ -368,11 +368,11 @@ export class OsRichTextEditor extends HTMLElement {
});

// Drive #reactive renders via store changes
const cleanupRender = this.#store.sink(() => {
const cancelRender = this.#store.sink(() => {
// Wire up reactive rendering
render(this.render(), this.#reactiveRoot);
});
this.#destroy.add(cleanupRender);
this.destroy.add(cancelRender);
}

get editor() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class OsContainer extends LitElement {
.container {
max-width: var(--container-width);
margin: 0 auto;
padding: var(--pad);
padding: 0 var(--pad);
}
`,
];
Expand Down
1 change: 1 addition & 0 deletions typescript/packages/common-os-ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export * as charmRow from "./components/os-charm-row.js";
export * as charmChip from "./components/os-charm-chip.js";
export * as dialog from "./components/os-dialog.js";
export * as richTextEditor from "./components/editor/os-rich-text-editor.js";
export * as codeEditor from "./components/code-editor/os-code-editor.js";
export * as floatingCompletions from "./components/os-floating-completions.js";
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { createCleanupGroup } from "./cleanup.js";
import { createCancelGroup } from "./cancel.js";
import * as assert from "node:assert/strict";

describe("cleanupGroup", () => {
it("should create a cleanup group with add and cleanup methods", () => {
const group = createCleanupGroup();
const group = createCancelGroup();
assert.equal(typeof group.add, "function");
assert.equal(typeof group.cleanup, "function");
assert.equal(typeof group, "function");
});

it("should execute added cleanup functions when cleanup is called", () => {
const group = createCleanupGroup();
const group = createCancelGroup();
let count = 0;

group.add(() => {
Expand All @@ -19,13 +19,13 @@ describe("cleanupGroup", () => {
count++;
});

group.cleanup();
group();

assert.equal(count, 2);
});

it("should not execute cleanup functions more than once", () => {
const group = createCleanupGroup();
const group = createCancelGroup();
let count = 0;

group.add(() => {
Expand All @@ -35,31 +35,31 @@ describe("cleanupGroup", () => {
count++;
});

group.cleanup();
group.cleanup();
group();
group();

assert.equal(count, 2);
});

it("should allow adding cleanup functions after cleanup has been called", () => {
const group = createCleanupGroup();
const group = createCancelGroup();
let count = 0;

group.add(() => {
count++;
});
group.cleanup();
group();

group.add(() => {
count++;
});
group.cleanup();
group();

assert.equal(count, 2);
});

it("should handle empty cleanup group", () => {
const group = createCleanupGroup();
assert.doesNotThrow(() => group.cleanup());
const group = createCancelGroup();
assert.doesNotThrow(() => group());
});
});
18 changes: 18 additions & 0 deletions typescript/packages/common-os-ui/src/shared/cancel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export type Cancel = () => void;

export const createCancelGroup = () => {
const cancels: Set<Cancel> = new Set();

const cancel = () => {
for (const cancel of cancels) {
cancel();
}
cancels.clear();
};

cancel.add = (cancel: Cancel) => {
cancels.add(cancel);
};

return cancel;
};
18 changes: 0 additions & 18 deletions typescript/packages/common-os-ui/src/shared/cleanup.ts

This file was deleted.

1 change: 0 additions & 1 deletion typescript/packages/lookslike-prototype/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
"dependencies": {
"@babel/parser": "^7.24.6",
"@bytecodealliance/preview2-shim": "^0.16.2",
"@codemirror/basic-setup": "^0.20.0",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.10.2",
Expand Down