Skip to content

Instantly share code, notes, and snippets.

@jhurliman
Last active June 16, 2022 19:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jhurliman/8feaec0f37793c47bd8216cd5843f6fb to your computer and use it in GitHub Desktop.
Save jhurliman/8feaec0f37793c47bd8216cd5843f6fb to your computer and use it in GitHub Desktop.
Foxglove Studio high-level settings API
// 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