From e8c89a44156e3bcff8bfb0de5058574ceb4c19bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Nov 2024 15:21:01 +0100 Subject: [PATCH 1/8] pl-fe: remove immutable usage from compose reducer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/src/actions/compose.ts | 10 ++--- packages/pl-fe/src/components/modal-root.tsx | 2 +- .../compose/components/compose-form.tsx | 4 +- .../compose/components/polls/poll-form.tsx | 2 +- .../compose/components/spoiler-input.tsx | 2 +- .../compose/components/upload-form.tsx | 4 +- .../editor/plugins/autosuggest-plugin.tsx | 20 ++++----- packages/pl-fe/src/reducers/compose.ts | 45 +++++++++---------- 8 files changed, 43 insertions(+), 46 deletions(-) diff --git a/packages/pl-fe/src/actions/compose.ts b/packages/pl-fe/src/actions/compose.ts index 051d621c5..d3cddcb7c 100644 --- a/packages/pl-fe/src/actions/compose.ts +++ b/packages/pl-fe/src/actions/compose.ts @@ -318,7 +318,7 @@ const needsDescriptions = (state: RootState, composeId: string) => { const media = state.compose[composeId]!.media_attachments; const missingDescriptionModal = useSettingsStore.getState().settings.missingDescriptionModal; - const hasMissing = media.filter(item => !item.description).size > 0; + const hasMissing = media.filter(item => !item.description).length > 0; return missingDescriptionModal && hasMissing; }; @@ -357,7 +357,7 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) => return; } - if ((!status || !status.length) && media.size === 0) { + if ((!status || !status.length) && media.length === 0) { return; } @@ -392,7 +392,7 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) => status, in_reply_to_id: compose.in_reply_to || undefined, quote_id: compose.quote || undefined, - media_ids: media.map(item => item.id).toArray(), + media_ids: media.map(item => item.id), sensitive: compose.sensitive, spoiler_text: compose.spoiler_text, visibility: compose.privacy, @@ -471,7 +471,7 @@ const uploadCompose = (composeId: string, files: FileList, intl: IntlShape) => const progress = new Array(files.length).fill(0); let total = Array.from(files).reduce((a, v) => a + v.size, 0); - const mediaCount = media ? media.size : 0; + const mediaCount = media ? media.length : 0; if (files.length + mediaCount > attachmentLimit) { toast.error(messages.uploadErrorLimit); @@ -726,7 +726,7 @@ const insertIntoTagHistory = (composeId: string, recognizedTags: Array, tex .map(tag => tag.name); const intersectedOldHistory = oldHistory.filter(name => names.findIndex(newName => newName.toLowerCase() === name.toLowerCase()) === -1); - names.push(...intersectedOldHistory.toJS()); + names.push(...intersectedOldHistory); const newHistory = names.slice(0, 1000); diff --git a/packages/pl-fe/src/components/modal-root.tsx b/packages/pl-fe/src/components/modal-root.tsx index a9839c65c..be0eb10fc 100644 --- a/packages/pl-fe/src/components/modal-root.tsx +++ b/packages/pl-fe/src/components/modal-root.tsx @@ -22,7 +22,7 @@ const checkComposeContent = (compose?: Compose) => !!compose && [ compose.editorState && compose.editorState.length > 0, compose.spoiler_text.length > 0, - compose.media_attachments.size > 0, + compose.media_attachments.length > 0, compose.poll !== null, ].some(check => check === true); diff --git a/packages/pl-fe/src/features/compose/components/compose-form.tsx b/packages/pl-fe/src/features/compose/components/compose-form.tsx index 9475c62d9..ef8149e2a 100644 --- a/packages/pl-fe/src/features/compose/components/compose-form.tsx +++ b/packages/pl-fe/src/features/compose/components/compose-form.tsx @@ -96,7 +96,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab const hasPoll = !!compose.poll; const isEditing = compose.id !== null; - const anyMedia = compose.media_attachments.size > 0; + const anyMedia = compose.media_attachments.length > 0; const [composeFocused, setComposeFocused] = useState(false); @@ -189,7 +189,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab ), [features, id, anyMedia]); - const showModifiers = !condensed && (compose.media_attachments.size || compose.is_uploading || compose.poll?.options.length || compose.schedule); + const showModifiers = !condensed && (compose.media_attachments.length || compose.is_uploading || compose.poll?.options.length || compose.schedule); const composeModifiers = showModifiers && ( diff --git a/packages/pl-fe/src/features/compose/components/polls/poll-form.tsx b/packages/pl-fe/src/features/compose/components/polls/poll-form.tsx index 624050d5f..f2247bd43 100644 --- a/packages/pl-fe/src/features/compose/components/polls/poll-form.tsx +++ b/packages/pl-fe/src/features/compose/components/polls/poll-form.tsx @@ -90,7 +90,7 @@ const Option: React.FC = ({ maxLength={maxChars} value={title} onChange={handleOptionTitleChange} - suggestions={suggestions.toArray()} + suggestions={suggestions} onSuggestionsFetchRequested={onSuggestionsFetchRequested} onSuggestionsClearRequested={onSuggestionsClearRequested} onSuggestionSelected={onSuggestionSelected} diff --git a/packages/pl-fe/src/features/compose/components/spoiler-input.tsx b/packages/pl-fe/src/features/compose/components/spoiler-input.tsx index e11d2449e..628e4326e 100644 --- a/packages/pl-fe/src/features/compose/components/spoiler-input.tsx +++ b/packages/pl-fe/src/features/compose/components/spoiler-input.tsx @@ -37,7 +37,7 @@ const SpoilerInput: React.FC = ({ placeholder={intl.formatMessage(messages.placeholder)} value={value} onChange={handleChangeSpoilerText} - suggestions={suggestions.toArray()} + suggestions={suggestions} onSuggestionsFetchRequested={onSuggestionsFetchRequested} onSuggestionsClearRequested={onSuggestionsClearRequested} onSuggestionSelected={onSuggestionSelected} diff --git a/packages/pl-fe/src/features/compose/components/upload-form.tsx b/packages/pl-fe/src/features/compose/components/upload-form.tsx index 81c2fe8c4..be5d0243e 100644 --- a/packages/pl-fe/src/features/compose/components/upload-form.tsx +++ b/packages/pl-fe/src/features/compose/components/upload-form.tsx @@ -38,13 +38,13 @@ const UploadForm: React.FC = ({ composeId, onSubmit }) => { dragOverItem.current = null; }, [dragItem, dragOverItem]); - if (!isUploading && mediaIds.isEmpty()) return null; + if (!isUploading && !mediaIds.length) return null; return (
- + 0 && 'm-[-5px]')}> {mediaIds.map((id: string) => ( { - const suggestion = suggestions.get(index) as AutoSuggestion; + const suggestion = suggestions[index]; editor.update(() => { dispatch((dispatch, getState) => { @@ -446,11 +446,11 @@ const AutosuggestPlugin = ({ ]); useEffect(() => { - if (suggestions && suggestions.size > 0) setSuggestionsHidden(false); + if (suggestions && suggestions.length > 0) setSuggestionsHidden(false); }, [suggestions]); useEffect(() => { - if (resolution !== null && !suggestionsHidden && !suggestions.isEmpty()) { + if (resolution !== null && !suggestionsHidden && suggestions.length) { const handleClick = (event: MouseEvent) => { const target = event.target as HTMLElement; @@ -462,7 +462,7 @@ const AutosuggestPlugin = ({ return () => document.removeEventListener('click', handleClick); } - }, [resolution, suggestionsHidden, suggestions.isEmpty()]); + }, [resolution, suggestionsHidden, !suggestions.length]); useEffect(() => { if (resolution === null) return; @@ -472,8 +472,8 @@ const AutosuggestPlugin = ({ KEY_ARROW_UP_COMMAND, (payload) => { const event = payload; - if (suggestions !== null && suggestions.size && selectedSuggestion !== null) { - const newSelectedSuggestion = selectedSuggestion !== 0 ? selectedSuggestion - 1 : suggestions.size - 1; + if (suggestions !== null && suggestions.length && selectedSuggestion !== null) { + const newSelectedSuggestion = selectedSuggestion !== 0 ? selectedSuggestion - 1 : suggestions.length - 1; setSelectedSuggestion(newSelectedSuggestion); event.preventDefault(); event.stopImmediatePropagation(); @@ -486,8 +486,8 @@ const AutosuggestPlugin = ({ KEY_ARROW_DOWN_COMMAND, (payload) => { const event = payload; - if (suggestions !== null && suggestions.size && selectedSuggestion !== null) { - const newSelectedSuggestion = selectedSuggestion !== suggestions.size - 1 ? selectedSuggestion + 1 : 0; + if (suggestions !== null && suggestions.length && selectedSuggestion !== null) { + const newSelectedSuggestion = selectedSuggestion !== suggestions.length - 1 ? selectedSuggestion + 1 : 0; setSelectedSuggestion(newSelectedSuggestion); event.preventDefault(); event.stopImmediatePropagation(); @@ -543,8 +543,8 @@ const AutosuggestPlugin = ({
{suggestions.map(renderSuggestion)} diff --git a/packages/pl-fe/src/reducers/compose.ts b/packages/pl-fe/src/reducers/compose.ts index ff0b10df4..090519f6c 100644 --- a/packages/pl-fe/src/reducers/compose.ts +++ b/packages/pl-fe/src/reducers/compose.ts @@ -1,9 +1,8 @@ -import { List as ImmutableList } from 'immutable'; import { create } from 'mutative'; -import { Instance, PLEROMA, type CredentialAccount, type MediaAttachment, type Tag } from 'pl-api'; +import { PLEROMA, type CredentialAccount, type Instance, type MediaAttachment, type Tag } from 'pl-api'; import { INSTANCE_FETCH_SUCCESS, InstanceAction } from 'pl-fe/actions/instance'; -import { isNativeEmoji } from 'pl-fe/features/emoji'; +import { isNativeEmoji, type Emoji } from 'pl-fe/features/emoji'; import { tagHistory } from 'pl-fe/settings'; import { hasIntegerMediaIds } from 'pl-fe/utils/status'; @@ -69,11 +68,9 @@ import { FE_NAME } from '../actions/settings'; import { TIMELINE_DELETE, type TimelineAction } from '../actions/timelines'; import { unescapeHTML } from '../utils/html'; -import type { Emoji } from 'pl-fe/features/emoji'; import type { Language } from 'pl-fe/features/preferences'; import type { Account } from 'pl-fe/normalizers/account'; import type { Status } from 'pl-fe/normalizers/status'; -import type { APIEntity } from 'pl-fe/types/entities'; const getResetFileKey = () => Math.floor((Math.random() * 0x10000)); @@ -109,7 +106,7 @@ interface Compose { is_composing: boolean; is_submitting: boolean; is_uploading: boolean; - media_attachments: ImmutableList; + media_attachments: Array; poll: ComposePoll | null; privacy: string; progress: number; @@ -119,9 +116,9 @@ interface Compose { sensitive: boolean; spoiler_text: string; spoilerTextMap: Partial>; - suggestions: ImmutableList; + suggestions: Array | Array; suggestion_token: string | null; - tagHistory: ImmutableList; + tagHistory: Array; text: string; textMap: Partial>; to: Array; @@ -149,7 +146,7 @@ const newCompose = (params: Partial = {}): Compose => ({ is_composing: false, is_submitting: false, is_uploading: false, - media_attachments: ImmutableList(), + media_attachments: [], poll: null, privacy: 'public', progress: 0, @@ -159,9 +156,9 @@ const newCompose = (params: Partial = {}): Compose => ({ sensitive: false, spoiler_text: '', spoilerTextMap: {}, - suggestions: ImmutableList(), + suggestions: [], suggestion_token: null, - tagHistory: ImmutableList(), + tagHistory: [], text: '', textMap: {}, to: [], @@ -204,7 +201,7 @@ const statusToMentionsAccountIdsArray = (status: Pick { - const prevSize = compose.media_attachments.size; + const prevSize = compose.media_attachments.length; compose.media_attachments.push(media); compose.is_uploading = false; @@ -217,7 +214,7 @@ const appendMedia = (compose: Compose, media: MediaAttachment, defaultSensitive? }; const removeMedia = (compose: Compose, mediaId: string) => { - const prevSize = compose.media_attachments.size; + const prevSize = compose.media_attachments.length; compose.media_attachments = compose.media_attachments.filter(item => item.id !== mediaId); compose.idempotencyKey = crypto.randomUUID(); @@ -235,17 +232,17 @@ const insertSuggestion = (compose: Compose, position: number, token: string | nu compose.poll.options[path[2]] = updateText(compose.poll.options[path[2]]); } compose.suggestion_token = null; - compose.suggestions = ImmutableList(); + compose.suggestions = []; compose.idempotencyKey = crypto.randomUUID(); }; const updateSuggestionTags = (compose: Compose, token: string, tags: Tag[]) => { const prefix = token.slice(1); - compose.suggestions = ImmutableList(tags + compose.suggestions = tags .filter((tag) => tag.name.toLowerCase().startsWith(prefix.toLowerCase())) .slice(0, 4) - .map((tag) => '#' + tag.name)); + .map((tag) => '#' + tag.name); compose.suggestion_token = token; }; @@ -299,7 +296,7 @@ const importAccount = (compose: Compose, account: CredentialAccount) => { if (settings.defaultPrivacy) compose.privacy = settings.defaultPrivacy; if (settings.defaultContentType) compose.content_type = settings.defaultContentType; - compose.tagHistory = ImmutableList(tagHistory.get(account.id)); + compose.tagHistory = tagHistory.get(account.id); }; // const updateSetting = (compose: Compose, path: string[], value: string) => { @@ -493,12 +490,12 @@ const compose = (state = initialState, action: ComposeAction | EventsAction | In }); case COMPOSE_SUGGESTIONS_CLEAR: return updateCompose(state, action.composeId, compose => { - compose.suggestions = compose.suggestions.clear(); + compose.suggestions = []; compose.suggestion_token = null; }); case COMPOSE_SUGGESTIONS_READY: return updateCompose(state, action.composeId, compose => { - compose.suggestions = ImmutableList(action.accounts ? action.accounts.map((item: APIEntity) => item.id) : action.emojis); + compose.suggestions = action.accounts ? action.accounts.map((item) => item.id) : action.emojis || []; compose.suggestion_token = action.token; }); case COMPOSE_SUGGESTION_SELECT: @@ -507,7 +504,7 @@ const compose = (state = initialState, action: ComposeAction | EventsAction | In return updateCompose(state, action.composeId, compose => updateSuggestionTags(compose, action.token, action.tags)); case COMPOSE_TAG_HISTORY_UPDATE: return updateCompose(state, action.composeId, compose => { - compose.tagHistory = ImmutableList(action.tags) as ImmutableList; + compose.tagHistory = action.tags; }); case TIMELINE_DELETE: return updateCompose(state, 'compose-modal', compose => { @@ -550,9 +547,9 @@ const compose = (state = initialState, action: ComposeAction | EventsAction | In compose.group_id = action.status.group_id; if (action.v?.software === PLEROMA && action.withRedraft && hasIntegerMediaIds(action.status)) { - compose.media_attachments = ImmutableList(); + compose.media_attachments = []; } else { - compose.media_attachments = ImmutableList(action.status.media_attachments); + compose.media_attachments = action.status.media_attachments; } if (action.status.spoiler_text.length > 0) { @@ -661,10 +658,10 @@ const compose = (state = initialState, action: ComposeAction | EventsAction | In case COMPOSE_CHANGE_MEDIA_ORDER: return updateCompose(state, action.composeId, compose => { const indexA = compose.media_attachments.findIndex(x => x.id === action.a); - const moveItem = compose.media_attachments.get(indexA)!; + const moveItem = compose.media_attachments[indexA]; const indexB = compose.media_attachments.findIndex(x => x.id === action.b); - return compose.media_attachments.splice(indexA, 1).splice(indexB, 0, moveItem); + compose.media_attachments = compose.media_attachments.splice(indexA, 1).splice(indexB, 0, moveItem); }); case COMPOSE_ADD_SUGGESTED_QUOTE: return updateCompose(state, action.composeId, compose => { From 44a4116a750b122c048d4c5c6eafd12971b075ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Nov 2024 21:18:56 +0100 Subject: [PATCH 2/8] pl-fe: wip migrate pl-fe config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/src/actions/pl-fe.ts | 9 +- packages/pl-fe/src/actions/timelines.ts | 2 +- packages/pl-fe/src/components/navlinks.tsx | 4 +- .../src/components/site-error-boundary.tsx | 12 +- packages/pl-fe/src/features/about/index.tsx | 6 +- .../components/crypto-donate-panel.tsx | 6 +- .../crypto-donate/components/site-wallet.tsx | 2 +- .../components/crypto-address-input.tsx | 2 +- .../components/footer-link-input.tsx | 2 +- .../components/promo-panel-input.tsx | 2 +- .../pl-fe-config/components/site-preview.tsx | 5 +- .../pl-fe/src/features/pl-fe-config/index.tsx | 15 +- .../pl-fe/src/features/theme-editor/index.tsx | 21 +- .../report-modal/steps/confirmation-step.tsx | 6 +- .../ui/components/panels/promo-panel.tsx | 6 +- .../features/ui/components/pending-status.tsx | 2 +- packages/pl-fe/src/features/ui/index.tsx | 2 +- .../ui/util/pending-status-builder.ts | 17 +- packages/pl-fe/src/hooks/use-pl-fe-config.ts | 4 +- packages/pl-fe/src/init/pl-fe-head.tsx | 5 +- packages/pl-fe/src/jest/test-helpers.tsx | 3 +- packages/pl-fe/src/layouts/home-layout.tsx | 4 +- .../src/normalizers/pl-fe/pl-fe-config.ts | 291 +++++++----------- .../pl-fe/src/reducers/pending-statuses.ts | 52 +++- packages/pl-fe/src/types/colors.ts | 2 +- packages/pl-fe/src/types/pl-fe.ts | 20 +- packages/pl-fe/src/utils/tailwind.ts | 35 ++- packages/pl-fe/src/utils/theme.ts | 4 +- 28 files changed, 237 insertions(+), 304 deletions(-) diff --git a/packages/pl-fe/src/actions/pl-fe.ts b/packages/pl-fe/src/actions/pl-fe.ts index 80b0ea886..6a81db1d9 100644 --- a/packages/pl-fe/src/actions/pl-fe.ts +++ b/packages/pl-fe/src/actions/pl-fe.ts @@ -1,7 +1,8 @@ import { createSelector } from 'reselect'; +import * as v from 'valibot'; import { getHost } from 'pl-fe/actions/instance'; -import { normalizePlFeConfig } from 'pl-fe/normalizers/pl-fe/pl-fe-config'; +import { plFeConfigSchema } from 'pl-fe/normalizers/pl-fe/pl-fe-config'; import KVStore from 'pl-fe/storage/kv-store'; import { useSettingsStore } from 'pl-fe/stores/settings'; @@ -17,10 +18,8 @@ const PLFE_CONFIG_REMEMBER_SUCCESS = 'PLFE_CONFIG_REMEMBER_SUCCESS' as const; const getPlFeConfig = createSelector([ (state: RootState) => state.plfe, -], (plfe) => { - // Do some additional normalization with the state - return normalizePlFeConfig(plfe); -}); +// Do some additional normalization with the state +], (plfe) => v.parse(plFeConfigSchema, plfe)); const rememberPlFeConfig = (host: string | null) => (dispatch: AppDispatch) => { diff --git a/packages/pl-fe/src/actions/timelines.ts b/packages/pl-fe/src/actions/timelines.ts index 287c3425f..dfc5f3a12 100644 --- a/packages/pl-fe/src/actions/timelines.ts +++ b/packages/pl-fe/src/actions/timelines.ts @@ -28,7 +28,7 @@ const processTimelineUpdate = (timeline: string, status: BaseStatus) => (dispatch: AppDispatch, getState: () => RootState) => { const me = getState().me; const ownStatus = status.account?.id === me; - const hasPendingStatuses = !getState().pending_statuses.isEmpty(); + const hasPendingStatuses = !!getState().pending_statuses.length; const columnSettings = useSettingsStore.getState().settings.timelines[timeline]; const shouldSkipQueue = shouldFilter({ diff --git a/packages/pl-fe/src/components/navlinks.tsx b/packages/pl-fe/src/components/navlinks.tsx index ee84fd150..8a49c5ef0 100644 --- a/packages/pl-fe/src/components/navlinks.tsx +++ b/packages/pl-fe/src/components/navlinks.tsx @@ -16,7 +16,7 @@ const Navlinks: React.FC = ({ type }) => { return (