added config editor (closes #19)

This commit is contained in:
Nathan DECHER 2020-04-06 20:16:28 +02:00
parent 65b33afa05
commit 0923ad56dd
7 changed files with 157 additions and 11 deletions

View file

@ -3,6 +3,11 @@
"name": "Input settings" "name": "Input settings"
}, },
"input.buffer": {
"name": "Enable input buffering",
"type": "boolean"
},
"input.touchscreen": { "input.touchscreen": {
"name": "Touchscreen settings" "name": "Touchscreen settings"
}, },
@ -99,11 +104,6 @@
"type": "boolean" "type": "boolean"
}, },
"input.buffer": {
"name": "Enable input buffering",
"type": "boolean"
},
"appearance": { "appearance": {
"name": "Appearance" "name": "Appearance"
}, },

View file

@ -12,9 +12,14 @@
</head> </head>
<body> <body>
<header> <header>
<img src="assets/icon256.png"> <a href="#"><img src="assets/icon256.png"></a>
<h1>Snek</h1> <h1>Snek</h1>
<h2>A "simple" Snake</h2> <h2>A "simple" Snake</h2>
<ul>
<li><a href="#">Menu</a></li>
<li><a href="#settings">Config</a></li>
<li><a href="#help">Help</a></li>
</ul>
</header> </header>
<main> <main>
<nav></nav> <nav></nav>

94
src/js/configEditor.js Normal file
View file

@ -0,0 +1,94 @@
const config=require('config');
const assets=require('assets');
const Popup=require('popup');
let lastCEId=0;
class ConfigEditor extends Popup {
constructor() {
super("Config editor", [], {ok: "OK"});
const metaConfig=assets.get('metaConfig');
this.watchers=[];
for(let key in metaConfig) {
const level=Array.from(key).reduce((a, c) => a+c=='.', 0)+2;
const data=metaConfig[key];
if(!data.type) {
this.addHeading(data.name);
} else {
let span=document.createElement('span');
let id='cfgInput-'+(lastCEId++)+'-'+key.replace(/\./g, '-');
let label=span.appendChild(document.createElement('label'));
label.innerText=data.name;
label.title=key;
let input;
if(data.type=='boolean') {
input=document.createElement('input');
input.type='checkbox';
input.checked=config.getB(key);
input.addEventListener('change', () => config.set(key, input.checked));
} else if(data.type=='choice') {
input=document.createElement('select');
data.bounds.choices.forEach(choice => {
let option=document.createElement('option');
option.value=choice;
option.innerText=choice;
input.appendChild(option);
});
input.value=config.getS(key);
input.addEventListener('change', () => config.set(key, input.value));
} else if(data.type=='number') {
input=document.createElement('input');
input.type='number';
if(data.bounds) {
input.setAttribute('min', data.bounds.min);
input.setAttribute('max', data.bounds.max);
input.setAttribute('step', data.bounds.inc);
}
input.value=config.getN(key);
input.addEventListener('change', () => config.set(key, input.value));
}
input.setAttribute('id', id);
span.appendChild(input);
label.setAttribute('for', id);
this.addContent(span);
if(data.excludes) {
const setEnabled=() =>
input.disabled=
data.excludes
.some(key => config.getB(key))
setEnabled();
data.excludes.forEach(key => {
let c=config.watchB(key, setEnabled);
this.watchers.push([key, c]);
});
}
if(data.parent) {
input.disabled=!config.getB(data.parent);
let c=config.watchB(data.parent, (k, v) => input.disabled=!v);
this.watchers.push([data.parent, c]);
}
}
}
this.large=true;
}
discard() {
this.watchers.forEach(([k, c]) => config.unwatch(k, c));
}
};
return module.exports={
show: async () => {
let editor=new ConfigEditor();
await editor.display();
editor.discard();
}
};

View file

@ -4,6 +4,7 @@
const assets=require('assets'); const assets=require('assets');
const Popup=require('popup'); const Popup=require('popup');
const SnekGame=require('snek'); const SnekGame=require('snek');
const configEditor=require('configEditor');
const input=require('input'); const input=require('input');
const levels=require('levels'); const levels=require('levels');
const config=require('config'); const config=require('config');
@ -154,6 +155,13 @@
stopGame(); stopGame();
}; };
// show config editor
settings=async () => {
stopGame();
await configEditor.show();
location.hash='menu';
};
// display the win popup // display the win popup
handleWin=async snek => { handleWin=async snek => {
// hide the HUD // hide the HUD

View file

@ -1,5 +1,7 @@
const objToDom=obj => { const objToDom=obj => {
if(obj[Popup.EM]) { if(obj instanceof Node) {
return obj;
} else if(obj[Popup.EM]) {
let em=document.createElement('em'); let em=document.createElement('em');
em.appendChild(document.createTextNode(obj[Popup.EM])); em.appendChild(document.createTextNode(obj[Popup.EM]));
return em; return em;
@ -27,10 +29,11 @@ const objToDom=obj => {
} }
class Popup { class Popup {
constructor(title, content=[], buttons={}) { constructor(title, content=[], buttons={}, large=false) {
this.title=title; this.title=title;
this.content=content.map(objToDom); this.content=content.map(objToDom);
this.buttons={...buttons}; this.buttons={...buttons};
this.large=large;
} }
addContent(cnt) { addContent(cnt) {
@ -45,12 +48,18 @@ class Popup {
addStrong(cnt) { addStrong(cnt) {
this.content.push(objToDom({[Popup.STRONG]: cnt})); this.content.push(objToDom({[Popup.STRONG]: cnt}));
} }
addHeading(cnt, level=2) {
let hn=document.createElement('h'+level);
hn.innerText=cnt;
this.content.push(hn);
}
async display(parent=document.body) { async display(parent=document.body) {
let outer=document.createElement('div'); let outer=document.createElement('div');
outer.classList.add('popup'); outer.classList.add('popup');
let popup=outer.appendChild(document.createElement('div')); let popup=outer.appendChild(document.createElement('div'));
popup.classList.add('content'); popup.classList.add('content');
if(this.large) popup.classList.add('large');
let title=popup.appendChild(document.createElement('h1')); let title=popup.appendChild(document.createElement('h1'));
title.innerText=this.title; title.innerText=this.title;

View file

@ -29,15 +29,17 @@
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
box-sizing: border-box; box-sizing: border-box;
max-width: 50vw;
max-height: 50vh;
padding: 2rem; padding: 2rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-size: 1.4rem; font-size: 1.4rem;
&.large {
width: 80vw;
height: 80vh;
}
& > section { & > section {
margin: 1rem; margin: 1rem;
@ -45,6 +47,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
overflow-y: auto;
& > * { & > * {
margin: .5rem; margin: .5rem;
@ -82,6 +85,9 @@
flex: 1; flex: 1;
} }
} }
label {
margin-right: 1ex;
}
button { button {
display: inline; display: inline;

View file

@ -33,6 +33,7 @@ h1, h2, h3, h4, h5, h6 {
a { a {
text-decoration: inherit; text-decoration: inherit;
display: contents;
} }
em { em {
@ -66,6 +67,20 @@ footer img {
height: 4rem; height: 4rem;
} }
header ul {
display: flex;
list-style-type: none;
li {
margin-right: 1ex;
font-size: 2rem;
a {
color: @fg;
}
}
}
header, footer, main { header, footer, main {
padding: 2rem; padding: 2rem;
} }
@ -88,6 +103,15 @@ h1 {
h2 { h2 {
font-size: 2rem; font-size: 2rem;
} }
h3 {
font-size: 1.9rem;
}
h4 {
font-size: 1.8rem;
}
h5 {
font-size: 1.7rem;
}
p { p {
font-size: 1.6rem; font-size: 1.6rem;
} }