mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-23 19:26:33 +00:00
Merge pull request #2551 from balena-io/add-potm-webview
feat(GUI): Add featured-project component
This commit is contained in:
commit
7e3f516b04
57
lib/gui/app/components/featured-project/featured-project.jsx
Normal file
57
lib/gui/app/components/featured-project/featured-project.jsx
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const React = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const SafeWebview = require('../safe-webview/safe-webview.jsx')
|
||||
const settings = require('../../models/settings')
|
||||
const analytics = require('../../modules/analytics')
|
||||
|
||||
class FeaturedProject extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
endpoint: null
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
return settings.load()
|
||||
.then(() => {
|
||||
const endpoint = settings.get('featuredProjectEndpoint') || 'https://assets.balena.io/etcher-featured/index.html'
|
||||
this.setState({ endpoint })
|
||||
})
|
||||
.catch(analytics.logException)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (this.state.endpoint) ? (
|
||||
<SafeWebview
|
||||
src={this.state.endpoint}
|
||||
{...this.props}>
|
||||
</SafeWebview>
|
||||
) : null
|
||||
}
|
||||
}
|
||||
|
||||
FeaturedProject.propTypes = {
|
||||
onWebviewShow: propTypes.func
|
||||
}
|
||||
|
||||
module.exports = FeaturedProject
|
34
lib/gui/app/components/featured-project/index.js
Normal file
34
lib/gui/app/components/featured-project/index.js
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.FeaturedProject
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.FeaturedProject'
|
||||
const FeaturedProject = angular.module(MODULE_NAME, [])
|
||||
|
||||
FeaturedProject.component(
|
||||
'featuredProject',
|
||||
react2angular(require('./featured-project.jsx'))
|
||||
)
|
||||
|
||||
module.exports = MODULE_NAME
|
@ -24,7 +24,7 @@ const { default: styled, keyframes } = require('styled-components')
|
||||
|
||||
const { ProgressBar, Provider } = require('rendition')
|
||||
|
||||
const { colors, consts } = require('./../../theme')
|
||||
const { colors } = require('./../../theme')
|
||||
const { StepButton, StepSelection } = require('./../../styled-components')
|
||||
|
||||
const darkenForegroundStripes = 0.18
|
||||
@ -51,13 +51,16 @@ const ProgressButtonStripes = keyframes `
|
||||
|
||||
const FlashProgressBar = styled(ProgressBar) `
|
||||
> div {
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
color: white !important;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
width: 100%;
|
||||
max-width: ${consts.btnMaxWidth};
|
||||
margin: auto;
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
line-height: 48px;
|
||||
|
||||
background: ${Color(colors.warning.background).darken(darkenForegroundStripes).string()};
|
||||
`
|
||||
|
34
lib/gui/app/components/reduced-flashing-infos/index.js
Normal file
34
lib/gui/app/components/reduced-flashing-infos/index.js
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.ReducedFlashingInfos
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.ReducedFlashingInfos'
|
||||
const ReducedFlashingInfos = angular.module(MODULE_NAME, [])
|
||||
|
||||
ReducedFlashingInfos.component(
|
||||
'reducedFlashingInfos',
|
||||
react2angular(require('./reduced-flashing-infos.jsx'))
|
||||
)
|
||||
|
||||
module.exports = MODULE_NAME
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const React = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const styled = require('styled-components').default
|
||||
const SvgIcon = require('../svg-icon/svg-icon.jsx')
|
||||
|
||||
const Div = styled.div `
|
||||
position: absolute;
|
||||
top: 45px;
|
||||
left: 545px;
|
||||
|
||||
> span.step-name {
|
||||
justify-content: flex-start;
|
||||
|
||||
> span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
> span:nth-child(2) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
> span:nth-child(3) {
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.svg-icon[disabled] {
|
||||
opacity: 0.4;
|
||||
}
|
||||
`
|
||||
|
||||
const ReducedFlashingInfos = (props) => {
|
||||
return (props.shouldShow) ? (
|
||||
<Div>
|
||||
<span className="step-name">
|
||||
<SvgIcon disabled contents={[ props.imageLogo ]} paths={[ '../../assets/image.svg' ]} width='20px'></SvgIcon>
|
||||
<span>{ props.imageName }</span>
|
||||
<span style={{ color: '#7e8085' }}>{ props.imageSize }</span>
|
||||
</span>
|
||||
|
||||
<span className="step-name">
|
||||
<SvgIcon disabled paths={[ '../../assets/drive.svg' ]} width='20px'></SvgIcon>
|
||||
<span>{ props.driveTitle }</span>
|
||||
</span>
|
||||
</Div>
|
||||
) : null
|
||||
}
|
||||
|
||||
ReducedFlashingInfos.propTypes = {
|
||||
imageLogo: propTypes.string,
|
||||
imageName: propTypes.string,
|
||||
imageSize: propTypes.string,
|
||||
driveTitle: propTypes.string,
|
||||
shouldShow: propTypes.bool
|
||||
}
|
||||
|
||||
module.exports = ReducedFlashingInfos
|
34
lib/gui/app/components/safe-webview/index.js
Normal file
34
lib/gui/app/components/safe-webview/index.js
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.SafeWebview
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.SafeWebview'
|
||||
const SafeWebview = angular.module(MODULE_NAME, [])
|
||||
|
||||
SafeWebview.component(
|
||||
'safeWebview',
|
||||
react2angular(require('./safe-webview.jsx'))
|
||||
)
|
||||
|
||||
module.exports = MODULE_NAME
|
@ -20,17 +20,12 @@
|
||||
|
||||
const _ = require('lodash')
|
||||
const electron = require('electron')
|
||||
const angular = require('angular')
|
||||
const react = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const { react2angular } = require('react2angular')
|
||||
const analytics = require('../modules/analytics')
|
||||
const store = require('../models/store')
|
||||
const settings = require('../models/settings')
|
||||
const packageJSON = require('../../../../package.json')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.SafeWebview'
|
||||
const angularSafeWebview = angular.module(MODULE_NAME, [])
|
||||
const analytics = require('../../modules/analytics')
|
||||
const store = require('../../models/store')
|
||||
const settings = require('../../models/settings')
|
||||
const packageJSON = require('../../../../../package.json')
|
||||
|
||||
/**
|
||||
* @summary Electron session identifier
|
||||
@ -207,6 +202,9 @@ class SafeWebview extends react.PureComponent {
|
||||
this.setState({
|
||||
shouldShow: event.httpResponseCode === HTTP_OK
|
||||
})
|
||||
if (this.props.onWebviewShow) {
|
||||
this.props.onWebviewShow(event.httpResponseCode === HTTP_OK)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,10 +268,13 @@ SafeWebview.propTypes = {
|
||||
/**
|
||||
* @summary Refresh the webview
|
||||
*/
|
||||
refreshNow: propTypes.bool
|
||||
refreshNow: propTypes.bool,
|
||||
|
||||
/**
|
||||
* @summary Webview lifecycle event
|
||||
*/
|
||||
onWebviewShow: propTypes.func
|
||||
|
||||
}
|
||||
|
||||
angularSafeWebview.component('safeWebview', react2angular(SafeWebview))
|
||||
|
||||
module.exports = MODULE_NAME
|
||||
module.exports = SafeWebview
|
@ -22,162 +22,11 @@
|
||||
* @module Etcher.Components.SVGIcon
|
||||
*/
|
||||
|
||||
const _ = require('lodash')
|
||||
const angular = require('angular')
|
||||
const react = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const react2angular = require('react2angular').react2angular
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const analytics = require('../modules/analytics')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.SVGIcon'
|
||||
const angularSVGIcon = angular.module(MODULE_NAME, [])
|
||||
|
||||
const DEFAULT_SIZE = '40px'
|
||||
|
||||
const domParser = new window.DOMParser()
|
||||
|
||||
/**
|
||||
* @summary Try to parse SVG contents and return it data encoded
|
||||
*
|
||||
* @param {String} contents - SVG XML contents
|
||||
* @returns {String|null}
|
||||
*
|
||||
* @example
|
||||
* const encodedSVG = tryParseSVGContents('<svg><path></path></svg>')
|
||||
*
|
||||
* img.src = encodedSVG
|
||||
*/
|
||||
const tryParseSVGContents = (contents) => {
|
||||
const doc = domParser.parseFromString(contents, 'image/svg+xml')
|
||||
const parserError = doc.querySelector('parsererror')
|
||||
const svg = doc.querySelector('svg')
|
||||
|
||||
if (!parserError && svg) {
|
||||
return `data:image/svg+xml,${encodeURIComponent(svg.outerHTML)}`
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary SVG element that takes both filepaths and file contents
|
||||
* @type {Object}
|
||||
* @public
|
||||
*/
|
||||
class SVGIcon extends react.Component {
|
||||
/**
|
||||
* @summary Render the SVG
|
||||
* @returns {react.Element}
|
||||
*/
|
||||
render () {
|
||||
// __dirname behaves strangely inside a Webpack bundle,
|
||||
// so we need to provide different base directories
|
||||
// depending on whether __dirname is absolute or not,
|
||||
// which helps detecting a Webpack bundle.
|
||||
// We use global.__dirname inside a Webpack bundle since
|
||||
// that's the only way to get the "real" __dirname.
|
||||
const baseDirectory = path.isAbsolute(__dirname)
|
||||
? path.join(__dirname, '..')
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
: global.__dirname
|
||||
|
||||
let svgData = ''
|
||||
|
||||
_.find(this.props.contents, (content) => {
|
||||
const attempt = tryParseSVGContents(content)
|
||||
|
||||
if (attempt) {
|
||||
svgData = attempt
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
if (!svgData) {
|
||||
_.find(this.props.paths, (relativePath) => {
|
||||
// This means the path to the icon should be
|
||||
// relative to *this directory*.
|
||||
// TODO: There might be a way to compute the path
|
||||
// relatively to the `index.html`.
|
||||
const imagePath = path.join(baseDirectory, 'assets', relativePath)
|
||||
|
||||
const contents = _.attempt(() => {
|
||||
return fs.readFileSync(imagePath, {
|
||||
encoding: 'utf8'
|
||||
})
|
||||
})
|
||||
|
||||
if (_.isError(contents)) {
|
||||
analytics.logException(contents)
|
||||
return false
|
||||
}
|
||||
|
||||
const parsed = _.attempt(tryParseSVGContents, contents)
|
||||
|
||||
if (parsed) {
|
||||
svgData = parsed
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
const width = this.props.width || DEFAULT_SIZE
|
||||
const height = this.props.height || DEFAULT_SIZE
|
||||
|
||||
return react.createElement('img', {
|
||||
className: 'svg-icon',
|
||||
style: {
|
||||
width,
|
||||
height
|
||||
},
|
||||
src: svgData,
|
||||
disabled: this.props.disabled
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Cause a re-render due to changed element properties
|
||||
* @param {Object} nextProps - the new properties
|
||||
*/
|
||||
componentWillReceiveProps (nextProps) {
|
||||
// This will update the element if the properties change
|
||||
this.setState(nextProps)
|
||||
}
|
||||
}
|
||||
|
||||
SVGIcon.propTypes = {
|
||||
|
||||
/**
|
||||
* @summary Paths to SVG files to be tried in succession if any fails
|
||||
*/
|
||||
paths: propTypes.array,
|
||||
|
||||
/**
|
||||
* @summary List of embedded SVG contents to be tried in succession if any fails
|
||||
*/
|
||||
contents: propTypes.array,
|
||||
|
||||
/**
|
||||
* @summary SVG image width unit
|
||||
*/
|
||||
width: propTypes.string,
|
||||
|
||||
/**
|
||||
* @summary SVG image height unit
|
||||
*/
|
||||
height: propTypes.string,
|
||||
|
||||
/**
|
||||
* @summary Should the element visually appear grayed out and disabled?
|
||||
*/
|
||||
disabled: propTypes.bool
|
||||
|
||||
}
|
||||
|
||||
angularSVGIcon.component('svgIcon', react2angular(SVGIcon))
|
||||
angularSVGIcon.component('svgIcon', react2angular(require('./svg-icon/svg-icon.jsx')))
|
||||
module.exports = MODULE_NAME
|
||||
|
176
lib/gui/app/components/svg-icon/svg-icon.jsx
Normal file
176
lib/gui/app/components/svg-icon/svg-icon.jsx
Normal file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.SVGIcon
|
||||
*/
|
||||
|
||||
const _ = require('lodash')
|
||||
const react = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const analytics = require('../../modules/analytics')
|
||||
const domParser = new window.DOMParser()
|
||||
|
||||
const DEFAULT_SIZE = '40px'
|
||||
|
||||
/**
|
||||
* @summary Try to parse SVG contents and return it data encoded
|
||||
*
|
||||
* @param {String} contents - SVG XML contents
|
||||
* @returns {String|null}
|
||||
*
|
||||
* @example
|
||||
* const encodedSVG = tryParseSVGContents('<svg><path></path></svg>')
|
||||
*
|
||||
* img.src = encodedSVG
|
||||
*/
|
||||
const tryParseSVGContents = (contents) => {
|
||||
const doc = domParser.parseFromString(contents, 'image/svg+xml')
|
||||
const parserError = doc.querySelector('parsererror')
|
||||
const svg = doc.querySelector('svg')
|
||||
|
||||
if (!parserError && svg) {
|
||||
return `data:image/svg+xml,${encodeURIComponent(svg.outerHTML)}`
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/* eslint-disable jsdoc/require-example */
|
||||
|
||||
/**
|
||||
* @summary SVG element that takes both filepaths and file contents
|
||||
* @type {Object}
|
||||
* @public
|
||||
*/
|
||||
class SVGIcon extends react.Component {
|
||||
/**
|
||||
* @summary Render the SVG
|
||||
* @returns {react.Element}
|
||||
*/
|
||||
render () {
|
||||
// __dirname behaves strangely inside a Webpack bundle,
|
||||
// so we need to provide different base directories
|
||||
// depending on whether __dirname is absolute or not,
|
||||
// which helps detecting a Webpack bundle.
|
||||
// We use global.__dirname inside a Webpack bundle since
|
||||
// that's the only way to get the "real" __dirname.
|
||||
const baseDirectory = path.isAbsolute(__dirname)
|
||||
? path.join(__dirname, '..')
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
: global.__dirname
|
||||
|
||||
let svgData = ''
|
||||
|
||||
_.find(this.props.contents, (content) => {
|
||||
const attempt = tryParseSVGContents(content)
|
||||
|
||||
if (attempt) {
|
||||
svgData = attempt
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
if (!svgData) {
|
||||
_.find(this.props.paths, (relativePath) => {
|
||||
// This means the path to the icon should be
|
||||
// relative to *this directory*.
|
||||
// TODO: There might be a way to compute the path
|
||||
// relatively to the `index.html`.
|
||||
const imagePath = path.join(baseDirectory, 'assets', relativePath)
|
||||
|
||||
const contents = _.attempt(() => {
|
||||
return fs.readFileSync(imagePath, {
|
||||
encoding: 'utf8'
|
||||
})
|
||||
})
|
||||
|
||||
if (_.isError(contents)) {
|
||||
analytics.logException(contents)
|
||||
return false
|
||||
}
|
||||
|
||||
const parsed = _.attempt(tryParseSVGContents, contents)
|
||||
|
||||
if (parsed) {
|
||||
svgData = parsed
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
const width = this.props.width || DEFAULT_SIZE
|
||||
const height = this.props.height || DEFAULT_SIZE
|
||||
|
||||
return react.createElement('img', {
|
||||
className: 'svg-icon',
|
||||
style: {
|
||||
width,
|
||||
height
|
||||
},
|
||||
src: svgData,
|
||||
disabled: this.props.disabled
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Cause a re-render due to changed element properties
|
||||
* @param {Object} nextProps - the new properties
|
||||
*/
|
||||
componentWillReceiveProps (nextProps) {
|
||||
// This will update the element if the properties change
|
||||
this.setState(nextProps)
|
||||
}
|
||||
}
|
||||
|
||||
SVGIcon.propTypes = {
|
||||
|
||||
/**
|
||||
* @summary Paths to SVG files to be tried in succession if any fails
|
||||
*/
|
||||
paths: propTypes.array,
|
||||
|
||||
/**
|
||||
* @summary List of embedded SVG contents to be tried in succession if any fails
|
||||
*/
|
||||
contents: propTypes.array,
|
||||
|
||||
/**
|
||||
* @summary SVG image width unit
|
||||
*/
|
||||
width: propTypes.string,
|
||||
|
||||
/**
|
||||
* @summary SVG image height unit
|
||||
*/
|
||||
height: propTypes.string,
|
||||
|
||||
/**
|
||||
* @summary Should the element visually appear grayed out and disabled?
|
||||
*/
|
||||
disabled: propTypes.bool
|
||||
|
||||
}
|
||||
|
||||
module.exports = SVGIcon
|
@ -115,7 +115,8 @@ const ACTIONS = _.fromPairs(_.map([
|
||||
'DESELECT_DRIVE',
|
||||
'DESELECT_IMAGE',
|
||||
'SET_APPLICATION_SESSION_UUID',
|
||||
'SET_FLASHING_WORKFLOW_UUID'
|
||||
'SET_FLASHING_WORKFLOW_UUID',
|
||||
'SET_WEBVIEW_SHOWING_STATUS'
|
||||
], (message) => {
|
||||
return [ message, message ]
|
||||
}))
|
||||
@ -511,6 +512,10 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
|
||||
return state.set('flashingWorkflowUuid', action.data)
|
||||
}
|
||||
|
||||
case ACTIONS.SET_WEBVIEW_SHOWING_STATUS: {
|
||||
return state.set('isWebviewShowing', action.data)
|
||||
}
|
||||
|
||||
default: {
|
||||
return state
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const store = require('../../../models/store')
|
||||
const settings = require('../../../models/settings')
|
||||
const flashState = require('../../../models/flash-state')
|
||||
@ -25,10 +26,12 @@ const availableDrives = require('../../../models/available-drives')
|
||||
const selectionState = require('../../../models/selection-state')
|
||||
const driveConstraints = require('../../../../../shared/drive-constraints')
|
||||
const messages = require('../../../../../shared/messages')
|
||||
const prettyBytes = require('pretty-bytes')
|
||||
|
||||
module.exports = function (
|
||||
TooltipModalService,
|
||||
OSOpenExternalService
|
||||
OSOpenExternalService,
|
||||
$filter
|
||||
) {
|
||||
// Expose several modules to the template for convenience
|
||||
this.selection = selectionState
|
||||
@ -38,6 +41,7 @@ module.exports = function (
|
||||
this.external = OSOpenExternalService
|
||||
this.constraints = driveConstraints
|
||||
this.progressMessage = messages.progress
|
||||
this.isWebviewShowing = Boolean(store.getState().toJS().isWebviewShowing)
|
||||
|
||||
/**
|
||||
* @summary Determine if the drive step should be disabled
|
||||
@ -93,4 +97,87 @@ module.exports = function (
|
||||
message: selectionState.getImagePath()
|
||||
}).catch(exceptionReporter.report)
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get drive title based on device quantity
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} - drives title
|
||||
*
|
||||
* @example
|
||||
* console.log(DriveSelectionController.getDrivesTitle())
|
||||
* > 'Multiple Drives (4)'
|
||||
*/
|
||||
this.getDrivesTitle = () => {
|
||||
const drives = this.selection.getSelectedDrives()
|
||||
|
||||
/* eslint-disable no-magic-numbers */
|
||||
if (drives.length === 1) {
|
||||
return drives[0].description || 'Untitled Device'
|
||||
}
|
||||
/* eslint-enable no-magic-numbers */
|
||||
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
if (drives.length === 0) {
|
||||
return 'No targets found'
|
||||
}
|
||||
|
||||
return `${drives.length} Targets`
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get drive subtitle
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} - drives subtitle
|
||||
*
|
||||
* @example
|
||||
* console.log(MainController.getDrivesSubtitle())
|
||||
* > '32 GB'
|
||||
*/
|
||||
this.getDrivesSubtitle = () => {
|
||||
const drive = this.selection.getCurrentDrive()
|
||||
|
||||
if (drive) {
|
||||
return prettyBytes(drive.size)
|
||||
}
|
||||
|
||||
return 'Please insert at least one target device'
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get the basename of the selected image
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} basename of the selected image
|
||||
*
|
||||
* @example
|
||||
* const imageBasename = ImageSelectionController.getImageBasename();
|
||||
*/
|
||||
this.getImageBasename = () => {
|
||||
if (!this.selection.hasImage()) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return path.basename(this.selection.getImagePath())
|
||||
}
|
||||
|
||||
this.setWebviewShowing = (data) => {
|
||||
this.isWebviewShowing = data
|
||||
store.dispatch({
|
||||
type: 'SET_WEBVIEW_SHOWING_STATUS',
|
||||
data: Boolean(data)
|
||||
})
|
||||
}
|
||||
|
||||
this.getDriveTitle = () => {
|
||||
/* eslint-disable no-magic-numbers */
|
||||
const driveTitleRaw = (this.selection.getSelectedDevices().length === 1)
|
||||
? this.getDrivesSubtitle()
|
||||
: `${this.selection.getSelectedDevices().length} Targets`
|
||||
return $filter('middleEllipsis:20')(driveTitleRaw)
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ const MainPage = angular.module(MODULE_NAME, [
|
||||
require('../../components/image-selector'),
|
||||
require('../../components/warning-modal/warning-modal'),
|
||||
require('../../components/file-selector'),
|
||||
require('../../components/featured-project'),
|
||||
require('../../components/reduced-flashing-infos'),
|
||||
|
||||
require('../../os/open-external/open-external'),
|
||||
require('../../os/dropzone/dropzone'),
|
||||
|
@ -48,11 +48,20 @@ svg-icon > img[disabled] {
|
||||
|
||||
.page-main .button-brick {
|
||||
min-width: $btn-min-width;
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.page-main .button-abort-write {
|
||||
margin-right: -35px;
|
||||
z-index: 1;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
position: absolute;
|
||||
right: -17px;
|
||||
top: 30%;
|
||||
}
|
||||
|
||||
%step-border {
|
||||
|
@ -26,8 +26,8 @@
|
||||
|
||||
<div class="col-xs" ng-controller="DriveSelectionController as drive">
|
||||
<div class="box text-center relative">
|
||||
<div class="step-border-left" ng-disabled="main.shouldDriveStepBeDisabled()"></div>
|
||||
<div class="step-border-right" ng-disabled="main.shouldFlashStepBeDisabled()"></div>
|
||||
<div class="step-border-left" ng-disabled="main.shouldDriveStepBeDisabled()" ng-hide="main.state.isFlashing() && main.isWebviewShowing"></div>
|
||||
<div class="step-border-right" ng-disabled="main.shouldFlashStepBeDisabled()" ng-hide="main.state.isFlashing() && main.isWebviewShowing"></div>
|
||||
|
||||
<div class="center-block">
|
||||
<svg-icon paths="[ '../../assets/drive.svg' ]"
|
||||
@ -90,6 +90,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<featured-project
|
||||
ng-class="{
|
||||
'fp-visible': main.state.isFlashing() && main.isWebviewShowing
|
||||
}"
|
||||
on-webview-show="main.setWebviewShowing"
|
||||
></featured-project>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<reduced-flashing-infos
|
||||
image-logo="main.selection.getImageLogo()"
|
||||
image-name="main.selection.getImageName() || main.getImageBasename() | middleEllipsis:16"
|
||||
image-size="main.selection.getImageSize() | closestUnit"
|
||||
drive-title="main.getDrivesTitle() | middleEllipsis:16"
|
||||
should-show="main.state.isFlashing() && main.isWebviewShowing"
|
||||
></reduced-flashing-infos>
|
||||
</div>
|
||||
|
||||
<div class="col-xs" ng-controller="FlashController as flash">
|
||||
<div class="box text-center">
|
||||
<div class="center-block">
|
||||
|
@ -67,6 +67,13 @@ $fa-font-path: "../../../node_modules/@fortawesome/fontawesome-free-webfonts/web
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url('../../../node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url('../../../node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff');
|
||||
@ -106,7 +113,7 @@ body,
|
||||
position: relative;
|
||||
|
||||
color: $palette-theme-dark-disabled-foreground;
|
||||
margin: 0 60px 20px 60px;
|
||||
margin: 0 30px 16px 30px;
|
||||
padding-top: 15px;
|
||||
border-top: 2px solid $palette-theme-dark-soft-background;
|
||||
text-align: center;
|
||||
@ -134,7 +141,6 @@ body,
|
||||
font-size: 10px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
> span[os-open-external] {
|
||||
@ -155,13 +161,13 @@ body,
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 255px;
|
||||
height: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
margin: 20px 60px;
|
||||
margin: 20px 50px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
@ -174,3 +180,22 @@ body,
|
||||
padding-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
featured-project {
|
||||
webview {
|
||||
flex: 0 1;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
&.fp-visible webview {
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 30px;
|
||||
top: 45px;
|
||||
border-radius: 7px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ $palette-theme-light-background: #fff;
|
||||
$palette-theme-dark-soft-foreground: #ddd;
|
||||
$palette-theme-dark-soft-background: #64686a;
|
||||
$palette-theme-light-soft-foreground: #b3b3b3;
|
||||
$palette-theme-dark-disabled-background: #313339;
|
||||
$palette-theme-dark-disabled-background: #3a3c41;
|
||||
$palette-theme-dark-disabled-foreground: #787c7f;
|
||||
$palette-theme-light-disabled-background: #d5d5d5;
|
||||
$palette-theme-light-disabled-foreground: #787c7f;
|
||||
|
@ -17,14 +17,15 @@
|
||||
'use strict'
|
||||
|
||||
const styled = require('styled-components').default
|
||||
const { colors, consts } = require('./theme')
|
||||
const { colors } = require('./theme')
|
||||
const {
|
||||
Button, Txt, Flex
|
||||
} = require('rendition')
|
||||
|
||||
exports.StepButton = styled(Button) `
|
||||
width: 100%;
|
||||
max-width: ${consts.btnMaxWidth};
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
|
||||
@ -39,7 +40,6 @@ exports.StepButton = styled(Button) `
|
||||
`
|
||||
|
||||
exports.ChangeButton = styled(Button) `
|
||||
font-size: 12px;
|
||||
color: ${colors.primary.background};
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
|
@ -6075,7 +6075,7 @@ body {
|
||||
top: 2px;
|
||||
margin-right: 2px; }
|
||||
.button[disabled] {
|
||||
background-color: #313339;
|
||||
background-color: #3a3c41;
|
||||
color: #787c7f; }
|
||||
|
||||
.button-block {
|
||||
@ -6425,11 +6425,20 @@ svg-icon > img[disabled] {
|
||||
position: relative; }
|
||||
|
||||
.page-main .button-brick {
|
||||
min-width: 170px; }
|
||||
min-width: 170px;
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
font-size: 16px; }
|
||||
|
||||
.page-main .button-abort-write {
|
||||
margin-right: -35px;
|
||||
z-index: 1; }
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
position: absolute;
|
||||
right: -17px;
|
||||
top: 30%; }
|
||||
|
||||
.page-main .step-border-left, .page-main .step-border-right {
|
||||
height: 2px;
|
||||
@ -9872,6 +9881,12 @@ readers do not read off random characters that represent icons */
|
||||
font-weight: 400;
|
||||
font-style: normal; }
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../../../node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff");
|
||||
font-weight: 500;
|
||||
font-style: normal; }
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url("../../../node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff");
|
||||
@ -9901,7 +9916,7 @@ body,
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
color: #787c7f;
|
||||
margin: 0 60px 20px 60px;
|
||||
margin: 0 30px 16px 30px;
|
||||
padding-top: 15px;
|
||||
border-top: 2px solid #64686a;
|
||||
text-align: center; }
|
||||
@ -9917,8 +9932,7 @@ body,
|
||||
.section-footer .footer-right {
|
||||
font-size: 10px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%; }
|
||||
right: 0; }
|
||||
.section-footer > span[os-open-external] {
|
||||
display: flex; }
|
||||
|
||||
@ -9933,11 +9947,11 @@ body,
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 255px; }
|
||||
height: 320px; }
|
||||
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
margin: 20px 60px; }
|
||||
margin: 20px 50px; }
|
||||
|
||||
.section-header {
|
||||
text-align: right;
|
||||
@ -9946,3 +9960,18 @@ body,
|
||||
.section-header > .button {
|
||||
padding-left: 3px;
|
||||
padding-right: 3px; }
|
||||
|
||||
featured-project webview {
|
||||
flex: 0 1;
|
||||
height: 0;
|
||||
width: 0; }
|
||||
|
||||
featured-project.fp-visible webview {
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 30px;
|
||||
top: 45px;
|
||||
border-radius: 7px;
|
||||
overflow: hidden; }
|
||||
|
@ -35,7 +35,7 @@ let mainWindow = null
|
||||
const createMainWindow = () => {
|
||||
mainWindow = new electron.BrowserWindow({
|
||||
width: 800,
|
||||
height: 380,
|
||||
height: 480,
|
||||
useContentSize: true,
|
||||
show: false,
|
||||
resizable: Boolean(config.fullscreen),
|
||||
|
Loading…
x
Reference in New Issue
Block a user