variabletime/bin/web/js/theatre-play.js

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
};