mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-23 09:33:01 +03:00
player: extract playback component
This commit is contained in:
parent
f6f961df9b
commit
ca7e9050dc
@ -23,18 +23,16 @@
|
||||
<script lang="ts">
|
||||
import Slider from './Slider.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { IconPlayerPauseFilled, IconPlayerPlayFilled } from '$lib/icons';
|
||||
import { page } from '$app/stores';
|
||||
import { DeltasViewer, DiffContext } from '$lib/components';
|
||||
import { DeltasViewer } from '$lib/components';
|
||||
import { asyncDerived, derived, writable } from '@square/svelte-store';
|
||||
import { format } from 'date-fns';
|
||||
import { onMount } from 'svelte';
|
||||
import { unsubscribe } from '$lib/utils';
|
||||
import { hotkeys, stores } from '$lib';
|
||||
import { stores } from '$lib';
|
||||
import Info from './Info.svelte';
|
||||
import Playback from './Playback.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
const { currentFilepath, currentTimestamp } = data;
|
||||
const { currentFilepath, currentTimestamp, currentSessionId } = data;
|
||||
|
||||
let fullContext = true;
|
||||
let context = 8;
|
||||
@ -45,6 +43,11 @@
|
||||
)
|
||||
);
|
||||
|
||||
page.subscribe((page) => {
|
||||
currentDeltaIndex = parseInt(page.url.searchParams.get('delta') || '0');
|
||||
currentSessionId.set(page.params.sessionId);
|
||||
});
|
||||
|
||||
const fileFilter = derived(page, (page) => page.url.searchParams.get('file'));
|
||||
const projectId = derived(page, (page) => page.params.projectId);
|
||||
|
||||
@ -63,50 +66,33 @@
|
||||
}
|
||||
);
|
||||
|
||||
const currentDeltaIndex = writable(parseInt($page.url.searchParams.get('delta') || '0'));
|
||||
const currentSessionId = writable($page.params.sessionId);
|
||||
currentSessionId.subscribe(data.currentSessionId.set);
|
||||
$: currentDeltaIndex = parseInt($page.url.searchParams.get('delta') || '0');
|
||||
|
||||
richSessions.subscribe((sessions) => {
|
||||
if (!sessions) return;
|
||||
if (sessions.length === 0) return;
|
||||
if (!sessions.some((s) => s.id === $currentSessionId)) {
|
||||
currentSessionId.set(sessions[0].id);
|
||||
$currentSessionId = sessions[0].id;
|
||||
}
|
||||
});
|
||||
|
||||
page.subscribe((page) => {
|
||||
currentDeltaIndex.set(parseInt(page.url.searchParams.get('delta') || '0'));
|
||||
currentSessionId.set(page.params.sessionId);
|
||||
});
|
||||
$: currentSession = $richSessions?.find((s) => s.id === $currentSessionId);
|
||||
|
||||
const currentSessionIndex = derived(
|
||||
[currentSessionId, richSessions],
|
||||
([currentSessionId, sessions]) =>
|
||||
sessions?.findIndex((session) => session.id === currentSessionId)
|
||||
);
|
||||
$: frameDeltas = currentSession?.deltas.slice(0, currentDeltaIndex + 1);
|
||||
$: frameFilepath = frameDeltas?.[frameDeltas.length - 1]?.[0];
|
||||
$: frame =
|
||||
!currentSession || !frameFilepath || !frameDeltas
|
||||
? null
|
||||
: {
|
||||
session: currentSession,
|
||||
filepath: frameFilepath,
|
||||
doc: currentSession.files[frameFilepath] || '',
|
||||
deltas: frameDeltas.filter((delta) => delta[0] === frameFilepath).map((delta) => delta[1])
|
||||
};
|
||||
|
||||
const currentSession = derived(
|
||||
[currentSessionIndex, richSessions],
|
||||
([currentSessionIndex, sessions]) =>
|
||||
currentSessionIndex > -1 ? sessions[currentSessionIndex] : null
|
||||
);
|
||||
|
||||
const frame = derived([currentSession, currentDeltaIndex], ([session, currentDeltaIndex]) => {
|
||||
if (!session) return null;
|
||||
const deltas = session.deltas.slice(0, currentDeltaIndex + 1);
|
||||
const filepath = deltas[deltas.length - 1][0];
|
||||
return {
|
||||
session,
|
||||
filepath,
|
||||
doc: session.files[filepath] || '',
|
||||
deltas: deltas.filter((delta) => delta[0] === filepath).map((delta) => delta[1])
|
||||
};
|
||||
});
|
||||
frame.subscribe((frame) => frame?.filepath && currentFilepath.set(frame.filepath));
|
||||
|
||||
$: currentDelta = $frame?.deltas[$frame?.deltas.length - 1];
|
||||
$: if (frame) currentFilepath.set(frame?.filepath);
|
||||
|
||||
$: currentDelta = frame?.deltas[frame?.deltas.length - 1];
|
||||
$: {
|
||||
const timestamp = currentDelta?.timestampMs;
|
||||
if (timestamp) {
|
||||
@ -114,91 +100,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
// scroller
|
||||
const maxInput = derived(richSessions, (sessions) =>
|
||||
sessions ? sessions.flatMap((session) => session.deltas).length : 0
|
||||
);
|
||||
const value = writable(0);
|
||||
|
||||
const inputValue = writable(0);
|
||||
$: {
|
||||
if ($richSessions && $currentSessionIndex > -1) {
|
||||
$inputValue =
|
||||
$richSessions.slice(0, $currentSessionIndex).flatMap((session) => session.deltas).length +
|
||||
$currentDeltaIndex;
|
||||
if ($richSessions && currentSession) {
|
||||
const currentSessionIndex = $richSessions.findIndex((s) => s.id === currentSession?.id);
|
||||
$value =
|
||||
$richSessions
|
||||
.filter((_, index) => index < currentSessionIndex)
|
||||
.reduce((acc, s) => acc + s.deltas.length, 0) + currentDeltaIndex;
|
||||
}
|
||||
}
|
||||
|
||||
inputValue.subscribe((value) => {
|
||||
value.subscribe((value) => {
|
||||
let i = 0;
|
||||
for (const session of $richSessions || []) {
|
||||
if (i < value && value < i + session.deltas.length) {
|
||||
currentSessionId.set(session.id);
|
||||
currentDeltaIndex.set(value - i);
|
||||
$currentSessionId = session.id;
|
||||
currentDeltaIndex = value - i;
|
||||
break;
|
||||
}
|
||||
i += session.deltas.length;
|
||||
}
|
||||
});
|
||||
|
||||
// player
|
||||
let interval: ReturnType<typeof setInterval> | undefined;
|
||||
let direction: -1 | 1 = 1;
|
||||
let speed = 1;
|
||||
let oneSecond = 1000;
|
||||
$: isPlaying = !!interval;
|
||||
|
||||
const stop = () => {
|
||||
clearInterval(interval);
|
||||
interval = undefined;
|
||||
speed = 1;
|
||||
};
|
||||
|
||||
const play = () => start({ direction, speed });
|
||||
|
||||
const start = (params: { direction: 1 | -1; speed: number }) => {
|
||||
if (interval) clearInterval(interval);
|
||||
interval = setInterval(() => {
|
||||
gotoNextDelta();
|
||||
}, oneSecond / params.speed);
|
||||
};
|
||||
|
||||
const gotoNextDelta = () => {
|
||||
if ($inputValue < $maxInput) {
|
||||
$inputValue += 1;
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
const gotoPrevDelta = () => {
|
||||
if ($inputValue > 0) {
|
||||
$inputValue -= 1;
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
const speedUp = () => {
|
||||
speed = speed * 2;
|
||||
start({ direction, speed });
|
||||
};
|
||||
|
||||
onMount(() =>
|
||||
unsubscribe(
|
||||
hotkeys.on('ArrowRight', () => gotoNextDelta()),
|
||||
hotkeys.on('ArrowLeft', () => gotoPrevDelta()),
|
||||
hotkeys.on('Space', () => {
|
||||
if (isPlaying) {
|
||||
stop();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
{#await frame.load()}
|
||||
{#await richSessions.load()}
|
||||
<div class="flex h-full flex-col items-center justify-center">
|
||||
<div
|
||||
class="loader border-gray-200 mb-4 h-12 w-12 rounded-full border-4 border-t-4 ease-linear"
|
||||
@ -206,15 +133,15 @@
|
||||
<h2 class="text-center text-2xl font-medium text-gray-500">Loading...</h2>
|
||||
</div>
|
||||
{:then}
|
||||
{#if !$frame}
|
||||
{#if !frame}
|
||||
<div class="mt-8 text-center">Select a playlist</div>
|
||||
{:else}
|
||||
<div id="code" class="flex-auto overflow-auto bg-[#1E2021]">
|
||||
<div class="pb-[200px]">
|
||||
<DeltasViewer
|
||||
doc={$frame.doc}
|
||||
deltas={$frame.deltas}
|
||||
filepath={$frame.filepath}
|
||||
doc={frame.doc}
|
||||
deltas={frame.deltas}
|
||||
filepath={frame.filepath}
|
||||
paddingLines={fullContext ? 100000 : context}
|
||||
/>
|
||||
</div>
|
||||
@ -225,7 +152,7 @@
|
||||
<Info
|
||||
projectId={$projectId}
|
||||
timestampMs={currentDelta.timestampMs}
|
||||
filename={$frame.filepath}
|
||||
filename={frame.filepath}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@ -241,83 +168,14 @@
|
||||
border: 0.5px solid rgba(63, 63, 70, 0.50);
|
||||
"
|
||||
>
|
||||
<Slider sessions={$richSessions} bookmarks={$bookmarks} bind:value={$inputValue} />
|
||||
<Slider sessions={$richSessions} bookmarks={$bookmarks} bind:value={$value} />
|
||||
|
||||
<div class="playback-controller-ui mx-auto flex w-full items-center justify-between gap-2">
|
||||
<div class="left-side flex space-x-8">
|
||||
<div class="play-button-button-container">
|
||||
{#if interval}
|
||||
<button
|
||||
class="player-button group fill-zinc-400 duration-300 ease-in-out hover:scale-125"
|
||||
on:click={stop}
|
||||
>
|
||||
<IconPlayerPauseFilled
|
||||
class="player-button-play icon-pointer h-6 w-6 fill-zinc-400 group-hover:fill-zinc-100 "
|
||||
/>
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="player-button group fill-zinc-400 duration-300 ease-in-out hover:scale-125"
|
||||
on:click={play}
|
||||
>
|
||||
<IconPlayerPlayFilled
|
||||
class="player-button-pause icon-pointer h-6 w-6 fill-zinc-400 group-hover:fill-zinc-100"
|
||||
/>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="back-forward-button-container ">
|
||||
<button
|
||||
on:click={gotoPrevDelta}
|
||||
class="player-button-back group duration-300 ease-in-out hover:scale-125"
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="icon-pointer h-6 w-6"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M13.7101 16.32C14.0948 16.7047 14.0955 17.3274 13.7117 17.7111C13.3254 18.0975 12.7053 18.094 12.3206 17.7093L5.37536 10.7641C5.18243 10.5711 5.0867 10.32 5.08703 10.069C5.08802 9.81734 5.18374 9.56621 5.37536 9.37458L12.3206 2.42932C12.7055 2.04445 13.328 2.04396 13.7117 2.42751C14.0981 2.81386 14.0946 3.43408 13.7101 3.81863C13.4234 4.10528 7.80387 9.78949 7.52438 10.069C9.59011 12.1474 11.637 14.2469 13.7101 16.32Z"
|
||||
fill="none"
|
||||
class="fill-zinc-400 group-hover:fill-zinc-100"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={gotoNextDelta}
|
||||
class="player-button-forward group duration-300 ease-in-out hover:scale-125"
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="icon-pointer h-6 w-6"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M6.28991 16.32C5.90521 16.7047 5.90455 17.3274 6.28826 17.7111C6.67461 18.0975 7.29466 18.094 7.67938 17.7093L14.6246 10.7641C14.8176 10.5711 14.9133 10.32 14.913 10.069C14.912 9.81734 14.8163 9.56621 14.6246 9.37458L7.67938 2.42932C7.29451 2.04445 6.67197 2.04396 6.28826 2.42751C5.90192 2.81386 5.90537 3.43408 6.28991 3.81863C6.57656 4.10528 12.1961 9.78949 12.4756 10.069C10.4099 12.1474 8.36301 14.2469 6.28991 16.32Z"
|
||||
fill="none"
|
||||
class="fill-zinc-400 group-hover:fill-zinc-100"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button on:click={speedUp}>{speed}x</button>
|
||||
</div>
|
||||
|
||||
<DiffContext bind:lines={context} bind:fullContext />
|
||||
</div>
|
||||
<Playback
|
||||
deltas={$richSessions.map(({ deltas }) => deltas)}
|
||||
bind:value={$value}
|
||||
bind:context
|
||||
bind:fullContext
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
|
@ -0,0 +1,148 @@
|
||||
<script lang="ts">
|
||||
import type { Delta } from '$lib/api';
|
||||
import { IconPlayerPauseFilled, IconPlayerPlayFilled } from '$lib/icons';
|
||||
import { DiffContext } from '$lib/components';
|
||||
import { unsubscribe } from '$lib/utils';
|
||||
import { onMount } from 'svelte';
|
||||
import { hotkeys } from '$lib';
|
||||
|
||||
export let value: number;
|
||||
export let context: number;
|
||||
export let fullContext: boolean;
|
||||
export let deltas: [string, Delta][][];
|
||||
|
||||
$: maxDeltaIndex = deltas.flatMap((d) => d).length - 1;
|
||||
|
||||
let interval: ReturnType<typeof setInterval> | undefined;
|
||||
let direction: -1 | 1 = 1;
|
||||
let speed = 1;
|
||||
let oneSecond = 1000;
|
||||
|
||||
$: isPlaying = !!interval;
|
||||
|
||||
const stop = () => {
|
||||
clearInterval(interval);
|
||||
interval = undefined;
|
||||
speed = 1;
|
||||
};
|
||||
|
||||
const play = () => start({ direction, speed });
|
||||
|
||||
const start = (params: { direction: 1 | -1; speed: number }) => {
|
||||
if (interval) clearInterval(interval);
|
||||
interval = setInterval(() => {
|
||||
gotoNextDelta();
|
||||
}, oneSecond / params.speed);
|
||||
};
|
||||
|
||||
const gotoNextDelta = () => {
|
||||
if (value < maxDeltaIndex) {
|
||||
value += 1;
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
const gotoPrevDelta = () => {
|
||||
if (value > 0) {
|
||||
value -= 1;
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
const speedUp = () => {
|
||||
speed = speed * 2;
|
||||
start({ direction, speed });
|
||||
};
|
||||
|
||||
onMount(() =>
|
||||
unsubscribe(
|
||||
hotkeys.on('ArrowRight', () => gotoNextDelta()),
|
||||
hotkeys.on('ArrowLeft', () => gotoPrevDelta()),
|
||||
hotkeys.on('Space', () => {
|
||||
if (isPlaying) {
|
||||
stop();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="playback-controller-ui mx-auto flex w-full items-center justify-between gap-2">
|
||||
<div class="left-side flex space-x-8">
|
||||
<div class="play-button-button-container">
|
||||
{#if interval}
|
||||
<button
|
||||
class="player-button group fill-zinc-400 duration-300 ease-in-out hover:scale-125"
|
||||
on:click={stop}
|
||||
>
|
||||
<IconPlayerPauseFilled
|
||||
class="player-button-play icon-pointer h-6 w-6 fill-zinc-400 group-hover:fill-zinc-100 "
|
||||
/>
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="player-button group fill-zinc-400 duration-300 ease-in-out hover:scale-125"
|
||||
on:click={play}
|
||||
>
|
||||
<IconPlayerPlayFilled
|
||||
class="player-button-pause icon-pointer h-6 w-6 fill-zinc-400 group-hover:fill-zinc-100"
|
||||
/>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="back-forward-button-container ">
|
||||
<button
|
||||
on:click={gotoPrevDelta}
|
||||
class="player-button-back group duration-300 ease-in-out hover:scale-125"
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="icon-pointer h-6 w-6"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M13.7101 16.32C14.0948 16.7047 14.0955 17.3274 13.7117 17.7111C13.3254 18.0975 12.7053 18.094 12.3206 17.7093L5.37536 10.7641C5.18243 10.5711 5.0867 10.32 5.08703 10.069C5.08802 9.81734 5.18374 9.56621 5.37536 9.37458L12.3206 2.42932C12.7055 2.04445 13.328 2.04396 13.7117 2.42751C14.0981 2.81386 14.0946 3.43408 13.7101 3.81863C13.4234 4.10528 7.80387 9.78949 7.52438 10.069C9.59011 12.1474 11.637 14.2469 13.7101 16.32Z"
|
||||
fill="none"
|
||||
class="fill-zinc-400 group-hover:fill-zinc-100"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={gotoNextDelta}
|
||||
class="player-button-forward group duration-300 ease-in-out hover:scale-125"
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="icon-pointer h-6 w-6"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M6.28991 16.32C5.90521 16.7047 5.90455 17.3274 6.28826 17.7111C6.67461 18.0975 7.29466 18.094 7.67938 17.7093L14.6246 10.7641C14.8176 10.5711 14.9133 10.32 14.913 10.069C14.912 9.81734 14.8163 9.56621 14.6246 9.37458L7.67938 2.42932C7.29451 2.04445 6.67197 2.04396 6.28826 2.42751C5.90192 2.81386 5.90537 3.43408 6.28991 3.81863C6.57656 4.10528 12.1961 9.78949 12.4756 10.069C10.4099 12.1474 8.36301 14.2469 6.28991 16.32Z"
|
||||
fill="none"
|
||||
class="fill-zinc-400 group-hover:fill-zinc-100"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button on:click={speedUp}>{speed}x</button>
|
||||
</div>
|
||||
|
||||
<DiffContext bind:lines={context} bind:fullContext />
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user