@lvce-editor/menu-worker 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -421,10 +421,10 @@ const create$4 = (method, params) => {
421
421
  };
422
422
  };
423
423
  const callbacks = Object.create(null);
424
- const set$3 = (id, fn) => {
424
+ const set$4 = (id, fn) => {
425
425
  callbacks[id] = fn;
426
426
  };
427
- const get$2 = id => {
427
+ const get$3 = id => {
428
428
  return callbacks[id];
429
429
  };
430
430
  const remove = id => {
@@ -440,7 +440,7 @@ const registerPromise = () => {
440
440
  resolve,
441
441
  promise
442
442
  } = Promise.withResolvers();
443
- set$3(id, resolve);
443
+ set$4(id, resolve);
444
444
  return {
445
445
  id,
446
446
  promise
@@ -601,14 +601,14 @@ const unwrapJsonRpcResult = responseMessage => {
601
601
  }
602
602
  throw new JsonRpcError('unexpected response message');
603
603
  };
604
- const warn = (...args) => {
604
+ const warn$1 = (...args) => {
605
605
  console.warn(...args);
606
606
  };
607
607
  const resolve = (id, response) => {
608
- const fn = get$2(id);
608
+ const fn = get$3(id);
609
609
  if (!fn) {
610
610
  console.log(response);
611
- warn(`callback ${id} may already be disposed`);
611
+ warn$1(`callback ${id} may already be disposed`);
612
612
  return;
613
613
  }
614
614
  fn(response);
@@ -1151,6 +1151,9 @@ const getKeyCodeString = keyCode => {
1151
1151
  const CtrlCmd = 1 << 11 >>> 0;
1152
1152
  const Shift = 1 << 10 >>> 0;
1153
1153
 
1154
+ const Separator$2 = 1;
1155
+ const Disabled$1 = 5;
1156
+
1154
1157
  const parseKey = rawKey => {
1155
1158
  const isCtrl = Boolean(rawKey & CtrlCmd);
1156
1159
  const isShift = Boolean(rawKey & Shift);
@@ -1166,10 +1169,10 @@ const parseKey = rawKey => {
1166
1169
  const RendererWorker = 1;
1167
1170
 
1168
1171
  const rpcs = Object.create(null);
1169
- const set$2 = (id, rpc) => {
1172
+ const set$3 = (id, rpc) => {
1170
1173
  rpcs[id] = rpc;
1171
1174
  };
1172
- const get$1 = id => {
1175
+ const get$2 = id => {
1173
1176
  return rpcs[id];
1174
1177
  };
1175
1178
 
@@ -1177,21 +1180,21 @@ const create$1 = rpcId => {
1177
1180
  return {
1178
1181
  // @ts-ignore
1179
1182
  invoke(method, ...params) {
1180
- const rpc = get$1(rpcId);
1183
+ const rpc = get$2(rpcId);
1181
1184
  // @ts-ignore
1182
1185
  return rpc.invoke(method, ...params);
1183
1186
  },
1184
1187
  // @ts-ignore
1185
1188
  invokeAndTransfer(method, ...params) {
1186
- const rpc = get$1(rpcId);
1189
+ const rpc = get$2(rpcId);
1187
1190
  // @ts-ignore
1188
1191
  return rpc.invokeAndTransfer(method, ...params);
1189
1192
  },
1190
1193
  set(rpc) {
1191
- set$2(rpcId, rpc);
1194
+ set$3(rpcId, rpc);
1192
1195
  },
1193
1196
  async dispose() {
1194
- const rpc = get$1(rpcId);
1197
+ const rpc = get$2(rpcId);
1195
1198
  await rpc.dispose();
1196
1199
  }
1197
1200
  };
@@ -1199,7 +1202,7 @@ const create$1 = rpcId => {
1199
1202
 
1200
1203
  const {
1201
1204
  invoke: invoke$1,
1202
- set: set$1} = create$1(RendererWorker);
1205
+ set: set$2} = create$1(RendererWorker);
1203
1206
 
1204
1207
  const matchesArgs = (a, b) => {
1205
1208
  if (!a && !b) {
@@ -1227,11 +1230,16 @@ const getCommandKeyBinding = (keyBindings, command, args) => {
1227
1230
  return undefined;
1228
1231
  };
1229
1232
 
1233
+ /* eslint-disable no-console */
1234
+ const warn = (...args) => {
1235
+ console.warn(...args);
1236
+ };
1237
+
1230
1238
  const invoke = async (method, ...params) => {
1231
1239
  // TODO
1232
1240
  };
1233
1241
 
1234
- const state$2 = {
1242
+ const state$1 = {
1235
1243
  /**
1236
1244
  * @type {any}
1237
1245
  */
@@ -1829,7 +1837,7 @@ const getMenuEntries2 = async (uid, menuId, ...args) => {
1829
1837
  };
1830
1838
 
1831
1839
  const addKeyBindings = menuEntries => {
1832
- const keyBindings = state$2.matchingKeyBindings;
1840
+ const keyBindings = state$1.matchingKeyBindings;
1833
1841
  const newMenuEntries = [];
1834
1842
  for (const menuEntry of menuEntries) {
1835
1843
  const keyBinding = getCommandKeyBinding(keyBindings, menuEntry.command, menuEntry.args);
@@ -2085,17 +2093,72 @@ const getVisible = (items, focusedIndex, expanded, level) => {
2085
2093
  return visibleItems;
2086
2094
  };
2087
2095
 
2096
+ /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
2088
2097
  // TODO lazyload menuEntries and use Command.execute (maybe)
2089
- const CONTEXT_MENU_WIDTH$1 = 250;
2090
- const state$1 = {
2091
- menus: []};
2092
- const addMenuInternal$1 = menu => {
2093
- if (state$1.menus.length > 5) {
2098
+
2099
+ // TODO difference between focusing with mouse or keyboard
2100
+ // with mouse -> open submenu
2101
+ // with keyboard -> don't open submenu, only focus
2102
+
2103
+ // TODO this code seems a bit too complicated, maybe it can be simplified
2104
+
2105
+ const state = {
2106
+ menus: [],
2107
+ latestTimeStamp: 0};
2108
+ const addMenuInternal = menu => {
2109
+ if (state.menus.length > 5) {
2094
2110
  throw new Error('too many menus');
2095
2111
  }
2096
- state$1.menus.push(menu);
2112
+ state.menus.push(menu);
2097
2113
  return menu;
2098
2114
  };
2115
+ const getCount = () => {
2116
+ return state.menus.length;
2117
+ };
2118
+ const reset = () => {
2119
+ state.menus = [];
2120
+ };
2121
+ const get$1 = index => {
2122
+ return state.menus[index];
2123
+ };
2124
+ const set$1 = menus => {
2125
+ state.menus = menus;
2126
+ };
2127
+ const getAll = () => {
2128
+ return state.menus;
2129
+ };
2130
+ const setTimestamp = timestamp => {
2131
+ state.latestTimeStamp = timestamp;
2132
+ };
2133
+ const getLatestTimestamp = () => {
2134
+ return state.latestTimeStamp;
2135
+ };
2136
+
2137
+ // TODO lazyload menuEntries and use Command.execute (maybe)
2138
+ const CONTEXT_MENU_ITEM_HEIGHT$2 = 26;
2139
+ const CONTEXT_MENU_SEPARATOR_HEIGHT$2 = 11;
2140
+ const CONTEXT_MENU_PADDING$2 = 8;
2141
+ const getMenuHeight$2 = items => {
2142
+ let height = CONTEXT_MENU_PADDING$2;
2143
+ for (const item of items) {
2144
+ switch (item.flags) {
2145
+ case Separator$1:
2146
+ height += CONTEXT_MENU_SEPARATOR_HEIGHT$2;
2147
+ break;
2148
+ default:
2149
+ height += CONTEXT_MENU_ITEM_HEIGHT$2;
2150
+ break;
2151
+ }
2152
+ }
2153
+ return height;
2154
+ };
2155
+
2156
+ // TODO lazyload menuEntries and use Command.execute (maybe)
2157
+ const MENU_WIDTH = 150;
2158
+ const CONTEXT_MENU_WIDTH$1 = 250;
2159
+ const getMenuWidth = () => {
2160
+ return CONTEXT_MENU_WIDTH$1;
2161
+ };
2099
2162
 
2100
2163
  // TODO handle printable letter and focus item that starts with that letter
2101
2164
 
@@ -2147,11 +2210,11 @@ const getMenuBounds$1 = (x, y, items) => {
2147
2210
  const show$2 = async (x, y, id, mouseBlocking = false, ...args) => {
2148
2211
  const items = await getMenuEntriesWithKeyBindings(id, ...args);
2149
2212
  const bounds = getMenuBounds$1(x, y, items);
2150
- const menu = addMenuInternal$1({
2213
+ const menu = addMenuInternal({
2151
2214
  id,
2152
2215
  items,
2153
2216
  focusedIndex: -1,
2154
- level: state$1.menus.length,
2217
+ level: getCount(),
2155
2218
  x: bounds.x,
2156
2219
  y: bounds.y
2157
2220
  });
@@ -2162,11 +2225,11 @@ const show$2 = async (x, y, id, mouseBlocking = false, ...args) => {
2162
2225
  const show2$2 = async (uid, menuId, x, y, mouseBlocking = false, ...args) => {
2163
2226
  const items = await getMenuEntriesWithKeyBindings2(uid, menuId, ...args);
2164
2227
  const bounds = getMenuBounds$1(x, y, items);
2165
- const menu = addMenuInternal$1({
2228
+ const menu = addMenuInternal({
2166
2229
  id: menuId,
2167
2230
  items,
2168
2231
  focusedIndex: -1,
2169
- level: state$1.menus.length,
2232
+ level: getCount(),
2170
2233
  x: bounds.x,
2171
2234
  y: bounds.y
2172
2235
  });
@@ -2175,10 +2238,10 @@ const show2$2 = async (uid, menuId, x, y, mouseBlocking = false, ...args) => {
2175
2238
  await invoke(/* Menu.show */'Menu.showMenu', /* x */bounds.x, /* y */bounds.y, /* width */bounds.width, /* height */bounds.height, /* items */menu.items, /* level */menu.level, /* parentIndex */-1, /* dom */dom, /* mouseBlocking */mouseBlocking);
2176
2239
  };
2177
2240
  const hide = async (restoreFocus = true) => {
2178
- if (state$1.menus.length === 0) {
2241
+ if (getCount() === 0) {
2179
2242
  return;
2180
2243
  }
2181
- state$1.menus = [];
2244
+ reset();
2182
2245
  await invoke(/* Menu.hide */'Menu.hide', /* restoreFocus */restoreFocus);
2183
2246
  };
2184
2247
 
@@ -2288,6 +2351,69 @@ const diff2 = uid => {
2288
2351
  return diff(oldState, newState);
2289
2352
  };
2290
2353
 
2354
+ const canBeFocused = item => {
2355
+ switch (item.flags) {
2356
+ case Separator$2:
2357
+ case Disabled$1:
2358
+ return false;
2359
+ default:
2360
+ return true;
2361
+ }
2362
+ };
2363
+
2364
+ const focusIndex = async (menu, index) => {
2365
+ if (menu.items.length === 0) {
2366
+ return;
2367
+ }
2368
+ const oldFocusedIndex = menu.focusedIndex;
2369
+ menu.focusedIndex = index;
2370
+ return {
2371
+ commands: [/* Menu.focusIndex */'Menu.focusIndex', /* level */menu.level, /* oldFocusedIndex */oldFocusedIndex, /* newFocusedIndex */index],
2372
+ menus: getAll()
2373
+ };
2374
+ };
2375
+
2376
+ const getCurrentMenu = () => {
2377
+ if (getCount() === 0) {
2378
+ warn('menu not available');
2379
+ }
2380
+ return getAll().at(-1);
2381
+ };
2382
+ const getIndexToFocusNextStartingAt = (items, startIndex) => {
2383
+ for (let i = startIndex; i < startIndex + items.length; i++) {
2384
+ const index = i % items.length;
2385
+ const item = items[index];
2386
+ if (canBeFocused(item)) {
2387
+ return index;
2388
+ }
2389
+ }
2390
+ return -1;
2391
+ };
2392
+ const getIndexToFocusFirst = items => {
2393
+ return getIndexToFocusNextStartingAt(items, 0);
2394
+ };
2395
+ const focusFirst = async () => {
2396
+ const menu = getCurrentMenu();
2397
+ if (getCount() === 0) {
2398
+ return;
2399
+ }
2400
+ const indexToFocus = getIndexToFocusFirst(menu.items);
2401
+ await focusIndex(menu, indexToFocus);
2402
+ };
2403
+
2404
+ const getIndexToFocusNext = menu => {
2405
+ const startIndex = menu.focusedIndex + 1;
2406
+ return getIndexToFocusNextStartingAt(menu.items, startIndex);
2407
+ };
2408
+ const focusNext = async () => {
2409
+ const menu = getCurrentMenu();
2410
+ if (menu.items.length === 0) {
2411
+ return;
2412
+ }
2413
+ const indexToFocus = getIndexToFocusNext(menu);
2414
+ await focusIndex(menu, indexToFocus);
2415
+ };
2416
+
2291
2417
  const commandsIds = ['closeMenu', 'handleClickAt', 'focus', 'focusFirst', 'focusIndex', 'focusLast', 'focusNext', 'focusPrevious', 'handleClick', 'handleFocus', 'handleFocusOut', 'handleKeyArrowDown', 'handleKeyArrowLeft', 'handleKeyArrowRight', 'handleKeyArrowUp', 'handleKeyEnd', 'handleKeyEnter', 'handleKeyEscape', 'handleKeyHome', 'handleKeySpace', 'handleMenuClick', 'handleMenuMouseOver', 'handleMouseOut', 'handleMouseOver', 'handlePointerOver', 'handlePointerOut', 'toggleIndex', 'toggleMenu'];
2292
2418
 
2293
2419
  const getCommandIds = () => {
@@ -2332,6 +2458,33 @@ const getKeyBindings = () => {
2332
2458
  }];
2333
2459
  };
2334
2460
 
2461
+ const getMenuHideCommands = async (restoreFocus = true) => {
2462
+ if (getCount() === 0) {
2463
+ return {
2464
+ newMenus: [],
2465
+ commands: []
2466
+ };
2467
+ }
2468
+ reset();
2469
+ return {
2470
+ commands: [/* Menu.hide */'Menu.hide', /* restoreFocus */restoreFocus],
2471
+ newMenus: []
2472
+ };
2473
+ };
2474
+
2475
+ const menus = [MenuEntriesEdit, MenuEntriesFile, MenuEntriesGo, MenuEntriesHelp, MenuEntriesRun, MenuEntriesSelection, MenuEntriesTerminal, MenuEntriesTitleBar, MenuEntriesView, MenuEntriesOpenRecent];
2476
+
2477
+ const getMenuIds = () => {
2478
+ return menus.map(menu => menu.id);
2479
+ };
2480
+ const getMenuEntries = (id, platform) => {
2481
+ const menu = menus.find(item => item.id === id);
2482
+ if (!menu) {
2483
+ return [];
2484
+ }
2485
+ return menu.getMenuEntries(platform);
2486
+ };
2487
+
2335
2488
  // TODO lazyload menuEntries and use Command.execute (maybe)
2336
2489
  const CONTEXT_MENU_WIDTH = 250;
2337
2490
 
@@ -2341,16 +2494,6 @@ const CONTEXT_MENU_WIDTH = 250;
2341
2494
 
2342
2495
  // TODO this code seems a bit too complicated, maybe it can be simplified
2343
2496
 
2344
- const state = {
2345
- menus: []};
2346
- const addMenuInternal = menu => {
2347
- if (state.menus.length > 5) {
2348
- throw new Error('too many menus');
2349
- }
2350
- state.menus.push(menu);
2351
- return menu;
2352
- };
2353
-
2354
2497
  // TODO handle printable letter and focus item that starts with that letter
2355
2498
 
2356
2499
  // TODO pageup / pagedown keys
@@ -2404,47 +2547,110 @@ const getMenuShowCommands = async (items, menuId, x, y, mouseBlocking = false) =
2404
2547
  id: menuId,
2405
2548
  items,
2406
2549
  focusedIndex: -1,
2407
- level: state.menus.length,
2550
+ level: getCount(),
2408
2551
  x: bounds.x,
2409
2552
  y: bounds.y
2410
2553
  });
2411
2554
  const visible = getVisible(menu.items, -1, false, menu.level);
2412
2555
  const dom = getMenuVirtualDom(visible).slice(1);
2413
2556
  return {
2557
+ menus: getAll(),
2414
2558
  menu,
2415
2559
  commands: [/* Menu.show */'Menu.showMenu', /* x */bounds.x, /* y */bounds.y, /* width */bounds.width, /* height */bounds.height, /* items */menu.items, /* level */menu.level, /* parentIndex */-1, /* dom */dom, /* mouseBlocking */mouseBlocking]
2416
2560
  };
2417
2561
  };
2418
2562
 
2419
- const getMenuHideCommands = async (restoreFocus = true) => {
2420
- if (state.menus.length === 0) {
2563
+ const handleContextMenu = state => {
2564
+ return state;
2565
+ };
2566
+
2567
+ const hideSubMenus = async level => {
2568
+ if (level < getCount()) {
2569
+ set$1(getAll().slice(0, level + 1));
2421
2570
  return {
2422
- newMenus: [],
2423
- commands: []
2571
+ commands: [/* Menu.hideSubMenu */'Menu.hideSubMenu', /* level */level + 1],
2572
+ menus: getAll()
2424
2573
  };
2425
2574
  }
2426
- state.menus = [];
2575
+ };
2576
+
2577
+ const showSubMenuAtEnter = async (level, index, enterX, enterY) => {
2578
+ // TODO delete old menus
2579
+ set$1(getAll().slice(0, level + 1));
2580
+ const parentMenu = get$1(level);
2581
+ const item = parentMenu.items[index];
2582
+ const subMenuItems = await getMenuEntries$1(item.id);
2583
+ const subMenu = addMenuInternal({
2584
+ id: item.id,
2585
+ items: subMenuItems,
2586
+ focusedIndex: -1,
2587
+ level: getCount(),
2588
+ y: parentMenu.y + index * 25,
2589
+ x: parentMenu.x + MENU_WIDTH,
2590
+ enterX,
2591
+ enterY
2592
+ });
2593
+ const width = getMenuWidth();
2594
+ const height = getMenuHeight$2(subMenuItems);
2595
+ const visible = getVisible(subMenu.items, -1, false, subMenu.level);
2596
+ const dom = getMenuVirtualDom(visible).slice(1);
2427
2597
  return {
2428
- commands: [/* Menu.hide */'Menu.hide', /* restoreFocus */restoreFocus],
2429
- newMenus: []
2598
+ commands: [/* Menu.showMenu */'Menu.showMenu', /* x */subMenu.x, /* y */subMenu.y, /* width */width, /* height */height, /* items */subMenu.items, /* level */subMenu.level, /* parentIndex */index, /* dom */dom],
2599
+ menus: getAll()
2430
2600
  };
2431
2601
  };
2602
+ const showSubMenu = async (level, index) => {
2603
+ return showSubMenuAtEnter(level, index, -1, -1);
2604
+ };
2432
2605
 
2433
- const menus = [MenuEntriesEdit, MenuEntriesFile, MenuEntriesGo, MenuEntriesHelp, MenuEntriesRun, MenuEntriesSelection, MenuEntriesTerminal, MenuEntriesTitleBar, MenuEntriesView, MenuEntriesOpenRecent];
2434
-
2435
- const getMenuIds = () => {
2436
- return menus.map(menu => menu.id);
2606
+ /* eslint-disable @typescript-eslint/no-implied-eval */
2607
+ const MENU_DELAY_TRIANGLE = 300;
2608
+ const resolveAfterTimeout = fn => {
2609
+ setTimeout(fn, MENU_DELAY_TRIANGLE);
2437
2610
  };
2438
- const getMenuEntries = (id, platform) => {
2439
- const menu = menus.find(item => item.id === id);
2440
- if (!menu) {
2441
- return [];
2611
+ const handleMouseEnter = async (level, index, enterX, enterY, enterTimeStamp) => {
2612
+ setTimestamp(enterTimeStamp);
2613
+ if (level >= getCount()) {
2614
+ return;
2615
+ }
2616
+ const menu = get$1(level);
2617
+ if (menu.focusedIndex === index) {
2618
+ return;
2619
+ }
2620
+ if (level < getCount() - 1) {
2621
+ const subMenu = get$1(level + 1);
2622
+ const subMenuEnterX = subMenu.enterX;
2623
+ // TODO should check for triangle position here
2624
+ if (enterX >= subMenuEnterX) {
2625
+ await new Promise(resolveAfterTimeout);
2626
+ if (getLatestTimestamp() !== enterTimeStamp) {
2627
+ return;
2628
+ }
2629
+ }
2630
+ }
2631
+ const item = menu.items[index];
2632
+ await focusIndex(menu, index);
2633
+ switch (item.flags) {
2634
+ case /* SubMenu */4:
2635
+ await showSubMenuAtEnter(menu.level, index, enterX, enterY);
2636
+ break;
2637
+ default:
2638
+ await hideSubMenus(menu.level);
2639
+ break;
2442
2640
  }
2443
- return menu.getMenuEntries(platform);
2444
2641
  };
2445
2642
 
2446
- const handleContextMenu = state => {
2447
- return state;
2643
+ const handleMouseLeave = async () => {
2644
+ const menu = get$1(0);
2645
+ if (menu.items.length === 0) {
2646
+ return;
2647
+ }
2648
+ const oldFocusedIndex = menu.focusedIndex;
2649
+ menu.focusedIndex = -1;
2650
+ return {
2651
+ commands: [/* Menu.focusIndex */'Menu.focusIndex', /* level */menu.level, /* oldFocusedIndex */oldFocusedIndex, /* newFocusedIndex */-1],
2652
+ menus: getAll()
2653
+ };
2448
2654
  };
2449
2655
 
2450
2656
  const loadContent = async (state, titleBarEntries) => {
@@ -2574,28 +2780,34 @@ const wrapCommand = fn => {
2574
2780
  };
2575
2781
 
2576
2782
  const commandMap = {
2577
- 'Menu.getMenuEntries': getMenuEntries,
2578
- 'Menu.getMenuIds': getMenuIds,
2579
- 'Menu.handleContextMenu': handleContextMenu,
2580
- 'Menu.renderEventListeners': renderEventListeners,
2581
2783
  'Menu.diff2': diff2,
2784
+ 'Menu.focusFirst': focusFirst,
2785
+ 'Menu.focusNext': focusNext,
2582
2786
  'Menu.getCommands': getCommandIds,
2787
+ 'Menu.getHideCommands': getMenuHideCommands,
2583
2788
  'Menu.getKeyBindings': getKeyBindings,
2789
+ 'Menu.getMenuEntries': getMenuEntries,
2790
+ 'Menu.getMenuIds': getMenuIds,
2584
2791
  'Menu.getMenus': getMenus,
2792
+ 'Menu.getShowCommands': getMenuShowCommands,
2793
+ 'Menu.handleContextMenu': handleContextMenu,
2794
+ 'Menu.handleMouseEnter': handleMouseEnter,
2795
+ 'Menu.handleMouseLeave': handleMouseLeave,
2796
+ 'Menu.hideSubMenus': hideSubMenus,
2585
2797
  'Menu.loadContent': wrapCommand(loadContent),
2586
2798
  'Menu.render2': render2,
2799
+ 'Menu.renderEventListeners': renderEventListeners,
2587
2800
  'Menu.saveState': saveState,
2588
2801
  'Menu.show': show,
2589
2802
  'Menu.show2': show2,
2590
- 'Menu.getShowCommands': getMenuShowCommands,
2591
- 'Menu.getHideCommands': getMenuHideCommands
2803
+ 'Menu.showSubMenu': showSubMenu
2592
2804
  };
2593
2805
 
2594
2806
  const listen = async () => {
2595
2807
  const rpc = await WebWorkerRpcClient.create({
2596
2808
  commandMap: commandMap
2597
2809
  });
2598
- set$1(rpc);
2810
+ set$2(rpc);
2599
2811
  };
2600
2812
 
2601
2813
  const main = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/menu-worker",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Menu Worker",
5
5
  "repository": {
6
6
  "type": "git",