Skip to content

Commit ff20159

Browse files
authored
User datasets community sharing updates (#1145)
* Add styleOverrides to SingleSelect component * Allow partial meta objects, per api * Use dropdown button when community user datasets are enabled, and use redux for modal state * Remove resize observer from modal content and allow more control over sizing and positioning * Tweak modal sizing and placement * Update modal for community sharing * cleanup: remove prop * Fix typo * Convert to function component * Remove unused export modifier
1 parent 6b9bba0 commit ff20159

16 files changed

+569
-144
lines changed

packages/libs/coreui/src/components/containers/Modal.tsx

+10-13
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type ModalStyleSpec = {
4747
width: CSSProperties['width'];
4848
height: CSSProperties['height'];
4949
};
50+
position: Pick<CSSProperties, 'top' | 'right' | 'bottom' | 'left'>;
5051
};
5152

5253
export type ModalProps = {
@@ -102,10 +103,6 @@ export default function Modal({
102103
// Track the height of the title text.
103104
const { observe, height: titleHeight } = useDimensions();
104105

105-
// Track the height of the modal content.
106-
const { observe: observeModalContent, height: modalContentHeight } =
107-
useDimensions();
108-
109106
const componentStyle: ModalStyleSpec = useMemo(() => {
110107
const defaultStyle: ModalStyleSpec = {
111108
border: {
@@ -138,6 +135,7 @@ export default function Modal({
138135
width: undefined,
139136
height: undefined,
140137
},
138+
position: {},
141139
};
142140

143141
// TODO: Handle color problems when level is too dark.
@@ -176,7 +174,6 @@ export default function Modal({
176174

177175
return (
178176
<ResponsiveModal
179-
ref={observeModalContent}
180177
open={visible}
181178
onClose={() => toggleVisible && toggleVisible(false)}
182179
showCloseIcon={false}
@@ -206,20 +203,19 @@ export default function Modal({
206203
},
207204
modalContainer: {
208205
position: 'absolute',
209-
...(componentStyle.size.width
210-
? {
211-
width: componentStyle.size.width,
212-
height: componentStyle.size.height,
213-
}
214-
: { top: 75, right: 75, bottom: 75, left: 75 }),
215-
206+
width: componentStyle.size.width,
207+
height: componentStyle.size.height,
216208
background: colors.white,
217209
borderRadius: componentStyle.border.radius,
218210
borderColor: componentStyle.border.color,
219211
borderWidth: componentStyle.border.width,
220212
borderStyle: componentStyle.border.style,
221213
overflow: 'hidden',
222214
opacity: visible ? 1 : 0,
215+
top: componentStyle.position.top,
216+
right: componentStyle.position.right,
217+
bottom: componentStyle.position.bottom,
218+
left: componentStyle.position.left,
223219
},
224220
modal: {
225221
width: '100%',
@@ -299,7 +295,7 @@ export default function Modal({
299295
)}
300296
<div
301297
css={{
302-
height: modalContentHeight - headerHeight,
298+
height: `calc(100% - ${headerHeight}px)`,
303299
overflowX: componentStyle.content.overflow.x,
304300
overflowY: componentStyle.content.overflow.y,
305301
}}
@@ -313,6 +309,7 @@ export default function Modal({
313309
paddingRight: componentStyle.content.padding.right,
314310
paddingBottom: componentStyle.content.padding.bottom,
315311
paddingLeft: componentStyle.content.padding.left,
312+
maxHeight: `calc(90vh - ${headerHeight}px)`,
316313
}}
317314
>
318315
{children}

packages/libs/coreui/src/components/inputs/SingleSelect.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Item } from './checkboxes/CheckboxList';
44
import { css } from '@emotion/react';
55
import { uniqueId } from 'lodash';
66
import { CheckIcon } from '../icons';
7+
import { PartialButtonStyleSpec } from '../buttons';
78

89
export interface ItemGroup<T> {
910
label: ReactNode;
@@ -23,6 +24,7 @@ export interface SingleSelectProps<T> {
2324
onSelect: (value: T) => void;
2425
buttonDisplayContent: ReactNode;
2526
isDisabled?: boolean;
27+
styleOverrides?: PartialButtonStyleSpec;
2628
}
2729

2830
const checkIconContainer = { height: 16, width: 16 };
@@ -33,6 +35,7 @@ export default function SingleSelect<T>({
3335
onSelect,
3436
buttonDisplayContent,
3537
isDisabled = false,
38+
styleOverrides,
3639
}: SingleSelectProps<T>) {
3740
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
3841

@@ -88,6 +91,7 @@ export default function SingleSelect<T>({
8891
buttonDisplayContent={buttonDisplayContent}
8992
setIsPopoverOpen={setIsPopoverOpen}
9093
isDisabled={isDisabled}
94+
styleOverrides={styleOverrides}
9195
>
9296
<ul
9397
aria-label={'Menu of selectable options'}

packages/libs/eda/src/lib/workspace/Subsetting/SubsetDownloadModal.tsx

+12-7
Original file line numberDiff line numberDiff line change
@@ -811,19 +811,24 @@ export default function SubsetDownloadModal({
811811
className="SubsetDownloadModal"
812812
styleOverrides={{
813813
content: {
814-
padding: {
815-
top: 0,
816-
right: 25,
817-
bottom: 25,
818-
left: 25,
819-
},
820814
size: {
821815
height: '100%',
822816
},
823817
},
818+
size: {
819+
height: '90vh',
820+
width: '90vw',
821+
},
824822
}}
825823
>
826-
<div css={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
824+
<div
825+
css={{
826+
display: 'flex',
827+
flexDirection: 'column',
828+
height: '100%',
829+
padding: '0 2em 2em 2em',
830+
}}
831+
>
827832
<div css={{ display: 'flex', flexDirection: 'column' }}>
828833
<div
829834
css={{

packages/libs/user-datasets/src/lib/Actions/UserDatasetsActions.ts

+77-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import {
2525
UserDatasetFileListing,
2626
} from '../Utils/types';
2727
import { FetchClientError } from '@veupathdb/http-utils';
28+
import {
29+
InferAction,
30+
makeActionCreator,
31+
} from '@veupathdb/wdk-client/lib/Utils/ActionCreatorUtils';
2832

2933
export type Action =
3034
| DetailErrorAction
@@ -43,7 +47,8 @@ export type Action =
4347
| SharingDatasetPendingAction
4448
| SharingSuccessAction
4549
| SharingModalOpenAction
46-
| SharingErrorAction;
50+
| SharingErrorAction
51+
| CommunityAction;
4752

4853
//==============================================================================
4954

@@ -401,6 +406,77 @@ export function projectFilter(filterByProject: boolean): ProjectFilterAction {
401406

402407
//==============================================================================
403408

409+
// Community sharing actions. Note, these are using the `makeActionCreator` utility
410+
// which reduces boilerplate dramatically.
411+
412+
export const updateCommunityModalVisibility = makeActionCreator(
413+
'user-datasets/update-community-modal-visibility',
414+
(isVisible: boolean) => ({ isVisible })
415+
);
416+
417+
export const updateDatasetCommunityVisibilityPending = makeActionCreator(
418+
'user-datasets/update-community-visibility-pending'
419+
);
420+
421+
export const updateDatasetCommunityVisibilitySuccess = makeActionCreator(
422+
'user-datasets/update-community-visibility-success'
423+
);
424+
425+
export const updateDatasetCommunityVisibilityError = makeActionCreator(
426+
'user-datastes/update-community-visibility-error',
427+
(errorMessage: string) => ({ errorMessage })
428+
);
429+
430+
type UpdateCommunityVisibilityThunkAction =
431+
| InferAction<typeof updateDatasetCommunityVisibilitySuccess>
432+
| InferAction<typeof updateDatasetCommunityVisibilityError>
433+
| DetailAction
434+
| ListAction;
435+
436+
type CommunityAction =
437+
| InferAction<typeof updateCommunityModalVisibility>
438+
| InferAction<typeof updateDatasetCommunityVisibilityPending>
439+
| InferAction<typeof updateDatasetCommunityVisibilitySuccess>
440+
| InferAction<typeof updateDatasetCommunityVisibilityError>;
441+
442+
export function updateDatasetCommunityVisibility(
443+
datasetIds: string[],
444+
isVisibleToCommunity: boolean,
445+
context: 'datasetDetails' | 'datasetsList'
446+
) {
447+
return [
448+
updateDatasetCommunityVisibilityPending(),
449+
validateVdiCompatibleThunk<UpdateCommunityVisibilityThunkAction>(
450+
async ({ wdkService }) => {
451+
try {
452+
await Promise.all(
453+
datasetIds.map((datasetId) =>
454+
wdkService.updateUserDataset(datasetId, {
455+
visibility: isVisibleToCommunity ? 'public' : 'private',
456+
})
457+
)
458+
);
459+
if (context === 'datasetDetails') {
460+
return [
461+
loadUserDatasetDetailWithoutLoadingIndicator(datasetIds[0]),
462+
updateDatasetCommunityVisibilitySuccess,
463+
];
464+
} else {
465+
return [
466+
loadUserDatasetListWithoutLoadingIndicator(),
467+
updateDatasetCommunityVisibilitySuccess,
468+
];
469+
}
470+
} catch (error) {
471+
return updateDatasetCommunityVisibilityError(String(error));
472+
}
473+
}
474+
),
475+
];
476+
}
477+
478+
//==============================================================================
479+
404480
type ListAction =
405481
| ListLoadingAction
406482
| ListReceivedAction

packages/libs/user-datasets/src/lib/Components/Detail/UserDatasetDetail.jsx

+34-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { bytesToHuman } from '@veupathdb/wdk-client/lib/Utils/Converters';
1515
import NotFound from '@veupathdb/wdk-client/lib/Views/NotFound/NotFound';
1616

1717
import SharingModal from '../Sharing/UserDatasetSharingModal';
18+
import CommunityModal from '../Sharing/UserDatasetCommunityModal';
1819
import UserDatasetStatus from '../UserDatasetStatus';
1920
import { makeClassifier, normalizePercentage } from '../UserDatasetUtils';
2021
import { ThemedGrantAccessButton } from '../ThemedGrantAccessButton';
@@ -353,8 +354,21 @@ class UserDatasetDetail extends React.Component {
353354
<div className={classify('Actions')}>
354355
{!isOwner ? null : (
355356
<ThemedGrantAccessButton
356-
buttonText={`Grant Access to ${this.props.dataNoun.singular}`}
357-
onPress={this.openSharingModal}
357+
buttonText={`Grant Access to ${this.props.dataNoun.plural}`}
358+
onPress={(grantType) => {
359+
switch (grantType) {
360+
case 'community':
361+
this.props.updateCommunityModalVisibility(true);
362+
break;
363+
case 'individual':
364+
this.openSharingModal();
365+
break;
366+
default:
367+
// noop
368+
break;
369+
}
370+
}}
371+
enablePublicUserDatasets={this.props.enablePublicUserDatasets}
358372
/>
359373
)}
360374
{isOwner ? (
@@ -652,6 +666,11 @@ class UserDatasetDetail extends React.Component {
652666
shareError,
653667
updateUserDatasetDetail,
654668
enablePublicUserDatasets,
669+
updateDatasetCommunityVisibility,
670+
updateCommunityModalVisibility,
671+
updateDatasetCommunityVisibilityError,
672+
updateDatasetCommunityVisibilityPending,
673+
updateDatasetCommunityVisibilitySuccess,
655674
} = this.props;
656675
const AllDatasetsLink = this.renderAllDatasetsLink;
657676
if (!userDataset)
@@ -683,6 +702,19 @@ class UserDatasetDetail extends React.Component {
683702
enablePublicUserDatasets={enablePublicUserDatasets}
684703
/>
685704
)}
705+
{this.props.communityModalOpen && enablePublicUserDatasets ? (
706+
<CommunityModal
707+
user={user}
708+
datasets={[userDataset]}
709+
context="datasetDetails"
710+
onClose={() => updateCommunityModalVisibility(false)}
711+
dataNoun={dataNoun}
712+
updateDatasetCommunityVisibility={updateDatasetCommunityVisibility}
713+
updatePending={updateDatasetCommunityVisibilityPending}
714+
updateSuccessful={updateDatasetCommunityVisibilitySuccess}
715+
updateError={updateDatasetCommunityVisibilityError}
716+
/>
717+
) : null}
686718
</div>
687719
);
688720
}

packages/libs/user-datasets/src/lib/Components/List/UserDatasetList.tsx

+45-6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131

3232
import UserDatasetEmptyState from '../EmptyState';
3333
import SharingModal from '../Sharing/UserDatasetSharingModal';
34+
import CommunityModal from '../Sharing/UserDatasetCommunityModal';
3435
import UserDatasetStatus from '../UserDatasetStatus';
3536
import { normalizePercentage, textCell } from '../UserDatasetUtils';
3637

@@ -76,6 +77,16 @@ interface Props {
7677
quotaSize: number;
7778
dataNoun: DataNoun;
7879
enablePublicUserDatasets: boolean;
80+
communityModalOpen: boolean;
81+
updateCommunityModalVisibility: (visibility: boolean) => any;
82+
updateDatasetCommunityVisibility: (
83+
datasetIds: string[],
84+
isVisibleToCommunity: boolean,
85+
context: 'datasetDetails' | 'datasetsList'
86+
) => any;
87+
updateDatasetCommunityVisibilityError: string | undefined;
88+
updateDatasetCommunityVisibilityPending: boolean;
89+
updateDatasetCommunityVisibilitySuccess: boolean;
7990
}
8091

8192
interface State {
@@ -406,16 +417,25 @@ class UserDatasetList extends React.Component<Props, State> {
406417

407418
getTableActions() {
408419
const { isMyDataset } = this;
409-
const { removeUserDataset, dataNoun } = this.props;
420+
const { removeUserDataset, dataNoun, enablePublicUserDatasets } =
421+
this.props;
410422
return [
411423
{
412-
callback: (rows: UserDataset[]) => {
413-
this.openSharingModal();
414-
},
424+
callback: (rows: UserDataset[]) => {},
415425
element: (
416426
<ThemedGrantAccessButton
417427
buttonText={`Grant Access to ${dataNoun.plural}`}
418-
onPress={() => null}
428+
onPress={(grantType) => {
429+
switch (grantType) {
430+
case 'community':
431+
this.props.updateCommunityModalVisibility(true);
432+
break;
433+
case 'individual':
434+
this.openSharingModal();
435+
break;
436+
}
437+
}}
438+
enablePublicUserDatasets={enablePublicUserDatasets}
419439
/>
420440
),
421441
selectionRequired: true,
@@ -598,6 +618,11 @@ class UserDatasetList extends React.Component<Props, State> {
598618
shareError,
599619
updateUserDatasetDetail,
600620
enablePublicUserDatasets,
621+
updateDatasetCommunityVisibility,
622+
updateCommunityModalVisibility,
623+
updateDatasetCommunityVisibilityError,
624+
updateDatasetCommunityVisibilityPending,
625+
updateDatasetCommunityVisibilitySuccess,
601626
} = this.props;
602627
const { uiState, selectedRows, searchTerm } = this.state;
603628

@@ -654,7 +679,21 @@ class UserDatasetList extends React.Component<Props, State> {
654679
shareSuccessful={shareSuccessful}
655680
shareError={shareError}
656681
updateUserDatasetDetail={updateUserDatasetDetail}
657-
enablePublicUserDatasets={enablePublicUserDatasets}
682+
/>
683+
) : null}
684+
{this.props.communityModalOpen && enablePublicUserDatasets ? (
685+
<CommunityModal
686+
user={user}
687+
datasets={selectedDatasets}
688+
context="datasetsList"
689+
onClose={() => updateCommunityModalVisibility(false)}
690+
dataNoun={dataNoun}
691+
updateDatasetCommunityVisibility={
692+
updateDatasetCommunityVisibility
693+
}
694+
updatePending={updateDatasetCommunityVisibilityPending}
695+
updateSuccessful={updateDatasetCommunityVisibilitySuccess}
696+
updateError={updateDatasetCommunityVisibilityError}
658697
/>
659698
) : null}
660699
<SearchBox

0 commit comments

Comments
 (0)