variabletime/bin/web/js/layer.js
2023-09-24 18:39:52 +02:00

1084 lines
50 KiB
JavaScript

import {
getUuid,
htmlToElement,
hashFromString,
uploadFile,
clone,
} from './utils.js'
const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) {
const transformOriginToInt = {
top_left: 0,
top_right: 1,
center: 2,
bottom_left: 3,
bottom_right: 4
};
//private
let props = {
x: 200,
y: 200,
width: 0,
height: 0,
rotation: 0,
fontSize_px: 100,
letterSpacing: tp.core.types.number(0, {
nudgeMultiplier: 0.01
}),
lineHeight: tp.core.types.number(1, {
nudgeMultiplier: 0.1
}),
textAlignment: tp.core.types.number(0, {
range: [0, 1]
}),
color: tp.core.types.rgba({
r: 0,
g: 0,
b: 0,
a: 1
}),
transformOrigin: tp.core.types.stringLiteral(
'center',
{
top_left: 'top left',
top_right: 'top right',
center: 'center',
bottom_left: 'bottom left',
bottom_right: 'bottom right'
},
),
mirror_x: false,
mirror_x_distance: 0,
mirror_y: false,
mirror_y_distance: 0,
mirror_xy: false,
text: config.layer.defaultTexts[Math.floor(Math.random()*config.layer.defaultTexts.length)],
};
let lastValues = {};
let fontsHashMap = {};
let sequenceEventBuffer = [];
let injectedPanel = false;
// generate fontsHashMap
// with initial fontsAndAxes
for (let f = 0; f < fontsAndAxes.length; f++) {
const hash = hashFromString(fontsAndAxes[f].fontPath);
fontsHashMap[hash] = fontsAndAxes[f];
}
this.fontsAndAxes = fontsAndAxes;
this.fontsHashMap = fontsHashMap;
this.selectFont = (selectedFont) => {
const selectableFonts = {};
let selectedFontPath = false;
let selectedFontIndex = false;
let selectedHash = false;
for (let f = 0; f < fontsAndAxes.length; f++) {
const hash = hashFromString(fontsAndAxes[f].fontPath);
fontsHashMap[hash] = fontsAndAxes[f];
const path = fontsAndAxes[f].fontPath;
const name = path.indexOf('data') === 0 ?
fontsAndAxes[f].fontName : `user: ${fontsAndAxes[f].fontName}`;
selectableFonts[hash] = name;
if (selectedFont === name
|| selectedFont === hash
|| selectedFont === path) {
selectedFontPath = path;
selectedFontIndex = f;
selectedHash = hash;
}
}
if (selectedFontPath !== false
&& selectedFontIndex !== false
&& selectedHash !== false) {
props.fontFamily = tp.core.types.stringLiteral(
selectedHash,
selectableFonts
);
let axes = fontsAndAxes[selectedFontIndex].axes;
if (axes.length > 0) {
let variationAxes = {};
for (let a = 0; a < axes.length; a++) {
const sanity_minMax = axes[a].minValue < axes[a].maxValue;
const sanity_minDefault = axes[a].minValue <= axes[a].defaultValue;
const sanity_maxDefault = axes[a].maxValue >= axes[a].defaultValue;
if (sanity_minMax && sanity_minDefault && sanity_maxDefault) {
variationAxes[axes[a].name] = tp.core.types.number(axes[a].defaultValue, {
range: [axes[a].minValue, axes[a].maxValue],
});
} else {
console.log('js::layer::selectFont', 'this axis is insane, abort', axes[a]);
}
}
props.fontVariationAxes = tp.core.types.compound(variationAxes);
} else {
delete props.fontVariationAxes;
}
} else {
console.error('js::layer::selectFont', 'could not select font',{selectedFont, selectedFontPath, selectedFontIndex, selectedHash});
}
};
this.afterUpdateTheatrePropsCallback = () => {};
let updateTheatrePropsTimeout = false;
this.updateTheatreProps = () => {
updateTheatrePropsTimeout = true;
return new Promise((resolve) => {
// NOTE: stupid hack, seems that theatrejs tries to be too smart
// detecting if reconfiguring the object is necessary.
// file bug report and test in future versions.
//
// this overcomplicates some of our code though.. urgh..
// btw, we need a dummy property
// this does not work
//tp.changeObject(this.id(), {});
//tp.changeObject(this.id(), {dummy:true});
//updateTheatrePropsTimeout = setTimeout(() => {
tp.changeObject(this.id(), {dummy: true});
setTimeout(() => {
tp.changeObject(this.id(), props);
setTimeout(() => {
//updateTheatrePropsTimeout = false;
//this.afterUpdateTheatrePropsCallback();
this.findInjectPanel();
resolve();
}, 100);
}, 100);
//}, 100);
});
};
const getDefaultFont = (selectableFonts) => {
const defaultFonts = config.layer.defaultFonts;
for (let a = 0; a < config.layer.defaultFonts.length; a++) {
const defaultFont = defaultFonts[a];
const keys = Object.keys(selectableFonts);
for (let i = 0; i < keys.length; i++) {
const selectableFont = selectableFonts[keys[i]];
if (selectableFont.indexOf(defaultFont) >= 0) {
return selectableFont;
}
}
}
return fontsAndAxes[0].fontName;
};
this.updateFonts = (doUpdateTheatre = true) => {
return new Promise((resolve) => {
if (typeof props['fontFamily'] === 'undefined') {
const selectableFonts = {};
for (let i in fontsAndAxes) {
const name = fontsAndAxes[i].fontPath.indexOf('data') === 0 ?
fontsAndAxes[i].fontName : `user: ${fontsAndAxes[i].fontName}`;
selectableFonts[fontsAndAxes[i].fontPath] = name;
}
let selectedFont = undefined;
if (typeof this.props.fontFamily !== 'undefined') {
selectedFont = this.props.fontFamily;
} else if (typeof lastValues['fontFamily'] !== 'undefined') {
selectedFont = lastValues['fontFamily'];
} else {
selectedFont = getDefaultFont(selectableFonts);
}
if (typeof selectedFont !== 'undefined') {
this.selectFont(selectedFont);
} else {
console.error('js::layer::updateFonts could not select font', {
selectedFont,
newFontsAndAxes
});
}
} else {
this.selectFont(this.theatreObject.value.fontFamily);
}
if (doUpdateTheatre) {
this.updateTheatreProps().
then(() => {
resolve();
});
} else {
resolve();
}
});
}
const onValuesChange = (values) => {
if (values.dummy === true)
return;
window.isRenderDirty = true;
if (Object.keys(values).length > 1) {
values.text = values.text.trim();
if (this.values.width === 0) {
const artboard = window.getArtboard();
setTimeout(() => {
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props.width, artboard.theatreObject.value.width);
});
}, 100);
}
if ((lastValues.hasOwnProperty('fontFamily')
&& lastValues.fontFamily === values.fontFamily)
|| !values.hasOwnProperty('fontFamily')) {
// don't do anything if: last fontFamily is same as before
// or current fontFamily is not there
} else {
this.selectFont(values.fontFamily);
this.updateTheatreProps();
}
let p = this.values2cppProps(values);
if (p !== false) {
Module.setProps(p, this.id());
}
lastValues = values;
}
};
const createDomElement = (name = getUuid()) => {
domElement = htmlToElement(`
<div id="${name}" class="textObject">
<div class="header">
<div class="move">move</div>
<div class="duplicate">duplicate</div>
<div class="delete">delete</div>
</div>
</div>
`.trim());
const content = document.querySelector('#content');
content.appendChild(domElement);
props.text = domElement.querySelector('.original.text').innerHTML;
};
let boundingBoxInterval = false;
const updateBoundingBoxDiv = (boundingBox = false) => {
const boundingBoxDivId = `boundingBox-${this.id()}`;
if (boundingBox === false) {
boundingBox = Module.getBoundingBox(this.id());
}
const boundingBoxDiv = document.getElementById(boundingBoxDivId);
const artboard = window.getArtboard();
const zoom = artboard.theatreObject.value.zoom;
const dpiScale = window.devicePixelRatio ? 1.0 / window.devicePixelRatio : 1.0;
boundingBoxDiv.style.width = `${boundingBox.w * zoom * dpiScale}px`;
boundingBoxDiv.style.height = `${boundingBox.h * zoom * dpiScale}px`;
boundingBoxDiv.style.left = `${boundingBox.x * zoom * dpiScale}px`;
boundingBoxDiv.style.top = `${boundingBox.y * zoom * dpiScale}px`;
boundingBoxDiv.style.marginLeft = `${artboard.theatreObject.value.x * dpiScale}px`;
boundingBoxDiv.style.marginTop = `${artboard.theatreObject.value.y * dpiScale}px`;
};
const showBoundingBoxDivIfSelected = () => {
if (this.isSelected()) {
showBoundingBoxDiv();
}
};
const showBoundingBoxDiv = (boundingBox = false) => {
const boundingBoxDivId = `boundingBox-${this.id()}`;
//const layerBoxDivId = `layerBox-${this.id()}`;
if (document.querySelector(`#${boundingBoxDivId}`) === null) {
if (boundingBox === false) {
boundingBox = Module.getBoundingBox(this.id());
}
const artboard = window.getArtboard();
const boundingBoxDiv = document.createElement('div');
//const layerBoxDiv = document.createElement('div');
boundingBoxDiv.id = boundingBoxDivId;
boundingBoxDiv.style.position = 'fixed';
boundingBoxDiv.style.background = 'transparent';
boundingBoxDiv.style.border = '1px dashed dimgrey';
boundingBoxDiv.style.boxSizing = 'border-box';
//layerBoxDiv.id = layerBoxDivId;
//layerBoxDiv.style.position = 'fixed';
//layerBoxDiv.style.background = 'transparent';
//layerBoxDiv.style.border = '1px solid green';
//layerBoxDiv.style.boxSizing = 'border-box';
document.getElementById('body').append(boundingBoxDiv);
//document.getElementById('body').append(layerBoxDiv);
clearInterval(boundingBoxInterval);
boundingBoxInterval = setInterval(() => {
updateBoundingBoxDiv();
}, 60);
return boundingBoxDiv;
} else {
return document.querySelector(`#${boundingBoxDivId}`);
}
};
const hideBoundingBoxDiv = () => {
const bb = document.getElementById(`boundingBox-${this.id()}`);
if (bb !== null) {
bb.remove();
}
//document.getElementById(`layerBox-${this.id()}`).remove();
clearInterval(boundingBoxInterval);
boundingBoxInterval = false;
};
this.showBoundingBoxDiv = showBoundingBoxDiv;
this.hideBoundingBoxDiv = hideBoundingBoxDiv;
//public
this.valuesCorrector = (values) => {
if (!fontsHashMap.hasOwnProperty(values.fontFamily)) {
let f = window.project_fontsHashMap[values.fontFamily];
if (typeof f !== 'object') {
f = {};
}
if (!f.hasOwnProperty('fontName')) {
f.fontName = '<unknown font>';
}
if (!f.hasOwnProperty('fontPath')) {
f.fontPath = '<unknown path>';
}
alert(`the font ${f.fontName} at ${f.fontPath} with hash ${values.fontFamily} is not in the project. please upload it again. in the meantime it will be substituted with another font.\n\nAre you perhaps surfing in incognito mode? If yes, please disable incognito mode to use variable time. Incognito mode prevents saving of fontfiles in your browser. This is necessary, as otherwise we have no way of using custom fonts. Should you be worried about this or have questions, please ask us. We're happy to explain the technical background.`);
values.fontFamily = Object.keys(fontsHashMap)[0];
//delete values.fontFamilyName;
}
// add missing props
Object.keys(this.props).forEach((propKey) => {
if (!values.hasOwnProperty(propKey)) {
const prop = this.props[propKey];
if (typeof prop === 'number' ||
typeof prop === 'string' ||
typeof prop === 'boolean') {
values[propKey] = prop;
} else if (prop.hasOwnProperty('default')) {
values[propKey] = prop.default;
} else {
console.error('Layer::valuesCorrector', 'could not correct values', prop);
}
}
});
};
this.init = (initialValues = false) => {
return new Promise((resolve) => {
const promises = [];
promises.push(this.findInjectHierarchyPanelPromise());
const doUpdateTheatre = false;
this.updateFonts(doUpdateTheatre)
.then(() => {
this.theatreObject = tp.addObject(this.id(), this.props, this.onValuesChange);
if (typeof initialValues === 'object') {
this.theatreObject.initialValue = initialValues;
}
window.layerOrder.add(this.id());
setTimeout(() => {
const values = JSON.parse(JSON.stringify(this.theatreObject.value));
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props, values);
});
setTimeout(() => {
promises.push(this.findInjectPanelPromise);
Promise.allSettled(promises).then(() => {
resolve();
if (config.autoSave && window.isInitialized) {
tp.saveProject();
}
});
}, 1000);
}, 100);
});
});
};
let hierarchyPanelFinderTimeout = false;
this.findInjectHierarchyPanelPromise = () => {
return new Promise((resolve) => {
this.findInjectHierarchyPanel(resolve);
});
};
this.findInjectHierarchyPanel = (resolve = false) => {
const panel = getHierarchyPanelButton();
let doItAgain = true;
if (panel !== null) {
const mom = panel.querySelector('div');
const upButton = document.createElement('div');
const downButton = document.createElement('div');
const removeButton = document.createElement('div');
const duplicateButton = document.createElement('div');
upButton.classList.add('moveLayerButton');
downButton.classList.add('moveLayerButton');
removeButton.classList.add('removeLayerButton');
duplicateButton.classList.add('duplicateLayerButton');
upButton.innerHTML = `<img src="/web/assets/movelayerup.svg" />`;
downButton.innerHTML = `<img src="/web/assets/movelayerdown.svg" />`;
removeButton.innerHTML = `<img src="/web/assets/delete_layer.svg" />`;
duplicateButton.innerHTML = `<img src="/web/assets/duplicate_layer.svg" />`;
upButton.addEventListener('click', (clickEvent) => {
window.moveLayerUp(this.id());
});
downButton.addEventListener('click', (clickEvent) => {
window.moveLayerDown(this.id());
});
removeButton.addEventListener('click', (clickEvent) => {
window.deleteLayer(this.id());
removeButton.remove();
});
duplicateButton.addEventListener('click', (clickEvent) => {
window.duplicateLayer(this);
});
mom.append(duplicateButton);
mom.append(upButton);
mom.append(downButton);
mom.append(removeButton);
//window.layerOrder.add(this.id());
panel.addEventListener('mouseover', showBoundingBoxDiv);
panel.addEventListener('mouseout', hideBoundingBoxDiv);
doItAgain = false;
if (typeof resolve === 'function') {
resolve();
}
}
if (doItAgain) {
hierarchyPanelFinderTimeout = setTimeout(() => {
this.findInjectHierarchyPanel(resolve);
}, 30);
window.hierarchyPanelFinderTimeout = hierarchyPanelFinderTimeout;
}
};
let hierarchyPanelButton = null;
const getHierarchyPanelButton = () => {
if (hierarchyPanelButton === null) {
hierarchyPanelButton = tp.shadowRoot.querySelector(`.layerMover${this.id()}`);
}
return hierarchyPanelButton;
};
this.getHierarchyPanelButton = getHierarchyPanelButton;
const togglePanelProp = (propName, active = null, updateTheatre = true) => {
if (propName === 'width') {
if (active === null) {
active = this.theatreObject.value.width <= 0;
}
const panelProp= tp.getPanelPropTitle(propName);
const panelPropMom = tp.getPanelPropContainer(panelProp);
const newValue = active ? getArtboard().theatreObject.value.width : 0;
if (active) {
panelPropMom.style.display = 'flex';
tp.getPanel().querySelector('.textWrappingButton').classList.add('active');
} else {
tp.getPanel().querySelector('.textWrappingButton').classList.remove('active');
panelPropMom.style.display = 'none';
}
if (updateTheatre) {
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props[propName], newValue);
});
}
}
};
// phew.. so we don't want to get into typescript and react atm
// so we simply reshuffle and manipulate manually
// not pretty (possibly unstable as well), but it will do the job good enough
let panelFinderTimeout = false;
this.findInjectPanelPromise = () => {
return new Promise((resolve) => {
this.findInjectPanel(resolve);
});
};
this.findInjectPanel = (resolve = false) => {
if (tp.studio.selection.length === 0 || (tp.studio.selection.length > 0 && tp.studio.selection[0].address.objectKey !== this.id())) {
return;
} else {
const panel = tp.getPanel();
let doItAgain = true;
if (panel !== null) {
const idElement = panel.querySelector(`[title="mainSheet: default > ${this.id()}"]`);
if (idElement !== null) {
tp.setPanelClasses();
const panelControlsWrapper = tp.getPanel().querySelector('.panelControlsWrapper');
const panelOrder = config.layer.panelOrder;
const panelPropTitles = {};
const panelPropContainers = {};
const propKeys = Object.keys(this.theatreObject.value);
propKeys.forEach((propKey) => {
const panelPropTitle = tp.getPanelPropTitle(propKey);
if (panelPropTitle !== null) {
panelPropTitles[propKey] = panelPropTitle;
const panelPropContainer = tp.getPanelPropContainer(panelPropTitle);
if (panelPropContainer !== null) {
panelPropContainers[propKey] = panelPropContainer;
const order_index = panelOrder.indexOf(propKey);
panelPropContainer.style.order = order_index;
if (propKey === 'fontVariationAxes'
|| propKey === 'letterDelays') {
panelPropContainer
.classList
.add(`${propKey}ContWrapper`);
} else {
panelPropContainer
.classList
.add(`${propKey}Wrapper`);
}
} else {
console.log('Layer::findInjectPanel',
`cannot find panelProp ${propKey}`);
}
} else {
console.log('Layer::findInjectPanel',
`cannot find panelProp ${propKey}`);
}
});
// adjust label text from unfriendly to friendly
Object.keys(config.layer.friendlyNames).forEach((unfriendlyName) => {
const friendlyName = config.layer.friendlyNames[unfriendlyName];
if (panelPropTitles.hasOwnProperty(unfriendlyName) &&
friendlyName !== '') {
if (unfriendlyName === 'fontVariationAxes' ||
unfriendlyName === 'letterDelays') {
// NOTE: perhaps we can do same for all
// and there is no need for nested conditionals
[...tp.getPanelPropContainer(unfriendlyName).querySelectorAll('span')].forEach((e) => {
Object.keys(config.layer.friendlyNames).forEach((subUnfriendlyName) => {
const subFriendlyName = config.layer.friendlyNames[subUnfriendlyName];
if (e.innerHTML === subUnfriendlyName) {
e.innerHTML = subFriendlyName;
}
});
});
} else {
panelPropTitles[unfriendlyName].innerHTML = friendlyName;
}
}
});
if (panelPropTitles['x'] !== null && panelPropTitles['y'] !== null) {
{
const vte_buttons = panel.querySelectorAll('.vte_button');
window.vte_buttons = vte_buttons;
if (vte_buttons !== null) {
for (let i = 0; i < vte_buttons.length; i++) {
vte_buttons[i].remove();
}
}
}
const artboard = window.getArtboard();
// first get previous align buttons,
// if they are already there
// to not add multiple
const previousAlignButtonsHorizontal = panel.querySelector('.alignButtonsHorizontal');
let panelPropAlignButtonsHorizontal = null;
// then, check if the previous buttons are there,
// or have to be created
if (previousAlignButtonsHorizontal === null) { // x
const alignButtons = document.createElement('div');
panelPropAlignButtonsHorizontal = alignButtons;
const alignLeftButton = document.createElement('div');
const alignCenterButton = document.createElement('div');
const alignRightButton = document.createElement('div');
alignLeftButton.innerHTML = `<img src="/web/assets/align-left.svg" alt="alignLeft" />`;
alignCenterButton.innerHTML = `<img src="/web/assets/align-center-horizontal.svg" alt="alignCenter" />`;
alignRightButton.innerHTML = `<img src="/web/assets/align-right.svg" alt="alignRight" />`;
panelControlsWrapper.append(alignButtons);
const order_index = panelOrder.indexOf('alignButtonsHorizontal');
alignButtons.style.order = order_index;
alignButtons.classList.add('alignButtons');
alignButtons.classList.add('alignButtonsHorizontal');
alignButtons.append(alignLeftButton);
alignButtons.append(alignCenterButton);
alignButtons.append(alignRightButton);
alignLeftButton.addEventListener('click', (clickEvent) => {
const boundingBox = Module.getBoundingBox(this.id());
const x = this.theatreObject.value.x - boundingBox.x;
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props.x, x);
});
});
alignCenterButton.addEventListener('click', (clickEvent) => {
const boundingBox = Module.getBoundingBox(this.id());
const artboardWidth = artboard.theatreObject.value.width;
const move = (artboardWidth * 0.5) - (boundingBox.w * 0.5);
const x = (this.theatreObject.value.x - boundingBox.x) + move;
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props.x, x);
});
});
alignRightButton.addEventListener('click', (clickEvent) => {
const boundingBox = Module.getBoundingBox(this.id());
const artboardWidth = artboard.theatreObject.value.width;
const move = (artboardWidth - boundingBox.w);
const x = (this.theatreObject.value.x - boundingBox.x) + move;
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props.x, x);
});
});
}
const previousAlignButtonsVertical = panel.querySelector('.alignButtonsVertical');
let panelPropAlignButtonsVertical = null;
if (previousAlignButtonsVertical === null) { // y
const alignButtons = document.createElement('div');
panelPropAlignButtonsVertical = alignButtons;
const alignTopButton = document.createElement('div');
const alignCenterButton = document.createElement('div');
const alignBottomButton = document.createElement('div');
alignTopButton.innerHTML = `<img src="/web/assets/align-top.svg" alt="alignTop" />`;
alignCenterButton.innerHTML = `<img src="/web/assets/align-center-vertical.svg" alt="alignCenter" />`;
alignBottomButton.innerHTML = `<img src="/web/assets/align-bottom.svg" alt="alignBottom" />`;
panelControlsWrapper.append(alignButtons);
const order_index = panelOrder.indexOf('alignButtonsVertical');
alignButtons.style.order = order_index;
alignButtons.classList.add('alignButtons');
alignButtons.classList.add('alignButtonsVertical');
alignButtons.append(alignTopButton);
alignButtons.append(alignCenterButton);
alignButtons.append(alignBottomButton);
alignTopButton.addEventListener('click', (clickEvent) => {
const boundingBox = Module.getBoundingBox(this.id());
const y = this.theatreObject.value.y - boundingBox.y;
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props.y, y);
});
});
alignCenterButton.addEventListener('click', (clickEvent) => {
const boundingBox = Module.getBoundingBox(this.id());
const artboardHeight = artboard.theatreObject.value.height;
const move = (artboardHeight * 0.5) - (boundingBox.h * 0.5);
const y = (this.theatreObject.value.y - boundingBox.y) + move;
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props.y, y);
});
});
alignBottomButton.addEventListener('click', (clickEvent) => {
const boundingBox = Module.getBoundingBox(this.id());
const artboard = window.getArtboard();
const artboardHeight = artboard.theatreObject.value.height;
const move = (artboardHeight - boundingBox.h);
const y = (this.theatreObject.value.y - boundingBox.y) + move;
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props.y, y);
});
});
}
const alignButtonsHorizontal = panel.querySelector('.alignButtonsHorizontal');
const alignButtonsVertical = panel.querySelector('.alignButtonsVertical');
//panelControlsWrapper.add(alignButtonsHorizontal);
//panelControlsWrapper.add(alignButtonsVertical);
// first get previous textAlign buttons,
// if they are already there
// to not add multiple
const previousTextAlignButtonsHorizontal = panel.querySelector('.textAlignButtonsHorizontal');
let panelPropTextAlignButtonsHorizontal = null;
// then, check if the previous buttons are there,
// or have to be created
if (previousTextAlignButtonsHorizontal === null) { // x
const textAlignButtons = document.createElement('div');
panelPropTextAlignButtonsHorizontal = textAlignButtons;
const textAlignLeftButton = document.createElement('div');
const textAlignCenterButton = document.createElement('div');
const textAlignRightButton = document.createElement('div');
const textWrappingButton = document.createElement('div');
textAlignLeftButton.innerHTML = `<img src="/web/assets/align-text-left.svg" alt="textAlignLeft" />`;
textAlignCenterButton.innerHTML = `<img src="/web/assets/align-text-center.svg" alt="textAlignCenter" />`;
textAlignRightButton.innerHTML = `<img src="/web/assets/align-text-right.svg" alt="textAlignRight" />`;
textWrappingButton.innerHTML = `<img src="/web/assets/text-wrapping.svg" alt="textWrapping" />`;
const order_index = panelOrder.indexOf('textAlignButtonsHorizontal');
textAlignButtons.style.order = order_index;
panelControlsWrapper.append(textAlignButtons);
textAlignButtons.classList.add('textAlignButtons');
textAlignButtons.classList.add('textAlignButtonsHorizontal');
textWrappingButton.classList.add('textWrappingButton');
textAlignButtons.append(textAlignLeftButton);
textAlignButtons.append(textAlignCenterButton);
textAlignButtons.append(textAlignRightButton);
textAlignButtons.append(textWrappingButton);
textAlignLeftButton.addEventListener('click', (clickEvent) => {
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props.textAlignment, 0.0);
});
});
textAlignCenterButton.addEventListener('click', (clickEvent) => {
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props.textAlignment, 0.5);
});
});
textAlignRightButton.addEventListener('click', (clickEvent) => {
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props.textAlignment, 1.0);
});
});
textWrappingButton.addEventListener('click', (clickEvent) => {
togglePanelProp('width');
});
}
togglePanelProp('width', this.theatreObject.value.width > 0, false);
let bottomButtonsContainer = panel.querySelector('.bottomButtonsContainer');
if (bottomButtonsContainer === null) {
bottomButtonsContainer = document.createElement('div');
bottomButtonsContainer.classList.add("bottomButtonsContainer");
panel.append(bottomButtonsContainer);
}
if (bottomButtonsContainer.querySelector('.vte_button') === null) {
const addFontButton = document.createElement('div');
addFontButton.classList.add('vte_button');
addFontButton.style.cursor = 'pointer';
addFontButton.innerHTML = "add Font";
addFontButton.addEventListener('click', (clickEvent) => {
addUserFont();
});
bottomButtonsContainer.append(addFontButton);
}
doItAgain = false;
clearTimeout(panelFinderTimeout);
panelFinderTimeout = false;
if (sequenceEventBuffer.length > 0) {
sequenceEventBuffer.forEach((detail) => {
this.handleSequenceEvent(detail);
});
sequenceEventBuffer = [];
}
// convert nodelist to array with [...nodelist]
[...panel.querySelectorAll('input[type="checkbox"]')].forEach((e) => {
e.addEventListener("keydown", (event) => {
if (event.keyCode === 32) {
event.preventDefault();
e.blur();
return;
}
});
e.addEventListener("change", () => {
e.blur();
});
const label = document.createElement(`label`);
label.innerHTML = "";
e.after(label);
});
panel.addEventListener("mouseover", showBoundingBoxDivIfSelected);
panel.addEventListener("mouseleave", hideBoundingBoxDiv);
injectedPanel = true;
const detail = {titles: Object.keys(panelPropTitles), containers: Object.keys(panelPropContainers)};
const e = new CustomEvent('injected', {detail});
tp.getPanel().dispatchEvent(e);
if (typeof resolve === 'function') {
resolve();
}
}
}
}
if (doItAgain) {
clearTimeout(panelFinderTimeout);
panelFinderTimeout = setTimeout(() => {
this.findInjectPanel(resolve);
}, 30);
window.panelFinderTimeout = panelFinderTimeout;
}
}
};
const letterDelays = {};
this.letterDelays = letterDelays;
const isInLetterDelays = (prop) => {
if (prop.length === 2 && prop[0] === 'fontVariationAxes') {
return letterDelays.hasOwnProperty('fontVariationAxes')
&& letterDelays.fontVariationAxes.props.hasOwnProperty(prop[1]);
}
return letterDelays.hasOwnProperty(prop[0]);
};
const addUserFont = () => {
return new Promise((resolve, reject) => {
uploadFile('font')
.then((fontFile) => {
moduleFS
.save(fontFile)
.then(() => {
getFontsAndAxes()
.then((newFontsAndAxes) => {
// let's select the new uploaded font
if (newFontsAndAxes.length > 0) {
const path = newFontsAndAxes[0].fontPath;
this.selectFont(path);
// we need to update theatreprops
// a bit awkwardly twice
//
// first like this
this.updateTheatreProps()
.then(() => {
// and again with a transaction
const hash = props.fontFamily.default;
tp.studio.transaction(({
set
}) => {
set(this.theatreObject.props.fontFamily, hash);
});
this.findInjectPanel();
resolve();
});
} else {
reject();
}
});
});
});
});
};
this.handleSequenceEvent = (detail, updateTheatre = true) => {
return new Promise((resolve) => {
tp.friendlySequenceNames();
if (detail.prop.length < 1 || detail.prop.length > 2 ||
config.layer.letterDelayProps.indexOf(detail.prop[0]) < 0) {
// not letterdelayable prop
return;
}
let somethingChanged = false;
const present = isInLetterDelays(detail.prop);
if (detail.sequenced && !present) {
if (detail.prop.length === 1) {
letterDelays[detail.prop[0]] = tp.core.types.number(0);
} else if (detail.prop[0] === 'fontVariationAxes') {
if (letterDelays.hasOwnProperty('fontVariationAxes')) {
let props = letterDelays.fontVariationAxes.props;
props[detail.prop[1]] = tp.core.types.number(0);
delete letterDelays.fontVariationAxes;
letterDelays.fontVariationAxes = tp.core.types.compound(props);
} else {
const props = {};
props[`${detail.prop[1]}`] = tp.core.types.number(0);
letterDelays.fontVariationAxes = tp.core.types.compound(props);
}
}
somethingChanged = true;
} else if (!detail.sequenced && present) {
if (detail.prop.length === 1) {
delete letterDelays[detail.prop[0]];
} else if (detail.prop[0] === 'fontVariationAxes') {
if (Object.keys(letterDelays.fontVariationAxes.props).length === 1) {
delete letterDelays.fontVariationAxes;
} else {
let props = letterDelays.fontVariationAxes.props;
delete props[detail.prop[1]];
delete letterDelays.fontVariationAxes;
letterDelays.fontVariationAxes = tp.core.types.compound(props);
}
}
somethingChanged = true;
}
if (somethingChanged) {
if (Object.keys(letterDelays).length > 0) {
props.letterDelays = tp.core.types.compound(letterDelays);
} else {
if (props.hasOwnProperty('letterDelays')) {
delete props['letterDelays'];
}
}
if (updateTheatre) {
this.updateTheatreProps().then(() => {
resolve(updateTheatre);
this.findInjectPanel();
});
} else {
resolve(updateTheatre);
this.findInjectPanel();
}
} else {
resolve(updateTheatre);
}
});
};
this.receivePanelEvent = (e) => {
// we need to workaround through a theatrejs bug to update the props of an object
// by setting a dummy value first
if (updateTheatrePropsTimeout !== false) {
this.afterUpdateTheatrePropsCallback = () => {
this.findInjectPanel();
};
} else {
setTimeout(() => {
this.findInjectPanel();
//this.receivePanelEvent();
}, 30);
}
};
this.receiveSequenceEvent = (detail) => {
if (!injectedPanel) {
let alreadyExists = false;
sequenceEventBuffer.forEach((e,i) => {
if (e.prop[0] === detail.prop[0]) {
alreadyExists = true;
sequenceEventBuffer[i].sequenced = detail.sequenced;
}
});
if (!alreadyExists) {
sequenceEventBuffer.push(detail);
}
} else {
this.handleSequenceEvent(detail);
}
};
this.props = props;
this.values = lastValues;
this.values2cppProps = (values = this.values) => {
let cppProps = JSON.parse(JSON.stringify(values)); // funny javascript
// NOTE: https://stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object?rq=1
// possibly more elegant, but then.. who cares
if (Object.keys(cppProps).length < 2) {
console.log('js::layer::values2cppProps', 'cannot set cppProps with dummy', cppProps);
return false;
}
if (typeof cppProps['fontFamily'] !== 'undefined') {
cppProps.fontPath = fontsHashMap[cppProps['fontFamily']].fontPath;
} else {
console.error('js::layer::values2cppProps', 'no fontFamily found');
return false;
}
if (typeof cppProps['textAlignment'] === 'undefined') {
cppProps['textAlignment'] = 0; // could get default
}
delete cppProps.fontFamily;
cppProps.fontVariations = new Module.FontVariations();
if (cppProps.hasOwnProperty('fontVariationAxes')) {
for (let key in cppProps['fontVariationAxes']) {
const fv = new Module.FontVariation();
fv.name = key;
fv.value = cppProps['fontVariationAxes'][key];
cppProps.fontVariations.push_back(fv);
}
}
delete cppProps.fontVariationAxes;
if (cppProps.color.hasOwnProperty('r')) {
cppProps.color = [
cppProps.color.r,
cppProps.color.g,
cppProps.color.b,
cppProps.color.a
];
} else if (cppProps.color.default.hasOwnProperty('r')) {
cppProps.color = [
cppProps.color.default.r,
cppProps.color.default.g,
cppProps.color.default.b,
cppProps.color.default.a
];
} else {
console.error('js::layer::values2cppProps', 'color could not be translated');
}
cppProps.transformOrigin = transformOriginToInt[cppProps.transformOrigin];
// milliseconds to seconds for letterDelay
//cppProps.letterDelay = cppProps.letterDelay * 0.001;
// actually, on second thought, we don't use letterDelay anymore
// but individual letterDelays. let's have a dummy number
cppProps.letterDelay = 42;
if (cppProps.hasOwnProperty('letterDelays')) {
const ld = new Module.map$string$$float$();
const keys = Object.keys(cppProps.letterDelays);
for (let i = 0; i < keys.length; i++) {
if (keys[i] === 'fontVariationAxes') {
const vKeys = Object.keys(cppProps.letterDelays[keys[i]]);
for (let v = 0; v < vKeys.length; v++) {
ld.set(`${keys[i]}_${vKeys[v]}`, cppProps.letterDelays[keys[i]][vKeys[v]] * 0.001);
}
} else {
ld.set(keys[i], cppProps.letterDelays[keys[i]] * 0.001);
}
}
cppProps.letterDelays = ld;
} else {
cppProps.letterDelays = new Module.map$string$$float$();
}
if (!cppProps.hasOwnProperty('fontPath') || cppProps.fontPath === '') {
console.error('js::layer::values2cppProps', 'no fontPath found');
return false;
}
return cppProps;
};
this.onValuesChange = onValuesChange;
this.prepareForDepartureFromThisBeautifulExperience = () => {
this.hide();
hideBoundingBoxDiv();
clearTimeout(panelFinderTimeout);
clearTimeout(hierarchyPanelFinderTimeout);
};
this.isSelected = () => {
const panel = getHierarchyPanelButton();
if (panel === null) {
return false;
} else {
const notSelected = panel.querySelector('.not-selected');
return !notSelected;
}
};
this.select = () => {
const panel = getHierarchyPanelButton();
const selectables = panel.querySelector('.not-selected');
if (selectables !== null && typeof selectables.dispatchEvent === 'function') {
var clickEvent = new MouseEvent("click", {
"view": window,
"bubbles": true,
"cancelable": false
});
selectables.dispatchEvent(clickEvent);
} else {
window.debugElement = panel;
}
};
this.hide = () => {
{
const panel = tp.getPanel();
panel.removeEventListener('mouseover', showBoundingBoxDivIfSelected);
panel.removeEventListener('mouseleave', hideBoundingBoxDiv);
}
{
const panel = getHierarchyPanelButton();
panel.removeEventListener('mouseover', showBoundingBoxDiv);
panel.removeEventListener('mouseout', hideBoundingBoxDiv);
}
};
this.id = () => {
return layerID;
};
if (autoInit) {
this.init();
}
};
export {
Layer
}