Skip to content

Commit

Permalink
Option view and add support
Browse files Browse the repository at this point in the history
  • Loading branch information
typicalninja committed Sep 12, 2023
1 parent b9c84ee commit 030e769
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/app.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="dark">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/slash.png" />
Expand Down
211 changes: 192 additions & 19 deletions src/components/commandOptions.svelte
Original file line number Diff line number Diff line change
@@ -1,27 +1,200 @@
<script lang="ts">
import Add from "./icons/Add.svelte";
import { OptionTypes, type AccordionOption, } from '$lib/constants';
import { OptionTypeToName, generateRandomAlphaNumeric } from '$lib/helpers';
import { onMount } from 'svelte';
import Add from './icons/Add.svelte';
import Minus from './icons/Minus.svelte';
import Trash from './icons/Trash.svelte';
import { slide, fly } from 'svelte/transition';
import { browser } from '$app/environment';
interface Option {
type: number
}
export let value = [] as AccordionOption[];
export let options = [] as AccordionOption[];
const addOption = () => {
options = options.concat([
{
type: 1,
open: false,
name: '',
id: 0,
itemId: generateRandomAlphaNumeric(10),
description: '',
autocomplete: false,
required: true,
min_value: null,
max_value: null,
max_length: null,
min_length: null
}
]);
};
let options = [] as Option[];
const removeOption = (itemId: string) => {
options = options.filter((item) => item.itemId !== itemId);
};
const addOption = () => {
options = options.concat([{ type: 1 }])
let preAdd = false;
$: {
// check if values are predefined and add them
if(browser && value.length && !options.length && !preAdd) {
const newItemsToAdd = value.filter(val => !options.find(opt => opt.id === val.id))
if(newItemsToAdd.length) options = options.concat(newItemsToAdd)
preAdd = true;
}
}
</script>

<div class="flex flex-col gap-2">
<div class="bg-primary-800 rounded-lg w-full p-2 h-12 flex items-center justify-between">
<span>Options</span>
<button on:click={addOption} type="button" class="bg-primary-800 h-10 hover:bg-primary-700 px-2">
<Add class="text-green-400" />
</button>
</div>
{#each options as option}
<div class="bg-primary-800 rounded-lg w-full p-2 h-12 flex items-center justify-between">
{option.type}
</div>
{/each}
</div>
<div class="bg-primary-800 rounded-lg w-full p-2 h-12 flex items-center justify-between">
<span>Options</span>
<button
on:click={addOption}
type="button"
class="bg-primary-800 h-10 hover:bg-primary-700 px-2"
>
<Add class="text-green-400" />
</button>
</div>
{#each options as option (option.itemId)}
<div in:fly={{ x: -200 }} out:fly={{ x: 200 }} class="flex flex-col">
<div
class="bg-primary-900 rounded-t-lg {option.open && 'border-b'} border-primary-600 w-full p-2 h-12 flex items-center justify-between"
>
<span>{option.name} {OptionTypeToName(option.type)} option</span>
<div class="flex gap-1">
<button
type="button"
class="bg-primary-800 h-10 hover:bg-primary-700 px-2"
on:click={() => (option.open = !option.open)}
>
{#if option.open}
<Minus class="text-yellow-300" />
{:else}
<Add class="text-blurple-300" />
{/if}
</button>
<button
type="button"
class="bg-primary-800 h-10 hover:bg-primary-700 px-2"
on:click={() => removeOption(option.itemId)}
>
<Trash class="text-red-300" />
</button>
</div>
</div>
{#if option.open}
<div
in:slide={{ axis: 'y' }}
out:slide={{}}
class="bg-primary-800 rounded-b-lg w-full p-2 h-max flex flex-col gap-1"
>
<label for={`option-${option.itemId}-name`}>Name</label>
<input
name={`option-${option.itemId}-name`}
id={`option-${option.itemId}-name`}
bind:value={option.name}
class="bg-primary-500 p-1 rounded focus:ring-0 focus:ring-offset-0 focus:outline-none"
/>

<label for={`option-${option.itemId}-description`}>Description</label>
<textarea
name={`option-${option.itemId}-description`}
id={`option-${option.itemId}-description`}
bind:value={option.description}
class="bg-primary-500 p-1 rounded focus:ring-0 focus:ring-offset-0 focus:outline-none"
/>

<label for={`option-${option.itemId}-type`}>Option type</label>
<select
id={`option-${option.itemId}-type`}
name={`option-${option.itemId}-type`}
bind:value={option.type}
class="bg-primary-500 rounded-md p-2 focus:ring-0 focus:ring-offset-0 focus:outline-none"
>
<option value={OptionTypes.SubCommand}>Sub command</option>
<option value={OptionTypes.SubCommandGroup}>Sub command group</option>
<option value={OptionTypes.String}>String</option>
<option value={OptionTypes.Integer}>Integer</option>
<option value={OptionTypes.Boolean}>Boolean</option>
<option value={OptionTypes.User}>User</option>
<option value={OptionTypes.Channel}>Channel</option>
<option value={OptionTypes.Role}>Role</option>
<option value={OptionTypes.Mentionable}>Mentionable</option>
<option value={OptionTypes.Number}>Number</option>
<option value={OptionTypes.Attachment}>Attachment</option>
</select>

{#if [OptionTypes.Integer, OptionTypes.Number].includes(option.type)}
<label for={`option-${option.itemId}-minValue`}>Min Value</label>
<input
type="number"
name={`option-${option.itemId}-minValue`}
id={`option-${option.itemId}-minValue`}
bind:value={option.min_value}
class="bg-primary-500 p-1 rounded focus:ring-0 focus:ring-offset-0 focus:outline-none"
/>

<label for={`option-${option.itemId}-minValue`}>Max Value</label>
<input
type="number"
name={`option-${option.itemId}-maxValue`}
id={`option-${option.itemId}-maxValue`}
bind:value={option.max_value}
class="bg-primary-500 p-1 rounded focus:ring-0 focus:ring-offset-0 focus:outline-none"
/>
{/if}

{#if option.type === OptionTypes.String}
<label for={`option-${option.itemId}-minLength`}>Min Length</label>
<input
type="number"
name={`option-${option.itemId}-minValue`}
id={`option-${option.itemId}-minValue`}
bind:value={option.min_length}
class="bg-primary-500 p-1 rounded focus:ring-0 focus:ring-offset-0 focus:outline-none"
/>

<label for={`option-${option.itemId}-minValue`}>Max Length</label>
<input
type="number"
name={`option-${option.itemId}-maxValue`}
id={`option-${option.itemId}-maxValue`}
bind:value={option.max_length}
class="bg-primary-500 p-1 rounded focus:ring-0 focus:ring-offset-0 focus:outline-none"
/>
{/if}

<div class="w-full h-1 bg-primary-700 rounded-lg"></div>
<div class="flex gap-3">
<label for="required">
<span>Required</span>
<input
name="required"
id="required"
class="h-5 w-5 bg-primary-500 rounded focus:ring-0 focus:ring-offset-0 focus:outline-none"
type="checkbox"
bind:checked={option.required}
/>
</label>
{#if [OptionTypes.String, OptionTypes.Number, OptionTypes.Integer].includes(option.type)}
<label for="autocomplete" class="ml-1">
<span>Autocompleted</span>
<input
name="autocomplete"
id="autocomplete"
class="h-5 w-5 bg-primary-500 rounded focus:ring-0 focus:ring-offset-0 focus:outline-none"
type="checkbox"
bind:checked={option.autocomplete}
/>
</label>
{/if}
</div>
</div>
{/if}
</div>
{/each}
</div>
10 changes: 10 additions & 0 deletions src/components/icons/ChevronD.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
/**
Original icon by lucide.dev
*/
let className = '';
export { className as class }
</script>

<svg class={className} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
10 changes: 10 additions & 0 deletions src/components/icons/Minus.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
/**
Original icon by lucide.dev
*/
let className = '';
export { className as class }
</script>

<svg class={className} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/></svg>
28 changes: 27 additions & 1 deletion src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,27 @@ export enum Urls {
Github = "https://github.com/typicalninja/discord-slash-manager"
}


// regex for validation
export const discordIdRegex = /^[0-9]{18}$/;
export const chatInputRegex = /^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u

/* All options supported by discord rolled into one */
export interface DiscordInteractionOption {
type: number;
type: OptionTypes;
id: number;
name: string;
description: string;
autocomplete: boolean;
required: boolean;
min_value: number | null;
max_value: number | null;
min_length: number | null;
max_length: number | null;
}

export type AccordionOption = DiscordInteractionOption & { open: boolean; itemId: string };

export interface DiscordInteraction {
id: string;
type: number;
Expand All @@ -23,6 +35,20 @@ export interface DiscordInteraction {
options: DiscordInteractionOption[]
}

export enum OptionTypes {
SubCommand = 1,
SubCommandGroup = 2,
String = 3,
Integer = 4,
Boolean = 5,
User = 6,
Channel = 7,
Role = 8,
Mentionable = 9,
Number = 10,
Attachment = 11,
}

export function typeToName(type: number | string): string {
type = Number(type)
switch (type) {
Expand Down
44 changes: 44 additions & 0 deletions src/lib/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { OptionTypes } from "./constants";

export function generateRandomAlphaNumeric(length: number) {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let result = "";

for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * charset.length);
result += charset.charAt(randomIndex);
}

return result;
}


export function OptionTypeToName(type: number | string) {
type = Number(type);
switch(type) {
case OptionTypes.SubCommand:
return 'Sub command';
case OptionTypes.SubCommandGroup:
return 'Sub command group';
case OptionTypes.String:
return 'String';
case OptionTypes.Integer:
return 'Integer';
case OptionTypes.Boolean:
return 'Boolean';
case OptionTypes.User:
return 'User';
case OptionTypes.Channel:
return 'Channel';
case OptionTypes.Role:
return 'Role';
case OptionTypes.Mentionable:
return 'Mentionable';
case OptionTypes.Number:
return 'Number';
case OptionTypes.Attachment:
return 'Attachment';
default:
return 'Unknown';
}
}
Loading

0 comments on commit 030e769

Please sign in to comment.