@@ -3,13 +3,16 @@ import _ from 'lodash';
3
3
import { DiscreteCopyNumberData , ResourceData } from 'cbioportal-ts-api-client' ;
4
4
import { PaginationControls } from '../../shared/components/paginationControls/PaginationControls' ;
5
5
import { IColumnVisibilityDef } from 'shared/components/columnVisibilityControls/ColumnVisibilityControls' ;
6
- import { toggleColumnVisibility } from 'cbioportal-frontend-commons' ;
6
+ import {
7
+ DefaultTooltip ,
8
+ toggleColumnVisibility ,
9
+ } from 'cbioportal-frontend-commons' ;
7
10
import {
8
11
PatientViewPageStore ,
9
12
buildCohortIdsFromNavCaseIds ,
10
13
} from './clinicalInformation/PatientViewPageStore' ;
11
14
import { inject , observer } from 'mobx-react' ;
12
- import { action , computed , observable , makeObservable } from 'mobx' ;
15
+ import { action , computed , observable , makeObservable , toJS } from 'mobx' ;
13
16
import { default as PatientViewMutationTable } from './mutation/PatientViewMutationTable' ;
14
17
import { MSKTab } from '../../shared/components/MSKTabs/MSKTabs' ;
15
18
import ValidationAlert from 'shared/components/ValidationAlert' ;
@@ -18,7 +21,11 @@ import PatientViewCnaDataStore from './copyNumberAlterations/PatientViewCnaDataS
18
21
19
22
import './patient.scss' ;
20
23
21
- import { getWholeSlideViewerUrl } from '../../shared/api/urls' ;
24
+ import {
25
+ buildCBioPortalPageUrl ,
26
+ getPatientViewUrl ,
27
+ getWholeSlideViewerUrl ,
28
+ } from '../../shared/api/urls' ;
22
29
import { PageLayout } from '../../shared/components/PageLayout/PageLayout' ;
23
30
import Helmet from 'react-helmet' ;
24
31
import { getServerConfig } from '../../config/config' ;
@@ -55,11 +62,13 @@ import { prepareCustomTabConfigurations } from 'shared/lib/customTabs/customTabH
55
62
import setWindowVariable from 'shared/lib/setWindowVariable' ;
56
63
import { getNavCaseIdsCache } from 'shared/lib/handleLongUrls' ;
57
64
import PatientViewPageHeader from 'pages/patientView/PatientViewPageHeader' ;
65
+ import { MAX_URL_LENGTH } from 'pages/studyView/studyPageHeader/ActionButtons' ;
58
66
59
67
export interface IPatientViewPageProps {
60
68
routing : any ;
61
69
appStore : AppStore ;
62
70
cohortIds ?: string [ ] ;
71
+ onCohortIdsUpdate : ( ids : string [ ] ) => void ;
63
72
}
64
73
65
74
export interface IGenePanelModal {
@@ -87,15 +96,14 @@ export default class PatientViewPage extends React.Component<
87
96
IPatientViewPageProps ,
88
97
{ }
89
98
> {
90
- cohortIds : string [ ] | undefined ;
99
+ @ observable cohortIds : string [ ] | undefined ;
91
100
92
101
constructor ( props : IPatientViewPageProps ) {
93
102
super ( props ) ;
94
-
103
+ makeObservable ( this ) ;
95
104
const postData = getBrowserWindow ( ) . clientPostedData ;
96
105
97
106
const urlData = getNavCaseIdsCache ( ) ;
98
-
99
107
if ( postData && postData . navCaseIds ) {
100
108
this . cohortIds = buildCohortIdsFromNavCaseIds ( postData . navCaseIds ) ;
101
109
getBrowserWindow ( ) . clientPostedData = null ;
@@ -104,12 +112,18 @@ export default class PatientViewPage extends React.Component<
104
112
}
105
113
}
106
114
115
+ @action . bound
116
+ updateCohortIds ( newCohortIds : string [ ] ) {
117
+ this . cohortIds = newCohortIds ;
118
+ }
119
+
107
120
render ( ) {
108
121
return (
109
122
< PatientViewPageInner
110
123
key = { `${ this . props . routing . query . caseId } -${ this . props . routing . query . studyId } -${ this . props . routing . query . sampleId } ` }
111
124
{ ...this . props }
112
125
cohortIds = { this . cohortIds }
126
+ onCohortIdsUpdate = { this . updateCohortIds }
113
127
/>
114
128
) ;
115
129
}
@@ -544,6 +558,70 @@ export class PatientViewPageInner extends React.Component<
544
558
return this . patientViewPageStore ;
545
559
}
546
560
561
+ @action . bound
562
+ private handleReturnToStudyView ( ) {
563
+ const patientIdentifiers = this . pageStore . patientIdsInCohort . map ( p => {
564
+ const patientIdParts = p . split ( ':' ) ;
565
+ return {
566
+ patientId : patientIdParts [ 1 ] ,
567
+ studyId : patientIdParts [ 0 ] ,
568
+ } ;
569
+ } ) ;
570
+ const queriedStudies : Set < string > = new Set (
571
+ patientIdentifiers . map ( p => p . studyId )
572
+ ) ;
573
+
574
+ // We need to do this because of url length limits. We post the data to the new window once it is opened.
575
+ const studyPage = window . open (
576
+ buildCBioPortalPageUrl ( `study` , {
577
+ id : Array . from ( queriedStudies ) . join ( ',' ) ,
578
+ } ) ,
579
+ '_blank'
580
+ ) ;
581
+ if ( patientIdentifiers . length > 0 ) {
582
+ ( studyPage as any ) . studyPageFilter = `filterJson=${ JSON . stringify ( {
583
+ patientIdentifiers,
584
+ } ) } `;
585
+ }
586
+ }
587
+
588
+ @action . bound
589
+ private handleDeletePatient ( deleteAtIndex : number ) {
590
+ var newCohortIdList = toJS ( this . pageStore . patientIdsInCohort ) ;
591
+ newCohortIdList . splice ( deleteAtIndex , 1 ) ;
592
+ let currentIndex =
593
+ deleteAtIndex > newCohortIdList . length - 1
594
+ ? deleteAtIndex - 1
595
+ : deleteAtIndex ;
596
+
597
+ let navCaseIds = newCohortIdList . map ( p => {
598
+ const patientIdParts = p . split ( ':' ) ;
599
+ return {
600
+ patientId : patientIdParts [ 1 ] ,
601
+ studyId : patientIdParts [ 0 ] ,
602
+ } ;
603
+ } ) ;
604
+ const url = getPatientViewUrl (
605
+ navCaseIds [ currentIndex ] . studyId ,
606
+ navCaseIds [ currentIndex ] . patientId ,
607
+ navCaseIds
608
+ ) ;
609
+
610
+ // Because of url length limits, we can only maintain the list in the url hash for small sets of ids.
611
+ // TODO: adapt updateURL to allow for hash mutation so that we don't have manipulate window.location.hash directly
612
+ this . props . onCohortIdsUpdate ( newCohortIdList ) ;
613
+ if ( url . length <= MAX_URL_LENGTH ) {
614
+ getBrowserWindow ( ) . location . hash = url . substring (
615
+ url . indexOf ( '#' ) + 1
616
+ ) ;
617
+ }
618
+ this . urlWrapper . updateURL ( {
619
+ studyId : navCaseIds [ currentIndex ] . studyId ,
620
+ caseId : navCaseIds [ currentIndex ] . patientId ,
621
+ sampleId : undefined ,
622
+ } ) ;
623
+ }
624
+
547
625
@computed
548
626
public get cohortNav ( ) {
549
627
if (
@@ -554,58 +632,105 @@ export class PatientViewPageInner extends React.Component<
554
632
this . pageStore . studyId + ':' + this . pageStore . patientId
555
633
) ;
556
634
return (
557
- < PaginationControls
558
- currentPage = { indexInCohort + 1 }
559
- showMoreButton = { false }
560
- showItemsPerPageSelector = { false }
561
- showFirstPage = { true }
562
- showLastPage = { true }
563
- textBetweenButtons = { ` of ${ this . pageStore . patientIdsInCohort . length } patients` }
564
- firstPageDisabled = { indexInCohort === 0 }
565
- previousPageDisabled = { indexInCohort === 0 }
566
- nextPageDisabled = {
567
- indexInCohort ===
568
- this . pageStore . patientIdsInCohort . length - 1
569
- }
570
- lastPageDisabled = {
571
- indexInCohort ===
572
- this . pageStore . patientIdsInCohort . length - 1
573
- }
574
- onFirstPageClick = { ( ) =>
575
- this . handlePatientClick (
576
- this . pageStore . patientIdsInCohort [ 0 ]
577
- )
578
- }
579
- onPreviousPageClick = { ( ) =>
580
- this . handlePatientClick (
581
- this . pageStore . patientIdsInCohort [ indexInCohort - 1 ]
582
- )
583
- }
584
- onNextPageClick = { ( ) =>
585
- this . handlePatientClick (
586
- this . pageStore . patientIdsInCohort [ indexInCohort + 1 ]
587
- )
588
- }
589
- onLastPageClick = { ( ) =>
590
- this . handlePatientClick (
591
- this . pageStore . patientIdsInCohort [
592
- this . pageStore . patientIdsInCohort . length - 1
593
- ]
594
- )
595
- }
596
- onChangeCurrentPage = { newPage => {
597
- if (
598
- newPage > 0 &&
599
- newPage <= this . pageStore . patientIdsInCohort . length
600
- ) {
635
+ < div
636
+ style = { {
637
+ display : 'flex' ,
638
+ justifyContent : 'flex-end' ,
639
+ alignItems : 'center' ,
640
+ } }
641
+ >
642
+ < PaginationControls
643
+ currentPage = { indexInCohort + 1 }
644
+ showMoreButton = { false }
645
+ showItemsPerPageSelector = { false }
646
+ showFirstPage = { true }
647
+ showLastPage = { true }
648
+ textBetweenButtons = {
649
+ < >
650
+ < span > of </ span >
651
+ < DefaultTooltip
652
+ placement = "bottom"
653
+ overlay = "Open all patients in study view"
654
+ >
655
+ < a
656
+ onClick = { this . handleReturnToStudyView }
657
+ target = { '_blank' }
658
+ >
659
+ { `${ this . pageStore . patientIdsInCohort . length } patients` }
660
+ </ a >
661
+ </ DefaultTooltip >
662
+ </ >
663
+ }
664
+ firstPageDisabled = { indexInCohort === 0 }
665
+ previousPageDisabled = { indexInCohort === 0 }
666
+ nextPageDisabled = {
667
+ indexInCohort ===
668
+ this . pageStore . patientIdsInCohort . length - 1
669
+ }
670
+ lastPageDisabled = {
671
+ indexInCohort ===
672
+ this . pageStore . patientIdsInCohort . length - 1
673
+ }
674
+ onFirstPageClick = { ( ) =>
601
675
this . handlePatientClick (
602
- this . pageStore . patientIdsInCohort [ newPage - 1 ]
603
- ) ;
676
+ this . pageStore . patientIdsInCohort [ 0 ]
677
+ )
604
678
}
605
- } }
606
- pageNumberEditable = { true }
607
- className = "cohortNav"
608
- />
679
+ onPreviousPageClick = { ( ) =>
680
+ this . handlePatientClick (
681
+ this . pageStore . patientIdsInCohort [
682
+ indexInCohort - 1
683
+ ]
684
+ )
685
+ }
686
+ onNextPageClick = { ( ) =>
687
+ this . handlePatientClick (
688
+ this . pageStore . patientIdsInCohort [
689
+ indexInCohort + 1
690
+ ]
691
+ )
692
+ }
693
+ onLastPageClick = { ( ) =>
694
+ this . handlePatientClick (
695
+ this . pageStore . patientIdsInCohort [
696
+ this . pageStore . patientIdsInCohort . length - 1
697
+ ]
698
+ )
699
+ }
700
+ onChangeCurrentPage = { newPage => {
701
+ if (
702
+ newPage > 0 &&
703
+ newPage <=
704
+ this . pageStore . patientIdsInCohort . length
705
+ ) {
706
+ this . handlePatientClick (
707
+ this . pageStore . patientIdsInCohort [
708
+ newPage - 1
709
+ ]
710
+ ) ;
711
+ }
712
+ } }
713
+ pageNumberEditable = { true }
714
+ className = "cohortNav"
715
+ />
716
+ < DefaultTooltip
717
+ placement = "bottom"
718
+ overlay = "Exclude the current patient"
719
+ >
720
+ < span
721
+ style = { {
722
+ marginLeft : '5px' ,
723
+ display : 'inline-block' ,
724
+ cursor : 'pointer' ,
725
+ } }
726
+ onClick = { ( ) =>
727
+ this . handleDeletePatient ( indexInCohort )
728
+ }
729
+ >
730
+ < i className = { 'fa fa-minus-circle' } />
731
+ </ span >
732
+ </ DefaultTooltip >
733
+ </ div >
609
734
) ;
610
735
}
611
736
}
0 commit comments