Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"svelte-adapter-bun": "^1.0.1",
"svelte-check": "^4.3.3",
"tailwindcss": "^4.1.17",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.3",
"vite": "^7.2.2",
Expand Down Expand Up @@ -1037,6 +1038,8 @@

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

"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],

"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],

"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
Expand Down
2 changes: 2 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:all": "bun run check && bun run lint && vitest run",
"test": "vitest",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
Expand Down Expand Up @@ -40,6 +41,7 @@
"svelte-adapter-bun": "^1.0.1",
"svelte-check": "^4.3.3",
"tailwindcss": "^4.1.17",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.3",
"vite": "^7.2.2",
Expand Down
1 change: 1 addition & 0 deletions web/src/app.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@import "tailwindcss";

@import "tw-animate-css";
@plugin "@iconify/tailwind4" {
prefixes: octicon;
}
Expand Down
103 changes: 103 additions & 0 deletions web/src/lib/components/menu-bar/MenuBar.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<script lang="ts">
import { MultiFileDiffViewerState } from "$lib/diff-viewer.svelte";
import { Keybinds } from "$lib/keybinds.svelte";
import { Menubar, Button } from "bits-ui";

const viewer = MultiFileDiffViewerState.get();
</script>

{#snippet keybind(key: string)}
<span class="text-em-med">{Keybinds.getModifierKey()}+{key}</span>
{/snippet}

<Menubar.Root class="flex border-b leading-none">
<Menubar.Menu>
<Menubar.Trigger class="btn-ghost px-2 py-1 text-sm font-medium data-[state=open]:btn-ghost-hover">diffs.dev</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content class="z-20 border bg-neutral text-sm" align="start">
<Menubar.Item>
<Button.Root
href="https://github.com/PaperMC/diff-viewer"
class="flex items-center gap-2 btn-ghost px-2 py-1"
target="_blank"
rel="noopener noreferrer"
>
<span class="iconify size-4 shrink-0 octicon--mark-github-16" aria-hidden="true"></span>
GitHub Repository
</Button.Root>
</Menubar.Item>
<Menubar.Item>
<Button.Root
href="https://chromewebstore.google.com/detail/patch-roulette/feaaoepdocmiibjilhoahgldkaajfnhb"
class="flex items-center gap-2 btn-ghost px-2 py-1"
target="_blank"
rel="noopener noreferrer"
>
<span class="iconify size-4 shrink-0 octicon--download-16" aria-hidden="true"></span>
Chrome Extension
</Button.Root>
</Menubar.Item>
<Menubar.Item>
<Button.Root
href="https://addons.mozilla.org/en-US/firefox/addon/patch-roulette/"
class="flex items-center gap-2 btn-ghost px-2 py-1"
target="_blank"
rel="noopener noreferrer"
>
<span class="iconify size-4 shrink-0 octicon--download-16" aria-hidden="true"></span>
Firefox Add-on
</Button.Root>
</Menubar.Item>
<Menubar.Separator class="h-px w-full bg-edge" />
<Menubar.Item
class="flex justify-between gap-2 btn-ghost px-2 py-1 select-none"
onSelect={() => {
viewer.openSettingsDialog();
}}
>
Open Settings
{@render keybind(",")}
</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger class="btn-ghost px-2 py-1 text-sm data-[state=open]:btn-ghost-hover">File</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content class="z-20 border bg-neutral text-sm" align="start">
<Menubar.Item
class="flex justify-between gap-2 btn-ghost px-2 py-1 select-none"
onSelect={() => {
viewer.openOpenDiffDialog();
}}
>
Open
{@render keybind("O")}
</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger class="btn-ghost px-2 py-1 text-sm data-[state=open]:btn-ghost-hover">View</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content class="z-20 border bg-neutral text-sm" align="start">
<Menubar.Item
class="btn-ghost px-2 py-1 select-none"
onSelect={() => {
viewer.expandAll();
}}
>
Expand All
</Menubar.Item>
<Menubar.Item
class="btn-ghost px-2 py-1 select-none"
onSelect={() => {
viewer.collapseAll();
}}
>
Collapse All
</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
44 changes: 0 additions & 44 deletions web/src/lib/components/settings-popover/SettingsPopover.svelte

This file was deleted.

74 changes: 74 additions & 0 deletions web/src/lib/components/settings/SettingsDialog.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script module>
export { globalThemeSetting, sectionHeader };
</script>

<script lang="ts">
import SettingsGroup from "./SettingsGroup.svelte";
import { Label, Dialog } from "bits-ui";
import SimpleRadioGroup from "$lib/components/settings/SimpleRadioGroup.svelte";
import { GlobalOptions } from "$lib/global-options.svelte";
import { getGlobalTheme, setGlobalTheme } from "$lib/theme.svelte";
import LabeledCheckbox from "../LabeledCheckbox.svelte";

import ShikiThemeSelector from "./ShikiThemeSelector.svelte";
interface Props {
open?: boolean;
}

let { open = $bindable(false) }: Props = $props();

const globalOptions = GlobalOptions.get();
</script>

{#snippet sectionHeader(text: string)}
<div class="mt-4 font-semibold">{text}</div>
{/snippet}

{#snippet globalThemeSetting()}
<SettingsGroup title="Theme">
<div class="px-2 py-1">
<SimpleRadioGroup values={["light", "dark", "auto"]} bind:value={getGlobalTheme, setGlobalTheme} aria-label="Select theme" />
</div>
</SettingsGroup>
{/snippet}

<Dialog.Root bind:open>
<Dialog.Portal>
<Dialog.Overlay
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"
/>
<Dialog.Content
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%]"
>
<header class="flex shrink-0 flex-row items-center justify-between rounded-t-sm bg-neutral-2 p-4">
<Dialog.Title class="text-xl font-semibold">Settings</Dialog.Title>
<Dialog.Close title="Close dialog" class="flex size-6 items-center justify-center rounded-md btn-ghost text-primary">
<span class="iconify octicon--x-16" aria-hidden="true"></span>
</Dialog.Close>
</header>

<div class="space-y-4 p-4">
{@render globalThemeSetting()}
<SettingsGroup title="Syntax Highlighting">
<LabeledCheckbox labelText="Enable" bind:checked={globalOptions.syntaxHighlighting} />
<ShikiThemeSelector mode="light" bind:value={globalOptions.syntaxHighlightingThemeLight} />
<ShikiThemeSelector mode="dark" bind:value={globalOptions.syntaxHighlightingThemeDark} />
</SettingsGroup>
<SettingsGroup title="Misc.">
<LabeledCheckbox labelText="Concise nested diffs" bind:checked={globalOptions.omitPatchHeaderOnlyHunks} />
<LabeledCheckbox labelText="Word diffs" bind:checked={globalOptions.wordDiffs} />
<LabeledCheckbox labelText="Line wrapping" bind:checked={globalOptions.lineWrap} />
<div class="flex justify-between px-2 py-1">
<Label.Root id="sidebarLocationLabel" for="sidebarLocation">Sidebar location</Label.Root>
<SimpleRadioGroup
id="sidebarLocation"
aria-labelledby="sidebarLocationLabel"
values={["left", "right"]}
bind:value={globalOptions.sidebarLocation}
/>
</div>
</SettingsGroup>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
<script module>
</script>

<script lang="ts">
import { useId, Label } from "bits-ui";
import type { Snippet } from "svelte";
Expand All @@ -23,6 +20,6 @@
{/snippet}

<div id={groupId} aria-labelledby={labelId} class="flex flex-col" role="group">
<Label.Root id={labelId} for={groupId} class="px-2 pt-4 pb-1 font-semibold">{title}</Label.Root>
<Label.Root id={labelId} for={groupId} class="px-2 py-1 text-lg font-semibold">{title}</Label.Root>
{@render renderChildren()}
</div>
17 changes: 17 additions & 0 deletions web/src/lib/diff-viewer.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { VList } from "virtua/svelte";
import { Context, Debounced, watch } from "runed";
import { MediaQuery } from "svelte/reactivity";
import { ProgressBarState } from "$lib/components/progress-bar/index.svelte";
import { Keybinds } from "./keybinds.svelte";

export const GITHUB_URL_PARAM = "github_url";
export const PATCH_URL_PARAM = "patch_url";
Expand Down Expand Up @@ -234,11 +235,27 @@ export class MultiFileDiffViewerState {

// Transient state
sidebarCollapsed = $state(false);
openDiffDialogOpen = $state(false);
settingsDialogOpen = $state(false);
activeSearchResult: ActiveSearchResult | null = $state(null);

private constructor() {
// Make sure to revoke object URLs when the component is destroyed
onDestroy(() => this.clearImages());

const keybinds = new Keybinds();
keybinds.registerModifierBind("o", () => this.openOpenDiffDialog());
keybinds.registerModifierBind(",", () => this.openSettingsDialog());
}

openOpenDiffDialog() {
this.openDiffDialogOpen = true;
this.settingsDialogOpen = false;
}

openSettingsDialog() {
this.settingsDialogOpen = true;
this.openDiffDialogOpen = false;
}

filterFile(file: FileDetails): boolean {
Expand Down
31 changes: 31 additions & 0 deletions web/src/lib/keybinds.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { onMount } from "svelte";
import { on } from "svelte/events";

export class Keybinds {
private static readonly IS_MAC = typeof navigator !== "undefined" && navigator.userAgent.includes("Mac");

static getModifierKey() {
return Keybinds.IS_MAC ? "⌘" : "Ctrl";
}

private readonly binds = new Map<string, () => void>();

constructor() {
onMount(() => {
return on(document, "keydown", (event) => {
const modifierPressed = Keybinds.IS_MAC ? event.metaKey : event.ctrlKey;
if (modifierPressed) {
const bindAction = this.binds.get(event.key.toLowerCase());
if (bindAction) {
bindAction();
event.preventDefault();
}
}
});
});
}

registerModifierBind(key: string, action: () => void) {
this.binds.set(key.toLowerCase(), action);
}
}
Loading