diff --git a/vuu-ui/packages/vuu-layout/src/index.ts b/vuu-ui/packages/vuu-layout/src/index.ts index f67818af5..18342616a 100644 --- a/vuu-ui/packages/vuu-layout/src/index.ts +++ b/vuu-ui/packages/vuu-layout/src/index.ts @@ -6,6 +6,7 @@ export * from "./DraggableLayout"; export * from "./flexbox"; export { Action } from "./layout-action"; export * from "./layout-header"; +export * from "./layout-persistence"; export * from "./layout-provider"; export * from "./layout-reducer"; export * from "./layout-view"; diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts new file mode 100644 index 000000000..eae323a67 --- /dev/null +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts @@ -0,0 +1,46 @@ +import { LayoutJSON } from "@finos/vuu-layout"; +import { LayoutMetadata } from "@finos/vuu-shell"; + +export interface LayoutPersistenceManager { + /** + * Saves a new layout + * + * @param metadata - Metadata about the layout to be saved + * @param layout - Full JSON representation of the layout to be saved + * + * @returns ID assigned to the saved layout + */ + createLayout: (metadata: Omit, layout: LayoutJSON) => string; + + /** + * Overwrites an existing layout with a new one + * + * @param id - Unique identifier of the existing layout to be updated + * @param metadata - Metadata describing the new layout to overwrite with + * @param layout - Full JSON representation of the new layout to overwrite with + */ + updateLayout: (id: string, metadata: Omit, layout: LayoutJSON) => void; + + /** + * Deletes an existing layout + * + * @param id - Unique identifier of the existing layout to be deleted + */ + deleteLayout: (id: string) => void; + + /** + * Retrieves an existing layout + * + * @param id - Unique identifier of the existing layout to be retrieved + * + * @returns the layout corresponding to provided metadata + */ + loadLayout: (id: string) => LayoutJSON; + + /** + * Retrieves metadata for all existing layouts + * + * @returns an array of all persisted layout metadata + */ + loadMetadata: () => LayoutMetadata[]; +} diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts new file mode 100644 index 000000000..19ddbb59e --- /dev/null +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts @@ -0,0 +1,79 @@ +import { Layout, LayoutMetadata } from "@finos/vuu-shell"; +import { LayoutJSON, LayoutPersistenceManager } from "@finos/vuu-layout"; + +import { getLocalEntity, saveLocalEntity } from "@finos/vuu-filters"; +import { getUniqueId } from "@finos/vuu-utils"; + +const metadataSaveLocation = "layouts/metadata"; +const layoutsSaveLocation = "layouts/layouts"; + +export class LocalLayoutPersistenceManager implements LayoutPersistenceManager { + createLayout(metadata: Omit, layout: LayoutJSON): string { + console.log(`Saving layout as ${metadata.name} to group ${metadata.group}...`); + + const existingLayouts = this.loadLayouts(); + const existingMetadata = this.loadMetadata(); + + const id = getUniqueId(); + + this.appendAndPersist(id, metadata, layout, existingLayouts, existingMetadata); + + return id; + } + + updateLayout(id: string, metadata: Omit, newLayoutJson: LayoutJSON): void { + const existingLayouts = this.loadLayouts().filter(layout => layout.id !== id); + const existingMetadata = this.loadMetadata().filter(metadata => metadata.id !== id); + + this.appendAndPersist(id, metadata, newLayoutJson, existingLayouts, existingMetadata); + } + + deleteLayout(id: string): void { + const layouts = this.loadLayouts().filter(layout => layout.id !== id); + const metadata = this.loadMetadata().filter(metadata => metadata.id !== id); + + this.saveLayoutsWithMetadata(layouts, metadata); + } + + loadLayout(id: string): LayoutJSON { + const layout = this.loadLayouts().filter(layout => layout.id === id); + + switch (layout.length) { + case 1: { + return layout[0].json; + } + case 0: { + console.log(`WARNING: no layout exists for ID "${id}"; returning empty layout`); + return {} as LayoutJSON; + } + default: { + console.log(`WARNING: multiple layouts exist for ID "${id}"; returning first instance`) + return layout[0].json; + } + } + } + + loadMetadata(): LayoutMetadata[] { + return getLocalEntity(metadataSaveLocation) || []; + } + + private loadLayouts(): Layout[] { + return getLocalEntity(layoutsSaveLocation) || []; + } + + private appendAndPersist(newId: string, + newMetadata: Omit, + newLayout: LayoutJSON, + existingLayouts: Layout[], + existingMetadata: LayoutMetadata[]) { + existingLayouts.push({id: newId, json: newLayout}); + existingMetadata.push({id: newId, ...newMetadata}); + + this.saveLayoutsWithMetadata(existingLayouts, existingMetadata); + } + + private saveLayoutsWithMetadata(layouts: Layout[], metadata: LayoutMetadata[]): void { + saveLocalEntity(layoutsSaveLocation, layouts); + saveLocalEntity(metadataSaveLocation, metadata); + } +} diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/index.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/index.ts new file mode 100644 index 000000000..602a44308 --- /dev/null +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/index.ts @@ -0,0 +1,2 @@ +export * from './LayoutPersistenceManager'; +export * from './LocalLayoutPersistenceManager'; diff --git a/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.tsx b/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.tsx index 2827530d9..01877f98c 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.tsx +++ b/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.tsx @@ -12,14 +12,12 @@ type LayoutGroups = { const classBase = "vuuLayoutList"; export const LayoutsList = (props: HTMLAttributes) => { - const { layouts } = useLayoutManager(); - - const layoutMetadata = layouts.map(layout => layout.metadata) + const { layoutMetadata } = useLayoutManager(); const handleLoadLayout = (layoutId?: string) => { // TODO load layout console.log("loading layout with id", layoutId) - console.log("json:", layouts.find(layout => layout.metadata.id === layoutId)) + console.log("json:", layoutMetadata.find(metadata => metadata.id === layoutId)) } const layoutsByGroup = layoutMetadata.reduce((acc: LayoutGroups, cur) => { diff --git a/vuu-ui/packages/vuu-shell/src/layout-management/layoutTypes.ts b/vuu-ui/packages/vuu-shell/src/layout-management/layoutTypes.ts index a59d42635..83e3387ec 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/layoutTypes.ts +++ b/vuu-ui/packages/vuu-shell/src/layout-management/layoutTypes.ts @@ -10,6 +10,6 @@ export type LayoutMetadata = { }; export type Layout = { + id: string, json: LayoutJSON; - metadata: LayoutMetadata; }; diff --git a/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx b/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx index 3ae6fc8b3..71f03e245 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx +++ b/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx @@ -1,47 +1,46 @@ import React, { useState, useCallback, useContext, useEffect } from "react"; -import { getLocalEntity, saveLocalEntity } from "@finos/vuu-filters"; -import { LayoutJSON } from "@finos/vuu-layout"; -import { getUniqueId } from "@finos/vuu-utils"; -import { LayoutMetadata, Layout } from "./layoutTypes"; +import { getLocalEntity } from "@finos/vuu-filters"; +import { LayoutJSON, LocalLayoutPersistenceManager } from "@finos/vuu-layout"; +import { LayoutMetadata } from "./layoutTypes"; + +const persistenceManager = new LocalLayoutPersistenceManager(); export const LayoutManagementContext = React.createContext<{ - layouts: Layout[], + layoutMetadata: LayoutMetadata[], saveLayout: (n: Omit) => void -}>({ layouts: [], saveLayout: () => { } }) - -export const LayoutManagementProvider = (props: { children: JSX.Element | JSX.Element[] }) => { +}>({ layoutMetadata: [], saveLayout: () => { } }) - const [layouts, setLayouts] = useState([]) +export const LayoutManagementProvider = (props: { + children: JSX.Element | JSX.Element[] + }) => { + const [layoutMetadata, setLayoutMetadata] = useState([]); useEffect(() => { - const layouts = getLocalEntity("layouts") - setLayouts(layouts || []) + const loadedMetadata = persistenceManager.loadMetadata(); + setLayoutMetadata(loadedMetadata || []) }, []) - useEffect(() => { - saveLocalEntity("layouts", layouts) - }, [layouts]) - const saveLayout = useCallback((metadata: Omit) => { - const json = getLocalEntity("api/vui") + const json = getLocalEntity("api/vui"); + if (json) { - setLayouts(prev => - [ - ...prev, - { - metadata: { - ...metadata, - id: getUniqueId() - }, - json - } - ] - ) + // Persist layouts + const generatedId = persistenceManager.createLayout(metadata, json); + + // Update state + const newMetadata: LayoutMetadata = { + ...metadata, + id: generatedId + }; + + setLayoutMetadata(prev => [...prev, newMetadata]); } }, []) + // TODO: add loadLayout function + return ( - + {props.children} ) diff --git a/vuu-ui/showcase/src/examples/Apps/NewTheme.examples.tsx b/vuu-ui/showcase/src/examples/Apps/NewTheme.examples.tsx index 8f4c9bc6b..c4a635a64 100644 --- a/vuu-ui/showcase/src/examples/Apps/NewTheme.examples.tsx +++ b/vuu-ui/showcase/src/examples/Apps/NewTheme.examples.tsx @@ -85,9 +85,6 @@ const ShellWithNewTheme = () => { const handleSave = useCallback( (layoutMetadata: Omit) => { - console.log( - `Save layout as ${layoutMetadata.name} to group ${layoutMetadata.group}` - ); saveLayout(layoutMetadata); setDialogContent(undefined); }, @@ -229,7 +226,6 @@ const ShellWithNewTheme = () => { style={{ maxHeight: 500, borderColor: "#6d188b" }} title={"Save Layout"} hideCloseButton - headerProps={{ className: "dialogHeader" }} > {dialogContent}