pl-fe: migrate shoutbox away from redux, this might break stuff
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -1,84 +0,0 @@
|
||||
import { getClient } from 'pl-fe/api';
|
||||
|
||||
import { importEntities } from './importer';
|
||||
import { getMeUrl } from './me';
|
||||
|
||||
import type { PlApiClient, ShoutMessage } from 'pl-api';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
|
||||
const SHOUTBOX_MESSAGE_IMPORT = 'SHOUTBOX_MESSAGE_IMPORT' as const;
|
||||
const SHOUTBOX_MESSAGES_IMPORT = 'SHOUTBOX_MESSAGES_IMPORT' as const;
|
||||
const SHOUTBOX_CONNECT = 'SHOUTBOX_CONNECT' as const;
|
||||
|
||||
const importShoutboxMessages = (messages: ShoutMessage[]) => (dispatch: AppDispatch): ShoutboxAction => {
|
||||
dispatch(importEntities({ accounts: messages.map((message) => message.author) }, { override: false }));
|
||||
|
||||
return dispatch({
|
||||
type: SHOUTBOX_MESSAGES_IMPORT,
|
||||
messages,
|
||||
});
|
||||
};
|
||||
|
||||
const importShoutboxMessage = (message: ShoutMessage) => (dispatch: AppDispatch): ShoutboxAction => {
|
||||
dispatch(importEntities({ accounts: [message.author] }, { override: false }));
|
||||
|
||||
return dispatch({
|
||||
type: SHOUTBOX_MESSAGE_IMPORT,
|
||||
message,
|
||||
});
|
||||
};
|
||||
|
||||
const createShoutboxMessage = (message: string) => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const socket = getState().shoutbox.socket;
|
||||
|
||||
if (!socket) return;
|
||||
|
||||
socket.message(message);
|
||||
};
|
||||
|
||||
const connectShoutbox = () => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const accountUrl = getMeUrl(state);
|
||||
|
||||
if (!accountUrl) return;
|
||||
|
||||
const client = getClient(state);
|
||||
|
||||
return client.settings.verifyCredentials().then((account) => {
|
||||
if (account.__meta.pleroma?.chat_token) {
|
||||
const socket = client.shoutbox.connect(account.__meta.pleroma?.chat_token, {
|
||||
onMessage: (message) => dispatch(importShoutboxMessage(message)),
|
||||
onMessages: (messages) => dispatch(importShoutboxMessages(messages)),
|
||||
});
|
||||
return dispatch({
|
||||
type: SHOUTBOX_CONNECT,
|
||||
socket,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
type ShoutboxAction =
|
||||
| {
|
||||
type: typeof SHOUTBOX_CONNECT;
|
||||
socket: ReturnType<(InstanceType<typeof PlApiClient>)['shoutbox']['connect']>;
|
||||
}
|
||||
| {
|
||||
type: typeof SHOUTBOX_MESSAGE_IMPORT;
|
||||
message: ShoutMessage;
|
||||
}
|
||||
| {
|
||||
type: typeof SHOUTBOX_MESSAGES_IMPORT;
|
||||
messages: ShoutMessage[];
|
||||
}
|
||||
|
||||
export {
|
||||
SHOUTBOX_MESSAGES_IMPORT,
|
||||
SHOUTBOX_MESSAGE_IMPORT,
|
||||
SHOUTBOX_CONNECT,
|
||||
importShoutboxMessages,
|
||||
importShoutboxMessage,
|
||||
connectShoutbox,
|
||||
createShoutboxMessage,
|
||||
type ShoutboxAction,
|
||||
};
|
||||
@ -7,9 +7,9 @@ import Avatar from 'pl-fe/components/ui/avatar';
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { useInstance } from 'pl-fe/hooks/use-instance';
|
||||
import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config';
|
||||
import { useShoutboxStore } from 'pl-fe/stores/shoutbox';
|
||||
|
||||
import type { Chat } from 'pl-api';
|
||||
|
||||
@ -20,7 +20,7 @@ interface IChatListShoutboxInterface {
|
||||
const ChatListShoutbox: React.FC<IChatListShoutboxInterface> = ({ onClick }) => {
|
||||
const instance = useInstance();
|
||||
const { logo } = usePlFeConfig();
|
||||
const messages = useAppSelector((state) => state.shoutbox.messages);
|
||||
const messages = useShoutboxStore().messages;
|
||||
|
||||
const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
|
||||
@ -6,8 +6,8 @@ import PullToRefresh from 'pl-fe/components/pull-to-refresh';
|
||||
import Spinner from 'pl-fe/components/ui/spinner';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import PlaceholderChat from 'pl-fe/features/placeholder/components/placeholder-chat';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { useChats } from 'pl-fe/queries/chats';
|
||||
import { useShoutboxStore } from 'pl-fe/stores/shoutbox';
|
||||
|
||||
import ChatListItem from './chat-list-item';
|
||||
import ChatListShoutbox from './chat-list-shoutbox';
|
||||
@ -20,7 +20,8 @@ interface IChatList {
|
||||
}
|
||||
|
||||
const ChatList: React.FC<IChatList> = ({ onClickChat, useWindowScroll = false }) => {
|
||||
const showShoutbox = useAppSelector((state) => !state.shoutbox.isLoading);
|
||||
const showShoutbox = !useShoutboxStore().isLoading;
|
||||
|
||||
const { chatsQuery: { data: chats, isFetching, hasNextPage, fetchNextPage, refetch } } = useChats();
|
||||
|
||||
const allChats: Array<Chat | 'shoutbox'> | undefined = showShoutbox ? ['shoutbox', ...(chats || [])] : chats;
|
||||
|
||||
@ -4,8 +4,8 @@ import { FormattedMessage } from 'react-intl';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import { ChatWidgetScreens, useChatContext } from 'pl-fe/contexts/chat-context';
|
||||
import { useStatContext } from 'pl-fe/contexts/stat-context';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { useChats } from 'pl-fe/queries/chats';
|
||||
import { useShoutboxStore } from 'pl-fe/stores/shoutbox';
|
||||
|
||||
import ChatList from '../chat-list';
|
||||
import ChatSearch from '../chat-search/chat-search';
|
||||
@ -21,7 +21,7 @@ import type { Chat } from 'pl-api';
|
||||
|
||||
const ChatPane = () => {
|
||||
const { unreadChatsCount } = useStatContext();
|
||||
const showShoutbox = useAppSelector((state) => !state.shoutbox.isLoading);
|
||||
const showShoutbox = !useShoutboxStore().isLoading;
|
||||
|
||||
const { screen, changeScreen, isOpen, toggleChatPane } = useChatContext();
|
||||
const { chatsQuery: { data: chats, isLoading } } = useChats();
|
||||
|
||||
@ -13,10 +13,11 @@ import Text from 'pl-fe/components/ui/text';
|
||||
import Emojify from 'pl-fe/features/emoji/emojify';
|
||||
import PlaceholderChatMessage from 'pl-fe/features/placeholder/components/placeholder-chat-message';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { useShoutboxStore, type ShoutMessage } from 'pl-fe/stores/shoutbox';
|
||||
|
||||
import { ChatMessageListList, ChatMessageListScroller } from './chat-message-list';
|
||||
|
||||
import type { ShoutMessage } from 'pl-fe/reducers/shoutbox';
|
||||
import type { } from 'pl-fe/reducers/shoutbox';
|
||||
|
||||
const START_INDEX = 10000;
|
||||
|
||||
@ -99,7 +100,7 @@ const ShoutboxMessageList: React.FC = () => {
|
||||
const [firstItemIndex, setFirstItemIndex] = useState(START_INDEX - 20);
|
||||
|
||||
const me = useAppSelector(state => state.me);
|
||||
const { isLoading, messages: shoutboxMessages } = useAppSelector(state => state.shoutbox);
|
||||
const { messages: shoutboxMessages = [], isLoading } = useShoutboxStore();
|
||||
|
||||
const lastShoutboxMessage = shoutboxMessages?.at(-1) || null;
|
||||
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { MutableRefObject, useEffect, useState } from 'react';
|
||||
|
||||
import { createShoutboxMessage } from 'pl-fe/actions/shoutbox';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useCreateShoutboxMessage } from 'pl-fe/stores/shoutbox';
|
||||
|
||||
import { clearNativeInputValue } from './chat';
|
||||
import ShoutboxComposer from './shoutbox-composer';
|
||||
@ -17,16 +16,16 @@ interface ChatInterface {
|
||||
}
|
||||
|
||||
const Shoutbox: React.FC<ChatInterface> = ({ inputRef, className }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [content, setContent] = useState<string>('');
|
||||
const [resetContentKey, setResetContentKey] = useState<number>(fileKeyGen());
|
||||
const [errorMessage] = useState<string>();
|
||||
|
||||
const { mutate: createShoutboxMessage } = useCreateShoutboxMessage();
|
||||
|
||||
const isSubmitDisabled = content.length === 0;
|
||||
|
||||
const submitMessage = () => {
|
||||
dispatch(createShoutboxMessage(content));
|
||||
createShoutboxMessage?.(content);
|
||||
|
||||
clearState();
|
||||
};
|
||||
@ -81,6 +80,7 @@ const Shoutbox: React.FC<ChatInterface> = ({ inputRef, className }) => {
|
||||
onSubmit={sendMessage}
|
||||
errorMessage={errorMessage}
|
||||
resetContentKey={resetContentKey}
|
||||
disabled={!createShoutboxMessage}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@ -7,7 +7,6 @@ import { fetchFilters } from 'pl-fe/actions/filters';
|
||||
import { fetchMarker } from 'pl-fe/actions/markers';
|
||||
import { expandNotifications } from 'pl-fe/actions/notifications';
|
||||
import { register as registerPushNotifications } from 'pl-fe/actions/push-notifications/registerer';
|
||||
import { connectShoutbox } from 'pl-fe/actions/shoutbox';
|
||||
import { fetchHomeTimeline } from 'pl-fe/actions/timelines';
|
||||
import { useUserStream } from 'pl-fe/api/hooks/streaming/use-user-stream';
|
||||
import { WITH_LANDING_PAGE } from 'pl-fe/build-config';
|
||||
@ -44,6 +43,7 @@ import { prefetchFollowRequests } from 'pl-fe/queries/accounts/use-follow-reques
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
import { prefetchCustomEmojis } from 'pl-fe/queries/instance/use-custom-emojis';
|
||||
import { scheduledStatusesQueryOptions } from 'pl-fe/queries/statuses/scheduled-statuses';
|
||||
import { useShoutboxSubscription } from 'pl-fe/stores/shoutbox';
|
||||
import { useUiStore } from 'pl-fe/stores/ui';
|
||||
import { getVapidKey } from 'pl-fe/utils/auth';
|
||||
import { isStandalone } from 'pl-fe/utils/state';
|
||||
@ -394,6 +394,8 @@ const UI: React.FC<IUI> = React.memo(({ children }) => {
|
||||
const { isDropdownMenuOpen } = useUiStore();
|
||||
const standalone = useAppSelector(isStandalone);
|
||||
|
||||
useShoutboxSubscription();
|
||||
|
||||
const { isDragging } = useDraggedFiles(node);
|
||||
|
||||
const handleServiceWorkerPostMessage = ({ data }: MessageEvent) => {
|
||||
@ -434,10 +436,6 @@ const UI: React.FC<IUI> = React.memo(({ children }) => {
|
||||
setTimeout(() => prefetchFollowRequests(client), 700);
|
||||
}
|
||||
|
||||
if (features.shoutbox) {
|
||||
dispatch(connectShoutbox());
|
||||
}
|
||||
|
||||
if (features.scheduledStatuses) {
|
||||
setTimeout(() => {
|
||||
queryClient.prefetchInfiniteQuery(scheduledStatusesQueryOptions);
|
||||
|
||||
@ -19,7 +19,6 @@ import pending_statuses from './pending-statuses';
|
||||
import plfe from './pl-fe';
|
||||
import polls from './polls';
|
||||
import push_notifications from './push-notifications';
|
||||
import shoutbox from './shoutbox';
|
||||
import statuses from './statuses';
|
||||
import timelines from './timelines';
|
||||
|
||||
@ -40,7 +39,6 @@ const reducers = {
|
||||
plfe,
|
||||
polls,
|
||||
push_notifications,
|
||||
shoutbox,
|
||||
statuses,
|
||||
timelines,
|
||||
};
|
||||
|
||||
@ -1,39 +1,14 @@
|
||||
import { SHOUTBOX_CONNECT, SHOUTBOX_MESSAGES_IMPORT, SHOUTBOX_MESSAGE_IMPORT, type ShoutboxAction } from 'pl-fe/actions/shoutbox';
|
||||
|
||||
import type { PlApiClient, ShoutMessage as BaseShoutMessage } from 'pl-api';
|
||||
|
||||
interface ShoutMessage extends Omit<BaseShoutMessage, 'author'> {
|
||||
author_id: string;
|
||||
}
|
||||
import type { PlApiClient } from 'pl-api';
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
interface State {
|
||||
socket: ReturnType<(InstanceType<typeof PlApiClient>)['shoutbox']['connect']> | null;
|
||||
isLoading: boolean;
|
||||
messages: Array<ShoutMessage>;
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
socket: null,
|
||||
isLoading: true,
|
||||
messages: [],
|
||||
};
|
||||
|
||||
const minifyMessage = ({ author, ...message }: BaseShoutMessage): ShoutMessage => ({
|
||||
author_id: author.id,
|
||||
...message,
|
||||
});
|
||||
const shoutboxReducer = (state = initialState, action: AnyAction) => state;
|
||||
|
||||
const shoutboxReducer = (state = initialState, action: ShoutboxAction) => {
|
||||
switch (action.type) {
|
||||
case SHOUTBOX_CONNECT:
|
||||
return { ...state, socket: action.socket };
|
||||
case SHOUTBOX_MESSAGES_IMPORT:
|
||||
return { ...state, messages: action.messages.map(minifyMessage), isLoading: false };
|
||||
case SHOUTBOX_MESSAGE_IMPORT:
|
||||
return { ...state, messages: [...state.messages, minifyMessage(action.message)] };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export { shoutboxReducer as default, type ShoutMessage };
|
||||
export { shoutboxReducer as default };
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { create } from 'zustand';
|
||||
import { mutative } from 'zustand-mutative';
|
||||
|
||||
import { MuteModalProps } from 'pl-fe/modals/mute-modal';
|
||||
|
||||
import type { ICryptoAddress } from 'pl-fe/features/crypto-donate/components/crypto-address';
|
||||
import type { ModalType } from 'pl-fe/features/ui/components/modal-root';
|
||||
import type { AltTextModalProps } from 'pl-fe/modals/alt-text-modal';
|
||||
@ -31,6 +29,7 @@ import type { ListEditorModalProps } from 'pl-fe/modals/list-editor-modal';
|
||||
import type { MediaModalProps } from 'pl-fe/modals/media-modal';
|
||||
import type { MentionsModalProps } from 'pl-fe/modals/mentions-modal';
|
||||
import type { MissingDescriptionModalProps } from 'pl-fe/modals/missing-description-modal';
|
||||
import type { MuteModalProps } from 'pl-fe/modals/mute-modal';
|
||||
import type { ReactionsModalProps } from 'pl-fe/modals/reactions-modal';
|
||||
import type { ReblogsModalProps } from 'pl-fe/modals/reblogs-modal';
|
||||
import type { ReplyMentionsModalProps } from 'pl-fe/modals/reply-mentions-modal';
|
||||
|
||||
72
packages/pl-fe/src/stores/shoutbox.ts
Normal file
72
packages/pl-fe/src/stores/shoutbox.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { useEffect } from 'react';
|
||||
import { create } from 'zustand';
|
||||
import { mutative } from 'zustand-mutative';
|
||||
|
||||
import { useClient } from 'pl-fe/hooks/use-client';
|
||||
import { useInstance } from 'pl-fe/hooks/use-instance';
|
||||
import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
|
||||
|
||||
import type { PlApiClient, ShoutMessage as BaseShoutMessage } from 'pl-api';
|
||||
|
||||
const minifyMessage = ({ author, ...message }: BaseShoutMessage) => ({
|
||||
author_id: author.id,
|
||||
...message,
|
||||
});
|
||||
|
||||
type ShoutMessage = ReturnType<typeof minifyMessage>;
|
||||
|
||||
type State = {
|
||||
socket: ReturnType<(InstanceType<typeof PlApiClient>)['shoutbox']['connect']> | null;
|
||||
messages: Array<ShoutMessage>;
|
||||
isLoading: boolean;
|
||||
setMessages: (messages: Array<BaseShoutMessage>) => void;
|
||||
pushMessage: (message: BaseShoutMessage) => void;
|
||||
};
|
||||
|
||||
const useShoutboxStore = create<State>()(mutative((set) => ({
|
||||
socket: null,
|
||||
messages: [],
|
||||
isLoading: true,
|
||||
setMessages: (messages: Array<BaseShoutMessage>) => set((state: State) => {
|
||||
state.messages = messages.map(minifyMessage);
|
||||
state.isLoading = false;
|
||||
}),
|
||||
pushMessage: (message: BaseShoutMessage) => set((state: State) => {
|
||||
state.messages.push(minifyMessage(message));
|
||||
}),
|
||||
}), {
|
||||
enableAutoFreeze: false,
|
||||
}));
|
||||
|
||||
const useShoutboxSubscription = () => {
|
||||
const client = useClient();
|
||||
const instance = useInstance();
|
||||
const { isLoggedIn } = useLoggedIn();
|
||||
const shoutboxStore = useShoutboxStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!(instance.fetched && isLoggedIn)) return;
|
||||
|
||||
let socket: ReturnType<(InstanceType<typeof PlApiClient>)['shoutbox']['connect']>;
|
||||
|
||||
client.settings.verifyCredentials().then((account) => {
|
||||
if (account.__meta.pleroma?.chat_token) {
|
||||
socket = client.shoutbox.connect(account.__meta.pleroma?.chat_token, {
|
||||
onMessage: (message) => shoutboxStore.pushMessage(message),
|
||||
onMessages: (messages) => shoutboxStore.setMessages(messages),
|
||||
});
|
||||
}
|
||||
}).catch(() => {});
|
||||
|
||||
return () => {
|
||||
socket?.close();
|
||||
};
|
||||
}, [instance.fetched && isLoggedIn]);
|
||||
};
|
||||
|
||||
const useCreateShoutboxMessage = () => {
|
||||
const { socket } = useShoutboxStore();
|
||||
return { mutate: socket?.message };
|
||||
};
|
||||
|
||||
export { useShoutboxStore, useShoutboxSubscription, useCreateShoutboxMessage, type ShoutMessage };
|
||||
Reference in New Issue
Block a user