Skip to content

Commit

Permalink
fix: upload image after switching accounts from comment editor (#1855)
Browse files Browse the repository at this point in the history
  • Loading branch information
aeharding authored Feb 17, 2025
1 parent 1498afe commit 0c4d1d7
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
IonIcon,
IonSpinner,
IonToolbar,
useIonModal,
} from "@ionic/react";
import { arrowBackSharp, send } from "ionicons/icons";
import {
Expand All @@ -14,10 +13,10 @@ import {
PostView,
ResolveObjectResponse,
} from "lemmy-js-client";
import { useEffect, useMemo, useRef, useState } from "react";
import { MutableRefObject, useEffect, useRef, useState } from "react";

import AccountSwitcher from "#/features/auth/AccountSwitcher";
import {
getInstanceFromHandle,
loggedInAccountsSelector,
userHandleSelector,
} from "#/features/auth/authSelectors";
Expand All @@ -34,6 +33,10 @@ import { useAppDispatch, useAppSelector } from "#/store";
import AppHeader from "../../../../AppHeader";
import CommentEditorContent from "./CommentEditorContent";
import ItemReplyingTo from "./ItemReplyingTo";
import {
TemporarySelectedAccountProvider,
useTemporarySelectedAccount,
} from "./TemporarySelectedAccountContext";

export type CommentReplyItem =
| CommentView
Expand All @@ -50,90 +53,85 @@ interface CommentReplyPageProps {
/**
* New comment replying to something
*/
export default function CommentReplyPage({
dismiss,
setCanDismiss,
item,
}: CommentReplyPageProps) {
const comment = "comment" in item ? item.comment : undefined;

const dispatch = useAppDispatch();
const [replyContent, setReplyContent] = useState("");
const client = useClient();
export default function CommentReplyPage(props: CommentReplyPageProps) {
const presentToast = useAppToast();
const [loading, setLoading] = useState(false);
const isSubmitDisabled = !replyContent.trim() || loading;

const userHandle = useAppSelector(userHandleSelector);
const [selectedAccount, setSelectedAccount] = useState(userHandle);
const comment = "comment" in props.item ? props.item.comment : undefined;

const isUsingAppAccount = selectedAccount === userHandle;
const resolvedRef = useRef<ResolveObjectResponse | undefined>();

const accounts = useAppSelector(loggedInAccountsSelector);
const resolvedRef = useRef<ResolveObjectResponse | undefined>();
const selectedAccountJwt = accounts?.find(
({ handle }) => handle === selectedAccount,
)?.jwt;
const selectedAccountClient = useMemo(() => {
if (!selectedAccount) return;
const userHandle = useAppSelector(userHandleSelector);

const instance = selectedAccount.split("@")[1]!;
async function onSelectAccount(account: string) {
// Switching back to local account
if (account === userHandle) {
resolvedRef.current = undefined;
return;
}

return getClient(instance, selectedAccountJwt);
}, [selectedAccount, selectedAccountJwt]);
// Using a remote account
const accountJwt = accounts?.find(({ handle }) => handle === account)?.jwt;

const textareaRef = useRef<HTMLTextAreaElement>(null);
if (!accountJwt) throw new Error("Error switching accounts");

const [presentAccountSwitcher, onDismissAccountSwitcher] = useIonModal(
AccountSwitcher,
{
allowEdit: false,
showGuest: false,
activeHandle: selectedAccount,
onDismiss: (data?: string, role?: string) =>
onDismissAccountSwitcher(data, role),
onSelectAccount: async (account: string) => {
// Switching back to local account
if (account === userHandle) {
resolvedRef.current = undefined;
setSelectedAccount(account);
return;
}
const instance = getInstanceFromHandle(account);
const client = getClient(instance, accountJwt);

// Using a remote account
const accountJwt = accounts?.find(
({ handle }) => handle === account,
)?.jwt;
// Lookup the comment from the perspective of the remote instance.
// The remote instance may not know about the thing we're trying to comment on.
// Also, IDs are not the same between instances.
try {
resolvedRef.current = await client.resolveObject({
q: comment?.ap_id ?? props.item.post.ap_id,
});
} catch (error) {
presentToast({
message: `This ${
comment ? "comment" : "post"
} does not exist on ${instance}.`,
color: "warning",
position: "top",
fullscreen: true,
});

if (!accountJwt) throw new Error("Error switching accounts");
throw error;
}
}

const instance = account.split("@")[1]!;
const client = getClient(instance, accountJwt);
return (
<TemporarySelectedAccountProvider onSelectAccount={onSelectAccount}>
<CommentReplyPageWithAccount {...props} resolvedRef={resolvedRef} />
</TemporarySelectedAccountProvider>
);
}

// Lookup the comment from the perspective of the remote instance.
// The remote instance may not know about the thing we're trying to comment on.
// Also, IDs are not the same between instances.
try {
resolvedRef.current = await client.resolveObject({
q: comment?.ap_id ?? item.post.ap_id,
});
} catch (error) {
presentToast({
message: `This ${
comment ? "comment" : "post"
} does not exist on ${instance}.`,
color: "warning",
position: "top",
fullscreen: true,
});
interface CommentReplyPageContentProps extends CommentReplyPageProps {
resolvedRef: MutableRefObject<ResolveObjectResponse | undefined>;
}

throw error;
}
function CommentReplyPageWithAccount({
dismiss,
setCanDismiss,
item,
resolvedRef,
}: CommentReplyPageContentProps) {
const comment = "comment" in item ? item.comment : undefined;

setSelectedAccount(account);
},
},
);
const dispatch = useAppDispatch();
const [replyContent, setReplyContent] = useState("");
const client = useClient();
const presentToast = useAppToast();
const [loading, setLoading] = useState(false);
const isSubmitDisabled = !replyContent.trim() || loading;

const { account, accountClient, presentAccountSwitcher } =
useTemporarySelectedAccount();

const userHandle = useAppSelector(userHandleSelector);
const isUsingAppAccount = account?.handle === userHandle;

const textareaRef = useRef<HTMLTextAreaElement>(null);

async function submit() {
if (isSubmitDisabled) return;
Expand All @@ -158,11 +156,10 @@ export default function CommentReplyPage({
resolvedRef.current?.post?.post.id;

if (!postId) throw new Error("Post not found.");
if (!selectedAccountClient)
throw new Error("Unexpected error occurred.");
if (!accountClient) throw new Error("Unexpected error occurred.");

// Post comment to selected remote instance
const remoteComment = await selectedAccountClient.createComment({
const remoteComment = await accountClient.createComment({
content: replyContent,
parent_id: resolvedRef.current?.comment?.comment.id,
post_id: postId,
Expand Down Expand Up @@ -233,17 +230,12 @@ export default function CommentReplyPage({
</IonButton>
</IonButtons>
<MultilineTitle
subheader={selectedAccount}
subheader={account?.handle}
onClick={() => {
if (accounts?.length === 1) return;

presentAccountSwitcher({
cssClass: "small",
onDidDismiss: () => {
requestAnimationFrame(() => {
textareaRef.current?.focus();
});
},
presentAccountSwitcher(() => {
requestAnimationFrame(() => {
textareaRef.current?.focus();
});
});
}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { useIonModal } from "@ionic/react";
import { noop } from "es-toolkit";
import { LemmyHttp } from "lemmy-js-client";
import { createContext, useContext, useState } from "react";

import AccountSwitcher from "#/features/auth/AccountSwitcher";
import {
loggedInAccountsSelector,
userHandleSelector,
} from "#/features/auth/authSelectors";
import { Credential } from "#/features/auth/authSlice";
import { getClient } from "#/services/lemmy";
import { useAppSelector } from "#/store";

const TemporarySelectedAccountContext = createContext<{
account: Credential | undefined;
accountClient: LemmyHttp | undefined;
presentAccountSwitcher: (onDidDismiss: () => void) => void;
}>({
account: undefined,
accountClient: undefined,
presentAccountSwitcher: noop,
});

export function useTemporarySelectedAccount() {
return useContext(TemporarySelectedAccountContext);
}

export function TemporarySelectedAccountProvider({
children,
onSelectAccount,
}: {
children: React.ReactNode;
onSelectAccount: (account: string) => Promise<void>;
}) {
const userHandle = useAppSelector(userHandleSelector);
const [selectedAccountHandle, setSelectedAccountHandle] =
useState(userHandle);

const accounts = useAppSelector(loggedInAccountsSelector);
const selectedAccount = accounts?.find(
({ handle }) => handle === selectedAccountHandle,
);

const selectedAccountJwt = selectedAccount?.jwt;
const selectedAccountClient = (() => {
if (!selectedAccountHandle) return;

const instance = selectedAccountHandle.split("@")[1]!;

return getClient(instance, selectedAccountJwt);
})();

const [presentAccountSwitcher, onDismissAccountSwitcher] = useIonModal(
AccountSwitcher,
{
allowEdit: false,
showGuest: false,
activeHandle: selectedAccountHandle,
onDismiss: (data?: string, role?: string) =>
onDismissAccountSwitcher(data, role),
onSelectAccount: async (account: string) => {
onSelectAccount(account);

setSelectedAccountHandle(account);
},
},
);

return (
<TemporarySelectedAccountContext.Provider
value={{
account: selectedAccount,
accountClient: selectedAccountClient,
presentAccountSwitcher: (onDidDismiss) => {
if (accounts?.length === 1) return;

presentAccountSwitcher({
cssClass: "small",
onDidDismiss,
});
},
}}
>
{children}
</TemporarySelectedAccountContext.Provider>
);
}
Loading

0 comments on commit 0c4d1d7

Please sign in to comment.