mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 06:22:45 -08:00
feat(core): add module scaffolding and provider registries
This commit is contained in:
21
src/core/action-bus.ts
Normal file
21
src/core/action-bus.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export type ActionWithType = { type: string };
|
||||
|
||||
export type ActionHandler<TAction extends ActionWithType> = (
|
||||
action: TAction,
|
||||
) => void | Promise<void>;
|
||||
|
||||
export class ActionBus<TAction extends ActionWithType> {
|
||||
private handlers = new Map<string, ActionHandler<TAction>>();
|
||||
|
||||
register(type: TAction["type"], handler: ActionHandler<TAction>): void {
|
||||
this.handlers.set(type, handler);
|
||||
}
|
||||
|
||||
async dispatch(action: TAction): Promise<void> {
|
||||
const handler = this.handlers.get(action.type);
|
||||
if (!handler) {
|
||||
throw new Error(`No handler registered for action: ${action.type}`);
|
||||
}
|
||||
await handler(action);
|
||||
}
|
||||
}
|
||||
16
src/core/actions.ts
Normal file
16
src/core/actions.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export type AppAction =
|
||||
| { type: "overlay.toggleVisible" }
|
||||
| { type: "overlay.toggleInvisible" }
|
||||
| { type: "overlay.setVisible"; visible: boolean }
|
||||
| { type: "overlay.setInvisibleVisible"; visible: boolean }
|
||||
| { type: "overlay.openSettings" }
|
||||
| { type: "subtitle.copyCurrent" }
|
||||
| { type: "subtitle.copyMultiplePrompt"; timeoutMs: number }
|
||||
| { type: "anki.mineSentence" }
|
||||
| { type: "anki.mineSentenceMultiplePrompt"; timeoutMs: number }
|
||||
| { type: "anki.updateLastCardFromClipboard" }
|
||||
| { type: "anki.markAudioCard" }
|
||||
| { type: "kiku.triggerFieldGrouping" }
|
||||
| { type: "subsync.triggerFromConfig" }
|
||||
| { type: "secondarySub.toggleMode" }
|
||||
| { type: "runtimeOptions.openPalette" };
|
||||
45
src/core/app-context.ts
Normal file
45
src/core/app-context.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
AnkiConnectConfig,
|
||||
JimakuApiResponse,
|
||||
JimakuDownloadQuery,
|
||||
JimakuDownloadResult,
|
||||
JimakuEntry,
|
||||
JimakuFileEntry,
|
||||
JimakuFilesQuery,
|
||||
JimakuMediaInfo,
|
||||
JimakuSearchQuery,
|
||||
RuntimeOptionState,
|
||||
SubsyncManualRunRequest,
|
||||
SubsyncMode,
|
||||
SubsyncResult,
|
||||
} from "../types";
|
||||
|
||||
export interface RuntimeOptionsModuleContext {
|
||||
getAnkiConfig: () => AnkiConnectConfig;
|
||||
applyAnkiPatch: (patch: Partial<AnkiConnectConfig>) => void;
|
||||
onOptionsChanged: (options: RuntimeOptionState[]) => void;
|
||||
}
|
||||
|
||||
export interface AppContext {
|
||||
runtimeOptions?: RuntimeOptionsModuleContext;
|
||||
jimaku?: {
|
||||
getMediaInfo: () => JimakuMediaInfo;
|
||||
searchEntries: (
|
||||
query: JimakuSearchQuery,
|
||||
) => Promise<JimakuApiResponse<JimakuEntry[]>>;
|
||||
listFiles: (
|
||||
query: JimakuFilesQuery,
|
||||
) => Promise<JimakuApiResponse<JimakuFileEntry[]>>;
|
||||
downloadFile: (
|
||||
query: JimakuDownloadQuery,
|
||||
) => Promise<JimakuDownloadResult>;
|
||||
};
|
||||
subsync?: {
|
||||
getDefaultMode: () => SubsyncMode;
|
||||
openManualPicker: () => Promise<void>;
|
||||
runAuto: () => Promise<SubsyncResult>;
|
||||
runManual: (request: SubsyncManualRunRequest) => Promise<SubsyncResult>;
|
||||
showOsd: (message: string) => void;
|
||||
runWithSpinner: <T>(task: () => Promise<T>, label?: string) => Promise<T>;
|
||||
};
|
||||
}
|
||||
36
src/core/module-registry.ts
Normal file
36
src/core/module-registry.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { SubminerModule } from "./module";
|
||||
|
||||
export class ModuleRegistry<TContext = unknown> {
|
||||
private readonly modules: SubminerModule<TContext>[] = [];
|
||||
|
||||
register(module: SubminerModule<TContext>): void {
|
||||
if (this.modules.some((existing) => existing.id === module.id)) {
|
||||
throw new Error(`Module already registered: ${module.id}`);
|
||||
}
|
||||
this.modules.push(module);
|
||||
}
|
||||
|
||||
async initAll(context: TContext): Promise<void> {
|
||||
for (const module of this.modules) {
|
||||
if (module.init) {
|
||||
await module.init(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async startAll(): Promise<void> {
|
||||
for (const module of this.modules) {
|
||||
if (module.start) {
|
||||
await module.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async stopAll(): Promise<void> {
|
||||
for (const module of [...this.modules].reverse()) {
|
||||
if (module.stop) {
|
||||
await module.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/core/module.ts
Normal file
6
src/core/module.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface SubminerModule<TContext = unknown> {
|
||||
id: string;
|
||||
init?: (context: TContext) => void | Promise<void>;
|
||||
start?: () => void | Promise<void>;
|
||||
stop?: () => void | Promise<void>;
|
||||
}
|
||||
Reference in New Issue
Block a user