Skip to content

Commit

Permalink
Added copy/paste function for message and parser configurations in admin
Browse files Browse the repository at this point in the history
  • Loading branch information
crycode-de committed Apr 6, 2021
1 parent 67a463f commit 77b79f9
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 58 deletions.
26 changes: 15 additions & 11 deletions admin/build/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion admin/build/index.js.map

Large diffs are not rendered by default.

153 changes: 122 additions & 31 deletions admin/src/components/message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import ConfirmDialog from '@iobroker/adapter-react/Dialogs/Confirm'
import I18n from '@iobroker/adapter-react/i18n';
import { AppContext } from '../common';

import {
ContentCopyIcon,
ContentPasteIcon,
} from '../lib/icons';

import { internalClipboard } from '../lib/helpers';

import { TabPanel } from './tab-panel';
import { InputCheckbox } from './input-checkbox';
import { InputText } from './input-text';
Expand Down Expand Up @@ -46,6 +53,11 @@ interface MessageProps {
*/
onAdd?: (uuid: string) => void;

/**
* Show a toast message.
*/
showToast?: (text: string) => void;

/**
* The app context.
*/
Expand Down Expand Up @@ -129,29 +141,50 @@ export class Message extends React.Component<MessageProps, MessageState> {
return (
<>
{this.props.onDelete && (
<Fab
size='small'
color='primary'
aria-label='delete'
className={classes.fabTopRight}
title={I18n.t('Remove')}
onClick={() => this.setState({ showRemoveConfirm: true })}
>
<DeleteIcon />
</Fab>
<div className={classes.fabTopRight}>
<Fab
size='small'
color='primary'
aria-label='copy'
title={I18n.t('Copy')}
onClick={this.copy}
>
<ContentCopyIcon />
</Fab>
<Fab
size='small'
color='primary'
aria-label='paste'
title={I18n.t('Paste')}
onClick={this.paste}
disabled={!internalClipboard.message}
>
<ContentPasteIcon />
</Fab>
<Fab
size='small'
color='primary'
aria-label='delete'
title={I18n.t('Remove')}
onClick={() => this.setState({ showRemoveConfirm: true })}
>
<DeleteIcon />
</Fab>
</div>
)}

{this.props.onAdd && (
<Fab
size='small'
color='primary'
aria-label='add'
className={classes.fabTopRight}
title={I18n.t('Add')}
onClick={() => this.props.onAdd && this.props.onAdd(this.props.uuid)}
>
<AddIcon />
</Fab>
<div className={classes.fabTopRight}>
<Fab
size='small'
color='primary'
aria-label='add'
title={I18n.t('Add')}
onClick={() => this.props.onAdd && this.props.onAdd(this.props.uuid)}
>
<AddIcon />
</Fab>
</div>
)}

<h2>{I18n.t('Message')}</h2>
Expand Down Expand Up @@ -269,6 +302,7 @@ export class Message extends React.Component<MessageProps, MessageState> {
onChange={this.onParserChange}
onValidate={this.onParserValidate}
onDelete={this.onParserDelete}
showToast={this.props.showToast}
context={context}
classes={classes}
readonly={this.props.readonly}
Expand Down Expand Up @@ -338,22 +372,29 @@ export class Message extends React.Component<MessageProps, MessageState> {

await new Promise<void>((resolve) => {
this.setState(newState, () => {
if (this.props.onChange) {
this.props.onChange(this.props.uuid, {
id: this.state.id,
name: this.state.name,
dlc: this.state.dlc,
receive: this.state.receive,
send: this.state.send,
autosend: this.state.autosend,
parsers: { ...this.state.parsers },
});
}
this.onChange();
resolve();
});
});
}

/**
* Submit changes to the parent component.
*/
private onChange (): void {
if (this.props.onChange) {
this.props.onChange(this.props.uuid, {
id: this.state.id,
name: this.state.name,
dlc: this.state.dlc,
receive: this.state.receive,
send: this.state.send,
autosend: this.state.autosend,
parsers: { ...this.state.parsers },
});
}
}

/**
* Validate the state of this component.
* If no state is given, the previous results will be used and only the parser
Expand Down Expand Up @@ -483,4 +524,54 @@ export class Message extends React.Component<MessageProps, MessageState> {
}
});
}

/**
* Copy the current configuration (the state) into the internal clipboard.
*/
@autobind
private copy (): void {
internalClipboard.message = JSON.stringify(this.state);

if (this.props.showToast) {
this.props.showToast(I18n.t('Message configuration copied. Use the paste button to paste this configuration to an other message.'));
}
}

/**
* Load the configuration (the state) from the internal clipboard.
*/
@autobind
private paste (): void {
if (!internalClipboard.message) {
if (this.props.showToast) {
this.props.showToast(I18n.t('Nothing to paste. Please use the copy button first.'));
}
return;
}

try {
const ms: MessageState = JSON.parse(internalClipboard.message);

// need to create new parser UUIDs to keep them unique
const parserUuids = Object.keys(ms.parsers);
for (const oldUuid of parserUuids) {
const newUuid = uuidv4();
ms.parsers[newUuid] = ms.parsers[oldUuid];
delete ms.parsers[oldUuid];
}

this.setState(this.validateState({
...ms
}), () => {
this.onChange();
if (this.props.showToast) {
this.props.showToast(I18n.t('Pasted'));
}
});
} catch (err) {
if (this.props.showToast) {
this.props.showToast(I18n.t('Error while pasting: %s', err.toString()));
}
}
}
}
98 changes: 87 additions & 11 deletions admin/src/components/parser.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import React from 'react';
import { autobind } from 'core-decorators';

import Grid from '@material-ui/core/Grid';
import Fab from '@material-ui/core/Fab';
import DeleteIcon from '@material-ui/icons/Delete'
import DeleteIcon from '@material-ui/icons/Delete';

import I18n from '@iobroker/adapter-react/i18n';

import {
ContentCopyIcon,
ContentPasteIcon,
} from '../lib/icons';

import { internalClipboard } from '../lib/helpers';

import { InputText } from './input-text';
import { InputCheckbox } from './input-checkbox';
import { InputSelect } from './input-select';
Expand Down Expand Up @@ -51,6 +59,11 @@ interface ParserProps {
*/
onDelete?: (uuid: string) => void;

/**
* Show a toast message.
*/
showToast?: (text: string) => void;

/**
* The app context.
*/
Expand Down Expand Up @@ -121,16 +134,36 @@ export class Parser extends React.PureComponent<ParserProps, ParserState> {
return (
<>
{this.props.onDelete && (
<Fab
size='small'
color='primary'
aria-label='delete'
className={classes.fabTopRight}
title={I18n.t('Remove')}
onClick={() => this.props.onDelete && this.props.onDelete(this.props.uuid)}
>
<DeleteIcon />
</Fab>
<div className={classes.fabTopRight}>
<Fab
size='small'
color='primary'
aria-label='copy'
title={I18n.t('Copy')}
onClick={this.copy}
>
<ContentCopyIcon />
</Fab>
<Fab
size='small'
color='primary'
aria-label='paste'
title={I18n.t('Paste')}
onClick={this.paste}
disabled={!internalClipboard.parser}
>
<ContentPasteIcon />
</Fab>
<Fab
size='small'
color='primary'
aria-label='delete'
title={I18n.t('Remove')}
onClick={() => this.props.onDelete && this.props.onDelete(this.props.uuid)}
>
<DeleteIcon />
</Fab>
</div>
)}

<Grid container spacing={3}>
Expand Down Expand Up @@ -440,4 +473,47 @@ export class Parser extends React.PureComponent<ParserProps, ParserState> {
this.props.onValidate(this.props.uuid, isValid);
return state;
}

/**
* Copy the current configuration (the state) into the internal clipboard.
*/
@autobind
private copy (): void {
internalClipboard.parser = JSON.stringify(this.state);

if (this.props.showToast) {
this.props.showToast(I18n.t('Parser configuration copied. Use the paste button to paste this configuration to an other parser.'));
}
}

/**
* Load the configuration (the state) from the internal clipboard.
*/
@autobind
private paste (): void {
if (!internalClipboard.parser) {
if (this.props.showToast) {
this.props.showToast(I18n.t('Nothing to paste. Please use the copy button first.'));
}
return;
}

try {
const ps: ParserState = JSON.parse(internalClipboard.parser);

this.setState(this.validateState({
...ps
}), () => {
this.onChange();
if (this.props.showToast) {
this.props.showToast(I18n.t('Pasted'));
}
});
} catch (err) {
if (this.props.showToast) {
this.props.showToast(I18n.t('Error while pasting: %s', err.toString()));
}
}
}

}
4 changes: 4 additions & 0 deletions admin/src/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ const styles = (theme: Theme): Record<string, CreateCSSProperties> => ({
overflowY: 'auto'
},
fabTopRight: {
'& > button': {
margin: theme.spacing(1),
},
position: 'absolute',
top: theme.spacing(2),
right: theme.spacing(2)
Expand Down Expand Up @@ -265,6 +268,7 @@ class Settings extends React.Component<SettingsProps, SettingsState> {
onChange={this.onMessageChange}
onDelete={this.onMessageDelete}
onValidate={this.onMessageValidate}
showToast={this.props.showToast}
/>
</TabPanel>
))}
Expand Down
9 changes: 8 additions & 1 deletion admin/src/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,12 @@
"Validation error:": "Validierungsfehler:",
"Ignore this error and try to import": "Diesen Fehler ignorieren und den Import versuchen",
"Import done": "Import abgeschlossen",
"The downloaded index is not valid": "Der heruntergeladene Index ist fehlerhaft"
"The downloaded index is not valid": "Der heruntergeladene Index ist fehlerhaft",
"Copy": "Kopieren",
"Paste": "Einfügen",
"Message configuration copied. Use the paste button to paste this configuration to an other message.": "Nachrichtenkonfiguration kopiert. Verwende den Einfügen-Knopf, um diese Konfiguration bei einer anderen Nachricht einzufügen.",
"Parser configuration copied. Use the paste button to paste this configuration to an other parser.": "Parserkonfiguration kopiert. Verwende den Einfügen-Knopf, um diese Konfiguration bei einem anderen Parser einzufügen.",
"Nothing to paste. Please use the copy button first.": "Nichts zum Einfügen. Bitte erst den Kopieren-Knopf verwenden.",
"Pasted": "Eingefügt",
"Error while pasting: %s": "Fehler beim Einfügen: %s"
}
9 changes: 8 additions & 1 deletion admin/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,12 @@
"Validation error:": "Validation error:",
"Ignore this error and try to import": "Ignore this error and try to import",
"Import done": "Import done",
"The downloaded index is not valid": "The downloaded index is not valid"
"The downloaded index is not valid": "The downloaded index is not valid",
"Copy": "Copy",
"Paste": "Paste",
"Message configuration copied. Use the paste button to paste this configuration to an other message.": "Message configuration copied. Use the paste button to paste this configuration to an other message.",
"Parser configuration copied. Use the paste button to paste this configuration to an other parser.": "Parser configuration copied. Use the paste button to paste this configuration to an other parser.",
"Nothing to paste. Please use the copy button first.": "Nothing to paste. Please use the copy button first.",
"Pasted": "Pasted",
"Error while pasting: %s": "Error while pasting: %s"
}
9 changes: 8 additions & 1 deletion admin/src/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,12 @@
"Validation error:": "Erreur de validation:",
"Ignore this error and try to import": "Ignorez cette erreur et essayez d'importer",
"Import done": "Importation terminée",
"The downloaded index is not valid": "L'index téléchargé n'est pas valide"
"The downloaded index is not valid": "L'index téléchargé n'est pas valide",
"Copy": "Copie",
"Paste": "Insérer",
"Message configuration copied. Use the paste button to paste this configuration to an other message.": "Configuration du message copiée. Utilisez le bouton Coller pour coller cette configuration dans un autre message.",
"Parser configuration copied. Use the paste button to paste this configuration to an other parser.": "Configuration de l'analyseur copiée. Utilisez le bouton Coller pour coller cette configuration dans un autre analyseur.",
"Nothing to paste. Please use the copy button first.": "Rien à insérer. Veuillez d'abord utiliser le bouton de copie.",
"Pasted": "Inséré",
"Error while pasting: %s": "Erreur d'insertion: %s"
}
Loading

0 comments on commit 77b79f9

Please sign in to comment.