diff --git a/web/css/main.css b/web/css/main.css index 10d98e2e..270b7ce8 100644 --- a/web/css/main.css +++ b/web/css/main.css @@ -40,7 +40,7 @@ body { background-image: url('/img/ui/bg.jpg'); background-repeat: no-repeat; background-size: 100% 100%; - border-radius: 22px; + border-radius: 24px; user-select: none; } diff --git a/web/css/ui.css b/web/css/ui.css index 41f70879..1a2d05fd 100644 --- a/web/css/ui.css +++ b/web/css/ui.css @@ -3,67 +3,6 @@ display: none !important; } -.modal-window { - position: fixed; - - top: 0; - right: 0; - bottom: 0; - left: 0; - - background-color: rgba(0, 0, 0, 0.7); - - z-index: 9999; - visibility: hidden; - opacity: 0; - pointer-events: none; - - -webkit-transition: all 0.2s; - transition: all 0.2s; -} - -.modal-visible { - visibility: visible; - opacity: 1; - pointer-events: auto; -} - - -.modal-window > div { - width: 42vw; - position: absolute; - top: 50%; - left: 50%; - -webkit-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); - padding: 2em; - background: #ffffff; -} - -.modal-window header { - font-weight: bold; -} - -.modal-window h1 { - font-size: 150%; - margin: 0 0 15px; -} - -.semi-button { - cursor: pointer; -} - - -#app-settings { - font-family: monospace; -} - -#settings-data { - overflow-y: auto; - height: 50vh; - padding: 1em 0; -} - .container { display: grid; -webkit-box-pack: center; @@ -73,10 +12,6 @@ height: 100vh; } -.modal-window div:not(:last-of-type) { - margin-bottom: 15px; -} - .btn2 { font-size: 80%; padding: .2em .4em; @@ -88,6 +23,10 @@ height: 1rem; } +.settings { + padding: 0 1em 1em 1em; +} + .settings__controls { color: #aaa; font-size: 80%; @@ -104,9 +43,23 @@ grid-template-rows: auto; } -.settings__option-name { +.settings__option-title { background-color: beige; - padding: 1em; + margin-top: .5em; + padding: .5em; +} + +.settings__option-name { +} + +.settings__option-desc { + font-size: 61%; + color: #444; + font-family: monospace; +} + +.settings__option-value { + padding: .5em; } .restart-needed-asterisk:after { @@ -114,13 +67,29 @@ color: red; } -.settings__option-value { +.restart-needed-asterisk-b:before { + content: '*'; + color: red; +} +.settings__option-value select, .settings__option-value input { + font-family: '6809', monospace; + width: 6em; + font-size: 90%; +} + +.settings__option-value option { + font-size: 150%; +} + +.settings__info { + font-size: 80%; } .keyboard-bindings .settings__option-value { display: grid; grid-template-columns: 25% 25% auto auto; + row-gap: 5px; } .binding-element { @@ -129,19 +98,17 @@ align-items: center; } -/* Server list styling */ -#servers { - background-color: white; - font-size: 12px; - +.binding-element button { font-family: '6809', monospace; - - z-index: 1; - position: relative; - - cursor: default; + min-width: 6em; } +.binding-element div { + font-size: 80%; +} + +/* Server list styling */ + .server-list div { display: grid; grid-template-columns: .2fr 1.2fr 1fr .5fr .2fr; @@ -169,6 +136,16 @@ display: flex; flex-grow: 1; flex-direction: column; + + background-color: white; + font-size: 12px; + + font-family: '6809', monospace; + + z-index: 1; + position: relative; + + cursor: default; } .panel__header { @@ -209,7 +186,7 @@ background-color: #ededed; padding: 2px 4px; - width: 0.7rem; + min-width: 0.7rem; text-align: center; } @@ -224,6 +201,10 @@ font-weight: bold; } +.panel__button_separator { + width: .5em; +} + .app-button { position: absolute; diff --git a/web/index.html b/web/index.html index 0c40aba5..2953032d 100644 --- a/web/index.html +++ b/web/index.html @@ -15,8 +15,8 @@ - - + + Cloud Retro @@ -46,6 +46,7 @@
+
Arrows (move), ZXCVAS;'./ (game ABXYL1-L3R1-R3), 1/2 (1st/2nd player), Shift/Enter/K/L (select/start/save/load), F (fullscreen), share (copy the link to the clipboard) @@ -94,21 +95,6 @@
{{end}}
-
69ff8ae @@ -116,7 +102,7 @@
- + diff --git a/web/js/gui/gui.js b/web/js/gui/gui.js index be5b603f..239affde 100644 --- a/web/js/gui/gui.js +++ b/web/js/gui/gui.js @@ -26,8 +26,7 @@ const gui = (() => { return el; } - const select = (key = '', callback = function () { - }, values = {values: [], labels: []}, current = '') => { + const select = (key = '', callback = () => ({}), values = {values: [], labels: []}, current = '') => { const el = _create(); const select = _create('select'); select.onchange = event => { @@ -45,6 +44,7 @@ const gui = (() => { const panel = (root, title = '', cc = '', content, buttons = [], onToggle) => { const state = { + br: null, shown: false, loading: false, title: title, @@ -70,6 +70,10 @@ const gui = (() => { el.classList.add('panel__header__controls'); buttons.forEach((b => el.append(_create('span', (el) => { + if (Object.keys(b).length === 0) { + el.classList.add('panel__button_separator'); + return + } el.classList.add('panel__button'); if (b.cl) b.cl.forEach(class_ => el.classList.add(class_)); if (b.title) el.title = b.title; @@ -99,17 +103,19 @@ const gui = (() => { function toggle(show) { state.shown = show; - if (onToggle) { - onToggle(state.shown, _root) - } - if (state.shown) { - gui.show(_root); - } else { - gui.hide(_root); - } + + // hack not transparent jpeg corners :_; + show ? _root.parentElement.style.borderRadius = '0px' : + state.br ? _root.parentElement.style.borderRadius = state.br : + state.br = window.getComputedStyle(_root.parentElement).borderRadius + + onToggle && onToggle(state.shown, _root) + + state.shown ? gui.show(_root) : gui.hide(_root) } return { + contentEl: _content, isHidden: () => !state.shown, setContent, setLoad, diff --git a/web/js/settings/settings.js b/web/js/settings/settings.js index 0e67fdd7..01da6f1c 100644 --- a/web/js/settings/settings.js +++ b/web/js/settings/settings.js @@ -15,7 +15,7 @@ */ const settings = (() => { // internal structure version - const revision = 1.4; + const revision = 1.5; // default settings // keep them for revert to defaults option @@ -46,17 +46,7 @@ const settings = (() => { const exportFileName = `cloud-game.settings.v${revision}.txt`; - // ui references - const ui = document.getElementById('app-settings'), - closeEl = document.getElementById('settings__controls__close'), - loadEl = document.getElementById('settings__controls__load'), - saveEl = document.getElementById('settings__controls__save'), - resetEl = document.getElementById('settings__controls__reset'); - - this._renderrer = this._renderrer || { - render: () => { - } - }; + let _renderer = {render: () => ({})}; const getStore = () => store.settings; @@ -64,8 +54,7 @@ const settings = (() => { * The NullObject provider if everything else fails. */ const voidProvider = (store_ = {settings: {}}) => { - const nil = () => { - } + const nil = () => ({}) return { get: key => store_.settings[key], @@ -107,7 +96,7 @@ const settings = (() => { const get = key => JSON.parse(localStorage.getItem(key)); - const set = (key, value) => save(); + const set = () => save(); const remove = () => save(); @@ -161,7 +150,6 @@ const settings = (() => { document.body.appendChild(el); el.click(); document.body.removeChild(el); - el = undefined; } const init = () => { @@ -256,13 +244,40 @@ const settings = (() => { provider.remove(key, subKey); } - const _render = () => settings._renderrer.render() + const panel = gui.panel(document.getElementById('settings'), '> OPTIONS', 'settings', null, [ + {caption: 'Export', handler: () => _export(), title: 'Save',}, + {caption: 'Import', handler: () => _fileReader.read(onFileLoad), title: 'Load',}, + { + caption: 'Reset', + handler: () => { + if (window.confirm("Are you sure want to reset your settings?")) { + _reset(); + event.pub(SETTINGS_CHANGED); + } + }, + title: 'Reset', + }, + {} + ], + (state) => { + if (state) return; - /** - * Settings modal window toggle handler. - * @returns {boolean} True in case if it's opened. - */ - const toggle = () => ui.classList.toggle('modal-visible') && !_render(); + event.pub(SETTINGS_CLOSED); + // to make sure it's disabled, but it's a tad verbose + event.pub(KEYBOARD_TOGGLE_FILTER_MODE, {mode: true}); + }) + + panel.toggle(false); + + const _render = () => { + _renderer.data = panel.contentEl; + _renderer.render() + } + + const toggle = () => { + panel.toggle(true); + _render() + } function _getType(value) { if (value === undefined) return option.undefined @@ -273,15 +288,8 @@ const settings = (() => { else return option.undefined; } - /** - * File reader submodule (FileReader API). - * - * @type {{read: read}} Tries to read a file. - * @private - */ const _fileReader = (() => { - let callback_ = () => { - } + let callback_ = () => ({}) const el = document.createElement('input'); const reader = new FileReader(); @@ -309,21 +317,6 @@ const settings = (() => { event.sub(SETTINGS_CHANGED, _render); - // internal init section - closeEl.addEventListener('click', () => { - event.pub(SETTINGS_CLOSED); - // to make sure it's disabled, but it's a tad verbose - event.pub(KEYBOARD_TOGGLE_FILTER_MODE, {mode: true}); - }); - saveEl.addEventListener('click', () => _export()); - loadEl.addEventListener('click', () => _fileReader.read(onFileLoad)); - resetEl.addEventListener('click', () => { - if (window.confirm("Are you sure want to reset your settings?")) { - _reset(); - event.pub(SETTINGS_CHANGED); - } - }); - return { init, loadOr, @@ -335,24 +328,31 @@ const settings = (() => { export: _export, ui: { toggle, + }, + set renderer(fn) { + _renderer = fn; } } })(document, event, JSON, localStorage, log, window); // hardcoded ui stuff -settings._renderrer = (() => { - // options to ignore (i.e. ignored = {'_version': 1}) - const ignored = {}; +settings.renderer = (() => { + // don't show these options (i.e. ignored = {'_version': 1}) + const ignored = {'_version': 1}; // the main display data holder element - const data = document.getElementById('settings-data'); + let data = null; - let sx, sy = 0; - - data.addEventListener("scroll", event => { - sx = data.scrollTop; - sy = data.scrollLeft; - }, {passive: true}); + const scrollState = ((sx = 0, sy = 0, el) => ({ + track(_el) { + el = _el + el.addEventListener("scroll", () => ({scrollTop: sx, scrollLeft: sy} = el), {passive: true}) + }, + restore() { + el.scrollTop = sx + el.scrollLeft = sy + } + }))() // a fast way to clear data holder. const clearData = () => { @@ -363,9 +363,11 @@ settings._renderrer = (() => { const wrapperEl = document.createElement('div'); wrapperEl.classList.add('settings__option'); + const titleEl = document.createElement('div'); + titleEl.classList.add('settings__option-title'); + wrapperEl.append(titleEl); + const nameEl = document.createElement('div'); - nameEl.classList.add('settings__option-name'); - wrapperEl.append(nameEl); const valueEl = document.createElement('div'); valueEl.classList.add('settings__option-value'); @@ -373,15 +375,23 @@ settings._renderrer = (() => { return { withName: function (name = '') { + if (name === '') return this; + nameEl.classList.add('settings__option-name'); nameEl.textContent = name; + titleEl.append(nameEl); return this; }, withClass: function (name = '') { wrapperEl.classList.add(name); return this; }, - readOnly: function () { - // reserved + withDescription(text = '') { + if (text === '') return this; + const descEl = document.createElement('div'); + descEl.classList.add('settings__option-desc'); + descEl.textContent = text; + titleEl.append(descEl); + return this; }, restartNeeded: function () { nameEl.classList.add('restart-needed-asterisk'); @@ -408,11 +418,7 @@ settings._renderrer = (() => { } } - // !to check leaks - if (handler) { - handler.unsub(); - handler = undefined; - } + handler?.unsub(); event.pub(KEYBOARD_TOGGLE_FILTER_MODE); event.pub(SETTINGS_CHANGED); @@ -433,12 +439,10 @@ settings._renderrer = (() => { * * @param key The name (id) of an option. * @param newValue A new value to set. - * @param oldValue An old value to use somehow if needed. */ - const onChange = (key, newValue, oldValue) => { + const onChange = (key, newValue) => { settings.set(key, newValue); - data.scrollTop = sx; - data.scrollLeft = sy; + scrollState.restore(data); } const onKeyBindingChange = (key, oldValue) => { @@ -457,7 +461,7 @@ settings._renderrer = (() => { const value = _settings[k]; switch (k) { case opts._VERSION: - _option(data).withName('Format version').add(value).build(); + _option(data).withName('Options format version').add(value).build(); break; case opts.LOG_LEVEL: _option(data).withName('Log level') @@ -474,8 +478,9 @@ settings._renderrer = (() => { .build(); break; case opts.MIRROR_SCREEN: - _option(data).withName('Video mirroring without smooth') - .add(gui.select(k, onChange, {values: ['mirror']}, value)) + _option(data).withName('Video mirroring') + .add(gui.select(k, onChange, {values: ['mirror'], labels: []}, value)) + .withDescription('Disables video image smoothing by rendering the video on a canvas (much more demanding on the CPU/GPU)') .build(); break; case opts.VOLUME: @@ -488,9 +493,25 @@ settings._renderrer = (() => { _option(data).withName(k).add(value).build(); } } + + data.append( + gui.create('br'), + gui.create('div', (el) => { + el.classList.add('settings__info', 'restart-needed-asterisk-b'); + el.innerText = ' -- applied after page reload' + }), + gui.create('div', (el) => { + el.classList.add('settings__info'); + el.innerText = `Options format version: ${_settings?._version}`; + }) + ); } return { render, + set data(el) { + data = el; + scrollState.track(el) + } } -})(document, log, opts, settings); +})(document, gui, log, opts, settings); diff --git a/web/js/workerManager.js b/web/js/workerManager.js index 56f463e4..3d119b2a 100644 --- a/web/js/workerManager.js +++ b/web/js/workerManager.js @@ -7,19 +7,13 @@ const workerManager = (() => { _class = 'server-list', trigger = document.getElementById('w'), panel = gui.panel(document.getElementById(id), 'WORKERS', 'server-list', null, [ - { - caption: '⟳', - cl: ['bold'], - handler: utils.debounce(handleReload, 1000), - title: 'Reload server data', - } - ], - // hack not transparent jpeg corners :_; - ((br) => (state, el) => { - state ? el.parentElement.style.borderRadius = '0px' : - br ? el.parentElement.style.borderRadius = br : - br = window.getComputedStyle(el.parentElement).borderRadius - })()), + { + caption: '⟳', + cl: ['bold'], + handler: utils.debounce(handleReload, 1000), + title: 'Reload server data', + } + ]), index = ((i = 1) => ({v: () => i++, r: () => i = 1}))(), // caption -- the field caption // renderer -- an arbitrary DOM output for the field @@ -117,7 +111,7 @@ const workerManager = (() => { if (server.room) { return gui.create('a', (el) => { el.innerText = state; - el.href = "/?id="+server.room; + el.href = "/?id=" + server.room; }) } return state