diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 128652f..33dab8b 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -1583,7 +1583,8 @@ class Channel extends SnowFlake { curWatch = () => {}; async submitCommand() { if (!this.curCommand) return; - if (await this.curCommand.submit(this)) { + const typebox = document.getElementById("typebox") as CustomHTMLDivElement; + if (await this.curCommand.submit(typebox, this)) { this.curCommand = undefined; const typebox = document.getElementById("typebox") as CustomHTMLDivElement; typebox.markdown.boxEnabled = true; diff --git a/src/webpage/i18n.ts b/src/webpage/i18n.ts index 85253e2..6aae3a6 100644 --- a/src/webpage/i18n.ts +++ b/src/webpage/i18n.ts @@ -67,7 +67,7 @@ class I18n { //thanks to geotale for the regex msg = msg.replace(/\$\d+/g, (match) => { const number = Number(match.slice(1)); - if (params[number - 1]) { + if (params[number - 1] !== undefined) { return params[number - 1]; } else { return match; diff --git a/src/webpage/interactions/commands.ts b/src/webpage/interactions/commands.ts index d2628e8..ddfe5ec 100644 --- a/src/webpage/interactions/commands.ts +++ b/src/webpage/interactions/commands.ts @@ -4,6 +4,7 @@ import {I18n} from "../i18n.js"; import {commandJson, commandOptionJson} from "../jsontypes.js"; import {Localuser} from "../localuser.js"; import {SnowFlake} from "../snowflake.js"; +import {removeAni} from "../utils/utils.js"; function focusInput(html: HTMLElement) { const input = html.getElementsByTagName("input")[0]; if (input) input.focus(); @@ -250,38 +251,50 @@ export class Command extends SnowFlake { return this.owner.headers; } - async submit(channel: Channel) { - const nonce = Math.floor(Math.random() * 10 ** 9) + ""; - const states = this.state.get(channel); - if (!states) { - return true; - } - const opts = states.filter((_) => typeof _ !== "string"); + async submit(html: HTMLElement, channel: Channel) { + try { + const nonce = Math.floor(Math.random() * 10 ** 9) + ""; + const states = this.state.get(channel); + if (!states) { + return true; + } + const opts = states.filter((_) => typeof _ !== "string"); - await fetch(this.info.api + "/interactions", { - method: "POST", - headers: this.headers, - body: JSON.stringify({ - type: 2, - nonce: nonce, - guild_id: channel.owner.id, - channel_id: channel.id, - application_id: this.applicationId, - session_id: this.localuser.session_id, - data: { - application_command: this.rawJson, - attachments: [], - id: this.id, - name: this.name, - options: opts.map(({option, state}) => { - return option.toJson(state); - }), - type: 1, - version: this.version, - }, - }), - }); - this.state.delete(channel); + await fetch(this.info.api + "/interactions", { + method: "POST", + headers: this.headers, + body: JSON.stringify({ + type: 2, + nonce: nonce, + guild_id: channel.owner.id, + channel_id: channel.id, + application_id: this.applicationId, + session_id: this.localuser.session_id, + data: { + application_command: this.rawJson, + attachments: [], + id: this.id, + name: this.name, + options: opts.map(({option, state}) => { + return option.toJson(state); + }), + type: 1, + version: this.version, + }, + }), + }); + this.state.delete(channel); + } catch (e) { + if (e instanceof OptionError) { + const message = e.message; + const error = document.createElement("span"); + error.classList.add("commandError"); + error.textContent = message; + html.parentElement?.append(error); + removeAni(error, 25000); + } + return false; + } return true; } } @@ -364,6 +377,11 @@ class ErrorOption extends Option { return span; } } +class OptionError extends Error { + constructor(reason: string) { + super(reason); + } +} class StringOption extends Option { minLeng: number; maxLeng: number; @@ -471,7 +489,7 @@ class StringOption extends Option { if (choice) { return choice.value; } - throw new Error(I18n.commands.errorNotValid(state, this.localizedName)); + throw new OptionError(I18n.commands.errorNotValid(state || '""', this.localizedName)); } return state; } diff --git a/src/webpage/style.css b/src/webpage/style.css index 0d6f244..c9e9bd1 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1142,6 +1142,31 @@ textarea { display: none; } } +.commandError { + position: absolute; + top: -36px; + left: 34px; + z-index: 1; + background: var(--red); + padding: 6px; + border-radius: 4px; + &.removeElm { + animation-duration: 10s; + animation-name: errorElm; + animation-fill-mode: forwards; + animation-timing-function: ease-in-out; + } +} +@keyframes errorElm { + 100%, + 0% { + opacity: 0; + } + 5%, + 95% { + opacity: 1; + } +} /* Animations */ @keyframes fade { 0%, @@ -1978,6 +2003,7 @@ span.instanceStatus { border-radius: 4px; } #typebox { + position: relative; margin: -10px 0px; flex-grow: 1; width: 1px; @@ -2003,6 +2029,7 @@ span.instanceStatus { border-radius: 4px; display: flex; flex-direction: row; + position: relative; } .searchBox:empty { @@ -4349,6 +4376,7 @@ img.error::after { } } } + #sentdms .groupDmDiv { background: transparent; width: 48px; diff --git a/src/webpage/utils/utils.ts b/src/webpage/utils/utils.ts index d8d4efe..830a8cd 100644 --- a/src/webpage/utils/utils.ts +++ b/src/webpage/utils/utils.ts @@ -665,12 +665,12 @@ async function isAnimated(src: string) { return src.endsWith(".apng") || src.endsWith(".gif"); } const staticImgMap = new Map>(); -export async function removeAni(elm: HTMLElement) { +export async function removeAni(elm: HTMLElement, time = 500) { elm.classList.add("removeElm"); const ani = elm.getAnimations(); await Promise.race([ Promise.all(ani.map((_) => _.finished)), - new Promise((res) => setTimeout(res, 500)), + new Promise((res) => setTimeout(res, time)), ]); elm.remove(); }