607 lines
20 KiB
JavaScript
607 lines
20 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 hideuiButton = document.querySelector('#hide_ui');
|
|
if (hideuiButton !== null) {
|
|
bottomButtonsContainer.append(hideuiButton);
|
|
hideuiButton.classList.add("main_panel_button");
|
|
}
|
|
const audiofileButton = document.querySelector('#upload_audio');
|
|
if (audiofileButton !== null) {
|
|
bottomButtonsContainer.append(audiofileButton);
|
|
audiofileButton.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();
|
|
console.log('Main::selectionChange',newSelection.length > 0 ? newSelection[0].address.objectKey : 'aah nothing');
|
|
});
|
|
});
|
|
// 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.getArtboard = () => {
|
|
return artboard;
|
|
};
|
|
|
|
window.getLayers = () => {
|
|
return layers;
|
|
};
|
|
|
|
window.getLayer = (layerID) => {
|
|
if (layerID === 'artboard') {
|
|
return artboard;
|
|
} else {
|
|
return layers.find((layer) => layer.id() === layerID);
|
|
}
|
|
};
|
|
|
|
window.moveLayerUp = (layerID) => {
|
|
layerOrder.moveUp(layerID);
|
|
};
|
|
|
|
window.moveLayerDown = (layerID) => {
|
|
layerOrder.moveDown(layerID);
|
|
};
|
|
|
|
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;
|
|
}
|
|
}
|
|
// delete from audio
|
|
if (typeof audio.getMapping() === 'object' && typeof audio.getMapping()[id] === 'object') {
|
|
delete audio.getMapping()[id];
|
|
}
|
|
if (typeof audio.getSavedMapping() === 'object' && typeof audio.getSavedMapping()[id] === 'object') {
|
|
delete audio.getSavedMapping()[id];
|
|
}
|
|
if (typeof audio.canvasCombos === 'object') {
|
|
Object.keys(audio.canvasCombos).forEach((propTitle) => {
|
|
if (audio.canvasCombos[propTitle][2] === id) {
|
|
delete audio.canvasCombos[propTitle];
|
|
}
|
|
});
|
|
}
|
|
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);
|
|
});
|
|
}
|
|
let audiofileButton = document.querySelector('#upload_audio');
|
|
if (audiofileButton === null) {
|
|
audiofileButton = tp.getPanel().querySelector('#upload_audio');
|
|
}
|
|
if (audiofileButton !== null) {
|
|
audiofileButton.addEventListener('click', () => {
|
|
uploadFile('audio')
|
|
.then((file) => {
|
|
moduleFS
|
|
.save(file)
|
|
.then(() => {
|
|
console.log('ermh... done uploading?', file);
|
|
audio.readAudioFiles();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
};
|