From fe293e71d7bb62182a29c6a0eca14134791d0970 Mon Sep 17 00:00:00 2001 From: dariaterekhovaae <98411986+dariaterekhovaae@users.noreply.github.com> Date: Thu, 9 Jun 2022 14:04:54 +0300 Subject: [PATCH] [Chore]: Technical: js to ts convertion components root modals (#1801) * Modals translated Signed-off-by: Daria Terekhova * Prettier changes Signed-off-by: Daria Terekhova * Build fixes Signed-off-by: Daria Terekhova * Review fixes Signed-off-by: Daria Terekhova * Rebase changes Signed-off-by: Daria Terekhova * Review changes Signed-off-by: Daria Terekhova --- package.json | 1 + src/actions/vis-state-actions.ts | 4 +- src/cloud-providers/provider.ts | 4 +- src/components/bottom-widget.tsx | 2 +- src/components/common/data-table/index.tsx | 92 ++++++++++++++++--- .../common/data-table/option-dropdown.tsx | 2 +- .../common/item-selector/item-selector.d.ts | 48 +++++----- src/components/common/modal.tsx | 5 +- src/components/common/styled-components.tsx | 8 +- src/components/modal-container.tsx | 10 +- ...style-modal.js => add-map-style-modal.tsx} | 31 ++++--- .../modals/{cloud-tile.js => cloud-tile.tsx} | 46 ++++++++-- ...ta-table-modal.js => data-table-modal.tsx} | 60 ++++++++---- ...te-data-modal.js => delete-data-modal.tsx} | 9 +- .../{error-display.js => error-display.tsx} | 6 +- ...rt-data-modal.js => export-data-modal.tsx} | 60 ++++++------ src/components/modals/export-image-modal.d.ts | 21 ----- ...-image-modal.js => export-image-modal.tsx} | 15 ++- .../{components.js => components.tsx} | 0 .../export-map-modal/export-html-map.d.ts | 21 ----- ...export-html-map.js => export-html-map.tsx} | 27 +++++- ...export-json-map.js => export-json-map.tsx} | 15 +-- ...port-map-modal.js => export-map-modal.tsx} | 39 ++++---- .../modals/image-modal-container.d.ts | 13 --- ...container.js => image-modal-container.tsx} | 11 ++- src/components/modals/load-data-modal.d.ts | 30 ------ ...load-data-modal.js => load-data-modal.tsx} | 32 ++++++- ...ad-storage-map.js => load-storage-map.tsx} | 51 ++++++++-- .../{loading-dialog.js => loading-dialog.tsx} | 10 +- .../{modal-dialog.js => modal-dialog.tsx} | 0 .../modals/{modal-tabs.js => modal-tabs.tsx} | 13 ++- .../modals/overwrite-map-modal.d.ts | 18 ---- ...e-map-modal.js => overwrite-map-modal.tsx} | 18 +++- .../modals/provider-modal-container.d.ts | 12 --- ...tainer.js => provider-modal-container.tsx} | 21 +++-- src/components/modals/save-map-modal.d.ts | 32 ------- .../{save-map-modal.js => save-map-modal.tsx} | 53 +++++++++-- ...share-map-modal.js => share-map-modal.tsx} | 55 ++++++----- .../{status-panel.js => status-panel.tsx} | 17 +++- ...e-map-viewer.js => storage-map-viewer.tsx} | 33 +++++-- src/localization/formatted-message.tsx | 2 +- src/reducers/map-style-updaters.ts | 2 +- yarn.lock | 11 ++- 43 files changed, 575 insertions(+), 385 deletions(-) rename src/components/modals/{add-map-style-modal.js => add-map-style-modal.tsx} (94%) rename src/components/modals/{cloud-tile.js => cloud-tile.tsx} (79%) rename src/components/modals/{data-table-modal.js => data-table-modal.tsx} (77%) rename src/components/modals/{delete-data-modal.js => delete-data-modal.tsx} (87%) rename src/components/modals/{error-display.js => error-display.tsx} (93%) rename src/components/modals/{export-data-modal.js => export-data-modal.tsx} (85%) delete mode 100644 src/components/modals/export-image-modal.d.ts rename src/components/modals/{export-image-modal.js => export-image-modal.tsx} (91%) rename src/components/modals/export-map-modal/{components.js => components.tsx} (100%) delete mode 100644 src/components/modals/export-map-modal/export-html-map.d.ts rename src/components/modals/export-map-modal/{export-html-map.js => export-html-map.tsx} (88%) rename src/components/modals/export-map-modal/{export-json-map.js => export-json-map.tsx} (90%) rename src/components/modals/export-map-modal/{export-map-modal.js => export-map-modal.tsx} (81%) delete mode 100644 src/components/modals/image-modal-container.d.ts rename src/components/modals/{image-modal-container.js => image-modal-container.tsx} (87%) delete mode 100644 src/components/modals/load-data-modal.d.ts rename src/components/modals/{load-data-modal.js => load-data-modal.tsx} (73%) rename src/components/modals/{load-storage-map.js => load-storage-map.tsx} (88%) rename src/components/modals/{loading-dialog.js => loading-dialog.tsx} (91%) rename src/components/modals/{modal-dialog.js => modal-dialog.tsx} (100%) rename src/components/modals/{modal-tabs.js => modal-tabs.tsx} (92%) delete mode 100644 src/components/modals/overwrite-map-modal.d.ts rename src/components/modals/{overwrite-map-modal.js => overwrite-map-modal.tsx} (86%) delete mode 100644 src/components/modals/provider-modal-container.d.ts rename src/components/modals/{provider-modal-container.js => provider-modal-container.tsx} (80%) delete mode 100644 src/components/modals/save-map-modal.d.ts rename src/components/modals/{save-map-modal.js => save-map-modal.tsx} (82%) rename src/components/modals/{share-map-modal.js => share-map-modal.tsx} (85%) rename src/components/modals/{status-panel.js => status-panel.tsx} (85%) rename src/components/modals/{storage-map-viewer.js => storage-map-viewer.tsx} (86%) diff --git a/package.json b/package.json index 2f2c341b19..882e2c60a4 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,7 @@ "@types/lodash.xor": "^4.5.7", "@types/mapbox__geo-viewport": "^0.4.1", "@types/react-dom": "^18.0.0", + "@types/react-copy-to-clipboard": "^5.0.2", "@types/react-lifecycles-compat": "^3.0.1", "@types/react-map-gl": "^6.1.2", "@types/react-modal": "^3.13.1", diff --git a/src/actions/vis-state-actions.ts b/src/actions/vis-state-actions.ts index cc0ed944c9..34175eaea3 100644 --- a/src/actions/vis-state-actions.ts +++ b/src/actions/vis-state-actions.ts @@ -496,7 +496,7 @@ export function updateTableColor( export type SortTableColumnUpdaterAction = { dataId: string; column: string; - mode: string; + mode?: string; }; /** * Sort dataset column, for table display @@ -510,7 +510,7 @@ export type SortTableColumnUpdaterAction = { export function sortTableColumn( dataId: string, column: string, - mode: string + mode?: string ): Merge { return { type: ActionTypes.SORT_TABLE_COLUMN, diff --git a/src/cloud-providers/provider.ts b/src/cloud-providers/provider.ts index e68a60bc15..c957193291 100644 --- a/src/cloud-providers/provider.ts +++ b/src/cloud-providers/provider.ts @@ -46,7 +46,7 @@ export type ProviderProps = { thumbnail?: Thumbnail; }; -interface IconProps { +export interface IconProps { height?: string; width?: string; } @@ -169,7 +169,7 @@ export default class Provider { * @param {function} onCloudLogoutSuccess - callbacks to be called after logout success * @public */ - async logout(onCloudLogoutSuccess) { + async logout(onCloudLogoutSuccess: () => void) { onCloudLogoutSuccess(); return; } diff --git a/src/components/bottom-widget.tsx b/src/components/bottom-widget.tsx index ead329ca9a..235be01316 100644 --- a/src/components/bottom-widget.tsx +++ b/src/components/bottom-widget.tsx @@ -33,7 +33,7 @@ const maxWidth = 1080; interface BottomWidgetContainerProps { hasPadding?: boolean; - width: number; + width?: number; } const BottomWidgetContainer = styled.div` diff --git a/src/components/common/data-table/index.tsx b/src/components/common/data-table/index.tsx index a5657a7876..bd7e354526 100644 --- a/src/components/common/data-table/index.tsx +++ b/src/components/common/data-table/index.tsx @@ -19,7 +19,7 @@ // THE SOFTWARE. import React, {Component, createRef} from 'react'; -import {ScrollSync, AutoSizer} from 'react-virtualized'; +import {ScrollSync, AutoSizer, OnScrollParams, GridProps, Index} from 'react-virtualized'; import styled, {withTheme} from 'styled-components'; import classnames from 'classnames'; import {createSelector} from 'reselect'; @@ -37,6 +37,7 @@ import {adjustCellsToContainer} from './cell-size'; import {ALL_FIELD_TYPES, SORT_ORDER} from 'constants/default-settings'; import FieldTokenFactory from 'components/common/field-token'; +import {DataContainerInterface} from 'utils/table-utils/data-container-interface'; const defaultHeaderRowHeight = 55; const defaultRowHeight = 32; @@ -229,10 +230,26 @@ const columnWidthFunction = (columns, cellSizeCache, ghost?) => ({index}) => { return (columns[index] || {}).ghost ? ghost : cellSizeCache[columns[index]] || defaultColumnWidth; }; +interface GetRowCellProps { + dataContainer: DataContainerInterface; + columns: (string & {ghost?: boolean})[]; + column: string; + colMeta; + rowIndex: number; + sortOrder?: number[] | null; +} + /* * This is an accessor method used to generalize getting a cell from a data row */ -const getRowCell = ({dataContainer, columns, column, colMeta, rowIndex, sortOrder}) => { +const getRowCell = ({ + dataContainer, + columns, + column, + colMeta, + rowIndex, + sortOrder +}: GetRowCellProps) => { const rowIdx = sortOrder && sortOrder.length ? get(sortOrder, rowIndex) : rowIndex; const {type} = colMeta[column]; @@ -241,6 +258,29 @@ const getRowCell = ({dataContainer, columns, column, colMeta, rowIndex, sortOrde return parseFieldValue(value, type); }; +interface TableSectionProps { + classList?: { + header: string; + rows: string; + }; + isPinned?: boolean; + columns: (string & {ghost?: boolean})[]; + headerGridProps?; + fixedWidth?: number; + fixedHeight?: number; + onScroll?: (params: OnScrollParams) => void; + scrollTop?: number; + dataGridProps: { + rowHeight: number | ((params: Index) => number); + rowCount: number; + } & Partial; + columnWidth?; + setGridRef?: Function; + headerCellRender?; + dataCellRender?; + scrollLeft?: number; +} + export const TableSection = ({ classList, isPinned, @@ -256,7 +296,7 @@ export const TableSection = ({ headerCellRender, dataCellRender, scrollLeft = 0 -}) => ( +}: TableSectionProps) => ( {({width, height}) => { const gridDimension = { @@ -303,10 +343,22 @@ type CellSizeCache = {[id: string]: number}; interface DataTableProps { cellSizeCache?: CellSizeCache; pinnedColumns?: string[]; + columns: (string & {ghost?: boolean})[]; fixedWidth?: number; theme?: any; - dataContainer; - fixedHeight; + dataContainer: DataContainerInterface; + fixedHeight?: number; + colMeta: { + [id: string]: { + name: string; + type: string; + }; + }; + sortColumn?: {[id: string]: string}; + sortTableColumn: (id: string, mode?: string) => void; + pinTableColumn: (id: string) => void; + copyTableColumn: (id: string) => void; + sortOrder?: number[] | null; } interface DataTableState { @@ -357,8 +409,8 @@ function DataTableFactory(FieldToken: ReturnType) { } root = createRef(); - columns = props => props.columns; - pinnedColumns = props => props.pinnedColumns; + columns = (props: DataTableProps) => props.columns; + pinnedColumns = (props: DataTableProps) => props.pinnedColumns; unpinnedColumns = createSelector(this.columns, this.pinnedColumns, (columns, pinnedColumns) => !Array.isArray(pinnedColumns) ? columns : columns.filter(c => !pinnedColumns.includes(c)) ); @@ -396,11 +448,17 @@ function DataTableFactory(FieldToken: ReturnType) { scaleCellsToWidth = debounce(this.doScaleCellsToWidth, 300); - renderHeaderCell = (columns, isPinned, props, toggleMoreOptions, moreOptionsColumn) => { + renderHeaderCell = ( + columns: (string & {ghost?: boolean})[], + isPinned: boolean, + props: DataTableProps, + toggleMoreOptions, + moreOptionsColumn + ) => { // eslint-disable-next-line react/display-name return cellInfo => { const {columnIndex, key, style} = cellInfo; - const {colMeta, sortColumn, sortTableColumn, pinTableColumn, copyTableColumn} = props; + const {colMeta, sortColumn = {}, sortTableColumn, pinTableColumn, copyTableColumn} = props; const column = columns[columnIndex]; const isGhost = column.ghost; @@ -468,7 +526,7 @@ function DataTableFactory(FieldToken: ReturnType) { }; }; - renderDataCell = (columns, isPinned, props) => { + renderDataCell = (columns, isPinned, props: DataTableProps) => { return cellInfo => { const {columnIndex, key, style, rowIndex} = cellInfo; const {dataContainer, colMeta} = props; @@ -483,7 +541,7 @@ function DataTableFactory(FieldToken: ReturnType) { const endCell = columnIndex === columns.length - 1; const firstCell = columnIndex === 0; const bottomCell = rowIndex === lastRowIndex; - const alignRight = fieldToAlignRight[type]; + const alignRight = fieldToAlignRight[Number(type)]; const cell = (
) { }; render() { - const {dataContainer, pinnedColumns = [], theme = {}, fixedWidth, fixedHeight} = this.props; + const { + dataContainer, + pinnedColumns = [], + theme = {}, + fixedWidth, + fixedHeight = 0 + } = this.props; const unpinnedColumns = this.unpinnedColumns(this.props); const {cellSizeCache = {}, moreOptionsColumn, ghost} = this.state; - const unpinnedColumnsGhost = ghost ? [...unpinnedColumns, {ghost: true}] : unpinnedColumns; + const unpinnedColumnsGhost = ghost + ? [...unpinnedColumns, {ghost: true} as string & {ghost: boolean}] + : unpinnedColumns; const pinnedColumnsWidth = pinnedColumns.reduce( (acc, val) => acc + get(cellSizeCache, val, 0), 0 diff --git a/src/components/common/data-table/option-dropdown.tsx b/src/components/common/data-table/option-dropdown.tsx index 6ce13384cc..82fc49212e 100644 --- a/src/components/common/data-table/option-dropdown.tsx +++ b/src/components/common/data-table/option-dropdown.tsx @@ -59,7 +59,7 @@ interface OptionDropdownProps { pinTableColumn: () => void; copyTableColumn: () => void; sortMode?: string; - isSorted?: boolean; + isSorted?: string; isPinned?: boolean; } diff --git a/src/components/common/item-selector/item-selector.d.ts b/src/components/common/item-selector/item-selector.d.ts index 1c818b120b..814bb3a6c8 100644 --- a/src/components/common/item-selector/item-selector.d.ts +++ b/src/components/common/item-selector/item-selector.d.ts @@ -1,30 +1,30 @@ import {FunctionComponent, ComponentType} from 'react'; export type ItemSelectorProps = { - selectedItems: ReadonlyArray, - options: ReadonlyArray, - onChange: (items: ReadonlyArray) => void, - fixedOptions?: any[], - erasable?: boolean, - showArrow?: boolean, - searchable?: boolean, - displayOption?: string | ((opt: any) => any), - getOptionValue?: string | ((opt: any) => any), - filterOption?: string | ((opt: any) => any), - placement?: string, - disabled?: boolean, - isError?: boolean, - multiSelect?: boolean, - inputTheme?: string, - size?: string, - onBlur?: () => void, - placeholder?: string, - closeOnSelect?: boolean, - typeaheadPlaceholder?: string, - DropdownHeaderComponent?: ComponentType | null, - DropDownRenderComponent?: ComponentType, - DropDownLineItemRenderComponent?: ComponentType, - CustomChickletComponent?: ComponentType + selectedItems: ReadonlyArray; + options: ReadonlyArray; + onChange: (items: ReadonlyArray) => void; + fixedOptions?: any[]; + erasable?: boolean; + showArrow?: boolean; + searchable?: boolean; + displayOption?: string | ((opt: any) => any); + getOptionValue?: string | ((opt: any) => any); + filterOption?: string | ((opt: any) => any); + placement?: string; + disabled?: boolean; + isError?: boolean; + multiSelect?: boolean; + inputTheme?: string; + size?: string; + onBlur?: () => void; + placeholder?: string; + closeOnSelect?: boolean; + typeaheadPlaceholder?: string; + DropdownHeaderComponent?: ComponentType | null; + DropDownRenderComponent?: ComponentType; + DropDownLineItemRenderComponent?: ComponentType; + CustomChickletComponent?: ComponentType; }; const ItemSelector: FunctionComponent; diff --git a/src/components/common/modal.tsx b/src/components/common/modal.tsx index 26460dafa8..dda16facbd 100644 --- a/src/components/common/modal.tsx +++ b/src/components/common/modal.tsx @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React, {Component} from 'react'; +import React, {Component, ReactNode} from 'react'; import {FormattedMessage} from 'localization'; import styled, {FlattenSimpleInterpolation} from 'styled-components'; @@ -155,9 +155,10 @@ export interface ModalDialogProps { cssStyle?: FlattenSimpleInterpolation | string; style?: React.CSSProperties; theme: any; + children?: ReactNode; } -class ModalDialog extends Component { +export class ModalDialog extends Component { static defaultProps = { footer: false, close: true, diff --git a/src/components/common/styled-components.tsx b/src/components/common/styled-components.tsx index 424889184a..447f411013 100644 --- a/src/components/common/styled-components.tsx +++ b/src/components/common/styled-components.tsx @@ -477,9 +477,9 @@ export const StyledModalVerticalPanel = styled.div.attrs({ } `; -export const StyledModalSection = styled.div.attrs({ - className: 'modal-section' -})` +export const StyledModalSection = styled.div.attrs(({className}) => ({ + className: classnames('modal-section', className) +}))` margin-bottom: 32px; .modal-section-title { @@ -559,7 +559,7 @@ export const StyledAttrbution = styled.div.attrs({ } `; -interface StyledExportSectionProps { +export interface StyledExportSectionProps { disabled?: boolean; } diff --git a/src/components/modal-container.tsx b/src/components/modal-container.tsx index f197022c12..f06f207685 100644 --- a/src/components/modal-container.tsx +++ b/src/components/modal-container.tsx @@ -20,7 +20,6 @@ import React, {Component} from 'react'; import {css} from 'styled-components'; -import {findDOMNode} from 'react-dom'; import {createSelector} from 'reselect'; import get from 'lodash.get'; import document from 'global/document'; @@ -70,6 +69,7 @@ import * as UIStateActions from 'actions/ui-state-actions'; import * as MapStyleActions from 'actions/map-style-actions'; import * as ProviderActions from 'actions/provider-actions'; import {ModalDialogProps} from './common/modal'; +import {Provider} from 'cloud-providers'; const DataTableModalStyle = css` top: 80px; @@ -114,7 +114,7 @@ export type ModalContainerProps = { mapStyleActions: typeof MapStyleActions; providerActions: typeof ProviderActions; onSaveToStorage?: () => void; - cloudProviders: object[]; + cloudProviders: Provider[]; onLoadCloudMapSuccess?: OnSuccessCallBack; onLoadCloudMapError?: OnErrorCallBack; onExportToCloudSuccess?: OnSuccessCallBack; @@ -274,7 +274,6 @@ export default function ModalContainerFactory( mapState, uiState, visState, - rootNode, visStateActions, uiStateActions, providerState @@ -300,8 +299,6 @@ export default function ModalContainerFactory( const width = containerW * 0.9; template = ( @@ -547,7 +542,6 @@ export default function ModalContainerFactory( return this.props.rootNode ? ( findDOMNode(rootNode)} isOpen={Boolean(currentModal)} onCancel={this._closeModal} {...modalProps} diff --git a/src/components/modals/add-map-style-modal.js b/src/components/modals/add-map-style-modal.tsx similarity index 94% rename from src/components/modals/add-map-style-modal.js rename to src/components/modals/add-map-style-modal.tsx index 9768007a01..ef89e13d31 100644 --- a/src/components/modals/add-map-style-modal.js +++ b/src/components/modals/add-map-style-modal.tsx @@ -20,7 +20,6 @@ import React, {Component} from 'react'; import {polyfill} from 'react-lifecycles-compat'; -import PropTypes from 'prop-types'; import classnames from 'classnames'; import styled from 'styled-components'; import MapboxGLMap from 'react-map-gl'; @@ -35,8 +34,10 @@ import {media} from 'styles/media-breakpoints'; // Utils import {transformRequest} from 'utils/map-style-utils/mapbox-utils'; -import {injectIntl} from 'react-intl'; +import {injectIntl, IntlShape} from 'react-intl'; import {FormattedMessage} from 'localization'; +import {InputStyle, MapState} from 'reducers'; +import mapboxgl from 'mapbox-gl'; const MapH = 190; const MapW = 264; @@ -104,17 +105,18 @@ const InlineLink = styled.a` } `; -function AddMapStyleModalFactory() { - class AddMapStyleModal extends Component { - static propTypes = { - inputMapStyle: PropTypes.func.isRequired, - inputStyle: PropTypes.object.isRequired, - loadCustomMapStyle: PropTypes.func.isRequired, - mapboxApiAccessToken: PropTypes.string.isRequired, - mapboxApiUrl: PropTypes.string.isRequired, - mapState: PropTypes.object.isRequired - }; +interface AddMapStyleModalProps { + inputMapStyle: Function; + inputStyle: InputStyle; + loadCustomMapStyle: Function; + mapboxApiAccessToken: string; + mapboxApiUrl?: string; + mapState: MapState; + intl: IntlShape; +} +function AddMapStyleModalFactory() { + class AddMapStyleModal extends Component { state = { reRenderKey: 0, previousToken: null @@ -139,6 +141,9 @@ function AddMapStyleModalFactory() { return null; } + mapRef: MapboxGLMap | null | undefined; + _map: mapboxgl.Map | undefined; + componentDidUpdate() { const map = this.mapRef && this.mapRef.getMap(); if (map && this._map !== map) { @@ -283,7 +288,7 @@ function AddMapStyleModalFactory() { key={this.state.reRenderKey} width={MapW} height={MapH} - mapStyle={inputStyle.url} + mapStyle={inputStyle.url === null ? undefined : inputStyle.url} /> )} diff --git a/src/components/modals/cloud-tile.js b/src/components/modals/cloud-tile.tsx similarity index 79% rename from src/components/modals/cloud-tile.js rename to src/components/modals/cloud-tile.tsx index 2aecaa492b..eb5ef65042 100644 --- a/src/components/modals/cloud-tile.js +++ b/src/components/modals/cloud-tile.tsx @@ -18,15 +18,20 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React from 'react'; +import React, {ReactNode} from 'react'; import styled from 'styled-components'; import {Logout, Login} from 'components/common/icons'; import {CenterVerticalFlexbox, Button, CheckMark} from 'components/common/styled-components'; import LoadingSpinner from 'components/common/loading-spinner'; +import {Provider} from 'cloud-providers'; + +interface StyledTileWrapperProps { + selected?: boolean; +} const StyledTileWrapper = styled.div.attrs({ className: 'provider-tile__wrapper' -})` +})` display: flex; flex-direction: column; align-items: center; @@ -72,28 +77,57 @@ const StyledUserName = styled.div` text-overflow: ellipsis; `; -const LoginButton = ({onClick}) => ( +interface OnClickProps { + onClick?: React.MouseEventHandler; +} + +const LoginButton = ({onClick}: OnClickProps) => ( ); -const LogoutButton = ({onClick}) => ( +const LogoutButton = ({onClick}: OnClickProps) => ( ); -const ActionButton = ({isConnected, actionName = null, isReady}) => +interface ActionButtonProps { + isConnected?: boolean; + actionName?: ReactNode | null; + isReady?: boolean; +} + +const ActionButton = ({isConnected, actionName = null, isReady}: ActionButtonProps) => isConnected && actionName ? ( ) : null; -const CloudTile = ({ +interface CloudTileProps { + onSelect?: React.MouseEventHandler; + // default to login + onConnect?: React.MouseEventHandler | null; + // default to logout + onLogout?: React.MouseEventHandler | null; + // action name + actionName?: ReactNode | null; + // cloud provider class + cloudProvider: Provider; + // function to take after login or logout + onSetCloudProvider; + // whether provider is selected as currentProvider + isSelected?: boolean; + // whether user has logged in + isConnected?: boolean; + isReady?: boolean; +} + +const CloudTile: React.FC = ({ // action when click on the tile onSelect, // default to login diff --git a/src/components/modals/data-table-modal.js b/src/components/modals/data-table-modal.tsx similarity index 77% rename from src/components/modals/data-table-modal.js rename to src/components/modals/data-table-modal.tsx index 1aee582826..1eca6b3ee2 100644 --- a/src/components/modals/data-table-modal.js +++ b/src/components/modals/data-table-modal.tsx @@ -25,6 +25,8 @@ import DataTableFactory from 'components/common/data-table'; import {createSelector} from 'reselect'; import {renderedSize} from 'components/common/data-table/cell-size'; import CanvasHack from 'components/common/data-table/canvas'; +import {Datasets} from 'reducers'; +import KeplerTable from 'utils/table-utils/kepler-table'; const dgSettings = { sidePadding: '38px', @@ -43,7 +45,11 @@ const DatasetCatalog = styled.div` padding: ${dgSettings.verticalPadding} ${dgSettings.sidePadding} 0; `; -export const DatasetModalTab = styled.div` +interface DatasetModalTabProps { + active?: boolean; +} + +export const DatasetModalTab = styled.div` align-items: center; border-bottom: 3px solid ${props => (props.active ? 'black' : 'transparent')}; cursor: pointer; @@ -58,7 +64,17 @@ export const DatasetModalTab = styled.div` } `; -const DatasetTabsUnmemoized = ({activeDataset, datasets, showDatasetTable}) => ( +interface DatasetTabsUnmemoizedProps { + activeDataset: KeplerTable; + datasets: Datasets; + showDatasetTable: (id: string) => void; +} + +const DatasetTabsUnmemoized: React.FC = ({ + activeDataset, + datasets, + showDatasetTable +}) => ( {Object.values(datasets).map(dataset => ( void; + pinTableColumn: (id: string, column: string) => void; + copyTableColumn: (id: string, column: string) => void; + datasets: Datasets; + showDatasetTable: (id: string) => void; + showTab?: boolean; +} + +function DataTableModalFactory(DataTable: ReturnType): React.ComponentType> { + class DataTableModal extends React.Component { datasetCellSizeCache = {}; - dataId = props => props.dataId; - datasets = props => props.datasets; - fields = props => (props.datasets[props.dataId] || {}).fields; + dataId = ({dataId = ''}: DataTableModalProps) => dataId; + datasets = (props: DataTableModalProps) => props.datasets; + fields = ({datasets, dataId = ''}: DataTableModalProps) => (datasets[dataId] || {}).fields; columns = createSelector(this.fields, fields => fields.map(f => f.name)); colMeta = createSelector(this.fields, fields => fields.reduce( @@ -113,7 +140,7 @@ function DataTableModalFactory(DataTable) { } const {fields, dataContainer} = datasets[dataId]; - let showCalculate = null; + let showCalculate: boolean | null = null; if (!this.datasetCellSizeCache[dataId]) { showCalculate = true; } else if ( @@ -152,23 +179,23 @@ function DataTableModalFactory(DataTable) { return cellSizeCache; }); - copyTableColumn = column => { - const {dataId, copyTableColumn} = this.props; + copyTableColumn = (column: string) => { + const {dataId = '', copyTableColumn} = this.props; copyTableColumn(dataId, column); }; - pinTableColumn = column => { - const {dataId, pinTableColumn} = this.props; + pinTableColumn = (column: string) => { + const {dataId = '', pinTableColumn} = this.props; pinTableColumn(dataId, column); }; - sortTableColumn = (column, mode) => { - const {dataId, sortTableColumn} = this.props; + sortTableColumn = (column: string, mode?: string) => { + const {dataId = '', sortTableColumn} = this.props; sortTableColumn(dataId, column, mode); }; render() { - const {datasets, dataId, showDatasetTable, showTab} = this.props; + const {datasets, dataId, showDatasetTable, showTab = true} = this.props; if (!datasets || !dataId) { return null; } @@ -208,9 +235,6 @@ function DataTableModalFactory(DataTable) { ); } } - DataTableModal.defaultProps = { - showTab: true - }; return withTheme(DataTableModal); } diff --git a/src/components/modals/delete-data-modal.js b/src/components/modals/delete-data-modal.tsx similarity index 87% rename from src/components/modals/delete-data-modal.js rename to src/components/modals/delete-data-modal.tsx index 970bf81a42..66f3b82d71 100644 --- a/src/components/modals/delete-data-modal.js +++ b/src/components/modals/delete-data-modal.tsx @@ -22,12 +22,19 @@ import React from 'react'; import styled from 'styled-components'; import DatasetLabel from 'components/common/dataset-label'; import {FormattedMessage} from 'localization'; +import {Layer} from 'layers'; +import KeplerTable from 'utils/table-utils/kepler-table'; const StyledMsg = styled.div` margin-top: 24px; `; -export const DeleteDatasetModal = ({dataset, layers = []}) => { +export interface DeleteDatasetModalProps { + dataset: KeplerTable; + layers: Layer[]; +} + +export const DeleteDatasetModal: React.FC = ({dataset, layers = []}) => { // retrieve only layers related to the current dataset const currDatasetLayers = layers.filter(layer => layer.config.dataId === (dataset && dataset.id)); diff --git a/src/components/modals/error-display.js b/src/components/modals/error-display.tsx similarity index 93% rename from src/components/modals/error-display.js rename to src/components/modals/error-display.tsx index 7791120c93..7bb5f4e7a3 100644 --- a/src/components/modals/error-display.js +++ b/src/components/modals/error-display.tsx @@ -23,7 +23,11 @@ import ErrorBoundary from 'components/common/error-boundary'; import NotificationItemFactory from 'components/notification-panel/notification-item'; const NotificationItem = NotificationItemFactory(); -const ErrorDisplay = ({error}) => ( +interface ErrorDisplayProps { + error: string; +} + +const ErrorDisplay: React.FC = ({error}) => ( { +const getDataRowCount = ( + datasets: Datasets, + selectedDataset: string | undefined, + filtered: boolean, + intl: IntlShape +) => { + if (selectedDataset === undefined) { + return; + } const selectedData = datasets[selectedDataset]; if (!selectedData) { return intl.formatMessage( @@ -60,29 +55,47 @@ const getDataRowCount = (datasets, selectedDataset, filtered, intl) => { return '-'; } - const rowCount = filtered ? filteredIdxCPU.length : dataContainer.numRows(); + const rowCount = filtered ? filteredIdxCPU?.length : dataContainer.numRows(); return intl.formatMessage( {id: 'modal.exportData.rowCount'}, - {rowCount: rowCount.toLocaleString()} + {rowCount: rowCount?.toLocaleString()} ); }; +export interface ExportDataModalProps { + datasets: Datasets; + selectedDataset?: string; + dataType: string; + filtered: boolean; + // callbacks + applyCPUFilter: (filter: string | string[]) => void; + onChangeExportSelectedDataset: (dataset: string) => void; + onChangeExportDataType: (type: string) => void; + onChangeExportFiltered: (isFiltered: boolean) => void; + intl: IntlShape; + supportedDataTypes: { + id: string; + label: string; + available: boolean; + }[]; +} + const ExportDataModalFactory = () => { - class ExportDataModal extends Component { + class ExportDataModal extends Component { componentDidMount() { const toCPUFilter = this.props.selectedDataset || Object.keys(this.props.datasets); this.props.applyCPUFilter(toCPUFilter); } - _onSelectDataset = ({target: {value}}) => { + _onSelectDataset: React.ChangeEventHandler = ({target: {value}}) => { this.props.applyCPUFilter(value); this.props.onChangeExportSelectedDataset(value); }; render() { const { - supportedDataTypes, + supportedDataTypes = EXPORT_DATA_TYPE_OPTIONS, datasets, selectedDataset, dataType, @@ -130,7 +143,6 @@ const ExportDataModalFactory = () => { op.available && onChangeExportDataType(op.id)} > @@ -182,10 +194,6 @@ const ExportDataModalFactory = () => { ); } } - ExportDataModal.propTypes = propTypes; - ExportDataModal.defaultProps = { - supportedDataTypes: EXPORT_DATA_TYPE_OPTIONS - }; return injectIntl(ExportDataModal); }; diff --git a/src/components/modals/export-image-modal.d.ts b/src/components/modals/export-image-modal.d.ts deleted file mode 100644 index 9ff387b75d..0000000000 --- a/src/components/modals/export-image-modal.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {FunctionComponent} from 'react'; -import {SetExportImageSettingUpdaterAction} from '../../actions'; -import {ExportImage} from 'reducers'; -import {IntlShape} from 'react-intl'; - -export type ExportImageModalProps = { - exportImage: ExportImage; - mapW: number; - mapH: number; - onUpdateImageSetting: (payload: SetExportImageSettingUpdaterAction.payload) => void; - cleanupExportImage: () => void; -}; - -type IntlProps = { - intl: IntlShape; -}; - -export const ExportImageModal: FunctionComponent; -function ExportImageModalFactory(): FunctionComponent; - -export default ExportImageModalFactory; diff --git a/src/components/modals/export-image-modal.js b/src/components/modals/export-image-modal.tsx similarity index 91% rename from src/components/modals/export-image-modal.js rename to src/components/modals/export-image-modal.tsx index 3a3d73f908..1fd801ca04 100644 --- a/src/components/modals/export-image-modal.js +++ b/src/components/modals/export-image-modal.tsx @@ -21,13 +21,15 @@ import React, {useEffect} from 'react'; import styled from 'styled-components'; import ImagePreview from 'components/common/image-preview'; +import {SetExportImageSettingUpdaterAction} from '../../actions'; import {EXPORT_IMG_RATIO_OPTIONS, EXPORT_IMG_RESOLUTION_OPTIONS} from 'constants/default-settings'; import {StyledModalContent, SelectionButton, CheckMark} from 'components/common/styled-components'; import Switch from 'components/common/switch'; -import {injectIntl} from 'react-intl'; +import {injectIntl, IntlShape} from 'react-intl'; import {FormattedMessage} from 'localization'; +import {ExportImage} from 'reducers'; const ImageOptionList = styled.div` display: flex; @@ -53,9 +55,18 @@ const ImageOptionList = styled.div` } `; +export interface ExportImageModalProps { + exportImage: ExportImage; + mapW: number; + mapH: number; + onUpdateImageSetting: (payload: SetExportImageSettingUpdaterAction['payload']) => void; + cleanupExportImage: () => void; + intl: IntlShape; +} + const ExportImageModalFactory = () => { /** @type {typeof import('./export-image-modal').ExportImageModal} */ - const ExportImageModal = ({ + const ExportImageModal: React.FC = ({ mapW, mapH, exportImage, diff --git a/src/components/modals/export-map-modal/components.js b/src/components/modals/export-map-modal/components.tsx similarity index 100% rename from src/components/modals/export-map-modal/components.js rename to src/components/modals/export-map-modal/components.tsx diff --git a/src/components/modals/export-map-modal/export-html-map.d.ts b/src/components/modals/export-map-modal/export-html-map.d.ts deleted file mode 100644 index 504e182b17..0000000000 --- a/src/components/modals/export-map-modal/export-html-map.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import {setUserMapboxAccessToken, setExportHTMLMapMode} from '../../actions'; -import {IntlShape} from 'react-intl'; - -export type ExportHtmlMapProps = { - onChangeExportMapHTMLMode: typeof setExportHTMLMapMode; - onEditUserMapboxAccessToken: typeof setUserMapboxAccessToken; - options: { - userMapboxToken?: string; - mode?: string; - }; -}; - -type IntlProps = { - intl: IntlShape; -}; - -export const ExportHtmlMap: React.FunctionComponent; -function ExportHtmlMapFactory(): React.FunctionComponent; - -export default ExportHtmlMapFactory; diff --git a/src/components/modals/export-map-modal/export-html-map.js b/src/components/modals/export-map-modal/export-html-map.tsx similarity index 88% rename from src/components/modals/export-map-modal/export-html-map.js rename to src/components/modals/export-map-modal/export-html-map.tsx index 0ac2006432..a50e0c42ab 100644 --- a/src/components/modals/export-map-modal/export-html-map.js +++ b/src/components/modals/export-map-modal/export-html-map.tsx @@ -26,6 +26,9 @@ import {EXPORT_HTML_MAP_DOC, EXPORT_HTML_MAP_MODES_DOC} from 'constants/user-gui import styled from 'styled-components'; import {injectIntl} from 'react-intl'; import {FormattedMessage} from 'localization'; +import {IntlShape} from 'react-intl'; + +import {setUserMapboxAccessToken, setExportHTMLMapMode, ActionHandler} from 'actions'; const ExportMapStyledExportSection = styled(StyledExportSection)` .disclaimer { @@ -35,7 +38,11 @@ const ExportMapStyledExportSection = styled(StyledExportSection)` } `; -const StyledInput = styled.input` +interface StyledInputProps { + error?: boolean; +} + +const StyledInput = styled.input` width: 100%; padding: ${props => props.theme.inputPadding}; color: ${props => (props.error ? 'red' : props.theme.titleColorLT)}; @@ -60,9 +67,22 @@ const BigStyledTile = styled(StyledType)` } `; -function ExportHtmlMapFactory() { +type ExportHtmlMapProps = { + onChangeExportMapHTMLMode: ActionHandler; + onEditUserMapboxAccessToken: ActionHandler; + options: { + userMapboxToken?: string; + mode?: string; + }; +}; + +type IntlProps = { + intl: IntlShape; +}; + +function ExportHtmlMapFactory(): React.ComponentType { /** @type {typeof import('./export-html-map').ExportHtmlMap} */ - const ExportHtmlMap = ({ + const ExportHtmlMap: React.FC = ({ onChangeExportMapHTMLMode = mode => {}, onEditUserMapboxAccessToken = token => {}, options = {}, @@ -119,7 +139,6 @@ function ExportHtmlMapFactory() { mode.available && onChangeExportMapHTMLMode(mode.id)} > diff --git a/src/components/modals/export-map-modal/export-json-map.js b/src/components/modals/export-map-modal/export-json-map.tsx similarity index 90% rename from src/components/modals/export-map-modal/export-json-map.js rename to src/components/modals/export-map-modal/export-json-map.tsx index f2287a8369..7cb5764d7d 100644 --- a/src/components/modals/export-map-modal/export-json-map.js +++ b/src/components/modals/export-map-modal/export-json-map.tsx @@ -19,7 +19,6 @@ // THE SOFTWARE. import React, {useState} from 'react'; -import PropTypes from 'prop-types'; import JSONPretty from 'react-json-pretty'; import {ADD_DATA_TO_MAP_DOC} from 'constants/user-guides'; import styled from 'styled-components'; @@ -62,11 +61,11 @@ const StyledJsonExportSection = styled(StyledExportSection)` } `; -const exportJsonPropTypes = { - options: PropTypes.object +type ExportJsonPropTypes = { + config: any; }; -const ExportJsonMapUnmemoized = ({config = {}}) => { +const ExportJsonMapUnmemoized = ({config = {}}: ExportJsonPropTypes) => { const [copied, setCopy] = useState(false); return (
@@ -89,11 +88,7 @@ const ExportJsonMapUnmemoized = ({config = {}}) => {
- setCopy(true)} - > + setCopy(true)}>
@@ -108,8 +103,6 @@ const ExportJsonMapUnmemoized = ({config = {}}) => { ); }; -ExportJsonMapUnmemoized.propTypes = exportJsonPropTypes; - ExportJsonMapUnmemoized.displayName = 'ExportJsonMap'; const ExportJsonMap = React.memo(ExportJsonMapUnmemoized); diff --git a/src/components/modals/export-map-modal/export-map-modal.js b/src/components/modals/export-map-modal/export-map-modal.tsx similarity index 81% rename from src/components/modals/export-map-modal/export-map-modal.js rename to src/components/modals/export-map-modal/export-map-modal.tsx index 4c992ba960..c8a863d1ab 100644 --- a/src/components/modals/export-map-modal/export-map-modal.js +++ b/src/components/modals/export-map-modal/export-map-modal.tsx @@ -19,7 +19,6 @@ // THE SOFTWARE. import React from 'react'; -import PropTypes from 'prop-types'; import {FileType} from 'components/common/icons'; import {StyledModalContent, StyledType, CheckMark} from 'components/common/styled-components'; @@ -28,30 +27,34 @@ import {StyledExportMapSection} from './components'; import ExportHtmlMapFactory from './export-html-map'; import ExportJsonMapFactory from './export-json-map'; import {FormattedMessage} from 'localization'; +import {ActionHandler, setExportHTMLMapMode, setUserMapboxAccessToken} from 'actions'; -const propTypes = { - options: PropTypes.object, - onEditUserMapboxAccessToken: PropTypes.func.isRequired, - onChangeExportData: PropTypes.func, - onChangeExportMapType: PropTypes.func, - mapFormat: PropTypes.string -}; +interface ExportMapModalFactoryProps { + options?: {format: string}; + config: any; + onEditUserMapboxAccessToken: ActionHandler; + onChangeExportMapHTMLMode?: ActionHandler; + onChangeExportMapFormat?: (format: string) => any; + mapFormat?: string; +} const style = {width: '100%'}; -const NO_OP = () => {}; +const NO_OP = (...args: any[]) => ({} as any); ExportMapModalFactory.deps = [ExportHtmlMapFactory, ExportJsonMapFactory]; -function ExportMapModalFactory(ExportHtmlMap, ExportJsonMap) { +function ExportMapModalFactory( + ExportHtmlMap: ReturnType, + ExportJsonMap: ReturnType +) { const ExportMapModalUnmemoized = ({ config = {}, - onChangeExportData = NO_OP, - onChangeExportMapFormat = format => {}, + onChangeExportMapFormat = NO_OP, onChangeExportMapHTMLMode = NO_OP, onEditUserMapboxAccessToken = NO_OP, options = {format: ''} - }) => ( + }: ExportMapModalFactoryProps) => (
@@ -68,7 +71,6 @@ function ExportMapModalFactory(ExportHtmlMap, ExportJsonMap) { op.available && onChangeExportMapFormat(op.id)} > @@ -87,13 +89,7 @@ function ExportMapModalFactory(ExportHtmlMap, ExportJsonMap) { options={options[options.format]} /> ), - [EXPORT_MAP_FORMATS.JSON]: ( - - ) + [EXPORT_MAP_FORMATS.JSON]: }[ // @ts-ignore options.format @@ -103,7 +99,6 @@ function ExportMapModalFactory(ExportHtmlMap, ExportJsonMap) { ); - ExportMapModalUnmemoized.propTypes = propTypes; ExportMapModalUnmemoized.displayName = 'ExportMapModal'; const ExportMapModal = React.memo(ExportMapModalUnmemoized); diff --git a/src/components/modals/image-modal-container.d.ts b/src/components/modals/image-modal-container.d.ts deleted file mode 100644 index e8257808f5..0000000000 --- a/src/components/modals/image-modal-container.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import {SetExportImageSettingUpdaterAction} from '../../actions'; -import {Provider} from '../../cloud-providers'; - -export type ImageModalContainerProps = { - cloudProviders?: Provider[]; - currentProvider?: string; - onUpdateImageSetting: (newSetting: SetExportImageSettingUpdaterAction['payload']) => void; - cleanupExportImage: () => void; -}; - -export const ImageModalContainer: React.FunctionComponent; -export default ImageModalContainer; diff --git a/src/components/modals/image-modal-container.js b/src/components/modals/image-modal-container.tsx similarity index 87% rename from src/components/modals/image-modal-container.js rename to src/components/modals/image-modal-container.tsx index 4a2f555ba2..1115e15e87 100644 --- a/src/components/modals/image-modal-container.js +++ b/src/components/modals/image-modal-container.tsx @@ -22,15 +22,24 @@ import React, {useEffect} from 'react'; import get from 'lodash.get'; import {MAP_THUMBNAIL_DIMENSION, EXPORT_IMG_RATIOS} from 'constants/default-settings'; +import {SetExportImageSettingUpdaterAction} from 'actions'; +import {Provider} from 'cloud-providers'; /** @typedef {import('./image-modal-container').ImageModalContainerProps} ImageModalContainerProps */ +export type ImageModalContainerProps = { + cloudProviders?: Provider[]; + currentProvider?: string | null; + onUpdateImageSetting: (newSetting: SetExportImageSettingUpdaterAction['payload']) => void; + cleanupExportImage: () => void; +}; + /** * A wrapper component in modals contain a image preview of the map with cloud providers * It sets export image size based on provider thumbnail size * @type {React.FunctionComponent} */ -const ImageModalContainer = ({ +const ImageModalContainer: React.FC = ({ onUpdateImageSetting, cleanupExportImage, cloudProviders, diff --git a/src/components/modals/load-data-modal.d.ts b/src/components/modals/load-data-modal.d.ts deleted file mode 100644 index ba3e1f3c57..0000000000 --- a/src/components/modals/load-data-modal.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import {FileLoading} from '../../reducers/vis-state-updaters'; - -export type LoadDataModalProps = { - // call backs - onFileUpload: (files: File[]) => void; - onLoadCloudMap: (provider: any, vis: any) => void; - fileLoading: FileLoading | false, - loadingMethods: { - id: string; - label: string; - elementType: React.Component; - tabElementType?: React.Component; - }[]; - /** A list of names of supported formats suitable to present to user */ - fileFormatNames: string[]; - /** A list of typically 3 letter extensions (without '.') for file matching */ - fileExtensions: string[]; - isCloudMapLoading: boolean; - /** Set to true if app wants to do its own file filtering */ - disableExtensionFilter?: boolean; -}; - -export function LoadDataModalFactory( - ModalTabs: React.Component, - FileUpload: React.Component, - LoadStorageMap: React.Component -): React.ComponentType; - -export default LoadDataModalFactory; diff --git a/src/components/modals/load-data-modal.js b/src/components/modals/load-data-modal.tsx similarity index 73% rename from src/components/modals/load-data-modal.js rename to src/components/modals/load-data-modal.tsx index eb642d4417..fcc4388781 100644 --- a/src/components/modals/load-data-modal.js +++ b/src/components/modals/load-data-modal.tsx @@ -21,7 +21,7 @@ import React, {useState} from 'react'; import styled from 'styled-components'; import get from 'lodash.get'; -import {useIntl} from 'react-intl'; +import {IntlShape, useIntl} from 'react-intl'; import FileUploadFactory from 'components/common/file-uploader/file-upload'; import LoadStorageMapFactory from './load-storage-map'; @@ -29,6 +29,7 @@ import ModalTabsFactory from './modal-tabs'; import LoadingDialog from './loading-dialog'; import {LOADING_METHODS} from 'constants/default-settings'; +import {FileLoading} from 'reducers/vis-state-updaters'; /** @typedef {import('./load-data-modal').LoadDataModalProps} LoadDataModalProps */ @@ -43,12 +44,37 @@ const StyledLoadDataModal = styled.div.attrs({ const noop = () => {}; const getDefaultMethod = methods => (Array.isArray(methods) ? get(methods, [0]) : null); +export interface LoadingMethod { + id: string; + label: string; + elementType: React.ComponentType; + tabElementType?: React.ComponentType<{onClick: React.MouseEventHandler; intl: IntlShape}>; +} + +type LoadDataModalProps = { + // call backs + onFileUpload: (files: File[]) => void; + onLoadCloudMap: (provider: any, vis: any) => void; + fileLoading: FileLoading | false; + loadingMethods: LoadingMethod[]; + /** A list of names of supported formats suitable to present to user */ + fileFormatNames: string[]; + /** A list of typically 3 letter extensions (without '.') for file matching */ + fileExtensions: string[]; + isCloudMapLoading: boolean; + /** Set to true if app wants to do its own file filtering */ + disableExtensionFilter?: boolean; +}; LoadDataModalFactory.deps = [ModalTabsFactory, FileUploadFactory, LoadStorageMapFactory]; -export function LoadDataModalFactory(ModalTabs, FileUpload, LoadStorageMap) { +export function LoadDataModalFactory( + ModalTabs: ReturnType, + FileUpload: ReturnType, + LoadStorageMap: ReturnType +) { /** @type {React.FunctionComponent} */ - const LoadDataModal = props => { + const LoadDataModal: React.FC = props => { const intl = useIntl(); const {loadingMethods, isCloudMapLoading} = props; const [currentMethod, toggleMethod] = useState(getDefaultMethod(loadingMethods)); diff --git a/src/components/modals/load-storage-map.js b/src/components/modals/load-storage-map.tsx similarity index 88% rename from src/components/modals/load-storage-map.js rename to src/components/modals/load-storage-map.tsx index fc3084b977..8be4127d25 100644 --- a/src/components/modals/load-storage-map.js +++ b/src/components/modals/load-storage-map.tsx @@ -28,6 +28,7 @@ import CloudTile from './cloud-tile'; import {Base, ArrowLeft} from 'components/common/icons'; import ProviderModalContainer from './provider-modal-container'; import {FormattedMessage} from 'localization'; +import {MapListItem, Provider} from 'cloud-providers'; const StyledProviderSection = styled.div.attrs({ className: 'provider-selection' @@ -179,7 +180,9 @@ const StyledVisualizationItem = styled.div` } `; -const MapIcon = props => { +type MapIconPorps = React.DetailedHTMLProps, HTMLDivElement>; + +const MapIcon: React.FC = props => { return (
{props.children} @@ -193,11 +196,24 @@ const MapIcon = props => { ); }; -const PrivacyBadge = ({privateMap}) => ( +interface PrivacyBadgeProps { + privateMap?: boolean; +} + +const PrivacyBadge: React.FC = ({privateMap}) => ( {privateMap ? 'Private' : 'Public'} ); -const VisualizationItem = ({vis, onClick}) => { +interface Visualization extends MapListItem { + thumbnail?: Blob; +} + +interface VisualizationItemProps { + onClick?: React.MouseEventHandler; + vis: Visualization; +} + +const VisualizationItem: React.FC = ({vis, onClick}) => { return ( {vis.thumbnail ? ( @@ -220,7 +236,14 @@ const VisualizationItem = ({vis, onClick}) => { ); }; -export const ProviderSelect = ({ +interface ProviderSelectProps { + cloudProviders: Provider[]; + onSelect: (name: string) => void; + onSetCloudProvider: () => void; + currentProvider?: string; +} + +export const ProviderSelect: React.FC = ({ cloudProviders = [], onSelect, onSetCloudProvider, @@ -243,8 +266,18 @@ export const ProviderSelect = ({

No storage provider available

); -function LoadStorageMapFactory() { - class LoadStorageMap extends Component { +interface LoadStorageMapProps { + cloudProviders: Provider[]; + onSetCloudProvider; + currentProvider?: string; + getSavedMaps: (provider?: Provider) => void; + onLoadCloudMap: ({loadParams: any, provider: Provider}) => void; + visualizations: Visualization[]; + isProviderLoading?: boolean; +} + +function LoadStorageMapFactory(): React.ComponentType { + class LoadStorageMap extends Component { state = { showProviderSelect: true }; @@ -272,7 +305,7 @@ function LoadStorageMapFactory() { } } - _onLoadCloudMap(provider, vis) { + _onLoadCloudMap(provider: Provider | undefined, vis: Visualization) { this.props.onLoadCloudMap({ loadParams: vis.loadParams, provider @@ -330,10 +363,10 @@ function LoadStorageMapFactory() { - {provider.getManagementUrl && ( + {provider?.getManagementUrl && ( ( +interface LoadingDialogProps { + size?: number; + message?: string; +} + +const LoadingDialog: React.FC = ({ + size = 64, + message = 'modal.loadingDialog.loading' +}) => (
diff --git a/src/components/modals/modal-dialog.js b/src/components/modals/modal-dialog.tsx similarity index 100% rename from src/components/modals/modal-dialog.js rename to src/components/modals/modal-dialog.tsx diff --git a/src/components/modals/modal-tabs.js b/src/components/modals/modal-tabs.tsx similarity index 92% rename from src/components/modals/modal-tabs.js rename to src/components/modals/modal-tabs.tsx index 3203afc60d..bafe3f4fee 100644 --- a/src/components/modals/modal-tabs.js +++ b/src/components/modals/modal-tabs.tsx @@ -24,6 +24,7 @@ import styled from 'styled-components'; import PropTypes from 'prop-types'; import {media} from 'styles'; import {FormattedMessage, useIntl} from 'react-intl'; +import {LoadingMethod} from './load-data-modal'; const ModalTab = styled.div` align-items: flex-end; @@ -74,7 +75,17 @@ const StyledLoadDataModalTabItem = styled.div` const noop = () => {}; -export const ModalTabItem = ({currentMethod, method, toggleMethod}) => { +interface ModalTabItemProps { + currentMethod?: string; + method: LoadingMethod; + toggleMethod: (method: LoadingMethod) => void; +} + +export const ModalTabItem: React.FC = ({ + currentMethod, + method, + toggleMethod +}) => { const onClick = useCallback(() => toggleMethod(method), [method, toggleMethod]); const intl = useIntl(); diff --git a/src/components/modals/overwrite-map-modal.d.ts b/src/components/modals/overwrite-map-modal.d.ts deleted file mode 100644 index e7c139de0d..0000000000 --- a/src/components/modals/overwrite-map-modal.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import {Provider} from '../../cloud-providers'; -import {ImageModalContainerProps} from './image-modal-container'; - -export type OverwriteMapModalProps = { - mapSaved: string; - title: string; - cloudProviders: Provider[]; - isProviderLoading: boolean; - currentProvider: string; - - // callbacks - onUpdateImageSetting: ImageModalContainerProps['onUpdateImageSetting']; - cleanupExportImage: onUpdateImageSetting['cleanupExportImage']; -}; - -function OverwriteMapModalFactory(): React.FunctionComponent; -export default OverwriteMapModalFactory; diff --git a/src/components/modals/overwrite-map-modal.js b/src/components/modals/overwrite-map-modal.tsx similarity index 86% rename from src/components/modals/overwrite-map-modal.js rename to src/components/modals/overwrite-map-modal.tsx index 40fb151e27..7f20642d9d 100644 --- a/src/components/modals/overwrite-map-modal.js +++ b/src/components/modals/overwrite-map-modal.tsx @@ -24,6 +24,10 @@ import {CenterVerticalFlexbox} from 'components/common/styled-components'; import {UploadAnimation} from './status-panel'; import ImageModalContainer from './image-modal-container'; import {FormattedMessage} from 'localization'; +import {Provider} from 'cloud-providers'; + +import {ImageModalContainerProps} from './image-modal-container'; +import {cleanupExportImage} from 'actions'; /** @typedef {import('./overwrite-map-modal').OverwriteMapModalProps} OverwriteMapModalProps */ @@ -46,11 +50,23 @@ const StyledOverwriteMapModal = styled(CenterVerticalFlexbox)` min-height: 220px; `; +type OverwriteMapModalProps = { + mapSaved: string | null; + title: string; + cloudProviders: Provider[]; + isProviderLoading: boolean; + currentProvider: string | null; + + // callbacks + onUpdateImageSetting: ImageModalContainerProps['onUpdateImageSetting']; + cleanupExportImage: typeof cleanupExportImage; +}; + const OverwriteMapModalFactory = () => { /** * @type {React.FunctionComponent} */ - const OverwriteMapModal = ({ + const OverwriteMapModal: React.FC = ({ mapSaved, title, currentProvider, diff --git a/src/components/modals/provider-modal-container.d.ts b/src/components/modals/provider-modal-container.d.ts deleted file mode 100644 index 16aaac0c9e..0000000000 --- a/src/components/modals/provider-modal-container.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import {SetCloudProviderPayload} from '../../actions'; -import {Provider} from '../../cloud-providers'; - -export type ProviderModalContainerProps = { - cloudProviders?: Provider[]; - currentProvider?: string; - onSetCloudProvider: (provider: SetCloudProviderPayload) => void; -}; - -export const ProviderModalContainer: React.FunctionComponent; -export default ProviderModalContainer; diff --git a/src/components/modals/provider-modal-container.js b/src/components/modals/provider-modal-container.tsx similarity index 80% rename from src/components/modals/provider-modal-container.js rename to src/components/modals/provider-modal-container.tsx index de58b59b14..bbfcbf0168 100644 --- a/src/components/modals/provider-modal-container.js +++ b/src/components/modals/provider-modal-container.tsx @@ -19,20 +19,21 @@ // THE SOFTWARE. import React, {Component} from 'react'; -import PropTypes from 'prop-types'; +import {Provider} from 'cloud-providers'; +import {SetCloudProviderPayload} from 'actions'; + +export type ProviderModalContainerProps = { + cloudProviders?: Provider[]; + currentProvider?: string | null; + onSetCloudProvider: (provider: SetCloudProviderPayload) => void; +}; /** * A wrapper component in modals contain cloud providers. * It set default provider by checking which provider has logged in * @component */ -export default class ProviderModalContainer extends Component { - static propTypes = { - onSetCloudProvider: PropTypes.func.isRequired, - cloudProviders: PropTypes.arrayOf(PropTypes.object), - currentProvider: PropTypes.string - }; - +export default class ProviderModalContainer extends Component { static defaultProps = { cloudProviders: [], currentProvider: null @@ -43,8 +44,8 @@ export default class ProviderModalContainer extends Component { } _setDefaultProvider() { - if (!this.props.currentProvider && this.props.cloudProviders.length) { - const connected = this.props.cloudProviders.find( + if (!this.props.currentProvider && this.props.cloudProviders?.length) { + const connected = this.props.cloudProviders?.find( p => typeof p.getAccessToken === 'function' && p.getAccessToken() ); diff --git a/src/components/modals/save-map-modal.d.ts b/src/components/modals/save-map-modal.d.ts deleted file mode 100644 index f84c784062..0000000000 --- a/src/components/modals/save-map-modal.d.ts +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import {Provider} from '../../cloud-providers'; -import {MapInfo, ExportImage} from 'reducers'; -import { - setMapInfo -} from '../../actions'; -import {ImageModalContainerProps} from './image-modal-container'; -import {ProviderModalContainerProps} from './provider-modal-container'; - -type CharacterLimits = { - title: number; - description: number; -}; - -export type SaveMapModalProps = { - mapInfo: MapInfo; - exportImage: ExportImage; - cloudProviders: Provider[]; - isProviderLoading: boolean; - currentProvider?: string; - providerError?: any; - characterLimits?: CharacterLimits; - - // callbacks - onSetCloudProvider: ProviderModalContainerProps['onSetCloudProvider']; - onUpdateImageSetting: ImageModalContainerProps['onUpdateImageSetting']; - cleanupExportImage: onUpdateImageSetting['cleanupExportImage']; - onSetMapInfo: typeof setMapInfo; -}; - -function LoadDataModalFactory(): React.FunctionComponent; -export default LoadDataModalFactory; diff --git a/src/components/modals/save-map-modal.js b/src/components/modals/save-map-modal.tsx similarity index 82% rename from src/components/modals/save-map-modal.js rename to src/components/modals/save-map-modal.tsx index 14b1bed7cf..2a1bd195b7 100644 --- a/src/components/modals/save-map-modal.js +++ b/src/components/modals/save-map-modal.tsx @@ -21,8 +21,8 @@ import React from 'react'; import styled from 'styled-components'; import CloudTile from './cloud-tile'; -import ImageModalContainer from './image-modal-container'; -import ProviderModalContainer from './provider-modal-container'; +import ImageModalContainer, {ImageModalContainerProps} from './image-modal-container'; +import ProviderModalContainer, {ProviderModalContainerProps} from './provider-modal-container'; import StatusPanel, {UploadAnimation} from './status-panel'; import {MAP_THUMBNAIL_DIMENSION, MAP_INFO_CHARACTER} from 'constants/default-settings'; @@ -37,6 +37,9 @@ import { } from 'components/common/styled-components'; import ImagePreview from 'components/common/image-preview'; import {FormattedMessage} from 'localization'; +import {ExportImage, MapInfo} from 'reducers'; +import {Provider} from 'cloud-providers'; +import {setMapInfo, cleanupExportImage} from 'actions'; /** @typedef {import('./save-map-modal').SaveMapModalProps} SaveMapModalProps */ @@ -73,7 +76,35 @@ const StyledSaveMapModal = styled.div.attrs({ const nop = _ => {}; -export const MapInfoPanel = ({ +type CharacterLimits = { + title?: number; + description?: number; +}; + +type SaveMapModalProps = { + mapInfo: MapInfo; + exportImage: ExportImage; + cloudProviders: Provider[]; + isProviderLoading: boolean; + currentProvider?: string; + providerError?: any; + characterLimits?: CharacterLimits; + + // callbacks + onSetCloudProvider: ProviderModalContainerProps['onSetCloudProvider']; + onUpdateImageSetting: ImageModalContainerProps['onUpdateImageSetting']; + cleanupExportImage: typeof cleanupExportImage; + onSetMapInfo: typeof setMapInfo; +}; + +type MapInfoPanelProps = Pick & { + onChangeInput: ( + type: string, + event: React.ChangeEvent + ) => void; +}; + +export const MapInfoPanel: React.FC = ({ mapInfo = {description: '', title: ''}, characterLimits, onChangeInput @@ -98,7 +129,7 @@ export const MapInfoPanel = ({
characterLimits.description + Boolean(characterLimits?.description) && mapInfo.description.length > Number(characterLimits?.description) } > - {mapInfo.description.length}/{characterLimits.description || MAP_INFO_CHARACTER.description}{' '} - characters + {mapInfo.description.length}/ + {characterLimits?.description || MAP_INFO_CHARACTER.description} characters
@@ -123,7 +153,7 @@ function SaveMapModalFactory() { /** * @type {React.FunctionComponent} */ - const SaveMapModal = ({ + const SaveMapModal: React.FC = ({ mapInfo, exportImage, characterLimits = {}, @@ -136,7 +166,10 @@ function SaveMapModalFactory() { cleanupExportImage, onSetMapInfo }) => { - const onChangeInput = (key, {target: {value}}) => { + const onChangeInput = ( + key: string, + {target: {value}}: React.ChangeEvent + ) => { onSetMapInfo({[key]: value}); }; const provider = currentProvider ? cloudProviders.find(p => p.name === currentProvider) : null; diff --git a/src/components/modals/share-map-modal.js b/src/components/modals/share-map-modal.tsx similarity index 85% rename from src/components/modals/share-map-modal.js rename to src/components/modals/share-map-modal.tsx index 4b977ca3eb..18442b2d39 100644 --- a/src/components/modals/share-map-modal.js +++ b/src/components/modals/share-map-modal.tsx @@ -22,7 +22,7 @@ import React, {useState} from 'react'; import styled, {ThemeProvider} from 'styled-components'; import {CopyToClipboard} from 'react-copy-to-clipboard'; import {themeLT} from 'styles/base'; -import ImageModalContainer from './image-modal-container'; +import ImageModalContainer, {ImageModalContainerProps} from './image-modal-container'; import ProviderModalContainer from './provider-modal-container'; import { @@ -34,6 +34,8 @@ import { import CloudTile from './cloud-tile'; import StatusPanel from './status-panel'; import {FormattedMessage} from 'localization'; +import {Provider} from 'cloud-providers'; +import {cleanupExportImage, SetCloudProviderPayload} from 'actions'; export const StyledInputLabel = styled.label` font-size: 12px; @@ -59,13 +61,18 @@ export const StyleSharingUrl = styled.div.attrs({ } `; -export const SharingUrl = ({url, message = ''}) => { +interface SharingUrlProps { + url: string; + message?: string; +} + +export const SharingUrl: React.FC = ({url, message = ''}) => { const [copied, setCopy] = useState(false); return ( {message}
- + setCopy(true)}> @@ -84,17 +91,30 @@ const StyledInnerDiv = styled.div` min-height: 500px; `; +interface ShareMapUrlModalFactoryProps { + isProviderLoading?: boolean; + isReady?: boolean; + onExport?: (provider: Provider) => void; + cloudProviders?: Provider[]; + currentProvider: string | null; + providerError?: string; + successInfo?: {shareUrl?: string; folderLink?: string}; + onSetCloudProvider?: (provider: SetCloudProviderPayload) => void; + onUpdateImageSetting: ImageModalContainerProps['onUpdateImageSetting']; + cleanupExportImage: typeof cleanupExportImage; +} + export default function ShareMapUrlModalFactory() { - const ShareMapUrlModal = ({ - isProviderLoading, + const ShareMapUrlModal: React.FC = ({ + isProviderLoading = false, isReady, - onExport, - cloudProviders, - currentProvider, - providerError, - successInfo, - onSetCloudProvider, - onUpdateImageSetting, + onExport = nop, + cloudProviders = [], + currentProvider = null, + providerError = null, + successInfo = {}, + onSetCloudProvider = nop, + onUpdateImageSetting = nop, cleanupExportImage }) => { const {shareUrl, folderLink} = successInfo; @@ -193,16 +213,5 @@ export default function ShareMapUrlModalFactory() { ); }; - ShareMapUrlModal.defaultProps = { - isProviderLoading: false, - onExport: nop, - cloudProviders: [], - currentProvider: null, - providerError: null, - successInfo: {}, - onSetCloudProvider: nop, - onUpdateImageSetting: nop - }; - return ShareMapUrlModal; } diff --git a/src/components/modals/status-panel.js b/src/components/modals/status-panel.tsx similarity index 85% rename from src/components/modals/status-panel.js rename to src/components/modals/status-panel.tsx index fa101d4d58..7d500a4f48 100644 --- a/src/components/modals/status-panel.js +++ b/src/components/modals/status-panel.tsx @@ -18,12 +18,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React from 'react'; +import React, {ComponentType} from 'react'; import styled from 'styled-components'; import {MapIcon} from 'components/common/icons'; import {StyledExportSection} from 'components/common/styled-components'; import ErrorDisplay from './error-display'; import {FormattedMessage} from 'localization'; +import {IconProps} from 'cloud-providers/provider'; const StyledUploader = styled.div` display: flex; @@ -64,7 +65,11 @@ const Line = () => ( ); -export const UploadAnimation = props => ( +interface UploadAnimationProps { + icon?: ComponentType | null; +} + +export const UploadAnimation: React.FC = props => ( @@ -74,7 +79,13 @@ export const UploadAnimation = props => ( ); -const StatusPanel = ({error, isLoading, providerIcon}) => ( +interface StatusPanelProps { + error?: string | null; + isLoading?: boolean; + providerIcon?: ComponentType | null; +} + +const StatusPanel: React.FC = ({error, isLoading, providerIcon}) => (
diff --git a/src/components/modals/storage-map-viewer.js b/src/components/modals/storage-map-viewer.tsx similarity index 86% rename from src/components/modals/storage-map-viewer.js rename to src/components/modals/storage-map-viewer.tsx index b0c927882e..4a4f7d2f4f 100644 --- a/src/components/modals/storage-map-viewer.js +++ b/src/components/modals/storage-map-viewer.tsx @@ -19,7 +19,6 @@ // THE SOFTWARE. import React from 'react'; -import PropTypes from 'prop-types'; import styled from 'styled-components'; import moment from 'moment'; import {LeftArrow} from 'components/common/icons'; @@ -27,11 +26,6 @@ import {FormattedMessage} from 'localization'; const imageH = 108; -const propTypes = { - onLoadAsset: PropTypes.func.isRequired, - back: PropTypes.func.isRequired -}; - const StyledAssetGallery = styled.div.attrs({ className: 'storage-asset-gallery' })` @@ -129,9 +123,23 @@ const StyledError = styled.div` margin-bottom: 16px; `; -const getDuration = last => moment.duration(new Date().valueOf() - last).humanize(); +const getDuration = (last: number = 0) => moment.duration(new Date().valueOf() - last).humanize(); + +interface Asset { + imageUrl?: string; + label?: string; + title?: string; + description?: string; + lastUpdated?: number; + id?: string; +} + +interface AssetItemProps { + asset: Asset; + onClick: React.MouseEventHandler; +} -const AssetItem = ({asset, onClick}) => ( +const AssetItem: React.FC = ({asset, onClick}) => (
{asset.imageUrl && } @@ -149,9 +157,14 @@ const AssetItem = ({asset, onClick}) => ( ); -class StorageAssetsViewer extends React.Component { - static propTypes = propTypes; +interface StorageAssetsViewerProps { + assets: Asset[]; + onLoadAsset: (asset: Asset) => void; + back?: React.MouseEventHandler; + error?: {message?: string}; +} +class StorageAssetsViewer extends React.Component { render() { const {assets, onLoadAsset, back, error} = this.props; diff --git a/src/localization/formatted-message.tsx b/src/localization/formatted-message.tsx index 6fc1bee627..bb3ebe83af 100644 --- a/src/localization/formatted-message.tsx +++ b/src/localization/formatted-message.tsx @@ -26,7 +26,7 @@ type EnhancedFormattedMessageProps = { defaultMessage?: string; defaultValue?: string; values?: { - [key: string]: string | number; + [key: string]: string | number | null; }; children?: () => React.ReactElement; }; diff --git a/src/reducers/map-style-updaters.ts b/src/reducers/map-style-updaters.ts index 7511d128c9..154283c941 100644 --- a/src/reducers/map-style-updaters.ts +++ b/src/reducers/map-style-updaters.ts @@ -77,7 +77,7 @@ export type InputStyle = { error: boolean; isValid: boolean; label: string | null; - style: Object | null; + style: any | null; url: string | null; icon: string | null; custom: boolean; diff --git a/yarn.lock b/yarn.lock index 51f376ad6c..aa20fd5203 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3372,9 +3372,9 @@ integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw== "@types/mapbox-gl@*": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@types/mapbox-gl/-/mapbox-gl-2.6.3.tgz#09e4992bb540fe5e024eebc5fbc315317cb13ffe" - integrity sha512-oF5eQmczkoPQfxfRSwpF9GcrWi3YleptJ67uiCQKps+7aKxwIbww0EHHqIrxvOg49l07+AZBtJU2FPKZm1jKAg== + version "2.7.0" + resolved "https://registry.yarnpkg.com/@types/mapbox-gl/-/mapbox-gl-2.7.0.tgz#1b33e9116fa185168c772448a4ce531ddb086bca" + integrity sha512-kNT3iHMZILCb5Kg07TaUnZglCucO9KbVy1/0uPkFs3G9fWyFz22PGlHJtd25+A6KZ2Sjl+ByXq5850jyeZWQBA== dependencies: "@types/geojson" "*" @@ -3444,6 +3444,11 @@ version "18.0.0" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.0.tgz#b13f8d098e4b0c45df4f1ed123833143b0c71141" integrity sha512-49897Y0UiCGmxZqpC8Blrf6meL8QUla6eb+BBhn69dTXlmuOlzkfr7HHY/O8J25e1lTUMs+YYxSlVDAaGHCOLg== + +"@types/react-copy-to-clipboard@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz#c29690b472a54edff35916f0d1c6c797ad0fd34b" + integrity sha512-O29AThfxrkUFRsZXjfSWR2yaWo0ppB1yLEnHA+Oh24oNetjBAwTDu1PmolIqdJKzsZiO4J1jn6R6TmO96uBvGg== dependencies: "@types/react" "*"