pl-fe: migrate drafts to mutative

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-11-10 00:05:08 +01:00
parent 4ddc5b38d1
commit 5c58196201
6 changed files with 74 additions and 48 deletions

View file

@ -3,27 +3,40 @@ import { makeGetAccount } from 'pl-fe/selectors';
import KVStore from 'pl-fe/storage/kv-store';
import type { AppDispatch, RootState } from 'pl-fe/store';
import type { APIEntity } from 'pl-fe/types/entities';
const DRAFT_STATUSES_FETCH_SUCCESS = 'DRAFT_STATUSES_FETCH_SUCCESS';
const PERSIST_DRAFT_STATUS = 'PERSIST_DRAFT_STATUS';
const CANCEL_DRAFT_STATUS = 'DELETE_DRAFT_STATUS';
const DRAFT_STATUSES_FETCH_SUCCESS = 'DRAFT_STATUSES_FETCH_SUCCESS' as const;
const PERSIST_DRAFT_STATUS = 'PERSIST_DRAFT_STATUS' as const;
const CANCEL_DRAFT_STATUS = 'DELETE_DRAFT_STATUS' as const;
const getAccount = makeGetAccount();
interface DraftStatusesFetchSuccessAction {
type: typeof DRAFT_STATUSES_FETCH_SUCCESS;
statuses: Array<APIEntity>;
}
const fetchDraftStatuses = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
const accountUrl = getAccount(state, state.me as string)!.url;
return KVStore.getItem(`drafts:${accountUrl}`).then((statuses) => {
dispatch({
return KVStore.getItem<Array<APIEntity>>(`drafts:${accountUrl}`).then((statuses) => {
dispatch<DraftStatusesFetchSuccessAction>({
type: DRAFT_STATUSES_FETCH_SUCCESS,
statuses,
});
}).catch(() => {});
};
interface PersistDraftStatusAction {
type: typeof PERSIST_DRAFT_STATUS;
status: Record<string, any>;
accountUrl: string;
}
const saveDraftStatus = (composeId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
@ -36,25 +49,36 @@ const saveDraftStatus = (composeId: string) =>
draft_id: compose.draft_id || crypto.randomUUID(),
};
dispatch({
dispatch<PersistDraftStatusAction>({
type: PERSIST_DRAFT_STATUS,
status: draft,
accountUrl,
});
};
interface CancelDraftStatusAction {
type: typeof CANCEL_DRAFT_STATUS;
statusId: string;
accountUrl: string;
}
const cancelDraftStatus = (statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
const accountUrl = getAccount(state, state.me as string)!.url;
dispatch({
dispatch<CancelDraftStatusAction>({
type: CANCEL_DRAFT_STATUS,
statusId,
accountUrl,
});
};
type DraftStatusesAction =
| DraftStatusesFetchSuccessAction
| PersistDraftStatusAction
| CancelDraftStatusAction
export {
DRAFT_STATUSES_FETCH_SUCCESS,
PERSIST_DRAFT_STATUS,
@ -62,4 +86,5 @@ export {
fetchDraftStatuses,
saveDraftStatus,
cancelDraftStatus,
type DraftStatusesAction,
};

View file

@ -99,7 +99,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.length);
const interactionRequestsCount = useInteractionRequestsCount().data || 0;
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
const draftCount = useAppSelector((state) => state.draft_statuses.size);
const draftCount = useAppSelector((state) => Object.keys(state.draft_statuses).length);
// const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
const [sidebarVisible, setSidebarVisible] = useState(isSidebarOpen);
const touchStart = useRef(0);

View file

@ -52,7 +52,7 @@ const SidebarNavigation = () => {
const interactionRequestsCount = useInteractionRequestsCount().data || 0;
const dashboardCount = useAppSelector((state) => state.admin.openReports.length + state.admin.awaitingApproval.length);
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
const draftCount = useAppSelector((state) => state.draft_statuses.size);
const draftCount = useAppSelector((state) => Object.keys(state.draft_statuses).length);
const restrictUnauth = instance.pleroma.metadata.restrict_unauthenticated;

View file

@ -8,11 +8,11 @@ import type { DraftStatus } from 'pl-fe/reducers/draft-statuses';
import type { RootState } from 'pl-fe/store';
const buildPoll = (draftStatus: DraftStatus) => {
if (draftStatus.hasIn(['poll', 'options'])) {
if (draftStatus.poll?.options) {
return {
...draftStatus.poll!.toJS(),
...draftStatus.poll,
id: `${draftStatus.draft_id}-poll`,
options: draftStatus.poll!.get('options').map((title: string) => ({ title })).toArray(),
options: draftStatus.poll.options.map((title: string) => ({ title })).toArray(),
};
} else {
return null;

View file

@ -31,7 +31,7 @@ const DraftStatuses = () => {
emptyMessage={emptyMessage}
listClassName='divide-y divide-solid divide-gray-200 dark:divide-gray-800'
>
{drafts.toOrderedSet().reverse().map((draft) => <DraftStatus key={draft.draft_id} draftStatus={draft} />)}
{Object.values(drafts).toReversed().map((draft) => <DraftStatus key={draft.draft_id} draftStatus={draft} />)}
</ScrollableList>
</Column>
);

View file

@ -1,63 +1,64 @@
import { List as ImmutableList, Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, Record as ImmutableRecord, fromJS } from 'immutable';
import { create } from 'mutative';
import { mediaAttachmentSchema } from 'pl-api';
import * as v from 'valibot';
import { COMPOSE_SUBMIT_SUCCESS, type ComposeAction } from 'pl-fe/actions/compose';
import { DRAFT_STATUSES_FETCH_SUCCESS, PERSIST_DRAFT_STATUS, CANCEL_DRAFT_STATUS } from 'pl-fe/actions/draft-statuses';
import { DRAFT_STATUSES_FETCH_SUCCESS, PERSIST_DRAFT_STATUS, CANCEL_DRAFT_STATUS, DraftStatusesAction } from 'pl-fe/actions/draft-statuses';
import { filteredArray } from 'pl-fe/schemas/utils';
import KVStore from 'pl-fe/storage/kv-store';
import type { MediaAttachment } from 'pl-api';
import type { StatusVisibility } from 'pl-fe/normalizers/status';
import type { APIEntity } from 'pl-fe/types/entities';
import type { AnyAction } from 'redux';
const DraftStatusRecord = ImmutableRecord({
content_type: 'text/plain',
draft_id: '',
editorState: null as string | null,
group_id: null as string | null,
in_reply_to: null as string | null,
media_attachments: ImmutableList<MediaAttachment>(),
poll: null as ImmutableMap<string, any> | null,
privacy: 'public' as StatusVisibility,
quote: null as string | null,
schedule: null as Date | null,
sensitive: false,
spoiler: false,
spoiler_text: '',
text: '',
to: ImmutableOrderedSet<string>(),
const draftStatusSchema = v.object({
content_type: v.fallback(v.string(), 'text/plain'),
draft_id: v.string(),
editorState: v.fallback(v.nullable(v.string()), null),
group_id: v.fallback(v.nullable(v.string()), null),
in_reply_to: v.fallback(v.nullable(v.string()), null),
media_attachments: filteredArray(mediaAttachmentSchema),
poll: v.fallback(v.nullable(v.record(v.string(), v.any())), null),
privacy: v.fallback(v.string(), 'public'),
quote: v.fallback(v.nullable(v.string()), null),
schedule: v.fallback(v.nullable(v.string()), null),
sensitive: v.fallback(v.boolean(), false),
spoiler: v.fallback(v.boolean(), false),
spoiler_text: v.fallback(v.string(), ''),
text: v.fallback(v.string(), ''),
});
type DraftStatus = ReturnType<typeof DraftStatusRecord>;
type State = ImmutableMap<string, DraftStatus>;
type DraftStatus = v.InferOutput<typeof draftStatusSchema>;
type State = Record<string, DraftStatus>;
const initialState: State = ImmutableMap();
const initialState: State = {};
const importStatus = (state: State, status: APIEntity) =>
state.set(status.draft_id, DraftStatusRecord(ImmutableMap(fromJS(status))));
const importStatus = (state: State, status: APIEntity) => {
state[status.draft_id] = v.parse(draftStatusSchema, status);
};
const importStatuses = (state: State, statuses: APIEntity[]) =>
state.withMutations(mutable => Object.values(statuses || {}).forEach(status => importStatus(mutable, status)));
const importStatuses = (state: State, statuses: APIEntity[]) => {
Object.values(statuses || {}).forEach(status => importStatus(state, status));
};
const deleteStatus = (state: State, statusId: string) => {
if (statusId) return state.delete(statusId);
if (statusId) delete state[statusId];
return state;
};
const persistState = (state: State, accountUrl: string) => {
KVStore.setItem(`drafts:${accountUrl}`, state.toJS());
KVStore.setItem(`drafts:${accountUrl}`, state);
return state;
};
const scheduled_statuses = (state: State = initialState, action: AnyAction | ComposeAction) => {
const scheduled_statuses = (state: State = initialState, action: DraftStatusesAction | ComposeAction) => {
switch (action.type) {
case DRAFT_STATUSES_FETCH_SUCCESS:
return importStatuses(state, action.statuses);
return create(state, (draft) => importStatuses(draft, action.statuses));
case PERSIST_DRAFT_STATUS:
return persistState(importStatus(state, action.status), action.accountUrl);
return persistState(create(state, (draft) => importStatus(draft, action.status)), action.accountUrl);
case CANCEL_DRAFT_STATUS:
return persistState(deleteStatus(state, action.statusId), action.accountUrl);
return persistState(create(state, (draft) => deleteStatus(draft, action.statusId)), action.accountUrl);
case COMPOSE_SUBMIT_SUCCESS:
return persistState(deleteStatus(state, action.draftId), action.accountUrl);
return action.draftId ? persistState(create(state, (draft) => deleteStatus(draft, action.draftId!)), action.accountUrl) : state;
default:
return state;
}