mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
This commit is contained in:
parent
cb53bb97ff
commit
376e068412
5 changed files with 291 additions and 7 deletions
|
|
@ -12,6 +12,17 @@ Note: Differences between the Web app and the Desktop app are specified in [[3.0
|
|||
|
||||
#### global-settings.Misc-Settings
|
||||
|
||||
- **Show current countdown in the tray / status menu (macOS only)** — Shows the
|
||||
active timer or countdown in the desktop status menu title. When this is
|
||||
disabled and current-task display is enabled, the title can show the current
|
||||
task instead.
|
||||
|
||||
#### global-settings.Tasks
|
||||
|
||||
- **Show current task in the tray / status menu (macOS/Windows only)** — Allows
|
||||
the desktop tray or status menu to show the active task. On macOS, the current
|
||||
countdown setting takes priority while it is enabled.
|
||||
|
||||
#### global-settings.Short-Syntax
|
||||
|
||||
#### global-settings.Idle-Handling
|
||||
|
|
|
|||
213
electron/app-control.test.cjs
Normal file
213
electron/app-control.test.cjs
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
const Module = require('node:module');
|
||||
|
||||
require('ts-node/register/transpile-only');
|
||||
|
||||
const originalModuleLoad = Module._load;
|
||||
const appControlModulePath = path.resolve(__dirname, 'ipc-handlers/app-control.ts');
|
||||
|
||||
let ipcHandlers;
|
||||
let nextIsLocked;
|
||||
let sharedState;
|
||||
let refreshIndicatorCalls;
|
||||
let localRestApiConfig;
|
||||
|
||||
const resetModule = () => {
|
||||
delete require.cache[appControlModulePath];
|
||||
};
|
||||
|
||||
const installMocks = () => {
|
||||
Module._load = function patchedLoad(request, parent, isMain) {
|
||||
if (request === 'electron') {
|
||||
return {
|
||||
app: {
|
||||
exit: () => {},
|
||||
relaunch: () => {},
|
||||
},
|
||||
ipcMain: {
|
||||
on: (eventName, handler) => {
|
||||
ipcHandlers.set(eventName, handler);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (request === '../shared-with-frontend/ipc-events.const') {
|
||||
return {
|
||||
IPC: {
|
||||
SHUTDOWN_NOW: 'SHUTDOWN_NOW',
|
||||
EXIT: 'EXIT',
|
||||
RELAUNCH: 'RELAUNCH',
|
||||
OPEN_DEV_TOOLS: 'OPEN_DEV_TOOLS',
|
||||
RELOAD_MAIN_WIN: 'RELOAD_MAIN_WIN',
|
||||
TRANSFER_SETTINGS_TO_ELECTRON: 'TRANSFER_SETTINGS_TO_ELECTRON',
|
||||
UPDATE_SETTINGS: 'UPDATE_SETTINGS',
|
||||
SHOW_OR_FOCUS: 'SHOW_OR_FOCUS',
|
||||
LOCK_SCREEN: 'LOCK_SCREEN',
|
||||
SET_PROGRESS_BAR: 'SET_PROGRESS_BAR',
|
||||
FLASH_FRAME: 'FLASH_FRAME',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (request === '../main-window') {
|
||||
return {
|
||||
getWin: () => ({
|
||||
webContents: {
|
||||
openDevTools: () => {},
|
||||
},
|
||||
reload: () => {},
|
||||
setProgressBar: () => {},
|
||||
flashFrame: () => {},
|
||||
once: () => {},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (request === '../various-shared') {
|
||||
return {
|
||||
quitApp: () => {},
|
||||
showOrFocus: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
if (request === '../shared-state') {
|
||||
return {
|
||||
getIsLocked: () => nextIsLocked,
|
||||
setIsMinimizeToTray: (value) => {
|
||||
sharedState.isMinimizeToTray = value;
|
||||
},
|
||||
setIsTrayShowCurrentTask: (value) => {
|
||||
sharedState.isTrayShowCurrentTask = value;
|
||||
},
|
||||
setIsTrayShowCurrentCountdown: (value) => {
|
||||
sharedState.isTrayShowCurrentCountdown = value;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (request === '../indicator') {
|
||||
return {
|
||||
refreshIndicator: () => {
|
||||
refreshIndicatorCalls += 1;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (request === '../lockscreen') {
|
||||
return {
|
||||
lockscreen: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
if (request === '../error-handler-with-frontend-inform') {
|
||||
return {
|
||||
errorHandlerWithFrontendInform: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
if (request === '../../src/app/features/config/global-config.model') {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (request === '../simple-store') {
|
||||
return {
|
||||
saveSimpleStore: async () => {},
|
||||
};
|
||||
}
|
||||
|
||||
if (request === '../shared-with-frontend/simple-store.const') {
|
||||
return {
|
||||
SimpleStoreKey: {
|
||||
IS_USE_CUSTOM_WINDOW_TITLE_BAR: 'isUseCustomWindowTitleBar',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (request === '../local-rest-api') {
|
||||
return {
|
||||
updateLocalRestApiConfig: (cfg) => {
|
||||
localRestApiConfig = cfg;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return originalModuleLoad.call(this, request, parent, isMain);
|
||||
};
|
||||
};
|
||||
|
||||
const loadAppControlModule = () => {
|
||||
resetModule();
|
||||
return require(appControlModulePath);
|
||||
};
|
||||
|
||||
test.beforeEach(() => {
|
||||
ipcHandlers = new Map();
|
||||
nextIsLocked = false;
|
||||
sharedState = {
|
||||
isMinimizeToTray: undefined,
|
||||
isTrayShowCurrentTask: undefined,
|
||||
isTrayShowCurrentCountdown: undefined,
|
||||
};
|
||||
refreshIndicatorCalls = 0;
|
||||
localRestApiConfig = undefined;
|
||||
|
||||
installMocks();
|
||||
});
|
||||
|
||||
test.afterEach(() => {
|
||||
Module._load = originalModuleLoad;
|
||||
resetModule();
|
||||
});
|
||||
|
||||
test('settings update reads current task tray setting from tasks config', async () => {
|
||||
const { initAppControlIpc } = loadAppControlModule();
|
||||
initAppControlIpc();
|
||||
|
||||
const updateSettings = ipcHandlers.get('TRANSFER_SETTINGS_TO_ELECTRON');
|
||||
assert.equal(typeof updateSettings, 'function');
|
||||
|
||||
const cfg = {
|
||||
tasks: {
|
||||
isTrayShowCurrent: false,
|
||||
},
|
||||
misc: {
|
||||
isMinimizeToTray: true,
|
||||
isTrayShowCurrentTask: true,
|
||||
isTrayShowCurrentCountdown: false,
|
||||
},
|
||||
};
|
||||
|
||||
await updateSettings({}, cfg);
|
||||
|
||||
assert.equal(sharedState.isMinimizeToTray, true);
|
||||
assert.equal(sharedState.isTrayShowCurrentTask, false);
|
||||
assert.equal(sharedState.isTrayShowCurrentCountdown, false);
|
||||
assert.equal(refreshIndicatorCalls, 1);
|
||||
assert.equal(localRestApiConfig, cfg);
|
||||
});
|
||||
|
||||
test('settings update falls back to legacy misc tray task setting', async () => {
|
||||
const { initAppControlIpc } = loadAppControlModule();
|
||||
initAppControlIpc();
|
||||
|
||||
const updateSettings = ipcHandlers.get('UPDATE_SETTINGS');
|
||||
assert.equal(typeof updateSettings, 'function');
|
||||
|
||||
await updateSettings(
|
||||
{},
|
||||
{
|
||||
misc: {
|
||||
isMinimizeToTray: false,
|
||||
isTrayShowCurrentTask: true,
|
||||
isTrayShowCurrentCountdown: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
assert.equal(sharedState.isTrayShowCurrentTask, true);
|
||||
assert.equal(sharedState.isTrayShowCurrentCountdown, true);
|
||||
assert.equal(refreshIndicatorCalls, 1);
|
||||
});
|
||||
|
|
@ -13,9 +13,13 @@ const indicatorModulePath = path.resolve(__dirname, 'indicator.ts');
|
|||
let createdTrayArgs;
|
||||
let createdFromPath = [];
|
||||
let traySetImageCalls = [];
|
||||
let traySetTitleCalls = [];
|
||||
let traySetToolTipCalls = [];
|
||||
let ipcHandlers = new Map();
|
||||
let nextNativeImageIsEmpty = false;
|
||||
let beforeQuitHandler = () => {};
|
||||
let mockIsTrayShowCurrentTask = false;
|
||||
let mockIsTrayShowCurrentCountdown = false;
|
||||
|
||||
const resetModule = () => {
|
||||
delete require.cache[indicatorModulePath];
|
||||
|
|
@ -41,9 +45,13 @@ const installMocks = () => {
|
|||
traySetImageCalls.push(image);
|
||||
}
|
||||
|
||||
setTitle() {}
|
||||
setTitle(title) {
|
||||
traySetTitleCalls.push(title);
|
||||
}
|
||||
|
||||
setToolTip() {}
|
||||
setToolTip(title) {
|
||||
traySetToolTipCalls.push(title);
|
||||
}
|
||||
|
||||
destroy() {}
|
||||
}
|
||||
|
|
@ -68,6 +76,7 @@ const installMocks = () => {
|
|||
iconPath,
|
||||
kind: 'native-image',
|
||||
isEmpty: () => nextNativeImageIsEmpty,
|
||||
setTemplateImage: () => {},
|
||||
};
|
||||
},
|
||||
},
|
||||
|
|
@ -95,8 +104,8 @@ const installMocks = () => {
|
|||
|
||||
if (request === './shared-state') {
|
||||
return {
|
||||
getIsTrayShowCurrentTask: () => false,
|
||||
getIsTrayShowCurrentCountdown: () => false,
|
||||
getIsTrayShowCurrentTask: () => mockIsTrayShowCurrentTask,
|
||||
getIsTrayShowCurrentCountdown: () => mockIsTrayShowCurrentCountdown,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -136,9 +145,13 @@ test.beforeEach(() => {
|
|||
createdTrayArgs = [];
|
||||
createdFromPath = [];
|
||||
traySetImageCalls = [];
|
||||
traySetTitleCalls = [];
|
||||
traySetToolTipCalls = [];
|
||||
ipcHandlers = new Map();
|
||||
nextNativeImageIsEmpty = false;
|
||||
beforeQuitHandler = () => {};
|
||||
mockIsTrayShowCurrentTask = false;
|
||||
mockIsTrayShowCurrentCountdown = false;
|
||||
|
||||
Object.defineProperty(process, 'platform', {
|
||||
configurable: true,
|
||||
|
|
@ -216,3 +229,40 @@ test('initIndicator falls back to icon path if NativeImage creation is empty', (
|
|||
assert.equal(typeof createdTrayArgs[0][0], 'string');
|
||||
assert.match(createdTrayArgs[0][0], /\/icons\/indicator\/stopped-d\.png$/);
|
||||
});
|
||||
|
||||
test('tray title shows the task title when countdown display is disabled', () => {
|
||||
mockIsTrayShowCurrentTask = true;
|
||||
mockIsTrayShowCurrentCountdown = false;
|
||||
const { initIndicator } = loadIndicatorModule();
|
||||
|
||||
initIndicator({
|
||||
showApp: () => {},
|
||||
quitApp: () => {},
|
||||
ICONS_FOLDER: '/icons/',
|
||||
forceDarkTray: false,
|
||||
app: {
|
||||
on: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const currentTaskUpdated = ipcHandlers.get('CURRENT_TASK_UPDATED');
|
||||
assert.equal(typeof currentTaskUpdated, 'function');
|
||||
|
||||
currentTaskUpdated(
|
||||
{},
|
||||
{
|
||||
id: 'T1',
|
||||
title: 'Write release notes',
|
||||
timeSpent: 5 * 60000,
|
||||
timeEstimate: 25 * 60000,
|
||||
},
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
0,
|
||||
undefined,
|
||||
);
|
||||
|
||||
assert.equal(traySetTitleCalls.at(-1), 'Write release notes');
|
||||
assert.equal(traySetToolTipCalls.at(-1), 'Write release notes');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -142,6 +142,12 @@ export const ensureIndicator = (): Tray | undefined => {
|
|||
return tray;
|
||||
};
|
||||
|
||||
export const refreshIndicator = (): void => {
|
||||
if (tray) {
|
||||
syncTray(tray);
|
||||
}
|
||||
};
|
||||
|
||||
const createTray = (): Tray => {
|
||||
const suf = shouldUseDarkColors ? '-d.png' : '-l.png';
|
||||
const trayIconPath = DIR + `stopped${suf}`;
|
||||
|
|
@ -431,6 +437,7 @@ const syncTray = (tr: Tray): void => {
|
|||
tr.setToolTip('');
|
||||
}
|
||||
}
|
||||
_lastTrayMsg = trayMsg;
|
||||
|
||||
if (_lastCurrentTask?.title && !_lastIsFocusModeEnabled) {
|
||||
const progress = _lastCurrentTask.timeEstimate
|
||||
|
|
@ -497,8 +504,7 @@ function createIndicatorMessage(
|
|||
return timeStr;
|
||||
}
|
||||
|
||||
// Fallback if no countdown is supposed to be shown, but we have a running task
|
||||
return getProgressMessage(task.timeSpent);
|
||||
return task.title;
|
||||
}
|
||||
|
||||
return '';
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
setIsTrayShowCurrentTask,
|
||||
setIsTrayShowCurrentCountdown,
|
||||
} from '../shared-state';
|
||||
import { refreshIndicator } from '../indicator';
|
||||
import { lockscreen } from '../lockscreen';
|
||||
import { errorHandlerWithFrontendInform } from '../error-handler-with-frontend-inform';
|
||||
import { GlobalConfigState } from '../../src/app/features/config/global-config.model';
|
||||
|
|
@ -41,8 +42,11 @@ export const initAppControlIpc = (): void => {
|
|||
|
||||
const updateSettings = async (ev: any, cfg: GlobalConfigState): Promise<void> => {
|
||||
setIsMinimizeToTray(cfg.misc.isMinimizeToTray);
|
||||
setIsTrayShowCurrentTask(!!cfg.misc.isTrayShowCurrentTask);
|
||||
setIsTrayShowCurrentTask(
|
||||
cfg.tasks?.isTrayShowCurrent ?? !!cfg.misc.isTrayShowCurrentTask,
|
||||
);
|
||||
setIsTrayShowCurrentCountdown(!!cfg.misc.isTrayShowCurrentCountdown);
|
||||
refreshIndicator();
|
||||
updateLocalRestApiConfig(cfg);
|
||||
|
||||
if (cfg.misc.isUseCustomWindowTitleBar !== undefined) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue