From 64c6276153e8d361ed4d89a7ebe2b9ed2650b0d0 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sat, 7 Jun 2025 06:25:51 +0200 Subject: [PATCH 01/39] feat: add dropdown for guild settings and invite --- components/GuildOptionsMenu.vue | 58 +++++++++++++++++++ .../[serverId]/channels/[channelId].vue | 48 ++++++++++----- 2 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 components/GuildOptionsMenu.vue diff --git a/components/GuildOptionsMenu.vue b/components/GuildOptionsMenu.vue new file mode 100644 index 0000000..d024ec8 --- /dev/null +++ b/components/GuildOptionsMenu.vue @@ -0,0 +1,58 @@ + + + + + \ No newline at end of file diff --git a/pages/servers/[serverId]/channels/[channelId].vue b/pages/servers/[serverId]/channels/[channelId].vue index c9aa8d2..b3d492a 100644 --- a/pages/servers/[serverId]/channels/[channelId].vue +++ b/pages/servers/[serverId]/channels/[channelId].vue @@ -1,21 +1,13 @@ \ No newline at end of file From 1e0b8e2ba18c680ee626ba274de3df97b076f797 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Mon, 7 Jul 2025 21:07:33 +0200 Subject: [PATCH 08/39] feat: add Dropdown component --- components/Dropdown.vue | 46 +++++++++++++++++++++++++++++++++++++++++ types/interfaces.ts | 6 ++++++ 2 files changed, 52 insertions(+) create mode 100644 components/Dropdown.vue diff --git a/components/Dropdown.vue b/components/Dropdown.vue new file mode 100644 index 0000000..ff03799 --- /dev/null +++ b/components/Dropdown.vue @@ -0,0 +1,46 @@ + + + + + \ No newline at end of file diff --git a/types/interfaces.ts b/types/interfaces.ts index 1aba1bc..fe58dd2 100644 --- a/types/interfaces.ts +++ b/types/interfaces.ts @@ -81,3 +81,9 @@ export interface ScrollPosition { offsetTop: number, offsetLeft: number } + +export interface DropdownOption { + name: string, + value: string | number, + callback: () => void +} From 6071bbce353b020341563e2c300fde2fea59536c Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sat, 12 Jul 2025 23:59:23 +0200 Subject: [PATCH 09/39] feat: make it so channels and members are re-fetched on each activation --- .../[serverId]/channels/[channelId].vue | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pages/servers/[serverId]/channels/[channelId].vue b/pages/servers/[serverId]/channels/[channelId].vue index f493bf8..dae60bc 100644 --- a/pages/servers/[serverId]/channels/[channelId].vue +++ b/pages/servers/[serverId]/channels/[channelId].vue @@ -39,30 +39,40 @@ const server = ref(); const channels = ref(); const channel = ref(); +const members = ref(); + const showInvitePopup = ref(false); const showGuildSettings = ref(false); -import type { ChannelResponse, GuildResponse, MessageResponse } from "~/types/interfaces"; +import { type ChannelResponse, type GuildMemberResponse, type GuildResponse, type MessageResponse } from "~/types/interfaces"; //const servers = await fetchWithApi("/servers") as { uuid: string, name: string, description: string }[]; //console.log("channelid: servers:", servers); const { fetchMembers } = useApi(); -const members = await fetchMembers(route.params.serverId as string); onMounted(async () => { - console.log("channelid: set loading to true"); + console.log("mounting"); const guildUrl = `guilds/${route.params.serverId}`; server.value = await fetchWithApi(guildUrl); + await setArrayVariables(); +}); +onActivated(async () => { + console.log("activating"); + const guildUrl = `guilds/${route.params.serverId}`; + server.value = await fetchWithApi(guildUrl); + await setArrayVariables(); +}); + +async function setArrayVariables() { + members.value = await fetchMembers(route.params.serverId as string); + const guildUrl = `guilds/${route.params.serverId}`; channels.value = await fetchWithApi(`${guildUrl}/channels`); console.log("channels:", channels.value); channel.value = await fetchWithApi(`/channels/${route.params.channelId}`); console.log("channel:", channel.value); - - console.log("channelid: channel:", channel); - console.log("channelid: set loading to false"); -}); +} function toggleGuildSettings(e: Event) { e.preventDefault(); From fb452d8a5b9ac1e1ca074eeeb1a1ea30556bbf3e Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 00:04:19 +0200 Subject: [PATCH 10/39] feat: add and use ModalProps interface --- components/InviteModal.vue | 5 +++-- components/Modal.vue | 4 +++- types/interfaces.ts | 7 +++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/components/InviteModal.vue b/components/InviteModal.vue index 28b0a49..743a28b 100644 --- a/components/InviteModal.vue +++ b/components/InviteModal.vue @@ -1,5 +1,5 @@ - \ No newline at end of file From a2a28f9dbfb3ce876cfc5e32407b34df44ed6bfa Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 00:42:07 +0200 Subject: [PATCH 17/39] fix: not being able to get guild ID from route due to it being in a component --- components/InviteModal.vue | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/InviteModal.vue b/components/InviteModal.vue index e43e660..15236da 100644 --- a/components/InviteModal.vue +++ b/components/InviteModal.vue @@ -20,8 +20,6 @@ const props = defineProps(); const invite = ref(); -const route = useRoute(); - async function generateInvite(): Promise { const chars = "ABCDEFGHIJKLMNOQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890" let randCode = ""; @@ -29,7 +27,7 @@ async function generateInvite(): Promise { randCode += chars[Math.floor(Math.random() * chars.length)]; } const createdInvite: InviteResponse | undefined = await fetchWithApi( - `/guilds/${route.params.serverId}/invites`, + `/guilds/${props.guildId}/invites`, { method: "POST", body: { custom_id: randCode } } ); From 68573f126213d30209152a87d51e7e6f54d0acf8 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 00:46:37 +0200 Subject: [PATCH 18/39] feat: add function for unrendering/removing components created with h() --- utils/unrender.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 utils/unrender.ts diff --git a/utils/unrender.ts b/utils/unrender.ts new file mode 100644 index 0000000..941a34b --- /dev/null +++ b/utils/unrender.ts @@ -0,0 +1,6 @@ +import { render } from "vue"; + +export default (div: HTMLDivElement) => { + render(null, div); + div.remove(); +} From 9d49012fb966250148b14af66fb4103ddc51bb6f Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 00:51:06 +0200 Subject: [PATCH 19/39] feat: remove Settings option from GuildOptionsMenu --- components/GuildOptionsMenu.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/components/GuildOptionsMenu.vue b/components/GuildOptionsMenu.vue index ba25a0e..236d740 100644 --- a/components/GuildOptionsMenu.vue +++ b/components/GuildOptionsMenu.vue @@ -15,7 +15,6 @@ const modal = ref(); const showInviteModal = ref(false); const settings = [ - { name: "Server Settings", icon: "lucide:cog" }, { name: "Invite", icon: "lucide:letter", action: openInviteModal } ] From 9293a48394b8535ff40d7fc5a98d4c6808064e57 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 00:54:07 +0200 Subject: [PATCH 20/39] feat: remove hardcoded InviteModal from GuildOptionsMenu --- components/GuildOptionsMenu.vue | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/components/GuildOptionsMenu.vue b/components/GuildOptionsMenu.vue index 236d740..87edc65 100644 --- a/components/GuildOptionsMenu.vue +++ b/components/GuildOptionsMenu.vue @@ -4,7 +4,6 @@ - From 86952219505316fe7624e20daef25b0d4c8884f3 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 00:57:46 +0200 Subject: [PATCH 23/39] feat: switch to em for guild option setting height --- components/GuildOptionsMenu.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/GuildOptionsMenu.vue b/components/GuildOptionsMenu.vue index 39a40b4..a39b0ea 100644 --- a/components/GuildOptionsMenu.vue +++ b/components/GuildOptionsMenu.vue @@ -41,7 +41,7 @@ function openInviteModal() { display: flex; justify-content: center; align-items: center; - height: 5dvh; + height: 2em; } .guild-option:hover { From 58518876bf0d85b94a8e5486476f113826761eda Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 00:58:11 +0200 Subject: [PATCH 24/39] style: improve style of Dropdown component --- components/Dropdown.vue | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/Dropdown.vue b/components/Dropdown.vue index ff03799..78950d8 100644 --- a/components/Dropdown.vue +++ b/components/Dropdown.vue @@ -1,7 +1,7 @@ @@ -15,19 +15,19 @@ const props = defineProps<{ options: DropdownOption[] }>(); \ No newline at end of file From f83b3f34d815075da54e628a93fb12d881b4ca60 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 04:11:06 +0200 Subject: [PATCH 30/39] feat: redesign InviteModal --- components/InviteModal.vue | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/components/InviteModal.vue b/components/InviteModal.vue index 15236da..d532ae3 100644 --- a/components/InviteModal.vue +++ b/components/InviteModal.vue @@ -1,20 +1,21 @@ \ No newline at end of file From 11e46049a00318c75d69147fe2299cf478e4f0c8 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 04:14:39 +0200 Subject: [PATCH 33/39] chore: add dropdown- prefix to button class and switch colors to using existing color variables in Dropdown component --- components/Dropdown.vue | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/components/Dropdown.vue b/components/Dropdown.vue index 78950d8..89be6d6 100644 --- a/components/Dropdown.vue +++ b/components/Dropdown.vue @@ -1,7 +1,7 @@ @@ -19,8 +19,8 @@ const props = defineProps<{ options: DropdownOption[] }>(); position: absolute; z-index: 100; left: 4dvw; - bottom: 2dvh; - background-color: var(--background-color); + bottom: 4dvh; + background-color: var(--chat-background-color); width: 8rem; display: flex; flex-direction: column; @@ -30,17 +30,17 @@ const props = defineProps<{ options: DropdownOption[] }>(); border: .09rem solid rgb(70, 70, 70); } -.button { +.dropdown-button { padding-top: .5dvh; padding-bottom: .5dvh; - color: var(--main-text-color); + color: var(--text-color); background-color: transparent; width: 100%; border: none; } -.button:hover { - background-color: rgb(70, 70, 70); +.dropdown-button:hover { + background-color: var(--padding-color); } \ No newline at end of file From 06df5cf75d690e81445db4c0b7b9b74b4f8cd9f1 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 04:15:51 +0200 Subject: [PATCH 34/39] style(ui): improve styling of guild creation and joining modals and switch to using Button components in client layout --- layouts/client.vue | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/layouts/client.vue b/layouts/client.vue index 61cec63..b2124dd 100644 --- a/layouts/client.vue +++ b/layouts/client.vue @@ -57,7 +57,8 @@ const options = [ }, onCancel: () => { unrender(div); - } + }, + style: "height: 20dvh; width: 15dvw" }, [ h("input", { @@ -65,8 +66,10 @@ const options = [ type: "text", placeholder: "oyqICZ", }), - h("button", { - onClick: async () => { + h(Button, { + text: "Join", + variant: "normal", + callback: async () => { const input = document.getElementById("guild-invite-input") as HTMLInputElement; const invite = input.value; if (invite.length == 6) { @@ -79,8 +82,7 @@ const options = [ } } } - }, - "Join") + }) ]); document.body.appendChild(div); render(guildJoinModal, div); @@ -91,23 +93,27 @@ const options = [ const user = await useAuth().getUser(); const div = document.createElement("div"); const guildCreateModal = h(Modal, { - title: "Join Guild", + title: "Create a Guild", id: "guild-join-modal", onClose: () => { unrender(div); }, onCancel: () => { unrender(div); - } + }, + style: "height: 20dvh; width: 15dvw;" }, [ h("input", { id: "guild-name-input", type: "text", placeholder: `${user?.display_name || user?.username}'s Awesome Bouncy Castle'`, + style: "width: 100%" }), - h("button", { - onClick: async () => { + h(Button, { + text: "Create!", + variant: "normal", + callback: async () => { const input = document.getElementById("guild-name-input") as HTMLInputElement; const name = input.value; try { @@ -116,8 +122,7 @@ const options = [ alert(`Couldn't create guild: ${error}`); } } - }, - "Create") + }) ]); document.body.appendChild(div); render(guildCreateModal, div); From efaf606c3c9345c047c405699ef1ce0d00fda4d8 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 04:16:17 +0200 Subject: [PATCH 35/39] feat: create channel called general when creating a guild --- layouts/client.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/layouts/client.vue b/layouts/client.vue index b2124dd..e86b3a0 100644 --- a/layouts/client.vue +++ b/layouts/client.vue @@ -117,7 +117,8 @@ const options = [ const input = document.getElementById("guild-name-input") as HTMLInputElement; const name = input.value; try { - await api.createGuild(name); + const guild = (await api.createGuild(name)) as GuildResponse; + await api.createChannel(guild.uuid, "general"); } catch (error) { alert(`Couldn't create guild: ${error}`); } From 690ef5ef027ef3943b09560c2d7460edabf9a747 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 04:16:44 +0200 Subject: [PATCH 36/39] fix: Button components not rendering due to missing import in client layout --- layouts/client.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/layouts/client.vue b/layouts/client.vue index e86b3a0..a375c02 100644 --- a/layouts/client.vue +++ b/layouts/client.vue @@ -37,6 +37,7 @@ import { render } from 'vue'; import Dropdown from '~/components/Dropdown.vue'; import Modal from '~/components/Modal.vue'; +import Button from '~/components/UserInterface/Button.vue'; import type { GuildResponse } from '~/types/interfaces'; const loading = useState("loading", () => false); From e27b1cfc9d09eb35975e91d69fabe05fc1a2ae85 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 04:17:27 +0200 Subject: [PATCH 37/39] chore: switch hardcoded colors to existing color variables in client layout --- layouts/client.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/layouts/client.vue b/layouts/client.vue index a375c02..3933aa7 100644 --- a/layouts/client.vue +++ b/layouts/client.vue @@ -257,17 +257,17 @@ function createDropdown() { #left-column-bottom { padding-top: 1dvh; - border-top: 1px solid rgb(70, 70, 70); + border-top: 1px solid var(--padding-color); } #middle-left-column { padding-left: 1dvw; padding-right: 1dvw; - border-right: 1px solid rgb(70, 70, 70); + border-right: 1px solid var(--padding-color); } #home-button { - border-bottom: 1px solid rgb(70, 70, 70); + border-bottom: 1px solid var(--padding-color); padding-bottom: 1dvh; } From 569bca810ec928ef838f2f209b690e39f4ace688 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 04:19:50 +0200 Subject: [PATCH 38/39] style(ui): change styling of left column so it includes guild creation and joining button --- layouts/client.vue | 55 +++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/layouts/client.vue b/layouts/client.vue index 3933aa7..6b3bfa1 100644 --- a/layouts/client.vue +++ b/layouts/client.vue @@ -10,23 +10,27 @@
- - - -
- - - +
+ + + +
+ + + + +
+
+
+
+ +
+ +
-
- -
- - -
@@ -42,7 +46,7 @@ import type { GuildResponse } from '~/types/interfaces'; const loading = useState("loading", () => false); -const joinServerButton = ref(); +const createButtonContainer = ref(); const api = useApi(); @@ -179,8 +183,8 @@ function createDropdown() { const dropdown = h(Dropdown, { options }); const div = document.createElement("div"); div.classList.add("dropdown", "destroy-on-click"); - if (joinServerButton.value) { - joinServerButton.value.appendChild(div); + if (createButtonContainer.value) { + createButtonContainer.value.appendChild(div); } else { document.body.appendChild(div); } @@ -239,6 +243,8 @@ function createDropdown() { #left-column { display: flex; flex-direction: column; + justify-content: space-between; + align-items: center; gap: .75em; padding-left: .25em; padding-right: .25em; @@ -248,9 +254,11 @@ function createDropdown() { background-color: var(--sidebar-background-color); } -#left-column-top { +#left-column-top, #left-column-bottom { display: flex; flex-direction: column; + justify-content: center; + align-items: center; gap: 1.5dvh; overflow-y: scroll; } @@ -279,8 +287,8 @@ function createDropdown() { padding-top: .5em; } -#join-server-button { - color: white; +#create-button { + color: var(--primary-color); background-color: transparent; border: none; cursor: pointer; @@ -289,7 +297,7 @@ function createDropdown() { display: inline-block; } -#join-server-icon { +#create-icon { float: left; } @@ -316,9 +324,6 @@ function createDropdown() { } #settings-menu { - position: absolute; - bottom: .25em; - color: var(--primary-color) } From 6cec8e92b364dd079391f473c7bceb8fe22eaef6 Mon Sep 17 00:00:00 2001 From: SauceyRed Date: Sun, 13 Jul 2025 04:20:26 +0200 Subject: [PATCH 39/39] feat: add handler for removing elements with destroy-on-click class upon clicking anywhere else on the screen --- app.vue | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app.vue b/app.vue index 686a15e..9aeae9d 100644 --- a/app.vue +++ b/app.vue @@ -25,6 +25,13 @@ onMounted(() => { if (e.target instanceof HTMLElement && e.target.classList.contains("message-text") && e.target.contentEditable) { e.target.contentEditable = "false"; } + const destroyOnClick = document.getElementsByClassName("destroy-on-click"); + for (const element of destroyOnClick) { + const closest = (e.target as HTMLElement).closest(".destroy-on-click"); + if (element != closest) { + unrender(element); + } + } }); document.addEventListener("keyup", (e) => { const messageReply = document.getElementById("message-reply") as HTMLDivElement;