2021-11-15 19:19:51 +02:00
|
|
|
import * as React from 'react';
|
2024-04-25 16:31:18 +02:00
|
|
|
import { useCallback, useState, useRef, useEffect, useId } from 'react';
|
2021-11-15 19:19:51 +02:00
|
|
|
import { _ } from '@joplin/lib/locale';
|
|
|
|
import DialogButtonRow, { ClickEvent } from '../DialogButtonRow';
|
|
|
|
import Dialog from '../Dialog';
|
|
|
|
import DialogTitle from '../DialogTitle';
|
|
|
|
import StyledInput from '../style/StyledInput';
|
|
|
|
import { IconSelector, ChangeEvent } from './IconSelector';
|
|
|
|
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
|
|
|
import Folder from '@joplin/lib/models/Folder';
|
2022-02-06 18:42:00 +02:00
|
|
|
import { FolderEntity, FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
|
2021-11-15 19:19:51 +02:00
|
|
|
import Button from '../Button/Button';
|
2021-12-31 08:26:06 +02:00
|
|
|
import bridge from '../../services/bridge';
|
2022-02-06 18:42:00 +02:00
|
|
|
import shim from '@joplin/lib/shim';
|
|
|
|
import FolderIconBox from '../FolderIconBox';
|
2024-04-01 16:34:22 +02:00
|
|
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
2021-11-15 19:19:51 +02:00
|
|
|
|
|
|
|
interface Props {
|
|
|
|
themeId: number;
|
2023-06-30 11:30:29 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
2021-11-15 19:19:51 +02:00
|
|
|
dispatch: Function;
|
|
|
|
folderId: string;
|
2021-12-31 08:26:06 +02:00
|
|
|
parentId: string;
|
2021-11-15 19:19:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export default function(props: Props) {
|
|
|
|
const [folderTitle, setFolderTitle] = useState('');
|
|
|
|
const [folderIcon, setFolderIcon] = useState<FolderIcon>();
|
2022-01-09 17:30:21 +02:00
|
|
|
const titleInputRef = useRef(null);
|
2021-11-15 19:19:51 +02:00
|
|
|
|
2021-12-31 08:26:06 +02:00
|
|
|
const isNew = !props.folderId;
|
|
|
|
|
2021-11-15 19:19:51 +02:00
|
|
|
useAsyncEffect(async (event: AsyncEffectEvent) => {
|
2021-12-31 08:26:06 +02:00
|
|
|
if (isNew) return;
|
|
|
|
|
2021-11-15 19:19:51 +02:00
|
|
|
const folder = await Folder.load(props.folderId);
|
|
|
|
if (event.cancelled) return;
|
|
|
|
setFolderTitle(folder.title);
|
|
|
|
setFolderIcon(Folder.unserializeIcon(folder.icon));
|
2021-12-31 08:26:06 +02:00
|
|
|
}, [props.folderId, isNew]);
|
2021-11-15 19:19:51 +02:00
|
|
|
|
|
|
|
const onClose = useCallback(() => {
|
|
|
|
props.dispatch({
|
|
|
|
type: 'DIALOG_CLOSE',
|
|
|
|
name: 'editFolder',
|
|
|
|
});
|
|
|
|
}, [props.dispatch]);
|
|
|
|
|
2022-01-09 17:30:21 +02:00
|
|
|
useEffect(() => {
|
2024-04-01 16:34:22 +02:00
|
|
|
focus('Dialog::titleInputRef', titleInputRef.current);
|
2022-01-09 17:30:21 +02:00
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
titleInputRef.current.select();
|
|
|
|
}, 100);
|
|
|
|
}, []);
|
|
|
|
|
2021-11-15 19:19:51 +02:00
|
|
|
const onButtonRowClick = useCallback(async (event: ClickEvent) => {
|
|
|
|
if (event.buttonName === 'cancel') {
|
|
|
|
onClose();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.buttonName === 'ok') {
|
2021-12-31 08:26:06 +02:00
|
|
|
const folder: FolderEntity = {
|
2021-11-15 19:19:51 +02:00
|
|
|
title: folderTitle,
|
|
|
|
icon: Folder.serializeIcon(folderIcon),
|
2021-12-31 08:26:06 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
if (!isNew) folder.id = props.folderId;
|
|
|
|
if (props.parentId) folder.parent_id = props.parentId;
|
|
|
|
|
|
|
|
try {
|
|
|
|
const savedFolder = await Folder.save(folder, { userSideValidation: true });
|
|
|
|
onClose();
|
|
|
|
|
|
|
|
props.dispatch({
|
|
|
|
type: 'FOLDER_SELECT',
|
|
|
|
id: savedFolder.id,
|
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
bridge().showErrorMessageBox(error.message);
|
|
|
|
}
|
|
|
|
|
2021-11-15 19:19:51 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-08-19 13:10:04 +02:00
|
|
|
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
2021-12-31 08:26:06 +02:00
|
|
|
}, [onClose, folderTitle, folderIcon, props.folderId, props.parentId]);
|
2021-11-15 19:19:51 +02:00
|
|
|
|
2024-04-05 13:16:49 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
2021-11-15 19:19:51 +02:00
|
|
|
const onFolderTitleChange = useCallback((event: any) => {
|
|
|
|
setFolderTitle(event.target.value);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const onFolderIconChange = useCallback((event: ChangeEvent) => {
|
|
|
|
setFolderIcon(event.value);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const onClearClick = useCallback(() => {
|
|
|
|
setFolderIcon(null);
|
|
|
|
}, []);
|
|
|
|
|
2022-02-06 18:42:00 +02:00
|
|
|
const onBrowseClick = useCallback(async () => {
|
2022-02-07 19:23:20 +02:00
|
|
|
const filePaths = await bridge().showOpenDialog({
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: _('Images'),
|
|
|
|
extensions: ['jpg', 'jpeg', 'png'],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
2022-02-06 18:42:00 +02:00
|
|
|
if (filePaths.length !== 1) return;
|
|
|
|
const filePath = filePaths[0];
|
|
|
|
|
|
|
|
try {
|
|
|
|
const dataUrl = await shim.imageToDataUrl(filePath, 256);
|
|
|
|
setFolderIcon(icon => {
|
|
|
|
return {
|
|
|
|
...icon,
|
|
|
|
emoji: '',
|
|
|
|
name: '',
|
|
|
|
type: FolderIconType.DataUrl,
|
|
|
|
dataUrl,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
await bridge().showErrorMessageBox(error.message);
|
|
|
|
}
|
|
|
|
}, []);
|
|
|
|
|
2024-04-25 16:31:18 +02:00
|
|
|
const formTitleInputId = useId();
|
2021-11-15 19:19:51 +02:00
|
|
|
function renderForm() {
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<div className="form">
|
|
|
|
<div className="form-input-group">
|
2024-04-25 16:31:18 +02:00
|
|
|
<label htmlFor={formTitleInputId}>{_('Title')}</label>
|
|
|
|
<StyledInput id={formTitleInputId} type="text" ref={titleInputRef} value={folderTitle} onChange={onFolderTitleChange}/>
|
2021-11-15 19:19:51 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="form-input-group">
|
|
|
|
<label>{_('Icon')}</label>
|
|
|
|
<div className="icon-selector-row">
|
2022-02-06 18:42:00 +02:00
|
|
|
{ folderIcon && <div className="foldericon"><FolderIconBox folderIcon={folderIcon} /></div> }
|
2021-11-15 19:19:51 +02:00
|
|
|
<IconSelector
|
2022-02-06 18:42:00 +02:00
|
|
|
title={_('Select emoji...')}
|
2021-11-15 19:19:51 +02:00
|
|
|
icon={folderIcon}
|
|
|
|
onChange={onFolderIconChange}
|
|
|
|
/>
|
2022-02-06 18:42:00 +02:00
|
|
|
<Button ml={1} title={_('Select file...')} onClick={onBrowseClick}/>
|
|
|
|
{ folderIcon && <Button ml={1} title={_('Clear')} onClick={onClearClick}/> }
|
2021-11-15 19:19:51 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderContent() {
|
|
|
|
return (
|
|
|
|
<div className="dialog-content">
|
|
|
|
{renderForm()}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-31 08:26:06 +02:00
|
|
|
const dialogTitle = isNew ? _('Create notebook') : _('Edit notebook');
|
|
|
|
|
2021-11-15 19:19:51 +02:00
|
|
|
function renderDialogWrapper() {
|
|
|
|
return (
|
|
|
|
<div className="dialog-root">
|
2021-12-31 08:26:06 +02:00
|
|
|
<DialogTitle title={dialogTitle}/>
|
2021-11-15 19:19:51 +02:00
|
|
|
{renderContent()}
|
|
|
|
<DialogButtonRow
|
|
|
|
themeId={props.themeId}
|
|
|
|
onClick={onButtonRowClick}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2024-07-31 15:10:58 +02:00
|
|
|
<Dialog onCancel={onClose} className="master-password-dialog">{renderDialogWrapper()}</Dialog>
|
2021-11-15 19:19:51 +02:00
|
|
|
);
|
|
|
|
}
|