diff --git a/web/avatars/edit-thread-avatar.css b/web/avatars/edit-thread-avatar.css new file mode 100644 index 000000000..c9801e0ed --- /dev/null +++ b/web/avatars/edit-thread-avatar.css @@ -0,0 +1,8 @@ +.editThreadAvatarContainer { + position: relative; + height: 112px; + width: 112px; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/web/avatars/edit-thread-avatar.react.js b/web/avatars/edit-thread-avatar.react.js new file mode 100644 index 000000000..ebc6edc87 --- /dev/null +++ b/web/avatars/edit-thread-avatar.react.js @@ -0,0 +1,35 @@ +// @flow + +import invariant from 'invariant'; +import * as React from 'react'; + +import { EditThreadAvatarContext } from 'lib/components/base-edit-thread-avatar-provider.react.js'; +import type { RawThreadInfo, ThreadInfo } from 'lib/types/thread-types.js'; + +import css from './edit-thread-avatar.css'; +import ThreadAvatar from './thread-avatar.react.js'; + +type Props = { + +threadInfo: RawThreadInfo | ThreadInfo, + +disabled?: boolean, +}; +function EditThreadAvatar(props: Props): React.Node { + const editThreadAvatarContext = React.useContext(EditThreadAvatarContext); + invariant(editThreadAvatarContext, 'editThreadAvatarContext should be set'); + + const { threadAvatarSaveInProgress } = editThreadAvatarContext; + + const { threadInfo } = props; + + return ( +
+ +
+ ); +} + +export default EditThreadAvatar; diff --git a/web/modals/threads/settings/thread-settings-general-tab.react.js b/web/modals/threads/settings/thread-settings-general-tab.react.js index 88568fa03..8255f6cf6 100644 --- a/web/modals/threads/settings/thread-settings-general-tab.react.js +++ b/web/modals/threads/settings/thread-settings-general-tab.react.js @@ -1,200 +1,208 @@ // @flow import * as React from 'react'; import tinycolor from 'tinycolor2'; import { changeThreadSettingsActionTypes, changeThreadSettings, } from 'lib/actions/thread-actions.js'; import { threadHasPermission, chatNameMaxLength, } from 'lib/shared/thread-utils.js'; import { type SetState } from 'lib/types/hook-types.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; import { type ThreadInfo, type ThreadChanges } from 'lib/types/thread-types.js'; import { useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils.js'; import { firstLine } from 'lib/utils/string-utils.js'; import SubmitSection from './submit-section.react.js'; import css from './thread-settings-general-tab.css'; +import EditThreadAvatar from '../../../avatars/edit-thread-avatar.react.js'; import LoadingIndicator from '../../../loading-indicator.react.js'; import Input from '../../input.react.js'; import ColorSelector from '../color-selector.react.js'; type ThreadSettingsGeneralTabProps = { +threadSettingsOperationInProgress: boolean, +threadInfo: ThreadInfo, +threadNamePlaceholder: string, +queuedChanges: ThreadChanges, +setQueuedChanges: SetState, +setErrorMessage: SetState, +errorMessage?: ?string, }; function ThreadSettingsGeneralTab( props: ThreadSettingsGeneralTabProps, ): React.Node { const { threadSettingsOperationInProgress, threadInfo, threadNamePlaceholder, queuedChanges, setQueuedChanges, setErrorMessage, errorMessage, } = props; const dispatchActionPromise = useDispatchActionPromise(); const callChangeThreadSettings = useServerCall(changeThreadSettings); const nameInputRef = React.useRef(); React.useEffect(() => { nameInputRef.current?.focus(); }, [threadSettingsOperationInProgress]); const changeQueued: boolean = React.useMemo( () => Object.values(queuedChanges).some(v => v !== null && v !== undefined), [queuedChanges], ); const onChangeName = React.useCallback( (event: SyntheticEvent) => { const target = event.currentTarget; const newName = firstLine(target.value); setQueuedChanges(prevQueuedChanges => Object.freeze({ ...prevQueuedChanges, name: newName !== threadInfo.name ? newName : undefined, }), ); }, [setQueuedChanges, threadInfo.name], ); const onChangeDescription = React.useCallback( (event: SyntheticEvent) => { const target = event.currentTarget; setQueuedChanges(prevQueuedChanges => Object.freeze({ ...prevQueuedChanges, description: target.value !== threadInfo.description ? target.value : undefined, }), ); }, [setQueuedChanges, threadInfo.description], ); const onChangeColor = React.useCallback( (color: string) => { setQueuedChanges(prevQueuedChanges => Object.freeze({ ...prevQueuedChanges, color: !tinycolor.equals(color, threadInfo.color) ? color : undefined, }), ); }, [setQueuedChanges, threadInfo.color], ); const changeThreadSettingsAction = React.useCallback(async () => { try { setErrorMessage(''); return await callChangeThreadSettings({ threadID: threadInfo.id, changes: queuedChanges, }); } catch (e) { setErrorMessage('unknown_error'); throw e; } finally { setQueuedChanges(Object.freeze({})); } }, [ callChangeThreadSettings, queuedChanges, setErrorMessage, setQueuedChanges, threadInfo.id, ]); const onSubmit = React.useCallback( (event: SyntheticEvent) => { event.preventDefault(); dispatchActionPromise( changeThreadSettingsActionTypes, changeThreadSettingsAction(), ); }, [changeThreadSettingsAction, dispatchActionPromise], ); const threadNameInputDisabled = !threadHasPermission( threadInfo, threadPermissions.EDIT_THREAD_NAME, ); const saveButtonContent = React.useMemo(() => { if (threadSettingsOperationInProgress) { return ; } return 'Save'; }, [threadSettingsOperationInProgress]); + const editingAvatarsOnWebEnabled = false; + let avatarNode; + if (editingAvatarsOnWebEnabled) { + avatarNode = ; + } + return (
+ {avatarNode}
Chat name
Description