From 6dbd425e8937b95c298445ae67f2c1f63c13b19a Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Thu, 4 Jul 2019 19:35:52 +0200 Subject: [PATCH] wip --- .../drive-selector/{index.js => index.ts} | 0 .../drive-selector2/drive-selector.tsx | 249 ++++++++++++++++++ .../app/components/drive-selector2/index.ts | 28 ++ 3 files changed, 277 insertions(+) rename lib/gui/app/components/drive-selector/{index.js => index.ts} (100%) create mode 100644 lib/gui/app/components/drive-selector2/drive-selector.tsx create mode 100644 lib/gui/app/components/drive-selector2/index.ts diff --git a/lib/gui/app/components/drive-selector/index.js b/lib/gui/app/components/drive-selector/index.ts similarity index 100% rename from lib/gui/app/components/drive-selector/index.js rename to lib/gui/app/components/drive-selector/index.ts diff --git a/lib/gui/app/components/drive-selector2/drive-selector.tsx b/lib/gui/app/components/drive-selector2/drive-selector.tsx new file mode 100644 index 00000000..122703a7 --- /dev/null +++ b/lib/gui/app/components/drive-selector2/drive-selector.tsx @@ -0,0 +1,249 @@ +/* + * Copyright 2019 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. + */ + +import { Meter } from 'grommet'; +import * as React from 'react'; +import { Badge, Heading, Modal, Table } from 'rendition'; + +import { getDrives } from '../../models/available-drives'; +import { getDriveImageCompatibilityStatuses } from '../../modules/drive-constraints'; +import { + deselectDrive, + getImage, + getSelectedDrives, + isDriveSelected, + selectDrive, +} from '../../models/selection-state'; +import { subscribe } from '../../models/store'; +import { ThemedProvider } from '../../styled-components'; +import { bytesToClosestUnit } from '../../modules/units'; + +interface Drive { + description: string; + device: string; + isSystem: boolean; + isReadOnly: boolean; + progress?: number; + size?: number; + link?: string; + linkCTA?: string; + displayName: string; +} + +interface Image { + path: string; + size: number; + url: string; + name: string; + supportUrl: string; + recommendedDriveSize: number; +} + +interface CompatibilityStatus { + type: number; + message: string; +} + +interface DriveSelectorProps { + unique: boolean; // TODO +} + +interface DriveSelectorState { + open: boolean; + drives: Drive[]; + image: Image; + selectedDrivesCount: number; +} + + +// TODO: no hardcoded size +const modalStyle = { + width: '800px', + height: '600px', + paddingTop: '20px', + paddingLeft: '30px', + paddingRight: '30px', + paddingBottom: '11px', +} + +const titleStyle = { + color: '#2a506f', +} + +const subtitleStyle = { + marginLeft: '10px', + fontSize: '11px', + color: '#5b82a7', +}; + +const wrapperStyle = { + height: '250px', + overflowX: 'hidden' as 'hidden', + overflowY: 'auto' as 'auto', +} + +export class DriveSelector2 extends React.Component { + private table: React.RefObject>; + private columns: any; // TODO + + constructor(props: DriveSelectorProps) { + super(props); + this.columns = [ + { + field: 'description', + label: 'Name', + } as const, + { + field: 'size', + label: 'Size', + render: this.renderSize.bind(this), + } as const, + { + field: 'displayName', + label: 'Location', + render: this.renderLocation.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: ' ', + render: this.renderBadges.bind(this), + } as const, + ]; + this.state = { + drives: getDrives(), + selectedDrivesCount: getSelectedDrives().length, + image: getImage(), + open: true, + }; + this.table = React.createRef(); + subscribe(() => { + const drives: Drive[] = getDrives(); + for (let i = 0; i < drives.length; i++) { + drives[i] = {...drives[i]}; + } + const selected = drives.filter(d => isDriveSelected(d.device)); + this.setState({ + drives, + selectedDrivesCount: selected.length, + image: getImage(), + }); + if (this.table.current != null) { + this.table.current.setRowSelection(selected); + } + }); + } + + private renderSize(size: number) { + if (size) { + return bytesToClosestUnit(size); + } else { + return null; + } + } + + private renderLocation(displayName: string, drive: Drive) { + const result: Array = [displayName]; + if (drive.link && drive.linkCTA) { + result.push({drive.linkCTA}); + } + return {result}; + } + + private renderBadges(_value: any, row: Drive) { + const result = []; + if (row.progress !== undefined) { + result.push(); + } + result.push(...getDriveImageCompatibilityStatuses(row, this.state.image).map((status: CompatibilityStatus) => { // TODO: badge color + return {status.message} + })) + // TODO: drive contains source mountpoint + // TODO: large drive + return {result}; + } + + private renderTbodyPrefix() { + if (this.state.drives.length === 0) { + return + + Connect a drive +
No removable drive detected.
+ + + } + } + + public render() { + console.log('render', this.state.drives.map(d => d.device)); + if (this.state.open) { + return + + Available targets + + {this.state.drives.length} found + + + } + action={`Select (${this.state.selectedDrivesCount})`} + style={modalStyle} + done={() => {this.setState({open: false})}} + > +
+ + ref={this.table} + rowKey='device' + onCheck={this.onCheck} + columns={this.columns} + data={this.state.drives} + tbodyPrefix={this.renderTbodyPrefix()} + > + +
+
+
+ } else { + return null; + } + } + + private onCheck(checkedDrives: Drive[]): void { + const checkedDevices = checkedDrives.map(d => d.device); + for (const drive of getDrives()) { + if (checkedDevices.indexOf(drive.device) !== -1) { + selectDrive(drive.device); + } else { + deselectDrive(drive.device); + } + } + } +} diff --git a/lib/gui/app/components/drive-selector2/index.ts b/lib/gui/app/components/drive-selector2/index.ts new file mode 100644 index 00000000..18dcc505 --- /dev/null +++ b/lib/gui/app/components/drive-selector2/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2019 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. + */ + +import * as angular from 'angular'; +import { react2angular } from 'react2angular'; + +import { DriveSelector2 } from './drive-selector.tsx'; + +const MODULE_NAME = 'Etcher.Components.DriveSelector2' + +angular + .module(MODULE_NAME, []) + .component('driveSelector2', react2angular(DriveSelector2)) + +export = MODULE_NAME;