|
- const State = Object.seal({
- username: null,
- websocket: null,
- online: [],
- get isConnected() {
- return State.websocket && State.websocket.readyState === 1
- },
- })
- const params = (new URL(document.location)).searchParams
- /*
- *
- * SIGNALING
- *
- */
- addEventListener('resize', () => m.redraw())
- const wire = (message) => State.websocket.send(JSON.stringify(message))
- const signal = (message) => dispatchEvent(new CustomEvent(message.kind, {detail: message}))
- const listen = (kind, handler) => {
- addEventListener(kind, handler)
- }
- listen('login', ({detail}) => {
- State.username = detail.value
- })
- listen('logout', ({detail}) => {
- State.online = []
- })
- listen('state', ({detail}) => {
- delete detail.ts
- delete detail.kind
- Object.assign(State, detail)
- })
- const doNotLog = new Set(['login', 'state', 'post', 'peerInfo', 'volumeMapMove'])
- /*
- *
- * UTILS
- *
- */
- const autoFocus = (vnode) => {
- vnode.dom.focus()
- }
- /*
- *
- * WEBSOCKET
- *
- */
- const connect = (username) => {
- const wsUrl = location.href.replace('http', 'ws')
-
- State.websocket = new WebSocket(wsUrl)
-
- State.websocket.onopen = (e) => {
- wire({kind: 'login', value: username})
- }
-
- State.websocket.onmessage = (e) => {
- const message = JSON.parse(e.data)
-
- if(message.online) {
- const difference = (l1, l2) => l1.filter(u => !l2.includes(u))
- difference(message.online, State.online).forEach(username => {
- if(username === State.username) return
- signal({kind: 'post', ts: message.ts, value: `${username} joined`})
- })
- difference(State.online, message.online).forEach(username => {
- if(username === State.username) return
- signal({kind: 'post', ts: message.ts, value: `${username} left`})
- })
- }
-
- if(!doNotLog.has(message.kind)) {
- console.log(message)
- }
- signal(message)
-
- m.redraw()
- }
-
- State.websocket.onclose = (e) => {
- State.online.forEach(signalPeerStop)
- if(!e.wasClean) {
- setTimeout(connect, 1000, username)
- }
- m.redraw()
- }
- }
- /*
- *
- * BASE
- *
- */
- const Shadow = {
- oncreate({dom, attrs}) {
- dom.listener = () => attrs.app.isOn = !attrs.app.isOn
- addEventListener(attrs.key, dom.listener)
- },
- onremove({dom, attrs}) {
- removeEventListener(attrs.key, dom.listener)
- },
- view({attrs}) {
- const style = {
- zIndex: 999,
- backgroundColor: 'rgba(0, 0, 0, 0.2)',
- visibility: attrs.app.isOn ? 'unset' : 'hidden',
- position: 'fixed',
- right: 0,
- top: 0,
- height: '100vh',
- width: '100vw',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- }
- const onclick = ({target: {classList}}) => {
- classList.contains('shadow') && signal({kind: attrs.key})
- }
- return m('.shadow', {style, onclick}, m(attrs.app))
- },
- }
- const Settings = {
- get(key) {
- try {
- return JSON.parse(localStorage.getItem(key))
- }
- catch(error) {
- return null
- }
- },
- set(key, value) {
- localStorage.setItem(key, JSON.stringify(value))
- },
- multiField: ([key, options]) => {
- let current = Settings.get(key)
- if(!options.includes(current)) {
- Settings.set(key, options[0])
- }
- return m('.field',
- m('label', key),
- options.map(value => {
- const style = {
- fontWeight: Settings.get(key) == value ? 'bold' : 'unset'
- }
- const onclick = () => Settings.set(key, value)
- return m('button', {style, onclick}, `${value}`)
- })
- )
- },
- view() {
- return m('.settings',
- Object.entries({
- blackBars: [true, false],
- }).map(Settings.multiField)
- )
- },
- }
- const Base = {
- oncreate: () => {
- const randomName = ('' + Math.random()).substring(2)
- connect(localStorage.username || randomName)
- },
- sendLogin: (e) => {
- e.preventDefault()
- const username = e.target.username.value
- localStorage.username = username
- connect(username)
- },
- sendLogout: (e) => {
- e.preventDefault()
- wire({kind: 'logout'})
- signal({kind: 'logout'})
- },
- view() {
- const attrs = {
- oncreate: autoFocus,
- name: 'username',
- autocomplete: 'off',
- value: localStorage.username,
- }
- const headerStyle = {
- display: 'grid',
- gridAutoFlow: 'column',
- justifyItems: 'start',
- marginRight: 'auto',
- }
- return [
- m('header', {style: headerStyle},
- State.isConnected
- ? Headers.map(h => m(...h))
- : [
- m('form.login',
- {onsubmit: Base.sendLogin},
- m('input', attrs),
- m('button', 're-join'),
- ),
- ],
- ),
- m('main', {style: {overflow: 'hidden'}},
- State.isConnected
- ? Apps.map(a => m(...a))
- : [
- m('span.error', State.info),
- m(Settings),
- ],
- ),
- ]
- },
- }
- const Headers = [
- ['button', {onclick: Base.sendLogout}, 'log-out'],
- // m('button', {onclick: VolumeMap.toggle}, 'volume'),
- // m('button', {onclick: ScreenShare.toggle}, 'screen'),
- ]
- const Apps = [
- // m(Shadow, {key: 'map-shadow', app: VolumeMap}),
- // m(Shadow, {key: 'screen-shadow', app: ScreenShare}),
- ]
- addEventListener('load', () => m.mount(document.body, Base))
|