Move notes store to rust
This commit is contained in:
parent
849400cf35
commit
75f3a84e5f
12 changed files with 617 additions and 138 deletions
|
|
@ -16,9 +16,11 @@
|
||||||
"@tailwindcss/vite": "^4.0.6",
|
"@tailwindcss/vite": "^4.0.6",
|
||||||
"@tauri-apps/api": "^2.2.0",
|
"@tauri-apps/api": "^2.2.0",
|
||||||
"@tauri-apps/plugin-opener": "^2.2.5",
|
"@tauri-apps/plugin-opener": "^2.2.5",
|
||||||
|
"@vueuse/core": "^12.5.0",
|
||||||
"pinia": "^2.3.1",
|
"pinia": "^2.3.1",
|
||||||
"tailwindcss": "^4.0.6",
|
"tailwindcss": "^4.0.6",
|
||||||
"vue": "^3.5.13"
|
"vue": "^3.5.13",
|
||||||
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.20.0",
|
"@eslint/js": "^9.20.0",
|
||||||
|
|
|
||||||
112
gdn-app/src-tauri/src/api.rs
Normal file
112
gdn-app/src-tauri/src/api.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use tauri::{AppHandle, Emitter, State};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
ids::NoteId,
|
||||||
|
store::Store,
|
||||||
|
types::{EventNotesStoreUpdate, Note},
|
||||||
|
};
|
||||||
|
|
||||||
|
// API methods are sorted alphabetically.
|
||||||
|
|
||||||
|
fn update_if_required(store: &mut Store, app: &AppHandle) {
|
||||||
|
if let Some(store_id) = store.needs_update() {
|
||||||
|
let payload = EventNotesStoreUpdate { store_id };
|
||||||
|
app.emit("notes_store_update", payload).unwrap();
|
||||||
|
store.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn note_child_add(
|
||||||
|
id: NoteId,
|
||||||
|
child_id: NoteId,
|
||||||
|
child_position: isize,
|
||||||
|
app: AppHandle,
|
||||||
|
store: State<'_, Arc<Mutex<Store>>>,
|
||||||
|
) {
|
||||||
|
let mut guard = store.lock().unwrap();
|
||||||
|
guard.add_child_at_position(id, child_id, child_position);
|
||||||
|
update_if_required(&mut guard, &app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn note_child_move(
|
||||||
|
child_id: NoteId,
|
||||||
|
from_id: NoteId,
|
||||||
|
from_iteration: usize,
|
||||||
|
to_id: NoteId,
|
||||||
|
to_position: isize,
|
||||||
|
app: AppHandle,
|
||||||
|
store: State<'_, Arc<Mutex<Store>>>,
|
||||||
|
) {
|
||||||
|
let mut guard = store.lock().unwrap();
|
||||||
|
guard.move_child_by_id_to_position(child_id, from_id, from_iteration, to_id, to_position);
|
||||||
|
update_if_required(&mut guard, &app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn note_child_remove(
|
||||||
|
id: NoteId,
|
||||||
|
child_id: NoteId,
|
||||||
|
child_iteration: usize,
|
||||||
|
app: AppHandle,
|
||||||
|
store: State<'_, Arc<Mutex<Store>>>,
|
||||||
|
) {
|
||||||
|
let mut guard = store.lock().unwrap();
|
||||||
|
guard.remove_child_by_id(id, child_id, child_iteration);
|
||||||
|
update_if_required(&mut guard, &app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn note_children_set(
|
||||||
|
id: NoteId,
|
||||||
|
children: Vec<NoteId>,
|
||||||
|
app: AppHandle,
|
||||||
|
store: State<'_, Arc<Mutex<Store>>>,
|
||||||
|
) {
|
||||||
|
let mut guard = store.lock().unwrap();
|
||||||
|
guard.set_children(id, children);
|
||||||
|
update_if_required(&mut guard, &app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn note_create(text: String, app: AppHandle, store: State<'_, Arc<Mutex<Store>>>) -> Note {
|
||||||
|
let mut guard = store.lock().unwrap();
|
||||||
|
let id = guard.create(text);
|
||||||
|
update_if_required(&mut guard, &app);
|
||||||
|
guard.get(id).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn note_delete(id: NoteId, app: AppHandle, store: State<'_, Arc<Mutex<Store>>>) {
|
||||||
|
let mut guard = store.lock().unwrap();
|
||||||
|
guard.delete(id);
|
||||||
|
update_if_required(&mut guard, &app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn note_get(id: NoteId, store: State<'_, Arc<Mutex<Store>>>) -> Option<Note> {
|
||||||
|
let guard = store.lock().unwrap();
|
||||||
|
guard.get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn note_text_set(
|
||||||
|
id: NoteId,
|
||||||
|
text: String,
|
||||||
|
app: AppHandle,
|
||||||
|
store: State<'_, Arc<Mutex<Store>>>,
|
||||||
|
) {
|
||||||
|
let mut guard = store.lock().unwrap();
|
||||||
|
guard.set_text(id, text);
|
||||||
|
update_if_required(&mut guard, &app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn notes_clear(app: AppHandle, store: State<'_, Arc<Mutex<Store>>>) {
|
||||||
|
let mut guard = store.lock().unwrap();
|
||||||
|
guard.clear();
|
||||||
|
update_if_required(&mut guard, &app);
|
||||||
|
}
|
||||||
|
|
@ -1,16 +1,28 @@
|
||||||
mod ids;
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
use store::Store;
|
||||||
#[tauri::command]
|
|
||||||
fn greet(name: &str) -> String {
|
mod api;
|
||||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
mod ids;
|
||||||
}
|
pub mod store;
|
||||||
|
mod types;
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.invoke_handler(tauri::generate_handler![greet])
|
.manage(Arc::new(Mutex::new(Store::new())))
|
||||||
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
api::note_child_add,
|
||||||
|
api::note_child_move,
|
||||||
|
api::note_child_remove,
|
||||||
|
api::note_children_set,
|
||||||
|
api::note_create,
|
||||||
|
api::note_delete,
|
||||||
|
api::note_get,
|
||||||
|
api::note_text_set,
|
||||||
|
api::notes_clear,
|
||||||
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
238
gdn-app/src-tauri/src/store.rs
Normal file
238
gdn-app/src-tauri/src/store.rs
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use crate::{ids::NoteId, types::Note};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NoteInfo {
|
||||||
|
pub text: String,
|
||||||
|
pub children: Vec<NoteId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A note store for testing.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Store {
|
||||||
|
last_id: u64,
|
||||||
|
curr_id: u64,
|
||||||
|
notes: HashMap<NoteId, NoteInfo>,
|
||||||
|
parents: HashMap<NoteId, HashMap<NoteId, usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Store {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn needs_update(&self) -> Option<u64> {
|
||||||
|
if self.last_id != self.curr_id {
|
||||||
|
Some(self.curr_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self) {
|
||||||
|
self.last_id = self.curr_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, id: NoteId) -> Option<Note> {
|
||||||
|
let info = self.notes.get(&id)?;
|
||||||
|
|
||||||
|
let parents = self
|
||||||
|
.parents
|
||||||
|
.get(&id)
|
||||||
|
.map(|ps| ps.keys().copied().collect::<HashSet<_>>())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
Some(Note {
|
||||||
|
id,
|
||||||
|
text: info.text.clone(),
|
||||||
|
children: info.children.clone(),
|
||||||
|
parents,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick(&mut self) {
|
||||||
|
self.curr_id += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_parents(&mut self) {
|
||||||
|
self.parents.clear();
|
||||||
|
for (id, info) in &self.notes {
|
||||||
|
for child in &info.children {
|
||||||
|
*self
|
||||||
|
.parents
|
||||||
|
.entry(*child)
|
||||||
|
.or_default()
|
||||||
|
.entry(*id)
|
||||||
|
.or_default() += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(&mut self, text: String) -> NoteId {
|
||||||
|
let id = NoteId::new();
|
||||||
|
let info = NoteInfo {
|
||||||
|
text,
|
||||||
|
children: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
self.notes.insert(id, info);
|
||||||
|
self.update_parents();
|
||||||
|
self.tick();
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(&mut self, id: NoteId) -> Option<NoteInfo> {
|
||||||
|
let info = self.notes.remove(&id)?;
|
||||||
|
self.update_parents();
|
||||||
|
self.tick();
|
||||||
|
Some(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_text(&mut self, id: NoteId, text: String) -> Option<()> {
|
||||||
|
let note = self.notes.get_mut(&id)?;
|
||||||
|
if note.text == text {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
note.text = text;
|
||||||
|
self.tick();
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_children(&mut self, id: NoteId, children: Vec<NoteId>) -> Option<()> {
|
||||||
|
let note = self.notes.get_mut(&id)?;
|
||||||
|
if note.children == children {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
note.children = children;
|
||||||
|
self.tick();
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the index of a child based on its id and iteration.
|
||||||
|
///
|
||||||
|
/// The index returned is in the range `[0, note.children.len())`.
|
||||||
|
///
|
||||||
|
/// Iteration 0 refers to the first occurrence of the id, iteration 1 to the
|
||||||
|
/// second, and so on. Returns [`None`] if no such iteration was found.
|
||||||
|
fn resolve_child_iteration(
|
||||||
|
children: &[NoteId],
|
||||||
|
child_id: NoteId,
|
||||||
|
child_iteration: usize,
|
||||||
|
) -> Option<usize> {
|
||||||
|
children
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, it)| **it == child_id)
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.nth(child_iteration)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the index of a child based on its position.
|
||||||
|
///
|
||||||
|
/// The index returned is in the range `[0, note.children.len()]`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// A small example for a note with children `[a, b, c]`:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// Child [ a, b, c] _
|
||||||
|
/// Position 0 1 2 3
|
||||||
|
/// Position -4 -3 -2 -1
|
||||||
|
/// ```
|
||||||
|
fn resolve_child_position(children: &[NoteId], child_position: isize) -> usize {
|
||||||
|
if child_position > 0 {
|
||||||
|
let child_position = child_position as usize;
|
||||||
|
child_position.min(children.len())
|
||||||
|
} else {
|
||||||
|
let child_position = (-child_position - 1) as usize;
|
||||||
|
children.len().saturating_sub(child_position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a child at the specified position.
|
||||||
|
///
|
||||||
|
/// Returns `Some(())` if the operation was successful.
|
||||||
|
pub fn add_child_at_position(
|
||||||
|
&mut self,
|
||||||
|
id: NoteId,
|
||||||
|
child_id: NoteId,
|
||||||
|
child_position: isize,
|
||||||
|
) -> Option<()> {
|
||||||
|
let note = self.notes.get_mut(&id)?;
|
||||||
|
let index = Self::resolve_child_position(¬e.children, child_position);
|
||||||
|
note.children.insert(index, child_id);
|
||||||
|
|
||||||
|
self.update_parents();
|
||||||
|
self.tick();
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the specified iteration of a child.
|
||||||
|
///
|
||||||
|
/// Returns `Some(())` if the operation was successful.
|
||||||
|
pub fn remove_child_by_id(
|
||||||
|
&mut self,
|
||||||
|
id: NoteId,
|
||||||
|
child_id: NoteId,
|
||||||
|
child_iteration: usize,
|
||||||
|
) -> Option<()> {
|
||||||
|
let note = self.notes.get_mut(&id)?;
|
||||||
|
let index = Self::resolve_child_iteration(¬e.children, child_id, child_iteration)?;
|
||||||
|
note.children.remove(index);
|
||||||
|
|
||||||
|
self.update_parents();
|
||||||
|
self.tick();
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A combination of [`Self::add_child_at_position`] and
|
||||||
|
/// [`Self::remove_child_by_id`].
|
||||||
|
///
|
||||||
|
/// Returns `Some(())` if the operation was successful.
|
||||||
|
pub fn move_child_by_id_to_position(
|
||||||
|
&mut self,
|
||||||
|
child_id: NoteId,
|
||||||
|
from_id: NoteId,
|
||||||
|
from_iteration: usize,
|
||||||
|
to_id: NoteId,
|
||||||
|
to_position: isize,
|
||||||
|
) -> Option<()> {
|
||||||
|
let from = self.get(from_id)?;
|
||||||
|
let to = self.get(to_id)?;
|
||||||
|
|
||||||
|
let from_idx = Self::resolve_child_iteration(&from.children, child_id, from_iteration)?;
|
||||||
|
let mut to_idx = Self::resolve_child_position(&to.children, to_position);
|
||||||
|
|
||||||
|
if from_id == to_id && from_idx < to_idx {
|
||||||
|
to_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let removed_id = self
|
||||||
|
.notes
|
||||||
|
.get_mut(&from_id)
|
||||||
|
.unwrap()
|
||||||
|
.children
|
||||||
|
.remove(from_idx);
|
||||||
|
assert!(removed_id == child_id);
|
||||||
|
|
||||||
|
self.notes
|
||||||
|
.get_mut(&to_id)
|
||||||
|
.unwrap()
|
||||||
|
.children
|
||||||
|
.insert(to_idx, child_id);
|
||||||
|
|
||||||
|
self.update_parents();
|
||||||
|
self.tick();
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.notes.clear();
|
||||||
|
|
||||||
|
self.update_parents();
|
||||||
|
self.tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
24
gdn-app/src-tauri/src/types.rs
Normal file
24
gdn-app/src-tauri/src/types.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::ids::NoteId;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Note {
|
||||||
|
pub id: NoteId,
|
||||||
|
pub text: String,
|
||||||
|
pub children: Vec<NoteId>,
|
||||||
|
pub parents: HashSet<NoteId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////
|
||||||
|
// Events //
|
||||||
|
////////////
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct EventNotesStoreUpdate {
|
||||||
|
pub store_id: u64,
|
||||||
|
}
|
||||||
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 notes = useNotesStore();
|
||||||
const ui = useUiStore();
|
const ui = useUiStore();
|
||||||
|
|
||||||
function mkNote(text: string, ...children: string[]): Note {
|
async function mkNote(text: string, ...children: string[]): Promise<Note> {
|
||||||
const note = notes.createNote(text);
|
const note = await notes.createNote(text);
|
||||||
for (const child of children) notes.addChild(note.id, child, -1);
|
for (const child of children) await notes.addChild(note.id, child, -1);
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSomeNotes(): void {
|
async function createSomeNotes(): Promise<void> {
|
||||||
notes.clearNotes();
|
await notes.clearNotes();
|
||||||
|
|
||||||
const n2n1 = mkNote("n2n1");
|
const n2n1 = await mkNote("n2n1");
|
||||||
const n2n2 = mkNote("n2n2");
|
const n2n2 = await mkNote("n2n2");
|
||||||
const n2n3 = mkNote("n2n3");
|
const n2n3 = await mkNote("n2n3");
|
||||||
|
|
||||||
const n1 = mkNote("n1");
|
const n1 = await mkNote("n1");
|
||||||
const n2 = mkNote("n2", n2n1.id, n2n2.id, n2n3.id);
|
const n2 = await mkNote("n2", n2n1.id, n2n2.id, n2n3.id);
|
||||||
const n3 = mkNote("n3", n2n1.id);
|
const n3 = await mkNote("n3", n2n1.id);
|
||||||
const n4 = mkNote("n4");
|
const n4 = await mkNote("n4");
|
||||||
const n5 = mkNote("n5", "NaN (not a note)");
|
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);
|
ui.pushAnchorId(root.id);
|
||||||
|
|
||||||
// Shuffle children of root
|
// Shuffle children of root
|
||||||
notes.setChildren(
|
const rootChildren = (await notes.getNote(root.id))?.children ?? [];
|
||||||
|
await notes.setChildren(
|
||||||
root.id,
|
root.id,
|
||||||
root.children
|
rootChildren
|
||||||
.map((it) => ({ it, rand: Math.random() }))
|
.map((it) => ({ it, rand: Math.random() }))
|
||||||
.sort((a, b) => a.rand - b.rand)
|
.sort((a, b) => a.rand - b.rand)
|
||||||
.map(({ it }) => it),
|
.map(({ it }) => it),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
createSomeNotes();
|
await createSomeNotes();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { computed, ref, watchEffect } from "vue";
|
||||||
import CNoteButton from "./CNoteButton.vue";
|
import CNoteButton from "./CNoteButton.vue";
|
||||||
import CNoteChildEditor from "./CNoteChildEditor.vue";
|
import CNoteChildEditor from "./CNoteChildEditor.vue";
|
||||||
import CNoteEditor from "./CNoteEditor.vue";
|
import CNoteEditor from "./CNoteEditor.vue";
|
||||||
|
import { computedAsync } from "@vueuse/core";
|
||||||
|
|
||||||
const notes = useNotesStore();
|
const notes = useNotesStore();
|
||||||
const ui = useUiStore();
|
const ui = useUiStore();
|
||||||
|
|
@ -39,15 +40,21 @@ const {
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const id = computed(() => segment.id);
|
const id = computed(() => segment.id);
|
||||||
const note = computed(() => notes.getNote(id.value));
|
const note = computedAsync(async () => await notes.getNote(id.value));
|
||||||
|
|
||||||
const parents = computed(() => {
|
const parents = computedAsync(async () => {
|
||||||
let parents = notes.getParents(id.value);
|
const result = [];
|
||||||
if (parentId) parents = parents.difference(new Set([parentId]));
|
for (const parent of note.value?.parents ?? new Set()) {
|
||||||
return [...parents].sort().map((id) => ({ id, text: notes.getNote(id)?.text }));
|
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(() => {
|
const children = computed(() => {
|
||||||
if (!note.value) return [];
|
if (!note.value) return [];
|
||||||
const seen = new Map<string, number>();
|
const seen = new Map<string, number>();
|
||||||
|
|
@ -84,13 +91,13 @@ function onClick(): void {
|
||||||
ui.toggleOpen(path);
|
ui.toggleOpen(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDeleteButtonClick(): void {
|
async function onDeleteButtonClick(): Promise<void> {
|
||||||
notes.deleteNote(segment.id);
|
await notes.deleteNote(segment.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUnlinkButtonClick(): void {
|
async function onUnlinkButtonClick(): Promise<void> {
|
||||||
if (parentId === undefined) return;
|
if (parentId === undefined) return;
|
||||||
notes.removeChild(parentId, segment);
|
await notes.removeChild(parentId, segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEditButtonClick(): void {
|
function onEditButtonClick(): void {
|
||||||
|
|
@ -127,9 +134,9 @@ function onEditEditorClose(): void {
|
||||||
ui.focus();
|
ui.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEditEditorFinish(text: string): void {
|
async function onEditEditorFinish(text: string): Promise<void> {
|
||||||
if (!note.value) return;
|
if (!note.value) return;
|
||||||
notes.setText(segment.id, text);
|
await notes.setText(segment.id, text);
|
||||||
onEditEditorClose();
|
onEditEditorClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,36 +144,36 @@ function onInsertEditorClose(): void {
|
||||||
ui.focus();
|
ui.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onInsertEditorFinish(text: string): void {
|
async function onInsertEditorFinish(text: string): Promise<void> {
|
||||||
if (!note.value) return;
|
if (!note.value) return;
|
||||||
|
|
||||||
if (insertIndex.value !== undefined) {
|
if (insertIndex.value !== undefined) {
|
||||||
const child = notes.createNote(text);
|
const child = await notes.createNote(text);
|
||||||
notes.addChild(segment.id, child.id, insertIndex.value);
|
await notes.addChild(segment.id, child.id, insertIndex.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onInsertEditorClose();
|
onInsertEditorClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onInsertEditorMove(): void {
|
async function onInsertEditorMove(): Promise<void> {
|
||||||
if (!ui.pinned) return;
|
if (!ui.pinned) return;
|
||||||
if (insertIndex.value === undefined) return;
|
if (insertIndex.value === undefined) return;
|
||||||
|
|
||||||
if (ui.pinned.parentId) {
|
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 {
|
} else {
|
||||||
notes.addChild(segment.id, ui.pinned.segment.id, insertIndex.value);
|
await notes.addChild(segment.id, ui.pinned.segment.id, insertIndex.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onInsertEditorClose();
|
onInsertEditorClose();
|
||||||
ui.unsetPinned();
|
ui.unsetPinned();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onInsertEditorCopy(): void {
|
async function onInsertEditorCopy(): Promise<void> {
|
||||||
if (!ui.pinned) return;
|
if (!ui.pinned) return;
|
||||||
if (insertIndex.value === undefined) 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();
|
onInsertEditorClose();
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +182,7 @@ function onInsertEditorCopy(): void {
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<!-- Parents -->
|
<!-- 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">
|
<div v-for="parent of parents" :key="parent.id" class="pl-6 text-xs text-neutral-400">
|
||||||
<RiCornerUpRightLine size="12px" class="inline" /> {{ parent.text }}
|
<RiCornerUpRightLine size="12px" class="inline" /> {{ parent.text }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import App from "./App.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 { Segment } from "@/lib/path";
|
||||||
|
import { EventNoteStoreUpdate } from "@/types";
|
||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { computed, ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
export interface Note {
|
export interface Note {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly text: string;
|
readonly text: string;
|
||||||
readonly children: readonly string[];
|
readonly children: readonly string[];
|
||||||
}
|
readonly parents: ReadonlySet<string>;
|
||||||
|
|
||||||
interface MutNote {
|
|
||||||
readonly id: string;
|
|
||||||
text: string;
|
|
||||||
children: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotesStore = defineStore("notes", () => {
|
export const useNotesStore = defineStore("notes", () => {
|
||||||
const notes = ref<Map<string, MutNote>>(new Map());
|
const storeId = ref<number>();
|
||||||
|
|
||||||
const parents = computed(() => {
|
async function initialize(): Promise<void> {
|
||||||
const result = new Map<string, Set<string>>();
|
await listen("notes_store_update", (ev) => {
|
||||||
for (const note of notes.value.values()) {
|
const data = EventNoteStoreUpdate.parse(ev.payload);
|
||||||
for (const childId of note.children) {
|
if (storeId.value === undefined || storeId.value < data.storeId) storeId.value = data.storeId;
|
||||||
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(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getParents(id: string): ReadonlySet<string> {
|
function dependOnStoreId(): void {
|
||||||
return parents.value.get(id) ?? new Set();
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
|
storeId.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNote(text: string): Note {
|
async function getNote(id: string): Promise<Note | null> {
|
||||||
const id = crypto.randomUUID();
|
dependOnStoreId();
|
||||||
notes.value.set(id, { id, text, children: [] });
|
return apiNoteGet(id);
|
||||||
return { id, text, children: [] };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteNote(id: string): void {
|
async function createNote(text: string): Promise<Note> {
|
||||||
for (const note of notes.value.values()) {
|
dependOnStoreId();
|
||||||
note.children = note.children.filter((it) => it !== id);
|
return apiNoteCreate(text);
|
||||||
}
|
|
||||||
|
|
||||||
notes.value.delete(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setText(id: string, text: string): void {
|
async function deleteNote(id: string): Promise<void> {
|
||||||
const note = notes.value.get(id);
|
return apiNoteDelete(id);
|
||||||
if (note === undefined) return;
|
|
||||||
|
|
||||||
note.text = text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setChildren(id: string, children: string[]): void {
|
async function setText(id: string, text: string): Promise<void> {
|
||||||
const note = notes.value.get(id);
|
return apiNoteTextSet(id, text);
|
||||||
if (note === undefined) return;
|
|
||||||
|
|
||||||
note.children = children.slice();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addChild(id: string, childId: string, index: number): void {
|
async function setChildren(id: string, children: string[]): Promise<void> {
|
||||||
const note = notes.value.get(id);
|
return apiNoteChildrenSet(id, children);
|
||||||
if (note === undefined) return;
|
|
||||||
|
|
||||||
if (index < 0) index = note.children.length + 1 + index;
|
|
||||||
note.children.splice(index, 0, childId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeChild(id: string, segment: Segment): void {
|
async function addChild(id: string, childId: string, childPosition: number): Promise<void> {
|
||||||
const note = notes.value.get(id);
|
return apiNoteChildAdd(id, childId, childPosition);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveChild(fromId: string, segment: Segment, toId: string, toIndex: number): void {
|
async function removeChild(id: string, segment: Segment): Promise<void> {
|
||||||
const from = notes.value.get(fromId);
|
return apiNoteChildRemove(id, segment.id, segment.iteration);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearNotes(): void {
|
async function moveChild(
|
||||||
notes.value.clear();
|
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 {
|
return {
|
||||||
|
storeId,
|
||||||
|
initialize,
|
||||||
getNote,
|
getNote,
|
||||||
getParents,
|
|
||||||
createNote,
|
createNote,
|
||||||
deleteNote,
|
deleteNote,
|
||||||
setText,
|
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(),
|
||||||
|
});
|
||||||
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
|
|
@ -32,6 +32,9 @@ importers:
|
||||||
'@tauri-apps/plugin-opener':
|
'@tauri-apps/plugin-opener':
|
||||||
specifier: ^2.2.5
|
specifier: ^2.2.5
|
||||||
version: 2.2.5
|
version: 2.2.5
|
||||||
|
'@vueuse/core':
|
||||||
|
specifier: ^12.5.0
|
||||||
|
version: 12.5.0(typescript@5.7.3)
|
||||||
pinia:
|
pinia:
|
||||||
specifier: ^2.3.1
|
specifier: ^2.3.1
|
||||||
version: 2.3.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
|
version: 2.3.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
|
||||||
|
|
@ -41,6 +44,9 @@ importers:
|
||||||
vue:
|
vue:
|
||||||
specifier: ^3.5.13
|
specifier: ^3.5.13
|
||||||
version: 3.5.13(typescript@5.7.3)
|
version: 3.5.13(typescript@5.7.3)
|
||||||
|
zod:
|
||||||
|
specifier: ^3.24.2
|
||||||
|
version: 3.24.2
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.20.0
|
specifier: ^9.20.0
|
||||||
|
|
@ -754,6 +760,9 @@ packages:
|
||||||
'@types/node@22.13.1':
|
'@types/node@22.13.1':
|
||||||
resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==}
|
resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==}
|
||||||
|
|
||||||
|
'@types/web-bluetooth@0.0.20':
|
||||||
|
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.24.0':
|
'@typescript-eslint/eslint-plugin@8.24.0':
|
||||||
resolution: {integrity: sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==}
|
resolution: {integrity: sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
@ -887,6 +896,15 @@ packages:
|
||||||
'@vue/shared@3.5.13':
|
'@vue/shared@3.5.13':
|
||||||
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
|
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
|
||||||
|
|
||||||
|
'@vueuse/core@12.5.0':
|
||||||
|
resolution: {integrity: sha512-GVyH1iYqNANwcahAx8JBm6awaNgvR/SwZ1fjr10b8l1HIgDp82ngNbfzJUgOgWEoxjL+URAggnlilAEXwCOZtg==}
|
||||||
|
|
||||||
|
'@vueuse/metadata@12.5.0':
|
||||||
|
resolution: {integrity: sha512-Ui7Lo2a7AxrMAXRF+fAp9QsXuwTeeZ8fIB9wsLHqzq9MQk+2gMYE2IGJW48VMJ8ecvCB3z3GsGLKLbSasQ5Qlg==}
|
||||||
|
|
||||||
|
'@vueuse/shared@12.5.0':
|
||||||
|
resolution: {integrity: sha512-vMpcL1lStUU6O+kdj6YdHDixh0odjPAUM15uJ9f7MY781jcYkIwFA4iv2EfoIPO6vBmvutI1HxxAwmf0cx5ISQ==}
|
||||||
|
|
||||||
acorn-jsx@5.3.2:
|
acorn-jsx@5.3.2:
|
||||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -1849,6 +1867,9 @@ packages:
|
||||||
resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
|
resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
zod@3.24.2:
|
||||||
|
resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==}
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@ampproject/remapping@2.3.0':
|
'@ampproject/remapping@2.3.0':
|
||||||
|
|
@ -2426,6 +2447,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.20.0
|
undici-types: 6.20.0
|
||||||
|
|
||||||
|
'@types/web-bluetooth@0.0.20': {}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3)':
|
'@typescript-eslint/eslint-plugin@8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.0(jiti@2.4.2))(typescript@5.7.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.1
|
'@eslint-community/regexpp': 4.12.1
|
||||||
|
|
@ -2650,6 +2673,23 @@ snapshots:
|
||||||
|
|
||||||
'@vue/shared@3.5.13': {}
|
'@vue/shared@3.5.13': {}
|
||||||
|
|
||||||
|
'@vueuse/core@12.5.0(typescript@5.7.3)':
|
||||||
|
dependencies:
|
||||||
|
'@types/web-bluetooth': 0.0.20
|
||||||
|
'@vueuse/metadata': 12.5.0
|
||||||
|
'@vueuse/shared': 12.5.0(typescript@5.7.3)
|
||||||
|
vue: 3.5.13(typescript@5.7.3)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- typescript
|
||||||
|
|
||||||
|
'@vueuse/metadata@12.5.0': {}
|
||||||
|
|
||||||
|
'@vueuse/shared@12.5.0(typescript@5.7.3)':
|
||||||
|
dependencies:
|
||||||
|
vue: 3.5.13(typescript@5.7.3)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- typescript
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.14.0):
|
acorn-jsx@5.3.2(acorn@8.14.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.14.0
|
acorn: 8.14.0
|
||||||
|
|
@ -3489,3 +3529,5 @@ snapshots:
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
yoctocolors@2.1.1: {}
|
yoctocolors@2.1.1: {}
|
||||||
|
|
||||||
|
zod@3.24.2: {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue