This commit is contained in:
Alexis Svinartchouk 2019-07-12 18:59:06 +02:00
parent 96c865f14a
commit 1398ca2931
12 changed files with 404 additions and 285 deletions

View File

@ -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;

View File

@ -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);
}
}
}
} }

View File

@ -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;

View File

@ -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

View File

@ -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}`
})
}
} }
} }

View File

@ -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());
}
} }

View File

@ -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)
}
} }

View File

@ -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">

View File

@ -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';

View File

@ -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");

View File

@ -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