diff --git a/app/src/components/FileBrowser.js b/app/src/components/FileBrowser.js
index d69f2150..d43bc1c1 100644
--- a/app/src/components/FileBrowser.js
+++ b/app/src/components/FileBrowser.js
@@ -1,17 +1,20 @@
-import {Button, Header, Icon, Modal, Placeholder, Progress, Segment, Table} from "./Theme";
import {
+ ActionInput,
+ Button,
Checkbox,
- Form,
- Grid,
- IconGroup,
- Input,
- PlaceholderLine,
- TableBody,
- TableCell,
- TableHeader,
- TableHeaderCell,
- TableRow
-} from "semantic-ui-react";
+ Header,
+ Icon,
+ IconButton,
+ IconStack,
+ Modal,
+ Panel,
+ Placeholder,
+ Progress,
+ Skeleton,
+ Stack,
+ Table,
+ TextInput,
+} from "./ui";
import {
APIButton,
DirectorySearch,
@@ -23,6 +26,7 @@ import {
useIsIgnoredDirectory
} from "./Common";
import React, {useContext, useEffect, useState} from "react";
+import {IconArrowsMove, IconChevronsUp, IconCursorText, IconTags} from "@tabler/icons-react";
import {useDropzone} from "react-dropzone";
import {useHotkeys} from "react-hotkeys-hook";
import {deleteFile, ignoreDirectory, makeDirectory, movePaths, renamePath, unignoreDirectory} from "../api";
@@ -111,23 +115,28 @@ function Folder({folder, onFolderClick, sortData, onFileClick, disabled, loading
const isLoading = loadingFolders && loadingFolders.has(path);
const depth = (path.match(/\//g) || []).length;
- const f = handleDragStart(path, e)}
onMouseEnter={() => handleDragMove(path)}
>
-
+
onSelect(folder['path'])}/>
-
- shouldAllowClick() && onFolderClick(path)} className='file-path' colSpan={2} disabled={is_empty}>
+
+ shouldAllowClick() && onFolderClick(path)}
+ className='file-path'
+ colSpan={2}
+ style={is_empty ? {opacity: 0.5} : undefined}
+ >
{depthIndentation(pathWithNoTrailingSlash)}
{is_empty ? : }
{ignored && }
{name}
-
- ;
+
+ ;
if (children) {
// Folder has children, recursively display them.
children = sortData(children);
@@ -164,27 +173,27 @@ function File({file, onFileClick, disabled}) {
const showAsSelected = (isInSelectedPaths && !isDragDeselecting) || isDragSelecting;
// Visual highlight class
const dragClass = isDragSelecting ? 'drag-selecting' : (isDragDeselecting ? 'drag-deselecting' : '');
- return handleDragStart(path, e)}
onMouseEnter={() => handleDragMove(path)}
>
-
+
onSelect(path)}/>
-
- shouldAllowClick() && onFileClick(file)} className='file-path'>
+
+ shouldAllowClick() && onFileClick(file)} className='file-path'>
{depthIndentation(path)}
{/*null size to make icon the correct size*/}
{pathName(path)}
-
-
+
+
{humanFileSize(size)}
-
-
+
+
}
function Path({path, onFolderClick, onFileClick, sortData, disabled, loadingFolders}) {
@@ -209,17 +218,15 @@ function Path({path, onFolderClick, onFileClick, sortData, disabled, loadingFold
function SkeletonFileRow({depth}) {
const indentation = '\xa0\xa0\xa0\xa0'.repeat(depth);
return (
-
-
+
+
-
-
+
+
{indentation}
-
-
-
-
-
+
+
+
);
}
@@ -387,28 +394,26 @@ export function FileBrowser() {
const [footerRef, footerWidth] = useElementWidth();
// Expand icon-only buttons to include labels once the footer is wide enough to fit them.
const showLabels = footerWidth >= FOOTER_LABELS_MIN_WIDTH;
- // Semantic UI renders a labeled icon button when `content` and `labelPosition` are set, else icon-only.
- const labelProps = (content) => showLabels ? {content, labelPosition: 'left'} : {};
+ // Show a label alongside the icon once there's room; otherwise the icon carries the meaning alone.
+ const label = (text) => showLabels ? text : null;
const footerClassName = `sticky-footer ${inverted}`;
const footer =
- {label('Delete')}
+ setRenameOpen(true)}
disabled={wrolModeEnabled || selectedPathsCount !== 1}
- {...labelProps('Rename')}
- />
+ >{label('Rename')}
{selectedPathsCount === 1 ?
: null}
- setMoveOpen(true)}
- {...labelProps('Move')}
- />
+ >{label('Move')}
{selectedPathsCount > 0 ?
setMoveOpen(false)}
@@ -435,10 +439,9 @@ export function FileBrowser() {
disabled={wrolModeEnabled}
style={{paddingLeft: '1em', paddingRight: showLabels ? '1em' : '0.8em'}}
>
-
+ } label='New folder'>
-
-
+
{showLabels ? <> New Folder> : null}
0 && !singleDirectorySelected)}
onClick={() => setUploadOpen(true)}
- {...labelProps('Upload')}
- />
+ >{label('Upload')}
setUploadOpen(false)}
@@ -465,8 +467,7 @@ export function FileBrowser() {
icon={isDirectoryIgnored ? 'eye' : 'eye slash'}
disabled={!singleDirectorySelected || wrolModeEnabled}
onClick={() => setIgnoreDirectoryOpen(true)}
- {...labelProps(isDirectoryIgnored ? 'Unignore' : 'Ignore')}
- />
+ >{label(isDirectoryIgnored ? 'Unignore' : 'Ignore')}
setBulkTagOpen(true)}
- {...labelProps('Tag')}
- />
+ >{label('Tag')}
setBulkTagOpen(false)}
@@ -536,10 +536,7 @@ export function FileBrowser() {
);
if (browseFiles === null) {
- return
-
-
- ;
+ return ;
} else if (browseFiles === undefined) {
return Could not fetch files
}
@@ -551,31 +548,29 @@ export function FileBrowser() {
openFolders={openFolders}
>
- }
placeholder='Filter files...'
value={filterStr}
- onChange={(e, {value}) => setFilterStr(value)}
+ onChange={(e) => setFilterStr(e.target.value)}
aria-label='Filter files'
className='file-browser-filter'
ref={filterInputRef}
- action={ setFilterStr('')}
disabled={!filterStr}
className='search-clear'
- aria-label='Clear filter'
+ label='Clear filter'
/>}
/>
-
@@ -598,7 +593,6 @@ function FileBrowserContent({browseFiles, headers, onFolderClick, setPreviewFile
return (
{
- if (e) {
- e.preventDefault();
- }
- setValue(props.value);
+ const handleInputChange = (e) => {
+ setValue(e.target.value);
}
const handleSubmit = async (e) => {
@@ -652,25 +643,31 @@ export function RenameModal({open, onClose, path, onSubmit, onPending}) {
setValue(pathName(path));
}, [path]);
- return
Rename: {name}
-
+
+ setValue(name)}
+ style={{marginRight: 'auto'}}
+ >
+ Reset
+
Rename
- setValue(name)}
- floated='left'
- >
- Reset
-
}
@@ -710,47 +701,47 @@ export function MoveModal({open, paths, onClose, onSubmit}) {
}
}
- const fileCell = i =>
+ const fileCell = i =>
{i}
- ;
+ ;
- const destinationRows = paths.map(i =>
+ const destinationRows = paths.map(i =>
{fileCell(i)}
{destination ? fileCell(destination + '/' + pathName(i)) : fileCell(pathName(i))}
- );
+ );
- return
Move Files/Directories
Search for a directory to move your files into:
-
+
-
-
- Source
- Destination
-
-
-
+
+
+ Source
+ Destination
+
+
+
{destinationRows}
-
+
+ setDestination(null)}
+ style={{marginRight: 'auto'}}
+ disabled={pending}
+ >Reset
Move
- setDestination(null)}
- floated='left'
- disabled={pending}
- >Reset
}
@@ -779,20 +770,20 @@ export function IgnoreDirectoryModal({open, onClose, onSubmit, directory, ignore
const content = ignored ?
'Remove ignore of the following directory? You must refresh your files after this.'
: 'Ignore the following directory? The files within will not be indexed and will not show in search results.';
- let submitButton = {ignored ? 'Un-ignore' : 'Ignore'} ;
- return
+ return
{header}
{content}
{mediaDirectory}/{directory}
+ Close
{submitButton}
- Close
}
@@ -801,11 +792,8 @@ export function MakeDirectoryModal({open, onClose, parent, onSubmit}) {
const [value, setValue] = useState('');
const mediaDirectory = useMediaDirectory();
- const handleInputChange = (e, props) => {
- if (e) {
- e.preventDefault();
- }
- setValue(props.value);
+ const handleInputChange = (e) => {
+ setValue(e.target.value);
}
const handleSubmit = async (e) => {
@@ -824,18 +812,18 @@ export function MakeDirectoryModal({open, onClose, parent, onSubmit}) {
}
}
- return
+ return
Make Directory
-
+
Create
@@ -845,7 +833,6 @@ export function MakeDirectoryModal({open, onClose, parent, onSubmit}) {
export function FileBrowserUploadModal({open, onClose, destination, onComplete}) {
const mediaDirectory = useMediaDirectory();
- const {t} = React.useContext(ThemeContext);
const {
setFiles,
@@ -909,27 +896,22 @@ export function FileBrowserUploadModal({open, onClose, destination, onComplete})
color = 'orange';
statusString = 'Already Exists:';
}
- let indicating = status === 'pending';
- return
-
-
-
-
-
- {statusString} {name}
-
-
-
+ return
})
}
const displayPath = destination ? `${mediaDirectory}/${destination}` : mediaDirectory;
- return
+ return
Upload to: {displayPath}
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+ Click here, or drop files here to upload
+
+
-
-
-
- {progresses && Object.keys(progresses).length > 1 && }
-
-
-
-
+ {progresses && Object.keys(progresses).length > 1 &&
+ }
+
+
{progressBars}
-
+
diff --git a/app/src/components/FileBrowser.test.js b/app/src/components/FileBrowser.test.js
index 9ba2b6fa..0a937b91 100644
--- a/app/src/components/FileBrowser.test.js
+++ b/app/src/components/FileBrowser.test.js
@@ -155,7 +155,9 @@ describe('FileBrowser', () => {
// Find the upload button
const buttons = screen.getAllByRole('button');
- const uploadButton = buttons.find(btn => btn.classList.contains('green'));
+ const uploadButton = buttons.find(btn =>
+ btn.classList.contains('green') || btn.querySelector('[class*="upload"]')
+ );
expect(uploadButton).not.toBeDisabled();
});
@@ -173,7 +175,9 @@ describe('FileBrowser', () => {
// Find the upload button
const buttons = screen.getAllByRole('button');
- const uploadButton = buttons.find(btn => btn.classList.contains('green'));
+ const uploadButton = buttons.find(btn =>
+ btn.classList.contains('green') || btn.querySelector('[class*="upload"]')
+ );
expect(uploadButton).toBeDisabled();
});
diff --git a/app/src/components/FilePreview.js b/app/src/components/FilePreview.js
index 163714df..4e28ba2d 100644
--- a/app/src/components/FilePreview.js
+++ b/app/src/components/FilePreview.js
@@ -1,14 +1,12 @@
import React from "react";
import _ from "lodash";
-import {Button as SButton, Dropdown, Header as SHeader, Icon, Image,} from "semantic-ui-react";
import {TagsSelector} from "../Tags";
import {encodeMediaPath, isSupportedArchive, useLocalStorage} from "./Common";
import {getFile, tagFileGroup, untagFileGroup} from "../api";
import {ArchivePreviewContent} from "./ArchivePreview";
import {CbzViewer} from "./CbzViewer";
import {StlViewer} from "react-stl-viewer";
-import {Button, Modal} from "./Theme";
-import {toast} from "./ui";
+import {Button, ButtonGroup, Header, Icon, IconButton, Menu, Modal, toast} from "./ui";
import {useOneQuery} from "../hooks/customHooks";
import {ShareButton} from "./Share";
import {AddToPlaylistButton} from "./AddToPlaylist";
@@ -63,25 +61,31 @@ function OpenInSlicerButton({previewFile}) {
// Browsers cannot report which slicers are installed, so remember the last one used.
const [slicerKey, setSlicerKey] = useLocalStorage('preferredSlicer', SLICERS[0].key);
const slicer = SLICERS.find(s => s.key === slicerKey) || SLICERS[0];
- return
-
- Open in {slicer.text}
+ return
+
+ Open in {slicer.text}
- >}>
-
+
+
+
+
+
{SLICERS.map(s =>
- {
- // Navigate programmatically so keyboard activation (which may not
- // follow the href) still launches the slicer.
- if (e) e.preventDefault();
- setSlicerKey(s.key);
- window.location.assign(getSlicerURL(previewFile, s.scheme));
- }}/>)}
-
-
-
+ {
+ // Navigate programmatically so keyboard activation (which may not
+ // follow the href) still launches the slicer.
+ if (e) e.preventDefault();
+ setSlicerKey(s.key);
+ window.location.assign(getSlicerURL(previewFile, s.scheme));
+ }}
+ >
+ Open in {s.text}
+ )}
+
+
+
}
function getEpubViewerURL(previewFile) {
@@ -115,7 +119,7 @@ function getImagePreviewModal(previewFile) {
@@ -131,7 +135,7 @@ function getVideoPreviewModal(previewFile) {
{name}
- {path['path']}
+
Unknown File
Cannot display preview, no such file exists: {previewQuery}
- Close
+ Close
@@ -500,21 +503,21 @@ export function FilePreviewProvider({children}) {
function setModalContent(content, url, downloadURL, path, taggable = true, extraButtons = null) {
// All action buttons share `size='small'` and `icon` for consistent height across viewports.
- // Close is handled by the Modal's `closeIcon` (X in the top-right corner) — no dedicated button.
+ // Close is handled by the Modal's close button (X in the top-right corner) — no dedicated button.
const openButton = url
- ?
- Open
-
+ ? Open
: null;
const tagsDisplay = taggable ?
: null;
const downloadButton = downloadURL
- ?
+ ?
: null;
const directoryURL = path ? `/files?folders=${encodeURIComponent(pathDirectory(path))}` : null;
- const directoryButton = path ? : null;
+ const directoryButton = path
+ ?
+ : null;
const pathContent = path ?
{path}
@@ -526,8 +529,7 @@ export function FilePreviewProvider({children}) {
// Single flex toolbar — same button order at every viewport size.
// `flex-wrap: wrap` lets the tags selector drop to a second line on narrow screens
// while the button groups stay together and in order: [file actions] [tags] [open/close].
- setPreviewModal( handleClose(e)}
>