diff --git a/src/application/index.js b/src/application/index.js
index 443a72587..934ae74dc 100644
--- a/src/application/index.js
+++ b/src/application/index.js
@@ -21,7 +21,7 @@ import { GROUP_ENABLED } from "./constants";
let imageBitmap;
const imageBitmapQueue = [];
-export default class ModV {
+class ModV {
_mediaStream;
_imageCapture;
setupMedia = setupMedia;
@@ -329,3 +329,5 @@ export default class ModV {
this.store.commit("fonts/SET_LOCAL_FONTS", fonts);
}
}
+
+export default new ModV();
diff --git a/src/application/plugins/context-menu/Menu.vue b/src/application/plugins/context-menu/Menu.vue
deleted file mode 100644
index eeaf7651f..000000000
--- a/src/application/plugins/context-menu/Menu.vue
+++ /dev/null
@@ -1,323 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/application/plugins/context-menu/MenuHandler.vue b/src/application/plugins/context-menu/MenuHandler.vue
deleted file mode 100644
index 594f4346c..000000000
--- a/src/application/plugins/context-menu/MenuHandler.vue
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/application/plugins/context-menu/MenuItem.vue b/src/application/plugins/context-menu/MenuItem.vue
deleted file mode 100644
index 696322469..000000000
--- a/src/application/plugins/context-menu/MenuItem.vue
+++ /dev/null
@@ -1,133 +0,0 @@
-
-
-
-
-
diff --git a/src/application/plugins/context-menu/index.js b/src/application/plugins/context-menu/index.js
deleted file mode 100644
index 60a96fd09..000000000
--- a/src/application/plugins/context-menu/index.js
+++ /dev/null
@@ -1,103 +0,0 @@
-import { Menu } from "nwjs-menu-browser";
-import contextMenuStore from "./store";
-import ContextMenuHandler from "./MenuHandler";
-
-function searchForSubMenus(menu) {
- let menus = [];
- const submenus = [];
-
- const parentItems = menu.items.filter(item => !!item.submenu);
- for (let i = 0, len = parentItems.length; i < len; ++i) {
- const item = parentItems[i];
-
- item.submenu.$id = `${menu.$id}-${i}`;
- item.submenu.isSubmenu = true;
- menus.push(item.submenu);
- submenus.push(item.submenu);
- menus = menus.concat(searchForSubMenus(item.submenu));
- }
- menu.submenus = submenus;
-
- return menus;
-}
-
-function buildMenu(e, id, options, vnode, store) {
- e.preventDefault();
- const menu = new Menu();
- menu.$id = id;
- menu.isSubmenu = false;
-
- if ("createMenus" in options) {
- if (typeof options.createMenus === "function") {
- options.createMenus();
- }
- }
-
- const menuItems = options.menuItems;
- for (let i = 0, len = menuItems.length; i < len; ++i) {
- menu.insert(menuItems[i], i);
- }
-
- // const moduleName = vnode.context.moduleName;
- // const controlVariable = vnode.context.variable;
- // const group = vnode.context.group;
- // const groupName = vnode.context.groupName;
-
- const hooks = store.getters["contextMenu/hooks"];
- let hookItems = [];
-
- const availableOptions = options.match;
- for (let i = 0, len = availableOptions.length; i < len; ++i) {
- const hook = availableOptions[i];
-
- if (hook in hooks) {
- hookItems = hookItems.concat(hooks[hook]);
- }
- }
-
- // for (let i = 0, len = hookItems.length; i < len; ++i) {
- // const item = hookItems[i];
-
- // if (options.internalVariable) {
- // menu.append(
- // item.buildMenuItem(moduleName, options.internalVariable, true)
- // );
- // } else {
- // menu.append(
- // item.buildMenuItem(moduleName, controlVariable, group, groupName)
- // );
- // }
- // }
-
- let menus = [];
- menus.push(menu);
- menus = menus.concat(searchForSubMenus(menu));
-
- for (let i = 0, len = menus.length; i < len; ++i) {
- const menu = menus[i];
- store.commit("contextMenu/addMenu", { Menu: menu, id: menu.$id });
- }
-
- store.dispatch("contextMenu/popup", { id, x: e.x, y: e.y });
- return false;
-}
-
-const ContextMenu = {
- name: "Context Menu",
- uiStore: contextMenuStore,
-
- install(Vue, _, uiStore) {
- Vue.component(ContextMenuHandler.name, ContextMenuHandler);
-
- Vue.directive("context-menu", {
- bind(el, binding, vnode) {
- el.addEventListener("contextmenu", e => {
- buildMenu(e, vnode.context._uid, binding.value, vnode, uiStore);
- });
- }
- });
- },
- component: ContextMenuHandler
-};
-
-export default ContextMenu;
diff --git a/src/application/plugins/context-menu/is-descendant.js b/src/application/plugins/context-menu/is-descendant.js
deleted file mode 100644
index 490cfaa56..000000000
--- a/src/application/plugins/context-menu/is-descendant.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export default function isDescendant(parent, child) {
- let node = child.parentNode;
- while (node !== null) {
- if (node === parent) {
- return true;
- }
- node = node.parentNode;
- }
- return false;
-}
diff --git a/src/application/plugins/context-menu/store.js b/src/application/plugins/context-menu/store.js
deleted file mode 100644
index 54abc20a1..000000000
--- a/src/application/plugins/context-menu/store.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import Vue from "vue";
-
-const state = {
- menus: {},
- activeMenus: [],
- visible: false,
- hooks: {
- default: []
- }
-};
-
-// getters
-const getters = {
- menus: state => state.menus,
- menu: state => id => state.menus[id],
- activeMenus: state => state.activeMenus.map(id => state.menus[id]),
- realActiveMenus: state => state.activeMenus,
- hooks: state => state.hooks
-};
-
-// actions
-const actions = {
- popdown({ commit }, { id }) {
- commit("popdown", { id });
- },
- popdownAll({ commit }, not) {
- commit("popdownAll", not);
- },
- popup({ commit }, { id, x, y }) {
- commit("popup", { id, x, y });
- }
-};
-
-// mutations
-const mutations = {
- addMenu(state, { Menu, id }) {
- // if(state.menus[id] && !force) return;
- Vue.set(state.menus, id, Menu);
- },
- popdown(state, { id }) {
- const indexToSplice = state.activeMenus.indexOf(id);
- if (indexToSplice < 0) {
- return;
- }
- state.activeMenus.splice(indexToSplice, 1);
- },
- popdownAll(state, not) {
- let toKeep = [];
- if (not) {
- toKeep = toKeep.concat(not);
- }
-
- Vue.set(state, "activeMenus", toKeep);
- },
- popup(state, { id, x, y }) {
- const existingMenuId = state.activeMenus.indexOf(id);
- if (existingMenuId < 0) {
- state.activeMenus.push(id);
- }
- Vue.set(state.menus[id], "x", x);
- Vue.set(state.menus[id], "y", y);
- Vue.set(state.menus[id], "visible", true);
- },
- editItemProperty(state, { id, index, property, value }) {
- Vue.set(state.menus[id].items[index], property, value);
- },
- addHook(state, { hookName, hook }) {
- if (!(hookName in state.hooks)) {
- Vue.set(state.hooks, hookName, []);
- }
-
- const hookArray = state.hooks[hookName];
- hookArray.push(hook);
- }
-};
-
-export default {
- namespaced: true,
- state,
- getters,
- actions,
- mutations
-};
diff --git a/src/application/worker/store/modules/groups.js b/src/application/worker/store/modules/groups.js
index d480ecb29..47851f1c8 100644
--- a/src/application/worker/store/modules/groups.js
+++ b/src/application/worker/store/modules/groups.js
@@ -262,6 +262,61 @@ const actions = {
dataOut = applyExpression({ inputId, value: dataOut });
commit("UPDATE_GROUP_BY_KEY", { groupId, key, data: dataOut, writeToSwap });
+ },
+
+ async duplicateModule({ commit }, { groupId, moduleId }) {
+ const group = state.groups.find(group => group.id === groupId);
+ const position =
+ group.modules.findIndex(moduleListId => moduleListId === moduleId) + 1;
+ const existingModule = store.state.modules.active[moduleId];
+
+ const existingInputIds = store.getters["modules/activeModuleInputIds"](
+ existingModule.$id
+ );
+
+ const existingInputLinks = existingInputIds.reduce((obj, id) => {
+ obj[id] = store.state.inputs.inputLinks[id];
+ return obj;
+ }, {});
+
+ const duplicateModule = await store.dispatch("modules/makeActiveModule", {
+ moduleName: existingModule.meta.name,
+ existingModule,
+ generateNewIds: true
+ });
+
+ const newInputIds = store.getters["modules/activeModuleInputIds"](
+ duplicateModule.$id
+ );
+
+ for (let i = 0; i < newInputIds.length; i += 1) {
+ const newInputId = newInputIds[i];
+ const existingInputId = existingInputIds[i];
+
+ if (existingInputLinks[existingInputId]) {
+ await store.dispatch("inputs/createInputLink", {
+ ...existingInputLinks[existingInputId],
+ inputId: newInputId
+ });
+ }
+
+ const existingExpression = store.getters["expressions/getByInputId"](
+ existingInputId
+ );
+
+ if (existingExpression) {
+ await store.dispatch("expressions/create", {
+ expression: existingExpression.expression,
+ inputId: newInputId
+ });
+ }
+ }
+
+ commit("ADD_MODULE_TO_GROUP", {
+ moduleId: duplicateModule.$id,
+ groupId,
+ position
+ });
}
};
diff --git a/src/application/worker/store/modules/inputs.js b/src/application/worker/store/modules/inputs.js
index bc4dd8fed..a0cfa8869 100644
--- a/src/application/worker/store/modules/inputs.js
+++ b/src/application/worker/store/modules/inputs.js
@@ -88,7 +88,12 @@ function getDefaultState() {
const state = getDefaultState();
const swap = getDefaultState();
-const getters = {};
+const getters = {
+ inputsByActiveModuleId: state => moduleId =>
+ Object.values(state.inputs).filter(
+ input => input.data.moduleId === moduleId
+ )
+};
const actions = {
setFocusedInput({ commit }, { id, title, writeToSwap }) {
diff --git a/src/application/worker/store/modules/modules.js b/src/application/worker/store/modules/modules.js
index 580ed463d..34b3afba7 100644
--- a/src/application/worker/store/modules/modules.js
+++ b/src/application/worker/store/modules/modules.js
@@ -44,7 +44,8 @@ async function initialiseModuleProperties(
isGallery = false,
useExistingData = false,
existingData = {},
- writeToSwap = false
+ writeToSwap = false,
+ generateNewIds = false
) {
const propKeys = Object.keys(props);
const propsWithoutId = [];
@@ -76,7 +77,8 @@ async function initialiseModuleProperties(
if (
(!isGallery && !useExistingData) ||
- (propsWithoutId.length && propsWithoutId.indexOf(propKey) > -1)
+ (propsWithoutId.length && propsWithoutId.indexOf(propKey) > -1) ||
+ generateNewIds
) {
const inputBind = await store.dispatch("inputs/addInput", {
type: "action",
@@ -117,6 +119,20 @@ async function initialiseModuleProperties(
return module;
}
+const getters = {
+ activeModuleInputIds: state => activeModuleId => {
+ const activeModule = state.active[activeModuleId];
+ return [
+ activeModule.meta.alphaInputId,
+ activeModule.meta.enabledInputId,
+ activeModule.meta.compositeOperationInputId,
+ ...store.getters["inputs/inputsByActiveModuleId"](activeModule.$id).map(
+ input => input.id
+ )
+ ];
+ }
+};
+
const actions = {
async registerModule(
{ commit, rootState },
@@ -209,7 +225,13 @@ const actions = {
async makeActiveModule(
{ commit, rootState },
- { moduleName, moduleMeta = {}, existingModule, writeToSwap }
+ {
+ moduleName,
+ moduleMeta = {},
+ existingModule,
+ generateNewIds = false,
+ writeToSwap
+ }
) {
const writeTo = writeToSwap ? swap : state;
const expectedModuleName = existingModule
@@ -224,7 +246,7 @@ const actions = {
if (moduleDefinition) {
module = {
meta: { ...moduleDefinition.meta, ...moduleMeta },
- ...existingModule,
+ ...(existingModule && JSON.parse(JSON.stringify(existingModule))),
$status: []
};
} else {
@@ -262,30 +284,8 @@ const actions = {
module.$props = JSON.parse(JSON.stringify(props));
- if (!existingModule) {
+ if (!existingModule || generateNewIds) {
module.$id = uuidv4();
- module.$moduleName = moduleName;
- module.props = {};
-
- await initialiseModuleProperties(props, module, moduleMeta.isGallery);
-
- const dataKeys = Object.keys(data);
- module.data = {};
-
- for (let i = 0, len = dataKeys.length; i < len; i++) {
- const dataKey = dataKeys[i];
-
- const datum = data[dataKey];
- module.data[dataKey] = datum;
- }
-
- module.meta.name = await getNextName(
- `${moduleName}`,
- Object.keys(state.active)
- );
- module.meta.alpha = 1;
- module.meta.enabled = false;
- module.meta.compositeOperation = "normal";
if (!moduleMeta.isGallery) {
const alphaInputBind = await store.dispatch("inputs/addInput", {
@@ -315,6 +315,31 @@ const actions = {
module.meta.compositeOperationInputId = coInputBind.id;
}
+ }
+
+ if (!existingModule) {
+ module.$moduleName = moduleName;
+ module.props = {};
+
+ await initialiseModuleProperties(props, module, moduleMeta.isGallery);
+
+ const dataKeys = Object.keys(data);
+ module.data = {};
+
+ for (let i = 0, len = dataKeys.length; i < len; i++) {
+ const dataKey = dataKeys[i];
+
+ const datum = data[dataKey];
+ module.data[dataKey] = datum;
+ }
+
+ module.meta.name = await getNextName(
+ `${moduleName}`,
+ Object.keys(state.active)
+ );
+ module.meta.alpha = 1;
+ module.meta.enabled = false;
+ module.meta.compositeOperation = "normal";
const { presets } = module;
@@ -347,7 +372,8 @@ const actions = {
moduleMeta.isGallery,
true,
existingModule,
- writeToSwap
+ writeToSwap,
+ generateNewIds
);
}
@@ -775,6 +801,7 @@ const mutations = {
export default {
namespaced: true,
state,
+ getters,
actions,
mutations
};
diff --git a/src/components/ActiveModule.vue b/src/components/ActiveModule.vue
index b050e7cb3..655451eef 100644
--- a/src/components/ActiveModule.vue
+++ b/src/components/ActiveModule.vue
@@ -6,12 +6,15 @@
@focus="clickActiveModule"
ref="activeModule"
:class="{ focused }"
+ v-contextMenu="
+ () => ActiveModuleContextMenu({ activeModule: module, groupId })
+ "
:id="`active-module-${id}`"
>
{{ name }}
@@ -102,10 +105,11 @@
+