Files
TurboSu/static/js/pages/scene.js
T
2026-07-25 15:23:40 +08:00

188 lines
7.2 KiB
JavaScript

const PageScene = {
_el: null,
_currentGameId: null,
_currentGameName: '',
_editorSceneId: null,
_editorData: null,
init() {
this._el = document.getElementById('page-scene');
},
async render() {
const cfg = await API.getConfig();
this._currentGameId = cfg.selected_game_id;
if (this._currentGameId) {
const games = await API.getGames();
const game = games.find(g => g.id === this._currentGameId);
this._currentGameName = game ? game.name : this._currentGameId;
}
this._el.innerHTML = this._template();
await this._loadScenes();
this._bindEvents();
},
async _loadScenes() {
const grid = document.getElementById('scene-grid');
if (!grid) return;
try {
const scenes = await API.getScenes(this._currentGameId);
if (!scenes || scenes.length === 0) {
grid.innerHTML = `
<div class="empty-state" style="grid-column:1/-1;">
<svg viewBox="0 0 24 24" width="64" height="64"><path d="M3 3h8v8H3zm10 0h8v8h-8zM3 13h8v8H3zm10 0h8v8h-8z" fill="currentColor"/></svg>
<h4>暂无场景</h4>
<p>点击"新建场景"创建你的第一个场景布局</p>
</div>`;
return;
}
grid.innerHTML = scenes.map(s => this._sceneCardHtml(s)).join('');
} catch (e) {
console.error('Failed to load scenes:', e);
}
},
_bindEvents() {
document.getElementById('btn-new-scene')?.addEventListener('click', () => this._showEditor());
const grid = document.getElementById('scene-grid');
if (grid) {
grid.addEventListener('click', (e) => {
const card = e.target.closest('[data-scene-id]');
if (!card) return;
const action = card.dataset.action;
const sceneId = card.dataset.sceneId;
if (action === 'render') {
this._renderScene(sceneId);
} else if (action === 'edit') {
e.stopPropagation();
this._showEditor(sceneId);
} else if (action === 'delete') {
e.stopPropagation();
this._deleteScene(sceneId);
}
});
}
},
async _showEditor(sceneId) {
let scene = null;
if (sceneId) {
scene = await API.getScene(sceneId);
if (!scene) return;
}
this._editorSceneId = sceneId;
const modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.innerHTML = this._editorTemplate(scene);
document.body.appendChild(modal);
modal.querySelector('.modal-overlay, .modal-actions .btn-secondary')
?.addEventListener('click', (e) => {
if (e.target === modal || e.target.matches('.btn-secondary')) {
modal.remove();
}
});
modal.querySelector('.btn-primary')?.addEventListener('click', async () => {
const name = modal.querySelector('#edit-scene-name').value || 'New Scene';
const desc = modal.querySelector('#edit-scene-desc').value || '';
const gameId = this._currentGameId || '';
const data = { name, description: desc, game_id: gameId };
if (this._editorSceneId) {
await API.updateScene(this._editorSceneId, data);
} else {
await API.createScene(data);
}
modal.remove();
Toast.show('场景保存成功', 'success');
this.render();
});
},
async _renderScene(sceneId) {
const url = `${location.origin}/scene/${sceneId}`;
window.open(url, '_blank');
navigator.clipboard.writeText(url).then(() => {
Toast.show('场景链接已复制,可在局域网设备访问', 'success');
}).catch(() => {});
},
async _deleteScene(sceneId) {
if (!confirm('确定删除此场景?')) return;
await API.deleteScene(sceneId);
Toast.show('场景已删除', 'info');
this._loadScenes();
},
_sceneCardHtml(scene) {
const canvasCount = (scene.canvases || []).length;
return `
<div class="glass-card scene-card" data-scene-id="${scene.id}" data-action="render">
<div class="scene-card-name">${scene.name}</div>
<div class="scene-card-game" style="margin-bottom:6px;">${this._currentGameName || '未关联游戏'}</div>
<div class="scene-card-meta">
<span>${canvasCount} 个画布</span>
<span>${scene.updated_at ? new Date(scene.updated_at).toLocaleDateString() : ''}</span>
</div>
<div class="scene-canvas-list">
${(scene.canvases || []).map(c => `<span class="scene-canvas-badge">${c.label || c.width + 'x' + c.height}</span>`).join('')}
</div>
<div style="margin-top:12px;display:flex;gap:6px;">
<button class="btn btn-sm btn-primary" data-scene-id="${scene.id}" data-action="render">渲染</button>
<button class="btn btn-sm btn-secondary" data-scene-id="${scene.id}" data-action="edit">编辑</button>
<button class="btn btn-sm btn-danger" data-scene-id="${scene.id}" data-action="delete">删除</button>
</div>
</div>`;
},
_editorTemplate(scene) {
const name = scene ? scene.name : '';
const desc = scene ? scene.description : '';
return `
<div class="modal">
<h3>${scene ? '编辑场景' : '新建场景'}</h3>
<div class="form-group">
<label>场景名称</label>
<input id="edit-scene-name" type="text" value="${name}" placeholder="输入场景名称">
</div>
<div class="form-group">
<label>描述</label>
<textarea id="edit-scene-desc" placeholder="场景描述(可选)">${desc}</textarea>
</div>
<p style="font-size:12px;color:var(--text-tertiary);margin-top:8px;">
提示:保存后可在场景编辑器中添加仪表盘、调整布局和画布比例。
</p>
<div class="modal-actions">
<button class="btn btn-secondary">取消</button>
<button class="btn btn-primary">保存</button>
</div>
</div>`;
},
_template() {
return `
<div class="scene-header animated">
<div>
<h2>场景管理</h2>
<p style="color:var(--text-secondary);font-size:14px;margin-top:4px;">
${this._currentGameId ? `当前游戏: ${this._currentGameName}` : '请先在侧边栏选择一个游戏'}
</p>
</div>
<button id="btn-new-scene" class="btn btn-primary" ${!this._currentGameId ? 'disabled' : ''}>
+ 新建场景
</button>
</div>
<div id="scene-grid" class="scene-grid animated"></div>`;
}
};
window.PageScene = PageScene;