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}}
-
-
-
- ↑
- ↓
- ⟲
- X
-
-
Options
-
-
- * -- applied after application restart
-
-
-
-
+
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