tycho-components 0.40.9 → 0.41.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/configs/api/applyAuthErrorHandling.js +2 -3
- package/dist/configs/localization/CommentsTexts.d.ts +24 -0
- package/dist/configs/localization/CommentsTexts.js +24 -0
- package/dist/features/Comments/{CommentAdd.d.ts → CommentAdd/CommentAdd.d.ts} +6 -4
- package/dist/features/Comments/CommentAdd/CommentAdd.js +107 -0
- package/dist/features/Comments/CommentAdd/index.d.ts +2 -0
- package/dist/features/Comments/CommentAdd/index.js +2 -0
- package/dist/features/Comments/CommentAdd/style.scss +124 -0
- package/dist/features/Comments/CommentInfo.d.ts +1 -0
- package/dist/features/Comments/CommentInfo.js +8 -4
- package/dist/features/Comments/CommentService.d.ts +9 -8
- package/dist/features/Comments/CommentService.js +74 -0
- package/dist/features/Comments/CommentService.mock.d.ts +4 -0
- package/dist/features/Comments/CommentService.mock.js +67 -0
- package/dist/features/Comments/CommentThread.d.ts +11 -0
- package/dist/features/Comments/CommentThread.js +14 -0
- package/dist/features/Comments/CommentUtils.d.ts +24 -0
- package/dist/features/Comments/CommentUtils.js +175 -0
- package/dist/features/Comments/Comments.d.ts +3 -1
- package/dist/features/Comments/Comments.js +254 -49
- package/dist/features/Comments/style.scss +178 -66
- package/dist/features/Comments/types/Comment.d.ts +0 -2
- package/dist/functions/StorybookUtils.d.ts +4 -0
- package/dist/functions/StorybookUtils.js +8 -0
- package/dist/functions/index.d.ts +1 -0
- package/dist/functions/index.js +1 -0
- package/package.json +4 -3
- package/dist/features/Comments/CommentAdd.js +0 -70
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import UsabilityUtils from '../../functions/UsabilityUtils';
|
|
3
|
+
import CommentInfo from './CommentInfo';
|
|
4
|
+
export default function CommentThread({ comments, hasEditAccess, onRemove, onUpdate, onEdit, onReply, }) {
|
|
5
|
+
const hasReplies = (thisComment) => comments.some((c) => c.reply === thisComment.id);
|
|
6
|
+
const getReplies = (thisComment) => comments.filter((c) => c.reply === thisComment.id);
|
|
7
|
+
return (_jsx(_Fragment, { children: comments.map((el, idx) => (_jsxs("div", { className: "thread", children: [!el.reply && (_jsx(CommentInfo, { comment: el, hasEditAccess: hasEditAccess, onRemove: () => onRemove(el.id), onUpdate: onUpdate, onEdit: () => {
|
|
8
|
+
onEdit(el);
|
|
9
|
+
UsabilityUtils.goToAnchor('comment-top');
|
|
10
|
+
}, onReply: () => {
|
|
11
|
+
onReply(el);
|
|
12
|
+
UsabilityUtils.goToAnchor('comment-top');
|
|
13
|
+
} })), hasReplies(el) && (_jsx("div", { className: "replies", children: getReplies(el).map((rel, idy) => (_jsx(CommentInfo, { comment: rel, hasEditAccess: hasEditAccess, onRemove: () => onRemove(rel.id), onUpdate: onUpdate, onEdit: () => onEdit(rel), onReply: () => onReply(rel) }, `${idx}_${idy}`))) }))] }, idx.valueOf()))) }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type DockPosition = 'left' | 'right' | 'bottom' | 'undocked';
|
|
2
|
+
export type DockedPosition = Exclude<DockPosition, 'undocked'>;
|
|
3
|
+
declare const CommentUtils: {
|
|
4
|
+
isDockPosition: (value: string | null) => value is DockPosition;
|
|
5
|
+
isDockedPosition: (value: DockPosition) => value is DockedPosition;
|
|
6
|
+
clampDrawerWidth: (width: number) => number;
|
|
7
|
+
clampDrawerHeight: (height: number) => number;
|
|
8
|
+
readStoredDrawerWidth: () => number;
|
|
9
|
+
readStoredDrawerHeight: () => number;
|
|
10
|
+
persistDrawerWidth: (width: number) => void;
|
|
11
|
+
persistDrawerHeight: (height: number) => void;
|
|
12
|
+
readStoredDockPosition: () => DockPosition;
|
|
13
|
+
persistDockPosition: (position: DockedPosition) => void;
|
|
14
|
+
getUndockedPopupSize: () => {
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
};
|
|
18
|
+
copyStylesToPopup: (popup: Window) => void;
|
|
19
|
+
openCommentsPopup: () => {
|
|
20
|
+
popup: Window;
|
|
21
|
+
container: HTMLElement;
|
|
22
|
+
} | null;
|
|
23
|
+
};
|
|
24
|
+
export default CommentUtils;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
const DRAWER_WIDTH_STORAGE_KEY = 'tycho-comments-drawer-width';
|
|
2
|
+
const DRAWER_HEIGHT_STORAGE_KEY = 'tycho-comments-drawer-height';
|
|
3
|
+
const DOCK_POSITION_STORAGE_KEY = 'tycho-comments-dock-position';
|
|
4
|
+
const DRAWER_MIN_WIDTH = 320;
|
|
5
|
+
const DRAWER_MIN_HEIGHT = 200;
|
|
6
|
+
const POPUP_NAME = 'tycho-comments';
|
|
7
|
+
const POPUP_MIN_WIDTH = 960;
|
|
8
|
+
const POPUP_MIN_HEIGHT = 720;
|
|
9
|
+
const isDockPosition = (value) => value === 'left' ||
|
|
10
|
+
value === 'right' ||
|
|
11
|
+
value === 'bottom' ||
|
|
12
|
+
value === 'undocked';
|
|
13
|
+
const isDockedPosition = (value) => value !== 'undocked';
|
|
14
|
+
const getDefaultDrawerWidth = () => {
|
|
15
|
+
if (typeof window === 'undefined')
|
|
16
|
+
return 480;
|
|
17
|
+
const { innerWidth } = window;
|
|
18
|
+
if (innerWidth <= 767)
|
|
19
|
+
return Math.round(innerWidth * 0.9);
|
|
20
|
+
if (innerWidth <= 768)
|
|
21
|
+
return Math.round(innerWidth * 0.6);
|
|
22
|
+
return Math.round(innerWidth * 0.4);
|
|
23
|
+
};
|
|
24
|
+
const getDefaultDrawerHeight = () => {
|
|
25
|
+
if (typeof window === 'undefined')
|
|
26
|
+
return 360;
|
|
27
|
+
return Math.round(window.innerHeight * 0.4);
|
|
28
|
+
};
|
|
29
|
+
const clampDrawerWidth = (width) => {
|
|
30
|
+
if (typeof window === 'undefined')
|
|
31
|
+
return width;
|
|
32
|
+
const maxWidth = Math.min(Math.round(window.innerWidth * 0.9), window.innerWidth - 48);
|
|
33
|
+
return Math.min(Math.max(width, DRAWER_MIN_WIDTH), maxWidth);
|
|
34
|
+
};
|
|
35
|
+
const clampDrawerHeight = (height) => {
|
|
36
|
+
if (typeof window === 'undefined')
|
|
37
|
+
return height;
|
|
38
|
+
const maxHeight = Math.min(Math.round(window.innerHeight * 0.9), window.innerHeight - 48);
|
|
39
|
+
return Math.min(Math.max(height, DRAWER_MIN_HEIGHT), maxHeight);
|
|
40
|
+
};
|
|
41
|
+
const readStoredDrawerWidth = () => {
|
|
42
|
+
try {
|
|
43
|
+
const stored = localStorage.getItem(DRAWER_WIDTH_STORAGE_KEY);
|
|
44
|
+
if (stored) {
|
|
45
|
+
const parsed = Number(stored);
|
|
46
|
+
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
47
|
+
return clampDrawerWidth(parsed);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// ignore storage access errors
|
|
53
|
+
}
|
|
54
|
+
return clampDrawerWidth(getDefaultDrawerWidth());
|
|
55
|
+
};
|
|
56
|
+
const readStoredDrawerHeight = () => {
|
|
57
|
+
try {
|
|
58
|
+
const stored = localStorage.getItem(DRAWER_HEIGHT_STORAGE_KEY);
|
|
59
|
+
if (stored) {
|
|
60
|
+
const parsed = Number(stored);
|
|
61
|
+
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
62
|
+
return clampDrawerHeight(parsed);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// ignore storage access errors
|
|
68
|
+
}
|
|
69
|
+
return clampDrawerHeight(getDefaultDrawerHeight());
|
|
70
|
+
};
|
|
71
|
+
const persistDrawerWidth = (width) => {
|
|
72
|
+
try {
|
|
73
|
+
localStorage.setItem(DRAWER_WIDTH_STORAGE_KEY, String(width));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// ignore storage access errors
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const persistDrawerHeight = (height) => {
|
|
80
|
+
try {
|
|
81
|
+
localStorage.setItem(DRAWER_HEIGHT_STORAGE_KEY, String(height));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// ignore storage access errors
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const readStoredDockPosition = () => {
|
|
88
|
+
try {
|
|
89
|
+
const stored = localStorage.getItem(DOCK_POSITION_STORAGE_KEY);
|
|
90
|
+
if (isDockPosition(stored) && stored !== 'undocked') {
|
|
91
|
+
return stored;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// ignore storage access errors
|
|
96
|
+
}
|
|
97
|
+
return 'right';
|
|
98
|
+
};
|
|
99
|
+
const persistDockPosition = (position) => {
|
|
100
|
+
try {
|
|
101
|
+
localStorage.setItem(DOCK_POSITION_STORAGE_KEY, position);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// ignore storage access errors
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const getUndockedPopupSize = () => {
|
|
108
|
+
if (typeof window === 'undefined') {
|
|
109
|
+
return { width: POPUP_MIN_WIDTH, height: POPUP_MIN_HEIGHT };
|
|
110
|
+
}
|
|
111
|
+
const availWidth = window.screen.availWidth || window.outerWidth;
|
|
112
|
+
const availHeight = window.screen.availHeight || window.outerHeight;
|
|
113
|
+
const width = Math.min(Math.max(Math.round(availWidth * 0.75), POPUP_MIN_WIDTH), availWidth - 40);
|
|
114
|
+
const height = Math.min(Math.max(Math.round(availHeight * 0.85), POPUP_MIN_HEIGHT), availHeight - 40);
|
|
115
|
+
return { width, height };
|
|
116
|
+
};
|
|
117
|
+
const copyStylesToPopup = (popup) => {
|
|
118
|
+
const { document: popupDoc } = popup;
|
|
119
|
+
popupDoc.head.innerHTML = '';
|
|
120
|
+
popupDoc.title = document.title;
|
|
121
|
+
document.querySelectorAll('link[rel="stylesheet"], style').forEach((node) => {
|
|
122
|
+
popupDoc.head.appendChild(node.cloneNode(true));
|
|
123
|
+
});
|
|
124
|
+
popupDoc.body.className = document.body.className;
|
|
125
|
+
popupDoc.body.style.margin = '0';
|
|
126
|
+
popupDoc.body.style.height = '100%';
|
|
127
|
+
popupDoc.documentElement.style.height = '100%';
|
|
128
|
+
popupDoc.documentElement.style.overflow = 'hidden';
|
|
129
|
+
};
|
|
130
|
+
const openCommentsPopup = () => {
|
|
131
|
+
if (typeof window === 'undefined')
|
|
132
|
+
return null;
|
|
133
|
+
const { width, height } = getUndockedPopupSize();
|
|
134
|
+
const left = Math.round(window.screenX + (window.outerWidth - width) / 2);
|
|
135
|
+
const top = Math.round(window.screenY + (window.outerHeight - height) / 2);
|
|
136
|
+
const features = [
|
|
137
|
+
'popup=yes',
|
|
138
|
+
`width=${width}`,
|
|
139
|
+
`height=${height}`,
|
|
140
|
+
`left=${left}`,
|
|
141
|
+
`top=${top}`,
|
|
142
|
+
].join(',');
|
|
143
|
+
const popup = window.open('', POPUP_NAME, features);
|
|
144
|
+
if (!popup)
|
|
145
|
+
return null;
|
|
146
|
+
try {
|
|
147
|
+
popup.resizeTo(width, height);
|
|
148
|
+
popup.moveTo(left, top);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// some browsers block resize/move on already-open named windows
|
|
152
|
+
}
|
|
153
|
+
copyStylesToPopup(popup);
|
|
154
|
+
const container = popup.document.createElement('div');
|
|
155
|
+
container.id = 'tycho-comments-root';
|
|
156
|
+
container.style.height = '100%';
|
|
157
|
+
popup.document.body.appendChild(container);
|
|
158
|
+
return { popup, container };
|
|
159
|
+
};
|
|
160
|
+
const CommentUtils = {
|
|
161
|
+
isDockPosition,
|
|
162
|
+
isDockedPosition,
|
|
163
|
+
clampDrawerWidth,
|
|
164
|
+
clampDrawerHeight,
|
|
165
|
+
readStoredDrawerWidth,
|
|
166
|
+
readStoredDrawerHeight,
|
|
167
|
+
persistDrawerWidth,
|
|
168
|
+
persistDrawerHeight,
|
|
169
|
+
readStoredDockPosition,
|
|
170
|
+
persistDockPosition,
|
|
171
|
+
getUndockedPopupSize,
|
|
172
|
+
copyStylesToPopup,
|
|
173
|
+
openCommentsPopup,
|
|
174
|
+
};
|
|
175
|
+
export default CommentUtils;
|
|
@@ -7,6 +7,8 @@ type Props = {
|
|
|
7
7
|
onClose: () => void;
|
|
8
8
|
mode: 'lexicon' | 'corpus' | 'parser';
|
|
9
9
|
onChange?: (a: Comment[]) => void;
|
|
10
|
+
/** Opens the add-comment panel on mount (useful in Storybook). */
|
|
11
|
+
initialOpenAdd?: boolean;
|
|
10
12
|
};
|
|
11
|
-
export default function Comments({ uid, keywords, references, mode, onClose, onChange, }: Props): import("react").JSX.Element;
|
|
13
|
+
export default function Comments({ uid, keywords, references, mode, onClose, onChange, initialOpenAdd, }: Props): import("react").JSX.Element;
|
|
12
14
|
export {};
|
|
@@ -1,23 +1,53 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Drawer } from '@mui/material';
|
|
3
|
-
import
|
|
3
|
+
import cx from 'classnames';
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
4
6
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { Button, IconButton } from 'tycho-storybook';
|
|
7
|
+
import { Button, DropdownMenu, IconButton } from 'tycho-storybook';
|
|
6
8
|
import AppLoading from '../../common/AppLoading';
|
|
7
9
|
import AppPlaceholder from '../../common/AppPlaceholder';
|
|
8
10
|
import SecurityUtils from '../../functions/SecurityUtils';
|
|
9
|
-
import UsabilityUtils from '../../functions/UsabilityUtils';
|
|
10
11
|
import CommentAdd from './CommentAdd';
|
|
11
|
-
import CommentInfo from './CommentInfo';
|
|
12
12
|
import CommentService from './CommentService';
|
|
13
|
+
import CommentThread from './CommentThread';
|
|
14
|
+
import CommentUtils from './CommentUtils';
|
|
13
15
|
import './style.scss';
|
|
14
|
-
|
|
16
|
+
function CommentsPanel({ dockPosition, showResizeHandle, onResizePointerDown, children, }) {
|
|
17
|
+
const isHorizontal = dockPosition === 'bottom';
|
|
18
|
+
return (_jsxs("div", { className: cx('comments-container', {
|
|
19
|
+
'dock-left': dockPosition === 'left',
|
|
20
|
+
'dock-right': dockPosition === 'right',
|
|
21
|
+
'dock-bottom': dockPosition === 'bottom',
|
|
22
|
+
'dock-undocked': dockPosition === 'undocked',
|
|
23
|
+
}), id: "comment-top", children: [showResizeHandle && (_jsx("div", { className: "comments-resize-handle", onPointerDown: onResizePointerDown, role: "separator", "aria-orientation": isHorizontal ? 'horizontal' : 'vertical', "aria-label": "Resize comments panel" })), children] }));
|
|
24
|
+
}
|
|
25
|
+
function CommentsHeader({ hasEditAccess, onToggleAdd, onClose, dockMenuOptions, }) {
|
|
26
|
+
const { t } = useTranslation('comments');
|
|
27
|
+
return (_jsxs("div", { className: "header", children: [_jsx(IconButton, { name: "close", size: "small", mode: "ghost", onClick: onClose }), _jsx("span", { className: "title", children: t('label.title.comments') }), _jsxs("div", { className: "actions", children: [hasEditAccess && (_jsx(Button, { text: t('button.add'), icon: "add", mode: "outlined", size: "small", className: "edit-button", onClick: onToggleAdd })), _jsx(DropdownMenu, { list: dockMenuOptions })] })] }));
|
|
28
|
+
}
|
|
29
|
+
export default function Comments({ uid, keywords, references, mode, onClose, onChange, initialOpenAdd = false, }) {
|
|
15
30
|
const { t } = useTranslation('comments');
|
|
16
|
-
const [openAddComment, setOpenAddComment] = useState(
|
|
31
|
+
const [openAddComment, setOpenAddComment] = useState(initialOpenAdd);
|
|
17
32
|
const [comment, setComment] = useState();
|
|
18
33
|
const [comments, setComments] = useState();
|
|
19
34
|
const [users, setUsers] = useState([]);
|
|
20
35
|
const [reply, setReply] = useState();
|
|
36
|
+
const [drawerWidth, setDrawerWidth] = useState(CommentUtils.readStoredDrawerWidth);
|
|
37
|
+
const [drawerHeight, setDrawerHeight] = useState(CommentUtils.readStoredDrawerHeight);
|
|
38
|
+
const [dockPosition, setDockPosition] = useState(CommentUtils.readStoredDockPosition);
|
|
39
|
+
const [externalContainer, setExternalContainer] = useState(null);
|
|
40
|
+
const commentAddRef = useRef(null);
|
|
41
|
+
const popupRef = useRef(null);
|
|
42
|
+
const popupPollRef = useRef(null);
|
|
43
|
+
const ignorePopupCloseRef = useRef(false);
|
|
44
|
+
const lastDockedRef = useRef(null);
|
|
45
|
+
if (lastDockedRef.current === null) {
|
|
46
|
+
const initial = CommentUtils.readStoredDockPosition();
|
|
47
|
+
lastDockedRef.current = CommentUtils.isDockedPosition(initial)
|
|
48
|
+
? initial
|
|
49
|
+
: 'right';
|
|
50
|
+
}
|
|
21
51
|
const load = async () => {
|
|
22
52
|
try {
|
|
23
53
|
const [commentsResponse, usersResponse] = await Promise.all([
|
|
@@ -29,21 +59,216 @@ export default function Comments({ uid, keywords, references, mode, onClose, onC
|
|
|
29
59
|
}
|
|
30
60
|
catch (error) {
|
|
31
61
|
console.error('Error loading comments or users', error);
|
|
32
|
-
// optionally handle specific error state here
|
|
33
62
|
}
|
|
34
63
|
};
|
|
35
64
|
const hasEditAccess = useMemo(() => {
|
|
36
65
|
return SecurityUtils.hasAccess(uid, ['ADMIN', 'EDITOR'], mode);
|
|
37
66
|
}, [uid, mode]);
|
|
38
|
-
const
|
|
39
|
-
|
|
67
|
+
const clearPopupPoll = useCallback(() => {
|
|
68
|
+
if (popupPollRef.current != null) {
|
|
69
|
+
window.clearInterval(popupPollRef.current);
|
|
70
|
+
popupPollRef.current = null;
|
|
71
|
+
}
|
|
72
|
+
}, []);
|
|
73
|
+
const redockFromPopup = useCallback(() => {
|
|
74
|
+
clearPopupPoll();
|
|
75
|
+
popupRef.current = null;
|
|
76
|
+
setExternalContainer(null);
|
|
77
|
+
if (ignorePopupCloseRef.current) {
|
|
78
|
+
ignorePopupCloseRef.current = false;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
setDockPosition((prev) => {
|
|
82
|
+
if (prev !== 'undocked')
|
|
83
|
+
return prev;
|
|
84
|
+
const next = lastDockedRef.current ?? 'right';
|
|
85
|
+
CommentUtils.persistDockPosition(next);
|
|
86
|
+
return next;
|
|
87
|
+
});
|
|
88
|
+
}, [clearPopupPoll]);
|
|
89
|
+
const closePopup = useCallback(() => {
|
|
90
|
+
clearPopupPoll();
|
|
91
|
+
const popup = popupRef.current;
|
|
92
|
+
if (popup && !popup.closed) {
|
|
93
|
+
ignorePopupCloseRef.current = true;
|
|
94
|
+
popup.close();
|
|
95
|
+
}
|
|
96
|
+
popupRef.current = null;
|
|
97
|
+
setExternalContainer(null);
|
|
98
|
+
}, [clearPopupPoll]);
|
|
99
|
+
const handleClose = useCallback(() => {
|
|
100
|
+
closePopup();
|
|
101
|
+
onClose();
|
|
102
|
+
}, [closePopup, onClose]);
|
|
103
|
+
const openUndockedWindow = useCallback(() => {
|
|
104
|
+
const existing = popupRef.current;
|
|
105
|
+
if (existing && !existing.closed) {
|
|
106
|
+
existing.focus();
|
|
107
|
+
setDockPosition('undocked');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const opened = CommentUtils.openCommentsPopup();
|
|
111
|
+
if (!opened)
|
|
112
|
+
return;
|
|
113
|
+
const { popup, container } = opened;
|
|
114
|
+
ignorePopupCloseRef.current = false;
|
|
115
|
+
popupRef.current = popup;
|
|
116
|
+
setExternalContainer(container);
|
|
117
|
+
setDockPosition('undocked');
|
|
118
|
+
popup.addEventListener('beforeunload', () => {
|
|
119
|
+
redockFromPopup();
|
|
120
|
+
});
|
|
121
|
+
clearPopupPoll();
|
|
122
|
+
popupPollRef.current = window.setInterval(() => {
|
|
123
|
+
if (!popupRef.current || popupRef.current.closed) {
|
|
124
|
+
redockFromPopup();
|
|
125
|
+
}
|
|
126
|
+
}, 500);
|
|
127
|
+
}, [clearPopupPoll, redockFromPopup]);
|
|
128
|
+
const setDockedPosition = useCallback((position) => {
|
|
129
|
+
lastDockedRef.current = position;
|
|
130
|
+
CommentUtils.persistDockPosition(position);
|
|
131
|
+
closePopup();
|
|
132
|
+
setDockPosition(position);
|
|
133
|
+
}, [closePopup]);
|
|
134
|
+
const dockMenuOptions = useMemo(() => [
|
|
135
|
+
{
|
|
136
|
+
label: t('menu.undock'),
|
|
137
|
+
icon: 'open_in_new',
|
|
138
|
+
disabled: dockPosition === 'undocked',
|
|
139
|
+
onClick: () => {
|
|
140
|
+
if (dockPosition === 'undocked')
|
|
141
|
+
return;
|
|
142
|
+
openUndockedWindow();
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
label: t('menu.dock.left'),
|
|
147
|
+
icon: 'dock_to_left',
|
|
148
|
+
disabled: dockPosition === 'left',
|
|
149
|
+
onClick: () => {
|
|
150
|
+
if (dockPosition === 'left')
|
|
151
|
+
return;
|
|
152
|
+
setDockedPosition('left');
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
label: t('menu.dock.bottom'),
|
|
157
|
+
icon: 'dock_to_bottom',
|
|
158
|
+
disabled: dockPosition === 'bottom',
|
|
159
|
+
onClick: () => {
|
|
160
|
+
if (dockPosition === 'bottom')
|
|
161
|
+
return;
|
|
162
|
+
setDockedPosition('bottom');
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
label: t('menu.dock.right'),
|
|
167
|
+
icon: 'dock_to_right',
|
|
168
|
+
disabled: dockPosition === 'right',
|
|
169
|
+
onClick: () => {
|
|
170
|
+
if (dockPosition === 'right')
|
|
171
|
+
return;
|
|
172
|
+
setDockedPosition('right');
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
], [dockPosition, openUndockedWindow, setDockedPosition, t]);
|
|
40
176
|
useEffect(() => {
|
|
41
177
|
load();
|
|
42
178
|
}, []);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
if (!openAddComment || !comments)
|
|
181
|
+
return;
|
|
182
|
+
const frame = requestAnimationFrame(() => {
|
|
183
|
+
commentAddRef.current?.scrollIntoView({
|
|
184
|
+
behavior: 'smooth',
|
|
185
|
+
block: 'nearest',
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
return () => cancelAnimationFrame(frame);
|
|
189
|
+
}, [openAddComment, comments, comment, reply]);
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
const onHostUnload = () => {
|
|
192
|
+
closePopup();
|
|
193
|
+
};
|
|
194
|
+
window.addEventListener('pagehide', onHostUnload);
|
|
195
|
+
window.addEventListener('beforeunload', onHostUnload);
|
|
196
|
+
return () => {
|
|
197
|
+
window.removeEventListener('pagehide', onHostUnload);
|
|
198
|
+
window.removeEventListener('beforeunload', onHostUnload);
|
|
199
|
+
closePopup();
|
|
200
|
+
};
|
|
201
|
+
}, [closePopup]);
|
|
202
|
+
const handleResizePointerDown = useCallback((event) => {
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
const handle = event.currentTarget;
|
|
205
|
+
handle.setPointerCapture(event.pointerId);
|
|
206
|
+
const previousUserSelect = document.body.style.userSelect;
|
|
207
|
+
const previousCursor = document.body.style.cursor;
|
|
208
|
+
const isBottom = dockPosition === 'bottom';
|
|
209
|
+
const isLeft = dockPosition === 'left';
|
|
210
|
+
document.body.style.userSelect = 'none';
|
|
211
|
+
document.body.style.cursor = isBottom ? 'row-resize' : 'col-resize';
|
|
212
|
+
const computeSize = (clientX, clientY) => {
|
|
213
|
+
if (isBottom) {
|
|
214
|
+
return CommentUtils.clampDrawerHeight(window.innerHeight - clientY);
|
|
215
|
+
}
|
|
216
|
+
if (isLeft) {
|
|
217
|
+
return CommentUtils.clampDrawerWidth(clientX);
|
|
218
|
+
}
|
|
219
|
+
return CommentUtils.clampDrawerWidth(window.innerWidth - clientX);
|
|
220
|
+
};
|
|
221
|
+
const onPointerMove = (moveEvent) => {
|
|
222
|
+
const next = computeSize(moveEvent.clientX, moveEvent.clientY);
|
|
223
|
+
if (isBottom) {
|
|
224
|
+
setDrawerHeight(next);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
setDrawerWidth(next);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const onPointerUp = (upEvent) => {
|
|
231
|
+
const next = computeSize(upEvent.clientX, upEvent.clientY);
|
|
232
|
+
if (isBottom) {
|
|
233
|
+
setDrawerHeight(next);
|
|
234
|
+
CommentUtils.persistDrawerHeight(next);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
setDrawerWidth(next);
|
|
238
|
+
CommentUtils.persistDrawerWidth(next);
|
|
239
|
+
}
|
|
240
|
+
document.body.style.userSelect = previousUserSelect;
|
|
241
|
+
document.body.style.cursor = previousCursor;
|
|
242
|
+
handle.releasePointerCapture(upEvent.pointerId);
|
|
243
|
+
handle.removeEventListener('pointermove', onPointerMove);
|
|
244
|
+
handle.removeEventListener('pointerup', onPointerUp);
|
|
245
|
+
handle.removeEventListener('pointercancel', onPointerUp);
|
|
246
|
+
};
|
|
247
|
+
handle.addEventListener('pointermove', onPointerMove);
|
|
248
|
+
handle.addEventListener('pointerup', onPointerUp);
|
|
249
|
+
handle.addEventListener('pointercancel', onPointerUp);
|
|
250
|
+
}, [dockPosition]);
|
|
251
|
+
const paperStyle = dockPosition === 'bottom'
|
|
252
|
+
? { height: drawerHeight, width: '100%' }
|
|
253
|
+
: { width: drawerWidth };
|
|
254
|
+
const content = !comments ? (_jsx(AppLoading, { pageLoading: true })) : (_jsxs(_Fragment, { children: [_jsx(CommentsHeader, { hasEditAccess: hasEditAccess, onToggleAdd: () => {
|
|
255
|
+
setComment(undefined);
|
|
256
|
+
setOpenAddComment(!openAddComment);
|
|
257
|
+
}, onClose: handleClose, dockMenuOptions: dockMenuOptions }), _jsxs("div", { className: "body", children: [comments.length === 0 && !openAddComment && (_jsx(AppPlaceholder, { text: t('placeholder.comments.empty'), useMarginTop: true })), _jsx(CommentThread, { comments: comments, hasEditAccess: hasEditAccess, onRemove: (id) => {
|
|
258
|
+
const updated = comments.filter((c) => c.id !== id);
|
|
259
|
+
setComments(updated);
|
|
260
|
+
onChange?.(updated);
|
|
261
|
+
}, onUpdate: (thisComment) => {
|
|
262
|
+
const updated = comments.map((c) => c.id === thisComment.id ? { ...c, ...thisComment } : c);
|
|
263
|
+
setComments(updated);
|
|
264
|
+
onChange?.(updated);
|
|
265
|
+
}, onEdit: (el) => {
|
|
266
|
+
setComment(el);
|
|
267
|
+
setOpenAddComment(true);
|
|
268
|
+
}, onReply: (el) => {
|
|
269
|
+
setReply(el);
|
|
270
|
+
setOpenAddComment(true);
|
|
271
|
+
} }), hasEditAccess && openAddComment && (_jsx("div", { ref: commentAddRef, children: _jsx(CommentAdd, { uid: uid, mode: mode, users: users, references: references, keywords: keywords, comment: comment, reply: reply, onClose: () => {
|
|
47
272
|
setComment(undefined);
|
|
48
273
|
setReply(undefined);
|
|
49
274
|
setOpenAddComment(false);
|
|
@@ -54,42 +279,22 @@ export default function Comments({ uid, keywords, references, mode, onClose, onC
|
|
|
54
279
|
onChange?.(updated);
|
|
55
280
|
}
|
|
56
281
|
else {
|
|
57
|
-
const updated = comments?.map((c) => (c.id === comment.id ? el : c)) ||
|
|
58
|
-
[];
|
|
282
|
+
const updated = comments?.map((c) => (c.id === comment.id ? el : c)) || [];
|
|
59
283
|
setComments(updated);
|
|
60
284
|
onChange?.(updated);
|
|
61
285
|
}
|
|
62
|
-
} })
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
setOpenAddComment(true);
|
|
77
|
-
UsabilityUtils.goToAnchor('comment-top');
|
|
78
|
-
} })), hasReplies(el) && (_jsx("div", { className: "replies", children: getReplies(el).map((rel, idy) => (_jsx(CommentInfo, { comment: rel, hasEditAccess: hasEditAccess, onRemove: () => {
|
|
79
|
-
const updated = comments?.filter((c) => c.id !== rel.id);
|
|
80
|
-
setComments(updated);
|
|
81
|
-
onChange && onChange(updated || []);
|
|
82
|
-
}, onUpdate: (thisComment) => {
|
|
83
|
-
const updated = comments?.map((c) => c.id === thisComment.id
|
|
84
|
-
? { ...c, ...thisComment }
|
|
85
|
-
: c) || [];
|
|
86
|
-
setComments(updated);
|
|
87
|
-
onChange?.(updated);
|
|
88
|
-
}, onEdit: () => {
|
|
89
|
-
setComment(rel);
|
|
90
|
-
setOpenAddComment(true);
|
|
91
|
-
}, onReply: () => {
|
|
92
|
-
setReply(rel);
|
|
93
|
-
setOpenAddComment(true);
|
|
94
|
-
} }, `${idx}_${idy}`))) }))] }, idx.valueOf())))] })] })) }));
|
|
286
|
+
} }) }))] })] }));
|
|
287
|
+
const panel = (_jsx(CommentsPanel, { dockPosition: dockPosition, showResizeHandle: CommentUtils.isDockedPosition(dockPosition), onResizePointerDown: handleResizePointerDown, children: content }));
|
|
288
|
+
const isDocked = CommentUtils.isDockedPosition(dockPosition);
|
|
289
|
+
const drawerAnchor = isDocked
|
|
290
|
+
? dockPosition
|
|
291
|
+
: (lastDockedRef.current ?? 'right');
|
|
292
|
+
return (_jsxs(_Fragment, { children: [_jsx(Drawer, { anchor: drawerAnchor, open: isDocked, onClose: handleClose, slotProps: {
|
|
293
|
+
backdrop: { invisible: true },
|
|
294
|
+
}, PaperProps: {
|
|
295
|
+
className: cx('comments-drawer-paper', `dock-${drawerAnchor}`),
|
|
296
|
+
style: paperStyle,
|
|
297
|
+
}, children: isDocked ? panel : null }), dockPosition === 'undocked' &&
|
|
298
|
+
externalContainer &&
|
|
299
|
+
createPortal(_jsx("div", { className: "comments-undocked-root", children: panel }), externalContainer)] }));
|
|
95
300
|
}
|