This is an automated email from the ASF dual-hosted git repository.
hbshin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/age-viewer.git
The following commit(s) were added to refs/heads/main by this push:
new 35bce43 node delete confirmation (#74)
35bce43 is described below
commit 35bce439e5a72da248a4ec5aa44954fb2240dd06
Author: MJinH <[email protected]>
AuthorDate: Wed Dec 28 23:32:39 2022 -0800
node delete confirmation (#74)
* delete confirmation
* Fix unused icon remove
Co-authored-by: [email protected] <[email protected]>
Co-authored-by: Hanbyeol Shin / David Shin / 신한별
<[email protected]>
---
frontend/src/app/reducers.js | 2 +
.../containers/CypherResultCytoscapeContainer.js | 8 ++-
.../presentations/CypherResultCytoscape.jsx | 6 ++
.../cytoscape/CypherResultCytoscapeChart.jsx | 19 ++++-
.../containers/Modal.js} | 15 ++--
.../src/components/modal/presentations/Modal.jsx | 82 ++++++++++++++++++++++
.../src/components/template/DefaultTemplate.js | 1 +
.../template/presentations/DefaultTemplate.jsx | 4 ++
frontend/src/features/modal/ModalSlice.js | 75 ++++++++++++++++++++
frontend/src/static/style.css | 39 +++++++++-
10 files changed, 239 insertions(+), 12 deletions(-)
diff --git a/frontend/src/app/reducers.js b/frontend/src/app/reducers.js
index 1c9e069..411eba8 100644
--- a/frontend/src/app/reducers.js
+++ b/frontend/src/app/reducers.js
@@ -26,6 +26,7 @@ import SettingReducer from '../features/setting/SettingSlice';
import CypherReducer from '../features/cypher/CypherSlice';
import AlertReducer from '../features/alert/AlertSlice';
import EditorSlice from '../features/editor/EditorSlice';
+import ModalSlice from '../features/modal/ModalSlice';
import LayoutSlice from '../features/layout/LayoutSlice';
const rootReducer = combineReducers({
@@ -37,6 +38,7 @@ const rootReducer = combineReducers({
cypher: CypherReducer,
alerts: AlertReducer,
editor: EditorSlice,
+ modal: ModalSlice,
layout: LayoutSlice,
});
diff --git
a/frontend/src/components/cypherresult/containers/CypherResultCytoscapeContainer.js
b/frontend/src/components/cypherresult/containers/CypherResultCytoscapeContainer.js
index 157e118..07c701f 100644
---
a/frontend/src/components/cypherresult/containers/CypherResultCytoscapeContainer.js
+++
b/frontend/src/components/cypherresult/containers/CypherResultCytoscapeContainer.js
@@ -20,6 +20,7 @@
import { connect } from 'react-redux';
import CypherResultCytoscape from '../presentations/CypherResultCytoscape';
import { setLabels } from '../../../features/cypher/CypherSlice';
+import { openModal, addGraphHistory, addElementHistory } from
'../../../features/modal/ModalSlice';
import { generateCytoscapeElement } from '../../../features/cypher/CypherUtil';
const mapStateToProps = (state, ownProps) => {
@@ -54,7 +55,12 @@ const mapStateToProps = (state, ownProps) => {
};
};
-const mapDispatchToProps = { setLabels };
+const mapDispatchToProps = {
+ setLabels,
+ openModal,
+ addGraphHistory,
+ addElementHistory,
+};
export default connect(
mapStateToProps,
diff --git
a/frontend/src/components/cypherresult/presentations/CypherResultCytoscape.jsx
b/frontend/src/components/cypherresult/presentations/CypherResultCytoscape.jsx
index 049a86e..982b710 100644
---
a/frontend/src/components/cypherresult/presentations/CypherResultCytoscape.jsx
+++
b/frontend/src/components/cypherresult/presentations/CypherResultCytoscape.jsx
@@ -380,6 +380,9 @@ const CypherResultCytoscape = forwardRef((props, ref) => {
addLegendData={addLegendData}
maxDataOfGraph={maxDataOfGraph}
graph={props.graph}
+ openModal={props.openModal}
+ addGraphHistory={props.addGraphHistory}
+ addElementHistory={props.addElementHistory}
/>
<CypherResultCytoscapeFooter
captions={captions}
@@ -421,6 +424,9 @@ CypherResultCytoscape.propTypes = {
refKey: PropTypes.string.isRequired,
setChartLegend: PropTypes.func.isRequired,
graph: PropTypes.string.isRequired,
+ openModal: PropTypes.func.isRequired,
+ addGraphHistory: PropTypes.func.isRequired,
+ addElementHistory: PropTypes.func.isRequired,
setIsTable: PropTypes.func.isRequired,
};
diff --git a/frontend/src/components/cytoscape/CypherResultCytoscapeChart.jsx
b/frontend/src/components/cytoscape/CypherResultCytoscapeChart.jsx
index a68c35b..17c9949 100644
--- a/frontend/src/components/cytoscape/CypherResultCytoscapeChart.jsx
+++ b/frontend/src/components/cytoscape/CypherResultCytoscapeChart.jsx
@@ -27,6 +27,7 @@ import klay from 'cytoscape-klay';
import euler from 'cytoscape-euler';
import avsdf from 'cytoscape-avsdf';
import spread from 'cytoscape-spread';
+import { useDispatch } from 'react-redux';
import CytoscapeComponent from 'react-cytoscapejs';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
@@ -53,10 +54,12 @@ cytoscape.use(cxtmenu);
const CypherResultCytoscapeCharts = ({
elements, cytoscapeObject, setCytoscapeObject, cytoscapeLayout,
maxDataOfGraph,
- onElementsMouseover, addLegendData, graph,
+ onElementsMouseover, addLegendData, graph, openModal,
+ addGraphHistory, addElementHistory,
}) => {
const [cytoscapeMenu, setCytoscapeMenu] = useState(null);
const [initialized, setInitialized] = useState(false);
+ const dispatch = useDispatch();
const addEventOnElements = (targetElements) => {
targetElements.bind('mouseover', (e) => {
onElementsMouseover({ type: 'elements', data: e.target.data() });
@@ -234,6 +237,17 @@ const CypherResultCytoscapeCharts = ({
});
},
},
+
+ {
+ content: ReactDOMServer.renderToString(
+ (<FontAwesomeIcon icon={faTrash} size="lg" />),
+ ),
+ select(ele) {
+ dispatch(openModal());
+ dispatch(addGraphHistory(graph));
+ dispatch(addElementHistory(ele.id()));
+ },
+ },
],
fillColor: 'rgba(210, 213, 218, 1)',
activeFillColor: 'rgba(166, 166, 166, 1)',
@@ -310,6 +324,9 @@ CypherResultCytoscapeCharts.propTypes = {
onElementsMouseover: PropTypes.func.isRequired,
addLegendData: PropTypes.func.isRequired,
graph: PropTypes.string.isRequired,
+ openModal: PropTypes.func.isRequired,
+ addGraphHistory: PropTypes.func.isRequired,
+ addElementHistory: PropTypes.func.isRequired,
};
export default CypherResultCytoscapeCharts;
diff --git a/frontend/src/components/template/DefaultTemplate.js
b/frontend/src/components/modal/containers/Modal.js
similarity index 64%
copy from frontend/src/components/template/DefaultTemplate.js
copy to frontend/src/components/modal/containers/Modal.js
index 880d09d..f34aed1 100644
--- a/frontend/src/components/template/DefaultTemplate.js
+++ b/frontend/src/components/modal/containers/Modal.js
@@ -18,16 +18,13 @@
*/
import { connect } from 'react-redux';
-import DefaultTemplate from './presentations/DefaultTemplate';
-import { changeSettings } from '../../features/setting/SettingSlice';
+import { closeModal, removeGraphHistory, removeElementHistory } from
'../../../features/modal/ModalSlice';
+import Modal from '../presentations/Modal';
const mapStateToProps = (state) => ({
- theme: state.setting.theme,
- maxNumOfFrames: state.setting.maxNumOfFrames,
- maxNumOfHistories: state.setting.maxNumOfHistories,
- maxDataOfGraph: state.setting.maxDataOfGraph,
- maxDataOfTable: state.setting.maxDataOfTable,
+ graphHistory: state.modal.graphHistory,
+ elementHistory: state.modal.elementHistory,
});
+const mapDispatchToProps = { closeModal, removeGraphHistory,
removeElementHistory };
-const mapDispatchToProps = { changeSettings };
-export default connect(mapStateToProps, mapDispatchToProps)(DefaultTemplate);
+export default connect(mapStateToProps, mapDispatchToProps)(Modal);
diff --git a/frontend/src/components/modal/presentations/Modal.jsx
b/frontend/src/components/modal/presentations/Modal.jsx
new file mode 100644
index 0000000..71854cc
--- /dev/null
+++ b/frontend/src/components/modal/presentations/Modal.jsx
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import { useDispatch } from 'react-redux';
+
+const Modal = ({
+ closeModal,
+ graphHistory,
+ elementHistory,
+ removeGraphHistory,
+ removeElementHistory,
+}) => {
+ const dispatch = useDispatch();
+
+ const removeNode = () => {
+ fetch('/api/v1/cypher',
+ {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ cmd: `SELECT * FROM
cypher('${graphHistory[0]}', $$ MATCH (S) WHERE id(S) = ${elementHistory[0]}
DETACH DELETE S $$) as (S agtype);` }),
+ })
+ .then((res) => {
+ if (res.ok) {
+ dispatch(removeGraphHistory());
+ dispatch(removeElementHistory());
+ dispatch(closeModal());
+ alert('The node has been deleted from your database. Please refresh
the page or frame.');
+ }
+ });
+ };
+
+ return (
+ <div className="modal-container">
+ <div className="modal-wrapper">
+ <h4>
+ After clicking on confirm, the node and related edge will be deleted
from the database.
+ </h4>
+ <div className="btn-container">
+ <button type="button" className="btn confirm-btn" onClick={() => {
dispatch(closeModal()); }}>
+ cancel
+ </button>
+ <button type="button" className="btn clear-btn" onClick={() => {
removeNode(); }}>
+ confirm
+ </button>
+ </div>
+ </div>
+ </div>
+ );
+};
+
+Modal.propTypes = {
+ closeModal: PropTypes.func.isRequired,
+ // eslint-disable-next-line react/forbid-prop-types
+ graphHistory: PropTypes.any.isRequired,
+ // eslint-disable-next-line react/forbid-prop-types
+ elementHistory: PropTypes.any.isRequired,
+ removeGraphHistory: PropTypes.func.isRequired,
+ removeElementHistory: PropTypes.func.isRequired,
+};
+
+export default Modal;
diff --git a/frontend/src/components/template/DefaultTemplate.js
b/frontend/src/components/template/DefaultTemplate.js
index 880d09d..80e8643 100644
--- a/frontend/src/components/template/DefaultTemplate.js
+++ b/frontend/src/components/template/DefaultTemplate.js
@@ -27,6 +27,7 @@ const mapStateToProps = (state) => ({
maxNumOfHistories: state.setting.maxNumOfHistories,
maxDataOfGraph: state.setting.maxDataOfGraph,
maxDataOfTable: state.setting.maxDataOfTable,
+ isOpen: state.modal.isOpen,
});
const mapDispatchToProps = { changeSettings };
diff --git a/frontend/src/components/template/presentations/DefaultTemplate.jsx
b/frontend/src/components/template/presentations/DefaultTemplate.jsx
index d24ffcc..323e1c9 100644
--- a/frontend/src/components/template/presentations/DefaultTemplate.jsx
+++ b/frontend/src/components/template/presentations/DefaultTemplate.jsx
@@ -23,6 +23,7 @@ import { useDispatch } from 'react-redux';
import EditorContainer from '../../contents/containers/Editor';
import Sidebar from '../../sidebar/containers/Sidebar';
import Contents from '../../contents/containers/Contents';
+import Modal from '../../modal/containers/Modal';
import { loadFromCookie, saveToCookie } from
'../../../features/cookie/CookieUtil';
const DefaultTemplate = ({
@@ -32,6 +33,7 @@ const DefaultTemplate = ({
maxDataOfGraph,
maxDataOfTable,
changeSettings,
+ isOpen,
}) => {
const dispatch = useDispatch();
const [stateValues] = useState({
@@ -74,6 +76,7 @@ const DefaultTemplate = ({
return (
<div className="default-template">
+ { isOpen && <Modal /> }
<input
type="radio"
className="theme-switch"
@@ -109,6 +112,7 @@ DefaultTemplate.propTypes = {
maxDataOfGraph: PropTypes.number.isRequired,
maxDataOfTable: PropTypes.number.isRequired,
changeSettings: PropTypes.func.isRequired,
+ isOpen: PropTypes.bool.isRequired,
};
export default DefaultTemplate;
diff --git a/frontend/src/features/modal/ModalSlice.js
b/frontend/src/features/modal/ModalSlice.js
new file mode 100644
index 0000000..a9003df
--- /dev/null
+++ b/frontend/src/features/modal/ModalSlice.js
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* eslint-disable no-param-reassign */
+import { createSlice } from '@reduxjs/toolkit';
+
+const ModalSlice = createSlice({
+ name: 'modal',
+ initialState: {
+ isOpen: false,
+ graphHistory: [],
+ elementHistory: [],
+ },
+ reducers: {
+ openModal: {
+ reducer: (state) => {
+ state.isOpen = true;
+ },
+ },
+ closeModal: {
+ reducer: (state) => {
+ state.isOpen = false;
+ },
+ },
+ addGraphHistory: {
+ reducer: (state, action) => {
+ state.graphHistory.push(action.payload.graph);
+ },
+ prepare: (graph) => ({ payload: { graph } }),
+ },
+ addElementHistory: {
+ reducer: (state, action) => {
+ state.elementHistory.push(action.payload.element);
+ },
+ prepare: (element) => ({ payload: { element } }),
+ },
+ removeGraphHistory: {
+ reducer: (state) => {
+ state.graphHistory = [];
+ },
+ },
+ removeElementHistory: {
+ reducer: (state) => {
+ state.elementHistory = [];
+ },
+ },
+ },
+});
+
+export const {
+ openModal,
+ closeModal,
+ addGraphHistory,
+ addElementHistory,
+ removeGraphHistory,
+ removeElementHistory,
+} = ModalSlice.actions;
+
+export default ModalSlice.reducer;
diff --git a/frontend/src/static/style.css b/frontend/src/static/style.css
index 8d5ad43..19e4186 100644
--- a/frontend/src/static/style.css
+++ b/frontend/src/static/style.css
@@ -797,4 +797,41 @@ textarea.editorTextarea
}
.refresh_button:hover {
opacity: 0.6;
-}
\ No newline at end of file
+}
+
+.modal-container {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.7);
+ z-index: 9999;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .modal-wrapper {
+ background: #F8F8F8;
+ width: 80vw;
+ max-width: 400px;
+ border-radius: 10px;
+ padding: 2rem 1rem;
+ text-align: center;
+ }
+ .modal-wrapper h4 {
+ margin-bottom: 0;
+ line-height: 1.5;
+ font-size: 1.2rem;
+ }
+ .modal-wrapper .clear-btn,
+ .modal-wrapper .confirm-btn {
+ margin-top: 1rem;
+ cursor: pointer;
+ }
+ .btn-container {
+ display: flex;
+ justify-content: space-around;
+ }
+
\ No newline at end of file