Compare commits
No commits in common. "main" and "feat/vt100" have entirely different histories.
main
...
feat/vt100
|
@ -4,8 +4,8 @@ AFRAME.registerComponent('codemirror', {
|
|||
schema: {
|
||||
file: { type:"string"},
|
||||
term: { type:"selector", default: "[isoterminal]" },
|
||||
width: { type:"number", default:700},
|
||||
height: { type:"number", default:500},
|
||||
width: { type:"number", default:900},
|
||||
height: { type:"number", default:700},
|
||||
},
|
||||
|
||||
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)
|
||||
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?)
|
||||
console.log(str)
|
||||
await this.isoterminal.worker['emulator.update_file'](file, term.convert.toUint8Array(str) )
|
||||
},
|
||||
|
||||
events:{
|
||||
|
@ -118,7 +118,7 @@ AFRAME.registerComponent('codemirror', {
|
|||
// component events
|
||||
DOMready: function(e){
|
||||
|
||||
this.isoterminal.worker.read_file(this.data.file)
|
||||
this.isoterminal.worker['emulator.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.4) )
|
||||
this.el.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.8) )
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ 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
|
||||
|
@ -55,7 +56,6 @@ if( typeof AFRAME != 'undefined '){
|
|||
this.calculateDimension()
|
||||
this.initHud()
|
||||
this.setupBox()
|
||||
this.setupPasteDrop()
|
||||
|
||||
fetch(this.data.iso,{method: 'HEAD'})
|
||||
.then( (res) => {
|
||||
|
@ -72,11 +72,10 @@ 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: "com/lib/xhook.min.js",
|
||||
xhook: "https://jpillora.com/xhook/dist/xhook.min.js",
|
||||
selfcontain: "com/selfcontainer.js",
|
||||
// html to texture
|
||||
htmlinxr: "com/html-as-texture-in-xr.js",
|
||||
|
@ -98,7 +97,7 @@ if( typeof AFRAME != 'undefined '){
|
|||
css: (me) => `.isoterminal{
|
||||
padding: ${me.com.data.padding}px;
|
||||
width:100%;
|
||||
height:90%;
|
||||
height:100%;
|
||||
position:relative;
|
||||
}
|
||||
.isoterminal div{
|
||||
|
@ -134,17 +133,6 @@ 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;
|
||||
|
@ -203,9 +191,7 @@ if( typeof AFRAME != 'undefined '){
|
|||
jsconsole: "com/isoterminal/feat/jsconsole.js",
|
||||
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",
|
||||
autorestore: "com/isoterminal/feat/autorestore.js",
|
||||
})
|
||||
|
||||
this.el.setAttribute("selfcontainer","")
|
||||
|
@ -234,7 +220,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}; class: no-full, no-resize, no-move`)
|
||||
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.addEventListener('window.oncreate', (e) => {
|
||||
|
@ -248,8 +234,7 @@ if( typeof AFRAME != 'undefined '){
|
|||
this.term.start(opts)
|
||||
})
|
||||
|
||||
instance.setAttribute("dom", "")
|
||||
instance.setAttribute("pastedrop", "")
|
||||
instance.setAttribute("dom", "")
|
||||
|
||||
this.term.addEventListener('ready', (e) => {
|
||||
instance.dom.classList.remove('blink')
|
||||
|
@ -274,10 +259,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) )
|
||||
|
@ -307,27 +292,21 @@ if( typeof AFRAME != 'undefined '){
|
|||
|
||||
setupVT100: function(instance){
|
||||
const el = this.el.dom.querySelector('#term')
|
||||
this.term.opts.vt100 = {
|
||||
const opts = {
|
||||
cols: this.cols,
|
||||
rows: this.rows,
|
||||
el_or_id: el,
|
||||
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
|
||||
}
|
||||
max_scroll_lines: 100,
|
||||
nodim: true
|
||||
}
|
||||
this.term.emit('initVT100',this)
|
||||
this.vt100 = new VT100( this.term.opts.vt100 )
|
||||
this.vt100 = new VT100( opts )
|
||||
this.vt100.el = el
|
||||
this.vt100.curs_set( 1, true)
|
||||
this.vt100.focus()
|
||||
this.el.addEventListener('focus', () => this.vt100.focus() )
|
||||
el.focus()
|
||||
this.el.addEventListener('focus', () => el.focus())
|
||||
this.vt100.getch( (ch,t) => {
|
||||
this.term.send( ch )
|
||||
this.vt100.curs_set( 0, true)
|
||||
})
|
||||
|
||||
this.el.addEventListener('serial-output-byte', (e) => {
|
||||
|
@ -339,23 +318,13 @@ 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 });
|
||||
})
|
||||
|
||||
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
|
||||
//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) )
|
||||
// })
|
||||
//})
|
||||
},
|
||||
|
||||
setupBox: function(){
|
||||
|
@ -370,13 +339,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 = Math.floor( document.body.offsetHeight - 30 )
|
||||
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.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*2)
|
||||
this.rows = Math.floor(this.data.height*0.53/this.data.lineHeight*1.7)
|
||||
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)
|
||||
},
|
||||
|
||||
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,10 +37,6 @@ 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){
|
||||
|
@ -52,9 +48,7 @@ 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})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -193,6 +187,7 @@ 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",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,6 +21,7 @@ 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})
|
||||
|
@ -49,47 +50,3 @@ 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.restore_state = async function(data){
|
||||
this['emulator.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.save_state = async function(){
|
||||
this['emulator.save_state'] = async function(){
|
||||
console.log("saving session")
|
||||
let state = await emulator.save_state()
|
||||
localforage.setDriver([
|
||||
|
@ -25,6 +25,7 @@ if( typeof emulator != 'undefined' ){
|
|||
localforage.setItem("state", ISOTerminal.prototype.convert.arrayBufferToBase64(state) )
|
||||
console.log("state saved")
|
||||
})
|
||||
console.dir(state)
|
||||
}
|
||||
|
||||
|
||||
|
@ -46,7 +47,7 @@ if( typeof emulator != 'undefined' ){
|
|||
if( stateBase64 && !err && confirm('continue last session?') ){
|
||||
this.noboot = true // see feat/boot.js
|
||||
try{
|
||||
await this.worker.restore_state()
|
||||
await this.worker['emulator.restore_state']()
|
||||
// simulate / fastforward boot events
|
||||
this.postBoot( () => {
|
||||
this.send("l\n")
|
||||
|
@ -57,7 +58,7 @@ if( typeof emulator != 'undefined' ){
|
|||
})
|
||||
|
||||
this.save = async () => {
|
||||
await this.worker.save_state()
|
||||
await this.worker['emulator.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]+'"') )
|
||||
}
|
||||
}
|
||||
this.worker.create_file("profile.browser", this.convert.toUint8Array( env.join('\n') ) )
|
||||
await this.emit("emulator.create_file", ["profile.browser", this.convert.toUint8Array( env.join('\n') ) ] )
|
||||
|
||||
if( this.serial_input == 0 ){
|
||||
if( !this.noboot ){
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
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=null
|
||||
let PID="?"
|
||||
try{
|
||||
if( script.match(/^PID/) ){
|
||||
PID = script.match(/^PID=([0-9]+);/)[1]
|
||||
|
@ -35,9 +35,7 @@ if( typeof emulator != 'undefined' ){
|
|||
}
|
||||
}
|
||||
// update output to 9p with PID as filename (in /mnt/run)
|
||||
if( PID ){
|
||||
this.worker.update_file(`run/${PID}`, this.convert.toUint8Array(res) )
|
||||
}
|
||||
this.emit('fs9p.update_file', [`run/${PID}`, this.convert.toUint8Array(res)] )
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
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,11 +42,10 @@ this.runISO = function(opts){
|
|||
/*
|
||||
* forward events/functions so non-worker world can reach them
|
||||
*/
|
||||
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]) }
|
||||
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]) }
|
||||
|
||||
// filename will be read from 9pfs: "/mnt/"+filename
|
||||
emulator.readFromPipe = function(filename,cb){
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,179 @@
|
|||
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.
|
||||
`
|
||||
}
|
||||
|
||||
});
|
||||
|
115
com/pastedrop.js
115
com/pastedrop.js
|
@ -1,115 +0,0 @@
|
|||
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,22 +34,16 @@ 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('selfcontainer.js: serving '+request.url+' from cache')
|
||||
console.log('selfcontained cache: '+request.url)
|
||||
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,8 +9,7 @@ AFRAME.registerComponent('window', {
|
|||
max: {type:'boolean',"default":false},
|
||||
min: {type:'boolean',"default":false},
|
||||
x: {type:'string',"default":"center"},
|
||||
y: {type:'string',"default":"center"},
|
||||
"class": {type:'array',"default":[]},
|
||||
y: {type:'string',"default":"center"}
|
||||
},
|
||||
|
||||
dependencies:{
|
||||
|
@ -29,7 +28,6 @@ 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,
|
||||
|
@ -72,7 +70,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, 0.02 )
|
||||
AFRAME.utils.positionObjectNextToNeighbor( current.object3D , last.object3D, els.length )
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -81,10 +79,11 @@ AFRAME.registerComponent('window', {
|
|||
}
|
||||
})
|
||||
|
||||
AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, margin ){
|
||||
AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, neighbours, margin = 0.45, degree = 20) {
|
||||
// *FIXME* this could be more sophisticated :)
|
||||
object.position.x = lastNeighbor.position.x + margin
|
||||
object.position.y = lastNeighbor.position.y - margin
|
||||
object.position.z = lastNeighbor.position.z + margin
|
||||
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);
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue