going public

This commit is contained in:
jrkb 2023-09-24 18:39:52 +02:00
commit be7d9edf91
84 changed files with 85206 additions and 0 deletions

BIN
bin/data/420px-01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
bin/data/42px-01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,4 @@
{
"backgroundColor": [0.8313, 0.8313, 0.8313, 1.0],
"tmpExportDir": "data/export"
}

View file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 21 21" style="enable-background:new 0 0 21 21;" xml:space="preserve">
<polygon points="12.6,0.3 8.6,0.3 8.6,8.4 0.3,8.4 0.3,12.4 8.6,12.4 8.6,20.6 12.6,20.6 12.6,12.4 20.6,12.4 20.6,8.4 12.6,8.4 "/>
</svg>

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

BIN
bin/web/assets/addlayer.mp4 Normal file

Binary file not shown.

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 172 162" style="enable-background:new 0 0 172 162;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:37.6701,10.4639;}
.st2{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:38.4324,10.6757;}
</style>
<g>
<path d="M101,68.3c7.7,0,14,6.3,14,14v29.9c0,7.7-6.3,14-14,14H71.1c-7.7,0-14-6.3-14-14V82.3c0-7.7,6.3-14,14-14H101 M101,55.3
H71.1c-14.9,0-27,12.1-27,27v29.9c0,14.9,12.1,27,27,27H101c14.9,0,27-12.1,27-27V82.3C128.1,67.4,116,55.3,101,55.3L101,55.3z"/>
</g>
<g>
<rect x="0.1" y="148.8" width="171.9" height="13"/>
</g>
<g>
<rect x="0.1" y="0.7" width="171.9" height="13"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 203 155" style="enable-background:new 0 0 203 155;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:37.6701,10.4639;}
.st2{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:38.4324,10.6757;}
</style>
<g>
<path d="M86,106.8c-7.7,0-14-6.3-14-14V62.9c0-7.7,6.3-14,14-14h29.9c7.7,0,14,6.3,14,14v29.9c0,7.7-6.3,14-14,14H86 M86,119.8
h29.9c14.9,0,27-12.1,27-27V62.9c0-14.9-12.1-27-27-27H86c-14.9,0-27,12.1-27,27v29.9C58.9,107.7,71.1,119.8,86,119.8L86,119.8z"/>
</g>
<g>
<rect x="189.4" y="0.4" width="13" height="155"/>
</g>
<g>
<rect x="-0.2" y="0.4" width="13" height="155"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 172 162" style="enable-background:new 0 0 172 162;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:37.6701,10.4639;}
.st2{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:38.4324,10.6757;}
</style>
<g>
<path d="M100.9,52.2c7.7,0,14,6.3,14,14v29.9c0,7.7-6.3,14-14,14H71c-7.7,0-14-6.3-14-14V66.3c0-7.7,6.3-14,14-14H100.9
M100.9,39.2H71c-14.9,0-27,12.1-27,27v29.9c0,14.9,12.1,27,27,27h29.9c14.9,0,27-12.1,27-27V66.3
C127.9,51.3,115.8,39.2,100.9,39.2L100.9,39.2z"/>
</g>
<g>
<rect x="0" y="0.7" width="171.9" height="13"/>
</g>
<g>
<rect x="0" y="148.8" width="171.9" height="13"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 204 155" style="enable-background:new 0 0 204 155;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:37.6701,10.4639;}
.st2{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:38.4324,10.6757;}
</style>
<g>
<path d="M87.3,48.9c7.7,0,14,6.3,14,14v29.9c0,7.7-6.3,14-14,14H57.4c-7.7,0-14-6.3-14-14V62.9c0-7.7,6.3-14,14-14H87.3 M87.3,35.9
H57.4c-14.9,0-27,12.1-27,27v29.9c0,14.9,12.1,27,27,27h29.9c14.9,0,27-12.1,27-27V62.9C114.3,48,102.2,35.9,87.3,35.9L87.3,35.9z"
/>
</g>
<g>
<rect x="0.8" y="0.4" width="13" height="155"/>
</g>
<g>
<rect x="190.4" y="0.4" width="13" height="155"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 203 155" style="enable-background:new 0 0 203 155;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:37.6701,10.4639;}
.st2{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:38.4324,10.6757;}
</style>
<g>
<path d="M115,106.8c-7.7,0-14-6.3-14-14V62.9c0-7.7,6.3-14,14-14h29.9c7.7,0,14,6.3,14,14v29.9c0,7.7-6.3,14-14,14H115 M115,119.8
h29.9c14.9,0,27-12.1,27-27V62.9c0-14.9-12.1-27-27-27H115c-14.9,0-27,12.1-27,27v29.9C87.9,107.7,100.1,119.8,115,119.8L115,119.8
z"/>
</g>
<g>
<rect x="189.4" y="0.4" width="13" height="155"/>
</g>
<g>
<rect x="-0.2" y="0.4" width="13" height="155"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 168 119" style="enable-background:new 0 0 168 119;" xml:space="preserve">
<g>
<rect x="-0.5" y="0" width="168" height="11"/>
</g>
<g>
<rect x="20.5" y="27" width="126" height="11"/>
</g>
<g>
<rect x="-0.5" y="54" width="168" height="11"/>
</g>
<g>
<rect x="-0.5" y="108" width="168" height="11"/>
</g>
<g>
<rect x="20.5" y="81" width="126" height="11"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 646 B

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 168 119" style="enable-background:new 0 0 168 119;" xml:space="preserve">
<g>
<rect x="-0.5" y="0" width="168" height="11"/>
</g>
<g>
<rect y="27" width="126" height="11"/>
</g>
<g>
<rect y="54" width="168" height="11"/>
</g>
<g>
<rect y="108" width="168" height="11"/>
</g>
<g>
<rect y="81" width="126" height="11"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 610 B

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 168 119" style="enable-background:new 0 0 168 119;" xml:space="preserve">
<g>
<rect y="0" width="168" height="11"/>
</g>
<g>
<rect x="42" y="27" width="126" height="11"/>
</g>
<g>
<rect y="54" width="168" height="11"/>
</g>
<g>
<rect y="108" width="168" height="11"/>
</g>
<g>
<rect x="42" y="81" width="126" height="11"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 615 B

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 172 162" style="enable-background:new 0 0 172 162;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:37.6701,10.4639;}
.st2{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:38.4324,10.6757;}
</style>
<g>
<path d="M101,37.1c7.7,0,14,6.3,14,14V81c0,7.7-6.3,14-14,14H71.1c-7.7,0-14-6.3-14-14V51.1c0-7.7,6.3-14,14-14H101 M101,24.1H71.1
c-14.9,0-27,12.1-27,27V81c0,14.9,12.1,27,27,27H101c14.9,0,27-12.1,27-27V51.1C128,36.2,115.9,24.1,101,24.1L101,24.1z"/>
</g>
<g>
<rect x="0.1" y="0.7" width="171.9" height="13"/>
</g>
<g>
<rect x="0.1" y="148.8" width="171.9" height="13"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 18 18" style="enable-background:new 0 0 18 18;" xml:space="preserve">
<polygon points="17.6,3.3 14.8,0.5 9.1,6.2 3.2,0.3 0.4,3.2 6.3,9 0.4,14.8 3.3,17.7 9.1,11.9 14.7,17.5 17.6,14.7 11.9,9 "/>
</svg>

After

Width:  |  Height:  |  Size: 475 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 28 19" style="enable-background:new 0 0 28 19;" xml:space="preserve">
<path d="M26.9,7.3l-6.2-6.2c-1.2-1.2-3.2-1.2-4.4,0L14,3.4l-2.3-2.3c-0.6-0.6-1.4-0.9-2.2-0.9c-0.8,0-1.6,0.3-2.2,0.9L1.1,7.3
c-1.2,1.2-1.2,3.2,0,4.4l6.2,6.2c0.6,0.6,1.4,0.9,2.2,0.9c0.8,0,1.6-0.3,2.2-0.9l2.3-2.3l2.3,2.3c1.2,1.2,3.2,1.2,4.4,0l6.2-6.2
C28.1,10.5,28.1,8.5,26.9,7.3z M10.3,16.5c-0.2,0.2-0.5,0.3-0.8,0.3s-0.6-0.1-0.8-0.3l-6.2-6.2c-0.2-0.2-0.3-0.5-0.3-0.8
c0-0.3,0.1-0.6,0.3-0.8l6.2-6.2c0.2-0.2,0.5-0.3,0.8-0.3s0.6,0.1,0.8,0.3l2.3,2.3l-2.5,2.5c-1.2,1.2-1.2,3.2,0,4.4l2.5,2.5
L10.3,16.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 61.4 137" style="enable-background:new 0 0 61.4 137;" xml:space="preserve">
<g>
<g>
<path d="M60.7,46.1l-25-43.3C33.5-1,28-1,25.8,2.9l-25,43.3c-2.2,3.8,0.6,8.6,5,8.6h50C60.1,54.7,62.9,49.9,60.7,46.1z"/>
</g>
</g>
<g>
<g>
<path d="M0.8,90.9l25,43.3c2.2,3.8,7.7,3.8,9.9,0l25-43.3c2.2-3.8-0.6-8.6-5-8.6h-50C1.3,82.3-1.4,87,0.8,90.9z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 633 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 61.4 54.7" style="enable-background:new 0 0 61.4 54.7;" xml:space="preserve">
<g>
<g>
<path d="M0.8,8.6l25,43.3c2.2,3.8,7.7,3.8,9.9,0l25-43.3c2.2-3.8-0.6-8.6-5-8.6h-50C1.3,0-1.4,4.8,0.8,8.6z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 490 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 61.4 54.7" style="enable-background:new 0 0 61.4 54.7;" xml:space="preserve">
<g>
<g>
<path d="M60.7,46.1l-25-43.3C33.5-1,28-1,25.8,2.9l-25,43.3c-2.2,3.8,0.6,8.6,5,8.6h50C60.1,54.7,62.9,49.9,60.7,46.1z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 501 B

BIN
bin/web/assets/props.mp4 Normal file

Binary file not shown.

BIN
bin/web/assets/sci_logo.mp4 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 220 153" style="enable-background:new 0 0 220 153;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:37.6701,10.4639;}
.st2{fill:none;stroke:#000000;stroke-width:13;stroke-miterlimit:10;stroke-dasharray:38.4324,10.6757;}
</style>
<g>
<rect y="0" width="178" height="13"/>
<rect y="44" width="138" height="13"/>
<rect y="88" width="178" height="13"/>
<rect y="132" width="138" height="13"/>
<path d="M192,44h-18.3v-7.8L149,50.5l24.7,14.3V57H192c15.7,0,28.5-12.8,28.5-28.5S207.7,0,192,0v13c8.5,0,15.5,7,15.5,15.5
S200.5,44,192,44z"/>
<path d="M192,88v13c8.5,0,15.5,7,15.5,15.5s-7,15.5-15.5,15.5h-18.3v-7.8L149,138.5l24.7,14.3V145H192c15.7,0,28.5-12.8,28.5-28.5
S207.7,88,192,88z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

928
bin/web/css/demo.css Executable file
View file

@ -0,0 +1,928 @@
:root {
--padding: 20px;
--about-w: 30vw;
}
body {
position: fixed;
top: 0px;
left: 0px;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
body:not(.debug) .debug {
display: none;
}
body.debug div:not(.centerLine) {
border: 1px solid green;
}
.hideBody {
display: none;
}
/*
* Note: These fonts are not free and therefore not in this repository.
* If you want to buy them, please visit celinehurka.com, thanks! :-)
*
* */
@font-face {
font-family: "vtVF";
src: url("/web/fonts/vtVF.ttf") format("TrueType");
}
@font-face {
font-family: "VariableIcons";
src: url("/web/fonts/variabletimeicons-Regular.otf") format("OpenType")
}
@font-face {
font-family: "Tonka";
src: url("/web/fonts/TonkaVF.woff2") format("woff2")
}
@font-face {
font-family: "Version-2-var";
src: url("/web/fonts/Version-2-var.ttf") format("TrueType");
font-weight: 100 1000;
font-display: swap;
/*animation: fontWeightAnimation 15s infinite ease-in-out;*/
}
#content {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
opacity: 0.1;
cursor: grab;
}
.textObject {
font-family: "Version-2-var";
font-size: 10em;
font-variation-settings: "wght" 375;
right: 0px;
top: 20px;
position: absolute;
max-width: 50%;
line-height: 1.1em;
z-index: 1;
box-sizing: border-box;
}
.textObject:hover .header {
display: flex;
}
.textObject .header {
display: none;
flex-direction: column;
position: absolute;
top: 0px;
left: 0px;
width: 100%;
padding: 2px;
background-color: blue;
color: white;
font-family: sans-serif;
font-size: 11px;
line-height: 11px;
z-index: 10;
}
.textObject .header .move,
.textObject .header .duplicate,
.textObject .header .delete {
display: flex;
justify-content: center;
align-items: center;
cursor: move;
padding: 4px;
width: 100%;
border: 1px solid red;
}
.textObject .text {
position: absolute;
z-index: 9;
box-sizing: border-box;
}
.textObject:hover .text.original {
border: 1px solid red;
}
.textObject:hover .text.mirror_x,
.textObject:hover .text.mirror_y,
.textObject:hover .text.mirror_xy {
opacity: 0.5;
}
.panel {
font-size: 10em;
right: 0px;
bottom: 10px;
position: absolute;
background-color: transparent;
height: fit-content;
display: flex;
z-index: 1000;
}
.panel .header {
flex-direction: column;
position: absolute;
top: 0px;
left: 0px;
width: 100%;
padding: 2px;
background-color: blue;
display: none;
color: white;
font-family: sans-serif;
font-size: 11px;
line-height: 11px;
}
.panelWrapper:first-child {
order: 0;
}
.panelWrapper {
display: flex;
flex-direction: column;
}
.panel button {
padding: 5px;
box-sizing: border-box;
white-space: nowrap;
border: none;
/* background: white; */
/* color: white; */
font-family: "Tonka";
margin-right: 10px;
text-transform: uppercase;
padding-top: 7px;
width: fit-content;
align-self: flex-end;
margin-top: 10px;
/* color: #ea2333; */
letter-spacing: 0.02em;
}
.panel button:hover {
background-color: #DADADB;
}
.panel .header .move {
display: flex;
cursor: move;
}
#notice {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
z-index: 2000;
display: none;
justify-content: center;
align-items: center;
font-family: "Tonka";
font-variation-settings: 'wght' 500;
font-size: 0.8em;
}
#notice.visible {
display: flex;
}
#notice .content {
width: fit-content;
height: fit-content;
padding: 2em;
color: black;
background-color: white;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
#notice .content .what p {
color: black;
}
#notice .content .details p {
color: black;
}
.exporterChild * {
font-family: "Tonka";
}
.options_title{
font-variation-settings: 'wght' 600 !important;
font-size: 0.9em;
text-align: right;
}
.options_cont{
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 25px;
padding: 10px;
}
.options_cont:not(.options_cont:first-of-type){
border-left: 1px dashed #91919177;
}
.options_cont p{
margin: 0;
height: fit-content;
font-variation-settings: 'wght' 800;
}
.options{
row-gap: 5px;
display: grid;
height: fit-content;
}
.options.adjustable{
row-gap: 0px;
display: grid;
column-gap: 10px;
font-variation-settings: 'wght' 800;
}
.options.adjustable label{
grid-column-start: 1;
white-space: nowrap;
}
.options input:focus{
border-color: transparent;
}
.options input {
font-variation-settings: 'wght' 800;
margin: 3px 0px 8px 0px;
padding: 1px 2px;
font-size: 1.1em;
/* grid-column-start: 2; */
width: fit-content;
height: fit-content;
/* text-align: center; */
border: none;
}
.exporterChild {
background-color: white;
width: 100%;
/* min-height: fit-content; */
/* max-height: 40vh;*/
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: 0px 15px;
display: flex;
transition: 0.5s margin-bottom;
}
.exporterChild:not(.exporterShown .exporterChild){
margin-bottom: -50vh;
}
.exporterShown .exporterChild{
margin-bottom: 0vh;
}
#loader{
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
flex-direction: column;
background-color: rgba(0, 0, 0, 0.5);
}
#exporter{
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
justify-content: flex-end;
align-items: center;
flex-direction: column;
background-color: rgba(0, 0, 0, 0.5);
}
.exporter_options_child{
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
#export_progress_task{
margin: 15px 0px 0px 0px;
}
.loaderChild {
text-align: center;
}
.loaderChild h1 {
font-size: 1em;
font-family: 'Tonka';
}
#exporter {
opacity: 0;
pointer-events: none;
z-index: 1000;
transition: 0.5s opacity;
display: flex;
}
#exporter.exporterShown{
opacity: 1;
pointer-events: all;
}
#loader {
display: flex;
z-index: 20002;
font-family: 'monospace';
background: white;
}
#loader #loader_progress,
#loader #loader_progress_task,
#exporter #export_progress,
#exporter #export_progress_task {
font-family: 'Tonka';
background-color: white;
font-variation-settings: 'wght' 600;
}
#export_progress{
margin-bottom: 40px;
}
.exportButtonsCont button {
margin: 0px;
background-color: rgba(222, 222, 222, 0.97);
border: none;
cursor: pointer;
padding: 7px 15px;
border-radius: 10px;
font-size: 0.9em;
text-transform: uppercase;
font-variation-settings: 'wght' 800;
}
.exporter_options_buttons{
display: flex;
margin: 0 10px;
}
.exportButtonsCont button:not([disabled]):hover{
background: black;
color: white;
}
.exportButtonsCont {
display: grid;
grid-template-rows: 1fr 1fr;
row-gap: 5px;
padding: 0px 5px;
}
#exporter_options {
/* border: 1px solid green;*/
overflow-y: auto;
width: 100%;
background: rgba(247,247,247,1);
border-radius: 10px;
padding: 10px;
box-sizing: border-box;
margin-left: 5px;
}
#exporter_close:hover{
background: black;
color: white;
}
#exporter_close{
padding: calc(var(--padding)/2) calc(var(--padding)/2) calc(var(--padding)/2.5) calc(var(--padding)/2);
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
width: fit-content;
height: fit-content;
font-variation-settings: 'wght' 800;
text-transform: uppercase;
background: rgba(255, 255, 255, 1);
font-size: 0.8em;
cursor: pointer;
grid-column-start: 5;
grid-row-start: 1;
justify-self: flex-end;
font-family: "Tonka";
color: black;
text-decoration: none;
background: rgba(242, 242, 242, 0.97);
}
.exporter_dimension_warning {
background: pink;
padding: 10px;
box-sizing: border-box;
font-variation-settings: 'wght' 500;
font-size: 0.8em;
flex-direction: column;
border-radius: 10px;
/* max-width: 70%; */
grid-column-start: 1;
grid-column-end: 3;
}
.exporter_dimension_warning p{
margin: 0;
}
#exporter_render_info {
display: none;
color: rgb(234, 35, 51);
padding: 4px;
border-radius: 4px;
justify-content: center;
align-items: center;
margin: 0;
font-family: 'Tonka';
font-variation-settings: 'wght' 800;
}
#exporter_render_info p {
max-width: 500px;
}
.artboard_width::before,
.render_width::before {
content: "width: ";
font-variation-settings: 'wght' 500;
}
.artboard_height::before,
.render_height::before {
content: "height: ";
font-variation-settings: 'wght' 500;
}
.render_pixels::before {
content: "total pixel count: ";
font-variation-settings: 'wght' 500;
}
.render_length::before {
content: "length: ";
font-variation-settings: 'wght' 500;
}
.render_length::after {
content: " seconds";
font-variation-settings: 'wght' 500;
}
.artboard_pixelDensity::before {
content: "pixel density: ";
font-variation-settings: 'wght' 500;
}
#artboard_scale_label::before {
content: "render scale (";
font-variation-settings: 'wght' 500;
}
#artboard_scale_label::after {
content: "): ";
font-variation-settings: 'wght' 500;
}
#render_timescale_label::before {
content: "render timestretch (";
font-variation-settings: 'wght' 500;
}
#render_timescale_label::after {
content: "): ";
font-variation-settings: 'wght' 500;
}
#player {
display: none;
position: relative;
max-width: 80%;
max-height: 80%;
}
/*.fontFamilyWrapper{
order: 0;
}
.fontSizeWrapper{
order: 1;
}
.etterSpacingWrapper{
order: 2;
}
.lineHeighWrapper{
order: 3;
}
.textWrapper{
order: 4;
}
.fontVariationAxesContWrapper{
order: 5;
}
.xWrapper{
order: 6;
}
.yWrapper{
order: 7;
}
.alignButtonsHorizontal{
order: 8;
}
.alignButtonsVertical{
order: 9;
}*/
#midiController {
display: none;
position: absolute;
top: 20px;
left: 20px;
width: 50%;
height: 25%;
background: yellow;
z-index: 1001;
}
#midiController .midiMessages {
overflow-y: scroll;
}
#midiController .buttons div {
cursor: pointer;
}
#timeline {
position: fixed;
bottom: 0px;
left: 0px;
width: 100%;
height: 20px;
padding: 0px;
margin: 0px;
background: grey;
z-index: 1002;
}
#timeline_head {
position: absolute;
bottom: 0px;
left: 0px;
width: 20px;
height: 20px;
padding: 0px;
margin: 0px;
background: black;
z-index: 1002;
}
/* ABOUT BEGIN */
.overlay-text-cont.hidden {
margin-left: calc(var(--about-w)*-1);
}
.overlay-text-cont {
display: flex;
position: fixed;
width: var(--about-w);
height: 100vh;
top: 0;
left: 0;
transition: 0.5s margin-left;
margin-left: 0vw;
overflow-y: scroll;
background: rgba(242, 242, 242, 0.97);
padding-bottom: var(--padding);
box-sizing: border-box;
z-index: 20100;
}
.overlay-text-cont::-webkit-scrollbar {
width: 6px;
}
.overlay-text-cont::-webkit-scrollbar-thumb {
background: #c2c2c2;
}
.overlay-text-cont::-webkit-scrollbar-track {
background: #f1f1f1;
}
.overlay-text {
z-index: 1005;
backdrop-filter: blur(3px);
width: 100%;
padding: calc(var(--padding)/2) calc(var(--padding)/2);
font-family: "Tonka";
display: grid;
column-gap: var(--padding);
margin-bottom: var(--padding);
height: fit-content;
}
.about-text {
grid-column-start: 1;
grid-column-end: 5;
font-size: 0.9em;
}
.overlay-text *::selection {
background-color: #91919177;
}
.overlay-text p {
grid-column-start: 1;
font-variation-settings: 'wght' 500;
line-height: 1.2em;
}
.overlay-text a {
font-variation-settings: 'wght' 700;
color: black;
text-decoration: none;
}
.expanded{
overflow: hidden;
height: fit-content;
transition: 0.5s max-height;
/* padding-left: 1em;*/
display: none;
flex-direction: column;
}
.expanded p:last-of-type{
margin-bottom: 1em;
}
.expanded p:first-of-type{
margin-top: 0.5em;
}
.instructions .textParent{
padding-left: 1em;
}
.textParent:not(.textParent:first-of-type){
/*border-top: 1px dashed #91919177;
padding-top: 0.5em;*/
}
.textParent p{
margin-top: 0px;
margin-bottom: 0.5em;
}
.openText .expanded{
display: flex;
}
.aboutParent{
margin-bottom: 1.5em;
}
.expanded video{
max-width: 250px;
min-width: 200px;
align-self: center;
justify-self: end;
display: flex;
margin: 1em;
border: 1px solid lightgrey;
pointer-events: none;
}
.expandText{
border: none;
font-size: 1em;
cursor: pointer;
display: inline;
mix-blend-mode: multiply;
/* padding: calc(var(--padding)/4); */
padding: calc(var(--padding)/6) calc(var(--padding)/6) calc(var(--padding)/6.5) calc(var(--padding)/6);
background: #91919177;
border-radius: 5px;
margin-left: 0.3em;
width: 1em;
height: 1em;
display: inline-flex;
align-items: center;
justify-content: center;
}
.expandText:hover{
background: black;
color: white;
}
*{
font-weight: normal !important;
}
h4{
font-weight: normal;
margin-top: 0px;
margin-bottom: 0.5em;
font-variation-settings: 'wght' 500;
font-size: 1.2em;
border-top: 1px dashed #91919177;
padding-top: 0.5em;
width: 100%;
}
.button-overlay {
padding: calc(var(--padding)/2) calc(var(--padding)/2) calc(var(--padding)/2.5) calc(var(--padding)/2);
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
width: fit-content;
height: fit-content;
font-variation-settings: 'wght' 750;
text-transform: uppercase;
background: rgba(255, 255, 255, 1);
font-size: 0.8em;
cursor: pointer;
grid-column-start: 5;
grid-row-start: 1;
justify-self: flex-end;
font-family: "Tonka";
color: black;
text-decoration: none;
}
.link-in-text {
display: inline;
mix-blend-mode: multiply;
/* padding: calc(var(--padding)/4); */
padding: calc(var(--padding)/7) calc(var(--padding)/7) calc(var(--padding)/7.5) calc(var(--padding)/7);
background: #91919177;
border-radius: 5px;
margin-left: calc(var(--padding)/8);
margin-right: calc(var(--padding)/8);
font-size: 0.7em;
}
.contact_us{
font-size: 1.1em;
}
.links .button-overlay {
margin: 0 calc(var(--padding)/2) calc(var(--padding)/2) 0;
}
.button-overlay.button-close {
position: sticky;
top: var(--padding);
margin: calc(var(--padding)/2) 0 calc(var(--padding)/2) calc(var(--padding)/2);
}
.links {
position: sticky;
bottom: 0;
grid-row-start: 3;
grid-column-start: 5;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.button-overlay:hover {
background: black;
color: white;
}
.vt-title {
font-family: "vtVF";
font-size: 4em;
text-align: left;
line-height: 1em;
margin-top: 0;
margin-bottom: calc(var(--padding)*2);
}
.letter:not(.hidden .letter) {
animation: key 3s infinite;
}
.logos {
grid-column-start: 1;
grid-column-end: 5;
font-size: 0.8em;
margin-top: calc(var(--padding)*2);
border-top: 1px dashed #91919177;
}
.logos img {
max-width: 15%;
max-height: 5vh;
}
@keyframes key {
0% {
font-variation-settings: "wght" 0, "opsz" 0;
}
50% {
font-variation-settings: "wght" 100, "opsz" 0;
}
100% {
font-variation-settings: "wght" 0, "opsz" 0;
}
}
.buttons-bottom {
position: fixed;
bottom: 0;
width: 10vw;
display: flex;
padding: calc(var(--padding)/2);
flex-direction: column;
z-index: 20000;
}
/* these are in theatre-play.js */
/*.main_panel_button{*/
/*color: red !important;*/
/*font-size: 1.15em;*/
/*}*/
#debug_profiling{
display: none;
}
.upload_font_button{
pointer-events: all;
background: white;
width: fit-content;
cursor: pointer;
font-family: 'Tonka';
border-radius: 5px;
padding: 10px;
text-transform: uppercase;
box-sizing: border-box;
}
.upload_font_button:hover{
background: black;
color: white;
}
.upload_font_button_container{
pointer-events: none;
display: flex;
justify-content: center;
align-items: center;
z-index: 1010;
position: fixed;
background: rgba(0, 0, 0, 0.5);
width: 100%;
height: 100%;
margin: 0px;
top: 0px;
left: 0px;
}
/* ABOUT END */

2
bin/web/ffmpeg_modules/ffmpeg.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

178
bin/web/js/artboard.js Normal file
View file

@ -0,0 +1,178 @@
import {
makeEven,
mapValue,
} from './utils.js';
const Artboard = function(tp, domElement = false, autoInit = true) {
//private
let animationFrameId = false;
let width = window.innerWidth;
let height = window.innerHeight;
let x = 0;
let y = 0;
let props = {
backgroundColor: tp.core.types.rgba({
r: 74,
g: 94,
b: 181,
a: 1
}),
x: tp.core.types.number(x),
y: tp.core.types.number(y),
width: tp.core.types.number(width),
height: tp.core.types.number(height),
zoom: tp.core.types.number(1, {
range: [config.artboard.minimumZoom, config.artboard.maximumZoom],
nudgeMultiplier: config.artboard.incrementZoom
}),
pixelDensity: tp.core.types.number(1.0, {
range: [config.artboard.incrementPixelDensity, config.artboard.maximumPixelDensity],
nudgeMultiplier: config.artboard.incrementPixelDensity
}),
};
const onValuesChange = (values) => {
window.isRenderDirty = true;
//window.cancelAnimationFrame(animationFrameId);
//animationFrameId = window.requestAnimationFrame(() => {
//domElement.style.backgroundColor = `${values.backgroundColor.toString()}`;
//});
// backward compatibility
if (values.zoom < config.artboard.minimumZoom) {
values.zoom = config.artboard.minimumZoom;
}
// makes sure that the number
// is both integer and even
//let makeEven = (n) => {
//let nr = Math.round(n);
//return nr - nr % 2;
//}
values.width = makeEven(values.width);
values.height = makeEven(values.height);
// We are absolutely aware that swearwords in code
// can be considered unprofessional bad practice, but
// (smiley) fuck this:
//const nw = makeRight(values.width);
//const nh = makeRight(values.height);
//const np = [];
//if (nw !== values.width) {
//values.width = nw;
//np.push({'key': 'width', 'value': nw});
//}
//if (nh !== values.height) {
//values.height = nh;
//np.push({'key': 'height', 'value': nh});
//}
//if (np.length > 0 && typeof this.theatreObject !== 'undefined') {
//tp.studio.transaction(({
//set
//}) => {
//np.forEach((e) => {
//set(this.theatreObject.props[e.key], e.value);
//});
//});
let cppProps = props2cppProps(values);
Module.setArtboardProps(cppProps);
};
const init = () => {
props.width.default = width;
props.height.default = height;
if (window.devicePixelRatio > 1) {
const pixelDensity = mapValue(window.devicePixelRatio, 1, 2, 1, 1.4);
const zoom = pixelDensity;
props.pixelDensity.default = pixelDensity;
props.zoom.default = zoom;
}
//this.theatreObject = tp.addObject('artboard', this.props, this.onValuesChange);
this.theatreObject = tp.addObject('artboard', this.props, this.onValuesChange);
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props, this.theatreObject.value);
});
//tp.studio.transaction(({ set }) => {
//set(this.theatreObject.props.width, width);
//set(this.theatreObject.props.height, height);
//});
};
const props2cppProps = (_props) => {
let cppProps = JSON.parse(JSON.stringify(_props));
let bgIsArray = Array.isArray(cppProps.backgroundColor);
if (bgIsArray && cppProps.backgroundColor.length === 4) {
// nothing to do
} else if (!bgIsArray && cppProps.backgroundColor.hasOwnProperty('r')) {
cppProps.backgroundColor = [
cppProps.backgroundColor.r,
cppProps.backgroundColor.g,
cppProps.backgroundColor.b,
cppProps.backgroundColor.a
];
} else if (!bgIsArray && cppProps.backgroundColor.default.hasOwnProperty('r')) {
cppProps.backgroundColor = [
cppProps.backgroundColor.default.r,
cppProps.backgroundColor.default.g,
cppProps.backgroundColor.default.b,
cppProps.backgroundColor.default.a
];
} else {
console.error('js::layer::props2cppProps', 'color could not be translated');
}
return cppProps;
};
this.values2cppProps = props2cppProps;
// public
this.props = props;
this.onValuesChange = onValuesChange;
this.init = init;
let panelFinderTimeout = false;
this.findInjectPanel = () => {
let doItAgain = true;
if (tp.studio.selection.length === 0 || (tp.studio.selection.length > 0 && tp.studio.selection[0].address.objectKey !== this.id())) {
// do it again
} else {
const panel = tp.getPanel();
if (panel !== null) {
tp.setPanelClasses();
// adjust label text from unfriendly to friendly
Object.keys(config.artboard.friendlyNames).forEach((unfriendlyName) => {
const friendlyName = config.artboard.friendlyNames[unfriendlyName];
const panelPropTitle = tp.getPanelPropTitle(unfriendlyName);
if (panelPropTitle !== null &&
friendlyName !== '') {
panelPropTitle.innerHTML = friendlyName;
}
});
doItAgain = false;
}
}
if (doItAgain) {
clearTimeout(panelFinderTimeout);
panelFinderTimeout = setTimeout(() => {
this.findInjectPanel();
}, 30);
window.artboardPanelFinderTimeout = panelFinderTimeout;
}
};
this.id = ()=> {
return 'artboard';
};
this.hide = () => {
// nothing to do
};
// action
if (typeof domElement !== 'object') {
console.error('whoops, please pass a domElement to Artboard');
}
if (autoInit) {
this.init();
}
};
export {
Artboard
}

110
bin/web/js/config.js Normal file
View file

@ -0,0 +1,110 @@
const config = {
artboard: {
minimumZoom: 0.01,
maximumZoom: 200,
incrementZoom: 0.01,
maximumPixelDensity: 3.0,
incrementPixelDensity: 0.01,
friendlyNames: {
'backgroundColor': 'Background<br>Color',
'x': 'Position X',
'y': 'Position Y',
'width': 'Artboard Width',
'height': 'Artboard Height',
'zoom': 'Zoom',
'pixelDensity': 'Preview<br>Resolution',
},
},
layer: {
defaultFonts: ['Version-2', 'TonkaVF'],
letterDelayProps: ['fontSize_px', 'letterSpacing', 'color', 'fontVariationAxes'],
autoCreateFirstLayer: true,
defaultTexts: ['text', 'variable time', 'hello world'],
panelOrder: [
'fontFamily',
'textAlignButtonsHorizontal',
'textAlignment',
'width',
'height',
'fontSize_px',
'letterSpacing',
'lineHeight',
'text',
'fontVariationAxes',
'x',
'y',
'alignButtonsHorizontal',
'alignButtonsVertical',
'rotation',
'transformOrigin',
'mirror_x',
'mirror_x_distance',
'mirror_y',
'mirror_y_distance',
'mirror_xy',
'color',
'letterDelays',
],
friendlyNames: {
'fontFamily': 'Font Family',
'textAlignButtonsHorizontal': '',
'textAlignment': 'Text Alignment',
'width': 'Wrapper Width',
'height': '',
'fontSize_px': 'Font Size',
'letterSpacing': 'Letter Spacing',
'lineHeight': 'Line Height',
'text': 'Text',
'fontVariationAxes': 'Variable Axes',
'x': 'Position X',
'y': 'Position Y',
'alignButtonsHorizontal': '',
'alignButtonsVertical': '',
'rotation': 'Rotation',
'transformOrigin': 'Rotation Origin',
'mirror_x': 'Mirror X',
'mirror_x_distance': 'Mirror X Distance',
'mirror_y': 'Mirror Y',
'mirror_y_distance': 'Mirror Y Distance',
'mirror_xy': 'Mirrox XY',
'color': 'Color',
'letterDelays': 'Letter Delays',
},
},
tp: {
addKeyframesTimeout_s: 0.01,
},
projects: {
savePrefix: 'vte_project_'
},
interactor: {
zoomBaseFactor: 0.001,
zoomDynamicMax: 42,
},
midi: {
touchTimeThreshold_s: 0.5,
smoothingMix: 0.1,
},
fs: {
idbfsDir: '/idbfs',
idbfsFontDir: '/idbfs/fonts',
idbfsTmpDir: '/idbfs/tmp',
},
timeline: {
rolloverReset: true,
rolloverThreshold_s: 0.02,
},
autoSave: true,
};
const Config = function() {
const configKeys = Object.keys(config);
for (let c = 0; c < configKeys.length; c++) {
const key = configKeys[c];
this[key] = config[key];
}
};
export {
Config
}

442
bin/web/js/exporter.js Normal file
View file

@ -0,0 +1,442 @@
import {
makeEven,
clone,
} from './utils.js';
const FfmpegExporter = function() {
let isFfmpegLoaded = false;
let isFfmpegAttached = () => {
return document.getElementById("ffmpeg.min.js") !== null;
};
const attachFfmpeg = () => {
if (!isFfmpegAttached()) {
// this does not work
// we refuse solving this, by simply attaching the script
// in the template from the beginning
//console.log("FFmpegExport::attachFfmpeg", "not attached yet, doing it");
var s = document.createElement("script");
s.id = "ffmpeg.min.js";
s.type = "application/javascript";
s.src = "/web/ffmpeg_modules/ffmpeg.min.js";
const mom = document.getElementById('body');
mom.appendChild(s);
} else {
//console.log("FFmpegExport::attachFfmpeg", "already attached");
}
};
const createFfmpeg = () => {
return new Promise((resolve) => {
if (!isFfmpegLoaded) {
attachFfmpeg();
const {
createFFmpeg
} = FFmpeg;
this.ffmpeg = createFFmpeg({
log: false
});
window.ffmpeg = this.ffmpeg;
this.ffmpeg.setLogger(({
type,
message
}) => {
if (typeof message === 'string' && message.toLowerCase().indexOf('error') >= 0) {
if(confirm('Oh, there seems to be an error transcoding the video.\n'
+ 'Please either decrease resolution or render Frames instead of mp4.\n'
+ '\n'
+ 'Should we reload the page to restore from the error? Your project should still be there afterwards. If you\'re a bit paranoid (nobody blames you), then you can also first save your project to a zipfile and then reload the page yourself')) {
window.location.reload();
}
}
console.log("FFmpegExport::renderDiary", type, message);
//type can be one of following:
//info: internal workflow debug messages
//fferr: ffmpeg native stderr output
//ffout: ffmpeg native stdout output
});
let texts = [
"We perceive loading bars differently depending on what information they show us. This one for exampl",
"something I always wanted to tell you, is how beautiful you are. ",
"The more you wait, the more it won't happen. Or maybe it will, I don't know. I'm a computer program.",
"Waiting is the rust of the soul. ",
"Waiting is a sin against both the time still to come and the moments one is currently disregarding. ",
"Things may come to those who wait, but only the things left by those who hustle. ",
"There is no great achievement that is not the result of patient working and waiting. ",
"What we are waiting for is not as important as what happens while we are waiting. Trust the process.",
"The worst part of life is waiting. The best part of life is to have someone worth waiting for. ",
"You are not just waiting in vain. There is a purpose behind every delay. And if there is no purpose,",
];
let text = texts[Math.floor(Math.random(0,texts.length))];
ffmpeg.setProgress(({
ratio
}) => {
const percent = ratio * 100;
//let text = "somthing I always wanted to tell you, is how beautiful you are ";
//let text = "The more you wait, the more it won't happen. Or maybe it will, I don't know. I'm a computer program.";
let innerHTML = "|";
for (let i = 0; i < 100; i++) {
if (i < percent) {
innerHTML += text[i%text.length];
} else {
innerHTML += "-";
}
}
innerHTML += "|";
let progress = document.getElementById("export_progress");
progress.innerHTML = innerHTML;
/*
* ratio is a float number between 0 to 1.
*/
});
const loadFfmpeg = async (ffmpeg) => {
await ffmpeg.load();
// mount ffmpeg in oF
if (FS.readdir("/data").indexOf("export") < 0) {
FS.mkdir("/data/export");
}
if (FS.readdir("/data/export").indexOf("frames") < 0) {
FS.mkdir("/data/export/frames");
}
ffmpeg.coreFS().mkdir("/frames");
FS.mount(FS.filesystems.PROXYFS, {
root: "/frames",
fs: ffmpeg.coreFS()
}, "/data/export/frames");
isFfmpegLoaded = true;
resolve();
};
loadFfmpeg(this.ffmpeg);
} else { // already loaded
resolve();
}
});
};
const transcodeVideo = async (finishedCallback) => {
const ffmpeg = this.ffmpeg;
//const of_framesDir = '/data/export/frames';
const ffmpeg_framesDir = '/frames';
// const frameNames = FS.readdir(of_framesDir).splice(2); // remove '.', '..'
// ffmpeg.FS('mkdir', ffmpeg_framesDir);
// for (let i = 0; i < frameNames.length; i++) {
// const frameBuffer = FS.readFile(of_framesDir + "/" + frameNames[i]);
// ffmpeg.FS('writeFile', ffmpeg_framesDir + "/" + frameNames[i], frameBuffer);
// }
const progress_task = document.getElementById('export_progress_task');
const progress = document.getElementById('export_progress');
progress_task.innerHTML = 'transcoding video';
{
let innerHTML = "|";
for (let i = 0; i < 100; i++) {
innerHTML += "-";
}
innerHTML += "|";
progress.innerHTML = innerHTML;
}
await ffmpeg.run('-framerate', '30', '-pattern_type', 'glob', '-i', `${ffmpeg_framesDir}/*.png`, '-c:v', 'libx264', '-pix_fmt', 'yuv420p', 'output.mp4');
progress_task.innerHTML = 'preparing download';
progress.innerHTML = '|----------------------------------------------------------------------------------------------------|'
const data = ffmpeg.FS('readFile', 'output.mp4');
progress.innerHTML = '|::::::::::------------------------------------------------------------------------------------------|'
const buffy = URL.createObjectURL(new Blob([data.buffer], {
type: 'video/mp4'
}));
progress.innerHTML = '|:::::::::::::::::::::::::---------------------------------------------------------------------------|'
// yeey, let's create a timestamp!
let date = new Date();
const offset = date.getTimezoneOffset();
date = new Date(date.getTime() - (offset * 60 * 1000));
progress.innerHTML = '|:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::---------------------------------------|'
const timestamp = date.toISOString();
// phew.. alright, it's not pretty but we did it
const filename = tp.sheet.project.address.projectId + "_" + timestamp + ".mp4";
// now: downloading!
let link = document.createElement("a");
link.href = buffy;
link.download = filename;
link.click();
progress.innerHTML = '|::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::|'
setTimeout(() => {
progress_task.innerHTML = 'idle';
progress.innerHTML = '|----------------------------------------------------------------------------------------------------|'
finishedCallback();
}, 500);
// no video
//const video = document.getElementById('player');
//video.src = URL.createObjectURL(new Blob([data.buffer], {
//type: 'video/mp4'
//}));
//video.style.display = 'flex';
};
// public
this.init = createFfmpeg;
this.transcodeVideo = transcodeVideo;
}
const Exporter = function() {
const exporterDom = document.getElementById('exporter');
const exporterDomChild = document.querySelector('.exporterChild');
const exporterOptionsDom = document.getElementById('exporter_options');
let can_export_mp4 = true;
const options = {
artboard: {
width: 0,
height: 0,
pixelDensity: 1,
userScale: 1,
},
};
const renderDimensions = {
width: 0,
height: 0,
timeScale: 1,
};
const updateArtboardOptions = () => {
options.artboard = {...options.artboard, ...Module.getArtboardProps()};
//options.artboard.width = getArtboard().theatreObject.value.width;
//options.artboard.height = getArtboard().theatreObject.value.height;
options.artboard.pixelDensity = getArtboard().theatreObject.value.pixelDensity;
[...exporterDom.querySelectorAll('.artboard_width')].forEach((e) => {
e.innerHTML = options.artboard.width;
});
[...exporterDom.querySelectorAll('.artboard_height')].forEach((e) => {
e.innerHTML = options.artboard.height;
});
[...exporterDom.querySelectorAll('.artboard_pixelDensity')].forEach((e) => {
e.innerHTML = options.artboard.pixelDensity;
e.remove(); // NOTE: as we ignore pixel density for the export, it's not necessary to show it here
});
};
const setArtboardPropsToRenderDimensions = () => {
const artboardValues = clone(options.artboard);//{...options.artboard, ...renderDimensions};
const densityRatio = renderDimensions.width / options.artboard.width;
//artboardValues.pixelDensity *= densityRatio;
artboardValues.pixelDensity = densityRatio;
const artboardCppProps = getArtboard().values2cppProps(artboardValues);
const currentArtboardValues = Module.getArtboardProps();
if (currentArtboardValues.width !== artboardCppProps.width
|| currentArtboardValues.height !== artboardCppProps.height
|| currentArtboardValues.pixelDensity !== artboardCppProps.pixelDensity) {
window.isRenderDirty = true;
Module.setArtboardProps(artboardCppProps);
}
Module.setTimeScale(renderDimensions.timeScale);
};
const resetArtboardProps = () => {
//const artboardCppProps = getArtboard().values2cppProps(options.artboard);
const artboardValues = getArtboard().theatreObject.value;
const artboardCppProps = getArtboard().values2cppProps(artboardValues);
Module.setArtboardProps(artboardCppProps);
Module.setTimeScale(1.0);
};
const updateRenderDimensions = () => {
const currentDimensions = {
width: options.artboard.width * options.artboard.pixelDensity,
height: options.artboard.height * options.artboard.pixelDensity,
};
const artboardUserScaleLabelDom = document.getElementById('artboard_scale_label');
artboardUserScaleLabelDom.innerHTML = options.artboard.userScale;
const timeScaleLabelDom = document.getElementById('render_timescale_label');
timeScaleLabelDom.innerHTML = renderDimensions.timeScale;
const timelineLength_seconds = window.tp.core.val(window.tp.sheet.sequence.pointer.length);
renderDimensions.width = makeEven(currentDimensions.width * options.artboard.userScale * (1.0 / options.artboard.pixelDensity));
renderDimensions.height = makeEven(currentDimensions.height * options.artboard.userScale * (1.0 / options.artboard.pixelDensity));
[...exporterDom.querySelectorAll('.render_width')].forEach((e) => {
e.innerHTML = renderDimensions.width;
});
[...exporterDom.querySelectorAll('.render_height')].forEach((e) => {
e.innerHTML = renderDimensions.height;
});
[...exporterDom.querySelectorAll('.render_pixels')].forEach((e) => {
// 12345678 => 12.345.678
function addDots(nStr) {
nStr += '';
let x = nStr.split('.');
let x1 = x[0];
let x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + '.' + '$2'); // changed comma to dot here
}
return x1 + x2;
}
e.innerHTML = addDots(`${renderDimensions.width * renderDimensions.height}`);
});
[...exporterDom.querySelectorAll('.render_length')].forEach((e) => {
e.innerHTML = (timelineLength_seconds / renderDimensions.timeScale).toFixed(2);
});
if (renderDimensions.width * renderDimensions.height > 1920 * 1080) {
exporterDom.querySelector('.exporter_dimension_warning').style.display = 'flex';
exporterDom.querySelector('#exporter_button_mp4').disabled = true;
can_export_mp4 = false;
} else {
exporterDom.querySelector('.exporter_dimension_warning').style.display = 'none';
exporterDom.querySelector('#exporter_button_mp4').disabled = false;
can_export_mp4 = true;
}
};
const registerEvents = () => {
const close_button = document.getElementById('exporter_close');
close_button.addEventListener("click", this.close);
const open_button = document.getElementById('exporter_open');
open_button.addEventListener("click", this.open);
const artboardUserScale_input = document.getElementById('artboard_scale');
artboardUserScale_input.addEventListener('change', (event) => {
options.artboard.userScale = event.target.value;
updateRenderDimensions();
});
artboardUserScale_input.addEventListener('input', (event) => {
options.artboard.userScale = event.target.value;
updateRenderDimensions();
});
const timeScale_input = document.getElementById('render_timescale');
timeScale_input.addEventListener('change', (event) => {
renderDimensions.timeScale = event.target.value;
updateRenderDimensions();
});
timeScale_input.addEventListener('input', (event) => {
renderDimensions.timeScale = event.target.value;
updateRenderDimensions();
});
};
window.isRenderDirty = true;
const cancel = (e) => {
e.stopPropagation();
if(confirm("Closing the export window during an active export will cancel the rendering process and reload the page. Is that okay for you?")) {
window.location.reload();
}
};
const resetAfter = () => {
exporterDom.querySelector('#exporter_button_mp4').disabled = !can_export_mp4;
exporterDom.querySelector('#exporter_button_zip').disabled = false;
const close_button = document.getElementById('exporter_close');
close_button.removeEventListener("click", cancel);
close_button.addEventListener("click", this.close);
exporterDom.querySelector('#exporter_render_info').style.display = 'none';
const progress_task = document.getElementById('export_progress_task');
const progress = document.getElementById('export_progress');
progress_task.innerHTML = 'idle';
progress.innerHTML = '|----------------------------------------------------------------------------------------------------|'
};
this.renderFrames = (exportType) => {
exporterDom.querySelector('#exporter_button_mp4').disabled = true;
exporterDom.querySelector('#exporter_button_zip').disabled = true;
const close_button = document.getElementById('exporter_close');
close_button.addEventListener("click", cancel);
close_button.removeEventListener("click", this.close);
exporterDom.querySelector('#exporter_render_info').style.display = 'flex';
setArtboardPropsToRenderDimensions();
if (window.isRenderDirty) {
tp.sheet.sequence.pause();
if (exportType === 'zip') {
window.renderDone = () => {
window.isRenderDirty = false;
const projectName = tp.sheet.project.address.projectId;
Module.exportFramesAsZip(projectName);
// progress is being set in separate cpp thread
// so we reset some things in ofApp.h -> ZipSaver
resetAfter();
};
} else if (exportType === 'mp4') {
window.renderDone = () => {
window.isRenderDirty = false;
this.ffmpegExporter
.init()
.then(() => {
this.ffmpegExporter
.transcodeVideo(resetAfter);
});
};
} else {
window.renderDone = () => {
console.log('rendering done! now.. what?');
window.isRenderDirty = false;
};
}
Module.setRendering(true);
} else {
if (exportType === 'zip') {
const projectName = tp.sheet.project.address.projectId;
Module.exportFramesAsZip(projectName);
// progress is being set in separate cpp thread
// so we reset some things in ofApp.h -> ZipSaver
resetAfter();
} else if (exportType === 'mp4') {
this.ffmpegExporter
.init()
.then(() => {
this.ffmpegExporter
.transcodeVideo(resetAfter);
});
} else {
console.log('now.. what?');
}
}
};
let isInitialized = false;
this.init = () => {
return new Promise((resolve) => {
if (!isInitialized) {
registerEvents();
this.ffmpegExporter
.init()
.then(() => {
resolve();
});
} else {
resolve();
}
});
};
this.ffmpegExporter = new FfmpegExporter();
this.open = () => {
updateArtboardOptions();
updateRenderDimensions();
const renderWidthDom = exporterDom.querySelector('.render_width');
const renderHeightDom = exporterDom.querySelector('.render_height');
// exporterDom.style.display = 'flex';
exporterDom.classList.add('exporterShown');
// exporterDomChild.style.marginBottom = '0vh';
};
this.close = () => {
// exporterDom.style.display = 'none';
exporterDom.classList.remove('exporterShown');
// exporterDomChild.style.marginBottom = '-50vh';
resetArtboardProps();
};
// action
//init();
};
export {
Exporter
}

108
bin/web/js/interactor.js Normal file
View file

@ -0,0 +1,108 @@
'use strict'
//import {
//config
//} from './config.js'
const Interactor = function() {
// private
let artboard;
let canvas;
let content;
let mouse = {};
const resetMouse = () => {
mouse.isDown = false;
mouse.isDragging = false;
content.style.cursor = 'grab';
}
const moveArtboard = (x, y, relative = true) => {
tp.studio.transaction(({
set
}) => {
if (relative) {
set(artboard.theatreObject.props.x, artboard.theatreObject.value.x + x);
set(artboard.theatreObject.props.y, artboard.theatreObject.value.y + y);
} else {
set(artboard.theatreObject.props.x, x);
set(artboard.theatreObject.props.y, y);
}
});
};
const zoomArtboard = (zoom) => {
zoom = Math.max(config.artboard.minimumZoom, zoom);
zoom = Math.min(config.artboard.maximumZoom, zoom);
tp.studio.transaction(({
set
}) => {
set(artboard.theatreObject.props.zoom, zoom);
});
};
const registerEvents = () => {
content.addEventListener('mousedown', (event) => {
event.preventDefault();
event.cancelBubble = true;
mouse.isDown = true;
});
// the mouse might move out of the content while moving it
// also, we don't capture the mouse. you will have to simply
// drag multiple times if you want to drag very far
document.addEventListener('mousemove', (event) => {
if (mouse.isDown) {
mouse.isDragging = true;
content.style.cursor = 'grabbing';
// for some reason we need to multiply the mouve movement by 0.5
// no idea why. but this is consistent across browsers, so we might
// just leave this here and investigate whenever we have a problem
const factor = 0.5;
moveArtboard(event.movementX * factor, event.movementY * factor);
event.preventDefault();
event.cancelBubble = true;
}
});
document.addEventListener('mouseup', (event) => {
if (mouse.isDown) {
event.preventDefault();
event.cancelBubble = true;
resetMouse();
}
});
content.addEventListener('wheel', (event) => {
const currentZoom = artboard.theatreObject.value.zoom;
const zoomFactor = config.interactor.zoomBaseFactor
* Math.min(config.interactor.zoomDynamicMax, currentZoom);
let zoom = currentZoom + (event.deltaY * zoomFactor);
zoom = Math.min(config.artboard.maximumZoom, Math.max(config.artboard.minimumZoom, zoom));
zoomArtboard(zoom);
const dpiRatio = 1.0; //window.devicePixelRatio ? window.devicePixelRatio : 1.0;
const currentX = artboard.theatreObject.value.x * dpiRatio;
const currentY = artboard.theatreObject.value.y * dpiRatio;
const currentW = artboard.theatreObject.value.width * dpiRatio;
const currentH = artboard.theatreObject.value.height * dpiRatio;
const relativeX = (((currentX * -1) + (event.clientX * dpiRatio)) / currentZoom) / currentW;
const newX = -1 * ((relativeX * currentW * zoom) - event.clientX * dpiRatio);
const relativeY = (((currentY * -1) + (event.clientY * dpiRatio)) / currentZoom) / currentH;
const newY = -1 * ((relativeY * currentH * zoom) - event.clientY * dpiRatio);
const x = newX;
const y = newY;
moveArtboard(x, y, false);
});
};
// public
this.init = () => {
artboard = window.getArtboard();
canvas = document.querySelector('canvas.emscripten');
content = document.querySelector('#content');
resetMouse();
registerEvents();
};
};
export {
Interactor
};

2
bin/web/js/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1084
bin/web/js/layer.js Normal file

File diff suppressed because it is too large Load diff

91
bin/web/js/layerOrder.js Normal file
View file

@ -0,0 +1,91 @@
const LayerOrder = function() {
const layerIDs = [];
let mom= false;
let updateDom = (from, to) => {
const layerID = layerIDs[from];
const domLayerSelector = `.layerMover${layerID}`;
const domLayer = tp.shadowRoot.querySelector(domLayerSelector);
const otherLayerID = layerIDs[to];
const otherDomLayerSelector = `.layerMover${otherLayerID}`;
const otherDomLayer = tp.shadowRoot.querySelector(otherDomLayerSelector);
if (domLayer === null || otherDomLayer == null) {
return false;
}
if (mom === false) {
mom = domLayer.parentNode;
}
if (mom !== otherDomLayer.parentNode) {
return false;
}
if (from > to) {
mom.insertBefore(domLayer, otherDomLayer);
} else {
mom.insertBefore(otherDomLayer, domLayer);
}
return true;
};
const updateOf = () => {
const layers = JSON.parse(JSON.stringify(layerIDs));
layers.reverse();
const v = new Module.vector$string$();
for (let i = 0; i < layers.length; i++) {
v.push_back(layers[i]);
}
Module.setLayerOrder(v);
};
this.add = (id) => {
layerIDs.push(id);
updateOf();
};
this.remove = (id) => {
const i = layerIDs.indexOf(id);
layerIDs.splice(i, 1);
updateOf();
};
this.move = (from, to) => {
if (updateDom(from, to)) {
// remove `from` item and store it
var l = layerIDs.splice(from, 1)[0];
// insert stored item into position `to`
layerIDs.splice(to, 0, l);
updateOf();
} else {
console.error('js::LayerOrder::move failed');
}
};
this.moveDown = (id) => {
let i = layerIDs.indexOf(id);
const nextI = i + 1;
if (nextI >= layerIDs.length) {
console.log('LayerOrder::moveDown', 'cannot move further down');
} else {
this.move(i, nextI);
}
};
this.moveUp = (id) => {
let i = layerIDs.indexOf(id);
const nextI = i - 1;
if (nextI < 0) {
console.log('LayerOrder::moveUp', 'cannot move further up');
} else {
this.move(i, nextI);
}
};
this.get = () => {
return layerIDs;
};
this.set = (newLayerIDs) => {
window.newLayerIDs = newLayerIDs;
window.layerIDs = layerIDs;
newLayerIDs.forEach((id, to_i) => {
//console.log(`id: ${id}, to_i: ${to_i}, layerIDs[0]: ${layerIDs[0]}`);
const from_i = layerIDs.indexOf(id);
//console.log({id, from_i, to_i});
this.move(from_i, to_i);
});
};
};
export {
LayerOrder
};

516
bin/web/js/main.js Normal file
View file

@ -0,0 +1,516 @@
import {
TheatrePlay
} from './theatre-play.js';
import {
Layer
} from './layer.js';
import {
Artboard
} from './artboard.js';
import {
LayerOrder
} from './layerOrder.js';
import {
Exporter
} from './exporter.js';
import {
Interactor
} from './interactor.js';
import {
ModuleFS
} from './moduleFS.js';
//import {
//MidiController
//} from './midiController.js';
import {
makeDraggable,
getBaseName,
uploadFile,
downloadFile,
hashFromString,
clone,
} from './utils.js';
import {
Config
} from './config.js';
window.uploadFile = uploadFile;
window.downloadFile = downloadFile;
window.isInitialized = false;
window.hashFromString = hashFromString;
const config = new Config();
window.config = config;
const tp = new TheatrePlay();
window.tp = tp;
const layers = [];
const layersById = {};
const layerOrder = new LayerOrder();
window.layerOrder = layerOrder;
const fontsAndAxes = [];
let artboard;
const exporter = new Exporter();
const interactor = new Interactor();
const moduleFS = new ModuleFS();
window.moduleFS = moduleFS;
window.panelFinderTimeout = false;
const sequenceEventBuffer = {};
//const midiController = new MidiController();
//window.midiController = midiController;
let about = null;
const getAbout = () => {
if (about === null) {
about = document.querySelector('#overlay-text-cont');
// should we succeed in getting about,
// attach events. this happens then exactly once
if (about !== null) {
const textParents = getAbout().querySelectorAll(".textParent");
textParents.forEach((textParent) => {
const buttonExp = textParent.querySelector(".expandText");
if (buttonExp === null) {
console.error("Could not find .expandText within .textParent");
return;
}
buttonExp.addEventListener("click", () => {
textParent.classList.toggle("openText");
if (textParent.classList.contains("openText")) {
buttonExp.textContent = "-";
} else {
buttonExp.textContent = "+";
}
});
});
}
}
return about;
};
window.showAbout = () => {
if (getAbout() === null) {
return;
}
getAbout().classList.remove("hidden");
}
window.hideAbout = () => {
if (getAbout() !== null) {
getAbout().classList.add("hidden");
}
}
let theatrePanel = null;
const getPanel = () => {
if (theatrePanel === null) {
theatrePanel = tp.shadowRoot.querySelector('[data-testid="DetailPanel-Object"]');
}
return theatrePanel;
};
const findInjectPanel = () => {
const panel = getPanel();
if (panel !== null) {
let bottomButtonsContainer = panel.querySelector('.bottomButtonsContainer');
if (bottomButtonsContainer === null) {
bottomButtonsContainer = document.createElement('div');
bottomButtonsContainer.classList.add("bottomButtonsContainer");
panel.append(bottomButtonsContainer);
}
const exportButton = document.querySelector('#exporter_open');
if (exportButton !== null) {
bottomButtonsContainer.append(exportButton);
exportButton.classList.add("main_panel_button");
}
const saveButton = document.querySelector('#save_project');
if (saveButton !== null) {
bottomButtonsContainer.append(saveButton);
saveButton.classList.add("main_panel_button");
}
const openButton = document.querySelector('#open_project');
if (openButton !== null) {
bottomButtonsContainer.append(openButton);
openButton.classList.add("main_panel_button");
}
const profilingButton = document.querySelector('#debug_profiling');
if (profilingButton !== null) {
bottomButtonsContainer.append(profilingButton);
profilingButton.classList.add("main_panel_button");
}
const startNewButton = document.querySelector('#start_new_project');
if (startNewButton !== null) {
bottomButtonsContainer.append(startNewButton);
startNewButton.classList.add("main_panel_button");
}
} else {
setTimeout(() => {
findInjectPanel();
}, 100);
}
};
window.onload = () => {
window.addEventListener('panelEvent', (e) => {
clearTimeout(window.panelFinderTimeout);
let target = false;
if (e.detail.panelID === 'artboard') {
target = artboard;
} else if (layersById.hasOwnProperty(e.detail.panelID)) {
target = layersById[e.detail.panelID];
}
if (target !== false &&
typeof target !== 'undefined' &&
target.hasOwnProperty('receivePanelEvent')) {
target.receivePanelEvent(e);
}
});
window.addEventListener('sequenceEvent', (e) => {
let target = false;
if (e.detail.panelID === 'artboard') {
target = artboard;
} else {
target = layersById[e.detail.panelID];
}
if (target !== false &&
typeof target !== 'undefined' &&
target.hasOwnProperty('receiveSequenceEvent')) {
target.receiveSequenceEvent(e.detail);
} else {
// whoops, no layers there yet
// let's put this stuff in a buffer and forward it later
if (!sequenceEventBuffer.hasOwnProperty(e.detail.panelID)) {
sequenceEventBuffer[e.detail.panelID] = [];
}
sequenceEventBuffer[e.detail.panelID].push(e.detail);
}
tp.friendlySequenceNames();
});
window.setLoadingTask('setting up animation', 0);
tp.init()
.then(() => {
const content = document.querySelector('#content');
if (window.moduleInitialized) {
postModuleInitialized();
} else {
window.addEventListener('initializedModule', postModuleInitialized);
}
tp.studio.onSelectionChange((newSelection) => {
if (newSelection.length > 0) {
[getArtboard(), getLayers()].flat().forEach((e) => {
if (e.id() === newSelection[0].address.objectKey) {
if (e.id().indexOf('layer-') === 0) {
e.findInjectPanel();
e.showBoundingBoxDiv();
setTimeout(() => {
e.hideBoundingBoxDiv();
}, 60);
} else if (e.id() === 'artboard') {
e.findInjectPanel();
}
}
});
}
findInjectPanel();
tp.friendlySequenceNames();
});
});
// ABOUT BEGIN
var lettersAndLinks = document.querySelectorAll(".vt-title");
lettersAndLinks.forEach(function(element) {
element.innerHTML = element.innerHTML.replace(/\w+/g, '<span class="word">$&</span>');
element.innerHTML = element.innerHTML.replace(/\*/g, '<span class="word">$&</span>');
});
var words = document.querySelectorAll(".word");
words.forEach(function(element) {
element.innerHTML = element.innerHTML.replace(/\S/g, '<span class="letter">$&</span>');
});
var letterElements = document.querySelectorAll(".letter");
letterElements.forEach(function(element, index) {
element.style.animationDelay = (index * -0.3) + "s";
});
// ABOUT END
}
const adjustPanel = () => {
const VTTitle = tp.shadowRoot.querySelector(`.layerMovervariable-time`);
if (VTTitle !== null) {
var titleText = VTTitle.querySelectorAll("span");
titleText.forEach((element, index) => {
if (element.innerHTML == "variable-time") {
element.innerHTML = "vt*";
element.classList.add("vtTitle");
element.innerHTML = element.innerHTML.replace(/\w+/g, '<span class="word">$&</span>');
const wordElements = tp.shadowRoot.querySelectorAll(".word");
wordElements.forEach(word => {
word.innerHTML = word.innerHTML.replace(/\S/g, '<span class="letter">$&</span>');
element.innerHTML = element.innerHTML.replace('*', '<span class="letter">$&</span>');
});
const letterElements = tp.shadowRoot.querySelectorAll(".letter");
letterElements.forEach(letter => {
letter.style.fontVariationSettings = "'wght' " + Math.floor(Math.random() * (100 - 0 + 1) + 0) + ", 'wdth'" + Math.floor(Math.random() * (100 - 0 + 1) + 0) + ", 'opsz'" + Math.floor(Math.random() * (10 - 0 + 1) + 0);
});
element.addEventListener("mouseover", function() {
letterElements.forEach(letter => {
letter.style.fontVariationSettings = "'wght' " + Math.floor(Math.random() * (100 - 0 + 1) + 0) + ", 'wdth'" + Math.floor(Math.random() * (100 - 0 + 1) + 0) + ", 'opsz'" + Math.floor(Math.random() * (10 - 0 + 1) + 0);
});
});
}
})
}
}
const resize = () => {
let width = document.body.clientWidth;
let height = document.body.clientHeight;
let ratio = window.devicePixelRatio ? window.devicePixelRatio : 1;
Module.canvas.setAttribute('width', width * ratio);
Module.canvas.setAttribute('height', height * ratio);
Module.canvas.style.width = `${width}px}`;
Module.canvas.style.height = `${height}px}`;
Module.windowResized(Math.round(width * ratio), Math.round(height * ratio));
};
const postModuleInitialized = () => {
window.setLoadingTask('setting up animation', 80);
moduleFS.init()
.then(() => {
artboard = new Artboard(tp, content);
initPanels();
// NOTE: we know that our TheatrePlay is initialized
tp.connectModuleCallbacks();
exporter.init();
getFontsAndAxes();
tp.loadProject().then(() => {
interactor.init();
resize();
adjustPanel();
window.setLoadingTask('setting up animation', 100);
window.isInitialized = true;
window.setLoadingDone();
window.autoSaveInterval = setInterval(() => {
if (config.autoSave && window.isInitialized) {
tp.saveProject();
}
}, 1000);
});
//midiController.init();
});
window.removeEventListener('initializedModule', postModuleInitialized);
window.addEventListener('resize', function(event) {
resize();
}, true);
};
const getFontsAndAxes = () => {
return new Promise((resolve) => {
const availableFontsAndAxes = listAvailableFontsAndAxes();
const newFontsAndAxes = [];
for (let i in availableFontsAndAxes) {
if (!fontsAndAxes.includes(availableFontsAndAxes[i])) {
// nevermind includes and test ourselves
let reallyNew = true;
fontsAndAxes.forEach((faa) => {
// path is enough
if (faa.fontPath === availableFontsAndAxes[i].fontPath) {
reallyNew = false;
}
});
if (reallyNew) {
fontsAndAxes.push(availableFontsAndAxes[i]);
newFontsAndAxes.push(availableFontsAndAxes[i]);
}
}
}
if (newFontsAndAxes.length > 0) {
const promises = [];
for (let l = 0; l < layers.length; l++) {
layers[l].updateFonts()
.then(() => {
resolve(newFontsAndAxes);
});
}
} else {
resolve(false);
}
});
};
const listAvailableFontsAndAxes = () => {
let availableFontsAndAxes = [];
let fontPaths = Module.listAvailableFonts();
for (let f = 0; f < fontPaths.size(); f++) {
const fontPath = fontPaths.get(f);
const fontName = getBaseName(fontPath);
const cppAxes = Module.listVariationAxes(fontPath);
// turn cppAxes in normal js array of objects
const axes = [];
for (let a = 0; a < cppAxes.size(); a++) {
let axis = {
name: cppAxes.get(a).name,
minValue: cppAxes.get(a).minValue,
maxValue: cppAxes.get(a).maxValue,
defaultValue: cppAxes.get(a).defaultValue
};
axes.push(axis);
}
availableFontsAndAxes.push({
fontName,
fontPath,
axes
});
}
return availableFontsAndAxes;
};
window.listAvailableFontsAndAxes = listAvailableFontsAndAxes;
window.getFontsAndAxes = getFontsAndAxes;
window.getLayers = () => {
return layers;
};
window.moveLayerUp = (layerID) => {
layerOrder.moveUp(layerID);
};
window.moveLayerDown = (layerID) => {
layerOrder.moveDown(layerID);
};
window.getArtboard = () => {
return artboard;
};
const addLayer = (autoInit = true) => {
const layerID = Module.addNewLayer();
const layer = new Layer(tp, layerID, fontsAndAxes, autoInit);
layers.push(layer);
layersById[layerID] = layer;
return layer;
};
const addExistingLayer = (layerID, values) => {
return new Promise((resolve) => {
const layer = new Layer(tp, layerID, fontsAndAxes, false);
// check if fonts exist?
layer.valuesCorrector(values);
const cppProps = layer.values2cppProps(values);
const checkID = Module.addExistingLayer(cppProps, layerID);
layer.init().then(() => {
layers.push(layer);
layersById[layerID] = layer;
resolve(layer);
});
});
};
const duplicateLayer = (originalLayer) => {
return new Promise((resolve) => {
const originalValues = clone(originalLayer.theatreObject.value);
const newLayer = addLayer(false);
newLayer.init(originalValues).then(() => {
const originalKeyframes = tp.getKeyframes(originalLayer);
const addKeyframes = (e) => {
const originalKeys = Object.keys(originalValues);
const givenKeys = e.detail.titles;
let allKeysFound = true;
for (let i = 0; i < originalKeys.length; i++) {
//const originalValue = originalValues[originalKeys[i]];
if (givenKeys.indexOf(originalKeys[i]) < 0) {
//delete originalValues[originalKeys[i]];
allKeysFound = false;
}
};
if (allKeysFound) {
tp.getPanel().removeEventListener("injected", addKeyframes);
}
tp.addKeyframes(newLayer, originalKeyframes).then(() => {
if (allKeysFound) {
resolve();
};
});
};
tp.getPanel().addEventListener("injected", addKeyframes);
newLayer.select();
//tp.shadowRoot.querySelector(`.layerMover${newLayer.id()} div`).click();
});
});
};
const deleteLayer = (id, saveProject = true) => {
let index = -1;
Module.removeLayer(id);
tp.removeObject(id);
layerOrder.remove(id);
// delete from array
for (let i = 0; i < layers.length; i++) {
if (layers[i].id() === id) {
index = i;
}
}
layers[index].prepareForDepartureFromThisBeautifulExperience();
layers.splice(index, 1);
delete layersById[id];
if (saveProject) {
setTimeout(() => {
tp.saveProject();
}, 1000);
}
};
// TODO: better function names
// because, come on. it may be funny for a second
// but tolerance for fun is low when you're grumpy
// because stuff doesn't work
const renderForReal = (position, frameTime) => {
position = position + frameTime;
tp.sheet.sequence.position = position;
Module.renderNextFrame();
}
window.isRenderDirty = true;
window.duplicateLayer = (layer) => {
const noticeDom = document.querySelector('#notice');
noticeDom.classList.add('visible');
noticeDom.querySelector('.what > p').innerHTML = `Duplicating Layer`;
noticeDom.querySelector('.details > p').innerHTML = `Please wait, thank you.`;
duplicateLayer(layer).then(() => {
document.querySelector('#notice').classList.remove('visible');
});
};
window.addLayer = addLayer;
window.addExistingLayer = addExistingLayer;
window.deleteLayer = deleteLayer;
window.renderFrames = exporter.renderFrames;
const layer_panel = document.querySelector('#layer_panel');
const initPanels = () => {
//makeDraggable(layer_panel);
};

View file

@ -0,0 +1,768 @@
'use strict'
import {
mix,
getMix,
mixObject,
} from './utils.js'
const PhysicalMidiMapping = {
"Launch Control MIDI 1": {
"knobs": [
21, 22, 23, 24, 25, 26, 27, 28, // first row
41, 42, 43, 44, 45, 46, 47, 48, // second row
],
"buttons": [
9, 10, 11, 12, 25, 26, 27, 28,
],
"arrows": [
114, 115, 116, 117, // up, down, left, right
]
}
};
const generalControl = {
"Launch Control MIDI 1": {}
};
const MidiController = function() {
window.mixObject = mixObject;
const element = document.querySelector('#midiController');
const openCloseButton = document.querySelector('#midi_open');
const buttons = element.querySelector(".buttons");
let inputs;
let outputs;
let isPanelOpen = false;
let layers = [];
let debugLog = false;
let isInitialized = false;
let activeLayer = 0;
let activePropSet = 0;
window.activeLayer = activeLayer;
let artboardWidth = 1920;
let lastSelectionPoint = -1;
let playbackSpeed = 1;
let setFontVariation = (layer, layerIndex, button, midiValue) => {
if (layer.theatreObject.value.hasOwnProperty('fontVariationAxes') &&
typeof layer.theatreObject.value.fontVariationAxes === 'object') {
const axes = layer.theatreObject.value.fontVariationAxes;
const index = button - 46;
const keys = Object.keys(axes);
if (index < keys.length) {
const key = keys[index];
const axesProps = layer.props.fontVariationAxes.props[key];
const min = axesProps.range[0];
const max = axesProps.range[1];
const v = (midiValue / 127.0) * (max - min) + min;
if (!currentValues[layerIndex].hasOwnProperty('fontVariationAxes')) {
currentValues[layerIndex].fontVariationAxes = {};
}
currentValues[layerIndex].fontVariationAxes[key] = v;
//tp.studio.transaction(({
//set
//}) => {
//set(layer.theatreObject.props.fontVariationAxes[key], v);
//});
}
}
};
let setLetterDelays = (layer, layerIndex, button, midiValue) => {
if (layer.theatreObject.value.hasOwnProperty('letterDelays')) {
const letterDelays = layer.theatreObject.value.letterDelays;
const keys = Object.keys(letterDelays);
const min = 0;
const max = 2000;
const v = (midiValue / 127.0) * (max - min) + min;
//console.log('MidiController::setLetterDelays - font has letterDelays', JSON.parse(JSON.stringify(letterDelays)));
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (typeof letterDelays[key] === 'object') {
const subKeys = Object.keys(letterDelays[key]);
for (let si = 0; si < subKeys.length; si++) {
const subKey = subKeys[si];
if (!currentValues[layerIndex].hasOwnProperty('letterDelays')) {
currentValues[layerIndex].letterDelays = {};
}
if (!currentValues[layerIndex].letterDelays.hasOwnProperty(key)) {
currentValues[layerIndex].letterDelays[key] = {};
}
currentValues[layerIndex].letterDelays[key][subKey] = v;
//tp.studio.transaction(({
//set
//}) => {
//set(layer.theatreObject.props.letterDelays[key][subKey], v);
//});
}
} else {
if (!currentValues[layerIndex].hasOwnProperty('letterDelays')) {
currentValues[layerIndex].letterDelays = {};
}
currentValues[layerIndex].letterDelays[key] = v;
//tp.studio.transaction(({
//set
//}) => {
//set(layer.theatreObject.props.letterDelays[key], v);
//});
}
}
} else {
//console.log('MidiController::setLetterDelays - font has no letterDelays');
}
};
let mirror_x = false;
let mirror_y = false;
let mirror_xy = false;
let setMirror = (button, midiValue) => {
}
let setLayer = (button, midiValue) => {
const layers = getLayers();
if (button === 116 && midiValue === 127) {
activeLayer = (activeLayer + 1) % layers.length;
} else if (button === 117 && midiValue === 127) {
activeLayer = (activeLayer - 1 + layers.length) % layers.length;
}
layers[activeLayer].showBoundingBoxDiv();
setTimeout(() => {
layers[activeLayer].hideBoundingBoxDiv();
}, 100);
};
let setProject = (button, midiValue) => {
const projects = tp.listProjects();
const activeProject = projects.indexOf(tp.sheet.project.address.projectId);
let direction;
if (button === 114 && midiValue === 127) {
direction = 1;
} else if (button === 115 && midiValue === 127) {
direction = -1;
}
const nextProjectIndex = (activeProject + direction + projects.length) % projects.length;
const nextProject = projects[nextProjectIndex];
tp.reloadToProject(nextProject, true);
};
let setBackgroundOpacity = (knob, midiValue) => {
let prop = 'backgroundColor.a';
setValue(getArtboard(), layers.length, prop, midiValue, [0, 1]);
};
let setBackgroundColor = (button, midiValue) => {
let pr = 'backgroundColor.r';
let pg = 'backgroundColor.g';
let pb = 'backgroundColor.b';
let r = Math.random() * 127.0;
let g = Math.random() * 127.0;
let b = Math.random() * 127.0;
setValue(getArtboard(), layers.length, pr, r, [0, 1]);
setValue(getArtboard(), layers.length, pg, g, [0, 1]);
setValue(getArtboard(), layers.length, pb, b, [0, 1]);
};
let setSpeed = (knob, midiValue) => {
if (midiValue >= 62 && midiValue <= 64) {
tp.sheet.sequence.pause();
} else {
const min = -6;
const max = 6;
let v = (midiValue / 127.0) * (max - min) + min;
if (v > 0) {
tp.sheet.sequence.play({
iterationCount: Infinity,
rate: v
});
} else if (v < 0) {
tp.sheet.sequence.play({
direction: 'reverse',
iterationCount: Infinity,
rate: Math.abs(v)
});
}
playbackSpeed = Math.abs(v);
}
};
const sentValues = [];
const sentMidiValues = [];
const currentValues = [];
const valueBuffer = [];
const populateValueBuffer = (_layers) => {
_layers.forEach((layer) => {
currentValues.push({});
sentValues.push({});
sentMidiValues.push({});
valueBuffer.push(new Map());
//console.log('pushed -------------------> ', _layers.length, JSON.parse(JSON.stringify(layer.theatreObject.value)));
});
// artboard
currentValues.push({});
sentValues.push({});
sentMidiValues.push({});
valueBuffer.push(new Map());
};
const addValuesToBuffer = (layerIndex, values, time_s, start_time_s) => {
const layerValueBuffer = valueBuffer[layerIndex];
const copiedValues = JSON.parse(JSON.stringify(values));
if (start_time_s !== -1) {
layerValueBuffer.forEach((value, value_time_s) => {
if (value_time_s > start_time_s && value_time_s <= time_s) {
const keys = Object.keys(values);
for (let k = 0; k < keys.length; k++) {
delete layerValueBuffer.get(value_time_s)[keys[k]];
if (Object.keys(layerValueBuffer.get(value_time_s)).length === 0) {
layerValueBuffer.delete(value_time_s);
break;
}
};
}
});
}
if (layerValueBuffer.has(time_s)) {
layerValueBuffer.set(time_s, {...layerValueBuffer.get(time_s), ...copiedValues});
} else {
layerValueBuffer.set(time_s, copiedValues);
}
};
const getValuesFromBuffer = (layerIndex, time_s) => {
if (valueBuffer[layerIndex].size === 0) {
return {};
} else {
valueBuffer[layerIndex] = new Map([...valueBuffer[layerIndex].entries()].sort());
let mergedValues = {};
let didMergeValues = {};
valueBuffer[layerIndex].forEach((value, value_time_s) => {
if (value_time_s < time_s) {
mergedValues = {...mergedValues, ...value};
} else {
if (Object.keys(didMergeValues).length === 0) {
didMergeValues = JSON.parse(JSON.stringify(mergedValues));
}
Object.keys(value).forEach((key) => {
if(!didMergeValues.hasOwnProperty(key)) {
mergedValues[key] = value[key];
}
});
}
});
return mergedValues;
}
};
this.getValuesFromBuffer = getValuesFromBuffer;
this.currentValue = currentValues;
this.valueBuffer = valueBuffer;
const smoothed = {
184: [21, 22, 23, 24, 26, 27, 28, 41, 42, 43, 44, 45, 46, 47, 48]
};
const ledButtonRowStatus = [0, 0, 0, 0, 0, 0, 0, 0];
const ledButtonRowMapping = [9, 10, 11, 12, 25, 26, 27, 28];
const ledColors = [ 0, 12, 13, 15, 29, 63, 62, 28, 60 ];
const setLed = (button, color, statusCode = 152) => {
outputs.forEach((midiOutput) => {
if (midiOutput.name === "Launch Control MIDI 1") {
midiOutput.send([statusCode, button, color]);
}
});
}
this.ledTimeColor = 2;
this.setLed = setLed;
this.ledMapping = ledButtonRowMapping;
this.ledColors = ledColors;
const mapping = {
"Launch Control MIDI 1": {
'general': {
184: { // status code
114: setProject,
115: setProject,
116: setLayer,
117: setLayer,
28: setSpeed,
25: setBackgroundOpacity,
},
// buttons
152: { // down
9: setBackgroundColor,
10: setBackgroundColor,
11: setBackgroundColor,
12: setBackgroundColor,
25: setBackgroundColor,
26: setBackgroundColor,
27: setBackgroundColor,
28: setBackgroundColor,
},
},
'props': [{
// knobs
184: {
// first row
21: ['x', [0, 1920]],
22: ['y', [0, 1080]],
23: ['fontSize_px', [-1000, 1000]],
24: ['rotation', [-360, 360]],
25: ['transformOrigin', ['top_left', 'top_right', 'center', 'bottom_left', 'bottom_right']],
26: ['letterSpacing', [-3, 3]],
27: ['lineHeight', [0, 10]],
// 28 free
// second row
41: ['color.r', [0, 1]],
42: ['color.g', [0, 1]],
43: ['color.b', [0, 1]],
44: ['color.a', [0, 1]],
45: setLetterDelays,
46: setFontVariation,
47: setFontVariation,
48: setFontVariation,
},
152: { // down
//9: setPropsSet,
//10: setPropsSet,
//11:
//12:
//25:
//26:
//27:
//28:
},
136: { // up
//9:
//10:
//11:
//12:
//25:
//26:
//27:
//28:
}
}]
}
};
this.mapping = mapping;
let updatingMidiValues = {};
let appliedMidiValues = {};
let doApplyMidiValues = true;
window.applyMidiTimeoutMs = 30;
this.mix = mix;
const applyMidiValuesInterval = () => {
if (doApplyMidiValues) {
// apply midi values
const device = "Launch Control MIDI 1";
const mkeys = Object.keys(updatingMidiValues);
if (updatingMidiValues.hasOwnProperty(device)) {
const keys = Object.keys(updatingMidiValues[device]);
for (let i = 0; i < keys.length; i++) {
const key = parseInt(keys[i]);
const statusCode = updatingMidiValues[device][key][0];
const midiValue = updatingMidiValues[device][key][1];
if (!appliedMidiValues[device].hasOwnProperty(keys[i]) ||
appliedMidiValues[device][keys[i]] !== midiValue) {
if (typeof mapping[device].general[statusCode][key] === 'function') {
mapping[device].general[statusCode][key](key, midiValue);
} else if (mapping[device].props[activePropSet][statusCode].hasOwnProperty(key)) {
const pm = mapping[device].props[activePropSet][statusCode][key];
if (typeof pm === 'function') {
pm(getLayers()[activeLayer], activeLayer, key, midiValue);
} else {
setValue(getLayers()[activeLayer], activeLayer, pm[0], midiValue, pm[1]);
}
}
appliedMidiValues[device][key] = updatingMidiValues[device][key];
}
delete updatingMidiValues[device][key];
}
}
setTimeout(() => {
if (doApplyMidiValues) {
requestAnimationFrame(applyMidiValuesInterval);
}
}, window.applyMidiTimeoutMs);
}
};
const directlyApplyMidiValues = (device, key, statusCode, midiValue) => {
if (typeof mapping[device].general[statusCode][key] === 'function') {
mapping[device].general[statusCode][key](key, midiValue);
} else if (mapping[device].props[activePropSet][statusCode].hasOwnProperty(key)) {
const pm = mapping[device].props[activePropSet][statusCode][key];
if (typeof pm === 'function') {
pm(getLayers()[activeLayer], activeLayer, key, midiValue);
} else {
setValue(getLayers()[activeLayer], activeLayer, pm[0], midiValue, pm[1]);
}
}
};
window.mapping = mapping;
if (!("requestMIDIAccess" in navigator)) {
element.innerHTML = `<h1>:-/</h1><p>I'm sorry, but your browser does not support the WebMIDI API ☹️🚫🎹</p>`;
}
const registerEvents = () => {
openCloseButton.addEventListener('click', () => {
if (!isPanelOpen) {
isPanelOpen = true;
element.style.display = 'flex';
} else {
isPanelOpen = false;
element.style.display = 'none';
}
});
const buttonOn = document.createElement('div');
buttonOn.innerHTML = "light on";
buttonOn.addEventListener('click', () => {
outputs.forEach((midiOutput) => {
midiOutput.send([152, 9, 2]);
});
});
const buttonOff = document.createElement('div');
buttonOff.innerHTML = "light off";
buttonOff.addEventListener('click', () => {
outputs.forEach((midiOutput) => {
midiOutput.send([152, 9, 0]);
});
});
const buttonDebug = document.createElement('div');
buttonDebug.innerHTML = "debug on";
buttonDebug.addEventListener('click', () => {
if (debugLog) {
debugLog = false;
buttonDebug.innerHTML = "debug on";
} else {
debugLog = true;
buttonDebug.innerHTML = "debug off";
}
});
buttons.append(buttonOn);
buttons.append(buttonOff);
buttons.append(buttonDebug);
};
window.debugCallTimes = [];
const tryGetLayers = (resolve) => {
if (getLayers().length > 0 && tp.isProjectLoaded) {
resolve();
} else {
setTimeout(() => {
tryGetLayers(resolve);
}, 10);
}
};
const tryGetLayersP = () => {
return new Promise((resolve) => {
tryGetLayers(resolve);
});
};
const selectLayers = () => {
return new Promise((resolve) => {
let delay = 500;//parseInt(localStorage.getItem('debugdelay'));
for (let i = 0; i <= layers.length; i++) {
setTimeout(() => {
if (i < layers.length) {
tp.studio.setSelection([layers[i].theatreObject]);
} else {
tp.studio.setSelection([]);
resolve();
}
}, i * delay);
}
});
};
window.ofRA = true;
window.ofUpdateMS = 1000 / 30;
const timeline_head = document.querySelector('#timeline_head');
const timeline = document.querySelector('#timeline');
let last_realtime_s = -999999;
let last_time_s = -999999;
let last_touchtime_s = -999999;
let last_processed_touchtime_s = -999999;
const ofUpdater = () => {
const realtime_s = performance.now() / 1000.0;
const time_s = tp.sheet.sequence.position;
const percent = time_s / tp.duration * 100;
{
const led = 114;
const statusCode = 184;
const color = [3,2,1,0][Math.floor(realtime_s * 4.0) % 4];
setLed(led, color, statusCode);
}
{
const led = 115;
const statusCode = 184;
const color = [0,1,2,3][Math.floor(realtime_s * 4.0) % 4];
setLed(led, color, statusCode);
}
{
const led = 117;
const statusCode = 184;
const color = [3,2,1,2][Math.floor(realtime_s * 6.0) % 4];
setLed(led, color, statusCode);
}
{
const led = 116;
const statusCode = 184;
const color = [1,2,3,2][Math.floor(realtime_s * 6.0) % 4];
setLed(led, color, statusCode);
}
for (let b = 0; b < ledButtonRowMapping.length; b++) {
const percentIndex = Math.floor((percent * 0.01) * ledButtonRowMapping.length);
if (b === percentIndex) {
ledButtonRowStatus[b] = Math.floor(Math.random() * 127.0);
}
setLed(ledButtonRowMapping[b], ledButtonRowStatus[b]);
}
let currentlyTouching = false;
if (realtime_s - last_touchtime_s < config.midi.touchTimeThreshold_s) {
currentlyTouching = true;
}
if (Object.keys(currentValues[activeLayer]).length > 0 && last_touchtime_s !== last_processed_touchtime_s) {
let starttime_s = -1;
if (realtime_s - last_realtime_s < config.midi.touchTimeThreshold_s) {
starttime_s = last_time_s; // fires first time prematurely, but this is okay (=> -1)
}
addValuesToBuffer(activeLayer, currentValues[activeLayer], time_s, starttime_s);
last_processed_touchtime_s = last_touchtime_s;
last_realtime_s = realtime_s;
last_time_s = time_s;
}
timeline_head.style.left = `calc(${percent}% - 10px)`;
timeline.style.background = currentlyTouching ? 'red' : 'grey';
for (let i = 0; i <= layers.length; i++) {
let bufferValues = JSON.parse(JSON.stringify(getValuesFromBuffer(i, time_s)));
bufferValues = {...bufferValues, ...currentValues[i]};
sentMidiValues[i] = mixObject(sentMidiValues[i], bufferValues, config.midi.smoothingMix);
if (i < layers.length) {
const values = {...layers[i].theatreObject.value, ...sentMidiValues[i]};
sentValues[i] = mixObject(sentValues[i], values, config.midi.smoothingMix);
let p = layers[i].values2cppProps(values);
if (p !== false) {
Module.setProps(p, layers[i].id());
}
} else {
const artboardValues = {...getArtboard().theatreObject.value, ...sentMidiValues[i]}
sentValues[i] = mixObject(sentValues[i], artboardValues, config.midi.smoothingMix);
let cppProps = getArtboard().values2cppProps(artboardValues);
Module.setArtboardProps(cppProps);
}
}
if (!currentlyTouching) {
for (let i = 0; i < currentValues.length; i++) {
currentValues[i] = {};
}
}
if (window.ofRA) {
requestAnimationFrame(ofUpdater);
} else {
setTimeout(() => {
ofUpdater();
}, window.ofUpdateMS);
}
}
const init = () => {
tryGetLayersP().then(() => {
layers = getLayers();
//console.log('what... this is layers' , layers);
const promises = [];
layers.forEach((layer) => {
promises.push(layer.updateFonts());
});
if (tp.sheet.project.address.projectId === 'rudi-midi') {
mapping["Launch Control MIDI 1"].props[0][184][23] = ['fontSize_px', [-128, 128]];
}
if (tp.sheet.project.address.projectId === 'sam-midi') {
mapping["Launch Control MIDI 1"].props[0][184][23] = ['fontSize_px', [-256, 256]];
}
Promise.all(promises).then(() => {
layers.forEach((layer, layerI) => {
const letterDelayProps = [
{
sequenced: true,
prop: ['color'],
},
{
sequenced: true,
prop: ['letterSpacing']
},
{
sequenced: true,
prop: ['fontSize_px']
},
];
if (layer.props.hasOwnProperty('fontVariationAxes')) {
const keys = Object.keys(layer.props.fontVariationAxes.props);
keys.forEach((key) => {
const detail = {
sequenced: true,
prop: ['fontVariationAxes', key],
};
letterDelayProps.push(detail);
});
}
letterDelayProps.forEach((detail, i) => {
// only update theatre for the last one
const updateTheatre = i === letterDelayProps.length - 1;
layer.handleSequenceEvent(detail, updateTheatre)
.then((updatedTheatre) => {
if (updatedTheatre && layerI === layers.length - 1) {
populateValueBuffer(layers);
ofUpdater();
isInitialized = true;
}
});
});
});
});
//selectLayers().then(() => {
//});
});
registerEvents();
navigator.requestMIDIAccess()
.then((access) => {
// Get lists of available MIDI controllers
inputs = access.inputs;
outputs = access.outputs;
const inputText = [];
const outputText = [];
inputs.forEach((midiInput) => {
inputText.push(`FOUND: ${midiInput.name}\n`);
updatingMidiValues[midiInput.name] = {};
appliedMidiValues[midiInput.name] = {};
midiInput.onmidimessage = function(message) {
//window.debugCallTimes.push(performance.now());
if (midiInput.name === "Launch Control MIDI 1") {
const isGeneral =
mapping[midiInput.name]
.general.hasOwnProperty(message.data[0]) &&
mapping[midiInput.name]
.general[message.data[0]].hasOwnProperty(message.data[1]);
const isProp =
mapping[midiInput.name]
.props[activePropSet].hasOwnProperty(message.data[0]) &&
mapping[midiInput.name]
.props[activePropSet][message.data[0]].hasOwnProperty(message.data[1]);
if (isInitialized && (isGeneral || isProp)) {
last_touchtime_s = performance.now() / 1000.0;
updatingMidiValues[midiInput.name][message.data[1]] = [message.data[0], message.data[2]];
//directlyApplyMidiValues(midiInput.name, message.data[1], message.data[0], message.data[2]);
}
autoSwitchPerhaps();
}
if (debugLog) {
element.querySelector(".midiMessages").innerText += `# ${midiInput.name}
${new Date()}
==================================
- Status: ${message.data[0]}
- Data 1: ${message.data[1]}
- Data 2: ${message.data[2]}
==================================\n\n`;
}
};
});
outputs.forEach((midiOutput) => {
outputText.push(`FOUND: ${midiOutput.name}\n`);
});
element.querySelector(".inputs").innerText = inputText.join('');
element.querySelector(".outputs").innerText = outputText.join('');
applyMidiValuesInterval();
// lalalaload another project
//autoSwitchPerhaps();
});
};
let autoSwitchTimeout = false;
const autoSwitchPerhaps = () => {
clearTimeout(autoSwitchTimeout);
autoSwitchTimeout = setTimeout(() => {
setProject(114, 127);
}, 5 * 60 * 1000);
};
const setValue = (layer, layerIndex, prop, value, minMax) => {
let v;
let propName = prop;
if (minMax.length > 2) {
const index = Math.floor((value / 128.0) * minMax.length);
v = minMax[index];
} else {
const min = minMax[0];
const max = minMax[1];
v = (value / 127.0) * (max - min) + min;
if (propName.indexOf('color') === 0) {
propName = propName.split('.')[1];
let color;
if (currentValues[layerIndex].hasOwnProperty('color')) {
color = {...layer.theatreObject.value.color, ...currentValues[layerIndex].color};
} else {
color = layer.theatreObject.value.color;
}
color[propName] = v;
propName = 'color';
v = color;
}
if (propName.indexOf('backgroundColor') === 0) {
propName = propName.split('.')[1];
let backgroundColor;
if (currentValues[layerIndex].hasOwnProperty('backgroundColor')) {
backgroundColor = {...layer.theatreObject.value.backgroundColor, ...currentValues[layerIndex].backgroundColor};
} else {
backgroundColor = layer.theatreObject.value.backgroundColor;
}
backgroundColor[propName] = v;
propName = 'backgroundColor';
v = backgroundColor;
}
}
currentValues[layerIndex][propName] = v;
//tp.studio.transaction(({
//set
//}) => {
//set(layer.theatreObject.props[propName], v);
//});
};
this.init = init;
this.getInputs = () => {
return inputs;
}
this.getOutputs = () => {
return outputs;
}
};
export {
MidiController
};

88
bin/web/js/moduleFS.js Normal file
View file

@ -0,0 +1,88 @@
const ModuleFS = function() {
const MODE_WRITE_TO_PERSISTENT = false;
const MODE_READ_FROM_PERSISTENT = true;
this.init = () => {
return new Promise((resolve) => {
FS.mkdir(config.fs.idbfsDir);
// Then mount with IDBFS type
FS.mount(IDBFS, {}, config.fs.idbfsDir);
this.syncfs(MODE_READ_FROM_PERSISTENT)
.then(() => {
// Then sync with true to get persistent data
if (!FS.analyzePath(config.fs.idbfsFontDir).exists) {
FS.mkdir(config.fs.idbfsFontDir);
}
if (!FS.analyzePath(config.fs.idbfsTmpDir).exists) {
FS.mkdir(config.fs.idbfsTmpDir);
}
resolve();
});
});
};
this.syncfs = (mode = MODE_READ_FROM_PERSISTENT) => {
return new Promise((resolve, reject) => {
FS.syncfs(mode, function(err) {
if (err !== null) {
// Error
console.error(err);
reject(error);
} else {
resolve();
}
});
});
};
// check utils::uploadFile() for details of file
this.save = (file) => {
return new Promise((resolve) => {
if (file.type.indexOf('font') >= 0 || file.hasOwnProperty('isFont') && file.isFont === true) {
var uint8View = new Uint8Array(file.arrayBuffer);
console.log('trying to save the font file, file, uint8View', file, uint8View);
if (!FS.analyzePath(`${config.fs.idbfsFontDir}/${file.name}`).exists) {
FS.createDataFile(config.fs.idbfsFontDir, file.name, uint8View, true, true);
}
this.syncfs(MODE_WRITE_TO_PERSISTENT)
.then(() => {
resolve(true);
});
} else if (file.type.indexOf('zip') >= 0 || file.hasOwnProperty('isZip') && file.isZip === true) {
var uint8View = new Uint8Array(file.arrayBuffer);
var filePath = `${config.fs.idbfsTmpDir}/${file.name}`;
console.log(filePath);
if (!FS.analyzePath(filePath).exists) {
FS.createDataFile(config.fs.idbfsTmpDir, file.name, uint8View, true, true);
}
this.syncfs(MODE_WRITE_TO_PERSISTENT)
.then(() => {
resolve(filePath);
});
} else {
resolve(false);
}
});
};
this.delete = (file) => {
if (file.type.indexOf('zip') >= 0 || file.hasOwnProperty('isZip') && file.isZip === true) {
var filePath = `${config.fs.idbfsTmpDir}/${file.name}`;
if (!FS.analyzePath(filePath).exists) {
console.log(`moduleFS::delete(${filePath})`, `file does not exist`);
} else {
FS.unlink(filePath);
}
this.syncfs(MODE_WRITE_TO_PERSISTENT)
.then(() => {
resolve(true);
});
} else {
resolve(false);
}
};
};
export {
ModuleFS
};

50
bin/web/js/record.js Normal file
View file

@ -0,0 +1,50 @@
const Record = function(tp) {
const hot = {};
const addRecordButton = (layer, propTitle, isActive) => {
const panel = tp.getPanel();
const panelPropTitle = tp.getPanelPropTitle(propTitle);
if (panelPropTitle !== null) {
const container = tp.getPanelPropContainer(panelPropTitle);
if (container === null) {
console.log("Record::addRecordButton",
`impossible! cannot find panelPropContainer for ${propTitle}`);
} else if (container.querySelector('.recordButton') !== null) {
console.log("Record::addRecordButton",
`already added an record button for ${propTitle}`);
} else {
const button = document.createElement('div');
button.classList.add('recordButton');
button.classList.add(`recordButton${propTitle}`);
button.innerHTML = `<img src="/web/assets/record.svg" alt="record" />`;
container.append(button);
button.addEventListener('click', () => {
if (!hot.hasOwnProperty(layer.id())) {
hot[layer.id()] = {};
}
if (!hot[layer.id()].hasOwnProperty(propTitle)) {
hot[layer.id()][propTitle] = {};
button.classList.add('active');
} else {
delete hot[layer.id()][propTitle];
if (Object.keys(hot[layer.id()]).length === 0) {
delete hot[layer.id()];
}
button.classList.remove('active');
}
});
}
} else {
console.log("Record::addRecordButton",
`cannot find panelPropTitle for ${propTitle}`);
}
};
// public
this.addRecordButton = addRecordButton;
};
export {
Record
}

118
bin/web/js/script.js Normal file
View file

@ -0,0 +1,118 @@
const scannerLine = document.getElementById("scannerLine");
const scannerLineH = document.getElementById("scannerLineH");
$(document).ready(function() {
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
$("body").removeClass("hideBody");
$("body").addClass("mobile");
}else if(('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0) && (testratio<0.7777777) ){
$("body").removeClass("hideBody");
$("body").addClass("mobile mobileOld");
}else{
$("body").removeClass("hideBody");
$("body").addClass("desktop")
};
const img = new Image();
img.crossOrigin = "anonymous";
img.src = "./media/360_F_163966311_qh3qSk57mw9oLPOklZigzX9zlB5DgdaM.jpeg";
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const scanner = $(".scanner");
const destination = document.getElementById("letter");
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
img.addEventListener("load", () => {
canvas.height = canvas.width * (img.height / img.width);
scanner.css("width", $("#canvas").width());
scanner.css("height", $("#canvas").height());
var hRatio = canvas.width / img.width ;
var vRatio = canvas.height / img.height ;
var ratio = Math.min ( hRatio, vRatio );
ctx.drawImage(img, 0,0, img.width, img.height, 0,0,img.width*ratio, img.height*ratio);
// ctx.drawImage(img, 0, 0);
img.style.display = "none";
});
const hoveredColor = document.getElementById("hovered-color");
const selectedColor = document.getElementById("selected-color");
// let rect = scannelLine.getBoundingClientRect();
function pick() {
const bounding = canvas.getBoundingClientRect();
const letters = document.getElementById("typeMaster");
const letter = $(".letter");
letter.each(function(i,e){
let rect = scannerLine.getBoundingClientRect();
let rectH = scannerLineH.getBoundingClientRect();
const x = rect.left - bounding.left;
const y = rectH.top - bounding.top;
const pixel = ctx.getImageData(x - (i*2), y , 1, 1);
const data = pixel.data;
let rgba = `rgba(${data[0]}, ${data[1]}, ${data[2]}, ${data[3] / 255})`;
hoveredColor.style.background = rgba;
hoveredColor.textContent = rgba;
// letters.style.color = rgba;
// $(".typeMaster").css("background",rgba);
$(e).css("color",rgba);
$(e).css("font-variation-settings","'wght' "+ ((data[0]) + (data[1]) + (data[2])));
return rgba;
})
}
const milliseconds = 100;
window.setInterval(pick, milliseconds);
});
$(window).on("load", function(){
$("#typeMaster").html(function(index, html) {
return html.replace(/\w+/g, '<span class="word">$&</span>');
});
$(".word").html(function(index, html) {
return html.replace(/\S/g, '<span class="letter">$&</span>');
})
var length = $(".letter").length;
var currentMousePos = { x: -1, y: -1 };
});
var slider = document.getElementById("myRange");
var output = document.getElementById("demo");
output.innerHTML = "duration X: " + slider.value; // Display the default slider value
var sliderY = document.getElementById("myRangeY");
var outputY = document.getElementById("demoY");
outputY.innerHTML = "duration Y: " + sliderY.value; // Display the default slider value
slider.oninput = function() {
output.innerHTML = "duration X: " + this.value;
scannerLine.style.animationDuration = this.value + "s";
}
sliderY.oninput = function() {
outputY.innerHTML = "duration Y: " + this.value;
scannerLineH.style.animationDuration = this.value + "s";
}

1037
bin/web/js/theatre-play.js Normal file

File diff suppressed because it is too large Load diff

408
bin/web/js/utils.js Normal file
View file

@ -0,0 +1,408 @@
/////////////////////////////////////
const UUID = function() {
let allowedIdChars = "0123456789abcdef";
// fallback in case we cannot crypto
const notUniqueId = (t = 16) => {
let out = "";
for (let i = 0; i < t; i++) {
out += allowedIdChars[(Math.random() * allowedIdChars.length + Math.floor(performance.now())) % allowedIdChars.length];
}
return out;
}
// from https://github.com/ai/nanoid/blob/main/nanoid.js
const uniqueId = (t = 16) => {
const indices = crypto.getRandomValues(new Uint8Array(t));
let out = "";
for (var i = 0; i < t; i++) {
out += allowedIdChars[indices[i] % allowedIdChars.length];
}
return out;
}
this.getUuid = () => {
return typeof crypto === 'object' && typeof crypto.getRandomValues === 'function' ?
uniqueId() : notUniqueId();
}
}
const uuid = new UUID();
const getUuid = () => {
return uuid.getUuid();
}
const makeEven = (n) => {
const nr = Math.round(n);
return nr - nr % 2;
}
const getMix = (before_s, after_s, time_s, clamp = true) => {
const diff = after_s - before_s;
const travel = time_s - before_s;
if (diff === 0 || travel === 0) {
return 0;
} else if (clamp) {
return Math.min(Math.max(travel / diff));
} else {
return travel / diff;
}
}
const mix = (a, b, m, t) => {
if (Math.abs(a - b) < t) {
return b;
} else {
return a * (1.0 - m) + b * m;
}
};
const mixObject = (a, b, m) => {
const out = JSON.parse(JSON.stringify(a));
const a_keys = Object.keys(a);
const b_keys = Object.keys(b);
let keys = [...new Set([...a_keys, ...b_keys])];
keys.forEach((key) => {
if (!a.hasOwnProperty(key)) {
out[key] = b[key];
} else if (!b.hasOwnProperty(key)) {
out[key] = a[key];
} else {
if (typeof a[key] === 'object') {
out[key] = mixObject(a[key], b[key], m);
} else if (typeof a[key] === 'number') {
out[key] = a[key] * (1.0 - m) + b[key] * m;
} else {
out[key] = b[key];
}
}
});
return out;
};
/////////////////////////////////////
const htmlToElement = (html) => {
var template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html;
return template.content.firstChild;
}
/////////////////////////////////////
// download(textData, 'lol.txt', 'text/plain');
// download(jsonData, 'lol.json', 'application/json');
function downloadFile(content, fileName, contentType) {
var a = document.createElement("a");
var file = new Blob([content], {
type: contentType
});
a.href = URL.createObjectURL(file);
a.download = fileName;
a.click();
}
function uploadFile(expectedType = 'application/json') {
return new Promise((resolve, reject) => {
var input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', () => {
let json;
let files = input.files;
if (files.length == 0) return;
const file = files[0];
console.log('file', file);
let reader = new FileReader();
if (expectedType === 'application/zip' || file.type === 'application/zip') {
reader.onload = (e) => {
const f = e.target.result;
console.log(e, file.name, file.size, file.type, f);
resolve({
name: file.name,
size: file.size,
type: file.type,
arrayBuffer: f,
});
};
reader.onerror = (e) => reject(e.target.error.name);
reader.readAsArrayBuffer(file);
} else if (expectedType === 'application/json') {
reader.onload = (e) => {
console.log(e);
const f = e.target.result;
// This is a regular expression to identify carriage
// Returns and line breaks
//const lines = file.split(/\r\n|\n/);
if (file.type === expectedType) {
try {
json = JSON.parse(f);
resolve(json);
} catch (e) {
reject("Caught: " + e.message)
}
}
};
reader.onerror = (e) => reject(e.target.error.name);
reader.readAsText(file);
} else if (expectedType.indexOf('font') >= 0) {
console.log('expect font');
reader.onload = (e) => {
console.log(e);
const f = e.target.result;
if (file.type.indexOf('font') >= 0) {
console.log('is font');
//var uint8View = new Uint8Array(f);
//console.log('trying to save the font file, file, uint8View', file, uint8View);
//FS.createDataFile(config.fs.idbfsFontDir, file.name, uint8View, true, true);
resolve({
name: file.name,
size: file.size,
type: file.type,
arrayBuffer: f
});
} else {
const extension = file.name.split('.').reverse().shift()
const fileType = `font/${extension}`;
if(confirm(`${file.name} has type ${file.type} instead of the expected ${fileType}. are you sure this is a font?`)) {
const outputFile = {
isFont: true,
name: file.name,
size: file.size,
type: file.type,
arrayBuffer: f
};
console.log({outputFile});
resolve(outputFile);
} else {
reject('not a font');
}
}
};
reader.onerror = (e) => reject(e.target.error.name);
reader.readAsArrayBuffer(file);
} else {
alert(`unknown filetype ${file.type}, what are you uploading?`);
resolve(false);
}
});
input.click();
//var a = document.createElement('a');
//a.onclick = () => {
//var e = document.createEvent('MouseEvents');
//e.initEvent('click', true, false);
//input.dispatchEvent(e);
//};
//a.click();
});
}
const makeDraggable = (elmnt, draggedCallback = false) => {
var pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
if (elmnt.querySelector('.header .move')) {
// if present, the header is where you move the DIV from:
elmnt.querySelector('.header .move').onmousedown = dragMouseDown;
} else {
// otherwise, move the DIV from anywhere inside the DIV:
elmnt.onmousedown = dragMouseDown;
}
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
function closeDragElement() {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
if (typeof draggedCallback === 'function') {
draggedCallback();
}
}
}
const cyrb53 = (str, seed = 0) => {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
};
const hashFromString = (str, prefix = 'hash') => {
return `${prefix}${cyrb53(str)}`;
};
function getBaseName(filePath) {
return filePath.substring(filePath.lastIndexOf('/') + 1, filePath.lastIndexOf('.'))
}
function verifyVariableTimeProject(vt_project) {
const exampleProject = {
projectId: 'exampleProject',
variable_time_version: VARIABLE_TIME_VERSION,
theatre: 'complete theatre saveFile',
layerOrder: ['layer-0', 'layer-4', 'layer-2'],
};
if (!vt_project) {
console.error('Utils::verifyVariableTimeProject::couldNotVerify',
'project equals false',
'this is what we received',
vt_project,
'compare to following example',
exampleProject);
return false;
}
if (typeof vt_project === 'string') {
console.error('Utils::verifyVariableTimeProject::couldNotVerify',
'project is a string, please first parse json ',
'this is what we received',
vt_project,
'compare to following example',
exampleProject);
return false;
// do not allow strings
//try {
//vt_project = JSON.parse(vt_project);
//} catch (e) {
//console.error('Utils::verifyVariableTimeProject::couldNotVerify',
//'project is a string,
//but could not parse json ',
//'this is what we received',
//vt_project,
//'compare to following example',
//exampleProject);
//return false;
//}
}
if (typeof vt_project !== 'object') {
console.error('Utils::verifyVariableTimeProject::couldNotVerify',
'project is not an object',
'this is what we received',
vt_project,
'compare to following example',
exampleProject);
return false;
}
const exampleKeys = Object.keys(exampleProject);
for (let i = 0; i < exampleKeys.length; i++) {
if (!vt_project.hasOwnProperty(exampleKeys[i])) {
console.error('Utils::verifyVariableTimeProject::couldNotVerify',
`${exampleKeys[i]} missing`,
'this is what we received',
vt_project,
'compare to following example',
exampleProject);
return false;
}
}
return true;
}
function clone(a) {
return JSON.parse(JSON.stringify(a));
};
function getParents(elem, until = null) {
const parents = [];
let done = false;
while (!done) {
elem = elem.parentNode;
if (elem === until) {
done = true;
} else if (elem === null) {
// until is not a parent
return null;
} else {
parents.push(elem);
}
}
return parents;
}
function arraysEqual(a, b, sortingMatters = false) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
if (!Array.isArray(a) || !Array.isArray(b)) return false;
let _a = sortingMatters ? a : a.toSorted();
let _b = sortingMatters ? b : b.toSorted();
for (var i = 0; i < _a.length; ++i) {
if (_a[i] !== _b[i]) return false;
}
return true;
}
const mapValue = (value, low1, high1, low2, high2, clamp=false) => {
const mapped = low2 + (high2 - low2) * (value - low1) / (high1 - low1);
return clamp ? Math.min(high2 > low2 ? high2 : low2, Math.max(low2 < high2 ? low2 : high2, mapped)) : mapped;
}
/////////////////////////////////////
export {
getUuid,
htmlToElement,
downloadFile,
uploadFile,
makeDraggable,
getBaseName,
hashFromString,
verifyVariableTimeProject,
makeEven,
mix,
getMix,
mixObject,
clone,
getParents,
arraysEqual,
mapValue,
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long