mirror of
https://github.com/He4eT/elseifplayer.git
synced 2026-11-02 01:08:30 +00:00
Rearrange components
This commit is contained in:
parent
7e2de6b269
commit
9f1057ed2f
16 changed files with 14 additions and 11 deletions
|
|
@ -3,7 +3,7 @@ import { Link } from 'wouter-preact'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
buildPlayLinkHref,
|
buildPlayLinkHref,
|
||||||
} from '~/src/utils/utils.routing'
|
} from '~/src/routing'
|
||||||
|
|
||||||
export default function GameEntry ({ name, ifdb, url }) {
|
export default function GameEntry ({ name, ifdb, url }) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
153
src/components/Player/InputBox/InputBox.jsx
Normal file
153
src/components/Player/InputBox/InputBox.jsx
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||||
|
|
||||||
|
import s from './MenuButton/MenuButton.module.scss'
|
||||||
|
|
||||||
|
/* 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 InputBox ({
|
||||||
|
inputType,
|
||||||
|
windows,
|
||||||
|
currentWindowId,
|
||||||
|
sendMessage,
|
||||||
|
onFullscreenRequest,
|
||||||
|
setMenuOpen,
|
||||||
|
}) {
|
||||||
|
const [targetWindow, setTargetWindow] = useState(null)
|
||||||
|
const [inputText, setInputText] = useState('')
|
||||||
|
const [lastInput, setLastInput] = useState('')
|
||||||
|
const inputEl = useRef(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInputText('')
|
||||||
|
inputEl.current && inputEl.current.focus()
|
||||||
|
}, [inputType])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTargetWindow(
|
||||||
|
windows
|
||||||
|
.find(({ id }) =>
|
||||||
|
id === currentWindowId))
|
||||||
|
}, [currentWindowId, windows])
|
||||||
|
|
||||||
|
const send = (message) => {
|
||||||
|
sendMessage(
|
||||||
|
message,
|
||||||
|
inputType,
|
||||||
|
targetWindow)
|
||||||
|
setLastInput(message)
|
||||||
|
setInputText('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const charHandler = (event) =>
|
||||||
|
(event.keyCode === 229
|
||||||
|
? charHandlerMobile
|
||||||
|
: charHandlerDefault)(event)
|
||||||
|
|
||||||
|
const charHandlerDefault = (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const key =
|
||||||
|
keyNames[event.keyCode] ||
|
||||||
|
event.key
|
||||||
|
|
||||||
|
send(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const charHandlerMobile = (event) =>
|
||||||
|
setTimeout(() => {
|
||||||
|
send(event.target.value.slice(-1).toUpperCase())
|
||||||
|
setInputText('')
|
||||||
|
})
|
||||||
|
|
||||||
|
const lineHandler = ({ keyCode, target: { value } }) => {
|
||||||
|
if (keyCode === keyCodes.KEY_RETURN) {
|
||||||
|
send(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineArrowHandler = ({ keyCode }) => {
|
||||||
|
if (keyCode === keyCodes.KEY_UP) {
|
||||||
|
setInputText(lastInput)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const end = lastInput.length
|
||||||
|
inputEl.current.setSelectionRange(end, end)
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
if (keyCode === keyCodes.KEY_DOWN) {
|
||||||
|
setInputText('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputHandlers = {
|
||||||
|
char: {
|
||||||
|
maxlength: '1',
|
||||||
|
placeholder: 'Press any key here',
|
||||||
|
onKeyDown: charHandler,
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
placeholder: ' > ',
|
||||||
|
onKeyDown: lineArrowHandler,
|
||||||
|
onKeyPress: lineHandler,
|
||||||
|
},
|
||||||
|
finished: {
|
||||||
|
placeholder: 'The program has finished',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className='inputControls'>
|
||||||
|
<input {...inputHandlers[inputType]}
|
||||||
|
className='inputBox'
|
||||||
|
ref={inputEl}
|
||||||
|
value={inputText}
|
||||||
|
autofocus
|
||||||
|
autocomplete='off'
|
||||||
|
spellCheck='false'
|
||||||
|
autocapitalize='off'
|
||||||
|
autocorrect='off'
|
||||||
|
onDblClick={onFullscreenRequest}
|
||||||
|
onInput={({ target: { value } }) => setInputText(value)}
|
||||||
|
type='search' />
|
||||||
|
<button
|
||||||
|
aria-label='Menu'
|
||||||
|
className={s.menuButton}
|
||||||
|
onClick={() => setMenuOpen(true)}
|
||||||
|
>
|
||||||
|
=
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
.menuButton {
|
||||||
|
border-left: none;
|
||||||
|
outline-offset: -8px;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline-offset: -4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/components/Player/MenuOverlay/MenuOverlay.css
Normal file
41
src/components/Player/MenuOverlay/MenuOverlay.css
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
.menu {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
border-color: var(--main-color);
|
||||||
|
color: var(--main-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu::backdrop {
|
||||||
|
background: none;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu > section {
|
||||||
|
margin: 32px auto 40px;
|
||||||
|
gap: 32px;
|
||||||
|
max-width: 250px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu .navigation {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu .appearance {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu select,
|
||||||
|
.menu button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
78
src/components/Player/MenuOverlay/MenuOverlay.jsx
Normal file
78
src/components/Player/MenuOverlay/MenuOverlay.jsx
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { useEffect, useRef } from 'preact/hooks'
|
||||||
|
import { Link } from 'wouter-preact'
|
||||||
|
|
||||||
|
import ThemeSelector from
|
||||||
|
'~/src/components/ThemeSelector/ThemeSelector'
|
||||||
|
|
||||||
|
import './MenuOverlay.css'
|
||||||
|
|
||||||
|
export default function MenuOverlay ({
|
||||||
|
themeEngine, onFullscreenRequest, menuOpen, setMenuOpen,
|
||||||
|
}) {
|
||||||
|
const dialog = useRef(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const dialogOpen = dialog.current.open
|
||||||
|
|
||||||
|
if (menuOpen && !dialogOpen) {
|
||||||
|
dialog.current.showModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!menuOpen && dialogOpen) {
|
||||||
|
dialog.current.close()
|
||||||
|
}
|
||||||
|
}, [menuOpen])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const closeHandler = () => {
|
||||||
|
setMenuOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.current.addEventListener('close', closeHandler)
|
||||||
|
return () => dialog.current.removeEventListener('close', closeHandler)
|
||||||
|
}, [dialog])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<dialog ref={dialog} className='menu'>
|
||||||
|
<section>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => dialog.current.close()}
|
||||||
|
>
|
||||||
|
Close this menu
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='appearance'>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
dialog.current.close()
|
||||||
|
onFullscreenRequest()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Full screen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => themeEngine.setRandomTheme()}
|
||||||
|
>
|
||||||
|
Set a random theme
|
||||||
|
</button>
|
||||||
|
<label>
|
||||||
|
Current theme:
|
||||||
|
<ThemeSelector {...{
|
||||||
|
themeEngine,
|
||||||
|
}} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='navigation'>
|
||||||
|
<Link href="/" tabIndex={0}>
|
||||||
|
ElseIfPlayer
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
79
src/components/Player/OutputBox/GridBuffer.jsx
Normal file
79
src/components/Player/OutputBox/GridBuffer.jsx
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
import { useEffect, useState } from 'preact/hooks'
|
||||||
|
|
||||||
|
import TextMessage from './TextMessage'
|
||||||
|
|
||||||
|
export default function GridBuffer ({ inbox, currentWindow }) {
|
||||||
|
const [prevMessages, setPrevMessages] = useState([])
|
||||||
|
const [messages, setMessages] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const currentInboxObj =
|
||||||
|
inbox.find(({ id }) =>
|
||||||
|
id === currentWindow.id)
|
||||||
|
|
||||||
|
const currentInbox = currentInboxObj?.lines ?? []
|
||||||
|
|
||||||
|
const newOrPrev = (cur, prev) => (i) => {
|
||||||
|
const byId = (list, i) =>
|
||||||
|
list.find(({ line }) => line === i)
|
||||||
|
|
||||||
|
return byId(cur, i) || byId(prev, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawMessages =
|
||||||
|
Array(currentWindow.gridheight)
|
||||||
|
.fill(null)
|
||||||
|
.map((_, i) => i)
|
||||||
|
.map(newOrPrev(currentInbox, prevMessages))
|
||||||
|
|
||||||
|
/* */
|
||||||
|
|
||||||
|
const shouldUpdatePrev = (rawMessages, prevMessages) => {
|
||||||
|
const serialize = JSON.stringify
|
||||||
|
return serialize(rawMessages) !== serialize(prevMessages)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldUpdatePrev(rawMessages, prevMessages)) {
|
||||||
|
setPrevMessages(rawMessages)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* */
|
||||||
|
|
||||||
|
|
||||||
|
const rawMessagesContent =
|
||||||
|
rawMessages
|
||||||
|
.map((x) => x.content)
|
||||||
|
.flat()
|
||||||
|
.map((message) => ({
|
||||||
|
...message,
|
||||||
|
text: message.text.trim(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isEmpty =
|
||||||
|
rawMessagesContent
|
||||||
|
.map(({text}) => text.length)
|
||||||
|
.every((l) => l === 0)
|
||||||
|
|
||||||
|
const getGridStyle = ({style}) => {
|
||||||
|
if (['alert', 'normal'].includes(style)) return 'grid'
|
||||||
|
else return style || 'grid'
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages =
|
||||||
|
rawMessagesContent
|
||||||
|
.map((message) => ({
|
||||||
|
style: getGridStyle(message),
|
||||||
|
text: message.text.replace(' ', ' / '),
|
||||||
|
}))
|
||||||
|
|
||||||
|
setMessages(isEmpty ? [] : messages)
|
||||||
|
}, [inbox, currentWindow, prevMessages])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className='buffer gridBuffer'>
|
||||||
|
{messages.map(TextMessage)}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
93
src/components/Player/OutputBox/TextBuffer.jsx
Normal file
93
src/components/Player/OutputBox/TextBuffer.jsx
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||||
|
|
||||||
|
import TextMessage from './TextMessage'
|
||||||
|
|
||||||
|
const isFakeStatus = (w) =>
|
||||||
|
w.height < 5
|
||||||
|
|
||||||
|
const trimInputPrompt = (messages) =>
|
||||||
|
messages.length < 1
|
||||||
|
? messages
|
||||||
|
: messages.slice(-1)[0].text === '>'
|
||||||
|
? messages.slice(0, messages.length - 1)
|
||||||
|
: messages
|
||||||
|
|
||||||
|
const parseInbox = (inbox, currentWindow) => {
|
||||||
|
const currentInbox =
|
||||||
|
inbox.find(({ id }) =>
|
||||||
|
id === currentWindow.id)
|
||||||
|
|
||||||
|
if (!currentInbox) {
|
||||||
|
return {
|
||||||
|
clear: false,
|
||||||
|
incoming: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { text: inboxMessagesRaw } =
|
||||||
|
currentInbox
|
||||||
|
|
||||||
|
const eol = { style: 'endOfLine' }
|
||||||
|
|
||||||
|
const incoming =
|
||||||
|
inboxMessagesRaw
|
||||||
|
/* Normalize. */
|
||||||
|
.map(({ content }) =>
|
||||||
|
content
|
||||||
|
? [...trimInputPrompt(content), eol]
|
||||||
|
: [eol])
|
||||||
|
/* Flatten. */
|
||||||
|
.reduce((acc, x) =>
|
||||||
|
acc.concat(x), [])
|
||||||
|
|
||||||
|
return {
|
||||||
|
incoming,
|
||||||
|
clear: isFakeStatus(currentWindow)
|
||||||
|
? true
|
||||||
|
: currentInbox.clear,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TextBuffer ({ inbox, currentWindow }) {
|
||||||
|
const [messages, setMessages] = useState([])
|
||||||
|
const textBufferEl = useRef(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { incoming, clear } =
|
||||||
|
parseInbox(inbox, currentWindow)
|
||||||
|
|
||||||
|
setMessages((messages) => clear
|
||||||
|
? incoming
|
||||||
|
: messages.concat(incoming))
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const inputs =
|
||||||
|
textBufferEl.current.querySelectorAll('.message.input')
|
||||||
|
const lastInput =
|
||||||
|
inputs[inputs.length - 1]
|
||||||
|
|
||||||
|
textBufferEl.current.scrollTo({
|
||||||
|
top: lastInput
|
||||||
|
? lastInput.offsetTop
|
||||||
|
: textBufferEl.current.scrollHeight,
|
||||||
|
behavior: 'smooth',
|
||||||
|
})
|
||||||
|
}, 0)
|
||||||
|
}, [currentWindow, inbox])
|
||||||
|
|
||||||
|
const classes = [
|
||||||
|
isFakeStatus(currentWindow)
|
||||||
|
? 'gridBuffer'
|
||||||
|
: 'textBuffer',
|
||||||
|
'buffer'].join(' ')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
tabindex='0'
|
||||||
|
ref={textBufferEl}
|
||||||
|
className={classes}>
|
||||||
|
{messages.map(TextMessage)}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
16
src/components/Player/OutputBox/TextMessage.jsx
Normal file
16
src/components/Player/OutputBox/TextMessage.jsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
|
||||||
|
export default function TextMessage ({ style, text }) {
|
||||||
|
const defaultContent = (
|
||||||
|
<span className={['message', style].join(' ')}>
|
||||||
|
{text}
|
||||||
|
</span>)
|
||||||
|
|
||||||
|
return ({
|
||||||
|
grid: (text?.length > 0 ? <div>{text}</div> : <br />),
|
||||||
|
input: (<span className='message input'>> {text}</span>),
|
||||||
|
subheader: (<strong className='message subheader'>{text}</strong>),
|
||||||
|
emphasized: (<em className='message emphasized'>{text}</em>),
|
||||||
|
endOfLine: (<br />),
|
||||||
|
})[style] || defaultContent
|
||||||
|
}
|
||||||
|
|
@ -3,13 +3,16 @@ import { useState, useEffect } from 'preact/hooks'
|
||||||
|
|
||||||
import CheapGlkOte from 'cheap-glkote'
|
import CheapGlkOte from 'cheap-glkote'
|
||||||
|
|
||||||
import TextBuffer from './TextBuffer'
|
import TextBuffer from './OutputBox/TextBuffer'
|
||||||
import GridBuffer from './GridBuffer'
|
import GridBuffer from './OutputBox/GridBuffer'
|
||||||
|
|
||||||
import InputBox from './InputBox'
|
import InputBox from './InputBox/InputBox'
|
||||||
import Status from './Status'
|
import Status from './Status/Status'
|
||||||
|
|
||||||
import { Handlers, unhandledRejectionHandler } from './playerHandlers'
|
import {
|
||||||
|
Handlers,
|
||||||
|
unhandledRejectionHandler
|
||||||
|
} from './common/playerHandlers'
|
||||||
|
|
||||||
import './player.css'
|
import './player.css'
|
||||||
|
|
||||||
|
|
|
||||||
31
src/components/Player/Status/Status.jsx
Normal file
31
src/components/Player/Status/Status.jsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { h } from 'preact'
|
||||||
|
import { Link } from 'wouter-preact'
|
||||||
|
|
||||||
|
const fail = (details) => (
|
||||||
|
<div className='status fail'>
|
||||||
|
<h1>
|
||||||
|
Error
|
||||||
|
</h1>
|
||||||
|
{details.map((x) => (<p key={x}>{x}</p>))}
|
||||||
|
<hr />
|
||||||
|
<Link href='/'>
|
||||||
|
Home
|
||||||
|
</Link>
|
||||||
|
|
|
||||||
|
<a
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
href='https://github.com/He4eT/elseifplayer/issues'>
|
||||||
|
Report bug
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const loading = (details) => (
|
||||||
|
<div className='status loading'>
|
||||||
|
{details.map((x) => (<div key={x}>{x}</div>))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ({ stage, details }) =>
|
||||||
|
({ fail, loading })[stage](details)
|
||||||
|
|
@ -4,7 +4,7 @@ import { useState, useEffect } from 'preact/hooks'
|
||||||
import { engineByFilename } from './common/engines'
|
import { engineByFilename } from './common/engines'
|
||||||
|
|
||||||
import Player from './Player'
|
import Player from './Player'
|
||||||
import Status from './Status'
|
import Status from './Status/Status'
|
||||||
|
|
||||||
const INITIAL_STATUS = {
|
const INITIAL_STATUS = {
|
||||||
stage: 'loading',
|
stage: 'loading',
|
||||||
|
|
|
||||||
57
src/components/Player/common/playerHandlers.js
Normal file
57
src/components/Player/common/playerHandlers.js
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { encode, decode } from 'base32768'
|
||||||
|
|
||||||
|
export const Handlers = ({
|
||||||
|
setStatus,
|
||||||
|
setWindows,
|
||||||
|
setCurrentWindowId,
|
||||||
|
setInputType,
|
||||||
|
setInbox,
|
||||||
|
}) => ({
|
||||||
|
onInit: () => {
|
||||||
|
setStatus({ stage: 'ready' })
|
||||||
|
},
|
||||||
|
/* */
|
||||||
|
onUpdateWindows: (windows) => {
|
||||||
|
setWindows(windows)
|
||||||
|
},
|
||||||
|
onUpdateInputs: (data) => {
|
||||||
|
if (data.length === 0) return null
|
||||||
|
|
||||||
|
const { type, id } = data[0]
|
||||||
|
setCurrentWindowId(id)
|
||||||
|
setInputType(type)
|
||||||
|
},
|
||||||
|
onUpdateContent: (inbox) => {
|
||||||
|
setInbox(inbox)
|
||||||
|
},
|
||||||
|
onDisable: () => {
|
||||||
|
setInputType(null)
|
||||||
|
},
|
||||||
|
/* */
|
||||||
|
onFileNameRequest: (_tosave, usage, _gameId, setFileName) => {
|
||||||
|
setFileName({
|
||||||
|
usage,
|
||||||
|
filename: prompt('Enter the filename'),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onFileRead: ({ filename }) => {
|
||||||
|
const content = localStorage.getItem(`fake-fs/${filename}`)
|
||||||
|
return decode(content)
|
||||||
|
},
|
||||||
|
onFileWrite: ({ filename }, content) => {
|
||||||
|
localStorage.setItem(`fake-fs/${filename}`, encode(content))
|
||||||
|
},
|
||||||
|
/* */
|
||||||
|
onExit: () => {
|
||||||
|
setInputType('finished')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const unhandledRejectionHandler = (onExit) => (event) => {
|
||||||
|
if (event.reason.name === 'ExitStatus' || event.reason.message === 'Program terminated with exit(0)') {
|
||||||
|
onExit()
|
||||||
|
} else {
|
||||||
|
console.error('Unhandled rejection (promise: ', event.promise, ', reason: ', event.reason, ').')
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import { Route, Router, Switch } from 'wouter-preact'
|
||||||
import {
|
import {
|
||||||
useHashLocation,
|
useHashLocation,
|
||||||
extractView,
|
extractView,
|
||||||
} from '~/src/utils/utils.routing'
|
} from './routing'
|
||||||
import {
|
import {
|
||||||
useThemeEngine,
|
useThemeEngine,
|
||||||
} from '~/src/themes/themes'
|
} from '~/src/themes/themes'
|
||||||
|
|
|
||||||
33
src/routing.js
Normal file
33
src/routing.js
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import {
|
||||||
|
useState, useEffect, useCallback,
|
||||||
|
} from 'preact/hooks'
|
||||||
|
|
||||||
|
export const useHashLocation = () => {
|
||||||
|
const currentLoc = () =>
|
||||||
|
window.location.hash.replace('#', '') || '/'
|
||||||
|
|
||||||
|
const [loc, setLoc] = useState(currentLoc())
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = () => {
|
||||||
|
setLoc(currentLoc())
|
||||||
|
window.scrollTo(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler()
|
||||||
|
window.addEventListener('hashchange', handler)
|
||||||
|
return () => window.removeEventListener('hashchange', handler)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const navigate = useCallback((to) =>
|
||||||
|
(window.location.hash = to.replace('#/', '')), [])
|
||||||
|
return [loc, navigate]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildPlayLinkHref = ({ url }) =>
|
||||||
|
`/#/play/${encodeURIComponent(url)}`
|
||||||
|
|
||||||
|
export const extractView = (location) => {
|
||||||
|
const currentView = location.split('/').filter(Boolean)[0]
|
||||||
|
return currentView || ''
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import { Link } from 'wouter-preact'
|
||||||
import {
|
import {
|
||||||
useHashLocation,
|
useHashLocation,
|
||||||
buildPlayLinkHref,
|
buildPlayLinkHref,
|
||||||
} from '~/src/utils/utils.routing'
|
} from '~/src/routing'
|
||||||
|
|
||||||
import LocalFileSelector from
|
import LocalFileSelector from
|
||||||
'~/src/components/FileSelector/LocalFileSelector'
|
'~/src/components/FileSelector/LocalFileSelector'
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { h } from 'preact'
|
||||||
import { useState, useEffect } from 'preact/hooks'
|
import { useState, useEffect } from 'preact/hooks'
|
||||||
|
|
||||||
import UrlPlayer from '~/src/components/Player/UrlPlayer'
|
import UrlPlayer from '~/src/components/Player/UrlPlayer'
|
||||||
import MenuOverlay from '~/src/components/Player/MenuOverlay'
|
import MenuOverlay from '~/src/components/Player/MenuOverlay/MenuOverlay'
|
||||||
|
|
||||||
import './PlayerView.css'
|
import './PlayerView.css'
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue