mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-24 15:27:17 +00:00
Move the main controller to React
Change-type: patch Signed-off-by: Stevche Radevski <stevche@balena.io>
This commit is contained in:
parent
84fe5004a9
commit
8e47829905
@ -90,7 +90,7 @@ const app = angular.module('Etcher', [
|
||||
require('./components/safe-webview'),
|
||||
|
||||
// Pages
|
||||
require('./pages/main/main'),
|
||||
require('./pages/main/main.ts').MODULE_NAME,
|
||||
require('./pages/finish/finish'),
|
||||
require('./components/settings/index.ts').MODULE_NAME,
|
||||
|
||||
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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
|
@ -28,7 +28,6 @@ const messages = require('../../../../shared/messages')
|
||||
const supportedFormats = require('../../../../shared/supported-formats')
|
||||
const shared = require('../../../../shared/units')
|
||||
const selectionState = require('../../models/selection-state')
|
||||
const settings = require('../../models/settings')
|
||||
const store = require('../../models/store')
|
||||
const analytics = require('../../modules/analytics')
|
||||
const exceptionReporter = require('../../modules/exception-reporter')
|
||||
@ -41,8 +40,7 @@ const {
|
||||
Footer,
|
||||
Underline,
|
||||
DetailsText,
|
||||
ChangeButton,
|
||||
ThemedProvider
|
||||
ChangeButton
|
||||
} = require('../../styled-components')
|
||||
const {
|
||||
Modal
|
||||
@ -306,7 +304,7 @@ class ImageSelector extends React.Component {
|
||||
const imageSize = selectionState.getImageSize()
|
||||
|
||||
return (
|
||||
<ThemedProvider>
|
||||
<React.Fragment>
|
||||
<div className="box text-center relative">
|
||||
<Dropzone multiple={false} onDrop={this.handleOnDrop}>
|
||||
{({ getRootProps, getInputProps }) => (
|
||||
@ -394,7 +392,7 @@ class ImageSelector extends React.Component {
|
||||
{selectionState.getImagePath()}
|
||||
</Modal>
|
||||
)}
|
||||
</ThemedProvider>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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'
|
||||
|
||||
/* eslint-disable jsdoc/require-example */
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.ImageSelector
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.ImageSelector'
|
||||
const ImageSelector = angular.module(MODULE_NAME, [])
|
||||
|
||||
ImageSelector.component(
|
||||
'imageSelector',
|
||||
react2angular(require('./image-selector.jsx')),
|
||||
[],
|
||||
[
|
||||
'WarningModalService'
|
||||
]
|
||||
)
|
||||
module.exports = MODULE_NAME
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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
|
@ -17,15 +17,11 @@
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const prettyBytes = require('pretty-bytes')
|
||||
const propTypes = require('prop-types')
|
||||
const React = require('react')
|
||||
const {
|
||||
ThemedProvider
|
||||
} = require('../../styled-components')
|
||||
const driveConstraints = require('../../../../shared/drive-constraints')
|
||||
const utils = require('../../../../shared/utils')
|
||||
const TargetSelector = require('../../components/drive-selector/target-selector')
|
||||
const TargetSelector = require('../../components/drive-selector/target-selector.jsx')
|
||||
const SvgIcon = require('../../components/svg-icon/svg-icon.jsx')
|
||||
const selectionState = require('../../models/selection-state')
|
||||
const settings = require('../../models/settings')
|
||||
@ -150,44 +146,42 @@ const DriveSelector = ({
|
||||
const showStepConnectingLines = !webviewShowing || !flashing
|
||||
|
||||
return (
|
||||
<ThemedProvider>
|
||||
<div className="box text-center relative">
|
||||
<div className="box text-center relative">
|
||||
|
||||
{showStepConnectingLines && (
|
||||
<React.Fragment>
|
||||
<div
|
||||
className="step-border-left"
|
||||
disabled={disabled}
|
||||
></div>
|
||||
<div
|
||||
className="step-border-right"
|
||||
disabled={nextStepDisabled}
|
||||
></div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<div className="center-block">
|
||||
<SvgIcon
|
||||
paths={[ '../../assets/drive.svg' ]}
|
||||
{showStepConnectingLines && (
|
||||
<React.Fragment>
|
||||
<div
|
||||
className="step-border-left"
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
></div>
|
||||
<div
|
||||
className="step-border-right"
|
||||
disabled={nextStepDisabled}
|
||||
></div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<div className="space-vertical-large">
|
||||
<TargetSelector
|
||||
disabled={disabled}
|
||||
show={!hasDrive && showDrivesButton}
|
||||
tooltip={driveListLabel}
|
||||
selection={selectionState}
|
||||
openDriveSelector={() => openDriveSelector(DriveSelectorService)}
|
||||
reselectDrive={() => reselectDrive(DriveSelectorService)}
|
||||
flashing={flashing}
|
||||
constraints={driveConstraints}
|
||||
targets={targets}
|
||||
/>
|
||||
</div>
|
||||
<div className="center-block">
|
||||
<SvgIcon
|
||||
paths={[ '../../assets/drive.svg' ]}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</ThemedProvider>
|
||||
|
||||
<div className="space-vertical-large">
|
||||
<TargetSelector
|
||||
disabled={disabled}
|
||||
show={!hasDrive && showDrivesButton}
|
||||
tooltip={driveListLabel}
|
||||
selection={selectionState}
|
||||
openDriveSelector={() => openDriveSelector(DriveSelectorService)}
|
||||
reselectDrive={() => reselectDrive(DriveSelectorService)}
|
||||
flashing={flashing}
|
||||
constraints={driveConstraints}
|
||||
targets={targets}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,6 @@ const React = require('react')
|
||||
const _ = require('lodash')
|
||||
|
||||
const { Modal, Txt } = require('rendition')
|
||||
const { ThemedProvider } = require('../../styled-components')
|
||||
const messages = require('../../../../shared/messages')
|
||||
const flashState = require('../../models/flash-state')
|
||||
const driveScanner = require('../../modules/drive-scanner')
|
||||
@ -93,7 +92,7 @@ const flashImageToDrive = async ($timeout, $state) => {
|
||||
// otherwise Windows throws EPERM
|
||||
driveScanner.stop()
|
||||
|
||||
const iconPath = '../../../assets/icon.png'
|
||||
const iconPath = '../../assets/icon.png'
|
||||
const basename = path.basename(image.path)
|
||||
try {
|
||||
await imageWriter.flash(image.path, drives)
|
||||
@ -167,23 +166,13 @@ const Flash = ({
|
||||
shouldFlashStepBeDisabled, lastFlashErrorCode, progressMessage,
|
||||
$timeout, $state, DriveSelectorService
|
||||
}) => {
|
||||
// This is a hack to re-render the component whenever the global state changes. Remove once we get rid of angular and use redux correctly.
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
const setRefresh = React.useState(false)[1]
|
||||
const state = flashState.getFlashState()
|
||||
const isFlashing = flashState.isFlashing()
|
||||
const isFlashStepDisabled = shouldFlashStepBeDisabled()
|
||||
const flashErrorCode = lastFlashErrorCode()
|
||||
|
||||
const [ warningMessages, setWarningMessages ] = React.useState([])
|
||||
const [ errorMessage, setErrorMessage ] = React.useState('')
|
||||
|
||||
React.useEffect(() => {
|
||||
return store.observe(() => {
|
||||
setRefresh((ref) => !ref)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleWarningResponse = async (shouldContinue) => {
|
||||
setWarningMessages([])
|
||||
|
||||
@ -229,10 +218,10 @@ const Flash = ({
|
||||
setErrorMessage(await flashImageToDrive($timeout, $state))
|
||||
}
|
||||
|
||||
return <ThemedProvider>
|
||||
return <React.Fragment>
|
||||
<div className="box text-center">
|
||||
<div className="center-block">
|
||||
<SvgIcon paths={[ '../../assets/flash.svg' ]} disabled={isFlashStepDisabled}/>
|
||||
<SvgIcon paths={[ '../../assets/flash.svg' ]} disabled={shouldFlashStepBeDisabled}/>
|
||||
</div>
|
||||
|
||||
<div className="space-vertical-large">
|
||||
@ -242,7 +231,7 @@ const Flash = ({
|
||||
active={isFlashing}
|
||||
percentage={state.percentage}
|
||||
label={getProgressButtonLabel()}
|
||||
disabled={Boolean(flashErrorCode) || isFlashStepDisabled}
|
||||
disabled={Boolean(flashErrorCode) || shouldFlashStepBeDisabled}
|
||||
callback={tryFlash}>
|
||||
</ProgressButton>
|
||||
|
||||
@ -300,7 +289,7 @@ const Flash = ({
|
||||
</Modal>
|
||||
}
|
||||
|
||||
</ThemedProvider>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
module.exports = Flash
|
||||
|
142
lib/gui/app/pages/main/MainPage.tsx
Normal file
142
lib/gui/app/pages/main/MainPage.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2019 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 path from 'path';
|
||||
import * as React from 'react';
|
||||
import * as FeaturedProject from '../../components/featured-project/featured-project';
|
||||
import * as ImageSelector from '../../components/image-selector/image-selector';
|
||||
import * as ReducedFlashingInfos from '../../components/reduced-flashing-infos/reduced-flashing-infos';
|
||||
import * as flashState from '../../models/flash-state';
|
||||
import * as selectionState from '../../models/selection-state';
|
||||
import * as store from '../../models/store';
|
||||
import { ThemedProvider } from '../../styled-components';
|
||||
import * as middleEllipsis from '../../utils/middle-ellipsis';
|
||||
|
||||
import * as messages from '../../../../shared/messages';
|
||||
import { bytesToClosestUnit } from '../../../../shared/units';
|
||||
|
||||
import * as DriveSelector from './DriveSelector';
|
||||
import * as Flash from './Flash';
|
||||
|
||||
const getDrivesTitle = (selection: any) => {
|
||||
const drives = selection.getSelectedDrives();
|
||||
|
||||
if (drives.length === 1) {
|
||||
return drives[0].description || 'Untitled Device';
|
||||
}
|
||||
|
||||
if (drives.length === 0) {
|
||||
return 'No targets found';
|
||||
}
|
||||
|
||||
return `${drives.length} Targets`;
|
||||
};
|
||||
|
||||
const getImageBasename = (selection: any) => {
|
||||
if (!selection.hasImage()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const selectionImageName = selection.getImageName();
|
||||
const imageBasename = path.basename(selection.getImagePath());
|
||||
return selectionImageName || imageBasename;
|
||||
};
|
||||
|
||||
const MainPage = ({ DriveSelectorService, $timeout, $state }: any) => {
|
||||
const setRefresh = React.useState(false)[1];
|
||||
const [isWebviewShowing, setIsWebviewShowing] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
return (store as any).observe(() => {
|
||||
setRefresh(ref => !ref);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setWebviewShowing = (isShowing: boolean) => {
|
||||
setIsWebviewShowing(isShowing);
|
||||
store.dispatch({
|
||||
type: 'SET_WEBVIEW_SHOWING_STATUS',
|
||||
data: Boolean(isShowing),
|
||||
});
|
||||
};
|
||||
|
||||
const isFlashing = flashState.isFlashing();
|
||||
const shouldDriveStepBeDisabled = !selectionState.hasImage();
|
||||
const shouldFlashStepBeDisabled =
|
||||
!selectionState.hasDrive() || shouldDriveStepBeDisabled;
|
||||
const hasDrive = selectionState.hasDrive();
|
||||
const imageLogo = selectionState.getImageLogo();
|
||||
const imageSize = bytesToClosestUnit(selectionState.getImageSize());
|
||||
const imageName = middleEllipsis(getImageBasename(selectionState), 16);
|
||||
const driveTitle = middleEllipsis(getDrivesTitle(selectionState), 16);
|
||||
const shouldShowFlashingInfos = isFlashing && isWebviewShowing;
|
||||
const lastFlashErrorCode = flashState.getLastFlashErrorCode;
|
||||
const progressMessage = messages.progress;
|
||||
|
||||
return (
|
||||
<ThemedProvider style={{ display: 'flex', height: '100%' }}>
|
||||
<div className="page-main row around-xs">
|
||||
<div className="col-xs">
|
||||
<ImageSelector flashing={isFlashing} />
|
||||
</div>
|
||||
|
||||
<div className="col-xs">
|
||||
<DriveSelector
|
||||
DriveSelectorService={DriveSelectorService}
|
||||
webviewShowing={isWebviewShowing}
|
||||
disabled={shouldDriveStepBeDisabled}
|
||||
nextStepDisabled={shouldFlashStepBeDisabled}
|
||||
hasDrive={hasDrive}
|
||||
flashing={isFlashing}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isFlashing && (
|
||||
<div>
|
||||
<FeaturedProject
|
||||
className={
|
||||
isFlashing && isWebviewShowing ? 'fp-visible' : undefined
|
||||
}
|
||||
onWebviewShow={setWebviewShowing}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<ReducedFlashingInfos
|
||||
imageLogo={imageLogo}
|
||||
imageName={imageName}
|
||||
imageSize={imageSize}
|
||||
driveTitle={driveTitle}
|
||||
shouldShow={shouldShowFlashingInfos}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-xs">
|
||||
<Flash
|
||||
DriveSelectorService={DriveSelectorService}
|
||||
$timeout={$timeout}
|
||||
$state={$state}
|
||||
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
|
||||
lastFlashErrorCode={lastFlashErrorCode}
|
||||
progressMessage={progressMessage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ThemedProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainPage;
|
@ -1,165 +0,0 @@
|
||||
/*
|
||||
* 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 path = require('path')
|
||||
const store = require('../../../models/store')
|
||||
const settings = require('../../../models/settings')
|
||||
const flashState = require('../../../models/flash-state')
|
||||
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 (
|
||||
OSOpenExternalService,
|
||||
$filter,
|
||||
$scope
|
||||
) {
|
||||
// Expose several modules to the template for convenience
|
||||
this.selection = selectionState
|
||||
this.drives = availableDrives
|
||||
this.state = flashState
|
||||
this.settings = settings
|
||||
this.external = OSOpenExternalService
|
||||
this.constraints = driveConstraints
|
||||
this.progressMessage = messages.progress
|
||||
this.isWebviewShowing = Boolean(store.getState().toJS().isWebviewShowing)
|
||||
|
||||
// Trigger an update if the store changes
|
||||
store.observe(() => {
|
||||
if (!$scope.$$phase) {
|
||||
$scope.$apply()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @summary Determine if the drive step should be disabled
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {Boolean} whether the drive step should be disabled
|
||||
*
|
||||
* @example
|
||||
* if (MainController.shouldDriveStepBeDisabled()) {
|
||||
* console.log('The drive step should be disabled');
|
||||
* }
|
||||
*/
|
||||
this.shouldDriveStepBeDisabled = () => {
|
||||
return !selectionState.hasImage()
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Determine if the flash step should be disabled
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {Boolean} whether the flash step should be disabled
|
||||
*
|
||||
* @example
|
||||
* if (MainController.shouldFlashStepBeDisabled()) {
|
||||
* console.log('The flash step should be disabled');
|
||||
* }
|
||||
*/
|
||||
this.shouldFlashStepBeDisabled = () => {
|
||||
return !selectionState.hasDrive() || this.shouldDriveStepBeDisabled()
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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'
|
||||
|
||||
/**
|
||||
* This page represents the application main page.
|
||||
*
|
||||
* @module Etcher.Pages.Main
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
const MODULE_NAME = 'Etcher.Pages.Main'
|
||||
|
||||
const MainPage = angular.module(MODULE_NAME, [
|
||||
require('angular-ui-router'),
|
||||
|
||||
require('../../components/drive-selector/drive-selector'),
|
||||
require('../../components/image-selector'),
|
||||
require('../../components/featured-project'),
|
||||
require('../../components/reduced-flashing-infos'),
|
||||
require('../../components/flash-another'),
|
||||
require('../../components/flash-results'),
|
||||
require('../../components/drive-selector'),
|
||||
|
||||
require('../../os/open-external/open-external'),
|
||||
|
||||
require('../../utils/byte-size/byte-size'),
|
||||
require('../../utils/middle-ellipsis/filter')
|
||||
])
|
||||
|
||||
MainPage.controller('MainController', require('./controllers/main'))
|
||||
MainPage.component('driveSelector', react2angular(require('./DriveSelector.jsx'),
|
||||
[
|
||||
'webviewShowing',
|
||||
'disabled',
|
||||
'nextStepDisabled',
|
||||
'hasDrive',
|
||||
'flashing'
|
||||
],
|
||||
[ 'DriveSelectorService' ]
|
||||
))
|
||||
MainPage.component('flash', react2angular(require('./Flash.jsx'),
|
||||
[ 'shouldFlashStepBeDisabled', 'lastFlashErrorCode', 'progressMessage' ],
|
||||
[ '$timeout', '$state', 'DriveSelectorService' ]))
|
||||
|
||||
MainPage.config(($stateProvider) => {
|
||||
$stateProvider
|
||||
.state('main', {
|
||||
url: '/main',
|
||||
controller: 'MainController as main',
|
||||
template: require('./templates/main.tpl.html')
|
||||
})
|
||||
})
|
||||
|
||||
module.exports = MODULE_NAME
|
60
lib/gui/app/pages/main/main.ts
Normal file
60
lib/gui/app/pages/main/main.ts
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2019 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';
|
||||
|
||||
/**
|
||||
* This page represents the application main page.
|
||||
*
|
||||
* @module Etcher.Pages.Main
|
||||
*/
|
||||
|
||||
import * as angular from 'angular';
|
||||
// @ts-ignore
|
||||
import * as angularRouter from 'angular-ui-router';
|
||||
import { react2angular } from 'react2angular';
|
||||
import MainPage from './MainPage';
|
||||
|
||||
import * as driveSelector from '../../components/drive-selector';
|
||||
import * as driveSelectorService from '../../components/drive-selector/drive-selector';
|
||||
import * as flashAnother from '../../components/flash-another';
|
||||
import * as flashResults from '../../components/flash-results';
|
||||
import * as openExternal from '../../os/open-external/open-external';
|
||||
import * as byteSize from '../../utils/byte-size/byte-size';
|
||||
|
||||
export const MODULE_NAME = 'Etcher.Pages.Main';
|
||||
|
||||
const Main = angular.module(MODULE_NAME, [
|
||||
angularRouter,
|
||||
driveSelectorService,
|
||||
flashAnother,
|
||||
flashResults,
|
||||
driveSelector,
|
||||
openExternal,
|
||||
byteSize,
|
||||
]);
|
||||
|
||||
Main.component(
|
||||
'mainPage',
|
||||
react2angular(MainPage, [], ['DriveSelectorService', '$timeout', '$state']),
|
||||
);
|
||||
|
||||
Main.config(($stateProvider: any) => {
|
||||
$stateProvider.state('main', {
|
||||
url: '/main',
|
||||
template: '<main-page style="width:100%"></main-page>',
|
||||
});
|
||||
});
|
@ -1,46 +0,0 @@
|
||||
<div class="page-main row around-xs">
|
||||
<div class="col-xs">
|
||||
<image-selector
|
||||
flashing="main.state.isFlashing()"
|
||||
>
|
||||
</image-selector>
|
||||
</div>
|
||||
|
||||
<div class="col-xs">
|
||||
<drive-selector
|
||||
webview-showing="main.isWebviewShowing"
|
||||
disabled="main.shouldDriveStepBeDisabled()"
|
||||
next-step-disabled="main.shouldFlashStepBeDisabled()"
|
||||
has-drive="main.selection.hasDrive()"
|
||||
flashing="main.state.isFlashing()"
|
||||
></drive-selector>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<featured-project
|
||||
ng-if="main.state.isFlashing()"
|
||||
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">
|
||||
<flash
|
||||
should-flash-step-be-disabled="main.shouldFlashStepBeDisabled"
|
||||
last-flash-error-code="main.state.getLastFlashErrorCode"
|
||||
progress-image="main.progressImage"
|
||||
></flash>
|
||||
</div>
|
||||
</div>
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Juan Cruz Viotti. https://github.com/jviotti
|
||||
* 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'
|
||||
|
||||
/**
|
||||
* The purpose of this module is to provide utilities
|
||||
* to work with sizes in bytes.
|
||||
*
|
||||
* @module Etcher.Utils.MiddleEllipsis
|
||||
*/
|
||||
|
||||
const _ = require('lodash')
|
||||
const angular = require('angular')
|
||||
const middleEllipsis = require('../middle-ellipsis')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Utils.MiddleEllipsis'
|
||||
const MiddleEllipsis = angular.module(MODULE_NAME, [])
|
||||
|
||||
/* eslint-disable lodash/prefer-lodash-method */
|
||||
|
||||
MiddleEllipsis.filter('middleEllipsis', _.constant(middleEllipsis))
|
||||
|
||||
/* eslint-enable lodash/prefer-lodash-method */
|
||||
|
||||
module.exports = MODULE_NAME
|
@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 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 m = require('mochainon')
|
||||
const _ = require('lodash')
|
||||
const fs = require('fs')
|
||||
const angular = require('angular')
|
||||
const availableDrives = require('../../../lib/gui/app/models/available-drives')
|
||||
const selectionState = require('../../../lib/gui/app/models/selection-state')
|
||||
|
||||
// Mock HTML requires by reading from the file-system
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
require.extensions['.html'] = (module, filename) => {
|
||||
module.exports = fs.readFileSync(filename, {
|
||||
encoding: 'utf8'
|
||||
})
|
||||
}
|
||||
|
||||
// NOTE(Shou): since we don't test React yet we just ignore JSX files
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
require.extensions['.jsx'] = _.constant(null)
|
||||
|
||||
describe('Browser: MainPage', function () {
|
||||
beforeEach(angular.mock.module(
|
||||
require('../../../lib/gui/app/pages/main/main')
|
||||
))
|
||||
|
||||
describe('MainController', function () {
|
||||
let $controller
|
||||
|
||||
beforeEach(angular.mock.inject(function (_$controller_) {
|
||||
$controller = _$controller_
|
||||
}))
|
||||
|
||||
describe('.shouldDriveStepBeDisabled()', function () {
|
||||
it('should return true if there is no drive', function () {
|
||||
const controller = $controller('MainController', {
|
||||
$scope: {
|
||||
$apply: _.noop
|
||||
}
|
||||
})
|
||||
|
||||
selectionState.clear()
|
||||
|
||||
m.chai.expect(controller.shouldDriveStepBeDisabled()).to.be.true
|
||||
})
|
||||
|
||||
it('should return false if there is a drive', function () {
|
||||
const controller = $controller('MainController', {
|
||||
$scope: {
|
||||
$apply: _.noop
|
||||
}
|
||||
})
|
||||
|
||||
selectionState.selectImage({
|
||||
path: 'rpi.img',
|
||||
extension: 'img',
|
||||
size: 99999,
|
||||
isSizeEstimated: false
|
||||
})
|
||||
|
||||
m.chai.expect(controller.shouldDriveStepBeDisabled()).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('.shouldFlashStepBeDisabled()', function () {
|
||||
it('should return true if there is no selected drive nor image', function () {
|
||||
const controller = $controller('MainController', {
|
||||
$scope: {
|
||||
$apply: _.noop
|
||||
}
|
||||
})
|
||||
|
||||
selectionState.clear()
|
||||
|
||||
m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.true
|
||||
})
|
||||
|
||||
it('should return true if there is a selected image but no drive', function () {
|
||||
const controller = $controller('MainController', {
|
||||
$scope: {
|
||||
$apply: _.noop
|
||||
}
|
||||
})
|
||||
|
||||
selectionState.clear()
|
||||
selectionState.selectImage({
|
||||
path: 'rpi.img',
|
||||
extension: 'img',
|
||||
size: 99999,
|
||||
isSizeEstimated: false
|
||||
})
|
||||
|
||||
m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.true
|
||||
})
|
||||
|
||||
it('should return true if there is a selected drive but no image', function () {
|
||||
const controller = $controller('MainController', {
|
||||
$scope: {
|
||||
$apply: _.noop
|
||||
}
|
||||
})
|
||||
|
||||
availableDrives.setDrives([
|
||||
{
|
||||
device: '/dev/disk2',
|
||||
description: 'Foo',
|
||||
size: 99999,
|
||||
mountpoint: '/mnt/foo',
|
||||
system: false
|
||||
}
|
||||
])
|
||||
|
||||
selectionState.clear()
|
||||
selectionState.selectDrive('/dev/disk2')
|
||||
|
||||
m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.true
|
||||
})
|
||||
|
||||
it('should return false if there is a selected drive and a selected image', function () {
|
||||
const controller = $controller('MainController', {
|
||||
$scope: {
|
||||
$apply: _.noop
|
||||
}
|
||||
})
|
||||
|
||||
availableDrives.setDrives([
|
||||
{
|
||||
device: '/dev/disk2',
|
||||
description: 'Foo',
|
||||
size: 99999,
|
||||
mountpoint: '/mnt/foo',
|
||||
system: false
|
||||
}
|
||||
])
|
||||
|
||||
selectionState.clear()
|
||||
selectionState.selectDrive('/dev/disk2')
|
||||
|
||||
selectionState.selectImage({
|
||||
path: 'rpi.img',
|
||||
extension: 'img',
|
||||
size: 99999,
|
||||
isSizeEstimated: false
|
||||
})
|
||||
|
||||
m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.false
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('page template', function () {
|
||||
let $state
|
||||
|
||||
beforeEach(angular.mock.inject(function (_$state_) {
|
||||
$state = _$state_
|
||||
}))
|
||||
|
||||
it('should match the file contents', function () {
|
||||
const {
|
||||
template
|
||||
} = $state.get('main')
|
||||
const contents = fs.readFileSync('lib/gui/app/pages/main/templates/main.tpl.html', {
|
||||
encoding: 'utf-8'
|
||||
})
|
||||
m.chai.expect(template).to.equal(contents)
|
||||
})
|
||||
})
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user