mirror of
https://github.com/flash555588/ai-model-workbench.git
synced 2026-07-22 06:56:38 +00:00
fix: localize loading states
This commit is contained in:
parent
cc9677b9cf
commit
8bfa94475e
10 changed files with 49 additions and 14 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "ai-3d-model-workbench",
|
||||
"name": "AI 3D Model Workbench",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"minAppVersion": "1.5.0",
|
||||
"description": "Render 3D models in Obsidian and connect them to linked knowledge notes.",
|
||||
"author": "flash",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-ai-3d-model-workbench",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"private": true,
|
||||
"description": "Obsidian plugin for viewing 3D assets, annotating them, and linking them to knowledge notes.",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -176,6 +176,8 @@ export const en = {
|
|||
"codeBlock.presetRequiresModels": "Preset \"{preset}\" requires {min}-{max} models, got {count}.",
|
||||
"codeBlock.gridFailed": "Grid failed: {reason}",
|
||||
"loading.default": "Loading...",
|
||||
"loading.preparingModel": "Preparing model...",
|
||||
"loading.loadingModel": "Loading model...",
|
||||
} as const;
|
||||
|
||||
export type TranslationKey = keyof typeof en;
|
||||
|
|
|
|||
|
|
@ -11,9 +11,13 @@ const dictionaries: Record<Locale, Record<TranslationKey, string>> = {
|
|||
};
|
||||
|
||||
let currentLocale: Locale = "en";
|
||||
const localeListeners = new Set<() => void>();
|
||||
|
||||
export function setLocale(locale: Locale): void {
|
||||
currentLocale = locale;
|
||||
for (const listener of localeListeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
export function getLocale(): Locale {
|
||||
|
|
@ -24,6 +28,13 @@ export function t(key: TranslationKey): string {
|
|||
return dictionaries[currentLocale]?.[key] ?? en[key] ?? key;
|
||||
}
|
||||
|
||||
export function onLocaleChange(listener: () => void): () => void {
|
||||
localeListeners.add(listener);
|
||||
return () => {
|
||||
localeListeners.delete(listener);
|
||||
};
|
||||
}
|
||||
|
||||
export function interpolate(template: string, values: Record<string, string>): string {
|
||||
return template.replace(/\{(\w+)\}/g, (_, key: string) => values[key] ?? "");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,4 +178,6 @@ export const zhCN: Record<TranslationKey, string> = {
|
|||
"codeBlock.presetRequiresModels": "预设 “{preset}” 需要 {min}-{max} 个模型,当前为 {count} 个。",
|
||||
"codeBlock.gridFailed": "网格渲染失败:{reason}",
|
||||
"loading.default": "加载中...",
|
||||
"loading.preparingModel": "正在准备模型...",
|
||||
"loading.loadingModel": "正在加载模型...",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ export class DirectModelView extends FileView {
|
|||
const settings = this.getSettings();
|
||||
const conversionManager = createConversionManager(settings);
|
||||
const absolutePath = resolveVaultAbsolutePath(this.app, file.path) ?? undefined;
|
||||
loading.setPhase("Preparing model...");
|
||||
loading.setPhaseKey("loading.preparingModel");
|
||||
const prepared = await prepareModelInput({
|
||||
path: file.path,
|
||||
absolutePath,
|
||||
|
|
@ -154,7 +154,7 @@ export class DirectModelView extends FileView {
|
|||
const source = toPreviewSource(prepared);
|
||||
|
||||
this.preview = new BabylonModelPreview(canvas);
|
||||
loading.setPhase("Loading model...");
|
||||
loading.setPhaseKey("loading.loadingModel");
|
||||
const data = await readBinaryPath(this.app, source.path);
|
||||
if (gen !== this.loadGeneration) { this.preview.destroy(); this.preview = null; return; }
|
||||
console.debug(`[AI3D] DirectView loading: ${file.path} via ${source.path} (${source.ext}, ${data.byteLength} bytes)`);
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ export function registerCodeBlockProcessor(
|
|||
try {
|
||||
const absolutePath = resolveVaultAbsolutePath(app, modelPath) ?? undefined;
|
||||
const conversionManager = createConversionManager(settings);
|
||||
loading.setPhase("Preparing model...");
|
||||
loading.setPhaseKey("loading.preparingModel");
|
||||
const prepared = await prepareModelInput({
|
||||
path: modelPath,
|
||||
absolutePath,
|
||||
|
|
@ -256,7 +256,7 @@ export function registerCodeBlockProcessor(
|
|||
});
|
||||
const source = toPreviewSource(prepared);
|
||||
preview = new BabylonModelPreview(canvas);
|
||||
loading.setPhase("Loading model...");
|
||||
loading.setPhaseKey("loading.loadingModel");
|
||||
const data = await readBinaryPath(app, source.path);
|
||||
const readFile = async (p: string) => readBinaryPath(app, p);
|
||||
|
||||
|
|
@ -500,7 +500,7 @@ export function registerGridCodeBlockProcessor(
|
|||
async function loadGrid() {
|
||||
if (loaded || destroyed) return;
|
||||
loaded = true;
|
||||
gridLoading.setPhase(t("codeBlock.renderingGrid"));
|
||||
gridLoading.setPhaseKey("codeBlock.renderingGrid");
|
||||
gridLoading.setProgress(-1);
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ class ModelEmbedWidget extends WidgetType {
|
|||
fbx2gltfCommand: this.fbx2gltfCommand,
|
||||
freecadcmdCommand: this.freecadcmdCommand,
|
||||
});
|
||||
loading.setPhase("Preparing model...");
|
||||
loading.setPhaseKey("loading.preparingModel");
|
||||
const prepared = await prepareModelInput({
|
||||
path: this.modelPath,
|
||||
absolutePath,
|
||||
|
|
@ -120,7 +120,7 @@ class ModelEmbedWidget extends WidgetType {
|
|||
conversionManager,
|
||||
convertedAssetCache: this.convertedAssetCache,
|
||||
});
|
||||
loading.setPhase("Loading model...");
|
||||
loading.setPhaseKey("loading.loadingModel");
|
||||
const data = await readBinaryPath(this.app, prepared.effectivePath);
|
||||
await this.preview.loadModel(
|
||||
data,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { t } from "../../i18n";
|
||||
import { onLocaleChange, t, type TranslationKey } from "../../i18n";
|
||||
|
||||
/**
|
||||
* Reusable loading overlay for 3D model preview hosts.
|
||||
|
|
@ -11,6 +11,8 @@ import { t } from "../../i18n";
|
|||
export interface LoadingOverlay {
|
||||
/** Update the phase description (e.g. "Converting CAD...", "Loading model..."). */
|
||||
setPhase(text: string): void;
|
||||
/** Update the phase description from a translation key. */
|
||||
setPhaseKey(key: TranslationKey): void;
|
||||
/** Set determinate progress (0–100). Pass -1 for indeterminate mode. */
|
||||
setProgress(percent: number): void;
|
||||
/** Fade out and remove the overlay from DOM. Safe to call multiple times. */
|
||||
|
|
@ -25,18 +27,35 @@ export function createLoadingOverlay(host: HTMLElement): LoadingOverlay {
|
|||
overlay.createDiv({ cls: "ai3d-loading-spinner" });
|
||||
|
||||
const text = overlay.createDiv({ cls: "ai3d-loading-text" });
|
||||
text.textContent = t("loading.default");
|
||||
let currentPhaseKey: TranslationKey | null = "loading.default";
|
||||
let currentPhaseText = "";
|
||||
const renderPhase = () => {
|
||||
text.textContent = currentPhaseKey ? t(currentPhaseKey) : currentPhaseText;
|
||||
};
|
||||
renderPhase();
|
||||
|
||||
const track = overlay.createDiv({ cls: "ai3d-loading-bar-track" });
|
||||
const fill = track.createDiv({ cls: "ai3d-loading-bar-fill is-indeterminate" });
|
||||
|
||||
let hidden = false;
|
||||
const stopListening = onLocaleChange(() => {
|
||||
if (!hidden && currentPhaseKey) {
|
||||
renderPhase();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
el: overlay,
|
||||
|
||||
setPhase(t: string) {
|
||||
if (!hidden) text.textContent = t;
|
||||
setPhase(phaseText: string) {
|
||||
currentPhaseKey = null;
|
||||
currentPhaseText = phaseText;
|
||||
if (!hidden) renderPhase();
|
||||
},
|
||||
|
||||
setPhaseKey(phaseKey: TranslationKey) {
|
||||
currentPhaseKey = phaseKey;
|
||||
if (!hidden) renderPhase();
|
||||
},
|
||||
|
||||
setProgress(pct: number) {
|
||||
|
|
@ -53,6 +72,7 @@ export function createLoadingOverlay(host: HTMLElement): LoadingOverlay {
|
|||
hide() {
|
||||
if (hidden) return;
|
||||
hidden = true;
|
||||
stopListening();
|
||||
overlay.classList.add("is-hiding");
|
||||
activeWindow.setTimeout(() => overlay.remove(), 300);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"0.0.1":"1.5.0","0.0.2":"1.5.0","0.0.3":"1.5.0","0.0.4":"1.5.0","0.0.5-beta.0":"1.5.0","0.1.1":"1.5.0"}
|
||||
{"0.0.1":"1.5.0","0.0.2":"1.5.0","0.0.3":"1.5.0","0.0.4":"1.5.0","0.0.5-beta.0":"1.5.0","0.1.1":"1.5.0","0.1.2":"1.5.0"}
|
||||
|
|
|
|||
Loading…
Reference in a new issue