diff --git a/Catalogger.Backend/Api/GuildsController.cs b/Catalogger.Backend/Api/GuildsController.cs index f225154..df301c3 100644 --- a/Catalogger.Backend/Api/GuildsController.cs +++ b/Catalogger.Backend/Api/GuildsController.cs @@ -137,156 +137,156 @@ public class GuildsController( // i love repeating myself wheeeeee if ( - req.GuildUpdate != null - && (req.GuildUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildUpdate)) + req.GuildUpdate == null + || (req.GuildUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildUpdate)) ) - guildConfig.Channels.GuildUpdate = req.GuildUpdate.Value; + guildConfig.Channels.GuildUpdate = req.GuildUpdate ?? 0; if ( - req.GuildEmojisUpdate != null - && ( + req.GuildEmojisUpdate == null + || ( req.GuildEmojisUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildEmojisUpdate) ) ) - guildConfig.Channels.GuildEmojisUpdate = req.GuildEmojisUpdate.Value; + guildConfig.Channels.GuildEmojisUpdate = req.GuildEmojisUpdate ?? 0; if ( - req.GuildRoleCreate != null - && ( + req.GuildRoleCreate == null + || ( req.GuildRoleCreate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildRoleCreate) ) ) - guildConfig.Channels.GuildRoleCreate = req.GuildRoleCreate.Value; + guildConfig.Channels.GuildRoleCreate = req.GuildRoleCreate ?? 0; if ( - req.GuildRoleUpdate != null - && ( + req.GuildRoleUpdate == null + || ( req.GuildRoleUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildRoleUpdate) ) ) - guildConfig.Channels.GuildRoleUpdate = req.GuildRoleUpdate.Value; + guildConfig.Channels.GuildRoleUpdate = req.GuildRoleUpdate ?? 0; if ( - req.GuildRoleDelete != null - && ( + req.GuildRoleDelete == null + || ( req.GuildRoleDelete == 0 || guildChannels.Any(c => c.ID.Value == req.GuildRoleDelete) ) ) - guildConfig.Channels.GuildRoleDelete = req.GuildRoleDelete.Value; + guildConfig.Channels.GuildRoleDelete = req.GuildRoleDelete ?? 0; if ( - req.ChannelCreate != null - && (req.ChannelCreate == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelCreate)) + req.ChannelCreate == null + || (req.ChannelCreate == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelCreate)) ) - guildConfig.Channels.ChannelCreate = req.ChannelCreate.Value; + guildConfig.Channels.ChannelCreate = req.ChannelCreate ?? 0; if ( - req.ChannelUpdate != null - && (req.ChannelUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelUpdate)) + req.ChannelUpdate == null + || (req.ChannelUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelUpdate)) ) - guildConfig.Channels.ChannelUpdate = req.ChannelUpdate.Value; + guildConfig.Channels.ChannelUpdate = req.ChannelUpdate ?? 0; if ( - req.ChannelDelete != null - && (req.ChannelDelete == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelDelete)) + req.ChannelDelete == null + || (req.ChannelDelete == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelDelete)) ) - guildConfig.Channels.ChannelDelete = req.ChannelDelete.Value; + guildConfig.Channels.ChannelDelete = req.ChannelDelete ?? 0; if ( - req.GuildMemberAdd != null - && (req.GuildMemberAdd == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberAdd)) + req.GuildMemberAdd == null + || (req.GuildMemberAdd == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberAdd)) ) - guildConfig.Channels.GuildMemberAdd = req.GuildMemberAdd.Value; + guildConfig.Channels.GuildMemberAdd = req.GuildMemberAdd ?? 0; if ( - req.GuildMemberUpdate != null - && ( + req.GuildMemberUpdate == null + || ( req.GuildMemberUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberUpdate) ) ) - guildConfig.Channels.GuildMemberUpdate = req.GuildMemberUpdate.Value; + guildConfig.Channels.GuildMemberUpdate = req.GuildMemberUpdate ?? 0; if ( - req.GuildKeyRoleUpdate != null - && ( + req.GuildKeyRoleUpdate == null + || ( req.GuildKeyRoleUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildKeyRoleUpdate) ) ) - guildConfig.Channels.GuildKeyRoleUpdate = req.GuildKeyRoleUpdate.Value; + guildConfig.Channels.GuildKeyRoleUpdate = req.GuildKeyRoleUpdate ?? 0; if ( - req.GuildMemberNickUpdate != null - && ( + req.GuildMemberNickUpdate == null + || ( req.GuildMemberNickUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberNickUpdate) ) ) - guildConfig.Channels.GuildMemberNickUpdate = req.GuildMemberNickUpdate.Value; + guildConfig.Channels.GuildMemberNickUpdate = req.GuildMemberNickUpdate ?? 0; if ( - req.GuildMemberAvatarUpdate != null - && ( + req.GuildMemberAvatarUpdate == null + || ( req.GuildMemberAvatarUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberAvatarUpdate) ) ) - guildConfig.Channels.GuildMemberAvatarUpdate = req.GuildMemberAvatarUpdate.Value; + guildConfig.Channels.GuildMemberAvatarUpdate = req.GuildMemberAvatarUpdate ?? 0; if ( - req.GuildMemberTimeout != null - && ( + req.GuildMemberTimeout == null + || ( req.GuildMemberTimeout == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberTimeout) ) ) - guildConfig.Channels.GuildMemberTimeout = req.GuildMemberTimeout.Value; + guildConfig.Channels.GuildMemberTimeout = req.GuildMemberTimeout ?? 0; if ( - req.GuildMemberRemove != null - && ( + req.GuildMemberRemove == null + || ( req.GuildMemberRemove == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberRemove) ) ) - guildConfig.Channels.GuildMemberRemove = req.GuildMemberRemove.Value; + guildConfig.Channels.GuildMemberRemove = req.GuildMemberRemove ?? 0; if ( - req.GuildMemberKick != null - && ( + req.GuildMemberKick == null + || ( req.GuildMemberKick == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberKick) ) ) - guildConfig.Channels.GuildMemberKick = req.GuildMemberKick.Value; + guildConfig.Channels.GuildMemberKick = req.GuildMemberKick ?? 0; if ( - req.GuildBanAdd != null - && (req.GuildBanAdd == 0 || guildChannels.Any(c => c.ID.Value == req.GuildBanAdd)) + req.GuildBanAdd == null + || (req.GuildBanAdd == 0 || guildChannels.Any(c => c.ID.Value == req.GuildBanAdd)) ) - guildConfig.Channels.GuildBanAdd = req.GuildBanAdd.Value; + guildConfig.Channels.GuildBanAdd = req.GuildBanAdd ?? 0; if ( - req.GuildBanRemove != null - && (req.GuildBanRemove == 0 || guildChannels.Any(c => c.ID.Value == req.GuildBanRemove)) + req.GuildBanRemove == null + || (req.GuildBanRemove == 0 || guildChannels.Any(c => c.ID.Value == req.GuildBanRemove)) ) - guildConfig.Channels.GuildBanRemove = req.GuildBanRemove.Value; + guildConfig.Channels.GuildBanRemove = req.GuildBanRemove ?? 0; if ( - req.InviteCreate != null - && (req.InviteCreate == 0 || guildChannels.Any(c => c.ID.Value == req.InviteCreate)) + req.InviteCreate == null + || (req.InviteCreate == 0 || guildChannels.Any(c => c.ID.Value == req.InviteCreate)) ) - guildConfig.Channels.InviteCreate = req.InviteCreate.Value; + guildConfig.Channels.InviteCreate = req.InviteCreate ?? 0; if ( - req.InviteDelete != null - && (req.InviteDelete == 0 || guildChannels.Any(c => c.ID.Value == req.InviteDelete)) + req.InviteDelete == null + || (req.InviteDelete == 0 || guildChannels.Any(c => c.ID.Value == req.InviteDelete)) ) - guildConfig.Channels.InviteDelete = req.InviteDelete.Value; + guildConfig.Channels.InviteDelete = req.InviteDelete ?? 0; if ( - req.MessageUpdate != null - && (req.MessageUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.MessageUpdate)) + req.MessageUpdate == null + || (req.MessageUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.MessageUpdate)) ) - guildConfig.Channels.MessageUpdate = req.MessageUpdate.Value; + guildConfig.Channels.MessageUpdate = req.MessageUpdate ?? 0; if ( - req.MessageDelete != null - && (req.MessageDelete == 0 || guildChannels.Any(c => c.ID.Value == req.MessageDelete)) + req.MessageDelete == null + || (req.MessageDelete == 0 || guildChannels.Any(c => c.ID.Value == req.MessageDelete)) ) - guildConfig.Channels.MessageDelete = req.MessageDelete.Value; + guildConfig.Channels.MessageDelete = req.MessageDelete ?? 0; if ( - req.MessageDeleteBulk != null - && ( + req.MessageDeleteBulk == null + || ( req.MessageDeleteBulk == 0 || guildChannels.Any(c => c.ID.Value == req.MessageDeleteBulk) ) ) - guildConfig.Channels.MessageDeleteBulk = req.MessageDeleteBulk.Value; + guildConfig.Channels.MessageDeleteBulk = req.MessageDeleteBulk ?? 0; db.Update(guildConfig); await db.SaveChangesAsync(); diff --git a/Catalogger.Frontend/package.json b/Catalogger.Frontend/package.json index 31d8f84..a86936f 100644 --- a/Catalogger.Frontend/package.json +++ b/Catalogger.Frontend/package.json @@ -25,6 +25,7 @@ "prettier": "^3.1.1", "prettier-plugin-svelte": "^3.1.2", "sass": "^1.80.1", + "svelecte": "^4.3.1", "svelte": "^4.2.7", "svelte-check": "^4.0.0", "typescript": "^5.0.0", diff --git a/Catalogger.Frontend/src/app.scss b/Catalogger.Frontend/src/app.scss index d8723b3..c984f99 100644 --- a/Catalogger.Frontend/src/app.scss +++ b/Catalogger.Frontend/src/app.scss @@ -1,3 +1,34 @@ @use "bootstrap/scss/bootstrap" with ( $color-mode-type: media-query ); + +:root { + --sv-min-height: 40px; +} + +// Svelecte doesn't have a built in dark mode :) +@media (prefers-color-scheme: dark) { + :root { + --sv-bg: #32363f; + --sv-disabled-bg: #eee; + --sv-border: 1px solid #626262; + --sv-item-selected-bg: #626262; + --sv-item-btn-color: #ccc; + --sv-item-btn-color-hover: #ccc; + --sv-item-btn-bg: #626262; + --sv-item-btn-bg-hover: #bc6063; + --sv-icon-color: #bbb; + --sv-icon-color-hover: #ccc; + --sv-separator-bg: #626262; + --sv-placeholder-color: #ccccd6; + --sv-dropdown-bg: var(--sv-bg); + --sv-dropdown-border: var(--sv-border); + --sv-dropdown-shadow: 0 1px 3px #555; + --sv-dropdown-active-bg: #553d3d; + --sv-dropdown-selected-bg: #754545; + --sv-create-kbd-border: 1px solid #626262; + --sv-create-kbd-bg: #626262; + --sv-create-disabled-bg: #fcbaba; + --sv-loader-border: 2px solid #626262; + } +} diff --git a/Catalogger.Frontend/src/lib/api.ts b/Catalogger.Frontend/src/lib/api.ts index ba05028..52a5b79 100644 --- a/Catalogger.Frontend/src/lib/api.ts +++ b/Catalogger.Frontend/src/lib/api.ts @@ -1,28 +1,3 @@ -export type User = { - id: string; - tag: string; - avatar_url: string; -}; - -export type PartialGuild = { - id: string; - name: string; - icon_url: string; - bot_in_guild: boolean; -}; - -export type CurrentUser = { - user: User; - guilds: PartialGuild[]; -}; - -export type AuthCallback = CurrentUser & { token: string }; - -export type ApiError = { - error_code: string; - message: string; -}; - export const TOKEN_KEY = "catalogger-token"; export default async function apiFetch( @@ -53,3 +28,83 @@ export default async function apiFetch( return (await resp.json()) as T; } + +export type User = { + id: string; + tag: string; + avatar_url: string; +}; + +export type PartialGuild = { + id: string; + name: string; + icon_url: string; + bot_in_guild: boolean; +}; + +export type FullGuild = { + id: string; + name: string; + icon_url: string; + categories: GuildCategory[]; + channels_without_category: GuildChannel[]; + config: GuildConfig; +}; + +export type GuildCategory = { + id: string; + name: string; + channels: GuildChannel[]; +}; + +export type GuildChannel = { + id: string; + name: string; + can_log_to: boolean; + can_redirect_from: boolean; +}; + +export type CurrentUser = { + user: User; + guilds: PartialGuild[]; +}; + +export type AuthCallback = CurrentUser & { token: string }; + +export type ApiError = { + error_code: string; + message: string; +}; + +export type GuildConfig = GuildChannelConfig & { + ignored_channels: string[]; + ignored_users: string[]; + ignored_users_per_channel: Record; + redirects: Record; +}; + +export type GuildChannelConfig = { + guild_update: string; + guild_emojis_update: string; + guild_role_create: string; + guild_role_update: string; + guild_role_delete: string; + channel_create: string; + channel_update: string; + channel_delete: string; + guild_member_add: string; + guild_member_update: string; + guild_key_role_update: string; + guild_member_nick_update: string; + guild_member_avatar_update: string; + guild_member_timeout: string; + guild_member_remove: string; + guild_member_kick: string; + guild_ban_add: string; + guild_ban_remove: string; + invite_create: string; + invite_delete: string; + message_update: string; + message_delete: string; + message_delete_bulk: string; +}; diff --git a/Catalogger.Frontend/src/routes/+page.svelte b/Catalogger.Frontend/src/routes/+page.svelte index ee972ad..3d398fe 100644 --- a/Catalogger.Frontend/src/routes/+page.svelte +++ b/Catalogger.Frontend/src/routes/+page.svelte @@ -12,6 +12,10 @@ }); + + Catalogger + +

Welcome to SvelteKit

Visit kit.svelte.dev to read the documentation diff --git a/Catalogger.Frontend/src/routes/callback/+page.ts b/Catalogger.Frontend/src/routes/callback/+page.ts new file mode 100644 index 0000000..d43d0cd --- /dev/null +++ b/Catalogger.Frontend/src/routes/callback/+page.ts @@ -0,0 +1 @@ +export const prerender = false; diff --git a/Catalogger.Frontend/src/routes/dash/+layout.ts b/Catalogger.Frontend/src/routes/dash/+layout.ts index 08eaf84..4b16cb6 100644 --- a/Catalogger.Frontend/src/routes/dash/+layout.ts +++ b/Catalogger.Frontend/src/routes/dash/+layout.ts @@ -1,6 +1,8 @@ import { addToast } from "$lib/toast"; import { redirect } from "@sveltejs/kit"; +export const prerender = false; + export const load = async ({ parent }) => { const data = await parent(); if (!data.user) { diff --git a/Catalogger.Frontend/src/routes/dash/+page.svelte b/Catalogger.Frontend/src/routes/dash/+page.svelte index efe1736..59cc2a0 100644 --- a/Catalogger.Frontend/src/routes/dash/+page.svelte +++ b/Catalogger.Frontend/src/routes/dash/+page.svelte @@ -8,6 +8,10 @@ $: unjoinedGuilds = data.guilds.filter((g) => !g.bot_in_guild); + + Catalogger - Dashboard + +

Manage your servers

diff --git a/Catalogger.Frontend/src/routes/dash/[guildId]/+layout.svelte b/Catalogger.Frontend/src/routes/dash/[guildId]/+layout.svelte new file mode 100644 index 0000000..839090b --- /dev/null +++ b/Catalogger.Frontend/src/routes/dash/[guildId]/+layout.svelte @@ -0,0 +1,86 @@ + + + + Catalogger - Managing {data.guild.name} + + +
+ + + {#if $page.url.pathname === `/dash/${data.guild.id}`} + + {/if} +
+ + diff --git a/Catalogger.Frontend/src/routes/dash/[guildId]/+layout.ts b/Catalogger.Frontend/src/routes/dash/[guildId]/+layout.ts new file mode 100644 index 0000000..8c062d8 --- /dev/null +++ b/Catalogger.Frontend/src/routes/dash/[guildId]/+layout.ts @@ -0,0 +1,17 @@ +import apiFetch, { type ApiError, type FullGuild } from "$lib/api"; +import { error } from "@sveltejs/kit"; + +export const load = async ({ params }) => { + try { + const guild = await apiFetch( + "GET", + `/api/guilds/${params.guildId}`, + ); + + return { guild }; + } catch (e) { + const err = e as ApiError; + console.log("Fetching guild", params.guildId, ":", e); + error(404, err.message); + } +}; diff --git a/Catalogger.Frontend/src/routes/dash/[guildId]/+page.svelte b/Catalogger.Frontend/src/routes/dash/[guildId]/+page.svelte new file mode 100644 index 0000000..bb22c87 --- /dev/null +++ b/Catalogger.Frontend/src/routes/dash/[guildId]/+page.svelte @@ -0,0 +1,138 @@ + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + Key roles can be designated with the /key-roles command or the + "Key roles" tab above. + +
+
+ + +
+
+ + +
+
+ + + Note that this will not log timeouts naturally expiring. +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + This will only be triggered by bots, not normal users. +
+
diff --git a/Catalogger.Frontend/src/routes/dash/[guildId]/ChannelSelect.svelte b/Catalogger.Frontend/src/routes/dash/[guildId]/ChannelSelect.svelte new file mode 100644 index 0000000..d774598 --- /dev/null +++ b/Catalogger.Frontend/src/routes/dash/[guildId]/ChannelSelect.svelte @@ -0,0 +1,21 @@ + + + diff --git a/Catalogger.Frontend/src/routes/dash/[guildId]/redirects/+page.svelte b/Catalogger.Frontend/src/routes/dash/[guildId]/redirects/+page.svelte new file mode 100644 index 0000000..5637e9f --- /dev/null +++ b/Catalogger.Frontend/src/routes/dash/[guildId]/redirects/+page.svelte @@ -0,0 +1 @@ +

aaaaaaaaa

\ No newline at end of file diff --git a/Catalogger.Frontend/svelte.config.js b/Catalogger.Frontend/svelte.config.js index 8048198..3e3342f 100644 --- a/Catalogger.Frontend/svelte.config.js +++ b/Catalogger.Frontend/svelte.config.js @@ -12,7 +12,7 @@ const config = { // If your environment is not supported, or you settled on a specific environment, switch out the adapter. // See https://kit.svelte.dev/docs/adapters for more information about adapters. adapter: adapter({ - fallback: "index.html", + fallback: "spa.html", }), }, }; diff --git a/Catalogger.Frontend/vite.config.ts b/Catalogger.Frontend/vite.config.ts index 4f83fc2..5fc435e 100644 --- a/Catalogger.Frontend/vite.config.ts +++ b/Catalogger.Frontend/vite.config.ts @@ -4,6 +4,8 @@ import { defineConfig } from "vite"; export default defineConfig({ plugins: [sveltekit()], server: { + port: 5173, + strictPort: true, proxy: { "/api": { target: "http://localhost:5005", @@ -12,6 +14,7 @@ export default defineConfig({ }, hmr: { host: "localhost", + port: 5173, protocol: "ws", }, }, diff --git a/Catalogger.Frontend/yarn.lock b/Catalogger.Frontend/yarn.lock index 88a0d2c..04ac7ac 100644 --- a/Catalogger.Frontend/yarn.lock +++ b/Catalogger.Frontend/yarn.lock @@ -1497,6 +1497,13 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +svelecte@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/svelecte/-/svelecte-4.3.1.tgz#d7bfb8c14ef080ac43e36f42c6020aae044dd2fe" + integrity sha512-0wyPDel4J16ecwaDHvgdXsI5QvuC1pU8n2FY3tO4X39e6gcrnoQkgV3zVWYdm7xAQaqawAgZNBDgNZlonPLOmw== + dependencies: + svelte-tiny-virtual-list "^2.1.0" + svelte-check@^4.0.0: version "4.0.5" resolved "https://registry.yarnpkg.com/svelte-check/-/svelte-check-4.0.5.tgz#5cd910c3b1d50f38159c17cc3bae127cbbb55c8d" @@ -1524,6 +1531,11 @@ svelte-hmr@^0.16.0: resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.16.0.tgz#9f345b7d1c1662f1613747ed7e82507e376c1716" integrity sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA== +svelte-tiny-virtual-list@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/svelte-tiny-virtual-list/-/svelte-tiny-virtual-list-2.1.2.tgz#db826e5915f0374b793798bc8125b8a66fbc6c34" + integrity sha512-jeP/WMvgFUR4mYXHGPiCexjX5DuzSO+3xzHNhxfcsFyy+uYPtnqI5UGb383swpzQAyXB0OBqYfzpYihD/5gxnA== + svelte@^4.2.7: version "4.2.19" resolved "https://registry.yarnpkg.com/svelte/-/svelte-4.2.19.tgz#4e6e84a8818e2cd04ae0255fcf395bc211e61d4c"