Skip to content

Commit cf2525d

Browse files
authored
internal: allow studio panel to be draggable (#31747)
* internal: allow studio panel to be draggable * change width calculation when screen is very small so panel can still be draggable * add tests and fix panel 4 width when studio panel is not open * remove unused variable
1 parent 2e0a738 commit cf2525d

File tree

4 files changed

+207
-27
lines changed

4 files changed

+207
-27
lines changed

packages/app/src/runner/ResizablePanels.cy.tsx

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import { runnerConstants } from './runner-constants'
55
// default values
66
const defaultPanel1Width = runnerConstants.defaultSpecListWidth
77
const defaultPanel2Width = runnerConstants.defaultReporterWidth
8+
const defaultPanel4Width = runnerConstants.defaultStudioWidth
89
const minPanel1Width = 100
910
const minPanel2Width = 100
1011
const minPanel3Width = 500
12+
const minPanel4Width = runnerConstants.absoluteStudioMinimum
1113

1214
// helpers
1315
const assertWidth = (panel: ResizablePanelName, width: number) => {
@@ -38,9 +40,11 @@ describe('<ResizablePanels />', { viewportWidth: 1500, defaultCommandTimeout: 40
3840
v-slots={slotContents}
3941
initialPanel1Width={defaultPanel1Width}
4042
initialPanel2Width={defaultPanel2Width}
43+
initialPanel4Width={defaultPanel4Width}
4144
minPanel1Width={minPanel1Width}
4245
minPanel2Width={minPanel2Width}
4346
minPanel3Width={minPanel3Width}
47+
minPanel4Width={minPanel4Width}
4448
/>
4549
</div>))
4650
})
@@ -106,6 +110,128 @@ describe('<ResizablePanels />', { viewportWidth: 1500, defaultCommandTimeout: 40
106110
})
107111
})
108112

113+
describe('when panel 4 is shown', () => {
114+
beforeEach(() => {
115+
cy.mount(() => (
116+
<div class="flex">
117+
<div class="h-screen">
118+
<ResizablePanels
119+
maxTotalWidth={2000}
120+
v-slots={slotContents}
121+
initialPanel1Width={defaultPanel1Width}
122+
initialPanel2Width={defaultPanel2Width}
123+
initialPanel4Width={defaultPanel4Width}
124+
minPanel1Width={minPanel1Width}
125+
minPanel2Width={minPanel2Width}
126+
minPanel3Width={minPanel3Width}
127+
minPanel4Width={minPanel4Width}
128+
showPanel4={true}
129+
/>
130+
</div></div>))
131+
})
132+
133+
it('the panels can be resized', () => {
134+
assertWidth('panel1', defaultPanel1Width)
135+
dragHandleToClientX('panel1', 500)
136+
assertWidth('panel1', 500)
137+
dragHandleToClientX('panel1', 400)
138+
assertWidth('panel1', 400)
139+
140+
assertWidth('panel2', defaultPanel2Width)
141+
dragHandleToClientX('panel2', 800)
142+
assertWidth('panel2', 400)
143+
dragHandleToClientX('panel2', 700)
144+
assertWidth('panel2', 300)
145+
146+
assertWidth('panel4', defaultPanel4Width)
147+
dragHandleToClientX('panel4', 1300)
148+
assertWidth('panel4', 700)
149+
dragHandleToClientX('panel4', 1500)
150+
assertWidth('panel4', 500)
151+
})
152+
153+
it('panel 1 can be resized between its minimum allowed width and maximum available space', () => {
154+
// drag panel 1 to its minimum width and attempt to go below it
155+
assertWidth('panel1', defaultPanel1Width)
156+
dragHandleToClientX('panel1', 100)
157+
dragHandleToClientX('panel1', 99)
158+
assertWidth('panel1', minPanel1Width)
159+
dragHandleToClientX('panel1', 50)
160+
assertWidth('panel1', minPanel1Width)
161+
162+
// drag panel 1 to the maximum space available and attempt to go above it
163+
dragHandleToClientX('panel1', 710)
164+
dragHandleToClientX('panel1', 800)
165+
assertWidth('panel1', 710)
166+
dragHandleToClientX('panel1', 900)
167+
assertWidth('panel1', 710)
168+
169+
// panel 2 was not reduced
170+
assertWidth('panel2', defaultPanel2Width)
171+
172+
// panel 3 reached its minimum allowed size
173+
assertWidth('panel3', 500)
174+
175+
// panel 4 was not reduced
176+
assertWidth('panel4', defaultPanel4Width)
177+
})
178+
179+
it('panel 2 can be resized between its minimum allowed width and maximum available space', () => {
180+
// drag panel 2 to its minimum width and attempt to go below it
181+
assertWidth('panel2', defaultPanel2Width)
182+
dragHandleToClientX('panel2', 380)
183+
dragHandleToClientX('panel2', 200)
184+
assertWidth('panel2', minPanel2Width)
185+
dragHandleToClientX('panel2', 180)
186+
assertWidth('panel2', minPanel2Width)
187+
188+
// drag panel 2 to the maximum space available and attempt to go above it
189+
dragHandleToClientX('panel2', 1160)
190+
dragHandleToClientX('panel2', 1200)
191+
assertWidth('panel2', 880)
192+
dragHandleToClientX('panel2', 1300)
193+
assertWidth('panel2', 880)
194+
195+
// panel 1 was not reduced
196+
assertWidth('panel1', defaultPanel1Width)
197+
198+
// panel 3 reached its minimum allowed size
199+
assertWidth('panel3', minPanel3Width)
200+
201+
// panel 4 was not reduced
202+
assertWidth('panel4', defaultPanel4Width)
203+
})
204+
205+
it('panel 4 can be resized between its minimum allowed width and maximum available space', () => {
206+
// since its starting width is the same as its minimum width,
207+
// drag panel 4 to a different width, then drag it to its minimum width and attempt to go below it
208+
assertWidth('panel4', defaultPanel4Width)
209+
dragHandleToClientX('panel4', 1400)
210+
assertWidth('panel4', 600)
211+
dragHandleToClientX('panel4', 1660)
212+
dragHandleToClientX('panel4', 1800)
213+
assertWidth('panel4', minPanel4Width)
214+
dragHandleToClientX('panel4', 1900)
215+
assertWidth('panel4', minPanel4Width)
216+
217+
// drag panel 4 to the maximum space available and attempt to go above it
218+
dragHandleToClientX('panel4', 1230)
219+
dragHandleToClientX('panel4', 1100)
220+
assertWidth('panel4', 770)
221+
dragHandleToClientX('panel4', 900)
222+
assertWidth('panel4', 770)
223+
224+
// panel 1 was not reduced
225+
assertWidth('panel1', defaultPanel1Width)
226+
227+
// panel 2 was not reduced
228+
assertWidth('panel2', defaultPanel2Width)
229+
230+
// panel 3 reached its absolute minimum allowed size
231+
assertWidth('panel3', minPanel3Width)
232+
})
233+
})
234+
109235
describe('when there is a side nav', () => {
110236
it('handles being offset by some distance on the left', () => {
111237
cy.mount(() => (

packages/app/src/runner/ResizablePanels.vue

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
id="resizable-panels-root"
55
class="flex"
66
:class="{
7-
'select-none': panel1IsDragging || panel2IsDragging,
7+
'select-none': panel1IsDragging || panel2IsDragging || panel4IsDragging,
88
}"
99
@mouseup="handleMouseup"
1010
@mousemove="handleMousemove"
@@ -14,7 +14,7 @@
1414
v-show="showPanel1"
1515
data-cy="specs-list-panel"
1616
class="h-full shrink-0 z-20 relative"
17-
:style="{width: `${panel1Width}px`}"
17+
:style="{ width: `${panel1Width}px` }"
1818
>
1919
<slot
2020
name="panel1"
@@ -32,7 +32,7 @@
3232
v-show="showPanel2"
3333
data-cy="reporter-panel"
3434
class="h-full shrink-0 z-10 relative"
35-
:style="{width: `${panel2Width}px`}"
35+
:style="{ width: `${panel2Width}px` }"
3636
>
3737
<slot name="panel2" />
3838

@@ -46,7 +46,7 @@
4646
<div
4747
data-cy="aut-panel"
4848
class="grow h-full bg-gray-100 relative"
49-
:class="{'pointer-events-none':panel2IsDragging}"
49+
:class="{ 'pointer-events-none': panel2IsDragging || panel4IsDragging }"
5050
:style="{ width: `${panel3width}px` }"
5151
>
5252
<slot
@@ -58,12 +58,15 @@
5858
<div
5959
v-show="showPanel4"
6060
data-cy="panel-4"
61-
class="h-full bg-gray-100 relative"
62-
:style="{width: `${panel4Width}px`}"
61+
class="h-full shrink-0 z-10 bg-gray-100 relative"
62+
:style="{ width: `${panel4Width}px` }"
6363
>
64-
<slot
65-
name="panel4"
66-
:width="panel4Width"
64+
<slot name="panel4" />
65+
66+
<div
67+
data-cy="panel4ResizeHandle"
68+
class="cursor-ew-resize h-full top-0 left-[-6px] w-[10px] z-30 absolute"
69+
@mousedown="handleMousedown('panel4', $event)"
6770
/>
6871
</div>
6972
</div>
@@ -86,9 +89,11 @@ const props = withDefaults(defineProps<{
8689
showPanel4?: boolean // studio in runner
8790
initialPanel1Width?: number
8891
initialPanel2Width?: number
92+
initialPanel4Width?: number
8993
minPanel1Width?: number
9094
minPanel2Width?: number
9195
minPanel3Width?: number
96+
minPanel4Width?: number
9297
maxTotalWidth?: number // windowWidth in runner
9398
offsetLeft?: number
9499
}>(), {
@@ -97,23 +102,28 @@ const props = withDefaults(defineProps<{
97102
showPanel4: false,
98103
initialPanel1Width: runnerConstants.defaultSpecListWidth,
99104
initialPanel2Width: runnerConstants.defaultReporterWidth,
105+
initialPanel4Width: runnerConstants.defaultStudioWidth,
100106
minPanel1Width: 200,
101107
minPanel2Width: 220,
102108
minPanel3Width: 100,
109+
minPanel4Width: 340,
103110
maxTotalWidth: window.innerWidth,
104111
offsetLeft: 0,
105112
})
106113
107114
const emit = defineEmits<{
108115
(e: 'resizeEnd', value: DraggablePanel): void
109-
(e: 'panelWidthUpdated', value: {panel: DraggablePanel, width: number}): void
116+
(e: 'panelWidthUpdated', value: { panel: DraggablePanel, width: number }): void
110117
}>()
111118
112119
const panel1HandleX = ref(props.initialPanel1Width)
113120
const panel2HandleX = ref(props.initialPanel2Width + props.initialPanel1Width)
121+
const panel4HandleX = ref(props.initialPanel2Width + props.initialPanel1Width + props.initialPanel4Width)
114122
const panel1IsDragging = ref(false)
115123
const panel2IsDragging = ref(false)
124+
const panel4IsDragging = ref(false)
116125
const cachedPanel1Width = ref<number>(props.initialPanel1Width) // because panel 1 (the inline specs list) can be opened and closed in the UI, we cache the width
126+
const cachedPanel4Width = ref(props.initialPanel4Width)
117127
const panel2Width = ref(props.initialPanel2Width)
118128
119129
const handleMousedown = (panel: DraggablePanel, event: MouseEvent) => {
@@ -122,10 +132,13 @@ const handleMousedown = (panel: DraggablePanel, event: MouseEvent) => {
122132
} else if (panel === 'panel2') {
123133
panel2IsDragging.value = true
124134
panel2HandleX.value = event.clientX
135+
} else if (panel === 'panel4') {
136+
panel4IsDragging.value = true
137+
panel4HandleX.value = event.clientX
125138
}
126139
}
127140
const handleMousemove = (event: MouseEvent) => {
128-
if (!panel1IsDragging.value && !panel2IsDragging.value) {
141+
if (!panel1IsDragging.value && !panel2IsDragging.value && !panel4IsDragging.value) {
129142
// nothing is dragging, ignore mousemove
130143
131144
return
@@ -139,6 +152,15 @@ const handleMousemove = (event: MouseEvent) => {
139152
panel2HandleX.value = event.clientX
140153
panel2Width.value = event.clientX - props.offsetLeft - panel1Width.value
141154
emit('panelWidthUpdated', { panel: 'panel2', width: panel2Width.value })
155+
} else if (panel4IsDragging.value && isNewWidthAllowed(event.clientX, 'panel4')) {
156+
panel4HandleX.value = event.clientX
157+
// Calculate width from the right edge of the window
158+
// so that when we drag the panel to the left, it grows
159+
// and when we drag it to the right, it shrinks
160+
const rightEdge = props.maxTotalWidth + props.offsetLeft
161+
162+
cachedPanel4Width.value = rightEdge - event.clientX
163+
emit('panelWidthUpdated', { panel: 'panel4', width: panel4Width.value })
142164
}
143165
}
144166
const handleMouseup = () => {
@@ -149,30 +171,37 @@ const handleMouseup = () => {
149171
return
150172
}
151173
152-
handleResizeEnd('panel2')
153-
panel2IsDragging.value = false
174+
if (panel2IsDragging.value) {
175+
handleResizeEnd('panel2')
176+
panel2IsDragging.value = false
177+
}
178+
179+
if (panel4IsDragging.value) {
180+
handleResizeEnd('panel4')
181+
panel4IsDragging.value = false
182+
}
154183
}
155184
156185
const maxPanel1Width = computed(() => {
157-
const unavailableWidth = panel2Width.value + props.minPanel3Width
186+
const unavailableWidth = panel2Width.value + props.minPanel3Width + panel4Width.value
158187
159188
return props.maxTotalWidth - unavailableWidth
160189
})
161190
162-
const panel4Width = computed(() => {
163-
if (!props.showPanel4) {
191+
const panel1Width = computed(() => {
192+
if (!props.showPanel1) {
164193
return 0
165194
}
166195
167-
return runnerConstants.defaultStudioWidth
196+
return cachedPanel1Width.value
168197
})
169198
170-
const panel1Width = computed(() => {
171-
if (!props.showPanel1) {
199+
const panel4Width = computed(() => {
200+
if (!props.showPanel4) {
172201
return 0
173202
}
174203
175-
return cachedPanel1Width.value
204+
return cachedPanel4Width.value
176205
})
177206
178207
const maxPanel2Width = computed(() => {
@@ -192,6 +221,12 @@ const panel3width = computed(() => {
192221
return panel3SpaceAvailable < props.minPanel3Width ? minimumWithBuffer : panel3SpaceAvailable
193222
})
194223
224+
const maxPanel4Width = computed(() => {
225+
const unavailableWidth = panel1Width.value + panel2Width.value + props.minPanel3Width
226+
227+
return props.maxTotalWidth - unavailableWidth
228+
})
229+
195230
function handleResizeEnd (panel: DraggablePanel) {
196231
emit('resizeEnd', panel)
197232
}
@@ -212,15 +247,29 @@ function isNewWidthAllowed (mouseClientX: number, panel: DraggablePanel) {
212247
return result
213248
}
214249
215-
const newWidth = mouseClientX - props.offsetLeft - panel1Width.value
250+
if (panel === 'panel2') {
251+
const newWidth = mouseClientX - props.offsetLeft - panel1Width.value
252+
253+
if (isMaxWidthSmall && newWidth > fallbackWidth) {
254+
return true
255+
}
256+
257+
return panel2IsDragging.value && newWidth >= props.minPanel2Width && newWidth <= maxPanel2Width.value
258+
}
259+
260+
if (panel === 'panel4') {
261+
const rightEdge = props.maxTotalWidth + props.offsetLeft
262+
const newWidth = rightEdge - mouseClientX
263+
264+
if (isMaxWidthSmall && newWidth >= props.minPanel4Width) {
265+
return true
266+
}
216267
217-
if (isMaxWidthSmall && newWidth > fallbackWidth) {
218-
return true
268+
return panel4IsDragging.value && newWidth >= props.minPanel4Width && newWidth <= maxPanel4Width.value
219269
}
220270
221-
return panel2IsDragging.value && newWidth >= props.minPanel2Width && newWidth <= maxPanel2Width.value
271+
return false
222272
}
223-
224273
watchEffect(() => {
225274
if (!props.showPanel1) {
226275
emit('panelWidthUpdated', { panel: 'panel1', width: 0 })
@@ -231,7 +280,7 @@ watchEffect(() => {
231280
if (!props.showPanel4) {
232281
emit('panelWidthUpdated', { panel: 'panel4', width: 0 })
233282
} else if (props.showPanel4) {
234-
emit('panelWidthUpdated', { panel: 'panel4', width: panel4Width.value })
283+
emit('panelWidthUpdated', { panel: 'panel4', width: cachedPanel4Width.value })
235284
}
236285
})
237286

0 commit comments

Comments
 (0)