diff --git a/app/srv/ws/sync/editor/load-page.ts b/app/srv/ws/sync/editor/load-page.ts index bf5eef22..3676b8b9 100644 --- a/app/srv/ws/sync/editor/load-page.ts +++ b/app/srv/ws/sync/editor/load-page.ts @@ -95,8 +95,31 @@ export const serverWalkMap = ( ) => { const { mitem, parent_item, parent_mcomp } = arg; + let override_id = ""; + const id = mitem.get("id"); + + if (parent_mcomp && id) { + const fcomp = parent_mcomp.mitem.get("component"); + if (fcomp) { + const ref_ids = fcomp.get("ref_ids"); + + if (ref_ids) { + let ref_id = ref_ids.get(id); + + if (!ref_id) { + ref_id = createId(); + ref_ids.set(id, ref_id); + } + override_id = ref_id; + } + } + } + const item = {} as unknown as IItem; mapItem(mitem, item); + if (override_id) { + item.id = override_id; + } const item_comp = item.component; const mitem_comp = mitem.get("component"); @@ -126,28 +149,27 @@ export const serverWalkMap = ( mapItem(mcomp, item); item.id = original_id; - const pcomp = p.scope_comps[item_comp.id]; - pcomp.scope[item.id] = { p: arg.parent_ids, s: null }; - const js = item.adv?.js; - if (typeof js === "string") { - const scope = parseJs(js); - if (scope) pcomp.scope[item.id].s = scope; - } - const mprops = mcomp.get("component")?.get("props")?.toJSON() as Record< string, FNCompDef >; + const scope = { props: {} } as Exclude< + ReturnType, + undefined + >; + if (mprops) { const mitem_comp = mitem.get("component"); if (mitem_comp) { const mitem_props = ensureMItemProps(mitem_comp, item_comp); if (mitem_props) { for (const [k, v] of Object.entries(mprops)) { + scope.props[k] = { name: k, value: `null as any` }; const mprop = ensureMProp(mitem_props, k, v); item_comp.props[k] = v; if (mprop && v.meta?.type === "content-element") { + scope.props[k].value = "null as ReactElement"; const mcontent = ensurePropContent(mprop, k); if (mcontent) { serverWalkMap(p, { @@ -168,6 +190,27 @@ export const serverWalkMap = ( } } + const pcomp = p.scope_comps[item_comp.id]; + pcomp.scope[item.id] = { p: arg.parent_ids, s: null }; + const js = item.adv?.js; + if (typeof js === "string") { + const res = parseJs(js); + if (res) { + scope.local = res.local; + scope.passprop = res.passprop; + } + } + + if (scope) pcomp.scope[item.id].s = scope; + if (!parent_mcomp) { + p.scope[item.id] = { + p: arg.parent_ids, + name: item.name, + s: null, + } as any; + if (scope) p.scope[item.id].s = scope; + } + const childs = mcomp.get("childs")?.map((e) => e) || []; for (const e of childs) { serverWalkMap(p, { @@ -198,7 +241,7 @@ export const serverWalkMap = ( if (scope) pcomp.scope[item.id].s = scope; } } else { - p.scope[item.id] = { p: arg.parent_ids, s: null }; + p.scope[item.id] = { p: arg.parent_ids, name: item.name, s: null } as any; const js = item.adv?.js; if (typeof js === "string") { const scope = parseJs(js); diff --git a/app/srv/ws/sync/editor/parser/parse-js.ts b/app/srv/ws/sync/editor/parser/parse-js.ts index fed3bbf1..aedeb4f7 100644 --- a/app/srv/ws/sync/editor/parser/parse-js.ts +++ b/app/srv/ws/sync/editor/parser/parse-js.ts @@ -6,7 +6,7 @@ export const parseJs = (code: string) => { }); const local = { name: "", value: "", index: 0 }; - const passprop: any = {}; + const passprop: Record = {}; recast.visit(ast, { visitJSXOpeningElement({ node }) { if (node.name.type === "JSXIdentifier" && node.attributes) { @@ -65,6 +65,7 @@ export const parseJs = (code: string) => { const result = {} as { local: typeof local | undefined; passprop: typeof passprop | undefined; + props: Record; }; if (local.name) { result.local = local; diff --git a/app/web/src/nova/ed/logic/ed-global.ts b/app/web/src/nova/ed/logic/ed-global.ts index 269c544d..e647e7a7 100644 --- a/app/web/src/nova/ed/logic/ed-global.ts +++ b/app/web/src/nova/ed/logic/ed-global.ts @@ -23,10 +23,11 @@ export type ESite = typeof EmptySite; export type EPage = typeof EmptyPage; export type EComp = typeof EmptyComp; -export type IScope = Record< - string, - { p: string[]; s: null | Exclude, undefined> } ->; +export type ISingleScope = { + p: string[]; + s: null | Exclude, undefined>; +}; +export type IScope = Record; export type IScopeComp = Record< string, @@ -64,6 +65,16 @@ export const active = { localStorage.setItem("prasi-active-id", val); target.active_id = val; }, + get comp_id() { + if (target.active_id === false) { + target.active_id = localStorage.getItem("prasi-comp-id") || ""; + } + return target.active_id; + }, + set comp_id(val: string) { + localStorage.setItem("prasi-comp-id", val); + target.active_id = val; + }, }; export type EdMeta = { @@ -122,6 +133,7 @@ export const EDGlobal = { on_update?: (bin: Uint8Array, origin: any) => Promise; } >, + scope: {} as IScope, building: false, meta: {} as Record, entry: [] as string[], @@ -129,7 +141,6 @@ export const EDGlobal = { render: () => {}, }, comp: { - cur: EmptyComp, doc: null as null | DComp, item: null as null | IItem, map: {} as Record, diff --git a/app/web/src/nova/ed/logic/ed-route.ts b/app/web/src/nova/ed/logic/ed-route.ts index 67c4a820..3f4362be 100644 --- a/app/web/src/nova/ed/logic/ed-route.ts +++ b/app/web/src/nova/ed/logic/ed-route.ts @@ -47,6 +47,8 @@ export const reloadPage = async (p: PG, page_id: string) => { p.render(); return; } + + p.page.scope = remotePage.scope || {}; if (remotePage.scope_comps) { for (const [id_comp, c] of Object.entries(remotePage.scope_comps)) { if (c && c.snapshot) { diff --git a/app/web/src/nova/ed/logic/ed-undo.ts b/app/web/src/nova/ed/logic/ed-undo.ts index 3d104d25..30a754c2 100644 --- a/app/web/src/nova/ed/logic/ed-undo.ts +++ b/app/web/src/nova/ed/logic/ed-undo.ts @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { PG } from "./ed-global"; +import { PG, active } from "./ed-global"; import { treeRebuild } from "./tree/build"; export const edUndoManager = async (p: PG) => { @@ -18,8 +18,8 @@ export const edUndoManager = async (p: PG) => { (evt.ctrlKey || evt.metaKey) && !evt.shiftKey ) { - if (p.comp.cur.id) { - p.sync.yjs.um("comp", "redo", p.comp.cur.id); + if (active.comp_id) { + p.sync.yjs.um("comp", "redo", active.comp_id); } else { p.sync.yjs.um("page", "redo", p.page.cur.id); } @@ -31,8 +31,8 @@ export const edUndoManager = async (p: PG) => { (evt.ctrlKey || evt.metaKey) && evt.shiftKey ) { - if (p.comp.cur.id) { - p.sync.yjs.um("comp", "redo", p.comp.cur.id); + if (active.comp_id) { + p.sync.yjs.um("comp", "redo", active.comp_id); } else { p.sync.yjs.um("page", "redo", p.page.cur.id); } @@ -44,8 +44,8 @@ export const edUndoManager = async (p: PG) => { (evt.ctrlKey || evt.metaKey) && !evt.shiftKey ) { - if (p.comp.cur.id) { - p.sync.yjs.um("comp", "undo", p.comp.cur.id); + if (active.comp_id) { + p.sync.yjs.um("comp", "undo", active.comp_id); } else { p.sync.yjs.um("page", "undo", p.page.cur.id); } diff --git a/app/web/src/nova/ed/panel/popup/script/monaco.tsx b/app/web/src/nova/ed/panel/popup/script/monaco.tsx index 67e20cc1..a5ba6fb7 100644 --- a/app/web/src/nova/ed/panel/popup/script/monaco.tsx +++ b/app/web/src/nova/ed/panel/popup/script/monaco.tsx @@ -1,12 +1,12 @@ import type { OnMount } from "@monaco-editor/react"; import { createStore } from "idb-keyval"; import trim from "lodash.trim"; -import { useEffect } from "react"; import { useGlobal, useLocal } from "web-utils"; import { jscript } from "../../../../../utils/script/jscript"; -import { EDGlobal, active } from "../../../logic/ed-global"; import { jsMount } from "../../../../../utils/script/mount"; import { monacoTypings } from "../../../../../utils/script/typings"; +import { EDGlobal, active } from "../../../logic/ed-global"; +import { declareScope } from "./scope"; export type MonacoEditor = Parameters[0]; export const ScriptMonaco = () => { @@ -109,8 +109,8 @@ export const ScriptMonaco = () => { "typescript", monaco.Uri.parse( `ts:${ - p.comp.cur.id - ? `comp-${p.comp.cur.id}` + active.comp_id + ? `comp-${active.comp_id}` : `page-${p.page.cur.id}` }-${active.item_id}.tsx` ) @@ -118,13 +118,22 @@ export const ScriptMonaco = () => { editor.setModel(model); } monaco.editor.registerEditorOpener({ - openCodeEditor(source, resource, selectionOrPosition) { + openCodeEditor(source, r, selectionOrPosition) { + const path = r.path.split("~"); + const id = path[path.length - 1].replace(".d.ts", ""); + const meta = p.page.meta[id]; + + if (meta) { + console.log(meta.item, meta); + } + // https://github.com/microsoft/vscode/pull/177064#issue-1623100628 return false; }, }); await jsMount(editor, monaco); + await declareScope(p, editor, monaco); await monacoTypings( { site_dts: p.site_dts, diff --git a/app/web/src/nova/ed/panel/popup/script/scope.tsx b/app/web/src/nova/ed/panel/popup/script/scope.tsx new file mode 100644 index 00000000..5bc0f205 --- /dev/null +++ b/app/web/src/nova/ed/panel/popup/script/scope.tsx @@ -0,0 +1,126 @@ +import type { OnMount } from "@monaco-editor/react"; +import { deepClone } from "web-utils"; +import { ISingleScope, PG, active } from "../../../logic/ed-global"; +type Monaco = Parameters[1]; +export type MonacoEditor = Parameters[0]; + +export const declareScope = async ( + p: PG, + editor: MonacoEditor, + monaco: Monaco +) => { + const active_id = active.item_id; + const s = deepClone(p.page.scope[active_id]); + + monaco.editor.getModels().forEach((model) => { + if (model.uri.toString().startsWith("ts:scope~")) { + model.dispose(); + } + }); + + const existing: Record = {}; + spreadScope(p, s, (arg) => { + const { name } = arg; + const e = existing[name]; + if (e && e.s.s) { + if (e.type === "local") { + delete e.s.s.local; + } + if (e.type === "passprop" && e.s.s.passprop) { + delete e.s.s.passprop[e.name]; + } + if (e.type === "prop" && e.s.s.props) { + delete e.s.s.props[e.name]; + } + } + existing[name] = arg; + }); + + spreadScope(p, s, (arg) => { + addScope( + monaco, + `${arg.type}~${arg.id}`, + `\ +export const {}; +const __val = ${arg.value}; +declare global { + const ${arg.name}: typeof __val; +}` + ); + }); +}; + +type IEachArgScope = { + s: ISingleScope; + name: string; + value: string; + id: string; + type: "local" | "prop" | "passprop"; + index?: number; + isProp?: boolean; +}; +const spreadScope = ( + p: PG, + s: ISingleScope, + each: (arg: IEachArgScope) => void +) => { + for (const parent_id of s.p) { + const item = p.page.scope[parent_id]; + if (item) { + const scope = item.s; + if (scope) { + if (scope.local) + each({ + s, + type: "local", + id: parent_id, + name: scope.local.name, + value: scope.local?.value, + index: scope.local?.index, + }); + + if (scope.passprop) { + for (const [k, v] of Object.entries(scope.passprop)) { + each({ + s, + type: "prop", + id: parent_id, + name: k, + value: v.value, + index: v.index, + }); + } + } + + if (scope.props) { + for (const [k, v] of Object.entries(scope.props)) { + each({ + s, + type: "passprop", + id: parent_id, + name: k, + value: v.value, + isProp: true, + }); + } + } + } + } + } +}; + +const addScope = (monaco: Monaco, id: string, source: string) => { + const model = monaco.editor.getModels().find((e) => { + return e.uri.toString() === `ts:scope~${id}.d.ts`; + }); + + if (model) { + model.setValue(source); + } else { + monaco.editor.createModel( + source, + "typescript", + monaco.Uri.parse(`ts:scope~${id}.d.ts`) + ); + } +}; diff --git a/app/web/src/nova/ed/panel/tree/body.tsx b/app/web/src/nova/ed/panel/tree/body.tsx index 5abc2f2c..24f76b4c 100644 --- a/app/web/src/nova/ed/panel/tree/body.tsx +++ b/app/web/src/nova/ed/panel/tree/body.tsx @@ -6,7 +6,7 @@ import { TreeMethods, } from "@minoru/react-dnd-treeview"; import { useGlobal, useLocal } from "web-utils"; -import { EDGlobal, EdMeta } from "../../logic/ed-global"; +import { EDGlobal, EdMeta, active } from "../../logic/ed-global"; import { indentHook } from "./node/item/indent-hook"; import { canDrop, nodeOnDrop } from "./node/on-drop"; import { nodeRender } from "./node/render"; @@ -25,10 +25,12 @@ export const EdTreeBody = () => { if (p.ui.tree.search) { tree = doTreeSearch(p); } else { + if (active.comp_id) { + } tree = p.page.tree; } - if (p.page.tree.length === 0) + if (tree.length === 0) return (
@@ -43,6 +45,7 @@ export const EdTreeBody = () => {
); + return ( + {isComponent && ( + { + const comp_id = item.component?.id; + if (comp_id) { + active.comp_id = comp_id; + p.render(); + } + }} + > + <>Edit + + )} + {!isComponent && ( -
`, }} >
- + )} ); diff --git a/app/web/src/nova/ed/panel/tree/node/item/action/new-comp.tsx b/app/web/src/nova/ed/panel/tree/node/item/action/new-comp.tsx index e0aab3e8..81c2f525 100644 --- a/app/web/src/nova/ed/panel/tree/node/item/action/new-comp.tsx +++ b/app/web/src/nova/ed/panel/tree/node/item/action/new-comp.tsx @@ -16,11 +16,11 @@ export const edActionNewComp = ( let item_id = active.item_id; p.ui.tree.item_loading.push(item_id); await treeRebuild(p); - if (p.comp.cur.id) { + if (active.comp_id) { await p.sync.comp.new({ group_id, item, - comp_id: p.comp.cur.id, + comp_id: active.comp_id, item_id: active.item_id, }); } else { diff --git a/app/web/src/nova/ed/panel/tree/node/item/ctx-menu.tsx b/app/web/src/nova/ed/panel/tree/node/item/ctx-menu.tsx index 104360a9..534816dc 100644 --- a/app/web/src/nova/ed/panel/tree/node/item/ctx-menu.tsx +++ b/app/web/src/nova/ed/panel/tree/node/item/ctx-menu.tsx @@ -3,19 +3,19 @@ import { useGlobal, useLocal } from "web-utils"; import { IItem } from "../../../../../../utils/types/item"; import { FNComponent } from "../../../../../../utils/types/meta-fn"; import { Menu, MenuItem } from "../../../../../../utils/ui/context-menu"; -import { EDGlobal, EdMeta } from "../../../../logic/ed-global"; +import { EDGlobal, EdMeta, active } from "../../../../logic/ed-global"; import { edActionAttach } from "./action/attach"; import { edActionClone } from "./action/clone"; import { edActionCopy } from "./action/copy"; import { edActionCut } from "./action/cut"; +import { edActionDelete } from "./action/del"; import { edActionDetach } from "./action/detach"; import { edActionHide } from "./action/hide"; import { edActionNewComp } from "./action/new-comp"; import { edActionPaste } from "./action/paste"; -import { edActionWrap } from "./action/wrap"; -import { edActionUnwrap } from "./action/unwrap"; import { edActionRename } from "./action/rename"; -import { edActionDelete } from "./action/del"; +import { edActionUnwrap } from "./action/unwrap"; +import { edActionWrap } from "./action/wrap"; export const EdTreeCtxMenu = ({ node, @@ -52,9 +52,8 @@ export const EdTreeCtxMenu = ({ const item = node.data?.item; const type = item?.type; const comp = (item as IItem).component as FNComponent | undefined; - const rootComp = p.comp.cur; const isComponent = comp?.id; - const isActiveComponent = rootComp && rootComp.id === item?.id && rootComp.id; + const isActiveComponent = active.comp_id === item?.id; if (!item) { return ( diff --git a/app/web/src/nova/ed/panel/tree/node/on-drop.tsx b/app/web/src/nova/ed/panel/tree/node/on-drop.tsx index 48837afb..2256462e 100644 --- a/app/web/src/nova/ed/panel/tree/node/on-drop.tsx +++ b/app/web/src/nova/ed/panel/tree/node/on-drop.tsx @@ -1,7 +1,7 @@ import { DropOptions, NodeModel } from "@minoru/react-dnd-treeview"; -import { EdMeta, PG } from "../../../logic/ed-global"; -import { MContent } from "../../../../../utils/types/general"; import get from "lodash.get"; +import { MContent } from "../../../../../utils/types/general"; +import { EdMeta, PG, active } from "../../../logic/ed-global"; export const nodeOnDrop: ( tree: NodeModel[], @@ -48,7 +48,7 @@ export const canDrop = (p: PG, arg: DropOptions) => { dropTarget.data.item.component?.id ) { if (p.comp) { - if (p.comp.cur.id === dropTarget.data.item.component?.id) { + if (active.comp_id === dropTarget.data.item.component?.id) { return true; } } @@ -65,7 +65,7 @@ export const canDrop = (p: PG, arg: DropOptions) => { dropTarget.data.item.component?.id ) { if (p.comp) { - if (p.comp.cur.id === dropTarget.data.item.component.id) { + if (active.comp_id === dropTarget.data.item.component.id) { return true; } } diff --git a/app/web/src/nova/view/logic/types.ts b/app/web/src/nova/view/logic/types.ts index 57cbe91a..30bdfe00 100644 --- a/app/web/src/nova/view/logic/types.ts +++ b/app/web/src/nova/view/logic/types.ts @@ -1,6 +1,3 @@ -import { component } from "../../../../../db/db"; -import { IItem } from "../../../utils/types/item"; -import { FNComponent } from "../../../utils/types/meta-fn"; import { EdMeta, PG } from "../../ed/logic/ed-global"; export type VLoad = diff --git a/app/web/src/render/live/logic/mobile.ts b/app/web/src/render/live/logic/mobile.ts index 3b45c6ce..94180e64 100644 --- a/app/web/src/render/live/logic/mobile.ts +++ b/app/web/src/render/live/logic/mobile.ts @@ -53,7 +53,6 @@ export const registerMobile = () => { if (window.parent) { window.addEventListener("message", async ({ data: raw }) => { if (typeof raw === "object" && raw.mobile) { - console.log("capacitor", raw); const data = raw as unknown as | { type: "notification-token"; diff --git a/app/web/src/utils/script/typings.ts b/app/web/src/utils/script/typings.ts index bb6b23b1..731f1bd2 100644 --- a/app/web/src/utils/script/typings.ts +++ b/app/web/src/utils/script/typings.ts @@ -109,7 +109,7 @@ import "./api" import type * as SRVAPI from "${apiPath}";` )} -declare global {; +declare global { const db: prisma.PrismaClient; ${baseTypings}