feat: publish CodexPi contest snapshot
Some checks are pending
Qualité / tests (push) Waiting to run

This commit is contained in:
CodexPi
2026-11-14 20:23:59 +01:00
commit 995b17a6e2
295 changed files with 36803 additions and 0 deletions

View 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()