Skip to content

Commit cf1a32b

Browse files
authored
Add menu bar and keybinds (#44)
* Add menu bar * Remove unused vars/imports * Add keybinds * Fix cursor on menu items * Fix sidebar covering menu bar items * Adjust sidebar auto-close behavior * Shrink settings dialog
1 parent a6d2f80 commit cf1a32b

File tree

15 files changed

+508
-398
lines changed

15 files changed

+508
-398
lines changed

bun.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"svelte-adapter-bun": "^1.0.1",
4444
"svelte-check": "^4.3.3",
4545
"tailwindcss": "^4.1.17",
46+
"tw-animate-css": "^1.4.0",
4647
"typescript": "^5.9.3",
4748
"typescript-eslint": "^8.46.3",
4849
"vite": "^7.2.2",
@@ -1037,6 +1038,8 @@
10371038

10381039
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
10391040

1041+
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
1042+
10401043
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
10411044

10421045
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],

web/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"preview": "vite preview",
1111
"prepare": "svelte-kit sync || echo ''",
1212
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
13+
"check:all": "bun run check && bun run lint && vitest run",
1314
"test": "vitest",
1415
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
1516
"format": "prettier --write .",
@@ -40,6 +41,7 @@
4041
"svelte-adapter-bun": "^1.0.1",
4142
"svelte-check": "^4.3.3",
4243
"tailwindcss": "^4.1.17",
44+
"tw-animate-css": "^1.4.0",
4345
"typescript": "^5.9.3",
4446
"typescript-eslint": "^8.46.3",
4547
"vite": "^7.2.2",

web/src/app.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@import "tailwindcss";
22

3+
@import "tw-animate-css";
34
@plugin "@iconify/tailwind4" {
45
prefixes: octicon;
56
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<script lang="ts">
2+
import { MultiFileDiffViewerState } from "$lib/diff-viewer.svelte";
3+
import { Keybinds } from "$lib/keybinds.svelte";
4+
import { Menubar, Button } from "bits-ui";
5+
6+
const viewer = MultiFileDiffViewerState.get();
7+
</script>
8+
9+
{#snippet keybind(key: string)}
10+
<span class="text-em-med">{Keybinds.getModifierKey()}+{key}</span>
11+
{/snippet}
12+
13+
<Menubar.Root class="flex border-b leading-none">
14+
<Menubar.Menu>
15+
<Menubar.Trigger class="btn-ghost px-2 py-1 text-sm font-medium data-[state=open]:btn-ghost-hover">diffs.dev</Menubar.Trigger>
16+
<Menubar.Portal>
17+
<Menubar.Content class="z-20 border bg-neutral text-sm" align="start">
18+
<Menubar.Item>
19+
<Button.Root
20+
href="https://github.com/PaperMC/diff-viewer"
21+
class="flex items-center gap-2 btn-ghost px-2 py-1"
22+
target="_blank"
23+
rel="noopener noreferrer"
24+
>
25+
<span class="iconify size-4 shrink-0 octicon--mark-github-16" aria-hidden="true"></span>
26+
GitHub Repository
27+
</Button.Root>
28+
</Menubar.Item>
29+
<Menubar.Item>
30+
<Button.Root
31+
href="https://chromewebstore.google.com/detail/patch-roulette/feaaoepdocmiibjilhoahgldkaajfnhb"
32+
class="flex items-center gap-2 btn-ghost px-2 py-1"
33+
target="_blank"
34+
rel="noopener noreferrer"
35+
>
36+
<span class="iconify size-4 shrink-0 octicon--download-16" aria-hidden="true"></span>
37+
Chrome Extension
38+
</Button.Root>
39+
</Menubar.Item>
40+
<Menubar.Item>
41+
<Button.Root
42+
href="https://addons.mozilla.org/en-US/firefox/addon/patch-roulette/"
43+
class="flex items-center gap-2 btn-ghost px-2 py-1"
44+
target="_blank"
45+
rel="noopener noreferrer"
46+
>
47+
<span class="iconify size-4 shrink-0 octicon--download-16" aria-hidden="true"></span>
48+
Firefox Add-on
49+
</Button.Root>
50+
</Menubar.Item>
51+
<Menubar.Separator class="h-px w-full bg-edge" />
52+
<Menubar.Item
53+
class="flex justify-between gap-2 btn-ghost px-2 py-1 select-none"
54+
onSelect={() => {
55+
viewer.openSettingsDialog();
56+
}}
57+
>
58+
Open Settings
59+
{@render keybind(",")}
60+
</Menubar.Item>
61+
</Menubar.Content>
62+
</Menubar.Portal>
63+
</Menubar.Menu>
64+
<Menubar.Menu>
65+
<Menubar.Trigger class="btn-ghost px-2 py-1 text-sm data-[state=open]:btn-ghost-hover">File</Menubar.Trigger>
66+
<Menubar.Portal>
67+
<Menubar.Content class="z-20 border bg-neutral text-sm" align="start">
68+
<Menubar.Item
69+
class="flex justify-between gap-2 btn-ghost px-2 py-1 select-none"
70+
onSelect={() => {
71+
viewer.openOpenDiffDialog();
72+
}}
73+
>
74+
Open
75+
{@render keybind("O")}
76+
</Menubar.Item>
77+
</Menubar.Content>
78+
</Menubar.Portal>
79+
</Menubar.Menu>
80+
<Menubar.Menu>
81+
<Menubar.Trigger class="btn-ghost px-2 py-1 text-sm data-[state=open]:btn-ghost-hover">View</Menubar.Trigger>
82+
<Menubar.Portal>
83+
<Menubar.Content class="z-20 border bg-neutral text-sm" align="start">
84+
<Menubar.Item
85+
class="btn-ghost px-2 py-1 select-none"
86+
onSelect={() => {
87+
viewer.expandAll();
88+
}}
89+
>
90+
Expand All
91+
</Menubar.Item>
92+
<Menubar.Item
93+
class="btn-ghost px-2 py-1 select-none"
94+
onSelect={() => {
95+
viewer.collapseAll();
96+
}}
97+
>
98+
Collapse All
99+
</Menubar.Item>
100+
</Menubar.Content>
101+
</Menubar.Portal>
102+
</Menubar.Menu>
103+
</Menubar.Root>

web/src/lib/components/settings-popover/SettingsPopover.svelte

Lines changed: 0 additions & 44 deletions
This file was deleted.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<script module>
2+
export { globalThemeSetting, sectionHeader };
3+
</script>
4+
5+
<script lang="ts">
6+
import SettingsGroup from "./SettingsGroup.svelte";
7+
import { Label, Dialog } from "bits-ui";
8+
import SimpleRadioGroup from "$lib/components/settings/SimpleRadioGroup.svelte";
9+
import { GlobalOptions } from "$lib/global-options.svelte";
10+
import { getGlobalTheme, setGlobalTheme } from "$lib/theme.svelte";
11+
import LabeledCheckbox from "../LabeledCheckbox.svelte";
12+
13+
import ShikiThemeSelector from "./ShikiThemeSelector.svelte";
14+
interface Props {
15+
open?: boolean;
16+
}
17+
18+
let { open = $bindable(false) }: Props = $props();
19+
20+
const globalOptions = GlobalOptions.get();
21+
</script>
22+
23+
{#snippet sectionHeader(text: string)}
24+
<div class="mt-4 font-semibold">{text}</div>
25+
{/snippet}
26+
27+
{#snippet globalThemeSetting()}
28+
<SettingsGroup title="Theme">
29+
<div class="px-2 py-1">
30+
<SimpleRadioGroup values={["light", "dark", "auto"]} bind:value={getGlobalTheme, setGlobalTheme} aria-label="Select theme" />
31+
</div>
32+
</SettingsGroup>
33+
{/snippet}
34+
35+
<Dialog.Root bind:open>
36+
<Dialog.Portal>
37+
<Dialog.Overlay
38+
class="fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0"
39+
/>
40+
<Dialog.Content
41+
class="fixed top-1/2 left-1/2 z-50 flex max-h-svh w-md max-w-full -translate-x-1/2 -translate-y-1/2 flex-col rounded-sm border bg-neutral shadow-md data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-[95%]"
42+
>
43+
<header class="flex shrink-0 flex-row items-center justify-between rounded-t-sm bg-neutral-2 p-4">
44+
<Dialog.Title class="text-xl font-semibold">Settings</Dialog.Title>
45+
<Dialog.Close title="Close dialog" class="flex size-6 items-center justify-center rounded-md btn-ghost text-primary">
46+
<span class="iconify octicon--x-16" aria-hidden="true"></span>
47+
</Dialog.Close>
48+
</header>
49+
50+
<div class="space-y-4 p-4">
51+
{@render globalThemeSetting()}
52+
<SettingsGroup title="Syntax Highlighting">
53+
<LabeledCheckbox labelText="Enable" bind:checked={globalOptions.syntaxHighlighting} />
54+
<ShikiThemeSelector mode="light" bind:value={globalOptions.syntaxHighlightingThemeLight} />
55+
<ShikiThemeSelector mode="dark" bind:value={globalOptions.syntaxHighlightingThemeDark} />
56+
</SettingsGroup>
57+
<SettingsGroup title="Misc.">
58+
<LabeledCheckbox labelText="Concise nested diffs" bind:checked={globalOptions.omitPatchHeaderOnlyHunks} />
59+
<LabeledCheckbox labelText="Word diffs" bind:checked={globalOptions.wordDiffs} />
60+
<LabeledCheckbox labelText="Line wrapping" bind:checked={globalOptions.lineWrap} />
61+
<div class="flex justify-between px-2 py-1">
62+
<Label.Root id="sidebarLocationLabel" for="sidebarLocation">Sidebar location</Label.Root>
63+
<SimpleRadioGroup
64+
id="sidebarLocation"
65+
aria-labelledby="sidebarLocationLabel"
66+
values={["left", "right"]}
67+
bind:value={globalOptions.sidebarLocation}
68+
/>
69+
</div>
70+
</SettingsGroup>
71+
</div>
72+
</Dialog.Content>
73+
</Dialog.Portal>
74+
</Dialog.Root>

web/src/lib/components/settings-popover/SettingsPopoverGroup.svelte renamed to web/src/lib/components/settings/SettingsGroup.svelte

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
<script module>
2-
</script>
3-
41
<script lang="ts">
52
import { useId, Label } from "bits-ui";
63
import type { Snippet } from "svelte";
@@ -23,6 +20,6 @@
2320
{/snippet}
2421

2522
<div id={groupId} aria-labelledby={labelId} class="flex flex-col" role="group">
26-
<Label.Root id={labelId} for={groupId} class="px-2 pt-4 pb-1 font-semibold">{title}</Label.Root>
23+
<Label.Root id={labelId} for={groupId} class="px-2 py-1 text-lg font-semibold">{title}</Label.Root>
2724
{@render renderChildren()}
2825
</div>

web/src/lib/diff-viewer.svelte.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { VList } from "virtua/svelte";
1717
import { Context, Debounced, watch } from "runed";
1818
import { MediaQuery } from "svelte/reactivity";
1919
import { ProgressBarState } from "$lib/components/progress-bar/index.svelte";
20+
import { Keybinds } from "./keybinds.svelte";
2021

2122
export const GITHUB_URL_PARAM = "github_url";
2223
export const PATCH_URL_PARAM = "patch_url";
@@ -234,11 +235,27 @@ export class MultiFileDiffViewerState {
234235

235236
// Transient state
236237
sidebarCollapsed = $state(false);
238+
openDiffDialogOpen = $state(false);
239+
settingsDialogOpen = $state(false);
237240
activeSearchResult: ActiveSearchResult | null = $state(null);
238241

239242
private constructor() {
240243
// Make sure to revoke object URLs when the component is destroyed
241244
onDestroy(() => this.clearImages());
245+
246+
const keybinds = new Keybinds();
247+
keybinds.registerModifierBind("o", () => this.openOpenDiffDialog());
248+
keybinds.registerModifierBind(",", () => this.openSettingsDialog());
249+
}
250+
251+
openOpenDiffDialog() {
252+
this.openDiffDialogOpen = true;
253+
this.settingsDialogOpen = false;
254+
}
255+
256+
openSettingsDialog() {
257+
this.settingsDialogOpen = true;
258+
this.openDiffDialogOpen = false;
242259
}
243260

244261
filterFile(file: FileDetails): boolean {

0 commit comments

Comments
 (0)