frontend-rw #1

Merged
marcin merged 347 commits from frontend-rw into develop 2024-12-05 15:32:18 -08:00
6 changed files with 74 additions and 48 deletions
Showing only changes of commit 5c58196201 - Show all commits

View file

@ -3,27 +3,40 @@ import { makeGetAccount } from 'pl-fe/selectors';
import KVStore from 'pl-fe/storage/kv-store'; import KVStore from 'pl-fe/storage/kv-store';
import type { AppDispatch, RootState } from 'pl-fe/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 DRAFT_STATUSES_FETCH_SUCCESS = 'DRAFT_STATUSES_FETCH_SUCCESS' as const;
const CANCEL_DRAFT_STATUS = 'DELETE_DRAFT_STATUS';
const PERSIST_DRAFT_STATUS = 'PERSIST_DRAFT_STATUS' as const;
const CANCEL_DRAFT_STATUS = 'DELETE_DRAFT_STATUS' as const;
const getAccount = makeGetAccount(); const getAccount = makeGetAccount();
interface DraftStatusesFetchSuccessAction {
type: typeof DRAFT_STATUSES_FETCH_SUCCESS;
statuses: Array<APIEntity>;
}
const fetchDraftStatuses = () => const fetchDraftStatuses = () =>
(dispatch: AppDispatch, getState: () => RootState) => { (dispatch: AppDispatch, getState: () => RootState) => {
const state = getState(); const state = getState();
const accountUrl = getAccount(state, state.me as string)!.url; const accountUrl = getAccount(state, state.me as string)!.url;
return KVStore.getItem(`drafts:${accountUrl}`).then((statuses) => { return KVStore.getItem<Array<APIEntity>>(`drafts:${accountUrl}`).then((statuses) => {
dispatch({ dispatch<DraftStatusesFetchSuccessAction>({
type: DRAFT_STATUSES_FETCH_SUCCESS, type: DRAFT_STATUSES_FETCH_SUCCESS,
statuses, statuses,
}); });
}).catch(() => {}); }).catch(() => {});
}; };
interface PersistDraftStatusAction {
type: typeof PERSIST_DRAFT_STATUS;
status: Record<string, any>;
accountUrl: string;
}
const saveDraftStatus = (composeId: string) => const saveDraftStatus = (composeId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => { (dispatch: AppDispatch, getState: () => RootState) => {
const state = getState(); const state = getState();
@ -36,25 +49,36 @@ const saveDraftStatus = (composeId: string) =>
draft_id: compose.draft_id || crypto.randomUUID(), draft_id: compose.draft_id || crypto.randomUUID(),
}; };
dispatch({ dispatch<PersistDraftStatusAction>({
type: PERSIST_DRAFT_STATUS, type: PERSIST_DRAFT_STATUS,
status: draft, status: draft,
accountUrl, accountUrl,
}); });
}; };
interface CancelDraftStatusAction {
type: typeof CANCEL_DRAFT_STATUS;
statusId: string;
accountUrl: string;
}
const cancelDraftStatus = (statusId: string) => const cancelDraftStatus = (statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => { (dispatch: AppDispatch, getState: () => RootState) => {
const state = getState(); const state = getState();
const accountUrl = getAccount(state, state.me as string)!.url; const accountUrl = getAccount(state, state.me as string)!.url;
dispatch({ dispatch<CancelDraftStatusAction>({
type: CANCEL_DRAFT_STATUS, type: CANCEL_DRAFT_STATUS,
statusId, statusId,
accountUrl, accountUrl,
}); });
}; };
type DraftStatusesAction =
| DraftStatusesFetchSuccessAction
| PersistDraftStatusAction
| CancelDraftStatusAction
export { export {
DRAFT_STATUSES_FETCH_SUCCESS, DRAFT_STATUSES_FETCH_SUCCESS,
PERSIST_DRAFT_STATUS, PERSIST_DRAFT_STATUS,
@ -62,4 +86,5 @@ export {
fetchDraftStatuses, fetchDraftStatuses,
saveDraftStatus, saveDraftStatus,
cancelDraftStatus, 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 followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.length);
const interactionRequestsCount = useInteractionRequestsCount().data || 0; const interactionRequestsCount = useInteractionRequestsCount().data || 0;
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size); 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 dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
const [sidebarVisible, setSidebarVisible] = useState(isSidebarOpen); const [sidebarVisible, setSidebarVisible] = useState(isSidebarOpen);
const touchStart = useRef(0); const touchStart = useRef(0);

View file

@ -52,7 +52,7 @@ const SidebarNavigation = () => {
const interactionRequestsCount = useInteractionRequestsCount().data || 0; const interactionRequestsCount = useInteractionRequestsCount().data || 0;
const dashboardCount = useAppSelector((state) => state.admin.openReports.length + state.admin.awaitingApproval.length); const dashboardCount = useAppSelector((state) => state.admin.openReports.length + state.admin.awaitingApproval.length);
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size); 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; 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'; import type { RootState } from 'pl-fe/store';
const buildPoll = (draftStatus: DraftStatus) => { const buildPoll = (draftStatus: DraftStatus) => {
if (draftStatus.hasIn(['poll', 'options'])) { if (draftStatus.poll?.options) {
return { return {
...draftStatus.poll!.toJS(), ...draftStatus.poll,
id: `${draftStatus.draft_id}-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 { } else {
return null; return null;

View file

@ -31,7 +31,7 @@ const DraftStatuses = () => {
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
listClassName='divide-y divide-solid divide-gray-200 dark:divide-gray-800' 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> </ScrollableList>
</Column> </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 { 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 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 { APIEntity } from 'pl-fe/types/entities';
import type { AnyAction } from 'redux';
const DraftStatusRecord = ImmutableRecord({ const draftStatusSchema = v.object({
content_type: 'text/plain', content_type: v.fallback(v.string(), 'text/plain'),
draft_id: '', draft_id: v.string(),
editorState: null as string | null, editorState: v.fallback(v.nullable(v.string()), null),
group_id: null as string | null, group_id: v.fallback(v.nullable(v.string()), null),
in_reply_to: null as string | null, in_reply_to: v.fallback(v.nullable(v.string()), null),
media_attachments: ImmutableList<MediaAttachment>(), media_attachments: filteredArray(mediaAttachmentSchema),
poll: null as ImmutableMap<string, any> | null, poll: v.fallback(v.nullable(v.record(v.string(), v.any())), null),
privacy: 'public' as StatusVisibility, privacy: v.fallback(v.string(), 'public'),
quote: null as string | null, quote: v.fallback(v.nullable(v.string()), null),
schedule: null as Date | null, schedule: v.fallback(v.nullable(v.string()), null),
sensitive: false, sensitive: v.fallback(v.boolean(), false),
spoiler: false, spoiler: v.fallback(v.boolean(), false),
spoiler_text: '', spoiler_text: v.fallback(v.string(), ''),
text: '', text: v.fallback(v.string(), ''),
to: ImmutableOrderedSet<string>(),
}); });
type DraftStatus = ReturnType<typeof DraftStatusRecord>; type DraftStatus = v.InferOutput<typeof draftStatusSchema>;
type State = ImmutableMap<string, DraftStatus>; type State = Record<string, DraftStatus>;
const initialState: State = ImmutableMap(); const initialState: State = {};
const importStatus = (state: State, status: APIEntity) => const importStatus = (state: State, status: APIEntity) => {
state.set(status.draft_id, DraftStatusRecord(ImmutableMap(fromJS(status)))); state[status.draft_id] = v.parse(draftStatusSchema, status);
};
const importStatuses = (state: State, statuses: APIEntity[]) => const importStatuses = (state: State, statuses: APIEntity[]) => {
state.withMutations(mutable => Object.values(statuses || {}).forEach(status => importStatus(mutable, status))); Object.values(statuses || {}).forEach(status => importStatus(state, status));
};
const deleteStatus = (state: State, statusId: string) => { const deleteStatus = (state: State, statusId: string) => {
if (statusId) return state.delete(statusId); if (statusId) delete state[statusId];
return state; return state;
}; };
const persistState = (state: State, accountUrl: string) => { const persistState = (state: State, accountUrl: string) => {
KVStore.setItem(`drafts:${accountUrl}`, state.toJS()); KVStore.setItem(`drafts:${accountUrl}`, state);
return state; return state;
}; };
const scheduled_statuses = (state: State = initialState, action: AnyAction | ComposeAction) => { const scheduled_statuses = (state: State = initialState, action: DraftStatusesAction | ComposeAction) => {
switch (action.type) { switch (action.type) {
case DRAFT_STATUSES_FETCH_SUCCESS: case DRAFT_STATUSES_FETCH_SUCCESS:
return importStatuses(state, action.statuses); return create(state, (draft) => importStatuses(draft, action.statuses));
case PERSIST_DRAFT_STATUS: 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: 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: 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: default:
return state; return state;
} }