From 274e2bfa7872fbe0121040799119b2cdb5461653 Mon Sep 17 00:00:00 2001 From: rxdn <29165304+rxdn@users.noreply.github.com> Date: Mon, 29 Jul 2024 23:26:38 +0100 Subject: [PATCH] Ticket panel rework --- .../endpoints/api/panel/multipanelcreate.go | 83 ++- .../api/panel/multipanelmessagedata.go | 53 +- .../endpoints/api/panel/multipanelupdate.go | 34 +- app/http/endpoints/api/panel/paneldelete.go | 9 +- app/http/endpoints/api/panel/panelupdate.go | 11 +- app/http/endpoints/api/panel/validation.go | 17 +- app/http/endpoints/api/tags/tagcreate.go | 5 +- frontend/src/components/Collapsible.svelte | 7 +- frontend/src/components/EmbedForm.svelte | 168 +++--- frontend/src/components/PanelDropdown.svelte | 2 - .../manage/MultiPanelCreationForm.svelte | 177 +++---- .../manage/MultiPanelEditModal.svelte | 91 ---- .../manage/PanelCreationForm.svelte | 4 + .../components/manage/PanelEditModal.svelte | 100 ---- frontend/src/includes/Navbar.svelte | 2 +- frontend/src/js/common.js | 78 +++ frontend/src/js/util.js | 52 +- frontend/src/routes.js | 37 +- frontend/src/views/Panels.svelte | 490 ------------------ .../src/views/panels/CreateMultiPanel.svelte | 95 ++++ frontend/src/views/panels/CreatePanel.svelte | 99 ++++ .../src/views/panels/EditMultiPanel.svelte | 104 ++++ frontend/src/views/panels/EditPanel.svelte | 108 ++++ frontend/src/views/panels/Panels.svelte | 295 +++++++++++ go.mod | 2 +- go.sum | 2 + utils/types/colour.go | 6 + 27 files changed, 1144 insertions(+), 987 deletions(-) delete mode 100644 frontend/src/components/manage/MultiPanelEditModal.svelte delete mode 100644 frontend/src/components/manage/PanelEditModal.svelte create mode 100644 frontend/src/js/common.js delete mode 100644 frontend/src/views/Panels.svelte create mode 100644 frontend/src/views/panels/CreateMultiPanel.svelte create mode 100644 frontend/src/views/panels/CreatePanel.svelte create mode 100644 frontend/src/views/panels/EditMultiPanel.svelte create mode 100644 frontend/src/views/panels/EditPanel.svelte create mode 100644 frontend/src/views/panels/Panels.svelte diff --git a/app/http/endpoints/api/panel/multipanelcreate.go b/app/http/endpoints/api/panel/multipanelcreate.go index 1b2d29f..c8edf47 100644 --- a/app/http/endpoints/api/panel/multipanelcreate.go +++ b/app/http/endpoints/api/panel/multipanelcreate.go @@ -8,35 +8,31 @@ import ( "github.com/TicketsBot/GoPanel/rpc" "github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/utils" + "github.com/TicketsBot/GoPanel/utils/types" "github.com/TicketsBot/common/premium" "github.com/TicketsBot/database" "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" "github.com/rxdn/gdl/objects/channel" "github.com/rxdn/gdl/rest/request" "golang.org/x/sync/errgroup" ) type multiPanelCreateData struct { - Title string `json:"title"` - Content string `json:"content"` - Colour int32 `json:"colour"` - ChannelId uint64 `json:"channel_id,string"` - SelectMenu bool `json:"select_menu"` - Panels []int `json:"panels"` - ImageUrl *string `json:"image_url,omitempty"` - ThumbnailUrl *string `json:"thumbnail_url,omitempty"` + ChannelId uint64 `json:"channel_id,string"` + SelectMenu bool `json:"select_menu"` + SelectMenuPlaceholder *string `json:"select_menu_placeholder,omitempty" validate:"omitempty,max=150"` + Panels []int `json:"panels"` + Embed *types.CustomEmbed `json:"embed" validate:"omitempty,dive"` } func (d *multiPanelCreateData) IntoMessageData(isPremium bool) multiPanelMessageData { return multiPanelMessageData{ - ChannelId: d.ChannelId, - Title: d.Title, - Content: d.Content, - Colour: int(d.Colour), - SelectMenu: d.SelectMenu, - IsPremium: isPremium, - ImageUrl: d.ImageUrl, - ThumbnailUrl: d.ThumbnailUrl, + IsPremium: isPremium, + ChannelId: d.ChannelId, + SelectMenu: d.SelectMenu, + SelectMenuPlaceholder: d.SelectMenuPlaceholder, + Embed: d.Embed.IntoDiscordEmbed(), } } @@ -49,6 +45,18 @@ func MultiPanelCreate(ctx *gin.Context) { return } + if err := validate.Struct(data); err != nil { + var validationErrors validator.ValidationErrors + if ok := errors.As(err, &validationErrors); !ok { + ctx.JSON(500, utils.ErrorStr("An error occurred while validating the panel")) + return + } + + formatted := "Your input contained the following errors:\n" + utils.FormatValidationErrors(validationErrors) + ctx.JSON(400, utils.ErrorStr(formatted)) + return + } + // validate body & get sub-panels panels, err := data.doValidations(guildId) if err != nil { @@ -86,14 +94,17 @@ func MultiPanelCreate(ctx *gin.Context) { return } + dbEmbed, dbEmbedFields := data.Embed.IntoDatabaseStruct() multiPanel := database.MultiPanel{ - MessageId: messageId, - ChannelId: data.ChannelId, - GuildId: guildId, - Title: data.Title, - Content: data.Content, - Colour: int(data.Colour), - SelectMenu: data.SelectMenu, + MessageId: messageId, + ChannelId: data.ChannelId, + GuildId: guildId, + SelectMenu: data.SelectMenu, + SelectMenuPlaceholder: data.SelectMenuPlaceholder, + Embed: &database.CustomEmbedWithFields{ + CustomEmbed: dbEmbed, + Fields: dbEmbedFields, + }, } multiPanel.Id, err = dbclient.Client.MultiPanels.Create(ctx, multiPanel) @@ -123,10 +134,12 @@ func MultiPanelCreate(ctx *gin.Context) { } func (d *multiPanelCreateData) doValidations(guildId uint64) (panels []database.Panel, err error) { + if err := validateEmbed(d.Embed); err != nil { + return nil, err + } + group, _ := errgroup.WithContext(context.Background()) - group.Go(d.validateTitle) - group.Go(d.validateContent) group.Go(d.validateChannel(guildId)) group.Go(func() (e error) { panels, e = d.validatePanels(guildId) @@ -137,26 +150,6 @@ func (d *multiPanelCreateData) doValidations(guildId uint64) (panels []database. return } -func (d *multiPanelCreateData) validateTitle() (err error) { - if len(d.Title) > 255 { - err = errors.New("Embed title must be between 1 and 255 characters") - } else if len(d.Title) == 0 { - d.Title = "Click to open a ticket" - } - - return -} - -func (d *multiPanelCreateData) validateContent() (err error) { - if len(d.Content) > 4096 { - err = errors.New("Embed content must be between 1 and 4096 characters") - } else if len(d.Content) == 0 { // Fill default - d.Content = "Click on the button corresponding to the type of ticket you wish to open" - } - - return -} - func (d *multiPanelCreateData) validateChannel(guildId uint64) func() error { return func() error { // TODO: Use proper context diff --git a/app/http/endpoints/api/panel/multipanelmessagedata.go b/app/http/endpoints/api/panel/multipanelmessagedata.go index ee34b6a..c82e2c1 100644 --- a/app/http/endpoints/api/panel/multipanelmessagedata.go +++ b/app/http/endpoints/api/panel/multipanelmessagedata.go @@ -13,46 +13,32 @@ import ( ) type multiPanelMessageData struct { + IsPremium bool + ChannelId uint64 - Title string - Content string - Colour int - SelectMenu bool - IsPremium bool - ImageUrl, ThumbnailUrl *string + SelectMenu bool + SelectMenuPlaceholder *string + + Embed *embed.Embed } func multiPanelIntoMessageData(panel database.MultiPanel, isPremium bool) multiPanelMessageData { return multiPanelMessageData{ - ChannelId: panel.ChannelId, - Title: panel.Title, - Content: panel.Content, - Colour: panel.Colour, - SelectMenu: panel.SelectMenu, - IsPremium: isPremium, - ImageUrl: panel.ImageUrl, - ThumbnailUrl: panel.ThumbnailUrl, + IsPremium: isPremium, + + ChannelId: panel.ChannelId, + + SelectMenu: panel.SelectMenu, + SelectMenuPlaceholder: panel.SelectMenuPlaceholder, + Embed: types.NewCustomEmbed(panel.Embed.CustomEmbed, panel.Embed.Fields).IntoDiscordEmbed(), } } func (d *multiPanelMessageData) send(ctx *botcontext.BotContext, panels []database.Panel) (uint64, error) { - e := embed.NewEmbed(). - SetTitle(d.Title). - SetDescription(d.Content). - SetColor(d.Colour) - - if d.ImageUrl != nil { - e.SetImage(*d.ImageUrl) - } - - if d.ThumbnailUrl != nil { - e.SetThumbnail(*d.ThumbnailUrl) - } - if !d.IsPremium { // TODO: Don't harcode - e.SetFooter("Powered by ticketsbot.net", "https://ticketsbot.net/assets/img/logo.png") + d.Embed.SetFooter("Powered by ticketsbot.net", "https://ticketsbot.net/assets/img/logo.png") } var components []component.Component @@ -68,13 +54,20 @@ func (d *multiPanelMessageData) send(ctx *botcontext.BotContext, panels []databa } } + var placeholder string + if d.SelectMenuPlaceholder == nil { + placeholder = "Select a topic..." + } else { + placeholder = *d.SelectMenuPlaceholder + } + components = []component.Component{ component.BuildActionRow( component.BuildSelectMenu( component.SelectMenu{ CustomId: "multipanel", Options: options, - Placeholder: "Select a topic...", + Placeholder: placeholder, MinValues: utils.IntPtr(1), MaxValues: utils.IntPtr(1), Disabled: false, @@ -116,7 +109,7 @@ func (d *multiPanelMessageData) send(ctx *botcontext.BotContext, panels []databa } data := rest.CreateMessageData{ - Embeds: []*embed.Embed{e}, + Embeds: []*embed.Embed{d.Embed}, Components: components, } diff --git a/app/http/endpoints/api/panel/multipanelupdate.go b/app/http/endpoints/api/panel/multipanelupdate.go index a5b4d08..df921b0 100644 --- a/app/http/endpoints/api/panel/multipanelupdate.go +++ b/app/http/endpoints/api/panel/multipanelupdate.go @@ -11,6 +11,7 @@ import ( "github.com/TicketsBot/common/premium" "github.com/TicketsBot/database" "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" "github.com/rxdn/gdl/rest" "github.com/rxdn/gdl/rest/request" "golang.org/x/sync/errgroup" @@ -53,6 +54,18 @@ func MultiPanelUpdate(c *gin.Context) { return } + if err := validate.Struct(data); err != nil { + var validationErrors validator.ValidationErrors + if ok := errors.As(err, &validationErrors); !ok { + c.JSON(500, utils.ErrorStr("An error occurred while validating the panel")) + return + } + + formatted := "Your input contained the following errors:\n" + utils.FormatValidationErrors(validationErrors) + c.JSON(400, utils.ErrorStr(formatted)) + return + } + // validate body & get sub-panels panels, err := data.doValidations(guildId) if err != nil { @@ -115,17 +128,18 @@ func MultiPanelUpdate(c *gin.Context) { } // update DB + dbEmbed, dbEmbedFields := data.Embed.IntoDatabaseStruct() updated := database.MultiPanel{ - Id: multiPanel.Id, - MessageId: messageId, - ChannelId: data.ChannelId, - GuildId: guildId, - Title: data.Title, - Content: data.Content, - Colour: int(data.Colour), - SelectMenu: data.SelectMenu, - ImageUrl: data.ImageUrl, - ThumbnailUrl: data.ThumbnailUrl, + Id: multiPanel.Id, + MessageId: messageId, + ChannelId: data.ChannelId, + GuildId: guildId, + SelectMenu: data.SelectMenu, + SelectMenuPlaceholder: data.SelectMenuPlaceholder, + Embed: &database.CustomEmbedWithFields{ + CustomEmbed: dbEmbed, + Fields: dbEmbedFields, + }, } if err = dbclient.Client.MultiPanels.Update(c, multiPanel.Id, updated); err != nil { diff --git a/app/http/endpoints/api/panel/paneldelete.go b/app/http/endpoints/api/panel/paneldelete.go index 42f2fc5..b58ade4 100644 --- a/app/http/endpoints/api/panel/paneldelete.go +++ b/app/http/endpoints/api/panel/paneldelete.go @@ -95,14 +95,7 @@ func DeletePanel(ctx *gin.Context) { return } - messageData := multiPanelMessageData{ - Title: multiPanel.Title, - Content: multiPanel.Content, - Colour: multiPanel.Colour, - ChannelId: multiPanel.ChannelId, - SelectMenu: multiPanel.SelectMenu, - IsPremium: premiumTier > premium.None, - } + messageData := multiPanelIntoMessageData(multiPanel, premiumTier > premium.None) messageId, err := messageData.send(botContext, panels) if err != nil { diff --git a/app/http/endpoints/api/panel/panelupdate.go b/app/http/endpoints/api/panel/panelupdate.go index cb71ad5..c15a385 100644 --- a/app/http/endpoints/api/panel/panelupdate.go +++ b/app/http/endpoints/api/panel/panelupdate.go @@ -306,16 +306,7 @@ func UpdatePanel(ctx *gin.Context) { return } - messageData := multiPanelMessageData{ - Title: multiPanel.Title, - Content: multiPanel.Content, - Colour: multiPanel.Colour, - ChannelId: multiPanel.ChannelId, - SelectMenu: multiPanel.SelectMenu, - IsPremium: premiumTier > premium.None, - ImageUrl: multiPanel.ImageUrl, - ThumbnailUrl: multiPanel.ThumbnailUrl, - } + messageData := multiPanelIntoMessageData(multiPanel, premiumTier > premium.None) messageId, err := messageData.send(botContext, panels) if err != nil { diff --git a/app/http/endpoints/api/panel/validation.go b/app/http/endpoints/api/panel/validation.go index 0fa1001..2053dac 100644 --- a/app/http/endpoints/api/panel/validation.go +++ b/app/http/endpoints/api/panel/validation.go @@ -10,6 +10,7 @@ import ( "github.com/TicketsBot/GoPanel/botcontext" dbclient "github.com/TicketsBot/GoPanel/database" "github.com/TicketsBot/GoPanel/utils" + "github.com/TicketsBot/GoPanel/utils/types" "github.com/TicketsBot/database" "github.com/rxdn/gdl/objects/channel" "github.com/rxdn/gdl/objects/guild" @@ -289,13 +290,7 @@ func validateNamingScheme(ctx PanelValidationContext) validation.ValidationFunc func validateWelcomeMessage(ctx PanelValidationContext) validation.ValidationFunc { return func() error { - wm := ctx.Data.WelcomeMessage - - if wm == nil || wm.Title != nil || wm.Description != nil || len(wm.Fields) > 0 || wm.ImageUrl != nil || wm.ThumbnailUrl != nil { - return nil - } - - return validation.NewInvalidInputError("Welcome message has no content") + return validateEmbed(ctx.Data.WelcomeMessage) } } @@ -339,3 +334,11 @@ func validateAccessControlList(ctx PanelValidationContext) validation.Validation return nil } } + +func validateEmbed(e *types.CustomEmbed) error { + if e == nil || e.Title != nil || e.Description != nil || len(e.Fields) > 0 || e.ImageUrl != nil || e.ThumbnailUrl != nil { + return nil + } + + return validation.NewInvalidInputError("Your embed message does not contain any content") +} diff --git a/app/http/endpoints/api/tags/tagcreate.go b/app/http/endpoints/api/tags/tagcreate.go index 2bec71c..7e89304 100644 --- a/app/http/endpoints/api/tags/tagcreate.go +++ b/app/http/endpoints/api/tags/tagcreate.go @@ -1,6 +1,7 @@ package api import ( + "errors" "fmt" "github.com/TicketsBot/GoPanel/botcontext" dbclient "github.com/TicketsBot/GoPanel/database" @@ -59,8 +60,8 @@ func CreateTag(ctx *gin.Context) { // TODO: Limit command amount if err := validate.Struct(data); err != nil { - validationErrors, ok := err.(validator.ValidationErrors) - if !ok { + var validationErrors validator.ValidationErrors + if ok := errors.As(err, &validationErrors); !ok { ctx.JSON(500, utils.ErrorStr("An error occurred while validating the integration")) return } diff --git a/frontend/src/components/Collapsible.svelte b/frontend/src/components/Collapsible.svelte index fd637fa..94ecc5d 100644 --- a/frontend/src/components/Collapsible.svelte +++ b/frontend/src/components/Collapsible.svelte @@ -36,7 +36,7 @@ @@ -98,12 +106,14 @@ import DateTimePicker from "./form/DateTimePicker.svelte"; import Checkbox from "./form/Checkbox.svelte"; import Button from "./Button.svelte"; + import {onMount} from "svelte"; + import {intToColour, colourToInt} from "../js/util"; export let data; $: data = data ?? { fields: [], - colour: '#2ECC71', + colour: 0x2ECC71, author: {}, footer: {}, }; @@ -119,4 +129,28 @@ data.fields.splice(i, 1); data = data; } + + let tempColour = "#2ecc71"; + function updateColour() { + data.colour = colourToInt(tempColour); + } + + let appliedOverrides = false; + onMount(() => { + data.author = data.author ?? {}; + data.footer = data.footer ?? {}; + data.fields = data.fields ?? []; + + if (!data.colour) { + data.colour = 0x2ECC71; + } else { + if (typeof data.colour === "string" && data.colour.startsWith('#')) { + data.colour = parseInt(data.colour.slice(1), 16) + } + + tempColour = intToColour(data.colour); + } + + appliedOverrides = true; + }); diff --git a/frontend/src/components/PanelDropdown.svelte b/frontend/src/components/PanelDropdown.svelte index d1cb411..4e7c8bc 100644 --- a/frontend/src/components/PanelDropdown.svelte +++ b/frontend/src/components/PanelDropdown.svelte @@ -24,8 +24,6 @@ selectedRaw = []; } - console.log(selectedRaw) - if (isMulti) { selected = selectedRaw.map((panel) => panel.panel_id); } else { diff --git a/frontend/src/components/manage/MultiPanelCreationForm.svelte b/frontend/src/components/manage/MultiPanelCreationForm.svelte index 7cac78f..819e67e 100644 --- a/frontend/src/components/manage/MultiPanelCreationForm.svelte +++ b/frontend/src/components/manage/MultiPanelCreationForm.svelte @@ -1,59 +1,41 @@
-
- -
-
-