UNPKG

js-draw

Version:

Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript.

185 lines (184 loc) 7.16 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const waitForAll_1 = __importDefault(require("../util/waitForAll")); const Command_1 = __importDefault(require("./Command")); const SerializableCommand_1 = __importDefault(require("./SerializableCommand")); class NonSerializableUnion extends Command_1.default { constructor(commands, applyChunkSize, descriptionOverride) { super(); this.commands = commands; this.applyChunkSize = applyChunkSize; this.descriptionOverride = descriptionOverride; } apply(editor) { if (this.applyChunkSize === undefined) { const results = this.commands.map((cmd) => cmd.apply(editor)); return (0, waitForAll_1.default)(results); } else { return editor.asyncApplyCommands(this.commands, this.applyChunkSize); } } unapply(editor) { const commands = [...this.commands]; commands.reverse(); if (this.applyChunkSize === undefined) { const results = commands.map((cmd) => cmd.unapply(editor)); return (0, waitForAll_1.default)(results); } else { return editor.asyncUnapplyCommands(commands, this.applyChunkSize, false); } } onDrop(editor) { this.commands.forEach((command) => command.onDrop(editor)); } description(editor, localizationTable) { if (this.descriptionOverride) { return this.descriptionOverride; } const descriptions = []; let lastDescription = null; let duplicateDescriptionCount = 0; let handledCommandCount = 0; for (const part of this.commands) { const description = part.description(editor, localizationTable); if (description !== lastDescription && lastDescription !== null) { descriptions.push(localizationTable.unionOf(lastDescription, duplicateDescriptionCount)); lastDescription = null; duplicateDescriptionCount = 0; } duplicateDescriptionCount++; handledCommandCount++; lastDescription ??= description; // Long descriptions aren't very useful to the user. const maxDescriptionLength = 12; if (descriptions.length > maxDescriptionLength) { break; } } if (duplicateDescriptionCount > 1) { descriptions.push(localizationTable.unionOf(lastDescription, duplicateDescriptionCount)); } else if (duplicateDescriptionCount === 1) { descriptions.push(lastDescription); } if (handledCommandCount < this.commands.length) { descriptions.push(localizationTable.andNMoreCommands(this.commands.length - handledCommandCount)); } return descriptions.join(', '); } } class SerializableUnion extends SerializableCommand_1.default { constructor(commands, applyChunkSize, descriptionOverride) { super('union'); this.commands = commands; this.applyChunkSize = applyChunkSize; this.descriptionOverride = descriptionOverride; this.nonserializableCommand = new NonSerializableUnion(commands, applyChunkSize, descriptionOverride); } serializeToJSON() { if (this.serializedData) { return this.serializedData; } return { applyChunkSize: this.applyChunkSize, data: this.commands.map((command) => command.serialize()), description: this.descriptionOverride, }; } apply(editor) { // Cache this' serialized form -- applying this may change how commands serialize. this.serializedData = this.serializeToJSON(); return this.nonserializableCommand.apply(editor); } unapply(editor) { return this.nonserializableCommand.unapply(editor); } onDrop(editor) { this.nonserializableCommand.onDrop(editor); } description(editor, localizationTable) { return this.nonserializableCommand.description(editor, localizationTable); } } /** * Creates a single command from `commands`. This is useful when undoing should undo *all* commands * in `commands` at once, rather than one at a time. * * @example * * ```ts,runnable * import { Editor, pathToRenderable, Stroke, uniteCommands } from 'js-draw'; * import { Path, Color4 } from '@js-draw/math'; * * const editor = new Editor(document.body); * editor.addToolbar(); * * // Create strokes! * const strokes = []; * for (let i = 0; i < 10; i++) { * const renderablePath = pathToRenderable( * Path.fromString(`M0,${i * 10} L100,100 L300,30 z`), * { fill: Color4.transparent, stroke: { color: Color4.red, width: 1, } } * ); * strokes.push(new Stroke([ renderablePath ])); * } * * // Convert to commands * const addStrokesCommands = strokes.map(stroke => editor.image.addElement(stroke)); * * // Apply all as a single undoable command (try applying each in a loop instead!) * await editor.dispatch(uniteCommands(addStrokesCommands)); * * // The second parameter to uniteCommands is for very large numbers of commands, when * // applying them shouldn't be done all at once (which would block the UI). * * // The second parameter to uniteCommands is for very large numbers of commands, when * // applying them shouldn't be done all at once (which would block the UI). * ``` */ const uniteCommands = (commands, options) => { let allSerializable = true; for (const command of commands) { if (!(command instanceof SerializableCommand_1.default)) { allSerializable = false; break; } } let applyChunkSize; let description; if (typeof options === 'number') { applyChunkSize = options; } else { applyChunkSize = options?.applyChunkSize; description = options?.description; } if (!allSerializable) { return new NonSerializableUnion(commands, applyChunkSize, description); } else { const castedCommands = commands; return new SerializableUnion(castedCommands, applyChunkSize, description); } }; SerializableCommand_1.default.register('union', (data, editor) => { if (typeof data.data.length !== 'number') { throw new Error('Unions of commands must serialize to lists of serialization data.'); } const applyChunkSize = data.applyChunkSize; if (typeof applyChunkSize !== 'number' && applyChunkSize !== undefined) { throw new Error('serialized applyChunkSize is neither undefined nor a number.'); } const description = typeof data.description === 'string' ? data.description : undefined; const commands = []; for (const part of data.data) { commands.push(SerializableCommand_1.default.deserialize(part, editor)); } return uniteCommands(commands, { applyChunkSize, description }); }); exports.default = uniteCommands;