replace week-day-session pages with the unified timeline

This commit is contained in:
Kiril Videlov 2023-02-28 09:38:02 +01:00
parent c0a85d91b8
commit 87e9eb85aa
6 changed files with 5 additions and 740 deletions

View File

@ -35,29 +35,7 @@
<nav
class="flex items-center flex-none justify-between py-2 px-8 space-x-3 border-b select-none text-zinc-300 border-zinc-700"
>
<div
class="text-zinc-400 w-64 font-medium grid grid-cols-3 items-center bg-zinc-700/50 rounded-lg h-7 px-4 gap-1"
>
<a
class="
{selection === 'week' ? 'bg-zinc-600/70 text-zinc-100' : ''}
rounded-lg h-7 flex items-center justify-center p-3 text-center hover:text-zinc-100"
href="/projects/{$project?.id}/week">Week</a
>
<a
href="/projects/{$project?.id}/day"
class="
{selection === 'day' ? 'bg-zinc-600/70 text-zinc-100' : ''}
rounded-lg h-7 flex items-center justify-center p-3 text-center hover:text-zinc-100">Day</a
>
<a
href="/projects/{$project?.id}/sessions/{lastSessionId}"
class="
{selection === 'sessions' ? 'bg-zinc-600/70 text-zinc-100' : ''}
rounded-lg h-7 flex items-center justify-center p-3 text-center hover:text-zinc-100"
title="go to current session">Session</a
>
</div>
<div />
<ul>
<li>

View File

@ -9,9 +9,9 @@
<h1 class="text-zinc-200 text-xl flex justify-center">
Overview of {$project?.title}
</h1>
<div class="flex justify-center space-x-2">
<a class="hover:text-zinc-200" href="/projects/{$project?.id}/week">Week</a>
<a href="/projects/{$project?.id}/day" class="hover:text-zinc-200">Day</a>
<a href="/projects/{$project?.id}/timeline" class="hover:text-zinc-200">New timeline</a>
<div class="flex justify-center space-x-2 text-lg">
<a href="/projects/{$project?.id}/timeline" class="hover:text-zinc-200 text-orange-400"
>Timeline</a
>
</div>
</div>

View File

@ -1,84 +0,0 @@
<script lang="ts">
import { IconChevronRight, IconChevronLeft } from '@tabler/icons-svelte';
import { TimelineDaySession } from '$lib/components/timeline';
import type { PageData } from './$types';
import type { Session } from '$lib/sessions';
import { derived } from 'svelte/store';
export let data: PageData;
const { project, sessions } = data;
let date = new Date();
$: canNavigateForwad = new Date(date.getTime() + 24 * 60 * 60 * 1000) < new Date();
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat('default', {
weekday: 'short',
day: 'numeric',
month: 'short'
}).format(date);
};
const sessionDisplayWidth = (session: Session) => {
let sessionDurationMinutes =
(session.meta.lastTimestampMs - session.meta.startTimestampMs) / 60;
if (sessionDurationMinutes <= 10) {
return 'w-40 min-w-40';
} else {
return 'w-60 min-w-60';
}
};
$: sessionsInDay = derived([sessions], ([sessions]) => {
const start = new Date(date.getFullYear(), date.getMonth(), date.getDate());
const end = new Date(start.getTime() + 24 * 60 * 60 * 1000);
return sessions.filter((session) => {
return (
start <= new Date(session.meta.startTimestampMs) &&
new Date(session.meta.startTimestampMs) <= end
);
});
});
</script>
<div class="flex flex-col h-full select-none text-zinc-400">
<header class="flex items-center justify-between flex-none px-8 py-1.5 border-b border-zinc-700">
<div class="flex items-center justify-start w-64">
<button
class="-ml-2 hover:text-zinc-100"
on:click={() => (date = new Date(date.getTime() - 24 * 60 * 60 * 1000))}
>
<IconChevronLeft class="w-8 h-8" />
</button>
<div class="flex-grow text-center">
{formatDate(date)}
</div>
<button
class="-mr-2 hover:text-zinc-100 disabled:text-zinc-700"
disabled={!canNavigateForwad}
on:click={() => {
if (canNavigateForwad) {
date = new Date(date.getTime() + 24 * 60 * 60 * 1000);
}
}}
>
<IconChevronRight class="w-8 h-8" />
</button>
</div>
</header>
<div class="w-full h-full overflow-scroll mx-2 flex">
{#if $project}
<div class="flex-grow items-center justify-center mt-4">
<div class="justify-center flex flex-row space-x-2 pt-2">
{#each $sessionsInDay as session}
<div class={sessionDisplayWidth(session)}>
<TimelineDaySession projectId={$project.id} {session} />
</div>
{/each}
</div>
</div>
{:else}
<p>Project not found</p>
{/if}
</div>
</div>

View File

@ -1,266 +0,0 @@
<script lang="ts">
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-svelte';
import { add, format, differenceInSeconds, addSeconds } from 'date-fns';
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { derived } from 'svelte/store';
import { Operation } from '$lib/deltas';
import { Slider } from 'fluent-svelte';
import { CodeViewer } from '$lib/components';
import 'fluent-svelte/theme.css';
import type { PageData } from './$types';
export let data: PageData;
$: session = data.session;
$: previousSesssion = data.previousSesssion;
$: nextSession = data.nextSession;
$: deltas = data.deltas;
let time = new Date();
$: start = new Date($session.meta.startTimestampMs);
$: end = $session.hash ? addSeconds(new Date($session.meta.lastTimestampMs), 10) : time; // For some reason, some deltas are stamped a few seconds after the session end.
// Also, if the session is current, the end time moves.
onMount(() => {
const interval = setInterval(() => {
time = new Date();
}, 10000);
return () => {
clearInterval(interval);
};
});
$: midpoint = add(start, {
seconds: differenceInSeconds(end, start) * 0.5
});
$: quarter = add(start, {
seconds: differenceInSeconds(end, start) * 0.25
});
$: threequarters = add(start, {
seconds: differenceInSeconds(end, start) * 0.75
});
const timeStampToCol = (deltaTimestamp: Date) => {
if (deltaTimestamp < start || deltaTimestamp > end) {
console.error(
`Delta timestamp out of session range. Delta timestamp: ${deltaTimestamp}, Session start: ${start}, Session end: ${end}`
);
}
// there are 88 columns
// start is column 17
const totalDiff = differenceInSeconds(end, start);
const eventDiff = differenceInSeconds(deltaTimestamp, start);
const rat = eventDiff / totalDiff;
const col = Math.floor(rat * 63 + 17);
return col;
};
const colToTimestamp = (col: number) => {
const totalDiff = differenceInSeconds(end, start);
const colDiff = col - 17;
const rat = colDiff / 63;
const eventDiff = totalDiff * rat;
const timestamp = addSeconds(start, eventDiff);
return timestamp;
};
$: tickSizeMs = Math.floor((end.getTime() - start.getTime()) / 63); // how many ms each column represents
let selectedFileIdx = 0;
let value = 0;
$: doc = derived([data.deltas], ([allDeltas]) => {
const filePath = Object.keys(allDeltas)[selectedFileIdx];
const deltas = allDeltas[filePath];
let text = data.files[filePath] || '';
if (!deltas) return text;
const sliderValueTimestampMs = colToTimestamp(value).getTime() + tickSizeMs; // Include the tick size so that the slider value is always in the future
// Filter operations based on the current slider value
const operations = deltas
.filter(
(delta) =>
delta.timestampMs >= start.getTime() && delta.timestampMs <= sliderValueTimestampMs
)
.sort((a, b) => a.timestampMs - b.timestampMs)
.flatMap((delta) => delta.operations);
operations.forEach((operation) => {
if (Operation.isInsert(operation)) {
text =
text.slice(0, operation.insert[0]) +
operation.insert[1] +
text.slice(operation.insert[0]);
} else if (Operation.isDelete(operation)) {
text =
text.slice(0, operation.delete[0]) +
text.slice(operation.delete[0] + operation.delete[1]);
}
});
return text;
});
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat('default', {
weekday: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
}).format(date);
};
</script>
<div class="flex flex-col h-full text-zinc-400 overflow-hidden">
<header class="flex items-center justify-between flex-none px-8 py-1.5 border-b border-zinc-700">
<div class="flex items-center justify-start w-64">
<a
href="/projects/{$page.params.projectId}/sessions/{$previousSesssion?.id}"
class="-ml-2 hover:text-zinc-100 {$previousSesssion
? ''
: 'opacity-50 pointer-events-none cursor-not-allowed'}"
>
<IconChevronLeft class="w-8 h-8" />
</a>
<div class="flex-grow text-center cursor-default grid grid-cols-7">
<span class="col-span-3">{formatDate(start)}</span>
<span>&mdash;</span>
<span class="col-span-3">{formatDate(end)}</span>
</div>
<a
href="/projects/{$page.params.projectId}/sessions/{$nextSession?.id}"
class="-mr-2 hover:text-zinc-100 {$nextSession
? ''
: 'text-zinc-700 pointer-events-none cursor-not-allowed'}"
>
<IconChevronRight class="w-8 h-8" />
</a>
</div>
</header>
<!-- main part -->
<div
class="flex flex-col flex-none max-w-full select-none border-b border-zinc-700 h-full overflow-auto"
>
<div class="flex flex-col flex-none max-w-full mb-40">
<!-- sticky header -->
<div
class="overflow-hidden sticky top-0 z-30 bg-zinc-800 flex-none shadow shadow-zinc-700 ring-1 ring-zinc-700 ring-opacity-5 mb-1"
>
<div class="grid-cols-11 -mr-px border-zinc-700 grid">
<div />
<div class="col-span-2 flex items-center justify-center py-2">
<span>{format(start, 'hh:mm')}</span>
</div>
<div class="col-span-2 flex items-center justify-center py-2">
<span>{format(quarter, 'hh:mm')}</span>
</div>
<div class="col-span-2 flex items-center justify-center py-2">
<span>{format(midpoint, 'hh:mm')}</span>
</div>
<div class="col-span-2 flex items-center justify-center py-2">
<span>{format(threequarters, 'hh:mm')}</span>
</div>
<div class="col-span-2 flex items-center justify-center py-2">
<span>{format(end, 'hh:mm')}</span>
</div>
</div>
<!-- needle -->
<div class="grid grid-cols-11">
<div class="col-span-2 flex items-center justify-center" />
<div class="-mx-1 col-span-8 flex items-center justify-center">
<Slider min={17} max={80} step={1} bind:value>
<svelte:fragment slot="tooltip" let:value>
{format(colToTimestamp(value), 'hh:mm')}
</svelte:fragment>
</Slider>
</div>
<div class="col-span-1 flex items-center justify-center" />
</div>
</div>
<div class="flex flex-auto mb-1">
<div class="grid flex-auto grid-cols-1 grid-rows-1">
<!-- file names list -->
<div
class="bg-col-start-1 col-end-2 row-start-1 grid divide-y divide-zinc-700/20"
style="grid-template-rows: repeat({Object.keys($deltas).length}, minmax(2rem, 1fr));"
>
<!-- <div class="row-end-1 h-7" /> -->
{#each Object.keys($deltas) as filePath, i}
<div class="flex {i == selectedFileIdx ? 'bg-zinc-500/70' : ''}">
<button
class="z-20 flex justify-end items-center overflow-hidden sticky left-0 w-1/6 leading-5
{selectedFileIdx == i
? 'text-zinc-200 cursor-default'
: 'text-zinc-400 hover:text-zinc-200 cursor-pointer'}"
on:click={() => (selectedFileIdx = i)}
title={filePath}
>
{filePath.split('/').pop()}
</button>
</div>
{/each}
</div>
<!-- col selection -->
<div
class="col-start-1 col-end-2 row-start-1 grid"
style="grid-template-columns: repeat(88, minmax(0, 1fr));"
>
<div class="bg-sky-400/60 " style=" grid-column: {value};" />
</div>
<!-- time vertical lines -->
<div
class="col-start-1 col-end-2 row-start-1 grid-rows-1 divide-x divide-zinc-700/50 grid grid-cols-11"
>
<div class="col-span-2 row-span-full" />
<div class="col-span-2 row-span-full" />
<div class="col-span-2 row-span-full" />
<div class="col-span-2 row-span-full" />
<div class="col-span-2 row-span-full" />
<div class="col-span-2 row-span-full" />
</div>
<!-- actual entries -->
<ol
class="col-start-1 col-end-2 row-start-1 grid"
style="
grid-template-columns: repeat(88, minmax(0, 1fr));
grid-template-rows: repeat({Object.keys($deltas)
.length}, minmax(0px, 1fr)) auto;"
>
{#each Object.entries($deltas) as [filePath, fileDeltas], idx}
{#each fileDeltas as delta}
<li
class="relative flex items-center bg-zinc-300 hover:bg-zinc-100 rounded m-0.5 cursor-pointer"
style="
grid-row: {idx + 1} / span 1;
grid-column: {timeStampToCol(
new Date(delta.timestampMs)
)} / span 1;"
>
<button
class="z-20 h-full flex flex-col w-full items-center justify-center"
on:click={() => {
value = timeStampToCol(new Date(delta.timestampMs));
selectedFileIdx = idx;
}}
/>
</li>
{/each}
{/each}
</ol>
</div>
</div>
<div class="grid grid-cols-11 mt-6">
<div class="col-span-2" />
<div class="col-span-8 p-1 bg-zinc-500/70 rounded select-text">
{#if $doc}
<CodeViewer value={$doc} />
{/if}
</div>
<div class="" />
</div>
</div>
</div>
</div>

View File

@ -1,45 +0,0 @@
import type { PageLoad } from './$types';
import { derived, readable } from 'svelte/store';
import { building } from '$app/environment';
import type { Delta } from '$lib/deltas';
export const prerender = false;
export const load: PageLoad = async ({ parent, params }) => {
const { sessions } = await parent();
const deltas = building
? readable({} as Record<string, Delta[]>)
: (await import('$lib/deltas')).default({
projectId: params.projectId,
sessionId: params.sessionId
});
const files = building
? ({} as Record<string, string>)
: (await import('$lib/sessions')).listFiles({
projectId: params.projectId,
sessionId: params.sessionId
});
return {
session: derived(sessions, (sessions) => {
const result = sessions.find((session) => session.id === params.sessionId);
return result ? result : sessions[0];
}),
previousSesssion: derived(sessions, (sessions) => {
const currentSessionIndex = sessions.findIndex((session) => session.id === params.sessionId);
if (currentSessionIndex - 1 < sessions.length) {
return sessions[currentSessionIndex - 1];
} else {
return undefined;
}
}),
nextSession: derived(sessions, (sessions) => {
const currentSessionIndex = sessions.findIndex((session) => session.id === params.sessionId);
if (currentSessionIndex + 1 < sessions.length) {
return sessions[currentSessionIndex + 1];
} else {
return undefined;
}
}),
files: files,
deltas: deltas
};
};

View File

@ -1,318 +0,0 @@
<script lang="ts">
import { Week } from '$lib/week';
import type { PageData } from './$types';
import { WeekBlockEntry } from '$lib/components/week';
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-svelte';
import { derived } from 'svelte/store';
import { toHumanBranchName } from '$lib/branch';
export let data: PageData;
const { project, sessions } = data;
let week = Week.from(new Date());
$: canNavigateForwad = week.end.getTime() < new Date().getTime();
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat('default', {
weekday: 'short',
day: 'numeric',
month: 'short'
}).format(date);
};
$: sessionsInWeek = derived([sessions], ([sessions]) => {
return sessions.filter((session) => {
return (
week.start <= new Date(session.meta.startTimestampMs) &&
new Date(session.meta.startTimestampMs) <= week.end
);
});
});
</script>
<div class="flex flex-col h-full select-none text-zinc-400">
<header class="flex items-center justify-between flex-none px-8 py-1.5 border-b border-zinc-700">
<div class="flex items-center justify-start w-64">
<button class="-ml-2 hover:text-zinc-100" on:click={() => (week = Week.previous(week))}>
<IconChevronLeft class="w-8 h-8" />
</button>
<div class="flex-grow text-center cursor-default grid grid-cols-7">
<span class="col-span-3">{formatDate(Week.nThDay(week, 0))}</span>
<span>&mdash;</span>
<span class="col-span-3">{formatDate(Week.nThDay(week, 6))}</span>
</div>
<button
class="-mr-2 hover:text-zinc-100 disabled:text-zinc-700"
disabled={!canNavigateForwad}
on:click={() => {
if (canNavigateForwad) {
week = Week.next(week);
}
}}
>
<IconChevronRight class="w-8 h-8" />
</button>
</div>
</header>
<div class="isolate flex flex-col flex-auto overflow-auto">
<div class="h-4/5 overflow-auto flex flex-col flex-none max-w-full border-b border-zinc-700">
<!-- sticky top -->
<div
class="overflow-hidden sticky top-0 z-30 bg-zinc-800 border-b border-zinc-700 flex-none px-8"
>
<div class="grid-cols-8 divide-x divide-zinc-700 grid">
<div class="py-4" />
<div class="flex items-center justify-center">
<span
>Mon <span class="items-center justify-center font-semibold"
>{Week.nThDay(week, 0).getDate()}</span
></span
>
</div>
<div class="flex items-center justify-center">
<span
>Tue <span class="items-center justify-center font-semibold"
>{Week.nThDay(week, 1).getDate()}</span
></span
>
</div>
<div class="flex items-center justify-center">
<span
>Wed <span class="items-center justify-center font-semibold"
>{Week.nThDay(week, 2).getDate()}</span
></span
>
</div>
<div class="flex items-center justify-center">
<span
>Thu <span class="items-center justify-center font-semibold"
>{Week.nThDay(week, 3).getDate()}</span
></span
>
</div>
<div class="flex items-center justify-center">
<span
>Fri <span class="items-center justify-center font-semibold"
>{Week.nThDay(week, 4).getDate()}</span
></span
>
</div>
<div class="flex items-center justify-center">
<span
>Sat <span class="items-center justify-center font-semibold"
>{Week.nThDay(week, 5).getDate()}</span
></span
>
</div>
<div class="flex items-center justify-center">
<span
>Sun <span class="items-center justify-center font-semibold"
>{Week.nThDay(week, 6).getDate()}</span
></span
>
</div>
</div>
</div>
<div class="flex flex-auto ">
<div class="grid flex-auto grid-cols-1 grid-rows-1">
<!-- hours y lines-->
<div
class="text-zinc-500 col-start-1 col-end-2 row-start-1 grid-rows-1 grid grid-cols-8 px-8"
>
<div
class="col-start-1 col-end-2 row-start-1 grid justify-end"
style="grid-template-rows: repeat(24, minmax(1.5rem, 1fr));"
>
<div class="row-end-1 h-7" />
<div>
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
12 AM
</div>
</div>
<div />
<div>
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
2 AM
</div>
</div>
<div />
<div>
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
4 AM
</div>
</div>
<div />
<div>
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
6 AM
</div>
</div>
<div />
<div>
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
8 AM
</div>
</div>
<div />
<div>
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
10 AM
</div>
</div>
<div />
<div>
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
12 PM
</div>
</div>
<div />
<div>
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
2 PM
</div>
</div>
<div />
<div>
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
4 PM
</div>
</div>
<div />
<div>
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
6 PM
</div>
</div>
<div />
<div>
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
8 PM
</div>
</div>
<div />
<div>
<div class="z-20 -mt-2.5 -ml-14 w-14 pr-4 text-right leading-5 text-zinc-500">
10 PM
</div>
</div>
<div />
</div>
</div>
<div
class="text-zinc-500 col-start-1 col-end-2 row-start-1 grid-rows-1 grid grid-cols-8 ml-8 "
>
<div
class="col-start-2 col-end-9 row-start-1 grid divide-y divide-zinc-700/20"
style="grid-template-rows: repeat(24, minmax(1.5rem, 1fr));"
>
<div class="row-end-1 h-7" />
<div>
<div
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
/>
</div>
<div />
<div>
<div
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
/>
</div>
<div />
<div>
<div
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
/>
</div>
<div />
<div>
<div
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
/>
</div>
<div />
<div>
<div
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
/>
</div>
<div />
<div>
<div
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
/>
</div>
<div />
<div>
<div
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
/>
</div>
<div />
<div>
<div
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
/>
</div>
<div />
<div>
<div
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
/>
</div>
<div />
<div>
<div
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
/>
</div>
<div />
<div>
<div
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
/>
</div>
<div />
<div>
<div
class="h-7 left-0 z-20 -mt-2.5 -ml-14 w-14 pr-2 text-right leading-5 text-zinc-500"
/>
</div>
</div>
<div />
</div>
<!-- day x lines -->
<div
class="col-start-1 col-end-2 row-start-1 grid-rows-1 divide-x divide-zinc-700/50 grid grid-cols-8 px-8"
>
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
</div>
<!-- actual entries -->
<ol
class="col-start-1 col-end-2 row-start-1 grid grid-cols-8 px-8"
style="grid-template-rows: 1.75rem repeat(96, minmax(0px, 1fr)) auto;"
>
{#each $sessionsInWeek as session}
<WeekBlockEntry
startTime={new Date(session.meta.startTimestampMs)}
endTime={new Date(session.meta.startTimestampMs)}
label={toHumanBranchName(session.meta.branch)}
href="/projects/{$project?.id}/sessions/{session.id}/"
/>
{/each}
</ol>
</div>
</div>
</div>
</div>
</div>