js-draw 0.10.3 → 0.11.0
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 +4 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +3 -1
- package/dist/src/Editor.js +52 -15
- 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/PanZoom.js +10 -0
- package/dist/src/tools/PasteHandler.js +1 -39
- 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 +66 -16
- 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/PanZoom.test.ts +65 -0
- package/src/tools/PanZoom.ts +12 -0
- package/src/tools/PasteHandler.ts +2 -51
- package/src/util/fileToBase64.ts +18 -0
- package/src/util/waitForTimeout.ts +9 -0
@@ -12,16 +12,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
12
12
|
});
|
13
13
|
};
|
14
14
|
import { TextComponent } from '../components/lib';
|
15
|
-
import { uniteCommands } from '../commands/lib';
|
16
15
|
import SVGLoader from '../SVGLoader';
|
17
16
|
import { Mat33 } from '../math/lib';
|
18
17
|
import BaseTool from './BaseTool';
|
19
|
-
import EditorImage from '../EditorImage';
|
20
|
-
import SelectionTool from './SelectionTool/SelectionTool';
|
21
18
|
import TextTool from './TextTool';
|
22
19
|
import Color4 from '../Color4';
|
23
20
|
import ImageComponent from '../components/ImageComponent';
|
24
|
-
import Viewport from '../Viewport';
|
25
21
|
// { @inheritDoc PasteHandler! }
|
26
22
|
export default class PasteHandler extends BaseTool {
|
27
23
|
constructor(editor) {
|
@@ -46,41 +42,7 @@ export default class PasteHandler extends BaseTool {
|
|
46
42
|
}
|
47
43
|
addComponentsFromPaste(components) {
|
48
44
|
return __awaiter(this, void 0, void 0, function* () {
|
49
|
-
|
50
|
-
for (const component of components) {
|
51
|
-
if (bbox) {
|
52
|
-
bbox = bbox.union(component.getBBox());
|
53
|
-
}
|
54
|
-
else {
|
55
|
-
bbox = component.getBBox();
|
56
|
-
}
|
57
|
-
}
|
58
|
-
if (!bbox) {
|
59
|
-
return;
|
60
|
-
}
|
61
|
-
// Find a transform that scales/moves bbox onto the screen.
|
62
|
-
const visibleRect = this.editor.viewport.visibleRect;
|
63
|
-
const scaleRatioX = visibleRect.width / bbox.width;
|
64
|
-
const scaleRatioY = visibleRect.height / bbox.height;
|
65
|
-
let scaleRatio = scaleRatioX;
|
66
|
-
if (bbox.width * scaleRatio > visibleRect.width || bbox.height * scaleRatio > visibleRect.height) {
|
67
|
-
scaleRatio = scaleRatioY;
|
68
|
-
}
|
69
|
-
scaleRatio *= 2 / 3;
|
70
|
-
scaleRatio = Viewport.roundScaleRatio(scaleRatio);
|
71
|
-
const transfm = Mat33.translation(visibleRect.center.minus(bbox.center)).rightMul(Mat33.scaling2D(scaleRatio, bbox.center));
|
72
|
-
const commands = [];
|
73
|
-
for (const component of components) {
|
74
|
-
// To allow deserialization, we need to add first, then transform.
|
75
|
-
commands.push(EditorImage.addElement(component));
|
76
|
-
commands.push(component.transformBy(transfm));
|
77
|
-
}
|
78
|
-
const applyChunkSize = 100;
|
79
|
-
this.editor.dispatch(uniteCommands(commands, applyChunkSize), true);
|
80
|
-
for (const selectionTool of this.editor.toolController.getMatchingTools(SelectionTool)) {
|
81
|
-
selectionTool.setEnabled(true);
|
82
|
-
selectionTool.setSelection(components);
|
83
|
-
}
|
45
|
+
yield this.editor.addAndCenterComponents(components);
|
84
46
|
});
|
85
47
|
}
|
86
48
|
doSVGPaste(data) {
|
@@ -0,0 +1,13 @@
|
|
1
|
+
const fileToBase64 = (file, onprogress) => {
|
2
|
+
const reader = new FileReader();
|
3
|
+
return new Promise((resolve, reject) => {
|
4
|
+
reader.onload = () => resolve(reader.result);
|
5
|
+
reader.onerror = reject;
|
6
|
+
reader.onabort = reject;
|
7
|
+
reader.onprogress = (evt) => {
|
8
|
+
onprogress === null || onprogress === void 0 ? void 0 : onprogress(evt);
|
9
|
+
};
|
10
|
+
reader.readAsDataURL(file);
|
11
|
+
});
|
12
|
+
};
|
13
|
+
export default fileToBase64;
|
package/package.json
CHANGED
package/src/Editor.ts
CHANGED
@@ -41,6 +41,10 @@ import IconProvider from './toolbar/IconProvider';
|
|
41
41
|
import { toRoundedString } from './math/rounding';
|
42
42
|
import CanvasRenderer from './rendering/renderers/CanvasRenderer';
|
43
43
|
import untilNextAnimationFrame from './util/untilNextAnimationFrame';
|
44
|
+
import fileToBase64 from './util/fileToBase64';
|
45
|
+
import uniteCommands from './commands/uniteCommands';
|
46
|
+
import SelectionTool from './tools/SelectionTool/SelectionTool';
|
47
|
+
import AbstractComponent from './components/AbstractComponent';
|
44
48
|
|
45
49
|
type HTMLPointerEventType = 'pointerdown'|'pointermove'|'pointerup'|'pointercancel';
|
46
50
|
type HTMLPointerEventFilter = (eventName: HTMLPointerEventType, event: PointerEvent)=>boolean;
|
@@ -513,20 +517,14 @@ export class Editor {
|
|
513
517
|
for (const file of clipboardData.files) {
|
514
518
|
const fileType = file.type.toLowerCase();
|
515
519
|
if (fileType === 'image/png' || fileType === 'image/jpg') {
|
516
|
-
const reader = new FileReader();
|
517
|
-
|
518
520
|
this.showLoadingWarning(0);
|
521
|
+
const onprogress = (evt: ProgressEvent<FileReader>) => {
|
522
|
+
this.showLoadingWarning(evt.loaded / evt.total);
|
523
|
+
};
|
524
|
+
|
519
525
|
try {
|
520
|
-
const data = await
|
521
|
-
|
522
|
-
reader.onerror = reject;
|
523
|
-
reader.onabort = reject;
|
524
|
-
reader.onprogress = (evt) => {
|
525
|
-
this.showLoadingWarning(evt.loaded / evt.total);
|
526
|
-
};
|
527
|
-
|
528
|
-
reader.readAsDataURL(file);
|
529
|
-
});
|
526
|
+
const data = await fileToBase64(file, onprogress);
|
527
|
+
|
530
528
|
if (data && this.toolController.dispatchInputEvent({
|
531
529
|
kind: InputEvtType.PasteEvent,
|
532
530
|
mime: fileType,
|
@@ -629,13 +627,14 @@ export class Editor {
|
|
629
627
|
/** `apply` a command. `command` will be announced for accessibility. */
|
630
628
|
public dispatch(command: Command, addToHistory: boolean = true) {
|
631
629
|
if (addToHistory) {
|
632
|
-
|
633
|
-
this.history.push(command);
|
634
|
-
} else {
|
635
|
-
command.apply(this);
|
630
|
+
const apply = false; // Don't double-apply
|
631
|
+
this.history.push(command, apply);
|
636
632
|
}
|
637
633
|
|
634
|
+
const applyResult = command.apply(this);
|
638
635
|
this.announceForAccessibility(command.description(this, this.localization));
|
636
|
+
|
637
|
+
return applyResult;
|
639
638
|
}
|
640
639
|
|
641
640
|
/**
|
@@ -825,6 +824,57 @@ export class Editor {
|
|
825
824
|
});
|
826
825
|
}
|
827
826
|
|
827
|
+
public async addAndCenterComponents(components: AbstractComponent[], selectComponents: boolean = true) {
|
828
|
+
let bbox: Rect2|null = null;
|
829
|
+
for (const component of components) {
|
830
|
+
if (bbox) {
|
831
|
+
bbox = bbox.union(component.getBBox());
|
832
|
+
} else {
|
833
|
+
bbox = component.getBBox();
|
834
|
+
}
|
835
|
+
}
|
836
|
+
|
837
|
+
if (!bbox) {
|
838
|
+
return;
|
839
|
+
}
|
840
|
+
|
841
|
+
// Find a transform that scales/moves bbox onto the screen.
|
842
|
+
const visibleRect = this.viewport.visibleRect;
|
843
|
+
const scaleRatioX = visibleRect.width / bbox.width;
|
844
|
+
const scaleRatioY = visibleRect.height / bbox.height;
|
845
|
+
|
846
|
+
let scaleRatio = scaleRatioX;
|
847
|
+
if (bbox.width * scaleRatio > visibleRect.width || bbox.height * scaleRatio > visibleRect.height) {
|
848
|
+
scaleRatio = scaleRatioY;
|
849
|
+
}
|
850
|
+
scaleRatio *= 2 / 3;
|
851
|
+
|
852
|
+
scaleRatio = Viewport.roundScaleRatio(scaleRatio);
|
853
|
+
|
854
|
+
const transfm = Mat33.translation(
|
855
|
+
visibleRect.center.minus(bbox.center)
|
856
|
+
).rightMul(
|
857
|
+
Mat33.scaling2D(scaleRatio, bbox.center)
|
858
|
+
);
|
859
|
+
|
860
|
+
const commands: Command[] = [];
|
861
|
+
for (const component of components) {
|
862
|
+
// To allow deserialization, we need to add first, then transform.
|
863
|
+
commands.push(EditorImage.addElement(component));
|
864
|
+
commands.push(component.transformBy(transfm));
|
865
|
+
}
|
866
|
+
|
867
|
+
const applyChunkSize = 100;
|
868
|
+
await this.dispatch(uniteCommands(commands, applyChunkSize), true);
|
869
|
+
|
870
|
+
if (selectComponents) {
|
871
|
+
for (const selectionTool of this.toolController.getMatchingTools(SelectionTool)) {
|
872
|
+
selectionTool.setEnabled(true);
|
873
|
+
selectionTool.setSelection(components);
|
874
|
+
}
|
875
|
+
}
|
876
|
+
}
|
877
|
+
|
828
878
|
// Get a data URL (e.g. as produced by `HTMLCanvasElement::toDataURL`).
|
829
879
|
// If `format` is not `image/png`, a PNG image URL may still be returned (as in the
|
830
880
|
// case of `HTMLCanvasElement::toDataURL`).
|
package/src/SVGLoader.ts
CHANGED
@@ -287,6 +287,7 @@ export default class SVGLoader implements ImageLoader {
|
|
287
287
|
private async addImage(elem: SVGImageElement) {
|
288
288
|
const image = new Image();
|
289
289
|
image.src = elem.getAttribute('xlink:href') ?? elem.href.baseVal;
|
290
|
+
image.setAttribute('alt', elem.getAttribute('aria-label') ?? '');
|
290
291
|
|
291
292
|
try {
|
292
293
|
const supportedAttrs: string[] = [];
|
@@ -90,6 +90,11 @@ export default abstract class AbstractComponent {
|
|
90
90
|
return new AbstractComponent.TransformElementCommand(affineTransfm, this);
|
91
91
|
}
|
92
92
|
|
93
|
+
// Returns a command that updates this component's z-index.
|
94
|
+
public setZIndex(newZIndex: number): SerializableCommand {
|
95
|
+
return new AbstractComponent.TransformElementCommand(Mat33.identity, this, newZIndex);
|
96
|
+
}
|
97
|
+
|
93
98
|
// @returns true iff this component can be selected (e.g. by the selection tool.)
|
94
99
|
public isSelectable(): boolean {
|
95
100
|
return true;
|
@@ -110,6 +115,7 @@ export default abstract class AbstractComponent {
|
|
110
115
|
public constructor(
|
111
116
|
private affineTransfm: Mat33,
|
112
117
|
private componentID: string,
|
118
|
+
private targetZIndex?: number,
|
113
119
|
) {
|
114
120
|
super(AbstractComponent.transformElementCommandId);
|
115
121
|
}
|
@@ -123,7 +129,9 @@ export default abstract class AbstractComponent {
|
|
123
129
|
if (!component) {
|
124
130
|
throw new Error(`Unable to resolve component with ID ${this.componentID}`);
|
125
131
|
}
|
126
|
-
this.command = new AbstractComponent.TransformElementCommand(
|
132
|
+
this.command = new AbstractComponent.TransformElementCommand(
|
133
|
+
this.affineTransfm, component, this.targetZIndex
|
134
|
+
);
|
127
135
|
}
|
128
136
|
|
129
137
|
public apply(editor: Editor) {
|
@@ -144,19 +152,23 @@ export default abstract class AbstractComponent {
|
|
144
152
|
return {
|
145
153
|
id: this.componentID,
|
146
154
|
transfm: this.affineTransfm.toArray(),
|
155
|
+
targetZIndex: this.targetZIndex,
|
147
156
|
};
|
148
157
|
}
|
149
158
|
};
|
150
159
|
|
151
160
|
private static TransformElementCommand = class extends SerializableCommand {
|
152
161
|
private origZIndex: number;
|
162
|
+
private targetZIndex: number;
|
153
163
|
|
154
164
|
public constructor(
|
155
165
|
private affineTransfm: Mat33,
|
156
166
|
private component: AbstractComponent,
|
167
|
+
targetZIndex?: number,
|
157
168
|
) {
|
158
169
|
super(AbstractComponent.transformElementCommandId);
|
159
170
|
this.origZIndex = component.zIndex;
|
171
|
+
this.targetZIndex = targetZIndex ?? AbstractComponent.zIndexCounter++;
|
160
172
|
}
|
161
173
|
|
162
174
|
private updateTransform(editor: Editor, newTransfm: Mat33) {
|
@@ -177,7 +189,7 @@ export default abstract class AbstractComponent {
|
|
177
189
|
}
|
178
190
|
|
179
191
|
public apply(editor: Editor) {
|
180
|
-
this.component.zIndex =
|
192
|
+
this.component.zIndex = this.targetZIndex;
|
181
193
|
this.updateTransform(editor, this.affineTransfm);
|
182
194
|
editor.queueRerender();
|
183
195
|
}
|
@@ -195,16 +207,17 @@ export default abstract class AbstractComponent {
|
|
195
207
|
static {
|
196
208
|
SerializableCommand.register(AbstractComponent.transformElementCommandId, (json: any, editor: Editor) => {
|
197
209
|
const elem = editor.image.lookupElement(json.id);
|
198
|
-
|
199
210
|
const transform = new Mat33(...(json.transfm as Mat33Array));
|
211
|
+
const targetZIndex = json.targetZIndex;
|
200
212
|
|
201
213
|
if (!elem) {
|
202
|
-
return new AbstractComponent.UnresolvedTransformElementCommand(transform, json.id);
|
214
|
+
return new AbstractComponent.UnresolvedTransformElementCommand(transform, json.id, targetZIndex);
|
203
215
|
}
|
204
216
|
|
205
217
|
return new AbstractComponent.TransformElementCommand(
|
206
218
|
transform,
|
207
219
|
elem,
|
220
|
+
targetZIndex,
|
208
221
|
);
|
209
222
|
});
|
210
223
|
}
|
@@ -213,6 +226,7 @@ export default abstract class AbstractComponent {
|
|
213
226
|
return {
|
214
227
|
id: this.component.getId(),
|
215
228
|
transfm: this.affineTransfm.toArray(),
|
229
|
+
targetZIndex: this.targetZIndex,
|
216
230
|
};
|
217
231
|
}
|
218
232
|
};
|
@@ -77,6 +77,9 @@ export default class ImageComponent extends AbstractComponent {
|
|
77
77
|
image.height = height;
|
78
78
|
}
|
79
79
|
|
80
|
+
image.setAttribute('alt', elem.getAttribute('alt') ?? '');
|
81
|
+
image.setAttribute('aria-label', elem.getAttribute('aria-label') ?? '');
|
82
|
+
|
80
83
|
return new ImageComponent({
|
81
84
|
image,
|
82
85
|
base64Url: url,
|
@@ -126,6 +129,18 @@ export default class ImageComponent extends AbstractComponent {
|
|
126
129
|
return this.image.label ? localizationTable.imageNode(this.image.label) : localizationTable.unlabeledImageNode;
|
127
130
|
}
|
128
131
|
|
132
|
+
public getAltText() {
|
133
|
+
return this.image.label;
|
134
|
+
}
|
135
|
+
|
136
|
+
public getURL() {
|
137
|
+
return this.image.base64Url;
|
138
|
+
}
|
139
|
+
|
140
|
+
public getTransformation(): Mat33 {
|
141
|
+
return this.image.transform;
|
142
|
+
}
|
143
|
+
|
129
144
|
protected createClone(): AbstractComponent {
|
130
145
|
return new ImageComponent({
|
131
146
|
...this.image,
|
package/src/localizations/es.ts
CHANGED
@@ -58,6 +58,9 @@ const localization: EditorLocalization = {
|
|
58
58
|
textTool: 'Texto',
|
59
59
|
enterTextToInsert: 'Entra texto',
|
60
60
|
rerenderAsText: 'Redibuja la pantalla al texto',
|
61
|
+
image: 'Imagen',
|
62
|
+
imageSize: (size: number, units: string) => `Tamaño del imagen: ${size} ${units}`,
|
63
|
+
imageLoadError: (message: string)=> `Error cargando imagen: ${message}`,
|
61
64
|
};
|
62
65
|
|
63
66
|
export default localization;
|
@@ -217,11 +217,16 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
217
217
|
}
|
218
218
|
|
219
219
|
public drawImage(image: RenderableImage) {
|
220
|
+
let label = image.label ?? image.image.getAttribute('aria-label') ?? '';
|
221
|
+
if (label === '') {
|
222
|
+
label = image.image.getAttribute('alt') ?? '';
|
223
|
+
}
|
224
|
+
|
220
225
|
const svgImgElem = document.createElementNS(svgNameSpace, 'image');
|
221
226
|
svgImgElem.setAttribute('href', image.base64Url);
|
222
227
|
svgImgElem.setAttribute('width', image.image.getAttribute('width') ?? '');
|
223
228
|
svgImgElem.setAttribute('height', image.image.getAttribute('height') ?? '');
|
224
|
-
svgImgElem.setAttribute('aria-label',
|
229
|
+
svgImgElem.setAttribute('aria-label', label);
|
225
230
|
this.transformFrom(image.transform, svgImgElem);
|
226
231
|
|
227
232
|
this.elem.appendChild(svgImgElem);
|
@@ -16,7 +16,7 @@ import SelectionToolWidget from './widgets/SelectionToolWidget';
|
|
16
16
|
import TextToolWidget from './widgets/TextToolWidget';
|
17
17
|
import HandToolWidget from './widgets/HandToolWidget';
|
18
18
|
import BaseWidget from './widgets/BaseWidget';
|
19
|
-
import { ActionButtonWidget } from './lib';
|
19
|
+
import { ActionButtonWidget, InsertImageWidget } from './lib';
|
20
20
|
|
21
21
|
export const toolbarCSSPrefix = 'toolbar-';
|
22
22
|
|
@@ -236,6 +236,8 @@ export default class HTMLToolbar {
|
|
236
236
|
this.addWidget(new TextToolWidget(this.editor, tool, this.localizationTable));
|
237
237
|
}
|
238
238
|
|
239
|
+
this.addWidget(new InsertImageWidget(this.editor, this.localizationTable));
|
240
|
+
|
239
241
|
const panZoomTool = toolController.getMatchingTools(PanZoomTool)[0];
|
240
242
|
if (panZoomTool) {
|
241
243
|
this.addWidget(new HandToolWidget(this.editor, panZoomTool, this.localizationTable));
|
@@ -323,6 +323,14 @@ export default class IconProvider {
|
|
323
323
|
|
324
324
|
return icon;
|
325
325
|
}
|
326
|
+
|
327
|
+
public makeInsertImageIcon(): IconType {
|
328
|
+
return this.makeIconFromPath(`
|
329
|
+
M 5 10 L 5 90 L 95 90 L 95 10 L 5 10 z
|
330
|
+
M 10 15 L 90 15 L 90 50 L 70 75 L 40 50 L 10 75 L 10 15 z
|
331
|
+
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
|
332
|
+
`);
|
333
|
+
}
|
326
334
|
|
327
335
|
public makeTextIcon(textStyle: TextStyle): IconType {
|
328
336
|
const icon = document.createElementNS(svgNamespace, 'svg');
|
@@ -9,6 +9,11 @@ export interface ToolbarLocalization {
|
|
9
9
|
filledRectanglePen: string;
|
10
10
|
linePen: string;
|
11
11
|
arrowPen: string;
|
12
|
+
image: string;
|
13
|
+
inputAltText: string;
|
14
|
+
chooseFile: string;
|
15
|
+
cancel: string;
|
16
|
+
submit: string;
|
12
17
|
freehandPen: string;
|
13
18
|
pressureSensitiveFreehandPen: string;
|
14
19
|
selectObjectType: string;
|
@@ -30,10 +35,14 @@ export interface ToolbarLocalization {
|
|
30
35
|
selectionToolKeyboardShortcuts: string;
|
31
36
|
paste: string;
|
32
37
|
|
38
|
+
errorImageHasZeroSize: string;
|
39
|
+
|
33
40
|
dropdownShown: (toolName: string)=> string;
|
34
41
|
dropdownHidden: (toolName: string)=> string;
|
35
42
|
zoomLevel: (zoomPercentage: number)=> string;
|
36
43
|
colorChangedAnnouncement: (color: string)=> string;
|
44
|
+
imageSize: (size: number, units: string)=> string;
|
45
|
+
imageLoadError: (message: string)=> string;
|
37
46
|
}
|
38
47
|
|
39
48
|
export const defaultToolbarLocalization: ToolbarLocalization = {
|
@@ -42,6 +51,11 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
|
|
42
51
|
select: 'Select',
|
43
52
|
handTool: 'Pan',
|
44
53
|
zoom: 'Zoom',
|
54
|
+
image: 'Image',
|
55
|
+
inputAltText: 'Alt text: ',
|
56
|
+
chooseFile: 'Choose file: ',
|
57
|
+
submit: 'Submit',
|
58
|
+
cancel: 'Cancel',
|
45
59
|
resetView: 'Reset view',
|
46
60
|
thicknessLabel: 'Thickness: ',
|
47
61
|
colorLabel: 'Color: ',
|
@@ -72,5 +86,9 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
|
|
72
86
|
dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
|
73
87
|
dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
|
74
88
|
zoomLevel: (zoomPercent: number) => `Zoom: ${zoomPercent}%`,
|
75
|
-
colorChangedAnnouncement: (color: string)=> `Color changed to ${color}`,
|
89
|
+
colorChangedAnnouncement: (color: string) => `Color changed to ${color}`,
|
90
|
+
imageSize: (size: number, units: string) => `Image size: ${size} ${units}`,
|
91
|
+
|
92
|
+
errorImageHasZeroSize: 'Error: Image has zero size',
|
93
|
+
imageLoadError: (message: string)=> `Error loading image: ${message}`,
|
76
94
|
};
|
package/src/toolbar/toolbar.css
CHANGED
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
.toolbar-image-selection-overlay {
|
3
|
+
position: absolute;
|
4
|
+
|
5
|
+
width: 100%;
|
6
|
+
height: 100%;
|
7
|
+
z-index: 10;
|
8
|
+
|
9
|
+
display: flex;
|
10
|
+
align-items: center;
|
11
|
+
justify-content: center;
|
12
|
+
}
|
13
|
+
|
14
|
+
.toolbar-image-selection-overlay > div {
|
15
|
+
background: var(--primary-background-color);
|
16
|
+
box-shadow: 1px 1px 3px var(--primary-shadow-color);
|
17
|
+
|
18
|
+
padding: 18px;
|
19
|
+
border-radius: 3px;
|
20
|
+
}
|
21
|
+
|
22
|
+
.toolbar-image-selection-overlay > div > div {
|
23
|
+
padding: 5px;
|
24
|
+
}
|
25
|
+
|
26
|
+
.toolbar-image-selection-overlay img {
|
27
|
+
max-width: min(50vw, 75%);
|
28
|
+
max-height: 50vh;
|
29
|
+
|
30
|
+
/* Center */
|
31
|
+
display: block;
|
32
|
+
margin-left: auto;
|
33
|
+
margin-right: auto;
|
34
|
+
}
|
35
|
+
|
36
|
+
.toolbar-image-selection-overlay .action-button-row {
|
37
|
+
margin-top: 4px;
|
38
|
+
display: flex;
|
39
|
+
flex-direction: row;
|
40
|
+
}
|
41
|
+
|
42
|
+
.toolbar-image-selection-overlay .action-button-row > button {
|
43
|
+
flex-grow: 1;
|
44
|
+
}
|