diff --git a/frontend/src/components/GroupHeader.jsx b/frontend/src/components/GroupHeader.jsx index a5183f5..a26adb5 100644 --- a/frontend/src/components/GroupHeader.jsx +++ b/frontend/src/components/GroupHeader.jsx @@ -24,6 +24,8 @@ import { gql, useMutation } from "@apollo/client"; import { connect } from "react-redux"; import { setDialog, showSnackbar, fillForm } from "../state/actions"; +import optimizeImage from "../utils/image"; + const DELETE_GROUP = gql` mutation DeleteGroup($group_id: ID!) { deleteGroup(groupId: $group_id) @@ -222,45 +224,31 @@ function GroupHeader(props) { }; }, []); - const handleCoverChange = (event) => { + const handleCoverChange = async (event) => { const files = event.target.files; if (files.length > 0) { setCover(URL.createObjectURL(files[0])); - const reader = new FileReader(); - reader.readAsDataURL(files[0]); - reader.onloadend = () => { - changeCover({ - variables: { - group_id: id, - image: reader.result, - }, - }); - }; - reader.onerror = () => { - showSnackbar("Something went wrong!"); - }; + const optimizedImage = await optimizeImage(files[0]); + changeCover({ + variables: { + image: optimizedImage, + }, + }); } }; - const handlePhotoChange = (event) => { + const handlePhotoChange = async (event) => { const files = event.target.files; if (files.length > 0) { setPhoto(URL.createObjectURL(files[0])); - const reader = new FileReader(); - reader.readAsDataURL(files[0]); - reader.onloadend = () => { - changeImage({ - variables: { - group_id: id, - image: reader.result, - }, - }); - }; - reader.onerror = () => { - showSnackbar("Something went wrong!"); - }; + const optimizedImage = await optimizeImage(files[0]); + changeImage({ + variables: { + image: optimizedImage, + }, + }); } }; diff --git a/frontend/src/components/PageHeader.jsx b/frontend/src/components/PageHeader.jsx index 898f92a..295c669 100644 --- a/frontend/src/components/PageHeader.jsx +++ b/frontend/src/components/PageHeader.jsx @@ -24,6 +24,8 @@ import { gql, useMutation } from "@apollo/client"; import { connect } from "react-redux"; import { setDialog, showSnackbar, fillForm } from "../state/actions"; +import optimizeImage from "../utils/image"; + const DELETE_PAGE = gql` mutation DeletePage($page_id: ID!) { deletePage(pageId: $page_id) @@ -216,45 +218,31 @@ function PageHeader(props) { }; }, []); - const handleCoverChange = (event) => { + const handleCoverChange = async (event) => { const files = event.target.files; if (files.length > 0) { setCover(URL.createObjectURL(files[0])); - const reader = new FileReader(); - reader.readAsDataURL(files[0]); - reader.onloadend = () => { - changeCover({ - variables: { - page_id: id, - image: reader.result, - }, - }); - }; - reader.onerror = () => { - showSnackbar("Something went wrong!"); - }; + const optimizedImage = await optimizeImage(files[0]); + changeCover({ + variables: { + image: optimizedImage, + }, + }); } }; - const handlePhotoChange = (event) => { + const handlePhotoChange = async (event) => { const files = event.target.files; if (files.length > 0) { setPhoto(URL.createObjectURL(files[0])); - const reader = new FileReader(); - reader.readAsDataURL(files[0]); - reader.onloadend = () => { - changeImage({ - variables: { - page_id: id, - image: reader.result, - }, - }); - }; - reader.onerror = () => { - showSnackbar("Something went wrong!"); - }; + const optimizedImage = await optimizeImage(files[0]); + changeImage({ + variables: { + image: optimizedImage, + }, + }); } }; diff --git a/frontend/src/components/ProfileHeader.jsx b/frontend/src/components/ProfileHeader.jsx index 461948d..596bb70 100644 --- a/frontend/src/components/ProfileHeader.jsx +++ b/frontend/src/components/ProfileHeader.jsx @@ -26,6 +26,8 @@ import { gql, useMutation } from "@apollo/client"; import { connect } from "react-redux"; import { setDialog, showSnackbar, fillForm } from "../state/actions"; +import optimizeImage from "../utils/image"; + const FOLLOW_USER = gql` mutation FollowUser($user_id: ID!) { follow(id: $user_id) { @@ -235,43 +237,31 @@ function ProfileHeader(props) { }; }, []); - const handleCoverChange = (event) => { + const handleCoverChange = async (event) => { const files = event.target.files; if (files.length > 0) { setCover(URL.createObjectURL(files[0])); - const reader = new FileReader(); - reader.readAsDataURL(files[0]); - reader.onloadend = () => { - changeCover({ - variables: { - image: reader.result, - }, - }); - }; - reader.onerror = () => { - showSnackbar("Something went wrong!"); - }; + const optimizedImage = await optimizeImage(files[0]); + changeCover({ + variables: { + image: optimizedImage, + }, + }); } }; - const handlePhotoChange = (event) => { + const handlePhotoChange = async (event) => { const files = event.target.files; if (files.length > 0) { setPhoto(URL.createObjectURL(files[0])); - const reader = new FileReader(); - reader.readAsDataURL(files[0]); - reader.onloadend = () => { - changeImage({ - variables: { - image: reader.result, - }, - }); - }; - reader.onerror = () => { - showSnackbar("Something went wrong!"); - }; + const optimizedImage = await optimizeImage(files[0]); + changeImage({ + variables: { + image: optimizedImage, + }, + }); } }; diff --git a/frontend/src/dialogs/CreateGroupDialog.jsx b/frontend/src/dialogs/CreateGroupDialog.jsx index fefba3d..9256fd1 100644 --- a/frontend/src/dialogs/CreateGroupDialog.jsx +++ b/frontend/src/dialogs/CreateGroupDialog.jsx @@ -17,6 +17,8 @@ import { gql, useMutation } from "@apollo/client"; import { connect } from "react-redux"; import { setDialog, showSnackbar } from "../state/actions"; +import optimizeImage from "../utils/image"; + const CREATE_GROUP = gql` mutation CreateGroup( $title: String! @@ -109,22 +111,16 @@ function CreatePostDialog(props) { setCardContentHeight(cardContent.current.offsetHeight); }); - const handleCreate = () => { + const handleCreate = async () => { if (cover) { - const reader = new FileReader(); - reader.readAsDataURL(cover); - reader.onloadend = () => { - createGroup({ - variables: { - title, - current_user_id: user.id, - cover_image: reader.result, - }, - }); - }; - reader.onerror = () => { - showSnackbar("Something went wrong!"); - }; + const optimizedImage = await optimizeImage(cover); + createGroup({ + variables: { + title, + current_user_id: user.id, + cover_image: optimizedImage, + }, + }); } else { createGroup({ variables: { diff --git a/frontend/src/dialogs/CreatePageDialog.jsx b/frontend/src/dialogs/CreatePageDialog.jsx index 311a645..0dfda20 100644 --- a/frontend/src/dialogs/CreatePageDialog.jsx +++ b/frontend/src/dialogs/CreatePageDialog.jsx @@ -17,6 +17,8 @@ import { gql, useMutation } from "@apollo/client"; import { connect } from "react-redux"; import { setDialog, showSnackbar } from "../state/actions"; +import optimizeImage from "../utils/image"; + const CREATE_PAGE = gql` mutation CreatePage( $title: String! @@ -107,21 +109,15 @@ function CreatePostDialog(props) { setCardContentHeight(cardContent.current.offsetHeight); }); - const handleCreate = () => { + const handleCreate = async () => { if (cover) { - const reader = new FileReader(); - reader.readAsDataURL(cover); - reader.onloadend = () => { - createPage({ - variables: { - title, - cover_image: reader.result, - }, - }); - }; - reader.onerror = () => { - showSnackbar("Something went wrong!"); - }; + const optimizedImage = await optimizeImage(cover); + createPage({ + variables: { + title, + cover_image: optimizedImage, + }, + }); } else { createPage({ variables: { diff --git a/frontend/src/dialogs/CreatePostDialog.jsx b/frontend/src/dialogs/CreatePostDialog.jsx index 92aa4ea..5e8f8b1 100644 --- a/frontend/src/dialogs/CreatePostDialog.jsx +++ b/frontend/src/dialogs/CreatePostDialog.jsx @@ -17,6 +17,8 @@ import { gql, useMutation } from "@apollo/client"; import { connect } from "react-redux"; import { setDialog, showSnackbar } from "../state/actions"; +import optimizeImage from "../utils/image"; + const ADD_PROFILE_POST = gql` mutation AddProfilePost($text: String!, $image: String) { createPost(body: $text, imageSrc: $image) { @@ -134,30 +136,24 @@ function CreatePostDialog(props) { setCardContentHeight(cardContent.current.offsetHeight); }); - const handlePost = () => { + const handlePost = async () => { if (media) { - const reader = new FileReader(); - reader.readAsDataURL(media); - reader.onloadend = () => { - switch (postForm.type) { - case "profile": - addProfilePost({ variables: { text, image: reader.result } }); - break; - case "group": - addGroupPost({ - variables: { group_id: postForm.id, text, image: reader.result }, - }); - break; - case "page": - addPagePost({ - variables: { page_id: postForm.id, text, image: reader.result }, - }); - break; - } - }; - reader.onerror = () => { - showSnackbar("Something went wrong!"); - }; + const optimizedImage = await optimizeImage(media); + switch (postForm.type) { + case "profile": + addProfilePost({ variables: { text, image: optimizedImage } }); + break; + case "group": + addGroupPost({ + variables: { group_id: postForm.id, text, image: optimizedImage }, + }); + break; + case "page": + addPagePost({ + variables: { page_id: postForm.id, text, image: optimizedImage }, + }); + break; + } } else { switch (postForm.type) { case "profile": diff --git a/frontend/src/utils/image.js b/frontend/src/utils/image.js new file mode 100644 index 0000000..f741aef --- /dev/null +++ b/frontend/src/utils/image.js @@ -0,0 +1,49 @@ +const MAX_WIDTH = 1280; +const QUALITY = 0.92; + +const readPhoto = async (photo) => { + const canvas = document.createElement("canvas"); + const img = document.createElement("img"); + + img.src = await new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = (e) => resolve(e.target.result); + reader.readAsDataURL(photo); + }); + await new Promise((resolve) => { + img.onload = resolve; + }); + + // draw image in canvas element + canvas.width = img.width; + canvas.height = img.height; + canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height); + + return canvas; +}; + +const scaleCanvas = (canvas, scale) => { + const scaledCanvas = document.createElement("canvas"); + scaledCanvas.width = canvas.width * scale; + scaledCanvas.height = canvas.height * scale; + + scaledCanvas + .getContext("2d") + .drawImage(canvas, 0, 0, scaledCanvas.width, scaledCanvas.height); + + return scaledCanvas; +}; + +export default async (photo) => { + let canvas = await readPhoto(photo); + + while (canvas.width >= 2 * MAX_WIDTH) { + canvas = scaleCanvas(canvas, 0.5); + } + + if (canvas.width > MAX_WIDTH) { + canvas = scaleCanvas(canvas, MAX_WIDTH / canvas.width); + } + + return canvas.toDataURL("image/jpeg", QUALITY); +};