mirror of
https://github.com/balena-io/etcher.git
synced 2025-08-02 07:57:44 +00:00
wip
This commit is contained in:
parent
96c865f14a
commit
1398ca2931
@ -21,12 +21,12 @@
|
|||||||
import * as angular from 'angular';
|
import * as angular from 'angular';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
|
|
||||||
const MODULE_NAME = 'Etcher.Components.TargetSelector'
|
const MODULE_NAME = 'Etcher.Components.TargetSelector';
|
||||||
const SelectTargetButton = angular.module(MODULE_NAME, [])
|
const SelectTargetButton = angular.module(MODULE_NAME, []);
|
||||||
|
|
||||||
SelectTargetButton.component(
|
SelectTargetButton.component(
|
||||||
'targetSelector',
|
'targetSelector',
|
||||||
react2angular(require('./target-selector.jsx'))
|
react2angular(require('./target-selector.jsx')),
|
||||||
)
|
);
|
||||||
|
|
||||||
export = MODULE_NAME;
|
export = MODULE_NAME;
|
||||||
|
@ -15,260 +15,259 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Meter } from 'grommet';
|
import { Meter } from 'grommet';
|
||||||
|
import { sortBy } from 'lodash';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Badge, Modal, Table } from 'rendition';
|
import { Badge, Modal, Table } from 'rendition';
|
||||||
|
|
||||||
import { getDrives } from '../../models/available-drives';
|
import { getDrives } from '../../models/available-drives';
|
||||||
import {
|
import { COMPATIBILITY_STATUS_TYPES } from '../../modules/drive-constraints';
|
||||||
COMPATIBILITY_STATUS_TYPES,
|
|
||||||
getDriveImageCompatibilityStatuses,
|
|
||||||
isDriveValid,
|
|
||||||
} from '../../modules/drive-constraints';
|
|
||||||
import {
|
|
||||||
deselectDrive,
|
|
||||||
getImage,
|
|
||||||
isDriveSelected,
|
|
||||||
selectDrive,
|
|
||||||
} from '../../models/selection-state';
|
|
||||||
import { subscribe } from '../../models/store';
|
import { subscribe } from '../../models/store';
|
||||||
import { ThemedProvider } from '../../styled-components';
|
import { ThemedProvider } from '../../styled-components';
|
||||||
import { bytesToClosestUnit } from '../../modules/units';
|
import { bytesToClosestUnit } from '../../modules/units';
|
||||||
|
|
||||||
interface Drive {
|
interface Drive {
|
||||||
description: string;
|
description: string;
|
||||||
device: string;
|
device: string;
|
||||||
isSystem: boolean;
|
isSystem: boolean;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
size?: number;
|
size?: number;
|
||||||
link?: string;
|
link?: string;
|
||||||
linkCTA?: string;
|
linkCTA?: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
}
|
|
||||||
|
|
||||||
interface Image {
|
|
||||||
path: string;
|
|
||||||
size: number;
|
|
||||||
url: string;
|
|
||||||
name: string;
|
|
||||||
supportUrl: string;
|
|
||||||
recommendedDriveSize: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CompatibilityStatus {
|
interface CompatibilityStatus {
|
||||||
type: number;
|
type: number;
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DriveSelectorProps {
|
interface DriveSelectorProps {
|
||||||
close: () => void;
|
close: () => void;
|
||||||
unique: boolean; // TODO
|
selectDrive: (drive: Drive) => void;
|
||||||
|
deselectDrive: (drive: Drive) => void;
|
||||||
|
isDriveSelected: (drive: Drive) => boolean;
|
||||||
|
isDriveValid: (drive: Drive) => boolean;
|
||||||
|
getDriveBadges: (drive: Drive) => CompatibilityStatus[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DriveSelectorState {
|
interface DriveSelectorState {
|
||||||
drives: Drive[];
|
drives: Drive[];
|
||||||
selected: Drive[];
|
selected: Drive[];
|
||||||
image: Image;
|
disabledDrives: string[];
|
||||||
disabledDrives: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const modalStyle = {
|
const modalStyle = {
|
||||||
width: '800px',
|
width: '800px',
|
||||||
height: '600px',
|
height: '600px',
|
||||||
paddingTop: '20px',
|
paddingTop: '20px',
|
||||||
paddingLeft: '30px',
|
paddingLeft: '30px',
|
||||||
paddingRight: '30px',
|
paddingRight: '30px',
|
||||||
paddingBottom: '11px',
|
paddingBottom: '11px',
|
||||||
}
|
};
|
||||||
|
|
||||||
const titleStyle = {
|
const titleStyle = {
|
||||||
color: '#2a506f',
|
color: '#2a506f',
|
||||||
}
|
};
|
||||||
|
|
||||||
const subtitleStyle = {
|
const subtitleStyle = {
|
||||||
marginLeft: '10px',
|
marginLeft: '10px',
|
||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
color: '#5b82a7',
|
color: '#5b82a7',
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapperStyle = {
|
const wrapperStyle = {
|
||||||
height: '250px',
|
height: '250px',
|
||||||
overflowX: 'hidden' as 'hidden',
|
overflowX: 'hidden' as 'hidden',
|
||||||
overflowY: 'auto' as 'auto',
|
overflowY: 'auto' as 'auto',
|
||||||
}
|
};
|
||||||
|
|
||||||
export class DriveSelector2 extends React.Component<DriveSelectorProps, DriveSelectorState> {
|
export class DriveSelector2 extends React.Component<
|
||||||
private table: Table<Drive> | null = null;
|
DriveSelectorProps,
|
||||||
private columns: {
|
DriveSelectorState
|
||||||
field: keyof Drive,
|
> {
|
||||||
label: string,
|
private table: Table<Drive> | null = null;
|
||||||
render?: (value: any, row: Drive) => string | number | JSX.Element | null,
|
private columns: {
|
||||||
}[];
|
field: keyof Drive;
|
||||||
private unsubscribe?: () => void;
|
label: string;
|
||||||
|
render?: (value: any, row: Drive) => string | number | JSX.Element | null;
|
||||||
constructor(props: DriveSelectorProps) {
|
}[];
|
||||||
super(props);
|
private unsubscribe?: () => void;
|
||||||
this.columns = [
|
|
||||||
{
|
constructor(props: DriveSelectorProps) {
|
||||||
field: 'description',
|
super(props);
|
||||||
label: 'Name',
|
this.columns = [
|
||||||
} as const,
|
{
|
||||||
{
|
field: 'description',
|
||||||
field: 'size',
|
label: 'Name',
|
||||||
label: 'Size',
|
} as const,
|
||||||
render: this.renderSize.bind(this),
|
{
|
||||||
} as const,
|
field: 'size',
|
||||||
{
|
label: 'Size',
|
||||||
field: 'displayName',
|
render: this.renderSize.bind(this),
|
||||||
label: 'Location',
|
} as const,
|
||||||
render: this.renderLocation.bind(this),
|
{
|
||||||
} as const,
|
field: 'displayName',
|
||||||
{
|
label: 'Location',
|
||||||
field: 'isReadOnly', // We don't use this, but a valid field that is not used in another column is required
|
render: this.renderLocation.bind(this),
|
||||||
label: ' ',
|
} as const,
|
||||||
render: this.renderBadges.bind(this),
|
{
|
||||||
} as const,
|
field: 'isReadOnly', // We don't use this, but a valid field that is not used in another column is required
|
||||||
];
|
label: ' ',
|
||||||
this.state = this.getNewState();
|
render: this.renderBadges.bind(this),
|
||||||
}
|
} as const,
|
||||||
|
];
|
||||||
public componentDidMount() {
|
this.state = this.getNewState();
|
||||||
this.update();
|
}
|
||||||
if (this.unsubscribe === undefined) {
|
|
||||||
this.unsubscribe = subscribe(this.update.bind(this));
|
public componentDidMount() {
|
||||||
}
|
this.update();
|
||||||
}
|
if (this.unsubscribe === undefined) {
|
||||||
|
this.unsubscribe = subscribe(this.update.bind(this));
|
||||||
public componentWillUnmount() {
|
}
|
||||||
if (this.unsubscribe !== undefined) {
|
}
|
||||||
this.unsubscribe();
|
|
||||||
this.unsubscribe = undefined;
|
public componentWillUnmount() {
|
||||||
}
|
if (this.unsubscribe !== undefined) {
|
||||||
}
|
this.unsubscribe();
|
||||||
|
this.unsubscribe = undefined;
|
||||||
private getNewState() {
|
}
|
||||||
const drives: Drive[] = getDrives();
|
}
|
||||||
for (let i = 0; i < drives.length; i++) {
|
|
||||||
drives[i] = {...drives[i]};
|
private getNewState() {
|
||||||
}
|
let drives: Drive[] = getDrives();
|
||||||
const selected = drives.filter(d => isDriveSelected(d.device));
|
for (let i = 0; i < drives.length; i++) {
|
||||||
const image = getImage();
|
drives[i] = { ...drives[i] };
|
||||||
const disabledDrives = drives.filter(d => !isDriveValid(d, image)).map(d => d.device);
|
}
|
||||||
return { drives, disabledDrives, image, selected };
|
drives = sortBy(drives, 'device');
|
||||||
}
|
const selected = drives.filter(d => this.props.isDriveSelected(d));
|
||||||
|
const disabledDrives = drives
|
||||||
private update() {
|
.filter(d => !this.props.isDriveValid(d))
|
||||||
this.setState(this.getNewState());
|
.map(d => d.device);
|
||||||
this.updateTableSelection();
|
return { drives, disabledDrives, selected };
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateTableSelection() {
|
private update() {
|
||||||
if (this.table !== null) {
|
this.setState(this.getNewState());
|
||||||
this.table.setRowSelection(this.state.selected);
|
this.updateTableSelection();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private updateTableSelection() {
|
||||||
private renderSize(size: number) {
|
if (this.table !== null) {
|
||||||
if (size) {
|
this.table.setRowSelection(this.state.selected);
|
||||||
return bytesToClosestUnit(size);
|
}
|
||||||
} else {
|
}
|
||||||
return null;
|
|
||||||
}
|
private renderSize(size: number) {
|
||||||
}
|
if (size) {
|
||||||
|
return bytesToClosestUnit(size);
|
||||||
private renderLocation(displayName: string, drive: Drive) {
|
} else {
|
||||||
const result: Array<string | JSX.Element> = [displayName];
|
return null;
|
||||||
if (drive.link && drive.linkCTA) {
|
}
|
||||||
result.push(<a href={drive.link}>{drive.linkCTA}</a>);
|
}
|
||||||
}
|
|
||||||
return <React.Fragment>{result}</React.Fragment>;
|
private renderLocation(displayName: string, drive: Drive) {
|
||||||
}
|
const result: Array<string | JSX.Element> = [displayName];
|
||||||
|
if (drive.link && drive.linkCTA) {
|
||||||
private renderBadges(_value: any, row: Drive) {
|
result.push(<a href={drive.link}>{drive.linkCTA}</a>);
|
||||||
const result = [];
|
}
|
||||||
if (row.progress !== undefined) {
|
return <React.Fragment>{result}</React.Fragment>;
|
||||||
result.push(<Meter
|
}
|
||||||
size="small"
|
|
||||||
thickness="xxsmall"
|
private renderBadges(_value: any, row: Drive) {
|
||||||
values={
|
const result = [];
|
||||||
[
|
if (row.progress !== undefined) {
|
||||||
{
|
result.push(
|
||||||
value: row.progress,
|
<Meter
|
||||||
label: row.progress + '%',
|
size="small"
|
||||||
color: '#2297de',
|
thickness="xxsmall"
|
||||||
}
|
values={[
|
||||||
]
|
{
|
||||||
}
|
value: row.progress,
|
||||||
/>);
|
label: row.progress + '%',
|
||||||
}
|
color: '#2297de',
|
||||||
result.push(...getDriveImageCompatibilityStatuses(row, this.state.image).map((status: CompatibilityStatus) => {
|
},
|
||||||
const props: { key: string, xsmall: true, danger?: boolean, warning?: boolean} = { xsmall: true, key: status.message };
|
]}
|
||||||
if (status.type === COMPATIBILITY_STATUS_TYPES.ERROR) {
|
/>,
|
||||||
props.danger = true;
|
);
|
||||||
} else if (status.type === COMPATIBILITY_STATUS_TYPES.WARNING) {
|
}
|
||||||
props.warning = true;
|
result.push(
|
||||||
}
|
...this.props.getDriveBadges(row).map(
|
||||||
return <Badge {...props}>{status.message}</Badge>
|
(status: CompatibilityStatus) => {
|
||||||
}))
|
const props: {
|
||||||
return <React.Fragment>{result}</React.Fragment>;
|
key: string;
|
||||||
}
|
xsmall: true;
|
||||||
|
danger?: boolean;
|
||||||
private renderTbodyPrefix() {
|
warning?: boolean;
|
||||||
if (this.state.drives.length === 0) {
|
} = { xsmall: true, key: status.message };
|
||||||
return <tr>
|
if (status.type === COMPATIBILITY_STATUS_TYPES.ERROR) {
|
||||||
<td
|
props.danger = true;
|
||||||
colSpan={this.columns.length}
|
} else if (status.type === COMPATIBILITY_STATUS_TYPES.WARNING) {
|
||||||
style={{ textAlign: 'center' }}
|
props.warning = true;
|
||||||
>
|
}
|
||||||
<b>Connect a drive</b>
|
return <Badge {...props}>{status.message}</Badge>;
|
||||||
<div>No removable drive detected.</div>
|
},
|
||||||
</td>
|
),
|
||||||
</tr>
|
);
|
||||||
}
|
return <React.Fragment>{result}</React.Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
private renderTbodyPrefix() {
|
||||||
return <ThemedProvider>
|
if (this.state.drives.length === 0) {
|
||||||
<Modal
|
return (
|
||||||
titleElement={
|
<tr>
|
||||||
<div style={titleStyle}>
|
<td colSpan={this.columns.length} style={{ textAlign: 'center' }}>
|
||||||
Available targets
|
<b>Connect a drive</b>
|
||||||
<span style={subtitleStyle}>
|
<div>No removable drive detected.</div>
|
||||||
{this.state.drives.length} found
|
</td>
|
||||||
</span>
|
</tr>
|
||||||
</div>
|
);
|
||||||
}
|
}
|
||||||
action={`Select (${this.state.selected.length})`}
|
}
|
||||||
style={modalStyle}
|
|
||||||
done={this.props.close}
|
public render() {
|
||||||
>
|
return (
|
||||||
<div style={wrapperStyle}>
|
<ThemedProvider>
|
||||||
<Table<Drive>
|
<Modal
|
||||||
ref={(t) => {
|
titleElement={
|
||||||
this.table = t;
|
<div style={titleStyle}>
|
||||||
this.updateTableSelection();
|
Available targets
|
||||||
}}
|
<span style={subtitleStyle}>
|
||||||
rowKey='device'
|
{this.state.drives.length} found
|
||||||
onCheck={this.onCheck}
|
</span>
|
||||||
columns={this.columns}
|
</div>
|
||||||
data={this.state.drives}
|
}
|
||||||
disabledRows={this.state.disabledDrives}
|
action={`Select (${this.state.selected.length})`}
|
||||||
tbodyPrefix={this.renderTbodyPrefix()}
|
style={modalStyle}
|
||||||
>
|
done={this.props.close}
|
||||||
</Table>
|
>
|
||||||
</div>
|
<div style={wrapperStyle}>
|
||||||
</Modal>
|
<Table<Drive>
|
||||||
</ThemedProvider>
|
ref={t => {
|
||||||
}
|
this.table = t;
|
||||||
|
this.updateTableSelection();
|
||||||
private onCheck(checkedDrives: Drive[]): void {
|
}}
|
||||||
const checkedDevices = checkedDrives.map(d => d.device);
|
rowKey="device"
|
||||||
for (const drive of getDrives()) {
|
onCheck={this.onCheck.bind(this)}
|
||||||
if (checkedDevices.indexOf(drive.device) !== -1) {
|
columns={this.columns}
|
||||||
selectDrive(drive.device);
|
data={this.state.drives}
|
||||||
} else {
|
disabledRows={this.state.disabledDrives}
|
||||||
deselectDrive(drive.device);
|
tbodyPrefix={this.renderTbodyPrefix()}
|
||||||
}
|
/>
|
||||||
}
|
</div>
|
||||||
}
|
</Modal>
|
||||||
|
</ThemedProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onCheck(checkedDrives: Drive[]): void {
|
||||||
|
const checkedDevices = checkedDrives.map(d => d.device);
|
||||||
|
for (const drive of getDrives()) {
|
||||||
|
if (checkedDevices.indexOf(drive.device) !== -1) {
|
||||||
|
this.props.selectDrive(drive);
|
||||||
|
} else {
|
||||||
|
this.props.deselectDrive(drive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,16 @@ import { react2angular } from 'react2angular';
|
|||||||
|
|
||||||
import { DriveSelector2 } from './drive-selector.tsx';
|
import { DriveSelector2 } from './drive-selector.tsx';
|
||||||
|
|
||||||
const MODULE_NAME = 'Etcher.Components.DriveSelector2'
|
const MODULE_NAME = 'Etcher.Components.DriveSelector2';
|
||||||
|
|
||||||
angular
|
angular
|
||||||
.module(MODULE_NAME, [])
|
.module(MODULE_NAME, [])
|
||||||
.component('driveSelector2', react2angular(DriveSelector2, ['close']))
|
.component(
|
||||||
|
'driveSelector2',
|
||||||
|
react2angular(
|
||||||
|
DriveSelector2,
|
||||||
|
['close', 'selectDrive', 'deselectDrive', 'isDriveSelected', 'isDriveValid', 'getDriveBadges']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
export = MODULE_NAME;
|
export = MODULE_NAME;
|
||||||
|
@ -19,7 +19,8 @@
|
|||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
const React = require('react')
|
const React = require('react')
|
||||||
const propTypes = require('prop-types')
|
const propTypes = require('prop-types')
|
||||||
const { Badge, Select } = require('rendition')
|
const { Badge, DropDownButton, Select } = require('rendition')
|
||||||
|
const { default: styled } = require('styled-components')
|
||||||
|
|
||||||
const middleEllipsis = require('./../../utils/middle-ellipsis')
|
const middleEllipsis = require('./../../utils/middle-ellipsis')
|
||||||
|
|
||||||
@ -35,6 +36,17 @@ const {
|
|||||||
ThemedProvider
|
ThemedProvider
|
||||||
} = require('./../../styled-components')
|
} = require('./../../styled-components')
|
||||||
|
|
||||||
|
const DropdownItem = styled.p`
|
||||||
|
padding-top: 10px;
|
||||||
|
text-align: left;
|
||||||
|
width: 150px;
|
||||||
|
cursor: pointer;
|
||||||
|
`
|
||||||
|
|
||||||
|
const DropdownItemIcon = styled.i`
|
||||||
|
padding-right: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
const SelectImageButton = (props) => {
|
const SelectImageButton = (props) => {
|
||||||
if (props.hasImage) {
|
if (props.hasImage) {
|
||||||
return (
|
return (
|
||||||
@ -51,9 +63,9 @@ const SelectImageButton = (props) => {
|
|||||||
<ChangeButton
|
<ChangeButton
|
||||||
plain
|
plain
|
||||||
mb={14}
|
mb={14}
|
||||||
onClick={props.reselectImage}
|
onClick={props.deselectImage}
|
||||||
>
|
>
|
||||||
Change
|
Remove
|
||||||
</ChangeButton>
|
</ChangeButton>
|
||||||
}
|
}
|
||||||
<DetailsText>
|
<DetailsText>
|
||||||
@ -65,18 +77,26 @@ const SelectImageButton = (props) => {
|
|||||||
return (
|
return (
|
||||||
<ThemedProvider>
|
<ThemedProvider>
|
||||||
<StepSelection>
|
<StepSelection>
|
||||||
<Select
|
<DropDownButton
|
||||||
value={props.sourceType}
|
primary
|
||||||
onChange={(e) => {console.log('changed')}}
|
label={
|
||||||
|
<div onClick={props.openImageSelector}>Select image</div>
|
||||||
|
}
|
||||||
|
style={{height: '48px'}}
|
||||||
>
|
>
|
||||||
<option value={'image'}>Select image file</option>
|
<DropdownItem
|
||||||
<option value={'drive'}>Duplicate drive</option>
|
onClick={props.openImageSelector}
|
||||||
</Select>
|
>
|
||||||
<StepButton
|
<DropdownItemIcon className="far fa-file"/>
|
||||||
onClick={props.openImageSelector}
|
Select image file
|
||||||
>
|
</DropdownItem>
|
||||||
Select image
|
<DropdownItem
|
||||||
</StepButton>
|
onClick={props.openDriveSelector}
|
||||||
|
>
|
||||||
|
<DropdownItemIcon className="far fa-copy"/>
|
||||||
|
Duplicate drive
|
||||||
|
</DropdownItem>
|
||||||
|
</DropDownButton>
|
||||||
<Footer>
|
<Footer>
|
||||||
{ props.mainSupportedExtensions.join(', ') }, and{' '}
|
{ props.mainSupportedExtensions.join(', ') }, and{' '}
|
||||||
<Underline
|
<Underline
|
||||||
@ -92,13 +112,14 @@ const SelectImageButton = (props) => {
|
|||||||
|
|
||||||
SelectImageButton.propTypes = {
|
SelectImageButton.propTypes = {
|
||||||
openImageSelector: propTypes.func,
|
openImageSelector: propTypes.func,
|
||||||
|
openDriveSelector: propTypes.func,
|
||||||
mainSupportedExtensions: propTypes.array,
|
mainSupportedExtensions: propTypes.array,
|
||||||
extraSupportedExtensions: propTypes.array,
|
extraSupportedExtensions: propTypes.array,
|
||||||
hasImage: propTypes.bool,
|
hasImage: propTypes.bool,
|
||||||
showSelectedImageDetails: propTypes.func,
|
showSelectedImageDetails: propTypes.func,
|
||||||
imageName: propTypes.string,
|
imageName: propTypes.string,
|
||||||
imageBasename: propTypes.string,
|
imageBasename: propTypes.string,
|
||||||
reselectImage: propTypes.func,
|
deselectImage: propTypes.func,
|
||||||
flashing: propTypes.bool,
|
flashing: propTypes.bool,
|
||||||
imageSize: propTypes.number,
|
imageSize: propTypes.number,
|
||||||
sourceType: propTypes.string
|
sourceType: propTypes.string
|
||||||
|
@ -68,10 +68,7 @@ const flashStateNoNilFields = [
|
|||||||
* @constant
|
* @constant
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
const selectImageNoNilFields = [
|
const selectImageNoNilFields = [ 'path' ]
|
||||||
'path',
|
|
||||||
'extension'
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Application default state
|
* @summary Application default state
|
||||||
@ -385,42 +382,44 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_.isString(action.data.extension)) {
|
if (!action.data.isDrive) { // We don't care about extensions if the source is a drive
|
||||||
throw errors.createError({
|
if (!_.isString(action.data.extension)) {
|
||||||
title: `Invalid image extension: ${action.data.extension}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const extension = _.toLower(action.data.extension)
|
|
||||||
|
|
||||||
if (!_.includes(supportedFormats.getAllExtensions(), extension)) {
|
|
||||||
throw errors.createError({
|
|
||||||
title: `Invalid image extension: ${action.data.extension}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastImageExtension = fileExtensions.getLastFileExtension(action.data.path)
|
|
||||||
lastImageExtension = _.isString(lastImageExtension) ? _.toLower(lastImageExtension) : lastImageExtension
|
|
||||||
|
|
||||||
if (lastImageExtension !== extension) {
|
|
||||||
if (!_.isString(action.data.archiveExtension)) {
|
|
||||||
throw errors.createError({
|
throw errors.createError({
|
||||||
title: 'Missing image archive extension'
|
title: `Invalid image extension: ${action.data.extension}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const archiveExtension = _.toLower(action.data.archiveExtension)
|
const extension = _.toLower(action.data.extension)
|
||||||
|
|
||||||
if (!_.includes(supportedFormats.getAllExtensions(), archiveExtension)) {
|
if (!_.includes(supportedFormats.getAllExtensions(), extension)) {
|
||||||
throw errors.createError({
|
throw errors.createError({
|
||||||
title: `Invalid image archive extension: ${action.data.archiveExtension}`
|
title: `Invalid image extension: ${action.data.extension}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastImageExtension !== archiveExtension) {
|
let lastImageExtension = fileExtensions.getLastFileExtension(action.data.path)
|
||||||
throw errors.createError({
|
lastImageExtension = _.isString(lastImageExtension) ? _.toLower(lastImageExtension) : lastImageExtension
|
||||||
title: `Image archive extension mismatch: ${action.data.archiveExtension} and ${lastImageExtension}`
|
|
||||||
})
|
if (lastImageExtension !== extension) {
|
||||||
|
if (!_.isString(action.data.archiveExtension)) {
|
||||||
|
throw errors.createError({
|
||||||
|
title: 'Missing image archive extension'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const archiveExtension = _.toLower(action.data.archiveExtension)
|
||||||
|
|
||||||
|
if (!_.includes(supportedFormats.getAllExtensions(), archiveExtension)) {
|
||||||
|
throw errors.createError({
|
||||||
|
title: `Invalid image archive extension: ${action.data.archiveExtension}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastImageExtension !== archiveExtension) {
|
||||||
|
throw errors.createError({
|
||||||
|
title: `Image archive extension mismatch: ${action.data.archiveExtension} and ${lastImageExtension}`
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ const store = require('../../../models/store')
|
|||||||
// eslint-disable-next-line node/no-missing-require
|
// eslint-disable-next-line node/no-missing-require
|
||||||
const settings = require('../../../models/settings')
|
const settings = require('../../../models/settings')
|
||||||
const selectionState = require('../../../models/selection-state')
|
const selectionState = require('../../../models/selection-state')
|
||||||
|
const driveConstraints = require('../../../modules/drive-constraints')
|
||||||
const analytics = require('../../../modules/analytics')
|
const analytics = require('../../../modules/analytics')
|
||||||
const exceptionReporter = require('../../../modules/exception-reporter')
|
const exceptionReporter = require('../../../modules/exception-reporter')
|
||||||
// eslint-disable-next-line node/no-missing-require
|
// eslint-disable-next-line node/no-missing-require
|
||||||
@ -169,4 +170,25 @@ module.exports = function ($timeout, DriveSelectorService) {
|
|||||||
// Trigger re-render
|
// Trigger re-render
|
||||||
$timeout()
|
$timeout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.selectDrive = (drive) => {
|
||||||
|
return selectionState.selectDrive(drive.device)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deselectDrive = (drive) => {
|
||||||
|
return selectionState.deselectDrive(drive.device)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isDriveSelected = (drive) => {
|
||||||
|
return selectionState.isDriveSelected(drive.device)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isDriveValid = (drive) => {
|
||||||
|
return driveConstraints.isDriveValid(drive, selectionState.getImage());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getDriveBadges = (drive) => {
|
||||||
|
return driveConstraints.getDriveImageCompatibilityStatuses(drive, selectionState.getImage());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -247,6 +247,11 @@ module.exports = function (
|
|||||||
this.openImageSelector()
|
this.openImageSelector()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.deselectImage = () => {
|
||||||
|
selectionState.deselectImage()
|
||||||
|
$timeout()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Get the basename of the selected image
|
* @summary Get the basename of the selected image
|
||||||
* @function
|
* @function
|
||||||
@ -264,4 +269,44 @@ module.exports = function (
|
|||||||
|
|
||||||
return path.basename(selectionState.getImagePath())
|
return path.basename(selectionState.getImagePath())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.driveSelectorModalOpen = false
|
||||||
|
|
||||||
|
this.openDriveSelector = () => {
|
||||||
|
this.driveSelectorModalOpen = true;
|
||||||
|
$timeout()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeDriveSelector = () => {
|
||||||
|
this.driveSelectorModalOpen = false;
|
||||||
|
$timeout()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectDrive = (drive) => {
|
||||||
|
selectionState.selectImage({
|
||||||
|
path: drive.device,
|
||||||
|
size: drive.size,
|
||||||
|
isDrive: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deselectDrive = (drive) => {
|
||||||
|
if (this.isDriveSelected(drive)) {
|
||||||
|
this.deselectImage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isDriveSelected = (drive) => {
|
||||||
|
const image = selectionState.getImage()
|
||||||
|
return image && image.isDrive && image.path === drive.device
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isDriveValid = (drive) => {
|
||||||
|
return true // TODO: not valid if already a destination drive
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getDriveBadges = (drive) => {
|
||||||
|
return [] // TODO: selected as destination (same as above)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,15 @@
|
|||||||
<div class="page-main row around-xs">
|
<div class="page-main row around-xs">
|
||||||
<div class="col-xs" ng-controller="ImageSelectionController as image">
|
<div class="col-xs" ng-controller="ImageSelectionController as image">
|
||||||
|
<drive-selector-2
|
||||||
|
ng-if="image.driveSelectorModalOpen"
|
||||||
|
close="image.closeDriveSelectorModal"
|
||||||
|
select-drive="image.selectDrive"
|
||||||
|
deselect-drive="image.deselectDrive"
|
||||||
|
is-drive-selected="image.isDriveSelected"
|
||||||
|
is-drive-valid="image.isDriveValid"
|
||||||
|
get-drive-badges="image.getDriveBadges"
|
||||||
|
>
|
||||||
|
</drive-selector-2>
|
||||||
<div class="box text-center relative" os-dropzone="image.selectImageByPath($file)">
|
<div class="box text-center relative" os-dropzone="image.selectImageByPath($file)">
|
||||||
|
|
||||||
<div class="center-block">
|
<div class="center-block">
|
||||||
@ -10,12 +20,13 @@
|
|||||||
<image-selector
|
<image-selector
|
||||||
has-image="main.selection.hasImage()"
|
has-image="main.selection.hasImage()"
|
||||||
open-image-selector="image.openImageSelector"
|
open-image-selector="image.openImageSelector"
|
||||||
|
open-drive-selector="image.openDriveSelector"
|
||||||
main-supported-extensions="image.mainSupportedExtensions"
|
main-supported-extensions="image.mainSupportedExtensions"
|
||||||
extra-supported-extensions="image.extraSupportedExtensions"
|
extra-supported-extensions="image.extraSupportedExtensions"
|
||||||
show-selected-image-details="main.showSelectedImageDetails"
|
show-selected-image-details="main.showSelectedImageDetails"
|
||||||
image-name="main.selection.getImageName()"
|
image-name="main.selection.getImageName()"
|
||||||
image-basename="image.getImageBasename()"
|
image-basename="image.getImageBasename()"
|
||||||
reselect-image="image.reselectImage"
|
deselect-image="image.deselectImage"
|
||||||
flashing="main.state.isFlashing()"
|
flashing="main.state.isFlashing()"
|
||||||
image-size="main.selection.getImageSize()"
|
image-size="main.selection.getImageSize()"
|
||||||
>
|
>
|
||||||
@ -29,7 +40,11 @@
|
|||||||
<drive-selector-2
|
<drive-selector-2
|
||||||
ng-if="drive.driveSelectorModalOpen"
|
ng-if="drive.driveSelectorModalOpen"
|
||||||
close="drive.closeDriveSelectorModal"
|
close="drive.closeDriveSelectorModal"
|
||||||
wololo="wololo"
|
select-drive="drive.selectDrive"
|
||||||
|
deselect-drive="drive.deselectDrive"
|
||||||
|
is-drive-selected="drive.isDriveSelected"
|
||||||
|
is-drive-valid="drive.isDriveValid"
|
||||||
|
get-drive-badges="drive.getDriveBadges"
|
||||||
>
|
>
|
||||||
</drive-selector-2>
|
</drive-selector-2>
|
||||||
<div class="box text-center relative">
|
<div class="box text-center relative">
|
||||||
|
@ -45,6 +45,7 @@ $fa-font-path: "../../../node_modules/@fortawesome/fontawesome-free-webfonts/web
|
|||||||
|
|
||||||
@import "../../../../node_modules/@fortawesome/fontawesome-free-webfonts/scss/fontawesome";
|
@import "../../../../node_modules/@fortawesome/fontawesome-free-webfonts/scss/fontawesome";
|
||||||
@import "../../../../node_modules/@fortawesome/fontawesome-free-webfonts/scss/fa-solid";
|
@import "../../../../node_modules/@fortawesome/fontawesome-free-webfonts/scss/fa-solid";
|
||||||
|
@import "../../../../node_modules/@fortawesome/fontawesome-free-webfonts/scss/fa-regular";
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Nunito';
|
font-family: 'Nunito';
|
||||||
|
@ -9860,6 +9860,17 @@ readers do not read off random characters that represent icons */
|
|||||||
font-family: 'Font Awesome 5 Free';
|
font-family: 'Font Awesome 5 Free';
|
||||||
font-weight: 900; }
|
font-weight: 900; }
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Font Awesome 5 Free';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url("../../../node_modules/@fortawesome/fontawesome-free-webfonts/webfonts/fa-regular-400.eot");
|
||||||
|
src: url("../../../node_modules/@fortawesome/fontawesome-free-webfonts/webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../../../node_modules/@fortawesome/fontawesome-free-webfonts/webfonts/fa-regular-400.woff2") format("woff2"), url("../../../node_modules/@fortawesome/fontawesome-free-webfonts/webfonts/fa-regular-400.woff") format("woff"), url("../../../node_modules/@fortawesome/fontawesome-free-webfonts/webfonts/fa-regular-400.ttf") format("truetype"), url("../../../node_modules/@fortawesome/fontawesome-free-webfonts/webfonts/fa-regular-400.svg#fontawesome") format("svg"); }
|
||||||
|
|
||||||
|
.far {
|
||||||
|
font-family: 'Font Awesome 5 Free';
|
||||||
|
font-weight: 400; }
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Nunito';
|
font-family: 'Nunito';
|
||||||
src: url("Nunito-Regular.eot");
|
src: url("Nunito-Regular.eot");
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "make lint test sanity-checks",
|
"test": "make lint test sanity-checks",
|
||||||
"prettier": "prettier --config ./node_modules/resin-lint/config/.prettierrc --write \"lib/**/*.ts\"",
|
"prettier": "prettier --config ./node_modules/resin-lint/config/.prettierrc --write \"lib/**/*.ts\" \"lib/**/*.tsx\"",
|
||||||
"start": "./node_modules/.bin/electron .",
|
"start": "./node_modules/.bin/electron .",
|
||||||
"postshrinkwrap": "node ./scripts/clean-shrinkwrap.js",
|
"postshrinkwrap": "node ./scripts/clean-shrinkwrap.js",
|
||||||
"configure": "node-gyp configure",
|
"configure": "node-gyp configure",
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 1b5bb595fe00a81e9a12df654c9909e674997dd9
|
Subproject commit 022f4509c58b3e2a5ca32a78d10bb7d59a2747a4
|
Loading…
x
Reference in New Issue
Block a user