123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- import type {Layout} from 'react-grid-layout';
- import {compact} from 'react-grid-layout/build/utils';
- import pickBy from 'lodash/pickBy';
- import sortBy from 'lodash/sortBy';
- import zip from 'lodash/zip';
- import {defined} from 'sentry/utils';
- import {uniqueId} from 'sentry/utils/guid';
- import {NUM_DESKTOP_COLS} from './dashboard';
- import type {Widget, WidgetLayout} from './types';
- import {DisplayType} from './types';
- export const DEFAULT_WIDGET_WIDTH = 2;
- export const METRIC_WIDGET_MIN_SIZE = {minH: 2, h: 2, w: 2};
- const WIDGET_PREFIX = 'grid-item';
- const STORE_KEYS = ['x', 'y', 'w', 'h', 'minW', 'maxW', 'minH', 'maxH'];
- export type Position = Pick<Layout, 'x' | 'y'>;
- type NextPosition = [position: Position, columnDepths: number[]];
- export function generateWidgetId(widget: Widget, index: number) {
- return widget.id ? `${widget.id}-index-${index}` : `index-${index}`;
- }
- export function constructGridItemKey(widget: {id?: string; tempId?: string}) {
- return `${WIDGET_PREFIX}-${widget.id ?? widget.tempId}`;
- }
- export function assignTempId(widget: Widget) {
- if (widget.id ?? widget.tempId) {
- return widget;
- }
- return {...widget, tempId: uniqueId()};
- }
- export function getDefaultPosition(index: number, displayType: DisplayType) {
- return {
- x: (DEFAULT_WIDGET_WIDTH * index) % NUM_DESKTOP_COLS,
- y: Number.MAX_SAFE_INTEGER,
- w: DEFAULT_WIDGET_WIDTH,
- h: displayType === DisplayType.BIG_NUMBER ? 1 : 2,
- minH: displayType === DisplayType.BIG_NUMBER ? 1 : 2,
- };
- }
- export function getMobileLayout(desktopLayout: Layout[], widgets: Widget[]) {
- if (desktopLayout.length === 0) {
-
-
- return [];
- }
- const layoutWidgetPairs = zip(desktopLayout, widgets) as [Layout, Widget][];
-
- const sorted = sortBy(layoutWidgetPairs, ['0.y', '0.x']);
- const mobileLayout = sorted.map(([layout, widget], index) => ({
- ...layout,
- x: 0,
- y: index * 2,
- w: 2,
- h: widget.displayType === DisplayType.BIG_NUMBER ? 1 : 2,
- }));
- return mobileLayout;
- }
- export function getDashboardLayout(widgets: Widget[]): Layout[] {
- type WidgetWithDefinedLayout = Omit<Widget, 'layout'> & {layout: WidgetLayout};
- return widgets
- .filter((widget): widget is WidgetWithDefinedLayout => defined(widget.layout))
- .map(({layout, ...widget}) => ({
- ...layout,
- i: constructGridItemKey(widget),
- }));
- }
- export function pickDefinedStoreKeys(layout: Layout): WidgetLayout {
-
- return pickBy(
- layout,
- (value, key) => defined(value) && STORE_KEYS.includes(key)
- ) as WidgetLayout;
- }
- export function getDefaultWidgetHeight(displayType: DisplayType): number {
- return displayType === DisplayType.BIG_NUMBER ? 1 : 2;
- }
- export function getInitialColumnDepths() {
- return Array(NUM_DESKTOP_COLS).fill(0);
- }
- export function calculateColumnDepths(
- layouts: Pick<Layout, 'h' | 'w' | 'x' | 'y'>[]
- ): number[] {
- const depths = getInitialColumnDepths();
-
- layouts.forEach(({x, w, y, h}) => {
-
- for (let col = x; col < x + w; col++) {
- depths[col] = Math.max(y + h, depths[col]);
- }
- });
- return depths;
- }
- export function getNextAvailablePosition(
- initialColumnDepths: number[],
- height: number
- ): NextPosition {
- const columnDepths = [...initialColumnDepths];
- const maxColumnDepth = Math.max(...columnDepths);
-
-
-
- for (let currDepth = 0; currDepth <= maxColumnDepth; currDepth++) {
- for (let start = 0; start <= columnDepths.length - DEFAULT_WIDGET_WIDTH; start++) {
- if (columnDepths[start] > currDepth) {
-
- continue;
- }
-
-
-
- const end = start + DEFAULT_WIDGET_WIDTH;
- if (columnDepths.slice(start, end).every(val => val <= currDepth)) {
- for (let col = start; col < start + DEFAULT_WIDGET_WIDTH; col++) {
- columnDepths[col] = currDepth + height;
- }
- return [{x: start, y: currDepth}, [...columnDepths]];
- }
- }
- }
- for (let col = 0; col < DEFAULT_WIDGET_WIDTH; col++) {
- columnDepths[col] = maxColumnDepth;
- }
- return [{x: 0, y: maxColumnDepth}, [...columnDepths]];
- }
- export function assignDefaultLayout<T extends Pick<Widget, 'displayType' | 'layout'>>(
- widgets: T[],
- initialColumnDepths: number[]
- ): T[] {
- let columnDepths = [...initialColumnDepths];
- const newWidgets = widgets.map(widget => {
- if (defined(widget.layout)) {
- return widget;
- }
- const height = getDefaultWidgetHeight(widget.displayType);
- const [nextPosition, nextColumnDepths] = getNextAvailablePosition(
- columnDepths,
- height
- );
- columnDepths = nextColumnDepths;
- return {
- ...widget,
- layout: {
- ...nextPosition,
- h: height,
- minH: height,
- w: DEFAULT_WIDGET_WIDTH,
- },
- };
- });
- return newWidgets;
- }
- export function enforceWidgetHeightValues(widget: Widget): Widget {
- const {displayType, layout} = widget;
- const nextWidget = {
- ...widget,
- };
- if (!defined(layout)) {
- return nextWidget;
- }
- const minH = getDefaultWidgetHeight(displayType);
- const nextLayout = {
- ...layout,
- h: Math.max(layout?.h ?? minH, minH),
- minH,
- };
- return {...nextWidget, layout: nextLayout};
- }
- export function generateWidgetsAfterCompaction(widgets: Widget[]) {
-
-
- const nextLayout = compact(getDashboardLayout(widgets), 'vertical', NUM_DESKTOP_COLS);
- return widgets.map(widget => {
- const layout = nextLayout.find(({i}) => i === constructGridItemKey(widget));
- if (!layout) {
- return widget;
- }
- return {...widget, layout};
- });
- }
- export function isValidLayout(layout: Layout) {
- return !isNaN(layout.x) && !isNaN(layout.y) && layout.w > 0 && layout;
- }
|