diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs index c0f9fd678..0b92de05b 100644 --- a/frontend/eslint.config.mjs +++ b/frontend/eslint.config.mjs @@ -1,9 +1,18 @@ // @ts-check import eslint from '@eslint/js'; +import globals from "globals"; import { defineConfig } from 'eslint/config'; import tseslint from 'typescript-eslint'; +const srcGlobs = [ + 'src/**/*.{js,mjs,jsx,ts,tsx}', +]; +const nodeGlobs = [ + 'scripts/**/*.{js,ts,mjs}', + '*.config.{js,ts,mjs}', +]; + export default defineConfig( eslint.configs.recommended, tseslint.configs.recommended, @@ -15,7 +24,6 @@ export default defineConfig( }, { rules: { - "no-undef": "off", // Temporarily disabled until codebase conformant "@typescript-eslint/no-empty-object-type": [ "error", { @@ -38,5 +46,23 @@ export default defineConfig( }, ], }, - } + }, + // Config for browser scripts + { + files: srcGlobs, + languageOptions: { + globals: { + ...globals.browser, + } + } + }, + // Config for node scripts + { + files: nodeGlobs, + languageOptions: { + globals: { + ...globals.node, + } + } + }, ); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 316e1af8a..f73143e83 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -41,6 +41,7 @@ "@tanstack/react-virtual": "^3.13.12", "autoprefixer": "^10.4.21", "axios": "^1.12.2", + "globals": "^16.4.0", "i18next": "^25.5.2", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", @@ -1760,6 +1761,19 @@ "mlly": "^1.7.4" } }, + "node_modules/@iconify/utils/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -6415,10 +6429,9 @@ } }, "node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true, + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", "license": "MIT", "engines": { "node": ">=18" diff --git a/frontend/package.json b/frontend/package.json index 0ec14e357..319653af1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -37,6 +37,7 @@ "@tanstack/react-virtual": "^3.13.12", "autoprefixer": "^10.4.21", "axios": "^1.12.2", + "globals": "^16.4.0", "i18next": "^25.5.2", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 61043172c..03c2eefac 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -83,6 +83,8 @@ "save": "Save", "saveToBrowser": "Save to Browser", "download": "Download", + "pin": "Pin", + "unpin": "Unpin", "undoOperationTooltip": "Click to undo the last operation and restore the original files", "undo": "Undo", "moreOptions": "More Options", diff --git a/frontend/src/components/fileEditor/FileEditor.module.css b/frontend/src/components/fileEditor/FileEditor.module.css index 173738c29..17184bbf4 100644 --- a/frontend/src/components/fileEditor/FileEditor.module.css +++ b/frontend/src/components/fileEditor/FileEditor.module.css @@ -34,9 +34,9 @@ .header { height: 2.25rem; border-radius: 0.0625rem 0.0625rem 0 0; - display: grid; - grid-template-columns: 44px 1fr 44px; + display: flex; align-items: center; + justify-content: space-between; padding: 0 6px; user-select: none; background: var(--bg-toolbar); @@ -86,14 +86,23 @@ } .headerIndex { + position: absolute; + left: 50%; + transform: translateX(-50%); text-align: center; font-weight: 500; font-size: 18px; letter-spacing: 0.04em; } -.kebab { - justify-self: end; +.headerActions { + display: flex; + align-items: center; + gap: 8px; + margin-left: auto; +} + +.headerIconButton { color: #FFFFFF !important; } @@ -216,6 +225,11 @@ color: rgba(0, 0, 0, 0.35); } +.pinned { + color: #FFC107 !important; +} + + /* Unsupported file indicator */ .unsupportedPill { margin-left: 1.75rem; @@ -384,4 +398,4 @@ .addFileSubtext { font-size: 0.875rem; opacity: 0.8; -} \ No newline at end of file +} diff --git a/frontend/src/components/fileEditor/FileEditor.tsx b/frontend/src/components/fileEditor/FileEditor.tsx index 626eaab4f..90ee6eebe 100644 --- a/frontend/src/components/fileEditor/FileEditor.tsx +++ b/frontend/src/components/fileEditor/FileEditor.tsx @@ -288,7 +288,7 @@ const FileEditor = ({ // File operations using context - const handleDeleteFile = useCallback((fileId: FileId) => { + const handleCloseFile = useCallback((fileId: FileId) => { const record = activeStirlingFileStubs.find(r => r.id === fileId); const file = record ? selectors.getFile(record.id) : null; if (record && file) { @@ -467,7 +467,7 @@ const FileEditor = ({ selectedFiles={localSelectedIds} selectionMode={selectionMode} onToggleFile={toggleFile} - onDeleteFile={handleDeleteFile} + onCloseFile={handleCloseFile} onViewFile={handleViewFile} _onSetStatus={showStatus} onReorderFiles={handleReorderFiles} diff --git a/frontend/src/components/fileEditor/FileEditorThumbnail.tsx b/frontend/src/components/fileEditor/FileEditorThumbnail.tsx index f09bfeeb1..8159e30a9 100644 --- a/frontend/src/components/fileEditor/FileEditorThumbnail.tsx +++ b/frontend/src/components/fileEditor/FileEditorThumbnail.tsx @@ -1,10 +1,10 @@ import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react'; -import { Text, ActionIcon, CheckboxIndicator } from '@mantine/core'; +import { Text, ActionIcon, CheckboxIndicator, Tooltip } from '@mantine/core'; import { alert } from '../toast'; import { useTranslation } from 'react-i18next'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined'; -import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import CloseIcon from '@mui/icons-material/Close'; import UnarchiveIcon from '@mui/icons-material/Unarchive'; import PushPinIcon from '@mui/icons-material/PushPin'; import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined'; @@ -29,7 +29,7 @@ interface FileEditorThumbnailProps { selectedFiles: FileId[]; selectionMode: boolean; onToggleFile: (fileId: FileId) => void; - onDeleteFile: (fileId: FileId) => void; + onCloseFile: (fileId: FileId) => void; onViewFile: (fileId: FileId) => void; _onSetStatus: (status: string) => void; onReorderFiles?: (sourceFileId: FileId, targetFileId: FileId, selectedFileIds: FileId[]) => void; @@ -44,7 +44,7 @@ const FileEditorThumbnail = ({ index, selectedFiles, onToggleFile, - onDeleteFile, + onCloseFile, _onSetStatus, onReorderFiles, onDownloadFile, @@ -258,18 +258,60 @@ const FileEditorThumbnail = ({ {index + 1} - {/* Kebab menu */} - { - e.stopPropagation(); - setShowActions((v) => !v); - }} - > - - + {/* Action buttons group */} +
+ {/* Pin/Unpin icon */} + + { + e.stopPropagation(); + if (actualFile) { + if (isPinned) { + unpinFile(actualFile); + 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 }); + } + } + }} + > + {isPinned ? : } + + + + {/* Download icon */} + + { + e.stopPropagation(); + onDownloadFile(file.id); + alert({ alertType: 'success', title: `Downloading ${file.name}`, expandable: false, durationMs: 2500 }); + }} + > + + + + + {/* Kebab menu */} + { + e.stopPropagation(); + setShowActions((v) => !v); + }} + > + + +
{/* Actions overlay */} @@ -294,7 +336,7 @@ const FileEditorThumbnail = ({ setShowActions(false); }} > - {isPinned ? : } + {isPinned ? : } {isPinned ? t('unpin', 'Unpin') : t('pin', 'Pin')} @@ -321,13 +363,13 @@ const FileEditorThumbnail = ({ )} @@ -394,13 +436,6 @@ const FileEditorThumbnail = ({ )} - {/* Pin indicator (bottom-left) */} - {isPinned && ( - - - - )} - {/* Drag handle (span wrapper so we can attach a ref reliably) */}