@antscorp/antsomi-ui 2.0.89 → 2.0.90

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.
@@ -69,6 +69,7 @@ export const formats = [
69
69
  'image',
70
70
  'color',
71
71
  'code-block',
72
+ 'imageBlot',
72
73
  ];
73
74
  const ToolBarWrapper = styled.div `
74
75
  border: 1px solid ${props => props.borderColor} !important;
@@ -1,7 +1,12 @@
1
1
  import 'quill/dist/quill.snow.css';
2
+ export declare enum TCallBack {
3
+ LOADING_UPLOAD = "LOADING_UPLOAD",
4
+ ERROR = "error"
5
+ }
2
6
  type TEditorProps = {
3
7
  value: string;
4
8
  onChange: (value: string) => void;
9
+ callback?: (type: TCallBack, value: string | boolean) => void;
5
10
  placeholder: string;
6
11
  uploadService?: Function;
7
12
  height?: string | number;
@@ -9,5 +14,5 @@ type TEditorProps = {
9
14
  borderColor?: string;
10
15
  isRoundCorner?: boolean;
11
16
  };
12
- export declare const QuillEditor: ({ value, onChange, placeholder, uploadService, height, maxImgHeight, borderColor, isRoundCorner, }: TEditorProps) => import("react/jsx-runtime").JSX.Element;
17
+ export declare const QuillEditor: ({ value, onChange, placeholder, uploadService, height, maxImgHeight, borderColor, isRoundCorner, callback, }: TEditorProps) => import("react/jsx-runtime").JSX.Element;
13
18
  export {};
@@ -1,48 +1,29 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef } from 'react';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
3
  import Quill from 'quill';
4
- import { ImageHandler } from 'quill-upload';
4
+ import { isFunction } from 'lodash';
5
+ import QuillImageDropAndPaste from 'quill-image-drop-and-paste';
5
6
  // Hooks
6
7
  import debounce from 'lodash/debounce';
7
8
  // Components
8
9
  import EditorToolbar, { modules, formats } from './EditorToolbar';
9
10
  // Styles
10
11
  import 'quill/dist/quill.snow.css';
11
- import styled from 'styled-components';
12
- Quill.register('modules/imageHandler', ImageHandler);
12
+ import { EditorWrapper, LoadingOverlay, QuillWrapper } from './styled';
13
+ import { Spin } from '../../atoms';
13
14
  const Block = Quill.import('blots/block');
14
15
  Block.tagName = 'DIV';
15
16
  Quill.register(Block, true);
16
- // const toolbarOptions = [
17
- // ['bold', 'italic', 'underline', 'strike'],
18
- // ['blockquote', 'code-block'],
19
- // ['link', 'image', 'video', 'formula'],
20
- // [{ header: 1 }, { header: 2 }],
21
- // [{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }],
22
- // [{ script: 'sub' }, { script: 'super' }],
23
- // [{ indent: '-1' }, { indent: '+1' }],
24
- // [{ direction: 'rtl' }],
25
- // [{ size: ['small', false, 'large', 'huge'] }],
26
- // [{ header: [1, 2, 3, 4, 5, 6, false] }],
27
- // [{ color: [] }, { background: [] }],
28
- // [{ font: [] }],
29
- // [{ align: [] }],
30
- // ['clean'],
31
- // ];
32
- const EditorWrapper = styled.div `
33
- border: 1px solid ${props => props.borderColor} !important;
34
-
35
- .ql-editor.ql-blank::before {
36
- font-style: normal !important;
37
- }
38
-
39
- .ql-editor a {
40
- color: #06c !important;
41
- }
42
- `;
43
- export const QuillEditor = ({ value, onChange, placeholder = '', uploadService, height = 'auto', maxImgHeight = '300px', borderColor = '#d4d4d4', isRoundCorner, }) => {
17
+ Quill.register('modules/imageDropAndPaste', QuillImageDropAndPaste);
18
+ export var TCallBack;
19
+ (function (TCallBack) {
20
+ TCallBack["LOADING_UPLOAD"] = "LOADING_UPLOAD";
21
+ TCallBack["ERROR"] = "error";
22
+ })(TCallBack || (TCallBack = {}));
23
+ export const QuillEditor = ({ value, onChange, placeholder = '', uploadService, height = 'auto', maxImgHeight = '300px', borderColor = '#d4d4d4', isRoundCorner, callback = () => { }, }) => {
44
24
  const editor = useRef(null);
45
25
  const rand = useRef(String(Math.random()).replace(/\./g, ''));
26
+ const [isUploading, setIsUploading] = useState(false);
46
27
  const processText = () => {
47
28
  editor.current?.root.querySelectorAll('li').forEach(li => {
48
29
  let max = 0;
@@ -56,22 +37,76 @@ export const QuillEditor = ({ value, onChange, placeholder = '', uploadService,
56
37
  const debounceQuickProcess = debounce(processText, 100);
57
38
  const debounceChange = debounce(onChange, 500);
58
39
  // Upload handler function
59
- const _onUpload = async (file, resolve) => {
40
+ const _onUpload = async (file) => {
60
41
  try {
42
+ setIsUploading(true);
43
+ callback(TCallBack.LOADING_UPLOAD, true);
61
44
  const result = await uploadService({ files: [file], mode: 'file' });
62
45
  if (result?.data?.length) {
63
- resolve(result.data[0].url);
64
- editor.current.root.querySelectorAll('img.quill-upload-progress').forEach(el => {
65
- el.classList.remove('quill-upload-progress');
66
- el.style.maxHeight = maxImgHeight;
67
- });
46
+ return result.data[0].url;
47
+ }
48
+ return '';
49
+ }
50
+ finally {
51
+ setIsUploading(false);
52
+ isFunction(callback) && callback(TCallBack.LOADING_UPLOAD, false);
53
+ }
54
+ };
55
+ const handlerDropImage = async (_imageDataUrl, _type, imageData) => {
56
+ callback(TCallBack.ERROR, 'RESET');
57
+ const file = imageData.toFile();
58
+ if (!editor.current)
59
+ return;
60
+ if (file.size > 10 * 1024 * 1024) {
61
+ callback(TCallBack.ERROR, 'FILE_TOO_LARGE');
62
+ return;
63
+ }
64
+ const range = editor.current?.getSelection();
65
+ if (!range)
66
+ return;
67
+ // Insert loading placeholder
68
+ try {
69
+ const fileUrl = await _onUpload(file);
70
+ if (fileUrl) {
71
+ const currentRange = editor.current?.getSelection();
72
+ editor.current?.insertEmbed(currentRange?.index || range.index, 'image', fileUrl, Quill.sources.USER);
68
73
  }
69
- resolve('');
70
74
  }
71
75
  catch (error) {
72
- resolve('');
76
+ callback(TCallBack.ERROR, 'UPLOAD_FAILED');
73
77
  }
74
78
  };
79
+ const imageHandler = useCallback(() => {
80
+ callback(TCallBack.ERROR, 'RESET');
81
+ const input = document.createElement('input');
82
+ input.setAttribute('type', 'file');
83
+ input.setAttribute('accept', 'image/*');
84
+ input.click();
85
+ input.onchange = async () => {
86
+ if (input !== null && input.files !== null) {
87
+ if (!editor.current)
88
+ return;
89
+ const file = input.files[0];
90
+ if (file.size > 10 * 1024 * 1024) {
91
+ callback(TCallBack.ERROR, 'FILE_TOO_LARGE');
92
+ return;
93
+ }
94
+ const range = editor.current?.getSelection();
95
+ if (!range)
96
+ return;
97
+ try {
98
+ const fileUrl = await _onUpload(file);
99
+ if (fileUrl) {
100
+ const currentRange = editor.current?.getSelection();
101
+ editor.current?.insertEmbed(currentRange?.index || range.index, 'image', fileUrl, Quill.sources.USER);
102
+ }
103
+ }
104
+ catch (error) {
105
+ callback(TCallBack.ERROR, 'UPLOAD_FAILED');
106
+ }
107
+ }
108
+ };
109
+ }, []);
75
110
  useEffect(() => {
76
111
  if (!value && editor.current) {
77
112
  editor.current.setText('');
@@ -80,22 +115,21 @@ export const QuillEditor = ({ value, onChange, placeholder = '', uploadService,
80
115
  useEffect(() => {
81
116
  if (editor.current)
82
117
  return;
118
+ const configModule = modules(`toolbar-${rand.current}`);
83
119
  editor.current = new Quill(`#editor-${rand.current}`, {
84
120
  theme: 'snow',
85
121
  modules: {
86
- ...modules(`toolbar-${rand.current}`),
87
- imageHandler: uploadService
88
- ? {
89
- imageClass: 'custom-image-class',
90
- upload: file => new Promise(resolve => {
91
- if (file.size > 10 * 1024 * 1024) {
92
- resolve('');
93
- return;
94
- }
95
- _onUpload(file, resolve);
96
- }),
97
- }
98
- : undefined,
122
+ ...configModule,
123
+ toolbar: {
124
+ ...configModule.toolbar,
125
+ handlers: {
126
+ ...configModule.toolbar.handlers,
127
+ image: imageHandler,
128
+ },
129
+ },
130
+ imageDropAndPaste: {
131
+ handler: handlerDropImage,
132
+ },
99
133
  },
100
134
  formats,
101
135
  placeholder,
@@ -111,8 +145,9 @@ export const QuillEditor = ({ value, onChange, placeholder = '', uploadService,
111
145
  else {
112
146
  debounceChange(editor.current?.root.innerHTML || '');
113
147
  }
148
+ callback(TCallBack.ERROR, 'RESET');
114
149
  }
115
150
  });
116
151
  }, []);
117
- return (_jsxs(_Fragment, { children: [_jsx(EditorToolbar, { id: `toolbar-${rand.current}`, isRoundCorner: isRoundCorner, borderColor: borderColor }), _jsx(EditorWrapper, { id: `editor-${rand.current}`, style: { height, color: '#000000' }, borderColor: borderColor })] }));
152
+ return (_jsxs(QuillWrapper, { children: [_jsx(EditorToolbar, { id: `toolbar-${rand.current}`, isRoundCorner: isRoundCorner, borderColor: borderColor }), _jsx(EditorWrapper, { id: `editor-${rand.current}`, style: { height, color: '#000000' }, borderColor: borderColor, children: isUploading && (_jsx(LoadingOverlay, { children: _jsx(Spin, { spinning: isUploading }) })) })] }));
118
153
  };
@@ -0,0 +1,5 @@
1
+ export declare const QuillWrapper: import("styled-components").StyledComponent<"div", any, {}, never>;
2
+ export declare const EditorWrapper: import("styled-components").StyledComponent<"div", any, {
3
+ borderColor: string;
4
+ }, never>;
5
+ export declare const LoadingOverlay: import("styled-components").StyledComponent<"div", any, {}, never>;
@@ -0,0 +1,29 @@
1
+ import styled from 'styled-components';
2
+ export const QuillWrapper = styled.div `
3
+ position: relative;
4
+ `;
5
+ export const EditorWrapper = styled.div `
6
+ position: relative;
7
+ border: 1px solid ${props => props.borderColor} !important;
8
+
9
+ .ql-editor.ql-blank::before {
10
+ font-style: normal !important;
11
+ }
12
+
13
+ .ql-editor a {
14
+ color: #06c !important;
15
+ }
16
+ `;
17
+ export const LoadingOverlay = styled.div `
18
+ position: absolute;
19
+ top: 0;
20
+ left: 0;
21
+ right: 0;
22
+ bottom: 0;
23
+ background-color: rgba(255, 255, 255, 0.8);
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ z-index: 1000;
28
+ backdrop-filter: blur(2px);
29
+ `;
@@ -30,6 +30,7 @@ import { BugIcon, CameraIcon, OpenUrlIcon, RequestIcon, ScreenshotMonitorIcon, }
30
30
  import { base64ToFile, convertBlobToFile, generateUniqueId, mergeAudioStreams, } from '@antscorp/antsomi-ui/es/components/molecules/CaptureScreen/utils';
31
31
  import { expendDefault, formatParams, postCustomEvent } from './utils';
32
32
  import { useGetListUser } from './queries';
33
+ import { TCallBack } from '../../molecules/QuillEditor/QuillEditor';
33
34
  const antIcon = _jsx(LoadingOutlined, { style: { fontSize: 40 }, spin: true });
34
35
  const Loading = ({ isLoading, height, width }) => isLoading && (_jsx(WrapperLoading, { className: "loader-container", height: height, width: width, children: _jsx(Spin, { indicator: antIcon }) }));
35
36
  const Help = props => {
@@ -93,6 +94,7 @@ const Help = props => {
93
94
  const streamTracks = useRef();
94
95
  const allAppOptionsRef = useRef([]);
95
96
  const [fileInputKey, setFileInputKey] = useState(0);
97
+ const [isLoadingUpload, setIsLoadingUpload] = useState(false);
96
98
  // const showModal = (captureKey: CaptureTypeProps) => {
97
99
  // setOpen(true);
98
100
  // setCaptureType(captureKey);
@@ -250,12 +252,17 @@ const Help = props => {
250
252
  }
251
253
  }, [allAppOptions, config, domainTicket, token, userId]);
252
254
  const handleOnchangeFile = e => {
253
- const limitSize = 50 * 1024 * 1024;
254
- const sizeFile = e.target?.files[0]?.size;
255
+ const file = e.target?.files[0];
256
+ if (!file)
257
+ return;
258
+ const sizeFile = file.size;
259
+ const fileType = file.type;
260
+ const isImage = fileType.startsWith('image/');
261
+ const limitSize = isImage ? 10 * 1024 * 1024 : 50 * 1024 * 1024;
255
262
  if (sizeFile >= limitSize) {
256
263
  setErrFile({
257
264
  isError: true,
258
- message: '*Maximum upload file size: 50MB',
265
+ message: `Maximum file size is reached (10MB for image & 50MB for other types).`,
259
266
  });
260
267
  }
261
268
  else {
@@ -265,6 +272,7 @@ const Help = props => {
265
272
  });
266
273
  handleUploadFile(e.target.files[0]);
267
274
  }
275
+ setFileInputKey(fileInputKey + 1);
268
276
  };
269
277
  const handleRemoveFile = token => {
270
278
  let newListFile = valueInput.files;
@@ -837,10 +845,13 @@ const Help = props => {
837
845
  domain: domainUpload,
838
846
  token,
839
847
  userId,
840
- }), onChange: content => setValueInput(prevVal => ({
841
- ...prevVal,
842
- message: content,
843
- })), placeholder: "Enter your comment...", borderColor: "#e6e6e6", isRoundCorner: false, height: 159 }), _jsxs("div", { children: [valueInput.files?.length > 0 && (_jsx(WrapperLinkFiles, { children: valueInput.files?.map((file, index) => (_jsxs(WrapperLinkItemFiles, { children: [_jsxs("span", { style: {
848
+ }), onChange: content => {
849
+ setValueInput(prevVal => ({
850
+ ...prevVal,
851
+ message: content,
852
+ }));
853
+ setIsLoadingUpload(content.includes('<img src="data:image/png;base64'));
854
+ }, callback: callbackEditor, placeholder: "Enter your comment...", borderColor: "#e6e6e6", isRoundCorner: false, height: 159 }), _jsxs("div", { children: [valueInput.files?.length > 0 && (_jsx(WrapperLinkFiles, { children: valueInput.files?.map((file, index) => (_jsxs(WrapperLinkItemFiles, { children: [_jsxs("span", { style: {
844
855
  display: 'flex',
845
856
  alignItems: 'center',
846
857
  gap: '10px',
@@ -877,9 +888,35 @@ const Help = props => {
877
888
  }
878
889
  }
879
890
  };
880
- const isDisableButtonSend = useMemo(() => {
881
- return !valueInput?.title || !valueInput?.message;
882
- }, [valueInput]);
891
+ const isDisableButtonSend = useMemo(() => !valueInput?.title || !valueInput?.message || isLoadingUpload, [valueInput, isLoadingUpload]);
892
+ const callbackEditor = (type, value) => {
893
+ switch (type) {
894
+ case TCallBack.LOADING_UPLOAD:
895
+ setIsLoadingUpload(value);
896
+ break;
897
+ case TCallBack.ERROR: {
898
+ switch (value) {
899
+ case 'FILE_TOO_LARGE':
900
+ setErrFile({
901
+ isError: true,
902
+ message: '*Maximum upload file size: 10MB',
903
+ });
904
+ break;
905
+ case 'RESET':
906
+ setErrFile({
907
+ isError: false,
908
+ message: '',
909
+ });
910
+ break;
911
+ default:
912
+ break;
913
+ }
914
+ break;
915
+ }
916
+ default:
917
+ break;
918
+ }
919
+ };
883
920
  const renderPopupLayout = (type) => (_jsxs(_Fragment, { children: [_jsx(Loading, { isLoading: isMainLoading || isFetchingUsers, width: "100%", height: "100%" }), _jsx(WrapperHeader, { className: "cursor", children: _jsx(Header, { children: title }) }), _jsx(WrapperBody, { className: "popup-content", style: {
884
921
  padding: type === REPORT_TYPES.CHAT ||
885
922
  type === REPORT_TYPES.HELP ||
@@ -11,6 +11,8 @@ import Service from './Service';
11
11
  import { MESSAGE_TYPE, TICKET_CUSTOM_MESSAGE_KEY } from './constant';
12
12
  import { WrapperContent, WrapperContentInput, WrapperEditor, WrapperIconEditor, WrapperInputFile, WrapperLable, WrapperLeftContent, WrapperLinkItemFiles, WrapperMessageContent, WrapperRightContent, WrapperTextEdit, WrapperTextInput, DrawerHeader, } from './styled';
13
13
  import { formatAccountId, formatDatarender, formatParams, handleValidateContent, postCustomEvent, } from './util';
14
+ import { TCallBack } from '../../molecules/QuillEditor/QuillEditor';
15
+ import { isBoolean } from 'lodash';
14
16
  const initValueInput = {
15
17
  originTitle: 'Create new ticket',
16
18
  title: '',
@@ -50,23 +52,6 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
50
52
  const isUpdate = action === 'edit' && ticketId;
51
53
  const valueInputRef = useRef(valueInput);
52
54
  valueInputRef.current = valueInput;
53
- // const isRulesEdit = useMemo(() => {
54
- // const { title, feature, ticketType, priority, category, ownerId, followers, message, files, referenceUrl } = valueInput
55
- // if (
56
- // !title?.length && referenceUrl === window.location.href
57
- // && Object.keys(feature)?.length <= 2
58
- // && Object.keys(ticketType)?.length <= 2 && (!Object.keys(priority)?.length || Object.values(priority)[0] === '-')
59
- // && Object.keys(category)?.length <= 2 && ((!ownerId?.length && accountEdit === 4 && isAllAccount) || (ownerId[0]?.userId === Number(initOwnerId || userId)))
60
- // && followers?.length === accountManage?.length
61
- // && !message?.length && !files.length && !isUpdate
62
- // ) {
63
- // return false
64
- // }else if(isUpdate && compareArrays(valueInput.followers, ticketDetails.followers)
65
- // && !valueInput?.message?.length && !valueInput?.files?.length) {
66
- // return false
67
- // }
68
- // return true
69
- // }, [valueInput])
70
55
  const updateValueInput = value => {
71
56
  setValueInput(prev => ({
72
57
  ...prev,
@@ -248,7 +233,7 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
248
233
  const handleEditorChange = content => {
249
234
  setTextValue(content);
250
235
  updateValueInput({ message: content });
251
- setIsLoadingUpload(content.includes('<img src="data:image/png;base64'));
236
+ // setIsLoadingUpload(content.includes('<img src="data:image/png;base64'));
252
237
  };
253
238
  const handleUpdateFollowers = arrFolowers => {
254
239
  const params = {
@@ -331,12 +316,17 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
331
316
  };
332
317
  }, [isOpenToast]);
333
318
  const handleOnchangeFile = e => {
334
- const limitSize = 50 * 1024 * 1024;
335
- const sizeFile = e.target?.files[0]?.size;
319
+ const file = e.target?.files[0];
320
+ if (!file)
321
+ return;
322
+ const sizeFile = file.size;
323
+ const fileType = file.type;
324
+ const isImage = fileType.startsWith('image/');
325
+ const limitSize = isImage ? 10 * 1024 * 1024 : 50 * 1024 * 1024;
336
326
  if (sizeFile >= limitSize) {
337
327
  setErrFile({
338
328
  isError: true,
339
- message: '*Maximum upload file size: 50MB',
329
+ message: `Maximum file size is reached (10MB for image & 50MB for other types).`,
340
330
  });
341
331
  }
342
332
  else {
@@ -363,34 +353,58 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
363
353
  });
364
354
  setFileInputKey(prev => prev + 1);
365
355
  };
356
+ const callbackEditor = (type, value) => {
357
+ switch (type) {
358
+ case TCallBack.LOADING_UPLOAD:
359
+ if (isBoolean(value)) {
360
+ setIsLoadingUpload(value);
361
+ }
362
+ break;
363
+ case TCallBack.ERROR: {
364
+ switch (value) {
365
+ case 'FILE_TOO_LARGE':
366
+ setErrFile({
367
+ isError: true,
368
+ message: '*Maximum upload file size: 10MB',
369
+ });
370
+ break;
371
+ case 'RESET':
372
+ setErrFile({
373
+ isError: false,
374
+ message: '',
375
+ });
376
+ break;
377
+ default:
378
+ break;
379
+ }
380
+ break;
381
+ }
382
+ default:
383
+ break;
384
+ }
385
+ };
366
386
  return (_jsxs("div", { style: { height: '100%' }, children: [_jsxs(Helmet, { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("title", { children: browserTitle })] }), _jsxs(Spin, { style: { height: '100vh' }, spinning: isLoading ||
367
387
  isLoadingDetails ||
368
388
  isLoadingDataSouce ||
369
389
  isLoadingFollower ||
370
390
  isLoadingSelectedAccount, children: [_jsxs(DrawerHeader, { children: [_jsx(EditableName, { value: valueInput.title, onChange: handleOnchangeTitle, readonly: isUpdate, error: isEmptyField && (!valueInput.title || !valueInput.title.trim())
371
391
  ? "Title can't be empty"
372
- : '' }), _jsx("div", { id: "header-journey-right", className: "right-content", children: !isUpdate && (_jsx(Button, { type: "primary", onClick: handleSubmit, children: "Save" })) })] }), _jsxs(WrapperContent, { style: { height: '100%' }, children: [_jsxs(WrapperLeftContent, { children: [_jsxs(WrapperContentInput, { style: { alignItems: 'flex-start' }, children: [_jsxs(WrapperLable, { children: ["Owner", _jsx("span", { style: { color: '#ff0000' }, children: "*" })] }), _jsxs("div", { children: [_jsx(SelectAccount, { type: "default", initData: (valueInput?.ownerId || []).map((item) => item.userId), nameKey: "userName", userIdKey: "userId", users: listUsers, isViewMode: isUpdate || !isAllAccount || accountEdit !== 4, onChange: (_, users) => updateValueInput({ ownerId: users }), onlyOne: true }), isEmptyField && !valueInput?.ownerId?.length && (_jsx("div", { className: "error-message", children: "*This field can't be empty" }))] })] }), _jsxs(WrapperContentInput, { style: { marginTop: '15px', alignItems: 'flex-start' }, children: [_jsx(WrapperLable, { children: "Follower(s)" }), _jsx(SelectAccount, { type: "default", initData: [
392
+ : '' }), _jsx("div", { id: "header-journey-right", className: "right-content", children: !isUpdate && (_jsx(Button, { disabled: isLoadingUpload, type: "primary", onClick: handleSubmit, children: "Save" })) })] }), _jsxs(WrapperContent, { style: { height: '100%' }, children: [_jsxs(WrapperLeftContent, { children: [_jsxs(WrapperContentInput, { style: { alignItems: 'flex-start' }, children: [_jsxs(WrapperLable, { children: ["Owner", _jsx("span", { style: { color: '#ff0000' }, children: "*" })] }), _jsxs("div", { children: [_jsx(SelectAccount, { type: "default", initData: (valueInput?.ownerId || []).map((item) => item.userId), nameKey: "userName", userIdKey: "userId", users: listUsers, isViewMode: isUpdate || !isAllAccount || accountEdit !== 4, onChange: (_, users) => updateValueInput({ ownerId: users }), onlyOne: true }), isEmptyField && !valueInput?.ownerId?.length && (_jsx("div", { className: "error-message", children: "*This field can't be empty" }))] })] }), _jsxs(WrapperContentInput, { style: { marginTop: '15px', alignItems: 'flex-start' }, children: [_jsx(WrapperLable, { children: "Follower(s)" }), _jsx(SelectAccount, { type: "default", initData: [
373
393
  ...accountManage,
374
394
  ...(valueInput?.followers || []).map((item) => item.userId),
375
- ], nameKey: "userName", userIdKey: "userId", users: listUsers, disabledAccount: accountManage, onChange: (_, followers) => updateValueInput({ followers }) })] }), dataSelects?.map((data) => (
376
- // eslint-disable-next-line react/jsx-key
377
- _jsxs(WrapperContentInput, { style: { marginTop: '15px' }, children: [_jsx(WrapperLable, { children: data.title }), _jsx("div", { style: { flex: 1 }, children: isUpdate ? (_jsx(WrapperTextEdit, { children: !valueInput[data?.value]?.name?.length
395
+ ], nameKey: "userName", userIdKey: "userId", users: listUsers, disabledAccount: accountManage, onChange: (_, followers) => updateValueInput({ followers }) })] }), dataSelects?.map((data) => (_jsxs(WrapperContentInput, { style: { marginTop: '15px' }, children: [_jsx(WrapperLable, { children: data.title }), _jsx("div", { style: { flex: 1 }, children: isUpdate ? (_jsx(WrapperTextEdit, { children: !valueInput[data?.value]?.name?.length
378
396
  ? '--'
379
- : valueInput[data?.value]?.name })) : (_jsx(DropdownComponent, { data: data.field_options, setValueInput: updateValueInput, valueInput: valueInput, type: data.value, isUpdate: isUpdate, title: data.title })) })] }, data.id))), _jsxs(WrapperContentInput, { style: { marginTop: '15px' }, children: [_jsx(WrapperLable, { htmlFor: "referenceUrl", children: "Reference URL" }), isUpdate ? (valueInput.referenceUrl ? (_jsx(Tooltip, { title: valueInput.referenceUrl, placement: "top", children: _jsx(WrapperTextEdit, { color: "#005fb8", href: valueInput.referenceUrl, target: "_blank", children: valueInput.referenceUrl }) })) : (_jsx(WrapperTextEdit, { color: "#005fb8", href: valueInput.referenceUrl, target: "_blank", children: valueInput.referenceUrl }))) : (_jsx(WrapperTextInput, { placeholder: "Reference URL", id: "referenceUrl",
380
- // width="300px"
381
- onChange: handleOnchangeInput, name: "referenceUrl", value: valueInput.referenceUrl }))] })] }), _jsxs(WrapperRightContent, { children: [_jsxs(WrapperEditor, { children: [_jsxs("div", { children: [_jsx(QuillEditor, { value: textValue, uploadService: Service.tickets.callApi.uploadImg({
397
+ : valueInput[data?.value]?.name })) : (_jsx(DropdownComponent, { data: data.field_options, setValueInput: updateValueInput, valueInput: valueInput, type: data.value, isUpdate: isUpdate, title: data.title })) })] }, data.id))), _jsxs(WrapperContentInput, { style: { marginTop: '15px' }, children: [_jsx(WrapperLable, { htmlFor: "referenceUrl", children: "Reference URL" }), isUpdate ? (valueInput.referenceUrl ? (_jsx(Tooltip, { title: valueInput.referenceUrl, placement: "top", children: _jsx(WrapperTextEdit, { color: "#005fb8", href: valueInput.referenceUrl, target: "_blank", children: valueInput.referenceUrl }) })) : (_jsx(WrapperTextEdit, { color: "#005fb8", href: valueInput.referenceUrl, target: "_blank", children: valueInput.referenceUrl }))) : (_jsx(WrapperTextInput, { placeholder: "Reference URL", id: "referenceUrl", onChange: handleOnchangeInput, name: "referenceUrl", value: valueInput.referenceUrl }))] })] }), _jsxs(WrapperRightContent, { children: [_jsxs(WrapperEditor, { children: [_jsxs("div", { children: [_jsx(QuillEditor, { value: textValue, uploadService: Service.tickets.callApi.uploadImg({
382
398
  domain: domainUpload,
383
399
  token,
384
400
  userId,
385
- }), onChange: handleEditorChange, placeholder: "Enter your comment...", height: 195 }), _jsxs("div", { children: [valueInput.files?.length > 0 && (_jsx(WrapperLinkItemFiles, { children: valueInput.files?.map((file, index) => (_jsxs("div", { className: "file-item", children: [_jsxs("div", { className: "file-name-group", children: [_jsx(Icon, { className: "file-icon", type: "icon-ants-attachment" }), _jsx(Tooltip, { title: file?.file_name, children: _jsx("span", { className: "file-name", children: file?.file_name }) })] }), _jsx(Icon, { onClick: () => {
401
+ }), onChange: handleEditorChange, callback: callbackEditor, placeholder: "Enter your comment...", height: 195 }), _jsxs("div", { children: [valueInput.files?.length > 0 && (_jsx(WrapperLinkItemFiles, { children: valueInput.files?.map((file, index) => (_jsxs("div", { className: "file-item", children: [_jsxs("div", { className: "file-name-group", children: [_jsx(Icon, { className: "file-icon", type: "icon-ants-attachment" }), _jsx(Tooltip, { title: file?.file_name, children: _jsx("span", { className: "file-name", children: file?.file_name }) })] }), _jsx(Icon, { onClick: () => {
386
402
  handleRemoveFileV2(index);
387
403
  }, className: "remove-btn", type: "icon-ants-remove-slim" })] }, file?.file_name))) })), _jsxs(WrapperIconEditor, { borderTop: !!valueInput.files?.length, children: [_jsxs(WrapperInputFile, { children: [_jsx("label", { htmlFor: `fileImage-${fileInputKey}`, className: "upload-wrapper-label", children: _jsx(Icon, { type: "icon-ants-attachment", className: "upload-icon" }) }), _jsx("input", { type: "file", style: { position: 'absolute', top: 0, right: 0, display: 'none' }, name: `fileImage-${fileInputKey}`, id: `fileImage-${fileInputKey}`, onChange: handleOnchangeFile }, fileInputKey)] }), isUpdate && (_jsx(Button, { type: "primary", disabled: !handleValidateContent(textValue) || isLoadingUpload, className: "reply-btn", style: {
388
404
  background: `${!handleValidateContent(textValue) || isLoadingUpload
389
405
  ? '#ccc'
390
406
  : '#1f5fac'}`,
391
- }, onClick: handleUpdateComment, loading: isLoadingUpload, children: "Reply" }))] })] })] }), errFile.isError ? (_jsx("div", { className: "error-message", children: errFile.message })) : (isEmptyField &&
392
- !valueInput?.message && (_jsx("div", { className: "error-message", children: "*This field can't be empty" })))] }), isUpdate && (_jsx(Spin, { spinning: isLoadingComment, children: _jsx(WrapperMessageContent, { children: listComment?.map(comment => (_jsx(MessageComponent, { toUser: comment?.toUser, fromUser: comment?.fromUser, followers: comment?.followers,
393
- // mailFollower={comment?.mailFollower}
394
- message: comment?.message, date: comment?.createdDate, attachments: comment?.attachments, timeZone: timeZone, submitterEmail: comment?.submitterEmail }, comment?.id))) }) }))] })] })] })] }));
407
+ }, onClick: handleUpdateComment, children: "Reply" }))] })] })] }), errFile.isError ? (_jsx("div", { className: "error-message", children: errFile.message })) : (isEmptyField &&
408
+ !valueInput?.message && (_jsx("div", { className: "error-message", children: "*This field can't be empty" })))] }), isUpdate && (_jsx(Spin, { spinning: isLoadingComment, children: _jsx(WrapperMessageContent, { children: listComment?.map(comment => (_jsx(MessageComponent, { toUser: comment?.toUser, fromUser: comment?.fromUser, followers: comment?.followers, message: comment?.message, date: comment?.createdDate, attachments: comment?.attachments, timeZone: timeZone, submitterEmail: comment?.submitterEmail }, comment?.id))) }) }))] })] })] })] }));
395
409
  };
396
410
  export default Content;
@@ -19,6 +19,7 @@ const MessageComponent = ({ toUser, fromUser, followers, attachments, message, d
19
19
  if (refMesage.current) {
20
20
  refMesage.current.querySelectorAll('img')?.forEach(img => {
21
21
  img.style.cursor = 'pointer';
22
+ img.style.width = '100%';
22
23
  img.addEventListener('click', () => {
23
24
  setModalImg({
24
25
  ...modalImg,
@@ -424,6 +424,7 @@ export const WrapperLinkItemFiles = styled.div `
424
424
  display: flex;
425
425
  align-items: center;
426
426
  gap: 10px;
427
+ overflow: hidden;
427
428
  }
428
429
  .file-icon {
429
430
  font-size: 20.5px;
@@ -10,6 +10,7 @@ import Service from './Service';
10
10
  import { WrapperContent, WrapperContentInput, WrapperEditor, WrapperIconEditor, WrapperInputFile, WrapperLable, WrapperLeftContent, WrapperLinkItemFiles, WrapperMessageContent, WrapperRightContent, WrapperTextEdit, WrapperTextInput, } from './styled';
11
11
  import { formatDatarender, handleValidateContent } from './util';
12
12
  import { get, keyBy } from 'lodash';
13
+ import { TCallBack } from '../../molecules/QuillEditor/QuillEditor';
13
14
  const initValueInput = {
14
15
  originTitle: 'Create new ticket',
15
16
  title: '',
@@ -137,7 +138,7 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
137
138
  const handleEditorChange = content => {
138
139
  setTextValue(content);
139
140
  updateValueInput({ message: content });
140
- setIsLoadingUpload(content.includes('<img src="data:image/png;base64'));
141
+ // setIsLoadingUpload(content.includes('<img src="data:image/png;base64'));
141
142
  };
142
143
  const handleUpdateFollowers = arrFolowers => {
143
144
  const params = {
@@ -221,12 +222,17 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
221
222
  };
222
223
  }, [isOpenToast]);
223
224
  const handleOnchangeFile = e => {
224
- const limitSize = 50 * 1024 * 1024;
225
- const sizeFile = e.target?.files[0]?.size;
225
+ const file = e.target?.files[0];
226
+ if (!file)
227
+ return;
228
+ const sizeFile = file.size;
229
+ const fileType = file.type;
230
+ const isImage = fileType.startsWith('image/');
231
+ const limitSize = isImage ? 10 * 1024 * 1024 : 50 * 1024 * 1024;
226
232
  if (sizeFile >= limitSize) {
227
233
  setErrFile({
228
234
  isError: true,
229
- message: '*Maximum upload file size: 50MB',
235
+ message: `Maximum file size is reached (10MB for image & 50MB for other types).`,
230
236
  });
231
237
  }
232
238
  else {
@@ -267,15 +273,43 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
267
273
  ? '--'
268
274
  : dataSelectOptions[ticketDetails[info?.value]]?.name }) })] }, info.id));
269
275
  };
276
+ const callbackEditor = (type, value) => {
277
+ switch (type) {
278
+ case TCallBack.LOADING_UPLOAD:
279
+ setIsLoadingUpload(value);
280
+ break;
281
+ case TCallBack.ERROR: {
282
+ switch (value) {
283
+ case 'FILE_TOO_LARGE':
284
+ setErrFile({
285
+ isError: true,
286
+ message: '*Maximum upload file size: 10MB',
287
+ });
288
+ break;
289
+ case 'RESET':
290
+ setErrFile({
291
+ isError: false,
292
+ message: '',
293
+ });
294
+ break;
295
+ default:
296
+ break;
297
+ }
298
+ break;
299
+ }
300
+ default:
301
+ break;
302
+ }
303
+ };
270
304
  return (_jsxs("div", { style: { height: '100%' }, children: [_jsxs(Helmet, { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("title", { children: browserTitle })] }), _jsx(Spin, { style: { height: '100vh' }, spinning: isLoading || isLoadingDetails || isLoadingDataSouce || isLoadingFollower, children: _jsxs(WrapperContent, { style: { height: '100%' }, children: [_jsxs(WrapperLeftContent, { children: [_jsxs(WrapperContentInput, { style: { alignItems: 'flex-start' }, children: [_jsxs(WrapperLable, { children: ["Owner", _jsx("span", { style: { color: '#ff0000' }, children: "*" })] }), _jsx("div", { children: ownerEmail })] }), _jsxs(WrapperContentInput, { style: { marginTop: '15px', alignItems: 'flex-start' }, children: [_jsx(WrapperLable, { children: "Follower(s)" }), _jsx(SelectAccount, { type: "default", initData: ticketDetails?.followers || [], nameKey: "userName", userIdKey: "userId", users: listUsers, onChange: (_, followers) => onChangeFollowers(followers) })] }), dataSelects?.map((data) => _jsx(_Fragment, { children: renderOptions(data) })), _jsxs(WrapperContentInput, { style: { marginTop: '15px' }, children: [_jsx(WrapperLable, { htmlFor: "referenceUrl", children: "Reference URL" }), isUpdate ? (ticketDetails.referenceUrl ? (_jsx(Tooltip, { title: ticketDetails.referenceUrl, placement: "top", children: _jsx(WrapperTextEdit, { color: "#005fb8", href: ticketDetails.referenceUrl, target: "_blank", children: ticketDetails.referenceUrl }) })) : (_jsx(WrapperTextEdit, { color: "#005fb8", href: ticketDetails.referenceUrl, target: "_blank", children: ticketDetails.referenceUrl }))) : (_jsx(WrapperTextInput, { placeholder: "Reference URL", id: "referenceUrl",
271
305
  // width="300px"
272
- onChange: handleOnchangeInput, name: "referenceUrl", value: valueInput.referenceUrl }))] })] }), _jsxs(WrapperRightContent, { children: [_jsxs(WrapperEditor, { children: [_jsxs("div", { children: [_jsx(QuillEditor, { value: textValue, uploadService: Service.tickets.callApi.uploadFile({
306
+ onChange: handleOnchangeInput, name: "referenceUrl", value: valueInput.referenceUrl }))] })] }), _jsxs(WrapperRightContent, { children: [_jsxs(WrapperEditor, { children: [_jsxs(_Fragment, { children: [_jsx(QuillEditor, { value: textValue, uploadService: Service.tickets.callApi.uploadFile({
273
307
  domain: domainUpload,
274
- }), onChange: handleEditorChange, placeholder: "Enter your comment...", height: 195 }), _jsxs("div", { children: [valueInput.files?.length > 0 && (_jsx(WrapperLinkItemFiles, { children: valueInput.files?.map((file, index) => (_jsxs("div", { className: "file-item", children: [_jsxs("div", { className: "file-name-group", children: [_jsx(Icon, { className: "file-icon", type: "icon-ants-attachment" }), _jsx(Tooltip, { title: file?.file_name, children: _jsx("span", { className: "file-name", children: file?.file_name }) })] }), _jsx(Icon, { onClick: () => handleRemoveFileV2(index), className: "remove-btn", type: "icon-ants-remove-slim" })] }, file?.token))) })), _jsxs(WrapperIconEditor, { borderTop: !!valueInput.files?.length, children: [_jsxs(WrapperInputFile, { children: [_jsx("label", { htmlFor: `fileImage-${fileInputKey}`, className: "upload-wrapper-label", children: _jsx(Icon, { type: "icon-ants-attachment", className: "upload-icon" }) }), _jsx("input", { type: "file", style: { position: 'absolute', top: 0, right: 0, display: 'none' }, name: `fileImage-${fileInputKey}`, id: `fileImage-${fileInputKey}`, onChange: handleOnchangeFile }, fileInputKey)] }), isUpdate && (_jsx(Button, { type: "primary", disabled: !handleValidateContent(textValue) || isLoadingUpload, className: "reply-btn", style: {
308
+ }), onChange: handleEditorChange, placeholder: "Enter your comment...", height: 195, callback: callbackEditor }), _jsxs("div", { children: [valueInput.files?.length > 0 && (_jsx(WrapperLinkItemFiles, { children: valueInput.files?.map((file, index) => (_jsxs("div", { className: "file-item", children: [_jsxs("div", { className: "file-name-group", children: [_jsx(Icon, { className: "file-icon", type: "icon-ants-attachment" }), _jsx(Tooltip, { title: file?.file_name, children: _jsx("span", { className: "file-name", children: file?.file_name }) })] }), _jsx(Icon, { onClick: () => handleRemoveFileV2(index), className: "remove-btn", type: "icon-ants-remove-slim" })] }, file?.token))) })), _jsxs(WrapperIconEditor, { borderTop: !!valueInput.files?.length, children: [_jsxs(WrapperInputFile, { children: [_jsx("label", { htmlFor: `fileImage-${fileInputKey}`, className: "upload-wrapper-label", children: _jsx(Icon, { type: "icon-ants-attachment", className: "upload-icon" }) }), _jsx("input", { type: "file", style: { position: 'absolute', top: 0, right: 0, display: 'none' }, name: `fileImage-${fileInputKey}`, id: `fileImage-${fileInputKey}`, onChange: handleOnchangeFile }, fileInputKey)] }), isUpdate && (_jsx(Button, { type: "primary", disabled: !handleValidateContent(textValue) || isLoadingUpload, className: "reply-btn", style: {
275
309
  background: `${!handleValidateContent(textValue) || isLoadingUpload
276
310
  ? '#ccc'
277
311
  : '#1f5fac'}`,
278
- }, onClick: handleUpdateComment, loading: isLoadingUpload, children: "Reply" }))] })] })] }), errFile.isError ? (_jsx("div", { className: "error-message", children: errFile.message })) : (isEmptyField &&
312
+ }, onClick: handleUpdateComment, children: "Reply" }))] })] })] }), errFile.isError ? (_jsx("div", { className: "error-message", children: errFile.message })) : (isEmptyField &&
279
313
  !valueInput?.message && (_jsx("div", { className: "error-message", children: "* This field can't be empty" })))] }), isUpdate && (_jsx(Spin, { spinning: isLoadingComment, children: _jsx(WrapperMessageContent, { children: listComment?.map(comment => (_jsx(MessageComponent, { toUser: comment?.toUser, fromUser: comment?.fromUser, followers: comment?.followers,
280
314
  // mailFollower={comment?.mailFollower}
281
315
  message: comment?.message, date: comment?.createdDate, attachments: comment?.attachments, timeZone: timeZone, submitterEmail: comment?.submitterEmail }, comment?.id))) }) }))] })] }) })] }));
@@ -18,6 +18,7 @@ const MessageComponent = ({ toUser, fromUser, followers, attachments, message, d
18
18
  if (refMesage.current) {
19
19
  refMesage.current.querySelectorAll('img')?.forEach(img => {
20
20
  img.style.cursor = 'pointer';
21
+ img.style.width = '100%';
21
22
  img.addEventListener('click', () => {
22
23
  setModalImg({
23
24
  ...modalImg,
@@ -34,7 +34,7 @@ export const WrapperLeftContent = styled.div `
34
34
  `;
35
35
  export const WrapperRightContent = styled.div `
36
36
  flex: 2;
37
- width: 100%;
37
+ max-width: 66.67%;
38
38
  ::-webkit-scrollbar {
39
39
  width: 8px;
40
40
  height: 8px;
@@ -423,6 +423,7 @@ export const WrapperLinkItemFiles = styled.div `
423
423
  display: flex;
424
424
  align-items: center;
425
425
  gap: 10px;
426
+ overflow: hidden;
426
427
  }
427
428
  .file-icon {
428
429
  font-size: 20.5px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antscorp/antsomi-ui",
3
- "version": "2.0.89",
3
+ "version": "2.0.90",
4
4
  "description": "An enterprise-class UI design language and React UI library.",
5
5
  "sideEffects": [
6
6
  "dist/*",
@@ -113,6 +113,7 @@
113
113
  "qs": "6.10.3",
114
114
  "quill": "2.0.3",
115
115
  "quill-upload": "0.0.13",
116
+ "quill-image-drop-and-paste": "^2.0.1",
116
117
  "react-ace": "9.5.0",
117
118
  "react-beautiful-dnd": "^13.1.1",
118
119
  "react-color": "2.19.3",