Skip to content

Commit a698936

Browse files
authored
Merge pull request #43 from FivEawE/issue_9 (fix #9)
Make the pane resizable on mobile
2 parents f32faa6 + 3aaa193 commit a698936

File tree

7 files changed

+195
-45
lines changed

7 files changed

+195
-45
lines changed

src/assets/tailwind.css

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,11 @@ div[contenteditable='true']:focus {
2222

2323
.wrapper,
2424
.wrapper--forced {
25-
grid-template-rows: auto minmax(0, 1fr) auto minmax(0, 1fr);
26-
grid-template-columns: 1fr;
27-
}
28-
29-
.column-resizer {
30-
@apply relative z-50;
31-
}
25+
--top: minmax(0, 1fr);
26+
--bottom: minmax(0, 1fr);
3227

33-
.column-resizer:hover::after {
34-
content: '';
35-
@apply absolute top-0 left-[-6px] right-[-6px] bg-brand-default h-full;
28+
grid-template-rows: auto minmax(0, var(--top)) 12px auto minmax(0, var(--bottom));
29+
grid-template-columns: 1fr;
3630
}
3731

3832
@screen md {
@@ -41,7 +35,7 @@ div[contenteditable='true']:focus {
4135
--right: minmax(0, 1fr);
4236

4337
grid-template-rows: auto minmax(0, 1fr);
44-
grid-template-columns: minmax(0, var(--left)) 2px minmax(0, var(--right));
38+
grid-template-columns: minmax(0, var(--left)) 12px minmax(0, var(--right));
4539
}
4640
}
4741

src/components/gridResizer/dot.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { Component, JSX } from 'solid-js';
2+
3+
export const Dot: Component = () => {
4+
return <span class="m-1 w-1 h-1 rounded-full bg-blueGray-200" />;
5+
};
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import {
2+
Component,
3+
JSX,
4+
splitProps,
5+
createSignal,
6+
createEffect,
7+
onCleanup,
8+
} from 'solid-js';
9+
import { throttle } from '../../utils/throttle';
10+
import { Dot } from './dot';
11+
12+
interface GridResizerProps extends JSX.HTMLAttributes<HTMLDivElement> {
13+
ref?: (el: HTMLDivElement) => any | undefined;
14+
isHorizontal: boolean;
15+
direction: 'horizontal' | 'vertical';
16+
onResize: (clientX: number, clientY: number) => void;
17+
}
18+
19+
export const GridResizer: Component<GridResizerProps> = (props) => {
20+
const [local, other] = splitProps(props, [
21+
'ref',
22+
'class',
23+
'isHorizontal',
24+
'direction',
25+
'onResize',
26+
]);
27+
28+
const [isDragging, setIsDragging] = createSignal(false);
29+
30+
const onResizeStart = () => {
31+
setIsDragging(true);
32+
};
33+
34+
const onResizeEnd = () => {
35+
setIsDragging(false);
36+
};
37+
38+
const onMouseMove = throttle((e: MouseEvent) => {
39+
local.onResize(e.clientX, e.clientY);
40+
}, 10);
41+
42+
const onTouchMove = throttle((e: TouchEvent) => {
43+
const touch = e.touches[0];
44+
local.onResize(touch.clientX, touch.clientY);
45+
}, 10);
46+
47+
const setRef = (el: HTMLDivElement) => {
48+
if (local.ref) {
49+
local.ref(el);
50+
}
51+
52+
el.addEventListener('mousedown', onResizeStart);
53+
el.addEventListener('touchstart', onResizeStart);
54+
55+
onCleanup(() => {
56+
el.removeEventListener('mousedown', onResizeStart);
57+
el.removeEventListener('touchstart', onResizeStart);
58+
});
59+
};
60+
61+
createEffect(() => {
62+
if (isDragging()) {
63+
window.addEventListener('mousemove', onMouseMove);
64+
window.addEventListener('mouseup', onResizeEnd);
65+
window.addEventListener('touchmove', onTouchMove);
66+
window.addEventListener('touchend', onResizeEnd);
67+
} else {
68+
window.removeEventListener('mousemove', onMouseMove);
69+
window.removeEventListener('mouseup', onResizeEnd);
70+
window.removeEventListener('touchmove', onTouchMove);
71+
window.removeEventListener('touchend', onResizeEnd);
72+
}
73+
});
74+
75+
const baseClasses = 'justify-center items-center border-blueGray-200 hover:bg-brand-default';
76+
const resizingClasses = () =>
77+
`${isDragging() ? 'bg-brand-default' : 'bg-blueGray-50 dark:bg-blueGray-800'}`;
78+
const directionClasses = () =>
79+
local.direction === 'horizontal'
80+
? `flex-col cursor-col-resize border-l-2 border-r-2 hidden${
81+
!local.isHorizontal ? ' md:flex' : ' '
82+
}`
83+
: `cursor-row-resize border-t-2 border-b-2 flex${!local.isHorizontal ? ' md:hidden' : ' '}`;
84+
const classes = () => `${baseClasses} ${resizingClasses()} ${directionClasses()} ${local.class}`;
85+
86+
return (
87+
<>
88+
<div
89+
class={
90+
isDragging()
91+
? `fixed inset-0 z-50 ${
92+
local.direction === 'horizontal' ? 'cursor-col-resize' : 'cursor-row-resize'
93+
}`
94+
: 'hidden'
95+
}
96+
/>
97+
<div ref={setRef} class={classes()} {...other}>
98+
<Dot />
99+
<Dot />
100+
<Dot />
101+
</div>
102+
</>
103+
);
104+
};

src/components/gridResizer/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './gridResizer';

src/components/repl.tsx

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { editor as mEditor } from 'monaco-editor';
66
import { Preview } from './preview';
77
import { TabItem } from './tab/item';
88
import { TabList } from './tab/list';
9+
import { GridResizer } from './gridResizer';
910
import { Error } from './error';
1011

1112
import type { Tab } from '../';
@@ -203,43 +204,78 @@ export const Repl: Component<ReplProps> = (props) => {
203204
};
204205

205206
/**
206-
* This whole block before the slice of view
207-
* is an experimental resizer, need to tidy this up
207+
* Upcomming 2 blocks before the slice of view is used for horizontal and vertical resizers.
208+
* This first block controls the horizontal resizer.
208209
*/
210+
const adjustPercentage = (percentage: number, lowerBound: number, upperBound: number) => {
211+
if (percentage < lowerBound) {
212+
return lowerBound;
213+
} else if (percentage > upperBound) {
214+
return upperBound;
215+
} else {
216+
return percentage;
217+
}
218+
};
219+
220+
const [horizontalResizer, setHorizontalResizer] = createSignal<HTMLElement>();
209221
const [left, setLeft] = createSignal(1.25);
210-
const [isDragging, setIsDragging] = createSignal(false);
211222

212-
const onMouseMove = throttle((e: MouseEvent) => {
213-
const percentage = e.clientX / (document.body.offsetWidth / 2);
214-
if (percentage < 0.5 || percentage > 1.5) return;
223+
const changeLeft = (clientX: number, _clientY: number) => {
224+
// Adjust the reading according to the width of the resizable panes
225+
const clientXAdjusted = clientX - horizontalResizer()!.offsetWidth / 2;
226+
const widthAdjusted = document.body.offsetWidth - horizontalResizer()!.offsetWidth;
215227

216-
setLeft(percentage);
217-
}, 10);
228+
const percentage = clientXAdjusted / (widthAdjusted / 2);
229+
const percentageAdjusted = adjustPercentage(percentage, 0.5, 1.5);
218230

219-
const onMouseUp = () => setIsDragging(false);
231+
setLeft(percentageAdjusted);
232+
};
220233

221-
createEffect(() => {
222-
if (isDragging()) {
223-
window.addEventListener('mousemove', onMouseMove);
224-
window.addEventListener('mouseup', onMouseUp);
225-
} else {
226-
window.removeEventListener('mousemove', onMouseMove);
227-
window.removeEventListener('mouseup', onMouseUp);
228-
}
229-
});
234+
/**
235+
* This second block controls the vertical resizer.
236+
*/
237+
const [grid, setGrid] = createSignal<HTMLElement>();
238+
const [fileTabs, setFileTabs] = createSignal<HTMLElement>();
239+
const [resultTabs, setResultTabs] = createSignal<HTMLElement>();
240+
const [verticalResizer, setVerticalResizer] = createSignal<HTMLElement>();
241+
const [top, setTop] = createSignal(1);
242+
243+
const changeTop = (_clientX: number, clientY: number) => {
244+
// Adjust the reading according to the height of the resizable panes
245+
const headerSize = document.body.offsetHeight - grid()!.offsetHeight;
246+
const clientYAdjusted =
247+
clientY - headerSize - fileTabs()!.offsetHeight - verticalResizer()!.offsetHeight / 2;
248+
const heightAdjusted =
249+
document.body.offsetHeight -
250+
headerSize -
251+
fileTabs()!.offsetHeight -
252+
verticalResizer()!.offsetHeight -
253+
resultTabs()!.offsetHeight;
254+
255+
const percentage = clientYAdjusted / (heightAdjusted / 2);
256+
const percentageAdjusted = adjustPercentage(percentage, 0.5, 1.5);
257+
258+
setTop(percentageAdjusted);
259+
};
230260

231261
const [reloadSignal, reload] = createSignal(false, { equals: false });
232262

233263
return (
234264
<div
265+
ref={(el) => setGrid(el)}
235266
class="relative grid bg-blueGray-50 h-full overflow-hidden text-blueGray-900 dark:text-blueGray-50 font-sans"
236267
classList={{
237268
'wrapper--forced': props.isHorizontal,
238269
wrapper: !props.isHorizontal,
239270
}}
240-
style={{ '--left': `${left()}fr`, '--right': `${2 - left()}fr` }}
271+
style={{
272+
'--left': `${left()}fr`,
273+
'--right': `${2 - left()}fr`,
274+
'--top': `${top()}fr`,
275+
'--bottom': `${2 - top()}fr`,
276+
}}
241277
>
242-
<TabList class="row-start-1 space-x-2">
278+
<TabList ref={(el) => setFileTabs(el)} class="row-start-1 space-x-2">
243279
<For each={props.tabs}>
244280
{(tab, index) => (
245281
<TabItem active={props.current === id(tab)}>
@@ -345,7 +381,8 @@ export const Repl: Component<ReplProps> = (props) => {
345381
</TabList>
346382

347383
<TabList
348-
class={`row-start-3 border-t-2 border-blueGray-200 ${
384+
ref={(el) => setResultTabs(el)}
385+
class={`row-start-4 border-blueGray-200 ${
349386
props.isHorizontal ? '' : 'md:row-start-1 md:col-start-3 md:border-t-0'
350387
}`}
351388
>
@@ -404,18 +441,25 @@ export const Repl: Component<ReplProps> = (props) => {
404441
ref={props.onEditorReady}
405442
/>
406443

407-
<div
408-
class="column-resizer h-full w-full row-start-1 row-end-3 col-start-2 hidden"
409-
style="cursor: col-resize"
410-
classList={{ 'md:block': !props.isHorizontal }}
411-
onMouseDown={[setIsDragging, true]}
412-
>
413-
<div class="h-full border-blueGray-200 dark:border-blueGray-700 border-l border-r rounded-lg mx-auto w-0"></div>
414-
</div>
444+
<GridResizer
445+
ref={(el) => setVerticalResizer(el)}
446+
isHorizontal={props.isHorizontal}
447+
direction="vertical"
448+
class="row-start-3"
449+
onResize={changeTop}
450+
/>
451+
452+
<GridResizer
453+
ref={(el) => setHorizontalResizer(el)}
454+
isHorizontal={props.isHorizontal}
455+
direction="horizontal"
456+
class="row-start-1 row-end-3 col-start-2"
457+
onResize={changeLeft}
458+
/>
415459

416460
<Show when={!showPreview()}>
417461
<section
418-
class="h-full max-h-screen bg-white dark:bg-blueGray-800 grid focus:outline-none row-start-4 relative divide-y-2 divide-blueGray-200 dark:divide-blueGray-500"
462+
class="h-full max-h-screen bg-white dark:bg-blueGray-800 grid focus:outline-none row-start-5 relative divide-y-2 divide-blueGray-200 dark:divide-blueGray-500"
419463
classList={{ 'md:row-start-2': !props.isHorizontal }}
420464
style="grid-template-rows: minmax(0, 1fr) auto"
421465
>
@@ -481,12 +525,9 @@ export const Repl: Component<ReplProps> = (props) => {
481525
<Preview
482526
reloadSignal={reloadSignal()}
483527
code={store.compiled}
484-
class={`h-full w-full bg-white row-start-4 ${
528+
class={`h-full w-full bg-white row-start-5 ${
485529
props.isHorizontal ? '' : 'md:row-start-2'
486530
}`}
487-
classList={{
488-
'pointer-events-none': isDragging(),
489-
}}
490531
/>
491532
</Show>
492533
</Suspense>

src/components/tab/list.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Component, JSX } from 'solid-js';
33
export const TabList: Component<JSX.HTMLAttributes<HTMLUListElement>> = (props) => {
44
return (
55
<ul
6+
ref={props.ref}
67
class={`flex tabs flex-wrap items-center list-none bg-white dark:bg-blueGray-800 m-0 ${
78
props.class || ''
89
}`}

tailwind.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ module.exports = {
2727
fontSize: {
2828
'0.5sm': ['0.84375rem', '1.25rem'],
2929
},
30+
cursor: {
31+
'col-resize': 'col-resize',
32+
'row-resize': 'row-resize'
33+
}
3034
},
3135
},
3236
darkMode: 'class',

0 commit comments

Comments
 (0)