From 09c28fda71242c93c62e6447bdbb2cd4ebf9f9ec Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Wed, 21 Jun 2023 22:51:34 +0100 Subject: [PATCH] feat(context-menu): add electron context menu (#790) * feat(context-menu): add electron context menu removes old context menu plugin and adds context menu with a few examples, including module duplication re #789 * fix(duplicate): clone inputlinks on module duplication * feat: add value reset for module controls * feat: add context menu item to reset module props to default * remove module * fix: stop propagation to prevent context menu * fix: duplicate expressions and fix duplication position * fix: fix after merge * fix: prevent drag on anything other than left mouse button * fix: remove left margin on module ghost * fix: prevent user-drag * fix: prevent grab state change on anything other than left mouse button --- src/application/index.js | 4 +- src/application/plugins/context-menu/Menu.vue | 323 ------------------ .../plugins/context-menu/MenuHandler.vue | 30 -- .../plugins/context-menu/MenuItem.vue | 133 -------- src/application/plugins/context-menu/index.js | 103 ------ .../plugins/context-menu/is-descendant.js | 10 - src/application/plugins/context-menu/store.js | 83 ----- .../worker/store/modules/groups.js | 55 +++ .../worker/store/modules/inputs.js | 7 +- .../worker/store/modules/modules.js | 83 +++-- src/components/ActiveModule.vue | 9 +- src/components/Control.vue | 8 +- src/components/GalleryItem.vue | 7 +- src/components/Group.vue | 23 +- src/components/ModuleControl.vue | 11 + src/components/StatusBar/BPMDisplay.vue | 14 +- src/components/directives/ContextMenu.js | 23 ++ .../inputs/RightClickNumberInput.vue | 4 +- src/main.js | 12 +- src/menus/context/activeModuleContextMenu.js | 59 ++++ .../context/activeModuleControlContextMenu.js | 36 ++ src/menus/context/bpmContextMenu.js | 14 + src/menus/context/galleryItemContextMenu.js | 36 ++ src/menus/context/groupContextMenu.js | 17 + src/ui-store/modules/ui-modules.js | 16 + 25 files changed, 380 insertions(+), 740 deletions(-) delete mode 100644 src/application/plugins/context-menu/Menu.vue delete mode 100644 src/application/plugins/context-menu/MenuHandler.vue delete mode 100644 src/application/plugins/context-menu/MenuItem.vue delete mode 100644 src/application/plugins/context-menu/index.js delete mode 100644 src/application/plugins/context-menu/is-descendant.js delete mode 100644 src/application/plugins/context-menu/store.js create mode 100644 src/components/directives/ContextMenu.js create mode 100644 src/menus/context/activeModuleContextMenu.js create mode 100644 src/menus/context/activeModuleControlContextMenu.js create mode 100644 src/menus/context/bpmContextMenu.js create mode 100644 src/menus/context/galleryItemContextMenu.js create mode 100644 src/menus/context/groupContextMenu.js 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 @@ +