Compare commits

..

15 Commits

Author SHA1 Message Date
Leon van Kammen 88c4b21818 added custom keymappings for VT100
/ mirror_to_github (push) Successful in 18s Details
/ test (push) Successful in 4s Details
2024-11-21 11:28:31 +00:00
Leon van Kammen d93401e572 add class to window.js
/ mirror_to_github (push) Successful in 20s Details
/ test (push) Successful in 4s Details
2024-11-20 11:03:44 +00:00
Leon van Kammen dcade45f82 finetuned httpfs.js
/ mirror_to_github (push) Successful in 18s Details
/ test (push) Successful in 4s Details
2024-11-19 16:00:49 +00:00
Leon van Kammen b8e6428611 added httpfs [webrequest to filesystem]
/ mirror_to_github (push) Successful in 20s Details
/ test (push) Successful in 4s Details
2024-11-18 20:18:33 +00:00
Leon van Kammen c5ed500a93 stable copy-paste
/ mirror_to_github (push) Successful in 19s Details
/ test (push) Successful in 4s Details
2024-11-15 09:23:06 +00:00
Leon van Kammen de8883673c refactor beautify: worker['emulator.foo'](..) => worker.foo(..)
/ mirror_to_github (push) Successful in 21s Details
/ test (push) Successful in 3s Details
2024-11-04 11:20:43 +00:00
Leon van Kammen 277dafbd36 don't output ~40MB to console.dir
/ mirror_to_github (push) Successful in 20s Details
/ test (push) Successful in 3s Details
2024-10-30 13:31:30 +00:00
Leon van Kammen 8375e5f1b5 better auto-position window
/ mirror_to_github (push) Successful in 18s Details
/ test (push) Successful in 3s Details
2024-10-28 12:25:14 +00:00
Leon van Kammen 5aeb860aef enter-workaround quest 2 2024-10-28 12:25:03 +00:00
Leon van Kammen 4105cbda09 added keyboard command
/ mirror_to_github (push) Successful in 21s Details
/ test (push) Successful in 5s Details
2024-10-25 13:59:06 +00:00
Leon van Kammen baba5de998 bugfix codemirror 2024-10-25 13:58:54 +00:00
Leon van Kammen c79b764f00 smaller codemirror so it fits on quest window
/ mirror_to_github (push) Successful in 35s Details
/ test (push) Successful in 8s Details
2024-10-24 14:57:54 +00:00
Leon van Kammen 4c52fff332 main: minor bugfix
/ mirror_to_github (push) Successful in 32s Details
/ test (push) Successful in 5s Details
2024-10-24 14:36:41 +00:00
Leon van Kammen bf9aca6d00 make cursor visible in XR too
/ mirror_to_github (push) Successful in 44s Details
/ test (push) Successful in 6s Details
2024-10-24 14:24:51 +00:00
Leon van Kammen 9e6ea795d5 added localforage
/ mirror_to_github (push) Successful in 34s Details
/ test (push) Successful in 7s Details
2024-10-23 16:53:01 +00:00
18 changed files with 3703 additions and 722 deletions

View File

@ -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")

View File

@ -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) )
}
},

View File

@ -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;
@ -191,7 +203,9 @@ 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",
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) => {
@ -234,7 +248,8 @@ if( typeof AFRAME != 'undefined '){
this.term.start(opts)
})
instance.setAttribute("dom", "")
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:{

View File

@ -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 isnt 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

View File

@ -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;
}

View File

@ -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) {

View File

@ -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 ){

View File

@ -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()
})
})
}

View File

@ -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) )
}
})
}

View File

@ -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

View File

@ -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){

4
com/lib/xhook.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -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.
`
}
});

115
com/pastedrop.js Normal file
View File

@ -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.
`
}
});

View File

@ -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{

View File

@ -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
}