Move notes store to rust
This commit is contained in:
parent
849400cf35
commit
75f3a84e5f
12 changed files with 617 additions and 138 deletions
52
gdn-app/src/api.ts
Normal file
52
gdn-app/src/api.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { Note } from "./types";
|
||||
|
||||
export async function apiNoteChildAdd(
|
||||
id: string,
|
||||
childId: string,
|
||||
childPosition: number,
|
||||
): Promise<void> {
|
||||
await invoke("note_child_add", { id, childId, childPosition });
|
||||
}
|
||||
|
||||
export async function apiNoteChildMove(
|
||||
childId: string,
|
||||
fromId: string,
|
||||
fromIteration: number,
|
||||
toId: string,
|
||||
toPosition: number,
|
||||
): Promise<void> {
|
||||
await invoke("note_child_move", { childId, fromId, fromIteration, toId, toPosition });
|
||||
}
|
||||
|
||||
export async function apiNoteChildRemove(
|
||||
id: string,
|
||||
childId: string,
|
||||
childIteration: number,
|
||||
): Promise<void> {
|
||||
await invoke("note_child_remove", { id, childId, childIteration });
|
||||
}
|
||||
|
||||
export async function apiNoteChildrenSet(id: string, children: string[]): Promise<void> {
|
||||
await invoke("note_children_set", { id, children });
|
||||
}
|
||||
|
||||
export async function apiNoteCreate(text: string): Promise<Note> {
|
||||
return Note.parse(await invoke("note_create", { text }));
|
||||
}
|
||||
|
||||
export async function apiNoteDelete(id: string): Promise<void> {
|
||||
await invoke("note_delete", { id });
|
||||
}
|
||||
|
||||
export async function apiNoteGet(id: string): Promise<Note | null> {
|
||||
return Note.nullable().parse(await invoke("note_get", { id }));
|
||||
}
|
||||
|
||||
export async function apiNoteTextSet(id: string, text: string): Promise<void> {
|
||||
await invoke("note_text_set", { id, text });
|
||||
}
|
||||
|
||||
export async function apiNotesClear(): Promise<void> {
|
||||
await invoke("notes_clear");
|
||||
}
|
||||
|
|
@ -16,41 +16,42 @@ const repos = useReposStore();
|
|||
const notes = useNotesStore();
|
||||
const ui = useUiStore();
|
||||
|
||||
function mkNote(text: string, ...children: string[]): Note {
|
||||
const note = notes.createNote(text);
|
||||
for (const child of children) notes.addChild(note.id, child, -1);
|
||||
async function mkNote(text: string, ...children: string[]): Promise<Note> {
|
||||
const note = await notes.createNote(text);
|
||||
for (const child of children) await notes.addChild(note.id, child, -1);
|
||||
return note;
|
||||
}
|
||||
|
||||
function createSomeNotes(): void {
|
||||
notes.clearNotes();
|
||||
async function createSomeNotes(): Promise<void> {
|
||||
await notes.clearNotes();
|
||||
|
||||
const n2n1 = mkNote("n2n1");
|
||||
const n2n2 = mkNote("n2n2");
|
||||
const n2n3 = mkNote("n2n3");
|
||||
const n2n1 = await mkNote("n2n1");
|
||||
const n2n2 = await mkNote("n2n2");
|
||||
const n2n3 = await mkNote("n2n3");
|
||||
|
||||
const n1 = mkNote("n1");
|
||||
const n2 = mkNote("n2", n2n1.id, n2n2.id, n2n3.id);
|
||||
const n3 = mkNote("n3", n2n1.id);
|
||||
const n4 = mkNote("n4");
|
||||
const n5 = mkNote("n5", "NaN (not a note)");
|
||||
const n1 = await mkNote("n1");
|
||||
const n2 = await mkNote("n2", n2n1.id, n2n2.id, n2n3.id);
|
||||
const n3 = await mkNote("n3", n2n1.id);
|
||||
const n4 = await mkNote("n4");
|
||||
const n5 = await mkNote("n5", "n0000000000000000");
|
||||
|
||||
const root = mkNote("root", n1.id, n2.id, n3.id, n4.id, n5.id, n2.id);
|
||||
const root = await mkNote("root", n1.id, n2.id, n3.id, n4.id, n5.id, n2.id);
|
||||
|
||||
ui.pushAnchorId(root.id);
|
||||
|
||||
// Shuffle children of root
|
||||
notes.setChildren(
|
||||
const rootChildren = (await notes.getNote(root.id))?.children ?? [];
|
||||
await notes.setChildren(
|
||||
root.id,
|
||||
root.children
|
||||
rootChildren
|
||||
.map((it) => ({ it, rand: Math.random() }))
|
||||
.sort((a, b) => a.rand - b.rand)
|
||||
.map(({ it }) => it),
|
||||
);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
createSomeNotes();
|
||||
onMounted(async () => {
|
||||
await createSomeNotes();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { computed, ref, watchEffect } from "vue";
|
|||
import CNoteButton from "./CNoteButton.vue";
|
||||
import CNoteChildEditor from "./CNoteChildEditor.vue";
|
||||
import CNoteEditor from "./CNoteEditor.vue";
|
||||
import { computedAsync } from "@vueuse/core";
|
||||
|
||||
const notes = useNotesStore();
|
||||
const ui = useUiStore();
|
||||
|
|
@ -39,15 +40,21 @@ const {
|
|||
}>();
|
||||
|
||||
const id = computed(() => segment.id);
|
||||
const note = computed(() => notes.getNote(id.value));
|
||||
const note = computedAsync(async () => await notes.getNote(id.value));
|
||||
|
||||
const parents = computed(() => {
|
||||
let parents = notes.getParents(id.value);
|
||||
if (parentId) parents = parents.difference(new Set([parentId]));
|
||||
return [...parents].sort().map((id) => ({ id, text: notes.getNote(id)?.text }));
|
||||
const parents = computedAsync(async () => {
|
||||
const result = [];
|
||||
for (const parent of note.value?.parents ?? new Set()) {
|
||||
if (parentId !== undefined && parent === parentId) continue;
|
||||
result.push({
|
||||
id: parent,
|
||||
text: (await notes.getNote(parent))?.text,
|
||||
});
|
||||
}
|
||||
result.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
||||
return result;
|
||||
});
|
||||
|
||||
// Our children and the
|
||||
const children = computed(() => {
|
||||
if (!note.value) return [];
|
||||
const seen = new Map<string, number>();
|
||||
|
|
@ -84,13 +91,13 @@ function onClick(): void {
|
|||
ui.toggleOpen(path);
|
||||
}
|
||||
|
||||
function onDeleteButtonClick(): void {
|
||||
notes.deleteNote(segment.id);
|
||||
async function onDeleteButtonClick(): Promise<void> {
|
||||
await notes.deleteNote(segment.id);
|
||||
}
|
||||
|
||||
function onUnlinkButtonClick(): void {
|
||||
async function onUnlinkButtonClick(): Promise<void> {
|
||||
if (parentId === undefined) return;
|
||||
notes.removeChild(parentId, segment);
|
||||
await notes.removeChild(parentId, segment);
|
||||
}
|
||||
|
||||
function onEditButtonClick(): void {
|
||||
|
|
@ -127,9 +134,9 @@ function onEditEditorClose(): void {
|
|||
ui.focus();
|
||||
}
|
||||
|
||||
function onEditEditorFinish(text: string): void {
|
||||
async function onEditEditorFinish(text: string): Promise<void> {
|
||||
if (!note.value) return;
|
||||
notes.setText(segment.id, text);
|
||||
await notes.setText(segment.id, text);
|
||||
onEditEditorClose();
|
||||
}
|
||||
|
||||
|
|
@ -137,36 +144,36 @@ function onInsertEditorClose(): void {
|
|||
ui.focus();
|
||||
}
|
||||
|
||||
function onInsertEditorFinish(text: string): void {
|
||||
async function onInsertEditorFinish(text: string): Promise<void> {
|
||||
if (!note.value) return;
|
||||
|
||||
if (insertIndex.value !== undefined) {
|
||||
const child = notes.createNote(text);
|
||||
notes.addChild(segment.id, child.id, insertIndex.value);
|
||||
const child = await notes.createNote(text);
|
||||
await notes.addChild(segment.id, child.id, insertIndex.value);
|
||||
}
|
||||
|
||||
onInsertEditorClose();
|
||||
}
|
||||
|
||||
function onInsertEditorMove(): void {
|
||||
async function onInsertEditorMove(): Promise<void> {
|
||||
if (!ui.pinned) return;
|
||||
if (insertIndex.value === undefined) return;
|
||||
|
||||
if (ui.pinned.parentId) {
|
||||
notes.moveChild(ui.pinned.parentId, ui.pinned.segment, segment.id, insertIndex.value);
|
||||
await notes.moveChild(ui.pinned.parentId, ui.pinned.segment, segment.id, insertIndex.value);
|
||||
} else {
|
||||
notes.addChild(segment.id, ui.pinned.segment.id, insertIndex.value);
|
||||
await notes.addChild(segment.id, ui.pinned.segment.id, insertIndex.value);
|
||||
}
|
||||
|
||||
onInsertEditorClose();
|
||||
ui.unsetPinned();
|
||||
}
|
||||
|
||||
function onInsertEditorCopy(): void {
|
||||
async function onInsertEditorCopy(): Promise<void> {
|
||||
if (!ui.pinned) return;
|
||||
if (insertIndex.value === undefined) return;
|
||||
|
||||
notes.addChild(segment.id, ui.pinned.segment.id, insertIndex.value);
|
||||
await notes.addChild(segment.id, ui.pinned.segment.id, insertIndex.value);
|
||||
|
||||
onInsertEditorClose();
|
||||
}
|
||||
|
|
@ -175,7 +182,7 @@ function onInsertEditorCopy(): void {
|
|||
<template>
|
||||
<div class="flex flex-col">
|
||||
<!-- Parents -->
|
||||
<div v-if="parents.length > 0" class="pt-1">
|
||||
<div v-if="(parents?.length ?? 0) > 0" class="pt-1">
|
||||
<div v-for="parent of parents" :key="parent.id" class="pl-6 text-xs text-neutral-400">
|
||||
<RiCornerUpRightLine size="12px" class="inline" /> {{ parent.text }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import { createPinia } from "pinia";
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import { useNotesStore } from "./stores/notes";
|
||||
|
||||
createApp(App).use(createPinia()).mount("#app");
|
||||
const app = createApp(App).use(createPinia());
|
||||
|
||||
await useNotesStore().initialize();
|
||||
|
||||
app.mount("#app");
|
||||
|
|
|
|||
|
|
@ -1,126 +1,89 @@
|
|||
import {
|
||||
apiNoteChildAdd,
|
||||
apiNoteChildMove,
|
||||
apiNoteChildRemove,
|
||||
apiNoteChildrenSet,
|
||||
apiNoteCreate,
|
||||
apiNoteDelete,
|
||||
apiNoteGet,
|
||||
apiNotesClear,
|
||||
apiNoteTextSet,
|
||||
} from "@/api";
|
||||
import { Segment } from "@/lib/path";
|
||||
import { EventNoteStoreUpdate } from "@/types";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { defineStore } from "pinia";
|
||||
import { computed, ref } from "vue";
|
||||
import { ref } from "vue";
|
||||
|
||||
export interface Note {
|
||||
readonly id: string;
|
||||
readonly text: string;
|
||||
readonly children: readonly string[];
|
||||
}
|
||||
|
||||
interface MutNote {
|
||||
readonly id: string;
|
||||
text: string;
|
||||
children: string[];
|
||||
readonly parents: ReadonlySet<string>;
|
||||
}
|
||||
|
||||
export const useNotesStore = defineStore("notes", () => {
|
||||
const notes = ref<Map<string, MutNote>>(new Map());
|
||||
const storeId = ref<number>();
|
||||
|
||||
const parents = computed(() => {
|
||||
const result = new Map<string, Set<string>>();
|
||||
for (const note of notes.value.values()) {
|
||||
for (const childId of note.children) {
|
||||
const parents = result.get(childId) ?? new Set();
|
||||
result.set(childId, parents);
|
||||
parents.add(note.id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
function getNote(id: string): Note | undefined {
|
||||
const note = notes.value.get(id);
|
||||
if (note === undefined) return;
|
||||
|
||||
return {
|
||||
id,
|
||||
text: note.text,
|
||||
children: note.children.slice(),
|
||||
};
|
||||
async function initialize(): Promise<void> {
|
||||
await listen("notes_store_update", (ev) => {
|
||||
const data = EventNoteStoreUpdate.parse(ev.payload);
|
||||
if (storeId.value === undefined || storeId.value < data.storeId) storeId.value = data.storeId;
|
||||
});
|
||||
}
|
||||
|
||||
function getParents(id: string): ReadonlySet<string> {
|
||||
return parents.value.get(id) ?? new Set();
|
||||
function dependOnStoreId(): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
storeId.value;
|
||||
}
|
||||
|
||||
function createNote(text: string): Note {
|
||||
const id = crypto.randomUUID();
|
||||
notes.value.set(id, { id, text, children: [] });
|
||||
return { id, text, children: [] };
|
||||
async function getNote(id: string): Promise<Note | null> {
|
||||
dependOnStoreId();
|
||||
return apiNoteGet(id);
|
||||
}
|
||||
|
||||
function deleteNote(id: string): void {
|
||||
for (const note of notes.value.values()) {
|
||||
note.children = note.children.filter((it) => it !== id);
|
||||
}
|
||||
|
||||
notes.value.delete(id);
|
||||
async function createNote(text: string): Promise<Note> {
|
||||
dependOnStoreId();
|
||||
return apiNoteCreate(text);
|
||||
}
|
||||
|
||||
function setText(id: string, text: string): void {
|
||||
const note = notes.value.get(id);
|
||||
if (note === undefined) return;
|
||||
|
||||
note.text = text;
|
||||
async function deleteNote(id: string): Promise<void> {
|
||||
return apiNoteDelete(id);
|
||||
}
|
||||
|
||||
function setChildren(id: string, children: string[]): void {
|
||||
const note = notes.value.get(id);
|
||||
if (note === undefined) return;
|
||||
|
||||
note.children = children.slice();
|
||||
async function setText(id: string, text: string): Promise<void> {
|
||||
return apiNoteTextSet(id, text);
|
||||
}
|
||||
|
||||
function addChild(id: string, childId: string, index: number): void {
|
||||
const note = notes.value.get(id);
|
||||
if (note === undefined) return;
|
||||
|
||||
if (index < 0) index = note.children.length + 1 + index;
|
||||
note.children.splice(index, 0, childId);
|
||||
async function setChildren(id: string, children: string[]): Promise<void> {
|
||||
return apiNoteChildrenSet(id, children);
|
||||
}
|
||||
|
||||
function removeChild(id: string, segment: Segment): void {
|
||||
const note = notes.value.get(id);
|
||||
if (note === undefined) return;
|
||||
|
||||
let index = note.children.indexOf(segment.id);
|
||||
for (let i = 0; i < segment.iteration; i++) {
|
||||
index = note.children.indexOf(segment.id, index + 1);
|
||||
}
|
||||
|
||||
if (index < 0) return;
|
||||
note.children.splice(index, 1);
|
||||
async function addChild(id: string, childId: string, childPosition: number): Promise<void> {
|
||||
return apiNoteChildAdd(id, childId, childPosition);
|
||||
}
|
||||
|
||||
function moveChild(fromId: string, segment: Segment, toId: string, toIndex: number): void {
|
||||
const from = notes.value.get(fromId);
|
||||
if (!from) return;
|
||||
|
||||
const to = notes.value.get(toId);
|
||||
if (!to) return;
|
||||
|
||||
// Find child index
|
||||
let fromIndex = from.children.indexOf(segment.id);
|
||||
for (let i = 0; i < segment.iteration; i++) {
|
||||
fromIndex = from.children.indexOf(segment.id, fromIndex + 1);
|
||||
}
|
||||
if (fromIndex < 0) return;
|
||||
|
||||
// Fix off-by-one caused by the deletion
|
||||
if (fromId === toId && fromIndex < toIndex) toIndex--;
|
||||
|
||||
from.children.splice(fromIndex, 1);
|
||||
to.children.splice(toIndex, 0, segment.id);
|
||||
async function removeChild(id: string, segment: Segment): Promise<void> {
|
||||
return apiNoteChildRemove(id, segment.id, segment.iteration);
|
||||
}
|
||||
|
||||
function clearNotes(): void {
|
||||
notes.value.clear();
|
||||
async function moveChild(
|
||||
fromId: string,
|
||||
segment: Segment,
|
||||
toId: string,
|
||||
toPosition: number,
|
||||
): Promise<void> {
|
||||
return apiNoteChildMove(segment.id, fromId, segment.iteration, toId, toPosition);
|
||||
}
|
||||
|
||||
async function clearNotes(): Promise<void> {
|
||||
return apiNotesClear();
|
||||
}
|
||||
|
||||
return {
|
||||
storeId,
|
||||
initialize,
|
||||
getNote,
|
||||
getParents,
|
||||
createNote,
|
||||
deleteNote,
|
||||
setText,
|
||||
|
|
|
|||
21
gdn-app/src/types.ts
Normal file
21
gdn-app/src/types.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export type NodeId = z.infer<typeof NodeId>;
|
||||
export const NodeId = z.string().startsWith("n").length(17);
|
||||
|
||||
export type Note = z.infer<typeof Note>;
|
||||
export const Note = z.object({
|
||||
id: NodeId,
|
||||
text: z.string(),
|
||||
children: z.array(NodeId),
|
||||
parents: z.array(NodeId).transform((it) => new Set(it)),
|
||||
});
|
||||
|
||||
////////////
|
||||
// Events //
|
||||
////////////
|
||||
|
||||
export type EventNoteStoreUpdate = z.infer<typeof EventNoteStoreUpdate>;
|
||||
export const EventNoteStoreUpdate = z.object({
|
||||
storeId: z.number(),
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue