diff --git a/package-lock.json b/package-lock.json index 16fcc7e..5a77499 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pulsar-web", - "version": "0.5.2", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pulsar-web", - "version": "0.5.2", + "version": "0.6.0", "license": "MIT", "dependencies": { "@solidjs/router": "^0.15.4", diff --git a/package.json b/package.json index be404bf..5ab239e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pulsar-web", - "version": "0.5.2", + "version": "0.6.0", "description": "", "type": "module", "scripts": { diff --git a/src/api/channel/types.ts b/src/api/channel/types.ts index d46668a..58c94e8 100644 --- a/src/api/channel/types.ts +++ b/src/api/channel/types.ts @@ -51,7 +51,10 @@ interface IFetchChannelMessage { id: string; text: string; iv: string; + replyToId?: string; edited: boolean; + reactions: string[]; + attachments: string[]; ownerId: string; creationDate: number; } diff --git a/src/api/community/types.ts b/src/api/community/types.ts index f7fb5e7..f15a3e3 100644 --- a/src/api/community/types.ts +++ b/src/api/community/types.ts @@ -76,6 +76,7 @@ interface IFetchCommunityMembersResponse extends IResponseSuccess { interface IFetchCommunityMember { id: string; username: string; + nickname: string; } interface IFetchCommunityInvitesRequest { diff --git a/src/api/file/file.ts b/src/api/file/file.ts new file mode 100644 index 0000000..8aa0876 --- /dev/null +++ b/src/api/file/file.ts @@ -0,0 +1,105 @@ +import { callApi, HTTP } from "../tools"; +import { IResponseError } from "../types"; +import { + IUploadUserAvatarRequest, + IUploadUserAvatarResponse, + IUploadCommunityAvatarRequest, + IUploadCommunityAvatarResponse, + IFetchAttachmentRequest, + IFetchAttachmentResponse, + ICreateAttachmentRequest, + ICreateAttachmentResponse, + IFinishAttachmentRequest, + IFinishAttachmentResponse, + IFetchChunkRequest, + IUploadChunkRequest, + IUploadChunkResponse, +} from "./types"; + +const uploadUserAvatarApi = async ( + request: IUploadUserAvatarRequest, +): Promise => { + const formData = new FormData(); + formData.append("file", request.file); + formData.append("filename", request.filename); + + return await callApi( + HTTP.POST, + `file/avatar/user/upload`, + undefined, + false, + formData, + ); +}; + +const uploadCommunityAvatarApi = async ( + request: IUploadCommunityAvatarRequest, +): Promise => { + const formData = new FormData(); + formData.append("file", request.file); + formData.append("filename", request.filename); + formData.append("communityId", request.communityId); + + return await callApi( + HTTP.POST, + `file/avatar/community/upload`, + undefined, + false, + formData, + ); +}; + +const fetchAttachmentApi = async ( + request: IFetchAttachmentRequest, +): Promise => { + return await callApi(HTTP.GET, `file/attachment/${request.id}`); +}; + +const createAttachmentApi = async ( + request: ICreateAttachmentRequest, +): Promise => { + return await callApi(HTTP.POST, `file/attachment`, request); +}; + +const finishAttachmentApi = async ( + request: IFinishAttachmentRequest, +): Promise => { + return await callApi(HTTP.GET, `file/attachment/${request.id}/finish`); +}; + +const fetchChunkApi = async ( + request: IFetchChunkRequest, +): Promise => { + return await callApi( + HTTP.GET_BINARY, + `file/attachment/${request.attachmentId}/chunk/${request.index}`, + ); +}; + +const uploadChunkApi = async ( + request: IUploadChunkRequest, +): Promise => { + const formData = new FormData(); + formData.append("file", request.file); + formData.append("iv", request.iv); + formData.append("index", request.index.toString()); + formData.append("attachmentId", request.attachmentId); + + return await callApi( + HTTP.POST, + `file/attachment/${request.attachmentId}/chunk/${request.index}`, + undefined, + false, + formData, + ); +}; + +export { + uploadUserAvatarApi, + uploadCommunityAvatarApi, + fetchAttachmentApi, + createAttachmentApi, + finishAttachmentApi, + fetchChunkApi, + uploadChunkApi, +}; diff --git a/src/api/file/index.ts b/src/api/file/index.ts new file mode 100644 index 0000000..2c6e239 --- /dev/null +++ b/src/api/file/index.ts @@ -0,0 +1,2 @@ +export * from "./file"; +export * from "./types"; diff --git a/src/api/file/types.ts b/src/api/file/types.ts new file mode 100644 index 0000000..72245f3 --- /dev/null +++ b/src/api/file/types.ts @@ -0,0 +1,90 @@ +import { IResponseSuccess } from "../types"; + +interface IUploadUserAvatarRequest { + filename: string; + file: File; +} + +interface IUploadUserAvatarResponse extends IResponseSuccess {} + +interface IUploadCommunityAvatarRequest { + communityId: string; + filename: string; + file: File; +} + +interface IUploadCommunityAvatarResponse extends IResponseSuccess {} + +interface IFetchAttachment { + id: string; + filename: string; + mimetype: string; + size: number; + messageId: string; + chunks: string[]; + finishedUploading: boolean; + creationDate: number; +} + +interface IFetchAttachmentRequest { + id: string; +} + +interface IFetchAttachmentResponse extends IResponseSuccess, IFetchAttachment {} + +interface ICreateAttachmentRequest { + filename: string; + mimetype: string; + size: number; + communityId: string; +} + +interface ICreateAttachmentResponse + extends IResponseSuccess, + IFetchAttachment {} + +interface IFinishAttachmentRequest { + id: string; +} + +interface IFinishAttachmentResponse extends IResponseSuccess { + id: string; +} + +interface IFetchChunk { + id: string; + index: number; + attachmentId: string; +} + +interface IFetchChunkRequest { + attachmentId: string; + index: number; +} + +interface IUploadChunkRequest { + attachmentId: string; + index: number; + iv: string; + file: Blob; +} + +interface IUploadChunkResponse extends IResponseSuccess, IFetchChunk {} + +export { + type IUploadUserAvatarRequest, + type IUploadUserAvatarResponse, + type IUploadCommunityAvatarRequest, + type IUploadCommunityAvatarResponse, + type IFetchAttachment, + type IFetchAttachmentRequest, + type IFetchAttachmentResponse, + type ICreateAttachmentRequest, + type ICreateAttachmentResponse, + type IFinishAttachmentRequest, + type IFinishAttachmentResponse, + type IFetchChunk, + type IFetchChunkRequest, + type IUploadChunkRequest, + type IUploadChunkResponse, +}; diff --git a/src/api/message/types.ts b/src/api/message/types.ts index 797dca6..e963ca3 100644 --- a/src/api/message/types.ts +++ b/src/api/message/types.ts @@ -4,8 +4,11 @@ interface IFetchMessage { id: string; text: string; iv: string; - editHistory: string[]; + replyToId?: string; edited: boolean; + editHistory: string[]; + reactions: string[]; + attachments: string[]; ownerId: string; channelId: string; creationDate: number; @@ -21,6 +24,8 @@ interface ICreateMessageRequest { text: string; iv: string; channelId: string; + replyToId?: string; + attachments: string[]; } interface ICreateMessageResponse extends IResponseSuccess, IFetchMessage {} diff --git a/src/api/tools.ts b/src/api/tools.ts index 0aa09f8..55f95d0 100644 --- a/src/api/tools.ts +++ b/src/api/tools.ts @@ -3,6 +3,7 @@ import config from "./config.json"; enum HTTP { GET, + GET_BINARY, POST, PATCH, DELETE, @@ -13,6 +14,7 @@ async function callApi( path: string, body?: object, includeCookies?: boolean, + formData?: FormData, ) { let response: Response; switch (type) { @@ -29,20 +31,47 @@ async function callApi( }, ); break; - case HTTP.POST: - response = await fetch( + case HTTP.GET_BINARY: + return await fetch( `${config.schema}://${config.url}:${config.port}/${config.path}/${path}`, { - method: "POST", + method: "GET", credentials: includeCookies ? "include" : "omit", headers: { "Content-Type": "application/json", Authorization: `Bearer ${state.auth.session?.token}`, }, - body: JSON.stringify(body ?? {}), }, ); - break; + case HTTP.POST: + if (formData) { + response = await fetch( + `${config.schema}://${config.url}:${config.port}/${config.path}/${path}`, + { + method: "POST", + credentials: includeCookies ? "include" : "omit", + headers: { + Authorization: `Bearer ${state.auth.session?.token}`, + }, + body: formData, + }, + ); + break; + } else { + response = await fetch( + `${config.schema}://${config.url}:${config.port}/${config.path}/${path}`, + { + method: "POST", + credentials: includeCookies ? "include" : "omit", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${state.auth.session?.token}`, + }, + body: JSON.stringify(body ?? {}), + }, + ); + break; + } case HTTP.PATCH: response = await fetch( `${config.schema}://${config.url}:${config.port}/${config.path}/${path}`, diff --git a/src/api/user/types.ts b/src/api/user/types.ts index ac0e08e..3cac3e3 100644 --- a/src/api/user/types.ts +++ b/src/api/user/types.ts @@ -3,6 +3,7 @@ import { IResponseSuccess } from "../types"; interface IFetchUser { id: string; username: string; + nickname: string; email?: string; description?: string; admin: boolean; @@ -52,6 +53,15 @@ interface IFetchUserCommunity { name: string; } +interface IUpdateUserRequest { + id: string; + nickname?: string; + email?: string; + description?: string; +} + +interface IUpdateUserResponse extends IResponseSuccess, IFetchUser {} + export { type IFetchUser, type IFetchLoggedUserResponse, @@ -63,4 +73,6 @@ export { type IFetchUserCommunitiesRequest, type IFetchUserCommunitiesResponse, type IFetchUserCommunity, + type IUpdateUserRequest, + type IUpdateUserResponse, }; diff --git a/src/api/user/user.ts b/src/api/user/user.ts index 63f8d94..cb5c632 100644 --- a/src/api/user/user.ts +++ b/src/api/user/user.ts @@ -8,6 +8,8 @@ import { IFetchUserSessionsResponse, IFetchUserCommunitiesRequest, IFetchUserCommunitiesResponse, + IUpdateUserRequest, + IUpdateUserResponse, } from "./types"; const fetchLoggedUserApi = async (): Promise< @@ -34,9 +36,16 @@ const fetchUserCommunitiesApi = async ( return await callApi(HTTP.GET, `user/${request.id}/communities`); }; +const updateUserApi = async ( + request: IUpdateUserRequest, +): Promise => { + return await callApi(HTTP.PATCH, `user/${request.id}`, request); +}; + export { fetchLoggedUserApi, fetchUserApi, fetchUserSessionsApi, fetchUserCommunitiesApi, + updateUserApi, }; diff --git a/src/components/ChannelBar/ChannelBar.tsx b/src/components/ChannelBar/ChannelBar.tsx index 858587e..709dedc 100644 --- a/src/components/ChannelBar/ChannelBar.tsx +++ b/src/components/ChannelBar/ChannelBar.tsx @@ -4,7 +4,7 @@ import { IChannelBarProps } from "./types"; const ChannelBar: Component = (props: IChannelBarProps) => { return (
-
+

{props.name ? `# ${props.name}` : undefined}

diff --git a/src/components/Community/Community.tsx b/src/components/Community/Community.tsx index bcf9bbc..90a6204 100644 --- a/src/components/Community/Community.tsx +++ b/src/components/Community/Community.tsx @@ -1,5 +1,6 @@ import type { Component } from "solid-js"; import { ICommunityProps } from "./types"; +import { ServerIcon } from "../../icons"; const Community: Component = (props: ICommunityProps) => { return ( @@ -10,7 +11,13 @@ const Community: Component = (props: ICommunityProps) => {
- + {props.avatar ? ( + + ) : ( +
+ +
+ )}
); diff --git a/src/components/Community/types.ts b/src/components/Community/types.ts index a24b93f..3ccfd37 100644 --- a/src/components/Community/types.ts +++ b/src/components/Community/types.ts @@ -1,7 +1,7 @@ interface ICommunityProps { id: string; name: string; - avatar: string; + avatar?: string; active: boolean; onCommunityClick?: (id: string) => void; } diff --git a/src/components/FileInput/FileInput.tsx b/src/components/FileInput/FileInput.tsx new file mode 100644 index 0000000..3a54d5b --- /dev/null +++ b/src/components/FileInput/FileInput.tsx @@ -0,0 +1,49 @@ +import { type Component, type JSXElement } from "solid-js"; +import { IFileInputProps } from "./types"; +import { UploadIcon, UploadMultiIcon } from "../../icons"; + +const FileInput: Component = (props: IFileInputProps) => { + const iconOnlyHtml = (): JSXElement => ( +
+ {props.multifile ? : } +
+ ); + + const pictureHtml = (): JSXElement => ( + <> +
+ +
+
+
+ {props.multifile ? : } +
+
+ + ); + + return ( +
+ +
+ ); +}; + +export { FileInput }; diff --git a/src/components/FileInput/index.ts b/src/components/FileInput/index.ts new file mode 100644 index 0000000..cfb6767 --- /dev/null +++ b/src/components/FileInput/index.ts @@ -0,0 +1,2 @@ +export * from "./FileInput"; +export * from "./types"; diff --git a/src/components/FileInput/types.ts b/src/components/FileInput/types.ts new file mode 100644 index 0000000..3a23293 --- /dev/null +++ b/src/components/FileInput/types.ts @@ -0,0 +1,9 @@ +interface IFileInputProps { + rounded?: boolean; + picture?: string; + outline?: boolean; + multifile?: boolean; + onChange?: (files: FileList | null) => void; +} + +export { type IFileInputProps }; diff --git a/src/components/FilePreview/FilePreview.tsx b/src/components/FilePreview/FilePreview.tsx new file mode 100644 index 0000000..c0eb8ec --- /dev/null +++ b/src/components/FilePreview/FilePreview.tsx @@ -0,0 +1,47 @@ +import { type Component } from "solid-js"; +import { IFilePreviewProps } from "./types"; +import { Dynamic } from "solid-js/web"; + +const FilePreview: Component = ( + props: IFilePreviewProps, +) => { + return ( +
+ +
+ ); +}; + +export { FilePreview }; diff --git a/src/components/FilePreview/index.ts b/src/components/FilePreview/index.ts new file mode 100644 index 0000000..6a357e9 --- /dev/null +++ b/src/components/FilePreview/index.ts @@ -0,0 +1,2 @@ +export * from "./FilePreview"; +export * from "./types"; diff --git a/src/components/FilePreview/types.ts b/src/components/FilePreview/types.ts new file mode 100644 index 0000000..b066419 --- /dev/null +++ b/src/components/FilePreview/types.ts @@ -0,0 +1,16 @@ +import { JSXElement } from "solid-js"; +import { IconParameters } from "../../icons"; + +interface IFilePreviewProps { + id: string | number; + icon?: (props: IconParameters) => JSXElement; + filename?: string; + rounded?: boolean; + picture?: string; + outline?: boolean; + clickable?: boolean; + clickIcon?: (props: IconParameters) => JSXElement; + onClick?: (id: string | number) => void; +} + +export { type IFilePreviewProps }; diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx index 8157ffc..924f505 100644 --- a/src/components/Input/Input.tsx +++ b/src/components/Input/Input.tsx @@ -17,20 +17,34 @@ const Input: Component = (props: IInputProps) => { ); + const inputHtml = (): JSXElement => ( + props.onChange?.(e.currentTarget.value)} + onKeyDown={handleEnter} + /> + ); + + const textAreaHtml = (): JSXElement => ( +