affirmations_v2.html
· 4.0 KiB · HTML
Raw
Playground
<!DOCTYPE html>
<html>
<div id="app"></div>
<style>
#app, body {
height: 100%;
}
#edit-affirmations {
display: grid;
grid-template-rows: 80% 20%;
height: 80vh;
}
.affirmation {
width: 100%;
}
.half-half {
display: grid;
grid-template-columns: 50% 50%;
width: 100%;
}
.right-corner {
width: 25%;
justify-self: end;
}
.w-full { width: 100%; }
.grid { display: grid; }
#edit-affirmations textarea {
height: 80%;
}
</style>
<template id="mode-edit-affirmations">
<div id="edit-affirmations">
<textarea class="wide 80-tall" id="affirmation-list"></textarea>
<button id="save-affirmations">Save!</button>
</div>
</template>
<template id="mode-view-affirmations">
<div class="w-full grid">
<button id="go-to-edit-mode" class="right-corner"></button>
</div>
<div id="affirmation-list"></div>
<div class="half-half">
<button id="add-affirmation">another</button>
<button id="clear-affirmations">clear</button>
</div>
</template>
<template id="affirmation"><h3 class="affirmation"></h3></template>
<script src="hex.js"></script>
<script>
hex.arise(function() {
let affirmation_idx = 0;
let app = hex.id("app");
const get_affirmations = () => hex.recall("affirmations") || ["You need to add affirmations!"];
const next_affirmation = () => {
const affirmations = get_affirmations().filter(x => !(/^\s+$/.test(x)));
const ret = affirmations[affirmation_idx];
affirmation_idx = (affirmation_idx + 1) % affirmations.length;
return ret;
}
var affirmHeader = hex.spell("#affirmation", { value: ".affirmation" });
var modeRecite = hex.spell("#mode-view-affirmations", {
toEdit: ["#go-to-edit-mode", {t: "edit", on: {
click() {
hex.become(modeEdit, app, {affirmations: get_affirmations()});
}
} }],
affirmations: ["#affirmation-list", (el, affirmation) => affirmHeader(el, { value: affirmation })],
another: ["#add-affirmation",
{ t: "another",
init() {
this._affirmations = [];
this._next = () => {
this._affirmations.push(next_affirmation());
this.affirmations(this._affirmations)
}
this._next();
},
on: { click() { this._next(); } }
}
],
clear: ["#clear-affirmations", {t: "clear", on: { click() { this._affirmations = []; this.affirmations([]); } } }]
});
var modeEdit = hex.spell("#mode-edit-affirmations", {
affirmations: ["#affirmation-list", {
init(arg) {
this.affirmations = arg.affirmations.join("\n");
this._affirmations = arg.affirmations || get_affirmations();
},
on: { input(e) { this._affirmations = e.target.value.split("\n"); } }
}],
save: ["#save-affirmations",
{ t:"save", on: { click() {
console.log(this);
hex.scrawl("affirmations", this._affirmations);
hex.become(modeRecite, app, {affirmations: get_affirmations()});
}}}
],
});
hex.become(modeRecite, app, {affirmations: get_affirmations()});
});
</script>
</html>
| 1 | <!DOCTYPE html> |
| 2 | <html> |
| 3 | |
| 4 | <div id="app"></div> |
| 5 | <style> |
| 6 | |
| 7 | #app, body { |
| 8 | height: 100%; |
| 9 | } |
| 10 | #edit-affirmations { |
| 11 | display: grid; |
| 12 | grid-template-rows: 80% 20%; |
| 13 | height: 80vh; |
| 14 | } |
| 15 | |
| 16 | .affirmation { |
| 17 | width: 100%; |
| 18 | } |
| 19 | .half-half { |
| 20 | display: grid; |
| 21 | grid-template-columns: 50% 50%; |
| 22 | width: 100%; |
| 23 | } |
| 24 | .right-corner { |
| 25 | width: 25%; |
| 26 | justify-self: end; |
| 27 | } |
| 28 | .w-full { width: 100%; } |
| 29 | .grid { display: grid; } |
| 30 | |
| 31 | #edit-affirmations textarea { |
| 32 | height: 80%; |
| 33 | } |
| 34 | |
| 35 | </style> |
| 36 | |
| 37 | <template id="mode-edit-affirmations"> |
| 38 | <div id="edit-affirmations"> |
| 39 | <textarea class="wide 80-tall" id="affirmation-list"></textarea> |
| 40 | <button id="save-affirmations">Save!</button> |
| 41 | </div> |
| 42 | </template> |
| 43 | <template id="mode-view-affirmations"> |
| 44 | <div class="w-full grid"> |
| 45 | <button id="go-to-edit-mode" class="right-corner"></button> |
| 46 | </div> |
| 47 | <div id="affirmation-list"></div> |
| 48 | <div class="half-half"> |
| 49 | <button id="add-affirmation">another</button> |
| 50 | <button id="clear-affirmations">clear</button> |
| 51 | </div> |
| 52 | </template> |
| 53 | <template id="affirmation"><h3 class="affirmation"></h3></template> |
| 54 | |
| 55 | <script src="hex.js"></script> |
| 56 | <script> |
| 57 | hex.arise(function() { |
| 58 | let affirmation_idx = 0; |
| 59 | |
| 60 | let app = hex.id("app"); |
| 61 | const get_affirmations = () => hex.recall("affirmations") || ["You need to add affirmations!"]; |
| 62 | const next_affirmation = () => { |
| 63 | const affirmations = get_affirmations().filter(x => !(/^\s+$/.test(x))); |
| 64 | const ret = affirmations[affirmation_idx]; |
| 65 | affirmation_idx = (affirmation_idx + 1) % affirmations.length; |
| 66 | return ret; |
| 67 | } |
| 68 | |
| 69 | var affirmHeader = hex.spell("#affirmation", { value: ".affirmation" }); |
| 70 | |
| 71 | var modeRecite = hex.spell("#mode-view-affirmations", { |
| 72 | toEdit: ["#go-to-edit-mode", {t: "edit", on: { |
| 73 | click() { |
| 74 | hex.become(modeEdit, app, {affirmations: get_affirmations()}); |
| 75 | } |
| 76 | } }], |
| 77 | affirmations: ["#affirmation-list", (el, affirmation) => affirmHeader(el, { value: affirmation })], |
| 78 | another: ["#add-affirmation", |
| 79 | { t: "another", |
| 80 | init() { |
| 81 | this._affirmations = []; |
| 82 | this._next = () => { |
| 83 | this._affirmations.push(next_affirmation()); |
| 84 | this.affirmations(this._affirmations) |
| 85 | } |
| 86 | this._next(); |
| 87 | }, |
| 88 | on: { click() { this._next(); } } |
| 89 | } |
| 90 | ], |
| 91 | clear: ["#clear-affirmations", {t: "clear", on: { click() { this._affirmations = []; this.affirmations([]); } } }] |
| 92 | }); |
| 93 | |
| 94 | var modeEdit = hex.spell("#mode-edit-affirmations", { |
| 95 | affirmations: ["#affirmation-list", { |
| 96 | init(arg) { |
| 97 | this.affirmations = arg.affirmations.join("\n"); |
| 98 | this._affirmations = arg.affirmations || get_affirmations(); |
| 99 | }, |
| 100 | on: { input(e) { this._affirmations = e.target.value.split("\n"); } } |
| 101 | }], |
| 102 | |
| 103 | save: ["#save-affirmations", |
| 104 | { t:"save", on: { click() { |
| 105 | console.log(this); |
| 106 | hex.scrawl("affirmations", this._affirmations); |
| 107 | hex.become(modeRecite, app, {affirmations: get_affirmations()}); |
| 108 | }}} |
| 109 | ], |
| 110 | }); |
| 111 | |
| 112 | hex.become(modeRecite, app, {affirmations: get_affirmations()}); |
| 113 | }); |
| 114 | </script> |
| 115 | </html> |
| 116 |
contrast-checker.html
· 5.6 KiB · HTML
Raw
Playground
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Discord Color Role Contrast Checker</title>
</head>
<style>
body {
margin-left: auto;
margin-right: auto;
max-width: 600px;
text-align: center;
color: white;
background: #000;
}
a:visited {
color: yellow;
}
.two-grid {
display: grid;
grid-template-columns: 1fr 1fr;
width: 100%;
color: var(--test-color, #476fbf);
}
.two-grid div {
padding-top: 2rem;
padding-bottom: 2rem;
}
.bg-white { font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; font-weight: bold; background-color: white; }
.bg-discord { font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; font-weight: bold; background-color: #36393E; }
</style>
<body class="">
<h2>Discord Role Color Contrast Checker</h2>
<div id="app"></div>
<template id="root">
<input type="color" value="#476fbf" id="color-picker" />
<p>Contrast of <span id="color-1"/>
<div class="two-grid">
<div class="bg-white">
<p>vs. white: <span id="white-contrast"></span>:1</p>
</div>
<div class="bg-discord">
<p>vs discord dark: <span id="discord-contrast"></span>:1</p>
</div>
</div>
<button id="btn-share">Copy Link</button>
<a id="a-share" href="/">(link if button doesn't work)</a>
</p>
</template>
<script src="hex.js"></script>
<script>
hex.arise(function() {
function relativeLuminanceW3C(R8bit, G8bit, B8bit) {
const RsRGB = R8bit/255;
const GsRGB = G8bit/255;
const BsRGB = B8bit/255;
const R = (RsRGB <= 0.03928) ? RsRGB/12.92 : Math.pow((RsRGB+0.055)/1.055, 2.4);
const G = (GsRGB <= 0.03928) ? GsRGB/12.92 : Math.pow((GsRGB+0.055)/1.055, 2.4);
const B = (BsRGB <= 0.03928) ? BsRGB/12.92 : Math.pow((BsRGB+0.055)/1.055, 2.4);
// For the sRGB colorspace, the relative luminance of a color is defined as:
const L = 0.2126 * R + 0.7152 * G + 0.0722 * B;
return L;
}
function hexToRGB8(color) {
const r = Number.parseInt(color.slice(1, 3), 16);
const g = Number.parseInt(color.slice(3, 5), 16);
const b = Number.parseInt(color.slice(5, 8), 16)
return {r,g,b}
}
function luminanceOfHex(color) {
const {r,g,b} = hexToRGB8(color);
return relativeLuminanceW3C(r,g,b);
}
const white_lum = luminanceOfHex("#FFFFFF");
const discord_lum = luminanceOfHex("#36393E");
const root = hex.spell("#root", {
init(arg) {
this.setColor(arg.color || "#4764bf");
this.$doc = document.documentElement;
},
setColor(color) {
const color_lum = luminanceOfHex(color);
this.$doc.style.setProperty('--test-color', color);
const white_contrast = (Math.max(white_lum, color_lum)+0.05) / (Math.min(white_lum, color_lum)+0.05);
const discord_contrast = (Math.max(discord_lum, color_lum)+0.05) / (Math.min(discord_lum, color_lum)+0.05);
this.colorName = color;
this.whiteContrast = white_contrast.toFixed(1);
this.discordContrast = discord_contrast.toFixed(1);
const link = `https://contrast.junglecoder.com/${curr_color}`
this.$toShare.href = link;
}
picker: ["#color-picker", { on: {
change(e) {
this.setColor(e.target.value);
}
}}],
colorName: ["#color-1"],
whiteContrast: ["#white-contrast"],
darkContrast: ["#discord-contrast"],
toShare: ["#a-share"],
shareBtn: ["#btn-share", {
t: 'Copy Link',
on: {
async click() {
const link = `https://contrast.junglecoder.com/${this.colorName}`
try {
await navigator.clipboard.writeText(link)
} finally {
this.toShare = "Manually copy link from here";
}
}
}
}]
});
const urlParams = new URLSearchParams(window.location.search);
let firstColor = window.loation.hash || urlParams.get('c') || "#4764bf";
if (firstColor[0] !== "#") {
firstColor = "#" + firstColor;
}
console.log('FIRST COLOR', firstColor);
hex.become(root, hex.id('app'), { color: firstColor });
});
</script>
</body>
</html>
| 1 | <!DOCTYPE html> |
| 2 | <html> |
| 3 | <head> |
| 4 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 5 | <title>Discord Color Role Contrast Checker</title> |
| 6 | </head> |
| 7 | <style> |
| 8 | body { |
| 9 | margin-left: auto; |
| 10 | margin-right: auto; |
| 11 | max-width: 600px; |
| 12 | text-align: center; |
| 13 | color: white; |
| 14 | background: #000; |
| 15 | } |
| 16 | |
| 17 | a:visited { |
| 18 | color: yellow; |
| 19 | } |
| 20 | |
| 21 | .two-grid { |
| 22 | display: grid; |
| 23 | grid-template-columns: 1fr 1fr; |
| 24 | width: 100%; |
| 25 | color: var(--test-color, #476fbf); |
| 26 | } |
| 27 | |
| 28 | .two-grid div { |
| 29 | padding-top: 2rem; |
| 30 | padding-bottom: 2rem; |
| 31 | } |
| 32 | |
| 33 | .bg-white { font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; font-weight: bold; background-color: white; } |
| 34 | .bg-discord { font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; font-weight: bold; background-color: #36393E; } |
| 35 | |
| 36 | </style> |
| 37 | <body class=""> |
| 38 | <h2>Discord Role Color Contrast Checker</h2> |
| 39 | <div id="app"></div> |
| 40 | |
| 41 | <template id="root"> |
| 42 | <input type="color" value="#476fbf" id="color-picker" /> |
| 43 | <p>Contrast of <span id="color-1"/> |
| 44 | <div class="two-grid"> |
| 45 | <div class="bg-white"> |
| 46 | <p>vs. white: <span id="white-contrast"></span>:1</p> |
| 47 | </div> |
| 48 | <div class="bg-discord"> |
| 49 | <p>vs discord dark: <span id="discord-contrast"></span>:1</p> |
| 50 | </div> |
| 51 | </div> |
| 52 | <button id="btn-share">Copy Link</button> |
| 53 | <a id="a-share" href="/">(link if button doesn't work)</a> |
| 54 | </p> |
| 55 | </template> |
| 56 | |
| 57 | <script src="hex.js"></script> |
| 58 | <script> |
| 59 | hex.arise(function() { |
| 60 | function relativeLuminanceW3C(R8bit, G8bit, B8bit) { |
| 61 | |
| 62 | const RsRGB = R8bit/255; |
| 63 | const GsRGB = G8bit/255; |
| 64 | const BsRGB = B8bit/255; |
| 65 | |
| 66 | const R = (RsRGB <= 0.03928) ? RsRGB/12.92 : Math.pow((RsRGB+0.055)/1.055, 2.4); |
| 67 | const G = (GsRGB <= 0.03928) ? GsRGB/12.92 : Math.pow((GsRGB+0.055)/1.055, 2.4); |
| 68 | const B = (BsRGB <= 0.03928) ? BsRGB/12.92 : Math.pow((BsRGB+0.055)/1.055, 2.4); |
| 69 | |
| 70 | // For the sRGB colorspace, the relative luminance of a color is defined as: |
| 71 | const L = 0.2126 * R + 0.7152 * G + 0.0722 * B; |
| 72 | return L; |
| 73 | } |
| 74 | |
| 75 | function hexToRGB8(color) { |
| 76 | const r = Number.parseInt(color.slice(1, 3), 16); |
| 77 | const g = Number.parseInt(color.slice(3, 5), 16); |
| 78 | const b = Number.parseInt(color.slice(5, 8), 16) |
| 79 | |
| 80 | return {r,g,b} |
| 81 | } |
| 82 | |
| 83 | function luminanceOfHex(color) { |
| 84 | const {r,g,b} = hexToRGB8(color); |
| 85 | return relativeLuminanceW3C(r,g,b); |
| 86 | } |
| 87 | |
| 88 | const white_lum = luminanceOfHex("#FFFFFF"); |
| 89 | const discord_lum = luminanceOfHex("#36393E"); |
| 90 | |
| 91 | const root = hex.spell("#root", { |
| 92 | init(arg) { |
| 93 | this.setColor(arg.color || "#4764bf"); |
| 94 | this.$doc = document.documentElement; |
| 95 | }, |
| 96 | setColor(color) { |
| 97 | const color_lum = luminanceOfHex(color); |
| 98 | this.$doc.style.setProperty('--test-color', color); |
| 99 | |
| 100 | const white_contrast = (Math.max(white_lum, color_lum)+0.05) / (Math.min(white_lum, color_lum)+0.05); |
| 101 | const discord_contrast = (Math.max(discord_lum, color_lum)+0.05) / (Math.min(discord_lum, color_lum)+0.05); |
| 102 | |
| 103 | this.colorName = color; |
| 104 | this.whiteContrast = white_contrast.toFixed(1); |
| 105 | this.discordContrast = discord_contrast.toFixed(1); |
| 106 | const link = `https://contrast.junglecoder.com/${curr_color}` |
| 107 | this.$toShare.href = link; |
| 108 | } |
| 109 | picker: ["#color-picker", { on: { |
| 110 | change(e) { |
| 111 | this.setColor(e.target.value); |
| 112 | } |
| 113 | }}], |
| 114 | colorName: ["#color-1"], |
| 115 | whiteContrast: ["#white-contrast"], |
| 116 | darkContrast: ["#discord-contrast"], |
| 117 | toShare: ["#a-share"], |
| 118 | shareBtn: ["#btn-share", { |
| 119 | t: 'Copy Link', |
| 120 | on: { |
| 121 | async click() { |
| 122 | const link = `https://contrast.junglecoder.com/${this.colorName}` |
| 123 | try { |
| 124 | await navigator.clipboard.writeText(link) |
| 125 | } finally { |
| 126 | this.toShare = "Manually copy link from here"; |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | }] |
| 131 | }); |
| 132 | const urlParams = new URLSearchParams(window.location.search); |
| 133 | let firstColor = window.loation.hash || urlParams.get('c') || "#4764bf"; |
| 134 | if (firstColor[0] !== "#") { |
| 135 | firstColor = "#" + firstColor; |
| 136 | } |
| 137 | console.log('FIRST COLOR', firstColor); |
| 138 | hex.become(root, hex.id('app'), { color: firstColor }); |
| 139 | }); |
| 140 | </script> |
| 141 | </body> |
| 142 | </html> |
| 143 |
hex.js
· 5.7 KiB · JavaScript
Raw
Playground
const hex = (function() {
const q = (el, sel) => !sel ? document.querySelectorAll(el) : el.querySelectorAll(sel);
const id = (sel) => document.getElementById(sel);
const q1 = (el, sel) => !sel ? document.querySelector(el) : el.querySelector(sel);
const isObj = (obj) => { var type = typeof obj; return type === 'object' && !!obj; };
const isSimple = (v) => {var type = typeof v; return type=== "number" || type === "string" || type === "boolean"; }
const arise = (fn) => document.addEventListener('DOMContentLoaded', fn, false);
const scrawl = (k, v) => localStorage.setItem(k, JSON.stringify(v));
const recall = (k) => JSON.parse(localStorage.getItem(k) || "null");
function setterFor(node) {
if (["INPUT", "TEXTAREA"].indexOf(node.tagName) > -1) {
return (val) => node.value = val;
} else {
return (val) => node.innerText = val;
}
}
let templs = new Map();
function from(tempSel) {
let toClone;
if (templs.has(tempSel)) {
toClone = templs.get(tempSel);
} else {
let n = q1(tempSel);
if (n) {
templs.set(tempSel, n);
toClone = n;
} else {
throw tempSel + " failed";
}
}
return toClone.content.cloneNode(true);
}
function become(spell, el, config) {
// TODO: Figure out a way for replaceChildren to work
el.replaceChildren();
spell(el, config);
}
function spell(template, config) {
return function(into, start) {
const basis = from(template)
const state = { vars: {}, dom: {}, setters: {} };
const interface = Object.create(spell);
const recursionGuard = new Set();
for (let [k, v] of Object.entries(config)) {
const makeProp = (el, initialValue) => {
state.setters[k] = setterFor(el);
Object.defineProperty(interface, k, {
get() { return state.vars[k] },
set(v) {
state.vars[k] = v;
state.setters[k]((v || "") + "");
}
});
interface[k] = initialValue;
}
if (typeof v === "string" && v) {
const el = q1(basis, v);
state.dom[k] = el;
makeProp(el, start[k]);
} else if (typeof v === "function") {
interface[k] = v.bind(interface);
} else if (Array.isArray(v)) {
if (v.length <= 0) {
throw new Error("Empty array not valid!");
}
let el;
if (typeof v[0] === "string") {
el = q1(basis, v[0]);
state.dom[k] = el;
} else {
throw new Error(`Selector for ${k} must be a string!`);
}
if (v.length == 1 && (typeof v[0]) == "string") {
state.vars[k] = start[k];
makeProp(el, start[k]);
} else if (v.length == 2 && (typeof v[0]) === "string" && isSimple(v[1])) {
makeProp(el, start[k] || v[1]);
} else if (v.length == 2 && (typeof v[0]) === "string" && isObj(v[1])) {
makeProp(el, start[k]);
const obj = v[1];
if (obj.t) {
interface[k] = start[k] || obj.t;
}
if (obj.on) {
for (const [evt, fn] of Object.entries(obj.on)) {
let inner = fn.bind(interface);
let handler = function() {
if (recursionGuard.has(handler)) { return; }
try {
recursionGuard.add(handler);
inner(...arguments);
} finally {
recursionGuard.delete(handler);
}
};
el.addEventListener(evt, handler);
}
}
if (obj.init && typeof obj.init === "function") {
obj.init.call(interface, start);
}
} else if (v.length === 3 &&
(typeof v[0]) === "string" &&
(typeof v[1]) === "function" &&
(typeof v[2]) === "function"
&& Array.isArray(start[k])
) {
// TODO:
state.vars[k] = start[k].map((data) => v[1](el, v[2](data)));
interface[k] = function(new_data) {
el.replaceChildren();
state.vars[k] = new_data.map((data) => v[1](el, v[2](data)));
}
} else {
throw new Error(`Invalid config: ${k}`);
}
}
}
interface._ = state;
into.appendChild(basis);
if (typeof interface.init === "function") {
interface.init(start);
}
return interface;
}
}
return {q, q1, id, from, arise, scrawl, recall, become, spell};
})();
window.hex = hex;
| 1 | const hex = (function() { |
| 2 | const q = (el, sel) => !sel ? document.querySelectorAll(el) : el.querySelectorAll(sel); |
| 3 | const id = (sel) => document.getElementById(sel); |
| 4 | const q1 = (el, sel) => !sel ? document.querySelector(el) : el.querySelector(sel); |
| 5 | const isObj = (obj) => { var type = typeof obj; return type === 'object' && !!obj; }; |
| 6 | const isSimple = (v) => {var type = typeof v; return type=== "number" || type === "string" || type === "boolean"; } |
| 7 | const arise = (fn) => document.addEventListener('DOMContentLoaded', fn, false); |
| 8 | const scrawl = (k, v) => localStorage.setItem(k, JSON.stringify(v)); |
| 9 | const recall = (k) => JSON.parse(localStorage.getItem(k) || "null"); |
| 10 | |
| 11 | function setterFor(node) { |
| 12 | if (["INPUT", "TEXTAREA"].indexOf(node.tagName) > -1) { |
| 13 | return (val) => node.value = val; |
| 14 | } else { |
| 15 | return (val) => node.innerText = val; |
| 16 | } |
| 17 | } |
| 18 | |
| 19 | let templs = new Map(); |
| 20 | function from(tempSel) { |
| 21 | let toClone; |
| 22 | if (templs.has(tempSel)) { |
| 23 | toClone = templs.get(tempSel); |
| 24 | } else { |
| 25 | let n = q1(tempSel); |
| 26 | if (n) { |
| 27 | templs.set(tempSel, n); |
| 28 | toClone = n; |
| 29 | } else { |
| 30 | throw tempSel + " failed"; |
| 31 | } |
| 32 | } |
| 33 | return toClone.content.cloneNode(true); |
| 34 | } |
| 35 | |
| 36 | function become(spell, el, config) { |
| 37 | // TODO: Figure out a way for replaceChildren to work |
| 38 | el.replaceChildren(); |
| 39 | spell(el, config); |
| 40 | } |
| 41 | |
| 42 | |
| 43 | function spell(template, config) { |
| 44 | |
| 45 | return function(into, start) { |
| 46 | const basis = from(template) |
| 47 | const state = { vars: {}, dom: {}, setters: {} }; |
| 48 | const interface = Object.create(spell); |
| 49 | const recursionGuard = new Set(); |
| 50 | |
| 51 | for (let [k, v] of Object.entries(config)) { |
| 52 | const makeProp = (el, initialValue) => { |
| 53 | state.setters[k] = setterFor(el); |
| 54 | |
| 55 | Object.defineProperty(interface, k, { |
| 56 | get() { return state.vars[k] }, |
| 57 | set(v) { |
| 58 | state.vars[k] = v; |
| 59 | state.setters[k]((v || "") + ""); |
| 60 | } |
| 61 | }); |
| 62 | |
| 63 | interface[k] = initialValue; |
| 64 | } |
| 65 | if (typeof v === "string" && v) { |
| 66 | const el = q1(basis, v); |
| 67 | state.dom[k] = el; |
| 68 | makeProp(el, start[k]); |
| 69 | } else if (typeof v === "function") { |
| 70 | interface[k] = v.bind(interface); |
| 71 | } else if (Array.isArray(v)) { |
| 72 | if (v.length <= 0) { |
| 73 | throw new Error("Empty array not valid!"); |
| 74 | } |
| 75 | let el; |
| 76 | if (typeof v[0] === "string") { |
| 77 | el = q1(basis, v[0]); |
| 78 | state.dom[k] = el; |
| 79 | } else { |
| 80 | throw new Error(`Selector for ${k} must be a string!`); |
| 81 | } |
| 82 | |
| 83 | if (v.length == 1 && (typeof v[0]) == "string") { |
| 84 | state.vars[k] = start[k]; |
| 85 | makeProp(el, start[k]); |
| 86 | } else if (v.length == 2 && (typeof v[0]) === "string" && isSimple(v[1])) { |
| 87 | makeProp(el, start[k] || v[1]); |
| 88 | } else if (v.length == 2 && (typeof v[0]) === "string" && isObj(v[1])) { |
| 89 | makeProp(el, start[k]); |
| 90 | const obj = v[1]; |
| 91 | if (obj.t) { |
| 92 | interface[k] = start[k] || obj.t; |
| 93 | } |
| 94 | if (obj.on) { |
| 95 | for (const [evt, fn] of Object.entries(obj.on)) { |
| 96 | let inner = fn.bind(interface); |
| 97 | let handler = function() { |
| 98 | if (recursionGuard.has(handler)) { return; } |
| 99 | try { |
| 100 | recursionGuard.add(handler); |
| 101 | inner(...arguments); |
| 102 | } finally { |
| 103 | recursionGuard.delete(handler); |
| 104 | } |
| 105 | }; |
| 106 | |
| 107 | el.addEventListener(evt, handler); |
| 108 | } |
| 109 | } |
| 110 | if (obj.init && typeof obj.init === "function") { |
| 111 | obj.init.call(interface, start); |
| 112 | } |
| 113 | |
| 114 | } else if (v.length === 3 && |
| 115 | (typeof v[0]) === "string" && |
| 116 | (typeof v[1]) === "function" && |
| 117 | (typeof v[2]) === "function" |
| 118 | && Array.isArray(start[k]) |
| 119 | ) { |
| 120 | // TODO: |
| 121 | state.vars[k] = start[k].map((data) => v[1](el, v[2](data))); |
| 122 | interface[k] = function(new_data) { |
| 123 | el.replaceChildren(); |
| 124 | state.vars[k] = new_data.map((data) => v[1](el, v[2](data))); |
| 125 | } |
| 126 | } else { |
| 127 | throw new Error(`Invalid config: ${k}`); |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | interface._ = state; |
| 133 | into.appendChild(basis); |
| 134 | if (typeof interface.init === "function") { |
| 135 | interface.init(start); |
| 136 | } |
| 137 | return interface; |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | return {q, q1, id, from, arise, scrawl, recall, become, spell}; |
| 142 | })(); |
| 143 | |
| 144 | window.hex = hex; |
| 145 |