From 8359503341ca429bf6bc82b95c6d2cb57b5abfce Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 08:49:14 +0200 Subject: [PATCH 01/16] feat: add homepage sidebar --- src/renderer/App.tsx | 1 + src/renderer/pages/HomePage/HomePage.tsx | 6 +++++- src/renderer/styles/page/HomePage.css | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/renderer/styles/page/HomePage.css diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index b51fbfd..df44c66 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -14,6 +14,7 @@ import '@/styles/channel/MessageAttachment.css'; import '@/styles/page/GuildChannelPage.css'; import '@/styles/page/GuildPage.css'; +import '@/styles/page/HomePage.css'; import '@/styles/page/PageLayout.css'; import '@/styles/page/PageSideBar.css'; diff --git a/src/renderer/pages/HomePage/HomePage.tsx b/src/renderer/pages/HomePage/HomePage.tsx index 2f91c73..7da693f 100644 --- a/src/renderer/pages/HomePage/HomePage.tsx +++ b/src/renderer/pages/HomePage/HomePage.tsx @@ -1,11 +1,15 @@ import PageLayout from '@/components/page/PageLayout'; import PageSideBar from '@/components/page/PageSideBar'; +import { Outlet } from 'react-router-dom'; export default function HomePage() { return ( -
+
+
+ +
); diff --git a/src/renderer/styles/page/HomePage.css b/src/renderer/styles/page/HomePage.css new file mode 100644 index 0000000..fc2320f --- /dev/null +++ b/src/renderer/styles/page/HomePage.css @@ -0,0 +1,14 @@ +.HomePage { + display: flex; + width: 100%; + height: 100%; + gap: 12px; +} + +.HomePage--content { + background: #1b1d21; + border-radius: 20px; + overflow: hidden; + width: 100%; + height: 100%; +} From bcf57af1e845055ba6df93097d1f2e8f3ef18938 Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 09:11:48 +0200 Subject: [PATCH 02/16] feat(discord): add private channels support --- src/discord/core/client.ts | 7 +++++++ src/discord/managers/ChannelManager.ts | 15 ++++++++++++++- src/discord/structures/channel/BaseChannel.ts | 12 ++++++++++++ src/discord/ws/types.ts | 6 ++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/discord/core/client.ts b/src/discord/core/client.ts index fd4b576..86e83d0 100644 --- a/src/discord/core/client.ts +++ b/src/discord/core/client.ts @@ -77,6 +77,13 @@ export class Client extends TypedEmitter { this.users.cache.set(userData.id, new MainUser(userData)); }); + data.private_channels.forEach((channelData) => { + this.channels.cache.set( + channelData.id, + new MainChannel(this, channelData), + ); + }); + this.relationships = data.relationships; this.users.clientUser = new MainUser(data.user); diff --git a/src/discord/managers/ChannelManager.ts b/src/discord/managers/ChannelManager.ts index c525c5a..d49369e 100644 --- a/src/discord/managers/ChannelManager.ts +++ b/src/discord/managers/ChannelManager.ts @@ -1,6 +1,6 @@ import { Client } from '../core/client'; import { debug, error } from '../core/logger'; -import { IChannelData } from '../structures/channel/BaseChannel'; +import { ChannelType, IChannelData } from '../structures/channel/BaseChannel'; import MainChannel from '../structures/channel/MainChannel'; import { Snowflake } from '../structures/Snowflake'; import Collection from '../util/collection'; @@ -27,6 +27,19 @@ export class ChannelManager { return this.cache.values().filter((v) => v.guildId === guildId); } + /** + * Returns all private channels (direct messages, groups) where the user is in + */ + public listPrivate(): MainChannel[] { + return this.cache + .values() + .filter( + (v) => + v.type === ChannelType.DirectMessage || + v.type === ChannelType.GroupDM, + ); + } + public async fetch( options: SingleChannelFetchOptions | GuildChannelFetchOptions, ): Promise { diff --git a/src/discord/structures/channel/BaseChannel.ts b/src/discord/structures/channel/BaseChannel.ts index a501a88..4809230 100644 --- a/src/discord/structures/channel/BaseChannel.ts +++ b/src/discord/structures/channel/BaseChannel.ts @@ -52,6 +52,8 @@ export interface IChannelData { permissions?: string; flags?: number; total_message_sent?: number; + recipient_ids?: Snowflake[]; + is_spam?: boolean; } export interface CreateMessageOptions { @@ -78,6 +80,10 @@ export default abstract class BaseChannel { public messages: Message[]; + public recipientIds: Snowflake[]; + + public lastMessageId: Snowflake | null; + constructor(data: IChannelData, messages?: Message[]) { this.id = data.id; this.type = data.type; @@ -86,6 +92,8 @@ export default abstract class BaseChannel { this.name = data.name ?? ''; this.parentId = data.parent_id ?? null; this.messages = messages ?? []; + this.recipientIds = data.recipient_ids ?? []; + this.lastMessageId = data.last_message_id ?? null; this.patch(data); } @@ -99,6 +107,8 @@ export default abstract class BaseChannel { this.position = data.position ?? 0; this.name = data.name ?? ''; this.parentId = data.parent_id ?? null; + this.recipientIds = data.recipient_ids || []; + this.lastMessageId = data.last_message_id ?? null; } } @@ -127,6 +137,8 @@ export default abstract class BaseChannel { position: this.position, name: this.name, parent_id: this.parentId, + recipient_ids: this.recipientIds, + last_message_id: this.lastMessageId, }; } } diff --git a/src/discord/ws/types.ts b/src/discord/ws/types.ts index e8f7cb6..29a4ba9 100644 --- a/src/discord/ws/types.ts +++ b/src/discord/ws/types.ts @@ -1,5 +1,6 @@ /* eslint-disable no-unused-vars */ +import { IChannelData } from '../structures/channel/BaseChannel'; import { IGuildData } from '../structures/guild/BaseGuild'; import { Relationship } from '../structures/Relationship'; import { IUserData } from '../structures/user/BaseUser'; @@ -139,4 +140,9 @@ export interface GatewayReadyDispatchData { * Your relationships with other users */ relationships: Relationship[]; + + /** + * All private channels the user is in + */ + private_channels: IChannelData[]; } From e618254ba5136e8cebe605fd3656c2ed82a50f84 Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 09:12:22 +0200 Subject: [PATCH 03/16] feat(ipc): add discord:private-channels and support for fetching another user --- src/main/app.ts | 6 +++++- src/main/ipc.ts | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/app.ts b/src/main/app.ts index 278cf60..3de1f40 100644 --- a/src/main/app.ts +++ b/src/main/app.ts @@ -206,7 +206,7 @@ export default class WaveCordApp { registerHandler('discord:user', (userId: string | undefined) => { if (userId === undefined) return this.discord.users.clientUser?.toRaw(); - return null; + return this.discord.users.cache.get(userId) ?? null; }); registerHandler('discord:guilds', () => { @@ -244,6 +244,10 @@ export default class WaveCordApp { }, ); + registerHandler('discord:private-channels', () => { + return this.discord.channels.listPrivate().map((v) => v.toRaw()); + }); + registerHandler('tenor:fetch-gif', async (url: string) => { const result = await this.tenor.fetchGif(url); return result; diff --git a/src/main/ipc.ts b/src/main/ipc.ts index bf38e51..00c1ce4 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -21,6 +21,7 @@ export type IpcChannels = | 'discord:create-message' | 'discord:gateway:message-create' | 'discord:relationships' + | 'discord:private-channels' | 'tenor:fetch-gif'; export function registerHandler( From 2e2e4cb97d77dcb71c779ced1470983d3f7cceab Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 09:12:31 +0200 Subject: [PATCH 04/16] feat: add private channels hook --- src/renderer/hooks/usePrivateChannels.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/renderer/hooks/usePrivateChannels.ts diff --git a/src/renderer/hooks/usePrivateChannels.ts b/src/renderer/hooks/usePrivateChannels.ts new file mode 100644 index 0000000..f36573d --- /dev/null +++ b/src/renderer/hooks/usePrivateChannels.ts @@ -0,0 +1,22 @@ +import { IChannelData } from '@/discord/structures/channel/BaseChannel'; +import RendererChannel from '@/discord/structures/channel/RendererChannel'; +import { useEffect, useState } from 'react'; + +/** + * Fetches all private channels from the user + */ +export default function usePrivateChannels() { + const [channels, setChannels] = useState([]); + + useEffect(() => { + window.electron.ipcRenderer + .invoke('discord:private-channels') + .then((data: IChannelData[]) => { + setChannels(data.map((v) => new RendererChannel(v))); + return true; + }) + .catch((err) => console.error(err)); + }); + + return channels; +} From 6ad92745805cf2d5fda3da3f1ab6b51c89d6a638 Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 09:22:41 +0200 Subject: [PATCH 05/16] feat(ipc): add discord:users for multiple user fetching --- src/main/app.ts | 8 ++++++++ src/main/ipc.ts | 1 + 2 files changed, 9 insertions(+) diff --git a/src/main/app.ts b/src/main/app.ts index 3de1f40..4785021 100644 --- a/src/main/app.ts +++ b/src/main/app.ts @@ -2,6 +2,7 @@ import { app, BrowserWindow, Menu, shell, Tray } from 'electron'; import fs from 'fs'; import path from 'path'; +import { Snowflake } from '@/discord/structures/Snowflake'; import logger, { Logger } from '../common/log/logger'; import { Client } from '../discord/core/client'; import { GatewayDispatchEvents, GatewaySocketEvent } from '../discord/ws/types'; @@ -209,6 +210,13 @@ export default class WaveCordApp { return this.discord.users.cache.get(userId) ?? null; }); + registerHandler('discord:users', (userIds: Snowflake[]) => { + return this.discord.users.cache + .values() + .filter((v) => userIds.includes(v.id)) + .map((v) => v.toRaw()); + }); + registerHandler('discord:guilds', () => { return this.discord.guilds.cache.values().map((v) => v.toRaw()); }); diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 00c1ce4..d4ee421 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -16,6 +16,7 @@ export type IpcChannels = | 'discord:fetch-messages' | 'discord:fetch-guild' | 'discord:user' + | 'discord:users' | 'discord:get-last-visited-channel' | 'discord:set-last-visited-channel' | 'discord:create-message' From d8157c8f8d6832fbe3c6e30f38cd23432d7cf601 Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 09:22:57 +0200 Subject: [PATCH 06/16] feat: add useUsers hook --- src/renderer/hooks/useUsers.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/renderer/hooks/useUsers.ts diff --git a/src/renderer/hooks/useUsers.ts b/src/renderer/hooks/useUsers.ts new file mode 100644 index 0000000..99308e0 --- /dev/null +++ b/src/renderer/hooks/useUsers.ts @@ -0,0 +1,23 @@ +import { Snowflake } from '@/discord/structures/Snowflake'; +import { IUserData } from '@/discord/structures/user/BaseUser'; +import RendererUser from '@/discord/structures/user/RendererUser'; +import { useEffect, useState } from 'react'; + +/** + * Fetches multiple users + */ +export default function useUsers(userIds: Snowflake[]) { + const [users, setUsers] = useState([]); + + useEffect(() => { + window.electron.ipcRenderer + .invoke('discord:users', userIds) + .then((data: IUserData[]) => { + setUsers(data.map((v) => new RendererUser(v))); + return true; + }) + .catch((err) => console.error(err)); + }); + + return users; +} From d23b9b73f348da530149d00c7940d8a96eaf00c0 Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 09:23:22 +0200 Subject: [PATCH 07/16] feat: sort and view private channels in homepage --- src/renderer/pages/HomePage/HomePage.tsx | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/renderer/pages/HomePage/HomePage.tsx b/src/renderer/pages/HomePage/HomePage.tsx index 7da693f..c442d74 100644 --- a/src/renderer/pages/HomePage/HomePage.tsx +++ b/src/renderer/pages/HomePage/HomePage.tsx @@ -1,12 +1,37 @@ +import DirectMessageButton from '@/components/directmessage/DirectMessageButton'; import PageLayout from '@/components/page/PageLayout'; import PageSideBar from '@/components/page/PageSideBar'; +import usePrivateChannels from '@/hooks/usePrivateChannels'; import { Outlet } from 'react-router-dom'; export default function HomePage() { + const channels = usePrivateChannels(); + return (
- + + { + /* Sort private channels by their last message id */ + channels + .sort((a, b) => { + if (a.lastMessageId === null) return -1; + + if (b.lastMessageId === null) return 1; + + return Number(a.lastMessageId) - Number(b.lastMessageId); + }) + .reverse() + .map((channel) => { + return ( + + ); + }) + } +
From 5cc8b2e58387b7582fbe5f09c014028320550f5b Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 10:26:14 +0200 Subject: [PATCH 08/16] fix: allow other text channel types to fetch messages --- src/discord/structures/channel/MainChannel.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/discord/structures/channel/MainChannel.ts b/src/discord/structures/channel/MainChannel.ts index 62e557e..784e925 100644 --- a/src/discord/structures/channel/MainChannel.ts +++ b/src/discord/structures/channel/MainChannel.ts @@ -7,7 +7,12 @@ import BaseChannel, { IChannelData, } from './BaseChannel'; -const supportedTypes = [ChannelType.GuildText, ChannelType.GuildAnnouncement]; +const supportedTypes = [ + ChannelType.GuildText, + ChannelType.GuildAnnouncement, + ChannelType.GroupDM, + ChannelType.DirectMessage, +]; export default class MainChannel extends BaseChannel { private readonly client: Client; @@ -67,7 +72,7 @@ export default class MainChannel extends BaseChannel { /* --------------------------- */ private async fetchMessagesApi(): Promise { - if (this.type !== ChannelType.GuildText || this.client === null) return []; + if (!supportedTypes.includes(this.type) || this.client === null) return []; try { const json = await this.client.restGet( From 4da5b0b57ecadff1a6c4b72d0a0c4f9a7491701a Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 10:26:43 +0200 Subject: [PATCH 09/16] feat: add channel icon (for private channels) --- src/discord/structures/channel/BaseChannel.ts | 5 +++++ src/discord/structures/channel/RendererChannel.ts | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/discord/structures/channel/BaseChannel.ts b/src/discord/structures/channel/BaseChannel.ts index 4809230..5f5822f 100644 --- a/src/discord/structures/channel/BaseChannel.ts +++ b/src/discord/structures/channel/BaseChannel.ts @@ -84,6 +84,8 @@ export default abstract class BaseChannel { public lastMessageId: Snowflake | null; + public icon: Snowflake | null; + constructor(data: IChannelData, messages?: Message[]) { this.id = data.id; this.type = data.type; @@ -94,6 +96,7 @@ export default abstract class BaseChannel { this.messages = messages ?? []; this.recipientIds = data.recipient_ids ?? []; this.lastMessageId = data.last_message_id ?? null; + this.icon = data.icon ?? null; this.patch(data); } @@ -109,6 +112,7 @@ export default abstract class BaseChannel { this.parentId = data.parent_id ?? null; this.recipientIds = data.recipient_ids || []; this.lastMessageId = data.last_message_id ?? null; + this.icon = data.icon ?? null; } } @@ -139,6 +143,7 @@ export default abstract class BaseChannel { parent_id: this.parentId, recipient_ids: this.recipientIds, last_message_id: this.lastMessageId, + icon: this.icon, }; } } diff --git a/src/discord/structures/channel/RendererChannel.ts b/src/discord/structures/channel/RendererChannel.ts index e8c9074..9cebc94 100644 --- a/src/discord/structures/channel/RendererChannel.ts +++ b/src/discord/structures/channel/RendererChannel.ts @@ -1,5 +1,5 @@ import { Message } from '../Message'; -import BaseChannel, { CreateMessageOptions } from './BaseChannel'; +import BaseChannel, { ChannelType, CreateMessageOptions } from './BaseChannel'; export default class RendererChannel extends BaseChannel { public async fetchMessages(): Promise { @@ -18,4 +18,10 @@ export default class RendererChannel extends BaseChannel { options, ); } + + public getChannelIcon(): string | null { + if (this.type !== ChannelType.GroupDM) return null; + + return `https://cdn.discordapp.com/channel-icons/${this.id}/${this.icon}.webp?size=32`; + } } From 78de9c0548705ce72af09cd17b49da0467cb64fd Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 10:27:03 +0200 Subject: [PATCH 10/16] chore: set message attachment image loading to lazy --- src/renderer/components/channel/MessageAttachment.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/components/channel/MessageAttachment.tsx b/src/renderer/components/channel/MessageAttachment.tsx index a0c0148..bf3c8cc 100644 --- a/src/renderer/components/channel/MessageAttachment.tsx +++ b/src/renderer/components/channel/MessageAttachment.tsx @@ -10,6 +10,7 @@ function ImageAttachment({ attachment }: MessageAttachmentProps) { {`Attachment:${attachment.filename}`}
From 20016e5d0ec918356fae3f46639a8ddc012e998b Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 10:27:34 +0200 Subject: [PATCH 11/16] fix: useChannels missing dependency array --- src/renderer/hooks/useChannels.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/hooks/useChannels.ts b/src/renderer/hooks/useChannels.ts index 9bad562..07e06b6 100644 --- a/src/renderer/hooks/useChannels.ts +++ b/src/renderer/hooks/useChannels.ts @@ -16,7 +16,7 @@ export default function useChannels(guildId: string) { return true; }) .catch((err) => console.error(err)); - }); + }, [guildId]); return channels; } From 638731ac15105a4272e6f941494959a1b0ef52ad Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 10:27:52 +0200 Subject: [PATCH 12/16] fix: usePrivateChannels missing dependency array --- src/renderer/hooks/usePrivateChannels.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/hooks/usePrivateChannels.ts b/src/renderer/hooks/usePrivateChannels.ts index f36573d..d58e3af 100644 --- a/src/renderer/hooks/usePrivateChannels.ts +++ b/src/renderer/hooks/usePrivateChannels.ts @@ -16,7 +16,7 @@ export default function usePrivateChannels() { return true; }) .catch((err) => console.error(err)); - }); + }, []); return channels; } From 59bc0c6f1b1a3b9f26dd5473110ac17dd8301fd0 Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 10:38:08 +0200 Subject: [PATCH 13/16] fix: inverted gif host check --- src/renderer/hooks/useGif.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/hooks/useGif.ts b/src/renderer/hooks/useGif.ts index b5dafb9..e43464f 100644 --- a/src/renderer/hooks/useGif.ts +++ b/src/renderer/hooks/useGif.ts @@ -10,7 +10,7 @@ export default function useGif(url: string): TenorGif | null { try { const uri = new URL(url); - if (allowedHosts.includes(uri.host.toLowerCase())) { + if (!allowedHosts.includes(uri.host.toLowerCase())) { setGif(null); return; } From 282d9a682e83e378b4d827bdeca6cb74e806cc5d Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 10:50:57 +0200 Subject: [PATCH 14/16] fix: `getChannelIcon` not returning null when icon is null --- src/discord/structures/channel/RendererChannel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/discord/structures/channel/RendererChannel.ts b/src/discord/structures/channel/RendererChannel.ts index 9cebc94..4f78190 100644 --- a/src/discord/structures/channel/RendererChannel.ts +++ b/src/discord/structures/channel/RendererChannel.ts @@ -20,7 +20,7 @@ export default class RendererChannel extends BaseChannel { } public getChannelIcon(): string | null { - if (this.type !== ChannelType.GroupDM) return null; + if (this.type !== ChannelType.GroupDM || this.icon === null) return null; return `https://cdn.discordapp.com/channel-icons/${this.id}/${this.icon}.webp?size=32`; } From d04a081fab66f54b52fdce8dca2a181c01933257 Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 10:51:20 +0200 Subject: [PATCH 15/16] fix: message gif max dimensions --- src/renderer/components/channel/Message.tsx | 1 + src/renderer/styles/channel/Message.css | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/renderer/components/channel/Message.tsx b/src/renderer/components/channel/Message.tsx index 60662c4..f0b58c5 100644 --- a/src/renderer/components/channel/Message.tsx +++ b/src/renderer/components/channel/Message.tsx @@ -34,6 +34,7 @@ export default function Message({ message }: MessageProps) { {gif !== null ? ( v.type === 'gif')?.url} alt="Gif" /> diff --git a/src/renderer/styles/channel/Message.css b/src/renderer/styles/channel/Message.css index f75c3f0..65f02df 100644 --- a/src/renderer/styles/channel/Message.css +++ b/src/renderer/styles/channel/Message.css @@ -36,3 +36,8 @@ .Message--attachments-container { display: flex; } + +.Message--gif-img { + max-width: 300px; + max-height: 300px; +} From 8fa442bc3bcc74627080f284dc294a2fd0e4b69d Mon Sep 17 00:00:00 2001 From: Calvin Rohloff Date: Thu, 19 Sep 2024 10:51:32 +0200 Subject: [PATCH 16/16] feat: add direct messages --- src/renderer/App.tsx | 8 ++- .../directmessage/DirectMessageButton.tsx | 71 +++++++++++++++++++ .../directmessage/DirectMessageList.tsx | 31 ++++++++ .../pages/HomePage/HomeChannelPage.tsx | 38 ++++++++++ src/renderer/pages/HomePage/HomePage.tsx | 26 +------ .../directmessage/DirectMessageButton.css | 33 +++++++++ .../directmessage/DirectMessageList.css | 9 +++ src/renderer/styles/page/GuildChannelPage.css | 25 ++++++- 8 files changed, 215 insertions(+), 26 deletions(-) create mode 100644 src/renderer/components/directmessage/DirectMessageButton.tsx create mode 100644 src/renderer/components/directmessage/DirectMessageList.tsx create mode 100644 src/renderer/pages/HomePage/HomeChannelPage.tsx create mode 100644 src/renderer/styles/directmessage/DirectMessageButton.css create mode 100644 src/renderer/styles/directmessage/DirectMessageList.css diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index df44c66..f454284 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -12,6 +12,9 @@ import '@/styles/channel/ChannelList.css'; import '@/styles/channel/Message.css'; import '@/styles/channel/MessageAttachment.css'; +import '@/styles/directmessage/DirectMessageButton.css'; +import '@/styles/directmessage/DirectMessageList.css'; + import '@/styles/page/GuildChannelPage.css'; import '@/styles/page/GuildPage.css'; import '@/styles/page/HomePage.css'; @@ -34,6 +37,7 @@ import SideBar from '@/components/app/SideBar'; import HomePage from '@/pages/HomePage/HomePage'; import GuildPage from '@/pages/GuildPage/GuildPage'; import GuildChannelPage from './pages/GuildPage/GuildChannelPage'; +import HomeChannelPage from './pages/HomePage/HomeChannelPage'; export default function App() { return ( @@ -43,7 +47,9 @@ export default function App() { - } /> + }> + } /> + }> } /> diff --git a/src/renderer/components/directmessage/DirectMessageButton.tsx b/src/renderer/components/directmessage/DirectMessageButton.tsx new file mode 100644 index 0000000..4355257 --- /dev/null +++ b/src/renderer/components/directmessage/DirectMessageButton.tsx @@ -0,0 +1,71 @@ +import { ChannelType } from '@/discord/structures/channel/BaseChannel'; +import RendererChannel from '@/discord/structures/channel/RendererChannel'; +import useUser from '@/hooks/useUser'; +import useUsers from '@/hooks/useUsers'; +import { useLocation, useNavigate } from 'react-router-dom'; + +type DirectMessageButtonProps = { + channel: RendererChannel; +}; + +export default function DirectMessageButton({ + channel, +}: DirectMessageButtonProps) { + const location = useLocation(); + const navigate = useNavigate(); + + const user = useUser(undefined); + const members = useUsers(channel.recipientIds ?? []); + + if (user === null) return null; + + const onClick = () => { + navigate(`/channel/${channel.id}`); + }; + + const getName = () => { + if (channel.type === ChannelType.GroupDM) { + if (channel.name.length > 0) return channel.name; + + const membs = [user, ...members]; + return membs + .map((v) => (v.globalName ? v.globalName : v.username)) + .join(', '); + } + + if (members.length === 0) return ''; + + const member = members[0]; + + if (member.globalName === null || member.globalName.length === 0) + return member.username; + + return member.globalName; + }; + + const getIcon = () => { + if (members.length === 0) return ''; + + if (channel.type === ChannelType.GroupDM) + return channel.getChannelIcon() ?? members[0].getAvatarUrl(); + + return members[0].getAvatarUrl(); + }; + + const isSelected = location.pathname.includes(`/channel/${channel.id}`); + + return ( +
onClick()} + > + Direct Message Icon +

{getName()}

+
+ ); +} diff --git a/src/renderer/components/directmessage/DirectMessageList.tsx b/src/renderer/components/directmessage/DirectMessageList.tsx new file mode 100644 index 0000000..a0bc1ce --- /dev/null +++ b/src/renderer/components/directmessage/DirectMessageList.tsx @@ -0,0 +1,31 @@ +import usePrivateChannels from '@/hooks/usePrivateChannels'; +import DirectMessageButton from './DirectMessageButton'; + +export default function DirectMessageList() { + const channels = usePrivateChannels(); + + return ( +
+ { + /* Sort private channels by their last message id */ + channels + .sort((a, b) => { + if (a.lastMessageId === null) return -1; + + if (b.lastMessageId === null) return 1; + + return Number(a.lastMessageId) - Number(b.lastMessageId); + }) + .reverse() + .map((channel) => { + return ( + + ); + }) + } +
+ ); +} diff --git a/src/renderer/pages/HomePage/HomeChannelPage.tsx b/src/renderer/pages/HomePage/HomeChannelPage.tsx new file mode 100644 index 0000000..3176596 --- /dev/null +++ b/src/renderer/pages/HomePage/HomeChannelPage.tsx @@ -0,0 +1,38 @@ +import Message from '@/components/channel/Message'; +import { ChannelType } from '@/discord/structures/channel/BaseChannel'; +import useChannel from '@/hooks/useChannel'; +import useMessages from '@/hooks/useMessages'; +import useUsers from '@/hooks/useUsers'; +import { useParams } from 'react-router-dom'; + +export default function HomeChannelPage() { + const params = useParams(); + const channelId = params.channelId ?? ''; + + const channel = useChannel(channelId); + const messages = useMessages(channel); + const members = useUsers(channel?.recipientIds ?? []); + + if (channel === null) return null; + + const getName = () => { + if (channel.type === ChannelType.GroupDM) return channel.name; + + if (members.length === 0) return ''; + + return members[0].globalName; + }; + + return ( +
+
+

{getName()}

+
+
+ {messages.map((msg) => { + return ; + })} +
+
+ ); +} diff --git a/src/renderer/pages/HomePage/HomePage.tsx b/src/renderer/pages/HomePage/HomePage.tsx index c442d74..d8ac5e2 100644 --- a/src/renderer/pages/HomePage/HomePage.tsx +++ b/src/renderer/pages/HomePage/HomePage.tsx @@ -1,36 +1,14 @@ -import DirectMessageButton from '@/components/directmessage/DirectMessageButton'; +import DirectMessageList from '@/components/directmessage/DirectMessageList'; import PageLayout from '@/components/page/PageLayout'; import PageSideBar from '@/components/page/PageSideBar'; -import usePrivateChannels from '@/hooks/usePrivateChannels'; import { Outlet } from 'react-router-dom'; export default function HomePage() { - const channels = usePrivateChannels(); - return (
- { - /* Sort private channels by their last message id */ - channels - .sort((a, b) => { - if (a.lastMessageId === null) return -1; - - if (b.lastMessageId === null) return 1; - - return Number(a.lastMessageId) - Number(b.lastMessageId); - }) - .reverse() - .map((channel) => { - return ( - - ); - }) - } +
diff --git a/src/renderer/styles/directmessage/DirectMessageButton.css b/src/renderer/styles/directmessage/DirectMessageButton.css new file mode 100644 index 0000000..00f8ed4 --- /dev/null +++ b/src/renderer/styles/directmessage/DirectMessageButton.css @@ -0,0 +1,33 @@ +.DirectMessage { + display: flex; + align-items: center; + width: calc(var(--channel-item-width) - var(--channel-item-padding) * 2); + height: calc(var(--channel-item-height) - var(--channel-item-padding) * 2); + padding: 10px; + border-radius: 12px; + color: #73747a; + font-weight: 600; + transition: all 0.34s cubic-bezier(0.075, 0.82, 0.165, 1); + cursor: pointer; + flex-shrink: 0; + flex-grow: 0; + gap: 6px; +} + +.DirectMessage:hover, +.DirectMessage--selected { + background: #1f1e25; + color: #fff; +} + +.DirectMessage--icon-img { + width: 32px; + border-radius: 50%; +} + +.DirectMessage--name { + width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} diff --git a/src/renderer/styles/directmessage/DirectMessageList.css b/src/renderer/styles/directmessage/DirectMessageList.css new file mode 100644 index 0000000..8fcdb7d --- /dev/null +++ b/src/renderer/styles/directmessage/DirectMessageList.css @@ -0,0 +1,9 @@ +.DirectMessageList { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + overflow: hidden; + overflow-y: auto; + align-items: center; +} diff --git a/src/renderer/styles/page/GuildChannelPage.css b/src/renderer/styles/page/GuildChannelPage.css index 163ffb9..d7472ec 100644 --- a/src/renderer/styles/page/GuildChannelPage.css +++ b/src/renderer/styles/page/GuildChannelPage.css @@ -1,13 +1,36 @@ +:root { + --guild-channel-header-height: 50px; +} + .GuildChannelPage { + position: relative; width: calc(100% - 40px); height: calc(100% - 40px); padding: 20px; } +.GuildChannelPage--header { + padding: 20px; + position: absolute; + top: 0; + left: 0; + z-index: 1; + width: calc(100% - 40px); + height: calc(2px + var(--guild-channel-header-height) - 20px); + background: #1b1d21; + filter: drop-shadow(0px 5px 8px #0000005c); +} + .GuildChannelPage--messages-container { + padding-top: calc(var(--guild-channel-header-height) + 10px); display: flex; flex-direction: column-reverse; - height: calc(100% - 20px); + height: calc(100% - 20px - var(--guild-channel-header-height)); overflow-y: auto; gap: 6px; } + +.GuildChannelPage--header-title { + font-size: 20px; + font-weight: 500; +}