PageSourceSearch

https://starwarsdatabase.com/gallery/src/robots/resident-ui.js?v=0.67.1&unlabelled=1&motion=on1

js starwarsdatabase.com collected 2026-10-07 19:30:20 UTC 4,213 bytes, 51 lines download raw bytes

1import * as THREE from '../../assets/vendor/three.module.js';
2const overlapping=(a,b)=>a.left<b.right+8&&a.right>b.left-8&&a.top<b.bottom+8&&a.bottom>b.top-8;
3export class ResidentDialogue{
4 constructor(service){
5  this.service=service;this.recent=[];this.ticket=0;this.cooldown=0;this.remaining=0;this.target=null;
6  this.balloon=document.createElement('aside');this.balloon.id='resident-balloon';this.balloon.hidden=true;this.balloon.setAttribute('aria-label','Robot greeting');
7  this.words=document.createElement('p');this.words.setAttribute('role','status');this.words.setAttribute('aria-live','polite');
8  this.close=document.createElement('button');this.close.type='button';this.close.textContent='×';this.close.setAttribute('aria-label','Dismiss robot greeting');this.balloon.append(this.words,this.close);document.body.append(this.balloon);
9  this.close.addEventListener('click',()=>this.hide());
10  document.addEventListener('keydown',e=>{if(e.key==='Escape'&&!this.balloon.hidden&&!document.querySelector('dialog[open]')){e.preventDefault();this.hide();}});
11  document.addEventListener('visibilitychange',()=>{if(document.hidden)this.suspend();});
12 }
13 hide(){this.ticket++;this.controller?.abort();this.controller=null;this.target=null;this.remaining=0;this.balloon.hidden=true;this.words.textContent='';}
14 suspend(){this.hide();}
15 forget(entry){if(this.target===entry)this.hide();}
16 async greet(entry){
17  if(performance.now()<this.cooldown)return;const room=this.service.getRoom();if(!this.service.eligible(room,entry))return;
18  this.hide();this.target=entry;this.cooldown=performance.now()+3000;const ticket=this.ticket;this.controller=new AbortController();
19  const facing=this.service.beginGreeting(entry,this.controller.signal);
20  try{
21   const response=await fetch('robots.php?greet=1&recent='+encodeURIComponent(this.recent.join(',')),{credentials:'omit',cache:'no-store',signal:this.controller.signal});
22   if(!response.ok)return;const data=await response.json();
23   if(ticket!==this.ticket||this.target!==entry||!this.service.eligible(this.service.getRoom(),entry))return;
24   if(data.enabled===false){this.service.setEnabled(false);return;}
25   const p=data.phrase;if(!p||!Number.isSafeInteger(p.id)||p.id<1||typeof p.text!=='string'||!p.text.trim()||[...p.text].length>240||/[\u0000-\u001f\u007f]/.test(p.text))return;
26   if(!(await facing)||ticket!==this.ticket||this.target!==entry||!this.service.eligible(this.service.getRoom(),entry))return;
27   this.recent.push(p.id);if(this.recent.length>20)this.recent.shift();this.words.textContent=p.text;this.remaining=12;this.balloon.hidden=false;if(!this.position(this.balloon,room,entry))this.hide();
28  }catch{/* Optional authored dialogue never blocks visiting. */}
29 }
30 position(node,room,entry,offset=1.35){
31  const p=new THREE.Vector3(entry.descriptor.x,entry.descriptor.y+offset,entry.descriptor.z).project(room.camera),x=(p.x+1)*innerWidth/2,y=(1-p.y)*innerHeight/2;
32  const w=Math.min(node.offsetWidth,innerWidth-24),h=node.offsetHeight;
33  if(w<=0||h<=0||w>innerWidth-24||h>innerHeight-24)return false;
34  const avoid=[...document.querySelectorAll('.masthead,.room-heading,.visitor-bar,#movement,#viewing-controls,#annex-controls,#nearby-display,#reset-view,#map-toggle,#booth-toggle,#explorer-toggle')].filter(n=>!n.hidden&&n.getClientRects().length).map(n=>n.getBoundingClientRect());
35  for(const [left,top] of [[x-w/2,y-h-12],[x+20,y-h/2],[x-w-20,y-h/2],[x-w/2,y+20]]){
36   const l=Math.max(12,Math.min(innerWidth-w-12,left)),t=Math.max(12,Math.min(innerHeight-h-12,top)),rect={left:l,right:l+w,top:t,bottom:t+h};
37   if(avoid.some(r=>overlapping(rect,r)))continue;node.style.left=l+'px';node.style.top=t+'px';return true;
38  }
39  return false;
40 }
41 update(room,dt){
42  const due=(this.layoutDue=(this.layoutDue||0)-dt)<=0;if(due)this.layoutDue=.1;
43  if(this.target){
44   if(this.target.state!==room.residents||(due&&!this.service.eligible(room,this.target,5))){this.hide();return;}
45   if(!this.balloon.hidden){if(due&&!this.position(this.balloon,room,this.target)){this.hide();return;}
46    if(!this.balloon.matches(':hover')&&!this.balloon.contains(document.activeElement))this.remaining-=dt;
47    if(this.remaining<=0)this.hide();
48   }
49  }
50 }
51}

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.