History View UI (#3773)

* Separated History logic layout into components

* Layout update, components split and cover new data

* Added file preview

* handle event trigger

* empty states added

* Temp store for snapshot files + threshold section

* formatting

* added a gap between elements in footer

* CSS: padding fix
This commit is contained in:
Pavel Laptev 2024-05-17 01:21:19 +02:00 committed by GitHub
parent 800e608208
commit 1be1218a71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 1305 additions and 76 deletions

View File

@ -0,0 +1,9 @@
<svg width="120" height="100" viewBox="0 0 120 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.26" d="M22 70V30C22 24.4772 26.4772 20 32 20H50.412C53.8847 20 57.1086 21.8015 58.9286 24.759L61.0714 28.241C62.8914 31.1984 66.1153 33 69.588 33H88C93.5229 33 98 37.4772 98 43V70C98 75.5228 93.5229 80 88 80H32C26.4772 80 22 75.5228 22 70Z" fill="var(--clr-scale-ntrl-60)"/>
<rect opacity="0.32" x="37" y="48" width="47" height="5" rx="2.5" fill="var(--clr-scale-ntrl-60)"/>
<rect opacity="0.32" x="37" y="59" width="47" height="5" rx="2.5" fill="var(--clr-scale-ntrl-60)"/>
<circle opacity="0.32" cx="81" cy="23" r="3" fill="var(--clr-scale-ntrl-60)"/>
<circle opacity="0.32" cx="14" cy="55" r="2" fill="var(--clr-scale-ntrl-60)"/>
<circle opacity="0.32" cx="109" cy="53" r="2" fill="var(--clr-scale-ntrl-60)"/>
</svg>

After

Width:  |  Height:  |  Size: 838 B

View File

@ -30,6 +30,7 @@ export class Project {
ok_with_force_push!: boolean;
omit_certificate_check: boolean | undefined;
use_diff_context: boolean | undefined;
snapshot_lines_threshold!: number | undefined;
get vscodePath() {
return this.path.includes('\\') ? '/' + this.path.replace('\\', '/') : this.path;

View File

@ -13,7 +13,7 @@
<button
class="btn"
class:pop
on:mousedown={async () => await goto('/settings/')}
on:click={async () => await goto('/settings/')}
class:collapsed={isNavCollapsed}
>
{#if !isNavCollapsed}

View File

@ -5,6 +5,7 @@
import CommitDialog from './CommitDialog.svelte';
import CommitList from './CommitList.svelte';
import DropzoneOverlay from './DropzoneOverlay.svelte';
import EmptyStatePlaceholder from './EmptyStatePlaceholder.svelte';
import InfoMessage from './InfoMessage.svelte';
import PullRequestCard from './PullRequestCard.svelte';
import ScrollableContainer from './ScrollableContainer.svelte';
@ -255,30 +256,21 @@
{/if}
</div>
{:else if branch.commits.length == 0}
<div class="new-branch card" data-dnd-ignore>
<div class="new-branch__content">
<div class="new-branch__image">
{@html laneNewSvg}
</div>
<h2 class="new-branch__title text-base-body-15 text-semibold">
This is a new branch.
</h2>
<p class="new-branch__caption text-base-body-13">
<div class="new-branch card">
<EmptyStatePlaceholder image={laneNewSvg} width="11rem">
<svelte:fragment slot="title">This is a new branch</svelte:fragment>
<svelte:fragment slot="caption">
You can drag and drop files or parts of files here.
</p>
</div>
</svelte:fragment>
</EmptyStatePlaceholder>
</div>
{:else}
<!-- attention: these markers have custom css at the bottom of thise file -->
<div class="no-changes card" data-dnd-ignore>
<div class="new-branch__content">
<div class="new-branch__image">
{@html noChangesSvg}
</div>
<h2 class="new-branch__caption text-base-body-13">
No uncommitted changes<br />on this branch
</h2>
</div>
<EmptyStatePlaceholder image={noChangesSvg} width="11rem" hasBottomShift={false}>
<svelte:fragment slot="caption"
>No uncommitted changes on this branch</svelte:fragment
>
</EmptyStatePlaceholder>
</div>
{/if}
</div>
@ -353,14 +345,6 @@
padding: 0 var(--size-12) var(--size-12) var(--size-12);
}
.new-branch__content {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--size-8);
max-width: 14rem;
}
.new-branch,
.no-changes {
user-select: none;
@ -371,35 +355,10 @@
color: var(--clr-scale-ntrl-60);
background: var(--clr-bg-1);
justify-content: center;
padding: var(--size-48) 0;
border-radius: var(--radius-m);
cursor: default; /* was defaulting to text cursor */
}
.no-changes {
color: var(--clr-scale-ntrl-40);
text-align: center;
}
.new-branch__title {
color: var(--clr-scale-ntrl-40);
}
.new-branch__caption {
color: var(--clr-scale-ntrl-50);
opacity: 0.6;
}
.new-branch__caption,
.new-branch__title {
text-align: center;
}
.new-branch__image {
width: 7.5rem;
margin-bottom: var(--size-10);
}
/* hunks drop zone */
/* cherry pick drop zone */
/* move commit drop zone */

View File

@ -0,0 +1,83 @@
<script lang="ts">
export let image: string;
export let width: string = '18rem';
export let hasBottomShift: boolean = true;
const SLOTS = $$props.$$slots;
</script>
<div class="empty-state-container">
<div
class="empty-state"
style:max-width={width}
style:margin-bottom={hasBottomShift ? 'var(--size-48)' : '0'}
>
<div class="empty-state__image">
{@html image}
</div>
<div class="empty-state__content">
{#if SLOTS.title}
<h2 class="empty-state__title text-base-body-15 text-semibold">
<slot name="title" />
</h2>
{/if}
{#if SLOTS.caption}
<p class="empty-state__caption text-base-body-13">
<slot name="caption" />
</p>
{/if}
</div>
</div>
</div>
<style>
.empty-state-container {
display: flex;
flex-grow: 1;
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
}
.empty-state {
user-select: none;
display: flex;
flex-grow: 1;
flex-direction: column;
align-items: center;
color: var(--clr-scale-ntrl-60);
background: var(--clr-bg-1);
justify-content: center;
padding: var(--size-48) 0;
width: 100%;
gap: var(--size-16);
border-radius: var(--radius-m);
cursor: default; /* was defaulting to text cursor */
}
.empty-state__content {
display: flex;
flex-direction: column;
gap: var(--size-8);
}
.empty-state__title {
color: var(--clr-scale-ntrl-40);
}
.empty-state__caption {
color: var(--clr-scale-ntrl-50);
opacity: 0.6;
}
.empty-state__caption,
.empty-state__title {
text-align: center;
}
.empty-state__image {
width: 7.5rem;
}
</style>

View File

@ -12,6 +12,7 @@
export let isUnapplied: boolean;
export let selectable = false;
export let readonly = false;
export let isCard = true;
const branchController = getContext(BranchController);
@ -30,7 +31,7 @@
.some((section) => section.hunk.locked);
</script>
<div id={`file-${file.id}`} class="file-card card">
<div id={`file-${file.id}`} class="file-card" class:card={isCard}>
<FileCardHeader {file} {isFileLocked} on:close />
{#if conflicted}
<div class="mb-2 bg-red-500 px-2 py-0 font-bold text-white">

View File

@ -72,14 +72,15 @@
<style lang="postcss">
.header {
display: flex;
padding: var(--size-16);
padding: var(--size-10);
gap: var(--size-12);
border-bottom: 1px solid var(--clr-border-2);
}
.header__inner {
display: flex;
flex-grow: 1;
gap: var(--size-6);
gap: var(--size-8);
padding: var(--size-4);
overflow: hidden;
}
.header__info {

View File

@ -89,7 +89,7 @@
position: relative;
max-height: 100%;
flex-shrink: 0;
padding: var(--size-16);
padding: var(--size-14);
gap: var(--size-16);
}
.hunk-wrapper {

View File

@ -14,14 +14,24 @@
icon="mail"
style="ghost"
size="cta"
on:mousedown={() => events.emit('openSendIssueModal')}
help="Share feedback"
on:click={() => events.emit('openSendIssueModal')}
wide={isNavCollapsed}
/>
<Button
icon="settings"
style="ghost"
size="cta"
on:mousedown={async () => await goto(`/${projectId}/settings`)}
help="Project settings"
on:click={async () => await goto(`/${projectId}/settings`)}
wide={isNavCollapsed}
/>
<Button
icon="timeline"
style="ghost"
size="cta"
help="Project history"
on:click={() => events.emit('openHistory')}
wide={isNavCollapsed}
/>
</div>
@ -33,6 +43,7 @@
display: flex;
justify-content: space-between;
padding: var(--size-12);
gap: var(--size-6);
border-top: 1px solid var(--clr-border-2);
border-color: var(--clr-border-2);
}

View File

@ -50,7 +50,7 @@
projectId: projectId,
sha: sha
});
console.log(JSON.stringify(resp));
console.log(resp);
return resp;
}
async function restoreSnapshot(projectId: string, sha: string) {

View File

@ -0,0 +1,524 @@
<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">
import Button from './Button.svelte';
import EmptyStatePlaceholder from './EmptyStatePlaceholder.svelte';
import FileCard from './FileCard.svelte';
import FullviewLoading from './FullviewLoading.svelte';
import Icon from './Icon.svelte';
import ScrollableContainer from './ScrollableContainer.svelte';
import SnapshotCard from './SnapshotCard.svelte';
import emptyFolderSvg from '$lib/assets/empty-state/empty-folder.svg?raw';
import { invoke, listen } from '$lib/backend/ipc';
import { clickOutside } from '$lib/clickOutside';
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
import { getContext, getContextStoreBySymbol } from '$lib/utils/context';
import { type RemoteHunk, RemoteFile } from '$lib/vbranches/types';
import { VirtualBranchService } from '$lib/vbranches/virtualBranch';
import { plainToInstance } from 'class-transformer';
import { onMount, onDestroy } from 'svelte';
import type { Writable } from 'svelte/store';
import { goto } from '$app/navigation';
export let projectId: string;
let currentFilePreview: RemoteFile | undefined = undefined;
const userSettings = getContextStoreBySymbol<Settings, Writable<Settings>>(SETTINGS);
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) {
if (!snapshotFilesTempStore) return;
const file = snapshotFilesTempStore.diffs[path];
selectedFile = {
entryId: entry.id,
path: path
};
currentFilePreview = plainToInstance(RemoteFile, {
path: path,
hunks: file.hunks,
binary: file.binary
});
}
function closeView() {
userSettings.update((s) => ({
...s,
showHistoryView: false
}));
}
onMount(async () => {
if (listElement) listElement.addEventListener('scroll', onLastInView, true);
});
onMount(() => {
const unsubscribe = listen<string>('menu://view/history/clicked', () => {
userSettings.update((s) => ({
...s,
showHistoryView: !$userSettings.showHistoryView
}));
});
return () => {
unsubscribe();
};
});
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:
| { entryId: string; diffs: { [key: string]: SnapshotDiff } }
| undefined = undefined;
let selectedFile: { entryId: string; path: string } | undefined = undefined;
$: if (snapshotFilesTempStore) {
console.log(snapshotFilesTempStore);
}
</script>
{#if $userSettings.showHistoryView}
<aside class="sideview-container" class:show-view={$userSettings.showHistoryView}>
<div
class="sideview-content-wrap"
use:clickOutside={{
handler: () => {
closeView();
}
}}
>
{#if currentFilePreview}
<div class="file-preview" class:show-file-view={currentFilePreview}>
<FileCard
isCard={false}
conflicted={false}
file={currentFilePreview}
isUnapplied={false}
readonly={true}
on:close={() => {
currentFilePreview = undefined;
selectedFile = undefined;
}}
/>
</div>
{/if}
<div class="sideview">
<div class="sideview__header" data-tauri-drag-region>
<i class="clock-icon">
<div class="clock-pointers" />
</i>
<h3 class="sideview__header-title text-base-15 text-bold">Project history</h3>
<Button
style="ghost"
icon="cross"
on:click={() => {
closeView();
}}
/>
</div>
<!-- EMPTY STATE -->
{#if snapshots.length == 0}
<EmptyStatePlaceholder image={emptyFolderSvg}>
<svelte:fragment slot="title">No snapshots yet</svelte:fragment>
<svelte:fragment slot="caption">
Gitbutler saves your work, including file changes, so your progress is always secure.
Adjust snapshot settings in project settings.
</svelte:fragment>
</EmptyStatePlaceholder>
{/if}
{#if isSnapshotsLoading}
<FullviewLoading />
{/if}
<!-- SNAPSHOTS -->
{#if snapshots.length > 0 && !isSnapshotsLoading}
<ScrollableContainer on:bottomReached={onLastInView}>
<div class="container" bind:this={listElement}>
<!-- SNAPSHOTS FEED -->
{#each snapshots as entry, idx}
{#if idx === 0 || createdOnDay(entry.createdAt) != createdOnDay(snapshots[idx - 1].createdAt)}
<div class="sideview__date-header">
<h4 class="text-base-12 text-semibold">
{createdOnDay(entry.createdAt)}
</h4>
</div>
{/if}
{#if entry.details}
<SnapshotCard
{entry}
isCurrent={idx == 0}
on:restoreClick={() => {
restoreSnapshot(projectId, entry.id);
}}
{selectedFile}
on:diffClick={async (filePath) => {
const path = filePath.detail;
if (snapshotFilesTempStore?.entryId == entry.id) {
updateFilePreview(entry, path);
} else {
snapshotFilesTempStore = {
entryId: entry.id,
diffs: await getSnapshotDiff(projectId, entry.id)
};
updateFilePreview(entry, path);
}
}}
/>
{/if}
{/each}
<div class="welcome-point">
<div class="welcome-point__icon">
<Icon name="finish" />
</div>
<div class="welcome-point__content">
<p class="text-base-13 text-semibold">Welcome to history!</p>
<p class="welcome-point__caption text-base-body-12">
Gitbutler saves your work, including file changes, so your progress is always
secure. Adjust snapshot settings in project settings.
</p>
</div>
</div>
</div>
</ScrollableContainer>
{/if}
</div>
</div>
</aside>
{/if}
<!-- TODO: HANDLE LOADING STATE -->
<style lang="postcss">
.sideview-container {
z-index: var(--z-floating);
position: fixed;
top: 0;
right: 0;
display: flex;
justify-content: flex-end;
height: 100%;
width: 100%;
background-color: var(--clr-overlay-bg);
}
.sideview-content-wrap {
display: flex;
transform: translateX(100%);
}
.sideview {
position: relative;
z-index: var(--z-lifted);
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
background-color: var(--clr-bg-1);
border-left: 1px solid var(--clr-border-2);
width: 26rem;
}
/* SIDEVIEW HEADER */
.sideview__header {
display: flex;
align-items: center;
gap: var(--size-12);
padding: var(--size-10) var(--size-10) var(--size-10) var(--size-12);
border-bottom: 1px solid var(--clr-border-2);
}
.sideview__header-title {
pointer-events: none;
flex: 1;
}
.clock-icon {
pointer-events: none;
position: relative;
width: var(--size-20);
height: var(--size-20);
background-color: #ffcf88;
border-radius: var(--radius-s);
}
.clock-pointers {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 100%;
width: 0.125rem;
height: 0.125rem;
background-color: #000;
&::before,
&::after {
content: '';
position: absolute;
bottom: -0.125rem;
left: 50%;
transform: translate(-50%, -50%);
transform-origin: bottom;
width: 0.125rem;
height: calc(var(--size-12) / 2);
background-color: #000;
}
&::before {
transform: translate(-50%, -50%) rotate(120deg);
animation: minute-pointer 1s forwards;
}
&::after {
transform: translate(-50%, -50%) rotate(0deg);
animation: hour-pointer 1.5s forwards;
}
}
@keyframes minute-pointer {
0% {
transform: translate(-50%, -50%) rotate(120deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
@keyframes hour-pointer {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(90deg);
}
}
/* DATE HEADER */
.sideview__date-header {
padding: var(--size-14) var(--size-14) var(--size-8) 5.25rem;
border-top: 1px solid var(--clr-border-2);
background-color: var(--clr-bg-1);
& h4 {
color: var(--clr-text-3);
}
&:first-child {
border-top: none;
}
}
/* FILE PREVIEW */
.file-preview {
position: relative;
z-index: var(--z-ground);
display: flex;
flex-direction: column;
width: 32rem;
border-left: 1px solid var(--clr-border-2);
}
/* WELCOME POINT */
.welcome-point {
display: flex;
gap: var(--size-10);
padding: var(--size-12) var(--size-16) var(--size-16) 3.7rem;
}
.welcome-point__content {
display: flex;
flex-direction: column;
gap: var(--size-8);
margin-top: var(--size-4);
}
.welcome-point__caption {
color: var(--clr-text-3);
}
/* MODIFIERS */
.show-view {
animation: view-fade-in 0.2s forwards;
& .sideview-content-wrap {
animation: view-slide-in 0.25s cubic-bezier(0.23, 1, 0.32, 1) forwards;
animation-delay: 0.05s;
}
}
@keyframes view-fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes view-slide-in {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
.show-file-view {
animation: file-view-slide-in 0.25s cubic-bezier(0.23, 1, 0.32, 1) forwards;
}
@keyframes file-view-slide-in {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
</style>

View File

@ -1,6 +1,7 @@
<script lang="ts">
import SectionCard from './SectionCard.svelte';
import Spacer from './Spacer.svelte';
import TextBox from './TextBox.svelte';
import { Project, ProjectService } from '$lib/backend/projects';
import Toggle from '$lib/components/Toggle.svelte';
import { projectRunCommitHooks } from '$lib/config/config';
@ -9,6 +10,7 @@
const projectService = getContext(ProjectService);
const project = getContext(Project);
let snaphotLinesThreshold = project?.snapshot_lines_threshold;
let allowForcePushing = project?.ok_with_force_push;
let omitCertificateCheck = project?.omit_certificate_check;
@ -23,6 +25,12 @@
project.omit_certificate_check = !!value;
await projectService.updateProject(project);
}
async function setSnapshotLinesThreshold(value: number) {
project.snapshot_lines_threshold = value;
await projectService.updateProject(project);
console.log(value);
}
</script>
<section class="wrapper">
@ -65,6 +73,28 @@
<Toggle id="runHooks" bind:checked={$runCommitHooks} />
</svelte:fragment>
</SectionCard>
<SectionCard orientation="row" centerAlign>
<svelte:fragment slot="title">Snapshot lines threshold</svelte:fragment>
<svelte:fragment slot="caption">
The number of lines that trigger a snapshot when saving.
</svelte:fragment>
<svelte:fragment slot="actions">
<TextBox
type="number"
width={100}
textAlign="center"
value={snaphotLinesThreshold?.toString()}
minVal={5}
maxVal={1000}
showCountActions
on:change={(e) => {
setSnapshotLinesThreshold(parseInt(e.detail));
}}
/>
</svelte:fragment>
</SectionCard>
</section>
<Spacer />

View File

@ -93,6 +93,7 @@
isUnapplied={false}
readonly={true}
on:close={() => {
console.log(selected);
fileIdSelection.clear();
}}
/>

View File

@ -20,7 +20,7 @@
let observer: ResizeObserver;
const dispatch = createEventDispatcher<{ dragging: boolean }>();
const dispatch = createEventDispatcher<{ dragging: boolean; bottomReached: boolean }>();
onMount(() => {
observer = new ResizeObserver(() => {
@ -44,7 +44,12 @@
<div
bind:this={viewport}
on:scroll={(e) => {
scrolled = e.currentTarget.scrollTop != 0;
const target = e.currentTarget;
scrolled = target.scrollTop != 0;
if (target.scrollTop + target.clientHeight >= target.scrollHeight) {
dispatch('bottomReached', true);
}
}}
class="viewport hide-native-scrollbar"
style:height

View File

@ -0,0 +1,101 @@
<script lang="ts">
import Icon from './Icon.svelte';
import { onMount } from 'svelte';
export let foldable: boolean = false;
export let foldedAmount: number | undefined = undefined;
export let foldedHeight = '2.6rem';
let isOpen: boolean = false;
let el: HTMLElement;
let contentHeight: string;
function setHeight() {
contentHeight = `calc(${el.scrollHeight}px + var(--size-8)`;
}
onMount(() => {
if (!foldable) return;
setHeight();
});
$: if (el) {
setHeight();
}
</script>
<div class="snapshot-attachment">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
bind:this={el}
on:click={() => {
if (foldable && !isOpen) {
isOpen = true;
}
}}
class="snapshot-attachment__content"
style="max-height: {foldable ? (isOpen ? contentHeight : foldedHeight) : 'auto'}"
>
<slot />
</div>
{#if foldable}
<button
class="toggle-btn"
on:click={() => {
isOpen = !isOpen;
}}
>
<span class="text-base-11">{isOpen ? 'Fold files' : `Show ${foldedAmount} more`}</span>
<div class="toggle-btn__icon" style="transform: rotate({isOpen ? '180deg' : '0'})">
<Icon name="chevron-up-small" />
</div>
</button>
{/if}
</div>
<style lang="postcss">
.snapshot-attachment {
display: flex;
flex-direction: column;
background-color: var(--clr-bg-1);
border-radius: var(--radius-m);
border: 1px solid var(--clr-border-2);
width: 100%;
overflow: hidden;
}
.snapshot-attachment__content {
display: flex;
flex-direction: column;
height: 100%;
transition: max-height 0.2s ease;
}
.toggle-btn {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--size-8);
color: var(--clr-text-2);
background-color: var(--clr-bg-1);
border-top: 1px solid var(--clr-border-2);
border-radius: 0 0 var(--radius-m) var(--radius-m);
transition:
color var(--transition-fast),
background-color var(--transition-fast);
&:hover {
color: var(--clr-text-1);
background-color: var(--clr-bg-2);
}
}
.toggle-btn__icon {
display: flex;
align-items: center;
}
</style>

View File

@ -0,0 +1,427 @@
<script lang="ts">
import { createdOnDay } from './HistoryNew.svelte';
import Icon from './Icon.svelte';
import SnapshotAttachment from './SnapshotAttachment.svelte';
import Tag from './Tag.svelte';
import { getVSIFileIcon } from '$lib/ext-icons';
import { createEventDispatcher } from 'svelte';
import type iconsJson from '$lib/icons/icons.json';
import type { Snapshot, SnapshotDetails } from './HistoryNew.svelte';
export let entry: Snapshot;
export let isCurrent: boolean = false;
export let selectedFile:
| {
entryId: string;
path: string;
}
| undefined = undefined;
function getShortSha(sha: string | undefined) {
if (!sha) return '';
return `#${sha.slice(0, 7)}`;
}
function createdAtTime(dateNumber: number) {
const d = new Date(dateNumber);
return d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
}
function createdOnDayAndTime(dateNumber: number) {
return `${createdOnDay(dateNumber)}, ${createdAtTime(dateNumber)}`;
}
const dispatch = createEventDispatcher<{ restoreClick: void; diffClick: string }>();
function camelToTitleCase(str: string | undefined) {
if (!str) return '';
const lowerCaseStr = str.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase();
return lowerCaseStr.charAt(0).toUpperCase() + lowerCaseStr.slice(1);
}
function mapOperation(snapshotDetails: SnapshotDetails | undefined): {
text: string;
icon: keyof typeof iconsJson;
} {
if (!snapshotDetails) return { text: '', icon: 'commit' };
switch (snapshotDetails.operation) {
// Branch operations
case 'DeleteBranch':
return {
text: `Delete branch "${entry.details?.trailers.find((t) => t.key == 'name')?.value}"`,
icon: 'delete-branch'
};
case 'ApplyBranch':
return {
text: `Apply branch "${entry.details?.trailers.find((t) => t.key == 'name')?.value}"`,
icon: 'apply-branch'
};
case 'UpdateBranchName':
return {
text: `Renamed branch "${snapshotDetails.trailers.find((t) => t.key == 'before')?.value}" to "${snapshotDetails.trailers.find((t) => t.key == 'after')?.value}"`,
icon: 'update-branch-name'
};
case 'CreateBranch':
return {
text: `Create branch "${snapshotDetails.trailers.find((t) => t.key == 'name')?.value}"`,
icon: 'create-branch'
};
case 'UnapplyBranch':
return {
text: `Unapply branch "${snapshotDetails.trailers.find((t) => t.key == 'name')?.value}"`,
icon: 'unapply-branch'
};
case 'ReorderBranches':
return {
text: `Reorder branches "${snapshotDetails.trailers.find((t) => t.key == 'before')?.value}" and "${snapshotDetails.trailers.find((t) => t.key == 'after')?.value}"`,
icon: 'reorder-branches'
};
case 'SelectDefaultVirtualBranch':
return {
text: `Select default virtual branch "${snapshotDetails.trailers.find((t) => t.key == 'after')?.value}"`,
icon: 'set-default-branch'
};
case 'UpdateBranchRemoteName':
return {
text: `Update branch remote name "${snapshotDetails.trailers.find((t) => t.key == 'before')?.value}" to "${snapshotDetails.trailers.find((t) => t.key == 'after')?.value}"`,
icon: 'update-branch-name'
};
case 'SetBaseBranch':
return { text: 'Set base branch', icon: 'set-base-branch' };
case 'GenericBranchUpdate':
return { text: 'Generic branch update', icon: 'update-branch-name' };
// Commit operations
case 'CreateCommit':
return {
text: `Create commit ${getShortSha(entry.details?.trailers.find((t) => t.key == 'sha')?.value)}`,
icon: 'new-commit'
};
case 'AmendCommit':
return { text: 'Amend commit', icon: 'amend-commit' };
case 'UndoCommit':
return {
text: `Undo commit ${getShortSha(entry.details?.trailers.find((t) => t.key == 'sha')?.value)}`,
icon: 'undo-commit'
};
case 'SquashCommit':
return { text: 'Squash commit', icon: 'squash-commit' };
case 'UpdateCommitMessage':
return { text: 'Update commit message', icon: 'edit-text' };
case 'MoveCommit':
return { text: 'Move commit', icon: 'move-commit' };
case 'ReorderCommit':
return { text: 'Reorder commit', icon: 'move-commit' };
case 'InsertBlankCommit':
return { text: 'Insert blank commit', icon: 'blank-commit' };
case 'MoveCommitFile':
return { text: 'Move commit file', icon: 'move-commit-file-small' };
// File operations
case 'MoveHunk':
return {
text: `Move hunk to "${entry.details?.trailers.find((t) => t.key == 'name')?.value}"`,
icon: 'move-hunk'
};
case 'DiscardHunk':
return { text: 'Discard hunk', icon: 'discard-hunk' };
case 'DiscardFile':
return { text: 'Discard file', icon: 'discard-file-small' };
case 'FileChanges':
return { text: 'File changes', icon: 'file-changes-small' };
// Other operations
case 'MergeUpstream':
return { text: 'Merge upstream', icon: 'merged-pr-small' };
case 'UpdateWorkspaceBase':
return { text: 'Update workspace base', icon: 'rebase-small' };
case 'RestoreFromSnapshot':
return { text: 'Restore from snapshot', icon: 'empty' };
default:
return { text: snapshotDetails.operation, icon: 'commit' };
}
}
const isRestoreSnapshot = entry.details?.operation == 'RestoreFromSnapshot';
const operation = mapOperation(entry.details);
function isRestorable() {
return !isCurrent && !isRestoreSnapshot;
}
function getPathOnly(path: string) {
return path.split('/').slice(0, -1).join('/');
}
</script>
<div
class="snapshot-card"
class:restore-btn_visible={isRestorable()}
class:restored-snapshot={isRestoreSnapshot}
>
<span class="snapshot-time text-base-12">
{createdAtTime(entry.createdAt)}
</span>
<div class="snapshot-line">
{#if isRestoreSnapshot}
<img src="/images/history/restore-icon.svg" alt="" />
{:else}
<Icon name={operation.icon} />
{/if}
</div>
<div class="snapshot-content">
<div class="snapshot-details">
{#if isCurrent}
<Tag style="pop" kind="soft">Current</Tag>
{/if}
<div class="snapshot-title-wrap">
<h4 class="snapshot-title text-base-body-13 text-semibold">
<span>{operation.text}</span>
<span class="snapshot-sha text-base-body-12">{getShortSha(entry.id)}</span>
</h4>
{#if isRestorable()}
<div class="restore-btn">
<Tag
style="ghost"
kind="solid"
clickable
on:click={() => {
dispatch('restoreClick');
}}>Restore</Tag
>
</div>
{/if}
</div>
</div>
{#if entry.filesChanged.length > 0 && !isRestoreSnapshot}
<SnapshotAttachment
foldable={entry.filesChanged.length > 2}
foldedAmount={entry.filesChanged.length - 2}
>
<div class="files-attacment">
{#each entry.filesChanged as filePath}
<button
class="files-attacment__file"
class:file-selected={selectedFile?.path == filePath &&
selectedFile?.entryId == entry.id}
on:click={() => {
dispatch('diffClick', filePath);
// console.log('diffClick', filePath);
}}
>
<img
draggable="false"
class="files-attacment__file-icon"
src={getVSIFileIcon(filePath)}
alt=""
/>
<div class="text-base-12 files-attacment__file-path-and-name">
<span class="files-attacment__file-name">
{filePath.split('/').pop()}
</span>
<span class="files-attacment__file-path">
{getPathOnly(filePath)}
</span>
</div>
</button>
{/each}
</div>
</SnapshotAttachment>
{/if}
{#if isRestoreSnapshot}
<SnapshotAttachment>
<div class="restored-attacment">
<Icon name="commit" />
<div class="restored-attacment__content">
<h4 class="text-base-13 text-semibold">
{camelToTitleCase(
entry.details?.trailers.find((t) => t.key == 'restored_operation')?.value
)}
</h4>
<span class="restored-attacment__details text-base-12">
{getShortSha(entry.details?.trailers.find((t) => t.key == 'restored_from')?.value)}{createdOnDayAndTime(
parseInt(entry.details?.trailers.find((t) => t.key == 'restored_date')?.value || '')
)}
</span>
</div>
</div>
</SnapshotAttachment>
{/if}
</div>
</div>
<style lang="postcss">
/* SNAPSHOT CARD */
.snapshot-card {
position: relative;
display: flex;
gap: var(--size-10);
padding: var(--size-10) var(--size-14) var(--size-8) var(--size-14);
overflow: hidden;
background-color: var(--clr-bg-1);
transition: padding 0.2s;
}
.restore-btn_visible {
/* padding: var(--size-10) var(--size-14) var(--size-12) var(--size-14); */
&:hover {
& .restore-btn {
display: flex;
}
background-color: var(--clr-bg-2);
}
}
.restore-btn {
display: none;
/* padding: var(--size-8) var(--size-14); */
}
.snapshot-time {
color: var(--clr-text-2);
/* background-color: #ffcf887d; */
width: 2.15rem;
text-align: right;
line-height: 1.5;
/* margin-top: var(--size-2); */
}
.snapshot-line {
position: relative;
display: flex;
align-items: center;
flex-direction: column;
gap: var(--size-4);
/* margin-top: var(--size-2); */
/* background-color: rgba(0, 255, 255, 0.299); */
&::after {
position: absolute;
top: var(--size-20);
content: '';
height: calc(100% - var(--size-12));
min-height: var(--size-8);
width: 1px;
background-color: var(--clr-border-2);
}
}
/* CARD CONTENT */
.snapshot-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--size-6);
overflow: hidden;
}
.snapshot-details {
display: flex;
width: 100%;
flex-direction: column;
align-items: flex-start;
gap: var(--size-6);
min-height: var(--size-tag);
}
.snapshot-title-wrap {
display: flex;
gap: var(--size-6);
width: 100%;
}
.snapshot-title {
flex: 1;
}
.snapshot-sha {
white-space: nowrap;
color: var(--clr-text-3);
}
/* ATTACHMENT FILES */
.files-attacment {
display: flex;
flex-direction: column;
gap: var(--size-2);
padding: var(--size-4);
}
.files-attacment__file {
display: flex;
align-items: center;
gap: var(--size-6);
padding: var(--size-4);
&:not(.file-selected):hover {
background-color: var(--clr-bg-1-muted);
}
}
.file-selected {
background-color: var(--clr-scale-pop-80);
& .files-attacment__file-name {
opacity: 0.9;
}
}
.files-attacment__file-path-and-name {
display: flex;
gap: var(--size-6);
overflow: hidden;
}
.files-attacment__file-path {
color: var(--clr-text-1);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
opacity: 0.2;
}
.files-attacment__file-name {
color: var(--clr-text-1);
opacity: 0.6;
white-space: nowrap;
}
.files-attacment__file-icon {
width: var(--size-12);
}
/* ATTACHMENT RESTORE */
.restored-attacment {
display: flex;
padding: var(--size-12);
gap: var(--size-8);
}
.restored-attacment__content {
display: flex;
flex-direction: column;
gap: var(--size-6);
}
.restored-attacment__details {
color: var(--clr-text-2);
}
/* RESTORED */
.restored-snapshot {
background-color: var(--clr-bg-2);
}
</style>

View File

@ -103,5 +103,33 @@
"vscode": "M10.587 0.48643C10.8501 0.251559 11.2308 0.203518 11.544 0.365687L15.2909 2.30603C15.5729 2.45208 15.75 2.7432 15.75 3.06082V12.9392C15.75 13.2568 15.5729 13.5479 15.2909 13.694L11.544 15.6343C11.2308 15.7965 10.8501 15.7484 10.587 15.5136L5.51792 10.9877L2.37075 13.5777L0.00970483 10.4296L2.43933 8L0.00970459 5.57037L2.37075 2.42232L5.51792 5.01231L10.587 0.48643ZM11.2519 1.90364L5.55769 6.98768L2.62923 4.57768L1.99027 5.42962L4.56065 8L1.99027 10.5704L2.62923 11.4223L5.55769 9.01232L11.2519 14.0964L14.25 12.5438V3.4562L11.2519 1.90364ZM10.3879 6.62148L8.7656 8L10.3879 9.37852V6.62148ZM10.4875 4.56846C11.0396 4.09933 11.8879 4.49171 11.8879 5.2162V10.7838C11.8879 11.5083 11.0396 11.9007 10.4875 11.4315L6.44909 8L10.4875 4.56846Z",
"warning": "M6.25684 2.09894L0.676539 12.0195C-0.0733903 13.3527 0.890036 15 2.41969 15H13.5803C15.11 15 16.0734 13.3527 15.3235 12.0195L9.74315 2.09895C8.97854 0.739643 7.02145 0.739641 6.25684 2.09894ZM6.99999 8.50001C6.99999 9.05229 7.44771 9.50001 7.99999 9.50001C8.55228 9.50001 8.99999 9.05229 8.99999 8.50001V7.00001C8.99999 6.44772 8.55228 6.00001 7.99999 6.00001C7.44771 6.00001 6.99999 6.44772 6.99999 7.00001V8.50001ZM6.99999 11.5C6.99999 12.0523 7.44771 12.5 7.99999 12.5C8.55228 12.5 8.99999 12.0523 8.99999 11.5C8.99999 10.9477 8.55228 10.5 7.99999 10.5C7.44771 10.5 6.99999 10.9477 6.99999 11.5Z",
"warning-small": "M6.27245 3.96153L2.75452 9.99226C1.97675 11.3256 2.9385 13 4.48208 13H11.5179C13.0615 13 14.0233 11.3256 13.2455 9.99226L9.72756 3.96153C8.95581 2.63852 7.0442 2.63852 6.27245 3.96153ZM7 7.73077C7 8.28305 7.44772 8.73077 8 8.73077C8.55228 8.73077 9 8.28305 9 7.73077V7C9 6.44772 8.55228 6 8 6C7.44772 6 7 6.44772 7 7V7.73077ZM7 10.5769C7 11.0867 7.41328 11.5 7.92308 11.5H8.07692C8.58672 11.5 9 11.0867 9 10.5769C9 10.0671 8.58672 9.65385 8.07692 9.65385H7.92308C7.41328 9.65385 7 10.0671 7 10.5769Z",
"x": "M9.14163 7.08118L13.6089 2H12.5503L8.67137 6.41192L5.57328 2H2L6.68492 8.6716L2 14H3.05866L7.15491 9.34087L10.4267 14H14L9.14163 7.08118Z"
"x": "M9.14163 7.08118L13.6089 2H12.5503L8.67137 6.41192L5.57328 2H2L6.68492 8.6716L2 14H3.05866L7.15491 9.34087L10.4267 14H14L9.14163 7.08118Z",
"timeline": "M6.25 4V9.75H12V8.25H7.75V4H6.25Z M5 0.25C2.37665 0.25 0.25 2.37665 0.25 5V11C0.25 13.6234 2.37665 15.75 5 15.75H11C13.6234 15.75 15.75 13.6234 15.75 11V5C15.75 2.37665 13.6234 0.25 11 0.25H5ZM1.75 5C1.75 3.20507 3.20507 1.75 5 1.75H11C12.7949 1.75 14.25 3.20507 14.25 5V11C14.25 12.7949 12.7949 14.25 11 14.25H5C3.20507 14.25 1.75 12.7949 1.75 11V5Z",
"camera": "M6.18287 1.25C5.40153 1.25 4.71486 1.76796 4.50021 2.51924L4.0057 4.25H4C2.48122 4.25 1.25 5.48122 1.25 7V11C1.25 12.5188 2.48122 13.75 4 13.75H12C13.5188 13.75 14.75 12.5188 14.75 11L14.75 7C14.75 5.48122 13.5188 4.25 12 4.25H11.9943L11.4998 2.51924C11.2851 1.76796 10.5985 1.25 9.81713 1.25H6.18287ZM5.94249 2.93132C5.97316 2.82399 6.07125 2.75 6.18287 2.75H9.81713C9.92875 2.75 10.0268 2.82399 10.0575 2.93132L10.7074 5.20604L10.8628 5.75H11.4286H12C12.6904 5.75 13.25 6.30964 13.25 7L13.25 11C13.25 11.6904 12.6904 12.25 12 12.25H4C3.30965 12.25 2.75 11.6904 2.75 11L2.75 7C2.75 6.30964 3.30964 5.75 4 5.75H4.57143H5.13715L5.29257 5.20604L5.94249 2.93132ZM8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6Z",
"edit-text": "M9.20755 1.14691L13.177 5.11637L6.99725 11.2961H3.02779V7.32667L9.20755 1.14691ZM4.52779 7.94799L8.48789 3.98789L10.336 5.83603L6.37593 9.79614H4.52779V7.94799Z M13 13.25H2.99997V14.75H13V13.25Z",
"reorder-branches": "M5.00009 8L11.0001 9.5V8L5.00009 6.5V8Z M4.75 12.75H5.07501C5.42247 14.4617 6.93578 15.75 8.75 15.75H11.25C13.3211 15.75 15 14.0711 15 12V7C15 4.92893 13.3211 3.25 11.25 3.25H10.925C10.5775 1.53832 9.06422 0.25 7.25 0.25H4.75C2.67893 0.25 1 1.92893 1 4V9C1 11.0711 2.67893 12.75 4.75 12.75ZM4.75 1.75C3.50736 1.75 2.5 2.75736 2.5 4V9C2.5 10.2426 3.50736 11.25 4.75 11.25H5V10H6.5V12C6.5 13.2426 7.50736 14.25 8.75 14.25H11.25C12.4926 14.25 13.5 13.2426 13.5 12V7C13.5 5.75736 12.4926 4.75 11.25 4.75H11V6H9.5V4C9.5 2.75736 8.49264 1.75 7.25 1.75H4.75Z",
"new-commit": "M3.30888 7.25C3.66846 4.98301 5.63185 3.25 8 3.25C10.3682 3.25 12.3315 4.98301 12.6911 7.25H15V8.75H12.6911C12.3315 11.017 10.3682 12.75 8 12.75C5.63185 12.75 3.66846 11.017 3.30888 8.75H1V7.25H3.30888ZM8.75 11V8.75H11V7.25H8.75V5H7.25V7.25H5V8.75H7.25V11H8.75Z",
"amend-commit": "M8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10Z M3.30888 8.75H1V7.25H3.30888C3.66846 4.98301 5.63185 3.25 8 3.25C10.3682 3.25 12.3315 4.98301 12.6911 7.25H15V8.75H12.6911C12.3315 11.017 10.3682 12.75 8 12.75C5.63185 12.75 3.66846 11.017 3.30888 8.75ZM4.75 8C4.75 6.20507 6.20507 4.75 8 4.75C9.79493 4.75 11.25 6.20507 11.25 8C11.25 9.79493 9.79493 11.25 8 11.25C6.20507 11.25 4.75 9.79493 4.75 8Z",
"undo-commit": "M7.0738 3.3404C7.37396 3.28101 7.68373 3.25 8 3.25C8.31627 3.25 8.62604 3.28101 8.9262 3.3404L8.6351 4.81188C8.43029 4.77136 8.21801 4.75 8 4.75C7.78199 4.75 7.56971 4.77136 7.3649 4.81188L7.0738 3.3404Z M10.6397 4.05051C11.1574 4.39708 11.6029 4.84263 11.9495 5.3603L10.703 6.19478C10.4655 5.84004 10.16 5.53446 9.80522 5.29696L10.6397 4.05051Z M4.05051 5.3603C4.39708 4.84263 4.84263 4.39708 5.3603 4.05051L6.19478 5.29696C5.84004 5.53446 5.53446 5.84004 5.29696 6.19478L4.05051 5.3603Z M12.6596 7.0738C12.6712 7.13218 12.6816 7.19092 12.691 7.25H15V8.75H12.691C12.6816 8.80908 12.6712 8.86782 12.6596 8.9262L11.1881 8.6351C11.2286 8.43029 11.25 8.21801 11.25 8C11.25 7.78199 11.2286 7.56971 11.1881 7.3649L12.6596 7.0738Z M3.30899 7.25C3.31837 7.19092 3.32885 7.13218 3.3404 7.0738L4.81188 7.3649C4.77136 7.56971 4.75 7.78199 4.75 8C4.75 8.21801 4.77136 8.43029 4.81188 8.6351L3.3404 8.9262C3.32885 8.86782 3.31837 8.80908 3.30899 8.75H1V7.25H3.30899Z M11.9495 10.6397C11.6029 11.1574 11.1574 11.6029 10.6397 11.9495L9.80522 10.703C10.16 10.4655 10.4655 10.16 10.703 9.80522L11.9495 10.6397Z M5.3603 11.9495C4.84263 11.6029 4.39708 11.1574 4.05051 10.6397L5.29696 9.80522C5.53446 10.16 5.84004 10.4655 6.19478 10.703L5.3603 11.9495Z M8 12.75C7.68373 12.75 7.37396 12.719 7.0738 12.6596L7.3649 11.1881C7.56971 11.2286 7.78199 11.25 8 11.25C8.21801 11.25 8.43029 11.2286 8.6351 11.1881L8.9262 12.6596C8.62604 12.719 8.31627 12.75 8 12.75Z",
"cherry-pick": "M4.56064 6.00373C3.68737 6.09683 2.87422 6.41894 2.23861 6.97271C1.46214 7.64921 1 8.63043 1 9.81252C1 12.1383 2.79967 14.0908 5.10031 14.0908C6.22939 14.0908 7.2378 13.6205 7.97205 12.8666C8.70631 13.6205 9.71475 14.0908 10.8439 14.0908C13.1445 14.0908 14.9442 12.1383 14.9442 9.81257C14.9442 8.63048 14.482 7.64925 13.7056 6.97276C12.9406 6.30628 11.9185 5.97536 10.8439 5.97536C10.7714 5.97536 10.6992 5.97687 10.6273 5.97988C10.0382 5.10953 9.64356 3.72883 10.1223 2.22792L9.13231 1.30243C7.87245 1.79997 6.76283 2.45055 5.92148 3.39842C5.28584 4.11454 4.82794 4.97256 4.56064 6.00373ZM6.10054 6.07535C6.79748 6.21902 7.43969 6.51764 7.96201 6.97271C7.96538 6.97565 7.96874 6.9786 7.9721 6.98154C7.97545 6.97861 7.97879 6.97568 7.98215 6.97276C8.30643 6.69023 8.67692 6.46801 9.07803 6.30572C8.6399 5.47334 8.3595 4.43555 8.41471 3.30164C7.87081 3.61734 7.4144 3.97609 7.04331 4.39417C6.63877 4.84992 6.31747 5.39659 6.10054 6.07535ZM8.869 8.19521C9.08381 8.67475 9.20061 9.21825 9.20061 9.81252C9.20061 10.4381 9.0704 11.0367 8.83525 11.5776C9.31823 12.2016 10.0488 12.5908 10.8439 12.5908C12.2439 12.5908 13.4442 11.384 13.4442 9.81257C13.4442 9.04606 13.1563 8.48368 12.7202 8.10372C12.2726 7.71375 11.6196 7.47536 10.8439 7.47536C10.0681 7.47536 9.4151 7.71375 8.9675 8.10372C8.93376 8.13312 8.9009 8.16362 8.869 8.19521ZM2.5 9.81252C2.5 9.04602 2.78785 8.48364 3.22396 8.10368C3.67156 7.7137 4.32459 7.47531 5.10031 7.47531C5.87603 7.47531 6.52905 7.7137 6.97666 8.10368C7.41277 8.48364 7.70061 9.04602 7.70061 9.81252C7.70061 11.3839 6.50032 12.5908 5.10031 12.5908C3.70029 12.5908 2.5 11.3839 2.5 9.81252Z",
"squash-commit": "M6.42166 3.58742C7.29858 2.80845 8.42986 2.3457 9.56214 2.67809C10.6944 3.01048 11.396 4.0113 11.7128 5.14067C11.8922 5.78056 11.9621 6.49861 11.9173 7.25H15V8.75H11.6818C11.6559 8.85201 11.628 8.95409 11.598 9.05614C11.1917 10.4404 10.4643 11.6251 9.57809 12.4123C8.70118 13.1913 7.56989 13.654 6.43762 13.3216C5.30534 12.9892 4.60371 11.9884 4.287 10.8591C4.10758 10.2192 4.03767 9.50127 4.08247 8.74997H1V7.24997H4.31793C4.34381 7.14788 4.37173 7.04571 4.40171 6.94358C4.80807 5.55934 5.53549 4.37461 6.42166 3.58742ZM7.41784 4.70886C6.77322 5.28147 6.18076 6.20865 5.84098 7.3661C5.5012 8.52354 5.49848 9.62384 5.73129 10.454C5.96744 11.2962 6.40263 11.7481 6.86013 11.8824C7.31762 12.0167 7.92804 11.8717 8.58191 11.2909C9.22653 10.7182 9.81899 9.79108 10.1588 8.63363C10.4986 7.47618 10.5013 6.37588 10.2685 5.54569C10.0323 4.70357 9.59712 4.25166 9.13963 4.11735C8.68213 3.98305 8.07172 4.12801 7.41784 4.70886Z",
"move-commit": "M8.33206 8.89026L11.9575 4.10941L10.7623 3.20306L7.13685 7.98391L8.33206 8.89026Z M7.67717 4.78605C6.83975 4.88147 6.04217 5.29921 5.48322 6.01511C4.37862 7.42989 4.63006 9.47225 6.04484 10.5769C7.45962 11.6815 9.50199 11.43 10.6066 10.0152C11.0472 9.45091 11.2457 8.72414 11.25 7.99546L11.2544 7.24989H15V8.74989H12.6946C12.5854 9.5055 12.3069 10.2749 11.7889 10.9383C10.1745 13.0061 7.18949 13.3736 5.12173 11.7592C4.13202 10.9864 3.53183 9.89971 3.35205 8.74985H1V7.24985H3.35607C3.47985 6.48719 3.79185 5.744 4.30091 5.092C5.11758 4.04601 6.28644 3.43481 7.50734 3.29569L7.67717 4.78605Z",
"blank-commit": "M1 8.75H6V7.25H1V8.75Z M10 8.75H15V7.25H10V8.75Z",
"move-hunk": "M1 5C1 3.34315 2.34315 2 4 2H12C13.6569 2 15 3.34315 15 5V11C15 12.6569 13.6569 14 12 14H4C2.34315 14 1 12.6569 1 11V5ZM12 12.5H4C3.17157 12.5 2.5 11.8284 2.5 11V8.75H7.11127L5.30367 10.4543L6.33269 11.5457L10.0933 8L6.33269 4.45431L5.30367 5.54569L7.11127 7.25H2.5V5C2.5 4.17157 3.17157 3.5 4 3.5H12C12.8284 3.5 13.5 4.17157 13.5 5V11C13.5 11.8284 12.8284 12.5 12 12.5Z",
"discard-hunk": "M4.58398 6.62404L6.64792 8L4.58397 9.37596L5.41603 10.624L8 8.90139L10.584 10.624L11.416 9.37596L9.35208 8L11.416 6.62404L10.584 5.37596L8 7.09861L5.41603 5.37596L4.58398 6.62404Z M1 5C1 3.34315 2.34315 2 4 2H12C13.6569 2 15 3.34315 15 5V11C15 12.6569 13.6569 14 12 14H4C2.34315 14 1 12.6569 1 11V5ZM13.5 11C13.5 11.8284 12.8284 12.5 12 12.5H4C3.17157 12.5 2.5 11.8284 2.5 11V5C2.5 4.17157 3.17157 3.5 4 3.5H12C12.8284 3.5 13.5 4.17157 13.5 5V11Z",
"discard-file": "M10.2981 5.2981L8 7.59619L5.7019 5.2981L4.64124 6.35876L6.93934 8.65685L4.64124 10.955L5.7019 12.0156L8 9.71751L10.2981 12.0156L11.3588 10.955L9.06066 8.65685L11.3588 6.35876L10.2981 5.2981Z M1 3C1 1.34315 2.34315 0 4 0H8.75736C9.55301 0 10.3161 0.316071 10.8787 0.87868L14.1213 4.12132C14.6839 4.68393 15 5.44699 15 6.24264V13C15 14.6569 13.6569 16 12 16H4C2.34315 16 1 14.6569 1 13V3ZM13.5 13C13.5 13.8284 12.8284 14.5 12 14.5H4C3.17157 14.5 2.5 13.8284 2.5 13V3C2.5 2.17157 3.17157 1.5 4 1.5H8.75736C9.15518 1.5 9.53671 1.65804 9.81802 1.93934L13.0607 5.18198C13.342 5.46329 13.5 5.84482 13.5 6.24264V13Z",
"file-changes": "M4.57529 9.9476L8.78729 5.78477L11.0174 8.0412L6.80538 12.204L4.56212 12.1909L4.57529 9.9476Z M1 3C1 1.34315 2.34315 0 4 0H8.75736C9.55301 0 10.3161 0.316071 10.8787 0.87868L14.1213 4.12132C14.6839 4.68393 15 5.44699 15 6.24264V13C15 14.6569 13.6569 16 12 16H4C2.34315 16 1 14.6569 1 13V3ZM12 14.5H4C3.17157 14.5 2.5 13.8284 2.5 13V3C2.5 2.17157 3.17157 1.5 4 1.5H8.75736C9.15518 1.5 9.53671 1.65804 9.81802 1.93934L13.0607 5.18198C13.342 5.46329 13.5 5.84482 13.5 6.24264V13C13.5 13.8284 12.8284 14.5 12 14.5Z",
"chat": "M12 5.75H4V4.25H12V5.75Z M4 8.75H12V7.25H4V8.75Z M0.25 3C0.25 1.48122 1.48122 0.25 3 0.25H13C14.5188 0.25 15.75 1.48122 15.75 3V10.5C15.75 12.0188 14.5188 13.25 13 13.25H5.67425C5.61696 13.25 5.5614 13.2697 5.51688 13.3058L3.10164 15.2625C1.95758 16.1894 0.25 15.3752 0.25 13.9028V3ZM3 1.75C2.30964 1.75 1.75 2.30964 1.75 3V13.9028C1.75 14.1131 1.99394 14.2295 2.15738 14.0971L4.57261 12.1403C4.88426 11.8878 5.27317 11.75 5.67425 11.75H13C13.6904 11.75 14.25 11.1904 14.25 10.5V3C14.25 2.30964 13.6904 1.75 13 1.75H3Z",
"move-commit-file": "M4 14.5H12C12.8284 14.5 13.5 13.8284 13.5 13V6.24264C13.5 5.84482 13.342 5.46329 13.0607 5.18198L9.81802 1.93934C9.53671 1.65804 9.15518 1.5 8.75736 1.5H4C3.17157 1.5 2.5 2.17157 2.5 3V13C2.5 13.8284 3.17157 14.5 4 14.5ZM4 0C2.34315 0 1 1.34315 1 3V13C1 14.6569 2.34315 16 4 16H12C13.6569 16 15 14.6569 15 13V6.24264C15 5.44699 14.6839 4.68393 14.1213 4.12132L10.8787 0.87868C10.3161 0.316071 9.55301 0 8.75736 0H4Z M9 8H2M9 8L5.81818 5M9 8L5.81818 11",
"discard-file-small": "M9.64124 5.95495L8 7.59619L6.35876 5.95495L5.2981 7.01561L6.93934 8.65685L5.2981 10.2981L6.35876 11.3588L8 9.71751L9.64124 11.3588L10.7019 10.2981L9.06066 8.65685L10.7019 7.01561L9.64124 5.95495Z M3 5C3 3.34315 4.34315 2 6 2H8.14286C8.96389 2 9.74904 2.33649 10.3153 2.93103L12.1724 4.88103C12.7037 5.43886 13 6.17967 13 6.95V11C13 12.6569 11.6569 14 10 14H6C4.34315 14 3 12.6569 3 11V5ZM11.5 11C11.5 11.8284 10.8284 12.5 10 12.5H6C5.17157 12.5 4.5 11.8284 4.5 11V5C4.5 4.17157 5.17157 3.5 6 3.5H8.14286C8.55337 3.5 8.94595 3.66825 9.22906 3.96552L11.0862 5.91552C11.3518 6.19443 11.5 6.56484 11.5 6.95V11Z",
"file-changes-small": "M9.64124 5.95495L5.2981 10.2981L6.35876 11.3588L10.7019 7.01561L9.64124 5.95495Z M3 5C3 3.34315 4.34315 2 6 2H8.14286C8.96389 2 9.74904 2.33649 10.3153 2.93103L12.1724 4.88103C12.7037 5.43886 13 6.17967 13 6.95V11C13 12.6569 11.6569 14 10 14H6C4.34315 14 3 12.6569 3 11V5ZM10 12.5H6C5.17157 12.5 4.5 11.8284 4.5 11V5C4.5 4.17157 5.17157 3.5 6 3.5H8.14286C8.55337 3.5 8.94595 3.66825 9.22906 3.96552L11.0862 5.91552C11.3518 6.19443 11.5 6.56484 11.5 6.95V11C11.5 11.8284 10.8284 12.5 10 12.5Z",
"move-commit-file-small": "M3 5C3 3.34315 4.34315 2 6 2H8.14286C8.96389 2 9.74904 2.33649 10.3153 2.93103L12.1724 4.88103C12.7037 5.43886 13 6.17967 13 6.95V11C13 12.6569 11.6569 14 10 14H6C4.34315 14 3 12.6569 3 11V5ZM10 12.5H6C5.17157 12.5 4.5 11.8284 4.5 11V8.75H8.11127L6.30367 10.4543L7.33269 11.5457L11.0933 8L7.33269 4.45431L6.30367 5.54569L8.11127 7.25H4.5V5C4.5 4.17157 5.17157 3.5 6 3.5H8.14286C8.55337 3.5 8.94595 3.66825 9.22906 3.96552L11.0862 5.91552C11.3518 6.19443 11.5 6.56484 11.5 6.95V11C11.5 11.8284 10.8284 12.5 10 12.5Z",
"create-branch": "M8.75 11V8.75H11V7.25H8.75V5H7.25V7.25H5V8.75H7.25V11H8.75Z M2 3C2 1.34315 3.34315 0 5 0H11C12.6569 0 14 1.34315 14 3V13C14 14.6569 12.6569 16 11 16H5C3.34315 16 2 14.6569 2 13V3ZM11 14.5H5C4.17157 14.5 3.5 13.8284 3.5 13V3C3.5 2.17157 4.17157 1.5 5 1.5H11C11.8284 1.5 12.5 2.17157 12.5 3V13C12.5 13.8284 11.8284 14.5 11 14.5Z",
"set-base-branch": "M7.99997 4.48525L11.7574 8.24264L7.99997 12L4.24258 8.24264L7.99997 4.48525ZM7.99997 6.60657L9.63604 8.24264L7.99997 9.87871L6.3639 8.24264L7.99997 6.60657Z M2 3C2 1.34315 3.34315 0 5 0H11C12.6569 0 14 1.34315 14 3V13C14 14.6569 12.6569 16 11 16H5C3.34315 16 2 14.6569 2 13V3ZM11 14.5H5C4.17157 14.5 3.5 13.8284 3.5 13V3C3.5 2.17157 4.17157 1.5 5 1.5H11C11.8284 1.5 12.5 2.17157 12.5 3V13C12.5 13.8284 11.8284 14.5 11 14.5Z",
"update-branch-name": "M6.62404 11.416L10.624 5.41603L9.37596 4.58397L5.37596 10.584L6.62404 11.416Z M2 3C2 1.34315 3.34315 0 5 0H11C12.6569 0 14 1.34315 14 3V13C14 14.6569 12.6569 16 11 16H5C3.34315 16 2 14.6569 2 13V3ZM11 14.5H5C4.17157 14.5 3.5 13.8284 3.5 13V3C3.5 2.17157 4.17157 1.5 5 1.5H11C11.8284 1.5 12.5 2.17157 12.5 3V13C12.5 13.8284 11.8284 14.5 11 14.5Z",
"set-default-branch": "M8 9C8.55228 9 9 8.55229 9 8C9 7.44772 8.55228 7 8 7C7.44772 7 7 7.44772 7 8C7 8.55229 7.44772 9 8 9Z M8 12C10.2091 12 12 10.2091 12 8C12 5.79086 10.2091 4 8 4C5.79086 4 4 5.79086 4 8C4 10.2091 5.79086 12 8 12ZM8 10.5C9.38071 10.5 10.5 9.38071 10.5 8C10.5 6.61929 9.38071 5.5 8 5.5C6.61929 5.5 5.5 6.61929 5.5 8C5.5 9.38071 6.61929 10.5 8 10.5Z M5 0C3.34315 0 2 1.34315 2 3V13C2 14.6569 3.34315 16 5 16H11C12.6569 16 14 14.6569 14 13V3C14 1.34315 12.6569 0 11 0H5ZM11 14.5C11.8284 14.5 12.5 13.8284 12.5 13V3C12.5 2.17157 11.8284 1.5 11 1.5H5C4.17157 1.5 3.5 2.17157 3.5 3V13C3.5 13.8284 4.17157 14.5 5 14.5H11Z",
"delete-branch": "M5.90535 11.1553L8 9.06063L10.0947 11.1553L11.1553 10.0946L9.06066 7.99996L11.1553 5.90531L10.0947 4.84465L8 6.9393L5.90535 4.84465L4.84469 5.90531L6.93934 7.99996L4.84469 10.0946L5.90535 11.1553Z M2 3C2 1.34315 3.34315 0 5 0H11C12.6569 0 14 1.34315 14 3V13C14 14.6569 12.6569 16 11 16H5C3.34315 16 2 14.6569 2 13V3ZM11 14.5H5C4.17157 14.5 3.5 13.8284 3.5 13V3C3.5 2.17157 4.17157 1.5 5 1.5H11C11.8284 1.5 12.5 2.17157 12.5 3V13C12.5 13.8284 11.8284 14.5 11 14.5Z",
"apply-branch": "M11.5472 6.51296L10.4528 5.48704L7.25 8.90341L5.54715 7.08704L4.45285 8.11296L7.25 11.0966L11.5472 6.51296Z M2 3C2 1.34315 3.34315 0 5 0H11C12.6569 0 14 1.34315 14 3V13C14 14.6569 12.6569 16 11 16H5C3.34315 16 2 14.6569 2 13V3ZM11 14.5H5C4.17157 14.5 3.5 13.8284 3.5 13V3C3.5 2.17157 4.17157 1.5 5 1.5H11C11.8284 1.5 12.5 2.17157 12.5 3V13C12.5 13.8284 11.8284 14.5 11 14.5Z",
"unapply-branch": "M14 3.83333H12.5V3C12.5 2.79385 12.4592 2.601 12.3868 2.42622L13.7724 1.85164C13.919 2.20536 14 2.59323 14 3V3.83333Z M5.75 0V1.5H5C4.79385 1.5 4.601 1.54075 4.42622 1.61323L3.85164 0.227642C4.20536 0.0809585 4.59323 0 5 0H5.75Z M2.22764 1.85164C2.08096 2.20536 2 2.59323 2 3V3.83333H3.5V3C3.5 2.79385 3.54075 2.601 3.61323 2.42622L2.22764 1.85164Z M2 12.1667H3.5V13C3.5 13.2062 3.54075 13.399 3.61323 13.5738L2.22764 14.1484C2.08096 13.7946 2 13.4068 2 13V12.1667Z M10.25 16V14.5H11C11.2062 14.5 11.399 14.4592 11.5738 14.3868L12.1484 15.7724C11.7946 15.919 11.4068 16 11 16H10.25Z M14 5.5H12.5V7.16667H14V5.5Z M14 8.83333H12.5V10.5H14V8.83333Z M14 12.1667H12.5V13C12.5 13.2062 12.4592 13.399 12.3868 13.5738L13.7724 14.1484C13.919 13.7946 14 13.4068 14 13V12.1667Z M8.75 16V14.5H7.25V16H8.75Z M5.75 16V14.5H5C4.79385 14.5 4.601 14.4592 4.42622 14.3868L3.85164 15.7724C4.20536 15.919 4.59323 16 5 16H5.75Z M2 10.5H3.5V8.83333H2V10.5Z M2 7.16667H3.5V5.5H2V7.16667Z M7.25 0V1.5H8.75V0H7.25Z M10.25 0V1.5H11C11.2062 1.5 11.399 1.54075 11.5738 1.61323L12.1484 0.227641C11.7946 0.0809584 11.4068 0 11 0H10.25Z",
"finish": "M2 1.25H1.25V2V11V15H2.75V11.75H13.25V15H14.75V11V2V1.25H14H2ZM4.4 10.25H2.75V8L4.4 8V10.25ZM6.8 10.25H9.2V8H11.6V10.25H13.25V8H11.6V5H13.25V2.75H11.6V5H9.2V2.75H6.8V5H4.4V2.75H2.75V5L4.4 5V8H6.8V10.25ZM6.8 8V5H9.2V8H6.8Z"
}

View File

@ -3,8 +3,7 @@ import { createNanoEvents } from 'nanoevents';
type Events = {
goto: (path: string) => void;
openSendIssueModal: () => void;
openBookmarkModal: () => void;
createBookmark: (params: { projectId: string }) => void;
openHistory: () => void;
};
const events = createNanoEvents<Events>();

View File

@ -0,0 +1,23 @@
export function useStickyPinned(
element: HTMLElement,
callback: (isPinned: boolean, element: HTMLElement) => void
) {
const observer = new IntersectionObserver(
([entry]) => {
callback(entry.intersectionRatio < 1, element);
console.log('sticky pinned', element, entry.intersectionRatio);
},
{
threshold: [1]
}
);
observer.observe(element);
return {
destroy() {
observer.disconnect();
}
};
}

View File

@ -53,6 +53,12 @@
return unsubscribe(
events.on('goto', async (path: string) => await goto(path)),
events.on('openSendIssueModal', () => shareIssueModal?.show()),
events.on('openHistory', () => {
userSettings.update((s) => ({
...s,
showHistoryView: !$userSettings.showHistoryView
}));
}),
// Zoom using cmd +, - and =
hotkeys.on('$mod+Equal', () => (zoom = Math.min(zoom + 0.0625, 3))),

View File

@ -1,14 +1,15 @@
<script lang="ts">
import { Project } from '$lib/backend/projects';
import { BranchService } from '$lib/branches/service';
import History from '$lib/components/History.svelte';
// import History from '$lib/components/History.svelte';
import History from '$lib/components/HistoryNew.svelte';
import Navigation from '$lib/components/Navigation.svelte';
import NoBaseBranch from '$lib/components/NoBaseBranch.svelte';
import NotOnGitButlerBranch from '$lib/components/NotOnGitButlerBranch.svelte';
import ProblemLoadingRepo from '$lib/components/ProblemLoadingRepo.svelte';
import ProjectSettingsMenuAction from '$lib/components/ProjectSettingsMenuAction.svelte';
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
import { getContextStoreBySymbol } from '$lib/utils/context';
// import { SETTINGS, type Settings } from '$lib/settings/userSettings';
// import { getContextStoreBySymbol } from '$lib/utils/context';
import { BaseBranchService, NoDefaultTarget } from '$lib/vbranches/baseBranch';
import { BranchController } from '$lib/vbranches/branchController';
import { BaseBranch } from '$lib/vbranches/types';
@ -33,7 +34,7 @@
$: baseBranch = baseBranchService.base;
$: baseError = baseBranchService.error;
$: projectError = projectService.error;
const userSettings = getContextStoreBySymbol<Settings>(SETTINGS);
// const userSettings = getContextStoreBySymbol<Settings>(SETTINGS);
$: setContext(VirtualBranchService, vbranchService);
$: setContext(BranchController, branchController);
@ -82,9 +83,9 @@
<div class="view-wrap" role="group" on:dragover|preventDefault>
<Navigation />
<slot />
{#if $userSettings.showHistoryView}
<History {projectId} />
{/if}
<!-- {#if $userSettings.showHistoryView} -->
<History {projectId} />
<!-- {/if} -->
</div>
{/if}
{/key}

View File

@ -53,7 +53,7 @@ body {
width: 100vw;
overflow-y: hidden;
padding: 0;
color: var(--clr-scale-ntrl-0);
color: var(--clr-text-1);
background-color: var(--clr-bg-2);
/* optimise font rendering */

View File

@ -6,9 +6,12 @@
:root {
--clr-bg-1: var(--clr-core-ntrl-100);
--clr-bg-1-muted: var(--clr-core-ntrl-95);
--clr-bg-2: var(--clr-core-ntrl-95);
--clr-bg-2-muted: var(--clr-core-ntrl-90);
--clr-bg-3: var(--clr-core-ntrl-90);
--clr-border-2: var(--clr-core-ntrl-60);
--clr-bg-3-muted: var(--clr-core-ntrl-80);
--clr-border-1: var(--clr-core-ntrl-60);
--clr-border-2: var(--clr-core-ntrl-70);
--clr-border-3: var(--clr-core-ntrl-80);
--clr-commit-local: var(--clr-core-pop-50);
@ -219,9 +222,12 @@
:root.dark {
--clr-bg-1: var(--clr-core-ntrl-10);
--clr-bg-1-muted: var(--clr-core-ntrl-20);
--clr-bg-2: var(--clr-core-ntrl-5);
--clr-bg-2-muted: var(--clr-core-ntrl-10);
--clr-bg-3: var(--clr-core-ntrl-0);
--clr-border-2: var(--clr-core-ntrl-40);
--clr-bg-3-muted: var(--clr-core-ntrl-5);
--clr-border-1: var(--clr-core-ntrl-40);
--clr-border-2: var(--clr-core-ntrl-30);
--clr-border-3: var(--clr-core-ntrl-20);
--clr-commit-local: var(--clr-core-pop-50);

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 3V13C0 14.6569 1.34315 16 3 16H13C14.6569 16 16 14.6569 16 13V3C16 1.34315 14.6569 0 13 0H3C1.34315 0 0 1.34315 0 3Z" fill="#FFD585"/>
<path d="M8 13V5M8 5L4 8.85714M8 5L12 8.85714" stroke="black" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@ -114,6 +114,10 @@ pub fn build(_package_info: &PackageInfo) -> Menu {
}
}
view_menu = view_menu.add_item(
CustomMenuItem::new("view/history", "Project history").accelerator("CmdOrCtrl+Shift+H"),
);
#[cfg(any(debug_assertions, feature = "devtools"))]
{
view_menu = view_menu.add_item(CustomMenuItem::new("view/devtools", "Developer Tools"));
@ -182,6 +186,11 @@ pub fn handle_event<R: Runtime>(event: &WindowMenuEvent<R>) {
return;
}
if event.menu_item_id() == "view/history" {
emit(event.window(), "menu://view/history/clicked");
return;
}
'open_link: {
let result = match event.menu_item_id() {
"help/documentation" => open::that("https://docs.gitbutler.com"),