dependencies hashes: openFrameworks d78075f4bca6be2a2533c6e51a75cc1f18404501 ofxMsdfgen e14da13d02c4dff04fb69d7923469f606924e6c3 ofxGPUFont d482bb7cbdf6b296fa4ab5abcf73fb5ff8c8b239 ofxVariableLab 0b5f9bdebc1e5550621957e73c040c258ec6317b ofxProfiler a868e34fa1a79189dd4fbdede2938e308535e5e8 theatre 86d3e07f6f2c75fd6e08fca8c97e3617c9e23b18
838 lines
35 KiB
JavaScript
838 lines
35 KiB
JavaScript
import '../theatre_modules/core-and-studio.js'
|
|
import {
|
|
downloadFile,
|
|
uploadFile,
|
|
verifyVariableTimeProject,
|
|
hashFromString,
|
|
clone,
|
|
getParents,
|
|
arraysEqual,
|
|
sequencialPromises,
|
|
getNestedProperty,
|
|
} from './utils.js';
|
|
|
|
const TheatrePlay = function(autoInit = false) {
|
|
|
|
//private
|
|
const {
|
|
core,
|
|
studio
|
|
} = Theatre;
|
|
|
|
let project = false;
|
|
const theatreObjects = {};
|
|
let theatrePanel = null;
|
|
let sequencePanelLeft = null;
|
|
const getSequencePanelLeft = () => {
|
|
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, layer = getLayer()) => {
|
|
if (!Array.isArray(path)) {
|
|
path = path.split('.');
|
|
}
|
|
const prop = getNestedProperty(layer.theatreObject.props, path);
|
|
return studio.__experimental.__experimental_isPropSequenced(prop);
|
|
};
|
|
|
|
const setSequenced = (path, sequenced = true, layer = getLayer()) => {
|
|
if (!Array.isArray(path)) {
|
|
path = path.split('.');
|
|
}
|
|
return new Promise((resolve) => {
|
|
if(isSequenced(path, layer) === sequenced) {
|
|
resolve(true);
|
|
} else {
|
|
const prop = getNestedProperty(layer.theatreObject.props, path);
|
|
if (sequenced) {
|
|
tp.studio.__experimental.__experimental_setPropAsSequenced(prop);
|
|
} else {
|
|
tp.studio.__experimental.__experimental_setPropAsStatic(prop);
|
|
}
|
|
let count = 0;
|
|
const interval = setInterval(() => {
|
|
if (isSequenced(path, layer) === sequenced) {
|
|
clearInterval(interval);
|
|
resolve(true);
|
|
} else if (count >= 10) {
|
|
clearInterval(interval);
|
|
resolve(false);
|
|
}
|
|
count++;
|
|
}, 10);
|
|
}
|
|
});
|
|
};
|
|
|
|
/*
|
|
* layer = getLayer()
|
|
* keyframes = [
|
|
* {
|
|
* path: ['fontVariationAxes','Weight'],
|
|
* keyframes: [
|
|
* {
|
|
* "id": "yHFP-HCINm", // not sure if used when sent back
|
|
* "position": 0,
|
|
* "connectedRight": true, // optional
|
|
* "handles": [ 0.5, 1, 0.5, 0 ], // optional
|
|
* "type": "bezier", // optional
|
|
* "value": 200
|
|
* }, ...
|
|
* ]
|
|
* }, ...
|
|
* ]
|
|
*/
|
|
const addKeyframes = (layer, keyframes) => {
|
|
return new Promise((resolve) => {
|
|
const promises = [];
|
|
keyframes.forEach((k) => {
|
|
promises.push(() => {
|
|
return new Promise((subResolve) => {
|
|
let prop = layer.theatreObject.props;
|
|
for (let i = 0; i < k.path.length; i++) {
|
|
prop = prop[k.path[i]];
|
|
}
|
|
setSequenced(k.path, true, layer).then(() => {
|
|
tp.studio.transaction(({
|
|
__experimental_addKeyframes
|
|
}) => {
|
|
__experimental_addKeyframes(prop, k.keyframes);
|
|
});
|
|
subResolve();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
sequencialPromises(promises, resolve);
|
|
});
|
|
};
|
|
|
|
// NOTE: instead of setting proptitle static and then sequenced again,
|
|
// we could simply delete all keyframes. but hey, pfff...
|
|
const setKeyframes = (layer, keyframes) => {
|
|
return new Promise((resolve) => {
|
|
if (!Array.isArray(keyframes)) {
|
|
resolve(false);
|
|
return false;
|
|
}
|
|
const promises = [];
|
|
let waitify = false;
|
|
keyframes.forEach((k) => {
|
|
const propTitle = k.path.join('.');
|
|
if (isSequenced(propTitle, layer)) {
|
|
waitify = true;
|
|
promises.push(() => {
|
|
return new Promise((subResolve) => {
|
|
setSequenced(propTitle, false, layer)
|
|
.then(() => {
|
|
subResolve();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
});
|
|
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) {
|
|
this.shadowRoot.insertBefore(Picker.StyleElement.cloneNode(true), this.shadowRoot.querySelector('#pointer-root'));
|
|
let style = document.createElement("link");
|
|
style.setAttribute('id', 'tpShadowCss');
|
|
style.setAttribute('rel', 'stylesheet');
|
|
style.setAttribute('href', '/web/css/theatre.css');
|
|
this.shadowRoot.insertBefore(style, this.shadowRoot.querySelector('#pointer-root'));
|
|
}
|
|
};
|
|
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');
|
|
}
|
|
});
|
|
} else if (verifyVariableTimeProject(vt_project)) {
|
|
this.possiblyAskForFontsPromise(vt_project).then((vt_project_u) => {
|
|
vt_project = vt_project_u;
|
|
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,
|
|
audioSavedMapping: audio.getSavedMapping(),
|
|
audioMapping: audio.getMapping(),
|
|
};
|
|
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);
|
|
}
|
|
Module.downloadProject(projectId, JSON.stringify(vt_project));
|
|
} else {
|
|
const p = this.getProject(projectId);
|
|
if (p !== false) {
|
|
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
|
|
});
|
|
//console.log({project, 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'];
|
|
}
|
|
if (artboardValues.hasOwnProperty('backgroundColor')) {
|
|
artboardValues['color'] = artboardValues['backgroundColor'];
|
|
delete artboardValues['backgroundColor'];
|
|
}
|
|
// for backward compatibility merge with current values
|
|
// for backward compatibility merge with current values
|
|
const defaultArtboardValues = window.getArtboard().theatreObject.value;
|
|
const artboardProps = {...defaultArtboardValues, ...artboardValues};
|
|
|
|
window.setLoadingTask('setting up artboard', 90);
|
|
studio.transaction(({
|
|
set
|
|
}) => {
|
|
set(window.getArtboard().theatreObject.props, artboardProps);
|
|
});
|
|
if (project.hasOwnProperty('audioMapping')) {
|
|
audio.setMapping(project.audioMapping);
|
|
}
|
|
if (project.hasOwnProperty('audioSavedMapping')) {
|
|
audio.setSavedMapping(project.audioSavedMapping);
|
|
}
|
|
|
|
// load layers
|
|
const layerPromises = [];
|
|
Object.keys(objects)
|
|
.filter((e) => e.indexOf('layer-') === 0)
|
|
.forEach((layerId) => {
|
|
window.setLoadingTask(`setting up the shapes of ${layerId} to come`, 90);
|
|
window.project_fontsHashMap = project.fontsHashMap;
|
|
layerPromises.push(window.addExistingLayer(layerId, objects[layerId]));
|
|
});
|
|
//console.log(clone(objects));
|
|
Object.keys(objects)
|
|
.filter((e) => e.indexOf('audio-') === 0)
|
|
.forEach((layerId) => {
|
|
window.setLoadingTask(`setting up the sounds of ${layerId} to come`, 90);
|
|
window.project_fontsHashMap = project.fontsHashMap;
|
|
layerPromises.push(window.addExistingAudioLayer(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 {
|
|
const layers = getLayers();
|
|
if (layers.length > 0) {
|
|
}
|
|
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');
|
|
|
|
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.isPlaying = () => {
|
|
return this.core.val(this.sheet.sequence.pointer.playing);
|
|
};
|
|
this.studio = studio;
|
|
this.core = core;
|
|
|
|
//action
|
|
if (autoInit) {
|
|
this.init();
|
|
}
|
|
};
|
|
|
|
export {
|
|
TheatrePlay
|
|
};
|