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 = ``; 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; } .source_Dom_Cont{ padding: 5px; } .source_Dom_Cont select{ background-color: white; box-sizing: border-box; border: 1px solid transparent; color: rgb(0, 0, 0); padding: 1px 6px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; font-size: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-kerning: inherit; font-feature-settings: inherit; outline: none; text-align: left; /* width: 100%; */ border-radius: 0px; font-variation-settings: "wght" 700; height: 26px; margin-right: 10px; } .audio_min_Cont, .audio_max_Cont, .letterDelayCont{ 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.1em; padding: 5px 5px 4px 5px; margin: 7px 10px; display: flex; flex-grow: 1; justify-content: center; border-radius: 10px; border: none; padding: 7px; text-transform: uppercase; width: calc(100% - 20px); box-sizing: border-box; cursor: pointer; font-variation-settings: 'wght' 750, 'wdth' 100; } .main_panel_button:hover, .vte_button:hover{ color: white; background-color:black; } .removeButtonContainer{ display: flex; padding-right: 10px; padding-bottom: 10px; } .alignButtons img, .textAlignButtons img{ height: 19px; cursor: pointer; margin: 5px 5px 2px 5px; } li.layerMover div { } li.layerMover div.selected { background: rgba(255, 255, 255, 1); } li.layerMover div.selected svg circle{ fill: #ea2333; } .letterDelaysContWrapper{ display: flex; flex-direction: column; width: 100%; padding-bottom: 10px; } .letterDelaysContWrapper > *, .fontVariationAxesContWrapper > *{ margin-left: calc(var(--left-pad) * (var(--depth) - 1)); } .fontVariationAxesContWrapper{ display: flex; flex-direction: column; width: 100%; padding-bottom: 10px; margin-bottom: 10px; border-bottom: 1px dashed #91919177; border-top: 1px dashed #91919177; margin-top: 10px; } .moveLayerButton { } .removeLayerButton img, .duplicateLayerButton img, .addLayerButton img, .moveLayerButton img { height: 10px; cursor: pointer; } .propTitleStuff { color: red; } .addLayerButton{ width: calc(100% - 40px); display: flex; align-items: center; justify-content: center; /* padding-left: 15px; */ align-self: end; margin-top: 5px; } .audioButton{ width: 17px; margin: 2px 2px 2px 5px; flex-shrink: 0; display: flex; background: rgb(163, 163, 163); border-radius: 50%; height: 17px; align-self: center; display: flex; align-items: center; justify-content: center; padding: 2px; box-sizing: border-box; cursor: pointer } .audioButton:hover{ 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 };