mirror of
https://github.com/trezor/trezor-wallet
synced 2024-11-24 01:08:27 +00:00
Merge branch 'styled-components-refactor' of https://github.com/satoshilabs/trezor-wallet into styled-components-refactor
This commit is contained in:
commit
db2008811d
@ -54,6 +54,7 @@
|
||||
"react-scale-text": "^1.2.2",
|
||||
"react-select": "2.0.0",
|
||||
"react-transition-group": "^2.2.1",
|
||||
"redbox-react": "^1.6.0",
|
||||
"redux": "4.0.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-raven-middleware": "^1.2.0",
|
||||
|
@ -14,18 +14,62 @@ const Wrapper = styled.button`
|
||||
background: ${colors.GREEN_PRIMARY};
|
||||
color: ${colors.WHITE};
|
||||
border: 0;
|
||||
|
||||
&:hover {
|
||||
background: ${colors.GREEN_SECONDARY};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: ${colors.GREEN_TERTIARY};
|
||||
}
|
||||
|
||||
${props => props.disabled && css`
|
||||
pointer-events: none;
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
background: ${colors.GRAY_LIGHT};
|
||||
`}
|
||||
|
||||
${props => props.isBlue && css`
|
||||
background: transparent;
|
||||
border: 1px solid ${colors.INFO_PRIMARY};
|
||||
color: ${colors.INFO_PRIMARY};
|
||||
padding: 12px 58px;
|
||||
|
||||
&:hover {
|
||||
color: ${colors.WHITE};
|
||||
background: ${colors.INFO_PRIMARY};
|
||||
}
|
||||
`}
|
||||
|
||||
${props => props.isWhite && css`
|
||||
background: @color_white;
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
border: 1px solid ${colors.DIVIDER};
|
||||
|
||||
&:hover {
|
||||
color: ${colors.TEXT_PRIMARY};
|
||||
border-color: ${colors.TEXT_PRIMARY};
|
||||
background: ${colors.DIVIDER};
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: ${colors.TEXT_PRIMARY};
|
||||
background: ${colors.DIVIDER};
|
||||
}
|
||||
`}
|
||||
|
||||
${props => props.isTransparent && css`
|
||||
background: transparent;
|
||||
border: 0px;
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
color: ${colors.TEXT_PRIMARY};
|
||||
background: transparent;
|
||||
}
|
||||
`}
|
||||
|
||||
${props => props.isWebUsb && css`
|
||||
position: relative;
|
||||
padding: 12px 24px 12px 40px;
|
||||
@ -81,7 +125,7 @@ const IconWrapper = styled.span`
|
||||
`;
|
||||
|
||||
const Button = ({
|
||||
className, text, icon, onClick = () => { }, disabled, isBlue = false, isWhite = false, isWebUsb = false,
|
||||
className, text, icon, onClick = () => { }, disabled, isBlue = false, isWhite = false, isWebUsb = false, isTransparent = false,
|
||||
}) => (
|
||||
<Wrapper
|
||||
className={className}
|
||||
@ -91,6 +135,7 @@ const Button = ({
|
||||
isBlue={isBlue}
|
||||
isWhite={isWhite}
|
||||
isWebUsb={isWebUsb}
|
||||
isTransparent={isTransparent}
|
||||
>
|
||||
{icon && (
|
||||
<IconWrapper>
|
||||
@ -112,6 +157,7 @@ Button.propTypes = {
|
||||
isBlue: PropTypes.bool,
|
||||
isWhite: PropTypes.bool,
|
||||
isWebUsb: PropTypes.bool,
|
||||
isTransparent: PropTypes.bool,
|
||||
icon: PropTypes.shape({
|
||||
type: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
color: PropTypes.string,
|
||||
|
@ -24,9 +24,12 @@ const LayoutWrapper = styled.div`
|
||||
height: 100%;
|
||||
max-width: 1170px;
|
||||
margin: 0 auto;
|
||||
padding: 0 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (max-width: 1170px) {
|
||||
padding: 0 25px;
|
||||
}
|
||||
`;
|
||||
|
||||
const A = styled.a`
|
||||
|
@ -1,39 +1,62 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
|
||||
const Icon = ({ icon, size = 32, color = 'black' }) => {
|
||||
const styles = {
|
||||
svg: {
|
||||
// TODO: make animation of icons better
|
||||
const rotate180up = keyframes`
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
`;
|
||||
|
||||
const rotate180down = keyframes`
|
||||
from {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
`;
|
||||
|
||||
const SvgWrapper = styled.svg`
|
||||
animation: ${props => (props.canAnimate ? (props.isActive ? rotate180up : rotate180down) : null)} 0.2s linear 1 forwards;
|
||||
`;
|
||||
|
||||
const Path = styled.path``;
|
||||
|
||||
const Icon = ({
|
||||
icon, size = 32, color = 'black', isActive, canAnimate,
|
||||
}) => (
|
||||
<SvgWrapper
|
||||
canAnimate={canAnimate}
|
||||
isActive={isActive}
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'middle',
|
||||
},
|
||||
path: {
|
||||
fill: color,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<svg
|
||||
style={styles.svg}
|
||||
width={`${size}`}
|
||||
height={`${size}`}
|
||||
viewBox="0 0 1024 1024"
|
||||
>
|
||||
{icon.map(iconPath => (
|
||||
<path
|
||||
key={iconPath}
|
||||
style={styles.path}
|
||||
d={iconPath}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
}}
|
||||
width={`${size}`}
|
||||
height={`${size}`}
|
||||
viewBox="0 0 1024 1024"
|
||||
>
|
||||
{icon.map(path => (
|
||||
<Path
|
||||
key={path}
|
||||
isActive={isActive}
|
||||
style={{ fill: color }}
|
||||
d={path}
|
||||
/>
|
||||
))}
|
||||
</SvgWrapper>
|
||||
);
|
||||
|
||||
Icon.propTypes = {
|
||||
canAnimate: PropTypes.bool,
|
||||
icon: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
size: PropTypes.number,
|
||||
isActive: PropTypes.bool,
|
||||
color: PropTypes.string,
|
||||
};
|
||||
|
||||
|
@ -1,23 +1,70 @@
|
||||
/* @flow */
|
||||
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import colors from 'config/colors';
|
||||
import { H2 } from 'components/Heading';
|
||||
import Icon from 'components/Icon';
|
||||
import P from 'components/P';
|
||||
|
||||
import * as LogActions from 'actions/LogActions';
|
||||
import type { State, Dispatch } from 'flowtype';
|
||||
import icons from 'config/icons';
|
||||
import { State, Dispatch } from 'flowtype';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
color: ${colors.INFO_PRIMARY};
|
||||
background: ${colors.INFO_SECONDARY};
|
||||
padding: 24px 48px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
const Click = styled.div`
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 0;
|
||||
padding: 12px;
|
||||
color: inherit;
|
||||
transition: opacity 0.3s;
|
||||
|
||||
&:active,
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
color: inherit;
|
||||
}
|
||||
`;
|
||||
|
||||
const Textarea = styled.textarea`
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
min-height: 200px;
|
||||
resize: vertical;
|
||||
font-size: 10px;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledP = styled(P)`
|
||||
margin: 10px 0;
|
||||
`;
|
||||
|
||||
const Log = (props: Props): ?React$Element<string> => {
|
||||
if (!props.log.opened) return null;
|
||||
return (
|
||||
<div className="log">
|
||||
<button className="log-close transparent" onClick={props.toggle} />
|
||||
<Wrapper>
|
||||
<Click onClick={props.toggle}>
|
||||
<Icon size={25} color={colors.INFO_PRIMARY} icon={icons.CLOSE} />
|
||||
</Click>
|
||||
<H2>Log</H2>
|
||||
<p>Attention: The log contains your XPUBs. Anyone with your XPUBs can see your account history.</p>
|
||||
<textarea value={JSON.stringify(props.log.entries)} readOnly />
|
||||
</div>
|
||||
<StyledP>Attention: The log contains your XPUBs. Anyone with your XPUBs can see your account history.</StyledP>
|
||||
<Textarea value={JSON.stringify(props.log.entries)} readOnly />
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
18
src/js/components/P/index.js
Normal file
18
src/js/components/P/index.js
Normal file
@ -0,0 +1,18 @@
|
||||
import styled from 'styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
const Wrapper = styled.p`
|
||||
`;
|
||||
|
||||
const P = ({ children, className }) => (
|
||||
<Wrapper className={className}>{children}</Wrapper>
|
||||
);
|
||||
|
||||
P.propTypes = {
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
||||
export default P;
|
BIN
src/js/components/TrezorImage/images/trezor-1.png
Normal file
BIN
src/js/components/TrezorImage/images/trezor-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
BIN
src/js/components/TrezorImage/images/trezor-T.png
Normal file
BIN
src/js/components/TrezorImage/images/trezor-T.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
22
src/js/components/TrezorImage/index.js
Normal file
22
src/js/components/TrezorImage/index.js
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Wrapper = styled.div``;
|
||||
|
||||
const Img = styled.img`
|
||||
width: ${props => (props.model === 'T' ? '17px' : '13px')};
|
||||
`;
|
||||
|
||||
const TrezorImage = ({ model }) => (
|
||||
<Wrapper>
|
||||
<Img model={model} src={model === 'T' ? './images/trezor-T.png' : './images/trezor-1.png'} />
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
TrezorImage.propTypes = {
|
||||
model: PropTypes.string,
|
||||
status: PropTypes.string,
|
||||
};
|
||||
|
||||
export default TrezorImage;
|
@ -1,7 +1,8 @@
|
||||
export default {
|
||||
WHITE: '#FFFFFF',
|
||||
BACKGROUND: '#EBEBEB',
|
||||
|
||||
HEADER: '#212121',
|
||||
HEADER: '#1A1A1A',
|
||||
BODY: '#E3E3E3',
|
||||
MAIN: '#FBFBFB',
|
||||
LANDING: '#F9F9F9',
|
||||
|
@ -1,4 +1,7 @@
|
||||
export default {
|
||||
CLOSE: [
|
||||
'M754.816 689.92c17.6 17.6 17.6 46.72 0 64.64-8.96 8.64-20.48 13.44-32.64 13.44s-23.68-4.8-32.32-13.44l-177.888-177.92-177.888 177.92c-16.32 16.96-47.040 17.6-64.64 0-17.92-17.92-17.92-47.040 0-64.64l178.208-177.92-178.208-177.92c-17.92-17.92-17.92-46.72 0-64.64 17.28-17.28 47.36-17.28 64.64 0l177.888 177.92 177.888-177.92c17.92-17.92 47.040-17.92 64.96 0 17.6 17.92 17.6 46.72 0 64.64l-178.24 177.92 178.24 177.92z',
|
||||
],
|
||||
DOWNLOAD: [
|
||||
'M346.56 410.24c3.255-1.423 7.047-2.252 11.033-2.252 0.284 0 0.566 0.004 0.848 0.013l-0.041-0.001c8.323 0.531 15.657 4.371 20.77 10.206l0.030 0.034 93.44 109.44c0.378 0.735 1.131 1.229 1.999 1.229 1.237 0 2.24-1.003 2.24-2.24 0-0.209-0.029-0.412-0.083-0.605l0.004 0.016v-233.28c-0.102-0.987-0.16-2.132-0.16-3.291 0-18.733 15.187-33.92 33.92-33.92s33.92 15.187 33.92 33.92c0 1.159-0.058 2.304-0.172 3.433l0.012-0.142v236.16c-0.050 0.177-0.079 0.379-0.079 0.589 0 1.237 1.003 2.24 2.24 2.24 0.868 0 1.621-0.494 1.993-1.216l0.006-0.013 88.32-104.32c5.204-6.343 13.042-10.358 21.819-10.358 7.711 0 14.699 3.099 19.784 8.121l-0.003-0.003c6.16 5.845 9.993 14.090 9.993 23.231 0 8.17-3.062 15.625-8.101 21.28l0.028-0.032-146.56 173.44c-5.311 6.15-13.061 10.069-21.731 10.24h-0.029c-8.727-0.036-16.523-3.991-21.724-10.196l-0.036-0.044-152-178.56c-5.441-6.124-8.764-14.234-8.764-23.121 0-12.698 6.785-23.81 16.927-29.911l0.157-0.088z',
|
||||
'M694.72 731.2c0.024 0.488 0.038 1.060 0.038 1.635 0 18.891-14.881 34.306-33.561 35.163l-0.077 0.003h-292.48c-18.795-1.81-33.372-17.523-33.372-36.64s14.577-34.83 33.222-36.628l0.15-0.012h292.48c18.751 0.866 33.625 16.278 33.625 35.165 0 0.463-0.009 0.923-0.027 1.381l0.002-0.066z',
|
||||
@ -9,6 +12,9 @@ export default {
|
||||
ARROW_LEFT: [
|
||||
'M603.072 757.216l-237.44-219.616c-8.576-8.128-13.632-19.296-13.632-31.040 0.288-11.744 5.056-23.2 13.664-31.040l227.040-208.768c16.928-15.36 43.040-14.176 58.176 3.008 15.424 16.864 13.952 43.392-2.656 59.040l-193.504 177.76 203.904 188.608c8 7.52 12.768 18.080 13.344 29.216 0.608 11.456-3.264 21.696-10.688 30.112-15.456 16.896-41.568 18.080-58.208 2.72z',
|
||||
],
|
||||
ARROW_DOWN: [
|
||||
'M757.216 420.928l-219.616 237.44c-8.128 8.576-19.296 13.632-31.040 13.632-11.744-0.288-23.2-5.056-31.040-13.664l-208.768-227.040c-15.36-16.928-14.176-43.040 3.008-58.176 16.864-15.424 43.392-13.952 59.040 2.656l177.76 193.504 188.608-203.904c7.52-8 18.080-12.768 29.216-13.344 11.456-0.608 21.696 3.264 30.112 10.688 16.896 15.456 18.080 41.568 2.72 58.208z',
|
||||
],
|
||||
CHAT: [
|
||||
'M580.992 256h-137.984c-103.296 0-187.008 85.952-187.008 192 0 96.608 69.536 176.32 160 189.792v130.208l128-128h36.992c103.296 0 187.008-85.952 187.008-192s-83.712-192-187.008-192z',
|
||||
],
|
||||
|
@ -1,23 +1,19 @@
|
||||
import React, { Component } from 'react';
|
||||
import RedBox from 'redbox-react';
|
||||
|
||||
class ErrorBoundary extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
this.state = { hasError: false, error: false };
|
||||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
// Display fallback UI
|
||||
this.setState({ hasError: true });
|
||||
console.error('error', error);
|
||||
// You can also log the error to an error reporting service
|
||||
// logErrorToMyService(error, info);
|
||||
this.setState({ hasError: true, error });
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// You can render any custom fallback UI
|
||||
return <div>Something went wrong.</div>;
|
||||
return <RedBox error={this.state.error} />;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
|
88
src/js/utils/device.js
Normal file
88
src/js/utils/device.js
Normal file
@ -0,0 +1,88 @@
|
||||
import colors from 'js/config/colors';
|
||||
|
||||
const getStatus = (device) => {
|
||||
let status = 'connected';
|
||||
if (!device.connected) {
|
||||
status = 'disconnected';
|
||||
} else if (!device.available) {
|
||||
status = 'unavailable';
|
||||
} else if (device.type === 'acquired') {
|
||||
if (device.status === 'occupied') {
|
||||
status = 'used-in-other-window';
|
||||
}
|
||||
} else if (device.type === 'unacquired') {
|
||||
status = 'unacquired';
|
||||
}
|
||||
|
||||
return status;
|
||||
};
|
||||
|
||||
const getStatusName = (deviceStatus) => {
|
||||
let statusName;
|
||||
switch (deviceStatus) {
|
||||
case 'used-in-other-window':
|
||||
statusName = 'Used in other window';
|
||||
break;
|
||||
case 'connected':
|
||||
statusName = 'Connected';
|
||||
break;
|
||||
case 'disconnected':
|
||||
statusName = 'Disconnected';
|
||||
break;
|
||||
case 'unacquired':
|
||||
statusName = 'Used in other window';
|
||||
break;
|
||||
case 'unavailable':
|
||||
statusName = 'Unavailable';
|
||||
break;
|
||||
default:
|
||||
statusName = 'Status unknown';
|
||||
}
|
||||
return statusName;
|
||||
};
|
||||
|
||||
const isWebUSB = transport => !!((transport && transport.version.indexOf('webusb') >= 0));
|
||||
|
||||
const isDisabled = (selectedDevice, devices, transport) => (devices.length < 1 && !isWebUSB(transport)) || (devices.length === 1 && !selectedDevice.features && !isWebUSB(transport));
|
||||
|
||||
const getVersion = (device) => {
|
||||
let version;
|
||||
if (device.features && device.features.major_version > 1) {
|
||||
version = 'T';
|
||||
} else {
|
||||
version = '1';
|
||||
}
|
||||
return version;
|
||||
};
|
||||
|
||||
const getStatusColor = (deviceStatus) => {
|
||||
let color;
|
||||
switch (deviceStatus) {
|
||||
case 'used-in-other-window':
|
||||
color = colors.WARNING_PRIMARY;
|
||||
break;
|
||||
case 'connected':
|
||||
color = colors.GREEN_PRIMARY;
|
||||
break;
|
||||
case 'unacquired':
|
||||
color = colors.WARNING_PRIMARY;
|
||||
break;
|
||||
case 'disconnected':
|
||||
color = colors.ERROR_PRIMARY;
|
||||
break;
|
||||
case 'unavailable':
|
||||
color = colors.ERROR_PRIMARY;
|
||||
break;
|
||||
default:
|
||||
color = colors.TEXT_PRIMARY;
|
||||
}
|
||||
return color;
|
||||
};
|
||||
|
||||
export {
|
||||
getStatus,
|
||||
isDisabled,
|
||||
getStatusName,
|
||||
getVersion,
|
||||
getStatusColor,
|
||||
};
|
@ -0,0 +1,156 @@
|
||||
import React, { Component } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from 'components/Icon';
|
||||
import icons from 'config/icons';
|
||||
import { getStatusColor, getStatusName } from 'utils/device';
|
||||
import TrezorImage from 'components/TrezorImage';
|
||||
|
||||
import colors from 'config/colors';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
height: 64px;
|
||||
width: 320px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: ${colors.WHITE};
|
||||
border-radius: 4px 0 0 0;
|
||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.04);
|
||||
|
||||
${props => props.isOpen && css`
|
||||
box-shadow: none;
|
||||
`}
|
||||
`;
|
||||
|
||||
const ClickWrapper = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding-left: 25px;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
${props => props.disabled && css`
|
||||
cursor: initial;
|
||||
`}
|
||||
`;
|
||||
|
||||
const LabelWrapper = styled.div`
|
||||
flex: 1;
|
||||
padding-left: 18px;
|
||||
`;
|
||||
|
||||
const Name = styled.div`
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: no-wrap;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: ${colors.TEXT_PRIMARY};
|
||||
`;
|
||||
|
||||
const Status = styled.div`
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
`;
|
||||
|
||||
const Counter = styled.div`
|
||||
border: 1px solid ${colors.DIVIDER};
|
||||
border-radius: 50%;
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
const IconWrapper = styled.div`
|
||||
padding-right: 25px;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const ImageWrapper = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const Dot = styled.div`
|
||||
border: 2px solid ${colors.WHITE};
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
background: ${props => props.color};
|
||||
top: -4px;
|
||||
right: -3px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
`;
|
||||
|
||||
class DeviceHeader extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
clicked: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
this.setState({ clicked: true });
|
||||
if (!this.props.disabled) {
|
||||
this.props.handleOpen();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
status, label, deviceCount, isOpen, trezorModel, disabled,
|
||||
} = this.props;
|
||||
return (
|
||||
<Wrapper isOpen={isOpen}>
|
||||
<ClickWrapper disabled={disabled} onClick={() => this.handleClick()}>
|
||||
<ImageWrapper>
|
||||
<Dot color={getStatusColor(status)} />
|
||||
<TrezorImage model={trezorModel} />
|
||||
</ImageWrapper>
|
||||
<LabelWrapper>
|
||||
<Name>{label}</Name>
|
||||
<Status>{getStatusName(status)}</Status>
|
||||
</LabelWrapper>
|
||||
<IconWrapper>
|
||||
{deviceCount > 1 && <Counter>{deviceCount}</Counter>}
|
||||
{!disabled && (
|
||||
<Icon
|
||||
canAnimate={this.state.clicked === true}
|
||||
isActive={isOpen}
|
||||
size={25}
|
||||
color={colors.TEXT_SECONDARY}
|
||||
icon={icons.ARROW_DOWN}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</IconWrapper>
|
||||
</ClickWrapper>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DeviceHeader.propTypes = {
|
||||
deviceCount: PropTypes.number,
|
||||
disabled: PropTypes.bool,
|
||||
isOpen: PropTypes.bool,
|
||||
trezorModel: PropTypes.string.isRequired,
|
||||
handleOpen: PropTypes.func.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DeviceHeader;
|
@ -1,33 +1,21 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import deviceConstants from 'constants/device';
|
||||
import { getStatus } from 'utils/device';
|
||||
|
||||
const Wrapper = styled.div``;
|
||||
|
||||
class DeviceList {
|
||||
getStatus(device) {
|
||||
let deviceStatus = '';
|
||||
if (device.type === 'unacquired' || (device.features && device.status === 'occupied')) {
|
||||
deviceStatus = 'Used in other window';
|
||||
} else if (device.type === 'unreadable') {
|
||||
deviceStatus = 'Connected';
|
||||
} else if (!device.connected) {
|
||||
deviceStatus = 'Disconnected';
|
||||
} else if (!device.available) {
|
||||
deviceStatus = 'Unavailable';
|
||||
}
|
||||
|
||||
return deviceStatus;
|
||||
}
|
||||
|
||||
class DeviceList extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Wrapper>
|
||||
{this.props.devices.map((device, index) => (
|
||||
<div key={index} className={css} onClick={() => this.props.onSelectDevice(device)}>
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => this.props.onSelectDevice(device)}
|
||||
>
|
||||
<div className="label-container">
|
||||
<span className="label">{device.instanceLabel}</span>
|
||||
<span className="status">{this.getStatus(device)}</span>
|
||||
<span className="status">{getStatus(device)}</span>
|
||||
</div>
|
||||
<div
|
||||
className="forget-button"
|
||||
|
@ -1,66 +1,39 @@
|
||||
/* @flow */
|
||||
import React, { Component } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import TrezorConnect from 'trezor-connect';
|
||||
|
||||
import type { TrezorDevice } from 'flowtype';
|
||||
import { getStatus, getVersion, isDisabled } from 'utils/device';
|
||||
|
||||
import DeviceHeader from './components/DeviceHeader';
|
||||
|
||||
// import DeviceList from './components/DeviceList';
|
||||
import type { Props } from '../common';
|
||||
|
||||
import AsideDivider from '../Divider';
|
||||
|
||||
const Wrapper = styled.div``;
|
||||
|
||||
export const DeviceSelect = (props: Props) => {
|
||||
const { devices } = props;
|
||||
const { transport } = props.connect;
|
||||
|
||||
const selected: ?TrezorDevice = props.wallet.selectedDevice;
|
||||
if (!selected) return null;
|
||||
|
||||
let deviceStatus: string = 'Connected';
|
||||
let css: string = 'device-select device';
|
||||
if (props.deviceDropdownOpened) css += ' opened';
|
||||
|
||||
if (!selected.connected) {
|
||||
css += ' disconnected';
|
||||
deviceStatus = 'Disconnected';
|
||||
} else if (!selected.available) {
|
||||
css += ' unavailable';
|
||||
deviceStatus = 'Unavailable';
|
||||
} else if (selected.type === 'acquired') {
|
||||
if (selected.status === 'occupied') {
|
||||
css += ' used-elsewhere';
|
||||
deviceStatus = 'Used in other window';
|
||||
} else if (selected.status === 'used') {
|
||||
css += ' reload-features';
|
||||
}
|
||||
} else if (selected.type === 'unacquired') {
|
||||
css += ' unacquired';
|
||||
deviceStatus = 'Used in other window';
|
||||
}
|
||||
|
||||
if (selected.features && selected.features.major_version > 1) {
|
||||
css += ' trezor-t';
|
||||
}
|
||||
const { selectedDevice } = props.wallet;
|
||||
const disabled = isDisabled(selectedDevice, devices, transport);
|
||||
|
||||
const handleOpen = () => {
|
||||
props.toggleDeviceDropdown(!props.deviceDropdownOpened);
|
||||
};
|
||||
|
||||
const deviceCount = devices.length;
|
||||
const webusb: boolean = !!((transport && transport.version.indexOf('webusb') >= 0));
|
||||
const disabled: boolean = (devices.length < 1 && !webusb) || (devices.length === 1 && !selected.features && !webusb);
|
||||
if (disabled) {
|
||||
css += ' disabled';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={css} onClick={!disabled ? handleOpen : null}>
|
||||
<div className="label-container">
|
||||
<span className="label">{selected.instanceLabel}</span>
|
||||
<span className="status">{deviceStatus}</span>
|
||||
</div>
|
||||
{deviceCount > 1 ? <div className="counter">{deviceCount}</div> : null}
|
||||
<div className="arrow" />
|
||||
</div>
|
||||
<DeviceHeader
|
||||
handleOpen={handleOpen}
|
||||
disabled={disabled}
|
||||
label={selectedDevice.instanceLabel}
|
||||
status={getStatus(selectedDevice)}
|
||||
deviceCount={devices.length}
|
||||
isOpen={props.deviceDropdownOpened}
|
||||
trezorModel={getVersion(selectedDevice)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -172,7 +145,7 @@ export class DeviceDropdown extends Component<Props> {
|
||||
const sortByInstance = (a: TrezorDevice, b: TrezorDevice) => {
|
||||
if (!a.instance || !b.instance) return -1;
|
||||
return a.instance > b.instance ? 1 : -1;
|
||||
}
|
||||
};
|
||||
const deviceList = devices.sort(sortByInstance).map((dev, index) => {
|
||||
if (dev === selected) return null;
|
||||
|
||||
@ -214,14 +187,14 @@ export class DeviceDropdown extends Component<Props> {
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Wrapper>
|
||||
{currentDeviceMenu}
|
||||
{deviceList.length > 1 ? <AsideDivider textLeft="Other devices" /> : null}
|
||||
{this.props.devices.length > 1 ? <AsideDivider textLeft="Other devices" /> : null}
|
||||
{/* <DeviceList devices={devices} /> */}
|
||||
{deviceList}
|
||||
{webUsbButton}
|
||||
</React.Fragment>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,9 @@ const StickyBottom = styled.div`
|
||||
border-right: 1px solid ${colors.DIVIDER};
|
||||
`;
|
||||
|
||||
const MenuWrapper = styled.div``;
|
||||
const MenuWrapper = styled.div`
|
||||
background: ${colors.LANDING};
|
||||
`;
|
||||
|
||||
const Help = styled.div`
|
||||
display: flex;
|
||||
@ -143,12 +145,13 @@ class LeftNavigation extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedDevice } = this.props.wallet;
|
||||
return (
|
||||
<StickyContainer
|
||||
location={this.props.location.pathname}
|
||||
deviceSelection={this.props.deviceDropdownOpened}
|
||||
>
|
||||
<DeviceSelect {...this.props} />
|
||||
{selectedDevice && <DeviceSelect {...this.props} />}
|
||||
<MenuWrapper>
|
||||
{this.state.shouldRenderDeviceSelection && <DeviceDropdown {...this.props} />}
|
||||
{this.shouldRenderAccounts() && this.getMenuTransition(<AccountMenu {...this.props} />)}
|
||||
|
@ -1,6 +1,8 @@
|
||||
/* @flow */
|
||||
|
||||
import * as React from 'react';
|
||||
import colors from 'config/colors';
|
||||
import styled from 'styled-components';
|
||||
import { connect } from 'react-redux';
|
||||
import { Route, withRouter } from 'react-router-dom';
|
||||
|
||||
@ -26,11 +28,43 @@ type ContentProps = {
|
||||
children?: React.Node
|
||||
}
|
||||
|
||||
const AppWrapper = styled.div`
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
min-width: 720px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: ${colors.BACKGROUND};
|
||||
|
||||
&.resized {
|
||||
// to make sure that unpacked coin menu will not overflow main container
|
||||
// 512 dropdown height + 50 header + 30 margin + 64 topnav height
|
||||
min-height: 680px;
|
||||
}
|
||||
`;
|
||||
|
||||
const WalletWrapper = styled.div`
|
||||
width: 100%;
|
||||
max-width: 1170px;
|
||||
margin: 0 auto;
|
||||
flex: 1;
|
||||
background: ${colors.WHITE};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-radius: 4px 4px 0px 0px;
|
||||
overflow: hidden;
|
||||
margin-top: 32px;
|
||||
|
||||
@media screen and (max-width: 1170px) {
|
||||
border-radius: 0px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Wallet = (props: WalletContainerProps) => (
|
||||
<div className="app">
|
||||
<AppWrapper>
|
||||
<Header />
|
||||
{/* <div>{ props.wallet.online ? "ONLINE" : "OFFLINE" }</div> */}
|
||||
<main>
|
||||
<WalletWrapper>
|
||||
<LeftNavigation />
|
||||
<article>
|
||||
<nav>
|
||||
@ -42,9 +76,9 @@ const Wallet = (props: WalletContainerProps) => (
|
||||
{ props.children }
|
||||
<Footer />
|
||||
</article>
|
||||
</main>
|
||||
</WalletWrapper>
|
||||
<ModalContainer />
|
||||
</div>
|
||||
</AppWrapper>
|
||||
);
|
||||
|
||||
const mapStateToProps: MapStateToProps<State, {}, WalletContainerProps> = (state: State, own: {}): WalletContainerProps => ({
|
||||
|
@ -1,7 +1,9 @@
|
||||
/* @flow */
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import colors from 'config/colors';
|
||||
import { Notification } from 'components/Notification';
|
||||
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
||||
|
||||
@ -12,6 +14,13 @@ type Props = {
|
||||
acquireDevice: typeof TrezorConnectActions.acquire
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
background: ${colors.WHITE};
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const Acquire = (props: Props) => {
|
||||
const actions = props.acquiring ? [] : [
|
||||
{
|
||||
@ -23,7 +32,7 @@ const Acquire = (props: Props) => {
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="acquire">
|
||||
<Wrapper>
|
||||
<Notification
|
||||
title="Device is used in other window"
|
||||
message="Do you want to use your device in this window?"
|
||||
@ -31,7 +40,7 @@ const Acquire = (props: Props) => {
|
||||
cancelable={false}
|
||||
actions={actions}
|
||||
/>
|
||||
</section>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,17 +1,41 @@
|
||||
/* @flow */
|
||||
import styled from 'styled-components';
|
||||
import { H2 } from 'components/Heading';
|
||||
import Button from 'components/Button';
|
||||
import P from 'components/P';
|
||||
import React from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Row = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const A = styled.a``;
|
||||
|
||||
const StyledP = styled(P)`
|
||||
margin: 20px 50px;
|
||||
display: block;
|
||||
`;
|
||||
|
||||
const Initialize = () => (
|
||||
<section className="device-settings">
|
||||
<div className="row">
|
||||
<Wrapper>
|
||||
<Row>
|
||||
<H2>Your device is in not initialized</H2>
|
||||
<p>Please use Bitcoin wallet interface to start initialization process</p>
|
||||
<a className="button" href="https://wallet.trezor.io/">Take me to the Bitcoin wallet</a>
|
||||
</div>
|
||||
</section>
|
||||
<StyledP>Please use Bitcoin wallet interface to start initialization process</StyledP>
|
||||
<A href="https://wallet.trezor.io/">
|
||||
<Button text="Take me to the Bitcoin wallet" />
|
||||
</A>
|
||||
</Row>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export default connect(null, null)(Initialize);
|
||||
|
@ -1,41 +0,0 @@
|
||||
.acquire {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: @color_white;
|
||||
|
||||
.warning {
|
||||
background: @color_info_secondary;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 26px 39px 26px 80px;
|
||||
|
||||
div {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: @color_info_primary;
|
||||
font-size: 14px;
|
||||
-webkit-font-smoothing: auto;
|
||||
margin-bottom: 5px;
|
||||
padding: 0px;
|
||||
|
||||
&:before {
|
||||
.icomoon-info;
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: -32px;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: @color_info_primary;
|
||||
font-size: 12px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background-color: @color_body;
|
||||
font-family: @font-default;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.app {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
min-width: 720px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&.resized {
|
||||
// to make sure that unpacked coin menu will not overflow main container
|
||||
// 512 dropdown height + 50 header + 30 margin + 64 topnav height
|
||||
min-height: 680px;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
width: 100%;
|
||||
max-width: 1170px;
|
||||
margin: 0 auto;
|
||||
flex: 1;
|
||||
background: @color_main;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-radius: 4px 4px 0px 0px;
|
||||
overflow: hidden;
|
||||
margin-top: 32px;
|
||||
|
||||
@media screen and (max-width: 1170px) {
|
||||
border-radius: 0px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ article {
|
||||
|
||||
nav {
|
||||
height: 64px;
|
||||
border-bottom: 1px solid @color_divider;
|
||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
background: @color_white;
|
||||
|
@ -1,36 +0,0 @@
|
||||
.history {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
border: 1px solid red;
|
||||
|
||||
.history-transaction {
|
||||
|
||||
|
||||
|
||||
.amount, .time, .address {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
&.in {
|
||||
color: green;
|
||||
.amount {
|
||||
&:before {
|
||||
content: '+'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.out {
|
||||
color: red;
|
||||
.amount {
|
||||
&:before {
|
||||
content: '-'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
@import './fonts.less';
|
||||
@import './colors.less';
|
||||
@import './mixins.less';
|
||||
@import './base.less';
|
||||
@import './aside.less';
|
||||
@import './content.less';
|
||||
@import './modal.less';
|
||||
@ -9,16 +8,12 @@
|
||||
@import './reactSelect.less';
|
||||
@import './rcTooltip.less';
|
||||
|
||||
@import './history.less';
|
||||
@import './send.less';
|
||||
@import './receive.less';
|
||||
@import './summary.less';
|
||||
|
||||
@import './landingPage.less';
|
||||
|
||||
@import './acquire.less';
|
||||
@import './notification.less';
|
||||
|
||||
@import './inputs.less';
|
||||
@import './loader.less';
|
||||
@import './log.less';
|
||||
|
@ -1,51 +0,0 @@
|
||||
.log {
|
||||
position: relative;
|
||||
color: @color_info_primary;
|
||||
background: @color_info_secondary;
|
||||
padding: 24px 48px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
|
||||
.log-close {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 0;
|
||||
padding: 12px;
|
||||
color: inherit;
|
||||
transition: opacity 0.3s;
|
||||
|
||||
&:after {
|
||||
.icomoon-close;
|
||||
}
|
||||
&:active,
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0px;
|
||||
margin: 2px 0px;
|
||||
font-size: 12px;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
min-height: 200px;
|
||||
resize: vertical;
|
||||
font-size: 10px;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
29
yarn.lock
29
yarn.lock
@ -3336,6 +3336,12 @@ error-ex@^1.2.0, error-ex@^1.3.1:
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
error-stack-parser@^1.3.6:
|
||||
version "1.3.6"
|
||||
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-1.3.6.tgz#e0e73b93e417138d1cd7c0b746b1a4a14854c292"
|
||||
dependencies:
|
||||
stackframe "^0.3.1"
|
||||
|
||||
error@^7.0.2:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02"
|
||||
@ -8220,6 +8226,15 @@ rechoir@^0.6.2:
|
||||
dependencies:
|
||||
resolve "^1.1.6"
|
||||
|
||||
redbox-react@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/redbox-react/-/redbox-react-1.6.0.tgz#e753ac02595bc1bf695b3935889a4f5b1b5a21a1"
|
||||
dependencies:
|
||||
error-stack-parser "^1.3.6"
|
||||
object-assign "^4.0.1"
|
||||
prop-types "^15.5.4"
|
||||
sourcemapped-stacktrace "^1.1.6"
|
||||
|
||||
redent@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
|
||||
@ -8981,6 +8996,10 @@ source-map-url@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
|
||||
|
||||
source-map@0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
|
||||
|
||||
source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
@ -8999,6 +9018,12 @@ source-map@^0.7.2:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||
|
||||
sourcemapped-stacktrace@^1.1.6:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/sourcemapped-stacktrace/-/sourcemapped-stacktrace-1.1.8.tgz#6b7a3f1a6fb15f6d40e701e23ce404553480d688"
|
||||
dependencies:
|
||||
source-map "0.5.6"
|
||||
|
||||
spdx-correct@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
|
||||
@ -9080,6 +9105,10 @@ stack-utils@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
|
||||
|
||||
stackframe@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4"
|
||||
|
||||
state-toggle@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.1.tgz#c3cb0974f40a6a0f8e905b96789eb41afa1cde3a"
|
||||
|
Loading…
Reference in New Issue
Block a user