js-draw
Version:
Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript.
235 lines (234 loc) • 9.84 kB
JavaScript
import { Color4 } from '@js-draw/math';
import { isRestylableComponent } from '../../components/RestylableComponent.mjs';
import uniteCommands from '../../commands/uniteCommands.mjs';
import { SelectionMode } from '../../tools/SelectionTool/SelectionTool.mjs';
import { EditorEventType } from '../../types.mjs';
import makeColorInput from './components/makeColorInput.mjs';
import BaseToolWidget from './BaseToolWidget.mjs';
import { resizeImageToSelectionKeyboardShortcut } from './keybindings.mjs';
import makeSeparator from './components/makeSeparator.mjs';
import { toolbarCSSPrefix } from '../constants.mjs';
import BaseWidget from './BaseWidget.mjs';
import makeButtonGrid from './components/makeButtonGrid.mjs';
import { MutableReactiveValue } from '../../util/ReactiveValue.mjs';
const makeFormatMenu = (editor, selectionTool, localizationTable) => {
const container = document.createElement('div');
container.classList.add('selection-format-menu', `${toolbarCSSPrefix}spacedList`, `${toolbarCSSPrefix}indentedList`);
const colorRow = document.createElement('div');
const colorLabel = document.createElement('label');
const colorInputControl = makeColorInput(editor, (color) => {
const selection = selectionTool.getSelection();
if (selection) {
const updateStyleCommands = [];
for (const elem of selection.getSelectedObjects()) {
if (isRestylableComponent(elem)) {
updateStyleCommands.push(elem.updateStyle({ color }));
}
}
const unitedCommand = uniteCommands(updateStyleCommands);
editor.dispatch(unitedCommand);
}
});
const { input: colorInput, container: colorInputContainer } = colorInputControl;
colorLabel.innerText = localizationTable.colorLabel;
const update = () => {
const selection = selectionTool.getSelection();
if (selection && selection.getSelectedItemCount() > 0) {
colorInput.disabled = false;
container.classList.remove('disabled');
const colors = [];
for (const elem of selection.getSelectedObjects()) {
if (isRestylableComponent(elem)) {
const color = elem.getStyle().color;
if (color) {
colors.push(color);
}
}
}
colorInputControl.setValue(Color4.average(colors));
}
else {
colorInput.disabled = true;
container.classList.add('disabled');
colorInputControl.setValue(Color4.transparent);
}
};
colorRow.replaceChildren(colorLabel, colorInputContainer);
container.replaceChildren(colorRow);
return {
addTo: (parent) => {
parent.appendChild(container);
},
update,
registerHelpText: (helpDisplay) => {
helpDisplay.registerTextHelpForElement(colorRow, localizationTable.selectionDropdown__changeColorHelpText);
colorInputControl.registerWithHelpTextDisplay(helpDisplay);
},
};
};
class LassoSelectToggle extends BaseWidget {
constructor(editor, tool, localizationTable) {
super(editor, 'selection-mode-toggle', localizationTable);
this.tool = tool;
editor.notifier.on(EditorEventType.ToolUpdated, (toolEvt) => {
if (toolEvt.kind === EditorEventType.ToolUpdated && toolEvt.tool === tool) {
this.setSelected(tool.modeValue.get() === SelectionMode.Lasso);
}
});
this.setSelected(false);
}
shouldAutoDisableInReadOnlyEditor() {
return false;
}
setModeFlag(enabled) {
this.tool.modeValue.set(enabled ? SelectionMode.Lasso : SelectionMode.Rectangle);
}
handleClick() {
this.setModeFlag(!this.isSelected());
}
getTitle() {
return this.localizationTable.selectionTool__lassoSelect;
}
createIcon() {
return this.editor.icons.makeSelectionIcon(SelectionMode.Lasso);
}
fillDropdown(_dropdown) {
return false;
}
getHelpText() {
return this.localizationTable.selectionTool__lassoSelect__help;
}
}
export default class SelectionToolWidget extends BaseToolWidget {
constructor(editor, tool, localization) {
super(editor, tool, 'selection-tool-widget', localization);
this.tool = tool;
this.updateFormatMenu = () => { };
this.addSubWidget(new LassoSelectToggle(editor, tool, this.localizationTable));
const hasSelection = () => {
const selection = this.tool.getSelection();
return !!selection && selection.getSelectedItemCount() > 0;
};
this.hasSelectionValue = MutableReactiveValue.fromInitialValue(hasSelection());
// Enable/disable actions based on whether items are selected
this.editor.notifier.on(EditorEventType.ToolUpdated, (toolEvt) => {
if (toolEvt.kind !== EditorEventType.ToolUpdated) {
throw new Error('Invalid event type!');
}
if (toolEvt.tool === this.tool) {
this.hasSelectionValue.set(hasSelection());
this.updateFormatMenu();
}
});
tool.modeValue.onUpdate(() => {
this.updateIcon();
});
}
resizeImageToSelection() {
const selection = this.tool.getSelection();
if (selection) {
this.editor.dispatch(this.editor.setImportExportRect(selection.region));
}
}
onKeyPress(event) {
const shortcuts = this.editor.shortcuts;
// Resize image to selection:
// Other keys are handled directly by the selection tool.
if (shortcuts.matchesShortcut(resizeImageToSelectionKeyboardShortcut, event)) {
this.resizeImageToSelection();
return true;
}
// If we didn't handle the event, allow the superclass to handle it.
if (super.onKeyPress(event)) {
return true;
}
return false;
}
getTitle() {
return this.localizationTable.select;
}
createIcon() {
return this.editor.icons.makeSelectionIcon(this.tool.modeValue.get());
}
getHelpText() {
return this.localizationTable.selectionDropdown__baseHelpText;
}
createSelectionActions(helpDisplay) {
const icons = this.editor.icons;
const grid = makeButtonGrid([
{
icon: () => icons.makeDeleteSelectionIcon(),
label: this.localizationTable.deleteSelection,
onCreated: (button) => {
helpDisplay?.registerTextHelpForElement(button, this.localizationTable.selectionDropdown__deleteHelpText);
},
onClick: () => {
const selection = this.tool.getSelection();
this.editor.dispatch(selection.deleteSelectedObjects());
this.tool.clearSelection();
},
enabled: this.hasSelectionValue,
},
{
icon: () => icons.makeDuplicateSelectionIcon(),
label: this.localizationTable.duplicateSelection,
onCreated: (button) => {
helpDisplay?.registerTextHelpForElement(button, this.localizationTable.selectionDropdown__duplicateHelpText);
},
onClick: async () => {
const selection = this.tool.getSelection();
const command = await selection?.duplicateSelectedObjects();
if (command) {
this.editor.dispatch(command);
}
},
enabled: this.hasSelectionValue,
},
{
icon: () => icons.makeResizeImageToSelectionIcon(),
label: this.localizationTable.resizeImageToSelection,
onCreated: (button) => {
helpDisplay?.registerTextHelpForElement(button, this.localizationTable.selectionDropdown__resizeToHelpText);
},
onClick: () => {
this.resizeImageToSelection();
},
enabled: this.hasSelectionValue,
},
], 3);
return { container: grid.container };
}
fillDropdown(dropdown, helpDisplay) {
super.fillDropdown(dropdown, helpDisplay);
const controlsContainer = document.createElement('div');
controlsContainer.classList.add(`${toolbarCSSPrefix}nonbutton-controls-main-list`);
dropdown.appendChild(controlsContainer);
// Actions (duplicate, delete, etc.)
makeSeparator().addTo(controlsContainer);
const actions = this.createSelectionActions(helpDisplay);
controlsContainer.appendChild(actions.container);
// Formatting
makeSeparator(this.localizationTable.reformatSelection).addTo(controlsContainer);
const formatMenu = makeFormatMenu(this.editor, this.tool, this.localizationTable);
formatMenu.addTo(controlsContainer);
this.updateFormatMenu = () => formatMenu.update();
if (helpDisplay) {
formatMenu.registerHelpText(helpDisplay);
}
formatMenu.update();
return true;
}
serializeState() {
return {
...super.serializeState(),
selectionMode: this.tool.modeValue.get(),
};
}
deserializeFrom(state) {
super.deserializeFrom(state);
const isValidSelectionMode = Object.values(SelectionMode).includes(state.selectionMode);
if (isValidSelectionMode) {
this.tool.modeValue.set(state.selectionMode);
}
}
}