Update options UI

This commit is contained in:
Sergey Stepanov 2024-03-11 15:24:48 +03:00 committed by sergystepanov
parent f8392ab0be
commit 4fc53e7220
6 changed files with 182 additions and 194 deletions

View file

@ -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;
}

View file

@ -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;

View file

@ -15,8 +15,8 @@
<link rel="icon" href="data:,">
<link href="css/main.css?5" rel="stylesheet">
<link href="css/ui.css?v=1" rel="stylesheet">
<link href="css/main.css?v=6" rel="stylesheet">
<link href="css/ui.css?v=2" rel="stylesheet">
<title>Cloud Retro</title>
</head>
<body>
@ -46,6 +46,7 @@
</div>
<div id="servers"></div>
<div id="settings"></div>
<div id="guide-txt">
<b>Arrows</b> (move), <b>ZXCVAS;'./</b> (game ABXYL1-L3R1-R3), <b>1/2</b> (1st/2nd player), <b>Shift/Enter/K/L</b> (select/start/save/load), <b>F</b> (fullscreen), <b>share</b> (copy the link to the clipboard)
@ -94,21 +95,6 @@
<div id="btn-rec" class="btn" value="rec"></div>
{{end}}
</div>
<div id="app-settings" class="modal-window">
<div>
<div class="settings__controls">
<span title="Save" id="settings__controls__save" class="semi-button"></span>
<span title="Load" id="settings__controls__load" class="semi-button"></span>
<span title="Reset" id="settings__controls__reset" class="semi-button"></span>
<span title="Close" id="settings__controls__close" class="semi-button">X</span>
</div>
<h1>Options</h1>
<div id="settings-data"></div>
<div>
* -- applied after application restart
</div>
</div>
</div>
<div class="source">
<span id="v">69ff8ae</span>
<a rel="noopener noreferrer" target="_blank" href="https://github.com/giongto35/cloud-game">
@ -116,7 +102,7 @@
</a>
</div>
<script src="js/gui/gui.js?v=1"></script>
<script src="js/gui/gui.js?v=2"></script>
<script src="js/utils.js?v1"></script>
<script src="js/gui/message.js?v=2"></script>
<script src="js/log.js?v=5"></script>

View file

@ -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,

View file

@ -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);

View file

@ -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