mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-15 02:54:06 +02:00
Add frontend autoformatting and set CI to require formatted code for all languages (#6052)
# Description of Changes Changes the strategy for autoformatting to reject PRs if they are not formatted correctly instead of allowing them to merge and then spawning a new PR to fix the formatting. The old strategy just caused more work for us because we'd have to manually approve the followup PR and get it merged, which required 2 reviewers so in practice it rarely got done and just meant everyone's PRs ended up containing reformatting for unrelated files, which makes code review unnecessarily difficult. If the PR's code is not formatted correctly after this PR, a comment will be added automatically to tell the author how to run the formatter script to fix their code so it can go in. This also enables autoformatting for the frontend code, using Prettier. I've enabled it for pretty much everything in the frontend folder, other than 3rd party files and files it doesn't make sense for. I also excluded Markdown because it sounds likely to be more annoying to have to autoformat the Markdown in the frontend folder but nowhere else. Open to changing this though if people disagree. > [!note] > > Advice to reviewers: The first commit contains all of the actual logic I've introduced (CI changes, Prettier config, etc.) > The second commit is just the reformatting of the entire frontend folder. > The first commit needs proper review, the second one just give it a spot-check that it's doing what you'd expect.
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Stack, Box, Text, Button, ActionIcon, Center } from '@mantine/core';
|
||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getFileSize } from '@app/utils/fileUtils';
|
||||
import { StirlingFileStub } from '@app/types/fileContext';
|
||||
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||
import React from "react";
|
||||
import { Stack, Box, Text, Button, ActionIcon, Center } from "@mantine/core";
|
||||
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
|
||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getFileSize } from "@app/utils/fileUtils";
|
||||
import { StirlingFileStub } from "@app/types/fileContext";
|
||||
import { PrivateContent } from "@app/components/shared/PrivateContent";
|
||||
|
||||
interface CompactFileDetailsProps {
|
||||
currentFile: StirlingFileStub | null;
|
||||
@@ -29,47 +29,56 @@ const CompactFileDetails: React.FC<CompactFileDetailsProps> = ({
|
||||
isAnimating,
|
||||
onPrevious,
|
||||
onNext,
|
||||
onOpenFiles
|
||||
onOpenFiles,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const hasSelection = selectedFiles.length > 0;
|
||||
const hasMultipleFiles = numberOfFiles > 1;
|
||||
const showOwner = Boolean(
|
||||
currentFile &&
|
||||
(currentFile.remoteOwnedByCurrentUser === false || currentFile.remoteSharedViaLink)
|
||||
currentFile && (currentFile.remoteOwnedByCurrentUser === false || currentFile.remoteSharedViaLink),
|
||||
);
|
||||
const ownerLabel = currentFile
|
||||
? currentFile.remoteOwnerUsername || t('fileManager.ownerUnknown', 'Unknown')
|
||||
: '';
|
||||
const ownerLabel = currentFile ? currentFile.remoteOwnerUsername || t("fileManager.ownerUnknown", "Unknown") : "";
|
||||
|
||||
return (
|
||||
<Stack gap="xs" style={{ height: '100%' }}>
|
||||
<Stack gap="xs" style={{ height: "100%" }}>
|
||||
{/* Compact mobile layout */}
|
||||
<Box style={{ display: 'flex', gap: '0.75rem', alignItems: 'center' }}>
|
||||
<Box style={{ display: "flex", gap: "0.75rem", alignItems: "center" }}>
|
||||
{/* Small preview */}
|
||||
<Box style={{ width: '7.5rem', height: '9.375rem', flexShrink: 0, position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Box
|
||||
style={{
|
||||
width: "7.5rem",
|
||||
height: "9.375rem",
|
||||
flexShrink: 0,
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{currentFile && thumbnail ? (
|
||||
<PrivateContent>
|
||||
<img
|
||||
src={thumbnail}
|
||||
alt={currentFile.name}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
objectFit: 'contain',
|
||||
borderRadius: '0.25rem',
|
||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)'
|
||||
maxWidth: "100%",
|
||||
maxHeight: "100%",
|
||||
objectFit: "contain",
|
||||
borderRadius: "0.25rem",
|
||||
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
|
||||
}}
|
||||
/>
|
||||
</PrivateContent>
|
||||
) : currentFile ? (
|
||||
<Center style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'var(--mantine-color-gray-1)',
|
||||
borderRadius: 4
|
||||
}}>
|
||||
<PictureAsPdfIcon style={{ fontSize: 20, color: 'var(--mantine-color-gray-6)' }} />
|
||||
<Center
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "var(--mantine-color-gray-1)",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<PictureAsPdfIcon style={{ fontSize: 20, color: "var(--mantine-color-gray-6)" }} />
|
||||
</Center>
|
||||
) : null}
|
||||
</Box>
|
||||
@@ -77,10 +86,10 @@ const CompactFileDetails: React.FC<CompactFileDetailsProps> = ({
|
||||
{/* File info */}
|
||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="sm" fw={500} truncate>
|
||||
<PrivateContent>{currentFile ? currentFile.name : 'No file selected'}</PrivateContent>
|
||||
<PrivateContent>{currentFile ? currentFile.name : "No file selected"}</PrivateContent>
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{currentFile ? getFileSize(currentFile) : ''}
|
||||
{currentFile ? getFileSize(currentFile) : ""}
|
||||
{selectedFiles.length > 1 && ` • ${selectedFiles.length} files`}
|
||||
{currentFile && ` • v${currentFile.versionNumber || 1}`}
|
||||
</Text>
|
||||
@@ -92,33 +101,23 @@ const CompactFileDetails: React.FC<CompactFileDetailsProps> = ({
|
||||
{/* Compact tool chain for mobile */}
|
||||
{currentFile?.toolHistory && currentFile.toolHistory.length > 0 && (
|
||||
<Text size="xs" c="dimmed">
|
||||
{currentFile.toolHistory.map((tool) => t(`home.${tool.toolId}.title`, tool.toolId)).join(' → ')}
|
||||
{currentFile.toolHistory.map((tool) => t(`home.${tool.toolId}.title`, tool.toolId)).join(" → ")}
|
||||
</Text>
|
||||
)}
|
||||
{currentFile && showOwner && (
|
||||
<Text size="xs" c="dimmed">
|
||||
{t('fileManager.owner', 'Owner')}: {ownerLabel}
|
||||
{t("fileManager.owner", "Owner")}: {ownerLabel}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Navigation arrows for multiple files */}
|
||||
{hasMultipleFiles && (
|
||||
<Box style={{ display: 'flex', gap: '0.25rem' }}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={onPrevious}
|
||||
disabled={isAnimating}
|
||||
>
|
||||
<Box style={{ display: "flex", gap: "0.25rem" }}>
|
||||
<ActionIcon variant="subtle" size="sm" onClick={onPrevious} disabled={isAnimating}>
|
||||
<ChevronLeftIcon style={{ fontSize: 16 }} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={onNext}
|
||||
disabled={isAnimating}
|
||||
>
|
||||
<ActionIcon variant="subtle" size="sm" onClick={onNext} disabled={isAnimating}>
|
||||
<ChevronRightIcon style={{ fontSize: 16 }} />
|
||||
</ActionIcon>
|
||||
</Box>
|
||||
@@ -132,14 +131,13 @@ const CompactFileDetails: React.FC<CompactFileDetailsProps> = ({
|
||||
disabled={!hasSelection}
|
||||
fullWidth
|
||||
style={{
|
||||
backgroundColor: hasSelection ? 'var(--btn-open-file)' : 'var(--mantine-color-gray-4)',
|
||||
color: 'white'
|
||||
backgroundColor: hasSelection ? "var(--btn-open-file)" : "var(--mantine-color-gray-4)",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
{selectedFiles.length > 1
|
||||
? t('fileManager.openFiles', `Open ${selectedFiles.length} Files`)
|
||||
: t('fileManager.openFile', 'Open File')
|
||||
}
|
||||
? t("fileManager.openFiles", `Open ${selectedFiles.length} Files`)
|
||||
: t("fileManager.openFile", "Open File")}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,79 +1,85 @@
|
||||
import React from 'react';
|
||||
import { Grid } from '@mantine/core';
|
||||
import FileSourceButtons from '@app/components/fileManager/FileSourceButtons';
|
||||
import FileDetails from '@app/components/fileManager/FileDetails';
|
||||
import SearchInput from '@app/components/fileManager/SearchInput';
|
||||
import FileListArea from '@app/components/fileManager/FileListArea';
|
||||
import FileActions from '@app/components/fileManager/FileActions';
|
||||
import HiddenFileInput from '@app/components/fileManager/HiddenFileInput';
|
||||
import { useFileManagerContext } from '@app/contexts/FileManagerContext';
|
||||
import React from "react";
|
||||
import { Grid } from "@mantine/core";
|
||||
import FileSourceButtons from "@app/components/fileManager/FileSourceButtons";
|
||||
import FileDetails from "@app/components/fileManager/FileDetails";
|
||||
import SearchInput from "@app/components/fileManager/SearchInput";
|
||||
import FileListArea from "@app/components/fileManager/FileListArea";
|
||||
import FileActions from "@app/components/fileManager/FileActions";
|
||||
import HiddenFileInput from "@app/components/fileManager/HiddenFileInput";
|
||||
import { useFileManagerContext } from "@app/contexts/FileManagerContext";
|
||||
|
||||
const DesktopLayout: React.FC = () => {
|
||||
const {
|
||||
activeSource,
|
||||
recentFiles,
|
||||
modalHeight,
|
||||
} = useFileManagerContext();
|
||||
const { activeSource, recentFiles, modalHeight } = useFileManagerContext();
|
||||
|
||||
return (
|
||||
<Grid gutter="xs" h="100%" grow={false} style={{ flexWrap: 'nowrap', minWidth: 0 }}>
|
||||
<Grid gutter="xs" h="100%" grow={false} style={{ flexWrap: "nowrap", minWidth: 0 }}>
|
||||
{/* Column 1: File Sources */}
|
||||
<Grid.Col span="content" p="lg" style={{
|
||||
minWidth: '13.625rem',
|
||||
width: '13.625rem',
|
||||
flexShrink: 0,
|
||||
height: '100%',
|
||||
}} data-tour="file-sources">
|
||||
<Grid.Col
|
||||
span="content"
|
||||
p="lg"
|
||||
style={{
|
||||
minWidth: "13.625rem",
|
||||
width: "13.625rem",
|
||||
flexShrink: 0,
|
||||
height: "100%",
|
||||
}}
|
||||
data-tour="file-sources"
|
||||
>
|
||||
<FileSourceButtons />
|
||||
</Grid.Col>
|
||||
|
||||
{/* Column 2: File List */}
|
||||
<Grid.Col span="auto" style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
flex: '1 1 0px'
|
||||
}}>
|
||||
<div style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: 'var(--bg-file-list)',
|
||||
border: '1px solid var(--mantine-color-gray-2)',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{activeSource === 'recent' && (
|
||||
<Grid.Col
|
||||
span="auto"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
flex: "1 1 0px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
backgroundColor: "var(--bg-file-list)",
|
||||
border: "1px solid var(--mantine-color-gray-2)",
|
||||
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{activeSource === "recent" && (
|
||||
<>
|
||||
<div style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: '1px solid var(--mantine-color-gray-3)'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: "1px solid var(--mantine-color-gray-3)",
|
||||
}}
|
||||
>
|
||||
<SearchInput />
|
||||
</div>
|
||||
<div style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: '1px solid var(--mantine-color-gray-3)'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: "1px solid var(--mantine-color-gray-3)",
|
||||
}}
|
||||
>
|
||||
<FileActions />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div style={{ flex: 1, minHeight: 0, overflow: 'hidden' }}>
|
||||
<div style={{ flex: 1, minHeight: 0, overflow: "hidden" }}>
|
||||
<FileListArea
|
||||
scrollAreaHeight={activeSource === 'recent' && recentFiles.length > 0
|
||||
? `calc(${modalHeight} - 7rem)`
|
||||
: '100%'}
|
||||
scrollAreaHeight={activeSource === "recent" && recentFiles.length > 0 ? `calc(${modalHeight} - 7rem)` : "100%"}
|
||||
scrollAreaStyle={{
|
||||
height: activeSource === 'recent' && recentFiles.length > 0
|
||||
? `calc(${modalHeight} - 7rem)`
|
||||
: '100%',
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
borderRadius: 0
|
||||
height: activeSource === "recent" && recentFiles.length > 0 ? `calc(${modalHeight} - 7rem)` : "100%",
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
borderRadius: 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -81,14 +87,18 @@ const DesktopLayout: React.FC = () => {
|
||||
</Grid.Col>
|
||||
|
||||
{/* Column 3: File Details */}
|
||||
<Grid.Col p="xl" span="content" style={{
|
||||
minWidth: '25rem',
|
||||
width: '25rem',
|
||||
flexShrink: 0,
|
||||
height: '100%',
|
||||
maxWidth: '18rem'
|
||||
}}>
|
||||
<div style={{ height: '100%', overflow: 'hidden' }}>
|
||||
<Grid.Col
|
||||
p="xl"
|
||||
span="content"
|
||||
style={{
|
||||
minWidth: "25rem",
|
||||
width: "25rem",
|
||||
flexShrink: 0,
|
||||
height: "100%",
|
||||
maxWidth: "18rem",
|
||||
}}
|
||||
>
|
||||
<div style={{ height: "100%", overflow: "hidden" }}>
|
||||
<FileDetails />
|
||||
</div>
|
||||
</Grid.Col>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Stack, Text, useMantineTheme, alpha } from '@mantine/core';
|
||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React from "react";
|
||||
import { Stack, Text, useMantineTheme, alpha } from "@mantine/core";
|
||||
import UploadFileIcon from "@mui/icons-material/UploadFile";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface DragOverlayProps {
|
||||
isVisible: boolean;
|
||||
@@ -16,29 +16,29 @@ const DragOverlay: React.FC<DragOverlayProps> = ({ isVisible }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: alpha(theme.colors.blue[6], 0.1),
|
||||
border: `0.125rem dashed ${theme.colors.blue[6]}`,
|
||||
borderRadius: '1.875rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: "1.875rem",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
zIndex: 1000,
|
||||
pointerEvents: 'none'
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
<Stack align="center" gap="md">
|
||||
<UploadFileIcon style={{ fontSize: '4rem', color: theme.colors.blue[6] }} />
|
||||
<UploadFileIcon style={{ fontSize: "4rem", color: theme.colors.blue[6] }} />
|
||||
<Text size="xl" fw={500} c="blue.6">
|
||||
{t('fileManager.dropFilesHere', 'Drop files here to upload')}
|
||||
{t("fileManager.dropFilesHere", "Drop files here to upload")}
|
||||
</Text>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DragOverlay;
|
||||
export default DragOverlay;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Group, Text, Stack, useMantineColorScheme } from '@mantine/core';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFileManagerContext } from '@app/contexts/FileManagerContext';
|
||||
import LocalIcon from '@app/components/shared/LocalIcon';
|
||||
import { useLogoAssets } from '@app/hooks/useLogoAssets';
|
||||
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
|
||||
import { useFileActionIcons } from '@app/hooks/useFileActionIcons';
|
||||
import React, { useState } from "react";
|
||||
import { Button, Group, Text, Stack, useMantineColorScheme } from "@mantine/core";
|
||||
import HistoryIcon from "@mui/icons-material/History";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFileManagerContext } from "@app/contexts/FileManagerContext";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
import { useLogoAssets } from "@app/hooks/useLogoAssets";
|
||||
import { useFileActionTerminology } from "@app/hooks/useFileActionTerminology";
|
||||
import { useFileActionIcons } from "@app/hooks/useFileActionIcons";
|
||||
|
||||
const EmptyFilesState: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -24,92 +24,90 @@ const EmptyFilesState: React.FC = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '2rem'
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "2rem",
|
||||
}}
|
||||
>
|
||||
{/* Container */}
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
padding: '3rem 2rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1.5rem',
|
||||
minWidth: '20rem',
|
||||
maxWidth: '28rem',
|
||||
width: '100%'
|
||||
backgroundColor: "transparent",
|
||||
padding: "3rem 2rem",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: "1.5rem",
|
||||
minWidth: "20rem",
|
||||
maxWidth: "28rem",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{/* No Recent Files Message */}
|
||||
<Stack align="center" gap="sm">
|
||||
<HistoryIcon style={{ fontSize: '3rem', color: 'var(--mantine-color-gray-5)' }} />
|
||||
<HistoryIcon style={{ fontSize: "3rem", color: "var(--mantine-color-gray-5)" }} />
|
||||
<Text c="dimmed" ta="center" size="lg">
|
||||
{t('fileManager.noRecentFiles', 'No recent files')}
|
||||
{t("fileManager.noRecentFiles", "No recent files")}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
{/* Stirling PDF Logo */}
|
||||
<Group gap="xs" align="center">
|
||||
<img
|
||||
src={colorScheme === 'dark' ? wordmark.white : wordmark.grey}
|
||||
src={colorScheme === "dark" ? wordmark.white : wordmark.grey}
|
||||
alt="Stirling PDF"
|
||||
style={{ height: '2.2rem', width: 'auto' }}
|
||||
style={{ height: "2.2rem", width: "auto" }}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
{/* Upload Button */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
marginTop: '0.5rem',
|
||||
marginBottom: '0.5rem'
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
marginTop: "0.5rem",
|
||||
marginBottom: "0.5rem",
|
||||
}}
|
||||
onMouseLeave={() => setIsUploadHover(false)}
|
||||
>
|
||||
<Button
|
||||
aria-label="Upload"
|
||||
style={{
|
||||
backgroundColor: 'var(--bg-file-manager)',
|
||||
color: 'var(--landing-button-color)',
|
||||
border: '1px solid var(--landing-button-border)',
|
||||
borderRadius: isUploadHover ? '2rem' : '1rem',
|
||||
height: '38px',
|
||||
width: isUploadHover ? '100%' : '58px',
|
||||
minWidth: '58px',
|
||||
paddingLeft: isUploadHover ? '1rem' : 0,
|
||||
paddingRight: isUploadHover ? '1rem' : 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition: 'width .5s ease, padding .5s ease, border-radius .5s ease'
|
||||
backgroundColor: "var(--bg-file-manager)",
|
||||
color: "var(--landing-button-color)",
|
||||
border: "1px solid var(--landing-button-border)",
|
||||
borderRadius: isUploadHover ? "2rem" : "1rem",
|
||||
height: "38px",
|
||||
width: isUploadHover ? "100%" : "58px",
|
||||
minWidth: "58px",
|
||||
paddingLeft: isUploadHover ? "1rem" : 0,
|
||||
paddingRight: isUploadHover ? "1rem" : 0,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
transition: "width .5s ease, padding .5s ease, border-radius .5s ease",
|
||||
}}
|
||||
onClick={handleUploadClick}
|
||||
onMouseEnter={() => setIsUploadHover(true)}
|
||||
>
|
||||
<LocalIcon icon={icons.uploadIconName} width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} />
|
||||
{isUploadHover && (
|
||||
<span style={{ marginLeft: '.5rem' }}>
|
||||
{terminology.uploadFromComputer}
|
||||
</span>
|
||||
)}
|
||||
<LocalIcon
|
||||
icon={icons.uploadIconName}
|
||||
width="1.25rem"
|
||||
height="1.25rem"
|
||||
style={{ color: "var(--accent-interactive)" }}
|
||||
/>
|
||||
{isUploadHover && <span style={{ marginLeft: ".5rem" }}>{terminology.uploadFromComputer}</span>}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Instruction Text */}
|
||||
<span
|
||||
className="text-[var(--accent-interactive)]"
|
||||
style={{ fontSize: '.8rem', textAlign: 'center' }}
|
||||
>
|
||||
<span className="text-[var(--accent-interactive)]" style={{ fontSize: ".8rem", textAlign: "center" }}>
|
||||
{terminology.dropFilesHere}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -30,9 +30,8 @@ const FileActions: React.FC = () => {
|
||||
onDownloadSelected,
|
||||
refreshRecentFiles,
|
||||
storageFilter,
|
||||
onStorageFilterChange
|
||||
} =
|
||||
useFileManagerContext();
|
||||
onStorageFilterChange,
|
||||
} = useFileManagerContext();
|
||||
const uploadEnabled = config?.storageEnabled === true;
|
||||
const sharingEnabled = uploadEnabled && config?.storageSharingEnabled === true;
|
||||
const shareLinksEnabled = sharingEnabled && config?.storageShareLinksEnabled === true;
|
||||
@@ -42,11 +41,11 @@ const FileActions: React.FC = () => {
|
||||
{ value: "all", label: t("fileManager.filterAll", "All") },
|
||||
{ value: "local", label: t("fileManager.filterLocal", "Local") },
|
||||
{ value: "sharedWithMe", label: t("fileManager.filterSharedWithMe", "Shared with me") },
|
||||
{ value: "sharedByMe", label: t("fileManager.filterSharedByMe", "Shared by me") }
|
||||
{ value: "sharedByMe", label: t("fileManager.filterSharedByMe", "Shared by me") },
|
||||
]
|
||||
: [
|
||||
{ value: "all", label: t("fileManager.filterAll", "All") },
|
||||
{ value: "local", label: t("fileManager.filterLocal", "Local") }
|
||||
{ value: "local", label: t("fileManager.filterLocal", "Local") },
|
||||
];
|
||||
useEffect(() => {
|
||||
if (!sharingEnabled && (storageFilter === "sharedWithMe" || storageFilter === "sharedByMe")) {
|
||||
@@ -56,10 +55,8 @@ const FileActions: React.FC = () => {
|
||||
const hasSelection = selectedFileIds.length > 0;
|
||||
const hasOnlyOwnedSelection = selectedFiles.every((file) => file.remoteOwnedByCurrentUser !== false);
|
||||
const hasDownloadAccess = selectedFiles.every((file) => {
|
||||
const role = (file.remoteOwnedByCurrentUser !== false
|
||||
? 'editor'
|
||||
: (file.remoteAccessRole ?? 'viewer')).toLowerCase();
|
||||
return role === 'editor' || role === 'commenter' || role === 'viewer';
|
||||
const role = (file.remoteOwnedByCurrentUser !== false ? "editor" : (file.remoteAccessRole ?? "viewer")).toLowerCase();
|
||||
return role === "editor" || role === "commenter" || role === "viewer";
|
||||
});
|
||||
const canBulkUpload = uploadEnabled && hasSelection && hasOnlyOwnedSelection;
|
||||
const canBulkShare = shareLinksEnabled && hasSelection && hasOnlyOwnedSelection;
|
||||
@@ -80,7 +77,6 @@ const FileActions: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Only show actions if there are files
|
||||
if (recentFiles.length === 0) {
|
||||
return null;
|
||||
@@ -120,9 +116,7 @@ const FileActions: React.FC = () => {
|
||||
<SegmentedControl
|
||||
size="xs"
|
||||
value={storageFilter}
|
||||
onChange={(value) =>
|
||||
onStorageFilterChange(value as "all" | "local" | "sharedWithMe" | "sharedByMe")
|
||||
}
|
||||
onChange={(value) => onStorageFilterChange(value as "all" | "local" | "sharedWithMe" | "sharedByMe")}
|
||||
data={storageFilterOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Stack, Button, Box } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useIndexedDBThumbnail } from '@app/hooks/useIndexedDBThumbnail';
|
||||
import { useFileManagerContext } from '@app/contexts/FileManagerContext';
|
||||
import FilePreview from '@app/components/shared/FilePreview';
|
||||
import FileInfoCard from '@app/components/fileManager/FileInfoCard';
|
||||
import CompactFileDetails from '@app/components/fileManager/CompactFileDetails';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Stack, Button, Box } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useIndexedDBThumbnail } from "@app/hooks/useIndexedDBThumbnail";
|
||||
import { useFileManagerContext } from "@app/contexts/FileManagerContext";
|
||||
import FilePreview from "@app/components/shared/FilePreview";
|
||||
import FileInfoCard from "@app/components/fileManager/FileInfoCard";
|
||||
import CompactFileDetails from "@app/components/fileManager/CompactFileDetails";
|
||||
|
||||
interface FileDetailsProps {
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
const FileDetails: React.FC<FileDetailsProps> = ({
|
||||
compact = false
|
||||
}) => {
|
||||
const FileDetails: React.FC<FileDetailsProps> = ({ compact = false }) => {
|
||||
const { selectedFiles, onOpenFiles, modalHeight } = useFileManagerContext();
|
||||
const { t } = useTranslation();
|
||||
const [currentFileIndex, setCurrentFileIndex] = useState(0);
|
||||
@@ -35,7 +33,7 @@ const FileDetails: React.FC<FileDetailsProps> = ({
|
||||
if (isAnimating) return;
|
||||
setIsAnimating(true);
|
||||
setTimeout(() => {
|
||||
setCurrentFileIndex(prev => prev > 0 ? prev - 1 : selectedFiles.length - 1);
|
||||
setCurrentFileIndex((prev) => (prev > 0 ? prev - 1 : selectedFiles.length - 1));
|
||||
setIsAnimating(false);
|
||||
}, 150);
|
||||
};
|
||||
@@ -44,7 +42,7 @@ const FileDetails: React.FC<FileDetailsProps> = ({
|
||||
if (isAnimating) return;
|
||||
setIsAnimating(true);
|
||||
setTimeout(() => {
|
||||
setCurrentFileIndex(prev => prev < selectedFiles.length - 1 ? prev + 1 : 0);
|
||||
setCurrentFileIndex((prev) => (prev < selectedFiles.length - 1 ? prev + 1 : 0));
|
||||
setIsAnimating(false);
|
||||
}, 150);
|
||||
};
|
||||
@@ -75,7 +73,7 @@ const FileDetails: React.FC<FileDetailsProps> = ({
|
||||
return (
|
||||
<Stack gap="lg" h={`calc(${modalHeight} - 2rem)`} justify="flex-start">
|
||||
{/* Section 1: Thumbnail Preview */}
|
||||
<Box style={{ width: '100%', height: 'min(35vh, 280px)', textAlign: 'center', flexShrink: 0 }}>
|
||||
<Box style={{ width: "100%", height: "min(35vh, 280px)", textAlign: "center", flexShrink: 0 }}>
|
||||
<FilePreview
|
||||
file={currentFile}
|
||||
thumbnail={getCurrentThumbnail()}
|
||||
@@ -89,10 +87,7 @@ const FileDetails: React.FC<FileDetailsProps> = ({
|
||||
</Box>
|
||||
|
||||
{/* Section 2: File Details */}
|
||||
<FileInfoCard
|
||||
currentFile={currentFile}
|
||||
modalHeight={modalHeight}
|
||||
/>
|
||||
<FileInfoCard currentFile={currentFile} modalHeight={modalHeight} />
|
||||
|
||||
<Button
|
||||
size="md"
|
||||
@@ -100,14 +95,13 @@ const FileDetails: React.FC<FileDetailsProps> = ({
|
||||
disabled={!hasSelection}
|
||||
fullWidth
|
||||
style={{
|
||||
backgroundColor: hasSelection ? 'var(--btn-open-file)' : 'var(--mantine-color-gray-4)',
|
||||
color: 'white'
|
||||
backgroundColor: hasSelection ? "var(--btn-open-file)" : "var(--mantine-color-gray-4)",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
{selectedFiles.length > 1
|
||||
? t('fileManager.openFiles', `Open ${selectedFiles.length} Files`)
|
||||
: t('fileManager.openFile', 'Open File')
|
||||
}
|
||||
? t("fileManager.openFiles", `Open ${selectedFiles.length} Files`)
|
||||
: t("fileManager.openFile", "Open File")}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Box, Text, Collapse, Group } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { StirlingFileStub } from '@app/types/fileContext';
|
||||
import FileListItem from '@app/components/fileManager/FileListItem';
|
||||
import React from "react";
|
||||
import { Box, Text, Collapse, Group } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { StirlingFileStub } from "@app/types/fileContext";
|
||||
import FileListItem from "@app/components/fileManager/FileListItem";
|
||||
|
||||
interface FileHistoryGroupProps {
|
||||
leafFile: StirlingFileStub;
|
||||
@@ -27,7 +27,7 @@ const FileHistoryGroup: React.FC<FileHistoryGroupProps> = ({
|
||||
|
||||
// Sort history files by version number (oldest first, excluding the current leaf file)
|
||||
const sortedHistory = historyFiles
|
||||
.filter(file => file.id !== leafFile.id) // Exclude the leaf file itself
|
||||
.filter((file) => file.id !== leafFile.id) // Exclude the leaf file itself
|
||||
.sort((a, b) => (b.versionNumber || 1) - (a.versionNumber || 1));
|
||||
|
||||
if (!isExpanded || sortedHistory.length === 0) {
|
||||
@@ -39,7 +39,7 @@ const FileHistoryGroup: React.FC<FileHistoryGroupProps> = ({
|
||||
<Box ml="md" mt="xs" mb="sm">
|
||||
<Group align="center" mb="sm">
|
||||
<Text size="xs" fw={600} c="dimmed">
|
||||
{t('fileManager.fileHistory', 'File History')} ({sortedHistory.length})
|
||||
{t("fileManager.fileHistory", "File History")} ({sortedHistory.length})
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Stack, Card, Box, Text, Badge, Group, Divider, ScrollArea, Button } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { detectFileExtension, getFileSize } from '@app/utils/fileUtils';
|
||||
import { StirlingFileStub } from '@app/types/fileContext';
|
||||
import ToolChain from '@app/components/shared/ToolChain';
|
||||
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||
import { useFileManagerContext } from '@app/contexts/FileManagerContext';
|
||||
import ShareManagementModal from '@app/components/shared/ShareManagementModal';
|
||||
import { useAppConfig } from '@app/contexts/AppConfigContext';
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { Stack, Card, Box, Text, Badge, Group, Divider, ScrollArea, Button } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { detectFileExtension, getFileSize } from "@app/utils/fileUtils";
|
||||
import { StirlingFileStub } from "@app/types/fileContext";
|
||||
import ToolChain from "@app/components/shared/ToolChain";
|
||||
import { PrivateContent } from "@app/components/shared/PrivateContent";
|
||||
import { useFileManagerContext } from "@app/contexts/FileManagerContext";
|
||||
import ShareManagementModal from "@app/components/shared/ShareManagementModal";
|
||||
import { useAppConfig } from "@app/contexts/AppConfigContext";
|
||||
|
||||
interface FileInfoCardProps {
|
||||
currentFile: StirlingFileStub | null;
|
||||
modalHeight: string;
|
||||
}
|
||||
|
||||
const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
currentFile,
|
||||
modalHeight
|
||||
}) => {
|
||||
const FileInfoCard: React.FC<FileInfoCardProps> = ({ currentFile, modalHeight }) => {
|
||||
const { t } = useTranslation();
|
||||
const { config } = useAppConfig();
|
||||
const { onMakeCopy } = useFileManagerContext();
|
||||
@@ -43,38 +40,53 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
const uploadEnabled = config?.storageEnabled === true;
|
||||
const sharingEnabled = uploadEnabled && config?.storageSharingEnabled === true;
|
||||
const ownerLabel = useMemo(() => {
|
||||
if (!currentFile) return '';
|
||||
if (!currentFile) return "";
|
||||
if (currentFile.remoteOwnerUsername) {
|
||||
return currentFile.remoteOwnerUsername;
|
||||
}
|
||||
return t('fileManager.ownerUnknown', 'Unknown');
|
||||
return t("fileManager.ownerUnknown", "Unknown");
|
||||
}, [currentFile, t]);
|
||||
const lastSyncedLabel = useMemo(() => {
|
||||
if (!currentFile?.remoteStorageUpdatedAt) return '';
|
||||
if (!currentFile?.remoteStorageUpdatedAt) return "";
|
||||
return new Date(currentFile.remoteStorageUpdatedAt).toLocaleString();
|
||||
}, [currentFile?.remoteStorageUpdatedAt]);
|
||||
|
||||
return (
|
||||
<Card withBorder p={0} mah={`calc(${modalHeight} * 0.45)`} style={{ overflow: 'hidden', flexShrink: 1, display: 'flex', flexDirection: 'column' }}>
|
||||
<Box bg="gray.4" p="sm" style={{ borderTopLeftRadius: 'var(--mantine-radius-md)', borderTopRightRadius: 'var(--mantine-radius-md)', flexShrink: 0 }}>
|
||||
<Card
|
||||
withBorder
|
||||
p={0}
|
||||
mah={`calc(${modalHeight} * 0.45)`}
|
||||
style={{ overflow: "hidden", flexShrink: 1, display: "flex", flexDirection: "column" }}
|
||||
>
|
||||
<Box
|
||||
bg="gray.4"
|
||||
p="sm"
|
||||
style={{
|
||||
borderTopLeftRadius: "var(--mantine-radius-md)",
|
||||
borderTopRightRadius: "var(--mantine-radius-md)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Text size="sm" fw={500} ta="center" c="white">
|
||||
{t('fileManager.details', 'File Details')}
|
||||
{t("fileManager.details", "File Details")}
|
||||
</Text>
|
||||
</Box>
|
||||
<ScrollArea style={{ flex: 1, minHeight: 0 }} p="md">
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">
|
||||
<PrivateContent>{t('fileManager.fileName', 'Name')}</PrivateContent>
|
||||
<PrivateContent>{t("fileManager.fileName", "Name")}</PrivateContent>
|
||||
</Text>
|
||||
<Text size="sm" fw={500} style={{ maxWidth: '60%', textAlign: 'right' }} truncate>
|
||||
{currentFile ? currentFile.name : ''}
|
||||
<Text size="sm" fw={500} style={{ maxWidth: "60%", textAlign: "right" }} truncate>
|
||||
{currentFile ? currentFile.name : ""}
|
||||
</Text>
|
||||
</Group>
|
||||
<Divider />
|
||||
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.fileFormat', 'Format')}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("fileManager.fileFormat", "Format")}
|
||||
</Text>
|
||||
{currentFile ? (
|
||||
<Badge size="sm" variant="light">
|
||||
{detectFileExtension(currentFile.name).toUpperCase()}
|
||||
@@ -86,38 +98,48 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
<Divider />
|
||||
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.fileSize', 'Size')}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("fileManager.fileSize", "Size")}
|
||||
</Text>
|
||||
<Text size="sm" fw={500}>
|
||||
{currentFile ? getFileSize(currentFile) : ''}
|
||||
{currentFile ? getFileSize(currentFile) : ""}
|
||||
</Text>
|
||||
</Group>
|
||||
<Divider />
|
||||
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.lastModified', 'Last modified')}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("fileManager.lastModified", "Last modified")}
|
||||
</Text>
|
||||
<Text size="sm" fw={500}>
|
||||
{currentFile ? new Date(currentFile.lastModified).toLocaleDateString() : ''}
|
||||
{currentFile ? new Date(currentFile.lastModified).toLocaleDateString() : ""}
|
||||
</Text>
|
||||
</Group>
|
||||
<Divider />
|
||||
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.fileVersion', 'Version')}</Text>
|
||||
{currentFile &&
|
||||
<Badge size="sm" variant="light" color={currentFile?.versionNumber ? 'blue' : 'gray'}>
|
||||
v{currentFile ? (currentFile.versionNumber || 1) : ''}
|
||||
</Badge>}
|
||||
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("fileManager.fileVersion", "Version")}
|
||||
</Text>
|
||||
{currentFile && (
|
||||
<Badge size="sm" variant="light" color={currentFile?.versionNumber ? "blue" : "gray"}>
|
||||
v{currentFile ? currentFile.versionNumber || 1 : ""}
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
{sharingEnabled && isSharedWithYou && (
|
||||
<>
|
||||
<Divider />
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.owner', 'Owner')}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("fileManager.owner", "Owner")}
|
||||
</Text>
|
||||
<Group gap="xs">
|
||||
<Text size="sm" fw={500}>{ownerLabel}</Text>
|
||||
<Text size="sm" fw={500}>
|
||||
{ownerLabel}
|
||||
</Text>
|
||||
<Badge size="xs" variant="light" color="grape">
|
||||
{t('fileManager.sharedWithYou', 'Shared with you')}
|
||||
{t("fileManager.sharedWithYou", "Shared with you")}
|
||||
</Badge>
|
||||
</Group>
|
||||
</Group>
|
||||
@@ -129,12 +151,10 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
<>
|
||||
<Divider />
|
||||
<Box py="xs">
|
||||
<Text size="xs" c="dimmed" mb="xs">{t('fileManager.toolChain', 'Tools Applied')}</Text>
|
||||
<ToolChain
|
||||
toolChain={currentFile.toolHistory}
|
||||
displayStyle="badges"
|
||||
size="xs"
|
||||
/>
|
||||
<Text size="xs" c="dimmed" mb="xs">
|
||||
{t("fileManager.toolChain", "Tools Applied")}
|
||||
</Text>
|
||||
<ToolChain toolChain={currentFile.toolHistory} displayStyle="badges" size="xs" />
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
@@ -142,13 +162,8 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
{currentFile && isSharedWithYou && (
|
||||
<>
|
||||
<Divider />
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
onClick={() => onMakeCopy(currentFile)}
|
||||
fullWidth
|
||||
>
|
||||
{t('fileManager.makeCopy', 'Make a copy')}
|
||||
<Button size="sm" variant="light" onClick={() => onMakeCopy(currentFile)} fullWidth>
|
||||
{t("fileManager.makeCopy", "Make a copy")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -157,39 +172,42 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
<>
|
||||
<Divider />
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.cloudFile', 'Cloud file')}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("fileManager.cloudFile", "Cloud file")}
|
||||
</Text>
|
||||
{uploadEnabled && isOutOfSync ? (
|
||||
<Badge size="xs" variant="light" color="yellow">
|
||||
{t('fileManager.changesNotUploaded', 'Changes not uploaded')}
|
||||
{t("fileManager.changesNotUploaded", "Changes not uploaded")}
|
||||
</Badge>
|
||||
) : uploadEnabled ? (
|
||||
<Badge size="xs" variant="light" color="teal">
|
||||
{t('fileManager.synced', 'Synced')}
|
||||
{t("fileManager.synced", "Synced")}
|
||||
</Badge>
|
||||
) : null}
|
||||
</Group>
|
||||
{lastSyncedLabel && (
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.lastSynced', 'Last synced')}</Text>
|
||||
<Text size="sm" fw={500}>{lastSyncedLabel}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("fileManager.lastSynced", "Last synced")}
|
||||
</Text>
|
||||
<Text size="sm" fw={500}>
|
||||
{lastSyncedLabel}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
{isSharedByYou && sharingEnabled && (
|
||||
<>
|
||||
<Divider />
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.sharing', 'Sharing')}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("fileManager.sharing", "Sharing")}
|
||||
</Text>
|
||||
<Badge size="xs" variant="light" color="blue">
|
||||
{t('fileManager.sharedByYou', 'Shared by you')}
|
||||
{t("fileManager.sharedByYou", "Shared by you")}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
onClick={() => setShowShareManageModal(true)}
|
||||
fullWidth
|
||||
>
|
||||
{t('storageShare.manage', 'Manage sharing')}
|
||||
<Button size="sm" variant="light" onClick={() => setShowShareManageModal(true)} fullWidth>
|
||||
{t("storageShare.manage", "Manage sharing")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -199,9 +217,11 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
||||
<>
|
||||
<Divider />
|
||||
<Group justify="space-between" py="xs">
|
||||
<Text size="sm" c="dimmed">{t('fileManager.storageState', 'Storage')}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("fileManager.storageState", "Storage")}
|
||||
</Text>
|
||||
<Badge size="xs" variant="light" color="gray">
|
||||
{t('fileManager.localOnly', 'Local only')}
|
||||
{t("fileManager.localOnly", "Local only")}
|
||||
</Badge>
|
||||
</Group>
|
||||
</>
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Center, ScrollArea, Text, Stack } from '@mantine/core';
|
||||
import CloudIcon from '@mui/icons-material/Cloud';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import FileListItem from '@app/components/fileManager/FileListItem';
|
||||
import FileHistoryGroup from '@app/components/fileManager/FileHistoryGroup';
|
||||
import EmptyFilesState from '@app/components/fileManager/EmptyFilesState';
|
||||
import { useFileManagerContext } from '@app/contexts/FileManagerContext';
|
||||
import React from "react";
|
||||
import { Center, ScrollArea, Text, Stack } from "@mantine/core";
|
||||
import CloudIcon from "@mui/icons-material/Cloud";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import FileListItem from "@app/components/fileManager/FileListItem";
|
||||
import FileHistoryGroup from "@app/components/fileManager/FileHistoryGroup";
|
||||
import EmptyFilesState from "@app/components/fileManager/EmptyFilesState";
|
||||
import { useFileManagerContext } from "@app/contexts/FileManagerContext";
|
||||
|
||||
interface FileListAreaProps {
|
||||
scrollAreaHeight: string;
|
||||
scrollAreaStyle?: React.CSSProperties;
|
||||
}
|
||||
|
||||
const FileListArea: React.FC<FileListAreaProps> = ({
|
||||
scrollAreaHeight,
|
||||
scrollAreaStyle = {},
|
||||
}) => {
|
||||
const FileListArea: React.FC<FileListAreaProps> = ({ scrollAreaHeight, scrollAreaStyle = {} }) => {
|
||||
const {
|
||||
activeSource,
|
||||
recentFiles,
|
||||
@@ -34,12 +31,12 @@ const FileListArea: React.FC<FileListAreaProps> = ({
|
||||
} = useFileManagerContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (activeSource === 'recent') {
|
||||
if (activeSource === "recent") {
|
||||
return (
|
||||
<ScrollArea
|
||||
h={scrollAreaHeight}
|
||||
style={{
|
||||
...scrollAreaStyle
|
||||
...scrollAreaStyle,
|
||||
}}
|
||||
type="always"
|
||||
scrollbarSize={8}
|
||||
@@ -48,8 +45,10 @@ const FileListArea: React.FC<FileListAreaProps> = ({
|
||||
{recentFiles.length === 0 && !isLoading ? (
|
||||
<EmptyFilesState />
|
||||
) : recentFiles.length === 0 && isLoading ? (
|
||||
<Center style={{ height: '12.5rem' }}>
|
||||
<Text c="dimmed" ta="center">{t('fileManager.loadingFiles', 'Loading files...')}</Text>
|
||||
<Center style={{ height: "12.5rem" }}>
|
||||
<Text c="dimmed" ta="center">
|
||||
{t("fileManager.loadingFiles", "Loading files...")}
|
||||
</Text>
|
||||
</Center>
|
||||
) : (
|
||||
filteredFiles.map((file, index) => {
|
||||
@@ -93,10 +92,12 @@ const FileListArea: React.FC<FileListAreaProps> = ({
|
||||
|
||||
// Google Drive placeholder
|
||||
return (
|
||||
<Center style={{ height: '12.5rem' }}>
|
||||
<Center style={{ height: "12.5rem" }}>
|
||||
<Stack align="center" gap="sm">
|
||||
<CloudIcon style={{ fontSize: '3rem', color: 'var(--mantine-color-gray-5)' }} />
|
||||
<Text c="dimmed" ta="center">{t('fileManager.googleDriveNotAvailable', 'Google Drive integration coming soon')}</Text>
|
||||
<CloudIcon style={{ fontSize: "3rem", color: "var(--mantine-color-gray-5)" }} />
|
||||
<Text c="dimmed" ta="center">
|
||||
{t("fileManager.googleDriveNotAvailable", "Google Drive integration coming soon")}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { Group, Box, Text, ActionIcon, Checkbox, Divider, Menu, Badge } from '@mantine/core';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import RestoreIcon from '@mui/icons-material/Restore';
|
||||
import UnarchiveIcon from '@mui/icons-material/Unarchive';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
||||
import CloudDoneIcon from '@mui/icons-material/CloudDone';
|
||||
import LinkIcon from '@mui/icons-material/Link';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getFileSize, getFileDate } from '@app/utils/fileUtils';
|
||||
import { FileId, StirlingFileStub } from '@app/types/fileContext';
|
||||
import { useFileManagerContext } from '@app/contexts/FileManagerContext';
|
||||
import { zipFileService } from '@app/services/zipFileService';
|
||||
import ToolChain from '@app/components/shared/ToolChain';
|
||||
import { Z_INDEX_OVER_FILE_MANAGER_MODAL } from '@app/styles/zIndex';
|
||||
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||
import { useFileManagement } from '@app/contexts/FileContext';
|
||||
import UploadToServerModal from '@app/components/shared/UploadToServerModal';
|
||||
import ShareFileModal from '@app/components/shared/ShareFileModal';
|
||||
import { useAppConfig } from '@app/contexts/AppConfigContext';
|
||||
import ShareManagementModal from '@app/components/shared/ShareManagementModal';
|
||||
import apiClient from '@app/services/apiClient';
|
||||
import { absoluteWithBasePath } from '@app/constants/app';
|
||||
import { alert } from '@app/components/toast';
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import { Group, Box, Text, ActionIcon, Checkbox, Divider, Menu, Badge } from "@mantine/core";
|
||||
import MoreVertIcon from "@mui/icons-material/MoreVert";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import HistoryIcon from "@mui/icons-material/History";
|
||||
import RestoreIcon from "@mui/icons-material/Restore";
|
||||
import UnarchiveIcon from "@mui/icons-material/Unarchive";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
||||
import CloudDoneIcon from "@mui/icons-material/CloudDone";
|
||||
import LinkIcon from "@mui/icons-material/Link";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getFileSize, getFileDate } from "@app/utils/fileUtils";
|
||||
import { FileId, StirlingFileStub } from "@app/types/fileContext";
|
||||
import { useFileManagerContext } from "@app/contexts/FileManagerContext";
|
||||
import { zipFileService } from "@app/services/zipFileService";
|
||||
import ToolChain from "@app/components/shared/ToolChain";
|
||||
import { Z_INDEX_OVER_FILE_MANAGER_MODAL } from "@app/styles/zIndex";
|
||||
import { PrivateContent } from "@app/components/shared/PrivateContent";
|
||||
import { useFileManagement } from "@app/contexts/FileContext";
|
||||
import UploadToServerModal from "@app/components/shared/UploadToServerModal";
|
||||
import ShareFileModal from "@app/components/shared/ShareFileModal";
|
||||
import { useAppConfig } from "@app/contexts/AppConfigContext";
|
||||
import ShareManagementModal from "@app/components/shared/ShareManagementModal";
|
||||
import apiClient from "@app/services/apiClient";
|
||||
import { absoluteWithBasePath } from "@app/constants/app";
|
||||
import { alert } from "@app/components/toast";
|
||||
|
||||
interface FileListItemProps {
|
||||
file: StirlingFileStub;
|
||||
@@ -51,7 +51,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
onDoubleClick,
|
||||
isHistoryFile = false,
|
||||
isLatestVersion = false,
|
||||
isActive = false
|
||||
isActive = false,
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
@@ -60,22 +60,22 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
const [showShareManageModal, setShowShareManageModal] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const { config } = useAppConfig();
|
||||
const {expandedFileIds, onToggleExpansion, onUnzipFile, refreshRecentFiles } = useFileManagerContext();
|
||||
const { expandedFileIds, onToggleExpansion, onUnzipFile, refreshRecentFiles } = useFileManagerContext();
|
||||
const { removeFiles } = useFileManagement();
|
||||
|
||||
// Check if this is a ZIP file
|
||||
const isZipFile = zipFileService.isZipFileStub(file);
|
||||
|
||||
// Check file extension
|
||||
const extLower = (file.name?.match(/\.([a-z0-9]+)$/i)?.[1] || '').toLowerCase();
|
||||
const isCBZ = extLower === 'cbz';
|
||||
const isCBR = extLower === 'cbr';
|
||||
const extLower = (file.name?.match(/\.([a-z0-9]+)$/i)?.[1] || "").toLowerCase();
|
||||
const isCBZ = extLower === "cbz";
|
||||
const isCBR = extLower === "cbr";
|
||||
|
||||
// Keep item in hovered state if menu is open
|
||||
const shouldShowHovered = isHovered || isMenuOpen;
|
||||
|
||||
// Get version information for this file
|
||||
const leafFileId = (isLatestVersion ? file.id : (file.originalFileId || file.id)) as FileId;
|
||||
const leafFileId = (isLatestVersion ? file.id : file.originalFileId || file.id) as FileId;
|
||||
const hasVersionHistory = (file.versionNumber || 1) > 1; // Show history for any processed file (v2+)
|
||||
const currentVersion = file.versionNumber || 1; // Display original files as v1
|
||||
const isExpanded = expandedFileIds.has(leafFileId);
|
||||
@@ -83,32 +83,28 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
const sharingEnabled = uploadEnabled && config?.storageSharingEnabled === true;
|
||||
const shareLinksEnabled = sharingEnabled && config?.storageShareLinksEnabled === true;
|
||||
const isOwnedOrLocal = file.remoteOwnedByCurrentUser !== false;
|
||||
const isSharedWithYou =
|
||||
sharingEnabled && (file.remoteOwnedByCurrentUser === false || file.remoteSharedViaLink);
|
||||
const isSharedWithYou = sharingEnabled && (file.remoteOwnedByCurrentUser === false || file.remoteSharedViaLink);
|
||||
const localUpdatedAt = file.createdAt ?? file.lastModified ?? 0;
|
||||
const remoteUpdatedAt = file.remoteStorageUpdatedAt ?? 0;
|
||||
const isUploaded = Boolean(file.remoteStorageId);
|
||||
const isUpToDate = isUploaded && remoteUpdatedAt >= localUpdatedAt;
|
||||
const isOutOfSync = isUploaded && !isUpToDate && isOwnedOrLocal;
|
||||
const isLocalOnly = !file.remoteStorageId && !file.remoteSharedViaLink;
|
||||
const accessRole = (isOwnedOrLocal ? 'editor' : (file.remoteAccessRole ?? 'viewer')).toLowerCase();
|
||||
const hasReadAccess = isOwnedOrLocal || accessRole === 'editor' || accessRole === 'commenter' || accessRole === 'viewer';
|
||||
const accessRole = (isOwnedOrLocal ? "editor" : (file.remoteAccessRole ?? "viewer")).toLowerCase();
|
||||
const hasReadAccess = isOwnedOrLocal || accessRole === "editor" || accessRole === "commenter" || accessRole === "viewer";
|
||||
const canUpload = uploadEnabled && isOwnedOrLocal && isLatestVersion && (!isUploaded || !isUpToDate);
|
||||
const canShare = shareLinksEnabled && isOwnedOrLocal && isLatestVersion;
|
||||
const canManageShare = sharingEnabled && isOwnedOrLocal && Boolean(file.remoteStorageId);
|
||||
const canCopyShareLink =
|
||||
shareLinksEnabled && Boolean(file.remoteHasShareLinks) && Boolean(file.remoteStorageId);
|
||||
const canCopyShareLink = shareLinksEnabled && Boolean(file.remoteHasShareLinks) && Boolean(file.remoteStorageId);
|
||||
const canDownloadFile = Boolean(onDownload) && hasReadAccess;
|
||||
|
||||
const shareBaseUrl = useMemo(() => {
|
||||
const frontendUrl = (config?.frontendUrl || '').trim();
|
||||
const frontendUrl = (config?.frontendUrl || "").trim();
|
||||
if (frontendUrl) {
|
||||
const normalized = frontendUrl.endsWith('/')
|
||||
? frontendUrl.slice(0, -1)
|
||||
: frontendUrl;
|
||||
const normalized = frontendUrl.endsWith("/") ? frontendUrl.slice(0, -1) : frontendUrl;
|
||||
return `${normalized}/share/`;
|
||||
}
|
||||
return absoluteWithBasePath('/share/');
|
||||
return absoluteWithBasePath("/share/");
|
||||
}, [config?.frontendUrl]);
|
||||
|
||||
const handleCopyShareLink = useCallback(async () => {
|
||||
@@ -116,14 +112,14 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
try {
|
||||
const response = await apiClient.get<{ shareLinks?: Array<{ token?: string }> }>(
|
||||
`/api/v1/storage/files/${file.remoteStorageId}`,
|
||||
{ suppressErrorToast: true } as any
|
||||
{ suppressErrorToast: true } as any,
|
||||
);
|
||||
const links = response.data?.shareLinks ?? [];
|
||||
const token = links[links.length - 1]?.token;
|
||||
if (!token) {
|
||||
alert({
|
||||
alertType: 'warning',
|
||||
title: t('storageShare.noLinks', 'No active share links yet.'),
|
||||
alertType: "warning",
|
||||
title: t("storageShare.noLinks", "No active share links yet."),
|
||||
expandable: false,
|
||||
durationMs: 2500,
|
||||
});
|
||||
@@ -131,16 +127,16 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
}
|
||||
await navigator.clipboard.writeText(`${shareBaseUrl}${token}`);
|
||||
alert({
|
||||
alertType: 'success',
|
||||
title: t('storageShare.copied', 'Link copied to clipboard'),
|
||||
alertType: "success",
|
||||
title: t("storageShare.copied", "Link copied to clipboard"),
|
||||
expandable: false,
|
||||
durationMs: 2000,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to copy share link:', error);
|
||||
console.error("Failed to copy share link:", error);
|
||||
alert({
|
||||
alertType: 'warning',
|
||||
title: t('storageShare.copyFailed', 'Copy failed'),
|
||||
alertType: "warning",
|
||||
title: t("storageShare.copyFailed", "Copy failed"),
|
||||
expandable: false,
|
||||
durationMs: 2500,
|
||||
});
|
||||
@@ -152,20 +148,22 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
<Box
|
||||
p="sm"
|
||||
style={{
|
||||
cursor: isHistoryFile || isActive ? 'default' : 'pointer',
|
||||
cursor: isHistoryFile || isActive ? "default" : "pointer",
|
||||
backgroundColor: isActive
|
||||
? 'var(--file-active-bg)'
|
||||
: isSelected
|
||||
? 'var(--mantine-color-gray-1)'
|
||||
: (shouldShowHovered ? 'var(--mantine-color-gray-1)' : 'var(--bg-file-list)'),
|
||||
? "var(--file-active-bg)"
|
||||
: isSelected
|
||||
? "var(--mantine-color-gray-1)"
|
||||
: shouldShowHovered
|
||||
? "var(--mantine-color-gray-1)"
|
||||
: "var(--bg-file-list)",
|
||||
opacity: isSupported ? 1 : 0.5,
|
||||
transition: 'background-color 0.15s ease',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
MozUserSelect: 'none',
|
||||
msUserSelect: 'none',
|
||||
paddingLeft: isHistoryFile ? '2rem' : '0.75rem', // Indent history files
|
||||
borderLeft: isHistoryFile ? '3px solid var(--mantine-color-blue-4)' : 'none' // Visual indicator for history
|
||||
transition: "background-color 0.15s ease",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
paddingLeft: isHistoryFile ? "2rem" : "0.75rem", // Indent history files
|
||||
borderLeft: isHistoryFile ? "3px solid var(--mantine-color-blue-4)" : "none", // Visual indicator for history
|
||||
}}
|
||||
onClick={isHistoryFile || isActive ? undefined : (e) => onSelect(e.shiftKey)}
|
||||
onDoubleClick={onDoubleClick}
|
||||
@@ -186,8 +184,8 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
color={isActive ? "green" : undefined}
|
||||
styles={{
|
||||
input: {
|
||||
cursor: isActive ? 'not-allowed' : 'pointer'
|
||||
}
|
||||
cursor: isActive ? "not-allowed" : "pointer",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@@ -203,12 +201,12 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
size="xs"
|
||||
variant="light"
|
||||
style={{
|
||||
backgroundColor: 'var(--file-active-badge-bg)',
|
||||
color: 'var(--file-active-badge-fg)',
|
||||
border: '1px solid var(--file-active-badge-border)'
|
||||
backgroundColor: "var(--file-active-badge-bg)",
|
||||
color: "var(--file-active-badge-fg)",
|
||||
border: "1px solid var(--file-active-badge-border)",
|
||||
}}
|
||||
>
|
||||
{t('fileManager.active', 'Active')}
|
||||
{t("fileManager.active", "Active")}
|
||||
</Badge>
|
||||
)}
|
||||
<Badge size="xs" variant="light" color={"blue"}>
|
||||
@@ -216,44 +214,33 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
</Badge>
|
||||
{sharingEnabled && isSharedWithYou ? (
|
||||
<Badge size="xs" variant="light" color="grape">
|
||||
{t('fileManager.sharedWithYou', 'Shared with you')}
|
||||
{t("fileManager.sharedWithYou", "Shared with you")}
|
||||
</Badge>
|
||||
) : null}
|
||||
{sharingEnabled && isSharedWithYou && accessRole && accessRole !== 'editor' ? (
|
||||
{sharingEnabled && isSharedWithYou && accessRole && accessRole !== "editor" ? (
|
||||
<Badge size="xs" variant="light" color="gray">
|
||||
{accessRole === 'commenter'
|
||||
? t('storageShare.roleCommenter', 'Commenter')
|
||||
: t('storageShare.roleViewer', 'Viewer')}
|
||||
{accessRole === "commenter"
|
||||
? t("storageShare.roleCommenter", "Commenter")
|
||||
: t("storageShare.roleViewer", "Viewer")}
|
||||
</Badge>
|
||||
) : isLocalOnly ? (
|
||||
<Badge size="xs" variant="light" color="gray">
|
||||
{t('fileManager.localOnly', 'Local only')}
|
||||
{t("fileManager.localOnly", "Local only")}
|
||||
</Badge>
|
||||
) : uploadEnabled && isOutOfSync ? (
|
||||
<Badge
|
||||
size="xs"
|
||||
variant="light"
|
||||
color="yellow"
|
||||
leftSection={<CloudUploadIcon style={{ fontSize: 12 }} />}
|
||||
>
|
||||
{t('fileManager.changesNotUploaded', 'Changes not uploaded')}
|
||||
<Badge size="xs" variant="light" color="yellow" leftSection={<CloudUploadIcon style={{ fontSize: 12 }} />}>
|
||||
{t("fileManager.changesNotUploaded", "Changes not uploaded")}
|
||||
</Badge>
|
||||
) : uploadEnabled && isUploaded ? (
|
||||
<Badge
|
||||
size="xs"
|
||||
variant="light"
|
||||
color="teal"
|
||||
leftSection={<CloudDoneIcon style={{ fontSize: 12 }} />}
|
||||
>
|
||||
{t('fileManager.synced', 'Synced')}
|
||||
<Badge size="xs" variant="light" color="teal" leftSection={<CloudDoneIcon style={{ fontSize: 12 }} />}>
|
||||
{t("fileManager.synced", "Synced")}
|
||||
</Badge>
|
||||
) : null}
|
||||
{sharingEnabled && file.remoteOwnedByCurrentUser !== false && file.remoteHasShareLinks && (
|
||||
<Badge size="xs" variant="light" color="blue">
|
||||
{t('fileManager.sharedByYou', 'Shared by you')}
|
||||
{t("fileManager.sharedByYou", "Shared by you")}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
</Group>
|
||||
<Group gap="xs" align="center">
|
||||
<Text size="xs" c="dimmed">
|
||||
@@ -262,12 +249,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
|
||||
{/* Tool chain for processed files */}
|
||||
{file.toolHistory && file.toolHistory.length > 0 && (
|
||||
<ToolChain
|
||||
toolChain={file.toolHistory}
|
||||
maxWidth={'150px'}
|
||||
displayStyle="text"
|
||||
size="xs"
|
||||
/>
|
||||
<ToolChain toolChain={file.toolHistory} maxWidth={"150px"} displayStyle="text" size="xs" />
|
||||
)}
|
||||
</Group>
|
||||
</Box>
|
||||
@@ -288,9 +270,9 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
opacity: shouldShowHovered ? 1 : 0,
|
||||
transform: shouldShowHovered ? 'scale(1)' : 'scale(0.8)',
|
||||
transition: 'opacity 0.3s ease, transform 0.3s ease',
|
||||
pointerEvents: shouldShowHovered ? 'auto' : 'none'
|
||||
transform: shouldShowHovered ? "scale(1)" : "scale(0.8)",
|
||||
transition: "opacity 0.3s ease, transform 0.3s ease",
|
||||
pointerEvents: shouldShowHovered ? "auto" : "none",
|
||||
}}
|
||||
>
|
||||
<MoreVertIcon style={{ fontSize: 20 }} />
|
||||
@@ -308,7 +290,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
removeFiles([file.id]);
|
||||
}}
|
||||
>
|
||||
{t('fileManager.closeFile', 'Close File')}
|
||||
{t("fileManager.closeFile", "Close File")}
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
</>
|
||||
@@ -322,7 +304,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
onDownload?.();
|
||||
}}
|
||||
>
|
||||
{t('fileManager.download', 'Download')}
|
||||
{t("fileManager.download", "Download")}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
@@ -335,8 +317,8 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
}}
|
||||
>
|
||||
{isUploaded
|
||||
? t('fileManager.updateOnServer', 'Update on Server')
|
||||
: t('fileManager.uploadToServer', 'Upload to Server')}
|
||||
? t("fileManager.updateOnServer", "Update on Server")
|
||||
: t("fileManager.uploadToServer", "Upload to Server")}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
@@ -348,7 +330,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
setShowShareModal(true);
|
||||
}}
|
||||
>
|
||||
{t('fileManager.share', 'Share')}
|
||||
{t("fileManager.share", "Share")}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
@@ -360,7 +342,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
void handleCopyShareLink();
|
||||
}}
|
||||
>
|
||||
{t('storageShare.copyLink', 'Copy share link')}
|
||||
{t("storageShare.copyLink", "Copy share link")}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
@@ -372,7 +354,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
setShowShareManageModal(true);
|
||||
}}
|
||||
>
|
||||
{t('storageShare.manage', 'Manage sharing')}
|
||||
{t("storageShare.manage", "Manage sharing")}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
@@ -380,20 +362,13 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
{isLatestVersion && hasVersionHistory && (
|
||||
<>
|
||||
<Menu.Item
|
||||
leftSection={
|
||||
<HistoryIcon style={{ fontSize: 16 }} />
|
||||
}
|
||||
leftSection={<HistoryIcon style={{ fontSize: 16 }} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleExpansion(leafFileId);
|
||||
}}
|
||||
>
|
||||
{
|
||||
(isExpanded ?
|
||||
t('fileManager.hideHistory', 'Hide History') :
|
||||
t('fileManager.showHistory', 'Show History')
|
||||
)
|
||||
}
|
||||
{isExpanded ? t("fileManager.hideHistory", "Hide History") : t("fileManager.showHistory", "Show History")}
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
</>
|
||||
@@ -408,7 +383,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{t('fileManager.restore', 'Restore')}
|
||||
{t("fileManager.restore", "Restore")}
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
</>
|
||||
@@ -424,7 +399,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
onUnzipFile(file);
|
||||
}}
|
||||
>
|
||||
{t('fileManager.unzip', 'Unzip')}
|
||||
{t("fileManager.unzip", "Unzip")}
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
</>
|
||||
@@ -437,14 +412,13 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
onRemove();
|
||||
}}
|
||||
>
|
||||
{t('fileManager.delete', 'Delete')}
|
||||
{t("fileManager.delete", "Delete")}
|
||||
</Menu.Item>
|
||||
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</Group>
|
||||
</Box>
|
||||
{ <Divider color="var(--mantine-color-gray-3)" />}
|
||||
{<Divider color="var(--mantine-color-gray-3)" />}
|
||||
{canUpload && (
|
||||
<UploadToServerModal
|
||||
opened={showUploadModal}
|
||||
@@ -462,11 +436,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
||||
/>
|
||||
)}
|
||||
{canManageShare && (
|
||||
<ShareManagementModal
|
||||
opened={showShareManageModal}
|
||||
onClose={() => setShowShareManageModal(false)}
|
||||
file={file}
|
||||
/>
|
||||
<ShareManagementModal opened={showShareManageModal} onClose={() => setShowShareManageModal(false)} file={file} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Stack, Text, Button, Group } from '@mantine/core';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import PhonelinkIcon from '@mui/icons-material/Phonelink';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFileManagerContext } from '@app/contexts/FileManagerContext';
|
||||
import { useGoogleDrivePicker } from '@app/hooks/useGoogleDrivePicker';
|
||||
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
|
||||
import { useFileActionIcons } from '@app/hooks/useFileActionIcons';
|
||||
import { useAppConfig } from '@app/contexts/AppConfigContext';
|
||||
import { useIsMobile } from '@app/hooks/useIsMobile';
|
||||
import MobileUploadModal from '@app/components/shared/MobileUploadModal';
|
||||
import React, { useState } from "react";
|
||||
import { Stack, Text, Button, Group } from "@mantine/core";
|
||||
import HistoryIcon from "@mui/icons-material/History";
|
||||
import PhonelinkIcon from "@mui/icons-material/Phonelink";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFileManagerContext } from "@app/contexts/FileManagerContext";
|
||||
import { useGoogleDrivePicker } from "@app/hooks/useGoogleDrivePicker";
|
||||
import { useFileActionTerminology } from "@app/hooks/useFileActionTerminology";
|
||||
import { useFileActionIcons } from "@app/hooks/useFileActionIcons";
|
||||
import { useAppConfig } from "@app/contexts/AppConfigContext";
|
||||
import { useIsMobile } from "@app/hooks/useIsMobile";
|
||||
import MobileUploadModal from "@app/components/shared/MobileUploadModal";
|
||||
|
||||
interface FileSourceButtonsProps {
|
||||
horizontal?: boolean;
|
||||
@@ -24,17 +24,15 @@ const GoogleDriveIcon: React.FC<{ disabled?: boolean }> = ({ disabled }) => (
|
||||
src="/images/google-drive.svg"
|
||||
alt="Google Drive"
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
opacity: disabled ? 0.5 : 1,
|
||||
filter: disabled ? 'grayscale(100%)' : 'none',
|
||||
filter: disabled ? "grayscale(100%)" : "none",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
horizontal = false
|
||||
}) => {
|
||||
const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({ horizontal = false }) => {
|
||||
const { activeSource, onSourceChange, onLocalFileClick, onGoogleDriveSelect, onNewFilesSelect } = useFileManagerContext();
|
||||
const { t } = useTranslation();
|
||||
const { isEnabled: isGoogleDriveEnabled, openPicker: openGoogleDrivePicker } = useGoogleDrivePicker();
|
||||
@@ -53,7 +51,7 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
onGoogleDriveSelect(files);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to pick files from Google Drive:', error);
|
||||
console.error("Failed to pick files from Google Drive:", error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,18 +72,18 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
const shouldHideMobileQR = !isMobileUploadEnabled && config?.hideDisabledToolsMobileQRScanner;
|
||||
|
||||
const buttonProps = {
|
||||
variant: (source: string) => activeSource === source ? 'filled' : 'subtle',
|
||||
getColor: (source: string) => activeSource === source ? 'var(--mantine-color-gray-2)' : undefined,
|
||||
variant: (source: string) => (activeSource === source ? "filled" : "subtle"),
|
||||
getColor: (source: string) => (activeSource === source ? "var(--mantine-color-gray-2)" : undefined),
|
||||
getStyles: (source: string) => ({
|
||||
root: {
|
||||
backgroundColor: activeSource === source ? undefined : 'transparent',
|
||||
color: activeSource === source ? 'var(--mantine-color-gray-9)' : 'var(--mantine-color-gray-6)',
|
||||
border: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: activeSource === source ? undefined : 'var(--mantine-color-gray-0)'
|
||||
}
|
||||
}
|
||||
})
|
||||
backgroundColor: activeSource === source ? undefined : "transparent",
|
||||
color: activeSource === source ? "var(--mantine-color-gray-9)" : "var(--mantine-color-gray-6)",
|
||||
border: "none",
|
||||
"&:hover": {
|
||||
backgroundColor: activeSource === source ? undefined : "var(--mantine-color-gray-0)",
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const buttons = (
|
||||
@@ -93,18 +91,18 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
<Button
|
||||
leftSection={<HistoryIcon />}
|
||||
justify={horizontal ? "center" : "flex-start"}
|
||||
onClick={() => onSourceChange('recent')}
|
||||
onClick={() => onSourceChange("recent")}
|
||||
fullWidth={!horizontal}
|
||||
size={horizontal ? "xs" : "sm"}
|
||||
color={buttonProps.getColor('recent')}
|
||||
styles={buttonProps.getStyles('recent')}
|
||||
color={buttonProps.getColor("recent")}
|
||||
styles={buttonProps.getStyles("recent")}
|
||||
>
|
||||
{horizontal ? t('fileManager.recent', 'Recent') : t('fileManager.recent', 'Recent')}
|
||||
{horizontal ? t("fileManager.recent", "Recent") : t("fileManager.recent", "Recent")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="subtle"
|
||||
color='var(--mantine-color-gray-6)'
|
||||
color="var(--mantine-color-gray-6)"
|
||||
leftSection={<UploadIcon />}
|
||||
justify={horizontal ? "center" : "flex-start"}
|
||||
onClick={onLocalFileClick}
|
||||
@@ -112,12 +110,12 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
size={horizontal ? "xs" : "sm"}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--mantine-color-gray-0)'
|
||||
}
|
||||
}
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--mantine-color-gray-0)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{horizontal ? terminology.upload : terminology.uploadFiles}
|
||||
@@ -126,7 +124,7 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
{!shouldHideGoogleDrive && (
|
||||
<Button
|
||||
variant="subtle"
|
||||
color='var(--mantine-color-gray-6)'
|
||||
color="var(--mantine-color-gray-6)"
|
||||
leftSection={<GoogleDriveIcon disabled={!isGoogleDriveEnabled} />}
|
||||
justify={horizontal ? "center" : "flex-start"}
|
||||
onClick={handleGoogleDriveClick}
|
||||
@@ -135,23 +133,27 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
disabled={!isGoogleDriveEnabled}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: isGoogleDriveEnabled ? 'var(--mantine-color-gray-0)' : 'transparent'
|
||||
}
|
||||
}
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
"&:hover": {
|
||||
backgroundColor: isGoogleDriveEnabled ? "var(--mantine-color-gray-0)" : "transparent",
|
||||
},
|
||||
},
|
||||
}}
|
||||
title={!isGoogleDriveEnabled ? t('fileManager.googleDriveNotAvailable', 'Google Drive integration not available') : undefined}
|
||||
title={
|
||||
!isGoogleDriveEnabled
|
||||
? t("fileManager.googleDriveNotAvailable", "Google Drive integration not available")
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{horizontal ? t('fileManager.googleDriveShort', 'Drive') : t('fileManager.googleDrive', 'Google Drive')}
|
||||
{horizontal ? t("fileManager.googleDriveShort", "Drive") : t("fileManager.googleDrive", "Google Drive")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{!shouldHideMobileQR && (
|
||||
<Button
|
||||
variant="subtle"
|
||||
color='var(--mantine-color-gray-6)'
|
||||
color="var(--mantine-color-gray-6)"
|
||||
leftSection={<PhonelinkIcon />}
|
||||
justify={horizontal ? "center" : "flex-start"}
|
||||
onClick={handleMobileUploadClick}
|
||||
@@ -160,16 +162,16 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
disabled={!isMobileUploadEnabled}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: isMobileUploadEnabled ? 'var(--mantine-color-gray-0)' : 'transparent'
|
||||
}
|
||||
}
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
"&:hover": {
|
||||
backgroundColor: isMobileUploadEnabled ? "var(--mantine-color-gray-0)" : "transparent",
|
||||
},
|
||||
},
|
||||
}}
|
||||
title={!isMobileUploadEnabled ? t('fileManager.mobileUploadNotAvailable', 'Mobile upload not available') : undefined}
|
||||
title={!isMobileUploadEnabled ? t("fileManager.mobileUploadNotAvailable", "Mobile upload not available") : undefined}
|
||||
>
|
||||
{horizontal ? t('fileManager.mobileShort', 'Mobile') : t('fileManager.mobileUpload', 'Mobile Upload')}
|
||||
{horizontal ? t("fileManager.mobileShort", "Mobile") : t("fileManager.mobileUpload", "Mobile Upload")}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
@@ -178,7 +180,7 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
if (horizontal) {
|
||||
return (
|
||||
<>
|
||||
<Group gap="xs" justify="center" style={{ width: '100%' }}>
|
||||
<Group gap="xs" justify="center" style={{ width: "100%" }}>
|
||||
{buttons}
|
||||
</Group>
|
||||
<MobileUploadModal
|
||||
@@ -192,9 +194,9 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack gap="xs" style={{ height: '100%' }}>
|
||||
<Text size="sm" pt="sm" fw={500} c="dimmed" mb="xs" style={{ paddingLeft: '1rem' }}>
|
||||
{t('fileManager.myFiles', 'My Files')}
|
||||
<Stack gap="xs" style={{ height: "100%" }}>
|
||||
<Text size="sm" pt="sm" fw={500} c="dimmed" mb="xs" style={{ paddingLeft: "1rem" }}>
|
||||
{t("fileManager.myFiles", "My Files")}
|
||||
</Text>
|
||||
{buttons}
|
||||
</Stack>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useFileManagerContext } from '@app/contexts/FileManagerContext';
|
||||
import React from "react";
|
||||
import { useFileManagerContext } from "@app/contexts/FileManagerContext";
|
||||
|
||||
const HiddenFileInput: React.FC = () => {
|
||||
const { fileInputRef, onFileInputChange } = useFileManagerContext();
|
||||
@@ -10,7 +10,7 @@ const HiddenFileInput: React.FC = () => {
|
||||
type="file"
|
||||
multiple={true}
|
||||
onChange={onFileInputChange}
|
||||
style={{ display: 'none' }}
|
||||
style={{ display: "none" }}
|
||||
data-testid="file-input"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mantine/core';
|
||||
import FileSourceButtons from '@app/components/fileManager/FileSourceButtons';
|
||||
import FileDetails from '@app/components/fileManager/FileDetails';
|
||||
import SearchInput from '@app/components/fileManager/SearchInput';
|
||||
import FileListArea from '@app/components/fileManager/FileListArea';
|
||||
import FileActions from '@app/components/fileManager/FileActions';
|
||||
import HiddenFileInput from '@app/components/fileManager/HiddenFileInput';
|
||||
import { useFileManagerContext } from '@app/contexts/FileManagerContext';
|
||||
import React from "react";
|
||||
import { Box } from "@mantine/core";
|
||||
import FileSourceButtons from "@app/components/fileManager/FileSourceButtons";
|
||||
import FileDetails from "@app/components/fileManager/FileDetails";
|
||||
import SearchInput from "@app/components/fileManager/SearchInput";
|
||||
import FileListArea from "@app/components/fileManager/FileListArea";
|
||||
import FileActions from "@app/components/fileManager/FileActions";
|
||||
import HiddenFileInput from "@app/components/fileManager/HiddenFileInput";
|
||||
import { useFileManagerContext } from "@app/contexts/FileManagerContext";
|
||||
|
||||
const MobileLayout: React.FC = () => {
|
||||
const {
|
||||
activeSource,
|
||||
selectedFiles,
|
||||
modalHeight,
|
||||
} = useFileManagerContext();
|
||||
const { activeSource, selectedFiles, modalHeight } = useFileManagerContext();
|
||||
|
||||
// Calculate the height more accurately based on actual content
|
||||
const calculateFileListHeight = () => {
|
||||
@@ -21,17 +17,17 @@ const MobileLayout: React.FC = () => {
|
||||
const baseHeight = `calc(${modalHeight} - 2rem)`; // Account for Stack padding
|
||||
|
||||
// Estimate heights of fixed components
|
||||
const fileSourceHeight = '3rem'; // FileSourceButtons height
|
||||
const fileDetailsHeight = selectedFiles.length > 0 ? '10rem' : '8rem'; // FileDetails compact height
|
||||
const fileActionsHeight = activeSource === 'recent' ? '3rem' : '0rem'; // FileActions height (now at bottom)
|
||||
const searchHeight = activeSource === 'recent' ? '3rem' : '0rem'; // SearchInput height
|
||||
const gapHeight = activeSource === 'recent' ? '3.75rem' : '2rem'; // Stack gaps
|
||||
const fileSourceHeight = "3rem"; // FileSourceButtons height
|
||||
const fileDetailsHeight = selectedFiles.length > 0 ? "10rem" : "8rem"; // FileDetails compact height
|
||||
const fileActionsHeight = activeSource === "recent" ? "3rem" : "0rem"; // FileActions height (now at bottom)
|
||||
const searchHeight = activeSource === "recent" ? "3rem" : "0rem"; // SearchInput height
|
||||
const gapHeight = activeSource === "recent" ? "3.75rem" : "2rem"; // Stack gaps
|
||||
|
||||
return `calc(${baseHeight} - ${fileSourceHeight} - ${fileDetailsHeight} - ${fileActionsHeight} - ${searchHeight} - ${gapHeight})`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box h="100%" p="sm" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
||||
<Box h="100%" p="sm" style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
|
||||
{/* Section 1: File Sources - Fixed at top */}
|
||||
<Box style={{ flexShrink: 0 }}>
|
||||
<FileSourceButtons horizontal={true} />
|
||||
@@ -42,28 +38,34 @@ const MobileLayout: React.FC = () => {
|
||||
</Box>
|
||||
|
||||
{/* Section 3 & 4: Search Bar + File List - Unified background extending to modal edge */}
|
||||
<Box style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: 'var(--bg-file-list)',
|
||||
borderRadius: '0.5rem',
|
||||
border: '1px solid var(--mantine-color-gray-2)',
|
||||
overflow: 'hidden',
|
||||
minHeight: 0
|
||||
}}>
|
||||
{activeSource === 'recent' && (
|
||||
<Box
|
||||
style={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
backgroundColor: "var(--bg-file-list)",
|
||||
borderRadius: "0.5rem",
|
||||
border: "1px solid var(--mantine-color-gray-2)",
|
||||
overflow: "hidden",
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
{activeSource === "recent" && (
|
||||
<>
|
||||
<Box style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: '1px solid var(--mantine-color-gray-2)'
|
||||
}}>
|
||||
<Box
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: "1px solid var(--mantine-color-gray-2)",
|
||||
}}
|
||||
>
|
||||
<SearchInput />
|
||||
</Box>
|
||||
<Box style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: '1px solid var(--mantine-color-gray-2)'
|
||||
}}>
|
||||
<Box
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
borderBottom: "1px solid var(--mantine-color-gray-2)",
|
||||
}}
|
||||
>
|
||||
<FileActions />
|
||||
</Box>
|
||||
</>
|
||||
@@ -74,11 +76,11 @@ const MobileLayout: React.FC = () => {
|
||||
scrollAreaHeight={calculateFileListHeight()}
|
||||
scrollAreaStyle={{
|
||||
height: calculateFileListHeight(),
|
||||
maxHeight: '60vh',
|
||||
minHeight: '9.375rem',
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
borderRadius: 0
|
||||
maxHeight: "60vh",
|
||||
minHeight: "9.375rem",
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
borderRadius: 0,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { TextInput } from '@mantine/core';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFileManagerContext } from '@app/contexts/FileManagerContext';
|
||||
import React from "react";
|
||||
import { TextInput } from "@mantine/core";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFileManagerContext } from "@app/contexts/FileManagerContext";
|
||||
|
||||
interface SearchInputProps {
|
||||
style?: React.CSSProperties;
|
||||
@@ -14,20 +14,19 @@ const SearchInput: React.FC<SearchInputProps> = ({ style }) => {
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
placeholder={t('fileManager.searchFiles', 'Search files...')}
|
||||
placeholder={t("fileManager.searchFiles", "Search files...")}
|
||||
leftSection={<SearchIcon />}
|
||||
value={searchTerm}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
|
||||
style={{ padding: '0.5rem', ...style }}
|
||||
style={{ padding: "0.5rem", ...style }}
|
||||
styles={{
|
||||
input: {
|
||||
border: 'none',
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
border: "none",
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchInput;
|
||||
export default SearchInput;
|
||||
|
||||
Reference in New Issue
Block a user