commit09bed34946Author: James Ritchie <james@jamesritchie.co.uk> Date: Fri Apr 8 14:03:50 2022 +0100 Fixed the layout of a search list item commit90126d43adAuthor: Eric Allam <eallam@icloud.com> Date: Fri Apr 1 17:25:13 2022 +0100 Insanely complicated path search results highlighitng and truncating commita4bbd5521eAuthor: James Ritchie <james@jamesritchie.co.uk> Date: Fri Apr 1 14:05:01 2022 +0100 Improved the title editing in the header commit5814053968Author: James Ritchie <james@jamesritchie.co.uk> Date: Fri Apr 1 13:38:33 2022 +0100 Improved text highlight colour commit110de37242Author: James Ritchie <james@jamesritchie.co.uk> Date: Fri Apr 1 13:36:57 2022 +0100 Icon hover state now uses isHighlighted commitfb6da943d9Author: James Ritchie <james@jamesritchie.co.uk> Date: Fri Apr 1 13:31:11 2022 +0100 Light mode styling commit2c378d5158Author: James Ritchie <james@jamesritchie.co.uk> Date: Fri Apr 1 12:24:43 2022 +0100 Spacing between search items no longer a hack commit85d90bb0aeAuthor: Eric Allam <eallam@icloud.com> Date: Fri Apr 1 12:19:10 2022 +0100 Added ellipsis to search matches if they are windowed commit91f3e70676Author: Eric Allam <eallam@icloud.com> Date: Fri Apr 1 12:08:00 2022 +0100 Fixed search item icons commite384c87c6fAuthor: James Ritchie <james@jamesritchie.co.uk> Date: Fri Apr 1 11:52:58 2022 +0100 Improved hover state commitf4681edc07Author: James Ritchie <james@jamesritchie.co.uk> Date: Fri Apr 1 11:47:33 2022 +0100 Removed focus state on button commitfaa2d0663cAuthor: Eric Allam <eallam@icloud.com> Date: Fri Apr 1 11:26:42 2022 +0100 made the search palette a little bigger commitdf8bd521feAuthor: Eric Allam <eallam@icloud.com> Date: Fri Apr 1 11:26:14 2022 +0100 Remove onOverlayClick prop commitecd046b3b2Author: Eric Allam <eallam@icloud.com> Date: Fri Apr 1 11:18:46 2022 +0100 Close search when the overlay is clicked commitfba330b5d9Author: Eric Allam <eallam@icloud.com> Date: Fri Apr 1 11:10:35 2022 +0100 hitting esc when search input is focused and empty should close the search commitfc2fe8ae6fAuthor: Eric Allam <eallam@icloud.com> Date: Fri Apr 1 11:00:08 2022 +0100 Highlight the window of the best search match in the search results commit45193ec92cAuthor: James Ritchie <james@jamesritchie.co.uk> Date: Fri Apr 1 10:45:01 2022 +0100 Now you can arrow down to the last item in the list commit0de5fef7e9Author: James Ritchie <james@jamesritchie.co.uk> Date: Fri Apr 1 10:37:20 2022 +0100 Added a hacky margin between items and remove the transition to make it feel snappier commit167b548343Author: Eric Allam <eallam@icloud.com> Date: Fri Apr 1 09:30:33 2022 +0100 WIP implementing search commit5655631f12Author: James Ritchie <james@jamesritchie.co.uk> Date: Fri Mar 25 15:28:19 2022 +0000 Build and styled search modal commite368db81c8Author: Eric Allam <eallam@icloud.com> Date: Fri Mar 25 14:42:03 2022 +0000 Implement JSON search through fuse.js + web worker + react hook commit8caa11ba1cAuthor: Eric Allam <eallam@icloud.com> Date: Wed Mar 23 10:21:33 2022 +0000 Start of the Command Palette using radix-ui Dialog commit904b64d781Author: Eric Allam <eallam@icloud.com> Date: Wed Mar 23 09:58:52 2022 +0000 Uncommented out the search bar field
205 lines
5.9 KiB
TypeScript
205 lines
5.9 KiB
TypeScript
import { inferType, JSONValueType } from "@jsonhero/json-infer-types";
|
|
import { JSONHeroPath, PathComponent } from "@jsonhero/path";
|
|
import { ColumnViewNode } from "~/useColumnView";
|
|
import { formatValue } from "./formatter";
|
|
import { iconForType } from "./icons";
|
|
|
|
export function generateColumnViewNode(json: unknown): ColumnViewNode {
|
|
const info = inferType(json);
|
|
const path = new JSONHeroPath("$");
|
|
const children = generateChildren(info, path);
|
|
|
|
return {
|
|
name: "root",
|
|
title: "root",
|
|
id: "$",
|
|
icon: iconForType(info),
|
|
children,
|
|
};
|
|
}
|
|
|
|
function generateChildren(
|
|
info: JSONValueType,
|
|
path: JSONHeroPath
|
|
): Array<ColumnViewNode> {
|
|
if (info.name === "array" && info.value) {
|
|
return info.value.map((value, index) => {
|
|
const childPath = path.child(index.toString());
|
|
const childInfo = inferType(value);
|
|
const children = generateChildren(childInfo, childPath);
|
|
|
|
return {
|
|
id: childPath.toString(),
|
|
name: index.toString(),
|
|
title: index.toString(),
|
|
longTitle: `Index ${index.toString()}`,
|
|
subtitle: formatValue(childInfo),
|
|
icon: iconForType(childInfo),
|
|
children,
|
|
};
|
|
});
|
|
}
|
|
|
|
if (info.name === "object" && info.value) {
|
|
return Object.entries(info.value).map(([key, value]) => {
|
|
const childPath = path.child(key);
|
|
const childInfo = inferType(value);
|
|
const children = generateChildren(childInfo, childPath);
|
|
|
|
return {
|
|
id: childPath.toString(),
|
|
name: key,
|
|
title: key,
|
|
subtitle: formatValue(childInfo),
|
|
icon: iconForType(childInfo),
|
|
children,
|
|
};
|
|
});
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
export function generateNodesToPath(
|
|
json: unknown,
|
|
path: string
|
|
): Array<ColumnViewNode> {
|
|
const heroPath = new JSONHeroPath(path);
|
|
|
|
const currentPathComponents: Array<PathComponent> = [];
|
|
|
|
const nodes: Array<ColumnViewNode> = [];
|
|
|
|
for (const component of heroPath.components) {
|
|
currentPathComponents.push(component);
|
|
|
|
const currentPath = new JSONHeroPath(currentPathComponents);
|
|
|
|
const info = inferType(currentPath.first(json));
|
|
|
|
const componentName = component.toString();
|
|
|
|
nodes.push({
|
|
name: componentName,
|
|
title: componentName === "$" ? "root" : componentName,
|
|
id: currentPath.toString(),
|
|
icon: iconForType(info),
|
|
children: [],
|
|
});
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
export function firstChildToDescendant(
|
|
ancestor: JSONHeroPath,
|
|
descendant: JSONHeroPath
|
|
): string | undefined {
|
|
const ancestorPathComponents = ancestor.components.map((component) =>
|
|
component.toString()
|
|
);
|
|
const descendantPathComponents = descendant.components.map((component) =>
|
|
component.toString()
|
|
);
|
|
|
|
const pathComponents = [];
|
|
|
|
for (let index = 0; index < descendantPathComponents.length; index++) {
|
|
const descendantPathComponent = descendantPathComponents[index];
|
|
|
|
if (ancestorPathComponents.length >= index) {
|
|
pathComponents.push(descendantPathComponent);
|
|
}
|
|
}
|
|
|
|
return pathComponents.join(".");
|
|
}
|
|
|
|
export function pathToDescendant(
|
|
ancestor: string,
|
|
descendant: string
|
|
): string | undefined {
|
|
const ancestorPath = new JSONHeroPath(ancestor);
|
|
const descendantPath = new JSONHeroPath(descendant);
|
|
|
|
const ancestorPathComponents = ancestorPath.components.map((component) =>
|
|
component.toString()
|
|
);
|
|
const descendantPathComponents = descendantPath.components.map((component) =>
|
|
component.toString()
|
|
);
|
|
|
|
const pathComponents = [];
|
|
|
|
for (let index = 0; index < descendantPathComponents.length; index++) {
|
|
const descendantPathComponent = descendantPathComponents[index];
|
|
|
|
if (ancestorPathComponents.length <= index) {
|
|
pathComponents.push(descendantPathComponent);
|
|
}
|
|
}
|
|
|
|
return pathComponents.join(".");
|
|
}
|
|
|
|
//Given the previous path and where the selection is, we calculate the new path
|
|
//This allows us to keep the deepest item possible still visible whilst navigating around
|
|
export function calculateStablePath(
|
|
previousPathString: string,
|
|
selectionPathString: string,
|
|
json: unknown
|
|
): string {
|
|
const previousPath = new JSONHeroPath(previousPathString);
|
|
const selectionPath = new JSONHeroPath(selectionPathString);
|
|
|
|
//if we are selecting at the right edge then the selection determines the path
|
|
if (selectionPath.components.length >= previousPath.components.length) {
|
|
return selectionPathString;
|
|
}
|
|
|
|
//if the selection is the same as the path from the start up to selection end then we leave the path as is
|
|
const previousPathWithSelectionLength = new JSONHeroPath(
|
|
previousPath.components.slice(0, selectionPath.components.length)
|
|
);
|
|
if (selectionPathString === previousPathWithSelectionLength.toString()) {
|
|
return previousPathString;
|
|
}
|
|
|
|
//from the start we add the selection components until they don't match the previous path (this should be until the last one)
|
|
let newComponents: PathComponent[] = [];
|
|
for (let index = 0; index < selectionPath.components.length; index++) {
|
|
const selectionComponent = selectionPath.components[index];
|
|
const previousComponent = previousPath.components[index];
|
|
|
|
//if they're different we need to bail and try build an alternative path
|
|
if (selectionComponent.toString() !== previousComponent.toString()) {
|
|
break;
|
|
}
|
|
|
|
newComponents.push(selectionComponent);
|
|
}
|
|
|
|
//we substitute all the remaining elements from the selection into the previousPath
|
|
const remainingSelectionComponents = selectionPath.components.slice(
|
|
newComponents.length
|
|
);
|
|
const remainingPreviousPathComponents = previousPath.components.slice(
|
|
selectionPath.components.length
|
|
);
|
|
const updatedPathComponents = [
|
|
...newComponents,
|
|
...remainingSelectionComponents,
|
|
...remainingPreviousPathComponents,
|
|
];
|
|
const updatedPath = new JSONHeroPath(updatedPathComponents);
|
|
|
|
const jsonAtUpdatedPath = updatedPath.first(json);
|
|
|
|
//not a match then we return the selection path
|
|
if (!jsonAtUpdatedPath) {
|
|
return selectionPathString;
|
|
} else {
|
|
return updatedPath.toString();
|
|
}
|
|
}
|