Skip to content

Commit ca67165

Browse files
committed
feat: paste and drag and drop image, with loader
1 parent d12ff06 commit ca67165

File tree

3 files changed

+150
-26
lines changed

3 files changed

+150
-26
lines changed

apps/frontend/src/components/media/new.uploader.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useVariables } from '@gitroom/react/helpers/variable.context';
1313
import Compressor from '@uppy/compressor';
1414
import { useT } from '@gitroom/react/translation/get.transation.service.client';
1515
import { useToaster } from '@gitroom/react/toaster/toaster';
16+
import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';
1617

1718
export function MultipartFileUploader({
1819
onUploadSuccess,
@@ -59,6 +60,7 @@ export function useUppyUploader(props: {
5960
onUploadSuccess: (result: UploadResult) => void;
6061
allowedFileTypes: string;
6162
}) {
63+
const setLocked = useLaunchStore(state => state.setLocked);
6264
const toast = useToaster();
6365
const { storageProvider, backendUrl, disableImageCompression } =
6466
useVariables();
@@ -135,12 +137,17 @@ export function useUppyUploader(props: {
135137
}
136138
// Set additional metadata when a file is added
137139
uppy2.on('file-added', (file) => {
140+
setLocked(true);
138141
uppy2.setFileMeta(file.id, {
139142
useCloudflare: storageProvider === 'cloudflare' ? 'true' : 'false', // Example of adding a custom field
140143
// Add more fields as needed
141144
});
142145
});
146+
uppy2.on('error', (result) => {
147+
setLocked(false);
148+
})
143149
uppy2.on('complete', (result) => {
150+
setLocked(false);
144151
onUploadSuccess(result);
145152
});
146153
uppy2.on('upload-success', (file, response) => {

apps/frontend/src/components/new-launch/editor.tsx

Lines changed: 108 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import React, {
77
useMemo,
88
useRef,
99
useState,
10+
ClipboardEvent,
1011
} from 'react';
1112
import { CopilotTextarea } from '@copilotkit/react-textarea';
1213
import clsx from 'clsx';
@@ -32,6 +33,10 @@ import {
3233
LinkedinCompanyPop,
3334
ShowLinkedinCompany,
3435
} from '@gitroom/frontend/components/launches/helpers/linkedin.component';
36+
import { DropEvent, FileRejection, useDropzone } from 'react-dropzone';
37+
import { useUppyUploader } from '@gitroom/frontend/components/media/new.uploader';
38+
import { UploadResult } from '@uppy/core';
39+
import { ProgressBar } from '@uppy/react';
3540
export const EditorWrapper: FC<{
3641
totalPosts: number;
3742
value: string;
@@ -46,6 +51,8 @@ export const EditorWrapper: FC<{
4651
addInternalValue,
4752
addGlobalValue,
4853
setInternalValueMedia,
54+
appendInternalValueMedia,
55+
appendGlobalValueMedia,
4956
setGlobalValueMedia,
5057
changeOrderGlobal,
5158
changeOrderInternal,
@@ -77,6 +84,8 @@ export const EditorWrapper: FC<{
7784
setGlobalValue: state.setGlobalValue,
7885
setInternalValue: state.setInternalValue,
7986
totalChars: state.totalChars,
87+
appendInternalValueMedia: state.appendInternalValueMedia,
88+
appendGlobalValueMedia: state.appendGlobalValueMedia,
8089
}))
8190
);
8291

@@ -101,7 +110,7 @@ export const EditorWrapper: FC<{
101110
}
102111

103112
return global;
104-
}, [current, internal, global]);
113+
}, [internal, global]);
105114

106115
const setValue = useCallback(
107116
(value: string[]) => {
@@ -165,6 +174,17 @@ export const EditorWrapper: FC<{
165174
[current, global, internal]
166175
);
167176

177+
const appendImages = useCallback(
178+
(index: number) => (value: any[]) => {
179+
if (internal) {
180+
return appendInternalValueMedia(current, index, value);
181+
}
182+
183+
return appendGlobalValueMedia(index, value);
184+
},
185+
[current, global, internal]
186+
);
187+
168188
const changeOrder = useCallback(
169189
(index: number) => (direction: 'up' | 'down') => {
170190
if (internal) {
@@ -274,6 +294,7 @@ export const EditorWrapper: FC<{
274294
validateChars={true}
275295
identifier={internalFromAll?.identifier || 'global'}
276296
totalChars={totalChars}
297+
appendImages={appendImages(index)}
277298
/>
278299
</div>
279300
<div className="flex flex-col items-center gap-[10px]">
@@ -337,6 +358,7 @@ export const Editor: FC<{
337358
allValues?: any[];
338359
onChange: (value: string) => void;
339360
setImages?: (value: any[]) => void;
361+
appendImages?: (value: any[]) => void;
340362
autoComplete?: boolean;
341363
validateChars?: boolean;
342364
identifier?: string;
@@ -350,13 +372,60 @@ export const Editor: FC<{
350372
autoComplete,
351373
validateChars,
352374
identifier,
375+
appendImages,
353376
} = props;
354377
const user = useUser();
355378
const [id] = useState(makeId(10));
356379
const newRef = useRef<any>(null);
357380
const [emojiPickerOpen, setEmojiPickerOpen] = useState(false);
381+
const [isUploading, setIsUploading] = useState(false);
358382
const t = useT();
359383

384+
const uppy = useUppyUploader({
385+
onUploadSuccess: (result: UploadResult<any, any>) => {
386+
appendImages([
387+
...result.successful.map((p) => ({
388+
id: p.response.body.saved.id,
389+
path: p.response.body.saved.path,
390+
})),
391+
]);
392+
uppy.clear();
393+
},
394+
allowedFileTypes: 'image/*,video/mp4',
395+
});
396+
397+
const onDrop = useCallback(
398+
(acceptedFiles: File[]) => {
399+
for (const file of acceptedFiles) {
400+
uppy.addFile(file);
401+
}
402+
},
403+
[uppy]
404+
);
405+
406+
const paste = useCallback(
407+
async (event: ClipboardEvent<HTMLDivElement> | File[]) => {
408+
// @ts-ignore
409+
const clipboardItems = event.clipboardData?.items;
410+
if (!clipboardItems) {
411+
return;
412+
}
413+
414+
// @ts-ignore
415+
for (const item of clipboardItems) {
416+
if (item.kind === 'file') {
417+
const file = item.getAsFile();
418+
if (file) {
419+
uppy.addFile(file);
420+
}
421+
}
422+
}
423+
},
424+
[uppy]
425+
);
426+
427+
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
428+
360429
const addText = useCallback(
361430
(emoji: string) => {
362431
setTimeout(() => {
@@ -367,8 +436,16 @@ export const Editor: FC<{
367436
[props.value, id]
368437
);
369438
return (
370-
<>
439+
<div {...getRootProps()}>
371440
<div className="relative bg-customColor2" id={id}>
441+
<div
442+
className={clsx(
443+
'absolute left-0 top-0 w-full h-full bg-black/70 z-[300] transition-all items-center justify-center flex text-white text-sm',
444+
!isDragActive ? 'pointer-events-none opacity-0' : 'opacity-100'
445+
)}
446+
>
447+
Drop your files here to upload
448+
</div>
372449
<div className="flex gap-[5px] bg-customColor55 border-b border-t border-customColor3 justify-center items-center p-[5px]">
373450
<SignatureBox editor={newRef?.current?.editor!} />
374451
<UText
@@ -401,30 +478,35 @@ export const Editor: FC<{
401478
</div>
402479
</div>
403480
</div>
404-
<CopilotTextarea
405-
disableBranding={true}
406-
ref={newRef}
407-
className={clsx(
408-
'!min-h-40 p-2 overflow-x-hidden scrollbar scrollbar-thumb-[#612AD5] bg-customColor2 outline-none',
409-
props.totalPosts > 1 && '!max-h-80'
410-
)}
411-
value={props.value}
412-
onChange={(e) => {
413-
props?.onChange?.(e.target.value);
414-
}}
415-
// onPaste={props.onPaste}
416-
placeholder={t('write_your_reply', 'Write your post...')}
417-
autosuggestionsConfig={{
418-
textareaPurpose: `Assist me in writing social media posts.`,
419-
chatApiConfigs: {
420-
suggestionsApiConfig: {
421-
maxTokens: 20,
422-
stop: ['.', '?', '!'],
481+
<div className="relative">
482+
<div className="pointer-events-none absolute end-0 bottom-0 z-[200]">
483+
<ProgressBar id={`prog-${num}`} uppy={uppy} />
484+
</div>
485+
<CopilotTextarea
486+
disableBranding={true}
487+
ref={newRef}
488+
className={clsx(
489+
'!min-h-40 p-2 overflow-x-hidden scrollbar scrollbar-thumb-[#612AD5] bg-customColor2 outline-none',
490+
props.totalPosts > 1 && '!max-h-80'
491+
)}
492+
value={props.value}
493+
onChange={(e) => {
494+
props?.onChange?.(e.target.value);
495+
}}
496+
onPaste={paste}
497+
placeholder={t('write_your_reply', 'Write your post...')}
498+
autosuggestionsConfig={{
499+
textareaPurpose: `Assist me in writing social media posts.`,
500+
chatApiConfigs: {
501+
suggestionsApiConfig: {
502+
maxTokens: 20,
503+
stop: ['.', '?', '!'],
504+
},
423505
},
424-
},
425-
disabled: user?.tier?.ai ? !autoComplete : true,
426-
}}
427-
/>
506+
disabled: user?.tier?.ai ? !autoComplete : true,
507+
}}
508+
/>
509+
</div>
428510
{validateChars && props.value.length < 6 && (
429511
<div className="px-3 text-sm bg-red-600 !text-white mb-[4px]">
430512
{t(
@@ -461,6 +543,6 @@ export const Editor: FC<{
461543
{props?.value?.length}/{props.totalChars}
462544
</div>
463545
)}
464-
</>
546+
</div>
465547
);
466548
};

apps/frontend/src/components/new-launch/store.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ interface StoreState {
102102
setTags: (tags: { label: string; value: string }[]) => void;
103103
setIsCreateSet: (isCreateSet: boolean) => void;
104104
setTotalChars?: (totalChars: number) => void;
105+
appendInternalValueMedia: (
106+
integrationId: string,
107+
index: number,
108+
media: { id: string; path: string }[]
109+
) => void;
110+
appendGlobalValueMedia: (
111+
index: number,
112+
media: { id: string; path: string }[]
113+
) => void;
105114
}
106115

107116
const initialState = {
@@ -452,4 +461,30 @@ export const useLaunchStore = create<StoreState>()((set) => ({
452461
set((state) => ({
453462
totalChars,
454463
})),
464+
appendInternalValueMedia: (
465+
integrationId: string,
466+
index: number,
467+
media: { id: string; path: string }[]
468+
) =>
469+
set((state) => ({
470+
internal: state.internal.map((item) =>
471+
item.integration.id === integrationId
472+
? {
473+
...item,
474+
integrationValue: item.integrationValue.map((v, i) =>
475+
i === index ? { ...v, media: [...v.media, ...media] } : v
476+
),
477+
}
478+
: item
479+
),
480+
})),
481+
appendGlobalValueMedia: (
482+
index: number,
483+
media: { id: string; path: string }[]
484+
) =>
485+
set((state) => ({
486+
global: state.global.map((item, i) =>
487+
i === index ? { ...item, media: [...item.media, ...media] } : item
488+
),
489+
})),
455490
}));

0 commit comments

Comments
 (0)