@uiengine/ui
Version:
UIengine user interface.
152 lines (125 loc) • 3.76 kB
JavaScript
const { join, resolve } = require('path')
const { compile } = require('ejs')
const htmlescape = require('htmlescape')
const merge = require('deepmerge')
const Color = require('color')
const defaultLocales = require('./locales')
const highlight = require('./shared/highlight')
const localize = require('./shared/localize')
const {
FileUtil: { read, copy },
StringUtil: { dasherize, titleize }
} = require('@uiengine/util')
const defaultOpts = {
lang: 'en',
hljs: 'atom-one-dark',
base: '/',
cache: true,
customStylesFile: null,
meta: [
{
tag: 'meta',
attrs: {
charset: 'utf-8'
}
},
{
tag: 'meta',
attrs: {
name: 'viewport',
content: 'width=device-width,initial-scale=1.0'
}
}
],
foot: ''
}
// templates are loaded on setup
const templates = {}
const templatesPath = resolve(__dirname, '..', 'lib', 'templates')
const staticPath = resolve(__dirname, '..', 'dist')
const templatePath = template => join(templatesPath, `${template}.ejs`)
async function copyStatic (target) {
await copy(staticPath, target)
}
async function compileTemplate (name) {
const templateString = await read(templatePath(name))
templates[name] = compile(templateString)
}
async function setup (options) {
// configure markdown renderer
const { markdownIt, target } = options
markdownIt.set({ highlight })
// load and assign template
try {
await Promise.all([
compileTemplate('index'),
compileTemplate('sketch'),
copyStatic(target)
])
} catch (err) {
const message = ['UI setup failed:', err]
if (options.debug) message.push(JSON.stringify(options, null, 2))
throw new Error(message.join('\n\n'))
}
}
async function render (options, template = 'index', data = null) {
// sanitize and prepare options, merge UI locales
const customLocales = options.locales || {}
const locales = merge(defaultLocales, customLocales)
delete options.locales
const supportedLocales = Object.keys(locales)
if (!supportedLocales.includes(options.lang)) delete options.lang
const opts = merge(defaultOpts, options)
const basePath = opts.base.replace(/\/$/, '')
const helpers = {
htmlescape,
dasherize,
titleize,
color (value) {
let color, definition
if (typeof value === 'string') {
if (value.startsWith('#')) {
definition = 'hex'
} else if (value.startsWith('hsl')) {
definition = 'hsl'
} else {
definition = 'rgb'
}
} else if (typeof value === 'object' && value.model) {
definition = value.model
}
try { color = Color(value) } catch { color = Color(`rgb(${value})`) }
return {
definition,
hex: color.hex().toString(),
rgb: color.rgb().toString(),
hsl: color.hsl().toString().replace(/(\(\d+\.(\d{1,3}))\d+/, '$1') // shorten the first values decimal places
}
},
colorDefinition (color) {
return color[color.definition]
},
localize (locale, key, interpolations) {
const dict = locales[locale]
return localize(dict, key, interpolations)
}
}
const context = Object.assign({ basePath, helpers }, data, opts)
try {
if (!options.cache) await compileTemplate(template)
const templateFn = templates[template]
const rendered = templateFn(context)
return rendered
} catch (err) {
const message = [`UI could not render template "${template}":`, err]
if (options.debug) message.push(JSON.stringify(context, null, 2))
const error = new Error(message.join('\n\n'))
error.code = err.code
error.path = templatePath(template)
throw error
}
}
module.exports = {
setup,
render
}