diff --git a/lib/gui/app/components/finish/finish.tsx b/lib/gui/app/components/finish/finish.tsx index 462833ca..5e19c7ce 100644 --- a/lib/gui/app/components/finish/finish.tsx +++ b/lib/gui/app/components/finish/finish.tsx @@ -27,7 +27,7 @@ import { updateLock } from '../../modules/update-lock'; import { open as openExternal } from '../../os/open-external/services/open-external'; import { FlashAnother } from '../flash-another/flash-another'; import { FlashResults } from '../flash-results/flash-results'; -import * as SVGIcon from '../svg-icon/svg-icon'; +import { SVGIcon } from '../svg-icon/svg-icon'; const restart = (options: any, goToMain: () => void) => { const { diff --git a/lib/gui/app/components/image-selector/image-selector.jsx b/lib/gui/app/components/image-selector/image-selector.jsx index 07faa88f..aae7ffd3 100644 --- a/lib/gui/app/components/image-selector/image-selector.jsx +++ b/lib/gui/app/components/image-selector/image-selector.jsx @@ -46,7 +46,7 @@ const { Modal } = require('rendition') const { middleEllipsis } = require('../../utils/middle-ellipsis') -const SVGIcon = require('../svg-icon/svg-icon.jsx') +const { SVGIcon } = require('../svg-icon/svg-icon') const { default: styled } = require('styled-components') // TODO move these styles to rendition diff --git a/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.jsx b/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.jsx index 0fdd161c..05a9855a 100644 --- a/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.jsx +++ b/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.jsx @@ -20,7 +20,8 @@ const React = require('react') const propTypes = require('prop-types') const styled = require('styled-components').default const { color } = require('styled-system') -const SvgIcon = require('../svg-icon/svg-icon.jsx') + +const { SVGIcon } = require('../svg-icon/svg-icon') const Div = styled.div ` position: absolute; @@ -57,13 +58,13 @@ const ReducedFlashingInfos = (props) => { return (props.shouldShow) ? (
- + { props.imageName } { props.imageSize } - + { props.driveTitle }
diff --git a/lib/gui/app/components/svg-icon/svg-icon.jsx b/lib/gui/app/components/svg-icon/svg-icon.jsx deleted file mode 100644 index 50c99bd9..00000000 --- a/lib/gui/app/components/svg-icon/svg-icon.jsx +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2018 balena.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('') - * - * 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 - }) - } -} - -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 diff --git a/lib/gui/app/components/svg-icon/svg-icon.tsx b/lib/gui/app/components/svg-icon/svg-icon.tsx new file mode 100644 index 00000000..61efcebe --- /dev/null +++ b/lib/gui/app/components/svg-icon/svg-icon.tsx @@ -0,0 +1,142 @@ +/* + * Copyright 2018 balena.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. + */ + +import * as fs from 'fs'; +import * as _ from 'lodash'; +import * as path from 'path'; +import * as React from 'react'; + +import * as analytics from '../../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('') + * + * img.src = encodedSVG + */ +function tryParseSVGContents(contents: string) { + 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; +} + +interface SVGIconProps { + // Paths to SVG files to be tried in succession if any fails + paths: string[]; + // List of embedded SVG contents to be tried in succession if any fails + contents?: string[]; + // SVG image width unit + width?: string; + // SVG image height unit + height?: string; + // Should the element visually appear grayed out and disabled? + disabled?: boolean; +} + +/** + * @summary SVG element that takes both filepaths and file contents + */ +export class SVGIcon extends React.Component { + public 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. + let baseDirectory: string; + if (path.isAbsolute(__dirname)) { + baseDirectory = path.join(__dirname, '..'); + } else { + // @ts-ignore + baseDirectory = 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 = 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 ( + + ); + } +} diff --git a/lib/gui/app/pages/main/DriveSelector.tsx b/lib/gui/app/pages/main/DriveSelector.tsx index a8cff995..132f475a 100644 --- a/lib/gui/app/pages/main/DriveSelector.tsx +++ b/lib/gui/app/pages/main/DriveSelector.tsx @@ -20,7 +20,7 @@ import styled from 'styled-components'; import * as driveConstraints from '../../../../shared/drive-constraints'; import * as DriveSelectorModal from '../../components/drive-selector/DriveSelectorModal.jsx'; import * as TargetSelector from '../../components/drive-selector/target-selector.jsx'; -import * as SvgIcon from '../../components/svg-icon/svg-icon.jsx'; +import { SVGIcon } from '../../components/svg-icon/svg-icon'; import * as selectionState from '../../models/selection-state'; import * as settings from '../../models/settings'; import { observe, store } from '../../models/store'; @@ -105,7 +105,7 @@ export const DriveSelector = ({ )}
- +
diff --git a/lib/gui/app/pages/main/Flash.tsx b/lib/gui/app/pages/main/Flash.tsx index c77e591e..d83bb075 100644 --- a/lib/gui/app/pages/main/Flash.tsx +++ b/lib/gui/app/pages/main/Flash.tsx @@ -22,7 +22,7 @@ import * as constraints from '../../../../shared/drive-constraints'; import * as messages from '../../../../shared/messages'; import * as DriveSelectorModal from '../../components/drive-selector/DriveSelectorModal.jsx'; import * as ProgressButton from '../../components/progress-button/progress-button.jsx'; -import * as SvgIcon from '../../components/svg-icon/svg-icon.jsx'; +import { SVGIcon } from '../../components/svg-icon/svg-icon'; import * as availableDrives from '../../models/available-drives'; import * as flashState from '../../models/flash-state'; import * as selection from '../../models/selection-state'; @@ -222,7 +222,7 @@ export const Flash = ({ shouldFlashStepBeDisabled, goToSuccess }: any) => {
- diff --git a/lib/gui/app/pages/main/MainPage.tsx b/lib/gui/app/pages/main/MainPage.tsx index ea7cc8f4..392c6cb9 100644 --- a/lib/gui/app/pages/main/MainPage.tsx +++ b/lib/gui/app/pages/main/MainPage.tsx @@ -26,7 +26,7 @@ import * as ImageSelector from '../../components/image-selector/image-selector'; import * as ReducedFlashingInfos from '../../components/reduced-flashing-infos/reduced-flashing-infos'; import * as SafeWebview from '../../components/safe-webview/safe-webview'; import { SettingsModal } from '../../components/settings/settings'; -import * as SvgIcon from '../../components/svg-icon/svg-icon.jsx'; +import { SVGIcon } from '../../components/svg-icon/svg-icon'; import * as flashState from '../../models/flash-state'; import * as selectionState from '../../models/selection-state'; import * as settings from '../../models/settings'; @@ -139,11 +139,11 @@ export class MainPage extends React.Component< } tabIndex={100} > - + />