tycho-components 0.41.0 → 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/localization/CommentsTexts.d.ts +12 -0
- package/dist/configs/localization/CommentsTexts.js +12 -0
- package/dist/features/Comments/CommentUtils.d.ts +24 -0
- package/dist/features/Comments/CommentUtils.js +175 -0
- package/dist/features/Comments/Comments.js +228 -82
- package/dist/features/Comments/style.scss +72 -16
- package/package.json +1 -1
|
@@ -33,6 +33,10 @@ export declare const CommentsTexts: {
|
|
|
33
33
|
"tab.write": string;
|
|
34
34
|
"tab.preview": string;
|
|
35
35
|
"placeholder.preview.empty": string;
|
|
36
|
+
"menu.undock": string;
|
|
37
|
+
"menu.dock.left": string;
|
|
38
|
+
"menu.dock.bottom": string;
|
|
39
|
+
"menu.dock.right": string;
|
|
36
40
|
};
|
|
37
41
|
"pt-BR": {
|
|
38
42
|
"label.title.comments": string;
|
|
@@ -68,6 +72,10 @@ export declare const CommentsTexts: {
|
|
|
68
72
|
"tab.write": string;
|
|
69
73
|
"tab.preview": string;
|
|
70
74
|
"placeholder.preview.empty": string;
|
|
75
|
+
"menu.undock": string;
|
|
76
|
+
"menu.dock.left": string;
|
|
77
|
+
"menu.dock.bottom": string;
|
|
78
|
+
"menu.dock.right": string;
|
|
71
79
|
};
|
|
72
80
|
it: {
|
|
73
81
|
"label.title.comments": string;
|
|
@@ -103,5 +111,9 @@ export declare const CommentsTexts: {
|
|
|
103
111
|
"tab.write": string;
|
|
104
112
|
"tab.preview": string;
|
|
105
113
|
"placeholder.preview.empty": string;
|
|
114
|
+
"menu.undock": string;
|
|
115
|
+
"menu.dock.left": string;
|
|
116
|
+
"menu.dock.bottom": string;
|
|
117
|
+
"menu.dock.right": string;
|
|
106
118
|
};
|
|
107
119
|
};
|
|
@@ -33,6 +33,10 @@ export const CommentsTexts = {
|
|
|
33
33
|
"tab.write": "Write",
|
|
34
34
|
"tab.preview": "Preview",
|
|
35
35
|
"placeholder.preview.empty": "Nothing to preview",
|
|
36
|
+
"menu.undock": "Undock into separate window",
|
|
37
|
+
"menu.dock.left": "Dock to left",
|
|
38
|
+
"menu.dock.bottom": "Dock to bottom",
|
|
39
|
+
"menu.dock.right": "Dock to right",
|
|
36
40
|
},
|
|
37
41
|
"pt-BR": {
|
|
38
42
|
"label.title.comments": "Comentários",
|
|
@@ -68,6 +72,10 @@ export const CommentsTexts = {
|
|
|
68
72
|
"tab.write": "Escrever",
|
|
69
73
|
"tab.preview": "Visualizar",
|
|
70
74
|
"placeholder.preview.empty": "Nada para visualizar",
|
|
75
|
+
"menu.undock": "Desencaixar em janela separada",
|
|
76
|
+
"menu.dock.left": "Encaixar à esquerda",
|
|
77
|
+
"menu.dock.bottom": "Encaixar embaixo",
|
|
78
|
+
"menu.dock.right": "Encaixar à direita",
|
|
71
79
|
},
|
|
72
80
|
it: {
|
|
73
81
|
"label.title.comments": "Commenti",
|
|
@@ -103,5 +111,9 @@ export const CommentsTexts = {
|
|
|
103
111
|
"tab.write": "Scrivi",
|
|
104
112
|
"tab.preview": "Anteprima",
|
|
105
113
|
"placeholder.preview.empty": "Niente da visualizzare",
|
|
114
|
+
"menu.undock": "Sgancia in una finestra separata",
|
|
115
|
+
"menu.dock.left": "Aggancia a sinistra",
|
|
116
|
+
"menu.dock.bottom": "Aggancia in basso",
|
|
117
|
+
"menu.dock.right": "Aggancia a destra",
|
|
106
118
|
},
|
|
107
119
|
};
|
|
@@ -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;
|
|
@@ -1,48 +1,31 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Drawer } from '@mui/material';
|
|
3
|
+
import cx from 'classnames';
|
|
3
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
11
|
import CommentAdd from './CommentAdd';
|
|
10
12
|
import CommentService from './CommentService';
|
|
11
13
|
import CommentThread from './CommentThread';
|
|
14
|
+
import CommentUtils from './CommentUtils';
|
|
12
15
|
import './style.scss';
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
if (typeof window === 'undefined')
|
|
27
|
-
return width;
|
|
28
|
-
const maxWidth = Math.min(Math.round(window.innerWidth * 0.9), window.innerWidth - 48);
|
|
29
|
-
return Math.min(Math.max(width, DRAWER_MIN_WIDTH), maxWidth);
|
|
30
|
-
};
|
|
31
|
-
const readStoredDrawerWidth = () => {
|
|
32
|
-
try {
|
|
33
|
-
const stored = localStorage.getItem(DRAWER_WIDTH_STORAGE_KEY);
|
|
34
|
-
if (stored) {
|
|
35
|
-
const parsed = Number(stored);
|
|
36
|
-
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
37
|
-
return clampDrawerWidth(parsed);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
// ignore storage access errors
|
|
43
|
-
}
|
|
44
|
-
return clampDrawerWidth(getDefaultDrawerWidth());
|
|
45
|
-
};
|
|
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
|
+
}
|
|
46
29
|
export default function Comments({ uid, keywords, references, mode, onClose, onChange, initialOpenAdd = false, }) {
|
|
47
30
|
const { t } = useTranslation('comments');
|
|
48
31
|
const [openAddComment, setOpenAddComment] = useState(initialOpenAdd);
|
|
@@ -50,8 +33,21 @@ export default function Comments({ uid, keywords, references, mode, onClose, onC
|
|
|
50
33
|
const [comments, setComments] = useState();
|
|
51
34
|
const [users, setUsers] = useState([]);
|
|
52
35
|
const [reply, setReply] = useState();
|
|
53
|
-
const [drawerWidth, setDrawerWidth] = useState(readStoredDrawerWidth);
|
|
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);
|
|
54
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
|
+
}
|
|
55
51
|
const load = async () => {
|
|
56
52
|
try {
|
|
57
53
|
const [commentsResponse, usersResponse] = await Promise.all([
|
|
@@ -63,12 +59,120 @@ export default function Comments({ uid, keywords, references, mode, onClose, onC
|
|
|
63
59
|
}
|
|
64
60
|
catch (error) {
|
|
65
61
|
console.error('Error loading comments or users', error);
|
|
66
|
-
// optionally handle specific error state here
|
|
67
62
|
}
|
|
68
63
|
};
|
|
69
64
|
const hasEditAccess = useMemo(() => {
|
|
70
65
|
return SecurityUtils.hasAccess(uid, ['ADMIN', 'EDITOR'], mode);
|
|
71
66
|
}, [uid, mode]);
|
|
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]);
|
|
72
176
|
useEffect(() => {
|
|
73
177
|
load();
|
|
74
178
|
}, []);
|
|
@@ -83,26 +187,55 @@ export default function Comments({ uid, keywords, references, mode, onClose, onC
|
|
|
83
187
|
});
|
|
84
188
|
return () => cancelAnimationFrame(frame);
|
|
85
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]);
|
|
86
202
|
const handleResizePointerDown = useCallback((event) => {
|
|
87
203
|
event.preventDefault();
|
|
88
204
|
const handle = event.currentTarget;
|
|
89
205
|
handle.setPointerCapture(event.pointerId);
|
|
90
206
|
const previousUserSelect = document.body.style.userSelect;
|
|
91
207
|
const previousCursor = document.body.style.cursor;
|
|
208
|
+
const isBottom = dockPosition === 'bottom';
|
|
209
|
+
const isLeft = dockPosition === 'left';
|
|
92
210
|
document.body.style.userSelect = 'none';
|
|
93
|
-
document.body.style.cursor = 'col-resize';
|
|
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
|
+
};
|
|
94
221
|
const onPointerMove = (moveEvent) => {
|
|
95
|
-
const
|
|
96
|
-
|
|
222
|
+
const next = computeSize(moveEvent.clientX, moveEvent.clientY);
|
|
223
|
+
if (isBottom) {
|
|
224
|
+
setDrawerHeight(next);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
setDrawerWidth(next);
|
|
228
|
+
}
|
|
97
229
|
};
|
|
98
230
|
const onPointerUp = (upEvent) => {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
231
|
+
const next = computeSize(upEvent.clientX, upEvent.clientY);
|
|
232
|
+
if (isBottom) {
|
|
233
|
+
setDrawerHeight(next);
|
|
234
|
+
CommentUtils.persistDrawerHeight(next);
|
|
103
235
|
}
|
|
104
|
-
|
|
105
|
-
|
|
236
|
+
else {
|
|
237
|
+
setDrawerWidth(next);
|
|
238
|
+
CommentUtils.persistDrawerWidth(next);
|
|
106
239
|
}
|
|
107
240
|
document.body.style.userSelect = previousUserSelect;
|
|
108
241
|
document.body.style.cursor = previousCursor;
|
|
@@ -114,41 +247,54 @@ export default function Comments({ uid, keywords, references, mode, onClose, onC
|
|
|
114
247
|
handle.addEventListener('pointermove', onPointerMove);
|
|
115
248
|
handle.addEventListener('pointerup', onPointerUp);
|
|
116
249
|
handle.addEventListener('pointercancel', onPointerUp);
|
|
117
|
-
}, []);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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: () => {
|
|
272
|
+
setComment(undefined);
|
|
273
|
+
setReply(undefined);
|
|
274
|
+
setOpenAddComment(false);
|
|
275
|
+
}, onChange: (el) => {
|
|
276
|
+
if (!comment) {
|
|
277
|
+
const updated = [...(comments || []), el];
|
|
278
|
+
setComments(updated);
|
|
279
|
+
onChange?.(updated);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
const updated = comments?.map((c) => (c.id === comment.id ? el : c)) || [];
|
|
283
|
+
setComments(updated);
|
|
284
|
+
onChange?.(updated);
|
|
285
|
+
}
|
|
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)] }));
|
|
154
300
|
}
|
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
.comments-drawer-paper {
|
|
2
|
-
width: 40vw;
|
|
3
|
-
max-width: 90vw;
|
|
4
|
-
min-width: 320px;
|
|
5
2
|
overflow: hidden;
|
|
3
|
+
|
|
4
|
+
&:not(.dock-bottom) {
|
|
5
|
+
width: 40vw;
|
|
6
|
+
max-width: 90vw;
|
|
7
|
+
min-width: 320px;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
&.dock-bottom {
|
|
11
|
+
width: 100%;
|
|
12
|
+
max-width: none;
|
|
13
|
+
min-width: 0;
|
|
14
|
+
max-height: 90vh;
|
|
15
|
+
min-height: 200px;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.comments-undocked-root {
|
|
20
|
+
width: 100%;
|
|
21
|
+
height: 100%;
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
background-color: var(--background-default);
|
|
6
24
|
}
|
|
7
25
|
|
|
8
26
|
.comments-container {
|
|
@@ -15,21 +33,12 @@
|
|
|
15
33
|
|
|
16
34
|
> .comments-resize-handle {
|
|
17
35
|
position: absolute;
|
|
18
|
-
top: 0;
|
|
19
|
-
left: 0;
|
|
20
|
-
width: 6px;
|
|
21
|
-
height: 100%;
|
|
22
|
-
cursor: col-resize;
|
|
23
36
|
z-index: 2;
|
|
24
37
|
touch-action: none;
|
|
25
38
|
|
|
26
39
|
&::after {
|
|
27
40
|
content: '';
|
|
28
41
|
position: absolute;
|
|
29
|
-
top: 0;
|
|
30
|
-
left: 0;
|
|
31
|
-
width: 2px;
|
|
32
|
-
height: 100%;
|
|
33
42
|
background-color: transparent;
|
|
34
43
|
transition: background-color 0.15s ease;
|
|
35
44
|
}
|
|
@@ -40,6 +49,51 @@
|
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
51
|
|
|
52
|
+
&.dock-right > .comments-resize-handle {
|
|
53
|
+
top: 0;
|
|
54
|
+
left: 0;
|
|
55
|
+
width: 6px;
|
|
56
|
+
height: 100%;
|
|
57
|
+
cursor: col-resize;
|
|
58
|
+
|
|
59
|
+
&::after {
|
|
60
|
+
top: 0;
|
|
61
|
+
left: 0;
|
|
62
|
+
width: 2px;
|
|
63
|
+
height: 100%;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&.dock-left > .comments-resize-handle {
|
|
68
|
+
top: 0;
|
|
69
|
+
right: 0;
|
|
70
|
+
width: 6px;
|
|
71
|
+
height: 100%;
|
|
72
|
+
cursor: col-resize;
|
|
73
|
+
|
|
74
|
+
&::after {
|
|
75
|
+
top: 0;
|
|
76
|
+
right: 0;
|
|
77
|
+
width: 2px;
|
|
78
|
+
height: 100%;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&.dock-bottom > .comments-resize-handle {
|
|
83
|
+
top: 0;
|
|
84
|
+
left: 0;
|
|
85
|
+
width: 100%;
|
|
86
|
+
height: 6px;
|
|
87
|
+
cursor: row-resize;
|
|
88
|
+
|
|
89
|
+
&::after {
|
|
90
|
+
top: 0;
|
|
91
|
+
left: 0;
|
|
92
|
+
width: 100%;
|
|
93
|
+
height: 2px;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
43
97
|
> .header {
|
|
44
98
|
display: flex;
|
|
45
99
|
align-items: center;
|
|
@@ -54,10 +108,12 @@
|
|
|
54
108
|
|
|
55
109
|
> .actions {
|
|
56
110
|
display: flex;
|
|
111
|
+
align-items: center;
|
|
57
112
|
margin-left: auto;
|
|
113
|
+
gap: var(--spacing-100);
|
|
58
114
|
|
|
59
115
|
.edit-button {
|
|
60
|
-
margin-right:
|
|
116
|
+
margin-right: 0;
|
|
61
117
|
}
|
|
62
118
|
}
|
|
63
119
|
}
|
|
@@ -181,19 +237,19 @@
|
|
|
181
237
|
}
|
|
182
238
|
|
|
183
239
|
@media (max-width: 1366px) {
|
|
184
|
-
.comments-drawer-paper {
|
|
240
|
+
.comments-drawer-paper:not(.dock-bottom) {
|
|
185
241
|
width: 40vw;
|
|
186
242
|
}
|
|
187
243
|
}
|
|
188
244
|
|
|
189
245
|
@media (max-width: 768px) {
|
|
190
|
-
.comments-drawer-paper {
|
|
246
|
+
.comments-drawer-paper:not(.dock-bottom) {
|
|
191
247
|
width: 60vw;
|
|
192
248
|
}
|
|
193
249
|
}
|
|
194
250
|
|
|
195
251
|
@media (max-width: 767px) {
|
|
196
|
-
.comments-drawer-paper {
|
|
252
|
+
.comments-drawer-paper:not(.dock-bottom) {
|
|
197
253
|
width: 90vw;
|
|
198
254
|
}
|
|
199
255
|
}
|