mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-24 19:56:37 +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 { ProgressBar, Provider } = require('rendition')
|
||||||
|
|
||||||
const { colors, consts } = require('./../../theme')
|
const { colors } = require('./../../theme')
|
||||||
const { StepButton, StepSelection } = require('./../../styled-components')
|
const { StepButton, StepSelection } = require('./../../styled-components')
|
||||||
|
|
||||||
const darkenForegroundStripes = 0.18
|
const darkenForegroundStripes = 0.18
|
||||||
@ -51,13 +51,16 @@ const ProgressButtonStripes = keyframes `
|
|||||||
|
|
||||||
const FlashProgressBar = styled(ProgressBar) `
|
const FlashProgressBar = styled(ProgressBar) `
|
||||||
> div {
|
> div {
|
||||||
|
width: 200px;
|
||||||
|
height: 48px;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
text-shadow: none !important;
|
text-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
width: 100%;
|
width: 200px;
|
||||||
max-width: ${consts.btnMaxWidth};
|
height: 48px;
|
||||||
margin: auto;
|
font-size: 16px;
|
||||||
|
line-height: 48px;
|
||||||
|
|
||||||
background: ${Color(colors.warning.background).darken(darkenForegroundStripes).string()};
|
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 _ = require('lodash')
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const angular = require('angular')
|
|
||||||
const react = require('react')
|
const react = require('react')
|
||||||
const propTypes = require('prop-types')
|
const propTypes = require('prop-types')
|
||||||
const { react2angular } = require('react2angular')
|
const analytics = require('../../modules/analytics')
|
||||||
const analytics = require('../modules/analytics')
|
const store = require('../../models/store')
|
||||||
const store = require('../models/store')
|
const settings = require('../../models/settings')
|
||||||
const settings = require('../models/settings')
|
const packageJSON = require('../../../../../package.json')
|
||||||
const packageJSON = require('../../../../package.json')
|
|
||||||
|
|
||||||
const MODULE_NAME = 'Etcher.Components.SafeWebview'
|
|
||||||
const angularSafeWebview = angular.module(MODULE_NAME, [])
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Electron session identifier
|
* @summary Electron session identifier
|
||||||
@ -207,6 +202,9 @@ class SafeWebview extends react.PureComponent {
|
|||||||
this.setState({
|
this.setState({
|
||||||
shouldShow: event.httpResponseCode === HTTP_OK
|
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
|
* @summary Refresh the webview
|
||||||
*/
|
*/
|
||||||
refreshNow: propTypes.bool
|
refreshNow: propTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Webview lifecycle event
|
||||||
|
*/
|
||||||
|
onWebviewShow: propTypes.func
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
angularSafeWebview.component('safeWebview', react2angular(SafeWebview))
|
module.exports = SafeWebview
|
||||||
|
|
||||||
module.exports = MODULE_NAME
|
|
@ -22,162 +22,11 @@
|
|||||||
* @module Etcher.Components.SVGIcon
|
* @module Etcher.Components.SVGIcon
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const _ = require('lodash')
|
|
||||||
const angular = require('angular')
|
const angular = require('angular')
|
||||||
const react = require('react')
|
|
||||||
const propTypes = require('prop-types')
|
|
||||||
const react2angular = require('react2angular').react2angular
|
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 MODULE_NAME = 'Etcher.Components.SVGIcon'
|
||||||
const angularSVGIcon = angular.module(MODULE_NAME, [])
|
const angularSVGIcon = angular.module(MODULE_NAME, [])
|
||||||
|
|
||||||
const DEFAULT_SIZE = '40px'
|
angularSVGIcon.component('svgIcon', react2angular(require('./svg-icon/svg-icon.jsx')))
|
||||||
|
|
||||||
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))
|
|
||||||
module.exports = MODULE_NAME
|
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_DRIVE',
|
||||||
'DESELECT_IMAGE',
|
'DESELECT_IMAGE',
|
||||||
'SET_APPLICATION_SESSION_UUID',
|
'SET_APPLICATION_SESSION_UUID',
|
||||||
'SET_FLASHING_WORKFLOW_UUID'
|
'SET_FLASHING_WORKFLOW_UUID',
|
||||||
|
'SET_WEBVIEW_SHOWING_STATUS'
|
||||||
], (message) => {
|
], (message) => {
|
||||||
return [ message, message ]
|
return [ message, message ]
|
||||||
}))
|
}))
|
||||||
@ -511,6 +512,10 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
|
|||||||
return state.set('flashingWorkflowUuid', action.data)
|
return state.set('flashingWorkflowUuid', action.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ACTIONS.SET_WEBVIEW_SHOWING_STATUS: {
|
||||||
|
return state.set('isWebviewShowing', action.data)
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
const path = require('path')
|
||||||
const store = require('../../../models/store')
|
const store = require('../../../models/store')
|
||||||
const settings = require('../../../models/settings')
|
const settings = require('../../../models/settings')
|
||||||
const flashState = require('../../../models/flash-state')
|
const flashState = require('../../../models/flash-state')
|
||||||
@ -25,10 +26,12 @@ const availableDrives = require('../../../models/available-drives')
|
|||||||
const selectionState = require('../../../models/selection-state')
|
const selectionState = require('../../../models/selection-state')
|
||||||
const driveConstraints = require('../../../../../shared/drive-constraints')
|
const driveConstraints = require('../../../../../shared/drive-constraints')
|
||||||
const messages = require('../../../../../shared/messages')
|
const messages = require('../../../../../shared/messages')
|
||||||
|
const prettyBytes = require('pretty-bytes')
|
||||||
|
|
||||||
module.exports = function (
|
module.exports = function (
|
||||||
TooltipModalService,
|
TooltipModalService,
|
||||||
OSOpenExternalService
|
OSOpenExternalService,
|
||||||
|
$filter
|
||||||
) {
|
) {
|
||||||
// Expose several modules to the template for convenience
|
// Expose several modules to the template for convenience
|
||||||
this.selection = selectionState
|
this.selection = selectionState
|
||||||
@ -38,6 +41,7 @@ module.exports = function (
|
|||||||
this.external = OSOpenExternalService
|
this.external = OSOpenExternalService
|
||||||
this.constraints = driveConstraints
|
this.constraints = driveConstraints
|
||||||
this.progressMessage = messages.progress
|
this.progressMessage = messages.progress
|
||||||
|
this.isWebviewShowing = Boolean(store.getState().toJS().isWebviewShowing)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Determine if the drive step should be disabled
|
* @summary Determine if the drive step should be disabled
|
||||||
@ -93,4 +97,87 @@ module.exports = function (
|
|||||||
message: selectionState.getImagePath()
|
message: selectionState.getImagePath()
|
||||||
}).catch(exceptionReporter.report)
|
}).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/image-selector'),
|
||||||
require('../../components/warning-modal/warning-modal'),
|
require('../../components/warning-modal/warning-modal'),
|
||||||
require('../../components/file-selector'),
|
require('../../components/file-selector'),
|
||||||
|
require('../../components/featured-project'),
|
||||||
|
require('../../components/reduced-flashing-infos'),
|
||||||
|
|
||||||
require('../../os/open-external/open-external'),
|
require('../../os/open-external/open-external'),
|
||||||
require('../../os/dropzone/dropzone'),
|
require('../../os/dropzone/dropzone'),
|
||||||
|
@ -48,11 +48,20 @@ svg-icon > img[disabled] {
|
|||||||
|
|
||||||
.page-main .button-brick {
|
.page-main .button-brick {
|
||||||
min-width: $btn-min-width;
|
min-width: $btn-min-width;
|
||||||
|
width: 200px;
|
||||||
|
height: 48px;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-main .button-abort-write {
|
.page-main .button-abort-write {
|
||||||
margin-right: -35px;
|
width: 20px;
|
||||||
z-index: 1;
|
height: 20px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
position: absolute;
|
||||||
|
right: -17px;
|
||||||
|
top: 30%;
|
||||||
}
|
}
|
||||||
|
|
||||||
%step-border {
|
%step-border {
|
||||||
|
@ -26,8 +26,8 @@
|
|||||||
|
|
||||||
<div class="col-xs" ng-controller="DriveSelectionController as drive">
|
<div class="col-xs" ng-controller="DriveSelectionController as drive">
|
||||||
<div class="box text-center relative">
|
<div class="box text-center relative">
|
||||||
<div class="step-border-left" ng-disabled="main.shouldDriveStepBeDisabled()"></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()"></div>
|
<div class="step-border-right" ng-disabled="main.shouldFlashStepBeDisabled()" ng-hide="main.state.isFlashing() && main.isWebviewShowing"></div>
|
||||||
|
|
||||||
<div class="center-block">
|
<div class="center-block">
|
||||||
<svg-icon paths="[ '../../assets/drive.svg' ]"
|
<svg-icon paths="[ '../../assets/drive.svg' ]"
|
||||||
@ -90,6 +90,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</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="col-xs" ng-controller="FlashController as flash">
|
||||||
<div class="box text-center">
|
<div class="box text-center">
|
||||||
<div class="center-block">
|
<div class="center-block">
|
||||||
|
@ -67,6 +67,13 @@ $fa-font-path: "../../../node_modules/@fortawesome/fontawesome-free-webfonts/web
|
|||||||
font-style: normal;
|
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-face {
|
||||||
font-family: Roboto;
|
font-family: Roboto;
|
||||||
src: url('../../../node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff');
|
src: url('../../../node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff');
|
||||||
@ -106,7 +113,7 @@ body,
|
|||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
color: $palette-theme-dark-disabled-foreground;
|
color: $palette-theme-dark-disabled-foreground;
|
||||||
margin: 0 60px 20px 60px;
|
margin: 0 30px 16px 30px;
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
border-top: 2px solid $palette-theme-dark-soft-background;
|
border-top: 2px solid $palette-theme-dark-soft-background;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -134,7 +141,6 @@ body,
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> span[os-open-external] {
|
> span[os-open-external] {
|
||||||
@ -155,13 +161,13 @@ body,
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 255px;
|
height: 320px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 20px 60px;
|
margin: 20px 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
@ -174,3 +180,22 @@ body,
|
|||||||
padding-right: 3px;
|
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-foreground: #ddd;
|
||||||
$palette-theme-dark-soft-background: #64686a;
|
$palette-theme-dark-soft-background: #64686a;
|
||||||
$palette-theme-light-soft-foreground: #b3b3b3;
|
$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-dark-disabled-foreground: #787c7f;
|
||||||
$palette-theme-light-disabled-background: #d5d5d5;
|
$palette-theme-light-disabled-background: #d5d5d5;
|
||||||
$palette-theme-light-disabled-foreground: #787c7f;
|
$palette-theme-light-disabled-foreground: #787c7f;
|
||||||
|
@ -17,14 +17,15 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const styled = require('styled-components').default
|
const styled = require('styled-components').default
|
||||||
const { colors, consts } = require('./theme')
|
const { colors } = require('./theme')
|
||||||
const {
|
const {
|
||||||
Button, Txt, Flex
|
Button, Txt, Flex
|
||||||
} = require('rendition')
|
} = require('rendition')
|
||||||
|
|
||||||
exports.StepButton = styled(Button) `
|
exports.StepButton = styled(Button) `
|
||||||
width: 100%;
|
width: 200px;
|
||||||
max-width: ${consts.btnMaxWidth};
|
height: 48px;
|
||||||
|
font-size: 16px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@ -39,7 +40,6 @@ exports.StepButton = styled(Button) `
|
|||||||
`
|
`
|
||||||
|
|
||||||
exports.ChangeButton = styled(Button) `
|
exports.ChangeButton = styled(Button) `
|
||||||
font-size: 12px;
|
|
||||||
color: ${colors.primary.background};
|
color: ${colors.primary.background};
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -6075,7 +6075,7 @@ body {
|
|||||||
top: 2px;
|
top: 2px;
|
||||||
margin-right: 2px; }
|
margin-right: 2px; }
|
||||||
.button[disabled] {
|
.button[disabled] {
|
||||||
background-color: #313339;
|
background-color: #3a3c41;
|
||||||
color: #787c7f; }
|
color: #787c7f; }
|
||||||
|
|
||||||
.button-block {
|
.button-block {
|
||||||
@ -6425,11 +6425,20 @@ svg-icon > img[disabled] {
|
|||||||
position: relative; }
|
position: relative; }
|
||||||
|
|
||||||
.page-main .button-brick {
|
.page-main .button-brick {
|
||||||
min-width: 170px; }
|
min-width: 170px;
|
||||||
|
width: 200px;
|
||||||
|
height: 48px;
|
||||||
|
font-size: 16px; }
|
||||||
|
|
||||||
.page-main .button-abort-write {
|
.page-main .button-abort-write {
|
||||||
margin-right: -35px;
|
width: 20px;
|
||||||
z-index: 1; }
|
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 {
|
.page-main .step-border-left, .page-main .step-border-right {
|
||||||
height: 2px;
|
height: 2px;
|
||||||
@ -9872,6 +9881,12 @@ readers do not read off random characters that represent icons */
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal; }
|
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-face {
|
||||||
font-family: Roboto;
|
font-family: Roboto;
|
||||||
src: url("../../../node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff");
|
src: url("../../../node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff");
|
||||||
@ -9901,7 +9916,7 @@ body,
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #787c7f;
|
color: #787c7f;
|
||||||
margin: 0 60px 20px 60px;
|
margin: 0 30px 16px 30px;
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
border-top: 2px solid #64686a;
|
border-top: 2px solid #64686a;
|
||||||
text-align: center; }
|
text-align: center; }
|
||||||
@ -9917,8 +9932,7 @@ body,
|
|||||||
.section-footer .footer-right {
|
.section-footer .footer-right {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0; }
|
||||||
top: 50%; }
|
|
||||||
.section-footer > span[os-open-external] {
|
.section-footer > span[os-open-external] {
|
||||||
display: flex; }
|
display: flex; }
|
||||||
|
|
||||||
@ -9933,11 +9947,11 @@ body,
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 255px; }
|
height: 320px; }
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 20px 60px; }
|
margin: 20px 50px; }
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@ -9946,3 +9960,18 @@ body,
|
|||||||
.section-header > .button {
|
.section-header > .button {
|
||||||
padding-left: 3px;
|
padding-left: 3px;
|
||||||
padding-right: 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 = () => {
|
const createMainWindow = () => {
|
||||||
mainWindow = new electron.BrowserWindow({
|
mainWindow = new electron.BrowserWindow({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 380,
|
height: 480,
|
||||||
useContentSize: true,
|
useContentSize: true,
|
||||||
show: false,
|
show: false,
|
||||||
resizable: Boolean(config.fullscreen),
|
resizable: Boolean(config.fullscreen),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user