Spaces:
Runtime error
Runtime error
File size: 6,156 Bytes
654f56a fbef5e8 84f775e 3df1e9f af10150 84f775e 64cfbce aac02fe 84f775e b2170a7 dab40ed 84f775e b1426d3 f77d645 7450ebd 1778c9e 6a8491a af10150 6a8491a 1778c9e 654f56a af10150 1778c9e af10150 1778c9e af10150 654f56a aac02fe 84f775e 1778c9e 84f775e aac02fe 84f775e 1778c9e 84f775e 654f56a aac02fe 654f56a af10150 654f56a aac02fe 7e80e42 654f56a 1778c9e 654f56a 64cfbce 654f56a f77d645 cf47645 7450ebd cf47645 84f775e cf47645 1778c9e cf47645 1778c9e cf47645 aac02fe 654f56a af10150 1778c9e af10150 7450ebd af10150 6a8491a aac02fe 6a8491a 7450ebd dab40ed 7450ebd 84f775e 7450ebd aac02fe af10150 aac02fe 1778c9e aac02fe af10150 aac02fe 1778c9e aac02fe 6a8491a af10150 aac02fe 654f56a 84f775e fbef5e8 84f775e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
<script lang="ts">
import { autofocus } from "$lib/attachments/autofocus.js";
import { checkpoints } from "$lib/state/checkpoints.svelte";
import { cn } from "$lib/utils/cn.js";
import { Select } from "melt/builders";
import type { EventHandler } from "svelte/elements";
import IconCaret from "~icons/carbon/chevron-down";
import IconCross from "~icons/carbon/close";
import IconEdit from "~icons/carbon/edit";
import IconHistory from "~icons/carbon/recently-viewed";
import IconSave from "~icons/carbon/save";
import IconDelete from "~icons/carbon/trash-can";
import ArrowSplitRounded from "~icons/material-symbols/arrow-split-rounded";
import Dialog from "../dialog.svelte";
import { prompt } from "../prompts.svelte";
import Tooltip from "../tooltip.svelte";
import CheckpointsMenu from "./checkpoints-menu.svelte";
import { projects } from "$lib/state/projects.svelte";
interface Props {
class?: string;
}
let { class: classNames = "" }: Props = $props();
const isDefault = $derived(projects.activeId === "default");
const select = new Select({
value: () => projects.activeId,
onValueChange(v) {
if (v) projects.activeId = v;
},
sameWidth: true,
});
type SaveDialogState = {
open: boolean;
moveCheckpoints: boolean;
name: string;
};
const defaultSdState: SaveDialogState = {
open: false,
moveCheckpoints: true,
name: "",
};
let sdState = $state(defaultSdState);
const projectPlaceholder = $derived(`Project #${projects.all.length}`);
function openSaveDialog() {
sdState = { ...defaultSdState, open: true };
}
const saveDialog = async function (e) {
e.preventDefault();
projects.saveProject({
...sdState,
name: sdState.name || projectPlaceholder,
});
sdState = { ...defaultSdState };
} satisfies EventHandler
</script>
<div class={cn("flex w-full items-stretch gap-2 ", classNames)}>
<button
{...select.trigger}
class={cn(
"relative flex grow items-center justify-between gap-6 overflow-hidden rounded-lg border bg-gray-100/80 px-3 py-1.5 leading-tight whitespace-nowrap shadow-sm",
"hover:brightness-95 dark:border-gray-700 dark:bg-gray-800 dark:hover:brightness-110",
)}
>
<div class="flex items-center gap-1 text-sm">
{projects.current?.name}
</div>
<div
class="absolute right-2 grid size-4 flex-none place-items-center rounded-sm bg-gray-100 text-xs dark:bg-gray-600"
>
<IconCaret />
</div>
</button>
<div class="flex items-center gap-2">
<CheckpointsMenu />
{#if isDefault}
<Tooltip>
{#snippet trigger(tooltip)}
<button class="btn size-[32px] p-0" {...tooltip.trigger} onclick={openSaveDialog}>
<IconSave />
</button>
{/snippet}
Save as project
</Tooltip>
{:else}
<Tooltip>
{#snippet trigger(tooltip)}
<button class="btn size-[32px] p-0" {...tooltip.trigger} onclick={() => (projects.activeId = "default")}>
<IconCross />
</button>
{/snippet}
Close project
</Tooltip>
{/if}
</div>
</div>
<div {...select.content} class="rounded-lg border bg-gray-100 dark:border-gray-700 dark:bg-gray-800">
{#each projects.all as { name, id } (id)}
{@const option = select.getOption(id)}
{@const hasCheckpoints = checkpoints.for(id).length > 0}
<div {...option} class="group block w-full p-1 text-sm dark:text-white">
<div
class="flex items-center gap-2 rounded-md py-1.5 pr-1 pl-2 group-data-[highlighted]:bg-gray-200 dark:group-data-[highlighted]:bg-gray-700"
>
<div class="flex items-center gap-2">
{name}
{#if projects.all.find(p => p.id === id)?.branchedFromId}
{@const originalProject = projects.getBranchedFromProject(id)}
<Tooltip>
{#snippet trigger(tooltip)}
<div
class="text-3xs grid aspect-square place-items-center rounded bg-blue-300 p-0.5 text-blue-700 dark:bg-blue-400/25 dark:text-blue-400"
aria-label="Branched project"
{...tooltip.trigger}
>
<ArrowSplitRounded />
</div>
{/snippet}
Branched from {originalProject?.name || "unknown project"}
</Tooltip>
{:else if hasCheckpoints}
<div
class="text-3xs grid aspect-square place-items-center rounded bg-yellow-300 p-0.5 text-yellow-700 dark:bg-yellow-400/25 dark:text-yellow-400"
aria-label="Project has checkpoints"
>
<IconHistory />
</div>
{/if}
</div>
{#if id !== "default"}
<div class="ml-auto flex items-center gap-1">
<button
class="grid place-items-center rounded-md p-1 text-xs hover:bg-gray-300 dark:hover:bg-gray-600"
onclick={async e => {
e.stopPropagation();
projects.update({ id, name: (await prompt("Edit project name", name)) || name });
}}
>
<IconEdit />
</button>
<button
class="grid place-items-center rounded-md p-1 text-xs hover:bg-gray-300 dark:hover:bg-gray-600"
onclick={e => {
e.stopPropagation();
projects.delete(id);
}}
>
<IconDelete />
</button>
</div>
{/if}
</div>
</div>
{/each}
</div>
<Dialog title="Set project name" open={sdState.open} onClose={() => (sdState.open = false)} onSubmit={saveDialog}>
<label class="flex flex-col gap-2 font-medium text-gray-900 dark:text-white">
<p>Project name</p>
<input
bind:value={sdState.name}
placeholder={projectPlaceholder}
type="text"
class="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
{@attach autofocus()}
/>
</label>
<label class="mt-4 flex gap-2 font-medium text-gray-900 dark:text-white">
<input bind:checked={sdState.moveCheckpoints} type="checkbox" />
<p>Move checkpoints over</p>
</label>
{#snippet footer()}
<button
type="submit"
class="ml-auto rounded-lg bg-black px-5 py-2.5 text-sm font-medium text-white hover:bg-gray-900 focus:ring-4 focus:ring-gray-300 focus:outline-hidden dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700 dark:focus:ring-gray-700"
>
Submit
</button>
{/snippet}
</Dialog>
|