mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2025-01-01 14:04:57 +03:00
quick pass at a new project page
This commit is contained in:
parent
12ac983978
commit
c339af69e4
@ -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>
|
||||
|
@ -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"
|
||||
>⌘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>
|
||||
|
@ -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
|
||||
};
|
||||
};
|
||||
|
@ -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>
|
||||
|
@ -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[]>([]);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user