@antscorp/antsomi-ui 1.3.5-beta.232 → 1.3.5-beta.234

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.
Files changed (34) hide show
  1. package/es/components/molecules/DatePicker/components/AdvancedPicker/utils.d.ts +12 -0
  2. package/es/components/molecules/DatePicker/components/AdvancedPicker/utils.js +27 -0
  3. package/es/components/molecules/DatePicker/components/AdvancedRangePicker/AdvancedRangePicker.d.ts +4 -0
  4. package/es/components/molecules/DatePicker/components/AdvancedRangePicker/AdvancedRangePicker.js +12 -3
  5. package/es/components/molecules/ImageEditor/components/ModalShortcut.d.ts +7 -0
  6. package/es/components/molecules/ImageEditor/components/ModalShortcut.js +45 -0
  7. package/es/components/molecules/ImageEditor/constants.d.ts +37 -0
  8. package/es/components/molecules/ImageEditor/constants.js +32 -0
  9. package/es/components/molecules/ImageEditor/index.d.ts +7 -0
  10. package/es/components/molecules/ImageEditor/index.js +555 -0
  11. package/es/components/molecules/ImageEditor/reducer.d.ts +80 -0
  12. package/es/components/molecules/ImageEditor/reducer.js +51 -0
  13. package/es/components/molecules/ImageEditor/styled.d.ts +4 -0
  14. package/es/components/molecules/ImageEditor/styled.js +8 -0
  15. package/es/components/molecules/ImageEditor/types.d.ts +35 -0
  16. package/es/components/molecules/ImageEditor/types.js +1 -0
  17. package/es/components/molecules/ImageEditor/utils.d.ts +23 -0
  18. package/es/components/molecules/ImageEditor/utils.js +29 -0
  19. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/BigImage.js +2 -6
  20. package/es/components/molecules/TemplateSaveAs/components/ImageSlider/SmallImage.js +2 -6
  21. package/es/components/molecules/index.d.ts +1 -0
  22. package/es/components/molecules/index.js +1 -0
  23. package/es/hooks/index.d.ts +3 -0
  24. package/es/hooks/index.js +3 -0
  25. package/es/hooks/useDragEvent.d.ts +1 -0
  26. package/es/hooks/useDragEvent.js +14 -0
  27. package/es/hooks/useDropFile.d.ts +1 -0
  28. package/es/hooks/useDropFile.js +20 -0
  29. package/es/hooks/useKeyEventCanvas.d.ts +1 -0
  30. package/es/hooks/useKeyEventCanvas.js +20 -0
  31. package/es/utils/common.d.ts +1 -1
  32. package/es/utils/common.js +8 -5
  33. package/es/utils/templateListing.js +17 -2
  34. package/package.json +5 -2
@@ -37,3 +37,15 @@ export declare const getFormatDisplay: (operatorKey?: TOperatorKey, type?: TAdva
37
37
  formatDisplay: string;
38
38
  timeFormatDisplay: string;
39
39
  };
40
+ /**
41
+ * Returns the formatted display string based on the given type and value type.
42
+ *
43
+ * @param {TAdvancedType} [type] - The type indicating whether it's a start or end date.
44
+ * @param {ValueTypes} [valueType] - The value type specifying the date/time format.
45
+ * @returns {string} The formatted display string.
46
+ *
47
+ * @example
48
+ * // returns ADVANCED_RANGE_PICKER_FORMAT.general
49
+ * getFormatDisplayFromValueType('anyType', 'YEAR_MONTH_DAY_SECOND');
50
+ */
51
+ export declare const getFormatDisplayFromValueType: (type?: TAdvancedType, valueType?: ValueTypes) => string;
@@ -8,6 +8,7 @@ import utc from 'dayjs/plugin/utc';
8
8
  import timezone from 'dayjs/plugin/timezone';
9
9
  // Constants
10
10
  import { DATE_TYPE_MAPPING, DEFAULT_DATE_FORMAT, MONTH_LABEL_SHORT, QUARTER_PLACEHOLDER, WEEK_PLACEHOLDER, } from './constants';
11
+ import { ADVANCED_RANGE_PICKER_FORMAT } from '../AdvancedRangePicker/constants';
11
12
  dayjs.extend(isoWeek);
12
13
  dayjs.extend(quarterOfYear);
13
14
  dayjs.extend(utc);
@@ -209,3 +210,29 @@ export const getFormatDisplay = (operatorKey, type, valueType, formatInputDispla
209
210
  }
210
211
  return { formatDisplay: formatInputDisplay || formatDisplay, timeFormatDisplay };
211
212
  };
213
+ /**
214
+ * Returns the formatted display string based on the given type and value type.
215
+ *
216
+ * @param {TAdvancedType} [type] - The type indicating whether it's a start or end date.
217
+ * @param {ValueTypes} [valueType] - The value type specifying the date/time format.
218
+ * @returns {string} The formatted display string.
219
+ *
220
+ * @example
221
+ * // returns ADVANCED_RANGE_PICKER_FORMAT.general
222
+ * getFormatDisplayFromValueType('anyType', 'YEAR_MONTH_DAY_SECOND');
223
+ */
224
+ export const getFormatDisplayFromValueType = (type, valueType) => {
225
+ switch (valueType) {
226
+ case 'YEAR_MONTH_DAY_HOUR':
227
+ return type === 'startDate' ? 'YYYY-MM-DD HH:00:00' : 'YYYY-MM-DD HH:59:59';
228
+ case 'YEAR_MONTH_DAY_MINUTE':
229
+ return type === 'startDate' ? 'YYYY-MM-DD HH:mm:00' : 'YYYY-MM-DD HH:mm:59';
230
+ case 'YEAR_MONTH_DAY_SECOND':
231
+ return ADVANCED_RANGE_PICKER_FORMAT.general;
232
+ case 'YEAR_MONTH_DAY':
233
+ default:
234
+ return type === 'startDate'
235
+ ? ADVANCED_RANGE_PICKER_FORMAT.startDate
236
+ : ADVANCED_RANGE_PICKER_FORMAT.endDate;
237
+ }
238
+ };
@@ -4,6 +4,10 @@ import { TDateConfig, TOnChangePayload, TTimeRange } from './types';
4
4
  export interface AdvancedRangePickerProps {
5
5
  className?: string;
6
6
  valueType?: ValueTypes;
7
+ /**
8
+ * If true => auto generate time format from valueType props; else use hard config
9
+ */
10
+ useFormatMapping?: boolean;
7
11
  disabled?: boolean;
8
12
  showCalculationTypeCondition?: TShowCalculationTypeCondition;
9
13
  startDateConfig?: TDateConfig;
@@ -15,11 +15,12 @@ import { handleError } from '@antscorp/antsomi-ui/es/utils';
15
15
  import { translations } from '@antscorp/antsomi-ui/es/locales/translations';
16
16
  // Instance
17
17
  import i18nInstance from '@antscorp/antsomi-ui/es/locales/i18n';
18
+ import { getFormatDisplayFromValueType } from '../AdvancedPicker/utils';
18
19
  const PATH = '@antscorp/antsomi-ui/es/components/molecules/DatePicker/components/AdvancedRangePicker/AdvancedRangePicker.tsx';
19
20
  export const AdvancedRangePicker = props => {
20
21
  const { t } = i18nInstance;
21
22
  // Props
22
- const { className, valueType, timeRange, errorMessage, showLabel, startDateConfig, endDateConfig, showTime, showCalculationTypeCondition, disabled, timezone, separator, inputStyle, isViewMode, onChange, } = props;
23
+ const { className, valueType, useFormatMapping, timeRange, errorMessage, showLabel, startDateConfig, endDateConfig, showTime, showCalculationTypeCondition, disabled, timezone, separator, inputStyle, isViewMode, onChange, } = props;
23
24
  const [timeRangeState, setTimeRange] = useState(timeRange);
24
25
  useDeepCompareEffect(() => {
25
26
  if (typeof onChange === 'function') {
@@ -55,9 +56,17 @@ export const AdvancedRangePicker = props => {
55
56
  }
56
57
  };
57
58
  return (React.createElement(Space, { size: 20, align: "end", className: className || '' },
58
- React.createElement(AdvancedPicker, { valueType: valueType, disabled: disabled, label: showLabel ? t(translations.datePicker.startDate) || '' : '', date: timeRange.startDate.date, option: omit(timeRange.startDate, 'date'), operatorKey: "between", type: "startDate", format: ADVANCED_RANGE_PICKER_FORMAT.startDate, errorMessage: errorMessage, disableAfterDate: timeRange.endDate.date, showTime: showTime, calculationTypeKeysShow: startDateConfig === null || startDateConfig === void 0 ? void 0 : startDateConfig.calculationTypeKeysShow, showCalculationTypeCondition: showCalculationTypeCondition, timezone: timezone, onUpdatedNewDate: ({ date }) => onUpdateTimeRange('startDate', { date }, 'system'), onApply: ({ date, option }) => onUpdateTimeRange('startDate', Object.assign({ date }, option), 'user'), inputStyle: inputStyle, isViewMode: isViewMode }),
59
+ React.createElement(AdvancedPicker, { valueType: valueType, disabled: disabled, label: showLabel ? t(translations.datePicker.startDate) || '' : '', date: timeRange.startDate.date, option: omit(timeRange.startDate, 'date'), operatorKey: "between", type: "startDate", format: useFormatMapping
60
+ ? getFormatDisplayFromValueType('startDate', valueType)
61
+ : ADVANCED_RANGE_PICKER_FORMAT.startDate,
62
+ // format={ADVANCED_RANGE_PICKER_FORMAT.startDate}
63
+ errorMessage: errorMessage, disableAfterDate: timeRange.endDate.date, showTime: showTime, calculationTypeKeysShow: startDateConfig === null || startDateConfig === void 0 ? void 0 : startDateConfig.calculationTypeKeysShow, showCalculationTypeCondition: showCalculationTypeCondition, timezone: timezone, onUpdatedNewDate: ({ date }) => onUpdateTimeRange('startDate', { date }, 'system'), onApply: ({ date, option }) => onUpdateTimeRange('startDate', Object.assign({ date }, option), 'user'), inputStyle: inputStyle, isViewMode: isViewMode }),
59
64
  separator || null,
60
- React.createElement(AdvancedPicker, { valueType: valueType, disabled: disabled, label: showLabel ? t(translations.datePicker.endDate) || '' : '', date: timeRange.endDate.date, option: omit(timeRange.endDate, 'date'), operatorKey: "between", type: "endDate", format: ADVANCED_RANGE_PICKER_FORMAT.endDate, errorMessage: errorMessage, showTime: showTime, calculationTypeKeysShow: endDateConfig === null || endDateConfig === void 0 ? void 0 : endDateConfig.calculationTypeKeysShow, showCalculationTypeCondition: showCalculationTypeCondition, timezone: timezone, onUpdatedNewDate: ({ date }) => onUpdateTimeRange('endDate', { date }, 'system'), onApply: ({ date, option }) => onUpdateTimeRange('endDate', Object.assign({ date }, option), 'user'), inputStyle: inputStyle, isViewMode: isViewMode })));
65
+ React.createElement(AdvancedPicker, { valueType: valueType, disabled: disabled, label: showLabel ? t(translations.datePicker.endDate) || '' : '', date: timeRange.endDate.date, option: omit(timeRange.endDate, 'date'), operatorKey: "between", type: "endDate", format: useFormatMapping
66
+ ? getFormatDisplayFromValueType('endDate', valueType)
67
+ : ADVANCED_RANGE_PICKER_FORMAT.endDate,
68
+ // format={ADVANCED_RANGE_PICKER_FORMAT.endDate}
69
+ errorMessage: errorMessage, showTime: showTime, calculationTypeKeysShow: endDateConfig === null || endDateConfig === void 0 ? void 0 : endDateConfig.calculationTypeKeysShow, showCalculationTypeCondition: showCalculationTypeCondition, timezone: timezone, onUpdatedNewDate: ({ date }) => onUpdateTimeRange('endDate', { date }, 'system'), onApply: ({ date, option }) => onUpdateTimeRange('endDate', Object.assign({ date }, option), 'user'), inputStyle: inputStyle, isViewMode: isViewMode })));
61
70
  };
62
71
  AdvancedRangePicker.defaultProps = {
63
72
  timeRange: {
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface ModalShortcutProps {
3
+ open: boolean;
4
+ onCancel: () => void;
5
+ }
6
+ declare const ModalShortcut: React.FC<ModalShortcutProps>;
7
+ export default ModalShortcut;
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { ModalV2 } from '../../ModalV2';
3
+ import { Flex } from 'antd';
4
+ import { Text } from '../../../atoms';
5
+ import styled from 'styled-components';
6
+ const RowStyled = styled(Flex) `
7
+ border-bottom: 1px solid #ccc;
8
+ padding: 6px 0;
9
+
10
+ &:last-child {
11
+ border-bottom: none;
12
+ }
13
+
14
+ .row-text {
15
+ font-size: 14px;
16
+ }
17
+ .header {
18
+ font-weight: bold;
19
+ }
20
+ .shortcut {
21
+ border: 1px solid #ccc;
22
+ padding: 4px 0;
23
+ width: 120px;
24
+ text-align: center;
25
+ border-radius: 4px;
26
+ }
27
+ `;
28
+ const SHORTCUT_LIST = [
29
+ { command: 'Delete', shortcut: 'Delete/Del' },
30
+ { command: 'Duplicate', shortcut: 'Ctrl + D' },
31
+ { command: 'Undo', shortcut: 'Ctrl + Z' },
32
+ { command: 'Redo', shortcut: 'Ctrl + Shift + Z' },
33
+ ];
34
+ const ModalShortcut = props => {
35
+ const { open, onCancel } = props;
36
+ return (React.createElement(ModalV2, { open: open, onCancel: onCancel, footer: null, destroyOnClose: true, title: "Keyboard shortcut" },
37
+ React.createElement(Flex, { vertical: true },
38
+ React.createElement(RowStyled, { justify: "space-between" },
39
+ React.createElement(Text, { className: "row-text header" }, "Commands"),
40
+ React.createElement(Text, { className: "row-text header" }, "Shortcut")),
41
+ SHORTCUT_LIST.map(({ command, shortcut }) => (React.createElement(RowStyled, { key: command, justify: "space-between", align: "center" },
42
+ React.createElement(Text, { className: "row-text" }, command),
43
+ React.createElement(Text, { className: "row-text shortcut" }, shortcut)))))));
44
+ };
45
+ export default ModalShortcut;
@@ -0,0 +1,37 @@
1
+ export declare enum DrawAction {
2
+ Select = "select",
3
+ Rectangle = "rectangle",
4
+ Circle = "circle",
5
+ Scribble = "freedraw",
6
+ Arrow = "arrow",
7
+ Crop = "crop"
8
+ }
9
+ export declare enum ShapeObject {
10
+ Scribbles = "scribbles",
11
+ Rectangles = "rectangles",
12
+ Circles = "circles",
13
+ Arrows = "arrows",
14
+ Image = "image"
15
+ }
16
+ export declare const PAINT_OPTIONS: {
17
+ id: DrawAction;
18
+ label: string;
19
+ icon: string;
20
+ }[];
21
+ export declare const DEFAULT_TRANSFORM: {
22
+ rotation: number;
23
+ scaleX: number;
24
+ scaleY: number;
25
+ skewX: number;
26
+ skewY: number;
27
+ };
28
+ export declare const IMAGE_COORDINATE: {
29
+ rotation: number;
30
+ scaleX: number;
31
+ scaleY: number;
32
+ skewX: number;
33
+ skewY: number;
34
+ id: string;
35
+ x: number;
36
+ y: number;
37
+ };
@@ -0,0 +1,32 @@
1
+ export var DrawAction;
2
+ (function (DrawAction) {
3
+ DrawAction["Select"] = "select";
4
+ DrawAction["Rectangle"] = "rectangle";
5
+ DrawAction["Circle"] = "circle";
6
+ DrawAction["Scribble"] = "freedraw";
7
+ DrawAction["Arrow"] = "arrow";
8
+ DrawAction["Crop"] = "crop";
9
+ })(DrawAction || (DrawAction = {}));
10
+ export var ShapeObject;
11
+ (function (ShapeObject) {
12
+ ShapeObject["Scribbles"] = "scribbles";
13
+ ShapeObject["Rectangles"] = "rectangles";
14
+ ShapeObject["Circles"] = "circles";
15
+ ShapeObject["Arrows"] = "arrows";
16
+ ShapeObject["Image"] = "image";
17
+ })(ShapeObject || (ShapeObject = {}));
18
+ export const PAINT_OPTIONS = [
19
+ { id: DrawAction.Select, label: 'Select Shapes', icon: 'icon-ants-cursor' },
20
+ { id: DrawAction.Rectangle, label: 'Draw Rectangle Shape', icon: 'icon-ants-square-outline' },
21
+ { id: DrawAction.Circle, label: 'Draw Cirle Shape', icon: 'icon-ants-circle-outline' },
22
+ { id: DrawAction.Arrow, label: 'Draw Arrow Shape', icon: 'icon-ants-arrow-up-square' },
23
+ { id: DrawAction.Scribble, label: 'Scribble', icon: 'icon-ants-pencil' },
24
+ ];
25
+ export const DEFAULT_TRANSFORM = {
26
+ rotation: 0,
27
+ scaleX: 1,
28
+ scaleY: 1,
29
+ skewX: 0,
30
+ skewY: 0,
31
+ };
32
+ export const IMAGE_COORDINATE = Object.assign({ id: 'image_id', x: 0, y: 0 }, DEFAULT_TRANSFORM);
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface ImageEditorProps {
3
+ width: number;
4
+ height: number;
5
+ }
6
+ export declare const ImageEditor: React.FC<ImageEditorProps>;
7
+ export {};
@@ -0,0 +1,555 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ /* eslint-disable default-case */
11
+ import Icon from '@antscorp/icons';
12
+ import { Flex } from 'antd';
13
+ import * as Konva from 'konva';
14
+ import { cloneDeep } from 'lodash';
15
+ import React, { useCallback, useReducer, useRef, useState } from 'react';
16
+ import { SketchPicker } from 'react-color';
17
+ import { Arrow as KonvaArrow, Circle as KonvaCircle, Image as KonvaImage, Line as KonvaLine, Rect as KonvaRect, Layer, Stage, Transformer, } from 'react-konva';
18
+ import { v4 as uuidV4 } from 'uuid';
19
+ import { Button, Popover } from '../../atoms';
20
+ import ModalShortcut from './components/ModalShortcut';
21
+ import { useDropFile, useKeyEventCanvas } from '@antscorp/antsomi-ui/es/hooks';
22
+ import { DEFAULT_TRANSFORM, DrawAction, PAINT_OPTIONS, ShapeObject } from './constants';
23
+ import { initialReducer, reducer } from './reducer';
24
+ import { IconButtonStyled } from './styled';
25
+ import { downloadURI, getTransformData } from './utils';
26
+ export const ImageEditor = React.memo(({ width, height }) => {
27
+ const [color, setColor] = useState('#000');
28
+ const [drawAction, setDrawAction] = useState(DrawAction.Select);
29
+ const [cropZone, setCropZone] = useState();
30
+ const [selectedId, setSelectedId] = useState();
31
+ const [selectedShape, setSelectedShape] = useState();
32
+ const [openHelper, setOpenHelper] = useState(false);
33
+ // TODO: START - handle selection
34
+ const [selectionPosition, setSelectionPosition] = useState({
35
+ x: 0,
36
+ y: 0,
37
+ width: 0,
38
+ height: 0,
39
+ visible: false,
40
+ });
41
+ // Konva.default.Util.haveIntersection
42
+ const [state, dispatch] = useReducer(reducer, initialReducer);
43
+ const { current: { arrows, circles, imageCoordinate, rectangles, scribbles, image }, } = state;
44
+ const stageRef = useRef(null);
45
+ const transformerRef = useRef(null);
46
+ const selectionRectangleRef = useRef(null);
47
+ const isPaintRef = useRef(false);
48
+ const changeImage = useCallback((imageUrl) => {
49
+ const image = new Image();
50
+ image.src = imageUrl;
51
+ image.decode().then(() => {
52
+ const originalWidth = image.width;
53
+ const originalHeight = image.height;
54
+ const ratio = originalWidth / originalHeight;
55
+ image.width = originalWidth > originalHeight ? width * 0.8 : height * 0.96 * ratio;
56
+ image.height = originalWidth > originalHeight ? (width * 0.8) / ratio : height * 0.96;
57
+ dispatch({
58
+ type: 'SET_NEW_STATE',
59
+ payload: {
60
+ image,
61
+ imageCoordinate: Object.assign(Object.assign({}, imageCoordinate), { id: uuidV4(), x: (width - image.width) / 2, y: (height - image.height) / 2, width: image.width, height: image.height }),
62
+ saveHistory: true,
63
+ },
64
+ });
65
+ });
66
+ }, [height, width, imageCoordinate]);
67
+ const onImportImageSelect = useCallback((e) => {
68
+ var _a;
69
+ if ((_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0]) {
70
+ changeImage(URL.createObjectURL(e.target.files[0]));
71
+ }
72
+ e.target.files = null;
73
+ }, [changeImage]);
74
+ const fileRef = useRef(null);
75
+ const onImportImageClick = useCallback(() => {
76
+ var _a;
77
+ (_a = fileRef === null || fileRef === void 0 ? void 0 : fileRef.current) === null || _a === void 0 ? void 0 : _a.click();
78
+ }, []);
79
+ const onExportClick = useCallback(() => {
80
+ var _a;
81
+ const dataUri = (_a = stageRef === null || stageRef === void 0 ? void 0 : stageRef.current) === null || _a === void 0 ? void 0 : _a.toDataURL();
82
+ downloadURI(dataUri, 'image.png');
83
+ }, []);
84
+ const onClear = useCallback(() => {
85
+ var _a;
86
+ dispatch({ type: 'SET_RECTANGLES', payload: { rectangles: [] } });
87
+ dispatch({ type: 'SET_CIRCLES', payload: { circles: [] } });
88
+ dispatch({ type: 'SET_SCRIBBLES', payload: { scribbles: [] } });
89
+ dispatch({ type: 'SET_ARROWS', payload: { arrows: [] } });
90
+ dispatch({ type: 'SET_IMAGE', payload: { image: undefined, saveHistory: true } });
91
+ setCropZone(undefined);
92
+ setDrawAction(DrawAction.Select);
93
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
94
+ }, []);
95
+ const removeOrDuplicateShape = useCallback((isDuplicate) => {
96
+ var _a;
97
+ if (!selectedShape)
98
+ return;
99
+ switch (selectedShape) {
100
+ case ShapeObject.Arrows: {
101
+ const currSelect = arrows.findIndex(item => item.id === selectedId);
102
+ if (isDuplicate)
103
+ dispatch({
104
+ type: 'SET_ARROWS',
105
+ payload: {
106
+ saveHistory: true,
107
+ arrows: [
108
+ ...arrows,
109
+ Object.assign(Object.assign({}, arrows[currSelect]), { points: arrows[currSelect].points.map((item, idx) => idx % 2 === 0 ? item + 25 : item + 10), id: uuidV4() }),
110
+ ],
111
+ },
112
+ });
113
+ else {
114
+ const cloneObject = cloneDeep(arrows);
115
+ cloneObject.splice(currSelect, 1);
116
+ dispatch({ type: 'SET_ARROWS', payload: { saveHistory: true, arrows: cloneObject } });
117
+ }
118
+ break;
119
+ }
120
+ case ShapeObject.Scribbles: {
121
+ const currSelect = scribbles.findIndex(item => item.id === selectedId);
122
+ if (isDuplicate) {
123
+ dispatch({
124
+ type: 'SET_SCRIBBLES',
125
+ payload: {
126
+ saveHistory: true,
127
+ scribbles: [
128
+ ...scribbles,
129
+ Object.assign(Object.assign({}, scribbles[currSelect]), { points: scribbles[currSelect].points.map(item => item + 25), id: uuidV4() }),
130
+ ],
131
+ },
132
+ });
133
+ }
134
+ else {
135
+ const cloneObject = cloneDeep(scribbles);
136
+ cloneObject.splice(currSelect, 1);
137
+ dispatch({
138
+ type: 'SET_SCRIBBLES',
139
+ payload: { saveHistory: true, scribbles: cloneObject },
140
+ });
141
+ }
142
+ break;
143
+ }
144
+ case ShapeObject.Rectangles: {
145
+ const currSelect = rectangles.findIndex(item => item.id === selectedId);
146
+ if (isDuplicate) {
147
+ dispatch({
148
+ type: 'SET_RECTANGLES',
149
+ payload: {
150
+ saveHistory: true,
151
+ rectangles: [
152
+ ...rectangles,
153
+ Object.assign(Object.assign({}, rectangles[currSelect]), { x: rectangles[currSelect].x + 25, y: rectangles[currSelect].y + 25, id: uuidV4() }),
154
+ ],
155
+ },
156
+ });
157
+ }
158
+ else {
159
+ const cloneObject = cloneDeep(rectangles);
160
+ cloneObject.splice(currSelect, 1);
161
+ dispatch({
162
+ type: 'SET_RECTANGLES',
163
+ payload: { saveHistory: true, rectangles: cloneObject },
164
+ });
165
+ }
166
+ break;
167
+ }
168
+ case ShapeObject.Circles: {
169
+ const currSelect = circles.findIndex(item => item.id === selectedId);
170
+ if (isDuplicate) {
171
+ dispatch({
172
+ type: 'SET_CIRCLES',
173
+ payload: {
174
+ saveHistory: true,
175
+ circles: [
176
+ ...circles,
177
+ Object.assign(Object.assign({}, circles[currSelect]), { x: circles[currSelect].x + 25, y: circles[currSelect].y + 25, id: uuidV4() }),
178
+ ],
179
+ },
180
+ });
181
+ }
182
+ else {
183
+ const cloneObject = cloneDeep(circles);
184
+ cloneObject.splice(currSelect, 1);
185
+ dispatch({ type: 'SET_CIRCLES', payload: { saveHistory: true, circles: cloneObject } });
186
+ }
187
+ break;
188
+ }
189
+ case ShapeObject.Image: {
190
+ if (isDuplicate)
191
+ return;
192
+ dispatch({ type: 'SET_IMAGE', payload: { image: undefined, saveHistory: true } });
193
+ break;
194
+ }
195
+ }
196
+ setSelectedId(undefined);
197
+ setSelectedShape(undefined);
198
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
199
+ }, [arrows, circles, rectangles, scribbles, selectedId, selectedShape]);
200
+ useKeyEventCanvas('keydown', event => {
201
+ var _a, _b;
202
+ event.preventDefault();
203
+ switch (true) {
204
+ case event.code === 'Delete':
205
+ removeOrDuplicateShape();
206
+ break;
207
+ case event.code === 'KeyD' && event.ctrlKey:
208
+ removeOrDuplicateShape(true);
209
+ break;
210
+ case event.code === 'KeyZ' && event.ctrlKey && !event.shiftKey:
211
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
212
+ dispatch({ type: 'UNDO' });
213
+ break;
214
+ case event.code === 'KeyZ' && event.ctrlKey && event.shiftKey:
215
+ (_b = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _b === void 0 ? void 0 : _b.nodes([]);
216
+ dispatch({ type: 'REDO' });
217
+ break;
218
+ case event.code === 'KeyV' && event.ctrlKey:
219
+ navigator.clipboard.read().then(clipboardContents => {
220
+ if (!clipboardContents[0].types.includes('image/png'))
221
+ return;
222
+ clipboardContents[0]
223
+ .getType('image/png')
224
+ .then(blob => changeImage(URL.createObjectURL(blob)));
225
+ });
226
+ break;
227
+ default:
228
+ break;
229
+ }
230
+ });
231
+ useDropFile('#image-editor', e => {
232
+ var _a, _b;
233
+ if (!((_a = e === null || e === void 0 ? void 0 : e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files[0]))
234
+ return;
235
+ changeImage(URL.createObjectURL((_b = e === null || e === void 0 ? void 0 : e.dataTransfer) === null || _b === void 0 ? void 0 : _b.files[0]));
236
+ });
237
+ const onStageMouseUp = useCallback(() => {
238
+ var _a, _b, _c;
239
+ isPaintRef.current = false;
240
+ if (drawAction !== DrawAction.Select)
241
+ dispatch({ type: 'SAVE_HISTORY' });
242
+ if (selectionPosition.visible) {
243
+ setSelectionPosition(prev => (Object.assign(Object.assign({}, prev), { visible: false })));
244
+ const shapes = (_a = stageRef.current) === null || _a === void 0 ? void 0 : _a.find('.shape');
245
+ const box = (_b = selectionRectangleRef.current) === null || _b === void 0 ? void 0 : _b.getClientRect();
246
+ const selected = shapes.filter(shape => Konva.default.Util.haveIntersection(box, shape.getClientRect()));
247
+ (_c = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _c === void 0 ? void 0 : _c.nodes(selected);
248
+ }
249
+ }, [drawAction, selectionPosition.visible]);
250
+ const currentShapeRef = useRef();
251
+ const onStageMouseDown = useCallback(() => {
252
+ if (drawAction !== DrawAction.Crop)
253
+ setCropZone(undefined);
254
+ // if (drawAction === DrawAction.Select) return;
255
+ isPaintRef.current = true;
256
+ const stage = stageRef === null || stageRef === void 0 ? void 0 : stageRef.current;
257
+ const pos = stage === null || stage === void 0 ? void 0 : stage.getPointerPosition();
258
+ const x = (pos === null || pos === void 0 ? void 0 : pos.x) || 0;
259
+ const y = (pos === null || pos === void 0 ? void 0 : pos.y) || 0;
260
+ const id = uuidV4();
261
+ currentShapeRef.current = id;
262
+ switch (drawAction) {
263
+ case DrawAction.Select: {
264
+ setSelectionPosition({ x, y, width: 1, height: 1, visible: false });
265
+ break;
266
+ }
267
+ case DrawAction.Scribble: {
268
+ dispatch({
269
+ type: 'SET_SCRIBBLES',
270
+ payload: {
271
+ scribbles: [...scribbles, Object.assign({ id, points: [x, y], color }, DEFAULT_TRANSFORM)],
272
+ },
273
+ });
274
+ break;
275
+ }
276
+ case DrawAction.Circle: {
277
+ dispatch({
278
+ type: 'SET_CIRCLES',
279
+ payload: {
280
+ circles: [...circles, Object.assign({ id, x, y, radius: 1, color }, DEFAULT_TRANSFORM)],
281
+ },
282
+ });
283
+ break;
284
+ }
285
+ case DrawAction.Rectangle: {
286
+ dispatch({
287
+ type: 'SET_RECTANGLES',
288
+ payload: {
289
+ rectangles: [
290
+ ...rectangles,
291
+ Object.assign({ id, x, y, width: 1, height: 1, color }, DEFAULT_TRANSFORM),
292
+ ],
293
+ },
294
+ });
295
+ break;
296
+ }
297
+ case DrawAction.Crop: {
298
+ setCropZone({ id, x, y, width: 1, height: 1 });
299
+ break;
300
+ }
301
+ case DrawAction.Arrow: {
302
+ dispatch({
303
+ type: 'SET_ARROWS',
304
+ payload: {
305
+ arrows: [...arrows, Object.assign({ id, points: [x, y, x, y], color }, DEFAULT_TRANSFORM)],
306
+ },
307
+ });
308
+ break;
309
+ }
310
+ }
311
+ }, [drawAction, color, arrows, circles, rectangles, scribbles]);
312
+ const onStageMouseMove = useCallback(() => {
313
+ if (!isPaintRef.current)
314
+ return;
315
+ const stage = stageRef === null || stageRef === void 0 ? void 0 : stageRef.current;
316
+ const id = currentShapeRef.current;
317
+ const pos = stage === null || stage === void 0 ? void 0 : stage.getPointerPosition();
318
+ const x = (pos === null || pos === void 0 ? void 0 : pos.x) || 0;
319
+ const y = (pos === null || pos === void 0 ? void 0 : pos.y) || 0;
320
+ switch (drawAction) {
321
+ case DrawAction.Select: {
322
+ // setSelectionVisible(true);
323
+ setSelectionPosition(prev => (Object.assign(Object.assign({}, prev), { width: x - prev.x, height: y - prev.y, visible: Math.abs(x - prev.x) > 4 || Math.abs(y - prev.y) > 4 })));
324
+ break;
325
+ }
326
+ case DrawAction.Scribble: {
327
+ dispatch({
328
+ type: 'SET_SCRIBBLES',
329
+ payload: {
330
+ scribbles: scribbles === null || scribbles === void 0 ? void 0 : scribbles.map(prevScribble => prevScribble.id === id
331
+ ? Object.assign(Object.assign({}, prevScribble), { points: [...prevScribble.points, x, y] }) : prevScribble),
332
+ },
333
+ });
334
+ break;
335
+ }
336
+ case DrawAction.Circle: {
337
+ dispatch({
338
+ type: 'SET_CIRCLES',
339
+ payload: {
340
+ circles: circles === null || circles === void 0 ? void 0 : circles.map(prevCircle => prevCircle.id === id
341
+ ? Object.assign(Object.assign({}, prevCircle), { radius: Math.pow((Math.pow((x - prevCircle.x), 2) + Math.pow((y - prevCircle.y), 2)), 0.5) }) : prevCircle),
342
+ },
343
+ });
344
+ break;
345
+ }
346
+ case DrawAction.Rectangle: {
347
+ dispatch({
348
+ type: 'SET_RECTANGLES',
349
+ payload: {
350
+ rectangles: rectangles === null || rectangles === void 0 ? void 0 : rectangles.map(prevRectangle => prevRectangle.id === id
351
+ ? Object.assign(Object.assign({}, prevRectangle), { height: y - prevRectangle.y, width: x - prevRectangle.x }) : prevRectangle),
352
+ },
353
+ });
354
+ break;
355
+ }
356
+ case DrawAction.Crop: {
357
+ setCropZone(prevRectangle => prevRectangle
358
+ ? Object.assign(Object.assign({}, prevRectangle), { height: y - prevRectangle.y, width: x - prevRectangle.x }) : prevRectangle);
359
+ break;
360
+ }
361
+ case DrawAction.Arrow: {
362
+ dispatch({
363
+ type: 'SET_ARROWS',
364
+ payload: {
365
+ arrows: arrows.map(prevArrow => prevArrow.id === id
366
+ ? Object.assign(Object.assign({}, prevArrow), { points: [prevArrow.points[0], prevArrow.points[1], x, y] }) : prevArrow),
367
+ },
368
+ });
369
+ break;
370
+ }
371
+ }
372
+ }, [drawAction, arrows, circles, rectangles, scribbles]);
373
+ const onApplyCropClick = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
374
+ var _a;
375
+ if (!cropZone)
376
+ return;
377
+ const dataUri = yield ((_a = stageRef === null || stageRef === void 0 ? void 0 : stageRef.current) === null || _a === void 0 ? void 0 : _a.toDataURL({
378
+ x: (cropZone === null || cropZone === void 0 ? void 0 : cropZone.x) + 2,
379
+ y: (cropZone === null || cropZone === void 0 ? void 0 : cropZone.y) + 2,
380
+ width: (cropZone === null || cropZone === void 0 ? void 0 : cropZone.width) - 4,
381
+ height: (cropZone === null || cropZone === void 0 ? void 0 : cropZone.height) - 4,
382
+ }));
383
+ onClear();
384
+ changeImage(dataUri);
385
+ }), [cropZone, changeImage, onClear]);
386
+ const onShapeClick = useCallback((e, id, shape) => {
387
+ var _a;
388
+ if (drawAction !== DrawAction.Select)
389
+ return;
390
+ const { currentTarget } = e || {};
391
+ setSelectedId(id);
392
+ setSelectedShape(shape);
393
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([currentTarget]);
394
+ }, [drawAction]);
395
+ const isDraggable = drawAction === DrawAction.Select;
396
+ const onBgClick = useCallback(() => {
397
+ var _a;
398
+ if (selectionPosition.visible)
399
+ return;
400
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
401
+ }, [selectionPosition.visible]);
402
+ return (React.createElement("div", { style: { width: `${width}px` } },
403
+ React.createElement(Flex, { justify: "space-between", align: "center", style: { marginBottom: '6px' } },
404
+ React.createElement(Flex, { gap: 4 },
405
+ PAINT_OPTIONS.map(({ id, label, icon }) => (React.createElement(IconButtonStyled, { key: id, "aria-label": label, icon: React.createElement(Icon, { type: icon, style: { fontSize: '16px' } }), onClick: () => {
406
+ var _a;
407
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
408
+ setCropZone(undefined);
409
+ setDrawAction(id);
410
+ }, type: id === drawAction ? 'primary' : 'default' }))),
411
+ React.createElement(Popover, { content: React.createElement(SketchPicker, { color: color, onChangeComplete: selectedColor => setColor(selectedColor.hex) }) },
412
+ React.createElement("div", { style: {
413
+ backgroundColor: color,
414
+ height: '30px',
415
+ width: '30px',
416
+ borderRadius: '3px',
417
+ cursor: 'pointer',
418
+ } })),
419
+ React.createElement(IconButtonStyled, { "aria-label": "Clear", icon: React.createElement(Icon, { type: "icon-ants-remove-circle", style: { fontSize: '16px' } }), onClick: onClear })),
420
+ React.createElement(Flex, { gap: 4, align: "center", style: { height: '100%' } },
421
+ React.createElement(IconButtonStyled, { "aria-label": "Help", icon: React.createElement(Icon, { type: "icon-ants-help", style: { fontSize: '16px' } }), onClick: () => setOpenHelper(true) }),
422
+ React.createElement("input", { type: "file", ref: fileRef, onChange: onImportImageSelect, style: { display: 'none' }, accept: "image/*" }),
423
+ React.createElement(Button, { onClick: onImportImageClick }, "Import Image"),
424
+ React.createElement(Button, { onClick: onExportClick }, "Export"))),
425
+ React.createElement(Flex, { gap: 4 },
426
+ React.createElement(Button, { onClick: () => {
427
+ var _a;
428
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
429
+ setDrawAction(DrawAction.Crop);
430
+ }, type: DrawAction.Crop === drawAction ? 'primary' : 'default' }, "Crop"),
431
+ DrawAction.Crop === drawAction ? (React.createElement(React.Fragment, null,
432
+ React.createElement(Button, { onClick: () => setCropZone(undefined), danger: true }, "Cancel Crop"),
433
+ React.createElement(Button, { onClick: () => onApplyCropClick() }, "Apply Crop"))) : null,
434
+ React.createElement(Button, { onClick: () => {
435
+ var _a;
436
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
437
+ dispatch({ type: 'UNDO' });
438
+ }, style: { marginLeft: 'auto' } }, "Undo"),
439
+ React.createElement(Button, { onClick: () => {
440
+ var _a;
441
+ (_a = transformerRef === null || transformerRef === void 0 ? void 0 : transformerRef.current) === null || _a === void 0 ? void 0 : _a.nodes([]);
442
+ dispatch({ type: 'REDO' });
443
+ } }, "Redo")),
444
+ React.createElement(Flex, { style: {
445
+ width: `${width}px`,
446
+ height: `${height}px`,
447
+ border: '1px solid #666',
448
+ marginTop: '16px',
449
+ } },
450
+ React.createElement(Stage, { id: "image-editor", width: width, height: height, ref: stageRef, onMouseUp: onStageMouseUp, onMouseDown: onStageMouseDown, onMouseMove: onStageMouseMove },
451
+ React.createElement(Layer, null,
452
+ React.createElement(KonvaRect, { x: 0, y: 0, height: width, width: height, fill: "white", id: "bg", onClick: onBgClick }),
453
+ image && (React.createElement(KonvaImage, Object.assign({ image: image }, imageCoordinate, { name: "shape", onClick: e => onShapeClick(e, imageCoordinate.id, ShapeObject.Image), onTap: e => onShapeClick(e, imageCoordinate.id, ShapeObject.Image), draggable: isDraggable, onDragEnd: ({ target: { attrs } }) => {
454
+ if (isDraggable)
455
+ dispatch({
456
+ type: 'SET_IMAGE_COORDINATE',
457
+ payload: {
458
+ imageCoordinate: Object.assign(Object.assign({}, imageCoordinate), { x: attrs.x, y: attrs.y }),
459
+ saveHistory: true,
460
+ },
461
+ });
462
+ }, onTransformEnd: e => {
463
+ dispatch({
464
+ type: 'SET_IMAGE_COORDINATE',
465
+ payload: {
466
+ imageCoordinate: Object.assign(Object.assign(Object.assign({}, imageCoordinate), { width: e.target.width(), height: e.target.height() }), getTransformData(e)),
467
+ saveHistory: true,
468
+ },
469
+ });
470
+ } }))),
471
+ arrows.map(arrow => (React.createElement(KonvaArrow, Object.assign({ key: arrow.id }, arrow, { name: "shape", fill: arrow.color, stroke: arrow.color, strokeWidth: 4, onClick: e => onShapeClick(e, arrow.id, ShapeObject.Arrows), onTap: e => onShapeClick(e, arrow.id, ShapeObject.Arrows), draggable: isDraggable, strokeScaleEnabled: false, onDragEnd: ({ target: { attrs } }) => {
472
+ if (isDraggable)
473
+ dispatch({
474
+ type: 'SET_ARROWS',
475
+ payload: {
476
+ arrows: arrows.map(item => item.id === arrow.id ? Object.assign(Object.assign({}, item), { x: attrs.x, y: attrs.y }) : item),
477
+ saveHistory: true,
478
+ },
479
+ });
480
+ }, onTransformEnd: e => {
481
+ dispatch({
482
+ type: 'SET_ARROWS',
483
+ payload: {
484
+ arrows: arrows.map(item => item.id === arrow.id
485
+ ? Object.assign(Object.assign(Object.assign({}, item), { points: e.target.attrs.points }), getTransformData(e)) : item),
486
+ saveHistory: true,
487
+ },
488
+ });
489
+ } })))),
490
+ rectangles.map(rectangle => (React.createElement(KonvaRect, Object.assign({ key: rectangle.id }, rectangle, { name: "shape", stroke: rectangle === null || rectangle === void 0 ? void 0 : rectangle.color, strokeWidth: 4, onClick: e => onShapeClick(e, rectangle.id, ShapeObject.Rectangles), onTap: e => onShapeClick(e, rectangle.id, ShapeObject.Rectangles), draggable: isDraggable, strokeScaleEnabled: false, onDragEnd: ({ target: { attrs } }) => {
491
+ if (isDraggable)
492
+ dispatch({
493
+ type: 'SET_RECTANGLES',
494
+ payload: {
495
+ rectangles: rectangles.map(item => item.id === rectangle.id ? Object.assign(Object.assign({}, item), { x: attrs.x, y: attrs.y }) : item),
496
+ saveHistory: true,
497
+ },
498
+ });
499
+ }, onTransformEnd: e => {
500
+ dispatch({
501
+ type: 'SET_RECTANGLES',
502
+ payload: {
503
+ rectangles: rectangles.map(item => item.id === rectangle.id
504
+ ? Object.assign(Object.assign(Object.assign({}, item), { width: e.target.width(), height: e.target.height() }), getTransformData(e)) : item),
505
+ saveHistory: true,
506
+ },
507
+ });
508
+ } })))),
509
+ circles.map(circle => (React.createElement(KonvaCircle, Object.assign({ key: circle.id }, circle, { name: "shape", stroke: circle === null || circle === void 0 ? void 0 : circle.color, strokeWidth: 4, onClick: e => onShapeClick(e, circle.id, ShapeObject.Circles), onTap: e => onShapeClick(e, circle.id, ShapeObject.Circles), draggable: isDraggable, strokeScaleEnabled: false, onDragEnd: ({ target: { attrs } }) => {
510
+ if (isDraggable)
511
+ dispatch({
512
+ type: 'SET_CIRCLES',
513
+ payload: {
514
+ circles: circles.map(item => item.id === circle.id ? Object.assign(Object.assign({}, item), { x: attrs.x, y: attrs.y }) : item),
515
+ saveHistory: true,
516
+ },
517
+ });
518
+ }, onTransformEnd: e => {
519
+ dispatch({
520
+ type: 'SET_CIRCLES',
521
+ payload: {
522
+ circles: circles.map(item => item.id === circle.id
523
+ ? Object.assign(Object.assign(Object.assign({}, item), { radius: e.target.attrs.radius }), getTransformData(e)) : item),
524
+ saveHistory: true,
525
+ },
526
+ });
527
+ } })))),
528
+ scribbles.map(scribble => (React.createElement(KonvaLine, Object.assign({ key: scribble.id }, scribble, { name: "shape", lineCap: "round", lineJoin: "round", stroke: scribble === null || scribble === void 0 ? void 0 : scribble.color, strokeWidth: 4, onClick: e => onShapeClick(e, scribble.id, ShapeObject.Scribbles), onTap: e => onShapeClick(e, scribble.id, ShapeObject.Scribbles), draggable: isDraggable, strokeScaleEnabled: false, onDragEnd: ({ target: { attrs } }) => {
529
+ if (isDraggable)
530
+ dispatch({
531
+ type: 'SET_SCRIBBLES',
532
+ payload: {
533
+ scribbles: scribbles.map(item => item.id === scribble.id ? Object.assign(Object.assign({}, item), { x: attrs.x, y: attrs.y }) : item),
534
+ saveHistory: true,
535
+ },
536
+ });
537
+ }, onTransformEnd: e => {
538
+ dispatch({
539
+ type: 'SET_SCRIBBLES',
540
+ payload: {
541
+ scribbles: scribbles.map(item => item.id === scribble.id
542
+ ? Object.assign(Object.assign(Object.assign({}, item), { points: e.target.attrs.points }), getTransformData(e)) : item),
543
+ saveHistory: true,
544
+ },
545
+ });
546
+ } })))),
547
+ cropZone ? (React.createElement(KonvaRect, Object.assign({}, cropZone, { dashEnabled: true, dash: [6], stroke: "red", strokeWidth: 2 }))) : null,
548
+ React.createElement(KonvaRect, Object.assign({}, selectionPosition, { ref: selectionRectangleRef, fill: "rgba(0,0,200,0.4)" })),
549
+ React.createElement(Transformer, { ref: transformerRef, ignoreStroke: true }))),
550
+ DrawAction.Select === drawAction && selectedId ? (React.createElement(Flex, { style: { marginLeft: '16px' } },
551
+ React.createElement(Flex, { vertical: true, gap: 4 },
552
+ React.createElement(Button, { onClick: () => removeOrDuplicateShape(), icon: React.createElement(Icon, { type: "icon-ants-outline-delete" }), danger: true }),
553
+ React.createElement(Button, { onClick: () => removeOrDuplicateShape(true), icon: React.createElement(Icon, { type: "icon-ants-copy-report" }) })))) : null,
554
+ React.createElement(ModalShortcut, { open: openHelper, onCancel: () => setOpenHelper(false) }))));
555
+ });
@@ -0,0 +1,80 @@
1
+ import { Arrow, Circle, Rectangle, Scribble } from './types';
2
+ type ImageCoordinate = {
3
+ x: number;
4
+ y: number;
5
+ rotation: number;
6
+ id: string;
7
+ width?: number;
8
+ height?: number;
9
+ scaleX: number;
10
+ scaleY: number;
11
+ skewX: number;
12
+ skewY: number;
13
+ };
14
+ interface CanvasState {
15
+ scribbles: Scribble[];
16
+ rectangles: Rectangle[];
17
+ circles: Circle[];
18
+ arrows: Arrow[];
19
+ image?: HTMLImageElement;
20
+ imageCoordinate: ImageCoordinate;
21
+ }
22
+ type ReducerAction = {
23
+ type: 'SET_SCRIBBLES';
24
+ payload: {
25
+ scribbles: Scribble[];
26
+ saveHistory?: boolean;
27
+ };
28
+ } | {
29
+ type: 'SET_RECTANGLES';
30
+ payload: {
31
+ rectangles: Rectangle[];
32
+ saveHistory?: boolean;
33
+ };
34
+ } | {
35
+ type: 'SET_CIRCLES';
36
+ payload: {
37
+ circles: Circle[];
38
+ saveHistory?: boolean;
39
+ };
40
+ } | {
41
+ type: 'SET_ARROWS';
42
+ payload: {
43
+ arrows: Arrow[];
44
+ saveHistory?: boolean;
45
+ };
46
+ } | {
47
+ type: 'SET_IMAGE';
48
+ payload: {
49
+ image: HTMLImageElement | undefined;
50
+ saveHistory?: boolean;
51
+ };
52
+ } | {
53
+ type: 'SET_IMAGE_COORDINATE';
54
+ payload: {
55
+ imageCoordinate: ImageCoordinate;
56
+ saveHistory?: boolean;
57
+ };
58
+ } | {
59
+ type: 'SET_NEW_STATE';
60
+ payload: Partial<CanvasState> & {
61
+ saveHistory?: boolean;
62
+ };
63
+ } | {
64
+ type: 'SAVE_HISTORY';
65
+ payload?: {};
66
+ } | {
67
+ type: 'UNDO';
68
+ payload?: {};
69
+ } | {
70
+ type: 'REDO';
71
+ payload?: {};
72
+ };
73
+ interface HistoryReducer {
74
+ history: CanvasState[];
75
+ historyStep: number;
76
+ current: CanvasState;
77
+ }
78
+ export declare const initialReducer: HistoryReducer;
79
+ export declare const reducer: (state: HistoryReducer, action: ReducerAction) => HistoryReducer;
80
+ export {};
@@ -0,0 +1,51 @@
1
+ import { IMAGE_COORDINATE } from './constants';
2
+ const initialState = {
3
+ scribbles: [],
4
+ rectangles: [],
5
+ arrows: [],
6
+ circles: [],
7
+ image: undefined,
8
+ imageCoordinate: IMAGE_COORDINATE,
9
+ };
10
+ export const initialReducer = {
11
+ history: [initialState],
12
+ historyStep: 0,
13
+ current: initialState,
14
+ };
15
+ export const reducer = (state, action) => {
16
+ const { type, payload } = action;
17
+ switch (type) {
18
+ case 'SET_SCRIBBLES':
19
+ case 'SET_RECTANGLES':
20
+ case 'SET_CIRCLES':
21
+ case 'SET_ARROWS':
22
+ case 'SET_IMAGE':
23
+ case 'SET_IMAGE_COORDINATE':
24
+ case 'SET_NEW_STATE': {
25
+ const newHistoryStep = state.historyStep + 1;
26
+ const newState = Object.assign(Object.assign({}, state.current), payload);
27
+ return Object.assign(Object.assign({}, state), { history: payload.saveHistory
28
+ ? state.history.slice(0, newHistoryStep).concat([newState])
29
+ : state.history, historyStep: payload.saveHistory ? newHistoryStep : state.historyStep, current: newState });
30
+ }
31
+ case 'SAVE_HISTORY': {
32
+ return Object.assign(Object.assign({}, state), { historyStep: state.historyStep + 1, history: state.history.slice(0, state.historyStep + 1).concat([state.current]) });
33
+ break;
34
+ }
35
+ case 'UNDO': {
36
+ if (state.historyStep === 0) {
37
+ return state;
38
+ }
39
+ return Object.assign(Object.assign({}, state), { historyStep: state.historyStep - 1, current: state.history[state.historyStep - 1] });
40
+ }
41
+ case 'REDO': {
42
+ if (state.historyStep === state.history.length - 1) {
43
+ return state;
44
+ }
45
+ return Object.assign(Object.assign({}, state), { historyStep: state.historyStep + 1, current: state.history[state.historyStep + 1] });
46
+ }
47
+ default:
48
+ break;
49
+ }
50
+ return state;
51
+ };
@@ -0,0 +1,4 @@
1
+ /// <reference types="react" />
2
+ export declare const IconButtonStyled: import("styled-components").StyledComponent<import("react").ForwardRefExoticComponent<import("antd").ButtonProps & import("react").RefAttributes<HTMLElement>> & {
3
+ Group: import("react").FC<import("antd/es/button").ButtonGroupProps>;
4
+ }, any, {}, never>;
@@ -0,0 +1,8 @@
1
+ import styled from 'styled-components';
2
+ import { Button } from '../../atoms';
3
+ export const IconButtonStyled = styled(Button) `
4
+ &.antsomi-btn-default.antsomi-btn-icon-only {
5
+ width: 30px !important;
6
+ height: 30px !important;
7
+ }
8
+ `;
@@ -0,0 +1,35 @@
1
+ export type Shape = {
2
+ id: string;
3
+ color: string;
4
+ x?: number;
5
+ y?: number;
6
+ rotation: number;
7
+ scaleX: number;
8
+ scaleY: number;
9
+ skewX: number;
10
+ skewY: number;
11
+ };
12
+ export type Rectangle = Shape & {
13
+ width: number;
14
+ height: number;
15
+ x: number;
16
+ y: number;
17
+ };
18
+ export type Circle = Shape & {
19
+ radius: number;
20
+ x: number;
21
+ y: number;
22
+ };
23
+ export type Scribble = Shape & {
24
+ points: number[];
25
+ };
26
+ export type Arrow = Shape & {
27
+ points: [number, number, number, number];
28
+ };
29
+ export type CropZone = {
30
+ id: string;
31
+ x: number;
32
+ y: number;
33
+ width: number;
34
+ height: number;
35
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { KonvaEventObject } from 'konva/lib/Node';
2
+ /**
3
+ * Creates a link with the specified URI and name, triggers a download, and removes the link from the document.
4
+ *
5
+ * @param {string | undefined} uri - The URI to download from.
6
+ * @param {string} name - The name to save the file as.
7
+ */
8
+ export declare const downloadURI: (uri: string | undefined, name: string) => void;
9
+ /**
10
+ * Returns an object with specific transformation data extracted from the Konva event object.
11
+ *
12
+ * @param e - The Konva event object containing transformation data.
13
+ * @return An object with x, y, rotation, scaleX, scaleY, skewX, and skewY properties.
14
+ */
15
+ export declare const getTransformData: (e: KonvaEventObject<Event>) => {
16
+ x: any;
17
+ y: any;
18
+ rotation: any;
19
+ scaleX: any;
20
+ scaleY: any;
21
+ skewX: any;
22
+ skewY: any;
23
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Creates a link with the specified URI and name, triggers a download, and removes the link from the document.
3
+ *
4
+ * @param {string | undefined} uri - The URI to download from.
5
+ * @param {string} name - The name to save the file as.
6
+ */
7
+ export const downloadURI = (uri, name) => {
8
+ const link = document.createElement('a');
9
+ link.download = name;
10
+ link.href = uri || '';
11
+ document.body.appendChild(link);
12
+ link.click();
13
+ document.body.removeChild(link);
14
+ };
15
+ /**
16
+ * Returns an object with specific transformation data extracted from the Konva event object.
17
+ *
18
+ * @param e - The Konva event object containing transformation data.
19
+ * @return An object with x, y, rotation, scaleX, scaleY, skewX, and skewY properties.
20
+ */
21
+ export const getTransformData = (e) => ({
22
+ x: e.target.attrs.x,
23
+ y: e.target.attrs.y,
24
+ rotation: e.target.attrs.rotation,
25
+ scaleX: e.target.attrs.scaleX,
26
+ scaleY: e.target.attrs.scaleY,
27
+ skewX: e.target.attrs.skewX,
28
+ skewY: e.target.attrs.skewY,
29
+ });
@@ -2,16 +2,12 @@ import React, { useMemo } from 'react';
2
2
  import { Image } from 'antd';
3
3
  import { Button } from '@antscorp/antsomi-ui/es/components';
4
4
  import Icon from '@antscorp/icons';
5
- import { checkShowSkeletonBaseUrl } from '@antscorp/antsomi-ui/es/utils';
5
+ import { checkShowSkeletonBaseUrl, getUrlNoCache } from '@antscorp/antsomi-ui/es/utils';
6
6
  export const BigImage = React.memo(props => {
7
7
  const { url, isDefaultThumbnail, isHideDefaultButton, index, showSkeleton, onClickDefaultButton, } = props;
8
8
  const currentTime = useMemo(() => new Date(), []);
9
9
  const timeGenerated = Math.floor(currentTime.getTime() / 1000 - (currentTime.getTime() % 3)) * 1000;
10
- const urlNoCache = url
11
- ? url.includes('nocache') || url.startsWith('data:image')
12
- ? url
13
- : `${url}?nocache=${timeGenerated}`
14
- : undefined;
10
+ const urlNoCache = getUrlNoCache(url, timeGenerated);
15
11
  const showSkeletonMemo = showSkeleton !== undefined ? showSkeleton : checkShowSkeletonBaseUrl(urlNoCache);
16
12
  return (React.createElement("div", { className: `image-container--big ${!showSkeletonMemo ? 'hide-skeleton' : ''}` },
17
13
  url ? (React.createElement(Image, { preview: false, width: "100%", height: "100%", src: urlNoCache })) : null,
@@ -1,15 +1,11 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { Image } from 'antd';
3
- import { checkShowSkeletonBaseUrl } from '@antscorp/antsomi-ui/es/utils';
3
+ import { checkShowSkeletonBaseUrl, getUrlNoCache } from '@antscorp/antsomi-ui/es/utils';
4
4
  export const SmallImage = React.memo(props => {
5
5
  const { url, isSelected, index, onClick, showSkeleton } = props;
6
6
  const currentTime = useMemo(() => new Date(), []);
7
7
  const timeGenerated = Math.floor(currentTime.getTime() / 1000 - (currentTime.getTime() % 3)) * 1000;
8
- const urlNoCache = url
9
- ? url.includes('nocache') || url.startsWith('data:image')
10
- ? url
11
- : `${url}?nocache=${timeGenerated}`
12
- : undefined;
8
+ const urlNoCache = getUrlNoCache(url, timeGenerated);
13
9
  const showSkeletonMemo = showSkeleton !== undefined ? showSkeleton : checkShowSkeletonBaseUrl(urlNoCache);
14
10
  return (React.createElement("div", { className: `image-container--small ${isSelected ? 'image-container--selected' : ''} ${!showSkeletonMemo ? 'hide-skeleton' : ''}`, onClick: () => onClick && onClick(index) }, url ? (React.createElement(Image, { preview: false, width: "100%", height: "100%",
15
11
  // src={imageUrl}
@@ -17,6 +17,7 @@ export { RadioGroup } from './RadioGroup';
17
17
  export { InputSearch } from './InputSearch';
18
18
  export { UploadImage } from './UploadImage';
19
19
  export { IconSelection } from './IconSelection';
20
+ export { ImageEditor as ImageResize } from './ImageEditor';
20
21
  export { IconSelectionRenderer } from './IconSelection/components/Icon';
21
22
  export { AlignSetting } from './AlignSetting';
22
23
  export { EdgeSetting } from './EdgeSetting';
@@ -17,6 +17,7 @@ export { RadioGroup } from './RadioGroup';
17
17
  export { InputSearch } from './InputSearch';
18
18
  export { UploadImage } from './UploadImage';
19
19
  export { IconSelection } from './IconSelection';
20
+ export { ImageEditor as ImageResize } from './ImageEditor';
20
21
  export { IconSelectionRenderer } from './IconSelection/components/Icon';
21
22
  export { AlignSetting } from './AlignSetting';
22
23
  export { EdgeSetting } from './EdgeSetting';
@@ -16,3 +16,6 @@ export * from './useUnmount';
16
16
  export * from './createEffectWithTarget ';
17
17
  export * from './useEffectWithTarget';
18
18
  export * from './useMutationObserver';
19
+ export * from './useKeyEventCanvas';
20
+ export * from './useDragEvent';
21
+ export * from './useDropFile';
package/es/hooks/index.js CHANGED
@@ -35,3 +35,6 @@ export * from './useUnmount';
35
35
  export * from './createEffectWithTarget ';
36
36
  export * from './useEffectWithTarget';
37
37
  export * from './useMutationObserver';
38
+ export * from './useKeyEventCanvas';
39
+ export * from './useDragEvent';
40
+ export * from './useDropFile';
@@ -0,0 +1 @@
1
+ export declare const useDragEvent: (keyEvent: 'dragenter' | 'dragexit' | 'dragover' | 'drop', selector: string, callback: (event: DragEvent) => void) => void;
@@ -0,0 +1,14 @@
1
+ import { useEffect } from 'react';
2
+ export const useDragEvent = (keyEvent, selector, callback) => {
3
+ useEffect(() => {
4
+ var _a;
5
+ const handleDragEvent = (event) => {
6
+ callback(event);
7
+ };
8
+ (_a = document.querySelector(selector)) === null || _a === void 0 ? void 0 : _a.addEventListener(keyEvent, handleDragEvent);
9
+ return () => {
10
+ var _a;
11
+ (_a = document.querySelector(selector)) === null || _a === void 0 ? void 0 : _a.removeEventListener(keyEvent, handleDragEvent);
12
+ };
13
+ }, [keyEvent, selector, callback]);
14
+ };
@@ -0,0 +1 @@
1
+ export declare const useDropFile: (selector: string, callback: (event: DragEvent) => void) => void;
@@ -0,0 +1,20 @@
1
+ import { useDragEvent } from './useDragEvent';
2
+ export const useDropFile = (selector, callback) => {
3
+ useDragEvent('dragenter', selector, e => {
4
+ e.stopPropagation();
5
+ e.preventDefault();
6
+ });
7
+ useDragEvent('dragover', selector, e => {
8
+ e.stopPropagation();
9
+ e.preventDefault();
10
+ });
11
+ useDragEvent('dragexit', selector, e => {
12
+ e.stopPropagation();
13
+ e.preventDefault();
14
+ });
15
+ useDragEvent('drop', selector, e => {
16
+ e.stopPropagation();
17
+ e.preventDefault();
18
+ callback(e);
19
+ });
20
+ };
@@ -0,0 +1 @@
1
+ export declare const useKeyEventCanvas: (keyEvent: 'keydown' | 'keyup', callback: (event: KeyboardEvent) => void) => void;
@@ -0,0 +1,20 @@
1
+ import { useEffect, useRef } from 'react';
2
+ export const useKeyEventCanvas = (keyEvent, callback) => {
3
+ const lastDownTarget = useRef(null);
4
+ useEffect(() => {
5
+ const handleKeyDown = (event) => {
6
+ if (lastDownTarget.current.localName !== 'canvas')
7
+ return;
8
+ callback(event);
9
+ };
10
+ const setLastDownTarget = event => {
11
+ lastDownTarget.current = event.target;
12
+ };
13
+ document.addEventListener('mousedown', setLastDownTarget);
14
+ document.addEventListener(keyEvent, handleKeyDown);
15
+ return () => {
16
+ document.removeEventListener(keyEvent, handleKeyDown);
17
+ document.removeEventListener('mousedown', setLastDownTarget);
18
+ };
19
+ }, [keyEvent, callback]);
20
+ };
@@ -84,4 +84,4 @@ export declare const simplifyString: (str: string) => string;
84
84
  */
85
85
  export declare const searchStringQuery: (str: string, queryStr: string) => boolean;
86
86
  export declare const isExistedImage: (imageUrl: string) => boolean;
87
- export declare const getUrlNoCache: (url?: string, cacheValue?: number | string) => string | undefined;
87
+ export declare const getUrlNoCache: (url?: string, cacheValue?: number | string) => string;
@@ -476,10 +476,13 @@ export const isExistedImage = (imageUrl) => {
476
476
  }
477
477
  };
478
478
  export const getUrlNoCache = (url = '', cacheValue = 0) => {
479
- const urlNoCache = url
480
- ? url.includes('nocache') || url.startsWith('data:image')
481
- ? url
482
- : `${url}?nocache=${cacheValue}`
483
- : undefined;
479
+ let urlNoCache = url;
480
+ if (urlNoCache.includes('nocache') || url.startsWith('data:image')) {
481
+ urlNoCache = url;
482
+ }
483
+ else {
484
+ const newUrl = new URL(url);
485
+ newUrl.searchParams.append('nocache', `${cacheValue}`);
486
+ }
484
487
  return urlNoCache;
485
488
  };
@@ -10,5 +10,20 @@ export const getCategoriesFromObjectTemplate = (objectTemplate) => {
10
10
  });
11
11
  return categories;
12
12
  };
13
- export const checkShowSkeletonBaseUrl = (url = '') => `${url || ''}`.includes('base64-img') &&
14
- ['jn_thumb_', 'v_thumb_'].every(prefix => !`${url || ''}`.includes(prefix));
13
+ export const checkShowSkeletonBaseUrl = (url = '') => {
14
+ if (url.includes('type')) {
15
+ switch (true) {
16
+ case url.includes('media'):
17
+ return true;
18
+ case url.includes('email'): {
19
+ return false;
20
+ }
21
+ default:
22
+ return false;
23
+ }
24
+ }
25
+ else {
26
+ return (`${url || ''}`.includes('base64-img') &&
27
+ ['jn_thumb_', 'v_thumb_'].every(prefix => !`${url || ''}`.includes(prefix)));
28
+ }
29
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antscorp/antsomi-ui",
3
- "version": "1.3.5-beta.232",
3
+ "version": "1.3.5-beta.234",
4
4
  "description": "An enterprise-class UI design language and React UI library.",
5
5
  "sideEffects": [
6
6
  "dist/*",
@@ -93,6 +93,7 @@
93
93
  "i18next": "21.6.16",
94
94
  "i18next-browser-languagedetector": "6.1.2",
95
95
  "immer": "3.0.0",
96
+ "konva": "^9.3.6",
96
97
  "lodash": "^4.17.21",
97
98
  "moment": "2.29.2",
98
99
  "pako": "2.0.4",
@@ -103,6 +104,7 @@
103
104
  "react-cookie": "^7.1.4",
104
105
  "react-draggable": "^4.4.5",
105
106
  "react-frame-component": "^5.2.6",
107
+ "react-konva": "^18.2.10",
106
108
  "react-markdown": "^8.0.7",
107
109
  "react-resizable": "^3.0.5",
108
110
  "react-syntax-highlighter": "^15.5.0",
@@ -115,7 +117,8 @@
115
117
  "uniqid": "^5.4.0",
116
118
  "use-context-selector": "^1.4.4",
117
119
  "use-immer": "^0.9.0",
118
- "zustand": "^4.5.2"
120
+ "zustand": "^4.5.2",
121
+ "use-image": "^1.1.1"
119
122
  },
120
123
  "devDependencies": {
121
124
  "@ant-design/cssinjs": "^1.6.2",