diff --git a/src/lib/deltas.ts b/src/lib/deltas.ts index 20d542710..e7a324a92 100644 --- a/src/lib/deltas.ts +++ b/src/lib/deltas.ts @@ -9,49 +9,49 @@ export type OperationInsert = { insert: [number, string] }; export type Operation = OperationDelete | OperationInsert; export namespace Operation { - export const isDelete = (operation: Operation): operation is OperationDelete => - 'delete' in operation; + export const isDelete = (operation: Operation): operation is OperationDelete => + 'delete' in operation; - export const isInsert = (operation: Operation): operation is OperationInsert => - 'insert' in operation; + export const isInsert = (operation: Operation): operation is OperationInsert => + 'insert' in operation; } export type Delta = { timestampMs: number; operations: Operation[] }; export type DeltasEvent = { - deltas: Delta[]; - filePath: string; + deltas: Delta[]; + filePath: string; }; export const list = (params: { projectId: string; sessionId: string; paths?: string[] }) => - invoke>('list_deltas', params); + invoke>('list_deltas', params); export const subscribe = ( - params: { projectId: string; sessionId: string }, - callback: (filepath: string, deltas: Delta[]) => void + params: { projectId: string; sessionId: string }, + callback: (filepath: string, deltas: Delta[]) => void ) => { - log.info(`Subscribing to deltas for ${params.projectId}, ${params.sessionId}`); - return appWindow.listen( - `project://${params.projectId}/sessions/${params.sessionId}/deltas`, - (event) => { - log.info( - `Received deltas for ${params.projectId}, ${params.sessionId}, ${event.payload.filePath}` - ); - callback(event.payload.filePath, event.payload.deltas); - } - ); + log.info(`Subscribing to deltas for ${params.projectId}, ${params.sessionId}`); + return appWindow.listen( + `project://${params.projectId}/sessions/${params.sessionId}/deltas`, + (event) => { + log.info( + `Received deltas for ${params.projectId}, ${params.sessionId}, ${event.payload.filePath}` + ); + callback(event.payload.filePath, event.payload.deltas); + } + ); }; export default async (params: { projectId: string; sessionId: string }) => { - const init = await list(params); + const init = await list(params); - const store = writable>(init); - subscribe(params, (filepath, newDeltas) => - store.update((deltas) => ({ - ...deltas, - [filepath]: newDeltas - })) - ); + const store = writable>(init); + subscribe(params, (filepath, newDeltas) => + store.update((deltas) => ({ + ...deltas, + [filepath]: newDeltas + })) + ); - return store as Readable>; + return store as Readable>; }; diff --git a/src/lib/sessions.ts b/src/lib/sessions.ts index 996f37982..5929ac984 100644 --- a/src/lib/sessions.ts +++ b/src/lib/sessions.ts @@ -4,54 +4,54 @@ import { writable, type Readable } from 'svelte/store'; import { log } from '$lib'; export type Activity = { - type: string; - timestampMs: number; - message: string; + type: string; + timestampMs: number; + message: string; }; export namespace Session { - export const within = (session: Session | undefined, timestampMs: number) => { - if (!session) return false; - const { startTimestampMs, lastTimestampMs } = session.meta; - return startTimestampMs <= timestampMs && timestampMs <= lastTimestampMs; - }; + export const within = (session: Session | undefined, timestampMs: number) => { + if (!session) return false; + const { startTimestampMs, lastTimestampMs } = session.meta; + return startTimestampMs <= timestampMs && timestampMs <= lastTimestampMs; + }; } export type Session = { - id: string; - hash?: string; - meta: { - startTimestampMs: number; - lastTimestampMs: number; - branch?: string; - commit?: string; - }; - activity: Activity[]; + id: string; + hash?: string; + meta: { + startTimestampMs: number; + lastTimestampMs: number; + branch?: string; + commit?: string; + }; + activity: Activity[]; }; export const listFiles = (params: { projectId: string; sessionId: string; paths?: string[] }) => - invoke>('list_session_files', params); + invoke>('list_session_files', params); const list = (params: { projectId: string }) => invoke('list_sessions', params); export default async (params: { projectId: string; earliestTimestampMs?: number }) => { - const store = writable([] as Session[]); - list(params).then((sessions) => { - store.set(sessions); - }); + const store = writable([] as Session[]); + list(params).then((sessions) => { + store.set(sessions); + }); - appWindow.listen(`project://${params.projectId}/sessions`, async (event) => { - log.info(`Received sessions event, projectId: ${params.projectId}`); - const session = event.payload; - store.update((sessions) => { - const index = sessions.findIndex((session) => session.id === event.payload.id); - if (index === -1) { - return [...sessions, session]; - } else { - return [...sessions.slice(0, index), session, ...sessions.slice(index + 1)]; - } - }); - }); + appWindow.listen(`project://${params.projectId}/sessions`, async (event) => { + log.info(`Received sessions event, projectId: ${params.projectId}`); + const session = event.payload; + store.update((sessions) => { + const index = sessions.findIndex((session) => session.id === event.payload.id); + if (index === -1) { + return [...sessions, session]; + } else { + return [...sessions.slice(0, index), session, ...sessions.slice(index + 1)]; + } + }); + }); - return store as Readable; + return store as Readable; }; diff --git a/src/routes/projects/[projectId]/+page.svelte b/src/routes/projects/[projectId]/+page.svelte index 857f7eb30..3072202b0 100644 --- a/src/routes/projects/[projectId]/+page.svelte +++ b/src/routes/projects/[projectId]/+page.svelte @@ -29,7 +29,7 @@ let datePass = format(date, 'yyyy-MM-dd'); if ($project) { - return `/projects/${$project.id}/player?date=${datePass}`; + return `/projects/${$project.id}/player/${datePass}`; } } diff --git a/src/routes/projects/[projectId]/player/+layout.svelte b/src/routes/projects/[projectId]/player/+layout.svelte new file mode 100644 index 000000000..bbe6f0084 --- /dev/null +++ b/src/routes/projects/[projectId]/player/+layout.svelte @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/routes/projects/[projectId]/player/+layout.ts b/src/routes/projects/[projectId]/player/+layout.ts new file mode 100644 index 000000000..60e5c8733 --- /dev/null +++ b/src/routes/projects/[projectId]/player/+layout.ts @@ -0,0 +1,14 @@ +import { building } from '$app/environment'; +import type { Session } from '$lib/sessions'; +import { readable, type Readable } from 'svelte/store'; +import type { LayoutLoad } from './$types'; + +export const load: LayoutLoad = async ({ params }) => { + const sessions: Readable = building + ? readable([]) + : await import('$lib/sessions').then((m) => m.default({ projectId: params.projectId })); + return { + sessions, + projectId: params.projectId + }; +}; diff --git a/src/routes/projects/[projectId]/player/+page.svelte b/src/routes/projects/[projectId]/player/+page.svelte deleted file mode 100644 index 018361a38..000000000 --- a/src/routes/projects/[projectId]/player/+page.svelte +++ /dev/null @@ -1,656 +0,0 @@ - - -{#if $sessions.length === 0} -
-
-

I haven't seen any changes yet

-

Go code something!

-
-
-{:else if currentPlaylist !== null} -
-
- {#if $fileFilter} - - {/if} -
-
    -
  • - -
  • - {#each Object.keys($sessionDays) as day} -
  • - -
  • - {/each} -
- -
-
-

- Activities - - {currentPlaylist.chapters.length} - -

-
- -
    - {#each currentPlaylist.chapters as chapter} - {@const isCurrent = - currentEdit !== null && currentEdit.sessionId == chapter.session.id} -
  • - -
  • - {:else} -
    No activities found
    - {/each} -
-
- -
-
-
- {#if dayPlaylist[$currentDay] !== undefined} - {#if currentEdit !== null} - - {:else} -
Select a playlist
- {/if} - {:else} - loading... - {/if} -
- - {#if currentEdit !== null} -
-
-
{currentEdit.filepath}
-
{new Date(currentEdit.delta.timestampMs).toLocaleString('en-US')}
-
-
- -
- - -
- -
- -
-
-
- {#if interval} - - {:else} - - {/if} -
- -
- - - -
- - -
- -
- - {#if !fullContext} - - {/if} -
-
-
- {/if} -
-
-
-
-
-{:else} -
loading data...
-{/if} diff --git a/src/routes/projects/[projectId]/player/+page.ts b/src/routes/projects/[projectId]/player/+page.ts deleted file mode 100644 index 064a43e26..000000000 --- a/src/routes/projects/[projectId]/player/+page.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { building } from '$app/environment'; -import type { Session } from '$lib/sessions'; -import { readable, type Readable } from 'svelte/store'; -import type { PageLoad } from './$types'; - -export const load: PageLoad = async ({ params }) => { - const sessions: Readable = building - ? readable([]) - : await import('$lib/sessions').then((m) => m.default({ projectId: params.projectId })); - return { - sessions, - projectId: params.projectId - }; -}; diff --git a/src/routes/projects/[projectId]/player/[date]/+layout.svelte b/src/routes/projects/[projectId]/player/[date]/+layout.svelte new file mode 100644 index 000000000..5a2d909c2 --- /dev/null +++ b/src/routes/projects/[projectId]/player/[date]/+layout.svelte @@ -0,0 +1,48 @@ + + +{#if $sessions.length === 0} +
+

I haven't seen any changes yet

+

Go code something!

+
+{:else} +
+ + + +
+{/if} diff --git a/src/routes/projects/[projectId]/player/[date]/+page.svelte b/src/routes/projects/[projectId]/player/[date]/+page.svelte new file mode 100644 index 000000000..8a641e446 --- /dev/null +++ b/src/routes/projects/[projectId]/player/[date]/+page.svelte @@ -0,0 +1,18 @@ + diff --git a/src/routes/projects/[projectId]/player/[date]/[sessionId]/+page.svelte b/src/routes/projects/[projectId]/player/[date]/[sessionId]/+page.svelte new file mode 100644 index 000000000..588da64cc --- /dev/null +++ b/src/routes/projects/[projectId]/player/[date]/[sessionId]/+page.svelte @@ -0,0 +1,363 @@ + + +
+ {#await sessions.load()} +
+
+

Loading...

+
+ {:then} +
+

+ Activities + + {$sessions.length} + +

+
+ + + {/await} +
+ +
+ {#if $frame} +
+
+ +
+ +
+
+
{shortPath($frame.filepath)}
+
+ {new Date($frame.deltas[$frame.deltas.length - 1].timestampMs).toLocaleString('en-US')} +
+
+
+ +
+ + +
+ +
+ +
+
+
+ {#if interval} + + {:else} + + {/if} +
+ +
+ + + +
+ + +
+ +
+ + {#if !fullContext} + + {/if} +
+
+
+
+ {:else} +
Select a playlist
+ {/if} +
diff --git a/src/routes/projects/[projectId]/player/[date]/[sessionId]/+page.ts b/src/routes/projects/[projectId]/player/[date]/[sessionId]/+page.ts new file mode 100644 index 000000000..a3d401177 --- /dev/null +++ b/src/routes/projects/[projectId]/player/[date]/[sessionId]/+page.ts @@ -0,0 +1,41 @@ +import type { Delta } from '$lib/deltas'; +import type { Session } from '$lib/sessions'; +import { asyncDerived } from '@square/svelte-store'; +import { format } from 'date-fns'; +import type { PageLoad } from './$types'; + +const enrichSession = async (projectId: string, session: Session) => { + const sessionsModule = await import('$lib/sessions'); + const deltasModule = await import('$lib/deltas'); + const files = await sessionsModule.listFiles({ projectId, sessionId: session.id }); + const deltas = await deltasModule.list({ projectId, sessionId: session.id }).then((deltas) => + Object.entries(deltas) + .flatMap(([path, deltas]) => deltas.map((delta) => [path, delta] as [string, Delta])) + .sort((a, b) => a[1].timestampMs - b[1].timestampMs) + ); + const deltasFiles = new Set(deltas.map(([path]) => path)); + return { + ...session, + files: Object.fromEntries( + Object.entries(files).filter(([filepath]) => deltasFiles.has(filepath)) + ), + deltas + }; +}; + +export const load: PageLoad = async ({ params, parent }) => { + const { sessions } = await parent(); + return { + sessions: asyncDerived(sessions, async (sessions) => + Promise.all( + sessions + .filter((session) => format(session.meta.startTimestampMs, 'yyyy-MM-dd') === params.date) + .map((session) => enrichSession(params.projectId, session)) + ).then((sessions) => + sessions + .filter((s) => Object.keys(s.files).length > 0) + .sort((a, b) => a.meta.startTimestampMs - b.meta.startTimestampMs) + ) + ) + }; +};