105 lines
No EOL
3.5 KiB
TypeScript
105 lines
No EOL
3.5 KiB
TypeScript
import { EmbedBuilder } from "discord.js";
|
|
import { PollState, VoteEntry, Nation } from "@types";
|
|
import { Config } from "@systems/config";
|
|
import { PollLayout, PollRowContext, PollEmbedOptions } from "@ui/types";
|
|
import path from "path";
|
|
import fs from "fs";
|
|
import { Runtime } from "@systems/runtime";
|
|
|
|
// ─── Runtime ──────────────────────────────────────────────────────────────────
|
|
Runtime.phase("restore", () => restoreLayout(), { name: "PollUI.restoreLayout" });
|
|
|
|
// ─── Layout registry ──────────────────────────────────────────────────────────
|
|
|
|
const _layouts = new Map<string, PollLayout>();
|
|
let _activeLayout: PollLayout;
|
|
|
|
export function registerLayout(layout: PollLayout): void {
|
|
_layouts.set(layout.name, layout);
|
|
if (!_activeLayout) _activeLayout = layout; // first registered = default
|
|
}
|
|
|
|
function isPollLayout(obj: any): obj is PollLayout {
|
|
return obj?.name &&
|
|
obj?.description &&
|
|
typeof obj?.buildEmbed === "function" &&
|
|
typeof obj?.formatRow === "function" &&
|
|
typeof obj?.buildContext === "function";
|
|
}
|
|
|
|
export function discoverLayouts(): void {
|
|
const layoutsDir = path.join(__dirname, "layouts");
|
|
if (!fs.existsSync(layoutsDir)) return;
|
|
|
|
const files = fs.readdirSync(layoutsDir)
|
|
.filter((f) => f.endsWith(".ts") || f.endsWith(".js"))
|
|
.sort(); // consistent order — default.ts loads before side-by-side.ts
|
|
|
|
for (const file of files) {
|
|
try {
|
|
const mod = require(path.join(layoutsDir, file));
|
|
for (const exported of Object.values(mod)) {
|
|
if (isPollLayout(exported)) {
|
|
registerLayout(exported);
|
|
console.log(`[PollUI] Registered layout: ${(exported as PollLayout).name}`);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error(`[PollUI] Failed to load layout ${file}:`, err);
|
|
}
|
|
}
|
|
}
|
|
|
|
function restoreLayout() {
|
|
const savedLayout = Config.get({ section: "poll", key: "layout" });
|
|
|
|
if (savedLayout && _layouts.has(savedLayout)) {
|
|
_activeLayout = _layouts.get(savedLayout)!;
|
|
console.log(`[PollUI] Restored layout: ${savedLayout}`);
|
|
}
|
|
}
|
|
|
|
// Auto-discover at module load time
|
|
Config.load();
|
|
discoverLayouts();
|
|
restoreLayout();
|
|
|
|
// ─── Dispatcher ───────────────────────────────────────────────────────────────
|
|
|
|
function activeLayout(): PollLayout {
|
|
return _activeLayout;
|
|
}
|
|
|
|
export const PollUI = {
|
|
buildEmbed(state: PollState, options?: PollEmbedOptions): EmbedBuilder {
|
|
return activeLayout().buildEmbed(state, options);
|
|
},
|
|
|
|
formatRow(entry: VoteEntry, context: PollRowContext): string {
|
|
return activeLayout().formatRow(entry, context);
|
|
},
|
|
|
|
buildContext(
|
|
entries: VoteEntry[],
|
|
nation: Nation,
|
|
options?: { showNationEmoji?: boolean }
|
|
): PollRowContext {
|
|
return activeLayout().buildContext(entries, nation, options);
|
|
},
|
|
|
|
setLayout(name: string): boolean {
|
|
const layout = _layouts.get(name);
|
|
if (!layout) return false;
|
|
_activeLayout = layout;
|
|
return true;
|
|
},
|
|
|
|
layouts(): { name: string; description: string }[] {
|
|
return [..._layouts.values()].map((l) => ({
|
|
name: l.name,
|
|
description: l.description,
|
|
}));
|
|
},
|
|
|
|
register: registerLayout,
|
|
}; |