From f7ee5457eff5cacd12a2a4ff2306f00290da3495 Mon Sep 17 00:00:00 2001 From: Niko PLP Date: Mon, 29 Jul 2024 14:51:42 +0300 Subject: [PATCH] history pane --- Cargo.lock | 13 ++ ng-app/src/apps/ListView.svelte | 5 + ng-app/src/lib/FullLayout.svelte | 55 +++++-- ng-app/src/lib/Pane.svelte | 37 +++++ ng-app/src/lib/Test.svelte | 4 +- ng-app/src/lib/components/BranchIcon.svelte | 2 +- ng-app/src/lib/panes/History.svelte | 155 ++++++++++++++++++ ng-app/src/{ => lib/panes}/history/LICENSE.md | 0 .../panes}/history/gitgraph-core/branch.ts | 0 .../history/gitgraph-core/branches-paths.ts | 0 .../panes}/history/gitgraph-core/commit.ts | 0 .../gitgraph-core/gitgraph-user-api.ts | 59 ++++--- .../panes}/history/gitgraph-core/gitgraph.ts | 0 .../history/gitgraph-core/graph-rows.ts | 0 .../panes}/history/gitgraph-core/index.ts | 0 .../gitgraph-core/regular-graph-rows.ts | 0 .../panes}/history/gitgraph-core/template.ts | 0 .../panes}/history/gitgraph-core/utils.ts | 0 .../panes}/history/gitgraph-js/gitgraph.ts | 21 ++- .../history/gitgraph-js/svg-elements.ts | 7 +- .../panes}/history/gitgraph-js/tooltip.ts | 2 +- ng-app/src/store.ts | 15 +- ng-app/src/styles.css | 3 + ng-app/src/tab.ts | 20 ++- ng-net/src/app_protocol.rs | 4 +- ng-repo/Cargo.toml | 6 +- ng-repo/src/commit.rs | 7 + ng-repo/src/repo.rs | 79 ++++++++- ng-repo/src/types.rs | 9 +- ng-repo/src/utils.rs | 45 ++++- ng-sdk-js/src/lib.rs | 15 +- 31 files changed, 474 insertions(+), 89 deletions(-) create mode 100644 ng-app/src/lib/Pane.svelte create mode 100644 ng-app/src/lib/panes/History.svelte rename ng-app/src/{ => lib/panes}/history/LICENSE.md (100%) rename ng-app/src/{ => lib/panes}/history/gitgraph-core/branch.ts (100%) rename ng-app/src/{ => lib/panes}/history/gitgraph-core/branches-paths.ts (100%) rename ng-app/src/{ => lib/panes}/history/gitgraph-core/commit.ts (100%) rename ng-app/src/{ => lib/panes}/history/gitgraph-core/gitgraph-user-api.ts (81%) rename ng-app/src/{ => lib/panes}/history/gitgraph-core/gitgraph.ts (100%) rename ng-app/src/{ => lib/panes}/history/gitgraph-core/graph-rows.ts (100%) rename ng-app/src/{ => lib/panes}/history/gitgraph-core/index.ts (100%) rename ng-app/src/{ => lib/panes}/history/gitgraph-core/regular-graph-rows.ts (100%) rename ng-app/src/{ => lib/panes}/history/gitgraph-core/template.ts (100%) rename ng-app/src/{ => lib/panes}/history/gitgraph-core/utils.ts (100%) rename ng-app/src/{ => lib/panes}/history/gitgraph-js/gitgraph.ts (96%) rename ng-app/src/{ => lib/panes}/history/gitgraph-js/svg-elements.ts (97%) rename ng-app/src/{ => lib/panes}/history/gitgraph-js/tooltip.ts (96%) diff --git a/Cargo.lock b/Cargo.lock index e40078e..2918d91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3459,6 +3459,7 @@ dependencies = [ "futures", "getrandom 0.2.10", "gloo-timers", + "lazy_static", "log", "num_enum", "once_cell", @@ -3814,6 +3815,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "objc" version = "0.2.7" @@ -5928,7 +5938,10 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa 1.0.6", + "js-sys", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", diff --git a/ng-app/src/apps/ListView.svelte b/ng-app/src/apps/ListView.svelte index 1e0948c..e91c46d 100644 --- a/ng-app/src/apps/ListView.svelte +++ b/ng-app/src/apps/ListView.svelte @@ -22,6 +22,11 @@

ListView

+ {#if Array.isArray(commits.history.commits)} + {#each commits.history.commits as c} +
{c[0]} {JSON.stringify(c[1])}
+ {/each} + {/if}
HEADS: {#each commits.heads as head} {head} , {/each}
diff --git a/ng-app/src/lib/FullLayout.svelte b/ng-app/src/lib/FullLayout.svelte index e6ba55c..73c3dc7 100644 --- a/ng-app/src/lib/FullLayout.svelte +++ b/ng-app/src/lib/FullLayout.svelte @@ -22,7 +22,7 @@ import { link, location, push } from "svelte-spa-router"; import MobileBottomBarItem from "./MobileBottomBarItem.svelte"; import MobileBottomBar from "./MobileBottomBar.svelte"; - import NavIcon from "./components/NavIcon.svelte"; + import Pane from "./Pane.svelte"; // @ts-ignore import Logo from "./components/Logo.svelte"; import MenuItem from "./components/MenuItem.svelte"; @@ -30,7 +30,7 @@ import BranchIcon from "./components/BranchIcon.svelte"; // @ts-ignore import { t } from "svelte-i18n"; - import { onMount, tick } from "svelte"; + import { onMount, onDestroy, tick } from "svelte"; import { cur_tab, cur_viewer, cur_editor, toggle_graph_discrete, cur_tab_update, available_editors, available_viewers, set_editor, set_viewer, set_view_or_edit, toggle_live_edit, has_editor_chat, all_files_count, all_comments_count, nav_bar, save, hideMenu, show_modal_menu } from "../tab"; @@ -215,6 +215,7 @@ let top; let shareMenu; let toolsMenu; + let unsub; async function scrollToTop() { await tick(); if (top) top.scrollIntoView(); @@ -229,6 +230,24 @@ } onMount(async () => { await scrollToTop(); + + unsub = show_modal_menu.subscribe((new_val) => { + if (!new_val) { + cur_tab_update(ct => { + ct.show_menu = false; + if (panes_available === 0) { + ct.right_pane = ""; + ct.folders_pane = false; + ct.toc_pane = false; + } + return ct; + }); + } + }); + }); + + onDestroy(() => { + if (unsub) unsub(); }); active_session.subscribe((as) => { if(!as) { @@ -291,37 +310,37 @@ hideMenu(); } - const find = (share:string) => { + const find = () => { // TODO hideMenu(); } - const bookmark = (share:string) => { + const bookmark = () => { // TODO hideMenu(); } - const annotate = (share:string) => { + const annotate = () => { // TODO hideMenu(); } - const openArchive = (share:string) => { + const openArchive = () => { // TODO hideMenu(); } const closeModal = () => { $show_modal_menu = false; - cur_tab_update(ct => { - ct.show_menu = false; - if (panes_available === 0) { - ct.right_pane = ""; - ct.folders_pane = false; - ct.toc_pane = false; - } - return ct; - }); + // cur_tab_update(ct => { + // ct.show_menu = false; + // if (panes_available === 0) { + // ct.right_pane = ""; + // ct.folders_pane = false; + // ct.toc_pane = false; + // } + // return ct; + // }); } const closePaneInModal = () => { @@ -657,17 +676,19 @@ {$t(`doc.menu.items.${$cur_tab.right_pane}.label`)}
+ {:else if $cur_tab.folders_pane}
{$t("doc.menu.items.folders.label")}
+ {:else if $cur_tab.toc_pane}
{$t("doc.menu.items.toc.label")}
- + {/if} @@ -867,7 +888,7 @@
- +
{/if} diff --git a/ng-app/src/lib/Pane.svelte b/ng-app/src/lib/Pane.svelte new file mode 100644 index 0000000..d76bb10 --- /dev/null +++ b/ng-app/src/lib/Pane.svelte @@ -0,0 +1,37 @@ + + + + +
+ + {#if pane_name} + {#await load_pane(pane_name) then pane} +
+ +
+ {/await} + {/if} + +
\ No newline at end of file diff --git a/ng-app/src/lib/Test.svelte b/ng-app/src/lib/Test.svelte index 36f9a10..465d321 100644 --- a/ng-app/src/lib/Test.svelte +++ b/ng-app/src/lib/Test.svelte @@ -14,7 +14,7 @@ createGitgraph, templateExtend, TemplateName, - } from "../history/gitgraph-js/gitgraph"; + } from "./panes/history/gitgraph-js/gitgraph"; import ng from "../api"; import { branch_subscribe, @@ -136,7 +136,7 @@ subject: "niko2", branch: "C", author: "", - parents: ["A"], + parents: ["B"], x: 2, y: 3, }, diff --git a/ng-app/src/lib/components/BranchIcon.svelte b/ng-app/src/lib/components/BranchIcon.svelte index 3a8efa8..3dec9fe 100644 --- a/ng-app/src/lib/components/BranchIcon.svelte +++ b/ng-app/src/lib/components/BranchIcon.svelte @@ -1,6 +1,6 @@ + viewBox="0 0 512 512" fill="currentColor" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''}> + import { + branch_subscribe, + active_session, + cannot_load_offline, + online, + get_blob + } from "../../store"; + import { get } from "svelte/store"; + import { onMount, onDestroy, tick } from "svelte"; + import { + Sun, + Cloud, + DocumentPlus, + DocumentMinus, + CircleStack, + Funnel, + FingerPrint, + Key, + Cog, + Icon, + ShieldCheck, + } from "svelte-heros-v2"; + import BranchIcon from "../components/BranchIcon.svelte"; + + import { t } from "svelte-i18n"; + import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte"; + import { cur_tab, nav_bar, can_have_header, header_icon, header_title, header_description, cur_branch, set_header_in_view, edit_header_button, cur_app, load_official_app } from "../../tab"; + import ng from "../../api"; + + import { + createGitgraph, + templateExtend, + TemplateName, + } from "./history/gitgraph-js/gitgraph"; + + let gitgraph; + let history = []; + let unsub = () => {}; + + onMount(async ()=>{ + const graphContainer = document.getElementById("graph-container"); + gitgraph = createGitgraph(graphContainer, { + template: templateExtend(TemplateName.Metro, { + branch: { label: { display: false } }, + commit: { message: { displayAuthor: false, displayHash: false } }, + }), + }); + let res = await ng.branch_history($active_session.session_id, "did:ng:"+$cur_tab.branch.nuri); + // for (const h of res.history) { + // console.log(h[0], h[1]); + // } + //console.log(res.swimlane_state); + history = [...res.history].reverse(); + + gitgraph.swimlanes(res.swimlane_state.map((s)=> s || false)); + gitgraph.import(res.history.map((h)=>{return { + hash:h[0], + branch:h[1].branch, + author:h[1].author, + parents:h[1].past, + x:h[1].x, + y:h[1].y, + subject:h[1].timestamp, + onClick:()=>openCommit(h[0]), + onMessageClick:()=>openCommit(h[0]) + };})); + + let branch = branch_subscribe($cur_tab.branch.nuri,false); + unsub = branch.subscribe((b) => { + //console.log("subscription callbak",b.history.commits); + if (Array.isArray(b.history.commits)) { + for (var h; h = b.history.commits.pop(); ) { + //console.log(h); + history.unshift(h); + history = history; + gitgraph.commit({ + hash: h[0], + author:h[1].author, + parents:h[1].past, + subject:h[1].timestamp, + onClick:()=>openCommit(h[0]), + onMessageClick:()=>openCommit(h[0]) + }); + } + } + }); + get(branch).history.start(); + }); + + onDestroy( ()=>{ + let branch = branch_subscribe($cur_tab.branch.nuri,false); + get(branch).history.stop(); + unsub(); + }); + + const openCommit = (id:string) => { + console.log("open commit",id); + } + + const commit_type_icons = { + "TransactionGraph": Sun, + "TransactionDiscrete": Cloud, + "TransactionBoth": Sun, + "FileAdd": DocumentPlus, + "FileRemove": DocumentMinus, + "Snapshot": CircleStack, + "Compact": Funnel, + "AsyncSignature": FingerPrint, + "SyncSignature": FingerPrint, + "Branch": BranchIcon, + "UpdateBranch": BranchIcon, + "BranchCapRefresh": Key, + "CapRefreshed": Key, + "Other": Cog, + }; + + + +
+ + {#each history as commit} + +
openCommit(commit[0])} on:keypress={()=>openCommit(commit[0])}> + {#if commit[1].final_consistency} + {:else if commit[1].signature} + {/if} + + {#if commit[1].commit_type==="TransactionBoth"}{/if} + {commit[0].substring(0,7)}
+ {commit[1].author.substring(0,9)} +
+ + {/each} + +
+ +
+ + \ No newline at end of file diff --git a/ng-app/src/history/LICENSE.md b/ng-app/src/lib/panes/history/LICENSE.md similarity index 100% rename from ng-app/src/history/LICENSE.md rename to ng-app/src/lib/panes/history/LICENSE.md diff --git a/ng-app/src/history/gitgraph-core/branch.ts b/ng-app/src/lib/panes/history/gitgraph-core/branch.ts similarity index 100% rename from ng-app/src/history/gitgraph-core/branch.ts rename to ng-app/src/lib/panes/history/gitgraph-core/branch.ts diff --git a/ng-app/src/history/gitgraph-core/branches-paths.ts b/ng-app/src/lib/panes/history/gitgraph-core/branches-paths.ts similarity index 100% rename from ng-app/src/history/gitgraph-core/branches-paths.ts rename to ng-app/src/lib/panes/history/gitgraph-core/branches-paths.ts diff --git a/ng-app/src/history/gitgraph-core/commit.ts b/ng-app/src/lib/panes/history/gitgraph-core/commit.ts similarity index 100% rename from ng-app/src/history/gitgraph-core/commit.ts rename to ng-app/src/lib/panes/history/gitgraph-core/commit.ts diff --git a/ng-app/src/history/gitgraph-core/gitgraph-user-api.ts b/ng-app/src/lib/panes/history/gitgraph-core/gitgraph-user-api.ts similarity index 81% rename from ng-app/src/history/gitgraph-core/gitgraph-user-api.ts rename to ng-app/src/lib/panes/history/gitgraph-core/gitgraph-user-api.ts index da15587..e7977d5 100644 --- a/ng-app/src/history/gitgraph-core/gitgraph-user-api.ts +++ b/ng-app/src/lib/panes/history/gitgraph-core/gitgraph-user-api.ts @@ -97,7 +97,14 @@ class GitgraphUserApi { data["parents"].forEach((parent) => { let new_lane = this._graph.last_on_swimlanes.indexOf(parent); if ( new_lane < lane ) { + if (lane != Number.MAX_VALUE) + this._graph.swimlanes[lane] = undefined; + lane = new_lane; + } else { + // we close that lane + //this._graph.swimlanes + this._graph.swimlanes[new_lane] = undefined; } }); branch = this._graph.swimlanes[lane]; @@ -108,35 +115,39 @@ class GitgraphUserApi { this._graph.last_on_swimlanes[lane] = data["hash"]; } else { branch = data["hash"]; - // this._graph.swimlanes.some((b, col) => { - // console.log("is empty? ",col,!b); - // if (!b) { - // lane = col; - // return true; - // } - // }); - // if (!lane) { + + this._graph.swimlanes.some((b, col) => { + //console.log("is empty? ",col,!b); + if (!b) { + let r = this._graph.rows.getRowOf(this._graph.last_on_swimlanes[col]); + if (data["parents"].some( (parent) => this._graph.rows.getRowOf(parent) < r )) + return false; + lane = col; + return true; + } + }); + if (!lane) { lane = this._graph.swimlanes.length; this._graph.swimlanes.push(branch); this._graph.last_on_swimlanes.push(branch); - // } else { - // this._graph.swimlanes[lane] = branch; - // this._graph.last_on_swimlanes[lane] = branch; - // } + } else { + this._graph.swimlanes[lane] = branch; + this._graph.last_on_swimlanes[lane] = branch; + } } - data["parents"].forEach((parent) => { - let r = this._graph.rows.getRowOf(parent); - let c = this._graph.commits[r]; - let b = c.branch; - if (branch!=b) { - this._graph.swimlanes.forEach((bb, col) => { - if (bb == b) { - this._graph.swimlanes[col] = undefined; - } - }); - } - }); + // data["parents"].forEach((parent) => { + // let r = this._graph.rows.getRowOf(parent); + // let c = this._graph.commits[r]; + // let b = c.branch; + // if (branch!=b) { + // this._graph.swimlanes.forEach((bb, col) => { + // if (bb == b) { + // this._graph.swimlanes[col] = undefined; + // } + // }); + // } + // }); // if (!this._graph.branches.has(branch)) { // this._graph.createBranch({name:branch}); diff --git a/ng-app/src/history/gitgraph-core/gitgraph.ts b/ng-app/src/lib/panes/history/gitgraph-core/gitgraph.ts similarity index 100% rename from ng-app/src/history/gitgraph-core/gitgraph.ts rename to ng-app/src/lib/panes/history/gitgraph-core/gitgraph.ts diff --git a/ng-app/src/history/gitgraph-core/graph-rows.ts b/ng-app/src/lib/panes/history/gitgraph-core/graph-rows.ts similarity index 100% rename from ng-app/src/history/gitgraph-core/graph-rows.ts rename to ng-app/src/lib/panes/history/gitgraph-core/graph-rows.ts diff --git a/ng-app/src/history/gitgraph-core/index.ts b/ng-app/src/lib/panes/history/gitgraph-core/index.ts similarity index 100% rename from ng-app/src/history/gitgraph-core/index.ts rename to ng-app/src/lib/panes/history/gitgraph-core/index.ts diff --git a/ng-app/src/history/gitgraph-core/regular-graph-rows.ts b/ng-app/src/lib/panes/history/gitgraph-core/regular-graph-rows.ts similarity index 100% rename from ng-app/src/history/gitgraph-core/regular-graph-rows.ts rename to ng-app/src/lib/panes/history/gitgraph-core/regular-graph-rows.ts diff --git a/ng-app/src/history/gitgraph-core/template.ts b/ng-app/src/lib/panes/history/gitgraph-core/template.ts similarity index 100% rename from ng-app/src/history/gitgraph-core/template.ts rename to ng-app/src/lib/panes/history/gitgraph-core/template.ts diff --git a/ng-app/src/history/gitgraph-core/utils.ts b/ng-app/src/lib/panes/history/gitgraph-core/utils.ts similarity index 100% rename from ng-app/src/history/gitgraph-core/utils.ts rename to ng-app/src/lib/panes/history/gitgraph-core/utils.ts diff --git a/ng-app/src/history/gitgraph-js/gitgraph.ts b/ng-app/src/lib/panes/history/gitgraph-js/gitgraph.ts similarity index 96% rename from ng-app/src/history/gitgraph-js/gitgraph.ts rename to ng-app/src/lib/panes/history/gitgraph-js/gitgraph.ts index a5fc3c0..d12871b 100644 --- a/ng-app/src/history/gitgraph-js/gitgraph.ts +++ b/ng-app/src/lib/panes/history/gitgraph-js/gitgraph.ts @@ -100,7 +100,8 @@ function createGitgraph( createG({ // Translate graph left => left-most branch label is not cropped (horizontal) // Translate graph down => top-most commit tooltip is not cropped - translate: { x: 0, y: TOOLTIP_PADDING }, + translate: { x: 10, y: TOOLTIP_PADDING }, + scale: 0.75, children: [renderBranchesPaths(branchesPaths), $commits], }), ); @@ -312,8 +313,17 @@ function createGitgraph( return message; } + let msg = commit.message.split(" "); + const text = createText({ - content: commit.message, + content: msg[0], + fill: commit.style.message.color || "", + font: commit.style.message.font, + onClick: commit.onMessageClick, + }); + + const text2 = createText({ + content: msg[1], fill: commit.style.message.color || "", font: commit.style.message.font, onClick: commit.onMessageClick, @@ -324,6 +334,13 @@ function createGitgraph( children: [text], }); + let message2 = createG({ + translate: { x: 0, y: commit.style.dot.size*2 }, + children: [text2], + }); + + message.appendChild(message2); + if (commit.body) { const body = createForeignObject({ width: 600, diff --git a/ng-app/src/history/gitgraph-js/svg-elements.ts b/ng-app/src/lib/panes/history/gitgraph-js/svg-elements.ts similarity index 97% rename from ng-app/src/history/gitgraph-js/svg-elements.ts rename to ng-app/src/lib/panes/history/gitgraph-js/svg-elements.ts index 27c79ee..a45115a 100644 --- a/ng-app/src/history/gitgraph-js/svg-elements.ts +++ b/ng-app/src/lib/panes/history/gitgraph-js/svg-elements.ts @@ -49,6 +49,7 @@ interface GOptions { x: number; y: number; }; + scale?: number; fill?: string; stroke?: string; strokeWidth?: number; @@ -60,11 +61,11 @@ interface GOptions { function createG(options: GOptions): SVGGElement { const g = document.createElementNS(SVG_NAMESPACE, "g"); options.children.forEach((child) => child && g.appendChild(child)); - + let scale = options.scale ? ` scale(${options.scale})`: ""; if (options.translate) { g.setAttribute( "transform", - `translate(${options.translate.x}, ${options.translate.y})`, + `translate(${options.translate.x}, ${options.translate.y})${scale}`, ); } @@ -118,7 +119,7 @@ function createText(options: TextOptions): SVGTextElement { } if (options.font) { - text.setAttribute("style", `font: ${options.font}`); + text.setAttribute("style", `font-family:monospace;font: Courier`); } if (options.anchor) { diff --git a/ng-app/src/history/gitgraph-js/tooltip.ts b/ng-app/src/lib/panes/history/gitgraph-js/tooltip.ts similarity index 96% rename from ng-app/src/history/gitgraph-js/tooltip.ts rename to ng-app/src/lib/panes/history/gitgraph-js/tooltip.ts index a69224e..eb76018 100644 --- a/ng-app/src/history/gitgraph-js/tooltip.ts +++ b/ng-app/src/lib/panes/history/gitgraph-js/tooltip.ts @@ -11,7 +11,7 @@ function createTooltip(commit: Commit): SVGElement { const path = createPath({ d: "", fill: "#EEE" }); const text = createText({ translate: { x: OFFSET + PADDING, y: 0 }, - content: `${commit.hashAbbrev} - ${commit.subject}`, + content: `${commit.hashAbbrev}`, fill: "#333", }); diff --git a/ng-app/src/store.ts b/ng-app/src/store.ts index 72cd530..14c1aed 100644 --- a/ng-app/src/store.ts +++ b/ng-app/src/store.ts @@ -452,7 +452,12 @@ export const branch_subscribe = function(nuri:string, in_tab:boolean) { //console.log("sub"); let already_subscribed = all_branches[nuri]; if (!already_subscribed) { - const { subscribe, set, update } = writable({graph:[], discrete:[], files:[], history: false, heads: []}); // create the underlying writable store + const { subscribe, set, update } = writable({graph:[], discrete:[], files:[], history: {start:()=>{}, stop:()=>{}, take:()=>{}, commits:false}, heads: []}); // create the underlying writable store + update((old)=> { + old.history.start = () => update((o) => {o.history.commits = true; return o;}) ; + old.history.stop = () => update((o) => {o.history.commits = false; return o;}) ; + old.history.take = () => { let res: boolean | Array<{}> = false; update((o) => {res = o.history.commits; o.history.commits = []; return o;}); return res;} + return old;}); let count = 0; let unsub = () => { }; already_subscribed = { @@ -538,12 +543,12 @@ export const branch_subscribe = function(nuri:string, in_tab:boolean) { } } old.heads.push(response.V0.Patch.commit_id); - if (old.history!==false) { + if (old.history.commits!==false) { let commit = [response.V0.Patch.commit_id, response.V0.Patch.commit_info]; - if (old.history === true) { - old.history = [commit]; + if (old.history.commits === true) { + old.history.commits = [commit]; } else { - old.history.push(commit); + old.history.commits.push(commit); } } if (response.V0.Patch.graph) { diff --git a/ng-app/src/styles.css b/ng-app/src/styles.css index d4b0f39..9877e6e 100644 --- a/ng-app/src/styles.css +++ b/ng-app/src/styles.css @@ -34,6 +34,9 @@ td.hljs { #menu-modal div { padding: 0; } +#menu-modal div.commit { + padding: 8px; +} #menu-modal > button { display: none; } diff --git a/ng-app/src/tab.ts b/ng-app/src/tab.ts index 7d5a77f..7481b71 100644 --- a/ng-app/src/tab.ts +++ b/ng-app/src/tab.ts @@ -160,19 +160,23 @@ const class_to_viewers_editors = (class_name: string) => { } export const open_branch = (nuri: string, in_tab: boolean) => { - all_tabs.update((old) => { - if (!old[nuri]) { - //console.log("creating tab for ",nuri) - old[nuri] = JSON.parse(JSON.stringify(old[""])); - old[nuri].branch.nuri = nuri; - } - return old; - }); + if (!get(all_tabs)[nuri]) { + all_tabs.update((old) => { + if (!old[nuri]) { + //console.log("creating tab for ",nuri) + old[nuri] = JSON.parse(JSON.stringify(old[""])); + old[nuri].branch.nuri = nuri; + } + return old; + }); + } + if (in_tab) { cur_branch.set(nuri); let store_type = get(all_tabs)[nuri].store.store_type; if (store_type) change_nav_bar(`nav:${store_type}`,get(format)(`doc.${store_type}_store`), undefined); } + } export const update_class = (cur_tab, class_name) => { diff --git a/ng-net/src/app_protocol.rs b/ng-net/src/app_protocol.rs index 1cabb72..1c99657 100644 --- a/ng-net/src/app_protocol.rs +++ b/ng-net/src/app_protocol.rs @@ -18,8 +18,8 @@ use ng_repo::errors::NgError; use ng_repo::log::*; use ng_repo::repo::CommitInfo; use ng_repo::types::*; -use ng_repo::utils::decode_overlayid; use ng_repo::utils::{decode_digest, decode_key, decode_sym_key}; +use ng_repo::utils::{decode_overlayid, display_timestamp_local}; use crate::types::*; @@ -144,6 +144,7 @@ pub struct CommitInfoJs { pub key: String, pub signature: Option, pub author: String, + pub timestamp: String, pub final_consistency: bool, pub commit_type: CommitType, pub branch: Option, @@ -158,6 +159,7 @@ impl From<&CommitInfo> for CommitInfoJs { key: info.key.to_string(), signature: info.signature.as_ref().map(|s| NuriV0::object_ref(&s)), author: info.author.clone(), + timestamp: display_timestamp_local(info.timestamp), final_consistency: info.final_consistency, commit_type: info.commit_type.clone(), branch: info.branch.map(|b| b.to_string()), diff --git a/ng-repo/Cargo.toml b/ng-repo/Cargo.toml index bfcb0c8..e48d2ec 100644 --- a/ng-repo/Cargo.toml +++ b/ng-repo/Cargo.toml @@ -35,13 +35,14 @@ blake3 = "1.3.1" chacha20 = "0.9.0" ed25519-dalek = "1.0.1" sbbf-rs-safe = "0.3.2" +lazy_static = "1.4.0" curve25519-dalek = "3.2.0" threshold_crypto = "0.4.0" crypto_box = { version = "0.8.2", features = ["seal"] } zeroize = { version = "1.7.0", features = ["zeroize_derive"] } base64-url = "2.0.0" web-time = "0.2.0" -time = { version= "0.3.36", features = ["formatting"] } +time = { version= "0.3.36", features = ["formatting","local-offset"] } wasm-bindgen = "0.2" os_info = "3" current_platform = "0.2.0" @@ -52,4 +53,5 @@ log = "0.4" getrandom = "0.2.7" [target.'cfg(target_arch = "wasm32")'.dependencies] -gloo-timers = "0.2.6" \ No newline at end of file +gloo-timers = "0.2.6" +time = { version= "0.3.36", features = ["formatting","local-offset","wasm-bindgen"] } \ No newline at end of file diff --git a/ng-repo/src/commit.rs b/ng-repo/src/commit.rs index e958e79..bf2278c 100644 --- a/ng-repo/src/commit.rs +++ b/ng-repo/src/commit.rs @@ -70,6 +70,7 @@ impl CommitV0 { branch, header_keys: headers.1, quorum, + timestamp: now_timestamp(), metadata, body, }); @@ -105,6 +106,7 @@ impl CommitV0 { branch, header_keys: headers.1, quorum, + timestamp: now_timestamp(), metadata, body, }); @@ -435,6 +437,10 @@ impl Commit { self.content().author() } + pub fn timestamp(&self) -> Timestamp { + self.content().timestamp() + } + pub fn final_consistency(&self) -> bool { self.content().final_consistency() } @@ -459,6 +465,7 @@ impl Commit { key: self.key().unwrap(), signature: None, author: repo.get_user_string(self.author()), + timestamp: self.timestamp(), final_consistency: self.final_consistency(), commit_type: self.get_type().unwrap(), branch: None, diff --git a/ng-repo/src/repo.rs b/ng-repo/src/repo.rs index 4fd6eb3..cfaf258 100644 --- a/ng-repo/src/repo.rs +++ b/ng-repo/src/repo.rs @@ -167,6 +167,7 @@ pub struct CommitInfo { pub key: ObjectKey, pub signature: Option, pub author: String, + pub timestamp: Timestamp, pub final_consistency: bool, pub commit_type: CommitType, pub branch: Option, @@ -204,6 +205,9 @@ impl Repo { } else { let commit_type = cobj.get_type().unwrap(); let acks = cobj.acks(); + for a in acks.iter() { + log_debug!("ACKS of {} {}", id.to_string(), a.id.to_string()); + } let (past, real_acks, next_future) = match commit_type { CommitType::SyncSignature => { assert_eq!(acks.len(), 1); @@ -221,6 +225,7 @@ impl Repo { key: o.key().unwrap(), signature: Some(sign_ref.clone()), author: self.get_user_string(o.author()), + timestamp: o.timestamp(), final_consistency: o.final_consistency(), commit_type: o.get_type().unwrap(), branch: None, @@ -256,6 +261,7 @@ impl Repo { key: cobj.key().unwrap(), signature: None, author: self.get_user_string(cobj.author()), + timestamp: cobj.timestamp(), final_consistency: cobj.final_consistency(), commit_type, branch: None, @@ -276,9 +282,13 @@ impl Repo { Ok(root) } - fn past_is_all_in(past: &Vec, already_in: &HashMap) -> bool { + fn past_is_all_in( + past: &Vec, + already_in: &HashMap, + coming_from: &ObjectId, + ) -> bool { for p in past { - if !already_in.contains_key(p) { + if !already_in.contains_key(p) && p != coming_from { return false; } } @@ -294,10 +304,10 @@ impl Repo { //swimlanes: &mut Vec>, ) -> Vec<(ObjectId, CommitInfo)> { let (_, c) = dag.get(id).unwrap(); - log_debug!("{id}"); - if c.past.len() > 1 && !Self::past_is_all_in(&c.past, already_in) { + //log_debug!("processing {id}"); + if c.past.len() > 1 && !Self::past_is_all_in(&c.past, already_in, id) { // we postpone the merge until all the past commits have been added - // log_debug!("postponed {}", id); + //log_debug!("postponed {}", id); vec![] } else { let (future, mut info) = dag.remove(id).unwrap(); @@ -328,7 +338,7 @@ impl Repo { // swimlane.push(branch.clone()); // } let mut res = vec![(*id, info)]; - let first_child_branch = branch.clone(); + let mut first_child_branch = branch.clone(); already_in.insert(*id, branch); let mut future = Vec::from_iter(future); future.sort(); @@ -345,9 +355,26 @@ impl Repo { let close = if previous_ordinal > new_ordinal { let _ = info.branch.insert(branch); // we close the previous branch + // log_debug!( + // "closing previous {} {} in favor of new {} {}", + // previous_ordinal, + // b, + // new_ordinal, + // branch + // ); &b } else { // otherwise we close the new branch + if first_child_branch == branch { + first_child_branch = b; + } + // log_debug!( + // "closing new branch {} {} in favor of previous {} {}", + // new_ordinal, + // branch, + // previous_ordinal, + // b + // ); &branch }; let i = branches.get(close).unwrap(); @@ -356,6 +383,15 @@ impl Repo { let _ = info.branch.insert(branch); } } + // log_debug!( + // "branches_order before children of {child} {:?}", + // branches_order + // .iter() + // .enumerate() + // .map(|(i, b)| b.map_or(format!("{i}:closed"), |bb| format!("{i}:{bb}"))) + // .collect::>() + // .join(" -- ") + // ); res.append(&mut Self::collapse( child, dag, @@ -364,18 +400,32 @@ impl Repo { branches, //swimlanes, )); + // log_debug!( + // "branches_order after children of {child} {:?}", + // branches_order + // .iter() + // .enumerate() + // .map(|(i, b)| b.map_or(format!("{i}:closed"), |bb| format!("{i}:{bb}"))) + // .collect::>() + // .join(" -- ") + // ); // each other child gets a new branch if let Some(next) = iterator.peek() { branch = **next; + if branches.contains_key(*next) { + continue; + } let mut branch_inserted = false; let mut first_child_branch_passed = false; for (i, next_branch) in branches_order.iter_mut().enumerate() { if let Some(b) = next_branch { if b == &first_child_branch { first_child_branch_passed = true; + //log_debug!("first_child_branch_passed"); } } if next_branch.is_none() && first_child_branch_passed { + //log_debug!("found empty lane {}, putting branch in it {}", i, branch); let _ = next_branch.insert(branch.clone()); branches.insert(branch, i); branch_inserted = true; @@ -384,6 +434,11 @@ impl Repo { } if !branch_inserted { //swimlanes.push(Vec::new()); + // log_debug!( + // "adding new lane {}, for branch {}", + // branches_order.len(), + // branch + // ); branches_order.push(Some(branch.clone())); branches.insert(branch, branches_order.len() - 1); } @@ -398,13 +453,23 @@ impl Repo { heads: &[ObjectRef], ) -> Result<(Vec<(ObjectId, CommitInfo)>, Vec>), VerifierError> { assert!(!heads.is_empty()); + // for h in heads { + // log_debug!("HEAD {}", h.id); + // } let mut visited = HashMap::new(); let mut root = None; for id in heads { if let Ok(cobj) = Commit::load(id.clone(), &self.store, true) { - root = self.load_causal_past(&cobj, &mut visited, None)?; + let r = self.load_causal_past(&cobj, &mut visited, None)?; + //log_debug!("ROOT? {:?}", r.map(|rr| rr.to_string())); + if r.is_some() { + root = r; + } } } + // for h in visited.keys() { + // log_debug!("VISITED {}", h); + // } if root.is_none() { return Err(VerifierError::MalformedDag); } diff --git a/ng-repo/src/types.rs b/ng-repo/src/types.rs index e359674..596861b 100644 --- a/ng-repo/src/types.rs +++ b/ng-repo/src/types.rs @@ -2592,7 +2592,9 @@ pub struct CommitContentV0 { /// This commit can only be accepted if signed by this quorum pub quorum: QuorumType, - /// App-specific metadata (commit message, creation time, etc) + pub timestamp: Timestamp, + + /// App-specific metadata (commit message?) #[serde(with = "serde_bytes")] pub metadata: Vec, @@ -2618,6 +2620,11 @@ impl CommitContent { CommitContent::V0(v0) => &v0.author, } } + pub fn timestamp(&self) -> Timestamp { + match self { + CommitContent::V0(v0) => v0.timestamp, + } + } pub fn branch(&self) -> &BranchId { match self { CommitContent::V0(v0) => &v0.branch, diff --git a/ng-repo/src/utils.rs b/ng-repo/src/utils.rs index 971e012..bdd0930 100644 --- a/ng-repo/src/utils.rs +++ b/ng-repo/src/utils.rs @@ -14,8 +14,7 @@ use ed25519_dalek::*; use futures::channel::mpsc; use rand::rngs::OsRng; use rand::RngCore; -#[cfg(not(target_arch = "wasm32"))] -use time::OffsetDateTime; +use time::{OffsetDateTime, UtcOffset}; use web_time::{Duration, SystemTime, UNIX_EPOCH}; use zeroize::Zeroize; @@ -220,20 +219,52 @@ pub fn timestamp_after(duration: Duration) -> Timestamp { /// displays the NextGraph Timestamp in UTC. #[cfg(not(target_arch = "wasm32"))] pub fn display_timestamp(ts: &Timestamp) -> String { - let st = SystemTime::UNIX_EPOCH - + Duration::from_secs(EPOCH_AS_UNIX_TIMESTAMP) - + Duration::from_secs(*ts as u64 * 60u64); - let dt: OffsetDateTime = st.into(); + let dur = + Duration::from_secs(EPOCH_AS_UNIX_TIMESTAMP) + Duration::from_secs(*ts as u64 * 60u64); + + let dt: OffsetDateTime = OffsetDateTime::UNIX_EPOCH + dur; dt.format(&time::format_description::parse("[day]/[month]/[year] [hour]:[minute] UTC").unwrap()) .unwrap() } +/// displays the NextGraph Timestamp in local time for the history (JS) +pub fn display_timestamp_local(ts: Timestamp) -> String { + let dur = Duration::from_secs(EPOCH_AS_UNIX_TIMESTAMP) + Duration::from_secs(ts as u64 * 60u64); + + let dt: OffsetDateTime = OffsetDateTime::UNIX_EPOCH + dur; + + let dt = dt.to_offset(TIMEZONE_OFFSET.clone()); + dt.format( + &time::format_description::parse("[day]/[month]/[year repr:last_two] [hour]:[minute]") + .unwrap(), + ) + .unwrap() +} + +use lazy_static::lazy_static; +lazy_static! { + static ref TIMEZONE_OFFSET: UtcOffset = unsafe { + time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Unsound); + UtcOffset::current_local_offset().unwrap() + }; +} + pub(crate) type Receiver = mpsc::UnboundedReceiver; #[cfg(test)] mod test { - use crate::log::*; + use crate::{ + log::*, + utils::{display_timestamp_local, now_timestamp}, + }; + + #[test] + pub fn test_time() { + let time = now_timestamp() + 120; // 2 hours later + log_info!("{}", display_timestamp_local(time)); + } + #[test] pub fn test_locales() { let list = vec!["C", "c", "aa-bb-cc-dd", "aa-ff_bb.456d"]; diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs index 28f1591..d2ce93f 100644 --- a/ng-sdk-js/src/lib.rs +++ b/ng-sdk-js/src/lib.rs @@ -270,8 +270,7 @@ pub async fn sparql_query( let session_id: u64 = serde_wasm_bindgen::from_value::(session_id) .map_err(|_| "Invalid session_id".to_string())?; let nuri = if nuri.is_string() { - NuriV0::new_from(&nuri.as_string().unwrap()) - .map_err(|_| "Deserialization error of Nuri".to_string())? + NuriV0::new_from(&nuri.as_string().unwrap()).map_err(|e| e.to_string())? } else { NuriV0::new_entire_user_site() }; @@ -320,7 +319,7 @@ pub async fn sparql_update( ) -> Result<(), String> { let session_id: u64 = serde_wasm_bindgen::from_value::(session_id) .map_err(|_| "Invalid session_id".to_string())?; - let nuri = NuriV0::new_from(&nuri).map_err(|_| "Deserialization error of Nuri".to_string())?; + let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?; let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_write_query(), @@ -350,8 +349,7 @@ pub async fn sparql_query( .map_err(|_| "Invalid session_id".to_string())?; let nuri = if nuri.is_string() { - NuriV0::new_from(&nuri.as_string().unwrap()) - .map_err(|_| "Deserialization error of Nuri".to_string())? + NuriV0::new_from(&nuri.as_string().unwrap()).map_err(|e| e.to_string())? } else { NuriV0::new_entire_user_site() }; @@ -416,13 +414,13 @@ pub async fn rdf_dump(session_id: JsValue) -> Result { } #[wasm_bindgen] -pub async fn branch_history(session_id: JsValue) -> Result { +pub async fn branch_history(session_id: JsValue, nuri: String) -> Result { let session_id: u64 = serde_wasm_bindgen::from_value::(session_id) .map_err(|_| "Invalid session_id".to_string())?; let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_history(), - nuri: NuriV0::new_private_store_target(), + nuri: NuriV0::new_from(&nuri).map_err(|e| e.to_string())?, payload: None, session_id, }); @@ -432,6 +430,7 @@ pub async fn branch_history(session_id: JsValue) -> Result { .map_err(|e: NgError| e.to_string())?; let AppResponse::V0(res) = res; + log_debug!("{:?}", res); match res { AppResponseV0::History(s) => Ok(serde_wasm_bindgen::to_value(&s.to_js()).unwrap()), _ => Err("invalid response".to_string()), @@ -932,7 +931,7 @@ pub async fn file_get_from_private_store( let session_id: u64 = serde_wasm_bindgen::from_value::(session_id) .map_err(|_| "Deserialization error of session_id".to_string())?; - let nuri = NuriV0::new_from(&nuri).map_err(|_| "Deserialization error of Nuri".to_string())?; + let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?; let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri.clone(), None); request.set_session_id(session_id);