Skip to content

Commit be60af1

Browse files
committed
feat: use DraggableGridLayout for all dashboard rendering
1 parent a453064 commit be60af1

File tree

5 files changed

+137
-119
lines changed

5 files changed

+137
-119
lines changed

packages/analytics/dashboard-renderer/src/components/DashboardRenderer.vue

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
<template>
2-
<div class="kong-ui-public-dashboard-renderer">
2+
<div
3+
class="kong-ui-public-dashboard-renderer"
4+
:class="{ editable: context.editable }"
5+
>
36
<KAlert
47
v-if="!queryBridge"
58
appearance="danger"
69
>
710
{{ i18n.t('renderer.noQueryBridge') }}
811
</KAlert>
9-
<component
10-
:is="context.editable ? DraggableGridLayout : GridLayout"
12+
<DraggableGridLayout
1113
v-else
1214
ref="gridLayoutRef"
15+
:editable="context.editable"
1316
:grid-size="model.gridSize"
1417
:tile-height="model.tileHeight"
1518
:tiles="gridTiles"
@@ -30,25 +33,26 @@
3033
:height="tile.layout.size.rows * (model.tileHeight || DEFAULT_TILE_HEIGHT) + parseInt(KUI_SPACE_70, 10)"
3134
:query-ready="queryReady"
3235
:refresh-counter="refreshCounter"
36+
:tile="tile"
3337
:tile-id="tile.id"
38+
@chart-data="onChartData"
3439
@duplicate-tile="onDuplicateTile(tile)"
3540
@edit-tile="onEditTile(tile)"
3641
@remove-tile="onRemoveTile(tile)"
3742
@zoom-time-range="emit('zoom-time-range', $event)"
3843
/>
3944
</template>
40-
</component>
45+
</DraggableGridLayout>
4146
</div>
4247
</template>
4348

4449
<script setup lang="ts">
4550
import type { DashboardRendererContext, DashboardRendererContextInternal, GridTile } from '../types'
4651
import type { AbsoluteTimeRangeV4, DashboardConfig, TileConfig, SlottableOptions, TileDefinition } from '@kong-ui-public/analytics-utilities'
4752
import DashboardTile from './DashboardTile.vue'
48-
import { computed, inject, ref } from 'vue'
53+
import { computed, inject, ref, nextTick } from 'vue'
4954
import type { ComponentPublicInstance } from 'vue'
5055
import composables from '../composables'
51-
import GridLayout from './layout/GridLayout.vue'
5256
import DraggableGridLayout from './layout/DraggableGridLayout.vue'
5357
import type { DraggableGridLayoutExpose } from './layout/DraggableGridLayout.vue'
5458
import type { AnalyticsBridge, TimeRangeV4 } from '@kong-ui-public/analytics-utilities'
@@ -236,6 +240,11 @@ const onRemoveTile = (tile: GridTile<TileDefinition>) => {
236240
}
237241
}
238242
243+
const onChartData = async () => {
244+
await nextTick()
245+
gridLayoutRef.value.redraw()
246+
}
247+
239248
const refreshTiles = () => {
240249
refreshCounter.value++
241250
}
@@ -265,11 +274,14 @@ defineExpose({ refresh: refreshTiles })
265274
background: var(--kui-color-background-transparent, $kui-color-background-transparent);
266275
border: var(--kui-border-width-10, $kui-border-width-10) solid var(--kui-color-border, $kui-color-border);
267276
border-radius: var(--kui-border-radius-20, $kui-border-radius-20);
268-
height: 100%;
269277
270278
&.slottable-tile {
271279
padding: var(--kui-space-60, $kui-space-60);
272280
}
273281
}
282+
283+
&.editable .tile-container {
284+
height: 100%;
285+
}
274286
}
275287
</style>

packages/analytics/dashboard-renderer/src/components/DashboardTile.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
</template>
117117
<script setup lang="ts">
118118
import type { DashboardRendererContextInternal } from '../types'
119-
import { type DashboardTileType, formatTime, type TileDefinition, TimePeriods } from '@kong-ui-public/analytics-utilities'
119+
import { type DashboardTileType, formatTime, type TileDefinition, type TileConfig, TimePeriods } from '@kong-ui-public/analytics-utilities'
120120
import { type Component, computed, inject, nextTick, onMounted, ref, watch } from 'vue'
121121
import '@kong-ui-public/analytics-chart/dist/style.css'
122122
import '@kong-ui-public/analytics-metric-provider/dist/style.css'
@@ -144,6 +144,7 @@ const props = withDefaults(defineProps<{
144144
height?: number
145145
queryReady: boolean
146146
refreshCounter: number
147+
tile: TileConfig
147148
tileId: string | number
148149
}>(), {
149150
height: DEFAULT_TILE_HEIGHT,
@@ -154,6 +155,7 @@ const emit = defineEmits<{
154155
(e: 'duplicate-tile', tile: TileDefinition): void
155156
(e: 'remove-tile', tile: TileDefinition): void
156157
(e: 'zoom-time-range', newTimeRange: AbsoluteTimeRangeV4): void
158+
(e: 'chart-data', data: ExploreResultV4)
157159
}>()
158160
159161
const queryBridge: AnalyticsBridge | undefined = inject(INJECT_QUERY_PROVIDER)
@@ -290,6 +292,7 @@ const removeTile = () => {
290292
291293
const onChartData = (data: ExploreResultV4) => {
292294
chartData.value = data
295+
emit('chart-data', data)
293296
}
294297
295298
const setExportModalVisibility = (val: boolean) => {
@@ -305,7 +308,6 @@ const exportCsv = () => {
305308
.tile-boundary {
306309
display: flex;
307310
flex-direction: column;
308-
height: v-bind('`${height}px`');
309311
overflow: hidden;
310312
311313
&.editable:hover {

packages/analytics/dashboard-renderer/src/components/layout/DraggableGridLayout.vue

Lines changed: 91 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,66 @@
11
<template>
2-
<div
3-
ref="gridContainer"
4-
class="grid-stack"
5-
>
2+
<div class="grid-wrapper">
63
<div
7-
v-for="tile in props.tiles"
8-
:key="tile.id"
9-
class="grid-stack-item"
10-
:data-id="`${tile.id}`"
11-
:data-testid="`grid-stack-item-${tile.id}`"
12-
:gs-h="tile.layout.size.rows"
13-
:gs-lazy-load="true"
14-
:gs-w="tile.layout.size.cols"
15-
:gs-x="tile.layout.position.col"
16-
:gs-y="tile.layout.position.row"
4+
ref="gridContainer"
5+
class="grid-stack"
176
>
18-
<div class="grid-stack-item-content">
19-
<slot
20-
name="tile"
21-
:tile="tile"
22-
/>
7+
<div
8+
v-for="tile in props.tiles"
9+
:key="tile.id"
10+
class="grid-stack-item"
11+
:data-id="`${tile.id}`"
12+
:data-testid="`grid-stack-item-${tile.id}`"
13+
:gs-h="rowToGridStack(tile.layout.size.rows)"
14+
:gs-lazy-load="true"
15+
:gs-size-to-content="!editable"
16+
:gs-w="tile.layout.size.cols"
17+
:gs-x="tile.layout.position.col"
18+
:gs-y="rowToGridStack(tile.layout.position.row)"
19+
>
20+
<div class="grid-stack-item-content">
21+
<slot
22+
name="tile"
23+
:tile="tile"
24+
/>
25+
</div>
2326
</div>
2427
</div>
2528
</div>
2629
</template>
2730

2831
<script lang='ts' setup generic="T">
29-
import { onMounted, onUnmounted, ref, watch, nextTick, watchEffect } from 'vue'
32+
import { onMounted, onUnmounted, computed, ref, watch, nextTick, watchEffect } from 'vue'
3033
import { GridStack } from 'gridstack'
3134
import type { GridStackNode } from 'gridstack'
32-
import type { GridSize, GridTile } from 'src/types'
35+
import type { GridTile } from 'src/types'
3336
import 'gridstack/dist/gridstack.min.css'
3437
import 'gridstack/dist/gridstack-extra.min.css'
38+
import { DEFAULT_TILE_HEIGHT, MIN_CELL_HEIGHT } from '../../constants'
39+
import { useDebounce } from '@kong-ui-public/core'
3540
3641
export type DraggableGridLayoutExpose<T> = {
3742
removeWidget: (id: number | string) => void
43+
redraw: () => void
3844
tiles: GridTile<T>[]
39-
gridSize: GridSize
4045
}
4146
4247
const props = withDefaults(defineProps<{
4348
tiles: GridTile<T>[]
44-
gridSize: GridSize
4549
tileHeight?: number
50+
editable?: boolean
4651
}>(), {
47-
tileHeight: 200,
52+
tileHeight: DEFAULT_TILE_HEIGHT,
4853
})
54+
55+
const rowToGridStack = (row) => props.editable ? row : row * Math.floor(props.tileHeight / MIN_CELL_HEIGHT)
56+
const gridStackToRow = (gridRow) => props.editable ? gridRow : Math.floor(gridRow / Math.floor(props.tileHeight / MIN_CELL_HEIGHT))
57+
4958
const emit = defineEmits<{
5059
(e: 'update-tiles', tiles: GridTile<T>[]): void
5160
}>()
5261
62+
const headerCursor = computed(() => props.editable ? 'move' : 'default')
63+
5364
const gridContainer = ref<HTMLDivElement | null>(null)
5465
5566
const tilesRef = ref<Map<string, GridTile<any>>>(new Map(props.tiles.map(t => [`${t.id}`, t])))
@@ -67,8 +78,8 @@ const makeTilesFromGridItems = (items: GridStackNode[]) => {
6778
return {
6879
...tile,
6980
layout: {
70-
position: { col: Number(item.x), row: Number(item.y) },
71-
size: { cols: Number(item.w), rows: Number(item.h) },
81+
position: { col: Number(item.x), row: gridStackToRow(Number(item.y)) },
82+
size: { cols: Number(item.w), rows: gridStackToRow(Number(item.h)) },
7283
},
7384
} satisfies GridTile<T>
7485
}
@@ -92,18 +103,36 @@ const removeHandler = (_: Event, items: GridStackNode[]) => {
92103
emit('update-tiles', Array.from(tilesRef.value.values()))
93104
}
94105
95-
onMounted(() => {
106+
onMounted(async () => {
96107
if (gridContainer.value) {
97108
grid = GridStack.init({
98-
column: props.gridSize.cols,
99-
cellHeight: props.tileHeight,
109+
margin: 10,
110+
column: 6,
111+
cellHeight: props.editable ? props.tileHeight : MIN_CELL_HEIGHT,
112+
cellHeightThrottle: 100,
100113
resizable: { handles: 'se, sw' },
101114
handle: '.tile-header',
102-
115+
staticGrid: !props.editable,
116+
columnOpts: !props.editable ? {
117+
breakpointForWindow: true,
118+
columnMax: 6,
119+
breakpoints: [{
120+
w: 640, c: 1, // mobile
121+
}, {
122+
w: 1024, c: 2, // tablet
123+
}, {
124+
w: 1280, c: 4, // laptop
125+
}, {
126+
w: 1536, c: 6, // desktop
127+
}],
128+
} : {},
103129
}, gridContainer.value)
104-
grid.on('change', updateTiles)
105-
grid.on('added', updateTiles)
106-
grid.on('removed', removeHandler)
130+
131+
if (props.editable) {
132+
grid.on('change', updateTiles)
133+
grid.on('added', updateTiles)
134+
grid.on('removed', removeHandler)
135+
}
107136
}
108137
})
109138
@@ -113,6 +142,16 @@ onUnmounted(() => {
113142
}
114143
})
115144
145+
const { debounce } = useDebounce()
146+
147+
const debouncedRedraw = debounce(() => {
148+
props.tiles.forEach(({ id }) => {
149+
const el = gridContainer.value.querySelector(makeSelector(id)) as HTMLElement
150+
grid.resizeToContent(el)
151+
grid.compact()
152+
})
153+
}, 100)
154+
116155
const removeWidget = (id: number | string) => {
117156
if (grid && gridContainer.value) {
118157
const el = gridContainer.value.querySelector(makeSelector(id)) as HTMLElement
@@ -131,7 +170,7 @@ watch(() => props.tiles.length, async (newLen, oldLen) => {
131170
grid.makeWidget(makeSelector(tile.id), {
132171
autoPosition: true,
133172
w: tile.layout.size.cols,
134-
h: tile.layout.size.rows,
173+
h: rowToGridStack(tile.layout.size.rows),
135174
})
136175
}
137176
}
@@ -144,14 +183,30 @@ watchEffect(() => {
144183
})
145184
})
146185
147-
defineExpose({ removeWidget })
186+
defineExpose({ removeWidget, redraw: debouncedRedraw })
148187
149188
</script>
150189

151190
<style lang="scss" scoped>
191+
.grid-wrapper {
192+
/*
193+
* If you want space between your items in GridStack, you have to provide a
194+
* margin. If you provide a margin, GridStack forces you to have an outer
195+
* margin not just between tiles. So, anything using this component is always
196+
* 20 pixels (2 * margin size) shorter in both the x and y axis than the space
197+
* that's provided. To solve this we can wrap it with a basic div and give the
198+
* GridStack container negative margin. That makes it so content actually fits
199+
* the intended layout of anything that uses this component.
200+
*/
201+
.grid-stack {
202+
margin: -10px;
203+
}
204+
}
205+
152206
:deep(.tile-header) {
153-
cursor: move;
207+
cursor: v-bind('headerCursor');
154208
}
209+
155210
$rotate-values: (
156211
'se': 0deg,
157212
'sw': 90deg,

0 commit comments

Comments
 (0)