diff --git a/package.json b/package.json index e20a1f635..949b94bb9 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "vue-property-decorator": "^8.3.0", "vue-smooth-dnd": "^0.8.1", "vuex": "^3.1.2", + "vuex-persistedstate": "^4.0.0-beta.3", "webpack": "^4.43.0", "webpack-2": "npm:webpack@2.7.0" }, diff --git a/src/App.vue b/src/App.vue index 761c951fc..1185ec431 100644 --- a/src/App.vue +++ b/src/App.vue @@ -66,6 +66,10 @@ + + + + @@ -101,6 +105,7 @@ import InfoView from "@/components/InfoView"; import Search from "@/components/Search"; import FrameRateDialog from "@/components/dialogs/FrameRateDialog"; import ErrorWatcher from "@/components/ErrorWatcher"; +import Plugins from "@/components/Plugins"; import getNextName from "@/application/utils/get-next-name"; import constants from "@/application/constants"; @@ -125,7 +130,8 @@ export default { ModuleInspector, Search, FrameRateDialog, - ErrorWatcher + ErrorWatcher, + Plugins }, data() { diff --git a/src/application/constants.js b/src/application/constants.js index 04fd2f2b4..c397f5612 100644 --- a/src/application/constants.js +++ b/src/application/constants.js @@ -9,5 +9,9 @@ export default { get LAYOUT_LOAD_ERROR() { return "layoutLoadError"; + }, + + get AUDIO_BUFFER_SIZE() { + return 512; } }; diff --git a/src/application/index.js b/src/application/index.js index 7b93ad05d..9d9c4928f 100644 --- a/src/application/index.js +++ b/src/application/index.js @@ -1,5 +1,9 @@ import Worker from "worker-loader!./worker/index.worker.js"; -import { setupMedia, enumerateDevices } from "./setup-media"; +import { + setupMedia, + enumerateDevices, + getByteFrequencyData +} from "./setup-media"; import setupBeatDetektor from "./setup-beat-detektor"; import setupMidi from "./setup-midi"; @@ -244,6 +248,7 @@ export default class ModV { const features = this.meyda.get(featuresToGet); this.updateBeatDetektor(delta, features); + features.byteFrequencyData = Array.from(getByteFrequencyData() || []); this.$worker.postMessage({ type: "meyda", payload: features }); for (let i = 0; i < featuresToGet.length; i += 1) { diff --git a/src/application/plugins/grab-canvas.js b/src/application/plugins/grab-canvas.js new file mode 100644 index 000000000..a4b9e93c9 --- /dev/null +++ b/src/application/plugins/grab-canvas.js @@ -0,0 +1,185 @@ +const mappingCanvas = new OffscreenCanvas(1, 1); +mappingCanvas.title = "mappingCanvas"; + +let timeout = 0; +let connection = undefined; +let outputContext = null; +let reconnect = false; + +const mappingContext = mappingCanvas.getContext("2d", { + // Boolean that indicates if the canvas contains an alpha channel. + // If set to false, the browser now knows that the backdrop is always opaque, + // which can speed up drawing of transparent content and images. + // (lights don't have an alpha channel, so let's drop it) + alpha: false, + desynchronized: true, + imageSmoothingEnabled: false +}); + +export default { + name: "Grab Canvas", + props: { + mappingWidth: { + type: "int", + default: 7, + min: 1, + max: 1024, + step: 1, + abs: true + }, + + mappingHeight: { + type: "int", + default: 7, + min: 1, + max: 1024, + step: 1, + abs: true + }, + + url: { + type: "text", + default: "ws://localhost:3006/modV" + }, + + reconnectAfter: { + type: "int", + default: 4000, + min: 1000, + max: 60000, + step: 1, + abs: true + }, + + shouldReconnect: { + type: "bool", + default: true + } + }, + + async init({ store, props }) { + if (!outputContext) { + outputContext = await store.dispatch("outputs/getAuxillaryOutput", { + name: "Fixture Canvas", + group: "Plugins", + canvas: mappingCanvas, + context: mappingContext, + reactToResize: false + }); + } + + mappingCanvas.width = props.mappingWidth; + mappingCanvas.height = props.mappingHeight; + + reconnect = props.shouldReconnect; + + this.setupSocket(props); + }, + + shutdown() { + this.stopReconnect(); + this.closeConnection(); + }, + + /** + * Create a WebSocket for example to luminave + */ + setupSocket(props) { + const { url, reconnectAfter } = props; + + // Close an old connection + this.closeConnection(); + + // Create a new connection + connection = new WebSocket(url); + + // Listen for errors (e.g. could not connect) + connection.addEventListener("error", event => { + console.error("grab-canvas: WebSocket: Error:", event); + + // Reconnect is allowed + if (reconnect) { + // Reconnect after a specific amount of time + timeout = setTimeout(() => { + this.setupSocket(props); + }, reconnectAfter); + } + }); + + // Connection is opened + connection.addEventListener("open", () => { + console.info("grab-canvas: WebSocket: Opened"); + }); + + connection.addEventListener("close", () => { + console.info("grab-canvas: WebSocket: Closed"); + }); + }, + + /** + * Close the WebSocket connection and stop reconnecting + */ + closeConnection() { + clearTimeout(timeout); + + if (connection !== undefined) { + connection.close(); + } + + connection = undefined; + }, + + /** + * Stop reconnecting to WebSocket + */ + stopReconnect() { + reconnect = false; + clearTimeout(timeout); + }, + + postProcessFrame({ canvas, props }) { + mappingContext.clearRect(0, 0, canvas.width, canvas.height); + mappingContext.drawImage( + canvas, + 0, + 0, + canvas.width, + canvas.height, + 0, + 0, + props.mappingWidth, + props.mappingHeight + ); + + const imageData = mappingContext.getImageData( + 0, + 0, + props.mappingWidth, + props.mappingHeight + ); + const { data } = imageData; + const arrayData = Array.from(data); + const rgbArray = arrayData.filter((value, index) => (index + 1) % 4 !== 0); + + this.send(rgbArray); + }, + + /** + * Send data to WebSocket if connection is established + * @param {Object} data + */ + send(data) { + // Connection is established + if (connection !== undefined && connection.readyState === 1) { + const message = { + _type: "modV", + colors: data + }; + + const messageString = JSON.stringify(message, null, 2); + + // Send JSON message + connection.send(messageString); + } + } +}; diff --git a/src/application/renderers/shader/default-shader.js b/src/application/renderers/shader/default-shader.js index 35eba0173..6c101c471 100644 --- a/src/application/renderers/shader/default-shader.js +++ b/src/application/renderers/shader/default-shader.js @@ -29,6 +29,8 @@ export default { uniform sampler2D iChannel2; // Texture #3 uniform sampler2D iChannel3; // Texture #4 uniform sampler2D u_modVCanvas; // modV's canvas + uniform sampler2D u_fft; // fft texture + uniform float u_fftResolution; // fft texture width varying vec2 vUv; @@ -62,6 +64,8 @@ export default { uniform sampler2D iChannel2; // Texture #3 uniform sampler2D iChannel3; // Texture #4 uniform sampler2D u_modVCanvas; // modV's canvas + uniform sampler2D u_fft; // fft texture + uniform float u_fftResolution; // fft texture width in vec2 vUv; out vec4 outColor; diff --git a/src/application/renderers/shader/index.js b/src/application/renderers/shader/index.js index 6bd248815..27de79e99 100644 --- a/src/application/renderers/shader/index.js +++ b/src/application/renderers/shader/index.js @@ -25,6 +25,7 @@ const indices = pex.indexBuffer([0, 1, 2, 3, 4, 5]); const commands = {}; let canvasTexture; +let fftTexture; const clearCmd = { pass: pex.pass({ @@ -63,6 +64,8 @@ function generateUniforms(canvasTexture, uniforms) { iChannel3: canvasTexture, iChannelResolution: [resolution, resolution, resolution, resolution], u_modVCanvas: canvasTexture, + u_fft: fftTexture, + u_fftResolution: fftTexture ? fftTexture.width : 1, u_delta: time / 1000, u_time: time }; @@ -138,7 +141,7 @@ async function setupModule(Module) { } } -function render({ module, props, canvas, context, pipeline }) { +function render({ module, props, canvas, context, pipeline, fftCanvas }) { resize({ width: canvas.width, height: canvas.height }); if (!canvasTexture) { @@ -152,12 +155,25 @@ function render({ module, props, canvas, context, pipeline }) { mag: pex.Filter.Linear, wrap: pex.Wrap.Repeat }); + fftTexture = pex.texture2D({ + data: fftCanvas.data || fftCanvas, + width: fftCanvas.width, + height: 1, + pixelFormat: pex.PixelFormat.RGBA8, + encoding: pex.Encoding.Linear, + wrap: pex.Wrap.Repeat + }); } else { pex.update(canvasTexture, { width: canvas.width, height: canvas.height, data: canvas.data || canvas }); + pex.update(fftTexture, { + width: fftCanvas.width, + height: 1, + data: fftCanvas.data || fftCanvas + }); } const shaderUniforms = {}; diff --git a/src/application/setup-media.js b/src/application/setup-media.js index 638c4f2f7..116080c4f 100644 --- a/src/application/setup-media.js +++ b/src/application/setup-media.js @@ -1,4 +1,9 @@ import Meyda from "meyda"; +import constants from "./constants"; + +let floatFrequencyDataArray; +let byteFrequencyDataArray; +let analyserNode; async function enumerateDevices() { const { _store: store } = this; @@ -114,7 +119,12 @@ async function setupMedia({ audioId, videoId }) { }); // Create new Audio Analyser - this.analyserNode = this.audioContext.createAnalyser(); + analyserNode = this.audioContext.createAnalyser(); + analyserNode.smoothingTimeConstant = 0; + + // Set up arrays for analyser + floatFrequencyDataArray = new Float32Array(analyserNode.frequencyBinCount); + byteFrequencyDataArray = new Uint8Array(analyserNode.frequencyBinCount); // Create a gain node this.gainNode = this.audioContext.createGain(); @@ -126,7 +136,7 @@ async function setupMedia({ audioId, videoId }) { this.audioStream = this.audioContext.createMediaStreamSource(mediaStream); // Connect the audio stream to the analyser (this is a passthru) (audio->(analyser)) - this.audioStream.connect(this.analyserNode); + this.audioStream.connect(analyserNode); // Connect the audio stream to the gain node (audio->(analyser)->gain) this.audioStream.connect(this.gainNode); @@ -139,7 +149,7 @@ async function setupMedia({ audioId, videoId }) { this.meyda = new Meyda.createMeydaAnalyzer({ audioContext: this.audioContext, source: this.audioStream, - bufferSize: 512, + bufferSize: constants.AUDIO_BUFFER_SIZE, windowingFunction: "rect", featureExtractors: ["complexSpectrum"] }); @@ -161,4 +171,20 @@ async function setupMedia({ audioId, videoId }) { return mediaStream; } -export { enumerateDevices, setupMedia }; +function getFloatFrequencyData() { + analyserNode.getFloatFrequencyData(floatFrequencyDataArray); + + return floatFrequencyDataArray; +} + +function getByteFrequencyData() { + analyserNode.getByteFrequencyData(byteFrequencyDataArray); + return byteFrequencyDataArray; +} + +export { + enumerateDevices, + setupMedia, + getFloatFrequencyData, + getByteFrequencyData +}; diff --git a/src/application/worker/index.worker.js b/src/application/worker/index.worker.js index a6aea5bfc..d51c6d85f 100644 --- a/src/application/worker/index.worker.js +++ b/src/application/worker/index.worker.js @@ -1,4 +1,5 @@ /* eslint-env worker node */ +const { default: constants } = require("../constants"); let lastKick = false; @@ -7,6 +8,8 @@ async function start() { const fs = require("fs"); const store = require("./store").default; const loop = require("./loop").default; + const grabCanvasPlugin = require("../plugins/grab-canvas").default; + const { tick: frameTick } = require("./frame-counter"); const { getFeatures, setFeatures } = require("./audio-features"); // const featureAssignmentPlugin = require("../plugins/feature-assignment"); @@ -43,6 +46,8 @@ async function start() { }); }); + store.dispatch("plugins/add", grabCanvasPlugin); + const renderers = require.context("../renderers/", false, /\.js$/); const rendererKeys = renderers.keys(); @@ -126,6 +131,15 @@ async function start() { }); store.dispatch("outputs/setWebcamOutput", webcamOutput.context); + const fftOutput = await store.dispatch("outputs/getAuxillaryOutput", { + name: "fft", + reactToResize: false, + width: constants.AUDIO_BUFFER_SIZE, + height: 1, + group: "audio", + id: "fft" + }); + // eslint-disable-next-line let raf = requestAnimationFrame(looper); let frames = 0; @@ -168,7 +182,7 @@ async function start() { payload: delta }); - loop(delta, getFeatures()); + loop(delta, getFeatures(), fftOutput); frameTick(); frames += 1; diff --git a/src/application/worker/loop.js b/src/application/worker/loop.js index d3310f1a1..a1ade5c43 100644 --- a/src/application/worker/loop.js +++ b/src/application/worker/loop.js @@ -9,8 +9,9 @@ const meyda = { windowing: applyWindow }; let bufferCanvas; let bufferContext; +const fallbackByteFrequencyData = Array(constants.AUDIO_BUFFER_SIZE).fill(0); -function loop(delta, features) { +function loop(delta, features, fftOutput) { if (!bufferCanvas) { bufferCanvas = new OffscreenCanvas(300, 300); bufferContext = bufferCanvas.getContext("2d"); @@ -37,6 +38,22 @@ function loop(delta, features) { return; } + // Put fftBuffer on fftCanvas + const byteFrequencyData = + features.byteFrequencyData || fallbackByteFrequencyData; + const fftBuffer = byteFrequencyData.reduce((arr, value) => { + arr.push(value, value, value, 255); + return arr; + }, []); + + const uInt8Array = Uint8ClampedArray.from(fftBuffer); + + fftOutput.context.putImageData( + new ImageData(uInt8Array, byteFrequencyData.length), + 0, + 0 + ); + // Update Input Links const inputLinkKeys = Object.keys(inputLinks); const inputLinkKeysLength = inputLinkKeys.length; @@ -79,7 +96,11 @@ function loop(delta, features) { const preProcessFrameFunctionsLength = preProcessFrameFunctions.length; for (let i = 0; i < preProcessFrameFunctionsLength; ++i) { - preProcessFrameFunctions[i].preProcessFrame({ features, store }); + preProcessFrameFunctions[i].preProcessFrame({ + features, + store, + props: preProcessFrameFunctions[i].$props + }); } const renderersWithTick = store.getters["renderers/renderersWithTick"]; @@ -100,7 +121,7 @@ function loop(delta, features) { continue; } - const { clearing, inherit, pipeline } = group; + const { clearing, inherit, inheritFrom, pipeline } = group; const aux = group.drawToCanvasId && auxillary[group.drawToCanvasId].context; const groupContext = group.context.context; @@ -124,8 +145,14 @@ function loop(delta, features) { } if (inherit) { + const canvasToInherit = + inheritFrom === -1 + ? lastCanvas + : groups.find(group => group.id === inheritFrom).context.context + .canvas; + drawTo.drawImage( - lastCanvas, + canvasToInherit, 0, 0, drawTo.canvas.width, @@ -134,7 +161,7 @@ function loop(delta, features) { if (pipeline && !isGalleryGroup) { bufferContext.drawImage( - lastCanvas, + canvasToInherit, 0, 0, drawTo.canvas.width, @@ -203,7 +230,8 @@ function loop(delta, features) { props, data: moduleData, pipeline, - kick + kick, + fftCanvas: fftOutput.context.canvas }); drawTo.restore(); @@ -314,7 +342,18 @@ function loop(delta, features) { } } - // main.getImageData(0, 0, main.canvas.width, main.canvas.height); + const postProcessFrameFunctions = + store.getters["plugins/postProcessFrame"] || []; + const postProcessFrameFunctionsLength = postProcessFrameFunctions.length; + + for (let i = 0; i < postProcessFrameFunctionsLength; ++i) { + postProcessFrameFunctions[i].postProcessFrame({ + canvas: main.canvas, + features, + store, + props: postProcessFrameFunctions[i].$props + }); + } } export default loop; diff --git a/src/application/worker/store/index.js b/src/application/worker/store/index.js index ae68f53e8..a1583374e 100644 --- a/src/application/worker/store/index.js +++ b/src/application/worker/store/index.js @@ -1,5 +1,6 @@ import Vue from "vue"; import Vuex from "vuex"; +import createPersistedState from "vuex-persistedstate"; const requireModule = require.context("./modules/", false, /\.js$/); const modules = {}; @@ -15,7 +16,22 @@ for (let i = 0, len = moduleKeys.length; i < len; i++) { Vue.use(Vuex); +const plugins = []; + +// createPersistedState doesn't work in the worker store, so don't run it there. +// That's okay as the worker doesn't need to know about mediaStream. +// If we want other persisted items that the worker needs to know about +// we'll need to implement something more complex to commit via postMessage. +if (self.document !== undefined) { + const dataState = createPersistedState({ + paths: ["mediaStream"] + }); + + plugins.push(dataState); +} + export default new Vuex.Store({ modules, + plugins, strict: false }); diff --git a/src/application/worker/store/modules/groups.js b/src/application/worker/store/modules/groups.js index c889b1d0e..9c62a753b 100644 --- a/src/application/worker/store/modules/groups.js +++ b/src/application/worker/store/modules/groups.js @@ -22,9 +22,9 @@ import uuidv4 from "uuid/v4"; * @property {Boolean} inherit Indicates whether the Group should inherit from another * Group at redraw * - * @property {Number} inheritFrom The target Group to inherit from, -1 being the previous - * Group in modV#Groups, 0-n being the index of - * another Group within modV#Groups + * @property {String|Number} inheritFrom The target Group to inherit from, -1 being the previous + * Group in modV#Groups, UUID4 string being the ID of + * another Group within modV.store.state.groups.groups * * @property {Boolean} pipeline Indicates whether the Group should render using pipeline * at redraw @@ -120,6 +120,7 @@ const actions = { hidden: args.hidden || false, modules: args.modules || [], inherit, + inheritFrom: -1, alpha: args.alpha || 1, compositeOperation: args.compositeOperation || "normal", context: await store.dispatch("outputs/getAuxillaryOutput", { @@ -194,6 +195,24 @@ const actions = { }); }, + updateGroupName({ commit }, { groupId, name }) { + const group = state.groups.find(group => group.id === groupId); + + store.commit("outputs/UPDATE_AUXILLARY", { + auxillaryId: group.context.id, + data: { + name + } + }); + + commit("UPDATE_GROUP", { + groupId, + data: { + name + } + }); + }, + async loadPresetData({ dispatch }, groups) { for (let i = 0, len = groups.length; i < len; i++) { const group = groups[i]; diff --git a/src/application/worker/store/modules/meyda.js b/src/application/worker/store/modules/meyda.js index eba5dee08..e55145ace 100644 --- a/src/application/worker/store/modules/meyda.js +++ b/src/application/worker/store/modules/meyda.js @@ -10,6 +10,7 @@ import uuidv4 from "uuid/v4"; const state = { features: [ + "buffer", "complexSpectrum", "rms", "zcr", diff --git a/src/application/worker/store/modules/outputs.js b/src/application/worker/store/modules/outputs.js index 1b47eb854..c0b8e7046 100644 --- a/src/application/worker/store/modules/outputs.js +++ b/src/application/worker/store/modules/outputs.js @@ -49,6 +49,7 @@ const actions = { { dispatch }, { canvas = new OffscreenCanvas(300, 300), + context, name, type = "2d", options = {}, @@ -63,13 +64,16 @@ const actions = { options.storage = "discardable"; } - const context = canvas.getContext(type, options); - canvas.width = width; - canvas.height = height; + if (!context) { + canvas.width = width; + canvas.height = height; + } + + const canvasContext = context || canvas.getContext(type, options); const outputContext = await dispatch("addAuxillaryOutput", { name, - context, + context: canvasContext, reactToResize, group, id @@ -127,6 +131,18 @@ const mutations = { Vue.delete(state.auxillary, id); }, + UPDATE_AUXILLARY(state, { auxillaryId, data }) { + if (state.auxillary[auxillaryId]) { + const dataKeys = Object.keys(data); + const dataKeysLength = dataKeys.length; + for (let i = 0; i < dataKeysLength; i += 1) { + const key = dataKeys[i]; + const value = data[key]; + state.auxillary[auxillaryId][key] = value; + } + } + }, + RESIZE_MAIN_OUTPUT(state, { width, height }) { if (!state.main.canvas) { return; diff --git a/src/application/worker/store/modules/plugins.js b/src/application/worker/store/modules/plugins.js index c3f540ed8..d4dcaa0c0 100644 --- a/src/application/worker/store/modules/plugins.js +++ b/src/application/worker/store/modules/plugins.js @@ -1,4 +1,5 @@ import store from "../"; +import getPropDefault from "../../../utils/get-prop-default"; import uuidv4 from "uuid/v4"; @@ -15,12 +16,16 @@ const state = []; const getters = { preProcessFrame: state => { - return state.filter(plugin => !!plugin.preProcessFrame); + return state.filter(plugin => !!plugin.preProcessFrame && plugin.enabled); + }, + + postProcessFrame: state => { + return state.filter(plugin => !!plugin.postProcessFrame && plugin.enabled); } }; const actions = { - add({ commit }, plugin) { + async add({ commit }, plugin) { if (!("name" in plugin)) { throw new Error("Plugin requires a name"); } @@ -30,14 +35,113 @@ const actions = { store.registerModule(storeName, plugin.store); } + plugin.$props = {}; + + if ("props" in plugin) { + const keys = Object.keys(plugin.props); + const keysLength = keys.length; + + for (let i = 0; i < keysLength; i += 1) { + const key = keys[i]; + const prop = plugin.props[key]; + + plugin.$props[key] = await getPropDefault(module, key, prop, false); + + if (!plugin.$props[key]) { + plugin.$props[key] = null; + } + } + } + plugin.id = uuidv4(); + plugin.enabled = false; commit("ADD_PLUGIN", plugin); + }, + + setEnabled({ commit }, { pluginId, enabled }) { + const plugin = state.find(item => item.id === pluginId); + + if (!plugin) { + return false; + } + + if (enabled) { + if ("init" in plugin) { + plugin.init({ store, props: plugin.$props }); + } + } else { + if ("shutdown" in plugin) { + plugin.shutdown({ store, props: plugin.$props }); + } + } + + commit("SET_PLUGIN_ENABLE", { pluginId, enabled }); + }, + + async updateProp({ commit }, { pluginId, prop, data }) { + const plugin = state.find(item => item.id === pluginId); + + if (!plugin) { + return false; + } + + let dataOut = data; + + const propData = plugin.props[prop]; + const currentValue = plugin.$props[prop]; + const { type } = propData; + + if (data === currentValue) { + return; + } + + if (store.state.dataTypes[type] && store.state.dataTypes[type].create) { + dataOut = await store.state.dataTypes[type].create(dataOut); + } + + if (!Array.isArray(dataOut)) { + const { strict, min, max, abs } = propData; + + if (strict && typeof min !== "undefined" && typeof max !== "undefined") { + dataOut = Math.min(Math.max(dataOut, min), max); + } + + if (abs) { + dataOut = Math.abs(dataOut); + } + + if (type === "int") { + dataOut = Math.round(dataOut); + } + } + + commit("UPDATE_PROP", { pluginId, prop, data: dataOut }); } }; const mutations = { ADD_PLUGIN(state, plugin) { state.push(plugin); + }, + + SET_PLUGIN_ENABLE(state, { pluginId, enabled }) { + const plugin = state.find(item => item.id === pluginId); + + if (!plugin) { + return false; + } + + plugin.enabled = enabled; + }, + + UPDATE_PROP(state, { pluginId, prop, data }) { + const plugin = state.find(item => item.id === pluginId); + + if (!plugin) { + return false; + } + + plugin.$props[prop] = data; } }; diff --git a/src/components/Control.vue b/src/components/Control.vue index 3870ef584..9715067f4 100644 --- a/src/components/Control.vue +++ b/src/components/Control.vue @@ -9,42 +9,43 @@
- +
- +
- +
- +
- +
- +
- +
- +
- +
-
- - diff --git a/src/components/ModuleInspector.vue b/src/components/ModuleInspector.vue index b5357cf88..04b2a0359 100644 --- a/src/components/ModuleInspector.vue +++ b/src/components/ModuleInspector.vue @@ -9,7 +9,7 @@
{{ module.meta.name }}
- diff --git a/src/components/Plugins.vue b/src/components/Plugins.vue new file mode 100644 index 000000000..7aa53acdd --- /dev/null +++ b/src/components/Plugins.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/yarn.lock b/yarn.lock index 9b24b71ec..c8b2afc64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3597,6 +3597,11 @@ deepmerge@^1.5.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753" integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ== +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + default-gateway@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" @@ -10506,6 +10511,11 @@ shell-quote@^1.6.1: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== +shvl@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/shvl/-/shvl-2.0.3.tgz#eb4bd37644f5684bba1fc52c3010c96fb5e6afd1" + integrity sha512-V7C6S9Hlol6SzOJPnQ7qzOVEWUQImt3BNmmzh40wObhla3XOYMe4gGiYzLrJd5TFa+cI2f9LKIRJTTKZSTbWgw== + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -12073,6 +12083,14 @@ vuex-localstorage@^1.0.0: resolved "https://registry.yarnpkg.com/vuex-localstorage/-/vuex-localstorage-1.0.0.tgz#3d680e9f6feee26d1a72a9c84ebd9daa64a06536" integrity sha1-PWgOn2/u4m0acqnITr2dqmSgZTY= +vuex-persistedstate@^4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/vuex-persistedstate/-/vuex-persistedstate-4.0.0-beta.3.tgz#89dd712de72d28e85cc95467d066002c1405f277" + integrity sha512-T4IRD27qoUWh+8qr6T6zVp15xO7x/nPgnU13OD0C2uUwA7U9PhGozrj6lvVmMYDyRgc36J0msMXn3GvwHjkIhA== + dependencies: + deepmerge "^4.2.2" + shvl "^2.0.2" + vuex@^3.1.2: version "3.4.0" resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.4.0.tgz#20cc086062d750769fce1febb34e7fceeaebde45"