From fcaff011b1d27e3c8c19391184fc8cf6034a6fb4 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 4 Mar 2020 12:59:30 +0300 Subject: [PATCH] ShareModal: able to extend tabs (#22537) * ShareModal: use generic tab type * ShareModal: able to extend with custom tabs * ShareModal: able to extend dash/panel tabs separately * grafana-ui: ModalTabContent component --- .../src/components/Modal/ModalTabContent.tsx | 28 +++++ packages/grafana-ui/src/components/index.ts | 1 + public/app/core/services/keybindingSrv.ts | 2 +- .../dashboard/components/DashNav/DashNav.tsx | 2 +- .../components/ShareModal/ShareLink.tsx | 2 +- .../components/ShareModal/ShareModal.tsx | 103 ++++++++++++------ .../dashboard/components/ShareModal/index.ts | 2 + .../dashboard/components/ShareModal/types.ts | 17 +++ public/app/features/dashboard/utils/panel.ts | 2 +- 9 files changed, 123 insertions(+), 36 deletions(-) create mode 100644 packages/grafana-ui/src/components/Modal/ModalTabContent.tsx create mode 100644 public/app/features/dashboard/components/ShareModal/index.ts create mode 100644 public/app/features/dashboard/components/ShareModal/types.ts diff --git a/packages/grafana-ui/src/components/Modal/ModalTabContent.tsx b/packages/grafana-ui/src/components/Modal/ModalTabContent.tsx new file mode 100644 index 00000000000..8defc27f3ad --- /dev/null +++ b/packages/grafana-ui/src/components/Modal/ModalTabContent.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { IconType } from '../Icon/types'; +import { Icon } from '../Icon/Icon'; + +interface Props { + icon?: IconType; + iconClass?: string; +} + +export const ModalTabContent: React.FC = ({ icon, iconClass, children }) => { + let iconElem; + const showIcon = icon || iconClass; + if (iconClass) { + iconElem = ; + } + if (icon) { + iconElem = ; + } + + return ( +
+
+ {showIcon &&
{iconElem}
} +
{children}
+
+
+ ); +}; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 3f23c3c3908..e069bd21ad9 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -48,6 +48,7 @@ export { QueryField } from './QueryField/QueryField'; export { Modal } from './Modal/Modal'; export { ModalHeader } from './Modal/ModalHeader'; export { ModalTabsHeader } from './Modal/ModalTabsHeader'; +export { ModalTabContent } from './Modal/ModalTabContent'; export { ModalsProvider, ModalRoot, ModalsController } from './Modal/ModalsContext'; // Renderless diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index 317ab7e24c8..f9276560746 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -15,7 +15,7 @@ import { ILocationService, IRootScopeService, ITimeoutService } from 'angular'; import { GrafanaRootScope } from 'app/routes/GrafanaCtrl'; import { getLocationSrv } from '@grafana/runtime'; import { DashboardModel } from '../../features/dashboard/state'; -import { ShareModal } from 'app/features/dashboard/components/ShareModal/ShareModal'; +import { ShareModal } from 'app/features/dashboard/components/ShareModal'; import { SaveDashboardModalProxy } from '../../features/dashboard/components/SaveDashboard/SaveDashboardModalProxy'; export class KeybindingSrv { diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 851d42b00b4..a7dbd00784f 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -15,7 +15,7 @@ import { updateLocation } from 'app/core/actions'; // Types import { DashboardModel } from '../../state'; import { CoreEvents, StoreState } from 'app/types'; -import { ShareModal } from '../ShareModal/ShareModal'; +import { ShareModal } from 'app/features/dashboard/components/ShareModal'; import { SaveDashboardModalProxy } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardModalProxy'; export interface OwnProps { diff --git a/public/app/features/dashboard/components/ShareModal/ShareLink.tsx b/public/app/features/dashboard/components/ShareModal/ShareLink.tsx index 72cd6bff8ec..5e7c8941f47 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLink.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLink.tsx @@ -13,7 +13,7 @@ const themeOptions: Array> = [ ]; export interface Props { - dashboard?: DashboardModel; + dashboard: DashboardModel; panel?: PanelModel; } diff --git a/public/app/features/dashboard/components/ShareModal/ShareModal.tsx b/public/app/features/dashboard/components/ShareModal/ShareModal.tsx index 3cad4f0d583..efe68781a3f 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareModal.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareModal.tsx @@ -1,18 +1,61 @@ -import React, { PureComponent } from 'react'; +import React from 'react'; import { Modal, ModalTabsHeader, TabContent } from '@grafana/ui'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { ShareLink } from './ShareLink'; import { ShareSnapshot } from './ShareSnapshot'; import { ShareExport } from './ShareExport'; import { ShareEmbed } from './ShareEmbed'; +import { ShareModalTabModel } from './types'; -const shareModalTabs = [ - { label: 'Link', value: 'link' }, - { label: 'Embed', value: 'embed' }, - { label: 'Snapshot', value: 'snapshot' }, - { label: 'Export', value: 'export' }, +const shareCommonTabs: ShareModalTabModel[] = [ + { label: 'Link', value: 'link', component: ShareLink }, + { label: 'Snapshot', value: 'snapshot', component: ShareSnapshot }, ]; +// prettier-ignore +const shareDashboardTabs: ShareModalTabModel[] = [ + { label: 'Export', value: 'export', component: ShareExport }, +]; + +// prettier-ignore +const sharePanelTabs: ShareModalTabModel[] = [ + { label: 'Embed', value: 'embed', component: ShareEmbed }, +]; + +const customDashboardTabs: ShareModalTabModel[] = []; +const customPanelTabs: ShareModalTabModel[] = []; + +export function addDashboardShareTab(tab: ShareModalTabModel) { + customDashboardTabs.push(tab); +} + +export function addPanelShareTab(tab: ShareModalTabModel) { + customPanelTabs.push(tab); +} + +function getInitialState(props: Props): State { + const tabs = getTabs(props); + return { + tabs, + activeTab: tabs[0].value, + }; +} + +function getTabs(props: Props) { + const { panel } = props; + const tabs = [...shareCommonTabs]; + + if (panel) { + tabs.push(...sharePanelTabs); + tabs.push(...customPanelTabs); + } else { + tabs.push(...shareDashboardTabs); + tabs.push(...customDashboardTabs); + } + + return tabs; +} + interface Props { dashboard: DashboardModel; panel?: PanelModel; @@ -21,64 +64,60 @@ interface Props { } interface State { - tab: string; + tabs: ShareModalTabModel[]; + activeTab: string; } -function getInitialState(): State { - return { - tab: shareModalTabs[0].value, - }; -} - -export class ShareModal extends PureComponent { +export class ShareModal extends React.Component { constructor(props: Props) { super(props); - this.state = getInitialState(); + this.state = getInitialState(props); } onDismiss = () => { - this.setState(getInitialState()); + this.setState(getInitialState(this.props)); this.props.onDismiss(); }; onSelectTab = (t: any) => { - this.setState({ tab: t.value }); + this.setState({ activeTab: t.value }); }; getTabs() { - const { panel } = this.props; + return getTabs(this.props); + } - // Filter tabs for dashboard/panel share modal - return shareModalTabs.filter(t => { - if (panel) { - return t.value !== 'export'; - } - return t.value !== 'embed'; - }); + getActiveTab() { + const { tabs, activeTab } = this.state; + return tabs.find(t => t.value === activeTab); } renderTitle() { const { panel } = this.props; - const { tab } = this.state; + const { activeTab } = this.state; const title = panel ? 'Share Panel' : 'Share'; const tabs = this.getTabs(); return ( - + ); } render() { const { dashboard, panel } = this.props; - const { tab } = this.state; + const activeTabModel = this.getActiveTab(); + const ActiveTab = activeTabModel?.component; return ( - {tab === 'link' && } - {tab === 'embed' && panel && } - {tab === 'snapshot' && } - {tab === 'export' && !panel && } + ); diff --git a/public/app/features/dashboard/components/ShareModal/index.ts b/public/app/features/dashboard/components/ShareModal/index.ts new file mode 100644 index 00000000000..2cccea87893 --- /dev/null +++ b/public/app/features/dashboard/components/ShareModal/index.ts @@ -0,0 +1,2 @@ +export { ShareModal, addDashboardShareTab, addPanelShareTab } from './ShareModal'; +export * from './types'; diff --git a/public/app/features/dashboard/components/ShareModal/types.ts b/public/app/features/dashboard/components/ShareModal/types.ts new file mode 100644 index 00000000000..3627981b752 --- /dev/null +++ b/public/app/features/dashboard/components/ShareModal/types.ts @@ -0,0 +1,17 @@ +import React from 'react'; +import { PanelModel } from '@grafana/data'; +import { DashboardModel } from 'app/features/dashboard/state'; + +export interface ShareModalTabProps { + dashboard: DashboardModel; + panel?: PanelModel; + onDismiss?(): void; +} + +export type ShareModalTab = React.ComponentType; + +export interface ShareModalTabModel { + label: string; + value: string; + component: ShareModalTab; +} diff --git a/public/app/features/dashboard/utils/panel.ts b/public/app/features/dashboard/utils/panel.ts index e59da14307d..ddc3bc1ddef 100644 --- a/public/app/features/dashboard/utils/panel.ts +++ b/public/app/features/dashboard/utils/panel.ts @@ -20,7 +20,7 @@ import templateSrv from 'app/features/templating/template_srv'; import { LS_PANEL_COPY_KEY, PANEL_BORDER } from 'app/core/constants'; import { CoreEvents } from 'app/types'; -import { ShareModal } from 'app/features/dashboard/components/ShareModal/ShareModal'; +import { ShareModal } from 'app/features/dashboard/components/ShareModal'; export const removePanel = (dashboard: DashboardModel, panel: PanelModel, ask: boolean) => { // confirm deletion