Files
SpineViewer/release/UNIVERSAL_SpineThumbnailGenerateServer/js/instance-coordinator.js
T

128 lines
3.4 KiB
JavaScript

// 多个 Eagle 插件窗口共用 localStorage,用它协调唯一的活动监听实例。
(function () {
'use strict';
const fs = require('fs');
const path = require('path');
const KEY = 'spine-thumbnail-active-instance';
const STALE_MS = 3500;
function createFileStorage(file) {
return {
file,
getItem(key) {
try {
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
return typeof data[key] === 'string' ? data[key] : null;
} catch (error) {
return null;
}
},
setItem(key, value) {
let data = {};
try {
const existing = JSON.parse(fs.readFileSync(file, 'utf8'));
if (existing && typeof existing === 'object') data = existing;
} catch (error) { /* first write or damaged file */ }
data[key] = String(value);
fs.mkdirSync(path.dirname(file), { recursive: true });
const temp = file + '.' + process.pid + '.' + Date.now() + '.' + Math.random().toString(16).slice(2) + '.tmp';
fs.writeFileSync(temp, JSON.stringify(data), 'utf8');
fs.renameSync(temp, file);
},
};
}
function readState(storage) {
try {
const state = JSON.parse(storage.getItem(KEY));
if (!state || typeof state.id !== 'string' || !Number.isFinite(state.updatedAt)) return null;
return state;
} catch (error) {
return null;
}
}
function writeState(storage, id, visible, now) {
const state = { id, visible, updatedAt: now };
try { storage.setItem(KEY, JSON.stringify(state)); } catch (error) { /* 无本地存储时仍可运行 */ }
return state;
}
function createCoordinator(options) {
const storage = options.storage;
const id = options.id;
const visible = options.isVisible || (() => true);
const hide = options.hide || (() => {});
const now = options.now || Date.now;
const staleMs = options.staleMs || STALE_MS;
const intervalMs = options.intervalMs || 1000;
let active = false;
let started = false;
let hiddenForDuplicate = false;
let timer = null;
function claim() {
writeState(storage, id, visible(), now());
hiddenForDuplicate = false;
if (!active) {
active = true;
if (options.onActiveChanged) options.onActiveChanged(true);
}
}
function evaluate() {
const state = readState(storage);
const currentVisible = visible();
const canClaim = !state ||
state.id === id ||
now() - state.updatedAt > staleMs;
const nextActive = canClaim;
if (nextActive) {
claim();
} else if (currentVisible && !hiddenForDuplicate) {
hiddenForDuplicate = true;
try { hide(); } catch (error) { /* 隐藏失败不影响主窗口 */ }
}
if (nextActive !== active) {
active = nextActive;
if (options.onActiveChanged) options.onActiveChanged(active);
}
return active;
}
function handleVisibility() {
if (visible()) claim();
else evaluate();
}
function start() {
if (started) return active;
started = true;
const state = readState(storage);
const canStart = visible() ||
!state ||
state.id === id ||
now() - state.updatedAt > staleMs;
if (canStart) claim();
else evaluate();
timer = setInterval(evaluate, intervalMs);
return active;
}
function stop() {
if (timer) clearInterval(timer);
timer = null;
started = false;
}
return { start, stop, evaluate, handleVisibility, isActive: () => active };
}
const api = { createCoordinator, createFileStorage, KEY, STALE_MS };
if (typeof window !== 'undefined') window.SpineThumbInstanceCoordinator = api;
else if (typeof module !== 'undefined' && module.exports) module.exports = api;
})();