Skip to content

Commit

Permalink
refactor: chain methods about preparing renderer
Browse files Browse the repository at this point in the history
* rename renderer property: "run" -> "steps"

* preRender is a promise which chains methods about preparing renderere.
  It's resolved value is renderer, used to chain promises from renderer.steps

* renderer.results stores results of "preRender" and "renderer.steps".
  If a result is within state='stop', all following steps would be
  ignored.
  • Loading branch information
Hsieh Chin Fan committed Sep 27, 2024
1 parent 5f3c69e commit cd8490a
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 51 deletions.
2 changes: 1 addition & 1 deletion src/BaseRenderer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default class {
if (this._map) throw Error("map cannot be reassigned")
this._map = value;
}
get run() {
get steps() {
return [
this.setOptionAliases,
this.createView,
Expand Down
2 changes: 1 addition & 1 deletion src/BasicOpenlayersRenderer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const Renderer = class extends defaultExport {
}
proj4 = proj4

get run() {
get steps() {
return [
this.setCoordinateSystem,
...super.run,
Expand Down
164 changes: 115 additions & 49 deletions src/mapclay.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,24 @@ const defaultAliases = Object.freeze({
},
}
});
/**
* just a single default converter for config
*
* @param {Object} config -- original config
* @return {Object} config -- config patched
*/
const applyDefaultAliases = (config) => {
config.aliases = { ...defaultAliases, ...(config.aliases ?? {}) }
return config
}
// }}}
// Parse yaml content with raw text {{{
/**
* parseConfigsFromYaml.
*
* @param {String} configText -- yaml text
* @return {Object[]} -- List of config for rendering
*/
const parseConfigsFromYaml = (configText) => {
const configList = []
yamlLoadAll(
Expand All @@ -47,20 +59,32 @@ const parseConfigsFromYaml = (configText) => {
// Get config from other file by 'apply' {{{
const appliedConfigs = {}

/**
* fetch remote config file and save it for cache
*
* @param {String} url -- url for remote config file
*/
const fetchConfig = async (url) => {
if (appliedConfigs[url]) return
if (!url || appliedConfigs[url]) return

appliedConfigs[url] = fetch(url)
.then(response => {
if (response.status !== 200) throw Error()
return response.text()
})
.then(text => yamlLoad(text))
.catch((err) => { throw Error(`Fail to fetch applied config ${url}`, err) })
.catch(err => { throw Error(`Fail to fetch applied config ${url}`, err) })
}

// }}}
// Set option value by aliases {{{
/**
* Replace aliases in each property by property 'aliases'.
* An alias must starts with upper-case
*
* @param {Object} config -- original config
* @return {Object} patched config
*/
const setValueByAliases = (config) => {
if (!config.aliases) return config

Expand All @@ -82,53 +106,64 @@ const setValueByAliases = (config) => {
}
// }}}
// Render each map container by config {{{
const isClass = (C) => typeof C === "function" && C.prototype !== undefined
const getRendererClass = async (config) => {
const rendererUrl = config.use ?? Object.values(config.aliases?.use)?.at(0)?.value
if (!rendererUrl) throw Error(`Renderer URL is not specified ${rendererUrl}`)

return (await import(rendererUrl).catch((err) => {
throw Error(`Fail to import renderer by URL ${rendererUrl}: ${err}`)
})).default
}

/**
* renderTargetWithConfig.
* applyOtherConfig.
*
* @param {HTMLElement} target -- target element for map view
* @param {Object} config -- options for map configuration
* @return {Object} renderer -- object responsible for rendering, check property "result" for details
* @param {Object} config -- original config for rendering
* @return {Promise} -- resolve "patched" config
*/
const renderTargetWithConfig = async (target, config) => {
// In case config.apply is using alias
setValueByAliases(config)
const applyOtherConfig = async (config) => {
if (config.apply) {
try {
fetchConfig(config.apply)
const preset = await appliedConfigs[config.apply]
config = { ...preset, ...config }
} catch (err) {
console.warn(err)
}
await fetchConfig(config.apply)
const preset = appliedConfigs[config.apply]
config = { ...preset, ...config }
setValueByAliases(config)
}
setValueByAliases(config)
return config
}

// Get renderer
const rendererClass = isClass(config.use)
? config.use
: await getRendererClass(config)
if (!rendererClass) throw Error(`Fail to get renderer class by module ${config.use}`)
const renderer = new rendererClass()
/**
* prepareRenderer.
*
* @param {Object} config -- prepare renderer by properties in config
* @return {Promise} -- resolve renderer used for rendering an HTMLElement
*/
const prepareRenderer = async (config) => {
let renderer
if (!config.use) {
renderer = config
} else {
renderer = config.use.steps
? config.use
: new (await import(config.use)).default()

Object.entries(config)
.forEach(([key, value]) => renderer[key] = value)
Object.entries(config)
.forEach(([key, value]) => renderer[key] = value)
}

// Run functions
renderer.results = []
target.renderer = renderer
// TODO Save passed arguments for each function
renderer.run.reduce((acc, func) => acc
return renderer
}

// TODO health check
const healthCheck = ( renderer) => {
if (!renderer.steps) throw Error('not health')
return renderer
}

/**
* runBySteps.
*
* @param {Object} renderer -- for each function in property "steps",
* run them one by one and store result into property "results"
* @return {Promise} -- chanined promises of function calls
*/
const runBySteps = (renderer) => renderer.steps
.reduce((acc, func) => acc
.then(() => {
if (renderer.results.at(-1).state === 'stop') {
return { state: 'stop' }
}
// If dependencies not success, just skip this function
if (func.depends) {
const dependentResult = renderer.results
Expand Down Expand Up @@ -156,8 +191,46 @@ const renderTargetWithConfig = async (target, config) => {
})),
Promise.resolve()
)
.then(() => renderer)

return renderer
/**
* renderTargetWithConfig.
*
* @param {HTMLElement} target -- target element for map view
* @param {Object} config -- options for map configuration
* @return {Promise} promise -- which resolve value is a renderer,
property "results" contains result objec of each step
*/
const renderTargetWithConfig = ({ target, config }) => {
// Store raw config into target element
target.mapclayConfig = JSON.parse(JSON.stringify(config))

// Prepare for rendering
config.results = []
config.target = target
setValueByAliases(config)

const preRender = [
applyOtherConfig,
prepareRenderer,
healthCheck,
].reduce(
(acc, step) => acc
.then(async (value) => {
if (value.results.at(-1)?.state === 'stop') return value
try {
const result = await step(value)
value.results.push({ func: step, state: 'success', result: result, })
return result
} catch (err) {
value.results.push({ func: step, state: 'stop', result: err, })
return value
}
}),
Promise.resolve(config)
)

return preRender.then(renderer => runBySteps(renderer))
}
// }}}
// Render target by config {{{
Expand Down Expand Up @@ -193,22 +266,15 @@ const renderWith = (converter) => (element, configObj) => {
target.title = config.id
}
target.style.position = 'relative'
target.classList.add('map-container')
target.classList.add('mapclay')
element.append(target)
return { target, config }
}

// List of promises about rendering each config
return configListArray
.map(createContainer)
.map(pair => {
const { target, config } = pair
return {
target,
config: JSON.parse(JSON.stringify(config)),
promise: renderTargetWithConfig(target, config)
}
})
.map(renderTargetWithConfig)
}
// }}}
// Render target element by textContent {{{
Expand Down

0 comments on commit cd8490a

Please sign in to comment.