1038 lines
39 KiB
JavaScript
1038 lines
39 KiB
JavaScript
|
import '../theatre_modules/core-and-studio.js'
|
||
|
import {
|
||
|
downloadFile,
|
||
|
uploadFile,
|
||
|
verifyVariableTimeProject,
|
||
|
hashFromString,
|
||
|
clone,
|
||
|
getParents,
|
||
|
arraysEqual,
|
||
|
} 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(path.join('.'));
|
||
|
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 addKeyframes = (layer, keyframes) => {
|
||
|
return new Promise((resolve) => {
|
||
|
if (!Array.isArray(keyframes)) {
|
||
|
return false;
|
||
|
}
|
||
|
const existingKeyframes = getKeyframes(layer);
|
||
|
const promises = [];
|
||
|
const ms = 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;
|
||
|
// 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();
|
||
|
});
|
||
|
}, ms * promises.length);
|
||
|
}));
|
||
|
}
|
||
|
});
|
||
|
promises.push(new Promise((subResolve) => {
|
||
|
setTimeout(() => {
|
||
|
tp.sheet.sequence.position = position;
|
||
|
subResolve();
|
||
|
}, ms * promises.length);
|
||
|
}));
|
||
|
});
|
||
|
Promise.all(promises).then(() => {
|
||
|
resolve();
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
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.friendlySequenceNames = friendlySequenceNames;
|
||
|
this.getKeyframes = getKeyframes;
|
||
|
this.addKeyframes = addKeyframes;
|
||
|
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.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;
|
||
|
}
|
||
|
label {
|
||
|
color: #ea2333;
|
||
|
margin-left:10px;
|
||
|
}
|
||
|
input[type=checkbox] {
|
||
|
position: absolute;
|
||
|
width: 27px;
|
||
|
opacity: 0;
|
||
|
}
|
||
|
input[type=checkbox]:checked + label {
|
||
|
color: #1cba94;
|
||
|
}
|
||
|
input[type=checkbox] + label::after{
|
||
|
content: ' OFF';
|
||
|
}
|
||
|
input[type=checkbox]:checked + label::after{
|
||
|
content: ' ON';
|
||
|
}
|
||
|
.mirror_yWrapper{
|
||
|
}
|
||
|
.mirror_xyWrapper{
|
||
|
}
|
||
|
.mirror_x_distanceWrapper{
|
||
|
}
|
||
|
.mirror_y_distanceWrapper{
|
||
|
}
|
||
|
|
||
|
.fontFamilyWrapper{
|
||
|
}
|
||
|
.rotationWrapper{
|
||
|
border-top: 1px dashed #91919177;
|
||
|
padding-top: 10px;
|
||
|
}
|
||
|
.transformOriginWrapper{
|
||
|
}
|
||
|
.widthWrapper{
|
||
|
}
|
||
|
.heightWrapper{
|
||
|
display: none;
|
||
|
}
|
||
|
.fontSizeWrapper{
|
||
|
}
|
||
|
.letterSpacingWrapper{
|
||
|
}
|
||
|
.letterDelayWrapper{
|
||
|
}
|
||
|
.lineHeighWrapper{
|
||
|
}
|
||
|
.textWrapper{
|
||
|
}
|
||
|
.colorWrapper{
|
||
|
border-bottom: 1px dashed #91919177;
|
||
|
border-top: 1px dashed #91919177;
|
||
|
margin-bottom: 10px;
|
||
|
padding-bottom: 10px;
|
||
|
padding-top: 10px;
|
||
|
}
|
||
|
|
||
|
span.icon{
|
||
|
font-family: 'VariableIcons';
|
||
|
font-size: 3em;
|
||
|
margin: 2px 5px;
|
||
|
line-height: 0.2em;
|
||
|
box-sizing: border-box;
|
||
|
}
|
||
|
|
||
|
|
||
|
.main_panel_button, .vte_button{
|
||
|
background-color: white;
|
||
|
font-size: 1.15em;
|
||
|
padding: 5px 5px 4px 5px;
|
||
|
margin: 10px;
|
||
|
display: flex;
|
||
|
flex-grow: 1;
|
||
|
justify-content: center;
|
||
|
border-radius: 10px;
|
||
|
border: none;
|
||
|
padding: 10px;
|
||
|
text-transform: uppercase;
|
||
|
width: calc(100% - 20px);
|
||
|
box-sizing: border-box;
|
||
|
cursor: pointer;
|
||
|
font-variation-settings: 'wght' 750, 'wdth' 100;
|
||
|
|
||
|
}
|
||
|
.main_panel_button:hover, .vte_button:hover{
|
||
|
color: white;
|
||
|
background-color:black;
|
||
|
}
|
||
|
|
||
|
.removeButtonContainer{
|
||
|
display: flex;
|
||
|
padding-right: 10px;
|
||
|
padding-bottom: 10px;
|
||
|
}
|
||
|
|
||
|
.alignButtons img,
|
||
|
.textAlignButtons img{
|
||
|
height: 19px;
|
||
|
cursor: pointer;
|
||
|
margin: 5px 5px 2px 5px;
|
||
|
|
||
|
}
|
||
|
|
||
|
li.layerMover div {
|
||
|
|
||
|
}
|
||
|
|
||
|
li.layerMover div.selected {
|
||
|
background: rgba(255, 255, 255, 1);
|
||
|
}
|
||
|
|
||
|
li.layerMover div.selected svg circle{
|
||
|
fill: #ea2333;
|
||
|
}
|
||
|
|
||
|
.letterDelaysContWrapper{
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
width: 100%;
|
||
|
padding-bottom: 10px;
|
||
|
}
|
||
|
|
||
|
.letterDelaysContWrapper > *, .fontVariationAxesContWrapper > *{
|
||
|
margin-left: calc(var(--left-pad) * (var(--depth) - 1));
|
||
|
}
|
||
|
|
||
|
.fontVariationAxesContWrapper{
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
width: 100%;
|
||
|
padding-bottom: 10px;
|
||
|
margin-bottom: 10px;
|
||
|
border-bottom: 1px dashed #91919177;
|
||
|
border-top: 1px dashed #91919177;
|
||
|
margin-top: 10px;
|
||
|
}
|
||
|
|
||
|
.moveLayerButton {
|
||
|
|
||
|
}
|
||
|
.removeLayerButton img,
|
||
|
.duplicateLayerButton img,
|
||
|
.addLayerButton img,
|
||
|
.moveLayerButton img {
|
||
|
height: 10px;
|
||
|
cursor: pointer;
|
||
|
}
|
||
|
.propTitleStuff {
|
||
|
color: red;
|
||
|
}
|
||
|
.addLayerButton{
|
||
|
width: calc(100% - 40px);
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
justify-content: center;
|
||
|
/* padding-left: 15px; */
|
||
|
align-self: end;
|
||
|
margin-top: 5px;
|
||
|
}
|
||
|
`;
|
||
|
this.shadowRoot.appendChild(style);
|
||
|
}
|
||
|
};
|
||
|
const waitingForShadowRoot = (callback) => {
|
||
|
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
|
||
|
};
|