import {Button, Header, Icon, Modal, Placeholder, Progress, Segment, Table} from "./Theme";
import {
Checkbox,
Form,
Grid,
IconGroup,
Input,
PlaceholderLine,
TableBody,
TableCell,
TableHeader,
TableHeaderCell,
TableRow
} from "semantic-ui-react";
import {
APIButton,
DirectorySearch,
ErrorMessage,
FileIcon,
humanFileSize,
mimetypeIconName,
Toggle,
useIsIgnoredDirectory
} from "./Common";
import React, {useEffect, useState} from "react";
import {useDropzone} from "react-dropzone";
import {deleteFile, ignoreDirectory, makeDirectory, movePaths, renamePath, unignoreDirectory} from "../api";
import _ from 'lodash';
import {SortableTable} from "./SortableTable";
import {useBrowseFiles, useMediaDirectory, useUploadFile, useWROLMode} from "../hooks/customHooks";
import {FileRowTagIcon, FilesRefreshButton} from "./Files";
import {FilePreviewContext} from "./FilePreview";
import {SettingsContext, ThemeContext} from "../contexts/contexts";
import {BulkTagModal} from "./BulkTagModal";
import {InlineErrorBoundary} from "./ErrorBoundary";
function depthIndentation(path) {
// Repeated spaces for every folder a path is in.
const depth = (path.match(/\//g) || []).length;
return '\xa0\xa0\xa0\xa0'.repeat(depth);
}
export function pathDirectory(path) {
// Return the full path of the path's directory.
// pathDirectory('/some/directory/file.txt') -> '/some/directory'
return path.substring(0, path.lastIndexOf('/'));
}
export function pathName(path) {
// Return the file's name.
// pathName('/some/directory/file.txt') -> 'file.txt'
// pathName('/some/directory') -> 'directory'
if (path.endsWith('/')) {
// Path is a directory, return its name.
path = path.substring(0, path.length - 1);
}
return path.substring(path.lastIndexOf('/') + 1);
}
export function splitPathParentAndName(path) {
if (path.endsWith('/')) {
// Path is a directory, return its name.
path = path.substring(0, path.length - 1);
}
const slashIndex = path.lastIndexOf('/');
return [path.substring(0, slashIndex), path.substring(slashIndex + 1, path.length)];
}
function Folder({folder, onFolderClick, sortData, selectedPaths, onFileClick, onSelect, disabled}) {
// Creates a single table row for a folder, or a row for itself and indented rows for its children.
let {path, children, is_empty} = folder;
const ignored = useIsIgnoredDirectory(path);
const pathWithNoTrailingSlash = path.substring(0, path.length - 1);
const name = path.substring(pathWithNoTrailingSlash.lastIndexOf('/') + 1);
const f = = 0} onChange={() => onSelect(folder['path'])}/>
onFolderClick(path)} className='file-path' colSpan={2} disabled={is_empty}>
{depthIndentation(pathWithNoTrailingSlash)}
{is_empty ? : }
{ignored && }
{name}
;
if (children) {
// Folder has children, recursively display them.
children = sortData(children);
let childPaths = [];
_.forEach(children, (p, k) => {
childPaths = [...childPaths,
];
});
return
{f}
{childPaths}
} else {
// Folder has no children.
return f;
}
}
function File({file, onFileClick, selectedPaths, onSelect, disabled}) {
const {path, size} = file;
return = 0} onChange={() => onSelect(path)}/>
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, selectedPaths, onSelect, disabled}) {
if (path['path'].endsWith('/')) {
return
} else {
return
}
}
export function FileBrowser() {
const [selectedPaths, setSelectedPaths] = React.useState([]);
const [renameOpen, setRenameOpen] = React.useState(false);
const [moveOpen, setMoveOpen] = React.useState(false);
const [makeDirectoryOpen, setMakeDirectoryOpen] = React.useState(false);
const [ignoreDirectoryOpen, setIgnoreDirectoryOpen] = React.useState(false);
const [bulkTagOpen, setBulkTagOpen] = React.useState(false);
const [uploadOpen, setUploadOpen] = React.useState(false);
const [pending, setPending] = React.useState(false);
// Only true if only one path is selected, and it is a directory.
const [singleDirectorySelected, setSingleDirectorySelected] = React.useState(false);
// Only true if single directory is selected, and it is in settings['ignored_directories']
const [isDirectoryIgnored, setIsDirectoryIgnored] = React.useState(false);
const {settings, fetchSettings} = React.useContext(SettingsContext);
const {inverted} = React.useContext(ThemeContext);
const selectedPathsCount = selectedPaths ? selectedPaths.length : 0;
const {browseFiles, openFolders, setOpenFolders, fetchFiles} = useBrowseFiles();
const wrolModeEnabled = useWROLMode();
const headers = [
{key: 'select', text: ''},
{key: 'path', text: 'Path', sortBy: i => i['path'].toLowerCase()},
{key: 'size', text: 'Size', sortBy: 'size'},
];
const {setPreviewFile, setCallbacks} = React.useContext(FilePreviewContext);
useEffect(() => {
// Fetch files after tagging.
setCallbacks([() => fetchFiles(openFolders)]);
return () => setCallbacks(null);
}, []);
useEffect(() => {
if (selectedPaths.length === 1 && selectedPaths[0].endsWith('/')) {
setSingleDirectorySelected(true);
if (!_.isEmpty(settings)) {
// Used to toggle between ignore/un-ignore (eye/eye lash) icon.
const directory = selectedPaths[0].slice(0, -1);
setIsDirectoryIgnored(settings['ignored_directories'].indexOf(directory) >= 0);
}
} else {
setSingleDirectorySelected(false);
}
}, [selectedPaths, settings]);
const onIgnore = async () => {
setIgnoreDirectoryOpen(false);
await fetchSettings();
}
const onSelect = (path) => {
let newSelectedPaths;
// Select or deselect if the paths was previously selected.
if (selectedPathsCount && selectedPaths.indexOf(path) >= 0) {
newSelectedPaths = selectedPaths.filter(i => i !== path);
} else {
newSelectedPaths = [...selectedPaths, path];
}
setSelectedPaths(newSelectedPaths);
console.debug('selectedPaths', newSelectedPaths);
}
const onDelete = async () => {
await deleteFile(selectedPaths);
await reset();
};
const handleMakeDirectory = async () => {
setMakeDirectoryOpen(false);
await reset();
}
const reset = async () => {
setSelectedPaths([]);
setPending(true);
try {
await fetchFiles();
} finally {
setPending(false);
}
}
const footerClassName = `sticky-footer ${inverted}`;
const footer =
;
const onFolderClick = async (folder) => {
let newFolders;
if (openFolders.indexOf(folder) >= 0) {
// Remove the folder that was clicked on, as well as its sub-folders.
newFolders = openFolders.filter(i => !i.startsWith(folder));
} else {
// Add the new folder to the opened folders.
newFolders = [...openFolders, folder];
}
console.debug(`newFolders=${newFolders}`);
setOpenFolders(newFolders);
}
if (browseFiles === null) {
return ;
} else if (browseFiles === undefined) {
return Could not fetch files
}
return <>
Move setDestination(null)}
floated='left'
disabled={pending}
>Reset
}
export function IgnoreDirectoryModal({open, onClose, onSubmit, directory, ignored}) {
const mediaDirectory = useMediaDirectory();
const [pending, setPending] = React.useState(false);
const handleIgnore = async () => {
try {
setPending(true);
if (ignored) {
await unignoreDirectory(`${mediaDirectory}/${directory}`);
} else {
await ignoreDirectory(`${mediaDirectory}/${directory}`);
}
if (onSubmit) {
onSubmit();
}
} finally {
setPending(false);
}
}
const header = ignored ? 'Remove Ignore Directory' : 'Ignore Directory';
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 {header}
{content}
{mediaDirectory}/{directory}
{submitButton}
Close
}
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 handleSubmit = async (e) => {
if (e) {
e.preventDefault();
}
try {
const path = parent ? `${parent}/${value}` : value;
await makeDirectory(path);
setValue('');
if (onSubmit) {
await onSubmit();
}
} catch (e) {
console.error(e);
}
}
return Make DirectoryCreate
}
export function FileBrowserUploadModal({open, onClose, destination, onComplete}) {
const mediaDirectory = useMediaDirectory();
const {t} = React.useContext(ThemeContext);
const {
setFiles,
progresses,
setDestination,
doClear,
tagsSelector,
overwrite,
setOverwrite,
overallProgress,
inProgress,
} = useUploadFile();
const onDrop = React.useCallback(async (acceptedFiles) => {
if (acceptedFiles && acceptedFiles.length > 0) {
setFiles(acceptedFiles);
}
}, [setFiles]);
const {getRootProps, getInputProps} = useDropzone({onDrop, disabled: inProgress});
// Set destination when modal opens or destination changes
useEffect(() => {
if (open) {
setDestination(destination);
}
}, [open, destination, setDestination]);
// Clear upload state when destination is cleared
useEffect(() => {
if (!destination && open) {
doClear();
}
}, [destination, open, doClear]);
const handleClose = () => {
if (!inProgress) {
doClear();
onClose();
if (onComplete) {
onComplete();
}
}
};
let progressBars;
if (!_.isEmpty(progresses)) {
progressBars = Object.entries(progresses).map(([name, value]) => {
const {percent, status, type} = value;
let color = 'grey';
let statusString;
if (status === 'complete') {
color = 'green';
statusString = 'Complete:';
} else if (status === 'pending') {
statusString = 'Pending:';
} else if (status === 'failed') {
color = 'red';
statusString = 'Failed:';
} else if (status === 'conflicting') {
color = 'orange';
statusString = 'Already Exists:';
}
let indicating = status === 'pending';
return
})
}
const displayPath = destination ? `${mediaDirectory}/${destination}` : mediaDirectory;
return Upload to: {displayPath}
{progresses && Object.keys(progresses).length > 1 && }
{progressBars}
Close
}