mirror of
https://seed.radicle.dev/rad:z4D5UCArafTzTQpDZNQRuqswh3ury.git
synced 2026-11-11 06:10:37 +08:00
87 lines
2.2 KiB
Svelte
87 lines
2.2 KiB
Svelte
<script lang="ts">
|
|
import type { ComponentProps } from "svelte";
|
|
|
|
import { saveToDisk, writeToClipboard } from "@app/lib/invoke";
|
|
|
|
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, { closeFocused } from "@app/components/Popover.svelte";
|
|
|
|
interface Props {
|
|
// A thunk so the diff text isn't generated until an action runs.
|
|
text: () => Promise<string>;
|
|
fileName: string;
|
|
title?: string;
|
|
}
|
|
|
|
const { text, fileName, title = "Diff actions" }: Props = $props();
|
|
|
|
let expanded = $state(false);
|
|
|
|
type Action = {
|
|
label: string;
|
|
icon: ComponentProps<typeof Icon>["name"];
|
|
run: () => Promise<void>;
|
|
};
|
|
|
|
const actions: Action[] = [
|
|
{
|
|
label: "Copy to clipboard",
|
|
icon: "copy",
|
|
run: async () => writeToClipboard(await text()),
|
|
},
|
|
{
|
|
label: "Save to disk",
|
|
icon: "download",
|
|
run: async () => saveToDisk(fileName, await text()),
|
|
},
|
|
];
|
|
|
|
async function run(action: Action) {
|
|
closeFocused();
|
|
try {
|
|
await action.run();
|
|
} catch (e) {
|
|
console.error(`${action.label} failed`, e);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<Popover placement="bottom-end" bind:expanded>
|
|
{#snippet toggle(onclick)}
|
|
<Button
|
|
variant="naked"
|
|
active={expanded}
|
|
{title}
|
|
{onclick}
|
|
styleHeight="1.75rem"
|
|
styleWidth="1.75rem"
|
|
stylePadding="0"
|
|
styleJustifyContent="center">
|
|
<Icon name="ellipsis-vertical" />
|
|
</Button>
|
|
{/snippet}
|
|
|
|
{#snippet popover()}
|
|
<div
|
|
style:border="1px solid var(--color-border-subtle)"
|
|
style:border-radius="var(--border-radius-md)"
|
|
style:background-color="var(--color-surface-canvas)"
|
|
style:padding="0.25rem">
|
|
<DropdownList items={actions}>
|
|
{#snippet item(action)}
|
|
<DropdownListItem
|
|
selected={false}
|
|
styleGap="0.5rem"
|
|
onclick={() => run(action)}>
|
|
<Icon name={action.icon} />
|
|
{action.label}
|
|
</DropdownListItem>
|
|
{/snippet}
|
|
</DropdownList>
|
|
</div>
|
|
{/snippet}
|
|
</Popover>
|