UNPKG

sweetpea

Version:

Signal and Web Component Enhanced Web Apps

286 lines (218 loc) 11 kB
import interpolate from 'interpolate'; import location from 'location'; import Signal from 'signal'; import signalMerge from 'signal-merge'; export default Inheritance => class WorkerSupport extends Inheritance { workerIndex = new Signal(); workerCategory = new Signal(); workerType = new Signal(); workerPath = new Signal(); WorkerClass = new Signal(); workerInstance = new Signal(); async createWorker(){ this.gc = this.workerCategory.subscribe(async workerCategory => { const category = workerCategory; const type = this.workerType.value; if(category && type) this.workerPath.value = `${category}/${type}` }); this.gc = this.workerType.subscribe(async workerType=>{ const category = this.workerCategory.value; const type = workerType; if(category && type) this.workerPath.value = `${category}/${type}` }); this.gc = this.workerPath.subscribe(async workerPath=>this.WorkerClass.set((await import(`${location(window.location.href)}/src/worker/${workerPath}/index.js`)).default)); this.gc = this.workerPath.subscribe(async workerPath => { if (this.host.getAttribute('worker') !== workerPath) { this.host.setAttribute('worker', workerPath); } }); this.gc = this.WorkerClass.subscribe(async WorkerClass => { if (this.workerInstance.value) { await this.workerInstance.value.disconnect(); await this.workerInstance.value.disconnected(); await this.workerInstance.value.destroy(); console.log( this.workerInstance.value ); } this.workerInstance.value = new WorkerClass({ queue: this.queue, buffer: this.buffer, stage: this.getStage().emitter, data: this.data }) }) this.gc = this.workerInstance.subscribe(async workerInstance=>{ await workerInstance.connect(); await workerInstance.connected() }); this.gc = ()=>this.workerInstance.value.disconnect(); // .gc will clean up on removal of element this.gc = ()=>this.workerInstance.value.disconnected(); // .gc will clean up on removal of element this.gc = ()=>this.workerInstance.value.destroy(); // .gc will clean up on removal of element await new Promise(resolve=>this.gc=this.workerInstance.subscribe(v=>Boolean(v)?resolve():null)); return this; } async installWorkerIndex() { this.workerIndex.value = await this.fetchJSON('src/worker/index.json'); return this; } listWorkerCategories() { const selector = '[data-slot="category-list"]'; this.gc = this.workerIndex.subscribe(workerIndex => this.host.shadowRoot.querySelectorAll(selector).forEach(containerNode => { // prepare state of co-dependent dropdown this.host.shadowRoot.querySelectorAll('[data-slot="worker-list"]').forEach(containerNode => { containerNode.replaceChildren(); containerNode.disabled = true }) const changeHandler = event => { if (event.target.value == "--label--") return; this.workerType.value = null; this.workerCategory.value = event.target.value; const description = event.target[event.target.selectedIndex].dataset.description; this.host.shadowRoot.querySelectorAll('[data-slot="description"]').forEach(el => { el.replaceChildren(description); }) }; containerNode.addEventListener('change', changeHandler); this.gc = ()=>containerNode.removeEventListener('change', changeHandler); containerNode.replaceChildren(); { const optionNode = document.createElement("option"); optionNode.append('Select Category:'); optionNode.value = "--label--"; containerNode.appendChild(optionNode); } for (const category of workerIndex.categories) { const optionNode = document.createElement("option"); optionNode.value = category.id; optionNode.dataset.description = category.description; optionNode.append(category.name); containerNode.appendChild(optionNode); } this.gc = this.workerCategory.subscribe(v=>containerNode.value=v||"--label--") })) } listCategoryWorkers() { const selector = '[data-slot="worker-list"]'; this.gc = this.workerCategory.subscribe(workerCategory => this.host.shadowRoot.querySelectorAll(selector).forEach(containerNode => { const changeHandler = event => { if (event.target.value == "--label--") return; this.workerType.value = event.target.value; const description = event.target[event.target.selectedIndex].dataset.description; this.host.shadowRoot.querySelectorAll('[data-slot="description"]').forEach(el => { el.replaceChildren(description); }) }; containerNode.addEventListener('change', changeHandler); this.gc = () => containerNode.removeEventListener('change', changeHandler); containerNode.disabled = false; containerNode.replaceChildren(); { const optionNode = document.createElement("option"); optionNode.value = "--label--"; optionNode.append('Select Type:'); containerNode.appendChild(optionNode); } const category = this.workerIndex.value.categories.find(o => o.id === workerCategory); for (const block of category.blocks) { const optionNode = document.createElement("option"); optionNode.value = block.id; // optionNode.dataset.path = block.path; optionNode.dataset.description = block.description; optionNode.append(block.name); containerNode.appendChild(optionNode); } this.gc = this.workerType.subscribe(v=>containerNode.value=v||"--label--") // select })); } activateInputPort(selector = `[data-feature="standard-input"]`){ this.gc = this.workerInstance.subscribe(workerInstance=>{ this.gc = workerInstance.input.subscribe(v=>this.host.shadowRoot.querySelectorAll(selector).forEach(el=>el.style.display = v.showPort?'block':'none')) }) return this; } activateOutputPort(selector = `[data-feature="standard-output"]`){ this.gc = this.workerInstance.subscribe(workerInstance=>{ this.gc = workerInstance.output.subscribe(v=>this.host.shadowRoot.querySelectorAll(selector).forEach(el=>el.style.display = v.showPort?'block':'none')) }) return this; } deactivateIO(selector = `[data-feature="standard-io"]`){ this.gc = this.workerInstance.subscribe(workerInstance=>{ const merged = signalMerge({ input: workerInstance.input, output: workerInstance.output }, this.subscriptions); const subscription = merged.subscribe(o=>{ const bothInActive = o.input.showPort===false&&o.output.showPort===false; this.host.shadowRoot.querySelectorAll(selector).forEach(el=>el.style.display = bothInActive?'none':'block'); }) this.subscriptions.push( {type:'signal-merge', id:selector, subscription} ); }) return this; } renderViewParameters(){ this.gc = this.workerInstance.subscribe(workerInstance=>{ // once worker instance becomes available this.renderParameters(workerInstance.parameters); }); return this; } renderParameters(parameters){ // const template = this.getStage().instance.theme.querySelector('template[id=worker-parameters]'); const parametersSlot = this.host.shadowRoot.querySelector('[data-slot=parameters]'); parametersSlot.replaceChildren(); // clear element for (const parameter of parameters) { // console.dir(parameter); if (parameter.type === 'port') { // taken care of continue; } const contentNode = this.getStage().instance.theme.template('worker-parameters'); const typeNode = this.getStage().instance.theme.template(`worker-parameters-${parameter.type}`); if (parameter.type == 'enum') { typeNode.querySelectorAll('[data-slot="option-list"]').forEach(containerNode => { const changeHandler = event => { this.data[parameter.name].value = event.target.value; }; containerNode.addEventListener('change', changeHandler); this.gc = () => containerNode.removeEventListener('change', changeHandler); for( const option of parameter.enumeratedMembers ){ const optionNode = document.createElement("option"); optionNode.value = option.value; optionNode.selected = option.selected; optionNode.append(option.name); containerNode.appendChild(optionNode); } // If the datastore has no value, find the default. const unknownValue = !this.data[parameter.name].value; if (unknownValue) { const selection = parameter.enumeratedMembers.find(o => o.selected); if(selection) this.data[parameter.name].value = selection.value; } // When datastore changes, use the value from the data store this.gc = this.data[parameter.name].subscribe(v=>containerNode.value=v) }); } contentNode.querySelector('[data-slot=type]').appendChild(typeNode); // WorkerSupport binding of bindings const boundElements = contentNode.querySelectorAll('[data-bind]'); for (const boundElement of boundElements) { //NOTE: split const [name, attribute] = boundElement.dataset.bind.split('@'); const value = parameter[name] switch (boundElement.tagName) { case 'INPUT': if(!this.data[parameter.name].value) this.data[parameter.name].value = parameter.defaultValue; // Initialize signal value (upsert) // boundElement.value = this.data[name].value; // Initialize valeu of input element // listen to input element const updateValue = () => this.data[parameter.name].value = boundElement.value; // const updateValue = () => { console.log(`Update ${name} value "${this.data[name].value}" with bound element value "${boundElement.value}"`) } boundElement.addEventListener('input', updateValue); this.gc = () => boundElement.removeEventListener('input', updateValue); this.gc = this.data[parameter.name].subscribe(v => boundElement.value = v); case 'B': ////console.log('B'); default: if(attribute){ const template = boundElement.dataset.bindTemplate; if(template){ //console.warn('TODO: template literals htmlz`` '); const data = {[name]:value}; const result = interpolate(template, data); ////console.log('OOO', {attribute, data, result, template}); boundElement.setAttribute(attribute, result); }else{ boundElement.setAttribute(attribute, value); } }else{ boundElement.innerText = value; } } // switch } // for parametersSlot.appendChild(contentNode); } } // fun render }