Files
standardnotes-app-web/app/assets/javascripts/components/NotesOptions.tsx
2021-05-07 15:41:00 -03:00

200 lines
6.3 KiB
TypeScript

import { AppState } from '@/ui_models/app_state';
import { Icon, IconType } from './Icon';
import { Switch } from './Switch';
import { observer } from 'mobx-react-lite';
import { useEffect, useRef, useState } from 'preact/hooks';
import {
Disclosure,
DisclosureButton,
DisclosurePanel,
} from '@reach/disclosure';
type Props = {
appState: AppState;
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void;
blurLocked: boolean;
setLockCloseOnBlur: (lock: boolean) => void;
};
export const NotesOptions = observer(
({ appState, closeOnBlur, blurLocked, setLockCloseOnBlur }: Props) => {
const [tagsMenuOpen, setTagsMenuOpen] = useState(false);
const [tagsMenuPosition, setTagsMenuPosition] = useState({
top: 0,
right: 0,
});
const [shouldTrashNotes, setShouldTrashNotes] = useState(false);
const notes = Object.values(appState.notes.selectedNotes);
const hidePreviews = !notes.some((note) => !note.hidePreview);
const locked = !notes.some((note) => !note.locked);
const archived = !notes.some((note) => !note.archived);
const trashed = !notes.some((note) => !note.trashed);
const pinned = !notes.some((note) => !note.pinned);
const trashButtonRef = useRef<HTMLButtonElement>();
const tagsButtonRef = useRef<HTMLButtonElement>();
const iconClass = 'fill-current color-neutral mr-2';
const buttonClass =
'flex items-center border-0 focus:inner-ring-info ' +
'cursor-pointer hover:bg-contrast color-text bg-transparent px-3 ' +
'text-left';
useEffect(() => {
const openTrashAlert = async () => {
if (shouldTrashNotes && blurLocked) {
await appState.notes.setTrashSelectedNotes(!trashed, trashButtonRef);
setShouldTrashNotes(false);
setLockCloseOnBlur(false);
}
};
openTrashAlert();
}, [
appState.notes,
blurLocked,
setLockCloseOnBlur,
shouldTrashNotes,
trashed,
]);
return (
<>
<Switch
onBlur={closeOnBlur}
className="px-3 py-1.5"
checked={locked}
onChange={() => {
appState.notes.setLockSelectedNotes(!locked);
}}
>
<span className="flex items-center">
<Icon type={IconType.PencilOff} className={iconClass} />
Prevent editing
</span>
</Switch>
<Switch
onBlur={closeOnBlur}
className="px-3 py-1.5"
checked={!hidePreviews}
onChange={() => {
appState.notes.setHideSelectedNotePreviews(!hidePreviews);
}}
>
<span className="flex items-center">
<Icon type={IconType.RichText} className={iconClass} />
Show preview
</span>
</Switch>
<div className="h-1px my-2 bg-border"></div>
{appState.tags.tagsCount > 0 && (
<Disclosure
open={tagsMenuOpen}
onChange={() => {
const buttonRect = tagsButtonRef.current.getBoundingClientRect();
const { offsetTop, offsetWidth } = tagsButtonRef.current;
setTagsMenuPosition({
top: offsetTop,
right:
buttonRect.right + 265 > document.body.clientWidth
? offsetWidth
: -offsetWidth,
});
setTagsMenuOpen(!tagsMenuOpen);
}}
>
<DisclosureButton
onKeyUp={(event) => {
if (event.key === 'Escape') {
setTagsMenuOpen(false);
}
}}
onBlur={closeOnBlur}
ref={tagsButtonRef}
className={`${buttonClass} py-1.5 justify-between`}
>
<div className="flex items-center">
<Icon type={IconType.Hashtag} className={iconClass} />
{'Add tag'}
</div>
<Icon
type={IconType.ChevronRight}
className="fill-current color-neutral"
/>
</DisclosureButton>
<DisclosurePanel
onKeyUp={(event) => {
if (event.key === 'Escape') {
setTagsMenuOpen(false);
tagsButtonRef.current.focus();
}
}}
style={{
...tagsMenuPosition,
}}
className="sn-dropdown sn-dropdown-anchor-right flex flex-col py-2 max-w-265px max-h-80 overflow-y-scroll"
>
{appState.tags.tags.map((tag) => (
<button
key={tag.title}
className={`${buttonClass} py-2`}
onBlur={closeOnBlur}
onClick={() => {
appState.tags.addTagToSelectedNotes(tag);
}}
>
{tag.title}
</button>
))}
</DisclosurePanel>
</Disclosure>
)}
<button
onBlur={closeOnBlur}
className={`${buttonClass} py-1.5`}
onClick={() => {
appState.notes.setPinSelectedNotes(!pinned);
}}
>
<Icon
type={pinned ? IconType.Unpin : IconType.Pin}
className={iconClass}
/>
{appState.notes.selectedNotesCount > 1
? pinned
? 'Unpin notes'
: 'Pin notes'
: pinned
? 'Unpin note'
: 'Pin note'}
</button>
<button
onBlur={closeOnBlur}
className={`${buttonClass} py-1.5`}
onClick={() => {
appState.notes.setArchiveSelectedNotes(!archived);
}}
>
<Icon
type={archived ? IconType.Unarchive : IconType.Archive}
className={iconClass}
/>
{archived ? 'Unarchive' : 'Archive'}
</button>
<button
ref={trashButtonRef}
onBlur={closeOnBlur}
className={`${buttonClass} py-1.5`}
onClick={async () => {
setShouldTrashNotes(true);
setLockCloseOnBlur(true);
}}
>
<Icon type={IconType.Trash} className={iconClass} />
{trashed ? 'Restore' : 'Move to Trash'}
</button>
</>
);
}
);