openpilot/dragonpilot/dashy/web/dist/js/app.js
2025-11-11 22:44:56 +08:00

2 lines
53 KiB
JavaScript
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.

const t=500,e=10,i=100,a=.5,s=60,n=1/30,r=[{r:13,g:248,b:122,a:102},{r:114,g:255,b:92,a:89},{r:114,g:255,b:92,a:0}],o=[{r:242,g:242,b:242,a:102},{r:242,g:242,b:242,a:89},{r:242,g:242,b:242,a:0}];function l(t,e){const i=[[0,0,0],[0,0,0],[0,0,0]];for(let a=0;a<3;a++)for(let s=0;s<3;s++)i[a][s]=t[a][0]*e[0][s]+t[a][1]*e[1][s]+t[a][2]*e[2][s];return i}function d(t,e){return[t[0][0]*e[0]+t[0][1]*e[1]+t[0][2]*e[2],t[1][0]*e[0]+t[1][1]*e[1]+t[1][2]*e[2],t[2][0]*e[0]+t[2][1]*e[1]+t[2][2]*e[2]]}function h(t,e,i,a){const s=t[0][0]*e+t[0][1]*i+t[0][2]*a,n=t[1][0]*e+t[1][1]*i+t[1][2]*a,r=t[2][0]*e+t[2][1]*i+t[2][2]*a;return Math.abs(r)<1e-6?{x:0,y:0,z:0,valid:!1}:{x:s/r,y:n/r,z:r,valid:!0}}function c(t,e,i){return Math.max(e,Math.min(i,t))}function _(t){const[e,i,a]=t,s=Math.cos(e),n=Math.sin(e),r=Math.cos(i),o=Math.sin(i),l=Math.cos(a),d=Math.sin(a);return[[l*r,l*o*n-d*s,l*o*s+d*n],[d*r,d*o*n+l*s,d*o*s-l*n],[-o,r*n,r*s]]}const g=[[0,-1,0],[0,0,-1],[1,0,0]],u={tici:{fcam:{intrinsics:[[2648,0,964],[0,2648,604],[0,0,1]],width:1928,height:1208}},mici:{fcam:{intrinsics:[[1141.5,0,672],[0,1141.5,380],[0,0,1]],width:1344,height:760}}};class p{constructor(){window.debug,this._longitudinal_control=!1,this._experimental_mode=!1,this._blend_factor=1,this._prev_allow_throttle=!0,this._lane_line_probs=new Float32Array(4),this._road_edge_stds=new Float32Array(2),this._lead_vehicles=[{},{}],this._path_offset_z=1.22,this._use_simple_lines=!0,this._path={raw_points:[],projected_points:[]},this._lane_lines=[{raw_points:[],projected_points:[]},{raw_points:[],projected_points:[]},{raw_points:[],projected_points:[]},{raw_points:[],projected_points:[]}],this._road_edges=[{raw_points:[],projected_points:[]},{raw_points:[],projected_points:[]}],this._acceleration_x=new Float32Array(0),this._maxPoints=200,this._leftPointsBuffer=new Array(this._maxPoints),this._rightPointsBuffer=new Array(this._maxPoints),this._transformCache=new Map,this._transformCacheValid=!1,this._batchTransformEnabled=!0,this._batchLeftX=new Float32Array(this._maxPoints),this._batchLeftY=new Float32Array(this._maxPoints),this._batchLeftZ=new Float32Array(this._maxPoints),this._batchRightX=new Float32Array(this._maxPoints),this._batchRightY=new Float32Array(this._maxPoints),this._batchRightZ=new Float32Array(this._maxPoints),this._gradientCache=new Map,this._gradientCacheMaxSize=20,this._pathCache=new Map,this._pathCacheMaxSize=10,this._lastPathPoints=null,this._lastPathHash=null,this._pathLengthCache=new Map,this._lastPathLengthCacheClear=0,this._framePathLengthCache=new Map,this._lastFrameTime=0,this._car_space_transform=[[1,0,0],[0,1,0],[0,0,1]],this._transform_dirty=!0,this._clip_region=null,this._rect=null,setInterval(()=>{this._transformCache.clear(),this._pathLengthCache.clear(),this._gradientCache.clear(),window.debug},3e4),this._exp_gradient={start:[0,1],end:[0,0],colors:[],stops:[]}}set_transform(t){window.debug,this._car_space_transform=t,this._transform_dirty=!0,this._transformCache.clear(),this._transformCacheValid=!1,this._gradientCache.clear(),this._transform_logged||(this._transform_logged=!0)}set_simple_lines_mode(t){window.debug,this._use_simple_lines=t}clear_caches(){window.debug,this._transformCache.clear(),this._transformCacheValid=!1,this._gradientCache.clear(),this._pathCache.clear(),this._lastPathHash=null}render(t,e,i){if(window.debug,this._rect=t,this.ctx=i,i.lineCap="round",i.lineJoin="round",this._debug_logged||(this._debug_logged=!0),!e.liveCalibration||!e.modelV2)return;this._clip_region={x:t.x,y:t.y,width:t.width,height:t.height},e.selfdriveState&&(this._experimental_mode=e.selfdriveState.experimentalMode||!1);const a=e.liveCalibration;a&&a.height&&a.height.length>0&&(this._path_offset_z=a.height[0]),e.updated&&e.updated.carParams?this._longitudinal_control=e.carParams.openpilotLongitudinalControl||!1:e.seen&&e.seen.carParams||(this._longitudinal_control=!0);const s=e.modelV2;if(!s)return;const n=e.valid&&e.valid.radarState?e.radarState:null,r=n?n.leadOne:null,o=this._longitudinal_control&&null!==n;!this._radar_logged&&n&&(this._radar_logged=!0);const l=e.updated&&e.updated.modelV2,d=e.updated&&e.updated.radarState;if(this._update_logged||!l&&!d||(this._update_logged=!0),l||d||this._transform_dirty){l&&this._update_raw_points(s);const t=this._path.raw_points.map(t=>t[0]);if(0===t.length)return;this._update_model(r,t),o&&this._update_leads(n,t),this._transform_dirty=!1}this._draw_lane_lines(),this._draw_path(e),o&&n&&this._draw_lead_indicator()}_update_raw_points(t){if(window.debug,t.position){this._path.raw_points.length=0;for(let e=0;e<t.position.x.length;e++)this._path.raw_points.push([t.position.x[e],t.position.y[e],t.position.z[e]])}if(this._transformCache.size>200){Array.from(this._transformCache.keys()).slice(0,100).forEach(t=>this._transformCache.delete(t)),window.debug}if(this._pathCache.size>this._pathCacheMaxSize){const t=Array.from(this._pathCache.entries()).slice(-5);this._pathCache.clear(),t.forEach(([t,e])=>this._pathCache.set(t,e))}t.laneLines&&t.laneLines.forEach((t,e)=>{this._lane_lines[e].raw_points.length=0;for(let i=0;i<t.x.length;i++)this._lane_lines[e].raw_points.push([t.x[i],t.y[i],t.z[i]])}),t.roadEdges&&t.roadEdges.forEach((t,e)=>{this._road_edges[e].raw_points.length=0;for(let i=0;i<t.x.length;i++)this._road_edges[e].raw_points.push([t.x[i],t.y[i],t.z[i]])}),this._lane_line_probs=new Float32Array(t.laneLineProbs||[0,0,0,0]),this._road_edge_stds=new Float32Array(t.roadEdgeStds||[1,1]),this._acceleration_x=new Float32Array(t.acceleration?t.acceleration.x:[])}_update_model(t,a){window.debug;let s=c(a[a.length-1],e,i);const n=this._get_path_length_idx(this._lane_lines[0].raw_points.map(t=>t[0]),s);this._batch_update_lines(n,t,a),this._update_experimental_gradient(this._rect.height)}_batch_update_lines(t,a,s){const n=[];for(let e=0;e<this._lane_lines.length;e++)n.push({line:this._lane_lines[e],raw_points:this._lane_lines[e].raw_points,y_offset:.025*this._lane_line_probs[e],z_offset:0,max_idx:t,allow_invert:!0});for(let e=0;e<this._road_edges.length;e++)n.push({line:this._road_edges[e],raw_points:this._road_edges[e].raw_points,y_offset:.025,z_offset:0,max_idx:t,allow_invert:!0});let r=c(s[s.length-1],e,i);if(a&&a.status){const t=2*a.dRel;r=c(t-Math.min(.35*t,10),0,r)}const o=this._get_path_length_idx(s,r);n.push({line:this._path,raw_points:this._path.raw_points,y_offset:.9,z_offset:this._path_offset_z,max_idx:o,allow_invert:!1}),this._batch_transform_all_lines(n)}_batch_transform_all_lines(t){let e=0;const i=[];for(const a of t){const t=a.raw_points.slice(0,a.max_idx+1).filter(t=>t[0]>=0);t.length>0&&(i.push({...a,points:t,startIdx:e,numPoints:t.length}),e+=2*t.length)}if(0===e)return;const a=new Float32Array(e),s=new Float32Array(e),n=new Float32Array(e);let r=0;for(const t of i)for(let e=0;e<t.numPoints;e++){const i=t.points[e];a[r]=i[0],s[r]=i[1]-t.y_offset,n[r]=i[2]+t.z_offset,r++,a[r]=i[0],s[r]=i[1]+t.y_offset,n[r]=i[2]+t.z_offset,r++}const o=this._batch_transform(a,s,n,e);for(const t of i){const e=[],i=[],a=[];for(let e=0;e<t.numPoints;e++){const s=t.startIdx+2*e,n=s+1,r=o.points[s],l=o.points[n];if(r.valid&&l.valid){const t=1e4;Math.abs(r.x)<t&&Math.abs(r.y)<t&&Math.abs(l.x)<t&&Math.abs(l.y)<t&&(i.push([r.x,r.y]),a.push([l.x,l.y]))}}if(!t.allow_invert&&i.length>1){const t=[],e=[];let s=1/0;for(let n=0;n<i.length;n++)i[n][1]<=s&&(s=i[n][1],t.push(i[n]),e.push(a[n]));i.length=0,a.length=0,i.push(...t),a.push(...e)}i.forEach(t=>e.push(t));for(let t=a.length-1;t>=0;t--)e.push(a[t]);t.line.projected_points=e}}_update_leads(t,e){window.debug,this._lead_vehicles=[{},{}];const i=[t.leadOne,t.leadTwo],a=[],s={x:[],y:[],z:[]};for(let t=0;t<i.length;t++){const n=i[t];if(n&&n.status){const i=n.dRel,r=n.yRel,o=n.vRel,l=this._get_path_length_idx(e,i);let d=0;l<this._path.raw_points.length&&(d=this._path.raw_points[l][2]),a.push({i:t,d_rel:i,v_rel:o}),s.x.push(i),s.y.push(-r),s.z.push(d+this._path_offset_z)}}if(0===a.length)return;const n=this._batch_transform(new Float32Array(s.x),new Float32Array(s.y),new Float32Array(s.z),a.length);for(let t=0;t<a.length;t++){const e=a[t],i=n.points[t];if(i.valid){const t=[i.x,i.y];this._lead_vehicles[e.i]=this._update_lead_vehicle(e.d_rel,e.v_rel,t,this._rect)}}}_map_to_screen(t,e,i){window.debug;const a=h(this._car_space_transform,t,e,i);return a.valid?[a.x,a.y]:null}_batch_transform(t,e,i,a){window.debug;const s=new Array(a),n=this._car_space_transform;let r=0;const o=n[0][0],l=n[0][1],d=n[0][2],h=n[1][0],c=n[1][1],_=n[1][2],g=n[2][0],u=n[2][1],p=n[2][2];for(let n=0;n<a;n++){const a=t[n],f=e[n],w=i[n],m=o*a+l*f+d*w,b=h*a+c*f+_*w,v=g*a+u*f+p*w;if(Math.abs(v)>=1e-6){const t=1/v;s[n]={x:m*t,y:b*t,valid:!0},r++}else s[n]={x:0,y:0,valid:!1}}return{points:s,validCount:r}}_map_line_to_polygon(t,e,i,a,s=!0){if(window.debug,0===t.length)return[];let n=t.slice(0,a+1);if(n=n.filter(t=>t[0]>=0),0===n.length)return[];const r=n.length,o=[],l=[];if(this._batchTransformEnabled&&r>10){for(let t=0;t<r;t++){const a=n[t];this._batchLeftX[t]=a[0],this._batchLeftY[t]=a[1]-e,this._batchLeftZ[t]=a[2]+i,this._batchRightX[t]=a[0],this._batchRightY[t]=a[1]+e,this._batchRightZ[t]=a[2]+i}const t=this._batch_transform(this._batchLeftX,this._batchLeftY,this._batchLeftZ,r),a=this._batch_transform(this._batchRightX,this._batchRightY,this._batchRightZ,r);for(let e=0;e<r;e++){const i=t.points[e],s=a.points[e];if(i.valid&&s.valid){const t=1e4;Math.abs(i.x)<t&&Math.abs(i.y)<t&&Math.abs(s.x)<t&&Math.abs(s.y)<t&&(o.push([i.x,i.y]),l.push([s.x,s.y]))}}}else for(let t=0;t<r;t++){const a=n[t],s=[a[0],a[1]-e,a[2]+i],r=[a[0],a[1]+e,a[2]+i],d=h(this._car_space_transform,s[0],s[1],s[2]),c=h(this._car_space_transform,r[0],r[1],r[2]);if(d.valid&&c.valid){const t=[d.x,d.y],e=[c.x,c.y],i=1e4;Math.abs(t[0])<i&&Math.abs(t[1])<i&&Math.abs(e[0])<i&&Math.abs(e[1])<i&&(o.push(t),l.push(e))}}if(0===o.length)return[];if(!s&&o.length>1){const t=[],e=[];let i=1/0;for(let a=0;a<o.length;a++)o[a][1]<=i&&(i=o[a][1],t.push(o[a]),e.push(l[a]));o.length=0,l.length=0,o.push(...t),l.push(...e)}const d=[];o.forEach(t=>d.push(t));for(let t=l.length-1;t>=0;t--)d.push(l[t]);return d}_get_path_length_idx(t,e){if(window.debug,0===t.length)return 0;const i=`${t.length}_${e.toFixed(2)}`,a=Date.now();if(a!==this._lastFrameTime&&(this._framePathLengthCache.clear(),this._lastFrameTime=a),this._framePathLengthCache.has(i))return this._framePathLengthCache.get(i);if(this._pathLengthCache.has(i)){const t=this._pathLengthCache.get(i);return this._framePathLengthCache.set(i,t),t}a-this._lastPathLengthCacheClear>6e4&&(this._pathLengthCache.clear(),this._lastPathLengthCacheClear=a);let s=0;for(let i=0;i<t.length&&t[i]<=e;i++)s=i;return this._pathLengthCache.set(i,s),this._framePathLengthCache.set(i,s),s}_draw_lane_lines(){window.debug,this._lane_debug_logged||(this._lane_debug_logged=!0),this._use_simple_lines?this._batch_draw_all_lines():(this._lane_lines.forEach((t,e)=>{if(0===t.projected_points.length)return;const i=c(this._lane_line_probs[e],0,.7);this._draw_polygon(t.projected_points,`rgba(255, 255, 255, ${i})`)}),this._road_edges.forEach((t,e)=>{if(0===t.projected_points.length)return;const i=c(1-this._road_edge_stds[e],0,1);this._draw_polygon(t.projected_points,`rgba(255, 0, 0, ${i})`)}))}_batch_draw_all_lines(){const t=[];if(this._lane_lines.forEach((e,i)=>{if(e.raw_points.length<2)return;const a=this._lane_line_probs[i];if(a<.1)return;const s=c(a,0,.7);t.push({points:e.raw_points,color:`rgba(255, 255, 255, ${s})`,lineWidth:4})}),this._road_edges.forEach((e,i)=>{if(e.raw_points.length<2)return;const a=this._road_edge_stds[i];if(a>.9)return;const s=c(1-a,0,1);t.push({points:e.raw_points,color:`rgba(255, 0, 0, ${s})`,lineWidth:4})}),0===t.length)return;let e=0;const i=[];for(const a of t){const t=[];for(let e=0;e<a.points.length;e++){const i=a.points[e];i[0]>=0&&i[0]<=100&&t.push(e)}t.length>=2&&(i.push({...a,validIndices:t,startIdx:e,numPoints:t.length}),e+=t.length)}if(0===e)return;const a=new Float32Array(e),s=new Float32Array(e),n=new Float32Array(e);let r=0;for(const t of i)for(const e of t.validIndices){const i=t.points[e];a[r]=i[0],s[r]=i[1],n[r]=i[2],r++}const o=this._batch_transform(a,s,n,e),l=this.ctx;for(const t of i){l.strokeStyle=t.color,l.lineWidth=t.lineWidth,l.beginPath();let e=!1;for(let i=0;i<t.numPoints;i++){const a=t.startIdx+i,s=o.points[a];s.valid&&Math.abs(s.x)<5e3&&Math.abs(s.y)<5e3?e?i>0&&t.validIndices[i]-t.validIndices[i-1]===1?l.lineTo(s.x,s.y):(l.stroke(),l.beginPath(),l.moveTo(s.x,s.y)):(l.moveTo(s.x,s.y),e=!0):e&&(l.stroke(),l.beginPath(),e=!1)}e&&l.stroke()}}_draw_path(t){if(window.debug,0!==this._path.projected_points.length)if(this._experimental_mode)this._exp_gradient.colors.length>2?this._draw_polygon_gradient(this._path.projected_points,this._exp_gradient):this._draw_polygon(this._path.projected_points,"rgba(255, 255, 255, 0.12)");else{const e=t.longitudinalPlan&&t.longitudinalPlan.allowThrottle||!this._longitudinal_control;e!==this._prev_allow_throttle&&(this._prev_allow_throttle=e,this._blend_factor=Math.max(1-this._blend_factor,0)),this._blend_factor<1&&(this._blend_factor=Math.min(this._blend_factor+n,1));const i=e?o:r,a=e?r:o,s={start:[0,1],end:[0,0],colors:this._blend_colors(i,a,this._blend_factor),stops:[0,.5,1]};this._draw_polygon_gradient(this._path.projected_points,s)}}_draw_polygon(t,e){if(window.debug,t.length<3)return;const i=this.ctx;i.fillStyle=e;const a=this._getCachedPath2D(t);i.fill(a)}_getCachedPath2D(t){window.debug;const e=this._generatePointsHash(t);if(this._pathCache.has(e))return this._pathCache.get(e);const i=new Path2D;i.moveTo(t[0][0],t[0][1]);for(let e=1;e<t.length;e++)i.lineTo(t[e][0],t[e][1]);if(i.closePath(),this._pathCache.set(e,i),this._pathCache.size>this._pathCacheMaxSize){const t=this._pathCache.keys().next().value;this._pathCache.delete(t)}return i}_generatePointsHash(t){if(window.debug,t.length<3)return"empty";const e=t[0],i=t[Math.floor(t.length/2)],a=t[t.length-1],s=t=>Math.round(10*t)/10;return`${t.length}:${s(e[0])},${s(e[1])}:${s(i[0])},${s(i[1])}:${s(a[0])},${s(a[1])}`}_draw_simple_line(t,e,a=2){if(window.debug,t.length<2)return;const s=this.ctx;s.strokeStyle=e,s.lineWidth=a;const n=Math.min(i,t[t.length-1][0]||i),r=[],o=[],l=[],d=[];for(let e=0;e<t.length;e++){const i=t[e];i[0]>=0&&i[0]<=n&&(r.push(e),o.push(i[0]),l.push(i[1]),d.push(i[2]))}if(r.length<2)return;const h=this._batch_transform(new Float32Array(o),new Float32Array(l),new Float32Array(d),o.length);let c=!1;s.beginPath();for(let t=0;t<h.points.length;t++){const e=h.points[t];e.valid&&Math.abs(e.x)<5e3&&Math.abs(e.y)<5e3?c?t>0&&r[t]-r[t-1]===1?s.lineTo(e.x,e.y):(s.stroke(),s.beginPath(),s.moveTo(e.x,e.y)):(s.moveTo(e.x,e.y),c=!0):c&&(s.stroke(),s.beginPath(),c=!1)}c&&s.stroke()}_draw_polygon_gradient(t,e){if(window.debug,t.length<3)return;const i=this.ctx,a=this._rect,s=e.start[0]*a.width+a.x,n=e.start[1]*a.height+a.y,r=e.end[0]*a.width+a.x,o=e.end[1]*a.height+a.y,l=this._getCachedGradient(i,s,n,r,o,e),d=this._getCachedPath2D(t);i.fillStyle=l,i.fill(d)}_getCachedGradient(t,e,i,a,s,n){window.debug;const r=`${Math.round(e)},${Math.round(i)},${Math.round(a)},${Math.round(s)}:${n.colors.map(t=>`${t.r},${t.g},${t.b},${t.a}`).join("|")}:${n.stops.join(",")}`;if(this._gradientCache.has(r))return this._gradientCache.get(r);const o=t.createLinearGradient(e,i,a,s);for(let t=0;t<n.stops.length;t++){const e=n.colors[t];o.addColorStop(n.stops[t],`rgba(${e.r}, ${e.g}, ${e.b}, ${e.a/255})`)}if(this._gradientCache.set(r,o),this._gradientCache.size>this._gradientCacheMaxSize){const t=this._gradientCache.keys().next().value;this._gradientCache.delete(t)}return o}_blend_colors(t,e,i){if(window.debug,i>=1)return e;if(i<=0)return t;const a=1-i;return t.map((t,s)=>{const n=e[s];return{r:Math.round(a*t.r+i*n.r),g:Math.round(a*t.g+i*n.g),b:Math.round(a*t.b+i*n.b),a:Math.round(a*t.a+i*n.a)}})}_update_experimental_gradient(t){window.debug,this._exp_gradient.colors=[],this._exp_gradient.stops=[]}_update_lead_vehicle(t,e,i,a){window.debug;const s=1.57*c(750/(t/3+30),15,30)*1;this._lead_size_logged||(this._lead_size_logged=!0);const n=c(i[0],0,a.width-s/2),r=Math.min(i[1],a.height-.6*s),o=s/5,l=s/10;let d=0;return t<40&&(d=255*(1-t/40),e<0&&(d+=e/10*-1*255),d=Math.min(d,255)),{glow:[[n+1.35*s+o,r+s+l],[n,r-l],[n-1.35*s-o,r+s+l]],chevron:[[n+1.25*s,r+s],[n,r],[n-1.25*s,r+s]],fill_alpha:d}}_draw_lead_indicator(){window.debug,this._lead_debug_logged||(this._lead_debug_logged=!0),this._lead_vehicles.forEach(t=>{t.glow&&t.chevron&&(this._draw_triangle_fan(t.glow,"rgba(218, 202, 37, 1)"),this._draw_triangle_fan(t.chevron,`rgba(201, 34, 49, ${t.fill_alpha/255})`))})}_draw_triangle_fan(t,e){if(window.debug,t.length<3)return;const i=this.ctx;i.fillStyle=e;const a=t[0],s=new Path2D;for(let e=1;e<t.length-1;e++)s.moveTo(a[0],a[1]),s.lineTo(t[e][0],t[e][1]),s.lineTo(t[e+1][0],t[e+1][1]),s.closePath();i.fill(s)}}window.ModelRenderer=p;const f=255,w=.621371,m="",b={header_height:150,border_size:15,button_size:96,set_speed_width_metric:100,set_speed_width_imperial:86,set_speed_height:102},v={current_speed:88,speed_unit:33,max_speed:20,set_speed:45},x={white:"rgba(255, 255, 255, 1)",disengaged:"rgba(145, 155, 149, 1)",override:"rgba(145, 155, 149, 1)",engaged:"rgba(128, 216, 166, 1)",disengaged_bg:"rgba(0, 0, 0, 0.6)",override_bg:"rgba(145, 155, 149, 0.8)",engaged_bg:"rgba(128, 216, 166, 0.8)",grey:"rgba(166, 166, 166, 1)",dark_grey:"rgba(114, 114, 114, 1)",black_translucent:"rgba(0, 0, 0, 0.65)",white_translucent:"rgba(255, 255, 255, 0.78)",border_translucent:"rgba(255, 255, 255, 0.29)",header_gradient_start:"rgba(0, 0, 0, 0.45)",header_gradient_end:"rgba(0, 0, 0, 0)"},y={DISENGAGED:"DISENGAGED",OVERRIDE:"OVERRIDE",ENGAGED:"ENGAGED"};class C{constructor(){window.debug,this.is_cruise_set=!1,this.is_cruise_available=!1,this.set_speed=f,this.speed=0,this.v_ego_cluster_seen=!1,this._text_cache=new Map,this._click_callback=null,this._gradientCache=null,this._gradientRect=null,this._offscreenCanvas=null,this._offscreenCtx=null,this._cachedState={is_cruise_set:null,is_cruise_available:null,set_speed:null,speed:null,ui_status:null,rect:null}}set_callbacks(t=null){window.debug,this._click_callback=t}render(t,e,i){window.debug,this.ctx=i,this._update_state(e);if(this._needsRedraw(t)){this._ensureOffscreenCanvas(t);const e=this.ctx;this.ctx=this._offscreenCtx,this._offscreenCtx.clearRect(0,0,t.width,t.height),this._draw_header_gradient(t),this.is_cruise_available&&this._draw_set_speed(t),this._draw_current_speed(t),this.ctx=e,this._updateCachedState(t)}return i.drawImage(this._offscreenCanvas,0,0),this.handle_mouse_event(t)}_needsRedraw(t){if(!this._cachedState.rect||this._cachedState.rect.width!==t.width||this._cachedState.rect.height!==t.height)return!0;const e=this._get_ui_status();return this._cachedState.is_cruise_set!==this.is_cruise_set||this._cachedState.is_cruise_available!==this.is_cruise_available||Math.round(this._cachedState.set_speed||0)!==Math.round(this.set_speed)||Math.round(this._cachedState.speed||0)!==Math.round(this.speed)||this._cachedState.ui_status!==e}_updateCachedState(t){this._cachedState={is_cruise_set:this.is_cruise_set,is_cruise_available:this.is_cruise_available,set_speed:this.set_speed,speed:this.speed,ui_status:this._get_ui_status(),rect:{width:t.width,height:t.height}}}_ensureOffscreenCanvas(t){this._offscreenCanvas&&this._offscreenCanvas.width===t.width&&this._offscreenCanvas.height===t.height||(this._offscreenCanvas=document.createElement("canvas"),this._offscreenCanvas.width=t.width,this._offscreenCanvas.height=t.height,this._offscreenCtx=this._offscreenCanvas.getContext("2d"),this._gradientCache=null,this._gradientRect=null)}_update_state(t){if(window.debug,!t.carState||!t.controlsState)return this.is_cruise_set=!1,this.set_speed=f,void(this.speed=0);const e=t.controlsState,i=t.carState,a=i.vCruiseCluster||0;this.set_speed=0===a?e.vCruiseDEPRECATED||0:a,this.is_cruise_set=this.set_speed>0&&this.set_speed<f,this.is_cruise_available=-1!==this.set_speed,this.is_cruise_set&&!Kt.is_metric&&(this.set_speed*=w);const s=i.vEgoCluster||0;this.v_ego_cluster_seen=this.v_ego_cluster_seen||0!==s;const n=this.v_ego_cluster_seen?s:i.vEgo||0,r=Kt.is_metric?3.6:2.236936;this.speed=Math.max(0,n*r)}_draw_header_gradient(t){window.debug,this._gradientCache&&this._gradientRect&&this._gradientRect.width===t.width&&this._gradientRect.height===t.height||(this._gradientCache=this.ctx.createLinearGradient(t.x,t.y,t.x,t.y+b.header_height),this._gradientCache.addColorStop(0,x.header_gradient_start),this._gradientCache.addColorStop(1,x.header_gradient_end),this._gradientRect={width:t.width,height:t.height}),this.ctx.fillStyle=this._gradientCache,this.ctx.fillRect(t.x,t.y,t.width,b.header_height)}_draw_set_speed(t){window.debug;const e=Kt.is_metric?b.set_speed_width_metric:b.set_speed_width_imperial,i=t.x+30+(b.set_speed_width_imperial-e)/2,a=t.y+22;this.ctx.fillStyle=x.black_translucent,this._draw_rounded_rect(i,a,e,b.set_speed_height,10),this.ctx.strokeStyle=x.border_translucent,this.ctx.lineWidth=3,this._stroke_rounded_rect(i,a,e,b.set_speed_height,10);let s=x.grey,n=x.dark_grey;if(this.is_cruise_set){n=x.white;const t=this._get_ui_status();s=t===y.ENGAGED?x.engaged:t===y.OVERRIDE?x.override:x.disengaged}this.ctx.fillStyle=s,this.ctx.font=`600 ${v.max_speed}px Arial`,this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillText("MAX",i+e/2,a+23);const r=this.is_cruise_set?Math.round(this.set_speed).toString():m;this.ctx.fillStyle=n,this.ctx.font=`bold ${v.set_speed}px Arial`,this.ctx.fillText(r,i+e/2,a+61)}_draw_current_speed(t){window.debug;const e=Math.round(this.speed).toString();this.ctx.fillStyle=x.white,this.ctx.font=`bold ${v.current_speed}px Arial`,this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillText(e,t.x+t.width/2,t.y+22+v.current_speed/2);const i=Kt.is_metric?"km/h":"mph";this.ctx.fillStyle=x.white_translucent,this.ctx.font=`500 ${v.speed_unit}px Arial`,this.ctx.fillText(i,t.x+t.width/2,t.y+22+v.current_speed+15)}_draw_wheel(t,e,i,a){window.debug;const s=a.selfdriveState,n=!!s&&s.experimentalMode,r=!!s&&s.enabled;let o=x.disengaged,l=.2;r&&(o=n?x.engaged:x.white,l=n?1:.6);const d=this._parse_rgba(o);this.ctx.strokeStyle=`rgba(${d.r}, ${d.g}, ${d.b}, ${l})`,this.ctx.lineWidth=5,this.ctx.beginPath(),this.ctx.arc(t,e,i,0,2*Math.PI),this.ctx.stroke(),this.ctx.beginPath(),this.ctx.arc(t,e,.4*i,0,2*Math.PI),this.ctx.stroke();for(let a=0;a<3;a++){const s=(120*a-90)*Math.PI/180;this.ctx.beginPath(),this.ctx.moveTo(t+Math.cos(s)*i*.4,e+Math.sin(s)*i*.4),this.ctx.lineTo(t+Math.cos(s)*i,e+Math.sin(s)*i),this.ctx.stroke()}}handle_mouse_event(t){if(window.debug,!this._click_callback)return!1;t.x,t.width,t.y;return!1}_get_ui_status(){window.debug;const t=window.selfdriveStateData;return t?t.enabled?y.ENGAGED:t.activeOverride?y.OVERRIDE:y.DISENGAGED:y.DISENGAGED}_draw_rounded_rect(t,e,i,a,s){window.debug,this.ctx.beginPath(),this.ctx.moveTo(t+s,e),this.ctx.arcTo(t+i,e,t+i,e+a,s),this.ctx.arcTo(t+i,e+a,t,e+a,s),this.ctx.arcTo(t,e+a,t,e,s),this.ctx.arcTo(t,e,t+i,e,s),this.ctx.closePath(),this.ctx.fill()}_stroke_rounded_rect(t,e,i,a,s){window.debug,this.ctx.beginPath(),this.ctx.moveTo(t+s,e),this.ctx.arcTo(t+i,e,t+i,e+a,s),this.ctx.arcTo(t+i,e+a,t,e+a,s),this.ctx.arcTo(t,e+a,t,e,s),this.ctx.arcTo(t,e,t+i,e,s),this.ctx.closePath(),this.ctx.stroke()}_parse_rgba(t){window.debug;const e=t.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);return e?{r:parseInt(e[1]),g:parseInt(e[2]),b:parseInt(e[3]),a:e[4]?parseFloat(e[4]):1}:{r:255,g:255,b:255,a:1}}}window.HudRenderer=C;const S=27,E=40,M=30,T=20,L=36,P=36,$=24,A=66,I=78,D=48,R=5,k=10,z={none:"none",small:"small",mid:"mid",full:"full"},B={normal:"normal",userPrompt:"userPrompt",critical:"cricital"},F={[B.normal]:"rgba(0, 0, 0, 0.92)",[B.userPrompt]:"rgba(254, 140, 52, 0.92)",[B.critical]:"rgba(201, 34, 49, 0.92)"},G={text1:"openpilot Unavailable",text2:"Waiting to start",size:z.mid,status:B.normal},V={text1:"TAKE CONTROL IMMEDIATELY",text2:"System Unresponsive",size:z.full,status:B.critical},O={text1:"System Unresponsive",text2:"Reboot Device",size:z.full,status:B.critical};class j{constructor(){window.debug,this.started_time=0,this.last_selfdrive_time=0,this._offscreenCanvas=null,this._offscreenCtx=null,this._cachedAlert=null,this._cachedRect=null}render(t,e,i){window.debug,this.ctx=i;const a=this.get_alert(e);if(!a)return this._cachedAlert=null,!1;if(this._needsRedraw(a,t)){this._ensureOffscreenCanvas(t);const e=this.ctx;this.ctx=this._offscreenCtx,this._offscreenCtx.clearRect(0,0,t.width,t.height);const i=this._get_alert_rect(t,a.size);this._draw_background(i,a);const s={x:i.x+E,y:i.y+E,width:i.width-80,height:i.height-80};this._draw_text(s,a),this.ctx=e,this._cachedAlert={text1:a.text1,text2:a.text2,size:a.size,status:a.status},this._cachedRect={width:t.width,height:t.height}}return i.drawImage(this._offscreenCanvas,0,0),!0}_needsRedraw(t,e){return!this._cachedRect||this._cachedRect.width!==e.width||this._cachedRect.height!==e.height||(!this._cachedAlert||this._cachedAlert.text1!==t.text1||this._cachedAlert.text2!==t.text2||this._cachedAlert.size!==t.size||this._cachedAlert.status!==t.status)}_ensureOffscreenCanvas(t){this._offscreenCanvas&&this._offscreenCanvas.width===t.width&&this._offscreenCanvas.height===t.height||(this._offscreenCanvas=document.createElement("canvas"),this._offscreenCanvas.width=t.width,this._offscreenCanvas.height=t.height,this._offscreenCtx=this._offscreenCanvas.getContext("2d"))}get_alert(t){window.debug;const e=Date.now();if(0===this.started_time&&t.selfdriveState&&(this.started_time=e),!t.selfdriveState)return this.started_time>0&&e-this.started_time>5e3?G:null;t.updated&&t.updated.selfdriveState&&(this.last_selfdrive_time=e);const i=t.deviceState;if(i&&["tici","tizi","mici"].includes(i.deviceType)&&this.last_selfdrive_time>0){const i=(e-this.last_selfdrive_time)/1e3;if(i>5){const e=t.selfdriveState;return e&&e.enabled&&i-5<k?V:O}}const a=t.selfdriveState;return a&&a.alertSize!==z.none&&a.alertText1?{text1:a.alertText1,text2:a.alertText2||"",size:a.alertSize,status:a.alertStatus||B.normal}:null}_get_alert_rect(t,e){return e===z.full?t:{x:0,y:.8*t.height,width:t.width,height:.2*t.height}}_draw_background(t,e){const i=F[e.status]||F[B.normal];this.ctx.fillStyle=i,this.ctx.fillRect(t.x,t.y,t.width,t.height)}_draw_text(t,e){if(this.ctx.fillStyle="white",this.ctx.textAlign="center",e.size===z.small)this.ctx.font="bold 36px Arial",this.ctx.textBaseline="middle",this.ctx.fillText(e.text1,t.x+t.width/2,t.y+t.height/2);else if(e.size===z.mid){const i=t.y+t.height/3,a=t.y+2*t.height/3;this.ctx.font="bold 36px Arial",this.ctx.textBaseline="bottom",this.ctx.fillText(e.text1,t.x+t.width/2,i),this.ctx.font="24px Arial",this.ctx.textBaseline="top",this.ctx.fillText(e.text2,t.x+t.width/2,a)}else{const i=e.text1.length>15?A:I;this.ctx.font=`bold ${i}px Arial`,this.ctx.textBaseline="middle",this.ctx.fillText(e.text1,t.x+t.width/2,t.y+t.height/2),this.ctx.font="bold 48px Arial",this.ctx.textBaseline="top",this.ctx.fillText(e.text2,t.x+t.width/2,t.y+2*t.height/3)}}}window.AlertRenderer=j;const N=8,H="calibrated",W=[1.22],q={DISENGAGED:{r:23,g:51,b:73,a:200},OVERRIDE:{r:145,g:155,b:149,a:241},ENGAGED:{r:23,g:134,b:68,a:241}};function c(t,e,i){return Math.max(e,Math.min(i,t))}class U{constructor(){window.debug,this.device_camera=null,this.view_from_calib=[...g.map(t=>[...t])],this._last_calib_time=0,this._last_rect_dims={width:0,height:0},this._cached_matrix=null,this._content_rect={x:0,y:0,width:0,height:0},this.model_renderer=new p,this._hud_renderer=new C,this.alert_renderer=new j,this._click_callback=null}render(t,e,i){window.debug,this.sm=e,this.ctx=i,this._update_calibration();const a=t.width-16,s=t.height-16,n=this.device_camera||u.tici,r=a/s,o=n.fcam.width/n.fcam.height;let l=a,d=s,h=t.x+8,c=t.y+8;r>o?(l=s*o,h=t.x+8+(a-l)/2):(d=a/o,c=t.y+8+(s-d)/2),this._content_rect={x:h,y:c,width:l,height:d},this._draw_border(t),i.save(),i.beginPath(),i.rect(Math.floor(t.x),Math.floor(t.y),Math.floor(t.width),Math.floor(t.height)),i.clip(),this._calc_frame_matrix(),this.model_renderer.render(t,e,i),i.restore(),this._hud_renderer.render(t,e,i);this.alert_renderer.render(t,e,i);this._hud_renderer.handle_mouse_event(t)||this._click_callback}set_callbacks(t=null){window.debug,this._click_callback=t}invalidate_transform_cache(){window.debug,this._cached_matrix=null,this._last_rect_dims={width:0,height:0},this._last_calib_time=0,this.model_renderer&&this.model_renderer.clear_caches(),this.model_renderer&&(this.model_renderer._transform_dirty=!0)}_update_calibration(){window.debug;const t=this.sm;if(!this.device_camera&&t.seen&&t.seen.roadCameraState&&t.seen.deviceState){const e=t.deviceState?t.deviceState.deviceType:"tici";this.device_camera="mici"===e?u.mici:u.tici}if(!t.liveCalibration)return;const e=t.liveCalibration;if(!e.rpyCalib||3!==e.rpyCalib.length||e.calStatus!==H)return void(e.calStatus!==H&&(this.view_from_calib=[...g.map(t=>[...t])]));const i=_(e.rpyCalib);this.view_from_calib=l(g,i),this._cached_matrix=null}_calc_frame_matrix(){window.debug;const t=this.sm.updated&&this.sm.updated.liveCalibration,e=this.sm.recv_frame?this.sm.recv_frame.liveCalibration:Date.now(),i=this.ctx.canvas.width,a=this.ctx.canvas.height,s={width:i,height:a};if(!t&&this._last_calib_time===e&&this._last_rect_dims.width===s.width&&this._last_rect_dims.height===s.height&&null!==this._cached_matrix)return this._cached_matrix;const n=this.device_camera||u.tici,r=n.fcam.intrinsics,o=n.fcam.width,d=n.fcam.height,h=this.view_from_calib,c=(this._content_rect.x,this._content_rect.y,this._content_rect.width,this._content_rect.height,Math.max(i/o,a/d)),_=r[0][0]*c*1.1,g=l([[-_,0,i/2],[0,-_,a/2],[0,0,1]],h);return this.model_renderer.set_transform(g),this._last_calib_time=e,this._last_rect_dims=s,this._cached_matrix=g,this._cached_matrix}_draw_border(t){window.debug;const e=this.sm.selfdriveState&&this.sm.selfdriveState.engageable?"ENGAGED":"DISENGAGED",i=q[e]||q.DISENGAGED;this.ctx.strokeStyle=`rgba(${i.r}, ${i.g}, ${i.b}, ${i.a/255})`,this.ctx.lineWidth=8,this.ctx.strokeRect(t.x,t.y,t.width,t.height)}}window.AugmentedRoadView=U;const X=new URLSearchParams(window.location.search),Y="true"===X.get("debug"),J="true"===localStorage.getItem("debug");if(window.debug=Y||J||!1,Y!==J&&(Y?localStorage.setItem("debug","true"):X.has("debug")&&localStorage.removeItem("debug")),window._originalConsoleLog=console.log,console.log=function(...t){window.debug&&window._originalConsoleLog(...t)},window.debug){window._originalConsoleLog("%c🐛 Debug Mode Enabled","color: #00ff00; font-weight: bold; font-size: 16px"),window._originalConsoleLog("To disable: add ?debug=false to URL or run dashy.debug(false)");const t=document.createElement("div");t.id="debug-indicator",t.textContent="🐛 DEBUG",t.style.cssText="\n position: fixed;\n top: 10px;\n right: 10px;\n background: rgba(255, 0, 0, 0.8);\n color: white;\n padding: 5px 10px;\n border-radius: 5px;\n font-family: monospace;\n font-size: 12px;\n z-index: 10000;\n pointer-events: none;\n ",document.addEventListener("DOMContentLoaded",()=>{document.body.appendChild(t)})}window.dashy={debug:function(t=!0){return window.debug=t,t?(localStorage.setItem("debug","true"),window._originalConsoleLog("%c🐛 Debug Mode Enabled","color: #00ff00; font-weight: bold"),window._originalConsoleLog("Refresh page to see all debug logs")):(localStorage.removeItem("debug"),window._originalConsoleLog("%c🔇 Debug Mode Disabled","color: #ff0000; font-weight: bold")),t?"Debug enabled":"Debug disabled"},perf:function(){const t={fps:window._fps||0,frameTime:window._frameTime||0,drawCalls:window._drawCalls||0,canvasOps:window._canvasOps||0};return window._originalConsoleLog("%cPerformance Metrics","color: #00aaff; font-weight: bold"),window._originalConsoleLog(t),t},help:function(){window._originalConsoleLog("%cDashy Debug Commands:","color: #ffaa00; font-weight: bold; font-size: 14px"),window._originalConsoleLog("dashy.debug(true/false) - Enable/disable debug mode"),window._originalConsoleLog("dashy.perf() - Show performance metrics"),window._originalConsoleLog("dashy.help() - Show this help"),window._originalConsoleLog("\nURL Parameters:"),window._originalConsoleLog("?debug=true - Enable debug mode"),window._originalConsoleLog("?debug=false - Disable debug mode")}},function(){let t=0,e=null,i=0;function a(a,s,n){const r=document.getElementById("driving-page");if(!r||!r.classList.contains("active"))return;const o=window.innerWidth,l=window.innerHeight;if(a>o-100&&s>l-100){const r=Date.now();if(r-i<100)return void window._originalConsoleLog(`[Debug Toggle] Ignoring duplicate ${n} event`);if(i=r,t++,window._originalConsoleLog(`[Debug Toggle] ${n} count: ${t}/5 (at ${a},${s})`),e&&clearTimeout(e),e=setTimeout(()=>{t=0,window._originalConsoleLog("[Debug Toggle] Tap count reset to 0")},1e3),5===t){t=0;const e=!window.debug;window.dashy.debug(e);const i=document.createElement("div");i.textContent=e?"🐛 Debug Enabled":"🔇 Debug Disabled",i.style.cssText=`\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: ${e?"rgba(0, 255, 0, 0.9)":"rgba(255, 0, 0, 0.9)"};\n color: white;\n padding: 20px 40px;\n border-radius: 10px;\n font-size: 24px;\n font-weight: bold;\n z-index: 10001;\n pointer-events: none;\n `,document.body.appendChild(i),navigator.vibrate&&navigator.vibrate(e?[100,50,100]:[200]),setTimeout(()=>{i.remove(),location.reload()},1500)}}}document.addEventListener("touchstart",t=>{const e=t.touches[0];e.clientX>window.innerWidth-100&&e.clientY>window.innerHeight-100&&(a(e.clientX,e.clientY,"Touch"),t.preventDefault())},{passive:!1}),document.addEventListener("click",t=>{t.clientX>window.innerWidth-100&&t.clientY>window.innerHeight-100&&a(t.clientX,t.clientY,"Click")})}();const Z={sections:[{title:"Visual",settings:[{id:"HudModeEnabled",type:"toggle",label:"Heads-up Display (HUD) Mode",description:"Mirror the display for windshield projection.",defaultValue:!1,storage:"local"},{id:"ShowVideoInHud",type:"toggle",label:"Show Video",description:"Show or hide video stream in HUD mode.",defaultValue:!0,storage:"local",indent:!0,dependsOn:"HudModeEnabled"}]}]},K={events:{},on(t,e){return this.events[t]||(this.events[t]=[]),this.events[t].push(e),()=>this.off(t,e)},off(t,e){this.events[t]&&(this.events[t]=this.events[t].filter(t=>t!==e))},emit(t,e){this.events[t]&&this.events[t].forEach(t=>t(e))}},Q={_data:{settings:{},localSettings:{},ui:{currentPage:"files",navBarVisible:!1,hudModeEnabled:!1,showVideoInHud:!0},files:{currentPath:"/",items:[]}},get(t){return t.split(".").reduce((t,e)=>t?.[e],this._data)},set(t,e){const i=t.split("."),a=i.pop(),s=i.reduce((t,e)=>(t[e]||(t[e]={}),t[e]),this._data),n=s[a];n!==e&&(s[a]=e,K.emit("state:change",{path:t,value:e,oldValue:n}),K.emit(`state:${t}`,e))},subscribe:(t,e)=>K.on(`state:${t}`,e)},tt={fileRow:(t,e)=>{const i=t.is_dir?"📁":t.name.endsWith(".ts")?"🎞️":"📄",a=`${e}/${t.name}`.replace("//","/");return`\n <tr>\n <td><span class="file-icon">${i}</span>${t.name.endsWith(".ts")?`<a href="/api/play?file=${encodeURIComponent(a)}" target="_blank" class="play-button">▶</a>`:""}</td>\n <td>${t.is_dir?`<a href="#" data-path="${a}">${t.name}</a>`:`<a href="/download/${encodeURIComponent(a.startsWith("/")?a.substring(1):a)}" target="_blank" download="${t.name}">${t.name}</a>`}</td>\n <td>${t.mtime}</td>\n <td class="file-size" style="text-align:right;">${t.is_dir?"-":Dt(t.size)}</td>\n </tr>\n `},breadcrumb:t=>{const e=t.split("/").filter(Boolean);let i="";const a=['<a href="#" data-path="/">root</a>'];return e.forEach((t,s)=>{i+="/"+t,s===e.length-1?a.push(t):a.push(`<a href="#" data-path="${i}">${t}</a>`)}),a.join(" / ")},alert:(t,e,i="normal")=>`\n <div class="alert ${{small:"alert-small",normal:"alert-normal",full:"alert-full"}[i]}">\n <h2>${t}</h2>\n ${e?`<p>${e}</p>`:""}\n </div>\n `};function et(t,...e){const i=t(...e),a=document.createElement("div");return a.innerHTML=i,a.firstElementChild||a.childNodes}function it(t,e,i={}){const a=document.createElement(t);return e&&(a.className=e),Object.entries(i).forEach(([t,e])=>{"dataset"===t?Object.entries(e).forEach(([t,e])=>{a.dataset[t]=e}):a[t]=e}),a}function at(t){const e=it("label","toggle-switch"),i=it("input","",{type:"checkbox",dataset:{param:t.id}});t.storage&&(i.dataset.storage=t.storage),t.defaultValue&&(i.checked=!0);const a=it("span","toggle-slider");return e.appendChild(i),e.appendChild(a),e}function st(t){const e=it("div","setting-control stepper",{dataset:{param:t.id}});void 0!==t.min&&(e.dataset.min=t.min),void 0!==t.max&&(e.dataset.max=t.max);const i=it("button","",{dataset:{step:-t.step},textContent:"-"}),a=it("span","stepper-value",{textContent:t.defaultValue}),s=it("button","",{dataset:{step:t.step},textContent:"+"});return e.appendChild(i),e.appendChild(a),e.appendChild(s),e}function nt(t){const e=it("div","setting-control btn-group",{dataset:{param:t.id}});return t.storage&&(e.dataset.storage=t.storage),t.options.forEach(i=>{const a=it("button","",{dataset:{value:i.value},textContent:i.text});i.value===t.defaultValue&&a.classList.add("active"),e.appendChild(a)}),e}function rt(t){return it("input","text-input",{type:"text",placeholder:t.placeholder||"",dataset:{param:t.id}})}function ot(t){const e=it("div","setting-item",{id:"ShowVideoInHud"===t.id?"show-video-setting":""});t.indent&&(e.style.marginLeft="2rem"),t.dependsOn&&(e.style.display="none");const i=it("div","setting-label"),a=it("p","",{textContent:t.label}),s=it("span","",{textContent:t.description});i.appendChild(a),i.appendChild(s);const n=it("div","setting-control");let r;switch(t.type){case"toggle":r=at(t);break;case"stepper":return r=st(t),e.appendChild(i),e.appendChild(r),e;case"btn-group":return r=nt(t),e.appendChild(i),e.appendChild(r),e;case"text-input":r=rt(t),n.appendChild(r)}return"toggle"===t.type&&n.appendChild(r),e.appendChild(i),e.appendChild(n),e}function lt(t,e){t.sections.forEach(t=>{const i=it("div","settings-category"),a=it("h2","",{textContent:t.title});i.appendChild(a),t.settings.forEach(t=>{i.appendChild(ot(t))}),e.appendChild(i)})}const dt=document.getElementById("app-container"),ht=document.getElementById("driving-page"),ct=document.getElementById("files-page"),_t=document.getElementById("settings-page"),gt=document.getElementById("local-settings-page"),ut=document.getElementById("nav-driving"),pt=document.getElementById("nav-files"),ft=document.getElementById("nav-settings"),wt=document.getElementById("nav-local-settings"),mt=document.getElementById("files-breadcrumbs"),bt=document.querySelector("#files-table tbody"),vt=document.getElementById("driving-page-content"),xt=document.getElementById("videoPlayer"),yt=document.getElementById("uiCanvas"),Ct=yt.getContext("2d");function St(){window.debug,document.addEventListener("click",t=>{if(t.target.matches("nav button")){const e=t.target.id.replace("nav-","");return void It(e)}if(t.target.matches(".stepper button")){const e=t.target.parentElement,i=e.querySelector(".stepper-value"),a=parseFloat(t.target.dataset.step),s=parseFloat(e.dataset.min)||-1/0,n=parseFloat(e.dataset.max)||1/0;let r=parseFloat(i.textContent)+a;String(a).includes(".")&&(r=parseFloat(r.toFixed(2))),i.textContent=Math.max(s,Math.min(n,r));const o=t.target.closest("[data-param]");return void(o&&"local"===o.dataset.storage&&Le(()=>{const t=o.dataset.param,e=Et(!0)[t];void 0!==e&&Mt(t,e)},50))}if(t.target.matches(".btn-group button")){t.target.parentElement.querySelectorAll("button").forEach(t=>t.classList.remove("active")),t.target.classList.add("active");const e=t.target.closest("[data-param]");return void(e&&"local"===e.dataset.storage&&Le(()=>{const t=e.dataset.param,i=Et(!0)[t];void 0!==i&&Mt(t,i)},50))}if(t.target.matches("#files-table a, #files-breadcrumbs a")){if(t.target.href&&t.target.href.includes("/api/play"))return;const e=t.target.dataset.path;return void(void 0!==e&&(t.preventDefault(),Bt(e)))}}),document.addEventListener("change",t=>{const e=t.target.closest("[data-param]");e&&"local"===e.dataset.storage&&Le(()=>{const t=e.dataset.param,i=Et(!0)[t];void 0!==i&&Mt(t,i)},50)}),document.addEventListener("input",t=>{const e=t.target.closest('[data-param][data-storage="local"]');e&&t.target.matches(".text-input")&&Mt(e.dataset.param,t.target.value)})}function Et(t=!0){window.debug;const e={},i=t?'[data-param][data-storage="local"]':'[data-param]:not([data-storage="local"])';return document.querySelectorAll(i).forEach(t=>{const i=t.dataset.param;t.matches('input[type="checkbox"]')?e[i]=t.checked:t.matches(".stepper")?e[i]=parseFloat(t.querySelector(".stepper-value").textContent):t.matches(".btn-group")?e[i]=t.querySelector("button.active").dataset.value:t.matches(".text-input")&&(e[i]=t.value)}),e}function Mt(t,e){window.debug;try{let i=JSON.parse(localStorage.getItem("dashySettings"))||{};i[t]=e,localStorage.setItem("dashySettings",JSON.stringify(i)),Pt(t,e)}catch(t){}}let Tt;function Lt(){const t=Q.get("ui.hudModeEnabled"),e=Q.get("ui.showVideoInHud");xt.style.display=t&&!e?"none":""}function Pt(t,e){window.debug,Q.set(`ui.${t.charAt(0).toLowerCase()+t.slice(1)}`,e)}function $t(){Q.subscribe("ui.theme",t=>{}),Q.subscribe("ui.hudModeEnabled",t=>{if(t){xt.classList.add("hud-mode");const t=document.getElementById("show-video-setting");t&&(t.style.display="")}else{xt.classList.remove("hud-mode");const t=document.getElementById("show-video-setting");t&&(t.style.display="none"),xt.style.display=""}Lt()}),Q.subscribe("ui.showVideoInHud",t=>{Lt()}),Q.subscribe("ui.navBarVisible",t=>{K.emit("nav:visibility",t)}),Q.subscribe("ui.currentPage",t=>{K.emit("page:change",t)})}function At(){window.debug;try{const t=JSON.parse(localStorage.getItem("dashySettings"))||{};for(const[e,i]of Object.entries(t)){const t=document.querySelector(`[data-param="${e}"][data-storage="local"]`);if(t)if(t.matches('input[type="checkbox"]'))t.checked=i;else if(t.matches(".stepper"))t.querySelector(".stepper-value").textContent=i;else if(t.matches(".btn-group")){t.querySelectorAll("button").forEach(t=>t.classList.remove("active"));const e=t.querySelector(`[data-value="${i}"]`);e&&e.classList.add("active")}else t.matches(".text-input")&&(t.value=i);Pt(e,i)}Q.get("ui.hudModeEnabled")?document.getElementById("show-video-setting").style.display="":document.getElementById("show-video-setting").style.display="none"}catch(t){}}function It(t){window.debug,document.querySelectorAll(".page").forEach(t=>t.classList.remove("active")),document.querySelectorAll("nav button").forEach(t=>t.classList.remove("active")),document.getElementById(`${t}-page`).classList.add("active"),document.getElementById(`nav-${t}`).classList.add("active"),"files"!==t||bt.hasChildNodes()||Bt("/"),"driving"===t?(Me.classList.remove("visible"),$e(Te)):(Me.classList.add("visible"),$e(Te))}function Dt(t){if(0===t)return"0 B";const e=Math.floor(Math.log(t)/Math.log(1024));return parseFloat((t/Math.pow(1024,e)).toFixed(1))+" "+["B","KB","MB","GB","TB"][e]}function Rt(t){if(window.debug,Nt===t)return;Nt=t;const e=1===t||"1"===t,i=2===t||"2"===t;ut&&(ut.style.display=i?"":"none"),wt&&(wt.style.display=i?"":"none"),pt&&(pt.style.display=""),ft&&(ft.style.display=e||i?"none":"");let a="files";Q.get("ui.currentPage")!==a&&It(a)}function kt(t,e){let i;return function(...a){clearTimeout(i),i=setTimeout(()=>{clearTimeout(i),t(...a)},e)}}const zt=new Map;async function Bt(t="/"){window.debug;const e=zt.get(t);e&&e.abort();const i=new AbortController;zt.set(t,i);try{bt.innerHTML='<tr><td colspan="4" style="text-align:center;">Loading...</td></tr>';const e=await fetch(`/api/files?path=${encodeURIComponent(t)}`,{signal:i.signal});if(!e.ok)throw new Error(`Server error: ${e.statusText}`);const a=await e.json();Ft(a.path),Gt(a.path,a.files)}catch(t){if("AbortError"===t.name)return;bt.innerHTML=`<tr><td colspan="4" style="text-align:center;color:#e53e3e;">Failed to load files: ${t.message}</td></tr>`}finally{zt.delete(t)}}function Ft(t){mt.innerHTML="<strong>Path:</strong> "+tt.breadcrumb(t),Q.set("files.currentPath",t)}function Gt(t,e){Q.set("files.items",e);let i="";if(t){const e=t.substring(0,t.lastIndexOf("/"))||"/";i+=`<tr><td><span class="file-icon">⤴️</span></td><td><a href="#" data-path="${e}">.. (Parent Directory)</a></td><td></td><td></td></tr>`}e.forEach(e=>{i+=tt.fileRow(e,t)}),bt.innerHTML=i}let Vt,Ot,jt=!1,Nt=null,Ht=null,Wt=null;const qt={modelV2:null,liveCalibration:null,longitudinalPlan:null,radarState:null,selfdriveState:null,deviceState:null,carState:null,controlsState:null,roadCameraState:null,driverStateV2:null,driverMonitoringState:null,recv_frame:{},recv_time:{},updated:{},valid:{},seen:{},frame:0};let Ut=0,Xt=0,Yt=0,Jt=0,Zt=0;const Kt={sm:qt,started_frame:0,is_metric:!1,status:"DISENGAGED",engaged:!1},Qt={textContent:""},te={style:{backgroundColor:""}},ee=20,ie=50;let ae=0,se=0,ne=0,re=!1;function oe(){window.debug;const{width:t,height:e}=vt.getBoundingClientRect();t>0&&(yt.width!==t||yt.height!==e)&&(yt.width=t,yt.height=e,Wt&&Wt.invalidate_transform_cache(),!Wt&&yt.width>0&&yt.height>0&&(Wt=new U,Wt.set_callbacks(()=>{qt.selfdriveState&&(qt.selfdriveState.experimentalMode=!qt.selfdriveState.experimentalMode)}),requestAnimationFrame(Fe)))}const le=new ResizeObserver(()=>oe());function de(){window.debug,!Wt&&yt.width>0&&yt.height>0&&(Wt=new U,Wt.set_callbacks(()=>{qt.selfdriveState&&(qt.selfdriveState.experimentalMode=!qt.selfdriveState.experimentalMode)}),requestAnimationFrame(Fe))}function he(t,e){window.debug,Qt.textContent=t;const i={green:"#48bb78",yellow:"#f6e05e",red:"#f56565"};te.style.backgroundColor=i[e]||i.red,we=!0,$e(Tt),Tt=Le(()=>{Qt.textContent="",we=!0},5e3)}le.observe(vt),window.addEventListener("orientationchange",()=>oe());let ce=0,_e=[];const ge=500;function ue(){window.debug;const t=Date.now();if(t-ce<ge)return _e;ce=t,window.debug;const e=[];return Yt>0&&t-Yt>2e3&&e.push({text:"Model data stale",severity:"warning"}),Jt>0&&t-Jt>2e3&&e.push({text:"Car state stale",severity:"warning"}),Zt>0&&t-Zt>3e3&&e.push({text:"Selfdrive state stale",severity:"critical"}),_e=e,e}let pe="",fe="",we=!0,me=null,be=null,ve=null;function xe(){window.debug;const t=Qt.textContent,e=te.style.backgroundColor;t===pe&&e===fe||(we=!0,pe=t,fe=e);const i=ue();if(i.length>0&&!t){const t=i.find(t=>"critical"===t.severity)||i[0];return void he(t.text,"critical"===t.severity?"red":"yellow")}if(t){if(ve&&ve.width===yt.width&&ve.height===yt.height||(we=!0,ve={width:yt.width,height:yt.height}),we){we=!1;const i=12,a=4,s=12;Ct.font=`${s}px Arial`;const n=Ct.measureText(t).width+2*i+6+4,r=s+2*a;me&&me.width===n&&me.height===r||(me=document.createElement("canvas"),me.width=n,me.height=r,be=me.getContext("2d")),be.clearRect(0,0,n,r);const o=5;be.fillStyle="rgba(0, 0, 0, 0.7)",be.beginPath(),be.roundRect(0,0,n,r,o),be.fill();const l=3,d=i+l,h=r/2;be.fillStyle=e,be.beginPath(),be.arc(d,h,l,0,2*Math.PI),be.fill(),be.fillStyle="white",be.font=`${s}px Arial`,be.textAlign="left",be.textBaseline="middle";const c=d+l+4;be.fillText(t,c,h)}if(me){const t=15,e=yt.width/2-me.width/2,i=yt.height-me.height-t;Ct.drawImage(me,e,i)}}}function ye(t){window.debug;const e=t.split("\r\n");let i=-1;const a=[];for(let t=0;t<e.length;t++)if(e[t].startsWith("m=video"))i=t;else if(e[t].includes("H264/90000")){const i=e[t].match(/a=rtpmap:(\d+) H264\/90000/);i&&i[1]&&a.push(i[1])}if(-1===i||0===a.length)return t;const s=[];for(const t of a)for(let i=0;i<e.length;i++)if(e[i].includes(`apt=${t}`)&&e[i].includes("rtx/90000")){const t=e[i].match(/a=rtpmap:(\d+) rtx\/90000/);t&&t[1]&&s.push(t[1])}const n=[...a,...s],r=e[i].split(" "),o=r.slice(3).filter(t=>!n.includes(t)),l=r.slice(0,3).concat(n,o).join(" ");return e[i]=l,e.join("\r\n")}const Ce=new Set,Se=new Set;let Ee,Me,Te;function Le(t,e){const i=setTimeout(()=>{t(),Ce.delete(i)},e);return Ce.add(i),i}function Pe(t,e){const i=setInterval(t,e);return Se.add(i),i}function $e(t){clearTimeout(t),Ce.delete(t)}function Ae(t){clearInterval(t),Se.delete(t)}function Ie(){window.debug,Ce.forEach(t=>clearTimeout(t)),Ce.clear(),Se.forEach(t=>clearInterval(t)),Se.clear(),clearTimeout(Ee),clearInterval(Ht),Ht=null,Vt&&(Vt.close(),Vt=null),Ot&&(Ot.close(),Ot=null),Wt=null,window.animationFrameId&&cancelAnimationFrame(window.animationFrameId)}function De(){window.debug;const t=window.location.hostname;Ie(),ht.classList.contains("active")||2===Nt&&It("driving"),ke(t),de()}async function Re(){window.debug;const t=window.location.hostname;he(`Connecting to ${t}...`,"yellow");try{const e=await fetch(`http://${t}:5088/api/init`,{method:"GET",signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Connection failed: ${e.statusText}`);{const i=await e.json();jt=i.is_metric,Kt.is_metric=jt,void 0!==i.dp_dev_dashy?(Rt(i.dp_dev_dashy),2===i.dp_dev_dashy&&ke(t)):Rt(null)}}catch(e){he(`Could not connect to device at ${t}.`,"red")}}async function ke(t){window.debug,he("Initializing WebRTC...","yellow"),clearTimeout(Ee),lastDataReceivedTime=Date.now(),Vt&&(Vt.close(),Vt=null);try{Vt=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]});Be(Vt.createDataChannel("data")),Vt.ontrack=t=>{"video"===t.track.kind&&(xt.srcObject=t.streams[0],oe(),he("Video connected","green"),xt.onplaying=()=>{clearTimeout(Ee)})},Vt.onconnectionstatechange=()=>{"failed"===Vt.connectionState||"disconnected"===Vt.connectionState||"closed"===Vt.connectionState?(he("WebRTC Error: Could not connect to server (server may be down or inaccessible)","red"),"driving"===Q.get("ui.currentPage")&&(Ee=Le(()=>De(),5e3))):"connected"===Vt.connectionState&&(he("WebRTC Connected","green"),clearTimeout(Ee))},Vt.addTransceiver("video",{direction:"recvonly"});const e=await Vt.createOffer();e.sdp=ye(e.sdp),await Vt.setLocalDescription(e);const i=await fetch(`http://${t}:5001/stream`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sdp:Vt.localDescription.sdp,cameras:["road"],bridge_services_in:[],bridge_services_out:["modelV2","liveCalibration","longitudinalPlan","radarState","selfdriveState","deviceState","carState","controlsState"]})});if(!i.ok){const t=await i.text();throw new Error(`HTTP Error! Status: ${i.status}, Message: ${t}`)}const a=await i.json();await Vt.setRemoteDescription(new RTCSessionDescription(a)),ze()}catch(t){let e=`WebRTC Error: ${t.message}`;t.message.includes("Failed to fetch")&&(e="WebRTC Error: Could not connect to server (server may be down or inaccessible)"),he(e,"red"),t.message.includes("Failed to fetch")&&"driving"===Q.get("ui.currentPage")&&(Ee=setTimeout(()=>De(),5e3))}}function ze(){window.debug,Ae(Ht),lastDataReceivedTime=Date.now(),lastVideoPlaybackTime=xt.currentTime,Ht=Pe(()=>{let t=!1,e="";Date.now()-lastDataReceivedTime>5e3&&(t=!0,e="Data stream stalled"),t||xt.paused||xt.currentTime!==lastVideoPlaybackTime||(t=!0,e="Video stream stalled"),t&&(he(`${e}. Reconnecting...`,"yellow"),De()),lastVideoPlaybackTime=xt.currentTime},2e3)}function Be(t){window.debug;const e=new TextDecoder("utf-8");let i="";function a(t){try{const e=JSON.parse(t.replace(/\bNaN\b/g,"null"));lastDataReceivedTime=Date.now();const i=Date.now();e.data&&void 0!==e.data.dp_dev_dashy&&Rt(e.data.dp_dev_dashy);const a=e.type;qt[a]=e.data,qt.recv_frame[a]=qt.frame,qt.recv_time[a]=i/1e3,qt.updated[a]=!0,qt.valid[a]=!0,qt.seen[a]=!0,"selfdriveState"===a?(Zt=i,0===Ut&&(Ut=i,Xt=qt.frame)):"modelV2"===a?Yt=i:"carState"===a&&(Jt=i),qt.frame++}catch(t){}}t.onmessage=t=>{const s="string"==typeof t.data?t.data:e.decode(t.data,{stream:!0});let n;for(i+=s;-1!==(n=i.indexOf("}{"));){a(i.substring(0,n+1)),i=i.substring(n+1)}i.length>0&&(a(i),i="")}}function Fe(t){window.animationFrameId=requestAnimationFrame(Fe);const e=t-ae;if(!(e<ie)&&(ae=t-e%ie,ht.classList.contains("active"))){if(window.debug,Ct.save(),Ct.clearRect(0,0,yt.width,yt.height),Q.get("ui.hudModeEnabled")&&(Ct.translate(0,yt.height),Ct.scale(1,-1)),Kt.is_metric=jt,Kt.frame=qt.frame,Kt.started_frame=Xt,qt.selfdriveState&&(Kt.engaged=qt.selfdriveState.enabled||!1,qt.selfdriveState.enabled?Kt.status="ENGAGED":qt.selfdriveState.activeOverride?Kt.status="OVERRIDE":Kt.status="DISENGAGED"),Wt){const t={x:0,y:0,width:yt.width,height:yt.height};Wt.render(t,qt,Ct)}xe(),Ct.restore();for(const t in qt.updated)qt.updated[t]=!1}}function It(t){window.debug;const e=Q.get("ui.currentPage");Q.set("ui.currentPage",t),document.querySelectorAll(".page").forEach(t=>t.classList.remove("active")),document.querySelectorAll("nav button").forEach(t=>t.classList.remove("active")),document.getElementById(`${t}-page`).classList.add("active"),document.getElementById(`nav-${t}`).classList.add("active"),"files"!==t||bt.hasChildNodes()||Bt("/"),"driving"===e&&"driving"!==t&&Ie(),"driving"===t&&"driving"!==e&&De(),"driving"===t?(Me.classList.remove("visible"),Q.set("ui.navBarVisible",!1),vt.addEventListener("click",Ge)):(Me.classList.add("visible"),Q.set("ui.navBarVisible",!0),vt.removeEventListener("click",Ge),clearTimeout(Te))}function Ge(){Me||(Me=document.querySelector("nav")),Me.classList.toggle("visible"),Me.classList.contains("visible")?($e(Te),Te=Le(()=>{Me.classList.remove("visible")},3e3)):$e(Te)}async function Ve(){window.debug;document.getElementById("settings-content");const t=document.getElementById("local-settings-content");lt(Z,t),Me=document.querySelector("nav"),ut&&(ut.style.display="none"),wt&&(wt.style.display="none"),St();document.getElementById("driving-page-content").addEventListener("click",t=>{ht.classList.contains("active")&&Ge()}),xt.addEventListener("click",t=>{ht.classList.contains("active")&&Ge()}),yt.addEventListener("click",t=>{ht.classList.contains("active")&&Ge()}),$t(),At(),await Re(),It("files"),de(),xt.onstalled=()=>{"driving"===Q.get("ui.currentPage")&&De()},xt.onplaying=()=>{clearTimeout(Ee)}}window.addEventListener("beforeunload",Ie),window.addEventListener("unload",Ie),Ve();