Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add destroy menubar capability #323

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ The return value of `menubar()` is a `Menubar` class instance, which has these p
- `getOption(option)`: get an menubar option,
- `showWindow()`: show the menubar window,
- `hideWindow()`: hide the menubar window
- `destroy()`: tear down the menubar instance
- `isDestroyed()`: whether menubar is currently destroyed

See the reference [API docs](./docs/classes/_menubar_.menubar.md).

Expand Down Expand Up @@ -135,4 +137,4 @@ See the reference [API docs](./docs/globals.md).
- Use `mb.on('focus-lost')` if you would like to perform some operation when using the option `browserWindow.alwaysOnTop: true`
- To restore focus of previous window after menubar hide, use `mb.on('after-hide', () => { mb.app.hide() } )` or similar
- To create a native menu, you can use `tray.setContextMenu(contextMenu)`, and pass this custom tray to menubar: `const mb = menubar({ tray });`. See [this example](https://github.com/maxogden/menubar/tree/master/examples/native-menu) for more information.
- To avoid a flash when opening your menubar app, you can disable backgrounding the app using the following: `mb.app.commandLine.appendSwitch('disable-backgrounding-occluded-windows', 'true');`
- To avoid a flash when opening your menubar app, you can disable backgrounding the app using the following: `mb.app.commandLine.appendSwitch('disable-backgrounding-occluded-windows', 'true');`
78 changes: 62 additions & 16 deletions src/Menubar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class Menubar extends EventEmitter {
private _app: Electron.App;
private _browserWindow?: BrowserWindow;
private _blurTimeout: NodeJS.Timeout | null = null; // track blur events with timeout
private _isDestroyed: boolean;
private _isVisible: boolean; // track visibility
private _cachedBounds?: Electron.Rectangle; // _cachedBounds are needed for double-clicked event
private _options: Options;
Expand All @@ -28,16 +29,13 @@ export class Menubar extends EventEmitter {
this._app = app;
this._options = cleanOptions(options);
this._isVisible = false;
this._isDestroyed = false;

if (app.isReady()) {
// See https://github.com/maxogden/menubar/pull/151
process.nextTick(() =>
this.appReady().catch((err) => console.error('menubar: ', err))
);
process.nextTick(this.onAppReady);
} else {
app.on('ready', () => {
this.appReady().catch((err) => console.error('menubar: ', err));
});
app.on('ready', this.onAppReady);
}
}

Expand Down Expand Up @@ -84,6 +82,38 @@ export class Menubar extends EventEmitter {
return this._browserWindow;
}

/**
* Tear down the menubar instance.
*/
destroy(): void {
if (this.isDestroyed()) {
return;
}

if (this._browserWindow) {
this._browserWindow.destroy();
this._browserWindow = undefined;
}

if (this.tray) {
// Ensure all potential listeners are removed.
for (const event of ['click', 'right-click', 'double-click']) {
this.tray.removeListener(
event as Parameters<Tray['on']>[0],
// eslint-disable-next-line @typescript-eslint/no-misused-promises
this.clicked
);
}
this.tray.setToolTip('');
this._tray = undefined;
}

this.app.removeListener('ready', this.onAppReady);
this.app.removeListener('activate', this.onAppActivate);

this._isDestroyed = true;
}

/**
* Retrieve a menubar option.
*
Expand All @@ -110,6 +140,13 @@ export class Menubar extends EventEmitter {
}
}

/**
* Indicates whether menubar is destroyed.
*/
isDestroyed(): boolean {
return this._isDestroyed;
}

/**
* Change an option after menubar is created.
*
Expand Down Expand Up @@ -199,11 +236,7 @@ export class Menubar extends EventEmitter {
this.app.dock.hide();
}

this.app.on('activate', (_event, hasVisibleWindows) => {
if (!hasVisibleWindows) {
this.showWindow().catch(console.error);
}
});
this.app.on('activate', this.onAppActivate);

let trayImage =
this._options.icon ||
Expand All @@ -229,10 +262,10 @@ export class Menubar extends EventEmitter {
this.tray.on(
defaultClickEvent as Parameters<Tray['on']>[0],
// eslint-disable-next-line @typescript-eslint/no-misused-promises
this.clicked.bind(this)
this.clicked
);
// eslint-disable-next-line @typescript-eslint/no-misused-promises
this.tray.on('double-click', this.clicked.bind(this));
this.tray.on('double-click', this.clicked);
this.tray.setToolTip(this._options.tooltip);

if (!this._options.windowPosition) {
Expand All @@ -253,10 +286,10 @@ export class Menubar extends EventEmitter {
* @param e
* @param bounds
*/
private async clicked(
private clicked = async (
event?: Electron.KeyboardEvent,
bounds?: Electron.Rectangle
): Promise<void> {
): Promise<void> => {
if (event && (event.shiftKey || event.ctrlKey || event.metaKey)) {
return this.hideWindow();
}
Expand All @@ -272,7 +305,7 @@ export class Menubar extends EventEmitter {

this._cachedBounds = bounds || this._cachedBounds;
await this.showWindow(this._cachedBounds);
}
};

private async createWindow(): Promise<void> {
this.emit('create-window');
Expand Down Expand Up @@ -321,6 +354,19 @@ export class Menubar extends EventEmitter {
this.emit('after-create-window');
}

private onAppActivate = (
_event: Event,
hasVisibleWindows: boolean
): void => {
if (!hasVisibleWindows) {
this.showWindow().catch(console.error);
}
};

private onAppReady = (): void => {
this.appReady().catch((err) => console.error('menubar: ', err));
};

private windowClear(): void {
this._browserWindow = undefined;
this.emit('after-close');
Expand Down