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