Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/limited rights #89

Merged
merged 7 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions assets/js/invenio_app_rdm/overridableRegistry/mapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { HiddenField } from "../../ic_data_repo/HiddenField";
import { OptionalRoleCreatibutorsField } from "../../ic_data_repo/OptionalRoleCreatibutors";
import { LimitedLicenseField } from "../../ic_data_repo/LimitedLicenseField";
import { parametrize } from "react-overridable";
import { TextAreaField } from "react-invenio-forms";

Expand All @@ -28,4 +29,5 @@ export const overriddenComponents = {
"InvenioAppRdm.Deposit.PublisherField.container": HiddenField,
"InvenioAppRdm.Deposit.PublicationDateField.container": HiddenField,
"InvenioAppRdm.Deposit.DescriptionsField.container": TextAreaField,
"InvenioAppRdm.Deposit.LicenseField.container": LimitedLicenseField
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// This file is part of Invenio-RDM-Records
// Copyright (C) 2020-2023 CERN.
// Copyright (C) 2020-2022 Northwestern University.
// Copyright (C) 2021 Graz University of Technology.
//
// Invenio-RDM-Records is free software; you can redistribute it and/or modify it
// under the terms of the MIT License; see LICENSE file for more details.

import _find from "lodash/find";
import React, { Component } from "react";
import PropTypes from "prop-types";
import { getIn, FieldArray } from "formik";
import { HTML5Backend } from "react-dnd-html5-backend";
import { DndProvider } from "react-dnd";
import { FieldLabel } from "react-invenio-forms";
import { Button, Form, Icon, List } from "semantic-ui-react";

import { LimitedLicenseModal } from "./LimitedLicenseModal";
import { LimitedLicenseFieldItem } from "./LimitedLicenseFieldItem";
import { i18next } from "@translations/invenio_rdm_records/i18next";

/**
* The user-facing license.
*
*/
class VisibleLicense {
/**
* Constructor.
*
* @param {array} uiRights
* @param {object} right
* @param {int} index
*/
constructor(uiRights, right, index) {
this.index = index;
this.type = right.id ? "standard" : "custom";
this.key = right.id || right.title;
this.initial = this.type === "custom" ? right : null;

let uiRight =
_find(
uiRights,
right.id ? (o) => o.id === right.id : (o) => o.title === right.title
) || {};

this.description = uiRight.description_l10n || right.description || "";
this.title = uiRight.title_l10n || right.title || "";
this.link =
(uiRight.props && uiRight.props.url) ||
uiRight.link ||
(right.props && right.props.url) ||
right.link ||
"";
}
}

class LicenseFieldForm extends Component {
render() {
const {
label,
labelIcon,
fieldPath,
uiFieldPath,
form: { values },
move: formikArrayMove,
push: formikArrayPush,
remove: formikArrayRemove,
replace: formikArrayReplace,
required,
searchConfig,
serializeLicenses,
} = this.props;

// Limiting the search results
// see https://github.com/inveniosoftware/react-searchkit
searchConfig.initialQueryState.filters = [['tags', 'data']];
searchConfig.initialQueryState.size = 2;
searchConfig.initialQueryState.queryString = 'id: cc-by-4.0 OR cc0-1.0';

const uiRights = getIn(values, uiFieldPath, []);

return (
<DndProvider backend={HTML5Backend}>
<Form.Field required={required}>
<FieldLabel htmlFor={fieldPath} icon={labelIcon} label={label} />
<List>
{getIn(values, fieldPath, []).map((value, index) => {
const license = new VisibleLicense(uiRights, value, index);
return (
<LimitedLicenseFieldItem
key={license.key}
license={license}
moveLicense={formikArrayMove}
replaceLicense={formikArrayReplace}
removeLicense={formikArrayRemove}
searchConfig={searchConfig}
serializeLicenses={serializeLicenses}
/>
);
})}
</List>
{values.metadata.rights.length === 0 && (<LimitedLicenseModal
searchConfig={searchConfig}
trigger={
<Button type="button" key="standard" icon labelPosition="left">
<Icon name="add" />
{i18next.t("Add license")}
</Button>
}
onLicenseChange={(selectedLicense) => {
formikArrayPush(selectedLicense);
}}
action="add"
serializeLicenses={serializeLicenses}
/>)
}
</Form.Field>
</DndProvider>
);
}
}

LicenseFieldForm.propTypes = {
label: PropTypes.node.isRequired,
labelIcon: PropTypes.node,
fieldPath: PropTypes.string.isRequired,
uiFieldPath: PropTypes.string,
form: PropTypes.object.isRequired,
move: PropTypes.func.isRequired,
push: PropTypes.func.isRequired,
remove: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired,
required: PropTypes.bool.isRequired,
searchConfig: PropTypes.object.isRequired,
serializeLicenses: PropTypes.func,
};

LicenseFieldForm.defaultProps = {
labelIcon: undefined,
uiFieldPath: undefined,
serializeLicenses: undefined,
};

export class LimitedLicenseField extends Component {
render() {
const { fieldPath } = this.props;
return (
<FieldArray
name={fieldPath}
component={(formikProps) => (
<LicenseFieldForm {...formikProps} {...this.props} />
)}
/>
);
}
}

LimitedLicenseField.propTypes = {
fieldPath: PropTypes.string.isRequired,
label: PropTypes.string,
labelIcon: PropTypes.string,
searchConfig: PropTypes.object.isRequired,
required: PropTypes.bool,
serializeLicenses: PropTypes.func,
uiFieldPath: PropTypes.string,
};

LimitedLicenseField.defaultProps = {
label: i18next.t("Licenses"),
uiFieldPath: "ui.rights",
labelIcon: "drivers license",
required: false,
serializeLicenses: undefined,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// This file is part of Invenio-RDM-Records
// Copyright (C) 2020-2023 CERN.
// Copyright (C) 2020-2022 Northwestern University.
// Copyright (C) 2021 Graz University of Technology.
//
// Invenio-RDM-Records is free software; you can redistribute it and/or modify it
// under the terms of the MIT License; see LICENSE file for more details.

import React from "react";
import { useDrag, useDrop } from "react-dnd";
import { Button, List, Ref } from "semantic-ui-react";
import _truncate from "lodash/truncate";
import { LimitedLicenseModal } from "./LimitedLicenseModal";
import { i18next } from "@translations/invenio_rdm_records/i18next";
import PropTypes from "prop-types";

export const LimitedLicenseFieldItem = ({
license,
moveLicense,
replaceLicense,
removeLicense,
searchConfig,
serializeLicenses,
}) => {
const dropRef = React.useRef(null);

const [, drag, preview] = useDrag({
item: { index: license.index, type: "license" },
});
const [{ hidden }, drop] = useDrop({
accept: "license",
hover(item, monitor) {
if (!dropRef.current) {
return;
}
const dragIndex = item.index;
const hoverIndex = license.index;

// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return;
}

if (monitor.isOver({ shallow: true })) {
moveLicense(dragIndex, hoverIndex);
item.index = hoverIndex;
}
},
collect: (monitor) => ({
hidden: monitor.isOver({ shallow: true }),
}),
});

// Initialize the ref explicitely
drop(dropRef);
return (
<Ref innerRef={dropRef} key={license.key}>
<List.Item
key={license.key}
className={hidden ? "deposit-drag-listitem hidden" : "deposit-drag-listitem"}
>
<List.Content floated="right">
<LimitedLicenseModal
searchConfig={searchConfig}
onLicenseChange={(selectedLicense) => {
replaceLicense(license.index, selectedLicense);
}}
initialLicense={license.initial}
action="edit"
trigger={
<Button size="mini" primary type="button">
{i18next.t("Change")}
</Button>
}
serializeLicenses={serializeLicenses}
/>
<Button
size="mini"
type="button"
onClick={() => {
removeLicense(license.index);
}}
>
{i18next.t("Remove")}
</Button>
</List.Content>
<Ref innerRef={drag}>
<List.Icon name="bars" className="drag-anchor" />
</Ref>
<Ref innerRef={preview}>
<List.Content>
<List.Header>{license.title}</List.Header>
{license.description && (
<List.Description>
{_truncate(license.description, { length: 300 })}
</List.Description>
)}
{license.link && (
<span>
<a href={license.link} target="_blank" rel="noopener noreferrer">
{license.description && <span>&nbsp;</span>}
{i18next.t("Read more")}
</a>
</span>
)}
</List.Content>
</Ref>
</List.Item>
</Ref>
);
};

LimitedLicenseFieldItem.propTypes = {
license: PropTypes.object.isRequired,
moveLicense: PropTypes.func.isRequired,
replaceLicense: PropTypes.func.isRequired,
removeLicense: PropTypes.func.isRequired,
searchConfig: PropTypes.object.isRequired,
serializeLicenses: PropTypes.func,
};

LimitedLicenseFieldItem.defaultProps = {
serializeLicenses: undefined,
};
Loading
Loading