{"version":3,"sources":["remote-control.sf.css","remote-control/js/src/PuppetNode/_init.sf"],"names":[],"mappings":"AAAA,8D,CCiEC,iB,CACC,e,CD9DF,kE,CCiEC,U,CACC,U,CACA,e,CACA,6B,CACA,Y,CD9DF,yD,CCiEC,kB,CACC,U,CACA,oB,CACA,c,CD9DF,4D,CCiEC,Q,CD9DD,0D,CCiEC,kC,CACC,8C,CACA,U,CACA,e,CACA,kB,CACA,S,CACA,U,CACA,c,CD9DF,gE,CCgEE,wC,CD7DF,yD,CCiEC,qB,CD9DD,gE,CAAA,8D,CAAA,gE,CCkEE,c,CACC,gB,CACA,iB,CACA,mB,CACA,iB,CACA,sC,CD/DH,wD,CCmEC,iB,CACC,a,CACA,W,CACA,gB,CACA,e,CACA,kB,CACA,e,CDhEF,+D,CCkEE,a,CACC,iB,CACA,O,CACA,iB,CACA,U,CACA,U,CD/DH,uD,CCmEC,W,CACC,c,CACA,gB,CACA,iB,CACA,iB,CACA,iB,CACA,sC,CDhEF,4D,CCkEE,kB,CD/DF,gE,CAAA,gE,CAAA,4D,CAAA,4D,CAAA,6D,CCmEC,e,CDhED,gE,CAAA,gE,CAAA,4D,CCmEC,Y,CDhED,+D,CAAA,gE,CCmEC,iB,CDhED,oE,CAAA,mE,CAAA,qE,CAAA,sE,CCoEE,a,CDjEF,4D,CCqEC,Y,CDlED,iE,CCoEE,e,CACC,e,CACA,W,CACA,U,CACA,kB,CACA,iB,CDjEH,0E,CCmEG,kB,CDhEH,wE,CCiEG,gB","file":"remote-control.sf.css","sourcesContent":["sf-space[blackprint] bpic-bpremote-PuppetNode .node .container {\n  text-align: center;\n  max-width: 400px;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node .section-split {\n  width: 100%;\n  height: 1px;\n  padding-top: 7px;\n  border-bottom: 1px dashed white;\n  display: flex;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node label {\n  margin: 5px 5px 2px 5px;\n  color: white;\n  display: inline-block;\n  font-size: 14px;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node textarea {\n  margin: 0px;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node button {\n  background: rgba(0, 0, 0, 0.5490196078);\n  border: 2px solid rgba(255, 255, 255, 0.4196078431);\n  margin: 3px;\n  padding: 2px 8px;\n  border-radius: 50px;\n  outline: none;\n  color: white;\n  cursor: pointer;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node button:hover {\n  background: rgba(145, 145, 145, 0.5490196078);\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node input {\n  vertical-align: middle;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node .media img, sf-space[blackprint] bpic-bpremote-PuppetNode .node .media video, sf-space[blackprint] bpic-bpremote-PuppetNode .node .media audio {\n  max-width: 100%;\n  max-height: 250px;\n  text-align: center;\n  justify-self: center;\n  position: relative;\n  vertical-align: -webkit-baseline-middle;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node .img {\n  position: relative;\n  margin: 0 auto;\n  width: 250px;\n  min-height: 100px;\n  background: black;\n  border-radius: 10px;\n  overflow: hidden;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node .img .label {\n  font-size: 8px;\n  text-align: center;\n  top: 5px;\n  position: absolute;\n  width: 100%;\n  color: gray;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node img {\n  height: 100%;\n  max-width: 100%;\n  max-height: 250px;\n  text-align: center;\n  visibility: hidden;\n  position: relative;\n  vertical-align: -webkit-baseline-middle;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node img[src] {\n  visibility: visible;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node .text_in, sf-space[blackprint] bpic-bpremote-PuppetNode .node .text_out, sf-space[blackprint] bpic-bpremote-PuppetNode .node .checkbox_in, sf-space[blackprint] bpic-bpremote-PuppetNode .node .dropdown_in, sf-space[blackprint] bpic-bpremote-PuppetNode .node .file_in {\n  text-align: left;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node .checkbox_in, sf-space[blackprint] bpic-bpremote-PuppetNode .node .dropdown_in, sf-space[blackprint] bpic-bpremote-PuppetNode .node .file_in {\n  margin: 5px 0 5px 0;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node .picture_in, sf-space[blackprint] bpic-bpremote-PuppetNode .node .picture_out {\n  margin-bottom: 2px;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node .picture_in label, sf-space[blackprint] bpic-bpremote-PuppetNode .node .picture_out label, sf-space[blackprint] bpic-bpremote-PuppetNode .node .file_out label, sf-space[blackprint] bpic-bpremote-PuppetNode .node .button_in label {\n  display: block;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node .led_out {\n  margin: 0 3px;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node .led_out .led {\n  background: gray;\n  margin: 5px auto;\n  height: 15px;\n  width: 15px;\n  border-radius: 15px;\n  text-align: center;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node .led_out .led.inactive {\n  background: #c93f3f;\n}\nsf-space[blackprint] bpic-bpremote-PuppetNode .node .led_out .led.active {\n  background: green;\n}\n\n/*# sourceMappingURL=remote-control.sf.css.map */","## html\n<div class=\"node {{ type || '' }}\" style=\"transform: translate3d({{ x }}px, {{ y }}px, 0px)\">\n\t<sf-template path=\"Blackprint/nodes/template/routes.sf\"></sf-template>\n\t<sf-template path=\"Blackprint/nodes/template/header.sf\"></sf-template>\n\n\t<div class=\"content\">\n\t\t<div class=\"left-port\">\n\t\t\t<sf-template path=\"Blackprint/nodes/template/input-port.sf\"></sf-template>\n\t\t</div>\n\t\t<div class=\"right-port\">\n\t\t\t<sf-template path=\"Blackprint/nodes/template/output-port.sf\"></sf-template>\n\t\t</div>\n\n\t\t<div class=\"section-split\" style=\"{{ hasInterfaceContent ? '' : 'display: none' }}\"></div>\n\n\t\t<div class=\"container\">\n\t\t\t<div sf-each=\"val in _interfaces\" class=\"{{ val.type }}\" style=\"display: {{ val.inline ? 'inline-block' : 'block' }}\">\n       \t\t{{@exec\n\t\t\t\tif(val.label){\n\t\t\t\t\tif(val.inline)\n\t\t\t\t\t\t{[ <label>{{ val.label }} </label> ]};\n\t\t\t\t\telse\n\t\t\t\t\t\t{[ <label>{{ val.label }}: </label> ]};\n\t\t\t\t}\n\n\t\t\t\tif(val.type === 'text_in' || val.type === 'text_out')\n\t\t\t\t\t{[ <textarea style=\"width: 99%\" placeholder=\"{{ val.placeholder || '' }}\" title=\"{{ val.tooltip || '' }}\"></textarea> ]};\n\t\t\t\telse if(val.type === 'picture_in')\n\t\t\t\t\t{[ <div class=\"img\"><div class=\"label\">{{ val.placeholder || \"Drag and drop picture here...\" }}</div><img title=\"{{ val.tooltip || '' }}\"></img></div> ]};\n\t\t\t\telse if(val.type === 'picture_out')\n\t\t\t\t\t{[ <div class=\"img\"><div class=\"label\">{{ val.placeholder || \"Picture will be displayed here...\" }}</div><img title=\"{{ val.tooltip || '' }}\"></img></div> ]};\n\t\t\t\telse if(val.type === 'media_out')\n\t\t\t\t\t{[ <div class=\"media\">\n\t\t\t\t\t\t<img style=\"display: none\" title=\"{{ val.tooltip || '' }}\"></img>\n\t\t\t\t\t\t<video style=\"display: none\" title=\"{{ val.tooltip || '' }}\" controls></video>\n\t\t\t\t\t\t<audio style=\"display: none\" title=\"{{ val.tooltip || '' }}\" controls></audio>\n\t\t\t\t\t</div> ]};\n\t\t\t\telse if(val.type === 'checkbox_in')\n\t\t\t\t\t{[ <input type=\"checkbox\" title=\"{{ val.tooltip || '' }}\"></input> ]};\n\t\t\t\telse if(val.type === 'led_out')\n\t\t\t\t\t{[ <div class=\"led\" title=\"{{ val.tooltip || '' }}\"></div> ]};\n\t\t\t\telse if(val.type === 'dropdown_in' && val.multiple)\n\t\t\t\t\t{[ <select multiple title=\"{{ val.tooltip || '' }}\"></select> ]};\n\t\t\t\telse if(val.type === 'dropdown_in' && !val.multiple)\n\t\t\t\t\t{[ <select title=\"{{ val.tooltip || '' }}\"></select> ]};\n\t\t\t\telse if(val.type === 'file_in' && val.multiple)\n\t\t\t\t\t{[ <input type=\"file\" multiple title=\"{{ val.tooltip || '' }}\"></input> ]};\n\t\t\t\telse if(val.type === 'file_in' && !val.multiple)\n\t\t\t\t\t{[ <input type=\"file\" title=\"{{ val.tooltip || '' }}\"></input> ]};\n\t\t\t\telse if(val.type === 'file_out')\n\t\t\t\t\t{[ <button title=\"{{ val.tooltip || '' }}\">{{ val.text || 'Download' }}</button> ]};\n\t\t\t\telse if(val.type === 'button_in')\n\t\t\t\t\t{[ <button title=\"{{ val.tooltip || '' }}\">{{ val.text || 'Button' }}</button> ]};\n\t\t\t\telse throw new Error(`Type is not recognized: ${val.type}`);\n\t\t\t}}\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\n\t<sf-template path=\"Blackprint/nodes/template/other.sf\"></sf-template>\n</div>\n\n## scss-global\nsf-space[blackprint] bpic-bpremote-PuppetNode .node {\n\t.container {\n\t\ttext-align: center;\n\t\tmax-width: 400px;\n\t}\n\t.section-split{\n\t\twidth: 100%;\n\t\theight: 1px;\n\t\tpadding-top: 7px;\n\t\tborder-bottom: 1px dashed white;\n\t\tdisplay: flex;\n\t}\n\tlabel {\n\t\tmargin: 5px 5px 2px 5px;\n\t\tcolor: white;\n\t\tdisplay: inline-block;\n\t\tfont-size: 14px;\n\t}\n\ttextarea {\n\t\tmargin: 0px;\n\t}\n\tbutton {\n\t\tbackground: #0000008c;\n\t\tborder: 2px solid #ffffff6b;\n\t\tmargin: 3px;\n\t\tpadding: 2px 8px;\n\t\tborder-radius: 50px;\n\t\toutline: none;\n\t\tcolor: white;\n\t\tcursor: pointer;\n\t\t&:hover{\n\t\t\tbackground: #9191918c;\n\t\t}\n\t}\n\tinput {\n\t\tvertical-align: middle;\n\t}\n\t.media{\n\t\timg, video, audio{\n\t\t\tmax-width: 100%;\n\t\t\tmax-height: 250px;\n\t\t\ttext-align: center;\n\t\t\tjustify-self: center;\n\t\t\tposition: relative;\n\t\t\tvertical-align: -webkit-baseline-middle;\n\t\t}\n\t}\n\t.img{\n\t\tposition: relative;\n\t\tmargin: 0 auto;\n\t\twidth: 250px;\n\t\tmin-height: 100px;\n\t\tbackground: black;\n\t\tborder-radius: 10px;\n\t\toverflow: hidden;\n\t\t.label {\n\t\t\tfont-size: 8px;\n\t\t\ttext-align: center;\n\t\t\ttop: 5px;\n\t\t\tposition: absolute;\n\t\t\twidth: 100%;\n\t\t\tcolor: gray;\n\t\t}\n\t}\n\timg{\n\t\theight: 100%;\n\t\tmax-width: 100%;\n\t\tmax-height: 250px;\n\t\ttext-align: center;\n\t\tvisibility: hidden;\n\t\tposition: relative;\n\t\tvertical-align: -webkit-baseline-middle;\n\t\t&[src]{\n\t\t\tvisibility: visible;\n\t\t}\n\t}\n\t.text_in, .text_out, .checkbox_in, .dropdown_in, .file_in {\n\t\ttext-align: left;\n\t}\n\t.checkbox_in, .dropdown_in, .file_in {\n\t\tmargin: 5px 0 5px 0;\n\t}\n\t.picture_in, .picture_out {\n\t\tmargin-bottom: 2px;\n\t}\n\t.picture_in, .picture_out, .file_out, .button_in {\n\t\tlabel{\n\t\t\tdisplay: block;\n\t\t}\n\t}\n\t.led_out {\n\t\tmargin: 0 3px;\n\t\t.led {\n\t\t\tbackground: gray;\n\t\t\tmargin: 5px auto;\n\t\t\theight: 15px;\n\t\t\twidth: 15px;\n\t\t\tborder-radius: 15px;\n\t\t\ttext-align: center;\n\t\t\t&.inactive { background: #c93f3f; }\n\t\t\t&.active { background: green; }\n\t\t}\n\t}\n}\n\n## js-global\nBlackprint.Sketch.registerInterface(\"BPIC/BPRemote/PuppetNode\", {\n\ttemplate: #this.path,\n}, class extends Blackprint._IPuppetNode {\n\tconstructor(node){\n\t\tsuper(node);\n\t\tthis._interfaces = {};\n\t\tthis.data = {};\n\t\tthis.hasInterfaceContent = false;\n\n\t\t// text_out, text_in, picture_out, picture_in, file_out, file_in, button_in\n\t\tthis.interfaceSync = node.constructor.interfaceSync || [\n\t\t\t/* {type: \"text_out\", id: \"log\", placeholder: \"Output log\", tooltip: \"aaa\"},\n\t\t\t{type: \"text_in\", id: \"input\", placeholder: \"Type text here...\"},\n\t\t\t{type: \"picture_out\", id: \"pic_out\", tooltip: \"Picture preview\"},\n\t\t\t{type: \"picture_in\", id: \"pic_in\", tooltip: \"Picture input\"},\n\t\t\t{type: \"media_out\", id: \"media_out\", tooltip: \"Media preview\", optional: true},\n\t\t\t{type: \"checkbox_in\", id: \"checkbox_in\", tooltip: \"Enable/disable\"},\n\t\t\t{type: \"led_out\", id: \"led_out\", tooltip: \"Status indicator\", inline: true},\n\t\t\t{type: \"dropdown_in\", id: \"dropdown_in\", tooltip: \"List selection\", options: [\n\t\t\t\t{id: 'aa', text: 'AA'},\n\t\t\t\t{id: 'bb', text: 'BB'},\n\t\t\t], multiple: false},\n\t\t\t{type: \"file_in\", id: \"file_in\", tooltip: \"File input\"},\n\t\t\t{type: \"file_out\", id: \"file_out\", tooltip: \"File download\", inline: true},\n\t\t\t{type: \"button_in\", id: \"button_in\", tooltip: \"Button input\", text: 'Button', inline: true},\n\n\t\t\t{type: \"text_in\", port_input: \"StringField\", default: \"...\"},\n\t\t\t{type: \"number_in\", port_input: \"IntegerField\", default: 0},\n\t\t\t{type: \"checkbox_in\", port_input: \"BooleanField\", default: true},\n\t\t\t{type: \"button_in\", port_output: \"TriggerField\"},\n\t\t\t*/\n\t\t];\n\t\tthis._interfaceSync = {};\n\t}\n\tinit(){\n\t\tlet sync = this.interfaceSync;\n\t\tlet _interfaces = this._interfaces;\n\t\tlet optional_out = this.data.optional_out ?? {};\n\n\t\tfor (let i=0; i < sync.length; i++) {\n\t\t\tlet temp = sync[i];\n\t\t\tthis._interfaceSync[temp.id] = temp;\n\n\t\t\tif(temp.id != null) {\n\t\t\t\tsf.Obj.set(_interfaces, temp.id, temp);\n\t\t\t\tthis.hasInterfaceContent = true;\n\n\t\t\t\tif(temp.optional === true) {\n\t\t\t\t\toptional_out = this.data.optional_out ??= {};\n\n\t\t\t\t\tlet enabled = optional_out[temp.id] ??= false;\n\t\t\t\t\tif(enabled) this.node.syncOut(temp.id, {request: true});\n\t\t\t\t\t_interfaces.$el(temp.id).css('display', enabled ? '' : 'none');\n\t\t\t\t}\n\n\t\t\t\tif(temp.type === 'text_in'){\n\t\t\t\t\t_interfaces.$el('textarea', temp.id).on('input', ev => {\n\t\t\t\t\t\tthis.syncOut(temp.id, ev.target.value);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse if(temp.type === 'picture_in'){\n\t\t\t\t\tlet el = _interfaces.$el('.img', temp.id);\n\t\t\t\t\tel.on('dragover', ev => ev.preventDefault());\n\t\t\t\t\tel.on('drop', ev => {\n\t\t\t\t\t\tev.preventDefault();\n\n\t\t\t\t\t\tlet file = ev.dataTransfer.files[0];\n\t\t\t\t\t\tif(!(file instanceof File))\n\t\t\t\t\t\t\tthrow new Error(\"Dropped item in this element was not a file\");\n\n\t\t\t\t\t\tthis._encodeFile(file).then(v => {\n\t\t\t\t\t\t\tthis.syncOut(temp.id, { url: 'data:image;base64,'+btoa(v) });\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tlet FR = new FileReader();\n\t\t\t\t\t\tFR.addEventListener(\"load\", ev => el.find('img')[0].src = ev.target.result);\n\t\t\t\t\t\tFR.readAsDataURL(file);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse if(temp.type === 'checkbox_in'){\n\t\t\t\t\t_interfaces.$el('input', temp.id).on('input', ev => {\n\t\t\t\t\t\tthis.syncOut(temp.id, ev.target.checked);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse if(temp.type === 'dropdown_in'){\n\t\t\t\t\tlet el = _interfaces.$el('select', temp.id);\n\t\t\t\t\tel.on('input', ev => {\n\t\t\t\t\t\tthis.syncOut(temp.id, {selected: [...ev.target.selectedOptions].map(v => v.value)});\n\t\t\t\t\t});\n\n\t\t\t\t\tlet options = temp.options;\n\t\t\t\t\tfor (let i=0; i < options.length; i++) {\n\t\t\t\t\t\tlet item = options[i];\n\t\t\t\t\t\tel.append(`<option value=\"${$.escapeText(item.id)}\">${$.escapeText(item.text)}</option>`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if(temp.type === 'file_in'){\n\t\t\t\t\t_interfaces.$el('input', temp.id).on('input', ev => {\n\t\t\t\t\t\tPromise.all([...ev.target.files].map(v => this._encodeFile(v))).then(v => {\n\t\t\t\t\t\t\tthis.syncOut(temp.id, { data: v });\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse if(temp.type === 'file_out'){\n\t\t\t\t\tlet el = _interfaces.$el('button', temp.id);\n\t\t\t\t\tel.on('click', ev => {\n\t\t\t\t\t\tlet fileData = el[0].fileData;\n\t\t\t\t\t\tif(fileData == null)\n\t\t\t\t\t\t\tthrow new Error(\"No downloadable content\");\n\n\t\t\t\t\t\tlet blob = new Blob([fileData], { type: 'application/octet-stream' });\n\n\t\t\t\t\t\tlet temp = document.createElement('a');\n\t\t\t\t\t\ttemp.href = URL.createObjectURL(blob);\n\t\t\t\t\t\ttemp.download = temp.filename || 'file';\n\t\t\t\t\t\ttemp.style = 'display:none';\n\t\t\t\t\t\tdocument.body.appendChild(temp);\n\t\t\t\t\t\ttemp.target = '_blank';\n\t\t\t\t\t\ttemp.click();\n\t\t\t\t\t\ttemp.remove();\n\n\t\t\t\t\t\tsetTimeout(()=> URL.revokeObjectURL(temp.href), 10000);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse if(temp.type === 'button_in'){\n\t\t\t\t\t_interfaces.$el('button', temp.id)\n\t\t\t\t\t\t.on('pointerdown', ev => {\n\t\t\t\t\t\t\tthis.syncOut(temp.id, {press: true});\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.on('pointerup', ev => {\n\t\t\t\t\t\t\tthis.syncOut(temp.id, {press: false});\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if(temp.port_input != null || temp.port_output != null) {\n\t\t\t\tlet type;\n\t\t\t\tlet componentName;\n\n\t\t\t\tif(temp.type === 'button_in') { type = Function; componentName = 'comp-port-button'; }\n\t\t\t\telse if(temp.type === 'text_in') { type = String; componentName = 'comp-port-textarea'; }\n\t\t\t\telse if(temp.type === 'string_in') { type = String; componentName = 'comp-port-input'; }\n\t\t\t\telse if(temp.type === 'number_in') { type = Number; componentName = 'comp-port-input'; }\n\t\t\t\telse if(temp.type === 'checkbox_in') { type = Boolean; componentName = 'comp-port-input'; }\n\t\t\t\telse throw new Error(`Type \"${temp.type}\" is not supported`);\n\n\t\t\t\tlet port;\n\t\t\t\tif(temp.port_input != null) port = this.input[temp.port_input];\n\t\t\t\telse if(temp.port_output != null) port = this.output[temp.port_output];\n\n\t\t\t\tlet data = {\n\t\t\t\t\tid: temp.port_input || temp.port_output,\n\t\t\t\t\twhich: temp.port_input ? 'input' : 'output',\n\t\t\t\t\tcall: type === Function ? 0 : -1,\n\t\t\t\t};\n\n\t\t\t\tlet item = port._boxInput = {\n\t\t\t\t\tvalue: temp.default,\n\t\t\t\t\tinline: temp.inline,\n\t\t\t\t\tvisible: port.cables.length === 0,\n\t\t\t\t\ttype,\n\t\t\t\t};\n\n\t\t\t\tif(type === Function){\n\t\t\t\t\titem.onClick = ev => {\n\t\t\t\t\t\tdata.call = 1;\n\t\t\t\t\t\tthis.syncOut('bp_port_default', data);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tlet debouncer = ()=> {\n\t\t\t\t\t\tif(type !== Function) data.value = port.default;\n\t\t\t\t\t\tthis.syncOut('bp_port_default', data);\n\t\t\t\t\t};\n\n\t\t\t\t\tlet _debounce;\n\t\t\t\t\titem.whenChanged = (now) => {\n\t\t\t\t\t\tclearTimeout(_debounce);\n\t\t\t\t\t\t_debounce = setTimeout(debouncer, 700);\n\t\t\t\t\t\treturn port.default = now;\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tport.insertComponent(null, componentName, item);\n\t\t\t\tport._hasComponent = false; // reset the flag as this was internal component\n\t\t\t}\n\t\t\telse throw new Error(\"Every 'interfaceSync' require an 'id' or 'port_input' or 'port_output', but one of the interface haven't been specified yet\");\n\t\t}\n\n\t\tthis.on('node.menu', ev => {\n\t\t\tlet switchOptional = id => {\n\t\t\t\tlet enabled = optional_out[id] ??= false;\n\t\t\t\tenabled = optional_out[id] = !enabled;\n\n\t\t\t\tif(enabled) this.syncOut(id, {request: enabled});\n\t\t\t\t_interfaces.$el(id).css('display', enabled ? '' : 'none');\n\t\t\t}\n\n\t\t\tlet deep = [];\n\t\t\tfor (let key in optional_out) {\n\t\t\t\tdeep.push({ title: (optional_out[key] ? 'Disable: ' : 'Enable: ') + key, callback(){ switchOptional(key) } });\n\t\t\t}\n\n\t\t\tif(deep.length !== 0) ev.menu.push({\n\t\t\t\ttitle: \"Remote Preview\",\n\t\t\t\tdeep\n\t\t\t});\n\t\t});\n\t}\n\tasync _encodeFile(file){\n\t\tfile = await file.arrayBuffer();\n\n\t\tlet encoded = '';\n\t\tlet bytes = new Uint8Array(file);\n\n\t\tfor (let i = 0, n = bytes.byteLength; i < n; i++)\n\t\t\tencoded += String.fromCharCode(bytes[i]);\n\n\t\treturn encoded;\n\t}\n\timported(data){\n\t\tif(!data) return;\n\n\t\tlet sync = this._interfaceSync;\n\t\tif(sync) for (let key in sync) {\n\t\t\tlet temp = sync[key];\n\t\t\tlet val = data[key];\n\t\t\tif(temp.save && val != null){\n\t\t\t\tif(temp.type === 'text_in') this._interfaces.$el('textarea', temp.id).val(val); // value\n\t\t\t\tif(temp.type === 'checkbox_in') this._interfaces.$el('input', temp.id).val(val); // checked\n\t\t\t\tif(temp.type === 'dropdown_in') this._interfaces.$el('select', temp.id).val(val); // selectedOptions\n\t\t\t}\n\t\t}\n\t}\n\tsyncIn(id, data){\n\t\tlet item = this._interfaces[id];\n\t\tif(item == null) return; // Skip node data sync from remote\n\n\t\tif(item.type === 'text_out')\n\t\t\tthis._interfaces.$el('textarea', id).val(data);\n\t\telse if(item.type === 'picture_out')\n\t\t\tthis._interfaces.$el('img', id).attr('src', data.url);\n\t\telse if(item.type === 'media_out'){ // ToDo: Optimize performance for code below\n\t\t\tlet img = this._interfaces.$el('img', id);\n\t\t\tlet video = this._interfaces.$el('video', id);\n\t\t\tlet audio = this._interfaces.$el('audio', id);\n\t\t\tURL.revokeObjectURL(img.attr('src'));\n\t\t\tURL.revokeObjectURL(video.attr('src'));\n\t\t\tURL.revokeObjectURL(audio.attr('src'));\n\n\t\t\tlet url = URL.createObjectURL(new Blob([data.data], { type: data.mimeType }));\n\t\t\tif(data.mimeType.startsWith('image/')){\n\t\t\t\timg.attr('src', url).css('display', 'block');\n\t\t\t\tvideo.css('display', 'none');\n\t\t\t\taudio.css('display', 'none');\n\t\t\t}\n\t\t\telse if(data.mimeType.startsWith('video/')){\n\t\t\t\tvideo.attr('src', url).css('display', 'block');\n\t\t\t\timg.css('display', 'none');\n\t\t\t\taudio.css('display', 'none');\n\t\t\t}\n\t\t\telse if(data.mimeType.startsWith('audio/')){\n\t\t\t\taudio.attr('src', url).css('display', 'block');\n\t\t\t\timg.css('display', 'none');\n\t\t\t\tvideo.css('display', 'none');\n\t\t\t}\n\t\t\telse URL.revokeObjectURL(url);\n\t\t}\n\t\telse if(item.type === 'checkbox_in')\n\t\t\tthis._interfaces.$el('input', id)[0].checked = data.active;\n\t\telse if(item.type === 'dropdown_in'){\n\t\t\tif(data.type === 'list'){\n\t\t\t\tlet el = this._interfaces.$el('select', id);\n\t\t\t\tel[0].textContent = '';\n\n\t\t\t\tlet options = data.options;\n\t\t\t\tfor (let i=0; i < options.length; i++) {\n\t\t\t\t\tlet item = options[i];\n\t\t\t\t\tel.append(`<option value=\"${$.escapeText(item.id)}\">${$.escapeText(item.text)}</option>`);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if(data.type === 'selected'){\n\t\t\t\tlet el = this._interfaces.$el('select', id);\n\t\t\t\tlet list = el.find('option');\n\t\t\t\tlet index = -1;\n\n\t\t\t\tfor (let i=0; i < list.length; i++) {\n\t\t\t\t\tif(list[i].value === data.id){\n\t\t\t\t\t\tindex = i;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif(index === -1)\n\t\t\t\t\treturn console.error(`Id \"${data.id}\" was not found in dropdown \"${id}\" list on node \"${this.iface.namespace}\"`);\n\n\t\t\t\tel[0].selectedIndex = index;\n\t\t\t}\n\t\t}\n\t\telse if(item.type === 'led_out'){\n\t\t\tlet div = this._interfaces.$el('div', id);\n\t\t\tdiv.removeClass(data.active ? 'inactive' : 'active');\n\t\t\tdiv.addClass(data.active ? 'active' : 'inactive');\n\t\t}\n\t\telse if(item.type === 'file_out'){\n\t\t\tlet temp = data.data;\n\t\t\tlet bytes = new Uint8Array(temp.length);\n\t\t\tfor (var i = 0; i < temp.length; i++)\n\t\t\t\tbytes[i] = temp.charCodeAt(i);\n\n\t\t\tthis._interfaces.$el('button', id)[0].fileData = bytes;\n\t\t}\n\t}\n\tsyncOut(id, data){\n\t\tlet sync = this._interfaceSync[id];\n\t\tif(sync.save) this.data[id] = data;\n\t\tthis.node.syncOut(id, data);\n\t}\n});"]}