Extract buttons into own components

This commit is contained in:
Rūdolfs Ošiņš
2025-05-15 17:08:43 +02:00
committed by Sebastian Martinez
parent ace802118e
commit 7d977f22da
16 changed files with 580 additions and 444 deletions
+48
View File
@@ -0,0 +1,48 @@
<script lang="ts">
import { formatOid } from "@app/lib/utils";
import Border from "@app/components/Border.svelte";
import Button from "@app/components/Button.svelte";
import Command from "@app/components/Command.svelte";
import Icon from "@app/components/Icon.svelte";
import Popover from "@app/components/Popover.svelte";
interface Props {
patchId: string;
selectedRevisionId: string;
tab: "patch" | "revisions" | "timeline";
}
const { patchId, selectedRevisionId, tab }: Props = $props();
const checkoutCommand = $derived.by(() => {
if (tab === "revisions" && selectedRevisionId !== patchId) {
return `rad patch checkout ${formatOid(patchId)} --revision ${formatOid(selectedRevisionId)}`;
} else {
return `rad patch checkout ${formatOid(patchId)}`;
}
});
</script>
<Popover popoverPositionRight="0" popoverPositionTop="3rem">
{#snippet toggle(onclick)}
<Button styleHeight="2.5rem" variant="secondary" {onclick}>
<Icon name="checkout" />Checkout patch<Icon name="chevron-down" />
</Button>
{/snippet}
{#snippet popover()}
<Border
styleAlignItems="flex-start"
styleBackgroundColor="var(--color-background-float)"
styleFlexDirection="column"
styleGap="0.5rem"
stylePadding="1rem"
styleWidth="max-content"
variant="ghost">
<span class="txt-small">
To checkout this patch in your working copy, run:
</span>
<Command command={checkoutCommand} styleWidth="100%" />
</Border>
{/snippet}
</Popover>
+37
View File
@@ -0,0 +1,37 @@
<script lang="ts">
import Border from "@app/components/Border.svelte";
import Button from "@app/components/Button.svelte";
import Command from "@app/components/Command.svelte";
import Icon from "@app/components/Icon.svelte";
import Popover from "@app/components/Popover.svelte";
interface Props {
rid: string;
}
const { rid }: Props = $props();
</script>
<Popover popoverPositionRight="0" popoverPositionTop="3rem">
{#snippet toggle(onclick)}
<Button styleHeight="2.5rem" variant="secondary" {onclick}>
<Icon name="checkout" />Checkout repo<Icon name="chevron-down" />
</Button>
{/snippet}
{#snippet popover()}
<Border
styleAlignItems="flex-start"
styleBackgroundColor="var(--color-background-float)"
styleFlexDirection="column"
styleGap="0.5rem"
stylePadding="1rem"
styleWidth="max-content"
variant="ghost">
<span class="txt-small">
To checkout a working copy of this repo, run:
</span>
<Command command={`rad checkout ${rid}`} styleWidth="100%" />
</Border>
{/snippet}
</Popover>
+1 -1
View File
@@ -21,7 +21,7 @@
}
</style>
<div class="cmd txt-monospace" style:width={styleWidth}>
<div class="cmd txt-monospace txt-small" style:width={styleWidth}>
<Border
hoverable
onclick={() => clipboard.copy()}
+9 -140
View File
@@ -1,29 +1,21 @@
<script lang="ts">
import type { Snippet } from "svelte";
import type { Config } from "@bindings/config/Config";
import type { Snippet } from "svelte";
import { boolean } from "zod";
import { onMount } from "svelte";
import * as router from "@app/lib/router";
import useLocalStorage from "@app/lib/useLocalStorage.svelte";
import {
checkRadicleCLI,
radicleInstalled,
} from "@app/lib/checkRadicleCLI.svelte";
import { checkRadicleCLI } from "@app/lib/checkRadicleCLI.svelte";
import { dynamicInterval } from "@app/lib/interval";
import { nodeRunning } from "@app/lib/events";
import { didFromPublicKey, truncateDid } from "@app/lib/utils";
import { setFocused } from "@app/components/Popover.svelte";
import Avatar from "@app/components/Avatar.svelte";
import Border from "@app/components/Border.svelte";
import Command from "@app/components/Command.svelte";
import CopyableId from "@app/components/CopyableId.svelte";
import Repos from "@app/views/home/guides/Repos.svelte";
import Icon from "@app/components/Icon.svelte";
import InfoButton from "@app/components/InfoButton.svelte";
import NakedButton from "@app/components/NakedButton.svelte";
import NodeId from "@app/components/NodeId.svelte";
import Popover, { setFocused } from "@app/components/Popover.svelte";
import NodeStatusButton from "@app/components/NodeStatusButton.svelte";
const activeRouteStore = router.activeRouteStore;
@@ -39,6 +31,8 @@
center?: Snippet;
}
const { center, config }: Props = $props();
onMount(async () => {
try {
await checkRadicleCLI();
@@ -52,8 +46,6 @@
firstLaunchStorage.value = false;
}
});
const { center, config }: Props = $props();
</script>
<style>
@@ -85,15 +77,6 @@
width: 100%;
justify-content: space-between;
}
.guide-header {
padding-bottom: 1rem;
}
.spacer {
width: 100%;
border-bottom: 1px solid var(--color-border-default);
height: 1px;
margin: 1rem 0;
}
</style>
<div class="header global-flex">
@@ -129,122 +112,8 @@
{@render center?.()}
<div class="global-flex">
<Popover
popoverId="popover-guide"
popoverPadding="0"
popoverPositionTop="2.5rem"
popoverPositionRight="-9.3rem">
{#snippet toggle(onclick)}
<NakedButton variant="ghost" {onclick} stylePadding="0 4px">
<Icon name="info" />
</NakedButton>
{/snippet}
{#snippet popover()}
<Border
variant="ghost"
styleGap="0"
stylePadding="1rem"
styleMinWidth="36rem"
styleOverflow="auto"
styleMaxHeight="calc(100vh - 5rem)"
styleAlignItems="flex-start"
styleFlexDirection="column">
<div
style:position="relative"
style:display="flex"
style:gap="0.5rem"
style:flex-direction="column"
style:padding="1rem"
style:margin-bottom="1rem"
style:width="100%"
style:background-color="var(--color-background-float)">
<div class="txt-semibold txt-medium" style:margin-bottom="1rem">
Getting started
</div>
<div class="txt-small" style:display="inline">
Hello <span style:padding-left="0.25rem">
<NodeId
inline
publicKey={config.publicKey}
alias={config.alias} />,
</span>
your identity has been created and stored on your machine.
</div>
<div class="txt-small">
Your public key is <CopyableId
inline
id={didFromPublicKey(config.publicKey)}>
{truncateDid(config.publicKey)}
</CopyableId>
you can share this with anyone to find you on the network.
</div>
<div class="spacer"></div>
{#if radicleInstalled()}
<div class="global-flex txt-small">
<Icon name="checkbox-checked" />Radicle CLI is setup
</div>
{:else}
<div class="txt-small">
<div class="global-flex" style:padding-bottom="0.5rem">
<Icon name="checkbox-unchecked" />Make sure to install
Radicle CLI
</div>
<div style:padding-bottom="0.5rem">
To be able to interact with repos on the Radicle network
you'll need to install a node on your computer. This node
will identify itself on the network with your keys to push
and pull changes.
</div>
<div style:padding-bottom="0.5rem">
To install the node and other Radicle CLI tooling, simply
run the command below from your shell:
</div>
<Command
styleWidth="fit-content"
command="curl -sSf https://radicle.xyz/install | sh" />
</div>
{/if}
</div>
<div class="guide-header txt-medium txt-semibold">Guide</div>
<Repos />
</Border>
{/snippet}
</Popover>
<Popover
popoverPadding="0"
popoverPositionTop="2.5rem"
popoverPositionRight="0">
{#snippet toggle(onclick)}
<NakedButton variant="ghost" {onclick}>
{#if $nodeRunning}
<Icon name="online" />
Online
{:else}
<Icon name="offline" />
Offline
{/if}
</NakedButton>
{/snippet}
{#snippet popover()}
<Border
variant="ghost"
stylePadding="1rem"
styleMinWidth="20rem"
styleAlignItems="flex-start"
styleFlexDirection="column">
<div class="txt-small txt-missing">
{#if $nodeRunning}
Your node is up and running, your changes will be synced
automatically.
{:else}
Your node is not running, changes you make are safe but won't
be announced.
{/if}
</div>
</Border>
{/snippet}
</Popover>
<InfoButton {config} />
<NodeStatusButton />
<NakedButton
variant="ghost"
stylePadding="0 4px"
+110
View File
@@ -0,0 +1,110 @@
<script lang="ts">
import type { Config } from "@bindings/config/Config";
import { didFromPublicKey, truncateDid } from "@app/lib/utils";
import { radicleInstalled } from "@app/lib/checkRadicleCLI.svelte";
import Border from "@app/components/Border.svelte";
import Command from "@app/components/Command.svelte";
import CopyableId from "@app/components/CopyableId.svelte";
import Icon from "@app/components/Icon.svelte";
import NakedButton from "@app/components/NakedButton.svelte";
import NodeId from "@app/components/NodeId.svelte";
import Popover from "@app/components/Popover.svelte";
import RepoGuide from "@app/components/RepoGuide.svelte";
interface Props {
config: Config;
}
const { config }: Props = $props();
</script>
<style>
.guide-header {
padding-bottom: 1rem;
}
.spacer {
width: 100%;
border-bottom: 1px solid var(--color-border-default);
height: 1px;
margin: 1rem 0;
}
</style>
<Popover
popoverId="popover-guide"
popoverPadding="0"
popoverPositionTop="2.5rem"
popoverPositionRight="-9.3rem">
{#snippet toggle(onclick)}
<NakedButton variant="ghost" {onclick} stylePadding="0 4px">
<Icon name="info" />
</NakedButton>
{/snippet}
{#snippet popover()}
<Border
variant="ghost"
styleGap="0"
stylePadding="1rem"
styleMinWidth="36rem"
styleOverflow="auto"
styleMaxHeight="calc(100vh - 5rem)"
styleAlignItems="flex-start"
styleFlexDirection="column">
<div
style:position="relative"
style:display="flex"
style:gap="0.5rem"
style:flex-direction="column"
style:padding="1rem"
style:margin-bottom="1rem"
style:width="100%"
style:background-color="var(--color-background-float)">
<div class="txt-semibold txt-medium" style:margin-bottom="1rem">
Getting started
</div>
<div class="txt-small" style:display="inline">
Hello <span style:padding-left="0.25rem">
<NodeId inline publicKey={config.publicKey} alias={config.alias} />,
</span>
your identity has been created and stored on your machine.
</div>
<div class="txt-small">
Your public key is <CopyableId
inline
id={didFromPublicKey(config.publicKey)}>
{truncateDid(config.publicKey)}
</CopyableId>
you can share this with anyone to find you on the network.
</div>
<div class="spacer"></div>
{#if radicleInstalled()}
<div class="global-flex txt-small">
<Icon name="checkbox-checked" />Radicle CLI is setup
</div>
{:else}
<div class="txt-small">
<div class="global-flex" style:padding-bottom="0.5rem">
<Icon name="checkbox-unchecked" />Make sure to install Radicle CLI
</div>
<div style:padding-bottom="0.5rem">
To be able to interact with repos on the Radicle network you'll
need to install a node on your computer. This node will identify
itself on the network with your keys to push and pull changes.
</div>
<div style:padding-bottom="0.5rem">
To install the node and other Radicle CLI tooling, simply run the
command below from your shell:
</div>
<Command
styleWidth="fit-content"
command="curl -sSf https://radicle.xyz/install | sh" />
</div>
{/if}
</div>
<div class="guide-header txt-medium txt-semibold">Guide</div>
<RepoGuide />
</Border>
{/snippet}
</Popover>
+10 -60
View File
@@ -1,40 +1,35 @@
<script lang="ts">
import type { Issue } from "@bindings/cob/issue/Issue";
import type { RepoInfo } from "@bindings/repo/RepoInfo";
import type { IssueStatus } from "@app/views/repo/router";
import type { RepoInfo } from "@bindings/repo/RepoInfo";
import capitalize from "lodash/capitalize";
import fuzzysort from "fuzzysort";
import * as router from "@app/lib/router";
import { issueStatusColor, modifierKey } from "@app/lib/utils";
import { modifierKey } from "@app/lib/utils";
import Border from "./Border.svelte";
import DropdownList from "./DropdownList.svelte";
import DropdownListItem from "./DropdownListItem.svelte";
import Icon from "./Icon.svelte";
import IssueStateFilterButton from "./IssueStateFilterButton.svelte";
import IssueTeaser from "@app/components/IssueTeaser.svelte";
import Link from "./Link.svelte";
import NakedButton from "./NakedButton.svelte";
import OutlineButton from "./OutlineButton.svelte";
import Popover, { closeFocused } from "./Popover.svelte";
import TextInput from "./TextInput.svelte";
const activeRouteStore = router.activeRouteStore;
interface Props {
changeFilter: (status: IssueStatus) => void;
issues: Issue[];
repo: RepoInfo;
selectedIssueId?: string;
issues: Issue[];
status: IssueStatus;
title: string;
changeFilter: (status: IssueStatus) => void;
}
/* eslint-disable prefer-const */
let { repo, selectedIssueId, issues, status, title, changeFilter }: Props =
const { changeFilter, issues, repo, selectedIssueId, status, title }: Props =
$props();
/* eslint-enable prefer-const */
const project = $derived(repo.payloads["xyz.radicle.project"]!);
@@ -81,24 +76,6 @@
}
</style>
{#snippet icons(status: IssueStatus)}
<div
class="icon"
style:color={status === "all" ? undefined : issueStatusColor[status]}>
<Icon name={status === "closed" ? "issue-closed" : "issue"} />
</div>
{/snippet}
{#snippet counters(status: IssueStatus)}
<div style:margin-left="auto" style:padding-left="0.25rem">
{#if status === "all"}
{project.meta.issues.open + project.meta.issues.closed}
{:else}
{project.meta.issues[status]}
{/if}
</div>
{/snippet}
<div class="container">
<div
class="txt-medium global-flex"
@@ -157,37 +134,10 @@
{#if showFilters}
<div class="global-flex" style:margin="1rem 0">
<Popover popoverPositionLeft="0" popoverPositionTop="3rem">
{#snippet toggle(onclick)}
<OutlineButton variant="ghost" {onclick} styleHeight="2.5rem">
{@render icons(status)}
{capitalize(status)}
{@render counters(status)}
<Icon name="chevron-down" />
</OutlineButton>
{/snippet}
{#snippet popover()}
<Border variant="ghost">
<DropdownList items={["all", "open", "closed"] as IssueStatus[]}>
{#snippet item(state)}
<DropdownListItem
styleGap="0.5rem"
styleMinHeight="2.5rem"
selected={status === state}
onclick={() => {
changeFilter(state);
closeFocused();
}}>
{@render icons(state)}
{capitalize(state)}
{@render counters(state)}
</DropdownListItem>
{/snippet}
</DropdownList>
</Border>
{/snippet}
</Popover>
<IssueStateFilterButton
{status}
counters={project.meta.issues}
{changeFilter} />
<TextInput
onSubmit={async () => {
if (searchResults.length === 1) {
@@ -0,0 +1,74 @@
<script lang="ts">
import type { IssueStatus } from "@app/views/repo/router";
import type { ProjectPayloadMeta } from "@bindings/repo/ProjectPayloadMeta";
import { closeFocused } from "./Popover.svelte";
import capitalize from "lodash/capitalize";
import { issueStatusColor } from "@app/lib/utils";
import Border from "./Border.svelte";
import DropdownList from "./DropdownList.svelte";
import DropdownListItem from "./DropdownListItem.svelte";
import Icon from "./Icon.svelte";
import OutlineButton from "./OutlineButton.svelte";
import Popover from "./Popover.svelte";
interface Props {
changeFilter: (status: IssueStatus) => void;
status: IssueStatus;
counters: ProjectPayloadMeta["issues"];
}
const { changeFilter, counters, status }: Props = $props();
</script>
{#snippet iconSnippet(status: IssueStatus)}
<div
class="icon"
style:color={status === "all" ? undefined : issueStatusColor[status]}>
<Icon name={status === "closed" ? "issue-closed" : "issue"} />
</div>
{/snippet}
{#snippet counterSnippet(status: IssueStatus)}
<div style:margin-left="auto" style:padding-left="0.25rem">
{#if status === "all"}
{counters.open + counters.closed}
{:else}
{counters[status]}
{/if}
</div>
{/snippet}
<Popover popoverPositionLeft="0" popoverPositionTop="3rem">
{#snippet toggle(onclick)}
<OutlineButton variant="ghost" {onclick} styleHeight="2.5rem">
{@render iconSnippet(status)}
{capitalize(status)}
{@render counterSnippet(status)}
<Icon name="chevron-down" />
</OutlineButton>
{/snippet}
{#snippet popover()}
<Border variant="ghost">
<DropdownList items={["all", "open", "closed"] as IssueStatus[]}>
{#snippet item(state)}
<DropdownListItem
styleGap="0.5rem"
styleMinHeight="2.5rem"
selected={status === state}
onclick={() => {
changeFilter(state);
closeFocused();
}}>
{@render iconSnippet(state)}
{capitalize(state)}
{@render counterSnippet(state)}
</DropdownListItem>
{/snippet}
</DropdownList>
</Border>
{/snippet}
</Popover>
+43
View File
@@ -0,0 +1,43 @@
<script lang="ts">
import { nodeRunning } from "@app/lib/events";
import Border from "@app/components/Border.svelte";
import Icon from "@app/components/Icon.svelte";
import NakedButton from "@app/components/NakedButton.svelte";
import Popover from "@app/components/Popover.svelte";
</script>
<Popover
popoverPadding="0"
popoverPositionTop="2.5rem"
popoverPositionRight="0">
{#snippet toggle(onclick)}
<NakedButton variant="ghost" {onclick}>
{#if $nodeRunning}
<Icon name="online" />
Online
{:else}
<Icon name="offline" />
Offline
{/if}
</NakedButton>
{/snippet}
{#snippet popover()}
<Border
variant="ghost"
stylePadding="1rem"
styleMinWidth="20rem"
styleAlignItems="flex-start"
styleFlexDirection="column">
<div class="txt-small txt-missing">
{#if $nodeRunning}
Your node is up and running, your changes will be synced
automatically.
{:else}
Your node is not running, changes you make are safe but won't be
announced.
{/if}
</div>
</Border>
{/snippet}
</Popover>
@@ -0,0 +1,77 @@
<script lang="ts">
import type { PatchStatus } from "@app/views/repo/router";
import type { ProjectPayloadMeta } from "@bindings/repo/ProjectPayloadMeta";
import capitalize from "lodash/capitalize";
import { patchStatusColor } from "@app/lib/utils";
import { closeFocused } from "@app/components/Popover.svelte";
import Border from "@app/components/Border.svelte";
import DropdownList from "@app/components/DropdownList.svelte";
import DropdownListItem from "@app/components/DropdownListItem.svelte";
import Icon from "@app/components/Icon.svelte";
import OutlineButton from "@app/components/OutlineButton.svelte";
import Popover from "@app/components/Popover.svelte";
interface Props {
counters: ProjectPayloadMeta["patches"];
select: (filter: PatchStatus | undefined) => Promise<void>;
status: PatchStatus | undefined;
}
const { status, select, counters }: Props = $props();
</script>
{#snippet iconSnippet(status: PatchStatus | undefined)}
<div class="icon" style:color={status ? patchStatusColor[status] : undefined}>
<Icon
name={status === undefined || status === "open"
? "patch"
: `patch-${status}`} />
</div>
{/snippet}
{#snippet counterSnippet(status: PatchStatus | undefined)}
<div style:margin-left="auto" style:padding-left="0.25rem">
{#if status}
{counters[status]}
{:else}
{counters.draft + counters.open + counters.archived + counters.merged}
{/if}
</div>
{/snippet}
<Popover popoverPositionLeft="0" popoverPositionTop="3rem">
{#snippet toggle(onclick)}
<OutlineButton variant="ghost" {onclick} styleHeight="2.5rem">
{@render iconSnippet(status)}
{status ? capitalize(status) : "All"}
{@render counterSnippet(status)}
<Icon name="chevron-down" />
</OutlineButton>
{/snippet}
{#snippet popover()}
<Border variant="ghost">
<DropdownList
items={[undefined, "draft", "open", "archived", "merged"] as const}>
{#snippet item(state)}
<DropdownListItem
styleGap="0.5rem"
styleMinHeight="2.5rem"
selected={status === state}
onclick={async () => {
await select(state);
closeFocused();
}}>
{@render iconSnippet(state)}
{state ? capitalize(state) : "All"}
{@render counterSnippet(state)}
</DropdownListItem>
{/snippet}
</DropdownList>
</Border>
{/snippet}
</Popover>
@@ -1,12 +1,14 @@
<script lang="ts">
import { z } from "zod";
import publish from "@app/views/home/publish.md?raw";
import clone from "@app/views/home/clone.md?raw";
import useLocalStorage from "@app/lib/useLocalStorage.svelte";
import clone from "@app/components/RepoGuide/clone.md?raw";
import publish from "@app/components/RepoGuide/publish.md?raw";
import Border from "@app/components/Border.svelte";
import Markdown from "@app/components/Markdown.svelte";
import Tab from "@app/components/Tab.svelte";
import useLocalStorage from "@app/lib/useLocalStorage.svelte";
const tab = useLocalStorage(
"repoGuideTab",
+136
View File
@@ -0,0 +1,136 @@
<script lang="ts">
import type { Config } from "@bindings/config/Config";
import type { PatchStatus } from "@app/views/repo/router";
import type { Review } from "@bindings/cob/patch/Review";
import type { Revision } from "@bindings/cob/patch/Revision";
import type { Verdict } from "@bindings/cob/patch/Verdict";
import { closeFocused } from "./Popover.svelte";
import { didFromPublicKey } from "@app/lib/utils";
import { push } from "@app/lib/router";
import Border from "@app/components/Border.svelte";
import Icon from "@app/components/Icon.svelte";
import NakedButton from "@app/components/NakedButton.svelte";
import OutlineButton from "@app/components/OutlineButton.svelte";
import Popover from "@app/components/Popover.svelte";
interface Props {
rid: string;
patchId: string;
revision: Revision;
config: Config;
status: PatchStatus | undefined;
loadPatch: () => Promise<void>;
createReview: (verdict?: Verdict) => Promise<Review | undefined>;
}
const {
rid,
patchId,
revision,
config,
status,
loadPatch,
createReview,
}: Props = $props();
const hasOwnReview = $derived(
Boolean(
revision.reviews &&
revision.reviews.some(
value => value.author.did === didFromPublicKey(config.publicKey),
),
),
);
</script>
<Popover popoverPositionRight="0" popoverPositionTop="2.5rem">
{#snippet toggle(onclick)}
<NakedButton
variant="ghost"
disabled={hasOwnReview}
{onclick}
title={hasOwnReview ? "You already published a review" : undefined}>
<Icon name="add" />
<span class="txt-small">Review</span>
</NakedButton>
{/snippet}
{#snippet popover()}
<Border
variant="ghost"
stylePadding="1rem"
styleDisplay="flex"
styleFlexDirection="column">
<div class="global-flex">
<OutlineButton
variant="ghost"
disabled={hasOwnReview}
title={hasOwnReview ? "You already published a review" : undefined}
onclick={async () => {
const newReview = await createReview();
if (newReview) {
await push({
resource: "repo.patch",
rid,
patch: patchId,
status,
reviewId: newReview.id,
});
}
closeFocused();
}}>
<span class="global-flex" style:color="var(--color-foreground-dim)">
<Icon name="comment" />
<span class="txt-small">Write Review</span>
</span>
</OutlineButton>
<OutlineButton
variant="ghost"
disabled={hasOwnReview}
title={hasOwnReview ? "You already published a review" : undefined}
onclick={async () => {
await createReview("reject");
await loadPatch();
closeFocused();
}}>
<span class="global-flex" style:color="var(--color-foreground-red)">
<Icon name="comment-cross" />
<span class="txt-small">Reject</span>
</span>
</OutlineButton>
<OutlineButton
variant="ghost"
disabled={hasOwnReview}
title={hasOwnReview ? "You already published a review" : undefined}
onclick={async () => {
await createReview("accept");
await loadPatch();
closeFocused();
}}>
<span
class="global-flex"
style:color="var(--color-foreground-success)">
<Icon name="comment-checkmark" />
<span class="txt-small">Accept</span>
<span></span>
</span>
</OutlineButton>
</div>
<div
class="txt-small txt-missing global-flex"
style:margin-top="0.5rem"
style:align-items="flex-start">
<div style:padding-top="3px"><Icon name="info" /></div>
<div>
Clicking the buttons will create a blank review, add comments, a
summary, and your verdict after. Depending on your sync settings your
review might be published to the network right away. We are actively
working on draft reviews, stay tuned.
</div>
</div>
</Border>
{/snippet}
</Popover>
+13 -118
View File
@@ -6,43 +6,29 @@
import type { Verdict } from "@bindings/cob/patch/Verdict";
import { announce } from "@app/components/AnnounceSwitch.svelte";
import { closeFocused } from "./Popover.svelte";
import { didFromPublicKey } from "@app/lib/utils";
import { invoke } from "@app/lib/invoke";
import { nodeRunning } from "@app/lib/events";
import { push } from "@app/lib/router";
import Border from "@app/components/Border.svelte";
import Icon from "@app/components/Icon.svelte";
import NakedButton from "@app/components/NakedButton.svelte";
import OutlineButton from "@app/components/OutlineButton.svelte";
import Popover from "@app/components/Popover.svelte";
import ReviewButton from "@app/components/ReviewButton.svelte";
import ReviewTeaser from "@app/components/ReviewTeaser.svelte";
interface Props {
rid: string;
config: Config;
loadPatch: () => Promise<void>;
patchId: string;
revision: Revision;
config: Config;
rid: string;
status: PatchStatus | undefined;
loadPatch: () => Promise<void>;
}
const { rid, patchId, revision, config, status, loadPatch }: Props = $props();
const { config, loadPatch, patchId, revision, rid, status }: Props = $props();
let hideReviews = $state(
revision.reviews === undefined || revision.reviews.length === 0,
);
const hasOwnReview = $derived(
Boolean(
revision.reviews &&
revision.reviews.some(
value => value.author.did === didFromPublicKey(config.publicKey),
),
),
);
$effect(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
patchId;
@@ -96,105 +82,14 @@
</NakedButton>
<div class="global-flex" style:margin-left="auto">
<Popover popoverPositionRight="0" popoverPositionTop="2.5rem">
{#snippet toggle(onclick)}
<NakedButton
variant="ghost"
disabled={hasOwnReview}
{onclick}
title={hasOwnReview ? "You already published a review" : undefined}>
<Icon name="add" />
<span class="txt-small">Review</span>
</NakedButton>
{/snippet}
{#snippet popover()}
<Border
variant="ghost"
stylePadding="1rem"
styleDisplay="flex"
styleFlexDirection="column">
<div class="global-flex">
<OutlineButton
variant="ghost"
disabled={hasOwnReview}
title={hasOwnReview
? "You already published a review"
: undefined}
onclick={async () => {
const newReview = await createReview();
if (newReview) {
await push({
resource: "repo.patch",
rid,
patch: patchId,
status,
reviewId: newReview.id,
});
}
closeFocused();
}}>
<span
class="global-flex"
style:color="var(--color-foreground-dim)">
<Icon name="comment" />
<span class="txt-small">Write Review</span>
</span>
</OutlineButton>
<OutlineButton
variant="ghost"
disabled={hasOwnReview}
title={hasOwnReview
? "You already published a review"
: undefined}
onclick={async () => {
await createReview("reject");
await loadPatch();
closeFocused();
}}>
<span
class="global-flex"
style:color="var(--color-foreground-red)">
<Icon name="comment-cross" />
<span class="txt-small">Reject</span>
</span>
</OutlineButton>
<OutlineButton
variant="ghost"
disabled={hasOwnReview}
title={hasOwnReview
? "You already published a review"
: undefined}
onclick={async () => {
await createReview("accept");
await loadPatch();
closeFocused();
}}>
<span
class="global-flex"
style:color="var(--color-foreground-success)">
<Icon name="comment-checkmark" />
<span class="txt-small">Accept</span>
<span></span>
</span>
</OutlineButton>
</div>
<div
class="txt-small txt-missing global-flex"
style:margin-top="0.5rem"
style:align-items="flex-start">
<div style:padding-top="3px"><Icon name="info" /></div>
<div>
Clicking the buttons will create a blank review, add comments, a
summary, and your verdict after. Depending on your sync settings
your review might be published to the network right away. We are
actively working on draft reviews, stay tuned.
</div>
</div>
</Border>
{/snippet}
</Popover>
<ReviewButton
{rid}
{patchId}
{revision}
{config}
{status}
{loadPatch}
{createReview} />
</div>
</div>
+13 -96
View File
@@ -10,7 +10,6 @@
import type { Review } from "@bindings/cob/patch/Review";
import type { Revision } from "@bindings/cob/patch/Revision";
import capitalize from "lodash/capitalize";
import fuzzysort from "fuzzysort";
import * as roles from "@app/lib/roles";
@@ -28,11 +27,8 @@
import AssigneeInput from "@app/components/AssigneeInput.svelte";
import Border from "@app/components/Border.svelte";
import Button from "@app/components/Button.svelte";
import Command from "@app/components/Command.svelte";
import CheckoutPatchButton from "@app/components/CheckoutPatchButton.svelte";
import CopyableId from "@app/components/CopyableId.svelte";
import DropdownList from "@app/components/DropdownList.svelte";
import DropdownListItem from "@app/components/DropdownListItem.svelte";
import EditableTitle from "@app/components/EditableTitle.svelte";
import Icon from "@app/components/Icon.svelte";
import LabelInput from "@app/components/LabelInput.svelte";
@@ -40,11 +36,10 @@
import Link from "@app/components/Link.svelte";
import NakedButton from "@app/components/NakedButton.svelte";
import NewPatchButton from "@app/components/NewPatchButton.svelte";
import OutlineButton from "@app/components/OutlineButton.svelte";
import PatchStateButton from "@app/components/PatchStateButton.svelte";
import PatchStateFilterButton from "@app/components/PatchStateFilterButton.svelte";
import PatchTeaser from "@app/components/PatchTeaser.svelte";
import PatchTimeline from "@app/components/PatchTimeline.svelte";
import Popover, { closeFocused } from "@app/components/Popover.svelte";
import ReviewComponent from "@app/components/Review.svelte";
import RevisionBadges from "@app/components/RevisionBadges.svelte";
import RevisionComponent from "@app/components/Revision.svelte";
@@ -104,13 +99,6 @@
more = patches.more;
});
const checkoutCommand = $derived.by(() => {
if (tab === "revisions" && selectedRevision.id !== patch.id) {
return `rad patch checkout ${formatOid(patch.id)} --revision ${formatOid(selectedRevision.id)}`;
} else {
return `rad patch checkout ${formatOid(patch.id)}`;
}
});
const project = $derived(repo.payloads["xyz.radicle.project"]!);
async function updateTitle(newTitle: string) {
@@ -334,28 +322,6 @@
}
</style>
{#snippet icons(status: PatchStatus | undefined)}
<div class="icon" style:color={status ? patchStatusColor[status] : undefined}>
<Icon
name={status === undefined || status === "open"
? "patch"
: `patch-${status}`} />
</div>
{/snippet}
{#snippet counters(status: PatchStatus | undefined)}
<div style:margin-left="auto" style:padding-left="0.25rem">
{#if status}
{project.meta.patches[status]}
{:else}
{project.meta.patches.draft +
project.meta.patches.open +
project.meta.patches.archived +
project.meta.patches.merged}
{/if}
</div>
{/snippet}
<Layout {config} loadMoreSecondColumn={loadMoreTeasers}>
{#snippet headerCenter()}
<CopyableId id={patch.id} />
@@ -410,44 +376,12 @@
</div>
{#if showFilters}
<div class="global-flex" style:margin="1rem 0">
<Popover popoverPositionLeft="0" popoverPositionTop="3rem">
{#snippet toggle(onclick)}
<OutlineButton variant="ghost" {onclick} styleHeight="2.5rem">
{@render icons(status)}
{status ? capitalize(status) : "All"}
{@render counters(status)}
<Icon name="chevron-down" />
</OutlineButton>
{/snippet}
{#snippet popover()}
<Border variant="ghost">
<DropdownList
items={[
undefined,
"draft",
"open",
"archived",
"merged",
] as const}>
{#snippet item(state)}
<DropdownListItem
styleGap="0.5rem"
styleMinHeight="2.5rem"
selected={status === state}
onclick={async () => {
await loadPatches(state);
closeFocused();
}}>
{@render icons(state)}
{state ? capitalize(state) : "All"}
{@render counters(state)}
</DropdownListItem>
{/snippet}
</DropdownList>
</Border>
{/snippet}
</Popover>
<PatchStateFilterButton
counters={project.meta.patches}
{status}
select={async selectedState => {
await loadPatches(selectedState);
}} />
{#if patchTeasers.length > 0}
<TextInput
onFocus={async () => {
@@ -562,28 +496,11 @@
allowedToEdit={true}
title={patch.title}
cobId={patch.id} />
<div class="txt-small" style:margin-left="auto" style:z-index="40">
<Popover popoverPositionRight="0" popoverPositionTop="3rem">
{#snippet toggle(onclick)}
<Button styleHeight="2.5rem" variant="secondary" {onclick}>
<Icon name="checkout" />Checkout patch<Icon
name="chevron-down" />
</Button>
{/snippet}
{#snippet popover()}
<Border
styleAlignItems="flex-start"
styleBackgroundColor="var(--color-background-float)"
styleFlexDirection="column"
styleGap="0.5rem"
stylePadding="1rem"
styleWidth="max-content"
variant="ghost">
To checkout this patch in your working copy, run:
<Command command={checkoutCommand} styleWidth="100%" />
</Border>
{/snippet}
</Popover>
<div style:margin-left="auto" style:z-index="40">
<CheckoutPatchButton
{tab}
selectedRevisionId={selectedRevision.id}
patchId={patch.id} />
</div>
</div>
<Border variant="ghost" styleGap="0">
+4 -26
View File
@@ -1,18 +1,16 @@
<script lang="ts">
import type { Config } from "@bindings/config/Config";
import type { RepoInfo } from "@bindings/repo/RepoInfo";
import type { Readme } from "@bindings/repo/Readme";
import type { RepoInfo } from "@bindings/repo/RepoInfo";
import Border from "@app/components/Border.svelte";
import Button from "@app/components/Button.svelte";
import Command from "@app/components/Command.svelte";
import CheckoutRepoButton from "@app/components/CheckoutRepoButton.svelte";
import CopyableId from "@app/components/CopyableId.svelte";
import File from "@app/components/File.svelte";
import Icon from "@app/components/Icon.svelte";
import Layout from "./Layout.svelte";
import Markdown from "@app/components/Markdown.svelte";
import Path from "@app/components/Path.svelte";
import Popover from "@app/components/Popover.svelte";
import RepoHomeSecondColumn from "@app/components/RepoHomeSecondColumn.svelte";
import RepoMetadata from "@app/components/RepoMetadata.svelte";
@@ -55,28 +53,8 @@
style:font-weight="var(--font-weight-medium)">
{project.data.name}
</div>
<div class="global-flex txt-small" style:margin-left="auto">
<Popover popoverPositionRight="0" popoverPositionTop="3rem">
{#snippet toggle(onclick)}
<Button styleHeight="2.5rem" variant="secondary" {onclick}>
<Icon name="checkout" />Checkout repo<Icon name="chevron-down" />
</Button>
{/snippet}
{#snippet popover()}
<Border
styleAlignItems="flex-start"
styleBackgroundColor="var(--color-background-float)"
styleFlexDirection="column"
styleGap="0.5rem"
stylePadding="1rem"
styleWidth="max-content"
variant="ghost">
To checkout a working copy of this repo, run:
<Command command={`rad checkout ${repo.rid}`} styleWidth="100%" />
</Border>
{/snippet}
</Popover>
<div class="global-flex" style:margin-left="auto">
<CheckoutRepoButton rid={repo.rid} />
</div>
</div>