mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-18 06:22:28 +03:00
Small refactor of history component
This commit is contained in:
parent
076ff9beed
commit
c699ad4dfa
@ -1,68 +1,3 @@
|
|||||||
<script lang="ts" context="module">
|
|
||||||
export type Trailer = {
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
export type Operation =
|
|
||||||
| 'CreateCommit'
|
|
||||||
| 'CreateBranch'
|
|
||||||
| 'SetBaseBranch'
|
|
||||||
| 'MergeUpstream'
|
|
||||||
| 'UpdateWorkspaceBase'
|
|
||||||
| 'MoveHunk'
|
|
||||||
| 'UpdateBranchName'
|
|
||||||
| 'UpdateBranchNotes'
|
|
||||||
| 'ReorderBranches'
|
|
||||||
| 'SelectDefaultVirtualBranch'
|
|
||||||
| 'UpdateBranchRemoteName'
|
|
||||||
| 'GenericBranchUpdate'
|
|
||||||
| 'DeleteBranch'
|
|
||||||
| 'ApplyBranch'
|
|
||||||
| 'DiscardHunk'
|
|
||||||
| 'DiscardFile'
|
|
||||||
| 'AmendCommit'
|
|
||||||
| 'UndoCommit'
|
|
||||||
| 'UnapplyBranch'
|
|
||||||
| 'CherryPick'
|
|
||||||
| 'SquashCommit'
|
|
||||||
| 'UpdateCommitMessage'
|
|
||||||
| 'MoveCommit'
|
|
||||||
| 'RestoreFromSnapshot'
|
|
||||||
| 'ReorderCommit'
|
|
||||||
| 'InsertBlankCommit'
|
|
||||||
| 'MoveCommitFile'
|
|
||||||
| 'FileChanges';
|
|
||||||
export type SnapshotDetails = {
|
|
||||||
title: string;
|
|
||||||
operation: Operation;
|
|
||||||
body: string | undefined;
|
|
||||||
trailers: Trailer[];
|
|
||||||
};
|
|
||||||
export type Snapshot = {
|
|
||||||
id: string;
|
|
||||||
linesAdded: number;
|
|
||||||
linesRemoved: number;
|
|
||||||
filesChanged: string[];
|
|
||||||
details: SnapshotDetails | undefined;
|
|
||||||
createdAt: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createdOnDay(dateNumber: number) {
|
|
||||||
const d = new Date(dateNumber);
|
|
||||||
const t = new Date();
|
|
||||||
return `${t.toDateString() == d.toDateString() ? 'Today' : d.toLocaleDateString('en-US', { weekday: 'short' })}, ${d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}`;
|
|
||||||
}
|
|
||||||
export type SnapshotDiff = {
|
|
||||||
binary: boolean;
|
|
||||||
hunks: RemoteHunk[];
|
|
||||||
newPath: string;
|
|
||||||
newSizeBytes: number;
|
|
||||||
oldPath: string;
|
|
||||||
oldSizeBytes: number;
|
|
||||||
skipped: boolean;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Button from './Button.svelte';
|
import Button from './Button.svelte';
|
||||||
import EmptyStatePlaceholder from './EmptyStatePlaceholder.svelte';
|
import EmptyStatePlaceholder from './EmptyStatePlaceholder.svelte';
|
||||||
@ -72,84 +7,29 @@
|
|||||||
import ScrollableContainer from './ScrollableContainer.svelte';
|
import ScrollableContainer from './ScrollableContainer.svelte';
|
||||||
import SnapshotCard from './SnapshotCard.svelte';
|
import SnapshotCard from './SnapshotCard.svelte';
|
||||||
import emptyFolderSvg from '$lib/assets/empty-state/empty-folder.svg?raw';
|
import emptyFolderSvg from '$lib/assets/empty-state/empty-folder.svg?raw';
|
||||||
import { invoke, listen } from '$lib/backend/ipc';
|
import { listen } from '$lib/backend/ipc';
|
||||||
|
import { Project } from '$lib/backend/projects';
|
||||||
import { clickOutside } from '$lib/clickOutside';
|
import { clickOutside } from '$lib/clickOutside';
|
||||||
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
import { HistoryService, createdOnDay } from '$lib/history/history';
|
||||||
import { getContext, getContextStoreBySymbol } from '$lib/utils/context';
|
import { persisted } from '$lib/persisted/persisted';
|
||||||
import { type RemoteHunk, RemoteFile } from '$lib/vbranches/types';
|
import { getContext } from '$lib/utils/context';
|
||||||
import { VirtualBranchService } from '$lib/vbranches/virtualBranch';
|
import * as hotkeys from '$lib/utils/hotkeys';
|
||||||
|
import { RemoteFile } from '$lib/vbranches/types';
|
||||||
import { plainToInstance } from 'class-transformer';
|
import { plainToInstance } from 'class-transformer';
|
||||||
import { onMount, onDestroy } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Snapshot, SnapshotDiff } from '$lib/history/types';
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
|
const project = getContext(Project);
|
||||||
|
const historyService = getContext(HistoryService);
|
||||||
|
const snapshots = historyService.snapshots;
|
||||||
|
|
||||||
|
const showHistoryView = persisted(false, 'showHistoryView');
|
||||||
|
const loading = historyService.loading;
|
||||||
|
|
||||||
export let projectId: string;
|
|
||||||
let currentFilePreview: RemoteFile | undefined = undefined;
|
let currentFilePreview: RemoteFile | undefined = undefined;
|
||||||
|
|
||||||
const userSettings = getContextStoreBySymbol<Settings, Writable<Settings>>(SETTINGS);
|
async function onLastInView() {
|
||||||
|
if (!$loading) await historyService.loadMore();
|
||||||
const vbranchService = getContext(VirtualBranchService);
|
|
||||||
|
|
||||||
let listElement: HTMLElement | undefined = undefined;
|
|
||||||
|
|
||||||
// TODO: Fires multiple times nad cause uninitialized variable error
|
|
||||||
vbranchService.activeBranches.subscribe(() => {
|
|
||||||
// whenever virtual branches change, we need to reload the snapshots
|
|
||||||
// TODO: if the list has results from more pages, merge into it?
|
|
||||||
listSnapshots(projectId)
|
|
||||||
.then((rsp) => {
|
|
||||||
snapshots = rsp;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error occurred while listing snapshots:', error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
let snapshots: Snapshot[] = [];
|
|
||||||
let isSnapshotsLoading = false;
|
|
||||||
|
|
||||||
async function listSnapshots(projectId: string, sha?: string) {
|
|
||||||
isSnapshotsLoading = true;
|
|
||||||
|
|
||||||
const resp = await invoke<Snapshot[]>('list_snapshots', {
|
|
||||||
projectId: projectId,
|
|
||||||
limit: 32,
|
|
||||||
sha: sha
|
|
||||||
});
|
|
||||||
|
|
||||||
isSnapshotsLoading = false;
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getSnapshotDiff(projectId: string, sha: string) {
|
|
||||||
const resp = await invoke<{ [key: string]: SnapshotDiff }>('snapshot_diff', {
|
|
||||||
projectId: projectId,
|
|
||||||
sha: sha
|
|
||||||
});
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function restoreSnapshot(projectId: string, sha: string) {
|
|
||||||
await invoke<string>('restore_snapshot', {
|
|
||||||
projectId: projectId,
|
|
||||||
sha: sha
|
|
||||||
});
|
|
||||||
|
|
||||||
await listSnapshots(projectId).then((rsp) => {
|
|
||||||
snapshots = rsp;
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: is there a better way to update all the state?
|
|
||||||
await goto(window.location.href, { replaceState: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function onLastInView() {
|
|
||||||
if (!listElement) return;
|
|
||||||
if (listElement.scrollTop + listElement.clientHeight >= listElement.scrollHeight) {
|
|
||||||
listSnapshots(projectId, snapshots[snapshots.length - 1].id).then((rsp) => {
|
|
||||||
snapshots = [...snapshots, ...rsp.slice(1)];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFilePreview(entry: Snapshot, path: string) {
|
function updateFilePreview(entry: Snapshot, path: string) {
|
||||||
@ -169,60 +49,35 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeView() {
|
|
||||||
userSettings.update((s) => ({
|
|
||||||
...s,
|
|
||||||
showHistoryView: false
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
if (listElement) listElement.addEventListener('scroll', onLastInView, true);
|
|
||||||
});
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const unsubscribe = listen<string>('menu://view/history/clicked', () => {
|
const unsubscribe = listen<string>('menu://view/history/clicked', () => {
|
||||||
userSettings.update((s) => ({
|
$showHistoryView = !$showHistoryView;
|
||||||
...s,
|
|
||||||
showHistoryView: !$userSettings.showHistoryView
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
// TODO: Refactor somehow
|
||||||
|
const unsubscribeHotkeys = hotkeys.on('$mod+Shift+H', () => {
|
||||||
|
$showHistoryView = !$showHistoryView;
|
||||||
|
});
|
||||||
|
|
||||||
|
return async () => {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
|
unsubscribeHotkeys();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
onDestroy(() => {
|
|
||||||
listElement?.removeEventListener('scroll', onLastInView, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// optimisation: don't fetch snapshots if the view is not visible
|
|
||||||
$: if (!$userSettings.showHistoryView) {
|
|
||||||
snapshots = [];
|
|
||||||
currentFilePreview = undefined;
|
|
||||||
selectedFile = undefined;
|
|
||||||
} else {
|
|
||||||
listSnapshots(projectId).then((rsp) => {
|
|
||||||
snapshots = rsp;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let snapshotFilesTempStore:
|
let snapshotFilesTempStore:
|
||||||
| { entryId: string; diffs: { [key: string]: SnapshotDiff } }
|
| { entryId: string; diffs: { [key: string]: SnapshotDiff } }
|
||||||
| undefined = undefined;
|
| undefined = undefined;
|
||||||
let selectedFile: { entryId: string; path: string } | undefined = undefined;
|
let selectedFile: { entryId: string; path: string } | undefined = undefined;
|
||||||
|
|
||||||
$: if (snapshotFilesTempStore) {
|
|
||||||
console.log(snapshotFilesTempStore);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $userSettings.showHistoryView}
|
{#if $showHistoryView}
|
||||||
<aside class="sideview-container" class:show-view={$userSettings.showHistoryView}>
|
<aside class="sideview-container" class:show-view={$showHistoryView}>
|
||||||
<div
|
<div
|
||||||
class="sideview-content-wrap"
|
class="sideview-content-wrap"
|
||||||
use:clickOutside={{
|
use:clickOutside={{
|
||||||
handler: () => {
|
handler: () => {
|
||||||
closeView();
|
$showHistoryView = false;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -252,13 +107,13 @@
|
|||||||
style="ghost"
|
style="ghost"
|
||||||
icon="cross"
|
icon="cross"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
closeView();
|
$showHistoryView = false;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- EMPTY STATE -->
|
<!-- EMPTY STATE -->
|
||||||
{#if snapshots.length == 0}
|
{#if $snapshots.length == 0}
|
||||||
<EmptyStatePlaceholder image={emptyFolderSvg}>
|
<EmptyStatePlaceholder image={emptyFolderSvg}>
|
||||||
<svelte:fragment slot="title">No snapshots yet</svelte:fragment>
|
<svelte:fragment slot="title">No snapshots yet</svelte:fragment>
|
||||||
<svelte:fragment slot="caption">
|
<svelte:fragment slot="caption">
|
||||||
@ -268,17 +123,17 @@
|
|||||||
</EmptyStatePlaceholder>
|
</EmptyStatePlaceholder>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if isSnapshotsLoading}
|
{#if $snapshots.length == 0 && $loading}
|
||||||
<FullviewLoading />
|
<FullviewLoading />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- SNAPSHOTS -->
|
<!-- SNAPSHOTS -->
|
||||||
{#if snapshots.length > 0 && !isSnapshotsLoading}
|
{#if $snapshots.length > 0}
|
||||||
<ScrollableContainer on:bottomReached={onLastInView}>
|
<ScrollableContainer on:bottomReached={onLastInView}>
|
||||||
<div class="container" bind:this={listElement}>
|
<div class="container">
|
||||||
<!-- SNAPSHOTS FEED -->
|
<!-- SNAPSHOTS FEED -->
|
||||||
{#each snapshots as entry, idx}
|
{#each $snapshots as entry, idx (entry.id)}
|
||||||
{#if idx === 0 || createdOnDay(entry.createdAt) != createdOnDay(snapshots[idx - 1].createdAt)}
|
{#if idx === 0 || createdOnDay(entry.createdAt) != createdOnDay($snapshots[idx - 1].createdAt)}
|
||||||
<div class="sideview__date-header">
|
<div class="sideview__date-header">
|
||||||
<h4 class="text-base-12 text-semibold">
|
<h4 class="text-base-12 text-semibold">
|
||||||
{createdOnDay(entry.createdAt)}
|
{createdOnDay(entry.createdAt)}
|
||||||
@ -291,7 +146,7 @@
|
|||||||
{entry}
|
{entry}
|
||||||
isCurrent={idx == 0}
|
isCurrent={idx == 0}
|
||||||
on:restoreClick={() => {
|
on:restoreClick={() => {
|
||||||
restoreSnapshot(projectId, entry.id);
|
historyService.restoreSnapshot(project.id, entry.id);
|
||||||
}}
|
}}
|
||||||
{selectedFile}
|
{selectedFile}
|
||||||
on:diffClick={async (filePath) => {
|
on:diffClick={async (filePath) => {
|
||||||
@ -302,7 +157,7 @@
|
|||||||
} else {
|
} else {
|
||||||
snapshotFilesTempStore = {
|
snapshotFilesTempStore = {
|
||||||
entryId: entry.id,
|
entryId: entry.id,
|
||||||
diffs: await getSnapshotDiff(projectId, entry.id)
|
diffs: await historyService.getSnapshotDiff(project.id, entry.id)
|
||||||
};
|
};
|
||||||
updateFilePreview(entry, path);
|
updateFilePreview(entry, path);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createdOnDay } from './History.svelte';
|
|
||||||
import Icon from './Icon.svelte';
|
import Icon from './Icon.svelte';
|
||||||
import SnapshotAttachment from './SnapshotAttachment.svelte';
|
import SnapshotAttachment from './SnapshotAttachment.svelte';
|
||||||
import Tag from './Tag.svelte';
|
import Tag from './Tag.svelte';
|
||||||
import { getVSIFileIcon } from '$lib/ext-icons';
|
import { getVSIFileIcon } from '$lib/ext-icons';
|
||||||
|
import { createdOnDay } from '$lib/history/history';
|
||||||
|
import { toHumanReadableTime } from '$lib/utils/time';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import type { Snapshot, SnapshotDetails } from '$lib/history/types';
|
||||||
import type iconsJson from '$lib/icons/icons.json';
|
import type iconsJson from '$lib/icons/icons.json';
|
||||||
import type { Snapshot, SnapshotDetails } from './History.svelte';
|
|
||||||
|
|
||||||
export let entry: Snapshot;
|
export let entry: Snapshot;
|
||||||
export let isCurrent: boolean = false;
|
export let isCurrent: boolean = false;
|
||||||
@ -23,13 +24,9 @@
|
|||||||
return `#${sha.slice(0, 7)}`;
|
return `#${sha.slice(0, 7)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createdAtTime(dateNumber: number) {
|
function createdOnDayAndTime(epoch: number) {
|
||||||
const d = new Date(dateNumber);
|
const date = new Date(epoch);
|
||||||
return d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
|
return `${createdOnDay(date)}, ${toHumanReadableTime(date)}`;
|
||||||
}
|
|
||||||
|
|
||||||
function createdOnDayAndTime(dateNumber: number) {
|
|
||||||
return `${createdOnDay(dateNumber)}, ${createdAtTime(dateNumber)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ restoreClick: void; diffClick: string }>();
|
const dispatch = createEventDispatcher<{ restoreClick: void; diffClick: string }>();
|
||||||
@ -160,7 +157,7 @@
|
|||||||
class:restored-snapshot={isRestoreSnapshot}
|
class:restored-snapshot={isRestoreSnapshot}
|
||||||
>
|
>
|
||||||
<span class="snapshot-time text-base-12">
|
<span class="snapshot-time text-base-12">
|
||||||
{createdAtTime(entry.createdAt)}
|
{toHumanReadableTime(entry.createdAt)}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="snapshot-line">
|
<div class="snapshot-line">
|
||||||
|
60
app/src/lib/history/history.ts
Normal file
60
app/src/lib/history/history.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { Snapshot, SnapshotDiff } from './types';
|
||||||
|
import { invoke } from '$lib/backend/ipc';
|
||||||
|
import { plainToInstance } from 'class-transformer';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export class HistoryService {
|
||||||
|
cursor: string | undefined = undefined;
|
||||||
|
|
||||||
|
snapshots = writable<Snapshot[]>([], (set) => {
|
||||||
|
this.loadSnapshots().then((x) => set(x));
|
||||||
|
return () => {
|
||||||
|
set([]);
|
||||||
|
this.cursor = undefined;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
loading = writable(false);
|
||||||
|
|
||||||
|
constructor(private projectId: string) {}
|
||||||
|
|
||||||
|
async loadSnapshots(after?: string) {
|
||||||
|
this.loading.set(true);
|
||||||
|
const resp = await invoke<Snapshot[]>('list_snapshots', {
|
||||||
|
projectId: this.projectId,
|
||||||
|
sha: after,
|
||||||
|
limit: 32
|
||||||
|
});
|
||||||
|
this.cursor = resp.length > 0 ? resp[resp.length - 1].id : undefined;
|
||||||
|
this.loading.set(false);
|
||||||
|
return plainToInstance(Snapshot, resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadMore() {
|
||||||
|
if (!this.cursor) throw new Error('Unable to load more without a cursor');
|
||||||
|
const more = await this.loadSnapshots(this.cursor);
|
||||||
|
this.snapshots.update((snapshots) => [...snapshots, ...more.slice(1)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSnapshotDiff(projectId: string, sha: string) {
|
||||||
|
const resp = await invoke<{ [key: string]: any }>('snapshot_diff', {
|
||||||
|
projectId: projectId,
|
||||||
|
sha: sha
|
||||||
|
});
|
||||||
|
return Object.entries(resp).reduce<{ [key: string]: SnapshotDiff }>((acc, [path, diff]) => {
|
||||||
|
acc[path] = plainToInstance(SnapshotDiff, diff);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async restoreSnapshot(projectId: string, sha: string) {
|
||||||
|
await invoke<string>('restore_snapshot', {
|
||||||
|
projectId: projectId,
|
||||||
|
sha: sha
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createdOnDay(d: Date) {
|
||||||
|
const t = new Date();
|
||||||
|
return `${t.toDateString() == d.toDateString() ? 'Today' : d.toLocaleDateString('en-US', { weekday: 'short' })}, ${d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}`;
|
||||||
|
}
|
66
app/src/lib/history/types.ts
Normal file
66
app/src/lib/history/types.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { RemoteHunk } from '$lib/vbranches/types';
|
||||||
|
import { Transform, Type } from 'class-transformer';
|
||||||
|
|
||||||
|
export type Operation =
|
||||||
|
| 'CreateCommit'
|
||||||
|
| 'CreateBranch'
|
||||||
|
| 'SetBaseBranch'
|
||||||
|
| 'MergeUpstream'
|
||||||
|
| 'UpdateWorkspaceBase'
|
||||||
|
| 'MoveHunk'
|
||||||
|
| 'UpdateBranchName'
|
||||||
|
| 'UpdateBranchNotes'
|
||||||
|
| 'ReorderBranches'
|
||||||
|
| 'SelectDefaultVirtualBranch'
|
||||||
|
| 'UpdateBranchRemoteName'
|
||||||
|
| 'GenericBranchUpdate'
|
||||||
|
| 'DeleteBranch'
|
||||||
|
| 'ApplyBranch'
|
||||||
|
| 'DiscardHunk'
|
||||||
|
| 'DiscardFile'
|
||||||
|
| 'AmendCommit'
|
||||||
|
| 'UndoCommit'
|
||||||
|
| 'UnapplyBranch'
|
||||||
|
| 'CherryPick'
|
||||||
|
| 'SquashCommit'
|
||||||
|
| 'UpdateCommitMessage'
|
||||||
|
| 'MoveCommit'
|
||||||
|
| 'RestoreFromSnapshot'
|
||||||
|
| 'ReorderCommit'
|
||||||
|
| 'InsertBlankCommit'
|
||||||
|
| 'MoveCommitFile'
|
||||||
|
| 'FileChanges';
|
||||||
|
|
||||||
|
export class Trailer {
|
||||||
|
key!: string;
|
||||||
|
value!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SnapshotDiff {
|
||||||
|
binary!: boolean;
|
||||||
|
@Type(() => RemoteHunk)
|
||||||
|
hunks!: RemoteHunk[];
|
||||||
|
newPath!: string;
|
||||||
|
newSizeBytes!: number;
|
||||||
|
oldPath!: string;
|
||||||
|
oldSizeBytes!: number;
|
||||||
|
skipped!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SnapshotDetails {
|
||||||
|
title!: string;
|
||||||
|
operation!: Operation;
|
||||||
|
body?: string | undefined;
|
||||||
|
@Type(() => Trailer)
|
||||||
|
trailers!: Trailer[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Snapshot {
|
||||||
|
id!: string;
|
||||||
|
linesAdded!: number;
|
||||||
|
linesRemoved!: number;
|
||||||
|
filesChanged!: string[];
|
||||||
|
details?: SnapshotDetails;
|
||||||
|
@Transform((obj) => new Date(obj.value * 1000))
|
||||||
|
createdAt!: Date;
|
||||||
|
}
|
@ -1,19 +1,19 @@
|
|||||||
export async function on(combo: string, callback: (event: KeyboardEvent) => void) {
|
import { tinykeys } from 'tinykeys';
|
||||||
|
|
||||||
|
export function on(combo: string, callback: (event: KeyboardEvent) => void) {
|
||||||
const comboContainsControlKeys =
|
const comboContainsControlKeys =
|
||||||
combo.includes('Meta') || combo.includes('Alt') || combo.includes('Ctrl');
|
combo.includes('Meta') || combo.includes('Alt') || combo.includes('Ctrl');
|
||||||
|
|
||||||
return await import('tinykeys').then(({ tinykeys }) =>
|
return tinykeys(window, {
|
||||||
tinykeys(window, {
|
[combo]: (event) => {
|
||||||
[combo]: (event) => {
|
const target = event.target as HTMLElement;
|
||||||
const target = event.target as HTMLElement;
|
const isInput = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA';
|
||||||
const isInput = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA';
|
if (isInput && !comboContainsControlKeys) return;
|
||||||
if (isInput && !comboContainsControlKeys) return;
|
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
callback(event);
|
callback(event);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
export function toHumanReadableTime(timestamp: number) {
|
export function toHumanReadableTime(d: Date) {
|
||||||
return new Date(timestamp).toLocaleTimeString('en-US', {
|
return d.toLocaleTimeString('en-US', {
|
||||||
hour: 'numeric',
|
hour: 'numeric',
|
||||||
minute: 'numeric'
|
minute: 'numeric'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toHumanReadableDate(timestamp: number) {
|
export function toHumanReadableDate(d: Date) {
|
||||||
return new Date(timestamp).toLocaleDateString('en-US', {
|
return d.toLocaleDateString('en-US', {
|
||||||
dateStyle: 'short'
|
dateStyle: 'short',
|
||||||
|
hour12: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -74,12 +74,6 @@
|
|||||||
// This prevent backspace from navigating back
|
// This prevent backspace from navigating back
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}),
|
}),
|
||||||
hotkeys.on('$mod+Shift+H', () => {
|
|
||||||
userSettings.update((s) => ({
|
|
||||||
...s,
|
|
||||||
showHistoryView: !$userSettings.showHistoryView
|
|
||||||
}));
|
|
||||||
}),
|
|
||||||
hotkeys.on('$mod+R', () => location.reload())
|
hotkeys.on('$mod+R', () => location.reload())
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
import NotOnGitButlerBranch from '$lib/components/NotOnGitButlerBranch.svelte';
|
import NotOnGitButlerBranch from '$lib/components/NotOnGitButlerBranch.svelte';
|
||||||
import ProblemLoadingRepo from '$lib/components/ProblemLoadingRepo.svelte';
|
import ProblemLoadingRepo from '$lib/components/ProblemLoadingRepo.svelte';
|
||||||
import ProjectSettingsMenuAction from '$lib/components/ProjectSettingsMenuAction.svelte';
|
import ProjectSettingsMenuAction from '$lib/components/ProjectSettingsMenuAction.svelte';
|
||||||
|
import { HistoryService } from '$lib/history/history';
|
||||||
import { BaseBranchService, NoDefaultTarget } from '$lib/vbranches/baseBranch';
|
import { BaseBranchService, NoDefaultTarget } from '$lib/vbranches/baseBranch';
|
||||||
import { BranchController } from '$lib/vbranches/branchController';
|
import { BranchController } from '$lib/vbranches/branchController';
|
||||||
import { BaseBranch } from '$lib/vbranches/types';
|
import { BaseBranch } from '$lib/vbranches/types';
|
||||||
@ -33,6 +34,7 @@
|
|||||||
$: projectError = projectService.error;
|
$: projectError = projectService.error;
|
||||||
// const userSettings = getContextStoreBySymbol<Settings>(SETTINGS);
|
// const userSettings = getContextStoreBySymbol<Settings>(SETTINGS);
|
||||||
|
|
||||||
|
$: setContext(HistoryService, data.historyService);
|
||||||
$: setContext(VirtualBranchService, vbranchService);
|
$: setContext(VirtualBranchService, vbranchService);
|
||||||
$: setContext(BranchController, branchController);
|
$: setContext(BranchController, branchController);
|
||||||
$: setContext(BranchService, branchService);
|
$: setContext(BranchService, branchService);
|
||||||
@ -80,9 +82,7 @@
|
|||||||
<div class="view-wrap" role="group" on:dragover|preventDefault>
|
<div class="view-wrap" role="group" on:dragover|preventDefault>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
<slot />
|
<slot />
|
||||||
<!-- {#if $userSettings.showHistoryView} -->
|
<History />
|
||||||
<History {projectId} />
|
|
||||||
<!-- {/if} -->
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { invoke } from '$lib/backend/ipc';
|
import { invoke } from '$lib/backend/ipc';
|
||||||
import { BranchService } from '$lib/branches/service';
|
import { BranchService } from '$lib/branches/service';
|
||||||
|
import { HistoryService } from '$lib/history/history';
|
||||||
import { getFetchNotifications } from '$lib/stores/fetches';
|
import { getFetchNotifications } from '$lib/stores/fetches';
|
||||||
import { getHeads } from '$lib/stores/head';
|
import { getHeads } from '$lib/stores/head';
|
||||||
import { RemoteBranchService } from '$lib/stores/remoteBranches';
|
import { RemoteBranchService } from '$lib/stores/remoteBranches';
|
||||||
@ -39,6 +40,7 @@ export async function load({ params, parent }) {
|
|||||||
const heads$ = getHeads(projectId);
|
const heads$ = getHeads(projectId);
|
||||||
const gbBranchActive$ = heads$.pipe(map((head) => head == 'gitbutler/integration'));
|
const gbBranchActive$ = heads$.pipe(map((head) => head == 'gitbutler/integration'));
|
||||||
|
|
||||||
|
const historyService = new HistoryService(projectId);
|
||||||
const baseBranchService = new BaseBranchService(projectId, remoteUrl$, fetches$, heads$);
|
const baseBranchService = new BaseBranchService(projectId, remoteUrl$, fetches$, heads$);
|
||||||
const vbranchService = new VirtualBranchService(projectId, gbBranchActive$);
|
const vbranchService = new VirtualBranchService(projectId, gbBranchActive$);
|
||||||
|
|
||||||
@ -67,6 +69,7 @@ export async function load({ params, parent }) {
|
|||||||
branchController,
|
branchController,
|
||||||
branchService,
|
branchService,
|
||||||
githubService,
|
githubService,
|
||||||
|
historyService,
|
||||||
projectId,
|
projectId,
|
||||||
project,
|
project,
|
||||||
remoteBranchService,
|
remoteBranchService,
|
||||||
|
Loading…
Reference in New Issue
Block a user