window buttons in XR (better)
Some checks failed
/ mirror_to_github (push) Failing after 5m44s
/ test (push) Successful in 13s

This commit is contained in:
Leon van Kammen 2025-07-01 16:14:56 +02:00
parent f2d0c36f09
commit 8ac69c4aed
4 changed files with 113 additions and 43 deletions

@ -1 +1 @@
Subproject commit 2b3c41fa6e4fa3a41edaee5ed325601a92c51bd8
Subproject commit c3d9b7aaed3f7409c4e3cbad1096eb3c9fb58fe4

BIN
xrsh.com

Binary file not shown.

View file

@ -1 +1 @@
4bea9e66bcb7e6b084fcafb16a06c28fea99a74b3edd41b7930ab2d78292c6e7 xrsh.com
a9d7554d07c06bdcc06568b555010afd94a637c9dcb2041f505707d498a6eaec xrsh.com

152
xrsh.js
View file

@ -23,9 +23,11 @@ document.head.innerHTML += `<style type='text/css'>
--xrsh-dark-gray: #343334;
--xrsh-gray: #424280;
--xrsh-white: #fdfdfd;
--xrsh-black: #001c;
--xrsh-light-gray: #efefef;
--xrsh-lighter-gray: #e4e2fb96;
--xrsh-window-radius: 10px;
--xrsh-control-radius: 7px;
--xrsh-font-sans-serif: system-ui, -apple-system, segoe ui, roboto, ubuntu, helvetica, cantarell, noto sans, sans-serif;
--xrsh-font-monospace: menlo, monaco, lucida console, liberation mono, dejavu sans mono, bitstream vera sans mono, courier new, monospace, serif;
--xrsh-font-size-0: 12px;
@ -60,7 +62,7 @@ a,a:visited,a:active{
*/
button,.btn,input[type=submit]{
border-radius:7px;
border-radius: var(--xrsh-control-radius);
background: var(--xrsh-primary);
color: var(--xrsh-primary-fg);
transition:0.3s;
@ -104,31 +106,55 @@ button,input,.btn{
*/
.winbox {
box-shadow:none !important;
background:transparent !important;
background: transparent !important;
}
.winbox .wb-body{
background: var(--xrsh-dark-gray);
margin-top: -6px !important;
/*margin-top: -6px !important;*/
z-index: 100;
border: 1px solid #FFF3;
border-radius:7px;
border-radius:0px 0px var(--xrsh-window-radius) var(--xrsh-window-radius);
}
.winbox .wb-header{
background:transparent !important;
background: var(--xrsh-dark-gray);
z-index:1000;
font-weight: bold;
border-radius: var(--xrsh-window-radius) var(--xrsh-window-radius) 0px 0px;
line-height:36px;
}
.winbox.max .wb-header{
border-radius:0px;
}
.XR .winbox .wb-header{
border-radius:0px !important;
margin-top:0px;
}
.XR .winbox .wb-control .xr {
display:block;
}
.winbox .wb-control .xr {
display:none;
background:transparent !important;
position: absolute;
top: -2px;
cursor:pointer;
}
.winbox .wb-control .xr.xr-wb-close,
.winbox .wb-control .xr.xr-wb-min{
right: 2px;
}
.winbox .wb-body{
top:0px;
top: 35 !important;
padding-top:35px;
}
.winbox .wb-control{
padding-right:5px;
height:29px;
color:#FFF;
z-index:1001;
}
.winbox .wb-drag{
padding-left:19px;
@ -166,7 +192,6 @@ button,input,.btn{
background: var(--xrsh-white);
}
/*
* icons for non-XR (please source from https://css.gg
*
@ -251,6 +276,7 @@ e.removeClass=function(a){this.g.classList.remove(a);return this};e.toggleClass=
*
* | property | type | default | info |
* |-------------------|-----------|------------------------|------|
* | `title` | `string` | 'xrsh.iso' | window title |
* | `iso` | `string` | https`//forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" | |
* | `overlayfs` | `string` | '' | zip URL/file to autoextract on top of filesystem |
* | `width` | `number` | 800 ||
@ -310,6 +336,7 @@ if( typeof AFRAME != 'undefined '){
schema: {
iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" },
overlayfs: { type:"string"},
title: { type:"string", "default":"xrsh.iso"},
width: { type: 'number',"default": 800 },
height: { type: 'number',"default": 600 },
depth: { type: 'number',"default": 0.03 },
@ -380,6 +407,8 @@ if( typeof AFRAME != 'undefined '){
.isoterminal{
padding: ${me.com.data.padding}px;
margin-top:-60px;
padding-bottom:60px;
width:100%;
height:99%;
resize: both;
@ -482,18 +511,19 @@ if( typeof AFRAME != 'undefined '){
-webkit-animation:none;
}
.wb-body:has(> .isoterminal){
background: #000C;
.winbox#${me.el.uid} .wb-header{
background: var(--xrsh-black) !important;
}
.wb-body:has(> .isoterminal){
background: var(--xrsh-black);
overflow:hidden;
}
.XR .wb-body:has(> .isoterminal){
background: transparent;
.XR .isoterminal{
background: #000 !important;
}
.XR .isoterminal{
background: #000;
}
.isoterminal *{
font-size: 14px;
font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
@ -580,7 +610,7 @@ if( typeof AFRAME != 'undefined '){
this.term.emit('term_init', {instance, aEntity:this})
//instance.winbox.resize(720,380)
let size = `width: ${this.data.width}; height: ${this.data.height}`
instance.setAttribute("window", `title: xrsh.iso; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}; class: no-full, no-max, no-resize; grabbable: components.html.el.object3D.children.${this.el.children.length}`)
instance.setAttribute("window", `title: ${this.data.title}; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}; class: no-full, no-min, no-close, no-max, no-resize; grabbable: components.html.el.object3D.children.${this.el.children.length}`)
})
instance.addEventListener('window.oncreate', (e) => {
@ -736,6 +766,7 @@ if( typeof AFRAME != 'undefined '){
"scope": "/",
"theme_color": "#3367D6",
"shortcuts": [
/*
{
"name": "What is the latest news?",
"cli":{
@ -750,8 +781,9 @@ if( typeof AFRAME != 'undefined '){
"url": "/today?source=pwa",
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
}
*/
],
"description": "Hello world information",
"description": "Runs an .iso file",
"screenshots": [
{
"src": "/images/screenshot1.png",
@ -761,7 +793,7 @@ if( typeof AFRAME != 'undefined '){
}
],
"help":`
Helloworld application
XRSH application
This is a help file which describes the application.
It will be rendered thru troika text, and will contain
@ -874,9 +906,9 @@ if( !AFRAME.components.dom ){
this
.ensureOverlay()
.addCSS()
.createReactiveDOMElement()
.assignUniqueID()
.addCSS()
.scaleDOMvsXR()
.triggerKeyboardForInputs()
@ -1409,16 +1441,6 @@ AFRAME.registerComponent('window', {
setupWindow: async function(){
await AFRAME.utils.require(this.dependencies)
if( !this.el.dom ) return console.error('window element requires dom-component as dependency')
const close = () => {
let e = {halt:false}
this.el.emit('window.onclose',e)
if( e.halt ) return true
this.data.dom.style.display = 'none';
if( this.el.parentNode ) this.el.remove() //parentElement.remove( this.el )
this.data.dom.parentElement.remove()
return false
}
this.el.addEventListener('close', () => {
close()
this.el.winbox.close()
@ -1447,8 +1469,12 @@ AFRAME.registerComponent('window', {
this.el.components['obb-collider'].data.trackedObject3D = this.data.grabbable
this.el.components['obb-collider'].update()
},1000)
this.patchButtons(e)
},
onclose: close
onminimize: this.onminimize,
onrestore: this.onrestore,
onclose: this.onclose.bind(this),
});
this.data.dom.style.display = '' // show
@ -1468,7 +1494,46 @@ AFRAME.registerComponent('window', {
show: function(state){
this.el.dom.closest('.winbox').style.display = state ? '' : 'none'
},
onminimize: function(e){
if( AFRAME.scenes[0].renderer.xr.isPresenting ){
this.window.style.display = 'none'
}
},
onrestore: function(e){
if( AFRAME.scenes[0].renderer.xr.isPresenting ){
this.window.style.display = ''
}
},
onclose: function(){
let e = {halt:false}
this.el.emit('window.onclose',e)
if( e.halt ) return true
this.data.dom.style.display = 'none';
if( this.el.parentNode ) this.el.remove() //parentElement.remove( this.el )
this.data.dom.parentElement.remove()
return false
},
// the buttons don't work in XR because HTMLMesh does not understand onclick on divs
patchButtons: function(e){
let wEl = e.mount;
let controls = [...wEl.closest(".winbox").querySelectorAll(".wb-control span")]
controls.map( (c) => {
if( c.className.match(/wb-(close|min)/) ){
let btn = document.createElement("button")
btn.className = `xr xr-${c.className}`
btn.innerText = c.className == "wb-close" ? "x" : "_"
btn.addEventListener("click", (e) => { } )// this will bubble up (to click ancestor in XR)
c.appendChild(btn)
}
})
}
})
AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, margin ){
@ -2185,14 +2250,18 @@ function ISOTerminal(instance,opts){
ISOTerminal.prototype.emit = function(event,data,sender){
data = data || false
// *TODO* wrap certain events into this.preventFrameDrop( () => { .. }) to boost performance
const evObj = new CustomEvent(event, {detail: data} )
// forward event to worker/instance/AFRAME element or component-function
// this feels complex, but actually keeps event- and function-names more concise in codebase
this.dispatchEvent( evObj )
if( sender != "instance" && this.instance ) this.instance.dispatchEvent(evObj)
if( sender != "worker" && this.worker ) this.worker.postMessage({event,data}, PromiseWorker.prototype.getTransferable(data) )
if( sender !== undefined && typeof this[event] == 'function' ) this[event].apply(this, data && data.push ? data : [data] )
let fire = () => {
this.dispatchEvent( evObj )
if( sender != "instance" && this.instance ) this.instance.dispatchEvent(evObj)
if( sender != "worker" && this.worker ) this.worker.postMessage({event,data}, PromiseWorker.prototype.getTransferable(data) )
if( sender !== undefined && typeof this[event] == 'function' ) this[event].apply(this, data && data.push ? data : [data] )
}
if( event.match(/^serial/) ){
this.preventFrameDrop( fire )
}else fire()
}
ISOTerminal.addEventListener = (event,cb) => {
@ -4080,10 +4149,10 @@ if( typeof window.PromiseWorker != 'undefined' ){ // if xrsh v86 is able to run
}
ISOTerminal.prototype.redirectConsole = function(handler){
const log = console.log;
const dir = console.dir;
const err = console.error;
const warn = console.warn;
const log = console._log = console.log;
const dir = console._dir = console.dir;
const err = console._error = console.error;
const warn = console._warn = console.warn;
const addLineFeeds = (str) => typeof str == 'string' ? str.replace(/\n/g,"\r\n") : str
console.log = (...args)=>{
@ -4118,12 +4187,13 @@ ISOTerminal.prototype.enableConsole = function(opts){
let _str = typeof str == 'string' ? str : JSON.stringify(str)
let finalStr = "";
prefix = prefix ? prefix+' ' : ''
_str.trim().split("\n").map( (line) => {
String(_str).trim().split("\n").map( (line) => {
finalStr += `${opts.stdout ? '' : "\x1b[38;5;165m/dev/browser: \x1b[0m"}`+prefix+line+'\n'
})
if( opts.stdout ){
this.emit('serial-output-string', finalStr)
this.emit('serial-output-string', finalStr, "worker")
}else this.emit('append_file', ["/dev/browser/console",finalStr])
this.lastStr = finalStr
})
window.addEventListener('error', function(event) {