Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/paste pictures into editor #472

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ tmp
.env
# dependencies
node_modules
uploads

# IDEs and editors
/.idea
Expand Down
69 changes: 69 additions & 0 deletions apps/frontend/src/components/launches/add.edit.model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,13 @@ import { useUser } from '@gitroom/frontend/components/layout/user.context';
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
import Image from 'next/image';
import { weightedLength } from '@gitroom/helpers/utils/count.length';
import { useVariables } from '@gitroom/react/helpers/variable.context';
import Uppy from '@uppy/core';
import { getUppyUploadPlugin } from '@gitroom/react/helpers/uppy.upload';
import { uniqBy } from 'lodash';
import { Select } from '@gitroom/react/form/select';


function countCharacters(text: string, type: string): number {
if (type !== 'x') {
return text.length;
Expand Down Expand Up @@ -107,6 +111,7 @@ export const AddEditModal: FC<{
>([{ content: '' }]);

const fetch = useFetch();
const { backendUrl, storageProvider } = useVariables();

const user = useUser();

Expand Down Expand Up @@ -406,6 +411,42 @@ export const AddEditModal: FC<{
});
}, [data, postFor, selectedIntegrations]);

const uploadMediaToServer = (_file: File, index: number) => {
const uppy2 = new Uppy({
autoProceed: true,
restrictions: {
maxNumberOfFiles: 1,
allowedFileTypes: ['image/*', 'video/mp4'],
maxFileSize: 1000000000,
},
});

const { plugin, options } = getUppyUploadPlugin(
storageProvider,
fetch,
backendUrl
);
uppy2.use(plugin, options);
uppy2.addFile(_file);

uppy2.on('complete', (result) => {
if (result) {
const mediaToAdd = result?.successful![0].response?.body;

if (mediaToAdd && mediaToAdd.path && mediaToAdd.id) {
const newMedia: {
path: string;
id: string;
}[] = [
...(value[index].image || []),
{ path: mediaToAdd.path, id: mediaToAdd.id },
];
changeImage(index)({ target: { name: 'image', value: newMedia } });
}
}
});
};
Comment on lines +414 to +448
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and loading state management

While the upload implementation is functional, it could be improved with:

  1. Error handling for upload failures
  2. Loading state management to provide user feedback during uploads

Consider applying these improvements:

 const uploadMediaToServer = (_file: File, index: number) => {
+  const [isUploading, setIsUploading] = useState(false);
   const uppy2 = new Uppy({
     autoProceed: true,
     restrictions: {
       maxNumberOfFiles: 1,
       allowedFileTypes: ['image/*', 'video/mp4'],
       maxFileSize: 1000000000,
     },
   });

   const { plugin, options } = getUppyUploadPlugin(
     storageProvider,
     fetch,
     backendUrl
   );
   uppy2.use(plugin, options);
+  setIsUploading(true);
   uppy2.addFile(_file);

+  uppy2.on('upload-error', (file, error) => {
+    setIsUploading(false);
+    toaster.show(`Failed to upload ${file.name}: ${error.message}`, 'error');
+  });

   uppy2.on('complete', (result) => {
+    setIsUploading(false);
     if (result) {
       const mediaToAdd = result?.successful![0].response?.body;

       if (mediaToAdd && mediaToAdd.path && mediaToAdd.id) {
         const newMedia: {
           path: string;
           id: string;
         }[] = [
           ...(value[index].image || []),
           { path: mediaToAdd.path, id: mediaToAdd.id },
         ];
         changeImage(index)({ target: { name: 'image', value: newMedia } });
+      } else {
+        toaster.show('Invalid response from server', 'error');
       }
     }
   });
 };

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (1.9.4)

[error] 413-413: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


return (
<>
{user?.tier?.ai && (
Expand Down Expand Up @@ -529,6 +570,34 @@ export const AddEditModal: FC<{
preview="edit"
// @ts-ignore
onChange={changeValue(index)}
onPaste={(event) => {
const clipboardData =
event.clipboardData ||
(
window as Window &
typeof globalThis & {
clipboardData: DataTransfer;
}
).clipboardData;
if (clipboardData && clipboardData.items) {
for (const item of Array.from(
clipboardData.items
)) {
const mediaTypes = ['image', 'video'];

if (
mediaTypes.some(
(type) => item.type.indexOf(type) !== -1
)
) {
const media = item.getAsFile();
if(media){
uploadMediaToServer(media, index);
}
}
}
}
}}
/>

{showError &&
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/components/launches/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const Editor = forwardRef<
textareaPurpose: `Assist me in writing social media posts.`,
chatApiConfigs: {},
}}
onPaste={props.onPaste}
/>
) : (
<MDEditor {...props} ref={ref} />
Expand Down
22 changes: 8 additions & 14 deletions apps/frontend/src/components/media/media.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,25 +215,19 @@ export const MultiMediaComponent: FC<{
}> = (props) => {
const { name, label, error, description, onChange, value } = props;
const user = useUser();
useEffect(() => {
if (value) {
setCurrentMedia(value);
}
}, []);

const [modal, setShowModal] = useState(false);
const [mediaModal, setMediaModal] = useState(false);

const [currentMedia, setCurrentMedia] = useState(value);
const mediaDirectory = useMediaDirectory();

const changeMedia = useCallback(
(m: { path: string; id: string }) => {
const newMedia = [...(currentMedia || []), m];
setCurrentMedia(newMedia);
const newMedia = [...(value || []), m];
// setCurrentMedia(newMedia);
onChange({ target: { name, value: newMedia } });
},
[currentMedia]
[value]
);

const showModal = useCallback(() => {
Expand All @@ -246,11 +240,11 @@ export const MultiMediaComponent: FC<{

const clearMedia = useCallback(
(topIndex: number) => () => {
const newMedia = currentMedia?.filter((f, index) => index !== topIndex);
setCurrentMedia(newMedia);
const newMedia = value?.filter((f, index) => index !== topIndex);
// setCurrentMedia(newMedia);
onChange({ target: { name, value: newMedia } });
},
[currentMedia]
[value]
);

const designMedia = useCallback(() => {
Expand Down Expand Up @@ -310,8 +304,8 @@ export const MultiMediaComponent: FC<{
</Button>
</div>

{!!currentMedia &&
currentMedia.map((media, index) => (
{!!value &&
value.map((media, index) => (
<>
<div className="cursor-pointer w-[40px] h-[40px] border-2 border-tableBorder relative flex">
<div
Expand Down
3 changes: 2 additions & 1 deletion apps/frontend/src/components/media/new.uploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function MultipartFileUploaderAfter({
allowedFileTypes: string;
}) {
const {storageProvider, backendUrl} = useVariables();

const fetch = useFetch();

const uppy = useMemo(() => {
Expand All @@ -86,7 +87,7 @@ export function MultipartFileUploaderAfter({
onUploadSuccess(result);
});

uppy2.on('upload-success', (file, response) => {
uppy2.on('upload-success', (file, response) => {
// @ts-ignore
uppy.setFileState(file.id, {
// @ts-ignore
Expand Down