| | import { useSyncExternalStore } from 'react' |
| | import type { SegmentNodeState } from '../userspace/app/segment-explorer-node' |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | type TrieNode<Value = string> = { |
| | value: Value | undefined |
| | children: { |
| | [key: string]: TrieNode<Value> | undefined |
| | } |
| | } |
| |
|
| | type Trie<Value = string> = { |
| | insert: (value: Value) => void |
| | remove: (value: Value) => void |
| | getRoot: () => TrieNode<Value> |
| | } |
| |
|
| | const listeners = new Set<() => void>() |
| | const createSegmentTreeStore = (): { |
| | subscribe: (callback: () => void) => () => void |
| | getSnapshot: () => SegmentTrieNode |
| | getServerSnapshot: () => SegmentTrieNode |
| | } => { |
| | |
| | return { |
| | subscribe: (callback) => { |
| | listeners.add(callback) |
| | return () => listeners.delete(callback) |
| | }, |
| | getSnapshot: () => { |
| | return trie.getRoot() |
| | }, |
| | getServerSnapshot: () => { |
| | return trie.getRoot() |
| | }, |
| | } |
| | } |
| |
|
| | |
| | const { subscribe, getSnapshot, getServerSnapshot } = createSegmentTreeStore() |
| |
|
| | function createTrie<Value = string>({ |
| | getCharacters = (item: Value) => [item] as string[], |
| | compare = (a: Value | undefined, b: Value | undefined) => a === b, |
| | }: { |
| | getCharacters?: (item: Value) => string[] |
| | compare?: (a: Value | undefined, b: Value | undefined) => boolean |
| | }): Trie<Value> { |
| | let root: TrieNode<Value> = { |
| | value: undefined, |
| | children: {}, |
| | } |
| |
|
| | function markUpdated() { |
| | for (const listener of listeners) { |
| | listener() |
| | } |
| | } |
| |
|
| | function insert(value: Value) { |
| | let currentNode = root |
| | const segments = getCharacters(value) |
| |
|
| | for (const segment of segments) { |
| | if (!currentNode.children[segment]) { |
| | currentNode.children[segment] = { |
| | value: undefined, |
| | |
| | children: {}, |
| | } |
| | } |
| | currentNode = currentNode.children[segment] |
| | } |
| |
|
| | currentNode.value = value |
| |
|
| | root = { ...root } |
| | markUpdated() |
| | } |
| |
|
| | function remove(value: Value) { |
| | let currentNode = root |
| | const segments = getCharacters(value) |
| |
|
| | const stack: TrieNode<Value>[] = [] |
| | let found = true |
| | for (const segment of segments) { |
| | if (!currentNode.children[segment]) { |
| | found = false |
| | break |
| | } |
| | stack.push(currentNode) |
| | currentNode = currentNode.children[segment]! |
| | } |
| | |
| | if (!found || !compare(currentNode.value, value)) { |
| | return |
| | } |
| | currentNode.value = undefined |
| | for (let i = stack.length - 1; i >= 0; i--) { |
| | const parentNode = stack[i] |
| | const segment = segments[i] |
| | if (Object.keys(parentNode.children[segment]!.children).length === 0) { |
| | delete parentNode.children[segment] |
| | } |
| | } |
| |
|
| | root = { ...root } |
| | markUpdated() |
| | } |
| |
|
| | function getRoot(): TrieNode<Value> { |
| | return root |
| | } |
| |
|
| | return { insert, remove, getRoot } |
| | } |
| |
|
| | type SegmentTrie = Trie<SegmentNodeState> |
| | export type SegmentTrieNode = TrieNode<SegmentNodeState> |
| |
|
| | const trie: SegmentTrie = createTrie({ |
| | compare: (a, b) => { |
| | if (!a || !b) return false |
| | return ( |
| | a.pagePath === b.pagePath && |
| | a.type === b.type && |
| | a.boundaryType === b.boundaryType |
| | ) |
| | }, |
| | getCharacters: (item) => item.pagePath.split('/'), |
| | }) |
| | export const insertSegmentNode = trie.insert |
| | export const removeSegmentNode = trie.remove |
| | export const getSegmentTrieRoot = trie.getRoot |
| |
|
| | export function useSegmentTree(): SegmentTrieNode { |
| | const state = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) |
| | return state |
| | } |
| |
|