128 lines
3.4 KiB
JavaScript
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;
|
|
|
|
})();
|