Files
spacedrive/interface/app/$libraryId/Explorer/File/RenameTextBox.tsx
nikec ff9515bdb4 [ENG 655] Explorer restructure (#858)
* wip

* wip 2

* Grid list single selection

* core & pnpm-lock

* Merge branch 'main'

Conflicts:
	interface/app/$libraryId/Explorer/index.tsx

* missing import from merge

* fix total_orphan_paths bug

* add top bar context

* missing pieces of merge

* missing pieces of merge

* missing divs

* Fill fallback value - was causing null error of page

* spelling fixes

* notice light theme, list view update, other explorer updates

* Update pnpm-lock

* Remove procedure

* fix light menu ink color

* fix list view scrolled state

* Change layout default

* Remove unused imports

* remove keys

* empty notice & context menu overview

* Fix prevent selection while context menu is up

* Fix scroll with keys

* Empty notice icon

* Add light icons

* Context menu and fixed list view scroll

* Fix name column sizing

* top/bottom scroll position

* Tag assign only when objectData

* Fix list view locked state

* fix ci

* shamefully ignore eslint

---------

Co-authored-by: Jamie Pine <ijamespine@me.com>
Co-authored-by: ameer2468 <33054370+ameer2468@users.noreply.github.com>
Co-authored-by: Jamie Pine <32987599+jamiepine@users.noreply.github.com>
Co-authored-by: Utku Bakir <74243531+utkubakir@users.noreply.github.com>
2023-06-06 06:55:56 +00:00

185 lines
4.2 KiB
TypeScript

import clsx from 'clsx';
import { HTMLAttributes, useEffect, useRef, useState } from 'react';
import { useKey } from 'rooks';
import { FilePath, useLibraryMutation } from '@sd/client';
import { getExplorerStore, useExplorerStore } from '~/hooks/useExplorerStore';
import { useOperatingSystem } from '~/hooks/useOperatingSystem';
interface Props extends HTMLAttributes<HTMLDivElement> {
filePathData: FilePath;
selected: boolean;
activeClassName?: string;
disabled?: boolean;
}
export default ({
filePathData,
selected,
className,
activeClassName,
disabled,
...props
}: Props) => {
const explorerStore = useExplorerStore();
const os = useOperatingSystem();
const ref = useRef<HTMLDivElement>(null);
const [allowRename, setAllowRename] = useState(false);
const renameFile = useLibraryMutation(['files.renameFile'], {
onError: () => reset()
});
const fileName = `${filePathData?.name}${
filePathData?.extension && `.${filePathData.extension}`
}`;
// Reset to original file name
function reset() {
if (ref.current) {
ref.current.innerText = fileName;
}
}
// Handle renaming
function rename() {
if (ref.current) {
const innerText = ref.current.innerText.trim();
if (!innerText) return reset();
const newName = innerText;
if (filePathData) {
const oldName =
filePathData.is_dir || !filePathData.extension
? filePathData.name
: filePathData.name + '.' + filePathData.extension;
if (newName !== oldName) {
renameFile.mutate({
location_id: filePathData.location_id,
file_name: oldName,
new_file_name: newName
});
}
}
}
}
// Highlight file name up to extension or
// fully if it's a directory or has no extension
function highlightFileName() {
if (ref.current) {
const range = document.createRange();
const node = ref.current.firstChild;
if (!node) return;
range.setStart(node, 0);
range.setEnd(node, filePathData?.name.length || 0);
const sel = window.getSelection();
sel?.removeAllRanges();
sel?.addRange(range);
}
}
// Blur field
function blur() {
if (ref.current) {
ref.current.blur();
setAllowRename(false);
}
}
// Handle keydown events
function handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
switch (e.key) {
case 'Tab':
e.preventDefault();
blur();
break;
case 'Escape':
reset();
blur();
break;
case 'z':
if (os === 'macOS' ? e.metaKey : e.ctrlKey) {
reset();
highlightFileName();
}
break;
}
}
// Focus and highlight when renaming is allowed
useEffect(() => {
if (allowRename) {
getExplorerStore().isRenaming = true;
setTimeout(() => {
if (ref.current) {
ref.current.focus();
highlightFileName();
}
});
} else getExplorerStore().isRenaming = false;
}, [allowRename]);
// Handle renaming when triggered from outside
useEffect(() => {
if (selected) {
if (explorerStore.isRenaming && !allowRename) setAllowRename(true);
else if (!explorerStore.isRenaming && allowRename) setAllowRename(false);
}
}, [explorerStore.isRenaming]);
// Rename or blur on Enter key
useKey('Enter', (e) => {
if (allowRename) {
e.preventDefault();
blur();
} else if (selected && !disabled) setAllowRename(true);
});
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (ref.current && !ref.current.contains(event.target as Node)) {
blur();
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [ref]);
return (
<div
ref={ref}
role="textbox"
contentEditable={allowRename}
suppressContentEditableWarning
className={clsx(
'cursor-default overflow-y-auto truncate rounded-md px-1.5 py-px text-xs text-ink',
allowRename && [
'whitespace-normal bg-app outline-none ring-2 ring-accent-deep',
activeClassName
],
className
)}
onClick={(e) => {
if (selected || allowRename) e.stopPropagation();
if (selected && !disabled) setAllowRename(true);
}}
onBlur={() => {
rename();
setAllowRename(false);
}}
onKeyDown={handleKeyDown}
{...props}
>
{fileName}
</div>
);
};