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/data/
|
||||
!bin/data/*
|
||||
!bin/web/
|
||||
!bin/web/*
|
||||
# ugly recursion https://stackoverflow.com/questions/3667986/git-ignore-exception-not-working-as-desired
|
||||
!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/
|
||||
bin/data/ofxMsdfgen
|
||||
bin/data/ofxGPUFont
|
||||
|
@ -39,3 +53,6 @@ bin/web/fonts/*
|
|||
# DIST
|
||||
# these are related to distributing variable time
|
||||
upload*
|
||||
|
||||
# GENERATED
|
||||
.browserpid
|
||||
|
|
|
@ -26,6 +26,7 @@ Install all dependencies, then
|
|||
|
||||
### Optional:
|
||||
- [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:
|
||||
- [ffmpeg.wasm](https://github.com/ffmpegwasm/ffmpeg.wasm)
|
||||
|
|
|
@ -290,10 +290,13 @@
|
|||
<!--<div class="move">move</div>-->
|
||||
<!--</div>-->
|
||||
<button id="midi_open">midi</button>
|
||||
<button id="hide_ui">hide ui</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="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="add_font">add font</button>
|
||||
<!--<button id="debug_profiling" onclick="window.toggleProfiling()">debug start profiling</button>-->
|
||||
</div>
|
||||
|
||||
|
@ -357,6 +360,13 @@
|
|||
<div class="content">
|
||||
<div class="what"><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>
|
||||
<!-- MIDI BEGIN -->
|
||||
|
@ -523,10 +533,50 @@
|
|||
</div>
|
||||
<!-- ABOUT END -->
|
||||
<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 -->
|
||||
<script id="ffmpeg.min.js" type="application/javascript" src="/web/ffmpeg_modules/ffmpeg.min.js"></script>
|
||||
<!-- 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>
|
||||
</body>
|
||||
</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 {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
padding: 2em;
|
||||
padding: 10px 10px 20px 10px;
|
||||
color: black;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
|
@ -217,11 +217,122 @@ body.debug div:not(.centerLine) {
|
|||
}
|
||||
|
||||
#notice .content .what p {
|
||||
font-size: 1.2em;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#notice .content .details p {
|
||||
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 * {
|
||||
|
@ -458,6 +569,8 @@ body.debug div:not(.centerLine) {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#exporter_render_info {
|
||||
display: none;
|
||||
color: rgb(234, 35, 51);
|
||||
|
@ -926,3 +1039,21 @@ h4{
|
|||
|
||||
|
||||
/* 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 y = 0;
|
||||
let props = {
|
||||
backgroundColor: tp.core.types.rgba({
|
||||
color: tp.core.types.rgba({
|
||||
r: 74,
|
||||
g: 94,
|
||||
b: 181,
|
||||
|
@ -100,25 +100,29 @@ const Artboard = function(tp, domElement = false, autoInit = true) {
|
|||
};
|
||||
const props2cppProps = (_props) => {
|
||||
let cppProps = JSON.parse(JSON.stringify(_props));
|
||||
let bgIsArray = Array.isArray(cppProps.backgroundColor);
|
||||
if (bgIsArray && cppProps.backgroundColor.length === 4) {
|
||||
let bgIsArray = Array.isArray(cppProps.color);
|
||||
if (bgIsArray && cppProps.color.length === 4) {
|
||||
cppProps.backgroundColor = cppProps.color;
|
||||
delete cppProps.color;
|
||||
// nothing to do
|
||||
} else if (!bgIsArray && cppProps.backgroundColor.hasOwnProperty('r')) {
|
||||
} else if (!bgIsArray && cppProps.color.hasOwnProperty('r')) {
|
||||
cppProps.backgroundColor = [
|
||||
cppProps.backgroundColor.r,
|
||||
cppProps.backgroundColor.g,
|
||||
cppProps.backgroundColor.b,
|
||||
cppProps.backgroundColor.a
|
||||
cppProps.color.r,
|
||||
cppProps.color.g,
|
||||
cppProps.color.b,
|
||||
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.default.r,
|
||||
cppProps.backgroundColor.default.g,
|
||||
cppProps.backgroundColor.default.b,
|
||||
cppProps.backgroundColor.default.a
|
||||
cppProps.color.default.r,
|
||||
cppProps.color.default.g,
|
||||
cppProps.color.default.b,
|
||||
cppProps.color.default.a
|
||||
];
|
||||
delete cppProps.color;
|
||||
} else {
|
||||
console.error('js::layer::props2cppProps', 'color could not be translated');
|
||||
console.error('js::artboard::props2cppProps', 'color could not be translated');
|
||||
}
|
||||
return cppProps;
|
||||
};
|
||||
|
@ -146,6 +150,16 @@ const Artboard = function(tp, domElement = false, autoInit = true) {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +177,35 @@ const Artboard = function(tp, domElement = false, autoInit = true) {
|
|||
this.hide = () => {
|
||||
// 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
|
||||
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,
|
||||
incrementPixelDensity: 0.01,
|
||||
friendlyNames: {
|
||||
'backgroundColor': 'Background<br>Color',
|
||||
'color': 'Background<br>Color',
|
||||
'x': 'Position X',
|
||||
'y': 'Position Y',
|
||||
'width': 'Artboard Width',
|
||||
|
@ -45,6 +45,7 @@ const config = {
|
|||
'color',
|
||||
'letterDelays',
|
||||
],
|
||||
orderSpacing: 2,
|
||||
friendlyNames: {
|
||||
'fontFamily': 'Font Family',
|
||||
'textAlignButtonsHorizontal': '',
|
||||
|
@ -63,9 +64,9 @@ const config = {
|
|||
'rotation': 'Rotation',
|
||||
'transformOrigin': 'Rotation Origin',
|
||||
'mirror_x': 'Mirror X',
|
||||
'mirror_x_distance': 'Mirror X Distance',
|
||||
'mirror_x_distance': 'X Distance',
|
||||
'mirror_y': 'Mirror Y',
|
||||
'mirror_y_distance': 'Mirror Y Distance',
|
||||
'mirror_y_distance': 'Y Distance',
|
||||
'mirror_xy': 'Mirrox XY',
|
||||
'color': 'Color',
|
||||
'letterDelays': 'Letter Delays',
|
||||
|
@ -81,6 +82,68 @@ const config = {
|
|||
zoomBaseFactor: 0.001,
|
||||
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: {
|
||||
touchTimeThreshold_s: 0.5,
|
||||
smoothingMix: 0.1,
|
||||
|
@ -88,6 +151,7 @@ const config = {
|
|||
fs: {
|
||||
idbfsDir: '/idbfs',
|
||||
idbfsFontDir: '/idbfs/fonts',
|
||||
idbfsAudioDir: '/idbfs/audio',
|
||||
idbfsTmpDir: '/idbfs/tmp',
|
||||
},
|
||||
timeline: {
|
|
@ -200,7 +200,12 @@ const Exporter = function() {
|
|||
};
|
||||
|
||||
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.height = getArtboard().theatreObject.value.height;
|
||||
options.artboard.pixelDensity = getArtboard().theatreObject.value.pixelDensity;
|
||||
|
@ -218,6 +223,10 @@ const Exporter = function() {
|
|||
|
||||
const setArtboardPropsToRenderDimensions = () => {
|
||||
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;
|
||||
//artboardValues.pixelDensity *= densityRatio;
|
||||
artboardValues.pixelDensity = densityRatio;
|
||||
|
@ -227,6 +236,10 @@ const Exporter = function() {
|
|||
|| currentArtboardValues.height !== artboardCppProps.height
|
||||
|| currentArtboardValues.pixelDensity !== artboardCppProps.pixelDensity) {
|
||||
window.isRenderDirty = true;
|
||||
if (typeof artboardCppProps.color === 'object') {
|
||||
artboardCppProps.backgroundColor = artboardCppProps.color;
|
||||
delete artboardCppProps.color;
|
||||
}
|
||||
Module.setArtboardProps(artboardCppProps);
|
||||
}
|
||||
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)],
|
||||
};
|
||||
|
||||
let updateValuesViaTheatre = true;
|
||||
let lastValues = {};
|
||||
let fontsHashMap = {};
|
||||
let sequenceEventBuffer = [];
|
||||
|
@ -106,7 +107,7 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
const sanity_maxDefault = axes[a].maxValue >= axes[a].defaultValue;
|
||||
if (sanity_minMax && sanity_minDefault && sanity_maxDefault) {
|
||||
variationAxes[axes[a].name] = tp.core.types.number(axes[a].defaultValue, {
|
||||
range: [axes[a].minValue, axes[a].maxValue],
|
||||
range: [axes[a].minValue, axes[a].maxValue],
|
||||
});
|
||||
} else {
|
||||
console.log('js::layer::selectFont', 'this axis is insane, abort', axes[a]);
|
||||
|
@ -127,26 +128,21 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
return new Promise((resolve) => {
|
||||
// NOTE: stupid hack, seems that theatrejs tries to be too smart
|
||||
// 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..
|
||||
// btw, we need a dummy property
|
||||
// this does not work
|
||||
//tp.changeObject(this.id(), {});
|
||||
|
||||
//tp.changeObject(this.id(), {dummy:true});
|
||||
//updateTheatrePropsTimeout = setTimeout(() => {
|
||||
tp.changeObject(this.id(), {dummy: true});
|
||||
setTimeout(() => {
|
||||
tp.changeObject(this.id(), props);
|
||||
setTimeout(() => {
|
||||
//updateTheatrePropsTimeout = false;
|
||||
//this.afterUpdateTheatrePropsCallback();
|
||||
this.findInjectPanel();
|
||||
resolve();
|
||||
}, 100);
|
||||
}, 100);
|
||||
//}, 100);
|
||||
});
|
||||
};
|
||||
const getDefaultFont = (selectableFonts) => {
|
||||
|
@ -204,6 +200,8 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
const onValuesChange = (values) => {
|
||||
if (values.dummy === true)
|
||||
return;
|
||||
if (!updateValuesViaTheatre)
|
||||
return;
|
||||
window.isRenderDirty = true;
|
||||
if (Object.keys(values).length > 1) {
|
||||
values.text = values.text.trim();
|
||||
|
@ -275,28 +273,19 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
|
||||
const showBoundingBoxDiv = (boundingBox = false) => {
|
||||
const boundingBoxDivId = `boundingBox-${this.id()}`;
|
||||
//const layerBoxDivId = `layerBox-${this.id()}`;
|
||||
if (document.querySelector(`#${boundingBoxDivId}`) === null) {
|
||||
if (boundingBox === false) {
|
||||
boundingBox = Module.getBoundingBox(this.id());
|
||||
}
|
||||
const artboard = window.getArtboard();
|
||||
const boundingBoxDiv = document.createElement('div');
|
||||
//const layerBoxDiv = document.createElement('div');
|
||||
boundingBoxDiv.id = boundingBoxDivId;
|
||||
boundingBoxDiv.style.position = 'fixed';
|
||||
boundingBoxDiv.style.background = 'transparent';
|
||||
boundingBoxDiv.style.border = '1px dashed dimgrey';
|
||||
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(layerBoxDiv);
|
||||
clearInterval(boundingBoxInterval);
|
||||
boundingBoxInterval = setInterval(() => {
|
||||
updateBoundingBoxDiv();
|
||||
|
@ -311,7 +300,6 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
if (bb !== null) {
|
||||
bb.remove();
|
||||
}
|
||||
//document.getElementById(`layerBox-${this.id()}`).remove();
|
||||
clearInterval(boundingBoxInterval);
|
||||
boundingBoxInterval = false;
|
||||
};
|
||||
|
@ -370,7 +358,7 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
set(this.theatreObject.props, values);
|
||||
});
|
||||
setTimeout(() => {
|
||||
promises.push(this.findInjectPanelPromise);
|
||||
promises.push(this.findInjectPanelPromise());
|
||||
Promise.allSettled(promises).then(() => {
|
||||
resolve();
|
||||
if (config.autoSave && window.isInitialized) {
|
||||
|
@ -425,7 +413,6 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
mom.append(upButton);
|
||||
mom.append(downButton);
|
||||
mom.append(removeButton);
|
||||
//window.layerOrder.add(this.id());
|
||||
|
||||
panel.addEventListener('mouseover', showBoundingBoxDiv);
|
||||
panel.addEventListener('mouseout', hideBoundingBoxDiv);
|
||||
|
@ -460,11 +447,14 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
const panelProp= tp.getPanelPropTitle(propName);
|
||||
const panelPropMom = tp.getPanelPropContainer(panelProp);
|
||||
const newValue = active ? getArtboard().theatreObject.value.width : 0;
|
||||
const textWrappingButton = tp.getPanel().querySelector('.textWrappingButton');
|
||||
|
||||
if (panelPropMom !== null && textWrappingButton !== null) {
|
||||
if (active) {
|
||||
panelPropMom.style.display = 'flex';
|
||||
tp.getPanel().querySelector('.textWrappingButton').classList.add('active');
|
||||
textWrappingButton.classList.add('active');
|
||||
} else {
|
||||
tp.getPanel().querySelector('.textWrappingButton').classList.remove('active');
|
||||
textWrappingButton.classList.remove('active');
|
||||
panelPropMom.style.display = 'none';
|
||||
}
|
||||
|
||||
|
@ -475,6 +465,7 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
set(this.theatreObject.props[propName], newValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -489,6 +480,9 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
};
|
||||
this.findInjectPanel = (resolve = false) => {
|
||||
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;
|
||||
} else {
|
||||
const panel = tp.getPanel();
|
||||
|
@ -509,17 +503,33 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
const panelPropContainer = tp.getPanelPropContainer(panelPropTitle);
|
||||
if (panelPropContainer !== null) {
|
||||
panelPropContainers[propKey] = panelPropContainer;
|
||||
const order_index = panelOrder.indexOf(propKey);
|
||||
const order_index = panelOrder.indexOf(propKey) * config.layer.orderSpacing;
|
||||
panelPropContainer.style.order = order_index;
|
||||
if (propKey === 'fontVariationAxes'
|
||||
|| propKey === 'letterDelays') {
|
||||
panelPropContainer
|
||||
.classList
|
||||
.add(`${propKey}ContWrapper`);
|
||||
panelPropContainer
|
||||
.classList
|
||||
.add(`propContWrapper`);
|
||||
} else {
|
||||
panelPropContainer
|
||||
.classList
|
||||
.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 {
|
||||
console.log('Layer::findInjectPanel',
|
||||
|
@ -545,11 +555,29 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
const subFriendlyName = config.layer.friendlyNames[subUnfriendlyName];
|
||||
if (e.innerHTML === subUnfriendlyName) {
|
||||
e.innerHTML = subFriendlyName;
|
||||
e.classList.add('propTitle');
|
||||
e.classList.add(`${unfriendlyName}_${subUnfriendlyName}Title`);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
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" />`;
|
||||
alignRightButton.innerHTML = `<img src="/web/assets/align-right.svg" alt="alignRight" />`;
|
||||
panelControlsWrapper.append(alignButtons);
|
||||
const order_index = panelOrder.indexOf('alignButtonsHorizontal');
|
||||
const order_index = panelOrder.indexOf('alignButtonsHorizontal') * config.layer.orderSpacing;
|
||||
alignButtons.style.order = order_index;
|
||||
alignButtons.classList.add('alignButtons');
|
||||
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" />`;
|
||||
alignBottomButton.innerHTML = `<img src="/web/assets/align-bottom.svg" alt="alignBottom" />`;
|
||||
panelControlsWrapper.append(alignButtons);
|
||||
const order_index = panelOrder.indexOf('alignButtonsVertical');
|
||||
const order_index = panelOrder.indexOf('alignButtonsVertical') * config.layer.orderSpacing;
|
||||
alignButtons.style.order = order_index;
|
||||
alignButtons.classList.add('alignButtons');
|
||||
alignButtons.classList.add('alignButtonsVertical');
|
||||
|
@ -684,8 +712,6 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
|
||||
const alignButtonsHorizontal = panel.querySelector('.alignButtonsHorizontal');
|
||||
const alignButtonsVertical = panel.querySelector('.alignButtonsVertical');
|
||||
//panelControlsWrapper.add(alignButtonsHorizontal);
|
||||
//panelControlsWrapper.add(alignButtonsVertical);
|
||||
|
||||
// first get previous textAlign buttons,
|
||||
// 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" />`;
|
||||
textAlignRightButton.innerHTML = `<img src="/web/assets/align-text-right.svg" alt="textAlignRight" />`;
|
||||
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;
|
||||
panelControlsWrapper.append(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);
|
||||
|
||||
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;
|
||||
clearTimeout(panelFinderTimeout);
|
||||
panelFinderTimeout = false;
|
||||
|
@ -792,8 +800,27 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
panel.addEventListener("mouseover", showBoundingBoxDivIfSelected);
|
||||
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;
|
||||
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});
|
||||
tp.getPanel().dispatchEvent(e);
|
||||
if (typeof resolve === 'function') {
|
||||
|
@ -820,43 +847,6 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
}
|
||||
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) => {
|
||||
return new Promise((resolve) => {
|
||||
tp.friendlySequenceNames();
|
||||
|
@ -1030,6 +1020,9 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
|
|||
return cppProps;
|
||||
};
|
||||
this.onValuesChange = onValuesChange;
|
||||
this.updateValuesViaTheatre = (doIt) => {
|
||||
updateValuesViaTheatre = doIt;
|
||||
};
|
||||
this.prepareForDepartureFromThisBeautifulExperience = () => {
|
||||
this.hide();
|
||||
hideBoundingBoxDiv();
|
|
@ -26,6 +26,17 @@ import {
|
|||
ModuleFS
|
||||
} from './moduleFS.js';
|
||||
|
||||
import {
|
||||
Audio
|
||||
} from './audio.js';
|
||||
|
||||
import {
|
||||
Record
|
||||
} from './record.js';
|
||||
|
||||
import {
|
||||
AudioLayer
|
||||
} from './audioLayer.js';
|
||||
//import {
|
||||
//MidiController
|
||||
//} from './midiController.js';
|
||||
|
@ -44,27 +55,35 @@ import {
|
|||
Config
|
||||
} from './config.js';
|
||||
|
||||
window.uploadFile = uploadFile;
|
||||
window.downloadFile = downloadFile;
|
||||
window.isInitialized = false;
|
||||
window.hashFromString = hashFromString;
|
||||
|
||||
const config = new Config();
|
||||
window.config = config;
|
||||
const tp = new TheatrePlay();
|
||||
window.tp = tp;
|
||||
const layers = [];
|
||||
const layersById = {};
|
||||
const layerOrder = new LayerOrder();
|
||||
window.layerOrder = layerOrder;
|
||||
const fontsAndAxes = [];
|
||||
let artboard;
|
||||
const exporter = new Exporter();
|
||||
const interactor = new Interactor();
|
||||
const moduleFS = new ModuleFS();
|
||||
window.moduleFS = moduleFS;
|
||||
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;
|
||||
|
||||
const sequenceEventBuffer = {};
|
||||
|
||||
//const midiController = new MidiController();
|
||||
|
@ -86,7 +105,7 @@ const getAbout = () => {
|
|||
const buttonExp = textParent.querySelector(".expandText");
|
||||
|
||||
if (buttonExp === null) {
|
||||
console.error("Could not find .expandText within .textParent");
|
||||
console.error("Main::getAbout","Could not find .expandText within .textParent");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -109,10 +128,7 @@ window.showAbout = () => {
|
|||
if (getAbout() === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
getAbout().classList.remove("hidden");
|
||||
|
||||
|
||||
}
|
||||
window.hideAbout = () => {
|
||||
if (getAbout() !== null) {
|
||||
|
@ -139,6 +155,16 @@ const findInjectPanel = () => {
|
|||
bottomButtonsContainer.classList.add("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');
|
||||
if (exportButton !== null) {
|
||||
bottomButtonsContainer.append(exportButton);
|
||||
|
@ -164,6 +190,11 @@ const findInjectPanel = () => {
|
|||
bottomButtonsContainer.append(startNewButton);
|
||||
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 {
|
||||
setTimeout(() => {
|
||||
findInjectPanel();
|
||||
|
@ -223,7 +254,7 @@ window.onload = () => {
|
|||
}
|
||||
tp.studio.onSelectionChange((newSelection) => {
|
||||
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().indexOf('layer-') === 0) {
|
||||
e.findInjectPanel();
|
||||
|
@ -233,12 +264,15 @@ window.onload = () => {
|
|||
}, 60);
|
||||
} else if (e.id() === 'artboard') {
|
||||
e.findInjectPanel();
|
||||
} else if (e.id().indexOf('audio') === 0) {
|
||||
e.findInjectPanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
findInjectPanel();
|
||||
tp.friendlySequenceNames();
|
||||
console.log('Main::selectionChange',newSelection.length > 0 ? newSelection[0].address.objectKey : 'aah nothing');
|
||||
});
|
||||
});
|
||||
// ABOUT BEGIN
|
||||
|
@ -299,8 +333,31 @@ const resize = () => {
|
|||
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 = () => {
|
||||
window.setLoadingTask('setting up animation', 80);
|
||||
setLoadingDoneHook();
|
||||
moduleFS.init()
|
||||
.then(() => {
|
||||
artboard = new Artboard(tp, content);
|
||||
|
@ -309,13 +366,13 @@ const postModuleInitialized = () => {
|
|||
tp.connectModuleCallbacks();
|
||||
exporter.init();
|
||||
getFontsAndAxes();
|
||||
window.setLoadingTask('loading project', 85);
|
||||
tp.loadProject().then(() => {
|
||||
interactor.init();
|
||||
resize();
|
||||
adjustPanel();
|
||||
window.setLoadingTask('setting up animation', 100);
|
||||
window.setLoadingTask('setting up whatever else is necessary', 100);
|
||||
window.isInitialized = true;
|
||||
window.setLoadingDone();
|
||||
window.autoSaveInterval = setInterval(() => {
|
||||
if (config.autoSave && window.isInitialized) {
|
||||
tp.saveProject();
|
||||
|
@ -394,10 +451,32 @@ const listAvailableFontsAndAxes = () => {
|
|||
window.listAvailableFontsAndAxes = listAvailableFontsAndAxes;
|
||||
window.getFontsAndAxes = getFontsAndAxes;
|
||||
|
||||
window.getArtboard = () => {
|
||||
return artboard;
|
||||
};
|
||||
|
||||
window.getLayers = () => {
|
||||
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) => {
|
||||
layerOrder.moveUp(layerID);
|
||||
};
|
||||
|
@ -406,10 +485,6 @@ window.moveLayerDown = (layerID) => {
|
|||
layerOrder.moveDown(layerID);
|
||||
};
|
||||
|
||||
window.getArtboard = () => {
|
||||
return artboard;
|
||||
};
|
||||
|
||||
const addLayer = (autoInit = true) => {
|
||||
const layerID = Module.addNewLayer();
|
||||
const layer = new Layer(tp, layerID, fontsAndAxes, autoInit);
|
||||
|
@ -444,24 +519,21 @@ const duplicateLayer = (originalLayer) => {
|
|||
const givenKeys = e.detail.titles;
|
||||
let allKeysFound = true;
|
||||
for (let i = 0; i < originalKeys.length; i++) {
|
||||
//const originalValue = originalValues[originalKeys[i]];
|
||||
if (givenKeys.indexOf(originalKeys[i]) < 0) {
|
||||
//delete originalValues[originalKeys[i]];
|
||||
allKeysFound = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
if (allKeysFound) {
|
||||
tp.getPanel().removeEventListener("injected", addKeyframes);
|
||||
}
|
||||
tp.addKeyframes(newLayer, originalKeyframes).then(() => {
|
||||
if (allKeysFound) {
|
||||
resolve();
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
tp.getPanel().addEventListener("injected", addKeyframes);
|
||||
newLayer.select();
|
||||
//tp.shadowRoot.querySelector(`.layerMover${newLayer.id()} div`).click();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -477,9 +549,33 @@ const deleteLayer = (id, saveProject = true) => {
|
|||
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.splice(index, 1);
|
||||
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) {
|
||||
setTimeout(() => {
|
||||
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
|
||||
// because, come on. it may be funny for a second
|
||||
// but tolerance for fun is low when you're grumpy
|
||||
|
@ -511,10 +678,102 @@ window.duplicateLayer = (layer) => {
|
|||
window.addLayer = addLayer;
|
||||
window.addExistingLayer = addExistingLayer;
|
||||
window.deleteLayer = deleteLayer;
|
||||
window.addAudioLayer = addAudioLayer;
|
||||
window.addExistingAudioLayer = addExistingAudioLayer;
|
||||
window.deleteAudioLayer = deleteAudioLayer;
|
||||
window.addAudioLayerTrack = addAudioLayerTrack;
|
||||
window.renderFrames = exporter.renderFrames;
|
||||
|
||||
const layer_panel = document.querySelector('#layer_panel');
|
||||
|
||||
const initPanels = () => {
|
||||
//makeDraggable(layer_panel);
|
||||
const ui = (show) => {
|
||||
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) {
|
||||
FS.mkdir(config.fs.idbfsFontDir);
|
||||
}
|
||||
if (!FS.analyzePath(config.fs.idbfsAudioDir).exists) {
|
||||
FS.mkdir(config.fs.idbfsAudioDir);
|
||||
}
|
||||
if (!FS.analyzePath(config.fs.idbfsTmpDir).exists) {
|
||||
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
|
||||
this.save = (file) => {
|
||||
return new Promise((resolve) => {
|
||||
if (file.type.indexOf('font') >= 0 || file.hasOwnProperty('isFont') && file.isFont === true) {
|
||||
var uint8View = new Uint8Array(file.arrayBuffer);
|
||||
console.log('trying to save the font file, file, uint8View', file, uint8View);
|
||||
if (!FS.analyzePath(`${config.fs.idbfsFontDir}/${file.name}`).exists) {
|
||||
FS.createDataFile(config.fs.idbfsFontDir, file.name, uint8View, true, true);
|
||||
}
|
||||
|
@ -51,7 +70,6 @@ const ModuleFS = function() {
|
|||
} else if (file.type.indexOf('zip') >= 0 || file.hasOwnProperty('isZip') && file.isZip === true) {
|
||||
var uint8View = new Uint8Array(file.arrayBuffer);
|
||||
var filePath = `${config.fs.idbfsTmpDir}/${file.name}`;
|
||||
console.log(filePath);
|
||||
if (!FS.analyzePath(filePath).exists) {
|
||||
FS.createDataFile(config.fs.idbfsTmpDir, file.name, uint8View, true, true);
|
||||
}
|
||||
|
@ -59,6 +77,18 @@ const ModuleFS = function() {
|
|||
.then(() => {
|
||||
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 {
|
||||
resolve(false);
|
||||
}
|
||||
|
@ -81,6 +111,33 @@ const ModuleFS = function() {
|
|||
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 {
|
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,
|
||||
getParents,
|
||||
arraysEqual,
|
||||
sequencialPromises,
|
||||
getNestedProperty,
|
||||
} from './utils.js';
|
||||
|
||||
//import {
|
||||
//config
|
||||
//} from './config.js';
|
||||
|
||||
const TheatrePlay = function(autoInit = false) {
|
||||
|
||||
//private
|
||||
|
@ -25,10 +23,12 @@ const TheatrePlay = function(autoInit = false) {
|
|||
const theatreObjects = {};
|
||||
let theatrePanel = null;
|
||||
let sequencePanelLeft = null;
|
||||
const getSequencePanel = () => {
|
||||
sequencePanelLeft = tp.shadowRoot.querySelector('[data-testid="SequencePanel-Object"]');
|
||||
return sequencePanelLeft;
|
||||
};
|
||||
const getSequencePanelLeft = () => {
|
||||
// if (sequencePanelLeft === null) {
|
||||
sequencePanelLeft = tp.shadowRoot.querySelector('[data-testid="SequencePanel-Left"]');
|
||||
// }
|
||||
return sequencePanelLeft;
|
||||
};
|
||||
const getPanel = () => {
|
||||
|
@ -96,7 +96,7 @@ const TheatrePlay = function(autoInit = false) {
|
|||
if (typeof value === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
return this.sheet.sequence.__experimental_getKeyframes(prop);
|
||||
};
|
||||
// wtf, this function was being written in one go
|
||||
|
@ -134,88 +134,126 @@ const TheatrePlay = function(autoInit = false) {
|
|||
return keyframes;
|
||||
};
|
||||
const getSequenceButton = (path) => {
|
||||
let t = getPanelPropTitle(path.join('.'));
|
||||
let t = getPanelPropTitle(Array.isArray(path) ? path.join('.') : path);
|
||||
if (t === null) {
|
||||
return null;
|
||||
}
|
||||
return t.parentElement.querySelector('[title="Sequence this prop"]');
|
||||
};
|
||||
// no idea how to delete keyframes
|
||||
// so we can only add
|
||||
const isSequenced = (path, layer = getLayer()) => {
|
||||
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) => {
|
||||
return new Promise((resolve) => {
|
||||
const promises = [];
|
||||
keyframes.forEach((k) => {
|
||||
promises.push(() => {
|
||||
return new Promise((subResolve) => {
|
||||
let prop = layer.theatreObject.props;
|
||||
for (let i = 0; i < k.path.length; i++) {
|
||||
prop = prop[k.path[i]];
|
||||
}
|
||||
setSequenced(k.path, true, layer).then(() => {
|
||||
tp.studio.transaction(({
|
||||
__experimental_addKeyframes
|
||||
}) => {
|
||||
__experimental_addKeyframes(prop, k.keyframes);
|
||||
});
|
||||
subResolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
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 existingKeyframes = getKeyframes(layer);
|
||||
const promises = [];
|
||||
const ms = config.tp.addKeyframesTimeout_s * 1000;
|
||||
let waitify = false;
|
||||
keyframes.forEach((k) => {
|
||||
let prop = layer.theatreObject.props;
|
||||
for (let i = 0; i < k.path.length; i++) {
|
||||
prop = prop[k.path[i]];
|
||||
}
|
||||
const position = tp.sheet.sequence.position;
|
||||
// NOTE: can we sequence values without pretend clicking?
|
||||
const sequenceButton = getSequenceButton(k.path);
|
||||
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);
|
||||
subResolve();
|
||||
const propTitle = k.path.join('.');
|
||||
if (isSequenced(propTitle, layer)) {
|
||||
waitify = true;
|
||||
promises.push(() => {
|
||||
return new Promise((subResolve) => {
|
||||
setSequenced(propTitle, false, layer)
|
||||
.then(() => {
|
||||
subResolve();
|
||||
});
|
||||
}, ms * promises.length);
|
||||
}));
|
||||
}
|
||||
});
|
||||
promises.push(new Promise((subResolve) => {
|
||||
setTimeout(() => {
|
||||
tp.sheet.sequence.position = position;
|
||||
subResolve();
|
||||
}, ms * promises.length);
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
Promise.all(promises).then(() => {
|
||||
resolve();
|
||||
sequencialPromises(promises, () => {
|
||||
const timeout_ms = waitify ? 1000 : 0;
|
||||
setTimeout(() => {
|
||||
addKeyframes(layer, keyframes)
|
||||
.then(resolve);
|
||||
}, timeout_ms);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const friendlySequenceNames = () => {
|
||||
const sequencePanelLeft = tp.getSequencePanelLeft();
|
||||
let doItAgain = true;
|
||||
|
@ -246,12 +284,19 @@ const TheatrePlay = function(autoInit = false) {
|
|||
friendlySequenceNames();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// dirty, should be not in here:
|
||||
if (getAudioLayers().length > 0) {
|
||||
getAudioLayer().findInjectSequencePanel();
|
||||
}
|
||||
};
|
||||
|
||||
//public
|
||||
this.setSequenced = setSequenced;
|
||||
this.friendlySequenceNames = friendlySequenceNames;
|
||||
this.getKeyframes = getKeyframes;
|
||||
this.addKeyframes = addKeyframes;
|
||||
this.setKeyframes = setKeyframes;
|
||||
this.theatreObjects = theatreObjects;
|
||||
this.addObject = (name, props, onValuesChange) => {
|
||||
const obj = this.sheet.object(name, props);
|
||||
|
@ -283,6 +328,9 @@ const TheatrePlay = function(autoInit = false) {
|
|||
// remove object from objects list
|
||||
delete theatreObjects[name];
|
||||
};
|
||||
this.isSequenced = isSequenced;
|
||||
this.getSequenceButton = getSequenceButton;
|
||||
this.getSequencePanel = getSequencePanel;
|
||||
this.getSequencePanelLeft = getSequencePanelLeft;
|
||||
this.getPanel = getPanel;
|
||||
this.getPanelPropTitle = getPanelPropTitle;
|
||||
|
@ -308,269 +356,12 @@ const TheatrePlay = function(autoInit = false) {
|
|||
};
|
||||
this.addShadowCss = () => {
|
||||
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');
|
||||
|
||||
// ASYA: here you can adjust css
|
||||
style.textContent = `
|
||||
.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);
|
||||
style.setAttribute('rel', 'stylesheet');
|
||||
style.setAttribute('href', '/web/css/theatre.css');
|
||||
this.shadowRoot.insertBefore(style, this.shadowRoot.querySelector('#pointer-root'));
|
||||
}
|
||||
};
|
||||
const waitingForShadowRoot = (callback) => {
|
||||
|
@ -779,13 +570,9 @@ const TheatrePlay = function(autoInit = false) {
|
|||
console.log('TheatrePlay::uploadProject', 'could not verify project');
|
||||
}
|
||||
});
|
||||
//Module.importProjectAsZip(vt_project.arrayBuffer, vt_project.fileSize, "debug_tmp");
|
||||
} else if (verifyVariableTimeProject(vt_project)) {
|
||||
this.possiblyAskForFontsPromise(vt_project).then((vt_project_u) => {
|
||||
vt_project = vt_project_u;
|
||||
//if (reeeload) {
|
||||
//localStorage.clear();
|
||||
//}
|
||||
this.saveProject(vt_project.projectId, vt_project, true);
|
||||
if (reeeload) {
|
||||
this.reloadToProject(vt_project.projectId);
|
||||
|
@ -808,8 +595,32 @@ const TheatrePlay = function(autoInit = false) {
|
|||
layerOrder: window.layerOrder.get(),
|
||||
fontsHashMap,
|
||||
projectId,
|
||||
audioSavedMapping: audio.getSavedMapping(),
|
||||
audioMapping: audio.getMapping(),
|
||||
};
|
||||
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);
|
||||
};
|
||||
this.saveProject = (projectId = project.address.projectId, vt_project = false, silent = true) => {
|
||||
|
@ -834,12 +645,10 @@ const TheatrePlay = function(autoInit = false) {
|
|||
if (saveBeforeDownloading) {
|
||||
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));
|
||||
} else {
|
||||
const p = this.getProject(projectId);
|
||||
if (p !== false) {
|
||||
//downloadFile(p, `${config.projects.savePrefix}${projectId}.json`, 'application/json');
|
||||
Module.downloadProject(projectId, JSON.stringify(p));
|
||||
} else {
|
||||
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, {
|
||||
state: projectJson
|
||||
});
|
||||
//console.log({project, projectJson});
|
||||
window.setLoadingTask('setting up animation', 10);
|
||||
project.ready.then(() => {
|
||||
this.sheet = project.sheet('mainSheet');
|
||||
|
@ -916,26 +726,78 @@ const TheatrePlay = function(autoInit = false) {
|
|||
artboardValues['zoom'] = 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
|
||||
const defaultArtboardValues = window.getArtboard().theatreObject.value;
|
||||
const artboardProps = {...defaultArtboardValues, ...artboardValues};
|
||||
|
||||
window.setLoadingTask('setting up artboard', 90);
|
||||
studio.transaction(({
|
||||
set
|
||||
}) => {
|
||||
set(window.getArtboard().theatreObject.props, artboardProps);
|
||||
});
|
||||
if (project.hasOwnProperty('audioMapping')) {
|
||||
audio.setMapping(project.audioMapping);
|
||||
}
|
||||
if (project.hasOwnProperty('audioSavedMapping')) {
|
||||
audio.setSavedMapping(project.audioSavedMapping);
|
||||
}
|
||||
|
||||
// load layers
|
||||
const layerPromises = [];
|
||||
Object.keys(objects)
|
||||
.filter((e) => e.indexOf('layer-') === 0)
|
||||
.forEach((layerId) => {
|
||||
window.setLoadingTask(`setting up the shapes of ${layerId} to come`, 90);
|
||||
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);
|
||||
this.duration = this.core.val(this.sheet.sequence.pointer.length);
|
||||
if (project.layerOrder.length > 0) {
|
||||
|
@ -961,6 +823,9 @@ const TheatrePlay = function(autoInit = false) {
|
|||
});
|
||||
});
|
||||
} else {
|
||||
const layers = getLayers();
|
||||
if (layers.length > 0) {
|
||||
}
|
||||
this.duration = this.core.val(this.sheet.sequence.pointer.length);
|
||||
resolve();
|
||||
this.isProjectLoaded = true;
|
||||
|
@ -1007,9 +872,6 @@ const TheatrePlay = function(autoInit = false) {
|
|||
}
|
||||
this.connectModuleCallbacks = () => {
|
||||
console.log('TheatrePlay::connectModuleCallbacks');
|
||||
//onChange(sequence.pointer.length, (len) => {
|
||||
//console.log('Length of the sequence changed to:', len)
|
||||
//})
|
||||
|
||||
if (config.timeline.rolloverReset) {
|
||||
core.onChange(this.sheet.sequence.pointer.position, (position) => {
|
||||
|
@ -1023,6 +885,9 @@ const TheatrePlay = function(autoInit = false) {
|
|||
Module.setPlaying(playing);
|
||||
});
|
||||
};
|
||||
this.isPlaying = () => {
|
||||
return this.core.val(this.sheet.sequence.pointer.playing);
|
||||
};
|
||||
this.studio = studio;
|
||||
this.core = core;
|
||||
|
|
@ -34,6 +34,19 @@ const 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 nr = Math.round(n);
|
||||
return nr - nr % 2;
|
||||
|
@ -115,14 +128,12 @@ function uploadFile(expectedType = 'application/json') {
|
|||
if (files.length == 0) return;
|
||||
|
||||
const file = files[0];
|
||||
console.log('file', file);
|
||||
|
||||
let reader = new FileReader();
|
||||
|
||||
if (expectedType === 'application/zip' || file.type === 'application/zip') {
|
||||
if (expectedType === 'application/zip' || file.type === 'application/zip' || file.type.indexOf('audio') === 0) {
|
||||
reader.onload = (e) => {
|
||||
const f = e.target.result;
|
||||
console.log(e, file.name, file.size, file.type, f);
|
||||
resolve({
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
|
@ -136,7 +147,6 @@ function uploadFile(expectedType = 'application/json') {
|
|||
reader.readAsArrayBuffer(file);
|
||||
} else if (expectedType === 'application/json') {
|
||||
reader.onload = (e) => {
|
||||
console.log(e);
|
||||
const f = e.target.result;
|
||||
|
||||
// This is a regular expression to identify carriage
|
||||
|
@ -156,12 +166,9 @@ function uploadFile(expectedType = 'application/json') {
|
|||
|
||||
reader.readAsText(file);
|
||||
} else if (expectedType.indexOf('font') >= 0) {
|
||||
console.log('expect font');
|
||||
reader.onload = (e) => {
|
||||
console.log(e);
|
||||
const f = e.target.result;
|
||||
if (file.type.indexOf('font') >= 0) {
|
||||
console.log('is font');
|
||||
//var uint8View = new Uint8Array(f);
|
||||
//console.log('trying to save the font file, file, uint8View', file, uint8View);
|
||||
//FS.createDataFile(config.fs.idbfsFontDir, file.name, uint8View, true, true);
|
||||
|
@ -182,7 +189,6 @@ function uploadFile(expectedType = 'application/json') {
|
|||
type: file.type,
|
||||
arrayBuffer: f
|
||||
};
|
||||
console.log({outputFile});
|
||||
resolve(outputFile);
|
||||
} else {
|
||||
reject('not a font');
|
||||
|
@ -347,7 +353,7 @@ function verifyVariableTimeProject(vt_project) {
|
|||
|
||||
function clone(a) {
|
||||
return JSON.parse(JSON.stringify(a));
|
||||
};
|
||||
}
|
||||
|
||||
function getParents(elem, until = null) {
|
||||
const parents = [];
|
||||
|
@ -366,6 +372,12 @@ function getParents(elem, until = null) {
|
|||
return parents;
|
||||
}
|
||||
|
||||
function removeAllEventListeners(elem) {
|
||||
const tmp = elem.cloneNode(true);
|
||||
elem.parentNode.replaceChild(tmp, elem);
|
||||
elem = tmp;
|
||||
}
|
||||
|
||||
function arraysEqual(a, b, sortingMatters = false) {
|
||||
if (a === b) return true;
|
||||
if (a == null || b == null) return false;
|
||||
|
@ -381,11 +393,37 @@ function arraysEqual(a, b, sortingMatters = false) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// NOTE: will break if input is not number or rgba
|
||||
const mapValue = (value, low1, high1, low2, high2, clamp=false) => {
|
||||
const mapped = low2 + (high2 - low2) * (value - low1) / (high1 - low1);
|
||||
return clamp ? Math.min(high2 > low2 ? high2 : low2, Math.max(low2 < high2 ? low2 : high2, mapped)) : mapped;
|
||||
if (typeof low2 === 'number') {
|
||||
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;
|
||||
} 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 = () => {
|
||||
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
|
||||
return true;
|
||||
|
@ -395,10 +433,184 @@ const isMobile = () => {
|
|||
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 {
|
||||
getUuid,
|
||||
getTimestamp,
|
||||
htmlToElement,
|
||||
downloadFile,
|
||||
uploadFile,
|
||||
|
@ -412,7 +624,20 @@ export {
|
|||
mixObject,
|
||||
clone,
|
||||
getParents,
|
||||
removeAllEventListeners,
|
||||
arraysEqual,
|
||||
mapValue,
|
||||
smoothValue,
|
||||
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);
|
||||
}
|
||||
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);
|
||||
if (!cur.connectedRight)
|
||||
return;
|
||||
const next = keyframes[i3 + 1];
|
||||
if (!next)
|
||||
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 + next.handles[1] * diff);
|
||||
});
|
||||
|
@ -50492,7 +50494,7 @@ ${content}</tr>
|
|||
};
|
||||
}, []);
|
||||
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 toValueSpace = (ex) => extremums[0] + deltaToValueSpace(ex);
|
||||
const deltaToValueSpace = (ex) => ex * (extremums[1] - extremums[0]);
|
||||
|
@ -50517,7 +50519,7 @@ ${content}</tr>
|
|||
layoutP,
|
||||
sheetObject,
|
||||
trackId,
|
||||
isScalar: propConfig.type === "number",
|
||||
isScalar: propConfig.type === "number" || propConfig.type === "boolean",
|
||||
key: kf.id,
|
||||
extremumSpace: cachedExtremumSpace.current,
|
||||
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);
|
||||
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;
|
||||
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) {
|
||||
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;
|
||||
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) {
|
||||
const track = _getTrack(p3);
|
||||
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
|
||||
init_get();
|
||||
init_src();
|
||||
init_utils2();
|
||||
init_pointerDeep();
|
||||
init_forEachDeep();
|
||||
init_getDeep();
|
||||
init_instanceTypes();
|
||||
init_selectors();
|
||||
init_getStudio();
|
||||
|
@ -62560,6 +62655,60 @@ Note that it **is okay** to import '@theatre/core' multiple times. But those imp
|
|||
},
|
||||
__experimental_createContentOfSaveFileTyped(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);
|
||||
};
|
||||
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({
|
||||
set: set3,
|
||||
unset: unset2,
|
||||
__experimental_forgetObject,
|
||||
__experimental_forgetSheet
|
||||
__experimental_forgetSheet,
|
||||
__experimental_sequenceProp,
|
||||
__experimental_staticProp,
|
||||
__experimental_deleteKeyframes,
|
||||
__experimental_addKeyframes
|
||||
});
|
||||
});
|
||||
}
|
2
bin/web/js/jquery.min.js
vendored
|
@ -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
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
const scannerLine = document.getElementById("scannerLine");
|
||||
const scannerLineH = document.getElementById("scannerLineH");
|
||||
|
||||
$(document).ready(function() {
|
||||
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
|
||||
$("body").removeClass("hideBody");
|
||||
$("body").addClass("mobile");
|
||||
|
||||
}else if(('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0) && (testratio<0.7777777) ){
|
||||
$("body").removeClass("hideBody");
|
||||
$("body").addClass("mobile mobileOld");
|
||||
|
||||
}else{
|
||||
$("body").removeClass("hideBody");
|
||||
$("body").addClass("desktop")
|
||||
};
|
||||
|
||||
|
||||
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
img.src = "./media/360_F_163966311_qh3qSk57mw9oLPOklZigzX9zlB5DgdaM.jpeg";
|
||||
const canvas = document.getElementById("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const scanner = $(".scanner");
|
||||
|
||||
const destination = document.getElementById("letter");
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
img.addEventListener("load", () => {
|
||||
canvas.height = canvas.width * (img.height / img.width);
|
||||
scanner.css("width", $("#canvas").width());
|
||||
scanner.css("height", $("#canvas").height());
|
||||
var hRatio = canvas.width / img.width ;
|
||||
var vRatio = canvas.height / img.height ;
|
||||
var ratio = Math.min ( hRatio, vRatio );
|
||||
ctx.drawImage(img, 0,0, img.width, img.height, 0,0,img.width*ratio, img.height*ratio);
|
||||
// ctx.drawImage(img, 0, 0);
|
||||
img.style.display = "none";
|
||||
});
|
||||
const hoveredColor = document.getElementById("hovered-color");
|
||||
const selectedColor = document.getElementById("selected-color");
|
||||
|
||||
|
||||
|
||||
// let rect = scannelLine.getBoundingClientRect();
|
||||
function pick() {
|
||||
const bounding = canvas.getBoundingClientRect();
|
||||
|
||||
|
||||
const letters = document.getElementById("typeMaster");
|
||||
const letter = $(".letter");
|
||||
|
||||
letter.each(function(i,e){
|
||||
let rect = scannerLine.getBoundingClientRect();
|
||||
let rectH = scannerLineH.getBoundingClientRect();
|
||||
|
||||
const x = rect.left - bounding.left;
|
||||
const y = rectH.top - bounding.top;
|
||||
const pixel = ctx.getImageData(x - (i*2), y , 1, 1);
|
||||
const data = pixel.data;
|
||||
|
||||
let rgba = `rgba(${data[0]}, ${data[1]}, ${data[2]}, ${data[3] / 255})`;
|
||||
|
||||
hoveredColor.style.background = rgba;
|
||||
hoveredColor.textContent = rgba;
|
||||
// letters.style.color = rgba;
|
||||
|
||||
// $(".typeMaster").css("background",rgba);
|
||||
$(e).css("color",rgba);
|
||||
$(e).css("font-variation-settings","'wght' "+ ((data[0]) + (data[1]) + (data[2])));
|
||||
|
||||
return rgba;
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
const milliseconds = 100;
|
||||
window.setInterval(pick, milliseconds);
|
||||
|
||||
});
|
||||
|
||||
$(window).on("load", function(){
|
||||
|
||||
$("#typeMaster").html(function(index, html) {
|
||||
return html.replace(/\w+/g, '<span class="word">$&</span>');
|
||||
});
|
||||
$(".word").html(function(index, html) {
|
||||
return html.replace(/\S/g, '<span class="letter">$&</span>');
|
||||
})
|
||||
var length = $(".letter").length;
|
||||
var currentMousePos = { x: -1, y: -1 };
|
||||
|
||||
});
|
||||
|
||||
var slider = document.getElementById("myRange");
|
||||
var output = document.getElementById("demo");
|
||||
output.innerHTML = "duration X: " + slider.value; // Display the default slider value
|
||||
|
||||
var sliderY = document.getElementById("myRangeY");
|
||||
var outputY = document.getElementById("demoY");
|
||||
outputY.innerHTML = "duration Y: " + sliderY.value; // Display the default slider value
|
||||
|
||||
slider.oninput = function() {
|
||||
output.innerHTML = "duration X: " + this.value;
|
||||
|
||||
scannerLine.style.animationDuration = this.value + "s";
|
||||
}
|
||||
|
||||
sliderY.oninput = function() {
|
||||
outputY.innerHTML = "duration Y: " + this.value;
|
||||
scannerLineH.style.animationDuration = this.value + "s";
|
||||
}
|
||||
|
||||
|
15
browser.sh
|
@ -6,7 +6,18 @@ fly ()
|
|||
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 $@)
|
||||
#/usr/bin/chromium --incognito --screen=1 $@ &
|
||||
/usr/bin/chromium --screen=1 --enable-logging --password-store=basic --v=1 $@ &
|
||||
echo $!
|
||||
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 $@ &
|
||||
#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
|
||||
|
||||
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/libopenFrameworks.a
|
||||
rm -rf ../../../libs/openFrameworksCompiled/lib/emscripten/obj
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#!/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)
|
||||
|
||||
rm -rf bin/$project*
|
||||
rm -rf bin/em/$project/index.*
|
||||
rm -rf bin/data/ofxMsdfgen
|
||||
rm -rf bin/data/ofxGPUFont
|
||||
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
|
||||
|
||||
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):
|
||||
def end_headers(self):
|
||||
self.send_header("Cross-Origin-Opener-Policy", "same-origin")
|
||||
|
@ -34,40 +36,41 @@ def serve(root, port, run_browser):
|
|||
host = '0.0.0.0'
|
||||
server_address = (host, port)
|
||||
|
||||
# test(CORSRequestHandler, HTTPServer, port=port)
|
||||
httpd = HTTPServer(server_address, CORSRequestHandler)
|
||||
certfile = "ssl/localhost+4.pem"
|
||||
keyfile = "ssl/localhost+4-key.pem"
|
||||
|
||||
if not os.path.exists(certfile):
|
||||
print("using ssl/server.pm")
|
||||
certfile = "ssl/server.pem"
|
||||
if not os.path.exists(keyfile):
|
||||
print("using ssl/key.pm")
|
||||
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):
|
||||
httpd.socket = ssl.wrap_socket(httpd.socket,
|
||||
server_side=True,
|
||||
certfile=certfile,
|
||||
keyfile=keyfile,
|
||||
ssl_version=ssl.PROTOCOL_TLS)
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.load_cert_chain(certfile, keyfile)
|
||||
httpd.socket = context.wrap_socket(httpd.socket,
|
||||
server_side=True)
|
||||
|
||||
protocol = 'https'
|
||||
|
||||
if run_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).")
|
||||
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}")
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
# The main function
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-p", "--port", help="port to listen on", default=8060, type=int)
|
||||
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.add_argument(
|
||||
|
|
10
src/main.cpp
|
@ -137,6 +137,7 @@ shared_ptr <VariableEditor::ofApp> app;
|
|||
dir.listDir();
|
||||
|
||||
ofDirectory userFonts("/idbfs/fonts");
|
||||
ofDirectory userAudio("/idbfs/audio"); // possibly rename to media?
|
||||
|
||||
nlohmann::json json;
|
||||
json["files"] = nlohmann::json::array();
|
||||
|
@ -157,6 +158,15 @@ shared_ptr <VariableEditor::ofApp> app;
|
|||
if(!file.moveTo(userFonts)){
|
||||
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{
|
||||
file.remove();
|
||||
}
|
||||
|
|
|
@ -31,7 +31,9 @@ namespace VariableEditor {
|
|||
//--------------------------------------------------------------
|
||||
void ofApp::setup(){
|
||||
OFX_PROFILER_FUNCTION();
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 0)});
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 0)});
|
||||
#endif
|
||||
|
||||
{
|
||||
ofFile sf("appSettings.json");
|
||||
|
@ -58,7 +60,9 @@ void ofApp::setup(){
|
|||
10000000, // farDist
|
||||
glm::vec2(0, 0) // lensOffset
|
||||
);
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 10)});
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 10)});
|
||||
#endif
|
||||
|
||||
ofDisableArbTex();
|
||||
fboSettings.width = ofGetWidth() * AA;
|
||||
|
@ -79,25 +83,26 @@ void ofApp::setup(){
|
|||
|
||||
//fbo.allocate(ofGetWidth() * AA, ofGetHeight() * AA, GL_RGB);
|
||||
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 30)});
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 30)});
|
||||
#endif
|
||||
layerComposition.setup();
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 90)});
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 90)});
|
||||
#endif
|
||||
layerComposition.setVFlip(true);
|
||||
|
||||
#ifndef TARGET_OPENGLES
|
||||
#ifndef TARGET_EMSCRIPTEN
|
||||
{
|
||||
std::string fontPath = "data/fonts/Version-2-var.ttf";
|
||||
ofxVariableLab::LayerType type = ofxVariableLab::LayerType::GPUFONT;
|
||||
std::string fontPath = "data/fonts/Version-2.ttf";
|
||||
ofxVariableLab::Layer::Props props;
|
||||
props.fontPath = fontPath;
|
||||
props.text = "yo, whatever you want, and especially pancakes";
|
||||
props.y = 120;
|
||||
props.x = 95;
|
||||
layerComposition.addLayer(
|
||||
{fontPath, type},
|
||||
props,
|
||||
{{"Weight", 100.0}, {"Weight", 700.0}}
|
||||
);
|
||||
if(ofFile(fontPath).exists()){
|
||||
layerComposition.addLayer(props, "layer-0");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -130,7 +135,9 @@ void ofApp::setup(){
|
|||
<< " | Minor(" << ofToString(glMajor) << ")"
|
||||
<< endl;
|
||||
artboard.setup();
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 100)});
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 100)});
|
||||
#endif
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
|
|
86
src/ofApp.h
|
@ -5,6 +5,7 @@
|
|||
#include "conversion.h"
|
||||
#include "Artboard.h"
|
||||
#include "ofEasyCam.h"
|
||||
#include "ofFileUtils.h"
|
||||
#include "ofMain.h"
|
||||
#include "ofQuaternion.h"
|
||||
#include "ofTrueTypeFont.h"
|
||||
|
@ -34,8 +35,8 @@ struct AppSettings {
|
|||
212 / 255.0,
|
||||
212 / 255.0,
|
||||
1}; // check data/appSettings.json
|
||||
string tmpExportDir = "data/export";
|
||||
string tmpImportDir = "data/import";
|
||||
std::string tmpExportDir = "data/export";
|
||||
std::string tmpImportDir = "data/import";
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AppSettings,
|
||||
backgroundColor,
|
||||
|
@ -43,23 +44,29 @@ struct AppSettings {
|
|||
|
||||
};
|
||||
|
||||
class ZipProjectSaver : public ofThread {
|
||||
class ZipProjectSaver :
|
||||
public ofThread {
|
||||
public:
|
||||
void setup(string projectName,
|
||||
string projectJsonString){
|
||||
this->projectName = projectName;
|
||||
this->projectJsonString = projectJsonString;
|
||||
this->userFontsPath = "/idbfs/fonts";
|
||||
this->userAudioPath = "/idbfs/audio";
|
||||
this->timestamp = ofGetTimestampString();
|
||||
this->filename = projectName + "_project_" + timestamp + ".zip";
|
||||
}
|
||||
|
||||
void update(){
|
||||
if(freshDownload.load()){
|
||||
emscripten_browser_file::download(filename.c_str(),
|
||||
"application/zip",
|
||||
buffer,
|
||||
buffer_size);
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
emscripten_browser_file::download(filename.c_str(),
|
||||
"application/zip",
|
||||
buffer,
|
||||
buffer_size);
|
||||
#else
|
||||
ofBufferToFile(filename.c_str(), ofBuffer(buffer, buffer_size));
|
||||
#endif
|
||||
freshDownload.store(false);
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +100,28 @@ class ZipProjectSaver : public ofThread {
|
|||
ofBuffer buffy = file.readToBuffer();
|
||||
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_size = 0;
|
||||
zip.getOutputBuffer(&buffer, buffer_size);
|
||||
|
@ -104,11 +133,12 @@ class ZipProjectSaver : public ofThread {
|
|||
void exit(){
|
||||
free(buffer);
|
||||
}
|
||||
string projectName;
|
||||
string projectJsonString;
|
||||
string userFontsPath;
|
||||
string timestamp;
|
||||
string filename;
|
||||
std::string projectName;
|
||||
std::string projectJsonString;
|
||||
std::string userFontsPath;
|
||||
std::string userAudioPath;
|
||||
std::string timestamp;
|
||||
std::string filename;
|
||||
char * buffer;
|
||||
size_t buffer_size;
|
||||
std::atomic <bool> freshDownload{false};
|
||||
|
@ -130,7 +160,8 @@ class ZipSaver : public ofThread {
|
|||
|
||||
void update(){
|
||||
if(freshDownload.load()){
|
||||
EM_ASM({
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
EM_ASM({
|
||||
document.getElementById('export_progress_task').innerHTML = 'rendering';
|
||||
let innerHTML = "|";
|
||||
for(let i = 0; i < 100; i++){
|
||||
|
@ -142,10 +173,13 @@ class ZipSaver : public ofThread {
|
|||
let progress_task = document.getElementById("export_progress_task");
|
||||
progress_task.innerHTML = "idle";
|
||||
});
|
||||
emscripten_browser_file::download(filename.c_str(),
|
||||
"application/zip",
|
||||
buffer,
|
||||
buffer_size);
|
||||
emscripten_browser_file::download(filename.c_str(),
|
||||
"application/zip",
|
||||
buffer,
|
||||
buffer_size);
|
||||
#else
|
||||
ofBufferToFile(filename.c_str(), ofBuffer(buffer, buffer_size));
|
||||
#endif
|
||||
freshDownload.store(false);
|
||||
}else if(isThreadRunning()){
|
||||
setProgress(percent.load());
|
||||
|
@ -153,7 +187,8 @@ class ZipSaver : public ofThread {
|
|||
}
|
||||
|
||||
void setProgress(int percent){
|
||||
EM_ASM_INT({
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
EM_ASM_INT({
|
||||
let percent = $0;
|
||||
document.getElementById('export_progress_task').innerHTML = 'rendering';
|
||||
let innerHTML = "|";
|
||||
|
@ -171,6 +206,9 @@ class ZipSaver : public ofThread {
|
|||
let progress_task = document.getElementById("export_progress_task");
|
||||
progress_task.innerHTML = "creating zip file";
|
||||
}, percent);
|
||||
#else
|
||||
std::cout << "progress: " << percent << "/100" << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
void threadedFunction(){
|
||||
|
@ -188,9 +226,9 @@ class ZipSaver : public ofThread {
|
|||
ofImage image;
|
||||
image.setUseTexture(false);
|
||||
image.load(filepath);
|
||||
ofBuffer buffer;
|
||||
ofSaveImage(image.getPixels(), buffer, OF_IMAGE_FORMAT_PNG);
|
||||
zip.addBuffer(f + ".png", buffer.getData(), buffer.size());
|
||||
ofBuffer b;
|
||||
ofSaveImage(image.getPixels(), b, OF_IMAGE_FORMAT_PNG);
|
||||
zip.addBuffer(f + ".png", b.getData(), b.size());
|
||||
percent.store((float(i) / float(total)) * 100.0f);
|
||||
i++;
|
||||
}
|
||||
|
@ -232,7 +270,8 @@ class ZipUnpacker : public ofThread {
|
|||
}
|
||||
|
||||
void setProgress(int percent){
|
||||
EM_ASM_INT({
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
EM_ASM_INT({
|
||||
let percent = $0;
|
||||
document.getElementById('export_progress_task').innerHTML = 'uploading and unpacking';
|
||||
let innerHTML = "|";
|
||||
|
@ -250,6 +289,9 @@ class ZipUnpacker : public ofThread {
|
|||
let progress_task = document.getElementById("import_progress_task");
|
||||
progress_task.innerHTML = "creating zip file";
|
||||
}, percent);
|
||||
#else
|
||||
std::cout << "progress: " << percent << "/100" << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
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
|