Compare commits

..

1 Commits

Author SHA1 Message Date
Sevi 080c7978c9
Merge 86415bcf2b into 4b049c4b7c 2025-11-01 09:49:18 +00:00
4 changed files with 60 additions and 82 deletions

View File

@ -285,9 +285,7 @@ var serialConfig = SerialSettings{
EnableEcho: false, EnableEcho: false,
NormalizeMode: "names", NormalizeMode: "names",
NormalizeLineEnd: "keep", NormalizeLineEnd: "keep",
TabRender: "",
PreserveANSI: true, PreserveANSI: true,
ShowNLTag: false,
Buttons: []QuickButton{}, Buttons: []QuickButton{},
} }

View File

@ -718,26 +718,10 @@
"saving": "Saving…", "saving": "Saving…",
"search_placeholder": "Search…", "search_placeholder": "Search…",
"serial_console": "Serial Console", "serial_console": "Serial Console",
"serial_console_add_button": "Add Button",
"serial_console_baud_rate": "Baud Rate", "serial_console_baud_rate": "Baud Rate",
"serial_console_button_editor_command": "Command",
"serial_console_button_editor_command_placeholder": "Command to send",
"serial_console_button_editor_delete": "Delete",
"serial_console_button_editor_explanation": "When sent, the selected line ending {terminator} will be appended.",
"serial_console_button_editor_label": "Label",
"serial_console_button_editor_label_placeholder": "New Command",
"serial_console_button_editor_move_down": "Move Down",
"serial_console_button_editor_move_up": "Move Up",
"serial_console_configure_description": "Configure your serial console settings", "serial_console_configure_description": "Configure your serial console settings",
"serial_console_crlf_handling": "CRLF Handling",
"serial_console_data_bits": "Data Bits", "serial_console_data_bits": "Data Bits",
"serial_console_get_settings_error": "Failed to get serial console settings: {error}", "serial_console_get_settings_error": "Failed to get serial console settings: {error}",
"serial_console_hide_settings": "Hide Settings",
"serial_console_line_ending": "Line Ending",
"serial_console_line_ending_explanation": "Character(s) sent at the end of each command",
"serial_console_local_echo": "Local Echo",
"serial_console_local_echo_description": "Show characters you type in the console",
"serial_console_normalization_mode": "Normalization Mode",
"serial_console_open_console": "Open Console", "serial_console_open_console": "Open Console",
"serial_console_parity": "Parity", "serial_console_parity": "Parity",
"serial_console_parity_even": "Even Parity", "serial_console_parity_even": "Even Parity",
@ -745,18 +729,8 @@
"serial_console_parity_none": "No Parity", "serial_console_parity_none": "No Parity",
"serial_console_parity_odd": "Odd Parity", "serial_console_parity_odd": "Odd Parity",
"serial_console_parity_space": "Space Parity", "serial_console_parity_space": "Space Parity",
"serial_console_preserve_ansi": "Preserve ANSI",
"serial_console_preserve_ansi_keep": "Keep escape code",
"serial_console_preserve_ansi_strip": "Strip escape code",
"serial_console_send_custom_command": "Failed to send custom command: {command}: {error}",
"serial_console_set_settings_error": "Failed to set serial console settings to {settings}: {error}", "serial_console_set_settings_error": "Failed to set serial console settings to {settings}: {error}",
"serial_console_show_newline_tag": "Show newline tag",
"serial_console_show_newline_tag_hide": "Hide <LF> tag",
"serial_console_show_newline_tag_show": "Show <LF> tag",
"serial_console_show_settings": "Show Settings",
"serial_console_stop_bits": "Stop Bits", "serial_console_stop_bits": "Stop Bits",
"serial_console_tab_replacement": "Tab replacement",
"serial_console_tab_replacement_description": "Empty for no replacement",
"setting_remote_description": "Setting remote description", "setting_remote_description": "Setting remote description",
"setting_remote_session_description": "Setting remote session description...", "setting_remote_session_description": "Setting remote session description...",
"setting_up_connection_to_device": "Setting up connection to device...", "setting_up_connection_to_device": "Setting up connection to device...",

View File

@ -8,13 +8,16 @@ import { WebglAddon } from "@xterm/addon-webgl";
import { Unicode11Addon } from "@xterm/addon-unicode11"; import { Unicode11Addon } from "@xterm/addon-unicode11";
import { ClipboardAddon } from "@xterm/addon-clipboard"; import { ClipboardAddon } from "@xterm/addon-clipboard";
import { m } from "@localizations/messages.js";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
import { AvailableTerminalTypes, useUiStore, useTerminalStore } from "@/hooks/stores"; import { AvailableTerminalTypes, useUiStore, useTerminalStore } from "@/hooks/stores";
import { CommandInput } from "@/components/CommandInput"; import { CommandInput } from "@/components/CommandInput";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc"; import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import notifications from "@/notifications"; import notifications from "@/notifications";
import { Button } from "./Button";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { m } from "@localizations/messages.js";
const isWebGl2Supported = !!document.createElement("canvas").getContext("webgl2"); const isWebGl2Supported = !!document.createElement("canvas").getContext("webgl2");

View File

@ -1,9 +1,12 @@
import { LuPlus, LuTrash2, LuPencil, LuSettings2, LuEye, LuEyeOff, LuSave, LuArrowBigUp, LuArrowBigDown, LuCircleX, LuTerminal } from "react-icons/lu"; import { LuPlus, LuTrash2, LuPencil, LuSettings2, LuEye, LuEyeOff, LuSave, LuArrowBigUp, LuArrowBigDown, LuCircleX, LuTerminal } from "react-icons/lu";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { m } from "@localizations/messages.js";
import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc"; import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc";
import { useUiStore } from "@hooks/stores";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import Card from "@components/Card"; import Card from "@components/Card";
import { SelectMenuBasic } from "@components/SelectMenuBasic";
import { SettingsPageHeader } from "@components/SettingsPageheader"; import { SettingsPageHeader } from "@components/SettingsPageheader";
import notifications from "@/notifications"; import notifications from "@/notifications";
import { SelectMenuBasic } from "@components/SelectMenuBasic"; import { SelectMenuBasic } from "@components/SelectMenuBasic";
@ -11,7 +14,7 @@ import { InputFieldWithLabel } from "@components/InputField";
import { useUiStore, useTerminalStore } from "@/hooks/stores"; import { useUiStore, useTerminalStore } from "@/hooks/stores";
import Checkbox from "@components/Checkbox"; import Checkbox from "@components/Checkbox";
import {SettingsItem} from "@components/SettingsItem"; import {SettingsItem} from "@components/SettingsItem";
import { m } from "@localizations/messages.js";
/** ============== Types ============== */ /** ============== Types ============== */
@ -48,7 +51,7 @@ export function SerialConsole() {
const { send } = useJsonRpc(); const { send } = useJsonRpc();
// extension config (buttons + prefs) // extension config (buttons + prefs)
const [settings, setSettings] = useState<SerialSettings>({ const [buttonConfig, setButtonConfig] = useState<SerialSettings>({
baudRate: 9600, baudRate: 9600,
dataBits: 8, dataBits: 8,
stopBits: "1", stopBits: "1",
@ -86,21 +89,21 @@ export function SerialConsole() {
return; return;
} }
setSettings(resp.result as SerialSettings); setButtonConfig(resp.result as SerialSettings);
setTerminator((resp.result as SerialSettings).terminator.value); setTerminator((resp.result as SerialSettings).terminator.value);
}); });
}, [send, setTerminator]); }, [send, setTerminator]);
const handleSerialSettingsChange = (config: keyof SerialSettings, value: unknown) => { const handleSerialSettingsChange = (config: keyof SerialSettings, value: unknown) => {
const newSettings = { ...settings, [config]: value }; const newButtonConfig = { ...buttonConfig, [config]: value };
send("setSerialSettings", { settings: newSettings }, (resp: JsonRpcResponse) => { send("setSerialSettings", { settings: newButtonConfig }, (resp: JsonRpcResponse) => {
if ("error" in resp) { if ("error" in resp) {
notifications.error(m.serial_console_set_settings_error({ settings: newSettings, error: resp.error.data || m.unknown_error() })); notifications.error(`Failed to update serial settings: ${resp.error.data || "Unknown error"}`);
return; return;
} }
}); });
setSettings(newSettings); setButtonConfig(newButtonConfig);
}; };
const onClickButton = (btn: QuickButton) => { const onClickButton = (btn: QuickButton) => {
@ -109,7 +112,7 @@ export function SerialConsole() {
send("sendCustomCommand", { command }, (resp: JsonRpcResponse) => { send("sendCustomCommand", { command }, (resp: JsonRpcResponse) => {
if ("error" in resp) { if ("error" in resp) {
notifications.error(m.serial_console_send_custom_command({ command: command, error: resp.error.data || m.unknown_error() })); notifications.error(m.serial_console_set_settings_error({ settings: setting, error: resp.error.data || m.unknown_error() }));
return; return;
} }
}); });
@ -131,14 +134,14 @@ export function SerialConsole() {
}; };
const removeBtn = (id: string) => { const removeBtn = (id: string) => {
const nextButtons = settings.buttons.filter(b => b.id !== id).map((b, i) => ({ ...b, sort: i })) ; const nextButtons = buttonConfig.buttons.filter(b => b.id !== id).map((b, i) => ({ ...b, sort: i })) ;
handleSerialSettingsChange("buttons", stableSort(nextButtons) ); handleSerialSettingsChange("buttons", stableSort(nextButtons) );
setEditorOpen(null); setEditorOpen(null);
}; };
const moveUpBtn = (id: string) => { const moveUpBtn = (id: string) => {
// Make a copy so we don't mutate state directly // Make a copy so we don't mutate state directly
const newButtons = [...settings.buttons]; const newButtons = [...buttonConfig.buttons];
// Find the index of the button to move // Find the index of the button to move
const index = newButtons.findIndex(b => b.id === id); const index = newButtons.findIndex(b => b.id === id);
@ -159,7 +162,7 @@ export function SerialConsole() {
const moveDownBtn = (id: string) => { const moveDownBtn = (id: string) => {
// Make a copy so we don't mutate state directly // Make a copy so we don't mutate state directly
const newButtons = [...settings.buttons]; const newButtons = [...buttonConfig.buttons];
// Find the index of the button to move // Find the index of the button to move
const index = newButtons.findIndex(b => b.id === id); const index = newButtons.findIndex(b => b.id === id);
@ -196,15 +199,15 @@ export function SerialConsole() {
// if new, assign next sort index // if new, assign next sort index
// if existing, keep sort index // if existing, keep sort index
const nextButtons = currentID const nextButtons = currentID
? settings.buttons.map(b => (b.id === currentID ? { ...b, label, command , terminator} : b)) ? buttonConfig.buttons.map(b => (b.id === currentID ? { ...b, label, command , terminator} : b))
: [...settings.buttons, { id: genId(), label, command, terminator, sort: settings.buttons.length }]; : [...buttonConfig.buttons, { id: genId(), label, command, terminator, sort: buttonConfig.buttons.length }];
handleSerialSettingsChange("buttons", stableSort(nextButtons) ); handleSerialSettingsChange("buttons", stableSort(nextButtons) );
setEditorOpen(null); setEditorOpen(null);
}; };
/** simple reordering: alphabetical by sort, then label */ /** simple reordering: alphabetical by sort, then label */
const sortedButtons = useMemo(() => settings.buttons, [settings.buttons]); const sortedButtons = useMemo(() => buttonConfig.buttons, [buttonConfig.buttons]);
return ( return (
<div className="space-y-4"> <div className="space-y-4">
@ -220,15 +223,15 @@ export function SerialConsole() {
<Button <Button
size="XS" size="XS"
theme="primary" theme="primary"
LeadingIcon={settings.hideSerialSettings ? LuEye : LuEyeOff} LeadingIcon={buttonConfig.hideSerialSettings ? LuEye : LuEyeOff}
text={settings.hideSerialSettings ? m.serial_console_show_settings() : m.serial_console_hide_settings()} text={buttonConfig.hideSerialSettings ? "Show Settings" : "Hide Settings"}
onClick={() => handleSerialSettingsChange("hideSerialSettings", !settings.hideSerialSettings )} onClick={() => handleSerialSettingsChange("hideSerialSettings", !buttonConfig.hideSerialSettings )}
/> />
<Button <Button
size="XS" size="XS"
theme="primary" theme="primary"
LeadingIcon={LuPlus} LeadingIcon={LuPlus}
text={m.serial_console_add_button()} text="Add Button"
onClick={addNew} onClick={addNew}
/> />
<Button <Button
@ -245,7 +248,7 @@ export function SerialConsole() {
<hr className="border-slate-700/30 dark:border-slate-600/30" /> <hr className="border-slate-700/30 dark:border-slate-600/30" />
{/* Serial settings (collapsible) */} {/* Serial settings (collapsible) */}
{!settings.hideSerialSettings && ( {!buttonConfig.hideSerialSettings && (
<> <>
<div className="grid grid-cols-2 gap-4 mb-1"> <div className="grid grid-cols-2 gap-4 mb-1">
<SelectMenuBasic <SelectMenuBasic
@ -260,7 +263,7 @@ export function SerialConsole() {
{ label: "57600", value: "57600" }, { label: "57600", value: "57600" },
{ label: "115200", value: "115200" }, { label: "115200", value: "115200" },
]} ]}
value={settings.baudRate} value={buttonConfig.baudRate}
onChange={(e) => handleSerialSettingsChange("baudRate", Number(e.target.value))} onChange={(e) => handleSerialSettingsChange("baudRate", Number(e.target.value))}
/> />
@ -270,7 +273,7 @@ export function SerialConsole() {
{ label: "8", value: "8" }, { label: "8", value: "8" },
{ label: "7", value: "7" }, { label: "7", value: "7" },
]} ]}
value={settings.dataBits} value={buttonConfig.dataBits}
onChange={(e) => handleSerialSettingsChange("dataBits", Number(e.target.value))} onChange={(e) => handleSerialSettingsChange("dataBits", Number(e.target.value))}
/> />
@ -281,7 +284,7 @@ export function SerialConsole() {
{ label: "1.5", value: "1.5" }, { label: "1.5", value: "1.5" },
{ label: "2", value: "2" }, { label: "2", value: "2" },
]} ]}
value={settings.stopBits} value={buttonConfig.stopBits}
onChange={(e) => handleSerialSettingsChange("stopBits", e.target.value)} onChange={(e) => handleSerialSettingsChange("stopBits", e.target.value)}
/> />
@ -294,13 +297,13 @@ export function SerialConsole() {
{ label: m.serial_console_parity_mark(), value: "mark" }, { label: m.serial_console_parity_mark(), value: "mark" },
{ label: m.serial_console_parity_space(), value: "space" }, { label: m.serial_console_parity_space(), value: "space" },
]} ]}
value={settings.parity} value={buttonConfig.parity}
onChange={(e) => handleSerialSettingsChange("parity", e.target.value)} onChange={(e) => handleSerialSettingsChange("parity", e.target.value)}
/> />
<div> <div>
<SelectMenuBasic <SelectMenuBasic
className="mb-1" className="mb-1"
label={m.serial_console_line_ending()} label="Line ending"
options={[ options={[
{ label: "None", value: "" }, { label: "None", value: "" },
{ label: "CR (\\r)", value: "\r" }, { label: "CR (\\r)", value: "\r" },
@ -308,38 +311,38 @@ export function SerialConsole() {
{ label: "CRLF (\\r\\n)", value: "\r\n" }, { label: "CRLF (\\r\\n)", value: "\r\n" },
{ label: "LFCR (\\n\\r)", value: "\n\r" }, { label: "LFCR (\\n\\r)", value: "\n\r" },
]} ]}
value={settings.terminator.value} value={buttonConfig.terminator.value}
onChange={(e) => { onChange={(e) => {
handleSerialSettingsChange("terminator", {label: e.target.selectedOptions[0].text, value: e.target.value}) handleSerialSettingsChange("terminator", {label: e.target.selectedOptions[0].text, value: e.target.value})
setTerminator(e.target.value); setTerminator(e.target.value);
}} }}
/> />
<div className="text-xs text-white opacity-70 mt-0 ml-2"> <div className="text-xs text-white opacity-70 mt-0 ml-2">
{m.serial_console_line_ending_explanation({terminator: settings.terminator.label})} When sent, the selected line ending ({buttonConfig.terminator.label}) will be appended.
</div> </div>
</div> </div>
<div> <div>
<SelectMenuBasic <SelectMenuBasic
className="mb-1" className="mb-1"
label={m.serial_console_normalization_mode()} label="Normalization Mode"
options={[ options={[
{ label: "Caret", value: "caret" }, { label: "Caret", value: "caret" },
{ label: "Names", value: "names" }, { label: "Names", value: "names" },
{ label: "Hex", value: "hex" }, { label: "Hex", value: "hex" },
]} ]}
value={settings.normalizeMode} value={buttonConfig.normalizeMode}
onChange={(e) => { onChange={(e) => {
handleSerialSettingsChange("normalizeMode", e.target.value) handleSerialSettingsChange("normalizeMode", e.target.value)
}} }}
/> />
<div className="text-xs text-white opacity-70 mt-0 ml-2"> <div className="text-xs text-white opacity-70 mt-0 ml-2">
{normalizeHelp[(settings.normalizeMode as NormalizeMode)]} {normalizeHelp[(buttonConfig.normalizeMode as NormalizeMode)]}
</div> </div>
</div> </div>
<div> <div>
<SelectMenuBasic <SelectMenuBasic
className="mb-1" className="mb-1"
label={m.serial_console_crlf_handling()} label="CRLF Handling"
options={[ options={[
{ label: "Keep", value: "keep" }, { label: "Keep", value: "keep" },
{ label: "LF", value: "lf" }, { label: "LF", value: "lf" },
@ -347,7 +350,7 @@ export function SerialConsole() {
{ label: "CRLF", value: "crlf" }, { label: "CRLF", value: "crlf" },
{ label: "LFCR", value: "lfcr" }, { label: "LFCR", value: "lfcr" },
]} ]}
value={settings.normalizeLineEnd} value={buttonConfig.normalizeLineEnd}
onChange={(e) => { onChange={(e) => {
handleSerialSettingsChange("normalizeLineEnd", e.target.value) handleSerialSettingsChange("normalizeLineEnd", e.target.value)
}} }}
@ -356,12 +359,12 @@ export function SerialConsole() {
<div> <div>
<SelectMenuBasic <SelectMenuBasic
className="mb-1" className="mb-1"
label={m.serial_console_preserve_ansi()} label="Preserve ANSI"
options={[ options={[
{ label: m.serial_console_preserve_ansi_strip(), value: "strip" }, { label: "Strip escape code", value: "strip" },
{ label: m.serial_console_preserve_ansi_keep(), value: "keep" }, { label: "Keep escape code", value: "keep" },
]} ]}
value={settings.preserveANSI ? "keep" : "strip"} value={buttonConfig.preserveANSI ? "keep" : "strip"}
onChange={(e) => { onChange={(e) => {
handleSerialSettingsChange("preserveANSI", e.target.value === "keep") handleSerialSettingsChange("preserveANSI", e.target.value === "keep")
}} }}
@ -372,10 +375,10 @@ export function SerialConsole() {
className="mb-1" className="mb-1"
label="Show newline tag" label="Show newline tag"
options={[ options={[
{ label: m.serial_console_show_newline_tag_hide(), value: "hide" }, { label: "Hide <LF> tag", value: "hide" },
{ label: m.serial_console_show_newline_tag_show(), value: "show" }, { label: "Show <LF> tag", value: "show" },
]} ]}
value={settings.showNLTag ? "show" : "hide"} value={buttonConfig.showNLTag ? "show" : "hide"}
onChange={(e) => { onChange={(e) => {
handleSerialSettingsChange("showNLTag", e.target.value === "show") handleSerialSettingsChange("showNLTag", e.target.value === "show")
}} }}
@ -385,25 +388,25 @@ export function SerialConsole() {
<InputFieldWithLabel <InputFieldWithLabel
size="MD" size="MD"
type="text" type="text"
label={m.serial_console_tab_replacement()} label="Tab replacement"
placeholder="ex. spaces, →, |" placeholder="ex. spaces, →, |"
value={settings.tabRender} value={buttonConfig.tabRender}
onChange={e => { onChange={e => {
handleSerialSettingsChange("tabRender", e.target.value) handleSerialSettingsChange("tabRender", e.target.value)
}} }}
/> />
<div className="text-xs text-white opacity-70 mt-1"> <div className="text-xs text-white opacity-70 mt-1">
{m.serial_console_tab_replacement_description()} Empty for no replacement
</div> </div>
</div> </div>
</div> </div>
<div className="space-y-4 m-2"> <div className="space-y-4 m-2">
<SettingsItem <SettingsItem
title={m.serial_console_local_echo()} title="Local Echo"
description={m.serial_console_local_echo_description()} description="Whether to echo received characters back to the sender"
> >
<Checkbox <Checkbox
checked={settings.enableEcho} checked={buttonConfig.enableEcho}
onChange={e => { onChange={e => {
handleSerialSettingsChange("enableEcho", e.target.checked); handleSerialSettingsChange("enableEcho", e.target.checked);
}} }}
@ -455,8 +458,8 @@ export function SerialConsole() {
<InputFieldWithLabel <InputFieldWithLabel
size="SM" size="SM"
type="text" type="text"
label={m.serial_console_button_editor_label()} label="Label"
placeholder={m.serial_console_button_editor_label_placeholder()} placeholder="New Command"
value={draftLabel} value={draftLabel}
onChange={e => { onChange={e => {
setDraftLabel(e.target.value); setDraftLabel(e.target.value);
@ -467,8 +470,8 @@ export function SerialConsole() {
<InputFieldWithLabel <InputFieldWithLabel
size="SM" size="SM"
type="text" type="text"
label={m.serial_console_button_editor_command()} label="Command"
placeholder={m.serial_console_button_editor_command_placeholder()} placeholder="Command to send"
value={draftCmd} value={draftCmd}
onChange={e => { onChange={e => {
setDraftCmd(e.target.value); setDraftCmd(e.target.value);
@ -476,14 +479,14 @@ export function SerialConsole() {
/> />
{draftTerminator.value != "" && ( {draftTerminator.value != "" && (
<div className="text-xs text-white opacity-70 mt-1"> <div className="text-xs text-white opacity-70 mt-1">
{m.serial_console_button_editor_explanation({terminator: draftTerminator.label})} When sent, the selected line ending ({draftTerminator.label}) will be appended.
</div> </div>
)} )}
</div> </div>
</div> </div>
<div className="flex justify-around items-end"> <div className="flex justify-around items-end">
<SelectMenuBasic <SelectMenuBasic
label={m.serial_console_line_ending()} label="Line ending"
options={[ options={[
{ label: "None", value: "" }, { label: "None", value: "" },
{ label: "CR (\\r)", value: "\r" }, { label: "CR (\\r)", value: "\r" },
@ -508,7 +511,7 @@ export function SerialConsole() {
size="SM" size="SM"
theme="danger" theme="danger"
LeadingIcon={LuTrash2} LeadingIcon={LuTrash2}
text={m.serial_console_button_editor_delete()} text="Delete"
onClick={() => removeBtn(editorOpen.id!)} onClick={() => removeBtn(editorOpen.id!)}
aria-label={`Delete ${draftLabel}`} aria-label={`Delete ${draftLabel}`}
/> />
@ -516,7 +519,7 @@ export function SerialConsole() {
size="SM" size="SM"
theme="primary" theme="primary"
LeadingIcon={LuArrowBigUp} LeadingIcon={LuArrowBigUp}
text={m.serial_console_button_editor_move_up()} text="Move Up"
aria-label={`Move ${draftLabel} up`} aria-label={`Move ${draftLabel} up`}
disabled={sortedButtons.findIndex(b => b.id === editorOpen.id) === 0} disabled={sortedButtons.findIndex(b => b.id === editorOpen.id) === 0}
onClick={() => moveUpBtn(editorOpen.id!)} onClick={() => moveUpBtn(editorOpen.id!)}
@ -525,7 +528,7 @@ export function SerialConsole() {
size="SM" size="SM"
theme="primary" theme="primary"
LeadingIcon={LuArrowBigDown} LeadingIcon={LuArrowBigDown}
text={m.serial_console_button_editor_move_down()} text="Move Down"
aria-label={`Move ${draftLabel} down`} aria-label={`Move ${draftLabel} down`}
disabled={sortedButtons.findIndex(b => b.id === editorOpen.id)+1 === sortedButtons.length} disabled={sortedButtons.findIndex(b => b.id === editorOpen.id)+1 === sortedButtons.length}
onClick={() => moveDownBtn(editorOpen.id!)} onClick={() => moveDownBtn(editorOpen.id!)}