diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index b3efafc9e39..05dc6a57c1e 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -196,6 +196,17 @@ export const DashNav = React.memo((props) => { ); } + if (config.featureToggles.scenes) { + buttons.push( + locationService.push(`/scenes/dashboard/${dashboard.uid}`)} + /> + ); + } + addCustomContent(customLeftActions, buttons); return buttons; }; @@ -310,16 +321,6 @@ export const DashNav = React.memo((props) => { buttons.push(renderTimeControls()); - if (config.featureToggles.scenes) { - buttons.push( - locationService.push(`/scenes/dashboard/${dashboard.uid}`)} - /> - ); - } return buttons; }; diff --git a/public/app/features/scenes/dashboard/DashboardScene.tsx b/public/app/features/scenes/dashboard/DashboardScene.tsx index c37f000c4a4..203bf398cb8 100644 --- a/public/app/features/scenes/dashboard/DashboardScene.tsx +++ b/public/app/features/scenes/dashboard/DashboardScene.tsx @@ -1,72 +1,55 @@ -import { css } from '@emotion/css'; -import React from 'react'; +import { + getUrlSyncManager, + SceneGridItem, + SceneObject, + SceneObjectBase, + SceneObjectState, + SceneObjectStateChangedEvent, +} from '@grafana/scenes'; -import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; -import { locationService } from '@grafana/runtime'; -import { SceneObjectBase, SceneComponentProps, SceneObject, SceneObjectState } from '@grafana/scenes'; -import { ToolbarButton, useStyles2 } from '@grafana/ui'; -import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; -import { Page } from 'app/core/components/Page/Page'; +import { DashboardSceneRenderer } from './DashboardSceneRenderer'; -interface DashboardSceneState extends SceneObjectState { +export interface DashboardSceneState extends SceneObjectState { title: string; uid?: string; body: SceneObject; actions?: SceneObject[]; controls?: SceneObject[]; + isEditing?: boolean; + isDirty?: boolean; } export class DashboardScene extends SceneObjectBase { static Component = DashboardSceneRenderer; -} -function DashboardSceneRenderer({ model }: SceneComponentProps) { - const { title, body, actions = [], uid, controls } = model.useState(); - const styles = useStyles2(getStyles); + constructor(state: DashboardSceneState) { + super(state); - const toolbarActions = (actions ?? []).map((action) => ); + this.addActivationHandler(() => { + return () => getUrlSyncManager().cleanUp(this); + }); - if (uid?.length) { - toolbarActions.push( - locationService.push(`/d/${uid}`)} - tooltip="View as Dashboard" - key="scene-to-dashboard-switch" - /> - ); + this.subscribeToEvent(SceneObjectStateChangedEvent, this.onChildStateChanged); } - return ( - - - {controls && ( -
- {controls.map((control) => ( - - ))} -
- )} -
- -
-
- ); -} + onChildStateChanged = (event: SceneObjectStateChangedEvent) => { + // Temporary hacky way to detect changes + if (event.payload.changedObject instanceof SceneGridItem) { + this.setState({ isDirty: true }); + } + }; -function getStyles(theme: GrafanaTheme2) { - return { - body: css({ - flexGrow: 1, - display: 'flex', - gap: '8px', - }), - controls: css({ - display: 'flex', - paddingBottom: theme.spacing(2), - flexWrap: 'wrap', - alignItems: 'center', - gap: theme.spacing(1), - }), + initUrlSync() { + getUrlSyncManager().initSync(this); + } + + onEnterEditMode = () => { + this.setState({ isEditing: true }); + }; + + onDiscard = () => { + // TODO open confirm modal if dirty + // TODO actually discard changes + this.setState({ isEditing: false }); }; } diff --git a/public/app/features/scenes/dashboard/DashboardSceneRenderer.tsx b/public/app/features/scenes/dashboard/DashboardSceneRenderer.tsx new file mode 100644 index 00000000000..3ba0e0adc21 --- /dev/null +++ b/public/app/features/scenes/dashboard/DashboardSceneRenderer.tsx @@ -0,0 +1,114 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; +import { SceneComponentProps } from '@grafana/scenes'; +import { Button, CustomScrollbar, useStyles2 } from '@grafana/ui'; +import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; +import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator'; +import { Page } from 'app/core/components/Page/Page'; +import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton'; + +import { DashboardScene } from './DashboardScene'; + +export function DashboardSceneRenderer({ model }: SceneComponentProps) { + const { title, body, actions = [], controls, isEditing, isDirty, uid } = model.useState(); + const styles = useStyles2(getStyles); + const toolbarActions = (actions ?? []).map((action) => ); + + if (uid) { + toolbarActions.push( + locationService.push(`/d/${uid}`)} + /> + ); + } + + toolbarActions.push(); + + if (!isEditing) { + // TODO check permissions + toolbarActions.push( + + ); + } else { + // TODO check permissions + toolbarActions.push( + + ); + toolbarActions.push( + + ); + toolbarActions.push( + + ); + } + + return ( + + +
+ + {controls && ( +
+ {controls.map((control) => ( + + ))} +
+ )} +
+ +
+
+
+
+ ); +} + +function getStyles(theme: GrafanaTheme2) { + return { + canvasContent: css({ + label: 'canvas-content', + display: 'flex', + flexDirection: 'column', + padding: theme.spacing(0, 2), + flexBasis: '100%', + flexGrow: 1, + }), + body: css({ + flexGrow: 1, + display: 'flex', + gap: '8px', + }), + controls: css({ + display: 'flex', + flexWrap: 'wrap', + alignItems: 'center', + gap: theme.spacing(1), + position: 'sticky', + top: 0, + background: theme.colors.background.canvas, + zIndex: 1, + padding: theme.spacing(2, 0), + }), + }; +} diff --git a/public/app/features/scenes/dashboard/DashboardsLoader.ts b/public/app/features/scenes/dashboard/DashboardsLoader.ts index 2d10839abf7..bca6a8f5f1b 100644 --- a/public/app/features/scenes/dashboard/DashboardsLoader.ts +++ b/public/app/features/scenes/dashboard/DashboardsLoader.ts @@ -24,6 +24,8 @@ import { SceneGridItem, SceneDataProvider, getUrlSyncManager, + SceneObject, + SceneControlsSpacer, } from '@grafana/scenes'; import { StateManagerBase } from 'app/core/services/StateManagerBase'; import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; @@ -177,6 +179,16 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) }); } + const controls: SceneObject[] = [ + new VariableValueSelectors({}), + new SceneControlsSpacer(), + new SceneTimePicker({}), + new SceneRefreshPicker({ + refresh: oldModel.refresh, + intervals: oldModel.timepicker.refresh_intervals, + }), + ]; + return new DashboardScene({ title: oldModel.title, uid: oldModel.uid, @@ -184,17 +196,8 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) children: createSceneObjectsForPanels(oldModel.panels), }), $timeRange: new SceneTimeRange(oldModel.time), - actions: [ - new SceneTimePicker({}), - new SceneRefreshPicker({ - refresh: oldModel.refresh, - intervals: oldModel.timepicker.refresh_intervals, - }), - ], $variables: variables, - ...(variables && { - controls: [new VariableValueSelectors({})], - }), + controls: controls, }); }