diff --git a/frontend/src/Components/CommentComponent.tsx b/frontend/src/Components/CommentComponent.tsx index 938bc7ca..cfbd339a 100644 --- a/frontend/src/Components/CommentComponent.tsx +++ b/frontend/src/Components/CommentComponent.tsx @@ -15,6 +15,7 @@ import {useInterpreter} from '../API/use/useInterpreter'; import OutsideClickHandler from 'react-outside-click-handler'; import {AltTranslateButton, AnnotateButton, TranslateButton} from './ContentButtons'; import {InviewContext} from '../Pages/PostPage'; +import {getPreferredLang, getShowInlineTranslateButton} from './UserProfileSettings'; interface CommentProps { comment: CommentInfo; @@ -135,7 +136,7 @@ interface ControlsProps { setAltContent: (value: string | undefined) => void; } -function Controls({contentRef, comment, setEditingText, hideRating, onEdit, onAnswer, answerOpen, setAnswerOpen, setAltContent}: ControlsProps) { +function Controls({contentRef, comment, setEditingText, hideRating, onEdit, onAnswer, answerOpen, setAnswerOpen, setAltContent: setCommentAltContent}: ControlsProps) { const api = useAPI(); const handleVote = useMemo(() => { @@ -168,7 +169,10 @@ function Controls({contentRef, comment, setEditingText, hideRating, onEdit, onAn e.preventDefault(); setAnswerOpen(!answerOpen); }; - useEffect(() => setAltContent(altContent), [altContent]); + useEffect(() => setCommentAltContent(altContent), [setCommentAltContent, altContent]); + const showTranslateButtonInline = useMemo(() => { + return getShowInlineTranslateButton() && comment.language !== getPreferredLang(); + }, [comment]); return (
@@ -179,9 +183,9 @@ function Controls({contentRef, comment, setEditingText, hideRating, onEdit, onAn {comment.canEdit && onEdit &&
}
- {currentMode === 'translate' && + {(showTranslateButtonInline || currentMode === 'translate') &&
- +
} {currentMode === 'altTranslate' &&
@@ -196,7 +200,7 @@ function Controls({contentRef, comment, setEditingText, hideRating, onEdit, onAn {showOptions && setShowOptions(false)}>
- {setShowOptions(false);translate();}} isActive={currentMode === 'translate'} /> + {!showTranslateButtonInline && {setShowOptions(false);translate();}} isActive={currentMode === 'translate'} />} {calcShowAltTranslate() && {setShowOptions(false);altTranslate();}} isActive={currentMode === 'altTranslate'}/>} {calcShowAnnotate() && diff --git a/frontend/src/Components/PostComponent.tsx b/frontend/src/Components/PostComponent.tsx index 6a4b5da3..4c719bc4 100644 --- a/frontend/src/Components/PostComponent.tsx +++ b/frontend/src/Components/PostComponent.tsx @@ -17,6 +17,7 @@ import {SignatureComponent} from './SignatureComponent'; import Conf from '../Conf'; import {useInterpreter} from '../API/use/useInterpreter'; import {AltTranslateButton, AnnotateButton, TranslateButton, UnwatchButton, WatchButton} from './ContentButtons'; +import {getPreferredLang, getShowInlineTranslateButton} from './UserProfileSettings'; interface PostComponentProps { post: PostInfo; @@ -135,6 +136,9 @@ export default function PostComponent(props: PostComponentProps) { const altMode = currentMode !== undefined || inProgress; const autoCut = altMode ? undefined : props.autoCut; + const showTranslateButtonInline = useMemo(() => { + return getShowInlineTranslateButton() && props.post.language !== getPreferredLang(); + }, [props.post]); return (
@@ -172,9 +176,9 @@ export default function PostComponent(props: PostComponentProps) { {/*
*/} {props.post.canEdit && props.onEdit &&
}
- {currentMode === 'translate' && + {(showTranslateButtonInline || currentMode === 'translate') &&
- +
} {currentMode === 'altTranslate' &&
@@ -189,7 +193,7 @@ export default function PostComponent(props: PostComponentProps) { {showOptions && setShowOptions(false)}>
- {setShowOptions(false);translate();}} isActive={currentMode === 'translate'} /> + {!showTranslateButtonInline && {setShowOptions(false);translate();}} isActive={currentMode === 'translate'} />} {calcShowAltTranslate() && {setShowOptions(false);altTranslate();}} isActive={currentMode === 'altTranslate'}/>} {calcShowAnnotate() && diff --git a/frontend/src/Components/UserProfileSettings.tsx b/frontend/src/Components/UserProfileSettings.tsx index db985aa5..eecf56b9 100644 --- a/frontend/src/Components/UserProfileSettings.tsx +++ b/frontend/src/Components/UserProfileSettings.tsx @@ -1,24 +1,28 @@ -import React, {useEffect} from 'react'; -import buttonStyles from '../Components/Buttons.module.scss'; -import {ReactComponent as LogoutIcon} from '../Assets/logout.svg'; -import {ReactComponent as UserIcon} from '../Assets/user.svg'; -import {useAPI, useAppState} from '../AppState/AppState'; +import React, {useEffect, useState} from 'react'; import {useLocation, useNavigate} from 'react-router-dom'; -import ThemeToggleComponent from './ThemeToggleComponent'; -import {BarmaliniAccessResult, UserGender} from '../Types/UserInfo'; -import {toast} from 'react-toastify'; -import {observer} from 'mobx-react-lite'; -import styles from './UserProfileSettings.module.scss'; + import classNames from 'classnames'; +import {observer} from 'mobx-react-lite'; import {confirmAlert} from 'react-confirm-alert'; +import {toast} from 'react-toastify'; + import {useUserProfile} from '../API/use/useUserProfile'; +import {useAPI, useAppState} from '../AppState/AppState'; +import {BarmaliniAccessResult, UserGender} from '../Types/UserInfo'; import {SecretMailKeyGeneratorForm} from './SecretMailbox'; +import ThemeToggleComponent from './ThemeToggleComponent'; + +import {ReactComponent as LogoutIcon} from '../Assets/logout.svg'; +import {ReactComponent as UserIcon} from '../Assets/user.svg'; +import {ReactComponent as TranslateIcon} from '../Assets/translate.svg'; +import buttonStyles from '../Components/Buttons.module.scss'; +import styles from './UserProfileSettings.module.scss'; type UserProfileSettingsProps = { - onChange: any; - gender: UserGender; - barmaliniAccess?: boolean; - isBarmalini?: boolean; + onChange: () => void; + gender: UserGender; + barmaliniAccess?: boolean; + isBarmalini?: boolean; }; const languages = new Map([ @@ -52,57 +56,65 @@ export function getLegacyZoom(): boolean { export function getPreferredLang(): string { return localStorage.getItem('preferredLang') || 'ru'; } +export function getShowInlineTranslateButton(): boolean { + return localStorage.getItem('showInlineTranslateButton') === 'true'; +} export default function UserProfileSettings(props: UserProfileSettingsProps) { - useEffect(() => { - window.scrollTo({ top: 0 }); - }, []); - - const api = useAPI(); - const navigate = useNavigate(); - const location = useLocation(); - - let gender = props.gender; - -const [autoStop, setAutoStop] = React.useState(getVideoAutopause()); -const [legacyZoom, setLegacyZoom] = React.useState(getLegacyZoom()); -const [preferredLang, setPreferredLang] = React.useState(getPreferredLang()); - -const confirmWrapper = (message: string, callback: () => void) => (e: React.MouseEvent) => { - e.preventDefault(); - confirmAlert({ - title: 'Астанавитесь! Подумайте!', - message, - buttons: [ - { - label: 'Да!', - onClick: callback - }, - { - label: 'Отмена', - className: 'cancel' - } - ], - overlayClassName: 'orbitar-confirm-overlay' - }); -}; + useEffect(() => { + window.scrollTo({top: 0}); + }, []); + + const api = useAPI(); + const navigate = useNavigate(); + const location = useLocation(); + + let gender = props.gender; + + const [autoStop, setAutoStop] = useState(getVideoAutopause()); + const [legacyZoom, setLegacyZoom] = useState(getLegacyZoom()); + const [preferredLang, setPreferredLang] = useState(getPreferredLang()); + const [showInlineTranslateButton, setShowInlineTranslateButton] = + useState(getShowInlineTranslateButton()); + + const confirmWrapper = (message: string, callback: () => void) => (e: React.MouseEvent) => { + e.preventDefault(); + confirmAlert({ + title: 'Астанавитесь! Подумайте!', + message, + buttons: [ + { + label: 'Да!', + onClick: callback, + }, + { + label: 'Отмена', + className: 'cancel', + }, + ], + overlayClassName: 'orbitar-confirm-overlay', + }); + }; - const handleLogout = confirmWrapper( - 'Вы действительно хотите выйти? Вы будете вынуждены войти в аккаунт заново.', () => { - api.auth.signOut().then(() => { - navigate(location.pathname); - }); - }); + const handleLogout = confirmWrapper( + 'Вы действительно хотите выйти? Вы будете вынуждены войти в аккаунт заново.', + () => { + api.auth.signOut().then(() => { + navigate(location.pathname); + }); + }, + ); - const handleResetSessions = confirmWrapper( - `Вы действительно хотите сбросить пароль и все сессии? + const handleResetSessions = confirmWrapper( + `Вы действительно хотите сбросить пароль и все сессии? Вы будете разлогинены на ВСЕХ устройствах, текущий пароль больше не будет работать. - На почту использованную при регистрации (у вас же всё ещё есть доступ к ней?) придет ссылка для сброса пароля через которую вы сможете войти в аккаунт заново и установить новый пароль.`, () => { - api.auth.dropPasswordAndSessions().then(() => { - navigate(location.pathname); - }); - } - ); + На почту использованную при регистрации (у вас же всё ещё есть доступ к ней?) придет ссылка для сброса пароля через которую вы сможете войти в аккаунт заново и установить новый пароль.`, + () => { + api.auth.dropPasswordAndSessions().then(() => { + navigate(location.pathname); + }); + }, + ); const toggleAutoStop = () => { setAutoStop(!autoStop); @@ -112,30 +124,37 @@ const confirmWrapper = (message: string, callback: () => void) => (e: React.Mous setLegacyZoom(!legacyZoom); }; - const changeLang = (ev: React.FormEvent) => { - const lang = ev.currentTarget.value; - setPreferredLang(lang); - }; - - const handleGenderChange = (e: React.MouseEvent) => { - e.preventDefault(); - if (gender === undefined) { - return; - } - - if (gender === UserGender.fluid) { - gender = UserGender.he; - } else if (gender === UserGender.he) { - gender = UserGender.she; - } else { - gender = UserGender.fluid; - } - api.userAPI.saveGender(gender).then((data) => { - props.onChange(); - }).catch((error) => { - toast.error(error?.message || 'Не удалось сохранить.'); - }); - }; + const toggleShowInlineTranslateButton = () => { + setShowInlineTranslateButton(!showInlineTranslateButton); + }; + + const changeLang = (ev: React.FormEvent) => { + const lang = ev.currentTarget.value; + setPreferredLang(lang); + }; + + const handleGenderChange = (e: React.MouseEvent) => { + e.preventDefault(); + if (gender === undefined) { + return; + } + + if (gender === UserGender.fluid) { + gender = UserGender.he; + } else if (gender === UserGender.he) { + gender = UserGender.she; + } else { + gender = UserGender.fluid; + } + api.userAPI + .saveGender(gender) + .then(() => { + props.onChange(); + }) + .catch((error) => { + toast.error(error?.message || 'Не удалось сохранить.'); + }); + }; useEffect(() => { localStorage.setItem('autoStopVideos', JSON.stringify(autoStop)); @@ -145,6 +164,10 @@ const confirmWrapper = (message: string, callback: () => void) => (e: React.Mous localStorage.setItem('legacyZoom', JSON.stringify(legacyZoom)); }, [legacyZoom]); + useEffect(() => { + localStorage.setItem('showInlineTranslateButton', JSON.stringify(showInlineTranslateButton)); + }, [showInlineTranslateButton]); + useEffect(() => { localStorage.setItem('preferredLang', preferredLang); }, [preferredLang]); @@ -152,40 +175,53 @@ const confirmWrapper = (message: string, callback: () => void) => (e: React.Mous return ( <>
- {gender !== undefined && - } + {gender !== undefined && ( + + )} - {} + {}
Язык перевода: +
+ {/**/} {props.barmaliniAccess && }
- {!props.isBarmalini && } - + {!props.isBarmalini && ( + + )} +
); } - /** * Component that displays the button, when clicked, * the button turns into a div with the password/token (got from the server via userAPi.getBarmaliniPassword) @@ -195,24 +231,28 @@ const BarmaliniAccess = observer(() => { const api = useAPI(); const [access, setAccess] = React.useState(); - const handleCopy = (e: React.MouseEvent) => { e.preventDefault(); if (access === undefined) { return; } - navigator.clipboard?.writeText(access.password) - ?.then(() => toast('В буфере!'))?.catch(); + navigator.clipboard + ?.writeText(access.password) + ?.then(() => toast('В буфере!')) + ?.catch(); }; const handleShowPassword = (e: React.MouseEvent) => { e.preventDefault(); if (!access) { - api.userAPI.getBarmaliniAccess().then((data) => { - setAccess(data); - }).catch((error) => { - toast.error(error?.message || 'Не удалось получить пароль.'); - }); + api.userAPI + .getBarmaliniAccess() + .then((data) => { + setAccess(data); + }) + .catch((error) => { + toast.error(error?.message || 'Не удалось получить пароль.'); + }); } else { setAccess(undefined); } @@ -233,16 +273,30 @@ const BarmaliniAccess = observer(() => { return (
- {access &&
+ {(access && (
- Логин: {access.login}
-
Пароль:  - {access.password} -   +
+ Логин: {access.login} +
+
+ Пароль:  + + {access.password} + +   + +
+
+ Счастливого бармаления. Пароль истекает через час. +
-
Счастливого бармаления. Пароль истекает через час.
-
|| } + )) || ( + + )}
); }); @@ -272,25 +326,29 @@ export const MailboxSettings = observer(() => { e.preventDefault(); confirmAlert({ title: 'Астанавитесь! Подумайте!', - message: ('Вы действительно хотите удалить почтовый ящик? Вы больше не сможете получать новые шифровки, ' + - 'но вы сможете читать старые шифровки, адресованные вам.'), + message: + 'Вы действительно хотите удалить почтовый ящик? Вы больше не сможете получать новые шифровки, ' + + 'но вы сможете читать старые шифровки, адресованные вам.', buttons: [ { label: 'Да!', onClick: () => { - api.userAPI.savePublicKey('').then(() => { - refreshProfile(); - }).catch((error) => { - toast.error(error?.message || 'Не удалось удалить почтовый ящик.'); - }); - } + api.userAPI + .savePublicKey('') + .then(() => { + refreshProfile(); + }) + .catch((error) => { + toast.error(error?.message || 'Не удалось удалить почтовый ящик.'); + }); + }, }, { label: 'Отмена', - className: 'cancel' - } + className: 'cancel', + }, ], - overlayClassName: 'orbitar-confirm-overlay' + overlayClassName: 'orbitar-confirm-overlay', }); }; @@ -298,53 +356,93 @@ export const MailboxSettings = observer(() => { e.preventDefault(); if (publicKey) { // Copy to clipboard - navigator.clipboard?.writeText(publicKey) - ?.then(() => toast('В буфере!'))?.catch(); + navigator.clipboard + ?.writeText(publicKey) + ?.then(() => toast('В буфере!')) + ?.catch(); } }; const handleCreatePublicKey = (key: string) => { - api.userAPI.savePublicKey(key).then(() => { - refreshProfile(); - }).catch((error) => { - toast.error(error?.message || 'Не удалось создать почтовый ящик.'); - }).finally(() => { - setCreatingMailbox(false); - }); + api.userAPI + .savePublicKey(key) + .then(() => { + refreshProfile(); + }) + .catch((error) => { + toast.error(error?.message || 'Не удалось создать почтовый ящик.'); + }) + .finally(() => { + setCreatingMailbox(false); + }); }; - return <>{state.status === 'ready' && -
- {publicKey && - <>{/*mailbox exists*/} -
- - Почтовый ящик готов! -
-
- - {!revealPublicKey && } - {revealPublicKey &&
- Публичный ключ:
- {publicKey} -
} -
- || -
{/*Mailbox doesn't exist*/} - -
} -
|| null} - {creatingMailbox &&
setCreatingMailbox(false)} - />
} - ; + return ( + <> + {(state.status === 'ready' && ( +
+ {(publicKey && ( + <> + {/*mailbox exists*/} +
+ + Почтовый ящик готов! +
+
+ + {!revealPublicKey && ( + + )} + {revealPublicKey && ( +
+ Публичный ключ: +
+ + {publicKey} + +
+ )} +
+ + )) || ( +
+ {/*Mailbox doesn't exist*/} + +
+ )} +
+ )) || + null} + {creatingMailbox && ( +
+ setCreatingMailbox(false)} + /> +
+ )} + + ); });