105 lines
2.7 KiB
TypeScript
105 lines
2.7 KiB
TypeScript
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>;
|
|
}[]
|
|
>([]);
|
|
|
|
const _anchorId = ref<string>();
|
|
const anchorId = computed(() => _anchorId.value);
|
|
const focusPath = ref<UiPath>(new UiPath());
|
|
const openPaths = ref<Set<string>>(new Set());
|
|
|
|
const pinned = ref<{ segment: Segment; parentId?: string }>();
|
|
|
|
// Ensure all nodes on the focusPath are unfolded.
|
|
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);
|
|
}
|
|
});
|
|
|
|
function pushAnchorId(id: string): void {
|
|
if (_anchorId.value) {
|
|
history.value.push({
|
|
anchorId: _anchorId.value,
|
|
focusPath: focusPath.value,
|
|
openPaths: openPaths.value,
|
|
});
|
|
}
|
|
|
|
_anchorId.value = id;
|
|
focusPath.value = new UiPath();
|
|
openPaths.value = new Set();
|
|
}
|
|
|
|
function popAnchorId(): void {
|
|
// Temporary solution until I implement some UI for anchorId===undefined
|
|
if (history.value.length === 0) return;
|
|
|
|
const entry = history.value.pop();
|
|
if (entry) {
|
|
_anchorId.value = entry.anchorId;
|
|
focusPath.value = entry.focusPath;
|
|
openPaths.value = entry.openPaths;
|
|
} else {
|
|
_anchorId.value = undefined;
|
|
focusPath.value = new UiPath();
|
|
openPaths.value = new Set();
|
|
}
|
|
}
|
|
|
|
function isOpen(path: UiPath): boolean {
|
|
return openPaths.value.has(path.fmt());
|
|
}
|
|
|
|
function setOpen(path: UiPath, value: boolean): void {
|
|
// Don't update openPaths unnecessarily.
|
|
// Just in case vue itself doesn't debounce Set operations.
|
|
if (value && !isOpen(path)) {
|
|
openPaths.value.add(path.fmt());
|
|
} else if (!value && isOpen(path)) {
|
|
// Move the focusPath if necessary
|
|
if (path.isPrefixOf(focusPath.value)) focusPath.value = path;
|
|
|
|
openPaths.value.delete(path.fmt());
|
|
}
|
|
}
|
|
|
|
function toggleOpen(path: UiPath): void {
|
|
setOpen(path, !isOpen(path));
|
|
}
|
|
|
|
function isPinned(segment: Segment, parentId?: string): boolean {
|
|
if (!pinned.value) return false;
|
|
return pinned.value.segment.eq(segment) && pinned.value.parentId === parentId;
|
|
}
|
|
|
|
function setPinned(segment: Segment, parentId?: string): void {
|
|
pinned.value = { segment, parentId };
|
|
}
|
|
|
|
function unsetPinned(): void {
|
|
pinned.value = undefined;
|
|
}
|
|
|
|
return {
|
|
anchorId,
|
|
focusPath,
|
|
pushAnchorId,
|
|
popAnchorId,
|
|
isOpen,
|
|
setOpen,
|
|
toggleOpen,
|
|
isPinned,
|
|
setPinned,
|
|
unsetPinned,
|
|
};
|
|
});
|