Line data Source code
1 : import * as React from 'react';
2 :
3 : type Action = Record<string, boolean>;
4 : type Dispatch = (action: Action) => void;
5 : type Listener = () => void;
6 :
7 : /// Hover changes on every mouseenter and mouseleave.
8 : /// We therefore keep the tool visibility in a store that notifies only the copy buttons of the changed nodes,
9 : /// instead of a context value that re-renders every copy button of the tree.
10 1 : export class ToolVisibilityStore {
11 1 : protected visible: Map<string, boolean> = new Map();
12 1 : protected listeners: Map<string, Set<Listener>> = new Map();
13 :
14 1 : public dispatch: Dispatch = (action: Action) => {
15 1 : for (const [key, value] of Object.entries(action)) {
16 1 : if ((this.visible.get(key) ?? false) === value) {
17 0 : continue;
18 : }
19 1 : this.visible.set(key, value);
20 1 : for (const listener of this.listeners.get(key) ?? []) {
21 1 : listener();
22 : }
23 1 : }
24 1 : };
25 1 : public subscribe(key: string, listener: Listener): () => void {
26 1 : let listeners = this.listeners.get(key);
27 1 : if (!listeners) {
28 1 : listeners = new Set();
29 1 : this.listeners.set(key, listeners);
30 1 : }
31 1 : listeners.add(listener);
32 1 : return () => {
33 1 : listeners.delete(listener);
34 1 : if (listeners.size == 0) {
35 1 : this.listeners.delete(key);
36 : }
37 1 : };
38 1 : }
39 1 : public isVisible(key: string): boolean {
40 1 : return this.visible.get(key) ?? false;
41 1 : }
42 1 : }
43 :
44 1 : const Context = React.createContext<ToolVisibilityStore | null>(null);
45 :
46 1 : export function useToolVisibility(key: string): boolean {
47 1 : const store = React.useContext(Context);
48 1 : const subscribe = React.useCallback((listener: Listener) => store?.subscribe(key, listener) ?? (() => { }), [store, key]);
49 1 : const getSnapshot = React.useCallback(() => store?.isVisible(key) ?? false, [store, key]);
50 1 : return React.useSyncExternalStore(subscribe, getSnapshot);
51 1 : }
52 :
53 1 : const noopDispatch: Dispatch = () => { };
54 :
55 1 : export function useToolVisibilityDispatch(): Dispatch {
56 1 : return React.useContext(Context)?.dispatch ?? noopDispatch;
57 1 : }
58 :
59 : interface ShowToolsProps {
60 : store: ToolVisibilityStore;
61 : }
62 :
63 1 : export const ToolVisibilityStateProvider: React.FC<React.PropsWithChildren<ShowToolsProps>> = ({ store, children }) => {
64 1 : return <Context.Provider value={store}>{children}</Context.Provider>;
65 1 : };
|