From f92841b68637977113d483c244591d8c9994ccee Mon Sep 17 00:00:00 2001 From: vferraro-scottlogic Date: Mon, 25 Sep 2023 15:59:57 +0100 Subject: [PATCH 01/16] VUU-27 interface to return promises --- .../LayoutPersistenceManager.ts | 10 +- .../LocalLayoutPersistenceManager.ts | 112 +++++++++++------- .../vuu-layout/src/layout-persistence/data.ts | 14 +++ .../layout-management/useLayoutManager.tsx | 24 ++-- 4 files changed, 98 insertions(+), 62 deletions(-) create mode 100644 vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts index 4dc086207..b392b5242 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts @@ -10,7 +10,7 @@ export interface LayoutPersistenceManager { * * @returns Unique identifier assigned to the saved layout */ - createLayout: (metadata: Omit, layout: LayoutJSON) => string; + createLayout: (metadata: Omit, layout: LayoutJSON) => Promise; /** * Overwrites an existing layout and its corresponding metadata with the provided infromation @@ -19,14 +19,14 @@ export interface LayoutPersistenceManager { * @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; + updateLayout: (id: string, metadata: Omit, layout: LayoutJSON) => Promise; /** * Deletes an existing layout and its corresponding metadata * * @param id - Unique identifier of the existing layout to be deleted */ - deleteLayout: (id: string) => void; + deleteLayout: (id: string) => Promise; /** * Retrieves an existing layout @@ -35,12 +35,12 @@ export interface LayoutPersistenceManager { * * @returns Full JSON representation of the layout corresponding to the provided ID */ - loadLayout: (id: string) => LayoutJSON; + loadLayout: (id: string) => Promise; /** * Retrieves metadata for all existing layouts * * @returns an array of all persisted layout metadata */ - loadMetadata: () => LayoutMetadata[]; + loadMetadata: () => Promise; } diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts index 19ddbb59e..2f4907c5f 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts @@ -3,76 +3,96 @@ import { LayoutJSON, LayoutPersistenceManager } from "@finos/vuu-layout"; import { getLocalEntity, saveLocalEntity } from "@finos/vuu-filters"; import { getUniqueId } from "@finos/vuu-utils"; +import { warningLayout } from "./data"; 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; + createLayout(metadata: Omit, layout: LayoutJSON): Promise { + return new Promise(async (resolve) =>{ + console.log(`Saving layout as ${metadata.name} to group ${metadata.group}...`); + + const existingLayouts = this.loadLayouts(); + const existingMetadata = await this.loadMetadata(); + + const id = getUniqueId(); + + this.appendAndPersist(id, metadata, layout, existingLayouts, existingMetadata); + + resolve(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); + updateLayout(id: string, metadata: Omit, newLayoutJson: LayoutJSON): Promise { + return new Promise(async (resolve)=> { + const existingLayouts = this.loadLayouts().filter(layout => layout.id !== id); + const existingMetadata = (await this.loadMetadata()).filter(metadata => metadata.id !== id); + this.appendAndPersist(id, metadata, newLayoutJson, existingLayouts, existingMetadata); + resolve() + }) } - 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); + deleteLayout(id: string): Promise { + return new Promise(async (resolve) => { + const layouts = this.loadLayouts().filter((layout) => layout.id !== id); + const metadata = ( await this.loadMetadata()).filter(metadata => metadata.id !== id); + this.saveLayoutsWithMetadata(layouts, metadata); + resolve() + }); } - 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; + loadLayout(id: string): Promise { + return new Promise((resolve) => { + const layout = this.loadLayouts().filter((layout) => layout.id === id); + + switch (layout.length) { + case 1: { + resolve(layout[0].json); + break; + } + case 0: { + console.log(`WARNING: no layout exists for ID "${id}"; returning empty layout`); + resolve(warningLayout); + break; + } + default: { + console.log(`WARNING: multiple layouts exist for ID "${id}"; returning first instance`); + resolve(layout[0].json); + break; + } } - } + }); } - loadMetadata(): LayoutMetadata[] { - return getLocalEntity(metadataSaveLocation) || []; + loadMetadata(): Promise { + return new Promise((resolve) => { + const metadata = getLocalEntity(metadataSaveLocation); + resolve(metadata || []) + }) } 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}); + 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 { + private saveLayoutsWithMetadata( + layouts: Layout[], + metadata: LayoutMetadata[] + ): void { saveLocalEntity(layoutsSaveLocation, layouts); saveLocalEntity(metadataSaveLocation, metadata); } diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts new file mode 100644 index 000000000..b8a75b159 --- /dev/null +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts @@ -0,0 +1,14 @@ +export const warningLayout = { + type: "View", + props: { + style: { height: "calc(100% - 6px)" }, + }, + children: [ + { + props: { + className: "vuuShell-warningPlaceholder", + }, + type: "Placeholder", + }, + ], + }; \ No newline at end of file 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 71f03e245..c92e0674d 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx +++ b/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx @@ -11,13 +11,14 @@ export const LayoutManagementContext = React.createContext<{ }>({ layoutMetadata: [], saveLayout: () => { } }) export const LayoutManagementProvider = (props: { - children: JSX.Element | JSX.Element[] - }) => { + children: JSX.Element | JSX.Element[] +}) => { const [layoutMetadata, setLayoutMetadata] = useState([]); useEffect(() => { - const loadedMetadata = persistenceManager.loadMetadata(); - setLayoutMetadata(loadedMetadata || []) + persistenceManager.loadMetadata().then(loadedMetadata => { + setLayoutMetadata(loadedMetadata) + }) }, []) const saveLayout = useCallback((metadata: Omit) => { @@ -25,15 +26,16 @@ export const LayoutManagementProvider = (props: { if (json) { // Persist layouts - const generatedId = persistenceManager.createLayout(metadata, json); + persistenceManager.createLayout(metadata, json).then(generatedId => { - // Update state - const newMetadata: LayoutMetadata = { - ...metadata, - id: generatedId - }; + // Update state + const newMetadata: LayoutMetadata = { + ...metadata, + id: generatedId + }; - setLayoutMetadata(prev => [...prev, newMetadata]); + setLayoutMetadata(prev => [...prev, newMetadata]); + }) } }, []) From a455ad29bdc5546dc4ce672db1e0a34550ed73e4 Mon Sep 17 00:00:00 2001 From: vferraro-scottlogic Date: Wed, 27 Sep 2023 16:25:04 +0100 Subject: [PATCH 02/16] VUU-47 add methods for loading and saving tempLayout --- .../LayoutPersistenceManager.ts | 14 +++ .../LocalLayoutPersistenceManager.ts | 102 +++++++++++++----- .../vuu-layout/src/layout-persistence/data.ts | 79 ++++++++++++-- .../src/layout-persistence/index.ts | 1 + .../src/layout-reducer/layoutTypes.ts | 3 +- .../layout-management/useLayoutManager.tsx | 65 ++++++----- vuu-ui/packages/vuu-shell/src/shell.tsx | 34 +----- .../src/examples/Apps/NewTheme.examples.tsx | 60 ----------- 8 files changed, 208 insertions(+), 150 deletions(-) diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts index b392b5242..4b8469ee2 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts @@ -43,4 +43,18 @@ export interface LayoutPersistenceManager { * @returns an array of all persisted layout metadata */ loadMetadata: () => Promise; + + /** + * Retrieves a temporary layout + * + * @returns Full JSON representation of a layouts + */ + loadTempLayout: () => Promise; + + /** + * Saves a temporary layout + * + * @param layout - Full JSON representation of layout to be saved + */ + saveTempLayout: (layout: LayoutJSON) => Promise } diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts index 2f4907c5f..c270e1b42 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts @@ -1,44 +1,70 @@ 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"; -import { warningLayout } from "./data"; + +import { defaultLayout, warningLayout } from "./data"; const metadataSaveLocation = "layouts/metadata"; const layoutsSaveLocation = "layouts/layouts"; export class LocalLayoutPersistenceManager implements LayoutPersistenceManager { - createLayout(metadata: Omit, layout: LayoutJSON): Promise { - return new Promise(async (resolve) =>{ - console.log(`Saving layout as ${metadata.name} to group ${metadata.group}...`); - + createLayout( + metadata: Omit, + layout: LayoutJSON + ): Promise { + return new Promise(async (resolve) => { + console.log( + `Saving layout as ${metadata.name} to group ${metadata.group}...` + ); + const existingLayouts = this.loadLayouts(); const existingMetadata = await this.loadMetadata(); - + const id = getUniqueId(); - - this.appendAndPersist(id, metadata, layout, existingLayouts, existingMetadata); - + + this.appendAndPersist( + id, + metadata, + layout, + existingLayouts, + existingMetadata + ); resolve(id); - }) + }); } - updateLayout(id: string, metadata: Omit, newLayoutJson: LayoutJSON): Promise { - return new Promise(async (resolve)=> { - const existingLayouts = this.loadLayouts().filter(layout => layout.id !== id); - const existingMetadata = (await this.loadMetadata()).filter(metadata => metadata.id !== id); - this.appendAndPersist(id, metadata, newLayoutJson, existingLayouts, existingMetadata); - resolve() - }) + updateLayout( + id: string, + metadata: Omit, + newLayoutJson: LayoutJSON + ): Promise { + return new Promise(async (resolve) => { + const existingLayouts = this.loadLayouts().filter( + (layout) => layout.id !== id + ); + const existingMetadata = (await this.loadMetadata()).filter( + (metadata) => metadata.id !== id + ); + this.appendAndPersist( + id, + metadata, + newLayoutJson, + existingLayouts, + existingMetadata + ); + resolve(); + }); } deleteLayout(id: string): Promise { return new Promise(async (resolve) => { const layouts = this.loadLayouts().filter((layout) => layout.id !== id); - const metadata = ( await this.loadMetadata()).filter(metadata => metadata.id !== id); - this.saveLayoutsWithMetadata(layouts, metadata); - resolve() + const metadata = (await this.loadMetadata()).filter( + (metadata) => metadata.id !== id + ); + this.saveLayoutsWithMetadata(layouts, metadata); + resolve(); }); } @@ -52,12 +78,16 @@ export class LocalLayoutPersistenceManager implements LayoutPersistenceManager { break; } case 0: { - console.log(`WARNING: no layout exists for ID "${id}"; returning empty layout`); + console.log( + `WARNING: no layout exists for ID "${id}"; returning empty layout` + ); resolve(warningLayout); break; } default: { - console.log(`WARNING: multiple layouts exist for ID "${id}"; returning first instance`); + console.log( + `WARNING: multiple layouts exist for ID "${id}"; returning first instance` + ); resolve(layout[0].json); break; } @@ -68,8 +98,30 @@ export class LocalLayoutPersistenceManager implements LayoutPersistenceManager { loadMetadata(): Promise { return new Promise((resolve) => { const metadata = getLocalEntity(metadataSaveLocation); - resolve(metadata || []) - }) + resolve(metadata || []); + }); + } + + loadTempLayout(): Promise { + return new Promise((resolve, reject) => { + const tempLayout = getLocalEntity("api/vui"); + if (tempLayout) { + resolve(tempLayout); + } else { + resolve(defaultLayout); + } + }); + } + + saveTempLayout(layout: LayoutJSON): Promise { + return new Promise((resolve, reject) => { + const savedLayout = saveLocalEntity("api/vui", layout); + if (savedLayout) { + resolve(); + } else { + reject(new Error("Layout failed to save")); + } + }); } private loadLayouts(): Layout[] { diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts index b8a75b159..4d5903a7a 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts @@ -1,14 +1,71 @@ -export const warningLayout = { - type: "View", - props: { - style: { height: "calc(100% - 6px)" }, +import { LayoutJSON } from "../layout-reducer"; + +export const warningLayout: LayoutJSON = { + type: "View", + props: { + style: { height: "calc(100% - 6px)" }, + }, + children: [ + { + props: { + className: "vuuShell-warningPlaceholder", + }, + type: "Placeholder", + }, + ], +}; + +export const defaultLayout: LayoutJSON = { + type: "Stack", + id: "main-tabs", + props: { + className: "vuuShell-mainTabs", + TabstripProps: { + allowAddTab: true, + allowRenameTab: true, + animateSelectionThumb: false, + location: "main-tab", }, - children: [ - { - props: { - className: "vuuShell-warningPlaceholder", + preserve: true, + active: 0, + }, + children: [ + { + type: "Stack", + props: { + active: 0, + title: "My Instruments", + TabstripProps: { + allowRenameTab: true, + allowCloseTab: true, }, - type: "Placeholder", }, - ], - }; \ No newline at end of file + children: [ + { + type: "View", + props: { + title: "European Stock", + }, + style: { height: "calc(100% - 6px)" }, + children: [ + { + type: "FilterTable", + }, + ], + }, + { + type: "View", + props: { + title: "Other Stock", + }, + style: { height: "calc(100% - 6px)" }, + children: [ + { + type: "FilterTable", + }, + ], + }, + ], + }, + ], +} \ No newline at end of file diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/index.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/index.ts index 602a44308..a047506db 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/index.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/index.ts @@ -1,2 +1,3 @@ export * from './LayoutPersistenceManager'; export * from './LocalLayoutPersistenceManager'; +export * from './data'; \ No newline at end of file diff --git a/vuu-ui/packages/vuu-layout/src/layout-reducer/layoutTypes.ts b/vuu-ui/packages/vuu-layout/src/layout-reducer/layoutTypes.ts index 81c602eb2..558152f24 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-reducer/layoutTypes.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-reducer/layoutTypes.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { ReactElement } from "react"; +import { CSSProperties, ReactElement } from "react"; import { DragDropRect, DragInstructions } from "../drag-drop"; import { DropTarget } from "../drag-drop/DropTarget"; import { ContributionLocation } from "../layout-view"; @@ -26,6 +26,7 @@ export interface LayoutJSON extends WithType { props?: { [key: string]: any }; state?: any; type: string; + style?: CSSProperties; } export interface WithActive { 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 c92e0674d..9b1d2078c 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx +++ b/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx @@ -1,48 +1,65 @@ import React, { useState, useCallback, useContext, useEffect } from "react"; -import { getLocalEntity } from "@finos/vuu-filters"; import { LayoutJSON, LocalLayoutPersistenceManager } from "@finos/vuu-layout"; import { LayoutMetadata } from "./layoutTypes"; +import { defaultLayout } from "@finos/vuu-layout/"; const persistenceManager = new LocalLayoutPersistenceManager(); export const LayoutManagementContext = React.createContext<{ layoutMetadata: LayoutMetadata[], - saveLayout: (n: Omit) => void -}>({ layoutMetadata: [], saveLayout: () => { } }) + saveLayout: (n: Omit) => void, + currentLayout: LayoutJSON, + saveCurrentLayout: (layout: LayoutJSON) => void, + loadLayoutById: (id: string) => void +}>({ + layoutMetadata: [], + saveLayout: () => { }, + currentLayout: defaultLayout, + saveCurrentLayout: () => { }, + loadLayoutById: () => defaultLayout +}) -export const LayoutManagementProvider = (props: { +type LayoutManagementProviderProps = { children: JSX.Element | JSX.Element[] -}) => { +} + +export const LayoutManagementProvider = (props: LayoutManagementProviderProps) => { const [layoutMetadata, setLayoutMetadata] = useState([]); + const [currentLayout, setCurrentLayout] = useState(defaultLayout); useEffect(() => { - persistenceManager.loadMetadata().then(loadedMetadata => { - setLayoutMetadata(loadedMetadata) - }) + Promise.all([persistenceManager.loadMetadata(), persistenceManager.loadTempLayout()]) + .then(([metadata, layout]) => { + setLayoutMetadata(metadata) + setCurrentLayout(layout); + }) }, []) - const saveLayout = useCallback((metadata: Omit) => { - const json = getLocalEntity("api/vui"); + const saveCurrentLayout = useCallback((layout: LayoutJSON) => { + setCurrentLayout(layout) + persistenceManager.saveTempLayout(layout) + }, []); - if (json) { - // Persist layouts - persistenceManager.createLayout(metadata, json).then(generatedId => { + const saveLayout = useCallback((metadata: Omit) => { + persistenceManager.createLayout(metadata, currentLayout).then(generatedId => { - // Update state - const newMetadata: LayoutMetadata = { - ...metadata, - id: generatedId - }; + const newMetadata: LayoutMetadata = { + ...metadata, + id: generatedId + }; - setLayoutMetadata(prev => [...prev, newMetadata]); - }) - } - }, []) + setLayoutMetadata(prev => [...prev, newMetadata]); + }) + }, [currentLayout]) - // TODO: add loadLayout function + const loadLayoutById = useCallback((id: string) => { + persistenceManager.loadLayout(id).then((layoutJson) => { + setCurrentLayout(layoutJson) + }) + }, []); return ( - + {props.children} ) diff --git a/vuu-ui/packages/vuu-shell/src/shell.tsx b/vuu-ui/packages/vuu-shell/src/shell.tsx index 13834f787..10c06aa2d 100644 --- a/vuu-ui/packages/vuu-shell/src/shell.tsx +++ b/vuu-ui/packages/vuu-shell/src/shell.tsx @@ -8,18 +8,17 @@ import { useEffect, useRef, } from "react"; -import { useLayoutConfig } from "./layout-config"; import { DraggableLayout, LayoutProvider, LayoutProviderProps, } from "@finos/vuu-layout"; -import { 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"; import { useShellLayout } from "./shell-layouts"; import { SaveLocation } from "./shellTypes"; +import { useLayoutManager } from "./layout-management"; import "./shell.css"; @@ -30,28 +29,12 @@ export type VuuUser = { const { error } = logger("Shell"); -const warningLayout = { - type: "View", - props: { - style: { height: "calc(100% - 6px)" }, - }, - children: [ - { - props: { - className: "vuuShell-warningPlaceholder", - }, - type: "Placeholder", - }, - ], -}; - export interface ShellProps extends HTMLAttributes { LayoutProps?: Pick< LayoutProviderProps, "createNewChild" | "pathToDropTarget" >; children?: ReactNode; - defaultLayout?: LayoutJSON; leftSidePanel?: ReactElement; leftSidePanelLayout?: "full-height" | "inlay"; loginUrl?: string; @@ -66,7 +49,6 @@ export const Shell = ({ LayoutProps, children, className: classNameProp, - defaultLayout = warningLayout, leftSidePanel, leftSidePanelLayout, loginUrl, @@ -78,22 +60,17 @@ export const Shell = ({ }: ShellProps) => { const rootRef = useRef(null); const layoutId = useRef("latest"); - const [layout, saveLayoutConfig, loadLayoutById] = useLayoutConfig({ - defaultLayout, - saveLocation, - saveUrl, - user, - }); + const { currentLayout, saveCurrentLayout, loadLayoutById } = useLayoutManager(); const handleLayoutChange = useCallback( (layout) => { try { - saveLayoutConfig(layout); + saveCurrentLayout(layout); } catch { error?.("Failed to save layout"); } }, - [saveLayoutConfig] + [currentLayout] ); const handleSwitchTheme = useCallback((mode: ThemeMode) => { @@ -140,8 +117,7 @@ export const Shell = ({ return ( { ]; }, [handleCloseDialog, handleSave]); - //TODO what the App actually receives is an array of layouts - const layout = useMemo(() => { - return { - type: "Stack", - id: "main-tabs", - props: { - className: "vuuShell-mainTabs", - TabstripProps: { - allowAddTab: true, - allowRenameTab: true, - animateSelectionThumb: false, - location: "main-tab", - }, - preserve: true, - active: 0, - }, - children: [ - { - type: "Stack", - props: { - active: 0, - title: "My Instruments", - TabstripProps: { - allowRenameTab: true, - allowCloseTab: true, - }, - }, - children: [ - { - type: "View", - props: { - title: "European Stock", - }, - style: { height: "calc(100% - 6px)" }, - children: [ - { - type: "FilterTable", - }, - ], - }, - { - type: "View", - props: { - title: "Other Stock", - }, - style: { height: "calc(100% - 6px)" }, - children: [ - { - type: "FilterTable", - }, - ], - }, - ], - }, - ], - }; - }, []); - return ( { LayoutProps={{ pathToDropTarget: "#main-tabs.ACTIVE_CHILD", }} - defaultLayout={layout} leftSidePanelLayout="full-height" leftSidePanel={ { } loginUrl={window.location.toString()} user={user} - saveLocation="local" style={ { "--vuuShell-height": "100vh", From f426b4e47c037d76e3f7a4d9a772db43ebbec34d Mon Sep 17 00:00:00 2001 From: vferraro-scottlogic Date: Wed, 27 Sep 2023 16:25:37 +0100 Subject: [PATCH 03/16] VUU-47 use loadLayoutById in LayoutList --- .../vuu-shell/src/layout-management/LayoutList.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 01877f98c..df0279135 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.tsx +++ b/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.tsx @@ -12,12 +12,12 @@ type LayoutGroups = { const classBase = "vuuLayoutList"; export const LayoutsList = (props: HTMLAttributes) => { - const { layoutMetadata } = useLayoutManager(); + const { layoutMetadata, loadLayoutById } = useLayoutManager(); const handleLoadLayout = (layoutId?: string) => { - // TODO load layout - console.log("loading layout with id", layoutId) - console.log("json:", layoutMetadata.find(metadata => metadata.id === layoutId)) + if (layoutId) { + loadLayoutById(layoutId) + } } const layoutsByGroup = layoutMetadata.reduce((acc: LayoutGroups, cur) => { From 02bb6af230e9d451920f1150f38b2e7a10b2554f Mon Sep 17 00:00:00 2001 From: vferraro-scottlogic Date: Wed, 27 Sep 2023 16:26:02 +0100 Subject: [PATCH 04/16] VUU-47 remove unused files --- vuu-ui/packages/vuu-shell/src/index.ts | 1 - .../vuu-shell/src/layout-config/index.ts | 1 - .../src/layout-config/local-config.ts | 35 ----------- .../src/layout-config/remote-config.ts | 50 --------------- .../src/layout-config/use-layout-config.ts | 61 ------------------- 5 files changed, 148 deletions(-) delete mode 100644 vuu-ui/packages/vuu-shell/src/layout-config/index.ts delete mode 100644 vuu-ui/packages/vuu-shell/src/layout-config/local-config.ts delete mode 100644 vuu-ui/packages/vuu-shell/src/layout-config/remote-config.ts delete mode 100644 vuu-ui/packages/vuu-shell/src/layout-config/use-layout-config.ts diff --git a/vuu-ui/packages/vuu-shell/src/index.ts b/vuu-ui/packages/vuu-shell/src/index.ts index 5e16606fe..c6e19420d 100644 --- a/vuu-ui/packages/vuu-shell/src/index.ts +++ b/vuu-ui/packages/vuu-shell/src/index.ts @@ -1,7 +1,6 @@ export * from "./connection-status"; export * from "./density-switch"; export * from "./feature"; -export * from "./layout-config"; export * from "./layout-management"; export * from "./left-nav"; export * from "./login"; diff --git a/vuu-ui/packages/vuu-shell/src/layout-config/index.ts b/vuu-ui/packages/vuu-shell/src/layout-config/index.ts deleted file mode 100644 index 0203a2231..000000000 --- a/vuu-ui/packages/vuu-shell/src/layout-config/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./use-layout-config"; diff --git a/vuu-ui/packages/vuu-shell/src/layout-config/local-config.ts b/vuu-ui/packages/vuu-shell/src/layout-config/local-config.ts deleted file mode 100644 index b8ad539a0..000000000 --- a/vuu-ui/packages/vuu-shell/src/layout-config/local-config.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { LayoutJSON } from "@finos/vuu-layout/src/layout-reducer"; -import { VuuUser } from "../shell"; - -export const loadLocalConfig = ( - saveUrl: string, - user?: VuuUser, - id = "latest" -): Promise => - new Promise((resolve, reject) => { - console.log( - `load local config at ${saveUrl} for user ${user?.username}, id ${id}` - ); - const data = localStorage.getItem(saveUrl); - if (data) { - const layout = JSON.parse(data); - resolve(layout); - } else { - reject(); - } - }); - -export const saveLocalConfig = ( - saveUrl: string, - user: VuuUser | undefined, - data: LayoutJSON -): Promise => - new Promise((resolve, reject) => { - try { - console.log(`save local config at ${saveUrl}`); - localStorage.setItem(saveUrl, JSON.stringify(data)); - resolve(undefined); - } catch { - reject(); - } - }); diff --git a/vuu-ui/packages/vuu-shell/src/layout-config/remote-config.ts b/vuu-ui/packages/vuu-shell/src/layout-config/remote-config.ts deleted file mode 100644 index 54c0a95df..000000000 --- a/vuu-ui/packages/vuu-shell/src/layout-config/remote-config.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { LayoutJSON } from "@finos/vuu-layout/src/layout-reducer"; -import { VuuUser } from "../shell"; - -export const loadRemoteConfig = ( - saveUrl: string, - user: VuuUser | undefined, - id = "latest" -): Promise => - new Promise((resolve, reject) => { - if (user === undefined) { - throw Error("user mustb be provided to load remote config"); - } - fetch(`${saveUrl}/${user.username}/${id}`, {}) - .then((response) => { - if (response.ok) { - resolve(response.json()); - } else { - reject(undefined); - } - }) - .catch(() => { - // TODO we should set a layout with a warning here - // setLayout(defaultLayout); - reject(undefined); - }); - }); - -export const saveRemoteConfig = ( - saveUrl: string, - user: VuuUser | undefined, - data: LayoutJSON -) => - new Promise((resolve, reject) => { - if (user === undefined) { - throw Error("user mustb be provided to load remote config"); - } - fetch(`${saveUrl}/${user.username}`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }).then((response) => { - if (response.ok) { - resolve(undefined); - } else { - reject(); - } - }); - }); diff --git a/vuu-ui/packages/vuu-shell/src/layout-config/use-layout-config.ts b/vuu-ui/packages/vuu-shell/src/layout-config/use-layout-config.ts deleted file mode 100644 index db06ee428..000000000 --- a/vuu-ui/packages/vuu-shell/src/layout-config/use-layout-config.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { LayoutJSON } from "@finos/vuu-layout/src/layout-reducer"; -import { useCallback, useEffect, useState } from "react"; -import { VuuUser } from "../shell"; -import { SaveLocation } from "../shellTypes"; -import { loadLocalConfig, saveLocalConfig } from "./local-config"; -import { loadRemoteConfig, saveRemoteConfig } from "./remote-config"; - -export interface LayoutConfigHookProps { - defaultLayout?: LayoutJSON; - saveLocation: SaveLocation; - saveUrl?: string; - user?: VuuUser; -} - -export type LayoutHookResult = [ - LayoutJSON, - (layout: LayoutJSON) => void, - (id: string) => void -]; - -const FALLBACK_LAYOUT = { type: "Placeholder" }; - -export const useLayoutConfig = ({ - saveLocation, - saveUrl = "api/vui", - user, - // TODO this should be an error panel - defaultLayout = FALLBACK_LAYOUT, -}: LayoutConfigHookProps): LayoutHookResult => { - const [layout, _setLayout] = useState(defaultLayout); - const usingRemote = saveLocation === "remote"; - const loadConfig = usingRemote ? loadRemoteConfig : loadLocalConfig; - const saveConfig = usingRemote ? saveRemoteConfig : saveLocalConfig; - - const load = useCallback( - async (id = "latest") => { - try { - const layout = await loadConfig(saveUrl, user, id); - _setLayout(layout); - } catch { - _setLayout(defaultLayout); - } - }, - [defaultLayout, loadConfig, saveUrl, user] - ); - - useEffect(() => { - load(); - }, [load]); - - const saveData = useCallback( - (data) => { - saveConfig(saveUrl, user, data); - }, - [saveConfig, saveUrl, user] - ); - - const loadLayoutById = useCallback((id) => load(id), [load]); - - return [layout, saveData, loadLayoutById]; -}; From 13283dd6fe0fd92ab688b7511e024b3ca8fd9f88 Mon Sep 17 00:00:00 2001 From: vferraro-scottlogic Date: Wed, 27 Sep 2023 16:26:36 +0100 Subject: [PATCH 05/16] VUU-47 update other examples to use new hook --- .../BasketTradingFeature.examples.tsx | 16 ++++++---------- .../VuuFeatures/FilterTableFeature.examples.tsx | 16 ++++++---------- .../InstrumentTilesFeature.examples.tsx | 16 ++++++---------- .../VuuFeatures/TableNextFeature.examples.tsx | 16 ++++++---------- 4 files changed, 24 insertions(+), 40 deletions(-) diff --git a/vuu-ui/showcase/src/examples/VuuFeatures/BasketTradingFeature.examples.tsx b/vuu-ui/showcase/src/examples/VuuFeatures/BasketTradingFeature.examples.tsx index fd738d2e6..127bbd530 100644 --- a/vuu-ui/showcase/src/examples/VuuFeatures/BasketTradingFeature.examples.tsx +++ b/vuu-ui/showcase/src/examples/VuuFeatures/BasketTradingFeature.examples.tsx @@ -1,5 +1,5 @@ import { LayoutProvider, View } from "@finos/vuu-layout"; -import { Feature, FeatureProps, useLayoutConfig } from "@finos/vuu-shell"; +import { Feature, FeatureProps, useLayoutManager } from "@finos/vuu-shell"; import { useCallback, useEffect } from "react"; import { BasketTradingFeature } from "../../features/BasketTrading.feature"; import { useTableSchema } from "../utils"; @@ -18,27 +18,23 @@ export const DefaultBasketTradingFeature = () => { // Likewise the Shell provides the LayoutProvider wrapper. Again, in a full Vuu // application, the Palette wraps each feature in a View. //----------------------------------------------------------------------------------- - const [layout, saveLayoutConfig] = useLayoutConfig({ - // save to local storage. Use browser devtools to purge this - saveLocation: "local", - saveUrl: "table-next-feature", - }); + const { currentLayout, saveCurrentLayout} = useLayoutManager(); useEffect(() => { console.log(`%clayout changed`, "color: blue; font-weight: bold;"); - }, [layout]); + }, [currentLayout]); const handleLayoutChange = useCallback( (layout) => { console.log("layout change"); - saveLayoutConfig(layout); + saveCurrentLayout(layout); }, - [saveLayoutConfig] + [saveCurrentLayout] ); // ---------------------------------------------------------------------------------- return ( - + { // Likewise the Shell provides the LayoutProvider wrapper. Again, in a full Vuu // application, the Palette wraps each feature in a View. //----------------------------------------------------------------------------------- - const [layout, saveLayoutConfig] = useLayoutConfig({ - // save to local storage. Use browser devtools to purge this - saveLocation: "local", - saveUrl: "table-next-feature", - }); + const {currentLayout, saveCurrentLayout} = useLayoutManager(); useEffect(() => { console.log(`%clayout changed`, "color: blue; font-weight: bold;"); - }, [layout]); + }, [currentLayout]); const handleLayoutChange = useCallback( (layout) => { console.log("layout change"); - saveLayoutConfig(layout); + saveCurrentLayout(layout); }, - [saveLayoutConfig] + [saveCurrentLayout] ); // ---------------------------------------------------------------------------------- return ( - + { // Likewise the Shell provides the LayoutProvider wrapper. Again, in a full Vuu // application, the Palette wraps each feature in a View. //----------------------------------------------------------------------------------- - const [layout, saveLayoutConfig] = useLayoutConfig({ - // save to local storage. Use browser devtools to purge this - saveLocation: "local", - saveUrl: "table-next-feature", - }); + const {currentLayout, saveCurrentLayout} = useLayoutManager(); useEffect(() => { console.log(`%clayout changed`, "color: blue; font-weight: bold;"); - }, [layout]); + }, [currentLayout]); const handleLayoutChange = useCallback( (layout) => { console.log("layout change"); - saveLayoutConfig(layout); + saveCurrentLayout(layout); }, - [saveLayoutConfig] + [saveCurrentLayout] ); // ---------------------------------------------------------------------------------- return ( - + { // Likewise the Shell provides the LayoutProvider wrapper. Again, in a full Vuu // application, the Palette wraps each feature in a View. //----------------------------------------------------------------------------------- - const [layout, saveLayoutConfig] = useLayoutConfig({ - // save to local storage. Use browser devtools to purge this - saveLocation: "local", - saveUrl: "table-next-feature", - }); + const {currentLayout, saveCurrentLayout} = useLayoutManager(); useEffect(() => { console.log(`%clayout changed`, "color: blue; font-weight: bold;"); - }, [layout]); + }, [currentLayout]); const handleLayoutChange = useCallback( (layout) => { console.log("layout change"); - saveLayoutConfig(layout); + saveCurrentLayout(layout); }, - [saveLayoutConfig] + [saveCurrentLayout] ); // ---------------------------------------------------------------------------------- return ( - + Date: Mon, 2 Oct 2023 13:30:51 +0100 Subject: [PATCH 06/16] VUU-47 improve naming --- .../layout-persistence/LayoutPersistenceManager.ts | 4 ++-- .../LocalLayoutPersistenceManager.ts | 12 ++++++------ .../src/layout-management/useLayoutManager.tsx | 13 +++++++------ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts index 4b8469ee2..9fc877c6c 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts @@ -49,12 +49,12 @@ export interface LayoutPersistenceManager { * * @returns Full JSON representation of a layouts */ - loadTempLayout: () => Promise; + loadCurrentLayout: () => Promise; /** * Saves a temporary layout * * @param layout - Full JSON representation of layout to be saved */ - saveTempLayout: (layout: LayoutJSON) => Promise + saveCurrentLayout: (layout: LayoutJSON) => Promise } diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts index c270e1b42..9fedf42db 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts @@ -102,18 +102,18 @@ export class LocalLayoutPersistenceManager implements LayoutPersistenceManager { }); } - loadTempLayout(): Promise { - return new Promise((resolve, reject) => { - const tempLayout = getLocalEntity("api/vui"); - if (tempLayout) { - resolve(tempLayout); + loadCurrentLayout(): Promise { + return new Promise((resolve) => { + const currentLayout = getLocalEntity("api/vui"); + if (currentLayout) { + resolve(currentLayout); } else { resolve(defaultLayout); } }); } - saveTempLayout(layout: LayoutJSON): Promise { + saveCurrentLayout(layout: LayoutJSON): Promise { return new Promise((resolve, reject) => { const savedLayout = saveLocalEntity("api/vui", layout); if (savedLayout) { 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 9b1d2078c..94671b8cc 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx +++ b/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx @@ -28,16 +28,17 @@ export const LayoutManagementProvider = (props: LayoutManagementProviderProps) = const [currentLayout, setCurrentLayout] = useState(defaultLayout); useEffect(() => { - Promise.all([persistenceManager.loadMetadata(), persistenceManager.loadTempLayout()]) - .then(([metadata, layout]) => { - setLayoutMetadata(metadata) - setCurrentLayout(layout); - }) + persistenceManager.loadMetadata().then(metadata => { + setLayoutMetadata(metadata) + }) + persistenceManager.loadCurrentLayout().then(layout => { + setCurrentLayout(layout); + }) }, []) const saveCurrentLayout = useCallback((layout: LayoutJSON) => { setCurrentLayout(layout) - persistenceManager.saveTempLayout(layout) + persistenceManager.saveCurrentLayout(layout) }, []); const saveLayout = useCallback((metadata: Omit) => { From 34798c6df0d58eaa1b8618e2153b4de2d7329a27 Mon Sep 17 00:00:00 2001 From: vferraro-scottlogic Date: Mon, 2 Oct 2023 13:34:11 +0100 Subject: [PATCH 07/16] VUU-47 use placeholder in defaultLayout --- .../vuu-layout/src/layout-persistence/data.ts | 37 ++----------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts index 4d5903a7a..22b5012ae 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts @@ -31,41 +31,10 @@ export const defaultLayout: LayoutJSON = { }, children: [ { - type: "Stack", props: { - active: 0, - title: "My Instruments", - TabstripProps: { - allowRenameTab: true, - allowCloseTab: true, - }, + className: "vuuShell-Placeholder", }, - children: [ - { - type: "View", - props: { - title: "European Stock", - }, - style: { height: "calc(100% - 6px)" }, - children: [ - { - type: "FilterTable", - }, - ], - }, - { - type: "View", - props: { - title: "Other Stock", - }, - style: { height: "calc(100% - 6px)" }, - children: [ - { - type: "FilterTable", - }, - ], - }, - ], + type: "Placeholder", }, ], -} \ No newline at end of file +}; From c5e295e16b2fb5ef232906666dedef9c43e92a69 Mon Sep 17 00:00:00 2001 From: vferraro-scottlogic Date: Mon, 2 Oct 2023 15:14:30 +0100 Subject: [PATCH 08/16] VUU-47 update docs with new naming --- .../src/layout-persistence/LayoutPersistenceManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts index 9fc877c6c..9af1e1628 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts @@ -45,14 +45,14 @@ export interface LayoutPersistenceManager { loadMetadata: () => Promise; /** - * Retrieves a temporary layout + * Retrieves the current layout * * @returns Full JSON representation of a layouts */ loadCurrentLayout: () => Promise; /** - * Saves a temporary layout + * Saves the current layout * * @param layout - Full JSON representation of layout to be saved */ From 2eac507e6a7dd029f7351693fd21d579806659f3 Mon Sep 17 00:00:00 2001 From: vferraro-scottlogic Date: Tue, 3 Oct 2023 12:09:09 +0100 Subject: [PATCH 09/16] VUU-47 fix layoutList styling --- .../src/layout-management/LayoutList.css | 2 ++ .../src/layout-management/LayoutList.tsx | 30 ++++++++----------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.css b/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.css index 44d383b16..eb5aaa5dd 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.css +++ b/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.css @@ -1,5 +1,7 @@ .vuuLayoutList { align-self: stretch; + height: 100%; + overflow: hidden; } .vuuLayoutList-header { 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 df0279135..e866a8e6e 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.tsx +++ b/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.tsx @@ -41,25 +41,21 @@ export const LayoutsList = (props: HTMLAttributes) => { source={Object.entries(layoutsByGroup)} ListItem={({ item }) => <>
{item?.[0]}
- - height='fit-content' - source={item?.[1]} - ListItem={({ item: layout }) => -
handleLoadLayout(layout?.id)} - > - -
-
{layout?.name}
-
-
{`${layout?.user}, ${layout?.date}`}
-
+ {item?.[1].map(layout => +
handleLoadLayout(layout?.id)} + > + +
+
{layout?.name}
+
+
{`${layout?.user}, ${layout?.date}`}
- } - /> +
+ )} } /> From 4e91e942faaec3380154b2d88f0e95430118b02e Mon Sep 17 00:00:00 2001 From: vferraro-scottlogic Date: Wed, 4 Oct 2023 10:08:28 +0100 Subject: [PATCH 10/16] VUU-47 add loaded layouts to layout view --- .../vuu-shell/src/layout-management/useLayoutManager.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 94671b8cc..42ee5bb9b 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx +++ b/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx @@ -55,7 +55,10 @@ export const LayoutManagementProvider = (props: LayoutManagementProviderProps) = const loadLayoutById = useCallback((id: string) => { persistenceManager.loadLayout(id).then((layoutJson) => { - setCurrentLayout(layoutJson) + setCurrentLayout(prev => ({ + ...prev, + children: [...(prev.children || []), ...(layoutJson.children || [])] + })) }) }, []); From d677a0b09632d0d343f0fd1f4e1f9721bd92b978 Mon Sep 17 00:00:00 2001 From: vferraro-scottlogic Date: Thu, 5 Oct 2023 17:16:10 +0100 Subject: [PATCH 11/16] VUU-47 rename currentLayout to applicationLayout --- .../LayoutPersistenceManager.ts | 12 ++--- .../LocalLayoutPersistenceManager.ts | 4 +- .../src/layout-management/LayoutList.tsx | 36 ++++++++------ .../layout-management/useLayoutManager.tsx | 49 ++++++++++--------- vuu-ui/packages/vuu-shell/src/shell.tsx | 8 +-- 5 files changed, 59 insertions(+), 50 deletions(-) diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts index 9af1e1628..670cc7f15 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts @@ -45,16 +45,16 @@ export interface LayoutPersistenceManager { loadMetadata: () => Promise; /** - * Retrieves the current layout + * Retrieves the application layout which includes all layouts on screen * - * @returns Full JSON representation of a layouts + * @returns Full JSON representation of the application layout */ - loadCurrentLayout: () => Promise; + loadApplicationLayout: () => Promise; /** - * Saves the current layout + * Saves the current layout which includes all layouts on screen * - * @param layout - Full JSON representation of layout to be saved + * @param layout - Full JSON representation of the application layout to be saved */ - saveCurrentLayout: (layout: LayoutJSON) => Promise + saveApplicationLayout: (layout: LayoutJSON) => Promise } diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts index 9fedf42db..d659403b5 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts @@ -102,7 +102,7 @@ export class LocalLayoutPersistenceManager implements LayoutPersistenceManager { }); } - loadCurrentLayout(): Promise { + loadApplicationLayout(): Promise { return new Promise((resolve) => { const currentLayout = getLocalEntity("api/vui"); if (currentLayout) { @@ -113,7 +113,7 @@ export class LocalLayoutPersistenceManager implements LayoutPersistenceManager { }); } - saveCurrentLayout(layout: LayoutJSON): Promise { + saveApplicationLayout(layout: LayoutJSON): Promise { return new Promise((resolve, reject) => { const savedLayout = saveLocalEntity("api/vui", layout); if (savedLayout) { 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 e866a8e6e..f4e931ed7 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.tsx +++ b/vuu-ui/packages/vuu-shell/src/layout-management/LayoutList.tsx @@ -39,24 +39,28 @@ export const LayoutsList = (props: HTMLAttributes) => { height='fit-content' source={Object.entries(layoutsByGroup)} - ListItem={({ item }) => <> -
{item?.[0]}
- {item?.[1].map(layout => -
handleLoadLayout(layout?.id)} - > - -
-
{layout?.name}
-
-
{`${layout?.user}, ${layout?.date}`}
+ ListItem={({ item }) => { + if (!item) return <> + const [groupName, layouts] = item + return <> +
{groupName}
+ {layouts.map(layout => +
handleLoadLayout(layout?.id)} + > + +
+
{layout?.name}
+
+
{`${layout?.user}, ${layout?.date}`}
+
-
- )} - + )} + + } } />
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 42ee5bb9b..b00440d62 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx +++ b/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx @@ -1,5 +1,5 @@ import React, { useState, useCallback, useContext, useEffect } from "react"; -import { LayoutJSON, LocalLayoutPersistenceManager } from "@finos/vuu-layout"; +import { LayoutJSON, LocalLayoutPersistenceManager, resolveJSONPath } from "@finos/vuu-layout"; import { LayoutMetadata } from "./layoutTypes"; import { defaultLayout } from "@finos/vuu-layout/"; @@ -8,14 +8,14 @@ const persistenceManager = new LocalLayoutPersistenceManager(); export const LayoutManagementContext = React.createContext<{ layoutMetadata: LayoutMetadata[], saveLayout: (n: Omit) => void, - currentLayout: LayoutJSON, - saveCurrentLayout: (layout: LayoutJSON) => void, + applicationLayout: LayoutJSON, + saveApplicationLayout: (layout: LayoutJSON) => void, loadLayoutById: (id: string) => void }>({ layoutMetadata: [], saveLayout: () => { }, - currentLayout: defaultLayout, - saveCurrentLayout: () => { }, + applicationLayout: defaultLayout, + saveApplicationLayout: () => { }, loadLayoutById: () => defaultLayout }) @@ -25,45 +25,50 @@ type LayoutManagementProviderProps = { export const LayoutManagementProvider = (props: LayoutManagementProviderProps) => { const [layoutMetadata, setLayoutMetadata] = useState([]); - const [currentLayout, setCurrentLayout] = useState(defaultLayout); + const [applicationLayout, setApplicationLayout] = useState(defaultLayout); useEffect(() => { persistenceManager.loadMetadata().then(metadata => { setLayoutMetadata(metadata) }) - persistenceManager.loadCurrentLayout().then(layout => { - setCurrentLayout(layout); + persistenceManager.loadApplicationLayout().then(layout => { + setApplicationLayout(layout); }) }, []) - const saveCurrentLayout = useCallback((layout: LayoutJSON) => { - setCurrentLayout(layout) - persistenceManager.saveCurrentLayout(layout) + const saveApplicationLayout = useCallback((layout: LayoutJSON) => { + setApplicationLayout(layout) + persistenceManager.saveApplicationLayout(layout) }, []); const saveLayout = useCallback((metadata: Omit) => { - persistenceManager.createLayout(metadata, currentLayout).then(generatedId => { - const newMetadata: LayoutMetadata = { - ...metadata, - id: generatedId - }; + const layoutToSave = resolveJSONPath(applicationLayout, "#main-tabs.ACTIVE_CHILD"); - setLayoutMetadata(prev => [...prev, newMetadata]); - }) - }, [currentLayout]) + if (layoutToSave) { + persistenceManager.createLayout(metadata, layoutToSave).then(generatedId => { + const newMetadata: LayoutMetadata = { + ...metadata, + id: generatedId + }; + + setLayoutMetadata(prev => [...prev, newMetadata]); + }) + } + //TODO else{ show error message} + }, [applicationLayout]) const loadLayoutById = useCallback((id: string) => { persistenceManager.loadLayout(id).then((layoutJson) => { - setCurrentLayout(prev => ({ + setApplicationLayout(prev => ({ ...prev, - children: [...(prev.children || []), ...(layoutJson.children || [])] + children: [...(prev.children || []), layoutJson] })) }) }, []); return ( - + {props.children} ) diff --git a/vuu-ui/packages/vuu-shell/src/shell.tsx b/vuu-ui/packages/vuu-shell/src/shell.tsx index 2f8b6dc6f..6fd9be378 100644 --- a/vuu-ui/packages/vuu-shell/src/shell.tsx +++ b/vuu-ui/packages/vuu-shell/src/shell.tsx @@ -64,19 +64,19 @@ export const Shell = ({ }: ShellProps) => { const rootRef = useRef(null); const layoutId = useRef("latest"); - const { currentLayout, saveCurrentLayout, loadLayoutById } = useLayoutManager(); + const { applicationLayout, saveApplicationLayout, loadLayoutById } = useLayoutManager(); const handleLayoutChange = useCallback( (layout, layoutChangeReason) => { try { - saveCurrentLayout(layout); + saveApplicationLayout(layout); console.log(`handle layout changed ${layoutChangeReason}`); // saveLayoutConfig(layout); } catch { error?.("Failed to save layout"); } }, - [currentLayout] + [applicationLayout] ); const handleSwitchTheme = useCallback((mode: ThemeMode) => { @@ -123,7 +123,7 @@ export const Shell = ({ return ( Date: Mon, 9 Oct 2023 09:22:46 +0100 Subject: [PATCH 12/16] VUU-47 make defaultLayout closeable and update features --- .../LocalLayoutPersistenceManager.ts | 6 +++--- .../packages/vuu-layout/src/layout-persistence/data.ts | 1 + .../VuuFeatures/BasketTradingFeature.examples.tsx | 10 +++++----- .../VuuFeatures/FilterTableFeature.examples.tsx | 10 +++++----- .../VuuFeatures/InstrumentTilesFeature.examples.tsx | 10 +++++----- .../examples/VuuFeatures/TableNextFeature.examples.tsx | 10 +++++----- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts index d659403b5..520f9aa25 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts @@ -104,9 +104,9 @@ export class LocalLayoutPersistenceManager implements LayoutPersistenceManager { loadApplicationLayout(): Promise { return new Promise((resolve) => { - const currentLayout = getLocalEntity("api/vui"); - if (currentLayout) { - resolve(currentLayout); + const applicationLayout = getLocalEntity("api/vui"); + if (applicationLayout) { + resolve(applicationLayout); } else { resolve(defaultLayout); } diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts index 22b5012ae..da9062c7a 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts @@ -25,6 +25,7 @@ export const defaultLayout: LayoutJSON = { allowRenameTab: true, animateSelectionThumb: false, location: "main-tab", + allowCloseTab: true }, preserve: true, active: 0, diff --git a/vuu-ui/showcase/src/examples/VuuFeatures/BasketTradingFeature.examples.tsx b/vuu-ui/showcase/src/examples/VuuFeatures/BasketTradingFeature.examples.tsx index a2399a77a..f021f557b 100644 --- a/vuu-ui/showcase/src/examples/VuuFeatures/BasketTradingFeature.examples.tsx +++ b/vuu-ui/showcase/src/examples/VuuFeatures/BasketTradingFeature.examples.tsx @@ -18,23 +18,23 @@ export const DefaultBasketTradingFeature = () => { // Likewise the Shell provides the LayoutProvider wrapper. Again, in a full Vuu // application, the Palette wraps each feature in a View. //----------------------------------------------------------------------------------- - const { currentLayout, saveCurrentLayout} = useLayoutManager(); + const { applicationLayout, saveApplicationLayout} = useLayoutManager(); useEffect(() => { console.log(`%clayout changed`, "color: blue; font-weight: bold;"); - }, [currentLayout]); + }, [applicationLayout]); const handleLayoutChange = useCallback( (layout) => { console.log("layout change"); - saveCurrentLayout(layout); + saveApplicationLayout(layout); }, - [saveCurrentLayout] + [saveApplicationLayout] ); // ---------------------------------------------------------------------------------- return ( - + { // Likewise the Shell provides the LayoutProvider wrapper. Again, in a full Vuu // application, the Palette wraps each feature in a View. //----------------------------------------------------------------------------------- - const {currentLayout, saveCurrentLayout} = useLayoutManager(); + const { applicationLayout, saveApplicationLayout} = useLayoutManager(); useEffect(() => { console.log(`%clayout changed`, "color: blue; font-weight: bold;"); - }, [currentLayout]); + }, [applicationLayout]); const handleLayoutChange = useCallback( (layout) => { console.log("layout change"); - saveCurrentLayout(layout); + saveApplicationLayout(layout); }, - [saveCurrentLayout] + [saveApplicationLayout] ); // ---------------------------------------------------------------------------------- return ( - + { // Likewise the Shell provides the LayoutProvider wrapper. Again, in a full Vuu // application, the Palette wraps each feature in a View. //----------------------------------------------------------------------------------- - const {currentLayout, saveCurrentLayout} = useLayoutManager(); + const {applicationLayout, saveApplicationLayout} = useLayoutManager(); useEffect(() => { console.log(`%clayout changed`, "color: blue; font-weight: bold;"); - }, [currentLayout]); + }, [applicationLayout]); const handleLayoutChange = useCallback( (layout) => { console.log("layout change"); - saveCurrentLayout(layout); + saveApplicationLayout(layout); }, - [saveCurrentLayout] + [saveApplicationLayout] ); // ---------------------------------------------------------------------------------- return ( - + { // Likewise the Shell provides the LayoutProvider wrapper. Again, in a full Vuu // application, the Palette wraps each feature in a View. //----------------------------------------------------------------------------------- - const {currentLayout, saveCurrentLayout} = useLayoutManager(); + const {applicationLayout, saveApplicationLayout} = useLayoutManager(); useEffect(() => { console.log(`%clayout changed`, "color: blue; font-weight: bold;"); - }, [currentLayout]); + }, [applicationLayout]); const handleLayoutChange = useCallback( (layout) => { console.log("layout change"); - saveCurrentLayout(layout); + saveApplicationLayout(layout); }, - [saveCurrentLayout] + [saveApplicationLayout] ); // ---------------------------------------------------------------------------------- return ( - + Date: Tue, 10 Oct 2023 11:10:12 +0100 Subject: [PATCH 13/16] VUU-47 rename imports --- .../src/examples/VuuFeatures/BasketSelector.examples.tsx | 2 +- .../src/examples/VuuFeatures/BasketToolbar.examples.tsx | 2 +- vuu-ui/showcase/src/features/BasketTrading.feature.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vuu-ui/showcase/src/examples/VuuFeatures/BasketSelector.examples.tsx b/vuu-ui/showcase/src/examples/VuuFeatures/BasketSelector.examples.tsx index bac05acfd..720b2c817 100644 --- a/vuu-ui/showcase/src/examples/VuuFeatures/BasketSelector.examples.tsx +++ b/vuu-ui/showcase/src/examples/VuuFeatures/BasketSelector.examples.tsx @@ -1,4 +1,4 @@ -import { BasketSelector } from "feature-vuu-basket-trading"; +import { BasketSelector } from "feature-basket-trading"; import { useSchema, useTableConfig } from "../utils"; let displaySequence = 1; diff --git a/vuu-ui/showcase/src/examples/VuuFeatures/BasketToolbar.examples.tsx b/vuu-ui/showcase/src/examples/VuuFeatures/BasketToolbar.examples.tsx index 11603675e..ea7f20506 100644 --- a/vuu-ui/showcase/src/examples/VuuFeatures/BasketToolbar.examples.tsx +++ b/vuu-ui/showcase/src/examples/VuuFeatures/BasketToolbar.examples.tsx @@ -1,4 +1,4 @@ -import { BasketToolbar } from "feature-vuu-basket-trading"; +import { BasketToolbar } from "feature-basket-trading"; import { useCallback, useMemo, useState } from "react"; import { BasketSelectorProps } from "sample-apps/feature-basket-trading/src/basket-selector"; import { BasketStatus } from "sample-apps/feature-basket-trading/src/VuuBasketTradingFeature"; diff --git a/vuu-ui/showcase/src/features/BasketTrading.feature.tsx b/vuu-ui/showcase/src/features/BasketTrading.feature.tsx index d646b68de..29b0608b8 100644 --- a/vuu-ui/showcase/src/features/BasketTrading.feature.tsx +++ b/vuu-ui/showcase/src/features/BasketTrading.feature.tsx @@ -1,6 +1,6 @@ import VuuBasketTradingFeature, { BasketTradingFeatureProps, -} from "feature-vuu-basket-trading"; +} from "feature-basket-trading"; import { useViewContext } from "@finos/vuu-layout"; import { useTableConfig } from "../examples/utils"; From 82af04732c2b4f7912de9ee5f673a88dd3b98209 Mon Sep 17 00:00:00 2001 From: vferraro-scottlogic Date: Tue, 10 Oct 2023 14:32:50 +0100 Subject: [PATCH 14/16] VUU-47 fix cypress test --- .gitignore | 1 + .../e2e/layout-management/screenshot.cy.js | 2 +- ...last but one will be overflowed (failed).png | Bin 82772 -> 0 bytes .../vuu-layout/src/layout-persistence/data.ts | 1 + 4 files changed, 3 insertions(+), 1 deletion(-) delete mode 100644 vuu-ui/cypress/screenshots/vuu-ui-controls/src/__tests__/__e2e__/tabstrip/Tabstrip.cy.tsx/WHEN initial size is sufficient to display all contents -- WHEN resized such that space is sufficient for only 4 tabs (LAST tab selected) -- THEN as last tab is selected, last but one will be overflowed (failed).png diff --git a/.gitignore b/.gitignore index ac02b5aa4..0f5a6d690 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ yarn-debug.log* yarn-error.log* /vuu-ui/showcase/src/examples/**/*.js +/vuu-ui/cypress/screenshots deployed_apps dist diff --git a/vuu-ui/cypress/e2e/layout-management/screenshot.cy.js b/vuu-ui/cypress/e2e/layout-management/screenshot.cy.js index a51c1cc92..db03b177b 100644 --- a/vuu-ui/cypress/e2e/layout-management/screenshot.cy.js +++ b/vuu-ui/cypress/e2e/layout-management/screenshot.cy.js @@ -9,7 +9,7 @@ context("Screenshot", () => { // TODO (#VUU24): Improve test alignment with the user flow it("Takes a screenshot of the current layout and displays it in the save layout dialog", () => { // TODO (#VUU24): Improve selector - cy.findByRole("tab", { name: "My Instruments" }).then((tab) => { + cy.get("#tab1-tab").then((tab) => { cy.wrap(tab).findByRole("button").click(); }); diff --git a/vuu-ui/cypress/screenshots/vuu-ui-controls/src/__tests__/__e2e__/tabstrip/Tabstrip.cy.tsx/WHEN initial size is sufficient to display all contents -- WHEN resized such that space is sufficient for only 4 tabs (LAST tab selected) -- THEN as last tab is selected, last but one will be overflowed (failed).png b/vuu-ui/cypress/screenshots/vuu-ui-controls/src/__tests__/__e2e__/tabstrip/Tabstrip.cy.tsx/WHEN initial size is sufficient to display all contents -- WHEN resized such that space is sufficient for only 4 tabs (LAST tab selected) -- THEN as last tab is selected, last but one will be overflowed (failed).png deleted file mode 100644 index f3e3fa91d47ab34cbc39ca834c0c90e5ebb0bca0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82772 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 z zW#4aY`P#00d7K0i^A|FU{RIFRJmioj7!!@)^WLE}3&glFudU$#khiAjR*Gcv}2`WJ`+mhdcRjWj+8fJC!%VbY=;OjCd=L`5xX zH<1&|;N?q!Kwqr$g&TS+{_myYoskceu|9QMwKN7XJEpA5#l zWN%IHx>)v{6ls7Zs6rmKr9v+*d3rx|T0&8~<%gXN*Iz8fXa1x$$ldyLT>}HS^!a?d z5A0ePbBe%33-iL%FrStwW0jsfd4eV8)f6T7s88Alp=!JSn27fGH8xd(%7+`wqUgn@ z9xS)v`)78UBl*SY8h|0jU@f?4cU$l+9s2?Gv+cn6%=qLBRv8ZM456g0t)%g(Y+?|5 z$}pf)b6@GYBwl+p(7#m@|Hf6CBb`%s2HUO`d~;M8=N)%FKj1p`YafL zf^YD&kMbA-Q(>5OA73eYC<-CFlF}&ZBLQgpQj&`&55~1&@!g3XS*@rGGzFBPZIHLz zdSFfUPnwZ>b8%k2a*n#(-N*F(n0SMAa2bf+LBcjhl^2J3gU^^<(qT1IL6Y7j@15to z4~osvlmG!02Wh@sNyV^M!oFItuvLdMN1U%5`-vL`hd$PGWh1?7T{~M;EIsCE)~55F zE}Hdr`IG_|xp@~eYq#2~84d4!e;gP>nT52Vt{@6*$GbM(Z#_Plm9uqn*KQ~?9)b8c zllfF#{lV}sp%`S-#umw@Pf%7|%;xHg)#!J7{Is$|=h;VnF6!?6;}y4t1Aks=Sx0S! z>wtSVJP#W>#1-g>D1x)ktHv1qax#6%f)5{RN18e^kJ-;ZRZ{ZK9&vA`nK?eI<7|Bk zJeL6U2G5vMF^?QYz?Cy4zOBxtXH>BOK_mdk#?(H%S%4ts8h-?zbnXLUxA%Aq!LsDh zx1+n?!>5{w2M@(7n{GbK*lrDHO89+AN!jSCi+c^jry{@&)tci@dH6JE!By|xJv#RkdvA+ z&-Fj3(pOvDy;|74%CP0qXG_q>^vK1>Iy=5x>bVR_D)j4c%HYQk5{7&8rWbf5K)b-f zp~dE6$~`#b!_yqIh>*kWw}dwU8Yn8}@l(e_I%L1j$@E%P^30K8B3Wczi)osr$-vTp z~#ED4zGf*{=_dz^#U(XP2RP{!`7V`XxQ8h$50G`G?``OsQm% z9}a7TmfzZ1h`i#FWM8rnq}CLp<Ysw#g zKZ@zP5K!o{s9pw^0!0*%t(J)371;?YPzs;QK!H;C@0e+^Le#i{kI%6H@?L{a;7<^oiwxo( z^?$|!fPo8AJrKu#FC=#TJBIP+wEx|w-1%ncoTjnC*bJR<+PFlTFAbVw2ELlOE_Wzu zfJK~j%7nitrNr1>9H6!4lwvr(HTaH`oT~i0kv0blKe2#B#b(s`oZo&=cLU*%H)cQ7 zYv)OzYQEzw3GfbK9;Lj&htX1J(3+*%%?c zLao~t@T^v;%eD9iDCj{rFQ5MjrHJcn*EO*e(6*hs?AOlv*4hf$4@5TC)YTm^+#K$M zT5hi6t9=idpy+kpv7bNNGQ~YFnqer8#>PfknZPs#%mLmD0|TE%OaKC9mcnB?;0c$8 zX9yQRe)41m_*R1a{Cpu{;lm+8?P|N>jN+r|<0GXC^cjj$Cil;}!E>UA8)upzKu0)G z^2)QHm!oTHvZp|Ed#2f*0@N9S6bxx6Ra1&(B#m^t_!(?wc5Ibv8o`{bnp9YAd%WFc zRjD-my1@~z%} zm(PK*AWio}6%P*&iQU2BVe6qZehsh;WNYj7Udyr8z`y|Ldr^_)VvC;`L<>aDYxZH( zAvY)IT}Q`L%q{{W`|H=Q>6%T#0*&;J5DYTJQXpNT78ZE98@R@-UE#AYGgIaaY3H-9#(M6?u=|(cEmhvP!J?(T4m;?EkLznzKy-gyvVS%k8+!_5T)=;b zE>=nZwXDX;`JZH=bX8Rzs_;IhhxdL>PD*^AGHX7w1xjgRnG1`06d^7S;%c&<@TgrMdGL0raXJOJB+KrQ;rGb4>czmAb_w*n4Y|UR9oji zUFXFt3V+pxqQBvA@e$-L_j?z$x(;%hKX@|e3QI*HuFZf%*va2I$m%y==%q=%b??Dm zv?9~tHo^J;{6F{$Q@q=Eo0{egm9)2ROA{A0-PWFo{v#5=aiswR#Q_g1sjK7T5!2sl z+x5ozoSm($r}yCU@;0(glzl`t7%ldvHn=zsxgnnX<5mDb9+h`D-Kyrn#bYYrbokf_h1UvFdRfzFCad~YX!Resy0^}aimy_Eb`w@~N% zoq&4YqCHSeUA=!`z-GB4Tmv$DBM#Do@W`MOwB2r+5p#~H$f!IZ^S47t#W&V-*&KbL z;(~HRNvp<^drI+Bmpz)qysF+$>J#IJF}m-q%%uo2!9LHA z0hz>Joi^PZ&HqHHKEkDsWuUiBztRmw0HgUZW7EFtvIM!7#NEB>{7AS8gc!AzbNAJr z!*$+}@PPp}K>a_ndI>BK`3E3*QaSn26cz|*EBdS+wtm>lV87BOFz+G?c&F$gD# zVg{bIQYae~r5${v7kFBH6Ym;)OcS^vc+-cJz3~GD$}k{qFgFBkdAmcaj+Wig+Md0p z#1%H15YWydSqK3xz^IDPH@Q6qaqrnpmYYMw>KlB8jyDjvUBwUTUFV*G>udGq*f}{V zWi3eoO-;>6@#8WgoXyS6n)-U5yBrWL3P7SVsjr<{ z4NQ-TG(*UJJnQ32E8JKZ&uXyZSuvpHV{-K_jVPBdP0b8I3Y1O%pZ= z&^sf&^R_fb9T(teo0>3$p3Y8Av`dR7YB4D(d$KhFt*K@S;v@&=-Yy+Kek3{hUXx|= z0AnXbTI}Ve=6U}}9RJ{q3#Divh@=0_c1l^XID^bvi#z`R`K`cPm*0wV|AG;ag(V$=kns%uzt2LjNUMQB}}KCrp+J2`)SngjHO(N@G5 z!0B7q82RP`U;q)=H7x_`-Cg-O2Fa&~eb2=pj-Wpv?pI~iR{o~esM1gu$U*R0cA-XY z+qxS=t;sjn3wq-d69j)cnYNYRKBp&p&McLOwOh2ku1>P59i4wsPV#q()`sLUNUG4+ z=5hXNVbz;n-1)taK;x$z(7PTH-y9G-Ei}b?ZJPn`coGRv>~WF|7F#v1C}VN8ASG+S zSl1``l=nn=tJ|o+2Cv~$G1`J0Xv%7D93N`q5|EN~zpnLI>8ivc1bKQKHSA-Qfx#SX zKAl&7nX=c*sRr|V^NsboI%0_wSvU9zDh=Am3=KA2U0ps(Vf@gxgXP;A5E4othAB8S+Gj{LlCwII4pDi zeh|^~j~SiI78vyH7Y5mMTV*FRwBStdCMLk!#?m{uA z<-`s*Y$=-Sli}(Bf2{r@ODwK4VPKU3O}Va2RJD9sD|K?PI!#A}IY4Fq0p)=GfDfYe zu2%b5rl|{}jz@0+aY+cellKm8Sm25EO#aNA z&KHwZ)6<7qLNo-}NPaQifg@?*EHK3WA`0z-jXb!R4`BXCu-T;SHf*;|u|VOq8Qw zS;z1&PFPO*PRU!55_3&psf&wHuEu43p`D012fmw9)KQFP#ks=%eoz{6=B7fSMU+@@-i?n=V9F;jBOhnjHsFnTAiPl zJ~%kQmMhg?U0)ANO7g5fH!)o(Dlg7!Y@`Z}iV_%qEvz-d^Cg9Rb$3@?#X$^|kTB}& z=ci|w77u|$8N0ON^SUw+(N8M`nbP5xzfyBByk()uh>GvkTL^&cy!dRb-H?k^ZWG>W~jMAsJokeIxz$+8a%uaTYPYe-%)=$JJSz4 zetEC1P=~{JW0M_FD{l)R?CsG9UTqcz9<+xPegD3cV(fDJp@Wc?Mz%~?61#R)@U_2OdxQIGI=?j=L+}M7 zr$HGD7gvnm@kYXzx0z&|i;a)?CtnymH!{*N^ko5AE-UbXjr;n<#>#`~=;)m9a)OdZ z?}YX|S5Ug50vJ@w<%Dnf1tjD$A98cCx3*tL#zZM0w|d&c0#wAkGSO(-r%yWp@oQY> z4#W9waZ%ASY`=BJ@|BObKJLsbDguW4>VUBW?y>*8B**f8Lt}uO#f?jy>s;GUOJ+mw zRd{41mF6o)e!y>@Bxm#npArC~&`cE>T)9_{cbS#Cuz{em2TgQ81R8ilfhWszfD!C9 zo}#%aj=Tcv^gTaKgsHaVz{OFs5;zl~u8!sd06AvwMUN%vU$U(6%To zNkuCDP@9-HgPI2Tq*4^|Q&VR`;JWNLO8jo!!u7d=LSm`t@6RCp;;DqGYiBW;?gD~( zMMY&>qi{KblC%DnVH#LLLqpqHknT#mXJSZj@Vhz`YxiZ{z4mHFWnzU={c4v5+_~C} zj9GlN$lndT!`S4h$PVi4hFt1&;I$;fWYX=!<8|K}Dmn{`i)rk&vgLAg_-y)gfbGTvzk5iw_T0cgT~CkMKS&y6W(Hkb z_cnRD`NK2sM-^#UyFc}7cXvh^D5jyo!5RMI z`04rV^Rd5tpdHPa-8IrO1|Z3dHWF zG4Pr#hrxJ(f=ueL%bG=lQu+CF{_<7~+vX-t$d9`J+#;(v_d%#yK3$nhR#%U9RS*Le zSy;DK!8fs+_wm_TSwwP%X{S1$e*R=2j`&_yhP!le$g&uCjMz20Zcw(I!y;E(Z`&zc z-`IPXlqC2vnM_DTWV)=dpn%VI%gJiBXJv1>f4LLobUw5}4nUZN^!=LFvb~o8H9}7R z=L|wZLf-%sz#ljjf(r8s_yR_-t*lmzVf8$&llA(9^wY38eMdYCK5B^*#IHF$exq~^ zrA#3$y0Y4rPnrUJy)VYMw~M}W6z1oLC*-ZDs%tVzF-5g~)tMvRj5-nky;tZE^B!-4 zWf+zAI0)ss{``Zkx{eVu^S@@x!$0CmmzCbF=c&a!sX=IK=DAyS^@*veNBf;arrh1# zmlJxkl;m%Mm9GT%%|kBvy}iAWCcK=Sn+6)W`%%%y&VwOyZ&T3G(b{6jHAMAXZQGda={K27<5i$r!LtR?SXv=->T+rUUH*yt^ zcQ!awTwMHxzJAr)P2i3M2nJmd%(Vh#v92DRgibmWDA^KIQj{DVD1(E80S&t`=*7!d z#f7A#Y>5zvrk|gnEk z&>8Zv7oZ6?YJ6zLY1 z)$Sp1+g_@HZ4L~Vl(8{6RV+rXa#%_V9~NfbdkZXV?C`;H6efEE$g5YuE1R2g`U~|) z{8f_~9`>zwS#|703jCpq*JLXzST-`Ahyfjm&f`WkwKBKWmaPxZcLEK)E}m3q3R`zd6oe`jn6$>F)k;(We%*ttRj&2v?ts zk1xV&uRg)=diBv>>)kD?t+loF#ZKkWuvPW-<)nfGEUmRpSV{Y>3}2MQs<@>k%lK}* zZ94yFabO^w*BQSxCe*tf-{#}$|h+PHAQtnlo}NwGk+CNOt;t^+rtSphs7urab}oGCR% zO~7At=3RSO;b@!$3Tbk4DQ%|88@ek<1e`#jxhOO`<>#r49dwe(KmG-l7ksB}dO8S+ z^b)1CbdR|32&$0S4=$T+3}Zn0lYKZQnd+0;{0HHPHqKcvJzZT}8PC~x2vQpLMZ#Bi zbfH$^^?}y*GarqoZAanHkRRtTgw4;{2p6c%(0&N|XA>c#zA^koBLk1o6@^o< zvVJKprpJEv2u+w8eqEhy7qTey^NY;Rvy{nX5H6fIF#(b18)%AG?+ zjs8Anby9-kgD6slHb<$67Vcybui|fkxV64ZR^a1I~Y>FrX~2=5z84v>jFK{c^be1Mk;$@P(_;urQGJ z&mOV()!VxQU@PMEg0iyw7T+Z-9>gviyR3qO<3LRfI0X4|z%F{^LSb2BU4Dc%{R=B= z&kRG0(Wxn6V`@KAPA;yIIsWo6+sbKgkuQD@hhT-c&8hEZGEnFA-QxUg0g}ShdgOO~ zsS)#gI5WSvI5kG_c1JXj@gD`3jZ1;R{cFKmF2>ZP76@W}X*Qw#-*((i6*P*%@=DG|Jvq6Q7a)^Asy9pTva!YTK}^ z7@5hd8TtR{-X|}`7vDZm1TPsIr!LK<>y?`H8>fhFOHudMa8K&LmqDmnNZV&&XSKtL zVNsz8{`0Ny2Xan@J<{Dj-xRn(IF0z9ufhlKEuwqOhb(;d0{g#eBJ}w4vo^Zq2PK0z zanb@)A2L;E4u8h_tz1%ap}ErBtCB$U1e^j$(}UO8&+_D2!=FA?si>{Z&+YozJ0wI# zN7rm{v!y$q$mfPpP>}yt(6?%G7yMxgGx_$<(>{6mb4?u6Un7&nn3~mP#^&VG6C~lI zLh^A@qoZ&3CfgT2yc;1eDBQYn60WQVlm?fwt;QQne0(?z3=G1H37DRqo;m78uNfJM z>e~L9IPh$jZ!!G?HqQ7+m&Bc3NyZSB>pe$)ww(Ni>!1sE_r}0rNDt00>#EKRe9Vny z7J#4}4bGM=?e6w{w2lWaA)*MBU2kmW%9DTmzzjwuZttJg_#12jw{zGcb*jQAr-asi z?pzAeRJ-B=EF8i%rfxY^**a4biQk=YVNuaki!EXjegTADy9pVnD*60W!|T8vU>3?f zMzsO*-)G-~g{EK+0g(p=K8_qvR7Dy-!D(s4GWO^AA4FbG&GLbc+n!S56dvbw3dk|8 zuH;*bhY+4=e4ClN2~JYIr~*ZrM0O9VoNn2+_Vy2RbM&0Hn#NxFVOaSXghKxbj+q?^ zN=xm)`)m5P#P9rtUof?N29;J{zP=BHLW4=}_~WE0%_@_AeYcpK@SNRsP%J`B%-9t4Sog@jb!)rjM3(j9!2 z=8IqSrWO%-`7Ko%+V!SSuFt-EU>>oY@$mHcm>T%6z$5z^L!zLlNCV;FN&2&9hM=gY zCIzCAqhlvXd1Ym1Pfya#m1_lt?Q7EKaBzFxG@X#wTUPxe##GE8 z$OB#9Jf45Z-n|n86>cOS2N|`Sn!FR;PL0ANo^iVzamy3rGx|}0d;WpJm~EkI{6|xx z^euZ-4+2CRZj`Lp<@wM17MVeu7d~OVy!?6YuC46w@Nm1P`F{jD=oOBC!1t;uJOcwi z@0$m8SLu>dRgNfod+DH736KUyjTl9*Je-T?^YbHTEASZWzfn;RAFRZ6W z+~Rvne5oMI|1(~0QCUrE@C4r(+ynFvz5wye8`BG(!@%G-OY3{>A8s0_fuxCrL>W#; z2~ks-v3s>FPF#Nd$YI^mHaZ$H>+4GjqzP0q{f)Akntj5e2xa8usb*_!%TKMD@iZ$h zojW?n9i5!AYDyo2PR zgRXAX*yQB2w*7Qd^tYd#0+&e}r>EYHXEL0&x?(2>N}%m&4SQ+xz3xklDi3CkOzPOdCX=%WHzhBXJX25UK!cc zMWC^qZ!Fy%9Y5_Z>KOkj#tkMZ2x|6^-=IvJ`W1k*rbbBdFnN%xrR7$%#;qpAUh@)g*i!{)0YhfS>Ff$m;y=Z9W~fU_)f(C1NFvJ-2)y3#!|m1#bA-s2zC@sS^bN;=bh{bR$g0yi&` zB-UTwK*9Uk9rAlr9=_o-Q@HmkCmRT67HFX@?q{DBAsA$2e25eRbf>NL>e5o%r(YId zOVmDSmqcTbuUgpIy&}JnO<2v%PcvKjwi!txkj`coINPf$=3*m{OR6k` z4h{m{+*+#!CFZ+C1YRw;53A)7G6*oDi3Ej#z+0;WW(aG0llBhEs;a7TpV`Mp$IhPD zf>&pIWWFa4Uof?5+*Aj zX8$i`mi+`7-*oe~LMEhVihb&(uCi_wFVOWC1~-#6LQ7mlg5(0b?6h&c>Oqs~qFF!3 z$Kyau`ZyqK-sKTM9xg8%mTHyf>h|fsO6CdOnjsR|V|`00 zj7B%x+$NV8k^i~oxc22)Tc6^8A)^4|;D4tfup`uyb`_7<9 zPRtn#0HYE)%daG8?j0#i|Ae{>rz14sH5-M46+9_OjWW;ory1O-$sNz6N1u7l`<@3X z)zw)daC?x21#903htP<}6pWwMdAYkKom=N9(l|Jb8SXE=RWnVZK=>!}S$Wm)U$~~S zO#5S(QH7oikIUDNkHsR9g*h3bM`i3Rv2(;d;(9_DlMai{dw19iS^4Yyj)*8~r#pW3 zDj73;Td9dxjccj8cVZcs{*!_Zw?l!yu_vZsO@1t9L}cwdl#c2v zNOom8IYG!yw|%g0K1qv7;m8Zx{S>MjR*%zgUqa^5iB?Ke3Tz$>%aWnS;vY?}Dqs}}ZJX0`lms0?$OAUMxW8D>rG8;xj%CLiug_$`(2s=tVwgAK90hl z>Zhu?xJEW|gVpSWQeY+<6iJQt(-eS^1bc1^R8|B9_}yWGmr!e{k|S-*>b_0K^d!m< z#!JHrMGZv}A4i-SGNzAtG2eJ|@N%1A8(0#yvd6Q25BAR+R zy53}0A&a*5tJ0*r$(~A|*3GQj+xgGpJ1((r>wFkd;!cs%a3p_k`KYeNEw8KwdxfHv ze$fAO?Lc5`%vLd}K;`1~D%bK60LeXH1r|B$BLwbHr}rkwlAaa{j_ac9!S z6sA=;?d!L_CWB@6ZRBGi{XY44UdR2uLs966FBt{fI{&Sp;`bq!mL2PyZ4b>lCpSf$xdB+!&HGefI8&yUZ`9^aPf z;PgXc1qM7cf~QSZ-t!7u9b zB!!!Z8jS%Z&c(*-kj~Eg=4Gkxx|!88TJH1Pb=2J=Cz296?^d9+XcY)yBDvZ>ExzW{a5ob#8BdgJd* zMP`fPC^Z!wnAwhpuH=^E`oG89hr{6A=VomXUcRX_JG^&^?y!P&v zSYhM+cs)=3YtiRH%m&%YzJY&i8DTUDqd22HnRDkm)a%pdsZDzLE^yr@<4#NZA_Ow$ zn|G{}q%~3Fnj?p#doyHZAjkP!YI{3c+j!`Ri*(a{el{w&mzVa3%r8NQ}ZtBU-;PR9+DQ=xS(`LZQ9~S9vS#_71Z~wS5Gcd2nZZ^w0`}^AqYMGYthWvl8`Of2XSqiwp6-O zeOKoMmbH=7sM8`(<*;}7qBy5gHis5PoU7AI(C$)d#3I$At*H1t5KkZBVDHGh+)cSL zvE}XfH4B5F`y9XTfW_pVI}n(+%*gcSHy%WzbnBb@z+OgBp>w+g+VaQcG<2TZjyu49 z?7_xw+1GBZibry)RDN6*xK$($)NFGEO~i`bNCct_nm&Y5AsQzPk=c5^8Yl@Cc*~_qyA)SM9$4Sb!tL$*-0+kD;z-x293!6S3|wKBoc& zvskv1QBjWxXt$YDw1=yya9w6{Qt%M<3U5;gzpt`MUIg}sa&_%eJ#NIuPT23xH(45 ze-p{PX@ulPP-AvZWBdKLwsMsD@y7Nethu$rC^|e0Mv8TNNzaSw;43v*lPqY@*+ZzG|VN0nnM4$amAiwL_xl@pZ#2L zc8OUz3X5qofB!Hp|M<0BzsawaryHU^m#3v6Lh~OYYUx;>Q=>aaaU3doQjz*gxeax5 zw8fS%6@NPr=QOXOX0egyPx4Ywj(?}eo90KKwtU>Zys{=OqD*DU_XRR_wxZF0F~CYS zFF*=$j!61beT4v&<-6?&1J>2$Z5z0P_bf|~=SJ+VY>T;|3aMt`Z+WSSTWo^D?dhW! zro_WvF8!C|$`E9ffgSHtLI|eD?z=sEdn2&0+|{?}f=VB5k_lg47THSK&rXVz#QDwg z@f>sxF@5qlwlDWtRC`d~l>H!}J+#L}K&`QU+F!XqyhMF;Z0!Al>wTQeNDpBC?;mt( zJung@5wBw20_gG#H99f!aim12|Cs$u*0ZnYFpX zlyxhC7nP4B(Gujl!LMr<+?;EOF4lb^IRciD`uX$b`4L-I_nvit+NXTRDH@xD{(3)iudQbD!vpyG(&hg2ZI?u$4Bgdu2KvOG#cG?yhKU-OfsUG?O=IlG zh=}89?}-J!7IOw9^`dc*jDM!@af*UUbp9ded70T|)$8lMO`%jaJ1IxskbSkSTeYVW zfelJ(YUA6kN=iyiM>FBRQ4Ejci{t}}u6nr^BYCIv_(;e%KKH}8JK1uCpq@d{pV z>3r!k;&e;CMzkok|5ks?@5iG>KNjcOg51alBODsD`8j5@bE0ogG=B?P#uvCPhz6%4 z6hjwy`7S1J-!Gjt6kS^zvlDn+#NDkA1#gKsN6LU6)!pNxpAPpAIp3JAfbc@}$%tF< zddqIl%;dk4c`1SE{}(D3FDu)2;Q$T#B4uTb!Wj2K%0?sw1gML@<$#%mKO)!jpmtav zUPJ_~=ShIH0%H%>H3@D`0wLqKUYRL$9lKF(RS_;`;-`C zWcf1N%YJEGZyyUAhxP5}(^RlwV9TB(-6(jN{TtI*ctZB9R$d&p%O4^$xRMT!so3oH zzCCBn%c*>QBxP3MrG6|h0_`(U< zc+v|dhsB>`5oG!2;il~aBja+{5mqwjlMV6M92xR-RXO9Ra|8?5Yq#*jD+1)WF&>tRC1v~D? zVYp9;N!ru=ZYc+OaXJ0!YC0=to9aMX8G)X?%~3d2ki<=$!LmAe4WFBFGsZSBg6i7pH0VQ3QTZtV+;}! z*Ur0kJ(==Km7}8ozjpbOl|2{G?*0|q>9}5o@YY80a{S`$N2O8d z@HcxSSVGFw(Fm{I_35rfDu?&Mj!upFuBz+xZES1hURs$FZCu@jd%5|ktKemyA7tw2 zkaxDSVia2b=d;e?!7;@Zpv~ZK?TG(+y^4EBu`SL^P=bZQqGV$!r))Lv!hd*1>fq{v zuvBBdmnF6ME1iSy@0vdpf9N>7zfbP)mYD{u6>5fH>&K3MZ%F#ZhW!-Z6k_>dKsC|1 zT`fcUan#cjkA+#NK*n(w7Mm%1AITN|o^LwjdZ>HIm?h=n&jrpmVm_Y*_kISm+kWc57mU5n=Vtj2Ra_KIy!8%u-_QWnb_S^}&Cm0be<&Gi?5L*0THPZZX_Ta{QL#SjIkqnl1?W)OyDR`WMk0Ei*1blJC`X5>z zB0tTm$tGAVhd{t!ZG5T|i^t!!WFhdMuV4WLji=$1>~l_z$9ddhc2#BNpoTko> z;i*W#724BTo9SBKJ!M1TVgD)!f8wY1Sr76TIMvMh@cdA0@()_|=G)iScgmDE3+nvW-o+c>wdT(RxX^BC}Rn5t3i%YDmhBJhvBiv2s$dTO?{hniQv zl?_z7uldTz9bqr?bh(}{qWk$y)#0`t2&bP9k)DqxFtJ!)c(XIg&qJqG?&eNjFxhxV z5Rx>a@WsvhpVHmqg~${IASbX|mfk_^SC^3&OyxUO9&*QjLwixc0|By*&P9kB zyqv1a)c~QJ6s#*u%P}riSo|a8Fhc2>5o}2Bk&HX&G5N94VoKj)kN0OWa;QL~~O3*TEB_RCd(_H4J|GF;~CSlngfhci6`36z~5Q zC2D<&pM`m3)Hx8GMWezZosWbuae_=?QPfNvDNSQGO%Iw6Q4Y<4nw<4jXt)$Ox9g&X z0L4k=Zt>(<*V`JumJj%S>nkgztH8heJCKhdK`b7J$S>x-5M@Z~gWz}PRFbvCJ1bmS z)>Jv4f2M_er!62e>$OiTk=}^SP+9B0w_6@fOsII#k*%BBeuO>>}~)>4n}cz z**o8j2*95O{B3Wpv8@7n+V0WCl#UaAZ0ScVv7ktlJ`gpA46NyRZX7$fUQ&!u9Oq)$ zE$_B5nd4$=aNT^#zb`|2qm`de+Iq3=2ONos)w-E&DSieCth|Z4nL@_dc!B_q*D21lu}klk z#iODnVAHKw$!PWqL(2mboyP2U`aMFN*C`dwm=p;2Nc+kc3Q!_abvk-#lK$2l`lEjC zwiWKYy}!5qYXWiS)|2iLzDHG}_5t$2<2JhCd<`G!T|pzAhG!ELI$n%++D!r8U}T%0 z5~XHb0)yO`OpjKQ&L3V?tVYNI1SEF4MqCxPIg`~I|AGy27JX(R@Sb=t4p;LEEfP74 zy*4rP*Qbf?T)k*54O6-Axga;k-r9;}j91M9c((BJKfQI$38>e$eg7YIx zC4pj59fg*$&CM2DnZ(9%-r($cuQT@a!N6{DR-a9^$Y!fo(tMO5ocpjM4Z*!~q6Mk# zNc->S$XuEvb*uF7r}qLbZj;_>wnX1fUESt6 z_nQj8PqANmeuX*ex@?P5^=Hgv&3~H80)7GpGsP4C&#@~9&r!$(4%yXm<0y82P(jLC zR<&uvdwUf6c83MQHt{`#8T@cwqZ0I5_L)6R{VH47S6FvBXpO+#a`h_)E^m&>`|2Z4 zEu+bfmJGME3M)dQ%jkHGiqb66OjUw4Y2D}9JGie=e)PNFEBHXU#E#CadmB%vO?>g8 z4LDFzicG|ow3DW~#b|ebba}AWD!4NnSUalT>^fRz4;%Zi`sbU8a)?!*0<-$qth6#1W&RJzNLy3icnyS_5pve4=HVJl_P@v)%JqcJw!J^0u8 zyX)-_u(vZlfknQqC_p2`-E6zEonB6lX-t0h;4djcx3$uNLsKej7^4RXp0OSGC=7@g z=*5T@w2k*gEn`V6W38C;X-!j4#SAZFJe1JFDs3NG{_DbH;{AHyXa7mm3_qQqk2Si*Gcbn4fDPmP#2XHpGwd0>3cAn?hKJ3X?f&d>vzSfHjAeGMdhuO z-RD!XKol=eqL415mlsz#vO2sWA5V09pUc?7fsIU6{~gq%!0!$ox4NIbv~xc(hk)o~ zYN3?2La4^~h*)0R9NI5scvQ`Hak9S;mb72e?*a{eccVe?asC1nW&?>g-p=1fpm{q) z&deIv|3{T5jTm3Rnm?!n#~gJi6m<7X1mFeY7NB;d5e{Huk+xqS4udc3j zo(;<5o~-uv+?%P6zj;+uKpZtaynW`gytO9bY|JKinNGPm<2+GsSmv17Q1wIXNXoQK z0=6uzCiNyks$jwC*fl?|$y4|$ussPLK|C~|uprg((URYJm%Ju-Ng&J7u|o?c`bKHm zf9NXiZSLOU@6%94uEN3w!Hw^1|3^>*e&_lEH%1krzcO=%+o%c@dILZ9AMYMeL}+_T zd4V+1$<3?N;_-64im`F`xsR<+zLX9?0Zkn}{5rN&-KhED72=%A5$|`!-|$?Q`wQe>BOZvp!bxdSZZ)7KNq3~3}@ZdtvXR(!!`M&z%vobu+ z{hd6@G(?+6Tl-#d72xhz*g}Cv{jUm-dLK9WRYOVcFtWJoEWW=k&SWpiRJZ|1jKeUu zvW^y88^*u+^#m}e@Q9B2$yiGn7&;4W_-Kgy84m&;s~rvbdd%uC7~g9dCZbPM>$i41 z9+JO(kDj*SqZx$sibVB_478)wIQ)4<3DNVOOY~e6{c)D>U^dG#>r!iI|M&e@i?i=R z*-=D#Bi7yLEP%s)v*DfFewYcu?bh>SvA~!}@hnvbBZF6ELYHua7?u0L7taRF5bhg! zX{tNVC7yJ>Q>WHTN0b9Hqu#g5oW%Hu8~_hQ9<*t6~j@KRsD?Q7?vUwR-;d%q8m1%a29JjkLqIswJ}BSQhjtqT zB0{757}Fjxu2uXcF(oq+I)lqkk*fs(*HE8)N0;?W+`V>@?T2lVlBJzfQ~d>UAyV4a zxY*C2j2Z2}f>xDyrea;lf&}bKI;Si)l{jpOZ}EflD7z^&*!Kik&P~#N^BApXGw67<2CL-!jIHsCh?+{1hO-A z1Jypksh)>^ap=|!9>9-^ZG4V0ehOBmBP*u%)N$PxJ05-DCm$^9)H$Cv|Es{=Mf8eF zJ!I~BU-C6iY)C-mdMb~@^An?m(Y}$(I`B+rV&+|+z1Y*5?ftqrh8CiAK5;=hVNsMe z9n2uHGNuHJqA-|K$s>C~{D{4m|P)4%;ejEQiUP-$2aKp@e+RTaR_7iQ8&&YD@N(t}|wot46`j=zVwIR3Y1ZmrMdD#UuRCW6@z$!@P;B zk@G_XafxI{&ew>laY3^$X*W!KAe8cKMhY_aLIXq4k{gwg0FrGMDf%v7+(4->G-y={ zeB~Pco7C$)GLb`!ejh837$e8$M=K}s)F&GR%xM$P*S+q`q^<zlMJz6Dh>zMYd%ScI?*pQn?k>HMUj2c<3zZ(hq(R`8{*-~=;YW0Je6{AIQUoP$ zcMW%1f>+&MO{afkzEP)}0rRaygJW~OPY`X#_p7Cv57;C&Bjq{6rWJo?PR>gEuE#&l zG9Hkbj$)jZObHKWvH5c#SODy0y~j=S4i@uzh~8)_b%bWu^NdbkN3~}HSN+S6O=jsh z;)Tx>YnhUQi*G=ViAa~oveX*P-73y9n()~NZ15x>DwerDEF94eUtYCYLL_trP>pjw zkP6Q9CfRn=yzQkhRK=4gnXxmTKJ4XEg(c)TI*ScyYBc6>Tidt?9++anx zT5i!xZ%IJ9W>|F+VpznwLzh;@2RTk}sqb{bwdb0hOdI$Rkw7QE$5;a|?BBd;e^uS3 z24SD%Fw9p6d7ARXp*wFK*|nrM6GO<`@0hd=YL9C+OP~`T^Yu<_){ELwYL=9L{q@Z6 z=trYYNH#?G_IyQ|>}i{fs$OpOQ6Jw_gR0W5VN6O45>GxWFTXi!^5E=QTE0Op_f>bS zk~M9itwCd*P%SP4)z z4VfPMXzfp}NzXp9am61Wpe{Dn&keMy=Ed;c@}Ip9Vp1W~Mtd99PZ?iD46o5uqt7PR zx?g)txLcjj?0zlw-O{69C#}@u2m&qh_`sxtNlMjp`!sJ~OlhS~5lMX7nc%feSJ;%5k)3`WkwTjiuYAjY1lFeOoi?lOLOlKYllSQ$Fk^Wnme3 zBF$>amLz*~!_s3Bc=CzN+hC{@%V}Q#=DnZRc6_j$>c#jNd$-2=9ZM2|!>Oyi%bemY zq|GhHZ_G+kKcIA%88b7hFdC{YaLW>%(doZlR>JN_^Xv`GYUizSRE~N^xxcyrp$iHi zofnofb)K?x2uzcEOb+3+=Ii-(?{6IvQp)%bDN2ZuYh6V%JBYtJF4gN-^Q(Uv7ZH9k zaK&pg>oo8+m+fizlb2-liCPH^ZPdd<9&$&2KJy=(<@DDdeTQABGQVzb{tbCbC?^*6R4!6fJcVePNgq2E1vX+A*;A-1>*N86!p_yG%q1lvjwi zx;JE$6&k4QB_I2K7WpZUCwg0oY(AAx%Z3;gLMH}`X`dg8@J~dC8n6+{#MaNVM7cyK zSrVXWT)zrofbR+( zB<%~_UWa_DrOT1LxmAYlb>f{y$?CHQ93JO0?{*w>E9r_z*(sGp-3zp6>T3!Je?T*Q z!}=>RliB8O0*l=(wyR%l6bh;D{JTam9eCw>D;+-ftIs;OE~K>r$4J4lZ`)05wgL(j z%0B98{n_s|5mrX7+;2?`+Uvv4)6leA|cQU2(oBfa;I_F&_k(VP$8LSQa` z<)@vD-(eYFe&&`ftm!_O(~>)lx<0u!XP;pIE(y zZZ=Llk|4CcU{&vnThl3V(AAHpp(twylZ}vaZ9iXBC@-Ry(B;#T1VXlbE$DfXxm&?{ z>|R#ZXB34J@m$x34Idm4r*7AIVbNX9?QxP!psW&-L^0w#3 zX&EK5 zwAPnuSo0OA2R(=(BSjuRrG8Y_{H8#41+Tvf7lZLVdz?kZi?Ss1+M)k~PA zq@XyMdrs%cUbCSmxL|kg4&Q0c|4$T@Z9hK2p{z4m-7k8=;DNMcFT5L;9L>c0C}jW| z_NCN*#(|ecO}ffX+tsIWt%|XdeKRVt0lms4c))5~{TwOU?Pxq+-K{>Y$HCLgdd8L$ zmPeGv;V{Dt)#rI0+w!y|pKcvW66m@qf6n&m-jz>f z#Ds(kD#dGb+@*UKwf3RUJG`IQC!n=*JU-2=P*-T*E0!N3`8PBZzl((^J{e*3Q6t7D z}w-?*03@Bcver zSS0x6Pc7L8|2rHUG{XFw;k|#qG62`ne;=tt?%9RNvEs)_BMU;TGtX-@QP2;usHZ4H|K(39qXaK?9}U z{IG@Ih?<7h@ykM3F`hO;QCF$+QHd*b%1M8G-qUZYU&r; z5dSDGWX!6%uA!mE%K2*7ZE0&OT)k)waJ2}ULe4{kt})W&y9w=!xp(0}^x|mQ2~cQr z3;S%xtx&9@^2RHaa=K$#z1fF^uJAm4#EP7ayXB~;%*eD$0Pl`7SletYe*Pw2oZFi( zpdqWXGa@;;a8xz!pCt-4;DaHyeZ`2Sq2-r&CxQwgh^iT~2JBuG;PbF_uXy{Gi}i2E z@CC0Sscy!W^H*QKd^v}=X4qCP9TAPX0)+m9#$C_GGGqH?9SwH7;#_koe_G2eI2+`=o*2K;Tm>DOuS+SNpB7Al&r*i5tsm zZD?d#WP7GL;LRUg)zEigi;@Unb5CJ)c_9|7e7=Ze587nAPr2rzBeYT4009h7V$0Ap zCp&v&kBS*47Gcb|73J!NNXv~`RL<>kP7>&(TRNv-^gUjSwUU-57z~suz(e+I(X9M( zbQCT2+uxrIP_%UTmpc#;Y)x1AU?H9D?@h2_tk@iGPx}!>1&?QVhYpwl_WmK6GQh_H z-~CI|@*i<`^h_H?$V_RZ2Gj~YuUDTwBk*s}gl#MfjE!$z76;Eax?zx#DhLaM&Nm*= z(gyfyP=vL#UN^e#8UYfThmRk-Gz_hFA3M0XP_n;=c={lMD-Vd6mNkmTua7fgKxd>@ z^CgzDaAt^d=^KBsOZ_@-*dV7!55e((z zI69RjmbbQoN2Y!af5ZOzjPmr<1@dRz4D@A*uY58Ajbb{dJjUz%ps@bJZXVa7TKi)F zQq>k*Q=UpnsuR$j@R-VBQ7h!vefJj$2ywdYSZk_Z>2lo{$#q)((N`w9w$d?3tP>g? zZQOp*H@sQ+YbZ9iL-cy4D?#^-ipt`xdHB63*ZiF#?Gj2dvc$Pmaubqy1smO0hlsIL zt@bh*;|EYeduF#ieu)?ozz(+!$Wtnhbv-?K0Ye%xak99qtZe?(+z(SWaPWfJU}`;} zC*U=Qd^FZ_aByHQ(kL@qg+>9so4~JMvGWu%M6|V&j~f8Z;?bCj!rA$4@r&n=F1ia! zY>5la%!w&Y&n>cZR%sX+$(WfF)A)Q_;2FNx(NlE~F+(Ua#`0szNTqWzQ^tGSPkc%~ z27kO>#r{J|4agiaGlx|sPIkF6Vv`&b=}H^IHa58D<^`DXPHY#%Z03ZUe7pe-q!!@!(C1ui}@u`{9_(qRp4Q5SUHUM5vD zS1z|p%?9U=0hv@`pn-&e&TwXBhf>US&07LG| zQu}*AJCwv~${G|Dl*+9n<#fahxKe2tc5jFS1o)fl&KhPx6CmtVBg5K-k#3vj7?-|% zzB3?o2N^h0Py}CJ1mq_XWZY9MIxH0Iky%om1*L`GKu_N$+xn-J6hN!-8fPsRw8~{F z@_rlyJ8@+t8#+IL7$@B`{B0RxVn*Z?os!I(vXqH#Wb_ztvIU;vTG|6DjP~{{*<}8- zWCG%DCssg<)MdiT!p)LYSSStpox{Szv+^V)qxN72ilNSFKwUEIl0m%Sd)jfPAdP3?z9b&U{%VuKcLm!ED|aay+*WE_l^{tOxbvNdgGC_nQ?bb1_AaUwV?s@ zmTi0N)thToS+D@hfC~=tD0$EZU-XMfv1>@uv2R9+lkNhHl_Q()0VmAroCi#A&TGFJ z0(gMZz4oSCnt90hu4s<6A_1d@mX;9HT~w5e;lg#-;C=`SVNdxx^65%1K2uX`Fa+b@ zfAIZ{*H@xs>C&{)}F4eJiQ`m)v1l| zi8W_taN(5B{W7WV5syVpt9d*v*>6xDc+BKLpJHcawUSaMxVuLE>gV*E#bIHKje^Cl z*Z=H|?0uS!oZ#uJ#?aF8#Hi+le9|&3 zE@2xrSRdF(2h=S%c1n|eow54(5vw3XNURnZk*RnxFle znblG{3lu_4L`=+TdZT@QO%1A_pAai+rTcAA!et}_XxRm%+F~_;ddA{g^Lmm*4 z<$e0A-NehLsw|%aiv=B;jCtIgetCI$%P95NFlO7cm!baV8UwZAonI{rZgfAPi&p)| zfep2D++i=ZaOt@Gdo@(||sJ#SwSd;!mjm`W-$(g`7I2YoW={jU)X2Gk> z*=)VSu9{7UzIgFKAj796n5pIbOSOUb^m_F+z_36isbyI~(8EwL6K9_(+rU#cblGrb z6_x(%?0gMb1Yv7hUwarhvl=&<($Q*$$}dm34F^ft7PS zbR-f;76CLex3{<7RcT*!bpHC)Z*=BSIkRJbu;h-3)a_Cf{}c;)I8eLDVx!3lg-Qrs zx>K^SumJYn$AQ6L;?v~~4arzWlmK-@p+wD0*qX_$! zJ0(F+b7p3yKls99-2TGn11N%$f^;gSAl)_6N{50-OGh8$N%QK&=zcev3jgE~~)`$)PCQ-9cSLUKgs&o;R})?0 z6`Am8g%-VSAy>LNo>hE=x;Gz4ey}kWevmIdDQRe@3;o zHi6OBYI+3^0FUO2Q40_=+jR}!;Yv_kUReQZre-PUr%q=G1emv}hKWzp8VR*^e*Y;W z<4a7OQ(ErFm6NF>sIZRQjkAD@{g?{aNOhvz!mO%fwsP;2qknBscXb8z4ecFj~}ww~9p zIQt}Nvhvu9D>gQ^=xOsun()V-agi02071Ca3nVb-1p*SG-8pm1#$~V6a-Q3i>y^6t zM8=}Ql;-AUyf`In`qqTPx+l5mjj7D3 zsyfQ|q|1f}9Czc!kleq8N9vrJcsF_tb29=pX5{1+XpfKyC}Cs(4AxZC(oi08?5YHe zQJ?rf*}mY`jT<*UB_&O7#z#iti;Ig>2|GWPygp=U)ZB+E|Lb`mohb8C28cmx0+=9g zSX@49k#R7US(n~3RPrR7STcA>_TQ%_pUh4SC!7jl(@PsTI;bry4qnu`?FMw5 zXz+nId@o4Z=~w)3P6gf=7}ozAM3EmL+;TG_{Jx_RH#^N&6nlman=6o(QiFsaAp17@=Km)Hocbpe#KMO3+c&I${I*R>YPs128;Nk+B~~= zZ=y}l7HTrhDe6-P5v+ZHE0tVdp+KBAG;E3Mi;~#E^m`0q;a6G$ysxyJM`G@x|G`wG zX@#>e8UafLhe8zjeF=K`12b=A5dU8Cb_D_I1=X*N`Hlbvwgi6_>tD~-{O+9o)-K(x zAqXgZyJJ_lrRk+*=Vk`A#|~H6;G|BU3ChF&|QOgS>ulJh0W9Pm0khc@T{}M&t--er%sgmgr_8 z6q+HNwb~|G%ci5j?3}< z`QVuC+MZU8ARHA6s#2yi&?~v<*uDmpbigm~6 zX&rd&mgS#HH|RuWFYxjv2rn?(aSb;MND00WM0EaceLIpU^uzOxaZyrRV@{`qm>za8 z_DYEs9Nlnbhr@_ZXD<}z^(35>P?UvA$^`=fooJ1+JB5H3_vIsEmo``0Q7ZhW>s43| zOy|ej&~Vw7O%-vVk0%s)xt5`&-GD;4PW`=qf^`fK>s2tYC@)VW=3d67o_r03d@lCW zk4(Go3^#8-5<>Or*WO2e_EMNoPm@bKui~!HEEXm^MI?LH4JRi-a~p1_y>R$m1Rm2$ zrU%(0OHMw)zKI@9SMlQ8i4*eE=f>H>>Q67Ww)U|084WFn$9INFwFXo(TUhUZ3V!o! zn2J>@$ARA6gsRNr9`DHdeh8(%Trhr$5lXDSKlM&KH|pyI)toP}p2W=-5+%bE82iE@ z!hm_JgIAqI#RNH0%Jp1;)1uveP<$@1@WmyM1hY20txKfTpU)D&F4;!gy{% z%5&Mr*s7$wI8Q3_yV{Dp-kBTxw`<+0n5DquJG3=;01&Uz!8iWRN5IQT<)2&2JzPjp zD|Pv1kg{_w`KBO0ziN)&%d_z^7BC2GZ$qQIiVk3f*=Z%hngRUUBCKYZv=)z<`2&Q2-UjAZRz*d4)*N*O!JIDN-1 zehgBTZ}%UQ`sCn+XHnkHcvp?Q4Z(?V@i%Ty(K4Z1hz=5*N;p=E$Cb4Tt)|G?D26Aqku}-26BIRPKFs#H@R?2v0!FXMo~; z@nHe{RoU3R=XQmb2JR&r&JUY_T8z{3mfkm20zNYW3=NI((+O0hg|H_e3S#Rjj;4Urqmg; zEa#%zAt-RSjm$;Az0(nm=%sNLJ>lAw5XY%8;+J?>%&R$y{t0nRblxhp(y|0t>MkA}Y=5C2oa-})NIcVzb zXi@9mKUgg?O|JB!_nn&ca=Ym&vOQTAq^0jcUDxz`j`7a{ZWU5Oe?rG1NEJFUyjG(= zO7-x;QbzO~wmDz2urs#+*~8h`w^}9yLo^M6nG|A_wpSdkHA}U?o;vu|ehOTC^s$1P z5mCBBWl$0KJ$UJqYZd+knDxqf5GA&Jh|JOsR?Z>^ncUfz)V_UP-I0QJ zy98V}?hZul_Tvvn??7==KIU`()e+{2L9I%DdFwv;2fXn6t_RoiBhopZF(QI6c=>Ln zLS|DVIQyB0i>W58c?Vt&L7q5VIF#2Otn>%)joJJb>bun%__=~7y?4_05>?Vp(V<)m zY@6aJ{;i~r-f$+c%3zn)Cbr1Hf2VLnk}N8kmc2UPlEMd3j>79jy26HC%hevS$bNC1 zskw~*S)Ylyn@QNs&BiQ`AFm%e^t#AyI^`)}n6l&wtsFlppon?(VaBXQRa>fWlDeQz zi>4=iK8RP91xr3eBFD99HYu6h1=plt_M_U^lg3X^buUGe-|97f!qU~{`?A}Y#$OQz z)-^+fCr4O)JO}anLKwXjd3=jy)^oUup^Evy)$H58z3DoVs}kWsNI=wlW@TplhCAJL zgmbHYWnE=>e?$sCzW{IhqC;A4Nh`|ou2`$bWKii2gr%P?tyoq?Mw2i#BuYA5xp8DF z`G-+4LmxTrlZATge5N}OLz%A~5N0n`(|fD6pPOTL{%=COWKd3)@cHz(y z&a}wHOqfIvQ3E60*NDbe0n_TaH4-!=m)CCuawPY}vzncpO@M^Dq&9c9nY)SLR6xxjtA`qW2&Lk+&Y$HC+2yxk;-z^!de1 z`jz&oRgMw_3eHsmVbyq zPYW}fKPXy$Usk0_fPUw4AH{O4pvgRlH{Ng_Kv4zuPzIQP!*6?2WpWjd*?6kr*PIV! zGhf6#gn$|}m2M5Se_+=ue$l#*!&9Up{j?WpF2AY~0krYXOQM<6j%Z*%FHJ?h&VFNf z8BH4vimhhTr8to2)DT$86^=g!%+~1Sl9Xp7^PKA3Q=kcZLClk15~;aPNr8ony94L` zW1|+6valj4M!mN95&mt~+h=K|zsAjDlbm~O%ZvsoL2n1_AW3b^KQs@sENko8TB7CA zxQ(msrI?CPirUz`Abe|Kn&G>^c0K&iw#;cDdmn4B`K7r=+6f=tF{;%U|$2d3Uyg&b9uNsIjt`X=5QNj)9<@>oG z7d@lw)0Puw$ zhV`(e_$_3h*cJXbm_CoYkd%en4T93k&CAh}jdQm{XQdZNgD$K^L3meqTA#&TQ`z6X zt*|wBRk9TNew`VBu|{KsGiT=)1K-9p3=HP}v(fLLpf~c&hRobd8lO3({DRa>IFdc_ zJ_C?9eXCHDZ3T>%Xyjiss^!00*hR!PIzW@sbQG0lyqK-r{tMXFY5u9ZI)~~T%&km# zyVg+G>BnN;`PVH<8+`r;7psWxWdivDFFBtAg!)3GZK}n?11>G+tADy`A(dEJ1AlXV}K(LZHew_?wNI zJj}>vi|65XT$gD=mwLDDS4aM9Jx_o;r1d{EW5^r-C4D*-Ly zE*_EYZ<6#F8`p5UdAV&%`f7d;@on$p6cMA6io z2-?zy-p%x-98Y=K)c!boT6om~4Vw-%c7rynWeZ03P9v{mRHd<0^sfXQhe|JM z82*MOiwx$jmd+9CR>R5luD#u~PTiH=EImCU;rEUe?Vqz~A_AAaloH-3G zxq;SoWyJVH4Oz6k)0I;PtxcEV7h@$00Y#Op$HLsFRJ$X^y2I?#$BVEKDyo*IUc}ip z^s41``H}pb1R;G`k`Nx;zp($2X!RjoMOaN39Ez;gvfRiy*$knMZTyw6qHi>N)!({d)MDw4hdpotBD)g`G|}c%hAp z%}d=I0#JvFLxk4V5nDz_C|c!SA?vdg(RW#eSCrwnn4g)$4bNw^4$i^1ozI%FiGZ4& zNV8XNNQ!%2*l@d2HxG@4LTgXl@T{j>);N4xAFIrSW)+dD%pp0sq?W2qu53*&Zy6N3 z^hWm7xN?D()s?my*!SG;pRaVzd4+CqdtAYacx-2{n-bxurF(d`6Znl`3af;Pq6AmJ z8(lk`51rQrq52A--RWfy$w~g)F4pK*^PridGrhB$h|>|iK2OJs!)VBjVv_92A(|bLwH7WfNbJ4^mt6a|Bx1j~EI)2_U zce2>>o%LCMVe4@r9)=rkjWiJh?PWi8bhJLS$#5b*9!{Jeo3(SqPv-ECK^;W^=-KMf zxs7sV4QR*7D4uPk^z(`ODo;&6V5ECKA1;j>k^%172#0s$g3TL$G~`!y<#pTs`A}w*fl%hP-6t1f*}f zIn0D(s8|`;*xyN{2SPZlH(4Z&TRKIOhHM{q;IDDgvA;9wEnNjR==Brb8*M@#AU9-h z5Zx7n*w;qegMUe>*N3{n`-Nw*U5heigyQna+OAxVx=@Oi^$jA9_`sZEY|xH0IK?4V zFk)!S?PR}&CLba6uB^7D0~B@r56Le+V5;imEDe!DO?LdW?(*T4~s3(RNBLWTB* zY?(MxB%TgKK;Ki$?{WKGuN^z&T&{V%L#ODp=!{5y=>xEJSg#z3bViX+XQTQ!O!0B< z+-GNeYy3w0`VMP*MSS9P#A?ZmkX%Vy>?5V2vwex~C9vHH`W5G{5WeUw0cfg^?%@Q7 z8cu#F4SDv_yhAIIlb;L{UwtGRJMH>VM%&|h^9FPfXA@N~K1@+ZlUOz=L^J*x;gao>^CrpV@1P$@+&rIQl+tbgy_ctGzUxAhJyV|Ag zn{a-t3B3}ZMT;;z2ptpOuwSJWd{^ginEA;~Fh~n^h6COdi9p-~O^wM)Fxy5b1Efr| zrD3$L8C?H1(vt_$)^R(T^5b*jFDb+D*2ApS=Q-|cSE%k(n_CuuO}tfd-il8veNvwe zc}@~@I67yr`Yti9bZ>PcosYPTE@O`aIQ=0Qd zZ9*_m`6c+w!L8SrQ++gI`xscbzg$gU5)<{oMl;Gif+C8qvh>1| zxMJ<*^jpR~qW2Gu&3><^-Uz%^x;s`h*II^O z<|()Mg^|aCn@k(gB@a%Q(S8&TjGqGb#K1iJFL7pZni0(qdVswrQ7~c;O!&E+)5HEN zAwkOoZLK7Y`LnOda~r;tmYYJcDbE%wjex%)x=Y>u^{R0D`P7k;SG?b)dM&c{8C9Yp zSbDO}W4+3}=#4E3$nWYJ^luJM-XmlDKSj)5dNY8H@IET!`rIU4RwatQI-WD(Od&?O z#2Ng60xYOmvEz_`cq4~hOrp`BPTui0UAi^hA6bvM7f9o)}iZ5G5i-f-Acn9CHW`5NnC(jd8eS(&SCN%Q)BS&hL)RXrIS2 zJ&atwDdDzP=A#t=@_qL^BPwCOL&fE$E6eoz@7hMJ!po0lY0we%QRi| zF6Y*TzUywfs7BoFnI8d4c>=25`2=}w@=sn8_&bpsnrGNdZs}Ivj`*|_2OxA1?xYr0 zm%reQE_dL#WBaoG`-dUy2%MFU$T6Y*2{!(2M)XXAXhnN19)YNVO7j>nIvrhV%n@2araf@9cQ+Y$*0Vmd{K zq;0>mok{$YOa!AC;lP(yZD`jXSHxBNp=`X%Uh@UW;+rnOV~uUOqx^W|zGJrt!4Gcg z+7BIQ?ks9xa)Go1I6uhKuYyU%PD~49wHq>%?RAizIy1r-8O+KTL0vt6^}FmhF0 zBgc5REEMu|?&s;66M0poM>WsZ!OfmpXZ<|an{w1}iA0BeU6L6`hdig6F`G8q3a4YK zhNZ6!GtHoHK8JeCDMb~$kp_+r>sbOxfokg!A zGmz5UkPCdx%lo+uj)ft|zzv^l(>;z$RISQ>I#-Ji_M(R`c1vd1eF3pVcYiguN#BF@<8!H?1gCS?-2U6rB=63@+7|;k3$hyacqJ!nb zY~1owL{CAuU;-EM9OSqrfC3-xIK2i^As(=--snDF&-)jr*y(*+4M*!sJ|ouVx;q{+qw#o^i-aig?+jphF6yEFM73F?a#V=qnGArI^ zXOz0$b0dncc&YVd_*hXF=qIZWb$7zgN9XhxtNoBEci^??5YVHC-w+=&PDCkwcH4~u zA9%xluu;zULIko|*p}+#43^wfrdvQD)?!;xim0)%KHIZ`d~~{vO-m-T+r*|Trb{kl z%*NQu11TV3<(NLdAt35`3*1aDcg4HfUeqG}W;y3+7P19x{jNI9nT+L4s#$B{4X7aCN5Vl4gB?22oW&35@a2-C18*+tKYhvyzXw4TI1+_lg|4eyFLqwX07voRSU537d|H-97B1 z3=s3C*E`YAaCo?Tr8x|#YpBgU@^%Dp*~zJdmi~w>%gBp*319G0b_ zswwa628E3%09?DDd7&Bp9??khtu@$kINYauEiKCIs4#F(^ThKy4E~*}=6=K7*p@e{ zL8Nr7?t=iZ6mU=xv2v9MO^IXze)1KFfl8~8#3X5( zH!d8SZmwr1QTUL`m2SBU=V{c1A}DGNs%p1*jfsZO*uphwI|jLhs=Eo1{QP8|rJ{e4 zBFQ7bpy)?myIQjJe((-L#%<2awS=h=qtyqblRQ?bW-+gQhg0M{Lmi2^4-=PI8< zF9tVobaXV}^s2+6TkoWLuQUO;hpR z=ASCJ64~z{Zt~z<)?s|%9#u%*Uo7u77cMkYW3So znDyeX3NJ{!M?W|EBIc)>%e;2I?%^qtyFBcKz+ME^k2j%4bQ*2V*OA3I2He=Ny(ioJ zlyUWr%r2(F^%sa5Jzu5?1kc&x$O}E&zk{}~+mEc#9Z6V(;^j6{MIoSt0Muh;HWs`g z-Ve|d0OP68?fe5{hdruAEB(34Zn~RV4FT8vl+r6Wm)w zF?-2&$+CvIB1TMXeNPQY7<>7gM?g+?04soByUV-Lygzh&{sZq8d+5;SwGFRwmB1z4 zfwXOOOq4HD;uX%|HO^~4P|N*-+=GuG_qMaBE*zNwJ*tl&PwMu~Etqa+L`6M@efbn( zK=$^jY5K8qM$WANjM)QDTL{EMBmyvNut1e)X6{Zyw&8)R3FMQY7cV%VKD2hrZ>+jtf#G&h`YY zNm#9}kWANq`15)vzSjY7*_Y|;>$%|6#eEx_hU%^41d3J;pmXoZTXKG}El$aa&jZBK zf2Qw~P&yimuzi>fYkg`?Q?SHXWBI+gAATd5?9k|gT6N&LqgtkhKyKPX-G7DcW*5nR z`X8bk%oD(dCf@CF6M=4jzhZd*IDRQlE11^#wQGEvN&C0Fj@}MxNLe1HKRFoG=Fn#W z$_1dN54v~yw7owd9DjUe1D2#iCJ!h+o;-lK2c4mDuJy;2Q*`kBs`z(f)Jjq<;TiBJ zEOawnrP0u?!KXYiC9oHUV>ZBx{f#qWd2wD@9fhnzXuqG^J;`vDNPTWtQRY);{!QTa zhvsu_em-7x1GS~vpxJ}w}hO=^om-d(HATaL&LF4JB;9sW+w6;J=;_+7jFTg7* zz!MlEZI%)2n4sJ75{)MP$!{ z-9gckYzyZL^<(ESoh`D{nhvOC8AhRm)?7N5NQ))+(&mitH!BbEKUG)X`XE`2pvz=I zP08)|^pzIF-ym?+t5Ns^>m{BI_7)Ctg=@45)Rn-{cdv{qjW@qX_#zqoUi`O38(tg~ zU6e{Id?V!%kJ>~!9=&iO-tDHhTT=_cNb+#7&%-5&Bfx1XT`mPq-e-FXl?HM-=h!=lRRG2mS2x9mzX{UZzv{?M_7anKC2JH3JzmNlX0JT=0KRcc_v@sFe9YZ~ zIGq}v(@8)F-@kp*^qSW=djsjXoDw!c_)oR+X5i+9ri8xEw6~c3fMqtv*a8tvqiNA> zZ%EQz@hU_^o&Ho&6~fDi>nkUDHSKb_OzQJ_%4HRebV4aTIMke8xlLwgGX2D1pnlz1 zc@8Xj;$X5=kq{-5NZu zCzN2j98==u<4rqu2Dw0VdFH9vQdx)U3`&H^tJ$`+I}G|aB3NYmC)SwSM#6I{E^2TF zaG4$D&rXW*_wn{yta;Tu1!0@D2me!}1m(edAjDf|DjksCjU5uO`fs)jL7KoXCc1wu zNAQ(7ygog>wy-YJ&zv`X)(ehK|DM5g6`gA!FoyYk}KR)J0>{k7TWSz2>~ z5j(u6f#G+?4Hb(rLn3ZJ%KkRHmED8rcxrq@k{GRd50T$S(L2=V+n1hD_H$y5ija`7 z>f=K+mivDmj`uXsRi*ztmLFx__!j)<@#}>m_7m{*e;!eJ@pt|IzUICb7b8UG?~4jw z%R@f?eUWb47YOm+7yZBZphgBG(1((QYIGREOCHN;c+z-N<9SVSZzmu>B(Ey)t|cOq)) z$mnRI^YbcVl2yH*C+O(!DmPZAux{g_;Sdv3Esew(kR7=&D}9xH=}z1|=aXDm*mo#A z*3^V1Cntx~ZgqGCVIScM38htjHR)b%t*@Z1z2T8;#XFf25-?!&+X3gp{5+@nAkztw z3J7mn2NWP1jr_zQ^aEi2Z^MpyF3)0Q7cTY?|(^Vxz^iiCG70^Dh9)cx+6YUP{TOi z1zrfFF8msrEX&Y;Y!}3#oxy2Qi)rO6K!%IjZ6+uvh*?NTj)h%V*~H{XrpndAouP$D z4v1Tu|Kq0gzr>25KY5TUfNPhUvJSr*0TLOB)wdbK&*tqFHet2h!|8vz?tUXsk&veBR z;q3z_@DoGC)6C-Hx1cV-EE}27c!s@kc5z``=6cvT)*u6zp?38sSm{Xs%m8vo!ge2s z99LJr8uh&(^ynx_;5n5|N=ia7u!qau+Vz`P1f3*hBe;@Bnv z*&d(OSuiz}zrX=3Q~yqDCT0W{EFT2{v^M*vWCAnZltBI1eEbJP7#^N?wHYJ$gq_QU zjg6a)KBv%+_gHBQyyMcAZibO@eSAYc21o~?ZG*}ZzU;IGy2@T&lxS!YZ1u|!=;(sa1=zq}WC3Vhpdhg?*PWBk2KNYzG~`Z1 z8!vak036`r4yR0hjy9eu0;jbZ$5(Bz9rGz3F`;uXv)x{8isWA->f~e-akVutI4C_4 zBKcW1;sGyP;QF}qK?HSuK9}gpxG^Bie@aN`w5gbpH8CNkq1lCqqRy6jl7vFbte~c+ zyL!`(-H!ncE*U~4_ydM<3lCjY)Ae1yI3i3ch~0NPBSYp4aqBKE4C~4B=WHm{+vl2J z(B^lRLW_!^5FnKZ^h$O0X8Nl8fu?6>yDZq3Xh0*q)tY~4b5&VJh8)~0Y1uXi0WB=J z!4Hy=(IzMuf4#OqY|*6Sq?ebEzhpj8C-`G|@02+w8R#!F=@}1s|31)ZzEPh|%w~90 z(}_(Ww66~j5KB57O9l1g{YJ$(DX1$iBfPNjBbR?IU;tgwYXR@;ivlAS9|_|@Zrvi1 zO~()yuLlf-f#K<37;FcnARjSRG&W}_4}D%&YPup9ycQxiULXx z*dX`4y^&86EGJzPkwHG;~bPA zP6I`>sA!}A(cH0KeTZo?+Aj~P-@X|+N6#Xd^ex3Ei|MNG`NKc1Fm99&NG$;EmA<`& z93Z%9#7p+ZI={x+Mov~1v|4rN71ek7p$_eUv~AU4UkwCpVlH8Zy#c2~E`{K`&15ey2P zLt1*)6$ChJ6tX?SP4 z>}ky}WuPF>H!=dOI*)HNabcnpM!y(B4#;o|>JL|_Z%2>NmT)*}!E9K?b!8l&&rdv@Fi|0xE=J6J>z? zy^c+2XxjFd%1s<>Z1`wvuSkY6`^_egwy^N9Gb*z`sf^{D0I5!JyP^`RqK&IyezFG< zrbDx%vcUXDL@0xgorGaGUby6igecKDNIN$4e{bMVPS#^TOVGt(WgKJWiPN~adsD18{*4ir2Wv8k{B3tr7$Pb9GjUnh*MAU@5x1{%X-xbyYBPNU zrKd;pZ*G2ti1g9;Gm*|1O32&12`)S;JgJMm&+&vAzVnjpu-~~X5}&% zd}U>3?cznu3hCp80g>VIT649aSuG9@yOIc+Rzpq#QURR25&5U~WX$p(0YCj)K|##w zDii1m3SbeQ706jx*-)}k!UY0~{v{>qKqdGhUtUK?2V?xAThqw>-XX3=l?xvIi;_sCu0#@@W|$y`QpZ#8rZD)^i<*6vs3M{ zA!tECL1R?`o%kbDoI7?GcQvg9z}Bj)s!AT~TDmL~B8Gj4PEOhu5lR>{IKa1-dUtBN zOUGw(|KS^=hlsP}P@3p}AsNjgaW*JAHkHV7+w@PMn;a z(1Ih%adpBzUU<{Ls}|N_Ttr1y2D9E26-UQc{a#ahtE(xwO&}ncWZi+ViS_@`8apriIFJrBp~YK&YT0F^MHU&r z#=z|Yb_)apua~oV9ZMn!h!UT?bpPTL?CcU_gJl8iuGi`Ksw_`pFNi6BPY;FxCji_J zxE`^wvcbW@FfA2F^Cw~rw=rAR`sdo(V!lV^043-B$Te8|%uKQz?G+j(=BWsGysyE@ zXt$tXQhkYysi}~JL@J2b+LsMq!&Ca7lJTRLPDFKmJP_}2g*ncQgKmSfGZ25fYWg5) z9z+jSdJOE?5Bh+}^%Ud;v$Nv7d=r6+u$=_|9dZFazD2LWQgPBz-xTb|N{8r;-QC3^ zm8(D?v|GRbo9M*KAY0*5TQIStD ztoK(;Xp7;jB5IOPp9&H&eUBD={T#N1`xE{(p;%WX3HYaMpu zv*;|)ek&)VBDv#kL<4N^LP#edp8`*;Vq(GzgpyAln5#lSNcc~<4tCeO04Z(zme-b6dDwSC>7X2OeSJ=Oqnk<=N{PPI%*tQvYdIrF zM>RpB$4L?H?uFnqdhH2wYHms2J345G(qZ|Jj~ktx-y9F>P3bhexLmF}QGTJVEqgIc z{Fg<|VHrn+15+KgC9}CX68L&Cv*tG)Ai^M-w!gEpVSRsz{qqRZZMtx58<- z+_3|ic=wr@V(+nv-}r$)hUm5Pm6DJUSBMjDEb_agtVbOeoeEW?roJ81_rk@;#|Ncf z=E~FABE2-0g5_l@0M`HwWo|&B-toeto|Xe~Tj?6xx5E1Jer=2$ksp5e2yQoEx}WGG z?ZI7s*g&%7W>pUZUzmGjC98KZhcs3$vu2|Gpij_r?KbT$BYj)jM3yj!cY1CRNG>QvR@7)7P$n?tv|1}3;FtJeBj2L?8h zu6{MdgU}K1z&pUD{CW_{?T7MC@jkdJc3M+LaP98yg3Lx$L&MmB?^OOZukif3#jcxv zD_iC+tZ1^#|1iG0O(L&CHYq%h8WO9-4v_WA7-fDrf&);0sWtea)Zv#=>HL zIdvL{mB;s(c^!|p8S+v%I5>!{rRJ?IIi4b(fV}s|E1{iI#P0`lz%>M{rCdIsp-JK8 z>znnW7`<%&(wN6cTpev!>vjQ=pgFiZ?RR`K&NYBXLsI5==_ z>OP!adwdUIZ1&4-W(fC$;kqqv>`lG7%*w zCreQW{<)yQCdd}#R4ySBSnGpeg4gy4-1L`wDBCTMHxD9VO zWin-!-=69KXMAs=vlcn%1Ttq%JJkJ?-Se>bMh~#=gwD^ut@HlrC`8V)$_)ZDTie~A zyaFJ`4ppKYgV?X>oeqqQ$h>%Q^G$k(#w+#K12=;#5N}=s94UgIXKKo$QpBT2%Qa^W zP?*5rL!Le*YYNjITiV%?0s)7WX3@9GN&^2c!^J)ETyfxe09V4V#{Y}TE;~`6i@pbP z@*CUh^O=cuO9LH#jXm0jdnYI4!+ew!n|p_c%$%HL{|Fjl(*4PFBg~kAi%Q`05(n@o zKovg(1Ux3)2B9>{r{>IJr3OSoTmU7ze?JXvZ~vH?-Ir{DYyY`sED@zZ*w3G!#Om(8 z2&et^+UWMTcr_KA3OW= z7qL8V4vr*d8veZ*!NErvw;So7gJrQQLJ{GsaL9&JD9LxdWKv4$GZ*1M-1r(p>f8&^ zJ9sN(_H(!EuWU4$rD=qCh zHE0j3YHepS}BQSaKqXm}Rh?dpNWUk=UA%nS^;h0o=4@iP_gIs+Abx!_YNp@?-_#I2*y`|5h(zwl7DI;2!7d~t*h&IFg4BanWe&lklRcSqWhPYhF->lZ}3XTW-2c)4+|F??i3E(DFkTrg+xTOjE&(JrR28D zR7_8-y;R|_Lr?|RtsU22mrSnSAnKRl8?nU-PU0YjxzRZ&q zk(89Ye~Q2D#!}d1XwrmR0g!{h^mL*OMjz6hc%eg2Ny%m~p)MV`SfG07=orCmw#_go z8)@EOxC7>z1>&l_UNhHWdC1;uxnqVrbN2ezZi2P_)s_xcQ`ns|ntDoLBB)89E#}?| zK+aemK29qsN!s0I-`t`Ac;YS5qMzS`rGielUQ2U7kjwm>>~rz4)}@TEvdV*#TPPf0 z?fiDHrp*1o&_5otz6@}?NAjSJmSv6rRKwkZ()(;GzJ9%kW?`Lez7>1y?EG@P%83#L z+B{Y#-gvZ=D)O7-p=0EKt2ASRQZJWTwp4F9hzpBM1`5B=O92H6P1Y<%99qw1FK1WR zz3EavFoh@@xZHfrsiOYyJ8_>tr~|#)Ck5Y_E%s#SZX#v?AqNKsxRVyUvx;)&2Wp3@;!joQXv}H>OC1&TknyI)PUls0KN-aSA0o`}LHblivETpCgR2YUMLP zBO~Vv+Y5dPVqTz!wLMkyp}agc6|Z|Y^b<1gSI^Q}H3kFQ*Y{eAzuzDluukm@&7cmf zkIR&-5o@`3gy$xPVuY@IM1nMZWo48*Wg?VK*1ju9yl>28pc zF6kIx=o)ec7?}ATKHrbe_gU}nUGE<+Yq8c0_sq??&)IS9YoD{X+CM&LDcMC)69NQ< zl14ISR#dRq6Ct;;`QsIBI^Q2---UAZKPr{{DX7yG@UStV$x*Mg-fmrBgqO04DZght zt8UQK+bd)7z9hs=#6rsobzix@Ik_hN;GZRS+OExc2qD0KZe{g7qoIKcurJJ@>SsiY z5zr3ucP{6mCHF$Ns;UO{R3f`3!=O$#AD{=?mo^BWqDl)=j~}JP@9XFhG{|CB@V(y{ ztfL-XmYkHIEuj?hs^!~?fAFH0mguRP^;Ht=3F(oixsYew^(sgFXnwn{K`wdr6dXMC zm{n$IcBm_4TU!X@#dFd%J&7tCFL_*r=Qz)l&nql?gO~r~1QCMR6-FHJp0~;}>M>L- zqOku9<8MoZAw0LH_I;bMDQ$zpQt8;1`GNDpmbdLgeM#2v`snX_t?L_WEba)rN}SCTeTFp1ac2VGtC zyws)^zj$^yXmZO8id<1+6bL@rOkY6e@s@GD6;#$^SN} zB!Vv(XNmDpG(|_d9I~v|PKgTbMvfZsMC~_=FsZ)A z^kY{wReY;07s&JDI%3{l9ye2U{~5Nsevh|C$i(IGbs-VzNJl87-bCsWJwvGXcjQ+j zzjccTo1ZKBn@%N1t9J5VBPK})=hJw<4XPr+46x1d2PbyPlBFccsjTq64dviXrr!-A zV>yb?b8&SU9-30>{`P>dBN(3tQSy?d_Z6#+9ox$~=U4iv{S%^m5S}`rl3g0ZdsZ)4 zh(se|>h>UbdzCe(KdyA&pGi`5w-ns&v%9&|o8xFHppj2SQWo;DshS4%`zarM^0~7j z0Y9UmZPCrt^EBPP@XJ|SR@Y6DiM(r#*K1U>aumziaTg5gC{vTvYe%7H;?qis*z6od zmI}+n;{O;bJ&AD_A|pjtBCZF8fq~LP6IwFh{5|+bv0X1@WH77_aMs@hXsxD^N_PR> z@3V$eNZ~y}g2jD75H-?_NA7(E_*t%dhg-kiWFf8yZNF8d?j!1MZu6*m!`skNQ8 zwyx>$LD^Pbp^5#bIFc(?1#j=TwFo_DXI+fFCVdpmELX|c&i!Ejz~fvz>2!qK^s(qf zRd}@xF;PDHwQF(I@1)F23UxxQrm=-gGBWue2Mlzi+%0JuRo=f)zWqMGh=lV=xX)=_ z)yeP(h4VQD@@r#OG6PO$cJ*uuLFIEeqIpR+!eY;X=?+MFfi4Jb%JM618PCa!&H$6a z+CLRR6uPx!Hn$O(prWB+ z0&U`U?zHY|0$FwpcE8lb`{Q3m#B1CIzqqb0v^Cg`k}~#Fi_zCRxciNb@3pw3_9>dh zJ0jOut#j=h9^^@y;ydCFnAFM_?vN{40DA-CGZaVD*OEQLh=^!OGEcX8YLEFroJt%ovxbLgLeZfOqkU+sH08sbgo-}sbI$3A-|$FLwTBU0qYwB zO^A_+qOhQmv4^e(;qB>Jf_le2r?U1~ z!?^<;H*$Awr)5#$Np~l{qNXLLi6BB`&&)GG9)T?gs;E6tsT)=Q$mZ{gH9#5Te@UQH zG0-6N&rRTtM+wx5h;qwMy9W0NosN&J25y!f!dy12-e#I@s)$`!qMo8*gYn*E8LTwY z(`|GBE8&;%5qkD<(>_x1%_0q{t7Sfl_&Hakj6ucI7BOo)E zQpLSt=1SByFtKmm&v#K`JNE%9o zmWWKtus$Q6!|;m!%HyuND#7Rmfsw|B%+craiuY#I^gU24G~xjyl43eba$qmUjAaYO zn&e4BvcO(>98_6c`5(9Vf~&yBi~K2son7}RAs`^+IM7T^Zl7Csn&GP59r;bCyv%#@dobR=cW!YGDBPN1bD5Syj z2-OP5K8Dg4N-!S3B;mztHP8LK7ho^ndCeBtytVavi#>i&Z?yR{(>aNtAw`a#voY*| zAowFcc1K&m(+Cf;tleHd_CzgOU%}CYFxr&T5#_?_qD~g#+=>6XoIMyg%m;$X#)mQ1 zWGQhSS>CIt8hbEZK>1kULeYU?H_4>$Wn&K3ttzgiNW?S;^62@BeM*T}(2^rdiznxB zmJgv#dAhOa(lIM;Gw6HIiFHkXt5tyFt6gF51!)P3IoovIzc#N%`Qbk<$;n#(fXd(6 zbokw4QDZ()fcEA5=8_&Z1tkw{J^i)VwbWP@;0pJ9|8?k{?~nc3M``q4)(raF3`UTF z-%m{nnM|jxD_GY`6YPkZpEQSFcb}wga@=<^J-jFA1YG+hdIq$Q)}xoz_Bt1Cr%I%j zpI_5@8$qoWog_2!i_}A!hK@K5BECePU6 z03x%3+0NZb>m@tk7t6fUgX@b8HDQT@zl6n=X86Go$YY)G1g2r;^s+wV}FVoYWIA}K1c-T8hAx$yWnnt@wIFcCr5NI)7=i*EVXwzD` z-B_Xhqdq$!&stPnQ9&hXY}q{v_tgS=m^v^S7ZDW(XQ;Rwd!f^Pd@Py;h|3d}pjB%t37wc6XZ88GWlfYcV@4 z$Onqy*B6&pj?7JX8SK@bc$)Cswv~d`NifygI`JXqtwfNUAwJh9VxWR0ZhaV(FwQF< zUrkBpLG{m{iKCW@m;p11bIgWMo3y8zuC5If79Gtyzeguv3%PUm?t97e<&V!QIB60R za<3SqbN$N)r3Rk{X>8CPqIcr9l8YeaItAsG0u-*amVYK~Tb5QPEK^!qCLH#Q++m?j ztG9~AMfK1MslKPmWaQG8Wy`+?DqLPezpZ@euW)lmr<@U3IlRqoSSK@7(s&q7bxzSL zk-}dwJ~{Wy(p#np^P=%LaQSKVBD-DcC+z=-HJ?l23E_NP;aWhzi$@9)oTSGuhbC<^ zz}tiZad~GtSi|81(DTbuQtJhA-+<)r^b5Z7-u>f)yF2FQmBtv1vi1Y5f9(%9E2PK~ zgcWKP2gk32_dXh1+nbK(+XRb{McZ`zzwvT@R7U^Oa@V8tlm3bY7}x*zw!brcCl@!9 zvzZ4kSldyPeh7l9{ne{DX0f|YpO{76r8BQ98y5A<*<0xmW?Cv5fzKr}^aZvB^7`%E z>=;Pd0%QsZBO%gdT-p8Jj< zw|l(A5iK2DTWVoJ4C6*pM#@fON!IaDv`BP2e$r=M*RZ8)G-+d44vRgPRP|s%Zn2ZA zE(V%4p7+MOxVsu#mqk@gi5S!vMI}&ZhX`JdA3?dFPRKASiQ{0=LHA|Gf#ciSb>#YgDitg!BPGuX zn1f`TjYb&f&YQ;9w$|$JkCR`bFgg55>T#L|eX|hFKyus>D0*@@8b`w#-*WqYdZG$x zSo#w@1XcQA<8bPL`@bZp9$jG=-yLDa#p7tRM@gXpUGFW^j~P_%nbe@!DHc}N`!{Id zNM~CsTOkVU2I#&D#bKF*qYd$!3nHOOONWpBoZ+~^sXx#6zdVlj+3a})UAf9PjubP+ zAXXGsI!Nr7kKpGinJ|X7+35hB*ABF6rZ{vN>YL_pwjx^X?Y&z^0)6hHN{_}Kr}*r5 zU#_gLitt3w^=1)`c%14s<d;bX%m(#5LCG$FxFJ@S=rz;iraUU zQEx`?ct9Ce!L(`QG~cD^*B(5;-D>rCkzuW8W!c7aeRTkDx2uO~DpD~?AgDcZ?xRC7 z6A?+|UN^Vl*Kgm+{BgdBswJl0_V(7ei6ue_Pjez01MT;qrjGK(k$?jylKCD{hh(z))f;C%zcZ94rNhofjC6yw)GUd9Oc+ z*b)2GHPkz?$)=*I-J@)eDZ;~uKQW~bWoiuQ>lu$}w4%08->E-1%(pcxdDrXXfO$VopDBdaS$|4{>M4S1;}YkwVrU3gFU@*|{D z`1f;@)^}(WqU-h6`yw~} z>j{KWHm8_(5sO;4PI!;Q3?J=-|fqK5O&sMBgWdg-Wu3uRP%{0;fNNio{Otw04FQ5@RJM zPO)AW9z;3*{Jn$Z0gI`HVmZ$K-@OnOvQ++>pZ3Oc4@Pk(B6gg1KBys!bW98EK|dE^ zv*=*LVD%+(2lzl4{9GJSq-?p)%~%1`)m3RNm2B$$7Q9o;&g;c6QBPu171u`EwKZLx za-A|p*pCHP+dYMc$m1%=5?U1s5tl6lS6y7)-dWkKeBBut9k~jT*Y$>JYARRZO}n_c z8iTXwOBNO->$A!&Mn+3t9(Nc@ex$%+f@umA!wk?qiH298z z1RE=gtv5xkB||U$dVEj7L;>fBMaUlLyAn?dJ@LVoI1X zADF4m)r}MzjYXtMNU#Tprgx4uYz2JN+FSlX(f) z9Yav>>B3I}!yj&nDZ3@W7P9A@``YiU%^yyJ6g%etV-b;!RZXnt3BW+Hm-?>uow=>! zRJHZ|Iwby+m5k0$xZ$sPiCdDpS1AHm>FnKCjLRQk><_&jZpt}DP8&F19yqC0bPcDU zhP_-2Q~?M6Zvb8NhI${Q_RluGqd7q8h5=h1f{5*!bPkN~SUIT9A9*797;k=QgUmlO zbi1Oa(Yjec3c(CEeX)coEp&!HyJ^_Hi;g}bDSQA#`dg-l&dM$8jBcWx}-pES?ygt8HJ%TNRJGh_+e~D1lw>Q zFz4}|J3H=22+PI;-5>G~2ZRe_)8xef_2_~++bpQvVtDJr#@hM$miH{nx7bB1Yg;y_ z7i8&;bEpBmqT%|*5o#9U1V3bf$uKfsLS_ zxw{JjAqiv*x%2kLBW%X|)k9I1|9~Y;b^u(j*%;`{&b8?A#rH0xGx?^~VS%U@P4;(~ z+uRq{c%f-YH_^^C##HU?y=`yz(Uqe+pg3(i6Q~NX6Ufibzhna9FS!Nor7H&MbEZ_p-t>IfB9)U#l+qa8^(1?XX<;=54v59(M)QWf=I3rp96DQRCxR2*S)HMxLoWt7Aot=kf!N0T|>aNZ&Bl|`o zaml~#d4{Ea0ZY$m^s7G4w?9<=R**xnWX8J%NLXsB9H_NdlO&T@@ZA_rtc$;US~U0K zD1_s)^Qp!Y*4)Sy8%fVkM-GLH74Wm3#s;8;zzXR>k&2Klcs1t0SLWc8+-Gx?{uNs9 z?rZ%BnSF2+dKyfd>(OMD^C-3bX-7i!s*E8!K+$a6yqgKgoe$$s4nDMwJzcRnI^4!{hw|(*> z=m%@eb9;9mZP6B}LMTmay4@8<1d5CtEiGyicjZSX23*-}(;s&W4M{g0Wf5Nl*Yo1HeLC4w)#Tv2sOYN_UD@+y)=mznMGk^dOe8cgYNXRLCW`?e*Dfvi=-+TF>`8X3Nmf8Wk zzzP|wFU1FJIV=41@<*A7BVoU>v;*zaKc-n0VFX5d_uNYl^EyAtDnY*17k}ADu!DjE zk_D#@cR;Ttf)v#JinV;8dYr}t+84D)y}@Cly{J)Sos~& ziIpVpqXb;@mX$@fBG^RubNM4X&O|wp?9WOQcD3ls665Mv=g!JB6mz}GclF__54UC6 zN*tA~FI3#rLfLFxEt1z{@jk@h^u+ngazgQOwCDvq6V-Gu@5_IPH$A9WmYGyfFuAln z@N3H|r#u(G7>Gra80z6?=LmkEnOF8&UZBrLK@4b8Xgue=a`juxVl^gMo|iW44AGTq zZDTE-t3_cu+4H{7J49K22@8|#4)k32w~Jf&5p&t^cu@_Y$Y^nD|Lc^XWMN+CR;amx z4o`TtvUtar6pPe~2Kgc6>WzV*D#?Z8As>qwH*tBGSJ)@xp@c{2D?w~vVoJ&@)C@d6 zd}!?i`xt#scNK^?&<4R(+opT1?(>vOof`=fYy5h(O}@Z`)bB2hB&N~VtU??65jeDz z#VjujRViRJj|_m#VD*?Hgf`p> z8g2lie6{-i?6Qhjul5eFnQVl9uqZ&m23ig9!Fe@%8}rxR_Q*qi)f3nTiU84idt`(LD!r?E8~SJEJdWGGgUUIRq8 zBTtG4I%HMo-SPg<nKLwrBU4xn%JNw-$fT;?Bs~%1Gt& zKUP5X+zd#Y=ht5EU|Up8Vi3Fl{^onbSa3!=FC+hxg?{m(QR^|`@#Sl&w6eCRugIiZ zUk5wA%=^u?@=+JsU8p&(kE(2V?i-@W6p|m=X>P+W#z`KVnREoz?!Qun{Zii-+`s8V zVaN?=N}#-k-!A=o%ylXB6_vl#{kLb1IgiqUz5Qt*hF+(}#X8!!L+?}}E&t4X$`MAL zSBHTw9u;iUpBagIC)g3^AEg!|caZINI5dxRY)HXC(bDk_J>()w*=iZtG1{31*xQJo zQ+@pjlB|BG_dDR?p^8vU)MBrxkaunAaTP^W)AprlT*GY{LCYN91LE z1}|N@U*tvd1!SsJQpN4;BI>-U^Fl`^_s?YL;c>qG=h0=WmzuCsk=5Ar6u{X?Vo3jE zO_-e&UlYw=ds}G#6{J%8o`gegZue(^mv%>epijUtThIWbV%K{gz&k56LK#~va?%fu zg8Sq$nd=CPt{HaB@wPV3iq60?VOtjGhVg1?h8@Rsme=|CKJWiD@nI$iN#>z1?sH$I zy!FcU*)0e^qwb4eVd|x07OLf85a$%r7r(hn4PF&~@AfO~vSk+Ij0h^d9kUXC&*R0Cw&@W$+Hx*( zr*eRaHkrQfs$>mGX2AA64uRLl(IqLL-ia72dLz9{!G=`*^Lv-M0DajGdl| z9%aH(o8Y@{;Tm?~8f$i83AY=a?-XYi+ZyO-eyo3*`xxkzAe|{Ne26j5!n+kkcqyLR`W9(4!(b+}j{LLZVNM@!aEluN#x;7jUa zvBD|oxG!Tr*wk-7xpmMn?2(lm*W?q9X7pyPH*n&D@hqX_$B^dROz=#sBUQBa-u*9) z>jnpHbz1o;L&1bYqch}BJ#@$zt`?Mk8KpE#BtDMM^3t>CsG`Xn+I$6zQ*OzhP%>$%>NYBz&9h^efjYIOyKj3U6S`(X|pbmb$F#elGTKxPBjc? z$>})4r>Z)?YPN(!y*|y;@{4Z^c!~JrZftxwgnY3RZfnzBIaR(rnBGG{ zJMHm(d=ngmd)stZqkT^ZKbW>opj#{;wFPOAKUS$F!`x$T4}qH&tyM_7CJ(|;^%M+bVt}P2p-5QN_R8tfC=#U17X>ZSJ=^<^C%B0 zjCbpR3(+;h4XY|bI~L!5E2Dgs8uiS->*-eF&C=jvmYO1EDXVL|p0X^;T@snRRinq` z-L&qlkN8$;ZltBGF{OYT&ahJQ2Hqx>k1*-3;%YT_C!a{q{&qbjJD_B|&!~mM{_AdS z68o&F&CzyK0+<^xo2+6_R>C^YT$gnz@OZX&UM}hLCe5_Ru31;9K&_6IwiG#kFXhM} zvy4z45b8%wA5s__C{4dxPn`a@8kb=BVw3KZ z@KUlNF7F{1Nl`-PCaM%YtGN0{vo)F{qHtDzs#lC}h5B{n$!i>4y#Yrh!(FgEI^A?< z@>X~n=L?+FXY<54vt_i9$Z5pDHTqY38?#voea2%|MZYEyU$KsFntV@*)k+!?({MU6 zD`zE-&KAPI{DjM#x5$0QmIQtMWVBzDsDO=AgII~Fif0LEo)73M=$Cvd{$hoKmWG$~ z&-owiycv|Q&8kceaME|FlUEr~WN*im$EuX8aFWZ*4Zmr+db0jXi zt{F)XYQDMB~B9!8lgpncTJ|@ye<{+fBKHpqBTGL<9IXDSqBUq?6K~0Jp1!B?j{(usvfG|5uW*k% z17+_7bLT+`;d9E04HTv!brSvRh74$nvyXL^_0u$?5W|RbPH^j&9 z6aR2eNKe>H`@QWlG-)M{JpUi7^!zXy^0k`9&SS;&n=|DQq1C!oxoYF`uIQ2i^6y@N zVoSFF_J7wSV}ab~k_8Xi_)qi7e@#SYYU)MLgTDoj+U~RAT{L<7`(7H|X!KnW`od0WhB@QzWn!%^J7&$6#r`^32t|7YCpcG6{ zS3p_SltK8 zas_Pn+r+Zwnf#^mS?{-n$#FJ$gNCIS6Zz?i^&cF!0-i$utK|;s*jJ$uX6f1t(}?}11kx8SU~{{wH!-Yr`CIAaa~11b^6XT60uQbSDD~{@%c0(A zQ0PKsuN#Yb9WkqBGOIwmIer|Yq4mD~+XjN>_>=E=&#>pp?`mf))in!LGsfS(e!$fS z$4e~|PD+gLT{b;98PBo%GA5X=q93RF-XQ;3!RbnGNdGmLu_N2jcMP>Nm>;8#fyfu$FdULZHqls8) z^`-VB#bJn)6`y`tmsdO^Wy2>v1Mm3@>+*lbFtgS738rb1{$c z&ND2?{)#^>W2OYeD`$E7(6O!D1ov&Wq z&y%@ftJtT2m`)gIMeOY^&js}Mb}fY~>S}w>UwSXXK%pm)5VGD?J;ON0B=$?*MsU%k z?&L&KK7>C7uiD3!F?Lw(yVYAp)co?#R0SY`ah8o*t?Bz53dG_~4cpR{jGW8a6?7-- zuA0VtYbSg84l&m$+tEcjZQj#vyC>-c1o3@+109H?{Sj#W$G)BU0rXt|R_Y_{aaY36 zM8Vh)>#noiWx=)5>5FxqJ+M63uMM|-z{M)j+ZzzN5`CTWrA=;lRn^VC$@Fu7ZjZgT z&&yj6ieBVv@bilX#P=&P`s`3q(?l#J%OXDU2hZ<$d2Y;!S`}CBg$lx%4u#u-Kkg4M z5@pHA3=fX=!!;a?I!l~(424JZ>=Z6sM z2Q%VO>g#NVp!@5?c*Nc>wTGJTz)B@e-g*Rk625kgQe26>u7|_(?eg2&Ew>x3t;c&6 znx~zoDee;AezfEB7{MAX9o+`bT}=A!o^>9L?;Zy~d?0*RcPBaIqr!mPYG+YHyQ>;o zaq*o<_e$z_+e@S`i0Mu?S8cQ_kaygBllniL(Lcw(s%1Cp(`YG)JAQiw(p0}Jl4>vb zrjPSNrmHL8J<1s3y$?chPmw0+xmG^ZuhW6#HyoE?Uo1P7_zVrUsV?#WZf+`Jz29Ix|s zvIlrPjuc&phe^9rc|(s0e5$m;P#U>@D#qlDzl$8#|Jb)Mk4W>Lgis5=m`i!imoc;Hq@m^jk;dE!_f3^qwC|9Zp*y0BLFv(SMplIme9NQ_@wiqwe-@)@?? ziE24q@exco4!AmqiIh}gV!VQ}6LXv*bw@7SyORkYXv0XKtdG`xoZtCq;-QxSy@Z^> z^G7&OgsQq6JhB_C%N1}O-d$+VqhaQWyR<_`YRb1^K8_ThmOqQN#9)-)?e~ZpnH8iT zrKO$uZW9}5HzkE7-Us6;Y{I2=cIUmdUq>^nJJ(7Kbf!XnihCpm^6&!_Mx5S=Qz}f? z5?T^heZxV8h9dC{yjGBBYb{K}Lv#Io?sH;UwSR#?KTHLNClu8TVQ|Y0{W)O;9>il~ z|~RZ$%O?ZqYZdcL1M?(ojFBdSaNCz$*X<*13s`34)l$Qoi#PyUFlSGS@8t z-$l!=6Kzs1nugt+mhaj1Y6w$QGTR(74O5iM$|}T@x1@<3QTxPm%hO?6sZm6$H)ErA zjzEkLtnIQxQC>j+?qFCFvmIWQTmM;n`Au(sSKEr??&3yKi|ydRmI*2(d`qWp#>`7$v7)$T2}z#Et^{(3I_dQ>v;d?sO2Nk;?IsoZF$>UDXc zu<6R75sp^^WttE=f> zndCL$T89`}My3IwL>meuuuEc7Aq5@K^XIQ3_lV3-;{%vl;CTbac!QIyclyX1{Z5X2P%U!y`X%(H z#nEHb+Qfgun|sSKO$+c zSu9^wC&IAy)@cQ#<*E7AF}KSxlsvZ%T_$Q}KVQn61`(cMR9QvV7pembUO{fUU81{q zIKJ_!sx70w_XteZ5z)|T>NaBpc|uQ9TX|$f)j;B4)SP61chNM^wMD18q!d^m2noWJ z6`4i3fqgM&tbr*i596xE7=eY{#ow;9|K=4SO0eOJ zck?rX+h2vLId$eycS`cf&XuC;w{QNIr-1j=G%~O5&p^-nWxmI;BL!X$%{+grP6+Tf z5C)%GZzeUq;Y)*7->vM|-I3!2T9m4>U`ba1j_^1wd(zw2vq&=jXdzJMnbqq%U=YD0 zFV%}f#+2Y_uV&%(KDltxt1ZKm%dms7yI6%ajF;cJsOM4Ob3H~1^x5)DAe)}3_jRAv z^%x)kxezw^pDaaS3EuP-a%F4ZQi=jP#py5}+ z!+293+W&@!_ts)AK{#$+R=;crd7k$g=m4$Mb{Sy&OL41mJPdfk7J_kAb*TtaB5i>CGIU0g!m;X0B3JiTf?SWBhNk!E0|db7K( z1sZ&aZr`8xngQ+B@}B33gljh*CLC)Pj#75yYyPhEC?pejHZ)IdlzBJ8w`2rcb-Vp&JLbrYsk+A z44(mSBP2jc{$=dP(hjz%x;hn(n~4GV4-}MG#^@I*s5=6KqCl)7XC^eS+aU&m_uKLh zv)Sn*_FY#u>l`~%*8#+?M~zxE&Unv<@qM$?7XZh=s|u^*nk3H!T^hVe-1NU1{`gBb z2vw*+FI{TRhd019o7^QK+>%aAMPg1&^V7$;Jqk(00_zoz1yOf?0pVzxpla?J#}jy@ zMCOml=T2tTl5}0ObpB_c=4R%%ypKT1_wtv=osl0V&o|s$(7PQucC<%$KFIE#8dKEa z0iQH?alsNTh#8j^`yOjepPO%z({~I{wu@h}7?~KGE1G+1o{wf)JP;>&b?J)va~(ba zVLYw3rJWA5u~Pg8!Kie#xLuJk_& zrSdf}1X^5?!8jj;csE_jgI99%@we2@Zh0eZCtHAf)YgjXVQ|h-8=B|^p%#VbPuSLs zl(p|YO(ohMQ~89b+f?sIwpU;&mDf#pc4w-P%zP7nxCyoZSn#l2zma2+g|}N!!w+7^ zjO>t1ton$f_j4>Bb5O~=-2_2z%l6*j#F$c*>3l0k#GkRUo%!n)W|q4Np!Ea^p|ZMz z-93QLA9m0T{+Vl$6tpKIPLBoV8U*;1zz2zyd*I1^`~YuiavIz_oHWqsN5$|tArQwc zPvjF*5{R1Xblr!I>=007ZCpx@@&?U3eL~D{Fla|axvakf(=RYl3#A|G@h~EW&ims7 ziwI6bOBb| zrD>x#OaVEOCM_lJ-5K(*hV;++;Ez#kSpULTRSsTa!b4Fj^J=}dRf%hW)Ee)R#jB-h zbQUD93Pp-x_(1giC`})`HB6aiuLRcM?t9|oCbx6FqyT*D?XBBm0+=a=5MfgC$St%( zoLoCox@YQmI`rc=&X+T_Cc-&JlmlXW&qyp79sM(ev=Ucu$JXhJUf;o}#-fJw7v_M~{VrJ%qX)M=Afk^3Lra zkGJ@9!p^eFePeR$|GH4E&m~e2f7kL*n1NiRfO>{z!FuH{WKHg3yx?p5lc$$|joCA- z8~-(YwEiH)@BvVvF)&)mpLT3P!GH+8k|{A?j=ssly4#US;k0~Qg0&$vw)mGM)fP@7 zKxK@42O;ZAX@x%zFtDg(v8BC@$8RkxERFcqg7sEYxcCK5%yHVy`rlgGo0Pqn9y_CzVU2Qby zUl!%kyOP)n|9h{p)fYi}=NS7Yv>LrmV_oKouCpJsuCXw=YgcJ6FOq_KecucR^P|&aB ztjDT6pgO}y&Sg}Nc7uV$q#o1aKfZl?YRc1dn@xZw!3f|J+^m=Qv|L2=jay0@wvY4C z42uBGED-s5_P7eo+W$z_)lyGX26PJU9Lw&4ngAUmW`yi+gkCuS=59jsP2UKtAR{}I z^5gTC^DC=7+>$A+Ph2Oqq(r~}O+5r&%M3MxM0^!p?VXqDDZKNT=h`k2rqTCA%&*!d z1eg9aEn?bId37V?81VMJ&O_68Gq_z_M*ufz%b|UwUuBE z+!!C`{;2r~s1>&JLG&yJ$i*u%3dAcr20)g%sMhG@u}JhPRAe7zmFik5wd}a;KN~8H z?S^=zDggj08$!l3tW}$))Pb^4f2+>%M~-zUqlM*V;Lwyg!5tBn#I0&t`(%lv0vUN> zJvEuv{^S*Mvc=B^Qc^-aRt_re?{W^24_du`5XTafQscJok;)|qfakcvZ5c15L2yaN zT?GaC93J85Zni53wb-+suQY(>?H3*q9-C-muS0?4LJJOi!HR;3IuuI1Pu2Ylt@S^h z1KZYEZu#F^I||pWHr`-KulE)f2~UQ@-|?q}%7cJm6zdKCALe1cwG}9YOb2+FkBY&O ze1KC1qlO4DJ}^c^-vGcmv!>zT%cPxaf!*0cK->j?wOp1H!^j{j<4$oB@{WCW+I|yN zAt*rM4{yRfc$vNI&kb?^rr9*ro6{mXx0aaC0%tzkoKh z6wP5j3D;-&`mrN!3X7atSP zl04t^QDfoLOfd=9I+6BG(SU;kQ3hxxOpZgqkb`#ayDJoo%z}60-79yDRE#CSIusSc z!=|_I@`{B$xRnQx?k!j{VY34q69}YP?4Y>{knQB_Wq*?r*GvwhAoG_t-U(?#cQ1YH zA_d=AFP*;NHh^WpiEE^UGc}>ctf#+MRW9s%078*;&?;!gT?*+m8IjHiak+gJFsT4D z0c0L8@VIpE6)B()AS6HDo|_v|6sB!^C}nf271s~Z=smQtl~`v4dmS81;KM5ts9_9O|2wKO)PGiySvgPXp7>Mse=nxlL1)7Bnu&3)>fybL?TY-7!j3!;GX z>QnV%(guxI7_z3p5DM~RExrRqRkQmY7XK?hnE;G=#8*I&I0GnGU+;FWD|(^6A^Q_R zbjBmmH`33Y7$9%sK9_&Lw8fPmc}+mwp#J{N&v)TX0EkQajXDh<+2>M1Y><58OP_ei zNB^V^X%QnFYk{T^Zn(Hi`nHsV-~yoH-*ZwT-=q}SNAvBi+c%GHP)_0QP=xEe4D9;7 zU6y-NM8(ElT9EbA5W)kL@yAN6X(HsWmwP8017u{%^Ji)W3AyQZ={w{vVOw#pu%qD57*5%% zS0C?6!fQWvuTTRPFOL6hQ^g2iLTEK3nB--YDd@bx%|&G{ZpQ0dB6cT$_~`8! zSZQfL@PuNx$V3H@gYjI@sQK%GG3dxD;l5C3fBW={>m6uC`0yzkq6iFNJ^wr+86 zoXQK|gQ2pz{-4&qJF2N>T|aaSf{IF$CWsUT=^Yf5B2}t%q)HFHgir)Ul%jN`D$=A$ zCv>G7AataL8VoJ;(7D68$M0LWoORY+zuzBOyJXMo*|TTnec$JKX3wQ?btQ8U^mU1~ z+BQ%myn9{g2@s0|q?tZAbr z7dA8;ZLKs7_)%~K1t2?H%ICGxq>ns6n9gj+R^03r7{X1(# zdvF1W4iw_=wpe>*jM$B8!pH+16_C~bOT;&>W6BWKu>h4D6-zoAF=T>%t04`9VNjR{ zI+yaEUb(aL_X=fw>V$!;xtk$fWdPxJK}dmd@C1(T8Ilopm|5E|t3E9i1GH}CwS-wH zg0$G1j-G;Pp}Tt=;rWyJWc4S~1u-ePv9TCDOb=oYK(`&9O3BEC?(d8Fm0Lu~K=!68 zQvs}<_#y5)fZ&c6%7%a=aE2`VL%Yq_l4Rb+)#7&yZ;aavH3Xm8KzVpzKvF^T!e)SO za`Q|hM*Ml+`uw(b4D;l|A1weNkn>-A-{RYg?_*a8am&r7sMkf7cWuY;L)5KyPUKWN z73YGGi+~V;R%bK1X_z&_JQH+WeV$7L{1)KjCA&>m0`%)EB6^!107EwL&(Lcc!0lI` zUoh|7BP^mZg-oNMk{UYA-=~Q50UA8&QSkD-ZKvEx!5`-@gofdoOPGaP`c=;>W5@5( z37|vHX*4q*jA{Uldj=HO)*Rf^k}`Ft6Edh6W12V=2c>*RIn@pDI&YYA-}~|7p~8_l zC}OWQ8AmyyL3Z8ze(&hOfHSTIfRK$+?vx-9MViW($|+`iGG)hHL?h@!$6LM9;7Pz0 ziSH@$zavfrSl%DuLFHcfUXLICArMT)1A|(JJ407y%{3izeXcNkpz@PmKxh zMKrgpxnH7rvEnHTcsZ`mY9$KO*x!P7Uk&a8ybd=lLdlxnEo_EpUuJ;PdMYa{au!P% z@hlmr18BP9@3q*@8k)KGFX-NH+DT`xnmxcd_6)q}2?vaBMX|73dC^Af0?rv7HqJT< z`>U2kC9XoQnTL3cuGTd?U$#ZcaE!qT6#ibJt5066pXkr%a3)enkpT(*O8);U%J@HO zM*hvMeZP<~lL~Wly-e1;%4gS*q%}0-z9FxF+f+yps0RM>v{`tRq6xe|9I}(0Lc-~U zM6%c-x9{`N8DvFsUU~k7m?Tp1^yznY5WHM@&QF1el>NjkA{`Hc7~t$+^>p9IHql2( zcf`%LQ{XiS5>wJ#HPcyd_hi^Sc|;Smm56)l{Dkh0}@v82RZ2@Q(Ds2sknN#3A0Red6f=?^^=h+|J4XuUh(_h63BU)7~KMl(neB zFvB~Lt9^8oZB^F@zk}`jz!A!+0nN*EsM>t&b6&&}Blk-&3UkCI7}`5%!GwKoqycEH z7k0SmwZ^(PBg?CLmH(7WoQrTUvu$H$2XHeZC(ywB2_!GG+JX9^)E-*zxLEvrkZiqo zcHdi!u?dQpoH*P@L#vItP$((RY8O5Oeci5fFQ-p6nU{j)o6CPa@|U(CkT0YN%<*)b z*(%M{t~3l2A=w%qeb;d@ig0?M)Z9d-Iux$D6SA};Dd#NbcpTBC*H@k z8}C!~VBNR^xv9*PsHwtCvM^9XiQ?GUfhFjD0j`(avle@wE7OELA|&6($;+BCt20PH zsY13|Sh)VOvc1jM``|0zn}|!@b^Zl}ycv4=)l&D8UiMy|FrhR|zFVhn@(MXez|7Ly z#9-UWMo)zGMC_N>M8*ku+*Jk(JXF&L8$9;QNHbOEfJ~pRjYT2nSlTicd&(^}!SE!O z;-_icUgK*jZ|SPRjJL|VlB>IA>#?dY6;+~iUJ|-5X@dUts+i*X)^R@19WnkC5EW7a zW(+8bS{+2SyJEYv0hog-HbC`QT!-Af+c5`Rcw?}&F;w0vs@p&-Zw4$R_5P^89MW&C}xZ2csrCYCb z!~ulSuqFf;T@cpd3et3f#6k=~$6YRm9GM$Qn@rMn=*f$l%i+y&C)hpa10*gwn^D3!L9Zk;E3Wg(^SjF7 zb3^^E6b9pbc7sP>m=jX(71rsP2>q7u5hwcFPN%#W%&8MIDjJCQA>(YMiPJYsqDyW% zO_fg1A8_59|8>Pgs+vB~_)^7@&!TmkLhB|{KY>q(J;*bZ*RXJWVSbTb54fm%3kAq( zKDTfg=pwWhfiLsoD5-?%op|56^zg``ncAT+pRj3pN7K%6COs5iT8v1uqD2tfmjBk~ z!mzQ$FfJ({(oG-2+QbH=FJZld1t=q1aGGe4wb;YH@ys~oc-@6sw9nrmF?`ZMV zNKo(2UVJkt=-E&eVQex%~X0eG!LWI-}Zl_}EWMntvyEw3KQcMlB4wRc^ zVHXF>O0&~+LxZo=a?Xb&LeQl%qQPnXk!nRx-bmyxej2d_lp)o%>5JR)q{)iswxnzT z#0Z2QSut%-jm94U8I_1SnAa}G@%{`<=6Ik09M2JTEK?%fyOR(zZ&hM_`rPv-w+-;~ zCI$_wdhD`t%gIBLkAap4=%o(x?T$RW{tkuf8%%3~kl{vn=l3<}?1nGGevLff8#F`O zIaw⪚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 Date: Tue, 10 Oct 2023 14:33:32 +0100 Subject: [PATCH 15/16] Update vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts Co-authored-by: Cara <99646608+cfisher-scottlogic@users.noreply.github.com> --- .../src/layout-persistence/LayoutPersistenceManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts index c4f51d3ae..54fbf9d54 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LayoutPersistenceManager.ts @@ -52,7 +52,7 @@ export interface LayoutPersistenceManager { loadApplicationLayout: () => Promise; /** - * Saves the current layout which includes all layouts on screen + * Saves the application layout which includes all layouts on screen * * @param layout - Full JSON representation of the application layout to be saved */ From da096c4cf717cfe8c32c600f77eb7cba542d39a8 Mon Sep 17 00:00:00 2001 From: vferraro-scottlogic Date: Tue, 10 Oct 2023 14:35:30 +0100 Subject: [PATCH 16/16] VUU-47 remove unused import --- .../src/layout-persistence/LocalLayoutPersistenceManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts index 9d6884bcd..4a86b464b 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-persistence/LocalLayoutPersistenceManager.ts @@ -3,7 +3,7 @@ import { LayoutJSON, LayoutPersistenceManager } from "@finos/vuu-layout"; import { getLocalEntity, saveLocalEntity } from "@finos/vuu-filters"; import { getUniqueId } from "@finos/vuu-utils"; -import { defaultLayout, warningLayout } from "./data"; +import { defaultLayout } from "./data"; const metadataSaveLocation = "layouts/metadata"; const layoutsSaveLocation = "layouts/layouts";