Notes/src/services/tray.ts

227 lines
6.5 KiB
TypeScript
Raw Normal View History

2025-01-09 18:07:02 +02:00
import { Menu, Tray } from "electron";
import path from "path";
import windowService from "./window.js";
import optionService from "./options.js";
2024-07-19 00:18:35 +03:00
import { fileURLToPath } from "url";
import type { KeyboardActionNames } from "./keyboard_actions_interface.js";
2025-02-01 02:18:10 +02:00
import date_notes from "./date_notes.js";
import type BNote from "../becca/entities/bnote.js";
2025-02-01 02:29:34 +02:00
import becca from "../becca/becca.js";
2025-02-01 02:40:04 +02:00
import becca_service from "../becca/becca_service.js";
import type BRecentNote from "../becca/entities/brecent_note.js";
2025-02-01 11:04:49 +02:00
import { ipcMain, nativeTheme } from "electron/main";
2025-02-01 11:20:29 +02:00
import { default as i18next, t } from "i18next";
import { isDev } from "./utils.js";
import cls from "./cls.js";
let tray: Tray;
// `mainWindow.isVisible` doesn't work with `mainWindow.show` and `mainWindow.hide` - it returns `false` when the window
// is minimized
let isVisible = true;
// Inspired by https://github.com/signalapp/Signal-Desktop/blob/dcb5bb672635c4b29a51adec8a5658e3834ec8fc/app/tray_icon.ts#L20
function getIconSize() {
switch (process.platform) {
2025-01-09 18:07:02 +02:00
case "darwin":
return 16;
2025-01-09 18:07:02 +02:00
case "win32":
return 32;
default:
return 256;
}
}
2025-02-01 10:18:41 +02:00
function getTrayIconPath() {
const iconSize = getIconSize();
2025-02-01 12:45:32 +02:00
const name = isDev ? "icon-purple" : "icon-color";
2025-02-01 12:45:32 +02:00
return path.join(path.dirname(fileURLToPath(import.meta.url)), "../..", "images", "app-icons", "tray", `${name}.png`);
}
2025-02-01 10:18:41 +02:00
function getIconPath(name: string) {
2025-02-01 11:59:42 +02:00
const suffix = (nativeTheme.shouldUseDarkColors ? "-inverted" : "");
return path.join(path.dirname(fileURLToPath(import.meta.url)), "../..", "images", "app-icons", "tray", `${name}${suffix}.png`);
2025-02-01 10:18:41 +02:00
}
function registerVisibilityListener() {
const mainWindow = windowService.getMainWindow();
2025-01-09 18:07:02 +02:00
if (!mainWindow) {
return;
}
// They need to be registered before the tray updater is registered
2025-01-09 18:07:02 +02:00
mainWindow.on("show", () => {
isVisible = true;
updateTrayMenu();
});
2025-01-09 18:07:02 +02:00
mainWindow.on("hide", () => {
isVisible = false;
updateTrayMenu();
});
mainWindow.on("minimize", updateTrayMenu);
mainWindow.on("maximize", updateTrayMenu);
2025-02-01 10:41:08 +02:00
nativeTheme.on("updated", updateTrayMenu);
2025-02-01 11:04:49 +02:00
ipcMain.on("reload-tray", updateTrayMenu);
2025-02-01 11:20:29 +02:00
i18next.on("languageChanged", updateTrayMenu);
}
function updateTrayMenu() {
const mainWindow = windowService.getMainWindow();
2025-01-09 18:07:02 +02:00
if (!mainWindow) {
return;
}
2025-02-01 10:46:27 +02:00
function ensureVisible() {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
}
}
function triggerKeyboardAction(actionName: KeyboardActionNames) {
mainWindow?.webContents.send("globalShortcut", actionName);
2025-02-01 10:46:27 +02:00
ensureVisible();
}
2025-02-01 02:40:04 +02:00
function openInSameTab(note: BNote | BRecentNote) {
2025-02-01 02:18:10 +02:00
mainWindow?.webContents.send("openInSameTab", note.noteId);
2025-02-01 10:46:27 +02:00
ensureVisible();
2025-02-01 02:18:10 +02:00
}
2025-02-01 02:29:34 +02:00
function buildBookmarksMenu() {
const parentNote = becca.getNoteOrThrow("_lbBookmarks");
const menuItems: Electron.MenuItemConstructorOptions[] = [];
for (const bookmarkNote of parentNote?.children) {
if (bookmarkNote.isLabelTruthy("bookmarkFolder")) {
2025-02-01 10:54:00 +02:00
menuItems.push({
label: bookmarkNote.title,
type: "submenu",
submenu: bookmarkNote.children.map((subitem) => {
return {
label: subitem.title,
type: "normal",
click: () => openInSameTab(subitem)
};
})
});
} else {
menuItems.push({
label: bookmarkNote.title,
type: "normal",
click: () => openInSameTab(bookmarkNote)
});
2025-02-01 02:29:34 +02:00
}
}
return menuItems;
}
2025-02-01 02:40:04 +02:00
function buildRecentNotesMenu() {
const recentNotes = becca.getRecentNotesFromQuery(`
SELECT recent_notes.*
FROM recent_notes
JOIN notes USING(noteId)
WHERE notes.isDeleted = 0
ORDER BY utcDateCreated DESC
LIMIT 10
`);
const menuItems: Electron.MenuItemConstructorOptions[] = [];
for (const recentNote of recentNotes) {
menuItems.push({
label: becca_service.getNoteTitle(recentNote.noteId),
type: "normal",
click: () => openInSameTab(recentNote)
})
}
return menuItems;
}
const contextMenu = Menu.buildFromTemplate([
{
2025-02-01 11:16:46 +02:00
label: t("tray.show-windows"),
type: "checkbox",
checked: isVisible,
click: () => {
if (isVisible) {
mainWindow.hide();
} else {
2025-02-01 10:46:27 +02:00
ensureVisible();
}
}
},
{ type: "separator" },
{
2025-02-01 11:16:46 +02:00
label: t("tray.new-note"),
type: "normal",
2025-02-01 10:18:41 +02:00
icon: getIconPath("new-note"),
click: () => triggerKeyboardAction("createNoteIntoInbox")
},
2025-02-01 02:18:10 +02:00
{
2025-02-01 11:16:46 +02:00
label: t("tray.today"),
2025-02-01 02:18:10 +02:00
type: "normal",
2025-02-01 10:18:41 +02:00
icon: getIconPath("today"),
click: cls.wrap(() => openInSameTab(date_notes.getTodayNote()))
2025-02-01 02:18:10 +02:00
},
2025-02-01 02:29:34 +02:00
{
2025-02-01 11:16:46 +02:00
label: t("tray.bookmarks"),
2025-02-01 02:29:34 +02:00
type: "submenu",
2025-02-01 10:18:41 +02:00
icon: getIconPath("bookmarks"),
2025-02-01 02:29:34 +02:00
submenu: buildBookmarksMenu()
},
2025-02-01 02:40:04 +02:00
{
2025-02-01 11:16:46 +02:00
label: t("tray.recents"),
2025-02-01 02:40:04 +02:00
type: "submenu",
2025-02-01 10:18:41 +02:00
icon: getIconPath("recents"),
2025-02-01 02:40:04 +02:00
submenu: buildRecentNotesMenu()
},
{ type: "separator" },
{
2025-02-01 11:16:46 +02:00
label: t("tray.close"),
2025-01-09 18:07:02 +02:00
type: "normal",
2025-02-01 10:18:41 +02:00
icon: getIconPath("close"),
click: () => {
mainWindow.close();
}
2025-01-09 18:07:02 +02:00
}
]);
tray?.setContextMenu(contextMenu);
}
function changeVisibility() {
2022-01-10 17:09:20 +01:00
const window = windowService.getMainWindow();
2025-01-09 18:07:02 +02:00
if (!window) {
return;
}
if (isVisible) {
window.hide();
} else {
window.show();
window.focus();
}
}
function createTray() {
2022-11-18 21:08:32 +01:00
if (optionService.getOptionBool("disableTray")) {
return;
}
2025-02-01 10:18:41 +02:00
tray = new Tray(getTrayIconPath());
2025-02-01 11:16:46 +02:00
tray.setToolTip(t("tray.tooltip"));
// Restore focus
2025-01-09 18:07:02 +02:00
tray.on("click", changeVisibility);
updateTrayMenu();
registerVisibilityListener();
}
export default {
createTray
2025-01-09 18:07:02 +02:00
};