commit
3f57d5f66b
@ -0,0 +1,40 @@ |
||||
// next version number
|
||||
const version = 28 |
||||
|
||||
/* |
||||
|
||||
normalizes txParams on unconfirmed txs |
||||
|
||||
*/ |
||||
const clone = require('clone') |
||||
|
||||
module.exports = { |
||||
version, |
||||
|
||||
migrate: async function (originalVersionedData) { |
||||
const versionedData = clone(originalVersionedData) |
||||
versionedData.meta.version = version |
||||
const state = versionedData.data |
||||
const newState = transformState(state) |
||||
versionedData.data = newState |
||||
return versionedData |
||||
}, |
||||
} |
||||
|
||||
function transformState (state) { |
||||
const newState = state |
||||
|
||||
if (newState.PreferencesController) { |
||||
if (newState.PreferencesController.tokens) { |
||||
const identities = newState.TransactionController.identities |
||||
const tokens = newState.PreferencesController.tokens |
||||
newState.PreferencesController.accountTokens = {} |
||||
for (const identity in identities) { |
||||
newState.PreferencesController.accountTokens[identity] = {'mainnet': tokens} |
||||
} |
||||
newState.PreferencesController.tokens = [] |
||||
} |
||||
} |
||||
|
||||
return newState |
||||
} |
@ -0,0 +1,86 @@ |
||||
const PropTypes = require('prop-types') |
||||
const {PureComponent} = require('react') |
||||
const h = require('react-hyperscript') |
||||
const {qrcode: qrCode} = require('qrcode-npm') |
||||
const {connect} = require('react-redux') |
||||
const {isHexPrefixed} = require('ethereumjs-util') |
||||
const actions = require('../../ui/app/actions') |
||||
const CopyButton = require('./components/copyButton') |
||||
|
||||
class AccountQrScreen extends PureComponent { |
||||
static defaultProps = { |
||||
warning: null, |
||||
} |
||||
|
||||
static propTypes = { |
||||
dispatch: PropTypes.func.isRequired, |
||||
buyView: PropTypes.any.isRequired, |
||||
Qr: PropTypes.object.isRequired, |
||||
selectedAddress: PropTypes.string.isRequired, |
||||
warning: PropTypes.node, |
||||
} |
||||
|
||||
render () { |
||||
const {dispatch, Qr, selectedAddress, warning} = this.props |
||||
const address = `${isHexPrefixed(Qr.data) ? 'ethereum:' : ''}${Qr.data}` |
||||
const qrImage = qrCode(4, 'M') |
||||
|
||||
qrImage.addData(address) |
||||
qrImage.make() |
||||
|
||||
return h('div.flex-column.full-width', { |
||||
style: { |
||||
alignItems: 'center', |
||||
boxSizing: 'border-box', |
||||
padding: '50px', |
||||
}, |
||||
}, [ |
||||
h('div.flex-row.full-width', { |
||||
style: { |
||||
alignItems: 'flex-start', |
||||
}, |
||||
}, [ |
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { |
||||
onClick () { |
||||
dispatch(actions.backToAccountDetail(selectedAddress)) |
||||
}, |
||||
}), |
||||
]), |
||||
h('div.qr-header', Qr.message), |
||||
warning && h('span.error.flex-center', { |
||||
style: { |
||||
textAlign: 'center', |
||||
width: '229px', |
||||
height: '82px', |
||||
}, |
||||
}, [ |
||||
this.props.warning, |
||||
]), |
||||
h('div#qr-container.flex-column', { |
||||
style: { |
||||
marginTop: '25px', |
||||
marginBottom: '15px', |
||||
}, |
||||
dangerouslySetInnerHTML: { |
||||
__html: qrImage.createTableTag(4), |
||||
}, |
||||
}), |
||||
h('div.flex-row.full-width', [ |
||||
h('h3.ellip-address.grow-tenx', Qr.data), |
||||
h(CopyButton, { |
||||
value: Qr.data, |
||||
}), |
||||
]), |
||||
]) |
||||
} |
||||
} |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
Qr: state.appState.Qr, |
||||
buyView: state.appState.buyView, |
||||
warning: state.appState.warning, |
||||
} |
||||
} |
||||
|
||||
module.exports = connect(mapStateToProps)(AccountQrScreen) |
@ -0,0 +1,432 @@ |
||||
const PropTypes = require('prop-types') |
||||
const {Component} = require('react') |
||||
const h = require('react-hyperscript') |
||||
const actions = require('../../../ui/app/actions') |
||||
const SandwichExpando = require('sandwich-expando') |
||||
const {Dropdown} = require('./dropdown') |
||||
const {DropdownMenuItem} = require('./dropdown') |
||||
const NetworkIndicator = require('./network') |
||||
const {AccountDropdowns} = require('./account-dropdowns') |
||||
|
||||
const LOCALHOST_RPC_URL = 'http://localhost:8545' |
||||
|
||||
module.exports = class AppBar extends Component { |
||||
static defaultProps = { |
||||
selectedAddress: undefined, |
||||
} |
||||
|
||||
static propTypes = { |
||||
dispatch: PropTypes.func.isRequired, |
||||
frequentRpcList: PropTypes.array.isRequired, |
||||
isMascara: PropTypes.bool.isRequired, |
||||
isOnboarding: PropTypes.bool.isRequired, |
||||
identities: PropTypes.any.isRequired, |
||||
selectedAddress: PropTypes.string, |
||||
isUnlocked: PropTypes.bool.isRequired, |
||||
network: PropTypes.any.isRequired, |
||||
keyrings: PropTypes.any.isRequired, |
||||
provider: PropTypes.any.isRequired, |
||||
} |
||||
|
||||
static renderSpace () { |
||||
return ( |
||||
h('span', { |
||||
dangerouslySetInnerHTML: { |
||||
__html: ' ', |
||||
}, |
||||
}) |
||||
) |
||||
} |
||||
|
||||
state = { |
||||
isNetworkMenuOpen: false, |
||||
} |
||||
|
||||
renderAppBar () { |
||||
if (window.METAMASK_UI_TYPE === 'notification') { |
||||
return null |
||||
} |
||||
|
||||
const props = this.props |
||||
const {isMascara, isOnboarding} = props |
||||
|
||||
// Do not render header if user is in mascara onboarding
|
||||
if (isMascara && isOnboarding) { |
||||
return null |
||||
} |
||||
|
||||
// Do not render header if user is in mascara buy ether
|
||||
if (isMascara && props.currentView.name === 'buyEth') { |
||||
return null |
||||
} |
||||
|
||||
return ( |
||||
h('div.app-bar', [ |
||||
this.renderAppBarNewUiNotice(), |
||||
this.renderAppBarAppHeader(), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
renderAppBarNewUiNotice () { |
||||
const {dispatch} = this.props |
||||
|
||||
return ( |
||||
h('div.app-bar__new-ui-banner', { |
||||
style: { |
||||
height: '28px', |
||||
zIndex: 12, |
||||
}, |
||||
}, [ |
||||
'Try the New MetaMask', |
||||
AppBar.renderSpace(), |
||||
h('span.banner__link', { |
||||
async onClick () { |
||||
await dispatch(actions.setFeatureFlag('betaUI', true)) |
||||
global.platform.openExtensionInBrowser() |
||||
}, |
||||
}, [ |
||||
'Now', |
||||
]), |
||||
AppBar.renderSpace(), |
||||
'or', |
||||
AppBar.renderSpace(), |
||||
h('span.banner__link', { |
||||
onClick () { |
||||
global.platform.openWindow({ |
||||
url: 'https://medium.com/metamask/74dba32cc7f7', |
||||
}) |
||||
}, |
||||
}, [ |
||||
'Learn More', |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
renderAppBarAppHeader () { |
||||
const { |
||||
identities, |
||||
selectedAddress, |
||||
isUnlocked, |
||||
network, |
||||
keyrings, |
||||
provider, |
||||
} = this.props |
||||
const { |
||||
isNetworkMenuOpen, |
||||
isMainMenuOpen, |
||||
} = this.state |
||||
|
||||
return ( |
||||
h('.full-width', { |
||||
style: { |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
height: '38px', |
||||
}, |
||||
}, [ |
||||
h('.app-header.flex-row.flex-space-between', { |
||||
style: { |
||||
alignItems: 'center', |
||||
visibility: isUnlocked ? 'visible' : 'none', |
||||
background: isUnlocked ? 'white' : 'none', |
||||
height: '38px', |
||||
position: 'relative', |
||||
zIndex: 12, |
||||
}, |
||||
}, [ |
||||
h('div.left-menu-section', { |
||||
style: { |
||||
display: 'flex', |
||||
flexDirection: 'row', |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
// mini logo
|
||||
h('img', { |
||||
height: 24, |
||||
width: 24, |
||||
src: './images/icon-128.png', |
||||
}), |
||||
h(NetworkIndicator, { |
||||
network: network, |
||||
provider: provider, |
||||
onClick: (event) => { |
||||
event.preventDefault() |
||||
event.stopPropagation() |
||||
this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen }) |
||||
}, |
||||
}), |
||||
]), |
||||
isUnlocked && h('div', { |
||||
style: { |
||||
display: 'flex', |
||||
flexDirection: 'row', |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
h(AccountDropdowns, { |
||||
style: {}, |
||||
enableAccountsSelector: true, |
||||
identities: identities, |
||||
selected: selectedAddress, |
||||
network, |
||||
keyrings, |
||||
}, []), |
||||
h(SandwichExpando, { |
||||
className: 'sandwich-expando', |
||||
width: 16, |
||||
barHeight: 2, |
||||
padding: 0, |
||||
isOpen: isMainMenuOpen, |
||||
color: 'rgb(247,146,30)', |
||||
onClick: () => { |
||||
this.setState({ |
||||
isMainMenuOpen: !isMainMenuOpen, |
||||
}) |
||||
}, |
||||
}), |
||||
]), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
renderNetworkDropdown () { |
||||
const { |
||||
dispatch, |
||||
frequentRpcList: rpcList, |
||||
provider, |
||||
} = this.props |
||||
const { |
||||
type: providerType, |
||||
rpcTarget: activeNetwork, |
||||
} = provider |
||||
const isOpen = this.state.isNetworkMenuOpen |
||||
|
||||
return h(Dropdown, { |
||||
useCssTransition: true, |
||||
isOpen, |
||||
onClickOutside: (event) => { |
||||
const { classList } = event.target |
||||
const isNotToggleElement = [ |
||||
classList.contains('menu-icon'), |
||||
classList.contains('network-name'), |
||||
classList.contains('network-indicator'), |
||||
].filter(bool => bool).length === 0 |
||||
// classes from three constituent nodes of the toggle element
|
||||
|
||||
if (isNotToggleElement) { |
||||
this.setState({ isNetworkMenuOpen: false }) |
||||
} |
||||
}, |
||||
zIndex: 11, |
||||
style: { |
||||
position: 'absolute', |
||||
left: '2px', |
||||
top: '64px', |
||||
}, |
||||
innerStyle: { |
||||
padding: '2px 16px 2px 0px', |
||||
}, |
||||
}, [ |
||||
h(DropdownMenuItem, { |
||||
key: 'main', |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), |
||||
onClick: () => dispatch(actions.setProviderType('mainnet')), |
||||
style: { |
||||
fontSize: '18px', |
||||
}, |
||||
}, [ |
||||
h('.menu-icon.diamond'), |
||||
'Main Ethereum Network', |
||||
providerType === 'mainnet' |
||||
? h('.check', '✓') |
||||
: null, |
||||
]), |
||||
h(DropdownMenuItem, { |
||||
key: 'ropsten', |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), |
||||
onClick: () => dispatch(actions.setProviderType('ropsten')), |
||||
style: { |
||||
fontSize: '18px', |
||||
}, |
||||
}, [ |
||||
h('.menu-icon.red-dot'), |
||||
'Ropsten Test Network', |
||||
providerType === 'ropsten' |
||||
? h('.check', '✓') |
||||
: null, |
||||
]), |
||||
h(DropdownMenuItem, { |
||||
key: 'kovan', |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), |
||||
onClick: () => dispatch(actions.setProviderType('kovan')), |
||||
style: { |
||||
fontSize: '18px', |
||||
}, |
||||
}, [ |
||||
h('.menu-icon.hollow-diamond'), |
||||
'Kovan Test Network', |
||||
providerType === 'kovan' |
||||
? h('.check', '✓') |
||||
: null, |
||||
]), |
||||
h(DropdownMenuItem, { |
||||
key: 'rinkeby', |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), |
||||
onClick: () => dispatch(actions.setProviderType('rinkeby')), |
||||
style: { |
||||
fontSize: '18px', |
||||
}, |
||||
}, [ |
||||
h('.menu-icon.golden-square'), |
||||
'Rinkeby Test Network', |
||||
providerType === 'rinkeby' |
||||
? h('.check', '✓') |
||||
: null, |
||||
]), |
||||
h(DropdownMenuItem, { |
||||
key: 'default', |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), |
||||
onClick: () => dispatch(actions.setProviderType('localhost')), |
||||
style: { |
||||
fontSize: '18px', |
||||
}, |
||||
}, [ |
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'), |
||||
'Localhost 8545', |
||||
activeNetwork === LOCALHOST_RPC_URL |
||||
? h('.check', '✓') |
||||
: null, |
||||
]), |
||||
|
||||
this.renderCustomOption(provider), |
||||
this.renderCommonRpc(rpcList, provider), |
||||
|
||||
h(DropdownMenuItem, { |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), |
||||
onClick: () => dispatch(actions.showConfigPage()), |
||||
style: { |
||||
fontSize: '18px', |
||||
}, |
||||
}, [ |
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'), |
||||
'Custom RPC', |
||||
activeNetwork === 'custom' |
||||
? h('.check', '✓') |
||||
: null, |
||||
]), |
||||
]) |
||||
} |
||||
|
||||
renderCustomOption ({ rpcTarget, type }) { |
||||
const {dispatch} = this.props |
||||
|
||||
if (type !== 'rpc') { |
||||
return null |
||||
} |
||||
|
||||
// Concatenate long URLs
|
||||
let label = rpcTarget |
||||
if (rpcTarget.length > 31) { |
||||
label = label.substr(0, 34) + '...' |
||||
} |
||||
|
||||
switch (rpcTarget) { |
||||
case LOCALHOST_RPC_URL: |
||||
return null |
||||
default: |
||||
return h(DropdownMenuItem, { |
||||
key: rpcTarget, |
||||
onClick: () => dispatch(actions.setRpcTarget(rpcTarget)), |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }), |
||||
}, [ |
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'), |
||||
label, |
||||
h('.check', '✓'), |
||||
]) |
||||
} |
||||
} |
||||
|
||||
renderCommonRpc (rpcList, {rpcTarget}) { |
||||
const {dispatch} = this.props |
||||
|
||||
return rpcList.map((rpc) => { |
||||
if ((rpc === LOCALHOST_RPC_URL) || (rpc === rpcTarget)) { |
||||
return null |
||||
} else { |
||||
return h(DropdownMenuItem, { |
||||
key: `common${rpc}`, |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }), |
||||
onClick: () => dispatch(actions.setRpcTarget(rpc)), |
||||
}, [ |
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'), |
||||
rpc, |
||||
rpcTarget === rpc |
||||
? h('.check', '✓') |
||||
: null, |
||||
]) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
renderDropdown () { |
||||
const {dispatch} = this.props |
||||
const isOpen = this.state.isMainMenuOpen |
||||
|
||||
return h(Dropdown, { |
||||
useCssTransition: true, |
||||
isOpen: isOpen, |
||||
zIndex: 11, |
||||
onClickOutside: (event) => { |
||||
const classList = event.target.classList |
||||
const parentClassList = event.target.parentElement.classList |
||||
|
||||
const isToggleElement = classList.contains('sandwich-expando') || |
||||
parentClassList.contains('sandwich-expando') |
||||
|
||||
if (isOpen && !isToggleElement) { |
||||
this.setState({ isMainMenuOpen: false }) |
||||
} |
||||
}, |
||||
style: { |
||||
position: 'absolute', |
||||
right: '2px', |
||||
top: '66px', |
||||
}, |
||||
innerStyle: {}, |
||||
}, [ |
||||
h(DropdownMenuItem, { |
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), |
||||
onClick: () => { dispatch(actions.showConfigPage()) }, |
||||
}, 'Settings'), |
||||
|
||||
h(DropdownMenuItem, { |
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), |
||||
onClick: () => { dispatch(actions.lockMetamask()) }, |
||||
}, 'Log Out'), |
||||
|
||||
h(DropdownMenuItem, { |
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), |
||||
onClick: () => { dispatch(actions.showInfoPage()) }, |
||||
}, 'Info/Help'), |
||||
|
||||
h(DropdownMenuItem, { |
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), |
||||
onClick: () => { |
||||
dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL')) |
||||
}, |
||||
}, 'Try Beta!'), |
||||
]) |
||||
} |
||||
|
||||
render () { |
||||
return h('div.full-width', [ |
||||
this.renderAppBar(), |
||||
this.renderNetworkDropdown(), |
||||
this.renderDropdown(), |
||||
]) |
||||
} |
||||
} |
@ -1,79 +0,0 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const qrCode = require('qrcode-npm').qrcode |
||||
const inherits = require('util').inherits |
||||
const connect = require('react-redux').connect |
||||
const isHexPrefixed = require('ethereumjs-util').isHexPrefixed |
||||
const CopyButton = require('./copyButton') |
||||
|
||||
module.exports = connect(mapStateToProps)(QrCodeView) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
Qr: state.appState.Qr, |
||||
buyView: state.appState.buyView, |
||||
warning: state.appState.warning, |
||||
} |
||||
} |
||||
|
||||
inherits(QrCodeView, Component) |
||||
|
||||
function QrCodeView () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
QrCodeView.prototype.render = function () { |
||||
const props = this.props |
||||
const Qr = props.Qr |
||||
const address = `${isHexPrefixed(Qr.data) ? 'ethereum:' : ''}${Qr.data}` |
||||
const qrImage = qrCode(4, 'M') |
||||
qrImage.addData(address) |
||||
qrImage.make() |
||||
return h('.main-container.flex-column', { |
||||
key: 'qr', |
||||
style: { |
||||
justifyContent: 'center', |
||||
paddingBottom: '45px', |
||||
paddingLeft: '45px', |
||||
paddingRight: '45px', |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
Array.isArray(Qr.message) ? h('.message-container', this.renderMultiMessage()) : h('.qr-header', Qr.message), |
||||
|
||||
this.props.warning ? this.props.warning && h('span.error.flex-center', { |
||||
style: { |
||||
textAlign: 'center', |
||||
width: '229px', |
||||
height: '82px', |
||||
}, |
||||
}, |
||||
this.props.warning) : null, |
||||
|
||||
h('#qr-container.flex-column', { |
||||
style: { |
||||
marginTop: '25px', |
||||
marginBottom: '15px', |
||||
}, |
||||
dangerouslySetInnerHTML: { |
||||
__html: qrImage.createTableTag(4), |
||||
}, |
||||
}), |
||||
h('.flex-row', [ |
||||
h('h3.ellip-address', { |
||||
style: { |
||||
width: '247px', |
||||
}, |
||||
}, Qr.data), |
||||
h(CopyButton, { |
||||
value: Qr.data, |
||||
}), |
||||
]), |
||||
]) |
||||
} |
||||
|
||||
QrCodeView.prototype.renderMultiMessage = function () { |
||||
var Qr = this.props.Qr |
||||
var multiMessage = Qr.message.map((message) => h('.qr-message', message)) |
||||
return multiMessage |
||||
} |
@ -0,0 +1,85 @@ |
||||
const PropTypes = require('prop-types') |
||||
const {PureComponent} = require('react') |
||||
const h = require('react-hyperscript') |
||||
const actions = require('../../ui/app/actions') |
||||
|
||||
module.exports = class NewUiAnnouncement extends PureComponent { |
||||
static propTypes = { |
||||
dispatch: PropTypes.func.isRequired, |
||||
}; |
||||
|
||||
close = async () => { |
||||
await this.props.dispatch(actions.setFeatureFlag('skipAnnounceBetaUI', true)) |
||||
} |
||||
|
||||
switchToNewUi = async () => { |
||||
const flag = 'betaUI' |
||||
const enabled = true |
||||
await this.props.dispatch(actions.setFeatureFlag( |
||||
flag, |
||||
enabled, |
||||
)) |
||||
await this.close() |
||||
global.platform.openExtensionInBrowser() |
||||
} |
||||
|
||||
render () { |
||||
return ( |
||||
h('div.new-ui-announcement', [ |
||||
h('section.new-ui-announcement__announcement-header', [ |
||||
h('h1', 'Announcement'), |
||||
h('a.close', { |
||||
onClick: this.close, |
||||
}, '×'), |
||||
]), |
||||
h('section.new-ui-announcement__body', [ |
||||
h('h1', 'A New Version of MetaMask'), |
||||
h('p', [ |
||||
"We're excited to announce a brand-new version of MetaMask with enhanced features and functionality.", |
||||
]), |
||||
h('div.updates-list', [ |
||||
h('h2', 'Updates include'), |
||||
h('ul', [ |
||||
h('li', 'New user interface'), |
||||
h('li', 'Full-screen mode'), |
||||
h('li', 'Better token support'), |
||||
h('li', 'Better gas controls'), |
||||
h('li', 'Advanced features for developers'), |
||||
h('li', 'New confirmation screens'), |
||||
h('li', 'And more!'), |
||||
]), |
||||
]), |
||||
h('p', [ |
||||
'You can still use the current version of MetaMask. The new version is still in beta, ' + |
||||
'however we encourage you to try it out as we transition into this exciting new update.', |
||||
h('span', { |
||||
dangerouslySetInnerHTML: { |
||||
__html: ' ', |
||||
}, |
||||
}), |
||||
h('a', { |
||||
href: 'https://medium.com/metamask/74dba32cc7f7', |
||||
onClick ({target}) { |
||||
const url = target.href |
||||
global.platform.openWindow({ |
||||
url, |
||||
}) |
||||
}, |
||||
}, [ |
||||
'Learn more.', |
||||
]), |
||||
]), |
||||
]), |
||||
h('section.new-ui-announcement__footer', [ |
||||
h('h1', 'Ready to try the new MetaMask?'), |
||||
h('button.positive', { |
||||
onClick: this.switchToNewUi, |
||||
}, 'Try it now'), |
||||
h('button.negative', { |
||||
onClick: this.close, |
||||
}, 'No thanks, maybe later'), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue