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 (
);
}
export default ThreadSettingsGeneralTab;