Last active
June 16, 2022 19:29
-
-
Save jhurliman/8feaec0f37793c47bd8216cd5843f6fb to your computer and use it in GitHub Desktop.
Foxglove Studio high-level settings API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This Source Code Form is subject to the terms of the Mozilla Public | |
// License, v2.0. If a copy of the MPL was not distributed with this | |
// file, You can obtain one at http://mozilla.org/MPL/2.0/ | |
import { produce } from "immer"; | |
import { | |
SettingsTreeAction, | |
SettingsTreeNode, | |
SettingsTreeRoots, | |
} from "@foxglove/studio-base/components/SettingsTreeEditor/types"; | |
import { Path } from "./LayerErrors"; | |
type ActionHandler = (action: SettingsTreeAction) => void; | |
type SettingsTreeNodeWithActionHandler = SettingsTreeNode & { handler?: ActionHandler }; | |
type SettingsTreeEntry = { path: Path; node: SettingsTreeNodeWithActionHandler }; | |
export class SettingsManager { | |
private _nodesByKey = new Map<string, SettingsTreeEntry[]>(); | |
private _root: SettingsTreeNodeWithActionHandler = { children: {} }; | |
setNodesForKey(key: string, nodes: SettingsTreeEntry[]): void { | |
this._root = produce(this._root, (draft) => { | |
// Delete all previous nodes for this key | |
const prevNodes = this._nodesByKey.get(key); | |
if (prevNodes) { | |
for (const { path } of prevNodes) { | |
removeNodeAtPath(draft, path); | |
} | |
} | |
// Add the new nodes | |
for (const { path, node } of nodes) { | |
addNodeAtPath(draft, path, node); | |
} | |
// Update the map of nodes by key | |
this._nodesByKey.set(key, nodes); | |
}); | |
} | |
settingsTree(): SettingsTreeRoots { | |
return this._root.children!; | |
} | |
handleAction(action: SettingsTreeAction): void { | |
const path = action.payload.path; | |
// Walk the settings tree down to the end of the path, firing any action | |
// handlers along the way | |
let curNode = this._root; | |
for (const segment of path) { | |
if (curNode.handler) { | |
curNode.handler(action); | |
} | |
const nextNode = curNode.children?.[segment]; | |
if (!nextNode) { | |
return; | |
} | |
curNode = nextNode; | |
} | |
} | |
} | |
function removeNodeAtPath(root: SettingsTreeNode, path: Path): boolean { | |
if (path.length === 0) { | |
return false; | |
} | |
const segment = path[0]!; | |
const nextNode = root.children?.[segment]; | |
if (!nextNode) { | |
return false; | |
} | |
if (path.length === 1) { | |
const hasEntry = root.children?.[segment] != undefined; | |
if (hasEntry) { | |
delete root.children![segment]; | |
} | |
return hasEntry; | |
} | |
return removeNodeAtPath(nextNode, path.slice(1)); | |
} | |
function addNodeAtPath(root: SettingsTreeNode, path: Path, node: SettingsTreeNode): void { | |
if (path.length === 0) { | |
throw new Error(`Empty path for settings node "${node.label}"`); | |
} | |
// Recursively walk/build the settings tree down to the end of the path | |
let curNode = root; | |
for (let i = 0; i < path.length - 1; i++) { | |
const segment = path[i]!; | |
if (!curNode.children) { | |
curNode.children = {}; | |
} | |
if (!curNode.children[segment]) { | |
curNode.children[segment] = {}; | |
} | |
curNode = curNode.children[segment]!; | |
} | |
// Assign the node to the last segment of the path | |
const lastSegment = path[path.length - 1]!; | |
if (!curNode.children) { | |
curNode.children = {}; | |
} | |
curNode.children[lastSegment] = node; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment