Prettier 2: Electric Boogaloo (#6113)

# Description of Changes
When I added Prettier formatting in #6052, my aim was to use just the
default settings in Prettier. Turns out, Prettier looks _really hard_
for any config files if it's not explicitly given one, which means that
if a developer has some sort of Prettier config file lying around on
their system, Prettier might find it and use it. Also, Prettier changes
its defaults based on stuff in `.editorconfig` without any good way of
disabling that behaviour explicitly in its config file.

To solve both of these issues, I've introduced a `.prettierrc` file
which sets Prettier's defaults explicitly, and then reformatted all our
code _again_ in Prettier's actual default settings. This should achieve
the aim of #6052 and remove the possibility for it breaking on different
dev computers.
This commit is contained in:
James Brunton
2026-04-17 09:50:16 +00:00
committed by GitHub
parent de8c483054
commit 8ab060a4be
1207 changed files with 52646 additions and 15352 deletions
@@ -16,7 +16,11 @@ interface AddFileCardProps {
multiple?: boolean;
}
const AddFileCard = ({ onFileSelect, accept, multiple = true }: AddFileCardProps) => {
const AddFileCard = ({
onFileSelect,
accept,
multiple = true,
}: AddFileCardProps) => {
const { t } = useTranslation();
const fileInputRef = useRef<HTMLInputElement>(null);
const { openFilesModal } = useFilesModalContext();
@@ -84,7 +88,9 @@ const AddFileCard = ({ onFileSelect, accept, multiple = true }: AddFileCardProps
<div className={styles.logoMark}>
<AddIcon sx={{ color: "inherit", fontSize: "1.5rem" }} />
</div>
<div className={styles.headerIndex}>{t("fileEditor.addFiles", "Add Files")}</div>
<div className={styles.headerIndex}>
{t("fileEditor.addFiles", "Add Files")}
</div>
<div className={styles.kebab} />
</div>
@@ -131,8 +137,15 @@ const AddFileCard = ({ onFileSelect, accept, multiple = true }: AddFileCardProps
onClick={handleOpenFilesModal}
onMouseEnter={() => setIsUploadHover(false)}
>
<LocalIcon icon="add" width="1.5rem" height="1.5rem" className="text-[var(--accent-interactive)]" />
{!isUploadHover && <span>{t("landing.addFiles", "Add Files")}</span>}
<LocalIcon
icon="add"
width="1.5rem"
height="1.5rem"
className="text-[var(--accent-interactive)]"
/>
{!isUploadHover && (
<span>{t("landing.addFiles", "Add Files")}</span>
)}
</Button>
<Button
aria-label="Upload"
@@ -160,14 +173,22 @@ const AddFileCard = ({ onFileSelect, accept, multiple = true }: AddFileCardProps
height="1.25rem"
style={{ color: "var(--accent-interactive)" }}
/>
{isUploadHover && <span style={{ marginLeft: ".5rem" }}>{terminology.uploadFromComputer}</span>}
{isUploadHover && (
<span style={{ marginLeft: ".5rem" }}>
{terminology.uploadFromComputer}
</span>
)}
</Button>
</div>
{/* Instruction Text */}
<span
className="text-[var(--accent-interactive)]"
style={{ fontSize: ".8rem", textAlign: "center", marginTop: "0.5rem" }}
style={{
fontSize: ".8rem",
textAlign: "center",
marginTop: "0.5rem",
}}
>
{terminology.dropFilesHere}
</span>
@@ -1,7 +1,13 @@
import { useState, useCallback, useRef, useMemo, useEffect } from "react";
import { Text, Center, Box, LoadingOverlay, Stack } from "@mantine/core";
import { Dropzone } from "@mantine/dropzone";
import { useFileSelection, useFileState, useFileManagement, useFileActions, useFileContext } from "@app/contexts/FileContext";
import {
useFileSelection,
useFileState,
useFileManagement,
useFileActions,
useFileContext,
} from "@app/contexts/FileContext";
import { useNavigationActions } from "@app/contexts/NavigationContext";
import { useViewer } from "@app/contexts/ViewerContext";
import { zipFileService } from "@app/services/zipFileService";
@@ -22,7 +28,10 @@ interface FileEditorProps {
supportedExtensions?: string[];
}
const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEditorProps) => {
const FileEditor = ({
toolMode = false,
supportedExtensions = ["pdf"],
}: FileEditorProps) => {
// Utility function to check if a file extension is supported
const isFileSupported = useCallback(
(fileName: string): boolean => {
@@ -40,7 +49,10 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
const { clearAllFileErrors } = fileContextActions;
// Extract needed values from state (memoized to prevent infinite loops)
const activeStirlingFileStubs = useMemo(() => selectors.getStirlingFileStubs(), [state.files.byId, state.files.ids]);
const activeStirlingFileStubs = useMemo(
() => selectors.getStirlingFileStubs(),
[state.files.byId, state.files.ids],
);
const selectedFileIds = state.ui.selectedFileIds;
const totalItems = state.files.ids.length;
const selectedCount = selectedFileIds.length;
@@ -58,11 +70,27 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
const [_error, _setError] = useState<string | null>(null);
// Toast helpers
const showStatus = useCallback((message: string, type: "neutral" | "success" | "warning" | "error" = "neutral") => {
alert({ alertType: type, title: message, expandable: false, durationMs: 4000 });
}, []);
const showStatus = useCallback(
(
message: string,
type: "neutral" | "success" | "warning" | "error" = "neutral",
) => {
alert({
alertType: type,
title: message,
expandable: false,
durationMs: 4000,
});
},
[],
);
const showError = useCallback((message: string) => {
alert({ alertType: "error", title: "Error", body: message, expandable: true });
alert({
alertType: "error",
title: "Error",
body: message,
expandable: true,
});
}, []);
const [selectionMode, setSelectionMode] = useState(toolMode);
@@ -83,7 +111,9 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
}, [toolMode]);
const [showFilePickerModal, setShowFilePickerModal] = useState(false);
// Get selected file IDs from context (defensive programming)
const contextSelectedIds = Array.isArray(selectedFileIds) ? selectedFileIds : [];
const contextSelectedIds = Array.isArray(selectedFileIds)
? selectedFileIds
: [];
// Create refs for frequently changing values to stabilize callbacks
const contextSelectedIdsRef = useRef<FileId[]>([]);
@@ -95,7 +125,9 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
const handleSelectAllFiles = useCallback(() => {
// Respect maxAllowed: if limited, select the last N files
const allIds = state.files.ids;
const idsToSelect = Number.isFinite(maxAllowed) ? allIds.slice(-maxAllowed) : allIds;
const idsToSelect = Number.isFinite(maxAllowed)
? allIds.slice(-maxAllowed)
: allIds;
setSelectedFiles(idsToSelect);
try {
clearAllFileErrors();
@@ -147,7 +179,9 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
await addFiles(uploadedFiles, { selectFiles: true });
// After auto-selection, enforce maxAllowed if needed
if (Number.isFinite(maxAllowed)) {
const nowSelectedIds = selectors.getSelectedStirlingFileStubs().map((r) => r.id);
const nowSelectedIds = selectors
.getSelectedStirlingFileStubs()
.map((r) => r.id);
if (nowSelectedIds.length > maxAllowed) {
setSelectedFiles(nowSelectedIds.slice(-maxAllowed));
}
@@ -155,7 +189,8 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
showStatus(`Added ${uploadedFiles.length} file(s)`, "success");
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Failed to process files";
const errorMessage =
err instanceof Error ? err.message : "Failed to process files";
showError(errorMessage);
console.error("File processing error:", err);
}
@@ -182,14 +217,18 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
// Add file to selection
// Determine max files allowed from the active tool (negative or undefined means unlimited)
const rawMax = selectedTool?.maxFiles;
const maxAllowed = !toolMode || rawMax == null || rawMax < 0 ? Infinity : rawMax;
const maxAllowed =
!toolMode || rawMax == null || rawMax < 0 ? Infinity : rawMax;
if (maxAllowed === 1) {
// Only one file allowed -> replace selection with the new file
newSelection = [contextFileId];
} else {
// If at capacity, drop the oldest selected and append the new one
if (Number.isFinite(maxAllowed) && currentSelectedIds.length >= maxAllowed) {
if (
Number.isFinite(maxAllowed) &&
currentSelectedIds.length >= maxAllowed
) {
newSelection = [...currentSelectedIds.slice(1), contextFileId];
} else {
newSelection = [...currentSelectedIds, contextFileId];
@@ -200,7 +239,13 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
// Update context (this automatically updates tool selection since they use the same action)
setSelectedFiles(newSelection);
},
[setSelectedFiles, toolMode, _setStatus, activeStirlingFileStubs, selectedTool?.maxFiles],
[
setSelectedFiles,
toolMode,
_setStatus,
activeStirlingFileStubs,
selectedTool?.maxFiles,
],
);
// Enforce maxAllowed when tool changes or when an external action sets too many selected files
@@ -226,13 +271,17 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
// Handle multi-file selection reordering
const filesToMove =
selectedFileIds.length > 1 ? selectedFileIds.filter((id) => currentIds.includes(id)) : [sourceFileId];
selectedFileIds.length > 1
? selectedFileIds.filter((id) => currentIds.includes(id))
: [sourceFileId];
// Create new order
const newOrder = [...currentIds];
// Remove files to move from their current positions (in reverse order to maintain indices)
const sourceIndices = filesToMove.map((id) => newOrder.findIndex((nId) => nId === id)).sort((a, b) => b - a); // Sort descending
const sourceIndices = filesToMove
.map((id) => newOrder.findIndex((nId) => nId === id))
.sort((a, b) => b - a); // Sort descending
sourceIndices.forEach((index) => {
newOrder.splice(index, 1);
@@ -278,11 +327,19 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
removeFiles([contextFileId], false);
// Remove from context selections
const currentSelected = selectedFileIds.filter((id) => id !== contextFileId);
const currentSelected = selectedFileIds.filter(
(id) => id !== contextFileId,
);
setSelectedFiles(currentSelected);
}
},
[activeStirlingFileStubs, selectors, removeFiles, setSelectedFiles, selectedFileIds],
[
activeStirlingFileStubs,
selectors,
removeFiles,
setSelectedFiles,
selectedFileIds,
],
);
const handleDownloadFile = useCallback(
@@ -315,7 +372,10 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
isDirty: false,
});
} else {
console.log("[FileEditor] Skipping clean mark:", { savedPath: result.savedPath, isDirty: record.isDirty });
console.log("[FileEditor] Skipping clean mark:", {
savedPath: result.savedPath,
isDirty: record.isDirty,
});
}
}
},
@@ -329,7 +389,10 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
if (record && file) {
try {
// Extract and store files using shared service method
const result = await zipFileService.extractAndStoreFilesWithHistory(file, record);
const result = await zipFileService.extractAndStoreFilesWithHistory(
file,
record,
);
if (result.success && result.extractedStubs.length > 0) {
// Add extracted file stubs to FileContext
@@ -376,7 +439,12 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
navActions.setWorkbench("viewer");
}
},
[activeStirlingFileStubs, setActiveFileId, setActiveFileIndex, navActions.setWorkbench],
[
activeStirlingFileStubs,
setActiveFileId,
setActiveFileIndex,
navActions.setWorkbench,
],
);
const handleLoadFromStorage = useCallback(async (selectedFiles: File[]) => {
@@ -417,7 +485,8 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
</Text>
<Text c="dimmed">No files loaded</Text>
<Text size="sm" c="dimmed">
Upload PDF files, ZIP archives, or load from storage to get started
Upload PDF files, ZIP archives, or load from storage to get
started
</Text>
</Stack>
</Center>
@@ -432,7 +501,12 @@ const FileEditor = ({ toolMode = false, supportedExtensions = ["pdf"] }: FileEdi
}}
>
{/* Add File Card - only show when files exist */}
{activeStirlingFileStubs.length > 0 && <AddFileCard key="add-file-card" onFileSelect={handleFileUpload} />}
{activeStirlingFileStubs.length > 0 && (
<AddFileCard
key="add-file-card"
onFileSelect={handleFileUpload}
/>
)}
{activeStirlingFileStubs.map((record, index) => {
return (
@@ -6,6 +6,8 @@ interface FileEditorFileNameProps {
file: StirlingFileStub;
}
const FileEditorFileName = ({ file }: FileEditorFileNameProps) => <PrivateContent>{file.name}</PrivateContent>;
const FileEditorFileName = ({ file }: FileEditorFileNameProps) => (
<PrivateContent>{file.name}</PrivateContent>
);
export default FileEditorFileName;
@@ -1,5 +1,15 @@
import React, { useState, useCallback, useRef, useMemo } from "react";
import { Text, ActionIcon, CheckboxIndicator, Tooltip, Modal, Button, Group, Stack, Loader } from "@mantine/core";
import {
Text,
ActionIcon,
CheckboxIndicator,
Tooltip,
Modal,
Button,
Group,
Stack,
Loader,
} from "@mantine/core";
import { useIsMobile } from "@app/hooks/useIsMobile";
import { alert } from "@app/components/toast";
import { useTranslation } from "react-i18next";
@@ -14,7 +24,10 @@ import PushPinIcon from "@mui/icons-material/PushPin";
import PushPinOutlinedIcon from "@mui/icons-material/PushPinOutlined";
import LockOpenIcon from "@mui/icons-material/LockOpen";
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
import {
draggable,
dropTargetForElements,
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
import { StirlingFileStub } from "@app/types/fileContext";
import { zipFileService } from "@app/services/zipFileService";
@@ -24,7 +37,9 @@ import { useFileState } from "@app/contexts/file/fileHooks";
import { FileId } from "@app/types/file";
import { formatFileSize } from "@app/utils/fileUtils";
import ToolChain from "@app/components/shared/ToolChain";
import HoverActionMenu, { HoverAction } from "@app/components/shared/HoverActionMenu";
import HoverActionMenu, {
HoverAction,
} from "@app/components/shared/HoverActionMenu";
import { downloadFile } from "@app/services/downloadService";
import { PrivateContent } from "@app/components/shared/PrivateContent";
import UploadToServerModal from "@app/components/shared/UploadToServerModal";
@@ -42,7 +57,11 @@ interface FileEditorThumbnailProps {
onCloseFile: (fileId: FileId) => void;
onViewFile: (fileId: FileId) => void;
_onSetStatus: (status: string) => void;
onReorderFiles?: (sourceFileId: FileId, targetFileId: FileId, selectedFileIds: FileId[]) => void;
onReorderFiles?: (
sourceFileId: FileId,
targetFileId: FileId,
selectedFileIds: FileId[],
) => void;
onDownloadFile: (fileId: FileId) => void;
onUnzipFile?: (fileId: FileId) => void;
toolMode?: boolean;
@@ -67,7 +86,14 @@ const FileEditorThumbnail = ({
const terminology = useFileActionTerminology();
const icons = useFileActionIcons();
const DownloadOutlinedIcon = icons.download;
const { pinFile, unpinFile, isFilePinned, activeFiles, actions: fileActions, openEncryptedUnlockPrompt } = useFileContext();
const {
pinFile,
unpinFile,
isFilePinned,
activeFiles,
actions: fileActions,
openEncryptedUnlockPrompt,
} = useFileContext();
const { state, selectors } = useFileState();
const hasError = state.ui.errorFileIds.includes(file.id);
@@ -117,18 +143,29 @@ const FileEditorThumbnail = ({
const isCBZ = extLower === "cbz";
const isCBR = extLower === "cbr";
const uploadEnabled = config?.storageEnabled === true;
const sharingEnabled = uploadEnabled && config?.storageSharingEnabled === true;
const shareLinksEnabled = sharingEnabled && config?.storageShareLinksEnabled === true;
const sharingEnabled =
uploadEnabled && config?.storageSharingEnabled === true;
const shareLinksEnabled =
sharingEnabled && config?.storageShareLinksEnabled === true;
const isOwnedOrLocal = file.remoteOwnedByCurrentUser !== false;
const isSharedFile = file.remoteOwnedByCurrentUser === false || file.remoteSharedViaLink;
const isSharedFile =
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 canUpload = uploadEnabled && isOwnedOrLocal && file.isLeaf && (!isUploaded || !isUpToDate);
const canUpload =
uploadEnabled &&
isOwnedOrLocal &&
file.isLeaf &&
(!isUploaded || !isUpToDate);
const canShare = shareLinksEnabled && isOwnedOrLocal && file.isLeaf;
const pageLabel = useMemo(() => (pageCount > 0 ? `${pageCount} ${pageCount === 1 ? "Page" : "Pages"}` : ""), [pageCount]);
const pageLabel = useMemo(
() =>
pageCount > 0 ? `${pageCount} ${pageCount === 1 ? "Page" : "Pages"}` : "",
[pageCount],
);
const dateLabel = useMemo(() => {
const d = new Date(file.lastModified);
@@ -198,7 +235,12 @@ const FileEditorThumbnail = ({
const handleConfirmClose = useCallback(() => {
onCloseFile(file.id);
alert({ alertType: "neutral", title: `Closed ${file.name}`, expandable: false, durationMs: 3500 });
alert({
alertType: "neutral",
title: `Closed ${file.name}`,
expandable: false,
durationMs: 3500,
});
setShowCloseModal(false);
}, [file.id, file.name, onCloseFile]);
@@ -222,16 +264,33 @@ const FileEditorThumbnail = ({
}
} catch (error) {
console.error(`Failed to save ${file.name}:`, error);
alert({ alertType: "error", title: "Save failed", body: `Could not save ${file.name}`, expandable: true });
alert({
alertType: "error",
title: "Save failed",
body: `Could not save ${file.name}`,
expandable: true,
});
setShowCloseModal(false);
return;
}
}
// Then close
onCloseFile(file.id);
alert({ alertType: "success", title: `Saved and closed ${file.name}`, expandable: false, durationMs: 3500 });
alert({
alertType: "success",
title: `Saved and closed ${file.name}`,
expandable: false,
durationMs: 3500,
});
setShowCloseModal(false);
}, [file.id, file.name, file.localFilePath, onCloseFile, selectors, fileActions]);
}, [
file.id,
file.name,
file.localFilePath,
onCloseFile,
selectors,
fileActions,
]);
const handleCancelClose = useCallback(() => {
setShowCloseModal(false);
@@ -298,7 +357,12 @@ const FileEditorThumbnail = ({
e.stopPropagation();
if (onUnzipFile) {
onUnzipFile(file.id);
alert({ alertType: "success", title: `Unzipping ${file.name}`, expandable: false, durationMs: 2500 });
alert({
alertType: "success",
title: `Unzipping ${file.name}`,
expandable: false,
durationMs: 2500,
});
}
},
hidden: !isZipFile || !onUnzipFile || isCBZ || isCBR,
@@ -381,7 +445,10 @@ const FileEditorThumbnail = ({
onDoubleClick={handleCardDoubleClick}
>
{/* Header bar */}
<div className={`${styles.header} ${getHeaderClassName()}`} data-has-error={hasError}>
<div
className={`${styles.header} ${getHeaderClassName()}`}
data-has-error={hasError}
>
{/* Logo/checkbox area */}
<div className={styles.logoMark}>
{hasError ? (
@@ -402,16 +469,27 @@ const FileEditorThumbnail = ({
</div>
{/* Centered index */}
<div className={styles.headerIndex} aria-label={`Position ${index + 1}`}>
<div
className={styles.headerIndex}
aria-label={`Position ${index + 1}`}
>
{index + 1}
</div>
{/* Action buttons group */}
<div className={styles.headerActions}>
{isEncrypted && (
<Tooltip label={t("encryptedPdfUnlock.unlockPrompt", "Unlock PDF to continue")}>
<Tooltip
label={t(
"encryptedPdfUnlock.unlockPrompt",
"Unlock PDF to continue",
)}
>
<ActionIcon
aria-label={t("encryptedPdfUnlock.unlockPrompt", "Unlock PDF to continue")}
aria-label={t(
"encryptedPdfUnlock.unlockPrompt",
"Unlock PDF to continue",
)}
variant="subtle"
className={styles.headerIconButton}
onClick={(e) => {
@@ -426,7 +504,9 @@ const FileEditorThumbnail = ({
{/* Pin/Unpin icon */}
<Tooltip
label={
isPinned ? t("unpin", "Unpin File (replace after tool run)") : t("pin", "Pin File (keep active after tool run)")
isPinned
? t("unpin", "Unpin File (replace after tool run)")
: t("pin", "Pin File (keep active after tool run)")
}
>
<ActionIcon
@@ -443,15 +523,29 @@ const FileEditorThumbnail = ({
if (actualFile) {
if (isPinned) {
unpinFile(actualFile);
alert({ alertType: "neutral", title: `Unpinned ${file.name}`, expandable: false, durationMs: 3000 });
alert({
alertType: "neutral",
title: `Unpinned ${file.name}`,
expandable: false,
durationMs: 3000,
});
} else {
pinFile(actualFile);
alert({ alertType: "success", title: `Pinned ${file.name}`, expandable: false, durationMs: 3000 });
alert({
alertType: "success",
title: `Pinned ${file.name}`,
expandable: false,
durationMs: 3000,
});
}
}
}}
>
{isPinned ? <PushPinIcon fontSize="small" /> : <PushPinOutlinedIcon fontSize="small" />}
{isPinned ? (
<PushPinIcon fontSize="small" />
) : (
<PushPinOutlinedIcon fontSize="small" />
)}
</ActionIcon>
</Tooltip>
</div>
@@ -470,7 +564,13 @@ const FileEditorThumbnail = ({
<Text size="lg" fw={700} className={styles.title} title={file.name}>
<PrivateContent>{truncateCenter(file.name, 40)}</PrivateContent>
</Text>
<Text size="sm" c="dimmed" className={styles.meta} lineClamp={3} title={`${extUpper || "FILE"}${prettySize}`}>
<Text
size="sm"
c="dimmed"
className={styles.meta}
lineClamp={3}
title={`${extUpper || "FILE"}${prettySize}`}
>
{/* e.g., v2 - Jan 29, 2025 - PDF file - 3 Pages */}
{`v${file.versionNumber} - `}
{dateLabel}
@@ -482,7 +582,11 @@ const FileEditorThumbnail = ({
{/* Preview area */}
<div
className={`${styles.previewBox} mx-6 mb-4 relative flex-1`}
style={isSupported || hasError ? undefined : { filter: "grayscale(80%)", opacity: 0.6 }}
style={
isSupported || hasError
? undefined
: { filter: "grayscale(80%)", opacity: 0.6 }
}
>
<div className={styles.previewPaper}>
{file.thumbnailUrl ? (
@@ -513,7 +617,12 @@ const FileEditorThumbnail = ({
/>
</PrivateContent>
) : file.type?.startsWith("application/pdf") ? (
<Stack align="center" justify="center" gap="xs" style={{ height: "100%" }}>
<Stack
align="center"
justify="center"
gap="xs"
style={{ height: "100%" }}
>
<Loader size="sm" />
<Text size="xs" c="dimmed">
Loading thumbnail...
@@ -554,7 +663,11 @@ const FileEditorThumbnail = ({
</div>
{/* Hover Menu */}
<HoverActionMenu show={showHoverMenu || isMobile} actions={hoverActions} position="outside" />
<HoverActionMenu
show={showHoverMenu || isMobile}
actions={hoverActions}
position="outside"
/>
{/* Close Confirmation Modal */}
<Modal
@@ -567,7 +680,9 @@ const FileEditorThumbnail = ({
<Stack gap="md">
{file.isDirty && file.localFilePath ? (
<>
<Text size="md">{t("confirmCloseUnsaved", "This file has unsaved changes.")}</Text>
<Text size="md">
{t("confirmCloseUnsaved", "This file has unsaved changes.")}
</Text>
<Text size="sm" c="dimmed" fw={500}>
{file.name}
</Text>
@@ -575,7 +690,11 @@ const FileEditorThumbnail = ({
<Button variant="light" onClick={handleCancelClose}>
{t("confirmCloseCancel", "Cancel")}
</Button>
<Button variant="filled" color="red" onClick={handleConfirmClose}>
<Button
variant="filled"
color="red"
onClick={handleConfirmClose}
>
{t("confirmCloseDiscard", "Discard changes and close")}
</Button>
<Button variant="filled" onClick={handleSaveAndClose}>
@@ -585,7 +704,12 @@ const FileEditorThumbnail = ({
</>
) : (
<>
<Text size="md">{t("confirmCloseMessage", "Are you sure you want to close this file?")}</Text>
<Text size="md">
{t(
"confirmCloseMessage",
"Are you sure you want to close this file?",
)}
</Text>
<Text size="sm" c="dimmed" fw={500}>
{file.name}
</Text>
@@ -593,7 +717,11 @@ const FileEditorThumbnail = ({
<Button variant="light" onClick={handleCancelClose}>
{t("confirmCloseCancel", "Cancel")}
</Button>
<Button variant="filled" color="red" onClick={handleConfirmClose}>
<Button
variant="filled"
color="red"
onClick={handleConfirmClose}
>
{t("confirmCloseConfirm", "Close File")}
</Button>
</Group>
@@ -623,8 +751,20 @@ const FileEditorThumbnail = ({
</Stack>
</Modal>
{canUpload && <UploadToServerModal opened={showUploadModal} onClose={() => setShowUploadModal(false)} file={file} />}
{canShare && <ShareFileModal opened={showShareModal} onClose={() => setShowShareModal(false)} file={file} />}
{canUpload && (
<UploadToServerModal
opened={showUploadModal}
onClose={() => setShowUploadModal(false)}
file={file}
/>
)}
{canShare && (
<ShareFileModal
opened={showShareModal}
onClose={() => setShowShareModal(false)}
file={file}
/>
)}
</div>
);
};
@@ -1,6 +1,9 @@
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { useRightRailButtons, RightRailButtonWithAction } from "@app/hooks/useRightRailButtons";
import {
useRightRailButtons,
RightRailButtonWithAction,
} from "@app/hooks/useRightRailButtons";
import LocalIcon from "@app/components/shared/LocalIcon";
interface FileEditorRightRailButtonsParams {
@@ -26,7 +29,10 @@ export function useFileEditorRightRailButtons({
id: "file-select-all",
icon: <LocalIcon icon="select-all" width="1.5rem" height="1.5rem" />,
tooltip: t("rightRail.selectAll", "Select All"),
ariaLabel: typeof t === "function" ? t("rightRail.selectAll", "Select All") : "Select All",
ariaLabel:
typeof t === "function"
? t("rightRail.selectAll", "Select All")
: "Select All",
section: "top" as const,
order: 10,
disabled: totalItems === 0 || selectedCount === totalItems,
@@ -35,9 +41,18 @@ export function useFileEditorRightRailButtons({
},
{
id: "file-deselect-all",
icon: <LocalIcon icon="crop-square-outline" width="1.5rem" height="1.5rem" />,
icon: (
<LocalIcon
icon="crop-square-outline"
width="1.5rem"
height="1.5rem"
/>
),
tooltip: t("rightRail.deselectAll", "Deselect All"),
ariaLabel: typeof t === "function" ? t("rightRail.deselectAll", "Deselect All") : "Deselect All",
ariaLabel:
typeof t === "function"
? t("rightRail.deselectAll", "Deselect All")
: "Deselect All",
section: "top" as const,
order: 20,
disabled: selectedCount === 0,
@@ -48,7 +63,10 @@ export function useFileEditorRightRailButtons({
id: "file-close-selected",
icon: <LocalIcon icon="close-rounded" width="1.5rem" height="1.5rem" />,
tooltip: t("rightRail.closeSelected", "Close Selected Files"),
ariaLabel: typeof t === "function" ? t("rightRail.closeSelected", "Close Selected Files") : "Close Selected Files",
ariaLabel:
typeof t === "function"
? t("rightRail.closeSelected", "Close Selected Files")
: "Close Selected Files",
section: "top" as const,
order: 30,
disabled: selectedCount === 0,
@@ -56,7 +74,15 @@ export function useFileEditorRightRailButtons({
onClick: onCloseSelected,
},
],
[t, i18n.language, totalItems, selectedCount, onSelectAll, onDeselectAll, onCloseSelected],
[
t,
i18n.language,
totalItems,
selectedCount,
onSelectAll,
onDeselectAll,
onCloseSelected,
],
);
useRightRailButtons(buttons);