Skip to content

Commit 351c6ff

Browse files
authored
feat: add tabs to switch between query and event mode (#296)
1 parent 28d506d commit 351c6ff

9 files changed

+337
-258
lines changed

src/components/App.js

+10-9
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ import React from 'react';
22
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
33
import Playground from './Playground';
44
import Embedded from './Embedded';
5-
import DomEvents from './DomEvents';
5+
import { PreviewEventsProvider } from '../context/PreviewEvents';
66

77
function App() {
88
return (
99
<Router>
10-
<Switch>
11-
<Route path="/embed/:gistId?/:gistVersion?" component={Embedded} />
12-
<Route path="/events" component={DomEvents} />
13-
<Route
14-
path={['/gist/:gistId/:gistVersion?', '/']}
15-
component={Playground}
16-
/>
17-
</Switch>
10+
<PreviewEventsProvider>
11+
<Switch>
12+
<Route path="/embed/:gistId?/:gistVersion?" component={Embedded} />
13+
<Route
14+
path={['/gist/:gistId/:gistVersion?', '/']}
15+
component={Playground}
16+
/>
17+
</Switch>
18+
</PreviewEventsProvider>
1819
</Router>
1920
);
2021
}

src/components/DomEvents.js

+54-206
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,18 @@
1-
import React, { useRef, useCallback, useState } from 'react';
2-
import { eventMap } from '@testing-library/dom/dist/event-map';
1+
import React, { useRef } from 'react';
32
import { ChevronUpIcon, ChevronDownIcon } from '@primer/octicons-react';
4-
import throttle from 'lodash.throttle';
3+
54
import AutoSizer from 'react-virtualized-auto-sizer';
65
import { TrashcanIcon } from '@primer/octicons-react';
76

8-
import Preview from './Preview';
9-
import MarkupEditor from './MarkupEditor';
10-
import usePlayground from '../hooks/usePlayground';
117
import { VirtualScrollable } from './Scrollable';
128
import IconButton from './IconButton';
139
import CopyButton from './CopyButton';
1410
import EmptyStreetImg from '../images/EmptyStreetImg';
1511
import StickyList from './StickyList';
16-
import Layout from './Layout';
17-
import { useParams } from 'react-router-dom';
18-
19-
function targetToString() {
20-
return [
21-
this.tagName.toLowerCase(),
22-
this.id && `#${this.id}`,
23-
this.name && `[name="${this.name}"]`,
24-
this.htmlFor && `[for="${this.htmlFor}"]`,
25-
this.value && `[value="${this.value}"]`,
26-
this.checked !== null && `[checked=${this.checked}]`,
27-
]
28-
.filter(Boolean)
29-
.join('');
30-
}
31-
32-
function getElementData(element) {
33-
const value =
34-
element.tagName === 'SELECT' && element.multiple
35-
? element.selectedOptions.length > 0
36-
? JSON.stringify(
37-
Array.from(element.selectedOptions).map((o) => o.value),
38-
)
39-
: null
40-
: element.value;
41-
42-
const hasChecked = element.type === 'checkbox' || element.type === 'radio';
43-
44-
return {
45-
tagName: element.tagName.toLowerCase(),
46-
id: element.id || null,
47-
name: element.name || null,
48-
htmlFor: element.htmlFor || null,
49-
value: value || null,
50-
checked: hasChecked ? !!element.checked : null,
51-
toString: targetToString,
52-
};
53-
}
54-
55-
function addLoggingEvents(node, log) {
56-
function createEventLogger(eventType) {
57-
return function logEvent(event) {
58-
if (event.target === event.currentTarget) {
59-
return;
60-
}
61-
62-
log({
63-
event: eventType,
64-
target: getElementData(event.target),
65-
});
66-
};
67-
}
68-
const eventListeners = [];
69-
Object.keys(eventMap).forEach((name) => {
70-
eventListeners.push({
71-
name: name.toLowerCase(),
72-
listener: node.addEventListener(
73-
name.toLowerCase(),
74-
createEventLogger({ name, ...eventMap[name] }),
75-
true,
76-
),
77-
});
78-
});
79-
80-
return eventListeners;
81-
}
12+
import {
13+
usePreviewEvents,
14+
usePreviewEventsActions,
15+
} from '../context/PreviewEvents';
8216

8317
function EventRecord({ index, style, data }) {
8418
const { id, type, name, element, selector } = data[index];
@@ -102,19 +36,9 @@ function EventRecord({ index, style, data }) {
10236
}
10337

10438
function DomEvents() {
105-
const { gistId, gistVersion } = useParams();
106-
107-
const buffer = useRef([]);
108-
const previewRef = useRef();
10939
const listRef = useRef();
110-
111-
const sortDirection = useRef('asc');
112-
const [appendMode, setAppendMode] = useState('bottom');
113-
const [state, dispatch] = usePlayground({ gistId, gistVersion });
114-
const { markup, result, status, dirty, settings } = state;
115-
116-
const [eventCount, setEventCount] = useState(0);
117-
const [eventListeners, setEventListeners] = useState([]);
40+
const { sortDirection, buffer, appendMode, eventCount } = usePreviewEvents();
41+
const { changeSortDirection, reset } = usePreviewEventsActions();
11842

11943
const getSortIcon = () => (
12044
<IconButton>
@@ -126,143 +50,67 @@ function DomEvents() {
12650
</IconButton>
12751
);
12852

129-
const changeSortDirection = () => {
130-
const newDirection = sortDirection.current === 'desc' ? 'asc' : 'desc';
131-
buffer.current = buffer.current.reverse();
132-
setAppendMode(newDirection === 'desc' ? 'top' : 'bottom');
133-
sortDirection.current = newDirection;
134-
};
135-
136-
const reset = () => {
137-
buffer.current = [];
138-
setEventCount(0);
139-
};
140-
14153
const getTextToCopy = () =>
14254
buffer.current
14355
.map((log) => `${log.target.toString()} - ${log.event.EventType}`)
14456
.join('\n');
14557

146-
const flush = useCallback(
147-
throttle(() => setEventCount(buffer.current.length), 16, {
148-
leading: false,
149-
}),
150-
[setEventCount],
151-
);
152-
153-
const setPreviewRef = useCallback((node) => {
154-
if (node) {
155-
previewRef.current = node;
156-
const eventListeners = addLoggingEvents(node, (event) => {
157-
const log = {
158-
id: buffer.current.length + 1,
159-
type: event.event.EventType,
160-
name: event.event.name,
161-
element: event.target.tagName,
162-
selector: event.target.toString(),
163-
};
164-
if (sortDirection.current === 'desc') {
165-
buffer.current.splice(0, 0, log);
166-
} else {
167-
buffer.current.push(log);
168-
}
169-
170-
setTimeout(flush, 0);
171-
});
172-
setEventListeners(eventListeners);
173-
} else if (previewRef.current) {
174-
eventListeners.forEach((event) =>
175-
previewRef.current.removeEventListener(event.name, event.listener),
176-
);
177-
previewRef.current = null;
178-
}
179-
}, []);
180-
18158
return (
182-
<Layout
183-
dispatch={dispatch}
184-
gistId={gistId}
185-
dirty={dirty}
186-
status={status}
187-
settings={settings}
188-
>
189-
<div className="flex flex-col h-auto md:h-full w-full">
190-
<div className="editor p-4 markup-editor gap-4 md:gap-8 md:h-56 flex-auto grid-cols-1 md:grid-cols-2">
191-
<div className="flex-auto relative h-56 md:h-full">
192-
<MarkupEditor markup={markup} dispatch={dispatch} />
59+
<div className="editor p-4 h-56 flex-auto overflow-hidden">
60+
<div className="h-full w-full flex flex-col">
61+
<div className="h-8 flex items-center w-full text-sm font-bold">
62+
<div
63+
className="p-2 w-16 cursor-pointer flex justify-between items-center"
64+
onClick={changeSortDirection}
65+
>
66+
# {getSortIcon()}
19367
</div>
19468

195-
<div className="flex-auto h-56 md:h-full">
196-
<Preview
197-
forwardedRef={setPreviewRef}
198-
markup={markup}
199-
elements={result?.elements}
200-
accessibleRoles={result?.accessibleRoles}
201-
dispatch={dispatch}
202-
variant="minimal"
203-
/>
69+
<div className="p-2 w-32 ">type</div>
70+
<div className="p-2 w-32 ">name</div>
71+
72+
<div className="p-2 w-40 ">element</div>
73+
<div className="flex-auto p-2 flex justify-between">
74+
<span>selector</span>
75+
<div>
76+
<CopyButton
77+
text={getTextToCopy}
78+
title="copy log"
79+
className="mr-5"
80+
/>
81+
<IconButton title="clear event log" onClick={reset}>
82+
<TrashcanIcon />
83+
</IconButton>
84+
</div>
20485
</div>
20586
</div>
20687

207-
<div className="flex-none h-8" />
208-
209-
<div className="editor p-4 md:h-56 flex-auto overflow-hidden">
210-
<div className="h-56 md:h-full w-full flex flex-col">
211-
<div className="h-8 flex items-center w-full text-sm font-bold">
212-
<div
213-
className="p-2 w-16 cursor-pointer flex justify-between items-center"
214-
onClick={changeSortDirection}
215-
>
216-
# {getSortIcon()}
217-
</div>
218-
219-
<div className="p-2 w-32 ">type</div>
220-
<div className="p-2 w-32 ">name</div>
221-
222-
<div className="p-2 w-40 ">element</div>
223-
<div className="flex-auto p-2 flex justify-between">
224-
<span>selector</span>
225-
<div>
226-
<CopyButton
227-
text={getTextToCopy}
228-
title="copy log"
229-
className="mr-5"
230-
/>
231-
<IconButton title="clear event log" onClick={reset}>
232-
<TrashcanIcon />
233-
</IconButton>
234-
</div>
235-
</div>
88+
<div className="flex-auto relative overflow-hidden">
89+
{buffer.current.length === 0 ? (
90+
<div className="flex w-full h-full opacity-50 items-end justify-center">
91+
<EmptyStreetImg height="80%" />
23692
</div>
237-
238-
<div className="flex-auto relative overflow-hidden">
239-
{buffer.current.length === 0 ? (
240-
<div className="flex w-full h-full opacity-50 items-end justify-center">
241-
<EmptyStreetImg height="80%" />
242-
</div>
243-
) : (
244-
<AutoSizer>
245-
{({ width, height }) => (
246-
<StickyList
247-
mode={appendMode}
248-
ref={listRef}
249-
height={height}
250-
itemCount={eventCount}
251-
itemData={buffer.current}
252-
itemSize={32}
253-
width={width}
254-
outerElementType={VirtualScrollable}
255-
>
256-
{EventRecord}
257-
</StickyList>
258-
)}
259-
</AutoSizer>
93+
) : (
94+
<AutoSizer>
95+
{({ width, height }) => (
96+
<StickyList
97+
mode={appendMode}
98+
ref={listRef}
99+
height={height}
100+
itemCount={eventCount}
101+
itemData={buffer.current}
102+
itemSize={32}
103+
width={width}
104+
outerElementType={VirtualScrollable}
105+
>
106+
{EventRecord}
107+
</StickyList>
260108
)}
261-
</div>
262-
</div>
109+
</AutoSizer>
110+
)}
263111
</div>
264112
</div>
265-
</Layout>
113+
</div>
266114
);
267115
}
268116

src/components/Embed.js

+1-18
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,7 @@ import Embedded from './Embedded';
55
import { SyncIcon, XIcon } from '@primer/octicons-react';
66

77
import { defaultPanes } from '../constants';
8-
9-
function TabButton({ children, active, onClick, disabled }) {
10-
return (
11-
<button
12-
disabled={disabled}
13-
className={[
14-
'text-xs select-none border-b-2',
15-
disabled ? '' : 'hover:text-blue-400 hover:border-blue-400',
16-
active
17-
? 'border-blue-600 text-blue-600'
18-
: 'border-transparent text-gray-800',
19-
].join(' ')}
20-
onClick={disabled ? undefined : onClick}
21-
>
22-
{children}
23-
</button>
24-
);
25-
}
8+
import TabButton from './TabButton';
269

2710
const possiblePanes = ['markup', 'preview', 'query', 'result'];
2811

src/components/Loader.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ function Loader({ loading }) {
66
<div
77
className={[
88
'w-full h-full absolute top-0 left-0 flex flex-col justify-center items-center w-full h-full space-y-4 fade',
9-
loading ? 'opacity-100' : 'opacity-0',
9+
loading ? 'opacity-100' : 'hidden opacity-0',
1010
].join(' ')}
1111
>
1212
<img className="opacity-50" src={frog} />

0 commit comments

Comments
 (0)