Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add tabs to switch between query and event mode #296

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Playground from './Playground';
import Embedded from './Embedded';
import DomEvents from './DomEvents';
import { PreviewEventsProvider } from '../context/PreviewEvents';

function App() {
return (
<Router>
<Switch>
<Route path="/embed/:gistId?/:gistVersion?" component={Embedded} />
<Route path="/events" component={DomEvents} />
<Route
path={['/gist/:gistId/:gistVersion?', '/']}
component={Playground}
/>
</Switch>
<PreviewEventsProvider>
<Switch>
<Route path="/embed/:gistId?/:gistVersion?" component={Embedded} />
<Route
path={['/gist/:gistId/:gistVersion?', '/']}
component={Playground}
/>
</Switch>
</PreviewEventsProvider>
</Router>
);
}
Expand Down
260 changes: 54 additions & 206 deletions src/components/DomEvents.js
Original file line number Diff line number Diff line change
@@ -1,84 +1,18 @@
import React, { useRef, useCallback, useState } from 'react';
import { eventMap } from '@testing-library/dom/dist/event-map';
import React, { useRef } from 'react';
import { ChevronUpIcon, ChevronDownIcon } from '@primer/octicons-react';
import throttle from 'lodash.throttle';

import AutoSizer from 'react-virtualized-auto-sizer';
import { TrashcanIcon } from '@primer/octicons-react';

import Preview from './Preview';
import MarkupEditor from './MarkupEditor';
import usePlayground from '../hooks/usePlayground';
import { VirtualScrollable } from './Scrollable';
import IconButton from './IconButton';
import CopyButton from './CopyButton';
import EmptyStreetImg from '../images/EmptyStreetImg';
import StickyList from './StickyList';
import Layout from './Layout';
import { useParams } from 'react-router-dom';

function targetToString() {
return [
this.tagName.toLowerCase(),
this.id && `#${this.id}`,
this.name && `[name="${this.name}"]`,
this.htmlFor && `[for="${this.htmlFor}"]`,
this.value && `[value="${this.value}"]`,
this.checked !== null && `[checked=${this.checked}]`,
]
.filter(Boolean)
.join('');
}

function getElementData(element) {
const value =
element.tagName === 'SELECT' && element.multiple
? element.selectedOptions.length > 0
? JSON.stringify(
Array.from(element.selectedOptions).map((o) => o.value),
)
: null
: element.value;

const hasChecked = element.type === 'checkbox' || element.type === 'radio';

return {
tagName: element.tagName.toLowerCase(),
id: element.id || null,
name: element.name || null,
htmlFor: element.htmlFor || null,
value: value || null,
checked: hasChecked ? !!element.checked : null,
toString: targetToString,
};
}

function addLoggingEvents(node, log) {
function createEventLogger(eventType) {
return function logEvent(event) {
if (event.target === event.currentTarget) {
return;
}

log({
event: eventType,
target: getElementData(event.target),
});
};
}
const eventListeners = [];
Object.keys(eventMap).forEach((name) => {
eventListeners.push({
name: name.toLowerCase(),
listener: node.addEventListener(
name.toLowerCase(),
createEventLogger({ name, ...eventMap[name] }),
true,
),
});
});

return eventListeners;
}
import {
usePreviewEvents,
usePreviewEventsActions,
} from '../context/PreviewEvents';

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

function DomEvents() {
const { gistId, gistVersion } = useParams();

const buffer = useRef([]);
const previewRef = useRef();
const listRef = useRef();

const sortDirection = useRef('asc');
const [appendMode, setAppendMode] = useState('bottom');
const [state, dispatch] = usePlayground({ gistId, gistVersion });
const { markup, result, status, dirty, settings } = state;

const [eventCount, setEventCount] = useState(0);
const [eventListeners, setEventListeners] = useState([]);
const { sortDirection, buffer, appendMode, eventCount } = usePreviewEvents();
const { changeSortDirection, reset } = usePreviewEventsActions();

const getSortIcon = () => (
<IconButton>
Expand All @@ -126,143 +50,67 @@ function DomEvents() {
</IconButton>
);

const changeSortDirection = () => {
const newDirection = sortDirection.current === 'desc' ? 'asc' : 'desc';
buffer.current = buffer.current.reverse();
setAppendMode(newDirection === 'desc' ? 'top' : 'bottom');
sortDirection.current = newDirection;
};

const reset = () => {
buffer.current = [];
setEventCount(0);
};

const getTextToCopy = () =>
buffer.current
.map((log) => `${log.target.toString()} - ${log.event.EventType}`)
.join('\n');

const flush = useCallback(
throttle(() => setEventCount(buffer.current.length), 16, {
leading: false,
}),
[setEventCount],
);

const setPreviewRef = useCallback((node) => {
if (node) {
previewRef.current = node;
const eventListeners = addLoggingEvents(node, (event) => {
const log = {
id: buffer.current.length + 1,
type: event.event.EventType,
name: event.event.name,
element: event.target.tagName,
selector: event.target.toString(),
};
if (sortDirection.current === 'desc') {
buffer.current.splice(0, 0, log);
} else {
buffer.current.push(log);
}

setTimeout(flush, 0);
});
setEventListeners(eventListeners);
} else if (previewRef.current) {
eventListeners.forEach((event) =>
previewRef.current.removeEventListener(event.name, event.listener),
);
previewRef.current = null;
}
}, []);

return (
<Layout
dispatch={dispatch}
gistId={gistId}
dirty={dirty}
status={status}
settings={settings}
>
<div className="flex flex-col h-auto md:h-full w-full">
<div className="editor p-4 markup-editor gap-4 md:gap-8 md:h-56 flex-auto grid-cols-1 md:grid-cols-2">
<div className="flex-auto relative h-56 md:h-full">
<MarkupEditor markup={markup} dispatch={dispatch} />
<div className="editor p-4 h-56 flex-auto overflow-hidden">
<div className="h-full w-full flex flex-col">
<div className="h-8 flex items-center w-full text-sm font-bold">
<div
className="p-2 w-16 cursor-pointer flex justify-between items-center"
onClick={changeSortDirection}
>
# {getSortIcon()}
</div>

<div className="flex-auto h-56 md:h-full">
<Preview
forwardedRef={setPreviewRef}
markup={markup}
elements={result?.elements}
accessibleRoles={result?.accessibleRoles}
dispatch={dispatch}
variant="minimal"
/>
<div className="p-2 w-32 ">type</div>
<div className="p-2 w-32 ">name</div>

<div className="p-2 w-40 ">element</div>
<div className="flex-auto p-2 flex justify-between">
<span>selector</span>
<div>
<CopyButton
text={getTextToCopy}
title="copy log"
className="mr-5"
/>
<IconButton title="clear event log" onClick={reset}>
<TrashcanIcon />
</IconButton>
</div>
</div>
</div>

<div className="flex-none h-8" />

<div className="editor p-4 md:h-56 flex-auto overflow-hidden">
<div className="h-56 md:h-full w-full flex flex-col">
<div className="h-8 flex items-center w-full text-sm font-bold">
<div
className="p-2 w-16 cursor-pointer flex justify-between items-center"
onClick={changeSortDirection}
>
# {getSortIcon()}
</div>

<div className="p-2 w-32 ">type</div>
<div className="p-2 w-32 ">name</div>

<div className="p-2 w-40 ">element</div>
<div className="flex-auto p-2 flex justify-between">
<span>selector</span>
<div>
<CopyButton
text={getTextToCopy}
title="copy log"
className="mr-5"
/>
<IconButton title="clear event log" onClick={reset}>
<TrashcanIcon />
</IconButton>
</div>
</div>
<div className="flex-auto relative overflow-hidden">
{buffer.current.length === 0 ? (
<div className="flex w-full h-full opacity-50 items-end justify-center">
<EmptyStreetImg height="80%" />
</div>

<div className="flex-auto relative overflow-hidden">
{buffer.current.length === 0 ? (
<div className="flex w-full h-full opacity-50 items-end justify-center">
<EmptyStreetImg height="80%" />
</div>
) : (
<AutoSizer>
{({ width, height }) => (
<StickyList
mode={appendMode}
ref={listRef}
height={height}
itemCount={eventCount}
itemData={buffer.current}
itemSize={32}
width={width}
outerElementType={VirtualScrollable}
>
{EventRecord}
</StickyList>
)}
</AutoSizer>
) : (
<AutoSizer>
{({ width, height }) => (
<StickyList
mode={appendMode}
ref={listRef}
height={height}
itemCount={eventCount}
itemData={buffer.current}
itemSize={32}
width={width}
outerElementType={VirtualScrollable}
>
{EventRecord}
</StickyList>
)}
</div>
</div>
</AutoSizer>
)}
</div>
</div>
</Layout>
</div>
);
}

Expand Down
19 changes: 1 addition & 18 deletions src/components/Embed.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,7 @@ import Embedded from './Embedded';
import { SyncIcon, XIcon } from '@primer/octicons-react';

import { defaultPanes } from '../constants';

function TabButton({ children, active, onClick, disabled }) {
return (
<button
disabled={disabled}
className={[
'text-xs select-none border-b-2',
disabled ? '' : 'hover:text-blue-400 hover:border-blue-400',
active
? 'border-blue-600 text-blue-600'
: 'border-transparent text-gray-800',
].join(' ')}
onClick={disabled ? undefined : onClick}
>
{children}
</button>
);
}
import TabButton from './TabButton';

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

Expand Down
2 changes: 1 addition & 1 deletion src/components/Loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function Loader({ loading }) {
<div
className={[
'w-full h-full absolute top-0 left-0 flex flex-col justify-center items-center w-full h-full space-y-4 fade',
loading ? 'opacity-100' : 'opacity-0',
loading ? 'opacity-100' : 'hidden opacity-0',
].join(' ')}
>
<img className="opacity-50" src={frog} />
Expand Down
Loading