Compare commits
118 commits
Author | SHA1 | Date | |
---|---|---|---|
|
3b6a57024d | ||
|
8353bb837c | ||
|
8075cf2b16 | ||
|
ef308f61b2 | ||
|
32778138ba | ||
|
8750151b7d | ||
|
18300baf46 | ||
|
42c47aa003 | ||
|
d42a1365a4 | ||
|
d050789d77 | ||
|
d35d949526 | ||
e625b0d14c | |||
|
09897d7178 | ||
|
c832982d46 | ||
|
5ff6ee3905 | ||
|
157703886d | ||
|
14e79208f3 | ||
|
996ad358e7 | ||
|
d7ac9e834d | ||
|
0d732943b3 | ||
|
7931dffca1 | ||
|
5c9f41b7a7 | ||
|
6c67d3aaa8 | ||
|
77ffed0225 | ||
|
30c662f5a4 | ||
|
87e296a1d5 | ||
|
f421a344b1 | ||
|
086e93e786 | ||
|
9bd2f08c8f | ||
|
8a92646038 | ||
|
64253eaf1f | ||
|
4ef2ee4670 | ||
|
ede5b5d44c | ||
|
94081b9345 | ||
|
6e60818136 | ||
|
6ec94b4beb | ||
|
a4fda03f7e | ||
|
83184737ea | ||
|
962344e8cd | ||
|
1fdce3ee96 | ||
|
a9a40a8de1 | ||
|
f2ed11c206 | ||
|
a1570e7fc3 | ||
|
28fb7e2198 | ||
|
b80f73558b | ||
|
efaad8d143 | ||
|
7d174dd189 | ||
|
0b29009db9 | ||
|
365ac34de6 | ||
|
6d3b064da7 | ||
|
8a17aa7a39 | ||
|
c8e90a1317 | ||
|
16e1839616 | ||
|
13eca27957 | ||
|
9b336aefed | ||
|
596b4fd413 | ||
|
826af338bf | ||
|
574f86a7b9 | ||
|
017b487a50 | ||
|
4282909d88 | ||
|
424053f080 | ||
|
e5ce29a4e9 | ||
|
7c01738d6d | ||
|
7e35b00d5b | ||
|
997247cde3 | ||
|
c5bdff23ee | ||
|
bd87a53872 | ||
|
7823711f1a | ||
|
f829eea911 | ||
|
be11f015b9 | ||
|
05c6f3f5f9 | ||
|
e10d8dbe1d | ||
|
f18bafc543 | ||
|
d95ec3d965 | ||
|
63ddc4b317 | ||
48c7db3f6e | |||
e4ca41c2f0 | |||
dc6e668d0c | |||
2d55ef11b0 | |||
ab3786ef23 | |||
f19843bd46 | |||
2d43063537 | |||
223121655e | |||
49ce43d324 | |||
e926d60d62 | |||
5f1f5a10a1 | |||
922834161f | |||
494f562b3b | |||
|
21a5888022 | ||
0ca73bca05 | |||
748af243fa | |||
16124c755d | |||
e6e705f86f | |||
5860343f70 | |||
b46d6bb5d4 | |||
6aba91b6ca | |||
64af8d49d1 | |||
62f03862d6 | |||
523eb8c7f8 | |||
|
9cd830f7de | ||
04041870da | |||
870a99d725 | |||
35ed6009e2 | |||
bb76bd09c6 | |||
667447b9a2 | |||
6ed4bd1227 | |||
fad1bbb875 | |||
7eb3ab7615 | |||
1ef64e4eba | |||
f8984174e0 | |||
de5550287c | |||
7f6ab572c7 | |||
64b3d26384 | |||
4cb5742afb | |||
50126f7cd4 | |||
ad7a7f7936 | |||
e2d56245d6 | |||
8c89d5abc2 |
26
.eslintrc.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
module.exports = {
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2021": true
|
||||||
|
},
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
".eslintrc.{js,cjs}"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "script"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
}
|
||||||
|
}
|
21
.gitignore
vendored
|
@ -2,8 +2,22 @@
|
||||||
bin/*
|
bin/*
|
||||||
!bin/data/
|
!bin/data/
|
||||||
!bin/data/*
|
!bin/data/*
|
||||||
!bin/web/
|
# ugly recursion https://stackoverflow.com/questions/3667986/git-ignore-exception-not-working-as-desired
|
||||||
!bin/web/*
|
!bin/em/
|
||||||
|
bin/em/*
|
||||||
|
!bin/em/*/
|
||||||
|
bin/em/*/*
|
||||||
|
!bin/em/*/web/
|
||||||
|
bin/em/*/web/*
|
||||||
|
!bin/em/*/web/js/
|
||||||
|
!bin/em/*/web/css/
|
||||||
|
!bin/em/*/web/assets/
|
||||||
|
bin/em/*/web/js/*
|
||||||
|
bin/em/*/web/css/*
|
||||||
|
bin/em/*/web/assets/*
|
||||||
|
!bin/em/*/web/js/*.*
|
||||||
|
!bin/em/*/web/css/*.*
|
||||||
|
!bin/em/*/web/assets/*.*
|
||||||
obj/
|
obj/
|
||||||
bin/data/ofxMsdfgen
|
bin/data/ofxMsdfgen
|
||||||
bin/data/ofxGPUFont
|
bin/data/ofxGPUFont
|
||||||
|
@ -39,3 +53,6 @@ bin/web/fonts/*
|
||||||
# DIST
|
# DIST
|
||||||
# these are related to distributing variable time
|
# these are related to distributing variable time
|
||||||
upload*
|
upload*
|
||||||
|
|
||||||
|
# GENERATED
|
||||||
|
.browserpid
|
||||||
|
|
|
@ -26,6 +26,7 @@ Install all dependencies, then
|
||||||
|
|
||||||
### Optional:
|
### Optional:
|
||||||
- [ofxProfiler](https://git.pointer.click/ofxAddons/ofxProfiler)
|
- [ofxProfiler](https://git.pointer.click/ofxAddons/ofxProfiler)
|
||||||
|
This allows us to profile the web export in the same way as a desktop export
|
||||||
|
|
||||||
### Included dependencies:
|
### Included dependencies:
|
||||||
- [ffmpeg.wasm](https://github.com/ffmpegwasm/ffmpeg.wasm)
|
- [ffmpeg.wasm](https://github.com/ffmpegwasm/ffmpeg.wasm)
|
||||||
|
|
|
@ -290,10 +290,13 @@
|
||||||
<!--<div class="move">move</div>-->
|
<!--<div class="move">move</div>-->
|
||||||
<!--</div>-->
|
<!--</div>-->
|
||||||
<button id="midi_open">midi</button>
|
<button id="midi_open">midi</button>
|
||||||
|
<button id="hide_ui">hide ui</button>
|
||||||
<button id="exporter_open">export</button>
|
<button id="exporter_open">export</button>
|
||||||
|
<button id="upload_audio">add audio</button>
|
||||||
<button id="save_project" onclick="window.tp.downloadProject()">save project</button>
|
<button id="save_project" onclick="window.tp.downloadProject()">save project</button>
|
||||||
<button id="open_project" onclick="window.tp.uploadProject(true)">open project</button>
|
<button id="open_project" onclick="window.tp.uploadProject(true)">open project</button>
|
||||||
<button id="start_new_project" onclick="window.tp.startNewProject()">start new project</button>
|
<button id="start_new_project" onclick="window.tp.startNewProject()">start new project</button>
|
||||||
|
<button id="add_font">add font</button>
|
||||||
<!--<button id="debug_profiling" onclick="window.toggleProfiling()">debug start profiling</button>-->
|
<!--<button id="debug_profiling" onclick="window.toggleProfiling()">debug start profiling</button>-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -357,6 +360,13 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="what"><p></p></div>
|
<div class="what"><p></p></div>
|
||||||
<div class="details"><p></p></div>
|
<div class="details"><p></p></div>
|
||||||
|
<div class="button"><p>OK</p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="notice_recording">
|
||||||
|
<div class="content">
|
||||||
|
<div class="what"><p>recording</p></div>
|
||||||
|
<div class="details"><p>please wait</p></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- MIDI BEGIN -->
|
<!-- MIDI BEGIN -->
|
||||||
|
@ -523,10 +533,50 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- ABOUT END -->
|
<!-- ABOUT END -->
|
||||||
<div id="timeline"><div id="timeline_head"></div><div>
|
<div id="timeline"><div id="timeline_head"></div><div>
|
||||||
|
<!-- AUDIO BEGIN -->
|
||||||
|
<div class="audioWrapper">
|
||||||
|
<header>
|
||||||
|
<h1>Voice-change-O-matic</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<canvas class="visualizer" width="640" height="100"></canvas>
|
||||||
|
|
||||||
|
<form class="controls">
|
||||||
|
<div>
|
||||||
|
<label for="voice">Voice setting</label>
|
||||||
|
<select id="voice" name="voice">
|
||||||
|
<option value="distortion">Distortion</option>
|
||||||
|
<option value="convolver">Reverb</option>
|
||||||
|
<option value="biquad">Bass Boost</option>
|
||||||
|
<option value="delay">Echo Delay</option>
|
||||||
|
<option value="off" selected>Off</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="visual">Visualizer setting</label>
|
||||||
|
<select id="visual" name="visual">
|
||||||
|
<option value="sinewave">Sinewave</option>
|
||||||
|
<option value="frequencybars" selected>Frequency bars</option>
|
||||||
|
<option value="off">Off</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a class="mute">Mute</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- AUDIO END -->
|
||||||
<!-- EXPORT BEGIN -->
|
<!-- EXPORT BEGIN -->
|
||||||
<script id="ffmpeg.min.js" type="application/javascript" src="/web/ffmpeg_modules/ffmpeg.min.js"></script>
|
<script id="ffmpeg.min.js" type="application/javascript" src="/web/ffmpeg_modules/ffmpeg.min.js"></script>
|
||||||
<!-- EXPORT END -->
|
<!-- EXPORT END -->
|
||||||
|
<script> const process = {
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<!--<script id="core-and-studio.min.js" type="application/javascript" src="/web/theatre_modules/core-and-studio.js"></script>-->
|
||||||
|
<script src="/web/js/vanilla-picker.js"> </script>
|
||||||
<script type="module" src="/web/js/main.js"> </script>
|
<script type="module" src="/web/js/main.js"> </script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 481 B After Width: | Height: | Size: 481 B |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 646 B After Width: | Height: | Size: 646 B |
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 610 B |
Before Width: | Height: | Size: 615 B After Width: | Height: | Size: 615 B |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 475 B After Width: | Height: | Size: 475 B |
Before Width: | Height: | Size: 854 B After Width: | Height: | Size: 854 B |
Before Width: | Height: | Size: 633 B After Width: | Height: | Size: 633 B |
Before Width: | Height: | Size: 490 B After Width: | Height: | Size: 490 B |
Before Width: | Height: | Size: 501 B After Width: | Height: | Size: 501 B |
11
bin/em/variabletime/web/assets/record.svg
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 27.0.1, 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 83.2 83.2" style="enable-background:new 0 0 83.2 83.2;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#ED2024;}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<circle class="st0" cx="41.6" cy="41.6" r="40.9"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 475 B |
8
bin/em/variabletime/web/assets/sound.svg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?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 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||||
|
<path d="M15.6,11c0-0.1,0-0.1,0-0.2V0.3L5.8,2.7v9.2c-0.7-0.3-1.5-0.5-2.5-0.3c-1.8,0.3-3.1,1.4-2.9,2.6c0.2,1.2,1.8,1.9,3.5,1.6
|
||||||
|
c1.5-0.2,2.7-1.1,2.9-2.1h0.1V3.6l7.6-1.9v8c-0.6-0.3-1.5-0.4-2.4-0.3C10.3,9.7,9,10.9,9.2,12c0.2,1.2,1.8,1.9,3.5,1.6
|
||||||
|
C14.5,13.4,15.8,12.2,15.6,11z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 628 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
@ -207,7 +207,7 @@ body.debug div:not(.centerLine) {
|
||||||
#notice .content {
|
#notice .content {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
padding: 2em;
|
padding: 10px 10px 20px 10px;
|
||||||
color: black;
|
color: black;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -217,11 +217,122 @@ body.debug div:not(.centerLine) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#notice .content .what p {
|
#notice .content .what p {
|
||||||
|
font-size: 1.2em;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#notice .content .details p {
|
#notice .content .details p {
|
||||||
color: black;
|
color: black;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 95%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
#notice .content .button {
|
||||||
|
display: none;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: rgba(222, 222, 222, 0.97);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 7px 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-variation-settings: 'wght' 800;
|
||||||
|
transition: all 0.125s;
|
||||||
|
}
|
||||||
|
#notice .content .button:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.97);
|
||||||
|
color: rgba(222, 222, 222, 1.0);
|
||||||
|
}
|
||||||
|
#notice .content .button.visible {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
#notice .content .details{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
#notice .content .button p {
|
||||||
|
user-select: none;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
#notice_recording {
|
||||||
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.1);
|
||||||
|
z-index: 2000;
|
||||||
|
display: none;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-family: "Tonka";
|
||||||
|
font-variation-settings: 'wght' 500;
|
||||||
|
font-size: 0.8em;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#notice_recording .what p, #notice_recording .details button{
|
||||||
|
text-align: center;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
}
|
||||||
|
#notice_recording .what p{
|
||||||
|
animation: blink 1s linear infinite !important;
|
||||||
|
}
|
||||||
|
#notice_recording .details button{
|
||||||
|
border: none;
|
||||||
|
background-color: white;
|
||||||
|
font-size: 1.1em;
|
||||||
|
padding: 5px 15px 4px 15px !important;
|
||||||
|
margin: 7px 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: none;
|
||||||
|
padding: 7px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
font-variation-settings: 'wght' 750, 'wdth' 100;
|
||||||
|
font-family: 'Tonka';
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink{
|
||||||
|
0%{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50%{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100%{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#notice_recording .details button:hover{
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notice_recording .content{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#notice_recording.visible {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
#notice_recording button {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
#notice_recording.impenetrable {
|
||||||
|
pointer-events: all;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.exporterChild * {
|
.exporterChild * {
|
||||||
|
@ -458,6 +569,8 @@ body.debug div:not(.centerLine) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#exporter_render_info {
|
#exporter_render_info {
|
||||||
display: none;
|
display: none;
|
||||||
color: rgb(234, 35, 51);
|
color: rgb(234, 35, 51);
|
||||||
|
@ -926,3 +1039,21 @@ h4{
|
||||||
|
|
||||||
|
|
||||||
/* ABOUT END */
|
/* ABOUT END */
|
||||||
|
.audioWrapper {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
z-index: 42000;
|
||||||
|
background-color: rgba(255,125,125,0.5);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.audioWrapper canvas.visualizer {
|
||||||
|
border-top: 1px solid black;
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
margin-bottom: -3px;
|
||||||
|
box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.7), 0 3px 4px rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
.invisible {
|
||||||
|
display: none;
|
||||||
|
}
|
750
bin/em/variabletime/web/css/theatre.css
Normal file
|
@ -0,0 +1,750 @@
|
||||||
|
.alignButtons,
|
||||||
|
.textAlignButtons {
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 10px 0px !important;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter {
|
||||||
|
transition: 0.5s font-variation-settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'vtVF';
|
||||||
|
src: url("/web/fonts/vtVF.ttf") format("TrueType");
|
||||||
|
}
|
||||||
|
|
||||||
|
.vtTitle {
|
||||||
|
font-family: 'vtVF';
|
||||||
|
font-size: 3em;
|
||||||
|
font-variation-settings: "wght" 0, "wdth" 0, "opsz" 0;
|
||||||
|
height: 50px;
|
||||||
|
width: 100%;
|
||||||
|
position: inherit;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#panel,
|
||||||
|
.panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel>div {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel>.panelWrapperMom {
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel>.bottomButtonsContainer {
|
||||||
|
order: 3;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel>.bottomButtonsContainer #start_new_project {
|
||||||
|
order: 1;
|
||||||
|
margin: 7px 10px 0px 10px;
|
||||||
|
}
|
||||||
|
.panel>.bottomButtonsContainer #open_project {
|
||||||
|
order: 2;
|
||||||
|
margin: 7px 10px 0px 10px;
|
||||||
|
}
|
||||||
|
.panel>.bottomButtonsContainer #save_project {
|
||||||
|
order: 3;
|
||||||
|
margin: 7px 10px 0px 10px;
|
||||||
|
}
|
||||||
|
.panel>.bottomButtonsContainer #add_font {
|
||||||
|
order: 4;
|
||||||
|
margin: 7px 10px 0px 10px;
|
||||||
|
}
|
||||||
|
.panel>.bottomButtonsContainer #upload_audio {
|
||||||
|
order: 5;
|
||||||
|
margin: 7px 10px 0px 10px;
|
||||||
|
}
|
||||||
|
.panel>.bottomButtonsContainer #exporter_open {
|
||||||
|
order: 6;
|
||||||
|
margin: 7px 10px 0px 10px;
|
||||||
|
}
|
||||||
|
.panel>.bottomButtonsContainer #hide_ui {
|
||||||
|
order: 7;
|
||||||
|
margin: 7px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelWrapper {}
|
||||||
|
|
||||||
|
.panelControlsWrapper {
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
|
grid-row-start: 2;
|
||||||
|
grid-column-start: 1;
|
||||||
|
grid-column-end: 7;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.propWrapper{
|
||||||
|
/* margin-top: 5px;*/
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xWrapper,
|
||||||
|
.yWrapper,
|
||||||
|
.fontFamilyWrapper,
|
||||||
|
.fontFamilyWrapper,
|
||||||
|
.rotationWrapper,
|
||||||
|
.letterDelayWrapper,
|
||||||
|
.transformOriginWrapper,
|
||||||
|
.widthWrapper,
|
||||||
|
.fontSizeWrapper,
|
||||||
|
.letterSpacingWrapper,
|
||||||
|
.lineHeighWrapper,
|
||||||
|
.textWrapper,
|
||||||
|
.colorWrapper,
|
||||||
|
.mirror_xWrapper,
|
||||||
|
.mirror_yWrapper,
|
||||||
|
.mirror_xyWrapper,
|
||||||
|
.mirror_x_distanceWrapper,
|
||||||
|
.mirror_y_distanceWrapper,
|
||||||
|
.alignButtons,
|
||||||
|
.textAlignButtons {
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
padding: 0px;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.colorTitleWrapper{
|
||||||
|
flex-grow: 0;
|
||||||
|
/* background: blue; */
|
||||||
|
width: fit-content;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.colorTitleWrapper + div div div:first-of-type{
|
||||||
|
border: none !important;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.colorTitleWrapper + div{
|
||||||
|
flex-grow: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textWrappingButton {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textWrappingButton.active {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.alignButtonsVertical {
|
||||||
|
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alignButtons {
|
||||||
|
display: flex;
|
||||||
|
width: 50%;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xWrapper {}
|
||||||
|
|
||||||
|
.yWrapper {}
|
||||||
|
|
||||||
|
.mirror_xWrapper {}
|
||||||
|
|
||||||
|
.mirror_xWrapper input,
|
||||||
|
.mirror_yWrapper input,
|
||||||
|
.mirror_xyWrapper input {
|
||||||
|
margin: 0px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mirror_xyWrapper input[type=checkbox] + label::after, .mirror_xWrapper input[type=checkbox] + label::after, .mirror_yWrapper input[type=checkbox] + label::after {
|
||||||
|
content: ' OFF';
|
||||||
|
}
|
||||||
|
|
||||||
|
.mirror_xyWrapper input[type=checkbox]:checked + label, .mirror_xWrapper input[type=checkbox]:checked + label, .mirror_yWrapper input[type=checkbox]:checked + label {
|
||||||
|
color: #1cba94;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mirror_xyWrapper input[type=checkbox]:checked + label::after, .mirror_xWrapper input[type=checkbox]:checked + label::after, .mirror_yWrapper input[type=checkbox]:checked + label::after {
|
||||||
|
content: ' ON';
|
||||||
|
}
|
||||||
|
|
||||||
|
.mirror_xyWrapper input[type=checkbox], .mirror_xWrapper input[type=checkbox], .mirror_yWrapper input[type=checkbox] {
|
||||||
|
position: absolute;
|
||||||
|
width: 27px;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio_min_max {
|
||||||
|
padding: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: grid;
|
||||||
|
/* flex-direction: column; */
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
row-gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.sourceLabel_Cont{
|
||||||
|
font-variation-settings: 'wght' 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source_Dom_Cont {
|
||||||
|
padding: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-top: 1px dashed #91919177;
|
||||||
|
/* padding-top: 10px; */
|
||||||
|
margin-top: 10px;
|
||||||
|
margin: 10px 0px;
|
||||||
|
border-bottom: 1px dashed #91919177;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source_Dom_Cont select {
|
||||||
|
background-color: white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
color: rgb(0, 0, 0);
|
||||||
|
padding: 1px 6px;
|
||||||
|
font-style: inherit;
|
||||||
|
font-variant: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
font-stretch: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
font-family: inherit;
|
||||||
|
font-optical-sizing: inherit;
|
||||||
|
font-kerning: inherit;
|
||||||
|
font-feature-settings: inherit;
|
||||||
|
outline: none;
|
||||||
|
text-align: left;
|
||||||
|
/* width: 100%; */
|
||||||
|
border-radius: 0px;
|
||||||
|
font-variation-settings: "wght" 700;
|
||||||
|
height: 26px;
|
||||||
|
margin-right: 10px;
|
||||||
|
width: -webkit-fill-available;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio_min_Cont,
|
||||||
|
.audio_max_Cont,
|
||||||
|
.letterDelayCont {
|
||||||
|
grid-column-start: 1;
|
||||||
|
grid-column-end: 3;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.letterDelayCont{
|
||||||
|
|
||||||
|
}
|
||||||
|
.letterDelayCont input {
|
||||||
|
margin-left: 10px;
|
||||||
|
flex-shrink: 1 !important;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio_min_max input, .letterDelayCont input {
|
||||||
|
background: rgb(255, 255, 255);
|
||||||
|
border: none;
|
||||||
|
color: rgb(0, 0, 0);
|
||||||
|
padding: 3px 6px 1px;
|
||||||
|
font-style: inherit;
|
||||||
|
font-variant: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
font-stretch: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
font-family: inherit;
|
||||||
|
font-optical-sizing: inherit;
|
||||||
|
font-kerning: inherit;
|
||||||
|
font-feature-settings: inherit;
|
||||||
|
outline: none;
|
||||||
|
/* cursor: ew-resize; */
|
||||||
|
/* width: 100%; */
|
||||||
|
height: calc(100% - 4px);
|
||||||
|
border-radius: 0px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
text-align: center;
|
||||||
|
font-variation-settings: "wght" 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio_min_max label, .letterDelayCont label{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audioOptions {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
padding: 0px 7px 7px 7px;
|
||||||
|
background: rgba(163, 163, 163, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fontVariationAxesContWrapper .audioOptions{
|
||||||
|
margin-left: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audioOptions label {
|
||||||
|
color: black;
|
||||||
|
font-family: 'Tonka';
|
||||||
|
font-variation-settings: 'wght' 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mirror_xWrapper label,
|
||||||
|
.mirror_yWrapper label,
|
||||||
|
.mirror_xyWrapper label {
|
||||||
|
color: #ea2333;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {}
|
||||||
|
|
||||||
|
input[type=checkbox]:checked+label {
|
||||||
|
color: #1cba94;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.muteSvg, .soundonSvg{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consentContainer{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark, .unchecked {
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
background: rgb(163, 163, 163);
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consentContainer input ~ .muteSvg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consentContainer input:checked ~ .soundonSvg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consentContainer input ~ .soundonSvg {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consentContainer input:checked ~ .muteSvg {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consentContainer input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0 !important;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
label svg{
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audioOptions input[type="radio"] {
|
||||||
|
appearance: none;
|
||||||
|
background-color: #fff;
|
||||||
|
margin: 0;
|
||||||
|
font: inherit;
|
||||||
|
color: black;
|
||||||
|
width: 1.15em;
|
||||||
|
height: 1.15em;
|
||||||
|
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audioOptions input[type="radio"]::before {
|
||||||
|
content: "";
|
||||||
|
width: 0.65em;
|
||||||
|
height: 0.65em;
|
||||||
|
border-radius: 50%;
|
||||||
|
opacity: 0;
|
||||||
|
transition: 120ms transform ease-in-out;
|
||||||
|
box-shadow: inset 1em 1em black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audioOptions input[type="radio"]:checked::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync_inputDom_Cont {
|
||||||
|
display: flex;
|
||||||
|
width: fit-content;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 5px;
|
||||||
|
margin: 5px 0px 5px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync_Dom {
|
||||||
|
padding: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: 7px;
|
||||||
|
border-top: 1px dashed #91919177;
|
||||||
|
padding-top: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin: 10px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync_Dom p{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync_titleDom_Cont {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mirror_yWrapper {}
|
||||||
|
|
||||||
|
.mirror_xyWrapper {}
|
||||||
|
|
||||||
|
.mirror_x_distanceWrapper {}
|
||||||
|
|
||||||
|
.mirror_y_distanceWrapper {}
|
||||||
|
|
||||||
|
.fontFamilyWrapper {}
|
||||||
|
|
||||||
|
.rotationWrapper {
|
||||||
|
border-top: 1px dashed #91919177;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transformOriginWrapper {}
|
||||||
|
|
||||||
|
.widthWrapper {}
|
||||||
|
|
||||||
|
.heightWrapper {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fontSizeWrapper {}
|
||||||
|
|
||||||
|
.letterSpacingWrapper {}
|
||||||
|
|
||||||
|
.letterDelayWrapper {}
|
||||||
|
|
||||||
|
.lineHeighWrapper {}
|
||||||
|
|
||||||
|
.textWrapper {}
|
||||||
|
|
||||||
|
.colorWrapper {
|
||||||
|
|
||||||
|
border-top: 1px dashed #91919177;
|
||||||
|
/* margin-bottom: 10px;*/
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
.letterDelaysContWrapper{
|
||||||
|
border-top: 1px dashed #91919177;
|
||||||
|
padding-top: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mirror_xyInputWrapper, .mirror_yInputWrapper, .mirror_xInputWrapper{
|
||||||
|
margin-left: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.icon {
|
||||||
|
font-family: 'VariableIcons';
|
||||||
|
font-size: 3em;
|
||||||
|
margin: 2px 5px;
|
||||||
|
line-height: 0.2em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.main_panel_button,
|
||||||
|
.vte_button {
|
||||||
|
background-color: white;
|
||||||
|
font-size: 1.1em;
|
||||||
|
padding: 5px 5px 4px 5px;
|
||||||
|
margin: 7px 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: none;
|
||||||
|
padding: 7px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
font-variation-settings: 'wght' 750, 'wdth' 100;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.main_panel_button:hover,
|
||||||
|
.vte_button:hover {
|
||||||
|
color: white;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.removeButtonContainer {
|
||||||
|
display: flex;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alignButtons img,
|
||||||
|
.textAlignButtons img {
|
||||||
|
height: 19px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 5px 5px 2px 5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
li.layerMover div {}
|
||||||
|
|
||||||
|
li.layerMover div.selected {
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
li.layerMover div.selected svg circle {
|
||||||
|
fill: #ea2333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.letterDelaysContWrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.letterDelaysContWrapper>*,
|
||||||
|
.fontVariationAxesContWrapper>* {
|
||||||
|
margin-left: calc(var(--left-pad) * (var(--depth) - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.fontVariationAxesContWrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-bottom: 1px dashed #91919177;
|
||||||
|
border-top: 1px dashed #91919177;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moveLayerButton {}
|
||||||
|
|
||||||
|
.removeLayerButton img,
|
||||||
|
.duplicateLayerButton img,
|
||||||
|
.addLayerButton img,
|
||||||
|
.moveLayerButton img {
|
||||||
|
height: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.propTitleStuff {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addLayerButton {
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
/* padding-left: 15px; */
|
||||||
|
align-self: end;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audioButton {
|
||||||
|
width: 17px;
|
||||||
|
margin: 2px 2px 2px 5px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
background: rgb(163, 163, 163);
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 17px;
|
||||||
|
align-self: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.audioButton:hover, .checkmark:hover, .unchecked:hover {
|
||||||
|
background: #1cba94;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audioButton img {
|
||||||
|
max-width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audioButton.active {
|
||||||
|
background: #1cba94;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record_Dom_Cont{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 5px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio_recordButton,
|
||||||
|
.recordButton {
|
||||||
|
max-width: 50%;
|
||||||
|
margin: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
height: 17px;
|
||||||
|
align-self: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* flex-grow: 1;*/
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio_recordButton,
|
||||||
|
.recordButton, .recordButton label{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recordButton .buttonCont{
|
||||||
|
background: rgb(163, 163, 163);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio_recordButton {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recordButton:hover .buttonCont{
|
||||||
|
background: #1cba94;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recordButton img {
|
||||||
|
max-width: 70%;
|
||||||
|
/* margin-left: 2px;*/
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recordButton label {
|
||||||
|
margin-left: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recordButton.active {
|
||||||
|
background: #1cba94;
|
||||||
|
}
|
||||||
|
|
||||||
|
.propInputWrapper{
|
||||||
|
flex-grow: 1 !important;
|
||||||
|
flex-shrink: 1 !important;
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording {
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgb(255, 255, 255);
|
||||||
|
border: none;
|
||||||
|
color: rgb(0, 0, 0);
|
||||||
|
padding: 3px 6px 1px;
|
||||||
|
font-style: inherit;
|
||||||
|
font-variant: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
font-stretch: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
font-family: inherit;
|
||||||
|
font-optical-sizing: inherit;
|
||||||
|
font-kerning: inherit;
|
||||||
|
font-feature-settings: inherit;
|
||||||
|
outline: none;
|
||||||
|
cursor: ew-resize;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 4px);
|
||||||
|
border-radius: 0px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
text-align: center;
|
||||||
|
font-variation-settings: "wght" 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelMomWrapper {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ASYA: color picker can be changed like this */
|
||||||
|
.picker_wrapper.popup .picker_arrow::before {
|
||||||
|
/* background: pink;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.color_preview{
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:disabled {
|
||||||
|
background: darkgrey;
|
||||||
|
color: lightgrey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ASYA: most the following code does not work*/
|
||||||
|
.onOffIndicatorWrapper input[type=checkbox] + label::after {
|
||||||
|
content: ' OFF';
|
||||||
|
}
|
||||||
|
|
||||||
|
.onOffIndicatorWrapper input[type=checkbox]:checked + label{
|
||||||
|
color: #1cba94;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onOffIndicatorWrapper input[type=checkbox]:checked + label::after{
|
||||||
|
content: ' ON';
|
||||||
|
}
|
||||||
|
.onOffIndicatorWrapper {
|
||||||
|
width: auto;
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ const Artboard = function(tp, domElement = false, autoInit = true) {
|
||||||
let x = 0;
|
let x = 0;
|
||||||
let y = 0;
|
let y = 0;
|
||||||
let props = {
|
let props = {
|
||||||
backgroundColor: tp.core.types.rgba({
|
color: tp.core.types.rgba({
|
||||||
r: 74,
|
r: 74,
|
||||||
g: 94,
|
g: 94,
|
||||||
b: 181,
|
b: 181,
|
||||||
|
@ -100,25 +100,29 @@ const Artboard = function(tp, domElement = false, autoInit = true) {
|
||||||
};
|
};
|
||||||
const props2cppProps = (_props) => {
|
const props2cppProps = (_props) => {
|
||||||
let cppProps = JSON.parse(JSON.stringify(_props));
|
let cppProps = JSON.parse(JSON.stringify(_props));
|
||||||
let bgIsArray = Array.isArray(cppProps.backgroundColor);
|
let bgIsArray = Array.isArray(cppProps.color);
|
||||||
if (bgIsArray && cppProps.backgroundColor.length === 4) {
|
if (bgIsArray && cppProps.color.length === 4) {
|
||||||
|
cppProps.backgroundColor = cppProps.color;
|
||||||
|
delete cppProps.color;
|
||||||
// nothing to do
|
// nothing to do
|
||||||
} else if (!bgIsArray && cppProps.backgroundColor.hasOwnProperty('r')) {
|
} else if (!bgIsArray && cppProps.color.hasOwnProperty('r')) {
|
||||||
cppProps.backgroundColor = [
|
cppProps.backgroundColor = [
|
||||||
cppProps.backgroundColor.r,
|
cppProps.color.r,
|
||||||
cppProps.backgroundColor.g,
|
cppProps.color.g,
|
||||||
cppProps.backgroundColor.b,
|
cppProps.color.b,
|
||||||
cppProps.backgroundColor.a
|
cppProps.color.a
|
||||||
];
|
];
|
||||||
} else if (!bgIsArray && cppProps.backgroundColor.default.hasOwnProperty('r')) {
|
delete cppProps.color;
|
||||||
|
} else if (!bgIsArray && cppProps.color.default.hasOwnProperty('r')) {
|
||||||
cppProps.backgroundColor = [
|
cppProps.backgroundColor = [
|
||||||
cppProps.backgroundColor.default.r,
|
cppProps.color.default.r,
|
||||||
cppProps.backgroundColor.default.g,
|
cppProps.color.default.g,
|
||||||
cppProps.backgroundColor.default.b,
|
cppProps.color.default.b,
|
||||||
cppProps.backgroundColor.default.a
|
cppProps.color.default.a
|
||||||
];
|
];
|
||||||
|
delete cppProps.color;
|
||||||
} else {
|
} else {
|
||||||
console.error('js::layer::props2cppProps', 'color could not be translated');
|
console.error('js::artboard::props2cppProps', 'color could not be translated');
|
||||||
}
|
}
|
||||||
return cppProps;
|
return cppProps;
|
||||||
};
|
};
|
||||||
|
@ -146,6 +150,16 @@ const Artboard = function(tp, domElement = false, autoInit = true) {
|
||||||
panelPropTitle.innerHTML = friendlyName;
|
panelPropTitle.innerHTML = friendlyName;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// should we have an audio object, let's inject the buttons, etc
|
||||||
|
if (typeof audio === 'object' && audio.hasOwnProperty('injectPanel')) {
|
||||||
|
const success = audio.injectPanel(this);
|
||||||
|
if (success) {
|
||||||
|
const e = new CustomEvent('injectedAll', {});
|
||||||
|
tp.getPanel().dispatchEvent(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Artboard::findInjectPanel', `cannot inject audio panel for ${this.id()} for some reason.`);
|
||||||
|
}
|
||||||
doItAgain = false;
|
doItAgain = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,6 +177,35 @@ const Artboard = function(tp, domElement = false, autoInit = true) {
|
||||||
this.hide = () => {
|
this.hide = () => {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
};
|
};
|
||||||
|
const getHierarchyPanelButton = () => {
|
||||||
|
if (hierarchyPanelButton === null) {
|
||||||
|
hierarchyPanelButton = tp.shadowRoot.querySelector(`.layerMover${this.id()}`);
|
||||||
|
}
|
||||||
|
return hierarchyPanelButton;
|
||||||
|
};
|
||||||
|
this.isSelected = () => {
|
||||||
|
const panel = getHierarchyPanelButton();
|
||||||
|
if (panel === null) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
const notSelected = panel.querySelector('.not-selected');
|
||||||
|
return !notSelected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.select = () => {
|
||||||
|
const panel = getHierarchyPanelButton();
|
||||||
|
const selectables = panel.querySelector('.not-selected');
|
||||||
|
if (selectables !== null && typeof selectables.dispatchEvent === 'function') {
|
||||||
|
var clickEvent = new MouseEvent("click", {
|
||||||
|
"view": window,
|
||||||
|
"bubbles": true,
|
||||||
|
"cancelable": false
|
||||||
|
});
|
||||||
|
selectables.dispatchEvent(clickEvent);
|
||||||
|
} else {
|
||||||
|
window.debugElement = panel;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// action
|
// action
|
||||||
if (typeof domElement !== 'object') {
|
if (typeof domElement !== 'object') {
|
1525
bin/em/variabletime/web/js/audio.js
Normal file
396
bin/em/variabletime/web/js/audioLayer.js
Normal file
|
@ -0,0 +1,396 @@
|
||||||
|
import {
|
||||||
|
sanitizeTheatreKey,
|
||||||
|
arraysEqual,
|
||||||
|
clone,
|
||||||
|
} from './utils.js';
|
||||||
|
|
||||||
|
const AudioLayer = function(tp, audioLayerID, values = false, autoInit = true) {
|
||||||
|
// private
|
||||||
|
let props = { };
|
||||||
|
if (typeof values === 'string') {
|
||||||
|
const audioID = sanitizeTheatreKey(values);
|
||||||
|
values = {};
|
||||||
|
values[audioID] = false;
|
||||||
|
}
|
||||||
|
if (typeof values === 'object') {
|
||||||
|
props[Object.keys(values)[0]] = tp.core.types.boolean(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onValuesChangeCallbacks = [];
|
||||||
|
let hierarchyPanelButton = null;
|
||||||
|
let panelFinderTimeout = false;
|
||||||
|
|
||||||
|
// private functions
|
||||||
|
//
|
||||||
|
const onValuesChange = (values) => {
|
||||||
|
const n_callbacks = onValuesChangeCallbacks.length;
|
||||||
|
const successes = [];
|
||||||
|
if (n_callbacks > 0) {
|
||||||
|
for (let i = 0; i < n_callbacks; i++) {
|
||||||
|
if (typeof onValuesChangeCallbacks[i] === 'function') {
|
||||||
|
if(onValuesChangeCallbacks[i](values)) {
|
||||||
|
successes.unshift(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('AudioLayer::onValuesChange', 'holy shit, the callback is not a function');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
successes.forEach((i) => {
|
||||||
|
onValuesChangeCallbacks.splice(i, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getHierarchyPanelButton = () => {
|
||||||
|
if (hierarchyPanelButton === null) {
|
||||||
|
hierarchyPanelButton = tp.shadowRoot.querySelector(`.layerMover${this.id()}`);
|
||||||
|
}
|
||||||
|
return hierarchyPanelButton;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPanelPropDiamond = (panelPropTitle) => {
|
||||||
|
return panelPropTitle.previousSibling;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPanelPropInput = (panelPropTitle) => {
|
||||||
|
return panelPropTitle
|
||||||
|
.parentElement
|
||||||
|
.nextSibling
|
||||||
|
.querySelector('input');
|
||||||
|
};
|
||||||
|
|
||||||
|
const findInjectPanel = () => {
|
||||||
|
let doItAgain = true;
|
||||||
|
|
||||||
|
console.log('AudioLayer::findInjectPanel');
|
||||||
|
const panel = tp.getPanel();
|
||||||
|
|
||||||
|
let panelSuccesses = 0;
|
||||||
|
const audioIDs = Object.keys(this.theatreObject.value);
|
||||||
|
{ // PANEL
|
||||||
|
for (let i = 0; i < audioIDs.length; i++) {
|
||||||
|
const audioID = audioIDs[i];
|
||||||
|
let panelPropTitle = tp.getPanelPropTitle(audioID);
|
||||||
|
const panelPropContainer = tp.getPanelPropContainer(panelPropTitle);
|
||||||
|
if (panelPropTitle === null || panelPropContainer == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 1) prevent unwanted interaction
|
||||||
|
|
||||||
|
// remove theatrejs listeners
|
||||||
|
// such as contextmenu
|
||||||
|
// NOTE: this does not work with stop(Immediate)Propagation here
|
||||||
|
const tmp = panelPropTitle.cloneNode(true);
|
||||||
|
panelPropTitle.parentNode.replaceChild(tmp, panelPropTitle);
|
||||||
|
panelPropTitle = tmp;
|
||||||
|
|
||||||
|
// remove diamond
|
||||||
|
const diamond = getPanelPropDiamond(panelPropTitle);
|
||||||
|
if (diamond !== null) {
|
||||||
|
diamond.remove();
|
||||||
|
}
|
||||||
|
// unchangeable boolean
|
||||||
|
const boolean = getPanelPropInput(panelPropTitle);
|
||||||
|
boolean.disabled = true;
|
||||||
|
boolean.parentElement.classList.add('onOffIndicatorWrapper');
|
||||||
|
boolean.classList.add('onOffIndicator');
|
||||||
|
boolean.style.width = 'auto';
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="main_panel_button button delete${audioID}">
|
||||||
|
delete
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
for (let i = 0; i < container.children.length; i++) {
|
||||||
|
panelPropContainer.append(container.children[i]);
|
||||||
|
};
|
||||||
|
panelPropContainer.querySelector(`.button.delete${audioID}`)
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
this.removeFile(audioID);
|
||||||
|
});
|
||||||
|
|
||||||
|
panelSuccesses++;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (panelSuccesses === audioIDs.length) {
|
||||||
|
doItAgain = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doItAgain) {
|
||||||
|
clearTimeout(panelFinderTimeout);
|
||||||
|
panelFinderTimeout = setTimeout(() => {
|
||||||
|
this.findInjectPanel();
|
||||||
|
}, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const findInjectSequencePanel = () => {
|
||||||
|
let doItAgain = true;
|
||||||
|
let sequencePanelSuccess = 0;
|
||||||
|
const sequencePanel = tp.getSequencePanel();
|
||||||
|
const sequencePanelLeft = tp.getSequencePanelLeft();
|
||||||
|
if (sequencePanelLeft !== null) { // SEQUENCE PANEL LEFT
|
||||||
|
const propTitles = sequencePanelLeft.querySelectorAll('[data-testid="SequencePanel-PropTitle"]');
|
||||||
|
const audioLayerTitle = (() => {
|
||||||
|
const titles = sequencePanelLeft.querySelectorAll('[data-testid="SequencePanel-Title"]');
|
||||||
|
for (let i = 0; i < titles.length; i++) {
|
||||||
|
if (titles[i].innerHTML === this.id()) {
|
||||||
|
return titles[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})();
|
||||||
|
if (audioLayerTitle !== null) {
|
||||||
|
const audioSection = audioLayerTitle.closest('li');
|
||||||
|
if (audioSection !== null) {
|
||||||
|
const audioPropTitles = audioSection.querySelectorAll('[data-testid="SequencePanel-PropTitle"]');
|
||||||
|
window.audioPropTitles = audioPropTitles;
|
||||||
|
audioPropTitles.forEach((audioPropTitle) => {
|
||||||
|
const diamond = (() => {
|
||||||
|
const potential = audioPropTitle.nextSibling;
|
||||||
|
if (potential !== null
|
||||||
|
&& potential.nodeName.toLowerCase() === 'div'
|
||||||
|
&& potential.querySelectorAll(':scope > [data-pi-key]').length === 3) {
|
||||||
|
return potential;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})();
|
||||||
|
if (diamond !== null) {
|
||||||
|
diamond.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (audioPropTitle.innerHTML.length > config.audio.maxFilenameLength) {
|
||||||
|
const file = audioPropTitle.innerHTML;
|
||||||
|
const m = (config.audio.maxFilenameLength / 2) - 2;
|
||||||
|
audioPropTitle.innerHTML = file.substr(0,m) + '..' + file.substr(file.length - m, m);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sequencePanelSuccess++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sequencePanelLeft !== null) { // SEQUENCE PANEL RIGHT
|
||||||
|
const audioSectionAnchor = sequencePanelLeft
|
||||||
|
.nextSibling
|
||||||
|
.querySelector(`[data-pi-key*="${this.id()}"]`);
|
||||||
|
if (audioSectionAnchor !== null) {
|
||||||
|
const audioSection = audioSectionAnchor.closest('li');
|
||||||
|
if (audioSection !== null) {
|
||||||
|
audioSection.addEventListener('contextmenu', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
});
|
||||||
|
audioSection.querySelectorAll('*').forEach((element) => {
|
||||||
|
element.addEventListener('contextmenu', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
sequencePanelSuccess++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequencePanelSuccess === 2) {
|
||||||
|
doItAgain = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doItAgain) {
|
||||||
|
clearTimeout(panelFinderTimeout);
|
||||||
|
panelFinderTimeout = setTimeout(() => {
|
||||||
|
this.findInjectSequencePanel();
|
||||||
|
}, 30);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setTime = (audioID, start, stop) => {
|
||||||
|
audioID = sanitizeTheatreKey(audioID);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const keyframes = [{
|
||||||
|
path: [audioID],
|
||||||
|
keyframes: [{
|
||||||
|
position: -1,
|
||||||
|
value: false,
|
||||||
|
type: 'hold',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: start,
|
||||||
|
value: true,
|
||||||
|
type: 'hold',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: stop,
|
||||||
|
value: false,
|
||||||
|
type: 'hold',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}];
|
||||||
|
if (tp.getKeyframes(this, [audioID]).length > 0) {
|
||||||
|
tp.studio.transaction(({
|
||||||
|
__experimental_deleteKeyframes
|
||||||
|
}) => {
|
||||||
|
__experimental_deleteKeyframes(this.theatreObject.props[audioID]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tp.addKeyframes(this, keyframes).then(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getValue = (audioID) => {
|
||||||
|
const values = this.theatreObject.value;
|
||||||
|
if (typeof values[audioID] !== 'undefined') {
|
||||||
|
return values[audioID];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.theatreObject = tp.addObject(audioLayerID, this.props, onValuesChange);
|
||||||
|
tp.studio.transaction(({
|
||||||
|
set
|
||||||
|
}) => {
|
||||||
|
const mergedValues = {
|
||||||
|
...this.theatreObject.value,
|
||||||
|
...values
|
||||||
|
};
|
||||||
|
set(this.theatreObject.props, values ? mergedValues : this.theatreObject.value);
|
||||||
|
});
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = () => {
|
||||||
|
tp.removeObject(audioLayerID);
|
||||||
|
};
|
||||||
|
|
||||||
|
// public
|
||||||
|
this.props = props;
|
||||||
|
|
||||||
|
this.findInjectPanel = findInjectPanel;
|
||||||
|
this.findInjectSequencePanel = findInjectSequencePanel;
|
||||||
|
|
||||||
|
this.addFile = (audioID) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
audioID = sanitizeTheatreKey(audioID);
|
||||||
|
const values = this.theatreObject.value;
|
||||||
|
values[audioID] = false;
|
||||||
|
props[audioID] = tp.core.types.boolean(values[audioID]);
|
||||||
|
this.props = props;
|
||||||
|
|
||||||
|
const setDummy = () => {
|
||||||
|
tp.changeObject(audioLayerID, {
|
||||||
|
dummy: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onValuesChangeCallbacks.push((changedValues) => {
|
||||||
|
if (changedValues.dummy) {
|
||||||
|
// NOTE: any idea how to avoid this ugly nesting?
|
||||||
|
onValuesChangeCallbacks.push((againChangedValues) => {
|
||||||
|
const expectedValueKeys = Object.keys(values);
|
||||||
|
const givenValueKeys = Object.keys(againChangedValues);
|
||||||
|
if (arraysEqual(expectedValueKeys, givenValueKeys)) {
|
||||||
|
resolve();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tp.changeObject(audioLayerID, this.props);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setDummy();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this.removeFile = (audioID) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
audioID = sanitizeTheatreKey(audioID);
|
||||||
|
const values = clone(this.theatreObject.value);
|
||||||
|
delete values[audioID];
|
||||||
|
delete props[audioID];
|
||||||
|
this.props = props;
|
||||||
|
const setDummy = () => {
|
||||||
|
tp.changeObject(audioLayerID, {
|
||||||
|
dummy: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onValuesChangeCallbacks.push((changedValues) => {
|
||||||
|
if (changedValues.dummy) {
|
||||||
|
// NOTE: any idea how to avoid this ugly nesting?
|
||||||
|
onValuesChangeCallbacks.push((againChangedValues) => {
|
||||||
|
const expectedValueKeys = Object.keys(values);
|
||||||
|
const givenValueKeys = Object.keys(againChangedValues);
|
||||||
|
if (arraysEqual(expectedValueKeys, givenValueKeys)) {
|
||||||
|
resolve();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tp.changeObject(audioLayerID, this.props);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setDummy();
|
||||||
|
|
||||||
|
if (!audio.audioPlayer.remove(audioID)) {
|
||||||
|
console.log('AudioLayer::remove', `could not remove ${audioID} from audioPlayer`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this.setTime = setTime;
|
||||||
|
|
||||||
|
this.getValue = getValue;
|
||||||
|
|
||||||
|
this.isSelected = () => {
|
||||||
|
const panel = getHierarchyPanelButton();
|
||||||
|
if (panel === null) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
const notSelected = panel.querySelector('.not-selected');
|
||||||
|
return !notSelected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.select = () => {
|
||||||
|
const panel = getHierarchyPanelButton();
|
||||||
|
const selectables = panel.querySelector('.not-selected');
|
||||||
|
if (selectables !== null && typeof selectables.dispatchEvent === 'function') {
|
||||||
|
var clickEvent = new MouseEvent("click", {
|
||||||
|
"view": window,
|
||||||
|
"bubbles": true,
|
||||||
|
"cancelable": false
|
||||||
|
});
|
||||||
|
selectables.dispatchEvent(clickEvent);
|
||||||
|
} else {
|
||||||
|
window.debugElement = panel;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init = init;
|
||||||
|
|
||||||
|
this.id = () => {
|
||||||
|
return audioLayerID;
|
||||||
|
};
|
||||||
|
|
||||||
|
// well, I know this is a shitty name. My apologies. However, ..
|
||||||
|
this.prepareForDepartureFromThisBeautifulExperience = remove;
|
||||||
|
|
||||||
|
// action
|
||||||
|
if (autoInit) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
AudioLayer,
|
||||||
|
}
|
160
bin/em/variabletime/web/js/audioPlayer.js
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
import {
|
||||||
|
clone,
|
||||||
|
sanitizeTheatreKey,
|
||||||
|
} from './utils.js';
|
||||||
|
|
||||||
|
const AudioPlayer = function() {
|
||||||
|
const audioElements = [];
|
||||||
|
let updateInterval = false;
|
||||||
|
let updateInterval_ms = 10;
|
||||||
|
|
||||||
|
const getAudioID = (filename) => {
|
||||||
|
let audioID = sanitizeTheatreKey(filename);
|
||||||
|
const duplicate = audioElements.findIndex((e) => e.audioID === audioID) !== -1;
|
||||||
|
if (duplicate) {
|
||||||
|
let index = 0;
|
||||||
|
let unique = false;
|
||||||
|
while (!unique) { // uuuh, while loops..
|
||||||
|
const newAudioID = `${audioID}_${index}`;
|
||||||
|
if (audioElements.findIndex((e) => e.audioID === newAudioID) === -1) {
|
||||||
|
audioID = newAudioID;
|
||||||
|
unique = true;
|
||||||
|
} else {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return audioID
|
||||||
|
}
|
||||||
|
return audioID;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addMany = (manyAudioElements) => {
|
||||||
|
manyAudioElements.forEach((e) => {
|
||||||
|
this.add(e.audioID, e.layerID, e.propTitle, e.file, e.startTime);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.add = (audioID, layer, propTitle, file, startTime) => {
|
||||||
|
const layerID = typeof layer === 'string' ? layer : layer.id();
|
||||||
|
propTitle = Array.isArray(propTitle) ? propTitle.join('.') : propTitle;
|
||||||
|
|
||||||
|
if (!audioID) {
|
||||||
|
audioID = getAudioID(file);
|
||||||
|
}
|
||||||
|
const audioDomElement = new Audio(moduleFS.objectUrl(`${config.fs.idbfsAudioDir}/${file}`));
|
||||||
|
audioDomElement.loop = true;
|
||||||
|
audioDomElement.load();
|
||||||
|
//const audioDomElement = document.createElement('audio');
|
||||||
|
//audioDomElement.classList.add('invisible');
|
||||||
|
//audioDomElement.classList.add('audio_file');
|
||||||
|
//audioDomElement.classList.add('audioPlayer');
|
||||||
|
//audioDomElement.src = moduleFS.objectUrl(`${config.fs.idbfsAudioDir}/${file}`);
|
||||||
|
audioDomElement.loop = true;
|
||||||
|
audioElements.push({
|
||||||
|
audioID,
|
||||||
|
layerID,
|
||||||
|
propTitle,
|
||||||
|
audioDomElement,
|
||||||
|
file,
|
||||||
|
startTime
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this.remove = (audioID) => {
|
||||||
|
const index = audioElements.findIndex((e) => { return e.audioID === audioID; });
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
audioElements.splice(index, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.update = () => {
|
||||||
|
audioElements.forEach((audioElement, i) => {
|
||||||
|
if (tp.isPlaying() && !record.isRecording()) {
|
||||||
|
const shouldBePlaying = getAudioLayer().getValue([audioElement.audioID]);
|
||||||
|
if (shouldBePlaying !== null) {
|
||||||
|
if (shouldBePlaying && audioElement.audioDomElement.paused) {
|
||||||
|
// sequence position is always greater than startTime
|
||||||
|
// this is true, as it is written
|
||||||
|
const diff = tp.sheet.sequence.position - audioElement.startTime;
|
||||||
|
audioElement.audioDomElement.currentTime = diff;
|
||||||
|
audioElement.audioDomElement.play();
|
||||||
|
} else if (!shouldBePlaying && !audioElement.audioDomElement.paused) {
|
||||||
|
audioElement.audioDomElement.pause();
|
||||||
|
audioElement.audioDomElement.currentTime = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('AudioPlayer::update',`${audioElement.audioID} does not exist in audioLayer.`);
|
||||||
|
}
|
||||||
|
} else if (!audioElement.audioDomElement.paused) {
|
||||||
|
audioElement.audioDomElement.pause();
|
||||||
|
audioElement.audioDomElement.currentTime = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this.audioElements = audioElements;
|
||||||
|
this.init = () => {
|
||||||
|
clearInterval(updateInterval);
|
||||||
|
updateInterval = setInterval(() => {
|
||||||
|
this.update();
|
||||||
|
}, updateInterval_ms);
|
||||||
|
};
|
||||||
|
let hot = false;
|
||||||
|
let startTime = false;
|
||||||
|
this.listener = (event) => {
|
||||||
|
if (event.detail === record.possibleStates.RECORDING) {
|
||||||
|
hot = clone(record.getHot());
|
||||||
|
startTime = tp.sheet.sequence.position;
|
||||||
|
const layerIDs = Object.keys(hot);
|
||||||
|
layerIDs.forEach((layerID) => {
|
||||||
|
const propTitles = Object.keys(hot[layerID]);
|
||||||
|
propTitles.forEach((propTitle) => {
|
||||||
|
const m = audio.getMapping()[layerID][propTitle];
|
||||||
|
if (m.addToTimeline) {
|
||||||
|
if (m.source === 'microphone') {
|
||||||
|
const waitForMicrophoneListener = (event) => {
|
||||||
|
if (event.detail.fileIsRead) {
|
||||||
|
const filename = event.detail.filename;
|
||||||
|
const audioID = getAudioID(filename);
|
||||||
|
this.add(audioID, layerID, propTitle, filename, startTime);
|
||||||
|
addAudioLayerTrack(audioID, startTime, tp.sheet.sequence.position);
|
||||||
|
window.removeEventListener(
|
||||||
|
'microphoneRecorder',
|
||||||
|
waitForMicrophoneListener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener(
|
||||||
|
'microphoneRecorder',
|
||||||
|
waitForMicrophoneListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (event.detail === record.possibleStates.NOT_RECORDING) {
|
||||||
|
const layerIDs = Object.keys(hot);
|
||||||
|
layerIDs.forEach((layerID) => {
|
||||||
|
const propTitles = Object.keys(hot[layerID]);
|
||||||
|
propTitles.forEach((propTitle) => {
|
||||||
|
const m = audio.getSavedMapping()[layerID][propTitle];
|
||||||
|
if (m.addToTimeline) {
|
||||||
|
if (m.source === 'microphone') {
|
||||||
|
// handled above, as we need to wait for the microphone recording to be written and read
|
||||||
|
// and we rather start waiting, when it has definitely not happened yet
|
||||||
|
} else {
|
||||||
|
const filename = m.source;
|
||||||
|
const audioID = getAudioID(filename);
|
||||||
|
this.add(audioID, layerID, propTitle, filename, startTime);
|
||||||
|
addAudioLayerTrack(audioID, startTime, tp.sheet.sequence.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
window.removeEventListener('record', this.listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
AudioPlayer
|
||||||
|
};
|
|
@ -6,7 +6,7 @@ const config = {
|
||||||
maximumPixelDensity: 3.0,
|
maximumPixelDensity: 3.0,
|
||||||
incrementPixelDensity: 0.01,
|
incrementPixelDensity: 0.01,
|
||||||
friendlyNames: {
|
friendlyNames: {
|
||||||
'backgroundColor': 'Background<br>Color',
|
'color': 'Background<br>Color',
|
||||||
'x': 'Position X',
|
'x': 'Position X',
|
||||||
'y': 'Position Y',
|
'y': 'Position Y',
|
||||||
'width': 'Artboard Width',
|
'width': 'Artboard Width',
|
||||||
|
@ -45,6 +45,7 @@ const config = {
|
||||||
'color',
|
'color',
|
||||||
'letterDelays',
|
'letterDelays',
|
||||||
],
|
],
|
||||||
|
orderSpacing: 2,
|
||||||
friendlyNames: {
|
friendlyNames: {
|
||||||
'fontFamily': 'Font Family',
|
'fontFamily': 'Font Family',
|
||||||
'textAlignButtonsHorizontal': '',
|
'textAlignButtonsHorizontal': '',
|
||||||
|
@ -63,9 +64,9 @@ const config = {
|
||||||
'rotation': 'Rotation',
|
'rotation': 'Rotation',
|
||||||
'transformOrigin': 'Rotation Origin',
|
'transformOrigin': 'Rotation Origin',
|
||||||
'mirror_x': 'Mirror X',
|
'mirror_x': 'Mirror X',
|
||||||
'mirror_x_distance': 'Mirror X Distance',
|
'mirror_x_distance': 'X Distance',
|
||||||
'mirror_y': 'Mirror Y',
|
'mirror_y': 'Mirror Y',
|
||||||
'mirror_y_distance': 'Mirror Y Distance',
|
'mirror_y_distance': 'Y Distance',
|
||||||
'mirror_xy': 'Mirrox XY',
|
'mirror_xy': 'Mirrox XY',
|
||||||
'color': 'Color',
|
'color': 'Color',
|
||||||
'letterDelays': 'Letter Delays',
|
'letterDelays': 'Letter Delays',
|
||||||
|
@ -81,6 +82,68 @@ const config = {
|
||||||
zoomBaseFactor: 0.001,
|
zoomBaseFactor: 0.001,
|
||||||
zoomDynamicMax: 42,
|
zoomDynamicMax: 42,
|
||||||
},
|
},
|
||||||
|
audio: {
|
||||||
|
defaultRange: { // check audio.getDefaultRange for dynamic defaults
|
||||||
|
'textAlignment': [0, 1],
|
||||||
|
'fontSize_px': [42, 100],
|
||||||
|
'letterSpacing': [0, 1],
|
||||||
|
'lineHeight': [0, 1],
|
||||||
|
'rotation': [0, 180],
|
||||||
|
'mirror_x_distance': [0, 200],
|
||||||
|
'mirror_y_distance': [0, 70],
|
||||||
|
'color': [{'r': 0, 'g': 0, 'b': 0, 'a': 1}, {'r': 1, 'g': 1, 'b': 1, 'a': 1}],
|
||||||
|
'letterDelays': [0, 1000],
|
||||||
|
},
|
||||||
|
ignoreProps: {
|
||||||
|
artboard: ['x', 'y', 'zoom', 'pixelDensity', 'width', 'height'],
|
||||||
|
layer: ['transformOrigin', 'fontFamily', 'text', 'mirror_x', 'mirror_y', 'mirror_xy', 'height'],
|
||||||
|
},
|
||||||
|
maxFilenameLength: 24,
|
||||||
|
defaultSmoothing: 0.5,
|
||||||
|
analyser: {
|
||||||
|
fftSize: 256 * 8,
|
||||||
|
minDecibels: -90,
|
||||||
|
maxDecibels: -10,
|
||||||
|
smoothingTimeConstant: 0.85,
|
||||||
|
},
|
||||||
|
fftBandsAnalysed: 256 * 8,
|
||||||
|
fftBandsUsed: 256 / 2,
|
||||||
|
fftHeight: 256 / 4,
|
||||||
|
ignoreOutboundFrequencies: true,
|
||||||
|
pitchCombineFrequencies: false,
|
||||||
|
rolloverResetLoop: true,
|
||||||
|
colors: {
|
||||||
|
background_fill: 'hsla(166, 74%, 20%, 1.0)',
|
||||||
|
select_fill: 'hsla(166, 52%, 42%, 1.0)',
|
||||||
|
select_stroke: 'hsla(166, 74%, 92%, 1.0)',
|
||||||
|
select_stroke_width: 1,
|
||||||
|
highlight_stroke: 'hsla(64, 100%, 64%, 1.0)',
|
||||||
|
highlight_stroke_width: 1,
|
||||||
|
highlight_2_stroke: 'hsla(166, 74%, 64%, 1.0)',
|
||||||
|
frequency_fill: 'hsla(166, 74%, 84%, 1.0)',
|
||||||
|
options_r_background: 'hsla(360, 100%, 50%, 0.2)',
|
||||||
|
options_g_background: 'hsla(120, 100%, 50%, 0.2)',
|
||||||
|
options_b_background: 'hsla(240, 100%, 50%, 0.2)',
|
||||||
|
options_a_background: 'hsla(0, 0%, 100%, 0.2)',
|
||||||
|
options_background: 'hsla(0, 0%, 64%, 0.2)',
|
||||||
|
// overlay is the div used for mouse interactions
|
||||||
|
// it spans over the whole canvas.
|
||||||
|
// it can be used to adjust the color underneath.
|
||||||
|
overlay: 'rgba(28, 186, 148, 0.0)',
|
||||||
|
// gradient needs backgroundImage not backgroundColor in audio.js
|
||||||
|
//overlay: 'linear-gradient(red, yellow)',
|
||||||
|
overlay_stroke: 'none',
|
||||||
|
overlay_blendmode: 'color',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
record: {
|
||||||
|
active: true,
|
||||||
|
ignoreProps: {
|
||||||
|
artboard: ['x', 'y', 'zoom', 'pixelDensity', 'width', 'height'],
|
||||||
|
layer: ['transformOrigin', 'fontFamily', 'text', 'mirror_x', 'mirror_y', 'mirror_xy'],
|
||||||
|
},
|
||||||
|
recordMapped: true,
|
||||||
|
},
|
||||||
midi: {
|
midi: {
|
||||||
touchTimeThreshold_s: 0.5,
|
touchTimeThreshold_s: 0.5,
|
||||||
smoothingMix: 0.1,
|
smoothingMix: 0.1,
|
||||||
|
@ -88,6 +151,7 @@ const config = {
|
||||||
fs: {
|
fs: {
|
||||||
idbfsDir: '/idbfs',
|
idbfsDir: '/idbfs',
|
||||||
idbfsFontDir: '/idbfs/fonts',
|
idbfsFontDir: '/idbfs/fonts',
|
||||||
|
idbfsAudioDir: '/idbfs/audio',
|
||||||
idbfsTmpDir: '/idbfs/tmp',
|
idbfsTmpDir: '/idbfs/tmp',
|
||||||
},
|
},
|
||||||
timeline: {
|
timeline: {
|
|
@ -200,7 +200,12 @@ const Exporter = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateArtboardOptions = () => {
|
const updateArtboardOptions = () => {
|
||||||
options.artboard = {...options.artboard, ...Module.getArtboardProps()};
|
const artboardValues = clone(Module.getArtboardProps());
|
||||||
|
if (typeof artboardValues.backgroundColor === 'object') {
|
||||||
|
artboardValues.color = artboardValues.backgroundColor;
|
||||||
|
delete artboardValues.backgroundColor;
|
||||||
|
}
|
||||||
|
options.artboard = {...options.artboard, ...artboardValues};
|
||||||
//options.artboard.width = getArtboard().theatreObject.value.width;
|
//options.artboard.width = getArtboard().theatreObject.value.width;
|
||||||
//options.artboard.height = getArtboard().theatreObject.value.height;
|
//options.artboard.height = getArtboard().theatreObject.value.height;
|
||||||
options.artboard.pixelDensity = getArtboard().theatreObject.value.pixelDensity;
|
options.artboard.pixelDensity = getArtboard().theatreObject.value.pixelDensity;
|
||||||
|
@ -218,6 +223,10 @@ const Exporter = function() {
|
||||||
|
|
||||||
const setArtboardPropsToRenderDimensions = () => {
|
const setArtboardPropsToRenderDimensions = () => {
|
||||||
const artboardValues = clone(options.artboard);//{...options.artboard, ...renderDimensions};
|
const artboardValues = clone(options.artboard);//{...options.artboard, ...renderDimensions};
|
||||||
|
if (typeof artboardValues.backgroundColor === 'object') {
|
||||||
|
artboardValues.color = artboardValues.backgroundColor;
|
||||||
|
delete artboardValues.backgroundColor;
|
||||||
|
}
|
||||||
const densityRatio = renderDimensions.width / options.artboard.width;
|
const densityRatio = renderDimensions.width / options.artboard.width;
|
||||||
//artboardValues.pixelDensity *= densityRatio;
|
//artboardValues.pixelDensity *= densityRatio;
|
||||||
artboardValues.pixelDensity = densityRatio;
|
artboardValues.pixelDensity = densityRatio;
|
||||||
|
@ -227,6 +236,10 @@ const Exporter = function() {
|
||||||
|| currentArtboardValues.height !== artboardCppProps.height
|
|| currentArtboardValues.height !== artboardCppProps.height
|
||||||
|| currentArtboardValues.pixelDensity !== artboardCppProps.pixelDensity) {
|
|| currentArtboardValues.pixelDensity !== artboardCppProps.pixelDensity) {
|
||||||
window.isRenderDirty = true;
|
window.isRenderDirty = true;
|
||||||
|
if (typeof artboardCppProps.color === 'object') {
|
||||||
|
artboardCppProps.backgroundColor = artboardCppProps.color;
|
||||||
|
delete artboardCppProps.color;
|
||||||
|
}
|
||||||
Module.setArtboardProps(artboardCppProps);
|
Module.setArtboardProps(artboardCppProps);
|
||||||
}
|
}
|
||||||
Module.setTimeScale(renderDimensions.timeScale);
|
Module.setTimeScale(renderDimensions.timeScale);
|
|
@ -55,6 +55,7 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
text: config.layer.defaultTexts[Math.floor(Math.random()*config.layer.defaultTexts.length)],
|
text: config.layer.defaultTexts[Math.floor(Math.random()*config.layer.defaultTexts.length)],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let updateValuesViaTheatre = true;
|
||||||
let lastValues = {};
|
let lastValues = {};
|
||||||
let fontsHashMap = {};
|
let fontsHashMap = {};
|
||||||
let sequenceEventBuffer = [];
|
let sequenceEventBuffer = [];
|
||||||
|
@ -127,26 +128,21 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// NOTE: stupid hack, seems that theatrejs tries to be too smart
|
// NOTE: stupid hack, seems that theatrejs tries to be too smart
|
||||||
// detecting if reconfiguring the object is necessary.
|
// detecting if reconfiguring the object is necessary.
|
||||||
// file bug report and test in future versions.
|
// file bug report and test in future versions?
|
||||||
//
|
//
|
||||||
// this overcomplicates some of our code though.. urgh..
|
// this overcomplicates some of our code though.. urgh..
|
||||||
// btw, we need a dummy property
|
// btw, we need a dummy property
|
||||||
// this does not work
|
// this does not work
|
||||||
//tp.changeObject(this.id(), {});
|
//tp.changeObject(this.id(), {});
|
||||||
|
|
||||||
//tp.changeObject(this.id(), {dummy:true});
|
|
||||||
//updateTheatrePropsTimeout = setTimeout(() => {
|
|
||||||
tp.changeObject(this.id(), {dummy: true});
|
tp.changeObject(this.id(), {dummy: true});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
tp.changeObject(this.id(), props);
|
tp.changeObject(this.id(), props);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
//updateTheatrePropsTimeout = false;
|
|
||||||
//this.afterUpdateTheatrePropsCallback();
|
|
||||||
this.findInjectPanel();
|
this.findInjectPanel();
|
||||||
resolve();
|
resolve();
|
||||||
}, 100);
|
}, 100);
|
||||||
}, 100);
|
}, 100);
|
||||||
//}, 100);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const getDefaultFont = (selectableFonts) => {
|
const getDefaultFont = (selectableFonts) => {
|
||||||
|
@ -204,6 +200,8 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
const onValuesChange = (values) => {
|
const onValuesChange = (values) => {
|
||||||
if (values.dummy === true)
|
if (values.dummy === true)
|
||||||
return;
|
return;
|
||||||
|
if (!updateValuesViaTheatre)
|
||||||
|
return;
|
||||||
window.isRenderDirty = true;
|
window.isRenderDirty = true;
|
||||||
if (Object.keys(values).length > 1) {
|
if (Object.keys(values).length > 1) {
|
||||||
values.text = values.text.trim();
|
values.text = values.text.trim();
|
||||||
|
@ -275,28 +273,19 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
|
|
||||||
const showBoundingBoxDiv = (boundingBox = false) => {
|
const showBoundingBoxDiv = (boundingBox = false) => {
|
||||||
const boundingBoxDivId = `boundingBox-${this.id()}`;
|
const boundingBoxDivId = `boundingBox-${this.id()}`;
|
||||||
//const layerBoxDivId = `layerBox-${this.id()}`;
|
|
||||||
if (document.querySelector(`#${boundingBoxDivId}`) === null) {
|
if (document.querySelector(`#${boundingBoxDivId}`) === null) {
|
||||||
if (boundingBox === false) {
|
if (boundingBox === false) {
|
||||||
boundingBox = Module.getBoundingBox(this.id());
|
boundingBox = Module.getBoundingBox(this.id());
|
||||||
}
|
}
|
||||||
const artboard = window.getArtboard();
|
const artboard = window.getArtboard();
|
||||||
const boundingBoxDiv = document.createElement('div');
|
const boundingBoxDiv = document.createElement('div');
|
||||||
//const layerBoxDiv = document.createElement('div');
|
|
||||||
boundingBoxDiv.id = boundingBoxDivId;
|
boundingBoxDiv.id = boundingBoxDivId;
|
||||||
boundingBoxDiv.style.position = 'fixed';
|
boundingBoxDiv.style.position = 'fixed';
|
||||||
boundingBoxDiv.style.background = 'transparent';
|
boundingBoxDiv.style.background = 'transparent';
|
||||||
boundingBoxDiv.style.border = '1px dashed dimgrey';
|
boundingBoxDiv.style.border = '1px dashed dimgrey';
|
||||||
boundingBoxDiv.style.boxSizing = 'border-box';
|
boundingBoxDiv.style.boxSizing = 'border-box';
|
||||||
|
|
||||||
//layerBoxDiv.id = layerBoxDivId;
|
|
||||||
//layerBoxDiv.style.position = 'fixed';
|
|
||||||
//layerBoxDiv.style.background = 'transparent';
|
|
||||||
//layerBoxDiv.style.border = '1px solid green';
|
|
||||||
//layerBoxDiv.style.boxSizing = 'border-box';
|
|
||||||
|
|
||||||
document.getElementById('body').append(boundingBoxDiv);
|
document.getElementById('body').append(boundingBoxDiv);
|
||||||
//document.getElementById('body').append(layerBoxDiv);
|
|
||||||
clearInterval(boundingBoxInterval);
|
clearInterval(boundingBoxInterval);
|
||||||
boundingBoxInterval = setInterval(() => {
|
boundingBoxInterval = setInterval(() => {
|
||||||
updateBoundingBoxDiv();
|
updateBoundingBoxDiv();
|
||||||
|
@ -311,7 +300,6 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
if (bb !== null) {
|
if (bb !== null) {
|
||||||
bb.remove();
|
bb.remove();
|
||||||
}
|
}
|
||||||
//document.getElementById(`layerBox-${this.id()}`).remove();
|
|
||||||
clearInterval(boundingBoxInterval);
|
clearInterval(boundingBoxInterval);
|
||||||
boundingBoxInterval = false;
|
boundingBoxInterval = false;
|
||||||
};
|
};
|
||||||
|
@ -370,7 +358,7 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
set(this.theatreObject.props, values);
|
set(this.theatreObject.props, values);
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
promises.push(this.findInjectPanelPromise);
|
promises.push(this.findInjectPanelPromise());
|
||||||
Promise.allSettled(promises).then(() => {
|
Promise.allSettled(promises).then(() => {
|
||||||
resolve();
|
resolve();
|
||||||
if (config.autoSave && window.isInitialized) {
|
if (config.autoSave && window.isInitialized) {
|
||||||
|
@ -425,7 +413,6 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
mom.append(upButton);
|
mom.append(upButton);
|
||||||
mom.append(downButton);
|
mom.append(downButton);
|
||||||
mom.append(removeButton);
|
mom.append(removeButton);
|
||||||
//window.layerOrder.add(this.id());
|
|
||||||
|
|
||||||
panel.addEventListener('mouseover', showBoundingBoxDiv);
|
panel.addEventListener('mouseover', showBoundingBoxDiv);
|
||||||
panel.addEventListener('mouseout', hideBoundingBoxDiv);
|
panel.addEventListener('mouseout', hideBoundingBoxDiv);
|
||||||
|
@ -460,11 +447,14 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
const panelProp= tp.getPanelPropTitle(propName);
|
const panelProp= tp.getPanelPropTitle(propName);
|
||||||
const panelPropMom = tp.getPanelPropContainer(panelProp);
|
const panelPropMom = tp.getPanelPropContainer(panelProp);
|
||||||
const newValue = active ? getArtboard().theatreObject.value.width : 0;
|
const newValue = active ? getArtboard().theatreObject.value.width : 0;
|
||||||
|
const textWrappingButton = tp.getPanel().querySelector('.textWrappingButton');
|
||||||
|
|
||||||
|
if (panelPropMom !== null && textWrappingButton !== null) {
|
||||||
if (active) {
|
if (active) {
|
||||||
panelPropMom.style.display = 'flex';
|
panelPropMom.style.display = 'flex';
|
||||||
tp.getPanel().querySelector('.textWrappingButton').classList.add('active');
|
textWrappingButton.classList.add('active');
|
||||||
} else {
|
} else {
|
||||||
tp.getPanel().querySelector('.textWrappingButton').classList.remove('active');
|
textWrappingButton.classList.remove('active');
|
||||||
panelPropMom.style.display = 'none';
|
panelPropMom.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,6 +466,7 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// phew.. so we don't want to get into typescript and react atm
|
// phew.. so we don't want to get into typescript and react atm
|
||||||
|
@ -489,6 +480,9 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
};
|
};
|
||||||
this.findInjectPanel = (resolve = false) => {
|
this.findInjectPanel = (resolve = false) => {
|
||||||
if (tp.studio.selection.length === 0 || (tp.studio.selection.length > 0 && tp.studio.selection[0].address.objectKey !== this.id())) {
|
if (tp.studio.selection.length === 0 || (tp.studio.selection.length > 0 && tp.studio.selection[0].address.objectKey !== this.id())) {
|
||||||
|
if (typeof resolve === 'function') {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const panel = tp.getPanel();
|
const panel = tp.getPanel();
|
||||||
|
@ -509,17 +503,33 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
const panelPropContainer = tp.getPanelPropContainer(panelPropTitle);
|
const panelPropContainer = tp.getPanelPropContainer(panelPropTitle);
|
||||||
if (panelPropContainer !== null) {
|
if (panelPropContainer !== null) {
|
||||||
panelPropContainers[propKey] = panelPropContainer;
|
panelPropContainers[propKey] = panelPropContainer;
|
||||||
const order_index = panelOrder.indexOf(propKey);
|
const order_index = panelOrder.indexOf(propKey) * config.layer.orderSpacing;
|
||||||
panelPropContainer.style.order = order_index;
|
panelPropContainer.style.order = order_index;
|
||||||
if (propKey === 'fontVariationAxes'
|
if (propKey === 'fontVariationAxes'
|
||||||
|| propKey === 'letterDelays') {
|
|| propKey === 'letterDelays') {
|
||||||
panelPropContainer
|
panelPropContainer
|
||||||
.classList
|
.classList
|
||||||
.add(`${propKey}ContWrapper`);
|
.add(`${propKey}ContWrapper`);
|
||||||
|
panelPropContainer
|
||||||
|
.classList
|
||||||
|
.add(`propContWrapper`);
|
||||||
} else {
|
} else {
|
||||||
panelPropContainer
|
panelPropContainer
|
||||||
.classList
|
.classList
|
||||||
.add(`${propKey}Wrapper`);
|
.add(`${propKey}Wrapper`);
|
||||||
|
panelPropContainer
|
||||||
|
.classList
|
||||||
|
.add(`propWrapper`);
|
||||||
|
const inputElement = panelPropContainer
|
||||||
|
.querySelector('input');
|
||||||
|
if (inputElement !== null) {
|
||||||
|
inputElement
|
||||||
|
.classList
|
||||||
|
.add(`${propKey}Input`);
|
||||||
|
inputElement
|
||||||
|
.classList
|
||||||
|
.add(`propInput`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('Layer::findInjectPanel',
|
console.log('Layer::findInjectPanel',
|
||||||
|
@ -545,11 +555,29 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
const subFriendlyName = config.layer.friendlyNames[subUnfriendlyName];
|
const subFriendlyName = config.layer.friendlyNames[subUnfriendlyName];
|
||||||
if (e.innerHTML === subUnfriendlyName) {
|
if (e.innerHTML === subUnfriendlyName) {
|
||||||
e.innerHTML = subFriendlyName;
|
e.innerHTML = subFriendlyName;
|
||||||
|
e.classList.add('propTitle');
|
||||||
|
e.classList.add(`${unfriendlyName}_${subUnfriendlyName}Title`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
panelPropTitles[unfriendlyName].innerHTML = friendlyName;
|
panelPropTitles[unfriendlyName].innerHTML = friendlyName;
|
||||||
|
panelPropTitles[unfriendlyName].classList.add('propTitle');
|
||||||
|
panelPropTitles[unfriendlyName].classList.add(`${unfriendlyName}Title`);
|
||||||
|
|
||||||
|
let panelPropContainer = panelPropContainers[unfriendlyName];
|
||||||
|
|
||||||
|
for (let i = 0; i < panelPropContainer.children.length; i++) {
|
||||||
|
const child = panelPropContainer.children[i];
|
||||||
|
if (child.querySelector('input') !== null) {
|
||||||
|
child.classList.add(`${unfriendlyName}InputWrapper`);
|
||||||
|
child.classList.add('propInputWrapper');
|
||||||
|
}
|
||||||
|
if (child.contains(panelPropTitles[unfriendlyName])) {
|
||||||
|
child.classList.add(`${unfriendlyName}TitleWrapper`);
|
||||||
|
child.classList.add('propTitleWrapper');
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -582,7 +610,7 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
alignCenterButton.innerHTML = `<img src="/web/assets/align-center-horizontal.svg" alt="alignCenter" />`;
|
alignCenterButton.innerHTML = `<img src="/web/assets/align-center-horizontal.svg" alt="alignCenter" />`;
|
||||||
alignRightButton.innerHTML = `<img src="/web/assets/align-right.svg" alt="alignRight" />`;
|
alignRightButton.innerHTML = `<img src="/web/assets/align-right.svg" alt="alignRight" />`;
|
||||||
panelControlsWrapper.append(alignButtons);
|
panelControlsWrapper.append(alignButtons);
|
||||||
const order_index = panelOrder.indexOf('alignButtonsHorizontal');
|
const order_index = panelOrder.indexOf('alignButtonsHorizontal') * config.layer.orderSpacing;
|
||||||
alignButtons.style.order = order_index;
|
alignButtons.style.order = order_index;
|
||||||
alignButtons.classList.add('alignButtons');
|
alignButtons.classList.add('alignButtons');
|
||||||
alignButtons.classList.add('alignButtonsHorizontal');
|
alignButtons.classList.add('alignButtonsHorizontal');
|
||||||
|
@ -637,7 +665,7 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
alignCenterButton.innerHTML = `<img src="/web/assets/align-center-vertical.svg" alt="alignCenter" />`;
|
alignCenterButton.innerHTML = `<img src="/web/assets/align-center-vertical.svg" alt="alignCenter" />`;
|
||||||
alignBottomButton.innerHTML = `<img src="/web/assets/align-bottom.svg" alt="alignBottom" />`;
|
alignBottomButton.innerHTML = `<img src="/web/assets/align-bottom.svg" alt="alignBottom" />`;
|
||||||
panelControlsWrapper.append(alignButtons);
|
panelControlsWrapper.append(alignButtons);
|
||||||
const order_index = panelOrder.indexOf('alignButtonsVertical');
|
const order_index = panelOrder.indexOf('alignButtonsVertical') * config.layer.orderSpacing;
|
||||||
alignButtons.style.order = order_index;
|
alignButtons.style.order = order_index;
|
||||||
alignButtons.classList.add('alignButtons');
|
alignButtons.classList.add('alignButtons');
|
||||||
alignButtons.classList.add('alignButtonsVertical');
|
alignButtons.classList.add('alignButtonsVertical');
|
||||||
|
@ -684,8 +712,6 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
|
|
||||||
const alignButtonsHorizontal = panel.querySelector('.alignButtonsHorizontal');
|
const alignButtonsHorizontal = panel.querySelector('.alignButtonsHorizontal');
|
||||||
const alignButtonsVertical = panel.querySelector('.alignButtonsVertical');
|
const alignButtonsVertical = panel.querySelector('.alignButtonsVertical');
|
||||||
//panelControlsWrapper.add(alignButtonsHorizontal);
|
|
||||||
//panelControlsWrapper.add(alignButtonsVertical);
|
|
||||||
|
|
||||||
// first get previous textAlign buttons,
|
// first get previous textAlign buttons,
|
||||||
// if they are already there
|
// if they are already there
|
||||||
|
@ -705,7 +731,7 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
textAlignCenterButton.innerHTML = `<img src="/web/assets/align-text-center.svg" alt="textAlignCenter" />`;
|
textAlignCenterButton.innerHTML = `<img src="/web/assets/align-text-center.svg" alt="textAlignCenter" />`;
|
||||||
textAlignRightButton.innerHTML = `<img src="/web/assets/align-text-right.svg" alt="textAlignRight" />`;
|
textAlignRightButton.innerHTML = `<img src="/web/assets/align-text-right.svg" alt="textAlignRight" />`;
|
||||||
textWrappingButton.innerHTML = `<img src="/web/assets/text-wrapping.svg" alt="textWrapping" />`;
|
textWrappingButton.innerHTML = `<img src="/web/assets/text-wrapping.svg" alt="textWrapping" />`;
|
||||||
const order_index = panelOrder.indexOf('textAlignButtonsHorizontal');
|
const order_index = panelOrder.indexOf('textAlignButtonsHorizontal') * config.layer.orderSpacing;
|
||||||
textAlignButtons.style.order = order_index;
|
textAlignButtons.style.order = order_index;
|
||||||
panelControlsWrapper.append(textAlignButtons);
|
panelControlsWrapper.append(textAlignButtons);
|
||||||
textAlignButtons.classList.add('textAlignButtons');
|
textAlignButtons.classList.add('textAlignButtons');
|
||||||
|
@ -743,24 +769,6 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
}
|
}
|
||||||
togglePanelProp('width', this.theatreObject.value.width > 0, false);
|
togglePanelProp('width', this.theatreObject.value.width > 0, false);
|
||||||
|
|
||||||
let bottomButtonsContainer = panel.querySelector('.bottomButtonsContainer');
|
|
||||||
if (bottomButtonsContainer === null) {
|
|
||||||
bottomButtonsContainer = document.createElement('div');
|
|
||||||
bottomButtonsContainer.classList.add("bottomButtonsContainer");
|
|
||||||
panel.append(bottomButtonsContainer);
|
|
||||||
}
|
|
||||||
if (bottomButtonsContainer.querySelector('.vte_button') === null) {
|
|
||||||
const addFontButton = document.createElement('div');
|
|
||||||
addFontButton.classList.add('vte_button');
|
|
||||||
addFontButton.style.cursor = 'pointer';
|
|
||||||
addFontButton.innerHTML = "add Font";
|
|
||||||
addFontButton.addEventListener('click', (clickEvent) => {
|
|
||||||
addUserFont();
|
|
||||||
});
|
|
||||||
bottomButtonsContainer.append(addFontButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
doItAgain = false;
|
doItAgain = false;
|
||||||
clearTimeout(panelFinderTimeout);
|
clearTimeout(panelFinderTimeout);
|
||||||
panelFinderTimeout = false;
|
panelFinderTimeout = false;
|
||||||
|
@ -792,8 +800,27 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
panel.addEventListener("mouseover", showBoundingBoxDivIfSelected);
|
panel.addEventListener("mouseover", showBoundingBoxDivIfSelected);
|
||||||
panel.addEventListener("mouseleave", hideBoundingBoxDiv);
|
panel.addEventListener("mouseleave", hideBoundingBoxDiv);
|
||||||
|
|
||||||
|
// should we have an audio object, let's inject the buttons, etc
|
||||||
|
if (typeof audio === 'object' && typeof audio.injectPanel === 'function') {
|
||||||
|
const success = audio.injectPanel(this);
|
||||||
|
if (success) {
|
||||||
|
const e = new CustomEvent('injectedAll', {});
|
||||||
|
tp.getPanel().dispatchEvent(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Layer::findInjectPanel', `cannot inject audio panel for ${this.id()} for some reason.`);
|
||||||
|
}
|
||||||
|
// should we have a record object, let's inject the buttons, etc
|
||||||
|
if (config.record.active) {
|
||||||
|
if (typeof record === 'object' && record.hasOwnProperty('injectPanel')) {
|
||||||
|
record.injectPanel(this);
|
||||||
|
} else {
|
||||||
|
console.log('Layer::findInjectPanel', `cannot inject record panel for ${this.id()} for some reason.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
injectedPanel = true;
|
injectedPanel = true;
|
||||||
const detail = {titles: Object.keys(panelPropTitles), containers: Object.keys(panelPropContainers)};
|
const detail = {layerID: this.id(), titles: Object.keys(panelPropTitles), containers: Object.keys(panelPropContainers)};
|
||||||
const e = new CustomEvent('injected', {detail});
|
const e = new CustomEvent('injected', {detail});
|
||||||
tp.getPanel().dispatchEvent(e);
|
tp.getPanel().dispatchEvent(e);
|
||||||
if (typeof resolve === 'function') {
|
if (typeof resolve === 'function') {
|
||||||
|
@ -820,43 +847,6 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
}
|
}
|
||||||
return letterDelays.hasOwnProperty(prop[0]);
|
return letterDelays.hasOwnProperty(prop[0]);
|
||||||
};
|
};
|
||||||
const addUserFont = () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uploadFile('font')
|
|
||||||
.then((fontFile) => {
|
|
||||||
moduleFS
|
|
||||||
.save(fontFile)
|
|
||||||
.then(() => {
|
|
||||||
getFontsAndAxes()
|
|
||||||
.then((newFontsAndAxes) => {
|
|
||||||
// let's select the new uploaded font
|
|
||||||
if (newFontsAndAxes.length > 0) {
|
|
||||||
const path = newFontsAndAxes[0].fontPath;
|
|
||||||
this.selectFont(path);
|
|
||||||
// we need to update theatreprops
|
|
||||||
// a bit awkwardly twice
|
|
||||||
//
|
|
||||||
// first like this
|
|
||||||
this.updateTheatreProps()
|
|
||||||
.then(() => {
|
|
||||||
// and again with a transaction
|
|
||||||
const hash = props.fontFamily.default;
|
|
||||||
tp.studio.transaction(({
|
|
||||||
set
|
|
||||||
}) => {
|
|
||||||
set(this.theatreObject.props.fontFamily, hash);
|
|
||||||
});
|
|
||||||
this.findInjectPanel();
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
this.handleSequenceEvent = (detail, updateTheatre = true) => {
|
this.handleSequenceEvent = (detail, updateTheatre = true) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
tp.friendlySequenceNames();
|
tp.friendlySequenceNames();
|
||||||
|
@ -1030,6 +1020,9 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
||||||
return cppProps;
|
return cppProps;
|
||||||
};
|
};
|
||||||
this.onValuesChange = onValuesChange;
|
this.onValuesChange = onValuesChange;
|
||||||
|
this.updateValuesViaTheatre = (doIt) => {
|
||||||
|
updateValuesViaTheatre = doIt;
|
||||||
|
};
|
||||||
this.prepareForDepartureFromThisBeautifulExperience = () => {
|
this.prepareForDepartureFromThisBeautifulExperience = () => {
|
||||||
this.hide();
|
this.hide();
|
||||||
hideBoundingBoxDiv();
|
hideBoundingBoxDiv();
|
|
@ -26,6 +26,17 @@ import {
|
||||||
ModuleFS
|
ModuleFS
|
||||||
} from './moduleFS.js';
|
} from './moduleFS.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Audio
|
||||||
|
} from './audio.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Record
|
||||||
|
} from './record.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AudioLayer
|
||||||
|
} from './audioLayer.js';
|
||||||
//import {
|
//import {
|
||||||
//MidiController
|
//MidiController
|
||||||
//} from './midiController.js';
|
//} from './midiController.js';
|
||||||
|
@ -44,27 +55,35 @@ import {
|
||||||
Config
|
Config
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
|
|
||||||
window.uploadFile = uploadFile;
|
|
||||||
window.downloadFile = downloadFile;
|
|
||||||
window.isInitialized = false;
|
|
||||||
window.hashFromString = hashFromString;
|
|
||||||
|
|
||||||
const config = new Config();
|
const config = new Config();
|
||||||
window.config = config;
|
|
||||||
const tp = new TheatrePlay();
|
const tp = new TheatrePlay();
|
||||||
window.tp = tp;
|
|
||||||
const layers = [];
|
const layers = [];
|
||||||
const layersById = {};
|
const layersById = {};
|
||||||
const layerOrder = new LayerOrder();
|
const layerOrder = new LayerOrder();
|
||||||
window.layerOrder = layerOrder;
|
|
||||||
const fontsAndAxes = [];
|
const fontsAndAxes = [];
|
||||||
let artboard;
|
let artboard;
|
||||||
const exporter = new Exporter();
|
const exporter = new Exporter();
|
||||||
const interactor = new Interactor();
|
const interactor = new Interactor();
|
||||||
const moduleFS = new ModuleFS();
|
const moduleFS = new ModuleFS();
|
||||||
window.moduleFS = moduleFS;
|
const record = new Record(tp);
|
||||||
|
const audio = new Audio(tp, record);
|
||||||
|
const audioLayers = [];
|
||||||
|
|
||||||
|
// globally reachables
|
||||||
|
// classes
|
||||||
|
window.config = config;
|
||||||
|
window.tp = tp;
|
||||||
|
window.layerOrder = layerOrder;
|
||||||
|
window.moduleFS = moduleFS;
|
||||||
|
window.record = record;
|
||||||
|
window.audio = audio;
|
||||||
|
// utilities
|
||||||
|
window.uploadFile = uploadFile;
|
||||||
|
window.downloadFile = downloadFile;
|
||||||
|
window.isInitialized = false;
|
||||||
window.panelFinderTimeout = false;
|
window.panelFinderTimeout = false;
|
||||||
|
|
||||||
const sequenceEventBuffer = {};
|
const sequenceEventBuffer = {};
|
||||||
|
|
||||||
//const midiController = new MidiController();
|
//const midiController = new MidiController();
|
||||||
|
@ -86,7 +105,7 @@ const getAbout = () => {
|
||||||
const buttonExp = textParent.querySelector(".expandText");
|
const buttonExp = textParent.querySelector(".expandText");
|
||||||
|
|
||||||
if (buttonExp === null) {
|
if (buttonExp === null) {
|
||||||
console.error("Could not find .expandText within .textParent");
|
console.error("Main::getAbout","Could not find .expandText within .textParent");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,10 +128,7 @@ window.showAbout = () => {
|
||||||
if (getAbout() === null) {
|
if (getAbout() === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAbout().classList.remove("hidden");
|
getAbout().classList.remove("hidden");
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
window.hideAbout = () => {
|
window.hideAbout = () => {
|
||||||
if (getAbout() !== null) {
|
if (getAbout() !== null) {
|
||||||
|
@ -139,6 +155,16 @@ const findInjectPanel = () => {
|
||||||
bottomButtonsContainer.classList.add("bottomButtonsContainer");
|
bottomButtonsContainer.classList.add("bottomButtonsContainer");
|
||||||
panel.append(bottomButtonsContainer);
|
panel.append(bottomButtonsContainer);
|
||||||
}
|
}
|
||||||
|
const hideuiButton = document.querySelector('#hide_ui');
|
||||||
|
if (hideuiButton !== null) {
|
||||||
|
bottomButtonsContainer.append(hideuiButton);
|
||||||
|
hideuiButton.classList.add("main_panel_button");
|
||||||
|
}
|
||||||
|
const audiofileButton = document.querySelector('#upload_audio');
|
||||||
|
if (audiofileButton !== null) {
|
||||||
|
bottomButtonsContainer.append(audiofileButton);
|
||||||
|
audiofileButton.classList.add("main_panel_button");
|
||||||
|
}
|
||||||
const exportButton = document.querySelector('#exporter_open');
|
const exportButton = document.querySelector('#exporter_open');
|
||||||
if (exportButton !== null) {
|
if (exportButton !== null) {
|
||||||
bottomButtonsContainer.append(exportButton);
|
bottomButtonsContainer.append(exportButton);
|
||||||
|
@ -164,6 +190,11 @@ const findInjectPanel = () => {
|
||||||
bottomButtonsContainer.append(startNewButton);
|
bottomButtonsContainer.append(startNewButton);
|
||||||
startNewButton.classList.add("main_panel_button");
|
startNewButton.classList.add("main_panel_button");
|
||||||
}
|
}
|
||||||
|
const addFontButton = document.querySelector('#add_font');
|
||||||
|
if (addFontButton !== null) {
|
||||||
|
bottomButtonsContainer.append(addFontButton);
|
||||||
|
addFontButton.classList.add("main_panel_button");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
findInjectPanel();
|
findInjectPanel();
|
||||||
|
@ -223,7 +254,7 @@ window.onload = () => {
|
||||||
}
|
}
|
||||||
tp.studio.onSelectionChange((newSelection) => {
|
tp.studio.onSelectionChange((newSelection) => {
|
||||||
if (newSelection.length > 0) {
|
if (newSelection.length > 0) {
|
||||||
[getArtboard(), getLayers()].flat().forEach((e) => {
|
[getArtboard(), getLayers(), getAudioLayers()].flat().forEach((e) => {
|
||||||
if (e.id() === newSelection[0].address.objectKey) {
|
if (e.id() === newSelection[0].address.objectKey) {
|
||||||
if (e.id().indexOf('layer-') === 0) {
|
if (e.id().indexOf('layer-') === 0) {
|
||||||
e.findInjectPanel();
|
e.findInjectPanel();
|
||||||
|
@ -233,12 +264,15 @@ window.onload = () => {
|
||||||
}, 60);
|
}, 60);
|
||||||
} else if (e.id() === 'artboard') {
|
} else if (e.id() === 'artboard') {
|
||||||
e.findInjectPanel();
|
e.findInjectPanel();
|
||||||
|
} else if (e.id().indexOf('audio') === 0) {
|
||||||
|
e.findInjectPanel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
findInjectPanel();
|
findInjectPanel();
|
||||||
tp.friendlySequenceNames();
|
tp.friendlySequenceNames();
|
||||||
|
console.log('Main::selectionChange',newSelection.length > 0 ? newSelection[0].address.objectKey : 'aah nothing');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// ABOUT BEGIN
|
// ABOUT BEGIN
|
||||||
|
@ -299,8 +333,31 @@ const resize = () => {
|
||||||
Module.windowResized(Math.round(width * ratio), Math.round(height * ratio));
|
Module.windowResized(Math.round(width * ratio), Math.round(height * ratio));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setLoadingDoneHook = () => {
|
||||||
|
let addedListener = false;
|
||||||
|
const loadingInjectInterval = setInterval(() => {
|
||||||
|
if (!addedListener) {
|
||||||
|
const panel = tp.getPanel();
|
||||||
|
if (panel !== null) {
|
||||||
|
if (audio.injectedPanelAtLeastOnce) {
|
||||||
|
window.setLoadingDone();
|
||||||
|
} else {
|
||||||
|
addedListener = true;
|
||||||
|
const injectedAll = () => {
|
||||||
|
window.setLoadingDone();
|
||||||
|
tp.getPanel().removeEventListener('injectedAll', injectedAll);
|
||||||
|
};
|
||||||
|
tp.getPanel().addEventListener('injectedAll', injectedAll);
|
||||||
|
}
|
||||||
|
clearInterval(loadingInjectInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 20);
|
||||||
|
};
|
||||||
|
|
||||||
const postModuleInitialized = () => {
|
const postModuleInitialized = () => {
|
||||||
window.setLoadingTask('setting up animation', 80);
|
window.setLoadingTask('setting up animation', 80);
|
||||||
|
setLoadingDoneHook();
|
||||||
moduleFS.init()
|
moduleFS.init()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
artboard = new Artboard(tp, content);
|
artboard = new Artboard(tp, content);
|
||||||
|
@ -309,13 +366,13 @@ const postModuleInitialized = () => {
|
||||||
tp.connectModuleCallbacks();
|
tp.connectModuleCallbacks();
|
||||||
exporter.init();
|
exporter.init();
|
||||||
getFontsAndAxes();
|
getFontsAndAxes();
|
||||||
|
window.setLoadingTask('loading project', 85);
|
||||||
tp.loadProject().then(() => {
|
tp.loadProject().then(() => {
|
||||||
interactor.init();
|
interactor.init();
|
||||||
resize();
|
resize();
|
||||||
adjustPanel();
|
adjustPanel();
|
||||||
window.setLoadingTask('setting up animation', 100);
|
window.setLoadingTask('setting up whatever else is necessary', 100);
|
||||||
window.isInitialized = true;
|
window.isInitialized = true;
|
||||||
window.setLoadingDone();
|
|
||||||
window.autoSaveInterval = setInterval(() => {
|
window.autoSaveInterval = setInterval(() => {
|
||||||
if (config.autoSave && window.isInitialized) {
|
if (config.autoSave && window.isInitialized) {
|
||||||
tp.saveProject();
|
tp.saveProject();
|
||||||
|
@ -394,10 +451,32 @@ const listAvailableFontsAndAxes = () => {
|
||||||
window.listAvailableFontsAndAxes = listAvailableFontsAndAxes;
|
window.listAvailableFontsAndAxes = listAvailableFontsAndAxes;
|
||||||
window.getFontsAndAxes = getFontsAndAxes;
|
window.getFontsAndAxes = getFontsAndAxes;
|
||||||
|
|
||||||
|
window.getArtboard = () => {
|
||||||
|
return artboard;
|
||||||
|
};
|
||||||
|
|
||||||
window.getLayers = () => {
|
window.getLayers = () => {
|
||||||
return layers;
|
return layers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.getAudioLayers = () => {
|
||||||
|
return audioLayers;
|
||||||
|
};
|
||||||
|
|
||||||
|
// for now we changed the design to have only one audio layer
|
||||||
|
window.getAudioLayer = () => {
|
||||||
|
return audioLayers.length > 0 ? audioLayers[0] : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.getLayer = (layerID = tp.studio.selection[0].address.objectKey) => {
|
||||||
|
if (layerID === 'artboard') {
|
||||||
|
return artboard;
|
||||||
|
} else {
|
||||||
|
//return layers.find((layer) => layer.id() === layerID);
|
||||||
|
return layersById[layerID];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.moveLayerUp = (layerID) => {
|
window.moveLayerUp = (layerID) => {
|
||||||
layerOrder.moveUp(layerID);
|
layerOrder.moveUp(layerID);
|
||||||
};
|
};
|
||||||
|
@ -406,10 +485,6 @@ window.moveLayerDown = (layerID) => {
|
||||||
layerOrder.moveDown(layerID);
|
layerOrder.moveDown(layerID);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.getArtboard = () => {
|
|
||||||
return artboard;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addLayer = (autoInit = true) => {
|
const addLayer = (autoInit = true) => {
|
||||||
const layerID = Module.addNewLayer();
|
const layerID = Module.addNewLayer();
|
||||||
const layer = new Layer(tp, layerID, fontsAndAxes, autoInit);
|
const layer = new Layer(tp, layerID, fontsAndAxes, autoInit);
|
||||||
|
@ -444,24 +519,21 @@ const duplicateLayer = (originalLayer) => {
|
||||||
const givenKeys = e.detail.titles;
|
const givenKeys = e.detail.titles;
|
||||||
let allKeysFound = true;
|
let allKeysFound = true;
|
||||||
for (let i = 0; i < originalKeys.length; i++) {
|
for (let i = 0; i < originalKeys.length; i++) {
|
||||||
//const originalValue = originalValues[originalKeys[i]];
|
|
||||||
if (givenKeys.indexOf(originalKeys[i]) < 0) {
|
if (givenKeys.indexOf(originalKeys[i]) < 0) {
|
||||||
//delete originalValues[originalKeys[i]];
|
|
||||||
allKeysFound = false;
|
allKeysFound = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
if (allKeysFound) {
|
if (allKeysFound) {
|
||||||
tp.getPanel().removeEventListener("injected", addKeyframes);
|
tp.getPanel().removeEventListener("injected", addKeyframes);
|
||||||
}
|
}
|
||||||
tp.addKeyframes(newLayer, originalKeyframes).then(() => {
|
tp.addKeyframes(newLayer, originalKeyframes).then(() => {
|
||||||
if (allKeysFound) {
|
if (allKeysFound) {
|
||||||
resolve();
|
resolve();
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
tp.getPanel().addEventListener("injected", addKeyframes);
|
tp.getPanel().addEventListener("injected", addKeyframes);
|
||||||
newLayer.select();
|
newLayer.select();
|
||||||
//tp.shadowRoot.querySelector(`.layerMover${newLayer.id()} div`).click();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -477,9 +549,33 @@ const deleteLayer = (id, saveProject = true) => {
|
||||||
index = i;
|
index = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// delete from audio
|
||||||
|
if (typeof audio.getMapping() === 'object' && typeof audio.getMapping()[id] === 'object') {
|
||||||
|
delete audio.getMapping()[id];
|
||||||
|
}
|
||||||
|
if (typeof audio.getSavedMapping() === 'object' && typeof audio.getSavedMapping()[id] === 'object') {
|
||||||
|
delete audio.getSavedMapping()[id];
|
||||||
|
}
|
||||||
|
if (typeof audio.canvasCombos === 'object') {
|
||||||
|
Object.keys(audio.canvasCombos).forEach((propTitle) => {
|
||||||
|
if (audio.canvasCombos[propTitle][2] === id) {
|
||||||
|
delete audio.canvasCombos[propTitle];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const wasSelected = layers[index].isSelected();
|
||||||
layers[index].prepareForDepartureFromThisBeautifulExperience();
|
layers[index].prepareForDepartureFromThisBeautifulExperience();
|
||||||
layers.splice(index, 1);
|
layers.splice(index, 1);
|
||||||
delete layersById[id];
|
delete layersById[id];
|
||||||
|
if (wasSelected) {
|
||||||
|
if (layers.length > index + 1) {
|
||||||
|
layers[index].select();
|
||||||
|
} else if (layers.length > 0) {
|
||||||
|
layers[layers.length - 1].select();
|
||||||
|
} else {
|
||||||
|
artboard.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (saveProject) {
|
if (saveProject) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
tp.saveProject();
|
tp.saveProject();
|
||||||
|
@ -487,6 +583,77 @@ const deleteLayer = (id, saveProject = true) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addAudioLayer = (filename = false) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const audioLayerID = (() => {
|
||||||
|
if (audioLayers.length === 0) {
|
||||||
|
return 'audio';
|
||||||
|
}
|
||||||
|
let index = 0;
|
||||||
|
for (let i = 0; i < audioLayers.length; i++) {
|
||||||
|
if (audioLayers.findIndex((audioLayer) => audioLayer.id() === `audio-${index}`) < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return `audio-${index}`;
|
||||||
|
})();
|
||||||
|
const audioLayer = new AudioLayer(tp, audioLayerID, filename, false);
|
||||||
|
layersById[audioLayerID] = audioLayer;
|
||||||
|
audioLayers.push(audioLayer);
|
||||||
|
audioLayer.init().then(() => {
|
||||||
|
resolve(audioLayer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addExistingAudioLayer = (audioLayerID, values = false) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const audioLayer = new AudioLayer(tp, audioLayerID, values, false);
|
||||||
|
audioLayers.push(audioLayer);
|
||||||
|
layersById[audioLayerID] = audioLayer;
|
||||||
|
audioLayer.init().then(() => {
|
||||||
|
resolve(audioLayer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteAudioLayer = (audioLayerID) => {
|
||||||
|
tp.removeObject(audioLayerID);
|
||||||
|
let index = 0;
|
||||||
|
for (let i = 0; i < audioLayers.length; i++) {
|
||||||
|
if (audioLayers[i].id() === audioLayerID) {
|
||||||
|
index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
audioLayers.splice(index, 1);
|
||||||
|
//delete audioLayersById[id];
|
||||||
|
};
|
||||||
|
|
||||||
|
const addAudioLayerTrack = (filename, start = 0, end = 1) => {
|
||||||
|
const addTrack = () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
audioLayers[0].addFile(filename).then(() => {
|
||||||
|
audioLayers[0].setTime(filename, start, end).then(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (audioLayers.length < 1) {
|
||||||
|
addAudioLayer().then((audioLayer) => {
|
||||||
|
addTrack().then(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});;
|
||||||
|
} else {
|
||||||
|
addTrack().then(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
// TODO: better function names
|
// TODO: better function names
|
||||||
// because, come on. it may be funny for a second
|
// because, come on. it may be funny for a second
|
||||||
// but tolerance for fun is low when you're grumpy
|
// but tolerance for fun is low when you're grumpy
|
||||||
|
@ -511,10 +678,102 @@ window.duplicateLayer = (layer) => {
|
||||||
window.addLayer = addLayer;
|
window.addLayer = addLayer;
|
||||||
window.addExistingLayer = addExistingLayer;
|
window.addExistingLayer = addExistingLayer;
|
||||||
window.deleteLayer = deleteLayer;
|
window.deleteLayer = deleteLayer;
|
||||||
|
window.addAudioLayer = addAudioLayer;
|
||||||
|
window.addExistingAudioLayer = addExistingAudioLayer;
|
||||||
|
window.deleteAudioLayer = deleteAudioLayer;
|
||||||
|
window.addAudioLayerTrack = addAudioLayerTrack;
|
||||||
window.renderFrames = exporter.renderFrames;
|
window.renderFrames = exporter.renderFrames;
|
||||||
|
|
||||||
const layer_panel = document.querySelector('#layer_panel');
|
const layer_panel = document.querySelector('#layer_panel');
|
||||||
|
|
||||||
const initPanels = () => {
|
const ui = (show) => {
|
||||||
//makeDraggable(layer_panel);
|
if (show && tp.studio.ui.isHidden) {
|
||||||
|
tp.studio.ui.restore();
|
||||||
|
} else if (!show && !tp.studio.ui.isHidden) {
|
||||||
|
tp.studio.ui.hide();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUiKeypress = (e) => {
|
||||||
|
if (e.key.toLowerCase() === 'q') {
|
||||||
|
document.removeEventListener('keypress', handleUiKeypress);
|
||||||
|
ui(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initPanels = () => {
|
||||||
|
let hideuiButton = document.querySelector('#hide_ui');
|
||||||
|
if (hideuiButton === null) {
|
||||||
|
hideuiButton = tp.getPanel().querySelector('#hide_ui');
|
||||||
|
}
|
||||||
|
if (hideuiButton !== null) {
|
||||||
|
hideuiButton.addEventListener('click', () => {
|
||||||
|
ui(false);
|
||||||
|
document.addEventListener('keypress', handleUiKeypress);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let audiofileButton = document.querySelector('#upload_audio');
|
||||||
|
if (audiofileButton === null) {
|
||||||
|
audiofileButton = tp.getPanel().querySelector('#upload_audio');
|
||||||
|
}
|
||||||
|
if (audiofileButton !== null) {
|
||||||
|
audiofileButton.addEventListener('click', () => {
|
||||||
|
uploadFile('audio')
|
||||||
|
.then((file) => {
|
||||||
|
moduleFS
|
||||||
|
.save(file)
|
||||||
|
.then(() => {
|
||||||
|
audio.readAudioFiles();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let addFontButton = document.querySelector('#add_font');
|
||||||
|
if (addFontButton === null) {
|
||||||
|
addFontButton = tp.getPanel().querySelector('#add_font');
|
||||||
|
}
|
||||||
|
if (addFontButton !== null) {
|
||||||
|
addFontButton.addEventListener('click', () => {
|
||||||
|
addUserFont();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addUserFont = () => {
|
||||||
|
uploadFile('font')
|
||||||
|
.then((fontFile) => {
|
||||||
|
moduleFS
|
||||||
|
.save(fontFile)
|
||||||
|
.then(() => {
|
||||||
|
getFontsAndAxes()
|
||||||
|
.then((newFontsAndAxes) => {
|
||||||
|
// let's select the new uploaded font
|
||||||
|
// if we have a layer selected
|
||||||
|
const layer = getLayer();
|
||||||
|
if (layer.id().indexOf('layer-') === 0 && newFontsAndAxes.length > 0) {
|
||||||
|
const path = newFontsAndAxes[0].fontPath;
|
||||||
|
layer.selectFont(path);
|
||||||
|
// we need to update theatreprops
|
||||||
|
// a bit awkwardly twice
|
||||||
|
//
|
||||||
|
// first like this
|
||||||
|
layer.updateTheatreProps()
|
||||||
|
.then(() => {
|
||||||
|
// and again with a transaction
|
||||||
|
const hash = layer.props.fontFamily.default;
|
||||||
|
tp.studio.transaction(({
|
||||||
|
set
|
||||||
|
}) => {
|
||||||
|
set(layer.theatreObject.props.fontFamily, hash);
|
||||||
|
});
|
||||||
|
layer.findInjectPanel();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.addUserFont = addUserFont;
|
|
@ -13,6 +13,9 @@ const ModuleFS = function() {
|
||||||
if (!FS.analyzePath(config.fs.idbfsFontDir).exists) {
|
if (!FS.analyzePath(config.fs.idbfsFontDir).exists) {
|
||||||
FS.mkdir(config.fs.idbfsFontDir);
|
FS.mkdir(config.fs.idbfsFontDir);
|
||||||
}
|
}
|
||||||
|
if (!FS.analyzePath(config.fs.idbfsAudioDir).exists) {
|
||||||
|
FS.mkdir(config.fs.idbfsAudioDir);
|
||||||
|
}
|
||||||
if (!FS.analyzePath(config.fs.idbfsTmpDir).exists) {
|
if (!FS.analyzePath(config.fs.idbfsTmpDir).exists) {
|
||||||
FS.mkdir(config.fs.idbfsTmpDir);
|
FS.mkdir(config.fs.idbfsTmpDir);
|
||||||
}
|
}
|
||||||
|
@ -35,12 +38,28 @@ const ModuleFS = function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.readdir = (path) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.syncfs(MODE_READ_FROM_PERSISTENT)
|
||||||
|
.then(() => {
|
||||||
|
const analyzed = FS.analyzePath(path);
|
||||||
|
if (analyzed.exists && FS.isDir(analyzed.object.mode)) {
|
||||||
|
const content = FS.readdir(path)
|
||||||
|
.filter((e) => ['.','..'].indexOf(e) < 0);
|
||||||
|
resolve(content);
|
||||||
|
} else {
|
||||||
|
console.log('ModuleFS::readdir', `${path} is not a directory or does not exist`);
|
||||||
|
resolve([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// check utils::uploadFile() for details of file
|
// check utils::uploadFile() for details of file
|
||||||
this.save = (file) => {
|
this.save = (file) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (file.type.indexOf('font') >= 0 || file.hasOwnProperty('isFont') && file.isFont === true) {
|
if (file.type.indexOf('font') >= 0 || file.hasOwnProperty('isFont') && file.isFont === true) {
|
||||||
var uint8View = new Uint8Array(file.arrayBuffer);
|
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) {
|
if (!FS.analyzePath(`${config.fs.idbfsFontDir}/${file.name}`).exists) {
|
||||||
FS.createDataFile(config.fs.idbfsFontDir, file.name, uint8View, true, true);
|
FS.createDataFile(config.fs.idbfsFontDir, file.name, uint8View, true, true);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +70,6 @@ const ModuleFS = function() {
|
||||||
} else if (file.type.indexOf('zip') >= 0 || file.hasOwnProperty('isZip') && file.isZip === true) {
|
} else if (file.type.indexOf('zip') >= 0 || file.hasOwnProperty('isZip') && file.isZip === true) {
|
||||||
var uint8View = new Uint8Array(file.arrayBuffer);
|
var uint8View = new Uint8Array(file.arrayBuffer);
|
||||||
var filePath = `${config.fs.idbfsTmpDir}/${file.name}`;
|
var filePath = `${config.fs.idbfsTmpDir}/${file.name}`;
|
||||||
console.log(filePath);
|
|
||||||
if (!FS.analyzePath(filePath).exists) {
|
if (!FS.analyzePath(filePath).exists) {
|
||||||
FS.createDataFile(config.fs.idbfsTmpDir, file.name, uint8View, true, true);
|
FS.createDataFile(config.fs.idbfsTmpDir, file.name, uint8View, true, true);
|
||||||
}
|
}
|
||||||
|
@ -59,6 +77,18 @@ const ModuleFS = function() {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
resolve(filePath);
|
resolve(filePath);
|
||||||
});
|
});
|
||||||
|
} else if (file.type.indexOf('audio') === 0) {
|
||||||
|
var uint8View = new Uint8Array(file.arrayBuffer);
|
||||||
|
if (!FS.analyzePath(`${config.fs.idbfsAudioDir}/${file.name}`).exists) {
|
||||||
|
FS.createDataFile(config.fs.idbfsAudioDir, file.name, uint8View, true, true);
|
||||||
|
this.syncfs(MODE_WRITE_TO_PERSISTENT)
|
||||||
|
.then(() => {
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert(`It seems as if an audiofile with the name "${file.name}" already exists. Please rename your file and upload again, thanks <3`);
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
}
|
}
|
||||||
|
@ -81,6 +111,33 @@ const ModuleFS = function() {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const objectUrls = {};
|
||||||
|
this.objectUrl = (filePath) => {
|
||||||
|
if (typeof objectUrls[filePath] === 'undefined') {
|
||||||
|
const arr = FS.readFile(filePath);
|
||||||
|
let type = 'audio/wav';
|
||||||
|
const filesplit = filePath.split('.');
|
||||||
|
const extension = filesplit[filesplit.length - 1];
|
||||||
|
|
||||||
|
// is there a way to get mime type without guessing by extension?
|
||||||
|
if (extension === 'wav') {
|
||||||
|
type = 'audio/wav';
|
||||||
|
} else if (extension === 'mp3') {
|
||||||
|
type = 'audio/mpeg';
|
||||||
|
} else if (extension === 'ogg') {
|
||||||
|
type = 'audio/ogg';
|
||||||
|
} else if (extension === 'webm') {
|
||||||
|
type = 'audio/webm';
|
||||||
|
}
|
||||||
|
objectUrls[filePath] = URL.createObjectURL(
|
||||||
|
new Blob([arr], {
|
||||||
|
type
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return objectUrls[filePath];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
653
bin/em/variabletime/web/js/record.js
Normal file
|
@ -0,0 +1,653 @@
|
||||||
|
import {
|
||||||
|
clone,
|
||||||
|
sequencialPromises,
|
||||||
|
toCssClass,
|
||||||
|
flattenObject,
|
||||||
|
deFlattenObject,
|
||||||
|
getNestedProperty,
|
||||||
|
} from './utils.js';
|
||||||
|
|
||||||
|
const LiveBuffer = function() {
|
||||||
|
// private
|
||||||
|
//
|
||||||
|
// constants
|
||||||
|
const NO_TIME = -1;
|
||||||
|
// variables
|
||||||
|
|
||||||
|
/// @brief valueBuffer stores all values.
|
||||||
|
/// it is an object with layerIDs/artboard as key
|
||||||
|
const valueBuffer = {};
|
||||||
|
|
||||||
|
// functions
|
||||||
|
const register = (id) => {
|
||||||
|
if (!valueBuffer.hasOwnProperty(id)) {
|
||||||
|
valueBuffer[id] = new Map();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const deregister = (id) => {
|
||||||
|
if (valueBuffer.hasOwnProperty(id)) {
|
||||||
|
delete valueBuffer[id];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/// @brief addValues
|
||||||
|
// values are expected to be
|
||||||
|
// {
|
||||||
|
// x: 42.0,
|
||||||
|
// fontSize_px: 24,
|
||||||
|
// whatever: "something",
|
||||||
|
// }
|
||||||
|
const addValues = (id, values, time_s, start_time_s) => {
|
||||||
|
const subValueBuffer = valueBuffer[id];
|
||||||
|
|
||||||
|
// delete between start_time_s and time_s
|
||||||
|
if (start_time_s !== NO_TIME) {
|
||||||
|
subValueBuffer.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 subValueBuffer.get(value_time_s)[keys[k]];
|
||||||
|
if (Object.keys(subValueBuffer.get(value_time_s)).length === 0) {
|
||||||
|
subValueBuffer.delete(value_time_s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (subValueBuffer.has(time_s)) {
|
||||||
|
subValueBuffer.set(time_s, {...subValueBuffer.get(time_s), ...values});
|
||||||
|
} else {
|
||||||
|
subValueBuffer.set(time_s, clone(values));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// get values and merge with previous
|
||||||
|
// 0.42s: { x: 4 }
|
||||||
|
// 0.64s: { y: 7 }
|
||||||
|
// 0.82s: { y: 12, z: 24 }
|
||||||
|
//
|
||||||
|
// return for 0.82s = { x: 4, y: 12, x: 24 }
|
||||||
|
const getValues = (id, time_s) => {
|
||||||
|
if (valueBuffer[id].size === 0) {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
valueBuffer[id] = new Map([...valueBuffer[id].entries()].sort());
|
||||||
|
let mergedValues = {};
|
||||||
|
let didMergeValues = {};
|
||||||
|
valueBuffer[id].forEach((value, value_time_s) => {
|
||||||
|
if (value_time_s <= time_s) {
|
||||||
|
mergedValues = {...mergedValues, ...value};
|
||||||
|
} else {
|
||||||
|
if (Object.keys(didMergeValues).length === 0) {
|
||||||
|
didMergeValues = clone(mergedValues);
|
||||||
|
}
|
||||||
|
Object.keys(value).forEach((key) => {
|
||||||
|
if(!didMergeValues.hasOwnProperty(key)) {
|
||||||
|
mergedValues[key] = value[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return mergedValues;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// public
|
||||||
|
this.NO_TIME = NO_TIME;
|
||||||
|
this.addValues = addValues;
|
||||||
|
this.getValues = getValues;
|
||||||
|
this.register = register;
|
||||||
|
this.deregister = deregister;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LiveUpdater = function(tp, buffy) {
|
||||||
|
const toUpdate = [];
|
||||||
|
const update = () => {
|
||||||
|
const time_s = tp.sheet.sequence.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.add = (layer) => {
|
||||||
|
if (toUpdate.indexOf(layer) < 0) {
|
||||||
|
toUpdate.push(layer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.remove = (layer) => {
|
||||||
|
const index = toUpdate.indexOf(layer);
|
||||||
|
if (index >= 0) {
|
||||||
|
toUpdate.push(layer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.immediateUpdate = (layer, values) => {
|
||||||
|
const cv = clone(values);
|
||||||
|
const ctv = clone(layer.theatreObject.value);
|
||||||
|
if (cv.hasOwnProperty('color.r')) {
|
||||||
|
cv['color'] = {
|
||||||
|
r: cv['color.r'],
|
||||||
|
g: cv['color.g'],
|
||||||
|
b: cv['color.b'],
|
||||||
|
a: cv['color.a'],
|
||||||
|
};
|
||||||
|
delete cv['color.r'];
|
||||||
|
delete cv['color.g'];
|
||||||
|
delete cv['color.b'];
|
||||||
|
delete cv['color.a'];
|
||||||
|
}
|
||||||
|
flattenObject(cv, ['color']);
|
||||||
|
flattenObject(ctv, ['color']);
|
||||||
|
const v = {...ctv, ...cv};
|
||||||
|
deFlattenObject(v, ['color']);
|
||||||
|
const p = layer.values2cppProps(v);
|
||||||
|
if (p !== false) {
|
||||||
|
const id = layer.id();
|
||||||
|
if (id !== 'artboard') {
|
||||||
|
Module.setProps(p, layer.id());
|
||||||
|
} else {
|
||||||
|
Module.setArtboardProps(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Record = function(tp) {
|
||||||
|
|
||||||
|
const NOT_RECORDING = 0;
|
||||||
|
const STARTING_RECORDING = 1;
|
||||||
|
const RECORDING = 2;
|
||||||
|
const STOPPING_RECORDING = 3;
|
||||||
|
|
||||||
|
this.possibleStates = {
|
||||||
|
NOT_RECORDING,
|
||||||
|
STARTING_RECORDING,
|
||||||
|
RECORDING,
|
||||||
|
STOPPING_RECORDING,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.friendlyState = (state) => {
|
||||||
|
switch(state) {
|
||||||
|
case NOT_RECORDING: return 'NOT_RECORDING';
|
||||||
|
case STARTING_RECORDING: return 'STARTING_RECORDING';
|
||||||
|
case RECORDING: return 'RECORDING';
|
||||||
|
case STOPPING_RECORDING: return 'STOPPING_RECORDING';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setIsRecording = (status) => {
|
||||||
|
isRecording = status;
|
||||||
|
window.dispatchEvent(new CustomEvent("record", {detail: status}));
|
||||||
|
console.log('setIsRecording', this.friendlyState(status));
|
||||||
|
};
|
||||||
|
|
||||||
|
const hot = {};
|
||||||
|
let isRecording = NOT_RECORDING;
|
||||||
|
const buffy = new LiveBuffer();
|
||||||
|
const liveUpdater = new LiveUpdater(tp, buffy);
|
||||||
|
let isInitialized = false;
|
||||||
|
|
||||||
|
let remember = {};
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
if (!isInitialized) {
|
||||||
|
tp.core.onChange(tp.sheet.sequence.pointer.playing, (playing) => {
|
||||||
|
if (isRecording === RECORDING && !playing) {
|
||||||
|
// when you hit space to stop recording,
|
||||||
|
// we want to keep it paused
|
||||||
|
remember.isPlaying = false;
|
||||||
|
stopRecording();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
isInitialized = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isHot = (layerID, propTitle) => {
|
||||||
|
return typeof hot[layerID] === 'object'
|
||||||
|
&& typeof hot[layerID][propTitle] === 'object';
|
||||||
|
//return hot.hasOwnProperty(layerID)
|
||||||
|
//&& hot[layerID].hasOwnProperty(propTitle);
|
||||||
|
};
|
||||||
|
const addHot = (layerID, propTitle) => {
|
||||||
|
if (!isHot(layerID, propTitle)) {
|
||||||
|
if (!hot.hasOwnProperty(layerID)) {
|
||||||
|
hot[layerID] = {};
|
||||||
|
}
|
||||||
|
hot[layerID][propTitle] = {
|
||||||
|
recording: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
buffy.register(layerID);
|
||||||
|
// handle UI only if layer is selected
|
||||||
|
if (getLayer(layerID).isSelected()) {
|
||||||
|
let cPropTitle = clone(propTitle);
|
||||||
|
// if colors are separate, there is still just one button
|
||||||
|
if (cPropTitle.indexOf('color.') === 0) {
|
||||||
|
cPropTitle = 'color';
|
||||||
|
}
|
||||||
|
const button = tp
|
||||||
|
.getPanelPropTitle(cPropTitle)
|
||||||
|
.parentNode.parentNode
|
||||||
|
.querySelector('.recordButton');
|
||||||
|
if (button !== null) {
|
||||||
|
button.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const removeHot = (layerID, propTitle) => {
|
||||||
|
if (isHot(layerID, propTitle)) {
|
||||||
|
delete hot[layerID][propTitle];
|
||||||
|
}
|
||||||
|
// what if it is the last prop in the layer
|
||||||
|
if (hot.hasOwnProperty(layerID)) {
|
||||||
|
if (Object.keys(hot[layerID]).length === 0) {
|
||||||
|
delete hot[layerID];
|
||||||
|
buffy.deregister(layerID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// handle UI only if layer is selected
|
||||||
|
if (getLayer(layerID).isSelected()) {
|
||||||
|
let cPropTitle = clone(propTitle);
|
||||||
|
// if colors are separate, there is still just one button
|
||||||
|
if (cPropTitle.indexOf('color.') === 0) {
|
||||||
|
cPropTitle = 'color';
|
||||||
|
}
|
||||||
|
const propTitleDom = tp.getPanelPropTitle(cPropTitle);
|
||||||
|
if (propTitleDom === null) {
|
||||||
|
// whoops, probably currently transitioning,
|
||||||
|
// so it will not be necessary to remove the class
|
||||||
|
// when it will be recreated on demand,
|
||||||
|
// it won't have the class anyways.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const button = tp
|
||||||
|
.getPanelPropTitle(cPropTitle)
|
||||||
|
.parentNode.parentNode
|
||||||
|
.querySelector('.recordButton');
|
||||||
|
if (button !== null) {
|
||||||
|
button.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addRecordButton = (layer, propTitle) => {
|
||||||
|
const panel = tp.getPanel();
|
||||||
|
const panelPropTitle = tp.getPanelPropTitle(propTitle);
|
||||||
|
if (panelPropTitle !== null) {
|
||||||
|
const container = panelPropTitle.parentNode.parentNode;
|
||||||
|
if (container === null) {
|
||||||
|
console.log("Record::addRecordButton",
|
||||||
|
`impossible! cannot find panelPropContainer for ${propTitle}`);
|
||||||
|
} else if (container.querySelector('.recordButton') !== null) {
|
||||||
|
// this is super verbose, let's not log by default
|
||||||
|
//console.log("Record::addRecordButton",
|
||||||
|
//`already added an record button for ${propTitle}`);
|
||||||
|
} else {
|
||||||
|
const button = document.createElement('div');
|
||||||
|
button.classList.add('recordButton');
|
||||||
|
button.classList.add(toCssClass(`recordButton${propTitle}`));
|
||||||
|
button.innerHTML = `<img src="/web/assets/record.svg" alt="record" />`;
|
||||||
|
container.append(button);
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
if(isRecording === RECORDING) {
|
||||||
|
stopRecording();
|
||||||
|
} else {
|
||||||
|
if (config.record.recordMapped) {
|
||||||
|
// make all mapped props hot and
|
||||||
|
Object.keys(audio.getMapping())
|
||||||
|
.forEach((layerID) => {
|
||||||
|
//if (getLayer(layerID).isSelected()) { // NOTE: multilayer recording
|
||||||
|
Object.keys(audio.getMapping()[layerID])
|
||||||
|
.forEach((propTitle) => {
|
||||||
|
addHot(layerID, propTitle);
|
||||||
|
});
|
||||||
|
//}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// only make this propTitle hot and
|
||||||
|
// register its layer for recording
|
||||||
|
addHot(layer.id(), propTitle);
|
||||||
|
}
|
||||||
|
startRecording();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//console.log("Record::addRecordButton",
|
||||||
|
//`added a record button for ${propTitle}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Record::addRecordButton",
|
||||||
|
`cannot find panelPropTitle for ${propTitle}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleRecording = (propPaths = false) => {
|
||||||
|
if(isRecording === RECORDING) {
|
||||||
|
stopRecording();
|
||||||
|
} else {
|
||||||
|
// set microphone recording to false by default
|
||||||
|
if (!propPaths) {
|
||||||
|
// make all mapped props hot and
|
||||||
|
Object.keys(audio.getMapping())
|
||||||
|
.forEach((layerID) => {
|
||||||
|
//if (getLayer(layerID).isSelected()) { // NOTE: multilayer recording
|
||||||
|
Object.keys(audio.getMapping()[layerID])
|
||||||
|
.forEach((propTitle) => {
|
||||||
|
addHot(layerID, propTitle);
|
||||||
|
});
|
||||||
|
//}
|
||||||
|
});
|
||||||
|
} else if (Array.isArray(propPaths)) {
|
||||||
|
// only make these propTitles hot and
|
||||||
|
// register their layer for recording
|
||||||
|
propPaths.forEach((p) => {
|
||||||
|
if (typeof p === 'string') {
|
||||||
|
p = p.split('.');
|
||||||
|
}
|
||||||
|
if (Array.isArray(p)) {
|
||||||
|
const layerID = p[0];
|
||||||
|
const propTitle = p.slice(1).join('.');
|
||||||
|
addHot(layerID, propTitle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
startRecording();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cloneInput = (layer, propTitle) => {
|
||||||
|
return;
|
||||||
|
const panel = tp.getPanel();
|
||||||
|
const panelPropTitle = tp.getPanelPropTitle(propTitle);
|
||||||
|
if (panelPropTitle !== null) {
|
||||||
|
const container = panelPropTitle.parentNode.parentNode;
|
||||||
|
if (container === null) {
|
||||||
|
console.log("Record::cloneInput",
|
||||||
|
`impossible! cannot find panelPropContainer for ${propTitle}`);
|
||||||
|
} else if (container.querySelector('input.recording') !== null) {
|
||||||
|
console.log("Record::cloneInput",
|
||||||
|
`already cloned input for ${propTitle}`);
|
||||||
|
} else {
|
||||||
|
const input = container.querySelector('input');
|
||||||
|
if (input === null) {
|
||||||
|
console.log("Record::cloneInput",
|
||||||
|
`uuh.. seems there is no input to clone for ${propTitle}`);
|
||||||
|
} else {
|
||||||
|
const input_clone = input.cloneNode();
|
||||||
|
input_clone.classList.value = '';
|
||||||
|
input_clone.classList.add('recording');
|
||||||
|
input.parentNode.after(input_clone);
|
||||||
|
input.setAttribute('data-previousDisplay', input.style.display);
|
||||||
|
input.style.display = 'none';
|
||||||
|
return input_clone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uncloneInput = (layer, propTitle) => {
|
||||||
|
return;
|
||||||
|
const panel = tp.getPanel();
|
||||||
|
const panelPropTitle = tp.getPanelPropTitle(propTitle);
|
||||||
|
if (panelPropTitle !== null) {
|
||||||
|
const container = panelPropTitle.parentNode.parentNode;
|
||||||
|
if (container === null) {
|
||||||
|
console.log("Record::uncloneInput",
|
||||||
|
`impossible! cannot find panelPropContainer for ${propTitle}`);
|
||||||
|
} else if (container.querySelector('input.recording') === null) {
|
||||||
|
console.log("Record::uncloneInput",
|
||||||
|
`already uncloned input for ${propTitle}`);
|
||||||
|
} else {
|
||||||
|
const input = container.querySelector('input:not(.recording)');
|
||||||
|
const input_clone = container.querySelector('input.recording');
|
||||||
|
if (input === null ) {
|
||||||
|
console.log("Record::uncloneInput",
|
||||||
|
`uuh.. seems there is no input for ${propTitle}`);
|
||||||
|
} else if (input_clone === null ) {
|
||||||
|
console.log("Record::uncloneInput",
|
||||||
|
`uuh.. seems there is no input_clone for ${propTitle}`);
|
||||||
|
} else {
|
||||||
|
input_clone.remove();
|
||||||
|
input.removeAttribute('data-previousDisplay');
|
||||||
|
const previousInputDisplay = input.getAttribute('data-previousDisplay');
|
||||||
|
input.style.display = previousInputDisplay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const injectPanel = (layer) => {
|
||||||
|
//const flatValues = clone(layer.theatreObject.value);
|
||||||
|
//flattenObject(flatValues, ['color']);
|
||||||
|
//const props = Object.keys(flatValues);
|
||||||
|
//const layerType = layer.id().split('-')[0];
|
||||||
|
//props.forEach((propTitle) => {
|
||||||
|
//if (config.record.ignoreProps[layerType].indexOf(propTitle) < 0) {
|
||||||
|
//addRecordButton(layer, propTitle);
|
||||||
|
//}
|
||||||
|
//});
|
||||||
|
};
|
||||||
|
|
||||||
|
let lastPositions = {};
|
||||||
|
const addValue = (layerID,
|
||||||
|
propTitle,
|
||||||
|
value,
|
||||||
|
position = tp.sheet.sequence.position,
|
||||||
|
lastPosition = buffy.NO_TIME) => {
|
||||||
|
// NOTE: multilayer recording
|
||||||
|
if (!hot.hasOwnProperty(layerID) || !hot[layerID].hasOwnProperty(propTitle)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hot[layerID][propTitle].recording.push({
|
||||||
|
position,
|
||||||
|
value,
|
||||||
|
type: 'linear',
|
||||||
|
});
|
||||||
|
const recording = {
|
||||||
|
[propTitle]: value,
|
||||||
|
};
|
||||||
|
if (!lastPositions.hasOwnProperty(layerID)) {
|
||||||
|
lastPositions[layerID] = {};
|
||||||
|
}
|
||||||
|
if (lastPosition === buffy.NO_TIME) {
|
||||||
|
if (!lastPositions[layerID].hasOwnProperty(propTitle)) {
|
||||||
|
lastPositions[layerID][propTitle] = position;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastPositions[layerID][propTitle] = lastPosition;
|
||||||
|
}
|
||||||
|
buffy.addValues(layerID, recording, position, lastPositions[layerID][propTitle]);
|
||||||
|
};
|
||||||
|
const getValue = (layerID, position) => {
|
||||||
|
const merged = clone(buffy.getValues(layerID, position));
|
||||||
|
deFlattenObject(merged);
|
||||||
|
return merged;
|
||||||
|
};
|
||||||
|
const liveUpdate = (layer, position = tp.sheet.sequence.position) => {
|
||||||
|
const merged = getValue(layer.id(), position);
|
||||||
|
liveUpdater.immediateUpdate(layer, merged);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief = copies letterDelay value from audio mapping to the recorded value.
|
||||||
|
///
|
||||||
|
/// @param layer - a layer object
|
||||||
|
/// @param propPaths - two dimensional array of strings
|
||||||
|
///
|
||||||
|
/// @return
|
||||||
|
const syncLetterDelays = (layer, propPaths) => {
|
||||||
|
propPaths.forEach((path) => {
|
||||||
|
const oldLetterDelay = getNestedProperty(layer.theatreObject.value.letterDelays, path, true);
|
||||||
|
//const isOriginalSequenced = tp.isSequenced(path, layer);
|
||||||
|
const isLetterDelaySequenced = tp.isSequenced(["letterDelays", ...path], layer);
|
||||||
|
// we use audio.getSavedMapping(), because mapping may be removed directly after recording
|
||||||
|
const mapping = getNestedProperty(audio.getSavedMapping(), [layer.id(),...[path.join('.')]], true);
|
||||||
|
const newLetterDelay = (() => {
|
||||||
|
if (typeof mapping['letterDelay'] === 'undefined') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mapping['letterDelay'];
|
||||||
|
})();
|
||||||
|
if (newLetterDelay !== oldLetterDelay &&
|
||||||
|
!isLetterDelaySequenced &&
|
||||||
|
newLetterDelay !== false) {
|
||||||
|
const prop = getNestedProperty(layer.theatreObject.props.letterDelays, path);
|
||||||
|
tp.studio.transaction(({
|
||||||
|
set
|
||||||
|
}) => {
|
||||||
|
set(prop, newLetterDelay);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const startRecording = () => {
|
||||||
|
setIsRecording(STARTING_RECORDING);
|
||||||
|
console.log('Record::startRecording');
|
||||||
|
document.querySelector('#notice_recording')
|
||||||
|
.classList.add('visible');
|
||||||
|
document.querySelector('#notice_recording')
|
||||||
|
.classList.remove('impenetrable');
|
||||||
|
document.querySelector('#notice_recording .what p').innerHTML = 'recording';
|
||||||
|
document.querySelector('#notice_recording .details p').innerHTML = '<button onclick="record.stopRecording()">stop recording</button>';
|
||||||
|
if (!isInitialized) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
lastPositions = {};
|
||||||
|
remember.isPlaying = tp.core.val(tp.sheet.sequence.pointer.playing);
|
||||||
|
tp.sheet.sequence.pause();
|
||||||
|
// if there are no sequenced values,
|
||||||
|
// we very very likely want to start recording from start
|
||||||
|
if (tp.getSequencePanelLeft() === null) {
|
||||||
|
tp.sheet.sequence.position = 0;
|
||||||
|
}
|
||||||
|
const layerKeys = Object.keys(hot);
|
||||||
|
layerKeys.forEach((layerID) => {
|
||||||
|
const layer = getLayer(layerID);
|
||||||
|
layer.updateValuesViaTheatre(false);
|
||||||
|
const propTitles = Object.keys(hot[layerID]);
|
||||||
|
propTitles.forEach((propTitle) => {
|
||||||
|
// NOTE: layerID is not actually used atm
|
||||||
|
// and should be the layer anyways
|
||||||
|
const input_clone = cloneInput(layerID, propTitle);
|
||||||
|
let lastPosition = buffy.NO_TIME;
|
||||||
|
if (input_clone !== null) {
|
||||||
|
//input_clone.addEventListener('change', () => {
|
||||||
|
//const position = tp.sheet.sequence.position;
|
||||||
|
//const value = parseFloat(input_clone.value);
|
||||||
|
//addValue(layerID, propTitle, value, position, lastPosition);
|
||||||
|
//const merged = getValue(layerID, position);
|
||||||
|
//liveUpdater.immediateUpdate(layer, merged);
|
||||||
|
//lastPosition = position;
|
||||||
|
//});
|
||||||
|
} else {
|
||||||
|
console.log('Record::startRecording', `whoops input_clone for ${propTitle} is null`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//tp.sheet.sequence.position = 0;
|
||||||
|
tp.sheet.sequence.play();
|
||||||
|
});
|
||||||
|
setIsRecording(RECORDING);
|
||||||
|
};
|
||||||
|
const stopRecording = () => {
|
||||||
|
document.querySelector('#notice_recording')
|
||||||
|
.classList.add('visible');
|
||||||
|
document.querySelector('#notice_recording')
|
||||||
|
.classList.add('imprenetrable');
|
||||||
|
document.querySelector('#notice_recording .what p').innerHTML = 'digesting recording';
|
||||||
|
document.querySelector('#notice_recording .details p').innerHTML = 'please wait';
|
||||||
|
setIsRecording(STOPPING_RECORDING);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const layerKeys = Object.keys(hot);
|
||||||
|
const promises = [];
|
||||||
|
layerKeys.forEach((layerID) => {
|
||||||
|
const layer = getLayer(layerID);
|
||||||
|
const propTitles = Object.keys(hot[layerID]);
|
||||||
|
const keyframes = [];
|
||||||
|
propTitles.forEach((propTitle) => {
|
||||||
|
audio.removeAudio(getLayer(layerID),propTitle);
|
||||||
|
// NOTE: layerID is not actually used atm
|
||||||
|
// and should be the layer anyways
|
||||||
|
uncloneInput(layerID, propTitle);
|
||||||
|
// special treatment if we have sperate RGBA for color
|
||||||
|
if (propTitle.indexOf('color.') === 0) {
|
||||||
|
if (propTitle === 'color.r') {
|
||||||
|
const recording = [];
|
||||||
|
hot[layerID]['color.r'].recording.forEach((rr, ri) => {
|
||||||
|
if (ri < hot[layerID]['color.r'].recording.length &&
|
||||||
|
ri < hot[layerID]['color.g'].recording.length &&
|
||||||
|
ri < hot[layerID]['color.b'].recording.length &&
|
||||||
|
ri < hot[layerID]['color.a'].recording.length) {
|
||||||
|
const r = clone(rr);
|
||||||
|
r.value = {
|
||||||
|
r: hot[layerID]['color.r'].recording[ri].value,
|
||||||
|
g: hot[layerID]['color.g'].recording[ri].value,
|
||||||
|
b: hot[layerID]['color.b'].recording[ri].value,
|
||||||
|
a: hot[layerID]['color.a'].recording[ri].value,
|
||||||
|
};
|
||||||
|
recording.push(r);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
keyframes.push({
|
||||||
|
path: ['color'],
|
||||||
|
keyframes: recording,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
keyframes.push({
|
||||||
|
path: propTitle.split('.'),
|
||||||
|
keyframes: hot[layerID][propTitle].recording,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promises.push(() => {
|
||||||
|
return new Promise((subResolve) => {
|
||||||
|
tp.addKeyframes(layer, keyframes).then(() => {
|
||||||
|
syncLetterDelays(layer, keyframes.map(k => k.path));
|
||||||
|
layer.updateValuesViaTheatre(true);
|
||||||
|
subResolve();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
sequencialPromises(promises, () => {
|
||||||
|
Object.keys(hot).forEach((layerID) => {
|
||||||
|
Object.keys(hot[layerID]).forEach((propTitle) => {
|
||||||
|
removeHot(layerID, propTitle);
|
||||||
|
});
|
||||||
|
buffy.deregister(layerID);
|
||||||
|
});
|
||||||
|
document.querySelector('#notice_recording')
|
||||||
|
.classList.remove('visible');
|
||||||
|
console.log('Record::stopRecording', 'stopped recording');
|
||||||
|
setIsRecording(NOT_RECORDING);
|
||||||
|
|
||||||
|
if (remember.isPlaying) {
|
||||||
|
tp.sheet.sequence.play();
|
||||||
|
} else {
|
||||||
|
tp.sheet.sequence.pause();
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// public
|
||||||
|
this.addValue = addValue;
|
||||||
|
this.getValue = getValue;
|
||||||
|
this.liveUpdate = liveUpdate;
|
||||||
|
this.liveUpdater = liveUpdater;
|
||||||
|
this.addRecordButton = addRecordButton;
|
||||||
|
this.isHot = isHot;
|
||||||
|
this.addHot = addHot;
|
||||||
|
this.removeHot = removeHot;
|
||||||
|
this.getHot = () => {
|
||||||
|
return hot;
|
||||||
|
};
|
||||||
|
this.isRecording = () => {
|
||||||
|
return isRecording != NOT_RECORDING;
|
||||||
|
};
|
||||||
|
this.injectPanel = injectPanel;
|
||||||
|
this.startRecording = startRecording;
|
||||||
|
this.stopRecording = stopRecording;
|
||||||
|
this.toggleRecording = toggleRecording;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
Record
|
||||||
|
}
|
|
@ -7,12 +7,10 @@ import {
|
||||||
clone,
|
clone,
|
||||||
getParents,
|
getParents,
|
||||||
arraysEqual,
|
arraysEqual,
|
||||||
|
sequencialPromises,
|
||||||
|
getNestedProperty,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
|
|
||||||
//import {
|
|
||||||
//config
|
|
||||||
//} from './config.js';
|
|
||||||
|
|
||||||
const TheatrePlay = function(autoInit = false) {
|
const TheatrePlay = function(autoInit = false) {
|
||||||
|
|
||||||
//private
|
//private
|
||||||
|
@ -25,10 +23,12 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
const theatreObjects = {};
|
const theatreObjects = {};
|
||||||
let theatrePanel = null;
|
let theatrePanel = null;
|
||||||
let sequencePanelLeft = null;
|
let sequencePanelLeft = null;
|
||||||
|
const getSequencePanel = () => {
|
||||||
|
sequencePanelLeft = tp.shadowRoot.querySelector('[data-testid="SequencePanel-Object"]');
|
||||||
|
return sequencePanelLeft;
|
||||||
|
};
|
||||||
const getSequencePanelLeft = () => {
|
const getSequencePanelLeft = () => {
|
||||||
// if (sequencePanelLeft === null) {
|
|
||||||
sequencePanelLeft = tp.shadowRoot.querySelector('[data-testid="SequencePanel-Left"]');
|
sequencePanelLeft = tp.shadowRoot.querySelector('[data-testid="SequencePanel-Left"]');
|
||||||
// }
|
|
||||||
return sequencePanelLeft;
|
return sequencePanelLeft;
|
||||||
};
|
};
|
||||||
const getPanel = () => {
|
const getPanel = () => {
|
||||||
|
@ -96,7 +96,7 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
if (typeof value === 'undefined') {
|
if (typeof value === 'undefined') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
return this.sheet.sequence.__experimental_getKeyframes(prop);
|
return this.sheet.sequence.__experimental_getKeyframes(prop);
|
||||||
};
|
};
|
||||||
// wtf, this function was being written in one go
|
// wtf, this function was being written in one go
|
||||||
|
@ -134,88 +134,126 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
return keyframes;
|
return keyframes;
|
||||||
};
|
};
|
||||||
const getSequenceButton = (path) => {
|
const getSequenceButton = (path) => {
|
||||||
let t = getPanelPropTitle(path.join('.'));
|
let t = getPanelPropTitle(Array.isArray(path) ? path.join('.') : path);
|
||||||
if (t === null) {
|
if (t === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return t.parentElement.querySelector('[title="Sequence this prop"]');
|
return t.parentElement.querySelector('[title="Sequence this prop"]');
|
||||||
};
|
};
|
||||||
// no idea how to delete keyframes
|
const isSequenced = (path, layer = getLayer()) => {
|
||||||
// so we can only add
|
if (!Array.isArray(path)) {
|
||||||
|
path = path.split('.');
|
||||||
|
}
|
||||||
|
const prop = getNestedProperty(layer.theatreObject.props, path);
|
||||||
|
return studio.__experimental.__experimental_isPropSequenced(prop);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSequenced = (path, sequenced = true, layer = getLayer()) => {
|
||||||
|
if (!Array.isArray(path)) {
|
||||||
|
path = path.split('.');
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if(isSequenced(path, layer) === sequenced) {
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
const prop = getNestedProperty(layer.theatreObject.props, path);
|
||||||
|
if (sequenced) {
|
||||||
|
tp.studio.__experimental.__experimental_setPropAsSequenced(prop);
|
||||||
|
} else {
|
||||||
|
tp.studio.__experimental.__experimental_setPropAsStatic(prop);
|
||||||
|
}
|
||||||
|
let count = 0;
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (isSequenced(path, layer) === sequenced) {
|
||||||
|
clearInterval(interval);
|
||||||
|
resolve(true);
|
||||||
|
} else if (count >= 10) {
|
||||||
|
clearInterval(interval);
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* layer = getLayer()
|
||||||
|
* keyframes = [
|
||||||
|
* {
|
||||||
|
* path: ['fontVariationAxes','Weight'],
|
||||||
|
* keyframes: [
|
||||||
|
* {
|
||||||
|
* "id": "yHFP-HCINm", // not sure if used when sent back
|
||||||
|
* "position": 0,
|
||||||
|
* "connectedRight": true, // optional
|
||||||
|
* "handles": [ 0.5, 1, 0.5, 0 ], // optional
|
||||||
|
* "type": "bezier", // optional
|
||||||
|
* "value": 200
|
||||||
|
* }, ...
|
||||||
|
* ]
|
||||||
|
* }, ...
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
const addKeyframes = (layer, keyframes) => {
|
const addKeyframes = (layer, keyframes) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (!Array.isArray(keyframes)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const existingKeyframes = getKeyframes(layer);
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const ms = config.tp.addKeyframesTimeout_s * 1000;
|
|
||||||
keyframes.forEach((k) => {
|
keyframes.forEach((k) => {
|
||||||
|
promises.push(() => {
|
||||||
|
return new Promise((subResolve) => {
|
||||||
let prop = layer.theatreObject.props;
|
let prop = layer.theatreObject.props;
|
||||||
for (let i = 0; i < k.path.length; i++) {
|
for (let i = 0; i < k.path.length; i++) {
|
||||||
prop = prop[k.path[i]];
|
prop = prop[k.path[i]];
|
||||||
}
|
}
|
||||||
const position = tp.sheet.sequence.position;
|
setSequenced(k.path, true, layer).then(() => {
|
||||||
// NOTE: can we sequence values without pretend clicking?
|
tp.studio.transaction(({
|
||||||
const sequenceButton = getSequenceButton(k.path);
|
__experimental_addKeyframes
|
||||||
if (sequenceButton !== null) {
|
|
||||||
promises.push(new Promise((subResolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
sequenceButton.click();
|
|
||||||
subResolve();
|
|
||||||
}, ms * promises.length);
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
//console.error(k.path, 'did not find sequence button');
|
|
||||||
// is (probably) already sequenced
|
|
||||||
}
|
|
||||||
let propHasKeyframesAt = -1;
|
|
||||||
if (existingKeyframes !== null &&
|
|
||||||
existingKeyframes !== false &&
|
|
||||||
typeof existingKeyframes !== 'undefined' &&
|
|
||||||
Array.isArray(existingKeyframes)) {
|
|
||||||
existingKeyframes.forEach((existingK, existingKI) => {
|
|
||||||
if (arraysEqual(k.path, existingK.path)) {
|
|
||||||
propHasKeyframesAt = existingKI;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
k.keyframes.forEach((keyframe) => {
|
|
||||||
let alreadyThere = false;
|
|
||||||
if (propHasKeyframesAt >= 0) {
|
|
||||||
existingKeyframes[propHasKeyframesAt].keyframes.forEach((kf) => {
|
|
||||||
if (keyframe.position === kf.position &&
|
|
||||||
keyframe.value === kf.value) {
|
|
||||||
alreadyThere = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!alreadyThere) {
|
|
||||||
promises.push(new Promise((subResolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
tp.sheet.sequence.position = keyframe.position;
|
|
||||||
this.studio.transaction(({
|
|
||||||
set
|
|
||||||
}) => {
|
}) => {
|
||||||
set(prop, keyframe.value);
|
__experimental_addKeyframes(prop, k.keyframes);
|
||||||
|
});
|
||||||
subResolve();
|
subResolve();
|
||||||
});
|
});
|
||||||
}, ms * promises.length);
|
});
|
||||||
}));
|
});
|
||||||
|
});
|
||||||
|
sequencialPromises(promises, resolve);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: instead of setting proptitle static and then sequenced again,
|
||||||
|
// we could simply delete all keyframes. but hey, pfff...
|
||||||
|
const setKeyframes = (layer, keyframes) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (!Array.isArray(keyframes)) {
|
||||||
|
resolve(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const promises = [];
|
||||||
|
let waitify = false;
|
||||||
|
keyframes.forEach((k) => {
|
||||||
|
const propTitle = k.path.join('.');
|
||||||
|
if (isSequenced(propTitle, layer)) {
|
||||||
|
waitify = true;
|
||||||
|
promises.push(() => {
|
||||||
|
return new Promise((subResolve) => {
|
||||||
|
setSequenced(propTitle, false, layer)
|
||||||
|
.then(() => {
|
||||||
|
subResolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
promises.push(new Promise((subResolve) => {
|
sequencialPromises(promises, () => {
|
||||||
|
const timeout_ms = waitify ? 1000 : 0;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
tp.sheet.sequence.position = position;
|
addKeyframes(layer, keyframes)
|
||||||
subResolve();
|
.then(resolve);
|
||||||
}, ms * promises.length);
|
}, timeout_ms);
|
||||||
}));
|
|
||||||
});
|
|
||||||
Promise.all(promises).then(() => {
|
|
||||||
resolve();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const friendlySequenceNames = () => {
|
const friendlySequenceNames = () => {
|
||||||
const sequencePanelLeft = tp.getSequencePanelLeft();
|
const sequencePanelLeft = tp.getSequencePanelLeft();
|
||||||
let doItAgain = true;
|
let doItAgain = true;
|
||||||
|
@ -246,12 +284,19 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
friendlySequenceNames();
|
friendlySequenceNames();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dirty, should be not in here:
|
||||||
|
if (getAudioLayers().length > 0) {
|
||||||
|
getAudioLayer().findInjectSequencePanel();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//public
|
//public
|
||||||
|
this.setSequenced = setSequenced;
|
||||||
this.friendlySequenceNames = friendlySequenceNames;
|
this.friendlySequenceNames = friendlySequenceNames;
|
||||||
this.getKeyframes = getKeyframes;
|
this.getKeyframes = getKeyframes;
|
||||||
this.addKeyframes = addKeyframes;
|
this.addKeyframes = addKeyframes;
|
||||||
|
this.setKeyframes = setKeyframes;
|
||||||
this.theatreObjects = theatreObjects;
|
this.theatreObjects = theatreObjects;
|
||||||
this.addObject = (name, props, onValuesChange) => {
|
this.addObject = (name, props, onValuesChange) => {
|
||||||
const obj = this.sheet.object(name, props);
|
const obj = this.sheet.object(name, props);
|
||||||
|
@ -283,6 +328,9 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
// remove object from objects list
|
// remove object from objects list
|
||||||
delete theatreObjects[name];
|
delete theatreObjects[name];
|
||||||
};
|
};
|
||||||
|
this.isSequenced = isSequenced;
|
||||||
|
this.getSequenceButton = getSequenceButton;
|
||||||
|
this.getSequencePanel = getSequencePanel;
|
||||||
this.getSequencePanelLeft = getSequencePanelLeft;
|
this.getSequencePanelLeft = getSequencePanelLeft;
|
||||||
this.getPanel = getPanel;
|
this.getPanel = getPanel;
|
||||||
this.getPanelPropTitle = getPanelPropTitle;
|
this.getPanelPropTitle = getPanelPropTitle;
|
||||||
|
@ -308,269 +356,12 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
};
|
};
|
||||||
this.addShadowCss = () => {
|
this.addShadowCss = () => {
|
||||||
if (this.shadowRoot.querySelector(`#tpShadowCss`) === null) {
|
if (this.shadowRoot.querySelector(`#tpShadowCss`) === null) {
|
||||||
let style = document.createElement("style");
|
this.shadowRoot.insertBefore(Picker.StyleElement.cloneNode(true), this.shadowRoot.querySelector('#pointer-root'));
|
||||||
|
let style = document.createElement("link");
|
||||||
style.setAttribute('id', 'tpShadowCss');
|
style.setAttribute('id', 'tpShadowCss');
|
||||||
|
style.setAttribute('rel', 'stylesheet');
|
||||||
// ASYA: here you can adjust css
|
style.setAttribute('href', '/web/css/theatre.css');
|
||||||
style.textContent = `
|
this.shadowRoot.insertBefore(style, this.shadowRoot.querySelector('#pointer-root'));
|
||||||
.alignButtons, .textAlignButtons {
|
|
||||||
flex-direction: row;
|
|
||||||
padding: 10px 0px !important;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.word{
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
.letter{
|
|
||||||
transition: 0.5s font-variation-settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'vtVF';
|
|
||||||
src: url("/web/fonts/vtVF.ttf") format("TrueType");
|
|
||||||
}
|
|
||||||
|
|
||||||
.vtTitle{
|
|
||||||
font-family: 'vtVF';
|
|
||||||
font-size: 3em;
|
|
||||||
font-variation-settings: "wght" 0, "wdth" 0, "opsz" 0;
|
|
||||||
height: 50px;
|
|
||||||
width: 100%;
|
|
||||||
position: inherit;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#panel,
|
|
||||||
.panel {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel > div {
|
|
||||||
order: 1;
|
|
||||||
}
|
|
||||||
.panel > .panelWrapperMom {
|
|
||||||
order: 2;
|
|
||||||
}
|
|
||||||
.panel > .bottomButtonsContainer {
|
|
||||||
order: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panelWrapper{
|
|
||||||
}
|
|
||||||
|
|
||||||
.panelControlsWrapper{
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
|
||||||
grid-row-start: 2;
|
|
||||||
grid-column-start: 1;
|
|
||||||
grid-column-end: 7;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xWrapper, .yWrapper, .fontFamilyWrapper, .fontFamilyWrapper, .rotationWrapper, .letterDelayWrapper, .transformOriginWrapper, .widthWrapper, .fontSizeWrapper, .letterSpacingWrapper, .lineHeighWrapper, .textWrapper, .colorWrapper, .mirror_xWrapper, .mirror_yWrapper, .mirror_xyWrapper, .mirror_x_distanceWrapper, .mirror_y_distanceWrapper, .alignButtons, .textAlignButtons{
|
|
||||||
|
|
||||||
border: none;
|
|
||||||
padding: 0px;
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.textWrappingButton {
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
.textWrappingButton.active {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.alignButtonsVertical{
|
|
||||||
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alignButtons{
|
|
||||||
display: flex;
|
|
||||||
width: 50%;
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xWrapper{
|
|
||||||
}
|
|
||||||
.yWrapper{
|
|
||||||
}
|
|
||||||
.mirror_xWrapper{
|
|
||||||
}
|
|
||||||
.mirror_xWrapper input, .mirror_yWrapper input, .mirror_xyWrapper input{
|
|
||||||
margin: 0px 10px;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
color: #ea2333;
|
|
||||||
margin-left:10px;
|
|
||||||
}
|
|
||||||
input[type=checkbox] {
|
|
||||||
position: absolute;
|
|
||||||
width: 27px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
input[type=checkbox]:checked + label {
|
|
||||||
color: #1cba94;
|
|
||||||
}
|
|
||||||
input[type=checkbox] + label::after{
|
|
||||||
content: ' OFF';
|
|
||||||
}
|
|
||||||
input[type=checkbox]:checked + label::after{
|
|
||||||
content: ' ON';
|
|
||||||
}
|
|
||||||
.mirror_yWrapper{
|
|
||||||
}
|
|
||||||
.mirror_xyWrapper{
|
|
||||||
}
|
|
||||||
.mirror_x_distanceWrapper{
|
|
||||||
}
|
|
||||||
.mirror_y_distanceWrapper{
|
|
||||||
}
|
|
||||||
|
|
||||||
.fontFamilyWrapper{
|
|
||||||
}
|
|
||||||
.rotationWrapper{
|
|
||||||
border-top: 1px dashed #91919177;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
.transformOriginWrapper{
|
|
||||||
}
|
|
||||||
.widthWrapper{
|
|
||||||
}
|
|
||||||
.heightWrapper{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.fontSizeWrapper{
|
|
||||||
}
|
|
||||||
.letterSpacingWrapper{
|
|
||||||
}
|
|
||||||
.letterDelayWrapper{
|
|
||||||
}
|
|
||||||
.lineHeighWrapper{
|
|
||||||
}
|
|
||||||
.textWrapper{
|
|
||||||
}
|
|
||||||
.colorWrapper{
|
|
||||||
border-bottom: 1px dashed #91919177;
|
|
||||||
border-top: 1px dashed #91919177;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.icon{
|
|
||||||
font-family: 'VariableIcons';
|
|
||||||
font-size: 3em;
|
|
||||||
margin: 2px 5px;
|
|
||||||
line-height: 0.2em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.main_panel_button, .vte_button{
|
|
||||||
background-color: white;
|
|
||||||
font-size: 1.15em;
|
|
||||||
padding: 5px 5px 4px 5px;
|
|
||||||
margin: 10px;
|
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: none;
|
|
||||||
padding: 10px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
width: calc(100% - 20px);
|
|
||||||
box-sizing: border-box;
|
|
||||||
cursor: pointer;
|
|
||||||
font-variation-settings: 'wght' 750, 'wdth' 100;
|
|
||||||
|
|
||||||
}
|
|
||||||
.main_panel_button:hover, .vte_button:hover{
|
|
||||||
color: white;
|
|
||||||
background-color:black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.removeButtonContainer{
|
|
||||||
display: flex;
|
|
||||||
padding-right: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alignButtons img,
|
|
||||||
.textAlignButtons img{
|
|
||||||
height: 19px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 5px 5px 2px 5px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
li.layerMover div {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
li.layerMover div.selected {
|
|
||||||
background: rgba(255, 255, 255, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
li.layerMover div.selected svg circle{
|
|
||||||
fill: #ea2333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.letterDelaysContWrapper{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.letterDelaysContWrapper > *, .fontVariationAxesContWrapper > *{
|
|
||||||
margin-left: calc(var(--left-pad) * (var(--depth) - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.fontVariationAxesContWrapper{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border-bottom: 1px dashed #91919177;
|
|
||||||
border-top: 1px dashed #91919177;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.moveLayerButton {
|
|
||||||
|
|
||||||
}
|
|
||||||
.removeLayerButton img,
|
|
||||||
.duplicateLayerButton img,
|
|
||||||
.addLayerButton img,
|
|
||||||
.moveLayerButton img {
|
|
||||||
height: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.propTitleStuff {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.addLayerButton{
|
|
||||||
width: calc(100% - 40px);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
/* padding-left: 15px; */
|
|
||||||
align-self: end;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
this.shadowRoot.appendChild(style);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const waitingForShadowRoot = (callback) => {
|
const waitingForShadowRoot = (callback) => {
|
||||||
|
@ -779,13 +570,9 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
console.log('TheatrePlay::uploadProject', 'could not verify project');
|
console.log('TheatrePlay::uploadProject', 'could not verify project');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//Module.importProjectAsZip(vt_project.arrayBuffer, vt_project.fileSize, "debug_tmp");
|
|
||||||
} else if (verifyVariableTimeProject(vt_project)) {
|
} else if (verifyVariableTimeProject(vt_project)) {
|
||||||
this.possiblyAskForFontsPromise(vt_project).then((vt_project_u) => {
|
this.possiblyAskForFontsPromise(vt_project).then((vt_project_u) => {
|
||||||
vt_project = vt_project_u;
|
vt_project = vt_project_u;
|
||||||
//if (reeeload) {
|
|
||||||
//localStorage.clear();
|
|
||||||
//}
|
|
||||||
this.saveProject(vt_project.projectId, vt_project, true);
|
this.saveProject(vt_project.projectId, vt_project, true);
|
||||||
if (reeeload) {
|
if (reeeload) {
|
||||||
this.reloadToProject(vt_project.projectId);
|
this.reloadToProject(vt_project.projectId);
|
||||||
|
@ -808,8 +595,32 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
layerOrder: window.layerOrder.get(),
|
layerOrder: window.layerOrder.get(),
|
||||||
fontsHashMap,
|
fontsHashMap,
|
||||||
projectId,
|
projectId,
|
||||||
|
audioSavedMapping: audio.getSavedMapping(),
|
||||||
|
audioMapping: audio.getMapping(),
|
||||||
};
|
};
|
||||||
const theatre = tp.studio.createContentOfSaveFile(currentProjectId);
|
const theatre = tp.studio.createContentOfSaveFile(currentProjectId);
|
||||||
|
// yeaaaah we don't want to fuck with theatre's saveFile stuff
|
||||||
|
// let's do this ourselves
|
||||||
|
const audioLayers = getAudioLayers();
|
||||||
|
if (audioLayers.length > 0) {
|
||||||
|
vt_params['audioLayers'] = {};
|
||||||
|
audioLayers.forEach((audioLayer) => {
|
||||||
|
vt_params['audioLayers'][audioLayer.id()] = getKeyframes(audioLayer);
|
||||||
|
});
|
||||||
|
const audioPlayerElements = audio
|
||||||
|
.audioPlayer
|
||||||
|
.audioElements
|
||||||
|
.map((e) => {
|
||||||
|
return {
|
||||||
|
audioID: e.audioID,
|
||||||
|
layerID: e.layerID,
|
||||||
|
propTitle: e.propTitle,
|
||||||
|
file: e.file,
|
||||||
|
startTime: e.startTime
|
||||||
|
};
|
||||||
|
});
|
||||||
|
vt_params['audioPlayerElements'] = audioPlayerElements;
|
||||||
|
}
|
||||||
return this.theatre2variableTime(theatre, vt_params);
|
return this.theatre2variableTime(theatre, vt_params);
|
||||||
};
|
};
|
||||||
this.saveProject = (projectId = project.address.projectId, vt_project = false, silent = true) => {
|
this.saveProject = (projectId = project.address.projectId, vt_project = false, silent = true) => {
|
||||||
|
@ -834,12 +645,10 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
if (saveBeforeDownloading) {
|
if (saveBeforeDownloading) {
|
||||||
this.saveProject(projectId, vt_project, true);
|
this.saveProject(projectId, vt_project, true);
|
||||||
}
|
}
|
||||||
//downloadFile(JSON.stringify(vt_project), `${config.projects.savePrefix}${projectId}.json`, 'application/json');
|
|
||||||
Module.downloadProject(projectId, JSON.stringify(vt_project));
|
Module.downloadProject(projectId, JSON.stringify(vt_project));
|
||||||
} else {
|
} else {
|
||||||
const p = this.getProject(projectId);
|
const p = this.getProject(projectId);
|
||||||
if (p !== false) {
|
if (p !== false) {
|
||||||
//downloadFile(p, `${config.projects.savePrefix}${projectId}.json`, 'application/json');
|
|
||||||
Module.downloadProject(projectId, JSON.stringify(p));
|
Module.downloadProject(projectId, JSON.stringify(p));
|
||||||
} else {
|
} else {
|
||||||
console.error('TheatrePlay::downloadProject', `cannot download project with id ${projectId}, because it is neither current project or saved in localStorage`);
|
console.error('TheatrePlay::downloadProject', `cannot download project with id ${projectId}, because it is neither current project or saved in localStorage`);
|
||||||
|
@ -874,6 +683,7 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
project = projectJson === false ? core.getProject(projectId) : core.getProject(projectId, {
|
project = projectJson === false ? core.getProject(projectId) : core.getProject(projectId, {
|
||||||
state: projectJson
|
state: projectJson
|
||||||
});
|
});
|
||||||
|
//console.log({project, projectJson});
|
||||||
window.setLoadingTask('setting up animation', 10);
|
window.setLoadingTask('setting up animation', 10);
|
||||||
project.ready.then(() => {
|
project.ready.then(() => {
|
||||||
this.sheet = project.sheet('mainSheet');
|
this.sheet = project.sheet('mainSheet');
|
||||||
|
@ -916,26 +726,78 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
artboardValues['zoom'] = artboardValues['scale'];
|
artboardValues['zoom'] = artboardValues['scale'];
|
||||||
delete artboardValues['scale'];
|
delete artboardValues['scale'];
|
||||||
}
|
}
|
||||||
|
if (artboardValues.hasOwnProperty('backgroundColor')) {
|
||||||
|
artboardValues['color'] = artboardValues['backgroundColor'];
|
||||||
|
delete artboardValues['backgroundColor'];
|
||||||
|
}
|
||||||
|
// for backward compatibility merge with current values
|
||||||
// for backward compatibility merge with current values
|
// for backward compatibility merge with current values
|
||||||
const defaultArtboardValues = window.getArtboard().theatreObject.value;
|
const defaultArtboardValues = window.getArtboard().theatreObject.value;
|
||||||
const artboardProps = {...defaultArtboardValues, ...artboardValues};
|
const artboardProps = {...defaultArtboardValues, ...artboardValues};
|
||||||
|
|
||||||
|
window.setLoadingTask('setting up artboard', 90);
|
||||||
studio.transaction(({
|
studio.transaction(({
|
||||||
set
|
set
|
||||||
}) => {
|
}) => {
|
||||||
set(window.getArtboard().theatreObject.props, artboardProps);
|
set(window.getArtboard().theatreObject.props, artboardProps);
|
||||||
});
|
});
|
||||||
|
if (project.hasOwnProperty('audioMapping')) {
|
||||||
|
audio.setMapping(project.audioMapping);
|
||||||
|
}
|
||||||
|
if (project.hasOwnProperty('audioSavedMapping')) {
|
||||||
|
audio.setSavedMapping(project.audioSavedMapping);
|
||||||
|
}
|
||||||
|
|
||||||
// load layers
|
// load layers
|
||||||
const layerPromises = [];
|
const layerPromises = [];
|
||||||
Object.keys(objects)
|
Object.keys(objects)
|
||||||
.filter((e) => e.indexOf('layer-') === 0)
|
.filter((e) => e.indexOf('layer-') === 0)
|
||||||
.forEach((layerId) => {
|
.forEach((layerId) => {
|
||||||
|
window.setLoadingTask(`setting up the shapes of ${layerId} to come`, 90);
|
||||||
window.project_fontsHashMap = project.fontsHashMap;
|
window.project_fontsHashMap = project.fontsHashMap;
|
||||||
layerPromises.push(window.addExistingLayer(layerId, objects[layerId]));
|
//layerPromises.push(window.addExistingLayer(layerId, objects[layerId]));
|
||||||
|
layerPromises.push(() => {
|
||||||
|
return new Promise((r) => {
|
||||||
|
window.addExistingLayer(layerId, objects[layerId]).then(() => {
|
||||||
|
r();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (typeof project['audioPlayerElements'] === 'object') {
|
||||||
|
audio.audioPlayer.addMany(project['audioPlayerElements']);
|
||||||
|
audio.audioPlayer.init();
|
||||||
|
}
|
||||||
|
if (typeof project['audioLayers'] === 'object') {
|
||||||
|
const audioLayerIDs = Object.keys(project['audioLayers']);
|
||||||
|
audioLayerIDs.forEach((audioLayerID) => {
|
||||||
|
layerPromises.push(() => {
|
||||||
|
window.setLoadingTask(`setting up the sounds of ${audioLayerID} to come`, 90);
|
||||||
|
return new Promise((r) => {
|
||||||
|
window.addExistingAudioLayer(audioLayerID).then((audioLayer) => {
|
||||||
|
const promises = [];
|
||||||
|
project['audioLayers'][audioLayerID].forEach((keyframeCombo) => {
|
||||||
|
const filename = keyframeCombo.path.join('');
|
||||||
|
promises.push(() => {
|
||||||
|
return new Promise((rr) => {
|
||||||
|
audioLayer.addFile(filename)
|
||||||
|
.then(() => {
|
||||||
|
rr(); // resolve
|
||||||
});
|
});
|
||||||
|
|
||||||
Promise.all(layerPromises).then(() => {
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
sequencialPromises(promises, () => {
|
||||||
|
tp.addKeyframes(audioLayer, project['audioLayers'][audioLayerID]);
|
||||||
|
r(); // resolve
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sequencialPromises(layerPromises, () => {
|
||||||
window.layerOrder.set(project.layerOrder);
|
window.layerOrder.set(project.layerOrder);
|
||||||
this.duration = this.core.val(this.sheet.sequence.pointer.length);
|
this.duration = this.core.val(this.sheet.sequence.pointer.length);
|
||||||
if (project.layerOrder.length > 0) {
|
if (project.layerOrder.length > 0) {
|
||||||
|
@ -961,6 +823,9 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
const layers = getLayers();
|
||||||
|
if (layers.length > 0) {
|
||||||
|
}
|
||||||
this.duration = this.core.val(this.sheet.sequence.pointer.length);
|
this.duration = this.core.val(this.sheet.sequence.pointer.length);
|
||||||
resolve();
|
resolve();
|
||||||
this.isProjectLoaded = true;
|
this.isProjectLoaded = true;
|
||||||
|
@ -1007,9 +872,6 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
}
|
}
|
||||||
this.connectModuleCallbacks = () => {
|
this.connectModuleCallbacks = () => {
|
||||||
console.log('TheatrePlay::connectModuleCallbacks');
|
console.log('TheatrePlay::connectModuleCallbacks');
|
||||||
//onChange(sequence.pointer.length, (len) => {
|
|
||||||
//console.log('Length of the sequence changed to:', len)
|
|
||||||
//})
|
|
||||||
|
|
||||||
if (config.timeline.rolloverReset) {
|
if (config.timeline.rolloverReset) {
|
||||||
core.onChange(this.sheet.sequence.pointer.position, (position) => {
|
core.onChange(this.sheet.sequence.pointer.position, (position) => {
|
||||||
|
@ -1023,6 +885,9 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
Module.setPlaying(playing);
|
Module.setPlaying(playing);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
this.isPlaying = () => {
|
||||||
|
return this.core.val(this.sheet.sequence.pointer.playing);
|
||||||
|
};
|
||||||
this.studio = studio;
|
this.studio = studio;
|
||||||
this.core = core;
|
this.core = core;
|
||||||
|
|
|
@ -34,6 +34,19 @@ const getUuid = () => {
|
||||||
return uuid.getUuid();
|
return uuid.getUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTimestamp = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const dd = String(now.getDate()).padStart(2, '0');
|
||||||
|
const mm = String(now.getMonth() + 1).padStart(2, '0'); //January is 0!
|
||||||
|
const yyyy = now.getFullYear();
|
||||||
|
const hours = String(now.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||||
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||||
|
|
||||||
|
const timestamp = `${yyyy}.${mm}.${dd}.${hours}:${minutes}:${seconds}`;
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
const makeEven = (n) => {
|
const makeEven = (n) => {
|
||||||
const nr = Math.round(n);
|
const nr = Math.round(n);
|
||||||
return nr - nr % 2;
|
return nr - nr % 2;
|
||||||
|
@ -115,14 +128,12 @@ function uploadFile(expectedType = 'application/json') {
|
||||||
if (files.length == 0) return;
|
if (files.length == 0) return;
|
||||||
|
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
console.log('file', file);
|
|
||||||
|
|
||||||
let reader = new FileReader();
|
let reader = new FileReader();
|
||||||
|
|
||||||
if (expectedType === 'application/zip' || file.type === 'application/zip') {
|
if (expectedType === 'application/zip' || file.type === 'application/zip' || file.type.indexOf('audio') === 0) {
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const f = e.target.result;
|
const f = e.target.result;
|
||||||
console.log(e, file.name, file.size, file.type, f);
|
|
||||||
resolve({
|
resolve({
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
|
@ -136,7 +147,6 @@ function uploadFile(expectedType = 'application/json') {
|
||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
} else if (expectedType === 'application/json') {
|
} else if (expectedType === 'application/json') {
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
console.log(e);
|
|
||||||
const f = e.target.result;
|
const f = e.target.result;
|
||||||
|
|
||||||
// This is a regular expression to identify carriage
|
// This is a regular expression to identify carriage
|
||||||
|
@ -156,12 +166,9 @@ function uploadFile(expectedType = 'application/json') {
|
||||||
|
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
} else if (expectedType.indexOf('font') >= 0) {
|
} else if (expectedType.indexOf('font') >= 0) {
|
||||||
console.log('expect font');
|
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
console.log(e);
|
|
||||||
const f = e.target.result;
|
const f = e.target.result;
|
||||||
if (file.type.indexOf('font') >= 0) {
|
if (file.type.indexOf('font') >= 0) {
|
||||||
console.log('is font');
|
|
||||||
//var uint8View = new Uint8Array(f);
|
//var uint8View = new Uint8Array(f);
|
||||||
//console.log('trying to save the font file, file, uint8View', file, uint8View);
|
//console.log('trying to save the font file, file, uint8View', file, uint8View);
|
||||||
//FS.createDataFile(config.fs.idbfsFontDir, file.name, uint8View, true, true);
|
//FS.createDataFile(config.fs.idbfsFontDir, file.name, uint8View, true, true);
|
||||||
|
@ -182,7 +189,6 @@ function uploadFile(expectedType = 'application/json') {
|
||||||
type: file.type,
|
type: file.type,
|
||||||
arrayBuffer: f
|
arrayBuffer: f
|
||||||
};
|
};
|
||||||
console.log({outputFile});
|
|
||||||
resolve(outputFile);
|
resolve(outputFile);
|
||||||
} else {
|
} else {
|
||||||
reject('not a font');
|
reject('not a font');
|
||||||
|
@ -347,7 +353,7 @@ function verifyVariableTimeProject(vt_project) {
|
||||||
|
|
||||||
function clone(a) {
|
function clone(a) {
|
||||||
return JSON.parse(JSON.stringify(a));
|
return JSON.parse(JSON.stringify(a));
|
||||||
};
|
}
|
||||||
|
|
||||||
function getParents(elem, until = null) {
|
function getParents(elem, until = null) {
|
||||||
const parents = [];
|
const parents = [];
|
||||||
|
@ -366,6 +372,12 @@ function getParents(elem, until = null) {
|
||||||
return parents;
|
return parents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeAllEventListeners(elem) {
|
||||||
|
const tmp = elem.cloneNode(true);
|
||||||
|
elem.parentNode.replaceChild(tmp, elem);
|
||||||
|
elem = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
function arraysEqual(a, b, sortingMatters = false) {
|
function arraysEqual(a, b, sortingMatters = false) {
|
||||||
if (a === b) return true;
|
if (a === b) return true;
|
||||||
if (a == null || b == null) return false;
|
if (a == null || b == null) return false;
|
||||||
|
@ -381,11 +393,37 @@ function arraysEqual(a, b, sortingMatters = false) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: will break if input is not number or rgba
|
||||||
const mapValue = (value, low1, high1, low2, high2, clamp=false) => {
|
const mapValue = (value, low1, high1, low2, high2, clamp=false) => {
|
||||||
|
if (typeof low2 === 'number') {
|
||||||
const mapped = low2 + (high2 - low2) * (value - low1) / (high1 - low1);
|
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;
|
return clamp ? Math.min(high2 > low2 ? high2 : low2, Math.max(low2 < high2 ? low2 : high2, mapped)) : mapped;
|
||||||
|
} else if (typeof low2 === 'object' && typeof low2.r === 'number') {
|
||||||
|
const mapped = {
|
||||||
|
r: mapValue(value, low1, high1, low2.r, high2.r, clamp),
|
||||||
|
g: mapValue(value, low1, high1, low2.g, high2.g, clamp),
|
||||||
|
b: mapValue(value, low1, high1, low2.b, high2.b, clamp),
|
||||||
|
a: mapValue(value, low1, high1, low2.a, high2.a, clamp),
|
||||||
|
};
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: will break if input is not number or rgba
|
||||||
|
const smoothValue = (oldValue, newValue, smoothing) => {
|
||||||
|
if (typeof oldValue === 'number') {
|
||||||
|
return oldValue * smoothing + (1.0 - smoothing) * newValue;
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
r: smoothValue(oldValue.r, newValue.r, smoothing),
|
||||||
|
g: smoothValue(oldValue.g, newValue.g, smoothing),
|
||||||
|
b: smoothValue(oldValue.b, newValue.b, smoothing),
|
||||||
|
a: smoothValue(oldValue.a, newValue.a, smoothing),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const isMobile = () => {
|
const isMobile = () => {
|
||||||
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
|
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -395,10 +433,184 @@ const isMobile = () => {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// NOTE:
|
||||||
|
// promises must be delivered inside a function like:
|
||||||
|
//
|
||||||
|
// const promises = [];
|
||||||
|
//
|
||||||
|
// promises.push(() => { return new Promise((resolve) => { console.log('lalala ONE'); resolve() }); });
|
||||||
|
// promises.push(() => { return new Promise((resolve) => { console.log('lalala TWO'); resolve() }); });
|
||||||
|
// promises.push(() => { return new Promise((resolve) => { console.log('lalala THREE'); resolve() }); });
|
||||||
|
//
|
||||||
|
// sequencialPromises(promises, () => { console.log('i am done'); });
|
||||||
|
const sequencialPromises = async (iterable, callback = false) => {
|
||||||
|
for (const x of iterable) {
|
||||||
|
await x();
|
||||||
|
}
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: this is perfect
|
||||||
|
const toCssClass = (text, prefix = '') => {
|
||||||
|
const cssClass = prefix + 'vt_' + text
|
||||||
|
.replaceAll('.', '-d-')
|
||||||
|
.replaceAll(' ', '_')
|
||||||
|
.replaceAll(':', '-c-')
|
||||||
|
.replaceAll('#', '-h-')
|
||||||
|
.replace(/[^a-zA-Z0-9_-]/g, "")
|
||||||
|
;
|
||||||
|
return cssClass;
|
||||||
|
};
|
||||||
|
const sanitizeTheatreKey = (key, removeExtension = true) => {
|
||||||
|
let theatreKey = key;
|
||||||
|
if (removeExtension) {
|
||||||
|
theatreKey = theatreKey.split('.');
|
||||||
|
if (theatreKey.length > 1) {
|
||||||
|
theatreKey.pop();
|
||||||
|
}
|
||||||
|
theatreKey = theatreKey.join('');
|
||||||
|
}
|
||||||
|
if (theatreKey.substr(0, 1).match(/^([0-9])$/i)) {
|
||||||
|
theatreKey = `t_${theatreKey}`;
|
||||||
|
}
|
||||||
|
return theatreKey.replace(/[^a-zA-Z0-9_]/g, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const renameProperty = (o, old_key, new_key) => {
|
||||||
|
Object.defineProperty(o, new_key,
|
||||||
|
Object.getOwnPropertyDescriptor(o, old_key));
|
||||||
|
delete o[old_key];
|
||||||
|
};
|
||||||
|
|
||||||
|
const flattenObject = (o, ignoreKeys = [], pathSymbol = '.') => {
|
||||||
|
if (typeof o !== 'object') {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
let doItAgain = false;
|
||||||
|
Object.keys(o).forEach((k) => {
|
||||||
|
if (typeof o[k] === 'object' &&
|
||||||
|
ignoreKeys.indexOf(k) < 0) {
|
||||||
|
doItAgain = true;
|
||||||
|
Object.keys(o[k]).forEach((sk) => {
|
||||||
|
const nk = `${k}${pathSymbol}${sk}`;
|
||||||
|
o[nk] = o[k][sk];
|
||||||
|
});
|
||||||
|
delete o[k];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (doItAgain) {
|
||||||
|
flattenObject(o, ignoreKeys, pathSymbol);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deFlattenObject = (o, ignoreKeys = [], pathSymbol = '.') => {
|
||||||
|
Object.keys(o).forEach((k) => {
|
||||||
|
if (ignoreKeys.indexOf(k) < 0) {
|
||||||
|
const ks = k.split(pathSymbol);
|
||||||
|
if (ks.length > 1) {
|
||||||
|
let sos = o[k];
|
||||||
|
for (let i = ks.length - 1; i > 0; i--) {
|
||||||
|
sos = {[ks[i]]: sos};
|
||||||
|
}
|
||||||
|
if (typeof o[ks[0]] === 'object') {
|
||||||
|
o[ks[0]] = {...o[ks[0]], ...sos};
|
||||||
|
} else {
|
||||||
|
o[ks[0]] = sos;
|
||||||
|
}
|
||||||
|
delete o[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNestedProperty = (o, a, verify = false) => {
|
||||||
|
if (verify && (!Array.isArray(a) || typeof o !== "object" || !o.hasOwnProperty(a[0]))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let b = clone(a);
|
||||||
|
if (b.length > 1) {
|
||||||
|
let c = o[b.shift()];
|
||||||
|
return getNestedProperty(c, b, verify);
|
||||||
|
}
|
||||||
|
return o[b[0]];
|
||||||
|
};
|
||||||
|
|
||||||
|
const findNestedObjectKey = (o, k, exact = true, history = [], result = []) => {
|
||||||
|
const oks = Object.keys(o);
|
||||||
|
for (let i = 0; i < oks.length; i++) {
|
||||||
|
if (k === oks[i] || (!exact && oks[i].indexOf(k) >= 0)) {
|
||||||
|
history.push(oks[i]);
|
||||||
|
result.push({found: o[oks[i]], history});
|
||||||
|
}
|
||||||
|
if (typeof o[oks[i]] === 'object') {
|
||||||
|
const ok = Object.keys(o[oks[i]]);
|
||||||
|
const ok_history = clone(history);
|
||||||
|
ok_history.push(oks[i]);
|
||||||
|
findNestedObjectKey(o[oks[i]], k, exact, ok_history, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (history.length === 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: all input values are range 0-1
|
||||||
|
const rgbaToHexa = (rgba) => {
|
||||||
|
return '#' + (
|
||||||
|
('0' + Math.round(rgba.r * 255).toString(16)).slice(-2) +
|
||||||
|
('0' + Math.round(rgba.g * 255).toString(16)).slice(-2) +
|
||||||
|
('0' + Math.round(rgba.b * 255).toString(16)).slice(-2) +
|
||||||
|
('0' + Math.round(rgba.a * 255).toString(16)).slice(-2)
|
||||||
|
).toUpperCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: all output values are range 0-1
|
||||||
|
const hexaToRgba = (hex_a) => {
|
||||||
|
const o = hex_a[0] === '#' ? 1 : 0;
|
||||||
|
// if it is hex, we need to add alpha
|
||||||
|
const hexa = hex_a.padEnd(8 + o, 'F');
|
||||||
|
return {
|
||||||
|
r: parseInt(hexa.slice(o + 0, o + 2), 16) / 255,
|
||||||
|
g: parseInt(hexa.slice(o + 2, o + 4), 16) / 255,
|
||||||
|
b: parseInt(hexa.slice(o + 4, o + 6), 16) / 255,
|
||||||
|
a: parseInt(hexa.slice(o + 6, o + 8), 16) / 255
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileExtensionFromMimeType = (mimeType) => {
|
||||||
|
if (mimeType.toLowerCase().indexOf('audio/webm') >= 0) {
|
||||||
|
return "webm";
|
||||||
|
}
|
||||||
|
if (mimeType.toLowerCase().indexOf('audio/mpeg') >= 0) {
|
||||||
|
return "mp3";
|
||||||
|
}
|
||||||
|
if (mimeType.toLowerCase().indexOf('audio/ogg') >= 0) {
|
||||||
|
return "ogg";
|
||||||
|
}
|
||||||
|
if (mimeType.toLowerCase().indexOf('audio/wav') >= 0) {
|
||||||
|
return "wav";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// you can test these functions in
|
||||||
|
// the browser like so:
|
||||||
|
/*
|
||||||
|
import("./web/js/utils.js").then((module) => {
|
||||||
|
const toast = module.toCssClass('lol.lol');
|
||||||
|
console.log(toast);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
//
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getUuid,
|
getUuid,
|
||||||
|
getTimestamp,
|
||||||
htmlToElement,
|
htmlToElement,
|
||||||
downloadFile,
|
downloadFile,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
|
@ -412,7 +624,20 @@ export {
|
||||||
mixObject,
|
mixObject,
|
||||||
clone,
|
clone,
|
||||||
getParents,
|
getParents,
|
||||||
|
removeAllEventListeners,
|
||||||
arraysEqual,
|
arraysEqual,
|
||||||
mapValue,
|
mapValue,
|
||||||
|
smoothValue,
|
||||||
isMobile,
|
isMobile,
|
||||||
|
sequencialPromises,
|
||||||
|
toCssClass,
|
||||||
|
sanitizeTheatreKey,
|
||||||
|
renameProperty,
|
||||||
|
flattenObject,
|
||||||
|
deFlattenObject,
|
||||||
|
getNestedProperty,
|
||||||
|
findNestedObjectKey,
|
||||||
|
rgbaToHexa,
|
||||||
|
hexaToRgba,
|
||||||
|
getFileExtensionFromMimeType,
|
||||||
}
|
}
|
1009
bin/em/variabletime/web/js/vanilla-picker.js
Normal file
|
@ -50429,14 +50429,16 @@ ${content}</tr>
|
||||||
max = Math.max(n3, max);
|
max = Math.max(n3, max);
|
||||||
}
|
}
|
||||||
keyframes.forEach((cur, i3) => {
|
keyframes.forEach((cur, i3) => {
|
||||||
const curVal = valueInProp(cur.value, propConfig);
|
const cv = typeof cur.value === "boolean" ? cur.value ? 1 : 0 : cur.value;
|
||||||
|
const curVal = valueInProp(cv, propConfig);
|
||||||
check(curVal);
|
check(curVal);
|
||||||
if (!cur.connectedRight)
|
if (!cur.connectedRight)
|
||||||
return;
|
return;
|
||||||
const next = keyframes[i3 + 1];
|
const next = keyframes[i3 + 1];
|
||||||
if (!next)
|
if (!next)
|
||||||
return;
|
return;
|
||||||
const diff = (typeof next.value === "number" ? next.value : 1) - curVal;
|
const nv = typeof next.value === "boolean" ? next.value ? 1 : 0 : next.value;
|
||||||
|
const diff = (typeof nv === "number" ? nv : 1) - curVal;
|
||||||
check(curVal + cur.handles[3] * diff);
|
check(curVal + cur.handles[3] * diff);
|
||||||
check(curVal + next.handles[1] * diff);
|
check(curVal + next.handles[1] * diff);
|
||||||
});
|
});
|
||||||
|
@ -50492,7 +50494,7 @@ ${content}</tr>
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
const extremumSpace = (0, import_react185.useMemo)(() => {
|
const extremumSpace = (0, import_react185.useMemo)(() => {
|
||||||
const extremums = propConfig.type === "number" ? calculateScalarExtremums(trackData.keyframes, propConfig) : calculateNonScalarExtremums(trackData.keyframes);
|
const extremums = propConfig.type === "number" || propConfig.type === "boolean" ? calculateScalarExtremums(trackData.keyframes, propConfig) : calculateNonScalarExtremums(trackData.keyframes);
|
||||||
const fromValueSpace = (val3) => (val3 - extremums[0]) / (extremums[1] - extremums[0]);
|
const fromValueSpace = (val3) => (val3 - extremums[0]) / (extremums[1] - extremums[0]);
|
||||||
const toValueSpace = (ex) => extremums[0] + deltaToValueSpace(ex);
|
const toValueSpace = (ex) => extremums[0] + deltaToValueSpace(ex);
|
||||||
const deltaToValueSpace = (ex) => ex * (extremums[1] - extremums[0]);
|
const deltaToValueSpace = (ex) => ex * (extremums[1] - extremums[0]);
|
||||||
|
@ -50517,7 +50519,7 @@ ${content}</tr>
|
||||||
layoutP,
|
layoutP,
|
||||||
sheetObject,
|
sheetObject,
|
||||||
trackId,
|
trackId,
|
||||||
isScalar: propConfig.type === "number",
|
isScalar: propConfig.type === "number" || propConfig.type === "boolean",
|
||||||
key: kf.id,
|
key: kf.id,
|
||||||
extremumSpace: cachedExtremumSpace.current,
|
extremumSpace: cachedExtremumSpace.current,
|
||||||
color: color2
|
color: color2
|
||||||
|
@ -61808,8 +61810,51 @@ Note that it **is okay** to import '@theatre/core' multiple times. But those imp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stateEditors2.coreByProject.historic.sheetsById.staticOverrides.byObject.setValueOfCompoundProp(p3);
|
stateEditors2.coreByProject.historic.sheetsById.staticOverrides.byObject.setValueOfCompoundProp(p3);
|
||||||
|
const toVT = {
|
||||||
|
sequenced: false,
|
||||||
|
panelID: p3.objectKey,
|
||||||
|
prop: p3.pathToProp,
|
||||||
|
origin: "stateEditors.ts"
|
||||||
|
};
|
||||||
|
const event = new CustomEvent("sequenceEvent", {
|
||||||
|
bubbles: false,
|
||||||
|
detail: toVT
|
||||||
|
});
|
||||||
|
window.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
sequence2.setCompoundPropAsStatic = setCompoundPropAsStatic;
|
sequence2.setCompoundPropAsStatic = setCompoundPropAsStatic;
|
||||||
|
function setCompoundPropAsSequenced(p3) {
|
||||||
|
const tracks = _ensureTracksOfObject(p3);
|
||||||
|
for (const encodedPropPath of Object.keys(tracks.trackIdByPropPath)) {
|
||||||
|
const propPath = JSON.parse(encodedPropPath);
|
||||||
|
const isSubOfTargetPath = p3.pathToProp.every((key, i3) => propPath[i3] === key);
|
||||||
|
if (isSubOfTargetPath) {
|
||||||
|
const possibleTrackId = tracks.trackIdByPropPath[encodedPropPath];
|
||||||
|
if (typeof possibleTrackId === "string")
|
||||||
|
return;
|
||||||
|
const trackId = generateSequenceTrackId();
|
||||||
|
const track = {
|
||||||
|
type: "BasicKeyframedTrack",
|
||||||
|
__debugName: `${p3.objectKey}:${encodedPropPath}`,
|
||||||
|
keyframes: []
|
||||||
|
};
|
||||||
|
tracks.trackData[trackId] = track;
|
||||||
|
tracks.trackIdByPropPath[encodedPropPath] = trackId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const toVT = {
|
||||||
|
sequenced: false,
|
||||||
|
panelID: p3.objectKey,
|
||||||
|
prop: p3.pathToProp,
|
||||||
|
origin: "stateEditors.ts"
|
||||||
|
};
|
||||||
|
const event = new CustomEvent("sequenceEvent", {
|
||||||
|
bubbles: false,
|
||||||
|
detail: toVT
|
||||||
|
});
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
sequence2.setCompoundPropAsSequenced = setCompoundPropAsSequenced;
|
||||||
function _getTrack(p3) {
|
function _getTrack(p3) {
|
||||||
return _ensureTracksOfObject(p3).trackData[p3.trackId];
|
return _ensureTracksOfObject(p3).trackData[p3.trackId];
|
||||||
}
|
}
|
||||||
|
@ -61933,6 +61978,51 @@ Note that it **is okay** to import '@theatre/core' multiple times. But those imp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sequence2.setHandlesForKeyframe = setHandlesForKeyframe;
|
sequence2.setHandlesForKeyframe = setHandlesForKeyframe;
|
||||||
|
function addKeyframes(p3, override = true) {
|
||||||
|
const track = _getTrack(p3);
|
||||||
|
if (!track)
|
||||||
|
return;
|
||||||
|
if (p3.keyframes.length < 1)
|
||||||
|
throw new Error("holy shit, we are trying to add non-existing keyframes");
|
||||||
|
if (!override)
|
||||||
|
throw new Error("whoopsie, not overriding is not implemented");
|
||||||
|
const oldKeyframes = track.keyframes;
|
||||||
|
track.keyframes = [];
|
||||||
|
oldKeyframes.forEach((kf) => {
|
||||||
|
if (p3.keyframes[0].position > kf.position) {
|
||||||
|
track.keyframes.push(kf);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
p3.keyframes.forEach((pkf) => {
|
||||||
|
track.keyframes.push({
|
||||||
|
id: generateKeyframeId(),
|
||||||
|
position: pkf.position,
|
||||||
|
connectedRight: true,
|
||||||
|
handles: pkf.handles || [0.5, 1, 0.5, 0],
|
||||||
|
type: pkf.type || "bezier",
|
||||||
|
value: pkf.value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
oldKeyframes.forEach((kf) => {
|
||||||
|
if (p3.keyframes[p3.keyframes.length - 1].position < kf.position) {
|
||||||
|
track.keyframes.push(kf);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sequence2.addKeyframes = addKeyframes;
|
||||||
|
function keepKeyframes(p3) {
|
||||||
|
const track = _getTrack(p3);
|
||||||
|
if (!track)
|
||||||
|
return;
|
||||||
|
const keyframes = track.keyframes;
|
||||||
|
track.keyframes = [];
|
||||||
|
keyframes.forEach((kf) => {
|
||||||
|
if (p3.keyframeIds.indexOf(kf.id) >= 0) {
|
||||||
|
track.keyframes.push(kf);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sequence2.keepKeyframes = keepKeyframes;
|
||||||
function deleteKeyframes(p3) {
|
function deleteKeyframes(p3) {
|
||||||
const track = _getTrack(p3);
|
const track = _getTrack(p3);
|
||||||
if (!track)
|
if (!track)
|
||||||
|
@ -62525,7 +62615,12 @@ Note that it **is okay** to import '@theatre/core' multiple times. But those imp
|
||||||
};
|
};
|
||||||
|
|
||||||
// ../../theatre/studio/src/TheatreStudio.ts
|
// ../../theatre/studio/src/TheatreStudio.ts
|
||||||
|
init_get();
|
||||||
init_src();
|
init_src();
|
||||||
|
init_utils2();
|
||||||
|
init_pointerDeep();
|
||||||
|
init_forEachDeep();
|
||||||
|
init_getDeep();
|
||||||
init_instanceTypes();
|
init_instanceTypes();
|
||||||
init_selectors();
|
init_selectors();
|
||||||
init_getStudio();
|
init_getStudio();
|
||||||
|
@ -62560,6 +62655,60 @@ Note that it **is okay** to import '@theatre/core' multiple times. But those imp
|
||||||
},
|
},
|
||||||
__experimental_createContentOfSaveFileTyped(projectId) {
|
__experimental_createContentOfSaveFileTyped(projectId) {
|
||||||
return getStudio().createContentOfSaveFile(projectId);
|
return getStudio().createContentOfSaveFile(projectId);
|
||||||
|
},
|
||||||
|
__experimental_setPropAsSequenced(prop) {
|
||||||
|
const { path, root: root3 } = getPointerParts(prop);
|
||||||
|
if (!isSheetObject(root3)) {
|
||||||
|
throw new Error("Argument prop must be a pointer to a SheetObject property");
|
||||||
|
}
|
||||||
|
const propAddress = __spreadProps(__spreadValues({}, root3.address), { pathToProp: path });
|
||||||
|
const propConfig = getPropConfigByPath(root3.template.staticConfig, path);
|
||||||
|
if (typeof propConfig !== "undefined")
|
||||||
|
getStudio().transaction(({ stateEditors: stateEditors2 }) => {
|
||||||
|
stateEditors2.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced(propAddress, propConfig);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
__experimental_setPropAsStatic(prop) {
|
||||||
|
const { path, root: root3 } = getPointerParts(prop);
|
||||||
|
if (!isSheetObject(root3)) {
|
||||||
|
throw new Error("Argument prop must be a pointer to a SheetObject property");
|
||||||
|
}
|
||||||
|
const propAddress = __spreadProps(__spreadValues({}, root3.address), { pathToProp: path });
|
||||||
|
const propConfig = getPropConfigByPath(root3.template.staticConfig, path);
|
||||||
|
if (typeof propConfig !== "undefined") {
|
||||||
|
for (const { path: subPath, conf } of iteratePropType(propConfig, [])) {
|
||||||
|
if (isPropConfigComposite(conf))
|
||||||
|
continue;
|
||||||
|
getStudio().transaction(({ stateEditors: stateEditors2 }) => {
|
||||||
|
const pointerToSub = pointerDeep(prop, subPath);
|
||||||
|
stateEditors2.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsStatic(__spreadProps(__spreadValues({}, propAddress), {
|
||||||
|
value: root3.getValueByPointer(pointerToSub)
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
__experimental_isPropSequenced(prop) {
|
||||||
|
const { path, root: root3 } = getPointerParts(prop);
|
||||||
|
if (!isSheetObject(root3)) {
|
||||||
|
throw new Error("Argument prop must be a pointer to a SheetObject property");
|
||||||
|
}
|
||||||
|
const propAddress = __spreadProps(__spreadValues({}, root3.address), { pathToProp: path });
|
||||||
|
const propConfig = getPropConfigByPath(root3.template.staticConfig, path);
|
||||||
|
const validTracks = root3.template.getArrayOfValidSequenceTracks().getValue();
|
||||||
|
tf:
|
||||||
|
for (let t4 = 0; t4 < validTracks.length; t4++) {
|
||||||
|
const otherPath = validTracks[t4].pathToProp;
|
||||||
|
if (otherPath.length === path.length) {
|
||||||
|
for (let p3 = 0; p3 < path.length; p3++) {
|
||||||
|
if (path[p3] !== otherPath[p3]) {
|
||||||
|
continue tf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -62584,11 +62733,130 @@ Note that it **is okay** to import '@theatre/core' multiple times. But those imp
|
||||||
}
|
}
|
||||||
stateEditors2.coreByProject.historic.sheetsById.forgetSheet(sheet.address);
|
stateEditors2.coreByProject.historic.sheetsById.forgetSheet(sheet.address);
|
||||||
};
|
};
|
||||||
|
const __experimental_sequenceProp = (prop) => {
|
||||||
|
const { path, root: root3 } = getPointerParts(prop);
|
||||||
|
if (!isSheetObject(root3)) {
|
||||||
|
throw new Error("Argument prop must be a pointer to a SheetObject property");
|
||||||
|
}
|
||||||
|
const propAddress = __spreadProps(__spreadValues({}, root3.address), { pathToProp: path });
|
||||||
|
const propConfig = getPropConfigByPath(root3.template.staticConfig, path);
|
||||||
|
if (propConfig === void 0) {
|
||||||
|
throw new Error("propConfig is undefined. so, yeah.");
|
||||||
|
}
|
||||||
|
stateEditors2.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced(propAddress, propConfig);
|
||||||
|
};
|
||||||
|
const __experimental_staticProp = (prop) => {
|
||||||
|
const { path, root: root3 } = getPointerParts(prop);
|
||||||
|
if (!isSheetObject(root3)) {
|
||||||
|
throw new Error("Argument prop must be a pointer to a SheetObject property");
|
||||||
|
}
|
||||||
|
const propAddress = __spreadProps(__spreadValues({}, root3.address), { pathToProp: path });
|
||||||
|
const propConfig = getPropConfigByPath(root3.template.staticConfig, path);
|
||||||
|
if (propConfig === void 0) {
|
||||||
|
throw new Error("propConfig is undefined. so, yeah.");
|
||||||
|
}
|
||||||
|
for (const { path: subPath, conf } of iteratePropType(propConfig, [])) {
|
||||||
|
if (isPropConfigComposite(conf))
|
||||||
|
continue;
|
||||||
|
const pointerToSub = pointerDeep(prop, subPath);
|
||||||
|
stateEditors2.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsStatic(__spreadProps(__spreadValues({}, propAddress), {
|
||||||
|
value: root3.getValueByPointer(pointerToSub)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const __experimental_deleteKeyframes = (prop, from = 0, to = 0) => {
|
||||||
|
const { root: root3, path } = getPointerParts(prop);
|
||||||
|
if (isSheetObject(root3)) {
|
||||||
|
const sequenceTracksTree = root3.template.getMapOfValidSequenceTracks_forStudio().getValue();
|
||||||
|
const defaultValue = getDeep(root3.template.getDefaultValues().getValue(), path);
|
||||||
|
const propConfig = getPropConfigByPath(root3.template.staticConfig, path);
|
||||||
|
const unsetStaticOrKeyframeProp = (value, path2) => {
|
||||||
|
const propAddress = __spreadProps(__spreadValues({}, root3.address), { pathToProp: path2 });
|
||||||
|
const trackId = get_default(sequenceTracksTree, path2);
|
||||||
|
const sequence = root3.sheet.getSequence();
|
||||||
|
const trackP = val(sequence._project.pointers.historic.sheetsById[sequence._sheet.address.sheetId].sequence.tracksByObject[root3.address.objectKey]);
|
||||||
|
if (!trackP) {
|
||||||
|
throw new Error("whatever, man");
|
||||||
|
}
|
||||||
|
const { trackData, trackIdByPropPath } = trackP;
|
||||||
|
if (typeof trackId === "string" && typeof trackData !== "undefined") {
|
||||||
|
const track = trackData[trackId];
|
||||||
|
if (!track) {
|
||||||
|
throw new Error("whatever, man");
|
||||||
|
}
|
||||||
|
const keyframeIds = [];
|
||||||
|
if (to > from) {
|
||||||
|
track.keyframes.forEach((kf) => {
|
||||||
|
if (kf.position >= from && kf.position <= to) {
|
||||||
|
} else {
|
||||||
|
keyframeIds.push(kf.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const objectKey = propAddress.objectKey;
|
||||||
|
stateEditors2.coreByProject.historic.sheetsById.sequence.keepKeyframes(__spreadProps(__spreadValues({}, root3.address), {
|
||||||
|
objectKey,
|
||||||
|
trackId,
|
||||||
|
keyframeIds
|
||||||
|
}));
|
||||||
|
} else if (propConfig !== void 0) {
|
||||||
|
stateEditors2.coreByProject.historic.sheetsById.staticOverrides.byObject.unsetValueOfPrimitiveProp(propAddress);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (propConfig.type === "compound") {
|
||||||
|
forEachPropDeep(defaultValue, (v6, pathToProp) => {
|
||||||
|
unsetStaticOrKeyframeProp(v6, pathToProp);
|
||||||
|
}, getPointerParts(prop).path);
|
||||||
|
} else {
|
||||||
|
unsetStaticOrKeyframeProp(defaultValue, path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error("Only setting props of SheetObject-s is supported in a transaction so far");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const __experimental_addKeyframes = (prop, keyframes) => {
|
||||||
|
const { root: root3, path } = getPointerParts(prop);
|
||||||
|
if (isSheetObject(root3)) {
|
||||||
|
let sequenceTracksTree = root3.template.getMapOfValidSequenceTracks_forStudio().getValue();
|
||||||
|
const defaultValue = getDeep(root3.template.getDefaultValues().getValue(), path);
|
||||||
|
const propConfig = getPropConfigByPath(root3.template.staticConfig, path);
|
||||||
|
const addStaticOrKeyframeProp = (value, path2) => {
|
||||||
|
const propAddress = __spreadProps(__spreadValues({}, root3.address), { pathToProp: path2 });
|
||||||
|
let trackId = get_default(sequenceTracksTree, path2);
|
||||||
|
if (typeof trackId !== "string" && propConfig !== void 0) {
|
||||||
|
throw Error("can only add keyframes to sequenced prop");
|
||||||
|
}
|
||||||
|
if (typeof trackId === "string") {
|
||||||
|
const objectKey = propAddress.objectKey;
|
||||||
|
stateEditors2.coreByProject.historic.sheetsById.sequence.addKeyframes(__spreadProps(__spreadValues({}, root3.address), {
|
||||||
|
objectKey,
|
||||||
|
trackId,
|
||||||
|
keyframes
|
||||||
|
}));
|
||||||
|
} else if (propConfig !== void 0) {
|
||||||
|
throw Error("hmmm");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (propConfig.type === "compound") {
|
||||||
|
forEachPropDeep(defaultValue, (v6, pathToProp) => {
|
||||||
|
addStaticOrKeyframeProp(v6, pathToProp);
|
||||||
|
}, getPointerParts(prop).path);
|
||||||
|
} else {
|
||||||
|
addStaticOrKeyframeProp(defaultValue, path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error("Only setting props of SheetObject-s is supported in a transaction so far");
|
||||||
|
}
|
||||||
|
};
|
||||||
return fn2({
|
return fn2({
|
||||||
set: set3,
|
set: set3,
|
||||||
unset: unset2,
|
unset: unset2,
|
||||||
__experimental_forgetObject,
|
__experimental_forgetObject,
|
||||||
__experimental_forgetSheet
|
__experimental_forgetSheet,
|
||||||
|
__experimental_sequenceProp,
|
||||||
|
__experimental_staticProp,
|
||||||
|
__experimental_deleteKeyframes,
|
||||||
|
__experimental_addKeyframes
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -1,50 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
15
browser.sh
|
@ -6,7 +6,18 @@ fly ()
|
||||||
echo $!
|
echo $!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
PREVIOUS_DIR=$(pwd)
|
||||||
|
|
||||||
|
#. ~/.bash_aliases
|
||||||
|
|
||||||
|
cd $DIR
|
||||||
|
|
||||||
#echo $(fly /usr/bin/chromium --incognito --screen=1 $@)
|
#echo $(fly /usr/bin/chromium --incognito --screen=1 $@)
|
||||||
#/usr/bin/chromium --incognito --screen=1 $@ &
|
#/usr/bin/chromium --incognito --screen=1 $@ &
|
||||||
/usr/bin/chromium --screen=1 --enable-logging --password-store=basic --v=1 $@ &
|
fly flatpak run com.github.Eloston.UngoogledChromium --screen=1 --disk-cache-dir=/dev/null --disk-cache-size=1 --enable-logging --password-store=basic --v=1 $@ &
|
||||||
echo $!
|
#flatchrome Tmp --screen=1 --disk-cache-dir=/dev/null --disk-cache-size=1 --enable-logging --password-store=basic --v=1 $@ &
|
||||||
|
browser_pid=$!
|
||||||
|
echo $browser_pid > .browserpid
|
||||||
|
|
||||||
|
cd $PREVIOUS_DIR
|
||||||
|
|
2
clean.sh
|
@ -5,7 +5,7 @@ PREVIOUS_DIR=$(pwd)
|
||||||
|
|
||||||
cd $DIR
|
cd $DIR
|
||||||
|
|
||||||
make clean && rm -rf obj && rm -rf ../../../addons/obj
|
emmake make clean && make clean && rm -rf obj && rm -rf ../../../addons/obj
|
||||||
rm -rf ../../../libs/openFrameworksCompiled/lib/linux64/obj
|
rm -rf ../../../libs/openFrameworksCompiled/lib/linux64/obj
|
||||||
rm -rf ../../../libs/openFrameworksCompiled/lib/linux64/libopenFrameworks.a
|
rm -rf ../../../libs/openFrameworksCompiled/lib/linux64/libopenFrameworks.a
|
||||||
rm -rf ../../../libs/openFrameworksCompiled/lib/emscripten/obj
|
rm -rf ../../../libs/openFrameworksCompiled/lib/emscripten/obj
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
grep bin/web/js --exclude="*.min.*" --exclude="node_modules" --exclude="*.swp" --exclude="*.bundle.js" --exclude="script.js" --exclude="ffmpeg_modules" -nr -e $@
|
grep bin/em/variabletime/web/js --exclude="*.min.*" --exclude="node_modules" --exclude="*.swp" --exclude="*.bundle.js" --exclude="script.js" --exclude="ffmpeg_modules" -nr -e "$@"
|
||||||
|
|
|
@ -7,7 +7,7 @@ cd $DIR
|
||||||
|
|
||||||
project=$(basename $DIR)
|
project=$(basename $DIR)
|
||||||
|
|
||||||
rm -rf bin/$project*
|
rm -rf bin/em/$project/index.*
|
||||||
rm -rf bin/data/ofxMsdfgen
|
rm -rf bin/data/ofxMsdfgen
|
||||||
rm -rf bin/data/ofxGPUFont
|
rm -rf bin/data/ofxGPUFont
|
||||||
cp -r ../../../addons/ofxMsdfgen/data/ofxMsdfgen ./bin/data/
|
cp -r ../../../addons/ofxMsdfgen/data/ofxMsdfgen ./bin/data/
|
||||||
|
|
14
rebuild.sh
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
PREVIOUS_DIR=$(pwd)
|
||||||
|
|
||||||
|
cd $DIR
|
||||||
|
|
||||||
|
./lightclean.sh
|
||||||
|
|
||||||
|
emmake make -j12
|
||||||
|
|
||||||
|
./reloadbrowser.sh
|
||||||
|
|
||||||
|
cd $PREVIOUS_DIR
|
17
reloadbrowser.sh
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
PREVIOUS_DIR=$(pwd)
|
||||||
|
|
||||||
|
cd $DIR
|
||||||
|
|
||||||
|
# get active window in a variable
|
||||||
|
lol=$(xdotool getactivewindow)
|
||||||
|
|
||||||
|
# reload the browser
|
||||||
|
xdotool search --onlyvisible --sync --pid $(cat ./.browserpid) --name chromium windowactivate --sync key CTRL+SHIFT+R
|
||||||
|
|
||||||
|
# reactivate old window
|
||||||
|
xdotool windowactivate $lol
|
||||||
|
|
||||||
|
cd $PREVIOUS_DIR
|
25
serve.py
|
@ -10,6 +10,8 @@ import ssl
|
||||||
|
|
||||||
# openssl req -new -x509 -keyout ssl/key.pem -out ssl/server.pem -days 365 -nodes
|
# openssl req -new -x509 -keyout ssl/key.pem -out ssl/server.pem -days 365 -nodes
|
||||||
|
|
||||||
|
browser_cmd = "/usr/bin/chromium --screen=1 --disk-cache-dir=/dev/null --disk-cache-size=1 --enable-logging --password-store=basic --v=1"
|
||||||
|
|
||||||
class CORSRequestHandler(SimpleHTTPRequestHandler):
|
class CORSRequestHandler(SimpleHTTPRequestHandler):
|
||||||
def end_headers(self):
|
def end_headers(self):
|
||||||
self.send_header("Cross-Origin-Opener-Policy", "same-origin")
|
self.send_header("Cross-Origin-Opener-Policy", "same-origin")
|
||||||
|
@ -34,40 +36,41 @@ def serve(root, port, run_browser):
|
||||||
host = '0.0.0.0'
|
host = '0.0.0.0'
|
||||||
server_address = (host, port)
|
server_address = (host, port)
|
||||||
|
|
||||||
# test(CORSRequestHandler, HTTPServer, port=port)
|
|
||||||
httpd = HTTPServer(server_address, CORSRequestHandler)
|
httpd = HTTPServer(server_address, CORSRequestHandler)
|
||||||
certfile = "ssl/localhost+4.pem"
|
certfile = "ssl/localhost+4.pem"
|
||||||
keyfile = "ssl/localhost+4-key.pem"
|
keyfile = "ssl/localhost+4-key.pem"
|
||||||
|
|
||||||
if not os.path.exists(certfile):
|
if not os.path.exists(certfile):
|
||||||
print("using ssl/server.pm")
|
|
||||||
certfile = "ssl/server.pem"
|
certfile = "ssl/server.pem"
|
||||||
if not os.path.exists(keyfile):
|
if not os.path.exists(keyfile):
|
||||||
print("using ssl/key.pm")
|
|
||||||
keyfile = "ssl/key.pem"
|
keyfile = "ssl/key.pem"
|
||||||
|
|
||||||
|
print(f"using {certfile} and {keyfile} for ssl")
|
||||||
|
|
||||||
|
# NOTE: read README.md for more instructions if you want a CA
|
||||||
if os.path.exists(certfile) and os.path.exists(keyfile):
|
if os.path.exists(certfile) and os.path.exists(keyfile):
|
||||||
httpd.socket = ssl.wrap_socket(httpd.socket,
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
server_side=True,
|
context.load_cert_chain(certfile, keyfile)
|
||||||
certfile=certfile,
|
httpd.socket = context.wrap_socket(httpd.socket,
|
||||||
keyfile=keyfile,
|
server_side=True)
|
||||||
ssl_version=ssl.PROTOCOL_TLS)
|
|
||||||
protocol = 'https'
|
protocol = 'https'
|
||||||
|
|
||||||
if run_browser:
|
if run_browser:
|
||||||
# Open the served page in the user's default browser.
|
# Open the served page in the user's default browser.
|
||||||
print("Opening the served URL in the default browser (use `--no-browser` or `-n` to disable this).")
|
print("Opening the served URL in the default browser (use `--no-browser` or `-n` to disable this).")
|
||||||
subprocess.call([f"../browser.sh", f"{protocol}://{open_host}:{port}/variabletime.html"])
|
subprocess.call(["pwd"])
|
||||||
|
subprocess.call([f"../../../browser.sh", f"{protocol}://{open_host}:{port}/index.html"])
|
||||||
|
|
||||||
print(f"serving on port {port}")
|
print(f"serving on port {port}")
|
||||||
httpd.serve_forever()
|
httpd.serve_forever()
|
||||||
|
|
||||||
|
# The main function
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("-p", "--port", help="port to listen on", default=8060, type=int)
|
parser.add_argument("-p", "--port", help="port to listen on", default=8060, type=int)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-r", "--root", help="path to serve as root (relative to `platform/web/`)", default="./bin", type=Path
|
"-r", "--root", help="path to serve as root (relative to `platform/web/`)", default="./bin/em/variabletime", type=Path
|
||||||
)
|
)
|
||||||
browser_parser = parser.add_mutually_exclusive_group(required=False)
|
browser_parser = parser.add_mutually_exclusive_group(required=False)
|
||||||
browser_parser.add_argument(
|
browser_parser.add_argument(
|
||||||
|
|
10
src/main.cpp
|
@ -137,6 +137,7 @@ shared_ptr <VariableEditor::ofApp> app;
|
||||||
dir.listDir();
|
dir.listDir();
|
||||||
|
|
||||||
ofDirectory userFonts("/idbfs/fonts");
|
ofDirectory userFonts("/idbfs/fonts");
|
||||||
|
ofDirectory userAudio("/idbfs/audio"); // possibly rename to media?
|
||||||
|
|
||||||
nlohmann::json json;
|
nlohmann::json json;
|
||||||
json["files"] = nlohmann::json::array();
|
json["files"] = nlohmann::json::array();
|
||||||
|
@ -157,6 +158,15 @@ shared_ptr <VariableEditor::ofApp> app;
|
||||||
if(!file.moveTo(userFonts)){
|
if(!file.moveTo(userFonts)){
|
||||||
std::cout << "Module::importProjectAsZip" << " - cannot move font " << file.getFileName();
|
std::cout << "Module::importProjectAsZip" << " - cannot move font " << file.getFileName();
|
||||||
}
|
}
|
||||||
|
}else if(ofToLower(file.getExtension()) == "mp3"
|
||||||
|
|| ofToLower(file.getExtension()) == "m4a"
|
||||||
|
|| ofToLower(file.getExtension()) == "wav"
|
||||||
|
|| ofToLower(file.getExtension()) == "webm"
|
||||||
|
|| ofToLower(file.getExtension()) == "aac"
|
||||||
|
|| ofToLower(file.getExtension()) == "ogg"){
|
||||||
|
if(!file.moveTo(userAudio)){
|
||||||
|
std::cout << "Module::importProjectAsZip" << " - cannot move audiofile " << file.getFileName();
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
file.remove();
|
file.remove();
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,9 @@ namespace VariableEditor {
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
void ofApp::setup(){
|
void ofApp::setup(){
|
||||||
OFX_PROFILER_FUNCTION();
|
OFX_PROFILER_FUNCTION();
|
||||||
|
#ifdef TARGET_EMSCRIPTEN
|
||||||
EM_ASM({window.setLoadingTask('setting up rendering', 0)});
|
EM_ASM({window.setLoadingTask('setting up rendering', 0)});
|
||||||
|
#endif
|
||||||
|
|
||||||
{
|
{
|
||||||
ofFile sf("appSettings.json");
|
ofFile sf("appSettings.json");
|
||||||
|
@ -58,7 +60,9 @@ void ofApp::setup(){
|
||||||
10000000, // farDist
|
10000000, // farDist
|
||||||
glm::vec2(0, 0) // lensOffset
|
glm::vec2(0, 0) // lensOffset
|
||||||
);
|
);
|
||||||
|
#ifdef TARGET_EMSCRIPTEN
|
||||||
EM_ASM({window.setLoadingTask('setting up rendering', 10)});
|
EM_ASM({window.setLoadingTask('setting up rendering', 10)});
|
||||||
|
#endif
|
||||||
|
|
||||||
ofDisableArbTex();
|
ofDisableArbTex();
|
||||||
fboSettings.width = ofGetWidth() * AA;
|
fboSettings.width = ofGetWidth() * AA;
|
||||||
|
@ -79,25 +83,26 @@ void ofApp::setup(){
|
||||||
|
|
||||||
//fbo.allocate(ofGetWidth() * AA, ofGetHeight() * AA, GL_RGB);
|
//fbo.allocate(ofGetWidth() * AA, ofGetHeight() * AA, GL_RGB);
|
||||||
|
|
||||||
|
#ifdef TARGET_EMSCRIPTEN
|
||||||
EM_ASM({window.setLoadingTask('setting up rendering', 30)});
|
EM_ASM({window.setLoadingTask('setting up rendering', 30)});
|
||||||
|
#endif
|
||||||
layerComposition.setup();
|
layerComposition.setup();
|
||||||
|
#ifdef TARGET_EMSCRIPTEN
|
||||||
EM_ASM({window.setLoadingTask('setting up rendering', 90)});
|
EM_ASM({window.setLoadingTask('setting up rendering', 90)});
|
||||||
|
#endif
|
||||||
layerComposition.setVFlip(true);
|
layerComposition.setVFlip(true);
|
||||||
|
|
||||||
#ifndef TARGET_OPENGLES
|
#ifndef TARGET_EMSCRIPTEN
|
||||||
{
|
{
|
||||||
std::string fontPath = "data/fonts/Version-2-var.ttf";
|
std::string fontPath = "data/fonts/Version-2.ttf";
|
||||||
ofxVariableLab::LayerType type = ofxVariableLab::LayerType::GPUFONT;
|
|
||||||
ofxVariableLab::Layer::Props props;
|
ofxVariableLab::Layer::Props props;
|
||||||
props.fontPath = fontPath;
|
props.fontPath = fontPath;
|
||||||
props.text = "yo, whatever you want, and especially pancakes";
|
props.text = "yo, whatever you want, and especially pancakes";
|
||||||
props.y = 120;
|
props.y = 120;
|
||||||
props.x = 95;
|
props.x = 95;
|
||||||
layerComposition.addLayer(
|
if(ofFile(fontPath).exists()){
|
||||||
{fontPath, type},
|
layerComposition.addLayer(props, "layer-0");
|
||||||
props,
|
}
|
||||||
{{"Weight", 100.0}, {"Weight", 700.0}}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -130,7 +135,9 @@ void ofApp::setup(){
|
||||||
<< " | Minor(" << ofToString(glMajor) << ")"
|
<< " | Minor(" << ofToString(glMajor) << ")"
|
||||||
<< endl;
|
<< endl;
|
||||||
artboard.setup();
|
artboard.setup();
|
||||||
|
#ifdef TARGET_EMSCRIPTEN
|
||||||
EM_ASM({window.setLoadingTask('setting up rendering', 100)});
|
EM_ASM({window.setLoadingTask('setting up rendering', 100)});
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
|
64
src/ofApp.h
|
@ -5,6 +5,7 @@
|
||||||
#include "conversion.h"
|
#include "conversion.h"
|
||||||
#include "Artboard.h"
|
#include "Artboard.h"
|
||||||
#include "ofEasyCam.h"
|
#include "ofEasyCam.h"
|
||||||
|
#include "ofFileUtils.h"
|
||||||
#include "ofMain.h"
|
#include "ofMain.h"
|
||||||
#include "ofQuaternion.h"
|
#include "ofQuaternion.h"
|
||||||
#include "ofTrueTypeFont.h"
|
#include "ofTrueTypeFont.h"
|
||||||
|
@ -34,8 +35,8 @@ struct AppSettings {
|
||||||
212 / 255.0,
|
212 / 255.0,
|
||||||
212 / 255.0,
|
212 / 255.0,
|
||||||
1}; // check data/appSettings.json
|
1}; // check data/appSettings.json
|
||||||
string tmpExportDir = "data/export";
|
std::string tmpExportDir = "data/export";
|
||||||
string tmpImportDir = "data/import";
|
std::string tmpImportDir = "data/import";
|
||||||
|
|
||||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AppSettings,
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AppSettings,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
|
@ -43,23 +44,29 @@ struct AppSettings {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ZipProjectSaver : public ofThread {
|
class ZipProjectSaver :
|
||||||
|
public ofThread {
|
||||||
public:
|
public:
|
||||||
void setup(string projectName,
|
void setup(string projectName,
|
||||||
string projectJsonString){
|
string projectJsonString){
|
||||||
this->projectName = projectName;
|
this->projectName = projectName;
|
||||||
this->projectJsonString = projectJsonString;
|
this->projectJsonString = projectJsonString;
|
||||||
this->userFontsPath = "/idbfs/fonts";
|
this->userFontsPath = "/idbfs/fonts";
|
||||||
|
this->userAudioPath = "/idbfs/audio";
|
||||||
this->timestamp = ofGetTimestampString();
|
this->timestamp = ofGetTimestampString();
|
||||||
this->filename = projectName + "_project_" + timestamp + ".zip";
|
this->filename = projectName + "_project_" + timestamp + ".zip";
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(){
|
void update(){
|
||||||
if(freshDownload.load()){
|
if(freshDownload.load()){
|
||||||
|
#ifdef TARGET_EMSCRIPTEN
|
||||||
emscripten_browser_file::download(filename.c_str(),
|
emscripten_browser_file::download(filename.c_str(),
|
||||||
"application/zip",
|
"application/zip",
|
||||||
buffer,
|
buffer,
|
||||||
buffer_size);
|
buffer_size);
|
||||||
|
#else
|
||||||
|
ofBufferToFile(filename.c_str(), ofBuffer(buffer, buffer_size));
|
||||||
|
#endif
|
||||||
freshDownload.store(false);
|
freshDownload.store(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,6 +100,28 @@ class ZipProjectSaver : public ofThread {
|
||||||
ofBuffer buffy = file.readToBuffer();
|
ofBuffer buffy = file.readToBuffer();
|
||||||
zip.addBuffer(fontFilename, buffy.getData(), buffy.size());
|
zip.addBuffer(fontFilename, buffy.getData(), buffy.size());
|
||||||
}
|
}
|
||||||
|
ofDirectory userAudio(userAudioPath);
|
||||||
|
userAudio.sort();
|
||||||
|
userAudio.allowExt("mp3");
|
||||||
|
userAudio.allowExt("ogg");
|
||||||
|
userAudio.allowExt("wav");
|
||||||
|
userAudio.allowExt("webm");
|
||||||
|
userAudio.allowExt("m4a");
|
||||||
|
userAudio.listDir();
|
||||||
|
for(int i = 0; i < userAudio.size(); i++){
|
||||||
|
std::string audioFilename = userAudio.getName(i);
|
||||||
|
std::string audioFilepath = userAudioPath + "/" + audioFilename;
|
||||||
|
ofFile file = userAudio.getFile(i);
|
||||||
|
ofStringReplace(audioFilepath, "/idbfs/", "idbfs/");
|
||||||
|
if(of::filesystem::exists(audioFilepath)){
|
||||||
|
//cout << "huuurrayy " << audioFilepath << " exists" << endl;
|
||||||
|
}else{
|
||||||
|
cout << "ofApp::downloadProject() trying to load " << audioFilepath << " but it does not exist." << endl;
|
||||||
|
}
|
||||||
|
file.open(audioFilepath);
|
||||||
|
ofBuffer buffy = file.readToBuffer();
|
||||||
|
zip.addBuffer(audioFilename, buffy.getData(), buffy.size());
|
||||||
|
}
|
||||||
buffer = NULL;
|
buffer = NULL;
|
||||||
buffer_size = 0;
|
buffer_size = 0;
|
||||||
zip.getOutputBuffer(&buffer, buffer_size);
|
zip.getOutputBuffer(&buffer, buffer_size);
|
||||||
|
@ -104,11 +133,12 @@ class ZipProjectSaver : public ofThread {
|
||||||
void exit(){
|
void exit(){
|
||||||
free(buffer);
|
free(buffer);
|
||||||
}
|
}
|
||||||
string projectName;
|
std::string projectName;
|
||||||
string projectJsonString;
|
std::string projectJsonString;
|
||||||
string userFontsPath;
|
std::string userFontsPath;
|
||||||
string timestamp;
|
std::string userAudioPath;
|
||||||
string filename;
|
std::string timestamp;
|
||||||
|
std::string filename;
|
||||||
char * buffer;
|
char * buffer;
|
||||||
size_t buffer_size;
|
size_t buffer_size;
|
||||||
std::atomic <bool> freshDownload{false};
|
std::atomic <bool> freshDownload{false};
|
||||||
|
@ -130,6 +160,7 @@ class ZipSaver : public ofThread {
|
||||||
|
|
||||||
void update(){
|
void update(){
|
||||||
if(freshDownload.load()){
|
if(freshDownload.load()){
|
||||||
|
#ifdef TARGET_EMSCRIPTEN
|
||||||
EM_ASM({
|
EM_ASM({
|
||||||
document.getElementById('export_progress_task').innerHTML = 'rendering';
|
document.getElementById('export_progress_task').innerHTML = 'rendering';
|
||||||
let innerHTML = "|";
|
let innerHTML = "|";
|
||||||
|
@ -146,6 +177,9 @@ class ZipSaver : public ofThread {
|
||||||
"application/zip",
|
"application/zip",
|
||||||
buffer,
|
buffer,
|
||||||
buffer_size);
|
buffer_size);
|
||||||
|
#else
|
||||||
|
ofBufferToFile(filename.c_str(), ofBuffer(buffer, buffer_size));
|
||||||
|
#endif
|
||||||
freshDownload.store(false);
|
freshDownload.store(false);
|
||||||
}else if(isThreadRunning()){
|
}else if(isThreadRunning()){
|
||||||
setProgress(percent.load());
|
setProgress(percent.load());
|
||||||
|
@ -153,6 +187,7 @@ class ZipSaver : public ofThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setProgress(int percent){
|
void setProgress(int percent){
|
||||||
|
#ifdef TARGET_EMSCRIPTEN
|
||||||
EM_ASM_INT({
|
EM_ASM_INT({
|
||||||
let percent = $0;
|
let percent = $0;
|
||||||
document.getElementById('export_progress_task').innerHTML = 'rendering';
|
document.getElementById('export_progress_task').innerHTML = 'rendering';
|
||||||
|
@ -171,6 +206,9 @@ class ZipSaver : public ofThread {
|
||||||
let progress_task = document.getElementById("export_progress_task");
|
let progress_task = document.getElementById("export_progress_task");
|
||||||
progress_task.innerHTML = "creating zip file";
|
progress_task.innerHTML = "creating zip file";
|
||||||
}, percent);
|
}, percent);
|
||||||
|
#else
|
||||||
|
std::cout << "progress: " << percent << "/100" << std::endl;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void threadedFunction(){
|
void threadedFunction(){
|
||||||
|
@ -188,9 +226,9 @@ class ZipSaver : public ofThread {
|
||||||
ofImage image;
|
ofImage image;
|
||||||
image.setUseTexture(false);
|
image.setUseTexture(false);
|
||||||
image.load(filepath);
|
image.load(filepath);
|
||||||
ofBuffer buffer;
|
ofBuffer b;
|
||||||
ofSaveImage(image.getPixels(), buffer, OF_IMAGE_FORMAT_PNG);
|
ofSaveImage(image.getPixels(), b, OF_IMAGE_FORMAT_PNG);
|
||||||
zip.addBuffer(f + ".png", buffer.getData(), buffer.size());
|
zip.addBuffer(f + ".png", b.getData(), b.size());
|
||||||
percent.store((float(i) / float(total)) * 100.0f);
|
percent.store((float(i) / float(total)) * 100.0f);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
@ -232,6 +270,7 @@ class ZipUnpacker : public ofThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setProgress(int percent){
|
void setProgress(int percent){
|
||||||
|
#ifdef TARGET_EMSCRIPTEN
|
||||||
EM_ASM_INT({
|
EM_ASM_INT({
|
||||||
let percent = $0;
|
let percent = $0;
|
||||||
document.getElementById('export_progress_task').innerHTML = 'uploading and unpacking';
|
document.getElementById('export_progress_task').innerHTML = 'uploading and unpacking';
|
||||||
|
@ -250,6 +289,9 @@ class ZipUnpacker : public ofThread {
|
||||||
let progress_task = document.getElementById("import_progress_task");
|
let progress_task = document.getElementById("import_progress_task");
|
||||||
progress_task.innerHTML = "creating zip file";
|
progress_task.innerHTML = "creating zip file";
|
||||||
}, percent);
|
}, percent);
|
||||||
|
#else
|
||||||
|
std::cout << "progress: " << percent << "/100" << std::endl;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void threadedFunction(){
|
void threadedFunction(){
|
||||||
|
|
14
watch.sh
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
PREVIOUS_DIR=$(pwd)
|
||||||
|
|
||||||
|
cd $DIR
|
||||||
|
|
||||||
|
while true;
|
||||||
|
do
|
||||||
|
echo "$(git ls-files src && git ls-files bin/data && echo "assets/template.html")" | entr -d ./rebuild.sh
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
cd $PREVIOUS_DIR
|
14
watchJs.sh
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
PREVIOUS_DIR=$(pwd)
|
||||||
|
|
||||||
|
cd $DIR
|
||||||
|
|
||||||
|
while true;
|
||||||
|
do
|
||||||
|
echo "$(git ls-files bin/em/variabletime/web && find "$(pwd)/bin/em/variabletime/web/theatre_modules" -iname "*.js")" | entr -d ./reloadbrowser.sh
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
cd $PREVIOUS_DIR
|