UNPKG

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
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); } } }