chatComponent = { html: `
`, init: (el) => new Proxy({ scene: null, visible: true, messages: [], oneMessagePerUser: false, username: '', // configured by 'network.connected' event $videos: el.querySelector("#videos"), $messages: el.querySelector("#messages"), $chatline: el.querySelector("#chatline"), $chatbar: el.querySelector("#chatbar"), install(opts){ this.opts = opts this.scene = opts.scene this.$chatbar.style.display = 'none' el.className = "xrf" el.style.display = 'none' // start hidden document.body.appendChild( el ) document.dispatchEvent( new CustomEvent("$chat:ready", {detail: opts}) ) this.send({message:`Welcome to ${document.location.search.substr(1)}, a 3D scene(file) which simply links to other ones.
You can start a solo offline exploration in XR right away.
Type /help below, or use the arrow- or WASD-keys on your keyboard, and mouse-drag to rotate.
`, class: ["info","guide","multiline"] }) }, initListeners(){ let {$chatline} = this $chatline.addEventListener('click', (e) => this.inform() ) $chatline.addEventListener('keydown', (e) => { if (e.key == 'Enter' ){ let event = $chatline.value.match(/^[!\/]/) ? "chat.command" : "network.send" let message = $chatline.value.replace(/^[!\/]/,'') document.dispatchEvent( new CustomEvent( event, {detail: {message}} ) ) if( event == "network.send" ) this.send({message: $chatline.value }) $chatline.value = '' if( window.innerHeight < 600 ) $chatline.blur() } }) document.addEventListener('network.connect', (e) => { this.visible = true this.$chatbar.style.display = '' // show }) document.addEventListener('network.connected', (e) => { if( e.detail.username ) this.username = e.detail.username }) document.addEventListener('chat.command', (e) => { if( String(e.detail.message).trim() == 'help' ){ let detail = {message:`The following commands are available

/help shows this help screen `} document.dispatchEvent( new CustomEvent( 'chat.command.help', {detail})) this.send({message: detail.message}) } }) }, inform(){ if( !this.inform.informed && (this.inform.informed = true) ){ window.notify("Connected via P2P. You can now type message which will be visible to others.") } }, toggle(){ this.visible = !this.visible if( this.visible && window.meeting.status == 'offline' ) window.meeting.start(this.opts) }, hyphenate(str){ return String(str).replace(/[^a-zA-Z0-9]/g,'-') }, // sending messages to the #messages div // every user can post maximum one msg at a time // it's more like a 'status' which is more friendly // for accessibility reasons // for a fullfledged chat/transcript see matrix clients send(opts){ let {$messages} = this opts = { linebreak:true, message:"", class:[], ...opts } if( window.frontend && window.frontend.emit ) window.frontend.emit('$chat.send', opts ) opts.pos = opts.pos || network.posName || network.pos let div = document.createElement('div') let msg = document.createElement('div') let br = document.createElement('br') let nick = document.createElement('div') msg.className = "msg" let html = `${ opts.message || ''}${ opts.html ? opts.html(opts) : ''}` if( $messages.last == html ) return msg.innerHTML = html if( opts.el ) msg.appendChild(opts.el) opts.id = Math.random() if( opts.class ){ msg.classList.add.apply(msg.classList, opts.class) br.classList.add.apply(br.classList, opts.class) div.classList.add.apply(div.classList, opts.class.concat(["envelope"])) } if( !msg.className.match(/(info|guide|ui)/) && !opts.from ){ let frag = xrf.URI.parse(document.location.hash).XRF opts.from = 'you' if( frag.pos ) opts.pos = frag.pos.string msg.classList.add('self') } if( opts.from ){ nick.className = "user" nick.innerText = opts.from+' ' div.appendChild(nick) if( opts.pos ){ let a = document.createElement("a") a.href = a.innerText = `#pos=${opts.pos}` nick.appendChild(a) } } div.appendChild(msg) // force one message per user if( this.oneMessagePerUser && opts.from ){ div.id = this.hyphenate(opts.from) let oldMsg = $messages.querySelector(`#${div.id}`) if( oldMsg ) oldMsg.remove() } // remove after timeout if( opts.timeout ) setTimeout( (div) => div.remove(), opts.timeout, div ) // finally add the message on top $messages.appendChild(div) if( opts.linebreak ) div.appendChild(br) $messages.scrollTop = $messages.scrollHeight // scroll down $messages.last = msg.innerHTML }, getChatLog(){ return ([...this.$messages.querySelectorAll('.envelope')]) .filter( (d) => !d.className.match(/(info|ui)/) ) .map( (d) => d.innerHTML ) .join('\n') } },{ get(me,k,v){ return me[k] }, set(me,k,v){ me[k] = v switch( k ){ case "visible": { el.style.display = me.visible ? 'block' : 'none' if( !el.inited && (el.inited = true) ) me.initListeners() break; } } } }) } // reactify component! document.addEventListener('$menu:ready', (opts) => { opts = opts.detail document.head.innerHTML += chatComponent.css window.$chat = document.createElement('div') $chat.innerHTML = chatComponent.html $chat = chatComponent.init($chat) $chat.install(opts) //$menu.buttons = ([`📜 toggle text
`]) // .concat($menu.buttons) }) // alpine component for displaying meetings chatComponent.css = ` `