diff --git a/web/avatars/avatar-hooks.react.js b/web/avatars/avatar-hooks.react.js index 9f6d0e8e9..5027fb573 100644 --- a/web/avatars/avatar-hooks.react.js +++ b/web/avatars/avatar-hooks.react.js @@ -1,82 +1,82 @@ // @flow import * as React from 'react'; import { uploadMultimedia, useBlobServiceUpload, } from 'lib/actions/upload-actions.js'; import type { UpdateUserAvatarRequest } from 'lib/types/avatar-types.js'; import { useLegacyAshoatKeyserverCall } from 'lib/utils/action-utils.js'; -import { ashoatKeyserverID } from 'lib/utils/validation-utils.js'; +import { authoritativeKeyserverID } from '../authoritative-keyserver.js'; import { encryptFile } from '../media/encryption-utils.js'; import { generateThumbHash } from '../media/image-utils.js'; import { validateFile } from '../media/media-utils.js'; // TODO: flip the switch const useBlobServiceUploads = false; function useUploadAvatarMedia(): File => Promise { const callUploadMultimedia = useLegacyAshoatKeyserverCall(uploadMultimedia); const callBlobServiceUpload = useBlobServiceUpload(); const uploadAvatarMedia = React.useCallback( async (file: File): Promise => { const validatedFile = await validateFile(file); const { result } = validatedFile; if (!result.success) { throw new Error('Avatar media validation failed.'); } const { file: fixedFile, dimensions } = result; const uploadExtras = { ...dimensions, loop: false, }; if (!useBlobServiceUploads) { const { id } = await callUploadMultimedia(fixedFile, uploadExtras); return { type: 'image', uploadID: id }; } const encryptionResponse = await encryptFile(fixedFile); const { result: encryptionResult } = encryptionResponse; if (!encryptionResult.success) { throw new Error('Avatar media encryption failed.'); } const { file: encryptedFile, sha256Hash: blobHash, encryptionKey, } = encryptionResult; const { result: thumbHashResult } = await generateThumbHash( fixedFile, encryptionKey, ); const thumbHash = thumbHashResult.success ? thumbHashResult.thumbHash : null; const { id } = await callBlobServiceUpload({ uploadInput: { blobInput: { type: 'file', file: encryptedFile, }, blobHash, encryptionKey, dimensions, loop: false, thumbHash, }, - keyserverOrThreadID: ashoatKeyserverID, + keyserverOrThreadID: authoritativeKeyserverID, callbacks: {}, }); return { type: 'encrypted_image', uploadID: id }; }, [callBlobServiceUpload, callUploadMultimedia], ); return uploadAvatarMedia; } export { useUploadAvatarMedia }; diff --git a/web/calendar/entry.react.js b/web/calendar/entry.react.js index 16d938214..c34922691 100644 --- a/web/calendar/entry.react.js +++ b/web/calendar/entry.react.js @@ -1,505 +1,507 @@ // @flow import classNames from 'classnames'; import invariant from 'invariant'; import * as React from 'react'; import { createEntryActionTypes, useCreateEntry, saveEntryActionTypes, useSaveEntry, deleteEntryActionTypes, useDeleteEntry, concurrentModificationResetActionType, } from 'lib/actions/entry-actions.js'; import { type PushModal, useModalContext, } from 'lib/components/modal-provider.react.js'; import { connectionSelector } from 'lib/selectors/keyserver-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import { colorIsDark } from 'lib/shared/color-utils.js'; import { entryKey } from 'lib/shared/entry-utils.js'; import { threadHasPermission } from 'lib/shared/thread-utils.js'; import { type EntryInfo, type CreateEntryInfo, type SaveEntryInfo, type SaveEntryResult, type SaveEntryPayload, type CreateEntryPayload, type DeleteEntryInfo, type DeleteEntryResult, type CalendarQuery, } from 'lib/types/entry-types.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import type { ResolvedThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import type { Dispatch } from 'lib/types/redux-types.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; import { dateString } from 'lib/utils/date-utils.js'; import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js'; import { ServerError } from 'lib/utils/errors.js'; import { useDispatchActionPromise, type DispatchActionPromise, } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; -import { ashoatKeyserverID } from 'lib/utils/validation-utils.js'; import css from './calendar.css'; +import { authoritativeKeyserverID } from '../authoritative-keyserver.js'; import LoadingIndicator from '../loading-indicator.react.js'; import LogInFirstModal from '../modals/account/log-in-first-modal.react.js'; import ConcurrentModificationModal from '../modals/concurrent-modification-modal.react.js'; import HistoryModal from '../modals/history/history-modal.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { nonThreadCalendarQuery } from '../selectors/nav-selectors.js'; import { HistoryVector, DeleteVector } from '../vectors.react.js'; type BaseProps = { +innerRef: (key: string, me: Entry) => void, +entryInfo: EntryInfo, +focusOnFirstEntryNewerThan: (time: number) => void, +tabIndex: number, }; type Props = { ...BaseProps, +threadInfo: ResolvedThreadInfo, +loggedIn: boolean, +calendarQuery: () => CalendarQuery, +online: boolean, +dispatch: Dispatch, +dispatchActionPromise: DispatchActionPromise, +createEntry: (info: CreateEntryInfo) => Promise, +saveEntry: (info: SaveEntryInfo) => Promise, +deleteEntry: (info: DeleteEntryInfo) => Promise, +pushModal: PushModal, +popModal: () => void, }; type State = { +focused: boolean, +loadingStatus: LoadingStatus, +text: string, }; class Entry extends React.PureComponent { textarea: ?HTMLTextAreaElement; creating: boolean; needsUpdateAfterCreation: boolean; needsDeleteAfterCreation: boolean; nextSaveAttemptIndex: number; mounted: boolean; currentlySaving: ?string; constructor(props: Props) { super(props); this.state = { focused: false, loadingStatus: 'inactive', text: props.entryInfo.text, }; this.creating = false; this.needsUpdateAfterCreation = false; this.needsDeleteAfterCreation = false; this.nextSaveAttemptIndex = 0; } guardedSetState(input: Partial) { if (this.mounted) { this.setState(input); } } componentDidMount() { this.mounted = true; this.props.innerRef(entryKey(this.props.entryInfo), this); this.updateHeight(); // Whenever a new Entry is created, focus on it if (!this.props.entryInfo.id) { this.focus(); } } componentDidUpdate(prevProps: Props) { if ( !this.state.focused && this.props.entryInfo.text !== this.state.text && this.props.entryInfo.text !== prevProps.entryInfo.text ) { this.setState({ text: this.props.entryInfo.text }); this.currentlySaving = null; } if ( this.props.online && !prevProps.online && this.state.loadingStatus === 'error' ) { this.save(); } } focus() { invariant( this.textarea instanceof HTMLTextAreaElement, 'textarea ref not set', ); this.textarea.focus(); } onMouseDown: (event: SyntheticEvent) => void = event => { if (this.state.focused && event.target !== this.textarea) { // Don't lose focus when some non-textarea part is clicked event.preventDefault(); } }; componentWillUnmount() { this.mounted = false; } updateHeight: () => void = () => { invariant( this.textarea instanceof HTMLTextAreaElement, 'textarea ref not set', ); this.textarea.style.height = 'auto'; this.textarea.style.height = this.textarea.scrollHeight + 'px'; }; render(): React.Node { let actionLinks = null; if (this.state.focused) { let historyButton = null; if (this.props.entryInfo.id) { historyButton = ( History ); } const rightActionLinksClassName = `${css.rightActionLinks} ${css.actionLinksText}`; actionLinks = (
Delete {historyButton} {this.props.threadInfo.uiName}
); } const darkColor = colorIsDark(this.props.threadInfo.color); const entryClasses = classNames({ [css.entry]: true, [css.darkEntry]: darkColor, [css.focusedEntry]: this.state.focused, }); const style = { backgroundColor: `#${this.props.threadInfo.color}` }; const loadingIndicatorColor = darkColor ? 'white' : 'black'; const canEditEntry = threadHasPermission( this.props.threadInfo, threadPermissions.EDIT_ENTRIES, ); return (