feat: publish CodexPi contest snapshot
Some checks are pending
Qualité / tests (push) Waiting to run
Some checks are pending
Qualité / tests (push) Waiting to run
This commit is contained in:
154
frontend/public/browser-touchpad.js
Normal file
154
frontend/public/browser-touchpad.js
Normal file
@@ -0,0 +1,154 @@
|
||||
import UI from '/browser/app/ui.js'
|
||||
import {relativePointerPosition} from '/browser-touchpad-math.js'
|
||||
|
||||
const POINTER_SPEED=1.35
|
||||
const MOVE_THRESHOLD=6
|
||||
const LONG_PRESS_DELAY=560
|
||||
const DOUBLE_TAP_DELAY=320
|
||||
const SCROLL_STEP=18
|
||||
|
||||
let installedCanvas=null
|
||||
let touchSurface=null
|
||||
let pointer={x:0,y:0}
|
||||
let primaryTouch=null
|
||||
let origin=null
|
||||
let lastPoint=null
|
||||
let moved=false
|
||||
let longPressed=false
|
||||
let dragging=false
|
||||
let longPressTimer=null
|
||||
let lastTapAt=0
|
||||
let secondTap=false
|
||||
let scrollY=null
|
||||
let scrollRemainder=0
|
||||
|
||||
function rfb(){return UI.rfb}
|
||||
function canvas(){return rfb()?._canvas||null}
|
||||
function bounds(){const target=canvas();return target?{target,rect:target.getBoundingClientRect()}:null}
|
||||
function syncPointer(){
|
||||
const remote=rfb()?._mousePos
|
||||
const target=canvas()
|
||||
if(Number.isFinite(remote?.x)&&Number.isFinite(remote?.y)){pointer={x:remote.x,y:remote.y}}
|
||||
else if(target){pointer={x:target.width/2,y:target.height/2}}
|
||||
}
|
||||
function movePointer(dx,dy){
|
||||
const current=rfb(),geometry=bounds()
|
||||
if(!current||!geometry||!geometry.rect.width||!geometry.rect.height)return
|
||||
const scaleX=geometry.target.width/geometry.rect.width
|
||||
const scaleY=geometry.target.height/geometry.rect.height
|
||||
pointer=relativePointerPosition(pointer,dx,dy,scaleX,scaleY,POINTER_SPEED,geometry.target.width,geometry.target.height)
|
||||
current._handleMouseMove(pointer.x,pointer.y)
|
||||
current._cursor?.move(geometry.rect.left+pointer.x/scaleX,geometry.rect.top+pointer.y/scaleY)
|
||||
}
|
||||
function click(mask=1){
|
||||
const current=rfb()
|
||||
if(!current)return
|
||||
syncPointer()
|
||||
current._handleMouseButton(pointer.x,pointer.y,mask)
|
||||
current._handleMouseButton(pointer.x,pointer.y,0)
|
||||
}
|
||||
function beginDrag(){
|
||||
if(dragging||!rfb())return
|
||||
dragging=true
|
||||
rfb()._handleMouseButton(pointer.x,pointer.y,1)
|
||||
}
|
||||
function endDrag(){
|
||||
if(!dragging||!rfb())return
|
||||
rfb()._handleMouseButton(pointer.x,pointer.y,0)
|
||||
dragging=false
|
||||
}
|
||||
function clearLongPress(){if(longPressTimer!==null){clearTimeout(longPressTimer);longPressTimer=null}}
|
||||
function touchAverage(touches){let total=0;for(const touch of touches)total+=touch.clientY;return touches.length?total/touches.length:0}
|
||||
function wheel(direction){
|
||||
const current=rfb()
|
||||
if(!current)return
|
||||
const mask=direction>0?8:16
|
||||
current._handleMouseButton(pointer.x,pointer.y,mask)
|
||||
current._handleMouseButton(pointer.x,pointer.y,0)
|
||||
}
|
||||
function consume(event){event.preventDefault();event.stopImmediatePropagation()}
|
||||
function onTouchStart(event){
|
||||
consume(event)
|
||||
canvas()?.focus()
|
||||
if(event.touches.length>=2){
|
||||
clearLongPress();endDrag();primaryTouch=null;scrollY=touchAverage(event.touches);scrollRemainder=0;return
|
||||
}
|
||||
const touch=event.changedTouches[0]
|
||||
if(!touch)return
|
||||
syncPointer()
|
||||
primaryTouch=touch.identifier
|
||||
origin={x:touch.clientX,y:touch.clientY}
|
||||
lastPoint={...origin}
|
||||
moved=false
|
||||
longPressed=false
|
||||
secondTap=Date.now()-lastTapAt<DOUBLE_TAP_DELAY
|
||||
clearLongPress()
|
||||
if(!secondTap)longPressTimer=setTimeout(()=>{longPressed=true;click(4);navigator.vibrate?.(25)},LONG_PRESS_DELAY)
|
||||
}
|
||||
function onTouchMove(event){
|
||||
consume(event)
|
||||
if(event.touches.length>=2){
|
||||
clearLongPress()
|
||||
const next=touchAverage(event.touches)
|
||||
if(scrollY!==null){
|
||||
scrollRemainder+=next-scrollY
|
||||
while(Math.abs(scrollRemainder)>=SCROLL_STEP){const direction=Math.sign(scrollRemainder);wheel(direction);scrollRemainder-=direction*SCROLL_STEP}
|
||||
}
|
||||
scrollY=next
|
||||
return
|
||||
}
|
||||
const touch=[...event.touches].find(item=>item.identifier===primaryTouch)
|
||||
if(!touch||!lastPoint||!origin)return
|
||||
const total=Math.hypot(touch.clientX-origin.x,touch.clientY-origin.y)
|
||||
if(total>MOVE_THRESHOLD){moved=true;clearLongPress();if(secondTap)beginDrag()}
|
||||
movePointer(touch.clientX-lastPoint.x,touch.clientY-lastPoint.y)
|
||||
lastPoint={x:touch.clientX,y:touch.clientY}
|
||||
}
|
||||
function onTouchEnd(event){
|
||||
consume(event)
|
||||
if(event.touches.length>=2){scrollY=touchAverage(event.touches);return}
|
||||
if(event.touches.length===1){
|
||||
scrollY=null
|
||||
const touch=event.touches[0]
|
||||
primaryTouch=touch.identifier;origin={x:touch.clientX,y:touch.clientY};lastPoint={...origin};return
|
||||
}
|
||||
clearLongPress();endDrag()
|
||||
if(!moved&&!longPressed){click(1);lastTapAt=Date.now()}else if(moved){lastTapAt=0}
|
||||
primaryTouch=null;origin=null;lastPoint=null;scrollY=null;scrollRemainder=0;secondTap=false
|
||||
}
|
||||
function onTouchCancel(event){
|
||||
consume(event);clearLongPress();endDrag()
|
||||
primaryTouch=null;origin=null;lastPoint=null;scrollY=null;scrollRemainder=0;secondTap=false;moved=false
|
||||
}
|
||||
function installTouchSurface(target){
|
||||
if(!touchSurface){
|
||||
touchSurface=document.createElement('div')
|
||||
touchSurface.id='codexpi-relative-touch-surface'
|
||||
touchSurface.setAttribute('aria-label','Pavé tactile relatif')
|
||||
// noVNC draws its software cursor at z-index 65535. Stay immediately
|
||||
// below it so touches hit this surface while the pointer remains visible.
|
||||
Object.assign(touchSurface.style,{position:'fixed',zIndex:'65534',background:'transparent',touchAction:'none',userSelect:'none'})
|
||||
touchSurface.addEventListener('touchstart',onTouchStart,{capture:true,passive:false})
|
||||
touchSurface.addEventListener('touchmove',onTouchMove,{capture:true,passive:false})
|
||||
touchSurface.addEventListener('touchend',onTouchEnd,{capture:true,passive:false})
|
||||
touchSurface.addEventListener('touchcancel',onTouchCancel,{capture:true,passive:false})
|
||||
document.body.appendChild(touchSurface)
|
||||
}
|
||||
const rect=target.getBoundingClientRect()
|
||||
Object.assign(touchSurface.style,{left:`${rect.left}px`,top:`${rect.top}px`,width:`${rect.width}px`,height:`${rect.height}px`,display:rect.width&&rect.height?'block':'none'})
|
||||
}
|
||||
function install(){
|
||||
const target=canvas()
|
||||
if(!target){if(touchSurface)touchSurface.style.display='none';return}
|
||||
target.style.touchAction='none'
|
||||
target.style.userSelect='none'
|
||||
installTouchSurface(target)
|
||||
if(target!==installedCanvas){
|
||||
installedCanvas=target
|
||||
syncPointer()
|
||||
}
|
||||
}
|
||||
|
||||
window.codexpiTouchpad={click,install}
|
||||
window.setInterval(install,500)
|
||||
install()
|
||||
Reference in New Issue
Block a user