Created
September 11, 2019 20:21
-
-
Save enjikaka/58329d90064020efe19115f67846a711 to your computer and use it in GitHub Desktop.
Nightcore-App Web.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class TemplateStore { | |
constructor (node) { | |
if (!(node instanceof HTMLElement)) { | |
throw new Error('DOM Node for template storage is not set up. Please pass in an HTMLElement to the constructor.'); | |
} | |
this.parentDOMNode = node; | |
} | |
/** | |
* Takes a string of content to be cached inside a | |
* <template> element. | |
* | |
* @param {string} id | |
* @param {string} templateContent | |
*/ | |
add (id, templateContent) { | |
if (this.parentDOMNode.querySelector(`#${id}`)) { | |
return; | |
} | |
const template = document.createElement('template'); | |
template.setAttribute('id', id); | |
template.innerHTML = templateContent; | |
this.parentDOMNode.appendChild(template); | |
} | |
get (id) { | |
if (!this.parentDOMNode.querySelector(`#${id}`)) { | |
throw new Error(`Template with id #${id} does not exist.`); | |
} | |
const templateElement = this.parentDOMNode.querySelector(`#${id}`); | |
if (templateElement instanceof HTMLTemplateElement) { | |
const templateContent = templateElement.content; | |
return document.importNode(templateContent, true); | |
} | |
return undefined; | |
} | |
/** | |
* Takes a string of content to be cached inside a | |
* <template> element. | |
* | |
* @param {string} id | |
*/ | |
remove (id) { | |
const template = this.parentDOMNode.querySelector(`#${id}`); | |
if (template) { | |
this.parentDOMNode.removeChild(template); | |
} | |
} | |
} | |
const templateStore = new TemplateStore(document.body); | |
export class Component extends HTMLElement { | |
async fetchResources () { | |
// @ts-ignore | |
if (!this.resources && !this.render && this.path) { | |
// @ts-ignore | |
const pathname = new URL(this.path).pathname; | |
const file = pathname.split('/').pop(); | |
if (file === 'web.js') { | |
return; | |
} | |
const filename = file.split('.js')[0]; | |
const path = pathname.split(file)[0]; | |
this.resources = [ | |
{ path: path + filename + '.css' }, | |
{ path: path + filename + '.html' } | |
]; | |
} | |
const resourceLoadPromises = this.resources.map(({ path }) => fetch(path)); | |
const fetches = await Promise.all(resourceLoadPromises); | |
const responses = await Promise.all(fetches.filter(r => r.ok).map(r => r.text())); | |
this.resources = this.resources.map((resource, i) => { | |
resource.content = responses[i]; | |
return resource; | |
}); | |
} | |
async buildTemplate () { | |
if (!this.resources) { | |
return; | |
} | |
const stylesheet = this.resources.filter(r => r.path.indexOf('css') !== -1).map(r => r.content).join('\n'); | |
const markup = this.resources.filter(r => r.path.indexOf('html') !== -1).map(r => r.content).join('\n'); | |
const templateContent = ` | |
<style> | |
${stylesheet} | |
</style> | |
${markup} | |
`; | |
// @ts-ignore | |
templateStore.add(this.constructor.is, templateContent); | |
} | |
connectedCallback () { | |
this.sDOM = this.attachShadow({ mode: 'closed' }); | |
this.renderToShadowDOM(); | |
} | |
get props () { | |
return mapObservedAttributesToObject(this); | |
} | |
/** | |
* @param {string} selectors | |
* @return {Element | undefined} | |
*/ | |
$ (selectors) { | |
if (this.sDOM) { | |
return this.sDOM.querySelector(selectors); | |
} | |
return undefined; | |
} | |
/** | |
* @param {string} selectors | |
* @return {NodeListOf<Element> | undefined} | |
*/ | |
$$ (selectors) { | |
if (this.sDOM) { | |
return this.sDOM.querySelectorAll(selectors); | |
} | |
return undefined; | |
} | |
async renderToShadowDOM () { | |
// @ts-ignore | |
if (this.render) { | |
// @ts-ignore | |
this.sDOM.innerHTML = this.render(); | |
} else { | |
await this.fetchResources(); | |
await this.buildTemplate(); | |
// @ts-ignore | |
const templateContent = await templateStore.get(this.constructor.is); | |
this.sDOM.appendChild(templateContent); | |
} | |
requestAnimationFrame(() => { | |
requestAnimationFrame(() => { | |
// @ts-ignore | |
if (this.postRender) { | |
// @ts-ignore | |
this.postRender(); | |
} | |
}); | |
}); | |
} | |
static get is () { | |
console.error('You need to set a name for the component.'); | |
return undefined; | |
} | |
} | |
export function register (StaticComponent) { | |
const hasName = Boolean(StaticComponent.is); | |
if (!hasName) { | |
console.error('Passed in stuff does not have name on static get is property.'); | |
return; | |
} | |
const alreadyDefined = customElements.get(StaticComponent.is); | |
if (alreadyDefined) { | |
console.debug(`<${StaticComponent.is}> already defined. Skipping.`); | |
return; | |
} | |
customElements.define(StaticComponent.is, StaticComponent); | |
} | |
function snakeToCamel (s) { | |
return s.replace(/(-\w)/g, m => m[1].toUpperCase()); | |
} | |
export function mapObservedAttributesToObject (componentInstance) { | |
const props = {}; | |
// Map attributes to props | |
componentInstance.constructor.observedAttributes | |
.filter(attr => componentInstance.hasAttribute(attr)) | |
.filter(attr => componentInstance.getAttribute(attr) !== undefined) | |
.forEach(attribute => { | |
props[snakeToCamel(attribute)] = componentInstance.getAttribute(attribute); | |
}); | |
return props; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment