diff --git a/libs/sdk-ui-dashboard/api/sdk-ui-dashboard.api.md b/libs/sdk-ui-dashboard/api/sdk-ui-dashboard.api.md index 161ed87f9dd..e0b764ea873 100644 --- a/libs/sdk-ui-dashboard/api/sdk-ui-dashboard.api.md +++ b/libs/sdk-ui-dashboard/api/sdk-ui-dashboard.api.md @@ -170,6 +170,7 @@ import { IDashboardInsightProps as IDashboardInsightProps_2 } from './types.js'; import { IDashboardKpiProps as IDashboardKpiProps_2 } from './types.js'; import { IDashboardLayout } from '@gooddata/sdk-model'; import { IDashboardLayoutItem } from '@gooddata/sdk-model'; +import { IDashboardLayoutProps as IDashboardLayoutProps_2 } from './types.js'; import { IDashboardLayoutSection } from '@gooddata/sdk-model'; import { IDashboardLayoutSectionHeader } from '@gooddata/sdk-model'; import { IDashboardLayoutSizeByScreenSize } from '@gooddata/sdk-model'; @@ -1226,6 +1227,9 @@ export type CustomDashboardKpiComponent = ComponentType; // @alpha (undocumented) export type CustomDashboardLayoutComponent = ComponentType; +// @alpha (undocumented) +export type CustomDashboardNestedLayoutComponent = ComponentType; + // @public (undocumented) export type CustomDashboardRichTextComponent = ComponentType; @@ -2404,6 +2408,28 @@ export interface DashboardLayoutChangedPayload { // @beta (undocumented) export type DashboardLayoutCommands = AddLayoutSection | MoveLayoutSection | RemoveLayoutSection | ChangeLayoutSectionHeader | AddSectionItems | MoveSectionItem | RemoveSectionItem | RemoveSectionItemByWidgetRef | ResizeHeight; +// @alpha (undocumented) +export type DashboardLayoutComponentProvider = (widget: IDashboardLayout) => CustomDashboardNestedLayoutComponent; + +// @internal (undocumented) +export type DashboardLayoutDraggableComponent = { + DraggingComponent?: DashboardLayoutDraggingComponent; + type: "dashboardLayout"; +}; + +// @internal (undocumented) +export type DashboardLayoutDraggableItem = BaseDraggableMovingItem & { + type: "dashboardLayout"; +}; + +// @internal (undocumented) +export type DashboardLayoutDraggableListItem = BaseDraggableLayoutItem & { + type: "dashboardLayoutListItem"; +}; + +// @internal (undocumented) +export type DashboardLayoutDraggingComponent = ComponentType; + // @beta export interface DashboardLayoutSectionAdded extends IDashboardEvent { // (undocumented) @@ -2530,6 +2556,9 @@ export interface DashboardLayoutSectionRemovedPayload { readonly stashIdentifier?: StashedDashboardItemsId; } +// @internal +export type DashboardLayoutWidgetComponentSet = CustomComponentBase> & DraggableComponent & Partial & Partial> & ConfigurableWidget; + // @beta (undocumented) export interface DashboardMetaState { descriptor?: DashboardDescriptor; @@ -3062,9 +3091,15 @@ export const DefaultDashboardKpiPlaceholderWidget: CustomDashboardWidgetComponen // @alpha (undocumented) export const DefaultDashboardLayout: (props: IDashboardLayoutProps) => JSX.Element; +// @internal (undocumented) +export function DefaultDashboardLayoutComponentSetFactory(dashboardLayoutComponentProvider: DashboardLayoutComponentProvider): DashboardLayoutWidgetComponentSet; + // @internal (undocumented) export const DefaultDashboardMainContent: (_: IDashboardProps) => React_2.JSX.Element; +// @public +export const DefaultDashboardNestedLayout: ComponentType; + // @public export const DefaultDashboardRichText: ComponentType; @@ -3202,14 +3237,14 @@ export function dispatchAndWaitFor( // @internal export type DraggableComponent = { - dragging: AttributeFilterDraggableComponent | DateFilterDraggableComponent | KpiDraggableComponent | InsightDraggableComponent | RichTextDraggableComponent | VisualizationSwitcherDraggableComponent | CustomDraggableComponent; + dragging: AttributeFilterDraggableComponent | DateFilterDraggableComponent | KpiDraggableComponent | InsightDraggableComponent | RichTextDraggableComponent | VisualizationSwitcherDraggableComponent | DashboardLayoutDraggableComponent | CustomDraggableComponent; }; // @internal (undocumented) -export type DraggableContentItem = AttributeFilterDraggableItem | AttributeFilterPlaceholderDraggableItem | DateFilterDraggableItem | InsightDraggableItem | InsightDraggableListItem | InsightPlaceholderDraggableItem | KpiDraggableItem | KpiPlaceholderDraggableItem | RichTextDraggableItem | RichTextDraggableListItem | VisualizationSwitcherDraggableItem | VisualizationSwitcherDraggableListItem | CustomWidgetDraggableItem | CustomDraggableItem; +export type DraggableContentItem = AttributeFilterDraggableItem | AttributeFilterPlaceholderDraggableItem | DateFilterDraggableItem | InsightDraggableItem | InsightDraggableListItem | InsightPlaceholderDraggableItem | KpiDraggableItem | KpiPlaceholderDraggableItem | RichTextDraggableItem | RichTextDraggableListItem | VisualizationSwitcherDraggableItem | VisualizationSwitcherDraggableListItem | DashboardLayoutDraggableItem | DashboardLayoutDraggableListItem | CustomWidgetDraggableItem | CustomDraggableItem; // @internal (undocumented) -export type DraggableContentItemType = "attributeFilter" | "dateFilter" | "attributeFilter-placeholder" | "insightListItem" | "insight" | "insight-placeholder" | "kpi" | "kpi-placeholder" | "richText" | "richTextListItem" | "visualizationSwitcher" | "visualizationSwitcherListItem" | "custom"; +export type DraggableContentItemType = "attributeFilter" | "dateFilter" | "attributeFilter-placeholder" | "insightListItem" | "insight" | "insight-placeholder" | "kpi" | "kpi-placeholder" | "richText" | "richTextListItem" | "visualizationSwitcher" | "visualizationSwitcherListItem" | "dashboardLayout" | "dashboardLayoutListItem" | "custom"; // @internal (undocumented) export const DraggableCreatePanelItem: React_2.FC; @@ -3237,6 +3272,8 @@ export type DraggableItemComponentTypeMapping = { richTextListItem: RichTextDraggableListItem; visualizationSwitcher: VisualizationSwitcherDraggableItem; visualizationSwitcherListItem: VisualizationSwitcherDraggableListItem; + dashboardLayout: DashboardLayoutDraggableItem; + dashboardLayoutListItem: DashboardLayoutDraggableListItem; custom: CustomDraggableItem; }; @@ -3253,7 +3290,7 @@ export type DraggableItemType = DraggableContentItemType | DraggableInternalItem export type DraggableItemTypeMapping = DraggableItemComponentTypeMapping & DraggableItemInternalTypeMapping; // @internal (undocumented) -export type DraggableLayoutItem = InsightDraggableItem | KpiDraggableItem | RichTextDraggableItem | VisualizationSwitcherDraggableItem | CustomWidgetDraggableItem; +export type DraggableLayoutItem = InsightDraggableItem | KpiDraggableItem | RichTextDraggableItem | VisualizationSwitcherDraggableItem | DashboardLayoutDraggableItem | CustomWidgetDraggableItem; // @internal (undocumented) export interface DraggingComponentProps { @@ -4012,6 +4049,7 @@ export interface IDashboardCustomComponentProps { DashboardContentComponentProvider?: OptionalDashboardContentComponentProvider; // @alpha DashboardDateFilterComponentProvider?: OptionalDateFilterComponentProvider; + DashboardLayoutComponentProvider?: OptionalDashboardLayoutComponentProvider; // @internal EmptyLayoutDropZoneBodyComponent?: CustomEmptyLayoutDropZoneBodyComponent; // @alpha @@ -4296,6 +4334,12 @@ export interface IDashboardLayoutCustomizer { customizeFluidLayout(fun: FluidLayoutCustomizationFn): IDashboardLayoutCustomizer; } +// @internal (undocumented) +export type IDashboardLayoutDraggingComponentProps = { + itemType: "dashboardLayout"; + item: DashboardLayoutDraggableItem; +}; + // @alpha (undocumented) export interface IDashboardLayoutProps { // (undocumented) @@ -4308,6 +4352,28 @@ export interface IDashboardLayoutProps { onFiltersChange?: (filters: (IDashboardFilter | FilterContextItem)[], resetOthers?: boolean) => void; } +// @alpha (undocumented) +export interface IDashboardNestedLayoutProps { + backend?: IAnalyticalBackend; + clientHeight?: number; + clientWidth?: number; + // (undocumented) + ErrorComponent?: React.ComponentType; + // (undocumented) + layout?: IDashboardLayout; + // (undocumented) + onDrill?: OnFiredDashboardDrillEvent; + // (undocumented) + onError?: OnError; + // (undocumented) + onFiltersChange?: (filters: (IDashboardFilter | FilterContextItem)[], resetOthers?: boolean) => void; + // (undocumented) + parentLayoutItemSize?: IDashboardLayoutSizeByScreenSize; + // (undocumented) + screen?: ScreenSize; + workspace?: string; +} + // @public export interface IDashboardPluginContract_V1 extends DashboardPluginDescriptor { onPluginLoaded?(ctx: DashboardContext, parameters?: string): Promise | void; @@ -5397,6 +5463,12 @@ export const isDashboardKpiWidgetMeasureChanged: (obj: unknown) => obj is Dashbo // @beta export const isDashboardLayoutChanged: (obj: unknown) => obj is DashboardLayoutChanged; +// @internal (undocumented) +export function isDashboardLayoutDraggableItem(item: any): item is DashboardLayoutDraggableItem; + +// @internal (undocumented) +export function isDashboardLayoutDraggableListItem(item: any): item is DashboardLayoutDraggableListItem; + // @beta export const isDashboardLayoutSectionAdded: (obj: unknown) => obj is DashboardLayoutSectionAdded; @@ -5552,6 +5624,8 @@ export interface ISidebarProps { // @internal AttributeFilterComponentSet?: AttributeFilterComponentSet; configurationPanelClassName?: string; + // @internal + DashboardLayoutWidgetComponentSet?: DashboardLayoutWidgetComponentSet; DefaultSidebar: ComponentType; // @internal DeleteDropZoneComponent?: React.ComponentType; @@ -6151,6 +6225,9 @@ export type OptionalAttributeFilterComponentProvider = OptionalProvider; +// @alpha (undocumented) +export type OptionalDashboardLayoutComponentProvider = OptionalProvider; + // @public (undocumented) export type OptionalDateFilterComponentProvider = OptionalProvider; diff --git a/libs/sdk-ui-dashboard/src/presentation/componentDefinition/types.ts b/libs/sdk-ui-dashboard/src/presentation/componentDefinition/types.ts index 0f2d60e5f21..8be6a4c6fec 100644 --- a/libs/sdk-ui-dashboard/src/presentation/componentDefinition/types.ts +++ b/libs/sdk-ui-dashboard/src/presentation/componentDefinition/types.ts @@ -11,10 +11,12 @@ import { IDashboardRichTextProps, IDashboardVisualizationSwitcherProps, IDashboardWidgetProps, + IDashboardNestedLayoutProps, } from "../widget/types.js"; import { AttributeFilterDraggableItem, CustomDraggableItem, + DashboardLayoutDraggableItem, DateFilterDraggableItem, DraggableContentItemType, IWrapCreatePanelItemWithDragComponent, @@ -31,8 +33,10 @@ import { RichTextComponentProvider, WidgetComponentProvider, VisualizationSwitcherComponentProvider, + DashboardLayoutComponentProvider, } from "../dashboardContexts/types.js"; import { + IDashboardLayout, IInsightWidget, IKpiWidget, IRichTextWidget, @@ -114,6 +118,14 @@ export type IVisualizationSwitcherDraggingComponentProps = { item: VisualizationSwitcherDraggableItem; }; +/** + * @internal + */ +export type IDashboardLayoutDraggingComponentProps = { + itemType: "dashboardLayout"; + item: DashboardLayoutDraggableItem; +}; + /** * @internal */ @@ -153,6 +165,11 @@ export type RichTextDraggingComponent = ComponentType; +/** + * @internal + */ +export type DashboardLayoutDraggingComponent = ComponentType; + /** * @internal */ @@ -206,6 +223,14 @@ export type VisualizationSwitcherDraggableComponent = { type: "visualizationSwitcher"; }; +/** + * @internal + */ +export type DashboardLayoutDraggableComponent = { + DraggingComponent?: DashboardLayoutDraggingComponent; + type: "dashboardLayout"; +}; + /** * @internal */ @@ -226,6 +251,7 @@ export type DraggableComponent = { | InsightDraggableComponent | RichTextDraggableComponent | VisualizationSwitcherDraggableComponent + | DashboardLayoutDraggableComponent | CustomDraggableComponent; }; @@ -396,6 +422,19 @@ export type VisualizationSwitcherWidgetComponentSet = CustomComponentBase< Partial> & ConfigurableWidget; +/** + * Definition of Dashboard layout widget + * @internal + */ +export type DashboardLayoutWidgetComponentSet = CustomComponentBase< + IDashboardNestedLayoutProps, + Parameters +> & + DraggableComponent & + Partial & + Partial> & + ConfigurableWidget; + /** * Definition of widget * @internal diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/CreationPanel.tsx b/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/CreationPanel.tsx index 54ed4e62cb7..3d46771c914 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/CreationPanel.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/CreationPanel.tsx @@ -15,6 +15,7 @@ import { selectEnableKDRichText, selectSupportsRichTextWidgets, selectEnableVisualizationSwitcher, + selectEnableFlexibleLayout, } from "../../../model/index.js"; import cx from "classnames"; import { @@ -27,6 +28,7 @@ import { KpiWidgetComponentSet, RichTextWidgetComponentSet, VisualizationSwitcherWidgetComponentSet, + DashboardLayoutWidgetComponentSet, } from "../../componentDefinition/index.js"; interface ICreationPanelProps { @@ -38,6 +40,7 @@ interface ICreationPanelProps { InsightWidgetComponentSet?: InsightWidgetComponentSet; RichTextWidgetComponentSet?: RichTextWidgetComponentSet; VisualizationSwitcherWidgetComponentSet?: VisualizationSwitcherWidgetComponentSet; + DashboardLayoutWidgetComponentSet?: DashboardLayoutWidgetComponentSet; } export const CreationPanel: React.FC = (props) => { @@ -46,6 +49,7 @@ export const CreationPanel: React.FC = (props) => { const supportsRichText = useDashboardSelector(selectSupportsRichTextWidgets); const enableRichText = useDashboardSelector(selectEnableKDRichText); const enableVisualizationSwitcher = useDashboardSelector(selectEnableVisualizationSwitcher); + const enableFlexibleLayout = useDashboardSelector(selectEnableFlexibleLayout); const isAnalyticalDesignerEnabled = useDashboardSelector(selectIsAnalyticalDesignerEnabled); const isNewDashboard = useDashboardSelector(selectIsNewDashboard); const settings = useDashboardSelector(selectSettings); @@ -54,12 +58,14 @@ export const CreationPanel: React.FC = (props) => { const InsightWidgetComponentSet = props.InsightWidgetComponentSet!; const RichTextWidgetComponentSet = props.RichTextWidgetComponentSet!; const VisualizationSwitcherWidgetComponentSet = props.VisualizationSwitcherWidgetComponentSet!; + const DashboardLayoutWidgetComponentSet = props.DashboardLayoutWidgetComponentSet!; const addItemPanelItems = useMemo(() => { const items = compact([ + InsightWidgetComponentSet.creating, supportsKpis && KpiWidgetComponentSet.creating, AttributeFilterComponentSet.creating, - InsightWidgetComponentSet.creating, + enableFlexibleLayout && DashboardLayoutWidgetComponentSet.creating, enableVisualizationSwitcher && VisualizationSwitcherWidgetComponentSet.creating, supportsRichText && enableRichText && RichTextWidgetComponentSet.creating, ]); @@ -78,10 +84,12 @@ export const CreationPanel: React.FC = (props) => { InsightWidgetComponentSet, RichTextWidgetComponentSet, VisualizationSwitcherWidgetComponentSet, + DashboardLayoutWidgetComponentSet, supportsKpis, supportsRichText, enableRichText, enableVisualizationSwitcher, + enableFlexibleLayout, WrapCreatePanelItemWithDragComponent, ]); diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/DashboardSidebar.tsx b/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/DashboardSidebar.tsx index a9c93f7cd66..50dcf28bde1 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/DashboardSidebar.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/DashboardSidebar.tsx @@ -17,6 +17,7 @@ export const DashboardSidebar = (props: ISidebarProps): JSX.Element => { InsightWidgetComponentSet, RichTextWidgetComponentSet, VisualizationSwitcherWidgetComponentSet, + DashboardLayoutWidgetComponentSet, } = useDashboardComponentsContext(); return ( @@ -27,6 +28,7 @@ export const DashboardSidebar = (props: ISidebarProps): JSX.Element => { InsightWidgetComponentSet={InsightWidgetComponentSet} RichTextWidgetComponentSet={RichTextWidgetComponentSet} VisualizationSwitcherWidgetComponentSet={VisualizationSwitcherWidgetComponentSet} + DashboardLayoutWidgetComponentSet={DashboardLayoutWidgetComponentSet} WrapCreatePanelItemWithDragComponent={WrapCreatePanelItemWithDragComponent} WrapInsightListItemWithDragComponent={WrapInsightListItemWithDragComponent} DeleteDropZoneComponent={DeleteDropZoneComponent} diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/SidebarConfigurationPanel.tsx b/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/SidebarConfigurationPanel.tsx index 3391084c60d..939a1653447 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/SidebarConfigurationPanel.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/SidebarConfigurationPanel.tsx @@ -19,6 +19,7 @@ export const SidebarConfigurationPanel: React.FC diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/types.ts b/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/types.ts index bf1d04712ac..d76c0c7340d 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/types.ts +++ b/libs/sdk-ui-dashboard/src/presentation/dashboard/DashboardSidebar/types.ts @@ -2,6 +2,7 @@ import { ComponentType } from "react"; import { AttributeFilterComponentSet, + DashboardLayoutWidgetComponentSet, InsightWidgetComponentSet, KpiWidgetComponentSet, RichTextWidgetComponentSet, @@ -83,6 +84,14 @@ export interface ISidebarProps { */ VisualizationSwitcherWidgetComponentSet?: VisualizationSwitcherWidgetComponentSet; + /** + * Layout (nested) widget component set. + * Do not set or override this property, it's injected by the Dashboard. + * + * @internal + */ + DashboardLayoutWidgetComponentSet?: DashboardLayoutWidgetComponentSet; + /** * Component, that renders delete drop zone. * Do not set or override this property, it's injected by the Dashboard. diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboard/DefaultDashboardContent/DefaultDashboardMainContent.tsx b/libs/sdk-ui-dashboard/src/presentation/dashboard/DefaultDashboardContent/DefaultDashboardMainContent.tsx index 8e139597188..187722a8582 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboard/DefaultDashboardContent/DefaultDashboardMainContent.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dashboard/DefaultDashboardContent/DefaultDashboardMainContent.tsx @@ -33,6 +33,8 @@ export const DefaultDashboardMainContent = (_: IDashboardProps) => { "richTextListItem", "visualizationSwitcher", "visualizationSwitcherListItem", + "dashboardLayout", + "dashboardLayoutListItem", ], {}, ); diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboard/components/DashboardMainContent.tsx b/libs/sdk-ui-dashboard/src/presentation/dashboard/components/DashboardMainContent.tsx index 7f20715af8e..da338c8c021 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboard/components/DashboardMainContent.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dashboard/components/DashboardMainContent.tsx @@ -31,6 +31,8 @@ export const DashboardMainContent = forwardRef(function DashboardMainContent(_: "richTextListItem", "visualizationSwitcher", "visualizationSwitcherListItem", + "dashboardLayout", + "dashboardLayoutListItem", ], {}, ); diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboard/components/DashboardRenderer.tsx b/libs/sdk-ui-dashboard/src/presentation/dashboard/components/DashboardRenderer.tsx index a8415d872e9..ac64c768a02 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboard/components/DashboardRenderer.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dashboard/components/DashboardRenderer.tsx @@ -75,6 +75,7 @@ export const DashboardRenderer: React.FC = (props: IDashboardPr richTextWidgetComponentSet, visualizationSwitcherWidgetComponentSet, visualizationSwitcherToolbarComponentProvider, + dashboardLayoutWidgetComponentSet, } = useDashboard(props); const dashboardRender = ( @@ -154,6 +155,7 @@ export const DashboardRenderer: React.FC = (props: IDashboardPr VisualizationSwitcherWidgetComponentSet={ visualizationSwitcherWidgetComponentSet } + DashboardLayoutWidgetComponentSet={dashboardLayoutWidgetComponentSet} AttributeFilterComponentSet={attributeFilterComponentSet} DateFilterComponentSet={dateFilterComponentSet} EmptyLayoutDropZoneBodyComponent={ diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboard/hooks/useDashboard.ts b/libs/sdk-ui-dashboard/src/presentation/dashboard/hooks/useDashboard.ts index c0e88182806..59fca217f27 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboard/hooks/useDashboard.ts +++ b/libs/sdk-ui-dashboard/src/presentation/dashboard/hooks/useDashboard.ts @@ -24,6 +24,8 @@ import { DefaultDashboardVisualizationSwitcher, DefaultDashboardVisualizationSwitcherComponentSetFactory, DefaultVisualizationSwitcherToolbar, + DefaultDashboardNestedLayout, + DefaultDashboardLayoutComponentSetFactory, } from "../../widget/index.js"; import { IDashboardProps } from "../types.js"; import { IAnalyticalBackend } from "@gooddata/sdk-backend-spi"; @@ -41,9 +43,11 @@ import { RichTextComponentProvider, VisualizationSwitcherComponentProvider, VisualizationSwitcherToolbarComponentProvider, + DashboardLayoutComponentProvider, } from "../../dashboardContexts/index.js"; import { AttributeFilterComponentSet, + DashboardLayoutWidgetComponentSet, DateFilterComponentSet, InsightWidgetComponentSet, KpiWidgetComponentSet, @@ -76,6 +80,8 @@ interface IUseDashboardResult { visualizationSwitcherProvider: VisualizationSwitcherComponentProvider; visualizationSwitcherWidgetComponentSet: VisualizationSwitcherWidgetComponentSet; visualizationSwitcherToolbarComponentProvider: VisualizationSwitcherToolbarComponentProvider; + dashboardLayoutProvider: DashboardLayoutComponentProvider; + dashboardLayoutWidgetComponentSet: DashboardLayoutWidgetComponentSet; } export const useDashboard = (props: IDashboardProps): IUseDashboardResult => { @@ -95,6 +101,7 @@ export const useDashboard = (props: IDashboardProps): IUseDashboardResult => { RichTextComponentProvider, VisualizationSwitcherComponentProvider, VisualizationSwitcherToolbarComponentProvider, + DashboardLayoutComponentProvider, } = props; const backend = useBackendStrict(props.backend); @@ -238,6 +245,18 @@ export const useDashboard = (props: IDashboardProps): IUseDashboardResult => { return DefaultDashboardVisualizationSwitcherComponentSetFactory(visualizationSwitcherProvider); }, [visualizationSwitcherProvider]); + const dashboardLayoutProvider = useCallback( + (dashboardLayout) => { + const userSpecified = DashboardLayoutComponentProvider?.(dashboardLayout); + return userSpecified ?? DefaultDashboardNestedLayout; + }, + [DashboardLayoutComponentProvider], + ); + + const dashboardLayoutWidgetComponentSet = useMemo(() => { + return DefaultDashboardLayoutComponentSetFactory(dashboardLayoutProvider); + }, [dashboardLayoutProvider]); + const isThemeLoading = useThemeIsLoading(); const hasThemeProvider = isThemeLoading !== undefined; @@ -265,5 +284,7 @@ export const useDashboard = (props: IDashboardProps): IUseDashboardResult => { visualizationSwitcherProvider, visualizationSwitcherWidgetComponentSet, visualizationSwitcherToolbarComponentProvider, + dashboardLayoutProvider, + dashboardLayoutWidgetComponentSet, }; }; diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboard/types.ts b/libs/sdk-ui-dashboard/src/presentation/dashboard/types.ts index 8c46dc7b9c8..b6fc82d6f5b 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboard/types.ts +++ b/libs/sdk-ui-dashboard/src/presentation/dashboard/types.ts @@ -44,6 +44,7 @@ import { OptionalRichTextComponentProvider, OptionalVisualizationSwitcherComponentProvider, OptionalVisualizationSwitcherToolbarComponentProvider, + OptionalDashboardLayoutComponentProvider, } from "../dashboardContexts/index.js"; import { CustomSidebarComponent } from "./DashboardSidebar/types.js"; import { InsightComponentSetProvider } from "../componentDefinition/types.js"; @@ -235,6 +236,19 @@ export interface IDashboardCustomComponentProps { */ VisualizationSwitcherComponentProvider?: OptionalVisualizationSwitcherComponentProvider; + /** + * Specify function to obtain custom component to use for rendering a dashboard layout (nested). + * + * @remarks + * - If not provided, the default implementation {@link DefaultDashboardLayout} will be used. + * - If factory function is provided and it returns undefined, then the default implementation {@link DefaultDashboardLayout}. + * This is useful if you want to customize just one particular nested layout and keep default rendering for + * the other nested layouts. + * + * @public + */ + DashboardLayoutComponentProvider?: OptionalDashboardLayoutComponentProvider; + /** * Specify function to obtain custom component to use for rendering a visualization switcher toolbar. * diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboardContexts/DashboardComponentsContext.tsx b/libs/sdk-ui-dashboard/src/presentation/dashboardContexts/DashboardComponentsContext.tsx index 71b76de8d39..2b9bb45ddb7 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboardContexts/DashboardComponentsContext.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dashboardContexts/DashboardComponentsContext.tsx @@ -41,6 +41,7 @@ import { import { CustomSidebarComponent } from "../dashboard/DashboardSidebar/types.js"; import { AttributeFilterComponentSet, + DashboardLayoutWidgetComponentSet, DateFilterComponentSet, InsightWidgetComponentSet, KpiWidgetComponentSet, @@ -86,6 +87,7 @@ interface IDashboardComponentsContext { KpiWidgetComponentSet: KpiWidgetComponentSet; RichTextWidgetComponentSet: RichTextWidgetComponentSet; VisualizationSwitcherWidgetComponentSet: VisualizationSwitcherWidgetComponentSet; + DashboardLayoutWidgetComponentSet: DashboardLayoutWidgetComponentSet; AttributeFilterComponentSet: AttributeFilterComponentSet; DateFilterComponentSet: DateFilterComponentSet; EmptyLayoutDropZoneBodyComponent: CustomEmptyLayoutDropZoneBodyComponent; @@ -138,6 +140,7 @@ const DashboardComponentsContext = createContext({ KpiWidgetComponentSet: null as any, // TODO how to throw here RichTextWidgetComponentSet: null as any, // TODO how to throw here VisualizationSwitcherWidgetComponentSet: null as any, // TODO how to throw here + DashboardLayoutWidgetComponentSet: null as any, // TODO how to throw here AttributeFilterComponentSet: null as any, // TODO how to throw here DateFilterComponentSet: null as any, // TODO how to throw here EmptyLayoutDropZoneBodyComponent: ThrowMissingComponentError("EmptyLayoutDropZoneBodyComponent"), diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboardContexts/types.ts b/libs/sdk-ui-dashboard/src/presentation/dashboardContexts/types.ts index 85e1f10dfd9..05270e1cd48 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboardContexts/types.ts +++ b/libs/sdk-ui-dashboard/src/presentation/dashboardContexts/types.ts @@ -12,6 +12,7 @@ import { CustomDashboardRichTextComponent, CustomDashboardVisualizationSwitcherComponent, CustomVisualizationSwitcherToolbarComponent, + CustomDashboardNestedLayoutComponent, } from "../widget/types.js"; import { DashboardConfig, ExtendedDashboardWidget } from "../../model/index.js"; import { @@ -31,6 +32,7 @@ import { IWorkspacePermissions, IRichTextWidget, IVisualizationSwitcherWidget, + IDashboardLayout, } from "@gooddata/sdk-model"; import { ComponentType } from "react"; @@ -154,6 +156,13 @@ export type VisualizationSwitcherComponentProvider = ( visualizationSwitcher: IVisualizationSwitcherWidget, ) => CustomDashboardVisualizationSwitcherComponent; +/** + * @alpha + */ +export type DashboardLayoutComponentProvider = ( + widget: IDashboardLayout, +) => CustomDashboardNestedLayoutComponent; + /** * @alpha */ @@ -173,6 +182,11 @@ export type OptionalVisualizationSwitcherToolbarComponentProvider = export type OptionalVisualizationSwitcherComponentProvider = OptionalProvider; +/** + * @alpha + */ +export type OptionalDashboardLayoutComponentProvider = OptionalProvider; + /** * @public */ diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/DragLayerPreview/ContentDragPreview.tsx b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/DragLayerPreview/ContentDragPreview.tsx index 47245f8a0d2..f875b7b0a8c 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/DragLayerPreview/ContentDragPreview.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/DragLayerPreview/ContentDragPreview.tsx @@ -33,6 +33,7 @@ export const ContentDragPreview: FC> = (p DateFilterComponentSet, RichTextWidgetComponentSet, VisualizationSwitcherWidgetComponentSet, + DashboardLayoutWidgetComponentSet, } = useDashboardComponentsContext(); const previewComponentsMap = useMemo>>( () => ({ @@ -42,6 +43,7 @@ export const ContentDragPreview: FC> = (p kpi: KpiWidgetComponentSet.dragging.DraggingComponent, richText: RichTextWidgetComponentSet.dragging.DraggingComponent, visualizationSwitcher: VisualizationSwitcherWidgetComponentSet.dragging.DraggingComponent, + dashboardLayout: DashboardLayoutWidgetComponentSet.dragging.DraggingComponent, }), [ AttributeFilterComponentSet.dragging.DraggingComponent, @@ -50,6 +52,7 @@ export const ContentDragPreview: FC> = (p DateFilterComponentSet.dragging.DraggingComponent, RichTextWidgetComponentSet.dragging.DraggingComponent, VisualizationSwitcherWidgetComponentSet.dragging.DraggingComponent, + DashboardLayoutWidgetComponentSet.dragging.DraggingComponent, ], ); diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/AddDashboardLayoutWidgetButton.tsx b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/AddDashboardLayoutWidgetButton.tsx new file mode 100644 index 00000000000..c55bf829935 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/AddDashboardLayoutWidgetButton.tsx @@ -0,0 +1,48 @@ +// (C) 2024 GoodData Corporation + +import React from "react"; +import { FormattedMessage } from "react-intl"; +import cx from "classnames"; +import { + Bubble, + BubbleHoverTrigger, + IAlignPoint, + Icon, + OverlayController, + OverlayControllerProvider, + useMediaQuery, +} from "@gooddata/sdk-ui-kit"; +import { useTheme } from "@gooddata/sdk-ui-theme-provider"; + +import { DASHBOARD_DIALOG_OVERS_Z_INDEX } from "../../constants/index.js"; + +const bubbleAlignPoints: IAlignPoint[] = [{ align: "cr cl", offset: { x: 5, y: 0 } }]; +const overlayController = OverlayController.getInstance(DASHBOARD_DIALOG_OVERS_Z_INDEX); + +export const AddDashboardLayoutWidgetButton: React.FC = () => { + const isMobileDevice = useMediaQuery("mobileDevice"); + const theme = useTheme(); + return ( +
+ + + + +
+
+
+ + + + + +
+ ); +}; diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/AddRichTextWidgetButton.tsx b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/AddRichTextWidgetButton.tsx index 7ae53f6e13c..fc3fd202391 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/AddRichTextWidgetButton.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/AddRichTextWidgetButton.tsx @@ -5,7 +5,7 @@ import { FormattedMessage } from "react-intl"; export const AddRichTextWidgetButton: React.FC = () => { return ( -
+
diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/AddVisualizationSwitcherWidgetButton.tsx b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/AddVisualizationSwitcherWidgetButton.tsx index ab1c1fc6585..1f174acfe7d 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/AddVisualizationSwitcherWidgetButton.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/AddVisualizationSwitcherWidgetButton.tsx @@ -23,13 +23,13 @@ export const AddVisualizationSwitcherWidgetButton: React.FC = () => { const isMobileDevice = useMediaQuery("mobileDevice"); const theme = useTheme(); return ( -
+
{ >
- + diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/DashboardLayoutDraggingComponent.tsx b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/DashboardLayoutDraggingComponent.tsx new file mode 100644 index 00000000000..348744bc256 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/DashboardLayoutDraggingComponent.tsx @@ -0,0 +1,12 @@ +// (C) 2024 GoodData Corporation + +import React from "react"; +import { IDashboardLayoutDraggingComponentProps } from "../../componentDefinition/types.js"; +import { AddDashboardLayoutWidgetButton } from "./AddDashboardLayoutWidgetButton.js"; + +/* + * @internal + */ +export function DashboardLayoutDraggingComponent(_props: IDashboardLayoutDraggingComponentProps) { + return ; +} diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/DraggableDashboardLayoutCreatePanelItem.tsx b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/DraggableDashboardLayoutCreatePanelItem.tsx new file mode 100644 index 00000000000..e256b2c56c3 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/DraggableDashboardLayoutCreatePanelItem.tsx @@ -0,0 +1,43 @@ +// (C) 2024 GoodData Corporation + +import React from "react"; +import { DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT } from "@gooddata/sdk-ui-ext"; + +import { CustomCreatePanelItemComponent } from "../../componentDefinition/index.js"; +import { DraggableCreatePanelItem } from "../DraggableCreatePanelItem.js"; +import { DraggableItem, IWrapCreatePanelItemWithDragComponent } from "../types.js"; + +/** + * @internal + */ +interface IDraggableDashboardLayoutCreatePanelItemProps { + CreatePanelItemComponent: CustomCreatePanelItemComponent; + WrapCreatePanelItemWithDragComponent?: IWrapCreatePanelItemWithDragComponent; +} + +const getDragItem = (): DraggableItem => { + return { + type: "dashboardLayoutListItem", + size: { + gridHeight: DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT.height.default, + gridWidth: DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT.width.default, + }, + }; +}; + +/** + * @internal + */ +export const DraggableDashboardLayoutCreatePanelItem: React.FC< + IDraggableDashboardLayoutCreatePanelItemProps +> = ({ CreatePanelItemComponent, WrapCreatePanelItemWithDragComponent }) => { + const dragItem = getDragItem(); + return ( + + ); +}; diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/EmptyDashboardDropZone.tsx b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/EmptyDashboardDropZone.tsx index 2a2789f235e..3670eff8390 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/EmptyDashboardDropZone.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/EmptyDashboardDropZone.tsx @@ -9,6 +9,7 @@ import { useDashboardDrop } from "../useDashboardDrop.js"; import { BaseDraggableLayoutItem, DraggableItemType, + isDashboardLayoutDraggableListItem, isInsightDraggableListItem, isInsightPlaceholderDraggableItem, isKpiPlaceholderDraggableItem, @@ -23,6 +24,7 @@ import { useNewSectionInsightPlaceholderDropHandler } from "./useNewSectionInsig import { getDashboardLayoutItemHeightForGrid } from "../../../_staging/layout/sizing.js"; import { useNewSectionRichTextPlaceholderDropHandler } from "./useNewSectionRichTextPlaceholderDropHandler.js"; import { useNewSectionVisualizationSwitcherPlaceholderDropHandler } from "./useNewSectionVisualizationSwitcherPlaceholderDropHandler.js"; +import { useNewSectionDashboardLayoutPlaceholderDropHandler } from "./useNewSectionDashboardLayoutPlaceholderDropHandler.js"; const widgetCategoryMapping: Partial<{ [D in DraggableItemType]: string }> = { "insight-placeholder": "insight", @@ -30,6 +32,7 @@ const widgetCategoryMapping: Partial<{ [D in DraggableItemType]: string }> = { "kpi-placeholder": "kpi", richTextListItem: "richText", visualizationSwitcherListItem: "visualizationSwitcher", + dashboardLayoutListItem: "dashboardLayout", }; export const EmptyDashboardDropZone: React.FC = () => { @@ -44,6 +47,7 @@ export const EmptyDashboardDropZone: React.FC = () => { const handleRichTextPlaceholderDrop = useNewSectionRichTextPlaceholderDropHandler(0); const handleVisualizationSwitcherPlaceholderDrop = useNewSectionVisualizationSwitcherPlaceholderDropHandler(0); + const handleDashboardLayoutPlaceholderDrop = useNewSectionDashboardLayoutPlaceholderDropHandler(0); const [{ canDrop, isOver, itemType, item }, dropRef] = useDashboardDrop( [ @@ -52,6 +56,8 @@ export const EmptyDashboardDropZone: React.FC = () => { "insight-placeholder", "richTextListItem", "visualizationSwitcherListItem", + "dashboardLayoutListItem", + "dashboardLayoutListItem", ], { drop: (item) => { @@ -70,6 +76,9 @@ export const EmptyDashboardDropZone: React.FC = () => { if (isVisualizationSwitcherDraggableListItem(item)) { handleVisualizationSwitcherPlaceholderDrop(); } + if (isDashboardLayoutDraggableListItem(item)) { + handleDashboardLayoutPlaceholderDrop(); + } }, }, [ @@ -79,6 +88,8 @@ export const EmptyDashboardDropZone: React.FC = () => { handleKpiPlaceholderDrop, handleInsightPlaceholderDrop, handleVisualizationSwitcherPlaceholderDrop, + handleRichTextPlaceholderDrop, + handleDashboardLayoutPlaceholderDrop, ], ); diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/Hotspot.tsx b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/Hotspot.tsx index 533b58798c2..f52277a420e 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/Hotspot.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/Hotspot.tsx @@ -4,6 +4,7 @@ import React, { useCallback, useEffect, useRef } from "react"; import { getDropZoneDebugStyle } from "../debug.js"; import { + isDashboardLayoutDraggableListItem, isInsightDraggableItem, isInsightDraggableListItem, isInsightPlaceholderDraggableItem, @@ -22,6 +23,7 @@ import { useMoveWidgetDropHandler } from "./useMoveWidgetHandler.js"; import { useWidgetDragHoverHandlers } from "./useWidgetDragHoverHandlers.js"; import { useRichTextPlaceholderDropHandler } from "./useRichTextPlaceholderDropHandler.js"; import { useVisualizationSwitcherPlaceholderDropHandler } from "./useVisualizationSwitcherPlaceholderDropHandler.js"; +import { useDashboardLayoutPlaceholderDropHandler } from "./useDashboardLayoutPlaceholderDropHandler.js"; interface IHotspotProps { sectionIndex: number; @@ -47,6 +49,10 @@ export const Hotspot: React.FC = (props) => { sectionIndex, targetItemIndex, ); + const handleDashboardLayoutPlaceholderDrop = useDashboardLayoutPlaceholderDropHandler( + sectionIndex, + targetItemIndex, + ); const handleWidgetDrop = useMoveWidgetDropHandler(sectionIndex, targetItemIndex); const { handleDragHoverStart } = useWidgetDragHoverHandlers(); @@ -61,6 +67,8 @@ export const Hotspot: React.FC = (props) => { "richTextListItem", "visualizationSwitcher", "visualizationSwitcherListItem", + "dashboardLayout", + "dashboardLayoutListItem", ], { drop: (item) => { @@ -79,6 +87,9 @@ export const Hotspot: React.FC = (props) => { if (isVisualizationSwitcherDraggableListItem(item)) { handleVisualizationSwitcherPlaceholderDrop(); } + if (isDashboardLayoutDraggableListItem(item)) { + handleDashboardLayoutPlaceholderDrop(); + } if ( isInsightDraggableItem(item) || @@ -98,6 +109,8 @@ export const Hotspot: React.FC = (props) => { handleKpiPlaceholderDrop, handleWidgetDrop, handleVisualizationSwitcherPlaceholderDrop, + handleRichTextPlaceholderDrop, + handleDashboardLayoutPlaceholderDrop, ], ); diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/SectionHotspot.tsx b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/SectionHotspot.tsx index 484fba5b613..dbbb114c9a7 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/SectionHotspot.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/SectionHotspot.tsx @@ -4,6 +4,7 @@ import React, { useEffect } from "react"; import { useDashboardDispatch } from "../../../model/index.js"; import { isBaseDraggableMovingItem, + isDashboardLayoutDraggableListItem, isInsightDraggableItem, isInsightDraggableListItem, isInsightPlaceholderDraggableItem, @@ -24,6 +25,7 @@ import { useNewSectionKpiPlaceholderDropHandler } from "./useNewSectionKpiPlaceh import { useWidgetDragHoverHandlers } from "./useWidgetDragHoverHandlers.js"; import { useNewSectionRichTextPlaceholderDropHandler } from "./useNewSectionRichTextPlaceholderDropHandler.js"; import { useNewSectionVisualizationSwitcherPlaceholderDropHandler } from "./useNewSectionVisualizationSwitcherPlaceholderDropHandler.js"; +import { useNewSectionDashboardLayoutPlaceholderDropHandler } from "./useNewSectionDashboardLayoutPlaceholderDropHandler.js"; export type RowPosition = "above" | "below"; @@ -43,6 +45,8 @@ export const SectionHotspot: React.FC = (props) => { const handleRichTextPlaceholderDrop = useNewSectionRichTextPlaceholderDropHandler(index); const handleVisualizationSwitcherPlaceholderDrop = useNewSectionVisualizationSwitcherPlaceholderDropHandler(index); + const handleDashboardLayoutPlaceholderDrop = useNewSectionDashboardLayoutPlaceholderDropHandler(index); + const moveWidgetToNewSection = useMoveWidgetToNewSectionDropHandler(index); const { handleDragHoverEnd } = useWidgetDragHoverHandlers(); @@ -57,6 +61,8 @@ export const SectionHotspot: React.FC = (props) => { "richTextListItem", "visualizationSwitcher", "visualizationSwitcherListItem", + "dashboardLayout", + "dashboardLayoutListItem", ], { drop: (item) => { @@ -81,6 +87,9 @@ export const SectionHotspot: React.FC = (props) => { if (isVisualizationSwitcherDraggableListItem(item)) { handleVisualizationSwitcherPlaceholderDrop(); } + if (isDashboardLayoutDraggableListItem(item)) { + handleDashboardLayoutPlaceholderDrop(); + } if (isKpiDraggableItem(item)) { moveWidgetToNewSection(item); } @@ -103,6 +112,7 @@ export const SectionHotspot: React.FC = (props) => { handleKpiPlaceholderDrop, handleInsightPlaceholderDrop, handleVisualizationSwitcherPlaceholderDrop, + handleDashboardLayoutPlaceholderDrop, ], ); diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/WidgetDropZoneColumn.tsx b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/WidgetDropZoneColumn.tsx index 5fe9a6b28e6..31e7f3c9c2e 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/WidgetDropZoneColumn.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/WidgetDropZoneColumn.tsx @@ -17,6 +17,7 @@ import { useMoveWidgetDropHandler } from "./useMoveWidgetHandler.js"; import { getDashboardLayoutItemHeightForGrid } from "../../../_staging/layout/sizing.js"; import { BaseDraggableLayoutItem, + isDashboardLayoutDraggableListItem, isInsightDraggableItem, isInsightDraggableListItem, isInsightPlaceholderDraggableItem, @@ -29,6 +30,7 @@ import { } from "../types.js"; import { useRichTextPlaceholderDropHandler } from "./useRichTextPlaceholderDropHandler.js"; import { useVisualizationSwitcherPlaceholderDropHandler } from "./useVisualizationSwitcherPlaceholderDropHandler.js"; +import { useDashboardLayoutPlaceholderDropHandler } from "./useDashboardLayoutPlaceholderDropHandler.js"; export type WidgetDropZoneColumnProps = { screen: ScreenSize; @@ -50,6 +52,10 @@ export const WidgetDropZoneColumn = (props: WidgetDropZoneColumnProps) => { sectionIndex, itemIndex, ); + const handleDashboardLayoutPlaceholderDrop = useDashboardLayoutPlaceholderDropHandler( + sectionIndex, + itemIndex, + ); const handleWidgetDrop = useMoveWidgetDropHandler(sectionIndex, itemIndex); const dispatch = useDashboardDispatch(); @@ -65,6 +71,8 @@ export const WidgetDropZoneColumn = (props: WidgetDropZoneColumnProps) => { "richTextListItem", "visualizationSwitcher", "visualizationSwitcherListItem", + "dashboardLayout", + "dashboardLayoutListItem", ], { drop: (item) => { @@ -83,6 +91,9 @@ export const WidgetDropZoneColumn = (props: WidgetDropZoneColumnProps) => { if (isVisualizationSwitcherDraggableListItem(item)) { handleVisualizationSwitcherPlaceholderDrop(); } + if (isDashboardLayoutDraggableListItem(item)) { + handleDashboardLayoutPlaceholderDrop(); + } if ( isInsightDraggableItem(item) || isKpiDraggableItem(item) || @@ -99,6 +110,9 @@ export const WidgetDropZoneColumn = (props: WidgetDropZoneColumnProps) => { handleInsightPlaceholderDrop, handleKpiPlaceholderDrop, handleVisualizationSwitcherPlaceholderDrop, + handleRichTextPlaceholderDrop, + handleWidgetDrop, + handleDashboardLayoutPlaceholderDrop, ], ); diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/index.ts b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/index.ts index e027b821613..61d873a5164 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/index.ts +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/index.ts @@ -3,9 +3,11 @@ export * from "./DraggableInsightListItem.js"; export * from "./DraggableKpiCreatePanelItem.js"; export * from "./DraggableRichTextCreatePanelItem.js"; export * from "./DraggableVisualizationSwitcherCreatePanelItem.js"; +export * from "./DraggableDashboardLayoutCreatePanelItem.js"; export * from "./AddKpiWidgetButton.js"; export * from "./AddRichTextWidgetButton.js"; export * from "./AddVisualizationSwitcherWidgetButton.js"; +export * from "./AddDashboardLayoutWidgetButton.js"; export * from "./SectionHotspot.js"; export * from "./RowEndHotspot.js"; export * from "./Hotspot.js"; @@ -23,3 +25,4 @@ export * from "./InsightDraggingComponent.js"; export * from "./KpiDraggingComponent.js"; export * from "./RichTextDraggingComponent.js"; export * from "./VisualizationSwitcherDraggingComponent.js"; +export * from "./DashboardLayoutDraggingComponent.js"; diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/useDashboardLayoutPlaceholderDropHandler.ts b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/useDashboardLayoutPlaceholderDropHandler.ts new file mode 100644 index 00000000000..6c3580686fe --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/useDashboardLayoutPlaceholderDropHandler.ts @@ -0,0 +1,48 @@ +// (C) 2024 GoodData Corporation + +import { useCallback } from "react"; +import { v4 as uuidv4 } from "uuid"; +import { idRef } from "@gooddata/sdk-model"; +import { DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT } from "@gooddata/sdk-ui-ext"; + +import { + addSectionItem, + uiActions, + useDashboardCommandProcessing, + useDashboardDispatch, +} from "../../../model/index.js"; + +export function useDashboardLayoutPlaceholderDropHandler(sectionIndex: number, itemIndex: number) { + const dispatch = useDashboardDispatch(); + + const { run: createDashboardLayout } = useDashboardCommandProcessing({ + commandCreator: addSectionItem, + errorEvent: "GDC.DASH/EVT.COMMAND.FAILED", + successEvent: "GDC.DASH/EVT.FLUID_LAYOUT.ITEMS_ADDED", + onSuccess: (event) => { + const ref = event.payload.itemsAdded[0].widget!.ref; + dispatch(uiActions.selectWidget(ref)); + dispatch(uiActions.setConfigurationPanelOpened(true)); + }, + }); + + return useCallback(() => { + const id = uuidv4(); + createDashboardLayout(sectionIndex, itemIndex, { + type: "IDashboardLayoutItem", + size: { + xl: { + gridHeight: DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT.height.default!, + gridWidth: DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT.width.default!, + }, + }, + widget: { + type: "IDashboardLayout", + sections: [], + identifier: id, + ref: idRef(id), + uri: `/${id}`, + }, + }); + }, [createDashboardLayout, itemIndex, sectionIndex]); +} diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/useIsDraggingWidget.ts b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/useIsDraggingWidget.ts index ffd6af33cf2..ffcda83bb58 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/useIsDraggingWidget.ts +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/useIsDraggingWidget.ts @@ -14,6 +14,8 @@ export function useIsDraggingWidget() { "richTextListItem", "visualizationSwitcher", "visualizationSwitcherListItem", + "dashboardLayout", + "dashboardLayoutListItem", ], {}, [], diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/useNewSectionDashboardLayoutPlaceholderDropHandler.ts b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/useNewSectionDashboardLayoutPlaceholderDropHandler.ts new file mode 100644 index 00000000000..22b94d5d8e3 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/draggableWidget/useNewSectionDashboardLayoutPlaceholderDropHandler.ts @@ -0,0 +1,55 @@ +// (C) 2024 GoodData Corporation + +import { useCallback } from "react"; +import { v4 as uuidv4 } from "uuid"; +import { idRef } from "@gooddata/sdk-model"; +import { DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT } from "@gooddata/sdk-ui-ext"; + +import { + useDashboardDispatch, + useDashboardCommandProcessing, + uiActions, + addLayoutSection, +} from "../../../model/index.js"; + +export function useNewSectionDashboardLayoutPlaceholderDropHandler(sectionIndex: number) { + const dispatch = useDashboardDispatch(); + + const { run: addNewSectionWithDashboardLayoutPlaceholder } = useDashboardCommandProcessing({ + commandCreator: addLayoutSection, + errorEvent: "GDC.DASH/EVT.COMMAND.FAILED", + successEvent: "GDC.DASH/EVT.FLUID_LAYOUT.SECTION_ADDED", + onSuccess: (event) => { + const ref = event.payload.section.items[0].widget!.ref; + dispatch(uiActions.selectWidget(ref)); + dispatch(uiActions.setConfigurationPanelOpened(true)); + }, + }); + + return useCallback(() => { + const id = uuidv4(); + addNewSectionWithDashboardLayoutPlaceholder(sectionIndex, {}, [ + { + type: "IDashboardLayoutItem", + size: { + xl: { + gridHeight: DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT.height.default!, + gridWidth: DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT.width.default!, + }, + }, + widget: { + type: "IDashboardLayout", + identifier: id, + sections: [], + ref: idRef(id), + uri: `/${id}`, + size: { + //TODO INE - duplicate size info + gridHeight: DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT.height.default!, + gridWidth: DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT.width.default!, + }, + }, + }, + ]); + }, [addNewSectionWithDashboardLayoutPlaceholder, sectionIndex]); +} diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/types.ts b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/types.ts index 3e28795dd43..13a965fb242 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/types.ts +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/types.ts @@ -18,6 +18,8 @@ export type DraggableContentItemType = | "richTextListItem" | "visualizationSwitcher" | "visualizationSwitcherListItem" + | "dashboardLayout" + | "dashboardLayoutListItem" | "custom"; /** @@ -195,6 +197,34 @@ export function isVisualizationSwitcherDraggableListItem( return item.type === "visualizationSwitcherListItem"; } +/** + * @internal + */ +export type DashboardLayoutDraggableItem = BaseDraggableMovingItem & { + type: "dashboardLayout"; +}; + +/** + * @internal + */ +export function isDashboardLayoutDraggableItem(item: any): item is DashboardLayoutDraggableItem { + return item.type === "dashboardLayout"; +} + +/** + * @internal + */ +export type DashboardLayoutDraggableListItem = BaseDraggableLayoutItem & { + type: "dashboardLayoutListItem"; +}; + +/** + * @internal + */ +export function isDashboardLayoutDraggableListItem(item: any): item is DashboardLayoutDraggableListItem { + return item.type === "dashboardLayoutListItem"; +} + /** * @internal */ @@ -286,6 +316,8 @@ export type DraggableContentItem = | RichTextDraggableListItem | VisualizationSwitcherDraggableItem | VisualizationSwitcherDraggableListItem + | DashboardLayoutDraggableItem + | DashboardLayoutDraggableListItem | CustomWidgetDraggableItem | CustomDraggableItem; @@ -297,6 +329,7 @@ export type DraggableLayoutItem = | KpiDraggableItem | RichTextDraggableItem | VisualizationSwitcherDraggableItem + | DashboardLayoutDraggableItem | CustomWidgetDraggableItem; /** @@ -330,6 +363,8 @@ export type DraggableItemComponentTypeMapping = { richTextListItem: RichTextDraggableListItem; visualizationSwitcher: VisualizationSwitcherDraggableItem; visualizationSwitcherListItem: VisualizationSwitcherDraggableListItem; + dashboardLayout: DashboardLayoutDraggableItem; + dashboardLayoutListItem: DashboardLayoutDraggableListItem; custom: CustomDraggableItem; }; diff --git a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/useDashboardDragScroll.ts b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/useDashboardDragScroll.ts index 27db6e3ac90..9312a255100 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/useDashboardDragScroll.ts +++ b/libs/sdk-ui-dashboard/src/presentation/dragAndDrop/useDashboardDragScroll.ts @@ -18,6 +18,8 @@ const SCROLLING_ITEM_TYPES: DraggableItemType[] = [ "richTextListItem", "visualizationSwitcher", "visualizationSwitcherListItem", + "dashboardLayout", + "dashboardLayoutListItem", "internal-width-resizer", "internal-height-resizer", ]; diff --git a/libs/sdk-ui-dashboard/src/presentation/filterBar/attributeFilter/DefaultDashboardAttributeFilterComponentSetFactory.ts b/libs/sdk-ui-dashboard/src/presentation/filterBar/attributeFilter/DefaultDashboardAttributeFilterComponentSetFactory.ts index e575ca78da6..162daa89273 100644 --- a/libs/sdk-ui-dashboard/src/presentation/filterBar/attributeFilter/DefaultDashboardAttributeFilterComponentSetFactory.ts +++ b/libs/sdk-ui-dashboard/src/presentation/filterBar/attributeFilter/DefaultDashboardAttributeFilterComponentSetFactory.ts @@ -17,7 +17,7 @@ export function DefaultDashboardAttributeFilterComponentSetFactory( CreatingPlaceholderComponent: AttributesDropdown, CreatePanelListItemComponent: CreatableAttributeFilter, type: "attributeFilter-placeholder", - priority: 10, + priority: 3, }, dragging: { DraggingComponent: DefaultAttributeFilterDraggingComponent, diff --git a/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/DashboardLayoutWidget.tsx b/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/DashboardLayoutWidget.tsx index 2b1a87cfe30..b6753d456f8 100644 --- a/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/DashboardLayoutWidget.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/DashboardLayoutWidget.tsx @@ -8,7 +8,11 @@ import { isRichTextWidget, isVisualizationSwitcherWidget, } from "@gooddata/sdk-model"; -import { IVisualizationSizeInfo, WIDGET_DROPZONE_SIZE_INFO_DEFAULT } from "@gooddata/sdk-ui-ext"; +import { + DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT, + IVisualizationSizeInfo, + WIDGET_DROPZONE_SIZE_INFO_DEFAULT, +} from "@gooddata/sdk-ui-ext"; import React, { useRef } from "react"; import cx from "classnames"; import { @@ -330,11 +334,10 @@ function createDraggableItem( size: getFilledSize(size, sizeInfo), }; } else if (isExtendedDashboardLayoutWidget(widget)) { - // TODO INE: create layout specific item - const sizeInfo = getSizeInfo(settings, "visualizationSwitcher"); + const sizeInfo = DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT; return { - type: "visualizationSwitcher", + type: "dashboardLayout", sectionIndex, itemIndex, title: "", diff --git a/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/DefaultFlexibleDashboardLayout.tsx b/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/DefaultFlexibleDashboardLayout.tsx index 7a343d9c9ba..33d1c3bfcde 100644 --- a/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/DefaultFlexibleDashboardLayout.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/DefaultFlexibleDashboardLayout.tsx @@ -159,7 +159,8 @@ export const DefaultFlexibleDashboardLayout = (props: IDashboardLayoutProps): JS // do not render the tailing section hotspot if there is only one section in the layout and it has only initial placeholders in it const shouldRenderSectionHotspot = transformedLayout.sections.length > 1 || - transformedLayout.sections[0].items.some((i) => !isInitialPlaceholderWidget(i.widget)); + (transformedLayout.sections.length === 1 && + transformedLayout.sections[0].items.some((i) => !isInitialPlaceholderWidget(i.widget))); return ( <> diff --git a/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/index.ts b/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/index.ts index 89b7130073a..6a2b9b79467 100644 --- a/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/index.ts +++ b/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/index.ts @@ -1,4 +1,4 @@ // (C) 2021-2024 GoodData Corporation -export { DashboardLayout } from "./DashboardLayout.js"; +export { DashboardLayout } from "../widget/dashboardLayout/DashboardLayout.js"; export { DefaultFlexibleDashboardLayout } from "./DefaultFlexibleDashboardLayout.js"; export * from "./types.js"; diff --git a/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/types.ts b/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/types.ts index db030f54188..3a5abdc67d8 100644 --- a/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/types.ts +++ b/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/types.ts @@ -1,37 +1,10 @@ // (C) 2020-2024 GoodData Corporation import { ComponentType } from "react"; -import { IErrorProps, OnError } from "@gooddata/sdk-ui"; -import { - FilterContextItem, - IDashboardLayout, - ScreenSize, - IDashboardLayoutSizeByScreenSize, -} from "@gooddata/sdk-model"; -import { IDashboardFilter, OnFiredDashboardDrillEvent } from "../../types.js"; -import { ExtendedDashboardWidget } from "../../model/index.js"; - -/** - * @alpha - */ -export interface IDashboardLayoutProps { - ErrorComponent?: React.ComponentType; - // TODO: is this necessary? (there are events for it) - onFiltersChange?: (filters: (IDashboardFilter | FilterContextItem)[], resetOthers?: boolean) => void; - onDrill?: OnFiredDashboardDrillEvent; - onError?: OnError; - // if not provided, root layout from appState will be used - layout?: IDashboardLayout; - // if not provided, the layout size will be detected - screen?: ScreenSize; - // if not provided, the layout size will be considered to be default (12 columns) - parentLayoutItemSize?: IDashboardLayoutSizeByScreenSize; -} - -/** - * @alpha - */ -export type CustomDashboardLayoutComponent = ComponentType; +export type { + IDashboardLayoutProps, + CustomDashboardLayoutComponent, +} from "../widget/dashboardLayout/types.js"; /** * @internal diff --git a/libs/sdk-ui-dashboard/src/presentation/layout/DashboardLayoutWidget.tsx b/libs/sdk-ui-dashboard/src/presentation/layout/DashboardLayoutWidget.tsx index 899837dc051..7a8715060f3 100644 --- a/libs/sdk-ui-dashboard/src/presentation/layout/DashboardLayoutWidget.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/layout/DashboardLayoutWidget.tsx @@ -8,7 +8,11 @@ import { isRichTextWidget, isVisualizationSwitcherWidget, } from "@gooddata/sdk-model"; -import { IVisualizationSizeInfo, WIDGET_DROPZONE_SIZE_INFO_DEFAULT } from "@gooddata/sdk-ui-ext"; +import { + DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT, + IVisualizationSizeInfo, + WIDGET_DROPZONE_SIZE_INFO_DEFAULT, +} from "@gooddata/sdk-ui-ext"; import React, { useRef } from "react"; import cx from "classnames"; import { @@ -329,11 +333,10 @@ function createDraggableItem( size: getFilledSize(size, sizeInfo), }; } else if (isExtendedDashboardLayoutWidget(widget)) { - // TODO INE: create layout specific item - const sizeInfo = getSizeInfo(settings, "visualizationSwitcher"); + const sizeInfo = DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT; return { - type: "visualizationSwitcher", + type: "dashboardLayout", sectionIndex, itemIndex, title: "", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-US.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-US.json index 32f74e177c4..f830889a081 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-US.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-US.json @@ -1532,6 +1532,16 @@ "comment": "", "limit": 0 }, + "addPanel.dashboardLayout": { + "value": "Container", + "comment": "", + "limit": 0 + }, + "addPanel.dashboardLayout.tooltip": { + "value": "Group and display multiple widgets together for more flexible layout. ", + "comment": "", + "limit": 0 + }, "addPanel.kpi.tooltip.no_measures._measure": { "value": "The Key Performance Indicator requires a calculated measure.", "comment": "", diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/CreatableDashboardLayout.tsx b/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/CreatableDashboardLayout.tsx new file mode 100644 index 00000000000..88a3ce0ae81 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/CreatableDashboardLayout.tsx @@ -0,0 +1,26 @@ +// (C) 2024 GoodData Corporation + +import React from "react"; +import { BubbleHoverTrigger } from "@gooddata/sdk-ui-kit"; + +import { + AddDashboardLayoutWidgetButton, + DraggableDashboardLayoutCreatePanelItem, +} from "../../dragAndDrop/index.js"; +import { ICreatePanelItemComponentProps } from "../../componentDefinition/index.js"; + +/** + * @internal + */ +export const CreatableDashboardLayout: React.FC = (props) => { + const { WrapCreatePanelItemWithDragComponent } = props; + + return ( + + + + ); +}; diff --git a/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/DashboardLayout.tsx b/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/DashboardLayout.tsx similarity index 64% rename from libs/sdk-ui-dashboard/src/presentation/flexibleLayout/DashboardLayout.tsx rename to libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/DashboardLayout.tsx index db610750b2b..eaad717d629 100644 --- a/libs/sdk-ui-dashboard/src/presentation/flexibleLayout/DashboardLayout.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/DashboardLayout.tsx @@ -1,7 +1,7 @@ // (C) 2020-2024 GoodData Corporation import React from "react"; -import { useDashboardComponentsContext } from "../dashboardContexts/index.js"; -import { IDashboardLayoutProps } from "./types.js"; +import { useDashboardComponentsContext } from "../../dashboardContexts/index.js"; +import { IDashboardLayoutProps } from "../../flexibleLayout/types.js"; /** * @internal diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/DefaultDashboardLayout.ts b/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/DefaultDashboardLayout.ts new file mode 100644 index 00000000000..ed33392c56a --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/DefaultDashboardLayout.ts @@ -0,0 +1,14 @@ +// (C) 2024 GoodData Corporation + +import { DashboardLayout } from "./DashboardLayout.js"; +import { renderModeAware } from "../../componentDefinition/index.js"; + +/** + * Default implementation of the dashboard layout widget. + * + * @public + */ +export const DefaultDashboardLayout = renderModeAware({ + view: DashboardLayout, + edit: DashboardLayout, +}); diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/DefaultDashboardLayoutComponentSetFactory.tsx b/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/DefaultDashboardLayoutComponentSetFactory.tsx new file mode 100644 index 00000000000..d78b9c6d8da --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/DefaultDashboardLayoutComponentSetFactory.tsx @@ -0,0 +1,29 @@ +// (C) 2024 GoodData Corporation + +import { DashboardLayoutWidgetComponentSet } from "../../componentDefinition/index.js"; +import { DashboardLayoutComponentProvider } from "../../dashboardContexts/index.js"; +import { DashboardLayoutDraggingComponent } from "../../dragAndDrop/index.js"; +import { CreatableDashboardLayout } from "./CreatableDashboardLayout.js"; + +/** + * @internal + */ +export function DefaultDashboardLayoutComponentSetFactory( + dashboardLayoutComponentProvider: DashboardLayoutComponentProvider, +): DashboardLayoutWidgetComponentSet { + return { + MainComponentProvider: dashboardLayoutComponentProvider, + creating: { + CreatePanelListItemComponent: CreatableDashboardLayout, + type: "dashboardLayoutListItem", + priority: 3, + }, + dragging: { + DraggingComponent: DashboardLayoutDraggingComponent, + type: "dashboardLayout", + }, + configuration: { + WidgetConfigPanelComponent: () => null, + }, + }; +} diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/index.ts b/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/index.ts new file mode 100644 index 00000000000..47b191efd61 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/index.ts @@ -0,0 +1,8 @@ +// (C) 2024 GoodData Corporation + +export type { + CustomDashboardLayoutComponent as CustomDashboardNestedLayoutComponent, + IDashboardLayoutProps as IDashboardNestedLayoutProps, +} from "./types.js"; +export { DefaultDashboardLayout as DefaultDashboardNestedLayout } from "./DefaultDashboardLayout.js"; +export { DefaultDashboardLayoutComponentSetFactory } from "./DefaultDashboardLayoutComponentSetFactory.js"; diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/types.ts b/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/types.ts new file mode 100644 index 00000000000..aa4df1393c6 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/widget/dashboardLayout/types.ts @@ -0,0 +1,72 @@ +// (C) 2024 GoodData Corporation + +import { ComponentType } from "react"; +import { IAnalyticalBackend } from "@gooddata/sdk-backend-spi"; +import { + FilterContextItem, + IDashboardLayout, + IDashboardLayoutSizeByScreenSize, + ScreenSize, +} from "@gooddata/sdk-model"; +import { IErrorProps, OnError } from "@gooddata/sdk-ui"; +import { IDashboardFilter, OnFiredDashboardDrillEvent } from "../../../types.js"; +import { ExtendedDashboardWidget } from "../../../model/index.js"; + +/** + * @alpha + */ +export interface IDashboardLayoutProps { + ErrorComponent?: React.ComponentType; + // TODO: is this necessary? (there are events for it) + onFiltersChange?: (filters: (IDashboardFilter | FilterContextItem)[], resetOthers?: boolean) => void; + onDrill?: OnFiredDashboardDrillEvent; + onError?: OnError; + // if not provided, root layout from appState will be used + layout?: IDashboardLayout; + // if not provided, the layout size will be detected + screen?: ScreenSize; + // if not provided, the layout size will be considered to be default (12 columns) + parentLayoutItemSize?: IDashboardLayoutSizeByScreenSize; + + // TODO INE: dashboard as a widget props + /** + * Backend to work with. + * + * @remarks + * Note: the backend must come either from this property or from BackendContext. If you do not specify + * backend here, then the component MUST be rendered within an existing BackendContext. + * + * @alpha + */ + backend?: IAnalyticalBackend; + + /** + * Workspace where the Insight widget exists. + * + * @remarks + * Note: the workspace must come either from this property or from WorkspaceContext. If you do not specify + * workspace here, then the component MUST be rendered within an existing WorkspaceContext. + * + * @alpha + */ + workspace?: string; + + /** + * Height of the visualization switcher widget container. + * + * @alpha + */ + clientHeight?: number; + + /** + * Width of the visualization switcher widget container. + * + * @alpha + */ + clientWidth?: number; +} + +/** + * @alpha + */ +export type CustomDashboardLayoutComponent = ComponentType; diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/index.ts b/libs/sdk-ui-dashboard/src/presentation/widget/index.ts index e10824e5dc7..0282d303822 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widget/index.ts +++ b/libs/sdk-ui-dashboard/src/presentation/widget/index.ts @@ -19,4 +19,5 @@ export * from "./kpi/index.js"; export * from "./kpiPlaceholder/index.js"; export * from "./richText/index.js"; export * from "./visualizationSwitcher/index.js"; +export * from "./dashboardLayout/index.js"; export * from "./widget/index.js"; diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/types.ts b/libs/sdk-ui-dashboard/src/presentation/widget/types.ts index d1b8a29c00d..20c5b720c2c 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widget/types.ts +++ b/libs/sdk-ui-dashboard/src/presentation/widget/types.ts @@ -25,3 +25,8 @@ export type { } from "../widget/visualizationSwitcher/types.js"; export type { CustomVisualizationSwitcherToolbarComponent } from "../widget/visualizationSwitcher/configuration/types.js"; + +export type { + CustomDashboardLayoutComponent as CustomDashboardNestedLayoutComponent, + IDashboardLayoutProps as IDashboardNestedLayoutProps, +} from "../widget/dashboardLayout/types.js"; diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/widget/DefaultDashboardWidget.tsx b/libs/sdk-ui-dashboard/src/presentation/widget/widget/DefaultDashboardWidget.tsx index 6fc0bf2af83..6b3972253d8 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widget/widget/DefaultDashboardWidget.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/widget/widget/DefaultDashboardWidget.tsx @@ -26,7 +26,7 @@ import { } from "../../../model/events/widget.js"; import { IDashboardWidgetProps } from "./types.js"; import { safeSerializeObjRef } from "../../../_staging/metadata/safeSerializeObjRef.js"; -import { DashboardLayout } from "../../flexibleLayout/DashboardLayout.js"; +import { DashboardLayout } from "../dashboardLayout/DashboardLayout.js"; import { DefaultDashboardKpiWidget } from "./DefaultDashboardKpiWidget.js"; import { RenderModeAwareDashboardInsightWidget } from "./InsightWidget/index.js"; diff --git a/libs/sdk-ui-dashboard/styles/scss/sidebar.scss b/libs/sdk-ui-dashboard/styles/scss/sidebar.scss index ee47c4942f5..71fbd6054ee 100644 --- a/libs/sdk-ui-dashboard/styles/scss/sidebar.scss +++ b/libs/sdk-ui-dashboard/styles/scss/sidebar.scss @@ -59,42 +59,34 @@ background-position: center; } - &-rich-text { + &.add-panel-item { padding: 0 10px 0 0; svg { margin: 0 14px; } - } - - &-rich-text::before { - content: none; - } - - &-visualization-switcher { - padding: 0 10px 0 0; - - svg { - margin: 0 14px; + &::before { + content: none; } &:hover { - .gd-add-visualization-switcher { + .gd-add-item-placeholder-help-trigger { display: block; cursor: help; } } - .gd-add-visualization-switcher { + .gd-add-item-placeholder-help-trigger { display: none; flex: 0 0 auto; - margin-left: 20px; + margin-left: auto; + margin-right: 5px; font-weight: normal; } - } - &-visualization-switcher::before { - content: none; + span { + flex: 0 0 auto; + } } span { diff --git a/libs/sdk-ui-ext/api/sdk-ui-ext.api.md b/libs/sdk-ui-ext/api/sdk-ui-ext.api.md index f5c0ba16755..035c5efe596 100644 --- a/libs/sdk-ui-ext/api/sdk-ui-ext.api.md +++ b/libs/sdk-ui-ext/api/sdk-ui-ext.api.md @@ -75,6 +75,9 @@ export const CreateUserGroupDialog: React_2.FC; // @internal (undocumented) export const DASHBOARD_LAYOUT_DEFAULT_VIS_HEIGHT_PX = 450; +// @internal (undocumented) +export const DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT: IVisualizationDefaultSizeInfo; + // @internal (undocumented) export type DataSourcePermission = "USE" | "MANAGE"; diff --git a/libs/sdk-ui-ext/src/index.ts b/libs/sdk-ui-ext/src/index.ts index 86c5246771a..99fe1f15026 100644 --- a/libs/sdk-ui-ext/src/index.ts +++ b/libs/sdk-ui-ext/src/index.ts @@ -58,6 +58,7 @@ export { RICH_TEXT_WIDGET_SIZE_INFO_NEW_DEFAULT, VISUALIZATION_SWITCHER_WIDGET_SIZE_INFO_DEFAULT, VISUALIZATION_SWITCHER_WIDGET_SIZE_INFO_NEW_DEFAULT, + DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT, EmbedInsightDialog, } from "./internal/index.js"; diff --git a/libs/sdk-ui-ext/src/internal/components/pluggableVisualizations/constants.ts b/libs/sdk-ui-ext/src/internal/components/pluggableVisualizations/constants.ts index 683b86dde6d..53ecf9be4e5 100644 --- a/libs/sdk-ui-ext/src/internal/components/pluggableVisualizations/constants.ts +++ b/libs/sdk-ui-ext/src/internal/components/pluggableVisualizations/constants.ts @@ -115,6 +115,21 @@ export const VISUALIZATION_SWITCHER_WIDGET_SIZE_INFO_NEW_DEFAULT: IVisualization }, }; +/** + * @internal + */ +export const DASHBOARD_LAYOUT_WIDGET_SIZE_INFO_DEFAULT: IVisualizationDefaultSizeInfo = { + width: { + min: 4, + default: 4, + }, + height: { + default: 22, + min: 12, + max: 40, + }, +}; + /** * @internal */ diff --git a/libs/sdk-ui-kit/src/Icon/Icon.tsx b/libs/sdk-ui-kit/src/Icon/Icon.tsx index bcb30d32b53..3f1c74dcf5a 100644 --- a/libs/sdk-ui-kit/src/Icon/Icon.tsx +++ b/libs/sdk-ui-kit/src/Icon/Icon.tsx @@ -102,6 +102,7 @@ import { GenAI } from "./icons/GenAI.js"; import { Search } from "./icons/Search.js"; import { NewVisualization } from "./icons/NewVisualization.js"; import { ChatBubble } from "./icons/ChatBubble.js"; +import { Container } from "./icons/Container.js"; /** * @internal @@ -207,6 +208,7 @@ export const Icon: Record> = { Search, NewVisualization, ChatBubble, + Section: Container, }; export type { IRowsIconProps, IColumnsIconProps }; diff --git a/libs/sdk-ui-kit/src/Icon/icons/Container.tsx b/libs/sdk-ui-kit/src/Icon/icons/Container.tsx new file mode 100644 index 00000000000..f3315615a97 --- /dev/null +++ b/libs/sdk-ui-kit/src/Icon/icons/Container.tsx @@ -0,0 +1,118 @@ +// (C) 2024 GoodData Corporation +import React from "react"; + +import { IIconProps } from "../typings.js"; + +/** + * @internal + */ +export const Container: React.FC = ({ color = "#94A1AD", className, width, height }) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +};