diff --git a/package.json b/package.json index f925e98a0..009b56e00 100644 --- a/package.json +++ b/package.json @@ -85,5 +85,8 @@ "xterm-addon-ligatures": "^0.6.0", "xterm-addon-unicode11": "^0.5.0", "xterm-addon-webgl": "^0.14.0" + }, + "dependencies": { + "svelte-loadable-store": "^1.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90bc2791d..c7e4008cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,10 @@ lockfileVersion: '6.0' +dependencies: + svelte-loadable-store: + specifier: ^1.0.0 + version: 1.0.0 + devDependencies: '@codemirror/autocomplete': specifier: ^6.4.2 @@ -3649,6 +3654,12 @@ packages: svelte: 3.55.1 dev: true + /svelte-loadable-store@1.0.0: + resolution: {integrity: sha512-HtCaU1k+tyjf0uDrMlNXzyXdOBZdsQJ1oJ+UDrEQJDbcmEr9W/FkvIIagDbAnriylgI8GUiQtFibI5UyR3rbDg==} + dependencies: + svelte: 3.59.1 + dev: false + /svelte-preprocess@5.0.1(postcss-load-config@4.0.1)(postcss@8.4.21)(svelte@3.55.1)(typescript@4.9.5): resolution: {integrity: sha512-0HXyhCoc9rsW4zGOgtInylC6qj259E1hpFnJMJWTf+aIfeqh4O/QHT31KT2hvPEqQfdjmqBR/kO2JDkkciBLrQ==} engines: {node: '>= 14.10.0'} @@ -3718,6 +3729,11 @@ packages: engines: {node: '>= 8'} dev: true + /svelte@3.59.1: + resolution: {integrity: sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==} + engines: {node: '>= 8'} + dev: false + /symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: true diff --git a/src-tauri/src/events.rs b/src-tauri/src/events.rs index 9e42bdb81..e0328895b 100644 --- a/src-tauri/src/events.rs +++ b/src-tauri/src/events.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use tauri::Manager; -use crate::{deltas, sessions}; +use crate::{bookmarks, deltas, sessions}; #[derive(Clone)] pub struct Sender { @@ -65,6 +65,13 @@ impl Event { } } + pub fn bookmark(project_id: &str, bookmark: &bookmarks::Bookmark) -> Self { + Event { + name: format!("project://{}/bookmarks", project_id), + payload: serde_json::to_value(bookmark).unwrap(), + } + } + pub fn deltas( project_id: &str, session_id: &str, diff --git a/src-tauri/src/watcher/handlers/mod.rs b/src-tauri/src/watcher/handlers/mod.rs index 160e07655..0ea661303 100644 --- a/src-tauri/src/watcher/handlers/mod.rs +++ b/src-tauri/src/watcher/handlers/mod.rs @@ -178,10 +178,16 @@ impl<'handler> Handler<'handler> { .context("failed to send deltas event")?; Ok(delta_events) } - events::Event::Bookmark(bookmark) => self - .index_handler - .index_bookmark(&bookmark) - .context("failed to index bookmark"), + events::Event::Bookmark(bookmark) => { + let bookmarks_events = self + .index_handler + .index_bookmark(&bookmark) + .context("failed to index bookmark")?; + self.events_sender + .send(app_events::Event::bookmark(&self.project_id, &bookmark)) + .context("failed to send bookmark event")?; + Ok(bookmarks_events) + } } } } diff --git a/src/lib/api/ipc/bookmarks.ts b/src/lib/api/ipc/bookmarks.ts index 881932985..c235a4157 100644 --- a/src/lib/api/ipc/bookmarks.ts +++ b/src/lib/api/ipc/bookmarks.ts @@ -1,4 +1,4 @@ -import { invoke } from '$lib/ipc'; +import { invoke, listen } from '$lib/ipc'; export type Bookmark = { projectId: string; @@ -21,3 +21,17 @@ export const list = (params: { end: number; }; }) => invoke('list_bookmarks', params); + +export const subscribe = ( + params: { projectId: string; range?: { start: number; end: number } }, + callback: (bookmark: Bookmark) => Promise | void +) => + listen(`project://${params.projectId}/bookmarks`, (event) => { + if ( + params.range && + (event.payload.timestampMs < params.range.start || + event.payload.timestampMs >= params.range.end) + ) + return; + callback({ ...params, ...event.payload }); + }); diff --git a/src/lib/icons/IconBookmarkFilled.svelte b/src/lib/icons/IconBookmarkFilled.svelte new file mode 100644 index 000000000..22884b5d8 --- /dev/null +++ b/src/lib/icons/IconBookmarkFilled.svelte @@ -0,0 +1,22 @@ + + + + + diff --git a/src/lib/icons/index.ts b/src/lib/icons/index.ts index 3586ee44b..c492f49d2 100644 --- a/src/lib/icons/index.ts +++ b/src/lib/icons/index.ts @@ -25,3 +25,4 @@ export { default as IconArrowRight } from './IconArrowRight.svelte'; export { default as IconBookmark } from './IconBookmark.svelte'; export { default as IconFolder } from './IconFolder.svelte'; export { default as IconEmail } from './IconEmail.svelte'; +export { default as IconBookmarkFilled } from './IconBookmarkFilled.svelte'; diff --git a/src/lib/stores/bookmarks.ts b/src/lib/stores/bookmarks.ts index 68816f2ad..7cb5caa5a 100644 --- a/src/lib/stores/bookmarks.ts +++ b/src/lib/stores/bookmarks.ts @@ -1,41 +1,16 @@ -import { asyncWritable, type Loadable } from '@square/svelte-store'; -import { type Bookmark, bookmarks } from '$lib/api'; +import { writable, type Loadable } from 'svelte-loadable-store'; +import { bookmarks, type Bookmark } from '$lib/api'; +import type { Readable } from 'svelte/store'; -export type Store = Loadable & { - create: (params?: { note?: string; timestampMs?: number }) => Promise; -}; +const stores: Record>> = {}; -const stores: Record = {}; +export default (params: { projectId: string }) => { + if (params.projectId in stores) return stores[params.projectId]; -export default (params: { projectId: string }): Store => { - const { projectId } = params; - if (projectId in stores) { - return stores[projectId]; - } - const store = asyncWritable<[], Bookmark[]>( - [], - () => bookmarks.list(params), - async (newValue, _parents, oldValue) => { - const changedBookmarks = newValue.filter((bookmark) => { - const oldBookmark = oldValue?.find((b) => b.timestampMs === bookmark.timestampMs); - if (!oldBookmark) return true; - return oldBookmark !== bookmark; - }); - await Promise.all(changedBookmarks.map((bookmark) => bookmarks.upsert(bookmark))); - return newValue; - } + const { subscribe } = writable(bookmarks.list(params), (set) => + bookmarks.subscribe(params, () => bookmarks.list(params).then(set)) ); - return { - ...store, - create: async ({ timestampMs, note }: { note?: string; timestampMs?: number } = {}) => { - const newBookmark = { - projectId, - timestampMs: timestampMs ?? Date.now(), - note: note ?? '', - deleted: false - }; - await store.update((value) => [...value, newBookmark]); - return newBookmark; - } - }; + const store = { subscribe }; + stores[params.projectId] = store; + return store; }; diff --git a/src/routes/projects/[projectId]/player/[date]/+layout.svelte b/src/routes/projects/[projectId]/player/[date]/+layout.svelte index 8be98b29a..090fb8f34 100644 --- a/src/routes/projects/[projectId]/player/[date]/+layout.svelte +++ b/src/routes/projects/[projectId]/player/[date]/+layout.svelte @@ -1,40 +1,20 @@ - - + +
  • + {#await bookmarks.load() then} + {#if $bookmarks?.length > 0} +
    + +
    + {/if} + {/await} + + +
    + {sessionRange(session)} + {sessionDuration(session)} +
    + + + {$changedFiles.length} + {$changedFiles.length !== 1 ? 'files' : 'file'} + + + {#if isCurrent} + {#await changedFiles.load() then} +
      + {#each $changedFiles.sort(lexically) as filename} +
    • + {collapse(filename)} +
    • + {/each} +
    + {/await} + {/if} +
    +
  • diff --git a/src/routes/projects/[projectId]/player/[date]/[sessionId]/+page.svelte b/src/routes/projects/[projectId]/player/[date]/[sessionId]/+page.svelte index e0e2bc708..3b927f55c 100644 --- a/src/routes/projects/[projectId]/player/[date]/[sessionId]/+page.svelte +++ b/src/routes/projects/[projectId]/player/[date]/[sessionId]/+page.svelte @@ -241,8 +241,6 @@ {collapse($frame.filepath)} - – - {currentDelta.timestampMs} – {new Date(currentDelta.timestampMs).toLocaleString('en-US')} diff --git a/tailwind.config.cjs b/tailwind.config.cjs index b556afcb9..f4eec6f84 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -44,6 +44,9 @@ const config = { icon: { default: '#A1A1AA' }, + bookmark: { + selected: '#2563EB' + }, white: '#FFFFFF', transparent: 'transparent', gray: {