2023-07-25 13:01:17 +02:00
|
|
|
// 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 = []
|
|
|
|
|
2023-07-25 13:48:25 +02:00
|
|
|
this.retrieveVersion()
|
|
|
|
|
2023-07-25 13:01:17 +02:00
|
|
|
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])
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}//}}}
|
2023-07-25 13:48:25 +02:00
|
|
|
retrieveVersion() {//{{{
|
|
|
|
return fetch('/VERSION')
|
|
|
|
.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}`
|
|
|
|
})
|
|
|
|
}//}}}
|
2023-07-25 13:01:17 +02:00
|
|
|
retrieveConfig() {//{{{
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
|
|
let page = urlParams.get('page')
|
|
|
|
if(page === null)
|
|
|
|
page = 'start'
|
|
|
|
|
|
|
|
return fetch(`/${page}.yaml`)
|
|
|
|
.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=>`<div class="page" style="${pageStyle}" onclick="window.open('/?page=${page.name}', '_self')">${page.label}</div>`)
|
|
|
|
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=>`<option value="${themeName}" ${this.theme.name == themeName ? 'selected' : ''}>${themeName}</option>`)
|
|
|
|
.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 = `
|
|
|
|
<button style="${style}" onclick="window._app.previousTheme()"><</button>
|
|
|
|
<select id="theme-select" onchange="window._app.selectTheme()" style="${style}">
|
|
|
|
${themeOptions}
|
|
|
|
</select>
|
|
|
|
<button style="${style}" onclick="window._app.nextTheme()">></button>
|
|
|
|
`
|
|
|
|
|
|
|
|
let sectionHTML = this.sections.map(s=>s.html())
|
|
|
|
document.getElementById('sections').innerHTML = sectionHTML.join('')
|
2023-07-25 13:48:25 +02:00
|
|
|
|
|
|
|
// 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
|
2023-07-25 13:01:17 +02:00
|
|
|
}//}}}
|
|
|
|
setTheme(theme) {//{{{
|
|
|
|
this.theme = theme
|
|
|
|
document.body.style.color = theme.colors.page.text
|
|
|
|
document.body.style.backgroundColor = 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',
|
2023-07-25 13:48:25 +02:00
|
|
|
background: '#fff',
|
|
|
|
footer: {
|
|
|
|
show: true,
|
|
|
|
text: "#aaa",
|
|
|
|
background: "#eee"
|
|
|
|
}
|
2023-07-25 13:01:17 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
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 `
|
|
|
|
<div class="section" style="border: 4px solid ${color[0]}; background: ${theme.colors.section.background}">
|
|
|
|
<div class="name" style="background: ${color[0]}; color: ${color[1]}">${this.label}</div>
|
|
|
|
<div class="items" style="background: ${theme.colors.section.background}">
|
|
|
|
${itemHTML.join('')}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`
|
|
|
|
}//}}}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 = `<div class="description" style="color: ${theme.colors.item.description}">${this.description}</div>`
|
|
|
|
|
|
|
|
return `
|
|
|
|
<div class="item" style="background: ${theme.colors.item.background}" onclick="window._app.open('${this.url}')">
|
|
|
|
<div class="icon mdi-set mdi-${this.icon}" style="color: ${colorIcon}"></div>
|
|
|
|
<div class="label" style="color: ${colorLabel}">${this.label}</div>
|
|
|
|
${description}
|
|
|
|
<div class="url" style="color: ${theme.colors.item.url}">${this.url}</div>
|
|
|
|
</div>
|
|
|
|
`
|
|
|
|
}//}}}
|
|
|
|
}
|
|
|
|
|
|
|
|
let app = new App()
|
|
|
|
|
|
|
|
// vim: foldmethod=marker
|