mirror of
https://github.com/He4eT/elseifplayer.git
synced 2026-11-01 16:58:29 +00:00
Rename components files to JSX
This commit is contained in:
parent
a100cbef4f
commit
884c53d6ae
8 changed files with 0 additions and 0 deletions
15
src/components/FileSelector.jsx
Normal file
15
src/components/FileSelector.jsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
|
||||||
|
export default function ({ setTargetName, setTargetUrl }) {
|
||||||
|
const fileInputHandler = ({ target }) => {
|
||||||
|
const file = target.files[0]
|
||||||
|
setTargetName(file.name)
|
||||||
|
setTargetUrl(`${URL.createObjectURL(file)}#${file.name}`)
|
||||||
|
target.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type='file'
|
||||||
|
onChange={fileInputHandler} />)
|
||||||
|
}
|
||||||
16
src/components/ThemeSelector.jsx
Normal file
16
src/components/ThemeSelector.jsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
|
||||||
|
export default function ({ themeEngine }) {
|
||||||
|
const options = themeEngine.themes.map(theme => (
|
||||||
|
<option value={theme}>
|
||||||
|
{theme}
|
||||||
|
</option>))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
value={themeEngine.currentTheme}
|
||||||
|
onChange={({ target }) =>
|
||||||
|
themeEngine.setTheme(target.value)}>
|
||||||
|
{options}
|
||||||
|
</select>)
|
||||||
|
}
|
||||||
23
src/components/URLSelector.jsx
Normal file
23
src/components/URLSelector.jsx
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
|
||||||
|
export default function ({ setTargetName, setTargetUrl }) {
|
||||||
|
const urlRE = /^(http|https):\/\/[^ "]+$/
|
||||||
|
|
||||||
|
const emit = url => {
|
||||||
|
setTargetName(url)
|
||||||
|
setTargetUrl(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlInputHandler = ({ target }) => {
|
||||||
|
const url = target.value
|
||||||
|
emit(urlRE.test(url)
|
||||||
|
? url
|
||||||
|
: null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
placeholder='https://...'
|
||||||
|
onInput={urlInputHandler} />)
|
||||||
|
}
|
||||||
63
src/components/player/InputBox.jsx
Normal file
63
src/components/player/InputBox.jsx
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
// import { useState, useEffect } from 'preact/hooks'
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
const keyCodes = {
|
||||||
|
KEY_BACKSPACE: 8,
|
||||||
|
KEY_TAB: 9,
|
||||||
|
KEY_RETURN: 13,
|
||||||
|
KEY_ESC: 27,
|
||||||
|
KEY_PAGEUP: 33,
|
||||||
|
KEY_PAGEDOWN: 34,
|
||||||
|
KEY_END: 35,
|
||||||
|
KEY_HOME: 36,
|
||||||
|
KEY_LEFT: 37,
|
||||||
|
KEY_UP: 38,
|
||||||
|
KEY_RIGHT: 39,
|
||||||
|
KEY_DOWN: 40}
|
||||||
|
|
||||||
|
const keyNames = {
|
||||||
|
[keyCodes.KEY_BACKSPACE]: 'delete',
|
||||||
|
[keyCodes.KEY_TAB]: 'tab',
|
||||||
|
[keyCodes.KEY_RETURN]: 'return',
|
||||||
|
[keyCodes.KEY_ESC]: 'escape',
|
||||||
|
[keyCodes.KEY_PAGEUP]: 'pageup',
|
||||||
|
[keyCodes.KEY_PAGEDOWN]: 'pagedown',
|
||||||
|
[keyCodes.KEY_END]: 'end',
|
||||||
|
[keyCodes.KEY_HOME]: 'home',
|
||||||
|
[keyCodes.KEY_LEFT]: 'left',
|
||||||
|
[keyCodes.KEY_UP]: 'up',
|
||||||
|
[keyCodes.KEY_RIGHT]: 'right',
|
||||||
|
[keyCodes.KEY_DOWN]: 'down'}
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
|
export default function ({ currentWindow, inputType, sendMessage }) {
|
||||||
|
const charHandler = event => {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const key =
|
||||||
|
keyNames[event.keyCode] ||
|
||||||
|
event.key
|
||||||
|
|
||||||
|
sendMessage(key, currentWindow)
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineHandler = ({ keyCode, target: { value } }) => {
|
||||||
|
if (keyCode === keyCodes.KEY_RETURN) {
|
||||||
|
sendMessage(value, currentWindow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputHandlers = {
|
||||||
|
'char': {
|
||||||
|
onKeyDown: charHandler
|
||||||
|
},
|
||||||
|
'line': {
|
||||||
|
onKeyPress: lineHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<input {...inputHandlers[inputType]}
|
||||||
|
type="text"/>
|
||||||
|
)
|
||||||
|
}
|
||||||
100
src/components/player/Player.jsx
Normal file
100
src/components/player/Player.jsx
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
import { useState, useEffect } from 'preact/hooks'
|
||||||
|
|
||||||
|
import CheapGlkOte from 'cheap-glkote'
|
||||||
|
|
||||||
|
import TextBuffer from './TextBuffer'
|
||||||
|
import InputBox from './InputBox'
|
||||||
|
|
||||||
|
const INITIAL_STATUS = {
|
||||||
|
stage: 'loading',
|
||||||
|
details: 'Preparing...'
|
||||||
|
}
|
||||||
|
|
||||||
|
const runMachine = ({ Engine, file, handlers }) => {
|
||||||
|
console.log('runMachine')
|
||||||
|
|
||||||
|
const vm = new Engine()
|
||||||
|
const { glkInterface, sendFn } = CheapGlkOte(handlers)
|
||||||
|
|
||||||
|
vm.prepare(file, glkInterface)
|
||||||
|
vm.start()
|
||||||
|
|
||||||
|
return { sendFn, instance: vm }
|
||||||
|
}
|
||||||
|
|
||||||
|
const Handlers = ({
|
||||||
|
setStatus,
|
||||||
|
setCurrentWindow,
|
||||||
|
setInputType,
|
||||||
|
setInbox
|
||||||
|
}) => ({
|
||||||
|
onInit: _ => setStatus({ stage: 'ready' }),
|
||||||
|
/* */
|
||||||
|
onUpdateWindows: windows => {
|
||||||
|
setCurrentWindow(windows
|
||||||
|
.filter(x => x.type === 'buffer')
|
||||||
|
.slice(-1)[0])
|
||||||
|
},
|
||||||
|
onUpdateInputs: setInputType,
|
||||||
|
onUpdateContent: setInbox,
|
||||||
|
onDisable: _ => setInputType(null),
|
||||||
|
/* */
|
||||||
|
onFileNameRequest: (tosave, usage, _, setFileName) => {
|
||||||
|
setFileName({ filename: 'filename', usage })
|
||||||
|
},
|
||||||
|
onFileRead: filename => {
|
||||||
|
return 'content'
|
||||||
|
},
|
||||||
|
onFileWrite: (filename, content) => {},
|
||||||
|
/* */
|
||||||
|
onExit: _ => setInputType(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function ({ vmParts: { file, engine } }) {
|
||||||
|
const [status, setStatus] = useState(INITIAL_STATUS)
|
||||||
|
|
||||||
|
const [currentWindow, setCurrentWindow] = useState(null)
|
||||||
|
const [inputType, setInputType] = useState(null)
|
||||||
|
const [inbox, setInbox] = useState([])
|
||||||
|
|
||||||
|
const [vm, setVm] = useState(null)
|
||||||
|
const [sendMessage, setSendMessage] = useState(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handlers = Handlers({
|
||||||
|
setStatus,
|
||||||
|
setCurrentWindow,
|
||||||
|
setInputType,
|
||||||
|
setInbox
|
||||||
|
})
|
||||||
|
|
||||||
|
const vm = runMachine({
|
||||||
|
Engine: engine,
|
||||||
|
file,
|
||||||
|
handlers
|
||||||
|
})
|
||||||
|
|
||||||
|
setVm(vm)
|
||||||
|
}, [file, engine])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSendMessage(_ => vm
|
||||||
|
? vm.sendFn
|
||||||
|
: null)
|
||||||
|
}, [vm])
|
||||||
|
|
||||||
|
return status.stage !== 'ready'
|
||||||
|
? (<div>{status.details}</div>)
|
||||||
|
: (<section>
|
||||||
|
<TextBuffer {...{
|
||||||
|
inbox,
|
||||||
|
currentWindow
|
||||||
|
}}/>
|
||||||
|
<InputBox {...{
|
||||||
|
currentWindow,
|
||||||
|
inputType,
|
||||||
|
sendMessage
|
||||||
|
}}/>
|
||||||
|
</section>)
|
||||||
|
}
|
||||||
55
src/components/player/TextBuffer.jsx
Normal file
55
src/components/player/TextBuffer.jsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
import { useState, useEffect } from 'preact/hooks'
|
||||||
|
|
||||||
|
const parseInbox = (inbox, currentWindow) => {
|
||||||
|
const currentInbox =
|
||||||
|
inbox.find(({id}) =>
|
||||||
|
id === currentWindow.id)
|
||||||
|
|
||||||
|
if (!currentInbox) return {
|
||||||
|
clear: false,
|
||||||
|
incoming: []}
|
||||||
|
|
||||||
|
const {clear, text: inboxMessagesRaw} =
|
||||||
|
currentInbox
|
||||||
|
|
||||||
|
const incoming =
|
||||||
|
inboxMessagesRaw
|
||||||
|
/* Normalize. */
|
||||||
|
.map(({content}) =>
|
||||||
|
content || [{ style: 'emptyLine' }])
|
||||||
|
/* Flatten. */
|
||||||
|
.reduce((acc, x) =>
|
||||||
|
acc.concat(x), [])
|
||||||
|
/* Collapse empty lines. */
|
||||||
|
.reduce((acc, x, i, xs) => {
|
||||||
|
if (x.style !== 'emptyLine') return [...acc, x]
|
||||||
|
|
||||||
|
const prev = xs[i - 1] || {}
|
||||||
|
return prev.style === 'emptyLine'
|
||||||
|
? acc
|
||||||
|
: [...acc, x]
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return {clear, incoming}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ({ inbox, currentWindow }) {
|
||||||
|
const [messages, setMessages] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const {incoming, clear} =
|
||||||
|
parseInbox(inbox, currentWindow)
|
||||||
|
|
||||||
|
setMessages(clear
|
||||||
|
? incoming
|
||||||
|
: messages.concat(incoming))
|
||||||
|
}, [inbox])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{messages?.map(({text}) =>
|
||||||
|
(<div>{text}</div>))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
51
src/components/player/UrlPlayer.jsx
Normal file
51
src/components/player/UrlPlayer.jsx
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
import { useState, useEffect } from 'preact/hooks'
|
||||||
|
|
||||||
|
import { engineByFilename } from './common/engines'
|
||||||
|
|
||||||
|
import Player from './Player'
|
||||||
|
|
||||||
|
const INITIAL_STATUS = {
|
||||||
|
stage: 'loading',
|
||||||
|
details: 'Loading...'
|
||||||
|
}
|
||||||
|
|
||||||
|
const prepareVM = ({ url, setStatus, setParts }) => {
|
||||||
|
const st = (stage, details) => args => {
|
||||||
|
setStatus({ stage, details })
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(st('loading', 'Downloading file...'))
|
||||||
|
.then(_ => fetch(url))
|
||||||
|
.then(st('loading', 'Processing file...'))
|
||||||
|
.then(response => response.arrayBuffer())
|
||||||
|
.then(arrayBuffer => new Uint8Array(arrayBuffer))
|
||||||
|
.then(st('loading', 'Downloading engine...'))
|
||||||
|
.then(file => setParts({
|
||||||
|
file,
|
||||||
|
engine: engineByFilename(url)
|
||||||
|
}))
|
||||||
|
.then(st('loading', 'Running...'))
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
setStatus({ stage: 'fail', details: e.message })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ({ url }) {
|
||||||
|
const [status, setStatus] = useState(INITIAL_STATUS)
|
||||||
|
const [vmParts, setParts] = useState(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setStatus(INITIAL_STATUS)
|
||||||
|
setParts(null)
|
||||||
|
|
||||||
|
prepareVM({ url, setStatus, setParts })
|
||||||
|
}, [url])
|
||||||
|
|
||||||
|
return vmParts
|
||||||
|
? (<Player vmParts={vmParts} />)
|
||||||
|
: (<div>{status.details}</div>)
|
||||||
|
}
|
||||||
51
src/views/HomeView.jsx
Normal file
51
src/views/HomeView.jsx
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
import { useState } from 'preact/hooks'
|
||||||
|
|
||||||
|
import { Link } from 'wouter-preact'
|
||||||
|
|
||||||
|
import FileSelector from '~/src/components/FileSelector'
|
||||||
|
import URLSelector from '~/src/components/URLSelector'
|
||||||
|
import ThemeSelector from '~/src/components/ThemeSelector'
|
||||||
|
|
||||||
|
import { buildPlayLinkHref } from '~/src/utils/utils.routing'
|
||||||
|
|
||||||
|
const playButton = (name, url, theme) => (
|
||||||
|
<Link href={buildPlayLinkHref(url, theme)}>
|
||||||
|
Play "{name}"
|
||||||
|
</Link>)
|
||||||
|
|
||||||
|
export default function ({ themeEngine }) {
|
||||||
|
const [targetName, setTargetName] = useState(null)
|
||||||
|
const [targetUrl, setTargetUrl] = useState(null)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<p>
|
||||||
|
You can browse some <Link href='/top100'>
|
||||||
|
games from IFDB
|
||||||
|
</Link> or play a game from a file.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<ThemeSelector {...{
|
||||||
|
themeEngine
|
||||||
|
}} />
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<FileSelector {...{
|
||||||
|
setTargetName,
|
||||||
|
setTargetUrl
|
||||||
|
}} />
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<URLSelector {...{
|
||||||
|
setTargetName,
|
||||||
|
setTargetUrl
|
||||||
|
}} />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{ targetUrl
|
||||||
|
? playButton(targetName, targetUrl, themeEngine.currentTheme)
|
||||||
|
: null }
|
||||||
|
</main>)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue