Files
SpineViewer/SpineThumbnailGenerateServer/index.html
T
2026-10-29 11:02:01 +08:00

142 lines
8.0 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title data-i18n="thumb.title">Spine缩略图</title>
<style>
:root { --bg: #1e1e1e; --panel: #252526; --text: #e6e6e6; --dim: #9d9d9d; --accent: #4fc1ff; --border: #3c3c3c; --warn: #e3b341; }
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif; font-size: 13px; }
body { display: flex; flex-direction: column; gap: 9px; padding: 14px; box-sizing: border-box; }
h1 { font-size: 15px; margin: 0; font-weight: 600; text-align: center; }
.notice { color: var(--dim); line-height: 1.6; background: var(--panel);
border: 1px solid var(--border); border-radius: 6px; padding: 9px 11px; }
.notice p { margin: 0 0 6px; }
.notice p:last-child { margin-bottom: 0; }
.notice b { color: var(--text); font-weight: 500; }
.notice code { color: var(--accent); font-family: ui-monospace, Consolas, monospace; }
.toggle-row { display: flex; align-items: center; gap: 8px; cursor: pointer; white-space: nowrap;
flex: 1 1 240px;
background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 9px 11px; }
.toggle-row input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }
/* 窗口窄的时候整组换行,而不是把标签压成一列一个字符。 */
.toolbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; }
.toolbar-group { display: flex; flex-wrap: wrap; gap: 8px; flex: 0 0 auto; }
#clear { background: var(--panel); color: var(--text); border: 1px solid var(--border);
border-radius: 6px; padding: 9px 12px; font: inherit; cursor: pointer; white-space: nowrap; }
#clear:hover { border-color: var(--accent); color: var(--accent); }
.mode-row { display: flex; align-items: center; gap: 8px; color: var(--dim); white-space: nowrap;
flex: 0 1 auto;
background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 9px 11px; }
.mode-row select { background: var(--panel); color: var(--text); border: 1px solid var(--border);
border-radius: 6px; padding: 3px 6px; font: inherit; max-width: 200px; }
.row { display: flex; gap: 8px; align-items: baseline; color: var(--dim); }
.row b { color: var(--text); font-weight: 500; word-break: break-all; }
#stats { color: var(--accent); }
#log { flex: 1; overflow: auto; margin: 0; padding: 10px; list-style: none; min-height: 140px;
background: var(--panel); border: 1px solid var(--border); border-radius: 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.55; }
#log li { white-space: pre-wrap; word-break: break-all; }
#log li.dim { color: var(--dim); }
#log li.ok { color: var(--accent); }
#log li.fail { color: #e06c75; }
#log li.warn { color: var(--warn); }
#log li.error { color: #ff5f56; }
/* 过滤直接用 CSS:给 #log 一个 data-filter,非该类别的行就不显示,不用遍历 DOM。 */
#log[data-filter="ok"] li:not(.ok),
#log[data-filter="fail"] li:not(.fail),
#log[data-filter="warn"] li:not(.warn),
#log[data-filter="error"] li:not(.error) { display: none; }
.filters { display: flex; gap: 6px; }
.filters button { background: var(--panel); color: var(--dim); border: 1px solid var(--border);
border-radius: 999px; padding: 3px 12px; font: inherit; font-size: 12px; cursor: pointer; }
.filters button.active { color: var(--accent); border-color: var(--accent); }
</style>
</head>
<body>
<h1 data-i18n="thumb.title">Spine缩略图</h1>
<div class="notice">
<p data-i18n="thumb.noticeIntro">导入 Spine 资产(.skel / .json + .atlas + 贴图)后,本插件会为还没设置缩略图的素材生成一张骨骼预览图。</p>
<p data-i18n="thumb.noticeSafety">它只处理本次新导入的素材,不扫描、不修改历史素材,也不会覆盖你已经设置过的自定义缩略图。</p>
<p data-i18n="thumb.noticeModes">动态取骨骼里的第一个动画压成 WebP;静帧始终使用 setup pose。</p>
<p data-i18n="thumb.noticeDefault">自动生成默认关闭,需要你在下面手动启用。</p>
</div>
<div class="toolbar">
<div class="toolbar-group">
<label class="toggle-row"><input type="checkbox" id="toggle"><span data-i18n="thumb.autoToggle">导入时自动生成缩略图</span></label>
<button id="clear" type="button" data-i18n="thumb.clear">清空日志</button>
</div>
<div class="toolbar-group">
<label class="mode-row"><span data-i18n="thumb.modeLabel">生成方式</span>
<select id="mode">
<option value="animated" data-i18n="thumb.modeAnimated">动态(第一个动画)</option>
<option value="static" data-i18n="thumb.modeStatic">静帧(setup pose)</option>
</select>
</label>
<label class="mode-row"><span data-i18n="thumb.strategyLabel">生成策略</span>
<select id="strategy">
<option value="size" data-i18n="thumb.strategySize">最优体积(慢)</option>
<option value="speed" data-i18n="thumb.strategySpeed">最优速度(快)</option>
</select>
</label>
</div>
</div>
<div class="row"><span data-i18n="thumb.statusLabel">状态</span><b id="status" data-i18n="thumb.statusStarting">启动中…</b></div>
<div class="row"><span data-i18n="thumb.libraryLabel">素材库</span><b id="library">—</b></div>
<div class="row"><span data-i18n="thumb.statsLabel">统计</span><b id="stats">—</b></div>
<div class="filters" id="filters">
<button type="button" data-filter="all" class="active" data-i18n="thumb.filterAll">全部</button>
<button type="button" data-filter="ok" data-i18n="thumb.filterOk">成功</button>
<button type="button" data-filter="fail" data-i18n="thumb.filterFail">失败</button>
<button type="button" data-filter="warn" data-i18n="thumb.filterWarn">警告</button>
<button type="button" data-filter="error" data-i18n="thumb.filterError">错误</button>
</div>
<ul id="log"></ul>
<script>
// service 页面里的 require 不以插件目录为基准,本地资源要用绝对路径,这里先把根目录算出来。
(function () {
try {
// Windows 上 pathname 形如 /C:/...,要去掉盘符前那个多余的斜杠;
// macOS/Linux 是 /Users/...,前导斜杠必须留着,否则会变成相对路径。
const pathname = decodeURIComponent(new URL('.', location.href).pathname).replace(/\\/g, '/');
window.SpinePluginRoot = pathname.replace(/^\/([A-Za-z]:)/, '$1');
} catch (error) {
window.SpinePluginRoot = '';
}
})();
</script>
<script src="js/i18n.js"></script>
<script>
// 诊断用:脚本一旦抛错,直接把原因写在状态栏,避免停在「启动中…」看不出问题。
window.addEventListener('error', (event) => {
const el = document.getElementById('status');
if (el) el.textContent = SpineThumbI18n.t('thumb.scriptError', {
error: event.message || event.error || SpineThumbI18n.t('thumb.unknownError'),
});
});
window.addEventListener('unhandledrejection', (event) => {
const el = document.getElementById('status');
if (el) el.textContent = SpineThumbI18n.t('thumb.promiseError', {
error: event.reason && event.reason.message || event.reason,
});
});
</script>
<!-- 顺序有依赖:rasterizer 先于 clipping,utils/badge 先于 renderer,plugin.js 最后。 -->
<script src="js/spine-webgl-rasterizer.js"></script>
<script src="js/spine-canvas-clipping.js"></script>
<script src="js/spine-background.js"></script>
<script src="js/spine-visible-bounds.js"></script>
<script src="js/animated-profiles.js"></script>
<script src="js/animated-webp-encoder.js"></script>
<script src="js/spine-utils.js"></script>
<script src="js/spine-legacy-binary.js"></script>
<script src="js/spine-badge.js"></script>
<script src="js/settings.js"></script>
<script src="js/progress-window.js"></script>
<script src="js/renderer.js"></script>
<script src="js/library-watcher.js"></script>
<script src="js/spine-assets.js"></script>
<script src="js/plugin.js"></script>
</body>
</html>