forked from syntaxbullet/AuroraBot-discord
feat(settings): group commands by category in system tab
This commit is contained in:
@@ -8,7 +8,7 @@ import { EventLoader } from "@lib/loaders/EventLoader";
|
|||||||
export class Client extends DiscordClient {
|
export class Client extends DiscordClient {
|
||||||
|
|
||||||
commands: Collection<string, Command>;
|
commands: Collection<string, Command>;
|
||||||
knownCommands: Set<string>;
|
knownCommands: Map<string, string>;
|
||||||
lastCommandTimestamp: number | null = null;
|
lastCommandTimestamp: number | null = null;
|
||||||
maintenanceMode: boolean = false;
|
maintenanceMode: boolean = false;
|
||||||
private commandLoader: CommandLoader;
|
private commandLoader: CommandLoader;
|
||||||
@@ -17,7 +17,7 @@ export class Client extends DiscordClient {
|
|||||||
constructor({ intents }: { intents: number[] }) {
|
constructor({ intents }: { intents: number[] }) {
|
||||||
super({ intents });
|
super({ intents });
|
||||||
this.commands = new Collection<string, Command>();
|
this.commands = new Collection<string, Command>();
|
||||||
this.knownCommands = new Set<string>();
|
this.knownCommands = new Map<string, string>();
|
||||||
this.commandLoader = new CommandLoader(this);
|
this.commandLoader = new CommandLoader(this);
|
||||||
this.eventLoader = new EventLoader(this);
|
this.eventLoader = new EventLoader(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export class CommandLoader {
|
|||||||
command.category = category;
|
command.category = category;
|
||||||
|
|
||||||
// Track all known commands regardless of enabled status
|
// Track all known commands regardless of enabled status
|
||||||
this.client.knownCommands.add(command.data.name);
|
this.client.knownCommands.set(command.data.name, category);
|
||||||
|
|
||||||
const isEnabled = config.commands[command.data.name] !== false;
|
const isEnabled = config.commands[command.data.name] !== false;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ interface ChannelOption { id: string; name: string; type: number; }
|
|||||||
interface SettingsMeta {
|
interface SettingsMeta {
|
||||||
roles: RoleOption[];
|
roles: RoleOption[];
|
||||||
channels: ChannelOption[];
|
channels: ChannelOption[];
|
||||||
commands: string[];
|
commands: { name: string; category: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
import { type GameConfigType } from "@shared/lib/config";
|
import { type GameConfigType } from "@shared/lib/config";
|
||||||
@@ -400,29 +400,43 @@ export function Settings() {
|
|||||||
{activeTab === "system" && (
|
{activeTab === "system" && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<SectionTitle icon={Terminal} title="Commands" description="Enable or disable specific bot commands" />
|
<SectionTitle icon={Terminal} title="Commands" description="Enable or disable specific bot commands" />
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
||||||
{meta.commands.map(cmd => (
|
{meta.commands.length === 0 ? (
|
||||||
<div key={cmd} className="flex items-center justify-between p-3 rounded-lg bg-white/5 border border-white/5">
|
<div className="text-center py-8 text-white/30">
|
||||||
<div className="flex flex-col">
|
No commands found in metadata.
|
||||||
<span className="text-sm font-medium">/{cmd}</span>
|
</div>
|
||||||
<span className="text-[10px] text-white/40">
|
) : (
|
||||||
{config?.commands?.[cmd] === false ? "Disabled" : "Enabled"}
|
Object.entries(
|
||||||
</span>
|
meta.commands.reduce((acc, cmd) => {
|
||||||
|
const cat = cmd.category || 'Uncategorized';
|
||||||
|
if (!acc[cat]) acc[cat] = [];
|
||||||
|
acc[cat].push(cmd);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, typeof meta.commands>)
|
||||||
|
).sort(([a], [b]) => a.localeCompare(b)).map(([category, commands]) => (
|
||||||
|
<div key={category} className="mb-6 last:mb-0">
|
||||||
|
<h4 className="text-sm font-semibold text-white/40 uppercase tracking-wider mb-3 px-1">{category}</h4>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{commands.map(cmd => (
|
||||||
|
<div key={cmd.name} className="flex items-center justify-between p-3 rounded-lg bg-white/5 border border-white/5 hover:border-white/10 transition-colors">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm font-medium text-white">/{cmd.name}</span>
|
||||||
|
<span className={`text-[10px] ${config?.commands?.[cmd.name] === false ? "text-red-400" : "text-green-400"}`}>
|
||||||
|
{config?.commands?.[cmd.name] === false ? "Disabled" : "Enabled"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={config?.commands?.[cmd.name] !== false}
|
||||||
|
onChange={(e) => updateConfig(`commands.${cmd.name}`, e.target.checked)}
|
||||||
|
className="h-4 w-4 rounded border-white/10 bg-white/5 accent-primary cursor-pointer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={config?.commands?.[cmd] !== false}
|
|
||||||
onChange={(e) => updateConfig(`commands.${cmd}`, e.target.checked)}
|
|
||||||
className="h-4 w-4 rounded border-white/10 bg-white/5 accent-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))
|
||||||
{meta.commands.length === 0 && (
|
)}
|
||||||
<div className="col-span-full text-center py-8 text-white/30">
|
|
||||||
No commands found in metadata.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -62,7 +62,11 @@ mock.module("../../bot/lib/BotClient", () => ({
|
|||||||
commands: [
|
commands: [
|
||||||
{ data: { name: "ping" } }
|
{ data: { name: "ping" } }
|
||||||
],
|
],
|
||||||
knownCommands: new Set(["ping", "help", "disabled-cmd"])
|
knownCommands: new Map([
|
||||||
|
["ping", "utility"],
|
||||||
|
["help", "utility"],
|
||||||
|
["disabled-cmd", "admin"]
|
||||||
|
])
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -156,6 +160,11 @@ describe("Settings API", () => {
|
|||||||
expect(data.roles).toHaveLength(2);
|
expect(data.roles).toHaveLength(2);
|
||||||
expect(data.roles[0]).toEqual({ id: "role1", name: "Admin", color: "#ffffff" });
|
expect(data.roles[0]).toEqual({ id: "role1", name: "Admin", color: "#ffffff" });
|
||||||
expect(data.channels[0]).toEqual({ id: "chan1", name: "general", type: 0 });
|
expect(data.channels[0]).toEqual({ id: "chan1", name: "general", type: 0 });
|
||||||
expect(data.commands).toContain("ping");
|
|
||||||
|
// Check new commands structure
|
||||||
|
expect(data.commands).toBeArray();
|
||||||
|
expect(data.commands.length).toBeGreaterThan(0);
|
||||||
|
expect(data.commands[0]).toHaveProperty("name");
|
||||||
|
expect(data.commands[0]).toHaveProperty("category");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -226,7 +226,12 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
|||||||
const channels = guild.channels.cache
|
const channels = guild.channels.cache
|
||||||
.map(c => ({ id: c.id, name: c.name, type: c.type }));
|
.map(c => ({ id: c.id, name: c.name, type: c.type }));
|
||||||
|
|
||||||
const commands = Array.from(AuroraClient.knownCommands).sort();
|
const commands = Array.from(AuroraClient.knownCommands.entries())
|
||||||
|
.map(([name, category]) => ({ name, category }))
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.category !== b.category) return a.category.localeCompare(b.category);
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
});
|
||||||
|
|
||||||
return Response.json({ roles, channels, commands });
|
return Response.json({ roles, channels, commands });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user