js-draw
Version:
Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript.
287 lines (286 loc) • 14.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const Erase_1 = __importDefault(require("../../commands/Erase"));
const uniteCommands_1 = __importDefault(require("../../commands/uniteCommands"));
const BackgroundComponent_1 = __importStar(require("../../components/BackgroundComponent"));
const EditorImage_1 = require("../../image/EditorImage");
const math_1 = require("@js-draw/math");
const types_1 = require("../../types");
const constants_1 = require("../constants");
const makeColorInput_1 = __importDefault(require("./components/makeColorInput"));
const BaseWidget_1 = __importDefault(require("./BaseWidget"));
class DocumentPropertiesWidget extends BaseWidget_1.default {
constructor(editor, localizationTable) {
super(editor, 'document-properties-widget', localizationTable);
this.updateDropdownContent = () => { };
this.dropdownUpdateQueued = false;
// Make it possible to open the dropdown, even if this widget isn't selected.
this.container.classList.add('dropdownShowable');
this.editor.notifier.on(types_1.EditorEventType.UndoRedoStackUpdated, () => {
this.queueDropdownUpdate();
});
this.editor.image.notifier.on(EditorImage_1.EditorImageEventType.ExportViewportChanged, () => {
this.queueDropdownUpdate();
});
}
getTitle() {
return this.localizationTable.documentProperties;
}
createIcon() {
return this.editor.icons.makeConfigureDocumentIcon();
}
handleClick() {
this.setDropdownVisible(!this.isDropdownVisible());
this.queueDropdownUpdate();
}
queueDropdownUpdate() {
if (!this.dropdownUpdateQueued) {
requestAnimationFrame(() => this.updateDropdown());
this.dropdownUpdateQueued = true;
}
}
updateDropdown() {
this.dropdownUpdateQueued = false;
if (this.isDropdownVisible()) {
this.updateDropdownContent();
}
}
setBackgroundColor(color) {
this.editor.dispatch(this.editor.setBackgroundColor(color));
}
getBackgroundColor() {
return this.editor.estimateBackgroundColor();
}
removeBackgroundComponents() {
const previousBackgrounds = [];
for (const component of this.editor.image.getBackgroundComponents()) {
if (component instanceof BackgroundComponent_1.default) {
previousBackgrounds.push(component);
}
}
return new Erase_1.default(previousBackgrounds);
}
/** Replace existing background components with a background of the given type. */
setBackgroundType(backgroundType) {
const prevBackgroundColor = this.editor.estimateBackgroundColor();
const newBackground = new BackgroundComponent_1.default(backgroundType, prevBackgroundColor);
const addBackgroundCommand = this.editor.image.addComponent(newBackground);
return (0, uniteCommands_1.default)([this.removeBackgroundComponents(), addBackgroundCommand]);
}
/** Returns the type of the topmost background component */
getBackgroundType() {
const backgroundComponents = this.editor.image.getBackgroundComponents();
for (let i = backgroundComponents.length - 1; i >= 0; i--) {
const component = backgroundComponents[i];
if (component instanceof BackgroundComponent_1.default) {
return component.getBackgroundType();
}
}
return BackgroundComponent_1.BackgroundType.None;
}
updateImportExportRectSize(size) {
const filterDimension = (dim) => {
if (dim !== undefined && (!isFinite(dim) || dim <= 0)) {
dim = 100;
}
return dim;
};
const width = filterDimension(size.width);
const height = filterDimension(size.height);
const currentRect = this.editor.getImportExportRect();
const newRect = new math_1.Rect2(currentRect.x, currentRect.y, width ?? currentRect.w, height ?? currentRect.h);
this.editor.dispatch(this.editor.image.setImportExportRect(newRect));
this.editor.queueRerender();
}
getHelpText() {
return this.localizationTable.pageDropdown__baseHelpText;
}
fillDropdown(dropdown, helpDisplay) {
const container = document.createElement('div');
container.classList.add(`${constants_1.toolbarCSSPrefix}spacedList`, `${constants_1.toolbarCSSPrefix}nonbutton-controls-main-list`, `${constants_1.toolbarCSSPrefix}document-properties-widget`);
// Background color input
const makeBackgroundColorInput = () => {
const backgroundColorRow = document.createElement('div');
const backgroundColorLabel = document.createElement('label');
backgroundColorLabel.innerText = this.localizationTable.backgroundColor;
const { input: colorInput, container: backgroundColorInputContainer, setValue: setBgColorInputValue, registerWithHelpTextDisplay: registerHelpForInputs, } = (0, makeColorInput_1.default)(this.editor, (color) => {
if (!color.eq(this.getBackgroundColor())) {
this.setBackgroundColor(color);
}
});
colorInput.id = `${constants_1.toolbarCSSPrefix}docPropertiesColorInput-${DocumentPropertiesWidget.idCounter++}`;
backgroundColorLabel.htmlFor = colorInput.id;
backgroundColorRow.replaceChildren(backgroundColorLabel, backgroundColorInputContainer);
const registerWithHelp = (helpDisplay) => {
if (!helpDisplay) {
return;
}
helpDisplay?.registerTextHelpForElement(backgroundColorRow, this.localizationTable.pageDropdown__backgroundColorHelpText);
registerHelpForInputs(helpDisplay);
};
return { setBgColorInputValue, backgroundColorRow, registerWithHelp };
};
const { backgroundColorRow, setBgColorInputValue, registerWithHelp: registerBackgroundRowWithHelp, } = makeBackgroundColorInput();
const makeCheckboxRow = (labelText, onChange) => {
const rowContainer = document.createElement('div');
const labelElement = document.createElement('label');
const checkboxElement = document.createElement('input');
checkboxElement.id = `${constants_1.toolbarCSSPrefix}docPropertiesCheckbox-${DocumentPropertiesWidget.idCounter++}`;
labelElement.htmlFor = checkboxElement.id;
checkboxElement.type = 'checkbox';
labelElement.innerText = labelText;
checkboxElement.oninput = () => {
onChange(checkboxElement.checked);
};
rowContainer.replaceChildren(labelElement, checkboxElement);
return { container: rowContainer, checkbox: checkboxElement };
};
// Background style selector
const { container: useGridRow, checkbox: useGridCheckbox } = makeCheckboxRow(this.localizationTable.useGridOption, (checked) => {
const prevBackgroundType = this.getBackgroundType();
const wasGrid = prevBackgroundType === BackgroundComponent_1.BackgroundType.Grid;
if (wasGrid === checked) {
// Already the requested background type.
return;
}
let newBackgroundType = BackgroundComponent_1.BackgroundType.SolidColor;
if (checked) {
newBackgroundType = BackgroundComponent_1.BackgroundType.Grid;
}
this.editor.dispatch(this.setBackgroundType(newBackgroundType));
});
// Adds a width/height input
const addDimensionRow = (labelContent, onChange) => {
const row = document.createElement('div');
const label = document.createElement('label');
const input = document.createElement('input');
label.innerText = labelContent;
input.type = 'number';
input.min = '0';
input.id = `${constants_1.toolbarCSSPrefix}docPropertiesDimensionRow-${DocumentPropertiesWidget.idCounter++}`;
label.htmlFor = input.id;
input.style.flexGrow = '2';
input.style.width = '25px';
input.oninput = () => {
onChange(parseFloat(input.value));
};
row.classList.add('js-draw-size-input-row');
row.replaceChildren(label, input);
return {
setValue: (value) => {
// Slightly improve the case where the user tries to change the
// first digit of a dimension like 600.
//
// As changing the value also gives the image zero size (which is unsupported,
// .setValue is called immediately). We work around this by trying to select
// the added/changed digits.
//
// See https://github.com/personalizedrefrigerator/js-draw/issues/58.
if (document.activeElement === input && input.value.match(/^0*$/)) {
// We need to switch to type="text" and back to type="number" because
// number inputs don't support selection.
//
// See https://stackoverflow.com/q/22381837
const originalValue = input.value;
input.type = 'text';
input.value = value.toString();
// Select the added digits
const lengthToSelect = Math.max(1, input.value.length - originalValue.length);
input.setSelectionRange(0, lengthToSelect);
input.type = 'number';
}
else {
input.value = value.toString();
}
},
setIsAutomaticSize: (automatic) => {
input.disabled = automatic;
const automaticSizeClass = 'size-input-row--automatic-size';
if (automatic) {
row.classList.add(automaticSizeClass);
}
else {
row.classList.remove(automaticSizeClass);
}
},
element: row,
};
};
const imageWidthRow = addDimensionRow(this.localizationTable.imageWidthOption, (value) => {
this.updateImportExportRectSize({ width: value });
});
const imageHeightRow = addDimensionRow(this.localizationTable.imageHeightOption, (value) => {
this.updateImportExportRectSize({ height: value });
});
// The autoresize checkbox
const { container: auroresizeRow, checkbox: autoresizeCheckbox } = makeCheckboxRow(this.localizationTable.enableAutoresizeOption, (checked) => {
const image = this.editor.image;
this.editor.dispatch(image.setAutoresizeEnabled(checked));
});
// The "About..." button
const aboutButton = document.createElement('button');
aboutButton.classList.add('about-button');
aboutButton.innerText = this.localizationTable.about;
aboutButton.onclick = () => {
this.editor.showAboutDialog();
};
// Add help text
registerBackgroundRowWithHelp(helpDisplay);
helpDisplay?.registerTextHelpForElement(useGridRow, this.localizationTable.pageDropdown__gridCheckboxHelpText);
helpDisplay?.registerTextHelpForElement(auroresizeRow, this.localizationTable.pageDropdown__autoresizeCheckboxHelpText);
helpDisplay?.registerTextHelpForElement(aboutButton, this.localizationTable.pageDropdown__aboutButtonHelpText);
this.updateDropdownContent = () => {
setBgColorInputValue(this.getBackgroundColor());
const autoresize = this.editor.image.getAutoresizeEnabled();
const importExportRect = this.editor.getImportExportRect();
imageWidthRow.setValue(importExportRect.width);
imageHeightRow.setValue(importExportRect.height);
autoresizeCheckbox.checked = autoresize;
imageWidthRow.setIsAutomaticSize(autoresize);
imageHeightRow.setIsAutomaticSize(autoresize);
useGridCheckbox.checked = this.getBackgroundType() === BackgroundComponent_1.BackgroundType.Grid;
};
this.updateDropdownContent();
container.replaceChildren(backgroundColorRow, useGridRow, imageWidthRow.element, imageHeightRow.element, auroresizeRow, aboutButton);
dropdown.replaceChildren(container);
return true;
}
}
DocumentPropertiesWidget.idCounter = 0;
exports.default = DocumentPropertiesWidget;