From c86dec9bf78a8fd20359417dfe0227706b0d3fcd Mon Sep 17 00:00:00 2001 From: learner97 Date: Thu, 1 Feb 2024 16:15:54 -0700 Subject: [PATCH 1/4] fixed issue with source not being a string, allowed title and description to be eeditable --- frontend/components/Viewing/MetadataInfo.js | 29 ++-- frontend/components/Viewing/ViewHeader.js | 171 +++++++++++++------- frontend/styles/view.module.css | 8 + 3 files changed, 133 insertions(+), 75 deletions(-) diff --git a/frontend/components/Viewing/MetadataInfo.js b/frontend/components/Viewing/MetadataInfo.js index 3c875d62..659b982a 100644 --- a/frontend/components/Viewing/MetadataInfo.js +++ b/frontend/components/Viewing/MetadataInfo.js @@ -178,7 +178,8 @@ export default function MetadataInfo({ title, link, label, icon, specific, uri } {sources.map((source, index) => { let processedSource = source; - if (source.match(urlRegex)) { + console.log(typeof (source)); + if (typeof (source) === 'string' && source.match(urlRegex)) { processedSource = getAfterThirdSlash(source); } return ( @@ -198,7 +199,7 @@ export default function MetadataInfo({ title, link, label, icon, specific, uri } ) : ( // Display mode @@ -207,18 +208,18 @@ export default function MetadataInfo({ title, link, label, icon, specific, uri } )} {label === "Source" && source && ( - - {editSourceIndex === index ? null : ( -
- - -
- )} - + + {editSourceIndex === index ? null : ( +
+ + +
+ )} + )} ); diff --git a/frontend/components/Viewing/ViewHeader.js b/frontend/components/Viewing/ViewHeader.js index 8f7e59bd..a6e0dcc4 100644 --- a/frontend/components/Viewing/ViewHeader.js +++ b/frontend/components/Viewing/ViewHeader.js @@ -5,6 +5,7 @@ import styles from '../../styles/view.module.css'; import React, { useRef } from 'react'; import { useSelector } from 'react-redux'; +import { useState } from 'react'; import axios from 'axios'; @@ -16,6 +17,14 @@ import getConfig from "next/config"; const { publicRuntimeConfig } = getConfig(); export default function ViewHeader(properties) { + const [displayedTitle, setDisplayedTitle] = useState(properties.name); // New state for the displayed title + const [isEditingTitle, setIsEditingTitle] = useState(false); + const [editedTitle, setEditedTitle] = useState(properties.name); + + const [displayedDescription, setDisplayedDescription] = useState(properties.description); + const [isEditingDescription, setIsEditingDescription] = useState(false); + const [editedDescription, setEditedDescription] = useState(properties.description); + var displayTitle = properties.type; if (properties.type.includes('#')) { displayTitle = properties.type.split('#')[1]; @@ -24,19 +33,13 @@ export default function ViewHeader(properties) { const descriptionRef = useRef(null); - const makeEditable = (description) => { - const descriptionContainer = descriptionRef.current; - if (descriptionContainer) { - // Replace the description text with an editable text box and a save button - descriptionContainer.innerHTML = ` - - - `; - - // Add click event listener to the save button - const saveButton = descriptionContainer.querySelector('.save-button'); - saveButton.addEventListener('click', () => saveDescription()); - } + + const handleEditClick = () => { + setIsEditingTitle(true); + }; + + const makeEditable = () => { + setIsEditingDescription(true); }; const objectUriParts = getAfterThirdSlash(properties.uri); @@ -47,12 +50,9 @@ export default function ViewHeader(properties) { var isOwner = isUriOwner(objectUri, username); const saveDescription = () => { - const editedText = descriptionRef.current.querySelector('.editable-description').value; - const previousDescription = properties.description; - axios.post(`${objectUri}/edit/description`, { - previous: previousDescription, - object: editedText + previous: properties.description, + object: editedDescription }, { headers: { "Accept": "text/plain; charset=UTF-8", @@ -60,12 +60,10 @@ export default function ViewHeader(properties) { } }) .then(response => { - // Handle response here - // Refresh the page after successful update - window.location.reload(); + setDisplayedDescription(editedDescription); // Update the displayed description + setIsEditingDescription(false); // Exit edit mode }) .catch(error => { - // Handle error here console.error('Error updating description:', error); }); }; @@ -102,15 +100,61 @@ export default function ViewHeader(properties) { }); }; + const handleSaveTitle = () => { + // Axios POST request to save edited title + axios.post(`${objectUri}/edit/title`, { + previous: properties.name, // original title + object: editedTitle // new (edited) title + }, { + headers: { + "Accept": "text/plain; charset=UTF-8", + "X-authorization": token + } + }) + .then(response => { + setIsEditingTitle(false); + setDisplayedTitle(editedTitle); // Update the displayed title + }) + .catch(error => { + console.error('Error saving title:', error); + }); + }; + + const handleCancelTitle = () => { + setEditedTitle(properties.name); // Reset to original title + setIsEditingTitle(false); + }; + return (
-
-

{properties.name}

- -
-

({properties.displayId})

-
- +
+
+ {isEditingTitle ? ( +
+ setEditedTitle(e.target.value)} + /> + + +
+ ) : ( +
+

{displayedTitle}

+ +
+ )} + + +

({properties.displayId})

+
+ +
@@ -124,44 +168,49 @@ export default function ViewHeader(properties) {
-
0 ? "Find all records with terms in common with this description" : ""}> -
{ - if (properties.description.length > 0) { - window.open(`/search/?q=${properties.description}`, '_blank'); - } - }} - > - {properties.description} - {isOwner && ( - <> - { - e.stopPropagation(); - makeEditable(properties.description); - }} - /> - {properties.description.length > 0 && ( +
0 ? "Find all records with terms in common with this description" : ""}> + {isEditingDescription ? ( +
+ setEditedDescription(e.target.value)} + /> + + +
+ ) : ( +
+ {displayedDescription} + {isOwner && ( + <> { e.stopPropagation(); - confirmDeletion(); + makeEditable(properties.description); }} /> - )} - - )} -
+ {properties.description.length > 0 && ( + { + e.stopPropagation(); + confirmDeletion(); + }} + /> + )} + + )} +
+ )}
); diff --git a/frontend/styles/view.module.css b/frontend/styles/view.module.css index 6822f376..ce6c9c1d 100644 --- a/frontend/styles/view.module.css +++ b/frontend/styles/view.module.css @@ -1085,5 +1085,13 @@ margin-right: 0.3rem; } +.titleContainer { + display: flex; + align-items: center; /* Aligns items vertically in the center */ +} + +.editIcon { + margin-left: 5px; /* Adds a bit of space between the title and the icon */ +} From f99581ef5e12f4750e70c8f6b34fa3dfe9cb4bf2 Mon Sep 17 00:00:00 2001 From: learner97 Date: Thu, 1 Feb 2024 16:35:10 -0700 Subject: [PATCH 2/4] fixed a couple of bugs with description --- frontend/components/Viewing/ViewHeader.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/components/Viewing/ViewHeader.js b/frontend/components/Viewing/ViewHeader.js index a6e0dcc4..ba6b4361 100644 --- a/frontend/components/Viewing/ViewHeader.js +++ b/frontend/components/Viewing/ViewHeader.js @@ -90,16 +90,17 @@ export default function ViewHeader(properties) { } }) .then(response => { - // Handle response here - // Refresh the page after successful update - window.location.reload(); + // Successfully deleted the description, now reset the relevant states + setDisplayedDescription(''); // Reset the displayed description + setEditedDescription(''); + setIsEditingDescription(false); // Exit edit mode if it's active }) .catch(error => { - // Handle error here console.error('Error removing description:', error); }); }; + const handleSaveTitle = () => { // Axios POST request to save edited title axios.post(`${objectUri}/edit/title`, { From e5e3bb729bea663643dba7f17ac07a5dc58d2452 Mon Sep 17 00:00:00 2001 From: learner97 Date: Thu, 1 Feb 2024 17:02:28 -0700 Subject: [PATCH 3/4] removed a console log --- frontend/components/Viewing/MetadataInfo.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/components/Viewing/MetadataInfo.js b/frontend/components/Viewing/MetadataInfo.js index 659b982a..95f49e27 100644 --- a/frontend/components/Viewing/MetadataInfo.js +++ b/frontend/components/Viewing/MetadataInfo.js @@ -178,7 +178,6 @@ export default function MetadataInfo({ title, link, label, icon, specific, uri } {sources.map((source, index) => { let processedSource = source; - console.log(typeof (source)); if (typeof (source) === 'string' && source.match(urlRegex)) { processedSource = getAfterThirdSlash(source); } From 9ac1c3891093084a9c9e04dad58044915dc8db5a Mon Sep 17 00:00:00 2001 From: learner97 Date: Thu, 1 Feb 2024 18:01:20 -0700 Subject: [PATCH 4/4] fixed an issue with sparql queries and from graphs, changed editable things so the icons are removed when there is no permissions --- .../components/Viewing/Collection/Members.js | 6 +-- frontend/components/Viewing/MetadataInfo.js | 51 ++++++++++++------- frontend/components/Viewing/ViewHeader.js | 17 +++++-- frontend/sparql/CountMembers.js | 2 +- frontend/sparql/CountMembersSearch.js | 2 +- frontend/sparql/getCollectionMembers.js | 2 +- 6 files changed, 50 insertions(+), 30 deletions(-) diff --git a/frontend/components/Viewing/Collection/Members.js b/frontend/components/Viewing/Collection/Members.js index ca058796..b111edc8 100644 --- a/frontend/components/Viewing/Collection/Members.js +++ b/frontend/components/Viewing/Collection/Members.js @@ -72,7 +72,7 @@ export default function Members(properties) { } const parameters = { - graphs: '', + from: '', graphPrefix: 'https://synbiohub.org/', // TODO: Maybe get this from somewhere? collection: properties.uri, sort: sort, @@ -81,10 +81,8 @@ export default function Members(properties) { limit: ' LIMIT 10000 ' }; - const { adminStatus, loading, error } = useStatus(token, dispatch); - if (token) { - parameters.graphs = privateGraph + parameters.from = "FROM <" + privateGraph + ">"; } else { } diff --git a/frontend/components/Viewing/MetadataInfo.js b/frontend/components/Viewing/MetadataInfo.js index 95f49e27..59f2f192 100644 --- a/frontend/components/Viewing/MetadataInfo.js +++ b/frontend/components/Viewing/MetadataInfo.js @@ -10,6 +10,7 @@ import axios from 'axios'; import getConfig from "next/config"; import { getAfterThirdSlash } from './ViewHeader'; +import { isUriOwner } from './Shell'; const { publicRuntimeConfig } = getConfig(); @@ -19,10 +20,18 @@ export default function MetadataInfo({ title, link, label, icon, specific, uri } const [isEditingSource, setIsEditingSource] = useState(false); const [newSource, setNewSource] = useState(''); const token = useSelector(state => state.user.token); + const username = useSelector(state => state.user.username); + + let objectUriParts = ""; + if (uri) { + objectUriParts = getAfterThirdSlash(uri); + } + const objectUri = `${publicRuntimeConfig.backend}/${objectUriParts}`; const [editSourceIndex, setEditSourceIndex] = useState(null); const [editedSource, setEditedSource] = useState(''); const [sources, setSources] = useState(processTitle(title)); // Assuming processTitle is your existing function + var isOwner = isUriOwner(objectUri, username); const handleEditSource = (index, source) => { @@ -44,9 +53,6 @@ export default function MetadataInfo({ title, link, label, icon, specific, uri } return; // Do not proceed with saving an empty source } - const objectUriParts = getAfterThirdSlash(uri); - const objectUri = `${publicRuntimeConfig.backend}/${objectUriParts}`; - axios .post( `${objectUri}/edit/wasDerivedFrom`, @@ -80,8 +86,6 @@ export default function MetadataInfo({ title, link, label, icon, specific, uri } }; const handleAddSource = () => { - const objectUriParts = getAfterThirdSlash(uri); - const objectUri = `${publicRuntimeConfig.backend}/${objectUriParts}`; const editedText = newSource; axios.post(`${objectUri}/add/wasDerivedFrom`, { @@ -109,8 +113,6 @@ export default function MetadataInfo({ title, link, label, icon, specific, uri } event.stopPropagation(); // Prevent event from propagating to parent elements if (window.confirm("Do you want to delete this source?")) { - const objectUriParts = getAfterThirdSlash(uri); - const objectUri = `${publicRuntimeConfig.backend}/${objectUriParts}`; // Find the index of the source to delete const indexToDelete = sources.findIndex(source => source === sourceToDelete); @@ -141,11 +143,18 @@ export default function MetadataInfo({ title, link, label, icon, specific, uri } {(label === "Source") && ( <> {/* Add space */} - setIsEditingSource(true)} - className={styles.plusIcon} - /> + { + isOwner && ( + <> + setIsEditingSource(true)} + className={styles.plusIcon} + /> + + ) + } + )}
@@ -210,12 +219,18 @@ export default function MetadataInfo({ title, link, label, icon, specific, uri } {editSourceIndex === index ? null : (
- - + { + isOwner && ( + <> + + + + ) + }
)} diff --git a/frontend/components/Viewing/ViewHeader.js b/frontend/components/Viewing/ViewHeader.js index ba6b4361..f070d594 100644 --- a/frontend/components/Viewing/ViewHeader.js +++ b/frontend/components/Viewing/ViewHeader.js @@ -143,11 +143,18 @@ export default function ViewHeader(properties) { ) : (

{displayedTitle}

- + { + isOwner && ( + <> + + + ) + } +
)} diff --git a/frontend/sparql/CountMembers.js b/frontend/sparql/CountMembers.js index 76c51329..181380f9 100644 --- a/frontend/sparql/CountMembers.js +++ b/frontend/sparql/CountMembers.js @@ -1,7 +1,7 @@ const query = `PREFIX sbol2: PREFIX dcterms: SELECT (COUNT(DISTINCT ?uri) AS ?count) -FROM <$graphs> +$from WHERE { <$collection> sbol2:member ?uri . OPTIONAL { ?uri a ?type . } diff --git a/frontend/sparql/CountMembersSearch.js b/frontend/sparql/CountMembersSearch.js index 10d75cc5..c8f3ca88 100644 --- a/frontend/sparql/CountMembersSearch.js +++ b/frontend/sparql/CountMembersSearch.js @@ -1,7 +1,7 @@ const query = `PREFIX sbol2: PREFIX dcterms: SELECT (COUNT(DISTINCT ?uri) AS ?count) -FROM <$graphs> +$from WHERE { <$collection> sbol2:member ?uri . OPTIONAL { ?uri a ?type . } diff --git a/frontend/sparql/getCollectionMembers.js b/frontend/sparql/getCollectionMembers.js index 6e3a4c95..5782b786 100644 --- a/frontend/sparql/getCollectionMembers.js +++ b/frontend/sparql/getCollectionMembers.js @@ -7,7 +7,7 @@ SELECT ?uri ?type ?sbolType ?role -FROM <$graphs> +$from WHERE { { SELECT DISTINCT ?uri ?displayId