import { TheatrePlay } from './theatre-play.js'; import { Layer } from './layer.js'; import { Artboard } from './artboard.js'; import { LayerOrder } from './layerOrder.js'; import { Exporter } from './exporter.js'; import { Interactor } from './interactor.js'; import { ModuleFS } from './moduleFS.js'; import { Audio } from './audio.js'; import { Record } from './record.js'; //import { //MidiController //} from './midiController.js'; import { makeDraggable, getBaseName, uploadFile, downloadFile, hashFromString, clone, isMobile, } from './utils.js'; import { Config } from './config.js'; window.uploadFile = uploadFile; window.downloadFile = downloadFile; window.isInitialized = false; window.hashFromString = hashFromString; const config = new Config(); window.config = config; const tp = new TheatrePlay(); window.tp = tp; const layers = []; const layersById = {}; const layerOrder = new LayerOrder(); window.layerOrder = layerOrder; const fontsAndAxes = []; let artboard; const exporter = new Exporter(); const interactor = new Interactor(); const moduleFS = new ModuleFS(); window.moduleFS = moduleFS; const record = new Record(tp); window.record = record; const audio = new Audio(tp, record); window.audio = audio; window.panelFinderTimeout = false; const sequenceEventBuffer = {}; //const midiController = new MidiController(); //window.midiController = midiController; let about = null; const getAbout = () => { if (about === null) { about = document.querySelector('#overlay-text-cont'); // should we succeed in getting about, // attach events. this happens then exactly once if (about !== null) { const textParents = getAbout().querySelectorAll(".textParent"); textParents.forEach((textParent) => { const buttonExp = textParent.querySelector(".expandText"); if (buttonExp === null) { console.error("Could not find .expandText within .textParent"); return; } buttonExp.addEventListener("click", () => { textParent.classList.toggle("openText"); if (textParent.classList.contains("openText")) { buttonExp.textContent = "-"; } else { buttonExp.textContent = "+"; } }); }); } } return about; }; window.showAbout = () => { if (getAbout() === null) { return; } getAbout().classList.remove("hidden"); } window.hideAbout = () => { if (getAbout() !== null) { getAbout().classList.add("hidden"); } } let theatrePanel = null; const getPanel = () => { if (theatrePanel === null) { theatrePanel = tp.shadowRoot.querySelector('[data-testid="DetailPanel-Object"]'); } return theatrePanel; }; const findInjectPanel = () => { const panel = getPanel(); if (panel !== null) { let bottomButtonsContainer = panel.querySelector('.bottomButtonsContainer'); if (bottomButtonsContainer === null) { bottomButtonsContainer = document.createElement('div'); bottomButtonsContainer.classList.add("bottomButtonsContainer"); panel.append(bottomButtonsContainer); } const hideuiButton = document.querySelector('#hide_ui'); if (hideuiButton !== null) { bottomButtonsContainer.append(hideuiButton); hideuiButton.classList.add("main_panel_button"); } const exportButton = document.querySelector('#exporter_open'); if (exportButton !== null) { bottomButtonsContainer.append(exportButton); exportButton.classList.add("main_panel_button"); } const saveButton = document.querySelector('#save_project'); if (saveButton !== null) { bottomButtonsContainer.append(saveButton); saveButton.classList.add("main_panel_button"); } const openButton = document.querySelector('#open_project'); if (openButton !== null) { bottomButtonsContainer.append(openButton); openButton.classList.add("main_panel_button"); } const profilingButton = document.querySelector('#debug_profiling'); if (profilingButton !== null) { bottomButtonsContainer.append(profilingButton); profilingButton.classList.add("main_panel_button"); } const startNewButton = document.querySelector('#start_new_project'); if (startNewButton !== null) { bottomButtonsContainer.append(startNewButton); startNewButton.classList.add("main_panel_button"); } } else { setTimeout(() => { findInjectPanel(); }, 100); } }; window.onload = () => { if (isMobile()) { alert('Sorry, Variable Time is a tool currently designed to be used on desktop!'); } window.addEventListener('panelEvent', (e) => { clearTimeout(window.panelFinderTimeout); let target = false; if (e.detail.panelID === 'artboard') { target = artboard; } else if (layersById.hasOwnProperty(e.detail.panelID)) { target = layersById[e.detail.panelID]; } if (target !== false && typeof target !== 'undefined' && target.hasOwnProperty('receivePanelEvent')) { target.receivePanelEvent(e); } }); window.addEventListener('sequenceEvent', (e) => { let target = false; if (e.detail.panelID === 'artboard') { target = artboard; } else { target = layersById[e.detail.panelID]; } if (target !== false && typeof target !== 'undefined' && target.hasOwnProperty('receiveSequenceEvent')) { target.receiveSequenceEvent(e.detail); } else { // whoops, no layers there yet // let's put this stuff in a buffer and forward it later if (!sequenceEventBuffer.hasOwnProperty(e.detail.panelID)) { sequenceEventBuffer[e.detail.panelID] = []; } sequenceEventBuffer[e.detail.panelID].push(e.detail); } tp.friendlySequenceNames(); }); window.setLoadingTask('setting up animation', 0); tp.init() .then(() => { const content = document.querySelector('#content'); if (window.moduleInitialized) { postModuleInitialized(); } else { window.addEventListener('initializedModule', postModuleInitialized); } tp.studio.onSelectionChange((newSelection) => { if (newSelection.length > 0) { [getArtboard(), getLayers()].flat().forEach((e) => { if (e.id() === newSelection[0].address.objectKey) { if (e.id().indexOf('layer-') === 0) { e.findInjectPanel(); e.showBoundingBoxDiv(); setTimeout(() => { e.hideBoundingBoxDiv(); }, 60); } else if (e.id() === 'artboard') { e.findInjectPanel(); } } }); } findInjectPanel(); tp.friendlySequenceNames(); }); }); // ABOUT BEGIN var lettersAndLinks = document.querySelectorAll(".vt-title"); lettersAndLinks.forEach(function(element) { element.innerHTML = element.innerHTML.replace(/\w+/g, '$&'); element.innerHTML = element.innerHTML.replace(/\*/g, '$&'); }); var words = document.querySelectorAll(".word"); words.forEach(function(element) { element.innerHTML = element.innerHTML.replace(/\S/g, '$&'); }); var letterElements = document.querySelectorAll(".letter"); letterElements.forEach(function(element, index) { element.style.animationDelay = (index * -0.3) + "s"; }); // ABOUT END } const adjustPanel = () => { const VTTitle = tp.shadowRoot.querySelector(`.layerMovervariable-time`); if (VTTitle !== null) { var titleText = VTTitle.querySelectorAll("span"); titleText.forEach((element, index) => { if (element.innerHTML == "variable-time") { element.innerHTML = "vt*"; element.classList.add("vtTitle"); element.innerHTML = element.innerHTML.replace(/\w+/g, '$&'); const wordElements = tp.shadowRoot.querySelectorAll(".word"); wordElements.forEach(word => { word.innerHTML = word.innerHTML.replace(/\S/g, '$&'); element.innerHTML = element.innerHTML.replace('*', '$&'); }); const letterElements = tp.shadowRoot.querySelectorAll(".letter"); letterElements.forEach(letter => { letter.style.fontVariationSettings = "'wght' " + Math.floor(Math.random() * (100 - 0 + 1) + 0) + ", 'wdth'" + Math.floor(Math.random() * (100 - 0 + 1) + 0) + ", 'opsz'" + Math.floor(Math.random() * (10 - 0 + 1) + 0); }); element.addEventListener("mouseover", function() { letterElements.forEach(letter => { letter.style.fontVariationSettings = "'wght' " + Math.floor(Math.random() * (100 - 0 + 1) + 0) + ", 'wdth'" + Math.floor(Math.random() * (100 - 0 + 1) + 0) + ", 'opsz'" + Math.floor(Math.random() * (10 - 0 + 1) + 0); }); }); } }) } } const resize = () => { let width = document.body.clientWidth; let height = document.body.clientHeight; let ratio = window.devicePixelRatio ? window.devicePixelRatio : 1; Module.canvas.setAttribute('width', width * ratio); Module.canvas.setAttribute('height', height * ratio); Module.canvas.style.width = `${width}px}`; Module.canvas.style.height = `${height}px}`; Module.windowResized(Math.round(width * ratio), Math.round(height * ratio)); }; const postModuleInitialized = () => { window.setLoadingTask('setting up animation', 80); moduleFS.init() .then(() => { artboard = new Artboard(tp, content); initPanels(); // NOTE: we know that our TheatrePlay is initialized tp.connectModuleCallbacks(); exporter.init(); getFontsAndAxes(); tp.loadProject().then(() => { interactor.init(); resize(); adjustPanel(); window.setLoadingTask('setting up animation', 100); window.isInitialized = true; window.setLoadingDone(); window.autoSaveInterval = setInterval(() => { if (config.autoSave && window.isInitialized) { tp.saveProject(); } }, 1000); }); //midiController.init(); }); window.removeEventListener('initializedModule', postModuleInitialized); window.addEventListener('resize', function(event) { resize(); }, true); }; const getFontsAndAxes = () => { return new Promise((resolve) => { const availableFontsAndAxes = listAvailableFontsAndAxes(); const newFontsAndAxes = []; for (let i in availableFontsAndAxes) { if (!fontsAndAxes.includes(availableFontsAndAxes[i])) { // nevermind includes and test ourselves let reallyNew = true; fontsAndAxes.forEach((faa) => { // path is enough if (faa.fontPath === availableFontsAndAxes[i].fontPath) { reallyNew = false; } }); if (reallyNew) { fontsAndAxes.push(availableFontsAndAxes[i]); newFontsAndAxes.push(availableFontsAndAxes[i]); } } } if (newFontsAndAxes.length > 0) { const promises = []; for (let l = 0; l < layers.length; l++) { layers[l].updateFonts() .then(() => { resolve(newFontsAndAxes); }); } } else { resolve(false); } }); }; const listAvailableFontsAndAxes = () => { let availableFontsAndAxes = []; let fontPaths = Module.listAvailableFonts(); for (let f = 0; f < fontPaths.size(); f++) { const fontPath = fontPaths.get(f); const fontName = getBaseName(fontPath); const cppAxes = Module.listVariationAxes(fontPath); // turn cppAxes in normal js array of objects const axes = []; for (let a = 0; a < cppAxes.size(); a++) { let axis = { name: cppAxes.get(a).name, minValue: cppAxes.get(a).minValue, maxValue: cppAxes.get(a).maxValue, defaultValue: cppAxes.get(a).defaultValue }; axes.push(axis); } availableFontsAndAxes.push({ fontName, fontPath, axes }); } return availableFontsAndAxes; }; window.listAvailableFontsAndAxes = listAvailableFontsAndAxes; window.getFontsAndAxes = getFontsAndAxes; window.getLayers = () => { return layers; }; window.getLayer = (layerID) => { return layers.find((layer) => layer.id() === layerID); }; window.moveLayerUp = (layerID) => { layerOrder.moveUp(layerID); }; window.moveLayerDown = (layerID) => { layerOrder.moveDown(layerID); }; window.getArtboard = () => { return artboard; }; const addLayer = (autoInit = true) => { const layerID = Module.addNewLayer(); const layer = new Layer(tp, layerID, fontsAndAxes, autoInit); layers.push(layer); layersById[layerID] = layer; return layer; }; const addExistingLayer = (layerID, values) => { return new Promise((resolve) => { const layer = new Layer(tp, layerID, fontsAndAxes, false); // check if fonts exist? layer.valuesCorrector(values); const cppProps = layer.values2cppProps(values); const checkID = Module.addExistingLayer(cppProps, layerID); layer.init().then(() => { layers.push(layer); layersById[layerID] = layer; resolve(layer); }); }); }; const duplicateLayer = (originalLayer) => { return new Promise((resolve) => { const originalValues = clone(originalLayer.theatreObject.value); const newLayer = addLayer(false); newLayer.init(originalValues).then(() => { const originalKeyframes = tp.getKeyframes(originalLayer); const addKeyframes = (e) => { const originalKeys = Object.keys(originalValues); const givenKeys = e.detail.titles; let allKeysFound = true; for (let i = 0; i < originalKeys.length; i++) { //const originalValue = originalValues[originalKeys[i]]; if (givenKeys.indexOf(originalKeys[i]) < 0) { //delete originalValues[originalKeys[i]]; allKeysFound = false; } } if (allKeysFound) { tp.getPanel().removeEventListener("injected", addKeyframes); } tp.addKeyframes(newLayer, originalKeyframes).then(() => { if (allKeysFound) { resolve(); } }); }; tp.getPanel().addEventListener("injected", addKeyframes); newLayer.select(); //tp.shadowRoot.querySelector(`.layerMover${newLayer.id()} div`).click(); }); }); }; const deleteLayer = (id, saveProject = true) => { let index = -1; Module.removeLayer(id); tp.removeObject(id); layerOrder.remove(id); // delete from array for (let i = 0; i < layers.length; i++) { if (layers[i].id() === id) { index = i; } } layers[index].prepareForDepartureFromThisBeautifulExperience(); layers.splice(index, 1); delete layersById[id]; if (saveProject) { setTimeout(() => { tp.saveProject(); }, 1000); } }; // TODO: better function names // because, come on. it may be funny for a second // but tolerance for fun is low when you're grumpy // because stuff doesn't work const renderForReal = (position, frameTime) => { position = position + frameTime; tp.sheet.sequence.position = position; Module.renderNextFrame(); } window.isRenderDirty = true; window.duplicateLayer = (layer) => { const noticeDom = document.querySelector('#notice'); noticeDom.classList.add('visible'); noticeDom.querySelector('.what > p').innerHTML = `Duplicating Layer`; noticeDom.querySelector('.details > p').innerHTML = `Please wait, thank you.`; duplicateLayer(layer).then(() => { document.querySelector('#notice').classList.remove('visible'); }); }; window.addLayer = addLayer; window.addExistingLayer = addExistingLayer; window.deleteLayer = deleteLayer; window.renderFrames = exporter.renderFrames; const layer_panel = document.querySelector('#layer_panel'); const ui = (show) => { if (show && tp.studio.ui.isHidden) { tp.studio.ui.restore(); } else if (!show && !tp.studio.ui.isHidden) { tp.studio.ui.hide(); } }; const handleUiKeypress = (e) => { if (e.key.toLowerCase() === 'q') { document.removeEventListener('keypress', handleUiKeypress); ui(true); } }; const initPanels = () => { let hideuiButton = document.querySelector('#hide_ui'); if (hideuiButton === null) { hideuiButton = tp.getPanel().querySelector('#hide_ui'); } if (hideuiButton !== null) { hideuiButton.addEventListener('click', () => { ui(false); document.addEventListener('keypress', handleUiKeypress); }); } };