Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 6 additions & 41 deletions backend/app/http/endpoints/api/panel/multipanelget.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/ticketsbot-cloud/dashboard/backend/app"
dbclient "github.com/ticketsbot-cloud/dashboard/backend/database"
"github.com/ticketsbot-cloud/dashboard/backend/utils"
"github.com/ticketsbot-cloud/dashboard/backend/utils/types"
)

func MultiPanelGet(ctx *gin.Context) {
Expand All @@ -19,36 +20,14 @@ func MultiPanelGet(ctx *gin.Context) {
CustomEmojiId *uint64 `json:"custom_emoji_id,omitempty,string"`
}

type embedAuthor struct {
Name *string `json:"name"`
IconUrl *string `json:"icon_url"`
Url *string `json:"url"`
}

type embedFooter struct {
Text *string `json:"text"`
IconUrl *string `json:"icon_url"`
}

type embedResponse struct {
Title *string `json:"title"`
Description *string `json:"description"`
Url *string `json:"url"`
Colour uint32 `json:"colour"`
Author embedAuthor `json:"author"`
ImageUrl *string `json:"image_url"`
ThumbnailUrl *string `json:"thumbnail_url"`
Footer embedFooter `json:"footer"`
}

type multiPanelResponse struct {
Id int `json:"id"`
MessageId uint64 `json:"message_id,string"`
ChannelId uint64 `json:"channel_id,string"`
GuildId uint64 `json:"guild_id,string"`
SelectMenu bool `json:"select_menu"`
SelectMenuPlaceholder *string `json:"select_menu_placeholder"`
Embed *embedResponse `json:"embed"`
Embed *types.CustomEmbed `json:"embed"`
Panels []panelConfiguration `json:"panels"`
}

Expand Down Expand Up @@ -76,25 +55,11 @@ func MultiPanelGet(ctx *gin.Context) {
return
}

var transformedEmbed *embedResponse
if multiPanel.Embed != nil {
e := multiPanel.Embed.CustomEmbed
transformedEmbed = &embedResponse{
Title: e.Title,
Description: e.Description,
Url: e.Url,
Colour: e.Colour,
Author: embedAuthor{Name: e.AuthorName, IconUrl: e.AuthorIconUrl, Url: e.AuthorUrl},
ImageUrl: e.ImageUrl,
ThumbnailUrl: e.ThumbnailUrl,
Footer: embedFooter{Text: e.FooterText, IconUrl: e.FooterIconUrl},
}
var transformedEmbed *types.CustomEmbed
if multiPanel.Embed != nil && multiPanel.Embed.CustomEmbed != nil {
transformedEmbed = types.NewCustomEmbed(multiPanel.Embed.CustomEmbed, multiPanel.Embed.Fields)
} else {
transformedEmbed = &embedResponse{
Colour: 0x5865f2,
Author: embedAuthor{},
Footer: embedFooter{},
}
transformedEmbed = &types.CustomEmbed{Colour: 0x5865f2}
}

panels, err := dbclient.Client.MultiPanelTargets.GetPanels(ctx, multiPanelId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,19 @@ func multiPanelDiscordSubPanelError(action, detail string) string {
}

func multiPanelIntoMessageData(panel database.MultiPanel, footer footerPolicy) multiPanelMessageData {
custom := &types.CustomEmbed{Colour: 0x5865f2}
if panel.Embed != nil && panel.Embed.CustomEmbed != nil {
custom = types.NewCustomEmbed(panel.Embed.CustomEmbed, panel.Embed.Fields)
}

return multiPanelMessageData{
Footer: footer,

ChannelId: panel.ChannelId,

SelectMenu: panel.SelectMenu,
SelectMenuPlaceholder: panel.SelectMenuPlaceholder,
Embed: types.NewCustomEmbed(panel.Embed.CustomEmbed, panel.Embed.Fields).IntoDiscordEmbed(),
Embed: custom.IntoDiscordEmbed(),
}
}

Expand Down
2 changes: 1 addition & 1 deletion backend/app/http/endpoints/api/panel/multipanelupdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,6 @@ func MultiPanelUpdate(c *gin.Context) {
})
c.JSON(200, gin.H{
"success": true,
"data": multiPanel,
"data": updated,
})
}
49 changes: 49 additions & 0 deletions frontend/src/components/PanelPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ function resolveEmoteName(emote: Panel["emote"] | undefined): string {
return "";
}

type PreviewField = { name: string; value: string; inline?: boolean };

type MultiPanelPreviewRequest = Omit<MultiPanelRequest, "channel_id"> &
Partial<Pick<MultiPanelRequest, "channel_id">>;

Expand Down Expand Up @@ -122,6 +124,30 @@ const PanelPreview: FC<PanelPreviewProps> = ({ type, data, brandingFooter }) =>
const displayFooterText = brandingFooter ? BRANDING_FOOTER_TEXT : footerText;
const displayFooterIconUrl = brandingFooter ? BRANDING_FOOTER_ICON : footerIconUrl;

// Discord packs at most 3 inline fields per row.
const fieldRows: PreviewField[][] = [];
if (embedData?.fields?.length) {
let currentRow: PreviewField[] = [];
for (const field of embedData.fields) {
if (field.inline) {
currentRow.push(field);
if (currentRow.length === 3) {
fieldRows.push(currentRow);
currentRow = [];
}
} else {
if (currentRow.length > 0) {
fieldRows.push(currentRow);
currentRow = [];
}
fieldRows.push([field]);
}
}
if (currentRow.length > 0) {
fieldRows.push(currentRow);
}
}

const thumbnailUrl = resolveTicketAvatar(embedData?.thumbnail_url);
const imageUrl = resolveTicketAvatar(embedData?.image_url);
const authorIconUrl = resolveTicketAvatar(embedData?.author?.icon_url);
Expand Down Expand Up @@ -180,6 +206,29 @@ const PanelPreview: FC<PanelPreviewProps> = ({ type, data, brandingFooter }) =>
className="text-sm mt-1 text-[#dbdee1] leading-snug"
/>
)}
{fieldRows.length > 0 && (
<div className="mt-2 flex flex-col gap-2">
{fieldRows.map((row, ri) => (
<div
key={ri}
className="grid gap-2"
style={{
gridTemplateColumns: row.length > 1 ? `repeat(${row.length}, 1fr)` : "1fr",
}}
>
{row.map((field, fi) => (
<div key={fi} className="min-w-0">
<DiscordContent
content={field.name}
className="text-xs font-semibold text-white"
/>
<DiscordContent content={field.value} className="text-xs text-[#dbdee1]" />
</div>
))}
</div>
))}
</div>
)}
</div>
{thumbnailUrl && (
<div className="ml-4 shrink-0">
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/lib/guild-channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export const sortGuildChannels = (channels: GuildChannel[]) => {
const toOption = (channel: GuildChannel) => ({
key: String(channel.id),
label: `${channel.type == 2 ? "🔈" : "#"} ${channel.name}`,
disabled: channel.type !== 0,
// Type 5 = announcement, which the API accepts.
disabled: channel.type !== 0 && channel.type !== 5,
});

return sorted.reduce(
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/pages/manage/multipanels/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { PANEL_MESSAGE_INFO } from "@/constants/panelChannelInfo";
import MultiPanelInfoModal from "@/components/modals/MultiPanelInfoModal";
import { EMBED_LIMITS } from "@/constants/embedLimits";
import EmbedCharacterTotal from "@/components/EmbedCharacterTotal";
import EmbedFieldsEditor from "@/components/EmbedFieldsEditor";
import { useApiErrorHandler } from "@/hooks/useApiErrorHandler";

type MultiPanelDraft = Omit<MultiPanelRequest, "channel_id"> & {
Expand Down Expand Up @@ -91,6 +92,7 @@ const MultiPanelsPage: FC = () => {
author: {},
colour: 0x5865f2,
description: "",
fields: [],
footer: {},
},
panels: [] as MultiPanelPanelEntry[],
Expand Down Expand Up @@ -351,6 +353,19 @@ const MultiPanelsPage: FC = () => {
}
/>
</div>
<div className="py-2">
<TextInput
label="Title URL"
placeholder="e.g. https://example.com"
value={multiPanel.embed?.url || ""}
onChange={(e) =>
setMultiPanel((prev) =>
prev ? { ...prev, embed: { ...prev.embed, url: e } } : prev,
)
}
maxLength={EMBED_LIMITS.URL}
/>
</div>
<div className="py-2">
<Textarea
label="Description"
Expand Down Expand Up @@ -516,6 +531,16 @@ const MultiPanelsPage: FC = () => {
}
/>
</Collapsible>
<Collapsible title="" subtitle="Embed Fields" defaultOpen={false}>
<EmbedFieldsEditor
fields={multiPanel.embed?.fields || []}
onChange={(fields) =>
setMultiPanel((prev) =>
prev ? { ...prev, embed: { ...prev.embed, fields } } : prev,
)
}
/>
</Collapsible>
<EmbedCharacterTotal embed={multiPanel.embed} />
</div>

Expand Down
25 changes: 25 additions & 0 deletions frontend/src/pages/manage/multipanels/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ import { PANEL_MESSAGE_INFO } from "@/constants/panelChannelInfo";
import MultiPanelInfoModal from "@/components/modals/MultiPanelInfoModal";
import { EMBED_LIMITS } from "@/constants/embedLimits";
import EmbedCharacterTotal from "@/components/EmbedCharacterTotal";
import EmbedFieldsEditor from "@/components/EmbedFieldsEditor";
import { useApiErrorHandler } from "@/hooks/useApiErrorHandler";

const defaultEmbed = {
author: {},
colour: 0x5865f2,
fields: [],
footer: {},
};

Expand Down Expand Up @@ -337,6 +339,19 @@ const MultiPanelsPage: FC = () => {
}
/>
</div>
<div className="py-2">
<TextInput
label="Title URL"
placeholder="e.g. https://example.com"
value={multiPanel?.embed?.url || ""}
onChange={(e) =>
setMultiPanel((prev) =>
prev ? { ...prev, embed: { ...prev.embed, url: e } } : prev,
)
}
maxLength={EMBED_LIMITS.URL}
/>
</div>
<div className="py-2">
<Textarea
label="Description"
Expand Down Expand Up @@ -502,6 +517,16 @@ const MultiPanelsPage: FC = () => {
}
/>
</Collapsible>
<Collapsible title="" subtitle="Embed Fields" defaultOpen={false}>
<EmbedFieldsEditor
fields={multiPanel?.embed?.fields || []}
onChange={(fields) =>
setMultiPanel((prev) =>
prev ? { ...prev, embed: { ...prev.embed, fields } } : prev,
)
}
/>
</Collapsible>
<EmbedCharacterTotal embed={multiPanel?.embed} />
</div>

Expand Down
5 changes: 5 additions & 0 deletions frontend/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,11 @@ export interface MultiPanelEmbed {
};
colour: number;
description?: string;
fields?: Array<{
name: string;
value: string;
inline?: boolean;
}>;
footer: {
text?: string;
icon_url?: string;
Expand Down
Loading