Dashboard: Auto grid options (#102374)
* Dashboard: Auto grid options * Update * Fix * update trans * Update cue * Fix persisting auto grid options (#102744) * Fix persisting auto grid options * Update i18n * fix serializer test * update schema * reset dashboard_object_gen.go files, run update-codegen.sh * rename in code * rename height fill in schema * rename heightFill fillScreen in code * fix test --------- Co-authored-by: oscarkilhed <oscar.kilhed@grafana.com>
This commit is contained in:
co-authored by
oscarkilhed
parent
ca14ca70de
commit
dc922717dc
@@ -563,8 +563,12 @@ ResponsiveGridLayoutKind: {
|
||||
}
|
||||
|
||||
ResponsiveGridLayoutSpec: {
|
||||
row: string
|
||||
col: string
|
||||
maxColumnCount?: number | *3
|
||||
columnWidthMode: "narrow" | *"standard" | "wide" | "custom"
|
||||
columnWidth?: number
|
||||
rowHeightMode: "short" | *"standard" | "tall" | "custom"
|
||||
rowHeight?: number
|
||||
fillScreen?: bool | *false
|
||||
items: [...ResponsiveGridLayoutItemKind]
|
||||
}
|
||||
|
||||
|
||||
@@ -965,14 +965,21 @@ func NewDashboardResponsiveGridLayoutKind() *DashboardResponsiveGridLayoutKind {
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type DashboardResponsiveGridLayoutSpec struct {
|
||||
Row string `json:"row"`
|
||||
Col string `json:"col"`
|
||||
Items []DashboardResponsiveGridLayoutItemKind `json:"items"`
|
||||
MaxColumnCount *float64 `json:"maxColumnCount,omitempty"`
|
||||
ColumnWidthMode DashboardResponsiveGridLayoutSpecColumnWidthMode `json:"columnWidthMode"`
|
||||
ColumnWidth *float64 `json:"columnWidth,omitempty"`
|
||||
RowHeightMode DashboardResponsiveGridLayoutSpecRowHeightMode `json:"rowHeightMode"`
|
||||
RowHeight *float64 `json:"rowHeight,omitempty"`
|
||||
FillScreen *bool `json:"fillScreen,omitempty"`
|
||||
Items []DashboardResponsiveGridLayoutItemKind `json:"items"`
|
||||
}
|
||||
|
||||
// NewDashboardResponsiveGridLayoutSpec creates a new DashboardResponsiveGridLayoutSpec object.
|
||||
func NewDashboardResponsiveGridLayoutSpec() *DashboardResponsiveGridLayoutSpec {
|
||||
return &DashboardResponsiveGridLayoutSpec{}
|
||||
return &DashboardResponsiveGridLayoutSpec{
|
||||
MaxColumnCount: (func(input float64) *float64 { return &input })(3),
|
||||
FillScreen: (func(input bool) *bool { return &input })(false),
|
||||
}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
@@ -1795,6 +1802,26 @@ const (
|
||||
DashboardConditionalRenderingVariableSpecOperatorNotEquals DashboardConditionalRenderingVariableSpecOperator = "notEquals"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type DashboardResponsiveGridLayoutSpecColumnWidthMode string
|
||||
|
||||
const (
|
||||
DashboardResponsiveGridLayoutSpecColumnWidthModeNarrow DashboardResponsiveGridLayoutSpecColumnWidthMode = "narrow"
|
||||
DashboardResponsiveGridLayoutSpecColumnWidthModeStandard DashboardResponsiveGridLayoutSpecColumnWidthMode = "standard"
|
||||
DashboardResponsiveGridLayoutSpecColumnWidthModeWide DashboardResponsiveGridLayoutSpecColumnWidthMode = "wide"
|
||||
DashboardResponsiveGridLayoutSpecColumnWidthModeCustom DashboardResponsiveGridLayoutSpecColumnWidthMode = "custom"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type DashboardResponsiveGridLayoutSpecRowHeightMode string
|
||||
|
||||
const (
|
||||
DashboardResponsiveGridLayoutSpecRowHeightModeShort DashboardResponsiveGridLayoutSpecRowHeightMode = "short"
|
||||
DashboardResponsiveGridLayoutSpecRowHeightModeStandard DashboardResponsiveGridLayoutSpecRowHeightMode = "standard"
|
||||
DashboardResponsiveGridLayoutSpecRowHeightModeTall DashboardResponsiveGridLayoutSpecRowHeightMode = "tall"
|
||||
DashboardResponsiveGridLayoutSpecRowHeightModeCustom DashboardResponsiveGridLayoutSpecRowHeightMode = "custom"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type DashboardTimeSettingsSpecWeekStart string
|
||||
|
||||
|
||||
@@ -3395,20 +3395,44 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardResponsiveGridLayoutSpec(ref co
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"row": {
|
||||
"maxColumnCount": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"number"},
|
||||
Format: "double",
|
||||
},
|
||||
},
|
||||
"columnWidthMode": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"col": {
|
||||
"columnWidth": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"number"},
|
||||
Format: "double",
|
||||
},
|
||||
},
|
||||
"rowHeightMode": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"rowHeight": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"number"},
|
||||
Format: "double",
|
||||
},
|
||||
},
|
||||
"fillScreen": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"boolean"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"items": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
@@ -3423,7 +3447,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardResponsiveGridLayoutSpec(ref co
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"row", "col", "items"},
|
||||
Required: []string{"columnWidthMode", "rowHeightMode", "items"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"github.com/grafana/grafana-app-sdk/app"
|
||||
)
|
||||
|
||||
var ()
|
||||
|
||||
var appManifestData = app.ManifestData{
|
||||
AppName: "dashboard",
|
||||
Group: "dashboard.grafana.app",
|
||||
|
||||
@@ -563,9 +563,13 @@ ResponsiveGridLayoutKind: {
|
||||
}
|
||||
|
||||
ResponsiveGridLayoutSpec: {
|
||||
row: string,
|
||||
col: string,
|
||||
items: [...ResponsiveGridLayoutItemKind]
|
||||
maxColumnCount?: number | *3
|
||||
columnWidthMode: "narrow" | *"standard" | "wide" | "custom"
|
||||
columnWidth?: number
|
||||
rowHeightMode: "short" | *"standard" | "tall" | "custom"
|
||||
rowHeight?: number
|
||||
fillScreen?: bool | *false
|
||||
items: [...ResponsiveGridLayoutItemKind]
|
||||
}
|
||||
|
||||
ResponsiveGridLayoutItemKind: {
|
||||
|
||||
@@ -824,14 +824,20 @@ export const defaultResponsiveGridLayoutKind = (): ResponsiveGridLayoutKind => (
|
||||
});
|
||||
|
||||
export interface ResponsiveGridLayoutSpec {
|
||||
row: string;
|
||||
col: string;
|
||||
maxColumnCount?: number;
|
||||
columnWidthMode: "narrow" | "standard" | "wide" | "custom";
|
||||
columnWidth?: number;
|
||||
rowHeightMode: "short" | "standard" | "tall" | "custom";
|
||||
rowHeight?: number;
|
||||
fillScreen?: boolean;
|
||||
items: ResponsiveGridLayoutItemKind[];
|
||||
}
|
||||
|
||||
export const defaultResponsiveGridLayoutSpec = (): ResponsiveGridLayoutSpec => ({
|
||||
row: "",
|
||||
col: "",
|
||||
maxColumnCount: 3,
|
||||
columnWidthMode: "standard",
|
||||
rowHeightMode: "standard",
|
||||
fillScreen: false,
|
||||
items: [],
|
||||
});
|
||||
|
||||
|
||||
@@ -333,13 +333,13 @@ export const defaultValueMapping = (): ValueMapping => (defaultValueMap());
|
||||
// Maps text values to a color or different display text and color.
|
||||
// For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.
|
||||
export interface ValueMap {
|
||||
type: MappingType & "value";
|
||||
type: unknown;
|
||||
// Map with <value_to_match>: ValueMappingResult. For example: { "10": { text: "Perfection!", color: "green" } }
|
||||
options: Record<string, ValueMappingResult>;
|
||||
}
|
||||
|
||||
export const defaultValueMap = (): ValueMap => ({
|
||||
type: "value",
|
||||
type: "unknown",
|
||||
options: {},
|
||||
});
|
||||
|
||||
@@ -370,7 +370,7 @@ export const defaultValueMappingResult = (): ValueMappingResult => ({
|
||||
// Maps numerical ranges to a display text and color.
|
||||
// For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.
|
||||
export interface RangeMap {
|
||||
type: MappingType & "range";
|
||||
type: unknown;
|
||||
// Range to match against and the result to apply when the value is within the range
|
||||
options: {
|
||||
// Min value of the range. It can be null which means -Infinity
|
||||
@@ -383,7 +383,7 @@ export interface RangeMap {
|
||||
}
|
||||
|
||||
export const defaultRangeMap = (): RangeMap => ({
|
||||
type: "range",
|
||||
type: "unknown",
|
||||
options: {
|
||||
from: 0,
|
||||
to: 0,
|
||||
@@ -394,7 +394,7 @@ export const defaultRangeMap = (): RangeMap => ({
|
||||
// Maps regular expressions to replacement text and a color.
|
||||
// For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.
|
||||
export interface RegexMap {
|
||||
type: MappingType & "regex";
|
||||
type: unknown;
|
||||
// Regular expression to match against and the result to apply when the value matches the regex
|
||||
options: {
|
||||
// Regular expression to match against
|
||||
@@ -405,7 +405,7 @@ export interface RegexMap {
|
||||
}
|
||||
|
||||
export const defaultRegexMap = (): RegexMap => ({
|
||||
type: "regex",
|
||||
type: "unknown",
|
||||
options: {
|
||||
pattern: "",
|
||||
result: defaultValueMappingResult(),
|
||||
@@ -416,7 +416,7 @@ export const defaultRegexMap = (): RegexMap => ({
|
||||
// See SpecialValueMatch to see the list of special values.
|
||||
// For example, you can configure a special value mapping so that null values appear as N/A.
|
||||
export interface SpecialValueMap {
|
||||
type: MappingType & "special";
|
||||
type: unknown;
|
||||
options: {
|
||||
// Special value to match against
|
||||
match: SpecialValueMatch;
|
||||
@@ -426,7 +426,7 @@ export interface SpecialValueMap {
|
||||
}
|
||||
|
||||
export const defaultSpecialValueMap = (): SpecialValueMap => ({
|
||||
type: "special",
|
||||
type: "unknown",
|
||||
options: {
|
||||
match: "true",
|
||||
result: defaultValueMappingResult(),
|
||||
@@ -691,7 +691,7 @@ export interface RowsLayoutRowSpec {
|
||||
collapsed: boolean;
|
||||
conditionalRendering?: ConditionalRenderingGroupKind;
|
||||
repeat?: RowRepeatOptions;
|
||||
layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind;
|
||||
layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind | RowsLayoutKind;
|
||||
}
|
||||
|
||||
export const defaultRowsLayoutRowSpec = (): RowsLayoutRowSpec => ({
|
||||
@@ -788,14 +788,20 @@ export const defaultResponsiveGridLayoutKind = (): ResponsiveGridLayoutKind => (
|
||||
});
|
||||
|
||||
export interface ResponsiveGridLayoutSpec {
|
||||
row: string;
|
||||
col: string;
|
||||
maxColumnCount?: number;
|
||||
columnWidthMode: "narrow" | "standard" | "wide" | "custom";
|
||||
columnWidth?: number;
|
||||
rowHeightMode: "short" | "standard" | "tall" | "custom";
|
||||
rowHeight?: number;
|
||||
fillScreen?: boolean;
|
||||
items: ResponsiveGridLayoutItemKind[];
|
||||
}
|
||||
|
||||
export const defaultResponsiveGridLayoutSpec = (): ResponsiveGridLayoutSpec => ({
|
||||
row: "",
|
||||
col: "",
|
||||
maxColumnCount: 3,
|
||||
columnWidthMode: "standard",
|
||||
rowHeightMode: "standard",
|
||||
fillScreen: false,
|
||||
items: [],
|
||||
});
|
||||
|
||||
@@ -859,7 +865,7 @@ export const defaultTabsLayoutTabKind = (): TabsLayoutTabKind => ({
|
||||
|
||||
export interface TabsLayoutTabSpec {
|
||||
title?: string;
|
||||
layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind;
|
||||
layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind;
|
||||
}
|
||||
|
||||
export const defaultTabsLayoutTabSpec = (): TabsLayoutTabSpec => ({
|
||||
|
||||
@@ -18,6 +18,7 @@ export type JustifyContent =
|
||||
| 'space-between'
|
||||
| 'space-around'
|
||||
| 'space-evenly'
|
||||
| 'stretch'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'left'
|
||||
|
||||
+113
-15
@@ -1,4 +1,5 @@
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes';
|
||||
import { GRID_CELL_VMARGIN } from 'app/core/constants';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
|
||||
@@ -20,8 +21,19 @@ import { getEditOptions } from './ResponsiveGridLayoutManagerEditor';
|
||||
|
||||
interface ResponsiveGridLayoutManagerState extends SceneObjectState {
|
||||
layout: ResponsiveGridLayout;
|
||||
maxColumnCount: number;
|
||||
rowHeight: AutoGridRowHeight;
|
||||
columnWidth: AutoGridColumnWidth;
|
||||
fillScreen: boolean;
|
||||
}
|
||||
|
||||
export type AutoGridColumnWidth = 'narrow' | 'standard' | 'wide' | 'custom' | number;
|
||||
export type AutoGridRowHeight = 'short' | 'standard' | 'tall' | 'custom' | number;
|
||||
|
||||
export const AUTO_GRID_DEFAULT_MAX_COLUMN_COUNT = 3;
|
||||
export const AUTO_GRID_DEFAULT_COLUMN_WIDTH = 'standard';
|
||||
export const AUTO_GRID_DEFAULT_ROW_HEIGHT = 'standard';
|
||||
|
||||
export class ResponsiveGridLayoutManager
|
||||
extends SceneObjectBase<ResponsiveGridLayoutManagerState>
|
||||
implements DashboardLayoutManager
|
||||
@@ -45,10 +57,30 @@ export class ResponsiveGridLayoutManager
|
||||
|
||||
public readonly descriptor = ResponsiveGridLayoutManager.descriptor;
|
||||
|
||||
public static defaultCSS = {
|
||||
templateColumns: 'repeat(auto-fit, minmax(400px, auto))',
|
||||
autoRows: 'minmax(300px, auto)',
|
||||
};
|
||||
public constructor(state: Partial<ResponsiveGridLayoutManagerState>) {
|
||||
const maxColumnCount = state.maxColumnCount ?? AUTO_GRID_DEFAULT_MAX_COLUMN_COUNT;
|
||||
const columnWidth = state.columnWidth ?? AUTO_GRID_DEFAULT_COLUMN_WIDTH;
|
||||
const rowHeight = state.rowHeight ?? AUTO_GRID_DEFAULT_ROW_HEIGHT;
|
||||
const fillScreen = state.fillScreen ?? false;
|
||||
|
||||
super({
|
||||
...state,
|
||||
maxColumnCount,
|
||||
columnWidth,
|
||||
rowHeight,
|
||||
fillScreen,
|
||||
layout:
|
||||
state.layout ??
|
||||
new ResponsiveGridLayout({
|
||||
templateColumns: getTemplateColumnsTemplate(maxColumnCount, columnWidth),
|
||||
autoRows: getAutoRowsTemplate(rowHeight, fillScreen),
|
||||
}),
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
this.state.layout.getDragClassCancel = () => 'drag-cancel';
|
||||
this.state.layout.isDraggable = () => true;
|
||||
}
|
||||
|
||||
public addPanel(vizPanel: VizPanel) {
|
||||
const panelId = dashboardSceneGraph.getNextPanelId(this);
|
||||
@@ -159,22 +191,44 @@ export class ResponsiveGridLayoutManager
|
||||
return getEditOptions(this);
|
||||
}
|
||||
|
||||
public changeColumns(columns: string) {
|
||||
this.state.layout.setState({ templateColumns: columns });
|
||||
public onMaxColumnCountChanged(maxColumnCount: number) {
|
||||
this.setState({ maxColumnCount: maxColumnCount });
|
||||
this.state.layout.setState({
|
||||
templateColumns: getTemplateColumnsTemplate(maxColumnCount, this.state.columnWidth),
|
||||
});
|
||||
}
|
||||
|
||||
public changeRows(rows: string) {
|
||||
this.state.layout.setState({ autoRows: rows });
|
||||
public onColumnWidthChanged(columnWidth: AutoGridColumnWidth) {
|
||||
if (columnWidth === 'custom') {
|
||||
columnWidth = getNamedColumWidthInPixels(this.state.columnWidth);
|
||||
}
|
||||
|
||||
this.setState({ columnWidth: columnWidth });
|
||||
this.state.layout.setState({
|
||||
templateColumns: getTemplateColumnsTemplate(this.state.maxColumnCount, this.state.columnWidth),
|
||||
});
|
||||
}
|
||||
|
||||
public onFillScreenChanged(fillScreen: boolean) {
|
||||
this.setState({ fillScreen });
|
||||
this.state.layout.setState({
|
||||
autoRows: getAutoRowsTemplate(this.state.rowHeight, fillScreen),
|
||||
});
|
||||
}
|
||||
|
||||
public onRowHeightChanged(rowHeight: AutoGridRowHeight) {
|
||||
if (rowHeight === 'custom') {
|
||||
rowHeight = getNamedHeightInPixels(this.state.rowHeight);
|
||||
}
|
||||
|
||||
this.setState({ rowHeight });
|
||||
this.state.layout.setState({
|
||||
autoRows: getAutoRowsTemplate(rowHeight, this.state.fillScreen),
|
||||
});
|
||||
}
|
||||
|
||||
public static createEmpty(): ResponsiveGridLayoutManager {
|
||||
return new ResponsiveGridLayoutManager({
|
||||
layout: new ResponsiveGridLayout({
|
||||
children: [],
|
||||
templateColumns: ResponsiveGridLayoutManager.defaultCSS.templateColumns,
|
||||
autoRows: ResponsiveGridLayoutManager.defaultCSS.autoRows,
|
||||
}),
|
||||
});
|
||||
return new ResponsiveGridLayoutManager({});
|
||||
}
|
||||
|
||||
public static createFromLayout(layout: DashboardLayoutManager): ResponsiveGridLayoutManager {
|
||||
@@ -195,3 +249,47 @@ export class ResponsiveGridLayoutManager
|
||||
function ResponsiveGridLayoutManagerRenderer({ model }: SceneComponentProps<ResponsiveGridLayoutManager>) {
|
||||
return <model.state.layout.Component model={model.state.layout} />;
|
||||
}
|
||||
|
||||
export function getTemplateColumnsTemplate(maxColumnCount: number, columnWidth: AutoGridColumnWidth) {
|
||||
return `repeat(auto-fit, minmax(min(max(100% / ${maxColumnCount} - ${GRID_CELL_VMARGIN}px, ${getNamedColumWidthInPixels(columnWidth)}px), 100%), 1fr))`;
|
||||
}
|
||||
|
||||
function getNamedColumWidthInPixels(columnWidth: AutoGridColumnWidth) {
|
||||
if (typeof columnWidth === 'number') {
|
||||
return columnWidth;
|
||||
}
|
||||
|
||||
switch (columnWidth) {
|
||||
case 'narrow':
|
||||
return 192;
|
||||
case 'wide':
|
||||
return 768;
|
||||
case 'custom':
|
||||
case 'standard':
|
||||
default:
|
||||
return 448;
|
||||
}
|
||||
}
|
||||
|
||||
function getNamedHeightInPixels(rowHeight: AutoGridRowHeight) {
|
||||
if (typeof rowHeight === 'number') {
|
||||
return rowHeight;
|
||||
}
|
||||
|
||||
switch (rowHeight) {
|
||||
case 'short':
|
||||
return 128;
|
||||
case 'tall':
|
||||
return 512;
|
||||
case 'custom':
|
||||
case 'standard':
|
||||
default:
|
||||
return 320;
|
||||
}
|
||||
}
|
||||
|
||||
export function getAutoRowsTemplate(rowHeight: AutoGridRowHeight, fillScreen: boolean) {
|
||||
const rowHeightPixels = getNamedHeightInPixels(rowHeight);
|
||||
const maxRowHeightValue = fillScreen ? 'auto' : `${rowHeightPixels}px`;
|
||||
return `minmax(${rowHeightPixels}px, ${maxRowHeightValue})`;
|
||||
}
|
||||
|
||||
+201
-45
@@ -1,25 +1,27 @@
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
import { capitalize } from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { Button, Combobox, ComboboxOption, Field, InlineSwitch, Input, Stack } from '@grafana/ui';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
|
||||
import { ResponsiveGridLayoutManager } from './ResponsiveGridLayoutManager';
|
||||
|
||||
const sizes = [100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 650];
|
||||
import { AutoGridColumnWidth, AutoGridRowHeight, ResponsiveGridLayoutManager } from './ResponsiveGridLayoutManager';
|
||||
|
||||
export function getEditOptions(layoutManager: ResponsiveGridLayoutManager): OptionsPaneItemDescriptor[] {
|
||||
const options: OptionsPaneItemDescriptor[] = [];
|
||||
|
||||
options.push(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.responsive-layout.options.columns', 'Columns'),
|
||||
title: 'Column options',
|
||||
skipField: true,
|
||||
render: () => <GridLayoutColumns layoutManager={layoutManager} />,
|
||||
})
|
||||
);
|
||||
|
||||
options.push(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.responsive-layout.options.rows', 'Rows'),
|
||||
title: 'Row height options',
|
||||
skipField: true,
|
||||
render: () => <GridLayoutRows layoutManager={layoutManager} />,
|
||||
})
|
||||
);
|
||||
@@ -28,56 +30,210 @@ export function getEditOptions(layoutManager: ResponsiveGridLayoutManager): Opti
|
||||
}
|
||||
|
||||
function GridLayoutColumns({ layoutManager }: { layoutManager: ResponsiveGridLayoutManager }) {
|
||||
const { templateColumns } = layoutManager.state.layout.useState();
|
||||
const { maxColumnCount, columnWidth } = layoutManager.useState();
|
||||
const [inputRef, setInputRef] = React.useState<HTMLInputElement | null>(null);
|
||||
const [focusInput, setFocusInput] = React.useState(false);
|
||||
const [customMinWidthError, setCustomMinWidthError] = React.useState(false);
|
||||
|
||||
const colOptions: Array<SelectableValue<string>> = [
|
||||
{ label: t('dashboard.responsive-layout.options.one-column', '1 column'), value: `1fr` },
|
||||
{ label: t('dashboard.responsive-layout.options.two-columns', '2 columns'), value: `1fr 1fr` },
|
||||
{ label: t('dashboard.responsive-layout.options.three-columns', '3 columns'), value: `1fr 1fr 1fr` },
|
||||
];
|
||||
useEffect(() => {
|
||||
if (focusInput && inputRef) {
|
||||
inputRef.focus();
|
||||
setFocusInput(false);
|
||||
}
|
||||
}, [focusInput, inputRef]);
|
||||
|
||||
for (const size of sizes) {
|
||||
colOptions.push({
|
||||
label: t('dashboard.responsive-layout.options.min', 'Min: {{size}}px', { size }),
|
||||
value: `repeat(auto-fit, minmax(${size}px, auto))`,
|
||||
});
|
||||
}
|
||||
const minWidthOptions: Array<ComboboxOption<AutoGridColumnWidth>> = [
|
||||
'narrow' as const,
|
||||
'standard' as const,
|
||||
'wide' as const,
|
||||
'custom' as const,
|
||||
].map((value) => ({
|
||||
label: capitalize(value),
|
||||
value,
|
||||
}));
|
||||
|
||||
const isStandardMinWidth = typeof columnWidth === 'string';
|
||||
|
||||
const minWidthLabel = isStandardMinWidth
|
||||
? t('dashboard.responsive-layout.options.min-width', 'Min column width')
|
||||
: t('dashboard.responsive-layout.options.min-width-custom', 'Custom min width');
|
||||
const colOptions = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'].map((value) => ({ label: value, value }));
|
||||
|
||||
const onCustomMinWidthChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const pixels = parseInt(e.target.value, 10);
|
||||
if (isNaN(pixels) || pixels < 50 || pixels > 2000) {
|
||||
setCustomMinWidthError(true);
|
||||
return;
|
||||
} else if (customMinWidthError) {
|
||||
setCustomMinWidthError(false);
|
||||
}
|
||||
|
||||
layoutManager.onColumnWidthChanged(pixels);
|
||||
};
|
||||
|
||||
const onNamedMinWidthChanged = (value: ComboboxOption<AutoGridColumnWidth>) => {
|
||||
if (value.value === 'custom') {
|
||||
setFocusInput(true);
|
||||
}
|
||||
layoutManager.onColumnWidthChanged(value.value);
|
||||
};
|
||||
|
||||
const onClearCustomMinWidth = () => {
|
||||
if (customMinWidthError) {
|
||||
setCustomMinWidthError(false);
|
||||
}
|
||||
|
||||
layoutManager.onColumnWidthChanged('standard');
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
options={colOptions}
|
||||
value={String(templateColumns)}
|
||||
onChange={({ value }) => layoutManager.changeColumns(value!)}
|
||||
allowCustomValue={true}
|
||||
/>
|
||||
<Stack gap={2} justifyContent={'stretch'}>
|
||||
<Field
|
||||
label={minWidthLabel}
|
||||
invalid={customMinWidthError}
|
||||
error={
|
||||
customMinWidthError
|
||||
? t('dashboard.responsive-layout.options.min-width-error', 'A number between 50 and 2000 is required')
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{isStandardMinWidth ? (
|
||||
<Combobox options={minWidthOptions} value={columnWidth} onChange={onNamedMinWidthChanged} />
|
||||
) : (
|
||||
<Input
|
||||
defaultValue={columnWidth}
|
||||
onBlur={onCustomMinWidthChanged}
|
||||
ref={(ref) => setInputRef(ref)}
|
||||
type="number"
|
||||
min={50}
|
||||
max={2000}
|
||||
invalid={customMinWidthError}
|
||||
suffix={
|
||||
<Button
|
||||
size="sm"
|
||||
fill="text"
|
||||
icon="times"
|
||||
tooltip={t(
|
||||
'dashboard.responsive-layout.options.min-width-custom-clear',
|
||||
'Back to standard min column width'
|
||||
)}
|
||||
onClick={onClearCustomMinWidth}
|
||||
>
|
||||
{t('dashboard.responsive-layout.options.custom-min-width.clear', 'Clear')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<Field label={t('dashboard.responsive-layout.options.max-columns', 'Max columns')}>
|
||||
<Combobox
|
||||
options={colOptions}
|
||||
value={String(maxColumnCount)}
|
||||
onChange={({ value }) => layoutManager.onMaxColumnCountChanged(parseInt(value, 10))}
|
||||
/>
|
||||
</Field>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function GridLayoutRows({ layoutManager }: { layoutManager: ResponsiveGridLayoutManager }) {
|
||||
const { autoRows } = layoutManager.state.layout.useState();
|
||||
const { rowHeight, fillScreen } = layoutManager.useState();
|
||||
const [inputRef, setInputRef] = React.useState<HTMLInputElement | null>(null);
|
||||
const [focusInput, setFocusInput] = React.useState(false);
|
||||
const [customMinWidthError, setCustomMinWidthError] = React.useState(false);
|
||||
|
||||
const rowOptions: Array<SelectableValue<string>> = [];
|
||||
useEffect(() => {
|
||||
if (focusInput && inputRef) {
|
||||
inputRef.focus();
|
||||
setFocusInput(false);
|
||||
}
|
||||
}, [focusInput, inputRef]);
|
||||
|
||||
for (const size of sizes) {
|
||||
rowOptions.push({
|
||||
label: t('dashboard.responsive-layout.options.min', 'Min: {{size}}px', { size }),
|
||||
value: `minmax(${size}px, auto)`,
|
||||
});
|
||||
}
|
||||
const minWidthOptions: Array<ComboboxOption<AutoGridRowHeight>> = [
|
||||
'short' as const,
|
||||
'standard' as const,
|
||||
'tall' as const,
|
||||
'custom' as const,
|
||||
].map((value) => ({
|
||||
label: capitalize(value),
|
||||
value,
|
||||
}));
|
||||
|
||||
for (const size of sizes) {
|
||||
rowOptions.push({
|
||||
label: t('dashboard.responsive-layout.options.fixed', 'Fixed: {{size}}px', { size }),
|
||||
value: `${size}px`,
|
||||
});
|
||||
}
|
||||
const isStandardHeight = typeof rowHeight === 'string';
|
||||
const rowHeightLabel = rowHeight
|
||||
? t('dashboard.responsive-layout.options.min-height', 'Row height')
|
||||
: t('dashboard.responsive-layout.options.min-height-custom', 'Custom row height');
|
||||
|
||||
const onCustomHeightChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const pixels = parseInt(e.target.value, 10);
|
||||
if (isNaN(pixels) || pixels < 50 || pixels > 2000) {
|
||||
setCustomMinWidthError(true);
|
||||
return;
|
||||
} else if (customMinWidthError) {
|
||||
setCustomMinWidthError(false);
|
||||
}
|
||||
|
||||
layoutManager.onRowHeightChanged(pixels);
|
||||
};
|
||||
|
||||
const onNamedMinHeightChanged = (value: ComboboxOption<AutoGridRowHeight>) => {
|
||||
if (value.value === 'custom') {
|
||||
setFocusInput(true);
|
||||
}
|
||||
layoutManager.onRowHeightChanged(value.value);
|
||||
};
|
||||
|
||||
const onClearCustomRowHeight = () => {
|
||||
if (customMinWidthError) {
|
||||
setCustomMinWidthError(false);
|
||||
}
|
||||
|
||||
layoutManager.onRowHeightChanged('standard');
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
options={rowOptions}
|
||||
value={String(autoRows)}
|
||||
onChange={({ value }) => layoutManager.changeRows(value!)}
|
||||
allowCustomValue={true}
|
||||
/>
|
||||
<Stack gap={2} wrap={true}>
|
||||
<Field
|
||||
label={rowHeightLabel}
|
||||
invalid={customMinWidthError}
|
||||
error={
|
||||
customMinWidthError
|
||||
? t('dashboard.responsive-layout.options.min-height-error', 'A number between 50 and 2000 is required')
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{isStandardHeight ? (
|
||||
<Combobox options={minWidthOptions} value={rowHeight} onChange={onNamedMinHeightChanged} width={18} />
|
||||
) : (
|
||||
<Input
|
||||
defaultValue={rowHeight}
|
||||
onBlur={onCustomHeightChanged}
|
||||
ref={(ref) => setInputRef(ref)}
|
||||
width={18}
|
||||
type="number"
|
||||
min={50}
|
||||
max={2000}
|
||||
invalid={customMinWidthError}
|
||||
suffix={
|
||||
<Button
|
||||
size="sm"
|
||||
fill="text"
|
||||
icon="times"
|
||||
tooltip={t(
|
||||
'dashboard.responsive-layout.options.min-width-custom-clear',
|
||||
'Back to standard min column width'
|
||||
)}
|
||||
onClick={onClearCustomRowHeight}
|
||||
>
|
||||
{t('dashboard.responsive-layout.options.custom-min-height.clear', 'Clear')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<Field label={t('dashboard.responsive-layout.options.height-fill', 'Fill screen')}>
|
||||
<InlineSwitch value={fillScreen} onChange={() => layoutManager.onFillScreenChanged(!fillScreen)} />
|
||||
</Field>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
+40
-7
@@ -2,7 +2,16 @@ import { DashboardV2Spec, ResponsiveGridLayoutItemKind } from '@grafana/schema/d
|
||||
|
||||
import { ResponsiveGridItem } from '../../scene/layout-responsive-grid/ResponsiveGridItem';
|
||||
import { ResponsiveGridLayout } from '../../scene/layout-responsive-grid/ResponsiveGridLayout';
|
||||
import { ResponsiveGridLayoutManager } from '../../scene/layout-responsive-grid/ResponsiveGridLayoutManager';
|
||||
import {
|
||||
AUTO_GRID_DEFAULT_COLUMN_WIDTH,
|
||||
AUTO_GRID_DEFAULT_MAX_COLUMN_COUNT,
|
||||
AUTO_GRID_DEFAULT_ROW_HEIGHT,
|
||||
AutoGridColumnWidth,
|
||||
AutoGridRowHeight,
|
||||
getAutoRowsTemplate,
|
||||
getTemplateColumnsTemplate,
|
||||
ResponsiveGridLayoutManager,
|
||||
} from '../../scene/layout-responsive-grid/ResponsiveGridLayoutManager';
|
||||
import { DashboardLayoutManager, LayoutManagerSerializer } from '../../scene/types/DashboardLayoutManager';
|
||||
import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph';
|
||||
import { getGridItemKeyForPanelId } from '../../utils/utils';
|
||||
@@ -14,10 +23,10 @@ export class ResponsiveGridLayoutSerializer implements LayoutManagerSerializer {
|
||||
return {
|
||||
kind: 'ResponsiveGridLayout',
|
||||
spec: {
|
||||
col:
|
||||
layoutManager.state.layout.state.templateColumns?.toString() ??
|
||||
ResponsiveGridLayoutManager.defaultCSS.templateColumns,
|
||||
row: layoutManager.state.layout.state.autoRows?.toString() ?? ResponsiveGridLayoutManager.defaultCSS.autoRows,
|
||||
maxColumnCount: layoutManager.state.maxColumnCount,
|
||||
fillScreen: layoutManager.state.fillScreen,
|
||||
...serializeAutoGridColumnWidth(layoutManager.state.columnWidth),
|
||||
...serializeAutoGridRowHeight(layoutManager.state.rowHeight),
|
||||
items: layoutManager.state.layout.state.children.map((child) => {
|
||||
if (!(child instanceof ResponsiveGridItem)) {
|
||||
throw new Error('Expected ResponsiveGridItem');
|
||||
@@ -73,11 +82,35 @@ export class ResponsiveGridLayoutSerializer implements LayoutManagerSerializer {
|
||||
});
|
||||
|
||||
return new ResponsiveGridLayoutManager({
|
||||
maxColumnCount: layout.spec.maxColumnCount,
|
||||
columnWidth: layout.spec.columnWidthMode === 'custom' ? layout.spec.columnWidth : layout.spec.columnWidthMode,
|
||||
rowHeight: layout.spec.rowHeightMode === 'custom' ? layout.spec.rowHeight : layout.spec.rowHeightMode,
|
||||
fillScreen: layout.spec.fillScreen,
|
||||
layout: new ResponsiveGridLayout({
|
||||
templateColumns: layout.spec.col,
|
||||
autoRows: layout.spec.row,
|
||||
templateColumns: getTemplateColumnsTemplate(
|
||||
layout.spec.maxColumnCount ?? AUTO_GRID_DEFAULT_MAX_COLUMN_COUNT,
|
||||
layout.spec.columnWidth ?? AUTO_GRID_DEFAULT_COLUMN_WIDTH
|
||||
),
|
||||
autoRows: getAutoRowsTemplate(
|
||||
layout.spec.rowHeight ?? AUTO_GRID_DEFAULT_ROW_HEIGHT,
|
||||
layout.spec.fillScreen ?? false
|
||||
),
|
||||
children,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function serializeAutoGridColumnWidth(columnWidth: AutoGridColumnWidth) {
|
||||
return {
|
||||
columnWidthMode: typeof columnWidth === 'number' ? 'custom' : columnWidth,
|
||||
columnWidth: typeof columnWidth === 'number' ? columnWidth : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function serializeAutoGridRowHeight(rowHeight: AutoGridRowHeight) {
|
||||
return {
|
||||
rowHeightMode: typeof rowHeight === 'number' ? 'custom' : rowHeight,
|
||||
rowHeight: typeof rowHeight === 'number' ? rowHeight : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
+16
-11
@@ -46,8 +46,9 @@ describe('deserialization', () => {
|
||||
layout: {
|
||||
kind: 'ResponsiveGridLayout',
|
||||
spec: {
|
||||
row: 'minmax(min-content, max-content)',
|
||||
col: 'repeat(auto-fit, minmax(400px, 1fr))',
|
||||
columnWidthMode: 'standard',
|
||||
rowHeightMode: 'standard',
|
||||
maxColumnCount: 4,
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
@@ -75,8 +76,9 @@ describe('deserialization', () => {
|
||||
layout: {
|
||||
kind: 'ResponsiveGridLayout',
|
||||
spec: {
|
||||
row: 'minmax(min-content, max-content)',
|
||||
col: 'repeat(auto-fit, minmax(400px, 1fr))',
|
||||
columnWidthMode: 'standard',
|
||||
rowHeightMode: 'standard',
|
||||
maxColumnCount: 4,
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
@@ -233,11 +235,10 @@ describe('serialization', () => {
|
||||
title: 'Row 1',
|
||||
isCollapsed: false,
|
||||
layout: new ResponsiveGridLayoutManager({
|
||||
layout: new ResponsiveGridLayout({
|
||||
children: [],
|
||||
templateColumns: 'repeat(auto-fit, minmax(400px, 1fr))',
|
||||
autoRows: 'minmax(min-content, max-content)',
|
||||
}),
|
||||
columnWidth: 'standard',
|
||||
rowHeight: 'standard',
|
||||
maxColumnCount: 4,
|
||||
layout: new ResponsiveGridLayout({}),
|
||||
}),
|
||||
}),
|
||||
new RowItem({
|
||||
@@ -269,8 +270,12 @@ describe('serialization', () => {
|
||||
layout: {
|
||||
kind: 'ResponsiveGridLayout',
|
||||
spec: {
|
||||
row: 'minmax(min-content, max-content)',
|
||||
col: 'repeat(auto-fit, minmax(400px, 1fr))',
|
||||
columnWidth: undefined,
|
||||
rowHeight: undefined,
|
||||
fillScreen: false,
|
||||
rowHeightMode: 'standard',
|
||||
columnWidthMode: 'standard',
|
||||
maxColumnCount: 4,
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
|
||||
+14
-2
@@ -28,7 +28,13 @@ describe('deserialization', () => {
|
||||
tabs: [
|
||||
{
|
||||
kind: 'TabsLayoutTab',
|
||||
spec: { title: 'Tab 1', layout: { kind: 'ResponsiveGridLayout', spec: { row: '', col: '', items: [] } } },
|
||||
spec: {
|
||||
title: 'Tab 1',
|
||||
layout: {
|
||||
kind: 'ResponsiveGridLayout',
|
||||
spec: { columnWidthMode: 'standard', rowHeightMode: 'standard', maxColumnCount: 4, items: [] },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -64,7 +70,13 @@ describe('deserialization', () => {
|
||||
tabs: [
|
||||
{
|
||||
kind: 'TabsLayoutTab',
|
||||
spec: { title: 'Tab 1', layout: { kind: 'ResponsiveGridLayout', spec: { row: '', col: '', items: [] } } },
|
||||
spec: {
|
||||
title: 'Tab 1',
|
||||
layout: {
|
||||
kind: 'ResponsiveGridLayout',
|
||||
spec: { columnWidthMode: 'standard', rowHeightMode: 'standard', maxColumnCount: 4, items: [] },
|
||||
},
|
||||
},
|
||||
},
|
||||
{ kind: 'TabsLayoutTab', spec: { title: 'Tab 2', layout: { kind: 'GridLayout', spec: { items: [] } } } },
|
||||
],
|
||||
|
||||
+19
-10
@@ -507,8 +507,10 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||
dashboard.spec.layout = {
|
||||
kind: 'ResponsiveGridLayout',
|
||||
spec: {
|
||||
col: 'colString',
|
||||
row: 'rowString',
|
||||
maxColumnCount: 4,
|
||||
columnWidthMode: 'custom',
|
||||
columnWidth: 100,
|
||||
rowHeightMode: 'standard',
|
||||
items: [
|
||||
{
|
||||
kind: 'ResponsiveGridLayoutItem',
|
||||
@@ -525,8 +527,9 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||
const scene = transformSaveModelSchemaV2ToScene(dashboard);
|
||||
const layoutManager = scene.state.body as ResponsiveGridLayoutManager;
|
||||
expect(layoutManager.descriptor.kind).toBe('ResponsiveGridLayout');
|
||||
expect(layoutManager.state.layout.state.templateColumns).toBe('colString');
|
||||
expect(layoutManager.state.layout.state.autoRows).toBe('rowString');
|
||||
expect(layoutManager.state.maxColumnCount).toBe(4);
|
||||
expect(layoutManager.state.columnWidth).toBe(100);
|
||||
expect(layoutManager.state.rowHeight).toBe('standard');
|
||||
expect(layoutManager.state.layout.state.children.length).toBe(1);
|
||||
const gridItem = layoutManager.state.layout.state.children[0] as ResponsiveGridItem;
|
||||
expect(gridItem.state.body.state.key).toBe('panel-1');
|
||||
@@ -545,8 +548,9 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||
layout: {
|
||||
kind: 'ResponsiveGridLayout',
|
||||
spec: {
|
||||
col: 'colString',
|
||||
row: 'rowString',
|
||||
maxColumnCount: 4,
|
||||
columnWidthMode: 'standard',
|
||||
rowHeightMode: 'standard',
|
||||
items: [
|
||||
{
|
||||
kind: 'ResponsiveGridLayoutItem',
|
||||
@@ -571,8 +575,9 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||
expect(layoutManager.state.tabs.length).toBe(1);
|
||||
expect(layoutManager.state.tabs[0].state.title).toBe('tab1');
|
||||
const gridLayoutManager = layoutManager.state.tabs[0].state.layout as ResponsiveGridLayoutManager;
|
||||
expect(gridLayoutManager.state.layout.state.templateColumns).toBe('colString');
|
||||
expect(gridLayoutManager.state.layout.state.autoRows).toBe('rowString');
|
||||
expect(gridLayoutManager.state.maxColumnCount).toBe(4);
|
||||
expect(gridLayoutManager.state.columnWidth).toBe('standard');
|
||||
expect(gridLayoutManager.state.rowHeight).toBe('standard');
|
||||
expect(gridLayoutManager.state.layout.state.children.length).toBe(1);
|
||||
const gridItem = gridLayoutManager.state.layout.state.children[0] as ResponsiveGridItem;
|
||||
expect(gridItem.state.body.state.key).toBe('panel-1');
|
||||
@@ -592,8 +597,9 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||
layout: {
|
||||
kind: 'ResponsiveGridLayout',
|
||||
spec: {
|
||||
col: 'colString',
|
||||
row: 'rowString',
|
||||
maxColumnCount: 4,
|
||||
columnWidthMode: 'standard',
|
||||
rowHeightMode: 'standard',
|
||||
items: [
|
||||
{
|
||||
kind: 'ResponsiveGridLayoutItem',
|
||||
@@ -645,6 +651,9 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||
expect(layoutManager.state.rows.length).toBe(2);
|
||||
const row1Manager = layoutManager.state.rows[0].state.layout as ResponsiveGridLayoutManager;
|
||||
expect(row1Manager.descriptor.kind).toBe('ResponsiveGridLayout');
|
||||
expect(row1Manager.state.maxColumnCount).toBe(4);
|
||||
expect(row1Manager.state.columnWidth).toBe('standard');
|
||||
expect(row1Manager.state.rowHeight).toBe('standard');
|
||||
const row1GridItem = row1Manager.state.layout.state.children[0] as ResponsiveGridItem;
|
||||
expect(row1GridItem.state.body.state.key).toBe('panel-1');
|
||||
|
||||
|
||||
+10
-4
@@ -631,9 +631,11 @@ describe('dynamic layouts', () => {
|
||||
const scene = setupDashboardScene(
|
||||
getMinimalSceneState(
|
||||
new ResponsiveGridLayoutManager({
|
||||
columnWidth: 100,
|
||||
rowHeight: 'standard',
|
||||
maxColumnCount: 4,
|
||||
fillScreen: true,
|
||||
layout: new ResponsiveGridLayout({
|
||||
autoRows: 'rowString',
|
||||
templateColumns: 'colString',
|
||||
children: [
|
||||
new ResponsiveGridItem({
|
||||
body: new VizPanel({}),
|
||||
@@ -649,8 +651,12 @@ describe('dynamic layouts', () => {
|
||||
const result = transformSceneToSaveModelSchemaV2(scene);
|
||||
expect(result.layout.kind).toBe('ResponsiveGridLayout');
|
||||
const respGridLayout = result.layout.spec as ResponsiveGridLayoutSpec;
|
||||
expect(respGridLayout.col).toBe('colString');
|
||||
expect(respGridLayout.row).toBe('rowString');
|
||||
expect(respGridLayout.columnWidthMode).toBe('custom');
|
||||
expect(respGridLayout.columnWidth).toBe(100);
|
||||
expect(respGridLayout.rowHeightMode).toBe('standard');
|
||||
expect(respGridLayout.rowHeight).toBeUndefined();
|
||||
expect(respGridLayout.maxColumnCount).toBe(4);
|
||||
expect(respGridLayout.fillScreen).toBe(true);
|
||||
expect(respGridLayout.items.length).toBe(2);
|
||||
expect(respGridLayout.items[0].kind).toBe('ResponsiveGridLayoutItem');
|
||||
});
|
||||
|
||||
@@ -1528,13 +1528,21 @@
|
||||
},
|
||||
"name": "Auto grid",
|
||||
"options": {
|
||||
"columns": "Columns",
|
||||
"fixed": "Fixed: {{size}}px",
|
||||
"min": "Min: {{size}}px",
|
||||
"one-column": "1 column",
|
||||
"rows": "Rows",
|
||||
"three-columns": "3 columns",
|
||||
"two-columns": "2 columns"
|
||||
"custom-min-height": {
|
||||
"clear": "Clear"
|
||||
},
|
||||
"custom-min-width": {
|
||||
"clear": "Clear"
|
||||
},
|
||||
"height-fill": "Fill screen",
|
||||
"max-columns": "Max columns",
|
||||
"min-height": "Row height",
|
||||
"min-height-custom": "Custom row height",
|
||||
"min-height-error": "A number between 50 and 2000 is required",
|
||||
"min-width": "Min column width",
|
||||
"min-width-custom": "Custom min width",
|
||||
"min-width-custom-clear": "Back to standard min column width",
|
||||
"min-width-error": "A number between 50 and 2000 is required"
|
||||
}
|
||||
},
|
||||
"rows-layout": {
|
||||
|
||||
Reference in New Issue
Block a user