Merge branch 'master' of github.com:jcalixte/bons-programmeurs

This commit is contained in:
Julien Calixte
2020-07-12 13:31:49 +02:00
15 changed files with 407 additions and 87 deletions

View File

@@ -11,11 +11,12 @@
"@vue/composition-api": "^1.0.0-beta.1", "@vue/composition-api": "^1.0.0-beta.1",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"plyr": "^3.6.2", "plyr": "^3.6.2",
"pretty-checkbox": "^3.0.3",
"register-service-worker": "^1.7.1", "register-service-worker": "^1.7.1",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-router": "^3.2.0", "vue-router": "^3.2.0",
"vuex": "^3.4.0" "vuex": "^3.4.0",
"vuex-composition-helpers": "^1.0.18",
"vuex-persist": "^2.2.0"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^2.33.0", "@typescript-eslint/eslint-plugin": "^2.33.0",

View File

@@ -1,5 +1,5 @@
<template> <template>
<div id="app"> <div id="app" class="plume">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="120" width="120"

View File

@@ -1,8 +1,14 @@
<template> <template>
<div class="chilled-music"> <div class="chilled-music" v-show="false">
<div class="plyr__video-embed" id="player" v-show="false"> <div
<iframe src="https://www.youtube-nocookie.com/embed/5qap5aO4i9A"></iframe> class="chilled-music plyr__video-embed"
</div> id="player"
allowfullscreen
allowtransparency
allow="autoplay"
data-plyr-provider="youtube"
data-plyr-embed-id="5qap5aO4i9A"
></div>
</div> </div>
</template> </template>
@@ -48,7 +54,19 @@ export default defineComponent({
} }
onMounted(() => { onMounted(() => {
chilledcow.video = new Plyr('#player') chilledcow.video = new Plyr('#player', {
youtube: {
noCookie: true,
rel: 0,
showinfo: 0,
iv_load_policy: 3,
modestbranding: 1
},
quality: {
default: 240,
options: [240]
}
})
chilledcow.video.on('play', onPlay) chilledcow.video.on('play', onPlay)
chilledcow.video.on('pause', onPause) chilledcow.video.on('pause', onPause)
chilledcow.video.on('ready', () => { chilledcow.video.on('ready', () => {

View File

@@ -8,30 +8,33 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, computed } from '@vue/composition-api' import { defineComponent, computed } from '@vue/composition-api'
import { useInterval } from '../hooks/useInterval'
const MIN_INTERVAL = 1 const MIN_INTERVAL = 1
export default defineComponent({ export default defineComponent({
name: 'IntervalInput', name: 'IntervalInput',
props: { props: {
editable: { type: Boolean, required: true }, editable: { type: Boolean, required: true }
interval: { type: Number, required: true }
}, },
setup(props, { emit }) { setup() {
const { interval, setInterval } = useInterval()
const minus = () => { const minus = () => {
if (props.interval > MIN_INTERVAL) { if (interval.value > MIN_INTERVAL) {
emit('update', props.interval - 1) setInterval(interval.value - 1)
} }
} }
const plus = () => { const plus = () => {
emit('update', props.interval + 1) setInterval(interval.value + 1)
} }
const label = computed(() => const label = computed(() =>
props.interval > MIN_INTERVAL ? 'minutes' : 'minute' interval.value > MIN_INTERVAL ? 'minutes' : 'minute'
) )
return { return {
interval,
minus, minus,
plus, plus,
label label

View File

@@ -0,0 +1,68 @@
<template>
<div class="zone-music">
<div class="pm-field">
<label for="use-music" data-pm-holder>
<span :class="{ disabled: withMusic }">Focus</span>
<input
type="checkbox"
name="use-music"
id="use-music"
:checked="withMusic"
@change="setWithMusic(!withMusic)"
data-pm-checkbox-toggle
/>
<span :class="{ disabled: !withMusic }">Chill</span>
</label>
</div>
<ChilledMusic v-if="withMusic" :play="play" @play="play" @pause="pause" />
</div>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api'
import { useMusic } from '@/hooks/useMusic'
export default defineComponent({
name: 'ZoneMusic',
components: {
ChilledMusic: () => import('@/components/ChilledMusic.vue')
},
props: {
play: {
type: Boolean,
required: true
}
},
setup(_, { emit }) {
const { withMusic, setWithMusic } = useMusic()
const play = () => {
emit('play')
}
const pause = () => {
emit('pause')
}
return { withMusic, setWithMusic, play, pause }
}
})
</script>
<style lang="scss" scoped>
@import '@/styles/variables';
label[data-pm-holder] {
display: flex;
justify-content: center;
margin: auto;
max-width: 300px;
span {
flex: 1;
}
}
.disabled {
color: darken($color, 60);
}
</style>

13
src/hooks/useInterval.ts Normal file
View File

@@ -0,0 +1,13 @@
import { useGetters, useActions } from 'vuex-composition-helpers'
import { GetterTree } from 'vuex'
import { State, RootActions } from '@/store'
export const useInterval = () => {
const { interval } = useGetters<GetterTree<State, State>>(['interval'])
const { setInterval } = useActions<RootActions>(['setInterval'])
return {
interval,
setInterval
}
}

View File

@@ -1,9 +1,13 @@
import { ref } from '@vue/composition-api' import { useGetters, useActions } from 'vuex-composition-helpers'
import { GetterTree } from 'vuex'
import { State, RootActions } from '@/store'
export const useMusic = () => { export const useMusic = () => {
const hasMusic = ref(true) const { withMusic } = useGetters<GetterTree<State, State>>(['withMusic'])
const { setWithMusic } = useActions<RootActions>(['setWithMusic'])
return { return {
hasMusic withMusic,
setWithMusic
} }
} }

View File

@@ -1,6 +1,7 @@
import { ref, computed, watch } from '@vue/composition-api' import { ref, computed, watch } from '@vue/composition-api'
import { StopwatchState } from '@/types/StopwatchState' import { StopwatchState } from '@/types/StopwatchState'
import { notify } from '@/utils/notification' import { notify } from '@/utils/notification'
import { useInterval } from './useInterval'
const format = (num: number) => { const format = (num: number) => {
return num.toString().padStart(2, '0') return num.toString().padStart(2, '0')
@@ -16,9 +17,9 @@ const formatSeconds = (seconds: number) => {
export const useTimer = () => { export const useTimer = () => {
const timeState = ref<StopwatchState>('stopped') const timeState = ref<StopwatchState>('stopped')
const seconds = ref(0) const seconds = ref(0)
const interval = ref(5) const { interval } = useInterval()
const sessionSeconds = ref(interval.value * 60) const sessionSeconds = ref(interval.value * 60)
const isProgrammer1Turn = ref(true) const isDev1Turn = ref(true)
const time = computed(() => formatSeconds(seconds.value)) const time = computed(() => formatSeconds(seconds.value))
const session = computed(() => formatSeconds(sessionSeconds.value)) const session = computed(() => formatSeconds(sessionSeconds.value))
@@ -36,14 +37,15 @@ export const useTimer = () => {
stopwatchId = setInterval(() => { stopwatchId = setInterval(() => {
seconds.value++ seconds.value++
if (seconds.value % intervalSeconds.value === 0) { const isTimeToChange = seconds.value % intervalSeconds.value === 0
if (isTimeToChange) {
sessionSeconds.value = intervalSeconds.value sessionSeconds.value = intervalSeconds.value
isProgrammer1Turn.value = !isProgrammer1Turn.value isDev1Turn.value = !isDev1Turn.value
const dev = isProgrammer1Turn.value ? 'Dev 1' : 'Dev 2' const dev = isDev1Turn.value ? 'Dev 1' : 'Dev 2'
notify('Changement !', { notify('Change!', {
body: `${dev}, à votre tour !` body: `${dev}, your turn!`
}) })
} }
}, 1000) }, 1000)
@@ -69,7 +71,7 @@ export const useTimer = () => {
pause() pause()
seconds.value = 0 seconds.value = 0
sessionSeconds.value = intervalSeconds.value sessionSeconds.value = intervalSeconds.value
isProgrammer1Turn.value = true isDev1Turn.value = true
} }
return { return {
@@ -80,6 +82,6 @@ export const useTimer = () => {
timeState, timeState,
time, time,
session, session,
isProgrammer1Turn isDev1Turn
} }
} }

View File

@@ -25,8 +25,8 @@ if (process.env.NODE_ENV === 'production') {
setNotificationInstance((title, options?) => setNotificationInstance((title, options?) =>
sw.showNotification(title, options) sw.showNotification(title, options)
) )
notify('Une nouvelle version est disponible !', { notify('An update is available!', {
body: `Fermer l'appli pour la mettre à jour.` body: `Reload the app to install the update.`
}) })
}, },
offline() { offline() {

View File

@@ -1,11 +1,55 @@
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex, { ActionContext, ActionTree } from 'vuex'
import VuexPersistence from 'vuex-persist'
Vue.use(Vuex) Vue.use(Vuex)
export default new Vuex.Store({ export interface State {
state: {}, interval: number
mutations: {}, withMusic: boolean
actions: {}, }
modules: {}
export interface RootActions extends ActionTree<State, State> {
setInterval: (ctx: ActionContext<State, State>, payload: number) => void
setWithMusic: (ctx: ActionContext<State, State>, payload: boolean) => void
}
const vuexLocal = new VuexPersistence<State>({
key: 'binome',
storage: window.localStorage
}) })
const SET_INTERVAL = 'SET_INTERVAL'
const WITH_MUSIC = 'WITH_MUSIC'
const store = new Vuex.Store<State>({
state: {
interval: 5,
withMusic: true
},
getters: {
interval: ({ interval }) => interval,
withMusic: ({ withMusic }) => withMusic
},
mutations: {
[SET_INTERVAL](state, interval: number) {
state.interval = interval
},
[WITH_MUSIC](state, withMusic: boolean) {
state.withMusic = withMusic
}
},
actions: {
setInterval({ commit }, interval: number) {
if (interval > 0) {
commit(SET_INTERVAL, interval)
}
},
setWithMusic({ commit }, withMusic: number) {
commit(WITH_MUSIC, withMusic)
}
},
plugins: [vuexLocal.plugin]
})
export default store

View File

@@ -1,9 +1,7 @@
@import url('https://fonts.googleapis.com/css2?family=Fira+Mono&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Fira+Mono&display=swap');
@import '~pretty-checkbox/src/pretty-checkbox';
$color: #f8efba; @import './variables';
$bgcolor: #2c3a47; @import './plume';
$font-family: 'Fira Mono', monospace;
body { body {
text-align: center; text-align: center;

172
src/styles/plume.scss Normal file
View File

@@ -0,0 +1,172 @@
:root {
--pm-placeholder-color: #999;
--pm-form-field-margin-bottom: 32px;
--pm-form-caption-font-size: 0.8em;
--pm-fieldset-border-style: solid;
--pm-fieldset-border-width: 1px;
--pm-fieldset-border-color: rgba(127, 127, 127, 0.4);
--pm-label-font-size: 1em;
--pm-label-font-weight: 600;
--pm-label-margin-bottom: 24px;
--pm-input-height: 40px;
--pm-input-box-shadow: none;
--pm-input-padding: 8px 16px;
--pm-input-border-style: solid;
--pm-input-border-radius: none;
--pm-input-border-width: 0 0 2px 0;
--pm-input-border-color: rgba(127, 127, 127, 0.4);
--pm-input-background-color: rgba(200, 200, 200, 0.1);
--pm-input-switch-size: 24px;
--pm-input-switch-border-width: 2px;
--pm-textarea-padding: 16px;
--pm-textarea-min-height: 100px;
}
.plume fieldset {
padding: 12px;
border-style: var(--pm-fieldset-border-style);
border-width: var(--pm-fieldset-border-width);
border-color: var(--pm-fieldset-border-color);
border-radius: var(--pm-input-border-radius);
}
.plume fieldset legend {
padding: 0 10px;
color: var(--pm-primary-color);
}
.plume .pm-field,
.plume fieldset {
width: 100%;
display: block;
margin-bottom: var(--pm-form-field-margin-bottom);
}
.plume label,
.plume legend {
display: block;
color: var(--pm-app-text-color);
font-weight: var(--pm-label-font-weight);
margin-bottom: var(--pm-label-margin-bottom);
}
.plume label[data-pm-holder],
.plume legend[data-pm-holder] {
font-weight: 300;
margin: 0 4px;
align-items: center;
display: inline-flex;
height: var(--pm-input-height);
}
.plume label.pm-required::after,
.plume legend.pm-required::after {
content: '*';
margin-left: 8px;
color: var(--pm-color-error);
}
.plume
input:not([type='range']):not([type='button']):not([type='reset']):not([type='submit']):not([type='checkbox']):not([type='radio']),
.plume textarea {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
.plume
input:not([type='range']):not([type='button']):not([type='reset']):not([type='submit']):not([type='checkbox']):not([type='radio']),
.plume select,
.plume textarea {
width: 100%;
display: block;
box-sizing: border-box;
height: var(--pm-input-height);
padding: var(--pm-input-padding);
box-shadow: var(--pm-input-box-shadow);
border-style: var(--pm-input-border-style);
border-color: var(--pm-input-border-color);
border-width: var(--pm-input-border-width);
border-radius: var(--pm-input-border-radius);
background-color: var(--pm-input-background-color);
}
.plume input[type='checkbox'],
.plume input[type='radio'] {
margin: 0 8px;
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
position: relative;
display: inline-block;
width: var(--pm-input-switch-size);
height: var(--pm-input-switch-size);
border: solid var(--pm-input-switch-border-width);
border-color: var(--pm-input-border-color);
}
.plume input[type='checkbox']::before {
top: 2px;
left: 2px;
content: '';
opacity: 0;
visibility: hidden;
position: absolute;
background-color: var(--pm-primary-color);
height: calc(100% - 4px);
width: calc(100% - 4px);
}
.plume input[type='checkbox']:checked,
.plume input[type='radio']:checked {
border-color: var(--pm-primary-color);
}
.plume input[type='checkbox']:checked::before,
.plume input[type='radio']:checked::before {
opacity: 1;
visibility: visible;
}
.plume input[type='radio'],
.plume input[type='radio']:checked::before {
border-radius: 100%;
}
.plume input[type='checkbox'][data-pm-checkbox-toggle] {
border-radius: 25px;
height: var(--pm-input-switch-size);
cursor: pointer;
width: calc(var(--pm-input-switch-size) * 2);
}
.plume input[type='checkbox'][data-pm-checkbox-toggle]::before {
top: 2px;
left: 2px;
content: '';
opacity: 1;
visibility: visible;
position: absolute;
border-radius: 100%;
height: calc(100% - 4px);
width: calc(
var(--pm-input-switch-size) - (var(--pm-input-switch-border-width) * 2) -
4px
);
background-color: var(--pm-input-border-color);
}
.plume input[type='checkbox'][data-pm-checkbox-toggle]:checked {
border-color: var(--pm-input-border-color);
}
.plume input[type='checkbox'][data-pm-checkbox-toggle]:checked::before {
content: '';
top: 2px;
left: calc(var(--pm-input-switch-size) + 2px);
background-color: var(--pm-primary-color);
}
.plume:not(.pm-no-effects) input[type='checkbox']::after,
.plume:not(.pm-no-effects) input[type='checkbox']::before {
transition: 0.1s;
}
.plume:not(.pm-no-effects) [data-pm-success]:not([data-pm-success=''])::after {
-webkit-animation: slide-down 0.3s ease-out;
animation: slide-down 0.3s ease-out;
}
.plume:not(.pm-no-effects) [data-pm-error]:not([data-pm-error=''])::after {
-webkit-animation: slide-down 0.3s ease-out;
animation: slide-down 0.3s ease-out;
}
.plume:not(.pm-no-effects) [data-pm-warning]:not([data-pm-warning=''])::after {
-webkit-animation: slide-down 0.3s ease-out;
animation: slide-down 0.3s ease-out;
}
.plume:not(.pm-no-effects) [data-pm-info]:not([data-pm-info=''])::after {
-webkit-animation: slide-down 0.3s ease-out;
animation: slide-down 0.3s ease-out;
}

View File

@@ -0,0 +1,8 @@
$color: #f8efba;
$bgcolor: #2c3a47;
$font-family: 'Fira Mono', monospace;
:root {
--pm-app-surface-color: #{#2c3a47};
--pm-primary-color: #{$color};
}

View File

@@ -1,67 +1,40 @@
<template> <template>
<div class="home"> <div class="home">
<IntervalInput <IntervalInput :editable="timeState === 'stopped'" />
:interval="interval"
:editable="timeState === 'stopped'"
@update="(int) => (interval = int)"
/>
<section class="developers"> <section class="developers">
<DevSession <DevSession position="1" :is-turn="isDev1Turn" :session="session" />
position="1" <DevSession position="2" :is-turn="!isDev1Turn" :session="session" />
:is-turn="isProgrammer1Turn"
:session="session"
/>
<DevSession
position="2"
:is-turn="!isProgrammer1Turn"
:session="session"
/>
</section> </section>
<div class="global-time">{{ time }}</div> <div class="global-time">{{ time }}</div>
<div class="actions"> <div class="actions">
<button v-show="timeState === 'stopped'" @click="start">commencer</button> <button v-show="timeState === 'stopped'" @click="start">start</button>
<button v-show="timeState === 'started'" @click="pause">pause</button> <button v-show="timeState === 'started'" @click="pause">pause</button>
<button v-show="timeState === 'started'" @click="clear">arrêter</button> <button v-show="timeState === 'started'" @click="clear">stop</button>
</div> </div>
<div class="music"> <ZoneMusic
<div class="pretty p-switch p-fill"> class="music"
<input
type="checkbox"
name="use-music"
id="use-music"
v-model="hasMusic"
/>
<div class="state p-success">
<label for="use-music">son</label>
</div>
</div>
<ChilledMusic
v-if="hasMusic"
:play="timeState === 'started'" :play="timeState === 'started'"
@play="start" @play="start"
@pause="pause" @pause="pause"
/> />
</div> </div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from '@vue/composition-api' import { defineComponent } from '@vue/composition-api'
import { useTimer } from '@/hooks/useTimer' import { useTimer } from '@/hooks/useTimer'
import { useMusic } from '@/hooks/useMusic'
import DevSession from '@/components/DevSession.vue' import DevSession from '@/components/DevSession.vue'
import ChilledMusic from '@/components/ChilledMusic.vue' import ZoneMusic from '@/components/ZoneMusic.vue'
import IntervalInput from '@/components/IntervalInput.vue' import IntervalInput from '@/components/IntervalInput.vue'
export default defineComponent({ export default defineComponent({
name: 'Home', name: 'Home',
components: { components: {
DevSession, DevSession,
ChilledMusic, ZoneMusic,
IntervalInput IntervalInput
}, },
setup() { setup() {
const { hasMusic } = useMusic()
const { const {
interval, interval,
start, start,
@@ -70,11 +43,10 @@ export default defineComponent({
timeState, timeState,
time, time,
session, session,
isProgrammer1Turn isDev1Turn
} = useTimer() } = useTimer()
return { return {
hasMusic,
interval, interval,
start, start,
pause, pause,
@@ -82,7 +54,7 @@ export default defineComponent({
timeState, timeState,
time, time,
session, session,
isProgrammer1Turn isDev1Turn
} }
} }
}) })
@@ -109,4 +81,8 @@ button {
.global-time { .global-time {
font-size: calc(3em + 10vw); font-size: calc(3em + 10vw);
} }
.music {
margin: 1rem 0;
}
</style> </style>

View File

@@ -5335,6 +5335,11 @@ lodash.memoize@^4.1.2:
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash.template@^4.4.0: lodash.template@^4.4.0:
version "4.5.0" version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
@@ -6850,11 +6855,6 @@ pretty-bytes@^5.1.0:
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2"
integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg== integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg==
pretty-checkbox@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/pretty-checkbox/-/pretty-checkbox-3.0.3.tgz#d49c8013a8fc08ee0c2d6ebde453464bfdbc428e"
integrity sha1-1JyAE6j8CO4MLW695FNGS/28Qo4=
pretty-error@^2.0.2: pretty-error@^2.0.2:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3"
@@ -8722,6 +8722,19 @@ vue@^2.6.11:
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5" resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ== integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==
vuex-composition-helpers@^1.0.18:
version "1.0.18"
resolved "https://registry.yarnpkg.com/vuex-composition-helpers/-/vuex-composition-helpers-1.0.18.tgz#62b50a6ec34885c113b825b18d9efbbe0ee28dfe"
integrity sha512-IM0CankybZn7rwm9EXQ13fG3w6c/zl89yOmrgI2CKd2dQIud53Hh73x48D++sV8ea9K530/vKyoAP5/drg1Ajg==
vuex-persist@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/vuex-persist/-/vuex-persist-2.2.0.tgz#4acec75562896b045c43d319690b93d6bc1b2bbc"
integrity sha512-o/qbBeMcKZZqMvCXc7kfIew/5cjHxlP1f53rx5YYp3r2tk2kxXYK/UZumxKn7OXywlurl2r0mgkuBzH6nIWFjw==
dependencies:
flatted "^2.0.0"
lodash.merge "^4.6.2"
vuex@^3.4.0: vuex@^3.4.0:
version "3.5.1" version "3.5.1"
resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.5.1.tgz#f1b8dcea649bc25254cf4f4358081dbf5da18b3d" resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.5.1.tgz#f1b8dcea649bc25254cf4f4358081dbf5da18b3d"