Skip to content

Commit

Permalink
Merge branch 'main' into feat-settings-window
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverschwendener authored Dec 11, 2024
2 parents f36625f + 547d380 commit 67a9848
Show file tree
Hide file tree
Showing 69 changed files with 383 additions and 356 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ueli",
"description": "Cross-Platform Keystroke Launcher",
"version": "9.11.1",
"version": "9.12.0",
"license": "MIT",
"author": {
"email": "[email protected]",
Expand Down
1 change: 1 addition & 0 deletions src/common/Core/FluentIcon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type FluentIcon =
| "AppsAddInRegular"
| "ArrowClockwiseRegular"
| "ArrowSquareUpRightRegular"
| "CheckmarkCircleRegular"
| "CopyRegular"
| "DeleteDismissRegular"
| "DismissCircleRegular"
Expand Down
14 changes: 13 additions & 1 deletion src/main/Core/GlobalShortcut/GlobalShortcutModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export class GlobalShortcutModule {
const settingsManager = dependencyRegistry.get("SettingsManager");
const logger = dependencyRegistry.get("Logger");

const hotkeyIsEnabled = () => settingsManager.getValue("general.hotkey.enabled", true);

const registerHotkey = () => {
const hotkey = settingsManager.getValue("general.hotkey", "Alt+Space");

Expand All @@ -21,8 +23,18 @@ export class GlobalShortcutModule {
globalShortcut.register(hotkey, () => eventEmitter.emitEvent("hotkeyPressed"));
};

registerHotkey();
if (hotkeyIsEnabled()) {
registerHotkey();
}

eventSubscriber.subscribe("settingUpdated[general.hotkey]", () => registerHotkey());

eventSubscriber.subscribe("settingUpdated[general.hotkey.enabled]", () => {
if (hotkeyIsEnabled()) {
registerHotkey();
} else {
globalShortcut.unregisterAll();
}
});
}
}
20 changes: 19 additions & 1 deletion src/main/Core/TrayIcon/ContextMenuTemplateProvider.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SettingsManager } from "@Core/SettingsManager";
import type { Translator } from "@Core/Translator";
import type { UeliCommandInvoker } from "@Core/UeliCommand";
import type { MenuItemConstructorOptions } from "electron";
Expand All @@ -10,19 +11,36 @@ describe(ContextMenuTemplateProvider, () => {
it("should return context menu template with translations", async () => {
const t = (key: string) => `translation[${key}]`;
const createTMock = vi.fn().mockReturnValue({ t });

const getValueMock = vi.fn().mockReturnValue(true);

const translator = <Translator>{ createT: createTMock };
const ueliCommandInvoker = <UeliCommandInvoker>{};
const settingsManager = <SettingsManager>{ getValue: (k, d) => getValueMock(k, d) };

const actual = await new ContextMenuTemplateProvider(translator, ueliCommandInvoker, resources).get();
const actual = await new ContextMenuTemplateProvider(
translator,
ueliCommandInvoker,
settingsManager,
resources,
).get();

const expected: MenuItemConstructorOptions[] = [
{ label: "translation[trayIcon.contextMenu.show]", click: () => null },
{ label: "translation[trayIcon.contextMenu.settings]", click: () => null },
{ label: "translation[trayIcon.contextMenu.about]", click: () => null },
{ label: "translation[trayIcon.contextMenu.quit]", click: () => null },
{
label: "translation[trayIcon.contextMenu.hotkey]",
click: () => null,
checked: true,
type: "checkbox",
toolTip: "translation[trayIcon.contextMenu.hotkey.tooltip]",
},
];

expect(createTMock).toHaveBeenCalledWith(resources);
expect(getValueMock).toHaveBeenCalledWith("general.hotkey.enabled", true);
expect(JSON.stringify(actual)).toEqual(JSON.stringify(expected));
});
});
Expand Down
14 changes: 14 additions & 0 deletions src/main/Core/TrayIcon/ContextMenuTemplateProvider.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SettingsManager } from "@Core/SettingsManager";
import type { Translator } from "@Core/Translator";
import type { UeliCommandInvoker } from "@Core/UeliCommand";
import type { Resources } from "@common/Core/Translator";
Expand All @@ -8,12 +9,15 @@ export class ContextMenuTemplateProvider {
public constructor(
private readonly translator: Translator,
private readonly ueliCommandInvoker: UeliCommandInvoker,
private readonly settingsManager: SettingsManager,
private readonly resources: Resources<TrayIconTranslations>,
) {}

public async get(): Promise<MenuItemConstructorOptions[]> {
const { t } = this.translator.createT(this.resources);

const hotkeyEnabled = this.settingsManager.getValue("general.hotkey.enabled", true);

return [
{
label: t("trayIcon.contextMenu.show"),
Expand All @@ -31,6 +35,16 @@ export class ContextMenuTemplateProvider {
label: t("trayIcon.contextMenu.quit"),
click: () => this.ueliCommandInvoker.invokeUeliCommand("quit"),
},
{
label: t("trayIcon.contextMenu.hotkey"),
checked: hotkeyEnabled,
type: "checkbox",
toolTip: t("trayIcon.contextMenu.hotkey.tooltip"),
click: () =>
hotkeyEnabled
? this.ueliCommandInvoker.invokeUeliCommand("disableHotkey")
: this.ueliCommandInvoker.invokeUeliCommand("enableHotkey"),
},
];
}
}
13 changes: 9 additions & 4 deletions src/main/Core/TrayIcon/TrayIconModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ export class TrayIconModule {
const nativeTheme = dependencyRegistry.get("NativeTheme");
const assetPathResolver = dependencyRegistry.get("AssetPathResolver");
const operatingSystem = dependencyRegistry.get("OperatingSystem");
const translator = dependencyRegistry.get("Translator");
const ueliCommandInvoker = dependencyRegistry.get("UeliCommandInvoker");
const eventSubscriber = dependencyRegistry.get("EventSubscriber");

const trayIconFilePathResolvers: Record<OperatingSystem, () => TrayIconFilePathResolver> = {
Linux: () => new LinuxTrayIconFilePathResolver(nativeTheme, assetPathResolver),
Expand All @@ -31,14 +28,22 @@ export class TrayIconModule {
const trayIconManager = new TrayIconManager(
new TrayCreator(),
trayIconFilePathResolvers[operatingSystem](),
new ContextMenuTemplateProvider(translator, ueliCommandInvoker, resources),
new ContextMenuTemplateProvider(
dependencyRegistry.get("Translator"),
dependencyRegistry.get("UeliCommandInvoker"),
dependencyRegistry.get("SettingsManager"),
resources,
),
new ContextMenuBuilder(),
);

await trayIconManager.createTrayIcon();

nativeTheme.on("updated", () => trayIconManager.updateImage());

const eventSubscriber = dependencyRegistry.get("EventSubscriber");

eventSubscriber.subscribe("settingUpdated[general.language]", () => trayIconManager.updateContextMenu());
eventSubscriber.subscribe("settingUpdated[general.hotkey.enabled]", () => trayIconManager.updateContextMenu());
}
}
2 changes: 2 additions & 0 deletions src/main/Core/TrayIcon/TrayIconTranslations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ export type TrayIconTranslations = {
"trayIcon.contextMenu.quit": string;
"trayIcon.contextMenu.settings": string;
"trayIcon.contextMenu.show": string;
"trayIcon.contextMenu.hotkey": string;
"trayIcon.contextMenu.hotkey.tooltip": string;
};
4 changes: 4 additions & 0 deletions src/main/Core/TrayIcon/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ export const resources: Resources<TrayIconTranslations> = {
"trayIcon.contextMenu.quit": "Quit",
"trayIcon.contextMenu.settings": "Settings",
"trayIcon.contextMenu.show": "Show",
"trayIcon.contextMenu.hotkey": "Hotkey",
"trayIcon.contextMenu.hotkey.tooltip": "Click to enable/disable the hotkey",
},
"de-CH": {
"trayIcon.contextMenu.about": "Über",
"trayIcon.contextMenu.quit": "Beenden",
"trayIcon.contextMenu.settings": "Einstellungen",
"trayIcon.contextMenu.show": "Anzeigen",
"trayIcon.contextMenu.hotkey": "Tastenkombination",
"trayIcon.contextMenu.hotkey.tooltip": "Klick um die Tastenkombination zu aktivieren/deaktivieren",
},
};
2 changes: 2 additions & 0 deletions src/main/Core/UeliCommand/Contract/UeliCommand.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export type UeliCommand =
| "centerWindow"
| "disableHotkey"
| "enableHotkey"
| "openAbout"
| "openExtensions"
| "openSettings"
Expand Down
16 changes: 16 additions & 0 deletions src/main/Core/UeliCommand/UeliCommandActionHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { SearchResultItemAction } from "@common/Core";
import { describe, expect, it, vi } from "vitest";
import type { UeliCommandInvoker } from "./Contract";
import { UeliCommandActionHandler } from "./UeliCommandActionHandler";

describe(UeliCommandActionHandler, () => {
it("should invoke an ueli command", async () => {
const invokeUeliCommandMock = vi.fn();
const ueliCommandInvoker = <UeliCommandInvoker>{ invokeUeliCommand: (u) => invokeUeliCommandMock(u) };
const ueliCommandActionHandler = new UeliCommandActionHandler(ueliCommandInvoker);

await ueliCommandActionHandler.invokeAction(<SearchResultItemAction>{ argument: "test ueli command" });

expect(invokeUeliCommandMock).toHaveBeenCalledWith("test ueli command");
});
});
13 changes: 13 additions & 0 deletions src/main/Core/UeliCommand/UeliCommandActionHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { SearchResultItemAction } from "@common/Core";
import type { ActionHandler } from "@Core/ActionHandler";
import type { UeliCommand, UeliCommandInvoker } from "./Contract";

export class UeliCommandActionHandler implements ActionHandler {
public readonly id = "UeliCommand";

public constructor(private readonly ueliCommandInvoker: UeliCommandInvoker) {}

public async invokeAction(action: SearchResultItemAction): Promise<void> {
await this.ueliCommandInvoker.invokeUeliCommand(action.argument as UeliCommand);
}
}
8 changes: 8 additions & 0 deletions src/main/Core/UeliCommand/UeliCommandInvoker.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SettingsManager } from "@Core/SettingsManager";
import type { App } from "electron";
import type { EventEmitter } from "../EventEmitter";
import type {
Expand All @@ -10,10 +11,13 @@ export class UeliCommandInvoker implements UeliCommandInvokerInterface {
public constructor(
private readonly app: App,
private readonly eventEmitter: EventEmitter,
private readonly settingsManager: SettingsManager,
) {}

public invokeUeliCommand(ueliCommand: UeliCommand): Promise<void> {
const map: Record<UeliCommand, () => Promise<void>> = {
disableHotkey: async () => await this.settingsManager.updateValue("general.hotkey.enabled", false),
enableHotkey: async () => await this.settingsManager.updateValue("general.hotkey.enabled", true),
openExtensions: async () =>
this.emitUeliCommandInvokedEvent({
ueliCommand: "openExtensions",
Expand Down Expand Up @@ -47,6 +51,10 @@ export class UeliCommandInvoker implements UeliCommandInvokerInterface {
}),
};

if (!Object.keys(map).includes(ueliCommand)) {
throw new Error(`Invalid ueli command: ${ueliCommand}`);
}

return map[ueliCommand]();
}

Expand Down
9 changes: 8 additions & 1 deletion src/main/Core/UeliCommand/UeliCommandModule.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import type { Dependencies } from "@Core/Dependencies";
import type { DependencyRegistry } from "@Core/DependencyRegistry";
import { UeliCommandActionHandler } from "./UeliCommandActionHandler";
import { UeliCommandInvoker } from "./UeliCommandInvoker";

export class UeliCommandModule {
public static bootstrap(dependencyRegistry: DependencyRegistry<Dependencies>) {
const app = dependencyRegistry.get("App");
const eventEmitter = dependencyRegistry.get("EventEmitter");
const actionHandlerRegistry = dependencyRegistry.get("ActionHandlerRegistry");
const settingsManager = dependencyRegistry.get("SettingsManager");

dependencyRegistry.register("UeliCommandInvoker", new UeliCommandInvoker(app, eventEmitter));
const ueliCommandInvoker = new UeliCommandInvoker(app, eventEmitter, settingsManager);

dependencyRegistry.register("UeliCommandInvoker", ueliCommandInvoker);

actionHandlerRegistry.register(new UeliCommandActionHandler(ueliCommandInvoker));
}
}
57 changes: 0 additions & 57 deletions src/main/Extensions/UeliCommand/UeliCommandActionHandler.test.ts

This file was deleted.

25 changes: 0 additions & 25 deletions src/main/Extensions/UeliCommand/UeliCommandActionHandler.ts

This file was deleted.

Loading

0 comments on commit 67a9848

Please sign in to comment.