Merge pull request #419 from trezor/feature/copy-log

Feature/Copy log to clipboard
pull/432/head
Vladimir Volek 5 years ago committed by GitHub
commit bcfca51699
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -39,6 +39,7 @@
"color-hash": "^1.0.3", "color-hash": "^1.0.3",
"commander": "^2.19.0", "commander": "^2.19.0",
"connected-react-router": "6.0.0", "connected-react-router": "6.0.0",
"copy-to-clipboard": "^3.0.8",
"copy-webpack-plugin": "^4.6.0", "copy-webpack-plugin": "^4.6.0",
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"date-fns": "^1.30.1", "date-fns": "^1.30.1",

@ -1,6 +1,7 @@
/* @flow */ /* @flow */
import * as LOG from 'actions/constants/log'; import * as LOG from 'actions/constants/log';
import copy from 'copy-to-clipboard';
import type { Action, ThunkAction, GetState, Dispatch } from 'flowtype'; import type { Action, ThunkAction, GetState, Dispatch } from 'flowtype';
import type { LogEntry } from 'reducers/LogReducer'; import type { LogEntry } from 'reducers/LogReducer';
@ -12,6 +13,12 @@ export type LogAction =
| { | {
type: typeof LOG.CLOSE, type: typeof LOG.CLOSE,
} }
| {
type: typeof LOG.COPY_RESET,
}
| {
type: typeof LOG.COPY_SUCCESS,
}
| { | {
type: typeof LOG.ADD, type: typeof LOG.ADD,
payload: LogEntry, payload: LogEntry,
@ -39,3 +46,24 @@ export const add = (type: string, message: any): Action => ({
message, message,
}, },
}); });
export const copyToClipboard = (): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const { entries } = getState().log;
try {
const res = copy(JSON.stringify(entries));
if (res) {
dispatch({
type: LOG.COPY_SUCCESS,
});
}
} catch (err) {
console.error(err);
}
};
export const resetCopyState = (): Action => ({
type: LOG.COPY_RESET,
});

@ -3,3 +3,5 @@
export const OPEN: 'log__open' = 'log__open'; export const OPEN: 'log__open' = 'log__open';
export const CLOSE: 'log__close' = 'log__close'; export const CLOSE: 'log__close' = 'log__close';
export const ADD: 'log__add' = 'log__add'; export const ADD: 'log__add' = 'log__add';
export const COPY_SUCCESS: 'log__copy_success' = 'log__copy_success';
export const COPY_RESET: 'log__copy_reset' = 'log__copy_reset';

@ -5,6 +5,8 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import colors from 'config/colors'; import colors from 'config/colors';
import { H2 } from 'components/Heading'; import { H2 } from 'components/Heading';
import Button from 'components/Button';
import Tooltip from 'components/Tooltip';
import ReactJson from 'react-json-view'; import ReactJson from 'react-json-view';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import P from 'components/Paragraph'; import P from 'components/Paragraph';
@ -18,6 +20,8 @@ import l10nMessages from './index.messages';
type Props = { type Props = {
log: $ElementType<State, 'log'>, log: $ElementType<State, 'log'>,
toggle: typeof LogActions.toggle, toggle: typeof LogActions.toggle,
copyToClipboard: typeof LogActions.copyToClipboard,
resetCopyState: typeof LogActions.resetCopyState,
}; };
const Wrapper = styled.div` const Wrapper = styled.div`
@ -59,8 +63,20 @@ const LogWrapper = styled.div`
overflow: scroll; overflow: scroll;
`; `;
const CopyWrapper = styled.div``;
const ButtonCopy = styled(Button)`
margin-top: 10px;
`;
const Log = (props: Props): ?React$Element<string> => { const Log = (props: Props): ?React$Element<string> => {
if (!props.log.opened) return null; if (!props.log.opened) return null;
const copyBtn = (
<ButtonCopy onClick={() => props.copyToClipboard()}>
<FormattedMessage {...l10nMessages.TR_COPY_TO_CLIPBOARD} />
</ButtonCopy>
);
return ( return (
<Wrapper> <Wrapper>
<Click onClick={props.toggle}> <Click onClick={props.toggle}>
@ -75,6 +91,19 @@ const Log = (props: Props): ?React$Element<string> => {
<LogWrapper> <LogWrapper>
<ReactJson src={props.log.entries} /> <ReactJson src={props.log.entries} />
</LogWrapper> </LogWrapper>
{props.log.copied ? (
<Tooltip
defaultVisible
maxWidth={285}
placement="top"
content={<FormattedMessage {...l10nMessages.TR_COPIED} />}
afterVisibleChange={props.resetCopyState}
>
{copyBtn}
</Tooltip>
) : (
<CopyWrapper>{copyBtn}</CopyWrapper>
)}
</Wrapper> </Wrapper>
); );
}; };
@ -85,5 +114,7 @@ export default connect(
}), }),
(dispatch: Dispatch) => ({ (dispatch: Dispatch) => ({
toggle: bindActionCreators(LogActions.toggle, dispatch), toggle: bindActionCreators(LogActions.toggle, dispatch),
copyToClipboard: bindActionCreators(LogActions.copyToClipboard, dispatch),
resetCopyState: bindActionCreators(LogActions.resetCopyState, dispatch),
}) })
)(Log); )(Log);

@ -13,6 +13,14 @@ const definedMessages: Messages = defineMessages({
defaultMessage: 'Log', defaultMessage: 'Log',
description: 'application event and error', description: 'application event and error',
}, },
TR_COPY_TO_CLIPBOARD: {
id: 'TR_COPY_TO_CLIPBOARD',
defaultMessage: 'Copy to clipboard',
},
TR_COPIED: {
id: 'TR_COPIED',
defaultMessage: 'Copied!',
},
}); });
export default definedMessages; export default definedMessages;

@ -34,12 +34,15 @@ const Tooltip = ({
readMoreLink, readMoreLink,
children, children,
enterDelayMs, enterDelayMs,
defaultVisible = false,
...rest
}) => ( }) => (
<Wrapper className={className}> <Wrapper className={className}>
<RcTooltip <RcTooltip
arrowContent={<div className="rc-tooltip-arrow-inner" />} arrowContent={<div className="rc-tooltip-arrow-inner" />}
placement={placement} placement={placement}
mouseEnterDelay={enterDelayMs || 0} mouseEnterDelay={enterDelayMs || 0}
defaultVisible={defaultVisible}
overlay={() => ( overlay={() => (
<ContentWrapper> <ContentWrapper>
<Content maxWidth={maxWidth}>{content}</Content> <Content maxWidth={maxWidth}>{content}</Content>
@ -52,6 +55,7 @@ const Tooltip = ({
)} )}
</ContentWrapper> </ContentWrapper>
)} )}
{...rest}
> >
{children} {children}
</RcTooltip> </RcTooltip>
@ -66,6 +70,7 @@ Tooltip.propTypes = {
content: PropTypes.oneOfType([PropTypes.element, PropTypes.string]), content: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
readMoreLink: PropTypes.string, readMoreLink: PropTypes.string,
enterDelayMs: PropTypes.number, enterDelayMs: PropTypes.number,
defaultVisible: PropTypes.bool,
}; };
export default Tooltip; export default Tooltip;

@ -12,11 +12,13 @@ export type LogEntry = {
export type State = { export type State = {
opened: boolean, opened: boolean,
entries: Array<LogEntry>, entries: Array<LogEntry>,
copied: boolean,
}; };
export const initialState: State = { export const initialState: State = {
opened: false, opened: false,
entries: [], entries: [],
copied: false,
}; };
export default (state: State = initialState, action: Action): State => { export default (state: State = initialState, action: Action): State => {
@ -31,6 +33,7 @@ export default (state: State = initialState, action: Action): State => {
return { return {
...state, ...state,
opened: false, opened: false,
copied: false,
}; };
case LOG.ADD: case LOG.ADD:
@ -39,6 +42,18 @@ export default (state: State = initialState, action: Action): State => {
entries: state.entries.concat([action.payload]), entries: state.entries.concat([action.payload]),
}; };
case LOG.COPY_SUCCESS:
return {
...state,
copied: true,
};
case LOG.COPY_RESET:
return {
...state,
copied: false,
};
default: default:
return state; return state;
} }

@ -3114,6 +3114,13 @@ copy-descriptor@^0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
copy-to-clipboard@^3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9"
integrity sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==
dependencies:
toggle-selection "^1.0.3"
copy-webpack-plugin@^4.6.0: copy-webpack-plugin@^4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz#e7f40dd8a68477d405dd1b7a854aae324b158bae" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz#e7f40dd8a68477d405dd1b7a854aae324b158bae"
@ -11076,6 +11083,11 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2" regex-not "^1.0.2"
safe-regex "^1.1.0" safe-regex "^1.1.0"
toggle-selection@^1.0.3:
version "1.0.6"
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
toposort@^1.0.0: toposort@^1.0.0:
version "1.0.7" version "1.0.7"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"

Loading…
Cancel
Save