Skip to content

Commit

Permalink
Merge pull request #350 from AppQuality/develop
Browse files Browse the repository at this point in the history
refactor new feat media attachments
  • Loading branch information
iacopolea authored May 17, 2024
2 parents 5ac60a8 + a27f065 commit 478c151
Show file tree
Hide file tree
Showing 13 changed files with 428 additions and 596 deletions.
14 changes: 7 additions & 7 deletions src/stories/chat/_types.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { PlaceholderOptions } from "@tiptap/extension-placeholder";
import { BubbleMenuProps, EditorOptions } from "@tiptap/react";
import { isError } from "util";

type validationStatus = "success" | "warning" | "error";

export type SuggestedUser = { id: number; name: string; email: string };

export interface ChatEditorArgs extends Partial<EditorOptions> {
author: Author;
messageBadFileFormat: string;

placeholderOptions?: Partial<PlaceholderOptions>;
hasFloatingMenu?: boolean;
hasButtonsMenu?: boolean;
Expand All @@ -20,7 +17,7 @@ export interface ChatEditorArgs extends Partial<EditorOptions> {
italic?: string;
mention?: string;
//react node
attachment?: string | React.ReactNode;
attachment?: React.ReactNode;
};
mention?: {
noResults?: string;
Expand All @@ -43,10 +40,13 @@ export interface EditorHeaderArgs {
validation?: validationStatus;
}

export interface FileItem extends File {
isLoadingMedia: boolean;
export interface CommentMedia {
id: string;
type: string;
name?: string;
isLoadingMedia?: boolean;
isError?: boolean;
internal_id: string;
url?: string;
}

export interface FloatingMenuArgs extends Partial<BubbleMenuProps> {}
26 changes: 14 additions & 12 deletions src/stories/chat/context/chatContext.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { Editor } from "@tiptap/react";
import React, { createContext, useContext, useMemo, useState } from "react";
import { FileItem, SuggestedUser } from "../_types";
import React, {
createContext,
useContext,
useMemo,
useState,
} from "react";
import { CommentMedia, SuggestedUser } from "../_types";

export type ChatContextType = {
triggerSave: () => void;
editor?: Editor;
setEditor: React.Dispatch<React.SetStateAction<Editor | undefined>>;
addThumbnails: (props: { files: FileItem[] }) => void;
addThumbnails: (props: { files: (File & CommentMedia)[] }) => void;
removeThumbnail: (index: number) => void;
thumbnails: FileItem[];
thumbnails: CommentMedia[];
mentionableUsers: (props: { query: string }) => SuggestedUser[];
afterUploadCallback: (failed: string[]) => void;
clearInput: () => void;
Expand All @@ -29,13 +34,13 @@ export const ChatContextProvider = ({
children,
}: {
onSave?: (editor: Editor, mentions: SuggestedUser[]) => void;
onFileUpload?: (files: FileItem[]) => Promise<Data>;
onFileUpload?: (files: (File & CommentMedia)[]) => Promise<Data>;
onDeleteThumbnail: (id: string) => void;
children: React.ReactNode;
setMentionableUsers: (props: { query: string }) => SuggestedUser[];
}) => {
const [editor, setEditor] = useState<Editor | undefined>();
const [thumbnails, setThumbnails] = useState<FileItem[]>([]);
const [thumbnails, setThumbnails] = useState<CommentMedia[]>([]);

const getMentions = (editor: Editor) => {
const result: SuggestedUser[] = [];
Expand Down Expand Up @@ -64,7 +69,7 @@ export const ChatContextProvider = ({
afterUploadCallback: (failed: string[]) => {
setThumbnails(
thumbnails.map((file) => {
if (failed.includes(file.name)) {
if (failed.includes(file.id)) {
file.isLoadingMedia = false;
//file.isError = true;
} else {
Expand All @@ -76,18 +81,16 @@ export const ChatContextProvider = ({
);
},

addThumbnails: ({ files }: { files: FileItem[] }) => {
files.forEach((file) => (file.isLoadingMedia = true));
addThumbnails: ({ files }: { files: (File & CommentMedia)[] }) => {
setThumbnails((prev) => [...prev, ...files]);

if (onFileUpload) {
onFileUpload(files).then((data: Data) => {
const failed = data.failed?.map((f) => f.name);

setThumbnails((prev) => {
return prev.map((file) => {
file.isLoadingMedia = false;
if (failed?.length && failed.includes(file.name)) {
if (failed?.length && failed.includes(file.id)) {
file.isError = true;
} else {
file.isError = false;
Expand All @@ -103,7 +106,6 @@ export const ChatContextProvider = ({
editor.commands.clearContent();
}
if (thumbnails.length > 0) setThumbnails([]);

},

onDeleteThumbnail: (id: string) => {
Expand Down
46 changes: 46 additions & 0 deletions src/stories/chat/hooks/useMedia.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useToast, Notification } from "../../notifications";
import { CommentMedia } from "../_types";
import { v4 as uuidv4 } from "uuid";

export const acceptedMediaTypes = /^(image|video)\//;

export function useMedia() {
const { addToast } = useToast();
function getValidMedia(data: FileList): File[] {
const wrongFiles = Array.from(data).filter(
(file) => !acceptedMediaTypes.test(file.type)
);
if (wrongFiles.length) {
addToast(
({ close }) => (
<Notification
onClose={close}
type="error"
message={
wrongFiles.length === 1
? `${wrongFiles[0].name} not supported, please upload video or image only`
: "Some attachments are not supported, please upload video or image only"
}
isPrimary
/>
),
{ placement: "top" }
);
}
return Array.from(data).filter((file) =>
acceptedMediaTypes.test(file.type)
);
}

function getMedia(data: FileList) {
return getValidMedia(data).map((file) => {
return Object.assign(file, {
url: URL.createObjectURL(file),
isLoadingMedia: true,
id: uuidv4(),
});
});
}

return { getMedia };
}
32 changes: 18 additions & 14 deletions src/stories/chat/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { Button } from "../buttons/button";
import { Col } from "../grid/col";
import { Grid } from "../grid/grid";
import { Row } from "../grid/row";
import { ChatEditorArgs, FileItem, SuggestedUser } from "./_types";
import { Comment, MediaType } from "./parts/comment";
import { ChatEditorArgs, CommentMedia, SuggestedUser } from "./_types";
import { Comment } from "./parts/comment";
import { theme } from "../theme";
import { Data } from "./context/chatContext";
import { ToastProvider } from "@zendeskgarden/react-notifications";
Expand All @@ -27,25 +27,26 @@ interface EditorStoryArgs extends ChatEditorArgs {
};
message: string;
date: string;
media?: MediaType[];
media?: CommentMedia[];
}[];
editorText?: string;
background?: string;
onSave: (editor: TipTapEditor, mentions: SuggestedUser[]) => void;
onFileUpload?: (files: FileItem[]) => Promise<Data>;
onFileUpload?: (files: (File & CommentMedia)[]) => Promise<Data>;
placeholderOptions?: Partial<PlaceholderOptions>;
}

const ChatPanel = ({ background, ...args }: EditorStoryArgs) => {
const { editor, triggerSave, clearInput } = useChatContext();
const { triggerSave, clearInput } = useChatContext();

return (
<Chat>
<Chat.Header>Titolone</Chat.Header>
<Chat.Comments chatBkg={background}>
{args.comments?.map((comment) => (
{args.comments?.map((comment, index) => (
<Comment
{...comment}
key={index}
header={{ title: "BUG1", message: "Attachment" }}
>
<>altre cose</>
Expand Down Expand Up @@ -154,9 +155,9 @@ const Template: StoryFn<EditorStoryArgs> = ({ children, ...args }) => {
console.log("internal_id - ", id);
}}

/*setIsMediaUploading={function (value: boolean): void {
throw new Error("Function not implemented.");
}}*/
/*setIsMediaUploading={function (value: boolean): void {
throw new Error("Function not implemented.");
}}*/
>
<ChatPanel {...args} />
</ChatProvider>
Expand All @@ -170,11 +171,11 @@ const Template: StoryFn<EditorStoryArgs> = ({ children, ...args }) => {
const defaultArgs: EditorStoryArgs = {
children:
"<p>I'm <em>a</em> <strong>stupid</strong> <code>editor</code>!</p>",
onSave: (editor: TipTapEditor, mentions) => {},
onSave: (editor: TipTapEditor, mentions) => { },
author: {
avatar: "LC",
},
onUpdate: ({ editor }) => {},
onUpdate: ({ editor }) => { },
comments: [
{
message: "Hi, I'm a comment",
Expand Down Expand Up @@ -212,17 +213,20 @@ const defaultArgs: EditorStoryArgs = {
{
url: "https://images.unsplash.com/photo-1638799692504-9b3c0093d54d?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
type: "image",
id: 1,
id: "1",
isLoadingMedia: false,
name: ""
},
{
url: "https://images.unsplash.com/photo-1544085311-11a028465b03?q=80&w=1932&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
type: "image",
id: 2,
id: "2",
isLoadingMedia: false,
name: ""
},
],
},
],
messageBadFileFormat: "Format not supported, please upload video or image",
};

export const Default = Template.bind({});
Expand Down
64 changes: 64 additions & 0 deletions src/stories/chat/parts/MediaLightbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Lightbox } from "../../lightbox";
import { Slider } from "../../slider";
import { CommentMedia } from "../_types";
import { Player } from "../../player";

interface MediaLightBoxProps {
isOpen: boolean;
header: React.ReactNode;
onClose: () => void;
slideChange: (index: number) => void;
selectedImageIndex: number;
thumbnails: CommentMedia[];
videoRefs: React.MutableRefObject<Array<HTMLVideoElement | null>>;
details?: React.ReactNode;
};

const MediaLightBox = ({header, onClose, slideChange, selectedImageIndex, thumbnails, videoRefs, isOpen, details}: MediaLightBoxProps) => {
if (!isOpen) {
return null;
}
return (
<Lightbox onClose={onClose}>
<Lightbox.Header>{header}</Lightbox.Header>
<Lightbox.Body>
<Lightbox.Body.Main style={{ flex: details ? 2 : 3 }}>
<Slider
prevArrow={<Slider.PrevButton isBright />}
nextArrow={<Slider.NextButton isBright />}
onSlideChange={slideChange}
initialSlide={selectedImageIndex}
>
{thumbnails.map((item) => (
<Slider.Slide key={item.id}>
{item.type.includes("image") && (
<img
src={item.url}
alt={`media ${item.name}`}
style={{ maxHeight: "100%", height: "auto" }}
/>
)}
{item.type.includes("video") && item.url && (
<Player
ref={(ref) => {
videoRefs.current.push(ref);
}}
url={item.url}
/>
)}
</Slider.Slide>
))}
</Slider>
</Lightbox.Body.Main>
{details && (
<Lightbox.Body.Details style={{ flex: 1 }}>
{details}
</Lightbox.Body.Details>
)}
</Lightbox.Body>
<Lightbox.Close aria-label="Close modal" />
</Lightbox>
);
}

export default MediaLightBox;
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const StyledDeleteThumbnailX = styled.div`
width: 32px;
height: 32px;
opacity: 0;
transition: opacity 0.2s;
z-index: 2;
`;

Expand Down
Loading

0 comments on commit 478c151

Please sign in to comment.