diff --git a/src/lib/components/app-sidebar.svelte b/src/lib/components/app-sidebar.svelte index 35afb4e..d8ad9be 100644 --- a/src/lib/components/app-sidebar.svelte +++ b/src/lib/components/app-sidebar.svelte @@ -11,6 +11,7 @@ import UsersRound from '@lucide/svelte/icons/users-round'; import CirclePlus from '@lucide/svelte/icons/circle-plus'; import Input from './ui/input/input.svelte'; + import * as ContextMenu from '$lib/components/ui/context-menu/index.js'; import Button, { buttonVariants } from './ui/button/button.svelte'; import User from './extra/User.svelte'; import type { SessionValidationResult } from '$lib/server/auth'; @@ -482,34 +483,147 @@ {#each overview_data.servers as server (server.id)} - - { - e.preventDefault(); - currentPage = server.id; - subPage = null; - }} - > - {server.name} - {server.name} - - + + + + { + e.preventDefault(); + currentPage = server.id; + subPage = null; + }} + > + {server.name} + {server.name} + + + + + + Create channel + + + + + +
{ + return async ({ result }) => { + if (result.type == 'success') { + toast.success('Created channel successfully'); + } + + if (result.type == 'error' || result.type == 'failure') { + toast.error( + 'Could not create channel: ' + + (result.type === 'error' ? result.error : result.data?.error) + ); + } + + await invalidateAll(); + await fill_overview_data(psd); + }; + }} + > + + Create a channel + Add a new channel to this server. + + +
+ + + +
+ + + Cancel + + +
+
+
{#each server.channels as channel (channel.id)} - { - e.preventDefault(); - currentPage = server.id; - subPage = channel.id; - }} - href="##" - class="flex items-center gap-2" - > - {channel.name} - + + + + { + e.preventDefault(); + currentPage = server.id; + subPage = channel.id; + }} + href="##" + class="flex items-center gap-2" + > + {channel.name} + + + + + Delete channel + + + + + +
{ + return async ({ result }) => { + if (result.type == 'success') { + toast.success('Deleted channel successfully'); + } + + if (result.type == 'error' || result.type == 'failure') { + toast.error( + 'Could not delete channel: ' + + (result.type === 'error' ? result.error : result.data?.error) + ); + } + + await invalidateAll(); + await fill_overview_data(psd); + }; + }} + > + + + + + Delete this channel + This action is IRREVERSIBLE and will delete all messages in the + channel. Are you sure you want to proceed? + + + + No + + +
+
+
{/each} {/each}
diff --git a/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte b/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte new file mode 100644 index 0000000..f3b6db3 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte @@ -0,0 +1,40 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.()} + {/snippet} + diff --git a/src/lib/components/ui/context-menu/context-menu-content.svelte b/src/lib/components/ui/context-menu/context-menu-content.svelte new file mode 100644 index 0000000..20b516d --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-content.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/src/lib/components/ui/context-menu/context-menu-group-heading.svelte b/src/lib/components/ui/context-menu/context-menu-group-heading.svelte new file mode 100644 index 0000000..2cb6207 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-group-heading.svelte @@ -0,0 +1,21 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu-group.svelte b/src/lib/components/ui/context-menu/context-menu-group.svelte new file mode 100644 index 0000000..c7c1e06 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu-item.svelte b/src/lib/components/ui/context-menu/context-menu-item.svelte new file mode 100644 index 0000000..4e8d224 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-item.svelte @@ -0,0 +1,27 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu-label.svelte b/src/lib/components/ui/context-menu/context-menu-label.svelte new file mode 100644 index 0000000..5e96107 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-label.svelte @@ -0,0 +1,24 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/context-menu/context-menu-portal.svelte b/src/lib/components/ui/context-menu/context-menu-portal.svelte new file mode 100644 index 0000000..96b1e3e --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu-radio-group.svelte b/src/lib/components/ui/context-menu/context-menu-radio-group.svelte new file mode 100644 index 0000000..964cb55 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-radio-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu-radio-item.svelte b/src/lib/components/ui/context-menu/context-menu-radio-item.svelte new file mode 100644 index 0000000..0141b14 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-radio-item.svelte @@ -0,0 +1,33 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.({ checked })} + {/snippet} + diff --git a/src/lib/components/ui/context-menu/context-menu-separator.svelte b/src/lib/components/ui/context-menu/context-menu-separator.svelte new file mode 100644 index 0000000..7f5b237 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu-shortcut.svelte b/src/lib/components/ui/context-menu/context-menu-shortcut.svelte new file mode 100644 index 0000000..6181881 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/context-menu/context-menu-sub-content.svelte b/src/lib/components/ui/context-menu/context-menu-sub-content.svelte new file mode 100644 index 0000000..2b6ca47 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-sub-content.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte b/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte new file mode 100644 index 0000000..38d74eb --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/src/lib/components/ui/context-menu/context-menu-sub.svelte b/src/lib/components/ui/context-menu/context-menu-sub.svelte new file mode 100644 index 0000000..a03827b --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-sub.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu-trigger.svelte b/src/lib/components/ui/context-menu/context-menu-trigger.svelte new file mode 100644 index 0000000..3efa857 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu.svelte b/src/lib/components/ui/context-menu/context-menu.svelte new file mode 100644 index 0000000..cfaefb3 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/context-menu/index.ts b/src/lib/components/ui/context-menu/index.ts new file mode 100644 index 0000000..cbeaee1 --- /dev/null +++ b/src/lib/components/ui/context-menu/index.ts @@ -0,0 +1,52 @@ +import Root from "./context-menu.svelte"; +import Sub from "./context-menu-sub.svelte"; +import Portal from "./context-menu-portal.svelte"; +import Trigger from "./context-menu-trigger.svelte"; +import Group from "./context-menu-group.svelte"; +import RadioGroup from "./context-menu-radio-group.svelte"; +import Item from "./context-menu-item.svelte"; +import GroupHeading from "./context-menu-group-heading.svelte"; +import Content from "./context-menu-content.svelte"; +import Shortcut from "./context-menu-shortcut.svelte"; +import RadioItem from "./context-menu-radio-item.svelte"; +import Separator from "./context-menu-separator.svelte"; +import SubContent from "./context-menu-sub-content.svelte"; +import SubTrigger from "./context-menu-sub-trigger.svelte"; +import CheckboxItem from "./context-menu-checkbox-item.svelte"; +import Label from "./context-menu-label.svelte"; + +export { + Root, + Sub, + Portal, + Item, + GroupHeading, + Label, + Group, + Trigger, + Content, + Shortcut, + Separator, + RadioItem, + SubContent, + SubTrigger, + RadioGroup, + CheckboxItem, + // + Root as ContextMenu, + Sub as ContextMenuSub, + Portal as ContextMenuPortal, + Item as ContextMenuItem, + GroupHeading as ContextMenuGroupHeading, + Group as ContextMenuGroup, + Content as ContextMenuContent, + Trigger as ContextMenuTrigger, + Shortcut as ContextMenuShortcut, + RadioItem as ContextMenuRadioItem, + Separator as ContextMenuSeparator, + RadioGroup as ContextMenuRadioGroup, + SubContent as ContextMenuSubContent, + SubTrigger as ContextMenuSubTrigger, + CheckboxItem as ContextMenuCheckboxItem, + Label as ContextMenuLabel, +}; diff --git a/src/routes/app/actions/server.ts b/src/routes/app/actions/server.ts index e4d2edf..9be372c 100644 --- a/src/routes/app/actions/server.ts +++ b/src/routes/app/actions/server.ts @@ -1,7 +1,7 @@ import { fail } from '@sveltejs/kit'; import { db } from '$lib/server/db'; import * as table from '$lib/server/db/schema'; -import { InviteID, ServerID } from '$lib'; +import { ChannelID, InviteID, ServerID } from '$lib'; import { eq } from 'drizzle-orm'; import { _sendToUser } from '../../api/updates/+server'; import type { Actions } from '../$types'; @@ -192,6 +192,79 @@ export default { _sendToUser(locals.user!.id, { type: 'server', status: 'server-created' }); return { success: true }; }, + createChannel: async ({ request, locals }) => { + const data = await request.formData(); + const name = data.get('channelName'); + const serverId = data.get('serverId'); + + if (typeof name !== 'string' || name.length < 1 || name.length > 32) { + return fail(400, { error: 'Channel name must be between 1-32 characters' }); + } + + if (typeof serverId !== 'string') { + return fail(400, { error: 'Server ID incorrect' }); + } + + const server = await db.select().from(table.server).where(eq(table.server.id, serverId)); + if (!server || server.length == 0) { + return fail(400, { error: 'Server ID invalid' }); + } + + // @TODO check permissions here (if owner ignore, then check roles, if any role has manageChannels then let create channel) + + const channelId = ChannelID.newV4(); + + await db.transaction(async (tx) => { + await tx.insert(table.channel).values({ + id: channelId, + serverId: serverId, + name + }); + + await tx + .update(table.server) + .set({ + channels: (server[0].channels as string[]).concat([channelId]) + }) + .where(eq(table.server.id, serverId)); + }); + + return { success: true }; + }, + deleteChannel: async ({ request, locals }) => { + const data = await request.formData(); + const serverId = data.get('serverId'); + const channelId = data.get('channelId'); + + if (typeof serverId !== 'string' || typeof channelId !== 'string') { + return fail(400, { error: 'Invalid channel data' }); + } + + const server = await db.select().from(table.server).where(eq(table.server.id, serverId)); + if (!server || server.length == 0) { + return fail(400, { error: 'Server ID invalid' }); + } + + const channel = await db.select().from(table.channel).where(eq(table.channel.id, channelId)); + if (!channel || channel.length == 0) { + return fail(400, { error: 'Channel not found' }); + } + + // @TODO check permissions here (if owner ignore, then check roles, if any role has manageChannels then let delete channel) + + await db.transaction(async (tx) => { + await tx.delete(table.channel).where(eq(table.channel.id, channelId)); + + await tx + .update(table.server) + .set({ + channels: (server[0].channels as string[]).filter((id) => id !== channelId) + }) + .where(eq(table.server.id, serverId)); + }); + + return { success: true }; + }, deleteServer: async ({ request, locals }) => { const data = await request.formData(); const serverId = data.get('serverId');