going public
BIN
bin/data/420px-01.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
bin/data/42px-01.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
4
bin/data/appSettings.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"backgroundColor": [0.8313, 0.8313, 0.8313, 1.0],
|
||||
"tmpExportDir": "data/export"
|
||||
}
|
0
bin/data/free-fonts/.gitkeep
Normal file
BIN
bin/web/assets/SCI_Woordbeeld_EN_3_regels_RGB.gif
Normal file
After Width: | Height: | Size: 6.3 KiB |
6
bin/web/assets/add_layer.svg
Normal 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 |
BIN
bin/web/assets/addkeyframes.mp4
Normal file
BIN
bin/web/assets/addlayer.mp4
Normal file
20
bin/web/assets/align-bottom.svg
Normal 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 |
20
bin/web/assets/align-center-horizontal.svg
Normal 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 |
21
bin/web/assets/align-center-vertical.svg
Normal 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 |
21
bin/web/assets/align-left.svg
Normal 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 |
21
bin/web/assets/align-right.svg
Normal 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 |
20
bin/web/assets/align-text-center.svg
Normal 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 |
20
bin/web/assets/align-text-left.svg
Normal 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 |
20
bin/web/assets/align-text-right.svg
Normal 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 |
20
bin/web/assets/align-top.svg
Normal 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 |
BIN
bin/web/assets/changeduration.mp4
Normal file
BIN
bin/web/assets/changeeasing.mp4
Normal file
6
bin/web/assets/delete_layer.svg
Normal 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 |
10
bin/web/assets/duplicate_layer.svg
Normal 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 |
BIN
bin/web/assets/letter-delays.mp4
Normal file
15
bin/web/assets/movelayer.svg
Normal 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 |
10
bin/web/assets/movelayerdown.svg
Normal 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 |
10
bin/web/assets/movelayerup.svg
Normal 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
BIN
bin/web/assets/sci_logo.mp4
Normal file
BIN
bin/web/assets/sci_logo_320.webm
Normal file
BIN
bin/web/assets/selectkeyframes.mp4
Normal file
BIN
bin/web/assets/sequenceprop.mp4
Normal file
BIN
bin/web/assets/showeasing.mp4
Normal file
20
bin/web/assets/text-wrapping.svg
Normal 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 |
BIN
bin/web/assets/vt-favicon.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
928
bin/web/css/demo.css
Executable 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
1
bin/web/ffmpeg_modules/ffmpeg.min.js.map
Normal file
178
bin/web/js/artboard.js
Normal 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
|
@ -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
|
@ -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
|
@ -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
1084
bin/web/js/layer.js
Normal file
91
bin/web/js/layerOrder.js
Normal 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
|
@ -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);
|
||||
};
|
768
bin/web/js/midiController.js
Normal 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
|
@ -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
|
@ -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
|
@ -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
408
bin/web/js/utils.js
Normal 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,
|
||||
}
|