mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-08-01 22:34:02 +00:00
Update options UI
This commit is contained in:
parent
f8392ab0be
commit
4fc53e7220
6 changed files with 182 additions and 194 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
139
web/css/ui.css
139
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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue