1345 lines
50 KiB
JavaScript
1345 lines
50 KiB
JavaScript
import '../theatre_modules/core-and-studio.js'
|
|
import {
|
|
downloadFile,
|
|
uploadFile,
|
|
verifyVariableTimeProject,
|
|
hashFromString,
|
|
clone,
|
|
getParents,
|
|
arraysEqual,
|
|
sequencialPromises,
|
|
} from './utils.js';
|
|
|
|
//import {
|
|
//config
|
|
//} from './config.js';
|
|
|
|
const TheatrePlay = function(autoInit = false) {
|
|
|
|
//private
|
|
const {
|
|
core,
|
|
studio
|
|
} = Theatre;
|
|
|
|
let project = false;
|
|
const theatreObjects = {};
|
|
let theatrePanel = null;
|
|
let sequencePanelLeft = null;
|
|
const getSequencePanelLeft = () => {
|
|
// if (sequencePanelLeft === null) {
|
|
sequencePanelLeft = tp.shadowRoot.querySelector('[data-testid="SequencePanel-Left"]');
|
|
// }
|
|
return sequencePanelLeft;
|
|
};
|
|
const getPanel = () => {
|
|
if (theatrePanel === null) {
|
|
theatrePanel = tp.shadowRoot.querySelector('[data-testid="DetailPanel-Object"]');
|
|
}
|
|
return theatrePanel;
|
|
};
|
|
const setPanelClasses = () => {
|
|
const parents = getParents(getPanel().querySelector('[title^="obj.props."]'), getPanel())
|
|
const panelControlsWrapper = parents[parents.length - 3];
|
|
const panelWrapper = parents[parents.length - 2];
|
|
const panelMomWrapper = parents[parents.length - 1];
|
|
panelControlsWrapper.classList.add("panelControlsWrapper");
|
|
panelWrapper.classList.add("panelWrapper");
|
|
panelMomWrapper.classList.add("panelMomWrapper");
|
|
getPanel().classList.add('panel');
|
|
getPanel().setAttribute('id', 'panel');
|
|
};
|
|
const getPanelPropTitle = (propTitle) => {
|
|
const panel = getPanel();
|
|
let panelPropTitle = panel.querySelector(`[title="obj.props.${propTitle}"]`);
|
|
if (panelPropTitle !== null) {
|
|
return panelPropTitle;
|
|
}
|
|
panelPropTitle = panel.querySelector(`[title^="obj.props.${propTitle}"]`);
|
|
if (panelPropTitle !== null) {
|
|
return panelPropTitle.parentElement.parentElement;
|
|
}
|
|
return null;
|
|
}
|
|
const getPanelPropContainer = (panelPropTitle) => {
|
|
if (typeof panelPropTitle === 'string') {
|
|
panelPropTitle = getPanelPropTitle(panelPropTitle);
|
|
}
|
|
if (panelPropTitle === null) {
|
|
return null;
|
|
}
|
|
const panel = getPanel();
|
|
let panelControls = panel.querySelector('.panelControlsWrapper');
|
|
if (panelControls === null) {
|
|
setPanelClasses();
|
|
}
|
|
panelControls = panel.querySelector('.panelControlsWrapper');
|
|
if (panelControls === null) {
|
|
return null;
|
|
}
|
|
const parents = getParents(panelPropTitle, panelControls);
|
|
if (parents === null || parents.length < 1) {
|
|
return null;
|
|
}
|
|
|
|
return parents[parents.length - 1];
|
|
};
|
|
const getPropKeyframes = (layer, propPath) => {
|
|
if (!Array.isArray(propPath) || propPath.length <= 0) {
|
|
return false;
|
|
}
|
|
let prop = layer.theatreObject.props;
|
|
let value = layer.theatreObject.value;
|
|
for (let i = 0; i < propPath.length; i++) {
|
|
const p = propPath[i];
|
|
prop = prop[p];
|
|
value = value[p];
|
|
if (typeof value === 'undefined') {
|
|
return false;
|
|
}
|
|
}
|
|
return this.sheet.sequence.__experimental_getKeyframes(prop);
|
|
};
|
|
// wtf, this function was being written in one go
|
|
// without any errors. huh?
|
|
const getKeyframes = (layer, valuePath = []) => {
|
|
const keyframes = [];
|
|
const theatreValues = layer.theatreObject.value;
|
|
let value = theatreValues;
|
|
for (let i = 0; i < valuePath.length; i++) {
|
|
value = value[valuePath[i]];
|
|
if (typeof value === 'undefined') {
|
|
return false;
|
|
}
|
|
}
|
|
const isColor = valuePath.length > 0 &&
|
|
typeof value === 'object' &&
|
|
valuePath[valuePath.length - 1] === 'color';
|
|
|
|
if (typeof value === 'object' && !isColor) {
|
|
Object.keys(value).forEach((key) => {
|
|
const newValuePath = [...valuePath, ...[key]];
|
|
const subkeyframes = getKeyframes(layer, newValuePath);
|
|
if (subkeyframes !== false && subkeyframes.length > 0) {
|
|
for (let i = 0; i < subkeyframes.length; i++) {
|
|
keyframes.push(subkeyframes[i]);
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
const propKeyframes = getPropKeyframes(layer, valuePath);
|
|
if (propKeyframes.length > 0) {
|
|
keyframes.push({path: valuePath, keyframes: propKeyframes});
|
|
}
|
|
}
|
|
return keyframes;
|
|
};
|
|
const getSequenceButton = (path) => {
|
|
let t = getPanelPropTitle(Array.isArray(path) ? path.join('.') : path);
|
|
if (t === null) {
|
|
return null;
|
|
}
|
|
return t.parentElement.querySelector('[title="Sequence this prop"]');
|
|
};
|
|
const isSequenced = (path) => {
|
|
return getSequenceButton(path) === null;
|
|
};
|
|
|
|
const setSequenced = (propTitle, sequenced, metaResolve = false) => {
|
|
const f = (resolve) => {
|
|
const propIsSequenced = isSequenced(propTitle);
|
|
const somethingToDo = sequenced !== propIsSequenced;
|
|
|
|
if (somethingToDo) {
|
|
const contextItem = sequenced ? 'sequence' : 'make static';
|
|
const antiContextItem = sequenced ? 'make static' : 'sequence';
|
|
|
|
const finishedSequencedEvent = (e) => {
|
|
// only care about events from our prop
|
|
if (propTitle === e.detail.prop.join('.')) {
|
|
// if we un-sequence, we listen to stateEditors' event
|
|
if (!sequenced && e.detail.origin === 'stateEditors.ts' && e.detail.sequenced === sequenced) {
|
|
window.removeEventListener('sequenceEvent', finishedSequencedEvent);
|
|
resolve(true);
|
|
|
|
// if we sequence, then we wait until the track is there
|
|
} else if (sequenced && e.detail.origin === 'BasicKeyframedTrack.tsx' && e.detail.sequenced === sequenced) {
|
|
window.removeEventListener('sequenceEvent', finishedSequencedEvent);
|
|
resolve(true);
|
|
} else {
|
|
// pretty verbose
|
|
//console.log('TheatrePlayu::setSequenced', 'ignored event', e, e.detail);
|
|
}
|
|
}
|
|
};
|
|
|
|
let counter = 0;
|
|
const clickContextMenu = (e) => {
|
|
let done = false;
|
|
if (e.target !== null) {
|
|
e.target.removeEventListener('contextmenu', clickContextMenu);
|
|
}
|
|
tp.shadowRoot.querySelectorAll('ul li span').forEach((s) => {
|
|
if (s.innerHTML.toLowerCase() === contextItem.toLowerCase()) {
|
|
window.addEventListener('sequenceEvent', finishedSequencedEvent);
|
|
s.click();
|
|
done = true;
|
|
} else if (s.innerHTML.toLowerCase() === antiContextItem.toLowerCase()) {
|
|
done = true;
|
|
resolve(false);
|
|
}
|
|
});
|
|
if (!done) {
|
|
setTimeout(() => {
|
|
if (counter < 4) {
|
|
clickContextMenu(e);
|
|
counter++;
|
|
} else {
|
|
setSequenced(propTitle, sequenced, resolve);
|
|
}
|
|
}, 100);
|
|
}
|
|
};
|
|
|
|
getPanelPropTitle(propTitle).addEventListener('contextmenu', clickContextMenu);
|
|
getPanelPropTitle(propTitle).dispatchEvent(new Event('contextmenu'));
|
|
} else {
|
|
resolve();
|
|
}
|
|
};
|
|
if (!metaResolve) {
|
|
return new Promise((resolve) => {
|
|
f(resolve);
|
|
});
|
|
} else {
|
|
f(metaResolve);
|
|
}
|
|
};
|
|
|
|
const addKeyframes = (layer, keyframes) => {
|
|
return new Promise((resolve) => {
|
|
if (!Array.isArray(keyframes)) {
|
|
resolve(false);
|
|
return false;
|
|
}
|
|
const existingKeyframes = getKeyframes(layer);
|
|
const promises = [];
|
|
const ms = 0; //config.tp.addKeyframesTimeout_s * 1000;
|
|
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;
|
|
promises.push(() => {
|
|
return new Promise((subResolve) => {
|
|
setTimeout(() => {
|
|
if (layer.isSelected()) {
|
|
setSequenced(k.path.join('.'), true)
|
|
.then(() => {
|
|
subResolve();
|
|
});
|
|
} else {
|
|
// we cannot select layers without pseudoclicking
|
|
// so let's wait for a happy 'injected' event that
|
|
// closes off the selection
|
|
//
|
|
// first, the listener callback
|
|
const f = () => {
|
|
tp.getPanel().removeEventListener('injected', f);
|
|
setSequenced(k.path.join('.'), true)
|
|
.then(() => {
|
|
subResolve();
|
|
});
|
|
};
|
|
// then add it
|
|
tp.getPanel().addEventListener('injected', f);
|
|
// and fire the click
|
|
layer.select();
|
|
}
|
|
}, ms); // * promises.length);
|
|
})
|
|
});
|
|
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(() => {
|
|
return new Promise((subResolve) => {
|
|
setTimeout(() => {
|
|
tp.sheet.sequence.position = keyframe.position;
|
|
this.studio.transaction(({
|
|
set
|
|
}) => {
|
|
set(prop, keyframe.value);
|
|
subResolve();
|
|
});
|
|
}, ms); // * promises.length);
|
|
})
|
|
});
|
|
}
|
|
});
|
|
promises.push(() => {
|
|
return new Promise((subResolve) => {
|
|
setTimeout(() => {
|
|
tp.sheet.sequence.position = position;
|
|
subResolve();
|
|
}, ms); // * promises.length);
|
|
})
|
|
});
|
|
});
|
|
sequencialPromises(promises, resolve);
|
|
//Promise.all(promises).then(() => {
|
|
//resolve();
|
|
//});
|
|
});
|
|
};
|
|
const setKeyframes = (layer, keyframes) => {
|
|
return new Promise((resolve) => {
|
|
if (!Array.isArray(keyframes)) {
|
|
resolve(false);
|
|
return false;
|
|
}
|
|
const promises = [];
|
|
let waitify = false;
|
|
keyframes.forEach((k) => {
|
|
const propTitle = k.path.join('.');
|
|
if (isSequenced(propTitle)) {
|
|
waitify = true;
|
|
promises.push(() => {
|
|
return new Promise((subResolve) => {
|
|
setSequenced(propTitle, false)
|
|
.then(() => {
|
|
subResolve();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
});
|
|
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;
|
|
if (sequencePanelLeft !== null) {
|
|
const titles = sequencePanelLeft.querySelectorAll('[data-testid="SequencePanel-Title"]');
|
|
const propTitles = sequencePanelLeft.querySelectorAll('[data-testid="SequencePanel-PropTitle"]');
|
|
const allTitles = [...[...titles], ...[...propTitles]];
|
|
if (allTitles.length > 0) {
|
|
allTitles.forEach((title) => {
|
|
// NOTE: should we have the same artboard and layer friendlyName,
|
|
// the layer friendlyName will be picked
|
|
Object.keys(config.layer.friendlyNames).forEach((key) => {
|
|
if (title.innerHTML === key) {
|
|
title.innerHTML = config.layer.friendlyNames[key];
|
|
}
|
|
});
|
|
Object.keys(config.artboard.friendlyNames).forEach((key) => {
|
|
if (title.innerHTML === key) {
|
|
title.innerHTML = config.artboard.friendlyNames[key];
|
|
}
|
|
});
|
|
});
|
|
}
|
|
doItAgain = false;
|
|
}
|
|
if (doItAgain) {
|
|
setTimeout(() => {
|
|
friendlySequenceNames();
|
|
}, 1000);
|
|
}
|
|
};
|
|
|
|
//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);
|
|
const listener = obj.onValuesChange(onValuesChange);
|
|
theatreObjects[name] = {
|
|
obj,
|
|
listener
|
|
};
|
|
return obj;
|
|
};
|
|
this.changeObject = (name, props) => {
|
|
this.sheet.object(name, props, {
|
|
reconfigure: true
|
|
});
|
|
};
|
|
this.removeObject = (name) => {
|
|
// detach listener
|
|
// yeah, it looks weird, but this is how it is described
|
|
// https://www.theatrejs.com/docs/latest/manual/objects#detaching-objects
|
|
theatreObjects[name].listener();
|
|
// make sure object is DELETED (or is it?)
|
|
studio.transaction(({unset}) => unset(theatreObjects[name].obj.props))
|
|
// detach object
|
|
this.sheet.detachObject(name);
|
|
// make sure object is DELETED (possibly is)
|
|
tp.studio.transaction((api) => {
|
|
api.__experimental_forgetObject(theatreObjects[name].obj);
|
|
});
|
|
// remove object from objects list
|
|
delete theatreObjects[name];
|
|
};
|
|
this.isSequenced = isSequenced;
|
|
this.getSequenceButton = getSequenceButton;
|
|
this.getSequencePanelLeft = getSequencePanelLeft;
|
|
this.getPanel = getPanel;
|
|
this.getPanelPropTitle = getPanelPropTitle;
|
|
this.getPanelPropContainer = getPanelPropContainer;
|
|
this.setPanelClasses = setPanelClasses;
|
|
this.findInjectPanels = () => {
|
|
const id = project.address.projectId;
|
|
{ // hierarchy panel
|
|
const panel = tp.shadowRoot.querySelector(`.layerMover${id}`);
|
|
if (panel !== null) {
|
|
if (panel.querySelector('addLayerButton') !== null) {
|
|
panel.querySelector('addLayerButton').remove();
|
|
}
|
|
const addLayerButton = document.createElement('div');
|
|
addLayerButton.classList.add('addLayerButton');
|
|
addLayerButton.innerHTML = `<img src="/web/assets/add_layer.svg" />`;
|
|
addLayerButton.addEventListener('click', (clickEvent) => {
|
|
window.addLayer();
|
|
});
|
|
panel.append(addLayerButton);
|
|
}
|
|
}
|
|
};
|
|
this.addShadowCss = () => {
|
|
if (this.shadowRoot.querySelector(`#tpShadowCss`) === null) {
|
|
let style = document.createElement("style");
|
|
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;
|
|
}
|
|
.audio_min_max{
|
|
padding: 5px;
|
|
box-sizing: border-box;
|
|
display: grid;
|
|
/* flex-direction: column; */
|
|
grid-template-columns: 1fr 1fr;
|
|
row-gap: 7px;
|
|
}
|
|
.audio_min_Cont, .audio_max_Cont{
|
|
grid-column-start: 1;
|
|
grid-column-end: 3;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
|
|
|
|
}
|
|
.audio_min_max 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;
|
|
}
|
|
.audioOptions{
|
|
margin-top: 5px;
|
|
margin-bottom: 5px;
|
|
}
|
|
.audioOptions label {
|
|
color: black;
|
|
font-family: 'Tonka';
|
|
font-variation-settings: 'wght' 500;
|
|
}
|
|
.mirror_xWrapper label, .mirror_yWrapper label, .mirror_xyWrapper label{
|
|
color:#ea2333;
|
|
}
|
|
// .mirror_xWrapper div:first-of-type, .mirror_yWrapper div:first-of-type, .mirror_xyWrapper div:first-of-type{
|
|
// width: fit-content;
|
|
// background: red;
|
|
// flex-grow: 0;
|
|
// margin-right: 15px;
|
|
// }
|
|
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';
|
|
}
|
|
.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 15px 5px 0px;
|
|
}
|
|
.sync_Dom{
|
|
padding: 5px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
row-gap: 7px;
|
|
}
|
|
.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-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;
|
|
}
|
|
.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{
|
|
background: #1cba94;
|
|
}
|
|
.audioButton img{
|
|
max-width: 70%;
|
|
}
|
|
.audioButton.active{
|
|
background: #1cba94;
|
|
}
|
|
.recordButton{
|
|
width: 17px;
|
|
margin: 2px;
|
|
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
|
|
}
|
|
.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;
|
|
}
|
|
.recordButton:hover{
|
|
background: #1cba94;
|
|
}
|
|
.recordButton img{
|
|
max-width: 70%;
|
|
}
|
|
.recordButton.active{
|
|
background: #1cba94;
|
|
}
|
|
.panelMomWrapper{
|
|
overflow-x: hidden;
|
|
}
|
|
`;
|
|
this.shadowRoot.appendChild(style);
|
|
}
|
|
};
|
|
const waitingForShadowRoot = (callback) => {
|
|
let studioRoot = document.getElementById('theatrejs-studio-root');
|
|
if (studioRoot !== null && studioRoot.shadowRoot !== null) {
|
|
callback();
|
|
} else {
|
|
setTimeout(() => {
|
|
waitingForShadowRoot(callback);
|
|
}, 10);
|
|
}
|
|
};
|
|
this.addUserFont = (previousName = "", filePath = "") => {
|
|
return new Promise((resolve) => {
|
|
const fileName = filePath.split("/").reverse().shift(); // basename
|
|
let uploadFontButton = document.createElement('div');
|
|
let uploadFontButtonContainer = document.createElement('div');
|
|
uploadFontButton.classList.add('upload_font_button');
|
|
uploadFontButtonContainer.classList.add('upload_font_button_container');
|
|
uploadFontButton.innerHTML = "upload font " +
|
|
(previousName === "" ? "" : `(${previousName})`);
|
|
uploadFontButton.style.cursor = 'pointer';
|
|
uploadFontButtonContainer.append(uploadFontButton);
|
|
document.getElementById('body').append(uploadFontButtonContainer);
|
|
uploadFontButton.addEventListener('click', () => {
|
|
uploadFontButtonContainer.remove();
|
|
uploadFile('font')
|
|
.then((fontFile) => {
|
|
if (fileName !== "") {
|
|
fontFile.name = fileName;
|
|
}
|
|
moduleFS
|
|
.save(fontFile)
|
|
.then(() => {
|
|
resolve(fontFile);
|
|
})
|
|
});
|
|
});
|
|
});
|
|
};
|
|
this.possiblyAskForFontsPromise = (vt_project) => {
|
|
return new Promise((resolve) => {
|
|
if (vt_project.fontsHashMap !== false) {
|
|
this.possiblyAskForFonts(vt_project, resolve);
|
|
} else {
|
|
resolve(vt_project);
|
|
}
|
|
});
|
|
};
|
|
this.projectUsesFont = (vt_project, hash) => {
|
|
const layerKeys = Object.keys(vt_project
|
|
.theatre
|
|
.sheetsById
|
|
.mainSheet
|
|
.staticOverrides
|
|
.byObject);
|
|
for (let l = 0; l < layerKeys.length; l++) {
|
|
if (vt_project
|
|
.theatre
|
|
.sheetsById
|
|
.mainSheet
|
|
.staticOverrides
|
|
.byObject[layerKeys[l]].fontFamily === hash) {
|
|
return true;
|
|
} else {
|
|
//console.log(hash, "not used by ", vt_project);
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
this.possiblyAskForFonts = (vt_project, resolve, ignoreThese = []) => {
|
|
const availables = listAvailableFontsAndAxes();
|
|
const hashes = Object.keys(vt_project.fontsHashMap);
|
|
let isRecursive = false;
|
|
for (let h = 0; h < hashes.length; h++) {
|
|
const hash = hashes[h];
|
|
if (ignoreThese.indexOf(hash) >= 0){
|
|
continue;
|
|
}
|
|
const fontInfo = vt_project.fontsHashMap[hash];
|
|
let found = false;
|
|
for (let i = 0; i < availables.length; i++) {
|
|
if (availables[i].fontPath === fontInfo.fontPath) {
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found) {
|
|
const used = this.projectUsesFont(vt_project, hash);
|
|
let satisfied = false;
|
|
for (let a = 0; a < availables.length; a++) {
|
|
if (fontInfo.fontName === availables[a].fontName) {
|
|
satisfied = true;
|
|
const uploadedPath = availables[a].fontPath;
|
|
const uploadedHash = hashFromString(uploadedPath);
|
|
delete vt_project.fontsHashMap[hash]; // will be generated? or maybe not
|
|
vt_project.fontsHashMap[uploadedHash] = {
|
|
fontName: availables[a].fontName,
|
|
fontPath: availables[a].fontPath,
|
|
axes: availables[a].axes,
|
|
};
|
|
const layerKeys = Object.keys(vt_project
|
|
.theatre
|
|
.sheetsById
|
|
.mainSheet
|
|
.staticOverrides
|
|
.byObject);
|
|
for (let l = 0; l < layerKeys.length; l++) {
|
|
if (vt_project
|
|
.theatre
|
|
.sheetsById
|
|
.mainSheet
|
|
.staticOverrides
|
|
.byObject[layerKeys[l]].fontFamily === hash) {
|
|
vt_project
|
|
.theatre
|
|
.sheetsById
|
|
.mainSheet
|
|
.staticOverrides
|
|
.byObject[layerKeys[l]].fontFamily = uploadedHash;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!satisfied && used && confirm(`font ${fontInfo.fontName} not found.\n` +
|
|
`do you want to upload it (or a substitute) now?\n` +
|
|
`Otherwise it will be later substituted.\n\n` +
|
|
`If you click okay, click on the upcoming button`)) {
|
|
isRecursive = true;
|
|
this.addUserFont(fontInfo.fontName).then((uploadedFile) => {
|
|
const uploadedPath = `${config.fs.idbfsFontDir}/${uploadedFile.name}`;
|
|
const uploadedHash = hashFromString(uploadedPath);
|
|
delete vt_project.fontsHashMap[hash]; // will be generated?
|
|
const layerKeys = Object.keys(vt_project
|
|
.theatre
|
|
.sheetsById
|
|
.mainSheet
|
|
.staticOverrides
|
|
.byObject);
|
|
for (let l = 0; l < layerKeys.length; l++) {
|
|
if (vt_project
|
|
.theatre
|
|
.sheetsById
|
|
.mainSheet
|
|
.staticOverrides
|
|
.byObject[layerKeys[l]].fontFamily === hash) {
|
|
vt_project
|
|
.theatre
|
|
.sheetsById
|
|
.mainSheet
|
|
.staticOverrides
|
|
.byObject[layerKeys[l]].fontFamily = uploadedHash;
|
|
}
|
|
}
|
|
this.possiblyAskForFonts(vt_project, resolve, ignoreThese);
|
|
});
|
|
break;
|
|
} else if (satisfied) {
|
|
isRecursive = true;
|
|
this.possiblyAskForFonts(vt_project, resolve, ignoreThese);
|
|
} else {
|
|
ignoreThese.push(hash);
|
|
}
|
|
}
|
|
}
|
|
if (!isRecursive) {
|
|
resolve(vt_project);
|
|
}
|
|
};
|
|
this.variableTime2theatre = (vt_project) => {
|
|
return vt_project.theatre;
|
|
};
|
|
this.theatre2variableTime = (theatre_project, vt_params) => {
|
|
vt_params.theatre = theatre_project;
|
|
vt_params.theatre.revisionHistory = [];
|
|
return vt_params;
|
|
};
|
|
this.listProjects = () => {
|
|
const projectIds = [];
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
let key = localStorage.key(i);
|
|
if (key.indexOf(config.projects.savePrefix) === 0) {
|
|
const projectId = key.replace(config.projects.savePrefix, '');
|
|
projectIds.push(projectId);
|
|
}
|
|
}
|
|
return projectIds;
|
|
};
|
|
this.uploadProject = (reeeload = false) => {
|
|
uploadFile().then((vt_project) => {
|
|
if (vt_project.type === 'application/zip') {
|
|
moduleFS.save(vt_project).then((fileLocation) => {
|
|
const jsonString = Module.importProjectAsZip(fileLocation, `${config.fs.idbfsTmpDir}/outdir`);
|
|
const json = JSON.parse(jsonString);
|
|
if (verifyVariableTimeProject(json.project)) {
|
|
this.possiblyAskForFontsPromise(json.project).then((vt_project_u) => {
|
|
vt_project = vt_project_u;
|
|
window.debug_vt_project = vt_project;
|
|
config.autoSave = false;
|
|
this.saveProject(vt_project.projectId, vt_project, true);
|
|
if (reeeload) {
|
|
this.reloadToProject(vt_project.projectId);
|
|
}
|
|
});
|
|
} else {
|
|
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);
|
|
}
|
|
});
|
|
} else {
|
|
console.log('TheatrePlay::uploadProject', 'could not verify project');
|
|
}
|
|
}, (error) => {
|
|
console.error(error);
|
|
});
|
|
};
|
|
// we always save the currently opened project,
|
|
// but we may save it under a different id
|
|
this.createSaveFile = (projectId = project.address.projectId) => {
|
|
const currentProjectId = project.address.projectId;
|
|
const fontsHashMap = window.getLayers().length > 0 ? window.getLayers()[0].fontsHashMap : false;
|
|
const vt_params = {
|
|
variable_time_version: VARIABLE_TIME_VERSION,
|
|
layerOrder: window.layerOrder.get(),
|
|
fontsHashMap,
|
|
projectId,
|
|
};
|
|
const theatre = tp.studio.createContentOfSaveFile(currentProjectId);
|
|
return this.theatre2variableTime(theatre, vt_params);
|
|
};
|
|
this.saveProject = (projectId = project.address.projectId, vt_project = false, silent = true) => {
|
|
if (!silent) {
|
|
projectId = prompt("Please choose a name for your project", projectId);
|
|
}
|
|
if (vt_project === false) {
|
|
vt_project = this.createSaveFile();
|
|
}
|
|
localStorage.setItem(`${config.projects.savePrefix}${projectId}`, JSON.stringify(vt_project));
|
|
};
|
|
this.getProject = (projectId, unwrapped = false) => {
|
|
const json = localStorage.getItem(`${config.projects.savePrefix}${projectId}`);
|
|
if (json === null) {
|
|
return false
|
|
}
|
|
return unwrapped ? JSON.parse(json) : json;
|
|
}
|
|
this.downloadProject = (projectId = project.address.projectId, saveBeforeDownloading = true) => {
|
|
if (projectId === project.address.projectId) {
|
|
let vt_project = this.createSaveFile();
|
|
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`);
|
|
}
|
|
}
|
|
};
|
|
this.reloadToProject = (projectId, hardReload = true) => {
|
|
const p = this.getProject(projectId, true);
|
|
if (p !== false) {
|
|
localStorage.setItem('currentProject', projectId);
|
|
if (hardReload) {
|
|
window.location.reload();
|
|
} else {
|
|
const layers = getLayers();
|
|
for (let l = 0; l < layers.length; l++) {
|
|
const doSaveProject = false;
|
|
console.log('TheatrePlay::reloadToProject','deleting layer' , layers[l].id());
|
|
deleteLayer(layers[l].id(), doSaveProject);
|
|
}
|
|
localStorage.removeItem('theatre-0.4.persistent');
|
|
studio.initialize();
|
|
this.initProject(projectId, p.theatre).then(() => {
|
|
this.loadProject(projectId, p);
|
|
});
|
|
}
|
|
} else {
|
|
console.error('TheatrePlay::reloadToProject', `no saved project with id ${projectId}`);
|
|
}
|
|
};
|
|
this.initProject = (projectId, projectJson = false) => {
|
|
return new Promise((resolve) => {
|
|
project = projectJson === false ? core.getProject(projectId) : core.getProject(projectId, {
|
|
state: projectJson
|
|
});
|
|
window.setLoadingTask('setting up animation', 10);
|
|
project.ready.then(() => {
|
|
this.sheet = project.sheet('mainSheet');
|
|
window.setLoadingTask('setting up animation', 30);
|
|
waitingForShadowRoot(() => {
|
|
this.shadowRoot = document.getElementById('theatrejs-studio-root').shadowRoot;
|
|
this.addShadowCss();
|
|
resolve();
|
|
window.setLoadingTask('setting up animation', 42);
|
|
});
|
|
localStorage.setItem('currentProject', project.address.projectId);
|
|
});
|
|
});
|
|
};
|
|
this.duration = 0;
|
|
this.loadProject = (projectId = false, project = false) => {
|
|
console.log('TheatrePlay::loadProject');
|
|
this.isProjectLoaded = false;
|
|
return new Promise((resolve) => {
|
|
if (projectId === false) {
|
|
projectId = this.sheet.project.address.projectId;
|
|
}
|
|
if (project === false) {
|
|
project = this.getProject(projectId, true);
|
|
}
|
|
if (project !== false) { // if project is not saved yet, create new
|
|
// get all objects
|
|
const objects = project
|
|
.theatre
|
|
.sheetsById
|
|
.mainSheet
|
|
.staticOverrides
|
|
.byObject;
|
|
|
|
// load artboard
|
|
let artboardValues = objects['artboard'];
|
|
|
|
// for backward compatibility rename old values
|
|
if (artboardValues.hasOwnProperty('scale')) {
|
|
artboardValues['zoom'] = artboardValues['scale'];
|
|
delete artboardValues['scale'];
|
|
}
|
|
// for backward compatibility merge with current values
|
|
const defaultArtboardValues = window.getArtboard().theatreObject.value;
|
|
const artboardProps = {...defaultArtboardValues, ...artboardValues};
|
|
|
|
studio.transaction(({
|
|
set
|
|
}) => {
|
|
set(window.getArtboard().theatreObject.props, artboardProps);
|
|
});
|
|
|
|
// load layers
|
|
const layerPromises = [];
|
|
Object.keys(objects)
|
|
.filter((e) => e.indexOf('layer-') === 0)
|
|
.forEach((layerId) => {
|
|
window.project_fontsHashMap = project.fontsHashMap;
|
|
layerPromises.push(window.addExistingLayer(layerId, objects[layerId]));
|
|
});
|
|
|
|
Promise.all(layerPromises).then(() => {
|
|
window.layerOrder.set(project.layerOrder);
|
|
this.duration = this.core.val(this.sheet.sequence.pointer.length);
|
|
if (project.layerOrder.length > 0) {
|
|
getLayers().forEach((layer) => {
|
|
if (layer.id() === project.layerOrder[project.layerOrder.length - 1]) {
|
|
layer.select();
|
|
}
|
|
});
|
|
}
|
|
resolve();
|
|
this.isProjectLoaded = true;
|
|
});
|
|
} else {
|
|
if (getLayers().length === 0 && config.layer.autoCreateFirstLayer) {
|
|
getFontsAndAxes().then((newFontsAndAxes) => {
|
|
const autoInitLayer = false;
|
|
const layer = addLayer(autoInitLayer);
|
|
layer.init().then(() => {
|
|
layer.select();
|
|
this.duration = this.core.val(this.sheet.sequence.pointer.length);
|
|
resolve();
|
|
this.isProjectLoaded = true;
|
|
});
|
|
});
|
|
} else {
|
|
this.duration = this.core.val(this.sheet.sequence.pointer.length);
|
|
resolve();
|
|
this.isProjectLoaded = true;
|
|
}
|
|
}
|
|
});
|
|
};
|
|
this.startNewProject = () => {
|
|
if(confirm('Starting a new project will clear all project data and spin up a blank project from scratch. Uploaded assets (a.k.a. fonts) will stay. Is this fine?')) {
|
|
setTimeout(() => {
|
|
window.localStorage.clear();
|
|
window.location.reload();
|
|
}, 100);
|
|
}
|
|
};
|
|
this.isInitialized = false;
|
|
this.isProjectLoaded = false;
|
|
this.init = () => {
|
|
return new Promise((resolve) => {
|
|
console.log('TheatrePlay', 'init');
|
|
localStorage.removeItem('theatre-0.4.persistent');
|
|
window.setLoadingTask('setting up animation', 0);
|
|
studio.initialize();
|
|
const currentProjectId = localStorage.getItem('currentProject');
|
|
|
|
if (currentProjectId === null ||
|
|
this.listProjects().indexOf(currentProjectId) < 0) {
|
|
this.initProject('variable-time').then(() => {
|
|
this.findInjectPanels();
|
|
window.setLoadingTask('setting up animation', 60);
|
|
this.isInitialized = true;
|
|
resolve();
|
|
});
|
|
} else {
|
|
const currentProject = this.getProject(currentProjectId, true);
|
|
this.initProject(currentProjectId, currentProject.theatre).then(() => {
|
|
this.findInjectPanels();
|
|
window.setLoadingTask('setting up animation', 60);
|
|
this.isInitialized = true;
|
|
resolve();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
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) => {
|
|
if (position < config.timeline.rolloverThreshold_s) {
|
|
Module.resetLetterDelay();
|
|
}
|
|
});
|
|
}
|
|
|
|
core.onChange(this.sheet.sequence.pointer.playing, (playing) => {
|
|
Module.setPlaying(playing);
|
|
});
|
|
};
|
|
this.studio = studio;
|
|
this.core = core;
|
|
|
|
//action
|
|
if (autoInit) {
|
|
this.init();
|
|
}
|
|
};
|
|
|
|
export {
|
|
TheatrePlay
|
|
};
|