diff --git a/app.py b/app.py index 6fd646b..165140e 100644 --- a/app.py +++ b/app.py @@ -23,35 +23,6 @@ 'relation': ['name', 'wd_node', 'wd_label', 'modality', 'wd_description', 'ta1ref', 'relationSubject', 'relationObject', 'relationPredicate'] } -# # def transform_version() - -# def is_ta2_format(data): -# return '@context' in data and 'instances' in data - -# def convert_ta2_to_ta1_format(ta2): -# ta1 = { -# 'events': [], -# 'entities': [], -# 'relations': [], -# } - -# if ta2['instances'] and len(ta2['instances']) > 0: -# instance = ta2['instances'][0] - -# for event in instance['events']: -# new_event = event.copy() -# if 'entities' in event: -# for entity in event['entities']: -# ta1['entities'].append(entity) -# del new_event['entities'] -# if 'relations' in event: -# for relation in event['relations']: -# ta1['relations'].append(relation) -# del new_event['relations'] -# ta1['events'].append(new_event) - -# return ta1 - def create_node(_id, _label, _type, _shape=''): """Creates a node. @@ -276,8 +247,6 @@ def get_nodes_and_edges(schema_json): entity_id = participant['entity'] if entity_id == '': entity_id = "Entities/20000/" - elif not entity_id.startswith("Entities/"): - entity_id = "Entities/" + entity_id edge = create_edge(event_id, entity_id, _label, _edge_type='step_participant') edge['data']['@id'] = participant['@id'] edges.append(edge) @@ -322,7 +291,7 @@ def get_nodes_and_edges(schema_json): # find root node(s) parentless_edge = {} for edge in edges: - if 'source' in edge['data'] and edge['data']['source'] in nodes: + if edge['data']['source'] not in parentless_edge: if nodes[edge['data']['source']]['data']['_type'] == 'entity': parentless_edge[edge['data']['source']] = False else: @@ -336,8 +305,6 @@ def get_nodes_and_edges(schema_json): # Zoey wants an entity-first view, so all entities are shown, with groups of events around them in clusters # Q: are we able to make a tab on the viewer itself to switch between views? - # print("\nnodes from get_nodes_and_edges:", nodes) - # print("\nedges from get_nodes_and_edges:", edges) return nodes, edges # NOTE: These are new?? @@ -484,6 +451,7 @@ def append_node(): """ global schema_json new_event = request.get_json() + print('Received data:', new_event) selected_element = new_event['parent_id'] #request.args.get('selected_element') del new_event['parent_id'] diff --git a/run_standalone_server.sh b/run_standalone_server.sh index 5cf7f51..ae2ca61 100755 --- a/run_standalone_server.sh +++ b/run_standalone_server.sh @@ -6,6 +6,7 @@ PATH=$APP_DIR/venv/bin:$PATH echo "Creating bundle.js" cd "$APP_DIR"/static +npm install npm run build cd "$APP_DIR" diff --git a/static/src/App.js b/static/src/App.js index 8aae724..78b42b7 100644 --- a/static/src/App.js +++ b/static/src/App.js @@ -79,7 +79,7 @@ export default function App() { sx={{ flexGrow: 1, display: { xs: 'none', sm: 'block' } }} > - - - - - - } /> - } /> + } + /> ); diff --git a/static/src/App.scss b/static/src/App.scss index 3c7686c..9922eec 100644 --- a/static/src/App.scss +++ b/static/src/App.scss @@ -66,7 +66,7 @@ a { background: white !important; height: 25vh !important; width: 25vw !important; - margin-bottom: 24px; + margin-bottom: 45px; margin-right: 45px; z-index: 1; } diff --git a/static/src/template/AddEntity.jsx b/static/src/template/AddEntity.jsx index 45fd195..ea80d05 100644 --- a/static/src/template/AddEntity.jsx +++ b/static/src/template/AddEntity.jsx @@ -38,6 +38,7 @@ function AddEntityDialog({ open, onClose, onSubmit, selectedElementForAddEntity, setEntityWdNode(''); setEntityWdLabel(''); setEntityWdDescription(''); + onClose(); // Added onClose here }; return ( diff --git a/static/src/template/AddEvent.jsx b/static/src/template/AddEvent.jsx index 3921898..ffcb932 100644 --- a/static/src/template/AddEvent.jsx +++ b/static/src/template/AddEvent.jsx @@ -35,39 +35,40 @@ function AddEventDialog({ open, onClose, onSubmit }) { onSubmit(newEvent); setEventName(''); setIsChapterEvent(false); + onClose(); // Added onClose here }; return ( - Add Event - - - Please enter event name. - - setEventName(e.target.value)} - /> - setIsChapterEvent(e.target.checked)} />} - label="Event Type" - /> - - - - - + Add Event + + + Please enter event name. + + setEventName(e.target.value)} + /> + setIsChapterEvent(e.target.checked)} />} + label="Event Type" + /> + + + + + ); } -export default AddEventDialog; +export default AddEventDialog; \ No newline at end of file diff --git a/static/src/template/Canvas.jsx b/static/src/template/Canvas.jsx index 7f9f346..d8e79a0 100644 --- a/static/src/template/Canvas.jsx +++ b/static/src/template/Canvas.jsx @@ -1,5 +1,6 @@ import React from 'react'; import axios from 'axios'; +import { ToastContainer, toast } from 'react-toastify'; import templates from './templates'; import GraphEdit from './GraphEdit'; @@ -28,8 +29,8 @@ import DeleteIcon from '@mui/icons-material/Delete'; import CloseIcon from '@material-ui/icons/Close'; import Button from '@material-ui/core/Button'; import IconButton from '@mui/material/IconButton'; +import EditIcon from '@mui/icons-material/Edit'; import Typography from '@mui/material/Typography'; -import FileCopyIcon from '@mui/icons-material/FileCopy' import 'cytoscape-context-menus/cytoscape-context-menus.css'; import "cytoscape-navigator/cytoscape.js-navigator.css"; @@ -42,7 +43,6 @@ cytoscape.use(cytoscapeNavigator); let event_counter = 20000; let entity_counter = 10000; let relation_counter = 30000; -let participant_counter = 20000; class Canvas extends React.Component { constructor(props) { @@ -61,6 +61,7 @@ constructor(props) { dialogContent: null, event_counter: 20000, entity_counter: 10000, + participant_counter: 20000, isAddEntityDialogOpen: false, isAddParticipantDialogOpen: false, addRelationDialogOpen: false, @@ -86,6 +87,7 @@ constructor(props) { this.removeSubTree = this.removeSubTree.bind(this); this.runLayout = this.runLayout.bind(this); this.reloadCanvas = this.reloadCanvas.bind(this); + this.updateGraph = this.updateGraph.bind(this); this.removeObject = this.removeObject.bind(this); this.restore = this.restore.bind(this); this.fitCanvas = this.fitCanvas.bind(this); @@ -96,6 +98,10 @@ constructor(props) { // console.log('canvasElements:', this.state.canvasElements); } +resetSelectedElement = () => { + this.setState({ selectedElement: null }); +}; + handleNavigatorToggle = () => { this.setState((prevState) => ({ isNavigatorVisible: !prevState.isNavigatorVisible @@ -126,23 +132,46 @@ handleCloseAddParticipantDialog = () => { handleAddParticipant = ({ participantName, participantRoleName, selectedEntity }) => { if (participantName) { + const newCounter = this.state.participant_counter + 1; + const participant = { ...templates.participant, - '@id': `Participants/${participant_counter++}/${participantName}`, + '@id': `Participants/${newCounter}/${participantName}`, 'roleName': participantRoleName, 'entity': selectedEntity }; + this.setState(prevState => ({ + participant_counter: prevState.participant_counter + 1 + })); + axios.post("/add_participant", { event_id: this.state.selectedElementForAddParticipant['@id'], participant_data: participant }) .then(res => { console.log("Response from server: ", res.data); - this.props.updateCallback(res.data); + + // Increment participant_counter in the .then callback + this.setState({ participant_counter: this.state.participant_counter + 1 }); + + const newSchema = res.data; + this.setState({ + canvasElements: CytoscapeComponent.normalizeElements(newSchema.events) + }, () => this.reloadCanvas()); // refresh the graph right after updating the state + + if (this.props.callbackFunction) { + this.props.callbackFunction(res.data); + } + + // Display success toast + toast.success('Participant added successfully!'); }) .catch(err => { console.error(err); + + // Display error toast + toast.error('Failed to add participant.'); }); this.handleCloseAddParticipantDialog(); @@ -169,10 +198,22 @@ onSubmitEvent = (newEvent) => { }) .then(res => { console.log("Response from server: ", res.data); - this.props.updateCallback(res.data); + + // Update the state with the new schema + const newSchema = res.data; + this.setState({ + canvasElements: CytoscapeComponent.normalizeElements(newSchema.events) + }, () => this.reloadCanvas()); // refresh the graph right after updating the state + + if (this.props.callbackFunction) { + this.props.callbackFunction(res.data); + } + + toast.success('Event added successfully!'); // Display success toast }) .catch(err => { console.error(err); + toast.error('Failed to add event.'); // Display error toast }); }; @@ -209,8 +250,8 @@ handleAddRelation = ({ relationName, wdNode, wdLabel, wdDescription, selectedEnt }) .then(res => { console.log("Response from server: ", res.data); - if (this.props.updateCallback) { - this.props.updateCallback(res.data); + if (this.props.callbackFunction) { + this.props.callbackFunction(res.data); } }) .catch(err => { @@ -247,12 +288,26 @@ handleAddEntity = (newEntity) => { console.log("Response from server: ", res.data); // increment entity_counter in the .then callback this.setState({ entity_counter: this.state.entity_counter + 1 }); - if (this.props.updateCallback) { - this.props.updateCallback(res.data); + + // Update the state with the new schema + // This assumes that `canvasElements` represents the current schema + const newSchema = res.data; + this.setState({ + canvasElements: CytoscapeComponent.normalizeElements(newSchema.events) + }, () => this.reloadCanvas()); // refresh the graph right after updating the state + + if (this.props.callbackFunction) { + this.props.callbackFunction(res.data); } + + // Display success toast + toast.success('Entity added successfully!'); }) .catch(err => { console.error(err); + + // Display error toast + toast.error('Failed to add entity.'); }); }; @@ -365,14 +420,37 @@ runLayout() { } reloadCanvas() { - this.setState({ - canvasElements: CytoscapeComponent.normalizeElements(this.props.elements), - hasSubtree: false, - showParticipants: true - }); - this.cy.elements().remove(); - this.cy.add(this.state.canvasElements); - this.runLayout(); + this.setState({ + canvasElements: CytoscapeComponent.normalizeElements(this.props.elements), + hasSubtree: false, + showParticipants: true + }); + this.cy.elements().remove(); + this.cy.add(this.state.canvasElements); + this.runLayout(); +} + +updateGraph() { + // Store current positions + let nodePositions = {}; + this.cy.nodes().forEach(node => { + nodePositions[node.id()] = node.position(); + }); + + this.setState({ + canvasElements: CytoscapeComponent.normalizeElements(this.props.elements) + }); + + this.cy.elements().remove(); + this.cy.add(this.state.canvasElements); + + // Set the positions back to their original values + for (let nodeId in nodePositions) { + const node = this.cy.getElementById(nodeId); + if (node) { + node.position(nodePositions[nodeId]); + } + } } removeObject(event) { @@ -402,6 +480,8 @@ handleOpen() { handleClose = () => { this.setState({ isGraphEditOpen: false }); + selectedElement: null + this.reloadCanvas } fitCanvas() { @@ -803,6 +883,13 @@ render() { onClick={this.reloadCanvas} /> + + + ); diff --git a/static/src/template/GraphEdit.jsx b/static/src/template/GraphEdit.jsx index 28faafb..afadce2 100644 --- a/static/src/template/GraphEdit.jsx +++ b/static/src/template/GraphEdit.jsx @@ -15,7 +15,8 @@ import { Typography, Paper, Box, - makeStyles + makeStyles, + Slider } from "@material-ui/core"; import _ from "lodash"; import Draggable from 'react-draggable'; @@ -30,21 +31,22 @@ function PaperComponent(props) { const useStyles = makeStyles((theme) => ({ halfWidthDialog: { - width: '50%', // Change the width of the dialog to 50% + width: '50%', }, dialogTitle: { fontSize: '3rem', - color: blue[900], // Change color to darker blue + color: blue[900], }, header: { - fontSize: '1rem', // normal size - fontWeight: 'bold', // make the font bold - color: blue[900], // Change color to darker blue + fontSize: '1rem', + fontWeight: 'bold', + color: blue[900], + marginTop: theme.spacing(1), }, multilineInput: { - minHeight: '3em', // Set the minimum height to accommodate 3 lines - maxHeight: '6em', // Set the maximum height to accommodate 3 lines - overflow: 'hidden', // Clip text to the last line + minHeight: '3em', + maxHeight: '6em', + overflow: 'hidden', }, })); @@ -60,7 +62,20 @@ const GraphEdit = React.forwardRef((props, ref) => { const [data, setData] = useState(initData); const [edit, setEdit] = useState(""); const [open, setOpen] = useState(!!props.selectedElement); - const refFocus = useRef(null); + // const refFocus = useRef(null); + + const handleSliderChange = (e, value) => { + setData(prevData => { + const newSelectedElement = { + ...prevData.selectedElement, + 'importance': [value], + }; + return { + ...prevData, + selectedElement: newSelectedElement, + }; + }); + }; // Add a useEffect hook to fetch entity names from the server useEffect(() => { @@ -118,19 +133,19 @@ const GraphEdit = React.forwardRef((props, ref) => { }); }; - const handleSwitchChange = (e) => { - // update data state with the new value - setData(prevData => { - const newSelectedElement = { - ...prevData.selectedElement, - [e.target.name]: e.target.checked, - }; - return { - ...prevData, - selectedElement: newSelectedElement, - }; - }); - }; + // const handleSwitchChange = (e) => { + // // update data state with the new value + // setData(prevData => { + // const newSelectedElement = { + // ...prevData.selectedElement, + // [e.target.name]: e.target.checked, + // }; + // return { + // ...prevData, + // selectedElement: newSelectedElement, + // }; + // }); + // }; const handleBooleanChange = (e) => { const targetName = e.target.name; @@ -148,17 +163,17 @@ const GraphEdit = React.forwardRef((props, ref) => { }); }; - const addChapterEventIdToChildren = (newEventId, selectedElementId) => { - schema_json['events'].forEach(element => { - if (element['@id'] === selectedElementId) { - if (!element.children) { - element.children = [newEventId]; - } else { - element.children.push(newEventId); - } - } - }); - }; + // const addChapterEventIdToChildren = (newEventId, selectedElementId) => { + // schema_json['events'].forEach(element => { + // if (element['@id'] === selectedElementId) { + // if (!element.children) { + // element.children = [newEventId]; + // } else { + // element.children.push(newEventId); + // } + // } + // }); + // }; const handleSubmit = (e) => { // create data to pass up @@ -175,13 +190,13 @@ const GraphEdit = React.forwardRef((props, ref) => { } }); - // console.log("\n(GRAPHEDIT.JSX) The node data from handleSubmit:\n", node_data); + console.log("(GRAPHEDIT.JSX) The node data from handleSubmit:\n", node_data); // change sidebar internal id if the id is changed if (e.target.id === '@id') { data.selectedElement['id'] = e.target.value; setData({ ...data }); - // console.log("(GRAPHEDIT.JSX) Selected element id updated to:", data.selectedElement['id']); + console.log("(GRAPHEDIT.JSX) Selected element id updated to:", data.selectedElement['id']); } // Determine which action to take based on the props @@ -193,11 +208,11 @@ const GraphEdit = React.forwardRef((props, ref) => { // Update the events list in the schema JSON with the new chapter event if (node_data.updatedFields.name) { - // console.log("(GRAPHEDIT.JSX) Sending chapter event to server:", data.selectedElement); + console.log("(GRAPHEDIT.JSX) Sending chapter event to server:", data.selectedElement); axios.post("/add_event", data.selectedElement) .then(res => { console.log("Response from server: ", res.data); - props.updateCallback(res.data); + props.callbackFunction(res.data); // or props.updateCallback(res.data); }) .catch(err => { console.error(err); @@ -208,21 +223,25 @@ const GraphEdit = React.forwardRef((props, ref) => { handleClose(); }; + const handleClose = () => { // close the dialog // console.log("Closing dialog"); // console.log("Type of onClose: ", typeof props.onClose); setOpen(false); + props.resetSelectedElement(); + props.updateGraph(); }; let i = 0; const excluded_ids = ['id', '_label', '_type', '_shape', '_edge_type', 'child','outlinks', 'relations', 'children_gate', 'key', 'modality'] - const selectedElement = data.selectedElement || {}; + // const selectedElement = data.selectedElement || {}; return ( {isEmpty(data) ? "" : data.selectedElement?.["_label"]} + {/* */} {isEmpty(data) ? ( "" ) : ( @@ -233,6 +252,25 @@ const GraphEdit = React.forwardRef((props, ref) => {
{data.selectedElement && Object.entries(data.selectedElement).map(([key, val]) => { + // Add a slider for the 'importance' field + if (key === 'importance') { + return ( + + + Importance + + + + ); + } if (excluded_ids.includes(key) || ['participants', 'children', 'entities'].includes(key)) return null; return ( @@ -274,7 +312,7 @@ const GraphEdit = React.forwardRef((props, ref) => { if (val && val.length > 0) { return ( - {_.capitalize(key)} + {_.capitalize(key)} {val.map(v => (v["@id"] || v["name"]) ? (v["name"] || data.entityNames[v["@id"]] || data.eventNames[v["@id"]] || v["@id"]) : v).join(", ")} ); @@ -292,4 +330,5 @@ const GraphEdit = React.forwardRef((props, ref) => {
); }); + export default GraphEdit; \ No newline at end of file diff --git a/static/src/template/Home.jsx b/static/src/template/Home.jsx index 26b95b4..3ce72d1 100644 --- a/static/src/template/Home.jsx +++ b/static/src/template/Home.jsx @@ -5,7 +5,7 @@ class Home extends Component { render() { return ( -
SCI 3.0 is a web application designed to visualize complex events and curate them for research purposes.
+
CAI 1.0 is a web application designed to facilitate annotator adjudication for research purposes.
); } diff --git a/static/src/template/MuiDrawer.jsx b/static/src/template/MuiDrawer.jsx index e56bc26..4c8a4cd 100644 --- a/static/src/template/MuiDrawer.jsx +++ b/static/src/template/MuiDrawer.jsx @@ -6,10 +6,6 @@ import JsonEdit from './JsonEdit'; const MuiDrawer = (props) => { const theme = useTheme(); - const [drawerOpen, setDrawerOpen] = useState(false); - const handleDrawerToggle = () => { - setDrawerOpen(!drawerOpen); - }; const handleJsonEditCallback = (json) => { console.log('JSON passed to MuiDrawer:', json); diff --git a/static/src/template/Viewer.jsx b/static/src/template/Viewer.jsx index 9bb885f..cdbc6b2 100644 --- a/static/src/template/Viewer.jsx +++ b/static/src/template/Viewer.jsx @@ -4,14 +4,11 @@ import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import IconButton from '@mui/material/IconButton'; import DownloadIcon from '@mui/icons-material/CloudDownload'; -import Tooltip from '@mui/material/Tooltip'; import axios from 'axios'; import UploadModal from './UploadModal'; import MuiDrawer from './MuiDrawer'; import Canvas from './Canvas'; -import SideEditor from './SideEditor'; -import JsonEdit from './JsonEdit'; /* Viewer page for the schema interface. */ class Viewer extends Component { @@ -123,7 +120,7 @@ class Viewer extends Component { render() { let canvas = ""; let schemaHeading = ""; - let jsonEdit = ""; + let muiDrawer = ""; let sidebarClassName = this.state.isOpen ? "sidebar-open" : "sidebar-closed"; let canvasClassName = this.state.isOpen ? "canvas-shrunk" : "canvas-wide"; @@ -142,11 +139,12 @@ class Viewer extends Component { className={canvasClassName} />; - jsonEdit = + muiDrawer = ; } return ( @@ -155,9 +153,7 @@ class Viewer extends Component { - - {schemaHeading}
{/* {console.log("Testing from Viewer.jsx", this.state.schemaJson)} */} - - + {muiDrawer} {canvas} - {jsonEdit}
)