Skip to content

Commit

Permalink
- Fixes #539
Browse files Browse the repository at this point in the history
- Also added an icon to top-level menu to copy URL to clipboard to
  restore current app state
  • Loading branch information
Sigfried committed Nov 7, 2024
1 parent d8f1e26 commit a83935a
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 25 deletions.
41 changes: 39 additions & 2 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,16 @@ import { AboutPage } from "./components/AboutPage";
import { ConceptGraph, } from "./components/GraphPlayground";
import {
ReducerProviders, useCodesetIds,
resetReducers,
// CodesetIdsProvider, CidsProvider, GraphOptionsProvider,
ViewCurrentState,
// AppOptionsProvider,
NewCsetProvider,
NewCsetProvider, useNewCset,
// AlertsProvider, useAlerts, useAlertsDispatch, useNewCset, urlWithSessionStorage,
} from './state/AppState';
import {
SearchParamsProvider,
SessionStorageProvider,
SessionStorageProvider, useSearchParamsState,
// SessionStorageWithSearchParamsProvider,
// useSessionStorageWithSearchParams,
} from './state/StorageProvider';
Expand Down Expand Up @@ -101,10 +102,46 @@ export function AppWrapper({children}) {
// window.compress = compress;
// window.decompress = decompress;
export function RoutesContainer() {
const spState = useSearchParamsState();
let {sp, updateSp, } = spState;
const [newCset, newCsetDispatch] = useNewCset();

const [codeset_ids, codesetIdsDispatch] = useCodesetIds();
const location = useLocation();
// const [newCset, newCsetDispatch] = useNewCset();

useEffect(() => {
if (sp.sstorage) {
// const sstorageString = decompress(sp.sstorage);
// const sstorage = JSON.parse(sstorageString);
const sstorage = JSON.parse(sp.sstorage);
Object.entries(sstorage).map(([k,v]) => {
if (k === 'newCset') {
// restore alters newCset.definitions (unabbreviates)
v = newCsetDispatch({type: 'restore', newCset: v});
} else {
// console.warn('was only expecting newCset in sstorage search param, got', {[k]: v}, 'adding to sessionStorage anyway');
sessionStorage.setItem(k, JSON.stringify(v));
}
});

updateSp({delProps: ['sstorage']});

resetReducers({useStorageState: true});

// this updateSp generates a warning
// You should call navigate() in a React.useEffect(), not when your component is first rendered.
// but seems to work ok anyway. If if doesn't, try going back to something like the code below.
// but the problem with code below is that you can't re-navigate by returning <Navigate...> from
// useEffect. has to be returned by RoutesContainer.
// sp = {...sp};
// delete sp.sstorage;
// let csp = createSearchParams(sp);
// let url = location.pathname + '?' + csp;
// return <Navigate to={url} replace={true} />;
}
})

let pathname = location.pathname;

if (pathname === "/cset-comparison" && isEmpty(codeset_ids)) {
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/AboutPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ export function AboutPage() {

// const linkPath = `/OMOPConceptSets?${localCodesetIds.map(d => `codeset_ids=${d}`).join("&")}`;

/* I don't know if this code was working before 2024-11 cache refactoring,
but now there's no dataCache.lastRefreshed or .cacheCheck, so it's broken
useEffect(() => {
(async () => {
try {
Expand All @@ -108,7 +110,7 @@ export function AboutPage() {
'was getting a max update depth exceeded here. fix it if it comes up again');
}
})();
}, []);
}, []); */

console.log(loadCSetsRef);
return (
Expand Down Expand Up @@ -236,6 +238,7 @@ export function AboutPage() {
View state
</Button>

{/* not keeping usage logs now, I don't think
<Button
to={`/usage${search ? search : ''}`}
variant={'contained'}
Expand All @@ -244,6 +247,7 @@ export function AboutPage() {
>
Usage report
</Button>
*/}
</TextBody>

</div>
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/components/CsetComparisonPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ export async function fetchGraphData(props) {

if (!isEmpty(newCset)) {
concept_ids = union(concept_ids,
Object.values(newCset.definitions).map(d => d.concept_id));
Object.keys(newCset.definitions).map(d => parseInt(d))
);
}
promises.push(
dataGetter.fetchAndCacheItems(dataGetter.apiCalls.concepts, concept_ids));
Expand Down Expand Up @@ -1551,12 +1552,12 @@ export function howToSaveStagedChanges(params) {
</ol>
<p>
Return to this work later by saving or bookmarking <a
href={urlWithSessionStorage({newCset: params.newCset})}
href={urlWithSessionStorage()}
target="_blank" rel="noreferrer">this link</a> (
<Button
onClick={() => {
navigator.clipboard.writeText(
urlWithSessionStorage({newCset: params.newCset}));
urlWithSessionStorage());
}}
>
Copy to clipboard
Expand Down
29 changes: 28 additions & 1 deletion frontend/src/components/MuiAppBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Container from "@mui/material/Container";
import IconButton from "@mui/material/IconButton";
import Snackbar from '@mui/material/Snackbar';
import Menu from "@mui/material/Menu";
import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography";
Expand All @@ -13,6 +14,8 @@ import MenuBookRounded from "@mui/icons-material/MenuBookRounded";
import MenuItem from "@mui/material/MenuItem";
import Tooltip from "@mui/material/Tooltip";
import { NavLink, useLocation } from "react-router-dom";
import ContentCopyIcon from '@mui/icons-material/ContentCopy';

// import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
// import ChevronRightIcon from "@mui/icons-material/ChevronRight";
// import CssBaseline from "@mui/material/CssBaseline";
Expand All @@ -25,7 +28,7 @@ import { NavLink, useLocation } from "react-router-dom";
// import ListItemText from "@mui/material/ListItemText";
import { cloneDeep } from "lodash";
import {VERSION, DEPLOYMENT} from "../env";
import {useCodesetIds} from "../state/AppState";
import {urlWithSessionStorage, useCodesetIds} from '../state/AppState';
// import {client} from "./utils";

const drawerWidth = 240;
Expand Down Expand Up @@ -61,6 +64,8 @@ export function getPages(codeset_ids) {
/* https://mui.com/material-ui/react-app-bar/ */
export default function MuiAppBar() {
const [codeset_ids, ] = useCodesetIds();
const [showCopySuccess, setShowCopySuccess] = useState(false);

const location = useLocation();
const { search } = location;
const pages = getPages(codeset_ids);
Expand Down Expand Up @@ -160,6 +165,15 @@ export default function MuiAppBar() {
})}
</Box>
);
const handleCopyAppStateToClipboard = async () => {
try {
await navigator.clipboard.writeText(urlWithSessionStorage());
setShowCopySuccess(true);
} catch (err) {
console.error('Failed to copy:', err);
}
};

return (
<AppBar
className="Mui-app-bar"
Expand Down Expand Up @@ -197,6 +211,19 @@ export default function MuiAppBar() {
>
v{ VERSION } {/*process.env.COMMIT_HASH*/}
</Typography>
<IconButton
onClick={handleCopyAppStateToClipboard}
title="Copy link to current application state to clipboard"
>
<ContentCopyIcon sx={{ color: 'white', display: { xs: "none", md: "flex" }, mr: 1 }} />
</IconButton>

<Snackbar
open={showCopySuccess}
autoHideDuration={2000}
onClose={() => setShowCopySuccess(false)}
message="Link copied to clipboard"
/>

{hamburgerMenu}

Expand Down
39 changes: 21 additions & 18 deletions frontend/src/state/AppState.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {pct_fmt, setOp} from '../utils';
// import CircularProgress from '@mui/material/CircularProgress';
import {useDataCache} from './DataCache';
import {graphOptionsInitialState, graphOptionsReducer} from './GraphState';
// import Markdown from 'react-markdown';
// import { Inspector } from 'react-inspector';
import Markdown from 'react-markdown';
import { Inspector } from 'react-inspector';

export const NEW_CSET_ID = -1;

Expand Down Expand Up @@ -58,8 +58,8 @@ export const [GraphOptionsProvider, useGraphOptions] = makeProvider(
initialSettings: graphOptionsInitialState,
storageProviderGetter: useSessionStorage, });

export function resetReducers() {
Object.values(resetFuncs).forEach(f => f());
export function resetReducers({useStorageState = false}) {
Object.values(resetFuncs).forEach(f => f({useStorageState}));
}

export function ReducerProviders({children}) {
Expand Down Expand Up @@ -191,7 +191,17 @@ function makeProvider({stateName, reducer, initialSettings, storageProviderGette
}, [stateName, state]);
*/

const resetFunc = () => dispatch({type: 'reset', resetValue: initialSettings});
const resetFunc = ({useStorageState}) => {
let resetValue;
if (useStorageState) {
resetValue = storageProvider.getItem(stateName);
}
if (typeof(resetValue) === 'undefined') {
resetValue = cloneDeep(initialSettings);
}

dispatch({type: 'reset', resetValue});
};
resetFuncs[stateName] = resetFunc;

return (
Expand Down Expand Up @@ -362,28 +372,21 @@ export function getSessionStorage() {
delete sstorage.AI_sentBuffer;
return sstorage;
}
export function serializeSessionStorage({newCset} = {}) {
export function serializeSessionStorage() {
const sstorage = getSessionStorage();
newCset = newCset || sstorage.newCset || {};
if (newCset) {
newCset = {...newCset};
if (sstorage.newCset) {
let newCset = {...sstorage.newCset};
delete newCset.provenance; // it's a mess. not using for now
newCset.definitions = abbreviateDefinitions(newCset.definitions);
sstorage.newCset = newCset;
}
let sstorageString = JSON.stringify(sstorage);
/*
if (zip) {
// compressing doesn't do much when you have to uri it
sstorageString = compressToEncodedURIComponent(sstorageString);
}
*/
return sstorageString;
}

// TODO: this probably needs fixing after refactor
export function urlWithSessionStorage({newCset} = {}) {
const sstorageString = serializeSessionStorage({newCset});
export function urlWithSessionStorage() {
const sstorageString = serializeSessionStorage();
return window.location.href + (window.location.search ? '&' : '?') + `sstorage=${sstorageString}`;
}
export function newCsetProvenance(newCset) {
Expand Down Expand Up @@ -537,7 +540,6 @@ Goals:

export function ViewCurrentState () {
// Inspector not working in playwright tests, so disabling this component
return null;
const { sp } = useSearchParamsState();
// const alerts = useAlerts();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand All @@ -563,6 +565,7 @@ export function ViewCurrentState () {

<h2>dataCache</h2>
<Inspector data={dataCache}/>
<Inspector data={dataCache.getStats()}/>

<h2>The different kinds of state</h2>
<Markdown>{stateDoc}</Markdown>
Expand Down

0 comments on commit a83935a

Please sign in to comment.