@jtfmumm/patchwork-standalone-frame 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/frame.d.ts +3 -2
- package/dist/index.d.ts +40 -0
- package/dist/index.js +439 -401
- package/dist/mount.d.ts +2 -2
- package/package.json +6 -1
- package/src/frame.tsx +125 -99
- package/src/index.ts +47 -0
- package/src/mount.tsx +43 -5
package/dist/mount.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { ToolRegistration } from "./index.ts";
|
|
2
|
-
export declare function mountStandaloneApp<D>(rootElement: HTMLElement,
|
|
1
|
+
import type { ToolRegistration, StandaloneFrameConfig, Plugin } from "./index.ts";
|
|
2
|
+
export declare function mountStandaloneApp<D>(rootElement: HTMLElement, toolOrPlugins: ToolRegistration<D> | Plugin<D>[], config?: StandaloneFrameConfig): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jtfmumm/patchwork-standalone-frame",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Reusable standalone frame for patchwork tools with keyhive, doc history, and access control",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
"@automerge/automerge-repo-storage-indexeddb": "^2.5.2-alpha.0",
|
|
23
23
|
"solid-js": "^1.9.9"
|
|
24
24
|
},
|
|
25
|
+
"peerDependenciesMeta": {
|
|
26
|
+
"@automerge/automerge-repo-keyhive": {
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
29
|
+
},
|
|
25
30
|
"devDependencies": {
|
|
26
31
|
"@automerge/automerge-repo": "^2.5.2-alpha.0",
|
|
27
32
|
"@automerge/automerge-repo-keyhive": "0.1.0-alpha.17za",
|
package/src/frame.tsx
CHANGED
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
import { createSignal, Show, For, onMount, onCleanup } from "solid-js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type DocHandle,
|
|
6
|
-
} from "@automerge/automerge-repo";
|
|
7
|
-
import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb";
|
|
8
|
-
import { BrowserWebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket";
|
|
9
|
-
import {
|
|
10
|
-
initializeAutomergeRepoKeyhive,
|
|
11
|
-
initKeyhiveWasm,
|
|
12
|
-
docIdFromAutomergeUrl,
|
|
13
|
-
Identifier,
|
|
14
|
-
uint8ArrayToHex,
|
|
15
|
-
type AutomergeRepoKeyhive,
|
|
16
|
-
} from "@automerge/automerge-repo-keyhive";
|
|
17
|
-
import type { ToolRegistration, ToolElement } from "./index.ts";
|
|
2
|
+
import type { AutomergeUrl, DocHandle } from "@automerge/automerge-repo";
|
|
3
|
+
import type { AutomergeRepoKeyhive } from "@automerge/automerge-repo-keyhive";
|
|
4
|
+
import type { ToolRegistration, ToolElement, StandaloneFrameConfig, FrameRepo, FrameDocHandle } from "./index.ts";
|
|
18
5
|
import {
|
|
19
6
|
type DocHistoryEntry,
|
|
20
7
|
loadHistory,
|
|
@@ -28,19 +15,16 @@ import { ShareModal } from "./share-modal.tsx";
|
|
|
28
15
|
import { ConfirmModal } from "./confirm-modal.tsx";
|
|
29
16
|
import { NewDocModal } from "./new-doc-modal.tsx";
|
|
30
17
|
|
|
31
|
-
// Init keyhive WASM at module level (must happen before initializeAutomergeRepoKeyhive)
|
|
32
|
-
initKeyhiveWasm();
|
|
33
|
-
|
|
34
18
|
declare global {
|
|
35
19
|
interface Window {
|
|
36
20
|
hive?: AutomergeRepoKeyhive;
|
|
37
21
|
}
|
|
38
22
|
}
|
|
39
23
|
|
|
40
|
-
export function StandaloneApp<D>(props: { tool: ToolRegistration<D
|
|
24
|
+
export function StandaloneApp<D>(props: { tool: ToolRegistration<D>; config?: StandaloneFrameConfig }) {
|
|
41
25
|
const tool = props.tool;
|
|
42
26
|
|
|
43
|
-
const [handle, setHandle] = createSignal<
|
|
27
|
+
const [handle, setHandle] = createSignal<FrameDocHandle<D> | null>(null);
|
|
44
28
|
const [docTitle, setDocTitle] = createSignal<string>("...");
|
|
45
29
|
const [history, setHistory] = createSignal<DocHistoryEntry[]>([]);
|
|
46
30
|
const [dropdownOpen, setDropdownOpen] = createSignal(false);
|
|
@@ -56,11 +40,11 @@ export function StandaloneApp<D>(props: { tool: ToolRegistration<D> }) {
|
|
|
56
40
|
const [pendingDocUrl, setPendingDocUrl] = createSignal<string | null>(null);
|
|
57
41
|
const [loading, setLoading] = createSignal(false);
|
|
58
42
|
|
|
59
|
-
let repo!:
|
|
43
|
+
let repo!: FrameRepo;
|
|
60
44
|
let titleCleanup: (() => void) | null = null;
|
|
61
45
|
let loadGeneration = 0;
|
|
62
46
|
|
|
63
|
-
function trackTitle(h:
|
|
47
|
+
function trackTitle(h: FrameDocHandle<D>): void {
|
|
64
48
|
if (titleCleanup) {
|
|
65
49
|
titleCleanup();
|
|
66
50
|
titleCleanup = null;
|
|
@@ -87,6 +71,7 @@ export function StandaloneApp<D>(props: { tool: ToolRegistration<D> }) {
|
|
|
87
71
|
const h = hive();
|
|
88
72
|
if (!h) return true;
|
|
89
73
|
try {
|
|
74
|
+
const { docIdFromAutomergeUrl, Identifier } = await import("@automerge/automerge-repo-keyhive");
|
|
90
75
|
const docId = docIdFromAutomergeUrl(url as AutomergeUrl);
|
|
91
76
|
const myId = h.active.individual.id;
|
|
92
77
|
const publicId = Identifier.publicId();
|
|
@@ -124,23 +109,23 @@ export function StandaloneApp<D>(props: { tool: ToolRegistration<D> }) {
|
|
|
124
109
|
}
|
|
125
110
|
if (gen !== loadGeneration) return;
|
|
126
111
|
|
|
127
|
-
const isDocReady = (h:
|
|
112
|
+
const isDocReady = (h: FrameDocHandle<D>) => {
|
|
128
113
|
const d = h.doc() as D;
|
|
129
114
|
if (!d) return false;
|
|
130
115
|
return tool.isDocReady ? tool.isDocReady(d) : !!d;
|
|
131
116
|
};
|
|
132
117
|
|
|
133
|
-
let docHandle =
|
|
118
|
+
let docHandle = repo.find<D>(url);
|
|
134
119
|
await docHandle.whenReady();
|
|
135
120
|
if (gen !== loadGeneration) return;
|
|
136
121
|
|
|
137
122
|
if (!isDocReady(docHandle)) {
|
|
138
123
|
console.log(`[${tool.name}] Doc incomplete, forcing re-sync for: ${url.slice(0, 30)}...`);
|
|
139
|
-
try { repo.delete(url
|
|
124
|
+
try { repo.delete(url); } catch { /* ignore */ }
|
|
140
125
|
await new Promise((r) => setTimeout(r, 100));
|
|
141
126
|
if (gen !== loadGeneration) return;
|
|
142
127
|
|
|
143
|
-
docHandle =
|
|
128
|
+
docHandle = repo.find<D>(url);
|
|
144
129
|
await docHandle.whenReady();
|
|
145
130
|
if (gen !== loadGeneration) return;
|
|
146
131
|
}
|
|
@@ -177,21 +162,25 @@ export function StandaloneApp<D>(props: { tool: ToolRegistration<D> }) {
|
|
|
177
162
|
}
|
|
178
163
|
|
|
179
164
|
async function createNewDoc(title: string = tool.defaultTitle): Promise<void> {
|
|
180
|
-
const h = hive();
|
|
181
|
-
if (!h) return;
|
|
182
|
-
|
|
183
165
|
const initialDoc = {} as Record<string, unknown>;
|
|
184
|
-
tool.init(initialDoc as D, repo);
|
|
166
|
+
tool.init(initialDoc as D, repo as unknown as Parameters<typeof tool.init>[1]);
|
|
185
167
|
if (tool.setTitle) tool.setTitle(initialDoc as D, title);
|
|
186
168
|
initialDoc["@patchwork"] = { type: tool.id };
|
|
187
|
-
const docHandle = await repo.create2<D>(initialDoc as D);
|
|
188
|
-
console.log(`[${tool.name}] Created document: ${docHandle.url}`);
|
|
189
169
|
|
|
190
|
-
|
|
191
|
-
|
|
170
|
+
const h = hive();
|
|
171
|
+
let docHandle: FrameDocHandle<D>;
|
|
172
|
+
if (h) {
|
|
173
|
+
docHandle = await (repo as unknown as { create2<T>(v: T): Promise<FrameDocHandle<T>> }).create2<D>(initialDoc as D);
|
|
174
|
+
console.log(`[${tool.name}] Created document (keyhive): ${docHandle.url}`);
|
|
175
|
+
await h.addSyncServerPullToDoc(docHandle.url as AutomergeUrl);
|
|
176
|
+
await h.keyhiveStorage.saveKeyhiveWithHash(h.keyhive);
|
|
177
|
+
} else {
|
|
178
|
+
docHandle = repo.create<D>(initialDoc as D);
|
|
179
|
+
console.log(`[${tool.name}] Created document (legacy): ${docHandle.url}`);
|
|
180
|
+
}
|
|
192
181
|
|
|
193
182
|
setHandle(docHandle);
|
|
194
|
-
setDocUrlInHash(docHandle.url);
|
|
183
|
+
setDocUrlInHash(docHandle.url as AutomergeUrl);
|
|
195
184
|
localStorage.setItem(activeDocKey(tool.id, identityHexId()), docHandle.url);
|
|
196
185
|
trackTitle(docHandle);
|
|
197
186
|
}
|
|
@@ -232,67 +221,102 @@ export function StandaloneApp<D>(props: { tool: ToolRegistration<D> }) {
|
|
|
232
221
|
}
|
|
233
222
|
|
|
234
223
|
onMount(async () => {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
onlyShareWithHardcodedServerPeerId: true,
|
|
248
|
-
cacheHashes: true,
|
|
249
|
-
});
|
|
250
|
-
window.hive = automergeRepoKeyhive;
|
|
251
|
-
|
|
252
|
-
repo = new Repo({
|
|
253
|
-
storage: new IndexedDBStorageAdapter(),
|
|
254
|
-
network: [automergeRepoKeyhive.networkAdapter],
|
|
255
|
-
peerId: automergeRepoKeyhive.peerId,
|
|
256
|
-
sharePolicy: async (peerId) => {
|
|
257
|
-
return peerId === automergeRepoKeyhive.syncServer?.peerId;
|
|
258
|
-
},
|
|
259
|
-
idFactory: automergeRepoKeyhive.idFactory,
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
automergeRepoKeyhive.linkRepo(repo);
|
|
263
|
-
setHive(automergeRepoKeyhive);
|
|
264
|
-
|
|
265
|
-
const hexId = uint8ArrayToHex(
|
|
266
|
-
automergeRepoKeyhive.active.individual.id.toBytes()
|
|
267
|
-
);
|
|
268
|
-
setIdentityHexId(hexId);
|
|
269
|
-
setHistory(loadHistory(tool.id, hexId));
|
|
270
|
-
setReady(true);
|
|
271
|
-
|
|
272
|
-
console.log(`[${tool.name}] Keyhive initialized, identity: 0x${hexId.slice(0, 12)}...`);
|
|
273
|
-
|
|
274
|
-
(automergeRepoKeyhive.networkAdapter as { on: (event: string, cb: () => void) => void }).on("ingest-remote", async () => {
|
|
275
|
-
const pending = pendingDocUrl();
|
|
276
|
-
if (pending) {
|
|
277
|
-
if (await checkAccess(pending)) {
|
|
278
|
-
console.log(`[${tool.name}] Access granted, loading: ${pending}`);
|
|
279
|
-
await loadDoc(pending);
|
|
280
|
-
}
|
|
281
|
-
return;
|
|
224
|
+
if (props.config?.legacyMode) {
|
|
225
|
+
// Legacy path: use the passed-in repo, no automerge-repo or keyhive imports
|
|
226
|
+
repo = props.config.repo!;
|
|
227
|
+
|
|
228
|
+
// Generate a stable identity hex ID for doc history via localStorage
|
|
229
|
+
const storageKey = `standalone-frame-identity-${tool.id}`;
|
|
230
|
+
let hexId = localStorage.getItem(storageKey);
|
|
231
|
+
if (!hexId) {
|
|
232
|
+
const bytes = new Uint8Array(16);
|
|
233
|
+
crypto.getRandomValues(bytes);
|
|
234
|
+
hexId = Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
|
|
235
|
+
localStorage.setItem(storageKey, hexId);
|
|
282
236
|
}
|
|
237
|
+
setIdentityHexId(hexId);
|
|
238
|
+
setHistory(loadHistory(tool.id, hexId));
|
|
239
|
+
setReady(true);
|
|
240
|
+
|
|
241
|
+
console.log(`[${tool.name}] Legacy mode initialized, identity: 0x${hexId.slice(0, 12)}...`);
|
|
242
|
+
} else {
|
|
243
|
+
// Keyhive path (default): dynamic import everything
|
|
244
|
+
const [
|
|
245
|
+
{ Repo },
|
|
246
|
+
{ IndexedDBStorageAdapter },
|
|
247
|
+
{ BrowserWebSocketClientAdapter },
|
|
248
|
+
keyhive,
|
|
249
|
+
] = await Promise.all([
|
|
250
|
+
import("@automerge/automerge-repo"),
|
|
251
|
+
import("@automerge/automerge-repo-storage-indexeddb"),
|
|
252
|
+
import("@automerge/automerge-repo-network-websocket"),
|
|
253
|
+
import("@automerge/automerge-repo-keyhive"),
|
|
254
|
+
]);
|
|
255
|
+
keyhive.initKeyhiveWasm();
|
|
256
|
+
|
|
257
|
+
const keyhiveStorage = new IndexedDBStorageAdapter(`${tool.id}-keyhive`);
|
|
258
|
+
const envSyncUrl = (import.meta as unknown as Record<string, Record<string, string>>).env?.VITE_SYNC_URL;
|
|
259
|
+
const networkAdapter = new BrowserWebSocketClientAdapter(
|
|
260
|
+
envSyncUrl || tool.syncUrl || "ws://localhost:3030"
|
|
261
|
+
);
|
|
262
|
+
const peerIdSuffix = `${tool.id}-${Math.random().toString(36).slice(2)}`;
|
|
263
|
+
|
|
264
|
+
const automergeRepoKeyhive = await keyhive.initializeAutomergeRepoKeyhive({
|
|
265
|
+
storage: keyhiveStorage,
|
|
266
|
+
peerIdSuffix,
|
|
267
|
+
networkAdapter,
|
|
268
|
+
automaticArchiveIngestion: true,
|
|
269
|
+
onlyShareWithHardcodedServerPeerId: true,
|
|
270
|
+
cacheHashes: true,
|
|
271
|
+
});
|
|
272
|
+
window.hive = automergeRepoKeyhive;
|
|
273
|
+
|
|
274
|
+
const realRepo = new Repo({
|
|
275
|
+
storage: new IndexedDBStorageAdapter(),
|
|
276
|
+
network: [automergeRepoKeyhive.networkAdapter],
|
|
277
|
+
peerId: automergeRepoKeyhive.peerId,
|
|
278
|
+
sharePolicy: async (peerId) => {
|
|
279
|
+
return peerId === automergeRepoKeyhive.syncServer?.peerId;
|
|
280
|
+
},
|
|
281
|
+
idFactory: automergeRepoKeyhive.idFactory,
|
|
282
|
+
});
|
|
283
|
+
repo = realRepo as unknown as FrameRepo;
|
|
284
|
+
|
|
285
|
+
automergeRepoKeyhive.linkRepo(realRepo);
|
|
286
|
+
setHive(automergeRepoKeyhive);
|
|
287
|
+
|
|
288
|
+
const hexId = keyhive.uint8ArrayToHex(
|
|
289
|
+
automergeRepoKeyhive.active.individual.id.toBytes()
|
|
290
|
+
);
|
|
291
|
+
setIdentityHexId(hexId);
|
|
292
|
+
setHistory(loadHistory(tool.id, hexId));
|
|
293
|
+
setReady(true);
|
|
294
|
+
|
|
295
|
+
console.log(`[${tool.name}] Keyhive initialized, identity: 0x${hexId.slice(0, 12)}...`);
|
|
296
|
+
|
|
297
|
+
(automergeRepoKeyhive.networkAdapter as { on: (event: string, cb: () => void) => void }).on("ingest-remote", async () => {
|
|
298
|
+
const pending = pendingDocUrl();
|
|
299
|
+
if (pending) {
|
|
300
|
+
if (await checkAccess(pending)) {
|
|
301
|
+
console.log(`[${tool.name}] Access granted, loading: ${pending}`);
|
|
302
|
+
await loadDoc(pending);
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
283
306
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
307
|
+
const h = handle();
|
|
308
|
+
if (h) {
|
|
309
|
+
if (!(await checkAccess(h.url))) {
|
|
310
|
+
console.log(`[${tool.name}] Access revoked for: ${h.url}`);
|
|
311
|
+
if (titleCleanup) { titleCleanup(); titleCleanup = null; }
|
|
312
|
+
setHandle(null);
|
|
313
|
+
setDocTitle("...");
|
|
314
|
+
setDocUnavailable(true);
|
|
315
|
+
setPendingDocUrl(h.url);
|
|
316
|
+
}
|
|
293
317
|
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
296
320
|
|
|
297
321
|
const hashUrl = getDocUrlFromHash();
|
|
298
322
|
if (hashUrl) {
|
|
@@ -301,7 +325,7 @@ export function StandaloneApp<D>(props: { tool: ToolRegistration<D> }) {
|
|
|
301
325
|
return;
|
|
302
326
|
}
|
|
303
327
|
|
|
304
|
-
const existingUrl = localStorage.getItem(activeDocKey(tool.id,
|
|
328
|
+
const existingUrl = localStorage.getItem(activeDocKey(tool.id, identityHexId()));
|
|
305
329
|
if (existingUrl) {
|
|
306
330
|
console.log(`[${tool.name}] Found existing doc: ${existingUrl}`);
|
|
307
331
|
await loadDoc(existingUrl);
|
|
@@ -604,18 +628,20 @@ export function StandaloneApp<D>(props: { tool: ToolRegistration<D> }) {
|
|
|
604
628
|
? "Loading document..."
|
|
605
629
|
: ready()
|
|
606
630
|
? "No document open"
|
|
607
|
-
:
|
|
631
|
+
: props.config?.legacyMode
|
|
632
|
+
? "Initializing..."
|
|
633
|
+
: "Initializing keyhive..."}
|
|
608
634
|
</div>
|
|
609
635
|
}
|
|
610
636
|
>
|
|
611
637
|
{(h) => {
|
|
612
638
|
const toolEl = document.createElement("div") as unknown as ToolElement;
|
|
613
|
-
toolEl.repo = repo;
|
|
639
|
+
toolEl.repo = repo as unknown as ToolElement["repo"];
|
|
614
640
|
toolEl.style.height = "100%";
|
|
615
641
|
return (
|
|
616
642
|
<div style={{ height: "100%" }} ref={(el) => {
|
|
617
643
|
el.appendChild(toolEl);
|
|
618
|
-
tool.render(h as DocHandle<D>, toolEl);
|
|
644
|
+
tool.render(h as unknown as DocHandle<D>, toolEl);
|
|
619
645
|
}} />
|
|
620
646
|
);
|
|
621
647
|
}}
|
|
@@ -626,7 +652,7 @@ export function StandaloneApp<D>(props: { tool: ToolRegistration<D> }) {
|
|
|
626
652
|
<Show when={hive() && handle()}>
|
|
627
653
|
<ShareModal
|
|
628
654
|
isOpen={shareModalOpen()}
|
|
629
|
-
docUrl={handle()!.url}
|
|
655
|
+
docUrl={handle()!.url as AutomergeUrl}
|
|
630
656
|
hive={hive()!}
|
|
631
657
|
onClose={() => setShareModalOpen(false)}
|
|
632
658
|
/>
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,53 @@ export interface ToolRegistration<D = unknown> {
|
|
|
18
18
|
render: (handle: DocHandle<D>, element: ToolElement) => (() => void);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** Duck-typed doc handle — structurally compatible with automerge-repo DocHandle */
|
|
22
|
+
export interface FrameDocHandle<D = unknown> {
|
|
23
|
+
url: string;
|
|
24
|
+
doc(): D | undefined;
|
|
25
|
+
whenReady(): Promise<unknown>;
|
|
26
|
+
on(event: string, cb: (...args: unknown[]) => void): void;
|
|
27
|
+
off(event: string, cb: (...args: unknown[]) => void): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Duck-typed repo — structurally compatible with automerge-repo Repo */
|
|
31
|
+
export interface FrameRepo {
|
|
32
|
+
find<D = unknown>(url: string): FrameDocHandle<D>;
|
|
33
|
+
create<D = unknown>(initialValue: D): FrameDocHandle<D>;
|
|
34
|
+
delete(url: string): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface StandaloneFrameConfig {
|
|
38
|
+
/** When true, use plain automerge docs without keyhive. Default: false */
|
|
39
|
+
legacyMode?: boolean;
|
|
40
|
+
/** Pre-built repo for legacy mode. Required when legacyMode is true. */
|
|
41
|
+
repo?: FrameRepo;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface PluginDescription {
|
|
45
|
+
id: string;
|
|
46
|
+
type: string;
|
|
47
|
+
name: string;
|
|
48
|
+
icon?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ToolPlugin<D = unknown> extends PluginDescription {
|
|
52
|
+
type: "patchwork:tool";
|
|
53
|
+
supportedDatatypes: "*" | string[];
|
|
54
|
+
load: () => Promise<(handle: DocHandle<D>, element: ToolElement) => (() => void)>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface DatatypePlugin<D = unknown> extends PluginDescription {
|
|
58
|
+
type: "patchwork:datatype";
|
|
59
|
+
load: () => Promise<{
|
|
60
|
+
init(doc: D, repo: Repo): void;
|
|
61
|
+
getTitle(doc: D): string;
|
|
62
|
+
setTitle?(doc: D, title: string): void;
|
|
63
|
+
}>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type Plugin<D = unknown> = ToolPlugin<D> | DatatypePlugin<D>;
|
|
67
|
+
|
|
21
68
|
export { mountStandaloneApp } from "./mount.tsx";
|
|
22
69
|
export { ShareModal } from "./share-modal.tsx";
|
|
23
70
|
export { ConfirmModal } from "./confirm-modal.tsx";
|
package/src/mount.tsx
CHANGED
|
@@ -1,10 +1,48 @@
|
|
|
1
|
+
import type { Repo } from "@automerge/automerge-repo";
|
|
1
2
|
import { render } from "solid-js/web";
|
|
2
3
|
import { StandaloneApp } from "./frame.tsx";
|
|
3
|
-
import type { ToolRegistration } from "./index.ts";
|
|
4
|
+
import type { ToolRegistration, StandaloneFrameConfig, Plugin, ToolPlugin, DatatypePlugin } from "./index.ts";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
async function resolvePlugins<D>(plugins: Plugin<D>[]): Promise<ToolRegistration<D>> {
|
|
7
|
+
const toolPlugin = plugins.find((p): p is ToolPlugin<D> => p.type === "patchwork:tool");
|
|
8
|
+
const datatypePlugin = plugins.find((p): p is DatatypePlugin<D> => p.type === "patchwork:datatype");
|
|
9
|
+
|
|
10
|
+
if (!toolPlugin) throw new Error("No patchwork:tool plugin found in plugins array");
|
|
11
|
+
if (!datatypePlugin) throw new Error("No patchwork:datatype plugin found in plugins array");
|
|
12
|
+
|
|
13
|
+
const [renderFn, datatype] = await Promise.all([
|
|
14
|
+
toolPlugin.load(),
|
|
15
|
+
datatypePlugin.load(),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
// Derive defaultTitle by initializing a scratch doc
|
|
19
|
+
const scratch = {} as D;
|
|
20
|
+
datatype.init(scratch, {} as Repo);
|
|
21
|
+
const defaultTitle = datatype.getTitle(scratch);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
id: toolPlugin.id,
|
|
25
|
+
name: toolPlugin.name,
|
|
26
|
+
defaultTitle,
|
|
27
|
+
init: datatype.init,
|
|
28
|
+
getTitle: datatype.getTitle,
|
|
29
|
+
setTitle: datatype.setTitle,
|
|
30
|
+
render: renderFn,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function mountStandaloneApp<D>(
|
|
6
35
|
rootElement: HTMLElement,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
36
|
+
toolOrPlugins: ToolRegistration<D> | Plugin<D>[],
|
|
37
|
+
config?: StandaloneFrameConfig,
|
|
38
|
+
): Promise<void> {
|
|
39
|
+
let tool: ToolRegistration<D>;
|
|
40
|
+
|
|
41
|
+
if (Array.isArray(toolOrPlugins)) {
|
|
42
|
+
tool = await resolvePlugins(toolOrPlugins);
|
|
43
|
+
} else {
|
|
44
|
+
tool = toolOrPlugins;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
render(() => <StandaloneApp tool={tool} config={config} />, rootElement);
|
|
10
48
|
}
|