pl-hooks migration works

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-10-28 14:58:54 +01:00
parent 92497207a8
commit 742e66c72b
14 changed files with 46 additions and 369 deletions

View file

@ -504,7 +504,7 @@
}
&__inner {
@apply flex rtl:space-x-reverse items-center space-x-1 grow;
@apply flex items-center gap-x-1 grow;
p {
@apply font-normal;

View file

@ -1,59 +0,0 @@
import { importEntities } from 'pl-hooks';
import client from 'bigbuffet/client';
import type { AppDispatch, RootState } from 'bigbuffet/store';
import type { StatusEdit } from 'pl-api';
const HISTORY_FETCH_REQUEST = 'HISTORY_FETCH_REQUEST' as const;
const HISTORY_FETCH_SUCCESS = 'HISTORY_FETCH_SUCCESS' as const;
const HISTORY_FETCH_FAIL = 'HISTORY_FETCH_FAIL' as const;
const fetchHistory = (statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const loading = getState().history.get(statusId)?.loading;
if (loading) {
return;
}
dispatch(fetchHistoryRequest(statusId));
return client.statuses.getStatusHistory(statusId).then(data => {
importEntities({ accounts: data.map((x) => x.account) });
dispatch(fetchHistorySuccess(statusId, data));
}).catch(error => dispatch(fetchHistoryFail(statusId, error)));
};
const fetchHistoryRequest = (statusId: string) => ({
type: HISTORY_FETCH_REQUEST,
statusId,
});
const fetchHistorySuccess = (statusId: string, history: Array<StatusEdit>) => ({
type: HISTORY_FETCH_SUCCESS,
statusId,
history,
});
const fetchHistoryFail = (statusId: string, error: unknown) => ({
type: HISTORY_FETCH_FAIL,
statusId,
error,
});
type HistoryAction =
| ReturnType<typeof fetchHistoryRequest>
| ReturnType<typeof fetchHistorySuccess>
| ReturnType<typeof fetchHistoryFail>;
export {
HISTORY_FETCH_REQUEST,
HISTORY_FETCH_SUCCESS,
HISTORY_FETCH_FAIL,
fetchHistory,
fetchHistoryRequest,
fetchHistorySuccess,
fetchHistoryFail,
type HistoryAction,
};

View file

@ -1,17 +1,13 @@
import type { DirectoryAction } from './directory';
import type { HashtagsAction } from './hashtags';
import type { HistoryAction } from './history';
import type { SearchAction } from './search';
import type { StatusQuotesAction } from './status-quotes';
import type { StatusesAction } from './statuses';
import type { TimelineAction } from './timelines';
type ActionType =
| DirectoryAction
| HashtagsAction
| HistoryAction
| SearchAction
| StatusQuotesAction
| StatusesAction
| TimelineAction;

View file

@ -1,10 +0,0 @@
import client from 'bigbuffet/client';
const remoteInteraction = (ap_id: string, profile: string) =>
client.accounts.remoteInteraction(ap_id, profile)
.then((data) => data.url)
.catch(error => {
throw error;
});
export { remoteInteraction };

View file

@ -1,135 +0,0 @@
import { importEntities } from 'pl-hooks';
import client from 'bigbuffet/client';
import type { AppDispatch, RootState } from 'bigbuffet/store';
import type { Status as BaseStatus, PaginatedResponse } from 'pl-api';
const STATUS_QUOTES_FETCH_REQUEST = 'STATUS_QUOTES_FETCH_REQUEST';
const STATUS_QUOTES_FETCH_SUCCESS = 'STATUS_QUOTES_FETCH_SUCCESS';
const STATUS_QUOTES_FETCH_FAIL = 'STATUS_QUOTES_FETCH_FAIL';
const STATUS_QUOTES_EXPAND_REQUEST = 'STATUS_QUOTES_EXPAND_REQUEST';
const STATUS_QUOTES_EXPAND_SUCCESS = 'STATUS_QUOTES_EXPAND_SUCCESS';
const STATUS_QUOTES_EXPAND_FAIL = 'STATUS_QUOTES_EXPAND_FAIL';
const noOp = () => new Promise(f => f(null));
interface FetchStatusQuotesRequestAction {
type: typeof STATUS_QUOTES_FETCH_REQUEST;
statusId: string;
}
interface FetchStatusQuotesSuccessAction {
type: typeof STATUS_QUOTES_FETCH_SUCCESS;
statusId: string;
statuses: Array<BaseStatus>;
next: (() => Promise<PaginatedResponse<BaseStatus>>) | null;
}
interface FetchStatusQuotesFailAction {
type: typeof STATUS_QUOTES_FETCH_FAIL;
statusId: string;
error: unknown;
}
const fetchStatusQuotes = (statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (getState().status_lists.get(`quotes:${statusId}`)?.isLoading) {
return dispatch(noOp);
}
const action: FetchStatusQuotesRequestAction = { type: STATUS_QUOTES_FETCH_REQUEST, statusId };
dispatch(action);
return client.statuses.getStatusQuotes(statusId).then(response => {
importEntities({ statuses: response.items });
const action: FetchStatusQuotesSuccessAction = {
type: STATUS_QUOTES_FETCH_SUCCESS,
statusId,
statuses: response.items,
next: response.next,
};
return dispatch(action);
}).catch(error => {
const action: FetchStatusQuotesFailAction = {
type: STATUS_QUOTES_FETCH_FAIL,
statusId,
error,
};
dispatch(action);
});
};
interface ExpandStatusQuotesRequestAction {
type: typeof STATUS_QUOTES_EXPAND_REQUEST;
statusId: string;
}
interface ExpandStatusQuotesSuccessAction {
type: typeof STATUS_QUOTES_EXPAND_SUCCESS;
statusId: string;
statuses: Array<BaseStatus>;
next: (() => Promise<PaginatedResponse<BaseStatus>>) | null;
}
interface ExpandStatusQuotesFailAction {
type: typeof STATUS_QUOTES_EXPAND_FAIL;
statusId: string;
error: unknown;
}
const expandStatusQuotes = (statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const next = getState().status_lists.get(`quotes:${statusId}`)?.next || null;
if (next === null || getState().status_lists.get(`quotes:${statusId}`)?.isLoading) {
return dispatch(noOp);
}
const action: ExpandStatusQuotesRequestAction = {
type: STATUS_QUOTES_EXPAND_REQUEST,
statusId,
};
dispatch(action);
return next().then(response => {
importEntities({ statuses: response.items });
const action: ExpandStatusQuotesSuccessAction = {
type: STATUS_QUOTES_EXPAND_SUCCESS,
statusId,
statuses: response.items,
next: response.next,
};
dispatch(action);
}).catch(error => {
const action: ExpandStatusQuotesFailAction = {
type: STATUS_QUOTES_EXPAND_FAIL,
statusId,
error,
};
dispatch(action);
});
};
type StatusQuotesAction =
| FetchStatusQuotesRequestAction
| FetchStatusQuotesSuccessAction
| FetchStatusQuotesFailAction
| ExpandStatusQuotesRequestAction
| ExpandStatusQuotesSuccessAction
| ExpandStatusQuotesFailAction;
export {
STATUS_QUOTES_FETCH_REQUEST,
STATUS_QUOTES_FETCH_SUCCESS,
STATUS_QUOTES_FETCH_FAIL,
STATUS_QUOTES_EXPAND_REQUEST,
STATUS_QUOTES_EXPAND_SUCCESS,
STATUS_QUOTES_EXPAND_FAIL,
fetchStatusQuotes,
expandStatusQuotes,
type StatusQuotesAction,
};

View file

@ -1,44 +1,33 @@
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
import debounce from 'lodash/debounce';
import { useStatusQuotes } from 'pl-hooks';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useParams } from 'react-router-dom';
import { expandStatusQuotes, fetchStatusQuotes } from 'bigbuffet/actions/status-quotes';
import StatusList from 'bigbuffet/components/status-list';
import Column from 'bigbuffet/components/ui/column';
import { useAppDispatch } from 'bigbuffet/hooks/useAppDispatch';
import { useAppSelector } from 'bigbuffet/hooks/useAppSelector';
const messages = defineMessages({
heading: { id: 'column.quotes', defaultMessage: 'Post quotes' },
});
const handleLoadMore = debounce((statusId: string, dispatch: React.Dispatch<any>) =>
dispatch(expandStatusQuotes(statusId)), 300, { leading: true });
const Quotes: React.FC = () => {
const dispatch = useAppDispatch();
const intl = useIntl();
const { statusId } = useParams<{ statusId: string }>();
const statusIds = useAppSelector((state) => state.status_lists.getIn([`quotes:${statusId}`, 'items'], ImmutableOrderedSet<string>()));
const isLoading = useAppSelector((state) => state.status_lists.getIn([`quotes:${statusId}`, 'isLoading'], true));
const hasMore = useAppSelector((state) => !!state.status_lists.getIn([`quotes:${statusId}`, 'next']));
const { data, isLoading, hasNextPage, fetchNextPage } = useStatusQuotes(statusId);
React.useEffect(() => {
dispatch(fetchStatusQuotes(statusId));
}, [statusId]);
const statusIds = ImmutableOrderedSet(data);
const emptyMessage = <FormattedMessage id='empty_column.quotes' defaultMessage='This post has not been quoted yet.' />;
return (
<Column label={intl.formatMessage(messages.heading)} transparent>
<StatusList
statusIds={statusIds as ImmutableOrderedSet<string>}
hasMore={hasMore}
statusIds={statusIds}
hasMore={hasNextPage}
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
onLoadMore={() => handleLoadMore(statusId, dispatch)}
onLoadMore={() => fetchNextPage()}
emptyMessage={emptyMessage}
divideType='space'
/>

View file

@ -1,15 +1,13 @@
import clsx from 'clsx';
import React, { useEffect } from 'react';
import { useStatusHistory } from 'pl-hooks';
import React from 'react';
import { FormattedDate, FormattedMessage } from 'react-intl';
import { fetchHistory } from 'bigbuffet/actions/history';
import AttachmentThumbs from 'bigbuffet/components/attachment-thumbs';
import { ParsedContent } from 'bigbuffet/components/parsed-content';
import Modal from 'bigbuffet/components/ui/modal';
import Spinner from 'bigbuffet/components/ui/spinner';
import Emojify from 'bigbuffet/features/emoji';
import { useAppDispatch } from 'bigbuffet/hooks/useAppDispatch';
import { useAppSelector } from 'bigbuffet/hooks/useAppSelector';
import { BaseModalProps } from '../modal-root';
@ -18,28 +16,22 @@ interface CompareHistoryModalProps {
}
const CompareHistoryModal: React.FC<BaseModalProps & CompareHistoryModalProps> = ({ onClose, statusId }) => {
const dispatch = useAppDispatch();
const loading = useAppSelector(state => state.history.get(statusId)?.loading);
const versions = useAppSelector(state => state.history.get(statusId)?.items);
const { isLoading, data: versions } = useStatusHistory(statusId);
const onClickClose = () => {
onClose('COMPARE_HISTORY');
};
useEffect(() => {
dispatch(fetchHistory(statusId));
}, [statusId]);
let body: JSX.Element;
if (loading) {
if (isLoading) {
body = <Spinner />;
} else {
body = (
<div className='compare-history'>
{versions?.map((version) => {
const poll = typeof version.poll !== 'string' && version.poll;
const poll = version.poll;
return (
<div className='compare-history__version'>

View file

@ -1,8 +1,7 @@
import { useAccount } from 'pl-hooks';
import { useAccount, usePlHooksApiClient } from 'pl-hooks';
import React, { useState } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { remoteInteraction } from 'bigbuffet/actions/interactions';
import Input from 'bigbuffet/components/ui/input';
import Modal from 'bigbuffet/components/ui/modal';
import toast from 'bigbuffet/toast';
@ -27,6 +26,7 @@ interface UnauthorizedModalProps {
/** Modal to display when a logged-out user tries to do something that requires login. */
const UnauthorizedModal: React.FC<BaseModalProps & UnauthorizedModalProps> = ({ action, onClose, account: accountId, ap_id: apId }) => {
const intl = useIntl();
const { client } = usePlHooksApiClient();
const accountQuery = useAccount(accountId);
@ -41,8 +41,8 @@ const UnauthorizedModal: React.FC<BaseModalProps & UnauthorizedModalProps> = ({
};
const onSubmit = () => {
remoteInteraction(apId!, account)
.then(url => {
client.accounts.remoteInteraction(apId!, account)
.then(({ url }) => {
window.open(url, '_new', 'noopener,noreferrer');
onClose('UNAUTHORIZED');
})

View file

@ -1,35 +0,0 @@
import { List as ImmutableList, Map as ImmutableMap, Record as ImmutableRecord } from 'immutable';
import { HISTORY_FETCH_REQUEST, HISTORY_FETCH_SUCCESS, HISTORY_FETCH_FAIL, type HistoryAction } from 'bigbuffet/actions/history';
import type { StatusEdit } from 'pl-api';
const HistoryRecord = ImmutableRecord({
loading: false,
items: ImmutableList<StatusEdit>(),
});
type State = ImmutableMap<string, ReturnType<typeof HistoryRecord>>;
const initialState: State = ImmutableMap();
const history = (state: State = initialState, action: HistoryAction) => {
switch (action.type) {
case HISTORY_FETCH_REQUEST:
return state.update(action.statusId, HistoryRecord(), history => history!.withMutations(map => {
map.set('loading', true);
map.set('items', ImmutableList());
}));
case HISTORY_FETCH_SUCCESS:
return state.update(action.statusId, HistoryRecord(), history => history!.withMutations(map => {
map.set('loading', false);
map.set('items', ImmutableList(action.history.map((x, i: number) => ({ ...x, original: i === 0 })).toReversed()));
}));
case HISTORY_FETCH_FAIL:
return state.update(action.statusId, HistoryRecord(), history => history!.set('loading', false));
default:
return state;
}
};
export default history;

View file

@ -2,18 +2,14 @@ import { combineReducers } from '@reduxjs/toolkit';
import contexts from './contexts';
import hashtags from './hashtags';
import history from './history';
import search from './search';
import status_lists from './status-lists';
import timelines from './timelines';
import user_lists from './user-lists';
const reducers = {
contexts,
hashtags,
history,
search,
status_lists,
timelines,
user_lists,
};

View file

@ -1,74 +0,0 @@
import {
Map as ImmutableMap,
OrderedSet as ImmutableOrderedSet,
Record as ImmutableRecord,
} from 'immutable';
import {
STATUS_QUOTES_EXPAND_FAIL,
STATUS_QUOTES_EXPAND_REQUEST,
STATUS_QUOTES_EXPAND_SUCCESS,
STATUS_QUOTES_FETCH_FAIL,
STATUS_QUOTES_FETCH_REQUEST,
STATUS_QUOTES_FETCH_SUCCESS,
type StatusQuotesAction,
} from 'bigbuffet/actions/status-quotes';
import type { PaginatedResponse, Status } from 'pl-api';
export const StatusListRecord = ImmutableRecord({
next: null as (() => Promise<PaginatedResponse<Status>>) | null,
loaded: false,
isLoading: null as boolean | null,
items: ImmutableOrderedSet<string>(),
});
type State = ImmutableMap<string, StatusList>;
type StatusList = ReturnType<typeof StatusListRecord>;
const initialState: State = ImmutableMap({});
const getStatusId = (status: string | Pick<Status, 'id'>) => typeof status === 'string' ? status : status.id;
const getStatusIds = (statuses: Array<string | Pick<Status, 'id'>> = []) => (
ImmutableOrderedSet(statuses.map(getStatusId))
);
const setLoading = (state: State, listType: string, loading: boolean) => state.setIn([listType, 'isLoading'], loading);
const normalizeList = (state: State, listType: string, statuses: Array<string | Pick<Status, 'id'>>, next: (() => Promise<PaginatedResponse<Status>>) | null) =>
state.update(listType, StatusListRecord(), listMap => listMap.withMutations(map => {
map.set('next', next);
map.set('loaded', true);
map.set('isLoading', false);
map.set('items', getStatusIds(statuses));
}));
const appendToList = (state: State, listType: string, statuses: Array<string | Pick<Status, 'id'>>, next: (() => Promise<PaginatedResponse<Status>>) | null) => {
const newIds = getStatusIds(statuses);
return state.update(listType, StatusListRecord(), listMap => listMap.withMutations(map => {
map.set('next', next);
map.set('isLoading', false);
map.update('items', items => items.union(newIds));
}));
};
const statusLists = (state = initialState, action: StatusQuotesAction) => {
switch (action.type) {
case STATUS_QUOTES_FETCH_REQUEST:
case STATUS_QUOTES_EXPAND_REQUEST:
return setLoading(state, `quotes:${action.statusId}`, true);
case STATUS_QUOTES_FETCH_FAIL:
case STATUS_QUOTES_EXPAND_FAIL:
return setLoading(state, `quotes:${action.statusId}`, false);
case STATUS_QUOTES_FETCH_SUCCESS:
return normalizeList(state, `quotes:${action.statusId}`, action.statuses, action.next);
case STATUS_QUOTES_EXPAND_SUCCESS:
return appendToList(state, `quotes:${action.statusId}`, action.statuses, action.next);
default:
return state;
}
};
export default statusLists;

View file

@ -1,18 +1,27 @@
.account-card {
@apply flex shrink-0 items-center justify-between w-full;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
width: 100%;
&__container {
@apply shrink-0 block w-full;
flex-shrink: 0;
display: block;
width: 100%;
}
&__account {
@apply flex rtl:space-x-reverse items-center space-x-3 overflow-hidden;
display: flex;
align-items: center;
gap: 0.75rem;
overflow: hidden;
&__name {
@apply grow overflow-hidden;
&__display-name {
@apply flex rtl:space-x-reverse items-center space-x-1 grow;
@apply flex rtl:space-x-reverse items-center gap-x-1 grow;
p {
@apply truncate text-sm text-gray-900 dark:text-gray-100 font-semibold tracking-normal font-sans normal-case;
@ -138,7 +147,8 @@
}
&__name {
@apply flex flex-col;
display: flex;
flex-direction: column;
}
&__display-name {
@ -312,11 +322,12 @@
@apply flex flex-col space-y-2;
&__container {
@apply relative;
position: relative;
}
&__header {
@apply flex flex-col;
display: flex;
flex-direction: column;
}
&__banner {
@ -340,7 +351,8 @@
}
&__name {
@apply flex flex-col;
display: flex;
flex-direction: column;
}
&__display-name {

View file

@ -4,7 +4,8 @@
}
&__details {
@apply flex flex-col;
display: flex;
flex-direction: column;
}
&__name {
@ -20,15 +21,18 @@
}
&__sparklines {
@apply w-[2.5rem];
width: 2.5rem;
}
}
.hashtags-panel {
@apply flex flex-col space-y-2;
display: flex;
flex-direction: column;
gap: 0.5rem;
.hashtag-link {
@apply flex flex-col;
display: flex;
flex-direction: column;
> p:first-child {
@apply text-base leading-5 text-gray-900 dark:text-gray-100 font-normal tracking-normal font-sans normal-case;

View file

@ -246,7 +246,8 @@
.poll {
> div {
@apply flex flex-col;
display: flex;
flex-direction: column;
}
&-option {