diff --git a/src/index.html b/src/index.html
index aa67aa8..174c5a7 100644
--- a/src/index.html
+++ b/src/index.html
@@ -4,8 +4,14 @@
-
Tauri App
+ Lightningbeam
+
+
+
+
+
+
diff --git a/src/jsMenus.css b/src/jsMenus.css
new file mode 100644
index 0000000..db94868
--- /dev/null
+++ b/src/jsMenus.css
@@ -0,0 +1,221 @@
+html, body {
+ margin: 0;
+ height: 100%;
+}
+body { display: flex; flex-direction: column }
+.menubar { flex: 0 0 22px }
+div.below-menubar { flex: 1 1 0; min-height: 0;}
+
+.menu-item > [role=button] {
+ display: flex;
+ width: 100%;
+ border: none;
+ padding: 0px;
+ color: inherit;
+ background-color: inherit;
+ appearance: none;
+ outline: none;
+}
+
+.nwjs-menu {
+ font-family: 'Helvetica Neue', HelveticaNeue, 'TeX Gyre Heros', TeXGyreHeros, FreeSans, 'Nimbus Sans L', 'Liberation Sans', Arimo, Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ color: #2c2c2c;
+ -webkit-user-select: none;
+ user-select: none;
+ -webkit-font-smoothing: subpixel-antialiased;
+ font-weight: 400;
+ white-space: pre;
+}
+
+.contextmenu {
+ min-width: 100px;
+ background-color: #fafafa;
+ position: fixed;
+ opacity: 0;
+ transition: opacity 250ms;
+ margin: 0;
+ padding: 0 0;
+ list-style: none;
+ pointer-events: none;
+ border: 1px rgba(191, 191, 191, 0.8) solid;
+ border-radius: 4px;
+ box-shadow: rgba(43, 43, 43, 0.34) 1px 1px 11px 0px;
+ z-index: 2147483647;
+}
+
+.contextmenu {
+ opacity: 1;
+ transition: opacity 30ms;
+ pointer-events: all;
+}
+
+.contextmenu.submenu {
+ transition: opacity 250ms;
+}
+
+.contextmenu.submenu {
+ transition: opacity 150ms;
+ transition-timing-function: step-end;
+}
+
+.menu-item.normal,
+.menu-item.checkbox,
+.menu-item.radio {
+ cursor: default;
+ margin: 2px 0;
+ padding: 0 0;
+ box-sizing: border-box;
+ position: relative;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ justify-content: flex-start;
+ align-content: stretch;
+ align-items: flex-start;
+ width: 100%;
+}
+
+.contextmenu .menu-item.active,
+.menu-item.normal.submenu-active, .menu-item.normal.submenu-active:hover {
+ background-color: #499BFE;
+ color: #fff;
+}
+.menu-item:hover { background-color: inherit; }
+
+.menu-item.normal > div,
+.menu-item.checkbox > div,
+.menu-item.radio > div {
+ align-self: center;
+ vertical-align: middle;
+ display: inline-flex;
+ justify-content: flex-start;
+ flex-shrink: 0;
+}
+
+.menu-item.normal .icon {
+ display: inline-flex;
+ vertical-align: middle;
+ max-width: 16px;
+ max-height: 16px;
+ align-self: center;
+}
+
+li.menu-item.separator {
+ height: 2px;
+ background-color: rgba(128, 128, 128, 0.2);
+ margin: 5px 0;
+}
+
+.menu-item .modifiers,
+.menu-item .keys,
+.menu-item .icon-wrap,
+.menu-item .checkmark {
+ display: inline-flex;
+ align-items: center;
+ vertical-align: middle;
+}
+
+.menu-item .keys {
+ opacity: 0.4;
+}
+
+.menu-item .checkmark {
+ width: 22px;
+}
+.menu-item > [role=button] > .modifiers,
+.menu-item > [role=button] > .keys {
+ box-sizing: border-box;
+ padding: 0 6px;
+ text-align: right;
+ order: 0;
+ flex: 0 0 auto;
+ align-self: center;
+}
+
+.menu-item > [role=button] > .label {
+ padding: 0 22px 0 0;
+ order: 0;
+ flex: 1 0 auto;
+ align-self: center;
+ text-align: left;
+}
+
+.menu-item.disabled,
+.menu-item.disabled:hover,
+.contextmenu .menu-item.disabled:hover {
+ color: #ababab;
+}
+
+.menu-item.disabled:hover,
+.contextmenu .menu-item.disabled:hover {
+ background-color: transparent;
+}
+
+.menu-item .icon-wrap {
+ padding: 0 6px 0 0;
+ display: inline-flex;
+ align-self: center;
+}
+
+.menu-item .label-text {
+ align-items: center;
+ vertical-align: middle;
+}
+
+.menu-item.checkbox.checked .checkmark::before {
+ content: '✔';
+ text-align: center;
+ width: 100%;
+}
+
+.menu-item.radio.checked .checkmark::before {
+ content: '⊚';
+ text-align: center;
+ width: 100%;
+}
+
+.menubar {
+ height: 22px;
+ margin: 0;
+ padding: 0;
+ top: 0;
+ left: 0;
+ right: 0;
+ background-color: #eee;
+ z-index: 2147483647;
+}
+
+.menubar > .menu-item.normal {
+ display: inline-block;
+ width: auto;
+ height: 100%;
+}
+
+.menubar .menu-item.normal > div {
+ vertical-align: top;
+}
+.menubar > .menu-item {
+ margin: 0 4px 0 0;
+}
+.menubar > .menu-item.normal .checkmark,
+.menubar > .menu-item.normal .modifiers {
+ display: none;
+}
+
+.menubar .menu-item.normal .label {
+ padding: 0 3px;
+}
+
+.contextmenu.menubar-submenu {
+ transition: opacity 0ms;
+}
+
+/* Mac only?
+.contextmenu {
+ border-radius: 7px;
+}
+.contextmenu.menubar-submenu {
+ border-radius: 0 0 7px 7px;
+}
+*/
diff --git a/src/jsMenus.js b/src/jsMenus.js
new file mode 100644
index 0000000..b67ae84
--- /dev/null
+++ b/src/jsMenus.js
@@ -0,0 +1,893 @@
+class Menu {
+ constructor(settings = {}, itemArgs = []) {
+ const typeEnum = ['contextmenu', 'menubar'];
+ let items = [];
+ let type = isValidType(settings.type) ? settings.type : 'contextmenu';
+ let beforeShow = settings.beforeShow;
+ Object.defineProperty(this, 'items', {
+ get: () => {
+ return items;
+ }
+ });
+
+ Object.defineProperty(this, 'beforeShow', {
+ get: () => {
+ return beforeShow;
+ }
+ });
+
+ Object.defineProperty(this, 'type', {
+ get: () => {
+ return type;
+ },
+ set: (typeIn) => {
+ type = isValidType(typeIn) ? typeIn : type;
+ }
+ });
+
+ this.append = item => {
+ if(!(item instanceof MenuItem)) {
+ console.error('appended item must be an instance of MenuItem');
+ return false;
+ }
+ let index = items.push(item);
+ return index;
+ };
+
+ this.insert = (item, index) => {
+ if(!(item instanceof MenuItem)) {
+ console.error('inserted item must be an instance of MenuItem');
+ return false;
+ }
+
+ items.splice(index, 0, item);
+ return true;
+ };
+
+ this.remove = item => {
+ if(!(item instanceof MenuItem)) {
+ console.error('item to be removed is not an instance of MenuItem');
+ return false;
+ }
+
+ let index = items.indexOf(item);
+ if(index < 0) {
+ console.error('item to be removed was not found in this.items');
+ return false;
+ } else {
+ items.splice(index, 0);
+ return true;
+ }
+ };
+
+ this.removeAt = index => {
+ items.splice(index, 0);
+ return true;
+ };
+
+ this.node = null;
+ let nitems = itemArgs.length;
+ for(let i = 0; i < nitems; i++) {
+ let item = itemArgs[i];
+ if (item instanceof MenuItem)
+ items.push(item);
+ else
+ items.push(new MenuItem(item));
+ }
+
+ function isValidType(typeIn = '', debug = false) {
+ if(typeEnum.indexOf(typeIn) < 0) {
+ if(debug) console.error(`${typeIn} is not a valid type`);
+ return false;
+ }
+ return true;
+ }
+
+ }
+
+ createMacBuiltin() {
+ console.error('This method is not available in browser :(');
+ return false;
+ }
+
+ popup(x, y, itemNode = null, menubarSubmenu = false) {
+ Menu._keydownListen(true);
+
+ let setRight = false;
+
+ let submenu = itemNode != null || this.submenu;
+ this.submenu = menubarSubmenu;
+
+ menubarSubmenu = menubarSubmenu || this.menubarSubmenu;
+ this.menubarSubmenu = menubarSubmenu;
+ let top = Menu.contextMenuParent || document.body;
+ if (! Menu._topSheet && Menu.topsheetZindex > 0) {
+ let topSheet = document.createElement("div");
+ topSheet.setAttribute("style",
+ "position: fixed; top: 0px; bottom: 0px; left: 0px; right: 0px; z-index: "+Menu.topsheetZindex);
+ top.appendChild(topSheet);
+ Menu._topSheet = topSheet;
+ top = topSheet;
+ }
+ if (! Menu._topmostMenu) {
+ Menu._topmostMenu = this;
+ Menu._listenerElement = top;
+ top.addEventListener('mouseup', Menu._mouseHandler, false);
+ top.addEventListener('mousedown', Menu._mouseHandler, false);
+ }
+
+ let menuNode = this.buildMenu(submenu, menubarSubmenu);
+ menuNode.jsMenu = this;
+ this.node = menuNode;
+ Menu._currentMenuNode = menuNode;
+
+ if(this.node.parentNode) {
+ if(menuNode === this.node) return;
+ this.node.parentNode.replaceChild(menuNode, this.node);
+ } else {
+ ((! (menubarSubmenu && Menu._topSheet) && itemNode)
+ || top).appendChild(this.node);
+ }
+
+ let width = menuNode.clientWidth;
+ let height = menuNode.clientHeight;
+ let wwidth = top.offsetWidth;
+ const base_x = x, base_y = y;
+ if ((x + width) > wwidth) {
+ setRight = true;
+ if(submenu && ! menubarSubmenu) {
+ x = wwidth - itemNode.parentNode.offsetLeft + 2;
+ if (width + x > wwidth) {
+ x = 0;
+ setRight = false;
+ }
+ } else {
+ x = 0;
+ }
+ }
+
+ let wheight = top.offsetHeight;
+ if((y + height) > wheight) {
+ y = wheight - height;
+ if (y < -0.5)
+ y = wheight - height;
+ }
+
+ if(!setRight) {
+ menuNode.style.left = x + 'px';
+ menuNode.style.right = 'auto';
+ } else {
+ menuNode.style.right = x + 'px';
+ menuNode.style.left = 'auto';
+ }
+ // Don't have topSheet cover menubar, so we can catch mouseenter
+ if (Menu._menubarNode && Menu._topSheet)
+ Menu._topSheet.style.top = `${Menu._menubarNode.offsetHeight}px`;
+
+ menuNode.style.top = y + 'px';
+ if (! Menu.showMenuNode
+ || ! Menu.showMenuNode(this, menuNode, width, height, base_x, base_y, x, y)) {
+ menuNode.classList.add('show');
+ }
+ }
+
+ popdown() {
+ this.items.forEach(item => {
+ if(item.submenu) {
+ item.submenu.popdown();
+ } else {
+ item.node = null;
+ }
+ });
+ if(this.node && this.type !== 'menubar') {
+ Menu._currentMenuNode = this.node.parentMenuNode;
+ if (this.menubarSubmenu)
+ Menu.showSubmenuActive(this.node.menuItem, false);
+ if (Menu.hideMenuNode)
+ Menu.hideMenuNode(this, this.node);
+ this.node.parentNode.removeChild(this.node);
+ if (Menu._topSheet && Menu._topSheet.firstChild == null) {
+ Menu._topSheet.parentNode.removeChild(Menu._topSheet);
+ Menu._topSheet = undefined;
+ }
+ this.node = null;
+ }
+ if (this == Menu._topmostMenu) {
+ Menu._topmostMenu = null;
+ let el = Menu._listenerElement;
+ if (el) {
+ el.removeEventListener('mouseup', Menu._mouseHandler, false);
+ el.removeEventListener('mousedown', Menu._mouseHandler, false);
+ Menu._listenerElement = null;
+ }
+ }
+
+ if(this.type === 'menubar') {
+ this.clearActiveSubmenuStyling();
+ }
+ }
+
+ static showSubmenuActive(node, active) {
+ if (active)
+ node.classList.add('submenu-active');
+ else
+ node.classList.remove('submenu-active');
+ if (node.firstChild instanceof Element)
+ node.firstChild.setAttribute('aria-expanded',
+ active?'true':'false');
+ }
+
+ static popdownAll() {
+ Menu._topmostMenu.popdown();
+ return;
+ }
+
+ buildMenu(submenu = false, menubarSubmenu = false) {
+ if (this.beforeShow)
+ (this.beforeShow)(this);
+ let menuNode = document.createElement('ul');
+ menuNode.classList.add('nwjs-menu', this.type);
+ menuNode.spellcheck = false;
+ // make focusable
+ menuNode.setAttribute('contenteditable', 'true');
+ menuNode.setAttribute('role',
+ this.type === 'menubar' ? 'menubar' : 'menu'); // ARIA recommended
+ if(submenu) menuNode.classList.add('submenu');
+ if(menubarSubmenu) menuNode.classList.add('menubar-submenu');
+
+ menuNode.jsMenu = this;
+ menuNode.parentMenuNode = Menu._currentMenuNode;
+ this.items.forEach(item => {
+ if (item.beforeShow)
+ (item.beforeShow)(item);
+ if (item.visible) {
+ item.buildItem(menuNode,
+ this.type === 'menubar');
+ }
+ });
+ return menuNode;
+ }
+
+ static isDescendant(parent, child) {
+ let node = child.parentNode;
+ while(node !== null) {
+ if(node === parent) {
+ return true;
+ }
+ node = node.parentNode;
+ }
+ return false;
+ }
+
+ static _inMenubar(node) {
+ if (Menu._menubarNode === null)
+ return false;
+ while(node instanceof Element
+ && ! node.classList.contains('submenu')) {
+ if(node === Menu._menubarNode)
+ return true;
+ node = node.parentNode;
+ }
+ return false;
+ }
+
+ static _activateSubmenu(miNode) {
+ let item = miNode.jsMenuItem;
+ let wasActive = item.node.classList.contains('submenu-active');
+ Menu.showSubmenuActive(item.node, !wasActive);
+ // FIXME use select method
+ if(item.submenu) {
+ if(! wasActive) {
+ miNode.jsMenu.node.activeItemNode = item.node;
+ let rect = item.node.getBoundingClientRect();
+ item.popupSubmenu(rect.left, rect.bottom, true);
+ } else {
+ item.submenu.popdown();
+ miNode.jsMenu.node.currentSubmenu = null;
+ miNode.jsMenu.node.activeItemNode = null;
+ }
+ }
+ }
+
+ static _mouseHandler(e) {
+ e.preventDefault(); // prevent focus change on mousedown
+ let inMenubar = Menu._inMenubar(e.target);
+ let menubarHandler = e.currentTarget == Menu._menubarNode;
+ let miNode = e.target;
+ while (miNode && ! miNode.jsMenuItem)
+ miNode = miNode.parentNode;
+ /* mouseenter:
+ if selected sibling: unhighlight (and popdown if submenu)
+ select item and if submenu popup
+ mouseout (or mouseleave):
+ if (! submenu) unhighlight
+ mousedown:
+ if (miNode) select
+ else popdownAll
+ */
+ if (e.type=="mousedown" && inMenubar == menubarHandler
+ && (!miNode || miNode.jsMenuItem.menuBarTopLevel)) {
+ if (Menu._topmostMenu) {
+ Menu.popdownAll();
+ if (Menu.menuDone)
+ Menu.menuDone(null);
+ }
+ }
+ if ((inMenubar == menubarHandler) && miNode) {
+ if (e.type=="mousedown") {
+ Menu._activateSubmenu(miNode);
+ }
+ if (e.type=="mouseup") {
+ miNode.jsMenuItem.doit(miNode);
+ }
+ }
+ }
+
+ static focusMenubar() {
+ const items = Menu._menubar?.items;
+ if (items && items.length > 0 && items[0].node
+ && Menu._menubarNode) {
+ Menu._activateSubmenu(items[0].node);
+ return true;
+ }
+ return false;
+ }
+
+ static setApplicationMenu(menubar, parent=document.body, before=undefined) {
+ let oldNode = Menu._menubarNode;
+ if (oldNode) {
+ let oldParent = oldNode.parentNode;
+ if (oldParent != null)
+ oldParent.removeChild(oldNode);
+ oldNode.removeEventListener('mousedown', Menu._mouseHandler, false);
+ Menu._menubarNode = null;
+ }
+ if (before==undefined) {
+ before = parent.firstChild
+ }
+ if (menubar != null) {
+ let newNode = menubar.buildMenu();
+ newNode.jsMenuItem = null;
+ parent.insertBefore(newNode, before);
+ newNode.addEventListener('mousedown', Menu._mouseHandler, false);
+ Menu._menubarNode = newNode;
+ menubar.node = newNode;
+ }
+ Menu._menubar = menubar;
+ }
+
+ clearActiveSubmenuStyling(notThisNode) {
+ if (! this.node)
+ return;
+ let submenuActive = this.node.querySelectorAll('.submenu-active');
+ for(let node of submenuActive) {
+ if(node === notThisNode) continue;
+ Menu.showSubmenuActive(node, false);
+ }
+ }
+
+ static recursiveNodeFind(menu, node) {
+ if(menu.node === node) {
+ return true;
+ } else if(Menu.isDescendant(menu.node, node)) {
+ return true;
+ } else if(menu.items.length > 0) {
+ for(var i=0; i < menu.items.length; i++) {
+ let menuItem = menu.items[i];
+ if(!menuItem.node) continue;
+
+ if(menuItem.node === node) {
+ return true;
+ } else if(Menu.isDescendant(menuItem.node, node)) {
+ return true;
+ } else {
+ if(menuItem.submenu) {
+ if(recursiveNodeFind(menuItem.submenu, node)) {
+ return true;
+ } else {
+ continue;
+ }
+ }
+ }
+ }
+ } else {
+ return false;
+ }
+ return false;
+ }
+
+ isNodeInChildMenuTree(node = false) {
+ if(!node) return false;
+ return recursiveNodeFind(this, node);
+ }
+}
+
+// Parent node for context menu popup. If null, document.body is the default.
+Menu.contextMenuParent = null;
+
+Menu._currentMenuNode = null;
+
+Menu._keydownListener = function(e) {
+ function nextItem(menuNode, curNode, forwards) {
+ let nullSeen = false;
+ let next = curNode;
+ for (;;) {
+ next = !next ? null
+ : forwards ? next.nextSibling
+ : next.previousSibling;
+ if (! next) {
+ next = forwards ? menuNode.firstChild
+ : menuNode.lastChild;
+ if (nullSeen || !next)
+ return null;
+ nullSeen = true;
+ }
+ if (next instanceof Element
+ && next.classList.contains("menu-item")
+ && next.jsMenuItem.type != 'separator'
+ && ! (next.classList.contains("disabled")))
+ return next;
+ }
+ }
+ function nextMenu(menuNode, forwards) {
+ let menubarNode = menuNode.menuItem.parentNode;
+ let next = nextItem(menubarNode,
+ menubarNode.activeItemNode,
+ forwards);
+ if (next)
+ next.jsMenuItem.select(next, true, true, true);
+ return next;
+
+ }
+ function openSubmenu(active) {
+ active.jsMenuItem.selectSubmenu(active);
+ menuNode = Menu._currentMenuNode;
+ let next = nextItem(menuNode, null, true);
+ if (next)
+ next.jsMenuItem.select(next, true, false);
+ }
+ let menuNode = Menu._currentMenuNode
+ if (menuNode) {
+ let active = menuNode.activeItemNode;
+ switch (e.keyCode) {
+ case 27: // Escape
+ case 37: // Left
+ e.preventDefault();
+ e.stopPropagation();
+ if (e.keyCode == 37
+ && menuNode.jsMenu.menubarSubmenu
+ && nextMenu(menuNode, false))
+ return;
+ menuNode.jsMenu.popdown();
+ if (! Menu._topmostMenu && Menu.menuDone)
+ Menu.menuDone(null);
+ break;
+ case 32: // Space
+ case 13: // Enter
+ e.preventDefault();
+ e.stopPropagation();
+ if (active) {
+ if (active.jsMenuItem.submenu)
+ openSubmenu(active);
+ else
+ active.jsMenuItem.doit(active);
+ }
+ break;
+ case 39: // Right
+ e.preventDefault();
+ e.stopPropagation();
+ if (active && active.jsMenuItem.submenu)
+ openSubmenu(active);
+ else if (Menu._topmostMenu.menubarSubmenu)
+ nextMenu(menuNode, true);
+ break;
+ case 38: // Up
+ case 40: // Down
+ e.preventDefault();
+ e.stopPropagation();
+ let next = nextItem(menuNode,
+ menuNode.activeItemNode,
+ e.keyCode == 40);
+ if (next)
+ next.jsMenuItem.select(next, true, false);
+ break;
+ }
+ }
+}
+Menu._keydownListening = false;
+Menu._keydownListen = function(value) {
+ if (value != Menu._keydownListening) {
+ if (value)
+ document.addEventListener('keydown', Menu._keydownListener, true);
+ else
+ document.removeEventListener('keydown', Menu._keydownListener, true);
+ }
+ Menu._keydownListening = value;
+}
+
+Menu._isMac = typeof navigator != "undefined" ? /Mac/.test(navigator.platform)
+ : typeof os != "undefined" ? os.platform() == "darwin" : false
+
+// If positive, create a "sheet" above the Menu.contextMenuParent
+// at the given z-index.
+// Used to capture mouse-clicks - needed if there are iframes involved.
+Menu.topsheetZindex = 5;
+
+class MenuItem {
+ constructor(settings = {}) {
+
+ const typeEnum = ['separator', 'checkbox', 'radio', 'normal'];
+ let type = isValidType(settings.type) ? settings.type : 'normal';
+ let submenu = settings.submenu || null;
+ if (submenu && ! (submenu instanceof Menu))
+ submenu = new Menu({}, submenu);
+ let click = settings.click || null;
+ this.modifiers = settings.modifiers;
+ let label = settings.label || '';
+ let enabled = settings.enabled;
+ if(typeof settings.enabled === 'undefined') enabled = true;
+ let visible = settings.visible;
+ if(typeof settings.visible === 'undefined') visible = true;
+ let beforeShow = settings.beforeShow;
+
+ Object.defineProperty(this, 'type', {
+ get: () => {
+ return type;
+ }
+ });
+
+ Object.defineProperty(this, 'beforeShow', {
+ get: () => {
+ return beforeShow;
+ }
+ });
+
+ Object.defineProperty(this, 'submenu', {
+ get: () => {
+ return submenu;
+ },
+ set: (inputMenu) => {
+ console.warn('submenu should be set on initialisation, changing this at runtime could be slow on some platforms.');
+ if(!(inputMenu instanceof Menu)) {
+ console.error('submenu must be an instance of Menu');
+ return;
+ } else {
+ submenu = inputMenu;
+ }
+ }
+ });
+
+ Object.defineProperty(this, 'click', {
+ get: () => {
+ return click;
+ },
+ set: (inputCallback) => {
+ if(typeof inputCallback !== 'function') {
+ console.error('click must be a function');
+ return;
+ } else {
+ click = inputCallback;
+ }
+ }
+ });
+
+ Object.defineProperty(this, 'enabled', {
+ get: () => {
+ return enabled;
+ },
+ set: (inputEnabled) => {
+ enabled = inputEnabled;
+ }
+ });
+
+ Object.defineProperty(this, 'visible', {
+ get: () => {
+ return visible;
+ },
+ set: (inputVisible) => {
+ visible = inputVisible;
+ }
+ });
+
+ Object.defineProperty(this, 'label', {
+ get: () => {
+ return label;
+ },
+ set: (inputLabel) => {
+ label = inputLabel;
+ }
+ });
+
+ this.icon = settings.icon || null;
+ this.iconIsTemplate = settings.iconIsTemplate || false;
+ this.tooltip = settings.tooltip || '';
+ this.checked = settings.checked || false;
+
+ this.key = settings.key || null;
+ let accelerator = settings.accelerator;
+ if (! accelerator && settings.key) {
+ accelerator = (settings.modifiers ? (settings.modifiers + "+") : "") + settings.key;
+ }
+ if (accelerator instanceof Array)
+ accelerator = accelerator.join(" ");
+ if (accelerator) {
+ accelerator = accelerator
+ .replace(/Command[+]/i, "Cmd+")
+ .replace(/Control[+]/i, "Ctrl+")
+ .replace(/(Mod|((Command|Cmd)OrCtrl))[+]/i,
+ Menu._isMac ? "Cmd+" : "Ctrl+");
+ }
+ this.accelerator = accelerator;
+ if (accelerator && ! settings.key) {
+ const plus =
+ accelerator.lastIndexOf("+", accelerator.length-2);
+ if (plus > 0) {
+ this.modifiers = accelerator.substring(0, plus);
+ this.key = accelerator.substring(plus+1);
+ } else {
+ settings.key = accelerator;
+ }
+ }
+ this.node = null;
+
+ if(this.key) {
+ this.key = this.key.toUpperCase();
+ }
+
+ function isValidType(typeIn = '', debug = false) {
+ if(typeEnum.indexOf(typeIn) < 0) {
+ if(debug) console.error(`${typeIn} is not a valid type`);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ toString() {
+ return this.type+"["+this.label+"]";
+ }
+
+ _mouseoverHandle_menubarTop() {
+ let pmenu = this.node.jsMenuNode;
+ if (pmenu.activeItemNode) {
+ pmenu.activeItemNode.classList.remove('active');
+ pmenu.activeItemNode = null;
+ }
+ if (pmenu && pmenu.querySelector('.submenu-active')) {
+ if(this.node.classList.contains('submenu-active')) return;
+ Menu.showSubmenuActive(this.node, true);
+ this.select(this.node, true, true, true);
+ }
+ }
+
+ doit(node) {
+ if (! this.submenu) {
+ Menu.popdownAll();
+ if(this.type === 'checkbox')
+ this.checked = !this.checked;
+ else if (this.type === 'radio') {
+ this.checked = true;
+ for (let dir = 0; dir <= 1; dir++) {
+ for (let n = node; ; ) {
+ n = dir ? n.nextSibling
+ : n.previousSibling;
+ if (! (n instanceof Element
+ && n.classList.contains("radio")))
+ break;
+ n.jsMenuItem.checked = false;
+ }
+ }
+ }
+ if(this.click) this.click(this);
+ if (Menu.menuDone)
+ Menu.menuDone(this);
+ }
+ }
+
+ select(node, turnOn, popupSubmenu, menubarSubmenu = false) {
+ let pmenu = node.jsMenuNode;
+ if (pmenu.activeItemNode) {
+ pmenu.activeItemNode.classList.remove('active');
+ Menu.showSubmenuActive(pmenu.activeItemNode, false);
+ pmenu.activeItemNode = null;
+ }
+ if(pmenu.currentSubmenu) {
+ pmenu.currentSubmenu.popdown();
+ pmenu.currentSubmenu = null;
+ }
+ if(this.submenu && popupSubmenu)
+ this.selectSubmenu(node, menubarSubmenu);
+ else
+ node.classList.add('active');
+ node.jsMenuNode.activeItemNode = node;
+ }
+
+ selectSubmenu(node, menubarSubmenu) {
+ node.jsMenuNode.currentSubmenu = this.submenu;
+ if(this.submenu.node)
+ return;
+
+ let parentNode = node.parentNode;
+ let x, y;
+ if (menubarSubmenu) {
+ let rect = node.getBoundingClientRect();
+ x = rect.left;
+ y = rect.bottom;
+ } else {
+ x = parentNode.offsetWidth + parentNode.offsetLeft - 2;
+ y = parentNode.offsetTop + node.offsetTop - 4;
+ }
+ this.popupSubmenu(x, y, menubarSubmenu);
+ Menu.showSubmenuActive(node, true);
+ }
+
+ buildItem(menuNode, menuBarTopLevel = false) {
+ let node = document.createElement('li');
+ node.setAttribute('role', this.type === 'separator' ? 'separator' : 'menuitem');
+ node.jsMenuNode = menuNode;
+ node.jsMenu = menuNode.jsMenu;
+ node.jsMenuItem = this;
+ node.classList.add('menu-item', this.type);
+
+ menuBarTopLevel = menuBarTopLevel || this.menuBarTopLevel || false;
+ this.menuBarTopLevel = menuBarTopLevel;
+
+ if(menuBarTopLevel) {
+ node.addEventListener('mouseenter', this._mouseoverHandle_menubarTop.bind(this));
+ }
+
+ let iconWrapNode = document.createElement('div');
+ iconWrapNode.classList.add('icon-wrap');
+
+ if(this.icon) {
+ let iconNode = new Image();
+ iconNode.src = this.icon;
+ iconNode.classList.add('icon');
+ iconWrapNode.appendChild(iconNode);
+ }
+
+ let labelNode = document.createElement('span');
+ labelNode.classList.add('label');
+
+ let checkmarkNode = document.createElement('span');
+ checkmarkNode.classList.add('checkmark');
+
+ if(this.checked && !menuBarTopLevel)
+ node.classList.add('checked');
+
+ if(this.submenu)
+ node.setAttribute('aria-haspopup', 'true');
+
+ if(this.submenu && !menuBarTopLevel) {
+ node.addEventListener('mouseleave', (e) => {
+ if(node !== e.target) {
+ if(!Menu.isDescendant(node, e.target))
+ this.submenu.popdown();
+ }
+ });
+ }
+
+ if(!this.enabled) {
+ node.classList.add('disabled');
+ }
+
+ if(this.icon) labelNode.appendChild(iconWrapNode);
+
+ let buttonNode;
+ if (this.type !== 'separator') {
+ buttonNode = document.createElement('span');
+ buttonNode.setAttribute('role', 'button');
+ node.appendChild(buttonNode);
+ if (this.submenu)
+ buttonNode.setAttribute('aria-expanded',
+ 'false');
+ if(!menuBarTopLevel) {
+ buttonNode.addEventListener('mouseenter', () => {
+ this.select(node, true, true);
+ });
+ }
+ } else
+ buttonNode = node;
+
+ let textLabelNode = document.createElement('span');
+ textLabelNode.textContent = this.label;
+ textLabelNode.classList.add('label-text');
+
+ buttonNode.appendChild(checkmarkNode);
+
+ labelNode.appendChild(textLabelNode);
+ buttonNode.appendChild(labelNode);
+
+ if(this.submenu && !menuBarTopLevel) {
+ const n = document.createElement('span');
+ n.classList.add('modifiers');
+ n.append(MenuItem.submenuSymbol);
+ buttonNode.appendChild(n);
+ }
+ let accelerator = this.accelerator;
+ if (accelerator) {
+ let keyNode = document.createElement('span');
+ keyNode.classList.add('keys');
+ let i = 0;
+ const len = accelerator.length;
+ for (;;) {
+ if (i > 0) {
+ keyNode.append(" ");
+ }
+ let sp = accelerator.indexOf(' ', i);
+ let key = accelerator.substring(i, sp < 0 ? len : sp);
+ let pl = key.lastIndexOf('+', key.length-2);
+ if (pl > 0) {
+ let mod = key.substring(0, pl);
+ let modNode = document.createElement('span');
+ modNode.classList.add('modifiers');
+ if (MenuItem.useModifierSymbols) {
+ let mods = mod.toLowerCase().split('+');
+ mod = "";
+ // Looping this way to keep order of symbols - required by macOS
+ for(let symbol in MenuItem.modifierSymbols) {
+ if(mods.indexOf(symbol) >= 0) {
+ mod += MenuItem.modifierSymbols[symbol];
+ }
+ }
+ } else
+ mod += "+";
+ modNode.append(mod);
+ keyNode.append(modNode);
+ key = key.substring(pl+1);
+ }
+ keyNode.append(key);
+ if (sp < 0)
+ break;
+ i = sp + 1;
+ }
+ keyNode.normalize();
+ buttonNode.appendChild(keyNode);
+ }
+
+ node.title = this.tooltip;
+ this.node = node;
+ menuNode.appendChild(node);
+ }
+
+ popupSubmenu(x, y, menubarSubmenu = false) {
+ this.submenu.popup(x, y, this.node, menubarSubmenu);
+ this.submenu.node.menuItem = this.node;
+ this.node.jsMenuNode.currentSubmenu = this.submenu;
+ }
+}
+
+MenuItem.submenuSymbol = '\u27a7'; // '➧' Squat Black Rightwards Arrow[
+
+MenuItem.modifierSymbols = {
+ shift: '⇧',
+ ctrl: '⌃',
+ alt: '⌥',
+ cmd: '⌘',
+ super: '⌘',
+ command: '⌘'
+};
+
+MenuItem.keySymbols = {
+ up: '↑',
+ esc: '⎋',
+ tab: '⇥',
+ left: '←',
+ down: '↓',
+ right: '→',
+ pageUp: '⇞',
+ escape: '⎋',
+ pageDown: '⇟',
+ backspace: '⌫',
+ space: 'Space'
+};
+MenuItem.useModifierSymbols = Menu._isMac;
+
+if (typeof module !== "undefined" && module.exports) {
+ module.exports = { Menu: Menu, MenuItem: MenuItem };
+}
+
+// Local Variables:
+// js-indent-level: 8
+// indent-tabs-mode: t
+// End:
diff --git a/src/main.js b/src/main.js
index 0d75ebd..bcd0700 100644
--- a/src/main.js
+++ b/src/main.js
@@ -28,10 +28,10 @@ function forwardConsole(fnName, logger) {
}
// forwardConsole('log', trace);
-forwardConsole('debug', debug);
-forwardConsole('info', info);
-forwardConsole('warn', warn);
-forwardConsole('error', error);
+// forwardConsole('debug', debug);
+// forwardConsole('info', info);
+// forwardConsole('warn', warn);
+// forwardConsole('error', error);
// Debug flags
const debugQuadtree = false
@@ -281,8 +281,9 @@ function getShortcut(shortcut) {
// Load the configuration from the file system
async function loadConfig() {
try {
- const configPath = await join(await appLocalDataDir(), CONFIG_FILE_PATH);
- const configData = await readTextFile(configPath);
+ // const configPath = await join(await appLocalDataDir(), CONFIG_FILE_PATH);
+ // const configData = await readTextFile(configPath);
+ const configData = localStorage.getItem("lightningbeamConfig") || "{}"
config = deepMerge({...config}, JSON.parse(configData));
updateUI()
} catch (error) {
@@ -293,8 +294,9 @@ async function loadConfig() {
// Save the configuration to a file
async function saveConfig() {
try {
- const configPath = await join(await appLocalDataDir(), CONFIG_FILE_PATH);
- await writeTextFile(configPath, JSON.stringify(config, null, 2));
+ // const configPath = await join(await appLocalDataDir(), CONFIG_FILE_PATH);
+ // await writeTextFile(configPath, JSON.stringify(config, null, 2));
+ localStorage.setItem("lightningbeamConfig", JSON.stringify(config, null, 2))
} catch (error) {
console.error('Error saving config:', error);
}
@@ -476,7 +478,6 @@ let actions = {
});
}
let img = await loadImage(action.src)
- console.log(img.crossOrigin)
// img.onload = function() {
let ct = {
...context,
diff --git a/src/tauri_polyfill.js b/src/tauri_polyfill.js
new file mode 100644
index 0000000..d1214ea
--- /dev/null
+++ b/src/tauri_polyfill.js
@@ -0,0 +1,76 @@
+if (!window.__TAURI__) {
+ // We are in a browser environment
+ window.__TAURI__ = {
+ core: {
+ invoke: () => {}
+ },
+ fs: {
+ writeFile: () => {},
+ readFile: () => {},
+ writeTextFile: () => {},
+ readTextFile: () => {}
+ },
+ dialog: {
+ open: () => {},
+ save: () => {},
+ message: () => {},
+ confirm: () => {},
+ },
+ path: {
+ documentDir: () => {},
+ join: () => {},
+ basename: () => {},
+ appLocalDataDir: () => {}
+ },
+ menu: {
+ Menu: {
+ new: (params) => {
+ let items = params.items
+ let menubar = new Menu({type: "menubar"})
+ for (let i in items) {
+ let item = items[i]
+ menubar.append(new MenuItem({label: item.text, submenu: item}))
+ }
+ menubar.setAsWindowMenu = () => {
+ Menu.setApplicationMenu(menubar)
+ }
+ menubar.setAsAppMenu = menubar.setAsWindowMenu
+ return menubar
+ }
+ },
+ MenuItem: MenuItem,
+ PredefinedMenuItem: () => {},
+ Submenu: {
+ new: (params) => {
+ const items = params.items
+ menu = new Menu()
+ for (let i in items) {
+ let item = items[i]
+ menuItem = new MenuItem({
+ label: item.text,
+ enabled: item.enabled,
+ click: item.action,
+ accelerator: item.accelerator
+ })
+ menu.append(menuItem)
+ }
+ menu.text = params.text
+ return menu
+ }
+ }
+ },
+ window: {
+ getCurrentWindow: () => {}
+ },
+ app: {
+ getVersion: () => {}
+ },
+ log: {
+ warn: () => {},
+ debug: () => {},
+ trace: () => {},
+ info: () => {},
+ error: () => {},
+ }
+ }
+}
\ No newline at end of file