From 8588154b7b1e4fc9cc12395e76f380161f6cf156 Mon Sep 17 00:00:00 2001
From: Emanuel Mutschlechner
Date: Fri, 25 Jan 2019 10:55:00 +0100
Subject: [PATCH 0001/1371] fix: Fix component refs (#814)
---
src/backend/index.js | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/src/backend/index.js b/src/backend/index.js
index c70f783c6..98cf2e3e6 100644
--- a/src/backend/index.js
+++ b/src/backend/index.js
@@ -687,12 +687,9 @@ function processState (instance) {
*/
function processRefs (instance) {
- if (Object.keys(instance.$refs).length === 0) {
- return []
- }
- console.log(instance.$refs)
- let refs = Object.keys(instance.$refs).map(key => getCustomRefDetails(instance, key, instance.$refs[key]))
- return refs.length > 0 ? refs : []
+ return Object.keys(instance.$refs)
+ .filter(key => instance.$refs[key])
+ .map(key => getCustomRefDetails(instance, key, instance.$refs[key]))
}
/**
From ada7f58b9c2d8fd07358fc1ca7a92e494bc7fb92 Mon Sep 17 00:00:00 2001
From: AnwarElbo
Date: Fri, 25 Jan 2019 11:20:41 +0100
Subject: [PATCH 0002/1371] fix: Show 0 as a valid key in the component tree,
closes #827 (#828)
* #827: Show 0 as a valid key in the component tree
* Delete spaces
* test(e2e): 0 key
Co-authored-by: Guillaume Chau
---
cypress/integration/components-tab.js | 8 ++++++++
shells/dev/target/Target.vue | 2 +-
src/devtools/views/components/ComponentInstance.vue | 2 +-
3 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/cypress/integration/components-tab.js b/cypress/integration/components-tab.js
index 27e17fb96..69048607c 100644
--- a/cypress/integration/components-tab.js
+++ b/cypress/integration/components-tab.js
@@ -28,6 +28,14 @@ suite('components tab', () => {
})
})
+ it('should display 0 key', () => {
+ cy.get('.tree > .instance .instance:nth-child(2)').within(() => {
+ cy.get('.arrow').click().then(() => {
+ cy.get('.instance:nth-child(3) .attr').contains('key=0')
+ })
+ })
+ })
+
it('should detect components in transition', () => {
cy.get('.tree > .instance .instance:nth-child(7)').within(() => {
cy.get('.arrow').click().then(() => {
diff --git a/shells/dev/target/Target.vue b/shells/dev/target/Target.vue
index f8481d4b9..6d743deb2 100644
--- a/shells/dev/target/Target.vue
+++ b/shells/dev/target/Target.vue
@@ -27,7 +27,7 @@
-
+
Date: Fri, 25 Jan 2019 13:41:11 +0300
Subject: [PATCH 0003/1371] fix: payload on replaying mutation, closes #773 ,
closes #792, closes #802 (#829)
* test(e2e): test mutation payload
* Fix payload on replaying mutation
* test(e2e): fix expect
Co-authored-by: Guillaume Chau
---
cypress/integration/vuex-tab.js | 7 ++++++-
shells/dev/target/store.js | 11 +++++++++--
src/backend/vuex.js | 2 +-
3 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/cypress/integration/vuex-tab.js b/cypress/integration/vuex-tab.js
index a222fa1a5..90a9c745e 100644
--- a/cypress/integration/vuex-tab.js
+++ b/cypress/integration/vuex-tab.js
@@ -23,7 +23,7 @@ suite('vuex tab', () => {
it('should filter state & getters', () => {
cy.get('.right .search input').clear().type('cou')
- cy.get('.data-field').should('have.length', 1)
+ cy.get('.data-field').should('have.length', 2)
cy.get('.right .search input').clear().type('no value')
cy.get('.data-field').should('have.length', 0)
cy.get('.right .search input').clear()
@@ -59,6 +59,11 @@ suite('vuex tab', () => {
expect(el.text()).to.include('type:"INCREMENT"')
expect(el.text()).to.include('count:2')
})
+ cy.get('.data-field .key').contains('lastCountPayload').click()
+ cy.get('.vuex-state-inspector').then(el => {
+ expect(el.text()).to.include('a:1')
+ expect(el.text()).to.include('b:Object')
+ })
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('1')
})
diff --git a/shells/dev/target/store.js b/shells/dev/target/store.js
index 34a68a52f..c99ed2f78 100644
--- a/shells/dev/target/store.js
+++ b/shells/dev/target/store.js
@@ -7,6 +7,7 @@ export default new Vuex.Store({
state: {
inited: 0,
count: 0,
+ lastCountPayload: null,
date: new Date(),
set: new Set(),
map: new Map(),
@@ -23,8 +24,14 @@ export default new Vuex.Store({
},
mutations: {
TEST_INIT: state => state.inited++,
- INCREMENT: state => state.count++,
- DECREMENT: state => state.count--,
+ INCREMENT: (state, payload) => {
+ state.count++
+ state.lastCountPayload = payload
+ },
+ DECREMENT: (state, payload) => {
+ state.count--
+ state.lastCountPayload = payload
+ },
UPDATE_DATE: state => {
state.date = new Date()
},
diff --git a/src/backend/vuex.js b/src/backend/vuex.js
index 08fec18cf..e74fe2b00 100644
--- a/src/backend/vuex.js
+++ b/src/backend/vuex.js
@@ -146,7 +146,7 @@ export function initVuexBackend (hook, bridge) {
// Replay mutations
for (let i = snapshot.index + 1; i <= index; i++) {
const mutation = mutations[i]
- mutation.handlers.forEach(handler => handler(state, mutation.payload))
+ mutation.handlers.forEach(handler => handler(mutation.payload))
if (i !== index && i % SharedData.cacheVuexSnapshotsEvery === 0) {
takeSnapshot(i, state)
}
From 793a97b057458e1a30c57a6becef0cba3b4dbda6 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Fri, 25 Jan 2019 11:45:05 +0100
Subject: [PATCH 0004/1371] chore: put store strict mode
---
shells/dev/target/store.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/shells/dev/target/store.js b/shells/dev/target/store.js
index c99ed2f78..ad0bff025 100644
--- a/shells/dev/target/store.js
+++ b/shells/dev/target/store.js
@@ -4,6 +4,7 @@ import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
+ strict: true,
state: {
inited: 0,
count: 0,
From 9305a517fd3d9ed19f485396c9e31b1a8425abb1 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Fri, 25 Jan 2019 11:48:31 +0100
Subject: [PATCH 0005/1371] fix: vuex throws warning when editing state in
strict mode, closes #757
---
src/backend/vuex.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/backend/vuex.js b/src/backend/vuex.js
index e74fe2b00..9aeb2bfba 100644
--- a/src/backend/vuex.js
+++ b/src/backend/vuex.js
@@ -178,7 +178,9 @@ export function initVuexBackend (hook, bridge) {
if (value) {
parsedValue = parse(value, true)
}
+ store._committing = true
set(store.state, path, parsedValue)
+ store._committing = false
bridge.send('vuex:inspected-state', {
index,
snapshot: getSnapshot()
From 8eebefe9c9f276f27728e5144a4bc1334b5ca4d0 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Fri, 25 Jan 2019 12:09:46 +0100
Subject: [PATCH 0006/1371] fix: don't allow state edition if not active
mutation
---
src/devtools/mixins/data-field-edit.js | 7 +++++
.../views/vuex/VuexStateInspector.vue | 29 ++++++++++++++++---
2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/src/devtools/mixins/data-field-edit.js b/src/devtools/mixins/data-field-edit.js
index d32fef867..7bba7d46f 100644
--- a/src/devtools/mixins/data-field-edit.js
+++ b/src/devtools/mixins/data-field-edit.js
@@ -21,6 +21,12 @@ function numberQuickEditMod (event) {
}
export default {
+ inject: {
+ InspectorInjection: {
+ default: null
+ }
+ },
+
props: {
editable: {
type: Boolean,
@@ -54,6 +60,7 @@ export default {
},
isEditable () {
+ if (this.InspectorInjection && !this.InspectorInjection.editable) return false
return this.editable &&
!this.fieldOptions.abstract &&
!this.fieldOptions.readOnly &&
diff --git a/src/devtools/views/vuex/VuexStateInspector.vue b/src/devtools/views/vuex/VuexStateInspector.vue
index 76cb79c08..999be2386 100644
--- a/src/devtools/views/vuex/VuexStateInspector.vue
+++ b/src/devtools/views/vuex/VuexStateInspector.vue
@@ -100,7 +100,7 @@ import StateInspector from 'components/StateInspector.vue'
import { searchDeepInObject, sortByKey, stringify, parse } from 'src/util'
import debounce from 'lodash.debounce'
import groupBy from 'lodash.groupby'
-import { mapGetters, mapActions } from 'vuex'
+import { mapState, mapGetters, mapActions } from 'vuex'
export default {
components: {
@@ -109,22 +109,32 @@ export default {
StateInspector
},
+ provide () {
+ return {
+ InspectorInjection: this.injection
+ }
+ },
+
data () {
return {
showStateCopiedMessage: false,
showBadJSONMessage: false,
showImportStatePopup: false,
- filter: ''
+ filter: '',
+ injection: {
+ editable: false
+ }
}
},
computed: {
- ...mapGetters('vuex', [
- 'inspectedState',
+ ...mapState('vuex', [
+ 'activeIndex',
'inspectedIndex'
]),
...mapGetters('vuex', [
+ 'inspectedState',
'filteredHistory'
]),
@@ -162,6 +172,10 @@ export default {
isOnlyMutationPayload () {
return Object.keys(this.inspectedState).length === 1 && this.inspectedState.mutation
+ },
+
+ isActive () {
+ return this.activeIndex === this.inspectedIndex
}
},
@@ -172,6 +186,13 @@ export default {
this.$el.querySelector('textarea').focus()
})
}
+ },
+
+ isActive: {
+ handler (value) {
+ this.injection.editable = value
+ },
+ immediate: true
}
},
From 6338a63fc4f7e5f79fd350d8ce322f4678e8c531 Mon Sep 17 00:00:00 2001
From: Ilkwon Sim
Date: Sat, 26 Jan 2019 02:09:00 +0900
Subject: [PATCH 0007/1371] feat: Add $attrs to StateInspector, closes #734
(#861)
This PR fixes #734
Co-authored-by: Guillaume Chau
---
cypress/integration/components-tab.js | 9 +++++++++
shells/dev/target/Other.vue | 1 +
shells/dev/target/Target.vue | 2 +-
src/backend/index.js | 13 ++++++++++++-
src/devtools/components/StateInspector.vue | 3 ++-
5 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/cypress/integration/components-tab.js b/cypress/integration/components-tab.js
index 69048607c..0cfafedcd 100644
--- a/cypress/integration/components-tab.js
+++ b/cypress/integration/components-tab.js
@@ -137,4 +137,13 @@ suite('components tab', () => {
expect(el.text()).to.contain('tester: {
+ cy.get('.instance .instance:nth-child(2) .arrow-wrapper').click()
+ cy.get('.instance .instance .instance:nth-child(1) .item-name').click()
+ cy.get('.right .data-wrapper').then(el => {
+ expect(el.text()).to.contain('$attrs')
+ expect(el.text()).to.contain('attr:"some-attr"')
+ })
+ })
})
diff --git a/shells/dev/target/Other.vue b/shells/dev/target/Other.vue
index 474020c6c..3dfa81203 100644
--- a/shells/dev/target/Other.vue
+++ b/shells/dev/target/Other.vue
@@ -18,6 +18,7 @@ const computedPropMixin = {
export default {
name: 'other-with-mine',
+ inheritAttrs: false,
mixins: [computedPropMixin],
provide: {
foo: 'bar',
diff --git a/shells/dev/target/Target.vue b/shells/dev/target/Target.vue
index 6d743deb2..f0af9eb3d 100644
--- a/shells/dev/target/Target.vue
+++ b/shells/dev/target/Target.vue
@@ -8,7 +8,7 @@
-
+
-
+
@@ -169,7 +169,7 @@ export default {
From c6a46f159bf5b68ad76fbdf5092174ed535fe524 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Wed, 20 Mar 2019 17:15:03 +0100
Subject: [PATCH 0039/1371] fix(background): fix chrome context menu error,
closes #629
---
shells/chrome/src/background.js | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/shells/chrome/src/background.js b/shells/chrome/src/background.js
index d9fd2a061..04ad5f80e 100644
--- a/shells/chrome/src/background.js
+++ b/shells/chrome/src/background.js
@@ -103,15 +103,15 @@ chrome.tabs.onActivated.addListener(({ tabId }) => {
})
function updateContextMenuItem () {
- if (ports[activeTabId]) {
- chrome.contextMenus.create({
- id: 'vue-inspect-instance',
- title: 'Inspect Vue component',
- contexts: ['all']
- })
- } else {
- chrome.contextMenus.remove('vue-inspect-instance')
- }
+ chrome.contextMenus.removeAll(() => {
+ if (ports[activeTabId]) {
+ chrome.contextMenus.create({
+ id: 'vue-inspect-instance',
+ title: 'Inspect Vue component',
+ contexts: ['all']
+ })
+ }
+ })
}
chrome.contextMenus.onClicked.addListener((info, tab) => {
From c51eab802658bf5ddd7fba6469b82afbcc46cd88 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Wed, 20 Mar 2019 17:30:10 +0100
Subject: [PATCH 0040/1371] fix: vue 1.0 support issues, closes #741
---
src/backend/index.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/index.js b/src/backend/index.js
index ec21e4bd0..284324208 100644
--- a/src/backend/index.js
+++ b/src/backend/index.js
@@ -370,7 +370,7 @@ function capture (instance, index, list) {
captureCount++
}
- if (instance.$options && instance.$options.abstract && instance._vnode.componentInstance) {
+ if (instance.$options && instance.$options.abstract && instance._vnode && instance._vnode.componentInstance) {
instance = instance._vnode.componentInstance
}
@@ -432,7 +432,7 @@ function capture (instance, index, list) {
.filter(Boolean)
}
- if (instance._vnode.children) {
+ if (instance._vnode && instance._vnode.children) {
ret.children = ret.children.concat(
flatten(instance._vnode.children.map(captureChild))
.filter(Boolean)
@@ -642,7 +642,7 @@ function processProps (instance) {
}
function processAttrs (instance) {
- return Object.entries(instance.$attrs).map(([key, value]) => {
+ return Object.entries(instance.$attrs || {}).map(([key, value]) => {
return {
type: '$attrs',
key,
From 247443b800e0e662221fcef2e7e517d893aa93a8 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Wed, 20 Mar 2019 17:48:54 +0100
Subject: [PATCH 0041/1371] fix(vuex): vuex 1.0 support
---
src/backend/index.js | 4 ++--
src/backend/vuex.js | 13 +++++++++++--
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/backend/index.js b/src/backend/index.js
index 284324208..fe14c9966 100644
--- a/src/backend/index.js
+++ b/src/backend/index.js
@@ -125,10 +125,10 @@ function connect (Vue) {
// vuex
if (hook.store) {
- initVuexBackend(hook, bridge)
+ initVuexBackend(hook, bridge, isLegacy)
} else {
hook.once('vuex:init', store => {
- initVuexBackend(hook, bridge)
+ initVuexBackend(hook, bridge, isLegacy)
})
}
diff --git a/src/backend/vuex.js b/src/backend/vuex.js
index 5ea4cd1b7..e2b8048df 100644
--- a/src/backend/vuex.js
+++ b/src/backend/vuex.js
@@ -3,7 +3,7 @@ import SharedData from 'src/shared-data'
import { set } from '../util'
import Vue from 'vue'
-export function initVuexBackend (hook, bridge) {
+export function initVuexBackend (hook, bridge, isLegacy) {
const store = hook.store
let originalVm = store._vm
@@ -147,7 +147,16 @@ export function initVuexBackend (hook, bridge) {
for (let i = snapshot.index + 1; i <= index; i++) {
const mutation = mutations[i]
if (mutation.handlers) {
- mutation.handlers.forEach(handler => handler(mutation.payload))
+ if (Array.isArray(mutation.handlers)) {
+ mutation.handlers.forEach(handler => handler(mutation.payload))
+ } else {
+ if (isLegacy) {
+ // Vuex 1
+ mutation.handlers(store.state, mutation.payload)
+ } else {
+ mutation.handlers(mutation.payload)
+ }
+ }
}
if (i !== index && i % SharedData.cacheVuexSnapshotsEvery === 0) {
From 68ff43f2ee39625da0d4b876812aa218be364623 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Wed, 20 Mar 2019 18:28:58 +0100
Subject: [PATCH 0042/1371] refactor(vuex): removed state loading progress =>
now indeterminate
---
src/backend/vuex.js | 16 +---------------
src/devtools/index.js | 2 +-
src/devtools/views/vuex/VuexStateInspector.vue | 4 +---
src/devtools/views/vuex/actions.js | 10 ++--------
src/shared-data.js | 2 +-
5 files changed, 6 insertions(+), 28 deletions(-)
diff --git a/src/backend/vuex.js b/src/backend/vuex.js
index e2b8048df..e5b9e1123 100644
--- a/src/backend/vuex.js
+++ b/src/backend/vuex.js
@@ -136,12 +136,7 @@ export function initVuexBackend (hook, bridge, isLegacy) {
const { state } = parse(snapshot.state, true)
store.replaceState(state)
- const total = index - snapshot.index
- SharedData.snapshotLoading = {
- current: 0,
- total
- }
- let time = Date.now()
+ SharedData.snapshotLoading = true
// Replay mutations
for (let i = snapshot.index + 1; i <= index; i++) {
@@ -162,15 +157,6 @@ export function initVuexBackend (hook, bridge, isLegacy) {
if (i !== index && i % SharedData.cacheVuexSnapshotsEvery === 0) {
takeSnapshot(i, state)
}
-
- const now = Date.now()
- if (now - time <= 100) {
- time = now
- SharedData.snapshotLoading = {
- current: i - snapshot.index,
- total
- }
- }
}
// Send final state after replay
diff --git a/src/devtools/index.js b/src/devtools/index.js
index 52cc77fe5..fcc97eb88 100644
--- a/src/devtools/index.js
+++ b/src/devtools/index.js
@@ -155,7 +155,7 @@ function initApp (shell) {
}
requestAnimationFrame(() => {
- SharedData.snapshotLoading = null
+ SharedData.snapshotLoading = false
})
})
diff --git a/src/devtools/views/vuex/VuexStateInspector.vue b/src/devtools/views/vuex/VuexStateInspector.vue
index fe0a42228..3d8665492 100644
--- a/src/devtools/views/vuex/VuexStateInspector.vue
+++ b/src/devtools/views/vuex/VuexStateInspector.vue
@@ -65,9 +65,7 @@
Loading state...
-
+
Date: Wed, 20 Mar 2019 18:34:41 +0100
Subject: [PATCH 0043/1371] fix(vuex): state label design tweaks
---
src/devtools/views/vuex/VuexStateInspector.vue | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/devtools/views/vuex/VuexStateInspector.vue b/src/devtools/views/vuex/VuexStateInspector.vue
index 3d8665492..79e01482f 100644
--- a/src/devtools/views/vuex/VuexStateInspector.vue
+++ b/src/devtools/views/vuex/VuexStateInspector.vue
@@ -73,7 +73,7 @@
>
Recording state...
@@ -260,13 +260,12 @@ function copyToClipboard (state) {
flex-direction column
box-center()
min-height 140px
- font-size 24px
+ font-size 16px
margin 0 42px
.label
display flex
align-items center
- font-weight lighter
color $blueishGrey
margin-bottom 12px
@@ -274,8 +273,6 @@ function copyToClipboard (state) {
margin-right 12px
>>> svg
fill @color
- .vue-ui-loading-bar
- width 100%
.message
margin-left 5px
From 8942bcbfbd454c5dbc0986ee0a5e7ccbd0645df8 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Wed, 20 Mar 2019 18:55:45 +0100
Subject: [PATCH 0044/1371] refactor(vuex): only take snapshots of the store
state, closes #848
---
shells/dev/target/store.js | 6 +-
src/backend/hook.js | 5 +-
src/backend/vuex.js | 91 +++++++++++++++++++------------
src/devtools/index.js | 4 +-
src/devtools/views/vuex/module.js | 4 +-
5 files changed, 69 insertions(+), 41 deletions(-)
diff --git a/shells/dev/target/store.js b/shells/dev/target/store.js
index 6fa3b6a26..4b54d83c8 100644
--- a/shells/dev/target/store.js
+++ b/shells/dev/target/store.js
@@ -57,7 +57,11 @@ export default new Vuex.Store({
}
},
getters: {
- twoFoos: state => state.foo.repeat(2)
+ twoFoos: state => state.foo.repeat(2),
+ dummy: () => {
+ console.log('dummy getter was computed')
+ return 'dummy'
+ }
},
mutations: {
ADD_BAR: (state) => {
diff --git a/src/backend/hook.js b/src/backend/hook.js
index 1705a8ad9..988e582e1 100644
--- a/src/backend/hook.js
+++ b/src/backend/hook.js
@@ -98,7 +98,10 @@ export function installHook (target) {
hook.once('vuex:init', store => {
hook.store = store
- hook.initialStore = clone(store)
+ hook.initialStore = {
+ state: clone(store.state),
+ getters: store.getters
+ }
})
Object.defineProperty(target, '__VUE_DEVTOOLS_GLOBAL_HOOK__', {
diff --git a/src/backend/vuex.js b/src/backend/vuex.js
index e5b9e1123..f3fa4687a 100644
--- a/src/backend/vuex.js
+++ b/src/backend/vuex.js
@@ -14,29 +14,26 @@ export function initVuexBackend (hook, bridge, isLegacy) {
computed: originalVm.$options.computed
})
- const getSnapshot = (_store = store) => stringify({
- state: _store.state,
- getters: _store.getters || {}
- })
+ const getStateSnapshot = (_store = store) => stringify(_store.state)
- let baseSnapshot, snapshots, mutations, lastState
+ let baseStateSnapshot, stateSnapshots, mutations, lastState
function reset () {
- baseSnapshot = getSnapshot(hook.initialStore)
+ baseStateSnapshot = getStateSnapshot(hook.initialStore)
hook.initialStore = undefined
mutations = []
resetSnapshotCache()
}
function resetSnapshotCache () {
- snapshots = [
- { index: -1, state: baseSnapshot }
+ stateSnapshots = [
+ { index: -1, state: baseStateSnapshot }
]
}
reset()
- bridge.send('vuex:init', baseSnapshot)
+ bridge.send('vuex:init')
// deal with multiple backend injections
hook.off('vuex:mutation')
@@ -66,11 +63,11 @@ export function initVuexBackend (hook, bridge, isLegacy) {
// devtool -> application
bridge.on('vuex:travel-to-state', ({ index, apply }) => {
- const snapshot = replayMutations(index)
- const { state } = parse(snapshot, true)
+ const stateSnapshot = replayMutations(index)
+ const state = parse(stateSnapshot, true)
bridge.send('vuex:inspected-state', {
index,
- snapshot
+ snapshot: getSnapshot(stateSnapshot)
})
if (apply) {
hook.emit('vuex:travel-to-state', state)
@@ -86,7 +83,7 @@ export function initVuexBackend (hook, bridge, isLegacy) {
})
bridge.on('vuex:commit', index => {
- baseSnapshot = lastState
+ baseStateSnapshot = lastState
resetSnapshotCache()
mutations = mutations.slice(index + 1)
mutations.forEach((mutation, index) => {
@@ -101,15 +98,22 @@ export function initVuexBackend (hook, bridge, isLegacy) {
bridge.on('vuex:import-state', state => {
hook.emit('vuex:travel-to-state', parse(state, true))
- bridge.send('vuex:init', getSnapshot())
+ bridge.send('vuex:init')
})
bridge.on('vuex:inspect-state', index => {
- const snapshot = replayMutations(index)
- bridge.send('vuex:inspected-state', {
- index,
- snapshot
- })
+ if (index === -1) {
+ bridge.send('vuex:inspected-state', {
+ index,
+ snapshot: getSnapshot(baseStateSnapshot)
+ })
+ } else {
+ const stateSnapshot = replayMutations(index)
+ bridge.send('vuex:inspected-state', {
+ index,
+ snapshot: getSnapshot(stateSnapshot)
+ })
+ }
})
function replayMutations (index) {
@@ -117,29 +121,29 @@ export function initVuexBackend (hook, bridge, isLegacy) {
// Get most recent snapshot for target index
// for faster replay
- let snapshot
- for (let i = 0; i < snapshots.length; i++) {
- const s = snapshots[i]
+ let stateSnapshot
+ for (let i = 0; i < stateSnapshots.length; i++) {
+ const s = stateSnapshots[i]
if (s.index > index) {
break
} else {
- snapshot = s
+ stateSnapshot = s
}
}
let resultState
// Snapshot was already replayed
- if (snapshot.index === index) {
- resultState = snapshot.state
+ if (stateSnapshot.index === index) {
+ resultState = stateSnapshot.state
} else {
- const { state } = parse(snapshot.state, true)
+ const state = parse(stateSnapshot.state, true)
store.replaceState(state)
SharedData.snapshotLoading = true
// Replay mutations
- for (let i = snapshot.index + 1; i <= index; i++) {
+ for (let i = stateSnapshot.index + 1; i <= index; i++) {
const mutation = mutations[i]
if (mutation.handlers) {
if (Array.isArray(mutation.handlers)) {
@@ -155,12 +159,12 @@ export function initVuexBackend (hook, bridge, isLegacy) {
}
if (i !== index && i % SharedData.cacheVuexSnapshotsEvery === 0) {
- takeSnapshot(i, state)
+ takeStateSnapshot(i, state)
}
}
// Send final state after replay
- resultState = getSnapshot()
+ resultState = getStateSnapshot()
}
lastState = resultState
@@ -185,15 +189,34 @@ export function initVuexBackend (hook, bridge, isLegacy) {
})
})
- function takeSnapshot (index) {
- snapshots.push({
+ function takeStateSnapshot (index) {
+ stateSnapshots.push({
index,
- state: getSnapshot()
+ state: getStateSnapshot()
})
// Delete old cached snapshots
- if (snapshots.length > SharedData.cacheVuexSnapshotsLimit) {
- snapshots.splice(1, 1)
+ if (stateSnapshots.length > SharedData.cacheVuexSnapshotsLimit) {
+ stateSnapshots.splice(1, 1)
+ }
+ }
+
+ function getSnapshot (stateSnapshot = null) {
+ if (stateSnapshot) {
+ store._vm = snapshotsVm
+ store.replaceState(parse(stateSnapshot, true))
}
+
+ const result = stringify({
+ state: store.state,
+ getters: store.getters || {}
+ })
+
+ if (stateSnapshot) {
+ // Restore user state
+ store._vm = originalVm
+ }
+
+ return result
}
}
diff --git a/src/devtools/index.js b/src/devtools/index.js
index fcc97eb88..3d5c74636 100644
--- a/src/devtools/index.js
+++ b/src/devtools/index.js
@@ -133,8 +133,8 @@ function initApp (shell) {
store.commit('components/TOGGLE_INSTANCE', parse(payload))
})
- bridge.on('vuex:init', snapshot => {
- store.commit('vuex/INIT', snapshot)
+ bridge.on('vuex:init', () => {
+ store.commit('vuex/INIT')
})
bridge.on('vuex:mutation', payload => {
diff --git a/src/devtools/views/vuex/module.js b/src/devtools/views/vuex/module.js
index f05bd013d..41f9485fe 100644
--- a/src/devtools/views/vuex/module.js
+++ b/src/devtools/views/vuex/module.js
@@ -10,7 +10,6 @@ let uid = 0
const state = {
hasVuex: false,
- initial: null,
base: null, // type Snapshot = { state: {}, getters: {} }
inspectedIndex: -1,
activeIndex: -1,
@@ -25,8 +24,7 @@ const state = {
}
const mutations = {
- 'INIT' (state, snapshot) {
- state.initial = state.base = snapshot
+ 'INIT' (state) {
state.hasVuex = true
reset(state)
},
From ba8b349063ccaf9598d778d977f907ffb385817b Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Thu, 21 Mar 2019 02:57:45 +0100
Subject: [PATCH 0045/1371] feat(vuex): new backend now supports dynamic
modules, closes #875
Known issue: modules registered before Vuex backend is ready may cause issues
---
shells/dev/target/Counter.vue | 35 +++-
shells/dev/target/Target.vue | 2 +-
shells/dev/target/dynamic-module.js | 19 +++
src/backend/vuex.js | 156 +++++++++++++++---
.../views/vuex/VuexStateInspector.vue | 3 +-
5 files changed, 187 insertions(+), 28 deletions(-)
create mode 100644 shells/dev/target/dynamic-module.js
diff --git a/shells/dev/target/Counter.vue b/shells/dev/target/Counter.vue
index cb5be9522..5247615d7 100644
--- a/shells/dev/target/Counter.vue
+++ b/shells/dev/target/Counter.vue
@@ -1,12 +1,12 @@
{{ count }}
-
-
+
+
-
+
Your counter is {{ $store.getters.isPositive ? 'positive' : 'negative' }}
@@ -15,14 +15,25 @@
foo: {{ foo }}
twoFoos: {{ twoFoos }}
-
-
+
+
+
+
+
+
+ {{ $store.state.dynamic }}
+ {{ $store.getters }}
+
+
+
+
diff --git a/shells/dev/target/Target.vue b/shells/dev/target/Target.vue
index f0af9eb3d..1f469084f 100644
--- a/shells/dev/target/Target.vue
+++ b/shells/dev/target/Target.vue
@@ -1,6 +1,6 @@
-
{{localMsg}}
+
{{localMsg}} {{ msg }}
Regex: {{regex.toString()}}
(Press enter to set)
diff --git a/shells/dev/target/dynamic-module.js b/shells/dev/target/dynamic-module.js
new file mode 100644
index 000000000..6d1fb250f
--- /dev/null
+++ b/shells/dev/target/dynamic-module.js
@@ -0,0 +1,19 @@
+export default {
+ namespaced: true,
+ state () {
+ return {
+ dynamic: true
+ }
+ },
+ getters: {
+ notDynamic: state => {
+ console.log('notDynamic', JSON.stringify(state, null, 2))
+ if (state) return !state.dynamic
+ }
+ },
+ mutations: {
+ TOGGLE: state => {
+ state.dynamic = !state.dynamic
+ }
+ }
+}
diff --git a/src/backend/vuex.js b/src/backend/vuex.js
index f3fa4687a..17f47281f 100644
--- a/src/backend/vuex.js
+++ b/src/backend/vuex.js
@@ -1,31 +1,36 @@
import { stringify, parse } from 'src/util'
import SharedData from 'src/shared-data'
-import { set } from '../util'
+import { set, get } from '../util'
import Vue from 'vue'
export function initVuexBackend (hook, bridge, isLegacy) {
const store = hook.store
- let originalVm = store._vm
- const snapshotsVm = new Vue({
- data: {
- $$state: {}
- },
- computed: originalVm.$options.computed
- })
+ let snapshotsVm = null
+ const updateSnapshotsVm = (state) => {
+ snapshotsVm = new Vue({
+ data: {
+ $$state: state || {}
+ },
+ computed: store._vm.$options.computed
+ })
+ }
+
+ updateSnapshotsVm()
const getStateSnapshot = (_store = store) => stringify(_store.state)
let baseStateSnapshot, stateSnapshots, mutations, lastState
+ let registeredModules = {}
+ let allTimeModules = {}
function reset () {
baseStateSnapshot = getStateSnapshot(hook.initialStore)
- hook.initialStore = undefined
mutations = []
resetSnapshotCache()
}
- function resetSnapshotCache () {
+ function resetSnapshotCache (force = true) {
stateSnapshots = [
{ index: -1, state: baseStateSnapshot }
]
@@ -33,6 +38,45 @@ export function initVuexBackend (hook, bridge, isLegacy) {
reset()
+ const origRegisterModule = store.registerModule.bind(store)
+ store.registerModule = (path, module, options) => {
+ if (typeof path === 'string') path = [path]
+ if (SharedData.recordVuex) {
+ addMutation(`Register module: ${path.join('/')}`, {
+ path,
+ module,
+ options
+ }, {
+ registerModule: true
+ })
+ }
+
+ const key = path.join('/')
+ registeredModules[key] = allTimeModules[key] = {
+ path,
+ module,
+ options
+ }
+
+ origRegisterModule(path, module, options)
+ }
+
+ const origUnregisterModule = store.unregisterModule.bind(store)
+ store.unregisterModule = (path) => {
+ if (typeof path === 'string') path = [path]
+ if (SharedData.recordVuex) {
+ addMutation(`Unregister module: ${path.join('/')}`, {
+ path
+ }, {
+ unregisterModule: true
+ })
+ }
+
+ delete registeredModules[path.join('/')]
+
+ origUnregisterModule(path)
+ }
+
bridge.send('vuex:init')
// deal with multiple backend injections
@@ -42,13 +86,19 @@ export function initVuexBackend (hook, bridge, isLegacy) {
hook.on('vuex:mutation', ({ type, payload }) => {
if (!SharedData.recordVuex) return
+ addMutation(type, payload)
+ })
+
+ function addMutation (type, payload, options = {}) {
const index = mutations.length
mutations.push({
type,
payload,
index,
- handlers: store._mutations[type]
+ handlers: store._mutations[type],
+ registeredModules: Object.keys(registeredModules),
+ ...options
})
bridge.send('vuex:mutation', {
@@ -59,17 +109,18 @@ export function initVuexBackend (hook, bridge, isLegacy) {
},
timestamp: Date.now()
})
- })
+ }
// devtool -> application
bridge.on('vuex:travel-to-state', ({ index, apply }) => {
- const stateSnapshot = replayMutations(index)
- const state = parse(stateSnapshot, true)
+ const snapshot = replayMutations(index)
+ const { state } = parse(snapshot, true)
bridge.send('vuex:inspected-state', {
index,
- snapshot: getSnapshot(stateSnapshot)
+ snapshot
})
if (apply) {
+ ensureRegisteredModules(mutations[index])
hook.emit('vuex:travel-to-state', state)
}
})
@@ -93,6 +144,7 @@ export function initVuexBackend (hook, bridge, isLegacy) {
bridge.on('vuex:revert', index => {
resetSnapshotCache()
+ ensureRegisteredModules(mutations[index - 1])
mutations = mutations.slice(0, index)
})
@@ -108,17 +160,21 @@ export function initVuexBackend (hook, bridge, isLegacy) {
snapshot: getSnapshot(baseStateSnapshot)
})
} else {
- const stateSnapshot = replayMutations(index)
+ const snapshot = replayMutations(index)
bridge.send('vuex:inspected-state', {
index,
- snapshot: getSnapshot(stateSnapshot)
+ snapshot
})
}
})
function replayMutations (index) {
+ const originalVm = store._vm
store._vm = snapshotsVm
+ let tempRemovedModules = []
+ let tempAddedModules = []
+
// Get most recent snapshot for target index
// for faster replay
let stateSnapshot
@@ -137,7 +193,18 @@ export function initVuexBackend (hook, bridge, isLegacy) {
if (stateSnapshot.index === index) {
resultState = stateSnapshot.state
} else {
+ const startMutation = mutations[stateSnapshot.index]
+ if (startMutation) {
+ tempRemovedModules = Object.keys(registeredModules).filter(m => !startMutation.registeredModules.includes(m))
+ } else {
+ tempRemovedModules = Object.keys(registeredModules)
+ }
+ tempRemovedModules.forEach(m => {
+ origUnregisterModule(m.split('/'))
+ })
+
const state = parse(stateSnapshot.state, true)
+ updateSnapshotsVm(state)
store.replaceState(state)
SharedData.snapshotLoading = true
@@ -145,7 +212,19 @@ export function initVuexBackend (hook, bridge, isLegacy) {
// Replay mutations
for (let i = stateSnapshot.index + 1; i <= index; i++) {
const mutation = mutations[i]
- if (mutation.handlers) {
+ if (mutation.registerModule) {
+ const { path, module, options } = mutation.payload
+ tempAddedModules.push(path.join('/'))
+ origRegisterModule(path, module, options)
+ updateSnapshotsVm(store.state)
+ } else if (mutation.unregisterModule && get(store.state, mutation.payload.path.join('.')) != null) {
+ const path = mutation.payload.path
+ const index = tempAddedModules.indexOf(path.join('/'))
+ if (index !== -1) tempAddedModules.splice(index, 1)
+ origUnregisterModule(path)
+ updateSnapshotsVm(store.state)
+ } else if (mutation.handlers) {
+ store._committing = true
if (Array.isArray(mutation.handlers)) {
mutation.handlers.forEach(handler => handler(mutation.payload))
} else {
@@ -156,10 +235,11 @@ export function initVuexBackend (hook, bridge, isLegacy) {
mutation.handlers(mutation.payload)
}
}
+ store._committing = false
}
if (i !== index && i % SharedData.cacheVuexSnapshotsEvery === 0) {
- takeStateSnapshot(i, state)
+ takeStateSnapshot(i)
}
}
@@ -169,10 +249,22 @@ export function initVuexBackend (hook, bridge, isLegacy) {
lastState = resultState
+ const result = stringify({
+ state: store.state,
+ getters: store.getters || {}
+ })
+
// Restore user state
+ tempAddedModules.forEach(m => {
+ origUnregisterModule(m.split('/'))
+ })
+ tempRemovedModules.forEach(m => {
+ const { path, module, options } = registeredModules[m]
+ origRegisterModule(path, module, options)
+ })
store._vm = originalVm
- return resultState
+ return result
}
bridge.on('vuex:edit-state', ({ index, value, path }) => {
@@ -201,7 +293,9 @@ export function initVuexBackend (hook, bridge, isLegacy) {
}
function getSnapshot (stateSnapshot = null) {
+ let originalVm
if (stateSnapshot) {
+ originalVm = store._vm
store._vm = snapshotsVm
store.replaceState(parse(stateSnapshot, true))
}
@@ -218,6 +312,28 @@ export function initVuexBackend (hook, bridge, isLegacy) {
return result
}
+
+ function ensureRegisteredModules (mutation) {
+ if (mutation) {
+ mutation.registeredModules.forEach(m => {
+ if (!Object.keys(registeredModules).includes(m)) {
+ const data = allTimeModules[m]
+ if (data) {
+ const { path, module, options } = data
+ origRegisterModule(path, module, options)
+ registeredModules[path.join('/')] = data
+ }
+ }
+ })
+ Object.keys(registeredModules).forEach(m => {
+ if (!mutation.registeredModules.includes(m)) {
+ origUnregisterModule(m.split('/'))
+ delete registeredModules[m]
+ }
+ })
+ updateSnapshotsVm(store.state)
+ }
+ }
}
export function getCustomStoreDetails (store) {
diff --git a/src/devtools/views/vuex/VuexStateInspector.vue b/src/devtools/views/vuex/VuexStateInspector.vue
index 79e01482f..22bd893cc 100644
--- a/src/devtools/views/vuex/VuexStateInspector.vue
+++ b/src/devtools/views/vuex/VuexStateInspector.vue
@@ -171,7 +171,8 @@ export default {
},
isOnlyMutationPayload () {
- return Object.keys(this.inspectedState).length === 1 && this.inspectedState.mutation
+ return (Object.keys(this.inspectedState).length === 1 && this.inspectedState.mutation) ||
+ Object.keys(this.inspectedState).length < 1
},
isActive () {
From 6c23944546f90b99854beb58008a199d826828ae Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Thu, 21 Mar 2019 03:22:24 +0100
Subject: [PATCH 0046/1371] feat(vuex): catch early modules before vuex backend
is ready
---
shells/dev/target/Counter.vue | 15 ++++++++++++++-
src/backend/hook.js | 21 +++++++++++++++++++++
src/backend/vuex.js | 35 +++++++++++++++++++++++------------
3 files changed, 58 insertions(+), 13 deletions(-)
diff --git a/shells/dev/target/Counter.vue b/shells/dev/target/Counter.vue
index 5247615d7..a7e495996 100644
--- a/shells/dev/target/Counter.vue
+++ b/shells/dev/target/Counter.vue
@@ -28,6 +28,8 @@
+
+ {{ $store.state.instant }}
@@ -41,6 +43,17 @@ export default {
this.$firebaseRefs = {
hello: 'world'
}
+
+ this.$store.registerModule('instant', {
+ namespaced: true,
+ state: () => ({
+ hey: 'hi'
+ }),
+ getters: {
+ ho: state => state.hey + ' ho'
+ }
+ })
+ console.log('registered instant')
},
computed: {
test () { return 1 },
@@ -55,7 +68,7 @@ export default {
...mapGetters('nested', [
'twoFoos'
- ])
+ ]),
},
watch: {
count (value) {
diff --git a/src/backend/hook.js b/src/backend/hook.js
index 988e582e1..a51fd07ed 100644
--- a/src/backend/hook.js
+++ b/src/backend/hook.js
@@ -102,6 +102,27 @@ export function installHook (target) {
state: clone(store.state),
getters: store.getters
}
+ // Dynamic modules
+ hook.storeModules = []
+ const origRegister = store.registerModule.bind(store)
+ store.registerModule = (path, module, options) => {
+ if (typeof path === 'string') path = [path]
+ hook.storeModules.push({ path, module, options })
+ origRegister(path, module, options)
+ }
+ const origUnregister = store.unregisterModule.bind(store)
+ store.unregisterModule = (path) => {
+ if (typeof path === 'string') path = [path]
+ const key = path.join('/')
+ const index = hook.storeModules.findIndex(m => m.path.join('/') === key)
+ if (index !== -1) hook.storeModules.splice(0, 1)
+ origUnregister(path)
+ }
+ hook.flushStoreModules = () => {
+ store.registerModule = origRegister
+ store.unregisterModule = origUnregister
+ return hook.storeModules
+ }
})
Object.defineProperty(target, '__VUE_DEVTOOLS_GLOBAL_HOOK__', {
diff --git a/src/backend/vuex.js b/src/backend/vuex.js
index 17f47281f..63b8b3f4e 100644
--- a/src/backend/vuex.js
+++ b/src/backend/vuex.js
@@ -38,11 +38,20 @@ export function initVuexBackend (hook, bridge, isLegacy) {
reset()
- const origRegisterModule = store.registerModule.bind(store)
- store.registerModule = (path, module, options) => {
+ const earlyModules = hook.flushStoreModules()
+
+ function addModule (path, module, options) {
if (typeof path === 'string') path = [path]
+
+ const key = path.join('/')
+ registeredModules[key] = allTimeModules[key] = {
+ path,
+ module,
+ options
+ }
+
if (SharedData.recordVuex) {
- addMutation(`Register module: ${path.join('/')}`, {
+ addMutation(`Register module: ${key}`, {
path,
module,
options
@@ -50,20 +59,20 @@ export function initVuexBackend (hook, bridge, isLegacy) {
registerModule: true
})
}
+ }
- const key = path.join('/')
- registeredModules[key] = allTimeModules[key] = {
- path,
- module,
- options
- }
-
+ const origRegisterModule = store.registerModule.bind(store)
+ store.registerModule = (path, module, options) => {
+ addModule(path, module, options)
origRegisterModule(path, module, options)
}
const origUnregisterModule = store.unregisterModule.bind(store)
store.unregisterModule = (path) => {
if (typeof path === 'string') path = [path]
+
+ delete registeredModules[path.join('/')]
+
if (SharedData.recordVuex) {
addMutation(`Unregister module: ${path.join('/')}`, {
path
@@ -72,13 +81,15 @@ export function initVuexBackend (hook, bridge, isLegacy) {
})
}
- delete registeredModules[path.join('/')]
-
origUnregisterModule(path)
}
bridge.send('vuex:init')
+ earlyModules.forEach(({ path, module, options }) => {
+ addModule(path, module, options)
+ })
+
// deal with multiple backend injections
hook.off('vuex:mutation')
From e085cd8d8a63dfff21fa727ba440e091d24b07f5 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Thu, 21 Mar 2019 03:41:07 +0100
Subject: [PATCH 0047/1371] fix(vuex): base state with early registered modules
not loading
---
src/backend/vuex.js | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/backend/vuex.js b/src/backend/vuex.js
index 63b8b3f4e..4d7965824 100644
--- a/src/backend/vuex.js
+++ b/src/backend/vuex.js
@@ -16,14 +16,25 @@ export function initVuexBackend (hook, bridge, isLegacy) {
})
}
- updateSnapshotsVm()
-
const getStateSnapshot = (_store = store) => stringify(_store.state)
let baseStateSnapshot, stateSnapshots, mutations, lastState
let registeredModules = {}
let allTimeModules = {}
+ const earlyModules = hook.flushStoreModules()
+
+ // Init additional state
+ earlyModules.forEach(({ path, module, options }) => {
+ if (!options || options.preserveState !== true) {
+ const state = typeof module.state === 'function' ? module.state() : module.state
+ const parentState = path.length === 1 ? hook.initialStore.state : get(hook.initialStore.state, path.slice(0, -1))
+ set(parentState, path[path.length - 1], state)
+ }
+ })
+
+ updateSnapshotsVm()
+
function reset () {
baseStateSnapshot = getStateSnapshot(hook.initialStore)
mutations = []
@@ -38,8 +49,6 @@ export function initVuexBackend (hook, bridge, isLegacy) {
reset()
- const earlyModules = hook.flushStoreModules()
-
function addModule (path, module, options) {
if (typeof path === 'string') path = [path]
From 0fb2dacf4072fa7436c4eab383eb4a725fb27f04 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Thu, 21 Mar 2019 04:01:42 +0100
Subject: [PATCH 0048/1371] fix(vuex): import state
---
src/backend/vuex.js | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/backend/vuex.js b/src/backend/vuex.js
index 4d7965824..3881241ce 100644
--- a/src/backend/vuex.js
+++ b/src/backend/vuex.js
@@ -169,8 +169,15 @@ export function initVuexBackend (hook, bridge, isLegacy) {
})
bridge.on('vuex:import-state', state => {
- hook.emit('vuex:travel-to-state', parse(state, true))
+ const parsed = parse(state, true)
+ hook.initialStore.state = parsed
+ reset()
+ hook.emit('vuex:travel-to-state', parsed)
bridge.send('vuex:init')
+ bridge.send('vuex:inspected-state', {
+ index: -1,
+ snapshot: getSnapshot(baseStateSnapshot)
+ })
})
bridge.on('vuex:inspect-state', index => {
From dd67b4d43f3369dfaddd0ad7d707fddf5a09ea39 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Thu, 21 Mar 2019 04:01:50 +0100
Subject: [PATCH 0049/1371] test(e2e): fix
---
cypress/integration/vuex-tab.js | 41 ++++++++++++++++---------------
shells/dev/target/NativeTypes.vue | 4 +--
2 files changed, 23 insertions(+), 22 deletions(-)
diff --git a/cypress/integration/vuex-tab.js b/cypress/integration/vuex-tab.js
index a40b7ee76..a52f5d833 100644
--- a/cypress/integration/vuex-tab.js
+++ b/cypress/integration/vuex-tab.js
@@ -10,7 +10,7 @@ suite('vuex tab', () => {
get('#counter p').contains('1')
})
cy.get('.vuex-tab').click()
- cy.get('.history .entry').should('have.length', 5)
+ cy.get('.history .entry').should('have.length', 6)
cy.get('[data-id="load-vuex-state"]').click()
cy.get('.recording-vuex-state').should('not.be.visible')
cy.get('.loading-vuex-state').should('not.be.visible')
@@ -18,7 +18,7 @@ suite('vuex tab', () => {
expect(el.text()).to.include('type:"DECREMENT"')
expect(el.text()).to.include('count:1')
})
- cy.get('.history .entry').eq(4).should('have.class', 'inspected').should('have.class', 'active')
+ cy.get('.history .entry').eq(5).should('have.class', 'inspected').should('have.class', 'active')
})
it('should filter state & getters', () => {
@@ -41,7 +41,7 @@ suite('vuex tab', () => {
cy.get('.history .entry[data-active="true"].active').should('have.length', 0)
cy.get('.left .search input').clear().type('/dec)/i')
- cy.get('.history .entry[data-active="true"]').should('have.length', 4)
+ cy.get('.history .entry[data-active="true"]').should('have.length', 5)
cy.get('.history .entry[data-active="true"].inspected').should('have.length', 0)
cy.get('.history .entry[data-active="true"].active').should('have.length', 1)
@@ -70,19 +70,19 @@ suite('vuex tab', () => {
})
it('should time-travel', () => {
- cy.get('.history .entry[data-index="3"] .entry-actions .action-time-travel').click({ force: true })
- cy.get('.history .entry[data-index="3"]')
+ cy.get('.history .entry[data-index="4"] .entry-actions .action-time-travel').click({ force: true })
+ cy.get('.history .entry[data-index="4"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('2')
})
- cy.get('.history .entry[data-index="2"] .mutation-type').click({ force: true })
- cy.get('.history .entry[data-index="2"]')
+ cy.get('.history .entry[data-index="3"] .mutation-type').click({ force: true })
+ cy.get('.history .entry[data-index="3"]')
.should('have.class', 'inspected')
.should('not.have.class', 'active')
- cy.get('.history .entry[data-index="3"]')
+ cy.get('.history .entry[data-index="4"]')
.should('not.have.class', 'inspected')
.should('have.class', 'active')
cy.get('.recording-vuex-state').should('not.be.visible')
@@ -95,11 +95,11 @@ suite('vuex tab', () => {
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('2')
})
- cy.get('.history .entry[data-index="2"] .entry-actions .action-time-travel').click({ force: true })
- cy.get('.history .entry[data-index="2"]')
+ cy.get('.history .entry[data-index="3"] .entry-actions .action-time-travel').click({ force: true })
+ cy.get('.history .entry[data-index="3"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
- cy.get('.history .entry[data-index="3"]')
+ cy.get('.history .entry[data-index="4"]')
.should('not.have.class', 'inspected')
.should('not.have.class', 'active')
cy.get('#target').iframe().then(({ get }) => {
@@ -117,8 +117,8 @@ suite('vuex tab', () => {
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('1')
})
- cy.get('.history .entry[data-index="1"] .entry-actions .action-time-travel').click({ force: true })
- cy.get('.history .entry[data-index="1"]')
+ cy.get('.history .entry[data-index="2"] .entry-actions .action-time-travel').click({ force: true })
+ cy.get('.history .entry[data-index="2"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
cy.get('#target').iframe().then(({ get }) => {
@@ -127,10 +127,10 @@ suite('vuex tab', () => {
})
it('should revert', () => {
- cy.get('.history .entry[data-index="4"] .mutation-type').click({ force: true })
- cy.get('.history .entry[data-index="4"]').find('.action-revert').click({ force: true })
- cy.get('.history .entry[data-active="true"]').should('have.length', 4)
- cy.get('.history .entry[data-index="3"]')
+ cy.get('.history .entry[data-index="5"] .mutation-type').click({ force: true })
+ cy.get('.history .entry[data-index="5"]').find('.action-revert').click({ force: true })
+ cy.get('.history .entry[data-active="true"]').should('have.length', 5)
+ cy.get('.history .entry[data-index="4"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
cy.get('.vuex-state-inspector').then(el => {
@@ -142,7 +142,7 @@ suite('vuex tab', () => {
})
it('should commit', () => {
- cy.get('.history .entry[data-index="3"] .action-commit').click({ force: true })
+ cy.get('.history .entry[data-index="4"] .action-commit').click({ force: true })
cy.get('.history .entry[data-active="true"]').should('have.length', 1)
cy.get('.history .entry[data-index="0"]')
.should('have.class', 'inspected')
@@ -185,7 +185,7 @@ suite('vuex tab', () => {
cy.get('#target').iframe().then(({ get }) => {
get('.increment').click({ force: true })
})
- cy.get('.history .entry').should('have.length', 5)
+ cy.get('.history .entry[data-active="true"]').should('have.length', 4)
})
it('should copy vuex state', () => {
@@ -200,9 +200,10 @@ suite('vuex tab', () => {
cy.get('.import-state').should('be.visible')
cy.get('.import-state textarea').clear().type('{{}invalid: json}')
cy.get('.message.invalid-json').should('be.visible')
- cy.get('.import-state textarea').clear().type('{{}"count":42,"date":"[native Date Fri Dec 22 2017 10:12:04 GMT+0100 (CET)]","nested":{{}"foo":"meow"}}')
+ cy.get('.import-state textarea').clear().type('{{}"count":42,"date":"[native Date Fri Dec 22 2017 10:12:04 GMT+0100 (CET)]","nested":{{}"foo":"meow"},"instant":{{}"hey":"hi"}}')
cy.wait(500)
cy.get('.message.invalid-json').should('not.be.visible')
+ cy.wait(500)
cy.get('.vuex-state-inspector').then(el => {
expect(el.text()).to.include('count:42')
expect(el.text()).to.include('date:' + new Date('Fri Dec 22 2017 10:12:04 GMT+0100 (CET)'))
diff --git a/shells/dev/target/NativeTypes.vue b/shells/dev/target/NativeTypes.vue
index 6e82de4c7..bcdf3e32b 100644
--- a/shells/dev/target/NativeTypes.vue
+++ b/shells/dev/target/NativeTypes.vue
@@ -155,11 +155,11 @@ export default {
},
setDisplay () {
- return Array.from(this.set)
+ if (this.set) return Array.from(this.set)
},
mapDisplay () {
- return [...this.map]
+ if (this.map) return [...this.map]
},
forceRefresh () {
From 426820b8f988f0bdbc12c826434d791f0df52a03 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Thu, 21 Mar 2019 04:08:22 +0100
Subject: [PATCH 0050/1371] test(e2e): fix
---
cypress/integration/vuex-edit.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/cypress/integration/vuex-edit.js b/cypress/integration/vuex-edit.js
index 86a53e472..1f3bc21ed 100644
--- a/cypress/integration/vuex-edit.js
+++ b/cypress/integration/vuex-edit.js
@@ -76,7 +76,7 @@ suite('vuex edit', () => {
it('should edit state nested field', () => {
// using the decrease button
- cy.get('.data-field > .children > .data-field').eq(3)
+ cy.get('.data-field > .children > .data-field').eq(4)
.find('.actions .vue-ui-button').eq(1)
.click({ force: true })
.click({ force: true })
@@ -87,7 +87,7 @@ suite('vuex edit', () => {
})
// using the increase button
- cy.get('.data-field > .children > .data-field').eq(3)
+ cy.get('.data-field > .children > .data-field').eq(4)
.find('.actions .vue-ui-button').eq(2)
.click({ force: true })
.click({ force: true })
@@ -98,7 +98,7 @@ suite('vuex edit', () => {
})
// using the input
- cy.get('.data-field > .children > .data-field').eq(3)
+ cy.get('.data-field > .children > .data-field').eq(4)
.find('.actions .vue-ui-button').eq(0).click({ force: true })
cy.get('.edit-input').eq(1).type('12')
cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()
From a31437eec562fa59cc81ed964ebc82102d95abdb Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Thu, 21 Mar 2019 04:20:05 +0100
Subject: [PATCH 0051/1371] chore: v5.0.0-beta.5
---
package.json | 4 ++--
shells/chrome/manifest.json | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/package.json b/package.json
index 41d6e30f9..ea0b2c6ee 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "vue-devtools",
- "version": "5.0.0-beta.4",
+ "version": "5.0.0-beta.5",
"description": "devtools for Vue.js!",
"main": "index.js",
"scripts": {
@@ -85,4 +85,4 @@
"engines": {
"node": ">=8.10"
}
-}
+}
\ No newline at end of file
diff --git a/shells/chrome/manifest.json b/shells/chrome/manifest.json
index 5d64aba2f..0582600d8 100644
--- a/shells/chrome/manifest.json
+++ b/shells/chrome/manifest.json
@@ -1,7 +1,7 @@
{
"name": "Vue.js devtools",
- "version": "5.0.0.4",
- "version_name": "5.0.0 beta 4",
+ "version": "5.0.0.5",
+ "version_name": "5.0.0 beta 5",
"description": "Chrome and Firefox DevTools extension for debugging Vue.js applications.",
"manifest_version": 2,
"icons": {
From 65018e72399377403e513cacdf8c34560f090ae1 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Thu, 21 Mar 2019 04:37:37 +0100
Subject: [PATCH 0052/1371] chore: @vue/devtools 5.0.0-beta.5
---
shells/electron/package.json | 2 +-
shells/electron/yarn.lock | 7 ++++---
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/shells/electron/package.json b/shells/electron/package.json
index 90d3b9202..fbd2b6622 100644
--- a/shells/electron/package.json
+++ b/shells/electron/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/devtools",
- "version": "5.0.0-beta.3",
+ "version": "5.0.0-beta.5",
"description": "StandAlone vue-devtools",
"repository": {
"url": "https://github.com/vuejs/vue-devtools.git",
diff --git a/shells/electron/yarn.lock b/shells/electron/yarn.lock
index 014eb312b..f7482b223 100644
--- a/shells/electron/yarn.lock
+++ b/shells/electron/yarn.lock
@@ -967,9 +967,10 @@ electron-download@^3.0.1:
semver "^5.3.0"
sumchecker "^1.2.0"
-electron@1.7.11:
- version "1.7.11"
- resolved "https://registry.yarnpkg.com/electron/-/electron-1.7.11.tgz#993b6aa79e0e79a7cfcc369f4c813fbd9a0b08d9"
+electron@1.7.16:
+ version "1.7.16"
+ resolved "https://registry.yarnpkg.com/electron/-/electron-1.7.16.tgz#40e6b5553e3a87ce7ea52eb05f7926bc40cab11c"
+ integrity sha512-k17+2K3hny6g1etWMkWvzEL06yapZGpqG66O3e5vxfzeHT3FgqrNa3xRF7znjaqvWQmmEdGFdSktQADjUZ0gog==
dependencies:
"@types/node" "^7.0.18"
electron-download "^3.0.1"
From 26b2ed7cf363cc28d880688bc0b1ddc10fe35125 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Sat, 23 Mar 2019 19:48:26 +0100
Subject: [PATCH 0053/1371] chore: no prop example
---
shells/dev/target/Counter.vue | 58 +++++++++++++++++++++++++++++------
shells/dev/target/NoProp.vue | 13 ++++++++
2 files changed, 61 insertions(+), 10 deletions(-)
create mode 100644 shells/dev/target/NoProp.vue
diff --git a/shells/dev/target/Counter.vue b/shells/dev/target/Counter.vue
index a7e495996..0129906d6 100644
--- a/shells/dev/target/Counter.vue
+++ b/shells/dev/target/Counter.vue
@@ -1,12 +1,24 @@
{{ count }}
-
-
+
+
-
+
Your counter is {{ $store.getters.isPositive ? 'positive' : 'negative' }}
@@ -15,8 +27,12 @@
foo: {{ foo }}
twoFoos: {{ twoFoos }}
-
-
+
+
@@ -24,20 +40,42 @@
{{ $store.state.dynamic }}
{{ $store.getters }}
-
-
-
+
+
+
{{ $store.state.instant }}
+
+
From 0243b14e30db91b04d4684aa0ddbde1a127ced7c Mon Sep 17 00:00:00 2001
From: Gavrilov Evgeniy
Date: Sat, 23 Mar 2019 21:48:44 +0300
Subject: [PATCH 0054/1371] fix: error on edit data of component without
properties in devtools (#907)
---
src/util.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/util.js b/src/util.js
index 9da143a91..fe16fae5a 100644
--- a/src/util.js
+++ b/src/util.js
@@ -524,6 +524,10 @@ export function get (object, path) {
}
export function has (object, path, parent = false) {
+ if (typeof object === 'undefined') {
+ return false
+ }
+
const sections = path.split('.')
const size = !parent ? 1 : 2
while (sections.length > size) {
From 29c786f388ccd5d3ce731328cb243bb8b425c12c Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Sat, 23 Mar 2019 20:10:37 +0100
Subject: [PATCH 0055/1371] chore: dev:chrome & dev:chrome:prod
---
package.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/package.json b/package.json
index ea0b2c6ee..5c3bf8698 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,8 @@
"scripts": {
"dev": "cross-env PORT=8100 npm run dev:shell",
"dev:shell": "cd shells/dev && webpack-dev-server --inline --hot --no-info",
- "dev:chrome": "cd shells/chrome && cross-env NODE_ENV=production webpack --watch --hide-modules",
+ "dev:chrome": "cd shells/chrome && webpack --watch --hide-modules",
+ "dev:chrome:prod": "cd shells/chrome && cross-env NODE_ENV=production webpack --watch --hide-modules",
"lint": "eslint src --ext=js,vue && eslint shells/chrome/src && eslint shells/dev/src && eslint shells/electron/src",
"build": "cd shells/chrome && cross-env NODE_ENV=production webpack --progress --hide-modules",
"run:firefox": "web-ext run -s shells/chrome -a dist -i src",
From de13143c621ac7a4c0de70ae55cae705b2088582 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Sat, 23 Mar 2019 20:11:38 +0100
Subject: [PATCH 0056/1371] fix: detect polyfilled Map & Set objects, closes
#744
---
src/util.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/util.js b/src/util.js
index fe16fae5a..9f33ac5f8 100644
--- a/src/util.js
+++ b/src/util.js
@@ -131,9 +131,9 @@ function replacer (key) {
} else if (type === 'symbol') {
return `[native Symbol ${Symbol.prototype.toString.call(val)}]`
} else if (val !== null && type === 'object') {
- if (val instanceof Map) {
+ if (val instanceof Map || val.toString() === '[object Map]') {
return encodeCache.cache(val, () => getCustomMapDetails(val))
- } else if (val instanceof Set) {
+ } else if (val instanceof Set || val.toString() === '[object Set]') {
return encodeCache.cache(val, () => getCustomSetDetails(val))
} else if (val instanceof RegExp) {
// special handling of native type
From 8f68d50d2a6a78f12582162ab9a0eadcf2269101 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Sat, 23 Mar 2019 20:13:08 +0100
Subject: [PATCH 0057/1371] test(e2e): fix
---
cypress/integration/components-tab.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cypress/integration/components-tab.js b/cypress/integration/components-tab.js
index 0cfafedcd..88b51a0fa 100644
--- a/cypress/integration/components-tab.js
+++ b/cypress/integration/components-tab.js
@@ -92,7 +92,7 @@ suite('components tab', () => {
it('should filter components', () => {
cy.get('.left .search input').clear().type('counter')
- cy.get('.instance').should('have.length', 1)
+ cy.get('.instance').should('have.length', 2)
cy.get('.left .search input').clear().type('target')
cy.get('.instance').should('have.length', 12)
cy.get('.left .search input').clear()
From a9b658b37ce3cd77b4615775f4686df7ab2456c8 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Sun, 24 Mar 2019 19:15:19 +0100
Subject: [PATCH 0058/1371] fix: maximum JSON serialization size to prevent
message being too long
---
shells/dev/target/NativeTypes.vue | 26 +++++++++++-----
src/bridge.js | 51 +++++++++++++++++++++++++++----
src/transfer.js | 19 ++++++++++--
3 files changed, 80 insertions(+), 16 deletions(-)
diff --git a/shells/dev/target/NativeTypes.vue b/shells/dev/target/NativeTypes.vue
index bcdf3e32b..dd0cee9c0 100644
--- a/shells/dev/target/NativeTypes.vue
+++ b/shells/dev/target/NativeTypes.vue
@@ -3,7 +3,9 @@
Date: {{ date.toString() }} - Hours: {{ hours }} - Prototype: {{ date | prototypeString }}
-
+
@@ -11,7 +13,9 @@
-
+
@@ -105,9 +115,9 @@ export default {
'html key': (h, t, m, l) => {},
proxy1,
sym: Symbol('test'),
- multiLineParameterFunction: function(a,
- b,
- c) {},
+ multiLineParameterFunction: function (a,
+ b,
+ c) {},
veryLongText
}
},
diff --git a/src/bridge.js b/src/bridge.js
index 4662f034d..f6fb0dbef 100644
--- a/src/bridge.js
+++ b/src/bridge.js
@@ -14,7 +14,10 @@ export default class Bridge extends EventEmitter {
this._emit(messages)
}
})
- this._queue = []
+ this._batchingQueue = []
+ this._sendingQueue = []
+ this._receivingQueue = []
+ this._sending = false
this._time = null
}
@@ -26,11 +29,21 @@ export default class Bridge extends EventEmitter {
*/
send (event, payload) {
- if (this._time === null) {
- this.wall.send([{ event, payload }])
+ if (Array.isArray(payload)) {
+ const lastIndex = payload.length - 1
+ payload.forEach((chunk, index) => {
+ this._send({
+ event,
+ _chunk: chunk,
+ last: index === lastIndex
+ })
+ })
+ this._flush()
+ } else if (this._time === null) {
+ this._send([{ event, payload }])
this._time = Date.now()
} else {
- this._queue.push({
+ this._batchingQueue.push({
event,
payload
})
@@ -55,17 +68,43 @@ export default class Bridge extends EventEmitter {
}
_flush () {
- if (this._queue.length) this.wall.send(this._queue)
+ if (this._batchingQueue.length) this._send(this._batchingQueue)
clearTimeout(this._timer)
- this._queue = []
+ this._batchingQueue = []
this._time = null
}
_emit (message) {
if (typeof message === 'string') {
this.emit(message)
+ } else if (message._chunk) {
+ this._receivingQueue.push(message._chunk)
+ if (message.last) {
+ this.emit(message.event, this._receivingQueue)
+ this._receivingQueue = []
+ }
} else {
this.emit(message.event, message.payload)
}
}
+
+ _send (messages) {
+ this._sendingQueue.push(messages)
+ this._nextSend()
+ }
+
+ _nextSend () {
+ if (!this._sendingQueue.length || this._sending) return
+ this._sending = true
+ const messages = this._sendingQueue.shift()
+ try {
+ this.wall.send(messages)
+ } catch (err) {
+ if (err.message === 'Message length exceeded maximum allowed length.') {
+ this._sendingQueue.splice(0, 0, messages.map(message => [message]))
+ }
+ }
+ this._sending = false
+ requestAnimationFrame(() => this._nextSend())
+ }
}
diff --git a/src/transfer.js b/src/transfer.js
index 6ece679f6..0b86e15bd 100644
--- a/src/transfer.js
+++ b/src/transfer.js
@@ -1,3 +1,5 @@
+const MAX_SERIALIZED_SIZE = 512 * 1024 // 1MB
+
function encode (data, replacer, list, seen) {
var stored, key, value, i, l
var seenIndex = seen.get(data)
@@ -57,16 +59,29 @@ function decode (list, reviver) {
}
export function stringify (data, replacer, space) {
+ let result
try {
- return arguments.length === 1
+ result = arguments.length === 1
? JSON.stringify(data)
: JSON.stringify(data, replacer, space)
} catch (e) {
- return stringifyStrict(data, replacer, space)
+ result = stringifyStrict(data, replacer, space)
}
+ if (result.length > MAX_SERIALIZED_SIZE) {
+ const chunkCount = Math.ceil(result.length / MAX_SERIALIZED_SIZE)
+ const chunks = []
+ for (let i = 0; i < chunkCount; i++) {
+ chunks.push(result.slice(i * MAX_SERIALIZED_SIZE, (i + 1) * MAX_SERIALIZED_SIZE))
+ }
+ return chunks
+ }
+ return result
}
export function parse (data, reviver) {
+ if (Array.isArray(data)) {
+ data = data.join('')
+ }
var hasCircular = /^\s/.test(data)
if (!hasCircular) {
return arguments.length === 1
From 57ec2ab62ad8ad64784f68bd1902020d92b01b57 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Sun, 24 Mar 2019 19:25:38 +0100
Subject: [PATCH 0059/1371] fix(perf): stringify replacer
---
src/util.js | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/src/util.js b/src/util.js
index 9f33ac5f8..80c7a4a4c 100644
--- a/src/util.js
+++ b/src/util.js
@@ -120,7 +120,15 @@ export function stringify (data) {
function replacer (key) {
const val = this[key]
const type = typeof val
- if (type === 'undefined') {
+ if (Array.isArray(val)) {
+ return val
+ } else if (typeof val === 'string') {
+ if (val.length > MAX_STRING_SIZE) {
+ return val.substr(0, MAX_STRING_SIZE) + `... (${(val.length)} total length)`
+ } else {
+ return val
+ }
+ } else if (type === 'undefined') {
return UNDEFINED
} else if (val === Infinity) {
return INFINITY
@@ -131,14 +139,15 @@ function replacer (key) {
} else if (type === 'symbol') {
return `[native Symbol ${Symbol.prototype.toString.call(val)}]`
} else if (val !== null && type === 'object') {
- if (val instanceof Map || val.toString() === '[object Map]') {
+ const proto = Object.prototype.toString.call(val)
+ if (proto === '[object Map]') {
return encodeCache.cache(val, () => getCustomMapDetails(val))
- } else if (val instanceof Set || val.toString() === '[object Set]') {
+ } else if (proto === '[object Set]') {
return encodeCache.cache(val, () => getCustomSetDetails(val))
- } else if (val instanceof RegExp) {
+ } else if (proto === '[object RegExp]') {
// special handling of native type
return `[native RegExp ${RegExp.prototype.toString.call(val)}]`
- } else if (val instanceof Date) {
+ } else if (proto === '[object Date]') {
return `[native Date ${Date.prototype.toString.call(val)}]`
} else if (val.state && val._vm) {
return encodeCache.cache(val, () => getCustomStoreDetails(val))
@@ -151,8 +160,6 @@ function replacer (key) {
}
} else if (Number.isNaN(val)) {
return NAN
- } else if (typeof val === 'string' && val.length > MAX_STRING_SIZE) {
- return val.substr(0, MAX_STRING_SIZE) + `... (${(val.length)} total length)`
}
return sanitize(val)
}
From 561a8928b75f21f9e3125cce35cdec9f731d7e77 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Sun, 24 Mar 2019 19:36:24 +0100
Subject: [PATCH 0060/1371] feat(perf): small encode gains
---
src/transfer.js | 20 ++++++++++----------
src/util.js | 3 +--
2 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/src/transfer.js b/src/transfer.js
index 0b86e15bd..1fcf8ab07 100644
--- a/src/transfer.js
+++ b/src/transfer.js
@@ -1,22 +1,22 @@
const MAX_SERIALIZED_SIZE = 512 * 1024 // 1MB
function encode (data, replacer, list, seen) {
- var stored, key, value, i, l
- var seenIndex = seen.get(data)
+ let stored, key, value, i, l
+ const seenIndex = seen.get(data)
if (seenIndex != null) {
return seenIndex
}
- var index = list.length
- var proto = Object.prototype.toString.call(data)
+ const index = list.length
+ const proto = Object.prototype.toString.call(data)
if (proto === '[object Object]') {
stored = {}
seen.set(data, index)
list.push(stored)
- var keys = Object.keys(data)
+ const keys = Object.keys(data)
for (i = 0, l = keys.length; i < l; i++) {
key = keys[i]
value = data[key]
- if (replacer) value = replacer.call(data, key, value)
+ if (replacer) value = replacer(key, value)
stored[key] = encode(value, replacer, list, seen)
}
} else if (proto === '[object Array]') {
@@ -25,7 +25,7 @@ function encode (data, replacer, list, seen) {
list.push(stored)
for (i = 0, l = data.length; i < l; i++) {
value = data[i]
- if (replacer) value = replacer.call(data, i, value)
+ if (replacer) value = replacer(i, value)
stored[i] = encode(value, replacer, list, seen)
}
} else {
@@ -35,13 +35,13 @@ function encode (data, replacer, list, seen) {
}
function decode (list, reviver) {
- var i = list.length
- var j, k, data, key, value, proto
+ let i = list.length
+ let j, k, data, key, value, proto
while (i--) {
data = list[i]
proto = Object.prototype.toString.call(data)
if (proto === '[object Object]') {
- var keys = Object.keys(data)
+ const keys = Object.keys(data)
for (j = 0, k = keys.length; j < k; j++) {
key = keys[j]
value = list[data[key]]
diff --git a/src/util.js b/src/util.js
index 80c7a4a4c..22e9384c5 100644
--- a/src/util.js
+++ b/src/util.js
@@ -117,8 +117,7 @@ export function stringify (data) {
return CircularJSON.stringify(data, replacer)
}
-function replacer (key) {
- const val = this[key]
+function replacer (key, val) {
const type = typeof val
if (Array.isArray(val)) {
return val
From b3baab5b0ef1625817a02154b5e52ba99b65e2dd Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Sun, 24 Mar 2019 20:07:02 +0100
Subject: [PATCH 0061/1371] feat(perf): limit size of arrays sent to devtools
---
src/devtools/components/DataField.vue | 36 +++++++++++++++++++--------
src/util.js | 9 +++++++
2 files changed, 35 insertions(+), 10 deletions(-)
diff --git a/src/devtools/components/DataField.vue b/src/devtools/components/DataField.vue
index bb035a478..72bdd6e79 100644
--- a/src/devtools/components/DataField.vue
+++ b/src/devtools/components/DataField.vue
@@ -172,7 +172,7 @@
class="children"
>
...
@@ -273,7 +274,7 @@ export default {
data () {
return {
contextMenuOpen: false,
- limit: Array.isArray(this.field.value) ? 10 : Infinity,
+ limit: 20,
expanded: this.depth === 0 && this.field.key !== '$route' && (subFieldCount(this.field.value) < 5)
}
},
@@ -309,7 +310,7 @@ export default {
} else {
return 'string'
}
- } else if (Array.isArray(value)) {
+ } else if (Array.isArray(value) || (value && value._isArray)) {
return 'array'
} else if (isPlainObject(value)) {
return 'plain-object'
@@ -367,7 +368,7 @@ export default {
}
},
- formattedSubFields () {
+ rawValue () {
let value = this.field.value
// CustomValue API
@@ -378,8 +379,17 @@ export default {
value = value._custom.value
}
+ if (value && value._isArray) {
+ value = value.items
+ }
+ return { value, inherit }
+ },
+
+ formattedSubFields () {
+ let { value, inherit } = this.rawValue
+
if (Array.isArray(value)) {
- value = value.map((item, i) => ({
+ return value.slice(0, this.limit).map((item, i) => ({
key: i,
value: item,
...inherit
@@ -394,11 +404,13 @@ export default {
value = sortByKey(value)
}
}
- return value
+
+ return value.slice(0, this.limit)
},
- limitedSubFields () {
- return this.formattedSubFields.slice(0, this.limit)
+ subFieldCount () {
+ const { value } = this.rawValue
+ return subFieldCount(value)
},
valueTooltip () {
@@ -505,6 +517,10 @@ export default {
this.$_contextMenuTimer = setTimeout(() => {
this.contextMenuOpen = false
}, 4000)
+ },
+
+ showMoreSubfields () {
+ this.limit += 20
}
}
}
diff --git a/src/util.js b/src/util.js
index 22e9384c5..92ee2c893 100644
--- a/src/util.js
+++ b/src/util.js
@@ -60,6 +60,7 @@ export const SPECIAL_TOKENS = {
}
export const MAX_STRING_SIZE = 10000
+export const MAX_ARRAY_SIZE = 5000
export function specialTokenToString (value) {
if (value === null) {
@@ -120,6 +121,14 @@ export function stringify (data) {
function replacer (key, val) {
const type = typeof val
if (Array.isArray(val)) {
+ const l = val.length
+ if (l > MAX_ARRAY_SIZE) {
+ return {
+ _isArray: true,
+ length: l,
+ items: val.slice(0, MAX_ARRAY_SIZE)
+ }
+ }
return val
} else if (typeof val === 'string') {
if (val.length > MAX_STRING_SIZE) {
From 3d11cabac416bae8678eccc86a15206b40db692e Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Sun, 24 Mar 2019 20:12:26 +0100
Subject: [PATCH 0062/1371] fix: better show more button style
---
src/devtools/components/DataField.vue | 24 +++++++++++-------------
1 file changed, 11 insertions(+), 13 deletions(-)
diff --git a/src/devtools/components/DataField.vue b/src/devtools/components/DataField.vue
index 72bdd6e79..7b2e73114 100644
--- a/src/devtools/components/DataField.vue
+++ b/src/devtools/components/DataField.vue
@@ -184,15 +184,14 @@
:force-collapse="forceCollapse"
:is-state-field="isStateField"
/>
-
- ...
-
+ />
>> .vue-ui-icon
+ width 16px
+ height @width
From 27e4d66e8ebcb0f012d6686091e199fe78e78f42 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Sun, 24 Mar 2019 20:14:43 +0100
Subject: [PATCH 0063/1371] chore(dev): display large array size
---
shells/dev/target/NativeTypes.vue | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/shells/dev/target/NativeTypes.vue b/shells/dev/target/NativeTypes.vue
index dd0cee9c0..d9cd126c0 100644
--- a/shells/dev/target/NativeTypes.vue
+++ b/shells/dev/target/NativeTypes.vue
@@ -24,6 +24,10 @@
+
+ Large array size: {{ largeArray.length }}
+
+
Set
{{ setDisplay() }}
From 0f88dfab42f9ab1b7b354147733412140fe199ac Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Sun, 24 Mar 2019 20:34:03 +0100
Subject: [PATCH 0064/1371] feat(perf): better select instance perceived per
and UX
---
src/devtools/views/components/ComponentInspector.vue | 8 ++++++++
src/devtools/views/components/ComponentInstance.vue | 10 ++++++++--
src/devtools/views/components/ComponentsTab.vue | 10 ++++++----
src/devtools/views/components/module.js | 9 +++++++++
4 files changed, 31 insertions(+), 6 deletions(-)
diff --git a/src/devtools/views/components/ComponentInspector.vue b/src/devtools/views/components/ComponentInspector.vue
index da747095f..3a18cb08c 100644
--- a/src/devtools/views/components/ComponentInspector.vue
+++ b/src/devtools/views/components/ComponentInspector.vue
@@ -16,6 +16,10 @@
placeholder="Filter inspected data"
>
+
+
diff --git a/src/devtools/views/vuex/VuexStateInspector.vue b/src/devtools/views/vuex/VuexStateInspector.vue
index 22bd893cc..4dd6a44bf 100644
--- a/src/devtools/views/vuex/VuexStateInspector.vue
+++ b/src/devtools/views/vuex/VuexStateInspector.vue
@@ -197,6 +197,17 @@ export default {
}
},
+ mounted () {
+ bridge.on('vuex:mutation', this.onMutation)
+ if (this.isOnlyMutationPayload && this.$shared.vuexAutoload) {
+ this.loadState()
+ }
+ },
+
+ destroyed () {
+ bridge.off('vuex:mutation', this.onMutation)
+ },
+
methods: {
...mapActions('vuex', [
'inspect'
@@ -241,7 +252,13 @@ export default {
loadState () {
const history = this.filteredHistory
this.inspect(history[history.length - 1])
- }
+ },
+
+ onMutation: debounce(function () {
+ if (this.$shared.vuexAutoload) {
+ this.loadState()
+ }
+ }, 800)
}
}
diff --git a/src/shared-data.js b/src/shared-data.js
index 58d476cd8..bfc0b6f67 100644
--- a/src/shared-data.js
+++ b/src/shared-data.js
@@ -12,7 +12,8 @@ const internalSharedData = {
snapshotLoading: false,
recordPerf: false,
editableProps: false,
- logDetected: true
+ logDetected: true,
+ vuexAutoload: false
}
const persisted = [
@@ -21,7 +22,8 @@ const persisted = [
'displayDensity',
'recordVuex',
'editableProps',
- 'logDetected'
+ 'logDetected',
+ 'vuexAutoload'
]
// ---- INTERNALS ---- //
diff --git a/yarn.lock b/yarn.lock
index cc3c798c6..10d69b0ed 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -51,15 +51,14 @@
source-map "~0.6.1"
vue-template-es2015-compiler "^1.9.0"
-"@vue/ui@^0.5.6":
- version "0.5.6"
- resolved "https://registry.yarnpkg.com/@vue/ui/-/ui-0.5.6.tgz#7c39b04a3b08ae3f217d2323987ae13347a90b29"
- integrity sha512-NmxJsFk8umipCbKLusz6L27DQIicpt8jLOjN1aLjyBIeX3t4H5RFshxH7IUtjDPtgOYGwwq7gy86bmFkDDBsIQ==
+"@vue/ui@^0.7.2":
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/@vue/ui/-/ui-0.7.2.tgz#c4501719267dd35e048a5be68aeea17f67ba47cd"
+ integrity sha512-LDhPgumKVeVSFpfLlrKOpbWPJB8pAacsBGEYLifOSNpEQhSZDTA7cMAfZ35GkXnrfUh3tovne0X+KyBewz3pOg==
dependencies:
- focus-visible "^4.1.4"
- material-design-icons "^3.0.1"
+ focus-visible "^4.1.5"
v-tooltip "^2.0.0-rc.33"
- vue-resize "^0.4.4"
+ vue-resize "^0.4.5"
"@webassemblyjs/ast@1.8.5":
version "1.8.5"
@@ -2445,7 +2444,7 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
-focus-visible@^4.1.4:
+focus-visible@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/focus-visible/-/focus-visible-4.1.5.tgz#50b44e2e84c24b831ceca3cce84d57c2b311c855"
integrity sha512-yo/njtk/BB4Z2euzaZe3CZrg4u5s5uEi7ZwbHBJS2quHx51N0mmcx9nTIiImUGlgy+vf26d0CcQluahBBBL/Fw==
@@ -3681,11 +3680,6 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
-material-design-icons@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/material-design-icons/-/material-design-icons-3.0.1.tgz#9a71c48747218ebca51e51a66da682038cdcb7bf"
- integrity sha1-mnHEh0chjrylHlGmbaaCA4zct78=
-
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@@ -6107,7 +6101,7 @@ vue-observe-visibility@^0.4.3:
resolved "https://registry.yarnpkg.com/vue-observe-visibility/-/vue-observe-visibility-0.4.3.tgz#b2694a83c94b274f566e03a497df51540e2daedc"
integrity sha512-YyyO3a5OUkgpmC0NEf+xWJR0jVdFWzVbKRDzUumOVMhfr3+jxXEycYNHCM3rEO5lcj3ZNJpDomZEYEx0Wqqh9A==
-vue-resize@^0.4.3, vue-resize@^0.4.4, vue-resize@^0.4.5:
+vue-resize@^0.4.3, vue-resize@^0.4.5:
version "0.4.5"
resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-0.4.5.tgz#4777a23042e3c05620d9cbda01c0b3cc5e32dcea"
integrity sha512-bhP7MlgJQ8TIkZJXAfDf78uJO+mEI3CaLABLjv0WNzr4CcGRGPIAItyWYnP6LsPA4Oq0WE+suidNs6dgpO4RHg==
From a54c55a2e8e8522f4b67a94d0c3d5fcb725593fa Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Mon, 25 Mar 2019 19:56:23 +0100
Subject: [PATCH 0071/1371] fix: setting label tweak
---
src/devtools/views/settings/GlobalPreferences.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/devtools/views/settings/GlobalPreferences.vue b/src/devtools/views/settings/GlobalPreferences.vue
index e54d0b0dd..d4eee50cf 100644
--- a/src/devtools/views/settings/GlobalPreferences.vue
+++ b/src/devtools/views/settings/GlobalPreferences.vue
@@ -67,7 +67,7 @@
icon="warning"
class="medium"
/>
- This may print warnings in the console
+ May print warnings in the console
From 702f07c11a3acd7bcfc09041cfa0cb6989642fb0 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Mon, 25 Mar 2019 20:19:18 +0100
Subject: [PATCH 0072/1371] chore: v5.0.0
---
package.json | 4 ++--
shells/chrome/manifest.json | 10 +++++-----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/package.json b/package.json
index 75ec2c36a..a5dc31e05 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "vue-devtools",
- "version": "5.0.0-beta.5",
+ "version": "5.0.0",
"description": "devtools for Vue.js!",
"main": "index.js",
"scripts": {
@@ -86,4 +86,4 @@
"engines": {
"node": ">=8.10"
}
-}
+}
\ No newline at end of file
diff --git a/shells/chrome/manifest.json b/shells/chrome/manifest.json
index 0582600d8..0a6db9a3b 100644
--- a/shells/chrome/manifest.json
+++ b/shells/chrome/manifest.json
@@ -1,13 +1,13 @@
{
"name": "Vue.js devtools",
- "version": "5.0.0.5",
- "version_name": "5.0.0 beta 5",
+ "version": "5.0.0",
+ "version_name": "5.0.0",
"description": "Chrome and Firefox DevTools extension for debugging Vue.js applications.",
"manifest_version": 2,
"icons": {
- "16": "icons/16-beta.png",
- "48": "icons/48-beta.png",
- "128": "icons/128-beta.png"
+ "16": "icons/16.png",
+ "48": "icons/48.png",
+ "128": "icons/128.png"
},
"browser_action": {
"default_icon": {
From 44e8b128f88422b550211056982c57ff31fa4de0 Mon Sep 17 00:00:00 2001
From: Guillaume Chau
Date: Mon, 25 Mar 2019 20:45:33 +0100
Subject: [PATCH 0073/1371] chore: update screenshots
---
media/screenshot-shadow.png | Bin 56893 -> 87407 bytes
media/screenshot.png | Bin 54687 -> 77485 bytes
2 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/media/screenshot-shadow.png b/media/screenshot-shadow.png
index d747569e0bdf8f6b08a094f7e3f376609e361c23..94ceb0d80f9b313faa2bb69633ba349ae4c8dec0 100644
GIT binary patch
literal 87407
zcmZs?b97x%w>O+LcH1P4lg4RmqYWF|c4OPNZQCcdZCfX{t?%@Cdhh+?{YJ)4M%La-
zb4~x|3Xzc#L4wDI2Ll5`68$YG2L=XV1O^8F0t*dlDFTFxf&M`2iHit=ef<5&ZZC=h
zwZK{bR<#2ILqPrefP_sJoV0Iv|P^r1HFL;l^z`lct3jR`XUO8QN(@=1D
zAH2dy0ypZzGHeTnrwGnt_@<%*43HCN9YEod)R8SG$f@7%{
zh90jbR}z3tRvfLzNm-m7nIwzl@H24q0_8K7B@ju5Z*tHt$ku58GQ+Opz-{6s{o)D#
zGe0b$|Nq~wKg`II_FDhm+AvXA!0U?jB>($TL;ts36LMCJHQ9grM$0%N5sq(TZ>VQ^
zljTVMcSxJEz^Z3RfGbqpGc#iMql>)F{AdbtuE6F)KOB$EITVIoczs>`rq}endX$6{%zCR9^0lANYSVHsKTBh
ziHG~|RxJCimF|l26F(NI(m57uKPw8P5N|mz*xsj?6a3q(_x|zz`Cd-A*vA6)z%lBW
z=}F6>%F&?cxAuQKR47i35i5PwQp+q8JHZ*F3}P~cGyFRCZ-Ke@I*ttK2}&c1%JT^|
zg;Yiu0x7lBPX7ikX3NQOxWMl4WuW!SGf
zWmTO-JUkh(v9T>X1+`3s?fjVB&ei2||D8b4@9Dht;?ZK%>h^p+t@@Yl;@7d#z}k_N
z(tB!qsGQT%s^o&AqMLhltHqP{S!0PL9i<6Owgkfwn=#yNQiQx8L*itvYGe@moxiiG
zk!()tnERwciq85>5Es_lHhWAZz8ejz0n!ChnBCAi0D1wZRDBiS$$gF(lm^D|{j?GX
zkQzxH5QKz;@(T;K%;@dP(MCYi&rqtC0i6cbd{rdDD+;ZZ=#pCHlvHJ9r!5-VC5_j@
zc22zt2jXTG?&(s7NsRYdJUPP6RJh$3cgVwyU*kEbr6zh@aab|E@UJ;V74c6zP4`Hk
zJFC+LBx|}FaOLz4=pAO92PBinZlA95)2tRfPV5IHBS>e}_P{LpnSUgejTPE2#^uE|9~)JTG+2eFjokXDleK-vDH)#xk!yrsuENlPgUS%;s8GGia(SLy
z9l1obWOpfFvo-Vl%!Lj5MgG6>+RP2=~xsXtH;SsN(l&=nQ-5UF{zg^wh;
zZuZHJ7-_x`C++9*SZnO>o;Vr5!Yg=PH310;_?BSNF{BxODflpqzRyLOzZ9~M8%*h+
z-7fSzjyZasTeZUt^WWQ0I)BAoS^Dmct9=n@YbHP~l~EqB47fp^n!xO~aKU?2
zfWInxVX(-j3#D55Jo{ViKvXA~u2C&Qd6UU!Sr!^?sXBEgB&{=783=R-TG!Ro|_m>JSoPUId2Mz
zQO}%&kP~XZ-Yz*Ihq#&|ZVs0UJIk7=2c2ZFf@l^|trs?TATZIQj%5NuPM-`Q#q+tI
zAw`1e(3dFXvq@KzGo=mIx9gNi13O5j0Vq8wtubJq#271ji}x2np}_3cZX{oub?7nh
zFB0%G33y|!0gR?U;ipZ6&3$|*=;QD)MnmhUuc!22aRdyK@fBS#Xk3qmmZKx#XaZyC
zuJwYE>0`dOCi^p^Dg}J&o%&4#lX&)6u4)#dnJHK}95_G~ygeCC)U=XRrz;jh%*v2}
zn+Cn<`6V1rcT0G1@!hXYwSzao?(Jm<*_txp?xpSMLmBV%CE3`xclYBJ%FBDM5vyS?
zOZtE>Fp?jcbX@-&cy~&5+x4+MED;9Zwy
zhfN=8VXVGl4gxGWBO@AJKtlXeoG4Q2=qMp&S6}L~-4Gsc6op^R(vO;Co`mFZdYxlt
z{xP_|%HIQjpd+LyzUYg{IO2CpJcp_%QftPpYA--Rfl>mm(4y1xNtuC<3xl7%EUjB*RigCabHJO!PS$y5MZI@$gDZ6jblw_4W)Pz^`&QidlPyv`o7MmFdqXx09
zpNDx^@STn$br~xYa+&e>#Du^ey4c*K?4hFfxS*JZi--IXEH1u~K`^oqaXlf}3axAC
zOzbc=Lm}`umxBf?E((8gP5h$PsDjkd$qjjIKH{&04E{u+0OdcB
z#N155(h{VLrN0Px6K!-I8jBoHa&~?qw$92#WsOs4ccqSq%*IYhXAJ0AKu~{HC;Q=6
z?~k-tV<|vc=;Jq?(0EhAfGSG#Ip7CQEmR%hmv9`DpMMW#bm%0vJKds3E}zVLH%t&(
z@kcd-Pt*cdGS3e*BqhMYz}FW6GNUs1s%Y3;qEFMG_HUi_l=Gtvf0zvt4MzM-yN`Km
z;B!L^br7dr2Sz7}_RKiPMi|MweErIhC<~1=*L-2N#@gAynU$m}&y_h06oq?~$QbxZ
zqa#V^Dh5wzNA^WeO6U0#@k)bH_&yk!ilBqNv
zKUz}K7t!foU8C8WW8c23h5j+q&JtD&QLPR=%_Ra9j`uLzU=z7yV#GShExJU~zE
zijV55`dbB%7^Zw=F<5?>mD1hD5eGY>-T%5|?mIAUxKU2wZueCs!KC$++ZJHqFO@#p
zRzfUeNK#EJ^2>TogUsJ_I#5dIyh%r4zrK%b1(V$YA7MYd*P1%BDIRBYd^$cYsAyyG
z9Nv%C9NsSPlPnkq974$Go$+Zol;NJVmW26AeYlwZs^)0g!uNGAi3b
zn3`(F%3Xv>Gwa>?qbBqA`aJwVZn1WhF8n=ddGp)3THj_;=D{{f$KL8kTj*k;s(ahf
z8WLR<;ZRa~zRE)WLA*o1>?0$)DVh&(i$KjMdd%@R1<
z4$CcuwG^GWKhYXDxNiAsYqsyw8z_VDQw2@gbfB?
z6Fy^bIQSTdTV
z7jm$9BV49uVU^4EhmXs}zii~{@6Nf5W#9%9aRm087@iQj>U~~N49VZq?BJkT?E{8oVt~WdvsV98<$w<&uPXpjzkG>9Vw(-DXnx?IuF7c#9WOR?#!;=6RnKqjL-kxf|Uh0C8)B`AJor=gD21y2HF(6j?j+UURTOx%Qy1h1&
zp|+5rXHodPSB^ssnr(5j0Kp9^Lcr^V_BSskrdV{QUe!dF5KT@A^!OuM$;XkY9GY>1Knh!`0
zE<;W~Aqf{0`$*HO%(*)2M98L!f=^Gm=?!BmkO>77L<`}r0d_xTa4t(H9c4QZM^Uhb
zH8Fbi^5|lApmWXg@2##+AtR57L$Hh6Pp_Nol{#8R7b?Om$UtJ1OKNb6mdo!z0Xm<2
zku>8L`-^B#FbEG83I#35Ll%|DJ#dbE!*5^cnb9&nDQM+{W8;NJ<$hWH+!YuZt)<2&
z&p3K4AO-(p0gT@REve_E5{rOyR|ENJt7O6$bg3(;KM4N;%|1d|7ql_edQR
z+S&^a5~xGo-wg}y^7}Vs7UBdzL@K5&Dvu}$V4y)Y{KM{}23711N5B*N-Qr8}Ye+J8
zq%|FScK_$>Uh|Hz2-SUiIa?1{9Lt}1U=f~>+t0W&l$M93hMSAQ7M>Ok9ruo}jC1e)
zkfyRfjxFudY}ZR|Ue?mY8qQ}Lj1P@}s%O1hz(WarLoMWj8p(wLjWnl|m@ez!ukEwR$l~3|k{>e^E%x~$u
zh?DTi{7I>}rZxpiyaQYMuHXlD1va&d%81(?m09t5Y4&fjUD8-gCch?kn!sPgA(+L4
zo2gMf0-8r$Cex+myNP&7int5As^z%iX}yoTBSr8yK=Vcz4`>(ER!@WgaMp#7ZIPw2
zq*Wz&4v%A8j*~&A$1-tw&F6G8?`z5_C}c89Yx)Lm;vZCrUw9@*@=J15sFj$X#foU=
zIp-EmsW_d7CP!iFpXJ77p1ywl6dls9N62DCl#r&h`JJkCDs|Dr4`h~zh=Q@ifueT|
z8uO0ls9PZ>wO`^aFdzj#o2rh5N}#T^8@anafenmWV%$f@Bw$Gw!#LIyIv{aPDS#C7+tJB!o941
zf!(}bMfQl!A|AdoQBIMKs-FzLMGm7A0RU4Vni+G@Wi8ND6UzdsLSDSBoTQX
zihO5jpT<|c@K#t5>G;}$E3ii5P?if-yYHvw7VtnyNgtm6(^>D6%>{{$>W!A?>h)?p^1H0EQR&?~;Mu$ane902(B2^p9+r
zUvM&dK+Z@Cq1}G{M6YoBB%F$*ibIZ|BtGH-?!tf|YU@Ohi7wmyTHJ(uKZ8DP!)K3v
zpC}Ux=9^j=dSbCKwT!6cPZ-iZdnq(abJ%kZ9_Y1DioZ$Xpnb>Vdw?$Dzb{kAVI#g$
zf4k0p)#(Xe&gHLYumV#QL(klnPyV)W2?N0I)_tT|?<jN>_FR6)}Cv%fuo
zCyStRsnWXv*Un0Ye|8A4q=t{koEoxbBqxnA!46MS6UrR6Jpvol>nWGXQ=+K;bcdCt
zsM^s%i5zEc1hDNIRSuCWA-x59Kslc977aM?6@m{EDjkZ@14a>nO1s*e&ZYWXRh;>0
zJ%iqsLfB>HO1H!g&dayEq?JUyGvEBQz>BM_TV+t09lwI7<%L9u8w)Gw?4ab@%v&VF
zJ5)HFsP#y2+GOQtd+L5=@=%OMoxDvR&6rirQ`pyUP_h(`NKSYbwh#bAO9kV1|GHF7
z2;~r%IyJ1N(^@AWTN+RCHc0Qr|2v^Y=EEZpPzmBMc6lt=>kwj-c~0}g5c6z(V>@AZ
zRiS1~<4EF~8CUUP+<=b48>m6*;`sV>k~#{RQiXba_Odz3@7adKcDU*zoQG%%O`CbvD7$STPmc|k1kNzVWTL&NB*x1~~5Xqm0E97aK
z>h~ms0;K{a6zfR@L6IV#4%kSzGjLXN3Cv825sokU5^g51D=Zq!m{F;Ma1?&GAg*b`
zKj*srD+^2V$v4z?60A5RG%*%RV;mvx5kAPb8j`QG<4e`CwT|dmBj5Q;ao1k_F{?=i
zc_6xZz!MYFv&Hyr&z8etxQoPm8y2p!5g7-tUf8J7^#
zgBJqNIYfuAQ$s2YM;v3taTzd}F)b-BrR{#iRUf6)Uv{M@qb1o7!Zm;VigqZW23!HS
z>N3T&5M?UZot<>}$iw&$ivZ)yc2#6O8f2m1%ThweCG!Tg6GT&&gTUyz*gkLSo8*@v
z8bU~ETsh_p$QKbi+e1FL+w>KvTOB)A-M2W)K*n
zS6R8a)^G=YCy-E|9b$uITOvl8VLRAjab;yd&>!*IXDY!kDAZ^nXCNoe(|aS$U!H<_
zR7htS9vbQiMY4OQTu5<;J(Y7KodsEL67qyIfeMatO~x!ieO^{^Pt~LuA}S?$vSD>e
z?~2z8<>h;-hNz$X+!5ts)NXD5^G%_O!gTz;brCJg3-kMnN(W|EgY|WaY0C*FJw~I%
zRHOx&QD5b9WtOT{u2ZeoOW#_5v_Jyws8jL*85kU`;VPk~+&_Dhe@VqiVhExh!-tq?dck_8#<6}fuQG6giUMuDtzRO8
zFAxh=_$zu{vLVx8?NZ^-G8AF}q2CZoUT+E_KjyBQmCnT}iXNa|NG4j8Voo<3No_?j@C?c4Qh48ZxW+@#<8dDduDna4M-o#
zp9I}aIW>I~X}{Yf119*=ZN3;P3wiVg>IaADKaY-1*~8^qSPWvPekRIds3CEr+@Z;y
z5BkE=aB3QLafyNT1w(u+-v8QxJGMq`YOduRCz&i+mY7W^y!Dkel-K|JJb!M{E-E^n
z(09&TS0qz29hdc6!s4)4>WGfqm(h81j6a`3Ff#^D_i8>r>Jc}FD=!*>QydqoD|_`}
z^$l$wQAehT0r*99=m^{so>b`K3fzN4zTo4C-{u>tC~85XL=mkapfMZa$aSP6zQM{A
z>BAwFZjfrC^6#pk*4Wf0H}wUIVf=n%%294WRIG0-Vu^^!frk~oNDm{+-aZc(WjKPX
zMPP!zh47V`5GR{0rQ@^EC2SibdCKt)US$Vi<<=DTRSFqM
zB^Du17Q*Vi*d$@~g^g<-<4Pp#m3H|(TGCCKhTXUe${K17tg$w+7VGYbSli0fjpsuICJ8O
zgE_DMKB74*j#xpgiF}dfJa=kY8VnfJ9v=op>vVqcYY`^JkB?^OD%E}-VxFfdQA0&nVf^Jw<`!W+bRLJHX9UXG00iXGUg=0iUUQ7B8U{EeoGE?fCn3RN%Q2fKBy#4(A{_j6FDmbJ6f%g92
z>HMR0{vmgt{}9)WAQW1^rl1PWHg_v*RbM0dw?Y*
zNd`x}I_-AiSglDwTMnYhk}Eec!WE;H_>$(={>d=n-%zRP!Y3n3s_ThCZLSJw=W{FQ{dlIWCdkOh?lDb)z6cnn
zg>)~+T&*(DTWbXzQfKk=`&`v;8IO&Z@hr6erC_v<(lWw2es)HwVf|5MPKEU!OD~3t
z#}Iizua$Z9VQ5}{0I^N!m@v-a)Dl2KrAMZt4C*u~
z)Lj1JX1U%bd&4V#f_l|mlaAuL=&BjnG9)D>ebpV~Uu-~MNu)`2r{J<0htWBunk7ls
zv}|pa8LHjc)S2u!Eqitssk(Qsp}=NSP7?m%1v-6c-Vf}7H<-%f8_hS)n%7yB(sk20
zl9dFY9x^Vc1n7edE*HgB+MH9t=&~^JyoBlIAJoNsPHxd`a!IlpY9@VAa@J?7p}tm8
zO92(*CVPG!3aZH4Rb5Cz1N>n%{NLLp6FHJCYlS=H
zE_+rNI%{5F0X->T0?I8%9zQ6Wx-var=+F7OU_PEvOpdrxce6bpWk?G;<*?tbLGkl;8Je$vixj6A~gQ;!h
zZfh>8r&Eu-s7*WHE$OTA!|CUELLY9etBaqRk!v-$m|H+Ek{T}^U)&-PzEf%QBLe1q;sK;q6isv@N)V)-F_VER{P4h_gg+{@4Y?vr$}2ZG2#Hu(x;JgJ)U
zs7G=kk)}cu4_=(sdNWYwMRpw`!XEDye;80BdER<^;bzqjwo~HZN*`X+?
zDJqa}iDZ=5W5PXl!6JV!!|Ad=7v*uQVmRFGUUY!xg@mAuC57&rhj&yX`3bA}ewhD7
zs$fb++DMO`we|P47yQJFO_ehiAt#mw3uDvwPwR7A8tnup+4oL4wvI91e!>#=SsdA9
zeQwAaAzOC`^VZ$?6>i$|py72d^oh$63yZahZ!#RfXC2{sC;5(cMd9J9_p9QDTzEEtY}mfbrn{;BdYpZQVE)(8y?f1OFA@12w{|_CzUB|6r2d
z>x3V`N)F^_DH6k;0Z8x=bn1y5s{Vk
z(G|8fFWeFZOL$ypZLeo}Im_K~Pr$y_<@`Y?+0u3uVrK2=xId4PS+ZeiWWZ={{b4$r
z<>gAk_ZHjZrR=_1+)Zuwz`No8K%8BUv!h%?uzj%O@k3C`BPnliBoZ$@Jw4bR7(>Q(
zDB@8W&%w!ld0rR7EKm95dKbmUoL*3HL2_O;UoN3Gnd@Suob~}STikqCk|2d)>y?!W
z3FVj+@$T9{0B@}2b|ei(vA$+{N~`hoJ`u7*^yD)8&AcI(O!i$!TzTvCbgb1%s`PGm
zFxF7JM&}AZR=<{}KrhePdWrEt|K7opIU`2w!cKu}bG8|XP!_oY)4*F{Eu(Nz%HAOE
zr7%&+R-T^Ma!0&&p%p3?y6kDrM^H!#Jl>T^p5&2qglMgGAb)qg*jWk<`^}fx&-FuF
zXTmG^%k%U4)?;^K6}w&c$m&~W$3CE(CwYQT9#!?}G5q8ik0-4$*DlPF`avLmFx$%w2?_hudHL(!A(vBb
zoD6iMI`v|sPKSbWG~SQDlE|v{J|OQf!I%IQnWV@MupP;;?q6A-U(Cim
zV7$JRig21E(!xXS%NLncwBNSr&R)g!dBzOo^ol&KmU$Hlv^Jnu;j!B{na|yleI7&J
z$0dA`q5b8ver2kCW3%8!Km+uG+hl9rJ71fFM=QSTmd?|+%JhJ>JI~Ia>J6BY-|IbS
z<;uG(@r+*2Nr7o!b!^WuG~Yg!5^=x*<81sh*xk0{Uh8BRhiqgyL5ps9Z?=qI9~SCm
z{uBz12T^IJVq*Tr1zoM@#8`Fbj#za4f>0rZ@tenVH(_gg@y_au^&mRISnu7B4tSoM
z4>~LEjS7MjL+^oKV
zb>LOK_Q|_x;&`qDGPp-Mn2!fs-sLCpoop|7oMJlk*#z(pMXXU?vM)yvCNDRF!eCD~
zB8u>>)OugD8uxEr`#P0d_r9-aLAh|{;4dt1xVw-5f#-25!yX;WaF`~z1O*(~br#!m)*
zMEA_b+NS)fb_BYkvD|Ld(R^&Y{E_FbIL6CDy?^?q=x_!Af$A37r7WMggrXK%t8*eY
z6Lm4DnM1NxRkZ%=`Ck7eMCvUSRSDr2C!aZKe^V>PFsD9HdQ+&@V5sb|-W;XN*Me%<
z1_+krFWCfSL^(wp8eCNZFgRfdaKS99o(CztX52IYzBrzTrH!S^TIJb0R@}JlYqC$a
z%|Uu{0btmSG4PLZ$LyStfa6Hw%kiycdRiA759F>+O@6%xV}e#ssTt(RQw^%(1JM39
zS<0%7XzDK6s%|=5_+l(5Ptf!DV{I3DHUrp=Qv#LD?3EJLAZnzoTVe{~RaOo;5DD;IlBm7otjA=#Gd~%*h
zW9MSq*jAaJt}Jt9zBUA)>?Oh&OLsh<5+{OY^>TmBDz(dP%HM5CsxoDVuuPS4tfiu8
z(qZe-+oSbK_^7hC>z(f}&?A{C%&l}?S6~??L|CO{SzDawbk$wYusJ`CjBA7+Oq?f(
z##&p6k~*)zX=@vt$SM?iQY`W)ilV3CNSuCusC6DHJLKWKe4hLmP?D1X(YvZb-M>;5
z;T`8)dE=BRmCnh^V7(hqi*jHAB_STUhg`u%z(XIok@;@Ul)%H^)ff;NdCY1)w61#P
z#73|&{FmuAu6t&
zFO6nAIJS
zjH2Ro_~4pgvez-bHa`GUwO{dtKlnKiaF{`5vbU1HU}>7`RB3P)gC|)UZuPzy#?8?4
zzR{SdFqG3?DH%b`&YC!>TyF3Z$p`jm9R5}uquy|_
zm3FZxYn>BKv+7uF=(bad`d~!)?c?Uml@EpXJ&k3aUzI5#R_JDukwAPJ`OO-->irYfbD
ziq77x`j-eBFv>zk=OJLRLrrR
zYfOlmwTWx%3sE2oTYIw$4z_?d)-^mG)4B1&?iz*6y^CY+w8fXL{~gD6q;d?8*O=_!
z6z=??<)n19%0|(qf&K%qr^MO1wYwFZ=SMtpeeU4U?zMSDe9CU)&E^`>Q)TG(Y31>+
zIR-Hk5V#xOQ*t_%J+q7siWWS1Z@EA&oYpA=8i)xoPjLjJD-q043L!4XH^#Z=J-~9b
zoMy&*5YRJG^
zt{~tuvg$lFPC8SQ-UztiUZ}Tsj1U4}cSmMitIdAEJ;)e~#SmbqH~
zSx9`^b&!utN-zOHP*R1l`z^jH+8uy^K5HA9?EH|GrG#LJbR|(6wk=T^&w{8f8?z3^
z3Wm2*Z@B$3>+-gDFkNqA_>j7gCluGLw=iACgV(+i*I~!TAtI54V#wumbNa}Z%xlaW
z85w~Sk$O5#S+SOSQ{KUcz__@C1PNTykllF^aXJ@QFDU#q(y;qL@?N_GBPJ&PgO+Cd
zs?vk1rT>h}!{0A)=QY~nD8~(|pH<>O+OE-$7<5h>D|O-P`x@647)>(fWrt$PZucY^
z>UAQrB~*Gd*0dZI%x&;#b()8I{Rx$Tm%ehLvaTUk@7#Pn
zMA>&F(C2)Ad-z*iO#W#_+Z=J*f{WGAvZ#$H!&{p{-w#4Thl5b~B`Xfcqphb`QQnbP
zWMvWK{d;%pNh39|4J7>DU7C-*jN7Ve%)qVr(-|K{V+ZxM44ezX@v6>OZr+VdM6W+O
z^)gB}UBF$zK79eK=Pi#vbLVJD@$|;;dSIFO^N$Y}3GwluG!la5suDMkEoOgzzX8G6
z-qCf=2MPg?uxB^at9#p%^Z91T$G#i%?MXmRnRZ;U2uAO6|F55Vyf4~!Zz>5sc-WNj
zipa*fKE5dx!6@H62i&Zrh&r>qV`FDbl?A1OA_{f%KaoVilj
zHbVD15f0{DxA)svvKo0+V7qGb2rO=G*^Xo(@lUNWY6i5cm&zTy;9$t{?=72r*ziwQ
zf~-5;eo)iu0av6iO>%l`X5hWr%(U?<8Qnd_*_`0R!jkLm<$K;-uN$MuZIqj?_5(kZ
z+6KX4G}2AANjHuc>@BAwBRII8&JHun&IgPutleK)#R^XWzl
zs{A>g37cerb%pa*cLJjGbhoB>QX<|XsWD8^_2kzzDQUZ;XQwI|cFvmJoDj#^V|c$H
zYVzb}8>~2Af`082&65}IwN$&lVRN>MxE3VxibT%QxF)uMAWoQ7C`V9&?N8?(rq?=P
z&N4O652DtW=y2_Irr2bi8PECToZN-y%#L@Y&MW!&g(r)!D^EYqGm1PgNt1<&2BbtU
zTARqbWOiPdO`XKV#FFvwdLuQ3g?Sj0yUv}MO_wKCCa%ZC<%c-Ta~-HqfJtLJ(h11d
zMnZ#^^CJYHu;Bai*^jl&BI7(hrwZQfa#|$35lbZ;R;#_a6z<665lg&~H=-J#ZSR*^
z8_*&9j;+6>8Z5_w5)mn4!%=)zx%CTv$tXrj;WN&!`6+{$9#AkahZtY`q&xFLS_lPy
zD#@xtdvw1JG1JWuyQ&jcv7!>6*EO7N^DVd>f@94ii7$$e`?s&EX1o+_8{REs)muY+
z?zLM|sjaQ`Zbde^<>BR{q9T66)iM`>0mz?&wnzNAcPb_#iG!>bxyqwGs%8(KP)ju+
zAH<<(ok)J(gz|80nF=KoAme!vm(+rOf?OakW>PV!gR{gCorP>peiD8J(0o1yxO!27
zzPi1mZcVDHwABueNSMESk4$Avry4r|1?Y8VN)6y2p4@2}@mMS#ueu!7
z%9xut6Ewe!)3wMx(VC>t%svQMdIZ>#XG#Lq-0Y-o6Ps-(|i^L#?-eRXBXkNhPH)>3Caxi}z+mWZYZIS%r1
z57k{4o>M?dE}vi&SoOe5>&8YX*e>MYkv))&UVPT4^7ixF*;C1I;#+&vw_0EtOWSo1
z(_aVCvhrSeN-CYZAE$P|hJ`pTQtK?`cBVE$Yd@nk9F%`ljJGnUVbA1E@s_G&LRx6Z6RC_i0J
z6Ed?nWrH#rM-y2~78AN4#%RD@7tnqB<@FH>p5CbW
z-EmN@TnJ-`J+r@1ERQDebM
z*u3PA3c94Y$W>Nu&N=n0dn<~neo>$W>2imT&b$_+ZcZLM)
zS8yS(3ot2_o9YC#=|(Y(E?OF{==k;o&3yrLzJ@FzEHg^28>PlVrywq8@;$Ft5Ep{j
zU~H`^JYZE>`2`1L!^W1H5K#>6@GlmUT5w4IBe9L2L4dbb?>JjFp$a#Sb{nSg|8o!jo$(>7sJh46xQ3phyEq1mk~(cV7?v7bf}ipKNIr*;L3huK8MH(H&~FpfS&lEr;M>$NQv1X4QGcc;-;?
zw700h8xrx?6JBm(@r%v|;{{cv_B<4^rTB8rUj8Rz3}wMhLh+yy$ZTH(NsYq6*)XbTB}`5_nB*tAA6`D?wD+a
zaPc!7B=I=Q?VXO4drn@0GC`^xW;}rlct_I>wdmbYD_32xO^pHEQUCi=2?x5GrVI!|9d7GB`?3&<{K
zPzZMRPkW{&cwAaueu8~keYAB_=X<{&HU1lo@Z!BbiF{D-N{H`HZhHN;!$22!b5&ZC
zXIc611bIDIz|^N=&9pD)PESk`XKOr%$J-%60GIyx;jqkeUT@q{II{O!I8G;&^M{UF
z;0B|2X8`wbkV%Nd)6-`BCqlfIt0Jy4?G0W7j$ft2uwD9awWnGyZdtG`uFkPd56aIj
zSk_Lmz{9OBt_Pmcwat$IEaGKfZ3EdOP%5>7rLxyo4@Y!aJ!QtspuW>$g3pZ23S`d|
z|EM=?Wz}wO1>L3nR_hF@z~UMnqUM7BQ)M>3<>P_HVondf-pZ1~*eJth{8aR@ReS^)
zXz4jW(KeaRnE6G3AX^K#Id1I<7#mnXiq5xnyMHoR=v*)<~bfjR%WElal#SU^9c~NMQOU-
z$7##3zIv^)R+xG`+UwgE?>WJYR2Fi4lG+2Gz_JF{0B7r}sHWyN8tJwpKP_ar
z-y%qgbNCKZztXCvXzu#@)o>HlzV?H+9~
z)@Ie$PJ#ZOQ=SP@o4RXWTcLc55(PkHkDVxb({VZ-tA$_YtgGlA6aD>Ad;}NR(!6U@
zAROb9x9@cp!cRgOg?D+x`1lWvHnt)lKHfpvo;dHR2TGY;LyWRY;+7^JdibIJg++bt
zLk2Iu@0BvA>53Qy2>%85ZmnHKM_QZi5fTa$#y{K+L-j0ZT}-RK&w@l()Lu-K8$|VOF(f
zHPgATBmRHDCfMY41=O6nr)I)>fJEnuPn(WZC!=5F3kLpy?A_&8JZBi875OTeM~c8GI_9Q?Bz7|&Zd&lk9!{J5ivCWU^5cs|GkkKqd8j~elVFeNkKzXke~1W
z7rG)t1g;
zb0^O$WQYCwdrbc+O8`2;8b{!d9aBbYu>9?;!X*M49%!_Bz^*;XuZXMdKt
zs}+PB5da!vKtX}VC>$u(y)s+Q+`7f=_XmQFxs3fux
zYg&Z1*IUBcn#T3bjmdN_)OxE^k$R&gfEBbuvUue`dxONCwUf5-Yj#Gs)?4kqkS(U1
z^x2wD%(SIx*KVNf_~puTKPc}=KXhQZQmhtjcMoX>XuLkz=2O9B#RuhF8pm9j53=@Z
z@%XkYWmW<}xp#Ocj6I~cRV$0oodbc+**ptlVMm05;=1HHVmD_qdEC}nHJv~u0Pb&hi)d(QQ+XmN%-BdlCL%Kw365Ff!7k
z$29l_OGs-rcqH&T*p%6$hr^@xWSuqPy->j+Qc?6o$d-Xr$mcAznRQOnZdH0=2=c_w
zw3Bvrx4LqJvsVhDgc;Dcl-REwG@Xeh8K%agxqakfI8
zO;1nH`M9LW+R3Stb`T09YIJRV-D+ef9hF>40ascp>S1qpcM4!QN>;<&XMsJ|V7ZRs
zy-V~zwE!al2pG<5_g__(>bEB|Cp@;qonBs@1CWP&2=
zh}YNGP!n?LOoNB>b!D_MkYJYvbgHixN~ZngV_K@trs&BB(%G@)1Z5}m9`23_
z>P+`3m;&7#wxCk2wjlk$X2utwvJ&>VL2fa^cfajw3VlLHTYWrzAW(Iq&zOZWHo2(W
zi=idtUa4n8j>o$!O1ZyYX%T9hzc2wo2v@T{_8`Eh8H5v(K}Qugt^DEig7$7*b*yaA
z!aw@zdo`yEm`HZs8%$Okg{^)XvgDoA7~B96!@;q8Y9!5uCvZk~J+Pb`)k_V}n?_-j`eF=_|(JxeD*f_#j@KotnB7T+?5mG*pTJh2y)BKjY@n+;9Q>Y
zbr}3nzDciqAUG&!T{ZrOff1LG5Fk~Q=W=smCv+~1%M}n1Fz-x__U&6>ZS4&U0mhvD
zsr^-xM5Nt_`DRDP)|L*O5!+BDQ!@o_#$m3dlSx_wZoiUTxEF#noDtZ|!^Ob_*G2gs
z724yqW~S9fD{?BTlTe(bmDiWe4>CqZ0PvxwhhM2uL-f}#aBYt}^eCQt%;n`}^Od@g
z*x0XgrYtDjmtg1T=jN*oNVUuVA7gI;6;=1|4}&NjB1(sdgmiZ(2q-NrEj4s^Bc-HB
zcS%Xt(B0h(-JvuLo$u!P-Fu(^8|$vuwO9*hICJL2-rrAs_ZcWN=sr9>&33lFPFZg9
zyuM3oybh1}-FGi?mzW^Ko^X=-@qqG&Ke-!LU
zWiXw%TNJUzOW(I2K72T6l${;8xkDu)jQWwDmzTD+7Kt)2Z$I3)C0BpoJ}eY>dwmX!
z&d{_p3NW(XXDdd=#&&`A5!i^sOsX_i*Vb$u9XF=S45DLVMrUWGTwVD>0iK(ef)q(_eBlNSgGq6pW=e5&`n~Pt6*|8&N6ve(#%2{4tf1Tz+`M4fk$;8BT
z^{#3BuxjUG!7@wP9`auBp11C&_LDaU9UeuVqCnTSWGB?yhIUDe_h=)_)_~Z
zh5^k_b&uGCF`Q+Stc{`R-c5GCN$MN>VTcSiQO}2a6KJaR`YF{C?w|EugpyKd`iR2d
zLQ>q9EJb_i1KW5RnkLz~VNcD)^{l@*tND(|%K`o+({{{}Xuh{Kly;Bb81XeE)P%2I
zNyTC&cZx{N%gD*Tu_o1S4;|r^xHzR88O<7plE_Gq&)+fU_S+}m^83G~mB+?UUJ1)G
zr^{o2^tOD)-_Yed!SW5)83-X3)3GEXVaJB=-m!e0@1vzo!-+=s-!YQ2`pp^(>E`Ym|2?}u?mcN
zux;;K%nu!H>I=`MvoZouppDX&6{$0kRHMHwr{i7yoA>Vgy+dneJeagBEtw`KC&8`N
z(AemG<~3MSdr;{LH--~cIEgH>m{?jGU7ziNE39V(mQt`?8~N)1>TIb|KR%fL^Giy$
z0!gg?I5f+Yz?^HBex{@hf|VXjOikZqE3%nQ(0%pu`&{qDR9syAoPZz;wsUlH@>*D!
zJVr{~$jB&nXMFN*HV})%Z6fu|GjDy}(89vvTWIL6UA^}2`Td1@5VLPF;aoj981_fQ
z`yeCpT0kJ__}ES&j96S&Vm-XIJz=XbWx}=;8Wcqek)`Ct0bX-+?wiP%6vOEj^jg3M(uo7A}wn&{uw}doj5IX7jj%P7b
z9^eY!%uuH99U3yeySdDpG!wr1L&eU)QF}IP-a9-D0q5|&xOj#FT@Uz+m9~KC+1YaU
zbE{|1o_&jmfV2l+iqZE-0zlgU%_?$0#c+vx2i2h(lq89rW(qI{*^w
zK#YHLC?!OrX!H7@nU;?)!Rz)4>^Z-vXya<9B)7OYJVN;D+uiLo$CtLp<>lq^BHqGo
zjj8QHIQHFS&U%)X562EV@lN*U)Pzq(QRUP&aJO)$OzF&XtIEH8gYf20UaHBmawN!r
zRHq_{McUhQfYF?u{&Ievc{eZauQ|dY)PBD*96=K2(NNG=)hu5%?4mB%VoEj5QczJ0
z7XsebEBo$1m<#+5e=3Q6Qs)iNIZ^k#P8MQ30au{rRAa~D{PPgKDMOQ$$%}i*=p+#Y
zJijXr=Rx>xuPhr%g>$&4e8Ff-(0FWONdht6)Ap~YEO3==@_iE4D7@^?7JP9%vY!lU
z>+d-ciX#Ypv#MfMAZK>cX||YC;>(4m0(KPZ>zy`BdCb*I&Za0I-t@0TQdT68&rXF|
zIjw(4ooW6G{+^xB4N)_5JKATX&~tjq^8`=lOalNfbi1GOR@iR-p{Z|zz-*I+>q_Q)0o4vr5
ze$W(+jcHt6U0vMW*Y@}CqMRy8h>5)_JZW3>n-TObk2gBHx}K4d$=ceo?MxM|g$wM1
z`&3a|TUJYp6c-m)d&)WgHWol<4zC**4^PiGOia{o-h2mh{B!gtpr#I_3Xw$hcJ#$C
ze3X~ZRV&f4n6D`tKONmUP1!FwtrP;&04blH{}T*+JqV;5KrMbtc`!`lHil9JBvn=M
zG4L4?^g4rIBn!F6zVk!21$QEiOuUa=?oRo9HZAk(ZPX&{I
zPTeN9F(m0H&A4cyzJssv>eZ`X?paUUZrZCUX(R2cs4?N8yU>QT6cYDfdH7ZDK>|5z
z%PQ(-Nl{Z%>dA=%CNdH;Gqa1Eo6~GeURhPGWjCxZXO2H2#!z8Yz5nYU7kfH$92|p3
z`WKX2kFTbTLrm9@m`Rrv>7PFqaF0|@HvLo*mlDDkT@~=2V0VHTo=|5j#!wHJOROJN
z;hcg7e5t}h3@3jbmyD#RyOQFd3hset@+d;aVyZbEIv#Y-<<^Ex!Q+Wlp}{N8$0)ZE3N4%M&84K;7f
zFr&nf6YC|NHu58@om`kZYWET=Gp~Xfju*?6sspmJ#34j`6nin#?+WbAR)v6jSMz3Z$;Has9U?cyWkbQHh;8D*S5Ac1-j>S&(R+tA-$oe
zZ-c?A`%m+Wi}xM8i)w3YCHLO2uyn&JTY}R{l&-Uhm;^GMKXk=KudjZ!+Q>r!!rNN_#`ukgb
zDK-{gLsN6qgp*f5!1)ik@tv2T_ri1pk_zP5ydM#@;ZI^Q#>(67@ZHzJI}B*shIN-b
zVi}o}CV!l6RnW6*wLXfy7z|W(GNdh|637?(Tmpw&2X8Z!DgHhj(LjDAg5qoyk>D^h
z$Jz`HMWB^eM(?i>xeMRVi=l^~{VB)V8YZqvWc0R2b_rl_VTj(&K|b5eF5S0yG)(Iu
zF4{_T$9KGG)jk8=T`T-3@K#P1O+}Uf+eIh8S_f1I_;u(OJUV4};A3VQgs$B)E1qzo
zx&IRb7cmxqv=cBGz!j#ZrnS2pXZs7+!3P3ZWc(Aa-kzSwr0SG(>#-iL1W_cWnOj~TaFQXv_)8z}2)$=>+*ZaW7wq}1Qcwzo8t+b*Z7#bW35Qfwae;qvT4}{+Ft^#%
zt}clX&vJ@`pah&%t;}Sd63B3>5SXsh73lJci@7OC?~mM0ekIp%`t^SMIC^GX-K;Id
zcpijp3})o@&CQXN30
zx#NVY&c}1^$8S$gmOK_xQc}JL2WOU+{umgLDJ(1mL#m~{y}NXJ=JaRYu?Q4HMTYiw2?;ak;fj}TYM*ko!
zjWipmU-bKTTld!3?V6aVD6i*Lw59{sC;++uLgh
zfh6@;9vB`Bi%go54U=8`ysmD<@)^E-_qyCHSRlIpH?u^x!fJlwu^)}ANs2;x`pih*
zQ*Fbb=DRIPzLRqMj82nuWnD`)Z*6I+-t4UbDzdZah(?l`E$c^~W%IL&m^4)U*Zm~Q
zYYDduHAV~4Dddf*HePSTLi@f-BX?R4?Hc)8Ti+2oBZ+ys;r`u*G$EG?sBR%yI71lw
zPq@}6qF0T!26lTQ8+gyKg_Dk0X=EDgciq!1Iop6PWOKa!5pZC@s;Oyc0!Kzh(w_Sl
z2YTj)oMY_n?b+Jdtqvq`yq$Zu8b-o%_lv`q44bpEvea%vYI}PdfgjW4XKJdPxp{%>
zy0otDl60}W+O@fmWrSSrLv^dG;}4a{a(!7vQLkJJMl`%C0KgtFDC{G3FHYN!9!oBf3H6i4TN&we7)ps&*y|2mp8or2Y$FFb)nUFLaB+91mzq
z!~Na$ydz9oCXw^|=?-jsb~ad}=wT9DnEGOEJ$!Mr_CUPAzl-Z@4!srxW%?K3E(Gn1
z*nX{`(+&w}3k|!DnCR&GuwreX3#|P4(=(B;gc#3lN&KLhP*NhsNOe}4j+d5|v3dJC
zz8L81>r*e&eD30id3(oW?%bX7!Ysa
z4dMjky4(+NNuX=d0~1fuc3#ZZ+PRBB9iKbG`V
z$KR{l26nE+j5Y@qfvp}_|&5->IAmo4-4?vPUJv~2HnY{pa
z#badTXT-Bk%NYfy0H$gKSpQ3ioSYnRy|@<)s(J0n*Kk7{lKfYg4FlG0!8`0nrTf0MuX0o1#!hKBi7eKp{y
zCQ_>z!fmgZEmX{CF3-duTcY_*fzf?hKN-qPRr9@%NMt}JSSxa+ZO$E~QkuSZeI_duXsD>IY3WrJ}A~%om9L1(XAfkaCKS$p{J03
zb&<(hzpaC7rF~?k-ma1^WucbVRd1wfGac|dmg$73j*j%Z6I-0ujqGVyM3V8u?B67V
zh7f0qbCXU7qreqqV{%|U^1TIHJJm#dt|*IfuWsm_>3amXxs(hUn@RJYUDeUsJ7>F|
zD|MBr%PZK}uJpXQ?jc@Gu@h<3x;5dfJRAu>=lzt}`SQT#ss=?rTRBoOR#x;K{R(qa
z1J|WaVT`JF@mlj)iJtiso5aN#&HQALl2I)w%TW5vbYDXr8{_`cR2X*|uV41ul2N)d
z;jNHZ0tFdPOL><0XGdd}tlO-=8{6`3Mu_$H@_MX9gXe7hB@A0EB==zes#mlHtr{A1
zue5Ojd}brL`x;+DzQy6rtxwlI9xig#G+yVpgNlRGTzEZ#ev}lz5Rl0-;&NAlW`e9S
zd{2b1cG-NEC9#<$sj8-F@PLcM!epx#PJq#LbEdD2bP2iVvtatL`Svf-@lZ_GTi=pK
zhNGskP?-3dVbQI2<;JQ4vL(+?FXz5nkLZQ~*#yaaYts(xot~2sUi>Z@RvdX8R_1gT
zr!`Iw4}B7fl3LKTYck+X~u6DXl8s^nC|AT3REyI6lF_JjRxUEU_JG
zFci5kdErt}TYmu?kZ|Cv?JQg2K4uA$$lg4?5HI{FuV=3MN;iA|-MhJ&Jn`EcTkQ@T
z_^F+C(VFgBad)dme{r%QHbM3%jKd#DBi;98TH<2;0p`^x00lEx(9M;HM`~;9V;ei4
zrpQ!e1PHmldHwv?vkm+fASw=soO@ZN!eEPG)iewJ*B_HE_#JTo0{0-Bzc8Oa1(0a0
zN$e~{OP=mpPW0xZs5U`_cLj&Bx3OK94C;~l`CY;u-E$kr`>I-d4AT(!f*lm!6}f%?Yu(>}
zO~wDcz50Kg;eY+e|Dz%Mf9&CZ8?yiPIse__-<|zG9Zc}G{4X{>-zFz8(9;`Y0RQ}A
zu=gMmd}|R$R(nUs7$(qAzRH7RbnO1%kDkl@Pb2%kd%Uo(_$m7mylbYWpxT*`EQ;_7
z$>E|#pkD%46!j?R739H{);b+pf3CPR{od0ciZA`aJpJd$zUhAQG*HUDe=xNF?JocK
z82&x{e~;nc6Xq2wGVeLCC_mc)c_VZeOw@NZnAu!j2mOAwI(g7O
zEynrhf6n~sIqpF0-R%>nJ%Ne8m1R_`E_=EW+L0SBW+|?&H`;xyQp_|VjvyQZH?=AiiJNsH)ra4RYo1Boz|+C
z4y1RWh=_`bv2aS|zZx0E^HG41T_4Mpu^dG6#RE;@cy`5_PuT`JTeR1vd6ALKgY3*K
ztTr4B(u?(VCqNJm%bYh@JagG+MTmWvww}43wZv_f8^ns}>r4SD@6pJ_#Cth8Y*35<
z1ML?8X8C&*jssWUYzzpi*M~|A%m?%QY{#~=dhKs_?BOyVixa8^_L-AV@q?rZ!TsYi6HqT>2Bj)jHCN-ub%3Wk@oCs%SYkBC`GNKGRxlM0zs96;;z#%{@1>%8=i_7x<
zemEct4Ljf_05{vyW^Gm^LLNI*1Idnrdd_e%-0qr4vAUF!+g)|gu01$7SW{bz`{G3a
zAae@P^x|Y&2J%i5c0|40DI4juBLqNZA{Bo0m7|4xcQ{2_2IuH-qKV}q?HvsKMd_Kv
zquWN&crw0cK6Cmsr3|#+s~~tax%Z*uRZ98Ft6N(^B_%9CnR^gp)z!acWl;g7z{|(i
zTQ84F>D9e`D$97SPZjiY+}5trU~}4+tj|K=#{iEej-rCb_kt)PSSZOr)sf$&)%BM}
z%_=M(HDR6ZIFj?DC0YV)JMw>S>f)L+=CHl{gYSKPQg(KB1HLl(yKZkk;uPVrgw;m{
z#!3jwEp~C?w*OfpM|kUH$%jBWw77Zvg;|(TmIi%?9@YMcvILrJLqHM2FP7rC>BWFfcHtJxkqxaE##X0_GsOwGh4-w(b>
z`oNdePvxxRS06&PWF_+6yr5O^w&jTP?)DOyJYFQ_&mn>wWc{d*cIWWO;mcIbI~kIN
zG?Ke5qy1(_;R9V)YL<6)q_wrlXyuY$0#x4B
z)g{;eJ4V%?*A;J%yd$f9^RUe~W_>aiQLpYte4SS8U(tMq%$%G+K!pL(QYe@NIOYQg
z7kyirX5txWQ8gG}an%gNs-Cy`yNIh&g|#be7hG@L%~5In_zU9VjpK?;p)!G&YWt(c
z%X5QjJP}T079QHJu2aglTSL7SwRAD)g_D-eNc1t!7#LKl;SUpNaS(R$%8+<~>)IO$>U7pBFiEIH^cV
zXZZ%n8~UQmNxffX`3ckCT9|ik-~8t=F0>c*W=BUOOG{bx4GaK#%51n9)5a``plyxU
z`R^Uvs(%l->aDA$3mB1D}tfSrrD-rtetrx}|IOk4
zx%AEmYD|4R9LbXuwA{=azd+9MT@9zm__(jR%w(Ko)0CT=`(%3(1E|i=(E}RBK>?zl
zT+A+`)g~NVy4SIyd;TL*bHXTr>J8ERpi7QC`MlZ!eFLQsBg#(sc*%!yJVvoEnp=M*
zw^H1s#4p%vv8oThU$7u^bvkL2p5hR4U>z5$r+BghjYpfu=XkgH5ex-^hdtch3$3iD
z8x1TAdmIj83u=>rF5cxGSrkU(d(=axtsN$mY3E%`aCI%Fqdw@^^0DC}5^R^jekRAt
zx};-!TUV10Qi0j63Mn`?
zsy&|2fz
zx4U5k!r}WW93*RUY@IhrU}pSh>h{w7w;2Eb$VNR(_Wx@g|0Y6Fur%H$e9?g+c>3oV
z>F%CanGq?o)LPa$lcDO1FGc0A+BzcyuH-VSHGOIaQX8J%Rax^TZ3$mT#o1GD*F+~x
zHGO!e&4)l7L+mXnkN>`#V^&0C5twif-&f1XOAP`RlMw1SNy0C)KWNqcbr6S7Q|5d;ukJ(H_5Z35QPD{e%8=i{rnBVpIb079l^cL>wdRVLBdW_i8
z4|RuFMNcedVs~TzuCkYIkE8|0&eov3gThTZ>|(d`;hS$+g=+nRXM@1rt9IT^7CcCzl2+SN{5R8~r;cw&wU~ay>GsQ3TPSCOPIypx-(6{jxO>S^+
zko#nH_rCTri9#1oICRqVnoluW?ne~tg}M7DZEBE?$gHqOWKAQgpC-t4I^Ot8VZudR
z_UZEx08IWVIL2n#!OdL@ddvkC_Fp{oM6d~R98rz$!pXse(t0!9K81#mt@s^;Q@|Qh
z%4%v#8X6D!A&|EpO4LK#gY`dS@2;oSV)q+ws#hUIn+OzspcAs8uxFRUrFRNdR3DVM
za&swO;Ftokxj%b|goBeXIPv41kVc7w7{k^4!
z=BI1);=XSvM(EztA!C2+FHR+qoK;YpUvyzGY;S3wtXw`2#+vH}&M#eI0pP^wEiVra
zvHQ380Mc{(ME4w^&$ZoM83O|Rn@n)
z2D(;+KRBaxHn|aKO)2%tXa>fu41^{Xex4ejR0u?uLm_%@JFdYL!
zyB;eb8**vFo^_ZF4+DNudE7rN3JY3niBeZ+p**0)O+*p|T6a2owAhv=6g1aUcLS$wxYxzw!WdaOSq&ia8yt0ZNFX<{T;mep6S$p
zUXxd5s*l(1grE6r&l7`t_>rdymGy}yj?ZWWX4VpuWwq@aUX6~;Zs$r%21$~fW^T0M
z#eMvK;JFA8348E5FSwCH}2;P+Wt8be5`1=0
zmSluJCoz$Tm!R{!EecrBl1#v<)MAERLqj88z?m6=rSasMudnYhBCaSxbH&QB?lsIn
zZ4m*_cPmY0*KBO)WXy>BPR!0S6BDybSJ2}x%pu<{qZ-?!PXZ-nVDA|eqvGe-Wfz|t+oQ=C*%{Ja+x&s-QdTUg3
zgl@wx_l0c)KEd9X?#wij#cr&weofIHK0_+6s6azQV-o=Z#+;g(_?#T*(m1d>%Jff18)y>%^ZddQdUEi#jZ+e`Qa?>-=
zAmh9on=BdEn^GYIF$<6rLcgDezu~4g&k4YOwVb)r9gR-?BCJZV>Kx;D;Z-y=4qo*z;
zv(wWf1!^pp&(SFFp8vl@AVqSZZrRf>nJW>p6Wo
zFAV0~8%WjZ?v#vi0~^tEy)_frlD}UJ_j_KsICSd}L76R4)-_I;-pgT8xyZbfs(MCJ
z#ii4@kUvn|1$le!XTo&jMNVIZj&RSb`!hDzfewD$&vIUa6&4nT@8?l<-z@ZA{QdiQ
zzd4X6ya2iQ`l1S;KiO{$v3XuO*pu<6gh$cJvjfu#L>ju=aYRBD^WPUt9?zPW^nU0R
z1!AIzczg)XaA@LER(W-&OetZ0X-a(&6-!u!V4D+UcfJ2NlAc%E(ZB)6;?2cb{6_-O
zY{gC`D(lAetD<tguB5du0e
zO(d5C?S=U*|QK*)ndwo{9>QOt!
zH5*YVqm~FxfK)31tYmxi_&40_@~*XLzUk6f(5*JIktHGw$NKuSB`PN7Ybq+gqqR9~
zJ7Z&Gkob%c9p!FO)m{jA_W}5B71dniN6a3NlTPVcoMB+#t#t&Ok8-AxKD1jdcn6MJS;{M=pU
zuSkBl1>pV>Q=6$?gw(Gkz7yW!DxHMUB26
zBcMe~BR#H|BNFC|Hb_e~!3c;6`&BHxqfk;d`N2w5*j-1sWx4r;r@B3>8U;P(Yny?1
zbn2U|+B;E!!o7(<45t$>UDpac6^jc!cY(%oj7u(UD9(&{-hwoxpbQiO@
zW#U&dI}X6Ne*M}Xi-a4&vU<(+
z?sCi2#KgS2Cz9$jSmha*wT(K%OLu2H;ba2Fsk#M|Ac7YHXkbQ0>nl>Aoo9x02g5UD
z(eg|LDvY0~BXRp6kHpL5yq@s~r_sxF=bWP2MbN-S1bz?HcCSf}XH|};GD+2_K3KX<
zb8GMNv2V6eKlaNjs%Zr^=8_5DHYVKY3vy#mkzOwIN(%Far{uP-oRjHjy+czCidV8$3OWrc>3+9=Zns``+3Fw3kGt-T
zdx>Vz}{qF+?hRb_cdQ<7;HEQQXwGgFOw#ml4Mn9GYkc~e5%VT8VD1QnpH9C
zG{mzRboHQUeG^fvQKY+i6t;Dr=wqCIdXt98ntE@3P-6M>pS5wn&d68VAH4JFugf*A
ze_^l2aUNp+N#nF|ybsh0x(|%{o8`U75*IO^*L&55hK7eDVmQ5BK#*B&l__R>SV)(7
zTprDE&q@QVgEDN$-Yan*1Na`ur#(c%L03*F36C6i@Y7Yb|1xM+8$iK5y4*_3Ql@`E
ze;`Z~6ZXpgLgj;+1fYa!jRi#vSzvIEn$}zqX%iVKKfL;&d|ZV6(_{C~|Nb>fT4hcY
zoMh1+41_y|!jj!`i;9dGwtfoAY{o6;@g~Sw$#N|cLw$0S@Y++JV%L3~BMm2*>VH1+
zb^L80NWOu{;zM>9_kQD^eXD-!;-Z5a;Srh8<;rH1Z9cj|Wk
zXpk?Qu2A%sLaq<^mzEXPgmuo1N-kIdGKIm`@8S(
z&(q{8C7+6)#(;cM{S8)u@&l1>Hr4Dq?w)>>Il!_;^fyFVG{a+UtRPL9slfVA*iQ7BPJnd9F%z?Kw|)=Zaf}e@a52>4w&p%m`}|;2)sIbWHVi2@2Kc
z_!HnY(GyP7_lt9AMGF8}qGHU)Q-&e5!2(Zv;
zdU~TkX8{p~do{WB@c2e8u(zF)ix^vg9}0YRbwa#|)tdz}Z2Z&}c$5Ix;1>x0M7SfI
zVJAyy3$KTvg}C1KI2)=EtXb{!Bt`9aE=)y&a9p$$HQ>AInwtI9
zA^)7xKV>4?YF!{~1A@xkVEBOGb0^Rn1ov#d5YxzXpqyQQ?!N$6eVV^|$Gl8O<|5I4
zQXEJSa*HYDk9jmv$d^uWk)uYnXqt;Lvqf8f@7#4oQCV-?t`CBJBOUhq;JdMbOAmyT
z_9~?5JpB6bgAdTHwH3ObBklL7@ca`41`UnN-&P#S<@y+<$4I-zFS4Qe#nsb7BE%NRc~m$0+Lxm
z70(TxKvm#9`O?Ii<&25RN|`prGZ1b^8IQHQPd8V$HKPSNmH2+;ZSw?D_8&%3`zwB?
zILihHU0DJmoYupsl~uGQXg1v;Q5o~S(f8sRH!bp|N9N+pzWr_h9CfU+)>;_$!`*l0aKYudMZ=WPUQopQq
z?ZXWIj%63>Hs1vojfb!_eXz1BZZLfS9L4zH1_!-Vz&!z}G|6m5V2_CdmJ1TVJaG*9
zB^`1_t#U<|om14gInf}&abwUi2o$TegXLBvzY?lk=)@{ND@jbE=2fUzwgQNZV#*i6
zjkIZ1m&SU^eqydVL{4jb2Hs82;ylI|LwIc#@2ttC^wOKhylys-rCwl+GKs=K?BWhG
z!UZw_xD?Y7Y=~v}{ptXH-|0|-EBU?I4+TKSmo1}y0FE>E$>M~GK@Nhe`hqXgy?&{l
z+(_Cv51}PuG6}L?@PkQ%h~R1e#7L`0oKuUIG005=jq*Xd1fOzWvZO3^I=T6BPFvIc
zDiI-`)z*FBb>{p{SyefW9KrXBuoS*{aR~{~T;d1Pf1rg0G`gUpqk|@yNNWnkk4-YKRSjh9w^H^chCP@Q)>mi
zl6iS_(S1YkyuWJdaX>IeF!sK-2r>=Ha
zmb<7<$B*=WFn<+trA;8=BKEnNwfmaVFt~vDf)3U8jC(juO)Xp_zioI$)CS9O)VAUE
znrOnT9cXA^j3wDFq-vhBlWghV7eyV@m{FLU7+G^#hsCTI90W>aL)E*c?7tJlyxB@I
zSc(Gat=~CraQM|!4tZ5oZpp{`MUm~qD+6GKTwGnP0@DU`;XQfsq!J2!_Vnq?C0@~Q
z{jv5h(*Q6O%TOMOT#$|F+
zCA%AIX@T~{y3&9DWL6@HGf_sFduk=KOcF8A>$$>Q{dyG>MTj1%Ubez5UC)_?=X3N<
zdF&P%rS41b-Gh3O*5Ot^=iK>-+^Ga$@#d6V(RRV-3hJR}CtSYJQEtm3=0?A5pD=8i->q>r$N{)BJxhjvch$?
z35LB&`ln<51?-bc;kON*;v870&2#!Ep2Z=*YM}|qNUmD&GvyHeac-j@?xjBB?)YP4
z(|z?+B9!1$gpnGUu-QRk-|?CZ(-M*cBGEwe-rNE)eWoFGGC=OtbX=c+d?LVG(xRe|
zKyy=fS65&R!1w@w|O(9OCQOuS+fGxOKd59YKSD%Vq-P0z3vHo&$mnymZ0ydHaTw
zF==4ymOAdJrn+tt3Ey29fG!#9##4O2LpL_!bi0<8bQk?azZEQ|Win(*WsByH?OAt4qiBX2vBU-5>Ysq{m25Kg>%IYcaQww3m%1=gCOXRN-Zi41-c)(5gBGdO
z9^bqv5p6~d(7wy5Q!e}5aozNn_P{caOOVITmo~Mqs6;h-XawEd#GteTeWTNJa|Y(-
z{h+V(4!@;3_{rZ=sW=A)CGs*gl?gJny
zk1Gm%K=|RymoK0R3K$LVT`vQyocZ0=Ko3}}cX+Xp>=oh|1IBcmb1*Oh%s&N%bdiGu
z!_F}IFBx62QzM5#42Kg#i?@u!ySlBdZ5+=hVxmAQW69ZD)KhxRC@8kFPY^f~Cft>Y
zD8G3%qUFTQ@z#*8pZ)n`dPlPSqAL~drvu_5Z@*|i0m(T8ODbMt)fWjn`y1PmH6bi%
zxw|bOlme`3XrTdqe;Sdnf5$4OYRZf>=@L>q5kWxjUo{mo3%Sl+Ta?EiiA
zj!nH_qQUDz6YLm-3kSMF+c!UPSQZr?l{&sEqa*W4+(f=hGIaHIb^hlv0-J(3Ebz}C
zlUGz!Ag>%l_L#CGz>mifOYov21pHu3OiiAfkOP%=3VVu9*>Z`Rp5%pUA_n9pL_Yd(
zkZkADa5m0^-GKT*vM@NmeYV|CJPjDrgFPW{T=;9*g^keLEy$}#Rob)BC<$l|woXkW
zF}JJtb8~a&f37Ni|E51diID<<>5VWN|BHpI
zaqPH~e^fTIR9$u({C#M+hpQV8K#nzLr19s*xa9wx1vtt|&v@Iq*1mbvmtlQD?}(Ez
z#4nr-eQrWhW__8jy;**4afyQfP*#KBJ~IM234f4tg!Os=lJ2Lud7rox4Db#(iE)#G
zA6tmc$@p
z$vVK(mw6*1xi|nvX0
z0s$xoJy{&`2Ol*{I?P|eexOmh>HpsiOXkn2YbIj$mK-#G54x?AB}Y*hGW>+=-0GFp
zC;~ELXy=IGa?H1Cw@Pf0hEj^C)*hhJD%)u)Y1AHks4X@)lSNc?AU=L0KTa0$OE_
za3^NLoCosV)0T~(=?qZKpj!JaCw|Zy(04O!!uc~QO6=|1w{fta7?d58GNYXaPb?wh
z(LUQAJ{6A&5lP-6naI#6{j?AT+^4zn^qQ$Yewff}1xf>C`I@>qoynag@A*^wh`qf^
z!CUq7R!qDGp%bbuLE*29u=h60B9ha$V_y3oYmiVV+;w(Nx$T>4u+w}ONpI=C1MzDm
zyr_B^f657`D|#}Y)Ppv1j(Z@hs_aEptAAH-ptY55(C}Nb_I;A~D`l@#7q7?+$jRQ_
zKVjPxKUiu`^S-}c@&J;D(DqP`%aML&R#xrp+1yN{I}gxDC_!Uk=RM@0sXgiU9ZGm}
zl*2FC*&O#rWFxIQM%D_@Z{JX{A3uNp?kS>ex~|Pv^sh*tp5q2kq<`%J7RA?rF-0lq
z^AB}N>*ta$73RiXy?m4ggRh&~ch_zuyW3j}^KGSu6csUnYEKThhR4<4T!e<3yKILY+q90MU0Kb>BNM03P>F`Jh=pMnS2t
zU6b&-yM%z|UUOONk@R;DH~}mi0{RPd^j{l;kPcOAjOZtvp5|kE1a#k31{Ny4x?^@-
zfpN|052p`~@qMZjQbuUo#yHucG>N6KMXz}Ac~A3BaSA93MR
zZ1%Qnt5eykZuMq&Sx}nJQ}K5|U0sflOu*;HaHkdEmo<}qU!suMF#SWN#BzDJ&g6#p
zV$4_z!%1e&Po7NPgG3z}0d!5i=HRFpLz0(G=8cpWxs3*^BY?nKx4=Pz0oETG8aymw
zPQCdW+h5t)qlKS2z@)_>1CFu_ELQN))qH$>jEarDIjP1Zr0l>?X5Dav3^aqze=M;f
z6pCn4?P}|#CZP8F0hK=#+}OhRH}-r80Z;E2K*;WVOdq6tRz=plOxOw>7-o{j}-`=7rqHSD&KI)lfZxJxp=BBy@gU;vt=b!-n=W#^$IiP_?iW-&j@mS2m
zsyz<`^!2|F9(o9l{`2aGe;-iv-_J<$uQ4}N?z&IE-@6DHw2g&0J7b%5Bn@6KaJU{+
zoOq^|zygX}!;B>Vvn14~k7N2yDWV2!mIR>??SYJXlK3jO;3A})u{WiyB60?2kt!#;
z!s1T9|Fgqq|LkQ|mGp9hmt)7Xd!mJfw}R#>+`{1AIq380Y~Zu;;|lbPX>a7K$L-oo
z_klt)6FYbT*4JVuE|;8fN+Ij%;Ize&PZxuZl}ON62^NU}YeK~6un6f6pWI5%#MuyM
zi+&98No*jVAt@@Ft&`EQMXZP@G&a|I9+)ZkRojDbu@vRB@hhUbgBgi=CXP+-kHD0^
zK>Rx{qR~9sA*b+|A;|{e7#3{itq+Qv;ZU<#)uQ$gthy}K8CCwUC{uz2TRoyJ_GdQb
z_P^A^HZcpwai0WBZP_m#Vcj^np-4o1r=^;Wos|Ib)(Y$rL+SZX!Ll^lF39ZsP%Pf~3h0DMbDhY-eZZXsJu4cc@2t`S;3}
z+afAS0k>ZFYSrc?RckXFw>Bp>HReXcLu~AZ`kZiK%nTJVTAJayj`(~6b=ImnQjnuX
z8`yUQ>=q!vvGH-xWC5DBmnN>1&KN1FmRbga0+lS-k5~4^2Lul#zhAcqPoH{s7*Zce
zz-bT1>+I?GUd5f5PPGgu#CNCH;oud*DRb%4h)`nLESD7TVB7gp8yO?&1LjFhSqZwl
zOO9l+R*~2fBS~DEI4CG!ZKFVIBs!c+i`-_V=iZCW{g9gt$wW^H&X=%nlggh04O!
z+JZwjYa|IHyJ(_Xw-=j-Qe*Jb>}>`nk)$_!f$3FB6^A8E4Yfwh_H|(r*){lv34^iw
zaRx~zlp~!u7mDOlOq0L{Ev9&Q0oaT*?ZQtm9y%Cxn{O>m{A_v5@Z$1ZUSN
z(MqQ4Ih)7Fca*%BrbX}^Ne{8>Y7INL4L{9DA4-X
zGz&h3aAl2tu(M+asn6xznTkARdeEhvr&53qGRQGq>>sp7M!bA|wyj4V+!zHB7F4OuPuhU0#2e+k)(X^Sv^MWjzB)yd_qLGhzh5kMgwZxdAvvNalA8Zoaz;0dW!;nkbK8
zaM=p?(sq$KFz1d)k<ZsRj{Q)5pV{rQ@dsmCU?yfZM>PB8o)+>g{#nlbs<1^$dB~~<6@ma{
zx3e*jr2QN-^XSJ_vW_Qqw$vwmPg+`92Y&7BJ_64&A7Yh~(+{?zzeo7;Yknxu3FxBQ
zF1fzc7+_~-{}vL`4(Rf?Z{NJ<$@YH=WBKT%L^x_Vs)@)Lb9@hU_+5WxD-36Mk|F_z
z9i24f+Yd=_w-93c99M+6a9Sk!LMM;?*>_m(m{sdec7LLEi|n4V?PtvV3)2N@2T#n=
z%ksNAUwQgINP}=y$Da@|S&&-nmTYiv;QDmOc`2A(gMFCQsWGt?D(2>uJ8y~n$?wy}E2N`xkAlq+PEOIj)QiZkCOX3-M(
z6T@)aaev)14JF}3)K)Q}Clt?Gd!k*KeZae2#$C~XE(HHCJwz8;bpdnEQJA6v4Mc
zQRPK%pl;-xE}}Cx|2c+4o^4g$y^BmtlvGy60;?K;2brn`R@8)Mnm>hX*5Iuexyczq
z*{cUPW*~$&c?)(NbDZV8k8nrkPdhoMqaEx&;0;$%@!tiO9IeH2I92b+
zUCa3sE5;n;k@V&C%t>&^g$reWxO-ogxtRmEE1=XV=ya5
zM%_pln28Y=s>})|rR=0O?Cj-#{Ztgc_yg5(o=3sQ?`Fdp;m9-GLIL+|19rYLHSl~m
zxwrsG+T3iBLgvUaIH8h~G_yyMtjo1J22DOHPSL#bw!=jNa_G#etT%;c&qr!cWoS0U
zA7(I{(i=Cmc%coiu)|n~{?6CXAgH!fy5?UIucf(z0Cy@pr4cqSj
za$g)>#&mP;WJ}rh)6yeNJ6@BtMQv}@9>TN2ZYo$P4yL<70l2=gv9h}>DK7p>R#x_O
z=baGGF-vOrde+7&p7{=5d{S)CrX*wK#hP|}7zIMHGbZ3f86qBB=}K*@DH+I~Al`Aw
zZkneCB~_WN@_HU6a^p*iuB?ClXjIWf-L8a>qnGqLCylkb9_`ns!)eK~5nErN1WSb4!d=jd
z!b~QFhU;Oj^!LPvkdDU=W-X?yGV((lvq9ogIHy4(tb`u~a&>yBfu8&o1^tbI4Qea$
ziYz9h43ViNxx>n@1>%>CWulHzjiL=`t}~
z)A{B%?oW#JhLjbYhd2e9S7+iE#xa9iWD`kcH-8mYFY@F2jXk-V%R26%gF2N3khm*H
zIzy|~ZEE-LLhj+rD424E#lexs~1#s(B)B1|8{(8Q!j6hc?yI}lvXb(2-@CJR
zEFv%JAd|=vGl_CfV7e+JzT?`fLHcBVoBfUAr*$NT~MuR8p80Ae4~>uJ24zV!E>ZB)U|?
z%M}&((z~bbPyLkQTkXdP3or6k*L%!lh}N34Q5X~Yoz1j%utx%C#8hK?yV?cAI1&lk
zY4D9B)wH_rED%P)gSw>F%yGc!oeEN8G+wKT@2B{~;Wk2-WfO9tWLx3vd9g*FBT}oj
zk~BzWQ(Cr`Sv~amY}po}Br!ZH1=fRXc9=IyBKvHHDV;*jzh*PrL3hQ}OfCp|VeLdU
zQ)Mkw(cI5ho*3c8AgUW5TJtYPd`W)a@^|*pZaVREeOyNn)4!VgR0^k~aihf-+n+ea
zK4Pt>4GH@uxmPxz&i(amh2~?hPII%Yc1DoN589qk?Jzfh!b0zMSFX7nb
zvQk%6VniUge8Njey#&-I5`uNUZoj1)qTy~{|CqG6^g>hHGYlA
zc_5CbtKpj*(zk(G1ej>#t4dRv)S|$Ox=r*Ch@%5<5tg5WCPQ4ptswWoqpA|%*EOON
zGhzqac3=J{kM!OQ+!^(9UgFfCZFubARQ~Vx{ohSRFygGmQEz-|iOJ+j%)T6tuxPU&
zR2|GuC1_1RNIisc7t^^dp)PzGKo^fVXJPgEZgtsqvXkIfI!I~lKs8#&SI4#s24#0#
z?{ll*GjkwoZT0tCz6$vHY!{wiIyBacRf=Y)q6W=NF+O;qW8wAx-N;yu77mi($X7He
zKX*3;_k}O{F4KJYRt^`Lo1Kny5K!^HL=f@f03qI|PoH4GjR~|n-pEt@_S+Z_w-*gX
zE_k2y{RuvRACH^Stz|%<(h<$c9v>O&Ji@e#fw!w-r6)pteoqEr^3Y!HP^+jY5p+42
zoA0gLHym}jdT#nVxPJw~P@*qdG5_4~STwqLd-S>COq>gmmAY#FN?UA<;r9R`Ljm~s
zw((NURa3Jk`=_29t{n4KwAnd1r)`MD$NkJhrKTgKz*RDy$*5N*`wbD%AVBB6=1!x5
zB@q<*hk!vScy4f0r`LSX8^Ypj_>%ZYh3`^gSkAN|A7wvp
z=6eMWu9NUAv)uBPF*F8iKC(#m>PPX6vUo;zFZZhA)1HD>VINeER7@#-*?*v46JHqQ
zAsGKT|NG_Q!soQweXM+Sl0!k8hE)w-Pco|XOM?}H>&I8HYJ4HZi6~OK>Jrv>&~bcu
zd~g215{goXcYAl*%vW~zQ&BL=W^V*?B`g$X3K-ud#lL3@6*NOE)gOn94jN%h7I7`)
z^24?M^=yf%J#hbms{fOICyki|
zZ&LV-Aw{LxtAizT`0r*L37-uRMn*>ZlQ}E?mRkYm3DAFY*>A(N+70n`Mal6=C@NyY
z01GCd=q&Sma0S30F1I5susngezdswg2QmO)qz8!F(D!uIZ?rrB*K&U`r3(}*&t9pn
z7rRC&PeAKXrr!yeFy!ZvbI|95skX@I=#JnwWW#$Gld82ou!y6;ca&2e)Uc|02!zop1Avl4&axyL|<|j
zqrB9ffc*P%NioX)I%=k)Sc)>C*(?8I^I|cB&Y8B79A6)OtU#0*G(NG(vxE0my{^=n6N4(LZzAv}_r$P_(aCO%|laqQ%a-B-g`-?G+oOuf0
zH3@4j7uk@1qV(&`tv>6TS#aj6JvKY4DeUu@+jzJ4v=G}@Q@t-Qd@eA?|Cb_nl&+h
zUSH5yWhqR}f0`BL?xP%Cy{5yS>5Zwae4cNPH>lNK07%3znZCW9L|u$|9QM
z2CDJdl@PsU32h01FJ5M9(Ch)57oqk1SR;E>`|%WSmro-!aA<=3#1yjyO>6L&*RM>W
zAQ)o*Qp^c4(DRAbI5WY)%8G)94?m82aPlH0IT_5)#h^&8t-eA+Dr=tFu?ILMCN_3Z
zZZ0J*E-vV5PtJz;E1(n9;NtHo)QIKbe>flG0e`d_og;zYXRNpraPb4R^~uB4k^v(Q
z&S!l%u(tgJ1Iw+J_$G_B`v8gxJY$}*M|Yr~X#_5R00&^Um}U7FPN+#b3}yupnHU>(
z)2dTdlp03n$^BhrRmADXD|pZLtMt0rv4G4VaGGqR_{nvwxt5FLq&HAwnp&lCq>(ri
z?`pMuVM=!RQT8yN@$9-BJ@4pUe`UDvjN=Ihc6n4$9}m{_&$6UuGCH}Tpo3Hc)23qS
z*_+S{sS894#M6y7{u!^E*D5JIE)|_ohMQZZoQG5}9)BUH?oEU4HrAYCzXsPb9kPAm
zHdbSP#fi+puZ_(9(U@^!5}xW`w|8~VFaI%njH*}HwJ#fe0Ue(2&P7O~C!phgrD3`A
z>(pavq%f4X<9F1YWWO49G|m7;&c3$LZ;}*h*;HxsnY42rSg`f-?KNng|G#VKJWkUJ
zzHKPVD?1c{{MyUTEcz4xu`AP(TSN&olrlXGXAZo
zub|b_UYiq9!LY)j$snq+w-;8#&s6-47&)x7mYd^kT#9t?VBCsXOIE%v2luKRFk_m2
zGqaN9d08{A;pH|Sx1#Q#I0BqGbBAP0b0l$>#e*XBapnuA9Usd+uK>})E~g^E3c
zybE;>XbLY3D#D~uS0yhCq2-Qjv12u5mhgvWRCgHacl)^XTR+{YV-SYMU$oYwve!fD
zJC0hu=JHYxmIwx>Vn#ZS
zUK?h6x{M5XOM($p;wp$oyZzPvOuJc0Q=npg$IMIu(7ILto*Il`&(zsb0*gw4;U*X<6yij#(eP)V^1YwDhk6VUs
zHi_g(88va6mS$xv4EEoo7A=f$9VN^XYJkdNV~du~1$4-r;U)<0_b$t_|QPaApn@RO$aIg{1m=i1R%
zQ106Vv_k*4ma+KXT`@J4?FxtL&Wf%Hbe0XZ#
z2Pl5o(RCdqXPV(aTJX&b&_;`)ywF;p{#yRx%NNE#gg4ljhr0d_dsY9))S?bc=>G0J
zQ0t#`ve2EXO#Hd)24v`z>8xyQHbY*c*oh7Ljt#1kT^*N{*tavW#ZkXp%NlGmoL};^
zfl`u4#I6#UJg(I*%3P5+R3%H(OGClFFdFq+HO>08;&kSCP_>-iSjkGjVzz0fTv1S0
z12{Bw_w;ji1Jk5aaj$_6D(6qaz8|%ySP5YpBChPxThO?M
zJ;dJNLFq5Rf2qKM?6&v3l80BcVrNz)s}{zDy9@Ihx$adbe@)){Jw^(H5B=hKq0Xt=
zM8Vaoy8IODGN*O%(Hm~nmYRY>yUfY5{e5}>hmEvy`(MqW85-<=%{hFi8gG4GE*W7Y~a`w0LD*wLQxg|{7!c1s;gh+-P$^HF=MjnG^FB2caq($0mjbz1bf^b{4
z?D90(__pZ>fx*}O4c(>FcA1Se!B)4#G^`FPilT?3-PA7yI@0zl7t4P9RZyd03JV
zwfup}TB@W_C^2kq>aQM&nD3jf*%wxj7TAF>!TUM221
zr96fXbCyi`PgKL$ipgFFcscT)7;#IgHnDJuQ-WA@@Dfq2zEs5F$32m^K5jN6m^QKG
z?JGA{I$4L@{VSd_wf4Y6Eso%8Ydsn&SsqU_3E93FI-Edz{n`-Jx06?ZTL&|ID@RAq
za!;UIm~`s~RSgwA{lA%+y1(`d^Yb|DmINy+E7_aRCYT8c8l)Kg{rw$7Lvm<3&&2TK%!N?PKX>}kBt&A_gaZ!1C}X#`Z69U7?>k9V-a1CB1ShVk#RSLnbd;KC!OX<_
z`tMvU%A1d5V@pamn(`cX-PG^zt2T-!&JJkJM4Np;ORj-$_tW8XQ2e3|)mem9;dtx&
zB3S5&zTctr>Lt195dPlct1jhQR(oT82m3(tSwKk7zixKfi2g47F<_!1Q6&GVd
zot|Gxb5g}YzPsV6EH-Grt0&WH@9ihEQ$9?oD{ofkI^anDEH(_C$-VP5FuB*=*u0t=
znRhADTTg)Lfpp!xHxPv2^WClykePb#SQJpMx5rTk;Mu9mw
zF?#SB)U5bVHDR}M6M_f&Ly_=Z3RI?xk
zH$@S0=NxpBw-VsP8+)=LI0nv`~3UHke=c=
zGg?Etf1lV)l1ckVf{f_}f1vLRX?b#SGY{FQ6C<|zbTG$%9xH?Js(v}5iIHtwWa6<5
z9-QK$`Vu^d9W|r%XK${EBh4kjRi|wO!FcD!wH(8nomnrI!jSHhjzs9Z_*0$x_0|
z(U%&>o-fAx^Z;cl*?LkirRr$Nq15Itx;|m(ic2;eyW@rdC&8)k7Ccona=q+ur%o2tpouZ08^sSCQ0`WeKkd`agBJo3?@o*oiz?gmAN
zjm^z}K(uuO2*U(E4=#t@@iMgauS^NRgkTGpsr9FDV*`;Am~x?xIRt)N&%`1wk2B-5
z+zl)Y0A}?YGXvL9a(eo3g-lTed3k{pE(aV|=y=sxWe_6xS|1b?plLcjZbxUf--9n6w{FT(1>aWx|pGTQ?aEFQ?@Ev>C>FZbk*<>WLRnQG38&mF{Y2f5u
z?`*u^^dEE6btS@9E{=bVYkMxuI@nC7s01?MZDS(=2#am^I8`f}e>3)caZT_M|8P5(GhN^)1Ui3zdOyBEUks%?s&+?C
zfk^q31v`93l<2E)_W}PzUGk?0@>ocZq`t{L(qqXK+-?WPTUH#A_e!W=pTt1lW>2EUiS2k~
zYOaaMxz67GC%0I}oE`(fdlE1kUuto6f(N}Y*_bla*uR(Lr6<1-w1dBx=x#s)5Ztok
z`uCMfvPWNhd~?klma8a=KW-V9Ut5vky_BB2;$~po7i#-1(f
zLIG2ury8Dd;_%36my+NH#5VTzb2xTP4#fr=KbQ5165!CM;L$f87}{ow3d}>};q-)FR?G<)6f_sXeg*JkZOb?F2}Pt;UG(PO
zL>*NDAY-y|yKGy6We5;b_}y99xGe3iR-fzwp
zm6+Owz4Y)m)laOayFd1O{n*ya6y^cE34?VMxs{bo>b3-M3p&~;ai6H3QDz$hu=JIU
z9Q-#m-LaONkPA&miR^nRCZ-CFCL8WfU|`d7^4*~x&R|tAGn)Dt&g}5R3D$nPy4`8G
zB926^M{zvjSKNH0GUG=g@k#j48+ZwVW7v;w<47pgsQ2j^*Vkibv7ZdRkZnqSt(*p2
zPnGcOl&)}dcwyKwW%Kp^9uc%A+dhA3x{$@!;qi5&TKK-qhCd&7rk2CZ6q3u;p6VRW
zy`X6}E@qc-^6$gUi>KXB(d#Qp2{aO1jqmdxW7LKq~wu#B=>WW~9d4wHS%Hu`*<_x`m4U_7UMW+p!KwMT-@D9!xB3