import type { FieldDefinition } from '../domain/types';
|
import type { OnlyOfficeBridge } from '../onlyoffice/bridge';
|
|
import { useAnnotationStore } from '../stores/annotation';
|
import { usePluginContextStore } from '../stores/plugin-context';
|
|
const SDK_READY_TIMEOUT_MS = 5_000;
|
|
function errorMessage(error: unknown): string {
|
return error instanceof Error ? error.message : String(error);
|
}
|
|
export function useTemplateAnnotator(bridge: OnlyOfficeBridge) {
|
const store = useAnnotationStore();
|
const contextStore = usePluginContextStore();
|
let disposed = false;
|
let inFlight = false;
|
let readyTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
function clearReadyTimeout(): void {
|
if (readyTimeout === undefined) return;
|
clearTimeout(readyTimeout);
|
readyTimeout = undefined;
|
}
|
|
async function runExclusive<T>(operation: () => Promise<T>): Promise<T> {
|
if (inFlight) {
|
throw new Error('已有操作正在执行');
|
}
|
|
inFlight = true;
|
try {
|
return await operation();
|
} finally {
|
inFlight = false;
|
}
|
}
|
|
function setWorking(status: 'initializing' | 'working', message: string): void {
|
if (disposed) return;
|
store.setStatus(status);
|
store.setOperation({ kind: 'working', message });
|
}
|
|
function initialize(): Promise<void> {
|
clearReadyTimeout();
|
return runExclusive(async () => {
|
setWorking('initializing', '正在读取文档字段...');
|
try {
|
await bridge.disableTrackRevisions();
|
const controls = await bridge.listControls();
|
if (disposed) return;
|
store.setControls(controls);
|
store.setStatus('ready');
|
store.setOperation({ kind: 'success', message: '文档字段已刷新' });
|
} catch (error: unknown) {
|
if (!disposed) {
|
store.setStatus('error');
|
store.setOperation({ kind: 'error', message: `读取内容控件失败:${errorMessage(error)}` });
|
}
|
throw error;
|
}
|
});
|
}
|
|
function refresh(): Promise<void> {
|
return runExclusive(async () => {
|
const wasFatal = store.status === 'error';
|
setWorking('working', '正在读取文档字段...');
|
try {
|
const controls = await bridge.listControls();
|
if (disposed) return;
|
store.setControls(controls);
|
store.setStatus('ready');
|
store.setOperation({ kind: 'success', message: '文档字段已刷新' });
|
} catch (error: unknown) {
|
if (!disposed) {
|
store.setStatus(wasFatal ? 'error' : 'ready');
|
store.setOperation({ kind: 'error', message: `读取内容控件失败:${errorMessage(error)}` });
|
}
|
throw error;
|
}
|
});
|
}
|
|
function insert(definition: FieldDefinition): Promise<void> {
|
return runExclusive(async () => {
|
setWorking('working', '正在标注当前位置...');
|
try {
|
const controls = await bridge.insertField(definition);
|
if (disposed) return;
|
store.setControls(controls);
|
store.setStatus('ready');
|
store.setOperation({ kind: 'success', message: `已创建字段:${definition.alias}` });
|
} catch (error: unknown) {
|
if (!disposed) {
|
store.setStatus('ready');
|
store.setOperation({ kind: 'error', message: `标注失败:${errorMessage(error)}` });
|
}
|
throw error;
|
}
|
});
|
}
|
|
function deleteControl(internalId: string): Promise<void> {
|
return runExclusive(async () => {
|
setWorking('working', '正在解除标注...');
|
try {
|
const controls = await bridge.deleteControl(internalId);
|
if (disposed) return;
|
store.setControls(controls);
|
store.setStatus('ready');
|
store.setOperation({ kind: 'success', message: '标注已解除' });
|
} catch (error: unknown) {
|
if (!disposed) {
|
store.setStatus('ready');
|
store.setOperation({ kind: 'error', message: `解除标注失败:${errorMessage(error)}` });
|
}
|
throw error;
|
}
|
});
|
}
|
|
function select(internalId: string): Promise<void> {
|
return runExclusive(async () => {
|
try {
|
await bridge.selectControl(internalId);
|
} catch (error: unknown) {
|
try {
|
const controls = await bridge.listControls();
|
if (!disposed) store.setControls(controls);
|
} catch {
|
// 定位错误是本次操作的主错误,补偿刷新不能替换它。
|
}
|
if (!disposed) {
|
store.setStatus('ready');
|
store.setOperation({ kind: 'error', message: `定位失败:${errorMessage(error)}` });
|
}
|
throw error;
|
}
|
});
|
}
|
|
const cleanup = bridge.bindLifecycle({
|
onReady: () => {
|
if (disposed) return;
|
if (!contextStore.initialize(bridge.getRuntimeOptions())) {
|
store.setStatus('error');
|
store.setOperation({ kind: 'error', message: contextStore.error });
|
return;
|
}
|
void initialize().catch(() => undefined);
|
},
|
onThemeChanged: () => undefined,
|
});
|
readyTimeout = setTimeout(() => {
|
readyTimeout = undefined;
|
if (disposed || store.status !== 'initializing') return;
|
store.setStatus('error');
|
store.setOperation({ kind: 'error', message: 'ONLYOFFICE SDK 初始化超时' });
|
}, SDK_READY_TIMEOUT_MS);
|
|
function dispose(): void {
|
if (disposed) return;
|
disposed = true;
|
clearReadyTimeout();
|
cleanup();
|
contextStore.reset();
|
}
|
|
return { contextStore, store, initialize, refresh, insert, deleteControl, select, dispose };
|
}
|