Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 17 additions & 21 deletions frontend/src/components/ui/BrowsePage/FileViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useEffect, useState } from 'react';
import { Typography } from '@material-tailwind/react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import {
Expand All @@ -10,6 +9,7 @@ import { useFileBrowserContext } from '@/contexts/FileBrowserContext';
import { formatFileSize, formatUnixTimestamp } from '@/utils';
import type { FileOrFolder } from '@/shared.types';
import { useFileContentQuery } from '@/queries/fileContentQueries';
import useDarkMode from '@/hooks/useDarkMode';

type FileViewerProps = {
readonly file: FileOrFolder;
Expand Down Expand Up @@ -76,27 +76,9 @@ const getLanguageFromExtension = (filename: string): string => {

export default function FileViewer({ file }: FileViewerProps) {
const { fspName } = useFileBrowserContext();

const [isDarkMode, setIsDarkMode] = useState<boolean>(false);

const isDarkMode = useDarkMode();
const contentQuery = useFileContentQuery(fspName, file.path);

// Detect dark mode from document
useEffect(() => {
const checkDarkMode = () => {
setIsDarkMode(document.documentElement.classList.contains('dark'));
};

checkDarkMode();
const observer = new MutationObserver(checkDarkMode);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
});

return () => observer.disconnect();
}, []);

const renderViewer = () => {
if (contentQuery.isLoading) {
return (
Expand All @@ -121,11 +103,25 @@ export default function FileViewer({ file }: FileViewerProps) {
const language = getLanguageFromExtension(file.name);
const content = contentQuery.data ?? '';

// Get the theme's code styles and merge with padding bottom for scrollbar
const theme = isDarkMode ? materialDark : coy;
const themeCodeStyles = theme['code[class*="language-"]'] || {};
const mergedCodeTagProps = {
style: {
...themeCodeStyles,
paddingBottom: '1em'
}
};

return (
<SyntaxHighlighter
codeTagProps={mergedCodeTagProps}
customStyle={{
margin: 0,
padding: '1rem',
paddingTop: '1em',
paddingRight: '1em',
paddingBottom: '0',
paddingLeft: '1em',
fontSize: '14px',
lineHeight: '1.5'
}}
Expand Down
41 changes: 13 additions & 28 deletions frontend/src/components/ui/BrowsePage/NavigationButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { useState } from 'react';
import type { MouseEvent } from 'react';
import { IoNavigateCircleSharp } from 'react-icons/io5';

import FgTooltip from '@/components/ui/widgets/FgTooltip';
import DialogIconBtn from '@/components/ui/buttons/DialogIconBtn';
import NavigationInput from '@/components/ui/BrowsePage/NavigateInput';
import FgDialog from '@/components/ui/Dialogs/FgDialog';

type NavigationButtonProps = {
readonly triggerClasses: string;
Expand All @@ -13,30 +10,18 @@ type NavigationButtonProps = {
export default function NavigationButton({
triggerClasses
}: NavigationButtonProps) {
const [showNavigationDialog, setShowNavigationDialog] = useState(false);

return (
<>
<FgTooltip
icon={IoNavigateCircleSharp}
label="Navigate to a path"
onClick={(e: MouseEvent<HTMLButtonElement>) => {
setShowNavigationDialog(true);
e.currentTarget.blur();
}}
triggerClasses={triggerClasses}
/>
{showNavigationDialog ? (
<FgDialog
onClose={() => setShowNavigationDialog(false)}
open={showNavigationDialog}
>
<NavigationInput
location="dialog"
setShowNavigationDialog={setShowNavigationDialog}
/>
</FgDialog>
) : null}
</>
<DialogIconBtn
icon={IoNavigateCircleSharp}
label="Navigate to a path"
triggerClasses={triggerClasses}
>
{closeDialog => (
<NavigationInput
location="dialog"
setShowNavigationDialog={closeDialog}
/>
)}
</DialogIconBtn>
);
}
131 changes: 59 additions & 72 deletions frontend/src/components/ui/BrowsePage/NewFolderButton.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { useState } from 'react';
import type { ChangeEvent, MouseEvent } from 'react';
import type { ChangeEvent } from 'react';
import { Button, Typography } from '@material-tailwind/react';
import { HiFolderAdd } from 'react-icons/hi';
import toast from 'react-hot-toast';

import FgTooltip from '@/components/ui/widgets/FgTooltip';
import FgDialog from '@/components/ui/Dialogs/FgDialog';
import DialogIconBtn from '@/components/ui/buttons/DialogIconBtn';
import { Spinner } from '@/components/ui/widgets/Loaders';
import useNewFolderDialog from '@/hooks/useNewFolderDialog';
import { useFileBrowserContext } from '@/contexts/FileBrowserContext';
Expand All @@ -17,15 +15,17 @@ type NewFolderButtonProps = {
export default function NewFolderButton({
triggerClasses
}: NewFolderButtonProps) {
const [showNewFolderDialog, setShowNewFolderDialog] = useState(false);
const { fspName, mutations } = useFileBrowserContext();
const { handleNewFolderSubmit, newName, setNewName, isDuplicateName } =
useNewFolderDialog();

const isSubmitDisabled =
!newName.trim() || isDuplicateName || mutations.createFolder.isPending;

const formSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
const formSubmit = async (
event: React.FormEvent<HTMLFormElement>,
closeDialog: () => void
) => {
event.preventDefault();
const result = await handleNewFolderSubmit();
if (result.success) {
Expand All @@ -34,75 +34,62 @@ export default function NewFolderButton({
} else {
toast.error(`Error creating folder: ${result.error}`);
}
setShowNewFolderDialog(false);
};

const handleClose = () => {
setNewName('');
setShowNewFolderDialog(false);
closeDialog();
};

return (
<>
<FgTooltip
as="button"
disabledCondition={!fspName}
icon={HiFolderAdd}
label="New folder"
onClick={(e: MouseEvent<HTMLButtonElement>) => {
setShowNewFolderDialog(true);
e.currentTarget.blur();
}}
triggerClasses={triggerClasses}
/>
{showNewFolderDialog ? (
<FgDialog onClose={handleClose} open={showNewFolderDialog}>
<form onSubmit={formSubmit}>
<div className="mt-8 flex flex-col gap-2">
<Typography
as="label"
className="text-foreground font-semibold"
htmlFor="new_name"
>
Create a New Folder
<DialogIconBtn
disabled={!fspName}
icon={HiFolderAdd}
label="New folder"
triggerClasses={triggerClasses}
>
{closeDialog => (
<form onSubmit={e => formSubmit(e, closeDialog)}>
<div className="mt-8 flex flex-col gap-2">
<Typography
as="label"
className="text-foreground font-semibold"
htmlFor="new_name"
>
Create a New Folder
</Typography>
<input
autoFocus
className="mb-4 p-2 text-foreground text-lg border border-primary-light rounded-sm focus:outline-none focus:border-primary bg-background"
id="new_name"
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setNewName(event.target.value);
}}
placeholder="Folder name ..."
type="text"
value={newName}
/>
</div>
<div className="flex items-center gap-2">
<Button
className="!rounded-md"
disabled={isSubmitDisabled}
type="submit"
>
{mutations.createFolder.isPending ? (
<Spinner customClasses="border-white" text="Creating..." />
) : (
'Submit'
)}
</Button>
{!newName.trim() ? (
<Typography className="text-sm text-gray-500">
Please enter a folder name
</Typography>
) : newName.trim() && isDuplicateName ? (
<Typography className="text-sm text-red-500">
A file or folder with this name already exists
</Typography>
<input
autoFocus
className="mb-4 p-2 text-foreground text-lg border border-primary-light rounded-sm focus:outline-none focus:border-primary bg-background"
id="new_name"
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setNewName(event.target.value);
}}
placeholder="Folder name ..."
type="text"
value={newName}
/>
</div>
<div className="flex items-center gap-2">
<Button
className="!rounded-md"
disabled={isSubmitDisabled}
type="submit"
>
{mutations.createFolder.isPending ? (
<Spinner customClasses="border-white" text="Creating..." />
) : (
'Submit'
)}
</Button>
{!newName.trim() ? (
<Typography className="text-sm text-gray-500">
Please enter a folder name
</Typography>
) : newName.trim() && isDuplicateName ? (
<Typography className="text-sm text-red-500">
A file or folder with this name already exists
</Typography>
) : null}
</div>
</form>
</FgDialog>
) : null}
</>
) : null}
</div>
</form>
)}
</DialogIconBtn>
);
}
27 changes: 21 additions & 6 deletions frontend/src/components/ui/BrowsePage/ZarrPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type { UseQueryResult } from '@tanstack/react-query';
import zarrLogo from '@/assets/zarr.jpg';
import ZarrMetadataTable from '@/components/ui/BrowsePage/ZarrMetadataTable';
import DataLinkDialog from '@/components/ui/Dialogs/DataLink';
import DataLinkUsageDialog from '@/components/ui/Dialogs/DataLinkUsageDialog';
import TextDialogBtn from '@/components/ui/buttons/DialogTextBtn';
import DataToolLinks from './DataToolLinks';
import type {
OpenWithToolUrls,
Expand Down Expand Up @@ -91,12 +93,25 @@ export default function ZarrPreview({
</div>

{openWithToolUrls ? (
<DataToolLinks
onToolClick={handleToolClick}
showCopiedTooltip={showCopiedTooltip}
title="Open with:"
urls={openWithToolUrls as OpenWithToolUrls}
/>
<>
<DataToolLinks
onToolClick={handleToolClick}
showCopiedTooltip={showCopiedTooltip}
title="Open with:"
urls={openWithToolUrls as OpenWithToolUrls}
/>
{openWithToolUrls.copy ? (
<TextDialogBtn label="More ways to open" variant="solid">
{closeDialog => (
<DataLinkUsageDialog
dataLinkUrl={openWithToolUrls.copy}
onClose={closeDialog}
open={true}
/>
)}
</TextDialogBtn>
) : null}
</>
) : null}

{showDataLinkDialog ? (
Expand Down
Loading