From 8552e6e720b45743739f41d3b172448f94396926 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Wed, 2 Feb 2022 14:24:59 -0500 Subject: [PATCH 01/42] fixed black screen on reload bug --- src/app/components/MainSlider.tsx | 11 +++++++---- src/app/containers/MainContainer.tsx | 4 ++-- src/app/reducers/mainReducer.js | 4 ++-- src/app/store.tsx | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/app/components/MainSlider.tsx b/src/app/components/MainSlider.tsx index a2646b8e8..2a4994ca2 100644 --- a/src/app/components/MainSlider.tsx +++ b/src/app/components/MainSlider.tsx @@ -1,9 +1,8 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import Slider from 'rc-slider'; import Tooltip from 'rc-tooltip'; import { changeSlider, pause } from '../actions/actions'; import { useStoreContext } from '../store'; -import { useEffect } from 'react'; const { Handle } = Slider; @@ -42,8 +41,12 @@ function MainSlider(props: MainSliderProps) { const [sliderIndex, setSliderIndex] = useState(0); useEffect(() => { - setSliderIndex(currLocation.index); - }, [currLocation]) + if (currLocation) { + setSliderIndex(currLocation.index); + } else { + setSliderIndex(0); + } + }, [currLocation]); return (

- If you are using a React application, make sure tha you application is - running in development mode. + If you are using a React application, make sure that your application is + running in development mode. TEST
NOTE: The React Developer Tools extension is also required for Reactime to run, if you do not already have it installed on your diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index 1ea0c068b..565386bcf 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -84,7 +84,7 @@ export default (state, action) => produce(state, draft => { // eslint-disable-next-line max-len // finds the name by the newIndex parsing through the hierarchy to send to background.js the current name in the jump action const nameFromIndex = findName(newIndex, hierarchy); - + port.postMessage({ action: 'jumpToSnap', payload: snapshots[newIndex], @@ -151,7 +151,7 @@ export default (state, action) => produce(state, draft => { // finds the name by the action.payload parsing through the hierarchy to send to background.js the current name in the jump action const nameFromIndex = findName(action.payload, hierarchy); // nameFromIndex is a number based on which jump button is pushed - + port.postMessage({ action: 'jumpToSnap', payload: snapshots[action.payload], diff --git a/src/app/store.tsx b/src/app/store.tsx index 541f39610..0241cca82 100644 --- a/src/app/store.tsx +++ b/src/app/store.tsx @@ -3,4 +3,4 @@ import React, { useContext } from 'react'; export const StoreContext = React.createContext(); -export const useStoreContext:any = () => useContext(StoreContext); \ No newline at end of file +export const useStoreContext:any = () => useContext(StoreContext); From 2526d9b13a88a759d8251625658714bbdac246a4 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Wed, 2 Feb 2022 15:36:21 -0500 Subject: [PATCH 02/42] returned the React App not found to original wording to pass test --- src/app/containers/MainContainer.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index 9c7afe06c..09821be5f 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -18,6 +18,7 @@ const mixpanel = require('mixpanel').init('12fa2800ccbf44a5c36c37bc9776e4c0', { debug: false, protocol: 'https', }); + function MainContainer(): any { const [store, dispatch] = useStoreContext(); const { tabs, currentTab, port: currentPort } = store; @@ -134,12 +135,9 @@ function MainContainer(): any { No React application found. Please visit reactime.io to more info.

- If you are using a React application, make sure that your application is - running in development mode. TEST + If you are using a React application, make sure tha you application is running in development mode.
- NOTE: The React Developer Tools extension is also required for - Reactime to run, if you do not already have it installed on your - browser. + NOTE: The React Developer Tools extension is also required for Reactime to run, if you do not already have it installed on your browser.

); From eb93ef2ef94a99f40974405dfab31f7075af30ed Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Fri, 4 Feb 2022 21:59:24 -0500 Subject: [PATCH 03/42] done for night post module --- src/app/containers/MainContainer.tsx | 8 ++++++-- src/backend/linkFiber.ts | 14 +++++++++----- src/extension/background.js | 10 ++++++---- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index 09821be5f..ce1c64656 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -33,6 +33,7 @@ function MainContainer(): any { useEffect(() => { // only open port once if (currentPort) return; + // open long-lived connection with background script const port = chrome.runtime.connect(); @@ -43,6 +44,8 @@ function MainContainer(): any { payload: Record; sourceTab: number; }) => { + console.log('message', message); + const { action, payload, sourceTab } = message; let maxTab; if (!sourceTab) { @@ -69,7 +72,7 @@ function MainContainer(): any { dispatch(initialConnect(payload)); break; } - case 'setCurrentLocation': { + case ' ': { dispatch(setCurrentLocation(payload)); break; } @@ -126,7 +129,7 @@ function MainContainer(): any { if (!tabs[currentTab]) { return (
- + Reactime Logo ); } + const { currLocation, viewIndex, sliderIndex, snapshots, hierarchy, webMetrics, } = tabs[currentTab]; diff --git a/src/backend/linkFiber.ts b/src/backend/linkFiber.ts index a9f3882e4..63511028b 100644 --- a/src/backend/linkFiber.ts +++ b/src/backend/linkFiber.ts @@ -12,6 +12,7 @@ /* eslint-disable no-param-reassign */ // import typescript types +import { element } from 'prop-types'; import { // tree Snapshot, @@ -32,7 +33,6 @@ import componentActionsRecord from './masterState'; // getHooksNames - helper function to grab the getters/setters from `elementType` import { throttle, getHooksNames } from './helpers'; import AtomsRelationship from '../app/components/AtomsRelationship'; -import { element } from 'prop-types'; // Set global variables to use in exported module and helper functions declare global { @@ -211,8 +211,8 @@ let atomsComponents = {}; const exclude = ['alternate', '_owner', '_store', 'get key', 'ref', '_self', '_source', 'firstBaseUpdate', 'updateQueue', 'lastBaseUpdate', 'shared', 'responders', 'pending', 'lanes', 'childLanes', 'effects', 'memoizedState', 'pendingProps', 'lastEffect', 'firstEffect', 'tag', 'baseState', 'baseQueue', 'dependencies', 'Consumer', 'context', '_currentRenderer', '_currentRenderer2', 'mode', 'flags', 'nextEffect', 'sibling', 'create', 'deps', 'next', 'destroy', 'parentSub', 'child', 'key', 'return', 'children', '$$typeof', '_threadCount', '_calculateChangedBits', '_currentValue', '_currentValue2', 'Provider', '_context', 'stateNode', 'elementType', 'type']; // This recursive function is used to grab the state of children components -// and push them into the parent componenent -// react elements throw errors on client side of application - convert react/functions into string +// and push them into the parent componenent +// react elements throw errors on client side of application - convert react/functions into string function convertDataToString(newObj, oldObj) { const newPropData = oldObj || {}; // const newPropData = Array.isArray(obj) === true ? {} : []; @@ -332,7 +332,7 @@ function createTree( } = {}; let componentFound = false; - // check to see if the parent component has any state/props + // check to see if the parent component has any state/props if (memoizedProps) { componentData.props = convertDataToString(memoizedProps, null); } @@ -394,7 +394,7 @@ function createTree( // which includes the dispatch() function we use to change their state. const hooksStates = traverseHooks(memoizedState); const hooksNames = getHooksNames(elementType.toString()); - + hooksStates.forEach((state, i) => { hooksIndex = componentActionsRecord.saveNew( state.state, @@ -537,6 +537,10 @@ export default (snap: Snapshot, mode: Mode): (() => void) => { return () => { // react devtools global hook is a global object that was injected by the React Devtools content script, allows access to fiber nodes and react version const devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; + + console.log(window); + console.log(devTools); + const reactInstance = devTools ? devTools.renderers.get(1) : null; // reactInstance returns an object of the react fiberRoot = devTools.getFiberRoots(1).values().next().value; diff --git a/src/extension/background.js b/src/extension/background.js index b79abb7d3..b47373d0f 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -49,7 +49,6 @@ function createTabObj(title) { // Each node stores a history of the link fiber tree. class Node { constructor(obj, tabObj) { - // continues the order of number of total state changes this.index = tabObj.index++; // continues the order of number of states changed from that parent @@ -120,6 +119,7 @@ function changeCurrLocation(tabObj, rootNode, index, name) { // Establishing incoming connection with devtools. chrome.runtime.onConnect.addListener(port => { + console.log('event listener triggered, devtools botted and running!'); // port is one end of the connection - an object // push every port connected to the ports array @@ -146,6 +146,7 @@ chrome.runtime.onConnect.addListener(port => { // listen for message containing a snapshot from devtools and send it to contentScript - // (i.e. they're all related to the button actions on Reactime) port.onMessage.addListener(msg => { + console.log('message from devtools received', msg); // msg is action denoting a time jump in devtools // --------------------------------------------------------------- // message incoming from devTools should look like this: @@ -209,6 +210,8 @@ chrome.runtime.onConnect.addListener(port => { // background.js listening for a message from contentScript.js chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + console.log('recevied a message from contentScript', request); + console.log('heres the sender of that message', sender); // IGNORE THE AUTOMATIC MESSAGE SENT BY CHROME WHEN CONTENT SCRIPT IS FIRST LOADED if (request.type === 'SIGN_CONNECT') { return true; @@ -258,7 +261,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { case 'injectScript': { chrome.tabs.executeScript(tabId, { code: ` - // Function will attach script to the dom + // Function will attach script to the dom const injectScript = (file, tag) => { const htmlBody = document.getElementsByTagName(tag)[0]; const script = document.createElement('script'); @@ -334,7 +337,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { break; } - // DUPLICATE SNAPSHOT CHECK const previousSnap = tabsObj[tabId].currLocation.stateSnapshot.children[0].componentData .actualDuration; @@ -440,7 +442,7 @@ chrome.contextMenus.onClicked.addListener(({ menuItemId }) => { type: 'panel', left: 0, top: 0, - width: 380, + width: 1000, height: window.screen.availHeight, url: chrome.runtime.getURL('panel.html'), }; From 12df2ef89e3fcb137136ced1c818da5012271a1b Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Mon, 7 Feb 2022 11:14:14 -0500 Subject: [PATCH 04/42] small maifest change --- src/extension/build/manifest.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/extension/build/manifest.json b/src/extension/build/manifest.json index 3d67cffaa..ddcfe3758 100644 --- a/src/extension/build/manifest.json +++ b/src/extension/build/manifest.json @@ -19,7 +19,8 @@ { "matches": [ "http://localhost/*", - "https://localhost/*" + "https://localhost/*", + "https://reactjs.org/*" ], "js": [ "bundles/content.bundle.js" @@ -36,4 +37,4 @@ "http://localhost/*", "https://localhost/*" ] -} \ No newline at end of file +} From 14bbff3a53cb6319a14525ec4300e403aba9b201 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Mon, 7 Feb 2022 17:54:29 -0500 Subject: [PATCH 05/42] made right click popout opening compatible with V3 (#3) --- src/extension/background.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/extension/background.js b/src/extension/background.js index b79abb7d3..914bd6c25 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -258,7 +258,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { case 'injectScript': { chrome.tabs.executeScript(tabId, { code: ` - // Function will attach script to the dom + // Function will attach script to the dom const injectScript = (file, tag) => { const htmlBody = document.getElementsByTagName(tag)[0]; const script = document.createElement('script'); @@ -440,8 +440,8 @@ chrome.contextMenus.onClicked.addListener(({ menuItemId }) => { type: 'panel', left: 0, top: 0, - width: 380, - height: window.screen.availHeight, + width: 1000, + height: 1000, url: chrome.runtime.getURL('panel.html'), }; if (menuItemId === 'reactime') chrome.windows.create(options); From e4d5d99d55b39d4e4bb4dba2c6439cac77b0eadc Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Tue, 8 Feb 2022 00:08:11 -0500 Subject: [PATCH 06/42] mostly done with dev tool checker --- src/app/actions/actions.ts | 5 ++++ src/app/constants/actionTypes.ts | 3 ++- src/app/containers/MainContainer.tsx | 7 +++++- src/app/reducers/mainReducer.js | 7 ++++++ src/backend/linkFiber.ts | 37 +++++++++++++++++++--------- src/extension/background.js | 17 +++++++++++-- src/extension/contentScript.ts | 7 +++++- 7 files changed, 66 insertions(+), 17 deletions(-) diff --git a/src/app/actions/actions.ts b/src/app/actions/actions.ts index 9c81fcc33..e0eee16dc 100644 --- a/src/app/actions/actions.ts +++ b/src/app/actions/actions.ts @@ -81,6 +81,11 @@ export const deleteTab = (tab) => ({ payload: tab, }); +export const noDev = (tab) => ({ + type: types.NO_DEV, + payload: tab, +}); + export const resetSlider = () => ({ type: types.SLIDER_ZERO, }); diff --git a/src/app/constants/actionTypes.ts b/src/app/constants/actionTypes.ts index e4612921d..6672c764a 100644 --- a/src/app/constants/actionTypes.ts +++ b/src/app/constants/actionTypes.ts @@ -12,9 +12,10 @@ export const INITIAL_CONNECT = 'INITIAL_CONNECT'; export const NEW_SNAPSHOTS = 'NEW_SNAPSHOTS'; export const SET_TAB = 'SET_TAB'; export const DELETE_TAB = 'DELETE_TAB'; +export const NO_DEV = 'NO_DEV'; export const SLIDER_ZERO = 'SLIDER_ZERO'; export const ON_HOVER = 'ON_HOVER'; export const ON_HOVER_EXIT = 'ON_HOVER_EXIT'; export const SAVE = 'SAVE'; export const DELETE_SERIES = 'DELETE_SERIES'; -export const SET_CURRENT_LOCATION = 'SET_CURRENT_LOCATION'; \ No newline at end of file +export const SET_CURRENT_LOCATION = 'SET_CURRENT_LOCATION'; diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index ce1c64656..9135e81d5 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -9,6 +9,7 @@ import { setPort, setTab, deleteTab, + noDev, setCurrentLocation, } from '../actions/actions'; import { useStoreContext } from '../store'; @@ -57,6 +58,10 @@ function MainContainer(): any { dispatch(deleteTab(payload)); break; } + case 'noDevTools': { + dispatch(noDev(payload)); + break; + } case 'changeTab': { dispatch(setTab(payload)); break; @@ -126,7 +131,7 @@ function MainContainer(): any { document.addEventListener('click', mpClickTrack); }, []); - if (!tabs[currentTab]) { + if (!tabs[currentTab] || tabs[currentTab].reactDevToolsInstalled === false) { return (
Reactime Logo diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index 565386bcf..dfd9061be 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -301,6 +301,13 @@ export default (state, action) => produce(state, draft => { } break; } + case types.NO_DEV: { + console.log('made it into the reducers'); + const { payload } = action; + const { reactDevToolsInstalled } = payload[currentTab]; + tabs[currentTab].reactDevToolsInstalled = reactDevToolsInstalled; + break; + } case types.SET_CURRENT_LOCATION: { const { payload } = action; const { currLocation } = payload[currentTab]; diff --git a/src/backend/linkFiber.ts b/src/backend/linkFiber.ts index 63511028b..8f1a92b0d 100644 --- a/src/backend/linkFiber.ts +++ b/src/backend/linkFiber.ts @@ -137,6 +137,17 @@ function updateSnapShotTree(snap: Snapshot, mode: Mode): void { sendSnapshot(snap, mode); } +// updating tree depending on current mode on the panel (pause, etc) +// function sendDevToolsInfo(snap: Snapshot, mode: Mode): void { +// window.postMessage( +// { +// action: 'recordSnap', +// payload, +// }, +// '*' +// ); +// } + /** * @method traverseRecoilHooks * @param memoizedState Property containing state on a stateful fctnl component's FiberNode object @@ -537,22 +548,17 @@ export default (snap: Snapshot, mode: Mode): (() => void) => { return () => { // react devtools global hook is a global object that was injected by the React Devtools content script, allows access to fiber nodes and react version const devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; - - console.log(window); - console.log(devTools); - const reactInstance = devTools ? devTools.renderers.get(1) : null; // reactInstance returns an object of the react - fiberRoot = devTools.getFiberRoots(1).values().next().value; - const throttledUpdateSnapshot = throttle( - () => { - updateSnapShotTree(snap, mode); - }, - 70 - ); + // fiberRoot = devTools.getFiberRoots(1).values().next().value; + + const throttledUpdateSnapshot = throttle(() => { updateSnapShotTree(snap, mode); }, 70); + // const throttledDevToolChecker = throttle(() => { sendDevToolsInfo(snap, mode); }, 70); + document.addEventListener('visibilitychange', onVisibilityChange); if (reactInstance && reactInstance.version) { + fiberRoot = devTools.getFiberRoots(1).values().next().value; devTools.onCommitFiberRoot = (function (original) { return function (...args) { // eslint-disable-next-line prefer-destructuring @@ -563,7 +569,14 @@ export default (snap: Snapshot, mode: Mode): (() => void) => { return original(...args); }; }(devTools.onCommitFiberRoot)); + } else { + window.postMessage( + { + action: 'noDevToolsInstalled', + payload: 'noDevToolsInstalled' + }, + '*' + ); } - throttledUpdateSnapshot(); }; }; diff --git a/src/extension/background.js b/src/extension/background.js index b47373d0f..215f3cd2f 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -12,7 +12,7 @@ const metrics = {}; // This function will create the first instance of the test app's tabs object // which will hold test app's snapshots, link fiber tree info, chrome tab info, etc. -function createTabObj(title) { +function createTabObj(title, tabId) { // TO-DO for save button // Save State // Knowledge of the comparison snapshot @@ -36,6 +36,8 @@ function createTabObj(title) { hierarchy: null, // records initial hierarchy to refresh page in case empty function is called initialHierarchy: null, + // checks for dev tools + reactDevToolsInstalled: true, mode: { persist: false, paused: false, @@ -211,7 +213,6 @@ chrome.runtime.onConnect.addListener(port => { // background.js listening for a message from contentScript.js chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.log('recevied a message from contentScript', request); - console.log('heres the sender of that message', sender); // IGNORE THE AUTOMATIC MESSAGE SENT BY CHROME WHEN CONTENT SCRIPT IS FIRST LOADED if (request.type === 'SIGN_CONNECT') { return true; @@ -233,6 +234,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { || action === 'recordSnap' || action === 'jumpToSnap' || action === 'injectScript' + || action === 'noDevToolsInstalled' ) { isReactTimeTravel = true; } else { @@ -241,6 +243,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { // everytime we get a new tabid, add it to the object if (isReactTimeTravel && !(tabId in tabsObj)) { + console.log('creatingTabObj: action = ', action); tabsObj[tabId] = createTabObj(tabTitle); } @@ -256,6 +259,16 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { } break; } + case 'noDevToolsInstalled': { + console.log('no devTools installed: in background script. Setting to false'); + tabsObj[tabId].reactDevToolsInstalled = false; + // now that we have confirmed no devTools installed, we can go ahead and send this info to frontend + portsArr.forEach(bg => bg.postMessage({ + action: 'noDevTools', + payload: tabsObj, + })); + break; + } // This injects a script into the app that you're testing Reactime on, // so that Reactime's backend files can communicate with the app's DOM. case 'injectScript': { diff --git a/src/extension/contentScript.ts b/src/extension/contentScript.ts index 71ab321e3..ce9e2d9bc 100644 --- a/src/extension/contentScript.ts +++ b/src/extension/contentScript.ts @@ -27,11 +27,16 @@ window.addEventListener('message', msg => { if (action === 'recordSnap') { chrome.runtime.sendMessage(msg.data); } + if (action === 'noDevToolsInstalled') { + console.log('noDevToolsInstalled in content script', msg.data); + chrome.runtime.sendMessage(msg.data); + } }); // Listening for messages from the UI of the Reactime extension. chrome.runtime.onMessage.addListener(request => { - const { action }: { action: string } = request; + const { action }: { action: string; } = request; + console.log('content Script: from background script', request); // this is only listening for Jump toSnap if (action) { if (action === 'jumpToSnap') { From 2379b21d9ce499d0662958b2663aff5622da47ff Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Tue, 8 Feb 2022 00:22:30 -0500 Subject: [PATCH 07/42] nearly done with react dev tools checker --- src/app/containers/MainContainer.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index 9135e81d5..bffdad28b 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -131,6 +131,14 @@ function MainContainer(): any { document.addEventListener('click', mpClickTrack); }, []); + function mynewFunc(arg: any) { + if (arg !== undefined && arg.reactDevToolsInstalled === false) { + return ( +

React Dev Tools not installed!

+ ); + } + } + if (!tabs[currentTab] || tabs[currentTab].reactDevToolsInstalled === false) { return (
@@ -147,6 +155,9 @@ function MainContainer(): any {
NOTE: The React Developer Tools extension is also required for Reactime to run, if you do not already have it installed on your browser.

+
+ {mynewFunc(tabs[currentTab]) } +
); } From 132b2d39cd16de959ccd4c7a45d71400bf9b6488 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Wed, 9 Feb 2022 17:15:17 -0500 Subject: [PATCH 08/42] foo --- src/app/containers/MainContainer.tsx | 1 + src/backend/linkFiber.ts | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index bffdad28b..ee6bef980 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -137,6 +137,7 @@ function MainContainer(): any {

React Dev Tools not installed!

); } + return

React Dev Tools found

; } if (!tabs[currentTab] || tabs[currentTab].reactDevToolsInstalled === false) { diff --git a/src/backend/linkFiber.ts b/src/backend/linkFiber.ts index 8f1a92b0d..f7ab8aa10 100644 --- a/src/backend/linkFiber.ts +++ b/src/backend/linkFiber.ts @@ -548,12 +548,18 @@ export default (snap: Snapshot, mode: Mode): (() => void) => { return () => { // react devtools global hook is a global object that was injected by the React Devtools content script, allows access to fiber nodes and react version const devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; - const reactInstance = devTools ? devTools.renderers.get(1) : null; + // check if reactDev Tools is installed + if (devTools) { + window.postMessage({ + action: 'noDevToolsInstalled', + payload: 'noDevToolsInstalled' + }, + '*'); + return; + } // reactInstance returns an object of the react - // fiberRoot = devTools.getFiberRoots(1).values().next().value; - + const reactInstance = devTools ? devTools.renderers.get(1) : null; const throttledUpdateSnapshot = throttle(() => { updateSnapShotTree(snap, mode); }, 70); - // const throttledDevToolChecker = throttle(() => { sendDevToolsInfo(snap, mode); }, 70); document.addEventListener('visibilitychange', onVisibilityChange); @@ -563,20 +569,14 @@ export default (snap: Snapshot, mode: Mode): (() => void) => { return function (...args) { // eslint-disable-next-line prefer-destructuring fiberRoot = args[1]; + console.log('in the call back and dowork = ', doWork); if (doWork) { throttledUpdateSnapshot(); } return original(...args); }; }(devTools.onCommitFiberRoot)); - } else { - window.postMessage( - { - action: 'noDevToolsInstalled', - payload: 'noDevToolsInstalled' - }, - '*' - ); + throttledUpdateSnapshot(); // only runs on start up } }; }; From 403db965b03daca7536e02f82acacd3329718e0a Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Sat, 12 Feb 2022 16:54:00 -0500 Subject: [PATCH 09/42] checkout main --- src/app/containers/StateContainer.tsx | 1 - src/backend/linkFiber.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/containers/StateContainer.tsx b/src/app/containers/StateContainer.tsx index 2b630e734..d55e5600b 100644 --- a/src/app/containers/StateContainer.tsx +++ b/src/app/containers/StateContainer.tsx @@ -41,7 +41,6 @@ const StateContainer = (props: StateContainerProps): JSX.Element => { viewIndex, webMetrics, currLocation, - // added snapshots, Rob 11/4 snapshots, } = props; diff --git a/src/backend/linkFiber.ts b/src/backend/linkFiber.ts index f7ab8aa10..f882e4e61 100644 --- a/src/backend/linkFiber.ts +++ b/src/backend/linkFiber.ts @@ -549,7 +549,7 @@ export default (snap: Snapshot, mode: Mode): (() => void) => { // react devtools global hook is a global object that was injected by the React Devtools content script, allows access to fiber nodes and react version const devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; // check if reactDev Tools is installed - if (devTools) { + if (!devTools) { window.postMessage({ action: 'noDevToolsInstalled', payload: 'noDevToolsInstalled' @@ -569,7 +569,6 @@ export default (snap: Snapshot, mode: Mode): (() => void) => { return function (...args) { // eslint-disable-next-line prefer-destructuring fiberRoot = args[1]; - console.log('in the call back and dowork = ', doWork); if (doWork) { throttledUpdateSnapshot(); } From 18688572b3dd950505518b848512e932f01b6876 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Sat, 12 Feb 2022 18:54:53 -0500 Subject: [PATCH 10/42] fixed self introduced slider error --- src/app/containers/MainContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index ee6bef980..0f1266f4a 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -77,7 +77,7 @@ function MainContainer(): any { dispatch(initialConnect(payload)); break; } - case ' ': { + case 'setCurrentLocation': { dispatch(setCurrentLocation(payload)); break; } From 892c00adc9b0ad83fd2750fd7f81780c3df70912 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Sun, 13 Feb 2022 00:32:35 -0500 Subject: [PATCH 11/42] third done with error page and added lock button functionality --- src/app/containers/ActionContainer.tsx | 8 ++-- src/app/containers/ErrorContainer.tsx | 54 ++++++++++++++++++++++ src/app/containers/MainContainer.tsx | 33 ++----------- src/app/reducers/mainReducer.js | 16 ++++--- src/app/styles/layout/_errorContainer.scss | 10 +++- src/extension/background.js | 4 +- 6 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 src/app/containers/ErrorContainer.tsx diff --git a/src/app/containers/ActionContainer.tsx b/src/app/containers/ActionContainer.tsx index 34b669aa0..c52bf1652 100644 --- a/src/app/containers/ActionContainer.tsx +++ b/src/app/containers/ActionContainer.tsx @@ -26,7 +26,7 @@ function ActionContainer(props) { function findDiff(index) { - const statelessCleanning = (obj: { + const statelessCleaning = (obj: { name?: string; componentData?: object; state?: string | any; @@ -44,7 +44,7 @@ function ActionContainer(props) { delete newObj.state; } if (newObj.stateSnaphot) { - newObj.stateSnaphot = statelessCleanning(obj.stateSnaphot); + newObj.stateSnaphot = statelessCleaning(obj.stateSnaphot); } if (newObj.children) { newObj.children = []; @@ -55,7 +55,7 @@ function ActionContainer(props) { element.state !== 'stateless' || element.children.length > 0 ) { - const clean = statelessCleanning(element); + const clean = statelessCleaning(element); newObj.children.push(clean); } }, @@ -66,7 +66,7 @@ function ActionContainer(props) { return newObj; }; // displays stateful data - const previousDisplay = statelessCleanning(snapshots[index - 1]); + const previousDisplay = statelessCleaning(snapshots[index - 1]); const delta = diff(previousDisplay, snapshots[index]); const changedState = findStateChangeObj(delta); const html = formatters.html.format(changedState[0]); diff --git a/src/app/containers/ErrorContainer.tsx b/src/app/containers/ErrorContainer.tsx new file mode 100644 index 000000000..971685966 --- /dev/null +++ b/src/app/containers/ErrorContainer.tsx @@ -0,0 +1,54 @@ +import React, { useEffect, useState } from 'react'; +import { useStoreContext } from '../store'; + +function ErrorContainer(props): any { + const [store, dispatch] = useStoreContext(); + const { tabs, currentTab } = store; + + function contentScriptCheck() { + // if (arg !== undefined && arg.reactDevToolsInstalled === false) { + // return ( + //

React Dev Tools not installed!

+ // ); + // } + // return

React Dev Tools found

; + } + console.log(currentTab); + console.log(tabs[currentTab]); + + + return ( +
+ Reactime Logo + +

+ Launching Reactime on tab: + {' '} + {currentTab} +

+ +
+

Checking if a content script has been launched on this tab

+

Checking if React Dev Tools has been installed

+

Checking if this is React app

+
+
+ No React application found. Please visit reactime.io to more info. + +

+ If you are using a React application, make sure tha you application is running in development mode. +
+ NOTE: The React Developer Tools extension is also required for Reactime to run, if you do not already have it installed on your browser. +

+ {/*
+ {mynewFunc() } +
*/} +
+ ); +} + +export default ErrorContainer; diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index 0f1266f4a..5815ab570 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -3,6 +3,7 @@ import ActionContainer from './ActionContainer'; import StateContainer from './StateContainer'; import TravelContainer from './TravelContainer'; import ButtonsContainer from './ButtonsContainer'; +import ErrorContainer from './ErrorContainer'; import { addNewSnapshots, initialConnect, @@ -131,35 +132,11 @@ function MainContainer(): any { document.addEventListener('click', mpClickTrack); }, []); - function mynewFunc(arg: any) { - if (arg !== undefined && arg.reactDevToolsInstalled === false) { - return ( -

React Dev Tools not installed!

- ); - } - return

React Dev Tools found

; - } - - if (!tabs[currentTab] || tabs[currentTab].reactDevToolsInstalled === false) { + // error page if any of tabs check + // if (!tabs[currentTab]?.reactDevToolsInstalled) { + if (!tabs[currentTab] || !tabs[currentTab].reactDevToolsInstalled) { return ( -
- Reactime Logo - - No React application found. Please visit reactime.io to more info. - -

- If you are using a React application, make sure tha you application is running in development mode. -
- NOTE: The React Developer Tools extension is also required for Reactime to run, if you do not already have it installed on your browser. -

-
- {mynewFunc(tabs[currentTab]) } -
-
+ ); } diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index dfd9061be..a2a67c27c 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -282,12 +282,16 @@ export default (state, action) => produce(state, draft => { break; } case types.SET_TAB: { - if (typeof action.payload === 'number') { - draft.currentTab = action.payload; - break; - } else if (typeof action.payload === 'object') { - draft.currentTab = action.payload.tabId; - break; + // if lock + console.log(mode); + if (!mode?.paused) { + if (typeof action.payload === 'number') { + draft.currentTab = action.payload; + break; + } else if (typeof action.payload === 'object') { + draft.currentTab = action.payload.tabId; + break; + } } break; } diff --git a/src/app/styles/layout/_errorContainer.scss b/src/app/styles/layout/_errorContainer.scss index daed34889..b37ce45bc 100644 --- a/src/app/styles/layout/_errorContainer.scss +++ b/src/app/styles/layout/_errorContainer.scss @@ -13,13 +13,21 @@ align-items: center; a { color: white; - margin-top: 5%; + margin-top: 1%; height: 3%; } img { text-align: center; + padding-top: 5%; + padding-bottom: 1.5%; } p { text-align: center; } + .loaderChecks{ + padding: 1%; + border: 1px solid; + border-style: solid; + border-color: white; + } } diff --git a/src/extension/background.js b/src/extension/background.js index 215f3cd2f..2acf3d755 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -121,7 +121,8 @@ function changeCurrLocation(tabObj, rootNode, index, name) { // Establishing incoming connection with devtools. chrome.runtime.onConnect.addListener(port => { - console.log('event listener triggered, devtools botted and running!'); + console.log('established inccoming connection with devtools on port', port); + console.log('tabsObj', tabsObj); // port is one end of the connection - an object // push every port connected to the ports array @@ -429,6 +430,7 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { // when tab view is changed, put the tabid as the current tab chrome.tabs.onActivated.addListener(info => { + console.log(info); // tell devtools which tab to be the current if (portsArr.length > 0) { portsArr.forEach(bg => bg.postMessage({ From daec17bf55748010b8915e17b1cc296327228e06 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Sun, 13 Feb 2022 01:39:13 -0500 Subject: [PATCH 12/42] added all active tabInfo on every tab change. ToDo finish error page --- src/app/reducers/mainReducer.js | 2 -- src/extension/background.js | 22 +++++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index a2a67c27c..ab65afb42 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -282,8 +282,6 @@ export default (state, action) => produce(state, draft => { break; } case types.SET_TAB: { - // if lock - console.log(mode); if (!mode?.paused) { if (typeof action.payload === 'number') { draft.currentTab = action.payload; diff --git a/src/extension/background.js b/src/extension/background.js index 2acf3d755..9b5dab041 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -4,8 +4,10 @@ import 'core-js'; const portsArr = []; const reloaded = {}; const firstSnapshotReceived = {}; + // There will be the same number of objects in here as there are // Reactime tabs open for each user application being worked on. +let activeTab = {}; const tabsObj = {}; // Will store Chrome web vital metrics and their corresponding values. const metrics = {}; @@ -121,13 +123,20 @@ function changeCurrLocation(tabObj, rootNode, index, name) { // Establishing incoming connection with devtools. chrome.runtime.onConnect.addListener(port => { - console.log('established inccoming connection with devtools on port', port); - console.log('tabsObj', tabsObj); // port is one end of the connection - an object - // push every port connected to the ports array portsArr.push(port); + console.log('established connection with devtools on port', port); + console.log('activeTabid', activeTab); + + if (portsArr.length > 0) { + portsArr.forEach(bg => bg.postMessage({ + action: 'changeTab', + payload: activeTab.id, + })); + } + // send tabs obj to the connected devtools as soon as connection to devtools is made if (Object.keys(tabsObj).length > 0) { port.postMessage({ @@ -430,7 +439,10 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { // when tab view is changed, put the tabid as the current tab chrome.tabs.onActivated.addListener(info => { - console.log(info); + // save active tab for global use + chrome.tabs.query({ currentWindow: true, active: true }, tabs => { + [activeTab] = tabs; + }); // tell devtools which tab to be the current if (portsArr.length > 0) { portsArr.forEach(bg => bg.postMessage({ @@ -458,7 +470,7 @@ chrome.contextMenus.onClicked.addListener(({ menuItemId }) => { left: 0, top: 0, width: 1000, - height: window.screen.availHeight, + height: 1000, url: chrome.runtime.getURL('panel.html'), }; if (menuItemId === 'reactime') chrome.windows.create(options); From f7a578b52f94ba014b5f6ed46b7637e458336518 Mon Sep 17 00:00:00 2001 From: Brian Zheng Date: Mon, 14 Feb 2022 08:16:23 -0800 Subject: [PATCH 13/42] hello --- DeveloperREADME.md | 37 +++++++++++++++++++++++++ src/app/containers/ButtonsContainer.tsx | 1 + src/app/containers/MainContainer.tsx | 6 ++-- src/app/containers/TravelContainer.tsx | 1 + yarn.lock | 35 +++++++++++++++++++++++ 5 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 DeveloperREADME.md diff --git a/DeveloperREADME.md b/DeveloperREADME.md new file mode 100644 index 000000000..b4645ccd2 --- /dev/null +++ b/DeveloperREADME.md @@ -0,0 +1,37 @@ +
+

+Development Enviroment Setup +

+ +

+Getting Started +

+ +

+1. Download React Dev Tools from the Chrome Web Store. +Click Here To Download + + +
+2. Run the following command in your terminal. +

+ +````` +npm install --force +````` +

+3. If everything was installed without any errors, great! Move on to testing section to make sure everything is running. +

+
+ +

+Testing +

+

+1. Access your Chrome extensions by clicking the puzzle piece icon in your browser. Click on manage extensions on the bottom. From there change into Developer Mode on the top right. + +
+2. Click on "Load unpacked" on the left side of the screen. From there locate the Reactime file, and load src/extension/build. If loaded correctly you should now see Reactime as an extension. +

+ +
\ No newline at end of file diff --git a/src/app/containers/ButtonsContainer.tsx b/src/app/containers/ButtonsContainer.tsx index 7dd80ecc2..3afda27e1 100644 --- a/src/app/containers/ButtonsContainer.tsx +++ b/src/app/containers/ButtonsContainer.tsx @@ -88,6 +88,7 @@ function ButtonsContainer() { )} {persist ? 'Unpersist' : 'Persist'} +
diff --git a/src/app/components/ComponentMap.tsx b/src/app/components/ComponentMap.tsx index 9cfe6231d..edbf2bc7e 100644 --- a/src/app/components/ComponentMap.tsx +++ b/src/app/components/ComponentMap.tsx @@ -236,9 +236,16 @@ export default function ComponentMap({ - { + { setTooltip(false); - hideTooltip();}} width={totalWidth} height={totalHeight} rx={14} fill="#242529" /> + hideTooltip(); + }} + width={totalWidth} + height={totalHeight} + rx={14} + fill="#242529" + /> (d.isExpanded ? null : d.children))} @@ -284,7 +291,8 @@ export default function ComponentMap({ // mousing controls & Tooltip display logic const handleMouseAndClickOver = event => { - () => dispatch(onHover(node.data.rtid)); + // looks like onHover isnt working? + // () => dispatch(onHover(node.data.rtid)); const coords = localPoint( event.target.ownerSVGElement, event, @@ -327,30 +335,36 @@ export default function ComponentMap({ // strokeDasharray={node.children ? '0' : '2,2'} strokeOpacity="1" rx={node.children ? 4 : 10} - onDoubleClick={() => { + // double clicking the component expands or collapses the child components + // onDoubleClick={() => { + // node.data.isExpanded = !node.data.isExpanded; + // hideTooltip(); + // setTooltip(false); + // forceUpdate(); + // }} + // Tooltip event handlers + // test feature + // onClick = {handleMouseAndClickOver} + onClick={() => { node.data.isExpanded = !node.data.isExpanded; hideTooltip(); setTooltip(false); forceUpdate(); }} - // Tooltip event handlers - // test feature - // onClick = {handleMouseAndClickOver} - onClick={event => { - if (!tooltip) { - handleMouseAndClickOver(event); - setTooltip(true); - } - // if (tooltip) { // cohort 45 - // hideTooltip(); - // setTooltip(false); - // } else { - // handleMouseAndClickOver(event); - // setTooltip(true); - // } + + onMouseOver={event => { + setTooltip(true); + handleMouseAndClickOver(event); }} - onMouseEnter={() => dispatch(onHover(node.data.rtid))} - onMouseLeave={() => dispatch(onHoverExit(node.data.rtid))} + // paired with onmouseOver listener, this produces a hover over effect for the component Tooltip + onMouseOut={() => { + hideTooltip(); + setTooltip(false); + }} + + // onMouseEnter={() => dispatch(onHover(node.data.rtid))} + // onMouseLeave={() => dispatch(onHoverExit(node.data.rtid))} + // eslint-disable-next-line react/jsx-curly-newline /> )} {/* Display text inside of each component node */} From 7f761860fa8646f619f52b98a39c306a263f2318 Mon Sep 17 00:00:00 2001 From: Winslow Date: Thu, 17 Feb 2022 15:24:42 -0600 Subject: [PATCH 18/42] UI Hover Changes --- src/app/containers/ActionContainer.tsx | 2 +- src/extension/background.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/containers/ActionContainer.tsx b/src/app/containers/ActionContainer.tsx index 34b669aa0..f9978c121 100644 --- a/src/app/containers/ActionContainer.tsx +++ b/src/app/containers/ActionContainer.tsx @@ -92,7 +92,7 @@ function ActionContainer(props) { return changedState; } - // function to traverse state from hierarchy and also getting information on display name and component name + // function to traverse state from hierarchy and also getting information on display name and component name const displayArray = (obj: { stateSnapshot: { children: any[] }; name: number; diff --git a/src/extension/background.js b/src/extension/background.js index b79abb7d3..5e48edce7 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -49,7 +49,6 @@ function createTabObj(title) { // Each node stores a history of the link fiber tree. class Node { constructor(obj, tabObj) { - // continues the order of number of total state changes this.index = tabObj.index++; // continues the order of number of states changed from that parent @@ -334,7 +333,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { break; } - // DUPLICATE SNAPSHOT CHECK const previousSnap = tabsObj[tabId].currLocation.stateSnapshot.children[0].componentData .actualDuration; From 3a17da069d68af6b4098c7b1ec8539a7dcb2f400 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Fri, 18 Feb 2022 01:20:17 -0500 Subject: [PATCH 19/42] ending for the night --- src/app/components/ErrorMsg.tsx | 67 ++++++++++++++++++++++++ src/app/components/Loader.tsx | 2 +- src/app/containers/ErrorContainer.tsx | 74 +++++++++++++++++++-------- src/app/containers/MainContainer.tsx | 17 +++--- src/app/reducers/mainReducer.js | 12 ++--- src/backend/linkFiber.ts | 25 +++++---- src/extension/background.js | 58 +++++++++++++-------- src/extension/contentScript.ts | 7 +-- 8 files changed, 189 insertions(+), 73 deletions(-) create mode 100644 src/app/components/ErrorMsg.tsx diff --git a/src/app/components/ErrorMsg.tsx b/src/app/components/ErrorMsg.tsx new file mode 100644 index 000000000..d8cf09e36 --- /dev/null +++ b/src/app/components/ErrorMsg.tsx @@ -0,0 +1,67 @@ +/* eslint-disable react/prop-types */ +import React from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faCheck, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; + +// takes in the loadingArray and the status and returns the correct message +function parseError(loadingArray: [], status: any): string { + // console.log('inside func', status); + // console.log('inside func', loadingArray); + + let stillLoading = true; + loadingArray.forEach(e => { if (e === false) stillLoading = false; }); + + if (stillLoading) return 'Still Loading'; + // check for first in status that is not true + if (!status.contentScriptLaunched) return 'Content Script Error'; + if (!status.reactDevToolsInstalled) return 'RDT Error'; + if (!status.targetPageisaReactApp) return 'Not React App'; + return 'default'; +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +function ErrorMsg({ + loadingArray, status, +}): JSX.Element { + switch (parseError(loadingArray, status)) { + case 'Still Loading': + return ( +
+ Still waiting for checks to complete +
+ ); + case 'Content Script Error': + return ( +
+ Content Script was not launched. Trying reloading the page. +
+ NOTE: By default Reactime only works with urls starting with localhost. +
+ IF this is not the case you press the launch button to manually launch the content script. +
+ ); + case 'RDT Error': + return ( +
+ ); + case 'Not React App': + return ( +
+ The Target app is either not a React application or is not compatible with Reactime +
+ ); + default: + return null; + } +} + +export default ErrorMsg; diff --git a/src/app/components/Loader.tsx b/src/app/components/Loader.tsx index 19b710b49..40723c50b 100644 --- a/src/app/components/Loader.tsx +++ b/src/app/components/Loader.tsx @@ -22,7 +22,7 @@ const Loader = ({ loading, result, }): JSX.Element => (loading ? ( - set status + const status = { contentScriptLaunched: false, reactDevToolsInstalled: false, targetPageisaReactApp: false }; + if (tabs[currentTab]) { + Object.assign(status, tabs[currentTab].status); + } + + // console.log('in error container, contentScriptLaunched =', contentScriptLaunched); + // console.log('in error container, reactDevToolsInstalled =', reactDevToolsInstalled); + // console.log('in error container, targetPageisaReactApp =', targetPageisaReactApp); + // timer waiting for a snapshot from the background script, resets if the tab changes/reloads useEffect(() => { + function setLoadingArray(i: number, value: boolean) { + if (loadingArray[i] !== value) { // avoid unecessary state changes + const loadingArrayClone = [...loadingArray]; + loadingArrayClone[i] = value; + setLoading(loadingArrayClone); + } + } + // check for tab reload/change if (titleTracker.current !== currentTitle) { titleTracker.current = currentTitle; - const newArray = [...loadingArray]; - newArray[0] = true; - setLoading(newArray); + setLoadingArray(0, true); + setLoadingArray(1, true); + setLoadingArray(2, true); if (timeout.current) { clearTimeout(timeout.current); timeout.current = null; } } - - if (loadingArray[0] === true) { - timeout.current = setTimeout(() => { - const newArray = [...loadingArray]; - newArray[0] = false; - setLoading(newArray); - // grey out other checkers - }, 4000); + // if content script hasnt been found, set timer + if (!status.contentScriptLaunched) { + if (loadingArray[0] === true) { + timeout.current = setTimeout(() => { + setLoadingArray(0, false); + }, 2000); + } + } else { + setLoadingArray(0, false); + setLoadingArray(1, false); + // TODO: SHOULD THIS AUTOMATICALLY SET LOADING ARRAY 3 TO NOT LOADING? TEST WITH RDT TURNED OFF + // works on a reactapp like tictactoe but it doesn't work on the server when RDT disabled + setLoadingArray(2, false); } - }, [currentTitle, loadingArray]); + }, [status.contentScriptLaunched, currentTitle, timeout, loadingArray]); return (
@@ -46,25 +70,31 @@ function ErrorContainer(props): JSX.Element {

-

Waiting for content script on this tab

- +

Checking if content script has been launched on current tab

+

Checking if React Dev Tools has been installed

- +

Checking if this is React app

- +
- +
+ +
+ +
+ {/*
No React application found. Please visit reactime.io to more info. - + */}

- If you are using a React application, make sure tha you application is running in development mode.
NOTE: The React Developer Tools extension is also required for Reactime to run, if you do not already have it installed on your browser.

diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index 6979ad918..696162ff4 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ import React, { useEffect, useState } from 'react'; import ActionContainer from './ActionContainer'; import StateContainer from './StateContainer'; @@ -59,7 +60,8 @@ function MainContainer(): any { dispatch(deleteTab(payload)); break; } - case 'noDevTools': { + case 'devTools': { + console.log('devTools received in front end'); dispatch(noDev(payload)); break; } @@ -74,7 +76,7 @@ function MainContainer(): any { break; } case 'initialConnectSnapshots': { - dispatch(setTab(maxTab)); + // dispatch(setTab({ tabId: maxTab, title: payload[maxTab]['title'] })); dispatch(initialConnect(payload)); break; } @@ -132,12 +134,11 @@ function MainContainer(): any { document.addEventListener('click', mpClickTrack); }, []); - // error page if any of tabs check - // if (!tabs[currentTab]?.reactDevToolsInstalled) - console.log('does currentTab exist?', currentTab); - console.log('does tabs exist?', tabs); - console.log('does reactDevToolsInstalled exist?', tabs[currentTab]?.reactDevToolsInstalled); - if (!tabs[currentTab] || !tabs[currentTab].reactDevToolsInstalled) { + // Error Page launch IF 1) Content script not launchd 2) RDT not installed 3) Target not React app + console.log('tabs[currentTab] status of checks', currentTab, tabs[currentTab]?.status); + // console.log('does currentTab exist?', currentTab, 'does tabs exist?', tabs, 'does reactDevToolsInstalled exist?', tabs[currentTab]?.status.reactDevToolsInstalled); + + if (!tabs[currentTab] || !tabs[currentTab].status.reactDevToolsInstalled || !tabs[currentTab].status.targetPageisaReactApp) { return ( ); diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index 83f197d2d..6ecaebcc8 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -298,18 +298,14 @@ export default (state, action) => produce(state, draft => { } case types.DELETE_TAB: { delete draft.tabs[action.payload]; - if (draft.currentTab === action.payload) { - // if the deleted tab was set to currentTab, replace currentTab with - // the first tabId within tabs obj - const newCurrentTab = parseInt(Object.keys(draft.tabs)[0], 10); - draft.currentTab = newCurrentTab; - } break; } case types.NO_DEV: { const { payload } = action; - const { reactDevToolsInstalled } = payload[currentTab]; - tabs[currentTab].reactDevToolsInstalled = reactDevToolsInstalled; + if (tabs[currentTab]) { + const { reactDevToolsInstalled } = payload[currentTab].status; + tabs[currentTab].status.reactDevToolsInstalled = reactDevToolsInstalled; + } break; } case types.SET_CURRENT_LOCATION: { diff --git a/src/backend/linkFiber.ts b/src/backend/linkFiber.ts index f882e4e61..4ef30f096 100644 --- a/src/backend/linkFiber.ts +++ b/src/backend/linkFiber.ts @@ -549,18 +549,21 @@ export default (snap: Snapshot, mode: Mode): (() => void) => { // react devtools global hook is a global object that was injected by the React Devtools content script, allows access to fiber nodes and react version const devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; // check if reactDev Tools is installed - if (!devTools) { - window.postMessage({ - action: 'noDevToolsInstalled', - payload: 'noDevToolsInstalled' - }, - '*'); - return; - } + if (!devTools) { return; } + window.postMessage({ + action: 'devToolsInstalled', + payload: 'devToolsInstalled' + }, '*'); // reactInstance returns an object of the react - const reactInstance = devTools ? devTools.renderers.get(1) : null; - const throttledUpdateSnapshot = throttle(() => { updateSnapShotTree(snap, mode); }, 70); + const reactInstance = devTools.renderers.get(1); + // if no React Instance found then target is not a compatible app + if (!reactInstance) { return; } + window.postMessage({ + action: 'aReactApp', + payload: 'aReactApp' + }, '*'); + const throttledUpdateSnapshot = throttle(() => { updateSnapShotTree(snap, mode); }, 70); document.addEventListener('visibilitychange', onVisibilityChange); if (reactInstance && reactInstance.version) { @@ -575,7 +578,7 @@ export default (snap: Snapshot, mode: Mode): (() => void) => { return original(...args); }; }(devTools.onCommitFiberRoot)); - throttledUpdateSnapshot(); // only runs on start up + throttledUpdateSnapshot(); // only runs on start up } }; }; diff --git a/src/extension/background.js b/src/extension/background.js index bfe5fad43..1c24f5046 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -7,14 +7,14 @@ const firstSnapshotReceived = {}; // There will be the same number of objects in here as there are // Reactime tabs open for each user application being worked on. -let activeTab = {}; +let activeTab; const tabsObj = {}; // Will store Chrome web vital metrics and their corresponding values. const metrics = {}; // This function will create the first instance of the test app's tabs object // which will hold test app's snapshots, link fiber tree info, chrome tab info, etc. -function createTabObj(title, tabId) { +function createTabObj(title) { // TO-DO for save button // Save State // Knowledge of the comparison snapshot @@ -38,8 +38,12 @@ function createTabObj(title, tabId) { hierarchy: null, // records initial hierarchy to refresh page in case empty function is called initialHierarchy: null, - // checks for dev tools - reactDevToolsInstalled: true, + // status checks: Content script launch, React Dev Tools installed, target is react app + status: { + contentScriptLaunched: true, + reactDevToolsInstalled: false, + targetPageisaReactApp: false, + }, mode: { persist: false, paused: false, @@ -127,6 +131,7 @@ chrome.runtime.onConnect.addListener(port => { // push every port connected to the ports array portsArr.push(port); + // On Reactime launch: make sure RT's active tab is correct if (portsArr.length > 0) { portsArr.forEach(bg => bg.postMessage({ action: 'changeTab', @@ -135,6 +140,7 @@ chrome.runtime.onConnect.addListener(port => { } // send tabs obj to the connected devtools as soon as connection to devtools is made + console.log('sending initialConnectSnapshots from RTONLINE: payload =', tabsObj); if (Object.keys(tabsObj).length > 0) { port.postMessage({ action: 'initialConnectSnapshots', @@ -220,10 +226,11 @@ chrome.runtime.onConnect.addListener(port => { // background.js listening for a message from contentScript.js chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.log('recevied a message from contentScript', request); - // IGNORE THE AUTOMATIC MESSAGE SENT BY CHROME WHEN CONTENT SCRIPT IS FIRST LOADED + // AUTOMATIC MESSAGE SENT BY CHROME WHEN CONTENT SCRIPT IS FIRST LOADED: set Content if (request.type === 'SIGN_CONNECT') { return true; } + const tabTitle = sender.tab.title; const tabId = sender.tab.id; const { @@ -241,13 +248,13 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { || action === 'recordSnap' || action === 'jumpToSnap' || action === 'injectScript' - || action === 'noDevToolsInstalled' + || action === 'devToolsInstalled' + || action === 'aReactApp' ) { isReactTimeTravel = true; } else { return true; } - // everytime we get a new tabid, add it to the object if (isReactTimeTravel && !(tabId in tabsObj)) { console.log('creatingTabObj: action = ', action); @@ -266,16 +273,26 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { } break; } - case 'noDevToolsInstalled': { - console.log('no devTools installed: in background script. Setting to false'); - tabsObj[tabId].reactDevToolsInstalled = false; - // now that we have confirmed no devTools installed, we can go ahead and send this info to frontend + case 'devToolsInstalled': { + tabsObj[tabId].status.reactDevToolsInstalled = true; + console.log('DevTools installed', tabsObj[tabId]); + // now that we have confirmed RDT installed, send this info to frontend portsArr.forEach(bg => bg.postMessage({ - action: 'noDevTools', + action: 'devTools', payload: tabsObj, })); break; } + case 'aReactApp': { + console.log('confimred target is a react app: in background script. Setting to true'); + tabsObj[tabId].status.targetPageisaReactApp = true; + // now that we have confirmed target is a react app,send this info to frontend + // portsArr.forEach(bg => bg.postMessage({ + // action: 'noDevTools', + // payload: tabsObj, + // })); + break; + } // This injects a script into the app that you're testing Reactime on, // so that Reactime's backend files can communicate with the app's DOM. case 'injectScript': { @@ -327,6 +344,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { tabsObj[tabId].currBranch = 0; // send a message to devtools + console.log('sending initialConnectSnapshots from TABRELOAD: payload =', tabsObj); portsArr.forEach(bg => bg.postMessage({ action: 'initialConnectSnapshots', payload: tabsObj, @@ -348,6 +366,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { tabsObj[tabId], new Node(request.payload, tabsObj[tabId]), ); + console.log('sending initialConnectSnapshots from RECORDSNAP: payload =', tabsObj); if (portsArr.length > 0) { portsArr.forEach(bg => bg.postMessage({ action: 'initialConnectSnapshots', @@ -436,17 +455,16 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { // when tab view is changed, put the tabid as the current tab chrome.tabs.onActivated.addListener(info => { - // set active tab for global use: ignore an active tab if its reactime - chrome.tabs.query({ currentWindow: true, active: true }, tabs => { - if (!tabs[0]?.pendingUrl?.match('^chrome-extension')) { - [activeTab] = tabs; - console.log(activeTab); - console.log(info); - // tell devtools which tab to be the current + // get info about tab information from tabId + chrome.tabs.get(info.tabId, tab => { + // never set a reactime instance to the active tab + if (!tab.pendingUrl?.match('^chrome-extension')) { + console.log('active Tab = ', tab); + activeTab = tab; if (portsArr.length > 0) { portsArr.forEach(bg => bg.postMessage({ action: 'changeTab', - payload: { tabId: activeTab.id, title: activeTab.title }, + payload: { tabId: tab.id, title: tab.title }, })); } } diff --git a/src/extension/contentScript.ts b/src/extension/contentScript.ts index ce9e2d9bc..c33a88769 100644 --- a/src/extension/contentScript.ts +++ b/src/extension/contentScript.ts @@ -27,8 +27,10 @@ window.addEventListener('message', msg => { if (action === 'recordSnap') { chrome.runtime.sendMessage(msg.data); } - if (action === 'noDevToolsInstalled') { - console.log('noDevToolsInstalled in content script', msg.data); + if (action === 'devToolsInstalled') { + chrome.runtime.sendMessage(msg.data); + } + if (action === 'aReactApp') { chrome.runtime.sendMessage(msg.data); } }); @@ -36,7 +38,6 @@ window.addEventListener('message', msg => { // Listening for messages from the UI of the Reactime extension. chrome.runtime.onMessage.addListener(request => { const { action }: { action: string; } = request; - console.log('content Script: from background script', request); // this is only listening for Jump toSnap if (action) { if (action === 'jumpToSnap') { From b5ae82a9ff64a320e63089203923b6ec69a88cb6 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Fri, 18 Feb 2022 12:44:47 -0500 Subject: [PATCH 20/42] nearly done with error page --- src/app/components/ErrorMsg.tsx | 5 +++-- src/app/containers/ErrorContainer.tsx | 19 +++++++++---------- src/app/containers/MainContainer.tsx | 2 -- src/app/reducers/mainReducer.js | 6 ++++++ src/app/styles/layout/_errorContainer.scss | 3 +++ 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/app/components/ErrorMsg.tsx b/src/app/components/ErrorMsg.tsx index d8cf09e36..eab5eae0d 100644 --- a/src/app/components/ErrorMsg.tsx +++ b/src/app/components/ErrorMsg.tsx @@ -35,15 +35,16 @@ function ErrorMsg({
Content Script was not launched. Trying reloading the page.
- NOTE: By default Reactime only works with urls starting with localhost. + NOTE: By default Reactime only works with URLS starting with localhost
- IF this is not the case you press the launch button to manually launch the content script. + If this is not the case you press the launch button to manually launch the content script.
); case 'RDT Error': return ( @@ -94,10 +93,10 @@ function ErrorContainer(props): JSX.Element { > No React application found. Please visit reactime.io to more info. */} -

+ {/*


NOTE: The React Developer Tools extension is also required for Reactime to run, if you do not already have it installed on your browser. -

+

*/}
); } diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index 696162ff4..532a9fefd 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -136,8 +136,6 @@ function MainContainer(): any { // Error Page launch IF 1) Content script not launchd 2) RDT not installed 3) Target not React app console.log('tabs[currentTab] status of checks', currentTab, tabs[currentTab]?.status); - // console.log('does currentTab exist?', currentTab, 'does tabs exist?', tabs, 'does reactDevToolsInstalled exist?', tabs[currentTab]?.status.reactDevToolsInstalled); - if (!tabs[currentTab] || !tabs[currentTab].status.reactDevToolsInstalled || !tabs[currentTab].status.targetPageisaReactApp) { return ( diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index 6ecaebcc8..64220acfc 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -298,6 +298,12 @@ export default (state, action) => produce(state, draft => { } case types.DELETE_TAB: { delete draft.tabs[action.payload]; + // if (draft.currentTab === action.payload) { + // // if the deleted tab was set to currentTab, replace currentTab with + // // the first tabId within tabs obj + // const newCurrentTab = parseInt(Object.keys(draft.tabs)[0], 10); + // draft.currentTab = newCurrentTab; + // } break; } case types.NO_DEV: { diff --git a/src/app/styles/layout/_errorContainer.scss b/src/app/styles/layout/_errorContainer.scss index 75702c6f2..3cecdc0f2 100644 --- a/src/app/styles/layout/_errorContainer.scss +++ b/src/app/styles/layout/_errorContainer.scss @@ -53,4 +53,7 @@ .fail{ color: red; } + .errorMsg{ + text-align: center; + } } From 242c9568bbf0fecd617386b05cc71b3912669328 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Fri, 18 Feb 2022 13:18:37 -0500 Subject: [PATCH 21/42] almost done with launch content script button --- src/app/actions/actions.ts | 5 +++++ src/app/components/ErrorMsg.tsx | 4 +++- src/app/constants/actionTypes.ts | 1 + src/app/containers/ErrorContainer.tsx | 11 ++++++++-- src/app/reducers/mainReducer.js | 14 ++++++------ src/extension/background.js | 31 +++++++++++++++++++-------- 6 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/app/actions/actions.ts b/src/app/actions/actions.ts index e0eee16dc..b5ea7915f 100644 --- a/src/app/actions/actions.ts +++ b/src/app/actions/actions.ts @@ -86,6 +86,11 @@ export const noDev = (tab) => ({ payload: tab, }); +export const launchContentScript = (tab) => ({ + type: types.LAUNCH_CONTENT, + payload: tab, +}); + export const resetSlider = () => ({ type: types.SLIDER_ZERO, }); diff --git a/src/app/components/ErrorMsg.tsx b/src/app/components/ErrorMsg.tsx index eab5eae0d..8a9010c16 100644 --- a/src/app/components/ErrorMsg.tsx +++ b/src/app/components/ErrorMsg.tsx @@ -21,7 +21,7 @@ function parseError(loadingArray: [], status: any): string { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types function ErrorMsg({ - loadingArray, status, + loadingArray, status, launchContent, }): JSX.Element { switch (parseError(loadingArray, status)) { case 'Still Loading': @@ -38,6 +38,8 @@ function ErrorMsg({ NOTE: By default Reactime only works with URLS starting with localhost
If this is not the case you press the launch button to manually launch the content script. +
+
); case 'RDT Error': diff --git a/src/app/constants/actionTypes.ts b/src/app/constants/actionTypes.ts index 6672c764a..2ded9c2dc 100644 --- a/src/app/constants/actionTypes.ts +++ b/src/app/constants/actionTypes.ts @@ -13,6 +13,7 @@ export const NEW_SNAPSHOTS = 'NEW_SNAPSHOTS'; export const SET_TAB = 'SET_TAB'; export const DELETE_TAB = 'DELETE_TAB'; export const NO_DEV = 'NO_DEV'; +export const LAUNCH_CONTENT = 'LAUNCH_CONTENT'; export const SLIDER_ZERO = 'SLIDER_ZERO'; export const ON_HOVER = 'ON_HOVER'; export const ON_HOVER_EXIT = 'ON_HOVER_EXIT'; diff --git a/src/app/containers/ErrorContainer.tsx b/src/app/containers/ErrorContainer.tsx index d0d6d246f..825abbd21 100644 --- a/src/app/containers/ErrorContainer.tsx +++ b/src/app/containers/ErrorContainer.tsx @@ -1,17 +1,24 @@ /* eslint-disable max-len */ import React, { useState, useEffect, useRef } from 'react'; +import { + launchContentScript, +} from '../actions/actions'; import Loader from '../components/Loader'; import ErrorMsg from '../components/ErrorMsg'; import { useStoreContext } from '../store'; function ErrorContainer(props): JSX.Element { - const [store] = useStoreContext(); + const [store, dispatch] = useStoreContext(); const { tabs, currentTitle, currentTab } = store; // hooks for error checks const [loadingArray, setLoading] = useState([true, true, true]); const titleTracker = useRef(currentTitle); const timeout = useRef(null); + function launch(): void{ + dispatch(launchContentScript(tabs[currentTab])); + } + // check if tabObj exists > set status const status = { contentScriptLaunched: false, reactDevToolsInstalled: false, targetPageisaReactApp: false }; if (tabs[currentTab]) { @@ -82,7 +89,7 @@ function ErrorContainer(props): JSX.Element {
- +

diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index 64220acfc..5b5ade17f 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -298,12 +298,14 @@ export default (state, action) => produce(state, draft => { } case types.DELETE_TAB: { delete draft.tabs[action.payload]; - // if (draft.currentTab === action.payload) { - // // if the deleted tab was set to currentTab, replace currentTab with - // // the first tabId within tabs obj - // const newCurrentTab = parseInt(Object.keys(draft.tabs)[0], 10); - // draft.currentTab = newCurrentTab; - // } + break; + } + case types.LAUNCH_CONTENT: { + port.postMessage({ + action: 'launchContentScript', + payload: action.payload, + tabId: currentTab, + }); break; } case types.NO_DEV: { diff --git a/src/extension/background.js b/src/extension/background.js index 1c24f5046..1a6cbc794 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -210,10 +210,31 @@ chrome.runtime.onConnect.addListener(port => { // "Pause" is a deprecated feature from a previous Reactime version. case 'setPause': tabsObj[tabId].mode.paused = payload; - break; + return true; case 'setPersist': tabsObj[tabId].mode.persist = payload; return true; + case 'launchContentScript': + // !!! in Manifest Version 3 this will need to be changed to the commented out code here !!! + // chrome.scripting.executeScript({ + // target: { tabId }, + // files: ['bundles/content.bundle.js'], + // }); + chrome.tabs.executeScript(tabId, { + code: ` + // Function will attach script to the dom + const injectScript = (file, tag) => { + const htmlBody = document.getElementsByTagName(tag)[0]; + const script = document.createElement('script'); + script.setAttribute('type', 'text/javascript'); + script.setAttribute('src', file); + document.title=${tabId} + '-' + document.title + htmlBody.appendChild(script); + }; + injectScript(chrome.runtime.getURL('bundles/content.bundle.js'), 'body'); + `, + }); + return true; case 'jumpToSnap': chrome.tabs.sendMessage(tabId, msg); return true; // attempt to fix message port closing error, consider return Promise @@ -275,7 +296,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { } case 'devToolsInstalled': { tabsObj[tabId].status.reactDevToolsInstalled = true; - console.log('DevTools installed', tabsObj[tabId]); // now that we have confirmed RDT installed, send this info to frontend portsArr.forEach(bg => bg.postMessage({ action: 'devTools', @@ -284,13 +304,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { break; } case 'aReactApp': { - console.log('confimred target is a react app: in background script. Setting to true'); tabsObj[tabId].status.targetPageisaReactApp = true; - // now that we have confirmed target is a react app,send this info to frontend - // portsArr.forEach(bg => bg.postMessage({ - // action: 'noDevTools', - // payload: tabsObj, - // })); break; } // This injects a script into the app that you're testing Reactime on, @@ -459,7 +473,6 @@ chrome.tabs.onActivated.addListener(info => { chrome.tabs.get(info.tabId, tab => { // never set a reactime instance to the active tab if (!tab.pendingUrl?.match('^chrome-extension')) { - console.log('active Tab = ', tab); activeTab = tab; if (portsArr.length > 0) { portsArr.forEach(bg => bg.postMessage({ From 26b36a9140ec9ef84882f883c95c31cb8c3d5252 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Fri, 18 Feb 2022 14:02:58 -0500 Subject: [PATCH 22/42] finished with launch button for V2 manifest --- src/app/components/ErrorMsg.tsx | 2 +- src/extension/background.js | 18 +++--------------- src/extension/build/manifest.json | 6 ++++-- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/app/components/ErrorMsg.tsx b/src/app/components/ErrorMsg.tsx index 8a9010c16..49146642f 100644 --- a/src/app/components/ErrorMsg.tsx +++ b/src/app/components/ErrorMsg.tsx @@ -33,7 +33,7 @@ function ErrorMsg({ case 'Content Script Error': return (
- Content Script was not launched. Trying reloading the page. + Content Script was not launched. Try reloading the page.
NOTE: By default Reactime only works with URLS starting with localhost
diff --git a/src/extension/background.js b/src/extension/background.js index 1a6cbc794..23b896201 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -215,25 +215,13 @@ chrome.runtime.onConnect.addListener(port => { tabsObj[tabId].mode.persist = payload; return true; case 'launchContentScript': - // !!! in Manifest Version 3 this will need to be changed to the commented out code here !!! + // !!! in Manifest Version 3 this will need to be changed to the commented out code below !!! // chrome.scripting.executeScript({ // target: { tabId }, // files: ['bundles/content.bundle.js'], // }); - chrome.tabs.executeScript(tabId, { - code: ` - // Function will attach script to the dom - const injectScript = (file, tag) => { - const htmlBody = document.getElementsByTagName(tag)[0]; - const script = document.createElement('script'); - script.setAttribute('type', 'text/javascript'); - script.setAttribute('src', file); - document.title=${tabId} + '-' + document.title - htmlBody.appendChild(script); - }; - injectScript(chrome.runtime.getURL('bundles/content.bundle.js'), 'body'); - `, - }); + // This line below will need to be removed + chrome.tabs.executeScript(tabId, { file: 'bundles/content.bundle.js' }); return true; case 'jumpToSnap': chrome.tabs.sendMessage(tabId, msg); diff --git a/src/extension/build/manifest.json b/src/extension/build/manifest.json index ea24048d2..ce68dcf32 100644 --- a/src/extension/build/manifest.json +++ b/src/extension/build/manifest.json @@ -7,7 +7,8 @@ "content_security_policy": "script-src 'self' https://www.gstatic.com:* 'unsafe-eval'; object-src 'self'", "background": { "scripts": [ - "bundles/background.bundle.js" + "bundles/background.bundle.js", + "bundles/content.bundle.js" ], "persistent": false }, @@ -34,6 +35,7 @@ "tabs", "activeTab", "http://localhost/*", - "https://localhost/*" + "https://localhost/*", + "" ] } From 74407f53388d0df02d97a9da43bf1e84b1cf6aee Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Fri, 18 Feb 2022 14:59:10 -0500 Subject: [PATCH 23/42] cleaned up for merge --- src/app/components/ErrorMsg.tsx | 29 ++++++----------- src/app/components/Loader.tsx | 11 +++---- src/app/containers/ErrorContainer.tsx | 36 +++++----------------- src/app/containers/MainContainer.tsx | 7 +---- src/app/reducers/mainReducer.js | 1 + src/app/styles/layout/_errorContainer.scss | 17 ++++++++++ src/extension/background.js | 11 ++----- 7 files changed, 43 insertions(+), 69 deletions(-) diff --git a/src/app/components/ErrorMsg.tsx b/src/app/components/ErrorMsg.tsx index 49146642f..6d8f8c725 100644 --- a/src/app/components/ErrorMsg.tsx +++ b/src/app/components/ErrorMsg.tsx @@ -1,45 +1,34 @@ /* eslint-disable react/prop-types */ import React from 'react'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCheck, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; -// takes in the loadingArray and the status and returns the correct message +// parses loadingArray and status and returns the correct message function parseError(loadingArray: [], status: any): string { - // console.log('inside func', status); - // console.log('inside func', loadingArray); - let stillLoading = true; loadingArray.forEach(e => { if (e === false) stillLoading = false; }); - - if (stillLoading) return 'Still Loading'; - // check for first in status that is not true + // As long as everything is still loading dont diplay an error message + if (stillLoading) return 'default'; + // Return first status that fails if (!status.contentScriptLaunched) return 'Content Script Error'; if (!status.reactDevToolsInstalled) return 'RDT Error'; if (!status.targetPageisaReactApp) return 'Not React App'; return 'default'; } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types function ErrorMsg({ loadingArray, status, launchContent, }): JSX.Element { switch (parseError(loadingArray, status)) { - case 'Still Loading': - return ( -
- Still waiting for checks to complete -
- ); case 'Content Script Error': return (
- Content Script was not launched. Try reloading the page. + Content Script not found! +
+ NOTE: By default Reactime only launches the Content Script on URLS starting with localhost.
- NOTE: By default Reactime only works with URLS starting with localhost + If your target URL does not match, you can manually launch the content script below.
- If this is not the case you press the launch button to manually launch the content script.
- +
); case 'RDT Error': diff --git a/src/app/components/Loader.tsx b/src/app/components/Loader.tsx index 40723c50b..47784f869 100644 --- a/src/app/components/Loader.tsx +++ b/src/app/components/Loader.tsx @@ -1,9 +1,7 @@ /* eslint-disable react/prop-types */ import React from 'react'; import { css } from '@emotion/react'; -import { - ClipLoader, DotLoader, SyncLoader, ClockLoader, -} from 'react-spinners'; +import { ClipLoader } from 'react-spinners'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCheck, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; @@ -12,11 +10,13 @@ const override = css` margin: 0 auto; `; -const handleFail = (result: boolean): JSX.Element => (result +// Displays the result of the check when loading is done +const handleResult = (result: boolean): JSX.Element => (result ? : ); +// Returns the Loader component // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types const Loader = ({ loading, @@ -25,10 +25,9 @@ const Loader = ({ -) : handleFail(result)); +) : handleResult(result)); export default Loader; diff --git a/src/app/containers/ErrorContainer.tsx b/src/app/containers/ErrorContainer.tsx index 825abbd21..8d688625e 100644 --- a/src/app/containers/ErrorContainer.tsx +++ b/src/app/containers/ErrorContainer.tsx @@ -1,8 +1,6 @@ /* eslint-disable max-len */ import React, { useState, useEffect, useRef } from 'react'; -import { - launchContentScript, -} from '../actions/actions'; +import { launchContentScript } from '../actions/actions'; import Loader from '../components/Loader'; import ErrorMsg from '../components/ErrorMsg'; import { useStoreContext } from '../store'; @@ -15,18 +13,13 @@ function ErrorContainer(props): JSX.Element { const titleTracker = useRef(currentTitle); const timeout = useRef(null); - function launch(): void{ - dispatch(launchContentScript(tabs[currentTab])); - } + function launch(): void{ dispatch(launchContentScript(tabs[currentTab])); } // check if tabObj exists > set status const status = { contentScriptLaunched: false, reactDevToolsInstalled: false, targetPageisaReactApp: false }; - if (tabs[currentTab]) { - Object.assign(status, tabs[currentTab].status); - } - console.log(status); + if (tabs[currentTab]) { Object.assign(status, tabs[currentTab].status); } - // timer waiting for a snapshot from the background script, resets if the tab changes/reloads + // hook that sets timer while waiting for a snapshot from the background script, resets if the tab changes/reloads useEffect(() => { function setLoadingArray(i: number, value: boolean) { if (loadingArray[i] !== value) { // avoid unecessary state changes @@ -36,7 +29,7 @@ function ErrorContainer(props): JSX.Element { } } - // check for tab reload/change + // check for tab reload/change: reset loadingArray if (titleTracker.current !== currentTitle) { titleTracker.current = currentTitle; setLoadingArray(0, true); @@ -47,12 +40,10 @@ function ErrorContainer(props): JSX.Element { timeout.current = null; } } - // if content script hasnt been found, set timer + // if content script hasnt been found, set timer or immediately resolve if (!status.contentScriptLaunched) { if (loadingArray[0] === true) { - timeout.current = setTimeout(() => { - setLoadingArray(0, false); - }, 2000); + timeout.current = setTimeout(() => { setLoadingArray(0, false); }, 1500); } } else { setLoadingArray(0, false); @@ -91,19 +82,6 @@ function ErrorContainer(props): JSX.Element {
- -
- {/* - No React application found. Please visit reactime.io to more info. - */} - {/*

-
- NOTE: The React Developer Tools extension is also required for Reactime to run, if you do not already have it installed on your browser. -

*/}
); } diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index 532a9fefd..966aa96f5 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -47,8 +47,6 @@ function MainContainer(): any { payload: Record; sourceTab: number; }) => { - console.log('message', message); - const { action, payload, sourceTab } = message; let maxTab; if (!sourceTab) { @@ -61,7 +59,6 @@ function MainContainer(): any { break; } case 'devTools': { - console.log('devTools received in front end'); dispatch(noDev(payload)); break; } @@ -76,7 +73,6 @@ function MainContainer(): any { break; } case 'initialConnectSnapshots': { - // dispatch(setTab({ tabId: maxTab, title: payload[maxTab]['title'] })); dispatch(initialConnect(payload)); break; } @@ -134,8 +130,7 @@ function MainContainer(): any { document.addEventListener('click', mpClickTrack); }, []); - // Error Page launch IF 1) Content script not launchd 2) RDT not installed 3) Target not React app - console.log('tabs[currentTab] status of checks', currentTab, tabs[currentTab]?.status); + // Error Page launch IF(Content script not launched OR RDT not installed OR Target not React app) if (!tabs[currentTab] || !tabs[currentTab].status.reactDevToolsInstalled || !tabs[currentTab].status.targetPageisaReactApp) { return ( diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index 5b5ade17f..cd6840cca 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -301,6 +301,7 @@ export default (state, action) => produce(state, draft => { break; } case types.LAUNCH_CONTENT: { + // Fired when user clicks launch button on the error page. Send msg to background to launch port.postMessage({ action: 'launchContentScript', payload: action.payload, diff --git a/src/app/styles/layout/_errorContainer.scss b/src/app/styles/layout/_errorContainer.scss index 3cecdc0f2..c0bbbac6c 100644 --- a/src/app/styles/layout/_errorContainer.scss +++ b/src/app/styles/layout/_errorContainer.scss @@ -7,6 +7,7 @@ margin: 0 auto; background-color: $brand-color; overflow: hidden; + font: 'Roboto', sans-serif; display: flex; flex-direction: column; @@ -55,5 +56,21 @@ } .errorMsg{ text-align: center; + font-weight: bold; + } + + .launchContentButton { + padding: 3px; + outline: transparent; + color: white; + border-style: solid; + border-color: transparent; + border-radius: 3px; + cursor: pointer; + line-height: 1.5em; + font: 300 14px 'Roboto', sans-serif; + font-size: $button-text-size; + width: 120px; + background: $red-color-gradient; } } diff --git a/src/extension/background.js b/src/extension/background.js index 23b896201..2fa09e208 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -38,7 +38,7 @@ function createTabObj(title) { hierarchy: null, // records initial hierarchy to refresh page in case empty function is called initialHierarchy: null, - // status checks: Content script launch, React Dev Tools installed, target is react app + // status checks: Content Script launched, React Dev Tools installed, target is react app status: { contentScriptLaunched: true, reactDevToolsInstalled: false, @@ -140,7 +140,6 @@ chrome.runtime.onConnect.addListener(port => { } // send tabs obj to the connected devtools as soon as connection to devtools is made - console.log('sending initialConnectSnapshots from RTONLINE: payload =', tabsObj); if (Object.keys(tabsObj).length > 0) { port.postMessage({ action: 'initialConnectSnapshots', @@ -161,7 +160,6 @@ chrome.runtime.onConnect.addListener(port => { // listen for message containing a snapshot from devtools and send it to contentScript - // (i.e. they're all related to the button actions on Reactime) port.onMessage.addListener(msg => { - console.log('message from devtools received', msg); // msg is action denoting a time jump in devtools // --------------------------------------------------------------- // message incoming from devTools should look like this: @@ -234,7 +232,6 @@ chrome.runtime.onConnect.addListener(port => { // background.js listening for a message from contentScript.js chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { - console.log('recevied a message from contentScript', request); // AUTOMATIC MESSAGE SENT BY CHROME WHEN CONTENT SCRIPT IS FIRST LOADED: set Content if (request.type === 'SIGN_CONNECT') { return true; @@ -266,7 +263,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { } // everytime we get a new tabid, add it to the object if (isReactTimeTravel && !(tabId in tabsObj)) { - console.log('creatingTabObj: action = ', action); tabsObj[tabId] = createTabObj(tabTitle); } @@ -282,15 +278,16 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { } break; } + // Confirmed React Dev Tools installed, send this info to frontend case 'devToolsInstalled': { tabsObj[tabId].status.reactDevToolsInstalled = true; - // now that we have confirmed RDT installed, send this info to frontend portsArr.forEach(bg => bg.postMessage({ action: 'devTools', payload: tabsObj, })); break; } + // Confirmed target is a react app. No need to send to frontend case 'aReactApp': { tabsObj[tabId].status.targetPageisaReactApp = true; break; @@ -346,7 +343,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { tabsObj[tabId].currBranch = 0; // send a message to devtools - console.log('sending initialConnectSnapshots from TABRELOAD: payload =', tabsObj); portsArr.forEach(bg => bg.postMessage({ action: 'initialConnectSnapshots', payload: tabsObj, @@ -368,7 +364,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { tabsObj[tabId], new Node(request.payload, tabsObj[tabId]), ); - console.log('sending initialConnectSnapshots from RECORDSNAP: payload =', tabsObj); if (portsArr.length > 0) { portsArr.forEach(bg => bg.postMessage({ action: 'initialConnectSnapshots', From b94d34f829f2a8e24d6bcac6282855a0c37e03e8 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Fri, 18 Feb 2022 15:13:04 -0500 Subject: [PATCH 24/42] small change error page --- src/app/components/ErrorMsg.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/ErrorMsg.tsx b/src/app/components/ErrorMsg.tsx index 6d8f8c725..0ebd9c79c 100644 --- a/src/app/components/ErrorMsg.tsx +++ b/src/app/components/ErrorMsg.tsx @@ -21,7 +21,7 @@ function ErrorMsg({ case 'Content Script Error': return (
- Content Script not found! + Content Script not found! Try closing Reactime and reloading the page.
NOTE: By default Reactime only launches the Content Script on URLS starting with localhost.
From 6a1152d57ca637790306d7566a54a3cc32a9c408 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Fri, 18 Feb 2022 15:24:14 -0500 Subject: [PATCH 25/42] finished with erroPage, ensured test compliance --- src/app/__tests__/MainContainer.test.tsx | 6 +++--- src/app/__tests__/mainReducer.test.tsx | 6 ------ src/app/containers/ErrorContainer.tsx | 8 ++++++++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/app/__tests__/MainContainer.test.tsx b/src/app/__tests__/MainContainer.test.tsx index ebd37c66a..22d150742 100644 --- a/src/app/__tests__/MainContainer.test.tsx +++ b/src/app/__tests__/MainContainer.test.tsx @@ -12,6 +12,7 @@ import ActionContainer from '../containers/ActionContainer'; import StateContainer from '../containers/StateContainer'; import TravelContainer from '../containers/TravelContainer'; import ButtonsContainer from '../containers/ButtonsContainer'; +import ErrorContainer from '../containers/ErrorContainer'; const chrome = require('sinon-chrome'); @@ -46,9 +47,7 @@ beforeEach(() => { describe('MainContainer rendering', () => { test('With no snapshots, should not render any containers', () => { - expect(wrapper.text()).toEqual( - 'No React application found. Please visit reactime.io to more info.If you are using a React application, make sure tha you application is running in development mode.NOTE: The React Developer Tools extension is also required for Reactime to run, if you do not already have it installed on your browser.', - ); + expect(wrapper.find(ErrorContainer).length).toBe(1); expect(wrapper.find(ActionContainer).length).toBe(0); expect(wrapper.find(StateContainer).length).toBe(0); expect(wrapper.find(TravelContainer).length).toBe(0); @@ -58,6 +57,7 @@ describe('MainContainer rendering', () => { state.currentTab = 87; state.tabs[87] = { snapshots: [{}], + status: { contentScriptLaunched: true, reactDevToolsInstalled: true, targetPageisaReactApp: true }, viewIndex: -1, sliderIndex: 0, mode: {}, diff --git a/src/app/__tests__/mainReducer.test.tsx b/src/app/__tests__/mainReducer.test.tsx index 413088f7d..4a2b5e8aa 100644 --- a/src/app/__tests__/mainReducer.test.tsx +++ b/src/app/__tests__/mainReducer.test.tsx @@ -356,12 +356,6 @@ describe('mainReducer testing', () => { expect(afterDelete.tabs[75]).toBe(undefined); expect(afterDelete.tabs[87]).not.toBe(undefined); }); - it('should change current tab if deleted tab matches current tab', () => { - const afterDelete = mainReducer(state, deleteTab(87)); - expect(afterDelete.tabs[87]).toBe(undefined); - expect(afterDelete.tabs[75]).not.toBe(undefined); - expect(afterDelete.currentTab).toBe(75); - }); }); describe('default', () => { diff --git a/src/app/containers/ErrorContainer.tsx b/src/app/containers/ErrorContainer.tsx index 8d688625e..71c4614cc 100644 --- a/src/app/containers/ErrorContainer.tsx +++ b/src/app/containers/ErrorContainer.tsx @@ -82,6 +82,14 @@ function ErrorContainer(props): JSX.Element {
+
+ + Please visit reactime.io to more info. +
); } From 721db49bb68225ae22bafb7aa493985a61f180ef Mon Sep 17 00:00:00 2001 From: Brian Zheng Date: Wed, 23 Feb 2022 08:30:43 -0800 Subject: [PATCH 26/42] Cant figure out split --- package.json | 1 + src/app/components/StateRoute.tsx | 2 +- src/app/containers/ButtonsContainer.tsx | 1 - src/app/containers/MainContainer.tsx | 18 ++++++++++++++++++ src/app/containers/StateContainer.tsx | 4 +++- src/app/styles/layout/_bodyContainer.scss | 1 + src/app/styles/layout/_mainContainer.scss | 1 + yarn.lock | 15 ++++++++++++++- 8 files changed, 39 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e6349e033..c35e81b18 100644 --- a/package.json +++ b/package.json @@ -175,6 +175,7 @@ "react-json-tree": "^0.11.2", "react-router-dom": "^5.2.0", "react-select": "^3.1.0", + "react-split": "^2.0.14", "recoil": "0.0.10", "web-vitals": "^1.1.0" } diff --git a/src/app/components/StateRoute.tsx b/src/app/components/StateRoute.tsx index a48b07615..5868209c0 100644 --- a/src/app/components/StateRoute.tsx +++ b/src/app/components/StateRoute.tsx @@ -64,7 +64,7 @@ const StateRoute = (props: StateRouteProps) => { return ( {({ width, height }) => ( - + )} ); diff --git a/src/app/containers/ButtonsContainer.tsx b/src/app/containers/ButtonsContainer.tsx index 3afda27e1..7dd80ecc2 100644 --- a/src/app/containers/ButtonsContainer.tsx +++ b/src/app/containers/ButtonsContainer.tsx @@ -88,7 +88,6 @@ function ButtonsContainer() { )} {persist ? 'Unpersist' : 'Persist'} -
diff --git a/src/app/containers/StateContainer.tsx b/src/app/containers/StateContainer.tsx index 2b630e734..0fadeceb6 100644 --- a/src/app/containers/StateContainer.tsx +++ b/src/app/containers/StateContainer.tsx @@ -8,7 +8,6 @@ import { } from 'react-router-dom'; import StateRoute from '../components/StateRoute'; import DiffRoute from '../components/DiffRoute'; - interface StateContainerProps { snapshot: Record< number, @@ -41,6 +40,8 @@ const StateContainer = (props: StateContainerProps): JSX.Element => { viewIndex, webMetrics, currLocation, + splitView, + setSplitView, // added snapshots, Rob 11/4 snapshots, } = props; @@ -66,6 +67,7 @@ const StateContainer = (props: StateContainerProps): JSX.Element => { > Diff + diff --git a/src/app/styles/layout/_bodyContainer.scss b/src/app/styles/layout/_bodyContainer.scss index 04d710bdb..921320aa4 100644 --- a/src/app/styles/layout/_bodyContainer.scss +++ b/src/app/styles/layout/_bodyContainer.scss @@ -23,3 +23,4 @@ 'buttons'; } } +//inside of state ccontainer use flexbox? \ No newline at end of file diff --git a/src/app/styles/layout/_mainContainer.scss b/src/app/styles/layout/_mainContainer.scss index 42b22f7be..781c579da 100644 --- a/src/app/styles/layout/_mainContainer.scss +++ b/src/app/styles/layout/_mainContainer.scss @@ -4,3 +4,4 @@ background-color: $brand-color; overflow: hidden; } + diff --git a/yarn.lock b/yarn.lock index e230f5972..d6f80d388 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8436,6 +8436,14 @@ "react-input-autosize" "^2.2.2" "react-transition-group" "^4.3.0" +"react-split@^2.0.14": + "integrity" "sha512-bKWydgMgaKTg/2JGQnaJPg51T6dmumTWZppFgEbbY0Fbme0F5TuatAScCLaqommbGQQf/ZT1zaejuPDriscISA==" + "resolved" "https://registry.npmjs.org/react-split/-/react-split-2.0.14.tgz" + "version" "2.0.14" + dependencies: + "prop-types" "^15.5.7" + "split.js" "^1.6.0" + "react-test-renderer@^16.0.0-0": "integrity" "sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ==" "resolved" "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.13.1.tgz" @@ -8463,7 +8471,7 @@ dependencies: "debounce" "^1.2.0" -"react@^0.14 || ^15.0.0 || ^16.0.0-alpha", "react@^0.14.0 || ^15.0.0 || ^16.0.0", "react@^0.14.0 || ^15.0.0 || ^16.0.0-0", "react@^0.14.9 || ^15.3.0 || ^16.0.0-rc || ^16.0", "react@^15.0.0 || ^16.0.0", "react@^15.0.0-0 || ^16.0.0-0", "react@^16.0.0-0", "react@^16.13.1", "react@^16.3.0-0", "react@^16.8.0", "react@^16.8.0 || ^17.0.0", "react@^16.8.0-0", "react@>=0.13", "react@>=15", "react@>=16.13", "react@>=16.3.0", "react@>=16.6.0", "react@>=16.x", "react@0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0": +"react@*", "react@^0.14 || ^15.0.0 || ^16.0.0-alpha", "react@^0.14.0 || ^15.0.0 || ^16.0.0", "react@^0.14.0 || ^15.0.0 || ^16.0.0-0", "react@^0.14.9 || ^15.3.0 || ^16.0.0-rc || ^16.0", "react@^15.0.0 || ^16.0.0", "react@^15.0.0-0 || ^16.0.0-0", "react@^16.0.0-0", "react@^16.13.1", "react@^16.3.0-0", "react@^16.8.0", "react@^16.8.0 || ^17.0.0", "react@^16.8.0-0", "react@>=0.13", "react@>=15", "react@>=16.13", "react@>=16.3.0", "react@>=16.6.0", "react@>=16.x", "react@0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0": "integrity" "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==" "resolved" "https://registry.npmjs.org/react/-/react-16.13.1.tgz" "version" "16.13.1" @@ -9452,6 +9460,11 @@ dependencies: "extend-shallow" "^3.0.0" +"split.js@^1.6.0": + "integrity" "sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==" + "resolved" "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz" + "version" "1.6.5" + "sprintf-js@~1.0.2": "integrity" "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" "resolved" "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" From 00fea6c417058468a31d256708fea6f51119100e Mon Sep 17 00:00:00 2001 From: Brian Zheng Date: Wed, 23 Feb 2022 08:59:32 -0800 Subject: [PATCH 27/42] split function --- src/app/containers/MainContainer.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index a2bca818b..41b1c6654 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -200,6 +200,11 @@ function MainContainer(): any { />
Date: Wed, 23 Feb 2022 14:05:44 -0500 Subject: [PATCH 28/42] split view working, no working button --- src/app/components/StateRoute.tsx | 2 +- src/app/containers/MainContainer.tsx | 68 +++++++------ src/app/containers/StateContainer.tsx | 6 +- src/app/containers/StateContainer2.tsx | 97 ++++++++++++++++++ src/app/styles/layout/_bodyContainer.scss | 2 +- src/app/styles/layout/_stateContainer.scss | 6 +- src/app/styles/main.scss | 3 +- yarn.lock | 110 +++++++-------------- 8 files changed, 184 insertions(+), 110 deletions(-) create mode 100644 src/app/containers/StateContainer2.tsx diff --git a/src/app/components/StateRoute.tsx b/src/app/components/StateRoute.tsx index 5868209c0..a48b07615 100644 --- a/src/app/components/StateRoute.tsx +++ b/src/app/components/StateRoute.tsx @@ -64,7 +64,7 @@ const StateRoute = (props: StateRouteProps) => { return ( {({ width, height }) => ( - + )} ); diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index cb9e813fb..27b171b49 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -3,6 +3,7 @@ import React, { useEffect, useState } from 'react'; import Split from 'react-split'; import ActionContainer from './ActionContainer'; import StateContainer from './StateContainer'; +import StateContainer2 from './StateContainer2'; import TravelContainer from './TravelContainer'; import ButtonsContainer from './ButtonsContainer'; import ErrorContainer from './ErrorContainer'; @@ -142,6 +143,11 @@ function MainContainer(): any { const snapshotDisplay = statelessCleaning(snapshotView); const hierarchyDisplay = statelessCleaning(hierarchy); // body container scss file + + // notes Split built in Styling you can give + // within its own component. + // scss file is limiting where State Container can g + // docs https://github.com/nathancahill/split/tree/master/packages/splitjs return (
@@ -150,36 +156,42 @@ function MainContainer(): any { setActionView={setActionView} toggleActionContainer={toggleActionContainer} /> -
- - {snapshots.length ? ( - - ) : null} + + {/* */} + {snapshots.length ? ( + + ) : null} + {snapshots.length ? ( + + ) : null} - -
+
diff --git a/src/app/containers/StateContainer.tsx b/src/app/containers/StateContainer.tsx index 0fadeceb6..bcc971b93 100644 --- a/src/app/containers/StateContainer.tsx +++ b/src/app/containers/StateContainer.tsx @@ -48,7 +48,7 @@ const StateContainer = (props: StateContainerProps): JSX.Element => { return ( -
+
@@ -58,7 +58,7 @@ const StateContainer = (props: StateContainerProps): JSX.Element => { exact to="/" > - State + STATE { > Diff - +
diff --git a/src/app/containers/StateContainer2.tsx b/src/app/containers/StateContainer2.tsx new file mode 100644 index 000000000..610dc6b2f --- /dev/null +++ b/src/app/containers/StateContainer2.tsx @@ -0,0 +1,97 @@ +// @ts-nocheck +import React, { useState } from 'react'; +import { + MemoryRouter as Router, + Route, + NavLink, + Switch, +} from 'react-router-dom'; +import StateRoute from '../components/StateRoute'; +import DiffRoute from '../components/DiffRoute'; +interface StateContainerProps { + snapshot: Record< + number, + { + name?: string; + componentData?: Record; + state?: Record; + stateSnaphot?: Record; + children?: unknown[]; + AtomsRelationship?: any[]; + atomSelectors?: object; + atomsComponents?: object; + } + >; + toggleActionContainer?: any; + webMetrics?: object; + AtomsRelationship?: any[]; + hierarchy: Record; + snapshots?: []; + viewIndex?: number; + currLocation?: object; +} + +// eslint-disable-next-line react/prop-types +const StateContainer2 = (props: StateContainerProps): JSX.Element => { + const { + snapshot, + hierarchy, + snapshots, + viewIndex, + webMetrics, + currLocation, + splitView, + setSplitView, + // added snapshots, Rob 11/4 + snapshots, + } = props; + + return ( + +
+
+
+
+ + TESTINGTESTING + + + Diff + + +
+
+ + } + /> + ( + + )} + /> + +
+ + ); +}; + +export default StateContainer2; diff --git a/src/app/styles/layout/_bodyContainer.scss b/src/app/styles/layout/_bodyContainer.scss index 921320aa4..7ea69eb75 100644 --- a/src/app/styles/layout/_bodyContainer.scss +++ b/src/app/styles/layout/_bodyContainer.scss @@ -23,4 +23,4 @@ 'buttons'; } } -//inside of state ccontainer use flexbox? \ No newline at end of file +//inside of state ccontainer use flexbox? diff --git a/src/app/styles/layout/_stateContainer.scss b/src/app/styles/layout/_stateContainer.scss index 2608411af..5efb5281c 100644 --- a/src/app/styles/layout/_stateContainer.scss +++ b/src/app/styles/layout/_stateContainer.scss @@ -2,6 +2,7 @@ font-size: 12px; overflow: auto; background-color: $brand-color; + display: flex; } #componentMapContainer, @@ -9,6 +10,9 @@ #atomsContainer { overflow: hidden; } +.state-container .state{ + overflow-y: auto; +} .toggleAC { // color: #00dffc; @@ -214,4 +218,4 @@ .bargraph-position { position: relative; -} \ No newline at end of file +} diff --git a/src/app/styles/main.scss b/src/app/styles/main.scss index 029440139..75deb171c 100644 --- a/src/app/styles/main.scss +++ b/src/app/styles/main.scss @@ -26,7 +26,7 @@ background: rgb(97, 97, 97); } -// fixing the tooltip display for overflow scrolling +// fixing the tooltip display for overflow scrolling // 1. Configuration and helpers @import 'abstracts/variables'; @@ -65,4 +65,3 @@ .props p{ line-height:1; } - diff --git a/yarn.lock b/yarn.lock index 552d295ae..be0a7b5b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1622,9 +1622,9 @@ "pretty-format" "^25.2.1" "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4": - "integrity" "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==" - "resolved" "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz" - "version" "7.0.5" + "integrity" "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" + "resolved" "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz" + "version" "7.0.9" "@types/json5@^0.0.29": "integrity" "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" @@ -2246,14 +2246,14 @@ "version" "1.0.1" "ajv-keywords@^3.1.0", "ajv-keywords@^3.4.1": - "integrity" "sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA==" - "resolved" "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.1.tgz" - "version" "3.5.1" + "integrity" "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + "resolved" "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" + "version" "3.5.2" "ajv@^6.1.0", "ajv@^6.10.0", "ajv@^6.10.2", "ajv@^6.12.2", "ajv@^6.5.5", "ajv@^6.9.1", "ajv@>=5.0.0": - "integrity" "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==" - "resolved" "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz" - "version" "6.12.3" + "integrity" "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==" + "resolved" "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + "version" "6.12.6" dependencies: "fast-deep-equal" "^3.1.1" "fast-json-stable-stringify" "^2.0.0" @@ -2300,10 +2300,10 @@ "micromatch" "^3.1.4" "normalize-path" "^2.1.1" -"anymatch@^3.0.3", "anymatch@~3.1.2": - "integrity" "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==" - "resolved" "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" - "version" "3.1.2" +"anymatch@^3.0.3", "anymatch@~3.1.1": + "integrity" "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==" + "resolved" "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz" + "version" "3.1.1" dependencies: "normalize-path" "^3.0.0" "picomatch" "^2.0.4" @@ -2725,13 +2725,6 @@ "resolved" "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz" "version" "2.1.0" -"bindings@^1.5.0": - "integrity" "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==" - "resolved" "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz" - "version" "1.5.0" - dependencies: - "file-uri-to-path" "1.0.0" - "bl@^4.0.1": "integrity" "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==" "resolved" "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz" @@ -3143,19 +3136,19 @@ "fsevents" "^1.2.7" "chokidar@^3.4.0", "chokidar@>=2.0.0 <4.0.0": - "integrity" "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==" - "resolved" "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" - "version" "3.5.3" + "integrity" "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==" + "resolved" "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz" + "version" "3.4.0" dependencies: - "anymatch" "~3.1.2" + "anymatch" "~3.1.1" "braces" "~3.0.2" - "glob-parent" "~5.1.2" + "glob-parent" "~5.1.0" "is-binary-path" "~2.1.0" "is-glob" "~4.0.1" "normalize-path" "~3.0.0" - "readdirp" "~3.6.0" + "readdirp" "~3.4.0" optionalDependencies: - "fsevents" "~2.3.2" + "fsevents" "~2.1.2" "chownr@^1.1.1": "integrity" "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" @@ -4907,11 +4900,6 @@ dependencies: "flat-cache" "^2.0.1" -"file-uri-to-path@1.0.0": - "integrity" "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - "resolved" "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" - "version" "1.0.0" - "fill-range@^4.0.0": "integrity" "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=" "resolved" "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz" @@ -5090,24 +5078,6 @@ "resolved" "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" "version" "1.0.0" -"fsevents@^1.2.7": - "integrity" "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==" - "resolved" "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz" - "version" "1.2.13" - dependencies: - "bindings" "^1.5.0" - "nan" "^2.12.1" - -"fsevents@^2.1.2": - "integrity" "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==" - "resolved" "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" - "version" "2.3.2" - -"fsevents@~2.1.2": - "integrity" "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==" - "resolved" "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz" - "version" "2.1.3" - "function-bind@^1.1.1": "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" "resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" @@ -5196,10 +5166,10 @@ "is-glob" "^3.1.0" "path-dirname" "^1.0.0" -"glob-parent@^5.0.0", "glob-parent@~5.1.2": - "integrity" "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==" - "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" - "version" "5.1.2" +"glob-parent@^5.0.0", "glob-parent@~5.1.0": + "integrity" "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==" + "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz" + "version" "5.1.1" dependencies: "is-glob" "^4.0.1" @@ -7130,11 +7100,6 @@ "resolved" "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" "version" "0.0.8" -"nan@^2.12.1": - "integrity" "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" - "resolved" "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz" - "version" "2.15.0" - "nanomatch@^1.2.9": "integrity" "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==" "resolved" "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz" @@ -8250,6 +8215,7 @@ "version" "0.11.0" dependencies: "@emotion/react" "^11.1.4" + "react-split@^2.0.14": "integrity" "sha512-bKWydgMgaKTg/2JGQnaJPg51T6dmumTWZppFgEbbY0Fbme0F5TuatAScCLaqommbGQQf/ZT1zaejuPDriscISA==" "resolved" "https://registry.npmjs.org/react-split/-/react-split-2.0.14.tgz" @@ -8285,11 +8251,7 @@ dependencies: "debounce" "^1.2.0" -<<<<<<< HEAD -"react@^0.14 || ^15.0.0 || ^16.0.0-alpha", "react@^0.14.0 || ^15.0.0 || ^16.0.0", "react@^0.14.0 || ^15.0.0 || ^16.0.0-0", "react@^0.14.9 || ^15.3.0 || ^16.0.0-rc || ^16.0", "react@^15.0.0 || ^16.0.0", "react@^15.0.0-0 || ^16.0.0-0", "react@^16.0.0 || ^17.0.0", "react@^16.0.0-0", "react@^16.13.1", "react@^16.3.0-0", "react@^16.8.0", "react@^16.8.0 || ^17.0.0", "react@^16.8.0-0", "react@>=0.13", "react@>=15", "react@>=16.13", "react@>=16.3.0", "react@>=16.6.0", "react@>=16.8.0", "react@>=16.x", "react@0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0": -======= -"react@*", "react@^0.14 || ^15.0.0 || ^16.0.0-alpha", "react@^0.14.0 || ^15.0.0 || ^16.0.0", "react@^0.14.0 || ^15.0.0 || ^16.0.0-0", "react@^0.14.9 || ^15.3.0 || ^16.0.0-rc || ^16.0", "react@^15.0.0 || ^16.0.0", "react@^15.0.0-0 || ^16.0.0-0", "react@^16.0.0-0", "react@^16.13.1", "react@^16.3.0-0", "react@^16.8.0", "react@^16.8.0 || ^17.0.0", "react@^16.8.0-0", "react@>=0.13", "react@>=15", "react@>=16.13", "react@>=16.3.0", "react@>=16.6.0", "react@>=16.x", "react@0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0": ->>>>>>> 00fea6c417058468a31d256708fea6f51119100e +"react@*", "react@^0.14 || ^15.0.0 || ^16.0.0-alpha", "react@^0.14.0 || ^15.0.0 || ^16.0.0", "react@^0.14.0 || ^15.0.0 || ^16.0.0-0", "react@^0.14.9 || ^15.3.0 || ^16.0.0-rc || ^16.0", "react@^15.0.0 || ^16.0.0", "react@^15.0.0-0 || ^16.0.0-0", "react@^16.0.0 || ^17.0.0", "react@^16.0.0-0", "react@^16.13.1", "react@^16.3.0-0", "react@^16.8.0", "react@^16.8.0 || ^17.0.0", "react@^16.8.0-0", "react@>=0.13", "react@>=15", "react@>=16.13", "react@>=16.3.0", "react@>=16.6.0", "react@>=16.8.0", "react@>=16.x", "react@0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0": "integrity" "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==" "resolved" "https://registry.npmjs.org/react/-/react-16.13.1.tgz" "version" "16.13.1" @@ -8469,10 +8431,10 @@ "micromatch" "^3.1.10" "readable-stream" "^2.0.2" -"readdirp@~3.6.0": - "integrity" "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==" - "resolved" "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" - "version" "3.6.0" +"readdirp@~3.4.0": + "integrity" "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==" + "resolved" "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz" + "version" "3.4.0" dependencies: "picomatch" "^2.2.1" @@ -8796,9 +8758,9 @@ "version" "1.3.3" "rxjs@^6.6.0": - "integrity" "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==" - "resolved" "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" - "version" "6.6.7" + "integrity" "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==" + "resolved" "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz" + "version" "6.6.0" dependencies: "tslib" "^1.9.0" @@ -10402,9 +10364,9 @@ "async-limiter" "~1.0.0" "ws@^7.2.3": - "integrity" "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==" - "resolved" "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz" - "version" "7.5.7" + "integrity" "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" + "resolved" "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz" + "version" "7.3.1" "xml-name-validator@^3.0.0": "integrity" "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" From bc497afc99708491a82da578821b7b93bb67721c Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Wed, 23 Feb 2022 16:37:13 -0500 Subject: [PATCH 29/42] done with split viewgit add .! --- src/app/actions/actions.ts | 4 + src/app/components/App.tsx | 11 ++- src/app/components/StateRoute.tsx | 2 +- src/app/constants/actionTypes.ts | 1 + src/app/containers/ButtonsContainer.tsx | 28 ++++-- src/app/containers/MainContainer.tsx | 108 ++++++++++++--------- src/app/containers/StateContainer.tsx | 7 +- src/app/containers/StateContainer2.tsx | 97 ------------------ src/app/reducers/mainReducer.js | 6 +- src/app/styles/base/_base.scss | 8 +- src/app/styles/layout/_bodyContainer.scss | 3 +- src/app/styles/layout/_mainContainer.scss | 8 ++ src/app/styles/layout/_stateContainer.scss | 19 ++-- 13 files changed, 129 insertions(+), 173 deletions(-) delete mode 100644 src/app/containers/StateContainer2.tsx diff --git a/src/app/actions/actions.ts b/src/app/actions/actions.ts index b5ea7915f..50dd63d68 100644 --- a/src/app/actions/actions.ts +++ b/src/app/actions/actions.ts @@ -86,6 +86,10 @@ export const noDev = (tab) => ({ payload: tab, }); +export const toggleSplit = () => ({ + type: types.TOGGLE_SPLIT, +}); + export const launchContentScript = (tab) => ({ type: types.LAUNCH_CONTENT, payload: tab, diff --git a/src/app/components/App.tsx b/src/app/components/App.tsx index 26f7dbf47..805368ea6 100644 --- a/src/app/components/App.tsx +++ b/src/app/components/App.tsx @@ -3,11 +3,16 @@ import MainContainer from '../containers/MainContainer'; import { StoreContext } from '../store'; import mainReducer from '../reducers/mainReducer.js'; -const initialState:{port: null|number, - currentTab: null|number, currentTitle: null|string, tabs: unknown } = { +const initialState: { + port: null | number, + currentTab: null | number, + currentTitle: null | string, + split: null | boolean, + tabs: unknown; } = { port: null, currentTab: null, - currentTitle: null, + currentTitle: 'No Target', + split: false, tabs: {}, }; diff --git a/src/app/components/StateRoute.tsx b/src/app/components/StateRoute.tsx index a48b07615..8996ea26b 100644 --- a/src/app/components/StateRoute.tsx +++ b/src/app/components/StateRoute.tsx @@ -62,7 +62,7 @@ const StateRoute = (props: StateRouteProps) => { const renderComponentMap = () => { if (hierarchy) { return ( - + {({ width, height }) => ( )} diff --git a/src/app/constants/actionTypes.ts b/src/app/constants/actionTypes.ts index 2ded9c2dc..3b5f62d8a 100644 --- a/src/app/constants/actionTypes.ts +++ b/src/app/constants/actionTypes.ts @@ -13,6 +13,7 @@ export const NEW_SNAPSHOTS = 'NEW_SNAPSHOTS'; export const SET_TAB = 'SET_TAB'; export const DELETE_TAB = 'DELETE_TAB'; export const NO_DEV = 'NO_DEV'; +export const TOGGLE_SPLIT = 'TOGGLE_SPLIT'; export const LAUNCH_CONTENT = 'LAUNCH_CONTENT'; export const SLIDER_ZERO = 'SLIDER_ZERO'; export const ON_HOVER = 'ON_HOVER'; diff --git a/src/app/containers/ButtonsContainer.tsx b/src/app/containers/ButtonsContainer.tsx index ff85bafdc..f1338998a 100644 --- a/src/app/containers/ButtonsContainer.tsx +++ b/src/app/containers/ButtonsContainer.tsx @@ -1,18 +1,17 @@ // @ts-nocheck import React from 'react'; -import { importSnapshots, toggleMode } from '../actions/actions'; -import { useStoreContext } from '../store'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faUpload, faQuestion, faDownload, - faMapMarker, faMapPin, faRedoAlt, faUnlock, faLock, } from '@fortawesome/free-solid-svg-icons'; +import { importSnapshots, toggleMode, toggleSplit } from '../actions/actions'; +import { useStoreContext } from '../store'; function exportHandler(snapshots: []) { // create invisible download anchor link @@ -20,7 +19,7 @@ function exportHandler(snapshots: []) { // set file in anchor link fileDownload.href = URL.createObjectURL( - new Blob([JSON.stringify(snapshots)], { type: 'application/json' }) + new Blob([JSON.stringify(snapshots)], { type: 'application/json' }), ); // set anchor as file download and click it @@ -56,7 +55,7 @@ function howToUseHandler() { } function ButtonsContainer(): JSX.Element { - const [{ tabs, currentTab }, dispatch] = useStoreContext(); + const [{ tabs, currentTab, split }, dispatch] = useStoreContext(); const { snapshots, mode: { paused, persist }, @@ -76,7 +75,8 @@ function ButtonsContainer(): JSX.Element { )} {paused ? 'Unlock' : 'Lock'} - */} +
); diff --git a/src/app/containers/MainContainer.tsx b/src/app/containers/MainContainer.tsx index 27b171b49..05a2ddd76 100644 --- a/src/app/containers/MainContainer.tsx +++ b/src/app/containers/MainContainer.tsx @@ -2,11 +2,10 @@ import React, { useEffect, useState } from 'react'; import Split from 'react-split'; import ActionContainer from './ActionContainer'; -import StateContainer from './StateContainer'; -import StateContainer2 from './StateContainer2'; import TravelContainer from './TravelContainer'; import ButtonsContainer from './ButtonsContainer'; import ErrorContainer from './ErrorContainer'; +import StateContainer from './StateContainer'; import { addNewSnapshots, initialConnect, @@ -15,15 +14,17 @@ import { deleteTab, noDev, setCurrentLocation, + pause, } from '../actions/actions'; import { useStoreContext } from '../store'; function MainContainer(): any { const [store, dispatch] = useStoreContext(); - const { tabs, currentTab, port: currentPort } = store; + const { + tabs, currentTab, port: currentPort, split, + } = store; const [actionView, setActionView] = useState(true); // this function handles Time Jump sidebar view - const [splitView, setSplitView] = useState(false); const toggleActionContainer = () => { setActionView(!actionView); const toggleElem = document.querySelector('aside'); @@ -142,12 +143,55 @@ function MainContainer(): any { }; const snapshotDisplay = statelessCleaning(snapshotView); const hierarchyDisplay = statelessCleaning(hierarchy); - // body container scss file - // notes Split built in Styling you can give - // within its own component. - // scss file is limiting where State Container can g - // docs https://github.com/nathancahill/split/tree/master/packages/splitjs + function handleSplit(currentSplitMode: boolean): JSX.Element { + if (!currentSplitMode) { + return ( +
+ +
+ ); + } + return ( + + + + + ); + } + return (
@@ -156,42 +200,7 @@ function MainContainer(): any { setActionView={setActionView} toggleActionContainer={toggleActionContainer} /> - - - {/* */} - {snapshots.length ? ( - - ) : null} - {snapshots.length ? ( - - ) : null} - - + {snapshots.length ? (handleSplit(split)) : null}
@@ -199,4 +208,13 @@ function MainContainer(): any { ); } +// export default MainContainer; diff --git a/src/app/containers/StateContainer.tsx b/src/app/containers/StateContainer.tsx index bcc971b93..6590ababf 100644 --- a/src/app/containers/StateContainer.tsx +++ b/src/app/containers/StateContainer.tsx @@ -40,15 +40,13 @@ const StateContainer = (props: StateContainerProps): JSX.Element => { viewIndex, webMetrics, currLocation, - splitView, - setSplitView, // added snapshots, Rob 11/4 snapshots, } = props; return ( -
+
@@ -58,7 +56,7 @@ const StateContainer = (props: StateContainerProps): JSX.Element => { exact to="/" > - STATE + State { > Diff -
diff --git a/src/app/containers/StateContainer2.tsx b/src/app/containers/StateContainer2.tsx deleted file mode 100644 index 610dc6b2f..000000000 --- a/src/app/containers/StateContainer2.tsx +++ /dev/null @@ -1,97 +0,0 @@ -// @ts-nocheck -import React, { useState } from 'react'; -import { - MemoryRouter as Router, - Route, - NavLink, - Switch, -} from 'react-router-dom'; -import StateRoute from '../components/StateRoute'; -import DiffRoute from '../components/DiffRoute'; -interface StateContainerProps { - snapshot: Record< - number, - { - name?: string; - componentData?: Record; - state?: Record; - stateSnaphot?: Record; - children?: unknown[]; - AtomsRelationship?: any[]; - atomSelectors?: object; - atomsComponents?: object; - } - >; - toggleActionContainer?: any; - webMetrics?: object; - AtomsRelationship?: any[]; - hierarchy: Record; - snapshots?: []; - viewIndex?: number; - currLocation?: object; -} - -// eslint-disable-next-line react/prop-types -const StateContainer2 = (props: StateContainerProps): JSX.Element => { - const { - snapshot, - hierarchy, - snapshots, - viewIndex, - webMetrics, - currLocation, - splitView, - setSplitView, - // added snapshots, Rob 11/4 - snapshots, - } = props; - - return ( - -
-
-
-
- - TESTINGTESTING - - - Diff - - -
-
- - } - /> - ( - - )} - /> - -
- - ); -}; - -export default StateContainer2; diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index e20f141cd..6f6b21954 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -4,7 +4,7 @@ import * as types from '../constants/actionTypes.ts'; export default (state, action) => produce(state, draft => { const { - port, currentTab, currentTitle, tabs, + port, currentTab, currentTitle, split, tabs, } = draft; const { hierarchy, snapshots, mode, intervalId, viewIndex, sliderIndex, @@ -316,6 +316,10 @@ export default (state, action) => produce(state, draft => { } break; } + case types.TOGGLE_SPLIT: { + draft.split = !draft.split; + break; + } case types.SET_CURRENT_LOCATION: { const { payload } = action; const { currLocation } = payload[currentTab]; diff --git a/src/app/styles/base/_base.scss b/src/app/styles/base/_base.scss index 0ee1da20e..27b21cff1 100644 --- a/src/app/styles/base/_base.scss +++ b/src/app/styles/base/_base.scss @@ -20,7 +20,13 @@ body { grid-area: actions; transition: 0.5s; } -.state-container { +// .state-container { +// grid-area: states; +// } +.state-container-container { + grid-area: states; +} +.split { grid-area: states; } .buttons-container { diff --git a/src/app/styles/layout/_bodyContainer.scss b/src/app/styles/layout/_bodyContainer.scss index 7ea69eb75..e056958f5 100644 --- a/src/app/styles/layout/_bodyContainer.scss +++ b/src/app/styles/layout/_bodyContainer.scss @@ -13,7 +13,7 @@ /* if extension width is less than 500px, stack the body containers */ @media (max-width: 500px) { - .body-container { + .body-container1 { grid-template-rows: 3fr 5fr 1fr; grid-template-columns: auto; grid-template-areas: @@ -23,4 +23,3 @@ 'buttons'; } } -//inside of state ccontainer use flexbox? diff --git a/src/app/styles/layout/_mainContainer.scss b/src/app/styles/layout/_mainContainer.scss index 781c579da..a9a67ae90 100644 --- a/src/app/styles/layout/_mainContainer.scss +++ b/src/app/styles/layout/_mainContainer.scss @@ -5,3 +5,11 @@ overflow: hidden; } +.state-container-container{ + display: contents; +} + +.split { + display: flex; + overflow-y: auto; +} diff --git a/src/app/styles/layout/_stateContainer.scss b/src/app/styles/layout/_stateContainer.scss index 5efb5281c..d8e0a83f1 100644 --- a/src/app/styles/layout/_stateContainer.scss +++ b/src/app/styles/layout/_stateContainer.scss @@ -1,19 +1,11 @@ .state-container { - font-size: 12px; + font-size: 10px; overflow: auto; background-color: $brand-color; - display: flex; -} - -#componentMapContainer, -#historyContainer, -#atomsContainer { - overflow: hidden; -} -.state-container .state{ overflow-y: auto; } + .toggleAC { // color: #00dffc; color: $blue-brand; @@ -79,10 +71,10 @@ .state-container { .main-navbar-text { margin: 6px; - font-size: 14px; } .main-router-link { + font-size: 14px; height: 75%; width: 75px; display: flex; @@ -219,3 +211,8 @@ .bargraph-position { position: relative; } + +/* if state view is width is less than 500px, stack the body containers */ +// @media (max-width: 500px) { + +// } From 0c0b41bfe0bcefc52d30b7b30508492f25436915 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Wed, 23 Feb 2022 19:36:30 -0500 Subject: [PATCH 30/42] dinner --- src/app/components/ComponentMap.tsx | 1 + src/app/containers/ActionContainer.tsx | 2 +- src/app/containers/ButtonsContainer.tsx | 31 ++++++++++--------- src/app/containers/MainContainer.tsx | 14 ++------- src/app/styles/base/_base.scss | 3 -- .../styles/components/_actionComponent.scss | 14 +-------- src/app/styles/components/_buttons.scss | 7 +++-- src/app/styles/layout/_bodyContainer.scss | 6 ++-- 8 files changed, 30 insertions(+), 48 deletions(-) diff --git a/src/app/components/ComponentMap.tsx b/src/app/components/ComponentMap.tsx index edbf2bc7e..4b0f80dd6 100644 --- a/src/app/components/ComponentMap.tsx +++ b/src/app/components/ComponentMap.tsx @@ -141,6 +141,7 @@ export default function ComponentMap({ const scrollStyle = { minWidth: '60', maxWidth: '300', + minHeight: '20px', maxHeight: '200px', overflowY: 'scroll', overflowWrap: 'break-word', diff --git a/src/app/containers/ActionContainer.tsx b/src/app/containers/ActionContainer.tsx index de373d44e..cf29f81ed 100644 --- a/src/app/containers/ActionContainer.tsx +++ b/src/app/containers/ActionContainer.tsx @@ -205,7 +205,7 @@ function ActionContainer(props) {
{actionView ? ( -
+
+ {/* removing the UI for now Defunt perist feauture. See docs for more info */} {/* + +
); case 'RDT Error': diff --git a/src/app/styles/layout/_errorContainer.scss b/src/app/styles/layout/_errorContainer.scss index c0bbbac6c..e789d5ae0 100644 --- a/src/app/styles/layout/_errorContainer.scss +++ b/src/app/styles/layout/_errorContainer.scss @@ -60,17 +60,19 @@ } .launchContentButton { - padding: 3px; - outline: transparent; - color: white; - border-style: solid; - border-color: transparent; - border-radius: 3px; - cursor: pointer; - line-height: 1.5em; - font: 300 14px 'Roboto', sans-serif; - font-size: $button-text-size; - width: 120px; - background: $red-color-gradient; + background: $blue-brand; + color: $brand-color; + margin: 3px; + padding: 5px 10px; + border-radius: 5px; + border: 1px solid rgb(36, 37, 41); } + + .launchContentButton:hover { + background: $light-background-color; + } + + .launchContentButton:active { + box-shadow: 1px 1px 10px black; + } } From 7373009c6d3705229c605dabb4329a764394d7cf Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Wed, 23 Feb 2022 21:12:39 -0500 Subject: [PATCH 34/42] modfied Loader icons --- src/app/components/Loader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/components/Loader.tsx b/src/app/components/Loader.tsx index 47784f869..5035b5143 100644 --- a/src/app/components/Loader.tsx +++ b/src/app/components/Loader.tsx @@ -12,8 +12,8 @@ const override = css` // Displays the result of the check when loading is done const handleResult = (result: boolean): JSX.Element => (result - ? - : + ? + : ); // Returns the Loader component From aee234fd92bb1357ea0f8b5cdffd9e6c5aa634a1 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Wed, 23 Feb 2022 21:20:01 -0500 Subject: [PATCH 35/42] ts test --- src/app/components/Loader.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/components/Loader.tsx b/src/app/components/Loader.tsx index 5035b5143..52c31cfa5 100644 --- a/src/app/components/Loader.tsx +++ b/src/app/components/Loader.tsx @@ -1,4 +1,6 @@ /* eslint-disable react/prop-types */ + + import React from 'react'; import { css } from '@emotion/react'; import { ClipLoader } from 'react-spinners'; @@ -12,6 +14,7 @@ const override = css` // Displays the result of the check when loading is done const handleResult = (result: boolean): JSX.Element => (result + /* tslint:disable-next-line */ ? : ); From af3a79530e6935bd00e841d4272fe21a89dc2214 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Wed, 23 Feb 2022 21:24:36 -0500 Subject: [PATCH 36/42] ts test2 --- src/app/components/Loader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/Loader.tsx b/src/app/components/Loader.tsx index 52c31cfa5..af87ccf10 100644 --- a/src/app/components/Loader.tsx +++ b/src/app/components/Loader.tsx @@ -1,5 +1,6 @@ /* eslint-disable react/prop-types */ +// @ts-nocheck import React from 'react'; import { css } from '@emotion/react'; @@ -14,7 +15,6 @@ const override = css` // Displays the result of the check when loading is done const handleResult = (result: boolean): JSX.Element => (result - /* tslint:disable-next-line */ ? : ); From e85fc26a0f25fb5f06096f675e9997e6c46e8e93 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Thu, 24 Feb 2022 02:33:03 -0500 Subject: [PATCH 37/42] fixed tooltips in component map and history map, cleaned --- src/app/components/Action.tsx | 10 +-- src/app/components/ComponentMap.tsx | 61 +++++++++++------- src/app/components/History.tsx | 74 ++++++++-------------- src/app/containers/ActionContainer.tsx | 62 ++---------------- src/app/reducers/mainReducer.js | 2 +- src/app/styles/components/d3graph.css | 13 ++-- src/app/styles/layout/_stateContainer.scss | 31 +++++++++ src/app/styles/main.scss | 13 ---- 8 files changed, 109 insertions(+), 157 deletions(-) diff --git a/src/app/components/Action.tsx b/src/app/components/Action.tsx index 7d91a9b21..8aad53af4 100644 --- a/src/app/components/Action.tsx +++ b/src/app/components/Action.tsx @@ -5,7 +5,6 @@ import React from 'react'; import ReactHover, { Trigger, Hover } from 'react-hover'; import { changeView, changeSlider } from '../actions/actions'; -import snapshots from './snapshots'; /** * @template ActionProps Props for the action component @@ -25,7 +24,6 @@ interface ActionProps { viewIndex: number; isCurrIndex: boolean; handleOnkeyDown: (e: any, i: number) => any; - logChangedState: (index: number) => any; } /** @@ -57,7 +55,6 @@ const Action = (props: ActionProps): JSX.Element => { viewIndex, isCurrIndex, handleOnkeyDown, - logChangedState, } = props; /** @@ -117,7 +114,6 @@ const Action = (props: ActionProps): JSX.Element => {
sliderIndex ? { color: '#5f6369' } : {}}>
{`${displayName}: ${componentName !== 'nameless' ? componentName : ''} `} - {/* {`displayName: ${displayName}`} */}
- - {/*
-

{(logChangedState(index))}

-
*/} -
+
); diff --git a/src/app/components/ComponentMap.tsx b/src/app/components/ComponentMap.tsx index 4b0f80dd6..9256e758f 100644 --- a/src/app/components/ComponentMap.tsx +++ b/src/app/components/ComponentMap.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-unused-expressions */ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable no-restricted-syntax */ /* eslint-disable guard-for-in */ @@ -76,7 +77,7 @@ export default function ComponentMap({ const [linkType, setLinkType] = useState('diagonal'); const [stepPercent, setStepPercent] = useState(10); const [tooltip, setTooltip] = useState(false); - const [expanded, setExpanded] = useState(); + // const [expanded, setExpanded] = useState(); const [selectedNode, setSelectedNode] = useState('root'); // Declared this variable and assigned it to the useForceUpdate function that forces a state to change causing that component to re-render and display on the map @@ -155,23 +156,6 @@ export default function ComponentMap({ // places all nodes into a flat array const nodeList = []; - // if (exclude.includes(key) === true) { - // nestedObj[key] = 'react related'; - // } - // if (typeof data[key] === 'object' && exclude.includes(key) !== true) { - // nestedObj = makePropsPretty(data[key]); - // if (Array.isArray(nestedObj)) { - // try { - // if (nestedObj[0].$$typeof) { - // nestedObj = null; - // } else { - // nestedObj = nestedObj.forEach(e => makePropsPretty(e)); - // } - // } catch (error) { - // } - // } - // } - const makePropsPretty = data => { const propsFormat = []; const nestedObj = []; @@ -179,7 +163,7 @@ export default function ComponentMap({ if (data[key] !== 'reactFiber' && typeof data[key] !== 'object' && exclude.includes(key) !== true) { propsFormat.push(

{`${key}: ${data[key]}`} -

); +

); } else if (data[key] !== 'reactFiber' && typeof data[key] === 'object' && exclude.includes(key) !== true) { const result = makePropsPretty(data[key]); nestedObj.push(result); @@ -192,6 +176,35 @@ export default function ComponentMap({ return propsFormat; }; + const formatState = state => { + if (state === 'stateless') return ['stateless']; + + const result = []; + + const inner = arg => { + if (Array.isArray(arg)) { + result.push('['); + arg.forEach(e => { inner(e); }); + result.push('] '); + } else if ((typeof arg) === 'object') { + result.push('{ '); + Object.keys(arg).forEach((key, i, arr) => { + result.push(`${key}: `); + ((typeof arg[key]) === 'object') ? inner(arg[key]) : result.push(arg[key]); + if (i !== arr.length - 1) result.push(', '); + }); + result.push(' } '); + } else { + result.push(` ${arg}, `); + } + }; + + + inner(state); + console.log(result); + return result; + }; + const collectNodes = node => { nodeList.splice(0, nodeList.length); nodeList.push(node); @@ -299,7 +312,10 @@ export default function ComponentMap({ event, ); const tooltipObj = { ...node.data }; - if (typeof tooltipObj.state === 'object') tooltipObj.state = 'stateful'; + // console.log(node.data); + // if (typeof tooltipObj.state === 'object') { + // tooltipObj.state = JSON.stringify(tooltipObj.state); + // } showTooltip({ tooltipLeft: coords.x, tooltipTop: coords.y, @@ -420,9 +436,10 @@ export default function ComponentMap({ {formatRenderTime(tooltipData.componentData.actualDuration)} {' '}
-
+
State: - {tooltipData.state} + {formatState(tooltipData.state)} + {/* {tooltipData.state} */}
diff --git a/src/app/components/History.tsx b/src/app/components/History.tsx index 74a8177d3..dcb37abb8 100644 --- a/src/app/components/History.tsx +++ b/src/app/components/History.tsx @@ -1,32 +1,18 @@ /* eslint-disable react-hooks/exhaustive-deps */ // @ts-nocheck -import React, { Component, useEffect, useState } from 'react'; -// ReactHover does not refer to individual nodes within the history tab but rather can only wrap the entire SVG div -import ReactHover, { Trigger, Hover } from 'react-hover'; -// used in action.tsx to format the return data of findDiff into a react component, not needed since the return of findDiff in History.tsx is simple a divs html -import ReactHtmlParser from 'react-html-parser'; -// formatting findDiff return data to show the changes with green and red background colors, aligns with actions.tsx +import React, { useEffect } from 'react'; +// formatting findDiff return data to show the changes with colors, aligns with actions.tsx import { diff, formatters } from 'jsondiffpatch'; import * as d3 from 'd3'; -// legend key taken out before ReactimeX(v 10.0) -import LegendKey from './legend'; import { changeView, changeSlider } from '../actions/actions'; -//unused function definition, didnt break reactime when commented out -const filterHooks = (data: any[]) => { - if (data[0].children && data[0].state === 'stateless') { - return filterHooks(data[0].children); - } - return JSON.stringify(data[0].state); -}; - const defaultMargin = { top: 30, left: 30, right: 55, bottom: 70, }; // main function exported to StateRoute // below we destructure the props -function History(props: Record) { +function History(props: Record): JSX.Element { const { width: totalWidth, height: totalHeight, @@ -34,7 +20,6 @@ function History(props: Record) { hierarchy, dispatch, currLocation, - //snapshots array passed down from state route, needed for findDiff function snapshots, } = props; @@ -99,9 +84,9 @@ function History(props: Record) { .append('path') .attr('class', 'link') .attr('d', d => `M${d.x},${d.y - }C${d.x},${(d.y + d.parent.y) / 2 - } ${d.parent.x},${(d.y + d.parent.y) / 2 - } ${d.parent.x},${d.parent.y}`); + }C${d.x},${(d.y + d.parent.y) / 2 + } ${d.parent.x},${(d.y + d.parent.y) / 2 + } ${d.parent.x},${d.parent.y}`); const node = g.selectAll('.node') .data(d3root.descendants()) @@ -112,16 +97,15 @@ function History(props: Record) { dispatch(changeView(d.data.index)); dispatch(changeSlider(d.data.index)); }) - //added to display state change information to node tree + // added to display state change information to node tree .on('mouseover', d => { // created popup div and appended it to display div(returned in this function) // D3 doesn't utilize z-index for priority, rather decides on placement by order of rendering // needed to define the return div with a className to have a target to append to with the correct level of priority const div = d3.select('.display').append('div') .attr('class', 'tooltip') - .style('opacity', 1) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY) + 'px') + .style('left', `${d3.event.pageX}px`) + .style('top', `${d3.event.pageY}px`); d3.selectAll('.tooltip').html(findDiff(d.data.index)); }) .on('mouseout', d => { @@ -191,8 +175,25 @@ function History(props: Record) { return newObj; }; - const previousDisplay = statelessCleanning(snapshots[index - 1]); - const delta = diff(previousDisplay, snapshots[index]); + function findStateChangeObj(delta, changedState = []) { + if (!delta.children && !delta.state) { + return changedState; + } + if (delta.state && delta.state[0] !== 'stateless') { + changedState.push(delta.state); + } + if (!delta.children) { + return changedState; + } + Object.keys(delta.children).forEach(child => { + // if (isNaN(child) === false) { + changedState.push(...findStateChangeObj(delta.children[child])); + // } + }); + return changedState; + } + + const delta = diff(statelessCleanning(snapshots[index - 1]), statelessCleanning(snapshots[index])); const changedState = findStateChangeObj(delta); // figured out the formatting for hover, applying diff.csss const html = formatters.html.format(changedState[0]); @@ -201,29 +202,10 @@ function History(props: Record) { return html; } - function findStateChangeObj(delta, changedState = []) { - if (!delta.children && !delta.state) { - return changedState; - } - if (delta.state && delta.state[0] !== 'stateless') { - changedState.push(delta.state); - } - if (!delta.children) { - return changedState; - } - Object.keys(delta.children).forEach(child => { - // if (isNaN(child) === false) { - changedState.push(...findStateChangeObj(delta.children[child])); - // } - }); - return changedState; - } - // below we are rendering the LegendKey component and passing hierarchy as props // then rendering each node in History tab to render using D3, which will share area with LegendKey return (
- {/* */} { } }; -function ActionContainer(props) { +function ActionContainer(props): JSX.Element { const [{ tabs, currentTab }, dispatch] = useStoreContext(); const { currLocation, hierarchy, sliderIndex, viewIndex, snapshots, @@ -24,56 +21,6 @@ function ActionContainer(props) { let actionsArr = []; const hierarchyArr: any[] = []; - - function findDiff(index) { - const statelessCleaning = (obj: { - name?: string; - componentData?: object; - state?: string | any; - stateSnaphot?: object; - children?: any[]; - }) => { - const newObj = { ...obj }; - if (newObj.name === 'nameless') { - delete newObj.name; - } - if (newObj.componentData) { - delete newObj.componentData; - } - if (newObj.state === 'stateless') { - delete newObj.state; - } - if (newObj.stateSnaphot) { - newObj.stateSnaphot = statelessCleaning(obj.stateSnaphot); - } - if (newObj.children) { - newObj.children = []; - if (obj.children.length > 0) { - obj.children.forEach( - (element: { state?: object | string; children?: [] }) => { - if ( - element.state !== 'stateless' - || element.children.length > 0 - ) { - const clean = statelessCleaning(element); - newObj.children.push(clean); - } - }, - ); - } - } - // nathan test - return newObj; - }; - // displays stateful data - const previousDisplay = statelessCleaning(snapshots[index - 1]); - const delta = diff(previousDisplay, snapshots[index]); - const changedState = findStateChangeObj(delta); - const html = formatters.html.format(changedState[0]); - const output = ReactHtmlParser(html); - return output; - } - function findStateChangeObj(delta, changedState = []) { if (!delta.children && !delta.state) { return changedState; @@ -92,7 +39,7 @@ function ActionContainer(props) { return changedState; } - // function to traverse state from hierarchy and also getting information on display name and component name + // function to traverse state from hierarchy and also getting information on display name and component name const displayArray = (obj: { stateSnapshot: { children: any[] }; name: number; @@ -182,7 +129,6 @@ function ActionContainer(props) { dispatch={dispatch} sliderIndex={sliderIndex} handleOnkeyDown={handleOnKeyDown} - logChangedState={findDiff} viewIndex={viewIndex} isCurrIndex={isCurrIndex} /> @@ -212,7 +158,7 @@ function ActionContainer(props) { className="empty-button" onClick={() => { dispatch(emptySnapshots()); - // set slider back to zero + // set slider back to zero, visually resetSlider(); }} type="button" diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index 6f6b21954..1a8eff925 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -4,7 +4,7 @@ import * as types from '../constants/actionTypes.ts'; export default (state, action) => produce(state, draft => { const { - port, currentTab, currentTitle, split, tabs, + port, currentTab, tabs, } = draft; const { hierarchy, snapshots, mode, intervalId, viewIndex, sliderIndex, diff --git a/src/app/styles/components/d3graph.css b/src/app/styles/components/d3graph.css index 15f2174c2..9d4a9eb8c 100644 --- a/src/app/styles/components/d3graph.css +++ b/src/app/styles/components/d3graph.css @@ -30,18 +30,15 @@ body { div.tooltip { position: absolute; - /* text-align: center; */ - /* display: inline; */ - max-width: 150px; - /* overflow-wrap: break-word; */ padding: 0.5rem 1rem; - /* padding: 6px; */ color: white; - font-size: 12px; + z-index: 100; + font-size: 14px; font-family: 'Roboto', sans-serif; - background: #51565e; + background: rgb(17, 17, 17, 0.9); + box-shadow: rgb(33 33 33 / 20%) 0px 1px 2px; border-radius: 5px; - /* pointer-events: none; */ + max-width: 300px; } .d3-tip { diff --git a/src/app/styles/layout/_stateContainer.scss b/src/app/styles/layout/_stateContainer.scss index dcd528f94..105cad699 100644 --- a/src/app/styles/layout/_stateContainer.scss +++ b/src/app/styles/layout/_stateContainer.scss @@ -214,6 +214,37 @@ position: relative; } +// tool tip styles + + +.visx-tooltip{ + overflow-y: auto; + overflow-wrap: break-word; + pointer-events:all !important; +} +.props, .stateTip{ + margin-top: 3px; + line-height: 1; + height: 100%; + overflow-y: hidden; + max-height: 400px; +} +.props{ + margin-top: 3px; +} +.props p{ + line-height:1; + margin-top: 3px; + margin-bottom: 0px; +} +.stateTip p{ + line-height:1; + margin-top: 3px; + margin-bottom: 0px; +} +.historyToolTip{ + z-index: 2; +} /* if state view is width is less than 500px, stack the body containers */ // @media (max-width: 500px) { diff --git a/src/app/styles/main.scss b/src/app/styles/main.scss index 75deb171c..7866bc317 100644 --- a/src/app/styles/main.scss +++ b/src/app/styles/main.scss @@ -52,16 +52,3 @@ // diff component @import './components/diff'; - -.visx-tooltip{ - overflow-y: scroll; - overflow-wrap: break-word; - pointer-events:all !important; -} -.props{ - line-height: 1; - height:100%; -} -.props p{ - line-height:1; -} From 84ce79c0becfc298a9af54ab3c5776277faf4b8f Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Thu, 24 Feb 2022 04:10:59 -0500 Subject: [PATCH 38/42] solved clear bug, TODO remove all refrences to empty mode --- src/app/containers/ActionContainer.tsx | 18 -------- src/app/reducers/mainReducer.js | 60 +++++++++++++++++--------- src/extension/background.js | 21 +++------ 3 files changed, 44 insertions(+), 55 deletions(-) diff --git a/src/app/containers/ActionContainer.tsx b/src/app/containers/ActionContainer.tsx index 9f1c31f0d..9fa88dac0 100644 --- a/src/app/containers/ActionContainer.tsx +++ b/src/app/containers/ActionContainer.tsx @@ -21,24 +21,6 @@ function ActionContainer(props): JSX.Element { let actionsArr = []; const hierarchyArr: any[] = []; - function findStateChangeObj(delta, changedState = []) { - if (!delta.children && !delta.state) { - return changedState; - } - if (delta.state && delta.state[0] !== 'stateless') { - changedState.push(delta.state); - } - if (!delta.children) { - return changedState; - } - Object.keys(delta.children).forEach(child => { - // if (isNaN(child) === false) { - changedState.push(...findStateChangeObj(delta.children[child])); - // } - }); - return changedState; - } - // function to traverse state from hierarchy and also getting information on display name and component name const displayArray = (obj: { stateSnapshot: { children: any[] }; diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index 1a8eff925..7ef9dc27c 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -166,36 +166,54 @@ export default (state, action) => produce(state, draft => { case types.EMPTY: { port.postMessage({ action: 'emptySnap', tabId: currentTab }); tabs[currentTab].sliderIndex = 0; - tabs[currentTab].viewIndex = -1; + tabs[currentTab].viewIndex = 0; tabs[currentTab].playing = false; - // activates empty mode - tabs[currentTab].mode.empty = true; - // records snapshot of page initial state - tabs[currentTab].initialSnapshot.push(tabs[currentTab].snapshots[0]); - // resets snapshots to page last state recorded - // eslint-disable-next-line max-len - tabs[currentTab].snapshots = [ - tabs[currentTab].snapshots[tabs[currentTab].snapshots.length - 1], - ]; - // records hierarchy of page initial state - tabs[currentTab].initialHierarchy = { ...tabs[currentTab].hierarchy }; - tabs[currentTab].initialHierarchy.children = []; + const lastSnapshot = tabs[currentTab].snapshots[tabs[currentTab].snapshots.length - 1]; + // resets hierarchy to page last state recorded + tabs[currentTab].hierarchy.stateSnapshot = { ...lastSnapshot }; // resets hierarchy tabs[currentTab].hierarchy.children = []; - // resets hierarchy to page last state recorded - // eslint-disable-next-line prefer-destructuring - tabs[currentTab].hierarchy.stateSnapshot = tabs[currentTab].snapshots[0]; + // resets snapshots to page last state recorded + tabs[currentTab].snapshots = [lastSnapshot]; // resets currLocation to page last state recorded tabs[currentTab].currLocation = tabs[currentTab].hierarchy; - // resets index - tabs[currentTab].index = 0; - // resets currParent plus current state + tabs[currentTab].index = 1; tabs[currentTab].currParent = 1; - // resets currBranch tabs[currentTab].currBranch = 0; - // resets series saved status tabs[currentTab].seriesSavedStatus = false; break; + // port.postMessage({ action: 'emptySnap', tabId: currentTab }); + // tabs[currentTab].sliderIndex = 0; + // tabs[currentTab].viewIndex = -1; + // tabs[currentTab].playing = false; + // // activates empty mode + // tabs[currentTab].mode.empty = true; + // // records snapshot of page initial state + // tabs[currentTab].initialSnapshot.push(tabs[currentTab].snapshots[0]); + // // resets snapshots to page last state recorded + // // eslint-disable-next-line max-len + // tabs[currentTab].snapshots = [ + // tabs[currentTab].snapshots[tabs[currentTab].snapshots.length - 1], + // ]; + // // records hierarchy of page initial state + // tabs[currentTab].initialHierarchy = { ...tabs[currentTab].hierarchy }; + // tabs[currentTab].initialHierarchy.children = []; + // // resets hierarchy + // tabs[currentTab].hierarchy.children = []; + // // resets hierarchy to page last state recorded + // // eslint-disable-next-line prefer-destructuring + // tabs[currentTab].hierarchy.stateSnapshot = tabs[currentTab].snapshots[0]; + // // resets currLocation to page last state recorded + // tabs[currentTab].currLocation = tabs[currentTab].hierarchy; + // // resets index + // tabs[currentTab].index = 0; + // // resets currParent plus current state + // tabs[currentTab].currParent = 1; + // // resets currBranch + // tabs[currentTab].currBranch = 0; + // // resets series saved status + // tabs[currentTab].seriesSavedStatus = false; + // break; } case types.SET_PORT: { draft.port = action.payload; diff --git a/src/extension/background.js b/src/extension/background.js index 2fa09e208..7cb728908 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -44,10 +44,10 @@ function createTabObj(title) { reactDevToolsInstalled: false, targetPageisaReactApp: false, }, + // Note: Persist is a now defunct feature. Paused = Locked mode: { persist: false, paused: false, - empty: false, }, // stores web metrics calculated by the content script file webMetrics: {}, @@ -177,19 +177,10 @@ chrome.runtime.onConnect.addListener(port => { tabsObj[tabId].snapshots = payload; return true; case 'emptySnap': - // activates empty mode - tabsObj[tabId].mode.empty = true; - // records snapshot of page initial state - tabsObj[tabId].initialSnapshot.push(tabsObj[tabId].snapshots[0]); // reset snapshots to page last state recorded tabsObj[tabId].snapshots = [ tabsObj[tabId].snapshots[tabsObj[tabId].snapshots.length - 1], ]; - // records hierarchy of page initial state - tabsObj[tabId].initialHierarchy = { - ...tabsObj[tabId].hierarchy, - children: [], - }; // resets hierarchy tabsObj[tabId].hierarchy.children = []; // resets hierarchy to page last state recorded @@ -198,17 +189,15 @@ chrome.runtime.onConnect.addListener(port => { }; // resets currLocation to page last state recorded tabsObj[tabId].currLocation = tabsObj[tabId].hierarchy; - // resets index - tabsObj[tabId].index = 0; - // resets currParent plus current state - tabsObj[tabId].currParent = 1; - // resets currBranch + tabsObj[tabId].index = 1; + tabsObj[tabId].currParent = 0; tabsObj[tabId].currBranch = 0; return true; - // "Pause" is a deprecated feature from a previous Reactime version. + // Pause = lock on tab case 'setPause': tabsObj[tabId].mode.paused = payload; return true; + // persist is now depreacted case 'setPersist': tabsObj[tabId].mode.persist = payload; return true; From f7b0e2e1df46ae54e202271c59cb52f95181ff6c Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Thu, 24 Feb 2022 04:29:32 -0500 Subject: [PATCH 39/42] deep cleaned background, removed onReload case, done for now --- src/app/__tests__/mainReducer.test.tsx | 6 +--- src/app/reducers/mainReducer.js | 37 ++------------------ src/extension/background.js | 47 +------------------------- 3 files changed, 5 insertions(+), 85 deletions(-) diff --git a/src/app/__tests__/mainReducer.test.tsx b/src/app/__tests__/mainReducer.test.tsx index 4a2b5e8aa..4cb4ca72d 100644 --- a/src/app/__tests__/mainReducer.test.tsx +++ b/src/app/__tests__/mainReducer.test.tsx @@ -12,7 +12,6 @@ describe('mainReducer testing', () => { state = { tabs: { 87: { - initialSnapshot: [], snapshots: [1, 2, 3, 4], sliderIndex: 2, viewIndex: -1, @@ -24,7 +23,6 @@ describe('mainReducer testing', () => { intervalId: 87, playing: true, index: 3, - initialHierarchy: null, // should be a linked list with four nodes hierarchy: { index: 0, @@ -84,7 +82,6 @@ describe('mainReducer testing', () => { currLocation: 4, }, 75: { - initialSnapshot: [], snapshots: [1, 2, 3, 4], sliderIndex: 3, viewIndex: -1, @@ -95,7 +92,6 @@ describe('mainReducer testing', () => { }, intervalId: 75, playing: false, - initialHierarchy: null, // should be a linked list with four nodes hierarchy: { index: 0, @@ -213,7 +209,7 @@ describe('mainReducer testing', () => { describe('empty', () => { it('should empty snapshots except the first one', () => { expect(mainReducer(state, emptySnapshots()).tabs[currentTab].sliderIndex).toEqual(0); - expect(mainReducer(state, emptySnapshots()).tabs[currentTab].viewIndex).toEqual(-1); + expect(mainReducer(state, emptySnapshots()).tabs[currentTab].viewIndex).toEqual(0); expect(mainReducer(state, emptySnapshots()).tabs[currentTab].playing).toEqual(false); expect(mainReducer(state, emptySnapshots()).tabs[currentTab] .snapshots).toEqual([state.tabs[currentTab].snapshots[state.tabs[currentTab].snapshots.length - 1]]); diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index 7ef9dc27c..da88b38f9 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -164,6 +164,7 @@ export default (state, action) => produce(state, draft => { break; } case types.EMPTY: { + // send msg to background script port.postMessage({ action: 'emptySnap', tabId: currentTab }); tabs[currentTab].sliderIndex = 0; tabs[currentTab].viewIndex = 0; @@ -178,42 +179,10 @@ export default (state, action) => produce(state, draft => { // resets currLocation to page last state recorded tabs[currentTab].currLocation = tabs[currentTab].hierarchy; tabs[currentTab].index = 1; - tabs[currentTab].currParent = 1; - tabs[currentTab].currBranch = 0; + tabs[currentTab].currParent = 0; + tabs[currentTab].currBranch = 1; tabs[currentTab].seriesSavedStatus = false; break; - // port.postMessage({ action: 'emptySnap', tabId: currentTab }); - // tabs[currentTab].sliderIndex = 0; - // tabs[currentTab].viewIndex = -1; - // tabs[currentTab].playing = false; - // // activates empty mode - // tabs[currentTab].mode.empty = true; - // // records snapshot of page initial state - // tabs[currentTab].initialSnapshot.push(tabs[currentTab].snapshots[0]); - // // resets snapshots to page last state recorded - // // eslint-disable-next-line max-len - // tabs[currentTab].snapshots = [ - // tabs[currentTab].snapshots[tabs[currentTab].snapshots.length - 1], - // ]; - // // records hierarchy of page initial state - // tabs[currentTab].initialHierarchy = { ...tabs[currentTab].hierarchy }; - // tabs[currentTab].initialHierarchy.children = []; - // // resets hierarchy - // tabs[currentTab].hierarchy.children = []; - // // resets hierarchy to page last state recorded - // // eslint-disable-next-line prefer-destructuring - // tabs[currentTab].hierarchy.stateSnapshot = tabs[currentTab].snapshots[0]; - // // resets currLocation to page last state recorded - // tabs[currentTab].currLocation = tabs[currentTab].hierarchy; - // // resets index - // tabs[currentTab].index = 0; - // // resets currParent plus current state - // tabs[currentTab].currParent = 1; - // // resets currBranch - // tabs[currentTab].currBranch = 0; - // // resets series saved status - // tabs[currentTab].seriesSavedStatus = false; - // break; } case types.SET_PORT: { draft.port = action.payload; diff --git a/src/extension/background.js b/src/extension/background.js index 7cb728908..71d0a1085 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -24,8 +24,6 @@ function createTabObj(title) { title, // snapshots is an array of ALL state snapshots for stateful and stateless components the Reactime tab working on a specific user application snapshots: [], - // records initial snapshot to refresh page in case empty function is called - initialSnapshot: [], // index here is the tab index that shows total amount of state changes index: 0, //* this is our pointer so we know what the current state the user is checking (this accounts for time travel aka when user clicks jump on the UI) @@ -36,8 +34,6 @@ function createTabObj(title) { currBranch: 0, // inserting a new property to build out our hierarchy dataset for d3 hierarchy: null, - // records initial hierarchy to refresh page in case empty function is called - initialHierarchy: null, // status checks: Content Script launched, React Dev Tools installed, target is react app status: { contentScriptLaunched: true, @@ -191,7 +187,7 @@ chrome.runtime.onConnect.addListener(port => { tabsObj[tabId].currLocation = tabsObj[tabId].hierarchy; tabsObj[tabId].index = 1; tabsObj[tabId].currParent = 0; - tabsObj[tabId].currBranch = 0; + tabsObj[tabId].currBranch = 1; return true; // Pause = lock on tab case 'setPause': @@ -300,47 +296,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { }); break; } - case 'tabReload': { - tabsObj[tabId].mode.paused = false; - // dont remove snapshots if persisting - if (!persist) { - if (empty) { - // resets snapshots to page initial state recorded when emptied - tabsObj[tabId].snapshots = tabsObj[tabId].initialSnapshot; - // resets hierarchy to page initial state recorded when emptied - tabsObj[tabId].hierarchy = tabsObj[tabId].initialHierarchy; - } else { - // triggered with new tab opened - // resets snapshots to page initial state - tabsObj[tabId].snapshots.splice(1); - // checks if hierarchy before reset - if (tabsObj[tabId].hierarchy) { - // resets hierarchy to page initial state - tabsObj[tabId].hierarchy.children = []; - // resets currParent plus current state - tabsObj[tabId].currParent = 1; - } else { - // resets currParent - tabsObj[tabId].currParent = 0; - } - } - // resets currLocation to page initial state - tabsObj[tabId].currLocation = tabsObj[tabId].hierarchy; - // resets index - tabsObj[tabId].index = 0; - // resets currBranch - tabsObj[tabId].currBranch = 0; - - // send a message to devtools - portsArr.forEach(bg => bg.postMessage({ - action: 'initialConnectSnapshots', - payload: tabsObj, - })); - } - reloaded[tabId] = true; - - break; - } case 'recordSnap': { const sourceTab = tabId; tabsObj[tabId].webMetrics = metrics; From cdac49b2702c4c74c2cea14ff7b1e3b92b4614d4 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Thu, 24 Feb 2022 18:30:39 -0500 Subject: [PATCH 40/42] adding the abilty to close a component --- src/app/actions/actions.ts | 7 +++- src/app/components/ComponentMap.tsx | 50 +++++++++++++---------------- src/app/constants/actionTypes.ts | 1 + src/app/reducers/mainReducer.js | 13 ++++++++ src/backend/linkFiber.ts | 1 + src/backend/tree.ts | 10 ++---- 6 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/app/actions/actions.ts b/src/app/actions/actions.ts index 50dd63d68..267e61961 100644 --- a/src/app/actions/actions.ts +++ b/src/app/actions/actions.ts @@ -7,7 +7,7 @@ export const save = (tabsObj) => ({ }); export const deleteSeries = () => ({ type: types.DELETE_SERIES, -}) +}); export const toggleMode = (mode) => ({ type: types.TOGGLE_MODE, payload: mode, @@ -90,6 +90,11 @@ export const toggleSplit = () => ({ type: types.TOGGLE_SPLIT, }); +export const toggleExpanded = (name) => ({ + type: types.TOGGLE_EXPANDED, + payload: name, +}); + export const launchContentScript = (tab) => ({ type: types.LAUNCH_CONTENT, payload: tab, diff --git a/src/app/components/ComponentMap.tsx b/src/app/components/ComponentMap.tsx index 9256e758f..8292b9204 100644 --- a/src/app/components/ComponentMap.tsx +++ b/src/app/components/ComponentMap.tsx @@ -3,7 +3,7 @@ /* eslint-disable no-restricted-syntax */ /* eslint-disable guard-for-in */ // @ts-nocheck -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { Group } from '@visx/group'; import { hierarchy, Tree } from '@visx/hierarchy'; import { LinearGradient } from '@visx/gradient'; @@ -15,12 +15,10 @@ import { TooltipWithBounds, defaultStyles, } from '@visx/tooltip'; -import { isAbsolute } from 'path'; -import { nest } from 'jscharting'; import useForceUpdate from './useForceUpdate'; import LinkControls from './LinkControls'; import getLinkComponent from './getLinkComponent'; -import { onHover, onHoverExit } from '../actions/actions'; +import { toggleExpanded } from '../actions/actions'; import { useStoreContext } from '../store'; const exclude = ['childExpirationTime', 'staticContext', '_debugSource', 'actualDuration', 'actualStartTime', 'treeBaseDuration', '_debugID', '_debugIsCurrentlyTiming', 'selfBaseDuration', 'expirationTime', 'effectTag', 'alternate', '_owner', '_store', 'get key', 'ref', '_self', '_source', 'firstBaseUpdate', 'updateQueue', 'lastBaseUpdate', 'shared', 'responders', 'pending', 'lanes', 'childLanes', 'effects', 'memoizedState', 'pendingProps', 'lastEffect', 'firstEffect', 'tag', 'baseState', 'baseQueue', 'dependencies', 'Consumer', 'context', '_currentRenderer', '_currentRenderer2', 'mode', 'flags', 'nextEffect', 'sibling', 'create', 'deps', 'next', 'destroy', 'parentSub', 'child', 'key', 'return', 'children', '$$typeof', '_threadCount', '_calculateChangedBits', '_currentValue', '_currentValue2', 'Provider', '_context', 'stateNode', 'elementType', 'type']; @@ -69,7 +67,7 @@ export default function ComponentMap({ const [{ tabs, currentTab }, dispatch] = useStoreContext(); // This is where we select the last object in the snapshots array from props to allow hierarchy to parse the data for render on the component map per hierarchy layout specifications. const lastNode = snapshots.length - 1; - const data: {} = snapshots[lastNode]; + const data: Record = snapshots[lastNode]; // importing custom hooks for the selection tabs. const [layout, setLayout] = useState('cartesian'); @@ -77,10 +75,10 @@ export default function ComponentMap({ const [linkType, setLinkType] = useState('diagonal'); const [stepPercent, setStepPercent] = useState(10); const [tooltip, setTooltip] = useState(false); - // const [expanded, setExpanded] = useState(); const [selectedNode, setSelectedNode] = useState('root'); + const [store, dispatch] = useStoreContext(); - // Declared this variable and assigned it to the useForceUpdate function that forces a state to change causing that component to re-render and display on the map + // Forces React to Rerender const forceUpdate = useForceUpdate(); // setting the margins for the Map to render in the tab window. @@ -136,7 +134,7 @@ export default function ComponentMap({ lineHeight: '18px', fontFamily: 'Roboto', zIndex: 100, - 'pointer-events': 'all !important', + pointerEvents: 'all !important', }; const scrollStyle = { @@ -153,10 +151,7 @@ export default function ComponentMap({ return `${time} ms `; }; - // places all nodes into a flat array - const nodeList = []; - - const makePropsPretty = data => { + const formatProps = data => { const propsFormat = []; const nestedObj = []; for (const key in data) { @@ -165,7 +160,7 @@ export default function ComponentMap({ {`${key}: ${data[key]}`}

); } else if (data[key] !== 'reactFiber' && typeof data[key] === 'object' && exclude.includes(key) !== true) { - const result = makePropsPretty(data[key]); + const result = formatProps(data[key]); nestedObj.push(result); } } @@ -180,7 +175,6 @@ export default function ComponentMap({ if (state === 'stateless') return ['stateless']; const result = []; - const inner = arg => { if (Array.isArray(arg)) { result.push('['); @@ -198,13 +192,14 @@ export default function ComponentMap({ result.push(` ${arg}, `); } }; - - inner(state); - console.log(result); + return result; }; + // places all nodes into a flat array + const nodeList = []; + const collectNodes = node => { nodeList.splice(0, nodeList.length); nodeList.push(node); @@ -262,7 +257,9 @@ export default function ComponentMap({ /> (d.isExpanded ? null : d.children))} + root={hierarchy(startNode || data, d => { + return d.componentData?.isExpanded ? d.children : null; + })} size={[sizeWidth, sizeHeight]} separation={(a, b) => (a.parent === b.parent ? 1 : 0.5) / a.depth} > @@ -305,17 +302,12 @@ export default function ComponentMap({ // mousing controls & Tooltip display logic const handleMouseAndClickOver = event => { - // looks like onHover isnt working? - // () => dispatch(onHover(node.data.rtid)); const coords = localPoint( event.target.ownerSVGElement, event, ); const tooltipObj = { ...node.data }; - // console.log(node.data); - // if (typeof tooltipObj.state === 'object') { - // tooltipObj.state = JSON.stringify(tooltipObj.state); - // } + showTooltip({ tooltipLeft: coords.x, tooltipTop: coords.y, @@ -331,7 +323,9 @@ export default function ComponentMap({ fill="url('#links-gradient')" stroke="#ff6569" onClick={() => { - node.data.isExpanded = !node.data.isExpanded; + console.log('on click IN CIRCLE', node); + // node.data.componentData.isExpanded = !node.data.componentData.isExpanded; + console.log('on click IN CIRCLE', node.data.componentData.isExpanded); forceUpdate(); }} /> @@ -363,7 +357,8 @@ export default function ComponentMap({ // test feature // onClick = {handleMouseAndClickOver} onClick={() => { - node.data.isExpanded = !node.data.isExpanded; + dispatch(toggleExpanded(node.data.name)); + console.log('on click', node.data.componentData.isExpanded); hideTooltip(); setTooltip(false); forceUpdate(); @@ -439,12 +434,11 @@ export default function ComponentMap({
State: {formatState(tooltipData.state)} - {/* {tooltipData.state} */}
Props: - {makePropsPretty(tooltipData.componentData.props)} + {formatProps(tooltipData.componentData.props)}
diff --git a/src/app/constants/actionTypes.ts b/src/app/constants/actionTypes.ts index 3b5f62d8a..54115e053 100644 --- a/src/app/constants/actionTypes.ts +++ b/src/app/constants/actionTypes.ts @@ -14,6 +14,7 @@ export const SET_TAB = 'SET_TAB'; export const DELETE_TAB = 'DELETE_TAB'; export const NO_DEV = 'NO_DEV'; export const TOGGLE_SPLIT = 'TOGGLE_SPLIT'; +export const TOGGLE_EXPANDED = 'TOGGLE_EXPANDED'; export const LAUNCH_CONTENT = 'LAUNCH_CONTENT'; export const SLIDER_ZERO = 'SLIDER_ZERO'; export const ON_HOVER = 'ON_HOVER'; diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index da88b38f9..b4f0c6703 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -307,6 +307,19 @@ export default (state, action) => produce(state, draft => { draft.split = !draft.split; break; } + case types.TOGGLE_EXPANDED: { + const name = action.payload; + const currentSnapshot = tabs[currentTab].snapshots[tabs[currentTab].sliderIndex]; + console.log('in main reducer: payload', name); + console.log('in main reducer: tabs[currentTab].snapshots[index]', currentSnapshot); + // recursive check through all to see if any in the hierarchy share the name + Object.keys(currentSnapshot).forEach(e => { + console.log(e); + console.log(currentSnapshot[e]); + if (e === name) { console.log('true'); } + }); + break; + } case types.SET_CURRENT_LOCATION: { const { payload } = action; const { currLocation } = payload[currentTab]; diff --git a/src/backend/linkFiber.ts b/src/backend/linkFiber.ts index 4ef30f096..00b47ed6d 100644 --- a/src/backend/linkFiber.ts +++ b/src/backend/linkFiber.ts @@ -106,6 +106,7 @@ function sendSnapshot(snap: Snapshot, mode: Mode): void { // this postMessage will be sending the most up-to-date snapshot of the current React Fiber Tree // the postMessage action will be received on the content script to later update the tabsObj // this will fire off everytime there is a change in test application + console.log('payload in backend', payload); window.postMessage( { action: 'recordSnap', diff --git a/src/backend/tree.ts b/src/backend/tree.ts index 89aa88e8b..48e94aa2d 100644 --- a/src/backend/tree.ts +++ b/src/backend/tree.ts @@ -44,7 +44,8 @@ class Tree { name: string; componentData: { - props:{} + props: {}, + isExpanded: boolean, }; children: (Tree | string)[]; @@ -67,15 +68,10 @@ class Tree { // If not, create the new component and also a new key: value pair in 'componentNames' with the component's name as the key and 0 as its value // EXAMPLE OF COMPONENTNAMES OBJECT: {editableInput: 1, Provider: 0, etc} - // Empty names: - // If string, rtid without 'fromLinkFiber" - // If object - // If null - constructor(state: string | {}, name = 'nameless', componentData: {} = {}, rtid: any = null, recoilDomNode: any = null, string: any = null) { this.state = state === 'root' ? 'root' : serializeState(state); this.name = name; - this.componentData = componentData ? JSON.parse(JSON.stringify(componentData)) : {}; + this.componentData = componentData ? { isExpanded: true, ...JSON.parse(JSON.stringify(componentData)) } : { isExpanded: true }; this.children = []; this.parent = null; // ref to parent so we can add siblings this.rtid = rtid; From e6d4841f109f097ae2ec109dca1ab07423f84a0e Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Sat, 26 Feb 2022 17:15:27 -0500 Subject: [PATCH 41/42] fixed component map expansion UI issue --- package.json | 4 + src/app/actions/actions.ts | 4 +- src/app/components/ComponentMap.tsx | 58 ++------ src/app/components/StateRoute.tsx | 10 +- src/app/reducers/mainReducer.js | 48 +++++-- src/backend/tree.ts | 8 +- src/extension/background.js | 1 - yarn.lock | 211 +++++++++++++++++++++------- 8 files changed, 219 insertions(+), 125 deletions(-) diff --git a/package.json b/package.json index 3448c7663..eeffe0772 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "@types/chrome": "^0.0.119", "@types/d3-scale-chromatic": "^2.0.0", "@types/jest": "^26.0.4", + "@types/lodash.isequal": "^4.5.5", "@types/node": "^12.19.6", "@typescript-eslint/eslint-plugin": "^3.6.1", "@typescript-eslint/parser": "^3.6.1", @@ -162,6 +163,8 @@ "jest-runner": "^26.1.0", "jscharting": "^3.0.2", "jsondiffpatch": "^0.3.11", + "lodash": "^4.17.21", + "lodash.isequal": "^4.5.0", "prop-types": "^15.7.2", "rc-slider": "^8.7.1", "rc-tooltip": "^3.7.3", @@ -176,6 +179,7 @@ "react-spinners": "^0.11.0", "react-split": "^2.0.14", "recoil": "0.0.10", + "util": "^0.12.4", "web-vitals": "^1.1.0" } } diff --git a/src/app/actions/actions.ts b/src/app/actions/actions.ts index 267e61961..3a589ec72 100644 --- a/src/app/actions/actions.ts +++ b/src/app/actions/actions.ts @@ -90,9 +90,9 @@ export const toggleSplit = () => ({ type: types.TOGGLE_SPLIT, }); -export const toggleExpanded = (name) => ({ +export const toggleExpanded = (node) => ({ type: types.TOGGLE_EXPANDED, - payload: name, + payload: node, }); export const launchContentScript = (tab) => ({ diff --git a/src/app/components/ComponentMap.tsx b/src/app/components/ComponentMap.tsx index 8292b9204..fcb0909ca 100644 --- a/src/app/components/ComponentMap.tsx +++ b/src/app/components/ComponentMap.tsx @@ -1,3 +1,5 @@ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable no-nested-ternary */ /* eslint-disable no-unused-expressions */ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable no-restricted-syntax */ @@ -15,7 +17,6 @@ import { TooltipWithBounds, defaultStyles, } from '@visx/tooltip'; -import useForceUpdate from './useForceUpdate'; import LinkControls from './LinkControls'; import getLinkComponent from './getLinkComponent'; import { toggleExpanded } from '../actions/actions'; @@ -54,7 +55,7 @@ export type LinkTypesProps = { width: number; height: number; margin?: { top: number; right: number; bottom: number; left: number }; - snapshots: []; + snapshots: Record; }; export default function ComponentMap({ @@ -62,13 +63,8 @@ export default function ComponentMap({ width: totalWidth, height: totalHeight, margin = defaultMargin, - snapshots, -}: LinkTypesProps) { - const [{ tabs, currentTab }, dispatch] = useStoreContext(); - // This is where we select the last object in the snapshots array from props to allow hierarchy to parse the data for render on the component map per hierarchy layout specifications. - const lastNode = snapshots.length - 1; - const data: Record = snapshots[lastNode]; - + currentSnapshot, +}: LinkTypesProps): JSX.Element { // importing custom hooks for the selection tabs. const [layout, setLayout] = useState('cartesian'); const [orientation, setOrientation] = useState('horizontal'); @@ -76,10 +72,9 @@ export default function ComponentMap({ const [stepPercent, setStepPercent] = useState(10); const [tooltip, setTooltip] = useState(false); const [selectedNode, setSelectedNode] = useState('root'); - const [store, dispatch] = useStoreContext(); + const [{ tabs, currentTab }, dispatch] = useStoreContext(); - // Forces React to Rerender - const forceUpdate = useForceUpdate(); + console.log(currentSnapshot); // setting the margins for the Map to render in the tab window. const innerWidth = totalWidth - margin.left - margin.right; @@ -212,7 +207,7 @@ export default function ComponentMap({ } } }; - collectNodes(snapshots[lastNode]); + collectNodes(currentSnapshot); // find the node that has been selected and use it as the root const startNode = null; @@ -234,7 +229,7 @@ export default function ComponentMap({ orientation={orientation} linkType={linkType} stepPercent={stepPercent} - snapShots={snapshots[lastNode]} + snapShots={currentSnapshot} selectedNode={selectedNode} setLayout={setLayout} setOrientation={setOrientation} @@ -257,9 +252,7 @@ export default function ComponentMap({ /> { - return d.componentData?.isExpanded ? d.children : null; - })} + root={hierarchy(startNode || data, d => (d.isExpanded ? d.children : null))} size={[sizeWidth, sizeHeight]} separation={(a, b) => (a.parent === b.parent ? 1 : 0.5) / a.depth} > @@ -323,10 +316,9 @@ export default function ComponentMap({ fill="url('#links-gradient')" stroke="#ff6569" onClick={() => { - console.log('on click IN CIRCLE', node); - // node.data.componentData.isExpanded = !node.data.componentData.isExpanded; - console.log('on click IN CIRCLE', node.data.componentData.isExpanded); - forceUpdate(); + dispatch(toggleExpanded(node.data)); + hideTooltip(); + setTooltip(false); }} /> )} @@ -337,46 +329,26 @@ export default function ComponentMap({ width={width} y={-height / 2} x={-width / 2} - // node.children = if node has children fill={node.children ? '#161521' : '#62d6fb'} - // node.data.isExpanded = if node is collapsed - // stroke={(node.data.isExpanded && node.child) ? '#95fb62' : '#a69ff5'} => node.child is gone when clicked, even if it actually has children. Maybe better call node.children => node.leaf stroke={(node.data.isExpanded && node.data.children.length > 0) ? '#ff6569' : '#4D4D4D'} strokeWidth={1.5} - // strokeDasharray={node.children ? '0' : '2,2'} strokeOpacity="1" rx={node.children ? 4 : 10} - // double clicking the component expands or collapses the child components - // onDoubleClick={() => { - // node.data.isExpanded = !node.data.isExpanded; - // hideTooltip(); - // setTooltip(false); - // forceUpdate(); - // }} - // Tooltip event handlers - // test feature - // onClick = {handleMouseAndClickOver} onClick={() => { - dispatch(toggleExpanded(node.data.name)); - console.log('on click', node.data.componentData.isExpanded); + dispatch(toggleExpanded(node.data)); hideTooltip(); setTooltip(false); - forceUpdate(); }} onMouseOver={event => { setTooltip(true); handleMouseAndClickOver(event); }} - // paired with onmouseOver listener, this produces a hover over effect for the component Tooltip + // with onmouseOver, this produces a hover over effect for the Tooltip onMouseOut={() => { hideTooltip(); setTooltip(false); }} - - // onMouseEnter={() => dispatch(onHover(node.data.rtid))} - // onMouseLeave={() => dispatch(onHoverExit(node.data.rtid))} - // eslint-disable-next-line react/jsx-curly-newline /> )} {/* Display text inside of each component node */} diff --git a/src/app/components/StateRoute.tsx b/src/app/components/StateRoute.tsx index 8996ea26b..ae8f60eb7 100644 --- a/src/app/components/StateRoute.tsx +++ b/src/app/components/StateRoute.tsx @@ -51,20 +51,14 @@ const StateRoute = (props: StateRouteProps) => { const { hierarchy, sliderIndex, viewIndex } = tabs[currentTab]; const isRecoil = !!snapshot.atomsComponents; - const [noRenderData, setNoRenderData] = useState(false); - // component map zoom state - const [{ x, y, k }, setZoomState]: any = useState({ - x: 150, - y: 250, - k: 1, - }); // Map const renderComponentMap = () => { if (hierarchy) { return ( {({ width, height }) => ( - + // eslint-disable-next-line react/prop-types + )} ); diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index b4f0c6703..3d2819874 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -1,5 +1,5 @@ import { produce } from 'immer'; - +import _ from 'lodash'; import * as types from '../constants/actionTypes.ts'; export default (state, action) => produce(state, draft => { @@ -254,9 +254,19 @@ export default (state, action) => produce(state, draft => { if (!payload[tab]) { delete tabs[tab]; } else { + // maintain isExpaned prop from old stateSnapshot to preserve componentMap expansion + const persistIsExpanded = (newNode, oldNode) => { + newNode.isExpanded = oldNode ? oldNode.isExpanded : true; + if (newNode.children) { + newNode.children.forEach((child, i) => { + persistIsExpanded(child, oldNode?.children[i]); + }); + } + }; + persistIsExpanded(payload[tab].currLocation.stateSnapshot, tabs[tab].currLocation.stateSnapshot); + const { snapshots: newSnaps } = payload[tab]; tabs[tab] = { - ...tabs[tab], ...payload[tab], sliderIndex: newSnaps.length - 1, seriesSavedStatus: false, @@ -308,22 +318,32 @@ export default (state, action) => produce(state, draft => { break; } case types.TOGGLE_EXPANDED: { - const name = action.payload; - const currentSnapshot = tabs[currentTab].snapshots[tabs[currentTab].sliderIndex]; - console.log('in main reducer: payload', name); - console.log('in main reducer: tabs[currentTab].snapshots[index]', currentSnapshot); - // recursive check through all to see if any in the hierarchy share the name - Object.keys(currentSnapshot).forEach(e => { - console.log(e); - console.log(currentSnapshot[e]); - if (e === name) { console.log('true'); } - }); + // find correct node from currLocation and toggle isExpanded + const checkChildren = node => { + if (_.isEqual(node, action.payload)) { + node.isExpanded = !node.isExpanded; + } + if (node.children) { + node.children.forEach(child => { + checkChildren(child); + }); + } + }; + checkChildren(tabs[currentTab].currLocation.stateSnapshot); break; } case types.SET_CURRENT_LOCATION: { const { payload } = action; - const { currLocation } = payload[currentTab]; - tabs[currentTab].currLocation = currLocation; + const persistIsExpanded = (newNode, oldNode) => { + newNode.isExpanded = oldNode ? oldNode.isExpanded : true; + if (newNode.children) { + newNode.children.forEach((child, i) => { + persistIsExpanded(child, oldNode?.children[i]); + }); + } + }; + persistIsExpanded(payload[currentTab].currLocation.stateSnapshot, tabs[currentTab].currLocation.stateSnapshot); + tabs[currentTab].currLocation = payload[currentTab].currLocation; break; } default: diff --git a/src/backend/tree.ts b/src/backend/tree.ts index 48e94aa2d..4dc9604cb 100644 --- a/src/backend/tree.ts +++ b/src/backend/tree.ts @@ -45,12 +45,13 @@ class Tree { componentData: { props: {}, - isExpanded: boolean, }; children: (Tree | string)[]; - parent: Tree + parent: Tree; + + isExpanded: boolean; atomsComponents: any; @@ -71,9 +72,10 @@ class Tree { constructor(state: string | {}, name = 'nameless', componentData: {} = {}, rtid: any = null, recoilDomNode: any = null, string: any = null) { this.state = state === 'root' ? 'root' : serializeState(state); this.name = name; - this.componentData = componentData ? { isExpanded: true, ...JSON.parse(JSON.stringify(componentData)) } : { isExpanded: true }; + this.componentData = componentData ? { ...JSON.parse(JSON.stringify(componentData)) } : { }; this.children = []; this.parent = null; // ref to parent so we can add siblings + this.isExpanded = true; this.rtid = rtid; this.recoilDomNode = recoilDomNode; } diff --git a/src/extension/background.js b/src/extension/background.js index 71d0a1085..53536ff69 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -251,7 +251,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { tabsObj[tabId] = createTabObj(tabTitle); } - const { persist, empty } = tabsObj[tabId].mode; switch (action) { case 'jumpToSnap': { changeCurrLocation(tabsObj[tabId], tabsObj[tabId].hierarchy, index, name); diff --git a/yarn.lock b/yarn.lock index be0a7b5b3..cfc32264a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1631,7 +1631,14 @@ "resolved" "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" "version" "0.0.29" -"@types/lodash@^4.14.146", "@types/lodash@^4.14.160": +"@types/lodash.isequal@^4.5.5": + "integrity" "sha512-4IKbinG7MGP131wRfceK6W4E/Qt3qssEFLF30LnJbjYiSfHGGRU/Io8YxXrZX109ir+iDETC8hw8QsDijukUVg==" + "resolved" "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.5.tgz" + "version" "4.5.5" + dependencies: + "@types/lodash" "*" + +"@types/lodash@*", "@types/lodash@^4.14.146", "@types/lodash@^4.14.160": "integrity" "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==" "resolved" "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz" "version" "4.14.168" @@ -2483,6 +2490,11 @@ "resolved" "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz" "version" "2.1.2" +"available-typed-arrays@^1.0.5": + "integrity" "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + "resolved" "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" + "version" "1.0.5" + "aws-sign2@~0.7.0": "integrity" "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" "resolved" "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" @@ -4373,27 +4385,31 @@ dependencies: "is-arrayish" "^0.2.1" -"es-abstract@^1.17.0", "es-abstract@^1.17.0-next.1", "es-abstract@^1.17.4", "es-abstract@^1.17.5", "es-abstract@^1.18.0-next.2": - "integrity" "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==" - "resolved" "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz" - "version" "1.18.0" +"es-abstract@^1.17.0", "es-abstract@^1.17.0-next.1", "es-abstract@^1.17.4", "es-abstract@^1.17.5", "es-abstract@^1.18.0-next.2", "es-abstract@^1.18.5": + "integrity" "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==" + "resolved" "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz" + "version" "1.19.1" dependencies: "call-bind" "^1.0.2" "es-to-primitive" "^1.2.1" "function-bind" "^1.1.1" "get-intrinsic" "^1.1.1" + "get-symbol-description" "^1.0.0" "has" "^1.0.3" "has-symbols" "^1.0.2" - "is-callable" "^1.2.3" + "internal-slot" "^1.0.3" + "is-callable" "^1.2.4" "is-negative-zero" "^2.0.1" - "is-regex" "^1.1.2" - "is-string" "^1.0.5" - "object-inspect" "^1.9.0" + "is-regex" "^1.1.4" + "is-shared-array-buffer" "^1.0.1" + "is-string" "^1.0.7" + "is-weakref" "^1.0.1" + "object-inspect" "^1.11.0" "object-keys" "^1.1.1" "object.assign" "^4.1.2" "string.prototype.trimend" "^1.0.4" "string.prototype.trimstart" "^1.0.4" - "unbox-primitive" "^1.0.0" + "unbox-primitive" "^1.0.1" "es-to-primitive@^1.2.1": "integrity" "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==" @@ -5010,6 +5026,11 @@ "resolved" "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz" "version" "1.0.2" +"foreach@^2.0.5": + "integrity" "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + "resolved" "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz" + "version" "2.0.5" + "forever-agent@~0.6.1": "integrity" "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" "resolved" "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" @@ -5118,7 +5139,7 @@ "resolved" "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz" "version" "2.0.0" -"get-intrinsic@^1.0.2", "get-intrinsic@^1.1.1": +"get-intrinsic@^1.0.2", "get-intrinsic@^1.1.0", "get-intrinsic@^1.1.1": "integrity" "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==" "resolved" "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" "version" "1.1.1" @@ -5146,6 +5167,14 @@ dependencies: "pump" "^3.0.0" +"get-symbol-description@^1.0.0": + "integrity" "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==" + "resolved" "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "call-bind" "^1.0.2" + "get-intrinsic" "^1.1.1" + "get-value@^2.0.3", "get-value@^2.0.6": "integrity" "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" "resolved" "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz" @@ -5268,7 +5297,7 @@ "ajv" "^6.5.5" "har-schema" "^2.0.0" -"has-bigints@^1.0.0": +"has-bigints@^1.0.1": "integrity" "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" "resolved" "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz" "version" "1.0.1" @@ -5283,11 +5312,18 @@ "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" "version" "4.0.0" -"has-symbols@^1.0.0", "has-symbols@^1.0.1", "has-symbols@^1.0.2": +"has-symbols@^1.0.1", "has-symbols@^1.0.2": "integrity" "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" "resolved" "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz" "version" "1.0.2" +"has-tostringtag@^1.0.0": + "integrity" "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==" + "resolved" "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "has-symbols" "^1.0.2" + "has-value@^0.3.1": "integrity" "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=" "resolved" "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz" @@ -5602,14 +5638,14 @@ "strip-ansi" "^6.0.0" "through" "^2.3.6" -"internal-slot@^1.0.2": - "integrity" "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==" - "resolved" "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz" - "version" "1.0.2" +"internal-slot@^1.0.2", "internal-slot@^1.0.3": + "integrity" "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==" + "resolved" "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz" + "version" "1.0.3" dependencies: - "es-abstract" "^1.17.0-next.1" + "get-intrinsic" "^1.1.0" "has" "^1.0.3" - "side-channel" "^1.0.2" + "side-channel" "^1.0.4" "internmap@^1.0.0": "integrity" "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" @@ -5645,15 +5681,25 @@ dependencies: "kind-of" "^6.0.0" +"is-arguments@^1.0.4": + "integrity" "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==" + "resolved" "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz" + "version" "1.1.1" + dependencies: + "call-bind" "^1.0.2" + "has-tostringtag" "^1.0.0" + "is-arrayish@^0.2.1": "integrity" "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" "resolved" "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" "version" "0.2.1" "is-bigint@^1.0.1": - "integrity" "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==" - "resolved" "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz" - "version" "1.0.1" + "integrity" "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==" + "resolved" "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" + "version" "1.0.4" + dependencies: + "has-bigints" "^1.0.1" "is-binary-path@^1.0.0": "integrity" "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=" @@ -5681,10 +5727,10 @@ "resolved" "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" "version" "1.1.6" -"is-callable@^1.1.4", "is-callable@^1.1.5", "is-callable@^1.2.3": - "integrity" "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" - "resolved" "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz" - "version" "1.2.3" +"is-callable@^1.1.4", "is-callable@^1.1.5", "is-callable@^1.2.4": + "integrity" "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==" + "resolved" "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz" + "version" "1.2.4" "is-ci@^2.0.0": "integrity" "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==" @@ -5779,6 +5825,13 @@ "resolved" "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" "version" "2.1.0" +"is-generator-function@^1.0.7": + "integrity" "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==" + "resolved" "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz" + "version" "1.0.10" + dependencies: + "has-tostringtag" "^1.0.0" + "is-glob@^3.1.0": "integrity" "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=" "resolved" "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz" @@ -5832,13 +5885,18 @@ "resolved" "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz" "version" "1.0.0" -"is-regex@^1.0.5", "is-regex@^1.1.0", "is-regex@^1.1.2": - "integrity" "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==" - "resolved" "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz" - "version" "1.1.2" +"is-regex@^1.0.5", "is-regex@^1.1.0", "is-regex@^1.1.4": + "integrity" "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==" + "resolved" "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" + "version" "1.1.4" dependencies: "call-bind" "^1.0.2" - "has-symbols" "^1.0.1" + "has-tostringtag" "^1.0.0" + +"is-shared-array-buffer@^1.0.1": + "integrity" "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==" + "resolved" "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz" + "version" "1.0.1" "is-stream@^1.1.0": "integrity" "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" @@ -5850,10 +5908,12 @@ "resolved" "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz" "version" "2.0.0" -"is-string@^1.0.5": - "integrity" "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" - "resolved" "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz" - "version" "1.0.5" +"is-string@^1.0.5", "is-string@^1.0.7": + "integrity" "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==" + "resolved" "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" + "version" "1.0.7" + dependencies: + "has-tostringtag" "^1.0.0" "is-subset@^0.1.1": "integrity" "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" @@ -5867,11 +5927,29 @@ dependencies: "has-symbols" "^1.0.1" +"is-typed-array@^1.1.3", "is-typed-array@^1.1.7": + "integrity" "sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==" + "resolved" "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz" + "version" "1.1.8" + dependencies: + "available-typed-arrays" "^1.0.5" + "call-bind" "^1.0.2" + "es-abstract" "^1.18.5" + "foreach" "^2.0.5" + "has-tostringtag" "^1.0.0" + "is-typedarray@^1.0.0", "is-typedarray@~1.0.0": "integrity" "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" "resolved" "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" "version" "1.0.0" +"is-weakref@^1.0.1": + "integrity" "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==" + "resolved" "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz" + "version" "1.0.2" + dependencies: + "call-bind" "^1.0.2" + "is-windows@^1.0.1": "integrity" "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" "resolved" "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" @@ -6776,7 +6854,7 @@ "resolved" "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz" "version" "4.0.1" -"lodash@^4.15.0", "lodash@^4.16.3", "lodash@^4.17.10", "lodash@^4.17.11", "lodash@^4.17.14", "lodash@^4.17.15", "lodash@^4.17.16", "lodash@^4.17.19", "lodash@^4.17.20", "lodash@^4.17.4", "lodash@4.x": +"lodash@^4.15.0", "lodash@^4.16.3", "lodash@^4.17.10", "lodash@^4.17.11", "lodash@^4.17.14", "lodash@^4.17.15", "lodash@^4.17.16", "lodash@^4.17.19", "lodash@^4.17.20", "lodash@^4.17.21", "lodash@^4.17.4", "lodash@4.x": "integrity" "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "resolved" "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" "version" "4.17.21" @@ -7287,10 +7365,10 @@ "define-property" "^0.2.5" "kind-of" "^3.0.3" -"object-inspect@^1.7.0", "object-inspect@^1.9.0": - "integrity" "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" - "resolved" "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz" - "version" "1.9.0" +"object-inspect@^1.11.0", "object-inspect@^1.7.0", "object-inspect@^1.9.0": + "integrity" "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" + "resolved" "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz" + "version" "1.12.0" "object-is@^1.0.2", "object-is@^1.1.2": "integrity" "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==" @@ -9036,13 +9114,14 @@ "resolved" "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz" "version" "0.1.1" -"side-channel@^1.0.2": - "integrity" "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==" - "resolved" "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz" - "version" "1.0.2" +"side-channel@^1.0.2", "side-channel@^1.0.4": + "integrity" "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==" + "resolved" "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" + "version" "1.0.4" dependencies: - "es-abstract" "^1.17.0-next.1" - "object-inspect" "^1.7.0" + "call-bind" "^1.0.0" + "get-intrinsic" "^1.0.2" + "object-inspect" "^1.9.0" "signal-exit@^3.0.0", "signal-exit@^3.0.2": "integrity" "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" @@ -9912,15 +9991,15 @@ "resolved" "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.2.tgz" "version" "3.13.2" -"unbox-primitive@^1.0.0": - "integrity" "sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA==" - "resolved" "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.0.tgz" - "version" "1.0.0" +"unbox-primitive@^1.0.1": + "integrity" "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==" + "resolved" "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz" + "version" "1.0.1" dependencies: "function-bind" "^1.1.1" - "has-bigints" "^1.0.0" - "has-symbols" "^1.0.0" - "which-boxed-primitive" "^1.0.1" + "has-bigints" "^1.0.1" + "has-symbols" "^1.0.2" + "which-boxed-primitive" "^1.0.2" "unbzip2-stream@^1.3.3": "integrity" "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==" @@ -10047,6 +10126,18 @@ dependencies: "inherits" "2.0.3" +"util@^0.12.4": + "integrity" "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==" + "resolved" "https://registry.npmjs.org/util/-/util-0.12.4.tgz" + "version" "0.12.4" + dependencies: + "inherits" "^2.0.3" + "is-arguments" "^1.0.4" + "is-generator-function" "^1.0.7" + "is-typed-array" "^1.1.3" + "safe-buffer" "^5.1.2" + "which-typed-array" "^1.1.2" + "util@0.10.3": "integrity" "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=" "resolved" "https://registry.npmjs.org/util/-/util-0.10.3.tgz" @@ -10262,7 +10353,7 @@ "tr46" "^2.0.2" "webidl-conversions" "^5.0.0" -"which-boxed-primitive@^1.0.1": +"which-boxed-primitive@^1.0.2": "integrity" "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==" "resolved" "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" "version" "1.0.2" @@ -10278,6 +10369,18 @@ "resolved" "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" "version" "2.0.0" +"which-typed-array@^1.1.2": + "integrity" "sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==" + "resolved" "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz" + "version" "1.1.7" + dependencies: + "available-typed-arrays" "^1.0.5" + "call-bind" "^1.0.2" + "es-abstract" "^1.18.5" + "foreach" "^2.0.5" + "has-tostringtag" "^1.0.0" + "is-typed-array" "^1.1.7" + "which@^1.2.14", "which@^1.2.9", "which@^1.3.1": "integrity" "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==" "resolved" "https://registry.npmjs.org/which/-/which-1.3.1.tgz" From 12f7b8c79d8c4af11efa5b5064f9157594efea64 Mon Sep 17 00:00:00 2001 From: Joseph Young Date: Tue, 1 Mar 2022 14:17:28 -0500 Subject: [PATCH 42/42] updated data diagram --- assets/DataFlowDiagram.PNG | Bin 0 -> 181233 bytes .../{dataflow.jpg => oldDataflowDiagram.jpg} | Bin src/README.md | 42 +++++++++--------- src/app/reducers/mainReducer.js | 1 + 4 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 assets/DataFlowDiagram.PNG rename assets/{dataflow.jpg => oldDataflowDiagram.jpg} (100%) diff --git a/assets/DataFlowDiagram.PNG b/assets/DataFlowDiagram.PNG new file mode 100644 index 0000000000000000000000000000000000000000..8a50bbcfdc9c38a21f4cb9c8e494505e89d4a7e5 GIT binary patch literal 181233 zcmeFZc{r5&|36+w<)oy|5}}QXkYZ$~ER`+EI`$=l8ImQ8B}t1Vl@PK-5i?^QVlc@Hg?;xWxqT0&(^-1{9aqOtiQdYqI|>4d}8&^3w`~H$yM1z*QAjL zZq3DGb7O7AG-sMK%B^XuThq=HLc_0)-x~^h=@#$SWPD@qA zz+`0+7z{>AI+J%xPfwYTCaFI(f%#LAt*kQ=dB#WPDRaO7GU^q)okZdQmoPOxljLxM z4=?l0Xi{=(Z5qY#W}LEYPnqM6JHeeRc@Lm;4-z@}_M!&15;@E_KcH{hH@@YXKO#)s zMeJe&ANMvtz&EjtZ=v%S@xjb0a4#i%Fz4n6^i3Ijiw!6A`z@HgS~)1o8o%e?ejfQ8 zfw>ZX>I=u!W4Mc-zr>zL)r7G78rkmt-!9-WBOqh_<~s~wcYc_{b1VZzjPpJU2@4O# zUky-L{2ByvB4NGyP7GVO9Vb^arHIHKAy@C(`~cs|pl^-O^g;B$q$WMPf z0P~u%Ed3TDJP{3d&fR%Huq!>upIhmP8BfI zB6SoT*yQJ)YQ@7u*5E`HewI*9u_p~jN450z_1RNtucY0+jjb=%DNZP^4Jv+1qIH&Z z^zVu>YVkfHt#jLFW_C#Cc>`zS$ns>Rm`~pmQ}x^OB~OxMEE4x^IOnHV_(suNh#cPP zn?`?j6|W^Iu9v2ntIp?rLiF)d-}-h3nTS43hUMNGQ`%$9_k9ZMD{knqw=okH%Rll* z649bR-eTTi8pgDR=b94bWkn0l2CVs_r)xNA-(xJEG>D8cJWAWrbqYt40u=h_yTr=& z2IV@76}yy(?cx;0*GHbLD@R*+e~;n{_c)YbSeQ<2nOC59*-XRS;bf-^#p@+vGX3^R zem8X#SMl;IQy=IIYBr_QB7oOLSlXgbzyiCtS3PlT^(tPq4?LJ^9tgo__Hk73?>lkF z4{XLE;)&@D0wZiu;+`Qt`{lfNn2)e5&hBpBtLE)ptbgOi$0oI3S# z{5GRF`cMmRhj~s&CgTXvn?0yt0YgXXCu=of9!H8|EHUL4^RFXK>p(Wp0-5<9#dI{Y zKVvstUYO)x7}Ebl^ft^gJ+$zJ_nhtJGUr|wvuuB8g>~gFk%5-P6xC^&g1YPqIj7id zX4!NugS`ey^qfM}Oic)z>ria%o)nRpCm?;F(_SJ%<)Hj%eD-)Fzs0Wdp(F#ZTpZ)Q z)3fi=Sc%~GlH(x+mSmGSC4R0e6YUQC{)<+-u?;a|5sJ&e^49nS35V;xm&VId$`J*f z*kjpQV%~RUa9He_AF3E?=YeF(OBoNx-8C}V6)WSujAHtyj=nsWg8J1K58+`Uve1`t z8FzS+@6463e8$~2KjNS!y``g%Q2%}Q%#wA@!wWN-gMoehD)6C00EbRc#6iSI``$T1 zKGrOuF1V4{Wi5xLBZ&q_4X6#{dC(GBl0yh44F-yOc>TK_e0lXjElccr?6(WvTpZw$ zl-5F5j!X1LE4(;(tKb3Z98pLvDf;HM(T-+!`4ECkb_z}uL%r5T>&{P42v}S41OTHR zW9AtP?BPoP8pKvBe@p)Gi!cCza|(jwW0ODea85oD5n9%EICc{oQ-+8Ja}S7tr3X=w zOm6HTVxOG4=cq+_q6Wv=mu4X_6qf*#JZ2m{tmEPJ+c|l*36vq<5Q0nDbghmWJVD)E z(LGrbR~moq9m&pp8Om{HAATq?HgT1ik*EZM&f$sXCh&sf?}!r4QdnCYbUpN&-|q*Z z6iL{J@{krWPJU`~!MoLgr9kHLOH!@=%Z}CVmk7*k`9dEWlRe45zNp@VAzYi`f&v0O z6A2x&84?gywwPMDg;r1UD)k}$<5pT1Gf1f&fyEqWjB*%ikuNjYw)>5}2oEQuP>Idv zzSA{cPjiNL;hfdiNvkjA(Vn|=Sa3YtC=b3~b&>4kQ#h*VO6zM%Wc6~iJ$Rhth2y}d z>CKWqvDr@tICt_7=Ux^dhwShRCY4TBpgqp&Pu-9pCR}UR5}W{!S8xXtZS4gzdN8iA zM7uY7k7XLugWfy+eg@{|d}66>FbVQ_9wQIS6g8IXz-Hci5*Sw6EMiCKP{nr;PJE+& zn*WlzamUm5qdb^W?q(F+WWT_?o6FMB-1}|s?FJEg%O$p&rx>Z2-?CE1vBvq|cr{aS z6)!4gn`B*2-?M9ouA%C^?C8Uj+X_~pz`Mil{E&^`S*q7PlJ(HJ6IphcPnomS5)ZTq zVW9qGlIg1L=m<+*Nw!$|+;zDCa8ten!x*C7{Ol0npcnI=!-!6EG_KccFx+#QR#+F5 z;~o1RfMsA`9Y_h@%|abm!M685G4l-v>OjfSnrpQ1OuPN=EuyO#0o0~ZcR!_5pE*K< z$g&2{AD5?u-pg!)dnsH|aCi3?fhPyO9@C+%W>U5I+-IuZl;7G_xi~%&@}*>Lh^{h1 z^!A7y8mV6;c~D^h&p2IN>Yod!@~fL~tR(x-l>p|zccMS(G2xRe3M|koS^9P+(Lf^O z8J&6jIep}GSr6I2f-o!SZn8`f@=R=Ecn_d(H2O@)Ul@hbtlv+30?B0^34We)6I%a2 zBql0wEazXyz@H4E?2{?JgIC$}^lbqmp=(Tu4}rZ>VZ<&AC{(aXt*zHJE>vj3o}s{D z4p7Yv3=IwE+>eM!0BUtpQ!P<|7%hItK?oV2ATW=htSJ|yi@p9N%IXth#=ULhs^4M+ zQ@OWn{3pQ%{95w5Z;L0^3qIj~gl$efEidn!f3A=xAa8MS#2FId%gZo1jkQZgZr`ob znclWkxDX7n&(>Z%SEtbLyFC0v#N8{^*F%hx94z+z=dTZ|?oj<87_(jRRN zP#T@AX<`}*thgqCK6S28$$YF-OI3Bd+peLwTFdYna`e1kc9-{Exy!>G^-ZkcPhVWO zTGl*ahBuNmi+`?4p+6%`HBq&WGoE)RDja@l#y$7BpZ)3Sf%?%7V6hA=^qkQg= zou*o!fZHyutI+r0VU8;kzs+2B+XdYu7Jj4|&+82073pnu{A4+3(^2^E@Wcg27Ae%n&>GOVnP3!Cfp{c{v1 zfGMSG=`Oa7fGrLt*bQ@h5)QB84lNKlU#OJmAWQPXuHUtE4%AY1+j=^zICyK}*`BMp zwL$`q3Bl0a1n-zi#jRXTA7u@OZg^p;QL6u=m{FPMr}L<~`uq3|uWOnUkj7z!$c9hH zQH6}_Z)q2H7Ao;Mz|He)cLMt*9*ZH^RZJeu#`iidcX(loZ|BZ#dHZ6n>5ZS@fWAEb zY@erX1VuZ7Knub-D_ICiE>c-jrJf*eSGbS0b{xF+6y=k^A!>@49ceDC3FVAe_!64H z_9@7FcDUQn!mnmAsD}agl3_%BSRM3mp&TM1l-xC2xSF{GJn_8!o?kAEJ4szO2e6YOJPV3FKZ!iNXdZhmHR^haRp8}O_7(sIbV!XrH@H={OGNJuzl!R?cwk% z+$$kazHO$9bSlB7k$)q^`~{vDZ=ccJ46#Fz&+N(ml6+2CrW5YTFP=A@zQWXDYKZO? zB9BjADe73bkf0OOwnQ!@X@h&-x;^3^RAOb8k!4z0k%7R-&eIC}Im+c`l(Um|D3)iD z--UoyY8SXShAZZ&A>)fyt?g<#NEEE_D>!10@kXY>Nj(<>?}8;>Cjwg`AcL$EK6;~#uZ2m8NRtEV)W@TfnwB#W0Qw|u{zRJ1c8l)>=p(Qj z&%D7UlW9hSfcF>!;8CqxX?2kpwXY8lMSKzAaP~+9F7>fFf$nxp?o4R;)~o_o1fZ#8 z!|`Wl0J<{J<8AV3R%n>ecbaHmmR)4wFk10^Lx9=})8N+%kWG8_YYW6#@@%e-f;l?BPe7Qgtf z$iMO5&@)$3aMrjKT8e6@0sm9YRf%o`S$XpK#a0EfW3~xx9AoBh|K;Vrn9E1VjHkZ2 z-!2{YPIC}^bNvpt5LRSs`o7W)I>tX1Gj_W(G;5!@~50ITf=SuD{Ow&oy#(Ug{ zOya!nPkOjfde3bX&1V9%R2&`O(O-~{G0D)@5Fk5Fo0`3Lz7aZ}c{l(xdn$1C|MlL_ zL3(Vh@WZdpG>APWXm7aVm;Jzne|3i+zN3T?OoTZcP=O;QAO=~a`i~S#HTndRPR*`- zBD@cw1y2#V3W*B;{tPPcHxK^%2hRllYX_*u{;wT=<@Wz;Jj8|)qN1Y2$)aW@?P*%^ z9Fs5n)$ol{<^I9M=*|P)(YwQtb)PWRhz8q(-uf2`TTGveAM$>SGIZPJ$8RkQxQ2o* z7pgn@nL61yQsJ$%8Xy`Dt+0%#%xkic>{fKks;Axq5VG7T$1fcOvI2Sh_J*DqkPtui zP*Jt&k`z{WAG>w$yL+^6HKBvNOGA-5NWd$^zmBieWHB!#ck1xy0UqsJ?k)dJz1|a3 zrAs+S5uZ8L?F=gV9kTPTLYqe6%>`_*$X>_Y9H<6Batz7N1*?v7q{BhQ+hLNZ06RN7 zm`k5ryn>aNuyJNxqiHYv~!Tt`d7!f$ZCw5zSIr*j} z$H^EtrN{a_Et;~A94wVlUNU3r0#`NBA3Uv>)&*pq!GxAX+ zP8eFjY8NJnGal~#Iq;J^Gd@j(`PeMrIMsJE6?e5ApM0+LzUY3LkZGI2=999FaKveF zK&mh~I#SFOGBd3cF9M9fhUXP_cX;v)%f^Bff|w0P64g1PWLy6oFH+e5L>AN)D>E8H z13w?M0n`FaHo0wqzeLx|phw<20eeImcP7Ioa|z;CEFjTljg z+kLDfKBF0oWMHv)C~85IFzEJ?o@nM14)y$-vN}`a@+0u&4@VOZE4!J23ZIoqG@t~+ zYJmdW+vh_SK@S9TP7o;0`NUN9x`4{1;dq!s>zg8?<1*1s1)U)nf)cB5W@(0dnJiPE z0{LNyY@r~nGvn5qK*xCFqFb*UlN9?oL`)8frBbfIv{4o^5kG>o+q~JYdKf3D zQxcD1`~?9S4tLpsdI+!$GZ`aVdHefkFYj`zxtmu61!c4fr&~3|YKr7+YQww|$o{+d zEneF`S8yBFL{C*Q51M}@56{;M^$gGR5m!%%iA9Wb`nC6!`3yRW^;S#0*6y^? z*K$^Yr0I{(9g``jQUKTt^Ig1EI$g^{ON)07K@SSua$Ihuoz7F3`E=ZM>>kjuDQaO{ zR*;_E)8x);68$x|jxQ8F_ZfURb(*?qnWhZPHY-~Am6qT@6Wg(ajSmSrzGAJHXOB9k zv!sU<1BLT}8JOBkD zhw`s!!av0Q5q~QYMDg3UFe z04m^}2I=@%pqon8b7Gpp2=%ZNCg+qjPL9G3YW0J}=Cb$T3pS=HW6G}=xZm|pqmulH z&F-t@sYse;puzMFWyxZH+Ez@tvq!*lO6&Zn|JaOA+i2co<+{IVlk&8Y#)X4@{TE9_ zXsCyH`rnVzv?h(xLf~>{uX^-{Vk-#Oh&0_HchWn-m(3iS1+5~)O$yh2TC!W9#k1w zpbsa^3_RqoUB*zZ+p?R*ROkGN5Vcr5Ji1b13Z{hXB@qGZ%l(6WAARzL$Q5+5)3p4H zk6dOC`>-ZIl&KTr73}70Ah39#<>m&0N)7dXv!GL((RQ;9uK}QuK(I*>$-C&bEk)!y zu^T{k*s(WzKELhCq;`O%71K?%_Nb*boFY5A+^G3nE#_H2@8Ck29;{Pm$pvf83$?SObaqq{`bTjQ^X>gM?tMev#K6|MGAymasuwR0!4Wsl zb8+cIC-2H#B@EJ&XiA9)ZMBVQ4mKC0aao%um`WHRo`4CD&~q_^1a?$GIU<7mkl+8p zjw``8dLIyC#cgU=5Z?C-RN<(vEGc9gLu#MUUB~($P+H|Hj=BdFBnUl$FnhXU)Ds2QYejAQT20Y#)fB2T>f);gcTwG{BH5`*vSZT57Ku&xfn7gTwLz)l7(p~{U z4uq2?6i(!5P<4Q$ssgtS9{xR!x*vFh=KzZRV&vV}S7}teFu&ngd+TKVQypY52PGfZ zC+g?wQDR}p8Y7fpJ*VudX@KF!b~|@(nil3Rrm}LbRb!greyEoXHDO|Jz&vxcDZVEy z`qbqe9yk%q5ey>k@HOm|KNU9r@}L3xbxNILZ?rXre$0Q4sBZI=v^4J}sQGelKW9lL zIaSKL$0zATGbRN`!fB=CBudE(CP?kGBwzAp^lt8)Go(SL@ zjS4vA!M|72R9wni{|<|Wo%sGPcfXJqK;2AciDKNzTRr9P$kWD`5%P+RkzBG)Uw)0! zaMkCjBq-Ak#b5L7Fvpr?DxnJm4>2_e9x&3 zRM%lgl+i0fqvLD%0L_$Hs`#l-<8meI(BO1;ZArF?%m7r0j~cFPr-Wu{Ea;>Q|4xM^ z`iy5>M*kQuA@_5D5;^#b1D*fLfuP*~o0|X-XLLVB2>Z|qe3f$Tc06xmv2nU>eucoZ z8hgKUDDR4#K3@NzNY7Ibg@iN!YrKMmW?pESEwO9u^MdIo=klwU^}ON^mMYQ&Mu^|% zAGG%skOz$vb+SrV=fR@MLgpu(#sx=~GboqpMy3B_fGVMQ{6EHic`6(Dllz)TxPk9= zfR}>p3g0}_mecoQiZW=|(P={djyoS%4@ioT=^l#gs9l}&B5LWU$&TK?mp^@21iFLw&eXD zJeQ9}-l_MS_;il7-rKwR#e-T%iPKIhCqKx)Ajm}BXua(J!H~Kkhfkd6DrmEatl3{Ra?%<_dHj6kE!v9<{sJ7CPM6_z-jD zMmJ0|N-AZrJsM)-OGsZGG0JV0aR)mKpq`J(ug~EOIn#&CKHDo^VKMfKtc zbDok-)LQ$?7sv*{Xa;lFm@e|q)HDT!>n%!0`x4lfCqC33{fQUQE<|o z;H03gTRs(=or+wGef-~k8h~vu|F?S2r^sEwxdtPhwdtr!_+0i@kFD|5d3Fs3by?u4 zjbpaK{rVhTDAps&bqD=L1(FPdk4i0RYJ5Y@#2PLxJz|B1^^g;rVZZ8WI0S%zv1J$= z;zf27WGwbOc=)3@S$3xs9U%)o&AP4PBPwxObl2bw)h-UOhpX!ax$;ivm3y?-w;H&z zva(U{7T0?FNQLydSb_QzrymzFe2@2dD*Q5amID|Rg3tzouV10PjDQ!hg6?3VdLz0x!G6VHx7S+Zb+nk5|Fjf+Dn-C^!BAtODH!*I<;SU~ zD`gt+y@{dO6$Do|jxLlNC%YnB&QA6U`*}WXe}6+SmwtY^f+-&KAoVSM`uWtM(ov`7 z8B8!iB5eD2VFMHW!^chPc84FRsXc%-8?e`(S$ew^wl=Gqgo-=6&smm#bN(Et&?dnF zrRpaB1A?T-g^KJn59p+3himdg`fC~TBv<47vl{T}vnfRnVBxH-mL63hQ+;uIdiF^M zBr+vRlCI_*ReoI;)f{C=IUp=dt76yp9GPL^DaW=x5ECx6(r7?kAUSsBohzNEvctTt z7%$$biO^=wLl}9vMC^9nH93zqH0VA)P`*Bt$AT6_HfINm58%$ie6Q)F&Q}{vYnhen zlcYY1zqw*_g;I1K`9&FHoutp$-FAB?1GO*Mw9!?fljsmkB&?obp{h5Z1ULyrEJN<^ zzL}{JTFQ%4p$v_XU(VHdf=xx@WPi}-sAe-O(oyll(Pzh8jO`lY;7D_u{6NgD%V>rtNV1z?D~&QS@e0;sGd*>i_NiN3)-prYo$kD5;hl+zMW*vqX> z+2Pl+6|=qT@Y}Qo%5PL=)p?I_m22jp)M+mT5f zl4RcRlCYyYVjX!(XX>8qNPw_DM3rJ)dq3CKluNHRxKihLn=is09)zOY(_zXYbTf+4 z4P@9w>NbtY6TB}q4+yUc?nG1mgpKIGA#~bQs6H6YxjGzgH7EEa6dV<_xaHj#pc$z0 z`i--Gs8+U1o?&KzAqv+EH@8VOB&?D1>CwU*7A?}i;K@MTkx2V!$6n3lrf!UrzoBcd zW6(i)=)p=zxpE+gXb>sPYpUlKZ)>fJr^)@og)@JTy#Y>Pzo%3klqn;Y zuFz_J88iY(Wy6Zs_7bxN#GEJ?I*q&ojZYyt3uD5a^z!DEeZU$l{ZddzYbd(HZ)Mi( zi5eaAu+w2zW_jYV3Sqif@8zHqWJkXITLaaL@h5;-Jgs;-QtUSsO86gmeeM@tgZ>9d zL+uAc3BlRu+k5k%S)`)$USqGMZ6=D>(fNMeEw(Nl5?v@eoRhXA?Fv0p3qlAa0x$Ar z{Y79yS0R}A2Lb}p!j_AA>`j|45_?I?f_&{{X-@Pd|%)3W`mA+Qf7y8 zO&7+q?HIW_sOb@5=jsUYQIw>sZ%KL?=_X0aI9#D09uk2vQ$1JJY9F^p&EP_DYjY>8 zxSH{+$lAyLtrCWS#zKjqueAoqY1QfWL_%!lZwX-P4+4 zakL}*xn~W6vbT@TqFoAbb=K)EiYCI(jpF5@n|($IbC=HXYo~tjP&71iNotoQx26q? zdOXbo59i02>mV95YyrVNn^;}1zg&OtPZ+ZK8w@RhX{uO$2!WyBnzbLrewO>ga`Krn zwu8t?Q5Sik<#(Q2w{=72eEWp3vj)D(+)lGXqVv=GDx8raHs-n0E%6&q*Jw9aoYpM8 zdh@aWhx-T$lz|Y(os&-cD|6#cp+ywnv3cJIG4G4zwB6GWnMz<4Tka;(%_1~StN~ANBKhPB`%-AFH*1YU+;i5LD??XGOgYc=i zkK&>|B-D3Fx|P}D_0FDeKE>NoP%{6%FchZf#6`Eza(8OZX1Y%FhN8k`&njR@ZQK-Zu~B zk+Z?TC@_7zR+x#g&4C6-T5zUMeB0iNuHdD+h2_qV31h#y){&QgOMwy}pv;9g#KqBi z<96_v8}Kf`ggMSXMFHdFWTfIorFP>bz8sG#pz8-!GQHS`YWpPxHH-fZM%?ES@V<7W zSZ6!sEBl05ygm4dr@AMMe7Hd#xbzd)0FJMvaL4QRBUr7RzglJd zw12X&Iy`(~#%#)OD#9foS+cpZtz<-m@uQ1-d;C)^j0raPZTC1g4>l!}JRV=YP(GEP zGNKxjvVa@D(Enf|r?1(^qG5c&o3gBUXje%10f)9%030{OMl*3T>>*ov6spSeH|E1` zkemcMA5|hfafW-jtU|nrZHDisk%K_A?;6DZrfqYryT`g;Lb7hYQ4!;Wm&)Xsh|Tmt zpZk)K#FB`Z^|}!n=5x#oo7u$2nl#9UefN97xV?vJb7Xkt(vOnT(%gz zTgwy{#e#u7Rt7_YK6FE@2z!MY>r><{D(zxsj%Z3hLhoN^3v-+mm@?p z;6GAeMe#3BJId#d?P5MS6AHQYFv`xoNasLx(s!#T$^9m$V5FP8n^5ufD0y7zQez{|hKDhR7iSqxU_$yOJ3LV4@Ci7pTX z$Y~G%^gGusa z2kfJ3y0^Xt)ubCW*jn5AM>O}P;uKEc)nAhOy^~X()Q9NR+X9hSuFdn+cSJnb0l{IH zhdYa_QF)@)=0Dxx9Y%1~+%wx3r6uATVZwWAV&Lq0d439PEj`}T^a#mQzFsbNQu!2n zxUj3Qv9ZmahttEa&;A%qzRCa4_1(EJb(u%qwM~a4(}8dg2@>H82n^GiGY-f-2S_G7 z7ERgy-m0J!ct0kXj7%iX#BpsKujYzRm;aSgWh(ISsuYVW=7)#NWRklY^yjCQE;86} zhIg+-8+QUpG0r6B8lv|H{$ObI$(46mN3-7ICWm>rUH)=M`6~RGYQ(CLQ7PNO#b=so zn~I(HBSH$(bvbi=r+l8cM})}2NI5uX<7qG!1ORq@P?9WktX-n8YpA5Q zNJ*xOBnADr9-f_6HNWuL1mm#tz%x&qTLwQt)i{D6)1tGQ$r0byVyp8o*J#$PX5Vlb z0_bsCT7%Em&O4~c-7?1c^|l|Z6Kmv`m3i3yhfmE@X`o!$C6T1a7ginBO4(Q!FF_^^ zC2+%CgfRv7nd9H81ZQ&UL^gx@X$JyhAwpt2)%b8>Ud4`DRrAQzD8@GNi|0KpEjM-4 z!vmk4CdM*N;H_P@m}*nZaScI)HjpI@pb>Ng(I9BeaDeG+@wg99{m>hY%YvzpDoq%a zQ>7s6!h87y`z&A*H5SB1pQ4}93-D61f&eP?+wexZk4KRFbz}PjpQ}gIa&r%zdio{O z?R?=~C)@y`R`9_zwJJmgolQ6@Iz6*mh#NG`oz{`~BX$bG!n0fWELsRE z5KCn~u*t9PqfHbUo>Dwo^h!X5;tu`q=oFmi<#$Nu$5dqn3p1AkAG&0P!pc`*hh&b$ zo*c1r4$M*>jEQzHmi4s{C{F83doYbt2y=6Siz@$7fxJQqjUpJN|D{OzYr1vj1hdQM z4hewWZ+PAjXB`SG$nXZ`qz9`3yCB85kMgs;ffCixS0Ef0_koMTwQ!Y(TY>*kfe}Bz z>{!n{ZeD`c>M*JOY!ePCd(C)x>CGRgG|hpc#=$27KZC5Oy-yH^@IX!-&imF>+)C|| z-b0cA<;EtG2Wj!53JQ(&8ia&*4HDa;=3Qwfo|oT002#)1(1khc9ih;X8)=+6Y~}Ri zahx9T8l+9t>fuqpQ?lrJy#26I&gHXrb)XVGf)JJwnxa^x;MV0D#`_4HwyR1DBXD1e zuU_>~XVQNDrjriFl-n`#-T{ou*_vZtEzRJE+N~@7>$@Mk1 z%M&7%PeHbqqY5M%?F+)`5uP^gAci(bL_C;!X~TJIlOw3yw7w7%QdW%`ko0u$vmJ|h zCg@XP0h1&NlSt1Ui_wTsNMCg=laGK~Ps|mX}64K885m^s` zF0-FQ3Px$@nAdbJJq0Bz8XslrLMdj|ntuPZrglQ>@g{${EK7D=CC{9tpX%z<&6;-# zjmUq`I7U8}Es=egV{PRfp(?sl%E%9#Tj&PU4M$TIii=^*&L6+;Q6SktX9>Wq#Jw(3 zOl-*%n3EG_|E574)G2vsH_?Rz4+YFMU1ZpH*fthA{Y~(qcAS)v~swgB^(>Je`9_xud;z5`sOx(5QjYJ zk7wD0tZU;4j5ksrFljxC=|T3QajAVuGT+wml>ce8TD>>)Nac7!va>%JF1{DIG2-3g}Ai~(No21 zw+pBRQisR~?)~x0I98j6iP|(SC+ShHtKnb?V54lsvd-^<9l_G@XW|^Ez;U4Kk(~(4 zmX?+t>%thPW7mBx)jJ`Q-3NxJ*hBoj9D?m80cBlAiLlK+Bl!lAARQ*`#vO-Ld0Cl< z=WK<#7@!=;8L#uP6GoVY$C0=FQdjE=YwF<1!1b54%u`V|4MNL;$aI$9ChVXSPg8)u zqA3-I6iY3dpuitC4nb^mqn-fmZs}9ChHB>||@&O(0U4$zAjwuzZsB7?@5D z94hNryvBt|Z@0I3{EPMHr=kWkInWqDh=fK8KRy)&>pjJR8f*^Iwd;TtXNf>(27A2(DQIe=Uf1iD6Ru+cu330CK_m*kh`vHFC1Te|84KfKO*Bi2${Lot_5HH z^6-HRySr3C4ubJ7iW*q&Nb4oHO!z!obv@a(IeolNYKbO$pV z!RYVzETA!t3(=r*{H&y=zw{#}76|M>aLe;Mi(v|4Fx4OJI?}?Ee}#k!MHK|ZMUQgU zlhIZOAYJE)3Y=In5OITK5;g2ftF=qc-$-Ls$|PVnlbx`fL=h4c?dGYtFmurAes;oE z6x&6vm2hu@L{2Wqjh5`OJ>TCKcYOp;59xKXp1lW#Uzup@G6$Z4DKPPXZS3tsB4o#} z9ot^Yb6k4_0fRcl()U`M^X=z9lg!d41?PVr2_=HLK2LyIVOP=bZ=fz)h8tz4$#us? zI6L=q^l*xeL{2W`r$peVrcBVWbUHmE7(wRodP2Z4KYW*)ceb+Y1(p00b{Sg9k%~-_WjkQt)O$v~ z9)4ByCC`rD^hN#ic!(l`A8dN(l1J7ms$=?T!=qqlP&X;tF_#mt#;si44IIXh&8fVo zprcmtNjEwz^(RoF&@A6^<{+v-0q|QjbWUEA+CG#nq|U6UQyiCb2vtIl2tOd1V}~jw zTHo~$7cTumX77dHkwVZ4!A%;!KFgF@&U^hB5vmP#&qF`9zORh8VyfR*mxs+!4{5pi z%zde}uBn&i@3IP`v>H|5BT!-SOU2a|d>)QoOwzMy$OKD_g&>n>KT5ShsDi)NZrFWP z6N*MdBAud7zX;MBoEhN?2A95f0|eZgeoZyOudV%XOzCM@Q_QdEIZE`jvT>8MF0L)L z9z2o*+R=fkS`|vVFcsx*(qw_|yY^3CQ#PQ}m2**aDucf#y0TSk9#6Ijvt97xw1Z4^! z;83RMfbAfFJMg&&BV^CWWo2_eB0xvas~BpOD<0v~QY=Q*yNn_4gG~w^N_n7SOkir$ z&K3$QJb{L*AU#f#{dsm6rHF(;OV8Sins@rJ>!&*zDwNu@24!%H<%ihRV?mHNY*J9B zDHtp%F`H$!O#cflXL|F=XhJC>BmjJ`18Yc) zlJvCq4)4HAaPl?;&O`Ih89*iJVT8sy&2N_*E-6m#yo?#)3dU;NEC0$u@ka>oSOgpW~8bj0(kD1-=0O1iG~0j=XWbIpGsj zGDgD?vRPQN0v44MG90Z1;|yl|^SZkTxX(|}Qcp#hEzV@8K0mLh3OY9sA8M3SVQmWr z?cJSPjq%6~(M5>zfjdB{@QCpCrM!5I(^mr+z_TJW&1F zs2u&nVY_)A{mb*gN$tpNlEnSoaL{OMEHsSp;RAXA;IY#QvNaQ^me9uR4Gk4F_Oo55 zf>ZQfjZm+1O%=tJ9U5^*33N8VIO-HBNA7Pfn#fG)(=*@~|IwtSIk~a&$Y;BtsE#EX zIFT!E=(_+F_A#OB$Uj=M1o$v#yVV(zuoW)Xmp_iTgm5BY45;97htAV zh6{c&G;H#{4%Cosgc*o7O6&1WZbM>6#ee9Uvg2wn;@j#CRShsNQD` z{wY2!&4`54VXWtYBC`{6+5`e^Z;UH9>$O~8^lY5|m#BuW5)Wf-qk_vI(G&PuR`BID z15~zt=K)l%^1p-$<-u8)Cg^%Lp6eiy{uX!^242AOKcg@w3jyb^eIPc@1MaYWb78WC zGP3!*V`S?O0B!$gOgI6(rP47Tj&uaA-_p;=w{!PWlvBso?0|^k|F9S^@8)}EKJEV} z+W-&|+wf;781ek~atPY~2)6&44%C=t@#s9HL`}~U<<*h~x8JvlJ zM0f&tVN;T#A`j7%oybuGxXxDMADqual_HZOavM7KETc#!?M3BQfmgi<#t4J=Nr>?~ zrJf)AeDDX@DClt#>;VwFP*P>x8SLBQl*M`_dzuVbWmcl>ovg-J(&wEY#eb8uc&k|8 z_dt;!gU#P(RsH%3^V1gQ$xkQ957@xY$gO#z+DBpvwJ*;vSdY|~R#{ccOFbrRAM`bu zD^9;43ugZ*MA**o`k|`<rQtvhucqo$j$mIfHd_LktkJYah4V&cGdZxKA#)|q4!+#J{C zefvkPrHQfkOT`thS2{t%B5|F*;5-t2EIp~VS+dauysm=lq!}apSYv<$+f@agP%}1v zlbIfI!~0Tkk)pe7y43C1VadH*%(mK;SNFWn-4=`GKN*sc9`vBV+txU?_j+PaJ zUg*UW)3U*y&IyuE1}h>S^LhzslLv$Qg+t8L2P&oX23Aj#k6%QaXEHKbgj>cwdt3s~ z{WSeAu`Z?1HpQrrwRIK8B35MY`sQbRdDN8IjWL_^_JDkTdkJFRPeUDKKc5)eq zvs&n~ucxzK9u3e?7sWiP#UfUdjE>wP)A_(VR}@1NPmVaVMo=3{e4HGmYSUF`IU1e7 zOVVbGRPL?0T~BhWzkf9;fN{9p^s?Kr)1LB1g|-T>6}|U7GvpSNP$%`B3-0%p@YfHf zG4kVNc*@Ea0gY7Sdx=a{c^wU9q=8$e?aIx1;rh};wJdOi)cX&Gsj{L!30M@

qO=seL_GRB3k1q?M z{>c%B`RnNDFZ>dmq=k^`1_4KWSHKxla5~TRw~U<*6uxaWO_6sFKf*eldIdkYasG*u!S}{{np=_Wxg<(@@1dC^ zu_w_9z|cm#s1I_*U1L-yKqhY;Ifr~VBwJGgPHpUY2EvI&EWKEDdGb&K=$X@L;FTrH zo9{~4AdbH}SFrgC%yXeVSjKs;i3UKWg1QKZqyCpsLG$(hpV=hx`u74@MP8pX#vza5 z7%ywGh1wT^Q=11oYkhPqv#^#FVmssr$97=XXl=*fJ?3>6TDF0^#bH zfC6F{?05mf#Lu@Pglak4MX@eBUg;V1oazaN-Ksq-yD%9+`Jv8hW51gp)fuqpuu`ne zf6nDu`04#O%hlkAF8Dp6`Ok@aepHPiYbE*V=H1PFq+mUrx7g#7PjRNl@ZQP0)Fj<0 zie6%kzt(nR?%XJt-2G~8{~jMTf`;ZiL9gkr^q;ohpLrf*UPz<&H8V8JK0IhLvptX* z|7~ng=-#o)%p^@UDSdo6b%q3^&{+GsKQiXZ(pbw*8@fv?aV5sHZpWUg_ zH!qfzM>_K%(7bnE&6f`*F0SOMTpSzo79>Y=Y-%GQVL9vNx~V-t6~3?GIMQ zxuNTKWt00oDQ!Oeo~5j7GZ@L}i$<3&tS^QTy5PF@gF-ykavg#AgZDzs)H>hFt4A`$ z;}pIcz%OX;seU_qu+%KU<)Z!t9WAOIyF&rzRB=GSmnRi-w`Ki4a+NO49)byKp796o zmAgYh)loZX(b?XL^M^mqb`*S^-4mzKNmm%Nl^t&tMt;SM(Vo;!8um|m$T!WuUA|p| zaUqKvn%StE`o@~glhA}^VqNzYCYJ)M*~&f5sM3*(&3=^Gw~D)xWaXutN?m+>OK?MT ziwi^ZmlGqkeKfr<{UK%j4{mQ;&rpFU7+Sf1TB-4*F-K0MjF^WoH;}PpoIcg#^7Cz}uuTxyDFPR^=F)W|=e`WNMvghE^8h(@ga(WhAgoe6zR6(MSvsFdc5Y#cQ#CEB1&&MUC(`nexuES%0qy z;SX+S+p@d{QKc8k{A0@JN_yw&Z}3dd&q#`Pg8dV}q`v=wEXH}}L|6-7mc8O)4$Y&w zp1y3cdXBFQIcu@@nJz!SyL2jc1leWSkKHHXfUWDz^*m7TkQfd z6oKYS({B}5((*98cN*tC z=i!r-mv&K@1(|Z!)*G@z6K)fV!Ly^uNX7ML9J5`U->NsI*?oOYBl#)F6tOyRNV2Zz zs5>R3=Oo)<>r#FXyxIJ4DJ(0mNxR7ZF0DT`J;9@&X}VaWiGOh6%MuBs0p(_=JZ%Ea zk2eQ?bXMoLS?}>w^yqK$o_fY*SyxEY!j60=FP!YoHMDS|(DEzI=4)j=S3IP;2!C+wDA-HuC`ow9+w zPAitY9&Jb&eakL>nL2Gc`{N-YV1du`qd-hIdv>MGrE0%6hjWfsy2tCHVq%#e&(t)*K1l1D0mm<7D-(B>i^|va->h6J3@Et7NV1QS zTYt{FKF-6l|3~LMzu(P=7Pns0ZoO9~`3)JxCkwCv_n>+B18Cv&&?&~NTOS$+$|48* zs)KHR3D5N$aemmfChzmH{AHc~nD>MX*q8bbH0dX{J_m<+6LcDW@0nRTQ@Yq4gY2pn zfL-VR%I+Z^*LT(-kLEh(>Pg0ZusLxzwoa_x$sme|s5x`QG<^-PiVcUrirAp+;SJef32Jnpr05N0DLX z9z|p3#ge&oTH}d4oeCMLARswE@3RDuymf#J7hu+rc&jYVk$&~FSmkDMTTQMICL`SDWkyRMzmeT|POY8yuIRJX!4b|FW-JW(i3h)Au5baD={=aba(|5iZ1O z{)k>C8guYCY?=iP^02(bYvgG z&g(s!&tYXlo1oJp2?X}AOV52CI4^<51J`xn9G;Otd3>Q(QZ z^g!G6SbaFE>dKRRT#*S3Ft;Ns(BF#d!+uJv=ozJ-m1$nJ0eg zwmO;j@dwhxCk7(e#NsqN)3VYrq^1{TIsdLYQZP>K_Z?lbCUC|qCdy~^@chAC{9^Rv z+BtV%mQ%k}U=(Kgb5kK;KYVq;Ztnwy!7cM2UYGm6#T5@O|E?P~9t{;v7YVLu=i@4c z7^c|r(=q7MwNycOmS<2pPN1jt9ZNHG@d0YFe53yUGMx>9ccA`sb2k6_Cc&?jow%R| zK`E|Cpp0dn%Yp@hjd^>$JTQR#wmM}sX{_fZiW#lOv@CiU;+Ij^p&K>xfS!2Mn$`z= z1asZIM}{crU|43l%ejTd!8!y$D<=-8X7%mjJq(iO$KS|1cIj6S^?X~%REaz>v&kYg z(qidN^;{w>_wvh|>WD^uWyd7CnQe{mNNkA>o$J}PVVr(|op zV=4t#-gt?NF$+aeq56<_KPokJuV|?iP5Nck&9pe!uwOR{YC*wbTOGMAgS6<%{`79o zhZSuh4~(E?0mb{2?YBi03&`?>X=f?*w}8wZx{q>MoDj~Z7h`3#P*1!#h+*4^3|T z&;I)DeUNm5YoU0YXjKj`t3LP;=}cOxA_`g#^dR27T?pE7Y2rnD8Q#`2zRnR)0mt4& zr%t2|7s4;giaLxcF)GUdUV~~%QtR7CtMlf5I8o`k^5gZ;0%X3qPMHgO0_gjkko9T| ze>;`k7k*Is(Ean8Vy;iZ&!^oiIIi?Z91BTbf(6XomSbXrt7w+cHELZ$=vM|X?`+SmKh4NOmBKCo5UAef9 z4sVcN$Vv@jsgQe77EAxoI0lEA1A0n=u2mF0md%~#a=W)0pUwFe3t$m3r~iwGe8#3w za6Ki4G&RN(JhGHcJgWC=E;k{NJ6oKLm~W766N{NeP5$U6aB;jd!!5IWv(oh|o*!sA zBRjb4#dE*pm$4(r*e>5U@>rY}rtFOd(%@Ek&P04&Lq^Twb|@SK2cJ^=P?AWw6{+%* zoR!y6swDV1%IVVKe)jq4`J6~AyoU<3yHnkiRaS6p-)g@k*MZIzjY)WTu5XsU3cDHn+g zvRc{;^Sh#HapRnA`-kv4fhO8Dvx^%%HL)4Lw^s5i4!lXrqmNkd z9#ex0HaQT0`w-N)HFhB8cLQrSa6N7rtCUlMN$J3Q;Z9feEdIW!;c39UGl%&AuFW(e zO?vyS(?B$C)ft$gW}WXtY4;)k8sF-0DwjO(|IqQse?<;Z4?V;mg2JMIY49A-B3!x!L3Ku>n5)z-3U5U=&+b>+;`p9C z@r)t3;qaXK0@)Tkk#gif2A)8l%1ZA0;UQELDoEO0?Zd z-_lr65Cn;us8zMZt!xaB{;8lICEF{w-GAw1y}Q2!)eg^*j}TFBofs_Z;jppjy3v57-6Vu+J_Y}lXlC;`so8Q&;#nk$7gi9?_U z40O?9%|S@^yzb`Ur2pcx{We3lKD##m*YrSgup8D52dZG@iKs5)Nt z$y4ra&PISIpu!$g3{`q*TkzsM(h2t2t{{%_JD`mzy|e22 zYd1H%t4aNk{|*X?z1Z(qP0N$8G;OprJd-&~vvPc*R76ZIz#K3a_7)dj0kV$h>#qsy zE-G{VT}$HKknI~_{JIY(Xv_0OR;;cp&j91NqlUrCr8JM>xao}l^1gGbPaU7%09g>F z%ny_4p$u`j_295F1(#4c()TL}J3JRMi&nWVU;23G-n1VJe%>l|69b!t4cl2R#!JST z_neB6Hm@ZuXB;Qy`N;Bc`Er!IpONWB=VQ`Cq{+lHsJ7CR+yr7-%p!J>Ns2Cb1*-m!oXp;&?vz>;LBKZJ zrnI$M9~YVQo`-lwBTU9J)O8&2 z-?J54ZVQWQCJe?akw(XO@WZ+&QY%6mBYXp28m?GH>_b74B;kp84~c_E=A_>h%K?-Z zzIS_5VU~00LUoEePqhmLTqaHeD)osVW!zBDSyR_J^g-G6|GbP-J0GNtCz6?C8bKc! zhXkYTdc=VYlZMV(`S84fPV@%Ve?0%i?VOtGx2TpWeL~N!bGq~d&Elb_?!1L96SZ zb?j$pgtfQf7tXV4RIN6umPIK1izEkgxSG}M_vVkXRSu)3EA?6uQv@-iUa0O4BD14_ zc16aQ>9abQ4{CmAuawr4z&(~Q{sX>lvXIsge(W8JiMAs7EM}cz0~z!`(|M!ih+p@OzAZ@o;AknqK*}{Rv zPtK^mL&&C;{TUbW4;QrP%6~5U)T7_JE^NMpEDph^=Erq`7|ojZWs$Z|s$B_9T3``nyTowy_^#drx$P{KM1LzJ6gVJDwAV z0AW|y-!Dv%J^sbTGu!(%ZVt=^Vrzlqf&ikrwK$PI{(H%yl=2OR^}zvk2|GA#wZz4# zrFb~3**E(!{ej!4)dcc^CACvs-%K-2(q=qZ1#(nNA2G~S@hiwtVGuu@XEFV#7OjaP zdn}BgOS>4;vD@-Mx9lISC%>LOZMy~WDLL_$6LZSi7V)N^o_KAH=B1oPfJE_=6SJx) zWaFHtK{HRmteE68=D)bxjGC^)_Pjwhd}<41#BCtx?ho-i#lG{k6Z$4$^jCsET7cGK z=Ti$VQDJc&?rRU4UMdyq)`67VhZ7euB8Obyc2d5T9JLFN{^fE$SwCFantj56=SBBbbz`B^foElcoHukb#3$4NV@fGfBv~_a^T;Kt?m{?RNX4tKdv!T z?d)0hCcxtXpno9({*<#`{UN(&)WEF^mDQM8&c%{m-*_u$?LTi^RW`gm&DGTlWE(03 zsHWSf8J$GL(3~C@skNo9{@Wi$S~U!aH@i^uSUw1$MHz&c4AA(b=rI24JJUoYeA^9Z zS2{X>D1}p14x|y1At6+c@VfQ+mEWE2{CG8~gFjesSsEz}P!f%AVh|DX0>&bdzDy|< zT=F%$l+`dddj3zJz5Qc^NwDqQ8E_(wr=hjW1>O3psL+7x@Yy-ye*7$ zgqC*I-ri9kR(M2$$82Y^;*j?xf$~LG-I5|($ukNJ=*!`$O7u><$)N6x7e1fDkeuSd zSekCkNORQjf#Sg!z2&p$rt#QXOkso1YWo6>GHFt1L4Kv>CSk=%nkj9$c2-wmxKNWE zEuwOMl&)s5;7O>a#p(|D)js`Unj&Ncc{xZ3H>nq-3l~;*W~*V*>C`RDW&7TQEO-+< zwDJqQJauqEgSne^w?N$xVS^mD@O?H1=g9Y?=1T+xf& zfpV!9ZY5ewutHW<$$ETq`#7jRLl~nMS%-w*cUmqldJ&@cc1y^EEHR?#PDL-H;O$+m{y(m*xLeeBAaON2Lkdw>%@$){+e;%W@2>6- zO!ST4hIBv|51NI?lhX^5dm`!!R>_z3fbuscy}Xp7np5>V2&T8Q#~z)8|1c;j zZ=8@&79Zq=$~sVuPY=)~WoT|3)4N?qfOkt~fe>LS6f(T94P=+5V(a{DU{{h5b=-_; z(}e+S{Kk9+3^i?_#~MYpnZ>VLeHjd0`+D&zZm};?qFaP#f8NhsyH<6fGPw@rcSC9n zCwpIh`er{Jn}QiML)Yh7247uwAGp=vekt8uqS9h^WqK|2!xP)!VhE~Ykc-SjlT=iA zl<6K?sT&qXpdBdc1MZ&hrobF7V$4~FjpoNeXRg^!WKtIMJbDR?mwz7t1vP)F2f9uM z;z74PpDJePb+AT(f4+JB^HyE-SOoN`yoNCQSc$z}%OPX>Do8yIFX=Heq!UQACOX)? z?S#^hoj4nE6nW0PJSRSW@!dwkfTgtgh1pQ5OR;LT(d9vJ4acp6st8Vx_m1@#EnL)t z_3N`J5%g=-Wy05SG{PWpV4Eh_*(!yQ$UBVRRHk)_q#1oV4O=U((;oCul2}n=(zg34 z>K_t9FWh{7BD^tt!QFlNIQn3w3(Tu-|Jk(Z1?HhL{E7nFI-2zFr0Kh~Qs?Rn&QcME z5Fg1=ldRUH{k*2;6yEy&zwL!Q<#zRl6Ziof`-GU}DQ6KlNKdwJ)@DqHV=IN>3O=~p)>7Q8YZtlXnZZUU+1 zP4Kc10DfD0=rnA(O~c5$wJl1k#mSKQ2J3vCkM1E1h85BIxdXMv#gm*Dp}A%om;s45 zSoPX@-p*t($~|M}AneH1Wp70YGf|=DnQpsjFM8}?e~ZW`DCfhch@D3T>tli2o)jHv z-M`0~_?Z8LuH$_FD9_9TfB5?So1=!q8C9p*ca_%RWu2oTpi=2Y?5$!p)69`J9=9WFYMM-~?&wpq8i`E^!K7Pc_+wI5z)kR|8(gU+LQ2hR=;PI;t}iq>oR zpfw|}dCV*om+tx*hts)X|Kb(#mKpN`!q&W+aXqJ{+8KL4z$A|OgCY~C|Egudy811! zl$#1iPQ=ktY}b^iWDPsy*;w`DlsAv;tfeISb`lkpuB~pt0nQXzLi%BHhRVC`Rp^4y z8CIbnD}_L0tx%-jlSS3|7Gal~rx(>T(lPceF^Ol>zIVrVO`8Jfq)H~{p52S!n~Z2s zuXQcB70Jrek#{YU@sOpn=%sNSvZ|+ZxI)rgcMX17w;HJss{L^}CG5`vzPQKI&(?UI z1OL2p1C;Yq2#O3tUexUGQ9H|_+bKk4O$SilKQ(LZYXjx_!6h5Mt4N%Yy z(4jO<)U+dwjabzMP`^IlplPU%JX;wN(BTF#KXiM?5+6yL4Dm}-_klNG5$ni{xgBuc3U=tuWAHNiIWvPlmt%|)xSUCCL?8|L`XjJDD z^;O#RV|{%R3xASUTnXgL1-ZKE-FCYdCB$59htwdtCi+t^suvV~exC__au@Kjo&v&~v-sZGLW}GwwP`P2 zUJ$q}wq!m3X>%sc_Q>N``em8LqHA1D0za(aHsI&{H*dy*)ncMvPkbnqP=nKOI8WDpK9%3p!j3TYem%oNF%zwK?R( z+b%AmC{eW{UefQkryi!c2iGt;WU5>$Ux^{Yu{HFN{G&1(^ziufcSjGY%cJ?F-j_Aq zV3mvYKS~qPV;oni@#>z?+WSE!FC47+XkOj)qhK&o@LJHf&c=sXgeECwz@~sXJzT?~ z*hG@XOC^Z6DdSNCPZEpAqUro2{)_I!@WaBtEPPJvzcb)x@Ntqo?6IMf6U<_4`if(x z*`c*hMmz5MF<&Mov$tZFi-ARMH;SF^?oDv)63uz-nHaK^eRy4gpL@Lu6wKAEBzN*6 zgo%pEOkY=)N5Dz7*h({T;DIC%zqF#DbLHx;dQsypBt<5$jmW8NhedT{kj7o1;%cva1Vk&3#DPzh0Xz2jGROk{WWL2wOVGHu)qjyn1VJ%T_dl^*TPr5OS3)P34)))^4OLs&* z^pJNIg=Kn+2^~PE_s3-bI^O;wXWGenQrL1`xE~zdlPc*-P1}$9d96z`(^=j8Y0KxJ zgFq4lYQr#v>*u{MxaS&~dY~U44A#6Nt`YMnh#)z>&zGcRO|9_l^Fqi)3v9@X>@Vq1uB@|hVRYsvtjI)cd8B=p)to7xY>2j-WwAKHDW zED*`?P_aC$iRW{!<*8aU*+Y&soj&Z!LTOk(@r}+~pM7*7qi%7#REEV=E&d)L??yq? zEEmr9Cti5%ir(lc8j%uYmeMlIpK?^b5vLCE5_F}s*PevI7JG>KvDIU{!ngKKGY0&n z4`W{q9EE43t7b0TQG*c*b?cilDB>Hvw0quyjcTyXs;8dUMMZGDzrnsp=YLZWI$$Rz zdZ5y2sUb+}N)<4Vdzs$8-v3r&brx@XQBu27m`Ztxx#rcgN^iH<4h3mcUAE3vJ5S{!G4b1~$h-4B zl?J!t{Ao2`uc*ffT4_z8U1ue*U5d z1e=R*i$*V-FbU_5OuQLO5TCbOuBXo`vEX=7EQ06I=@#m7X5JlwaP~AZb)be6|8#}a z>WXib>8yKk?lod_KJh0hJ6%#7>cE^AUJzi!h3ueBG%Vk)j52`O353A^=?+)c1N6F` z$p7{SpZh=m%tZXJ)$^aZzyE2j{>#4l-?sT9dH25~^S>kW|9NNl|9bNOQl$Bxq5l7m zq4wCv>eOb{e&rN@;f#KcFk7AR9zL1QboLMXclam&3s&J@RRR0;x-QfsT>is~kj2#Nx|5tT`RU)wkw7H9nhd-1RGfxAUu*r=$rZE^k?h537GnSwnDKDw@s z+yV$>n-F;l7|*}zE&#Ene#2@y&Zl>}9fNzV=J6Ra1HQ$X&MdrFMZmu_J)P;zjeYrN zQ7h`NuTb%HQAGp*Ny1ijS+9m&^#JOhAyXD%*~YD^&b8-~&lfJn|LL#?1VKQ{5)7x9 zX;f9r52bzvF_CT9VSuglvaIW1uG>vQ=V#U8pXwq-+pK~Mv-*QGfL)w5RNB)Bs7h16 zV*qqOPa~%!WYdkG{>aJwi?}^Hg^;rs8yrZ;Y8+~N{pK^62fP$nt^Qj**ObiZfSq3; z1OMk$68+sa?Qd0ZszdifzEfP;fN2wG!5%@u(O+>+|MyiMVEq{0eh@GQU*FIlJH7(! zhp8Qih^aTzCj|R5zT%1gqL=#oi?$-F$^=xeJLa!SY6bou|JJqDGAf-ArpKO&(tPov z>V{M9H|iEqNM-ypZB-*dOcYR zfbdx#6j%_z=E`t)foEQzgXvM(aJ)uO&*}g0&==jp{Y-a4$GkRY16~A}U4Sz2N2xIp?(4MVZ6HN!x zn}h%X@r7^{gfCsg9!p!?uTivuIPkZ$GVMRgL3P&b;o&ec%n|A21$&GmF|+gcf7yr% zmc%Z=k{Ac!UGnAp2?ZJQr9olo;_K>)K(zkbb6+VL`!l;P9EGT7&TS7M*maZ+_g= zR@}!Xq$&9cOk{Ygm(1-im!sq?I1L1b9?Y=J>|ElwJ*f_o;1jZBYL5fSL%yHwttW zwc%0mYa;W}I=RBi*y0f$aI)EC-A@Dk-{S?>z`udjy})HH-Po^j_M4MmJ`5C*?@u~9 zRT&wlaXD2QoP-R;P`T(#Le4P;0Jic07u`85b$#2Ef>_jlihX2beJQ!+!u6U{j<(Y{ zGypn%X7U2uiD)0@1pF=_giqd@_==9%(*Q<30ys`L^DLl#D6k#igP$abXBS)Mi>r71 zS`q|MXDd||l&!%F0JshFcfVI!2m0AQv#}M@Xg#UK={hcxGxvSVXZno|+c{`^$!<7h z0RpDwB;GC-5dT*?9|@aGQM^mQ8qZgDaO!PN zJhfG$$oMOCNdc7e2~Ul4XI$cf?1PDdi~=}2K3hI>cDA{-C@&}gPUhBbg6T(|;p&Ya zYuXiE834w(imhEd{sHsICnE01my@UXFoe36Z}N1AySh9(a(6rUVg+yJ-4ty85n)#) zblXl)$oL^0T$rB7i~UTXO)xY)V0pYz8+`AZaJ=2Mr2W5(wz;<(o%r*t{0LXqs$TffKa@d_}X&h4p~h`IrnE*xiSyV}TeZbNj!`}1@bGiAF*4Y{G^hCuIN1p>8| z5cbM*h{GgoO3KG`lQN!${&oZCkSx808d zHQSCDb>F#a_L`~Bu<#J#-NY87D`%3@4`HjOS(;G&!3qGNzdr38%x3=`ZDo;-q;Fw}FleajY(XlO%o`ehq zZU;Q}cm3lx>oaexsi31)~B`H z=0YG#srUi!q^^N`(NKo*yX7Hjb4D#xDo@$uecS7O&?G(@dsjNM3yn7Tr*Y;IAU`}{%#f_b0v z0*PPab;-9>p92odPGFMTcc1;PxJnJ2t&48KJ34oNIa>g6hH_HZ&VTia*d?F}j>vZy zQQnrtBW~wKw_k?Ja?YR@BMO+?jAPe6<+V5n=J-}sj@Z1*MIO?Csaqlq)7~zAV5&Kxl+R=SQLM4Z8{cI#`3Auvr8M=uD#BZV%}0OxvcV8i~cyYp$jQzP@j}Nn=M{fUH)E5 zTPS$?j`U>zWF2}bTI*uwih?A5!OwG+f72+Y-F!(8e?b^NFzp~CF&DrfE^$;Kvqi5O zgIc0h;XXh^gE_AN}rSY_4vw_ulaxl0@5@45m@46dSu{L^&wdvndUlIr7_;U!G zwIVCQKo7+Tgi(Kdul?Us{^sB$D?inMs1i*(qtNTJgDsRCbhmH)lZDS21=s3>l-ZSb72 zlaVVsO1-dm^#|mczkMdV0T^I$;;F-HGX)7Lt{VBn8?JpG;ROiji<#KRKD~hAS?FZQ z6FLJ!^qGMQbZ_a2hF<2eb6 zp8ONJDQnsD)oo^^smFbE?g*`f@sqgRIsowlFh}dJOz#N?(%?@<`oWd1ZO`Mk*zUQ+ z1;#C>Yy+aO+{58z5I*_fxXFu4NoBW4qDMZW(4(4ti zd0-Y;(;^&BJy;@d+BVI2mzwm=P95NO4Ir1_ch-d8SU(^9G95b}1qQPbX1m&7O+1A9 zwp3XwBUQgk0w_j5m8G{a>k9%IWMY%%i?UQIDWI(nd23fLxNGd3ZxK?I(>rv+ghynq z*!VgkbMH3`iaXOKgRquhHkBPOBbrfnedPy(+1|zbz8yJstgjF_6oJ-N17<-!=6azHZn`(Z+De{UOz=_$k1jCqp4TB;JlYJnF5^o@9<#SR zmRaAU$%MgIxsCm01M};H6-d7xrzgOU(QhOy&eon56CxCmXvKC!)!v5Dsa1m3SGE0G z)nDUV#nYHR_x|0_E5r1vb=CSM@0vWr_!x6_d)%*od+GyRA-;}8-K4oJF6g%1MJ$M& zvd)|MeK^LCH(;Cmh&)+X`tVA}Uebr&BylX_qbp~4?_dY~mh{YUjhx`2nLyGr54^wth(j&n>qQ0S zzjtaHGh(nRDvpg5Ue&VsLzwz@x(%hvpfTbtv9hIO4Z}ch5QdwLczd3&QRX1guA%uQ zN&Edis#$lkTN`G(Bj3-_**gqKL(!}#nJB7(`z`|OFx8q<*>ZznVxAEn6QA;Ma1hW3 z4V3I~{6QFbxeFCilNT6#1pgj@GNq<&UZ@!zN;_tQm!p?i|*j>agKx3F5nq z^~0j~B7WRM9-NNe^djJ zrRx#c^W1I^%d8u;Bw&_fofR0`!40nHiO!kr8Yikd6q8E;#3Ks~Eeup;zaEY!UvNZv z8XHj)&9-Vd&R+jw6uwI%Ko&X+{Ye!s`@rCV$`hW4E8{`8OssF5jeY8_nVaGJU5S%K z=5&^&H!scEWT|1%Av=f)oB0*e9h42}!+f9ieM%tzCbG}7v3M6jXg221&$6nUVBD|6@Q_4PbYvf>&+%=*|0zG zq_~x3IhR$*q^3ZM)JOu=+6nyws}#|a@l$or7VT+!Pw%$&c5c@8fm<{yxp{n7m)gU< zQ@bhnT>**Re4V7nFpY=eGWzKk7qRQlX*lBet-1FfJ;AkFzIsWN9tuRvr;Mwg`PBjL z{JtcE0LD;W*%iB{+sHgHz^DH?;PLD$Tq)b%km>}6nC*JIhSk#H%?-#`U~4+SsCfjv z#6_PqZuL}D@Y^EoN4qC?*4!UC*<7~L%8vFp-?rnBV*cZaqcHyv_lY_0@thn4%tKMG zejv&_($QHxX27@bj_GFyak$uWD`aJ=0NwD39zNz+NMhJ|LnCddykSYc{b!LpW4otS#VfXt>= z)W`-w#0$eZE#*zDdeMGxV_IwVJI5xLkoFrN!tm+Ag zX(aRqLZMS>xq|n6&-H94W=6_G3}S9@%;3rmNGK~^oU9|iOoFf&D5m@vd+e8<;N&<5 zd|gJ(n~eIY6@0ZpbX^0wlo@gLddq6-kv&oeg@6ge95beDDhrP(3Z8X&$|w@P*yhEK zdlN4y?wBaAhnG+vseVwuFtHSrvt5_79U=0w;ZXd(N%k~s&5w_~6(`Tj%t#3SVdNhv zgz-YurRB`Em*shWtcy>7=$yYB6M4MN0mmC^Q& zkF9sPng?qQ&h%Ty@AHCTe)KBuRqIm_vGfRz&(Gp&!g*zb8jQr}k1EOrE!W-s4GIJl zZ?-sv=^1~_{GRa!@T#r^M0fQhxC3OwPC(s_SYMJBix$6(&Fxc>?$9cjz-z#W2#HC5{FeB#Bd;uu zSDBv>ZtQ9JZmq1E@Oj2ZMV6T*$zfyowvx!gg#Wv$kps}FWA z#fe0#YH78?pnjBz!^~`bVSJSNbj+REv6u#$H2uM*5H3PGfRtxK!e6gMiL0@$TAn6| zbiU5a65jGYp+~v1=u)KkdJ>8y!m`ge*In|v&SwC>t3DLcT2RW^rs)tB&oJ@SkS$Sp zhoTT41cU)%sN3|MVHrt8Sc?tMW0hg$h|~}KO2#=gCQi+A!*ZPoOrBY$N46}LoFig+ zXNoXh`a>;@GEppW|8SFu`>BQqsuO*4-lmd{A=5*Rv#(a(4v4-^U<+P^dUC}t7(da@ zkmYM*N->_Z(G2*3{t$M)P)sYx-{XOrdsw}<0S69I(ZEuMXouxK*mCmhKAIih=q}m5 zlJ8j0Ua6KF)@!&{8H=4FT;NbY8WnxBNK=xz zL}>&?cE6(u@Il&r?ZQ|@v8%uHsCjGT+PJK2({SHlpVmRbc+r_^oh8_9ccX=9K)uR6 z&?4LHaq-xRuJpYWO>8Kjr&;J)%?~B2hluvs7@t>Q)MDyH`<$>f%kMSc$8=OB3mTG? zxiJJ5X_1?0m4UNdLxR2k=F_lgwmU4K@%{zoO`lKtO95EBkIAsgVa{*OOea9H)5Alk zh?+M60o%5di-PZ_K!Elf<(3%32H2>@LR= z>QL)&45b`4Ec9C!%dw{{2!=fPVSFk*@+yBY%glkl;41l{M>DSOhMlUFw8>s;%D%ck zd6+YdC-5hrF&Pl_^hv@%z6~owDSzupKMHOZIM@47OIsXR%SD6!(!3};wrK3iS|`sY z>2v7|=__e}ZL;uv6ua(*gDNjK&~I?@%sM@b)p6=H2K=4P^h_x&{lkyd`6sm40mk*9 z$9M4s0w?N3&AV{nR~?(2-u}Eqj~EkgxpRkCBwDl#iAx(^-b-`9h7MZp@nr@W*KNU` zMC*?;UWt}QO_zrk9&FV$wwNs4A>rIa(-#|>?BjtqT%Ewf_+@iHDRjl9AEvy@7 zk5q_eO4%o>DaI7PI-3qx8513-v7XTv!xm!0*fFuK1PyBHJPbLY>q;xqADW$t8I3Ka z6)rd0NbH0Uv1pd&~{EyHiafOp!4|4it)~m+{ z`u9^>ohQ)EtdXs`0g{1&I}`VF58wWE;=toWb^wZ(@aC%DoPq3BEDp?l8WlMXFjhZd zK>JcJU`a5}UY0hHeXf3CXp-XLT$aRp{P>w^S)WOWPgVNi9_&5_^Wx>f>U*V>wr5pU zn``iqPvU|0?)d#?L`@0nv>tLG+?{=u#jd!2Qhno~1DH^B(L;TcbLrXwFjM*MjvijV zNp#I>HK-`1b zgMw?f3?{Ny6u5ves6_i&`RFBdy2APY;iqI%{EwaSVjI&AWg34*YB7lQB>;H>N=_Q@_uIm1gB=8PU{D1eb0 z-#cG|tK^CVnC@Oe`|ANgd41uB1kz$!u$zW6T9!WFuz15tnq*Z)2&BC(U=7FFuW|J#SE~0-%%VJF*WL zLVg(^I`v+oLDn6oA~{D}xJeVdv)m(~B=tn(7p!6n!`-7owA^J&O@`JsNsI&L91Lp) zi+Z~t0SbT3U@KLg3=svBSSPq0)O2&opjsyboEP-uvc{LoTxYALbmBs%jZEqcam%iVs;p3eG? zWf>*JSFTw$>)9ASQz_S6UdNDDPDOffJR_M!B12_%XCK^`4IE4#l4f^Lyo8O(#HRn9 zOCUi4sj#R5(RRyymR7a#&FuzA89Bm^{i^m!c%s;qH)v=S4116lhvvT)vpB-4uJD&4 z?<)GlieVAUOURu7$f*{>U{!A3m4R6?Lm;W($Yu(-sM5N_(#*U~@;%-Z$1wxclg~m~X;D&b57}AwETj6FccF=1#>D>ofeQ7Qbkfm!eq4`F^uj zOD$qk@wd>W5**=^C$VypNY)^9U?=8n{RS{9XsxK_A~(_!WgAuiG!+2lI^~6)VJl84PvGPZ#|<6J|rdZUt2VU&pS+q zA*UILhM=$r_gSJ`9kL;;9>mHX)6wAP%aI0b?#;@^Y=DxbNN@=Dq!_|NS<|uz%^|}w z8B+dCWX2WUpvcF0xqV?-<~v$iXdlo&vY%?UugS9VhE7C2k%_ZPd9n_q+)%gc%J)$; zVm)?CL(z3c(L7?S*`72t9!N`N4Vv@~$}YW(lh-w7B-ovv{d-3QgYdl6EU-cwF;n+s z>Pn6qo?@?Z>c*3`l#2is6BmsDGnzip8x!8ppX&!pl{IoE#@b>uu>W9gU3oihEpf3x zz8AG3`l;X|<%&t5ORdsw4aj$N%ZulNKuM&OT+=K_*nX#34yHc`RAmy5YX*?tNp^Om zrsmZfnfY_ctDKC6GF(uLzkq=3#aL+-^#ZU$&i!1 z8~q&06VWO-t6-c{lzmys?H@DDg!X)c*dNIS+U@{YAdjRKcBV6;-$N=s*psLz za&W=9Qoip&a)j1AEOvQwTBHS@D2TsEnPN`5$(VE1o0UXCLdwD?U(^o;w^0+TBuQ-Y zo?vdG+0Gkdq;-N{^G|YR-7AI&9PYbn-$~$B`T99rykvot zy~$7^M$gqmHZc&%GKUn2h?zR6N|Omt{rfPPN|`+?vhVP!v(*bu2M9eqIC;}y6a4~w zdFexMX_9m!F>`8ZN2E)2{Ryp$a$LCJt(z5IAU5w8LxB=s$I@cY!sXN?@>LqHOuC^csbi~PPl1MH6@Cq)!n1Qhq6@0pxs9D9xW~*?(Hf` z3nM~mk-3g0%AJ;#3)f`zX(o%(pbOyZb5I z=*W+0)rbkTd;`Diqnqcy>r}9ab zsAIe8BSlM*SK0SWVArYfi@y-vJfU@2jYh@tHwzuuWxy{T+e5Fdmz?Dn&kiizM^v(3 z6EIPobM--Sb=s_uWKjyxw!LP>-vJ2}Qpc&xq3m6E5pSyXCfYQfGtpYQm6%uu()YYG z^^RA^6W*+qegj#4SsPi5-^W@!f%oVkdOfLogliov2`QHz;s?*SjYow(5^vga z89)#;gHyn)#~`FKrv9P_(qKHdq$R9;%hwi*lZ6P!Y}?KP&j9Y^a`)ok%!543nPh(w z2;-@uVAb)kV%a#ys4}rreZ$E|@t@+TAjwooB~G?Rf9&H5nu-gmW`(d2wL!NKi`Dgh zZo=*kv8DKf`UzLYQ4k4v;Zz2yt(Vr< zsT7f^@2qb}tZPpwV%tmeHcL+T!YHi~o4XPDsK#OfbtCV3d%k+%uak+L*zYQ5>$Ryj zS&{G6Q1nqf_)vEoFjaoum(Bbdo`hP35Frg*Co;WjfTqc)%_^8Aig))fWnyjy-N}!X z8c!cxtogpgn>%J)A4qkdzeKJFkMFBC;JLA}*OKaf2`a#(h&0XiEqs9MvbRqn=eHIV@I`E zFEXKRw=f_ZZ5gf|VV>i05N;t=apni>|NfYMzE`%@nx3a>(wjknE5vOK}DRYnAoz2ftbMvmgy$7<_+^y9r?FqLGJSE3QD7wI0648uO zZF5I!dh=_|Hn3L=6CCP>efWi+R2#A%%MUY;_GfW*yJA?gf+th%&MORN}iMnC6r~(v=x};T^r)abGo?v zNj`XZIr*k$`+}sTJRIryElgh20FUmC>0k76pBV=26SPM*>Pm}AIG-hE0P0ivT{!64 zAT+lGUpg7=kKY0Ya4C0*{~xa2J&@_>{~xbZDn(N6mr4ks$o%o6Br2o0Us$A;d5&_grRdwqa)9SMSgJ_xb*Qzd!m%YtA`6FOTzhKF{OX z-NwCu~=Eu;5dlE?o?8z`pq!MFe{H&a@pHIU(Y>$rYmXozC^cXV8k9Q|56en z9zB&$?36BwEAKj@7Gg1-ACN!8@owFUDxy3TsQQI&Ra`wj?@`(>0+9Ix$jb=zl9lu7 zF7xxekKq1yQX)kruAtxI?2vfT&AOoR_o@gyz$FJlMcmP`Zs4D8+oW#Sv{Y)LVp)J>++z4&4Hw{DTZAs7v$F8rQ444hP^r;OY5s=5F@B9} z(#{l$Jiakf`1On@@TeZBg*dSsZgu0kSvn{}s-i!%g~ks52*2^bZ5;pwG;(0Ja1j16 zmCS5>p6KGkG44k^6*0?ss5ux;#VaLXlxn&s`I9hlKJKmPWVmLs8g`I(D-boitHC{u zqFBXR`VXwlFi;0-EGKEsHAaYQ%{Fwe{jpR_ z9TQJPl+OUPe$dwsp<{lxXuycex3``6b(w z_mc~pp&~)=fLj+*Hejo~8j)mt)ngAU1jcZ|;-RKVD!J;-RiMt|4tUVfSrz_$3>q3b)JS64-)Pxy5JWqoYg5gO->!fh2SAFe1Pd=jKCP18ywJ$r=2Pa31t*emRI~6x`P3Z*CRF(kEq6IJ^~}NuoK+@_8WF%4nSWS3;MrGH%dxV4^dFWIl+VGbG*v^ zEgTCw$hcTnwYhXf8N)^&ZH^4pT|AsXsh$BZ`o7;Jdc!U3-iQu`Io=#PkB zS35k2K-Ac_bf*DH%JUiE3S9r+DYg?v*Gi1jqFAk`@peTr#JER_L>68Z(Th! zkHUATJ5AsI_5{`d$c^EXwTQ7~$wUe&z|4k7PO(0m92DHKpYHI)WWdfLeRKf9gF15z zDSSk6sLM_Oe?`NY5Hmdt=#!6r&$wvHY+-0oiF4bB8yJL^GG?C^fv|1;Q1^Y9O2aUl zKZGzUZ_P^vuD-TsrSQ4}5Is{;UV70D*2yHB&QW&ls@Jo{-Tl{y#iK)KQ~Z)#TUR)5 zy?5`8ewosKu9S`*cNKtdj;Tm%B&BmeaoGwauhGI|#eYe3| zB!bg5H%?@htgT~R?N+I2qa27c`KuIu5!^xkNc;JGPU+&3BIp3P9NC3dS$PF_*UGq` zJ|hHwfN=sQl~tlWOotUKK|O zgA)RN?pag-OFYM+>X%CJeZ;X2bgqm+f2|=(uv@+3(4KNIRBV=i5DV0B6jcuS8>2Fd z)8_}Tp!KJUkdwoMf(M4dp?(fGWcb0Rk{7_SFwLnoQ-bD1HT?)KG(kI>9_)VBS<@3D zr%@v@|NSRohVbWMG6D(L#qn4HVMTfBA0)hh)g9yiSPe@`j%IouH0}pS=r;=Qyc%$Y z+D|L^+0Ge5AFhQ0c4_rjSKNfu(C(((C3YQY#-FzADhjZI6T%a~ zt@KasI=FF`G$HOyGQ9TOKmxSIeaKBnEyo=_6=FjR+W6xv0xrgxDxd zg>P};HVY8A5rn`jR2MN5J!pAjO6td&Rsj@OysN+{AL5%k1gNBbME9pK0)WQ&u^VH& zfcZfWen|$-^$dY`npTyZr_TyJgzLWhk1Pzh`>7Ig;@?YvhhSj91=pn*j_e0L|2m-l z&E0-q%5p=?{JEFkTK@cnn@KYE?T=rg$cg`LkxB%HMx=gKaz41xYXAEw9TzS!H4Cr- zKqh;6w*1Vs_@`$ywRLc+2$%m|kp86kxkRO8R4(r-JpD6tMxr_k%44CLBcU#CF z4HOj}c^@Acj!f98?2xeCD*+#NYj^$e+a%a^YdTzdbn%8dyd1o{XK1O-;{|Py!_R>w0|uSUbYTIi5K*uQ1i35)LgA% zKNI5Q+E<@A!WjfFrl#zx^#r9wtNgnNd?Wh zJgFvn6Cfz(_ajb0+QoK>(cN~3!NGzF3k##})$4yXfl|Nf)|;&mI|rBP9p~NW>)#s& z*1t;4 zRu}*JDcTuarffM&9YuH>a*z_!Bowpa_@)@#gpWr64IRYuWy z_-O0=1>QNu5x*L+m5*IN+m1Uih;CH#TqwwBw$FTdg!d4g z45~NeXMb~y3d8yg-Bfa`{?nw<6F1LXy&Mug1@t%D6OM+j1;|Z&LdSL+RN>;?onmq7 z^BddSACp*Vg8VdJkTqOW6txMo7p&~xOitv(x8%qSbC>>))V$DWi%q+f_2h3sY)e>k z%d95{SN7vh+=KtN{7*Glu<}2t4Xdvh)cOLh-8*OEvzp_uU}O2;R|5V(%@)%0R3HE+ zD4_PL0^C4LL(m_ublHU^M}mWKpvX5`^_nSKcm#euj(5^OwU9v7`hmca|89}V9x#Br z(tnSIA-$R@vOY=APO#}Fi4O7eER1CcJwMcwEffF~aR%IIf@cB?e5q^-PX|5hT}LPX(vZy}^= zudBUcj8_Lvj8ZjaSrZbU6Y z7=1kgf8(UO%Jg?2xG{E|s`2ie*N z47gjdEEinrJQelPU{Li9@F(_)zOK$=oyVNlP8A*iU7ppZ??2o0FJpI1OY*AsK1mMz7m6hta=hh?vO?xvji5tpCVelhAAoi?Jh8B+Cn zd_Z)svhPW$m0?|(;~2d@G$*$u;M;wOv>V!WCU|Z$q9U>E>n~}qV7CX{(hYiW+Z(3b zUg9Z>l~TF_=yE3?+)T>fwr}R?*@m-5nv3IfKjXR|Lk}9BBufBSr?&c=N`N+rCqpmo}5V(SDp@O)v&E^95+>jMO<>b zZAF8jiC-_|{m^IoogPcJ90m&&R!^9pcp+SadPFyeY~_ylVnz2FZJ)?(5Ds3}Is48; z$XX6tiX00Li_x^*+nfspsgvhJTRd22Sf6Qv(HfSU=%?#-O*ur?Ldo^4Cx(~enw94z z3Ft>?PP0#BK`>9LmFZKWt?*S1Bb-lxO1tC%v~X22b5V zG4{5uOx7}AKbUE@oSj7&gJ$ZDL%fW*4Wh&q^_GD__M99qGhGv895^;3^MiWwt%E9G z&q+BO0+apjy~Dh7jNrQ zsXx6SHacEY8!h?TnnxkT-55Hyj z-cJ@xUl><6e=*PHrNw@+qn}+)Q)7>mNx0mV7y2>kTJag$a@J}$=u7D_`>f60s54P6 zg}y>!-Qb|!bISN-pVsN3*7u8*Bl@p6_U?A^>bM;5b3ys9GfoAq6MXR*pr#6R>GDS+ zYB$HAvBXJAUW@7g*4#Kt9IgnnH0aId8goC-@nYT&=482wwvh_R7o{!BR+Vq!y)$@V zIY$Z)rE~ z-Ml)g*$moRTe;i3y==WyW__JzBZUwZzw(2-f|(tJQ5TD~3`9;d418@voctF|kbTub z+Shc^bhSC*>p{^C^r9PAI5f=7)-ONbRkM13)wC-=>B(@eMs3hm0M_pFRT*gd)sH>{ zewUyE-DAMYFsV@g?6Dr{)!s$M&)I1}ThRTA5fMFRIz4q!d z$)r4TSIAmdmGCE!=GyF#jKr55$YF({ZxL45mCfVIHZ zJ4L_uK18Y~0Bc+7KVs6}u6 z`zjP(oy>VFqHrj2GvRd^>@pO;aQZLEWt_ksr@V9svY8D#y;5_c)38llynv^i>!}TB zN7#@Fb+U@75yh*2ACE+eZ8xc8%n38cYI|v7VNAMA^FjLPy|%r=+D+o+9bw^;G*cP5 zOP|xb#G}_l1h0dkJ}=P5Y%c_Hacxi}Iv) z5lOk~`*H=9EIganQSzd~8ez6{i|^y5{nG9cwoj;2+9+nDrn`Vi4adk&N+=AhCT2Q| z7F{bK+Jr_M58{Wztox&O&U5>`heEG0shEQ;Bxrr+O*moy0>r`kQUsqEGEFof?3$q4 zZxzN|@&U|RIGFL;yBAYF9%u^@nNe!oK<$sZqY;5sFN_P*V{H0(hp80_Oz{|jHqlEV zKSwoEAG)xK_TN&~t}Jn&>8_=%^ME-$`a|%SHV}{YDRx)G=rSgg+~0MqMHOxhkH_(e zPZbIZKK*f5OI;&-V^R(Vo)oj?Jg@natQ7dqj#ySNf$J2}N)Wc|#Jo))-21ch;ezf^ zg&iK4BA(o(N^{;CR#N)UC;23&j@@hjc(lL72c}E9FzcW-H-1w-p-+>iV#2&_J(RH+ zL9+%1Md~@Ep}KG1Bg@Y~1vsk>VT_2p_|m^@@fn1UiizW@8ZEnR%xS@=K3 z3on*IM5Ez#GBYXB0%&l~jV+I%3N+nG+ObJFJKu#+_uf@N0QkcB$V6^%WEl6PSKadw zY`O@i%)R$F6BB;`CscVS(aCPUoq|sn@M?SR*>o#*`u&1DstN$k<>g6WHMcr+aETr0C=_=6zV?RpWxpWe z=$nZ1-umDf8HO^^h|}AobKu#37V}%jT4KaOdF#s(a?U!iZ&wKVvc<(wMbKwymQlan z^Uccvy!O8<|9Kv~jaZ`CQf;dy{XS)GAnx?Bp1Sxp?eV`~e5s{c2MK8rX%r6s@hN7= z@?RK9YfqYeOteOJb-s(c2$l|J=8!g4`a#gm`N`36PWr_E=a_R@ichm13R1;4t*IiZ zFxp1OJ3#EM6-VvC;R5>wtb_Xg478RI%U<3(`S^$Fuf-k%Apk`Le?(%FGn71Y3Iz~r z_*Wbn0=Hfm*Bj8s$7j|a(}$^=@_sbX-f)9V@b5KYoc_kcR89HA*msD!e>N$s!Z#c^ zwg#5Jd3e{OzuH zwaV01Ur2l~*I9@lmoB;BOYg&HZYZ+KWYrUvg#)xZdd?QO_JjU7p@*VCIKq_6yR#~h zFK{z#qa_0xd!c{>=>2d2@Rb4(sAV?7*jU+x)5Buf`-Ht5FpkW-lYc0x)MA@jXwie2 zclw*Me1A;hCb)9m3>pRrbuGYI~+73m{G&dh*)0;e^f4aEo%$mW+H?*yT^A%EQX1cqnlUY}Z19zi3Cog+v|E0_*eN{X@Jgxyw z`pG)X&4H6I{1940wppF=s@qv@co0%rpHltVeZ;|SHazlY0T{?@fW!VLuQhP5NOo8H zLs4zWg>QE!H|p$}N1tPeuR~kCFjbSSb{5dfYxQ1BXHu5*wUQY19z-pflo)G7KUO`k z_Y7=0s+~{I;+_EG#{x!p$8leQk#~q5UtiGSNIN#vn2TRuL(IrM(s99FWq{7Yq8D@&hX4oMBq+vtj1`H*&2I1cyh8DUnG zo)Rp^ulN4r&@l!Uk7Y0pE<4I8O*Hf#5Rm8ee!M6Kd)x%VJ?n_fREVHfsDW1Q0ovch z>MEgCs1;USG|R{~f@vjW#Na@^)?yRFNIku@JXhF7CHJafzBA1*P>2Am{WaONfdZIlOB9|RvveJ{& z5S~6@P5olaUXOj7Cj8$pu4nUSQpSb%$#Kt)9_O1>wfvv>yY`70K&q7y6Z}hQ! zV>qYZ@5e@J$k>s(ZfpzEA+`3%DP-_;;3`_DFTn0P0Dn?Rob568C9$7%aGnVz7s`o2 zg>p_)16lka+&XE1do_n!4kzkug7s1|W&~NTDzPo8Up<547Sa0=^}_mXM>k4ox&?c6 z6;F3x9r26YQA>8kp@$q+eMV?d(nSS_@N#=#x6qjnw{LtRYq)0s)Z1@!OA9EsXMVN@+wQM`8~{`acK}y%&cwY zo}|6C7%$(QcvqZL+nE=afSBeBUN)Q@CzoRP36W7A@Us)m)@ncTm&fRG!`0kU{@YU? zmRz1miIJ?9r5ftbigEW0`D>q^+yY)5_5Ibb z@;z|s=19UnGwRtau4Ymk9zIdNz_k{Ebg865-fr^@$)1S0m00UHApqqFRtipW$>0#t zIL|Ateiw5#f2H+*gY)xYZgyV)?x5wp=EU9bOYUyJLJNPpD_MfJwcdOo4u2`-`SIC1 zW0K_B0V^?Bm^3Tiw#+r^756LgSno2c8E}xCaR`6GD+)Cman~0^DLsXMw=_e2zwvbh zS_j|VP+x8EDc^B2j=t@g;C*(MVu%OlApDdJnghutI!}n@9tzqec%lH^Tv?Y&<5Xp z;dnod?1%;v?y3o?g&fc9WO792I9b`C^@Ko4M^uq~VT`t6d6z)xVH2$6yM$i}C2?%T zXSnQ7t)9}NzMCmMmyEWAX&dHvDH#q?a3#cSxYxV$aq(^HC0GtzDY@QcWOco$WTE}R zt4h7;(b>;2SP(s!>RI5zeeKkmpD&|-?}W{_NWD5${B z3$OjMfqJ;(C_5x>3LWXrx8B)zOW2udxxWtuOIF7w>$3P<2#WAJY{!QkYvAh zn-}yDid&PNXejNg=4^utOzSKeXUwXkG&z0}=g`q}eLAllz#OB!$;AXcuVd%^V+fYR|7~z8lk?lVS|iP;$RT z--o?3J=LaKGNt=F8An^$4KRtM&fE0`TKVX`7-dsE)VckcoAnnutv1e6!8-`s`l*H> zI=hg@vP|{Muva;Qw>-j~Un>3sn4*A_(lxbkjh1R&)Apv`Nf+No%k!D+yIKDszD*PQ zC`6lWvQ1a=NUDsvglIwL6kW*=>V3G(#~G-Z{l9SXW*mZ@dK%xx{PbXZy)E-Fc`=dD?5+_|~6EWnIO9P5&tBQ9A}Q02ZnJWME?8-dgqt z`7Y}PB6->N>xbBH1;?Nsz*(I97xeW4FMuIah?hg*5SMjXO4T{}13ia%@z4KI7T=VQ zMm*x<)i>SKGxV4A$lnr+xh5KMh{O*2$%^ZW^49PGrx)Z$q6()oT-h|Xru_bX3~dd< zpfHX(I}`5@%AThVBxOgc(~QY{ev6S;yB2>o45eX!s)=t$M~cy^7R$@6!uyd}-boH+ z(`00GOd9%ocmQQ%LLUbaaK{L$d@o2)jR8XBvwwM#Z_*IXpln^qt#w51;8D`|QEZ93 z{{g6RF2meCqjA-(i@@!pe&a=bZ5#q~KuSn2gj~$HCm#3U_?vK3xe_t7TYR+qORc*$ z80KLs3Fn1zGB~iogfl{7)QsIjqf;z)#j7eSdx*`4Si1<5UXiT`i@}V`jkg8dV(ZUl zxUNsEPnMpebM&NZ(*5Q95ygfn!TnTL24XX_4~CIsqITp}De%#!a7H)gP%I@=R-%Lb zFxw~AobP#jzC`!JbbAk|_H8$)4*dz&N|hKYw8uW&xl`2qpB=tP{zuPo)n~ZU$=~mj zW)YgN7T=saD(d5t@fbj`8)peBHM-U*g#tl;KfQc+W9kC!XRd-1bz@Qu86POCYsLFH zmP16A*ha4@Q~$hdU$WXQ5|R1!N%Vdya4<$hD&QE7bPHY-lJJ%cnPovzdwQ}np`d=Tq8IO3~6xmbfK@87r@q;Bg z2z|2?alA(MCEOIbJBYau~6VaoMW{AUw8yG-;< z-~-p5)lL@6_-*&)1PTrn%lK1BT}QdzwP;4=I|i)%Hsn`)RWkocz|zqARg-(}!8u`# zXL>2Z)$^a}GJiv8Vb{{;UI%lg_2tP0mtNf$1F_d ziZLbx^+oM}_Atu4o@SPF=$`cVlTC-z)Um)!))(sRf|*?&A_y=tak@{yt(QJiXn+zz z(P}Rehb?7WSC>EQSU3HNkLY(?k}`r3l>y@ZE>>WY6)>u*d*I=+5AwenjJ<*pi$H>T zIae4K@{3pLh79>G@b@>r3;y#j@t1PL)Q_mv0cwL{a+eP7XAFrz9u4I3RAA`?u7Ggc zepOB#oVoavQHro;i95DNviP$=gwW4A-Zy*+d-x*unw3#))&=#Ehr!G4i!myqqo%Vx z+CHt`q20&HZ#cA4rOYdO`m&@c&>%Z+^09XCWWDZ!x6@*Z$m5IKNUO$IW}ohzI5&hi z8tr(sKQ!ueRYK0~bkF$`;e)*;W#!2Lh~vYx&7~m>o51sM&+z-aJSzfTs4HRnJ8AE@ zBwnFpHDzTldyXWRdnfTL?s(-mYdizYTQ+FgCl+uaeV9bbbPmccaMEd{%tYXM?Tfoc z{F2(6CcLCOQ#}f_6(|$k%sO|lG(f4Dogx0_+)lNe_n{m$&c2a$ut)B1@>#ge9`T%S z_0nO@Pqw%c)84SOYeSFd(`dK)%!eYkpxU~vpXLPC9{ZJIlBdaj1 z#O_UA4E|uvJ9lmJa3l37z%9^mK@h4NCcaUyiuYyz!{ZC8)c3bi>twGd+IOB=KJ`=K zUtvHfvC#T=(}@%3;<}C{9OVKl67}&^yUy% z)m#2KW%*4#JU*ciPZpKpTmM(p&F1GWG3cAErZ8y`s_9JNV!yU$1gTd- z>I=xH>k>b^>4Y*q^oeGK2z28XgFR)~rrFm`t%;S4aURI)wGEmn#n^>W#@5pMLsLn< zJ}l6dHu9i4zsM}&5Z@EDSaK=FoaBcCBi*Kh7t`(5PRLmz%>OhP=g9hQ0*YFIZC(e= zIcsyKwg2@|7XS2(KCi#OKFRwo8eFo1H81lgarjVxC}6^=3Z-pULs~am))zm;3a6JB zh8RsXx|jRx_rDAM`To}Zh>rg3K$c8a2z0eH2mX`6rmy9uo@tzRRoB1ucwf=p(>Ev2 zJ-z0faBA%>+%{m$oS#{&n_#wSVk+PuNJ;$p&*QoOnB#B_3KRLV0vSWM-V-K2 zRTpZ`pYLKR3~PUQViM;1B2jU8{1ls~i9J99tOEL3F~KZW2}3TuPnM+nW>o!pXHr(O z>4l+2+QeSUdkyQ-Ja~bssi_SI>QKjHQp`{QVc4*$usB&UR?EiclT$au8xA391$B4$oFV=RilKExOhCwZz7~2u(g2gH-v=Oys zB+Mr-Dvij6{CC-x)58i*ovHn@@n?)`r zY=0nDT2xCh_9;*9UOJj1bF4|&o}-SdE=^^5@Iz(Bqj1q`bOGlnubS=`)0+zHq=tZfg2K>sNRdlSG zcwEDS*Xlu=Rr6ml2j_1|Vp6mZpJ_1<{02l<%RCI!olFbQt4xxP_yo_j$F#6k+l7h? z`qT&Aytm6^7n(M5k~4R&#$~9mdvV6LAC;@-D2E3m4uDsZ`FEzSI|Ar~a}4ml>SNWo zwW#I*I+bAlO#h(F<_pKj>hgT%iR~+W7dXHy+c_at4bCP;>T`J#MWJS8rbRVmwTL}| z;k&s`62s$>VKWh1sUF(ikH$A~a{aW8oS{HM7N>PaSEg{GjIi$?Wyr^8r}yX(=GluL zb2|z{nU%z5{j~^r-ID|@Uf0p2jYt4yIDcBt6TJr>Sr#JYDVrXSG$lGs*^ z|FeDkH}}sD{j#fQ`^qzrAnjc1nBTJas)47EFPJ9QRE`^da+X>Aba3LgvBGtmmhHYO zW-{8W2_g+STnZTp?ps+~;!ASH4Y74}7SFOSls*{J0_7sJ9l&Y^!{Y?_K|;{R4a%%Y z`LS2d4`0!Px$;1GaHenA$)2$0qExB-AS|@ZIPs~yUq1W;MrSkh@7=GAL8w%oi;j@! zTjZ`UA`^I0*oehv&I(B497iYc(#(?!68#HZ#(fLA3GNL2w}u><`XjXb(#e&E#k_vm ztSPS)L+%oVI)>hTOf^WruGylGx+Wuc+x1HT#Ee5c`L{a91*1TM+}BgSahW9v+kC*# zFUt`V8^Q1xeW}7vnUi)Aj8ebb(f4(XqcsjA_Lg!eg`16Gn{(V+iF9$8(2Y9wCZmTd!7JcH~m7#lr3* z=gG(LfEO+qC#K(osL|^Idi+NXTHcK=>$bP8(|p)QH!oQFbL;pN=7VqiVZ@qSd9RyG zLE4S!!hq~d((+Mz-iV|fpidx;vo?8+5Q)`V_ZiIP`&)hPhX-EfN#JJM^H$p0;Z#1l z`E+3SlmW9t zT-j!@;8c|2E5pMUN=a9uYOnR<4f>??XLB>J4VAB&`y}fX6)WQSV+TXkl))Z zr(t(HsQ7y87##N~Cf(}uY*sc1U0byJIO;lS&ecejfT0d%^+(oQ zi(Y=ChV{~tPKn4YV({4?%$$1M&D?%nc2kI`3xYW!*Zv4I(9nQq2XXs&6H$384 zlOAq&jaV}O=aU&63^n;YTeQ#7VrsN+=Cu%7wAeN3zs5uQ)&MEl<55@$j10XxoDDf~ zbMEf9c7Y3X97H{FEeUm^H&<2dJ4A45eUKCkt-wweT96TbAM7%3+duJuPX+iFeD<82 zoZ!jjBXgJR&##SDmbYJ_S6%(%^Nf357>JvyB;`H*R2>yP|I)L&g{QAOrSQPWQKbdD z10wp@7Gu{Ze2)%i!mmgv)QK@mKHNv0||W&sTv_Q|3@6ZFVAH`Z?aa{VcOzbe})-~99sjf-qlO-5(>Mr5A!hI#jP}8AaTX0E7*idfiI}^Ze=0+-il&54U-A5fWC4{?z=jQ7YdVrx{ z!j!_Y@aDJjKQegI@_dIX_N)Wn`VI{iSBrSmY>KWft(FU^m+CEu`!YLHdPGySq*VR1 zuh3RPYz8&fZ(MpMhu+p}URrfodaEy-B~5I4LV}X3mm8K>?@wKB^^6t)%pLwsL+b)U z^Do9J7PU6usJgW!r>;^vssT(Jz=;hV>@k%flSxW8A z*SF;G1>)v)-Wf_eGJ!#r3sjCBwoz&zf7$$+_!J=wY>;>D27X9WuKV=T zt%jrNi_SG+4VN>*y8M3I5s&=Wdzs4FZGf$+z);9nNI(1?0`wk1Zp)4>lDpi63mTzcJxj8VuU(!Y zej!}nIaEiHa5ro%VOaXQ#mR>+#_&jYo5jcI2k0%2mLeN{Z6lZnBG^adbn7tNde>d_ z`D{7`&v}Iki4g5H8xNZ}{MK(tYt#;*O?*fUN9S?&GLcUAeOo*3Hxri$`dFIJua1kTwol;DWgAI+(CdrD3Ee4~XY-BQUg+DQ zD`U+(_%wL4DM7(K;+K3eyJYOgdYzrpb6M*%rbe_4QK6jb2Y|=jirbFu`TnV{^?c}hHS+0SaSph9=aXmP~R<(jx{S}YJt)W0*UaeQ^Rr< zyb5nvHJj;~6kKDr>^gvrhsM9iXrfe21-#fTX=sk6ug)^1%M(8PcP1uDJ$reap3@q+ zLm%-;o#Lh14Q0RgOQ{F4AN6+R^sITRbwLnQWh%K+?oGOJ5wSBUU18D zKbWiT(3-YZx&5HltC_*hU>r-4*6^fo{|x93y*7iPpw%!ZVBZ-nIqQzP@qJyOrhMPU z)Cw4Tj%?*P!}Qlq`y2(gLFP+`S=csDvju(92YH#{56HQ*MD@~sWxP?$;hP=MG|em} z0%8-mNcK-Tki!(;ySRN7PJKpxlSx!I*o5x$?UmD@gT-BoSsYpx_Q@=@O>(iCin(gG zq^FfNJ~Q53n9`|rQL%^*`)0*qE4SYvqJe#REfq=>KDu>*WAQtbz4s1Lxbtqy!Lh>4 zEc&R$S8dO94bpsqZ(0x&x7lnNk6gb-z<@SoK-5>wPx)1JHY3DS3W$vGvAq?;tWO=K z*_LIdS=Ari!oSgzz>?>0NVD@w(H4P_Np34YTlDp*E|U9l(R7A}XsxA)mf0CaFYzSt za4FNw8`VzX<;cr&`M*uqbGHTna<5$4JUb#7V|z%$NaWBa%gcYO(`r_-B#NE$I<@7_ zo!U~w3yX@W2WE*Eh1WREada@N+w$awENaRKZ|O4`>d4Hs@%#IpCbI7BK6W|-$6&Wd zG_uymEQNdR?$nUlwVyOs_>-ns?tLj6YJCgOUYojKqslx6n3Y9M@obOsOo$)wOqsGc zOvzVlN!bj@v8!^*Ew?gpYkg6723L}TIP8n%THI%oB~2Z&_7>M$dQMl4e6vq4e`QYb zw7iQYE(u0kiVJ0jik7xY^k&VXUGa;!#sE?TV+?DQHV!rq?ve)7Nj2Q^uoiaNg33$3 zZgPY$8v80m;h0obrltW8^p5hLN129Y<6C%P!`~j3BX(-@uP+aCr>!#&iA?{3IddPK zsRX&UXM<|({t#y>cB|L5y%Rug)*I9H9|6!9v6gn8xNXAcS zU6v`X)<$>Uhtx5pggP+ND<6TgDst*flyj-V@FgxC%%Xmg&PwlMQ(8DY3Bt zom0+>*ixWQlo9KjqC7l)7xTGjL@Ov{FaL0l$G2B3=gUAfsJtc=0|rJi2Hk0!Y_?wZ zz+HKQ-lMVNg8JiRTAg0I^iN_gKsRGqh7z~#&UjycN(b^o5ssyU_qjfdFw6gv_UU2^ zl>GgpH=p54JYi`dW)-^uWkg~D>Ah*`1nNe{*lZ~Hjk36>cJQ4*5tT2o?+A{9TD%^X z@&Z3pfE-=>O)zq=4siFfH^ZNYAK}HPoQ1l%z?@%OE>E$CnF~wXJm62vzczr`HCjWd zTlCR{t`Ch>$qb;F+eZk5bEp|hSdLy%s1)T#c<8mCM|y2rs!$q%(@Y(lOt80SmeHHH zhH2S^r>Clw15v+)$e%VZSuYF(`D@P{okeqgUrWiqenLPo`wWA%^87A|E@JpD*|szk z6FK5Mi(RvyfJ_=emO4^Or?%2a9*z7Y*H7&&JgUujfBu~5Z?KZNql-f!?ef?PiaTsCiCx}4BR1g0{) zk~*hVSmoeALI)XNh|N4FAe+K{z2uk(^h4WxmiD~&?3!YUKMlnH7JS}n;QeR%goU8W zQ{hlqOk0hq>ecDW7p?oyO9_l6@2lr)zLqm3TqP~o8at118`9X3pB^zE@P%JEZ z!^&a7nML}erfqW{v~A;@z_J{0D1Rl9>o})jZm8MG%^=q8RN-jj<7Oz6Tmiko6Yo;i z4F!r027^3;z8!sGqdz5WUQtqr{0dGNxv=$-qJAC<1_UP!o@aG6cWYU!(>$ydi)gSTp3wAqC+Kd^H2j=urr zPet5m!w20Nhy163Xxl@zm|ZEXv^yfnyWRzJ1K+6MHrrxru`wmhq^!x_Ea3FB5K*~C z9LSTAq3>j|&xY3N;=@Txd5!h2nBTu`@tt5~g&Q+Z?32|v_LUi->ydBjS<=P@I41M6 z15`M0j&mkZjp^xsh3B(P&AI==3D7#C5~l~RRszsM#XYAm#eCvu$G@q;W5@h`5GSkH-y!Z(l#3(KSDIA-i)4^|g_-eJ6Ty8w<<*@_Nznm+zD^|xuoS)nUVP->dICe+v*c3>SE zxw0iYz~6}(g`cGt8aOS6CxPTVAbFt_b^IgE)ErMx!wqsPK2n`x#482BSmHuDpFW#U2O(kCQil z+3(HKi7(rCx1~^;%mcD7gMwYRICbh|_(OHV?PkijwfV%nd4R?P&g-g^BHQPVroPFZ zz-54sY+d`H;F272Rn{8upj%Euj1yT<+dqABI01itxW3ZWxQ!PHAr#*Ka-HLcC#k1# zF=o5Ky+`!kS=NllO4AwMp7OoYl#STFHe@E-wwZIZsBF7hS^?q0d6`M=?AyC5QHv?i zZB4R%+k#aeH}N-Vsehe%rX)fB&Vg=0z})*cM<{9B!`U}CFUyM$cAoHC860ChcB=Yk zKo8edaZv3BeVzR(dF7}93u~H>NMA?H6;m6#5(7?(nzQf2=RR_Moc8bCw>ET-m$QG# zaMc8B1H7@MHKGdPwH6(G+7!acv-h(96m_apI4<^P=IJ>@7JM^&;_gd{*h;@X?Z33( zNNT%%@I(00r#F~twYrnR=cZEPRf1D+E>F(}t51!|F-uT|X&hPvVbMAZZz4nT0UF*7c z^*@bQBdWIQF1@Bhf6d0~dPKKJ(!cbxU3l!YIiXDBDPkqU-m~UPQ9Q#u#oH+I#?9|0Y0zQ9P}AU6of@P=Mq@(;(=En)6;bU% zZxE{8@7YDkEM0^#Hd7Us%f>2;DN1T?>1d5uH)QVWO}Qx-$vS0ox%{GP^7DT`Hs!wm zc5jt#s(+1OfT|->?V*Pd|5)WJ7*7{g#7(oNJ)#}r z{NJAl>1F+H`aIF?6424uSHe@?3>{Uif-)RDmC71e3!hIoJ1JFGva#*sWZUSTzw+^i8P_P3 zM%Vd7RV}y`7I1&%lWcxf^~sJ1@~EpO?ul>-?xD;VwM>d{P6HcGGLwDnX%Ofm-CU$j z;=K%Hj+N>7YsC#-=`}}!41puQUF%2X;aSf&Cw+uJQ@d^QI~Urhtn7AuorjWDfr)}Z zOxy=+6}Gt}?=fHMvvtqAXSk1(Qe;p6Q2D`7>c3f?0dVs{Ix;qMbW2|c#EqTSPKPM) z@vxI`VUe>+@QuZi9SqUS$KR)2WP^y9>90Tq^ENK@f%!$EO$*s|SMX#hC4a_lzU`6I zq(f|h^ix{8hBv7%{OqTP?h-xCIu6VVP#-}+HDA7ZrJNcnUa_qwK_iJTaYKC6z+)~& zi-WkL{q4Nbs*#Nyi%w8>CxM87PVgSx)bv@h*x-mY*O%?lbc3Ay=)Lc--( z4UeWzz-11F&lUPB9nbhvuj({uHD29&$lVLCwD;U;CVBdU_FDzo;5PcA$8TYLPJ(@c zNXFv1XljR(K^4Vpn7l|}0$;XkN7$L2@GXWN+P2v{vdFjrWyGc1HZN6$uegz;9kob& z93G{>n`GxjGI!JG{P*!zr1?D0cUm2&bvvSA_}|PDEC%o&Bb71X(&)wwyP^laq>5Eh zcQ#@qZ&-(WV_>femu0%@i3!A)67cQ`bj-Iiqzs<-V`^Q7ET{gyF9y$0H0lzt6TIcqz#srFuD&&&x+*}F? z1|)9m(ubkf1Ve$Q6cjBvgRn=or1$kF9TbG_-iH#Xj@_RL^1ReOS~SvCIK;rXtlgM8 z%dptGd@56N`r)Hc6mQBVB-mP}iq(UNmG?sN&Fp0>5mdM$??hpDD35ML+ckId&g!9r zv7#z$zPUje+N7y-F1WCAH#+@At=Ot_j`QJeqsgwA5u~3LNt@T_Q3a1w^?h6Antvf}QpOSDIUVYjkPzZUt7{<_o(|BcrUGfOhn z0L;j+O!mx(u-~7RyPT?Qku{8bIWRxPkkSXmba5-?u#?c-Txf1T#2~`Nc}e^`99~M( zwiGBazO?o%s>A0~wo({PUh-71noM`>-IYe4bZUg@S;f^#X2!|B#?!>Pq_6vp@69$# zrom{1dBzMDE-Ij zG0sQJM{_d>b!#oVu4hkoY1e0G#%eu1i6*v%B&&>n8?GDT7|Ge&rG4XIs>Y`_giz&A zq`ah>ek51q;N_{Px~P6T5k_2U+V^6~@OLMm^e2XJkQIjS&-wkx&u7wv|^Tg1@6D=xsU&u76;Nh7$-J_*@ zk?ATwoq<*OwQACF9QqJUl|AR-PH`$|T0q27uwF7}zwFZJ#xY+<4a(9K>SU|c8dqr!B0BgOP^(Bf zMzR{q5sI(?v;5#PL3_grTT%DR>%ZJDX5?b-Ygr`QdCHPug@h&>j<@cBV(n*!j;KhN zEzidQyp3RG9(n50adCRDefD8~u4lCtRa=7ELpSc%)q>?zAG5E9g--?Z5rX=G({JEV zKT}O^FzN1bP@^>WLpks0hHzH)_|Jp(BVk*sL&y^<5p13hX|#-KkOn4iruu(Xvr2eu zV3?Wu(5)G*vS@_$ooC7OaXH&FjZDwX+gVf9W>#p9o6$dYoawiUwIpn#Ss>6QmM9UO ziKkOkFLPwss3t2)+6WZ2ueKhx1R^f5ey}1f<@1U!DOhJpo*?c#+JaUWwdt-IGfluV zVh>N4pzJ4KCv|Fgmvzd3SBV(b5nNv@dFENQ^PHr|BY&Tsy|UT{v+#jpi!`u8ZJX6cIz;LC3;3EljFRZ$@E15ua}j)eZ?AqNSSs5brUkQp@jRcSu3*RuX72xf zlyY^RNd*CFM{>h2XfJP_YvPVfL#RN~ASc!~(>5-+i!Q(kLLpH-g&s(k2zIKFy zE#|%w=nWh_Ot5X_8(_Vt=lZlaTVs#qCFvYupRKq6_ zVRoG_;_QbPe~KVtcjT<)0)ue&>@M67R}(%aUYR|&tOxbuqBnbU&XG9YS@DN(!nS&XHcwV2 z$?GF8J|>2QoHz2FRc+1zDHHmhsDr?MomGDq zbMfv4)~g*{ZKDDwtKH1BOp-;3G$@*cX1=_Ae}!4PD}xGF6xK$9aBP0%>chz#Vzw1a z9qmC1{&%*~tdAhoJbZP*Rskc9W5K06nJLc3#jjkPp{Z9flR8OOu`W85J!-{2rl>zw z*_Y~k*=vqXU)d*HZ4Xk)JK5>Da}zPsl&!olG;(0gk1*kD$w79i#J*jt;$c^R<+%D2 zk1b23LsPgYaLK=`;>j)dyv8rl$jcSofL;JjX5i7i>k)VdU+5LZEinuZ1FKl^iQ0ET z^CuGyu$aCn91yaD*;5zKW%BcOdm2Xl5ZkZWkDWK)1t|G8ljGpAw>%bN-x-{DaMRgX za&WJrp3FIp2HVtIE9qr8-E-mi|Geo;i*g5R(ZB7&d&>QCJ)ZO z+Hk#n+C`m0_mzDkyTa=mD{cO>G#^fR^u8F!#$ieeQ6G7hw?Cr}nFyhC zXzRp|brR#-C`p8Glwiov7vpcO20-dx;*w4Iv9j?x?&;QRj;rx5YI%vfSx4>6LknlP zpA}?lU#AuWyzf=>P4W3`+l8T={y<^j;Y-GFVwaakdlJ7xq~H#-+WiKilR*Y zKfXUOpMDusvPSbnz%j;!+CA+B$C+i3!}Gl!6PYm)w86>}n7@MZ1a|A%*^sWQia*}yfnxKFCBIySejQ-`?tSp^Rit8Vz&p5KUQTVKtxma( zuTATX8&~@;CB)tkyJgfoupCnef?IAalqWra4k)~GQtU^Y#D^1fx)g40F+^q!mqmhIk zAiKn!li%K?wu$mbJD9o}Wv)C`&U%S0T=uK~TdE7=_|Ve1yl@H%!?JOFcdE;@RWklX z+x1o7A!YTcIa@}hV{DPm7(%;1qSR@5!w<@35+S6MUPi?uP37WVtAV%M`4%2PQsWPg zl=dU~{>&*3=ER84@)hK{ zT|JsQ+RyD?b=RqIRb-k-3mx=0)2=45Dd&sym|zLbbYl%CVr1;95*hb_(1zXa*zpZQ zxE^Iv*w)82OeO#37_E&Q86RUi=oZ|1qo_g55iWcf*DXwnhEGuTsYgbA{`ip&Lj}zt zEpG9}jr{L)Zo7Bcv0lw&y8`q$OD`m@(-BAE_ktc3!sgF&mMS-0*NtaHpPb!OY$DG- zC;v{wUn796^n)}Y|{k=C%SR!;yEAnhxMwfYdl+APyJTq7>O7iy z?=MNa@o|H6nN7%4uMq!1cL1)5C@9X1Q&9=(TH~+T=HRtP_Vo`+Fgn+dlMTcPrsN`V z;$VVB7)AL-^=PhHg9z^N|v75!4PkH*SgOeoE9tv+S$pOP}<{Su1WlSY3_KqIkQ?_fHcC+jO?IB-7P8 zlarBLu|nGxR0mn}L+-PNntl~J>bS=hdo%vkF5S6Dwd2@lN?v^r9Ql0%=T77=%+^- zde73L&UiMrUcudUVfXPHO8%52`JWAP$6cXA=9T4CN676++btEvPGLYG^slaX5n>!Z z+|4`;I}mt-v9k#%v8!G_e-Nws(16^oYLVxfbc#a0S)A(*?I+3v&ij5Zl&jns%=mUx z+EuDQaeqCJ52_q|WPka-Bx++)k%~=KdDDINj&rxUw8QjSTh8>s*JNO_r%9H!$} zjYd}6U4maq_qW%vuwtXP#&y5S?W~^5BnK3qtsW?|W~T^gAiSv*M(idPk%;q!h8XN7 z$!W>Nnz;yncoj91(sSxxWg=3wrpN7XK!+{#Xgt_fF;DAD>)Jq+xZ0M8Z{L}V*+#W~ z0zAI>`BPj977W1h-CW~EpWqeQ4n}y8_qa7cl;idbdQ1cFKe4~L2Y5uwmx&>Ym*#1H zWl6okpF3f#WsAO$s{1JVHm+dU9(j~1)7YoE@`q2(nsP3X) zP)VJeb%z|r$tZaQ(jtpmDnWy5XqbpqYt;8UpZ5VFV)XwCiGDr>m|KQtUXAVf>VjY> zV9J@7c6)oZWSp~JyXVMlNk`IYnpyLUq^82OXOWQ^k$dHdaM$6 ztCjfS#g{Md`?pB&DJ$;XusQwn?yCAEVPs8Pt0|#MvaZjy{(kIL4Vn{GZ|+_0^@!a- zRXE1%)wDZhdp<&XG5$(rch-;LUWCK91*gY&(nHe`K;K|V`{^!jAWqIqWG$WOO9}}d zw{%qJPx#fdEeogTQ`Pp;KM0P)Y+nYFSJjqiqx*(q=E(x~6KKFj%;#$=J}lP0aqEJB zBZ89#%5=21kYr!BE5~Y;AG%wc&oXgv$^9W=yCc?ixaiP>LATwRzrthxlup0ooROOC zS0$EyA3$DI8W^f-QS zAj;j}#BZ@5*#P=Xd^LdcE5a?Os+AFmW#07Pr%2s!_O{nh?^!Nkyw?ztS%WFwpdIM!uP0cOB=2=mx4Z zU$MFcfzm4|ZFls!UGVj6_VZ~}e&=R@drpWmUZ$eK`%)LZcH_wr$1rHDxrSJyYRUfmr`K)@Sa6#9pVlP;_@Kh zGyB`w^7rsnF_Fm%hk<62BfGP;(}DXvs53y@ACn+}Y8Xv9O}l^SgjIU^P=LkZ{ojj| z80RyW-8cH6EIBxS{Ibm!5 zg7pS$wn4uU{-s2^(n-ezD*NH@(s&0o#&X7wVs6_pfZc|9)qIsngWx3==u&haU!HBP zHU2J4vt<$zn0O-1l7`9!f~s`akbN z*)!BsC1~SGpp*V;O3uLEt@r;oV1{>nj)3Z}@f6vR?~m8+I({e<&PT)*g5qQo;63&>9Uq?? zcgLDvxsnv7#Q07SRZvtrsu! z%d$mt8h5mf78*MIl3WihJ&6L#7J6PQ_nxZ!$OJI5kN@WZ0>&JS zV#awNd@Jv#X{E&&r$(hCG$t64vN_7^!$-mN;x_wz@NHZmq%tL`xy$X?N(I#Dp2xT% zIe+l?#hio^Lg^UPA|kU*XGFAuX@1WbgE5o=cdH~=uyyC`uMa~%Paks5LPm@hNE0Id zUXgZiMGD3Iut^jM!T_Nd*i~Ykx-W#MkH-xpiae&*SYv%I6WDIfB$~b$7XZc_)VLgc zIDi}5Bt&Ds5-J|Ww)u_FUWA%cpw3zd9}Id@AqtFoM8;OSxEcyFA>(xHeFlk!3XMl( zPmls-)D2j8&%w+L4VYeyay!}02q-1dwvQ0H*dORGGL}8rZp3p)G%k#pTv7u-=gy4_ z_SX`cix;SQf(HpkFAd^%(wIh@^wNsGMT?~pgln4xJc)Ben z=?mOB6Nr)Ay`{zmw$ef&$aJk_4hH%V=4Vz@PYe-Ts`W9#9X{)>EY^f9R1&+yfeBhO z#w|wxDiRvRJ`M`{5Mnzm&+7)W+&Z67 zC9dK76HzR-d^0hMbUVEjM@X?Az-iy2OM@!$(ao(?8mkqR!P2Q8GGHyl!EGw{cLNb( zGev~%!h)0Ld$`U{YnEe0(+LIahv=h+>h)!zj$AS()r>!&=nnXLxTfa=zc`fcNxpahHv;EuT5>8f9w~|Ih+6ixsAV z8}Nbo4(9wQV#vGYvNJHyZB%&@gW849z&)R!289!yrkrgX@9{3H0tvYmtPtK&143w{ zbh_!;A0NC9&;mD0gFfvIo794EEt!5IKuO4PG(vunHf1}Bm}zm-5*H2@6GHjR{1jJH zYR3pa6PA&%>97P7LzIbGwX#^$3usg{mN|On_MG>OcGt^y16yHq1M%FRsZ|{MX>TkC z)$hLfN!t^}%G~985R)Aoi}@-1j@@F1@0gv@x=Bi>v~G74_;(C{E>7^zCD-54U%NEh z5>n)%6iHb7D{kL4m-wjWNbR~2$0KwY%w~N4Yiz7Sh=D@?Ypnm+-cn8%ZqWkSy}5Am z@VNXNa}VZ-UO*zBTA#ts7*u~dlM+tC;>NIVEhp|_BM{R#w-ZX<{;awyjn0-ViO zmTQfZL#^ecfbeIWzL;jVjXKDeF;6QnZqI2x%yP1tlWzp(9ykqKLgY+-v?j)L86`W_ zo={@w{`g8d=lEx&@tf z!Zkq!lkpga_;Yx3_=f$7{C4(kU$;u$N@!8B(++bMg4wuGD%_Tj2}0kDs_Y1J)B_Q? zi^?p$q*f@xh1t;0=m{{QUg2ffGrpAKspE3Eo{cAa2Ym2c=i2YNLxA5qHIJm1p9i@@ z6mwNMr9a*50`-HbHl6={(X0(Kfl2;|{A#*(glKN`Z)r78=axX%BCevCeW9sGur3sT zY(G^hb^cMzfLf}mK28g$qh3WgZXmNG?%t)A#W!2*0@816n7tz_M^?2L2-Z#8g^nkv z03ycBSVbVbDMo4t?cHkG*>N4FwdIt$iAPbW0viEIs6rzU%~EcL#lB6;_2DN>$t!SanIw2bZ6@y39U5n(e4cg=;E)( zYgsq2M+UFuRVo|_i}&K)rN-787hit?PZh@W$QkFb;|@swH0j@lxO&v{*wPX)dt9EF zTu|^@Cc2F2fJyZaR{TRrev^aQD$lyoxpt8S*V3+!znYGxienvYEEu8<$ zx`CG6s;EqvajLAQ^(-Xx!K0qQvgba}W(ijlN1RYBz({P|oaO$EU_|h@?LzcIY#%cy z`(8a&2$k?V+xfOsfBGbs)$A|4Ryxk8DFJ-!ik1LehACPe%nQpzQeI*Xo*kNcw&7D@ zi;p{@Heoy6qS---62=%I&7;qBR8Z5`LyIVSqf3Q-rb5!2DBPT^Ygv}ZUrd83*9S7g zMMAECeK5VvQGJFw9ijhvIv{LbFO`c{Qd8~fyvK^0n_;5Dr#%rS>yxfsfG^dQ;ZC}9 z&!M{};_c5jeeKPmyi<(AF8jR*f(IfoG>+x&Pl1dOCnoYn^0!2ba4-7~rc6`KGEh+; z;1gPW8WKCz>y@^UC@=;`X()lW(R-%^u6dYmd_J4%Luf!cJ>eT|aAvgaQIwA5d~v;hlMB)`Eh(O+PSciW!DW%5>> zqJ$RRW$M(JN}2P2>DUc`rEJKy?!i%V)BEVcSc>gz-WC%xM{CL zOQ6jh%T($)<%1R(a?3=6Ztn|&b6dhEEj%sHTlg79r4)WP+z95~-3BSY>`H&efsxZG zg{xxC@oX@s+p#rKR^#J|uc-P4+@o9WL_5pi??!k{N5A@=`*%@2H1iio_dr!|3UCOR z)el?S7bS$M_&cJMW{9tqW&XRIUCJeoqwzxu}s(;HlV3BDA0^ov;l?VEJ<9_`@5^I-ZB$SQ2^>x{t&#Shw||T@z#0pCV$z-X zK`q(i)QqyT@dF`{)W9ulh8veoaW&kWWjPz18&jyQ1gaeND1L zAk$bsjq5e-#WT~S$?!*jU<7Z6Z0F8F*u5+elNDG;2jHGpmq-QghSt05QG0SYr;}}@ zS)LMS${DZBE~IS%WY9^dD3q$x^B%h&cZ0Mrrk*O5gE7R$;CwEEJk#s2lN2+=#L~jf zFp2!^+|X$Bc%yXsjSLw|3E(NLY~X0K=FD<3{EHi=HW;0r@V3Q8NfMz2f`y^QOA8+i zs(erZPA9{v`*;LP>d@Ebo~E28;uMD@pg6_-P@|*jG_P{=%gR7zf@#8CxZyg7T}VOC zNXT(3h=K3R{NDD`Y+B*{AufA03*uuj* zEE)Du-;X2!I$cc6;GB!Bm&9|G<#%YdU_b0z&&4ApbbRSi9bT{HzZrr^aDTo z3$OKNh}GctK*WrX^E04`JUFn$XFn)0KA8Hq83cY6H$$x2O5UVhX$?g?$JV8@`6m3w z`E{wKc9PbF{eV2DX)g`Yo$F&E;%P52rn7sV$flew6Xc|eUsZIm+sW1m7>^~@=*@=c zbM)VEeKo0m(VSyxlQ92H zy+Jw!3qgy3;WvFWLWQ$KW6dpNE7GOE6>5_PLYxsW9>Zq#1%<4@ZE5+d()8D^q~I7M zJZbf{j6Ad0r$Ppax^PM%Sb+j7>Wz^}u1P}6PIL41s&4yz(iO4v4&iDW8YW3kG81iI zbzD*CG)%+v>SJ6**dps{yfQ<-Deoy8AUvC*BZ@Fm!@EEZ1yCA8| zHsqT-$}OGxNwrCj#1t3gL1{sRgrbtmNALQ0AZ3n5P1O4*M9iubPmo2Ww2`P#aw}nh zSXHc+oB`|N>n^nS+ee}E?Z(|v!|<1z6Xge6Ts=Zul35@ym>eiQzNF?b@U_&?=XvB} z+KRK;O^@Zrp%CIJ^a>sS9ZaSnt;cy{%5AT!zr_bk_{*YC}N#Bauzm&kd$gPhA!8wh~`u~u{6J1BFBAOmZ!-F6YWNEw;FNZZ(| zy`mx&QPnF=)CZTAkH6qxG)Sk~rQM&}BWv!)?8Wv>;{pmlLG+f{80Pvo?=7~2(mMml zCvlqfKTd%CxY?p(5?TDhl&gp>0Ae|_bXZos(?Xu%ur@5@t?!F>YQSi1|@JJ4YE6tU*#!iVsct4CJ78Jc4wnPJUz;degZD& zdZK3=5aJ4NRW9Hd_HIeFJU^)j?EiF+e7Kngc9TVHK(tBzRmeD_ssPra9e+=euaJD9q6y$p-Dg|So(&Ztqo9fX)n-k z1hJI8;6fBk{sT%X-3IuZM3#N|^;E&c_35iir;Zn&A+p3Q>ffL&#Y}pZamlp@u#C<^B zK#;zz=?t-g(3uD_EL-40JAsA_TI0hpti`tLx(>_T65JJKpnO4@iISfY#GB>o4Z8;7 zP+u-{|pvYg=P`UfJwjv(bW71s1LW*KV+X zR5ILs#0hl|dyh_rKxHU_g4_U}v_O ziO>Z%hZJ1cK*SwjoAH1kBPHbn4G~IQ->lqHhj)k%b~m)Bu# zcR(V8&$eoH@p1tkSy*=nj$#M3O78u)i?*=cC|2!vC?szqpMrfKyvOQj3Yi^0FhziF zMu779^U@Fbgr7;rBS5MR^kVukOy7^*q2u{`{R;97@=w4EXq-`C1$Y3tA+UG=u1dw} zXjG{l0d{5={is?oN21ASFiPO->^af zWFWBx*8leuP-lQDl_6y#u%HJ_X_3J|ajE?phLvZRgLhG&)=wxAdykwa>vp3)18hPE z$Qf}Owc$^;a?&l?gWrQnJO}|QAt2yg^}6*C=n-`GXUIBSl02BYE&4kGSRKPYGcIh= zOX-x{ZWUa|e_b-ve9m8oLr-E6r<1OP23K>of^k4624dB;fj7dz%0HXbMvmAQG0Og@UVhr(Czd-{}48it+4g(+4IhXn%WtbEDECQt5{r zS-gmQh07R*cC6~ZLKH!;$nhUE;61{TShx^Sy#qHmM*7#$of0Llhm!LSUpk@$r~u>x zjNvIT>%ZX&rM5&Ir}Zb}05nb3=4XNAUNitXzW(P8nr?%MBYd-2Ao_2tjg`g7G=Icrh=9e zd}~(!BlJ9nqb5-1yAsqGNZdRCw_HP{yv{P-lw9zi^EUt=7DB#D;DOE$Yg!G7xDCkj zzDyBuHyTGkTjgj6g8nf;$X?a8Q&-LX={N&Upk!dZn19p`RIGOlcL4W39Yj~;G;9wj z&J6-V)Z2vrwmtUv6k%7y=Et^qT2Ozb#%bx50@-aKgZ&;C{7oTxWne25!1izAK$F96 z9vo3L8P|JLa-n$sd`76VrW`{izfz`RGaZqTI@GT za)?1CTEo-ID4V&)hQJpbfY&qf0P$T0Z%+=XFPFFI) z;5FL^QIV~xy>z4YFGaAO*+}H{@ITf^oKOJ3rIpdb??`mtao#DI_!&I+tcc+s^XF|! zMv*gg1pzcIzJqC~{@>S6gS4fQ%+o5j=ez>RNE@&XRJ8lo?uQa)=m6b}cF-a@T##ig zmpJ2^a$G6dK%KqU8a1dAzL*GYW8si~JG^mvxREnGR>9`laDDdPrj@n}ugn1T^za!W zG#nMP;`HSH?ecE+#i*H3yYIhF(yH3;mqn#KAim-JJHDxUX}1>VIc-f<~mbrK7l#49%X2H<`xcqPR?%uWXCI83#2SJQahmIqV zBNL8~HQej${^!!WJwQ`tw^tl*Wj;+Ng4U%2NS_4y>B6i3cw_HJv+8Vc_8raxN7?qY zUj9gN){!_jh(drh za0Sq<63F(4SnxqBlt{R0gOJxt1}ynx;D1oftrdNVm7Ton-MIf2Zme(w?r=NErJrUd zIlePRG;sD~*%_hkz}y;${wjx`KOIfE_EaHN{)Z9zUhMUVju5uQt6S@Jf$HeLSU}Ok zMfY{9V0H0YkL`M_g^Q!<5q|x5i*EnA{IHX*BtbP$=wv#2VI9y(hr!hl#Z12=3a|Ug zO=CbH321O>!}IHbUkCXI_}$EZ9AMKJC>%Rc*#%)R@Y^}OAm#^x2F!KbSM`BRg2e3& z;FzQGjB)@SprX{N_I%SW47f3nZ9YZPKF(DyeDPrqm=}Os z70ch!ar}+;-Q9TL`qCNo5pUH}3A<$7;{#q{pS{TRtd@W^j_&%ID!(&b4^}hW?~z(T z*Kt1CqV5|uSohypN7G$I*#Ie>NFxjeyM~JO|M+{~n@cAHpb{uwP7AhI>_ma7r7eMu zd4J(JU}81AR#U$;zw4oq&aDmSnw!^J+_f|MTS;B|)(vDTrab6#YziQ#NG_~#1iJPh zlN3QEgWy5~Bb!|Z2;UPx6%Z)QQZG#VoygDCsmR}a?Q07zfV&ey{okR zW#TUsQ4JrN`e5VNnSF=@$n9^r!+_S6?U-@wO5mY!M^pqz@sZhwWB7k>oCOY^%<0iq z3g<9qY0F|{7Y{Qg>+wNy7D_7hn6CvT&pV7u!N)EM2TDO%!$h?h z+L3M7(e5-0+#N8)12S3#_ys6OzY_+dGKKZAV)^UL{$H!VRT*SLY4s-2c`@D=6pyk% z_ry05uhn*J!|p%^CNL( zfOwO=$Xj7)MWK=O%imFlf1Pa7AQh5@H1cXrKP0Iac%^{Q2l8A@#5DMN$aPxQ+oQp! z0HD9ksy&5`C2(2uH>;g!tw81vQcaP%3EJ17W2%#_{Nx0rni+U&nk65M=iGRF`HN7w zuE!^L6pKzpLKE@o&&NCe-wj|HWRU;*SWU+{LxBafQlEgSO8|}jC)xA82OvsT^-KlI z^4TC}crC!w)Io%@n*hTSpsrva<|vhU1NaujvCr^U2`tC%ul0yt;ow;LlKkUz$;@Mc z;IXhIe4(B(E&Tt#2Z?;IS!>U2lhm!hZUf!Z|9%L*C-wU)11LldIkHpsy%uD)!b8sA zP{H>3nSxPYjK}%mxK$<$SqoUc2;mq1tlqDJjNdl>kYjl$Cl+9I_4YA07@5Jo6+EW~ z?3%x+?#;eKRBsR!#~6q~yodw^%6~V&+lNHtsCG{ILX0LHC@ik!g1!t+k~L-*1}KeY zZz3H*zFEiDH2wx?A7&-mb}&`cLV{JuQ;JANt*`H*@8v$oR5Ah(!0)Ja`SEIdH!yIb zuGW9No`mKh@BA}Ybm~Syi;+O8;DSOx1EhKzTR`b28F>}WthQlgd%ZM=8*O+RorVsV;%)L=#-M*25Nno#rX6Yli^t{tVGXP;gPBVm6lP z$ZrS;G8iofKuxp*1e#p;WVFaQ^Rb8QXLYE6zwOO;2GS$2@+1L=F94(fSsV(9inF#u zF*oH<1`eS?dVrBW)ac`CQ<}|OOOgGG@_!5aarKv6SQdj>uRBPUs$Coo>O%JssPn@D zF^L!o1XLj?q%7H_x60XWV;q6`TjaXRszTWWr{lW%q_?k4iTl~%)5|zh*K7ClpM57@ zoNEbH3CE@<1|V?sLT9YWNz<)LfZ?ozs?QEp7z&P_0-5WM>Z2*<%oL9b4+AXP)*GnH z2dLuWF5Bv@VU#ErYFm#%BGdlmNx19nR05G}x37Z_6$ObnrAoYFso8)O$hg4&_r~9I zALqlO3pw_S=w9UnTPcGLA4dsib_=YrB&q+g0lpT{k_d6uK{vJ?K_RG6+jd*i5}1M` zEXUj3!nt!cgsZ!^M)$kaJwT+`c#W!SrG&h+743mG)aJp6Zfq-+s~q*H=x!Tb1SyGizzw;+;aY?{P!S5 zQJ!Lm44<1>R?a&E9%k8Q-G9MAmL2fW~pIW;g7^WWvTZAH{hoh=XGIy zoKkfJ1ht6SbbcL>ktvc&YOa3%_!P1cTrh71ya=boD7!EPGs`V1_drKp*!G_&m)W6M z0Eu??riw2Bhx3~4$tvygDf~|d>vCzxjn@pjL{z>xzmnB^{ZXX`ycp~oh4ubItZoO0 zCWCo>Cd<+kpGrM0PH|SaRxrrrYP(ZJYmpR~EBl9N9g!T4yYubH%u)xg_X9!I!t=A~s~7=?Rf=b&wszP^)%lW75!!VzPb$F2AWbIK3JoMF z*Vq6I5)Z1(?sCQViR=bgj$|kr7pQa=sy>5$oZ^|fdN=kRx_`*pq^4<50UBYWWN{9B zGYzGAAn%6?ZO)*i$pF!(;u!ea@jE1i@fet-H%maWk5cD8=zbO6r{B9!vR`D}`LncB z_-KL-a8eZbDSX`*VbOtG*u*SZ2G4t6OhA4$a0W!;B8gp3(j8vNkh_B}BKH*obDGhE zJb7B~NdW7j(&ToaO8*PWsg|duTxPAWy-u44C|_!OR1Xxn^&Y6p&@HED%1ff9<$4yo zq0R8DCM$6}5gxrK4J&HZSS0Z(}Ua}~%!m2=-cL$&RT>~)1(K7$nK z6}ZFuL$6y}!TIpADt(H2f}kM31gwcyfTo!NFz^rJark=q1gd1Be9b`herv=UR4q9= zXQ1vg)v)Q{H3R&so-pRb*B+ykLfK`>SV19>6^NPzpMjv+LQOX@$1r=mzSg3Sexf*O zpIEiNxpW9=sv+6N;tr5&;?hDC84RG_P1|nKt{6*a@-9X(DsyDG+Ac1aJ-(fz%LdArC*9KJY zyI^?0^pWvf=^jSV#wL^sc1aVXkvdu(k`>P5WAyzPKi%f}!N)#MLK8`N&}<$Hdz3lk zL%8(;Q(%>92Th+22T*~$q~U_b3A;aPmNoaEpufj7qk5`jwc`Bkxd;a-tiH(j=8lQf zm65^6R)G}^B#`D0#DX$&Ndj`t1`A5h8x-}~+9tp2TV~wZL4J4zOLv&$I`=f0R#qlx6i}=KkGl%u&rtPLUtSXqOp=tR7E6{ z#L%|_2Jf;ghIRF9gHXeuk@^dj(?qD8(%j?81KB9ugxLk)J z)6P+qb-*A9Y)J9E%(n7K1e$!v>6Nu#IYlm;CK6bO4?%kjMLa7&%;w5gjjrhaJI^4$ zu|fLxQyFn|>?0uS3f0@6^1O048_bHRSzOtj+iiMIRPIHCM=E(}Aa3o|=6*w)^ho%K zUk7&O5TPG`yj}71jOJ8w{98e5FnL*+;>c~&2ilcD_e}olGIN$ztJP(Jn{*;8gcauq zi=-4=x&{pMe%deJ-+uX3TgyWWsh#MSoRt_D%+sP%3$l}uk(8^gt4qcFolMAmkMA>d z+<#I(jeG#i!V-vtf`XQS)|6HaMS8s_+a*2DwDczi?T8t=8QxO@9or1q*cea*o06xC zcoi)Y5vQ_b3W}CsFW8T$c+ERKfYKSb}5rUPx;*S zs8qh8%%g58u;DD9jzwQ2m*pslW5C8=z)2f!??M85KP5zOwpoLMQLWsgFt{ z;b5j4ns7E5eQ)JX(1^AoOs!>tfsk#3WfZd=fF{bsr*~(% z=+|#8TqnuBF@=7CtVjc{c+EWo>74Lfl=H3k)TBfLN(E`!O?KBiSF ztfk4nGlQXu5=KdD8fb@n)1HW;o~pKB?Dmc;8}t9{ZjK)vzt3kyC-h}oftF>K(yV^& z&TC)S(>dIR$#6Bz$%f?fvqq)N^hk^YFtp8r5{ti^FxND3KC2uk(7-wnhfUywZ|c#5jt^D~u5v>Gq6TpMh= zLddw_Q!;}2EWA+U8$W~H#Z7|191@A~`%hkF6+GnDPZPSMlBdOgQ03s9@qMn#|Fbrz zh}S9M#-DUCWO}3EQuEaXu3D)jz`!sxos*Pl4gLF#5ygX17%V z3)I19m$2dFpg;=84gl--pC1gM%PzNl|BKct#3zJdT{z^TNgSPE3rvN4F-dV(t#%@d9o_v@=u=3j z`LMhMqk-cV%|V^|3!w@BBBw*YeRfq2OhT-)r(bkB$k+_4{NlMelJR*LqLNS9(|q_b z51uFKqoDv6Xl6)pR1OFuj#z>XWf9AOwOx+-k_q1X%x6|pS|s*vMQu%te-@g|m*kZ3Ej6}_Z!Z*(qAL@BWO;)x0OY{J{Ah38!iLUD z`##T_7evP(u83j_RwM2Tz}mdQ(LgAA^y0HzuxV%7=vL^$x4r^K1|8h}db?m(+F6j7 z%9E@#iK!=VKZ={R@mr!dqlLn3i*ElA2G%8&Psi$!Ie{+ZV>mXR9I-#q0gph9Vya|1CjiSu$>&!+B5I6y*3MUH&tJFA4E+K-?hqEp#)f_0&AG zH7#*+F^L)8-Ip-*d;Qwww#Hp(cz2Zvv9+rcW#Ack>gTr9$py-mZapRQp#pO?QPd7n zlS+%EcV_2OZ#lz29{ncnkCm_XDH=57Qb@l6ok$Xv8(Ji`UdM%}k1b9(Chh$V7hf>$ z&5ezFbMqa1Idzu&jimdP5rw^~oGS1PR%h790uowtifQ7PL}4xo9%V739VrRymzEU(>(P%Vb5RiIcKVRlnqr4A!0I^>J9m?=-u17LK%Lu`Y7b%75JLgu zx<)~I{Q2mjxsO`u@^!2RTa4HiM22LBG>4!3ZExo{N%DUo_E_Szc?-|?C%nNyubCRibaFF_>a*a@j3|_y;Nb#iL$8Ox#Zi$UNUt~>{e4k z+FG*&?F~^u5>du|fmzbVhUtM=H@#r$4^H;T&`)SDfTZLkEwRn#eHEQSI%m6+jO0M< zEd?Q7jrAfgY5ONEQeR*iiF0@Syu?mn4nb#gW9=7rz#LrYk3=Tv*>5cP(jWZ<^4mG% zGKPnblQF|hyAApR;S(ts0>Lf~uan&rGhamME2W>mBtMW9%!JNCTL=||(zDt6UwT+p ztk8$z^?j_liBoX9!jk%B16D@(d9n=r2^5yvhihs$NNDz>Dlp-cCMgO7dS4%1(acx9 z-&_6D2%5otTFepU`bJG&$b6T@#@6};M^g=VeLeQp99|;x zI%5BiuJaCSGTXwvN*5&{(jhdZNEf9S=`tXosPqmBLZ||vcN4k@(u*Kf=|VuN^eRY^ z7J8H32_5c^&dfRI-sjGrGxLxy`SxCG?X}of zod&;=#=FX904d2eOvcTOqhW*f-`}>a!6tg4k2UP&HR#vt*Rq{S#X4+j>i-nc2 z;%gg(x`e9mo0_ejN-3J`WtTUW8^w`LY2LFrsiORZcIXe*r(1Mx=9{X=luS_8H}soT z+7AqW3vk-Cb2qzh!VL80(yul$Z0}wbTEF=?3bk@fFn1$oWLG2na`dX@uT!2!$S7>T$~{~$qP95++7~NAs`ix~62ie)$L`&#j?yx* zaYRKsW^Ycp{Ys&Y_1LQIVoc*@)Q@B^>vrCkKRQ&og&C9zIP?ask zLEd;n8Wk&mbF$G%^-z3{5JF29-l6Bd+JuC5Ms0_PUSsbX&`OwMiXWi*Xa$zv^J1zI z=$aNdwp=+&76>ls`+i~X`^~Cp)QGi)pTsQGs!PV2l-1hgI^qi{BxPBzJ1xGx@+fax z+OloLY>}#L^#lJKGA`;1)iOzd7 zX@n|Mwh_j{?FBeZqB=hR;+99E7g8D^%n=HFjr12dP$WEVHfuq+>huvU=@rtO!J#K5 z?bzGVXAkC5J$p0mj|JUcPOnqDHFkqB0Ov@-c*agbR2gFU${WlPR;8RmW}H{C8N_g< zJ_efV;9&UT?6$I`yk1Xc94Za~N6U&MS&mw9V&HyzEZj~;fs8AGl}k?vCDuN#Viuhp zq73WI`qUH2d^t5yk<&h%w@P{Qj8+nkNp46Q)XKAt_1BY{a2`AaE$%8YpfuUM$jcFQ zUc?=IDeZWl!b3a)%{*(ZwQKB2wVMfwp}@d54T+D?8J_X4G#-C?okDZW21)i ze#G`&&`b2#HLad-=(g2P*lm&bU$2>W6Y zFC`?>AN8@rjhp0nPjpaV3Zr+kY*Jk37F%H z;hZOPwc^CGHHKXW+{dTHUii|Kb%c(DM$}}@4{;wEvdTW(r8B6=ZIL4r_)*K!xtay{ zWmYX_=adzc<-N|7NJIt}5oj(SO-vxP9&Nu-V0t0lO2Fu;p#ZDf4Moid2Mkzxp5G(o zQ@_-~I{bs0>@NpbiMg((SH9%xTT6W z83O(+-lgjx6Y}_~Nx<(xNC$H@z|ZUKr&@)a%DR2OFBbk#+ORhU^cJg({U5kJ?u{Cj zZ?r4(3KSEzdRea#7&m7?MVu@Ck{T;Lr(6*S#}tiG;QXA4854}T9O;#skiKkMXTXFelGGcpPFZc2 zDO_?cDmN2%vwxTfNQUVIrp1zYF$`r$=+N ztxqP68(i)lFnI@>+fb)Tz#V74#)=eAAyMp_jEZ9)ytcNe1X8?U7u%6OOHCxLyB~zC zsYmhF-hv+ak6yRA!1al(PcI8QX*&Y5wwkot;Goec;;`EbrlzR?7~4_49p1mm6U%jZ zL8^M!_lkUF3)5_Qi|G}=wJ0TMA0876L#hag;pR(bwHem@()L?x!SZ^Y;eQ#IsMN@0 z@Ps;1&e=X*^_JUX<;4{vsg%(+OILPFvz(E0&n*JphyGv$AJj^MfxUo`e4c`jm+}j6 z?CG68+4THXPqpqB#BZ3W|I3i`McXYv? zD+}@&JPyn(;K)5o+uyo6OvnT$zCmEoJq5R7o!+4{G0^n~%{XdViiL2FK;)x|f!3`= zGdf7K{>6P6kRWeyM_+xQK@`CAVFTNO9(2u@*PHepxpFK4nSmSBsilbBneXQ-;UX*{ zEX-`OGh6DC$YtS1CDr+U=n6appPj)^ta)snY1y^8_flf6#H2^NmSp<67>=H zNvRLr)U5jSB&*0+kO~^oG-W=s$L2ak?hxAPq~CU8hdMtF+6I$SR%Q=`_SS+(rK8r&=1RD-dm zWp>-Gq&=#hj5npC{t@F}`V*U8kY+~reD}W+&^{a2s9RE;K)<4qFyp2C$*Mr&7=WgjevUzCpv7GxCA zRctMOkT%OX1BKH3PYH>*2@_b1pv$$Bhla{V<~6*GBfah0JWtB9=nomt-*NtGCGTJF zeB&wbVhckA$*W}F&Use$NZpGX4BmYami1_RsLOfSAjhk1s_hzUwIA%8A`oEMOwk04 zygUcA(w`Nh@ZBH>A>bB+QX^=L{wa;T8_~wyk@4z+?4}cKpe;mB^S8SO%-RKa9P62A zBmP3BtvknubCEv>Vp}*azWky!j3>0anF}t+Up3DahCeTBLE1|m%)VEz5#yBeImRmY z_MbOuhE-0~CSSfn`H|b3jFlArk!+BV{MJ}$ykimX&K`-(h=H6T=?Fb_`P0e=WO_>VBJkmTXp_{%a+DW&j*C zcLRW?*8Yec8h8s81}>X~hx>=O{BQfj2rM)?>q~>%RtjEHMr0bG^CBmZ^o8L!Vlfd^ zEUAmlb7OeJeT8{RplIrZq=ULcU+Fl~)qwkh{zIT+D)GZsNU?P?XlJjMh6B(u6NChl zPE#j|Fug-4ji?G=5<>>8B85RMQ;bIl-P-dxV36sd* zYPeZ4u)e<08E|@KxFpvV+N7$hKdQ+G%1b?2dcrB2Hs42c&_HJk#M@A+`>8#9uoc1* zA=*BS`AXA6@fc?3W|1Zn=aE3{w7?UN1;*FmCV>oN_nnA;7udL%l#*bF6>z^6r>Q2Y z0Q*9uGK1KTbh`{2U2`}zCTT|nz>5T3O20GdvFT2UQH}{HZ!0nmO7=CZq`y*#w~yoH7Dw&Ap-X<1s!MV5zUJ%fW@) z4;Z+w#tR$yI|+TM^zHqw+IaT*#}7~Yd6;DC{9r+r#NS>I6gPC+rQ3g>n8OoJAixJu z*1A%eQDv-4C6(FN`b#CROwIHuLrI0)`Zq`}91N2Kok9;mXDrn)GfpuwHv}PYC8SGu z+4+E=E{EX;$xI+dS;U9>{N+r9eFVRFqeyQa+o4m#|(xW9;{Q!oU`#w-l ze*7O5gz7%*&|_aZiH~G3vLGwX%%JtxO93@mzSMVxQ6y|>gJ?scsuWpr@>3C z_Iw)qB8jL*jX85%gcJ3*H`t9M5T=;{ z60H4-Tw|sK1A2^#$M!Dq4y!)XOQhgJZ@?oMs+p0;Xp8ZBo#N1`1u%u9o z@E!kFQdWKAB$_6x-|25r_`E<#d#=O>4Cey3eyvbJsvHC0hY8c3X;m1aWE)cqRuLw) zGSpJyUMOa(@jpze71zn#{v_fyM|(9i=x!qwIRqnW;n#Uycp7;?$wJ{~VJ9Si_m|21 zPS13k3jsH8if`*dCLT^0vU8T&A?oZh8ND_~^`=holS0B!?l+?Z5e4}CBgi}?%TsUj zAVpdCO5vjJxxNk-bbT><3Sr=B4_6VK-sv036+9MBe_qqlUEAAfgiPFZU+aBjTQ_5v zXPK?ClikD~!bu)~Jk2BM4BqOC5e!fAcuPRJ+T1X$;KoEUaIQ~d@6${xPuEF7)FZC1kOeV3spj1Lqq2TwtP&;Peu-@;ooVvI$)~;AgoyC2-qh$r zs5djXuDEG1MB$6w@|W^Bo0$n6=1P4?xHL}JYufL>C9gLpHk%Q~sRTylULV72ym=rG z&*piY7DHpvRz4_h(aCwz9B%A~46qM+Nn>%fj0|P=0L~_nu^vo0R5+fe#75y;asNPi z(z(QU$MKE8k}IoOdYzzVS<51+FeV7q>*qg0J$BD4sxEA6iIS1<^*QWit^3t z1*y<~xtDNd`bnu8HK*e8#25bzE67Z9@Nbw3*ZejgL!e5SI>LQi_NBv<#by9e!e_%d zT|WfbU$kK%2tR5WXfGa*-oB*QNZo)T+|XU|z56Y8`v*aF;J1kDvtl+ZLiFiLh^!bQ z2s_y`8YLR_lG~XnJKuN(hcqxFo~`VLU)m<0V0gokWgFkGOR*~GT~s>X-8ac(T!|?O z`bq`KS^lyoIc#{HReZ(<2-rLLhfVdT{Qyilt#Iyru*ytZ0jFh`&|(l|(YL!GzWQ$3 z3$Kpj-s7Jo|2bCzF>p4{)vy-UP*r){2VfXNlx<^WBlFp7_Zk(q^%$>1(#DaGM10U@ zxlZ54MLWvi7(X8wRh?}HXeJVWLwGiuMLTo$a*Js)ob2j|Fh!Aizw&8dlgc}VTe6o3 zGNWFw$!30^Se1WEDRF8W<}jmZTf<|w?l z&(@t05{``WS9V%v>1U#X_@SD}@9_65H=NY_9WorVuZ>G`kmK{MkoF4pKcsC>>mt6M zf@}jGWu!jqx^y2ziDt#R_}rb;*|&A`$h<;`MVAo%b^XN^`C=2eN8cJ6;aMH6$gR z&~FOB=_Kh3z{saK8Lg1;S#N}DC`g^IixI{_cR&Q{1f8cyyutVX9XuU0@;y$vo9G>aE^PQ7pJB14r;>Er`QiHYgC>>9 zQ8gR#v$vHGAudhf@56BRh>vNkvU^MM{4N?aZ;c1kA-~U?P#^3bC>4IRV|`v-sh>7S zF0MjLEq5qhesakx(DO#@UJlBamBfbN;MV4N+UD0(0zHY-72qwuIzCU8G`}Wd#l%OL z4cBFfiIyI_=DgDSeAek`+8x?V1}&kaQ=ntwrFtWl5m6HCdE6$_zsgvlmMs;w*TfR- zumb{vMzwtxPlcsqB21{1bp&cdgyLB`kjF{tIt;9Qja943i7zIiieQx{`DSV|zc2Wi z#_smcrr)1q)0^AYnE0)?`(x_`CkepOA|k&UMh_KHWuf={Wi*XvFbC<~_uMq%uG(ZN zV!t+gBB?h4uxaX{Dg;xz!Y2NWM@`3b5@gLdd0B!xQiBzGs$cgii+R;I#6Q4_w-)ak zxQalAaqwd>{%?^~Jd+>pnZ{8{1rMaPwL7{TxGdxPRIxP4YbpjhJM=}7+H5)AhMjJx zhf3GST#we2*e8u7bDO|GWM(1}U6Iv8j7l2k^q#$&8@jIQJKgNKK?ev>Z68E!Z+V$Z z$dD2=60;C5N~%|udu8QPxoXvQZT1`?u;DsO6cT#z)$ZQG<$eYoUYSads?)EV z{yPu#^MV|fS;B}{>mil*j^_5Jh8ZI^17?}>rE8;>?YFD;vMUyU#vfbr)=h=NM(_1T2MJsPC(iO!( z&oI1i?d+iu7;!e41xMX2#t(Xg%K`2dQtfR8B8K%dD`P1B{dM*ZKyR6x;`rT{4wEIV zH&M`H9C+3g5Fovx8;MLP$h+ZodasmSEfXkgXgUMRnbl~7CtgP<1CMG|qzI(hzmaan zSM=^G!*9n5D_Xo{g3ydj?{GVo8j=8}5#0S-9uSW;9FPO(RlksB4Dhu4#Hl8g()r*( zNRt6Qp)6g$96(aEjL1s?8b3!3Z)z6QyAR%ek`AolkDfoE#h&!{tjJ-4SQjPy(k2rM zY6`K$)S64XOnxiD%Z-{a&KK;%SZeeANE;nEjnPzl>=*nsJzQrd=(oT)sQfmX&>{8u zu2oNm$)*pTU}OupM*9OgDGNb8m^1TvDo;yNW@;~kyFg@8{_5xJG`R{svv@WFWJDiy z1ZNMAq-$oH7Soow4&;0)M|aqS4jFmbR4hY&G4d|!6d^2%Ga>xvQ-0)#*q5H1QC9a$wqVdqRQk4ZAaE0XbC_<1JP`vQwxpNL1so-_-)5XJ zH>^UOSrRn!4zAt3#g(!zrzE<)ey>4Y)Pl(_V^+tCB#| zV%HM4F*fMm_N?tIP7m1%03eiWa0Cf#UY)>F;p|6?J&^&Ma)1M58_Xie_c{S){NaK= zo6tb}-`=FH#`t!V;rDl*^`F1qtUc&NlCgp{^zEO;jCE#Nn!Hn+vg^wTQoDl{>h70; z_rLwOOc+U}FK!61IJV*6Ow2vUq`VYe8s*W(98|3wlQoV`zB5gTRo%X!H!}ghw+&vO z3t$O7DS!2BT?ISl1;b9q^=IesPhzbV*m2mFGG#3)pMW1iEbp*Q`kWaEl>txYa>GAP z)Mnx`Ghu`c@c7zB+5<_W^5^FT^H}A5O)GyRz8M1y=v>WAjL;@cZ#uB%5+`5a-d;I= z`_&YHbXSeBHc&XA?0P)|9rtyWiYf1E=pGC#*#C9|lJThC0S?sd|UEa{3C5w0F&a zfAaL0fKvzXfj+GBJQ`F3BSU2&Qv?Ptse!51mRz zu`OYA@6XR#>TMnLqK@s;4FNxsBsn};{-z9IHyzf0svD0SxWI}CqeDV@s~$oUfG_<& z@A?#ke_0?H|G7?vHEv80Lj% zEv!FxUsnOK=+9mPvGtD+p$}N&j2o`F0rQ#j6NlxjxKCQZ^At?*uMfl!c)z$MK=ydl zksz3ZO%m^|F;IvPwlhy&9E@7>&$o%8vlzyGsG6NT*x&z7*5F(6+4 zNB<8V07(OW>%PYMWI481L-~2pA)gDY{sOW^^9~zKgoiB>N`u09{@m8w> z-8pzSe{1jmxP0Y_GuDCICkxo1!3F~kAl7Jhb)eF`4Bj+W_>ZIlZ~5ZOA-&*>FsuuB z9S`b8OvO9saC=9$32WYhGIkK?@cZu{zBB&;F(?$pJddu}G{(9E@rL zbWyBn;9rMdMv(GhzRnjc3_~o>_~R*XSb^f@AKzCqF}OAG&;-75T1OxT>ijM|#XXSy zNH9`@br7U+Q|ixPQ+|T%BoJera9Cr3;U2U9pM+cothl<%gL%=IbYkAh2LQeW7;XR8 zHxISNFy{lum)aeGTw>PDQ3EghKi?a`k0`U~glv8UabRYFm^CV04uN%9+VENhQj){e zuUP;;AX`V0a1r$0;kZ+8$Km!I%m_{T_i{UIcaeeT2}*&J>DT^R4**;Fv);mdu$v3( zdD4yd1+>AqCv~1~DNo0Np=N~>I3s5L)`kC0s8rw)j8nIPb!7Q-+Q>C=t4Ts}a7Re3 zr3maXaVmVZPqECS<%#MFBDKH2H;AYJ=~Xo>ZpO+qt;;9|9`wI{Z9E4Tumqj}4^4{3 zxI-JmF+e5+|5IAjfDux6pKqaBKSoM@68Z?nU8Rqjw(vGnT7tDFrY+ZxhGyt{j=2~CJ zLVf8Nx*OmeO{ZIB18M^VcgfjQMLu27=@9J88G!$&MopKX34uz8D3q-OJ28mS|4W2G zQZ|o*eg70>+HCR=aNP)uYq-Et-Hmygn@XL>?rnH#lfuU2nyr zWck31uog8laog&3AyI6CZJ{!{hc*ekF$PqkeN86jz-so}O!o_KeXM*yA%9&brTkAi zFn%dyH^~ikn^DGAikFNwdl17$Ua)ts|5xS%)1~ni>A?T~4(n=y zDS^sw&nF2%ZPFx1_x=8l2Rky4@qc zD%e*xGv%h1Qm$QtE@Ma8hghOP6#G^jjKzM&|C^71MQG;Vk}iUM%(N)MXx7AINarQi zMExn}g;_n!&?=B<4D5uyCQtkROyxU6si3fS1Qdd0nE)A+Ir23s!xrww60d4Wm-ZPs z^q02`j!V-nb}1FlT-L^!2x`>ph)L+CG^h|HC2IE8hAVeg+vwN2f$PQm^l;k|=+jq| ztH2u(^B#+b-vjU4#Q)stf0y0AaS}Ws)^xyGZao}}4T}e;0ze2Gvj-!vO#Ct60(@`! zaW(MMS&dhg^bo^21c4O^cVswL;lNPTbBX=v=m%{i#ZFiHZ78wCBD}jtw(>&D@$j2g zsp|pNal*b;=biQg*&^>)xaZtF2oDw!{fPHJXOJ2Bzi}WQm_hPIASc(KU@-v+4Rw%3b_aV935Rxu?la5s>|UAv z&;Z;st=p7D1oKbF=Imca@M3OexU_4>p+Q{%nu7@p94XQp;&WQ;?&ArdCLg_vbVJp8{rScI9d$TF*vRvA=~ps- z_pM|i;Yo7uJ`C^$3~rLyCTIg=tql69MsfntZ%T#C3D`crPsLQ=n%Pv2Z~(Y6MX@Yt zU|D<%oM`Qd_pNQBUIpR{Q-Pw$eW@N8INuiP3{T+n~>Mfa3VYpvO2)cDg`Ash23ZT*0sa zb0Ag>kBxU(fN~?&7B*GO7rusXmV%wtb$3o9OF5ct8O7cWxk|;GzuUZ4uW)765L0Au zr^93T1>1u38F|$9Ihi;biiD@I8kcZA1+XLi7d{&G(oS07_C8#tXGZ~Q-Ol5JTakTmNCsH?m= zVxf63motLgqzwtE&5sznaklflmeLXP`Coea>m&h8YkgN{<_kH}t>a`EKiGQ)WbLwT z^vsN*K@r3TC*krc_1=5%n)Tb7K$W_D0V>|`|BjV;eih_rwX#81cd!^}Ba?;bVeJGB zrtuwVeEFs`MelWe-YqbkczIs+ zMk?TC0XeKIj<3j0d@PCm@0e84?F@|4cR?2|e9%66Bbc0h#WYb_{uS1E$WEp6Gon}z zpp8k!0A}JhM5~50+_KB`HK^V0EMm>v#}DiQKtb%F>?n&2-x2|?6c{?gy(Pdw>g#fL z(%y`>-xa*q%t>Bts3s6dzaFo6x{Ogafa*(kdHLr)X;BZAyK~ynWFdTz<+Rvy^iqhR(#g`RAZbo5=30OC#GAN--^VrXNcXrb+I_(fs@1;o& zkffMDPq+czJ7i%ieY5;JZbf+5N+jj1N4vk` zfCGKh#9Wc%cj!OE5>>^7A0VrM$~+Ux!!o*dmCn{X$_S$VehLD4w^n)o3OKky-QmvX zZ%!R8>rj5vs3-w&5m4k}W-`+lv~JvU30qMGg00hyEi>@hu7k=*ycHcgJSlP}{+XTP zcc}l*VWhw^l&Ww1tJAZ5ENOEAnAqdAjJ$r5unNlh0q6_CS-jUL(1mdj-R%|xNCMlw zAlN+1{WW@tz0apE@UReg!>r*UI7mnq7r}gv_P5v##oNc7Nl-7E-duMC^t$EbDvfm6 zyRfl)|GZyp)WJ4AXyfmxrb!tXx-Vyalt+Uc(hf#1eL3A{!1~ZjK(+O!s7C_PN;NW= z*UrXR35yO`9xMMmdV}>Qw!vJ9xQpf4pL>B8MkhF}TYxM-`s%vd)(^fN^h{sl>dxO! zcLts{|LTDZjP>IOX{NDBpuZW@x(Zyp9uJZ%J^qwE-v3tHesy)AXvKnq^Z}p85d@Gf zfFb0$9St4nWSOuYTTHKam*@j0!-I00K+#Yb`$Oz>^QCsK-CFNZkS8O=_JT z*dT#*8{14G#J*D8v)HfLn)UY-2%h?j^J;8$y#p@cmjbW!60ONlv)%lY#{bjB0l%Fy z`DCxui7K`+P<|T_2jV~-KYE#4F;?#69$*U@8}ftDJV84Hj$_e)56g>Y5Q^{1SWlBC z@JUMmYYkupros~}PXHEfRU{sOl8?Zk5Kuz>&V7}l`TsuA#{D7C8dwgT1wIyyk2mqf zeJ4Q1bH?tl=vmhm8u|0!C`FoK~4d00XKxoA?tp_P{AM zJf0N453@z)jX7)~_><8QA@~UR#s4-~<6a{st#48Y9ez!2cUTNexd0u2xFNG$o04f>ykMpUb z+J%dg+KTo~ub7M8xeM<{{Zpi>u6C>31$sAQXa-h03cshAE;=VIWxyOKZUvwADV-nR z=Hyr92VWe}cevFh_hT^8?5S{BEF0**w-oDVa473P_dI;NCS|YTJ>vSHZnWBna($GY zSn}{*W7|6%#C(&2kxE__U)MNHe14VHIrb;_c&%35SgFAx-0CXu!5Y`Nr`0-Fhv}87 z>eh+r(?V!jKEC)Kd;0`}j}XrAcad;y1l9u^KPHw^%W_`6k26+1#?>NU8`n|tv zE_t+=WY?)cWsvFrbfNRT1z^#Sz6cFml1HaH!qtTn_K&#kdRpo%_buFB9xHi!4XUjh z>oxHdv#Cm{T4A@s!AI%&P>Nbp0d+bebABqybH(&n8Ss%BTrLU=OJAF4S`B~tkPocH z=OQ=hG1{i=V~#ElLAWl9lmI-<9l%Oy2?9``rPYzB=BomRa=G&wZ28 zDRHe=9s8;$HCdw6YBRxOXEVxKWIK7gNobQ_w)3-|t8#t0Gzj*Z=DAu%O-xMs-P`>F z_D#=e%B(!#{Wphi4WGSDLv3Txap58Pmf4i}e?oArF-BQ)}z^risv`sCR1pTDywG|d*bft;T zs;S=CWy|NehiK{K2QSscd&=4BX{jb?9x?>OU`wSi{smE0xiJ}d)A+xu| z!Dt2@(8(4E?XpZi1*GDxm@_dqJPC0(GT_EiAe1c2srI~wO5EEpP*o)1V`r>2dN#@M zOu5R%O&?)hQ;TsUj=nODC796PQ?d(DtE$aSlN+U8MVE0X0)Z^7(c)Ht{zgg z2(OQ7HNv>=o^K@F*zg%TPgIht6IY)Qdpi9EVGn}*UJ_akz&&#}k`M5k%0yAQE*R&a#dqOyz^Wv9alaM^|!D9Lv5T2Qj zS2}#Uoi!YB$qb)`8pWA-x>GN$`pOpI8&o3|Q*CgJaD_4+RWGa;5 zHGb*6t;bqy+=501Z**{PgqaK3j4)xF3?LhHfO%2-P5{(!>@K!@`oY@tvi0>bLW15} z@LKL%EhPoFlRRZqfe@0?6D%}iZy&U8+d(tg0x$aKFxieQK zRO)4>G`c6}FFY+CJB&zIOKSJ*NKfvfX-Npy8?+h)4t#Qcv9TUr3! zNvS!!h?Aa`tckQ1>tf@Rnw_ZsoYXqkm>Co>>4n)`%Ye3d)V$VI)Vr@%M4UZ^e5LNP4(y4v}OU~ngwvl2>~3mKBcJ9&4vt=rrN-erHOjRw<_HS zl<($Zwk8zBeV7kz5dj4ADX{9yf{H^WLd%l@m!@`7-HXamGwW$P08X)-8&wz+NRz35 zdAmTe7+oN|_7<=Dpd(d|;QQPVVe@w$P>&`A@Q+$BN>k>8+tFO!4b!6sqLao_k%U{I z5d8dW2M}QjExIt|$hq}7SQ}u00Yhf%vWcBPdRHg(I{I7=r)cg z>lN1CGe9;Ki#>dUo498WZb`smFm5$Mtzy4`Y=4+h{pll!+7jq{2f1glbSs7s+14u7 z-FceJ1a1bNMGE-DM+e=d6?e&hny>v>nstEmy1pDu$s2kp&&tzPql4_zO`TU{No>#8?(C1Za?0khj-CkWj4PCV-`$hE`{JoZ!u|r+U0a*`ZZJz49g&>$ zwaKB|3i0yxr#6lZG-8=$uUDVQPr7~1u(`;K&exrkoutuGmZH~GFxpijJfE|Fw>C1U zOxa_7>*HiDN5FAx3}4RA_ur=~G?`;Pc11lDC68<$aV1Dsr>mxFXwDsb!|2=&);hqJ_A%mZ5?$RE}++Q9!SFqBMj?{NrFYm8875j&;o)n)hAglIVC~0}Z+HC&pl=%j2+}mlT1bl7B^u4aeephw;^l$J;1|HM zdZqW}6?c};zyUDVIdeYGj~|=f8zHmD#1qu4`EBkjKe%Z-kR2W}Ji@yHu-QM%8WkE* zs+r-U?ZKVFZeaAbP+ZCVF;O+r7`e9^-IDku^ts!+4|v5#+U0M*&frl5~ib1gHedlYRojV@>tn`-s$FBAu>~6lVo~;WW7J@&mW2+Y1TXR95y@}4b)@3 zaxw1z)%>25LSwOr7q)jn28w&vj(7pHd(b57Y=*@hZffkU;1xKSE71*L_bI&bZS|lX zgQeZ(Q9XIiL#WJ3jHM1O$tIPF%G{SEr6PV!xjUaEsToWj$)RTQ%h{7&y^;V10}a!$ zR>EjqnjaW#f)Z$m4PryMvmBIyck*96)wuz&F2oWU*>}L^h}0RmJO#ijD!ZXC9TY=Q z^O5H#fX@36)I?7}FJVtS^zZy6=*%pZNE9yjlk|0yz%dds5Uc=aRJ+aQJ_XYEx=xm>py{mkO*PXgD z8uH<2Wx9Vy)(>HE98*SqisHZs~iPfYOl|iq9`41(jk<@WuLq~L*fBRC<@#r55JCz zEgK;t80If}@L^Gg-s617p32}Vx(BCP3F;;fWAkxh?7TTcj&7o(2F%!~r=OocYLjmz zEHo&8^*(zWu8aJF7!h1SBK&QZ1BGqe*QB-`>{{!<>2&F^#Wgdbf(`zobMpM0AYAd# z9(E%=)%kGuSbf+Ib__@yHDQs~iSOOntKQH+QZ(4fkX$nI?Ior*X&9b@nDf@b2Akn33Xxh zP*~tq$OIv@FH?ZSt1revvG`@L)X~v5j-z$s9n2+fZ|Olf7n==^G`0NOxBr@tdEb@T zk%sVql9Oz?OjM<(NSy3R-f8wenOfZG^4qw>M6zZii@(QPIGIa`3cUD9-202J=>7XR zLr)ESDapuNTrlq#eRB9o5keUly zi{;VsV%7bfkRQvv=xt-ZDWbVgDrm~yEtUMA4~151fpH{rA#8 zY(0=RQSrsj#9~m0Z{A?OWQum%3NSFG-#?gWue&)IsZU*ZaIjClU)^4u$td1|Qx3p7 zOR?8`VzYA8qp48$;GnbdEMDOw;LY4V{T}cEbZ#Fj=p4{3Tpo`*iR02z1KOf&Q`Cj4 z4~>GXZeLv`yK{2@_Zk)YvAmh)na~aDxJpWNr}lTi52}f#Y`xujX-wWcS)Y-HeTpE+ zsa_%0NrQ$UNc^!EwMTh`rOF^HL^atyw1UTp!%@Xun&)-WT>_kSp$UH?+TMKhuva@mzc{@#zD8HKD) zt{#8qFC`{KjixhHJoR%6t*LWJvKd==B+p_~A2q^{Fqfs>(|c8X2|K}H!?lBRcg?3m zVsJ6hDnQ{f@d3c9<+kmBww7~GkAF80Er;f`o$K{o8A=AEHDTJfDKO7?Lk!bsAuVIr z_RFq@gN(%Odfc#^4!gz$uVmplk^o{ftH9r~`#y+se;lv`4fNhBHtq^UJMX|HVcs_x zc#XOJ6XselbaHTt?DCer8JVKQ8hKb8*R}-c5LSK1ndK=eE46h5d z0pZB21g8-u^sTqTor$RC)o=50>gT`QHfMRYs;8B2Zt@)$pSoIU?)QXUBaq%S>m7C3dAd<;v8P92F?TFP zi`kkiyt@#c*k5BMl^{@C7*e;9I7PA_6V=r9SI2DRr?~Z*TEaHo9@|oDYo$EBRUI0s zva*(_&mF^FLV0-BSFPEICKv_O(Z|KU?8)tx_r6-SqtQYmVw+ z@u$6&Snk8~ePaJ2*V?#q;o2GsUcf89foV&J?re|T=|?@foUrxy{!v$T#OhJ<#WSzv zzVf%8hlUjU#S0#@BVP)lLqJNDy2BNFukZEo=6d7$__$2AdI2j$5}wqZa`e}rRZb6N z(`UdMhrkoYGUwohFR-=gb5T7G6(tIL&!C?Kc#k%tVx_)c zguw`N)_fx2p>J1q+S%PHGPw1+-cqhf)r(e2L+j<a1+k}fVtY-OLjLs_OTlZSC#jCpx z8*B9q3DaU760J*zT3;A(khk)8rnwP&RO|{{sG&2}HCCUg1Qt-uZ$FeVCfIbfA7LZ&{A`$V!1U8^KD*_m!OgP|8J6-FBH z)C;TYv7Wq3k$|PQM4~g>)X^5LE_PRDzcc!`USbt6;az6S?dAz+l=p9AC<>S}c?~*! z%g>2a-@pnJTsS-^e-fG##&n~Pq;k0p&Wib83bgrCk=+n@r*+S*AZ7I{C8^rzKqMYJ zu}Y1(Xl}?r&$hiOb60JG-i_3rq??}`&L~i`L1M^` z6fP-BOdHCa1-vFF2(?)WP+;=vU~Dx|p*H+z;h=W(m~uV~bVqa}_VQR1LY8XD)X4}) z({$#(ijZE>9*j2XoaImH2PQD#@~t~cxKRz|f*rjF+6Fich;mUfpfdn9$pbwfhD;(TP1E81gz;xUK} z2e6-?DSJnHbB3bc?5Am}kIbLhMK=3XBEmdtlViA2G>1F~WLp#u=&o=XmVR}d7*x0ubzATg%9CKPepx>6OJyQtbOVIT%C6CJ6C}o7Mxm(4o6#OcXr|q&bHDmGM(C}%s29nIcM4jLRub~FMWG=H`mBv zfAHsOZRu2P!58l235`3kKKd2e_D2VDA~nv^Y0s6wxB!v_!Bq;L>UR5vX)EzgYn%?~ zUIN~FoVS9}Tsl6c!!3g#h~QW}6-Agy=p*r3ww~xJasClefU)vaR=vYCja?``{?5k*+&IQCp+(~x<0m4iv=oq1F)E>UA+DYxkQjs3l|M+1Gn&lRMAWsq!|pd0TW&O~cfOQ}`U~!Rt-Nt){lvI2|ItRw7o%>OFE73p z!an;RjQE$BJGO=7!;}L_tlt>|NjMg6nn0xUYACd8jXzc?nOQM`^e&CrjRrT9u+5~{ z|HIXLI8q&d|Ksg6g(7?JaU**s>vD}yMpk8Y;}SyGOe%X`dlnbTzOE}=lw@5am#m9h z+)KtK_ZnUE`||#LzTe;H^ZNrt_nzk&k8>W6N1dhzPR?+iEBT!dKa&l%{~efrP&n!6 zwz#&H+@#Ch*dMNPcX@PA@GnM--BnF3h>;xzBMpAYrtpI>Ia}1ffskzPKAN8+N;_gdgn{+N$9(3Vl7K zY{T}1TXU2-tWfj?IYd1$aA!Lsy-sXEX?1&#o+S3`Ra$6~0B|?xUSN-tU6{AndB190 z6%?nbd~RI$oogU!W!c{=wW4hWNEiZyX1jH#lKn< zw(ij#itF(uhiG>tReL$^e}^=ia<6+ha*Z{+YSs@*dwg-6=sqFnBAKBxrJtp~8d$l) z(b!VqGO&w9lz!6nj+3KraIY+f?fSSbU4LNAItY8xXE;hhy&WL^iQtiQcN1({F9^qd zB3B6?-UHDC?!w^SzgkKw{oLhKQ-vK7u{UNjmiPgQk@7P3tuF;XfK-?-@(H?@n1TPP4SOn_a};Vtst&1xa$} zPG5u=*+8LFVy~3zOY5s(rtW^!HLFs@`Ge~8K;6rJpjs^(-|8huIb)c7v1U1F*8&5~ zyYcXgF}tLY+AE8Z&Uz?cUIAj-bQx4Dq+kPh+;{VTuzoaH&ZEsqdzO1IXUZ~;NAB3X zp?tI^s*ec4CdKg?cOR`$a>RqEcX?&JI1;=U3mo%Iqcc^`CxJVwl|$)SbJq8#B!1r! zGqn5F{do`obHB2z?VJ$yd(zDsFFey07#Y(GECv#O99t)lfRimHL?OxeQ(F5sn*tv- zmT&=XIB85D@$e*0xnP4g>BsG1*Vny5KN8ooNw=AFtprB?<;7fh&BotToJaaT9kG5b zRM$tA$<^0b?ap=P)?T6Pm@D+u0!nny-HU=tl~OJ$rv$QkI8)5pkY#xx>!I*4(!UW_sFNZ{C{@&ww=*xwfRracu zEpm!UbA8gAceVF!rxc6nAUkyOT<@Jx$zd1WA5$T(d?TgVy1Oo=MDM!JnT7ftUb~`8 z*vLFO-LW4dT96E^&St?df(VSAN&i@J);2XlRRkaB_Y4q8`4$Gt{acm0xu9EwCVzJd zXA8!zN(4a+;9F#O*WrCP=I}G+e^R0mPG> z&av(-Fu}@q4vP*A|Is2c1j|(g&%cAaOtbwY2f80|olYqi&2Qf@2+^vo zqp@c^|NIIZ@HzAkcvatOFqOz-`=Bo)Xhz)H&ysDVbQB?zAJXQpL(T?{p}ePfla+)(HiQ!V%b7qix>lFb~3PjN4t_~U9xmj_I<3IP{WdH+-kcE*%pqd6iD>59ucyX$``?Ou;y$Ytb5P+3{w_ZSLBQC{t0+(#$(__XFw?H=r&+OAKT;X^~HU4 z$TY{+>Q%2WOYp)q_1L5==kR^vAooOKjM!o>H5pXMf90iA=KiE8J`GzH@>f&eX`-N) zrRe6#gg3vKLPR!3Pr_1P7^ley!zLN`bZ+4@=mkD0PEvL;tI$|)U z*89i4`5@JEPvB<{ap!Z7s5E{oOg~M!a1u7bhzRO?`^DfYADHQudRsqzWA;4lj?1-0 zqV-g2&5e347IQSVGG0Lr{$~8*LnSCAjW@~7FTZ~OEajcLCz72K%T_z-FUkaFxS*-? z;+|43jAiL787Y10Z%-Q!zQ!9`ZY^5I)Tmn|V4y{>ExNpFtv|LuG1a3}pQnvk?9ai- zI^qLnE8pRfK3>^Md#};tbtm_8*=Tt79oZAuK!PbhAHO|tW(VV zWAto;Rzpg2gJ18P4F&SQlS7vpRrOgjgfHdfuq6x>Oy2WqJLDIH#atJ8odLGPkjZX^ zA@>b~0wjSljSkpW`jO_^hg79DAc$jExb4YLEnW)1Rv`8I@chuU^kP$ojY&`i!Ehym zK2S%ATUtO;Kx&v5X>FlKkb@}|m+@^e`nSpTf_w+k{No!XuCx%;UZbhLz27P+f^X3# zR|zLc$lFaJp3fd?#knJ^>W6q26jIy^LX50bw_KOES6_8Do928mva7a3QUJw&_i9M> zg~m-*OflmJq98SGCH*jwAppsBAVWXJw5855w57}Ix+hdEyeo;B@8;Sy5RjtR4O9=q z_7tmCc8&Y=d5s?Kp4>hE=C&4i5{(JjL`0iZJE=31^(4wRa~Aw|#rpaz`E^^5&r?hD zrk(%s$G0&;jfe{^8VAse!#OL?&axz%0 z=%hb3J1Fa=EcDK-9J|iNzkk%3-WxNh7(^?M{irZ6en(eIsyL0fFW?{j>XO`h3!%+JCr$JJK>_x4|30>8WO{a=$z+f^bHeXZo6fac8}9j*Bmq}G z#wdF-JP8mVziMrJ-bAenWdK3r22pP(4~;#btkY4Pl~Ci==p zUK6*HjBi?7h7s`QgZZnVy(i78i$~HxKgd1VQc+))&CLOfVYIVd$?nm?TA$(&#Lh4b`L(mC~L*;*CdyNX((|t zoiQpyi5^NJ5yVkh{do_~w)F#L;k#qn7Fl3|_!iQx?1?FRUr+4!fq|U1nU))flLvh{ zB^)n*b3n~_DAHQe9^&7$J_$L6Sn0L>!{*rTK7K!(A-o?K0eOm=0zE^uq}m}B~dCJGF~Tz+An%NRzEx;h%EQL73! zg;emYhw2)40$_2ehEC%dYYwr7N!7wGV}Vy(oQ&D%8nY}ZkWI>G^PesQW>o|2Oi0~C z=GIu`O!F|y_aG|RTM*I`AR)m-xoFrO-GOg|X#-v2tIbu%y8x_{BPMJ(M7?xrQ6c5) z>Asv$vuqoKCxHP0Z9BRp0y^!5!*?*=iD}N~gK|pFylb_d)vZRUcXTOG20@&^R9KO# z)s+1B&lG%l*5;bI5oD>eQyZ&Ey5=tz)i}a;fdLga% z^zk{n&kxt8Ib1uRx$Jf5PgMIYUy>c%NxMQfx$aZ3Ko>58@zZ?em&;6Wr{rqG+3U}( zEzPrmgqgx8$|JKAkWT9srW&kigXZ7kZ)%49sDJVOEqeBh)f7s-AMnd@uD&E-jgW*% zV~EAPf_;UeR{tQpuiwnK^)=`aUG@Zb;lHA|NW9~sq0;7+(RxBf6t*6s-PBa@7r?%* zKS|9CKQ_y@ic&#}wgYd)^V>n3oW(3KU}cru1mi4Kz|Oj&2OJ0QcQ9Yv#nUk%W-fA} zB0D_*$CU%#ms87N?bzRSNgDo`bB;yp9oM2`jw3!JESr~WNi+t%rOAtl-@juR{9DtI zXI86v%Fkuwzu!ta#^}UxgLg>+mj0a2rGz4T{4VeUFk2^tflf?^&i1VZ>~yD*p{DUi zrjTe0$44DmyeeR{kX&N-w5`Bb&d!qpC#FSKNh@mXF4XQfIR=|Crmm+TxwU@hG0rmD z_mk2w+v7JP(Y3mbHx*vLA`5&xpSW1l zekgbM3>Fc)P;!{hWRV>N{_hfrS@&Gq|MpIch0|WPu(DEd->!UXTUhZlkK6G z814&|{1U4e1#rp zk#O6yOB#fioz3ONG^obb3!mn=3wdbrZn4?IVIZKKleHpJf*Ix85ctJTC8Aiyv!? zj;BoUrshy=FBK}((x)>fJHmh_r(CdY@x1b`P1t7v#zB@JyaLhm**2Gi-A6~$w0|G) zdj;;lkHk|jc@QC0twW#qHhjkVQgF;E{T~PeBP8tEP}H1CW}fqL4^+n@2}Ie)|Fk{F z3HTMa9rFH^ob4kyZCycpMrQ1-jSW^aBkChx{nm}ljpnnmrf--*@!A)C6R*Wpy1I*o z%n~L-R@;HMl?}RO``AfWjQ<+Ea%ADVqOq^d-4i~z>DTL0n9C$Hc5%tOg_*T~^LOf| z4sOF6>2}JFA|ug;0(Du0dtPT`1Ty6&U}7Ma=SU$It8G|!kMWaiw4h%V+O=a4_p&D{ zoDc#Y+87D51!Wz;GviQG^E8dCm%8Im@T(>?JBPP*>xa@(l|_E87OH1|lbtj$@{{t^ z2}6o93gEA)q{YPu;~4oonaX-j;3s^2RvG+VPP{%DG2r~=_|goHXl_)0zc$I6bUC;N ziYxA5a712Re2oq0v+6pKCdUvRNzTkq!6_q`6`kG>VIVh?YbZF2AQMEF-IOoa zm_eOcVhZ`FIo|U z&S|thGNAm}gRQXjif}F9Pi)ji?K%`jsXk*=;xr)Gei-dssKC3yFr(%z$X?1zHT=Uk)QMW;4~n9qY>wy2*PmtOH; zeyg8bM|n$&>UkVWV20~i(uRbJK&`b=I8ebjfZ%a zbbwO#(I(^Z1g1Q)v-G*fbi62ly{o$o$qCxCYz}9pV(eEo6V$oCI7vqT;kB82%X{z7 z>3`a9uuA~^_s(E1CZutklekgLIGnd>Xn(Xx1iG@2?gpc9({Oh|hb8ykOx|c^5zd6Z z!1AWjNyhF!A~1Tv{I5g3aAOS%ul3-wTj@aSlKs)Z)iqZ+CfdQRpN+|TBj^d zJTa;8%U|Yj>VMrq?bZYN%%IN-`9!3O4 zGgT7S#8|Q&A_f$(6Hi2~?DM>cWjBBTdw&m_o46Vvl3N#rgiJFnVRvd@#H#!%Ff|PP z?@V+nY=1e^!nz$Ze5eumo5frz@AcbeK9am<`GjP);I|##S~_X>7mv80gdbJWP{Doy_JR zdW=Pox^C1{@_pZ@UvGjfx6M?ylRmo6fwP$+NsDSnk1RUh#Jtdbk;2*&i4|V~c=2vR zX9;rSk2{9KUY=y^?wKzE6fuBRH&e3u8id_O^|hFj$k!hCDKS`XkVs$b1;wo{AO0(= zTrvLe$wc9+(W6h;&u;e~M#tYRn%JeirDPi;w7NTr@%trflqQlY|EKhrA`Ka;vxON+ z9)CXN3*5C`muO*n=8P+vrg281J38+dpfKC>WRTJSS#hl)E+pCis)|UT?!&|shFgvT zJt`v2tWZeqovGScVe^JjQP=vpQ??s`H*c0l>+|0oBG0l9_(#Rh`Df2T=UY8@6(XKyItZA%&}rR~O~(!U<1V7Sgd$pd=^C%JKA%6OUzr~DTU*Mk#RL%Vh~^D}54KB6C- ziZ-$nwg=atXIhCpJ*IWrO9u3ZgO;wxbpGe#s8Ihj_1~ zx5po3f8l4z{!1-l<7qYMV@l{Fy?$ZL1^47N>{kD3s{AjpSA)g1GcO^J?AI6h*;N2s zpo>DWNbj+X=+|s@uZyyPb$L^Ajd_?(S#c5O(YM~|eHL(IDvo|6@TBURTgQM< zpXra@$QTI^bpcSck)FDRI~R-V`vOl*w1LPx!xfPYwE2`We0Y#42Vl?xL0VT2vIF&J zM?cbgpUjq&W1*jzJ72dn{T^!t2KZfjB2TjXuZXkx8@|bJyjSO^EV7+e$Y9UcaYa0~ zpYLLZbK~A4V1=T4>h8&iJEN@3Q3lR2Uj$O)7+|?tWNl_X)|VI9yLGs4Z^s8cy8ERW<6hA+%5vp|<9hM)`s|9Rk_8x^# zw_h9GZmBO+rICRg zWFn><$KVw0!^>sFF3|4(iGL2Qz6(%0IRR=3g_mKYSo-psDX7cHf^;S@eiEkh295gjI zD*qclEahhpbM9>FXjtpc@BDTR-u*38+>RHN;)$h{X2MTMCdmL@QxjmFh2f{bdT3Pfm1DZL2>I1DS0b`D&vUOt|rq^S}h@RCvcyz z^RSUlRau%GQ%i!Rf4$Om{SF89IRwH(8gL@6uZDpdd4C1xj%BEcuWrECN5U3D)tPTigzlqQn$lKQXuAGgB{$3quC&=B?fjE1$8Y%7hB z8d6J;F6$@pAxmcZ@#iFR6l|5pjXM|eQ!5@j_nOy4+~?K$YN^?tIV=c^d$BBHrny(? zQezHT9>}Y~E8aQ&IwD4m?7GrDb>w5yG3rm?BkFo$g!^ z>f?8 zS+z*J^Uc5Fzy9(NP>EXlBvwteu;JlW{b-?zQ;xJPKU2GY@bXW0wQqD^p#`7A zqiSe6uIRs$WdxYTG0YdRr|kpuCZM`kJtC=1c0s_0AptwMF;lR70pwEtz$!q*7SEj4Oftz+(;KNt=+ z+5EVoXhr3(%tLeM!Bp3tV}yNKnf@SV!PXWXctMVzIW>uhW)q-^l^l!7vFgwh0mH0bPz0Nw}~9&Hg$ST|ZCL!!{`WviM2} zFFEZk^(KFJow=1LxsMe2RvDPN^^p zpvI(L&PG-6|5LtWDto@fZ~E~P*UB#uPZh`WiM^u{_4zyS;wwU>v{}<8q5l8?(s27l!Q{ zH`)KcH#N31m?|9Cw6ifM^Z)+@t5*{dR|$c+nKpG-pCxRtl=S`Cu<+|}fyxJ_plw^s zivsf4iWark_{*oNi2@01GAR!crQilxr<@-AyZS{)u77U)Wh~fKbq`7*{ru5eKaBm3 z4;u-9-r6}|PTy)A)D$(GS$8vplwD>b1j4s}lZam50?O4wtM79vX-kAro35{o>N!f> z`yr)q&+W^#^8LxD_`IjE^Y*SS%q`AnJow9PX;Wt5F_+I+>6vrhQoLn$Xo|FQvDe{G(; z#&h)x*}LBBAw!xL4sFKAT?Q984lP`i!ItoM?y5*N*5<8&zODCwbZgPSxO?ds-IW9$ zjVbI2sVu=!z=|m&^Tvh%3b5%QVBrPM&mo28AJGH-XB6=nY`U$Rc*w$RH?I8tlw&;b z4`Vj1aHV&gGkvV$eOB zRnq$xzfyvWoCt@g2cHrSjHrs<(nIa}I)|Z)+*A_HmPC2zkEE6rC!7$nxIs6#?}e=A z?VPhq|75hCOS3l>RG7A`1kRCq=(fz)H~EfXN+89YI-Tp?8`(I+cLd*M zhyR*wAsqr@1IO$Np1q-Y>R%J3zSDz`*52l+lmPl8_=QDjLy*IB@sHg=G1z!rLVZ>@ zXF7Y75|tOi+eQg03)#VG47L{rk#C3(SG1!Ep9gej9VZk`r;eH}IR>0aAw;uTF+07Lee4 z$xUF~{_gZ92iFI@y5q(?mOUDXaEh{(|$LcHRr~zRl0=55)EXGgc6)E-~(G){B$sZ zj_oiIjPF@_>G%cnuvNS7Du)EfZAgZkzYctgGBAMZF}9)z*{XBG`Arg2p3DRp;R+SO zixZJRCCOt7zv4 zGPj^AdI)d14=R9Gp4Erd$E*!Z)_wr0tfoU^@ntmwtIdxY7T7@~_9P3c>Br8K0cuZx@>D z3rVcZls;IU{6p@6$>PIk-Xcyv#aQ%9t13x4c$2gR7M3CYi2jsg#!<|s8;lg z7Qu7(>(9v$nAuHSwt#eE*k)B_0*D%T{R?JKi~G;Y9-4x|zIh6$n&L}MI0b!PdNu=Q z??9G+#qtTSIQnRRR^z%r#-{Ifi-v)G*Y!bi=LWz@43^l3bGHU#zARsA;=+cs68OzwxO;jbS z5z_w*cS}9KcUb%z53gC&v*&@TY5R_rySM4=>njuZlLRoUsk`+HoF8mHh8mRL_J8Ld zJ{a%?hqd|Eu$Goh4Vv_IgPzF{zcZLa&5={v3+y}3Ku}y!Vz;$J2b&3c+Uhjo+KTc1 zL|JI#;|kz=F{LWPgx?CNai&1>Up!B&8fzR8@@Tr4TI0%D?{X)qswE%G)U$_ND!M@p zXy7nylSa;Ns+4LPZ`RSqE#F1r9}{H6CuA&q6<}VZ=ZmdTAc^A11@A?Hx3yZ9N{h6a z`OzA`{?+v@xpkwMSxx+yA;jfH0;oO3^T9K4dE4m{ve`<1fI}X2qY=0oLT3AgNM}^Z zLX1AQJvZ^u`wx=#U`p=9f~(Onb>1G3C1qx3FUJXroir$cJ^D4Z@EyviwOnS2BtfMeJSK+-MuzVRMm$GEyjB z+YOshZYW+ie{`_8mQccGb^${kbz>QyvNo|5esqi*vjI@%>Ux@4E&H>7IQc{hLSo z7g^rP@oz}MnsnSlRex6TZpKY{>X;gAml;65GtQyR7Xcv}r5&Ag1lw^jV>x~lz=&;l zJ9>b-4j(ilBF@DmJh?KeU|wQ%E@oHAT@bua$8=hn!OMUOb7 z`dval>XMY*qqCynYMumX|F(RYn(H9(lD#W(D;R;k{;<}>1T+?1d6;*W`lRi(h(zNs zRu~_2;08UMe^?&?Vk&*0pO`*vk_#ETm~MguZ4E!RiyzHVcbNTjU+-ezs)PwENXeBp z+N((mJ!F*@HlPGqQ1&5R{Tpx!FVx|^$s3m%;*ZK39+)k0=OCRKeJ>(xd*@k<#Tq`~ zn^pa$&Y-G461=#E|FiejE&Ck4u2j~%AwM8k2;Lk`Hh%Np{e`P%@2kMkkLDtM8ND9$ zZUg%QG4kn=rmM?B;FWkTJ#+_3Z-Ct5z0zB`bcGZ}H*pztf8cVM z*M;&378Sh3lovhPbepqpeBMKKX&IMFHzDuYULnmhO7u+ICXjyd4Inys$W{O_XvB2oWW z{!x6iwTrk%p<9LhgJt7vD6()zQMVo?rJdM(#hAnK>buLUpMwj5!O`4T;hEIiDLum| zVgNfkm!TS=X`F%d#35Lf@bmUvg(sX$)jYO20?-xDk+6}g<%eWlIF-&OWF6UMfg;`! zS98fq<5nNN7xZJ#qSnCxXpvH}51LT&SsFwvSG?-)x31pfABDd+Z>73Hta2Efwug4x zbw%0Me)RU%$J}RE7}VW$+(aovy-&jo9tvXC6hW^f$4bg<+cBG8J}l_D_?Lx)64;~^ zaV~tn)Uw#V`cU>7`+xsetW}_0S5*L+^(NY&+5o8m(52F!yXH^ABozHSosNl!Xb1$9 ztOWommD}Tc4uLbmfYflt=g*(tXL;;GrUX=z*4D+3&j76*L#?c2V8TQ*Z?ObUa{7>F zxm4Y-15yvMk8gnZOzzhk#IMn-5W0HTQ+Q?|Jwp|QzBP)u9Q-af^ao#3f8hw&w0lQE zZu1Xt(A5UxUmE>T8{;_BQV~aMOn9+$-(Hi~adNU6k@8`Yd6y%F(ELC|{cT9t}&K z{S`?5_mwB+6o%#!sTV>^)s^mETZP~3{RT1BvMFp72X007}S^gIz0$>-P#S8j; z6_84GUMv1eo!7hd{(sKAR@V}A6BHeGi(M-0C#3}M?Gcvnbh+0|P{NB|Bk|iWKVmIg zOX1lQL?mq5ygsBu?WYI(*QsaZp@m>cn4jWP0LIqmPP?~E zNx{`$u4_e603;w^ z0y9zR5ns9LG6cFhs@FO8)USV*AYu5QeI#B079o3KegL4(j2k=RGNdH|g_4UgY?9)? zBaH}O9*MS9*)?W(R5x|m)6C7ywP`vD?IqGlyO}Z@rSNGjHv@P2a~qYV*@id!z%6PB z&7YfH5&7B{_3F=PQq+UA3~6fb+*uQoO-ZU{7_U&|ZhFdZUMqPBGv5s&Jl^34EG0JjK8XW*7WqMn<`?TsQ;f9F{g`@G(f+7&hvAvBf z&vKnz_lWriAu6S%Y0~*WrB+_my3!d)UbqE1>EG9aQmNIWcuW1|&Ws_gGp18>PS>;m zvp`JKIbSuu_=yRdbcEyoYTeGKv{N$NAl8PMWd$K8+L#D_+Z)d+-g`6@?j``sJaj3t zvgK?T46H9XSm0)ZISayw$D8Pzj(3pf0CkRwH*Q%z>Tz4c!G?slqT5cCNe-4UfY^pu!!9HMG%9B|`SINtHw! zU=%ZmMZM_Q|8Dwl5&M)kS?WL6J`YQz=W&34VG#|yPE;r({y2P5RXUURR2aPA(c++H@9)PGpipFMmhk#|{q zac(bmdS zI!VI9v&MA=rf#GS7h2zTEzV2JJP6s;K1-96!*K8E`HPx7nB4b!!!)#Dlh=M}K|aOJ zgqjT|T6XVdh1kC1_dc{9Xxm=>zJn3f<65DgSp3tW?T?2-<3 z)BT_0+W;Sb;A+a4hhi2Om$$_U9;j(cF_i^R(TYaDR?|3^en74BQerC4+d(@QslLCN z^_ekr*Ojw%D?eP1WNCLRx%^ik1*HRd>f1*5;|-)r+XsIXNkvj3JpY=d;92o$v5vhyw?bZW-(cx^a6pYB0#o-uB1xCdLx6p=<~o<NMyaP0I3DTY^$%CQwQ8Q3iluD0V^JPaS(F@ed)O-7}>$E(3DNS~ZZ<@zWsOb*xD^ zVZAM_{l8wk1T3c7d79pF3Dl!?xWQQ8#p|6mo|z&Zp<4p`b!RXP&Iu#+^O-tev0ryzx;e zF>ke}{{C?hMb2cX=Vq#tg57f0*I{W=U5I-|o&0Mg=kmxlTdh8YD{;kZI|(~;U?*ye zsciyshjjL>wo>@xn>fz?EDMlNzP(KwvdQ=#sx&@uyH^!0^&wefkH$RNR%=_f9d61& zjU&u_r?-{f7cVr?^zBQ`qKsFdLo~5Pm4|idDP0WXW}1~^HzeAmHJYz0b$_V~0nQFT z=$J3fR=tsO8nrjg|Kr@(Yr$06fU~%^8jiK>V&b)yBPUD__v(_GfBH-v-xYL-8YFTp zaP$;tw0O=mKOiFk@HG9=T_8|y0k3Nbm};So@Rs}k9=*XnC`YA*9_g@z_Tsu!DH@)R zXU04yCCZhYtL1G!8w&(>W<4F<&c#5g3@yp|91Y0(#0b3S#zjG=Ng0WS_bC~|`9prA zpFrzvI}tA?{lLaZ81Qsg22AsXYck>1MvOsb4^gYHX`$|Al_GNK2Z9FN*Q%tgs}Woc zD@JWr6yyR@yd$U~KOglXQR8Wb+0$DdZTAnJfMBrmo;q`-U~Ygs=ub++<0WA1PE;E)kKK(YMS!@v ze$dXEwEIwjze$bQ;T<5z9xWk&ZI}^Y+7e&e3`heat!o@b<}`~l!97o*asSXMmK^|{^UW0DB~fTA??`9e=Hxe))VOiF1;NK z7FEk?%s=a*Gtm^$ji?|T&_)=O){2@;;;%VkYqR0MIUwB03pKY-yY%-w8?3vY3N%eZ zkoDn zBCu@Y+hU1(-Z2L3J6ZucOL&pYpadl}<&r*HT7Qaf{-@`n@2Tnv0{|gX<@gWq!0t#} zCM(V=c)KN}0qxk{8*M9T9&^1)J_PAfVc`&~*8K$|9cJ%=A3N|WO=JIC;5W@=F<%iQ zvU~j`*MM5Hv;Yehy}<8NrMc}v0l7u#l= zQm&rAR~vzmzGSA9n+>FPXLeAN17ss`c1o`&GH_Pd7WY8je+bVKj=;*;E8@o{4FD9p zWDF^8aZA8LB81vBTBtFkE7^!N&1?5B9ZXYC$Tb``o$05tatC%9ic(IezWvX*#t3ck zCTf=7`|x_&7bi`v2|s{LVPKMgWsKkd4b0l{eZXK<;MgTA#R`zZMgU{7`@e@{C(|*x zLlD9m8&aAHEy>#Ww-UOtzc>GwoM7)7oN-+_v@&UR0&hMSP+uUu@?$&w+g>wndmx3m zu_BJCAiQ7+v}n4WVlMaz?9w7YJ&1n?)E~8!R?nfFhVTORrx~~Tj2hjOnDs}QHG*B7 z7-ODV-V+IXfU8Wwg_&_v1ZP@U1N$E01z|&=mOCnK`S%Poci&@I6V@cu^0AFa9O5k) z|5Tm28IlxPQ{xUm3lu24S|e6prX=6pm3mpXAC=Zo>NJHj6b&ccMg=X7i3x_+u-!2nbd@ zi;I*6t8HezV`%Sqa{|c=f7e&R^GT=khaMS=y_Rg|rk}LW*qERhR_7w`IRCZBo$dvt z(*25bf`795fL->GZNUk4sDK`SP>rqoHjoE}op6e#JT1s?a41AIhd7rCD~C@Z znq4dxmW8EKimSqQZH;z#1ynwb%cWE{aVn+TU&&FH&%j5b9GZE{M z*-{f>v)g5WD3-Gye&GeCcf6>~3iHKt6+uEfZKTtAHWVwqD2MZy4tmt)wW(vGorB~3KE-fFPvnU}8gVkFH$ zxd?Hecdr5M&_3xm7_wdb`M(OX&XnAt=kjJF*6@Fb|rLyqpnhCzo76f?uN93O6V0*@#7mV-|-pLDB4jg zFxOc|b;!B=YC+A9i>Z8WY}rSZ=HIW#aA?c5w+5gr**RLK0<1(k(q3+Xw#;M8G44>* zsAu$+>TTF!b=GCRd0W4BjWDCfq1YpsLb;gj4^8UaO8$1W*uV;rNV(LTmwkntWZ5$s zhOY@U1p5BGm>vymO_Zm9-@_I+)1}F(&y|kDQ)z6&SRV(qr9WvKZ{3e1Z%Dc!ioQE$ zcARxWuny3A`^&G8I)6JZ{9|0BxH;b19#jMM?mnOxvp0IhCu5{98Kz(K#vRg_gVqOS zfVP5aQEhKke<b-23Afe*2fZo+t4jzZ?GARQWss&g$O$2k4E@{0BOXLA? z`Tv2oaz?8}ZQB)>Y%oGf=Au(9!~~3}@*XnZTXs^OIky9YdpK3MXl;8|W>EJDstuOl zgFwDaa&*%e?ci30kcaeh|CnUl{>*o%o`M1Y0DdAPhow!yV@7{%+S!HeiTo3qY54)L z2vc2C)f9jq;g!Qx z-sP~Afwz22+1X?QH5b)tR!I9{1hsAp*uJj#SVJZWI-v_-_)wB>%AiBZcH;;r1G5NV zxGivY`=yhSCfrO+Turn&Rd6iMh(JqrYAI~YaR%+p-V+~fD<%c@>2E&&o}}fM2+j86 z3A;@fc5jb858&3y$vpTB*{4Z78l+(ZUWJt@z%1w?Agszbz8O2mcz@~~tH3j$q)RJ2 z8@+`SoCXX^wCki|wMr-(ks7WPGZ@mhBryKW42u75+fDNa@2-e@3%Id`5p6^gYs5XQ zF^Zs>x%yDq8JGR691GMxPJjxr^ZtH06$8MYx>o@cZPJRZ(TUz>m?bjgZx~!Wi}Au? zGHGBh)s~ncd`=yA_2r`rZtCTN@3oo5-hi8QC4Q0#4)iyb;^m5`hya8V-@Yszug|~v;k!$sB|0s4 zMNZNCF9`U8kS0#2IH8vSi0d|f8}M981C*HosPMR*{C{gB2kk*h{B0Z)n(i9MR0kd( zs|ZiQaAL9>Of;TQpTxKq83lG4?IjRQ(xjLwq^;`)tm@Og>OoE@^XbF0ZGc4BuNd-9 zS2tk8q5G+5&460}j_z*`pO;|--8}V_uRUX|TKP@^ue$fd*PER{4|%@+;^KsG)DBAF zPG`xfqHP-ufo&S?uFzllCRhp4?spkHl-RC9bV}p(_~x}EQ9K^VD#}=ICd$3_7}rD0 zFwMshHwnb$E-#kfTw4BBzdF9-G03z(8ebkv1ud%#at7;H zlYvP@Y3>uCMUMg`LM}Uuk2&F4uO)VG@5q~Dq6b*6)(z*o?!fE%d{60qL#dynj@Fwv z#mxNF8>@LH2w_vqyy8LNcpSX@?`@_}O2!{cM>&k!*_Bbn^ummWTkt~F1$a}Y2|(Jd zEz3nDxl`8%G*J@AO8tNO}leWuX89;TTzl}KE z*He(6-=BKE$1TJ14w&)ibJ1vZ_BScwFWbP(r}I3=3xxWNHDGIy`}7HsEzZZ2OVAvP zV}U33$i=>%{*xZXAA*60co+G2Pn9T8|FJHDh|gs7;d#6T{Z{o2YIs|(j9V5cu_ z4NN11nGX-v!*f@7BDN@vM2>7S#)R*08Tu?sZ_uHzBagbrx2bjYqn(-3c3T}*{%4I_ zhqE1l$ochb7Q@WKeRZftszSLsEG*#QA^tBr-(%Zc48Uc<*M^zW$u)U=8a!;eC9 z){1{0uW0|@D*yBII`o6o1WR|_9!1C6`y7J@+8iqKSqf%-;eYF}wr#IzfuD8%RbHt4 z$M}EqpHDB~`#2z2q`1tGeO$+n|J#iIgPfbdKJkjuG#?C>`DMM5MK4tus2mO3Y(=?gUu2x#AY?Vzn-ux zaK%mf|FHGt@l@_x+^12cGF6h9OcgR@$W)Sf2&D{BnPVFZJM&P;JhTxKZQ&G(-8NG) z&o;-fjhVI~v&^=4Jv!&yd*Ao{?|kk#*M6Sgvwmy%uC>13NX?RD+&M``>ODZ{nX+L` zTBww>#11W8552M5^_nn7%cZWx4B8{$x>*cKz`7TWQ0agKF-yLJykdE%dOE6sO-GD; z%RV@4?tBHA@a^OCt-f65X4>PIQm%<+_qlvi8BRw9({eS*%!6Q4Y?f}uYxg&X#3p1C z<3FeTmLN7B|0STHR(XI=Bz-Bi%(y; zeYu+q%=m(^Ro?iC)mF1AMRY_n9c6*l`kIac!rm~PCrBEYL#t}S>(4c5s{iM=VO|D0 zfdL2L*9^Zr=Gxutu4qyVUYcmW`1$i^9>$;;=$~;7v+6Vi}Nm zy(d}atROrJ=g#<(N`(yOB(h=20)=wlpLaf-vu=#OmI6G);x(=_ zUCjziJIg7%qBh$%m6T@Ib&>Hvk(0*KVlF@LA$7Bn=dZt(>Nq3nfatoTKLh)MZZTtA zy|TOVYWCbC!F6rx#B-Ysy#WuME5(NN6eSMI+84KFIKJRq5L%fJw1D@F3y9Rh%Gc32d&RUyDb>rN&@C z8j~#LYVmb@IX3B~$t6oV#Ul}oC6wjEis`7w53B=(oP@oSI}Y!6ZoN+I%F?afR<5Ty z4brb~7KNsbr2kDz!8^z#Pz!cYG5zm)vyZ|9*q9gVEn(IvE>zjOS!!`a>FwkF zlCDr)h>)t!(g8w94Y&@efDlO-u51hF^5CD&p|J-K6600-Lj9Gu zK$unybfcPn?ZWfbfj?8P7{&}%Ful@UbmqDprwkL9I`_^!jC5|Hmn6wLj51Nd=@&&R|vA+7|zN!`=QskPva#8gv(zBtES0OIlFp9gs zHU7QlfKyLG=lS)OUDXM$GAw_D@-E0(=-_SjIZ;7{iNfYDtE#J~&fZ>>f=_#9>}M#0 z2v$8ocw(~x5&8tyU$4Y`qu09d1;dh5*45=5gAcyK^Q9L!=Ttmt+2AJiQwZcSTEn!M za}h|SG{tZYl(=@Y@|FGDPX7UmGUvT&KaUEdciHcE>n&ac8*^Zb#i9ZyJqdynK20)i z3gJ`3`imSg&``P2S71E^YpF}%J>&O%4!?dHabu_fEg_zm%9 zUulmXb>bjLCcWI%mL`bd#xhj>%ndtQw!SGw9FZ5=Xt&Mvaon!_)nxB#{~57C*06!9%du%-IvjNdyy*>{*Rg)FJ>iqFP>z6wN5v;Qyz_05wvcU zh$%I|Sv`c$9J*AxOgYmL7wg6j+9Q4CLR-ywN49{J72hxf&q?{w zhJl8Lz1zq+Y#SV1tQd+|2f_Ca@q!l&rqtX}m<5m7M9X*A7@aN+jPt@)ZEugi1Z~1{ zo-wm+K__l9qh#Xc&N^Awqi4Z?l%48Su_qaMv1N>ANRnJaBx~nLLltZ&;Nxbal3wuX z2eICXbhDM~|Ltv##NrNNRC*l7;zL8Ecx7DkS<)5*&v=>E~`^)l71EAvPpb(cX98`PH3h)d&2%4FFY$+#>QO9D0Rkppt93%sw|% zQBg!jCLZ!9Nadbe!fq_jh()_Q7vK>adjlfIE1iyhSo|X^B>$*C(+eq*Q~3F+ z-HD{W(2{~A8F#&_E6|D$SpMDrIdD}&iE)QEYg2HQQ7t8_DRKK9Y15+$RL_Cy zUwkrhRZ(mX-;Iyf@zVfJLr{-G5Ac*dT5Wvsu68UlSk!5rbeaq9TFkwG6ZQl}VyvKv zv0jc*7C~>%39z`IyvO0IR5Nt|!V3?$tfPLi!?8(0azFbiyXz7HB0{&N;x2#P=Syjq zcG>in^$aD+(nr$Ll2+EW<;P)?kyAxO506CQ(9^koeHPCZ?fh_OU`qBMrg%C0uk%uT zo6f&b`I=Kx%Csneiz1?7-80Z^_{|RY%MaDiNyrK~K06w&O1iN~_~qdDL^77G*=5#M zgQR7k;BJ*?8X>yDuV-*|*F`O3oq@E8nojb6FK7Siy;0*c7M`iib?H~oX*%Znosbh>D zKh24PiJsTK_7Qi5VL;0NfRvDhbu4&PH8R{?Wl;QSBJkNwiG;sv*{(@ot=;A385NhZ zJ$8(CM7$x`v_fLm?MoHwZuJ$7iAVXmJW@=nQlv|tmWLXBakGDYh?55CEek&CH<#L# z`zZg-1?qUMZLSaQkLbOSS}keGAv2xdioF-bF+u<1QmdSxkS5R5%`uhk74pQ}j=RiH zuh!^lR*bEdjhI+bHDWGd(rzAWj8%y-n|~vtY1rl*MLUx0KJS>JCHr%w{wR<1O!Z+( ze6Gt(?dj^c6%5|Q^Vh4fhF6WseMpZ66nIRHZ%tr~wF#83@Za{4d?op4){vDn^aoi% z?$PC8I`O73bd{`|gd@kdsgQ`q@*Rrg4VGWyZv>pbp^wQ%z8_l!F}VeKI}2!ELCh za1bn1KOyPESqRXb-~vChX(6(2X0vzh-Zv*!B1$bRQSwLtl7FVw2OWXLy5ftur8;)y z8x{%D=sZ=!>iJCE2>}UKKy$8)9>;cJRbtdlMlpItf0&xR!l&*dxD0>wU4n*de9;{c z^E)plrdQiR+8b~(ii2PEfuC35hnHvs*)?{53Mw)Cl63ydsgDWRU{KMPe}~fbfw*F% ztYxjnn&+|MWZjt=HDy9w08+}A*VqbHUPeP^dqRNPfQE)%{}M!0(1E2Z&Xd-y0J5FA&}$g6l_2 zPSKVk)&BF9SbV?kSyw!?Q9gVBs0LLM?d>pd8h(x>*x2MocsdYjjR`J|B-Z2z<8w82 z7f?42U}xJ>Z_t^PZb1I0tgar4UtIpz-jM;=Gq3Q+*vy0TAU@xvdwy&oEfN@=ryqzM z`u&?2{P7@AK}zVUSdi!Ne1{*gw=xOkhf0-VTe;M2@ln3Q{++MOa61^q?K-!{%^!q* zB#4PVu=@qt3w1#GW=E8eyEQ4XxgQStzv`_lm=oQu5XKGKgEewRR1}OVP#=PE{f!jJvpG3uSnndw%bKhW2tJ+aXrK>y| zf4^=`V`(S7z8WLX$eDfE**<^dkcW$J!!#RYkgCWrx7lw)E*ea;ul&=5QQ5O!kaxgR zvT9-E8^W1Mn|riBUS7<(?5TG9c5Lt`UD#Eln~>alVJhi?8`vEvJM1?7WIZ?QAj|(E zSa|UF*A0!+H~0x@itucr)zajo)l-4r28Ngy8odO4 zZFjPZt`0eT@0IPX-8WaPmzRW$?Ea|9}frxSR{Qri%3Zm z>5cZ+m>?f`LJ!FU^`U13cVFwn|Al9MfzODy?d1SnJ1jDa8=TffpZ4(C%H+d2o@cFv zqVRwY-&hGQ4Y7PqAg1@)>-iBfXeeZ6YHvdeWcYiIjo}A9{to)m^^p)^*Eu84G!pG) zMLlcAb8X=j@V`|Roi??)e{6E^`~K6Ihp&s8c<$V^;}ge)RLv9Hba&jrz7oBc>4RRD z{CL55-lpp#H&KrwIZhE=lEC^6V$16*7Z;-@LBXsZkQaMdS2aj^0BIQZcRXm?JU391 zSneZqL@mQ1{E6}O$=^5I`2(mqsM`C#=Dn>x96n9c7e&^lPIp zkME#ozjU8B#M>@Sf5>fsp&MI;Ss2zcSK#+=fi92!T-lnjKF%si^G^Z9Y>XBHamnKL zZ_s>{Yl-0O8h~Uj1F{vUB>bsbj_y#l2LWNzyD2IpWnvVwz4iIk-^S3CUB11fmXA9- zIz_?i(WcCX>ihpZ70GRRqS>PNK-E$4KS5ts1q5I;46hBtic@XB$V6+3J@;JzEMlI{ zbWMF-rv2wOZr`d>qsq5zwcCsb4Xy_9ArJ8Hd(T9d7vti1Dptm#+4jPudfz2JD36OLlPAN>6< z_pG-d=}PoHw1kP%_~%(hf$Hl{bl~5=kLZu+D<#Uls7+G}Jqwrn_vyE&85t$%W2P{M zc0!)=pj;_@$$_os-(LnfPCgy1GSFKNW{{eHyCpB=*O4X6y|@Pm0V@*b{N%8JBiW>T z?QFh}K#v6TjQLZ^f~Q_TT_XafM>iLxz_#S#xfuZBoioLFm9J;@(@|CrYC8aE5}Dlu z9XAZ9PA0nlX}H<+KPW5W{O-Z-J5q}}u$Lia@3PA}c>C9~6F@W>jl`$ke9QYC0Biu5 z=9a4Ups9Yd3o-Ii5Jp4Pd#qE2_y4|nqZVEsyxM8N*LfeVjHB@MeZ;Pi@(gKH!@V=C z25`g^LA5B!k@(Wkbko}K_Yp+^-C#{Bye?Rss(^p*_L=k^uJ8nnlktnjil_JcD!%W% z&DX+&MQ0F}X)2q`DVY=A&7jhmN6I6CLT)6F0o#c-fZv1IHTBO`9l)a!(Y@53O&~^O zNzR7P!lQTmPw<=);-MR|J1e@-4ayj2-CYkvWBXnAe*~w>b?=zfH@`!{f&-<0}}Gz1_3W~=>n<)DUgvX(0DeMi)PX^r)c6FBITAO<=S!u5GsmL%d>{? z7d7KcE(<~s|1d?LXWsbUp6Kc;l&9L)_J?(&(-Fu|G-C0(@709_2X%kH4!pzOYUlsF zj51&so?KV25*7Yh#IGYt$yc9D0z~xEBy}JQcfxMc24tvhz3R}Q&}aU$=ktU}@;hUn zzkjF1wpW<%ZcY+|R{p6rNS?SL+v&sowzt=NVNmKbA+a!8=f98d)2swkW@~W(mnN!Q z+EE99;3?Ce>!*ov;D29Su?1(%S1{yNf~ap!)H1cit)8I&9q6wSq45}@lPdpQ0UlcL z-NfB*dp0-x`?vlMx&K-nL>f%(4YYb}?ae7m(1^0z^k5FTK=#AKjW1*;mV;I}Ql&H^ z1wW!Qt5om#&+m*FAiPU+&f8~&o~>AM`rp!9XLW8H9(?KDl>hmyIZHRpV?dWx`=U6N&QZPlZGDI{dXao@O2bHn z53*89Cl?~l8kq?M>J)^iMxe(f=?wv6g9zC<0h4aL*9#T?Q6Q9iSpglpZHMr)?qb%2 zsujx8%v)UD)xV>)gB)moyaNQ9Q=8}o-0#KGm1yL(4t8VV&a(Y;XZ@1Qv+DsR{1c~f z;P2D`<4$>C1fa#%6%k0+Vxh19T3HH7diAWFkvB6|>?(72EdtzDW9X~eF>%|Tw{nw7 zfqVAoBUS>szFDy6mxf~&dK93br1Zhn5x&+zI-?l)`~RliluqDwD_((e(1-u}<@!th zKPXt-uH_(;vl1(WbTtI5xdHC(tJ?}>Vq!DB!iN!cv`Z%CMq4kI|9;gJ(_U80ZYuu} zq#n?s06C0gslQ4j;2r<7_k|gb#xEKZ^tRHk`6ulF+=+eg%?~d6UZ%eghcjo|1lir! zUhkhw5%L^<$e$K@IRvMB?s8W=6dkAdbQE%mr!OGx*=+S^@72SSRL<^5v9Tt35CwYF zbm_B7te)5?{Qs2(`o6-$q`Td!ws-)Bo+n&#R-$DpKmOi^N5=Zs=5Jk=n@JbB73p|1 zV_^(9>z|V_Az?I!W}SY$nM<`Ecg@8wK;RA(9LggAd<*&($oObD9>PRa0#{Q%>HrsZW+F zQ0)`_=iU_+P@`6U=C{7~dXZTS1aek#@77NclfaYID*jlT>Zn~gu>he&#n$#?%N^$U zz7Mz)C#-UDeE`KJx`ggE0XDutUk1#waQNAse=_%@0XP{NZ*|QY`?!vy`D3<~mnGxc zno~<|M1;UZ;(bl#nM)ZFt~-6MFBu1tHkrmcXRymlXB-ossVEz+1L(+Q*^>00AA&6N zo?XM4`YwR!!vTwC|?)9?B1&PbUUS9pF z{Glu|2C$*@nioZCP05W(KZT?N^xJ#Sl#|?C7U&)cDThhh#BKQ9RdX|l9-=$nDFsW} zz6VlulfU^E$#{N7Zp`vu081!X_f>d%1MrckYWN@Q8xE^;gw@*JNV9sE*K$CcXr&pC%%5WQr($lWHd(ZY#RS3Og|L5IRvp2aIGp2d8-41y5w-Zgzo3m-*ZB#RLR2RbzP{ZYFdayKBg9FWf+Q z@_t(vwLz1v{_gVoM7!t@ImHd7q3T<3K&gjOcu=LBkVT09l;;P_t`JtG;jjHWik-!^ z1C1fcSD&u6a8sKn3a3tSqjp`N`D~*te6}5@=LIiw5?;;kUX^O&nf{W?MC`l2MDo75 zxhlY4@zI)8?D?22g}Fkp9_HAUXC@B8YvruXl=nBd#a1i85*N;-ds z+hJ%t7=A%kX5b~V^-GqZ0o$#7wb~xiFlLFn+nFLyr*FfKLJbE1*>Omx@L6v);uI$x z=K3Y4+EV?|sVs8WRzNWl>fz$3!CY9H)0 z)FIoYz*n8;I8Z%uTKk!siK~9`r=pda`>L8ubF*ApTMguRyQPKi5h@BLetFMsUfdX( zT8Y6Min~OD`U3{x$qx3H@xMi^{6jKvA@zI}t4oW@QVSQ#_si(L8_+eLh9`iWIU28= zfE2@WyBk_)m<4E}M3O|ZpI8P#?8$I>k=?-6B50O1-NPCcIpvwv)8-FmL&m>2|7p2L8xRbMZ-V5wENN?wMNF17R$&)0_@Fl3+GmZS ze81AsqaJy~Tin6G7v9bB&~>*TfT68|t2R`6*1mkVV}lIawZdLco+9gCRX`*> zgr0AV(eln`)$)2%I&kn(A2DdCf(eh3C+bBLuQ=5$-k?`AG?Z-^t{*ZKbtl_*nDBmo zE{a!Q-OUQL>bo9I-Fp7Y24NM6>$H%_{d*2Ts0!^Z80b2Tp3cg6(yY3Ni10@mG~G>s z{B5IFj?9i;o_GaI-o#MteuCwhJ%`8~iHDUb3+ZO6SV)vagq44X6eRgT=p!B@-xv67 zLt|iyhU~rR#=2pxuVzg=pVE{>{Hic$N0yYw2tF22I=K=#B<30!#d$&PwwbnyL?!Xd zj#CND^M|f9Zc>O>R>Rgzh6Pg(mFz!+JitoGaH9-BaE9)2Ly=C~? zs~z{^hL>FYW~-r!d1z1fUR+Lc=w|85&K&e-oVi@P;k&UwFfQ`Xnmn&NhA=Ri7?tNE z{a!k7?H9CXKhO0OwSli)@sZ0DvA%rr-1O<}@t|c-Vl_X(cK{L57BcS)5M^t59l3?vmFkG$p~$KzRv8pO$qW z&RTcb34>5V6S8!95NG)b1vk~|LF?V`FHVeyOU*?hKu_uOl)9Uc-5>hx)aR`4m$+OR zwH}tuh{EpyKnK^D{;5_l!x+5hc^4)6tcAHm;b&%7I33h5ZW=6TXk8gqB8>ODgXvAL z3o^o2h5DG}I@MO^rZ4n~E2$DH_s4+FyO-VVlwZ)F4K z12E!gJdm_RXl#w&O$l)&yqV_lKU{io*LB&SJ7u8asj$ts*P``l^l~;SUFEy9A`ua? z(04DmUS<@{(;d=6zFIdpa39gY;8L2SA20KrZ^#<;G@otgTAHdoZMIZJe9uIKqEss{ z6>NZQ(es0RYny+e(lxV_VA9TA&@stCM-YWJO299`&>-aVhyIQ`7-{fB{CgD8NFiy? zpK!m9m;7Dz8j?7ZLY)5s#cMQz<3&gz)Bi9kHgLt$!nddl;`To4zB2bUh})k*{Tyc< z^LN~SSep(d60%yQ!vF0Osuc(VzPpz|S!Vy*iWoN2r}9HSS2_KqJ*+`Co3*SQCS5EomCCN@i;V)hES&;Vnlu>H983s}!Bg%bnLsbe|sx!h!oqvs4 ztcohWJjM5@#nj@AG*ij%gUh$nDgrIG=J?lf(Wt>v{q*eLc)%c%L+#;DwEauY_L6yV4mdTdRfMuVk}SW z%8gSSi?}}FoMxV(mBkarI2Oe>)Okhc>6D)ZArI{}HSi8wH-A9m>eBUzaHcu^jx*OA z)!K?(`33G=G>?%h?MmBOZn_4JG*vgZKy3xj%NZ@lWMKSStl{EOrLFkct*dBnxeuw^ zxY94j8CG(dl6&hE_UmdUYE3Jb;Lvwfq5Y+)L3Ak|-%lBIDLO*N_A<%)DhnsLluW~%K zGp{E*Z0~iBP($UO<528eF`Wi!*?N$Gy-mE@77jJ|Hv}x*1Nzi1s~KW=B@)~2s>Y#j z{HcdFL^fo~H;Ui?BxmQZ%-zvZ#kj$Pw<1Ca^$B$JODZ8a)IGTZ51E`7DivneMXdkw zQ|{B@rj2{zQEq-CAk<+g+gX;dI!S&*_@b-LXCm-HpE7Qgjeo=y6NPte%$e1;82T>v zk?m$kIfspxEx)PKoj8pOTwFrOv|1qkIo7!OCv90-j4s8SbQYL1NJjw-WWG33{ohBj zAobwuAIpBJNPcuq)Cv+K1f{tV11R>5hB!g{%^;pPZk3^0lTyMnrLX>RKu|Ii0m*Gs z{A+U`F}wcEe{x2xe{;s8FnTg%5w@&c|0q|b!kEi^rsky6w3Yo-S5cvv_ZB+q{>2P^ z7avsAm%6(fo{$j!nwb!N^kfasn}2-&5c~ZYPE=Ix=aORO8O$Nij^|=|)1n=r&#T@uN&`bhC3G1%0 z9a}eZ{erwOrr;U&j*QN@$FGK>#Erf69=(%FG-tO>#=gO+!3foI12i`(6^TpoSHjze z4p{63JxHz#SIX7~MFbf*Ugp5mH{xaY>f>i^Jfb#T>Hss#76B?0wvr~-@Nrr1cgVxe zBBSC^-Gj;KWg9TC|5Ng5{Xm=zGU@TEleUR)Ch{XVKj#LQiq5R$8?c*fK4!UvKa|`$ zhj~**+eWy%pr`QKqv=-uY(W>pf}M>=%PXYe6z3YRC!vvO z5RDjrwhcJw_+l+<9D-9m?#x6Q4U9NR7hA@A|iZG>Zmh|0UN-csnWWv*0-BHCy3CEkjvB> zd$jM2f84P3@mNF2!17Rl$GR6fKVW~#G|7&4El{;)VYb?c7h$rqHsIMV^ec`*X$FeUR(9n1#u8E?y4}f z72rIiQt_8IQj3CL->k{|dA%~qIBzZkPsq19It5F_QN=PPr{jb;KO~#Xz0K{d&8<+7 zNVy{}C@xi85x8MH4%$N-gP>({C(^AeUxxKzk{IM0h{|Bc89>G_K~1QJ$07Xy^%U6w z>D7M7*_QXefJsOMI%^6Oe0SIaXU^DrH}e*|v%2-v#i_mm=#WBx;z}*U>C;@Gv>anp<8ij%)#81257-k}MJ&|w1}`;zeF$yZC0N8sh%@+< z(rKJ)Yq>~H+I;x&vi+cRaBy(DyBc?O3Sl+BRaR}=Hxo*_rLFmjlJ|FGaq^z1S> zgUmA=`e-k*4vw@ao9=Pss|wcKLVy7*|7c4ft5;7y380;vf8fMWn%l(9qYXb_7!t32 zSGj3?IctLcVS#9U_3T;C)a6!nLKjOxpDRP~n&e_Nzvn0FpK#$m12iD~D3*(Q{b%Rr zL8t8pf|6c!QYe#PIfCKI`5AVlRg>d0Ej3c;$ePmOruvw=e#*Y9=r@xyKi?Gu=Y3HV zVY}1IlfpVMbiXBO!9(fXs`-*LA!;H**S2Gb&qhnq>um;2)urSIjmIx}UNepz@OsfX zzAink5|K{BHcP-;wWq=8#FCkcp&4h)#jf{FAb5)~UB2gk#j9PZTn`(`uic~j@Z=Zx zCQpZ+PY;*C!!*1+eUZ?4msWBcihIU(1sS1K4`^_Xff8;cP`)VxS;iR59*El)zc(R6 zu9*&{@O$HgCxA-MICW@1W0E=9Q|>19lv zlm|}K9DIsl-{EGiWo;36Z26E)N5mPv z4)t?#WU&faEp;nUBibk){m@8rjBuv3I%CZ;ZnHnmc{oO_=={WwrDVZ+m6(! z>o~l6EOk;`bdXWpN{MvkI$+(Z(BKLCICfZZ%d!&M@+JwvdW!cC;LN>2$HE3(f3Ul- ztX04Or%Wucx1WxH&45><6Yg(wz@u}?JX^Uuv#hPud3s2Qtw~{*PP5ptyqf5HHg)C8Xi}50_buBCONg`(aSzQbD_QsOP ze&r!#q=)%U=4HJz^gcD@zELKx3q>u*Gt4Wwl-@KvN!l3hW$j#*{q(qjVq`?hfoo!eEp>^Z)sQMu(enL=%o%!O6`A9sx zeGD{y6G}_xOO*9FE|hi)jyjj~XesciadqaJ4D~0@;>S?C+=ptv?NFt*L{maXTgd!} z?7W_F!RY92=f&NWt55&bZ`tp=3gl}E^JQCn1xue;hc?q&dg5FeY_Im&Z(zP>hwFj@ z!tM`2YqjzrkL55>=&D}10!=SrQXhYI8us+&NwDOjYbU*iu_=W+t9{M7m%a&;sYLrD zra};4H`M&BE79ENoR}@|OjowZC=*n@M~R;yDZDrA1psdHb>boigV|Xp-&)67*l)2L z`Iu67P?QPFKdz*?93osxA+35Z*H|XYxU4ve)5L2dePbKwKSjj%aYXoUaJLAxv(Utc z&fj8XdxDx5nq8evB<3yU7K^92Q|9Z@6>2CW1BPi2Qi~`6t>* z&nfARb$qxE&A!|m5wgh{Sn;4a;13HVKa`2k^?rskIEF~h*D`AVR`%;v&(f6Ro0T*^ItZ5D$~419H!y5)dJwK(wzQa!MxCLO9E%T1F+p7w8@GTNNm_ijo- zV9U)?BWRx_v}-xCNCd8Q_h(2d*0?kL@yGYSR1~h?9zsSlGgBtmd9oI=gWoJgUNTb4BP;q{-{8jneMF8$W;lu@P;K!|uCzEI)Mqyw2)+!#dMalSpekzVU2=&|?eRlJd6j zsR#zs$wJL~D`YlDgNK*1_FGtD@EUZ0n}wE&o~xv+VgK?|Fr{rc&zMznFgE7viHO-u z9`1zBqrd#0nCr+nPpBUt_^p)l_|MiS;>H>e2KXMNa~gc9zCtt9iyOOcjOCPRSHhB* zjHV~l7&aQo2|glG=7v?iJA;}ZGda(l(+LodU*>jAd%DroV7ruhf5C>ewt*bQi8UQ* zHA}wAu66DbjZSPFuLb3a`OMWiT0T3mM7y(4l0nh zH#Bf(8aYAqFvUfRpiL2UcD4Gmqcb)qi15f=cC^qoVPCse>^Tw(`*j(I!7NI$*0aQ} z6Xm2N&05zbK~hNO{k{q18$92>G^{eo*#y#l zQ`?Zp#WRiFxQacx(c}D8D4#X4g&rKADmXqul-HsY;fz&`f-}bmb?(X`WvLugA7;Jr z=?l^G#OT`XB8w^3dz2WjRGoBHZk<=Hg?DMp)%0Iqv9TbEP1VQXBYbWiahi=3?`9A_ z+a5IhrF}cr-*YM8@NAM-f~ow$B~EJXG^1Lh5B+XaRE4WC({b6jbU_Pmj~XwZSw5=T zZxOakA#0w&-WiI-=od?DvQt}=HWG(7eGR@mX4p`vkZ0%tor1L2_4{KEPy5Yhd2hms zkr}6!Ep>I(klgNe2X0?@1UW$Yo^g_*XFC-;NwT2xU6|K|Q6y&b?O>6TqkU@A3MY=D z%lXE%)6m4($%u7vIeD5B8s3Qf#X41sf7wKTxfXwt8c{%Sd~}n$ArVi)=xGyQU;5B~K^w;ZvCs3C$3OCt1BCXQ z-UkQwhdDNHayv3row}%PfM6iub%EBm8jCqe7t}J+uF3rw%Xz{n#P^Q_2M(TzQW2Ah zv6r>1d_42ABFKkN_kDb=F3KS7rCSyyoAgKAI5vjDdX`fb*&^wsqN8tVO>gm+Nl_0< z*ml}hx0+q+JTyMqPn)XHdCcCxTu$06XiUL^SjsCE@y6++f*ps%Pa!rvKB}-+wO8G+ zt2K=F>f0=KWxw<|wW;}Iw{(6reHKAmqA6CVlBooXu3x*wGE`&7?tSneN7Ce_0E&^A zy31oY=0dg$g>4chopQAP!o*sgV`nZ|5hG3~<28c?I5$Hwg{ND{953UqeT>YQKQw*f zsXgb5$TGX`Q{q6mM`MbkB(iL1+bf%MG`jhhX47*p1+$&Ech{^>ng59%5BsIe;JzKi z(Y2Eo^WedN<#}-(uB_GSxAN&IiRfq6Hr+!U$3!G)_;IK0?yWcm-NxT6U-YhG@f4q` z3#>nrck+7U(`vj?iES&DJTf6)^rld(P;>qBNR$$b(9WrpC=AMKZMB{zqrNbk)hYjZoQO8>S!XR~x>YtnSE5PeLlTH#tW)J& zBwi+U&u(>A7L9KXk3V0bN4N=~kJ{~*hCQ{(T=Qk60P&w8WkXIwdl0Uh&)b9U-mU6w zas>Jg46Mba=NSJR%~=VCo>mErQ$aZu8rdf&yQonBk^_h2;GT#RRUI+Z-V}FW+pG^ zOPa_hxYa7!G6hK(bhY9LQEvTODG+ZjWf{P*f9{R@oDdD9b_e&PMWVQ-gEbpVw#$2# z(UR6762phTnFV>aEd+%YPrXkJ^)WYzO)36CFSj&uQ(yh3P;DEkGKz(=VT5XU-h-US=Dyf+B+`hj7ROc*rWqh{N_r^3(A+bMteXH zNPb{RVisAA3*i6N_+06}Gtj@2Vr+a+nYtX^)X8;B*nMb+k4Ja{+7JqH;%J2 zP6SMzc3`ZpiBT5C(r7WZJV*jZQ`sp_AVC%-HXFH}#c7;Z+i1W`PkObC>N~Na%kwj3 zTf*CDD2U1H>?c%riTarw*5BDAN|hoWr?h!{)7mSPJ>sK&{%fA|>*3opPTN5WiRbfT zthCd8o{PKa>)n@#lLuvnw!Z$^VQ>LW_;|3aGxb$7@P)6&7 zmpsER@P8~TOUfy-M`XD+Sj;e}G=E9%9K$?WLDwVFKyt@v?yH;SgiU`zYs+p5U}dxm zJ+#I|q7$~xpnF`ZTs*W4-~WL~ZzUVG*3^rfJRDdWHkQ zBk?d4{)^<)CF+zqQSO*kZKLquV7}V@{Y~-bIoyKNaM8yjkA3`BI~JqDn<8XA7I!(| zVvOT-Qx`i2M#({?^{6R9V-RFkMLRjsk#9B?P6^X|NfvR62^JCdRpCYJC@yixcyBb# zy7A*LfqWSLx}%3E8{xoK#v5ukv9#c$lI>d3(aK||Y9BCi)+${kh$w^5$8lvEGhUsj z7tN063*OZGqasI@83bN5YSe6m2g{L|_+FkG?spJkzZPu7&AO^&eB`d#5$z6zq4vS7 z1LYZEY#C^Go(*kpFv@TRcXsEji-aY~aULp+%mW`~(NBi@SzC=N&>>wN3uLo^+|_@_ zVeqGyO6_wkuj&wb@hQ|PVN#M(gTCxOhAMR4)(^q0n@@UGj`p=-Bm=iUMRtZoT?au6 zhw}iOxEy00Pit;_Ut|%5S!rI`7bKm-PL$Xm=M|`YJ8dSYoPx+I_xiI5!C{uLy3rm8 z!tAjP>818*BJ3&+o6hVEBCVu^*Eh>=O7`0hl1tVB)hX*s@#M6SVOy_ zYNLW6O+%&a%Ze4DOj?hf8cFrQ5-%Rr#>BcKw-e{e`pt1^^bAMh_z4MD?8ExwoA>6J z@ywr@hiG7n=1tdLK$e>A*&?b@SqWY;F-s{=m2sw0Yx(k)m+!f3ploM&L~NU{SBj&o zoI2s|A8SV@E)A}|ZTe$}(^CiqXu-nw_?Hhjw<uEXhJ&ygi*_L9JMgY38C$~OYb6Uep8Q)xMSbG1M zyR0A=sy;?^tkL!B;u6D*0LP^$HxDftc1G&>PC9gt(=k@rbkSQuU^a0{dkuFeJgi>smtrq! z!hkvV2xjAo?0u z4r6;>AvV?Io=Pe%g~pD%2J3QazCGrL3h%bxZL?hPr{C!HjEpP5rL&T1Q%Nb_gB~rv zEcz~cZ@e+(jE^+63nRiyW97qA z4=2`(?k@Rrd0y~5Ra>*e(CZ;-ow%3T<%s0moVO8_d0Xb-J>qr4<3w%e3TgbrY= zL-OA0DX^tSTk0@3>0g-9e)kW#gTe``id&QR<$d&p+ZlLGR#C*WxsF3%<0w2fLLJ>U(-i{#~*xcrOXL>I~E`RN#rP(d`K{R zUQ@XO<&T#EoY6GC*UUNBzZU_ut!|dGmB3*O*3pk4E1N6b7<0ok?^F?XwIhK&e8S<(@9Pu zM-?8hVW2K1chc8J{8Tb?GG0%*I=oFQNxGk5Yr}Pg^m?Xx)*y8bdQU|Q!8M~3%^N?A z95q$6D11G*S;He%zYEg_9RtF=PFX8fBdQ=~Gn!hz2M^s3N{wr&o^1EJL)?rT(pq(O z*lqE~8WWdBc!~RW4!1kRm1xHJ%>?6HzP!9-9{r7q2bqRwUtL}F@|xVYWuIgIHbOZZ zH!iqC>6f7Fl_N6u6yJ|CCyLTWj?0&Fu}6mQEXJ8DuBQfb`p2M5RJysBw27 z$FfCyM?7y0V!!FT2>Fj4fx_=UZA88q#SUGsB=B@pwDZ6qx4bIo#>)NdsVrd5INidj zc38;eZSKP?K5{PU?jpqsZ2sJ!pNrc5qn>bNg}y)VY>IP)gBokjEvnyTf>gyLrkf>P z@gYK=nJLXJe($;X{;m(a{G3G46ZaBsa~z7tahyqT5N5~5s2}UPHTwAwcWICs2U`O6 z8Rf;p2M6hFFB0Q3MHqgCJ>ro0)p+pSlwuJt>xN@qR731CvJKImrQpiV^Q`VoD`*rq zXkfcRz*!v^vlf!=6U3z;AWlip@fxbt^EJH|xv)+X?UO0at$S?)EgeT6*?!Ght3Jze zDUbe!Q+jrpPtO^eQH;3YQ&V{@nV2`%nbQ1tMa8W{ZVyXyxJa^gUL>43`ol28R*fad z#{Jz>DXmUVr?dw&e;zqC9qS}KEKkhmAO8_Z!Q#FuD@!oZB*^YBEhVEkb`!*6-5>FC-MGW#r{i+s;9Yvz6jCinq6rl1ibO(SyKJT@x!FsZiiu}!Kc@9Z zAWuUJ)mw;iV^MqAdy7za_S%;?mbSwT$DN1X<^CXlUw__;@PZh;6-yWswHKxxkfboOv1jVL6=PpuV5Uux#MFg%hz29}(1JR)PRqwg->Z z9O=>pYBJ7P){SZf8ups8SGywZoL2*{TtnlMO_hA~C0wpjP6w*+#dJWTG2N~zz+G$? zh3^!k34hWuX2ZjtMAt&W6L<7djOSIyDxvYBPV^^IhXye(Iqd3$TLMnxVMeMp6_eCV z^HL+u6`S2>Ms)YKt$kLos=c?f_OV0FQ^yi(suO-$MN>xP>ou$Mcj5DLka)!-4miIX zHEOf-EgtuvDSD|s@o0p@)T#AZ5Oy&~cRFO0WJK#|Ng>_IN9bBZ;eN6fy*x5*B$E2% zqF}8HaM~IlabCT#!U5wx-^-(vaXfvJ)Zf(aa}-f`b+VIWvpn0h*>vvloH`nz{$o3u zzA`k9gZ)Ev^bNcWg#nQ~zw*b%a%f;h%SlmhnlR9c;RLN1SMqLo-wM5?_^q+E>l5xA z_dGk|pJWJNM+LW>)Yd4bl05sKypbO0(5`75uf+$OZ<|Ik^$y*y@zfsZocT&NH|vI? z)?92$%$&(;X=1m0W8YNS+N_@79M*_PJFXflK2xbl>7MTW z$0i>O#pt8OS=-ZWgf`ByhwGBST+Tc^>@}QsHgW8?d07!41ms9J)r*OeqQlI_8e5|rz zL&WxY*8VwbqwexRf^q`et`X!Mut?P|ftGCln1`UrhIp&mkU&Gq3 zVel@WDkE-q)Ch}qo=#~e=|*&J0h@;9+<{o3tgYiijya|2D1#Poc!e#nE@N%EZS|e% z4;*8Tqx;}*BX0e{a)WgeLTA52t9fXP!8$m-yM^?W82G_0)1RysIuGyCXo%{sQ0oeYWG1WIYX8@ z6#O2MW8`^aoF`3hdU(RD1nUvq^AfeKY}J*HQ6{j+6Ka)q(t1eYmCl-id9M?(_(8?T z(~YAgKNF>cAKbul1S*?$jYkiJ&qtC68#!;aUb1K{kN+7R-nzkJ9d4vJx4O9p^h0+C8CP;Z3Ia2wJHvBKHDDskv~0ZAJRAG4%t+_Y zl|}ww5^Fb`B#iae*DrmWW!u;Jt-d5fMM=T2DZGX7?f)?L=J8OrfB*kg zDoIhPRMrStCzO4u$Q~hNFS{5-MwTHZvhQ2AWKh|{7|U3avTsqA8I0^=kZmx={Ek!C z=lXo^@BRJV_xGRcaXsqMoacEg@8f+O$NTks@tfuz8=j)voVFp)sBzoYtzFh5-Efwi z?=MJ55G3bab4gJJr9pUUm7Y?ce2VP*c+4vWSuBp@TAXM=s0k8O3Pv17F_x02YbI68Z@oO0 z+JNG7b*WN*xAs{GC#E>{En3u#*R@zOll=|Sqi)5x)at7iRp;6v9krDUwlhbF$ij0w zQpKu4qAm8MF0ZK$`6Q0XcS>)C=8_jTl+C%gj4?sUh{xsjzRSD9&P~q(bLpk{cHO>u zF8ad3p-*-vgz3@>=(?_z)rwj7{?0^Fo#I1YCX@r2XIG=`7gAE0gQ7X6s;mCbA9 z;f~&>rq61{=hyYI$=R`d8ya=u#UD4C8TD_6oe@=J^;W&DYq4rQe4IV}Kepj+3P2?6 zRLoQy(5xxIbtd2j_4C&EO=}HsSDGw+B#?fcIfCWr_tLdDod+k6?^xl=HWM2e*94m% zM8rE(?xvLV4bXs%QHp3Vo1&nG%EMV*ATT)~JARAdB+nO{7<#rX$#^x3-p`Y@t#@7Q zSUH$9c?-2ZzJJ=MKmSUECAO8S&jlyizq-qtfO!Acruyh=6+hcG-S=wRhghcVI9O@6 z-Tk+sRMm-xcHFIk*1Y)vZtS?$;;Htdr|TzEwiUk_so4>xPmQ32Te>%uKzkFae*3XYcaV6l__u{Np)EL}N1$p{<_|WvwkmPKb zYP`4dYx?N5TgFn_$_$=Stm7%elDCpVt)biEi^a^vEEpcH7?02^cYc}tyuY2P&KLWa z^1D)<)t_6A9Iy22`HsDo;p8}Ud^X6^BDN=0Iug}a>GC}GXRetYU?=UI)Fg9GmCDzF=lNz=mG0Ib}0 zgxMhX!CrFYirfO~HW~u<^6GpL2KFwoH!1CE1dCjhcX@3$;;il0zH_0mC7_xn_JTf( znvJq9#Oz(4(=$7A;>JPdMBvQQ_KhMx)frm98WKPIT>1{m*Jzf!x2!SP{WT&1luzGR zV6iYpxP-d*Z9HCGgoV#sPuwh`m)TB;%^VHWOx6Zo9Kh1U<(?<^lVO-`MEDX{d7fm# zxjSp>%j~%kej>W-*C9&?XTq7(Dgp$_j^lW(zL2z?o8`eu@l8&`z>l>)Q^>W&lvCQQ z>mp<1k+so6}v2ou?d}+z;?cZ zp|9^q`3vR10Yt$@$*!+>>%jB9FIf4~6U3y2U33U09UkWG zR@AF=>E?F#(u=*MSLVgstG2#Nv{PRd=3(kRo?ocd-TXA1q4ONIgA$~dJ8vv=I53I{ zL(4nfCAclqCkl2rN4Xzevm%j_?T6oU}O7 zqR-T-?~j_GPT7(=N;$tt+bav`XZiJ5c3&c0Z+DCiJaCL6?0K8wp6=XJKi8zDI+hR^ zdfoGeqFp8JYL4Zss?e%V2XsC*+t1OLw02@7YtHH9Iy!!REc)$VNZQHUz;k&yaXk33 zFo{0%N>0NA?Izqtgym>oh~RqadMw&@Y1_pyVLWhC{cd1}rKnN4&bjJbaFV-!*VS7* z+IL0c&HiGU@g^c6BUDQ7**i6vTLVG1dTmot+NW-Yy1iAhDeL%>(yGl{fZ}#Px+UqJ z2wqT`Pa`2V@@|>u=P6eHP-mrM_yZiC?au;RuLiMt-^r94)-v-ZOY7cI_T`GbOZt zFH>UJ%1s3~g;x?v-DLsocPHk!rQhk;T$#Q4{=jKl zRYN9YwNZUb^odP%+1p&)x7tEpUB{i_ymIo2@*Eexlo8qzqdIfiT+LWr)wtT?)UGlq z_DJ>1^tZyN+0>1RnoAD!W-Q7lnc9N4X8g7hS7J`}C3#w?M!n+_zBrrw11w}41-N$c z9!369Y*KQw%)p$)@ams~oHpwTGvqb|*qz>AO(h`D#4sp$Nal zuBSAjm#Ih=anI9xt-{`RZrnXj&Tq57EU8cW!>ihDm2<-l`Dw$Xu&=@><4M0&mto0_ zo|-fnnc9+&2#&74W0fgLswZ%0nk3IhqJT-vehupCTyD}EwG1@R&hI_)vLrk(|1Sq? za9Dc7G@CJ7I5y-Zb*N4D~AMd9=n?yT3Axl^t?x|aw7GGWMqPp9v9b4Y@ zYsj(8Zuo2UcUL&IoQ=G>H*#Vb-Nw%}U)$lTxTc^&sI6V-7&uE<8{8x09X919$c^@G zC$+E;Cd=l*FA>z?C0iT;>ZapnrgTcma*BuEy}G-5fD!^O;9mOvwPu*9o!zhxv55XM z!Z8BoPQ=eYB`0W|nHQY}4wcHUm|Ac6A~ds7|1NB71{Li6bnUNJl$=iqx1C61I2rR& zIq1~wY4eB9P1M-9qvE`W5iAx5Rg_Qhy^OzMbJ~jO=QP{5rppQOs`rEsMldPWtx9`_ z6)N9|n+1hsR{(J1`oy`|*74_l{F2WjTL+*+F4XO?75UFeYt|j8*&g&@)-BYT^eGHb=`e>!$ib&On>1q z=F4R?FTw!#9q41YfS@Mvj$&o964 z_R-RD7`Vs)i^e;U*g@&>Xs+e5p2`Tjd~ZJr{}Ge%WZC4F&l6!00E>>B?4DIFENdoO z&JKQh30xC$O=qq2>Tt5|Lv6Du@k`%zu}Ln}@Fdk67paeHvWz7jT51`s>6Mbw-Hn8j z4M6EK{i*7J3^q`?!9VZ(9Xl3L@i zOFmAj<+U^|pHCkJrSm#_Q9; zwyhPzK0gf>1?p6gKiZ-+&G|ki;2y7Dr1jL18PkWmSv}jCPf+&Khm}8gsouP(z#Tx4d#8#a z$zPIi{gougAD=Pu*E~FR%c%q2@d=U8=xdq$;)s%QD}n5>TDtw1b&7Fpy43T_cAgfd zd_C!ypQ?uZ#tE#no(G&KDRp<0{1d*B z?jtV=x3u}kG%0h@oOs|Def?&Um3DevIE`h>DLM@?KPpj& z4_roI;JPZ9QZjnB1G}_E&sreNLBQ7zMfGfT4+w6@G#5n)U*Lz6H%0gx3gPxP4isFq zO`nk`a3o+C+gTIP%UzY_J(g7AteHF!^30TNNr6iG+~6_o(^yCwK6faIXT{GeI@FBV zpMw%I?$u&gA|_q2rb=DEQNLCAp?2l7zIF{h*Dcm4S4sI{s%S<1m)!yNJ-G%8;dz7$ z@iGt*Ts9{7bgTO&w~?rO%MAP$i`PSesnKG*r)R$Q2f^ufyF|t@L07IPp{p5+*wXKX zv7^%6dU0;3=+M>M=cokg=#1O)b^~ii#w>O05`BZ~qeC!q7BE*@yD-0WZD9%Fpm|{- zm9k379jv&Mfu61bXoz3S)Xx3`o3zY8KrL%FXy#>BFh(hO8*~xFX1S!0elZ3k*9A_n z8I$EoC!#4=j!j-@ZjEResW*Dg(#)y#*7$SC-UP&$(YgKs?WvQ0h_NPhw|M@30O1MBbN@oO2o7}kl(rJ;mwW_h_#P3UDXBZC=21HCY3!zuQ4iu zdnlJUKZ)eA97{$$q>PApn9!tdn^VHK9;<6Iy$nNL)|2o7UQ_`0ua#!jBb2?;2;3%q zj#lPc5Yu@TxvWF=cr+06j9s=n2|jklL9yE20!D3i18>YRA5&vXTfYS&JJbUGN?M=r zDWaAZ=TD$K!*}&+zO$5hC9C3I?;;RMpgz6b!#FcZvP?lx+QTMx*V`hYJNC9M$u*-( z>=;nxR98)Pklwb@*G9igEfm;;-gaI^}@XRP*E*WG~`AOH#`_II76hA~4wfz9W6IZz0}-dkozB=9}W& zcvEtPH4nwqYkaND_3DXp2Oc-Eibq1zSseCwS&8r@$s4b^q#0W_Vz!#*`*P8}0o%Y(#NrwW1lZE{lAnkG;sc2SLu z3~gTd$rV07Bd}h!NNBoP&F&a}GE*V99{niR(#I3VU+0^UaEik~jJz(?HO}hm7r;bk z_TEZDx+))QY#2x>FBWd`exM`C6sd`u4Do5x%{Nmf?|It2VU_PyLbyc6{kFW9Je%d) z-fGuZKJvnA)PmC!BzWZjeP;)#RgZp78#cN}h8)}1%MtpLuJ6q`S@_K7eqmm#&NWCt zgOe!q&s|YV+fX@jj#%Rbt+(R+tV!PVNE8Uz&!l-Yc~{Gva6+(nL&t?cL?M_-=c=Ni znN+Zqu^xv(A7(F^9dsUWuuVN=5%smB_G$Img-HfBneV%gS`P`P;Gz>TKwu>bj!J9l zMuf#$w3AG$ZVhXA0V9qiSwPK2G*Q{6A`}h{^@>-{! z)l^BM*oiM~urfKz5FIBiezCppWmm+Ia{8)!v3EI32T7czjhAoKwE>YZDl60WicwHK zUd4O5g-1BTX8Q1h@C;&}zrK;balbakH7r41R<;8Ta#}Bv-fCm7yULQwcJ7NO#Cx5? z647l=@#O@|fT)6{uCdjq83RJQh$UvuGA|YF7K;tc`^;|FnSZB!hPh`s!2f|F>`t?Gd9;p|U z{qFko6rjB4K5tu#bdYZ?uHLCt3Zg^UUCg@b3g(f_)%>Zi1Fa@Fq16Er-H%SlRz78% z9VkKRjskRY;DM8H8vsYLyXDz*LNX<->yG58jf<2}_znEStj25i-wG+yt+k#pTPh-% zaep_`gL!ob0S>Aya!{9BfaOw+i-8ljz0*Ba+&~;iXl%ac8=(9SN26~mumc5z4@qb` zVfiNCsB9nVF*{^4GL^9iYX!kU?DM%xH9ohU*80Q-f{RRL^S>9wYYND0*RD?lFG zo(>q&&l|<(pv)JZYu8|N^}bdDt?mdf5r~Turh3-mhL=;f>QzPqdb6mV(hzvMqTgidP2lZI`#NP~Bp?XHrLPQpFpk66u99NnGs9(WF~* zFWchI%ss9xWqadGS(3}NhN~=7bb7bv#@q7I0Ihq=;G|(XI-b%cQ%AG&Td#2eWn#c* zUX(uL8ntLVRgC3GoP+DyT{yh#Qd({~r8Mqgfdh|!VU79u7w-xAzfcal6 z_lI^yk2H<-fh*<8&m$#lt6y+GmbdAzIhwi&H{W(v=Gk^}moGm=qshr>q^^n2!02zTNh)Z2!8%%=*T~#6(b&WmfD%)G(Miu- z|GTP@S5f7hz`fRy*{7E-e47w}L&DB|f*Yz5UC}Es;-<+WghEGTyJ_uRuFHk{?K(0I}ww1V+od@PBgZ9_N4a7@mSxPFJI@c2*!D4tszH> zMO02_c`DeOj{qC3^i@)96WOPgtTdJg4ztpxSzAe{HEEE;&0hf$pDDLf%1VCMUCv;75hZpK(fP3Y+N-m>iKIQ}06@<=M;Lv!@-M2;IkD z962E-3HX8gUO4+{W{=krF(Z^6gP9OMA!p>i$uB-Z!d(yfk5VVsS*~eU(OE* zBc(o%WGd(Z?aDJWkV&}a0;`i5g=BeOL?UK3`)}bL3@k^_0IsAUND+Ws!2RpiZGbti zy37L?Zt$g&QK~VSGR^d#M-JuPY0+$vgRy&%$J{TO>%o4l-cV?3>lcQRFO-q$ZrV;a zd*lxkY3)EKD*_RLL5y{{i7PYo{r2{D7c$Cw006rTa55koJf~aQ|4uNW)rFzY)VQ@P zA432E7meBM;(Z* zbiwBprkEbsC z4xl5!r9y^r;JH8^>|c?fH@Faz|P@|1D5S_FilsTqDFu1QueM*`!wqKwJu7O2Y8a*EtQB9xZ_|Vgz!Y!?*;H7X^E?`Xwdj zjsawYdkdSF8X#VP*S|^MhuB%4*8{g<9$D7v(GZv!+xkB@lg0+LrgOE*Ia+da2<-g4%l4$m|jX-Y~X~V@Vq0)`K98X21<9x8c2Rj>=m!1?Uw) z&;1#c>)9M8Wj3M4ED8SH06Co30~P`>fASf?ryQy8lK^yn2nC#oVfcgZ{&XHlaf~zeyr~d6ye!wdK2$ z;(sg3dS0CUE)0`U!%K5py=3<7Ww;M>8L4`Fnu#fg^y+WYmoVhRW4n{q!tSf3ur*Et z=ogl6pec_`!2@xRK^btGkg^yBsFu7`@;<ila?XUKSJ1*}1yIDoO;_0)jO@^=_QE{;e5-fkWNQ2ZrrK(O?m&w73;D$Cjc0OkNl z3fyrT{>PyYL{r#DFlvtH7D+Bn9^}*hA`9MREgEaSn=uh=nvoX?+$t_>;DLrp4S@4N z5B|YBfGB8GlDThZ0c67Wk1(=>UCVEL(fRl}6fJ zK#;W?FZlNpF@$m3_t14LZ0P4x=>{BS_My-G%%p zHd(vZ9KPqdftZQSFIN~g0$fV=$C?``nEp3){I6M-n-E6`G-Azyh1{U)0I~<7@c24YLO2OFfpqJbR zAqI4WTO;KK8H@)Agb&cWmFT)noRb00PEU$3!gx{ACH)HAyF*x#3-lXAzN$KT)X^7k z+|E1Vb5wz$+TZht@#!gJ06jO>ds+V?|HWL6m{LO{1mm3>2P#Eu2H()XG`Achs!*s< zg&@3z=q`w-!UA)ZjEsuvj`0ul*My9!wp9ko4bu}b;A-_=Xxy>CCyemtX`nt%@?EM{ zt?^nerbJFKJLm)T91RK%$pUj`l;_3meva4$`@i_pff{&#(S7}RgN9L#6KJXR|K2cE z1%MCz7qB^vTa`il9p{82Xd(C2eSe0ae_0v4Sc@!l?R^vINL$EU>F|w9p85SgK^1EE z17M2K^d%)n%Y#+L9q^|$!DX^|7mmD7bZ4mVOJbe{C0PILP=HI%K6Log_RQE_0GC$Y z=VA9(R^=MS?L*A}{iB!w>K-I%YgGjBHc(j-CS3{5uO=g08N=E>M*L4Wg1!QZ#1K&S z*;^Q2hM|x(GGgGY$O80Y}-VD-YV@c*4!VsgYj2pLZn4xlV&oQ04RM z`}@BI=KpdDhVaXWr3gSBD;j;Vk0Ittfniaf0?%3K4S`vVh7>pm@q5rI<2-cLdpgmm z^L{Q+0H0=N26%Hi8NXa2C|9@yIK{gEu|NCw*N;IQ*+OG+U0ks_)-cyv01Wd#W?o>= zvGpm%b8F@2DiGtRm1+PpD)BC}q<>AuF~DOLfd64`gAe1_0YX^*Gl>^v z#tw~AsNreBio+OT48&g^5Rmdh!Bj`Gj+M1Flz@bkpzDD7lg;W>+yDLZ7Y0j`fuqlB z#3y>DSGAs+1!nso^Z)qWmWT`7fOsuweYUw6z&D|!imkqH6M`QC(K>)t?sJmFH|20bVC6R9d9ro#5xJt!~yI_hz)_8x9)z9?5zm^@1FaWzXCMVEBAAbtM zsoOmP?ZEKjUl9)c@h9-Us6)!EaSSO^0l&X8Gpn1*2p>5sap%p>(H*eSxj-}P4Z!kx zpc>|QGAVoCdI0J|nm>z%bw8jAY9Bi*VACuBt6g~xkiGU{f9B(OFv4M08?3X5sp9s4 zRtd-?18SyANQpakY}OEDgsvD0M-YwqGV_t6Pm}?PLp-{PdnTBH?e&&k2vB&NLb%#A zN!O$w1x9Jt)-MFc=&YLpFv$_1pxXzm|Hqs3A%GxPukgO8xZ(D402MmeKIZTOKoKC? zp)sJapHiG`j*N$pxFEfGh+6WdTYO@KL)7u+sY} zGBh3kf@|c$pFZ3721o!$RuKm5zKf#6z?d)Ze8E0}xb|w4%21z3fk6Q;q~{0Rq;^x+ zI$H%Sr~!qr?m)%L{N5akx%ckw%_9t~4$8WA*kyoh{RhblTJJ14A4vk>zE+S7JMeZ2 zMS(h_!OIK+CoBEBsQ?Ui{$u0S|Nd@JsA!CONaUVnlwXfYH3OX@b`i%}2ADx>0!#4D zawr){!5rJol0HYRhoI;BkuKgZAuP{7D9^T+3mM(hKs2Jks(kq{?CJSy4l%~bZNeq% z!TYft#10D7OZ4C$x$RH+`?f%~2F0Yc*6jvheFDZn?Fm4Z|G6ao$mgfF!BesbbuSM%hDb*wR6l5ECIX0(f=^R_XjEef6sa;?F=SJ=Y5DW zln6A^cK-}cQSsS)0PjdXQz?}Csirb@Lk?m@(ET$dioJK9L#P0k1`1;Ez*lHZp^!<* z`8y+yQcf3cuUPwXHlE z*A4dtj|OQ4@U%+v|E~dBtL*Wl3U}T4f?hAq$Kp+ONupv9!*+bON0FKU4f_g!KK(Xw z449|DfN7tYj(iO~?E>hVLrkER_kBnBF)t>Fd9O?fH@~j@Y`TLzW<&kIJZ*n7#eS9d zq4c4H^w8t3B7UoKoOQmcG9v|P3~TA90DvVKoZC{~R-e5FybSsY3x94b8)Z&G96AsL zQAc);TFkA01)i6IcMrrRje5WBDUg*Ot@@Zl07ObrpyXDBW)PHR0>4WZ1@J_6Q-FaG zH+(pH*J3x}LUZKbW;yzDniY?JoSrpW!8w(r=FW?ehfKhb`NyWYqF3i+=`)n|jmHZ` zmF;hCzR8JWU8uwjO$H)FjO+RzXS^E8cY15NAsyqzk%y|JTEte_oz2F3)>pZyhfznDh zDCuMPlK`i8fL(m5YG)BTV#C_)hq*U^{CIlF^ zK0EeB(eu|m;8IFYuK$xq1#X^aVzSP(s0EOAh>_FWU^BxUV+=j>(BKE?nPwU|-Ye}q zQuA)j7jF1nbxyfgq<4F!J5#tN!+#Rh539;0Nz$g*|9aIgnyKYRwfN1Luagm%<%c3} z!tqnz+v1Do6H(HQb%XQqjM7N=+BMh8YhkAs-CREsx6YFeWi9u&FVA-KsC3+{EHw) zgMm^>TyFZ=x{ME(#Y8&dBYEiYaPAfLlWA^xc``4;9%lLTnoex0qx+*q`nJ;?LM$^V zuChy$q8t1`E0}yyYD!VeltN|JUnlvh|iFy^(6QY zdr!S{x_Fz;CtYEyQYj<@;dd9os$jKaKltSR<;^Mn?cD+x&s$Z-&?h@-r>s@dD8xPm zRdv^N3ESp#v<#2P$7rn5tGkl>^HRYScQLo^3@Vvsw)baQnoOL_X|b8j=3ZSGL+0rq zG;zVYnm!&=a`L5D4>$3T$<)Uu$ zxIBdh^LaJ=3+)`~r6+#hRHHk}lvHg80=dOJsSi`DmSg;;S90H8ZZy)C-L_ei@D3O~ zlKONm#p(64lU{dKh+~4*R+A$QM!U^M+5w_ZC@L3L`%NxjHkq5ACO>JPzOam*6fGs& zKG90wt~3R$aJc!)Qv5gUq|M?OO)(+=@J>3TM!jkY^HxkB@SumZzUPN zW69mr(LP&=^ZLCU;)V#aLdpY?Yi-i5Qi;Q~(XD;p%a%)-+;@Z8@%z*ADg%c!7nWf$ zxq_Cl<66G{Jaf4vZaFM|;M?OqL*2NW5i0+2b{wsUW z*9Z@Lve$agt+S${kbQRf{ZtuZRabYN2tiO|MTtX;B~K?$9-FH!h?~6<_zW|$D#>OV z#1vr+TW1SF5sN9s^PlN^hMr>vCk-P2Xvn!`ku`ToW(z{WS{qLW+$$W~!ZmK7|4LTnyOOjgjq-g8j zdKsmys3kj70ygTWkM#!6$5*fkW@~?OmBHM-#j#%!+@^8Gx}`OHfN#>!O-LBg-1bbK zZ2PS-$9*fv{*HyDYEIA*upMqt;(NTMT&p$gb=QP)PB7Mo$> z1)$e+pHQyd^;t|gKI(B7M~sMP05A3VumAlA>vU8`5MvdV!AsJdqAk%KrA$lRDs(Q; z`!xxLpA7h1tBzLf^bZ|6Rlp?3Beb7@B5r&@mYr^Y%4@LSiqJ!DSVH%~o{Q(DS$C#d z*Mtmh*>#F`exb=j^{XO!fD{ z-~AdAb(h*`Q^RvGvL#w8tZLKw>j}b%h~mD1W5X!U*se|uQ?Z%^K5emrW~add`WKz#yPiWvqj^IRm-_uyoM>f1N<+(%--+kdK-G-ssnIkObm&-tYIn{ zl5>bJy##2i{5~XNK1m>C58A*LBzma$O4qE*AyrjU6^ zAlpEU%jlzH6G7t-CfwGsQV%3XI0ppTxP;fe?Sx1t#ReR zx#IN2UVq0HsJ$ci+Z*)y(>GusGCajSma3R;#*v$ z_rT`L`q;DUhz=z>?I@WqGP7Ra_FiOKet0V%d-aJ=W~<0;x;qXHS|8+M&6ga{@SN!d zeMjrilBqCE@x4uV7V1SB=H|8~5C^Z^0E-Va9;!WrI!GJ|FBHZ=1aQl)(&KU-EH)Qp zk#N3Rf5tdmaaz{zL*$2zu>t=m-;KT9vJUQNC-Cx!#gySzk?2nhaBvFD)x^1$8tfKw z?l%J&IFZ`YMyq`t}B(?0>fBQDr&TpugQW=2-rj+cufk;jx$7yx-EvN32y;Uoi_FsgxMaz>887|MKkH?-jKM(7I8x>R$ZubydT6d(kqUe}e)L%Dl{GDK*`Cm5W$SCa&EA=AhiH$)UPujbad3DO zXxDeyReO&kGhnpheRrODB5t;F!wpRqSX?WzB;=d4vA-^|$OVL2B6tdFhs43-j-3qWf!|&XVwKKK z5prY!aM-uHG2jKc9R>9`FQdZRVqdI38T{Ciwmf{wKEqa+mXUN2uGNha-pIE29_^!N z3ERF5XGth!!I;e2TVnc)>zlQSZjq5@rIO5eoQZ)CC!mnBwQ6yqA~ zO9E}I*q>Bd8Qq7N60rO2vR>xCxKkwIbEh6>{-_p>TsrH7+cuFU{t}7ghL2viRu6J1 zsdXVP*Aa?$i8+QCzY4211upPq&!jjV1+Dm|TKT`)sN=D3h(PKfXz$jFZmv4?L%--MI{fhtWU(4+&SokdDZ8-`bPTBOB}F@hnfN-o(R$KGEe0Tj5IIG!UU+WQo30NK{Z$G|QKM*W(e~C1mZaZ!85`LTPUySxtR4qnGlEVc@2n)v; z`2}$V4%h39Vg;+S=jFU&Bi(Fe<;i6keZMb~lX>qfzRI&V$d%^JJQb-nn35 zi}`5r8uy$?0n1q``|59ZmQp{@o1DRK#0e_i{skq_f&|It|4bkkB_;(JdCxW_6O zKx@*dn-VaOB-sU=$ff6DZOGB^ot&ctqxBqoK;J;rwFBG=96>Ngb?tcG^xmgx_=N*1gM z)XhqwV%oLRED5+>k!#i0_9UYn{f5nVnnfg5qW_Lj$*NVSp9rd z-PG zyu5IFC3axB$$05}LG2+_Ju<)Z<2jlmcUpr`+Ub_}tWDYo5$#>&M+(VMbJ?*>8V8|a!nU=$)MKCnYCbgJ?#m~OsV6)=G z`qwN5hQP0Rnv$i(!y35uV^2Ql^?%1-@JA{)`wUSLAP59PFh5e{p?e}QB0NGW@zvo5jpRb!!}qGnIZ3{_ArTC+<4F#4aM2ne9MnY0XzEG@g0_; zChq9r>pH;AdggZJn2T!W@Q6qaQG})D!R?!yOLBbFi;B)a#jw9B1Kehv?UF~lqY3$w z);hl5@P(#cvP;hg5MN#WXPb=@ORSU7x0R!c#vaiqymn$H_*WuH0o1gL>S>9EwMJD7 zKRs1kuyWmQqv~Q|;ETodvTwyvJ*F*+a?{-#RTkbWGv;xNe;Y@KCStJuR54th;MZfm zFf&l=W$6^q14FW5>sEtj3vVhd^5thfvfo^+>wzR?rd53zu6jQ&V*OXP6okts=le?~ z&jd-r1#n&|EKc?$7H2-oZ;c}4Y1hJd7N>|FpHvFG!knA*vxKy;mx6j)3yY36SogGUxT*{T+!#FbKy~PN- z&+hMmja)i^6#=owUseqeqbPpFSnK0~$rE3uO&YZ4Q#P&-*sng3%^uA~*!3t3Om2kw z57-d5+XszFgM`w488f&&<9RFOSeliP^e3MWLhs!+taVJ&(p<|$dK&aj2D@aLqu>P| zwNI3XHn$(tR8;>yC;P#h-s!q?5OdX0IheB-=J8hCF()55qilte>caZ>H3^p{%x9So zi7bol1O+=j%i+eJPq}78w1KU!DdPQj#f?v4Ua8qF>&=!fV9vBJ z7f#wT?hP;~tcM_`l^QCuIbdG1_+L9V;g%{t`z-fxQSuEH=DX#-lrpC89$t)8km`e( z5rv(zTG&RgIW82lh*kgS{Ru+OFc@yUEX%*NILr`KCB%p~^Zky-rb1_9+ideCzx7jZ z*FR21ZA|!WO}G;naI!W(-LWfq_{DdIv%?qUi}Ci3^!1U&l=P;_9fkFI$BY_Wbxt!; z0Pck;A_r9x&l^!af}>YExIQvoWvZdBOYhM?@9^JXQDi?G0%G>X6^s9pU;X#_tsT27 zyOZuw^y6bJSgSQ)D?>}uvmZ_jY^O34Ra;o$%!J<_EZ2NvlS5w_?sa7nUtQb$xl!sQ z={p=6;WSW@mYm!w6ChWaQK1g=9hXFmGcuMhmDJgCkDQPpzuftFZsFtC>6gQ0vI`f2 z915~+5$n~n+ZXb|1h$@yzTm1VA=}hpw)xPo$kGEb>p#VKw4S?10soq`WKU5{pX3q5 zztiujB$OLAsp56*E8U4EbIszV-uZHej5v~v-<;sH80j`S!9LUC_gn1Lft_bQ8Io4$ zjPZ5`^TnOmn!DYNdW=oUhC`vm;uUc--v-IAq~UrE{3OiTiFxcP){;@O(?Laxlqogb z%_83&g?$rw;&pkeieZfpmNK_yZE9&VTjM0}HnWS{d-gjGvE?*cXv)~&d|OEk-!uN} zxGsO_rG>^5qNi|RbLG`cS2LH<( zyS=*HYFJdes~mMs^WJ5Ll2DEzezszaxG$-^zTa+}hoN@FOeyt*gw^!x&rM%uM9LaH z&JBMu=D=@T7gySi`kIn0<;LwX?sA(8lJF?=r)zARIo#3fr8<+W(}9DCOV^4=3gr6l*x zJ)34AR2kaSqxM$x9Mv%Drf(+fc5BZ+tuWu{UH0=0UpRN;lLci<9I;a|x;Ub1O`*Xc zPK7NEaT^tv=Vf3``in=4`t@N0VJ*m5%+U!<<>-90yw9C%3v{e;?@^s3nHBLEY2Jfl z8Kf4z^zp$Fwrj4x3B}jR)ioc5RKy2^k9eOnK9{Yt5RYM)TKsfjt=3IpF~yn~hg~l9 z*S8<~i*#}F!BF2Z7(&``pkhm4KfC>3gWB!Y=nAWHlyKa~LHzPCT{61z9EJAm-id|} zd7PI82k^EeMF&1hL?YYM)@bL3493)npQgKVWk0GB+cb>SJ2);TKK}=^(HHGiC0u`g zO}C6*_xNHOgPExqNmDRl>wbR-xmUYDPgRY6;)q;cf%a`Pd zb4m7~*Yw*ch-Rc zSDX-anWQu$jL{5u+(4Ooms3O}dXYntUWVM+rVaBXKFmifP7SgNXFB4&+O?cKn&60` z$1SFWwthG&JhYzu_QnU-JYu!2^G1Tnm1IJIsE_cK4kijBj&lg)mR z;=s~z+HCe$w3>XaqdrI`vxY8(aJaOX4-ItB`Dau#ED!o82|JApEHS&a9P8T*LL}o8 zH`GmAhCI_KG+j_`7BD_+fB9oUN9$PwkWNcBWWp$4PM@$yP#~9GCysp8ZaAWAVj!sT z0u85@lNYNn$%{E?PtSMvmj|EAucufSX2{wh#o<8RwlfXovTWflRXxV4qYn;VdRNE`IeXlRHFz(^si0e_&@PjkqpiPV6K5 zn+w8I&q_ZXvvLX`P!KCR{?4O>lzaEFi2M=dkw^>dH<;H|zxfX)vCJsp4sKR&LqXWf zmO}iZEy186U_`tALrw`TBd^l<*$zGSg9E-3XF%41L<~H;6xBHJmT+S*DXj=3b*2p$ z)|{^qyW;$pD&&(f*9VA+m4(yeKS9zjB4L$Upt$j-@P#e!(@5hS`}JRNc{9B6+rSOu z66E~#L8H+kesa-$eP1m*UdGquU^;Cu$o`JoBwvQt`FVQIf!!uzWE;M?`*;w~Sv=kC zbfR;2!wH9=qyuIhb0vpo{)w*?O7%w=a!0y@`;6TK*LA1M-0^|amz#@9emgOdjr&3< zUuqgQF)V@w;w5cHbkHt%>I~TaAZ|sa>uDvSBSB@vw<}-~O@O-Rx^&UBH1T$Ki5-diG*KCxHE$ft*8oZ>9 zjWi*;)PUtlnp<`FJzGhs=CW^Zv+K^6EXLC2oyv?X@3ttJg2W2yHd5ljj30GpuHsey z#sD?P8}Xwl+N6?hc`G%^V)d}2tW6Lba&LQJA*1bOh+a`a>CbSZ9b3A3YTfevG!~cA z103DI@lw3oG}h~%O_$5U!N1$^rYd3Q;~Q|1_&sMy=k54NTlK^2c`Ay3AOJ7m4 zv>Vd7N_^N~*#vAo|4~hFHh8-%ZqPwfb}+9=$Muo9C9;X6bfpDXXKv{~8s+Nsp1Bzv z0!L?)Q2hSq*3^lx@_G&}lIKmszNk#s2Fv@$Ci*%kVrMPy<7!BPvtWG+u3(k=u3fz< zpr~K%JZ%Cide8ltrOm%@+6ZiatDA;^JM4LbDV3T;dr^kYEd$~b%OETXqpe44Jzs7^i{kPMrD3iMG`~Bxq@jJn3#Annk zFYIn(NYV253;u0~Y;2O5d565YRY2BOQjQ?)wbPGss?J}p(wlE&Hzol^PUtb zzcvAI=+?0bA2bpUIbL22yR*U=&4O`wx#2fyE}P@4BRi+6h@KhTC^Eu{SyxTJ$=)1w zr6voKxfj+u!(ZMXC%<{;e6F~22_-C^8Y0Y3vf2iWIGZl>2GY|mb4f3@YDDLH{jb;- zKWwVGyxV5W(#YXzYN`kFC3I$SgB8ri|7j1~t-U>n{4BdUIjMsNS<>*2Xn9-v^*LJt zAs~S46VJPuV;!~8ar(H8`8}gOS1XS?Xj=t(t$?!LIkR_3mpU(1N}2XQF|yY`Yf_Ni zM0~dF*8!dGfkUNQL-fIpzn=K4O3UUp{n(@|`?n}oExu7=_${{dT2aor>0Baz#QF#)%;#IV(M{J_7>Dn-n41kbCHz zkdXbesReP{V9-$`JN_%Np5A+B!H$o9U?Wko2-*diFn5SjvA&VUs6t2IF8vlTb)uCu^DMR-HY>Ec`bQW-La?Zj>7%IelEoP}aSssZ+_pOn<0aM8@-kB=gFfH*VQ$DXAlwCOW>mZH2 z$O@qC|2$yQS;Hpfo1K`b?{8GCZrN+8Gy^MTch!1A(YCCg8?3us)t9+2w*BVg$Sru* znPL|4N2j{6-y18SbHjliA|C9kmxe?Fj^T$1qJE)x(&#M$^DmO!!z8~-sH0CixDQp7|7vSFuAd}-bfNGZ`!2dm(K)O_xo)ul3p*G^d%K7* z(Xq3-B*n@|H#^AbiI=h%*n7D)aEAS=0daFWP+yh=>#PzI;D|E9);X9CY1jRK?7eqX zlik)YY6k@bM4BK~MWhKRMM{*aprWEe2t}$OB(#?fL8K~Olp;+8E6orHy+{kaiIjxi zf`rfsC6u$myZ8I;y}x~Qk8#I6<9v7AKXeEt>silQbIm!|ob&gaJ_<6FWkC}O;3+F) z?(QRQZHXY^Mm(6h;`#^n;0A7`%AT{bi}0O})`Wo=(*h+x`TVO)n&F4NCY+=%*|yW{ zdP8dmiQQJOhl?2r+b|P0o~1fR#iY#Jr5lO8{l;6KB$=73zLrTbq|%k`woS>*e%|lV zF0G0MBs`WT+6Xk0PZWlyVHj3!z$@A7X;-rfktWxu!OtjNAdG!S<(;%7p&6iRxWm;%sGJAT$J5tI9$T)!$|>Gq<{Z`=&pFDP8b4o#0;$Ke*T3jCV4h0kf@>O(WKtxtjn{*@8QYY;I$ zP$G#VlT-UmHMwmFk;Oxsw>BCeBh8SIuwaAMB>U5;?mfp9rZ^O`RfUL3HcLaTr&v=X zOOGpl6;s>_4=!4CC~W3+@4H>LrA2krR@?sLM=K$cj2EkFTyZ~=XFJRz3hZTU%y#G2 zgT_Zvxe-G7l`XiT4QB~QkE?o%2dir~@bmb$ z@^s+RxpF5nD6k(b2;$GdpIp3G8jW}+dL{$)vUh{g#~8!>!?x>Es7amQMZ84K_!RY9 zAckU!9Zixrh&wzz-0a4oZ7*c{)1p;IMUfe|5rcJ?z#sDr)Qrjg-)&h4=Omi|D^-$y zmuM2OdIbx)zh~I~IXOlR-tb??gZP}lo=8&T@zB~ZC%R~Ai2iU$-Cv%n1`c_~hU+Xf zhjkT)aO!ot-TILC40yN}7-KJ~miKSxGf-((-kQ_@L}A9AE83ST z*VF|9YbB{$^!X*g+gB! z-wM&6CY4ibnBFyRkvM1g33e^=Vh3k)(=mLLybn_&wBfZocv2| zVXd&)nweG^__hYcS~r}lxqmXC;WA?2_S(3+|E=Moek(-Vd32=c`gkg7M^C74!3$~X z4B5DZ4r8K(H4B|99WZ<2M8_;S!S-Dij%l>*#cOiChJ{>r9FJXWE*}c!lDVvf=!{yJ zeJdZ~F^4`_nD+$liZ{^gcwPi#^K804F3<45LX;E|nK#aYZ*yl7m@ir^`BwMkR-&yP zR)`ZH81giG4(;X~9@?0AOj15Gj7jniTDtHP|J~NU3JLEqr}n^5w*C%&f6|*w1)Rxl zWhI-F9{;KM)8d8tm3{+eXfQi5{6ODXHtEaNs__wXK9{&!V%*-RX(Y3I!X+lQE9Hk80KJ%Ty7>m`dV9wzvVO89PsZqMjd z=IP#+ue+^4x|C^9+T%LCEpTf8oO6SiOevQLP>&Cb3WdNUT`Gml>M6qPk&TiwP2&7o z;Xuqe_cR=g|DqL~UEm0iwhNbH^UwBB!3-FHR_yDoAYWEkAMq!p1@m0+HvO1%|I)>4 zI4ASh1x6tpdTBu3bB0^3)zu$=H;(APn|+TG#KI-5N1Viz_fy>M|3Qf>aGGcb}XD9-Y9>= zfwD?`JiI;UmVE15h6f-5+XoLGG-<2*@5Ls$pD=}^xpRd+lsItw%y)i$fBYQwBLD!@B{`X6bE$u7~CBmh6?Ps4}-HTc$;Z0n2E z(;p=W48cjTxE>&r^oo8@t%F-QvjP~E^%P)Y2D!Sz)EfY2&$~PBYoUdgZj-A?aUVNs zB17a*nHBr(>M{t!6XX?qHvgzZz+5Dsr^Homon6*fnPvF>Oh5w%>C7)*>_F2|<0c-Tl}=Zj z&BuPK4DPN`cMHcK%xxx=qzNUw5aE*67fvR-4T+$zIth^ePO zGv})Rwh%e|0{oAD}u8`{eVe0taMW2+W{yZ}0)?3Q&CjY95{H9sKfe?fH(>vog`}w--$T zqx_40+VhcqE0y8Ga4MeF02^@{Ub#(11N?;vDBn2spL{W457*rjItLv2{-;({f;)rV!Y3Z8ss+!b=vmk zY&T718qzqCfjPyWyBR;??7n&7$9lEPId**j@M=v~Sd*5$;iWBS%=2E1G-lA2qJ(FY zFrLkId05wUPB z(b7I?F+xj%Ibe<`a3$eL&Y}z|7b8KRCVtQ?eextIiH@(lyLF-M5*%>UU2tct9_o25 zr$KLSf^5H_8^;8K@#?b|BY-wGrkW7Jzt4~A>-=hoC({(LjO9pE~>+>`Di)&u6?>S0DT#42FdQW*hGzR(De_aTd)Dz%yl zYbvDp?s!l}{2A>4!l1a_*9NkC0Xg&eBMv}S4Pb;4S%%N3+yfF}$xyUazbd2B#PW+( zVlSmPugN!|BS%pS&j{J-sMITZyj4~qblbr{G{B-~fqi438J+)LymEJYo&*M38V^}i z9~`Nm%q^RIDO>G<8Dp$SChVKR$g@GrYt^$)#_#}by@iSugOL*)t2RPRfwpqxz=FRe zn7)JE`UknKd7KLz20ukD*iuqsjF1<~kMDwbd3vEbq>?r63c2Ll#m7E$!oGaU0S4=8W@5FggnEAsl;y;b(DZB-b0S6^2h^|2kItY z9HmsH*ohz++qE{sbhH zST*kw8S?;JLO5ugnX6RSt=Icif^;Dk6S6V3+^zTVYR@9kR@KKve5hV$^VkjfC*9{^ z0A>K0nv2jL3bLCWDlaujjO+cReV#W0R+v)u&^rkv6UehrE;{90qt&~^8+j3^(#^x) zZ~zi*9t7#a$bqhuSAMiV6=DlC_a5?CPu5>qYL>tQe$07aM*O45esN3GJL4cQ4_&$KGmP7k^b zQz5afbZisiT}Opg4n)){tPMEB0V4q!KfKcuYNB@6nwMnxVLadF`;MId?K>cVN_)odTR4SQ5$!hPyFQ&Yt>_q2j!M! zE&Ff#vfC4ix)Zs$HicGrx4#wtWNE|7TT5(9fvb@3(3s+D^Am!_v4DJGaWY=%mXZwx zutk+uiB#~vGrE2|N31uVxB^zo!-g#)e0{Yck)c6gTZ zO8KLrgYn#8UPQY~dZi7-2g~uuyZf|nw?DBK<Z=iR}VINN&_OS0d65;P| z!R_1{T?yC`7JL0=IvLB{rfDMdymv~zCUd{Ubj`2O5z>b}ve(O5-Xd#{tUgr*<4WJWUMybsDX!p7B|9iKbq?1NSOjt}PI*LH_>gJuJ_&h0afBMt zUQ+$I@lvWvX?72{n@>e1t z`C2=1bkyH&)Ktfmi~Em`YsRzuA?~>X5T;8lc9xX2iZvy`Yo^R!`Howgl`ob_af{+} zp0Oof?Of2-o9%{j-^EaqK6Q2@3u=^LqWQFAE%OdXNNq`~hc}zRSxK6s5hT?>!<6XbU717OD>VB$!9Y*K6C5b72H=qmu3&ipwYIRdr*hvau^)C9raH zMdP!8s%}_arIWBCD=qn%cN=SK7|E|1)R&7~6{(zZppZ$n#I~CE#@>tS5D;5PRow%*jqApric!e$0$sW{Hhw-RNtm1LSV2OMe`3hqVvFo%FR$?hH4 zk!hhhA8VSWm&(F9f-hg%YI&WnL9K85(eNx_Deb#kIwo^~JZS~1G8CwsLr1xnibs^T z8er^fhPS37T)6AJ9qe_Me#Ayf zgcR)^oS}E?`ZnMdT6z}adB{e=byk1(q7=>nFnV{#Lgvesw_GND3QF4_=>_@iA z6Z)uu7s__zm6`LE;+IZfig<-1SEl$K(s()j4HYls!=xTss zZ=$41shm#yX|6|Gx@_NCUF*f>9cvYyCCDJRi6+C7hPAX~8sGxpO_4um9_RVpeudXO zbq#`cb?cQ?p9 zqs<+as$2IHZKUYj7Dm1)fBe{*Uui#FQE|M^Wy!m@}tABBIqr!(CEj5m4={j0;RKHaO%Q_VJ_7u$SY-H%P~K zKL2E4vbmoFLi=ch{e-XKsiWWe)IgffjEYZmu%YZ`;Nl@F-6KTeSuAO` zooL8aZ#+L#US4Hx*ulF(2L$5|Z_{$cL_9Z*>5Gt$K4GdAf4ohFn#g4N-Sl-%E;2SO zG_%oGD6TLh@IgN_cCnb=h6Fk4xh3U6a_^>?FPx*Ek3^kYr&a64 z@4dFIl^53ZvVp7;2D*du%G%<@%i56Ksm|=ZXR9bllH0)7*_2cn>`Ps{H2zZJq}(}z zc~~Xj5OH1U)bzTcJ?O#f=u>>8`?a!A8{miPX>Vg(Ycow6^dx5QgdQ_vAXz3YBQ3}> zgtzoAmMx2uhn7;~)l%6co?nwcv|l>uxje1@7#m=zEVGvv?-f}Q+t%P+Jj{ljD%Uls zq>UDGPhA=U(GQQOMU9V>sJRkge~OF})*#@?C%7am!w20-M59II= z%L`~{nb2qmcb9k>C4G=-Lq#Q`Y)o;XmPNH90(R*uDf`?|82yG}iZ8&Nt+n`{nQdg(`r1^Rlx(#SW?o?07`h>I=d8X&x zfhBXF^CvIQu|`L?%vdD_=B2^wI(k>+-zBac@@XcfDMEgF%~VnRT^Fxx-++7{9^i21 zM#M&Ucz=Ex&p26AaP6jm4)dEbwwXKhlOCyeJ}z+5hurN!PnF-S)Ro9NOh~U}!gR;v z8OkCFax22{vCu>&6P4Y&Y(Kd(#NZ=JPvh0aosn&-9<`UzPZH#t*U#=8vf-Lg>V3Ak zkn%x-!=5{a=%+?FwRojB(X|WW{q5T7p-113mmDII~Ui2tB|<#D*wHxN^nAM!*Q#qAkLtM z6UYPbt5^90;=`{yMKFyz2fGSHRnr#&N}1dpQ`PS1B+hF=v+5;I+_k5qn7(QIsQCEb z86QrG)<3iLv`QYZc#%K{_(ka}8)4~)q||Md)RQ&D6{YGBXF>lyi6ojE0Z%6tM_tI{ z@!bi&@2y-V2FY^#4iV+A0hjxs%T8gG(6Mn8Ulc#!(c>JW2LZzx|-b5K+ciK$e`N)0mVt1tT{%tj*R39=F(i~&k>R8-! zElSe)O@Dby)r5iI4;5YqX{>Fk8|i=x@zh&hj!Z*Uj3AtOra^VC)m>~>=XOBVR-+Db ztd1Lg)pHqnEh^ZSvq={+(>D94UTj+nzjhzDbY-3w7k)njkv5N}9S={)ZqS-}xaORT zfH<@k9S?EMkekL6e9KeAD>ydzpMl^-MdEz53byp-jYOFMS;{cS^7Jp?=2XV?x% zch+6fc1TK zg?=tGsmjNHN&$kNv*S(|8|oY3$nI2I%gvqgQ_#9fA;HVnKgjdIKk49J(g_-TVmxt3 zxRzc93EEEBwa}^$Eo=nrBXzwGjf>!x?UqkE=+PkGP$&=CH5)2%A>yEk*X1$FPtk&HzV77^DyxrU$q#i#l^sT>GSAma;>tdD z#ir&b=h;2J>Bxio<`wW>=EY<=NdB!I8c$xEnHvjbdjE92nUF!X2eh8_r$Aakx^^!y z@?ePZu9e;Jd=D9jZMf!^&Q8XRxAR;QR=3l3SqheIEC?`bt!26S{ zR^&i9=LZ*c-fh-HJmvwL%U4`Rb`A^I--Pmrv*vWlRxC0|d~*e@%#;VhBr~tf#vPVs zPv~!;zlE!->_mTf;(n10tJbjS>BtqDwj00y_ON5}3tqM(qJdSHC)a5e>sfXWq@IOu z9C#+#DyrS~eEq%n^|CSI0aJj)k>^?Sc?BB(NTooLX<#3H+Bh^2POa5mEEVK}+zkos z87UggmC_u^i=jzy(wPd!73p04;q@`z>9W9Cq89J zgL6XbS`F_`M>C66_oA`dNvqcUNoN|5TF=a_X2s`MUY@BMzFrxXC1?v2)se+&q#Z#n z>vJQ$e9FmoQHXJ7VrXz;2-r1wX1Nt!3Okk)BlryJ8H<_W&XDxVR zeMUH{lo*I(dEB+2?v!L@=U^NWnm;~2X3Ic}8dHAfTT`IwDV3OLTU}F81f|zdkWpZkj6+5E8V$HO`=T0QmTI}}|JVis|FLazE^*z-FnL=9iQ zl<1k2)8mQAM^kVk8*LbK(u%FB7T%#X;aiL97{~m|jUx8RO}@*~pX$0|oea5}0++!Q z6CA5?d10fXjanv)%A=#+El(_GmxtcZ%VPn#+cfs1<+Kn++KMBNLGpX-x|%}K=wpvV zn~}@Qbw_s(SIv$&q+(>d*!m*n>ODJKE?FsTV_haaS+V>0myV}zkMOFwq@Rz$0$ zyz(Tc|8>UzKC@Kh7{@D1F(V5bIc36>&(jgRp|aw*n6d~pdXM z=QQ;qxoF0hiLOgL7M#asG?a?1%4FT!rFAb|haqC2AJ?ksBv!N&PZoxnqguX!fXQ4g zuXzeQjc2Q^;E{}!C5QR7uU)stjtH=RYm&3%ZS%WhWy6Z&+zj-o_Tcntu*-li7Gxm8 zl7V7_C(&JN_VQm7CQW!Qb`*WJH?nE=uwj``Uf`3OF&a>|Vp`@I@u|)7i3p7+oSL6I zbu?Nw zpX&BYJbH##TCaxwrm^OQ#rdI@BsCSmQtR;{Eg0EUM#PMBG3e98`lyQq3rQ{Oh>iB? z=qycc#{dW;>D};Wmfjc8@D?(?&-1q#a7Dfs!!@^9Fq(cC0H%r}xp3&pAy< z7c&eVv&GJr1<7p>tyK_+O%-TYm> z$xq)1on0vkTN>A9!m@*}TjSZd^&N8!*;P||!2_HkA)1>p;M%Y$=@nzk0th80-du#S zcXH$?KvWeDrj>IC`wnVig4ZN{8d{(@4W%08SA^|viwS-PqVR+|?~ZFU8xn%ml!UeY zz5COe*WZQb7b29_-#tZr1tlB3%2}dGk(ED9BEHGJAGQ&iXRen+Jww$x%JUMe6wIGO zv#-NyFPRJ0GVsNo)P}WTUF%uLU)y5!$CWrM&cEX&YQbjOigp!USWZ(VC34%GS`zZQ8-SN1uxz_}3M$qBfw&oW3R45OQXQ*X*0 zqiqErW+wxeRH8M6`;0~uX7q9Zn+M~Ds1(iOGw!kB_~`3fwh*l42X3tDSO90hmbTmh zhW%K{hDZ<6EV`X6eW{60bTTB4&e(d7< znomUFAJ?Y&-$z-R_Sg&>CRP5VEJ%#z7p$Y1@79E=W}HJS+sFz;mPo-EIoVA`*hT;q{X)ga6*^Rb?A?EP*G$_IbDJ zi7u?7U8pNxZ~Wos@gEdj-h~^w#XcHwo2k~RXI5v}O*M4=4B&tj2rgk58$sjsAU6sC+-kFva&d zEBjwYIo?Ptiny-U)Y-nmYx4A>V4{TdG;hQ$4_pu*VY?dDU+>;_>LXY@R$(m)Ze_8;$@r*)a(%@CV6&pDfyLcGm2O;dkqTSKWI((*PN#<-chc9dixBYhxl zl2h*Z>X=WXSD;kwiTb1S%x7fHqb9|t6BmXEpquKmwTt6#f-5DJix45cWI|DkcrIv0 z!ZIEqaZ$WzzBW9rkk)t2G%z|3?m`m-KhGQGX`U6dNw%bZ!qZbX%0gsGn*GZ^jYynGL%% zZV2UgrFGC=kU-hLr zH8Rd0P*`CwizW+{M7yU&M&W4{EhH`1@b;Cs$rDA0hFge|qT@;aj(*&a>qRD+TkBqi zLem;X7p|gtw?=4o`mX=H*nDDtqo`i}eoKzbe7YN&757pvrbi|mZnV=PAFSV3&6p6f zm0nq-ydt^82yKYhun6*zV}F-7x5(Mt9bKUj8&o2BPuW)nqH!jhKJs&ea!jagWiqi7P5z>~t!M12FMMP9n7iqZ9#igZo&T;s$Wde zswf{_&VWDdeY;+Mf6Ji3V}t8w(` z>X>q<81r$@oORJY1u1%6(Up z%-|FEK-M_juIKV-Ja?oIv7$}#Gu?PLLdlx_*=hI6A8YUZm^^~kj+iH++KSpP2&#SN zt2puT@6jJ`W0kXNDg21Qs(5PA<_KE0cA`Y~iAt>+2NtSgX40=~(>(08Y+&@Fxm%CC zjOo?;FlXzx+0^?Uk+8X5mgf=)GuhZQ-c_98DP5Nvb+h6Wh}O4_ho*Z+2)@cAf7W(P z7|W}xoX_!IB-Tw<+jjdqmX$6(xVcDH64T{{QdoD54RhAl%w9DAc#r5W7c<#17|g4~ z&)|*YvX<&{S?xu3%HvD$M3F*;8GK>Ic(+*U194lg#d+u~!pjF=O{~Lj+~Bqf5=T3D z$nou}gCw3>3#ubD}E?mOoPnzC*iLrT_Z(07ZXMSST>?KBJ zR$-#9<&!0YB~J?W;CFr9Hw6jkCJ;{p-~D&xo#xuG*8E#E8Dfm?W7jO+*of3tu*^+@ zM8<7}i73@A|3=UvVm43=1x4Mf1r_iOEVcLkdrcpxIPf~S7-35b#aD{%MQ?&VE<)Y; zxEKX(!{SBNK7$YZyTZ^j@VcSu)n@$V3}rCAhcAY82MCgT6~F*u1ePxIzrQrKF744x zYN;&0=D9ue0Z4KD`?3H3qCl#=P^0j_tj?u=;J4L&)DP@c*!-$`(%Q}jpAC}*<%83| z9S{ibkDIW&$Nmpak2?SVnUzuhhxhS6S}0Ihh=CO%{z@|bMldoHFz-(ORw}4PrK!8i zd*Gwr7L`(G0BXC)tGT{|6|33y)DpS9(*9r7<$@A95~%FA_1#_bwTG;KV*~=2zE|@8 zn<`4ogUp9L88=v{DRZjP`t5h9qV|7XnehLOlJx&@pu>Xm(XuN@snx16>{7Hw)pCGaZf{QA5I4uJj6!53Hicn5|j@Xa=dp{05w!d}j z?#;IIf@<^u17>v3pByBx?QVnGYKpc*fxRyU@4BwTScn?0XSGr(NxGC-#$wl4NHF*DojyVY7@Jt`!o zV9x5v>W5PzxkDDVg@|TRLA9NJ)7RpKpB%=EoV05QY+So5k4rZq6td0N5Ivu5^|%x* zq8``{SP)NA)DPCg8&n|uc})OnE-MOlAJP9rM8@vxW$|;iqM>sAquBW`;fe6q&98eA zdMTo(;^FvmbgdC$+aeh5RylqNZ78HB2H%}t|Nb$x?^N&f&%{EU^pDG~Yq)Tk5-0y~ zrv=@KVCKx8{;x|HxQa_~8o5p4f@*Wn@^3-eS$WKp#JoZ}=KX?$ay)lO&6b*6=GImd zs3`#O75qinSk02uT0)kT$ty|QFYx<;&9H~}J(~vREhjE=S_by%c_T*aoqA-bfTKrd zlIH!`i7Y7D5%DU$mTVazThLA69b( zsITLW?(XWN^~Zj`exMOjrrRLaLvbI_A9ufRSrBf3Mm?oaAy1TJ<_Qv=07TfwU&&G8 zl)bnACS#KFL1uZ@^dYa!jf_2uA(-22pZbu75d8Dj3>;4y zyak)>Tume$>1~i2y04(lXaD7{K-{YbS<8Ioc_1-*xIx^_yiUJuKwRz6x`eFAz+)k4 z_~2t}YU$}yN5clOOA+b==L?yHuOcPACk+5v^K#hmv(gSZws6gLm3?vYE#bvP5!t>P zqAWh+7R+w4heZCEJa_4WP3Tvn(F?myF5!bEA3#Lj3fJG4b(?A99n(5ERue#X9|DHq zGxfAy1JF$>?g3zi#+vsP%J+qr!+P%HLw{U;h;s27O|z7|0ZYN{WPZ6N(^2jbe>>^N zooJI|emY$b_mGbJz*niDRaUT8dtLbQM&7N!nX=(SiPmD>fH=WdV} z-(2vrPz`^b9s4yrL1iQ+pEVj3;Gdvsc`B6SFhH^G`CDK(#ix|n`TCJeuEM@N2CghK z%j54!Eu7MJqu6EA_7B^v-7n!MFwwfS-e3w&UF!dn4W0(e^9~eiiOJ9Y^y#aI^kbZeGoim-FAFpJ64UwT zYalcL2J9~xdGyD7fK5IksP_Q;cADy>n^{{HC;>`UD{&Ju}1J*CV##K~@KcH6rf)Z>? zIEw(=pBO}H+4Wt;EE)4sCKPoE_e&P@!O?|u(Z1E})mu~;sW4~B7u=jkZoV7482T)4 zdtqHDJN^Z$NSSP1>e$_cL!o+gxon}Z3^9DQt>3wmLo8pvaX&o>2SvB3Ot2PFJm{E< zOZu!!`W9(ruv^FaaO#4A`*Mi2u*qY=rrwfay8U-T^f^bdc|b*Tg_}noQoy3>^FmI8 ze;x@NzpyLqRm#K+W;PL$mMPO`)cmktn~tr2Oy1cJApM5oq&iuBUuf2SJaP>-Ygj|9 z%e}@TGkX+t8(I7A(VGR;RVZ}@O>;2RO40lIKUs!RUgvZQdFb@|LS7T?)?X9|m}z8v z$}<%?&4~{PIb1I>9>17Gy-0J6uH*IFfDWc#M`aDDoOuegk^&XJeX`z(zpz7}%|u4k z@MgJV=P@tu9>c`7(MyS-1qmn%Yi}>2uFK*cJ@_HQsU2}7gR+~)p$|WUeim<8kOIFd z9-yJK#fisX{>~GB8S5!wWNH4;euZ)02}dbh`zzzZWfP9f?UJ`pfR9y3A6!2@SLtFz zz`9UTwDjYy6EoGag)Mv|ZsC^9f`J2L_RWIU+@bn+5&UMsUJ5;EF=v$fp=QDJ=R&WW z4zMjvRExsu3kCw$r8rTZ>4T`UE@S<&+J~b0QN0Vdc~=)bx?+VZ!1ep? zzdywLHmIxyRA{IW=Nh8P z59t|LC1fKa03ab}s4z2X(cdw!t$Q~)phos?#9_ZV+Efs$2LA%^n;XFSJi4dZsj3W| zTPhlqs>=58XzBlK0YKP7Gq+#Af&I1g3tF&Wf7hCSVp;#?{kG)9C2Gf+MKbN~hqN|v zsayQFmjzEb@i*fld38?q!}W>+o*EXj^&Tt*hsCiWdwPe+AOb8 zQpx2`d9y~2A+c_8 zJsh94>?{ETyl1m-YmOKAI$K7_p^J^&zms})YxHU70BheMA{u_tdQL#Wt=3v0jdXU$&m0L$R_vhD*MuJK!YuIR>E@Q< z00otWp7PQsBU#5h7mqns;zEPD@mCpQ=l3hhn;GFom35@TPL0^yf*sw*P{4?vm&vo? zpWO4)K%rb)R>Dg`nVj#Ex_~iS?p~(y1_Oe-t!m{tALlyNO0;h7%p-O~Uo>EMk=}{d zD_s&Y!VR^ZzFhBl>y6pCj9r1`gG5qU9?ErzRnPq{-fig*3HwAN{CC{&+S65}?-!S? zK^F4&50CrmAf>&VI*I1ygUb1&FTuP8y17MNZDL&T(}oGhBb6uJyCRfw!@b~PtFDBO zH|0Om{=xJ5Z-UQiA#`9lYsY_f9m9@)$_eU)!<_clFAitGH5F{Dn20O0p-A~>i$Cyo zEt{uG0a+R8l}Jt=;1ez3n5!?tN0z;G4#yV_*5}~I&dxfrV^+H`?KI!vEe3_~nulD( z!*=0bsrUP@#>J#~Pd;!Rnnv}Ehd-BjBQ?h@>gUj(>uU=*9ML_p1GBvEWDrK-$+AvK zmrF6FE?e6yGRWnRJ(NLV#BL@D5K!ey#hU5s!dpGbi%uBOv0(G;=_4)ikEuka1*a4T z+N&%dE*8yX%Y1p)BwkD;xK>-nK@+*`m*IUzl_Bf@;06E1O>hPn6smPtY68l*$8AZ| z)hf5bwWXMVuS!^6)5oPadC(}>cgq1aM={3ao#t0?s8_U=w+KjfxZ%Ym<@?-g+ul57 z>%>pbq4-7DiGD&KU{=+qjO5~^=GII@^$b1&2~kBZu@F~Q=#aNyO{nB6Xl2rdd2S(# z1cFdUT%Qqhom=R1b=|#&te0?#$0T*3VvL;D$^pBw6$BQ0_LRX2uic=rPfa;{@5Ur2 z(!DQoYjzMY`&SMeIN-5We;R3X$YPF4OLz?&Q0qzE8^7M{L)NK{(peiW+q3b@Fv|O* zAaT7(6Ful2y81>Au)pq$g>iuX5y`=K;J%;Pi|Vj+U6>z$V!lkrm$;n`T?{7D{ejI^v}{p@wUNGkp9Vl*H&#jTR8=s5>RpQ0}qII zNi%J(jYexl+p?Pp6=J~=U!^_C7@$4|HFETANl4e!Wbcg!R0^d2w@|t*)rKwqF2JTr zJa-HAP*tOEk-eY*P%aYNNJFJfkum>pRr@!(FXX1x7tATCf!k#hdCAgnV*7rGAl$yF z-?Lm-6W0ctecYAOr_4QXPoc5FJ6R`Op8wtsu?Vwdt|mK#`8l&gOG}ZafSb14vwYR~ zTI^iQt@3=A`S+oS@|uWBDjD{amke^8eACA30vKEiZ|np39EZNfUv4P1dZ@gDAB`aul+$g51U zv0iUJJKtwequ6!5zYMqs>V*TQr*{_u#y_`rtWL_>)#g2z+wz?M5b!HUK+n5^FlJlo zg|yU69ma|Kj>yhkuC+D|he|;8ati^8xm7tN-b$4%$HBa)#PNN$!dg^dbNNN3*&>!% zz&A&pPrV~FLHw#d+2gqt?rMf7_qfg6wuRu%IhkV<5^1CW+?^kz%Fk3a^UJ8>Q{c{VtH8I{BaJFnExXx4N%(Zaa|;ua|NEs z#@Sg;+Gfwcikw|(>&3BFB8$GlSL@5;Fygsq3e}R9VrZTL4*jn#9-{@%PP$M$yo**KV<&+c8nlb@6Yz)4?Z4W~+L-AnZGB=ex<6OHFw~mATJ74Z0jXhQ#`$h?D?IMj zSMch%v)6v2Ve71n``igY_c=LiHTm!!vK%mglE^W-x6z@#WM{4sr|xTLcK7GrUGL*? zKaEPDg>lAfXgg@hXwqmpSs%B_S&?&d(0L_QvK=5FP$VFLBG3#ZXK=@ zHjni)`D?b`HE!!yCVP=~6isXRb)+B3um zxobawilaaa)Ab>R@0zYCFXkr?6=&ji1Szt5b01sVzB)J8>*u;DFF5tKx4BHV&mxnP zo%&gO78!LYXW5T<%i$vw(*p~5d}G4@aQT3|RqE zh;P>&l(874c85wbC;M~ zips45#GY`Y6tB7?tws~%+$HR_Pn(v<*s>RzIld-40cz9lN4zifYKZ2FPGlZOk`ra{ zo3|2vK3HaY(wsGm+Ksu z!Bmn5nfN*r9-jeU`P~5gV1Vcsmu2iXj=^4msH4{ueWJ{+xfkYL*OB5ER+q2Vs{SVM zz)@C$OEG+4DWaE^f7l}igSKk>D!)91hh_HYSB5o(7X5DFe2ffzoK_8Bu!=6_w#58o zdGjE#E%%CBxrVmmJ$89_kL}$3ZTPhWwcb(EN**m?z<5r+kTAhHRhii+9JEpJtq?PU zSxVRsm|5m@Mg8E2_We!Frs_KOs_c>B;j$kWGGz_aJ&MKjzg7W`iL%iAy7X8zoYPA_ zxIAFMxc}tVgVzrbyR~!rFnGFN?%M12C$Ac0e;w^Hk&)A>AN6^_1)s!#oKoYqVR09w z69g=$Kp5SipvkKB&E&^jYCzqMaub(dhk&6wn^yRl&i;1TcGTSCN%CpTc#KZD2t!xw zBy;@43t6K|Wce`0vN^Wv2JCi;RqLrt5EKSI-aTzX8@4CD>kJ|lW}{sa?cymHIo>fo zoBs7yt>--oB0ZAbK^W@7=k0K2u;}d6om)6@!=esAn=s{zO&rsS&I=cZ7sByVI@RBv z<Is?-AG}NIANU z&1X5OOeA+Awj$0;gjt50g9h}lF^7awl$mJEd27*74&ezlF4rlxQ&<6Gj#6MnXV+20 z<%tVG=^JnqPMoYYccKqB@8lQ}Z4QZxFpU_UtC(d6*WtjKQ;CHxrSP2z7Rva?RNTk8 z$5EIpjVjfKhgtfgJ~j`4OzG+2r&<%abZ`e0z|d>L|YjD7n6Lz?iiXXWbPW;~;fhD=;43EDC#j@8;ntAzeRcjbD?v z<^G}f+F8}x6&>5kHl1c(TjOV4DO>6?kJ(`%#W5MG5#s&bmTV>)G)TzcYA!z+qn=;Hp@^MiLpO?16LNH+K8g=P)ViL) zhGv5nLU~IE1WEzv;?>)oa7=TgQADSxiMcvoWsSWChvFB$l&?PVb5lda%Z*<48V@9p z3LdAR+>AZ?hmfSpiSc)Dhm=V-*1gdK1T5#u7M|U58oxs2ZL}J*_137k7t+`cf!kMZ z<$2vQht|@JlUL)6^LkUf>W921)v4lm?pFLG3CMEBE%!P3T`KX-Qdnd;i!rLu*rUk4Q_96Me9-3BrE_?qvGk4CM^ZT9e zoO|bf-|xE%ug&zK33_nKt zOnOdV>c75I|GYWHQobrGy=n_;;`!L_W?v@KH9(-OitHln^-dn-RdFc!9DQW0?=BoG zKn#k|JKTxU7nA#kr-%wGqdTrvQ{itpQ$ZP$?Y-98*=$Car6k+N+ZCRIB9}`)3f)a+Q9MqvME5ABpfh8ezoo{E z3~2c}+Qg7nd~}A&4NhlY5yTAWvUIff>a@=7UtH~#MZBuA*2e5jxu3;fDva{Qhnc() z2Hsn|_{;uid2n~@(vCM5Hk`P|E0LFDbG^HvB;Su3V4_8ho=*lNw`L-}p~nmtmt)+?~mrc1O%#3mw=2VYJG;1`(A9R~)iJt}ftV+yAPQw>w7X}5)gBhjYnbx` z!E)PdSOy0G5@_V1N>SfVfW!l>w?>s;3kSVQA{w!06$TaGj)6HP758|eJ!vrlK$GBU zj8}7fl4v1b^g{;+3pqN@LmUB=or%n7rGe&Ozxo(wZGKTWpMOQ zG9C?4fDVK&i`>RuriF_cARai9u*sZ`nG9v?COvcyJ=-3-n-g7LeNpPG{d zGZ@P-NUB$w4@BEl#>?hXGtNRcT#7yB$&pUTc$hNH3+k2yvC|w)awvmyB^2L7z6%x- zoWw5Uh{Mbz;IE348&t1IKA<4XC{oYBYTUvrgDFVKET&F(vyq0=WlE1SMq@I~w&oTo zNf<6IS}G*%>%bFM+0hT^@|zAjtgsTd3|5$9AW@&|az?e=EF1?I55&-y0shP71~&?< zlBPxgOH$BZB)rsblpJaj(& z2%vepWI@E&z<*f2#3K(|!NxdNhg{c(I|xqW!UY1r#z_-R-aBfRIdb^2$|{N70D2bh zX!<}sS+~m;)hteD6jKCW8P3lK|GSH^be @@ -82,21 +82,21 @@ The general flow of data is described in the following steps: Navigation between different console.log panels can be confusing when running Reactime. We created a short instruction where you can find the results for your console.log ### /src/extension -Console.logs from the Extension folder you can find here: +Console.logs from the Extension folder you can find here: - Chrome Extension (Developer mode) -- Background page +- Background page -![extension](../assets/extension-console.gif) +![extension](../assets/extension-console.gif) ### /src/app -Console.logs from the App folder you can find here: +Console.logs from the App folder you can find here: - Chrome Browser - Inspect ![frontend](../assets/console.gif) ### /src/backend -Console.logs from the App folder you can find here: +Console.logs from the App folder you can find here: - Open the Reactime extension in Chrome - Click "Inspect" on Reactime @@ -105,15 +105,15 @@ Console.logs from the App folder you can find here: ## Chrome Developer Resources Still unsure about what content scripts and background scripts do for Reactime, or for a chrome extensions in general? - The implementation details [can be found](./extension/background.js) [in the source files](./extension/contentScript.ts) themselves. - - We also encourage you to dive into [the official Chrome Developer Docs](https://developer.chrome.com/home). - + - We also encourage you to dive into [the official Chrome Developer Docs](https://developer.chrome.com/home). + Some relevant sections are reproduced below: -> Content scripts are files that run in the context of web pages. +> Content scripts are files that run in the context of web pages. > > By using the standard Document Object Model (DOM), they are able to **read** details of the web pages the browser visits, **make changes** to them and **pass information back** to their parent extension. ([Source](https://developer.chrome.com/extensions/content_scripts)) -- One helpful way to remember a content script's role in the Chrome ecosystem is to think: a *content* script is used to read and modify a target web page's rendered *content*. +- One helpful way to remember a content script's role in the Chrome ecosystem is to think: a *content* script is used to read and modify a target web page's rendered *content*. >A background page is loaded when it is needed, and unloaded when it goes idle. > @@ -122,12 +122,12 @@ Still unsure about what content scripts and background scripts do for Reactime, >The background page was listening for an event, and the event is dispatched. >A content script or other extension sends a message. >Another view in the extension, such as a popup, calls `runtime.getBackgroundPage`. -> +> >Once it has been loaded, a background page will stay running as long as it is performing an action, such as calling a Chrome API or issuing a network request. > > Additionally, the background page will not unload until all visible views and all message ports are closed. Note that opening a view does not cause the event page to load, but only prevents it from closing once loaded. ([Source](https://developer.chrome.com/extensions/background_pages)) - You can think of background scripts serving a purpose analogous to that of a **server** in the client/server paradigm. Much like a server, our `background.js` listens constantly for messages (i.e. requests) from two main places: 1. The content script - 2. The chrome extension "front-end" **(*NOT* the interface of the browser, this is an important distinction.)** -- In other words, a background script works as a sort of middleman, directly maintaining connection with its parent extension, and acting as a proxy enabling communication between it and the content script. + 2. The chrome extension "front-end" **(*NOT* the interface of the browser, this is an important distinction.)** +- In other words, a background script works as a sort of middleman, directly maintaining connection with its parent extension, and acting as a proxy enabling communication between it and the content script. diff --git a/src/app/reducers/mainReducer.js b/src/app/reducers/mainReducer.js index 3d2819874..9cfc352bb 100644 --- a/src/app/reducers/mainReducer.js +++ b/src/app/reducers/mainReducer.js @@ -322,6 +322,7 @@ export default (state, action) => produce(state, draft => { const checkChildren = node => { if (_.isEqual(node, action.payload)) { node.isExpanded = !node.isExpanded; + return; } if (node.children) { node.children.forEach(child => {