mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-05-16 20:57:12 +02:00
Split codebase (#455)
This commit is contained in:
5
apps/yaak-proxy/lib/fireAndForget.ts
Normal file
5
apps/yaak-proxy/lib/fireAndForget.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function fireAndForget(promise: Promise<unknown>) {
|
||||
promise.catch((err: unknown) => {
|
||||
console.error("Unhandled async error:", err);
|
||||
});
|
||||
}
|
||||
63
apps/yaak-proxy/lib/hotkeys.ts
Normal file
63
apps/yaak-proxy/lib/hotkeys.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { ActionInvocation, ActionMetadata } from "@yaakapp-internal/proxy-lib";
|
||||
import { rpc } from "./rpc";
|
||||
|
||||
type ActionBinding = {
|
||||
invocation: ActionInvocation;
|
||||
meta: ActionMetadata;
|
||||
keys: { key: string; ctrl: boolean; shift: boolean; alt: boolean; meta: boolean };
|
||||
};
|
||||
|
||||
/** Parse a hotkey string like "Ctrl+Shift+P" into its parts. */
|
||||
function parseHotkey(hotkey: string): ActionBinding["keys"] {
|
||||
const parts = hotkey.split("+").map((p) => p.trim().toLowerCase());
|
||||
return {
|
||||
ctrl: parts.includes("ctrl") || parts.includes("control"),
|
||||
shift: parts.includes("shift"),
|
||||
alt: parts.includes("alt"),
|
||||
meta: parts.includes("meta") || parts.includes("cmd") || parts.includes("command"),
|
||||
key:
|
||||
parts.filter(
|
||||
(p) => !["ctrl", "control", "shift", "alt", "meta", "cmd", "command"].includes(p),
|
||||
)[0] ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
function matchesEvent(binding: ActionBinding["keys"], e: KeyboardEvent): boolean {
|
||||
return (
|
||||
e.ctrlKey === binding.ctrl &&
|
||||
e.shiftKey === binding.shift &&
|
||||
e.altKey === binding.alt &&
|
||||
e.metaKey === binding.meta &&
|
||||
e.key.toLowerCase() === binding.key
|
||||
);
|
||||
}
|
||||
|
||||
/** Fetch all actions from Rust and register a global keydown listener. */
|
||||
export async function initHotkeys(): Promise<() => void> {
|
||||
const { actions } = await rpc("list_actions", {});
|
||||
|
||||
const bindings: ActionBinding[] = actions
|
||||
.filter(
|
||||
// oxlint-disable-next-line no-redundant-type-constituents -- ActionMetadata resolves at runtime
|
||||
(entry): entry is [ActionInvocation, ActionMetadata & { defaultHotkey: string }] =>
|
||||
entry[1].defaultHotkey != null,
|
||||
)
|
||||
.map(([invocation, meta]) => ({
|
||||
invocation,
|
||||
meta,
|
||||
keys: parseHotkey(meta.defaultHotkey),
|
||||
}));
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
for (const binding of bindings) {
|
||||
if (matchesEvent(binding.keys, e)) {
|
||||
e.preventDefault();
|
||||
void rpc("execute_action", binding.invocation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => window.removeEventListener("keydown", onKeyDown);
|
||||
}
|
||||
17
apps/yaak-proxy/lib/rpc.ts
Normal file
17
apps/yaak-proxy/lib/rpc.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { RpcEventSchema, RpcSchema } from "@yaakapp-internal/proxy-lib";
|
||||
import { command, subscribe } from "./tauri";
|
||||
|
||||
export type Req<K extends keyof RpcSchema> = RpcSchema[K][0];
|
||||
export type Res<K extends keyof RpcSchema> = RpcSchema[K][1];
|
||||
|
||||
export async function rpc<K extends keyof RpcSchema>(cmd: K, payload: Req<K>): Promise<Res<K>> {
|
||||
return command<Res<K>>("rpc", { cmd, payload });
|
||||
}
|
||||
|
||||
/** Subscribe to a backend event. Returns an unsubscribe function. */
|
||||
export function listen<K extends keyof RpcEventSchema>(
|
||||
event: K & string,
|
||||
callback: (payload: RpcEventSchema[K]) => void,
|
||||
): () => void {
|
||||
return subscribe<RpcEventSchema[K]>(event, callback);
|
||||
}
|
||||
11
apps/yaak-proxy/lib/store.ts
Normal file
11
apps/yaak-proxy/lib/store.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createModelStore } from "@yaakapp-internal/model-store";
|
||||
import type { HttpExchange } from "@yaakapp-internal/proxy-lib";
|
||||
|
||||
type ProxyModels = {
|
||||
http_exchange: HttpExchange;
|
||||
};
|
||||
|
||||
export const { dataAtom, applyChange, replaceAll, listAtom, orderedListAtom } =
|
||||
createModelStore<ProxyModels>(["http_exchange"]);
|
||||
|
||||
export const httpExchangesAtom = orderedListAtom("http_exchange", "createdAt", "desc");
|
||||
30
apps/yaak-proxy/lib/tauri.ts
Normal file
30
apps/yaak-proxy/lib/tauri.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen as tauriListen } from "@tauri-apps/api/event";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { type as tauriOsType } from "@tauri-apps/plugin-os";
|
||||
|
||||
/** Call a Tauri command. */
|
||||
export function command<T>(cmd: string, args?: Record<string, unknown>): Promise<T> {
|
||||
return invoke(cmd, args) as Promise<T>;
|
||||
}
|
||||
|
||||
/** Subscribe to a Tauri event. Returns an unsubscribe function. */
|
||||
export function subscribe<T>(event: string, callback: (payload: T) => void): () => void {
|
||||
let unsub: (() => void) | null = null;
|
||||
tauriListen<T>(event, (e) => callback(e.payload))
|
||||
.then((fn) => {
|
||||
unsub = fn;
|
||||
})
|
||||
.catch(console.error);
|
||||
return () => unsub?.();
|
||||
}
|
||||
|
||||
/** Show the current webview window. */
|
||||
export function showWindow(): Promise<void> {
|
||||
return getCurrentWebviewWindow().show();
|
||||
}
|
||||
|
||||
/** Get the current OS type (e.g. "macos", "linux", "windows"). */
|
||||
export function getOsType() {
|
||||
return tauriOsType();
|
||||
}
|
||||
35
apps/yaak-proxy/lib/theme.ts
Normal file
35
apps/yaak-proxy/lib/theme.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { setWindowTheme } from "@yaakapp-internal/mac-window";
|
||||
import {
|
||||
applyThemeToDocument,
|
||||
defaultDarkTheme,
|
||||
defaultLightTheme,
|
||||
getCSSAppearance,
|
||||
platformFromUserAgent,
|
||||
setPlatformOnDocument,
|
||||
subscribeToPreferredAppearance,
|
||||
type Appearance,
|
||||
} from "@yaakapp-internal/theme";
|
||||
import { showWindow } from "./tauri";
|
||||
|
||||
setPlatformOnDocument(platformFromUserAgent(navigator.userAgent));
|
||||
|
||||
// Apply a quick initial theme based on CSS media query
|
||||
let preferredAppearance: Appearance = getCSSAppearance();
|
||||
applyTheme(preferredAppearance);
|
||||
|
||||
// Then subscribe to accurate OS appearance detection and changes
|
||||
subscribeToPreferredAppearance((a) => {
|
||||
preferredAppearance = a;
|
||||
applyTheme(preferredAppearance);
|
||||
});
|
||||
|
||||
// Show window after initial theme is applied (window starts hidden to prevent flash)
|
||||
showWindow().catch(console.error);
|
||||
|
||||
function applyTheme(appearance: Appearance) {
|
||||
const theme = appearance === "dark" ? defaultDarkTheme : defaultLightTheme;
|
||||
applyThemeToDocument(theme);
|
||||
if (theme.base.surface != null) {
|
||||
setWindowTheme(theme.base.surface);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user