This commit is contained in:
Rizky 2023-10-20 17:15:20 +07:00
parent 4de34a8a86
commit b5273222ba
13 changed files with 146 additions and 40 deletions

View File

@ -1,26 +1,26 @@
export const SyncActionDefinition = { export const SyncActionDefinition = {
"site": { "site": {
"all": "0", "list": "0",
"group": "1", "group": "1",
"load": "2" "load": "2"
}, },
"comp": { "comp": {
"all": "3", "list": "3",
"group": "4", "group": "4",
"doc": "5" "doc": "5"
}, },
"page": { "page": {
"all": "6", "list": "6",
"load": "7" "load": "7"
} }
}; };
export const SyncActionPaths = { export const SyncActionPaths = {
"0": "site.all", "0": "site.list",
"1": "site.group", "1": "site.group",
"2": "site.load", "2": "site.load",
"3": "comp.all", "3": "comp.list",
"4": "comp.group", "4": "comp.group",
"5": "comp.doc", "5": "comp.doc",
"6": "page.all", "6": "page.list",
"7": "page.load" "7": "page.load"
}; };

View File

@ -3,7 +3,7 @@ import { component, site, page } from "dbgen";
export const SyncActions = { export const SyncActions = {
site: { site: {
all: () => list: () =>
({}) as Promise< ({}) as Promise<
Record<string, { id: string; name: string; domain: string }> Record<string, { id: string; name: string; domain: string }>
>, >,
@ -11,12 +11,12 @@ export const SyncActions = {
load: (id: string) => ({}) as Promise<site>, load: (id: string) => ({}) as Promise<site>,
}, },
comp: { comp: {
all: () => ({}) as Record<string, Exclude<component, "content_tree">>, list: () => ({}) as Record<string, Exclude<component, "content_tree">>,
group: () => ({}) as Record<string, string[]>, group: () => ({}) as Record<string, string[]>,
doc: (id: string) => ({}) as Uint8Array, doc: (id: string) => ({}) as Uint8Array,
}, },
page: { page: {
all: (id_site: string) => list: (id_site: string) =>
({}) as Record<string, Exclude<page, "content_tree">>, ({}) as Record<string, Exclude<page, "content_tree">>,
load: (id: string) => ({}) as Uint8Array, load: (id: string) => ({}) as Uint8Array,
}, },

View File

@ -0,0 +1 @@
export * from "./site_load";

View File

@ -0,0 +1,3 @@
export const site_load = async () => {
return "moka";
};

View File

@ -5,6 +5,8 @@ import { WSData } from "../../../../pkgs/core/server/create";
import { ClientEvent } from "../../../web/src/utils/sync/client"; import { ClientEvent } from "../../../web/src/utils/sync/client";
import { loadUserConf } from "./editor/load"; import { loadUserConf } from "./editor/load";
import { SyncType } from "./type"; import { SyncType } from "./type";
import { SyncActionPaths } from "./actions-def";
import * as actions from "./actions/index";
const packr = new Packr({ structuredClone: true }); const packr = new Packr({ structuredClone: true });
const conns = new Map< const conns = new Map<
@ -56,6 +58,21 @@ export const syncHandler: WebSocketHandler<WSData> = {
data: conf, data: conf,
}); });
} }
if (msg.type === SyncType.Action) {
const code = msg.code as keyof typeof SyncActionPaths;
const actionName = SyncActionPaths[code].replace(/\./gi, "_");
if (actionName) {
const action = (actions as any)[actionName];
ws.sendBinary(
packr.pack({
type: SyncType.ActionResult,
argid: msg.argid,
val: await action(...msg.args),
})
);
}
}
} }
} }
}, },

View File

@ -2,4 +2,6 @@ export enum SyncType {
ClientID, ClientID,
UserID, UserID,
Event, Event,
Action,
ActionResult,
} }

View File

@ -2,6 +2,7 @@ import { page, useGlobal } from "web-utils";
import { EDGlobal } from "../../render/ed/logic/global"; import { EDGlobal } from "../../render/ed/logic/global";
import { clientStartSync } from "../../utils/sync/client"; import { clientStartSync } from "../../utils/sync/client";
import { Loading } from "../../utils/ui/loading"; import { Loading } from "../../utils/ui/loading";
import { Ed } from "../../render/ed/ed";
export default page({ export default page({
url: "/ed/:site_id/:page_id", url: "/ed/:site_id/:page_id",
@ -13,15 +14,22 @@ export default page({
) as { data: { user: { id: string } } }; ) as { data: { user: { id: string } } };
if (!session) { if (!session) {
navigate("/login"); navigate("/login");
return <Loading />; return <Loading note="logging in" />;
} }
if (!p.sync) { if (!p.sync) {
p.sync = clientStartSync({ clientStartSync({
user_id: session.data.user.id, user_id: session.data.user.id,
events: { events: {
editor_start(e) { editor_start(e) {
if (params.site_id !== "_" && params.page_id !== "_") { if (
!!params.site_id &&
!!params.page_id &&
params.site_id !== "_" &&
params.page_id !== "_"
) {
p.site.id = params.site_id;
p.page.id = params.page_id;
p.render(); p.render();
} else { } else {
if (e.site_id && e.page_id) { if (e.site_id && e.page_id) {
@ -29,11 +37,17 @@ export default page({
} }
} }
}, },
site_loaded({ site }) {
p.site = site;
p.render();
},
}, },
}); }).then((e) => (p.sync = e));
return <Loading />; return <Loading note="editor start" />;
} }
return <div></div>; if (!p.site.id && p.page.id) return <Loading note="waiting page" />;
return <Ed />;
}, },
}); });

View File

@ -10,7 +10,7 @@ const start = async () => {
let react = { let react = {
root: null as null | ReactRoot, root: null as null | ReactRoot,
}; };
if (true || !["localhost", "127.0.0.1"].includes(location.hostname)) { if (!["localhost", "127.0.0.1"].includes(location.hostname)) {
const sw = await registerServiceWorker(); const sw = await registerServiceWorker();
navigator.serviceWorker.addEventListener("message", (e) => { navigator.serviceWorker.addEventListener("message", (e) => {
if (react.root) { if (react.root) {

View File

@ -0,0 +1,12 @@
import { useGlobal } from "web-utils";
import { Loading } from "../../utils/ui/loading";
import { EDGlobal } from "./logic/global";
import { edRoute } from "./logic/route";
export const Ed = () => {
const p = useGlobal(EDGlobal, "EDITOR");
edRoute(p);
return <Loading />;
};

View File

@ -1,5 +1,22 @@
import { clientStartSync } from "../../../utils/sync/client"; import { clientStartSync } from "../../../utils/sync/client";
export const EDGlobal = { const EmptySite = {
sync: null as unknown as ReturnType<typeof clientStartSync>, id: "",
name: "",
domain: "",
js: "",
js_compiled: "",
config: { api_url: "" },
}; };
export type ESite = typeof EmptySite;
const EmptyPage = {
id: "",
};
export const EDGlobal = {
sync: null as unknown as Awaited<ReturnType<typeof clientStartSync>>,
site: EmptySite,
page: EmptyPage,
};
export type PG = typeof EDGlobal & { render: () => void };

View File

@ -0,0 +1,8 @@
import { PG } from "./global";
export const edRoute = async (p: PG) => {
if (!p.site.domain && !p.site.name) {
const res = await p.sync.site.load(p.site.id);
console.log(res);
}
};

View File

@ -8,6 +8,7 @@ import { SyncActionDefinition } from "../../../../srv/ws/sync/actions-def";
import { initIDB } from "./idb"; import { initIDB } from "./idb";
import { SyncType } from "../../../../srv/ws/sync/type"; import { SyncType } from "../../../../srv/ws/sync/type";
import { w } from "../types/general"; import { w } from "../types/general";
import { ESite } from "../../render/ed/logic/global";
const packr = new Packr({ structuredClone: true }); const packr = new Packr({ structuredClone: true });
const conf = { const conf = {
ws: null as null | WebSocket, ws: null as null | WebSocket,
@ -16,6 +17,15 @@ const conf = {
event: null as null | ClientEventObject, event: null as null | ClientEventObject,
}; };
const runtime = {
action: {
pending: {} as Record<
string,
{ ts: number; resolve: (value: any) => void }
>,
},
};
type User = { type User = {
id: string; id: string;
name: string; name: string;
@ -32,6 +42,7 @@ export const clientStartSync = async (arg: {
site_id?: string; site_id?: string;
page_id?: string; page_id?: string;
}) => void; }) => void;
site_loaded: (arg: { site: ESite }) => void;
}; };
}) => { }) => {
const { user_id, events } = arg; const { user_id, events } = arg;
@ -47,11 +58,17 @@ export const clientStartSync = async (arg: {
path.push(key); path.push(key);
if (typeof value === "string") { if (typeof value === "string") {
if (path[0] === "then") path.shift(); for (let i = 0; i < path.length; i++) {
if (path[i] !== "then") {
path.splice(0, i);
break;
}
}
return (...args: any[]) => return (...args: any[]) =>
new Promise((resolve) => { new Promise((resolve) => {
doAction({ doAction({
path: path.join("."), path: path.join("."),
code: value,
resolve, resolve,
args, args,
}); });
@ -75,7 +92,7 @@ const connect = (user_id: string, event: ClientEventObject) => {
return new Promise<void>(async (resolve) => { return new Promise<void>(async (resolve) => {
resolve(); resolve();
const eventName = "editor_start"; const eventName = "editor_start";
const data = await loadOfflineMsg("ev", eventName); const data = await loadEventOffline(eventName);
if (event[eventName]) { if (event[eventName]) {
event[eventName](data); event[eventName](data);
@ -112,10 +129,20 @@ const connect = (user_id: string, event: ClientEventObject) => {
if (event[eventName]) { if (event[eventName]) {
if (offlineEvents.includes(eventName)) { if (offlineEvents.includes(eventName)) {
saveOfflineMsg("ev", eventName, msg.data); saveEventOffline(eventName, msg.data);
} }
event[eventName](msg.data); event[eventName](msg.data);
} }
} else if (msg.type === SyncType.ActionResult) {
const pending = runtime.action.pending[msg.argid];
if (pending) {
delete runtime.action.pending[msg.argid];
const idb = conf.idb;
if (idb) {
await set(msg.argid, msg.val, idb);
}
pending.resolve(msg.val);
}
} }
}; };
} }
@ -124,41 +151,46 @@ const connect = (user_id: string, event: ClientEventObject) => {
}; };
const offlineEvents: ClientEvent[] = ["editor_start"]; const offlineEvents: ClientEvent[] = ["editor_start"];
const saveOfflineMsg = async (type: "ev", name: ClientEvent, data: any) => { const saveEventOffline = async (name: ClientEvent, data: any) => {
const idb = conf.idb; const idb = conf.idb;
if (idb) { if (idb) {
const hargs = await xxhash32(`${type}-${name}`); const hargs = await xxhash32(`ev-${name}`);
await set(hargs, data, idb); await set(hargs, data, idb);
} }
}; };
const loadOfflineMsg = async (type: "ev", name: ClientEvent) => { const loadEventOffline = async (name: ClientEvent) => {
const idb = conf.idb; const idb = conf.idb;
if (idb) { if (idb) {
const hargs = await xxhash32(`${type}-${name}`); const hargs = await xxhash32(`ev-${name}`);
return await get(hargs, idb); return await get(hargs, idb);
} }
}; };
const doAction = <T>(arg: { const doAction = async <T>(arg: {
path: string; path: string;
code: string;
resolve: (value: any) => void; resolve: (value: any) => void;
args: any[]; args: any[];
}) => { }) => {
return new Promise<T>(async (resolve) => { const { path, args, code, resolve } = arg;
const ws = conf.ws; const ws = conf.ws;
const idb = conf.idb; const idb = conf.idb;
if (idb) { if (idb) {
const sargs = stringify(arg.args); const sargs = stringify(args);
const hargs = await xxhash32(`op-${arg.path}-${sargs}`); const argid = await xxhash32(`op-${path}-${sargs}`);
if (w.offline || (ws && ws.readyState === ws.OPEN)) { if (ws && ws.readyState === ws.OPEN) {
// online // online
} else { runtime.action.pending[argid] = {
// offline ts: Date.now(),
const cache = await get(hargs, idb); resolve,
resolve(cache as T); };
} ws.send(packr.pack({ type: SyncType.Action, code, args, argid }));
} else {
// offline
const cache = await get(argid, idb);
resolve(cache as T);
} }
}); }
}; };

Binary file not shown.