Browse Source

Begin sharing code between web browsers and QuickJS

This begins splitting the code into logical chunks that can run on any
JS runtime from the code that is specific to an OS or a browser.

QuickJS is required to  run `qjs ./bin/build.js`. Nothing is output from
this file yet, as I still need to figure out how to best work with the
file system.

The config has also been simplified, as well as the UI. The three basic
concepts are now: Layout, Palette, Skin. Where does everything go, which
colors are we working with, how is the theme applied.
main
Tyler Childs 2 years ago
parent
commit
d396c1b23c
  1. 44
      bin/build.js
  2. 68
      browser.js
  3. 22
      config.json
  4. 30
      engine/boilerplates/default.html
  5. 29
      engine/compose.js
  6. 12
      engine/layouts/landing.css
  7. 6
      engine/layouts/sidebar.css
  8. 153
      index.html
  9. 3
      lib/decimal.js
  10. 3
      lib/hex.js
  11. 33
      mods.json

44
bin/build.js

@ -0,0 +1,44 @@
// This build script is responsible for three things
//
// 1. Seed the Mod Permutations configs
// 2. Index each config as a cheatcode
// 3. Run each config through the engine
import { loadFile } from 'std';
import { open } from 'os';
import compose from '../engine/compose.js';
import hex from '../lib/hex.js';
import decimal from '../lib/decimal.js';
const [
layouts,
palettes,
skins
] = JSON.parse(
loadFile('mods.json')
);
const config = JSON.parse(
loadFile('config.json')
);
const mods = {
layout: layouts,
palette: palettes,
skin: skins
};
mods.layout.forEach((layout, l) => {
mods.palette.forEach((palette, p) => {
mods.skin.forEach((skin, s) => {
const cheat = [l,p,s].map(hex).join();
const cheatConfig = {
...config,
layout,
palette,
skin
};
console.log(cheat, JSON.stringify(cheatConfig));
});
});
});

68
browser.js

@ -0,0 +1,68 @@
import compose from './engine/compose.js';
import hex from './lib/hex.js';
import decimal from './lib/decimal.js';
(async function() {
let config = {};
const [
layouts,
palettes,
skins
] = JSON.parse(
await download('/mods.json')
);
const mods = {
layout: layouts,
palette: palettes,
skin: skins
};
init();
async function download(url) {
return await fetch(url)
.then(r => r.status < 300 ? r.text() : '' )
.catch(console.error);
}
async function init() {
config = JSON.parse(await download('/config.json'));
const inputs = [...document.querySelectorAll('select, input')];
inputs.map(el => {
mods[el.id].forEach((option, index) => {
const node = document.createElement('option');
node.value = hex(index);
node.innerText = option.label;
el.appendChild(node);
});
el.addEventListener('change', updateConfig)
el.addEventListener('change', render)
});
render();
}
function updateConfig(event) {
const index = decimal(event.target.value);
const { value } = mods[event.target.id][index];
config[event.target.id] = value;
}
async function render() {
const blob = await Promise.all([
download(config.boilerplate),
download(config.template),
download(config.base),
download(config.layout),
download(config.palette),
download(config.skin)
]).then(compose);
document.getElementById('customizer-demo').href = URL.createObjectURL(blob);
}
})();

22
config.json

@ -1,28 +1,10 @@
{
"title": "Hello World",
"header": "<h1>Untitled</h1>",
"footer": "This generated code is licensed under the MIT License. That basically means you're free to do whatever you'd like with it, enjoy!",
"navigation": [
{
"title": "About",
"href": "#about"
},
{
"title": "Features",
"href": "#features"
},
{
"title": "Contact",
"href": "#contact"
}
],
"boilerplate": "/engine/boilerplates/default.html",
"template": "/engine/templates/default.html",
"base": "/engine/bases/default.css",
"palette": "/engine/palettes/default.css",
"layout": "/engine/layouts/default.css",
"palette": "/engine/palettes/default.css",
"skin": "/engine/skins/default.css",
"template": "/engine/templates/default.html",
"embed": true,
"styles": [],
"scripts": []
}

30
engine/boilerplates/default.html

@ -3,7 +3,9 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title><!--title--></title>
<title>
Welcome!
</title>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1">
@ -13,15 +15,30 @@
</head>
<body>
<header id="header">
<!--header-->
<h1>My Webpage</h1>
</header>
<aside id="aside">
<!--aside-->
</aside>
<nav id="nav">
<a href="#services">
Services
</a>
<a href="#about">
About
</a>
<a href="#contact">
Contact
</a>
</nav>
<main id="main">
<!--main-->
<a name="services"></a>
<h2>Services</h2>
<a name="about"></a>
<h2>About</h2>
<a name="contact"></a>
<h2>Contact</h2>
</main>
<footer id="footer">
@ -31,4 +48,3 @@
<!--scripts-->
</body>
</html>

29
engine/compose.js

@ -0,0 +1,29 @@
export default function compose(
[
boilerplate,
template,
base,
layout,
palette,
skin
]
) {
const slugs = {
styles: ((a, b, c, d, styles) => {
const external = '';
return `<style>${a}${b}${c}${d}</style>${external}`;
})(base, palette, layout, skin),
main: template,
scripts: ((s) => {
return '';
})(),
};
const html = Object.keys(slugs).reduce((page, key) => {
return page.replace(`<!--${key}-->`, slugs[key]);
}, boilerplate);
return new Blob([html], { type: 'text/html' });
}

12
engine/layouts/landing.css

@ -3,22 +3,16 @@ body{
margin: 0 auto;
max-width: 65rem;
}
.header {
#header {
text-align: center;
}
.navigation {
text-align: center;
}
.navigation ul {
#nav {
display: flex;
list-style-type: none;
margin-left: 0;
justify-content: center;
}
.navigation li {
.nav li {
padding: 0 var(--rhythm-half);
}

6
engine/layouts/sidebar.css

@ -2,7 +2,7 @@ body {
display: grid;
grid-template-areas:
"header header"
"aside main"
"nav main"
"footer footer";
grid-template-columns: 10em 1fr;
}
@ -12,8 +12,8 @@ body {
padding: 0 var(--rhythm-half);
}
#aside {
grid-area: aside;
#nav {
grid-area: nav;
padding: var(--rhythm-double) var(--rhythm-half) var(--rhythm);
}

153
index.html

@ -61,59 +61,20 @@
<legend>Customization Options</legend>
<form id="customization-form">
<label for="boilerplate">
Boilerplate
</label>
<select id="boilerplate">
<option selected="selected" value="/engine/boilerplates/default.html">
Default
</option>
</select>
<label for="base">
Base
<label for="layout">
Layout
</label>
<select id="base">
<option selected="selected" value="/engine/bases/default.css">
Default
</option>
</select>
<select id="layout"></select>
<label for="palette">
Palette
</label>
<select id="palette">
<option selected="selected" value="/engine/palettes/default.css">
Default
</option>
</select>
<label for="layout">
Layout
</label>
<select id="layout">
<option selected="selected" value="/engine/layouts/default.css">
Default
</option>
</select>
<select id="palette"></select>
<label for="skin">
Skin
</label>
<select id="skin">
<option selected="selected" value="/engine/skins/default.css">
Default
</option>
</select>
<label for="template">
Template
</label>
<select id="template">
<option selected="selected" value="/engine/templates/default.html">
Default
</option>
</select>
<select id="skin"></select>
</form>
</fieldset>
@ -124,108 +85,6 @@
<footer id="footer">
</footer>
<script>
(function() {
let config = {};
init();
async function download(url) {
return await fetch(url)
.then(r => r.status < 300 ? r.text() : '' )
.catch(console.error);
}
async function init() {
config = JSON.parse(await download('/config.json'));
const options = JSON.parse(await download('/options.json'));
const inputs = [...document.querySelectorAll('select, input')];
inputs.map(el => {
options[el.id].forEach(option => {
const node = document.createElement('option');
node.value = option.value;
node.innerText = option.label;
el.appendChild(node);
});
el.addEventListener('change', updateConfig)
el.addEventListener('change', render)
});
const fields = inputs.filter(el => Object.keys(config).includes(el.id));
render();
}
function updateConfig(el) {
config[el.target.id] = el.target.value;
}
async function render() {
const boilerplate = download(config.boilerplate);
const base = download(config.base);
const palette = download(config.palette);
const layout = download(config.layout);
const skin = download(config.skin);
const template = download(config.template);
const webpage = await Promise.all([
boilerplate,
base,
palette,
layout,
skin,
template
]).then(composeWebpage);
const blob = new Blob([webpage], { type: 'text/html' });
document.getElementById('customizer-demo').href = URL.createObjectURL(blob);
}
function composeWebpage(
[
boilerplate,
base,
palette,
layout,
skin,
template
] = promises
) {
const slugs = {
title: config.title,
styles: ((a, b, c, d, styles) => {
const external = '';
return `<style>${a}${b}${c}${d}</style>${external}`;
})(base, palette, layout, skin, config.styles),
header: config.header,
aside: ((items) => {
const list = items.reduce((nav, item) => {
return `${nav}<a href=${item.href}>${item.title}</a>`;
}, '');
return `<nav>${list}</nav>`
})(config.navigation),
main: template,
footer: config.footer,
scripts: ((scripts) => {
return '';
})(config.scripts),
};
return Object.keys(slugs).reduce((page, key) => {
return page.replace(`<!--${key}-->`, slugs[key]);
}, boilerplate);
}
})();
</script>
<script type="module" src="browser.js"></script>
</body>
</html>

3
lib/decimal.js

@ -0,0 +1,3 @@
export default function decimal(string) {
return parseInt(string, 16);
}

3
lib/hex.js

@ -0,0 +1,3 @@
export default function hex(integer) {
return integer.toString(16).padStart(2, '0');
}

33
mods.json

@ -0,0 +1,33 @@
[
[
{
"type": "layout",
"value": "/engine/layouts/default.css",
"label": "Default"
},
{
"type": "layout",
"value": "/engine/layouts/landing.css",
"label": "Landing"
},
{
"type": "layout",
"value": "/engine/layouts/sidebar.css",
"label": "Sidebar"
}
],
[
{
"type": "palette",
"value": "/engine/layouts/default.css",
"label": "Default"
}
],
[
{
"type": "skin",
"value": "/engine/layouts/default.css",
"label": "Default"
}
]
]
Loading…
Cancel
Save