wip fix
This commit is contained in:
parent
4711bafef1
commit
51d0f5accd
|
|
@ -146,24 +146,25 @@ model org_user {
|
|||
}
|
||||
|
||||
model page {
|
||||
id String @id(map: "page_id") @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
id String @id(map: "page_id") @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
name String
|
||||
url String
|
||||
content_tree Json
|
||||
id_site String @db.Uuid
|
||||
created_at DateTime? @default(now()) @db.Timestamp(6)
|
||||
id_site String @db.Uuid
|
||||
created_at DateTime? @default(now()) @db.Timestamp(6)
|
||||
js_compiled String?
|
||||
js String?
|
||||
updated_at DateTime? @default(now()) @db.Timestamp(6)
|
||||
id_folder String? @db.Uuid
|
||||
is_deleted Boolean @default(false)
|
||||
id_layout String? @db.Uuid
|
||||
is_default_layout Boolean @default(false)
|
||||
updated_at DateTime? @default(now()) @db.Timestamp(6)
|
||||
id_folder String? @db.Uuid
|
||||
is_deleted Boolean @default(false)
|
||||
id_layout String? @db.Uuid
|
||||
is_default_layout Boolean @default(false)
|
||||
code_assign code_assign[]
|
||||
page_folder page_folder? @relation(fields: [id_folder], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
page page? @relation("pageTopage", fields: [id_layout], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
other_page page[] @relation("pageTopage")
|
||||
site site @relation(fields: [id_site], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
page_folder page_folder? @relation(fields: [id_folder], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
page page? @relation("pageTopage", fields: [id_layout], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
other_page page[] @relation("pageTopage")
|
||||
site site @relation(fields: [id_site], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
page_history page_history[]
|
||||
}
|
||||
|
||||
model page_folder {
|
||||
|
|
@ -307,3 +308,11 @@ model code_assign {
|
|||
component_group component_group? @relation(fields: [id_component_group], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
page page? @relation(fields: [id_page], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
}
|
||||
|
||||
model page_history {
|
||||
id String @id(map: "page_history_id") @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
id_page String @db.Uuid
|
||||
content_tree Bytes
|
||||
ts String
|
||||
page page @relation(fields: [id_page], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { SyncConnection } from "../type";
|
|||
import { parseJs } from "../editor/parser/parse-js";
|
||||
import { snapshot } from "../entity/snapshot";
|
||||
import { validate } from "uuid";
|
||||
import { gzipAsync } from "utils/diff";
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
const timeout = {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ import { SAction } from "../actions";
|
|||
import { docs } from "../entity/docs";
|
||||
import { gunzipAsync } from "../entity/zlib";
|
||||
import { SyncConnection } from "../type";
|
||||
import { gzipAsync } from "utils/diff";
|
||||
|
||||
const history = {} as Record<string, string>;
|
||||
|
||||
export const yjs_diff_local: SAction["yjs"]["diff_local"] = async function (
|
||||
this: SyncConnection,
|
||||
|
|
@ -25,6 +28,32 @@ export const yjs_diff_local: SAction["yjs"]["diff_local"] = async function (
|
|||
if (root) {
|
||||
if (mode === "page") {
|
||||
if (validate(id) && id) {
|
||||
let mode = "create" as "create" | "update";
|
||||
const cur = Math.round(Date.now() / 5000) + "";
|
||||
if (history[id] && history[id] === cur) {
|
||||
mode = "update";
|
||||
}
|
||||
history[id] = cur;
|
||||
if (mode === "create") {
|
||||
await _db.page_history.create({
|
||||
data: {
|
||||
id_page: id,
|
||||
content_tree: await gzipAsync(JSON.stringify(root.toJSON())),
|
||||
ts: history[id],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await _db.page_history.updateMany({
|
||||
data: {
|
||||
content_tree: await gzipAsync(JSON.stringify(root.toJSON())),
|
||||
},
|
||||
where: {
|
||||
id_page: id,
|
||||
ts: history[id],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await _db.page.update({
|
||||
where: { id },
|
||||
data: {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { EdPopComp } from "./panel/popup/comp/comp-popup";
|
|||
import { EdPopPage } from "./panel/popup/page/page-popup";
|
||||
import { EdPopScript } from "./panel/popup/script/pop-script";
|
||||
import { EdPopSite } from "./panel/popup/site/site-popup";
|
||||
import { EdPageHistoryMain } from "./panel/main/main-history";
|
||||
|
||||
export const EdBase = () => {
|
||||
const p = useGlobal(EDGlobal, "EDITOR");
|
||||
|
|
@ -51,22 +52,27 @@ export const EdBase = () => {
|
|||
)}
|
||||
<div className="flex flex-1 flex-col items-stretch">
|
||||
<EdMid />
|
||||
<div
|
||||
className={cx(
|
||||
"flex flex-1 items-stretch",
|
||||
p.mode === "mobile" ? mobileCSS : "bg-white"
|
||||
)}
|
||||
>
|
||||
{p.status !== "ready" ? (
|
||||
<Loading note={`page-${p.status}`} />
|
||||
) : (
|
||||
<>
|
||||
<EdMain />
|
||||
<EdPane type="right" min_size={240} />
|
||||
<EdRight />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{p.page.history.id ? (
|
||||
<EdPageHistoryMain />
|
||||
) : (
|
||||
<div
|
||||
className={cx(
|
||||
"flex flex-1 items-stretch",
|
||||
p.mode === "mobile" ? mobileCSS : "bg-white"
|
||||
)}
|
||||
>
|
||||
{p.status !== "ready" ? (
|
||||
<Loading note={`page-${p.status}`} />
|
||||
) : (
|
||||
<>
|
||||
<EdMain />
|
||||
<EdPane type="right" min_size={240} />
|
||||
<EdRight />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -7,11 +7,17 @@ import { EdApi } from "./panel/header/left/api";
|
|||
import { EdSiteJS } from "./panel/header/left/js";
|
||||
import { EdSitePicker } from "./panel/header/left/site-picker";
|
||||
import { EdTreeBody } from "./panel/tree/body";
|
||||
import { EdPageHistoryBtn } from "./panel/tree/history-btn";
|
||||
import { EdPageHistoryList } from "./panel/tree/history-list";
|
||||
import { EdTreeSearch } from "./panel/tree/search";
|
||||
import { treeRebuild } from "./logic/tree/build";
|
||||
|
||||
export const EdLeft = () => {
|
||||
const p = useGlobal(EDGlobal, "EDITOR");
|
||||
const local = useLocal({ tree: null as any, timeout: null as any });
|
||||
const local = useLocal({
|
||||
tree: null as any,
|
||||
timeout: null as any,
|
||||
});
|
||||
|
||||
if (!local.tree) {
|
||||
clearTimeout(local.timeout);
|
||||
|
|
@ -40,27 +46,46 @@ export const EdLeft = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<EdTreeSearch />
|
||||
<div className="flex flex-row items-stretch border-b">
|
||||
<EdPageHistoryBtn
|
||||
show={p.page.history.show}
|
||||
onShow={async (show) => {
|
||||
p.page.history.id = "";
|
||||
p.page.history.show = show;
|
||||
if (!show) {
|
||||
await treeRebuild(p);
|
||||
}
|
||||
|
||||
p.render();
|
||||
local.render();
|
||||
}}
|
||||
/>
|
||||
{!p.page.history.show && <EdTreeSearch />}
|
||||
</div>
|
||||
<div
|
||||
className="tree-body flex relative flex-1 overflow-y-auto overflow-x-hidden"
|
||||
ref={(ref) => {
|
||||
if (ref) local.tree = ref;
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 flex flex-col">
|
||||
{local.tree && (
|
||||
<DndProvider
|
||||
backend={HTML5Backend}
|
||||
options={getBackendOptions({
|
||||
html5: {
|
||||
rootElement: local.tree,
|
||||
},
|
||||
})}
|
||||
>
|
||||
<EdTreeBody />
|
||||
</DndProvider>
|
||||
)}
|
||||
</div>
|
||||
{p.page.history.show ? (
|
||||
<EdPageHistoryList />
|
||||
) : (
|
||||
<div className="absolute inset-0 flex flex-col">
|
||||
{local.tree && (
|
||||
<DndProvider
|
||||
backend={HTML5Backend}
|
||||
options={getBackendOptions({
|
||||
html5: {
|
||||
rootElement: local.tree,
|
||||
},
|
||||
})}
|
||||
>
|
||||
<EdTreeBody />
|
||||
</DndProvider>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { getCompMeta } from "../comp/comp-meta";
|
|||
import { IMeta, PG, active } from "../ed-global";
|
||||
|
||||
export const isMetaActive = (p: PG, meta: IMeta) => {
|
||||
if (!meta.item) return false;
|
||||
|
||||
let is_active: boolean = active.item_id === meta.item.id;
|
||||
if (active.comp_id) {
|
||||
if (meta.parent?.comp_id === active.comp_id) {
|
||||
|
|
|
|||
|
|
@ -168,6 +168,10 @@ export const EDGlobal = {
|
|||
init_local_effect: {} as Record<string, boolean>,
|
||||
},
|
||||
page: {
|
||||
history: {
|
||||
id: "",
|
||||
show: false
|
||||
},
|
||||
root_id: "root",
|
||||
cur: EmptyPage,
|
||||
doc: null as null | DPage,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
import { useGlobal, useLocal } from "web-utils";
|
||||
import { EDGlobal } from "../../logic/ed-global";
|
||||
import { FC, useEffect } from "react";
|
||||
import { decompress } from "wasm-gzip";
|
||||
import { Vi } from "../../../vi/vi";
|
||||
import { genMeta } from "../../../vi/meta/meta";
|
||||
import { IRoot } from "../../../../utils/types/root";
|
||||
import { IContent } from "../../../../utils/types/general";
|
||||
import { initLoadComp } from "../../../vi/meta/comp/init-comp-load";
|
||||
import { loadCompSnapshot } from "../../logic/comp/load";
|
||||
import { IItem } from "../../../../utils/types/item";
|
||||
import { mainStyle } from "./main";
|
||||
import { Loading } from "../../../../utils/ui/loading";
|
||||
import { treeRebuild } from "../../logic/tree/build";
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
export const EdPageHistoryMain: FC<{}> = ({}) => {
|
||||
const p = useGlobal(EDGlobal, "EDITOR");
|
||||
const local = useLocal({
|
||||
loading: true,
|
||||
root: null as any,
|
||||
meta: {} as any,
|
||||
entry: [] as any,
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
local.loading = true;
|
||||
local.render();
|
||||
_db.page_history
|
||||
.findFirst({
|
||||
where: { id: p.page.history.id },
|
||||
select: {
|
||||
content_tree: true,
|
||||
},
|
||||
})
|
||||
.then(async (e) => {
|
||||
if (e) {
|
||||
const zip = new Uint8Array((e.content_tree as any).data);
|
||||
const root = JSON.parse(decoder.decode(decompress(zip))) as IRoot;
|
||||
local.root = root;
|
||||
await initLoadComp(
|
||||
{
|
||||
comps: p.comp.loaded,
|
||||
meta: local.meta,
|
||||
mode: "page",
|
||||
},
|
||||
root as unknown as IItem,
|
||||
{
|
||||
async load(comp_ids) {
|
||||
if (!p.sync) return;
|
||||
|
||||
const ids = comp_ids.filter((id) => !p.comp.loaded[id]);
|
||||
const comps = await p.sync.comp.load(ids, true);
|
||||
let result = Object.entries(comps);
|
||||
|
||||
for (const [id_comp, comp] of result) {
|
||||
if (comp && comp.snapshot && !p.comp.list[id_comp]) {
|
||||
if (!p.comp.loaded[id_comp]) {
|
||||
await loadCompSnapshot(p, id_comp, comp.snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
local.meta = {};
|
||||
local.entry = [];
|
||||
for (const item of root.childs) {
|
||||
local.entry.push(item.id);
|
||||
genMeta(
|
||||
{
|
||||
note: "cache-rebuild",
|
||||
comps: p.comp.loaded,
|
||||
meta: local.meta,
|
||||
mode: "page",
|
||||
},
|
||||
{ item: item as IContent }
|
||||
);
|
||||
}
|
||||
|
||||
local.loading = false;
|
||||
local.render();
|
||||
p.render();
|
||||
}
|
||||
});
|
||||
}, [p.page.history.id]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-stretch">
|
||||
<div className="border-b p-1 text-sm flex">
|
||||
<div
|
||||
className="border px-2 cursor-pointer hover:bg-blue-200 border border-blue-700 hover:bg-blue-700 hover:text-white transition-all "
|
||||
onClick={async () => {
|
||||
if (confirm("Are you sure ?")) {
|
||||
p.page.history.id = "";
|
||||
p.page.history.show = false;
|
||||
|
||||
p.page.doc?.transact(() => {
|
||||
const root = p.page.doc?.getMap("map").get("root");
|
||||
|
||||
if (root) {
|
||||
syncronize(root as any, local.root);
|
||||
}
|
||||
});
|
||||
|
||||
p.render();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Revert to this version
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cx(
|
||||
"flex flex-1 relative overflow-auto",
|
||||
p.mode === "mobile" ? "flex-col items-center" : ""
|
||||
)}
|
||||
ref={(el) => {
|
||||
if (el) {
|
||||
const bound = el.getBoundingClientRect();
|
||||
if (local.width !== bound.width || local.height !== bound.height) {
|
||||
local.width = bound.width;
|
||||
local.height = bound.height;
|
||||
local.render();
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{local.loading ? (
|
||||
<Loading backdrop={true} />
|
||||
) : (
|
||||
<div className={mainStyle(p, local.meta)}>
|
||||
<Vi
|
||||
meta={local.meta}
|
||||
mode={p.mode}
|
||||
api_url={p.site.config.api_url}
|
||||
site_id={p.site.id}
|
||||
page_id={p.page.cur.id}
|
||||
entry={local.entry}
|
||||
api={p.script.api}
|
||||
db={p.script.db}
|
||||
script={{ init_local_effect: p.script.init_local_effect }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -107,7 +107,7 @@ export const EdMain = () => {
|
|||
return null;
|
||||
};
|
||||
|
||||
const mainStyle = (p: PG, meta?: IMeta) => {
|
||||
export const mainStyle = (p: PG, meta?: IMeta) => {
|
||||
let is_active = meta ? isMetaActive(p, meta) : false;
|
||||
|
||||
const scale = parseInt(p.ui.zoom.replace("%", "")) / 100;
|
||||
|
|
|
|||
|
|
@ -87,9 +87,9 @@ export const EdPopComp = () => {
|
|||
className={cx(
|
||||
"border cursor-pointer -mb-[1px] px-2 hover:text-blue-500 hover:border-blue-500 hover:border-b-transparent select-none",
|
||||
local.tab === e &&
|
||||
"bg-white border-b-transparent",
|
||||
"bg-white border-b-transparent",
|
||||
local.tab !== e &&
|
||||
"text-slate-400 border-b-slate-200 border-transparent bg-transparent"
|
||||
"text-slate-400 border-b-slate-200 border-transparent bg-transparent"
|
||||
)}
|
||||
onClick={() => {
|
||||
local.tab = e;
|
||||
|
|
@ -101,7 +101,7 @@ export const EdPopComp = () => {
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="flex flex-1 mr-1 justify-end">
|
||||
<div className="flex flex-1 mr-1">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Search"
|
||||
|
|
@ -128,19 +128,23 @@ export const EdPopComp = () => {
|
|||
background: #efefff;
|
||||
}
|
||||
`,
|
||||
compPicker.search ? css`
|
||||
> .tree-root {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}` : css`
|
||||
> .tree-root > .listitem > .container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}`
|
||||
compPicker.search
|
||||
? css`
|
||||
> .tree-root {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}
|
||||
`
|
||||
: css`
|
||||
> .tree-root > .listitem > .container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}
|
||||
`
|
||||
)}
|
||||
>
|
||||
{compPicker.ref && compPicker.status === "ready" && (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
import { FC } from "react";
|
||||
|
||||
export const EdPageHistoryBtn: FC<{
|
||||
show: boolean;
|
||||
onShow: (show: boolean) => void;
|
||||
}> = ({ show, onShow }) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cx(
|
||||
"border-r min-w-[25px] px-2 flex items-center justify-center cursor-pointer select-none",
|
||||
show && "bg-blue-700 text-white flex-1",
|
||||
!show && "hover:bg-blue-100"
|
||||
)}
|
||||
onClick={() => {
|
||||
onShow(!show);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={css`
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
`}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-history"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l4 2"/></svg>`,
|
||||
}}
|
||||
></div>
|
||||
{show && (
|
||||
<>
|
||||
<div className="text-xs ml-1 flex-1">History</div>
|
||||
<div className="ml-3 border-l border-l-blue-100/20 pl-2">
|
||||
×
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import { useGlobal, useLocal } from "web-utils";
|
||||
import { EDGlobal } from "../../logic/ed-global";
|
||||
import { Loading } from "../../../../utils/ui/loading";
|
||||
import { format, formatDistance } from "date-fns";
|
||||
export const EdPageHistoryList = () => {
|
||||
const p = useGlobal(EDGlobal, "EDITOR");
|
||||
const local = useLocal(
|
||||
{ loading: true, list: [] as Awaited<ReturnType<typeof queryList>> },
|
||||
async () => {
|
||||
console.log("query list");
|
||||
local.list = await queryList(p.page.cur.id);
|
||||
local.loading = false;
|
||||
local.render();
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{local.loading ? (
|
||||
<Loading backdrop={false} />
|
||||
) : (
|
||||
<div className="flex flex-1 flex-col items-stretch">
|
||||
{local.list.map((e) => {
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
"flex justify-between items-center text-sm px-2 py-1 cursor-pointer hover:bg-blue-100 border-b transition-all select-none",
|
||||
e.id === p.page.history.id &&
|
||||
"border-r-4 bg-blue-50 border-r-blue-700"
|
||||
)}
|
||||
key={e.id}
|
||||
onClick={() => {
|
||||
p.page.history.id = e.id;
|
||||
p.render();
|
||||
local.render();
|
||||
}}
|
||||
>
|
||||
<div className="flex-1">
|
||||
{format(parseInt(e.ts) * 5000, "yyyy-MM-dd HH:mm:ss")}
|
||||
</div>
|
||||
|
||||
<div className="text-right text-[11px]">
|
||||
{formatDistance(Date.now(), parseInt(e.ts) * 5000) + " ago"}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const queryList = async (page_id: string) => {
|
||||
return await _db.page_history.findMany({
|
||||
where: { id_page: page_id },
|
||||
select: {
|
||||
id: true,
|
||||
ts: true,
|
||||
},
|
||||
orderBy: { ts: "desc" },
|
||||
});
|
||||
};
|
||||
|
|
@ -22,7 +22,6 @@ export const EdTreeSearch = () => {
|
|||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col items-stretch border-b"
|
||||
onMouseOver={() => {
|
||||
if (local.focus) {
|
||||
local.hover = true;
|
||||
|
|
|
|||
Loading…
Reference in New Issue