1'use strict'; 2(() => { 3 const tr=(a,pt,en)=>a.state.lang==='pt'?pt:en; 4 const esc=(a,value)=>a.esc(value??''); 5 const canEdit=(a,data)=>data?.can_edit===true&&!a.readOnlySeason?.(); 6 const managed=data=>Boolean(data.sync?.configured&&data.sync?.enabled); 7 let presentationLibrary; 8 function localURL(value){try{if(!value)return '';const base=new URL('.',document.baseURI),url=new URL(value,base);return url.origin===base.origin&&url.pathname.startsWith(base.pathname)&&!url.username&&!url.password?url.href:'';}catch{return '';}} 9 function onshapeLink(a,value,label){try{const url=new URL(value);if(url.protocol!=='https:'||url.username||url.password||!['cad.onshape.com','forum.onshape.com','www.onshape.com','onshape.com'].includes(url.hostname))return '';return '<a class="button" href="'+esc(a,url.href)+'" target="_blank" rel="noopener noreferrer">'+a.icon('external')+esc(a,label)+'</a>';}catch{return '';}} 10 function modelStats(a,model){const stats=model?.stats||{},points=model?.presentation?.points,format=value=>new Intl.NumberFormat(a.state.lang==='pt'?'pt-BR':'en').format(Number(value)),rows=[['vertices',tr(a,'Vértices','Vertices')],['triangles',tr(a,'Triângulos','Triangles')],['meshes',tr(a,'Malhas','Meshes')]].filter(([key])=>stats[key]!==null&&stats[key]!==undefined&&Number.isFinite(Number(stats[key]))).map(([key,label])=>[label,stats[key]]);if(points!==null&&points!==undefined&&Number.isFinite(Number(points)))rows.unshift([tr(a,'Pontos','Points'),points]);if(!rows.length)return '';return '<dl class="engineering-model-stats">'+rows.map(([label,value])=>'<div><dt>'+label+'</dt><dd>'+format(value)+'</dd></div>').join('')+'</dl>';} 11 function uploadButton(a,data){return canEdit(a,data)&&data.robot_id&&!managed(data)?'<button type="button" class="button primary" data-engineering-upload>'+a.icon('plus')+tr(a,'Enviar nova versão','Upload new version')+'</button>':'';} 12 function shareButton(a,data){return canEdit(a,data)&&data.robot_id&&data.model?.presentation?.state==='ready'?'<button type="button" class="button" data-robot-share>'+a.icon('share')+tr(a,'Compartilhar robô','Share robot')+'</button>':'';} 13 function renderConfiguration(a,data,robotOnly=false){ 14 const model=data.model,presentation=localURL(model?.presentation?.url);const heading=robotOnly?(canEdit(a,data)?'<div class="engineering-catalog-actions"><button type="button" class="button" data-engineering-edit>'+a.icon('edit')+a.t('edit')+'</button>'+(!managed(data)?'<button type="button" class="button" data-engineering-upload>'+a.icon('plus')+tr(a,'Enviar nova versão','Upload new version')+'</button>':'')+'</div>':''):a.pageHeading('engineering','engineeringSubtitle',(window.AltaVoltagemResponsibilities?.slot(a,'engineering')||'')+(canEdit(a,data)?(data.robot_registry_available?'<button type="button" class="button primary" data-robot-new>'+a.icon('plus')+tr(a,'Novo robô','New robot')+'</button>':'')+(data.robot_id||!data.robot_registry_available?'<button type="button" class="button" data-engineering-edit>'+a.icon('edit')+a.t('edit')+'</button>'+(!managed(data)?'<button type="button" class="button" data-engineering-upload>'+a.icon('plus')+tr(a,'Enviar nova versão','Upload new version')+'</button>':''):''):''));return heading+'<section class="panel"><div class="panel-head engineering-panel-head"><h2>'+esc(a,data.title||tr(a,'Robô da temporada','Season robot'))+'</h2>'+modelStats(a,model)+shareButton(a,data)+'</div><div class="panel-body">'+(managed(data)?'<p class="notice">'+tr(a,'Execute a manutenção desta atualização para habilitar a publicação manual.','Run this updateâs maintenance to enable manual publishing.')+'</p>':'')+versionCaption(a,data)+(data.description?'<p class="detail-copy">'+esc(a,data.description)+'</p>':'')+presentationControls(a,model,presentation)+'<div class="engineering-viewer engineering-points-surface" data-engineering-viewer aria-busy="'+String(!!presentation)+'">'+(presentation?'<div data-model-status role="status">'+a.loading()+'</div><canvas data-presentation-canvas aria-label="'+esc(a,data.title||tr(a,'Apresentação 3D do robô','3D robot presentation'))+'"></canvas><span class="engineering-pivot-cross" data-pivot-cross role="img" aria-label="'+tr(a,'Centro de giro','Rotation center')+'">+</span>':'<div data-presentation-unavailable role="status" class="engineering-presentation-message">'+(model?.presentation?.state==='pending'?tr(a,'A apresentação está em preparação.','The presentation is being prepared.'):model?.presentation?.state==='unavailable'?tr(a,'A apresentação não está disponÃvel para este arquivo.','The presentation is unavailable for this file.'):tr(a,'A apresentação do robô ainda não está disponÃvel.','The robot presentation is not available yet.'))+'</div>')+'</div>'+(presentation?'<div class="engineering-viewer-tools"><p class="muted detail-copy">'+tr(a,'Escolha uma vista para posicionar o centro. Libere o giro para arrastar; use a roda do mouse para aproximar.','Choose a view to position the center. Unlock rotation to drag; use the mouse wheel to zoom.')+'</p><button type="button" class="button small" data-presentation-fullscreen>'+tr(a,'Tela cheia','Full screen')+'</button></div>
14':'')+(presentation?cameraControls(a):'')+viewControls(a,data)+versionsHTML(a,data)+'<div class="inline-fields section-gap">'+onshapeLink(a,data.onshape_document_url,tr(a,'Abrir documento no Onshape','Open Onshape document'))+onshapeLink(a,data.onshape_community_url,tr(a,'Comunidade Onshape','Onshape community'))+'</div><p class="muted detail-copy section-gap">'+tr(a,'A equipe vê a última versão publicada. Alterações no Onshape aparecem aqui depois de um novo envio.','The team sees the latest version. Changes in Onshape appear here after a new upload.')+'</p></div></section>'+(robotOnly?'':robotCards(a,data)+'<div data-resource-links="engineering"></div>'); 15 } 16 function presentationControls(a,model,url){ 17 if(!model||url||model.presentation?.state!=='pending'&&model.presentation?.state!=='unavailable')return ''; 18 if(model.presentation?.state==='pending')return '<p class="muted detail-copy">'+tr(a,'A apresentação está em preparação.','The presentation is being prepared.')+'</p>'; 19 if(model.presentation?.state==='unavailable')return '<p class="muted detail-copy">'+tr(a,'A apresentação não está disponÃvel para este arquivo.','The presentation is unavailable for this file.')+'</p>'; 20 return ''; 21 } 22 function cameraControls(a){return '<div class="engineering-camera-controls"><div class="engineering-camera-presets" role="group" aria-label="'+tr(a,'Vistas do robô','Robot views')+'">'+[['front','Frente','Front'],['top','Topo','Top'],['left','Esquerda','Left'],['right','Direita','Right'],['back','Trás','Back'],['bottom','Base','Bottom']].map(([view,pt,en])=>'<button type="button" class="button small" data-camera-view="'+view+'" aria-pressed="'+String(view==='front')+'">'+tr(a,pt,en)+'</button>').join('')+'</div><button type="button" class="button small" data-camera-unlock aria-pressed="false">'+tr(a,'Liberar giro','Unlock rotation')+'</button></div>';} 23 function pivotControls(a,settings){ 24 const cornerChoices=[['50,50,50',tr(a,'Centro do robô','Robot center')],...Array.from({length:8},(_,i)=>[[i&1?100:0,i&2?100:0,i&4?100:0].join(','),tr(a,'Canto ','Corner ')+(i+1)])]; 25 return '<fieldset class="engineering-pivot"><legend>'+tr(a,'Centro de giro','Rotation center')+'</legend><p class="muted">'+tr(a,'Escolha o centro ou um canto e ajuste os três eixos. A cruz amarela marca o ponto de giro na prévia.','Choose the center or a corner and adjust the three axes. The yellow cross marks the pivot in the preview.')+'</p><label>'+tr(a,'Ponto inicial','Starting point')+'<select data-pivot-preset><option value="">'+tr(a,'Personalizado','Custom')+'</option>'+cornerChoices.map(([value,label])=>'<option value="'+value+'">'+label+'</option>').join('')+'</select></label><div class="engineering-pivot-axes">'+['x','y','z'].map(key=>'<label>'+key.toUpperCase()+' (%)<input type="number" min="0" max="100" step="1" data-pivot-axis="'+key+'" value="'+Number(settings.pivot?.[key]??50)+'"></label>').join('')+'</div></fieldset>'; 26 } 27 function xrayControls(a,settings){ 28 return '<fieldset class="engineering-xray"><legend>'+tr(a,'Raio-X através das paredes','X-ray through walls')+'</legend><p class="muted">'+tr(a,'Use o mesmo grupo de raio-X nas peças de uma roda mecanum e grupos diferentes para cada roda. As peças do mesmo grupo não escondem umas à s outras. Escolha quais componentes aparecem atrás das paredes. Clique no robô ou use Localizar para destacar um grupo em amarelo; a numeração corresponde aos grupos do arquivo enviado.','Choose components visible behind walls. Click the robot or use Locate to highlight a group in yellow; numbers identify groups in the uploaded file.')+'</p><label>'+tr(a,'Visibilidade interna','Internal visibility')+'<select data-xray-mode>'+[['none','Ocultar tudo atrás das paredes','Hide everything behind walls'],['manual','Escolher componentes','Choose components'],['auto','Automático','Automatic']].map(([id,pt,en])=>'<option value="'+id+'" '+((settings.xray_mode||'auto')===id?'selected':'')+'>'+tr(a,pt,en)+'</option>').join('')+'</select></label><button type="button" class="button small" data-xray-clear>'+tr(a,'Limpar destaque','Clear highlight')+'</button><div data-xray-components style="max-height:250px;overflow:auto;margin-top:12px">'+tr(a,'Carregando componentesâ¦','Loading componentsâ¦')+'</div></fieldset>'; 29 }
30 function viewControls(a,data){ 31 const settings=data.view_settings||{},editor=canEdit(a,data)&&data.robot_id; 32 return editor?'<details class="engineering-orientation" open><summary>'+tr(a,'Orientação e centro de giro','Orientation and rotation center')+'</summary><p class="muted">'+tr(a,'Ajuste a orientação e o ponto de giro; a prévia de partÃculas será atualizada antes de salvar.','Adjust orientation and rotation point; the particle preview updates before saving.')+'</p><div class="inline-fields"><label>'+tr(a,'Eixo de cima','Up axis')+'<select class="filter-input" data-model-top>'+['+Y','-Y','+Z','-Z','+X','-X'].map(axis=>'<option value="'+axis+'" '+((settings.up_axis||'+Y')===axis?'selected':'')+'>'+axis+'</option>').join('')+'</select></label><label>'+tr(a,'Direção da frente (graus)','Front direction (degrees)')+'<input class="filter-input" data-model-heading type="number" min="0" max="359" step="1" value="'+(Number(settings.heading)||0)+'"></label></div>'+pivotControls(a,settings)+xrayControls(a,settings)+'<label class="engineering-initial-zoom">'+tr(a,'Zoom inicial','Initial zoom')+' <output data-model-zoom-label>'+Number(settings.zoom??100)+'%</output><input type="range" min="50" max="160" step="1" data-model-zoom value="'+Number(settings.zoom??100)+'"></label><div class="section-gap"><button type="button" class="button small" data-model-snapshot>Usar visão atual como thumbnail</button><div data-model-snapshot-preview></div></div><button type="button" class="button small primary" data-model-view-save>'+tr(a,'Salvar visualização','Save view')+'</button></details>':''; 33 } 34 function robotCards(a,data){ 35 if(!data.robot_registry_available)return ''; 36 return '<section class="section-gap"><h2>'+tr(a,'Robôs da equipe','Team robots')+'</h2><div class="resource-grid">'+(data.robots||[]).map(robot=>{const preview=robot.view_settings?.snapshot, image=preview?.model===robot.model?.sha256&&/^data:image\/webp;base64,[A-Za-z0-9+/=]+$/.test(preview?.data||'')?preview.data:'';return '<article class="resource-card engineering-robot-card '+(robot.id===data.robot_id?'is-selected':'')+'"><button type="button" class="engineering-robot-preview" data-robot-select="'+robot.id+'" aria-label="'+esc(a,robot.title)+'"><span class="engineering-robot-name">'+esc(a,robot.title)+'</span>'+(image?'<img class="engineering-robot-snapshot" src="'+esc(a,image)+'" alt="'+esc(a,robot.title)+'" loading="lazy">':'<span class="engineering-robot-preview-empty">'+tr(a,'Prévia indisponÃvel','Preview unavailable')+'</span>')+'</button>'+(canEdit(a,data)&&!managed(data)?'<button type="button" class="button small" data-robot-upload="'+robot.id+'">'+tr(a,'Enviar nova versão','Upload new version')+'</button>':'')+(robot.is_primary?'<span class="library-featured-badge">â '+tr(a,'Robô atual','Current robot')+'</span>':'')+'</article>';}).join('')+'</div></section>'; 37 } 38 function openRobotActions(a,data,robot){ 39 const editable=canEdit(a,data),season=a.state.seasonId,context=a.readContextKey?.(),current=()=>a.state.seasonId===season&&(context===undefined||context===a.readContextKey?.()),selected={...data,...robot,robot_id:robot.id,robots:data.robots},initial=JSON.stringify(robot.view_settings||{}); 40 const footer='<button type="button" class="button" data-dialog-close>
40'+tr(a,'Voltar à tela','Back to screen')+'</button>'+(editable&&!robot.is_primary?'<button type="button" class="button primary" data-robot-action-primary>'+tr(a,'Tornar robô atual','Make current robot')+'</button>':''); 41 const dialog=a.openDialog(robot.title,'<div data-engineering-configuration></div>',footer);dialog.classList.add('engineering-configuration-dialog');dialog.dataset.removalType='engineering_robot';dialog.dataset.removalId=String(robot.id); 42 const host=dialog.querySelector('[data-engineering-configuration]');host.innerHTML=renderConfiguration(a,selected,true);bindConfiguration(a,host,selected);window.AltaVoltagemContributions?.mount(a,host,{page:'engineering',robotId:robot.id}); 43 dialog.addEventListener('close',()=>{if(current()&&JSON.stringify(robot.view_settings||{})!==initial)a.loadPage();},{once:true}); 44 dialog.querySelector('[data-robot-action-primary]')?.addEventListener('click',async event=>{if(!editable||!canEdit(a,data)||!current())return;const button=event.currentTarget;button.disabled=true;try{await a.api('engineering.robot.primary',{season_id:season,id:robot.id,version:robot.version},'POST');if(a.state.seasonId!==season||context!==undefined&&context!==a.readContextKey?.())return;dialog.close();await a.loadPage();}catch(error){a.toast(error.message,true);}finally{if(button.isConnected)button.disabled=false;}}); 45 } 46 function exportGuide(a){ 47 return '<details class="section-gap"><summary>'+tr(a,'Como exportar o modelo no Onshape','How to export the model from Onshape')+'</summary><ol><li>'+tr(a,'Clique com o botão direito na aba da montagem completa (Assembly) e escolha Export.','Right-click the complete Assembly tab and choose Export.')+'</li><li>'+tr(a,'Format: GLB (.glb).','Format: GLB (.glb).')+'</li><li>'+tr(a,'Resolution: Coarse. Deixe Compress desmarcado.','Resolution: Coarse. Leave Compress unchecked.')+'</li><li>'+tr(a,'Exporte em um único arquivo; desmarque Export unique parts as individual files, se aparecer.','Export as one file; uncheck Export unique parts as individual files, if shown.')+'</li><li>'+tr(a,'Options: Download. Envie o arquivo .glb abaixo (até 1 GiB).','Options: Download. Upload the .glb file below (up to 1 GiB).')+'</li></ol><p class="muted">'+tr(a,'Se ultrapassar o tamanho ou os detalhes aceitos, reduza a complexidade da montagem antes de exportar. O arquivo é validado antes de substituir a versão atual.','If size or detail limits are exceeded, reduce assembly complexity before exporting. The file is validated before replacing the current version.')+'</p><a href="https://cad.onshape.com/help/Content/File/exporting_files.htm" target="_blank" rel="noopener noreferrer">'+tr(a,'Instruções do Onshape','Onshape instructions')+'</a></details>'; 48 } 49 function selectedVersion(data){return (data.model_versions||[]).find(item=>data.selected_model_version?item.id===data.selected_model_version:item.current);} 50 function versionCaption(a,data){const item=selectedVersion(data);return item?'<div class="section-gap"><h3>'+esc(a,item.label||item.model.filename)+'</h3><p class="muted">'+esc(a,item.author)+' · '+esc(a,item.created_at)+' UTC'+(item.current?' · '+tr(a,'Versão atual','Current version'):'')+'</p>'+(item.notes?'<p class="detail-copy">'+esc(a,item.notes)+'</p>':'')+'</div>':'';} 51 function versionsHTML(a,data){ 52 const items=data.model_versions||[];if(!items.length)return ''; 53 const selected=selectedVersion(data); 54 return '<section class="section-gap"><h3>'+tr(a,'Versões publicadas','Published versions')+'</h3><ul class="engineering-versions">'+items.map(item=>'<li><div><strong>'+esc(a,item.label||item.model.filename)+'</strong><p class="muted">'+esc(a,item.author)+' · '+esc(a,item.created_at)+' UTC · '+new Intl.NumberFormat(a.state.lang==='pt'?'pt-BR':'en',{maximumFractionDigits:1}).format(item.model.size_bytes/1048576)+' MiB'+(item.current?' · '+tr(a,'Atual','Current'):'')+'</p>'+(item.notes?'<p class="detail-copy">'+esc(a,item.notes)+'</p>':'')+'</div><button type="button" class="button small" data-engineering-version="'+item.id+'" '+(selected?.id===item.id?'disabled':'')+'>'+tr(a,selected?.id===item.id?'Em visualização':'Visualizar',selected?.id===item.id?'Viewing':'View')+'</button></li>').join('')+'</ul></section>'; 55 } 56 async function bindConfiguration(a,container,data){ 57 const viewer=container.querySelector('[data-engineering-viewer]'),body=viewer?.parentElement; 58 if(container.closest('.engineering-configuration-dialog')&&body&&!body.classList.contains('engineering-editor-split')){ 59 const preview=document.createElement('div'),controls=document.createElement('div');preview.className='engineering-editor-preview';
59controls.className='engineering-editor-controls';controls.tabIndex=0;controls.setAttribute('role','region');controls.setAttribute('aria-label',tr(a,'Configurações do robô','Robot settings')); 60 for(const child of [...body.children]){if(child===viewer||child.matches('.engineering-viewer-tools,.engineering-camera-controls'))preview.append(child);else controls.append(child);} 61 body.classList.add('engineering-editor-split');body.append(preview,controls); 62 } 63 container.querySelector('[data-robot-share]')?.addEventListener('click',()=>window.AltaVoltagemRobotSharing?.open(a,data)); 64 window.AltaVoltagemResourceLinks?.mount(a,container.querySelector('[data-resource-links="engineering"]'),'engineering'); 65 66 container.querySelector('[data-robot-new]')?.addEventListener('click',()=>editRobot(a,data)); 67 container.querySelectorAll('[data-robot-upload]').forEach(button=>button.onclick=()=>{const robot=data.robots.find(robot=>robot.id===Number(button.dataset.robotUpload));if(robot)upload(a,{...data,...robot,robot_id:robot.id});}); 68 container.querySelectorAll('[data-robot-select]').forEach(button=>button.onclick=()=>{const robot=data.robots.find(robot=>robot.id===Number(button.dataset.robotSelect));if(robot)openRobotActions(a,data,robot);}); 69 70 container.querySelectorAll('[data-engineering-version]').forEach(button=>button.addEventListener('click',()=>{ 71 const item=(data.model_versions||[]).find(item=>item.id===Number(button.dataset.engineeringVersion));if(!item)return; 72 const selected={...data,model:item.model,selected_model_version:item.id};if(selected.view_settings?.xray_model!==item.model.sha256)selected.view_settings={...selected.view_settings,component_tooltips:[],tooltip_groups:[],xray_groups:[]};if(selected.view_settings?.xray_mode==='manual'&&selected.view_settings.xray_model!==item.model.sha256)selected.view_settings={...selected.view_settings,xray_mode:'none',xray_parts:[]};container.innerHTML=renderConfiguration(a,selected,!!container.closest('.engineering-configuration-dialog'));bindConfiguration(a,container,selected); 73 })); 74 container.querySelector('[data-engineering-edit]')?.addEventListener('click',()=>data.robot_id?editRobot(a,data,data.robots.find(robot=>robot.id===data.robot_id)):edit(a,data)); 75 container.querySelector('[data-engineering-upload]')?.addEventListener('click',()=>upload(a,data)); 76 const surface=container.querySelector('[data-engineering-viewer]'),status=container.querySelector('[data-model-status]'),canvas=container.querySelector('[data-presentation-canvas]');if(!surface)return; 77 const url=localURL(data.model?.presentation?.url);let presentation=null,generation=0,rotationUnlocked=false,cameraView='front'; 78 const zoom=container.querySelector('[data-model-zoom]'),top=container.querySelector('[data-model-top]'),heading=container.querySelector('[data-model-heading]'),save=container.querySelector('[data-model-view-save]'),pivotFields=[...container.querySelectorAll('[data-pivot-axis]')],preset=container.querySelector('[data-pivot-preset]'); 79 const xrayMode=container.querySelector('[data-xray-mode]'),xrayList=container.querySelector('[data-xray-components]'),xrayParts=new Set(data.view_settings?.xray_parts||[]); 80 function locatePickedComponent(id){ 81 if(selectTooltipParts)toggleTooltipPart(id); 82 const input=xrayList?.querySelector('[data-xray-part="'+id+'"]');if(!input)return; 83 const details=input.closest('details');if(details)details.open=true; 84 xrayList.querySelectorAll('.is-located').forEach(row=>row.classList.remove('is-located'));input.parentElement.parentElement.classList.add('is-located'); 85 input.scrollIntoView({block:'nearest',inline:'nearest'});input.focus({preventScroll:true}); 86 } 87 const xrayGroups=new Map((data.view_settings?.xray_groups||[]).map(item=>[item.part,item.group])); 88 const tooltipGroups=(data.view_settings?.tooltip_groups||[]).map(item=>({...item,parts:[...item.parts]})); 89 if(!data.view_settings?.tooltip_groups){for(const item of data.view_settings?.component_tooltips||[]){let group=tooltipGroups.find(g=>g.title===item.title);
89if(!group){group={title:item.title,description:item.description||'',parts:[]};tooltipGroups.push(group);}group.parts.push(item.part);}} 90 let activeTooltip=tooltipGroups.length?0:-1,selectTooltipParts=false; 91 const applyTooltips=()=>presentation?.setTooltips(tooltipGroups.flatMap(group=>group.parts.map(part=>({part,title:group.title,description:group.description})))); 92 const tooltipEditor=document.createElement('fieldset');tooltipEditor.className='engineering-tooltip-editor'; 93 tooltipEditor.innerHTML='<legend>Tooltips do robô</legend><p class="muted">Crie um tooltip e selecione os componentes. Com o giro travado, clique e arraste para vincular várias peças visÃveis de uma vez.</p><div class="inline-fields"><select data-tooltip-select aria-label="Tooltip"></select><button type="button" class="button small" data-tooltip-add>Adicionar tooltip</button><button type="button" class="button small" data-tooltip-remove>Remover</button></div><div data-tooltip-fields><label>TÃtulo<input data-tooltip-title maxlength="120"></label><label>Descrição<textarea data-tooltip-description maxlength="500" rows="3"></textarea></label><button type="button" class="button small" data-tooltip-pick aria-pressed="false">Selecionar componentes</button><p data-tooltip-count class="muted"></p></div>'; 94 xrayList?.closest('fieldset')?.before(tooltipEditor); 95 const tooltipSelect=tooltipEditor.querySelector('[data-tooltip-select]'),tooltipTitle=tooltipEditor.querySelector('[data-tooltip-title]'),tooltipDescription=tooltipEditor.querySelector('[data-tooltip-description]'),tooltipPick=tooltipEditor.querySelector('[data-tooltip-pick]'); 96 function syncTooltipEditor(){tooltipSelect.replaceChildren(...tooltipGroups.map((group,i)=>{const option=document.createElement('option');option.value=i;option.textContent=group.title||'Sem tÃtulo';return option;}));tooltipSelect.value=String(activeTooltip);const group=tooltipGroups[activeTooltip];tooltipEditor.querySelector('[data-tooltip-fields]').hidden=!group;tooltipEditor.querySelector('[data-tooltip-remove]').disabled=!group;tooltipTitle.value=group?.title||'';tooltipDescription.value=group?.description||'';tooltipPick.setAttribute('aria-pressed',String(selectTooltipParts));tooltipPick.textContent=selectTooltipParts?'Concluir seleção':'Selecionar componentes';tooltipEditor.querySelector('[data-tooltip-count]').textContent=(group?.parts.length||0)+' componentes vinculados';xrayList?.querySelectorAll('[data-tooltip-part]').forEach(input=>{input.disabled=!group;input.checked=!!group?.parts.includes(Number(input.dataset.tooltipPart));});} 97 function toggleTooltipPart(id,checked){const group=tooltipGroups[activeTooltip];if(!group)return;const parts=new Set(group.parts);if(checked??!parts.has(id))parts.add(id);else parts.delete(id);group.parts=[...parts];applyTooltips();syncTooltipEditor();} 98 tooltipSelect.onchange=()=>{activeTooltip=Number(tooltipSelect.value);syncTooltipEditor();}; 99 tooltipEditor.querySelector('[data-tooltip-add]').onclick=()=>{if(tooltipGroups.length>=50){a.toast('Use até 50 tooltips por modelo.');return;}tooltipGroups.push({title:'Tooltip '+(tooltipGroups.length+1),description:'',parts:[]});activeTooltip=tooltipGroups.length-1;syncTooltipEditor();tooltipTitle.focus();}; 100 tooltipEditor.querySelector('[data-tooltip-remove]').onclick=()=>{if(activeTooltip<0)return;tooltipGroups.splice(activeTooltip,1);activeTooltip=tooltipGroups.length?Math.min(activeTooltip,tooltipGroups.length-1):-1;selectTooltipParts=false;applyTooltips();syncTooltipEditor();}; 101 tooltipTitle.oninput=()=>{const group=tooltipGroups[activeTooltip];if(group){group.title=tooltipTitle.value;tooltipSelect.options[activeTooltip].textContent=group.title||'Sem tÃtulo';applyTooltips();}}; 102 tooltipDescription.oninput=()=>{const group=tooltipGroups[activeTooltip];if(group){group.description=tooltipDescription.value;applyTooltips();}}; 103 tooltipPick.onclick=()=>{selectTooltipParts=!selectTooltipParts;presentation?.setSelectionEnabled(selectTooltipParts);syncTooltipEditor();};syncTooltipEditor(); 104 const applyXray=()=>presentation?.setXray(xrayMode?.value||data.view_settings?.xray_mode||'auto',[...xrayParts]); 105 function paintComponents(){ 106 if(!xrayList)return; 107 const components=presentation?.components||[]; 108 xrayList.innerHTML=components.length?components.map(({id})=>'<div style="display:flex;flex-wrap:wrap;gap:12px;align-items:center;padding:7px 0"><label style="flex:1"><input type="checkbox" data-xray-part="'+id+'" '+(xrayParts.has(id)?'checked':'')+'> '+tr(a,'Componente ','Component ')+(id+1)+'</label><button type="button" class="button small" data-xray-locate="'+id+'">'+tr(a,'Localizar','Locate')+'</button><div class="engineering-tooltip-fields"><input data-xray-group="'+id+'" maxlength="60" aria-label="Grupo de raio-X" placeholder="Grupo de raio-X (ex.: roda dianteira esquerda)" value="'+esc(a,xrayGroups.get(id)||'')+'"><label><input type="checkbox" data-tooltip-part="'+id+'"> Vincular ao tooltip selecionado</label></div></div>').join(''):tr(a,'Nenhum componente disponÃvel nesta prévia.','No components available in this preview.'); 109 xrayList.querySelectorAll('[data-xray-group]').forEach(input=>input.addEventListener('change',()=>{const id=Number(input.dataset.xrayGroup),group=input.value.trim();if(group)xrayGroups.set(id,group);else xrayGroups.delete(id);presentation?.setXrayGroups([...xrayGroups].map(([part,group])=>({part,group})));})); 110 xrayList.querySelectorAll('[data-tooltip-part]').forEach(input=>input.onchange=()=>toggleTooltipPart(Number(input.dataset.tooltipPart),input.checked));syncTooltipEditor(); 111 xrayList.querySelectorAll('[data-xray-part]').forEach(input=>input.onchange=()=>{const id=Number(input.dataset.xrayPart);input.checked?xrayParts.add(id):xrayParts.delete(id);xrayMode.value='manual';applyXray();}); 112 xrayList.querySelectorAll('[data-xray-locate]').forEach(button=>button.onclick=()=>presentation?.highlightComponent(Number(button.dataset.xrayLocate))); 113 } 114 xrayMode?.addEventListener('change',applyXray); 115 container.querySelector('[data-xray-clear]')?.addEventListener('click',()=>presentation?.highlightComponent(null)); 116 const preferences=()=>({xray_groups:[...xrayGroups].map(([part,group])=>({part,group})),component_tooltips:[],tooltip_groups:tooltipGroups.map(group=>({...group,title:group.title.trim()})),xray_mode:xrayMode?.value||data.view_settings?.xray_mode||'auto',xray_parts:[...xrayParts],xray_model:data.model?.sha256||'',up_axis:top?.value||data.view_settings?.up_axis||'+Y',heading:Number(heading?.value??data.view_settings?.heading??0),edges:!!data.view_settings?.edges,zoom:Number(zoom?.value??data.view_settings?.zoom??100),pivot:Object.fromEntries(pivotFields.map(input=>[input.dataset.pivotAxis,Number(input.value)]))}); 117 const valid=()=>!heading||heading.reportValidity()&&pivotFields.every(input=>input.value!==''&&input.reportValidity()); 118 let pendingSnapshot=data.view_settings?.snapshot?.model===data.model?.sha256?data.view_settings.snapshot:null; 119 const snapshotButton=container.querySelector('[data-model-snapsh
119ot]'),snapshotPreview=container.querySelector('[data-model-snapshot-preview]'); 120 const showSnapshot=()=>{if(!snapshotPreview)return;snapshotPreview.replaceChildren();if(pendingSnapshot){const img=document.createElement('img');img.src=pendingSnapshot.data;img.alt=tr(a,'Thumbnail da visão escolhida','Thumbnail of selected view');img.className='engineering-robot-snapshot';snapshotPreview.append(img);}};showSnapshot(); 121 if(snapshotButton)snapshotButton.onclick=()=>{const image=presentation?.snapshot();if(!image){a.toast(tr(a,'Aguarde o robô terminar de carregar.','Wait for the robot to finish loading.'));return;}pendingSnapshot={model:data.model.sha256,data:image};showSnapshot();a.toast(tr(a,'Thumbnail capturada. Clique em Salvar visualização para aplicar.','Thumbnail captured. Click Save view to apply.'));}; 122 const season=a.state.seasonId,context=a.readContextKey?.(); 123 async function remount(){const request=++generation;if(!canvas?.isConnected||!url||!valid())return;presentation?.dispose();presentation=null;status.hidden=false;surface.setAttribute('aria-busy','true');try{presentationLibrary??=import(new URL('assets/modules/engineering-presentation.js?v=c9779fa57a269fa4',document.baseURI).href).catch(error=>{presentationLibrary=null;throw error;});const visual=await presentationLibrary;if(request!==generation||!canvas.isConnected||a.state.seasonId!==season||context!==undefined&&context!==a.readContextKey?.())return;const mounted=await visual.mount(canvas,url,preferences(),{autoRotate:false,rotationEnabled:rotationUnlocked,assembled:true,onComponentPick:locatePickedComponent,onComponentsPick:ids=>{if(selectTooltipParts){const group=tooltipGroups[activeTooltip];if(group){group.parts=[...new Set([...group.parts,...ids])];applyTooltips();syncTooltipEditor();}}}});if(request!==generation||!canvas.isConnected){mounted.dispose();return;}mounted.setPivot(preferences().pivot);mounted.setView(cameraView);presentation=mounted;mounted.setSelectionEnabled(selectTooltipParts);paintComponents();status.hidden=true;surface.setAttribute('aria-busy','false');}catch{if(request!==generation)return;status.hidden=false;status.className='model-error';status.textContent=tr(a,'Não foi possÃvel carregar a apresentação de partÃculas.','The particle presentation could not be loaded.');surface.setAttribute('aria-busy','false');}} 124 const cameraButtons=[...container.querySelectorAll('[data-camera-view]')],unlock=container.querySelector('[data-camera-unlock]'); 125 cameraButtons.forEach(button=>button.addEventListener('click',()=>{cameraView=button.dataset.cameraView;presentation?.setView(cameraView);cameraButtons.forEach(item=>item.setAttribute('aria-pressed',String(item===button)));})); 126 unlock?.addEventListener('click',()=>{rotationUnlocked=!rotationUnlocked;presentation?.setRotationEnabled(rotationUnlocked);unlock.setAttribute('aria-pressed',String(rotationUnlocked));unlock.textContent=tr(a,rotationUnlocked?'Travar giro':'Liberar giro',rotationUnlocked?'Lock rotation':'Unlock rotation');}); 127 if(canvas&&url)remount(); 128 const pivot=()=>{if(!valid())return;preset&&(preset.value='');presentation?.setPivot(preferences().pivot);}; 129 pivotFields.forEach(input=>input.addEventListener('input',pivot));preset?.addEventListener('change',()=>{if(!preset.value)return;pivotFields.forEach((input,i)=>input.value=preset.value.split(',')[i]);pivot();}); 130 zoom?.addEventListener('input',()=>{container.querySelector('[data-model-zoom-label]').textContent=zoom.value+'%';presentation?.setZoom(Number(zoom.value));}); 131 top?.addEventListener('change',remount);heading?.addEventListener('change',remount); 132 const full=container.querySelector('[data-presentation-fullscreen]');if(full){full.hidden=typeof surface.requestFullscreen!=='function';full.onclick=async()=>{if(!document.fullscreenElement&&canvas?.dataset.presentationReady!=='true')return;try{if(document.fullscreenElement)await document.exitFullscreen();else await surface.requestFullscreen();}catch{a.toast(tr(a,'O navegador não permitiu abrir a tela cheia.','The browser did not allow full screen.'));}};} 133 if(save){let revision=data.version;save.onclick=async()=>
133{if(!valid()||url&&!canvas?.isConnected||a.state.seasonId!==season||context!==undefined&&context!==a.readContextKey?.())return;save.disabled=true;try{presentation?.highlightComponent(null);const settings=preferences();if(settings.tooltip_groups.some(group=>!group.title))throw Error('Preencha o tÃtulo de todos os tooltips.');if(pendingSnapshot)settings.snapshot=pendingSnapshot;const updated=await a.api('engineering.robot.save',{season_id:season,id:data.robot_id,version:revision,view_settings:settings},'POST');if(a.state.seasonId!==season||context!==undefined&&context!==a.readContextKey?.()||url&&!canvas?.isConnected)return;revision=updated.version;data.version=revision;data.view_settings=settings;const robot=data.robots?.find(r=>r.id===data.robot_id);if(robot){robot.version=revision;robot.view_settings=settings;}a.toast(tr(a,'Visualização salva para a equipe.','View saved for the team.'));}catch(error){a.toast(error.message,true);}finally{save.disabled=false;}};} 134 const dialog=container.closest('.engineering-configuration-dialog'),foot=dialog?.querySelector('.dialog-foot'),actions=container.querySelector(':scope > .engineering-catalog-actions'); 135 if(foot&&actions){foot.querySelector('.engineering-footer-actions')?.remove();actions.classList.add('engineering-footer-actions');foot.prepend(actions);} 136 } 137 function edit(a,data){if(!canEdit(a,data))return;a.openDialog(tr(a,'Engenharia · configuração','Engineering · settings'),a.field('title',a.t('title'),data.title||'','text','required maxlength="180"')+a.textArea('description',a.t('description'),data.description||'','maxlength="10000"')+a.field('onshape_document_url',tr(a,'Link do projeto no Onshape','Onshape project link'),data.onshape_document_url||'','url','maxlength="2000" placeholder="https://cad.onshape.com/documents/â¦" '+(managed(data)?'readonly':''))+(managed(data)?'<p class="muted detail-copy">'+tr(a,'Altere o documento em Configurar conexão. Para enviar um GLB manual, desconecte primeiro a atualização automática.','Change the document in Configure connection. Disconnect automatic updates before uploading a manual GLB.')+'</p>':'')+a.field('onshape_community_url',tr(a,'Link da comunidade Onshape','Onshape community link'),data.onshape_community_url||'','url','maxlength="2000"'),a.editorFooter(),async(form,dialog)=>{await a.api('engineering.save',{season_id:a.state.seasonId,version:data.version,...a.formObject(form)},'POST');await a.saved(dialog);});} 138 async function sendModel(a,body,dialog){ 139 dialog.querySelector('[data-model-upload-progress]')?.remove(); 140 const box=document.createElement('div');box.className='engineering-upload-progress';box.dataset.modelUploadProgress='';box.setAttribute('aria-busy','true'); 141 box.innerHTML='<div><span data-upload-stage role="status" aria-live="polite"></span><strong data-upload-percent>0%</strong></div><progress max="100" value="0" aria-label="'+tr(a,'Envio do GLB','GLB upload')+'"></progress><div data-model-processing hidden><span data-processing-label></span> <strong data-processing-percent></strong><progress max="100" aria-label="'+tr(a,'Processamento do GLB','GLB processing')+'"></progress></div>'; 142 const input=dialog.querySelector('input[type=file]');(input?.closest('.field')||dialog.querySelector('form')||dialog).append(box); 143 const stage=box.querySelector('[data-upload-stage]'),percent=box.querySelector('[data-upload-percent]'),bar=box.querySelector('progress'); 144 // Keep the server job across a failed connection or a retry of this editor. 145 if(!dialog._modelUpload||dialog._modelUpload.terminal)dialog._modelUpload={nonce:Array.from(crypto.getRandomValues(new Uint8Array(32)),v=>v.toString(16).padStart(2,'0')).join('')}; 146 const saved=dialog._modelUpload,background=dialog._backgroundUpload,controller=background?.controller||new AbortController(),signal=controller.signal,csrf=a.state.csrf; 147 const phase=message=>{stage.textContent=message;if(background){background.message=message;background.card.querySelector('[data-phase]').textContent=message;}}; 148 const processing=status=>{const kind=status.phase||'queued',value=Number(status.progress?.percent??status.val
148idation_progress),details={label:kind==='validation'?tr(a,'Validando arquivoâ¦','Validating fileâ¦'):kind==='presentation'?tr(a,'Preparando visualizaçãoâ¦','Preparing visualizationâ¦'):tr(a,'Aguardando processamentoâ¦','Waiting for processingâ¦'),percent:kind==='validation'&&Number.isFinite(value)?Math.max(0,Math.min(100,Math.floor(value))):null};saved.processing=status;const area=box.querySelector('[data-model-processing]');area.hidden=false;area.querySelector('[data-processing-label]').textContent=details.label;area.querySelector('[data-processing-percent]').textContent=details.percent===null?'':details.percent+'%';const meter=area.querySelector('progress');if(details.percent===null)meter.removeAttribute('value');else meter.value=details.percent;window.AltaVoltagemBackgroundUploads?.processing(dialog,details);}; 149 const transferred=()=>{saved.transferPercent=100;bar.value=100;percent.textContent='100%';phase(tr(a,'Arquivo enviado.','File uploaded.'));processing({phase:'queued'});}; 150 if(Number.isFinite(saved.transferPercent)){bar.value=saved.transferPercent;percent.textContent=saved.transferPercent+'%';if(background&&saved.transferPercent===100)background.files.forEach(file=>background.progress.set(file,file.size));} 151 const cancelled=()=>Object.assign(Error(tr(a,'Envio cancelado.','Upload cancelled.')),{name:'AbortError'}); 152 const request=async(endpoint,options={})=>{const response=await fetch(new URL(endpoint,document.baseURI),{credentials:'same-origin',headers:{Accept:'application/json','X-CSRF-Token':csrf,...options.headers},...options});const payload=await response.json();if(!response.ok||payload?.ok!==true)throw Error(payload?.error?.message||a.t('invalidResponse'));return payload.data;}; 153 let cancelRequest=null; 154 const cancel=()=>{if(!cancelRequest)cancelRequest=request('model-upload-cancel.php',{method:'POST',headers:{Accept:'application/json','Content-Type':'application/json','X-CSRF-Token':csrf},body:JSON.stringify({job_token:saved.token,upload_nonce:saved.nonce,season_id:Number(body.get('season_id'))})}).then(status=>{if(status.state==='cancelled')saved.terminal=true;return status;}).catch(error=>{cancelRequest=null;throw error;});cancelRequest?.catch(()=>{});}; 155 const close=()=>controller.abort();dialog.addEventListener('close',close,{once:true});signal.addEventListener('abort',cancel); 156 try{ 157 if(saved.done)return saved.model; 158 if(!saved.token){ 159 if(signal.aborted)throw cancelled();phase(tr(a,'Enviando arquivoâ¦','Uploading fileâ¦'));body.set('upload_nonce',saved.nonce); 160 const payload=await new Promise((resolve,reject)=>{ 161 const xhr=new XMLHttpRequest(),abort=()=>xhr.abort();let finished=false; 162 const complete=(error,value)=>{if(finished)return;finished=true;signal.removeEventListener('abort',abort);error?reject(error):resolve(value);}; 163 xhr.open('POST',new URL('model-upload.php',document.baseURI));xhr.withCredentials=true;xhr.timeout=3600000;xhr.responseType='json';xhr.setRequestHeader('Accept','application/json');xhr.setRequestHeader('X-CSRF-Token',csrf); 164 xhr.upload.onprogress=event=>{if(event.lengthComputable){const value=Math.min(100,Math.floor(event.loaded/event.total*100));saved.transferPercent=value;bar.value=value;percent.textContent=value+'%';}else{bar.removeAttribute('value');percent.textContent='';}}; 165 xhr.upload.onload=transferred; 166 xhr.onload=()=>{const result=xhr.response;if(xhr.status<200||xhr.status>=300||result?.ok!==true)complete(Error(result?.error?.message||a.t('invalidResponse')));else complete(null,result.data);}; 167 xhr.onerror=xhr.ontimeout=()=>complete(Error(a.t('uncertain')));xhr.onabort=()=>complete(cancelled());signal.addEventListener('abort',abort,{once:true}); 168 try{xhr.send(body);}catch(error){complete(error);} 169 }); 170 if(payload?.state==='cancelled'){saved.terminal=true;throw cancelled();} 171 if(payload?.state==='failed'){saved.terminal=true;throw Error(payload.error?.message||a.t('invalidResponse'));} 172 if(!payload?.job_token)throw Error(a.t('invalidResponse')); 173 saved.token=payload.job_token; 174 } 175 phase(tr(a,'Arquivo enviado.','File uploaded.'));processing(saved.processing||{phase:'queued'});const deadline=Date.now()+3600000;let failures=0; 176 while(Date.now()<deadline){ 177 if(signal.aborted){phase(tr(a,'Cancelando processamentoâ¦','Cancelling processingâ¦'));cancel();if(cancelRequest)await cancelRequest;} 178 let status; 179 try{status=await request('model-upload-status.php?job_token='+encodeURIComponent(saved.token));failures=0;} 180 catch(error){if(++failures>=3)throw error;await new Promise(resolve=>setTimeout(resolve,2000));continue;} 181 if(status.state==='ready'){saved.done=true;saved.model=status.result??status.model;phase(tr(a,'Versão salva.','Version saved.'));box.querySelector('[data-model-processing]').hidden=true;
181window.AltaVoltagemBackgroundUploads?.processing(dialog,null);return saved.model;} 182 if(status.state==='cancelled'){saved.terminal=true;throw cancelled();} 183 if(status.state==='failed'){saved.terminal=true;throw Error(status.error?.message||a.t('invalidResponse'));} 184 if(!['pending','processing'].includes(status.state))throw Error(a.t('invalidResponse')); 185 if(!signal.aborted)processing(status); 186 await new Promise(resolve=>setTimeout(resolve,2000)); 187 } 188 throw Error(tr(a,'O processamento continua. Use Tentar novamente para consultar este mesmo envio.','Processing is continuing. Use Retry to check this same upload.')); 189 }catch(error){if(signal.aborted){cancel();try{await cancelRequest;}catch{error=Error(tr(a,'Não foi possÃvel confirmar o cancelamento. Tente novamente para consultar o envio.','Cancellation could not be confirmed. Retry to check the upload.'));}}phase(error.message);throw error;} 190 finally{dialog.removeEventListener('close',close);signal.removeEventListener('abort',cancel);box.setAttribute('aria-busy','false');} 191 } 192 function editRobot(a,data,robot=null){a=window.AltaVoltagemBackgroun
192dUploads?.isolate(a)||a; 193 if(!canEdit(a,data))return; 194 const season=a.state.seasonId,context=a.readContextKey?.(),current=()=>a.state.seasonId===season&&(context===undefined||context===a.readContextKey?.());let stored=robot,savedRecord=false; 195 const body=a.field('title',tr(a,'Nome do robô','Robot name'),robot?.title||'','text','required maxlength="180"')+a.field('onshape_document_url',tr(a,'Link do projeto no Onshape','Onshape project link'),robot?.onshape_document_url||'','url','maxlength="2000" placeholder="https://cad.onshape.com/documents/â¦"')+a.textArea('description',a.t('description'),robot?.description||'','maxlength="10000"')+(robot?'':a.field('file',tr(a,'Visual 3D â arquivo GLB (opcional)','3D visual â GLB file (optional)'),'','file','accept=".glb,model/gltf-binary"')+a.field('label',tr(a,'Nome da primeira versão','First version name'),'V1','text','maxlength="180"')+exportGuide(a)); 196 a.openDialog(robot?tr(a,'Editar robô','Edit robot'):tr(a,'Novo robô','New robot'),body,a.editorFooter(),async(form,dialog)=>{ 197 if(!current())throw Error(tr(a,'Reabra o cadastro na temporada correta.','Reopen the editor in the correct season.')); 198 const file=form.get('file');if(file?.size&&managed(data))throw Error(tr(a,'Desconecte a atualização automática antes de enviar um GLB manual.','Disconnect automatic updates before uploading a manual GLB.'));if(file?.size&&(file.size>1024*1024*1024||!file.name.toLowerCase().endsWith('.glb')))throw Error(tr(a,'Escolha um GLB de até 1 GiB.','Choose a GLB up to 1 GiB.')); 199 if(!savedRecord){stored=await a.api('engineering.robot.save',{season_id:season,id:stored?.id,version:stored?.version,title:form.get('title'),description:form.get('description'),onshape_document_url:form.get('onshape_document_url')},'POST');savedRecord=true;} 200 if(file?.size){if(!current()||!dialog.open)throw Error(tr(a,'O cadastro mudou. Reabra o robô.','The editor changed. Reopen the robot.'));const upload=new FormData();upload.append('file',file);upload.append('season_id',String(season));upload.append('robot_id',String(stored.id));upload.append('version',String(stored.version));upload.append('label',String(form.get('label')||'V1'));await sendModel(a,upload,dialog);} 201 await a.saved(dialog); 202 }); 203 }
204 function upload(a,data){a=window.AltaVoltagemBackgroundUploads?.isolate(a)||a; 205 if(!canEdit(a,data))return; 206 if(managed(data)){a.toast(tr(a,'Desconecte a atualização automática antes de enviar um GLB manual.','Disconnect automatic updates before uploading a manual GLB.'));return;} 207 const season=Number(a.state.seasonId),context=a.readContextKey(); 208 a.openDialog(tr(a,'Enviar nova versão','Upload new version')+' · '+data.title,'<p class="notice">'+tr(a,'Envie um GLB (.glb) de até 1 GiB, sem a opção Compress. A versão anterior será preservada.','Upload a GLB (.glb) up to 1 GiB, with Compress disabled. The previous version will be preserved.')+'</p>'+exportGuide(a)+a.field('label',tr(a,'Nome da versão','Version name'),'V'+(Math.max(0,...(data.model_versions||[]).map(item=>Number(item.version)||0))+1),'text','required maxlength="180" placeholder="V1 â Chassi inicial"')+a.textArea('notes',tr(a,'O que mudou?','What changed?'),'','maxlength="10000"')+a.field('file','GLB','','file','required accept=".glb,model/gltf-binary"'),a.editorFooter(),async(form,dialog)=>{ 209 if(!a.readDialogCurrent(dialog,context))return; 210 const file=form.get('file'); 211 if(!file?.size||file.size>1024*1024*1024||!file.name.toLowerCase().endsWith('.glb'))throw Error(tr(a,'Escolha um arquivo GLB de até 1 GiB.','Choose a GLB file up to 1 GiB.')); 212 const body=new FormData();body.append('file',file);body.append('season_id',String(season));body.append('version',String(data.version));body.append('label',String(form.get('label')||''));body.append('notes',String(form.get('notes')||'')); 213 if(data.robot_id)body.append('robot_id',String(data.robot_id));await sendModel(a,body,dialog); 214 await a.saved(dialog); 215 }); 216 } 217 function showcaseSeasonLogo(a){const season=(a.state.bootstrap?.seasons||[]).find(item=>String(item.id)===String(a.state.seasonId)),url=localURL(season?.logo_url)||'assets/brand/biobuzz-wordmark-white.png';return '<img class="engineering-season-logo" src="'+esc(a,url)+'" alt="'+esc(a,season?.name||'BIOBUZZ')+'">';} 218 function renderPage(a,data){ 219 const model=data.model||null,presentation=localURL(model?.presentation?.url),name=data.title||tr(a,'Robô da temporada','Season robot'); 220 const actions=uploadButton(a,data)+shareButton(a,data)+(window.AltaVoltagemResponsibilities?.slot(a,'engineering')||'')+(canEdit(a,data)?'<button type="button" class="button" data-engineering-config>'+a.icon('settings')+tr(a,'Configuração','Configuration')+'</button>':'')+onshapeLink(a,data.onshape_document_url,tr(a,'Abrir robô no Onshape','Open robot in Onshape')); 221 const visual=presentation?'<div data-engineering-showcase-stage class="engineering-showcase-stage" aria-busy="true"><canvas data-engineering-showcase-canvas aria-label="'+esc(a,name)+'"></canvas></div>':'<div class="engineering-showcase-empty" role="status"><p>'+tr(a,model?.presentation?.state==='pending'?'A apresentação está em preparação.':model?.presentation?.state==='unavailable'?'A apresentação não está disponÃvel.':'A apresentação do robô ainda não está disponÃvel.','The particle presentation is not available yet.')+'</p></div>'; 222 return a.pageHeading('engineering','engineeringSubtitle',actions)+'<section class="engineering-showcase" aria-label="'+esc(a,name)+'">'+visual+'<div class="engineering-showcase-caption"><p class="eyebrow">FTC #25706</p><div class="engineering-showcase-heading"><h2>'+esc(a,name)+'</h2></div></div>'+showcaseSeasonLogo(a)+(presentation?'<button type="button" class="engineering-fullscreen-toggle" data-engineering-fullscreen aria-label="'+tr(a,'Tela cheia','Full screen')+'"></button>':'')+'</section>'; 223 } 224 function openConfiguration(a,data){ 225 if(!canEdit(a,data))return; 226 const dialog=a.openDialog(tr(a,'Robôs da equipe','Team robots'),'<div data-engineering-configuration></div>','<button type="button" class="button" data-dialog-close>
226'+tr(a,'Voltar à tela','Back to screen')+'</button>'); 227 dialog.classList.add('engineering-configuration-dialog');const host=dialog.querySelector('[data-engineering-configuration]'); 228 host.innerHTML=data.robot_registry_available?'<div class="engineering-catalog-actions"><button type="button" class="button primary" data-robot-new>'+a.icon('plus')+tr(a,'Novo robô','New robot')+'</button></div>'+robotCards(a,data)+'<div data-resource-links="engineering"></div>':renderConfiguration(a,data); 229 bindConfiguration(a,host,data); 230 } 231 async function bindPage(a,container,data){ 232 container.querySelector('[data-engineering-upload]')?.addEventListener('click',()=>upload(a,data)); 233 container.querySelector('[data-robot-share]')?.addEventListener('click',()=>window.AltaVoltagemRobotSharing?.open(a,data)); 234 container.querySelector('[data-engineering-config]')?.addEventListener('click',()=>openConfiguration(a,data)); 235 window.AltaVoltagemEngineeringFullscreen?.bind(a,container.querySelector('.engineering-showcase'),container.querySelector('[data-engineering-fullscreen]')); 236 const stage=container.querySelector('[data-engineering-showcase-stage]'),canvas=stage?.querySelector('[data-engineering-showcase-canvas]');if(!canvas)return; 237 try{ 238 presentationLibrary??=import(new URL('assets/modules/engineering-presentation.js?v=c9779fa57a269fa4',document.baseURI).href).catch(error=>{presentationLibrary=null;throw error;}); 239 const visual=await presentationLibrary;if(!canvas.isConnected)return; 240 await visual.mount(canvas,localURL(data.model?.presentation?.url),data.view_settings||{});if(stage.isConnected)stage.setAttribute('aria-busy','false'); 241 }catch{ 242 if(!stage?.isConnected)return;stage.setAttribute('aria-busy','false');stage.innerHTML='<p class="notice">'+tr(a,'Não foi possÃvel carregar a apresentação de partÃculas.','The particle presentation could not be loaded.')+'</p>'; 243 } 244 } 245 window.AltaVoltagemEngineering={renderPage,bindPage}; 246})(); 247
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.