Skip to content

Commit

Permalink
User datasets community sharing updates (#1145)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dmfalke authored Jul 30, 2024
1 parent 6b9bba0 commit ff20159
Show file tree
Hide file tree
Showing 16 changed files with 569 additions and 144 deletions.
23 changes: 10 additions & 13 deletions packages/libs/coreui/src/components/containers/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type ModalStyleSpec = {
width: CSSProperties['width'];
height: CSSProperties['height'];
};
position: Pick<CSSProperties, 'top' | 'right' | 'bottom' | 'left'>;
};

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

// Track the height of the modal content.
const { observe: observeModalContent, height: modalContentHeight } =
useDimensions();

const componentStyle: ModalStyleSpec = useMemo(() => {
const defaultStyle: ModalStyleSpec = {
border: {
Expand Down Expand Up @@ -138,6 +135,7 @@ export default function Modal({
width: undefined,
height: undefined,
},
position: {},
};

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

return (
<ResponsiveModal
ref={observeModalContent}
open={visible}
onClose={() => toggleVisible && toggleVisible(false)}
showCloseIcon={false}
Expand Down Expand Up @@ -206,20 +203,19 @@ export default function Modal({
},
modalContainer: {
position: 'absolute',
...(componentStyle.size.width
? {
width: componentStyle.size.width,
height: componentStyle.size.height,
}
: { top: 75, right: 75, bottom: 75, left: 75 }),

width: componentStyle.size.width,
height: componentStyle.size.height,
background: colors.white,
borderRadius: componentStyle.border.radius,
borderColor: componentStyle.border.color,
borderWidth: componentStyle.border.width,
borderStyle: componentStyle.border.style,
overflow: 'hidden',
opacity: visible ? 1 : 0,
top: componentStyle.position.top,
right: componentStyle.position.right,
bottom: componentStyle.position.bottom,
left: componentStyle.position.left,
},
modal: {
width: '100%',
Expand Down Expand Up @@ -299,7 +295,7 @@ export default function Modal({
)}
<div
css={{
height: modalContentHeight - headerHeight,
height: `calc(100% - ${headerHeight}px)`,
overflowX: componentStyle.content.overflow.x,
overflowY: componentStyle.content.overflow.y,
}}
Expand All @@ -313,6 +309,7 @@ export default function Modal({
paddingRight: componentStyle.content.padding.right,
paddingBottom: componentStyle.content.padding.bottom,
paddingLeft: componentStyle.content.padding.left,
maxHeight: `calc(90vh - ${headerHeight}px)`,
}}
>
{children}
Expand Down
4 changes: 4 additions & 0 deletions packages/libs/coreui/src/components/inputs/SingleSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Item } from './checkboxes/CheckboxList';
import { css } from '@emotion/react';
import { uniqueId } from 'lodash';
import { CheckIcon } from '../icons';
import { PartialButtonStyleSpec } from '../buttons';

export interface ItemGroup<T> {
label: ReactNode;
Expand All @@ -23,6 +24,7 @@ export interface SingleSelectProps<T> {
onSelect: (value: T) => void;
buttonDisplayContent: ReactNode;
isDisabled?: boolean;
styleOverrides?: PartialButtonStyleSpec;
}

const checkIconContainer = { height: 16, width: 16 };
Expand All @@ -33,6 +35,7 @@ export default function SingleSelect<T>({
onSelect,
buttonDisplayContent,
isDisabled = false,
styleOverrides,
}: SingleSelectProps<T>) {
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);

Expand Down Expand Up @@ -88,6 +91,7 @@ export default function SingleSelect<T>({
buttonDisplayContent={buttonDisplayContent}
setIsPopoverOpen={setIsPopoverOpen}
isDisabled={isDisabled}
styleOverrides={styleOverrides}
>
<ul
aria-label={'Menu of selectable options'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -811,19 +811,24 @@ export default function SubsetDownloadModal({
className="SubsetDownloadModal"
styleOverrides={{
content: {
padding: {
top: 0,
right: 25,
bottom: 25,
left: 25,
},
size: {
height: '100%',
},
},
size: {
height: '90vh',
width: '90vw',
},
}}
>
<div css={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<div
css={{
display: 'flex',
flexDirection: 'column',
height: '100%',
padding: '0 2em 2em 2em',
}}
>
<div css={{ display: 'flex', flexDirection: 'column' }}>
<div
css={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import {
UserDatasetFileListing,
} from '../Utils/types';
import { FetchClientError } from '@veupathdb/http-utils';
import {
InferAction,
makeActionCreator,
} from '@veupathdb/wdk-client/lib/Utils/ActionCreatorUtils';

export type Action =
| DetailErrorAction
Expand All @@ -43,7 +47,8 @@ export type Action =
| SharingDatasetPendingAction
| SharingSuccessAction
| SharingModalOpenAction
| SharingErrorAction;
| SharingErrorAction
| CommunityAction;

//==============================================================================

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

//==============================================================================

// Community sharing actions. Note, these are using the `makeActionCreator` utility
// which reduces boilerplate dramatically.

export const updateCommunityModalVisibility = makeActionCreator(
'user-datasets/update-community-modal-visibility',
(isVisible: boolean) => ({ isVisible })
);

export const updateDatasetCommunityVisibilityPending = makeActionCreator(
'user-datasets/update-community-visibility-pending'
);

export const updateDatasetCommunityVisibilitySuccess = makeActionCreator(
'user-datasets/update-community-visibility-success'
);

export const updateDatasetCommunityVisibilityError = makeActionCreator(
'user-datastes/update-community-visibility-error',
(errorMessage: string) => ({ errorMessage })
);

type UpdateCommunityVisibilityThunkAction =
| InferAction<typeof updateDatasetCommunityVisibilitySuccess>
| InferAction<typeof updateDatasetCommunityVisibilityError>
| DetailAction
| ListAction;

type CommunityAction =
| InferAction<typeof updateCommunityModalVisibility>
| InferAction<typeof updateDatasetCommunityVisibilityPending>
| InferAction<typeof updateDatasetCommunityVisibilitySuccess>
| InferAction<typeof updateDatasetCommunityVisibilityError>;

export function updateDatasetCommunityVisibility(
datasetIds: string[],
isVisibleToCommunity: boolean,
context: 'datasetDetails' | 'datasetsList'
) {
return [
updateDatasetCommunityVisibilityPending(),
validateVdiCompatibleThunk<UpdateCommunityVisibilityThunkAction>(
async ({ wdkService }) => {
try {
await Promise.all(
datasetIds.map((datasetId) =>
wdkService.updateUserDataset(datasetId, {
visibility: isVisibleToCommunity ? 'public' : 'private',
})
)
);
if (context === 'datasetDetails') {
return [
loadUserDatasetDetailWithoutLoadingIndicator(datasetIds[0]),
updateDatasetCommunityVisibilitySuccess,
];
} else {
return [
loadUserDatasetListWithoutLoadingIndicator(),
updateDatasetCommunityVisibilitySuccess,
];
}
} catch (error) {
return updateDatasetCommunityVisibilityError(String(error));
}
}
),
];
}

//==============================================================================

type ListAction =
| ListLoadingAction
| ListReceivedAction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { bytesToHuman } from '@veupathdb/wdk-client/lib/Utils/Converters';
import NotFound from '@veupathdb/wdk-client/lib/Views/NotFound/NotFound';

import SharingModal from '../Sharing/UserDatasetSharingModal';
import CommunityModal from '../Sharing/UserDatasetCommunityModal';
import UserDatasetStatus from '../UserDatasetStatus';
import { makeClassifier, normalizePercentage } from '../UserDatasetUtils';
import { ThemedGrantAccessButton } from '../ThemedGrantAccessButton';
Expand Down Expand Up @@ -353,8 +354,21 @@ class UserDatasetDetail extends React.Component {
<div className={classify('Actions')}>
{!isOwner ? null : (
<ThemedGrantAccessButton
buttonText={`Grant Access to ${this.props.dataNoun.singular}`}
onPress={this.openSharingModal}
buttonText={`Grant Access to ${this.props.dataNoun.plural}`}
onPress={(grantType) => {
switch (grantType) {
case 'community':
this.props.updateCommunityModalVisibility(true);
break;
case 'individual':
this.openSharingModal();
break;
default:
// noop
break;
}
}}
enablePublicUserDatasets={this.props.enablePublicUserDatasets}
/>
)}
{isOwner ? (
Expand Down Expand Up @@ -652,6 +666,11 @@ class UserDatasetDetail extends React.Component {
shareError,
updateUserDatasetDetail,
enablePublicUserDatasets,
updateDatasetCommunityVisibility,
updateCommunityModalVisibility,
updateDatasetCommunityVisibilityError,
updateDatasetCommunityVisibilityPending,
updateDatasetCommunityVisibilitySuccess,
} = this.props;
const AllDatasetsLink = this.renderAllDatasetsLink;
if (!userDataset)
Expand Down Expand Up @@ -683,6 +702,19 @@ class UserDatasetDetail extends React.Component {
enablePublicUserDatasets={enablePublicUserDatasets}
/>
)}
{this.props.communityModalOpen && enablePublicUserDatasets ? (
<CommunityModal
user={user}
datasets={[userDataset]}
context="datasetDetails"
onClose={() => updateCommunityModalVisibility(false)}
dataNoun={dataNoun}
updateDatasetCommunityVisibility={updateDatasetCommunityVisibility}
updatePending={updateDatasetCommunityVisibilityPending}
updateSuccessful={updateDatasetCommunityVisibilitySuccess}
updateError={updateDatasetCommunityVisibilityError}
/>
) : null}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {

import UserDatasetEmptyState from '../EmptyState';
import SharingModal from '../Sharing/UserDatasetSharingModal';
import CommunityModal from '../Sharing/UserDatasetCommunityModal';
import UserDatasetStatus from '../UserDatasetStatus';
import { normalizePercentage, textCell } from '../UserDatasetUtils';

Expand Down Expand Up @@ -76,6 +77,16 @@ interface Props {
quotaSize: number;
dataNoun: DataNoun;
enablePublicUserDatasets: boolean;
communityModalOpen: boolean;
updateCommunityModalVisibility: (visibility: boolean) => any;
updateDatasetCommunityVisibility: (
datasetIds: string[],
isVisibleToCommunity: boolean,
context: 'datasetDetails' | 'datasetsList'
) => any;
updateDatasetCommunityVisibilityError: string | undefined;
updateDatasetCommunityVisibilityPending: boolean;
updateDatasetCommunityVisibilitySuccess: boolean;
}

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

getTableActions() {
const { isMyDataset } = this;
const { removeUserDataset, dataNoun } = this.props;
const { removeUserDataset, dataNoun, enablePublicUserDatasets } =
this.props;
return [
{
callback: (rows: UserDataset[]) => {
this.openSharingModal();
},
callback: (rows: UserDataset[]) => {},
element: (
<ThemedGrantAccessButton
buttonText={`Grant Access to ${dataNoun.plural}`}
onPress={() => null}
onPress={(grantType) => {
switch (grantType) {
case 'community':
this.props.updateCommunityModalVisibility(true);
break;
case 'individual':
this.openSharingModal();
break;
}
}}
enablePublicUserDatasets={enablePublicUserDatasets}
/>
),
selectionRequired: true,
Expand Down Expand Up @@ -598,6 +618,11 @@ class UserDatasetList extends React.Component<Props, State> {
shareError,
updateUserDatasetDetail,
enablePublicUserDatasets,
updateDatasetCommunityVisibility,
updateCommunityModalVisibility,
updateDatasetCommunityVisibilityError,
updateDatasetCommunityVisibilityPending,
updateDatasetCommunityVisibilitySuccess,
} = this.props;
const { uiState, selectedRows, searchTerm } = this.state;

Expand Down Expand Up @@ -654,7 +679,21 @@ class UserDatasetList extends React.Component<Props, State> {
shareSuccessful={shareSuccessful}
shareError={shareError}
updateUserDatasetDetail={updateUserDatasetDetail}
enablePublicUserDatasets={enablePublicUserDatasets}
/>
) : null}
{this.props.communityModalOpen && enablePublicUserDatasets ? (
<CommunityModal
user={user}
datasets={selectedDatasets}
context="datasetsList"
onClose={() => updateCommunityModalVisibility(false)}
dataNoun={dataNoun}
updateDatasetCommunityVisibility={
updateDatasetCommunityVisibility
}
updatePending={updateDatasetCommunityVisibilityPending}
updateSuccessful={updateDatasetCommunityVisibilitySuccess}
updateError={updateDatasetCommunityVisibilityError}
/>
) : null}
<SearchBox
Expand Down
Loading

0 comments on commit ff20159

Please sign in to comment.