PageSourceSearch

https://qwelp.ru/desktop-shortcuts.js

js qwelp.ru collected 2026-10-08 20:35:11 UTC 5,906 bytes, 95 lines download raw bytes

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.