From 972fbd6ffd12436ef926b7913ee3513421a2b5cb Mon Sep 17 00:00:00 2001 From: Rizky Date: Tue, 23 Jan 2024 17:36:36 +0700 Subject: [PATCH] wip fix --- app/srv/api/nova-load.ts | 2 +- app/srv/ws/sync/actions.ts | 12 +- app/srv/ws/sync/actions/code_load.ts | 12 +- app/srv/ws/sync/actions/site_load.ts | 14 ++- app/srv/ws/sync/actions/yjs_sv_remote.ts | 6 +- app/srv/ws/sync/editor/code/build-code.ts | 116 +++++++++++++----- app/srv/ws/sync/editor/code/pkg-install.ts | 75 ----------- app/srv/ws/sync/editor/code/prep-code.ts | 57 +++++++-- .../editor/code/{util.ts => util-code.ts} | 26 ++-- app/srv/ws/sync/entity/snapshot.ts | 5 +- app/web/src/nova/ed/logic/ed-global.ts | 16 ++- app/web/src/nova/ed/logic/ed-site.ts | 94 +++++++------- app/web/src/nova/ed/logic/ed-sync.tsx | 40 +----- app/web/src/nova/ed/panel/popup/code/code.tsx | 54 ++------ app/web/src/nova/vi/load/load-snapshot.tsx | 57 +++++++++ app/web/src/utils/sync/ws-client.ts | 21 +--- app/web/src/utils/types/root.ts | 2 +- 17 files changed, 309 insertions(+), 300 deletions(-) delete mode 100644 app/srv/ws/sync/editor/code/pkg-install.ts rename app/srv/ws/sync/editor/code/{util.ts => util-code.ts} (63%) create mode 100644 app/web/src/nova/vi/load/load-snapshot.tsx diff --git a/app/srv/api/nova-load.ts b/app/srv/api/nova-load.ts index f734fe86..5a4ab8b3 100644 --- a/app/srv/api/nova-load.ts +++ b/app/srv/api/nova-load.ts @@ -1,7 +1,7 @@ import { dir } from "dir"; import { apiContext } from "../../../pkgs/core/server/api/api-ctx"; import { g } from "utils/global"; -import { code } from "../ws/sync/editor/code/util"; +import { code } from "../ws/sync/editor/code/util-code"; export const _ = { url: "/nova-load/:mode/:id/**", diff --git a/app/srv/ws/sync/actions.ts b/app/srv/ws/sync/actions.ts index da21c417..424a7606 100644 --- a/app/srv/ws/sync/actions.ts +++ b/app/srv/ws/sync/actions.ts @@ -65,12 +65,12 @@ export const SyncActions = { bin: Uint8Array ) => ({}) as { diff: Uint8Array; sv: Uint8Array } | void, diff_local: async ( - mode: "page" | "comp" | "site", + mode: "page" | "comp" | "site" | "code", id: string, bin: Uint8Array ) => {}, sv_remote: async ( - mode: "page" | "comp" | "site", + mode: "page" | "comp" | "site" | "code", id: string, sv: Uint8Array, diff: Uint8Array @@ -84,7 +84,13 @@ export const SyncActions = { load: async (id: string, type: "src" | "build") => ({}) as { id: string; - snapshot: null | Uint8Array; + snapshot: null | Record< + string, + { + id_doc: number; + bin: Uint8Array; + } + >; }, edit: async ( arg: diff --git a/app/srv/ws/sync/actions/code_load.ts b/app/srv/ws/sync/actions/code_load.ts index 5e2f1035..a2004618 100644 --- a/app/srv/ws/sync/actions/code_load.ts +++ b/app/srv/ws/sync/actions/code_load.ts @@ -1,5 +1,5 @@ import { SAction } from "../actions"; -import { getCode, prepDCode } from "../editor/code/prep-code"; +import { prepCodeSnapshot } from "../editor/code/prep-code"; import { SyncConnection } from "../type"; export const code_load: SAction["code"]["load"] = async function ( @@ -7,13 +7,9 @@ export const code_load: SAction["code"]["load"] = async function ( site_id, type ) { - const code = await getCode(site_id, "site"); - - if (code) { - const prep = await prepDCode(site_id); - if (prep) { - return { id: site_id, snapshot: prep.bin[type] }; - } + const snap = await prepCodeSnapshot(site_id, "site"); + if (snap && snap.type === "code") { + return { id: site_id, snapshot: snap.build }; } return { id: site_id, snapshot: null }; diff --git a/app/srv/ws/sync/actions/site_load.ts b/app/srv/ws/sync/actions/site_load.ts index 432346c7..0ed07ecf 100644 --- a/app/srv/ws/sync/actions/site_load.ts +++ b/app/srv/ws/sync/actions/site_load.ts @@ -3,6 +3,7 @@ import { ESite } from "../../../../web/src/nova/ed/logic/ed-global"; import { SAction } from "../actions"; import { prepCodeSnapshot } from "../editor/code/prep-code"; import { SyncConnection } from "../type"; +import { gzipAsync } from "../entity/zlib"; export const site_load: SAction["site"]["load"] = async function ( this: SyncConnection, @@ -27,7 +28,13 @@ export const site_load: SAction["site"]["load"] = async function ( select: { id: true }, }); - await prepCodeSnapshot(site_id, "site"); + const snap = await prepCodeSnapshot(site_id, "site"); + const compressed: any = {}; + if (snap) { + for (const [key, value] of Object.entries(snap.build)) { + compressed[key] = { bin: await gzipAsync(value.bin) }; + } + } return { id: site.id, @@ -38,7 +45,10 @@ export const site_load: SAction["site"]["load"] = async function ( responsive: site.responsive as ESite["responsive"], js_compiled: site.js_compiled || "", layout: { id: layout?.id || "", snapshot: null, meta: undefined }, - code: { snapshot: null, mode: site.code_mode as "old" | "vsc" }, + code: { + snapshot: compressed, + mode: site.code_mode as "old" | "vsc", + }, }; } } diff --git a/app/srv/ws/sync/actions/yjs_sv_remote.ts b/app/srv/ws/sync/actions/yjs_sv_remote.ts index 2490f3b3..7bfff072 100644 --- a/app/srv/ws/sync/actions/yjs_sv_remote.ts +++ b/app/srv/ws/sync/actions/yjs_sv_remote.ts @@ -14,7 +14,11 @@ export const yjs_sv_remote: SAction["yjs"]["sv_remote"] = async function ( console.log(`sv_remote not found`, mode, id); return; } - const doc = docs[mode][id].doc; + let doc = null; + if (mode !== "code") doc = docs[mode][id].doc; + else { + doc = docs.code[id].build.site; + } const diff_local = Y.encodeStateAsUpdate(doc as any, await gunzipAsync(sv)); Y.applyUpdate(doc as any, await gunzipAsync(diff), "local"); return { diff: await gzipAsync(diff_local) }; diff --git a/app/srv/ws/sync/editor/code/build-code.ts b/app/srv/ws/sync/editor/code/build-code.ts index fa03a2ff..6af7c068 100644 --- a/app/srv/ws/sync/editor/code/build-code.ts +++ b/app/srv/ws/sync/editor/code/build-code.ts @@ -1,50 +1,78 @@ -import { build, context } from "esbuild"; -import { Code } from "./watcher"; -import { g } from "utils/global"; -import { dir } from "dir"; import globalExternals from "@fal-works/esbuild-plugin-global-externals"; import { style } from "@hyrious/esbuild-plugin-style"; -import { sendWS } from "../../sync-handler"; -import { SyncType } from "../../type"; -import { gzipAsync } from "../../entity/zlib"; -import { ServerWebSocket } from "bun"; -import { WSData } from "../../../../../../pkgs/core/server/create"; +import { dir } from "dir"; +import { context } from "esbuild"; +import { existsAsync, dirAsync, removeAsync, writeAsync } from "fs-jetpack"; +import { CodeMode, code } from "./util-code"; import { user } from "../../entity/user"; -import { conns } from "../../entity/conn"; -import { CodeMode, code } from "./util"; +import { docs } from "../../entity/docs"; +import { DCode } from "../../../../../web/src/utils/types/root"; +import { readDirectoryRecursively } from "../../../../api/site-export"; const encoder = new TextEncoder(); export const codeBuild = async (id_site: any, mode: CodeMode) => { const src_path = code.path(id_site, mode, "src"); + if (!(await existsAsync(src_path))) return; const build_path = code.path(id_site, mode, "build"); - const build_file = dir.path(`${build_path}/index.js`); + + await removeAsync(build_path); + await dirAsync(build_path); + const build_file = `${build_path}/index.js`; + await writeAsync(build_file, ""); if (!code.esbuild[id_site]) { code.esbuild[id_site] = { site: null, ssr: null }; } - code.esbuild[id_site][mode] = await context({ - absWorkingDir: src_path, - entryPoints: ["index.tsx"], - bundle: true, - outfile: build_file, - minify: true, - treeShaking: true, - sourcemap: true, - plugins: [ - style(), - globalExternals({ - react: { - varName: "window.React", - type: "cjs", + if (!code.esbuild[id_site][mode]) { + code.esbuild[id_site][mode] = await context({ + absWorkingDir: src_path, + entryPoints: ["index.tsx"], + bundle: true, + outfile: build_file, + minify: true, + treeShaking: true, + format: "cjs", + sourcemap: true, + plugins: [ + style(), + globalExternals({ + react: { + varName: "window.React", + type: "cjs", + }, + "react-dom": { + varName: "window.ReactDOM", + type: "cjs", + }, + }), + { + name: "prasi", + setup(setup) { + setup.onEnd((res) => { + const cdoc = docs.code[id_site]; + if (cdoc) { + const doc = cdoc.build[mode]; + const build_dir = code.path(id_site, mode, "build"); + + if (doc) { + codeApplyChanges(build_dir, doc); + } + } + }); + }, }, - "react-dom": { - varName: "window.ReactDOM", - type: "cjs", - }, - }), - ], - }); + ], + }); + const esbuild = code.esbuild[id_site][mode]; + esbuild?.watch(); + } + const esbuild = code.esbuild[id_site][mode]; + if (esbuild) { + try { + await esbuild.rebuild(); + } catch (e) {} + } const out = Bun.file(build_file); const src = (await out.text()).replace( @@ -53,3 +81,25 @@ export const codeBuild = async (id_site: any, mode: CodeMode) => { ); await Bun.write(out, src); }; + +const codeApplyChanges = (path: string, doc: DCode) => { + const map = doc.getMap("map"); + + const files = map.get("files"); + + const dirs = readDirectoryRecursively(path); + doc.transact(() => { + files?.forEach((v, k) => { + if (!dirs[k]) { + files?.delete(k); + } + }); + for (const [k, v] of Object.entries(dirs)) { + if (files) { + files.set(k, v); + } + } + }); + + return doc; +}; diff --git a/app/srv/ws/sync/editor/code/pkg-install.ts b/app/srv/ws/sync/editor/code/pkg-install.ts deleted file mode 100644 index 76873963..00000000 --- a/app/srv/ws/sync/editor/code/pkg-install.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { spawn } from "bun"; -import { sendWS } from "../../sync-handler"; -import { SyncType } from "../../type"; -import { Code } from "./watcher"; - -const decoder = new TextDecoder(); -export const codePkgInstall = async (id_site: string, mode: string) => { - try { - const proc = spawn({ - cmd: ["bun", "i"], - cwd: Code.path(code.id_site, code.id), - stderr: "pipe", - stdout: "pipe", - }); - - const broadcast = (content: string) => { - activity.site - .room(code.id_site) - .findAll({ site_js: code.name }) - .forEach((item, ws) => { - sendWS(ws, { - type: SyncType.Event, - event: "code", - data: { - name: code.name, - id: code.id, - content, - }, - }); - }); - }; - (async () => { - for await (const chunk of proc.stdout) { - broadcast(decoder.decode(chunk)); - } - })(); - (async () => { - for await (const chunk of proc.stderr) { - broadcast(decoder.decode(chunk)); - } - })(); - await proc.exited; - - activity.site - .room(code.id_site) - .findAll({ site_js: code.name }) - .forEach((item, ws) => { - sendWS(ws, { - type: SyncType.Event, - event: "code", - data: { - name: code.name, - id: code.id, - event: "code-done", - }, - }); - }); - } catch (e: any) { - activity.site - .room(code.id_site) - .findAll({ site_js: code.name }) - .forEach((item, ws) => { - sendWS(ws, { - type: SyncType.Event, - event: "code", - data: { - name: code.name, - id: code.id, - event: "code-done", - content: `ERROR: ${e.message}`, - }, - }); - }); - } -}; diff --git a/app/srv/ws/sync/editor/code/prep-code.ts b/app/srv/ws/sync/editor/code/prep-code.ts index 7a3642c1..a8aef910 100644 --- a/app/srv/ws/sync/editor/code/prep-code.ts +++ b/app/srv/ws/sync/editor/code/prep-code.ts @@ -1,11 +1,15 @@ -import { existsAsync } from "fs-jetpack"; import { Doc } from "yjs"; import { DCode } from "../../../../../web/src/utils/types/root"; import { readDirectoryRecursively } from "../../../../api/site-export"; import { docs } from "../../entity/docs"; import { snapshot } from "../../entity/snapshot"; +import { gzipAsync } from "../../entity/zlib"; import { codeBuild } from "./build-code"; -import { CodeMode, code } from "./util"; +import { CodeMode, code } from "./util-code"; +import { user } from "../../entity/user"; +import { SyncType } from "../../type"; +import { sendWS } from "../../sync-handler"; +import { conns } from "../../entity/conn"; export const prepCodeSnapshot = async (id_site: string, mode: CodeMode) => { await code @@ -17,23 +21,47 @@ export const prepCodeSnapshot = async (id_site: string, mode: CodeMode) => { ) .await(); - let doc = docs.code[id_site]; + let dcode = docs.code[id_site]; if (!docs.code[id_site]) { docs.code[id_site] = { id: id_site, build: {}, }; - doc = docs.code[id_site]; + dcode = docs.code[id_site]; } - if (doc) { - if (!doc.build[mode]) { + if (dcode) { + if (!dcode.build[mode]) { const build_dir = code.path(id_site, mode, "build"); + await codeBuild(id_site, mode); + dcode.build[mode] = codeLoad(id_site, build_dir); + const doc = dcode.build[mode] as Doc; + if (doc) { + doc.on("update", async (e, origin) => { + const bin = Y.encodeStateAsUpdate(doc); - if (!(await existsAsync(build_dir))) { - await codeBuild(id_site, mode); + if (snap && snap.type === "code") { + snap.build[mode].bin = bin; + snapshot.update({ + id: id_site, + type: "code", + build: snap.build, + }); + } + + const sv_local = await gzipAsync(bin); + user.active.findAll({ site_id: id_site }).map((e) => { + const ws = conns.get(e.client_id)?.ws; + if (ws) { + sendWS(ws, { + type: SyncType.Event, + event: "remote_svlocal", + data: { type: "code", sv_local, id: id_site }, + }); + } + }); + }); } - doc.build[mode] = codeLoad(id_site, build_dir); } const build: Record< @@ -43,9 +71,10 @@ export const prepCodeSnapshot = async (id_site: string, mode: CodeMode) => { bin: Uint8Array; } > = {}; - for (const [k, v] of Object.entries(doc.build)) { + + for (const [k, v] of Object.entries(dcode.build)) { const bin = Y.encodeStateAsUpdate(v as Doc); - build[k] = { bin, id_doc: v.clientID }; + build[k] = { bin: bin, id_doc: v.clientID }; } let snap = await snapshot.getOrCreate({ @@ -54,7 +83,9 @@ export const prepCodeSnapshot = async (id_site: string, mode: CodeMode) => { build, }); - return snap; + if (snap.type === "code") { + return snap; + } } }; @@ -66,7 +97,7 @@ const codeLoad = (id: string, path: string) => { const dirs = readDirectoryRecursively(path); for (const [k, v] of Object.entries(dirs)) { - files.set(k, new Y.Text(v)); + files.set(k, v); } doc.transact(() => { diff --git a/app/srv/ws/sync/editor/code/util.ts b/app/srv/ws/sync/editor/code/util-code.ts similarity index 63% rename from app/srv/ws/sync/editor/code/util.ts rename to app/srv/ws/sync/editor/code/util-code.ts index 90a975f0..a57f3a9d 100644 --- a/app/srv/ws/sync/editor/code/util.ts +++ b/app/srv/ws/sync/editor/code/util-code.ts @@ -1,13 +1,17 @@ import { dir } from "dir"; -import { g } from "utils/global"; -import { dirAsync, writeAsync } from "fs-jetpack"; -import { dirname } from "path"; import { BuildContext } from "esbuild"; +import { dirAsync, existsAsync, writeAsync } from "fs-jetpack"; +import { dirname } from "path"; +import { g } from "utils/global"; export type CodeMode = "site" | "ssr"; export const code = { path(id_site: string, mode: CodeMode, type: "src" | "build", path?: string) { - return dir.path(`${g.datadir}/code/${id_site}/${mode}/${type}`); + let file_path = ""; + if (path) { + file_path = path[0] === "/" ? path : `/${path}`; + } + return dir.path(`${g.datadir}/code/${id_site}/${mode}/${type}${file_path}`); }, esbuild: {} as Record>, prep(id_site: string, mode: CodeMode) { @@ -17,15 +21,21 @@ export const code = { }); const promises: Promise[] = []; return { - path(type: "src" | "build", path?: string) { - return dir.path(`${g.datadir}/code/${id_site}/${mode}/${type}`); + path(type: "src" | "build", path: string) { + return dir.path( + `${g.datadir}/code/${id_site}/${mode}/${type}${ + path[0] === "/" ? path : `/${path}` + }` + ); }, new_file(path: string, content: string) { promises.push( new Promise(async (done) => { const full_path = this.path("src", path); - await dirAsync(dirname(full_path)); - await writeAsync(full_path, content); + if (!(await existsAsync(full_path))) { + await dirAsync(dirname(full_path)); + await writeAsync(full_path, content); + } done(); }) ); diff --git a/app/srv/ws/sync/entity/snapshot.ts b/app/srv/ws/sync/entity/snapshot.ts index 22773537..eb306158 100644 --- a/app/srv/ws/sync/entity/snapshot.ts +++ b/app/srv/ws/sync/entity/snapshot.ts @@ -85,8 +85,9 @@ export const snapshot = { async getOrCreate(data: DocSnapshot) { const id = `${data.type}-${data.id}`; let res = this.db.get(id); - if (!res) { - await this.db.put(id, structuredClone(emptySnapshot as DocSnapshot)); + + if (!res || !res.id) { + await this.db.put(id, structuredClone(data as DocSnapshot)); res = this.db.get(id); } return res as DocSnapshot; diff --git a/app/web/src/nova/ed/logic/ed-global.ts b/app/web/src/nova/ed/logic/ed-global.ts index 6e13a4a1..48351674 100644 --- a/app/web/src/nova/ed/logic/ed-global.ts +++ b/app/web/src/nova/ed/logic/ed-global.ts @@ -5,7 +5,7 @@ import { SAction } from "../../../../../srv/ws/sync/actions"; import { parseJs } from "../../../../../srv/ws/sync/editor/parser/parse-js"; import { clientStartSync } from "../../../utils/sync/ws-client"; import { IItem } from "../../../utils/types/item"; -import { DComp, DPage } from "../../../utils/types/root"; +import { DCode, DComp, DPage } from "../../../utils/types/root"; import { GenMetaP, IMeta as LogicMeta } from "../../vi/utils/types"; export type IMeta = LogicMeta; @@ -24,7 +24,15 @@ export const EmptySite = { meta: undefined as void | Record, }, code: { - snapshot: null as null | Uint8Array, + snapshot: {} as + | undefined + | Record< + string, + { + id_doc: number; + bin: Uint8Array; + } + >, mode: "old" as "old" | "vsc", }, }; @@ -183,7 +191,8 @@ export const EDGlobal = { >, group: {} as Record>>, }, - global_prop: [] as string[], + code: {} as Record, + global_prop: [] as string[], ui: { zoom: localStorage.zoom || "100%", should_render: false, @@ -211,7 +220,6 @@ export const EDGlobal = { code: { init: false, open: false, - id: "", name: "site", log: "", loading: false, diff --git a/app/web/src/nova/ed/logic/ed-site.ts b/app/web/src/nova/ed/logic/ed-site.ts index 8c3bcc3a..c5ff795e 100644 --- a/app/web/src/nova/ed/logic/ed-site.ts +++ b/app/web/src/nova/ed/logic/ed-site.ts @@ -1,57 +1,59 @@ import { viLoadLegacy } from "../../vi/load/load-legacy"; +import { viLoadSnapshot } from "../../vi/load/load-snapshot"; import { ESite, PG } from "./ed-global"; import { reloadPage } from "./ed-route"; export const loadSite = async (p: PG, site: ESite, note: string) => { - const old_layout_id = p.site.layout.id; - const layout_changed = p.site.layout.id !== site.layout.id; - p.site = site; + const old_layout_id = p.site.layout.id; + const layout_changed = p.site.layout.id !== site.layout.id; + p.site = site; - p.mode = "desktop"; - if (p.site.responsive === "mobile-only") { - p.mode = "mobile"; - } else if (p.site.responsive === "desktop-only") { - p.mode = "desktop"; - } + p.mode = "desktop"; + if (p.site.responsive === "mobile-only") { + p.mode = "mobile"; + } else if (p.site.responsive === "desktop-only") { + p.mode = "desktop"; + } - if (layout_changed) { - const old_layout = p.page.list[old_layout_id]; + if (layout_changed) { + const old_layout = p.page.list[old_layout_id]; - if (old_layout && old_layout.on_update) { - old_layout.doc.off("update", old_layout.on_update); - } + if (old_layout && old_layout.on_update) { + old_layout.doc.off("update", old_layout.on_update); + } - if (!p.script.db && !p.script.api) { - if (p.site.code.mode === "old") { - await viLoadLegacy({ - site: { - api_url: p.site.config.api_url, - id: p.site.id, - api: { - get() { - return p.script.api; - }, - set(val) { - p.script.api = val; - }, - }, - db: { - get() { - return p.script.db; - }, - set(val) { - p.script.db = val; - }, - }, - }, - render: () => {}, - }); - } else { - } - } + if (!p.script.db && !p.script.api) { + if (p.site.code.mode === "old") { + await viLoadLegacy({ + site: { + api_url: p.site.config.api_url, + id: p.site.id, + api: { + get() { + return p.script.api; + }, + set(val) { + p.script.api = val; + }, + }, + db: { + get() { + return p.script.db; + }, + set(val) { + p.script.db = val; + }, + }, + }, + render: () => {}, + }); + } else { + await viLoadSnapshot(p); + } + } - if (site.layout.id) { - await reloadPage(p, site.layout.id, "load-layout"); - } - } + if (site.layout.id) { + await reloadPage(p, site.layout.id, "load-layout"); + } + } }; diff --git a/app/web/src/nova/ed/logic/ed-sync.tsx b/app/web/src/nova/ed/logic/ed-sync.tsx index 4d0339f9..6aea22c1 100644 --- a/app/web/src/nova/ed/logic/ed-sync.tsx +++ b/app/web/src/nova/ed/logic/ed-sync.tsx @@ -64,47 +64,11 @@ export const edInitSync = (p: PG) => { site_id: params.site_id, page_id: params.page_id, events: { - code(arg) { - p.ui.popup.code.error = false; - - if (arg.event === "code-loading") { - p.ui.popup.code.log = ""; - p.ui.popup.code.loading = true; - p.render(); - } else if (arg.event === "code-done") { - if (typeof arg.content === "string") { - if (arg.content !== "OK") { - p.ui.popup.code.error = true; - } - - p.ui.popup.code.log += arg.content; - } - p.ui.popup.code.loading = false; - - if (arg.src) { - const w = window as any; - const module = evalCJS(decoder.decode(decompress(arg.src))); - p.global_prop = Object.keys(module); - if (typeof module === "object") { - for (const [k, v] of Object.entries(module)) { - w[k] = v; - } - } - } - p.render(); - } else { - if (typeof arg.content === "string") - p.ui.popup.code.log += arg.content; - p.render(); - } - }, - activity(arg) {}, opened() { if (w.offline) { console.log("reconnected!"); w.offline = false; p.ui.syncing = true; - p.sync.activity("site", { type: "join", id: params.site_id }); p.render(); } else { w.offline = false; @@ -158,6 +122,8 @@ export const edInitSync = (p: PG) => { doc = p.page.doc as Y.Doc; } else if (data.type === "comp" && p.comp.list[data.id]) { doc = p.comp.list[data.id].doc; + } else if (data.type === "code") { + doc = p.code.site.doc; } if (doc) { @@ -179,7 +145,7 @@ export const edInitSync = (p: PG) => { Y.applyUpdate(doc, decompress(res.diff), "sv_remote"); if (data.type === "page") { await treeRebuild(p, { note: "sv_remote" }); - } else { + } else if (data.type === "comp") { const updated = await updateComponentMeta(p, doc, data.id); if (updated) { p.comp.list[data.id].meta = updated.meta; diff --git a/app/web/src/nova/ed/panel/popup/code/code.tsx b/app/web/src/nova/ed/panel/popup/code/code.tsx index 01a8ad47..8c15c88b 100644 --- a/app/web/src/nova/ed/panel/popup/code/code.tsx +++ b/app/web/src/nova/ed/panel/popup/code/code.tsx @@ -7,11 +7,11 @@ import { Popover } from "../../../../../utils/ui/popover"; import { Tooltip } from "../../../../../utils/ui/tooltip"; import { EDGlobal } from "../../../logic/ed-global"; import { - iconChevronDown, - iconLoading, - iconLog, - iconNewTab, - iconTrash + iconChevronDown, + iconLoading, + iconLog, + iconNewTab, + iconTrash, } from "./icons"; import { CodeNameItem, CodeNameList, codeName } from "./name-list"; @@ -116,7 +116,6 @@ const CodeBody = () => { onPick={async (e) => { local.namePicker = false; p.ui.popup.code.name = e.name; - p.ui.popup.code.id = ""; p.render(); }} /> @@ -142,41 +141,6 @@ const CodeBody = () => { > - {p.ui.popup.code.name !== "site" && - p.ui.popup.code.name !== "SSR" && ( - <> - { - if ( - prompt( - "Are you sure want to delete this code?\ntype 'yes' to confirm:" - ) === "yes" - ) { - await db.code.delete({ - where: { id: p.ui.popup.code.id }, - }); - - codeName.list = codeName.list.filter( - (e) => e.id !== p.ui.popup.code.id - ); - - p.ui.popup.code.name = codeName.list[0].name; - p.ui.popup.code.id = codeName.list[0].id; - p.render(); - } - }} - > -
-
- - )} { placement="bottom" className={cx("flex items-stretch relative")} onClick={() => { - window.open( - `${vscode_url}folder=/site/code/${p.site.id}/${p.ui.popup.code.id}` - ); + window.open(`${vscode_url}folder=/site/${p.site.id}/site/src`); }} >
{ {code_mode === "vsc" ? (
- {!p.ui.popup.code.open || !p.ui.popup.code.id ? ( + {!p.ui.popup.code.open ? ( ) : ( <>
Loading VSCode... diff --git a/app/web/src/nova/vi/load/load-snapshot.tsx b/app/web/src/nova/vi/load/load-snapshot.tsx new file mode 100644 index 00000000..6e50b824 --- /dev/null +++ b/app/web/src/nova/vi/load/load-snapshot.tsx @@ -0,0 +1,57 @@ +import { decompress } from "wasm-gzip"; +import { loadApiProxyDef } from "../../../base/load/api/api-proxy-def"; +import { PG } from "../../ed/logic/ed-global"; +import { evalCJS } from "../../ed/logic/ed-sync"; +import { treeRebuild } from "../../ed/logic/tree/build"; + +const decoder = new TextDecoder(); + +export const viLoadSnapshot = async (p: PG) => { + let api_url = p.site.config.api_url; + + try { + const apiURL = new URL(api_url); + if (api_url && apiURL.hostname) { + try { + await loadApiProxyDef(api_url, true); + } catch (e) { + console.warn("Failed to load API:", api_url); + } + } + } catch (e) {} + + if (p.site.code.snapshot) { + for (const [name, build] of Object.entries(p.site.code.snapshot)) { + const doc = new Y.Doc(); + Y.applyUpdate(doc, decompress(build.bin)); + + p.code[name] = { doc: doc as any }; + const code = p.code[name].doc; + if (code) { + const src = code.getMap("map").get("files")?.get("index.js"); + applyEnv(p, src); + treeRebuild(p); + p.render(); + code.on("update", (e, origin) => { + const src = code.getMap("map").get("files")?.get("index.js"); + applyEnv(p, src); + treeRebuild(p); + p.render(); + }); + } + } + } +}; + +const applyEnv = (p: PG, src?: string) => { + if (src) { + const w = window as any; + const module = evalCJS(src); + p.global_prop = Object.keys(module); + if (typeof module === "object") { + for (const [k, v] of Object.entries(module)) { + w[k] = v; + } + } + } +}; diff --git a/app/web/src/utils/sync/ws-client.ts b/app/web/src/utils/sync/ws-client.ts index 62a491a2..6efe778e 100644 --- a/app/web/src/utils/sync/ws-client.ts +++ b/app/web/src/utils/sync/ws-client.ts @@ -77,28 +77,9 @@ export const clientStartSync = async (arg: { site_id?: string; page_id?: string; events: { - code: (arg: { - name: string; - id: string; - event: "code-loading" | "code-done"; - content?: string; - src?: Uint8Array; - }) => void; - activity: (arg: { - activity: string; - room_id: string; - clients: { - user: { - client_id: string; - user_id: string; - username: string; - }; - data: any; - }[]; - }) => void; editor_start: (arg: UserConf) => void; remote_svlocal: (arg: { - type: "page" | "comp"; + type: "page" | "comp" | "code"; id: string; sv_local: Uint8Array; }) => void; diff --git a/app/web/src/utils/types/root.ts b/app/web/src/utils/types/root.ts index 38b2bac2..c61b8dc9 100644 --- a/app/web/src/utils/types/root.ts +++ b/app/web/src/utils/types/root.ts @@ -28,6 +28,6 @@ export type DComp = TypedDoc<{ export type DCode = TypedDoc<{ map: TypedMap<{ id: string; - files: TypedMap>; + files: TypedMap>; }>; }>;