mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
(feat:docs) adding view option
This commit is contained in:
11
frontend/src/assets/eye-view.svg
Normal file
11
frontend/src/assets/eye-view.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_5819_12451)">
|
||||
<path d="M8.40039 14.3C5.06706 14.3 2.53372 12.4334 0.533724 8.63338L0.400391 8.30005L0.533724 7.96672C2.53372 4.16672 5.06706 2.30005 8.40039 2.30005C11.7337 2.30005 14.2671 4.16672 16.2671 7.96672L16.4004 8.30005L16.2671 8.63338C14.2671 12.4334 11.7337 14.3 8.40039 14.3ZM1.93372 8.30005C3.60039 11.4334 5.73372 12.9667 8.40039 12.9667C11.0671 12.9667 13.2004 11.4334 14.8671 8.30005C13.2004 5.16672 11.0671 3.63338 8.40039 3.63338C5.73372 3.63338 3.60039 5.16672 1.93372 8.30005Z" fill="#747474"/>
|
||||
<path d="M8.40072 11.6333C6.53405 11.6333 5.06738 10.1667 5.06738 8.30001C5.06738 6.43334 6.53405 4.96667 8.40072 4.96667C10.2674 4.96667 11.734 6.43334 11.734 8.30001C11.734 10.1667 10.2674 11.6333 8.40072 11.6333ZM8.40072 6.30001C7.26738 6.30001 6.40072 7.16667 6.40072 8.30001C6.40072 9.43334 7.26738 10.3 8.40072 10.3C9.53405 10.3 10.4007 9.43334 10.4007 8.30001C10.4007 7.16667 9.53405 6.30001 8.40072 6.30001Z" fill="#747474"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5819_12451">
|
||||
<rect width="16" height="16" fill="white" transform="translate(0.400391 0.300049)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
type DropdownMenuProps = {
|
||||
name: string;
|
||||
@@ -10,7 +11,9 @@ type DropdownMenuProps = {
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
anchorRef?: React.RefObject<HTMLElement>;
|
||||
className?: string;
|
||||
position?: 'left' | 'right';
|
||||
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
||||
offset?: { x: number; y: number };
|
||||
contextMenuAdjacent?: boolean; // New prop to indicate if it should position next to context menu
|
||||
};
|
||||
|
||||
export default function DropdownMenu({
|
||||
@@ -22,8 +25,10 @@ export default function DropdownMenu({
|
||||
isOpen: controlledIsOpen,
|
||||
onOpenChange,
|
||||
anchorRef,
|
||||
className,
|
||||
position = 'left',
|
||||
className = '',
|
||||
position = 'bottom-right',
|
||||
offset = { x: 0, y: 8 },
|
||||
contextMenuAdjacent = false, // Default to false for backward compatibility
|
||||
}: DropdownMenuProps) {
|
||||
const dropdownRef = React.useRef<HTMLDivElement>(null);
|
||||
const [internalIsOpen, setInternalIsOpen] = React.useState(false);
|
||||
@@ -38,7 +43,8 @@ export default function DropdownMenu({
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
!dropdownRef.current.contains(event.target as Node) &&
|
||||
!anchorRef?.current?.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
@@ -51,20 +57,62 @@ export default function DropdownMenu({
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
if (isOpen) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () =>
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<div className={`fixed ${className || ''}`} ref={dropdownRef}>
|
||||
if (!isOpen) return null;
|
||||
|
||||
const getMenuPosition = (): React.CSSProperties => {
|
||||
if (!anchorRef?.current) return {};
|
||||
|
||||
const rect = anchorRef.current.getBoundingClientRect();
|
||||
|
||||
// Default positioning
|
||||
let top = rect.bottom + offset.y;
|
||||
let left = rect.right + offset.x;
|
||||
|
||||
if (contextMenuAdjacent) {
|
||||
// Position to the left of the context menu
|
||||
left = rect.left - 50; // Width of dropdown + some spacing
|
||||
top = rect.top; // Align tops
|
||||
} else {
|
||||
// Standard positioning based on position prop
|
||||
switch (position) {
|
||||
case 'bottom-left':
|
||||
left = rect.left - offset.x;
|
||||
break;
|
||||
case 'top-right':
|
||||
top = rect.top - offset.y;
|
||||
break;
|
||||
case 'top-left':
|
||||
top = rect.top - offset.y;
|
||||
left = rect.left - offset.x;
|
||||
break;
|
||||
// bottom-right is default
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
position: 'fixed',
|
||||
top: `${top}px`,
|
||||
left: `${left}px`,
|
||||
zIndex: 9999,
|
||||
};
|
||||
};
|
||||
|
||||
// Use a portal to render the dropdown outside the table flow
|
||||
return ReactDOM.createPortal(
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
style={{ ...getMenuPosition() }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div
|
||||
className={`w-28 transform rounded-md bg-white dark:bg-dark-charcoal shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-200 ease-in-out ${
|
||||
isOpen
|
||||
? 'scale-100 opacity-100'
|
||||
: 'pointer-events-none scale-95 opacity-0'
|
||||
}`}
|
||||
className={`w-28 transform rounded-md bg-white dark:bg-dark-charcoal shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-200 ease-in-out ${className}`}
|
||||
>
|
||||
<div
|
||||
role="menu"
|
||||
@@ -89,6 +137,7 @@ export default function DropdownMenu({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import userService from '../api/services/userService';
|
||||
import ArrowLeft from '../assets/arrow-left.svg';
|
||||
import caretSort from '../assets/caret-sort.svg';
|
||||
import Edit from '../assets/edit.svg';
|
||||
import EyeView from '../assets/eye-view.svg';
|
||||
import NoFilesDarkIcon from '../assets/no-files-dark.svg';
|
||||
import NoFilesIcon from '../assets/no-files.svg';
|
||||
import SyncIcon from '../assets/sync.svg';
|
||||
@@ -81,13 +82,19 @@ export default function Documents({
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Close any open menu if clicking on a different button
|
||||
if (activeMenuId && activeMenuId !== docId) {
|
||||
const isAnyMenuOpen =
|
||||
(syncMenuState.isOpen && syncMenuState.docId === docId) ||
|
||||
activeMenuId === docId;
|
||||
|
||||
if (isAnyMenuOpen) {
|
||||
// Close both menus
|
||||
setSyncMenuState((prev) => ({ ...prev, isOpen: false, docId: null }));
|
||||
setActiveMenuId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle the clicked menu
|
||||
setActiveMenuId((prev) => (prev === docId ? null : docId));
|
||||
// If no menu is open, open the context menu
|
||||
setActiveMenuId(docId);
|
||||
};
|
||||
|
||||
// Close menu when clicking outside
|
||||
@@ -223,6 +230,16 @@ export default function Documents({
|
||||
|
||||
const getActionOptions = (index: number, document: Doc): MenuOption[] => {
|
||||
const actions: MenuOption[] = [
|
||||
{
|
||||
icon: EyeView,
|
||||
label: t('settings.documents.view'),
|
||||
onClick: () => {
|
||||
setShowDocumentChunks(document);
|
||||
},
|
||||
iconWidth: 18,
|
||||
iconHeight: 18,
|
||||
variant: 'primary',
|
||||
},
|
||||
{
|
||||
icon: Trash,
|
||||
label: t('convTile.delete'),
|
||||
@@ -312,8 +329,8 @@ export default function Documents({
|
||||
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver uppercase w-[45%]">
|
||||
{t('settings.documents.name')}
|
||||
</th>
|
||||
<th className="py-3 px-4 text-center text-xs font-medium text-sonic-silver uppercase w-[20%]">
|
||||
<div className="flex justify-center items-center">
|
||||
<th className="py-3 px-4 text-xs font-medium text-sonic-silver uppercase w-[30%]">
|
||||
<div className="flex justify-start items-center">
|
||||
{t('settings.documents.date')}
|
||||
<img
|
||||
className="cursor-pointer ml-2"
|
||||
@@ -323,8 +340,8 @@ export default function Documents({
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
<th className="py-3 px-4 text-center text-xs font-medium text-sonic-silver uppercase w-[25%]">
|
||||
<div className="flex justify-center items-center">
|
||||
<th className="py-3 px-4 text-xs font-medium text-sonic-silver uppercase w-[15%]">
|
||||
<div className="flex justify-start items-center">
|
||||
<span className="hidden sm:inline">
|
||||
{t('settings.documents.tokenUsage')}
|
||||
</span>
|
||||
@@ -339,10 +356,8 @@ export default function Documents({
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
<th className="py-3 px-4 text-right text-xs font-medium text-gray-700 dark:text-[#E0E0E0] uppercase w-[10%]">
|
||||
<span className="sr-only">
|
||||
{t('settings.documents.actions')}
|
||||
</span>
|
||||
<th className="py-3 px-4 sr-only w-[10%]">
|
||||
{t('settings.documents.actions')}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -363,27 +378,23 @@ export default function Documents({
|
||||
const docId = document.id ? document.id.toString() : '';
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={docId}
|
||||
className="group transition-colors cursor-pointer"
|
||||
onClick={() => setShowDocumentChunks(document)}
|
||||
>
|
||||
<tr key={docId} className="group transition-colors">
|
||||
<td
|
||||
className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] w-[45%] min-w-48 max-w-0 truncate group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50"
|
||||
className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] min-w-48 max-w-0 truncate group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50"
|
||||
title={document.name}
|
||||
>
|
||||
{document.name}
|
||||
</td>
|
||||
<td className="py-4 px-4 text-center text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap w-[20%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
|
||||
<td className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
|
||||
{document.date ? formatDate(document.date) : ''}
|
||||
</td>
|
||||
<td className="py-4 px-4 text-center text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap w-[25%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
|
||||
<td className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
|
||||
{document.tokens
|
||||
? formatTokens(+document.tokens)
|
||||
: ''}
|
||||
</td>
|
||||
<td
|
||||
className="py-4 px-4 text-right w-[10%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50"
|
||||
className="py-4 px-4 text-right group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div
|
||||
@@ -412,7 +423,9 @@ export default function Documents({
|
||||
}));
|
||||
}}
|
||||
anchorRef={getMenuRef(docId)}
|
||||
className="absolute right-12 top-0"
|
||||
position="bottom-left"
|
||||
offset={{ x: 24, y: -24 }}
|
||||
className="min-w-[120px]"
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user