diff --git a/src/components/DownloadDialog.jsx b/src/components/DownloadDialog.jsx index d13fb31e..3142df0d 100644 --- a/src/components/DownloadDialog.jsx +++ b/src/components/DownloadDialog.jsx @@ -11,6 +11,7 @@ import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormControl from '@material-ui/core/FormControl'; import TextField from '@material-ui/core/TextField'; import csv from 'csv-stringify'; +import { useLocalStorage } from '~/hooks'; const jsonToCsvString = (json) => new Promise((res, rej) => { csv.stringify(json, (err, output) => { @@ -104,31 +105,65 @@ const constructCsvObj = (message) => { }; export default function DownloadDialog({ - open, setOpen, message, + open, setOpen, message, download_type = 'answer', }) { const [type, setType] = React.useState('json'); const [fileName, setFileName] = React.useState('ROBOKOP_message'); + const [queryHistory, setQueryHistory] = useLocalStorage('query_history', {}); const handleClose = () => { setOpen(false); }; const handleClickDownload = async () => { - let blob; - if (type === 'json') { - blob = new Blob([JSON.stringify({ message }, null, 2)], { type: 'application/json' }); - } - if (type === 'csv') { - const csvString = await jsonToCsvString(constructCsvObj(message)); - blob = new Blob([csvString], { type: 'text/csv' }); - } + switch (download_type) { + case 'answer': { + let blob; + if (type === 'json') { + blob = new Blob([JSON.stringify({ message }, null, 2)], { type: 'application/json' }); + } + if (type === 'csv') { + const csvString = await jsonToCsvString(constructCsvObj(message)); + blob = new Blob([csvString], { type: 'text/csv' }); + } - const a = document.createElement('a'); - a.download = `${fileName}.${type}`; - a.href = window.URL.createObjectURL(blob); - document.body.appendChild(a); - a.click(); - a.remove(); + const a = document.createElement('a'); + a.download = `${fileName}.${type}`; + a.href = window.URL.createObjectURL(blob); + document.body.appendChild(a); + a.click(); + a.remove(); + break; + } + case 'all_queries': { + const raw = window.localStorage.getItem('query_history'); + const parsed = raw ? JSON.parse(raw) : {}; + const blob = new Blob([JSON.stringify({ bookmarked_queries: parsed }, null, 2)], { type: 'application/json' }); + // const blob = new Blob([JSON.stringify({ queryHistory }, null, 2)], { type: 'application/json' }); + const a = document.createElement('a'); + a.download = `${fileName}.${type}`; + a.href = window.URL.createObjectURL(blob); + document.body.appendChild(a); + a.click(); + a.remove(); + break; + } + case 'query': { + // Bookmark the query with the filename that's given. + if (!(fileName in queryHistory)) { + setQueryHistory((prev) => ({ + ...prev, + [fileName]: { + query_graph: message, + }, + })); + } + break; + } + default: { + handleClose(); + } + } handleClose(); }; @@ -142,7 +177,7 @@ export default function DownloadDialog({ Download Answer { setFileName(e.target.value); }} /> - - { setType(e.target.value); }}> - } label="JSON" /> - } label="CSV" /> - - + { // Show the radio group only when the download type is answers. + download_type === 'answer' && ( + + { setType(e.target.value); }}> + } label="JSON" /> + } label="CSV" /> + + + ) + } { type === 'csv' && ( @@ -170,7 +209,7 @@ export default function DownloadDialog({ Cancel diff --git a/src/hooks/index.js b/src/hooks/index.js new file mode 100644 index 00000000..87c1953d --- /dev/null +++ b/src/hooks/index.js @@ -0,0 +1 @@ +export * from './use-local-storage'; diff --git a/src/hooks/use-local-storage.js b/src/hooks/use-local-storage.js new file mode 100644 index 00000000..2fad50be --- /dev/null +++ b/src/hooks/use-local-storage.js @@ -0,0 +1,41 @@ +import { useState } from 'react'; + +export const useLocalStorage = (key, initialValue) => { + // State to store our value + // Pass initial state function to useState so logic is only executed once + const [storedValue, setStoredValue] = useState(() => { + try { + // Get from local storage by key + const item = window.localStorage.getItem(key); + // Parse stored json or if none return initialValue + return item ? JSON.parse(item) : initialValue; + } catch (error) { + // If error also return initialValue + console.log(error); + return initialValue; + } + }); + + // Return a wrapped version of useState's setter function that ... + // ... persists the new value to localStorage. + const setValue = (value) => { + try { + // Allow value to be a function so we have same API as useState + const valueToStore = + value instanceof Function ? value(storedValue) : value; + // Save state + setStoredValue(valueToStore); + // Save to local storage + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + } catch (error) { + // A more advanced implementation would handle the error case + console.log(error); + } + }; + + return [storedValue, setValue]; +}; + +export default { + useLocalStorage, +}; diff --git a/src/pages/answer/leftDrawer/LeftDrawer.jsx b/src/pages/answer/leftDrawer/LeftDrawer.jsx index b2a73c4d..b503ee35 100644 --- a/src/pages/answer/leftDrawer/LeftDrawer.jsx +++ b/src/pages/answer/leftDrawer/LeftDrawer.jsx @@ -63,6 +63,24 @@ export default function LeftDrawer({ ))} + { setDownloadOpen(true); }} + > + + + + + + + - + toggleJson(false)} /> + diff --git a/src/pages/queryBuilder/graphEditor/GraphEditor.jsx b/src/pages/queryBuilder/graphEditor/GraphEditor.jsx index 7b71a472..1bac0856 100644 --- a/src/pages/queryBuilder/graphEditor/GraphEditor.jsx +++ b/src/pages/queryBuilder/graphEditor/GraphEditor.jsx @@ -1,5 +1,5 @@ import React, { - useContext, useReducer, useEffect, + useContext, useReducer, useEffect, useState, } from 'react'; import Popover from '@material-ui/core/Popover'; import Button from '@material-ui/core/Button'; @@ -11,6 +11,7 @@ import nodeUtils from '~/utils/d3/nodes'; import QueryGraph from './QueryGraph'; import NodeSelector from '../textEditor/textEditorRow/NodeSelector'; import PredicateSelector from '../textEditor/textEditorRow/PredicateSelector'; +import DownloadDialog from '~/components/DownloadDialog'; import './graphEditor.css'; @@ -68,6 +69,7 @@ function clickReducer(state, action) { export default function GraphEditor() { const queryBuilder = useContext(QueryBuilderContext); const { query_graph } = queryBuilder; + const [downloadOpen, setDownloadOpen] = useState(false); const [clickState, clickDispatch] = useReducer(clickReducer, { creatingConnection: false, @@ -144,6 +146,17 @@ export default function GraphEditor() { > Connect Terms + )} + ); diff --git a/src/pages/queryBuilder/templatedQueries/TemplatedQueriesModal.jsx b/src/pages/queryBuilder/templatedQueries/TemplatedQueriesModal.jsx index c1c2283d..6b209bf4 100644 --- a/src/pages/queryBuilder/templatedQueries/TemplatedQueriesModal.jsx +++ b/src/pages/queryBuilder/templatedQueries/TemplatedQueriesModal.jsx @@ -7,6 +7,7 @@ import { Close } from '@material-ui/icons'; import QueryBuilderContext from '~/context/queryBuilder'; import examples from './templates.json'; import NodeSelector from '../textEditor/textEditorRow/NodeSelector'; +import { useLocalStorage } from '~/hooks'; const useStyles = makeStyles((theme) => ({ modal: { @@ -89,9 +90,10 @@ export default function TemplatedQueriesModal({ }) { const classes = useStyles(); const queryBuilder = useContext(QueryBuilderContext); - const [selectedExample, setSelectedExample] = useState(null); - + const raw = window.localStorage.getItem('query_history'); + const bookmarked_queries = raw ? JSON.parse(raw) : null; + console.log(bookmarked_queries); const handleClose = () => { setOpen(false); setSelectedExample(null); @@ -107,6 +109,24 @@ export default function TemplatedQueriesModal({ queryBuilder.dispatch({ type: 'editNode', payload: { id, node } }); }; + const handleSelectBookmarkedQuery = (query_graph) => { + const example = { + template: [ + { + text: JSON.stringify(query_graph.query_graph, null, 2), + type: 'json_text', + }, + ], + }; + console.log('In handeSelectBookmarkedQuery'); + console.log(query_graph); + setSelectedExample(example); + const payload = { + message: query_graph, + }; + queryBuilder.dispatch({ type: 'saveGraph', payload }); + }; + return (
@@ -143,6 +163,23 @@ export default function TemplatedQueriesModal({ /> ))} + {bookmarked_queries && Object.entries(bookmarked_queries).map(([key, value], i) => ( + handleSelectBookmarkedQuery(value)} + > + + {' '} + {key} + + )} + /> + + ))}
); } + if (part.type === 'json_text') { + return
{part.text}
; + } return null; }) }