feat: TurboSu initial release - racing telemetry dashboard
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
const Sidebar = {
|
||||
_el: null,
|
||||
_gameListEl: null,
|
||||
_gameToggleEl: null,
|
||||
_gameLabelEl: null,
|
||||
|
||||
init() {
|
||||
this._el = document.getElementById('sidebar');
|
||||
this._gameListEl = document.getElementById('game-list');
|
||||
this._gameToggleEl = document.getElementById('game-selector-toggle');
|
||||
this._gameLabelEl = document.getElementById('game-selector-label');
|
||||
this._topbarLogo = document.getElementById('topbar-logo-title');
|
||||
|
||||
document.getElementById('sidebar-toggle').addEventListener('click', () => this.toggle());
|
||||
this._gameToggleEl.addEventListener('click', () => this._toggleGameList());
|
||||
|
||||
const saved = localStorage.getItem('turbosu-sidebar-collapsed');
|
||||
if (saved === 'true') this.collapse(true);
|
||||
|
||||
this._loadGames();
|
||||
},
|
||||
|
||||
toggle() {
|
||||
this._el.classList.toggle('collapsed');
|
||||
this._el.classList.toggle('expanded');
|
||||
const isCollapsed = this._el.classList.contains('collapsed');
|
||||
this._topbarLogo.classList.toggle('hidden', !isCollapsed);
|
||||
localStorage.setItem('turbosu-sidebar-collapsed', isCollapsed);
|
||||
},
|
||||
|
||||
collapse(silent) {
|
||||
this._el.classList.add('collapsed');
|
||||
this._el.classList.remove('expanded');
|
||||
this._topbarLogo.classList.remove('hidden');
|
||||
if (!silent) localStorage.setItem('turbosu-sidebar-collapsed', 'true');
|
||||
},
|
||||
|
||||
expand(silent) {
|
||||
this._el.classList.remove('collapsed');
|
||||
this._el.classList.add('expanded');
|
||||
this._topbarLogo.classList.add('hidden');
|
||||
if (!silent) localStorage.setItem('turbosu-sidebar-collapsed', 'false');
|
||||
},
|
||||
|
||||
async _loadGames() {
|
||||
const games = await API.getGames();
|
||||
this._gameListEl.innerHTML = '';
|
||||
|
||||
if (!Array.isArray(games) || games.length === 0) {
|
||||
this._gameListEl.innerHTML = '<div style="padding:8px 12px;color:var(--text-tertiary);font-size:12px;">暂无可用的游戏插件</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedId = (await API.getConfig()).selected_game_id;
|
||||
games.forEach(game => {
|
||||
const item = document.createElement('button');
|
||||
item.className = 'nav-item';
|
||||
if (selectedId === game.id) item.classList.add('active');
|
||||
item.innerHTML = `
|
||||
<span style="font-size:16px;flex-shrink:0;">◉</span>
|
||||
<span class="nav-label">${game.name}</span>
|
||||
`;
|
||||
item.addEventListener('click', () => this._selectGame(game.id));
|
||||
this._gameListEl.appendChild(item);
|
||||
});
|
||||
|
||||
this._updateGameLabel(selectedId, games);
|
||||
},
|
||||
|
||||
_updateGameLabel(selectedId, games) {
|
||||
if (selectedId && games) {
|
||||
const game = games.find(g => g.id === selectedId);
|
||||
if (game) {
|
||||
this._gameLabelEl.textContent = game.name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._gameLabelEl.textContent = '选择游戏';
|
||||
},
|
||||
|
||||
async _selectGame(gameId) {
|
||||
await API.updateConfig({ selected_game_id: gameId });
|
||||
await API.startTelemetry();
|
||||
this._gameListEl.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));
|
||||
const items = this._gameListEl.querySelectorAll('.nav-item');
|
||||
items.forEach(item => {
|
||||
if (item.textContent.trim()) item.classList.add('active');
|
||||
});
|
||||
const games = await API.getGames();
|
||||
this._updateGameLabel(gameId, games);
|
||||
this._loadGames();
|
||||
|
||||
if (typeof Router !== 'undefined' && Router.currentPage === 'scene') {
|
||||
Router.navigate('scene');
|
||||
}
|
||||
},
|
||||
|
||||
_toggleGameList() {
|
||||
const toggle = this._gameToggleEl;
|
||||
const list = this._gameListEl;
|
||||
const isCollapsed = toggle.classList.contains('collapsed');
|
||||
|
||||
if (isCollapsed) {
|
||||
toggle.classList.remove('collapsed');
|
||||
list.classList.remove('collapsed');
|
||||
} else {
|
||||
toggle.classList.add('collapsed');
|
||||
list.classList.add('collapsed');
|
||||
}
|
||||
},
|
||||
|
||||
setActive(route) {
|
||||
this._el.querySelectorAll('.nav-item[data-route]').forEach(el => {
|
||||
el.classList.toggle('active', el.getAttribute('data-route') === route);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.Sidebar = Sidebar;
|
||||
@@ -0,0 +1,32 @@
|
||||
const Topbar = {
|
||||
_statusEl: null,
|
||||
|
||||
init() {
|
||||
this._statusEl = document.getElementById('connection-status');
|
||||
|
||||
document.getElementById('theme-toggle').addEventListener('click', () => {
|
||||
Theme.toggle();
|
||||
});
|
||||
|
||||
WS.on('status', (status) => {
|
||||
this._statusEl.className = `status-indicator ${status}`;
|
||||
this._statusEl.querySelector('.status-text').textContent =
|
||||
status === 'connected' ? '已连接' :
|
||||
status === 'error' ? '连接错误' : '未连接';
|
||||
});
|
||||
|
||||
setInterval(() => this._refreshStatus(), 5000);
|
||||
},
|
||||
|
||||
async _refreshStatus() {
|
||||
try {
|
||||
const status = await API.getStatus();
|
||||
const wsStatus = status.ws_clients > 0 ? 'connected' : 'disconnected';
|
||||
this._statusEl.className = `status-indicator ${wsStatus}`;
|
||||
this._statusEl.querySelector('.status-text').textContent =
|
||||
wsStatus === 'connected' ? `已连接 (${status.ws_clients})` : '未连接';
|
||||
} catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
window.Topbar = Topbar;
|
||||
Reference in New Issue
Block a user