diff --git a/jsapp/js/actions.d.ts b/jsapp/js/actions.d.ts index 5ac7653edc..5904d59a14 100644 --- a/jsapp/js/actions.d.ts +++ b/jsapp/js/actions.d.ts @@ -138,6 +138,28 @@ interface DuplicateSubmissionCompletedDefinition extends Function { listen: (callback: (assetUid: string, submissionUid: string, duplicatedSubmission: SubmissionResponse) => void) => Function; } +interface GetUserDefinition extends Function { + (username: string): void; + completed: GetUserCompletedDefinition; + failed: GenericFailedDefinition; +} + +interface GetUserCompletedDefinition extends Function { + (response: AccountResponse): void; + listen: (callback: (response: AccountResponse) => void) => Function; +} + +interface SetAssetPublicDefinition extends Function { + (asset: AssetResponse, shouldSetAnonPerms: boolean): void; + completed: SetAssetPublicCompletedDefinition; + failed: GenericFailedDefinition; +} + +interface SetAssetPublicCompletedDefinition extends Function { + (assetUid: string, shouldSetAnonPerms: boolean): void; + listen: (callback: (assetUid: string, shouldSetAnonPerms: boolean) => void) => Function; +} + // NOTE: as you use more actions in your ts files, please extend this namespace, // for now we are defining only the ones we need. export namespace actions { @@ -171,7 +193,9 @@ export namespace actions { getAssetFiles: GenericDefinition; }; const hooks: object; - const misc: object; + const misc: { + getUser: GetUserDefinition; + }; const reports: object; const table: { updateSettings: TableUpdateSettingsDefinition; @@ -184,6 +208,7 @@ export namespace actions { assignAssetPermission: GenericDefinition; bulkSetAssetPermissions: GenericDefinition; getAssetPermissions: GenericDefinition; + setAssetPublic: SetAssetPublicDefinition; }; const help: { getInAppMessages: GenericDefinition; diff --git a/jsapp/js/components/assetsTable/assetsTable.tsx b/jsapp/js/components/assetsTable/assetsTable.tsx index 9cb8da8213..1242cd564a 100644 --- a/jsapp/js/components/assetsTable/assetsTable.tsx +++ b/jsapp/js/components/assetsTable/assetsTable.tsx @@ -47,17 +47,17 @@ interface AssetsTableProps { /** Displays a spinner */ isLoading?: boolean; /** To display contextual empty message when zero assets. */ - emptyMessage?: string; + emptyMessage?: React.ReactNode; /** List of assets to be displayed. */ assets: AssetResponse[]; /** Number of assets on all pages. */ - totalAssets: number; + totalAssets: number | null; /** List of available filters values. */ metadata?: MetadataResponse; // this type ?? /** Seleceted order column id, one of ASSETS_TABLE_COLUMNS. */ orderColumnId: string; - /** Seleceted order column value. */ - orderValue: string; + /** Seleceted order column value. Defaults to "ascending" */ + orderValue: OrderDirection | null; /** Called when user selects a column for odering. */ onOrderChange: OrderChangeCallback; /** Seleceted filter column, one of ASSETS_TABLE_COLUMNS. */ @@ -84,7 +84,13 @@ interface AssetsTableState { } /** - * Displays a table of assets. + * DEPRECATED-ish (see below) + * Displays a table of assets. This old-ish component is handling three routes: + * - My Library + * - Public Collections + * - Single Collection + * The new and shiny component that handles Projects List is `ProjectsTable`, + * and in the future it should become (if possible) the only one to be used. */ export default class AssetsTable extends React.Component< AssetsTableProps, diff --git a/jsapp/js/components/library/assetContentSummary.es6 b/jsapp/js/components/library/assetContentSummary.tsx similarity index 71% rename from jsapp/js/components/library/assetContentSummary.es6 rename to jsapp/js/components/library/assetContentSummary.tsx index bff30aa0e2..a13a43da37 100644 --- a/jsapp/js/components/library/assetContentSummary.es6 +++ b/jsapp/js/components/library/assetContentSummary.tsx @@ -1,28 +1,38 @@ import React from 'react'; -import autoBind from 'react-autobind'; import bem from 'js/bem'; import { getFlatQuestionsList, renderQuestionTypeIcon, } from 'js/assetUtils'; -import {QUESTION_TYPES} from 'js/constants'; +import {ANY_ROW_TYPE_NAMES} from 'js/constants'; +import type {FlatQuestion} from 'js/assetUtils'; +import type {AssetResponse} from 'js/dataInterface'; + +interface AssetContentSummaryProps { + asset: AssetResponse; +} + +interface AssetContentSummaryState { + isExpanded: boolean; +} const DISPLAY_LIMIT = 8; /** - * AKA "Quick Look" component + * AKA "Quick Look" component, it displays a list of questions from given asset. */ - -class AssetContentSummary extends React.Component { - constructor(props){ +export default class AssetContentSummary extends React.Component< + AssetContentSummaryProps, + AssetContentSummaryState +> { + constructor(props: AssetContentSummaryProps) { super(props); this.state = { isExpanded: false, }; - autoBind(this); } - renderQuestion(question, itemIndex) { + renderQuestion(question: FlatQuestion, itemIndex: number) { const modifiers = ['columns', 'padding-small']; if (itemIndex !== 0) { modifiers.push('bordertop'); @@ -47,9 +57,9 @@ class AssetContentSummary extends React.Component { ); } - filterRealQuestions(questions) { + filterRealQuestions(questions: FlatQuestion[]) { return questions.filter((question) => { - return QUESTION_TYPES[question.type]; + return Object.values(ANY_ROW_TYPE_NAMES).includes(question.type); }); } @@ -58,7 +68,7 @@ class AssetContentSummary extends React.Component { } render() { - if (!this.props.asset) { + if (!this.props.asset?.content?.survey) { return null; } @@ -82,12 +92,12 @@ class AssetContentSummary extends React.Component { return ( - {items.map(this.renderQuestion)} + {items.map(this.renderQuestion.bind(this))} {isExpandable && - @@ -97,5 +107,3 @@ class AssetContentSummary extends React.Component { ); } } - -export default AssetContentSummary; diff --git a/jsapp/js/components/library/assetInfoBox.es6 b/jsapp/js/components/library/assetInfoBox.tsx similarity index 81% rename from jsapp/js/components/library/assetInfoBox.es6 rename to jsapp/js/components/library/assetInfoBox.tsx index 2069729cf6..0c6a2ffa1f 100644 --- a/jsapp/js/components/library/assetInfoBox.es6 +++ b/jsapp/js/components/library/assetInfoBox.tsx @@ -1,7 +1,4 @@ import React from 'react'; -import reactMixin from 'react-mixin'; -import autoBind from 'react-autobind'; -import Reflux from 'reflux'; import bem from 'js/bem'; import {actions} from 'js/actions'; import sessionStore from 'js/stores/session'; @@ -10,37 +7,57 @@ import {ASSET_TYPES} from 'js/constants'; import { notify, formatTime, -} from 'utils'; +} from 'js/utils'; import './assetInfoBox.scss'; +import type {AssetResponse, AccountResponse} from 'js/dataInterface'; + +interface AssetInfoBoxProps { + asset: AssetResponse; +} + +interface AssetInfoBoxState { + areDetailsVisible: boolean; + ownerData: AccountResponse | {username: string; date_joined: string} | null; +} /** - * @prop asset + * Displays some meta information about given asset. */ -class AssetInfoBox extends React.Component { - constructor(props){ +export default class AssetInfoBox extends React.Component< + AssetInfoBoxProps, + AssetInfoBoxState +> { + private unlisteners: Function[] = []; + + constructor(props: AssetInfoBoxProps){ super(props); this.state = { areDetailsVisible: false, ownerData: null, }; - autoBind(this); } componentDidMount() { if (!assetUtils.isSelfOwned(this.props.asset)) { - this.listenTo(actions.misc.getUser.completed, this.onGetUserCompleted); - this.listenTo(actions.misc.getUser.failed, this.onGetUserFailed); + this.unlisteners.push( + actions.misc.getUser.completed.listen(this.onGetUserCompleted.bind(this)), + actions.misc.getUser.failed.listen(this.onGetUserFailed.bind(this)) + ) actions.misc.getUser(this.props.asset.owner); } else { this.setState({ownerData: sessionStore.currentAccount}); } } + componentWillUnmount() { + this.unlisteners.forEach((clb) => {clb();}); + } + toggleDetails() { this.setState({areDetailsVisible: !this.state.areDetailsVisible}); } - onGetUserCompleted(userData) { + onGetUserCompleted(userData: AccountResponse) { this.setState({ownerData: userData}); } @@ -136,7 +153,7 @@ class AssetInfoBox extends React.Component { - + {this.state.areDetailsVisible ? : } {this.state.areDetailsVisible ? t('Hide full details') : t('Show full details')} @@ -145,7 +162,3 @@ class AssetInfoBox extends React.Component { ); } } - -reactMixin(AssetInfoBox.prototype, Reflux.ListenerMixin); - -export default AssetInfoBox; diff --git a/jsapp/js/components/library/assetPublicButton.es6 b/jsapp/js/components/library/assetPublicButton.tsx similarity index 70% rename from jsapp/js/components/library/assetPublicButton.es6 rename to jsapp/js/components/library/assetPublicButton.tsx index 1482177e26..6268af9672 100644 --- a/jsapp/js/components/library/assetPublicButton.es6 +++ b/jsapp/js/components/library/assetPublicButton.tsx @@ -1,36 +1,53 @@ import React from 'react'; -import autoBind from 'react-autobind'; import bem from 'js/bem'; import {actions} from 'js/actions'; import assetUtils from 'js/assetUtils'; import {ASSET_TYPES} from 'js/constants'; -import { - notify -} from 'js/utils'; +import {notify} from 'js/utils'; +import type {AssetResponse} from 'js/dataInterface'; + +interface AssetPublicButtonProps { + asset: AssetResponse; +} + +interface AssetPublicButtonState { + isPublicPending: boolean; + isAwaitingFreshPermissions: boolean; +} /** - * @prop asset + * Button for making asset (works only for `collection` type) public or non-public. */ -class AssetPublicButton extends React.Component { - constructor(props){ +export default class AssetPublicButton extends React.Component< + AssetPublicButtonProps, + AssetPublicButtonState +> { + private unlisteners: Function[] = []; + + constructor(props: AssetPublicButtonProps) { super(props); this.state = { isPublicPending: false, isAwaitingFreshPermissions: false }; - autoBind(this); } componentDidMount() { - actions.permissions.setAssetPublic.completed.listen(this.onSetAssetPublicCompleted); - actions.permissions.setAssetPublic.failed.listen(this.onSetAssetPublicFailed); + this.unlisteners.push( + actions.permissions.setAssetPublic.completed.listen(this.onSetAssetPublicCompleted.bind(this)), + actions.permissions.setAssetPublic.failed.listen(this.onSetAssetPublicFailed.bind(this)) + ); + } + + componentWillUnmount() { + this.unlisteners.forEach((clb) => {clb();}); } componentWillReceiveProps() { this.setState({isAwaitingFreshPermissions: false}); } - onSetAssetPublicCompleted(assetUid) { + onSetAssetPublicCompleted(assetUid: string) { if (this.props.asset.uid === assetUid) { this.setState({ isPublicPending: false, @@ -39,7 +56,7 @@ class AssetPublicButton extends React.Component { } } - onSetAssetPublicFailed(assetUid) { + onSetAssetPublicFailed(assetUid: string) { if (this.props.asset.uid === assetUid) { this.setState({isPublicPending: false}); notify(t('Failed to change asset public status.'), 'error'); @@ -86,7 +103,7 @@ class AssetPublicButton extends React.Component { {!isPublic && @@ -96,7 +113,7 @@ class AssetPublicButton extends React.Component { {isPublic && @@ -107,5 +124,3 @@ class AssetPublicButton extends React.Component { ); } } - -export default AssetPublicButton; diff --git a/jsapp/js/components/library/assetRoute.js b/jsapp/js/components/library/assetRoute.tsx similarity index 65% rename from jsapp/js/components/library/assetRoute.js rename to jsapp/js/components/library/assetRoute.tsx index 5ef78b8d8f..6b19aa321f 100644 --- a/jsapp/js/components/library/assetRoute.js +++ b/jsapp/js/components/library/assetRoute.tsx @@ -1,11 +1,7 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import autoBind from 'react-autobind'; -import reactMixin from 'react-mixin'; -import Reflux from 'reflux'; +import clonedeep from 'lodash.clonedeep'; import DocumentTitle from 'react-document-title'; import bem from 'js/bem'; -import mixins from 'js/mixins'; import {actions} from 'js/actions'; import assetUtils from 'js/assetUtils'; import {ASSET_TYPES, ACCESS_TYPES} from 'js/constants'; @@ -16,24 +12,42 @@ import AssetBreadcrumbs from './assetBreadcrumbs'; import AssetContentSummary from './assetContentSummary'; import CollectionAssetsTable from 'js/components/library/collectionAssetsTable'; import LoadingSpinner from 'js/components/common/loadingSpinner'; +import {getRouteAssetUid} from 'js/router/routerUtils'; +import type {AssetResponse} from 'js/dataInterface'; -class AssetRoute extends React.Component { - constructor(props) { +interface AssetRouteProps { + params: { + uid: string; + } +} + +interface AssetRouteState { + asset: AssetResponse | undefined; +} + +export default class AssetRoute extends React.Component< + AssetRouteProps, + AssetRouteState +> { + private unlisteners: Function[] = []; + + constructor(props: AssetRouteProps) { super(props); - this.state = {asset: false}; - this.unlisteners = []; - autoBind(this); + + this.state = { + asset: undefined, + }; } componentDidMount() { this.unlisteners.push( - actions.library.moveToCollection.completed.listen(this.onMoveToCollectionCompleted), - actions.library.subscribeToCollection.completed.listen(this.onSubscribeToCollectionCompleted), - actions.library.unsubscribeFromCollection.completed.listen(this.onUnsubscribeFromCollectionCompleted), - actions.resources.loadAsset.completed.listen(this.onAssetChanged), - actions.resources.updateAsset.completed.listen(this.onAssetChanged), - actions.resources.cloneAsset.completed.listen(this.onAssetChanged), - actions.resources.createResource.completed.listen(this.onAssetChanged), + actions.library.moveToCollection.completed.listen(this.onAssetChanged.bind(this)), + actions.library.subscribeToCollection.completed.listen(this.onSubscribeToCollectionCompleted.bind(this)), + actions.library.unsubscribeFromCollection.completed.listen(this.onUnsubscribeFromCollectionCompleted.bind(this)), + actions.resources.loadAsset.completed.listen(this.onAssetChanged.bind(this)), + actions.resources.updateAsset.completed.listen(this.onAssetChanged.bind(this)), + actions.resources.cloneAsset.completed.listen(this.onAssetChanged.bind(this)), + actions.resources.createResource.completed.listen(this.onAssetChanged.bind(this)), ); this.loadCurrentAsset(); } @@ -42,16 +56,16 @@ class AssetRoute extends React.Component { this.unlisteners.forEach((clb) => {clb();}); } - componentWillReceiveProps(nextProps) { + componentWillReceiveProps(nextProps: AssetRouteProps) { // trigger loading when switching assets if (nextProps.params.uid !== this.props.params.uid) { - this.setState({asset: false}); + this.setState({asset: undefined}); this.loadCurrentAsset(); } } loadCurrentAsset() { - const uid = this.currentAssetID(); + const uid = getRouteAssetUid(); if (uid) { actions.resources.loadAsset({id: uid}); } @@ -65,35 +79,40 @@ class AssetRoute extends React.Component { this.onAssetAccessTypeChanged(false); } - onAssetAccessTypeChanged(setSubscribed) { - let newAsset = this.state.asset; - if (setSubscribed) { - newAsset.access_types.push(ACCESS_TYPES.subscribed); - } else { - newAsset.access_types.splice( - newAsset.access_types.indexOf(ACCESS_TYPES.subscribed), - 1 - ); + /** + * This updates the local asset object, avoiding the need to fetch whole thing + * from Back End. + */ + onAssetAccessTypeChanged(setSubscribed: boolean) { + let newAsset = clonedeep(this.state.asset); + if (newAsset) { + if (setSubscribed && newAsset.access_types === null) { + newAsset.access_types = [ACCESS_TYPES.subscribed]; + } else if (setSubscribed && newAsset.access_types !== null) { + newAsset.access_types.push(ACCESS_TYPES.subscribed); + } else if (!setSubscribed && newAsset.access_types !== null) { + newAsset.access_types.splice( + newAsset.access_types.indexOf(ACCESS_TYPES.subscribed), + 1 + ); + // Cleanup if empty array is left + if (newAsset.access_types.length === 0) { + newAsset.access_types = null; + } + } + + this.setState({asset: newAsset}); } - this.setState({asset: newAsset}); } - onMoveToCollectionCompleted(asset) { - if (asset.parent === null) { - this.onAssetRemoved(asset.uid); - } else { - this.onAssetChanged(asset); - } - } - - onAssetChanged(asset) { - if (asset.uid === this.currentAssetID()) { + onAssetChanged(asset: AssetResponse) { + if (asset.uid === getRouteAssetUid()) { this.setState({asset: asset}); } } render() { - if (this.state.asset === false) { + if (!this.state.asset) { return (); } @@ -148,12 +167,3 @@ class AssetRoute extends React.Component { ); } } - -reactMixin(AssetRoute.prototype, mixins.contextRouter); -reactMixin(AssetRoute.prototype, Reflux.ListenerMixin); - -AssetRoute.contextTypes = { - router: PropTypes.object -}; - -export default AssetRoute; diff --git a/jsapp/js/components/library/librarySidebar.es6 b/jsapp/js/components/library/librarySidebar.tsx similarity index 64% rename from jsapp/js/components/library/librarySidebar.es6 rename to jsapp/js/components/library/librarySidebar.tsx index 32ccb43280..d4d63498bb 100644 --- a/jsapp/js/components/library/librarySidebar.es6 +++ b/jsapp/js/components/library/librarySidebar.tsx @@ -1,29 +1,33 @@ import React from 'react'; -import Reflux from 'reflux'; -import reactMixin from 'react-mixin'; -import PropTypes from 'prop-types'; -import autoBind from 'react-autobind'; import sessionStore from 'js/stores/session'; import bem from 'js/bem'; import {MODAL_TYPES} from 'js/constants'; import myLibraryStore from './myLibraryStore'; -import { routerIsActive } from '../../router/legacy'; -import {ROUTES} from '../../router/routerConstants'; +import {routerIsActive} from 'js/router/legacy'; +import {ROUTES} from 'js/router/routerConstants'; import {NavLink} from 'react-router-dom'; import pageState from 'js/pageState.store'; -class LibrarySidebar extends Reflux.Component { - constructor(props){ - super(props); - this.state = { - myLibraryCount: 0, - isLoading: true - }; - autoBind(this); +interface LibrarySidebarState { + myLibraryCount: number | null; + isLoading: boolean; +} + +/** + * Displays "NEW" button (for adding a Library item) and two navigation links + * pointing to "My Library" and "Public Collections". + */ +export default class LibrarySidebar extends React.Component< + {}, + LibrarySidebarState +> { + state = { + myLibraryCount: 0, + isLoading: true, } componentDidMount() { - this.listenTo(myLibraryStore, this.myLibraryStoreChanged); + myLibraryStore.listen(this.myLibraryStoreChanged.bind(this), this); this.setState({ isLoading: false, myLibraryCount: myLibraryStore.getCurrentUserTotalAssets() @@ -37,21 +41,13 @@ class LibrarySidebar extends Reflux.Component { }); } - showLibraryNewModal(evt) { + showLibraryNewModal(evt: React.TouchEvent) { evt.preventDefault(); pageState.showModal({ type: MODAL_TYPES.LIBRARY_NEW_ITEM }); } - isMyLibrarySelected() { - return routerIsActive(ROUTES.MY_LIBRARY); - } - - isPublicCollectionsSelected() { - return routerIsActive(ROUTES.PUBLIC_COLLECTIONS); - } - render() { let sidebarModifier = ''; if (this.state.isLoading) { @@ -59,11 +55,11 @@ class LibrarySidebar extends Reflux.Component { } return ( - + <> {t('new')} @@ -74,7 +70,7 @@ class LibrarySidebar extends Reflux.Component { to='/library/my-library' > {t('My Library')} @@ -87,22 +83,14 @@ class LibrarySidebar extends Reflux.Component { to='/library/public-collections' > {t('Public Collections')} - + ); } } - -LibrarySidebar.contextTypes = { - router: PropTypes.object -}; - -reactMixin(LibrarySidebar.prototype, Reflux.ListenerMixin); - -export default LibrarySidebar; diff --git a/jsapp/js/components/library/myLibraryRoute.js b/jsapp/js/components/library/myLibraryRoute.tsx similarity index 62% rename from jsapp/js/components/library/myLibraryRoute.js rename to jsapp/js/components/library/myLibraryRoute.tsx index ae5edecc8d..49e5709ff7 100644 --- a/jsapp/js/components/library/myLibraryRoute.js +++ b/jsapp/js/components/library/myLibraryRoute.tsx @@ -1,34 +1,36 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import reactMixin from 'react-mixin'; -import autoBind from 'react-autobind'; -import Reflux from 'reflux'; import DocumentTitle from 'react-document-title'; import Dropzone from 'react-dropzone'; -import mixins from 'js/mixins'; import bem from 'js/bem'; -import {validFileTypes} from 'utils'; +import mixins from 'js/mixins'; +import {stores} from 'js/stores'; +import {validFileTypes} from 'js/utils'; import myLibraryStore from './myLibraryStore'; import AssetsTable from 'js/components/assetsTable/assetsTable'; import {MODAL_TYPES} from 'js/constants'; import {ROOT_BREADCRUMBS} from 'js/components/library/libraryConstants'; -import {ASSETS_TABLE_CONTEXTS} from 'js/components/assetsTable/assetsTableConstants'; +import {AssetsTableContextName} from 'js/components/assetsTable/assetsTableConstants'; import pageState from 'js/pageState.store'; +import type {MyLibraryStoreData} from './myLibraryStore'; +import type {OrderDirection} from 'js/projects/projectViews/constants'; +import type {FileWithPreview} from 'react-dropzone'; +import type {DragEvent} from 'react'; -class MyLibraryRoute extends React.Component { - constructor(props) { - super(props); - this.state = this.getFreshState(); - this.unlisteners = []; - autoBind(this); - } +export default class MyLibraryRoute extends React.Component< + {}, + MyLibraryStoreData +> { + private unlisteners: Function[] = []; + + state = this.getFreshState(); - getFreshState() { + getFreshState(): MyLibraryStoreData { return { - isLoading: myLibraryStore.data.isFetchingData, + isFetchingData: myLibraryStore.data.isFetchingData, assets: myLibraryStore.data.assets, metadata: myLibraryStore.data.metadata, - totalAssets: myLibraryStore.data.totalSearchAssets, + totalUserAssets: myLibraryStore.data.totalUserAssets, + totalSearchAssets: myLibraryStore.data.totalSearchAssets, orderColumnId: myLibraryStore.data.orderColumnId, orderValue: myLibraryStore.data.orderValue, filterColumnId: myLibraryStore.data.filterColumnId, @@ -40,7 +42,7 @@ class MyLibraryRoute extends React.Component { componentDidMount() { this.unlisteners.push( - myLibraryStore.listen(this.myLibraryStoreChanged) + myLibraryStore.listen(this.myLibraryStoreChanged.bind(this), this) ); } @@ -52,15 +54,15 @@ class MyLibraryRoute extends React.Component { this.setState(this.getFreshState()); } - onAssetsTableOrderChange(orderColumnId, orderValue) { - myLibraryStore.setOrder(orderColumnId, orderValue); + onAssetsTableOrderChange(columnId: string, value: OrderDirection) { + myLibraryStore.setOrder(columnId, value); } - onAssetsTableFilterChange(filterColumnId, filterValue) { - myLibraryStore.setFilter(filterColumnId, filterValue); + onAssetsTableFilterChange(columnId: string | null, value: string | null) { + myLibraryStore.setFilter(columnId, value); } - onAssetsTableSwitchPage(pageNumber) { + onAssetsTableSwitchPage(pageNumber: number) { myLibraryStore.setCurrentPage(pageNumber); } @@ -68,19 +70,24 @@ class MyLibraryRoute extends React.Component { * If only one file was passed, then open a modal for selecting the type. * Otherwise just start uploading all files. */ - onFileDrop(files, rejectedFiles, evt) { - if (files.length === 1) { + onFileDrop( + acceptedFiles: FileWithPreview[], + rejectedFiles: FileWithPreview[], + evt: DragEvent + ) { + if (acceptedFiles.length === 1) { pageState.switchModal({ type: MODAL_TYPES.LIBRARY_UPLOAD, - file: files[0], + file: acceptedFiles[0], }); } else { - this.dropFiles(files, rejectedFiles, evt); + // TODO comes from mixin + mixins.droppable.dropFiles(acceptedFiles, rejectedFiles, evt); } } render() { - let contextualEmptyMessage = t('Your search returned no results.'); + let contextualEmptyMessage: React.ReactNode = t('Your search returned no results.'); if (myLibraryStore.data.totalUserAssets === 0) { contextualEmptyMessage = ( @@ -96,7 +103,7 @@ class MyLibraryRoute extends React.Component { return ( @@ -134,12 +141,3 @@ class MyLibraryRoute extends React.Component { ); } } - -MyLibraryRoute.contextTypes = { - router: PropTypes.object, -}; - -reactMixin(MyLibraryRoute.prototype, mixins.droppable); -reactMixin(MyLibraryRoute.prototype, Reflux.ListenerMixin); - -export default MyLibraryRoute; diff --git a/jsapp/js/components/library/myLibraryStore.ts b/jsapp/js/components/library/myLibraryStore.ts index 392fa9acd6..52ae6a5ad3 100644 --- a/jsapp/js/components/library/myLibraryStore.ts +++ b/jsapp/js/components/library/myLibraryStore.ts @@ -21,7 +21,7 @@ import type { import type {AssetTypeName} from 'js/constants'; import {reaction} from 'mobx'; -interface MyLibraryStoreData { +export interface MyLibraryStoreData { isFetchingData: boolean; currentPage: number; totalPages: number | null; diff --git a/jsapp/js/components/library/publicCollectionsRoute.js b/jsapp/js/components/library/publicCollectionsRoute.tsx similarity index 62% rename from jsapp/js/components/library/publicCollectionsRoute.js rename to jsapp/js/components/library/publicCollectionsRoute.tsx index 32668c0653..05b1bf872a 100644 --- a/jsapp/js/components/library/publicCollectionsRoute.js +++ b/jsapp/js/components/library/publicCollectionsRoute.tsx @@ -1,29 +1,27 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import reactMixin from 'react-mixin'; -import autoBind from 'react-autobind'; -import Reflux from 'reflux'; import DocumentTitle from 'react-document-title'; import bem from 'js/bem'; import publicCollectionsStore from './publicCollectionsStore'; import AssetsTable from 'js/components/assetsTable/assetsTable'; import {ROOT_BREADCRUMBS} from 'js/components/library/libraryConstants'; -import {ASSETS_TABLE_CONTEXTS} from 'js/components/assetsTable/assetsTableConstants'; +import {AssetsTableContextName} from 'js/components/assetsTable/assetsTableConstants'; +import type {PublicCollectionsStoreData} from './publicCollectionsStore'; +import type {OrderDirection} from 'js/projects/projectViews/constants'; -class PublicCollectionsRoute extends React.Component { - constructor(props) { - super(props); - this.state = this.getFreshState(); - this.unlisteners = []; - autoBind(this); - } +export default class PublicCollectionsRoute extends React.Component< + {}, + PublicCollectionsStoreData +> { + private unlisteners: Function[] = []; + + state = this.getFreshState(); getFreshState() { return { - isLoading: publicCollectionsStore.data.isFetchingData, + isFetchingData: publicCollectionsStore.data.isFetchingData, assets: publicCollectionsStore.data.assets, metadata: publicCollectionsStore.data.metadata, - totalAssets: publicCollectionsStore.data.totalSearchAssets, + totalSearchAssets: publicCollectionsStore.data.totalSearchAssets, orderColumnId: publicCollectionsStore.data.orderColumnId, orderValue: publicCollectionsStore.data.orderValue, filterColumnId: publicCollectionsStore.data.filterColumnId, @@ -35,7 +33,7 @@ class PublicCollectionsRoute extends React.Component { componentDidMount() { this.unlisteners.push( - publicCollectionsStore.listen(this.publicCollectionsStoreChanged) + publicCollectionsStore.listen(this.publicCollectionsStoreChanged.bind(this), this) ); } @@ -47,15 +45,15 @@ class PublicCollectionsRoute extends React.Component { this.setState(this.getFreshState()); } - onAssetsTableOrderChange(orderColumnId, orderValue) { - publicCollectionsStore.setOrder(orderColumnId, orderValue); + onAssetsTableOrderChange(columnId: string, value: OrderDirection) { + publicCollectionsStore.setOrder(columnId, value); } - onAssetsTableFilterChange(filterColumnId, filterValue) { - publicCollectionsStore.setFilter(filterColumnId, filterValue); + onAssetsTableFilterChange(columnId: string | null, value: string | null) { + publicCollectionsStore.setFilter(columnId, value); } - onAssetsTableSwitchPage(pageNumber) { + onAssetsTableSwitchPage(pageNumber: number) { publicCollectionsStore.setCurrentPage(pageNumber); } @@ -68,19 +66,19 @@ class PublicCollectionsRoute extends React.Component { @@ -88,11 +86,3 @@ class PublicCollectionsRoute extends React.Component { ); } } - -PublicCollectionsRoute.contextTypes = { - router: PropTypes.object -}; - -reactMixin(PublicCollectionsRoute.prototype, Reflux.ListenerMixin); - -export default PublicCollectionsRoute; diff --git a/jsapp/js/components/library/publicCollectionsStore.ts b/jsapp/js/components/library/publicCollectionsStore.ts index 51b1ca7c4e..b9b451557e 100644 --- a/jsapp/js/components/library/publicCollectionsStore.ts +++ b/jsapp/js/components/library/publicCollectionsStore.ts @@ -8,6 +8,7 @@ import { ORDER_DIRECTIONS, ASSETS_TABLE_COLUMNS, } from 'js/components/assetsTable/assetsTableConstants'; +import type {OrderDirection} from 'js/projects/projectViews/constants'; import type {AssetsTableColumn} from 'js/components/assetsTable/assetsTableConstants'; import {ASSET_TYPES, ACCESS_TYPES} from 'js/constants'; import {ROUTES} from 'js/router/routerConstants'; @@ -22,17 +23,17 @@ import type { } from 'js/dataInterface'; import {reaction} from 'mobx'; -interface PublicCollectionsStoreData { +export interface PublicCollectionsStoreData { isFetchingData: boolean; currentPage: number; totalPages: number | null; totalSearchAssets: number | null; assets: AssetResponse[]; metadata: MetadataResponse; - orderColumnId?: string; - orderValue?: string | null; - filterColumnId?: string | null; - filterValue?: string | null; + orderColumnId: string; + orderValue: OrderDirection | null | undefined; + filterColumnId: string | null; + filterValue: string | null; } class PublicCollectionsStore extends Reflux.Store { @@ -60,6 +61,10 @@ class PublicCollectionsStore extends Reflux.Store { sectors: [], organizations: [], }, + orderColumnId: this.DEFAULT_ORDER_COLUMN.id, + orderValue: this.DEFAULT_ORDER_COLUMN.defaultValue, + filterColumnId: null, + filterValue: null, }; init() { @@ -339,7 +344,7 @@ class PublicCollectionsStore extends Reflux.Store { this.fetchData(); } - setOrder(orderColumnId: string, orderValue: string) { + setOrder(orderColumnId: string, orderValue: OrderDirection) { if ( this.data.orderColumnId !== orderColumnId || this.data.orderValue !== orderValue