ufh/web/Nav.svelte

213 lines
6.9 KiB
Svelte

<script lang="ts">
import { state } from "./state";
import SettingsIc from "carbon-icons-svelte/lib/Settings.svelte";
import HomeIc from "carbon-icons-svelte/lib/Home.svelte";
import ForumIc from "carbon-icons-svelte/lib/Forum.svelte";
import FolderIc from "carbon-icons-svelte/lib/Folder.svelte";
import BookmarkIc from "carbon-icons-svelte/lib/Bookmark.svelte";
import DocumentIc from "carbon-icons-svelte/lib/Document.svelte";
import MusicIc from "carbon-icons-svelte/lib/Music.svelte";
import ImageIc from "carbon-icons-svelte/lib/Image.svelte";
import VideoIc from "carbon-icons-svelte/lib/Video.svelte";
import ChatIc from "carbon-icons-svelte/lib/Chat.svelte";
import UnknownIc from "carbon-icons-svelte/lib/Unknown.svelte";
import { Event } from "./lib/api";
import { filterRels } from "./lib/util";
export let items: Array<Event>;
export let selected: Promise<Event | null> = Promise.resolve(null);
let selectedRes: Event | null = null;
$: selected.then(s => selectedRes = s);
function getIcon(event: Event) {
switch (event.type) {
case "home": return HomeIc;
case "settings": return SettingsIc;
case "x.file": {
switch (event.derived?.file?.mime.split("/")[0]) {
case "image": return ImageIc;
case "video": return VideoIc;
case "audio": return MusicIc;
default: return DocumentIc;
}
}
case "l.files": return FolderIc;
case "l.notes": return BookmarkIc;
case "l.forum": return ForumIc;
case "l.forum.post": return ChatIc;
case "l.docs": return DocumentIc;
case "l.doc": return DocumentIc;
default: return UnknownIc;
}
}
function getName(event: Event) {
const cont = event.getContent();
if (!cont) return "---";
switch (event.type) {
case "home": return "home";
case "settings": return "settings";
case "x.file": return cont.name || "file";
case "l.files": return cont.name;
case "l.notes": return cont.name;
case "l.forum": return cont.name;
case "l.forum.post": return cont.title;
case "l.docs": return cont.name;
case "l.doc": return cont.name;
default: return null;
}
}
const openContext = (event: Event) => (e) => {
const items = [
{ type: "submenu", text: "edit", clicked: state.curry("popup/open", { type: "edit", event, panel: "general" }), items: [
{ type: "item", text: "general", clicked: state.curry("popup/open", { type: "edit", event, panel: "general" }) },
{ type: "item", text: "access", clicked: state.curry("popup/open", { type: "edit", event, panel: "access" }) },
{ type: "item", text: "members", clicked: state.curry("popup/open", { type: "edit", event, panel: "members" }) },
{ type: "item", text: "debug", clicked: state.curry("popup/open", { type: "edit", event, panel: "debug" }) },
] },
{ type: "item", text: "pin", clicked: state.curry("popup/open", { type: "text", text: "not implemented yet!" }) },
{ type: "item", text: "copy id", clicked: navigator.clipboard.writeText(event.id) },
];
state.do("menu/open", { x: e.clientX, y: e.clientY, items });
};
$: selectedResParentId = selectedRes ? filterRels(selectedRes, "in")[0] : null;
</script>
<nav id="nav">
{#if selectedRes && !selectedResParentId && items.findIndex(ev => ev.id === selectedRes.id) === -1}
{@const name = getName(selectedRes) || "unnamed"}
<a
href="#/{selectedRes.id}"
class="wrapper selected temp"
on:contextmenu|preventDefault|stopPropagation={openContext(selectedRes)}
>
<div class="highlight">
<div class="icon"><svelte:component this={getIcon(selectedRes)} /></div>
<span class="name" title={name}>{name}</span>
<button on:click|preventDefault={state.curry("popup/open", { type: "edit", event: selectedRes, panel: "general" })}>+</button>
</div>
</a>
{/if}
{#each items as item (item.id)}
{@const name = getName(item) || "unnamed"}
<a
href="#/{item.id}"
class="wrapper"
class:selected={item.id === selectedRes?.id}
on:contextmenu|preventDefault|stopPropagation={openContext(item)}
>
<div class="highlight">
<div class="icon"><svelte:component this={getIcon(item)} /></div>
<span class="name" title={name}>{name}</span>
{#if !["home", "settings"].includes(item.id)}
<button on:click|preventDefault={state.curry("popup/open", { type: "edit", event: item, panel: "general" })}>+</button>
{/if}
</div>
</a>
{#if item.id === selectedResParentId}
{@const name = getName(selectedRes) || "unnamed"}
<a
href="#/{selectedRes.id}"
class="wrapper selected child"
on:contextmenu|preventDefault|stopPropagation={openContext(selectedRes)}
>
<div class="highlight">
<span class="name" title={name}>{name}</span>
<button on:click|preventDefault={state.curry("popup/open", { type: "edit", event: selectedRes, panel: "general" })}>+</button>
</div>
</a>
{/if}
{/each}
</nav>
<style lang="scss">
#nav {
grid-area: nav;
display: flex;
flex-direction: column;
background: var(--bg-secondary);
padding: 2px 0;
overflow-y: auto;
contain: strict;
padding-bottom: 64px;
& > .wrapper {
padding: 2px 4px;
text-decoration: none;
&.selected > .highlight {
background: #ffffff33;
}
&.temp > .highlight {
border-left: solid var(--fg-dimmed) 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
margin-left: -4px;
}
&.child {
position: relative;
padding-left: 32px;
&::before {
content: "";
display: block;
position: absolute;
--size: 12px;
left: calc(4px + var(--size));
top: calc(50% - var(--size));
height: var(--size);
width: var(--size);
--color: #44535f;
border-bottom: solid var(--color) 1px;
border-left: solid var(--color) 1px;
}
}
& > .highlight {
display: flex;
border-radius: 2px;
overflow: hidden;
text-overflow: ellipsis;
& > .icon {
display: flex;
align-items: center;
padding: 4px;
}
& > .name {
flex: 1;
padding: 4px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
& > button {
padding: 4px;
border: none;
background: none;
color: inherit;
display: none;
text-decoration: none;
}
}
&:is(:hover, :focus-visible) > .highlight {
background: #ffffff22;
& > .name {
text-decoration: underline;
}
& > button {
display: block;
}
}
}
}
</style>