quick pass at a new project page

This commit is contained in:
Scott Chacon 2023-03-09 09:00:41 -08:00
parent 12ac983978
commit c339af69e4
5 changed files with 241 additions and 25 deletions

View File

@ -203,12 +203,6 @@
</div>
</div>
</div>
<div class="absolute bottom-0 left-0 w-full">
<div class="h-18 flex flex-shrink-0 select-none items-center border-t border-zinc-700 p-4">
<div class="text-sm text-zinc-300">Timeline</div>
</div>
</div>
{/if}
</div>
</div>

View File

@ -33,7 +33,47 @@
<nav
class="flex flex-none select-none items-center justify-between space-x-3 border-b border-zinc-700 py-1 px-8 text-zinc-300"
>
<div />
<div class="flex flex-row items-center space-x-2">
<form action="/projects/{$project?.id}/search" method="GET">
<div class="flex w-48 max-w-lg rounded-md shadow-sm">
<input
type="text"
name="search"
id="search"
placeholder="search"
class="block w-full pl-3 min-w-0 flex-1 rounded-none bg-zinc-900 border-r-0 rounded-l-md border-0 py-1.5 text-zinc-200 ring-1 ring-inset ring-zinc-700 placeholder:text-zinc-400 focus:ring-1 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
/>
<span
class="inline-flex items-center rounded-r-md bg-zinc-900 border border-l-0 border-zinc-700 px-3 text-gray-500 sm:text-sm"
>&#8984;K</span
>
</div>
</form>
<a href="/projects/{$project?.id}/player" class="text-zinc-400 hover:text-zinc-200">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.91 11.672a.375.375 0 010 .656l-5.603 3.113a.375.375 0 01-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112z"
/>
</svg>
</a>
<a href="/projects/{$project?.id}/timeline" class="text-orange-400 hover:text-zinc-200"
>Timeline</a
>
</div>
<ul>
<li>

View File

@ -1,10 +1,79 @@
import type { LayoutLoad } from './$types';
import { building } from '$app/environment';
import { readable, derived } from 'svelte/store';
import type { Session } from '$lib/sessions';
import type { UISession } from '$lib/uisessions';
import { asyncDerived } from '@square/svelte-store';
import type { Delta } from '$lib/deltas';
import { startOfDay } from 'date-fns';
export const prerender = false;
export const load: LayoutLoad = async ({ parent, params }) => {
const { projects } = await parent();
const sessions = building
? readable<Session[]>([])
: await (await import('$lib/sessions')).default({ projectId: params.projectId });
const orderedSessions = derived(sessions, (sessions) => {
return sessions.slice().sort((a, b) => a.meta.startTimestampMs - b.meta.startTimestampMs);
});
let dateSessions = readable<Record<number, UISession[]>>({});
if (!building) {
const listDeltas = (await import('$lib/deltas')).list;
dateSessions = asyncDerived([orderedSessions], async ([sessions]) => {
const deltas = await Promise.all(
sessions.map((session) => {
return listDeltas({
projectId: params.projectId ?? '',
sessionId: session.id
});
})
);
// Sort deltas by timestamp
deltas.forEach((delta) => {
Object.keys(delta).forEach((key) => {
delta[key].sort((a, b) => a.timestampMs - b.timestampMs).reverse();
});
});
const uiSessions = sessions
.map((session, i) => {
return { session, deltas: deltas[i] } as UISession;
})
.filter((uiSession) => {
return Object.keys(uiSession.deltas).length > 0;
});
const dateSessions: Record<number, UISession[]> = {};
uiSessions.forEach((uiSession) => {
const date = startOfDay(new Date(uiSession.session.meta.startTimestampMs));
if (dateSessions[date.getTime()]) {
dateSessions[date.getTime()]?.push(uiSession);
} else {
dateSessions[date.getTime()] = [uiSession];
}
})
// For each UISession in dateSessions, set the earliestDeltaTimestampMs and latestDeltaTimestampMs
Object.keys(dateSessions).forEach((date: any) => {
dateSessions[date].forEach((uiSession: any) => {
const deltaTimestamps = Object.keys(uiSession.deltas).reduce((acc, key) => {
return acc.concat(uiSession.deltas[key].map((delta: Delta) => delta.timestampMs));
}, []);
uiSession.earliestDeltaTimestampMs = Math.min(...deltaTimestamps);
uiSession.latestDeltaTimestampMs = Math.max(...deltaTimestamps);
});
});
return dateSessions;
});
}
return {
project: projects.get(params.projectId),
projectId: params.projectId
projectId: params.projectId,
sessions: orderedSessions,
dateSessions: dateSessions
};
};

View File

@ -3,19 +3,128 @@
export let data: LayoutData;
$: project = data.project;
$: dateSessions = data.dateSessions;
// convert a list of timestamps to a sparkline
function timestampsToSpark(tsArray) {
let range = tsArray[0] - tsArray[tsArray.length - 1];
console.log(range);
let totalBuckets = 18;
let bucketSize = range / totalBuckets;
let buckets = [];
for (let i = 0; i <= totalBuckets; i++) {
buckets.push([]);
}
tsArray.forEach((ts) => {
let bucket = Math.floor((tsArray[0] - ts) / bucketSize);
if (bucket && ts) {
buckets[bucket].push(ts);
}
});
console.log(buckets);
let spark = '';
buckets.forEach((entries) => {
let size = entries.length;
if (size < 1) {
spark += '<span class="text-zinc-600"></span>';
} else if (size < 2) {
spark += '<span class="text-blue-200"></span>';
} else if (size < 3) {
spark += '<span class="text-blue-200"></span>';
} else if (size < 4) {
spark += '<span class="text-blue-200"></span>';
} else if (size < 5) {
spark += '<span class="text-blue-200"></span>';
} else if (size < 6) {
spark += '<span class="text-blue-200"></span>';
} else if (size < 7) {
spark += '<span class="text-blue-200"></span>';
} else {
spark += '<span class="text-blue-200"></span>';
}
});
return spark;
}
// reduce a group of sessions to a map of filename to timestamps array
function sessionFileMap(sessions: any[]) {
let sessionsByFile = {};
sessions.forEach((session) => {
Object.entries(session.deltas).forEach((deltas) => {
let filename = deltas[0];
let timestamps = deltas[1].map((delta: any) => {
return delta.timestampMs;
});
if (sessionsByFile[filename]) {
sessionsByFile[filename] = sessionsByFile[filename].concat(timestamps).sort();
} else {
sessionsByFile[filename] = timestamps;
}
});
});
return sessionsByFile;
}
// order the sessions and summarize the changes by file
function orderedSessions(dateSessions: Record<string, any>) {
return Object.entries(dateSessions)
.sort((a, b) => {
return parseInt(b[0]) - parseInt(a[0]);
})
.map(([date, sessions]) => {
return [date, sessionFileMap(sessions)];
})
.slice(0, 3);
}
</script>
<div class="mt-12 flex flex-col">
<h1 class="flex justify-center text-xl text-zinc-200">
Overview of {$project?.title}
<div class="mt-4 px-8 flex flex-col">
<h1 class="flex text-xl text-zinc-200">
{$project?.title} <span class="text-zinc-600 ml-2">Project</span>
</h1>
<div class="flex justify-center space-x-2 text-lg">
<a href="/projects/{$project?.id}/timeline" class="text-orange-400 hover:text-zinc-200"
>Timeline</a
>
<a href="/projects/{$project?.id}/search" class="text-orange-400 hover:text-zinc-200"
>search (test)</a
>
<a href="/projects/{$project?.id}/player" class="text-orange-400 hover:text-zinc-200">player</a>
<div class="grid grid-cols-3 mt-4">
<div class="col-span-2 pr-6">
<h2 class="text-lg font-bold text-zinc-500 mb-4">Recent File Changes</h2>
{#if $dateSessions === undefined}
<span>Loading...</span>
{:else}
<div class="flex flex-col space-y-4">
{#each orderedSessions($dateSessions) as [dateMilliseconds, fileSessions]}
<div class="flex flex-col">
<div class="text-zinc-400 text-lg text-zinc-200 mb-1">
{new Date(parseInt(dateMilliseconds)).toLocaleDateString('en-us', {
weekday: 'long',
year: 'numeric',
month: 'short',
day: 'numeric'
})}
</div>
<div class="bg-zinc-700 rounded p-4">
{#each Object.entries(fileSessions) as filetime}
<div class="flex flex-row justify-between">
<div class="text-zinc-200 font-mono">{filetime[0]}</div>
<div class="text-zinc-400">{@html timestampsToSpark(filetime[1])}</div>
</div>
{/each}
</div>
</div>
{/each}
</div>
{/if}
</div>
<div class="col-span-1 space-y-6">
<div>
<h2 class="text-lg font-bold text-zinc-500">Work in Progress</h2>
<div class="text-zinc-400 mt-4 mb-1 bg-zinc-700 rounded p-4">No uncommitted work</div>
</div>
<div>
<h2 class="text-lg font-bold text-zinc-500">Recent Activity</h2>
<div class="text-zinc-400 mt-4 mb-1 bg-zinc-700 rounded p-4">No recent activity</div>
</div>
</div>
</div>
</div>

View File

@ -8,17 +8,21 @@
import type { Delta } from '$lib/deltas';
import { structuredPatch } from 'diff';
import { formatDistanceToNow } from 'date-fns';
// import { onMount } from 'svelte';
import { onMount } from 'svelte';
export let data: PageData;
const { project } = data;
const urlParams = new URLSearchParams(window.location.search);
console.log(urlParams.get('search'));
let query: string;
// onMount(async () => {
// await new Promise((r) => setTimeout(r, 100));
// query = 'transform';
// fetchResults();
// });
onMount(async () => {
await new Promise((r) => setTimeout(r, 100));
query = urlParams.get('search');
fetchResults();
});
const results = writable<SearchResult[]>([]);