Implement controls for new UI state

This commit is contained in:
Joscha 2025-02-12 01:05:38 +01:00
parent 2f9b1925dc
commit 6e96ae7fd4
5 changed files with 113 additions and 22 deletions

View file

@ -9,7 +9,12 @@ const ui = useUiStore();
window.addEventListener("keypress", (ev) => { window.addEventListener("keypress", (ev) => {
if (document.activeElement !== document.body) return; if (document.activeElement !== document.body) return;
if (ev.key === "Escape" && ui.mode === "focus") { if (ev.key === "Escape") {
if (ui.mode !== "focus") {
ui.focus();
return;
}
const parent = ui.focusPath.parent(); const parent = ui.focusPath.parent();
if (parent) ui.focusOn(parent); if (parent) ui.focusOn(parent);
return; return;

View file

@ -3,10 +3,12 @@ import { Path, Segment } from "@/lib/path";
import { useNotesStore } from "@/stores/notes"; import { useNotesStore } from "@/stores/notes";
import { useUiStore } from "@/stores/ui"; import { useUiStore } from "@/stores/ui";
import { import {
RiAddLine,
RiArrowDownSLine, RiArrowDownSLine,
RiArrowDownWideLine,
RiArrowRightDoubleLine, RiArrowRightDoubleLine,
RiArrowRightSLine, RiArrowRightSLine,
RiArrowUpWideLine,
RiCornerDownRightLine,
RiCornerUpRightLine, RiCornerUpRightLine,
RiEditLine, RiEditLine,
RiPushpinFill, RiPushpinFill,
@ -14,6 +16,7 @@ import {
} from "@remixicon/vue"; } from "@remixicon/vue";
import { computed, ref, watchEffect } from "vue"; import { computed, ref, watchEffect } from "vue";
import CNoteButton from "./CNoteButton.vue"; import CNoteButton from "./CNoteButton.vue";
import CNoteChildEditor from "./CNoteChildEditor.vue";
import CNoteEditor from "./CNoteEditor.vue"; import CNoteEditor from "./CNoteEditor.vue";
const notes = useNotesStore(); const notes = useNotesStore();
@ -23,11 +26,13 @@ const {
path, path,
segment, segment,
parentId, parentId,
parentIndex = 0,
forceOpen = false, forceOpen = false,
} = defineProps<{ } = defineProps<{
path: Path; // From root to here path: Path; // From root to here
segment: Segment; segment: Segment;
parentId?: string; parentId?: string;
parentIndex?: number;
forceOpen?: boolean; forceOpen?: boolean;
}>(); }>();
@ -54,7 +59,6 @@ const children = computed(() => {
}); });
const hovering = ref(false); const hovering = ref(false);
const hover = computed(() => hovering.value && !editing.value);
const mayOpen = computed(() => children.value.length > 0); const mayOpen = computed(() => children.value.length > 0);
const open = computed(() => mayOpen.value && ui.isOpen(path)); const open = computed(() => mayOpen.value && ui.isOpen(path));
@ -78,9 +82,20 @@ function onClick(): void {
ui.toggleOpen(path); ui.toggleOpen(path);
} }
function onPinButtonClick(): void { function onInsertSiblingBeforeButtonClick(): void {
if (pinned.value) ui.unsetPinned(); const parent = path.parent();
else ui.setPinned(segment, parentId); if (!parent) return;
ui.insertAt(parent, parentIndex);
}
function onInsertSiblingAfterButtonClick(): void {
const parent = path.parent();
if (!parent) return;
ui.insertAt(parent, parentIndex + 1);
}
function onInsertChildButtonClick(): void {
ui.insertAt(path, children.value.length);
} }
function onEditButtonClick(): void { function onEditButtonClick(): void {
@ -98,9 +113,29 @@ function onEditEditorFinish(text: string): void {
onEditEditorClose(); onEditEditorClose();
} }
function onPinButtonClick(): void {
if (pinned.value) ui.unsetPinned();
else ui.setPinned(segment, parentId);
}
function onMoveButtonClick(): void { function onMoveButtonClick(): void {
ui.pushAnchorId(segment.id); ui.pushAnchorId(segment.id);
} }
function onInsertEditorClose(): void {
ui.focus();
}
function onInsertEditorFinish(text: string): void {
if (!note.value) return;
if (insertIndex.value !== undefined) {
const childNote = notes.createNote(text);
note.value.children.splice(insertIndex.value, 0, childNote.id);
}
onInsertEditorClose();
}
</script> </script>
<template> <template>
@ -114,7 +149,7 @@ function onMoveButtonClick(): void {
<div <div
class="relative flex min-h-6 pl-1" class="relative flex min-h-6 pl-1"
:class="focused ? 'bg-neutral-200' : hover ? 'bg-neutral-100' : ''" :class="focused ? 'bg-neutral-200' : hovering ? 'bg-neutral-100' : ''"
@mouseenter="hovering = true" @mouseenter="hovering = true"
@mouseleave="hovering = false" @mouseleave="hovering = false"
@click="onClick" @click="onClick"
@ -148,16 +183,41 @@ function onMoveButtonClick(): void {
<div v-else class="px-1 font-light italic">note not found</div> <div v-else class="px-1 font-light italic">note not found</div>
<!-- Controls --> <!-- Controls -->
<div class="absolute right-0 flex h-6 items-center gap-0.5"> <div v-show="!editing" class="absolute right-0 flex h-6 items-center gap-0.5">
<CNoteButton :visible="hover" @click.stop="onEditButtonClick"> <!-- Maybe this should be two separate fullsize buttons so they're easier to click. -->
<!-- Maybe I should reorder/group the buttons, especially once I add a delete button. -->
<div class="flex h-5 w-5 flex-col">
<button
class="flex h-0 grow select-none items-center justify-center rounded-t-sm border border-b-0 border-black bg-white p-0.5 text-black transition hover:bg-neutral-200 active:scale-95"
:class="{ invisible: !hovering }"
@click.stop="onInsertSiblingBeforeButtonClick"
>
<RiArrowUpWideLine size="16px" />
</button>
<button
class="flex h-0 grow select-none items-center justify-center rounded-b-sm border border-t-0 border-black bg-white p-0.5 text-black transition hover:bg-neutral-200 active:scale-95"
:class="{ invisible: !hovering }"
@click.stop="onInsertSiblingAfterButtonClick"
>
<RiArrowDownWideLine size="16px" />
</button>
</div>
<CNoteButton :visible="hovering" @click.stop="onInsertChildButtonClick">
<RiCornerDownRightLine size="16px" />
</CNoteButton>
<CNoteButton :visible="hovering" @click.stop="onEditButtonClick">
<RiEditLine size="16px" /> <RiEditLine size="16px" />
</CNoteButton> </CNoteButton>
<CNoteButton :visible="hover || pinned" :inverted="pinned" @click.stop="onPinButtonClick"> <CNoteButton
:visible="hovering || pinned"
:inverted="pinned"
@click.stop="onPinButtonClick"
>
<RiPushpinFill v-if="pinned" size="16px" /> <RiPushpinFill v-if="pinned" size="16px" />
<RiPushpinLine v-else size="16px" /> <RiPushpinLine v-else size="16px" />
</CNoteButton> </CNoteButton>
<CNoteButton <CNoteButton
:visible="hover" :visible="hovering"
:disabled="ui.anchorId === segment.id" :disabled="ui.anchorId === segment.id"
@click.stop="onMoveButtonClick" @click.stop="onMoveButtonClick"
> >
@ -167,19 +227,21 @@ function onMoveButtonClick(): void {
</div> </div>
<!-- Children --> <!-- Children -->
<div v-if="open && children.length > 0" class="flex flex-col pl-2"> <div v-if="open || insertIndex !== undefined" class="flex flex-col pl-2">
<div v-if="insertIndex === 0" class="flex items-start pl-3"> <CNoteChildEditor
<div class="flex h-6 items-center"><RiAddLine size="16px" /></div> v-if="insertIndex === 0"
<CNoteEditor class="flex-1" /> @close="onInsertEditorClose"
</div> @finish="onInsertEditorFinish"
/>
<template v-for="(child, index) of children" :key="child.fmt()"> <template v-for="(child, index) of children" :key="child.fmt()">
<CNote :path="path.concat(child)" :segment="child" :parent-id="id" /> <CNote :path="path.concat(child)" :segment="child" :parent-id="id" :parent-index="index" />
<div v-if="insertIndex === index + 1" class="flex items-start pl-3"> <CNoteChildEditor
<div class="flex h-6 items-center"><RiAddLine size="16px" /></div> v-if="insertIndex === index + 1"
<CNoteEditor class="flex-1" /> @close="onInsertEditorClose"
</div> @finish="onInsertEditorFinish"
/>
</template> </template>
</div> </div>
</div> </div>

View file

@ -12,7 +12,7 @@ const {
<template> <template>
<button <button
class="flex select-none items-center rounded-sm border border-black p-0.5 transition" class="flex h-5 w-5 select-none items-center justify-center rounded-sm border border-black transition"
:class="{ :class="{
'bg-white text-black': !inverted, 'bg-white text-black': !inverted,
'bg-black text-white': inverted, 'bg-black text-white': inverted,

View file

@ -0,0 +1,23 @@
<script setup lang="ts">
import { RiAddLine } from "@remixicon/vue";
import CNoteEditor from "./CNoteEditor.vue";
const emit = defineEmits<{
(e: "close"): void;
(e: "finish", text: string): void;
}>();
</script>
<template>
<div class="flex items-start pl-1">
<div class="flex h-6 items-center">
<RiAddLine size="16px" />
</div>
<CNoteEditor
class="flex-1"
@close="() => emit('close')"
@finish="(text) => emit('finish', text)"
/>
</div>
</template>

View file

@ -14,6 +14,7 @@ const emit = defineEmits<{
const textarea = useTemplateRef<HTMLTextAreaElement>("textarea"); const textarea = useTemplateRef<HTMLTextAreaElement>("textarea");
const text = ref(initialText); const text = ref(initialText);
// TODO Store text globally somewhere so it doesn't get lost when editor moves
onMounted(() => { onMounted(() => {
textarea.value?.focus(); textarea.value?.focus();