1import {SHORTCUT_STORAGE_KEY,CELL,readLayout,gridSize,nearestCell,normalizedCell,arrangeShortcuts,moveShortcut} from './shortcut-layout.js'; 2 3export function mountShortcuts(host, {notify=()=>{}}={}) { 4 const buttons=[...host.querySelectorAll('.shortcut')],ids=buttons.map(button=>button.dataset.app); 5 let storage;try{storage=window.localStorage;}catch{} 6 let preferences;try{preferences=readLayout(storage?.getItem(SHORTCUT_STORAGE_KEY),ids);}catch{preferences=readLayout(null,ids);} 7 let grid,positions,drag=null,suppressClickUntil=0; 8 const controller=new AbortController(); 9 const listen=(element,type,fn,options={})=>element.addEventListener(type,fn,{...options,signal:controller.signal}); 10 const enabled=()=>['desktop','flat'].includes(document.body.dataset.mode)&&host.clientWidth>0&&host.clientHeight>0; 11 const help=document.createElement('span');help.id='shortcut-move-help';help.className='shortcut-help'; 12 help.textContent='ÐеÑеÑаÑиÑе ÑÑлÑк на Ñвободное меÑÑо. С клавиаÑÑÑÑ: Alt и ÑÑÑелки. Escape оÑменÑÐµÑ Ð¿ÐµÑеноÑ. РаÑположение ÑÐ¾Ñ ÑанÑеÑÑÑ Ð² ÑÑом бÑаÑзеÑе.';host.append(help); 13 for(const button of buttons){button.setAttribute('aria-describedby',help.id);button.title='ÐеÑеÑаÑиÑÑ Â· Alt + ÑÑÑелки';button.draggable=false;} 14 15 function paint(){ 16 for(const button of buttons){ 17 const cell=positions[button.dataset.app]; 18 if(!cell) continue; 19 button.style.left=cell.col*CELL.width+'px';button.style.top=cell.row*CELL.height+'px'; 20 } 21 } 22 function cancel(){ 23 if(!drag)return; 24 const current=drag;drag=null; 25 current.button.classList.remove('is-dragging');host.classList.remove('has-drag'); 26 if(current.moved)suppressClickUntil=performance.now()+500; 27 if(current.button.hasPointerCapture(current.pointerId))current.button.releasePointerCapture(current.pointerId);
28 paint(); 29 } 30 function layout(){ 31 if(!enabled()){cancel();return;} 32 cancel();grid=gridSize(host.clientWidth,host.clientHeight); 33 positions=arrangeShortcuts(ids,preferences,grid);paint(); 34 } 35 function commit(id,target){ 36 const before=positions[id]; 37 if(before.col===target.col&&before.row===target.row){paint();return;} 38 positions=moveShortcut(positions,id,target); 39 // Save the whole visible arrangement so untouched icons do not fill the 40 // newly vacant slot on the next visit. Resize itself never writes a save. 41 for(const key of ids)if(positions[key])preferences[key]=normalizedCell(positions[key],grid); 42 paint(); 43 let saved=false; 44 try{if(storage){storage.setItem(SHORTCUT_STORAGE_KEY,JSON.stringify({version:1,positions:preferences}));saved=true;}}catch{} 45 const name=buttons.find(button=>button.dataset.app===id).textContent.trim(); 46 notify(saved?name+': ÑаÑположение ÑÐ¾Ñ Ñанено':name+': пеÑемеÑÑн. ÐÑаÑÐ·ÐµÑ Ð½Ðµ ÑазÑеÑил ÑÐ¾Ñ ÑаниÑÑ ÑаÑположение.'); 47 } 48 listen(host,'pointerdown',event=>{ 49 const button=event.target.closest('.shortcut'); 50 if(!button||event.button!==0||!event.isPrimary||drag||!enabled())return; 51 if(!positions)layout(); 52 const rect=host.getBoundingClientRect(),cell=positions[button.dataset.app]; 53 if(!cell||!rect.width||!rect.height)return; 54 drag={button,pointerId:event.pointerId,x:event.clientX,y:event.clientY,left:cell.col*CELL.width,top:cell.row*CELL.height,sx:host.clientWidth/rect.width,sy:host.clientHeight/rect.height,moved:false}; 55 button.focus({preventScroll:true});button.setPointerCapture(event.pointerId); 56 }); 57 listen(host,'pointermove',event=>{ 58 if(!drag||event.pointerId!==drag.pointerId)return; 59 const dx=event.clientX-drag.x,dy=event.clientY-drag.y; 60 if(!drag.moved&&Math.hypot(dx,dy)<6)return; 61 drag.moved=true;drag.button.classList.add('is-dragging');host.classList.add('has-drag'); 62 drag.button.style.left=Math.max(0,Math.min(host.clientWidth-drag.button.offsetWidth,drag.left+dx*drag.sx))+'px'; 63 drag.button.style.top=Math.max(0,Math.min(host.clientHeight-drag.button.offsetHeight,drag.top+dy*drag.sy))+'px'; 64 event.preventDefault(); 65 }); 66 listen(host,'pointerup',event=>{ 67 if(!drag||event.pointerId!==drag.pointerId)return; 68 const current=drag,target=nearestCell(current.button.offsetLeft,current.button.offsetTop,grid); 69 cancel(); 70 if(current.moved)commit(current.button.dataset.app,target); 71 }); 72 listen(host,'pointercancel',cancel);listen(host,'lostpointercapture',cancel); 73 listen(host,'dragstart',event=>event.preventDefault()); 74 listen(host,'click',event=>{ 75 if(event.detail>0&&performance.now()<suppressClickUntil){event.preventDefault();event.stopPropagation();suppressClickUntil=0;} 76 },{capture:true}); 77 listen(host,'keydown',event=>{ 78 if(event.key==='Escape'&&drag){event.preventDefault();event.stopPropagation();cancel();return;} 79 const direction={ArrowLeft:[-1,0],ArrowRight:[1,0],ArrowUp:[0,-1],ArrowDown:[0,1]}[event.key]; 80 const button=event.target.closest('.shortcut'); 81 if(!button||!event.altKey||event.ctrlKey||event.metaKey||!direction||!enabled())return; 82 event.preventDefault();event.stopPropagation();cancel(); 83 const cell=positions[button.dataset.app]; 84 commit(button.dataset.app,nearestCell((cell.col+direction[0])*CELL.width,(cell.row+direction[1])*CELL.height,grid)); 85 }); 86 listen(window,'blur',cancel); 87 listen(window,'storage',event=>{ 88 if(event.key!==SHORTCUT_STORAGE_KEY&&event.key!==null)return; 89 preferences=readLayout(event.key===null?null:event.newValue,ids);layout(); 90 }); 91 const resize=new ResizeObserver(layout);resize.observe(host); 92 const mode=new MutationObserver(layout);mode.observe(document.body,{attributes:true,attributeFilter:['data-mode']}); 93 layout(); 94 return {destroy(){cancel();controller.abort();resize.disconnect();mode.disconnect();help.remove();}}; 95}
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.