From 56c0834dbb75180a28fcafd94d600ea46ee75089 Mon Sep 17 00:00:00 2001 From: xvoorvaa Date: Tue, 13 Aug 2024 18:22:44 +0200 Subject: [PATCH 01/17] =?UTF-8?q?=F0=9F=93=A6=EF=B8=8F:=20Updated=20a=20fe?= =?UTF-8?q?w=20packages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../destinations/connect/page.tsx | 13 +++++----- .../components/UploadTwitterButton.tsx | 14 +++++------ .../[organization]/library/[session]/page.tsx | 14 +++++------ packages/app/package.json | 10 +++----- yarn.lock | 25 ++++++++----------- 5 files changed, 33 insertions(+), 43 deletions(-) diff --git a/packages/app/app/studio/[organization]/destinations/connect/page.tsx b/packages/app/app/studio/[organization]/destinations/connect/page.tsx index 08c61d151..83bb4f1d1 100644 --- a/packages/app/app/studio/[organization]/destinations/connect/page.tsx +++ b/packages/app/app/studio/[organization]/destinations/connect/page.tsx @@ -1,8 +1,7 @@ import { Button } from '@/components/ui/button'; import Link from 'next/link'; -import React from 'react'; import { LuArrowLeft } from 'react-icons/lu'; -import { SiTwitter } from 'react-icons/si'; +import { SiX } from 'react-icons/si'; import YoutubeConnectButton from './components/YoutubeConnectButton'; import { fetchOrganization } from '@/lib/services/organizationService'; @@ -21,25 +20,25 @@ const ConnectSocials = async ({ }) ); return ( -
+
-
+

Back to destinations

-
+

Add a destination

Connect an account to StreamEth. Once connected, you can stream and upload clips and videos to it as often as you like.

-
+
{/* */} {/* */} diff --git a/packages/app/app/studio/[organization]/library/[session]/components/UploadTwitterButton.tsx b/packages/app/app/studio/[organization]/library/[session]/components/UploadTwitterButton.tsx index 7c6a686de..b7f2db5df 100644 --- a/packages/app/app/studio/[organization]/library/[session]/components/UploadTwitterButton.tsx +++ b/packages/app/app/studio/[organization]/library/[session]/components/UploadTwitterButton.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'; -import { SiTwitter } from 'react-icons/si'; +import { SiX } from 'react-icons/si'; import { IExtendedOrganization } from '@/lib/types'; import { uploadSessionToYouTubeAction } from '@/lib/actions/sessions'; import { toast } from 'sonner'; @@ -65,13 +65,13 @@ const UploadTwitterButton = ({ - +

Select Twitter Destination

-
+
{organization?.socials ?.filter((s) => s.type == 'twitter') .map(({ name, thumbnail, _id }) => ( @@ -83,17 +83,17 @@ const UploadTwitterButton = ({ }`} >
-

{name}

+

{name}

))}

Add New

diff --git a/packages/app/app/studio/[organization]/library/[session]/page.tsx b/packages/app/app/studio/[organization]/library/[session]/page.tsx index c88c8883f..5c25e5586 100644 --- a/packages/app/app/studio/[organization]/library/[session]/page.tsx +++ b/packages/app/app/studio/[organization]/library/[session]/page.tsx @@ -11,8 +11,6 @@ import SessionOptions from './components/SessionOptions'; import { Label } from '@/components/ui/label'; import GetHashButton from '../components/GetHashButton'; import TextPlaceholder from '@/components/ui/text-placeholder'; -import { Button } from '@/components/ui/button'; -import { SiTwitter } from 'react-icons/si'; import { Accordion, AccordionContent, @@ -41,15 +39,15 @@ const EditSession = async ({ params, searchParams }: studioPageParams) => { if (!videoUrl) return notFound(); return ( -
+
-
+

Back to library

-
-
+
+

Video Details

{ @@ -116,7 +114,7 @@ const EditSession = async ({ params, searchParams }: studioPageParams) => { diff --git a/packages/app/package.json b/packages/app/package.json index ffd5b223b..7fe0c5b2d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -42,8 +42,8 @@ "@radix-ui/react-tooltip": "^1.0.7", "@tanstack/react-query": "^5.18.1", "@uiw/react-md-editor": "^4.0.3", - "@vercel/analytics": "^1.0.2", - "@vercel/speed-insights": "^1.0.2", + "@vercel/analytics": "^1.3.1", + "@vercel/speed-insights": "^1.0.12", "autoprefixer": "10.4.14", "class-validator": "^0.14.0", "class-variance-authority": "^0.7.0", @@ -66,13 +66,11 @@ "livepeer": "^3.0.2", "lodash": "^4.17.21", "lokijs": "^1.5.12", - "lucide-react": "^0.414.0", + "lucide-react": "^0.427.0", "moment-timezone": "^0.5.43", - "momentjs": "^2.0.0", "mux-embed": "^5.2.1", "next": "^14.2.0", "next-themes": "^0.2.1", - "oauth-1.0a": "^2.2.6", "pino-pretty": "^10.3.1", "postcss": "8.4.31", "react": "^18.2.0", @@ -81,7 +79,7 @@ "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-hook-form": "^7.49.3", - "react-icons": "^5.2.1", + "react-icons": "^5.3.0", "react-markdown": "^9.0.0", "react-resizable-panels": "^1.0.8", "react-scroll": "^1.8.9", diff --git a/yarn.lock b/yarn.lock index ed5ee06bf..b3b25fcf1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6163,14 +6163,14 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@vercel/analytics@^1.0.2": +"@vercel/analytics@^1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@vercel/analytics/-/analytics-1.3.1.tgz#e2b1deac1b5d14fa2e4fe36186ac5054c6385ae4" integrity sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA== dependencies: server-only "^0.0.1" -"@vercel/speed-insights@^1.0.2": +"@vercel/speed-insights@^1.0.12": version "1.0.12" resolved "https://registry.yarnpkg.com/@vercel/speed-insights/-/speed-insights-1.0.12.tgz#71c2edffdedae98d34e306d7b0a573e6816898b4" integrity sha512-ZGQ+a7bcfWJD2VYEp2R1LHvRAMyyaFBYytZXsfnbOMkeOvzGNVxUL7aVUvisIrTZjXTSsxG45DKX7yiw6nq2Jw== @@ -13581,10 +13581,10 @@ lru_map@^0.3.3: resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ== -lucide-react@^0.414.0: - version "0.414.0" - resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.414.0.tgz#15245174d3ea111c85eae571b0e903c1026ac1fd" - integrity sha512-Krr/MHg9AWoJc52qx8hyJ64X9++JNfS1wjaJviLM1EP/68VNB7Tv0VMldLCB1aUe6Ka9QxURPhQm/eB6cqOM3A== +lucide-react@^0.427.0: + version "0.427.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.427.0.tgz#e06974514bbd591049f9d736b3d3ae99d4ede8c9" + integrity sha512-lv9s6c5BDF/ccuA0EgTdskTxIe11qpwBDmzRZHJAKtp8LTewAvDvOM+pTES9IpbBuTqkjiMhOmGpJ/CB+mKjFw== luxon@^3.2.1: version "3.4.4" @@ -14439,11 +14439,6 @@ moment@^2.29.1, moment@^2.29.4: resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== -momentjs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/momentjs/-/momentjs-2.0.0.tgz#73df904b4fa418f6e3c605e831cef6ed5518ebd4" - integrity sha512-GYMUxLyCwVhECkJR1/LMHEyb9gWYSPRnXi+elGN0m5bet7ngQOxU4QLWUI/eBzgN4N/T194n6yP7lQiE+Udw9A== - mongodb-connection-string-url@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz#57901bf352372abdde812c81be47b75c6b2ec5cf" @@ -16204,10 +16199,10 @@ react-hook-form@^7.49.3: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.52.1.tgz#ec2c96437b977f8b89ae2d541a70736c66284852" integrity sha512-uNKIhaoICJ5KQALYZ4TOaOLElyM+xipord+Ha3crEFhTntdLvWZqVY49Wqd/0GiVCA/f9NjemLeiNPjG7Hpurg== -react-icons@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.2.1.tgz#28c2040917b2a2eda639b0f797bff1888e018e4a" - integrity sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw== +react-icons@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.3.0.tgz#ccad07a30aebd40a89f8cfa7d82e466019203f1c" + integrity sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg== react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" From 4b3f380ee69c01d4e9971cbeef9c3672e873bb7b Mon Sep 17 00:00:00 2001 From: xvoorvaa Date: Tue, 13 Aug 2024 20:05:21 +0200 Subject: [PATCH 02/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F:=20Refactor=20imageDro?= =?UTF-8?q?pzone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[session]/components/ImageDropzone.tsx | 117 ++++++++++++++---- .../components/upload/UploadVideoForm.tsx | 12 +- 2 files changed, 100 insertions(+), 29 deletions(-) diff --git a/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx b/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx index 1d6b58eae..c61d49ad0 100644 --- a/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx +++ b/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx @@ -1,11 +1,12 @@ 'use client'; -import Image from 'next/image'; +import NextImage from 'next/image'; import { ImageUp, X } from 'lucide-react'; import { getImageUrl } from '@/lib/utils/utils'; import { toast } from 'sonner'; import { useCallback, useState, forwardRef } from 'react'; import { useDropzone } from 'react-dropzone'; +import { AspectRatio } from '@radix-ui/react-aspect-ratio'; interface ImageDropzoneProps { id?: string; @@ -17,14 +18,79 @@ interface ImageDropzoneProps { function getImageData(file: File) { const dataTransfer = new DataTransfer(); - dataTransfer.items.add(file); - const files = dataTransfer.files; const displayUrl = URL.createObjectURL(file); return { files, displayUrl }; } +export const resizeImage = async ( + file: File, + options = { + width: 1280, + height: 720, + contentType: 'image/jpeg', + quality: 0.9, + } +): Promise => { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = function () { + const canvas = document.createElement('canvas'); + canvas.width = options.width; + canvas.height = options.height; + const ctx = canvas.getContext('2d'); + + if (!ctx) { + reject(new Error('Failed to get canvas context')); + return; + } + + // Calculate dimensions to maintain aspect ratio and fill the canvas + const scale = Math.max( + canvas.width / img.width, + canvas.height / img.height + ); + const x = canvas.width / 2 - (img.width / 2) * scale; + const y = canvas.height / 2 - (img.height / 2) * scale; + + // Draw image on canvas, cropping if necessary + ctx.drawImage(img, x, y, img.width * scale, img.height * scale); + + canvas.toBlob( + (blob) => { + if (!blob) { + reject(new Error('Failed to create blob')); + return; + } + // Create a new File object + const resizedFile = new File([blob], file.name, { + type: options.contentType, + lastModified: new Date().getTime(), + }); + resolve(resizedFile); + }, + options.contentType, + options.quality + ); + }; + + img.onerror = function () { + reject(new Error('Failed to load image')); + }; + + // Read the file and set the result as the img src + const reader = new FileReader(); + reader.onload = (e) => { + img.src = e.target?.result as string; + }; + reader.onerror = () => { + reject(new Error('Failed to read file')); + }; + reader.readAsDataURL(file); + }); +}; + const ImageDropzone = forwardRef( (props, ref) => { const { onChange, value, path, ...rest } = props; @@ -37,8 +103,9 @@ const ImageDropzone = forwardRef( if (!file) return; setIsUploading(true); try { + const processedFile = await resizeImage(file); const data = new FormData(); - data.set('file', file); + data.set('file', processedFile); data.set('path', path); const res = await fetch('/api/upload', { method: 'POST', @@ -47,11 +114,11 @@ const ImageDropzone = forwardRef( if (!res.ok) { throw new Error(await res.text()); } - onChange(getImageUrl('/' + path + '/' + file.name)); + console.log(getImageUrl('/' + path + '/' + processedFile.name)); + onChange(getImageUrl('/' + path + '/' + processedFile.name)); toast.success('Image uploaded successfully'); setIsUploading(false); - } catch (e: any) { - // Handle errors here + } catch (e) { setIsUploading(false); toast.error('Error uploading image'); console.error(e); @@ -59,12 +126,15 @@ const ImageDropzone = forwardRef( }; const onDrop = useCallback( - (acceptedFiles: File[]) => { + async (acceptedFiles: File[]) => { if (acceptedFiles.length > 0) { - const { displayUrl } = getImageData(acceptedFiles[0]); + const processedFile = await resizeImage(acceptedFiles[0]); + const { displayUrl } = getImageData(processedFile); + + console.log(displayUrl); setPreview(displayUrl); - onSubmit(acceptedFiles[0]); + onSubmit(processedFile); } }, [onChange] @@ -72,9 +142,9 @@ const ImageDropzone = forwardRef( const { getRootProps, getInputProps } = useDropzone({ accept: { - 'image/*': ['.png', '.jpg', '.jpeg'], + 'image/*': ['.png', '.jpg', '.gif'], }, - maxSize: 5 * 1024 * 1024, // 5 MB + maxSize: 2 * 1024 * 1024, // 2 MB maxFiles: 1, onDrop, }); @@ -82,33 +152,34 @@ const ImageDropzone = forwardRef( return (
{isUploading ? ( -
+
Uploading image...
) : preview ? ( - <> +
{ onChange(null); setPreview(''); }} /> -
- +
- +
) : (
@@ -117,7 +188,7 @@ const ImageDropzone = forwardRef( Drag and drop your thumbnail to upload... Or just click here!

- Maximum image file size is 1MB. Best resolution of 1920 x 1080. + Maximum image file size is 5MB. Best resolution is 1920 x 1080. Aspect ratio of 16:9

diff --git a/packages/app/app/studio/[organization]/library/components/upload/UploadVideoForm.tsx b/packages/app/app/studio/[organization]/library/components/upload/UploadVideoForm.tsx index 9c4b7746a..d90546bce 100644 --- a/packages/app/app/studio/[organization]/library/components/upload/UploadVideoForm.tsx +++ b/packages/app/app/studio/[organization]/library/components/upload/UploadVideoForm.tsx @@ -77,7 +77,7 @@ const UploadVideoForm = ({ .then(async (session) => { onFinish(); - const state = await createStateAction({ + await createStateAction({ state: { sessionId: session._id, type: StateType.video, @@ -103,7 +103,7 @@ const UploadVideoForm = ({ name="name" render={({ field }) => ( -
+
Video title
@@ -118,7 +118,7 @@ const UploadVideoForm = ({ name="description" render={({ field }) => ( -
+
Description
@@ -135,7 +135,7 @@ const UploadVideoForm = ({ Visibility -
+
{field.value ? ( <> @@ -151,7 +151,7 @@ const UploadVideoForm = ({ - + {!field.value ? (
field.onChange(true)} @@ -225,7 +225,7 @@ const UploadVideoForm = ({ > {isLoading ? ( <> - + Please wait... ) : ( From 3c610d1eadc772e96a9f9292cf7ebc4aa01fc813 Mon Sep 17 00:00:00 2001 From: xvoorvaa Date: Tue, 13 Aug 2024 20:15:50 +0200 Subject: [PATCH 03/17] =?UTF-8?q?=F0=9F=9A=9A:=20Moved=20resizeImage=20to?= =?UTF-8?q?=20/lib?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[session]/components/ImageDropzone.tsx | 69 +--------------- packages/app/lib/utils/resizeImage.ts | 79 +++++++++++++++++++ 2 files changed, 80 insertions(+), 68 deletions(-) create mode 100644 packages/app/lib/utils/resizeImage.ts diff --git a/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx b/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx index c61d49ad0..d0fc6924b 100644 --- a/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx +++ b/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx @@ -6,7 +6,7 @@ import { getImageUrl } from '@/lib/utils/utils'; import { toast } from 'sonner'; import { useCallback, useState, forwardRef } from 'react'; import { useDropzone } from 'react-dropzone'; -import { AspectRatio } from '@radix-ui/react-aspect-ratio'; +import { resizeImage } from '@/lib/utils/resizeImage'; interface ImageDropzoneProps { id?: string; @@ -24,73 +24,6 @@ function getImageData(file: File) { return { files, displayUrl }; } -export const resizeImage = async ( - file: File, - options = { - width: 1280, - height: 720, - contentType: 'image/jpeg', - quality: 0.9, - } -): Promise => { - return new Promise((resolve, reject) => { - const img = new Image(); - img.onload = function () { - const canvas = document.createElement('canvas'); - canvas.width = options.width; - canvas.height = options.height; - const ctx = canvas.getContext('2d'); - - if (!ctx) { - reject(new Error('Failed to get canvas context')); - return; - } - - // Calculate dimensions to maintain aspect ratio and fill the canvas - const scale = Math.max( - canvas.width / img.width, - canvas.height / img.height - ); - const x = canvas.width / 2 - (img.width / 2) * scale; - const y = canvas.height / 2 - (img.height / 2) * scale; - - // Draw image on canvas, cropping if necessary - ctx.drawImage(img, x, y, img.width * scale, img.height * scale); - - canvas.toBlob( - (blob) => { - if (!blob) { - reject(new Error('Failed to create blob')); - return; - } - // Create a new File object - const resizedFile = new File([blob], file.name, { - type: options.contentType, - lastModified: new Date().getTime(), - }); - resolve(resizedFile); - }, - options.contentType, - options.quality - ); - }; - - img.onerror = function () { - reject(new Error('Failed to load image')); - }; - - // Read the file and set the result as the img src - const reader = new FileReader(); - reader.onload = (e) => { - img.src = e.target?.result as string; - }; - reader.onerror = () => { - reject(new Error('Failed to read file')); - }; - reader.readAsDataURL(file); - }); -}; - const ImageDropzone = forwardRef( (props, ref) => { const { onChange, value, path, ...rest } = props; diff --git a/packages/app/lib/utils/resizeImage.ts b/packages/app/lib/utils/resizeImage.ts new file mode 100644 index 000000000..4d591c2d2 --- /dev/null +++ b/packages/app/lib/utils/resizeImage.ts @@ -0,0 +1,79 @@ +/** + * Resizes an image file to a specified width and height while maintaining aspect ratio. + * If the image's aspect ratio doesn't match the target dimensions, black bars are added. + * + * @param file - The original image file to be resized. + * @param [options] - Configuration options for the resize operation. + * @param [options.width=1280] - The target width of the resized image in pixels. + * @param [options.height=720] - The target height of the resized image in pixels. + * @param [options.contentType='image/jpeg'] - The MIME type of the output image. + * @param [options.quality=0.9] - The quality of the output image, between 0 and 1. + * @returns {Promise} A promise that resolves with the resized image as a new File object. + * @throws If there's an issue loading the image or creating the resized version. + */ +export const resizeImage = async ( + file: File, + options = { + width: 1280, + height: 720, + contentType: 'image/jpeg', + quality: 0.9, + } +): Promise => { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = function () { + const canvas = document.createElement('canvas'); + canvas.width = options.width; + canvas.height = options.height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + reject(new Error('Failed to get canvas context')); + return; + } + + ctx.fillStyle = 'black'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + const scale = Math.min( + canvas.width / img.width, + canvas.height / img.height + ); + const newWidth = img.width * scale; + const newHeight = img.height * scale; + const x = (canvas.width - newWidth) / 2; + const y = (canvas.height - newHeight) / 2; + + ctx.drawImage(img, x, y, newWidth, newHeight); + + canvas.toBlob( + (blob) => { + if (!blob) { + reject(new Error('Failed to create blob')); + return; + } + const resizedFile = new File([blob], file.name, { + type: options.contentType, + lastModified: new Date().getTime(), + }); + resolve(resizedFile); + }, + options.contentType, + options.quality + ); + }; + + img.onerror = function () { + reject(new Error('Failed to load image')); + }; + + const reader = new FileReader(); + reader.onload = (e) => { + img.src = e.target?.result as string; + }; + reader.onerror = () => { + reject(new Error('Failed to read file')); + }; + reader.readAsDataURL(file); + }); +}; From bbd0770a328a073fe88fbb4cd5db87a898853306 Mon Sep 17 00:00:00 2001 From: xvoorvaa Date: Thu, 15 Aug 2024 17:32:08 +0200 Subject: [PATCH 04/17] =?UTF-8?q?=E2=9C=8F=EF=B8=8F:=20Fix=20typos.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[session]/components/ImageDropzone.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx b/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx index d0fc6924b..67c47ec33 100644 --- a/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx +++ b/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx @@ -1,6 +1,6 @@ 'use client'; -import NextImage from 'next/image'; +import Image from 'next/image'; import { ImageUp, X } from 'lucide-react'; import { getImageUrl } from '@/lib/utils/utils'; import { toast } from 'sonner'; @@ -36,10 +36,10 @@ const ImageDropzone = forwardRef( if (!file) return; setIsUploading(true); try { - const processedFile = await resizeImage(file); const data = new FormData(); - data.set('file', processedFile); + data.set('file', file); data.set('path', path); + const res = await fetch('/api/upload', { method: 'POST', body: data, @@ -47,8 +47,8 @@ const ImageDropzone = forwardRef( if (!res.ok) { throw new Error(await res.text()); } - console.log(getImageUrl('/' + path + '/' + processedFile.name)); - onChange(getImageUrl('/' + path + '/' + processedFile.name)); + onChange(getImageUrl('/' + path + '/' + file.name)); + toast.success('Image uploaded successfully'); setIsUploading(false); } catch (e) { @@ -64,8 +64,6 @@ const ImageDropzone = forwardRef( const processedFile = await resizeImage(acceptedFiles[0]); const { displayUrl } = getImageData(processedFile); - console.log(displayUrl); - setPreview(displayUrl); onSubmit(processedFile); } @@ -99,7 +97,7 @@ const ImageDropzone = forwardRef( }} />
- ( Drag and drop your thumbnail to upload... Or just click here!

- Maximum image file size is 5MB. Best resolution is 1920 x 1080. + Maximum image file size is 2MB. Best resolution is 1920 x 1080. Aspect ratio of 16:9

From 8a7755041843c492d13ea254943c8a25eac4d6b6 Mon Sep 17 00:00:00 2001 From: xvoorvaa Date: Wed, 21 Aug 2024 12:02:05 +0300 Subject: [PATCH 05/17] =?UTF-8?q?=F0=9F=90=9B:=20Thumbnail=20uploads=20AFT?= =?UTF-8?q?ER=20session=20creation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[session]/components/ImageDropzone.tsx | 19 +++--------- .../[organization]/library/[session]/page.tsx | 4 --- .../components/upload/UploadVideoForm.tsx | 30 +++++++++++++++---- packages/app/lib/schema.ts | 2 +- packages/app/lib/utils/resizeImage.ts | 18 ++++++----- 5 files changed, 40 insertions(+), 33 deletions(-) diff --git a/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx b/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx index 67c47ec33..6760e477f 100644 --- a/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx +++ b/packages/app/app/studio/[organization]/library/[session]/components/ImageDropzone.tsx @@ -11,8 +11,8 @@ import { resizeImage } from '@/lib/utils/resizeImage'; interface ImageDropzoneProps { id?: string; placeholder?: string; - onChange: (files: string | null) => void; - value: string | null | undefined; + onChange: (file: File | string | null) => void; + value: File | string | null | undefined; path: string; } @@ -36,18 +36,7 @@ const ImageDropzone = forwardRef( if (!file) return; setIsUploading(true); try { - const data = new FormData(); - data.set('file', file); - data.set('path', path); - - const res = await fetch('/api/upload', { - method: 'POST', - body: data, - }); - if (!res.ok) { - throw new Error(await res.text()); - } - onChange(getImageUrl('/' + path + '/' + file.name)); + onChange(file); toast.success('Image uploaded successfully'); setIsUploading(false); @@ -83,7 +72,7 @@ const ImageDropzone = forwardRef( return (
{isUploading ? ( -
+
Uploading image...
) : preview ? ( diff --git a/packages/app/app/studio/[organization]/library/[session]/page.tsx b/packages/app/app/studio/[organization]/library/[session]/page.tsx index 5c25e5586..aac94d597 100644 --- a/packages/app/app/studio/[organization]/library/[session]/page.tsx +++ b/packages/app/app/studio/[organization]/library/[session]/page.tsx @@ -20,7 +20,6 @@ import { import UploadToYoutubeButton from './components/UploadToYoutubeButton'; import { fetchOrganization } from '@/lib/services/organizationService'; import { getVideoUrlAction } from '@/lib/actions/livepeer'; -import UploadTwitterButton from './components/UploadTwitterButton'; const EditSession = async ({ params, searchParams }: studioPageParams) => { const organization = await fetchOrganization({ @@ -30,12 +29,9 @@ const EditSession = async ({ params, searchParams }: studioPageParams) => { session: params.session, }); - // Check if session exists and has a playbackId. If not, return a 'not found' response. if (!session?.playbackId) return notFound(); const videoUrl = await getVideoUrlAction(session); - - // If we couldn't get a video URL, return a 'not found' response. if (!videoUrl) return notFound(); return ( diff --git a/packages/app/app/studio/[organization]/library/components/upload/UploadVideoForm.tsx b/packages/app/app/studio/[organization]/library/components/upload/UploadVideoForm.tsx index d90546bce..85ab6ec8f 100644 --- a/packages/app/app/studio/[organization]/library/components/upload/UploadVideoForm.tsx +++ b/packages/app/app/studio/[organization]/library/components/upload/UploadVideoForm.tsx @@ -16,10 +16,13 @@ import { import { Input } from '@/components/ui/input'; import { sessionSchema } from '@/lib/schema'; import { toast } from 'sonner'; -import { createSessionAction } from '@/lib/actions/sessions'; +import { + createSessionAction, + updateSessionAction, +} from '@/lib/actions/sessions'; import { Loader2, Earth, Lock, ChevronDown } from 'lucide-react'; import Dropzone from './Dropzone'; -import { getFormSubmitStatus } from '@/lib/utils/utils'; +import { getFormSubmitStatus, getImageUrl } from '@/lib/utils/utils'; import { DialogClose } from '@/components/ui/dialog'; import { SessionType } from 'streameth-new-server/src/interfaces/session.interface'; import { createStateAction } from '@/lib/actions/state'; @@ -50,7 +53,6 @@ const UploadVideoForm = ({ defaultValues: { name: '', description: '', - coverImage: '', assetId: '', }, }); @@ -65,6 +67,7 @@ const UploadVideoForm = ({ createSessionAction({ session: { ...values, + coverImage: '', organizationId, speakers: [], start: 0, @@ -75,8 +78,25 @@ const UploadVideoForm = ({ }, }) .then(async (session) => { - onFinish(); + const file = values.coverImage as File; + const data = new FormData(); + data.set('file', file); + data.set('path', `sessions/${eventId}`); + + const res = await fetch('/api/upload', { + method: 'POST', + body: data, + }); + if (!res.ok) { + throw new Error(await res.text()); + } + const imageUrl = getImageUrl('/session/${eventId}/' + file.name); + console.log(imageUrl); + + await updateSessionAction({ + session: { ...session, coverImage: imageUrl }, + }); await createStateAction({ state: { sessionId: session._id, @@ -85,6 +105,7 @@ const UploadVideoForm = ({ organizationId: session.organizationId, }, }); + onFinish(); }) .catch((e) => { console.log(e); @@ -177,7 +198,6 @@ const UploadVideoForm = ({ )} /> ( diff --git a/packages/app/lib/schema.ts b/packages/app/lib/schema.ts index d28590d20..918122414 100644 --- a/packages/app/lib/schema.ts +++ b/packages/app/lib/schema.ts @@ -119,7 +119,7 @@ export const sessionSchema = z.object({ .max(600, { message: 'Description is too long. The maximum length is 600 characters.', }), - coverImage: z.string().optional(), + coverImage: z.union([z.string(), z.instanceof(File)]).optional(), assetId: z.string().min(1, { message: 'Please upload a video.' }), published: z.boolean().default(false), }); diff --git a/packages/app/lib/utils/resizeImage.ts b/packages/app/lib/utils/resizeImage.ts index 4d591c2d2..e65e938f5 100644 --- a/packages/app/lib/utils/resizeImage.ts +++ b/packages/app/lib/utils/resizeImage.ts @@ -1,6 +1,6 @@ /** - * Resizes an image file to a specified width and height while maintaining aspect ratio. - * If the image's aspect ratio doesn't match the target dimensions, black bars are added. + * Resizes an image file to a specified width and height without stretching. + * The image is scaled to fit within the target dimensions, and black bars are added to fill the remaining space. * * @param file - The original image file to be resized. * @param [options] - Configuration options for the resize operation. @@ -22,7 +22,7 @@ export const resizeImage = async ( ): Promise => { return new Promise((resolve, reject) => { const img = new Image(); - img.onload = function () { + img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = options.width; canvas.height = options.height; @@ -39,10 +39,12 @@ export const resizeImage = async ( canvas.width / img.width, canvas.height / img.height ); - const newWidth = img.width * scale; - const newHeight = img.height * scale; - const x = (canvas.width - newWidth) / 2; - const y = (canvas.height - newHeight) / 2; + + const newWidth = Math.round(img.width * scale); + const newHeight = Math.round(img.height * scale); + + const x = Math.round((canvas.width - newWidth) / 2); + const y = Math.round((canvas.height - newHeight) / 2); ctx.drawImage(img, x, y, newWidth, newHeight); @@ -63,7 +65,7 @@ export const resizeImage = async ( ); }; - img.onerror = function () { + img.onerror = () => { reject(new Error('Failed to load image')); }; From e0808ad79fa95f3a8b6b5622513de4182e15ba3b Mon Sep 17 00:00:00 2001 From: xvoorvaa Date: Wed, 21 Aug 2024 13:48:36 +0300 Subject: [PATCH 06/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F:=20Reverted=20last=20f?= =?UTF-8?q?ew=20commits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[session]/components/EditSessionForm.tsx | 10 +++++----- .../[session]/components/ImageDropzone.tsx | 19 ++++++++++++++---- .../components/upload/UploadVideoForm.tsx | 20 +------------------ packages/app/lib/schema.ts | 2 +- 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/packages/app/app/studio/[organization]/library/[session]/components/EditSessionForm.tsx b/packages/app/app/studio/[organization]/library/[session]/components/EditSessionForm.tsx index 359ca6035..8264f0130 100644 --- a/packages/app/app/studio/[organization]/library/[session]/components/EditSessionForm.tsx +++ b/packages/app/app/studio/[organization]/library/[session]/components/EditSessionForm.tsx @@ -88,7 +88,7 @@ const EditSessionForm = ({ Video title @@ -104,7 +104,7 @@ const EditSessionForm = ({ Description