Files
SpineViewer/SpineThumbnailGenerateServer/index.html
T
2026-09-20 23:29:13 +08:00

134 lines
7.2 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>SpineThumbnailGenerateServer</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 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>Spine缩略图</h1>
<div class="notice">
导入 Spine 资产(<code>.skel</code> / <code>.json</code> + <code>.atlas</code> + 贴图)后,
本插件会为还没设置缩略图的素材生成一张骨骼预览图。
<b>它只处理本次新导入的素材,不扫描、不修改历史素材,也不会覆盖你已经设置过的自定义缩略图。</b>
生成方式可切换:<b>动态</b>取骨骼里的第一个动画压成 WebP(200×200,体积按动画时长自动定,
参数与主插件默认值一致),<b>静帧</b>始终用 setup pose。
自动生成默认关闭,需要你在下面手动启用。
</div>
<div class="toolbar">
<div class="toolbar-group">
<label class="toggle-row"><input type="checkbox" id="toggle"><span>导入时自动生成缩略图</span></label>
<button id="clear" type="button">清空日志</button>
</div>
<div class="toolbar-group">
<label class="mode-row"><span>生成方式</span>
<select id="mode">
<option value="animated">动态(第一个动画)</option>
<option value="static">静帧(setup pose)</option>
</select>
</label>
<label class="mode-row"><span>生成策略</span>
<select id="strategy">
<option value="size">最优体积(慢)</option>
<option value="speed">最优速度(快)</option>
</select>
</label>
</div>
</div>
<div class="row"><span>状态</span><b id="status">启动中…</b></div>
<div class="row"><span>素材库</span><b id="library">—</b></div>
<div class="row"><span>统计</span><b id="stats">—</b></div>
<div class="filters" id="filters">
<button type="button" data-filter="all" class="active">全部</button>
<button type="button" data-filter="ok">成功</button>
<button type="button" data-filter="fail">失败</button>
<button type="button" data-filter="warn">警告</button>
<button type="button" data-filter="error">错误</button>
</div>
<ul id="log"></ul>
<script>
// 诊断用:脚本一旦抛错,直接把原因写在状态栏,避免停在「启动中…」看不出问题。
window.addEventListener('error', (event) => {
const el = document.getElementById('status');
if (el) el.textContent = '脚本错误:' + (event.message || event.error || '未知错误');
});
window.addEventListener('unhandledrejection', (event) => {
const el = document.getElementById('status');
if (el) el.textContent = '未处理的 Promise 错误:' + (event.reason && event.reason.message || event.reason);
});
// service 页面里的 require 不以插件目录为基准,本地资源要用绝对路径,这里先把根目录算出来。
(function () {
try {
// Windows 上 pathname 可能是 /C:/... 也可能是 \C:\...,统一成正斜杠并去掉前导分隔符。
const pathname = decodeURIComponent(new URL('.', location.href).pathname);
window.SpinePluginRoot = pathname.replace(/\\/g, '/').replace(/^\/+/, '');
} catch (error) {
window.SpinePluginRoot = '';
}
})();
</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>