Skip to content

Commit

Permalink
fix autoform + add demo
Browse files Browse the repository at this point in the history
  • Loading branch information
YousefED committed May 16, 2024
1 parent e9ef8e3 commit f22e086
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 84 deletions.
1 change: 1 addition & 0 deletions packages/editor/public/_docs/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"manual/3. Reactive variables.md",
"manual/4. Inputs.md",
"manual/5. Imports and NPM.md",
"manual/6. Plugins.md",
"README.md"
]
}
79 changes: 79 additions & 0 deletions packages/editor/public/_docs/manual/6. Plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Plugins

A powerful concept that TypeCell explores is _End User Programming_. In TypeCell, it's possible to customize the program you're using (a Notion-style document editor) with new capabilities, _right from within the application itself_. This means you can modify the way TypeCell works, without changing the source code, but just by creating new code in TypeCell code blocks.

## Map block

When using software like Notion, Google Docs, or Word, you're limited to the blocks they provide (paragraphs, images, lists, tables, etc.). What if you want to add an interactive map, or chart to your document? Let's explore how this can be done in TypeCell.

Let's first set up the code to render a Map, based on _react-map-gl_.

### Map code

First, let's set up some reactive variables for our map component:

```typescript
export let zoom = 1;
export let latitude = 1;
export let longitude = 1;
export let markers: Array<{
latitude: number;
longitude: number;
color: string;
text: string;
}> = [];
```

And import the required CSS stylesheet:

```typescript
import css from "maplibre-gl/dist/maplibre-gl.css";
export { css };
```

Now, let's create the main code that renders our map component:

```typescript
import MapLibre, { Marker, Source, Layer, Popup } from "react-map-gl/maplibre";
import maplibregl from "maplibre-gl";

export const map = (
<div style={{ width: 700, height: 400 }}>
<MapLibre
onMove={(e) => {
$.zoom = e.viewState.zoom;
$.latitude = e.viewState.latitude;
$.longitude = e.viewState.longitude;
}}
longitude={$.longitude}
latitude={$.latitude}
zoom={$.zoom}
mapStyle="https://demotiles.maplibre.org/style.json">
{$.markers.map((m, i) => (
<Marker
key={i}
latitude={m.latitude}
longitude={m.longitude}
color={m.color}
popup={m.text ? new maplibregl.Popup().setText(m.text) : undefined}
/>
))}
</MapLibre>
</div>
);
```

### Register a plugin

```typescript
// Plugin registration
typecell.editor.registerBlock({
name: "Map",
blockVariable: "map",
settings: {
latitude: true,
longitude: true,
zoom: true,
},
});
```
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ const PluginDialog = observer(

const enabledPlugins = [...props.document.plugins.keys()];

const enabledAndAvailablePlugins = enabledPlugins.filter((p) =>
availablePlugins.includes(p),
);

const oldPlugins = enabledPlugins.filter(
(p) => !availablePlugins.includes(p),
);
Expand All @@ -102,7 +106,7 @@ const PluginDialog = observer(
<ModalTitle>Plugins</ModalTitle>
</ModalHeader>
<ModalBody>
{enabledPlugins.map((plugin) => (
{enabledAndAvailablePlugins.map((plugin) => (
<div key={plugin}>
<PluginCheckbox
onChange={(val) => {
Expand Down
19 changes: 15 additions & 4 deletions packages/editor/src/store/DocumentResourceModelProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ type ModelProvider = {
models: BasicCodeModel[];
};

function textFromYXmlFragment(node: Y.XmlFragment): string {
let text = "";
node.forEach((c) => {
if (c instanceof Y.XmlElement && c.nodeName === "hardBreak") {
text += "\n";
} else if (c instanceof Y.XmlText) {
text += c.toString();
} else {
throw new Error("not a text or hardBreak node");
}
});
return text;
}

export class DocumentResourceModelProvider
extends lifecycle.Disposable
implements ModelProvider
Expand Down Expand Up @@ -52,10 +66,7 @@ export class DocumentResourceModelProvider
throw new Error("no id specified");
}

const code = node.firstChild;
if (!code || !(code instanceof Y.XmlText)) {
throw new Error("should be text");
}
const code = textFromYXmlFragment(node);

const attrLanguage = node.getAttribute("language");
if (!attrLanguage) {
Expand Down
10 changes: 5 additions & 5 deletions packages/editor/src/store/yjs-sync/DocumentCoordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ export class DocumentCoordinator extends lifecycle.Disposable {

if (!localDoc) {
// we expect loadDocument only to be called once per document
throw new Error("loadDocument: document already loaded");
throw new Error("deleteLocal: document not loaded");
}

await localDoc.idbProvider.clearData();
Expand All @@ -343,15 +343,15 @@ export class DocumentCoordinator extends lifecycle.Disposable {
throw new Error("not initialized");
}

const localDoc = this.loadedDocuments.get(idStr);
const localDoc = this.documents.get(idStr);

if (!localDoc) {
// we expect loadDocument only to be called once per document
throw new Error("markPlugins: document already loaded");
throw new Error("markPlugins: document not found");
}

localDoc.meta.has_plugins = value;
this.documents.set(localDoc.meta.id, { ...localDoc.meta });
localDoc.has_plugins = value;
this.documents.set(localDoc.id, { ...localDoc });
// console.log("plugins", JSON.stringify(localDoc.meta), value);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/engine/src/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ async function resolveDependencyArray(
userDisposes: Array<() => void>,
) {
const runContext = {
context,
onDispose: (disposer: () => void) => {
userDisposes.push(() => {
try {
Expand Down Expand Up @@ -47,6 +48,7 @@ async function resolveDependencyArray(
}

export type RunContext = {
context: TypeCellContext<any>;
onDispose: (disposer: () => void) => void;
};

Expand Down
20 changes: 19 additions & 1 deletion packages/frame/src/Frame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,13 +298,15 @@ export const Frame: React.FC<Props> = observer((props) => {
},
});

// enable / disable plugins for the document
useEffect(() => {
const pluginModels = new Map<string, BasicCodeModel>();

const observer = () => {
const plugins = document.ydoc.getMap("plugins");
const keys = [...plugins.keys()];
for (const key of keys) {
// create a hidden model for each plugin
const code = `import * as doc from "!${key.toString()}"; export default { doc };`;
const path = uri.URI.parse(
"file:///!plugins/" + key.toString() + ".cell.tsx",
Expand Down Expand Up @@ -332,7 +334,7 @@ export const Frame: React.FC<Props> = observer((props) => {
pluginModels.delete(key);
}
};
}, [document.ydoc, tools.newExecutionHost.engine]);
}, [document.ydoc, tools.newCompiler, tools.newExecutionHost.engine]);

const getSlashMenuItems = useCallback(
async (query: string) => {
Expand Down Expand Up @@ -361,6 +363,19 @@ export const Frame: React.FC<Props> = observer((props) => {
varName = origVarName + "_" + i;
}

const settingsPart = data.settings
? `
typecell.editor.registerBlockSettings({
content: (visible: boolean) => (
<typecell.AutoForm
inputObject={doc}
fields={${JSON.stringify(data.settings, undefined, 2)}}
visible={visible}
/>
),
});`
: "";

insertOrUpdateBlock(editor, {
type: "codeblock",
props: {
Expand All @@ -375,6 +390,8 @@ export const Frame: React.FC<Props> = observer((props) => {
export let ${varName} = doc.${data.blockVariable};
export let ${varName}Scope = doc;
${settingsPart}
export default ${varName};
`,
});
Expand Down Expand Up @@ -435,6 +452,7 @@ export const Frame: React.FC<Props> = observer((props) => {
<MonacoContext.Provider value={{ monaco }}>
<RichTextContext.Provider
value={{
editorStore: editorStore.current,
executionHost: tools.newExecutionHost,
compiler: tools.newCompiler,
documentId: props.documentIdString,
Expand Down
4 changes: 4 additions & 0 deletions packages/frame/src/RichTextContext.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { createContext } from "react";
import { EditorStore } from "./EditorStore";
import SourceModelCompiler from "./runtime/compiler/SourceModelCompiler";
import { ExecutionHost } from "./runtime/executor/executionHosts/ExecutionHost";

export const RichTextContext = createContext<{
editorStore: EditorStore;
executionHost: ExecutionHost;
compiler: SourceModelCompiler;
documentId: string;
}>({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
editorStore: undefined as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
executionHost: undefined as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
Loading

0 comments on commit f22e086

Please sign in to comment.