feat: add import commands modal
This commit is contained in:
parent
3ba3f5efd5
commit
7a20ae1536
@ -14,6 +14,7 @@
|
||||
href="./src/lib/bootstrap-icons-1.11.3/font/bootstrap-icons.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script src="./lib/papaparse-5.5.2.min.js"></script>
|
||||
<script src="./lib/fusejs-7.1.0.min.js"></script>
|
||||
<script src="./lib/popper.min.js"></script>
|
||||
<script src="./lib/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
function Command({
|
||||
id,
|
||||
protocol,
|
||||
command: commandNumber,
|
||||
commandNumber,
|
||||
device,
|
||||
commandType,
|
||||
title,
|
||||
@ -54,7 +54,8 @@ function Command({
|
||||
}
|
||||
|
||||
function getTitle() {
|
||||
return _title;
|
||||
if (_title) return _title;
|
||||
return `${Command.CommandTypes[commandType]} (${Command.Protocols[protocol]})`;
|
||||
}
|
||||
|
||||
function setTitle(title) {
|
||||
@ -102,7 +103,7 @@ Command.Protocols = {
|
||||
fast: "FAST",
|
||||
whynter: "Whynter",
|
||||
magiquest: "MagiQuest",
|
||||
}
|
||||
};
|
||||
|
||||
Command.CommandTypes = {
|
||||
power: "Power",
|
||||
@ -126,13 +127,13 @@ Command.CommandTypes = {
|
||||
home: "Home",
|
||||
settings: "Settings",
|
||||
options: "Options",
|
||||
up_arrow: "Up Arrow",
|
||||
down_arrow: "Down Arrow",
|
||||
left_arrow: "Left Arrow",
|
||||
right_arrow: "Right Arrow",
|
||||
select: "Select",
|
||||
up: "Up",
|
||||
down: "Down",
|
||||
left: "Left",
|
||||
right: "Right",
|
||||
enter: "Enter",
|
||||
info: "Info",
|
||||
back: "Back",
|
||||
return: "Return",
|
||||
exit: "Exit",
|
||||
red: "Red",
|
||||
green: "Green",
|
||||
@ -144,6 +145,6 @@ Command.CommandTypes = {
|
||||
stop: "Stop",
|
||||
forward: "Forward",
|
||||
other: "Other",
|
||||
}
|
||||
};
|
||||
|
||||
export default Command;
|
||||
|
||||
7
www/src/lib/papaparse-5.5.2.min.js
vendored
Normal file
7
www/src/lib/papaparse-5.5.2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
489
www/src/modals/import-commands-modal.jsx
Normal file
489
www/src/modals/import-commands-modal.jsx
Normal file
@ -0,0 +1,489 @@
|
||||
import {
|
||||
createEffect,
|
||||
createMemo,
|
||||
createSignal,
|
||||
Match,
|
||||
on,
|
||||
Show,
|
||||
Switch,
|
||||
} from "solid-js";
|
||||
import RemotesService from "../services/remotes-service.js";
|
||||
import EventEmitter from "../tools/event-emitter.js";
|
||||
import ModalHandler from "./modal-handler.js";
|
||||
import Modal from "./modal.jsx";
|
||||
import Command from "../data/command.js";
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
const COMMANDS_IMPORTED_EVENT = "success";
|
||||
|
||||
const ENTER_CSV_STEP = 1;
|
||||
const MAP_FIELDS_STEP = 2;
|
||||
const MAP_VALUES_STEP = 3;
|
||||
const CONFIRM_STEP = 4;
|
||||
const TOTAL_STEPS = 4;
|
||||
|
||||
const PROTOCOL_FIELD = "protocol";
|
||||
const COMMAND_NUMBER_FIELD = "commandNumber";
|
||||
const DEVICE_FIELD = "device";
|
||||
const COMMAND_TYPE_FIELD = "commandType";
|
||||
const TITLE_FIELD = "title";
|
||||
let FieldTitles = {};
|
||||
FieldTitles[PROTOCOL_FIELD] = "Protocol";
|
||||
FieldTitles[COMMAND_NUMBER_FIELD] = "Command";
|
||||
FieldTitles[DEVICE_FIELD] = "Device";
|
||||
FieldTitles[COMMAND_TYPE_FIELD] = "Type";
|
||||
FieldTitles[TITLE_FIELD] = "Title";
|
||||
|
||||
const CommandFieldsRequiringValueMapping = ["protocol", "commandType"];
|
||||
const commandTypeFuse = new Fuse(Object.values(Command.CommandTypes));
|
||||
const protocolsFuse = new Fuse(Object.values(Command.Protocols));
|
||||
|
||||
function ImportCommandsModal(props) {
|
||||
const [csvString, setCsvString] = createSignal("");
|
||||
const [currentStep, setCurrentStep] = createSignal(ENTER_CSV_STEP);
|
||||
const [error, setError] = createSignal("");
|
||||
const [csvArray, setCsvArray] = createSignal([]);
|
||||
const [commands, setCommands] = createSignal([]);
|
||||
const [fieldMapping, setFieldMapping] = createSignal(
|
||||
{},
|
||||
{ equals: () => false }
|
||||
);
|
||||
const [isComputingValueMapping, setComputingValueMapping] =
|
||||
createSignal(false);
|
||||
const [valueMapping, setValueMapping] = createSignal(
|
||||
(() => {
|
||||
let mapping = {};
|
||||
mapping[PROTOCOL_FIELD] = {};
|
||||
mapping[COMMAND_TYPE_FIELD] = {};
|
||||
return mapping;
|
||||
})(),
|
||||
{ equals: () => false }
|
||||
);
|
||||
|
||||
const canMakeNextStep = createMemo(() => {
|
||||
switch (currentStep()) {
|
||||
case ENTER_CSV_STEP:
|
||||
return csvString() !== "";
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
const fields = createMemo(() =>
|
||||
Object.keys(csvArray().find(() => true) || {})
|
||||
);
|
||||
|
||||
createEffect(() => {
|
||||
switch (currentStep()) {
|
||||
case MAP_VALUES_STEP:
|
||||
onMapValuesStep();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
function handleNextStep() {
|
||||
switch (currentStep()) {
|
||||
case ENTER_CSV_STEP:
|
||||
handleEnterCsvStep();
|
||||
break;
|
||||
case MAP_FIELDS_STEP:
|
||||
handleMapFieldsStep();
|
||||
break;
|
||||
case MAP_VALUES_STEP:
|
||||
handleMapValuesStep();
|
||||
break;
|
||||
case CONFIRM_STEP:
|
||||
handleImportCommands();
|
||||
break;
|
||||
default:
|
||||
nextStep();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleImportCommands() {
|
||||
let newCommands = [];
|
||||
try {
|
||||
newCommands = await RemotesService.createCommands(commands());
|
||||
} catch (e) {
|
||||
setError(e.message);
|
||||
return;
|
||||
}
|
||||
resetFields();
|
||||
ImportCommandsModal.Handler.hide();
|
||||
eventEmitter.dispatchEvent(COMMANDS_IMPORTED_EVENT, newCommands);
|
||||
}
|
||||
|
||||
function EnterCsvStep() {
|
||||
return (
|
||||
<div class="mb-3">
|
||||
<label for="csvTextarea" class="form-label">
|
||||
Enter CSV:
|
||||
</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="csvTextarea"
|
||||
rows="10"
|
||||
value={csvString()}
|
||||
onInput={(e) => setCsvString(e.target.value)}
|
||||
></textarea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function handleEnterCsvStep() {
|
||||
if (!csvString()) {
|
||||
setError("Please enter a CSV string.");
|
||||
return;
|
||||
}
|
||||
let result = Papa.parse(csvString(), { header: true });
|
||||
if (result.errors.length > 0) {
|
||||
setError(result.errors[0].message);
|
||||
return;
|
||||
}
|
||||
let csvArray = result.data;
|
||||
setCsvArray(csvArray);
|
||||
nextStep();
|
||||
}
|
||||
|
||||
function MapFieldsStep() {
|
||||
function setMapping(field, commandField) {
|
||||
let mapping = fieldMapping();
|
||||
mapping[field] = commandField;
|
||||
setFieldMapping(mapping);
|
||||
}
|
||||
return (
|
||||
<div class="mb-3">
|
||||
<div>Map Fields:</div>
|
||||
<div class="d-flex flex-column">
|
||||
{fields().map((field) => (
|
||||
<div class="d-flex flex-row align-items-center justify-content-center text-end mb-2">
|
||||
<div class="col-sm-4">
|
||||
<div>{field}</div>
|
||||
<div class="fw-light lh-1">(e.g. {csvArray()[0][field]})</div>
|
||||
</div>
|
||||
<div style="font-size: 1.5rem; line-height: 1;">
|
||||
<i class="bi bi-arrow-right-short"></i>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<select
|
||||
class="form-select"
|
||||
onChange={(e) => setMapping(field, e.target.value)}
|
||||
>
|
||||
<option value="" selected>
|
||||
Please select
|
||||
</option>
|
||||
{Object.keys(FieldTitles).map((commandField) => (
|
||||
<option
|
||||
value={commandField}
|
||||
selected={fieldMapping()[field] === commandField}
|
||||
>
|
||||
{FieldTitles[commandField]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function handleMapFieldsStep() {
|
||||
let usedCommandFields = [];
|
||||
let mapping = fieldMapping();
|
||||
for (let field in mapping) {
|
||||
if (usedCommandFields.includes(mapping[field])) {
|
||||
setError("Duplicate mapping found.");
|
||||
return false;
|
||||
}
|
||||
usedCommandFields.push(mapping[field]);
|
||||
}
|
||||
nextStep();
|
||||
}
|
||||
|
||||
function MapValuesStep() {
|
||||
function setMapping(field, value, commandValue) {
|
||||
let mapping = valueMapping();
|
||||
if (!mapping[field]) mapping[field] = {};
|
||||
mapping[field][value] = commandValue;
|
||||
setValueMapping(mapping);
|
||||
}
|
||||
return (
|
||||
<div class="mb-3">
|
||||
<div>Map Values:</div>
|
||||
{Object.keys(fieldMapping())
|
||||
.filter((field) =>
|
||||
CommandFieldsRequiringValueMapping.includes(fieldMapping()[field])
|
||||
)
|
||||
.map((csvField) => {
|
||||
let field = fieldMapping()[csvField];
|
||||
return (
|
||||
<div>
|
||||
<div>{FieldTitles[field]}:</div>
|
||||
<div class="d-flex flex-column">
|
||||
{csvArray()
|
||||
.map((row) => row[csvField])
|
||||
.filter(
|
||||
(value, index, array) => array.indexOf(value) === index
|
||||
)
|
||||
.map((value) => (
|
||||
<div class="d-flex flex-row align-items-center justify-content-center text-end mb-2">
|
||||
<div class="col-sm-4">
|
||||
<div>{value}</div>
|
||||
</div>
|
||||
<div style="font-size: 1.5rem; line-height: 1;">
|
||||
<i class="bi bi-arrow-right-short"></i>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<Switch>
|
||||
<Match when={field === "protocol"}>
|
||||
<select
|
||||
class="form-select"
|
||||
onChange={(e) =>
|
||||
setMapping(field, value, e.target.value)
|
||||
}
|
||||
>
|
||||
<option value="" selected>
|
||||
Please select
|
||||
</option>
|
||||
{Object.keys(Command.Protocols).map(
|
||||
(protocol) => (
|
||||
<option
|
||||
value={protocol}
|
||||
selected={
|
||||
valueMapping()[PROTOCOL_FIELD][
|
||||
value
|
||||
] === protocol
|
||||
}
|
||||
>
|
||||
{Command.Protocols[protocol]}
|
||||
</option>
|
||||
)
|
||||
)}
|
||||
</select>
|
||||
</Match>
|
||||
<Match when={field === "commandType"}>
|
||||
<select
|
||||
class="form-select"
|
||||
onChange={(e) =>
|
||||
setMapping(field, value, e.target.value)
|
||||
}
|
||||
>
|
||||
<option value="" selected>
|
||||
Please select
|
||||
</option>
|
||||
{Object.keys(Command.CommandTypes).map(
|
||||
(commandType) => (
|
||||
<option
|
||||
value={commandType}
|
||||
selected={
|
||||
valueMapping()[COMMAND_TYPE_FIELD][
|
||||
value
|
||||
] === commandType
|
||||
}
|
||||
>
|
||||
{Command.CommandTypes[commandType]}
|
||||
</option>
|
||||
)
|
||||
)}
|
||||
</select>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function onMapValuesStep() {
|
||||
setComputingValueMapping(true);
|
||||
(async () => {
|
||||
let valueMapping = {};
|
||||
valueMapping[PROTOCOL_FIELD] = {};
|
||||
valueMapping[COMMAND_TYPE_FIELD] = {};
|
||||
let protocolField = Object.keys(fieldMapping()).find(
|
||||
(field) => fieldMapping()[field] === PROTOCOL_FIELD
|
||||
);
|
||||
let commandTypeField = Object.keys(fieldMapping()).find(
|
||||
(field) => fieldMapping()[field] === COMMAND_TYPE_FIELD
|
||||
);
|
||||
csvArray().forEach((row) => {
|
||||
let protocolValue = row[protocolField];
|
||||
let commandTypeValue = row[commandTypeField];
|
||||
if (!valueMapping[PROTOCOL_FIELD][protocolValue]) {
|
||||
let result = protocolsFuse.search(protocolValue).shift();
|
||||
if (result) {
|
||||
let protocol = Object.keys(Command.Protocols).find(
|
||||
(protocol) => Command.Protocols[protocol] === result.item
|
||||
);
|
||||
valueMapping[PROTOCOL_FIELD][protocolValue] = protocol;
|
||||
}
|
||||
}
|
||||
if (!valueMapping[COMMAND_TYPE_FIELD][commandTypeValue]) {
|
||||
let result = commandTypeFuse.search(commandTypeValue).shift();
|
||||
if (result) {
|
||||
let commandType = Object.keys(Command.CommandTypes).find(
|
||||
(commandType) => Command.CommandTypes[commandType] === result.item
|
||||
);
|
||||
valueMapping[COMMAND_TYPE_FIELD][commandTypeValue] = commandType;
|
||||
}
|
||||
}
|
||||
});
|
||||
setValueMapping(valueMapping);
|
||||
setComputingValueMapping(false);
|
||||
})();
|
||||
}
|
||||
|
||||
function handleMapValuesStep() {
|
||||
let protocolField = Object.keys(fieldMapping()).find(
|
||||
(field) => fieldMapping()[field] === PROTOCOL_FIELD
|
||||
);
|
||||
let commandNumberField = Object.keys(fieldMapping()).find(
|
||||
(field) => fieldMapping()[field] === COMMAND_NUMBER_FIELD
|
||||
);
|
||||
let deviceField = Object.keys(fieldMapping()).find(
|
||||
(field) => fieldMapping()[field] === DEVICE_FIELD
|
||||
);
|
||||
let commandTypeField = Object.keys(fieldMapping()).find(
|
||||
(field) => fieldMapping()[field] === COMMAND_TYPE_FIELD
|
||||
);
|
||||
let titleField = Object.keys(fieldMapping()).find(
|
||||
(field) => fieldMapping()[field] === TITLE_FIELD
|
||||
);
|
||||
let commands = csvArray()
|
||||
.map((row) => {
|
||||
let protocol = valueMapping()[PROTOCOL_FIELD][row[protocolField]];
|
||||
if (!protocol) return null;
|
||||
let commandNumber = row[commandNumberField];
|
||||
let device = row[deviceField];
|
||||
let commandType =
|
||||
valueMapping()[COMMAND_TYPE_FIELD][row[commandTypeField]];
|
||||
if (!commandType) return null;
|
||||
let title = row[titleField];
|
||||
let command = new Command({
|
||||
protocol,
|
||||
commandNumber,
|
||||
device,
|
||||
commandType,
|
||||
title,
|
||||
});
|
||||
return command;
|
||||
})
|
||||
.filter((command) => command !== null);
|
||||
setCommands(commands);
|
||||
nextStep();
|
||||
}
|
||||
|
||||
function ConfirmStep() {
|
||||
return (
|
||||
<div class="mb-3">
|
||||
<div>Confirm:</div>
|
||||
<div class="d-flex flex-column">
|
||||
{commands().map((command) => (
|
||||
<div class="d-flex flex-row align-items-center justify-content-center mb-2">
|
||||
<div class="col-sm-2">{command.getProtocol()}</div>
|
||||
<div class="col-sm-2">{command.getCommandNumber()}</div>
|
||||
<div class="col-sm-2">{command.getDevice()}</div>
|
||||
<div class="col-sm-2">{command.getCommandType()}</div>
|
||||
<div class="col-sm-2">{command.getTitle()}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function nextStep() {
|
||||
if (currentStep() >= TOTAL_STEPS) return;
|
||||
setError("");
|
||||
setCurrentStep(currentStep() + 1);
|
||||
}
|
||||
|
||||
function previousStep() {
|
||||
if (currentStep() <= 1) return;
|
||||
setError("");
|
||||
setCurrentStep(currentStep() - 1);
|
||||
}
|
||||
|
||||
function resetFields() {
|
||||
setCsvString("");
|
||||
setError("");
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
ref={props.ref}
|
||||
id="importCommandsModal"
|
||||
modalTitle="Import Commands from CSV"
|
||||
centered={true}
|
||||
>
|
||||
<div class="modal-body">
|
||||
<Show when={error() !== ""}>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{error()}
|
||||
</div>
|
||||
</Show>
|
||||
<Switch>
|
||||
<Match when={currentStep() === ENTER_CSV_STEP}>
|
||||
<EnterCsvStep />
|
||||
</Match>
|
||||
<Match when={currentStep() === MAP_FIELDS_STEP}>
|
||||
<MapFieldsStep />
|
||||
</Match>
|
||||
<Match when={currentStep() === MAP_VALUES_STEP}>
|
||||
<MapValuesStep />
|
||||
</Match>
|
||||
<Match when={currentStep() === CONFIRM_STEP}>
|
||||
<ConfirmStep />
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<Show when={currentStep() > 1}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={previousStep}
|
||||
class="btn btn-secondary"
|
||||
disabled={false}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</Show>
|
||||
<Show when={currentStep() < TOTAL_STEPS}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleNextStep}
|
||||
class="btn btn-secondary"
|
||||
disabled={!canMakeNextStep()}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</Show>
|
||||
<Show when={currentStep() === TOTAL_STEPS}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleImportCommands}
|
||||
class="btn btn-primary"
|
||||
disabled={false}
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
ImportCommandsModal.Handler = new ModalHandler();
|
||||
ImportCommandsModal.onCommandsImported = (callback) =>
|
||||
eventEmitter.on(COMMANDS_IMPORTED_EVENT, callback);
|
||||
|
||||
export default ImportCommandsModal;
|
||||
@ -10,6 +10,7 @@ import CreateCommandModal from "./create-command-modal.jsx";
|
||||
import DeleteCommandModal from "./delete-command-modal.jsx";
|
||||
import CreateRemoteModal from "./create-remote-modal.jsx";
|
||||
import DeleteRemoteModal from "./delete-remote-modal.jsx";
|
||||
import ImportCommandsModal from "./import-commands-modal.jsx";
|
||||
|
||||
const ModalRegistry = (function () {
|
||||
const modals = [
|
||||
@ -62,6 +63,11 @@ const ModalRegistry = (function () {
|
||||
id: "deleteRemoteModal",
|
||||
component: DeleteRemoteModal,
|
||||
ref: null,
|
||||
},
|
||||
{
|
||||
id: "importCommandsModal",
|
||||
component: ImportCommandsModal,
|
||||
ref: null,
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import Command from "../data/command";
|
||||
import Serializer from "../data/serializer";
|
||||
|
||||
function RemotesService() {
|
||||
let commands = [
|
||||
let _commands = [
|
||||
new Command({
|
||||
id: 1,
|
||||
protocol: "samsung",
|
||||
@ -50,21 +50,31 @@ function RemotesService() {
|
||||
}
|
||||
|
||||
async function getCommands() {
|
||||
return [].concat(commands);
|
||||
return [].concat(_commands);
|
||||
}
|
||||
|
||||
async function createCommand(commandObject) {
|
||||
let command = Serializer.deserializeCommand(commandObject);
|
||||
let id = Math.random().toString(36).substr(2, 9);
|
||||
command.setId(id);
|
||||
commands.push(command);
|
||||
_commands.push(command);
|
||||
return command;
|
||||
}
|
||||
|
||||
async function createCommands(commands) {
|
||||
if (!commands || commands.length === 0) return [];
|
||||
commands.forEach((command) => {
|
||||
let id = Math.random().toString(36).substr(2, 9);
|
||||
command.setId(id);
|
||||
_commands.push(command);
|
||||
});
|
||||
return commands;
|
||||
}
|
||||
|
||||
async function deleteCommand(commandId) {
|
||||
let index = commands.findIndex((command) => command.getId() === commandId);
|
||||
let index = _commands.findIndex((command) => command.getId() === commandId);
|
||||
if (index >= 0) {
|
||||
commands.splice(index, 1);
|
||||
_commands.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,6 +84,7 @@ function RemotesService() {
|
||||
deleteRemote,
|
||||
getCommands,
|
||||
createCommand,
|
||||
createCommands,
|
||||
deleteCommand,
|
||||
};
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import List from "../../components/list";
|
||||
import RemotesService from "../../services/remotes-service";
|
||||
import CreateCommandModal from "../../modals/create-command-modal";
|
||||
import DeleteCommandModal from "../../modals/delete-command-modal";
|
||||
import ImportCommandsModal from "../../modals/import-commands-modal";
|
||||
|
||||
function CommandsList(props) {
|
||||
const [commands, { refetch: refetchCommands }] = createResource(
|
||||
@ -17,6 +18,10 @@ function CommandsList(props) {
|
||||
refetchCommands();
|
||||
});
|
||||
|
||||
ImportCommandsModal.onCommandsImported(() => {
|
||||
refetchCommands();
|
||||
});
|
||||
|
||||
DeleteCommandModal.onCommandDeleted(() => {
|
||||
refetchCommands();
|
||||
});
|
||||
@ -25,11 +30,15 @@ function CommandsList(props) {
|
||||
refetchCommands();
|
||||
CreateCommandModal.Handler.show();
|
||||
}
|
||||
|
||||
|
||||
function handleDeleteCommand(command) {
|
||||
DeleteCommandModal.setCommand(command);
|
||||
DeleteCommandModal.Handler.show();
|
||||
}
|
||||
|
||||
function handleImportCommands() {
|
||||
ImportCommandsModal.Handler.show();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -38,9 +47,13 @@ function CommandsList(props) {
|
||||
{props.navigation ? props.navigation : null}
|
||||
</div>
|
||||
<div class="d-flex flex-row justify-content-end flex-fill">
|
||||
<button class="btn btn-dark me-2 mb-3" onClick={handleImportCommands}>
|
||||
<i class="bi bi-box-arrow-in-down me-2"></i>
|
||||
Import
|
||||
</button>
|
||||
<button class="btn btn-dark me-2 mb-3" onClick={handleNewCommand}>
|
||||
<i class="bi bi-plus-square me-2"></i>
|
||||
New Command
|
||||
New
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user