Compare commits
15 Commits
feat/vt100
...
main
Author | SHA1 | Date |
---|---|---|
Leon van Kammen | 88c4b21818 | |
Leon van Kammen | d93401e572 | |
Leon van Kammen | dcade45f82 | |
Leon van Kammen | b8e6428611 | |
Leon van Kammen | c5ed500a93 | |
Leon van Kammen | de8883673c | |
Leon van Kammen | 277dafbd36 | |
Leon van Kammen | 8375e5f1b5 | |
Leon van Kammen | 5aeb860aef | |
Leon van Kammen | 4105cbda09 | |
Leon van Kammen | baba5de998 | |
Leon van Kammen | c79b764f00 | |
Leon van Kammen | 4c52fff332 | |
Leon van Kammen | bf9aca6d00 | |
Leon van Kammen | 9e6ea795d5 |
|
@ -4,8 +4,8 @@ AFRAME.registerComponent('codemirror', {
|
|||
schema: {
|
||||
file: { type:"string"},
|
||||
term: { type:"selector", default: "[isoterminal]" },
|
||||
width: { type:"number", default:900},
|
||||
height: { type:"number", default:700},
|
||||
width: { type:"number", default:700},
|
||||
height: { type:"number", default:500},
|
||||
},
|
||||
|
||||
init: function () {
|
||||
|
@ -109,8 +109,8 @@ AFRAME.registerComponent('codemirror', {
|
|||
// we don't do via shellcmd: isoterminal.exec(`echo '${str}' > ${file}`,1)
|
||||
// as it would require all kindof ugly stringescaping
|
||||
console.log("updating "+file)
|
||||
console.log(str)
|
||||
await this.isoterminal.worker['emulator.update_file'](file, term.convert.toUint8Array(str) )
|
||||
await this.isoterminal.worker.update_file(file, this.isoterminal.convert.toUint8Array(str) )
|
||||
this.isoterminal.exec("touch "+file) // *FIXME* notify filesystem (why does inotifyd need this? v86's 9pfees is cached?)
|
||||
},
|
||||
|
||||
events:{
|
||||
|
@ -118,7 +118,7 @@ AFRAME.registerComponent('codemirror', {
|
|||
// component events
|
||||
DOMready: function(e){
|
||||
|
||||
this.isoterminal.worker['emulator.read_file'](this.data.file)
|
||||
this.isoterminal.worker.read_file(this.data.file)
|
||||
.then( this.isoterminal.convert.Uint8ArrayToString )
|
||||
.then( (str) => {
|
||||
console.log("creating editor")
|
||||
|
|
|
@ -21,7 +21,7 @@ if( !AFRAME.components['html-as-texture-in-xr'] ){
|
|||
this.el.setAttribute("html",`html: ${this.data.domid}; cursor:#cursor; xrlayer: true`)
|
||||
this.el.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
||||
if( this.data.faceuser ){
|
||||
this.el.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.8) )
|
||||
this.el.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.4) )
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@ if( typeof AFRAME != 'undefined '){
|
|||
depth: { type: 'number',"default": 0.03 },
|
||||
lineHeight: { type: 'number',"default": 18 },
|
||||
padding: { type: 'number',"default": 18 },
|
||||
minimized: { type: 'boolean',"default":false},
|
||||
maximized: { type: 'boolean',"default":true},
|
||||
muteUntilPrompt:{ type: 'boolean',"default":true}, // mute stdout until a prompt is detected in ISO
|
||||
HUD: { type: 'boolean',"default":false}, // link to camera movement
|
||||
|
@ -56,6 +55,7 @@ if( typeof AFRAME != 'undefined '){
|
|||
this.calculateDimension()
|
||||
this.initHud()
|
||||
this.setupBox()
|
||||
this.setupPasteDrop()
|
||||
|
||||
fetch(this.data.iso,{method: 'HEAD'})
|
||||
.then( (res) => {
|
||||
|
@ -72,10 +72,11 @@ if( typeof AFRAME != 'undefined '){
|
|||
requires:{
|
||||
com: "com/dom.js",
|
||||
window: "com/window.js",
|
||||
pastedrop: "com/pastedrop.js",
|
||||
v86: "com/isoterminal/libv86.js",
|
||||
vt100: "com/isoterminal/VT100.js",
|
||||
// allow xrsh to selfcontain scene + itself
|
||||
xhook: "https://jpillora.com/xhook/dist/xhook.min.js",
|
||||
xhook: "com/lib/xhook.min.js",
|
||||
selfcontain: "com/selfcontainer.js",
|
||||
// html to texture
|
||||
htmlinxr: "com/html-as-texture-in-xr.js",
|
||||
|
@ -97,7 +98,7 @@ if( typeof AFRAME != 'undefined '){
|
|||
css: (me) => `.isoterminal{
|
||||
padding: ${me.com.data.padding}px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
height:90%;
|
||||
position:relative;
|
||||
}
|
||||
.isoterminal div{
|
||||
|
@ -133,6 +134,17 @@ if( typeof AFRAME != 'undefined '){
|
|||
box-shadow:none;
|
||||
}
|
||||
|
||||
.cursor {
|
||||
background: #70F !important;
|
||||
animation:fade 1000ms infinite;
|
||||
-webkit-animation:fade 1000ms infinite;
|
||||
}
|
||||
|
||||
.XR .cursor {
|
||||
animation:none;
|
||||
-webkit-animation:none;
|
||||
}
|
||||
|
||||
.wb-body:has(> .isoterminal){
|
||||
background: #000C;
|
||||
overflow:hidden;
|
||||
|
@ -192,6 +204,8 @@ if( typeof AFRAME != 'undefined '){
|
|||
indexhtml: "com/isoterminal/feat/index.html.js",
|
||||
indexjs: "com/isoterminal/feat/index.js.js",
|
||||
autorestore: "com/isoterminal/feat/autorestore.js",
|
||||
pastedropFeat: "com/isoterminal/feat/pastedrop.js",
|
||||
httpfs: "com/isoterminal/feat/httpfs.js",
|
||||
})
|
||||
|
||||
this.el.setAttribute("selfcontainer","")
|
||||
|
@ -220,7 +234,7 @@ if( typeof AFRAME != 'undefined '){
|
|||
},100)
|
||||
//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}`)
|
||||
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-resize, no-move`)
|
||||
})
|
||||
|
||||
instance.addEventListener('window.oncreate', (e) => {
|
||||
|
@ -235,6 +249,7 @@ if( typeof AFRAME != 'undefined '){
|
|||
})
|
||||
|
||||
instance.setAttribute("dom", "")
|
||||
instance.setAttribute("pastedrop", "")
|
||||
|
||||
this.term.addEventListener('ready', (e) => {
|
||||
instance.dom.classList.remove('blink')
|
||||
|
@ -259,10 +274,10 @@ if( typeof AFRAME != 'undefined '){
|
|||
instance.addEventListener('window.onmaximize', resize )
|
||||
|
||||
const focus = (showdom) => (e) => {
|
||||
this.el.emit('focus',e.detail)
|
||||
if( this.el.components.window && this.data.renderer == 'canvas'){
|
||||
this.el.components.window.show( showdom )
|
||||
}
|
||||
this.el.emit('focus',e.detail)
|
||||
}
|
||||
|
||||
this.el.addEventListener('obbcollisionstarted', focus(false) )
|
||||
|
@ -292,21 +307,27 @@ if( typeof AFRAME != 'undefined '){
|
|||
|
||||
setupVT100: function(instance){
|
||||
const el = this.el.dom.querySelector('#term')
|
||||
const opts = {
|
||||
this.term.opts.vt100 = {
|
||||
cols: this.cols,
|
||||
rows: this.rows,
|
||||
el_or_id: el,
|
||||
max_scroll_lines: 100,
|
||||
nodim: true
|
||||
max_scroll_lines: this.rows,
|
||||
nodim: true,
|
||||
rainbow: [VT100.COLOR_MAGENTA, VT100.COLOR_CYAN ],
|
||||
xr: AFRAME.scenes[0].renderer.xr,
|
||||
map: {
|
||||
'ArrowRight': { ch: false, ctrl: '\x1b\x66' }, // this triggers ash-shell forward-word
|
||||
'ArrowLeft': { ch: false, ctrl: '\x1b\x62' } // backward-word
|
||||
}
|
||||
this.vt100 = new VT100( opts )
|
||||
}
|
||||
this.term.emit('initVT100',this)
|
||||
this.vt100 = new VT100( this.term.opts.vt100 )
|
||||
this.vt100.el = el
|
||||
this.vt100.curs_set( 1, true)
|
||||
el.focus()
|
||||
this.el.addEventListener('focus', () => el.focus())
|
||||
this.vt100.focus()
|
||||
this.el.addEventListener('focus', () => this.vt100.focus() )
|
||||
this.vt100.getch( (ch,t) => {
|
||||
this.term.send( ch )
|
||||
this.vt100.curs_set( 0, true)
|
||||
})
|
||||
|
||||
this.el.addEventListener('serial-output-byte', (e) => {
|
||||
|
@ -318,13 +339,23 @@ if( typeof AFRAME != 'undefined '){
|
|||
this.vt100.write(e.detail)
|
||||
})
|
||||
|
||||
// translate file upload into pasteFile
|
||||
this.vt100.upload.addEventListener('change', (e) => {
|
||||
const file = this.vt100.upload.files[0];
|
||||
const item = {...file, getAsFile: () => file }
|
||||
this.el.emit('pasteFile', { item, type: file.type });
|
||||
})
|
||||
|
||||
//this.el.dom.querySelector('input').addEventListener('keyup', (e) => {
|
||||
// VT100.handle_onkeypress_( {charCode : e.charCode || e.keyCode, keyCode: e.keyCode}, (chars) => {
|
||||
// debugger
|
||||
// chars.map( (c) => this.term.send(str) )
|
||||
// })
|
||||
//})
|
||||
return this
|
||||
},
|
||||
|
||||
setupPasteDrop: function(){
|
||||
this.el.addEventListener('pasteFile', (e) => {
|
||||
e.preventDefault() // prevent bubbling up to window (which is triggering this initially)
|
||||
if( !this.term.pasteFile ) return // skip if feat/pastedrop.js is not loaded
|
||||
this.term.pasteFile(e.detail)
|
||||
})
|
||||
return this
|
||||
},
|
||||
|
||||
setupBox: function(){
|
||||
|
@ -339,13 +370,13 @@ if( typeof AFRAME != 'undefined '){
|
|||
},
|
||||
|
||||
calculateDimension: function(){
|
||||
if( this.data.width == -1 ) this.data.width = document.body.offsetWidth
|
||||
if( this.data.height == -1 ) this.data.height = document.body.offsetHeight
|
||||
if( this.data.width == -1 ) this.data.width = document.body.offsetWidth;
|
||||
if( this.data.height == -1 ) this.data.height = Math.floor( document.body.offsetHeight - 30 )
|
||||
if( this.data.height > this.data.width ) this.data.height = this.data.width // mobile smartphone fix
|
||||
this.data.width -= this.data.padding*2
|
||||
this.data.height -= this.data.padding*2
|
||||
this.cols = Math.floor(this.data.width/this.data.lineHeight*1.9)
|
||||
this.rows = Math.floor(this.data.height*0.5/this.data.lineHeight*1.7) // keep extra height for mobile browser bottom-bar (android)
|
||||
this.cols = Math.floor(this.data.width/this.data.lineHeight*2)
|
||||
this.rows = Math.floor(this.data.height*0.53/this.data.lineHeight*1.7)
|
||||
},
|
||||
|
||||
events:{
|
||||
|
|
|
@ -17,14 +17,14 @@ function ISOTerminal(instance,opts){
|
|||
ISOTerminal.prototype.emit = function(event,data,sender){
|
||||
data = data || false
|
||||
const evObj = new CustomEvent(event, {detail: data} )
|
||||
//this.preventFrameDrop( () => {
|
||||
this.preventFrameDrop( () => {
|
||||
// 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] )
|
||||
//})
|
||||
})
|
||||
}
|
||||
|
||||
ISOTerminal.addEventListener = (event,cb) => {
|
||||
|
@ -37,6 +37,10 @@ ISOTerminal.prototype.exec = function(shellscript){
|
|||
this.send(shellscript+"\n",1)
|
||||
}
|
||||
|
||||
ISOTerminal.prototype.hook = function(hookname,args){
|
||||
this.exec(`{ type hook || source /etc/profile.sh; }; hook ${hookname} "${args.join('" "')}"`)
|
||||
}
|
||||
|
||||
ISOTerminal.prototype.serial_input = 0; // can be set to 0,1,2,3 to define stdinput tty (xterm plugin)
|
||||
|
||||
ISOTerminal.prototype.send = function(str, ttyNr){
|
||||
|
@ -48,7 +52,9 @@ ISOTerminal.prototype.send = function(str, ttyNr){
|
|||
}else{
|
||||
this.convert.toUint8Array( str ).map( (c) => {
|
||||
this.preventFrameDrop(
|
||||
() => this.worker.postMessage({event:`serial${ttyNr}-input`,data:c})
|
||||
() => {
|
||||
this.worker.postMessage({event:`serial${ttyNr}-input`,data:c})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -187,7 +193,6 @@ ISOTerminal.prototype.startVM = function(opts){
|
|||
"Learned helplessness fades when we realize tech isn’t too complex to understand",
|
||||
"FOSS empowers users to customize and improve their tools",
|
||||
"Engaging with FOSS helps build confidence and self-reliance in tech",
|
||||
"FOSS tools are accessible and often better than closed alternatives",
|
||||
"FOSS shows that anyone can shape the digital world with curiosity and effort",
|
||||
"Linux can revive old computers, extending their life and reducing e-waste",
|
||||
"Many lightweight Linux distributions run smoothly on older hardware",
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
//
|
||||
// Released under the GNU LGPL v2.1, by Frank Bi <bi@zompower.tk>
|
||||
//
|
||||
//
|
||||
// 2024-08-xx upgraded things to work in WebXR (xterm.js too heavy)
|
||||
// 2007-08-12 - refresh():
|
||||
// - factor out colour code to html_colours_()
|
||||
// - fix handling of A_REVERSE | A_DIM
|
||||
|
@ -118,6 +120,8 @@ function VT100(opts)
|
|||
this.redraw_[r] = 1;
|
||||
}
|
||||
this.scr_ = scr;
|
||||
this.scr_.style.display = 'inline'
|
||||
this.setupTouchInputFallback() // smartphone/android
|
||||
this.cursor_vis_ = true;
|
||||
this.cursor_key_mode_ = VT100.CK_CURSOR;
|
||||
this.grab_events_ = false;
|
||||
|
@ -131,8 +135,6 @@ function VT100(opts)
|
|||
|
||||
// rate limit this.refresh
|
||||
this.refresh = this.throttleSmart( VT100.prototype.refresh.bind(this), 100)
|
||||
|
||||
this.setupTouchInputFallback() // smartphone
|
||||
}
|
||||
|
||||
// public constants -- colours and colour pairs
|
||||
|
@ -216,7 +218,7 @@ VT100.handle_onkeypress_ = function VT100_handle_onkeypress(event,cb)
|
|||
ch = '\n';
|
||||
}
|
||||
} else {
|
||||
switch (event.key) {
|
||||
switch (event.code) {
|
||||
case "Backspace":
|
||||
ch = '\b';
|
||||
break;
|
||||
|
@ -264,14 +266,21 @@ VT100.handle_onkeypress_ = function VT100_handle_onkeypress(event,cb)
|
|||
return true
|
||||
break;
|
||||
}
|
||||
// custom map override
|
||||
if( vt.opts.map[ event.code ].ch ) ch = vt.opts.map[ event.code ].ch
|
||||
if( vt.opts.map[ event.code ].ctrl && event.ctrlKey ) ch = vt.opts.map[ event.code ].ctrl
|
||||
}
|
||||
// Stop the event from doing anything else.
|
||||
event.preventDefault();
|
||||
|
||||
// Workaround: top the event from doing anything else.
|
||||
// (prevent input from adding characters instead of via VM)
|
||||
event.preventDefault()
|
||||
vt.key_buf_.push(ch);
|
||||
|
||||
if( cb ){
|
||||
cb(vt.key_buf_)
|
||||
vt.key_buf_ = []
|
||||
}else setTimeout(VT100.go_getch_, 0);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -285,6 +294,7 @@ VT100.handle_onkeydown_ = function VT100_handle_onkeydown()
|
|||
default:
|
||||
return true;
|
||||
}
|
||||
event.preventDefault()
|
||||
vt.key_buf_.push(ch);
|
||||
setTimeout(VT100.go_getch_, 0);
|
||||
return false;
|
||||
|
@ -549,24 +559,28 @@ VT100.prototype.clearpos = function VT100_clearpos(row, col)
|
|||
this.redraw_[row] = 1;
|
||||
}
|
||||
|
||||
VT100.prototype.curs_set = function(vis, grab, eventist)
|
||||
VT100.prototype.curs_set = function(vis, grab, offscreenKB)
|
||||
{
|
||||
// offscreenKB is a div which receives keys from physical kb's
|
||||
// but not from touch keyboards (they require an input-field)
|
||||
// hence setupTouchInputFallback()..this is how we seperate the two
|
||||
this.info("curs_set:: vis: " + vis + ", grab: " + grab);
|
||||
if (vis !== undefined)
|
||||
if (vis !== undefined){
|
||||
this.cursor_vis_ = (vis > 0);
|
||||
if (eventist === undefined)
|
||||
eventist = this.scr_;
|
||||
}
|
||||
if (offscreenKB === undefined)
|
||||
offscreenKB = this.scr_;
|
||||
if (grab === true || grab === false) {
|
||||
if (grab === this.grab_events_)
|
||||
return;
|
||||
if (grab) {
|
||||
this.grab_events_ = true;
|
||||
VT100.the_vt_ = this;
|
||||
eventist.addEventListener("keypress", VT100.handle_onkeypress_, false);
|
||||
eventist.addEventListener("keydown", VT100.handle_onkeypress_, false);
|
||||
offscreenKB.addEventListener("keypress", VT100.handle_onkeypress_, false);
|
||||
offscreenKB.addEventListener("keydown", VT100.handle_onkeypress_, false);
|
||||
} else {
|
||||
eventist.removeEventListener("keypress", VT100.handle_onkeypress_, false);
|
||||
eventist.removeEventListener("keydown", VT100.handle_onkeypress_, false);
|
||||
offscreenKB.removeEventListener("keypress", VT100.handle_onkeypress_, false);
|
||||
offscreenKB.removeEventListener("keydown", VT100.handle_onkeypress_, false);
|
||||
this.grab_events_ = false;
|
||||
VT100.the_vt_ = undefined;
|
||||
}
|
||||
|
@ -656,7 +670,8 @@ VT100.prototype.refresh = function VT100_refresh()
|
|||
for (c = 0; c < wd; ++c) {
|
||||
added_end_tag = false;
|
||||
n_at = this.attr_[r][c];
|
||||
if (cv && r == cr && c == cc) {
|
||||
const drawCursor = cv && r == cr && c == cc
|
||||
if (drawCursor){
|
||||
// Draw the cursor here.
|
||||
n_at = this._cloneAttr(n_at);
|
||||
n_at.mode ^= VT100.A_REVERSE;
|
||||
|
@ -681,7 +696,9 @@ VT100.prototype.refresh = function VT100_refresh()
|
|||
start_tag += ';font-weight: bolder';
|
||||
if (n_at.mode & VT100.A_UNDERLINE)
|
||||
start_tag += ';text-decoration:underline';
|
||||
start_tag += ';">';
|
||||
if ( drawCursor )
|
||||
start_tag += '" class="cursor'
|
||||
start_tag += '">';
|
||||
row_html += start_tag;
|
||||
end_tag = "</span>" + end_tag;
|
||||
at = n_at;
|
||||
|
@ -721,6 +738,7 @@ VT100.prototype.refresh = function VT100_refresh()
|
|||
div_element.innerHTML = row_html;
|
||||
//dump("adding row html: " + row_html + "\n");
|
||||
}
|
||||
this.curs_set(1)
|
||||
}
|
||||
|
||||
VT100.prototype.set_max_scroll_lines = function(max_lines)
|
||||
|
@ -1067,6 +1085,10 @@ VT100.prototype.write = function VT100_write(stuff)
|
|||
case 'm':
|
||||
for (j=0; j<this.csi_parms_.length; ++j) {
|
||||
x = this.csi_parms_[j];
|
||||
if( x > 89 && x < 98 && this.opts.rainbow ){
|
||||
const rainbow = this.opts.rainbow
|
||||
this.fgset( rainbow[ x % rainbow.length ] )
|
||||
}
|
||||
switch (x) {
|
||||
case 0:
|
||||
this.standend();
|
||||
|
@ -1166,6 +1188,7 @@ VT100.prototype.write = function VT100_write(stuff)
|
|||
default:
|
||||
this.warn(" unknown command: " + ch);
|
||||
this.csi_parms_ = [];
|
||||
return
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -1309,38 +1332,90 @@ VT100.prototype.throttleSmart = function throttleSmart(fn, wait) {
|
|||
}
|
||||
|
||||
VT100.prototype.setupTouchInputFallback = function(){
|
||||
this.scr_.addEventListener('touchend', () => {
|
||||
if( !this.input ){
|
||||
this.form = document.createElement("form")
|
||||
this.form.addEventListener("submit", (e) => {
|
||||
e.preventDefault()
|
||||
this.key_buf_.push('\n')
|
||||
setTimeout(VT100.go_getch_, 0);
|
||||
})
|
||||
this.upload = document.createElement("input")
|
||||
this.upload.setAttribute("type", "file")
|
||||
this.upload.style.opacity = '0'
|
||||
this.upload.style.position = 'absolute'
|
||||
this.upload.style.left = '-9999px'
|
||||
|
||||
this.input = document.createElement("input")
|
||||
this.input.setAttribute("type", "text")
|
||||
this.input.setAttribute("cols", this.opts.cols )
|
||||
this.input.setAttribute("rows", this.opts.rows )
|
||||
this.input.style.opacity = '0'
|
||||
this.input.style.position = 'absolute'
|
||||
this.input.style.left = '-9999px'
|
||||
|
||||
this.form = document.createElement("form")
|
||||
this.form.addEventListener("submit", (e) => {
|
||||
e.preventDefault()
|
||||
this.key_buf_.push('\n')
|
||||
setTimeout(VT100.go_getch_, 0);
|
||||
return false
|
||||
})
|
||||
this.form.appendChild(this.upload)
|
||||
this.form.appendChild(this.input)
|
||||
this.scr_.parentElement.appendChild(this.form)
|
||||
|
||||
this.input.handler = () => {
|
||||
let ch = this.input.value
|
||||
// detect backspace
|
||||
//if( e.inputType == 'deleteContentBackward' ) ch = '\b'
|
||||
this.input.addEventListener('blur', () => {
|
||||
if( this.input.value != '' ){
|
||||
ch = '\n'
|
||||
this.key_buf_.push(ch);
|
||||
setTimeout(VT100.go_getch_, 0);
|
||||
this.input.value = ''
|
||||
}
|
||||
})
|
||||
|
||||
this.input.addEventListener("keydown", VT100.handle_onkeypress_, false);
|
||||
|
||||
this.input.handler = (e) => {
|
||||
let ch
|
||||
let isEnter = String(e?.key).toLowerCase() == "enter" || e?.code == 13
|
||||
let isBackspace = String(e?.key).toLowerCase() == "backspace" || e?.code == 8
|
||||
if( isEnter ){
|
||||
ch = '\n'
|
||||
}else if( isBackspace ){
|
||||
ch = '\b' // naive
|
||||
}else{
|
||||
ch = this.input.value.substr(-1)
|
||||
}
|
||||
// detect backspace
|
||||
if( !ch ) return
|
||||
this.key_buf_.push(ch);
|
||||
setTimeout(VT100.go_getch_, 0);
|
||||
this.input.valueLast = this.input.value
|
||||
this.input.lastValue = this.input.value
|
||||
}
|
||||
this.input.addEventListener('input', this.input.handler )
|
||||
this.input.addEventListener('input', (e) => this.input.handler(e) )
|
||||
|
||||
this.scr_.addEventListener('touchend', (e) => this.focus() )
|
||||
this.scr_.addEventListener('click', (e) => this.focus() )
|
||||
|
||||
}
|
||||
setTimeout( () => this.input.focus(), 10 )
|
||||
})
|
||||
this.useFallbackInput = true
|
||||
this.focus()
|
||||
}
|
||||
|
||||
VT100.prototype.focus = function(){
|
||||
setTimeout( () => {
|
||||
const el = this[ this.useFallbackInput ? 'input' : 'scr_' ]
|
||||
el.focus()
|
||||
}, 10 )
|
||||
}
|
||||
|
||||
window.keyboard = function(n){
|
||||
let msg = 'unknown keyboard'
|
||||
if( n == 0 ){
|
||||
msg = "using onscreen keyboard"
|
||||
VT100.the_vt_.useFallbackInput = true
|
||||
VT100.the_vt_.focus()
|
||||
}
|
||||
if( n == 1 ){
|
||||
msg = "using offscreen keyboard"
|
||||
VT100.the_vt_.useFallbackInput = false
|
||||
VT100.the_vt_.focus()
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
function dump(x) {
|
||||
|
|
|
@ -21,7 +21,6 @@ emulator.fs9p.update_file = async function(file,data){
|
|||
inode.size = buf.length
|
||||
const now = Math.round(Date.now() / 1000);
|
||||
inode.atime = inode.mtime = now;
|
||||
me.postMessage({event:'exec',data:[`touch /mnt/${file}`]}) // update inode
|
||||
return new Promise( (resolve,reject) => resolve(buf) )
|
||||
}catch(e){
|
||||
console.error({file,data})
|
||||
|
@ -50,3 +49,47 @@ emulator.fs9p.append_file = async function(file,data){
|
|||
|
||||
}
|
||||
|
||||
emulator.fs9p.read_file_world = async function(file){
|
||||
const p = this.SearchPath(file);
|
||||
|
||||
if(p.id === -1)
|
||||
{
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const inode = this.GetInode(p.id);
|
||||
const perms = this.parseFilePermissions(inode.mode)
|
||||
|
||||
if( !perms.world.read ){
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return this.Read(p.id, 0, inode.size);
|
||||
}
|
||||
|
||||
emulator.fs9p.parseFilePermissions = function(permissionInt) {
|
||||
// Convert the permission integer to octal
|
||||
const octalPermissions = permissionInt.toString(8);
|
||||
|
||||
// Extract the permission bits (last 3 digits in octal)
|
||||
const permissionBits = octalPermissions.slice(-3);
|
||||
|
||||
|
||||
function parsePermission(digit) {
|
||||
const num = parseInt(digit, 10);
|
||||
|
||||
return {
|
||||
read: Boolean(num & 4), // 4 = read
|
||||
write: Boolean(num & 2), // 2 = write
|
||||
execute: Boolean(num & 1) // 1 = execute
|
||||
};
|
||||
}
|
||||
// Decode the permissions
|
||||
const permissions = {
|
||||
owner: parsePermission(permissionBits[0]),
|
||||
group: parsePermission(permissionBits[1]),
|
||||
world: parsePermission(permissionBits[2]),
|
||||
};
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ if( typeof emulator != 'undefined' ){
|
|||
// inside worker-thread
|
||||
importScripts("localforage.js") // we don't instance it again here (just use its functions)
|
||||
|
||||
this['emulator.restore_state'] = async function(data){
|
||||
this.restore_state = async function(data){
|
||||
return new Promise( (resolve,reject) => {
|
||||
localforage.getItem("state", async (err,stateBase64) => {
|
||||
if( stateBase64 && !err ){
|
||||
|
@ -14,7 +14,7 @@ if( typeof emulator != 'undefined' ){
|
|||
})
|
||||
})
|
||||
}
|
||||
this['emulator.save_state'] = async function(){
|
||||
this.save_state = async function(){
|
||||
console.log("saving session")
|
||||
let state = await emulator.save_state()
|
||||
localforage.setDriver([
|
||||
|
@ -25,7 +25,6 @@ if( typeof emulator != 'undefined' ){
|
|||
localforage.setItem("state", ISOTerminal.prototype.convert.arrayBufferToBase64(state) )
|
||||
console.log("state saved")
|
||||
})
|
||||
console.dir(state)
|
||||
}
|
||||
|
||||
|
||||
|
@ -47,7 +46,7 @@ if( typeof emulator != 'undefined' ){
|
|||
if( stateBase64 && !err && confirm('continue last session?') ){
|
||||
this.noboot = true // see feat/boot.js
|
||||
try{
|
||||
await this.worker['emulator.restore_state']()
|
||||
await this.worker.restore_state()
|
||||
// simulate / fastforward boot events
|
||||
this.postBoot( () => {
|
||||
this.send("l\n")
|
||||
|
@ -58,7 +57,7 @@ if( typeof emulator != 'undefined' ){
|
|||
})
|
||||
|
||||
this.save = async () => {
|
||||
await this.worker['emulator.save_state']()
|
||||
await this.worker.save_state()
|
||||
}
|
||||
|
||||
window.addEventListener("beforeunload", function (e) {
|
||||
|
|
|
@ -14,7 +14,7 @@ ISOTerminal.prototype.boot = async function(e){
|
|||
env.push( 'export '+String(i).toUpperCase()+'="'+decodeURIComponent( document.location[i]+'"') )
|
||||
}
|
||||
}
|
||||
await this.emit("emulator.create_file", ["profile.browser", this.convert.toUint8Array( env.join('\n') ) ] )
|
||||
this.worker.create_file("profile.browser", this.convert.toUint8Array( env.join('\n') ) )
|
||||
|
||||
if( this.serial_input == 0 ){
|
||||
if( !this.noboot ){
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
if( typeof emulator != 'undefined' ){
|
||||
|
||||
}else{
|
||||
|
||||
ISOTerminal.addEventListener('ready', function(e){
|
||||
|
||||
// listen for http request to the filesystem ( file://host/path )
|
||||
xhook.before( (request,callback) => {
|
||||
|
||||
if (request.url.match(/^file:\/\/xrsh\/mnt\/.*/) ){
|
||||
let response
|
||||
let file = request.url.replace(/^file:\/\/xrsh\/mnt\//,'')
|
||||
this.worker.read_file_world(file)
|
||||
.then( (data) => {
|
||||
response = new Response( new Blob( [data] ) ) // wrap Uint8Array into array
|
||||
response.status = 200
|
||||
callback(response)
|
||||
})
|
||||
.catch( (e) => {
|
||||
response = new Response()
|
||||
response.status = 404
|
||||
callback(response)
|
||||
})
|
||||
return
|
||||
}
|
||||
callback()
|
||||
})
|
||||
|
||||
})
|
||||
}
|
|
@ -6,7 +6,7 @@ if( typeof emulator != 'undefined' ){
|
|||
const convert = ISOTerminal.prototype.convert
|
||||
const buf = await this.emulator.read_file("dev/browser/js")
|
||||
const script = convert.Uint8ArrayToString(buf)
|
||||
let PID="?"
|
||||
let PID=null
|
||||
try{
|
||||
if( script.match(/^PID/) ){
|
||||
PID = script.match(/^PID=([0-9]+);/)[1]
|
||||
|
@ -35,7 +35,9 @@ if( typeof emulator != 'undefined' ){
|
|||
}
|
||||
}
|
||||
// update output to 9p with PID as filename (in /mnt/run)
|
||||
this.emit('fs9p.update_file', [`run/${PID}`, this.convert.toUint8Array(res)] )
|
||||
if( PID ){
|
||||
this.worker.update_file(`run/${PID}`, this.convert.toUint8Array(res) )
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
if( typeof emulator != 'undefined' ){
|
||||
// inside worker-thread
|
||||
|
||||
}else{
|
||||
// inside browser-thread
|
||||
//
|
||||
ISOTerminal.prototype.pasteWriteFile = async function(data,type,filename){
|
||||
this.pasteWriteFile.fileCount = this.pasteWriteFile.fileCount || 0
|
||||
const file = `clipboard/`+ ( filename || `user-paste-${this.pasteWriteFile.fileCount}`)
|
||||
await this.worker.create_file(file, data )
|
||||
// run the xrsh hook
|
||||
this.hook("clipboard", [ `/mnt/${file}`, type ] )
|
||||
console.log("clipboard paste: /mnt/"+file)
|
||||
this.pasteWriteFile.fileCount += 1
|
||||
}
|
||||
|
||||
ISOTerminal.prototype.pasteFile = async function(data){
|
||||
const {type,item,pastedText} = data
|
||||
if( pastedText){
|
||||
this.pasteWriteFile( this.convert.toUint8Array(pastedText) ,type)
|
||||
}else{
|
||||
const file = item.getAsFile();
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const arr = new Uint8Array(e.target.result)
|
||||
this.pasteWriteFile( arr, type, file.name ); // or use readAsDataURL for images
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -42,10 +42,11 @@ this.runISO = function(opts){
|
|||
/*
|
||||
* forward events/functions so non-worker world can reach them
|
||||
*/
|
||||
this['emulator.create_file'] = async function(){ return emulator.create_file.apply(emulator, arguments[0]) }
|
||||
this['emulator.read_file'] = async function(){ return emulator.read_file.apply(emulator, arguments[0]) }
|
||||
this['emulator.append_file'] = async function(){ emulator.fs9p.append_file.apply(emulator.fs9p, arguments[0]) }
|
||||
this['emulator.update_file'] = async function(){ emulator.fs9p.update_file.apply(emulator.fs9p, arguments[0]) }
|
||||
this.create_file = async function(){ return emulator.create_file.apply(emulator, arguments[0]) }
|
||||
this.read_file = async function(){ return emulator.read_file.apply(emulator, arguments[0]) }
|
||||
this.read_file_world = async function(){ return emulator.fs9p.read_file_world.apply(emulator.fs9p, arguments[0]) }
|
||||
this.append_file = async function(){ emulator.fs9p.append_file.apply(emulator.fs9p, arguments[0]) }
|
||||
this.update_file = async function(){ emulator.fs9p.update_file.apply(emulator.fs9p, arguments[0]) }
|
||||
|
||||
// filename will be read from 9pfs: "/mnt/"+filename
|
||||
emulator.readFromPipe = function(filename,cb){
|
||||
|
|
File diff suppressed because one or more lines are too long
179
com/paste.js
179
com/paste.js
|
@ -1,179 +0,0 @@
|
|||
AFRAME.registerComponent('paste', {
|
||||
schema: {
|
||||
foo: { type:"string"}
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this.el.object3D.visible = false
|
||||
//this.el.innerHTML = ` `
|
||||
},
|
||||
|
||||
requires:{
|
||||
osbutton: "com/osbutton.js"
|
||||
},
|
||||
|
||||
events:{
|
||||
|
||||
// component events
|
||||
somecomponent: function( ){ console.log("component requirement mounted") },
|
||||
ready: function(e){ console.log("requires are loaded") },
|
||||
|
||||
launcher: function(e){
|
||||
const paste = () => {
|
||||
navigator.clipboard.readText()
|
||||
.then( (base64) => {
|
||||
let mimetype = base64.replace(/;base64,.*/,'')
|
||||
let data = base64.replace(/.*;base64,/,'')
|
||||
let type = this.textHeuristic(data)
|
||||
console.log("type="+type)
|
||||
switch( this.textHeuristic(data) ){
|
||||
case "aframe": this.insertAFRAME(data); break;
|
||||
default: this.insertText(data); break;
|
||||
}
|
||||
this.count += 1
|
||||
})
|
||||
}
|
||||
|
||||
navigator.permissions.query({ name: 'clipboard-read' })
|
||||
.then( (permission) => {
|
||||
if( permission.state != 'granted' ){
|
||||
this.el.sceneEl.exitVR()
|
||||
setTimeout( () => paste(), 500 )
|
||||
return
|
||||
}else paste()
|
||||
})
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
textHeuristic: function(text){
|
||||
// Script type identification clues
|
||||
const bashClues = ["|", "if ", "fi", "cat"];
|
||||
const htmlClues = ["/>", "href=", "src="];
|
||||
const aframeClues = ["<a-entity", "/>", "position="];
|
||||
const jsClues = ["var ", "let ", "function ", "setTimeout","console."];
|
||||
// Count occurrences of clues for each script type
|
||||
const bashCount = bashClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||
const htmlCount = htmlClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||
const aframeCount = aframeClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||
const jsCount = jsClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||
|
||||
// Identify the script with the most clues or return unknown if inconclusive
|
||||
const maxCount = Math.max(bashCount, htmlCount, jsCount, aframeCount);
|
||||
if (maxCount === 0) {
|
||||
return "unknown";
|
||||
} else if (bashCount === maxCount) {
|
||||
return "bash";
|
||||
} else if (htmlCount === maxCount) {
|
||||
return "html";
|
||||
} else if (jsCount === maxCount) {
|
||||
return "javascript";
|
||||
} else {
|
||||
return "aframe";
|
||||
}
|
||||
},
|
||||
|
||||
insertAFRAME: function(data){
|
||||
let scene = document.createElement('a-entity')
|
||||
scene.id = "embedAframe"
|
||||
scene.innerHTML = data
|
||||
let el = document.createElement('a-text')
|
||||
el.setAttribute("value",data)
|
||||
el.setAttribute("color","white")
|
||||
el.setAttribute("align","center")
|
||||
el.setAttribute("anchor","align")
|
||||
let osbutton = this.wrapOSButton(el,"aframe",data)
|
||||
AFRAME.scenes[0].appendChild(osbutton)
|
||||
console.log(data)
|
||||
},
|
||||
|
||||
insertText: function(data){
|
||||
let el = document.createElement('a-text')
|
||||
el.setAttribute("value",data)
|
||||
el.setAttribute("color","white")
|
||||
el.setAttribute("align","center")
|
||||
el.setAttribute("anchor","align")
|
||||
let osbutton = this.wrapOSButton(el,"text",data)
|
||||
AFRAME.scenes[0].appendChild(osbutton)
|
||||
console.log(data)
|
||||
},
|
||||
|
||||
wrapOSButton: function(el,type,data){
|
||||
let osbutton = document.createElement('a-entity')
|
||||
let height = type == 'aframe' ? 0.3 : 0.1
|
||||
let depth = type == 'aframe' ? 0.3 : 0.05
|
||||
osbutton.setAttribute("osbutton",`width:0.3; height: ${height}; depth: ${depth}; color:blue `)
|
||||
osbutton.appendChild(el)
|
||||
osbutton.object3D.position.copy( this.getPositionInFrontOfCamera() )
|
||||
return osbutton
|
||||
},
|
||||
|
||||
getPositionInFrontOfCamera: function(distance){
|
||||
const camera = this.el.sceneEl.camera;
|
||||
let pos = new THREE.Vector3()
|
||||
let direction = new THREE.Vector3();
|
||||
// Get camera's forward direction (without rotation)
|
||||
camera.getWorldDirection(direction);
|
||||
camera.getWorldPosition(pos)
|
||||
direction.normalize();
|
||||
// Scale the direction by 1 meter
|
||||
if( !distance ) distance = 1.5
|
||||
direction.multiplyScalar(distance);
|
||||
// Add the camera's position to the scaled direction to get the target point
|
||||
pos.add(direction);
|
||||
return pos
|
||||
},
|
||||
|
||||
manifest: { // HTML5 manifest to identify app to xrsh
|
||||
"short_name": "Paste",
|
||||
"name": "Paste",
|
||||
"icons": [
|
||||
{
|
||||
"src": "https://css.gg/clipboard.svg",
|
||||
"type": "image/svg+xml",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"id": "/?source=pwa",
|
||||
"start_url": "/?source=pwa",
|
||||
"background_color": "#3367D6",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"theme_color": "#3367D6",
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "What is the latest news?",
|
||||
"cli":{
|
||||
"usage": "helloworld <type> [options]",
|
||||
"example": "helloworld news",
|
||||
"args":{
|
||||
"--latest": {type:"string"}
|
||||
}
|
||||
},
|
||||
"short_name": "Today",
|
||||
"description": "View weather information for today",
|
||||
"url": "/today?source=pwa",
|
||||
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
|
||||
}
|
||||
],
|
||||
"description": "Paste the clipboard",
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/images/screenshot1.png",
|
||||
"type": "image/png",
|
||||
"sizes": "540x720",
|
||||
"form_factor": "narrow"
|
||||
}
|
||||
],
|
||||
"help":`
|
||||
Helloworld application
|
||||
|
||||
This is a help file which describes the application.
|
||||
It will be rendered thru troika text, and will contain
|
||||
headers based on non-punctualized lines separated by linebreaks,
|
||||
in above's case "\nHelloworld application\n" will qualify as header.
|
||||
`
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
AFRAME.registerComponent('pastedrop', {
|
||||
schema: {
|
||||
foo: { type:"string"}
|
||||
},
|
||||
|
||||
init: function () {
|
||||
|
||||
window.addEventListener('paste', this.onPaste.bind(this) )
|
||||
|
||||
document.body.addEventListener('dragover',(e) => e.preventDefault() )
|
||||
document.body.addEventListener('drop', this.onDrop.bind(this) )
|
||||
},
|
||||
|
||||
initClipboard: function(){
|
||||
navigator.permissions.query({ name: 'clipboard-read' })
|
||||
.then( (permission) => {
|
||||
if( permission.state != 'granted' ){
|
||||
this.el.sceneEl.exitVR()
|
||||
setTimeout( () => this.paste(), 500 )
|
||||
return
|
||||
}else this.paste()
|
||||
})
|
||||
},
|
||||
|
||||
//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()
|
||||
this.onPaste({...e, type: "paste", clipboardData: e.dataTransfer})
|
||||
},
|
||||
|
||||
onPaste: function(e){
|
||||
if( e.type != "paste" ) return
|
||||
|
||||
const clipboardData = e.clipboardData || navigator.clipboard;
|
||||
const items = clipboardData.items;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const type = item.type;
|
||||
|
||||
// Check if the item is a file
|
||||
if (item.kind === "file") {
|
||||
this.el.emit('pasteFile',{item,type})
|
||||
} else if (type === "text/plain") {
|
||||
const pastedText = clipboardData.getData("text/plain");
|
||||
const newType = "text" // let /root/hook.d/mimetype/text further decide whether this is text/plain (or something else)
|
||||
this.el.emit('pasteFile',{item,type:newType,pastedText})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
manifest: { // HTML5 manifest to identify app to xrsh
|
||||
"short_name": "Paste",
|
||||
"name": "Paste",
|
||||
"icons": [
|
||||
{
|
||||
"src": "https://css.gg/clipboard.svg",
|
||||
"type": "image/svg+xml",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"id": "/?source=pwa",
|
||||
"start_url": "/?source=pwa",
|
||||
"background_color": "#3367D6",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"theme_color": "#3367D6",
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "What is the latest news?",
|
||||
"cli":{
|
||||
"usage": "helloworld <type> [options]",
|
||||
"example": "helloworld news",
|
||||
"args":{
|
||||
"--latest": {type:"string"}
|
||||
}
|
||||
},
|
||||
"short_name": "Today",
|
||||
"description": "View weather information for today",
|
||||
"url": "/today?source=pwa",
|
||||
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
|
||||
}
|
||||
],
|
||||
"description": "Paste the clipboard",
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/images/screenshot1.png",
|
||||
"type": "image/png",
|
||||
"sizes": "540x720",
|
||||
"form_factor": "narrow"
|
||||
}
|
||||
],
|
||||
"help":`
|
||||
Helloworld application
|
||||
|
||||
This is a help file which describes the application.
|
||||
It will be rendered thru troika text, and will contain
|
||||
headers based on non-punctualized lines separated by linebreaks,
|
||||
in above's case "\nHelloworld application\n" will qualify as header.
|
||||
`
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -34,16 +34,22 @@ AFRAME.registerComponent('selfcontainer', {
|
|||
|
||||
installProxyServer: function(){
|
||||
if( !window.store ) window.store = {}
|
||||
|
||||
// selfcontain every webrequest to store (and serve if stored)
|
||||
let curry = function(me){
|
||||
return function(request, response, cb){
|
||||
|
||||
let data = request ? window.store[ request.url ] || false : false
|
||||
if( data ){ // return inline version
|
||||
console.log('selfcontained cache: '+request.url)
|
||||
console.log('selfcontainer.js: serving '+request.url+' from cache')
|
||||
let res = new Response()
|
||||
res[ data.binary ? 'data' : 'text' ] = data.binary ? () => me.convert.base64ToArrayBuffer(data.text) : data.text
|
||||
cb(res)
|
||||
}else{
|
||||
|
||||
if( request.url.match(/(^file:\/\/xrsh)/) ) return cb(response)
|
||||
|
||||
console.log("selfcontainer.js: caching "+request.url)
|
||||
if( response.text ){
|
||||
data = {text: response.text}
|
||||
}else{
|
||||
|
|
|
@ -9,7 +9,8 @@ AFRAME.registerComponent('window', {
|
|||
max: {type:'boolean',"default":false},
|
||||
min: {type:'boolean',"default":false},
|
||||
x: {type:'string',"default":"center"},
|
||||
y: {type:'string',"default":"center"}
|
||||
y: {type:'string',"default":"center"},
|
||||
"class": {type:'array',"default":[]},
|
||||
},
|
||||
|
||||
dependencies:{
|
||||
|
@ -28,6 +29,7 @@ AFRAME.registerComponent('window', {
|
|||
|
||||
this.el.dom.style.display = 'none'
|
||||
let winbox = this.el.winbox = new WinBox( this.data.title, {
|
||||
class: this.data.class,
|
||||
height:this.data.height,
|
||||
width:this.data.width,
|
||||
x: this.data.x,
|
||||
|
@ -70,7 +72,7 @@ AFRAME.registerComponent('window', {
|
|||
if( els.length < 2 ) return
|
||||
let current = els[ els.length-1 ]
|
||||
let last = els[ els.length-2 ]
|
||||
AFRAME.utils.positionObjectNextToNeighbor( current.object3D , last.object3D, els.length )
|
||||
AFRAME.utils.positionObjectNextToNeighbor( current.object3D , last.object3D, 0.02 )
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -79,11 +81,10 @@ AFRAME.registerComponent('window', {
|
|||
}
|
||||
})
|
||||
|
||||
AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, neighbours, margin = 0.45, degree = 20) {
|
||||
AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, margin ){
|
||||
// *FIXME* this could be more sophisticated :)
|
||||
object.position.x = lastNeighbor.position.x + ((neighbours-1) * margin)
|
||||
object.position.y = lastNeighbor.position.y
|
||||
object.position.z = lastNeighbor.position.z
|
||||
//object.rotation.y += THREE.MathUtils.degToRad( (neighbours-1) * degree);
|
||||
object.position.x = lastNeighbor.position.x + margin
|
||||
object.position.y = lastNeighbor.position.y - margin
|
||||
object.position.z = lastNeighbor.position.z + margin
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue