Skip to content

Commit

Permalink
rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
aeharding committed Jan 20, 2025
1 parent b97d28b commit f1db2b7
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 34 deletions.
16 changes: 15 additions & 1 deletion src/features/post/new/PhotoPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
import { IonSpinner } from "@ionic/react";

import { cx } from "#/helpers/css";
import { isUrlVideo } from "#/helpers/url";

import styles from "./PhotoPreview.module.css";

interface PhotoPreviewProps {
src: string;
loading: boolean;
onClick?: () => void;
isVideo: boolean;
}

export default function PhotoPreview({
src,
loading,
onClick,
isVideo,
}: PhotoPreviewProps) {
const Media = isVideo || isUrlVideo(src, undefined) ? "video" : "img";

return (
<div className={styles.container}>
<img
<Media
src={src}
playsInline
muted
autoPlay
onPlaying={(e) => {
if (!(e.target instanceof HTMLVideoElement)) return;

// iOS won't show preview unless the video plays
e.target.pause();
}}
onClick={onClick}
className={cx(styles.img, loading && styles.loadingImage)}
/>
Expand Down
59 changes: 33 additions & 26 deletions src/features/post/new/PostEditorRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,8 @@ import { Post } from "lemmy-js-client";
import { useEffect, useMemo, useState } from "react";

import AppHeader from "#/features/shared/AppHeader";
import {
deletePendingImageUploads,
uploadImage,
} from "#/features/shared/markdown/editing/uploadImageSlice";
import { deletePendingImageUploads } from "#/features/shared/markdown/editing/uploadImageSlice";
import useUploadImage from "#/features/shared/markdown/editing/useUploadImage";
import { buildPostLink } from "#/helpers/appLinkBuilder";
import { isAndroid } from "#/helpers/device";
import { getRemoteHandle } from "#/helpers/lemmy";
Expand All @@ -49,7 +47,7 @@ import { PostEditorProps } from "./PostEditor";

import styles from "./PostEditorRoot.module.css";

type PostType = "photo" | "link" | "text";
type PostType = "media" | "link" | "text";

const MAX_TITLE_LENGTH = 200;

Expand All @@ -59,6 +57,7 @@ export default function PostEditorRoot({
...props
}: PostEditorProps) {
const dispatch = useAppDispatch();
const { uploadImage } = useUploadImage();
const [presentAlert] = useIonAlert();
const router = useOptimizedIonRouter();
const buildGeneralBrowseLink = useBuildGeneralBrowseLink();
Expand All @@ -82,9 +81,9 @@ export default function PostEditorRoot({
const initialImage = isImage ? existingPost!.post.url : undefined;

const initialPostType = (() => {
if (!existingPost) return "photo";
if (!existingPost) return "media";

if (initialImage) return "photo";
if (initialImage) return "media";

if (existingPost.post.url) return "link";

Expand Down Expand Up @@ -115,15 +114,24 @@ export default function PostEditorRoot({
const [photoPreviewURL, setPhotoPreviewURL] = useState<string | undefined>(
initialImage,
);
const [isPreviewVideo, setIsPreviewVideo] = useState(false);
const [photoUploading, setPhotoUploading] = useState(false);

const showAutofill = !!url && isValidUrl(url) && !title;

const showNsfwToggle = !!(
(postType === "photo" && photoPreviewURL) ||
(postType === "media" && photoPreviewURL) ||
(postType === "link" && url)
);

useEffect(() => {
return () => {
if (!photoPreviewURL) return;

URL.revokeObjectURL(photoPreviewURL);
};
}, [photoPreviewURL]);

useEffect(() => {
setCanDismiss(
initialPostType === postType &&
Expand Down Expand Up @@ -158,7 +166,7 @@ export default function PostEditorRoot({
if (!url) return false;
break;

case "photo":
case "media":
if (!photoUrl) return false;
break;
}
Expand All @@ -182,7 +190,7 @@ export default function PostEditorRoot({
switch (postType) {
case "link":
return url || undefined;
case "photo":
case "media":
return photoUrl || undefined;
default:
return;
Expand All @@ -194,7 +202,7 @@ export default function PostEditorRoot({
case "link":
default:
return undefined;
case "photo":
case "media":
return altText;
}
})();
Expand All @@ -211,7 +219,7 @@ export default function PostEditorRoot({
) {
errorMessage =
"Please add a valid URL to your post (start with https://).";
} else if (postType === "photo" && !photoUrl) {
} else if (postType === "media" && !photoUrl) {
errorMessage = "Please add a photo to your post.";
} else if (!canSubmit()) {
errorMessage =
Expand Down Expand Up @@ -290,6 +298,7 @@ export default function PostEditorRoot({

async function receivedImage(image: File) {
setPhotoPreviewURL(URL.createObjectURL(image));
setIsPreviewVideo(image.type.startsWith("video/"));
setPhotoUploading(true);

let imageUrl;
Expand All @@ -298,15 +307,8 @@ export default function PostEditorRoot({
if (isAndroid()) await new Promise((resolve) => setTimeout(resolve, 250));

try {
imageUrl = await dispatch(uploadImage(image));
imageUrl = await uploadImage(image);
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";

presentToast({
message: `Problem uploading image: ${message}. Please try again.`,
color: "danger",
fullscreen: true,
});
clearImage();

throw error;
Expand All @@ -322,6 +324,7 @@ export default function PostEditorRoot({
function clearImage() {
setPhotoUrl("");
setPhotoPreviewURL(undefined);
setIsPreviewVideo(false);
}

async function fetchPostTitle() {
Expand Down Expand Up @@ -402,7 +405,7 @@ export default function PostEditorRoot({
value={postType}
onIonChange={(e) => setPostType(e.target.value as PostType)}
>
<IonSegmentButton value="photo">Photo</IonSegmentButton>
<IonSegmentButton value="media">Media</IonSegmentButton>
<IonSegmentButton value="link">Link</IonSegmentButton>
<IonSegmentButton value="text">Text</IonSegmentButton>
</IonSegment>
Expand Down Expand Up @@ -440,28 +443,31 @@ export default function PostEditorRoot({
</IonButton>
)}
</IonItem>
{postType === "photo" && (
{postType === "media" && (
<>
<label htmlFor="photo-upload-post">
<label htmlFor="media-upload-post">
<IonItem>
<IonLabel color="primary">
<IonIcon
className={styles.cameraIcon}
icon={cameraOutline}
/>{" "}
Choose Photo
Choose Photo / Video
</IonLabel>

<input
type="file"
accept="image/*"
id="photo-upload-post"
accept="image/*,video/webm"
id="media-upload-post"
className={styles.hiddenInput}
onInput={(e) => {
const image = (e.target as HTMLInputElement).files?.[0];
if (!image) return;

receivedImage(image);

// Allow next upload attempt
(e.target as HTMLInputElement).value = "";
}}
/>
</IonItem>
Expand All @@ -471,6 +477,7 @@ export default function PostEditorRoot({
<PhotoPreview
src={photoPreviewURL}
loading={photoUploading}
isVideo={isPreviewVideo}
/>
<IonButton
fill={altText ? "solid" : "outline"}
Expand Down
2 changes: 1 addition & 1 deletion src/features/shared/markdown/editing/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export default function Editor({
}

async function onReceivedImage(image: File) {
const markdown = await uploadImage(image);
const markdown = await uploadImage(image, true);

textareaRef.current?.focus();
insertBlock(markdown);
Expand Down
4 changes: 2 additions & 2 deletions src/features/shared/markdown/editing/modes/DefaultMode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -353,13 +353,13 @@ export default function DefaultMode({
<input
className="ion-hide"
type="file"
accept="image/*"
accept="image/*,video/webm"
id="photo-upload-toolbar"
onInput={async (e) => {
const image = (e.target as HTMLInputElement).files?.[0];
if (!image) return;

const markdown = await uploadImage(image);
const markdown = await uploadImage(image, true);

insertBlock(markdown);
}}
Expand Down
19 changes: 15 additions & 4 deletions src/features/shared/markdown/editing/useUploadImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,40 @@ export default function useUploadImage() {

return {
jsx: <IonLoading isOpen={imageUploading} message="Uploading image..." />,
uploadImage: async (image: File) => {
uploadImage: async (image: File, toMarkdown = false) => {
setImageUploading(true);

let imageUrl: string;

try {
imageUrl = await dispatch(uploadImage(image));
} catch (error) {
const message =
error instanceof Error ? error.message : "Unknown error";
const message = (() => {
if (error instanceof Error) {
if (error.message.startsWith("NetworkError"))
return "Issue with network connectivity, or upload was too large";

return error.message;
}

return "Unknown error";
})();

presentToast({
message: `Problem uploading image: ${message}. Please try again.`,
color: "danger",
fullscreen: true,
duration: 5_000,
});

throw error;
} finally {
setImageUploading(false);
}

return `![](${imageUrl})`;
if (toMarkdown) return `![](${imageUrl})`;

return imageUrl;
},
};
}
2 changes: 2 additions & 0 deletions src/helpers/imageCompress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export async function reduceFileSize(
maxHeight: number,
quality = 0.7,
): Promise<Blob | File> {
if (file.type.startsWith("video/")) return file;

if (file.size <= acceptFileSize) {
return file;
}
Expand Down

0 comments on commit f1db2b7

Please sign in to comment.