A Pen by Eero Pitkänen on CodePen.
Created
November 10, 2021 20:51
-
-
Save eeropic/78725e753cfd66e03c1c5741fd40feb9 to your computer and use it in GitHub Desktop.
touchosc custom properties
This file contains 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
<!-- https://forum.freecodecamp.org/t/nesting-custom-web-components/326322/4 --> | |
<template id="toscPropertyTemplate"><slot></slot><slot></slot></template> | |
<template id="toscKeyTemplate"></template> | |
<template id="toscValueTemplate"></template> | |
<div id="editor"> | |
</div> | |
<div id="toolbar"> | |
<div id="file-controls"> | |
<div id="drop-zone"> | |
<span id="drop-zone-text">Drop a .TOSC file here</span> | |
<input type="file" id="tosc-file" name="tosc_file" accept="binary/tosc" label="ktekteo"> | |
<label id="tosc-file-label" for="tosc-file">Or click to upload .TOSC</label> | |
</div> | |
<a id="download-link" href="#" target="_blank">Finally, drag this to save as .TOSC</a> | |
</div> | |
<div id="properties-wrapper"> | |
</div> | |
</div> | |
<div id="update-buttons"> | |
<button id="update-xml"><< Update --</button> | |
<button id="update-controls">-- Update >></button> | |
</div> |
This file contains 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
var liveUpdate = true | |
if(liveUpdate){ | |
document.getElementById("update-buttons").style = "display:none;" | |
} | |
const tosc_doc = `<?xml version='1.0' encoding='UTF-8'?><lexml version='2'><node type='GROUP'><properties><property type='s'><key>metaCreator</key><value>Created with an unofficial .tosc MK2 web editor</value></property><property type='s'><key>metaComments</key><value>Use at your own risk! eero.pitkanen@gmail.com</value></property><property type='c'><key>color</key><value><r>0</r><g>0</g><b>0</b><a>1</a></value></property><property type='r'><key>frame</key><value><x>0</x><y>0</y><w>640</w><h>860</h></value></property><property type='b'><key>myBoolean</key><value>1</value></property><property type='f'><key>myFloat</key><value>1.5</value></property><property type='i'><key>myInt</key><value>4</value></property><property type='s'><key>script</key><value>--hidden custom properties:</value></property></properties><values></values><children></children></node></lexml>` | |
const compToHex = (c) => (hex = c.toString(16), hex.length == 1 ? "0" + hex : hex); | |
const rgbToHex = (r, g, b) => "#" + compToHex(r) + compToHex(g) + compToHex(b); | |
const hexToRgb = (hex) => ( | |
res = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex), | |
res ? {r:parseInt(res[1],16),g:parseInt(res[2],16),b:parseInt(res[3],16)} : null | |
); | |
XML = { | |
parse: (string, type = 'text/xml') => new DOMParser().parseFromString(string, type), | |
stringify: DOM => new XMLSerializer().serializeToString(DOM), | |
// (c) https://stackoverflow.com/a/49458964 | |
prettify: (xml,tab) => { | |
let formatted = '', indent= ''; | |
tab = tab || '\t'; | |
xml.split(/>\s*</).forEach(function(node) { | |
if (node.match( /^\/\w/ )) indent = indent.substring(tab.length); // decrease indent by one 'tab' | |
formatted += indent + '<' + node + '>\r\n'; | |
if (node.match( /^<?\w[^>]*[^\/]$/ )) indent += tab; // increase indent | |
}); | |
return formatted.substring(1, formatted.length-3); | |
} | |
} | |
var editor = ace.edit("editor"); | |
editor.setTheme("ace/theme/chrome"); | |
editor.getSession().setMode("ace/mode/xml"); | |
editor.getSession().setValue(XML.prettify(tosc_doc.match(/<properties>[\s\S]*?<\/properties>/)[0])) | |
updateControls(); | |
editor.getSession().on('change', function(e) { | |
if (editor.curOp && editor.curOp.command.name){ | |
if(liveUpdate){ | |
console.log('user change') | |
updateControls() | |
} | |
} | |
else { | |
console.log('other change') | |
} | |
}); | |
var xmlStr; | |
var tosc_filename = "edited.tosc" | |
function onFileLoad(){ | |
var arrayBuffer = this.result | |
var array = new Uint8Array(arrayBuffer) | |
console.log(array) | |
var tosc_xml; | |
var result; | |
try { | |
const result = pako.inflate(array); | |
tosc_xml = String.fromCharCode.apply(null, result); | |
} catch (err) { | |
tosc_xml = String.fromCharCode.apply(null, array); | |
} | |
xmlStr = tosc_xml | |
var tosc_properties = xmlStr.match(/<properties>[\s\S]*?<\/properties>/)[0] | |
tosc_properties = tosc_properties.replace(/\<!\[CDATA\[(\w*)\]\]\>/g,"$1") | |
editor.getSession().setValue(XML.prettify(tosc_properties)) | |
document.getElementById('properties-wrapper').innerHTML=addToscPrefix(tosc_properties) | |
} | |
function dropHandler(ev) { | |
console.log(ev) | |
ev.preventDefault(); | |
if (ev.dataTransfer.items) { | |
var file = ev.dataTransfer.items[0].getAsFile(); | |
tosc_filename = file.name; | |
document.getElementById('drop-zone-text').innerHTML = file.name | |
var reader = new FileReader(); | |
reader.onload = onFileLoad | |
reader.readAsArrayBuffer(file); | |
} | |
} | |
const dragOverEnterHandler = e => { | |
e.preventDefault() | |
e.stopPropagation() | |
} | |
function fileInputHandler(e){ | |
var reader = new FileReader(); | |
reader.onload = onFileLoad | |
reader.readAsArrayBuffer(this.files[0]); | |
} | |
var file_input = document.getElementById("tosc-file") | |
var file_dropzone = document.getElementById("drop-zone") | |
file_dropzone.addEventListener('drop',dropHandler) | |
file_dropzone.addEventListener('dragover',dragOverEnterHandler) | |
file_dropzone.addEventListener('dragenter',dragOverEnterHandler) | |
file_input.addEventListener('change', fileInputHandler) | |
document.getElementById('download-link').addEventListener("dragstart", function (e) { | |
let props = editor.getSession().getValue(); | |
let docstring = tosc_doc.replace(/<properties>[\s\S]*?<\/properties>/,props); | |
var data = btoa(docstring) | |
e.dataTransfer.setData("DownloadURL", `text/xml:${tosc_filename}:data:image/png;base64,${data}`); | |
}); | |
function updateDownload(){ | |
let elem = document.getElementById('download-link') | |
let props = editor.getSession().getValue(); | |
let docstring = tosc_doc.replace(/<properties>[\s\S]*?<\/properties>/,props); | |
elem.download = tosc_filename || "mypatch.tosc" | |
var blob = new Blob([docstring], {type: 'text/xml'}); | |
elem.href = window.URL.createObjectURL(blob); | |
} | |
customElements.define('tosc-property', class extends HTMLElement { | |
constructor(){ | |
super(); | |
const shadow = this.attachShadow({ mode: 'open' }) | |
shadow.appendChild(toscPropertyTemplate.content.cloneNode(true)); | |
const type = this.getAttribute('type') | |
const slot = this.shadowRoot.querySelector('slot'); | |
slot.addEventListener('slotchange', (event) => { | |
const children = event.target.assignedElements(); | |
children.forEach(child => { | |
if (child.slot === 'key') console.log(child.innerHTML) | |
else if (child.slot === 'value') console.log(child.innerHTML) | |
}); | |
}); | |
} | |
}); | |
customElements.define('tosc-key', class extends HTMLElement { | |
constructor(){ | |
super(); | |
const shadow = this.attachShadow({mode: "open"}); | |
const input= document.createElement('input') | |
input.setAttribute('value', this.innerHTML) | |
input.addEventListener('change', function(){ | |
this.getRootNode().host.innerHTML = this.value | |
if(liveUpdate)updateXML(); | |
}) | |
shadow.appendChild(toscKeyTemplate.content.cloneNode(true)); | |
shadow.appendChild(input) | |
} | |
}); | |
toscInputTypes = { | |
b: 'checkbox', | |
i: 'number', | |
f: 'number', | |
s: 'string', | |
c: 'color' | |
} | |
customElements.define('tosc-value', class extends HTMLElement { | |
constructor(){ | |
super(); | |
let type = this.parentNode.getAttribute("type") | |
let value = parseFloat(this.innerHTML) || this.innerHTML | |
const shadow = this.attachShadow({mode: "open"}) | |
// No suitable native input element for rectangles | |
if(type !== 'r'){ | |
let input= document.createElement('input') | |
input.setAttribute('type', toscInputTypes[type]) | |
if(type !== 'b' && type !== 'c') | |
input.setAttribute('value', value) | |
else input.checked = value == 1 ? true : false; | |
if(type == 'i') | |
input.setAttribute('step', 1) | |
if(type == 'c'){ | |
//color | |
let r = parseFloat(this.querySelector('tosc-r').innerHTML) * 255 | |
let g = parseFloat(this.querySelector('tosc-g').innerHTML) * 255 | |
let b = parseFloat(this.querySelector('tosc-b').innerHTML) * 255 | |
let a = parseFloat(this.querySelector('tosc-a').innerHTML) * 255 | |
input.setAttribute('value',rgbToHex(r,g,b)) | |
} | |
// event listeners | |
if(type == 'b'){ | |
input.addEventListener('change', function(e){ | |
this.getRootNode().host.innerHTML = this.checked == true ? 1 : 0; | |
if(liveUpdate)updateXML(); | |
}) | |
} | |
if(type == 'i' || type =='f' || type == 's'){ | |
input.addEventListener('change', function(e){ | |
this.getRootNode().host.innerHTML = this.value | |
if(liveUpdate)updateXML(); | |
}) | |
} | |
if(type == 'c'){ | |
input.addEventListener('change', function(e){ | |
let parent = this.getRootNode().host | |
this.setAttribute('value', this.value) | |
let rgb = hexToRgb(this.value) | |
console.log(this.getRootNode().host.childNodes) | |
parent.getElementsByTagName('tosc-r')[0].innerHTML = rgb.r / 255 | |
parent.getElementsByTagName('tosc-g')[0].innerHTML = rgb.g / 255 | |
parent.getElementsByTagName('tosc-b')[0].innerHTML = rgb.b / 255 | |
if(liveUpdate)updateXML(); | |
}) | |
} | |
shadow.insertBefore(input,shadow.firstChild) | |
} | |
//rectangle | |
else { | |
let inputs = ['x','y','w','h'] | |
inputs.forEach( i => { | |
let input = document.createElement('input') | |
let val = parseFloat(this.querySelector('tosc-'+i).innerHTML) | |
input.setAttribute('id', i) | |
input.setAttribute('type', 'number') | |
input.setAttribute('value', val) | |
input.addEventListener('change',function(e){ | |
let parent = this.getRootNode().host | |
parent.getElementsByTagName('tosc-'+this.id)[0].innerHTML = this.value | |
if(liveUpdate)updateXML(); | |
}) | |
shadow.appendChild(input) | |
}) | |
} | |
shadow.appendChild(toscValueTemplate.content.cloneNode(true)) | |
} | |
}); | |
function removeToscPrefix(str){ | |
return str.replace(/\<tosc-/g,'<') | |
.replace(/\<\/tosc-/g,'</') | |
.replace('<div xmlns="http://www.w3.org/1999/xhtml" class="tosc-properties">','<properties>') | |
.replace('</div>','</properties>') | |
} | |
// i'm not proud of this | |
function addToscPrefix(str){ | |
return str | |
.replace(/<properties>/g,'<div xmlns="http://www.w3.org/1999/xhtml" class="tosc-properties">') | |
.replace(/<property/g,'<tosc-property') | |
.replace(/<key/g,'<tosc-key') | |
.replace(/<value/g,'<tosc-value') | |
.replace(/<r/g,'<tosc-r') | |
.replace(/<g/g,'<tosc-g') | |
.replace(/<b/g,'<tosc-b') | |
.replace(/<a/g,'<tosc-a') | |
.replace(/<x/g,'<tosc-x') | |
.replace(/<y/g,'<tosc-y') | |
.replace(/<w/g,'<tosc-w') | |
.replace(/<h/g,'<tosc-h') | |
.replace(/<\/properties>/g,'</div>') | |
.replace(/<\/property/g,'</tosc-property') | |
.replace(/<\/key/g,'</tosc-key') | |
.replace(/<\/value/g,'</tosc-value') | |
.replace(/<\/r/g,'</tosc-r') | |
.replace(/<\/g/g,'</tosc-g') | |
.replace(/<\/b/g,'</tosc-b') | |
.replace(/<\/a/g,'</tosc-a') | |
.replace(/<\/x/g,'</tosc-x') | |
.replace(/<\/y/g,'</tosc-y') | |
.replace(/<\/w/g,'</tosc-w') | |
.replace(/<\/h/g,'</tosc-h') | |
} | |
function updateXML(){ | |
var s = new XMLSerializer(); | |
var tosc_properties = s.serializeToString(document.querySelector('.tosc-properties')) | |
editor.getSession().setValue(XML.prettify(removeToscPrefix(tosc_properties))) | |
updateDownload(); | |
} | |
function updateControls(){ | |
let tosc_properties = editor.getSession().getValue().replace(/\t/g,''); | |
document.getElementById('properties-wrapper').innerHTML=addToscPrefix(tosc_properties) | |
updateDownload(); | |
} | |
document.getElementById('update-controls').addEventListener('click', function(e){ | |
updateControls(); | |
}) | |
document.getElementById('update-xml').addEventListener('click', function(e){ | |
updateXML(); | |
}) | |
/* | |
var s = new XMLSerializer(); | |
var str = s.serializeToString(document.querySelector('.tosc-properties')) | |
console.log(removeToscPrefix(str)) | |
*/ |
This file contains 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
<script src="https://unpkg.com/pako@2.0.4/dist/pako.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.13/ace.js"></script> |
This file contains 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
:root { | |
font-family: Monaco, monospace; | |
font-size: 12px; | |
} | |
#tosc-file { | |
display: none; | |
} | |
#tosc-file-label { | |
border: 1px solid gray; | |
border-radius: 4px; | |
cursor: pointer; | |
margin-top: 4rem; | |
} | |
#update-buttons { | |
display:flex; | |
flex-direction: column; | |
position:absolute; | |
top: 30%; | |
right: 30rem; | |
width: 8rem; | |
gap: 2rem; | |
} | |
#toolbar { | |
display: flex; | |
flex-direction: column; | |
align-items: top; | |
position: absolute; | |
top: 0; | |
padding: 1rem; | |
gap: 10px; | |
bottom: 0; | |
right: 1rem; | |
border-left: 1px solid gray; | |
} | |
#file-controls { | |
display:flex; | |
flex-direction: column; | |
height: 30%; | |
} | |
#tosc_file { | |
} | |
#drop-zone { | |
text-align: center; | |
border: 1px dashed black; | |
display: flex; | |
flex-direction: column; | |
text-align: center; | |
justify-content: center; | |
height: 50%; | |
margin-bottom: 1rem; | |
padding: 2rem; | |
} | |
#download-link { | |
border: 1px solid black; | |
padding: 4px; | |
} | |
#editor { | |
position: absolute; | |
top: 3rem; | |
right: 35%; | |
bottom: 0; | |
left: 0; | |
} | |
.ace-chrome .ace_cdata {color: darkgray;} | |
.ace-chrome .ace_meta.ace_tag {color: gray;} | |
.ace-chrome span.ace_text.ace_xml {color: blue;} | |
tosc-property { | |
font-family: Monaco, monospace; | |
font-size: 12px; | |
display: flex; | |
margin-bottom: 0.5rem; | |
} | |
tosc-key { | |
font-family: inherit; | |
color: gray; | |
margin-right: 0.5rem; | |
} | |
tosc-value { | |
display:flex; | |
flex-direction: column; | |
font-family: inherit; | |
color: gray; | |
font-size: 0; | |
} | |
.tosc-properties { | |
display: flex; | |
flex-direction: column; | |
} | |
input[type="number"] { | |
max-width: 6rem; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment