mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-15 11:20:36 +08:00
Restyle and move issue/patch state buttons
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Issue } from "@bindings/cob/issue/Issue";
|
||||
|
||||
import capitalize from "lodash/capitalize";
|
||||
import { issueStatusColor } from "@app/lib/utils";
|
||||
|
||||
interface Props {
|
||||
state: Issue["state"];
|
||||
}
|
||||
|
||||
const { state }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="global-counter txt-small"
|
||||
style:width="fit-content"
|
||||
style:color="var(--color-foreground-match-background)"
|
||||
style:background-color={issueStatusColor[state.status]}>
|
||||
{capitalize(state.status)}{state.status === "closed"
|
||||
? ` as ${state.reason}`
|
||||
: ""}
|
||||
</div>
|
||||
@@ -1,88 +1,91 @@
|
||||
<script lang="ts">
|
||||
import type { State } from "@bindings/cob/issue/State";
|
||||
|
||||
import capitalize from "lodash/capitalize";
|
||||
import isEqual from "lodash/isEqual";
|
||||
|
||||
import { closeFocused } from "@app/components/Popover.svelte";
|
||||
import { issueStatusBackgroundColor, issueStatusColor } from "@app/lib/utils";
|
||||
|
||||
import Border from "@app/components/Border.svelte";
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import DropdownList from "@app/components/DropdownList.svelte";
|
||||
import DropdownListItem from "@app/components/DropdownListItem.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import Popover from "@app/components/Popover.svelte";
|
||||
|
||||
const {
|
||||
save,
|
||||
issueState,
|
||||
}: {
|
||||
save: (state: State) => Promise<void>;
|
||||
issueState: State;
|
||||
} = $props();
|
||||
interface Props {
|
||||
selectedState: State;
|
||||
onSelect: (selectedStatus: State) => void;
|
||||
}
|
||||
|
||||
const actions: { caption: string; state: State }[] = [
|
||||
{ caption: "Reopen", state: { status: "open" } },
|
||||
{
|
||||
caption: "Close as solved",
|
||||
state: { status: "closed", reason: "solved" },
|
||||
},
|
||||
{ caption: "Close as other", state: { status: "closed", reason: "other" } },
|
||||
];
|
||||
|
||||
let selectedAction = $state(
|
||||
issueState.status === "open" ? actions[1] : actions[0],
|
||||
);
|
||||
|
||||
// React to state changes that come from outside of this button.
|
||||
$effect(() => {
|
||||
selectedAction = issueState.status === "open" ? actions[1] : actions[0];
|
||||
});
|
||||
const { selectedState, onSelect }: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.main {
|
||||
button {
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
background: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.badge {
|
||||
gap: 6px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="main">
|
||||
<Button
|
||||
styleHeight="2.5rem"
|
||||
variant="secondary"
|
||||
flatRight
|
||||
onclick={() => void save($state.snapshot(selectedAction["state"]))}>
|
||||
{selectedAction["caption"]}
|
||||
</Button>
|
||||
|
||||
<Popover
|
||||
popoverPadding="0"
|
||||
popoverPositionTop="3rem"
|
||||
popoverPositionRight="0">
|
||||
{#snippet toggle(onclick)}
|
||||
<Button styleHeight="2.5rem" flatLeft {onclick} variant="secondary">
|
||||
<Popover popoverPadding="0" popoverPositionTop="2rem" popoverPositionLeft="0">
|
||||
{#snippet toggle(onclick)}
|
||||
<button {onclick}>
|
||||
<span
|
||||
class="global-counter badge"
|
||||
style:color={issueStatusColor[selectedState.status]}
|
||||
style:background-color={issueStatusBackgroundColor[
|
||||
selectedState.status
|
||||
]}>
|
||||
<Icon
|
||||
name={selectedState.status === "open"
|
||||
? "issue"
|
||||
: `issue-${selectedState.status}`} />
|
||||
{capitalize(selectedState.status)}
|
||||
{selectedState.status === "closed" ? `as ${selectedState.reason}` : ""}
|
||||
<Icon name="chevron-down" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
{#snippet popover()}
|
||||
<Border variant="ghost">
|
||||
<DropdownList
|
||||
items={actions.filter(a => !isEqual(a.state, issueState))}>
|
||||
{#snippet item(action)}
|
||||
<DropdownListItem
|
||||
styleGap="0.5rem"
|
||||
styleMinHeight="2.5rem"
|
||||
selected={isEqual(selectedAction, action)}
|
||||
onclick={() => {
|
||||
selectedAction = action;
|
||||
closeFocused();
|
||||
}}>
|
||||
{action.caption}
|
||||
</DropdownListItem>
|
||||
{/snippet}
|
||||
</DropdownList>
|
||||
</Border>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
{/snippet}
|
||||
{#snippet popover()}
|
||||
<Border variant="ghost">
|
||||
<DropdownList
|
||||
items={[
|
||||
{ status: "open" },
|
||||
{ status: "closed", reason: "solved" },
|
||||
{ status: "closed", reason: "other" },
|
||||
] as State[]}>
|
||||
{#snippet item(state)}
|
||||
<DropdownListItem
|
||||
selected={isEqual(selectedState, state)}
|
||||
onclick={() => {
|
||||
onSelect(state);
|
||||
closeFocused();
|
||||
}}>
|
||||
<span
|
||||
class="global-flex"
|
||||
style:color={issueStatusColor[state.status]}>
|
||||
<Icon
|
||||
name={state.status === "open"
|
||||
? "issue"
|
||||
: `issue-${state.status}`} />
|
||||
{capitalize(state.status)}
|
||||
{state.status === "closed" ? `as ${state.reason}` : ""}
|
||||
</span>
|
||||
</DropdownListItem>
|
||||
{/snippet}
|
||||
</DropdownList>
|
||||
</Border>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Patch } from "@bindings/cob/patch/Patch";
|
||||
|
||||
import capitalize from "lodash/capitalize";
|
||||
import { patchStatusColor } from "@app/lib/utils";
|
||||
|
||||
interface Props {
|
||||
state: Patch["state"];
|
||||
}
|
||||
|
||||
const { state }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="global-counter txt-small"
|
||||
style:width="fit-content"
|
||||
style:color="var(--color-foreground-match-background)"
|
||||
style:background-color={patchStatusColor[state.status]}>
|
||||
{capitalize(state.status)}
|
||||
</div>
|
||||
@@ -1,89 +1,88 @@
|
||||
<script lang="ts">
|
||||
import type { State } from "@bindings/cob/patch/State";
|
||||
|
||||
import isEqual from "lodash/isEqual";
|
||||
import capitalize from "lodash/capitalize";
|
||||
|
||||
import { closeFocused } from "@app/components/Popover.svelte";
|
||||
import { patchStatusBackgroundColor, patchStatusColor } from "@app/lib/utils";
|
||||
|
||||
import Border from "@app/components/Border.svelte";
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import DropdownList from "@app/components/DropdownList.svelte";
|
||||
import DropdownListItem from "@app/components/DropdownListItem.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import Popover from "@app/components/Popover.svelte";
|
||||
|
||||
const {
|
||||
save,
|
||||
patchState,
|
||||
}: {
|
||||
save: (state: State) => Promise<void>;
|
||||
patchState: State;
|
||||
} = $props();
|
||||
interface Props {
|
||||
selectedState: State;
|
||||
onSelect: (newState: State) => void;
|
||||
}
|
||||
|
||||
const actions: { caption: string; state: State }[] = [
|
||||
{
|
||||
caption: "Reopen",
|
||||
state: { status: "open" },
|
||||
},
|
||||
{ caption: "Convert to draft", state: { status: "draft" } },
|
||||
{ caption: "Archive", state: { status: "archived" } },
|
||||
];
|
||||
|
||||
let selectedAction = $state(
|
||||
patchState.status === "open" ? actions[1] : actions[0],
|
||||
);
|
||||
|
||||
// React to state changes that come from outside of this button.
|
||||
$effect(() => {
|
||||
selectedAction = patchState.status === "open" ? actions[1] : actions[0];
|
||||
});
|
||||
const { selectedState, onSelect }: Props = $props();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.main {
|
||||
button {
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
background: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.badge {
|
||||
gap: 6px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="main">
|
||||
<Button
|
||||
styleHeight="2.5rem"
|
||||
variant="secondary"
|
||||
flatRight
|
||||
onclick={() =>
|
||||
void save($state.snapshot(selectedAction["state"]) as State)}>
|
||||
{selectedAction["caption"]}
|
||||
</Button>
|
||||
|
||||
<Popover
|
||||
popoverPadding="0"
|
||||
popoverPositionTop="3rem"
|
||||
popoverPositionRight="0">
|
||||
{#snippet toggle(onclick)}
|
||||
<Button styleHeight="2.5rem" flatLeft {onclick} variant="secondary">
|
||||
<Popover popoverPadding="0" popoverPositionTop="2rem" popoverPositionLeft="0">
|
||||
{#snippet toggle(onclick)}
|
||||
<button {onclick}>
|
||||
<span
|
||||
class="global-counter badge"
|
||||
style:color={patchStatusColor[selectedState.status]}
|
||||
style:background-color={patchStatusBackgroundColor[
|
||||
selectedState.status
|
||||
]}>
|
||||
<Icon
|
||||
name={selectedState.status === "open"
|
||||
? "patch"
|
||||
: `patch-${selectedState.status}`} />
|
||||
{capitalize(selectedState.status)}
|
||||
<Icon name="chevron-down" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
{#snippet popover()}
|
||||
<Border variant="ghost">
|
||||
<DropdownList
|
||||
items={actions.filter(a => !isEqual(a.state, patchState))}>
|
||||
{#snippet item(action)}
|
||||
<DropdownListItem
|
||||
styleGap="0.5rem"
|
||||
styleMinHeight="2.5rem"
|
||||
selected={isEqual(selectedAction, action)}
|
||||
onclick={() => {
|
||||
selectedAction = action;
|
||||
closeFocused();
|
||||
}}>
|
||||
{action.caption}
|
||||
</DropdownListItem>
|
||||
{/snippet}
|
||||
</DropdownList>
|
||||
</Border>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
{/snippet}
|
||||
{#snippet popover()}
|
||||
<Border variant="ghost">
|
||||
<DropdownList
|
||||
items={[
|
||||
{ status: "open" },
|
||||
{ status: "draft" },
|
||||
{ status: "archived" },
|
||||
] as State[]}>
|
||||
{#snippet item(state)}
|
||||
<DropdownListItem
|
||||
selected={selectedState.status === state.status}
|
||||
onclick={() => {
|
||||
onSelect(state);
|
||||
closeFocused();
|
||||
}}>
|
||||
<span
|
||||
class="global-flex"
|
||||
style:color={patchStatusColor[state.status]}>
|
||||
<Icon
|
||||
name={state.status === "open"
|
||||
? "patch"
|
||||
: `patch-${state.status}`} />
|
||||
{capitalize(state.status)}
|
||||
</span>
|
||||
</DropdownListItem>
|
||||
{/snippet}
|
||||
</DropdownList>
|
||||
</Border>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import InlineTitle from "@app/components/InlineTitle.svelte";
|
||||
import IssueSecondColumn from "@app/components/IssueSecondColumn.svelte";
|
||||
import IssueStateBadge from "@app/components/IssueStateBadge.svelte";
|
||||
import IssueStateButton from "@app/components/IssueStateButton.svelte";
|
||||
import IssueTimeline from "@app/components/IssueTimeline.svelte";
|
||||
import LabelInput from "@app/components/LabelInput.svelte";
|
||||
@@ -410,7 +409,6 @@
|
||||
updatedTitle = issue.title;
|
||||
editingTitle = !editingTitle;
|
||||
}} />
|
||||
<IssueStateButton issueState={issue.state} save={saveState} />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
@@ -433,7 +431,6 @@
|
||||
{#if roles.isDelegateOrAuthor( config.publicKey, repo.delegates.map(delegate => delegate.did), issue.body.author.did, )}
|
||||
<div class="title-icons">
|
||||
<Icon name="pen" onclick={() => (editingTitle = !editingTitle)} />
|
||||
<IssueStateButton issueState={issue.state} save={saveState} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -443,7 +440,14 @@
|
||||
<Border variant="ghost" styleGap="0">
|
||||
<div class="metadata-section" style:min-width="8rem">
|
||||
<div class="metadata-section-title">Status</div>
|
||||
<IssueStateBadge state={issue.state} />
|
||||
<IssueStateButton
|
||||
selectedState={issue.state}
|
||||
onSelect={newState => {
|
||||
void saveState(newState);
|
||||
if (status !== "all" && newState.status !== status) {
|
||||
void loadIssues("all");
|
||||
}
|
||||
}} />
|
||||
</div>
|
||||
|
||||
<div class="metadata-divider"></div>
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import NakedButton from "@app/components/NakedButton.svelte";
|
||||
import OutlineButton from "@app/components/OutlineButton.svelte";
|
||||
import PatchStateBadge from "@app/components/PatchStateBadge.svelte";
|
||||
import PatchStateButton from "@app/components/PatchStateButton.svelte";
|
||||
import PatchTeaser from "@app/components/PatchTeaser.svelte";
|
||||
import PatchTimeline from "@app/components/PatchTimeline.svelte";
|
||||
@@ -174,20 +173,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function saveState(state: Patch["state"]) {
|
||||
async function saveState(newState: Patch["state"]) {
|
||||
try {
|
||||
await invoke("edit_patch", {
|
||||
rid: repo.rid,
|
||||
cobId: patch.id,
|
||||
action: {
|
||||
type: "lifecycle",
|
||||
state,
|
||||
state: newState,
|
||||
},
|
||||
opts: { announce: $nodeRunning && $announce },
|
||||
});
|
||||
if (initialStatus !== undefined) {
|
||||
status = state["status"];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Changing state failed", error);
|
||||
} finally {
|
||||
@@ -343,6 +339,7 @@
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
height: 100%;
|
||||
z-index: 20;
|
||||
}
|
||||
.metadata-section-title {
|
||||
margin-bottom: 0.5rem;
|
||||
@@ -594,7 +591,6 @@
|
||||
updatedTitle = patch.title;
|
||||
editingTitle = !editingTitle;
|
||||
}} />
|
||||
<PatchStateButton patchState={patch.state} save={saveState} />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
@@ -618,7 +614,6 @@
|
||||
<Icon
|
||||
name="pen"
|
||||
onclick={() => (editingTitle = !editingTitle)} />
|
||||
<PatchStateButton patchState={patch.state} save={saveState} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -627,7 +622,15 @@
|
||||
<Border variant="ghost" styleGap="0">
|
||||
<div class="metadata-section" style:min-width="8rem">
|
||||
<div class="metadata-section-title">Status</div>
|
||||
<PatchStateBadge state={patch.state} />
|
||||
<PatchStateButton
|
||||
selectedState={patch.state}
|
||||
onSelect={newState => {
|
||||
void saveState(newState);
|
||||
if (status !== undefined && newState.status !== status) {
|
||||
status = undefined;
|
||||
void loadPatches(status);
|
||||
}
|
||||
}} />
|
||||
</div>
|
||||
|
||||
<div class="metadata-divider"></div>
|
||||
|
||||
Reference in New Issue
Block a user