js-draw 0.10.3 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/translation.yml +72 -0
- package/CHANGELOG.md +9 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +12 -3
- package/dist/src/Editor.js +72 -25
- package/dist/src/EditorImage.js +1 -1
- package/dist/src/SVGLoader.js +3 -2
- package/dist/src/components/AbstractComponent.d.ts +1 -0
- package/dist/src/components/AbstractComponent.js +15 -6
- package/dist/src/components/ImageComponent.d.ts +3 -0
- package/dist/src/components/ImageComponent.js +12 -1
- package/dist/src/localizations/es.js +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.js +9 -5
- package/dist/src/toolbar/HTMLToolbar.js +2 -1
- package/dist/src/toolbar/IconProvider.d.ts +1 -0
- package/dist/src/toolbar/IconProvider.js +7 -0
- package/dist/src/toolbar/localization.d.ts +8 -0
- package/dist/src/toolbar/localization.js +8 -0
- package/dist/src/toolbar/widgets/InsertImageWidget.d.ts +19 -0
- package/dist/src/toolbar/widgets/InsertImageWidget.js +169 -0
- package/dist/src/toolbar/widgets/lib.d.ts +1 -0
- package/dist/src/toolbar/widgets/lib.js +1 -0
- package/dist/src/tools/Eraser.d.ts +1 -1
- package/dist/src/tools/Eraser.js +16 -18
- package/dist/src/tools/PanZoom.js +10 -0
- package/dist/src/tools/PasteHandler.js +1 -39
- package/dist/src/tools/SelectionTool/Selection.d.ts +2 -3
- package/dist/src/tools/SelectionTool/Selection.js +63 -26
- package/dist/src/tools/SelectionTool/SelectionTool.js +9 -0
- package/dist/src/util/fileToBase64.d.ts +3 -0
- package/dist/src/util/fileToBase64.js +13 -0
- package/dist/src/util/waitForTimeout.d.ts +2 -0
- package/dist/src/util/waitForTimeout.js +7 -0
- package/package.json +1 -1
- package/src/Editor.ts +90 -27
- package/src/EditorImage.ts +1 -1
- package/src/SVGLoader.ts +1 -0
- package/src/components/AbstractComponent.ts +18 -4
- package/src/components/ImageComponent.ts +15 -0
- package/src/localizations/es.ts +3 -0
- package/src/rendering/renderers/SVGRenderer.ts +6 -1
- package/src/toolbar/HTMLToolbar.ts +3 -1
- package/src/toolbar/IconProvider.ts +8 -0
- package/src/toolbar/localization.ts +19 -1
- package/src/toolbar/toolbar.css +2 -0
- package/src/toolbar/widgets/InsertImageWidget.css +44 -0
- package/src/toolbar/widgets/InsertImageWidget.ts +222 -0
- package/src/toolbar/widgets/lib.ts +2 -0
- package/src/tools/Eraser.ts +19 -15
- package/src/tools/PanZoom.test.ts +65 -0
- package/src/tools/PanZoom.ts +12 -0
- package/src/tools/PasteHandler.ts +2 -51
- package/src/tools/SelectionTool/Selection.ts +62 -22
- package/src/tools/SelectionTool/SelectionTool.ts +12 -0
- package/src/util/fileToBase64.ts +18 -0
- package/src/util/waitForTimeout.ts +9 -0
package/dist/src/Editor.d.ts
CHANGED
@@ -29,6 +29,7 @@ import Pointer from './Pointer';
|
|
29
29
|
import Rect2 from './math/Rect2';
|
30
30
|
import { EditorLocalization } from './localization';
|
31
31
|
import IconProvider from './toolbar/IconProvider';
|
32
|
+
import AbstractComponent from './components/AbstractComponent';
|
32
33
|
type HTMLPointerEventType = 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel';
|
33
34
|
type HTMLPointerEventFilter = (eventName: HTMLPointerEventType, event: PointerEvent) => boolean;
|
34
35
|
export interface EditorSettings {
|
@@ -157,7 +158,7 @@ export declare class Editor {
|
|
157
158
|
/** Adds event listners for keypresses to `elem` and forwards those events to the editor. */
|
158
159
|
handleKeyEventsFrom(elem: HTMLElement): void;
|
159
160
|
/** `apply` a command. `command` will be announced for accessibility. */
|
160
|
-
dispatch(command: Command, addToHistory?: boolean): void
|
161
|
+
dispatch(command: Command, addToHistory?: boolean): void | Promise<void>;
|
161
162
|
/**
|
162
163
|
* Dispatches a command without announcing it. By default, does not add to history.
|
163
164
|
* Use this to show finalized commands that don't need to have `announceForAccessibility`
|
@@ -173,7 +174,7 @@ export declare class Editor {
|
|
173
174
|
* editor.dispatchNoAnnounce(editor.viewport.zoomTo(someRectangle), addToHistory);
|
174
175
|
* ```
|
175
176
|
*/
|
176
|
-
dispatchNoAnnounce(command: Command, addToHistory?: boolean): void
|
177
|
+
dispatchNoAnnounce(command: Command, addToHistory?: boolean): void | Promise<void>;
|
177
178
|
/**
|
178
179
|
* Apply a large transformation in chunks.
|
179
180
|
* If `apply` is `false`, the commands are unapplied.
|
@@ -185,8 +186,15 @@ export declare class Editor {
|
|
185
186
|
asyncUnapplyCommands(commands: Command[], chunkSize: number): Promise<void>;
|
186
187
|
private announceUndoCallback;
|
187
188
|
private announceRedoCallback;
|
189
|
+
private nextRerenderListeners;
|
188
190
|
private rerenderQueued;
|
189
|
-
|
191
|
+
/**
|
192
|
+
* Schedule a re-render for some time in the near future. Does not schedule an additional
|
193
|
+
* re-render if a re-render is already queued.
|
194
|
+
*
|
195
|
+
* @returns a promise that resolves when
|
196
|
+
*/
|
197
|
+
queueRerender(): Promise<void>;
|
190
198
|
rerender(showImageBounds?: boolean): void;
|
191
199
|
drawWetInk(...path: RenderablePathSpec[]): void;
|
192
200
|
clearWetInk(): void;
|
@@ -197,6 +205,7 @@ export declare class Editor {
|
|
197
205
|
addStyleSheet(content: string): HTMLStyleElement;
|
198
206
|
sendKeyboardEvent(eventType: InputEvtType.KeyPressEvent | InputEvtType.KeyUpEvent, key: string, ctrlKey?: boolean, altKey?: boolean): void;
|
199
207
|
sendPenEvent(eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, point: Point2, allPointers?: Pointer[]): void;
|
208
|
+
addAndCenterComponents(components: AbstractComponent[], selectComponents?: boolean): Promise<void>;
|
200
209
|
toDataURL(format?: 'image/png' | 'image/jpeg' | 'image/webp'): string;
|
201
210
|
toSVG(): SVGElement;
|
202
211
|
loadFrom(loader: ImageLoader): Promise<void>;
|
package/dist/src/Editor.js
CHANGED
@@ -45,6 +45,9 @@ import IconProvider from './toolbar/IconProvider';
|
|
45
45
|
import { toRoundedString } from './math/rounding';
|
46
46
|
import CanvasRenderer from './rendering/renderers/CanvasRenderer';
|
47
47
|
import untilNextAnimationFrame from './util/untilNextAnimationFrame';
|
48
|
+
import fileToBase64 from './util/fileToBase64';
|
49
|
+
import uniteCommands from './commands/uniteCommands';
|
50
|
+
import SelectionTool from './tools/SelectionTool/SelectionTool';
|
48
51
|
// { @inheritDoc Editor! }
|
49
52
|
export class Editor {
|
50
53
|
/**
|
@@ -81,6 +84,8 @@ export class Editor {
|
|
81
84
|
this.announceRedoCallback = (command) => {
|
82
85
|
this.announceForAccessibility(this.localization.redoAnnouncement(command.description(this, this.localization)));
|
83
86
|
};
|
87
|
+
// Listeners to be called once at the end of the next re-render.
|
88
|
+
this.nextRerenderListeners = [];
|
84
89
|
this.rerenderQueued = false;
|
85
90
|
this.localization = Object.assign(Object.assign({}, getLocalizationTable()), settings.localization);
|
86
91
|
// Fill default settings.
|
@@ -372,18 +377,12 @@ export class Editor {
|
|
372
377
|
for (const file of clipboardData.files) {
|
373
378
|
const fileType = file.type.toLowerCase();
|
374
379
|
if (fileType === 'image/png' || fileType === 'image/jpg') {
|
375
|
-
const reader = new FileReader();
|
376
380
|
this.showLoadingWarning(0);
|
381
|
+
const onprogress = (evt) => {
|
382
|
+
this.showLoadingWarning(evt.loaded / evt.total);
|
383
|
+
};
|
377
384
|
try {
|
378
|
-
const data = yield
|
379
|
-
reader.onload = () => resolve(reader.result);
|
380
|
-
reader.onerror = reject;
|
381
|
-
reader.onabort = reject;
|
382
|
-
reader.onprogress = (evt) => {
|
383
|
-
this.showLoadingWarning(evt.loaded / evt.total);
|
384
|
-
};
|
385
|
-
reader.readAsDataURL(file);
|
386
|
-
});
|
385
|
+
const data = yield fileToBase64(file, onprogress);
|
387
386
|
if (data && this.toolController.dispatchInputEvent({
|
388
387
|
kind: InputEvtType.PasteEvent,
|
389
388
|
mime: fileType,
|
@@ -477,14 +476,9 @@ export class Editor {
|
|
477
476
|
}
|
478
477
|
/** `apply` a command. `command` will be announced for accessibility. */
|
479
478
|
dispatch(command, addToHistory = true) {
|
480
|
-
|
481
|
-
// .push applies [command] to this
|
482
|
-
this.history.push(command);
|
483
|
-
}
|
484
|
-
else {
|
485
|
-
command.apply(this);
|
486
|
-
}
|
479
|
+
const dispatchResult = this.dispatchNoAnnounce(command, addToHistory);
|
487
480
|
this.announceForAccessibility(command.description(this, this.localization));
|
481
|
+
return dispatchResult;
|
488
482
|
}
|
489
483
|
/**
|
490
484
|
* Dispatches a command without announcing it. By default, does not add to history.
|
@@ -503,11 +497,10 @@ export class Editor {
|
|
503
497
|
*/
|
504
498
|
dispatchNoAnnounce(command, addToHistory = false) {
|
505
499
|
if (addToHistory) {
|
506
|
-
|
507
|
-
|
508
|
-
else {
|
509
|
-
command.apply(this);
|
500
|
+
const apply = false; // Don't double-apply
|
501
|
+
this.history.push(command, apply);
|
510
502
|
}
|
503
|
+
return command.apply(this);
|
511
504
|
}
|
512
505
|
/**
|
513
506
|
* Apply a large transformation in chunks.
|
@@ -550,16 +543,27 @@ export class Editor {
|
|
550
543
|
asyncUnapplyCommands(commands, chunkSize) {
|
551
544
|
return this.asyncApplyOrUnapplyCommands(commands, false, chunkSize);
|
552
545
|
}
|
553
|
-
|
554
|
-
|
546
|
+
/**
|
547
|
+
* Schedule a re-render for some time in the near future. Does not schedule an additional
|
548
|
+
* re-render if a re-render is already queued.
|
549
|
+
*
|
550
|
+
* @returns a promise that resolves when
|
551
|
+
*/
|
555
552
|
queueRerender() {
|
556
553
|
if (!this.rerenderQueued) {
|
557
554
|
this.rerenderQueued = true;
|
558
555
|
requestAnimationFrame(() => {
|
559
|
-
|
560
|
-
|
556
|
+
// If .rerender was called manually, we might not need to
|
557
|
+
// re-render.
|
558
|
+
if (this.rerenderQueued) {
|
559
|
+
this.rerender();
|
560
|
+
this.rerenderQueued = false;
|
561
|
+
}
|
561
562
|
});
|
562
563
|
}
|
564
|
+
return new Promise(resolve => {
|
565
|
+
this.nextRerenderListeners.push(() => resolve());
|
566
|
+
});
|
563
567
|
}
|
564
568
|
rerender(showImageBounds = true) {
|
565
569
|
this.display.startRerender();
|
@@ -576,6 +580,8 @@ export class Editor {
|
|
576
580
|
renderer.drawRect(this.importExportViewport.visibleRect, exportRectStrokeWidth, exportRectFill);
|
577
581
|
}
|
578
582
|
this.rerenderQueued = false;
|
583
|
+
this.nextRerenderListeners.forEach(listener => listener());
|
584
|
+
this.nextRerenderListeners = [];
|
579
585
|
}
|
580
586
|
drawWetInk(...path) {
|
581
587
|
for (const part of path) {
|
@@ -628,6 +634,47 @@ export class Editor {
|
|
628
634
|
current: mainPointer,
|
629
635
|
});
|
630
636
|
}
|
637
|
+
addAndCenterComponents(components, selectComponents = true) {
|
638
|
+
return __awaiter(this, void 0, void 0, function* () {
|
639
|
+
let bbox = null;
|
640
|
+
for (const component of components) {
|
641
|
+
if (bbox) {
|
642
|
+
bbox = bbox.union(component.getBBox());
|
643
|
+
}
|
644
|
+
else {
|
645
|
+
bbox = component.getBBox();
|
646
|
+
}
|
647
|
+
}
|
648
|
+
if (!bbox) {
|
649
|
+
return;
|
650
|
+
}
|
651
|
+
// Find a transform that scales/moves bbox onto the screen.
|
652
|
+
const visibleRect = this.viewport.visibleRect;
|
653
|
+
const scaleRatioX = visibleRect.width / bbox.width;
|
654
|
+
const scaleRatioY = visibleRect.height / bbox.height;
|
655
|
+
let scaleRatio = scaleRatioX;
|
656
|
+
if (bbox.width * scaleRatio > visibleRect.width || bbox.height * scaleRatio > visibleRect.height) {
|
657
|
+
scaleRatio = scaleRatioY;
|
658
|
+
}
|
659
|
+
scaleRatio *= 2 / 3;
|
660
|
+
scaleRatio = Viewport.roundScaleRatio(scaleRatio);
|
661
|
+
const transfm = Mat33.translation(visibleRect.center.minus(bbox.center)).rightMul(Mat33.scaling2D(scaleRatio, bbox.center));
|
662
|
+
const commands = [];
|
663
|
+
for (const component of components) {
|
664
|
+
// To allow deserialization, we need to add first, then transform.
|
665
|
+
commands.push(EditorImage.addElement(component));
|
666
|
+
commands.push(component.transformBy(transfm));
|
667
|
+
}
|
668
|
+
const applyChunkSize = 100;
|
669
|
+
yield this.dispatch(uniteCommands(commands, applyChunkSize), true);
|
670
|
+
if (selectComponents) {
|
671
|
+
for (const selectionTool of this.toolController.getMatchingTools(SelectionTool)) {
|
672
|
+
selectionTool.setEnabled(true);
|
673
|
+
selectionTool.setSelection(components);
|
674
|
+
}
|
675
|
+
}
|
676
|
+
});
|
677
|
+
}
|
631
678
|
// Get a data URL (e.g. as produced by `HTMLCanvasElement::toDataURL`).
|
632
679
|
// If `format` is not `image/png`, a PNG image URL may still be returned (as in the
|
633
680
|
// case of `HTMLCanvasElement::toDataURL`).
|
package/dist/src/EditorImage.js
CHANGED
@@ -2,7 +2,7 @@ var _a;
|
|
2
2
|
import AbstractComponent from './components/AbstractComponent';
|
3
3
|
import Rect2 from './math/Rect2';
|
4
4
|
import SerializableCommand from './commands/SerializableCommand';
|
5
|
-
// @internal
|
5
|
+
// @internal Sort by z-index, low to high
|
6
6
|
export const sortLeavesByZIndex = (leaves) => {
|
7
7
|
leaves.sort((a, b) => a.getContent().getZIndex() - b.getContent().getZIndex());
|
8
8
|
};
|
package/dist/src/SVGLoader.js
CHANGED
@@ -233,16 +233,17 @@ export default class SVGLoader {
|
|
233
233
|
}
|
234
234
|
}
|
235
235
|
addImage(elem) {
|
236
|
-
var _a, _b;
|
236
|
+
var _a, _b, _c;
|
237
237
|
return __awaiter(this, void 0, void 0, function* () {
|
238
238
|
const image = new Image();
|
239
239
|
image.src = (_a = elem.getAttribute('xlink:href')) !== null && _a !== void 0 ? _a : elem.href.baseVal;
|
240
|
+
image.setAttribute('alt', (_b = elem.getAttribute('aria-label')) !== null && _b !== void 0 ? _b : '');
|
240
241
|
try {
|
241
242
|
const supportedAttrs = [];
|
242
243
|
const transform = this.getTransform(elem, supportedAttrs);
|
243
244
|
const imageElem = yield ImageComponent.fromImage(image, transform);
|
244
245
|
this.attachUnrecognisedAttrs(imageElem, elem, new Set(supportedAttrs), new Set(['transform']));
|
245
|
-
(
|
246
|
+
(_c = this.onAddComponent) === null || _c === void 0 ? void 0 : _c.call(this, imageElem);
|
246
247
|
}
|
247
248
|
catch (e) {
|
248
249
|
console.error('Error loading image:', e, '. Element: ', elem, '. Continuing...');
|
@@ -28,6 +28,7 @@ export default abstract class AbstractComponent {
|
|
28
28
|
protected abstract serializeToJSON(): any[] | Record<string, any> | number | string | null;
|
29
29
|
protected abstract applyTransformation(affineTransfm: Mat33): void;
|
30
30
|
transformBy(affineTransfm: Mat33): SerializableCommand;
|
31
|
+
setZIndex(newZIndex: number): SerializableCommand;
|
31
32
|
isSelectable(): boolean;
|
32
33
|
getProportionalRenderingTime(): number;
|
33
34
|
private static transformElementCommandId;
|
@@ -48,6 +48,10 @@ export default class AbstractComponent {
|
|
48
48
|
transformBy(affineTransfm) {
|
49
49
|
return new AbstractComponent.TransformElementCommand(affineTransfm, this);
|
50
50
|
}
|
51
|
+
// Returns a command that updates this component's z-index.
|
52
|
+
setZIndex(newZIndex) {
|
53
|
+
return new AbstractComponent.TransformElementCommand(Mat33.identity, this, newZIndex);
|
54
|
+
}
|
51
55
|
// @returns true iff this component can be selected (e.g. by the selection tool.)
|
52
56
|
isSelectable() {
|
53
57
|
return true;
|
@@ -126,10 +130,11 @@ AbstractComponent.zIndexCounter = 0;
|
|
126
130
|
AbstractComponent.deserializationCallbacks = {};
|
127
131
|
AbstractComponent.transformElementCommandId = 'transform-element';
|
128
132
|
AbstractComponent.UnresolvedTransformElementCommand = class extends SerializableCommand {
|
129
|
-
constructor(affineTransfm, componentID) {
|
133
|
+
constructor(affineTransfm, componentID, targetZIndex) {
|
130
134
|
super(AbstractComponent.transformElementCommandId);
|
131
135
|
this.affineTransfm = affineTransfm;
|
132
136
|
this.componentID = componentID;
|
137
|
+
this.targetZIndex = targetZIndex;
|
133
138
|
this.command = null;
|
134
139
|
}
|
135
140
|
resolveCommand(editor) {
|
@@ -140,7 +145,7 @@ AbstractComponent.UnresolvedTransformElementCommand = class extends Serializable
|
|
140
145
|
if (!component) {
|
141
146
|
throw new Error(`Unable to resolve component with ID ${this.componentID}`);
|
142
147
|
}
|
143
|
-
this.command = new AbstractComponent.TransformElementCommand(this.affineTransfm, component);
|
148
|
+
this.command = new AbstractComponent.TransformElementCommand(this.affineTransfm, component, this.targetZIndex);
|
144
149
|
}
|
145
150
|
apply(editor) {
|
146
151
|
this.resolveCommand(editor);
|
@@ -157,15 +162,17 @@ AbstractComponent.UnresolvedTransformElementCommand = class extends Serializable
|
|
157
162
|
return {
|
158
163
|
id: this.componentID,
|
159
164
|
transfm: this.affineTransfm.toArray(),
|
165
|
+
targetZIndex: this.targetZIndex,
|
160
166
|
};
|
161
167
|
}
|
162
168
|
};
|
163
169
|
AbstractComponent.TransformElementCommand = (_a = class extends SerializableCommand {
|
164
|
-
constructor(affineTransfm, component) {
|
170
|
+
constructor(affineTransfm, component, targetZIndex) {
|
165
171
|
super(AbstractComponent.transformElementCommandId);
|
166
172
|
this.affineTransfm = affineTransfm;
|
167
173
|
this.component = component;
|
168
174
|
this.origZIndex = component.zIndex;
|
175
|
+
this.targetZIndex = targetZIndex !== null && targetZIndex !== void 0 ? targetZIndex : AbstractComponent.zIndexCounter++;
|
169
176
|
}
|
170
177
|
updateTransform(editor, newTransfm) {
|
171
178
|
// Any parent should have only one direct child.
|
@@ -182,7 +189,7 @@ AbstractComponent.TransformElementCommand = (_a = class extends SerializableComm
|
|
182
189
|
}
|
183
190
|
}
|
184
191
|
apply(editor) {
|
185
|
-
this.component.zIndex =
|
192
|
+
this.component.zIndex = this.targetZIndex;
|
186
193
|
this.updateTransform(editor, this.affineTransfm);
|
187
194
|
editor.queueRerender();
|
188
195
|
}
|
@@ -198,6 +205,7 @@ AbstractComponent.TransformElementCommand = (_a = class extends SerializableComm
|
|
198
205
|
return {
|
199
206
|
id: this.component.getId(),
|
200
207
|
transfm: this.affineTransfm.toArray(),
|
208
|
+
targetZIndex: this.targetZIndex,
|
201
209
|
};
|
202
210
|
}
|
203
211
|
},
|
@@ -205,10 +213,11 @@ AbstractComponent.TransformElementCommand = (_a = class extends SerializableComm
|
|
205
213
|
SerializableCommand.register(AbstractComponent.transformElementCommandId, (json, editor) => {
|
206
214
|
const elem = editor.image.lookupElement(json.id);
|
207
215
|
const transform = new Mat33(...json.transfm);
|
216
|
+
const targetZIndex = json.targetZIndex;
|
208
217
|
if (!elem) {
|
209
|
-
return new AbstractComponent.UnresolvedTransformElementCommand(transform, json.id);
|
218
|
+
return new AbstractComponent.UnresolvedTransformElementCommand(transform, json.id, targetZIndex);
|
210
219
|
}
|
211
|
-
return new AbstractComponent.TransformElementCommand(transform, elem);
|
220
|
+
return new AbstractComponent.TransformElementCommand(transform, elem, targetZIndex);
|
212
221
|
});
|
213
222
|
})(),
|
214
223
|
_a);
|
@@ -23,6 +23,9 @@ export default class ImageComponent extends AbstractComponent {
|
|
23
23
|
};
|
24
24
|
protected applyTransformation(affineTransfm: Mat33): void;
|
25
25
|
description(localizationTable: ImageComponentLocalization): string;
|
26
|
+
getAltText(): string | undefined;
|
27
|
+
getURL(): string;
|
28
|
+
getTransformation(): Mat33;
|
26
29
|
protected createClone(): AbstractComponent;
|
27
30
|
static deserializeFromJSON(data: any): ImageComponent;
|
28
31
|
}
|
@@ -33,7 +33,7 @@ export default class ImageComponent extends AbstractComponent {
|
|
33
33
|
}
|
34
34
|
// Load from an image. Waits for the image to load if incomplete.
|
35
35
|
static fromImage(elem, transform) {
|
36
|
-
var _a;
|
36
|
+
var _a, _b, _c;
|
37
37
|
return __awaiter(this, void 0, void 0, function* () {
|
38
38
|
if (!elem.complete) {
|
39
39
|
yield new Promise((resolve, reject) => {
|
@@ -70,6 +70,8 @@ export default class ImageComponent extends AbstractComponent {
|
|
70
70
|
image.width = width;
|
71
71
|
image.height = height;
|
72
72
|
}
|
73
|
+
image.setAttribute('alt', (_b = elem.getAttribute('alt')) !== null && _b !== void 0 ? _b : '');
|
74
|
+
image.setAttribute('aria-label', (_c = elem.getAttribute('aria-label')) !== null && _c !== void 0 ? _c : '');
|
73
75
|
return new ImageComponent({
|
74
76
|
image,
|
75
77
|
base64Url: url,
|
@@ -111,6 +113,15 @@ export default class ImageComponent extends AbstractComponent {
|
|
111
113
|
description(localizationTable) {
|
112
114
|
return this.image.label ? localizationTable.imageNode(this.image.label) : localizationTable.unlabeledImageNode;
|
113
115
|
}
|
116
|
+
getAltText() {
|
117
|
+
return this.image.label;
|
118
|
+
}
|
119
|
+
getURL() {
|
120
|
+
return this.image.base64Url;
|
121
|
+
}
|
122
|
+
getTransformation() {
|
123
|
+
return this.image.transform;
|
124
|
+
}
|
114
125
|
createClone() {
|
115
126
|
return new ImageComponent(Object.assign({}, this.image));
|
116
127
|
}
|
@@ -14,5 +14,5 @@ const localization = Object.assign(Object.assign({}, defaultEditorLocalization),
|
|
14
14
|
return `Color fue cambiado a ${color}`;
|
15
15
|
}, keyboardPanZoom: 'Mover la pantalla con el teclado', penTool: function (penId) {
|
16
16
|
return `Lapiz ${penId}`;
|
17
|
-
}, selectionTool: 'Selecciona', eraserTool: 'Borrador', textTool: 'Texto', enterTextToInsert: 'Entra texto', rerenderAsText: 'Redibuja la pantalla al texto' });
|
17
|
+
}, selectionTool: 'Selecciona', eraserTool: 'Borrador', textTool: 'Texto', enterTextToInsert: 'Entra texto', rerenderAsText: 'Redibuja la pantalla al texto', image: 'Imagen', imageSize: (size, units) => `Tamaño del imagen: ${size} ${units}`, imageLoadError: (message) => `Error cargando imagen: ${message}` });
|
18
18
|
export default localization;
|
@@ -188,15 +188,19 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
188
188
|
}
|
189
189
|
}
|
190
190
|
drawImage(image) {
|
191
|
-
var _a, _b, _c, _d, _e;
|
191
|
+
var _a, _b, _c, _d, _e, _f;
|
192
|
+
let label = (_b = (_a = image.label) !== null && _a !== void 0 ? _a : image.image.getAttribute('aria-label')) !== null && _b !== void 0 ? _b : '';
|
193
|
+
if (label === '') {
|
194
|
+
label = (_c = image.image.getAttribute('alt')) !== null && _c !== void 0 ? _c : '';
|
195
|
+
}
|
192
196
|
const svgImgElem = document.createElementNS(svgNameSpace, 'image');
|
193
197
|
svgImgElem.setAttribute('href', image.base64Url);
|
194
|
-
svgImgElem.setAttribute('width', (
|
195
|
-
svgImgElem.setAttribute('height', (
|
196
|
-
svgImgElem.setAttribute('aria-label',
|
198
|
+
svgImgElem.setAttribute('width', (_d = image.image.getAttribute('width')) !== null && _d !== void 0 ? _d : '');
|
199
|
+
svgImgElem.setAttribute('height', (_e = image.image.getAttribute('height')) !== null && _e !== void 0 ? _e : '');
|
200
|
+
svgImgElem.setAttribute('aria-label', label);
|
197
201
|
this.transformFrom(image.transform, svgImgElem);
|
198
202
|
this.elem.appendChild(svgImgElem);
|
199
|
-
(
|
203
|
+
(_f = this.objectElems) === null || _f === void 0 ? void 0 : _f.push(svgImgElem);
|
200
204
|
}
|
201
205
|
startObject(boundingBox) {
|
202
206
|
super.startObject(boundingBox);
|
@@ -12,7 +12,7 @@ import EraserWidget from './widgets/EraserToolWidget';
|
|
12
12
|
import SelectionToolWidget from './widgets/SelectionToolWidget';
|
13
13
|
import TextToolWidget from './widgets/TextToolWidget';
|
14
14
|
import HandToolWidget from './widgets/HandToolWidget';
|
15
|
-
import { ActionButtonWidget } from './lib';
|
15
|
+
import { ActionButtonWidget, InsertImageWidget } from './lib';
|
16
16
|
export const toolbarCSSPrefix = 'toolbar-';
|
17
17
|
export default class HTMLToolbar {
|
18
18
|
/** @internal */
|
@@ -179,6 +179,7 @@ export default class HTMLToolbar {
|
|
179
179
|
for (const tool of toolController.getMatchingTools(TextTool)) {
|
180
180
|
this.addWidget(new TextToolWidget(this.editor, tool, this.localizationTable));
|
181
181
|
}
|
182
|
+
this.addWidget(new InsertImageWidget(this.editor, this.localizationTable));
|
182
183
|
const panZoomTool = toolController.getMatchingTools(PanZoomTool)[0];
|
183
184
|
if (panZoomTool) {
|
184
185
|
this.addWidget(new HandToolWidget(this.editor, panZoomTool, this.localizationTable));
|
@@ -19,6 +19,7 @@ export default class IconProvider {
|
|
19
19
|
makeAllDevicePanningIcon(): IconType;
|
20
20
|
makeZoomIcon(): IconType;
|
21
21
|
makeRotationLockIcon(): IconType;
|
22
|
+
makeInsertImageIcon(): IconType;
|
22
23
|
makeTextIcon(textStyle: TextStyle): IconType;
|
23
24
|
makePenIcon(tipThickness: number, color: string | Color4, roundedTip?: boolean): IconType;
|
24
25
|
makeIconFromFactory(pen: Pen, factory: ComponentBuilderFactory): IconType;
|
@@ -284,6 +284,13 @@ export default class IconProvider {
|
|
284
284
|
icon.setAttribute('viewBox', '10 10 70 70');
|
285
285
|
return icon;
|
286
286
|
}
|
287
|
+
makeInsertImageIcon() {
|
288
|
+
return this.makeIconFromPath(`
|
289
|
+
M 5 10 L 5 90 L 95 90 L 95 10 L 5 10 z
|
290
|
+
M 10 15 L 90 15 L 90 50 L 70 75 L 40 50 L 10 75 L 10 15 z
|
291
|
+
M 22.5 25 A 7.5 7.5 0 0 0 15 32.5 A 7.5 7.5 0 0 0 22.5 40 A 7.5 7.5 0 0 0 30 32.5 A 7.5 7.5 0 0 0 22.5 25 z
|
292
|
+
`);
|
293
|
+
}
|
287
294
|
makeTextIcon(textStyle) {
|
288
295
|
var _a, _b;
|
289
296
|
const icon = document.createElementNS(svgNamespace, 'svg');
|
@@ -7,6 +7,11 @@ export interface ToolbarLocalization {
|
|
7
7
|
filledRectanglePen: string;
|
8
8
|
linePen: string;
|
9
9
|
arrowPen: string;
|
10
|
+
image: string;
|
11
|
+
inputAltText: string;
|
12
|
+
chooseFile: string;
|
13
|
+
cancel: string;
|
14
|
+
submit: string;
|
10
15
|
freehandPen: string;
|
11
16
|
pressureSensitiveFreehandPen: string;
|
12
17
|
selectObjectType: string;
|
@@ -27,9 +32,12 @@ export interface ToolbarLocalization {
|
|
27
32
|
resetView: string;
|
28
33
|
selectionToolKeyboardShortcuts: string;
|
29
34
|
paste: string;
|
35
|
+
errorImageHasZeroSize: string;
|
30
36
|
dropdownShown: (toolName: string) => string;
|
31
37
|
dropdownHidden: (toolName: string) => string;
|
32
38
|
zoomLevel: (zoomPercentage: number) => string;
|
33
39
|
colorChangedAnnouncement: (color: string) => string;
|
40
|
+
imageSize: (size: number, units: string) => string;
|
41
|
+
imageLoadError: (message: string) => string;
|
34
42
|
}
|
35
43
|
export declare const defaultToolbarLocalization: ToolbarLocalization;
|
@@ -4,6 +4,11 @@ export const defaultToolbarLocalization = {
|
|
4
4
|
select: 'Select',
|
5
5
|
handTool: 'Pan',
|
6
6
|
zoom: 'Zoom',
|
7
|
+
image: 'Image',
|
8
|
+
inputAltText: 'Alt text: ',
|
9
|
+
chooseFile: 'Choose file: ',
|
10
|
+
submit: 'Submit',
|
11
|
+
cancel: 'Cancel',
|
7
12
|
resetView: 'Reset view',
|
8
13
|
thicknessLabel: 'Thickness: ',
|
9
14
|
colorLabel: 'Color: ',
|
@@ -31,4 +36,7 @@ export const defaultToolbarLocalization = {
|
|
31
36
|
dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
|
32
37
|
zoomLevel: (zoomPercent) => `Zoom: ${zoomPercent}%`,
|
33
38
|
colorChangedAnnouncement: (color) => `Color changed to ${color}`,
|
39
|
+
imageSize: (size, units) => `Image size: ${size} ${units}`,
|
40
|
+
errorImageHasZeroSize: 'Error: Image has zero size',
|
41
|
+
imageLoadError: (message) => `Error loading image: ${message}`,
|
34
42
|
};
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import Editor from '../../Editor';
|
2
|
+
import { ToolbarLocalization } from '../localization';
|
3
|
+
import ActionButtonWidget from './ActionButtonWidget';
|
4
|
+
export default class InsertImageWidget extends ActionButtonWidget {
|
5
|
+
private imageSelectionOverlay;
|
6
|
+
private imagePreview;
|
7
|
+
private imageFileInput;
|
8
|
+
private imageAltTextInput;
|
9
|
+
private statusView;
|
10
|
+
private imageBase64URL;
|
11
|
+
private submitButton;
|
12
|
+
constructor(editor: Editor, localization?: ToolbarLocalization);
|
13
|
+
private static nextInputId;
|
14
|
+
private fillOverlay;
|
15
|
+
private hideDialog;
|
16
|
+
private updateImageSizeDisplay;
|
17
|
+
private clearInputs;
|
18
|
+
private onClicked;
|
19
|
+
}
|