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">
|
<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
|
## 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).
|
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/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)
|
[<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
|
* 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
|
* instances reactive DOM component from AFRAME component's `dom` metadata
|
||||||
*
|
*
|
||||||
|
@ -36,10 +36,6 @@ if( !AFRAME.components.dom ){
|
||||||
|
|
||||||
AFRAME.registerComponent('dom',{
|
AFRAME.registerComponent('dom',{
|
||||||
|
|
||||||
requires: {
|
|
||||||
"requestAnimationFrameXR": "com/requestAnimationFrameXR.js"
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function(){
|
init: function(){
|
||||||
Object.values(this.el.components)
|
Object.values(this.el.components)
|
||||||
.map( (c) => {
|
.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'] ){
|
if( !AFRAME.components['html-as-texture-in-xr'] ){
|
||||||
|
|
||||||
AFRAME.registerComponent('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
|
* css/html template
|
||||||
*
|
*
|
||||||
* ┌─────────┐ ┌────────────┐ exit-AR
|
* ┌─────────┐ ┌────────────┐ exit-AR
|
||||||
|
@ -27,6 +62,7 @@
|
||||||
*
|
*
|
||||||
* NOTE: For convenience reasons, events are forwarded between com/isoterminal.js, worker.js and ISOTerminal
|
* 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())
|
* Instead of a melting pot of different functionnames, events are flowing through everything (ISOTerminal.emit())
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if( typeof AFRAME != 'undefined '){
|
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', {
|
AFRAME.registerComponent('pastedrop', {
|
||||||
schema: {
|
schema: {
|
||||||
foo: { type:"string"}
|
foo: { type:"string"}
|
||||||
|
@ -22,16 +37,16 @@ AFRAME.registerComponent('pastedrop', {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
//getClipboard: function(){
|
getClipboard: function(){
|
||||||
// navigator.clipboard.readText()
|
navigator.clipboard.readText()
|
||||||
// .then( async (base64) => {
|
.then( async (base64) => {
|
||||||
// let mimetype = base64.replace(/;base64,.*/,'')
|
let mimetype = base64.replace(/;base64,.*/,'')
|
||||||
// let data = base64.replace(/.*;base64,/,'')
|
let data = base64.replace(/.*;base64,/,'')
|
||||||
// let type = this.textHeuristic(data)
|
let type = this.textHeuristic(data)
|
||||||
// const term = document.querySelector('[isoterminal]').components.isoterminal.term
|
const term = document.querySelector('[isoterminal]').components.isoterminal.term
|
||||||
// this.el.emit('pasteFile',{}) /*TODO* data incompatible */
|
this.el.emit('pasteFile',{}) /*TODO* data incompatible */
|
||||||
// })
|
})
|
||||||
//},
|
},
|
||||||
|
|
||||||
onDrop: function(e){
|
onDrop: function(e){
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
// usage:
|
/**
|
||||||
//
|
* ## [require](com/require('').js)
|
||||||
// await AFRAME.utils.require( this.dependencies ) (*) autoload missing components
|
*
|
||||||
// await AFRAME.utils.require( this.el.getAttributeNames() ) (*) autoload missing components
|
* automatically requires dependencies
|
||||||
// await AFRAME.utils.require({foo: "https://foo.com/aframe/components/foo.js"},this)
|
*
|
||||||
// await AFRAME.utils.require(["./app/foo.js","foo.css"],this)
|
* ```javascript
|
||||||
//
|
* await AFRAME.utils.require( this.dependencies ) (*) autoload missing components
|
||||||
// (*) = prefixes baseURL AFRAME.utils.require.baseURL ('./com/' e.g.)
|
* 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){
|
AFRAME.utils.require = function(arr_or_obj,opts){
|
||||||
opts = opts || {}
|
opts = opts || {}
|
||||||
let i = 0
|
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', {
|
AFRAME.registerComponent('window', {
|
||||||
schema:{
|
schema:{
|
||||||
title: {type:'string',"default":"title"},
|
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