mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-25 14:50:30 +08:00
This patch moves the revision list from the tab bar drop-down to a sidebar
on wide screens and above the current revision on narrow screens. This
way all revisions are visible at all times.
We also move and nest the revision reviews into that list so that one
can see whether there's anything actionable at a glance.
Other tweaks:
- added comment counts to revisions and reviews
- displayed unresolved comment thread counts per review
- swapped icons for accepted/rejected and neutral reviews to avoid clashing
with resolved/unresolved comment icons
- made label and assignee sections more compact with hover-triggered "+" buttons
- added author and status sections to the sidebar
- enabled patch state changes from the title bar
- made "Review revision" the primary action and "Checkout patch" secondary
- added "Draft" label to draft patches
- capitalized all badges for consistent UI text
153 lines
4.1 KiB
Svelte
153 lines
4.1 KiB
Svelte
<script lang="ts">
|
|
import type { FileDiff } from "@bindings/diff/FileDiff";
|
|
import type { CodeComments } from "./Diff.svelte";
|
|
|
|
import Diff from "@app/components/Diff.svelte";
|
|
import File from "@app/components/File.svelte";
|
|
import Icon from "@app/components/Icon.svelte";
|
|
import Path from "@app/components/Path.svelte";
|
|
|
|
interface Props {
|
|
expanded: boolean;
|
|
file: FileDiff;
|
|
head: string;
|
|
codeComments?: CodeComments;
|
|
}
|
|
|
|
const { expanded, file, head, codeComments }: Props = $props();
|
|
|
|
// Pass down only the comments that apply to the given diff.
|
|
function filterThreadsByFilePath() {
|
|
if (codeComments) {
|
|
if (
|
|
file.status === "added" ||
|
|
file.status === "deleted" ||
|
|
file.status === "modified"
|
|
) {
|
|
return {
|
|
...codeComments,
|
|
threads:
|
|
codeComments.threads.filter(t => {
|
|
return t.root.location?.path === file.path;
|
|
}) ?? [],
|
|
};
|
|
} else {
|
|
return { ...codeComments, threads: [] };
|
|
}
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
const commentsOfThisFile = $derived(filterThreadsByFilePath());
|
|
const resolvedThreads = $derived(
|
|
commentsOfThisFile
|
|
? commentsOfThisFile.threads.filter(t => {
|
|
return t.root.resolved === true;
|
|
}).length
|
|
: 0,
|
|
);
|
|
const unresolvedThreads = $derived(
|
|
commentsOfThisFile
|
|
? commentsOfThisFile.threads.filter(t => {
|
|
return t.root.resolved === false;
|
|
}).length
|
|
: 0,
|
|
);
|
|
</script>
|
|
|
|
<style>
|
|
.added {
|
|
color: var(--color-foreground-success);
|
|
background-color: var(--color-fill-diff-green-light);
|
|
}
|
|
.deleted {
|
|
color: var(--color-foreground-red);
|
|
background-color: var(--color-fill-diff-red-light);
|
|
}
|
|
.moved,
|
|
.copied {
|
|
color: var(--color-foreground-dim);
|
|
background: var(--color-fill-ghost);
|
|
}
|
|
.stats {
|
|
font-size: var(--font-size-tiny);
|
|
font-family: var(--font-family-monospace);
|
|
font-weight: var(--font-weight-semibold);
|
|
}
|
|
</style>
|
|
|
|
{#snippet emptyPlaceholder()}
|
|
<div class="global-flex" style:margin="1rem 0" style:justify-content="center">
|
|
<Icon name="none" />Empty file
|
|
</div>
|
|
{/snippet}
|
|
|
|
<File {expanded}>
|
|
{#snippet leftHeader()}
|
|
{#if file.status === "moved" || file.status === "copied"}
|
|
<span style="display: flex; align-items: center; flex-wrap: wrap;">
|
|
<Path fullPath={file.oldPath} />
|
|
<span style:padding="0 0.5rem">→</span>
|
|
<Path fullPath={file.newPath} />
|
|
</span>
|
|
{:else}
|
|
<Path fullPath={file.path} />
|
|
{/if}
|
|
|
|
{#if file.status === "added"}
|
|
<span class="global-counter added">Added</span>
|
|
{:else if file.status === "deleted"}
|
|
<span class="global-counter deleted">Deleted</span>
|
|
{:else if file.status === "moved"}
|
|
<span class="global-counter moved">Moved</span>
|
|
{:else if file.status === "copied"}
|
|
<span class="global-counter copied">Copied</span>
|
|
{/if}
|
|
{/snippet}
|
|
|
|
{#snippet rightHeader()}
|
|
{#if file.diff.type === "plain" && file.diff.hunks.length > 0}
|
|
<div class="stats">
|
|
<span style:color="var(--color-foreground-success)">
|
|
+{file.diff.stats.additions}
|
|
</span>
|
|
<span style:color="var(--color-foreground-red)">
|
|
-{file.diff.stats.deletions}
|
|
</span>
|
|
</div>
|
|
{/if}
|
|
{#if commentsOfThisFile && commentsOfThisFile.threads.length > 0}
|
|
{#if unresolvedThreads > 0}
|
|
<div class="global-flex">
|
|
<Icon name="comment-cross" />
|
|
{unresolvedThreads}
|
|
</div>
|
|
{/if}
|
|
{#if resolvedThreads > 0}
|
|
<div class="global-flex">
|
|
<Icon name="comment-checkmark" />
|
|
{resolvedThreads}
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
{/snippet}
|
|
|
|
{#if file.diff.type === "plain"}
|
|
{#if file.diff.hunks.length > 0}
|
|
<Diff {file} {head} codeComments={commentsOfThisFile} />
|
|
{:else}
|
|
{@render emptyPlaceholder()}
|
|
{/if}
|
|
{:else if file.diff.type === "binary"}
|
|
<div
|
|
class="global-flex"
|
|
style:margin="1rem 0"
|
|
style:justify-content="center">
|
|
<Icon name="binary" />Binary file
|
|
</div>
|
|
{:else}
|
|
{@render emptyPlaceholder()}
|
|
{/if}
|
|
</File>
|