Rust implementation of NextGraph, a Decentralized and local-first web 3.0 ecosystem https://nextgraph.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nextgraph-rs/ng-app/src/tab.ts

361 lines
11 KiB

// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
import {
writable,
readable,
readonly,
derived,
get,
type Writable,
} from "svelte/store";
import { official_classes } from "./classes";
import { official_apps, official_services } from "./zeras";
let loaded_external_apps = {};
export const load_app = async (appName: string) => {
if (appName.startsWith("n:g:z")) {
let app = official_apps[appName];
if (!app) throw new Error("Unknown official app");
return await import(`./apps/${app["ng:b"]}.svelte`);
} else {
//TODO: load external app from its repo
// TODO: return IFrame component
}
};
export const get_app = (appName: string) => {
if (appName.startsWith("n:g:z")) {
let app = official_apps[appName];
if (!app) throw new Error("Unknown official app");
return app;
} else {
//TODO: load external app from its repo
// TODO: keep it in cache in loaded_external_apps
}
};
export const invoke_service = async (serviceName: string, nuri: string, args: object) => {
if (serviceName.startsWith("n:g:z")) {
let service = official_services[serviceName];
if (!service) throw new Error("Unknown official service");
// TODO: do this in WebWorker
// TODO: if on native app or CLI: use deno
//return await ng.app_invoke(serviceName[6..], nuri, args);
} else {
// TODO: if on webapp: only allow those invocations from IFrame of external app or from n:g:z:external_service_invoke (which runs in an IFrame) and run it from webworker
// TODO: if on native app or CLI: use deno
// TODO: load external service from its repo
}
};
export const get_class = (class_name) => {
if (class_name.startsWith("app/") && class_name !== "app/z") {
//TODO: load external app from its repo
// cache it in loaded_external_apps
// return the class
} else {
return official_classes[class_name];
}
};
const find_viewers_for_class = (class_name: string) => {
let found = [];
for (const zera of Object.entries(official_apps)) {
if (zera[0].includes("dump") || zera[0].includes("source")) continue;
let viewers = zera[1]["ng:o"];
if (viewers && viewers.includes(class_name)) {
found.push(zera[0]);
}
}
return found;
}
const find_editors_for_class = (class_name: string) => {
let found = [];
for (const zera of Object.entries(official_apps)) {
let viewers = zera[1]["ng:w"];
if (viewers && viewers.includes(class_name)) {
found.push(zera[0]);
}
}
return found;
}
const find_source_viewer_for_class = (class_def) => {
switch (class_def["ng:crdt"]) {
case 'Graph':
return "n:g:z:crdt_source_viewer:rdf";
case 'YMap':
case 'YArray':
case 'Automerge':
case 'Elmer':
return "n:g:z:crdt_source_viewer:json";
case 'YXml':
return "n:g:z:crdt_source_viewer:xml";
case 'YText':
return "n:g:z:crdt_source_viewer:text";
}
}
const class_to_viewers_editors = (class_name: string) => {
let class_def = get_class(class_name);
let has_discrete = class_def["ng:crdt"] !== "Graph";
let discrete_viewer = has_discrete ? class_def["ng:o"] : undefined;
let discrete_editor = has_discrete ? class_def["ng:w"] : undefined;
let graph_viewers = [];
let graph_editors = [];
if (!has_discrete) {
if (class_def["ng:o"]) graph_viewers.push(class_def["ng:o"]);
if (class_def["ng:w"]) graph_editors.push(class_def["ng:w"]);
}
graph_viewers.push.apply(graph_viewers, find_viewers_for_class("data/graph"));
graph_editors.push.apply(graph_editors, find_editors_for_class("data/graph"));
let graph_viewer = graph_viewers[0];
let graph_editor = graph_editors[0];
let discrete_viewers = [];
let discrete_editors = [];
if (has_discrete) {
if (discrete_viewer) discrete_viewers.push(discrete_viewer);
if (discrete_editor) discrete_editors.push(discrete_editor);
for (const v of find_viewers_for_class(class_name)) {
if (v!==discrete_viewer) discrete_viewers.push(v);
}
for (const e of find_editors_for_class(class_name)) {
if (e!==discrete_editor) discrete_editors.push(e);
}
discrete_viewers.push(find_source_viewer_for_class(class_def));
if (!discrete_viewer) discrete_viewer = discrete_viewers[0];
}
return {
graph_viewers,
graph_editors,
discrete_viewers,
discrete_editors,
graph_viewer,
graph_editor,
discrete_viewer,
discrete_editor
}
}
export const open_branch = async (nuri: string) => {
let class_name = "post/rich";
}
export const open_doc = async (nuri: string) => {
//let class_name = "doc/viz/plotly";
let class_name = "post/md";
cur_tab.update(ct => {
return {...ct, ...class_to_viewers_editors(class_name)};
});
}
export const cur_tab = writable({
cur_store: {
has_outer: {
nuri_trail: ":v:l"
},
type: "public", // "protected", "private", "group", "dialog",
favicon: "",
title: "Group B",
is_member: true,
},
cur_branch: {
b: "b:xxx", //branch id (can be null if not of type "branch")
c: "c:xxx", //commit(s) id
type: "main", // "stream", "detached", "branch", "in_memory" (does not save)
display: "c:X", // or main or stream or a:xx or branch:X (only 7 chars)
attachments: 1,
files: 1,
comments: 2,
class: "post/rich",
title: false,
icon: false,
description: "",
app: "n:g:z:json_ld_editor", // current app being used
},
view_or_edit: true,
graph_viewer: "", // selected viewer
graph_editor: "", // selected editor
discrete_viewer: "", // selected viewer
discrete_editor: "", // selected editor
graph_viewers: [], // list of available viewers
graph_editors: [], // list of available editors
discrete_viewers: [], // list of available viewers
discrete_editors: [], // list of available editors
find: false,//or string to find
graph_or_discrete: false, // set to cur_branch.class === "Graph"
read_cap: 'r:',
doc: {
is_store: false,
is_member: true,
can_edit: true,
live_edit: false,
title: "Doc A",
authors: "",
icon: "",
description: "",
stream: {
notif: 1,
last: "",
},
live_editors: {
},
},
folders_pane: false,
toc_pane: false,
right_pane: "", // "branches", "files", "history", "comments", "info", "chat", "mc"
action: false, // "repost", "dm", "react", "author", "copy", "forward", "link", "qr", "download", "embed", "new_block", "notifs", "schema", "signature", "permissions", "settings", "print", "console", "source", "services", "dev",
show_modal_menu: false,
6 months ago
show_menu: false,
});
export const showMenu = () => {
cur_tab.update(ct => {
ct.show_menu = true;
ct.show_modal_menu = true;
6 months ago
return ct;
});
}
export const hideMenu = () => {
cur_tab.update(ct => {
ct.show_menu = false;
ct.show_modal_menu = false;
6 months ago
return ct;
});
}
export const nav_bar = writable({
//icon: "class:post/rich",
icon: "nav:private",
//icon: "blob:http://localhost:1421/be6f968f-ff51-4e8f-bd32-36c60b7af49a",
title: "Private Store",
back: false,
newest: 0,
save: true,
});
export const save = async () => {
// saving the doc
}
export const all_files_count = derived(cur_tab, ($cur_tab) => {
let total = $cur_tab.cur_branch.attachments + $cur_tab.cur_branch.files;
return total ? `(${total})` : "";
});
export const all_comments_count = derived(cur_tab, ($cur_tab) => {
let total = $cur_tab.cur_branch.comments;
return total ? `(${total})` : "";
});
export const has_editor_chat = derived(cur_tab, ($cur_tab) => {
return $cur_tab.doc.can_edit && $cur_tab.cur_store.type !== "private" && $cur_tab.cur_store.type !== "dialog";
});
export const toggle_live_edit = () => {
cur_tab.update(ct => {
ct.doc.live_edit = !ct.doc.live_edit;
return ct;
});
}
export const set_viewer = (app_name: string) => {
if (get(cur_tab).graph_or_discrete) {
cur_tab.update(ct => {ct.graph_viewer = app_name; ct.cur_branch.app = app_name; return ct;});
} else {
cur_tab.update(ct => {ct.discrete_viewer = app_name; ct.cur_branch.app = app_name; return ct;});
}
}
export const set_editor = (app_name: string) => {
if (get(cur_tab).graph_or_discrete) {
cur_tab.update(ct => {ct.graph_editor = app_name; ct.cur_branch.app = app_name; return ct;});
} else {
cur_tab.update(ct => {ct.discrete_editor = app_name; ct.cur_branch.app = app_name; return ct;});
}
}
export const toggle_graph_discrete = () => {
cur_tab.update(ct => {
ct.graph_or_discrete = !ct.graph_or_discrete;
return ct;
});
}
export const set_graph_discrete = (val:boolean) => {
cur_tab.update(ct => {
ct.graph_or_discrete = val;
return ct;
});
}
export const set_view_or_edit = (val:boolean) => {
cur_tab.update(ct => {
ct.view_or_edit = val;
return ct;
});
}
export const cur_viewer = derived(cur_tab, ($cur_tab) => {
let app_name = $cur_tab.graph_or_discrete ? $cur_tab.graph_viewer : $cur_tab.discrete_viewer;
if (app_name) {
let app = get_app(app_name);
return app;
}
});
export const cur_editor = derived(cur_tab, ($cur_tab) => {
let app_name = $cur_tab.graph_or_discrete ? $cur_tab.graph_editor : $cur_tab.discrete_editor;
if (app_name) {
let app = get_app(app_name);
return app;
}
});
export const available_viewers = derived(cur_tab, ($cur_tab) => {
let list = $cur_tab.graph_or_discrete ? $cur_tab.graph_viewers : $cur_tab.discrete_viewers;
return list.map((viewer) => {
let app = { ...get_app(viewer) };
if (!app["ng:u"]) app["ng:u"] = "view";
return app
});
});
export const available_editors = derived(cur_tab, ($cur_tab) => {
let list = $cur_tab.graph_or_discrete ? $cur_tab.graph_editors : $cur_tab.discrete_editors;
return list.map((editor) => {
let app = { ...get_app(editor) };
if (!app["ng:u"]) app["ng:u"] = "edit";
return app
});
});
export const cur_branch_has_discrete = derived(cur_tab, ($cur_tab) => (get_class($cur_tab.cur_branch.class)["ng:crdt"]) !== "Graph");