) => {
- // We only trigger selection on left click.
- // `onMouseDown` is used instead of `onClick` so we can mark an object as selected on focusing instead of after the click completes.
- if (e.button !== 0) return;
-
- if (e.ctrlKey) {
- addSelectedObject(object.path);
- } else {
- addSelectedObject(object.path, true);
- }
- };
-
- const handleDoubleClick = () => {
- if (object.isDirectory) {
- router.push(`/bucket/${currentBucket?.raw}/${object.path}`);
- } else {
- triggerFilePreview(object.path);
- }
- };
-
- const handleContextMenu = () => {
- addSelectedObject(object.path, true);
- // TODO: Render a context menu.
- };
-
return (
<>
{/* eslint-disable-next-line jsx-a11y/interactive-supports-focus */}
@@ -59,9 +30,9 @@ export const ObjectRow = ({ row, virtualRowSize }: Props): JSX.Element => {
'border-accent !bg-secondary dark:border-accent-dark dark:!bg-secondary-dark',
)}
style={{ height: virtualRowSize }}
- onMouseDown={handleMouseDown}
- onDoubleClick={handleDoubleClick}
- onContextMenu={handleContextMenu}
+ onMouseDown={(e) => handleMouseDown(e, object)}
+ onDoubleClick={() => handleDoubleClick(object)}
+ onContextMenu={() => handleContextMenu(object)}
>
{row.getVisibleCells().map((cell) => (
diff --git a/components/providers/explorer-events-provider.tsx b/components/providers/explorer-events-provider.tsx
new file mode 100644
index 0000000..90f383f
--- /dev/null
+++ b/components/providers/explorer-events-provider.tsx
@@ -0,0 +1,81 @@
+'use client';
+
+import { createContext, useCallback, useContext, useMemo } from 'react';
+import type { FileObject } from '@/utils/file-object';
+import { useRouter } from 'next/navigation';
+import { useObjectExplorer } from './object-explorer-provider';
+import { useLocation } from './location-provider';
+import { useFilePreview } from './file-preview-provider';
+
+export type IExplorerEventsContext = {
+ handleMouseDown: (e: React.MouseEvent, object: FileObject) => void;
+ handleDoubleClick: (object: Pick) => void;
+ handleContextMenu: (object: FileObject) => void;
+};
+
+const ExplorerEventsContext = createContext({
+ handleMouseDown: () => {},
+ handleDoubleClick: () => {},
+ handleContextMenu: () => {},
+});
+
+export const useExplorerEvents = () => useContext(ExplorerEventsContext);
+
+type Props = {
+ children: React.ReactNode;
+};
+
+export const ExplorerEventsProvider = ({ children }: Props): JSX.Element => {
+ const router = useRouter();
+ const { currentBucket } = useLocation();
+
+ const { addSelectedObject } = useObjectExplorer();
+ const { triggerFilePreview } = useFilePreview();
+
+ const handleMouseDown = useCallback(
+ (e: React.MouseEvent, object: FileObject) => {
+ // We only trigger selection on left click.
+ // `onMouseDown` is used instead of `onClick` so we can mark an object as selected on focusing instead of after the click completes.
+ if (e.button !== 0) return;
+
+ if (e.ctrlKey) {
+ addSelectedObject(object.path);
+ } else {
+ addSelectedObject(object.path, true);
+ }
+ },
+ [addSelectedObject],
+ );
+
+ const handleDoubleClick = useCallback(
+ (object: Pick) => {
+ if (object.isDirectory) {
+ router.push(`/bucket/${currentBucket?.raw}/${object.path}`);
+ } else {
+ triggerFilePreview(object.path);
+ }
+ },
+ [triggerFilePreview, router, currentBucket],
+ );
+
+ const handleContextMenu = useCallback(
+ (object: FileObject) => {
+ addSelectedObject(object.path, true);
+ // TODO: Render a context menu.
+ },
+ [addSelectedObject],
+ );
+
+ return (
+ ({ handleMouseDown, handleDoubleClick, handleContextMenu }),
+ [handleMouseDown, handleDoubleClick, handleContextMenu],
+ )}
+ >
+ {children}
+
+ );
+};
+
+export type { Props as ExplorerEventsProviderProps };
diff --git a/components/providers/file-preview-provider.tsx b/components/providers/file-preview-provider.tsx
index d935aca..8a9eef5 100644
--- a/components/providers/file-preview-provider.tsx
+++ b/components/providers/file-preview-provider.tsx
@@ -15,10 +15,12 @@ import { XCircle } from '../icons';
export type IFilePreviewContext = {
triggerFilePreview: (key: string) => void;
+ isFilePreviewActive: boolean;
};
const FilePreviewContext = createContext({
triggerFilePreview: () => {},
+ isFilePreviewActive: false,
});
export const useFilePreview = () => useContext(FilePreviewContext);
@@ -104,7 +106,10 @@ export const FilePreviewProvider = ({ bucketName, children }: Props): JSX.Elemen
return (
({ triggerFilePreview }), [triggerFilePreview])}
+ value={useMemo(
+ () => ({ triggerFilePreview, isFilePreviewActive: !!previewKey }),
+ [triggerFilePreview, previewKey],
+ )}
>
{children}
diff --git a/components/providers/index.ts b/components/providers/index.ts
index 2204eca..fb51d27 100644
--- a/components/providers/index.ts
+++ b/components/providers/index.ts
@@ -3,3 +3,4 @@ export * from './location-provider';
export * from './file-preview-provider';
export * from './object-explorer-provider';
export * from './upload-files-provider';
+export * from './explorer-events-provider';
diff --git a/components/providers/object-explorer-provider.tsx b/components/providers/object-explorer-provider.tsx
index 47e01fc..e5106e2 100644
--- a/components/providers/object-explorer-provider.tsx
+++ b/components/providers/object-explorer-provider.tsx
@@ -21,7 +21,7 @@ const ObjectExplorerContext = createContext({
objects: [],
updateObjects: () => {},
tryFetchMoreObjects: () => {},
- selectedObjects: new Set(),
+ selectedObjects: new Set(),
addSelectedObject: () => {},
removeSelectedObject: () => {},
clearSelectedObjects: () => {},
@@ -36,7 +36,7 @@ type Props = {
export const ObjectExplorerProvider = ({ children }: Props): JSX.Element => {
const { currentBucket, location } = useLocation();
const [objects, setObjects] = useState(undefined);
- const [selectedObjects, setSelectedObjects] = useState>(new Set());
+ const [selectedObjects, setSelectedObjects] = useState>(new Set());
const [, setIsFetchingMoreObjects] = useState(false);
const isFetchingMoreObjectsRef = useRef(false);
@@ -110,7 +110,7 @@ export const ObjectExplorerProvider = ({ children }: Props): JSX.Element => {
[selectedObjects],
);
- const clearSelectedObjects = useCallback(() => setSelectedObjects(new Set()), []);
+ const clearSelectedObjects = useCallback(() => setSelectedObjects(new Set()), []);
return (
{
},
};
};
+
+export type FileObject = ReturnType;