variabletime/bin/web/js/main.js

536 lines
17 KiB
JavaScript

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 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, '<span class="word">$&</span>');
element.innerHTML = element.innerHTML.replace(/\*/g, '<span class="word">$&</span>');
});
var words = document.querySelectorAll(".word");
words.forEach(function(element) {
element.innerHTML = element.innerHTML.replace(/\S/g, '<span class="letter">$&</span>');
});
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, '<span class="word">$&</span>');
const wordElements = tp.shadowRoot.querySelectorAll(".word");
wordElements.forEach(word => {
word.innerHTML = word.innerHTML.replace(/\S/g, '<span class="letter">$&</span>');
element.innerHTML = element.innerHTML.replace('*', '<span class="letter">$&</span>');
});
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 initPanels = () => {
//makeDraggable(layer_panel);
};