// import * as YAML from 'yaml' import jsYaml from '/js/js-yaml.mjs' class App { constructor() {//{{{ window._app = this this.theme = '' this.themes = { default: Theme.default() } this.sections = [] this.retrieveVersion() this.retrieveThemes() .then(()=>this.retrieveConfig()) .then(config=>this.parseConfig(config)) .then(()=>this.setTheme(this.theme)) .then(()=>this.renderPage()) .catch(alert) }//}}} async retrieveThemes() {//{{{ return fetch(`/themes.yaml`) .then(res=>{ if(res.ok) return res.text() throw `Error when fetching /theme.yaml: ${res.status} ${res.statusText}` }) .then(text=>{ let themes = jsYaml.load(text) Object.keys(themes).forEach(themeName=>{ this.themes[themeName] = new Theme(themeName, themes[themeName]) }) }) }//}}} retrieveVersion() {//{{{ return fetch('/VERSION', {cache: "no-store"}) .then(res=>{ if(res.ok) return res.text() throw `Error when fetching /${page}.yaml: ${res.status} ${res.statusText}` }) .then(version=>{ this.version = version document.querySelector('footer').innerHTML = `Dashie ${version}` }) }//}}} retrieveConfig() {//{{{ const urlParams = new URLSearchParams(window.location.search); let page = urlParams.get('page') if(page === null) page = 'start' return fetch(`/${page}.yaml`, {cache: "no-store"}) .then(res=>{ if(res.ok) return res.text() throw `Error when fetching /${page}.yaml: ${res.status} ${res.statusText}` }) .then(text=>{ return jsYaml.load(text) }) }//}}} parseConfig(config) {//{{{ // Pages to switch between this.pages = config.pages ? config.pages : [] // Settings for this page. this.page = config.page // Theme to be used for the initial page render. // Can be changed later. this.theme = this.themes[config.page.theme ? config.page.theme : 'default'] if(this.theme === undefined) throw new Error(`Theme '${config.page.theme}' isn't defined in themes.yaml.`) // Sections are created; items are created within the Section objects. if(config.sections === undefined) config.sections = [] this.sections = config.sections.map((d,idx)=>new Section(idx, d)) }//}}} renderPage() {//{{{ // Page header document.querySelector('header').style.backgroundColor = this.theme.colors.page.header // Pages to switch between let pageStyle = `color: ${this.theme.colors.page_select.text}; background: ${this.theme.colors.page_select.background}` let pages = this.pages.map(page=>`
${page.label}
`) document.getElementById('pages').innerHTML = pages.join('') // Page label let pageLabel = document.getElementById('page-label') pageLabel.innerHTML = this.page.label pageLabel.style.color = this.theme.colors.page.text // The theme selector is sorted by name for easier usage. let themeOptions = Object.keys(this.themes) .sort((a, b)=>{ if(a < b) return -1 if(a > b) return 1 return 0 }) .map(themeName=>``) .join('') // Theme selector also has prev and next buttons to // faster switch between themes (probably trying them out). let t = this.theme let themes = document.getElementById('theme') let style = `color: ${t.colors.theme_select.text}; background: ${t.colors.theme_select.background}; border: 1px solid ${t.colors.theme_select.border}` themes.innerHTML = ` ` let sectionHTML = this.sections.map(s=>s.html()) document.getElementById('sections').innerHTML = sectionHTML.join('') // Footer let footer = document.querySelector('footer') footer.style.display = t.colors.page.footer.show ? 'block' : 'none'; footer.style.color = t.colors.page.footer.text footer.style.backgroundColor = t.colors.page.footer.background }//}}} setTheme(theme) {//{{{ this.theme = theme document.body.style.color = theme.colors.page.text document.body.style.background = theme.colors.page.background let sectionClasses = document.getElementById('sections').classList sectionClasses.remove('theme-brighter') sectionClasses.remove('theme-darker') if(theme.hover == 'brighter') sectionClasses.add('theme-brighter') else if(theme.hover == 'darker') sectionClasses.add('theme-darker') }//}}} selectTheme() {//{{{ let option = document.querySelector('#theme option:checked') this.setThemeByName(option.value) // Focus the theme select box after page render, since // user could be trying themes out and wanting to use the // keyboard up/down. document.querySelector('#theme select').focus() }//}}} setThemeByName(name) {//{{{ this.setTheme(this.themes[name]) this.renderPage() }//}}} previousTheme() {//{{{ let selected = document.querySelector('#theme option:checked') if(!selected) return let prevOption = selected.previousElementSibling if(prevOption) { this.setThemeByName(prevOption.value) } }//}}} nextTheme() {//{{{ let selected = document.querySelector('#theme option:checked') if(!selected) return let nextOption = selected.nextElementSibling if(nextOption) { this.setThemeByName(nextOption.value) } }//}}} open(url) {//{{{ window.open(url, '_blank') }//}}} } class Theme { static default() {//{{{ return new Theme('default', { page: { text: '#fff', header: '#333', background: '#fff', footer: { show: true, text: "#aaa", background: "#eee" } }, page_select: { text: "#ccc", background: "#666" }, theme_select: { text: "#ccc", background: "#333", border: "#888" }, section: { background: '#fff', borders: [ ['#fff', '#22a511'] ] }, item: { label: '#333', background: '#eaeaea', description: '#555', url: '#666', icon: '#000', hover: 'darker' } }) }//}}} constructor(name, data) {//{{{ this.name = name this.hover = data.item.hover this.colors = data }//}}} colorNo(i) {//{{{ // Returns a wrapping color from the list of let idx = i % this.colors.section.borders.length return this.colors.section.borders[idx] }//}}} } class Section { constructor(idx, data) {//{{{ this.section_index = idx this.label = 'Section' this.icon = 'head-question' this.items = [] Object.keys(data).forEach(key=> this[key] = data[key] ) }//}}} html() {//{{{ let theme = window._app.theme let colorIndex = this.color ? this.color : this.section_index let color = theme.colorNo(colorIndex) let itemHTML = this.items.map(itemData=> new Item(this, itemData).html() ) return `
${this.label}
${itemHTML.join('')}
` }//}}} } class Item { constructor(section, data) {//{{{ this.section = section this.label = 'Item' this.icon = 'head-question' this.url = 'https://example.com' Object.keys(data).forEach(key=> this[key] = data[key] ) }//}}} html() {//{{{ let theme = window._app.theme let colorIndex = this.section.color ? this.section.color : this.section.section_index let sectionColor = theme.colorNo(colorIndex)[0] let colorLabel = theme.colors.item.label if(!colorLabel) colorLabel = sectionColor let colorIcon = theme.colors.item.icon if(!colorIcon) colorIcon = sectionColor let description = '' if(this.description) description = `
${this.description}
` return `
${this.label}
${description}
${this.url}
` }//}}} } let app = new App() // vim: foldmethod=marker