import { css } from '@emotion/css'; import { memo, ReactNode, useEffect, useId, useState } from 'react'; import { GrafanaTheme2, store } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { config, locationService } from '@grafana/runtime'; import { Badge, Button, ButtonGroup, Dropdown, Icon, InlineLabel, Menu, Stack, Switch, ToolbarButton, ToolbarButtonRow, useStyles2, } from '@grafana/ui'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator'; import { LS_PANEL_COPY_KEY } from 'app/core/constants'; import { contextSrv } from 'app/core/core'; import { Trans, t } from 'app/core/internationalization'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { ScopesSelector } from 'app/features/scopes'; import { shareDashboardType } from '../../dashboard/components/ShareModal/utils'; import { PanelEditor, buildPanelEditScene } from '../panel-edit/PanelEditor'; import ExportButton from '../sharing/ExportButton/ExportButton'; import ShareButton from '../sharing/ShareButton/ShareButton'; import { DashboardInteractions } from '../utils/interactions'; import { DynamicDashNavButtonModel, dynamicDashNavActions } from '../utils/registerDynamicDashNavAction'; import { isLibraryPanel } from '../utils/utils'; import { DashboardScene } from './DashboardScene'; import { GoToSnapshotOriginButton } from './GoToSnapshotOriginButton'; interface Props { dashboard: DashboardScene; } export const NavToolbarActions = memo(({ dashboard }) => { const id = useId(); const actions = ; return ; }); NavToolbarActions.displayName = 'NavToolbarActions'; /** * This part is split into a separate component to help test this */ export function ToolbarActions({ dashboard }: Props) { const { isEditing, showHiddenElements, viewPanelScene, isDirty, uid, meta, editview, editPanel, editable } = dashboard.useState(); const { isPlaying } = playlistSrv.useState(); const [isAddPanelMenuOpen, setIsAddPanelMenuOpen] = useState(false); const canSaveAs = contextSrv.hasEditPermissionInFolders; const toolbarActions: ToolbarAction[] = []; const leftActions: ToolbarAction[] = []; const styles = useStyles2(getStyles); const isEditingPanel = Boolean(editPanel); const isViewingPanel = Boolean(viewPanelScene); const isEditedPanelDirty = usePanelEditDirty(editPanel); const isEditingLibraryPanel = editPanel && isLibraryPanel(editPanel.state.panelRef.resolve()); const isNew = !Boolean(uid); const hasCopiedPanel = store.exists(LS_PANEL_COPY_KEY); // Means we are not in settings view, fullscreen panel or edit panel const isShowingDashboard = !editview && !isViewingPanel && !isEditingPanel; const isEditingAndShowingDashboard = isEditing && isShowingDashboard; const showScopesSelector = config.featureToggles.scopeFilters && !isEditing; const dashboardNewLayouts = config.featureToggles.dashboardNewLayouts; if (!isEditingPanel) { // This adds the presence indicators in enterprise addDynamicActions(toolbarActions, dynamicDashNavActions.left, 'left-actions'); } toolbarActions.push({ group: 'icon-actions', condition: uid && Boolean(meta.canStar) && isShowingDashboard && !isEditing, render: () => { let desc = meta.isStarred ? t('dashboard.toolbar.unmark-favorite', 'Unmark as favorite') : t('dashboard.toolbar.mark-favorite', 'Mark as favorite'); return ( } key="star-dashboard-button" data-testid={selectors.components.NavToolbar.markAsFavorite} onClick={() => { DashboardInteractions.toolbarFavoritesClick(); dashboard.onStarDashboard(); }} /> ); }, }); if (meta.publicDashboardEnabled) { toolbarActions.push({ group: 'icon-actions', condition: uid && Boolean(meta.canStar) && isShowingDashboard && !isEditing, render: () => { return ( ); }, }); } const isDevEnv = config.buildInfo.env === 'development'; toolbarActions.push({ group: 'icon-actions', condition: isDevEnv && uid && isShowingDashboard && !isEditing, render: () => ( { locationService.partial({ scenes: false }); }} /> ), }); toolbarActions.push({ group: 'icon-actions', condition: meta.isSnapshot && !isEditing, render: () => ( ), }); if (!isEditingPanel && !isEditing) { // This adds the alert rules button and the dashboard insights button addDynamicActions(toolbarActions, dynamicDashNavActions.right, 'icon-actions'); } if (dashboardNewLayouts) { leftActions.push({ group: 'hidden-elements', condition: isEditingAndShowingDashboard, render: () => ( { evt.stopPropagation(); dashboard.onToggleHiddenElements(); }} data-testid={selectors.components.PageToolbar.itemButton('toggle_hidden_elements')} /> Show hidden ), }); } else { toolbarActions.push({ group: 'add-panel', condition: isEditingAndShowingDashboard, render: () => ( { setIsAddPanelMenuOpen(isOpen); DashboardInteractions.toolbarAddClick(); }} overlay={() => ( { const vizPanel = dashboard.onCreateNewPanel(); DashboardInteractions.toolbarAddButtonClicked({ item: 'add_visualization' }); dashboard.setState({ editPanel: buildPanelEditScene(vizPanel, true) }); }} /> { dashboard.onShowAddLibraryPanelDrawer(); DashboardInteractions.toolbarAddButtonClicked({ item: 'add_library_panel' }); }} /> { dashboard.onCreateNewRow(); DashboardInteractions.toolbarAddButtonClicked({ item: 'add_row' }); }} /> { dashboard.pastePanel(); DashboardInteractions.toolbarAddButtonClicked({ item: 'paste_panel' }); }} /> )} placement="bottom" offset={[0, 6]} > ), }); } toolbarActions.push({ group: 'playlist-actions', condition: isPlaying && isShowingDashboard && !isEditing, render: () => ( playlistSrv.prev()} /> ), }); toolbarActions.push({ group: 'playlist-actions', condition: isPlaying && isShowingDashboard && !isEditing, render: () => ( playlistSrv.stop()} data-testid={selectors.pages.Dashboard.DashNav.playlistControls.stop} > Stop playlist ), }); toolbarActions.push({ group: 'playlist-actions', condition: isPlaying && isShowingDashboard && !isEditing, render: () => ( playlistSrv.next()} narrow /> ), }); toolbarActions.push({ group: 'back-button', condition: (isViewingPanel || isEditingPanel) && !isEditingLibraryPanel, render: () => ( ), }); toolbarActions.push({ group: 'back-button', condition: Boolean(editview), render: () => ( ), }); const showShareButton = uid && !isEditing && !meta.isSnapshot && !isPlaying; toolbarActions.push({ group: 'main-buttons', condition: !config.featureToggles.newDashboardSharingComponent && showShareButton, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: !isEditing && dashboard.canEditDashboard() && !isViewingPanel && !isPlaying && editable, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: !isEditing && dashboard.canEditDashboard() && !isViewingPanel && !isPlaying && !editable, render: () => ( ), }); toolbarActions.push({ group: 'new-share-dashboard-buttons', condition: config.featureToggles.newDashboardSharingComponent && showShareButton, render: () => , }); toolbarActions.push({ group: 'new-share-dashboard-buttons', condition: config.featureToggles.newDashboardSharingComponent && showShareButton, render: () => , }); toolbarActions.push({ group: 'settings', condition: isEditing && dashboard.canEditDashboard() && isShowingDashboard, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: isEditing && !isNew && isShowingDashboard, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: isEditingPanel && !isEditingLibraryPanel && !editview && !isViewingPanel, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: isEditingPanel && isEditingLibraryPanel && !editview && !isViewingPanel, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: isEditingPanel && isEditingLibraryPanel && !editview && !isViewingPanel, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: isEditingPanel && isEditingLibraryPanel && !editview && !isViewingPanel, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: isEditing && !isEditingLibraryPanel && (meta.canSave || canSaveAs), render: () => { // if we only can save if (isNew) { return ( ); } // If we only can save as copy if (canSaveAs && !meta.canSave && !meta.canMakeEditable) { return ( ); } // If we can do both save and save as copy we show a button group with dropdown menu const menu = ( { dashboard.openSaveDrawer({}); }} /> { dashboard.openSaveDrawer({ saveAsCopy: true }); }} /> ); return (