feat: TurboSu initial release - racing telemetry dashboard
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user