cleanup delete buttons

This commit is contained in:
Andrew Davis 2025-04-04 00:41:57 +10:00
parent 5fcc1f4079
commit 7d5cf918fc
No known key found for this signature in database
GPG Key ID: 30AB5B89A109D044
4 changed files with 131 additions and 132 deletions

View File

@ -9,7 +9,6 @@ import Fieldset from "@/components/Fieldset";
import { MacroStepCard } from "@/components/MacroStepCard";
import { DEFAULT_DELAY, MAX_STEPS_PER_MACRO, MAX_KEYS_PER_STEP } from "@/constants/macros";
import FieldLabel from "@/components/FieldLabel";
import { ConfirmDialog } from "@/components/ConfirmDialog";
interface ValidationErrors {
name?: string;
@ -26,11 +25,6 @@ interface MacroFormProps {
onCancel: () => void;
isSubmitting?: boolean;
submitText?: string;
showCancelConfirm?: boolean;
onCancelConfirm?: () => void;
showDelete?: boolean;
onDelete?: () => void;
isDeleting?: boolean;
}
export function MacroForm({
@ -39,17 +33,11 @@ export function MacroForm({
onCancel,
isSubmitting = false,
submitText = "Save Macro",
showCancelConfirm = false,
onCancelConfirm,
showDelete = false,
onDelete,
isDeleting = false
}: MacroFormProps) {
const [macro, setMacro] = useState<Partial<KeySequence>>(initialData);
const [keyQueries, setKeyQueries] = useState<Record<number, string>>({});
const [errors, setErrors] = useState<ValidationErrors>({});
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const showTemporaryError = (message: string) => {
setErrorMessage(message);
@ -261,28 +249,7 @@ export function MacroForm({
</div>
)}
<div className="mt-6 flex items-center justify-between">
{showCancelConfirm ? (
<div className="flex items-center gap-2">
<span className="text-sm text-slate-600 dark:text-slate-400">
Cancel changes?
</span>
<Button
size="SM"
theme="danger"
text="Yes"
onClick={onCancelConfirm}
/>
<Button
size="SM"
theme="light"
text="No"
onClick={() => onCancel()}
/>
</div>
) : (
<>
<div className="flex gap-x-2">
<div className="mt-6 flex items-center gap-x-2">
<Button
size="SM"
theme="primary"
@ -297,34 +264,8 @@ export function MacroForm({
onClick={onCancel}
/>
</div>
{showDelete && (
<Button
size="SM"
theme="danger"
text={isDeleting ? "Deleting..." : "Delete Macro"}
onClick={() => setShowDeleteConfirm(true)}
disabled={isDeleting}
/>
)}
</>
)}
</div>
</div>
</div>
<ConfirmDialog
open={showDeleteConfirm}
onClose={() => setShowDeleteConfirm(false)}
title="Delete Macro"
description="Are you sure you want to delete this macro? This action cannot be undone."
variant="danger"
confirmText={isDeleting ? "Deleting" : "Delete"}
onConfirm={() => {
onDelete?.();
setShowDeleteConfirm(false);
}}
isConfirming={isDeleting}
/>
</>
);
}

View File

@ -1,4 +1,4 @@
import { LuArrowUp, LuArrowDown, LuX } from "react-icons/lu";
import { LuArrowUp, LuArrowDown, LuX, LuTrash } from "react-icons/lu";
import { Button } from "@/components/Button";
import { Combobox } from "@/components/Combobox";
@ -6,7 +6,7 @@ import { SelectMenuBasic } from "@/components/SelectMenuBasic";
import Card from "@/components/Card";
import { keys, modifiers, keyDisplayMap } from "@/keyboardMappings";
import { MAX_KEYS_PER_STEP } from "@/constants/macros";
import FieldLabel from "@/components/FieldLabel";1
import FieldLabel from "@/components/FieldLabel";
// Filter out modifier keys since they're handled in the modifiers section
const modifierKeyPrefixes = ['Alt', 'Control', 'Shift', 'Meta'];
@ -121,6 +121,7 @@ export function MacroStepCard({
size="XS"
theme="danger"
text="Delete"
LeadingIcon={LuTrash}
onClick={onDelete}
/>
)}
@ -138,28 +139,20 @@ export function MacroStepCard({
</span>
<div className="flex flex-wrap gap-1">
{mods.map(option => (
<span
<Button
key={option.value}
className={`flex items-center px-2 py-1 rounded border cursor-pointer text-xs font-medium transition-colors ${
ensureArray(step.modifiers).includes(option.value)
? 'bg-blue-100 border-blue-300 text-blue-700 dark:bg-blue-900/40 dark:border-blue-600 dark:text-blue-200'
: 'bg-slate-100 border-slate-200 text-slate-600 hover:bg-slate-200 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-700'
}`}
>
<input
type="checkbox"
className="sr-only"
checked={ensureArray(step.modifiers).includes(option.value)}
onChange={e => {
size="XS"
theme={ensureArray(step.modifiers).includes(option.value) ? "primary" : "light"}
text={option.label.split(' ')[1] || option.label}
onClick={() => {
const modifiersArray = ensureArray(step.modifiers);
const newModifiers = e.target.checked
? [...modifiersArray, option.value]
: modifiersArray.filter(m => m !== option.value);
const isSelected = modifiersArray.includes(option.value);
const newModifiers = isSelected
? modifiersArray.filter(m => m !== option.value)
: [...modifiersArray, option.value];
onModifierChange(newModifiers);
}}
/>
{option.label.split(' ')[1] || option.label}
</span>
))}
</div>
</div>
@ -169,7 +162,7 @@ export function MacroStepCard({
<div className="w-full flex flex-col gap-1">
<div className="flex items-center gap-1">
<FieldLabel label="Keys" info={`You can add up to a maximum of ${MAX_KEYS_PER_STEP} keys to press per step.`} />
<FieldLabel label="Keys" description={`Maximum ${MAX_KEYS_PER_STEP} keys per step.`} />
</div>
<div className="flex flex-wrap gap-1 pb-2">
{ensureArray(step.keys).map((key, keyIndex) => (

View File

@ -1,10 +1,13 @@
import { useNavigate, useParams } from "react-router-dom";
import { useState, useEffect } from "react";
import { LuTrash } from "react-icons/lu";
import { KeySequence, useMacrosStore } from "@/hooks/stores";
import { SettingsPageHeader } from "@/components/SettingsPageheader";
import { MacroForm } from "@/components/MacroForm";
import notifications from "@/notifications";
import { Button } from "@/components/Button";
import { ConfirmDialog } from "@/components/ConfirmDialog";
const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => {
return macros.map((macro, index) => ({
@ -20,6 +23,7 @@ export default function SettingsMacrosEditRoute() {
const navigate = useNavigate();
const { macroId } = useParams<{ macroId: string }>();
const [macro, setMacro] = useState<KeySequence | null>(null);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
useEffect(() => {
const foundMacro = macros.find(m => m.id === macroId);
@ -89,19 +93,40 @@ export default function SettingsMacrosEditRoute() {
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<SettingsPageHeader
title="Edit Macro"
description="Modify your keyboard macro"
/>
<Button
size="SM"
theme="danger"
text="Delete Macro"
LeadingIcon={LuTrash}
onClick={() => setShowDeleteConfirm(true)}
disabled={isDeleting}
/>
</div>
<MacroForm
initialData={macro}
onSubmit={handleUpdateMacro}
onCancel={() => navigate("../")}
isSubmitting={isUpdating}
submitText="Save Changes"
showDelete
onDelete={handleDeleteMacro}
isDeleting={isDeleting}
/>
<ConfirmDialog
open={showDeleteConfirm}
onClose={() => setShowDeleteConfirm(false)}
title="Delete Macro"
description="Are you sure you want to delete this macro? This action cannot be undone."
variant="danger"
confirmText={isDeleting ? "Deleting" : "Delete"}
onConfirm={() => {
handleDeleteMacro();
setShowDeleteConfirm(false);
}}
isConfirming={isDeleting}
/>
</div>
);

View File

@ -1,6 +1,6 @@
import { useEffect, Fragment, useMemo, useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { LuPenLine, LuLoader, LuCopy, LuMoveRight, LuCornerDownRight, LuArrowUp, LuArrowDown } from "react-icons/lu";
import { LuPenLine, LuLoader, LuCopy, LuMoveRight, LuCornerDownRight, LuArrowUp, LuArrowDown, LuTrash } from "react-icons/lu";
import { KeySequence, useMacrosStore, generateMacroId } from "@/hooks/stores";
import { SettingsPageHeader } from "@/components/SettingsPageheader";
@ -10,7 +10,7 @@ import Card from "@/components/Card";
import { MAX_TOTAL_MACROS, COPY_SUFFIX } from "@/constants/macros";
import { keyDisplayMap, modifierDisplayMap } from "@/keyboardMappings";
import notifications from "@/notifications";
import { SettingsItem } from "@/routes/devices.$id.settings";
import { ConfirmDialog } from "@/components/ConfirmDialog";
const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => {
return macros.map((macro, index) => ({
@ -23,6 +23,8 @@ export default function SettingsMacrosRoute() {
const { macros, loading, initialized, loadMacros, saveMacros } = useMacrosStore();
const navigate = useNavigate();
const [actionLoadingId, setActionLoadingId] = useState<string | null>(null);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [macroToDelete, setMacroToDelete] = useState<KeySequence | null>(null);
const isMaxMacrosReached = useMemo(() =>
macros.length >= MAX_TOTAL_MACROS,
@ -98,6 +100,27 @@ export default function SettingsMacrosRoute() {
}
}, [macros, saveMacros, setActionLoadingId]);
const handleDeleteMacro = useCallback(async () => {
if (!macroToDelete?.id) return;
setActionLoadingId(macroToDelete.id);
try {
const updatedMacros = normalizeSortOrders(macros.filter(m => m.id !== macroToDelete.id));
await saveMacros(updatedMacros);
notifications.success(`Macro "${macroToDelete.name}" deleted successfully`);
setShowDeleteConfirm(false);
setMacroToDelete(null);
} catch (error: unknown) {
if (error instanceof Error) {
notifications.error(`Failed to delete macro: ${error.message}`);
} else {
notifications.error("Failed to delete macro");
}
} finally {
setActionLoadingId(null);
}
}, [macroToDelete, macros, saveMacros]);
const MacroList = useMemo(() => (
<div className="space-y-2">
{macros.map((macro, index) => (
@ -176,11 +199,21 @@ export default function SettingsMacrosRoute() {
</div>
<div className="flex items-center gap-1 ml-4">
<Button
size="XS"
theme="danger"
LeadingIcon={LuTrash}
onClick={() => {
setMacroToDelete(macro);
setShowDeleteConfirm(true);
}}
disabled={actionLoadingId === macro.id}
aria-label={`Delete macro ${macro.name}`}
/>
<Button
size="XS"
theme="light"
LeadingIcon={LuCopy}
text="Duplicate"
onClick={() => handleDuplicateMacro(macro)}
disabled={actionLoadingId === macro.id}
aria-label={`Duplicate macro ${macro.name}`}
@ -198,24 +231,33 @@ export default function SettingsMacrosRoute() {
</div>
</Card>
))}
<ConfirmDialog
open={showDeleteConfirm}
onClose={() => {
setShowDeleteConfirm(false);
setMacroToDelete(null);
}}
title="Delete Macro"
description={`Are you sure you want to delete "${macroToDelete?.name}"? This action cannot be undone.`}
variant="danger"
confirmText={actionLoadingId === macroToDelete?.id ? "Deleting..." : "Delete"}
onConfirm={handleDeleteMacro}
isConfirming={actionLoadingId === macroToDelete?.id}
/>
</div>
), [macros, actionLoadingId]);
), [macros, actionLoadingId, showDeleteConfirm, macroToDelete, handleDeleteMacro]);
return (
<div className="space-y-4">
{macros.length > 0 && (
<>
<div className="flex items-center justify-between">
<SettingsPageHeader
title="Keyboard Macros"
description="Create and manage keyboard macros for quick actions"
description={`Create and manage keyboard macros for quick actions. Currently ${macros.length}/${MAX_TOTAL_MACROS} macros are active.`}
/>
<div className="flex items-center justify-between mb-4">
<SettingsItem
title="Macros"
description={`${loading ? '?' : macros.length}/${MAX_TOTAL_MACROS}`}
>
{ macros.length > 0 && (
<div className="flex items-center gap-2">
<div className="flex items-center pl-2">
<Button
size="SM"
theme="primary"
@ -226,9 +268,7 @@ export default function SettingsMacrosRoute() {
/>
</div>
)}
</SettingsItem>
</div>
</>
)}
<div className="space-y-4">