mirror of https://github.com/jetkvm/kvm.git
Settings macros pages
This commit is contained in:
parent
214bd69d10
commit
1647b80b8c
|
|
@ -185,7 +185,7 @@
|
||||||
"connection_stats_video_description": "The video stream from the JetKVM to the client.",
|
"connection_stats_video_description": "The video stream from the JetKVM to the client.",
|
||||||
"connection_stats_video": "Video",
|
"connection_stats_video": "Video",
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"creating_peer_connection": "Creating peer connection...",
|
"creating_peer_connection": "Creating peer connection…",
|
||||||
"dc_power_control_current_unit": "A",
|
"dc_power_control_current_unit": "A",
|
||||||
"dc_power_control_current": "Current",
|
"dc_power_control_current": "Current",
|
||||||
"dc_power_control_get_state_error": "Failed to get DC power state: {error}",
|
"dc_power_control_get_state_error": "Failed to get DC power state: {error}",
|
||||||
|
|
@ -236,7 +236,7 @@
|
||||||
"extensions_dc_power_control_description": "Control your DC Power extension",
|
"extensions_dc_power_control_description": "Control your DC Power extension",
|
||||||
"extensions_dc_power_control": "DC Power Control",
|
"extensions_dc_power_control": "DC Power Control",
|
||||||
"extensions_popover_extensions": "Extensions",
|
"extensions_popover_extensions": "Extensions",
|
||||||
"gathering_ice_candidates": "Gathering ICE candidates...",
|
"gathering_ice_candidates": "Gathering ICE candidates…",
|
||||||
"general_app_version": "App: {version}",
|
"general_app_version": "App: {version}",
|
||||||
"general_auto_update_description": "Automatically update the device to the latest version",
|
"general_auto_update_description": "Automatically update the device to the latest version",
|
||||||
"general_auto_update_error": "Failed to set auto-update: {error}",
|
"general_auto_update_error": "Failed to set auto-update: {error}",
|
||||||
|
|
@ -258,7 +258,7 @@
|
||||||
"general_update_background_button": "Update in Background",
|
"general_update_background_button": "Update in Background",
|
||||||
"general_update_check_again_button": "Check Again",
|
"general_update_check_again_button": "Check Again",
|
||||||
"general_update_checking_description": "We're ensuring your device has the latest features and improvements.",
|
"general_update_checking_description": "We're ensuring your device has the latest features and improvements.",
|
||||||
"general_update_checking_title": "Checking for updates...",
|
"general_update_checking_title": "Checking for updates…",
|
||||||
"general_update_completed_description": "Your device has been successfully updated to the latest version. Enjoy the new features and improvements!",
|
"general_update_completed_description": "Your device has been successfully updated to the latest version. Enjoy the new features and improvements!",
|
||||||
"general_update_completed_title": "Update Completed Successfully",
|
"general_update_completed_title": "Update Completed Successfully",
|
||||||
"general_update_error_description": "An error occurred while updating your device. Please try again later.",
|
"general_update_error_description": "An error occurred while updating your device. Please try again later.",
|
||||||
|
|
@ -266,12 +266,12 @@
|
||||||
"general_update_error_title": "Update Error",
|
"general_update_error_title": "Update Error",
|
||||||
"general_update_later_button": "Do it later",
|
"general_update_later_button": "Do it later",
|
||||||
"general_update_now_button": "Update Now",
|
"general_update_now_button": "Update Now",
|
||||||
"general_update_rebooting": "Rebooting to complete the update...",
|
"general_update_rebooting": "Rebooting to complete the update…",
|
||||||
"general_update_status_awaiting_reboot": "Awaiting reboot",
|
"general_update_status_awaiting_reboot": "Awaiting reboot",
|
||||||
"general_update_status_downloading": "Downloading {update_type} update...",
|
"general_update_status_downloading": "Downloading {update_type} update…",
|
||||||
"general_update_status_fetching": "Fetching update information...",
|
"general_update_status_fetching": "Fetching update information…",
|
||||||
"general_update_status_installing": "Installing {update_type} update...",
|
"general_update_status_installing": "Installing {update_type} update…",
|
||||||
"general_update_status_verifying": "Verifying {update_type} update...",
|
"general_update_status_verifying": "Verifying {update_type} update…",
|
||||||
"general_update_system_type": "System",
|
"general_update_system_type": "System",
|
||||||
"general_update_system_update_title": "Linux System Update",
|
"general_update_system_update_title": "Linux System Update",
|
||||||
"general_update_up_to_date_description": "Your system is running the latest version. No updates are currently available.",
|
"general_update_up_to_date_description": "Your system is running the latest version. No updates are currently available.",
|
||||||
|
|
@ -438,6 +438,54 @@
|
||||||
"macro_step_search_for_key": "Search for key…",
|
"macro_step_search_for_key": "Search for key…",
|
||||||
"macro_steps_description": "Keys/modifiers executed in sequence with a delay between each step.",
|
"macro_steps_description": "Keys/modifiers executed in sequence with a delay between each step.",
|
||||||
"macro_steps_label": "Steps",
|
"macro_steps_label": "Steps",
|
||||||
|
"macros_add_description": "Create a new keyboard macro",
|
||||||
|
"macros_add_new": "Add New Macro",
|
||||||
|
"macros_create_first": "Create your first macro to get started",
|
||||||
|
"macros_created_success": "Macro \"{name}\" created successfully",
|
||||||
|
"macros_delete_confirm": "Are you sure you want to delete this macro? This action cannot be undone.",
|
||||||
|
"macros_delete_macro": "Delete Macro",
|
||||||
|
"macros_deleted_success": "Macro \"{name}\" deleted successfully",
|
||||||
|
"macros_deleting": "Deleting",
|
||||||
|
"macros_duplicate": "Duplicate",
|
||||||
|
"macros_duplicated_success": "Macro \"{name}\" duplicated successfully",
|
||||||
|
"macros_edit_description": "Modify your keyboard macro",
|
||||||
|
"macros_edit_title": "Edit Macro",
|
||||||
|
"macros_edit": "Edit",
|
||||||
|
"macros_failed_create": "Failed to create macro",
|
||||||
|
"macros_failed_create_error": "Failed to create macro: {error}",
|
||||||
|
"macros_failed_delete": "Failed to delete macro",
|
||||||
|
"macros_failed_delete_error": "Failed to delete macro: {error}",
|
||||||
|
"macros_failed_duplicate": "Failed to duplicate macro",
|
||||||
|
"macros_failed_duplicate_error": "Failed to duplicate macro: {error}",
|
||||||
|
"macros_failed_reorder": "Failed to reorder macros",
|
||||||
|
"macros_failed_reorder_error": "Failed to reorder macros: {error}",
|
||||||
|
"macros_failed_update": "Failed to update macro",
|
||||||
|
"macros_failed_update_error": "Failed to update macro: {error}",
|
||||||
|
"macros_invalid_data": "Invalid macro data",
|
||||||
|
"macros_maximum_macros_reached": "You have reached the maximum number of {maximum} macros allowed.",
|
||||||
|
"macros_move_down": "Move Down",
|
||||||
|
"macros_move_up": "Move Up",
|
||||||
|
"macros_no_macros_available": "No macros available",
|
||||||
|
"macros_no_macros_found": "No macros found",
|
||||||
|
"macros_order_updated": "Macro order updated successfully",
|
||||||
|
"macros_title": "Keyboard Macros",
|
||||||
|
"macros_updated_success": "Macro \"{name}\" updated successfully",
|
||||||
|
"macros_aria_delete": "Delete macro {name}",
|
||||||
|
"macros_aria_duplicate": "Duplicate macro {name}",
|
||||||
|
"macros_aria_edit": "Edit macro {name}",
|
||||||
|
"macros_aria_move_down": "Move {name} down",
|
||||||
|
"macros_aria_move_up": "Move {name} up",
|
||||||
|
"macros_confirm_delete_description": "Are you sure you want to delete \"{name}\"? This action cannot be undone.",
|
||||||
|
"macros_confirm_delete_title": "Delete Macro",
|
||||||
|
"macros_confirm_deleting": "Deleting…",
|
||||||
|
"macros_add_new_macro": "Add New Macro",
|
||||||
|
"macros_aria_add_new": "Add new macro",
|
||||||
|
"macros_create_first_headline": "Create Your First Macro",
|
||||||
|
"macros_create_first_description": "Combine keystrokes into a single action",
|
||||||
|
"macros_delay_only": "Delay only",
|
||||||
|
"macros_edit_button": "Edit",
|
||||||
|
"macros_loading": "Loading macros…",
|
||||||
|
"macros_max_reached": "Max Reached",
|
||||||
"metric_not_supported": "Metric not supported",
|
"metric_not_supported": "Metric not supported",
|
||||||
"metric_waiting_for_data": "Waiting for data…",
|
"metric_waiting_for_data": "Waiting for data…",
|
||||||
"mount_add_file_to_get_started": "Add a file to get started",
|
"mount_add_file_to_get_started": "Add a file to get started",
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,19 @@
|
||||||
import { useNavigate } from "react-router";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
import { KeySequence, useMacrosStore, generateMacroId } from "@/hooks/stores";
|
import { KeySequence, useMacrosStore, generateMacroId } from "@hooks/stores";
|
||||||
import { SettingsPageHeader } from "@/components/SettingsPageheader";
|
import { MacroForm } from "@components/MacroForm";
|
||||||
import { MacroForm } from "@/components/MacroForm";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import { DEFAULT_DELAY } from "@/constants/macros";
|
import { DEFAULT_DELAY } from "@/constants/macros";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
|
import { normalizeSortOrders } from "@/utils";
|
||||||
|
import { m } from "@localizations/messages.js";
|
||||||
|
|
||||||
export default function SettingsMacrosAddRoute() {
|
export default function SettingsMacrosAddRoute() {
|
||||||
const { macros, saveMacros } = useMacrosStore();
|
const { macros, saveMacros } = useMacrosStore();
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => {
|
|
||||||
return macros.map((macro, index) => ({
|
|
||||||
...macro,
|
|
||||||
sortOrder: index + 1,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddMacro = async (macro: Partial<KeySequence>) => {
|
const handleAddMacro = async (macro: Partial<KeySequence>) => {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
try {
|
try {
|
||||||
|
|
@ -30,13 +25,13 @@ export default function SettingsMacrosAddRoute() {
|
||||||
};
|
};
|
||||||
|
|
||||||
await saveMacros(normalizeSortOrders([...macros, newMacro]));
|
await saveMacros(normalizeSortOrders([...macros, newMacro]));
|
||||||
notifications.success(`Macro "${newMacro.name}" created successfully`);
|
notifications.success(m.macros_created_success({name: newMacro.name}));
|
||||||
navigate("../");
|
navigate("../");
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
notifications.error(`Failed to create macro: ${error.message}`);
|
notifications.error(m.macros_failed_create_error({error: error.message || m.unknown_error() }));
|
||||||
} else {
|
} else {
|
||||||
notifications.error("Failed to create macro");
|
notifications.error(m.macros_failed_create());
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
|
|
@ -46,8 +41,8 @@ export default function SettingsMacrosAddRoute() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<SettingsPageHeader
|
<SettingsPageHeader
|
||||||
title="Add New Macro"
|
title={m.macros_add_new()}
|
||||||
description="Create a new keyboard macro"
|
description={m.macros_add_description()}
|
||||||
/>
|
/>
|
||||||
<MacroForm
|
<MacroForm
|
||||||
initialData={{
|
initialData={{
|
||||||
|
|
@ -60,4 +55,4 @@ export default function SettingsMacrosAddRoute() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,20 +1,15 @@
|
||||||
import { useNavigate, useParams } from "react-router";
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
import { useNavigate, useParams } from "react-router";
|
||||||
import { LuTrash2 } from "react-icons/lu";
|
import { LuTrash2 } from "react-icons/lu";
|
||||||
|
|
||||||
import { KeySequence, useMacrosStore } from "@/hooks/stores";
|
import { KeySequence, useMacrosStore } from "@hooks/stores";
|
||||||
import { SettingsPageHeader } from "@/components/SettingsPageheader";
|
import { Button } from "@components/Button";
|
||||||
import { MacroForm } from "@/components/MacroForm";
|
import { ConfirmDialog } from "@components/ConfirmDialog";
|
||||||
|
import { MacroForm } from "@components/MacroForm";
|
||||||
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
import { Button } from "@/components/Button";
|
import { normalizeSortOrders } from "@/utils";
|
||||||
import { ConfirmDialog } from "@/components/ConfirmDialog";
|
import { m } from "@localizations/messages.js";
|
||||||
|
|
||||||
const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => {
|
|
||||||
return macros.map((macro, index) => ({
|
|
||||||
...macro,
|
|
||||||
sortOrder: index + 1,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function SettingsMacrosEditRoute() {
|
export default function SettingsMacrosEditRoute() {
|
||||||
const { macros, saveMacros } = useMacrosStore();
|
const { macros, saveMacros } = useMacrosStore();
|
||||||
|
|
@ -56,13 +51,13 @@ export default function SettingsMacrosEditRoute() {
|
||||||
);
|
);
|
||||||
|
|
||||||
await saveMacros(normalizeSortOrders(newMacros));
|
await saveMacros(normalizeSortOrders(newMacros));
|
||||||
notifications.success(`Macro "${updatedMacro.name}" updated successfully`);
|
notifications.success(m.macros_updated_success({ name: updatedMacro.name }));
|
||||||
navigate("../");
|
navigate("../");
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
notifications.error(`Failed to update macro: ${error.message}`);
|
notifications.error(m.macros_failed_update({ error: error.message }));
|
||||||
} else {
|
} else {
|
||||||
notifications.error("Failed to update macro");
|
notifications.error(m.macros_failed_update());
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsUpdating(false);
|
setIsUpdating(false);
|
||||||
|
|
@ -76,13 +71,13 @@ export default function SettingsMacrosEditRoute() {
|
||||||
try {
|
try {
|
||||||
const updatedMacros = normalizeSortOrders(macros.filter(m => m.id !== macro.id));
|
const updatedMacros = normalizeSortOrders(macros.filter(m => m.id !== macro.id));
|
||||||
await saveMacros(updatedMacros);
|
await saveMacros(updatedMacros);
|
||||||
notifications.success(`Macro "${macro.name}" deleted successfully`);
|
notifications.success(m.macros_deleted_success({ name: macro.name }));
|
||||||
navigate("../macros");
|
navigate("../macros");
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
notifications.error(`Failed to delete macro: ${error.message}`);
|
notifications.error(m.macros_failed_delete_error({ error: error.message }));
|
||||||
} else {
|
} else {
|
||||||
notifications.error("Failed to delete macro");
|
notifications.error(m.macros_failed_delete());
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
|
|
@ -95,13 +90,13 @@ export default function SettingsMacrosEditRoute() {
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<SettingsPageHeader
|
<SettingsPageHeader
|
||||||
title="Edit Macro"
|
title={m.macros_edit_title()}
|
||||||
description="Modify your keyboard macro"
|
description={m.macros_edit_description()}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="SM"
|
size="SM"
|
||||||
theme="light"
|
theme="light"
|
||||||
text="Delete Macro"
|
|
||||||
className="text-red-500 dark:text-red-400"
|
className="text-red-500 dark:text-red-400"
|
||||||
LeadingIcon={LuTrash2}
|
LeadingIcon={LuTrash2}
|
||||||
onClick={() => setShowDeleteConfirm(true)}
|
onClick={() => setShowDeleteConfirm(true)}
|
||||||
|
|
@ -118,10 +113,10 @@ export default function SettingsMacrosEditRoute() {
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
open={showDeleteConfirm}
|
open={showDeleteConfirm}
|
||||||
onClose={() => setShowDeleteConfirm(false)}
|
onClose={() => setShowDeleteConfirm(false)}
|
||||||
title="Delete Macro"
|
title={m.macros_delete_macro()}
|
||||||
description="Are you sure you want to delete this macro? This action cannot be undone."
|
description={m.macros_delete_confirm()}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
confirmText={isDeleting ? "Deleting" : "Delete"}
|
confirmText={isDeleting ? m.macros_deleting() : m.delete()}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
handleDeleteMacro();
|
handleDeleteMacro();
|
||||||
setShowDeleteConfirm(false);
|
setShowDeleteConfirm(false);
|
||||||
|
|
@ -130,4 +125,4 @@ export default function SettingsMacrosEditRoute() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -11,23 +11,18 @@ import {
|
||||||
LuCommand,
|
LuCommand,
|
||||||
} from "react-icons/lu";
|
} from "react-icons/lu";
|
||||||
|
|
||||||
import { KeySequence, useMacrosStore, generateMacroId } from "@/hooks/stores";
|
import { KeySequence, useMacrosStore, generateMacroId } from "@hooks/stores";
|
||||||
import { SettingsPageHeader } from "@/components/SettingsPageheader";
|
import useKeyboardLayout from "@hooks/useKeyboardLayout";
|
||||||
import { Button } from "@/components/Button";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import EmptyCard from "@/components/EmptyCard";
|
import { Button } from "@components/Button";
|
||||||
import Card from "@/components/Card";
|
import Card from "@components/Card";
|
||||||
import { MAX_TOTAL_MACROS, COPY_SUFFIX, DEFAULT_DELAY } from "@/constants/macros";
|
import { ConfirmDialog } from "@components/ConfirmDialog";
|
||||||
|
import EmptyCard from "@components/EmptyCard";
|
||||||
|
import LoadingSpinner from "@components/LoadingSpinner";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
import { ConfirmDialog } from "@/components/ConfirmDialog";
|
import { normalizeSortOrders } from "@/utils";
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
import { MAX_TOTAL_MACROS, COPY_SUFFIX, DEFAULT_DELAY } from "@/constants/macros";
|
||||||
import useKeyboardLayout from "@/hooks/useKeyboardLayout";
|
import { m } from "@localizations/messages.js";
|
||||||
|
|
||||||
const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => {
|
|
||||||
return macros.map((macro, index) => ({
|
|
||||||
...macro,
|
|
||||||
sortOrder: index + 1,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function SettingsMacrosRoute() {
|
export default function SettingsMacrosRoute() {
|
||||||
const { macros, loading, initialized, loadMacros, saveMacros } = useMacrosStore();
|
const { macros, loading, initialized, loadMacros, saveMacros } = useMacrosStore();
|
||||||
|
|
@ -35,7 +30,7 @@ export default function SettingsMacrosRoute() {
|
||||||
const [actionLoadingId, setActionLoadingId] = useState<string | null>(null);
|
const [actionLoadingId, setActionLoadingId] = useState<string | null>(null);
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
const [macroToDelete, setMacroToDelete] = useState<KeySequence | null>(null);
|
const [macroToDelete, setMacroToDelete] = useState<KeySequence | null>(null);
|
||||||
const { selectedKeyboard } = useKeyboardLayout();
|
const { selectedKeyboard } = useKeyboardLayout();
|
||||||
|
|
||||||
const isMaxMacrosReached = useMemo(
|
const isMaxMacrosReached = useMemo(
|
||||||
() => macros.length >= MAX_TOTAL_MACROS,
|
() => macros.length >= MAX_TOTAL_MACROS,
|
||||||
|
|
@ -51,12 +46,12 @@ export default function SettingsMacrosRoute() {
|
||||||
const handleDuplicateMacro = useCallback(
|
const handleDuplicateMacro = useCallback(
|
||||||
async (macro: KeySequence) => {
|
async (macro: KeySequence) => {
|
||||||
if (!macro?.id || !macro?.name) {
|
if (!macro?.id || !macro?.name) {
|
||||||
notifications.error("Invalid macro data");
|
notifications.error(m.macros_invalid_data());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMaxMacrosReached) {
|
if (isMaxMacrosReached) {
|
||||||
notifications.error(`Maximum of ${MAX_TOTAL_MACROS} macros allowed`);
|
notifications.error(m.macros_maximum_macros_reached({ maximum: MAX_TOTAL_MACROS }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,12 +66,12 @@ export default function SettingsMacrosRoute() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await saveMacros(normalizeSortOrders([...macros, newMacroCopy]));
|
await saveMacros(normalizeSortOrders([...macros, newMacroCopy]));
|
||||||
notifications.success(`Macro "${newMacroCopy.name}" duplicated successfully`);
|
notifications.success(m.macros_duplicated_success({ name: newMacroCopy.name }));
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
notifications.error(`Failed to duplicate macro: ${error.message}`);
|
notifications.error(m.macros_failed_duplicate_error({ error: error.message || m.unknown_error() }));
|
||||||
} else {
|
} else {
|
||||||
notifications.error("Failed to duplicate macro");
|
notifications.error(m.macros_failed_duplicate());
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setActionLoadingId(null);
|
setActionLoadingId(null);
|
||||||
|
|
@ -88,7 +83,7 @@ export default function SettingsMacrosRoute() {
|
||||||
const handleMoveMacro = useCallback(
|
const handleMoveMacro = useCallback(
|
||||||
async (index: number, direction: "up" | "down", macroId: string) => {
|
async (index: number, direction: "up" | "down", macroId: string) => {
|
||||||
if (!Array.isArray(macros) || macros.length === 0) {
|
if (!Array.isArray(macros) || macros.length === 0) {
|
||||||
notifications.error("No macros available");
|
notifications.error(m.macros_no_macros_available());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,12 +98,12 @@ export default function SettingsMacrosRoute() {
|
||||||
const updatedMacros = normalizeSortOrders(newMacros);
|
const updatedMacros = normalizeSortOrders(newMacros);
|
||||||
|
|
||||||
await saveMacros(updatedMacros);
|
await saveMacros(updatedMacros);
|
||||||
notifications.success("Macro order updated successfully");
|
notifications.success(m.macros_order_updated());
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
notifications.error(`Failed to reorder macros: ${error.message}`);
|
notifications.error(m.macros_failed_reorder_error({ error: error.message || m.unknown_error() }));
|
||||||
} else {
|
} else {
|
||||||
notifications.error("Failed to reorder macros");
|
notifications.error(m.macros_failed_reorder());
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setActionLoadingId(null);
|
setActionLoadingId(null);
|
||||||
|
|
@ -126,14 +121,14 @@ export default function SettingsMacrosRoute() {
|
||||||
macros.filter(m => m.id !== macroToDelete.id),
|
macros.filter(m => m.id !== macroToDelete.id),
|
||||||
);
|
);
|
||||||
await saveMacros(updatedMacros);
|
await saveMacros(updatedMacros);
|
||||||
notifications.success(`Macro "${macroToDelete.name}" deleted successfully`);
|
notifications.success(m.macros_deleted_success({ name: macroToDelete.name }));
|
||||||
setShowDeleteConfirm(false);
|
setShowDeleteConfirm(false);
|
||||||
setMacroToDelete(null);
|
setMacroToDelete(null);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
notifications.error(`Failed to delete macro: ${error.message}`);
|
notifications.error(m.macros_failed_delete_error({ error: error.message || m.unknown_error() }));
|
||||||
} else {
|
} else {
|
||||||
notifications.error("Failed to delete macro");
|
notifications.error(m.macros_failed_delete());
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setActionLoadingId(null);
|
setActionLoadingId(null);
|
||||||
|
|
@ -153,7 +148,7 @@ export default function SettingsMacrosRoute() {
|
||||||
onClick={() => handleMoveMacro(index, "up", macro.id)}
|
onClick={() => handleMoveMacro(index, "up", macro.id)}
|
||||||
disabled={index === 0 || actionLoadingId === macro.id}
|
disabled={index === 0 || actionLoadingId === macro.id}
|
||||||
LeadingIcon={LuArrowUp}
|
LeadingIcon={LuArrowUp}
|
||||||
aria-label={`Move ${macro.name} up`}
|
aria-label={m.macros_aria_move_up({ name: macro.name })}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="XS"
|
size="XS"
|
||||||
|
|
@ -161,7 +156,7 @@ export default function SettingsMacrosRoute() {
|
||||||
onClick={() => handleMoveMacro(index, "down", macro.id)}
|
onClick={() => handleMoveMacro(index, "down", macro.id)}
|
||||||
disabled={index === macros.length - 1 || actionLoadingId === macro.id}
|
disabled={index === macros.length - 1 || actionLoadingId === macro.id}
|
||||||
LeadingIcon={LuArrowDown}
|
LeadingIcon={LuArrowDown}
|
||||||
aria-label={`Move ${macro.name} down`}
|
aria-label={m.macros_aria_move_down({ name: macro.name })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -180,7 +175,7 @@ export default function SettingsMacrosRoute() {
|
||||||
<span className="rounded-md border border-slate-200/50 bg-slate-50 px-2 py-0.5 dark:border-slate-700/50 dark:bg-slate-800">
|
<span className="rounded-md border border-slate-200/50 bg-slate-50 px-2 py-0.5 dark:border-slate-700/50 dark:bg-slate-800">
|
||||||
{(Array.isArray(step.modifiers) &&
|
{(Array.isArray(step.modifiers) &&
|
||||||
step.modifiers.length > 0) ||
|
step.modifiers.length > 0) ||
|
||||||
(Array.isArray(step.keys) && step.keys.length > 0) ? (
|
(Array.isArray(step.keys) && step.keys.length > 0) ? (
|
||||||
<>
|
<>
|
||||||
{Array.isArray(step.modifiers) &&
|
{Array.isArray(step.modifiers) &&
|
||||||
step.modifiers.map((modifier, idx) => (
|
step.modifiers.map((modifier, idx) => (
|
||||||
|
|
@ -189,10 +184,7 @@ export default function SettingsMacrosRoute() {
|
||||||
{selectedKeyboard.modifierDisplayMap[modifier] || modifier}
|
{selectedKeyboard.modifierDisplayMap[modifier] || modifier}
|
||||||
</span>
|
</span>
|
||||||
{idx < step.modifiers.length - 1 && (
|
{idx < step.modifiers.length - 1 && (
|
||||||
<span className="text-slate-400 dark:text-slate-600">
|
<span className="text-slate-400 dark:text-slate-600"> + </span>
|
||||||
{" "}
|
|
||||||
+{" "}
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
|
@ -201,10 +193,7 @@ export default function SettingsMacrosRoute() {
|
||||||
step.modifiers.length > 0 &&
|
step.modifiers.length > 0 &&
|
||||||
Array.isArray(step.keys) &&
|
Array.isArray(step.keys) &&
|
||||||
step.keys.length > 0 && (
|
step.keys.length > 0 && (
|
||||||
<span className="text-slate-400 dark:text-slate-600">
|
<span className="text-slate-400 dark:text-slate-600"> + </span>
|
||||||
{" "}
|
|
||||||
+{" "}
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{Array.isArray(step.keys) &&
|
{Array.isArray(step.keys) &&
|
||||||
|
|
@ -214,17 +203,14 @@ export default function SettingsMacrosRoute() {
|
||||||
{selectedKeyboard.keyDisplayMap[key] || key}
|
{selectedKeyboard.keyDisplayMap[key] || key}
|
||||||
</span>
|
</span>
|
||||||
{idx < step.keys.length - 1 && (
|
{idx < step.keys.length - 1 && (
|
||||||
<span className="text-slate-400 dark:text-slate-600">
|
<span className="text-slate-400 dark:text-slate-600"> + </span>
|
||||||
{" "}
|
|
||||||
+{" "}
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span className="font-medium text-slate-500 dark:text-slate-400">
|
<span className="font-medium text-slate-500 dark:text-slate-400">
|
||||||
Delay only
|
{m.macros_delay_only()}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{step.delay !== DEFAULT_DELAY && (
|
{step.delay !== DEFAULT_DELAY && (
|
||||||
|
|
@ -251,7 +237,7 @@ export default function SettingsMacrosRoute() {
|
||||||
setShowDeleteConfirm(true);
|
setShowDeleteConfirm(true);
|
||||||
}}
|
}}
|
||||||
disabled={actionLoadingId === macro.id}
|
disabled={actionLoadingId === macro.id}
|
||||||
aria-label={`Delete macro ${macro.name}`}
|
aria-label={m.macros_aria_delete({ name: macro.name })}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="XS"
|
size="XS"
|
||||||
|
|
@ -259,16 +245,16 @@ export default function SettingsMacrosRoute() {
|
||||||
LeadingIcon={LuCopy}
|
LeadingIcon={LuCopy}
|
||||||
onClick={() => handleDuplicateMacro(macro)}
|
onClick={() => handleDuplicateMacro(macro)}
|
||||||
disabled={actionLoadingId === macro.id}
|
disabled={actionLoadingId === macro.id}
|
||||||
aria-label={`Duplicate macro ${macro.name}`}
|
aria-label={m.macros_aria_duplicate({ name: macro.name })}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="XS"
|
size="XS"
|
||||||
theme="light"
|
theme="light"
|
||||||
LeadingIcon={LuPenLine}
|
LeadingIcon={LuPenLine}
|
||||||
text="Edit"
|
text={m.macros_edit_button()}
|
||||||
onClick={() => navigate(`${macro.id}/edit`)}
|
onClick={() => navigate(`${macro.id}/edit`)}
|
||||||
disabled={actionLoadingId === macro.id}
|
disabled={actionLoadingId === macro.id}
|
||||||
aria-label={`Edit macro ${macro.name}`}
|
aria-label={m.macros_aria_edit({ name: macro.name })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -281,10 +267,10 @@ export default function SettingsMacrosRoute() {
|
||||||
setShowDeleteConfirm(false);
|
setShowDeleteConfirm(false);
|
||||||
setMacroToDelete(null);
|
setMacroToDelete(null);
|
||||||
}}
|
}}
|
||||||
title="Delete Macro"
|
title={m.macros_confirm_delete_title()}
|
||||||
description={`Are you sure you want to delete "${macroToDelete?.name}"? This action cannot be undone.`}
|
description={m.macros_confirm_delete_description({ name: macroToDelete?.name || "" })}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
confirmText={actionLoadingId === macroToDelete?.id ? "Deleting..." : "Delete"}
|
confirmText={actionLoadingId === macroToDelete?.id ? m.macros_confirm_deleting() : m.macros_delete_confirm_button()}
|
||||||
onConfirm={handleDeleteMacro}
|
onConfirm={handleDeleteMacro}
|
||||||
isConfirming={actionLoadingId === macroToDelete?.id}
|
isConfirming={actionLoadingId === macroToDelete?.id}
|
||||||
/>
|
/>
|
||||||
|
|
@ -309,18 +295,18 @@ export default function SettingsMacrosRoute() {
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<SettingsPageHeader
|
<SettingsPageHeader
|
||||||
title="Keyboard Macros"
|
title={m.macros_title()}
|
||||||
description={`Combine keystrokes into a single action for faster workflows.`}
|
description={m.macros_add_new()}
|
||||||
/>
|
/>
|
||||||
{macros.length > 0 && (
|
{macros.length > 0 && (
|
||||||
<div className="flex items-center pl-2">
|
<div className="flex items-center pl-2">
|
||||||
<Button
|
<Button
|
||||||
size="SM"
|
size="SM"
|
||||||
theme="primary"
|
theme="primary"
|
||||||
text={isMaxMacrosReached ? `Max Reached` : "Add New Macro"}
|
text={isMaxMacrosReached ? m.macros_max_reached() : m.macros_add_new_macro()}
|
||||||
onClick={() => navigate("add")}
|
onClick={() => navigate("add")}
|
||||||
disabled={isMaxMacrosReached}
|
disabled={isMaxMacrosReached}
|
||||||
aria-label="Add new macro"
|
aria-label={m.macros_aria_add_new()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -330,7 +316,7 @@ export default function SettingsMacrosRoute() {
|
||||||
{loading && macros.length === 0 ? (
|
{loading && macros.length === 0 ? (
|
||||||
<EmptyCard
|
<EmptyCard
|
||||||
IconElm={LuCommand}
|
IconElm={LuCommand}
|
||||||
headline="Loading macros..."
|
headline={m.macros_loading()}
|
||||||
BtnElm={
|
BtnElm={
|
||||||
<div className="my-2 flex flex-col items-center space-y-2 text-center">
|
<div className="my-2 flex flex-col items-center space-y-2 text-center">
|
||||||
<LoadingSpinner className="h-6 w-6 text-blue-700 dark:text-blue-500" />
|
<LoadingSpinner className="h-6 w-6 text-blue-700 dark:text-blue-500" />
|
||||||
|
|
@ -340,16 +326,16 @@ export default function SettingsMacrosRoute() {
|
||||||
) : macros.length === 0 ? (
|
) : macros.length === 0 ? (
|
||||||
<EmptyCard
|
<EmptyCard
|
||||||
IconElm={LuCommand}
|
IconElm={LuCommand}
|
||||||
headline="Create Your First Macro"
|
headline={m.macros_create_first_headline()}
|
||||||
description="Combine keystrokes into a single action"
|
description={m.macros_create_first_description()}
|
||||||
BtnElm={
|
BtnElm={
|
||||||
<Button
|
<Button
|
||||||
size="SM"
|
size="SM"
|
||||||
theme="primary"
|
theme="primary"
|
||||||
text="Add New Macro"
|
text={m.macros_add_new_macro()}
|
||||||
onClick={() => navigate("add")}
|
onClick={() => navigate("add")}
|
||||||
disabled={isMaxMacrosReached}
|
disabled={isMaxMacrosReached}
|
||||||
aria-label="Add new macro"
|
aria-label={m.macros_aria_add_new()}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { KeySequence } from "@hooks/stores";
|
||||||
|
|
||||||
export const formatters = {
|
export const formatters = {
|
||||||
date: (date: Date, options?: Intl.DateTimeFormatOptions) =>
|
date: (date: Date, options?: Intl.DateTimeFormatOptions) =>
|
||||||
new Intl.DateTimeFormat("en-US", {
|
new Intl.DateTimeFormat("en-US", {
|
||||||
|
|
@ -243,3 +245,10 @@ export function isChromeOS() {
|
||||||
/* ChromeOS sets navigator.platform to Linux :/ */
|
/* ChromeOS sets navigator.platform to Linux :/ */
|
||||||
return !!navigator.userAgent.match(" CrOS ");
|
return !!navigator.userAgent.match(" CrOS ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeSortOrders(macros: KeySequence[]): KeySequence[] {
|
||||||
|
return macros.map((macro, index) => ({
|
||||||
|
...macro,
|
||||||
|
sortOrder: index + 1,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue