Skip to content

Commit

Permalink
add resolveJSONPath utility method (finos#887)
Browse files Browse the repository at this point in the history
  • Loading branch information
heswell authored Oct 2, 2023
1 parent cb32528 commit 35b8e16
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 11 deletions.
38 changes: 33 additions & 5 deletions vuu-ui/packages/vuu-layout/src/layout-provider/LayoutProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import {
} from "react";
import {
LayoutActionType,
LayoutChangeHandler,
LayoutChangeReason,
layoutFromJson,
LayoutJSON,
layoutReducer,
LayoutReducerAction,
layoutToJSON,
processLayoutElement,
SaveAction,
} from "../layout-reducer";
import { findTarget, getChildProp, getProp, getProps, typeOf } from "../utils";
import {
Expand All @@ -34,7 +37,31 @@ const shouldSave = (action: LayoutReducerAction) =>
"switch-tab",
].includes(action.type);

type LayoutChangeHandler = (layout: LayoutJSON, source: string) => void;
const getLayoutChangeReason = (
action: LayoutReducerAction | SaveAction
): LayoutChangeReason => {
switch (action.type) {
case "switch-tab":
// TODO how can we make this more robust, shouldn't rely on 'main-tabs'
if (action.id === "main-tabs") {
return "switch-active-layout";
} else {
return "switch-active-tab";
}
case "save":
return "save-feature-props";
case "drag-drop":
return "drag-drop-operation";
case "remove":
return "remove-component";
case "splitter-resize":
return "resize-component";
case "set-title":
return "edit-feature-title";
default:
throw Error("unknown layout action");
}
};

export interface LayoutProviderProps {
children: ReactElement;
Expand All @@ -58,7 +85,8 @@ export const LayoutProvider = (props: LayoutProviderProps): ReactElement => {
const [, forceRefresh] = useState<unknown>(null);

const serializeState = useCallback(
(source) => {
(source, layoutChangeReason: LayoutChangeReason) => {
console.log(`serialize state ${layoutChangeReason}`);
if (onLayoutChange) {
const targetContainer =
findTarget(source, withDropTarget) || state.current;
Expand All @@ -67,7 +95,7 @@ export const LayoutProvider = (props: LayoutProviderProps): ReactElement => {
? getProps(targetContainer).children[0]
: targetContainer;
const serializedModel = layoutToJSON(target);
onLayoutChange(serializedModel, "drag-root");
onLayoutChange(serializedModel, layoutChangeReason);
}
},
[onLayoutChange]
Expand All @@ -80,7 +108,7 @@ export const LayoutProvider = (props: LayoutProviderProps): ReactElement => {
state.current = nextState;
forceRefresh({});
if (!suppressSave && shouldSave(action)) {
serializeState(nextState);
serializeState(nextState, getLayoutChangeReason(action));
}
}
},
Expand All @@ -95,7 +123,7 @@ export const LayoutProvider = (props: LayoutProviderProps): ReactElement => {
break;
}
case "save": {
serializeState(state.current);
serializeState(state.current, getLayoutChangeReason(action));
break;
}
default: {
Expand Down
44 changes: 44 additions & 0 deletions vuu-ui/packages/vuu-layout/src/layout-reducer/layoutTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export type LayoutResizeAction = {
};

export type SwitchTabAction = {
id?: string;
nextIdx: number;
path: string;
type: typeof LayoutActionType.SWITCH_TAB;
Expand Down Expand Up @@ -193,3 +194,46 @@ export type DragStartAction = {
path: string;
type: typeof LayoutActionType.DRAG_START;
};

export type LayoutLevelChange =
| "switch-active-tab"
| "edit-feature-title"
| "save-feature-props"
| "resize-component"
| "remove-component"
| "drag-drop-operation";

export type ApplicationLevelChange =
| "switch-active-layout"
| "open-layout"
| "close-layout"
| "rename-layout";

export type LayoutChangeReason = LayoutLevelChange | ApplicationLevelChange;

export type LayoutChangeHandler = (
layout: LayoutJSON,
layoutChangeReason: LayoutChangeReason
) => void;

export const isApplicationLevelChange = (
layoutChangeReason: LayoutChangeReason
): layoutChangeReason is ApplicationLevelChange =>
[
"switch-active-layout",
"open-layout",
"close-layout",
"rename-layout",
].includes(layoutChangeReason);

export const isLayoutLevelChange = (
layoutChangeReason: LayoutChangeReason
): layoutChangeReason is LayoutLevelChange =>
[
"switch-active-tab",
"edit-feature-title",
"save-feature-props",
"remove-component",
"resize-component",
"drag-drop-operation",
].includes(layoutChangeReason);
2 changes: 1 addition & 1 deletion vuu-ui/packages/vuu-layout/src/stack/StackLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const StackLayout = (props: StackProps) => {

const handleTabSelection = (nextIdx: number) => {
if (path) {
dispatch({ type: "switch-tab", path, nextIdx });
dispatch({ type: "switch-tab", id, path, nextIdx });
onTabSelectionChanged?.(nextIdx);
}
};
Expand Down
52 changes: 51 additions & 1 deletion vuu-ui/packages/vuu-layout/src/utils/pathUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { isValidElement, ReactElement } from "react";
import { LayoutModel } from "../layout-reducer";
import { LayoutJSON, LayoutModel, WithActive } from "../layout-reducer";
import { isContainer } from "../registry/ComponentRegistry";
import { getProp, getProps } from "./propUtils";
import { typeOf } from "./typeOf";
Expand Down Expand Up @@ -42,6 +42,30 @@ export const resolvePath = (source: ReactElement, path = ""): string => {
return "";
};

/**
* Similar to resolvePath but operates on a JSON
* layout structure and returns the matching JSON node.
*/
export const resolveJSONPath = (
source: LayoutJSON,
path = ""
): LayoutJSON | undefined => {
const [step1, ...steps] = path.split(".");
if (step1?.startsWith("#")) {
const node = findTargetJSONById(source, step1.slice(1), true);
if (node && steps.length) {
return resolveJSONPath(node, steps.join("."));
}
} else if (step1 === "ACTIVE_CHILD") {
const { children, props } = source;
const { active } = props as WithActive;
if (typeof active === "number" && children?.[active]) {
return children[active];
}
}
return;
};

export function followPathToParent(
source: ReactElement,
path: string
Expand Down Expand Up @@ -146,6 +170,32 @@ const findTargetById = (
}
};

const findTargetJSONById = (
source: LayoutJSON,
id: string,
throwIfNotFound = true
): LayoutJSON | undefined => {
const { children, id: idProp } = source;
if (idProp === id) {
return source;
}

if (Array.isArray(children) && children.length > 0) {
for (const child of children) {
if (child !== null && typeof child === "object") {
const target = findTargetJSONById(child, id, false);
if (target) {
return target;
}
}
}
}

if (throwIfNotFound === true) {
throw Error(`pathUtils.findTargetJSONById id #${id} not found in source`);
}
};

export function followPath(
source: LayoutModel,
path: string
Expand Down
6 changes: 5 additions & 1 deletion vuu-ui/packages/vuu-shell/src/layout-config/local-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { LayoutJSON } from "@finos/vuu-layout/src/layout-reducer";
import { resolveJSONPath } from "@finos/vuu-layout";
import { VuuUser } from "../shell";

export const loadLocalConfig = (
Expand Down Expand Up @@ -26,7 +27,10 @@ export const saveLocalConfig = (
): Promise<undefined> =>
new Promise((resolve, reject) => {
try {
console.log(`save local config at ${saveUrl}`);
// Just for demonstration,not currently being used
const layoutJson = resolveJSONPath(data, "#main-tabs.ACTIVE_CHILD");
console.log(layoutJson);

localStorage.setItem(saveUrl, JSON.stringify(data));
resolve(undefined);
} catch {
Expand Down
10 changes: 7 additions & 3 deletions vuu-ui/packages/vuu-shell/src/shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import {
LayoutProvider,
LayoutProviderProps,
} from "@finos/vuu-layout";
import { LayoutJSON } from "@finos/vuu-layout/src/layout-reducer";
import {
LayoutChangeHandler,
LayoutJSON,
} from "@finos/vuu-layout/src/layout-reducer";
import { AppHeader } from "./app-header";
import { ThemeMode, ThemeProvider, useThemeAttributes } from "./theme-provider";
import { logger } from "@finos/vuu-utils";
Expand Down Expand Up @@ -85,9 +88,10 @@ export const Shell = ({
user,
});

const handleLayoutChange = useCallback(
(layout) => {
const handleLayoutChange = useCallback<LayoutChangeHandler>(
(layout, layoutChangeReason) => {
try {
console.log(`handle layout changed ${layoutChangeReason}`);
saveLayoutConfig(layout);
} catch {
error?.("Failed to save layout");
Expand Down

0 comments on commit 35b8e16

Please sign in to comment.