mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Sidecar: Add local storage persistence for the sidecar state (#94820)
* Add local storage persistence * Fix arg name
This commit is contained in:
parent
ce3da025cc
commit
1b8b1d6c7a
@ -2,12 +2,23 @@ import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { config, locationService } from '@grafana/runtime';
|
||||
|
||||
interface Options {
|
||||
localStorageKey?: string;
|
||||
}
|
||||
|
||||
export class SidecarService {
|
||||
// The ID of the app plugin that is currently opened in the sidecar view
|
||||
private _activePluginId: BehaviorSubject<string | undefined>;
|
||||
private localStorageKey: string | undefined;
|
||||
|
||||
constructor() {
|
||||
this._activePluginId = new BehaviorSubject<string | undefined>(undefined);
|
||||
constructor(options: Options) {
|
||||
this.localStorageKey = options.localStorageKey;
|
||||
let initialId = undefined;
|
||||
if (this.localStorageKey) {
|
||||
initialId = localStorage.getItem(this.localStorageKey) || undefined;
|
||||
}
|
||||
|
||||
this._activePluginId = new BehaviorSubject<string | undefined>(initialId);
|
||||
}
|
||||
|
||||
private assertFeatureEnabled() {
|
||||
@ -28,6 +39,9 @@ export class SidecarService {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.localStorageKey) {
|
||||
localStorage.setItem(this.localStorageKey, pluginId);
|
||||
}
|
||||
return this._activePluginId.next(pluginId);
|
||||
}
|
||||
|
||||
@ -37,6 +51,9 @@ export class SidecarService {
|
||||
}
|
||||
|
||||
if (this._activePluginId.getValue() === pluginId) {
|
||||
if (this.localStorageKey) {
|
||||
localStorage.removeItem(this.localStorageKey);
|
||||
}
|
||||
return this._activePluginId.next(undefined);
|
||||
}
|
||||
}
|
||||
@ -54,7 +71,7 @@ export class SidecarService {
|
||||
}
|
||||
}
|
||||
|
||||
export const sidecarService = new SidecarService();
|
||||
export const sidecarService = new SidecarService({ localStorageKey: 'grafana.sidecar.activePluginId' });
|
||||
|
||||
// The app plugin that is "open" in the main Grafana view
|
||||
function getMainAppPluginId() {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { css } from '@emotion/css';
|
||||
import * as H from 'history';
|
||||
import { ComponentType } from 'react';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||
@ -15,6 +14,8 @@ import { ModalsContextProvider } from '../core/context/ModalsContextProvider';
|
||||
import { useSidecar } from '../core/context/SidecarContext';
|
||||
import AppRootPage from '../features/plugins/components/AppRootPage';
|
||||
|
||||
import { createLocationStorageHistory } from './utils';
|
||||
|
||||
type RouterWrapperProps = {
|
||||
routes?: JSX.Element | false;
|
||||
bodyRenderHooks: ComponentType[];
|
||||
@ -75,7 +76,9 @@ export function ExperimentalSplitPaneRouterWrapper(props: RouterWrapperProps) {
|
||||
|
||||
const headerHeight = useChromeHeaderHeight();
|
||||
const styles = useStyles2(getStyles, headerHeight);
|
||||
const memoryLocationService = new HistoryWrapper(H.createMemoryHistory({ initialEntries: ['/'] }));
|
||||
const memoryLocationService = new HistoryWrapper(
|
||||
createLocationStorageHistory({ storageKey: 'grafana.sidecar.history' })
|
||||
);
|
||||
|
||||
return (
|
||||
// Why do we need these 2 wrappers here? We want for one app case to render very similar as if there was no split
|
||||
|
@ -1,3 +1,6 @@
|
||||
import * as H from 'history';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
import { NavLinkDTO } from '@grafana/data';
|
||||
|
||||
export function isSoloRoute(path: string): boolean {
|
||||
@ -12,3 +15,85 @@ export function pluginHasRootPage(pluginId: string, navTree: NavLinkDTO[]): bool
|
||||
?.children?.some((page) => page.url?.endsWith(`/a/${pluginId}`))
|
||||
);
|
||||
}
|
||||
|
||||
type LocalStorageHistoryOptions = {
|
||||
storageKey: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple wrapper over the memory history that persists the location in the localStorage.
|
||||
* @param options
|
||||
*/
|
||||
export function createLocationStorageHistory(options: LocalStorageHistoryOptions): H.MemoryHistory {
|
||||
const storedLocation = localStorage.getItem(options.storageKey);
|
||||
const initialEntry = storedLocation ? JSON.parse(storedLocation) : '/';
|
||||
|
||||
const memoryHistory = H.createMemoryHistory({ initialEntries: [initialEntry] });
|
||||
|
||||
// We have to check whether location was actually changed by this way because the function don't actually offer
|
||||
// a return value that would tell us whether the change was successful or not and there are a few ways where the
|
||||
// actual location change could be blocked.
|
||||
let currentLocation = memoryHistory.location;
|
||||
function maybeUpdateLocation() {
|
||||
if (memoryHistory.location !== currentLocation) {
|
||||
localStorage.setItem(
|
||||
options.storageKey,
|
||||
JSON.stringify(pick(memoryHistory.location, 'pathname', 'search', 'hash'))
|
||||
);
|
||||
currentLocation = memoryHistory.location;
|
||||
}
|
||||
}
|
||||
|
||||
// This creates a sort of proxy over the memory location just to add the localStorage persistence. We could achieve
|
||||
// the same effect by a listener but that would create a memory leak as there would be no reasonable way to
|
||||
// unsubcribe the listener later on.
|
||||
return {
|
||||
get index() {
|
||||
return memoryHistory.index;
|
||||
},
|
||||
get entries() {
|
||||
return memoryHistory.entries;
|
||||
},
|
||||
canGo(n: number) {
|
||||
return memoryHistory.canGo(n);
|
||||
},
|
||||
get length() {
|
||||
return memoryHistory.length;
|
||||
},
|
||||
get action() {
|
||||
return memoryHistory.action;
|
||||
},
|
||||
get location() {
|
||||
return memoryHistory.location;
|
||||
},
|
||||
push(location: H.Path | H.LocationDescriptor<H.LocationState>, state?: H.LocationState) {
|
||||
memoryHistory.push(location, state);
|
||||
maybeUpdateLocation();
|
||||
},
|
||||
replace(location: H.Path | H.LocationDescriptor<H.LocationState>, state?: H.LocationState) {
|
||||
memoryHistory.replace(location, state);
|
||||
maybeUpdateLocation();
|
||||
},
|
||||
go(n: number) {
|
||||
memoryHistory.go(n);
|
||||
maybeUpdateLocation();
|
||||
},
|
||||
goBack() {
|
||||
memoryHistory.goBack();
|
||||
maybeUpdateLocation();
|
||||
},
|
||||
goForward() {
|
||||
memoryHistory.goForward();
|
||||
maybeUpdateLocation();
|
||||
},
|
||||
block(prompt?: boolean | string | H.TransitionPromptHook<H.LocationState>) {
|
||||
return memoryHistory.block(prompt);
|
||||
},
|
||||
listen(listener: H.LocationListener<H.LocationState>) {
|
||||
return memoryHistory.listen(listener);
|
||||
},
|
||||
createHref(location: H.LocationDescriptorObject<H.LocationState>) {
|
||||
return memoryHistory.createHref(location);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user