Tabs: Support for nested url sync (#102670)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { sceneGraph, SceneObject, SceneObjectBase, SceneObjectState, VariableDependencyConfig } from '@grafana/scenes';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
|
||||
import { ConditionalRendering } from '../../conditional-rendering/ConditionalRendering';
|
||||
@@ -72,6 +73,10 @@ export class RowItem
|
||||
return this.state.layout;
|
||||
}
|
||||
|
||||
public getSlug(): string {
|
||||
return kbn.slugifyForUrl(sceneGraph.interpolate(this, this.state.title ?? 'Row'));
|
||||
}
|
||||
|
||||
public switchLayout(layout: DashboardLayoutManager) {
|
||||
this.setState({ layout: this._layoutRestorer.getLayout(layout, this.state.layout) });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { SceneObjectState, SceneObjectBase, sceneGraph, VariableDependencyConfig, SceneObject } from '@grafana/scenes';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
|
||||
import { getDefaultVizPanel } from '../../utils/utils';
|
||||
@@ -53,6 +54,10 @@ export class TabItem
|
||||
return this.state.layout;
|
||||
}
|
||||
|
||||
public getSlug(): string {
|
||||
return kbn.slugifyForUrl(sceneGraph.interpolate(this, this.state.title ?? 'Tab'));
|
||||
}
|
||||
|
||||
public switchLayout(layout: DashboardLayoutManager) {
|
||||
this.setState({ layout: this._layoutRestorer.getLayout(layout, this.state.layout) });
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@ export function TabItemRenderer({ model }: SceneComponentProps<TabItem>) {
|
||||
const { tabs, currentTabIndex } = parentLayout.useState();
|
||||
const titleInterpolated = sceneGraph.interpolate(model, title, undefined, 'text');
|
||||
const { isSelected, onSelect, isSelectable } = useElementSelection(key);
|
||||
const mySlug = model.getSlug();
|
||||
const urlKey = parentLayout.getUrlKey();
|
||||
const myIndex = tabs.findIndex((tab) => tab === model);
|
||||
const isActive = myIndex === currentTabIndex;
|
||||
const location = useLocation();
|
||||
const href = textUtil.sanitize(locationUtil.getUrlForPartial(location, { tab: myIndex }));
|
||||
const href = textUtil.sanitize(locationUtil.getUrlForPartial(location, { [urlKey]: mySlug }));
|
||||
|
||||
return (
|
||||
<Tab
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { RowItem } from '../layout-rows/RowItem';
|
||||
import { RowsLayoutManager } from '../layout-rows/RowsLayoutManager';
|
||||
|
||||
import { TabItem } from './TabItem';
|
||||
import { TabsLayoutManager } from './TabsLayoutManager';
|
||||
|
||||
describe('TabsLayoutManager', () => {
|
||||
describe('url sync', () => {
|
||||
it('when on top level', () => {
|
||||
const tabsLayoutManager = new TabsLayoutManager({
|
||||
tabs: [new TabItem({ title: 'Performance' })],
|
||||
});
|
||||
|
||||
const urlState = tabsLayoutManager.getUrlState();
|
||||
expect(urlState).toEqual({ dtab: 'performance' });
|
||||
});
|
||||
|
||||
it('when nested under row and parent tab', () => {
|
||||
const innerMostTabs = new TabsLayoutManager({
|
||||
tabs: [new TabItem({ title: 'Performance' })],
|
||||
});
|
||||
|
||||
new RowsLayoutManager({
|
||||
rows: [
|
||||
new RowItem({
|
||||
title: 'Overview',
|
||||
layout: new TabsLayoutManager({
|
||||
tabs: [
|
||||
new TabItem({
|
||||
title: 'Frontend',
|
||||
layout: innerMostTabs,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const urlState = innerMostTabs.getUrlState();
|
||||
expect(urlState).toEqual({
|
||||
['overview-frontend-dtab']: 'performance',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ObjectRemovedFromCanvasEvent,
|
||||
ObjectsReorderedOnCanvasEvent,
|
||||
} from '../../edit-pane/shared';
|
||||
import { RowItem } from '../layout-rows/RowItem';
|
||||
import { RowsLayoutManager } from '../layout-rows/RowsLayoutManager';
|
||||
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
|
||||
import { LayoutRegistryItem } from '../types/LayoutRegistryItem';
|
||||
@@ -44,7 +45,7 @@ export class TabsLayoutManager extends SceneObjectBase<TabsLayoutManagerState> i
|
||||
|
||||
public readonly descriptor = TabsLayoutManager.descriptor;
|
||||
|
||||
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['tab'] });
|
||||
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: () => [this.getUrlKey()] });
|
||||
|
||||
public constructor(state: Partial<TabsLayoutManagerState>) {
|
||||
super({
|
||||
@@ -65,19 +66,23 @@ export class TabsLayoutManager extends SceneObjectBase<TabsLayoutManagerState> i
|
||||
}
|
||||
|
||||
public getUrlState() {
|
||||
return { tab: this.state.currentTabIndex.toString() };
|
||||
const key = this.getUrlKey();
|
||||
return { [key]: this.getCurrentTab().getSlug() };
|
||||
}
|
||||
|
||||
public updateFromUrl(values: SceneObjectUrlValues) {
|
||||
if (!values.tab) {
|
||||
const key = this.getUrlKey();
|
||||
const urlValue = values[key];
|
||||
|
||||
if (!urlValue) {
|
||||
return;
|
||||
}
|
||||
if (typeof values.tab === 'string') {
|
||||
const tabIndex = parseInt(values.tab, 10);
|
||||
if (this.state.tabs[tabIndex]) {
|
||||
this.setState({ currentTabIndex: tabIndex });
|
||||
} else {
|
||||
this.setState({ currentTabIndex: 0 });
|
||||
|
||||
if (typeof values[key] === 'string') {
|
||||
// find tab with matching slug
|
||||
const matchIndex = this.state.tabs.findIndex((tab) => tab.getSlug() === urlValue);
|
||||
if (matchIndex !== -1) {
|
||||
this.setState({ currentTabIndex: matchIndex });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,4 +217,24 @@ export class TabsLayoutManager extends SceneObjectBase<TabsLayoutManagerState> i
|
||||
|
||||
return new TabsLayoutManager({ tabs });
|
||||
}
|
||||
|
||||
getUrlKey(): string {
|
||||
let parent = this.parent;
|
||||
// Panel edit uses tab key already so we are using dtab here to not conflict
|
||||
let key = 'dtab';
|
||||
|
||||
while (parent) {
|
||||
if (parent instanceof TabItem) {
|
||||
key = `${parent.getSlug()}-${key}`;
|
||||
}
|
||||
|
||||
if (parent instanceof RowItem) {
|
||||
key = `${parent.getSlug()}-${key}`;
|
||||
}
|
||||
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user