| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| 'use strict' | ||
|
|
||
| function Acels (client) { | ||
| this.el = document.createElement('ul') | ||
| this.el.id = 'acels' | ||
|
|
||
| this.order = [] | ||
| this.all = {} | ||
| this.roles = {} | ||
| this.pipe = null | ||
|
|
||
| this.install = (host = document.body) => { | ||
| window.addEventListener('keydown', this.onKeyDown, false) | ||
| window.addEventListener('keyup', this.onKeyUp, false) | ||
| host.appendChild(this.el) | ||
| } | ||
|
|
||
| this.start = () => { | ||
| const cats = this.sort() | ||
| for (const cat of this.order) { | ||
| const main = document.createElement('li') | ||
| const head = document.createElement('a') | ||
| head.innerText = cat | ||
| const subs = document.createElement('ul') | ||
| for (const item of cats[cat]) { | ||
| const option = document.createElement('li') | ||
| option.onclick = item.downfn | ||
| option.innerHTML = item.accelerator ? `${item.name} <i>${item.accelerator.replace('CmdOrCtrl+', '^')}</i>` : `${item.name}` | ||
| subs.appendChild(option) | ||
| } | ||
| main.appendChild(head) | ||
| main.appendChild(subs) | ||
| this.el.appendChild(main) | ||
| } | ||
| } | ||
|
|
||
| this.set = (cat, name, accelerator, downfn, upfn) => { | ||
| if (this.all[accelerator]) { console.warn('Acels', `Trying to overwrite ${this.all[accelerator].name}, with ${name}.`) } | ||
| if (this.order.indexOf(cat) < 0) { this.order.push(cat) } | ||
| this.all[accelerator] = { cat, name, downfn, upfn, accelerator } | ||
| } | ||
|
|
||
| this.add = (cat, role) => { | ||
| this.all[':' + role] = { cat, name: role, role } | ||
| } | ||
|
|
||
| this.get = (accelerator) => { | ||
| return this.all[accelerator] | ||
| } | ||
|
|
||
| this.sort = () => { | ||
| const h = {} | ||
| for (const item of Object.values(this.all)) { | ||
| if (!h[item.cat]) { h[item.cat] = [] } | ||
| h[item.cat].push(item) | ||
| } | ||
| return h | ||
| } | ||
|
|
||
| this.convert = (event) => { | ||
| const accelerator = event.key === ' ' ? 'Space' : capitalize(event.key.replace('Arrow', '')) | ||
| if ((event.ctrlKey || event.metaKey) && event.shiftKey) { | ||
| return `CmdOrCtrl+Shift+${accelerator}` | ||
| } | ||
| if (event.shiftKey && event.key.toUpperCase() !== event.key) { | ||
| return `Shift+${accelerator}` | ||
| } | ||
| if (event.altKey && event.key.length !== 1) { | ||
| return `Alt+${accelerator}` | ||
| } | ||
| if (event.ctrlKey || event.metaKey) { | ||
| return `CmdOrCtrl+${accelerator}` | ||
| } | ||
| return accelerator | ||
| } | ||
|
|
||
| this.route = (obj) => { | ||
| this.pipe = obj | ||
| } | ||
|
|
||
| this.onKeyDown = (e) => { | ||
| const target = this.get(this.convert(e)) | ||
| if (!target || !target.downfn) { return this.pipe ? this.pipe.onKeyDown(e) : null } | ||
|
|
||
| target.downfn() | ||
| e.preventDefault() | ||
| } | ||
|
|
||
| this.onKeyUp = (e) => { | ||
| const target = this.get(this.convert(e)) | ||
| if (!target || !target.upfn) { return this.pipe ? this.pipe.onKeyUp(e) : null } | ||
| target.upfn() | ||
| e.preventDefault() | ||
| } | ||
|
|
||
| this.toMarkdown = () => { | ||
| const cats = this.sort() | ||
| let text = '' | ||
| for (const cat in cats) { | ||
| text += `\n### ${cat}\n\n` | ||
| for (const item of cats[cat]) { | ||
| text += item.accelerator ? `- \`${item.accelerator}\`: ${item.name}\n` : '' | ||
| } | ||
| } | ||
| return text.trim() | ||
| } | ||
|
|
||
| this.toString = () => { | ||
| const cats = this.sort() | ||
| let text = '' | ||
| for (const cat of this.order) { | ||
| for (const item of cats[cat]) { | ||
| text += item.accelerator ? `${cat.padEnd(8, ' ')} ${item.name.padEnd(16, ' ')} ${item.accelerator.replace('CmdOrCtrl+', '^')}\n` : '' | ||
| } | ||
| } | ||
| return text.trim() | ||
| } | ||
|
|
||
| this.toggle = () => { | ||
| this.el.className = this.el.className === 'hidden' ? '' : 'hidden' | ||
| } | ||
|
|
||
| function capitalize (s) { return s.substr(0, 1).toUpperCase() + s.substr(1) } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| 'use strict' | ||
|
|
||
| const fs = require('fs') | ||
| const libs = fs.readdirSync('./scripts/lib').filter((file) => { return file.indexOf('.js') > 0 && file !== 'build.js' }) | ||
| const scripts = fs.readdirSync('./scripts').filter((file) => { return file.indexOf('.js') > 0 }) | ||
| const styles = fs.readdirSync('./links').filter((file) => { return file.indexOf('.css') > 0 }) | ||
| const id = process.cwd().split('/').slice(-1)[0] | ||
|
|
||
| function cleanup (txt) { | ||
| const lines = txt.split('\n') | ||
| let output = '' | ||
| for (const line of lines) { | ||
| if (line.trim() === '') { continue } | ||
| if (line.trim().substr(0, 2) === '//') { continue } | ||
| if (line.indexOf('/*') > -1 && line.indexOf('*/') > -1) { continue } | ||
| output += line + '\n' | ||
| } | ||
| return output | ||
| } | ||
|
|
||
| // Create release | ||
|
|
||
| fs.writeFileSync('index.html', cleanup(` | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <title>${id}</title> | ||
| </head> | ||
| <body> | ||
| <script> | ||
| ${libs.reduce((acc, item) => { return `${acc}// Including Library ${item}\n\n${fs.readFileSync('./scripts/lib/' + item, 'utf8')}\n` }, '')} | ||
| ${scripts.reduce((acc, item) => { return `${acc}// Including Script ${item}\n\n${fs.readFileSync('./scripts/' + item, 'utf8')}\n` }, '')} | ||
| const client = new Client() | ||
| client.install(document.body) | ||
| window.addEventListener('load', () => { | ||
| client.start() | ||
| }) | ||
| </script> | ||
| <style> | ||
| ${styles.reduce((acc, item) => { return `${acc}/* Including Style ${item} */ \n\n${fs.readFileSync('./links/' + item, 'utf8')}\n` }, '')} | ||
| </style> | ||
| </body> | ||
| </html>`)) | ||
|
|
||
| // Create debug | ||
|
|
||
| fs.writeFileSync('debug.html', ` | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <title>${id}</title> | ||
| ${styles.reduce((acc, item) => { return `${acc}<link rel="stylesheet" type="text/css" href="./links/${item}"/>\n` }, '')} | ||
| ${libs.reduce((acc, item) => { return `${acc}<script type="text/javascript" src="./scripts/lib/${item}"></script>\n` }, '')} | ||
| ${scripts.reduce((acc, item) => { return `${acc}<script type="text/javascript" src="./scripts/${item}"></script>\n` }, '')} | ||
| </head> | ||
| <body> | ||
| <script> | ||
| const client = new Client() | ||
| client.install(document.body) | ||
| window.addEventListener('load', () => { | ||
| client.start() | ||
| }) | ||
| </script> | ||
| </body> | ||
| </html>`) | ||
|
|
||
| console.log(`Built ${id}`) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| 'use strict' | ||
|
|
||
| /* global FileReader */ | ||
| /* global MouseEvent */ | ||
|
|
||
| function Source (client) { | ||
| this.cache = {} | ||
|
|
||
| this.install = () => { | ||
| } | ||
|
|
||
| this.start = () => { | ||
| this.new() | ||
| } | ||
|
|
||
| this.new = () => { | ||
| console.log('Source', 'New file..') | ||
| this.cache = {} | ||
| } | ||
|
|
||
| this.open = (ext, callback, store = false) => { | ||
| console.log('Source', 'Open file..') | ||
| const input = document.createElement('input') | ||
| input.type = 'file' | ||
| input.onchange = (e) => { | ||
| const file = e.target.files[0] | ||
| if (file.name.indexOf('.' + ext) < 0) { console.warn('Source', `Skipped ${file.name}`); return } | ||
| this.read(file, callback, store) | ||
| } | ||
| input.click() | ||
| } | ||
|
|
||
| this.load = (ext, callback) => { | ||
| console.log('Source', 'Load files..') | ||
| const input = document.createElement('input') | ||
| input.type = 'file' | ||
| input.setAttribute('multiple', 'multiple') | ||
| input.onchange = (e) => { | ||
| for (const file of e.target.files) { | ||
| if (file.name.indexOf('.' + ext) < 0) { console.warn('Source', `Skipped ${file.name}`); continue } | ||
| this.read(file, this.store) | ||
| } | ||
| } | ||
| input.click() | ||
| } | ||
|
|
||
| this.store = (file, content) => { | ||
| console.info('Source', 'Stored ' + file.name) | ||
| this.cache[file.name] = content | ||
| } | ||
|
|
||
| this.save = (name, content, type = 'text/plain', callback) => { | ||
| this.saveAs(name, content, type, callback) | ||
| } | ||
|
|
||
| this.saveAs = (name, ext, content, type = 'text/plain', callback) => { | ||
| console.log('Source', 'Save new file..') | ||
| this.write(name, ext, content, type, callback) | ||
| } | ||
|
|
||
| // I/O | ||
|
|
||
| this.read = (file, callback, store = false) => { | ||
| const reader = new FileReader() | ||
| reader.onload = (event) => { | ||
| const res = event.target.result | ||
| if (callback) { callback(file, res) } | ||
| if (store) { this.store(file, res) } | ||
| } | ||
| reader.readAsText(file, 'UTF-8') | ||
| } | ||
|
|
||
| this.write = (name, ext, content, type, settings = 'charset=utf-8') => { | ||
| const link = document.createElement('a') | ||
| link.setAttribute('download', `${name}-${timestamp()}.${ext}`) | ||
| if (type === 'image/png' || type === 'image/jpeg') { | ||
| link.setAttribute('href', content) | ||
| } else { | ||
| link.setAttribute('href', 'data:' + type + ';' + settings + ',' + encodeURIComponent(content)) | ||
| } | ||
| link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })) | ||
| } | ||
|
|
||
| function timestamp (d = new Date(), e = new Date(d)) { | ||
| return `${arvelie()}-${neralie()}` | ||
| } | ||
|
|
||
| function arvelie (date = new Date()) { | ||
| const start = new Date(date.getFullYear(), 0, 0) | ||
| const diff = (date - start) + ((start.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000) | ||
| const doty = Math.floor(diff / 86400000) - 1 | ||
| const y = date.getFullYear().toString().substr(2, 2) | ||
| const m = doty === 364 || doty === 365 ? '+' : String.fromCharCode(97 + Math.floor(doty / 14)).toUpperCase() | ||
| const d = `${(doty === 365 ? 1 : doty === 366 ? 2 : (doty % 14)) + 1}`.padStart(2, '0') | ||
| return `${y}${m}${d}` | ||
| } | ||
|
|
||
| function neralie (d = new Date(), e = new Date(d)) { | ||
| const ms = e - d.setHours(0, 0, 0, 0) | ||
| return (ms / 8640 / 10000).toFixed(6).substr(2, 6) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| 'use strict' | ||
|
|
||
| /* global localStorage */ | ||
| /* global FileReader */ | ||
| /* global DOMParser */ | ||
|
|
||
| function Theme (client) { | ||
| this.el = document.createElement('style') | ||
| this.el.type = 'text/css' | ||
|
|
||
| this.active = {} | ||
| this.default = { | ||
| background: '#eeeeee', | ||
| f_high: '#0a0a0a', | ||
| f_med: '#4a4a4a', | ||
| f_low: '#6a6a6a', | ||
| f_inv: '#111111', | ||
| b_high: '#a1a1a1', | ||
| b_med: '#c1c1c1', | ||
| b_low: '#ffffff', | ||
| b_inv: '#ffb545' | ||
| } | ||
|
|
||
| // Callbacks | ||
| this.onLoad = () => {} | ||
|
|
||
| this.install = (host = document.body) => { | ||
| window.addEventListener('dragover', this.drag) | ||
| window.addEventListener('drop', this.drop) | ||
| host.appendChild(this.el) | ||
| } | ||
|
|
||
| this.start = () => { | ||
| console.log('Theme', 'Starting..') | ||
| if (isJson(localStorage.theme)) { | ||
| const storage = JSON.parse(localStorage.theme) | ||
| if (isValid(storage)) { | ||
| console.log('Theme', 'Loading theme in localStorage..') | ||
| this.load(storage) | ||
| return | ||
| } | ||
| } | ||
| this.load(this.default) | ||
| } | ||
|
|
||
| this.open = () => { | ||
| console.log('Theme', 'Open theme..') | ||
| const input = document.createElement('input') | ||
| input.type = 'file' | ||
| input.onchange = (e) => { | ||
| this.read(e.target.files[0], this.load) | ||
| } | ||
| input.click() | ||
| } | ||
|
|
||
| this.load = (data) => { | ||
| const theme = this.parse(data) | ||
| if (!isValid(theme)) { console.warn('Theme', 'Invalid format'); return } | ||
| console.log('Theme', 'Loaded theme!') | ||
| this.el.innerHTML = `:root { | ||
| --background: ${theme.background}; | ||
| --f_high: ${theme.f_high}; | ||
| --f_med: ${theme.f_med}; | ||
| --f_low: ${theme.f_low}; | ||
| --f_inv: ${theme.f_inv}; | ||
| --b_high: ${theme.b_high}; | ||
| --b_med: ${theme.b_med}; | ||
| --b_low: ${theme.b_low}; | ||
| --b_inv: ${theme.b_inv}; | ||
| }` | ||
| localStorage.setItem('theme', JSON.stringify(theme)) | ||
| this.active = theme | ||
| if (this.onLoad) { | ||
| this.onLoad(data) | ||
| } | ||
| } | ||
|
|
||
| this.reset = () => { | ||
| this.load(this.default) | ||
| } | ||
|
|
||
| this.set = (key, val) => { | ||
| if (!val) { return } | ||
| const hex = (`${val}`.substr(0, 1) !== '#' ? '#' : '') + `${val}` | ||
| if (!isColor(hex)) { console.warn('Theme', `${hex} is not a valid color.`); return } | ||
| this.active[key] = hex | ||
| } | ||
|
|
||
| this.read = (key) => { | ||
| return this.active[key] | ||
| } | ||
|
|
||
| this.parse = (any) => { | ||
| if (isValid(any)) { return any } | ||
| if (isJson(any)) { return JSON.parse(any) } | ||
| if (isHtml(any)) { return extract(any) } | ||
| } | ||
|
|
||
| // Drag | ||
|
|
||
| this.drag = (e) => { | ||
| e.stopPropagation() | ||
| e.preventDefault() | ||
| e.dataTransfer.dropEffect = 'copy' | ||
| } | ||
|
|
||
| this.drop = (e) => { | ||
| e.preventDefault() | ||
| const file = e.dataTransfer.files[0] | ||
| if (file.name.indexOf('.svg') > -1) { | ||
| this.read(file, this.load) | ||
| } | ||
| e.stopPropagation() | ||
| } | ||
|
|
||
| this.read = (file, callback) => { | ||
| const reader = new FileReader() | ||
| reader.onload = (event) => { | ||
| callback(event.target.result) | ||
| } | ||
| reader.readAsText(file, 'UTF-8') | ||
| } | ||
|
|
||
| // Helpers | ||
|
|
||
| function extract (xml) { | ||
| const svg = new DOMParser().parseFromString(xml, 'text/xml') | ||
| try { | ||
| return { | ||
| background: svg.getElementById('background').getAttribute('fill'), | ||
| f_high: svg.getElementById('f_high').getAttribute('fill'), | ||
| f_med: svg.getElementById('f_med').getAttribute('fill'), | ||
| f_low: svg.getElementById('f_low').getAttribute('fill'), | ||
| f_inv: svg.getElementById('f_inv').getAttribute('fill'), | ||
| b_high: svg.getElementById('b_high').getAttribute('fill'), | ||
| b_med: svg.getElementById('b_med').getAttribute('fill'), | ||
| b_low: svg.getElementById('b_low').getAttribute('fill'), | ||
| b_inv: svg.getElementById('b_inv').getAttribute('fill') | ||
| } | ||
| } catch (err) { | ||
| console.warn('Theme', 'Incomplete SVG Theme', err) | ||
| } | ||
| } | ||
|
|
||
| function isValid (json) { | ||
| if (!json) { return false } | ||
| if (!json.background || !isColor(json.background)) { return false } | ||
| if (!json.f_high || !isColor(json.f_high)) { return false } | ||
| if (!json.f_med || !isColor(json.f_med)) { return false } | ||
| if (!json.f_low || !isColor(json.f_low)) { return false } | ||
| if (!json.f_inv || !isColor(json.f_inv)) { return false } | ||
| if (!json.b_high || !isColor(json.b_high)) { return false } | ||
| if (!json.b_med || !isColor(json.b_med)) { return false } | ||
| if (!json.b_low || !isColor(json.b_low)) { return false } | ||
| if (!json.b_inv || !isColor(json.b_inv)) { return false } | ||
| return true | ||
| } | ||
|
|
||
| function isColor (hex) { | ||
| return /^#([0-9A-F]{3}){1,2}$/i.test(hex) | ||
| } | ||
|
|
||
| function isJson (text) { | ||
| try { JSON.parse(text); return true } catch (error) { return false } | ||
| } | ||
|
|
||
| function isHtml (text) { | ||
| try { new DOMParser().parseFromString(text, 'text/xml'); return true } catch (error) { return false } | ||
| } | ||
| } |