Skip to content

Commit

Permalink
Merge pull request #8 from c2r0b/unlisteners-fix
Browse files Browse the repository at this point in the history
Unlisteners fix
  • Loading branch information
c2r0b authored Sep 29, 2023
2 parents af0e113 + 1353f6b commit 31e2a04
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 79 deletions.
1 change: 0 additions & 1 deletion examples/ts-utility/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,5 @@ onEventShowMenu('contextmenu', async (e:MouseEvent) => {
}
]
};
console.log(e, options);
return options;
 });
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tauri-plugin-context-menu",
"version": "0.4.0",
"version": "0.4.1",
"author": "c2r0b",
"type": "module",
"description": "",
Expand Down
2 changes: 1 addition & 1 deletion webview-dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as ContextMenu from './types';
export { ContextMenu };
export declare function assetToPath(asset: string): Promise<string>;
export declare function showMenu(options: ContextMenu.Options): void;
export declare function showMenu(options: ContextMenu.Options): Promise<void>;
export declare function onEventShowMenu(eventName: string, options: ContextMenu.EventOptions): void;
10 changes: 5 additions & 5 deletions webview-dist/index.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions webview-dist/index.js.map

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions webview-dist/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { UnlistenFn } from "@tauri-apps/api/event";
export interface Position {
x: number;
y: number;
Expand All @@ -21,5 +22,9 @@ export interface Options {
pos?: Position;
items: Item[];
}
export interface ProcessResult {
unlisteners: UnlistenFn[];
processed: Item[];
}
export type EventOptionsFunction = (e?: MouseEvent) => Options | Promise<Options>;
export type EventOptions = Options | EventOptionsFunction;
62 changes: 31 additions & 31 deletions webview-src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,51 @@ import * as tauriApiPath from '@tauri-apps/api/path';
import { assetToPath, showMenu, onEventShowMenu, ContextMenu } from './index';

jest.mock('@tauri-apps/api', () => ({
invoke: jest.fn()
invoke: jest.fn()
}));

jest.mock('@tauri-apps/api/event', () => ({
listen: jest.fn()
listen: jest.fn()
}));

jest.mock('@tauri-apps/api/path', () => ({
resolveResource: jest.fn()
resolveResource: jest.fn()
}));

describe('assetToPath', () => {
it('calls tauriApiPath.resolveResource', async () => {
const asset = 'testAsset';
await assetToPath(asset);
expect(tauriApiPath.resolveResource).toHaveBeenCalledWith(asset);
});
it('calls tauriApiPath.resolveResource', async () => {
const asset = 'testAsset';
await assetToPath(asset);
expect(tauriApiPath.resolveResource).toHaveBeenCalledWith(asset);
});
});

describe('showMenu', () => {
it('sets up event listeners for item events', () => {
const items = [
{ event: jest.fn() },
{
event: jest.fn(),
subitems: [
{ event: jest.fn() }
]
}
];
showMenu({ items });
expect(tauriEvent.listen).toHaveBeenCalledTimes(3);
});
it('sets up event listeners for item events', async () => {
const items = [
{ event: jest.fn() },
{
event: jest.fn(),
subitems: [
{ event: jest.fn() }
]
}
];
await showMenu({ items });
expect(tauriEvent.listen).toHaveBeenCalledTimes(4); // events + menu-did-close
});

it('invokes tauriApi with the SHOW_COMMAND', () => {
showMenu({ items: [] });
expect(tauriApi.invoke).toHaveBeenCalledWith(expect.stringMatching('plugin:context_menu|show_context_menu'), expect.any(Object));
});
it('invokes tauriApi with the SHOW_COMMAND', () => {
showMenu({ items: [] });
expect(tauriApi.invoke).toHaveBeenCalledWith(expect.stringMatching('plugin:context_menu|show_context_menu'), expect.any(Object));
});
});

describe('onEventShowMenu', () => {
it('sets up a window event listener', () => {
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
onEventShowMenu('testEvent', {} as ContextMenu.Options);
expect(addEventListenerSpy).toHaveBeenCalledWith('testEvent', expect.any(Function));
addEventListenerSpy.mockRestore();
});
it('sets up a window event listener', () => {
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
onEventShowMenu('testEvent', {} as ContextMenu.Options);
expect(addEventListenerSpy).toHaveBeenCalledWith('testEvent', expect.any(Function));
addEventListenerSpy.mockRestore();
});
});
87 changes: 50 additions & 37 deletions webview-src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,46 +8,59 @@ import * as ContextMenu from './types';
export { ContextMenu };

export async function assetToPath(asset: string): Promise<string> {
return await tauriApiPath.resolveResource(asset);
return await tauriApiPath.resolveResource(asset);
}

export function showMenu(options: ContextMenu.Options): void {
// for each item, if it is a function, replace it with an event listener
function processItems(items: ContextMenu.Item[], prefix: string): void {
for (let i = 0; i < items.length; i++) {
const itemEvent = items[i].event;

if (typeof itemEvent === 'function') {
const eventName = `${prefix}_context_menu_item_${i}`;

// Listen to the event and call the function directly
tauriEvent.listen(eventName, (e) => itemEvent(e));
items[i].event = eventName;
}

// Recurse into subitems if they exist
if (items[i].subitems) {
processItems(items[i].subitems as ContextMenu.Item[], `${prefix}_${i}`);
}
}
}

processItems(options.items, 'root');

// send the options to the plugin
tauriApi.invoke(SHOW_COMMAND, options as any);
}

// for each item, if it is a function, replace it with an event listener
async function processItems(items: ContextMenu.Item[], prefix: string): Promise<ContextMenu.ProcessResult> {
const unlisteners: tauriEvent.UnlistenFn[] = [];
const processed:ContextMenu.Item[] = [ ...items.map((item) => ({ ...item })) ];

export function onEventShowMenu(eventName: string, options: ContextMenu.EventOptions): void {
window.addEventListener(eventName, async (e) => {
e.preventDefault();
for (let i = 0; i < processed.length; i++) {
const itemEvent = processed[i].event;

if (typeof itemEvent === 'function') {
const eventName = `${prefix}_context_menu_item_${i}`;

// Listen to the event and call the function directly
unlisteners.push(await tauriEvent.listen(eventName, (e) => itemEvent(e)));
processed[i].event = eventName;
}

// Recurse into subitems if they exist
if (items[i].subitems) {
const result = await processItems(items[i].subitems as ContextMenu.Item[], `${prefix}_${i}`);
unlisteners.push(...result.unlisteners);
processed[i].subitems = result.processed;
}
}

return { unlisteners, processed };
}

// if options is a function, call it to get the options
if (typeof options === 'function') {
options = await options(e as MouseEvent);
}
export async function showMenu(options: ContextMenu.Options) {
const { unlisteners, processed } = await processItems(options.items, 'root');

// unlisten all events when the menu closes
const unlistenMenuClose = await tauriEvent.listen("menu-did-close", () => {
unlisteners.forEach((unlistener) => unlistener());
unlisteners.length = 0;
unlistenMenuClose();
});

// send the options to the plugin
tauriApi.invoke(SHOW_COMMAND, { ...options, items: processed } as any);
}

export function onEventShowMenu(eventName: string, options: ContextMenu.EventOptions): void {
window.addEventListener(eventName, async (e) => {
e.preventDefault();

// if options is a function, call it to get the options
if (typeof options === 'function') {
options = await options(e as MouseEvent);
}

showMenu(options);
});
await showMenu(options);
});
}
7 changes: 7 additions & 0 deletions webview-src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { UnlistenFn } from "@tauri-apps/api/event"

export interface Position {
x: number
y: number
Expand Down Expand Up @@ -25,6 +27,11 @@ export interface Options {
items: Item[]
}

export interface ProcessResult {
unlisteners: UnlistenFn[]
processed: Item[]
}

export type EventOptionsFunction = (e?: MouseEvent) => Options | Promise<Options>;

export type EventOptions = Options | EventOptionsFunction;

0 comments on commit 31e2a04

Please sign in to comment.