added more documentation
This commit is contained in:
parent
c0405e344c
commit
65e38dc898
|
@ -0,0 +1,2 @@
|
|||
git remote | grep codeberg || git remote add codeberg git@codeberg.org:xrsh/xrsh-com.git
|
||||
git remote | grep c-frame || git remote add c-frame git@github.com:c-frame/xrsh-com.git
|
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env -S awk -f
|
||||
# a no-nonsense source-to-markdown generator which scans for:
|
||||
#
|
||||
# /**
|
||||
# * # foo
|
||||
# *
|
||||
# * this is markdown $(cat bar.md)
|
||||
# */
|
||||
#
|
||||
# var foo; // comment with 2 leading spaces is markdown too $(date)
|
||||
#
|
||||
# easily refactorable to hash-based languages (py/bash/perl/lua e.g.)
|
||||
# by changing the regexes
|
||||
#
|
||||
|
||||
BEGIN{
|
||||
# printf README.md until '# Component List'
|
||||
system("grep -B9999 '# Component List' README.md")
|
||||
print ""
|
||||
}
|
||||
|
||||
/\$\(/ { cmd=$0;
|
||||
gsub(/^.*\$\(/,"",cmd);
|
||||
gsub(/\).*/,"",cmd);
|
||||
cmd | getline stdout; close(cmd);
|
||||
sub(/\$\(.*\)/,stdout);
|
||||
}
|
||||
/\/\*\*/ { doc=1; sub(/^.*\/\*/,""); }
|
||||
doc && /\*\// { doc=0;
|
||||
sub(/[[:space:]]*\*\/.*/,"");
|
||||
sub(/^[[:space:]]*\*[[:space:]]?/,"");
|
||||
print
|
||||
}
|
||||
doc && /^[[:space:]]*\*/ { sub(/^[[:space:]]*\*[[:space:]]?/,"");
|
||||
print
|
||||
}
|
||||
#!doc && /\/\/ / { sub(".*// ","");
|
||||
# sub("# ","\n# ");
|
||||
# sub("> ","\n> ");
|
||||
# print
|
||||
# }
|
149
README.md
149
README.md
|
@ -17,6 +17,10 @@ Characteristics:
|
|||
<a-entity helloworld="foo:1" class="cubes" name="box">
|
||||
```
|
||||
|
||||
See component list below
|
||||
|
||||
> this README.md is generated by running `echo "$(./README.awk com/*.js)" > README.md`
|
||||
|
||||
## Funding
|
||||
|
||||
This project is partially funded through [NGI0 Entrust](https://nlnet.nl/entrust), a fund established by [NLnet](https://nlnet.nl) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) program. Learn more at the [NLnet project page](https://nlnet.nl/project/xrsh).
|
||||
|
@ -24,3 +28,148 @@ This project is partially funded through [NGI0 Entrust](https://nlnet.nl/entrust
|
|||
[<img src="https://nlnet.nl/logo/banner.png" alt="NLnet foundation logo" width="20%" />](https://nlnet.nl)
|
||||
[<img src="https://nlnet.nl/image/logos/NGI0_tag.svg" alt="NGI Zero Logo" width="20%" />](https://nlnet.nl/entrust)
|
||||
|
||||
# Component List
|
||||
|
||||
|
||||
## [data_events](com/data_events.js)
|
||||
|
||||
allows components to react to data changes
|
||||
|
||||
```html
|
||||
<script>
|
||||
AFRAME.registerComponent('mycom',{
|
||||
init: function(){ this.data.foo = 1 },
|
||||
event: {
|
||||
foo: (e) => alert("I was updated!")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<a-entity mycom data_events/>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## [html-as-texture-in-xr](com/html-as-texture-in-xr.js)
|
||||
|
||||
shows domid **only** in immersive mode
|
||||
(wrapper around [aframe-htmlmesh](https://ada.is/aframe-htmlmesh/)
|
||||
|
||||
It also sets class 'XR' to the (HTML) body-element in immersive mode.
|
||||
This allows CSS (in [dom component](com/dom.js)) to visually update accordingly.
|
||||
|
||||
> depends on [AFRAME.utils.require](com/require.js)
|
||||
|
||||
```html
|
||||
<style type="text/css">
|
||||
.XR #foo { color:red; }
|
||||
</style>
|
||||
|
||||
<a-entity html-as-texture-in-xr="domid: #foo">
|
||||
<b id="foo">hello</b>
|
||||
</a-entitiy>
|
||||
```
|
||||
|
||||
| property | type |
|
||||
|--------------|--------------------|
|
||||
| `domid` | `string` |
|
||||
|
||||
| event | target | info |
|
||||
|--------------|------------|--------------------------------------|
|
||||
| `3D` | a-scene | fired when going into immersive mode |
|
||||
| `2D` | a-scene | fired when leaving immersive mode |
|
||||
|
||||
|
||||
|
||||
## [isoterminal](com/isoterminal.js)
|
||||
|
||||
Renders a windowed terminal in both (non)immersive mode.
|
||||
It displays an interactive javascript console or boots into
|
||||
a Linux ISO image (via WASM).
|
||||
|
||||
```html
|
||||
<a-entity isoterminal="iso: xrsh.iso" position="0 1.6 -0.3"></a-entity>
|
||||
```
|
||||
|
||||
> depends on [AFRAME.utils.require](com/require.js)
|
||||
|
||||
| property | type | default | info |
|
||||
|------------------|-----------|------------------------|------|
|
||||
| `iso` | `string` | https`//forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" | |
|
||||
| `overlayfs` | `string` | *WORK-IN-PROGRESS* | |
|
||||
| `width` | `number` | 800 ||
|
||||
| `height` | `number` | 600 ||
|
||||
| `depth` | `number` | 0.03 ||
|
||||
| `lineHeight` | `number` | 18 ||
|
||||
| `prompt` | `boolean` | true | boot straight into ISO or give user choice |
|
||||
| `padding` | `number`` | 18 | |
|
||||
| `maximized` | `boolean` | false | |
|
||||
| `minimized` | `boolean` | false | |
|
||||
| `muteUntilPrompt`| `boolean` | true | mute stdout until a prompt is detected in ISO |
|
||||
| `HUD` | `boolean` | false | link to camera movement |
|
||||
| `transparent` | `boolean` | false | heavy, needs good gpu |
|
||||
| `memory` | `number` | 60 | VM memory (in MB) [NOTE` quest or smartphone webworker might crash > 40mb ] |
|
||||
| `bufferLatency` | `number` | 1 | in ms` bufferlatency from webworker to term (batch-update every char to texture) |
|
||||
| `debug` | `boolean` | false | |
|
||||
| `emulator` | `string` | fbterm | terminal emulator |
|
||||
|
||||
> for more info see [xrsh.isvery.ninja](https://xrsh.isvery.ninja)
|
||||
|
||||
Component design:
|
||||
```
|
||||
css/html template
|
||||
|
||||
┌─────────┐ ┌────────────┐ exit-AR
|
||||
┌───────►│ com/dom ┼──►│ com/window ├───────────────── exit-VR ◄─┐
|
||||
│ └─────────┘ └───────────┬┘ │
|
||||
│ │ │
|
||||
┌──────────┴────────┐ │ ┌───────────┐ ┌─────────────────────────────┐
|
||||
│ com/isoterminal ├────────────────────────────►│com/term.js│ │com/html-as-texture-in-XR.js │
|
||||
└────────┬─┬────────┘ │ └──┬─────┬▲─┘ └─────────────────────────────┘
|
||||
│ │ ┌────────┐ ┌──▼──────▼──────┐ ││ │
|
||||
│ └───────►│ plane ├─────►text───┼►div#isoterminal│◄────────────────── enter-VR │
|
||||
│ └────────┘ └────────────────┘ enter-AR ◄─┘
|
||||
│ │
|
||||
│ │
|
||||
│ ISOTerminal.js
|
||||
│ ┌───────────────────────────┐
|
||||
│ │ com/isoterminal/worker.js ├
|
||||
│ └──────────────┌────────────┤
|
||||
│ │ │ v86.js │
|
||||
│ │ │ feat/*.js │
|
||||
│ │ │ libv86.js │
|
||||
│ │ └────────────┘
|
||||
│ │
|
||||
└─────────────────────┘
|
||||
|
||||
NOTE: For convenience reasons, events are forwarded between com/isoterminal.js, worker.js and ISOTerminal
|
||||
Instead of a melting pot of different functionnames, events are flowing through everything (ISOTerminal.emit())
|
||||
```
|
||||
|
||||
|
||||
## [pastedrop](com/pastedrop.js)
|
||||
|
||||
detects user copy/paste and file dragdrop action
|
||||
and clipboard functions
|
||||
|
||||
```html
|
||||
<a-entity pastedrop/>
|
||||
```
|
||||
|
||||
| event | target | info |
|
||||
|--------------|--------|------------------------------------------|
|
||||
| `pasteFile` | self | always translates input to a File object |
|
||||
|
||||
|
||||
## [require](com/require('').js)
|
||||
|
||||
automatically requires dependencies
|
||||
|
||||
```javascript
|
||||
await AFRAME.utils.require( this.dependencies ) (*) autoload missing components
|
||||
await AFRAME.utils.require( this.el.getAttributeNames() ) (*) autoload missing components
|
||||
await AFRAME.utils.require({foo: "https://foo.com/aframe/components/foo.js"},this)
|
||||
await AFRAME.utils.require(["./app/foo.js","foo.css"],this)
|
||||
```
|
||||
|
||||
> (*) = prefixes baseURL AFRAME.utils.require.baseURL ('./com/' e.g.)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* ## data_events
|
||||
/**
|
||||
* ## [data_events](com/data_events.js)
|
||||
*
|
||||
* allows components to react to data changes
|
||||
*
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* ## dom
|
||||
* ## [dom](com/dom.js)
|
||||
*
|
||||
* instances reactive DOM component from AFRAME component's `dom` metadata
|
||||
*
|
||||
|
@ -36,10 +36,6 @@ if( !AFRAME.components.dom ){
|
|||
|
||||
AFRAME.registerComponent('dom',{
|
||||
|
||||
requires: {
|
||||
"requestAnimationFrameXR": "com/requestAnimationFrameXR.js"
|
||||
},
|
||||
|
||||
init: function(){
|
||||
Object.values(this.el.components)
|
||||
.map( (c) => {
|
||||
|
|
|
@ -1,3 +1,35 @@
|
|||
/**
|
||||
* ## [html-as-texture-in-xr](com/html-as-texture-in-xr.js)
|
||||
*
|
||||
* shows domid **only** in immersive mode
|
||||
* (wrapper around [aframe-htmlmesh](https://ada.is/aframe-htmlmesh/)
|
||||
*
|
||||
* It also sets class 'XR' to the (HTML) body-element in immersive mode.
|
||||
* This allows CSS (in [dom component](com/dom.js)) to visually update accordingly.
|
||||
*
|
||||
* > depends on [AFRAME.utils.require](com/require.js)
|
||||
*
|
||||
* ```html
|
||||
* <style type="text/css">
|
||||
* .XR #foo { color:red; }
|
||||
* </style>
|
||||
*
|
||||
* <a-entity html-as-texture-in-xr="domid: #foo">
|
||||
* <b id="foo">hello</b>
|
||||
* </a-entitiy>
|
||||
* ```
|
||||
*
|
||||
* | property | type |
|
||||
* |--------------|--------------------|
|
||||
* | `domid` | `string` |
|
||||
*
|
||||
* | event | target | info |
|
||||
* |--------------|------------|--------------------------------------|
|
||||
* | `3D` | a-scene | fired when going into immersive mode |
|
||||
* | `2D` | a-scene | fired when leaving immersive mode |
|
||||
*
|
||||
*/
|
||||
|
||||
if( !AFRAME.components['html-as-texture-in-xr'] ){
|
||||
|
||||
AFRAME.registerComponent('html-as-texture-in-xr', {
|
||||
|
|
|
@ -1,5 +1,40 @@
|
|||
/*
|
||||
/**
|
||||
* ## [isoterminal](com/isoterminal.js)
|
||||
*
|
||||
* Renders a windowed terminal in both (non)immersive mode.
|
||||
* It displays an interactive javascript console or boots into
|
||||
* a Linux ISO image (via WASM).
|
||||
*
|
||||
* ```html
|
||||
* <a-entity isoterminal="iso: xrsh.iso" position="0 1.6 -0.3"></a-entity>
|
||||
* ```
|
||||
*
|
||||
* > depends on [AFRAME.utils.require](com/require.js)
|
||||
*
|
||||
* | property | type | default | info |
|
||||
* |------------------|-----------|------------------------|------|
|
||||
* | `iso` | `string` | https`//forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" | |
|
||||
* | `overlayfs` | `string` | *WORK-IN-PROGRESS* | |
|
||||
* | `width` | `number` | 800 ||
|
||||
* | `height` | `number` | 600 ||
|
||||
* | `depth` | `number` | 0.03 ||
|
||||
* | `lineHeight` | `number` | 18 ||
|
||||
* | `prompt` | `boolean` | true | boot straight into ISO or give user choice |
|
||||
* | `padding` | `number`` | 18 | |
|
||||
* | `maximized` | `boolean` | false | |
|
||||
* | `minimized` | `boolean` | false | |
|
||||
* | `muteUntilPrompt`| `boolean` | true | mute stdout until a prompt is detected in ISO |
|
||||
* | `HUD` | `boolean` | false | link to camera movement |
|
||||
* | `transparent` | `boolean` | false | heavy, needs good gpu |
|
||||
* | `memory` | `number` | 60 | VM memory (in MB) [NOTE` quest or smartphone webworker might crash > 40mb ] |
|
||||
* | `bufferLatency` | `number` | 1 | in ms` bufferlatency from webworker to term (batch-update every char to texture) |
|
||||
* | `debug` | `boolean` | false | |
|
||||
* | `emulator` | `string` | fbterm | terminal emulator |
|
||||
*
|
||||
* > for more info see [xrsh.isvery.ninja](https://xrsh.isvery.ninja)
|
||||
*
|
||||
* Component design:
|
||||
* ```
|
||||
* css/html template
|
||||
*
|
||||
* ┌─────────┐ ┌────────────┐ exit-AR
|
||||
|
@ -27,6 +62,7 @@
|
|||
*
|
||||
* NOTE: For convenience reasons, events are forwarded between com/isoterminal.js, worker.js and ISOTerminal
|
||||
* Instead of a melting pot of different functionnames, events are flowing through everything (ISOTerminal.emit())
|
||||
* ```
|
||||
*/
|
||||
|
||||
if( typeof AFRAME != 'undefined '){
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
/**
|
||||
* ## [pastedrop](com/pastedrop.js)
|
||||
*
|
||||
* detects user copy/paste and file dragdrop action
|
||||
* and clipboard functions
|
||||
*
|
||||
* ```html
|
||||
* <a-entity pastedrop/>
|
||||
* ```
|
||||
*
|
||||
* | event | target | info |
|
||||
* |--------------|--------|------------------------------------------|
|
||||
* | `pasteFile` | self | always translates input to a File object |
|
||||
*/
|
||||
|
||||
AFRAME.registerComponent('pastedrop', {
|
||||
schema: {
|
||||
foo: { type:"string"}
|
||||
|
@ -22,16 +37,16 @@ AFRAME.registerComponent('pastedrop', {
|
|||
})
|
||||
},
|
||||
|
||||
//getClipboard: function(){
|
||||
// navigator.clipboard.readText()
|
||||
// .then( async (base64) => {
|
||||
// let mimetype = base64.replace(/;base64,.*/,'')
|
||||
// let data = base64.replace(/.*;base64,/,'')
|
||||
// let type = this.textHeuristic(data)
|
||||
// const term = document.querySelector('[isoterminal]').components.isoterminal.term
|
||||
// this.el.emit('pasteFile',{}) /*TODO* data incompatible */
|
||||
// })
|
||||
//},
|
||||
getClipboard: function(){
|
||||
navigator.clipboard.readText()
|
||||
.then( async (base64) => {
|
||||
let mimetype = base64.replace(/;base64,.*/,'')
|
||||
let data = base64.replace(/.*;base64,/,'')
|
||||
let type = this.textHeuristic(data)
|
||||
const term = document.querySelector('[isoterminal]').components.isoterminal.term
|
||||
this.el.emit('pasteFile',{}) /*TODO* data incompatible */
|
||||
})
|
||||
},
|
||||
|
||||
onDrop: function(e){
|
||||
e.preventDefault()
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
// usage:
|
||||
//
|
||||
// await AFRAME.utils.require( this.dependencies ) (*) autoload missing components
|
||||
// await AFRAME.utils.require( this.el.getAttributeNames() ) (*) autoload missing components
|
||||
// await AFRAME.utils.require({foo: "https://foo.com/aframe/components/foo.js"},this)
|
||||
// await AFRAME.utils.require(["./app/foo.js","foo.css"],this)
|
||||
//
|
||||
// (*) = prefixes baseURL AFRAME.utils.require.baseURL ('./com/' e.g.)
|
||||
//
|
||||
/**
|
||||
* ## [require](com/require('').js)
|
||||
*
|
||||
* automatically requires dependencies
|
||||
*
|
||||
* ```javascript
|
||||
* await AFRAME.utils.require( this.dependencies ) (*) autoload missing components
|
||||
* await AFRAME.utils.require( this.el.getAttributeNames() ) (*) autoload missing components
|
||||
* await AFRAME.utils.require({foo: "https://foo.com/aframe/components/foo.js"},this)
|
||||
* await AFRAME.utils.require(["./app/foo.js","foo.css"],this)
|
||||
* ```
|
||||
*
|
||||
* > (*) = prefixes baseURL AFRAME.utils.require.baseURL ('./com/' e.g.)
|
||||
*/
|
||||
AFRAME.utils.require = function(arr_or_obj,opts){
|
||||
opts = opts || {}
|
||||
let i = 0
|
||||
|
|
|
@ -1,3 +1,28 @@
|
|||
* ## [window](com/window.js)
|
||||
*
|
||||
* wraps a draggable window around a dom id or [dom](com/dom.js) component.
|
||||
*
|
||||
* ```html
|
||||
* <a-entity window="dom: #mydiv"/>
|
||||
* ```
|
||||
*
|
||||
* > depends on [AFRAME.utils.require](com/require.js)
|
||||
*
|
||||
* | property | type | default | info |
|
||||
* |------------------|-----------|------------------------|------|
|
||||
* | `title` |`string` | "" | |
|
||||
* | `width` |`string` | | |
|
||||
* | `height` |`string` | 260px | |
|
||||
* | `uid` |`string` | | |
|
||||
* | `attach` |`selector` | | |
|
||||
* | `dom` |`selector` | | |
|
||||
* | `max` |`boolean` | false | |
|
||||
* | `min` |`boolean` | false | |
|
||||
* | `x` |`string` | "center" | |
|
||||
* | `y` |`string` | "center" | |
|
||||
* | `class` |`array` | [] | |
|
||||
*/
|
||||
|
||||
AFRAME.registerComponent('window', {
|
||||
schema:{
|
||||
title: {type:'string',"default":"title"},
|
||||
|
|
268
com/xterm.js
268
com/xterm.js
|
@ -1,268 +0,0 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* 2019 Mauve Ranger
|
||||
* 2024 Leon van Kammen
|
||||
*/
|
||||
|
||||
let terminalInstance = 0
|
||||
|
||||
const TERMINAL_THEME = {
|
||||
theme_foreground: {
|
||||
// 'default': '#ffffff'
|
||||
},
|
||||
theme_background: {
|
||||
// 'default': '#000'
|
||||
},
|
||||
theme_cursor: {
|
||||
// 'default': '#ffffff'
|
||||
},
|
||||
theme_selection: {
|
||||
// 'default': 'rgba(255, 255, 255, 0.3)'
|
||||
},
|
||||
theme_black: {
|
||||
// 'default': '#000000'
|
||||
},
|
||||
theme_red: {
|
||||
// 'default': '#e06c75'
|
||||
},
|
||||
theme_brightRed: {
|
||||
// 'default': '#e06c75'
|
||||
},
|
||||
theme_green: {
|
||||
// 'default': '#A4EFA1'
|
||||
},
|
||||
theme_brightGreen: {
|
||||
// 'default': '#A4EFA1'
|
||||
},
|
||||
theme_brightYellow: {
|
||||
// 'default': '#EDDC96'
|
||||
},
|
||||
theme_yellow: {
|
||||
// 'default': '#EDDC96'
|
||||
},
|
||||
theme_magenta: {
|
||||
// 'default': '#e39ef7'
|
||||
},
|
||||
theme_brightMagenta: {
|
||||
// 'default': '#e39ef7'
|
||||
},
|
||||
theme_cyan: {
|
||||
// 'default': '#5fcbd8'
|
||||
},
|
||||
theme_brightBlue: {
|
||||
// 'default': '#5fcbd8'
|
||||
},
|
||||
theme_brightCyan: {
|
||||
// 'default': '#5fcbd8'
|
||||
},
|
||||
theme_blue: {
|
||||
// 'default': '#5fcbd8'
|
||||
},
|
||||
theme_white: {
|
||||
// 'default': '#d0d0d0'
|
||||
},
|
||||
theme_brightBlack: {
|
||||
// 'default': '#808080'
|
||||
},
|
||||
theme_brightWhite: {
|
||||
// 'default': '#ffffff'
|
||||
}
|
||||
}
|
||||
|
||||
AFRAME.registerComponent('xterm', {
|
||||
schema: Object.assign({
|
||||
XRrenderer: { type: 'string', default: 'canvas', },
|
||||
cols: { type: 'number', default: 110, },
|
||||
rows: { type: 'number', default: Math.floor( (window.innerHeight * 0.7 ) * 0.054 ) },
|
||||
canvasLatency:{ type:'number', default: 200 }
|
||||
}, TERMINAL_THEME),
|
||||
|
||||
write: function(message) {
|
||||
this.term.write(message)
|
||||
},
|
||||
init: function () {
|
||||
const terminalElement = document.createElement('div')
|
||||
terminalElement.setAttribute('style', `
|
||||
width: 800px;
|
||||
height: ${Math.floor( 800 * 0.527 )}px;
|
||||
overflow: hidden;
|
||||
`)
|
||||
|
||||
this.el.terminalElement = terminalElement
|
||||
|
||||
if( this.data.XRrenderer == 'canvas' ){
|
||||
// setup slightly bigger black backdrop (this.el.getObject3D("mesh"))
|
||||
// and terminal text (this.el.planeText.getObject("mesh"))
|
||||
const w = 2;
|
||||
const h = (this.data.rows*5/this.data.cols)
|
||||
this.el.setAttribute("geometry",`primitive: box; width:${w}; height:${h}; depth: -0.12`)
|
||||
this.el.setAttribute("material","shader:flat; color:black; opacity:0.5; transparent:true; ")
|
||||
this.el.planeText = document.createElement('a-entity')
|
||||
this.el.planeText.setAttribute("geometry",`primitive: plane; width:${w}; height:${h}`)
|
||||
this.el.appendChild(this.el.planeText)
|
||||
|
||||
// we switch between dom/canvas rendering because canvas looks pixely in nonimmersive mode
|
||||
this.el.sceneEl.addEventListener('enter-vr', this.enterImmersive.bind(this) )
|
||||
this.el.sceneEl.addEventListener('enter-ar', this.enterImmersive.bind(this) )
|
||||
this.el.sceneEl.addEventListener('exit-vr', this.exitImmersive.bind(this) )
|
||||
this.el.sceneEl.addEventListener('exit-ar', this.exitImmersive.bind(this) )
|
||||
|
||||
}
|
||||
|
||||
this.tick = AFRAME.utils.throttleLeadingAndTrailing( () => {
|
||||
if( this.el.sceneEl.renderer.xr.isPresenting ){
|
||||
// workaround
|
||||
// xterm relies on window.requestAnimationFrame (which is not called WebXR immersive mode)
|
||||
//this.term._core.viewport._innerRefresh()
|
||||
this.term._core.renderer._renderDebouncer._innerRefresh()
|
||||
}
|
||||
}, this.data.canvasLatency)
|
||||
|
||||
// Build up a theme object
|
||||
const theme = Object.keys(this.data).reduce((theme, key) => {
|
||||
if (!key.startsWith('theme_')) return theme
|
||||
const data = this.data[key]
|
||||
if(!data) return theme
|
||||
theme[key.slice('theme_'.length)] = data
|
||||
return theme
|
||||
}, {})
|
||||
|
||||
this.fontSize = 14
|
||||
|
||||
const term = this.term = new Terminal({
|
||||
logLevel:"off",
|
||||
theme: theme,
|
||||
allowTransparency: true,
|
||||
cursorBlink: true,
|
||||
disableStdin: false,
|
||||
rows: this.data.rows,
|
||||
cols: this.data.cols,
|
||||
fontFamily: 'Cousine, monospace',
|
||||
fontSize: this.fontSize,
|
||||
lineHeight: 1.15,
|
||||
useFlowControl: true,
|
||||
rendererType: this.renderType // 'dom' // 'canvas'
|
||||
})
|
||||
|
||||
this.term.open(terminalElement)
|
||||
this.term.focus()
|
||||
this.setRenderType('dom')
|
||||
|
||||
terminalElement.querySelector('.xterm-viewport').style.background = 'transparent'
|
||||
|
||||
// now we can scale canvases to the parent element
|
||||
const $screen = terminalElement.querySelector('.xterm-screen')
|
||||
$screen.style.width = '100%'
|
||||
|
||||
term.on('refresh', AFRAME.utils.throttleLeadingAndTrailing( () => this.update(), 150 ) )
|
||||
term.on('data', (data) => {
|
||||
this.el.emit('xterm-input', data)
|
||||
})
|
||||
|
||||
this.el.addEventListener('serial-output-byte', (e) => {
|
||||
const byte = e.detail
|
||||
var chr = String.fromCharCode(byte);
|
||||
this.term.write(chr)
|
||||
})
|
||||
|
||||
this.el.addEventListener('serial-output-string', (e) => {
|
||||
this.term.write(e.detail)
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
update: function(){
|
||||
if( this.renderType == 'canvas' ){
|
||||
const material = this.el.planeText.getObject3D('mesh').material
|
||||
if (!material.map ) return
|
||||
if( this.cursorCanvas ) this.canvasContext.drawImage(this.cursorCanvas, 0,0)
|
||||
else console.log("no cursorCanvas")
|
||||
material.map.needsUpdate = true
|
||||
//material.needsUpdate = true
|
||||
}
|
||||
},
|
||||
|
||||
setRenderType: function(type){
|
||||
|
||||
|
||||
if( type.match(/(dom|canvas)/) ){
|
||||
|
||||
if( type == 'dom'){
|
||||
this.el.dom.appendChild(this.el.terminalElement)
|
||||
this.term.setOption('fontSize', this.fontSize )
|
||||
this.term.setOption('rendererType',type )
|
||||
this.renderType = type
|
||||
}
|
||||
|
||||
if( type == 'canvas'){
|
||||
this.el.appendChild(this.el.terminalElement)
|
||||
this.term.setOption('fontSize', this.fontSize * 3 )
|
||||
this.term.setOption('rendererType',type )
|
||||
this.renderType = type
|
||||
this.update()
|
||||
setTimeout( () => {
|
||||
this.canvas = this.el.terminalElement.querySelector('.xterm-text-layer')
|
||||
this.canvas.id = "xterm-canvas"
|
||||
this.canvasContext = this.canvas.getContext('2d')
|
||||
this.cursorCanvas = this.el.terminalElement.querySelector('.xterm-cursor-layer')
|
||||
// Create a texture from the canvas
|
||||
const canvasTexture = new THREE.Texture(this.canvas)
|
||||
//canvasTexture.minFilter = THREE.NearestFilter //LinearFilter
|
||||
//canvasTexture.magFilter = THREE.LinearMipMapLinearFilter //THREE.NearestFilter //LinearFilter
|
||||
canvasTexture.needsUpdate = true; // Ensure the texture updates
|
||||
let plane = this.el.planeText.getObject3D("mesh") //this.el.getObject3D('mesh')
|
||||
if( plane.material ) plane.material.dispose()
|
||||
plane.material = new THREE.MeshBasicMaterial({
|
||||
map: canvasTexture, // Set the texture from the canvas
|
||||
transparent: false, // Set transparency
|
||||
//side: THREE.DoubleSide // Set to double-sided rendering
|
||||
//blending: THREE.AdditiveBlending
|
||||
});
|
||||
this.el.object3D.scale.x = 0.2
|
||||
this.el.object3D.scale.y = 0.2
|
||||
this.el.object3D.scale.z = 0.2
|
||||
},100)
|
||||
}
|
||||
|
||||
this.el.terminalElement.style.opacity = type == 'canvas' ? 0 : 1
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
enterImmersive: function(){
|
||||
if( this.mode == 'immersive' ) return
|
||||
this.el.object3D.visible = true
|
||||
this.mode = "immersive"
|
||||
this.setRenderType('canvas')
|
||||
this.term.focus()
|
||||
},
|
||||
|
||||
exitImmersive: function(){
|
||||
if( this.mode == 'nonimmersive' ) return
|
||||
this.el.object3D.visible = false
|
||||
this.mode = "nonimmersive"
|
||||
this.setRenderType('dom')
|
||||
},
|
||||
|
||||
})
|
Loading…
Reference in New Issue