Refactor ui state
Now with more mode!
This commit is contained in:
parent
4710f19b1e
commit
2f9b1925dc
3 changed files with 119 additions and 85 deletions
|
|
@ -2,42 +2,62 @@ import { Segment, Path as UiPath } from "@/lib/path";
|
|||
import { defineStore } from "pinia";
|
||||
import { computed, ref, watchEffect } from "vue";
|
||||
|
||||
export const useUiStore = defineStore("ui", () => {
|
||||
const history = ref<
|
||||
{
|
||||
anchorId: string;
|
||||
focusPath: UiPath;
|
||||
openPaths: Set<string>;
|
||||
}[]
|
||||
>([]);
|
||||
interface HistoryEntry {
|
||||
anchorId: string;
|
||||
focusPath: UiPath;
|
||||
openPaths: Set<string>;
|
||||
}
|
||||
|
||||
type Mode =
|
||||
| { type: "focus" }
|
||||
| { type: "edit"; path: UiPath }
|
||||
| { type: "insert"; path: UiPath; index: number };
|
||||
|
||||
export const useUiStore = defineStore("ui", () => {
|
||||
const history = ref<HistoryEntry[]>([]);
|
||||
|
||||
// Managed by history
|
||||
const _anchorId = ref<string>();
|
||||
const anchorId = computed(() => _anchorId.value);
|
||||
const focusPath = ref<UiPath>(new UiPath());
|
||||
const _focusPath = ref<UiPath>(new UiPath());
|
||||
const openPaths = ref<Set<string>>(new Set());
|
||||
|
||||
const _mode = ref<Mode>({ type: "focus" });
|
||||
const pinned = ref<{ segment: Segment; parentId?: string }>();
|
||||
|
||||
// Ensure all nodes on the focusPath are unfolded.
|
||||
// Ensure the currently focused note is visible.
|
||||
watchEffect(() => {
|
||||
// The node pointed to by the path itself doesn't need to be unfolded.
|
||||
for (const ancestor of focusPath.value.ancestors().slice(1)) {
|
||||
setOpen(ancestor, true);
|
||||
if (_mode.value.type === "insert") {
|
||||
for (const ancestor of _mode.value.path.ancestors()) {
|
||||
setOpen(ancestor, true);
|
||||
}
|
||||
} else {
|
||||
// The node pointed to by the path itself doesn't need to be unfolded.
|
||||
for (const ancestor of _focusPath.value.ancestors().slice(1)) {
|
||||
setOpen(ancestor, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
///////////////////////////////////
|
||||
// History and anchor management //
|
||||
///////////////////////////////////
|
||||
|
||||
const anchorId = computed(() => _anchorId.value); // Getter
|
||||
|
||||
function pushAnchorId(id: string): void {
|
||||
if (_anchorId.value) {
|
||||
history.value.push({
|
||||
anchorId: _anchorId.value,
|
||||
focusPath: focusPath.value,
|
||||
focusPath: _focusPath.value,
|
||||
openPaths: openPaths.value,
|
||||
});
|
||||
}
|
||||
|
||||
_anchorId.value = id;
|
||||
focusPath.value = new UiPath();
|
||||
_focusPath.value = new UiPath();
|
||||
openPaths.value = new Set();
|
||||
|
||||
_mode.value = { type: "focus" };
|
||||
}
|
||||
|
||||
function popAnchorId(): void {
|
||||
|
|
@ -47,15 +67,60 @@ export const useUiStore = defineStore("ui", () => {
|
|||
const entry = history.value.pop();
|
||||
if (entry) {
|
||||
_anchorId.value = entry.anchorId;
|
||||
focusPath.value = entry.focusPath;
|
||||
_focusPath.value = entry.focusPath;
|
||||
openPaths.value = entry.openPaths;
|
||||
} else {
|
||||
_anchorId.value = undefined;
|
||||
focusPath.value = new UiPath();
|
||||
_focusPath.value = new UiPath();
|
||||
openPaths.value = new Set();
|
||||
}
|
||||
|
||||
_mode.value = { type: "focus" };
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// Mode and focus management //
|
||||
///////////////////////////////
|
||||
|
||||
const mode = computed(() => _mode.value.type);
|
||||
const focusPath = computed(() => _focusPath.value);
|
||||
|
||||
function isFocused(path: UiPath): boolean {
|
||||
return _mode.value.type === "focus" && _focusPath.value.eq(path);
|
||||
}
|
||||
|
||||
function isEditing(path: UiPath): boolean {
|
||||
return _mode.value.type === "edit" && _mode.value.path.eq(path);
|
||||
}
|
||||
|
||||
function getInsertIndex(path: UiPath): number | undefined {
|
||||
if (_mode.value.type !== "insert") return;
|
||||
if (!_mode.value.path.eq(path)) return;
|
||||
if (_mode.value.index < 0) return;
|
||||
return _mode.value.index;
|
||||
}
|
||||
|
||||
function focus(): void {
|
||||
_mode.value = { type: "focus" };
|
||||
}
|
||||
|
||||
function focusOn(path: UiPath): void {
|
||||
focus();
|
||||
_focusPath.value = path;
|
||||
}
|
||||
|
||||
function edit(path: UiPath): void {
|
||||
_mode.value = { type: "edit", path };
|
||||
}
|
||||
|
||||
function insertAt(path: UiPath, index: number): void {
|
||||
_mode.value = { type: "insert", path, index };
|
||||
}
|
||||
|
||||
//////////////////
|
||||
// Node folding //
|
||||
//////////////////
|
||||
|
||||
function isOpen(path: UiPath): boolean {
|
||||
return openPaths.value.has(path.fmt());
|
||||
}
|
||||
|
|
@ -67,7 +132,7 @@ export const useUiStore = defineStore("ui", () => {
|
|||
openPaths.value.add(path.fmt());
|
||||
} else if (!value && isOpen(path)) {
|
||||
// Move the focusPath if necessary
|
||||
if (path.isPrefixOf(focusPath.value)) focusPath.value = path;
|
||||
if (path.isPrefixOf(_focusPath.value)) _focusPath.value = path;
|
||||
|
||||
openPaths.value.delete(path.fmt());
|
||||
}
|
||||
|
|
@ -77,6 +142,10 @@ export const useUiStore = defineStore("ui", () => {
|
|||
setOpen(path, !isOpen(path));
|
||||
}
|
||||
|
||||
/////////////
|
||||
// Pinning //
|
||||
/////////////
|
||||
|
||||
function isPinned(segment: Segment, parentId?: string): boolean {
|
||||
if (!pinned.value) return false;
|
||||
return pinned.value.segment.eq(segment) && pinned.value.parentId === parentId;
|
||||
|
|
@ -92,9 +161,17 @@ export const useUiStore = defineStore("ui", () => {
|
|||
|
||||
return {
|
||||
anchorId,
|
||||
focusPath,
|
||||
pushAnchorId,
|
||||
popAnchorId,
|
||||
isFocused,
|
||||
isEditing,
|
||||
getInsertIndex,
|
||||
mode,
|
||||
focusPath,
|
||||
focus,
|
||||
focusOn,
|
||||
edit,
|
||||
insertAt,
|
||||
isOpen,
|
||||
setOpen,
|
||||
toggleOpen,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue