diff --git a/.vscode/settings.json b/.vscode/settings.json index 56d158ae..fc334dae 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,6 @@ "**/CVS": true, "**/.DS_Store": true, "**/Thumbs.db": true - } + }, + "hide-files.files": [] } diff --git a/app/srv/ws/sync/editor/load-page.ts b/app/srv/ws/sync/editor/load-page.ts index 6a56a83a..f7141d01 100644 --- a/app/srv/ws/sync/editor/load-page.ts +++ b/app/srv/ws/sync/editor/load-page.ts @@ -260,33 +260,42 @@ export const serverWalkMap = ( let id = item.originalId || item.id; const pcomp = p.scope_comps[arg.parent_mcomp.id]; - pcomp.scope[id] = { - p: arg.parent_mcomp.parent_ids, - n: item.name, - s: null, - }; - - const js = item.adv?.js; - if (typeof js === "string") { - const scope = parseJs(js); - if (scope) pcomp.scope[id].s = scope; + if (!pcomp) { + console.log(arg.parent_mcomp.id); } - if (item.name.startsWith("jsx=")) { - const name = item.name.substring(4).trim(); - if (arg.parent_mcomp.jsx_props[name]) { - const jsx = arg.parent_mcomp.jsx_props[name]; - serverWalkMap(p, { - mitem: jsx.mitem, - parent_item: { id: item.id, mitem: mitem as MItem }, - parent_mcomp: jsx.parent_mcomp - ? { - ...jsx.parent_mcomp, - parent_ids: [...(arg.parent_ids || []), mitem.get("id") || ""], - } - : undefined, - parent_ids: [...arg.parent_ids, mitem.get("id") || ""], - }); + if (pcomp) { + pcomp.scope[id] = { + p: arg.parent_mcomp.parent_ids, + n: item.name, + s: null, + }; + + const js = item.adv?.js; + if (typeof js === "string") { + const scope = parseJs(js); + if (scope) pcomp.scope[id].s = scope; + } + + if (item.name.startsWith("jsx=")) { + const name = item.name.substring(4).trim(); + if (arg.parent_mcomp.jsx_props[name]) { + const jsx = arg.parent_mcomp.jsx_props[name]; + serverWalkMap(p, { + mitem: jsx.mitem, + parent_item: { id: item.id, mitem: mitem as MItem }, + parent_mcomp: jsx.parent_mcomp + ? { + ...jsx.parent_mcomp, + parent_ids: [ + ...(arg.parent_ids || []), + mitem.get("id") || "", + ], + } + : undefined, + parent_ids: [...arg.parent_ids, mitem.get("id") || ""], + }); + } } } } else { diff --git a/app/web/src/nova/ed/logic/ed-global.ts b/app/web/src/nova/ed/logic/ed-global.ts index c4a087f6..b3c886ef 100644 --- a/app/web/src/nova/ed/logic/ed-global.ts +++ b/app/web/src/nova/ed/logic/ed-global.ts @@ -241,6 +241,7 @@ export const EDGlobal = { responsive?: string; }, comp: { + preview_id: "", open: null as null | ((comp_id: string) => void | Promise), }, comp_group: null as null | { diff --git a/app/web/src/nova/ed/panel/header/mid/page-picker.tsx b/app/web/src/nova/ed/panel/header/mid/page-picker.tsx index d244b280..ac94856c 100644 --- a/app/web/src/nova/ed/panel/header/mid/page-picker.tsx +++ b/app/web/src/nova/ed/panel/header/mid/page-picker.tsx @@ -21,7 +21,7 @@ export const EdPagePicker = () => { __html: ``, }} > -
+
{p.page.cur.name}
diff --git a/app/web/src/nova/ed/panel/popup/comp/comp-popup.tsx b/app/web/src/nova/ed/panel/popup/comp/comp-popup.tsx index 19a2f280..ef50a265 100644 --- a/app/web/src/nova/ed/panel/popup/comp/comp-popup.tsx +++ b/app/web/src/nova/ed/panel/popup/comp/comp-popup.tsx @@ -4,14 +4,15 @@ import { TreeMethods, getBackendOptions, } from "@minoru/react-dnd-treeview"; -import { useEffect } from "react"; import { HTML5Backend } from "react-dnd-html5-backend"; +import { useEffect } from "react"; import { useGlobal, useLocal } from "web-utils"; import { Loading } from "../../../../../utils/ui/loading"; import { Modal } from "../../../../../utils/ui/modal"; -import { EDGlobal } from "../../../logic/ed-global"; +import { EDGlobal, active } from "../../../logic/ed-global"; import { compPicker, reloadCompPicker } from "./comp-reload"; import { CompItem, edPageTreeRender } from "./comp-tree"; +import { EdCompPreview } from "./comp-preview"; export const EdPopComp = () => { const p = useGlobal(EDGlobal, "EDITOR"); @@ -27,6 +28,10 @@ export const EdPopComp = () => { if (!p.ui.popup.comp.open) return null; + if (!compPicker.active_id && active.item_id) { + compPicker.active_id = active.item_id; + } + if (p.site.id !== compPicker.site_id) { compPicker.site_id = p.site.id; reloadCompPicker(p); @@ -42,6 +47,7 @@ export const EdPopComp = () => { p.render(); } }} + fade={false} >
{ }} className={cx("absolute inset-[5%] bg-white flex")} > -
+
{compPicker.status === "loading" && ( )} - - {compPicker.ref && ( - - { - if (local.tree !== ref) { - local.tree = ref; - } - }} - tree={compPicker.tree} - rootId={"comp-root"} - onDrop={() => {}} - dragPreviewRender={() => <>} - canDrag={() => true} - classes={{ root: "flex-1" }} - render={edPageTreeRender} - /> - + {compPicker.status !== "loading" && ( + <> +
+
.tree-root > .listitem > .container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + position: relative; + } + ` + )} + > + {compPicker.ref && compPicker.status === "ready" && ( + + { + if (local.tree !== ref) { + local.tree = ref; + } + }} + tree={compPicker.tree} + initialOpen={true} + rootId={"comp-root"} + onDrop={() => {}} + dragPreviewRender={() => <>} + canDrag={() => true} + classes={{ + root: "tree-root flex-1", + listItem: "listitem", + container: "container", + }} + render={edPageTreeRender} + /> + + )} +
+
+ + )}
diff --git a/app/web/src/nova/ed/panel/popup/comp/comp-preview-tree.tsx b/app/web/src/nova/ed/panel/popup/comp/comp-preview-tree.tsx new file mode 100644 index 00000000..e0a8316e --- /dev/null +++ b/app/web/src/nova/ed/panel/popup/comp/comp-preview-tree.tsx @@ -0,0 +1,67 @@ +import { + Tree as DNDTree, + DndProvider, + NodeModel, + getBackendOptions, +} from "@minoru/react-dnd-treeview"; +import { FC, useEffect } from "react"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { EDGlobal, EdMeta } from "../../../logic/ed-global"; +import { compPicker } from "./comp-reload"; +import { nodeRender } from "../../tree/node/render"; +import { useGlobal, useLocal } from "web-utils"; + +export const EdCompPreviewTree: FC<{ tree: NodeModel[] }> = ({ + tree, +}) => { + const p = useGlobal(EDGlobal, "EDITOR"); + const local = useLocal({ ref: null as any }); + const TypedTree = DNDTree; + + useEffect(() => { + if (local.ref) local.ref.openAll(); + }, [p.ui.popup.comp.preview_id]); + + return ( +
{ + if (!compPicker.preview_ref) { + setTimeout(p.render, 100); + } + if (ref) compPicker.preview_ref = ref; + }} + > +
+ {compPicker.preview_ref && ( + + { + if (ref) local.ref = ref; + }} + initialOpen={true} + rootId={"root"} + onDrop={() => {}} + dragPreviewRender={() => <>} + canDrag={() => false} + classes={{ + root: "tree-root flex-1 text-xs", + listItem: "listitem", + container: "container", + }} + render={nodeRender} + /> + + )} +
+
+ ); +}; diff --git a/app/web/src/nova/ed/panel/popup/comp/comp-preview.tsx b/app/web/src/nova/ed/panel/popup/comp/comp-preview.tsx new file mode 100644 index 00000000..076560e0 --- /dev/null +++ b/app/web/src/nova/ed/panel/popup/comp/comp-preview.tsx @@ -0,0 +1,156 @@ +import { FC, useEffect } from "react"; +import { useGlobal } from "web-utils"; +import { produceCSS } from "../../../../../utils/css/gen"; +import { IItem } from "../../../../../utils/types/item"; +import { IText } from "../../../../../utils/types/text"; +import { EDGlobal, PG, active } from "../../../logic/ed-global"; +import { loadComponent } from "../../../logic/tree/sync-walk"; +import { EdCompPreviewTree } from "./comp-preview-tree"; +import { compPicker } from "./comp-reload"; + +export const EdCompPreview = () => { + const p = useGlobal(EDGlobal, "EDITOR"); + const comp_id = p.ui.popup.comp.preview_id; + const ref = p.comp.list[comp_id]; + + const item = ref?.doc?.getMap("map").get("root")?.toJSON() as + | IItem + | undefined; + + useEffect(() => { + if (!p.comp.list[comp_id] && !!comp_id) { + loadComponent(p, comp_id).then(() => { + p.render(); + }); + } + }, [comp_id]); + + return ( +
+ {comp_id && item && ( +
+
+
Preview
+
+ {comp_id} +
+
+
+
{ + e.stopPropagation(); + + if (p.ui.popup.comp.open) { + p.ui.popup.comp.open(item.id); + } + p.ui.popup.comp.open = null; + + active.item_id = compPicker.active_id; + compPicker.active_id = ""; + + p.render(); + }} + > + Select Component +
+
{ + e.stopPropagation(); + if (confirm("Are you sure to delete this component?")) { + await db.component.delete({ + where: { id: p.ui.popup.comp.preview_id }, + }); + const idx = + compPicker.tree.findIndex((e) => e.id === comp_id) + 1; + + if (idx >= 0 && compPicker.tree[idx]) + p.ui.popup.comp.preview_id = compPicker.tree[idx].id as any; + + compPicker.tree = compPicker.tree.filter( + (e) => e.id !== comp_id + ); + p.render(); + } + }} + > + +
+
+
+ )} +
+
+ {comp_id ? ( + <> + {item && ref ? ( +
+ +
+ +
+
+ ) : ( +
Loading...
+ )} + + ) : ( + <> + Select component +
to preview + + )} +
+
+
+ ); +}; + +const CItem: FC<{ item: IItem | IText; p: PG }> = ({ item, p }) => { + const className = produceCSS(item, { + mode: p.mode, + }); + + if (item.type === "item") { +
+ {item.type === "item" && + item.childs.map((e) => { + return ; + })} +
; + } + + return ( +
+ ); +}; + +const DeleteIcon = () => ( + + + +); diff --git a/app/web/src/nova/ed/panel/popup/comp/comp-reload.ts b/app/web/src/nova/ed/panel/popup/comp/comp-reload.ts index d5dc5622..28e0d421 100644 --- a/app/web/src/nova/ed/panel/popup/comp/comp-reload.ts +++ b/app/web/src/nova/ed/panel/popup/comp/comp-reload.ts @@ -5,10 +5,12 @@ import { CompItem } from "./comp-tree"; export const compPicker = { site_id: "", ref: null as any, + preview_ref: null as any, tab: "all" as "all" | "trash", tree: [] as NodeModel[], trash: [] as NodeModel[], status: "ready" as "loading" | "ready", + active_id: "", render: () => {}, }; @@ -61,8 +63,6 @@ export const reloadCompPicker = async (p: PG) => { } } - console.log(compPicker.tree); - compPicker.status = "ready"; compPicker.render(); }; diff --git a/app/web/src/nova/ed/panel/popup/comp/comp-tree.tsx b/app/web/src/nova/ed/panel/popup/comp/comp-tree.tsx index 47b23bf4..0478d905 100644 --- a/app/web/src/nova/ed/panel/popup/comp/comp-tree.tsx +++ b/app/web/src/nova/ed/panel/popup/comp/comp-tree.tsx @@ -1,7 +1,7 @@ import { NodeModel, NodeRender } from "@minoru/react-dnd-treeview"; import { FC } from "react"; import { useGlobal, useLocal } from "web-utils"; -import { EDGlobal } from "../../../logic/ed-global"; +import { EDGlobal, active } from "../../../logic/ed-global"; import { compPicker, reloadCompPicker } from "./comp-reload"; export type CompItem = { @@ -21,7 +21,7 @@ export const edPageTreeRender: NodeRender = ( return (
= ( opacity: 1; } `, - item.id === p.page.cur.id && `bg-blue-50` + item.id === p.page.cur.id && `bg-blue-50`, + item.type === "component" && "m-1 border flex-1", + item.type === "folder" && "border-t py-[2px] items-center", + item.id === p.ui.popup.comp.preview_id && + css` + border: 1px solid blue !important; + ` )} onClick={() => { if (item.type === "folder") { onToggle(); - } else if (p.ui.popup.page.open) { - p.ui.popup.page.open(item.id); + } else { + p.ui.popup.comp.preview_id = item.id; + p.render(); } }} > -
- {item.id === p.page.cur.id && ( -
+ {item.id === p.page.cur.id && ( +
+ )} +
- {item.type === "folder" && ( - <> - {isOpen && } - {!isOpen && } - + >
+ {item.type === "folder" && ( + <> + {isOpen && } + {!isOpen && } + + )} +
- {local.renaming ? ( - { - local.renaming = false; - item.name = local.rename_to; - if (item.id === "") { - if (item.name) { - db.page_folder.create({ - data: { name: local.rename_to, id_site: p.site.id }, - }); - } - await reloadCompPicker(p); - } else { - db.page_folder.update({ - where: { id: item.id }, - data: { name: local.rename_to }, + > + {local.renaming ? ( + { + local.renaming = false; + item.name = local.rename_to; + if (item.id === "") { + if (item.name) { + db.page_folder.create({ + data: { name: local.rename_to, id_site: p.site.id }, }); } + await reloadCompPicker(p); + } else { + db.page_folder.update({ + where: { id: item.id }, + data: { name: local.rename_to }, + }); + } + local.render(); + }} + className="border px-1 bg-white flex-1 outline-none mr-1 border-blue-500 " + onChange={(e) => { + local.rename_to = e.currentTarget.value; + local.render(); + }} + onKeyDown={(e) => { + if (e.key === "Enter") e.currentTarget.blur(); + if (e.key === "Escape") { + local.rename_to = item.name; local.render(); - }} - className="border px-1 bg-white flex-1 outline-none mr-1 border-blue-500 " - onChange={(e) => { - local.rename_to = e.currentTarget.value; - local.render(); - }} - onKeyDown={(e) => { - if (e.key === "Enter") e.currentTarget.blur(); - if (e.key === "Escape") { - local.rename_to = item.name; - local.render(); - e.currentTarget.blur(); - } - }} - /> - ) : ( - - )} -
- - {!local.renaming && ( -
- {item.type === "folder" && ( - <> -
{ - e.stopPropagation(); - compPicker.tree.push({ - id: "", - parent: item.id, - text: "", - data: { - id: "", - name: "", - type: "folder", - }, - }); - p.render(); - }} - > - + Folder -
-
{ - e.stopPropagation(); - - p.ui.popup.page.form = { - id_site: p.site.id, - id_folder: item.id === "root" ? null : item.id, - }; - p.render(); - }} - > - + Page -
- - )} - {item.id !== "root" && ( - <> -
{ - e.stopPropagation(); - if (item.type === "folder") { - local.rename_to = item.name; - local.renaming = true; - local.render(); - } else { - p.ui.popup.page.form = item; - p.render(); - } - }} - > - -
-
{ - e.stopPropagation(); - if (confirm("Deletting cannot be undone. Are you sure ?")) { - if (item.type === "folder") { - await db.page.updateMany({ - where: { id_folder: node.id as string }, - data: { - id_folder: - node.parent === "root" - ? null - : (node.parent as string), - }, - }); - await db.page_folder.update({ - where: { id: node.id as string }, - data: { - is_deleted: true, - }, - }); - } else { - await db.page.update({ - where: { id: node.id as string }, - data: { - is_deleted: true, - }, - }); - } - - await reloadCompPicker(p); - } - }} - > - -
- - )} -
+ e.currentTarget.blur(); + } + }} + /> + ) : ( + )}
+ + {item.type === "component" && ( +
{ + e.stopPropagation(); + + if (p.ui.popup.comp.open) { + p.ui.popup.comp.open(item.id); + } + p.ui.popup.comp.open = null; + + console.log(active.item_id, compPicker.active_id); + active.item_id = compPicker.active_id; + compPicker.active_id = ""; + + p.page.render(); + }} + > + +
+ )}
); }; @@ -206,7 +139,7 @@ const Name: FC<{ name: string }> = ({ name }) => { if (name.startsWith("layout::")) { return (
-
+
LAYOUT
{name.substring("layout::".length)}
@@ -216,7 +149,13 @@ const Name: FC<{ name: string }> = ({ name }) => { return
{name}
; }; - +const ImageIcon = () => ( +
`, + }} + >
+); const DeleteIcon = () => ( { }} className={cx("absolute inset-[5%] bg-white flex")} > -
+
{pagePicker.status === "loading" && ( )}