feat: WS sends selected_game_id on connect, gear indicator auto-adapts to game mapping (Forza/AC/iRacing)

This commit is contained in:
2026-07-25 16:29:00 +08:00
parent 636801b6a3
commit 7959d911e6
3 changed files with 68 additions and 19 deletions
+6 -1
View File
@@ -84,7 +84,12 @@ async def scene_render(request: Request, scene_id: str):
async def websocket_endpoint(ws: WebSocket): async def websocket_endpoint(ws: WebSocket):
cid = await ws_manager.connect(ws) cid = await ws_manager.connect(ws)
try: try:
await ws.send_json({"type": "connected", "client_id": cid}) cfg = get_config()
await ws.send_json({
"type": "connected",
"client_id": cid,
"selected_game_id": cfg.get("selected_game_id"),
})
while True: while True:
msg = await ws.receive_json() msg = await ws.receive_json()
if msg.get("type") == "ping": if msg.get("type") == "ping":
+60 -17
View File
@@ -46,6 +46,60 @@ html,body{width:100%;height:100%;overflow:hidden;background:#000;
</div></div></div> </div></div></div>
<script> <script>
const M={aspect_ratio:"1:1",render_mode:"contain",gear_style:"auto"}; const M={aspect_ratio:"1:1",render_mode:"contain",gear_style:"auto"};
let GAME_ID='';
// Forza: 0=R, 1-8=1-8 (no neutral in UDP)
// AC/F1: -1=R, 0=N, 1-8=1-8
// iRacing: 0=N, 1=R, 2=1st... (offset +2)
function gearLabel(g){
if(!g&&g!==0)return'-';
const style=M.gear_style||'auto';
const isForza=GAME_ID&&(GAME_ID.includes('forza'));
const isIRacing=GAME_ID&&GAME_ID.includes('iracing');
if(style==='forza'||isForza){
if(g===0)return'R';
if(g>=1&&g<=8)return String(g);
return String(g);
}
if(style==='ac'||(!isForza&&!isIRacing)){
if(g===-1)return'R';
if(g===0)return'N';
if(g>=1&&g<=8)return String(g);
return String(g);
}
if(isIRacing){
if(g===0)return'N';
if(g===1)return'R';
if(g>=2&&g<=9)return String(g-1);
return String(g);
}
// fallback
if(g===0)return'N';
if(g>0)return String(g);
return String(g);
}
function gearBarIndex(g){
if(!g&&g!==0)return-1;
const isForza=GAME_ID&&GAME_ID.includes('forza');
const isIRacing=GAME_ID&&GAME_ID.includes('iracing');
if(isForza){
if(g===0)return 0; // R at index 0
if(g>=1&&g<=8)return g+1; // 1->2
}
if(isIRacing){
if(g===0)return 1; // N at index 1
if(g===1)return 0; // R at index 0
if(g>=2&&g<=9)return g; // 2->2(index for '1')
}
// AC/F1/default
if(g===-1)return 0; // R
if(g===0)return 1; // N
if(g>=1&&g<=8)return g+1; // 1->2
return -1;
}
(function(){ (function(){
const box=document.getElementById('box'),gearTxt=document.getElementById('gear-txt'), const box=document.getElementById('box'),gearTxt=document.getElementById('gear-txt'),
gearEl=document.getElementById('gear-el'),barRow=document.getElementById('bar-row'); gearEl=document.getElementById('gear-el'),barRow=document.getElementById('bar-row');
@@ -91,21 +145,8 @@ const M={aspect_ratio:"1:1",render_mode:"contain",gear_style:"auto"};
function updateGear(g){ function updateGear(g){
if(g===lastGear)return;lastGear=g; if(g===lastGear)return;lastGear=g;
const label=gearLabel(g);
// Determine mapping based on actual gear value const activeIndex=gearBarIndex(g);
let label,activeIndex;
if(g<0){// AC style: negative is reverse
label='R';activeIndex=0;
}else if(g===0){
// ambiguous: could be R (Forza) or N (AC)
// Show R by default for Forza, but check gear_style
label='R';activeIndex=0;
}else{
// Forward gear: g is 1-based in Forza, but some games might use 0-based
label=String(g);
if(g>=1&&g<=8)activeIndex=g+1; // gear 1 -> index 2
else activeIndex=-1;
}
gearTxt.textContent=label; gearTxt.textContent=label;
gearEl.className='gear-circle active'; gearEl.className='gear-circle active';
@@ -131,8 +172,10 @@ const M={aspect_ratio:"1:1",render_mode:"contain",gear_style:"auto"};
} }
function connect(){ function connect(){
ws=new WebSocket(wsUrl);ws.onopen=()=>{if(rt){clearTimeout(rt);rt=null}}; ws=new WebSocket(wsUrl);ws.onopen=()=>{if(rt){clearTimeout(rt);rt=null}};
ws.onmessage=(e)=>{try{const m=JSON.parse(e.data);if(m.type==='telemetry')updateGear(m.data.gear||0)}catch(e){}}; ws.onmessage=(e)=>{try{const m=JSON.parse(e.data);
if(m.type==='connected'){GAME_ID=m.selected_game_id||'';console.log('[Gear] game:',GAME_ID)}
if(m.type==='telemetry')updateGear(m.data.gear||0)}catch(e){}};
ws.onclose=()=>{rt=setTimeout(connect,2000)}; ws.onclose=()=>{rt=setTimeout(connect,2000)};
} }
+2 -1
View File
@@ -136,7 +136,8 @@ const PageDashboard = {
}, },
_openDashboard(themeId) { _openDashboard(themeId) {
const url = `${location.origin}/dashboard/${themeId}`; const gameParam = this._selectedGameId ? `?game=${this._selectedGameId}` : '';
const url = `${location.origin}/dashboard/${themeId}${gameParam}`;
window.open(url, '_blank'); window.open(url, '_blank');
navigator.clipboard.writeText(url).then(() => { navigator.clipboard.writeText(url).then(() => {
Toast.show('链接已复制,可在局域网设备访问', 'success'); Toast.show('链接已复制,可在局域网设备访问', 'success');