diff --git a/public/static/locales/en/localcontexts.json b/public/static/locales/en/localcontexts.json
index bca3b5533..8ddce94df 100644
--- a/public/static/locales/en/localcontexts.json
+++ b/public/static/locales/en/localcontexts.json
@@ -1,3 +1,5 @@
{
+ "localContextsAttrDefaultValue": "The \"{{projectTitle}}\" project has Labels and/or Notices applied through the Local Contexts Hub. For more information, refer to the project page: {{projectPage}}",
+ "localContextsHubProjectURI": "Local Contexts Hub Project URI",
"projectMoreInfoWithLink": "For more information on the Local Contexts project for this data, please visit the project's page: {{projectTitle}}"
}
diff --git a/src/components/data/listing/TableView.js b/src/components/data/listing/TableView.js
index fbfc80b28..e0b2d18ea 100644
--- a/src/components/data/listing/TableView.js
+++ b/src/components/data/listing/TableView.js
@@ -1,5 +1,5 @@
/**
- * @author aramsey
+ * @author aramsey, psarando
*
* A component intended for showing a data listing in a table format.
*/
@@ -28,6 +28,10 @@ import EmptyTable from "components/table/EmptyTable";
import { formatDate } from "components/utils/DateFormatter";
import LocalContextsLabelDisplay from "components/metadata/LocalContextsLabelDisplay";
+import {
+ LocalContextsAttrs,
+ parseProjectID,
+} from "components/models/metadata/LocalContexts";
import ResourceTypes from "components/models/ResourceTypes";
import InstantLaunchButton from "components/instantlaunches";
@@ -36,6 +40,8 @@ import { defaultInstantLaunch } from "serviceFacades/instantlaunches";
import {
FILESYSTEM_METADATA_QUERY_KEY,
getFilesystemMetadata,
+ getLocalContextsProject,
+ LOCAL_CONTEXTS_QUERY_KEY,
} from "serviceFacades/metadata";
import {
@@ -74,9 +80,10 @@ function ResourceNameCell({
const { avus } = metadata;
const rightsURI = avus
- ?.find((avu) => avu.attr === "LocalContexts")
+ ?.find((avu) => avu.attr === LocalContextsAttrs.RIGHTS_ALIAS)
?.avus?.find(
- (childAVU) => childAVU.attr === "rightsURI"
+ (childAVU) =>
+ childAVU.attr === LocalContextsAttrs.RIGHTS_URI
)?.value;
if (rightsURI) {
@@ -90,6 +97,19 @@ function ResourceNameCell({
), // fail silently.
});
+ const projectID = parseProjectID(localContextsProjectURI);
+
+ const { data: project } = useQuery({
+ queryKey: [LOCAL_CONTEXTS_QUERY_KEY, projectID],
+ queryFn: () => getLocalContextsProject({ projectID }),
+ enabled: URL.canParse(localContextsProjectURI),
+ onError: (error) =>
+ console.log("Error fetching Local Contexts project.", {
+ localContextsProjectURI,
+ error,
+ }), // fail silently.
+ });
+
return (
{resource.label}
- {localContextsProjectURI && (
-
+ {localContextsProjectURI && project && (
+
)}
{instantLaunch && (
diff --git a/src/components/metadata/LocalContextsLabelDisplay.js b/src/components/metadata/LocalContextsLabelDisplay.js
index 5976b30c3..11a9daa4a 100644
--- a/src/components/metadata/LocalContextsLabelDisplay.js
+++ b/src/components/metadata/LocalContextsLabelDisplay.js
@@ -1,6 +1,5 @@
import React from "react";
-import { useQuery } from "react-query";
import {
Card,
CardContent,
@@ -17,11 +16,6 @@ import { Trans, useTranslation } from "i18n";
import DEDialog from "components/utils/DEDialog";
import ExternalLink from "components/utils/ExternalLink";
-import {
- LOCAL_CONTEXTS_QUERY_KEY,
- getLocalContextsProject,
-} from "serviceFacades/metadata";
-
const sizeToSpacing = (size, theme) =>
size === "large"
? theme.spacing(8)
@@ -100,31 +94,7 @@ const LocalContextsLabel = ({ baseId, label, project, size = "medium" }) => {
);
};
-const LocalContextsLabelDisplay = ({ rightsURI, size = "medium" }) => {
- // Remove any trailing slash from the rightsURI
- // and take the final part of the path as the project ID.
- const projectID = rightsURI?.replace(/\/$/, "").split("/").at(-1);
-
- const { data: project } = useQuery({
- queryKey: [
- LOCAL_CONTEXTS_QUERY_KEY,
- projectID && {
- projectID,
- },
- ],
- queryFn: () =>
- getLocalContextsProject({
- projectID,
- }),
- enabled: !!projectID,
- onError: (error) => {
- console.log("Error fetching Local Contexts project.", {
- rightsURI,
- error,
- });
- },
- });
-
+const LocalContextsLabelDisplay = ({ project, size = "medium" }) => {
const labels = [
...(project?.notice || []),
...(project?.bc_labels || []),
diff --git a/src/components/metadata/templates/LocalContextsField.js b/src/components/metadata/templates/LocalContextsField.js
new file mode 100644
index 000000000..a9625c4d4
--- /dev/null
+++ b/src/components/metadata/templates/LocalContextsField.js
@@ -0,0 +1,217 @@
+/**
+ * @author psarando
+ */
+import React from "react";
+
+import { useQuery } from "react-query";
+
+import { useTranslation } from "i18n";
+
+import LocalContextsLabelDisplay from "../LocalContextsLabelDisplay";
+
+import FormTextField from "components/forms/FormTextField";
+import {
+ LocalContextsAttrs,
+ parseProjectID,
+} from "components/models/metadata/LocalContexts";
+
+import {
+ LOCAL_CONTEXTS_QUERY_KEY,
+ getLocalContextsProject,
+} from "serviceFacades/metadata";
+
+import { Skeleton } from "@mui/material";
+
+const findAVU = (avus, attr) => avus?.find((avu) => avu.attr === attr);
+
+const LocalContextsField = ({
+ avu,
+ onUpdate,
+ form: { setFieldValue, ...form },
+ field: { value, onChange, ...field },
+ ...props
+}) => {
+ const { t } = useTranslation("localcontexts");
+
+ const [rightsURIAVU, setRightsURIAVU] = React.useState(
+ () =>
+ findAVU(avu.avus, LocalContextsAttrs.RIGHTS_URI) || {
+ attr: LocalContextsAttrs.RIGHTS_URI,
+ value: "",
+ unit: "",
+ }
+ );
+
+ const [projectHubURI, setProjectHubURI] = React.useState(
+ rightsURIAVU.value
+ );
+
+ const [rightsIDSchemeURIAVU] = React.useState(() => {
+ let rightsIDScheme = findAVU(
+ avu.avus,
+ LocalContextsAttrs.RIGHTS_ID_SCHEME
+ );
+
+ if (
+ rightsIDScheme?.value !== LocalContextsAttrs.RIGHTS_ID_SCHEME_VALUE
+ ) {
+ rightsIDScheme = {
+ attr: LocalContextsAttrs.RIGHTS_ID_SCHEME,
+ value: LocalContextsAttrs.RIGHTS_ID_SCHEME_VALUE,
+ unit: "",
+ };
+ }
+
+ return rightsIDScheme;
+ });
+
+ const [schemeURIAVU] = React.useState(() => {
+ let schemeURI = findAVU(avu.avus, LocalContextsAttrs.SCHEME_URI);
+
+ if (schemeURI?.value !== LocalContextsAttrs.SCHEME_URI_VALUE) {
+ schemeURI = {
+ attr: LocalContextsAttrs.SCHEME_URI,
+ value: LocalContextsAttrs.SCHEME_URI_VALUE,
+ unit: "",
+ };
+ }
+
+ return schemeURI;
+ });
+
+ const projectID = parseProjectID(projectHubURI);
+
+ const { data: project, isFetching } = useQuery({
+ queryKey: [LOCAL_CONTEXTS_QUERY_KEY, projectID],
+ queryFn: () =>
+ getLocalContextsProject({
+ projectID,
+ }),
+ enabled: URL.canParse(projectHubURI),
+ onSuccess: (project) => {
+ let newValue = avu.value || "";
+
+ const notices =
+ project?.notice?.filter((label) => label?.name) || [];
+ const bc_labels =
+ project?.bc_labels?.filter((label) => label?.name) || [];
+ const tk_labels =
+ project?.tk_labels?.filter((label) => label?.name) || [];
+
+ const labels = [...bc_labels, ...tk_labels];
+
+ const newLabels = [...notices, ...labels];
+
+ if (newLabels.length === 1) {
+ newValue = newLabels[0].label_text || newLabels[0].default_text;
+ } else {
+ newValue = t("localContextsAttrDefaultValue", {
+ projectTitle: project?.title,
+ projectPage: project?.project_page,
+ });
+ }
+
+ let newAVUs = avu.avus || [];
+
+ const currentLabels = newAVUs
+ .filter(
+ (childAVU) => childAVU.attr === LocalContextsAttrs.RIGHTS_ID
+ )
+ ?.map((childAVU) => childAVU.value);
+
+ const missingLabels = newLabels.filter(
+ (label) => !currentLabels.includes(label.name)
+ );
+
+ if (missingLabels.length > 0) {
+ newAVUs = newAVUs.filter(
+ (childAVU) =>
+ childAVU.attr !== LocalContextsAttrs.RIGHTS_URI &&
+ childAVU.attr !== LocalContextsAttrs.RIGHTS_ID_SCHEME &&
+ childAVU.attr !== LocalContextsAttrs.SCHEME_URI &&
+ (childAVU.attr !== LocalContextsAttrs.RIGHTS_ID ||
+ newLabels.find(
+ (label) => label.name === childAVU.value
+ ))
+ );
+
+ newAVUs = [
+ ...newAVUs,
+ rightsURIAVU,
+ schemeURIAVU,
+ rightsIDSchemeURIAVU,
+ ...missingLabels.map((label) => ({
+ attr: LocalContextsAttrs.RIGHTS_ID,
+ value: label.name,
+ unit: "",
+ })),
+ ];
+ }
+
+ if (avu.value !== newValue || avu.avus !== newAVUs) {
+ onUpdate({ ...avu, value: newValue, avus: newAVUs });
+ }
+ },
+ onError: (error) => {
+ console.log("Error fetching Local Contexts project.", {
+ projectHubURI,
+ error,
+ });
+ },
+ });
+
+ const updateProjectHubURI = (uri) => {
+ setProjectHubURI(uri);
+
+ let newAVUs = avu.avus || [];
+
+ if (rightsURIAVU.value !== uri) {
+ const newRightsURIAVU = {
+ attr: LocalContextsAttrs.RIGHTS_URI,
+ value: uri,
+ unit: "",
+ };
+ setRightsURIAVU(newRightsURIAVU);
+
+ newAVUs = newAVUs.filter(
+ (childAVU) =>
+ childAVU.attr !== LocalContextsAttrs.RIGHTS_URI &&
+ childAVU.attr !== LocalContextsAttrs.RIGHTS_ID_SCHEME &&
+ childAVU.attr !== LocalContextsAttrs.SCHEME_URI
+ );
+
+ newAVUs = [
+ ...newAVUs,
+ newRightsURIAVU,
+ schemeURIAVU,
+ rightsIDSchemeURIAVU,
+ ];
+
+ onUpdate({ ...avu, avus: newAVUs });
+ }
+ };
+
+ return (
+ <>
+ updateProjectHubURI(event?.target?.value)}
+ {...props}
+ label={t("localContextsHubProjectURI")}
+ required
+ />
+ {isFetching ? (
+
+
+
+ ) : (
+
+ )}
+ >
+ );
+};
+
+export default LocalContextsField;
diff --git a/src/components/metadata/templates/index.js b/src/components/metadata/templates/index.js
index f1dfef807..a568ec684 100644
--- a/src/components/metadata/templates/index.js
+++ b/src/components/metadata/templates/index.js
@@ -43,8 +43,8 @@ import FormCheckboxStringValue from "components/forms/FormCheckboxStringValue";
import AstroThesaurusSearchField from "./AstroThesaurusSearchField";
import OntologyLookupServiceSearchField from "./OntologyLookupServiceSearchField";
+import LocalContextsField from "./LocalContextsField";
-import LocalContextsLabelDisplay from "../LocalContextsLabelDisplay";
import SlideUpTransition from "../SlideUpTransition";
import {
@@ -68,6 +68,7 @@ import {
} from "@mui/icons-material";
import { Skeleton } from "@mui/material";
+import { LocalContextsAttrs } from "components/models/metadata/LocalContexts";
const useStyles = makeStyles(styles);
@@ -272,6 +273,17 @@ const MetadataTemplateAttributeForm = (props) => {
return null;
}
+ const isLocalContextsAttr =
+ attribute.name ===
+ LocalContextsAttrs.RIGHTS_ALIAS;
+ if (isLocalContextsAttr) {
+ FieldComponent = LocalContextsField;
+ fieldProps.avu = avu;
+ fieldProps.onUpdate = (avu) => {
+ arrayHelpers.replace(index, avu);
+ };
+ }
+
const avuFieldName = `${field}.avus[${index}]`;
const avuError = getFormError(
avuFieldName,
@@ -318,17 +330,6 @@ const MetadataTemplateAttributeForm = (props) => {
const avuField = (
- {attribute.name === "LocalContexts" && (
-
- childAVU.attr ===
- "rightsURI"
- )?.value
- }
- />
- )}
{
const avuArrayErrors = [];
avus.forEach((avu, avuIndex) => {
const avuErrors = {};
- const value = avu.value;
const attrTemplate = attributeMap[avu.attr];
if (!attrTemplate) {
return;
}
- if (attrTemplate.required && value === "") {
+ let attrType = attrTemplate.type;
+ let value = avu.value;
+
+ const isLocalContexts =
+ avu.attr === LocalContextsAttrs.RIGHTS_ALIAS;
+ if (isLocalContexts) {
+ const rightsURIAVU = avu.avus?.find(
+ (childAVU) =>
+ childAVU.attr === LocalContextsAttrs.RIGHTS_URI
+ );
+
+ attrType = AttributeTypes.URL;
+ value = rightsURIAVU?.value;
+ }
+
+ const isRequired = isLocalContexts || attrTemplate.required;
+ if (isRequired && value === "") {
avuErrors.value = t("required");
avuErrors.error = true;
avuArrayErrors[avuIndex] = avuErrors;
} else if (value) {
- switch (attrTemplate.type) {
+ switch (attrType) {
case AttributeTypes.NUMBER:
case AttributeTypes.INTEGER:
const numVal = Number(value);
diff --git a/src/components/models/metadata/LocalContexts.js b/src/components/models/metadata/LocalContexts.js
new file mode 100644
index 000000000..56d8c0fd8
--- /dev/null
+++ b/src/components/models/metadata/LocalContexts.js
@@ -0,0 +1,14 @@
+// Remove any trailing slash from the rightsURI
+// and take the final part of the path as the project ID.
+export const parseProjectID = (projectHubURI) =>
+ projectHubURI?.replace(/\/$/, "").split("/").at(-1);
+
+export const LocalContextsAttrs = {
+ RIGHTS_ALIAS: "LocalContexts",
+ RIGHTS_URI: "rightsURI",
+ RIGHTS_ID: "rightsIdentifier",
+ RIGHTS_ID_SCHEME: "rightsIdentifierScheme",
+ RIGHTS_ID_SCHEME_VALUE: "Local Contexts",
+ SCHEME_URI: "schemeURI",
+ SCHEME_URI_VALUE: "https://localcontexts.org",
+};