From e97bb78ce442b335084de9001df63fd29e1ed079 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 15 Nov 2021 17:19:51 +0000 Subject: [PATCH] Desktop, Mobile: Added support for notebook icons --- .eslintignore | 12 + .gitignore | 12 + packages/app-desktop/app.reducer.ts | 2 + packages/app-desktop/app.ts | 3 +- packages/app-desktop/gui/Button/Button.tsx | 27 +- .../gui/EditFolderDialog/Dialog.tsx | 115 +++++++ .../gui/EditFolderDialog/IconSelector.tsx | 92 ++++++ .../gui/EditFolderDialog/loadEmojiLib.js | 2 + .../gui/EditFolderDialog/style.scss | 4 + .../gui/MainScreen/commands/index.ts | 2 + .../MainScreen/commands/openFolderDialog.ts | 22 ++ .../NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx | 45 +-- packages/app-desktop/gui/Root.tsx | 31 +- packages/app-desktop/gui/Sidebar/Sidebar.tsx | 9 +- .../app-desktop/gui/Sidebar/styles/index.ts | 2 +- packages/app-desktop/gui/utils/loadScript.ts | 48 +++ packages/app-desktop/package-lock.json | 303 +++++++++++++++++- packages/app-desktop/package.json | 1 + packages/app-desktop/style.scss | 5 +- .../app-mobile/components/screen-header.js | 4 +- .../app-mobile/components/screens/notes.js | 5 +- .../components/side-menu-content.js | 5 +- packages/lib/JoplinDatabase.ts | 6 +- packages/lib/models/Folder.ts | 11 +- packages/lib/services/database/types.ts | 9 + 25 files changed, 715 insertions(+), 62 deletions(-) create mode 100644 packages/app-desktop/gui/EditFolderDialog/Dialog.tsx create mode 100644 packages/app-desktop/gui/EditFolderDialog/IconSelector.tsx create mode 100644 packages/app-desktop/gui/EditFolderDialog/loadEmojiLib.js create mode 100644 packages/app-desktop/gui/EditFolderDialog/style.scss create mode 100644 packages/app-desktop/gui/MainScreen/commands/openFolderDialog.ts create mode 100644 packages/app-desktop/gui/utils/loadScript.ts diff --git a/.eslintignore b/.eslintignore index 95acf5fc91..956f8e5771 100644 --- a/.eslintignore +++ b/.eslintignore @@ -216,6 +216,12 @@ packages/app-desktop/gui/DialogTitle.js.map packages/app-desktop/gui/DropboxLoginScreen.d.ts packages/app-desktop/gui/DropboxLoginScreen.js packages/app-desktop/gui/DropboxLoginScreen.js.map +packages/app-desktop/gui/EditFolderDialog/Dialog.d.ts +packages/app-desktop/gui/EditFolderDialog/Dialog.js +packages/app-desktop/gui/EditFolderDialog/Dialog.js.map +packages/app-desktop/gui/EditFolderDialog/IconSelector.d.ts +packages/app-desktop/gui/EditFolderDialog/IconSelector.js +packages/app-desktop/gui/EditFolderDialog/IconSelector.js.map packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.d.ts packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js.map @@ -282,6 +288,9 @@ packages/app-desktop/gui/MainScreen/commands/newTodo.js.map packages/app-desktop/gui/MainScreen/commands/openFolder.d.ts packages/app-desktop/gui/MainScreen/commands/openFolder.js packages/app-desktop/gui/MainScreen/commands/openFolder.js.map +packages/app-desktop/gui/MainScreen/commands/openFolderDialog.d.ts +packages/app-desktop/gui/MainScreen/commands/openFolderDialog.js +packages/app-desktop/gui/MainScreen/commands/openFolderDialog.js.map packages/app-desktop/gui/MainScreen/commands/openNote.d.ts packages/app-desktop/gui/MainScreen/commands/openNote.js packages/app-desktop/gui/MainScreen/commands/openNote.js.map @@ -699,6 +708,9 @@ packages/app-desktop/gui/utils/SyncScrollMap.js.map packages/app-desktop/gui/utils/convertToScreenCoordinates.d.ts packages/app-desktop/gui/utils/convertToScreenCoordinates.js packages/app-desktop/gui/utils/convertToScreenCoordinates.js.map +packages/app-desktop/gui/utils/loadScript.d.ts +packages/app-desktop/gui/utils/loadScript.js +packages/app-desktop/gui/utils/loadScript.js.map packages/app-desktop/plugins/GotoAnything.d.ts packages/app-desktop/plugins/GotoAnything.js packages/app-desktop/plugins/GotoAnything.js.map diff --git a/.gitignore b/.gitignore index 2df940c1c1..426b5cc079 100644 --- a/.gitignore +++ b/.gitignore @@ -199,6 +199,12 @@ packages/app-desktop/gui/DialogTitle.js.map packages/app-desktop/gui/DropboxLoginScreen.d.ts packages/app-desktop/gui/DropboxLoginScreen.js packages/app-desktop/gui/DropboxLoginScreen.js.map +packages/app-desktop/gui/EditFolderDialog/Dialog.d.ts +packages/app-desktop/gui/EditFolderDialog/Dialog.js +packages/app-desktop/gui/EditFolderDialog/Dialog.js.map +packages/app-desktop/gui/EditFolderDialog/IconSelector.d.ts +packages/app-desktop/gui/EditFolderDialog/IconSelector.js +packages/app-desktop/gui/EditFolderDialog/IconSelector.js.map packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.d.ts packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js.map @@ -265,6 +271,9 @@ packages/app-desktop/gui/MainScreen/commands/newTodo.js.map packages/app-desktop/gui/MainScreen/commands/openFolder.d.ts packages/app-desktop/gui/MainScreen/commands/openFolder.js packages/app-desktop/gui/MainScreen/commands/openFolder.js.map +packages/app-desktop/gui/MainScreen/commands/openFolderDialog.d.ts +packages/app-desktop/gui/MainScreen/commands/openFolderDialog.js +packages/app-desktop/gui/MainScreen/commands/openFolderDialog.js.map packages/app-desktop/gui/MainScreen/commands/openNote.d.ts packages/app-desktop/gui/MainScreen/commands/openNote.js packages/app-desktop/gui/MainScreen/commands/openNote.js.map @@ -682,6 +691,9 @@ packages/app-desktop/gui/utils/SyncScrollMap.js.map packages/app-desktop/gui/utils/convertToScreenCoordinates.d.ts packages/app-desktop/gui/utils/convertToScreenCoordinates.js packages/app-desktop/gui/utils/convertToScreenCoordinates.js.map +packages/app-desktop/gui/utils/loadScript.d.ts +packages/app-desktop/gui/utils/loadScript.js +packages/app-desktop/gui/utils/loadScript.js.map packages/app-desktop/plugins/GotoAnything.d.ts packages/app-desktop/plugins/GotoAnything.js packages/app-desktop/plugins/GotoAnything.js.map diff --git a/packages/app-desktop/app.reducer.ts b/packages/app-desktop/app.reducer.ts index 08748f8a62..45551c072a 100644 --- a/packages/app-desktop/app.reducer.ts +++ b/packages/app-desktop/app.reducer.ts @@ -18,6 +18,7 @@ export enum AppStateDialogName { export interface AppStateDialog { name: AppStateDialogName; + props: Record; } export interface AppState extends State { @@ -287,6 +288,7 @@ export default function(state: AppState, action: any) { newDialogs.push({ name: action.name, + props: action.props || {}, }); newState.dialogs = newDialogs; diff --git a/packages/app-desktop/app.ts b/packages/app-desktop/app.ts index 7da195e862..6e53d52de5 100644 --- a/packages/app-desktop/app.ts +++ b/packages/app-desktop/app.ts @@ -564,7 +564,8 @@ class Application extends BaseApplication { // setTimeout(() => { // this.dispatch({ // type: 'DIALOG_OPEN', - // name: 'masterPassword', + // name: 'editFolder', + // props: { folderId: '3d90f7da26b947dc9c8c6c65e86cd231' }, // }); // }, 2000); diff --git a/packages/app-desktop/gui/Button/Button.tsx b/packages/app-desktop/gui/Button/Button.tsx index e1e0266906..53f843a158 100644 --- a/packages/app-desktop/gui/Button/Button.tsx +++ b/packages/app-desktop/gui/Button/Button.tsx @@ -27,6 +27,9 @@ interface Props { disabled?: boolean; style?: any; size?: ButtonSize; + isSquare?: boolean; + iconOnly?: boolean; + fontSize?: number; } const StyledTitle = styled.span` @@ -41,6 +44,10 @@ export const buttonSizePx = (props: Props) => { throw new Error(`Unknown size: ${props.size}`); }; +const isSquare = (props: Props) => { + return props.iconOnly || props.isSquare; +}; + const StyledButtonBase = styled.button` display: flex; align-items: center; @@ -48,19 +55,19 @@ const StyledButtonBase = styled.button` height: ${(props: Props) => buttonSizePx(props)}px; min-height: ${(props: Props) => buttonSizePx(props)}px; max-height: ${(props: Props) => buttonSizePx(props)}px; - width: ${(props: any) => props.iconOnly ? `${buttonSizePx}px` : 'auto'}; - ${(props: any) => props.iconOnly ? `min-width: ${buttonSizePx}px;` : ''} - ${(props: any) => !props.iconOnly ? 'min-width: 100px;' : ''} - ${(props: any) => props.iconOnly ? `max-width: ${buttonSizePx}px;` : ''} + width: ${(props: Props) => isSquare(props) ? `${buttonSizePx(props)}px` : 'auto'}; + ${(props: Props) => isSquare(props) ? `min-width: ${buttonSizePx(props)}px;` : ''} + ${(props: Props) => !isSquare(props) ? 'min-width: 100px;' : ''} + ${(props: Props) => isSquare(props) ? `max-width: ${buttonSizePx(props)}px;` : ''} box-sizing: border-box; border-radius: 3px; border-style: solid; border-width: 1px; - /*font-size: ${(props: any) => props.theme.fontSize}px; */ - padding: 0 ${(props: any) => props.iconOnly ? 4 : 14}px; + padding: 0 ${(props: Props) => isSquare(props) ? 4 : 14}px; justify-content: center; - opacity: ${(props: any) => props.disabled ? 0.5 : 1}; + opacity: ${(props: Props) => props.disabled ? 0.5 : 1}; user-select: none; + ${(props: Props) => props.fontSize ? `font-size: ${props.fontSize}px;` : ''} `; const StyledIcon = styled(styled.span(space))` @@ -200,7 +207,7 @@ function buttonClass(level: ButtonLevel) { return StyledButtonSecondary; } -function Button(props: Props) { +const Button = React.forwardRef((props: Props, ref: any) => { const iconOnly = props.iconName && !props.title; const StyledButton = buttonClass(props.level); @@ -221,11 +228,11 @@ function Button(props: Props) { } return ( - + {renderIcon()} {renderTitle()} ); -} +}); export default styled(Button)`${space}`; diff --git a/packages/app-desktop/gui/EditFolderDialog/Dialog.tsx b/packages/app-desktop/gui/EditFolderDialog/Dialog.tsx new file mode 100644 index 0000000000..7dcd5e9e47 --- /dev/null +++ b/packages/app-desktop/gui/EditFolderDialog/Dialog.tsx @@ -0,0 +1,115 @@ +import * as React from 'react'; +import { useCallback, useState } from 'react'; +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'; +import { FolderIcon } from '@joplin/lib/services/database/types'; +import Button from '../Button/Button'; + +interface Props { + themeId: number; + dispatch: Function; + folderId: string; +} + +export default function(props: Props) { + const [folderTitle, setFolderTitle] = useState(''); + const [folderIcon, setFolderIcon] = useState(); + + useAsyncEffect(async (event: AsyncEffectEvent) => { + const folder = await Folder.load(props.folderId); + if (event.cancelled) return; + setFolderTitle(folder.title); + setFolderIcon(Folder.unserializeIcon(folder.icon)); + }, [props.folderId]); + + const onClose = useCallback(() => { + props.dispatch({ + type: 'DIALOG_CLOSE', + name: 'editFolder', + }); + }, [props.dispatch]); + + const onButtonRowClick = useCallback(async (event: ClickEvent) => { + if (event.buttonName === 'cancel') { + onClose(); + return; + } + + if (event.buttonName === 'ok') { + await Folder.save({ + id: props.folderId, + title: folderTitle, + icon: Folder.serializeIcon(folderIcon), + }); + onClose(); + return; + } + }, [onClose, folderTitle, folderIcon, props.folderId]); + + const onFolderTitleChange = useCallback((event: any) => { + setFolderTitle(event.target.value); + }, []); + + const onFolderIconChange = useCallback((event: ChangeEvent) => { + setFolderIcon(event.value); + }, []); + + const onClearClick = useCallback(() => { + setFolderIcon(null); + }, []); + + function renderForm() { + return ( +
+
+
+ + +
+ +
+ +
+ +
+
+
+
+ ); + } + + function renderContent() { + return ( +
+ {renderForm()} +
+ ); + } + + function renderDialogWrapper() { + return ( +
+ + {renderContent()} + +
+ ); + } + + return ( + + ); +} diff --git a/packages/app-desktop/gui/EditFolderDialog/IconSelector.tsx b/packages/app-desktop/gui/EditFolderDialog/IconSelector.tsx new file mode 100644 index 0000000000..1e3fe3de1e --- /dev/null +++ b/packages/app-desktop/gui/EditFolderDialog/IconSelector.tsx @@ -0,0 +1,92 @@ +import { EmojiButton } from '@joeattardi/emoji-button'; +import { useEffect, useState, useCallback, useRef } from 'react'; +import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect'; +import { loadScript } from '../utils/loadScript'; +import Button from '../Button/Button'; +import { FolderIcon } from '@joplin/lib/services/database/types'; + +export interface ChangeEvent { + value: FolderIcon; +} + +type ChangeHandler = (event: ChangeEvent)=> void; + +interface Props { + onChange: ChangeHandler; + icon: FolderIcon | null; +} + +export const IconSelector = (props: Props) => { + const [emojiButtonClassReady, setEmojiButtonClassReady] = useState(false); + const [picker, setPicker] = useState(); + const buttonRef = useRef(null); + + useAsyncEffect(async (event: AsyncEffectEvent) => { + const loadScripts = async () => { + // The emoji-button lib is annoying to load as it only comes as an + // ES module. So we first need to load the lib, then load a loader + // script, which will copy the class to the window object. + + await loadScript({ + id: 'emoji-button-lib', + src: 'node_modules/@joeattardi/emoji-button/dist/index.js', + attrs: { + type: 'module', + }, + }); + + if (event.cancelled) return; + + await loadScript({ + id: 'emoji-button-lib-loader', + src: 'gui/EditFolderDialog/loadEmojiLib.js', + attrs: { + type: 'module', + }, + }); + + if (event.cancelled) return; + + setEmojiButtonClassReady(true); + }; + + void loadScripts(); + }, []); + + useEffect(() => { + if (!emojiButtonClassReady) return () => {}; + + const p: EmojiButton = new (window as any).EmojiButton({ + zIndex: 10000, + }); + + const onEmoji = (selection: FolderIcon) => { + props.onChange({ value: selection }); + }; + + p.on('emoji', onEmoji); + + setPicker(p); + + return () => { + p.off('emoji', onEmoji); + }; + }, [emojiButtonClassReady, props.onChange]); + + const onClick = useCallback(() => { + picker.togglePicker(buttonRef.current); + }, [picker]); + + const buttonText = props.icon ? props.icon.emoji : '...'; + + return ( +