diff --git a/app/web/src/nova/ed/logic/active/get-meta.tsx b/app/web/src/nova/ed/logic/active/get-meta.tsx index fb2e2dac..c1e6df5f 100644 --- a/app/web/src/nova/ed/logic/active/get-meta.tsx +++ b/app/web/src/nova/ed/logic/active/get-meta.tsx @@ -18,5 +18,17 @@ export const getMetaById = (p: PG, id: string) => { }; export const getActiveMeta = (p: PG) => { - return getMetaById(p, active.item_id); + const meta = getMetaById(p, active.item_id); + if (meta) return meta; + + if (active.comp_id) { + const comp = p.comp.list[active.comp_id]; + if (comp) { + const first = comp.tree.find((e) => e.parent === "root"); + if (first && first.data?.item) return first.data; + } + } + + const first = p.page.tree.find((e) => e.parent === "root"); + if (first && first.data?.item) return first.data; }; diff --git a/app/web/src/nova/ed/panel/header/mid/comp-picker.tsx b/app/web/src/nova/ed/panel/header/mid/comp-picker.tsx index 2ca815ec..bb81747f 100644 --- a/app/web/src/nova/ed/panel/header/mid/comp-picker.tsx +++ b/app/web/src/nova/ed/panel/header/mid/comp-picker.tsx @@ -9,6 +9,7 @@ import { loadComponent } from "../../../logic/comp/load"; import { EDGlobal, active } from "../../../logic/ed-global"; import { fillID } from "../../../logic/tree/fill-id"; import { TopBtn } from "../top-btn"; +import { useEffect } from "react"; export const EdCompPicker = () => { const p = useGlobal(EDGlobal, "EDITOR"); @@ -36,7 +37,9 @@ export const EdCompPicker = () => { let active_meta = getActiveMeta(p); if (!active_meta) { alert("Please select an item/section to add component!"); - } else { + } + + if (active_meta) { let item = active_meta.item as IContent; if ( item.type === "item" && @@ -68,9 +71,9 @@ export const EdCompPicker = () => { p.render(); }; - // useEffect(() => { - // activatePopup(); - // }, []); + useEffect(() => { + activatePopup(); + }, []); return ( { className={cx( "absolute inset-0", css` + > .container { + max-width: 100%; + } + > .tree-root > .listitem:first-child > div { border-top: 0; } @@ -216,7 +220,6 @@ export const EdPopComp = () => { - )} 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 index c0ca069d..794e94bc 100644 --- a/app/web/src/nova/ed/panel/popup/comp/comp-preview.tsx +++ b/app/web/src/nova/ed/panel/popup/comp/comp-preview.tsx @@ -3,12 +3,10 @@ import { useGlobal } from "web-utils"; import { produceCSS } from "../../../../../utils/css/gen"; import { IItem } from "../../../../../utils/types/item"; import { IText } from "../../../../../utils/types/text"; +import { loadComponent } from "../../../logic/comp/load"; import { EDGlobal, PG, active } from "../../../logic/ed-global"; import { EdCompPreviewTree } from "./comp-preview-tree"; import { compPicker, reloadCompPicker } from "./comp-reload"; -import { loadComponent } from "../../../logic/comp/load"; -import { NodeModel } from "@minoru/react-dnd-treeview"; -import { CompItem } from "./comp-tree"; export const EdCompPreview = () => { const p = useGlobal(EDGlobal, "EDITOR"); 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 f8d62515..856a56ff 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 @@ -4,7 +4,7 @@ import { useGlobal, useLocal } from "web-utils"; import { EDGlobal, active } from "../../../logic/ed-global"; import { compPicker, reloadCompPicker } from "./comp-reload"; import { treeRebuild } from "../../../logic/tree/build"; - +import tc from "tinycolor2"; export type CompItem = { id: string; name: string; @@ -21,10 +21,64 @@ export const edPageTreeRender: NodeRender = ( const isTrashed = !!compPicker.trash.find((e) => e.id === item.id); + const addComponent = (e: React.MouseEvent) => { + if (isTrashed) { + p.ui.popup.comp.preview_id = item.id; + p.ui.popup.comp_group = { + mouse_event: e, + async on_pick(group_id) { + await _db.component.update({ + where: { id: item.id }, + data: { id_component_group: group_id }, + }); + await reloadCompPicker(p); + treeRebuild(p); + p.render(); + }, + }; + p.render(); + } else { + 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 = ""; + treeRebuild(p); + p.render(); + } + }; + + const delComponent = async (comp_id: string) => { + if (isTrashed) { + if (confirm("Permanently 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(); + } + } else { + if (confirm("Move component to trash?")) { + await _db.component.update({ + where: { id: comp_id }, + data: { id_component_group: compPicker.trash_id }, + }); + await reloadCompPicker(p); + p.render(); + } + } + }; + return (
= ( } `, 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.type === "component" && "ml-1 mr-2 mb-3 border flex-1", + item.type === "component" && + css` + min-width: 190px; + `, + item.type === "folder" && "border-t py-[2px] ", item.id === p.ui.popup.comp.preview_id && css` border: 1px solid blue !important; ` )} - onClick={() => { + onClick={(e) => { if (item.type === "folder") { onToggle(); } else { - if (p.ui.popup.comp.preview_id !== item.id) { - p.ui.popup.comp.preview_id = item.id; - } else { - p.ui.popup.comp.preview_id = ""; - } - p.render(); + addComponent(e); } }} > - {item.id === p.page.cur.id && ( -
- )} + {item.type === "component" && }
- {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.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(); - } - }} - /> - ) : ( - + {item.id === p.page.cur.id && ( +
)} -
- - {item.type === "component" && (
+ {item.type === "folder" && ( + <> + {isOpen && } + {!isOpen && } + + )} +
{ - e.stopPropagation(); - - if (isTrashed) { - p.ui.popup.comp.preview_id = item.id; - p.ui.popup.comp_group = { - mouse_event: e, - async on_pick(group_id) { - await _db.component.update({ - where: { id: item.id }, - data: { id_component_group: group_id }, - }); - await reloadCompPicker(p); - treeRebuild(p); - p.render(); - }, - }; - p.render(); - } else { - 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 = ""; - treeRebuild(p); - p.render(); - } - }} > -
- -
-
- {isTrashed ? : } -
+ {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(); + e.currentTarget.blur(); + } + }} + /> + ) : ( + + )}
- )} + + {item.type === "component" && ( +
{ + e.stopPropagation(); + delComponent(item.id); + }} + > +
+ +
+
+ +
+
+ )} +
); }; +const colorize = (str: string) => { + let hash = 0; + if (str.length === 0) return ""; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + hash = hash & hash; + } + var color = "#"; + for (var i = 0; i < 3; i++) { + var value = (hash >> (i * 8)) & 255; + color += ("00" + value.toString(16)).substr(-2); + } + return color; +}; + const Name: FC<{ name: ReactNode }> = ({ name }) => { if (typeof name !== "string") return name; if (name === "__TRASH__") return "Trash"; @@ -192,6 +244,26 @@ const Name: FC<{ name: ReactNode }> = ({ name }) => { return
{name}
; }; +const Pic: FC<{ name: string }> = ({ name }) => { + const bg = colorize(name); + const fg = tc(bg); + return ( +
+ {name.split("_").join(" ")} +
+ ); +}; + const CheckIcon = () => (
{ ? p.comp.list[active.comp_id].meta[active.item_id] : p.page.meta[active.item_id]; - let item = meta.item; + let item = meta?.item; let is_inherit = false; - if (item.component?.id) { + if (item?.component?.id) { const comp = p.comp.list[item.component.id].doc .getMap("map") .get("root") diff --git a/bun.lockb b/bun.lockb index e237a1b4..43fc8360 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/pkgs/core/server/serve-static.ts b/pkgs/core/server/serve-static.ts index dd399d2b..483a1284 100644 --- a/pkgs/core/server/serve-static.ts +++ b/pkgs/core/server/serve-static.ts @@ -4,7 +4,6 @@ import { InspectTreeResult } from "fs-jetpack/types"; import { join } from "path"; import { watch } from "fs"; import { CORS_HEADERS } from "./serve-api"; - import mime from "mime"; import { g } from "utils/global"; @@ -26,8 +25,30 @@ const cache = { >, }; -export const serveStatic = { - init: async () => { +export const serveStatic: any = { + async init() { + await this.walk(); + if (g.mode === "dev") { + watch(dir.path(`app/static`), async (_, filename) => { + if (filename) { + const path = join("static", filename); + try { + const file = Bun.file(dir.path(`app/${path}`)); + if (await file.exists()) { + cache.static[`/${filename}`] = { + type: mime.getType(path) || "application/octet-stream", + compression: g.mode === "prod" ? "br" : "", + content: await file.arrayBuffer(), + }; + } + } catch (e: any) { + cache.static = {} + } + } + }); + } + }, + walk: async () => { const list = await inspectTreeAsync(dir.path(`app/${web.path}`)); const walk = async ( list: InspectTreeResult, @@ -52,31 +73,11 @@ export const serveStatic = { if (list) { await walk(list); } - - if (g.mode === "dev") { - watch(dir.path(`app/static`), async (_, filename) => { - if (filename) { - const path = join("static", filename); - try { - const file = Bun.file(dir.path(`app/${path}`)); - if (await file.exists()) { - cache.static[`/${filename}`] = { - type: mime.getType(path) || "application/octet-stream", - compression: g.mode === "prod" ? "br" : "", - content: await file.arrayBuffer(), - }; - } - } catch (e: any) { - cache.static = {} - } - } - }); - } }, exists: (url: URL) => { return !!cache.static[url.pathname]; }, - serve: (url: URL) => { + async serve(url: URL) { let file = cache.static[url.pathname]; if (file) { return new Response(file.content, { @@ -88,6 +89,10 @@ export const serveStatic = { }); } + if (g.mode === 'dev' && url.pathname.endsWith('.js')) { + await this.walk(); + } + file = cache.static["/index.html"]; if (file) { return new Response(file.content, {