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

Add Root-Only Filter Feature in History Tab #872

85 changes: 78 additions & 7 deletions tools/devtools/src/devtools/contexts/YorkieSource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,30 @@
* limitations under the License.
*/

import type { ReactNode } from 'react';
import type { Dispatch, ReactNode, SetStateAction } from 'react';
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react';

import type { SDKToPanelMessage, TransactionEvent } from 'yorkie-js-sdk';
import {
DocEventType,
type SDKToPanelMessage,
type TransactionEvent,
} from 'yorkie-js-sdk';
import { connectPort, sendToSDK } from '../../port';

const DocKeyContext = createContext<string>(null);
const YorkieDocContext = createContext(null);
const TransactionEventsContext = createContext<Array<TransactionEvent>>(null);
const TransactionEventsContext = createContext<{
events: Array<TransactionEvent>;
hidePresenceEvents: boolean;
setHidePresenceEvents: Dispatch<SetStateAction<boolean>>;
}>(null);

type Props = {
children?: ReactNode;
Expand All @@ -41,6 +50,10 @@ export function YorkieSourceProvider({ children }: Props) {
Array<TransactionEvent>
>([]);

// filter out presence events
const [hideTransactionPresenceEvents, setHideTransactionPresenceEvents] =
useState(false);

const resetDocument = () => {
setCurrentDocKey('');
setTransactionEvents([]);
Expand Down Expand Up @@ -94,7 +107,13 @@ export function YorkieSourceProvider({ children }: Props) {

return (
<DocKeyContext.Provider value={currentDocKey}>
<TransactionEventsContext.Provider value={transactionEvents}>
<TransactionEventsContext.Provider
value={{
events: transactionEvents,
hidePresenceEvents: hideTransactionPresenceEvents,
setHidePresenceEvents: setHideTransactionPresenceEvents,
}}
>
<YorkieDocContext.Provider value={[doc, setDoc]}>
{children}
</YorkieDocContext.Provider>
Expand All @@ -121,12 +140,64 @@ export function useYorkieDoc() {
return value;
}

export enum TransactionEventType {
Document = 'document',
Presence = 'presence',
}

export const getTransactionEventType = (
event: TransactionEvent,
): TransactionEventType => {
for (const docEvent of event) {
if (
docEvent.type === DocEventType.StatusChanged ||
docEvent.type === DocEventType.Snapshot ||
docEvent.type === DocEventType.LocalChange ||
docEvent.type === DocEventType.RemoteChange
) {
return TransactionEventType.Document;
}
}

return TransactionEventType.Presence;
};

export function useTransactionEvents() {
const value = useContext(TransactionEventsContext);
if (value === undefined) {
const { events, hidePresenceEvents, setHidePresenceEvents } = useContext(
TransactionEventsContext,
);

if (events === undefined) {
throw new Error(
'useTransactionEvents should be used within YorkieSourceProvider',
);
}
return value;

// create an enhanced events with metadata
const enhancedEvents = useMemo(() => {
return events.map((event) => {
const transactionEventType = getTransactionEventType(event);

return {
event,
transactionEventType,
isFiltered:
hidePresenceEvents &&
transactionEventType === TransactionEventType.Presence,
};
});
}, [hidePresenceEvents, events]);

// filter out presence events from the original events
const presenceFilteredEvents = useMemo(() => {
if (!hidePresenceEvents) return enhancedEvents;
return enhancedEvents.filter((e) => !e.isFiltered);
}, [enhancedEvents]);

return {
originalEvents: enhancedEvents,
presenceFilteredEvents,
hidePresenceEvents,
setHidePresenceEvents,
};
}
56 changes: 43 additions & 13 deletions tools/devtools/src/devtools/panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { createRoot } from 'react-dom/client';
import { useEffect, useState } from 'react';
import yorkie from 'yorkie-js-sdk';
import { useResizable } from 'react-resizable-layout';

import { SelectedNodeProvider } from '../contexts/SelectedNode';
import { SelectedPresenceProvider } from '../contexts/SelectedPresence';
import {
Expand All @@ -34,7 +33,8 @@ import { Separator } from '../components/ResizableSeparator';

const Panel = () => {
const currentDocKey = useCurrentDocKey();
const events = useTransactionEvents();
const { originalEvents, presenceFilteredEvents, hidePresenceEvents } =
useTransactionEvents();
const [, setDoc] = useYorkieDoc();
const [selectedEventIndexInfo, setSelectedEventIndexInfo] = useState({
index: null,
Expand All @@ -57,6 +57,8 @@ const Panel = () => {
axis: 'x',
initial: 300,
});
const [hidePresenceTab, setHidePresenceTab] = useState(false);
const events = hidePresenceEvents ? presenceFilteredEvents : originalEvents;

useEffect(() => {
if (events.length === 0) {
Expand All @@ -78,13 +80,23 @@ const Panel = () => {

useEffect(() => {
if (selectedEventIndexInfo.index === null) return;

const doc = new yorkie.Document(currentDocKey);
for (let i = 0; i <= selectedEventIndexInfo.index; i++) {
doc.applyTransactionEvent(events[i]);

let eventIndex = 0;
let filteredEventIndex = 0;

while (filteredEventIndex <= selectedEventIndexInfo.index) {
if (!originalEvents[eventIndex].isFiltered) {
filteredEventIndex++;
}

doc.applyTransactionEvent(originalEvents[eventIndex].event);
eventIndex++;
}

setDoc(doc);
setSelectedEvent(events[selectedEventIndexInfo.index]);
setSelectedEvent(events[selectedEventIndexInfo.index].event);
}, [selectedEventIndexInfo]);

if (!currentDocKey) {
Expand Down Expand Up @@ -117,22 +129,40 @@ const Panel = () => {
selectedEventIndexInfo={selectedEventIndexInfo}
setSelectedEventIndexInfo={setSelectedEventIndexInfo}
/>

<Separator
dir={'horizontal'}
isDragging={isHistoryDragging}
{...historySeparatorProps}
/>

<div className="devtools-data">
<SelectedNodeProvider>
<Document style={{ width: documentW }} />
<Document
style={{
width: hidePresenceTab ? '100%' : documentW,
maxWidth: hidePresenceTab ? '100%' : '90%',
borderRight: hidePresenceTab
? 'none'
: '1px solid var(--gray-300)',
}}
hidePresenceTab={hidePresenceTab}
setHidePresenceTab={setHidePresenceTab}
/>
</SelectedNodeProvider>
<Separator
isDragging={isDocumentDragging}
{...documentSeparatorProps}
/>
<SelectedPresenceProvider>
<Presence />
</SelectedPresenceProvider>

{!hidePresenceTab && (
<>
<Separator
isDragging={isDocumentDragging}
{...documentSeparatorProps}
/>

<SelectedPresenceProvider>
<Presence />
</SelectedPresenceProvider>
</>
)}
chacha912 marked this conversation as resolved.
Show resolved Hide resolved
</div>
</div>
);
Expand Down
4 changes: 4 additions & 0 deletions tools/devtools/src/devtools/panel/slider.css
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,7 @@
.rc-slider-mark-text-active .mark-remote {
color: var(--blue-0);
}

.history-slider-wrap[data-length='1'] .rc-slider-rail {
display: none;
}
9 changes: 3 additions & 6 deletions tools/devtools/src/devtools/panel/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,13 @@
width: 100%;
}

.devtools-history-toolbar {
.devtools-tab-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
}

.toggle-history-btn {
.toggle-tab-btn {
margin-left: 4px;
padding: 2px 6px;
border: 1px solid var(--gray-300);
Expand All @@ -100,7 +100,7 @@
font-size: 10px;
}

.toggle-history-btn:hover {
.toggle-tab-btn:hover {
background: var(--gray-200);
}

Expand Down Expand Up @@ -152,9 +152,6 @@

.yorkie-root {
min-width: 10%;
max-width: 90%;
width: 60%;
border-right: 1px solid var(--gray-300);
}

.yorkie-presence {
Expand Down
15 changes: 13 additions & 2 deletions tools/devtools/src/devtools/tabs/Document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { useSelectedNode } from '../contexts/SelectedNode';
import { useCurrentDocKey, useYorkieDoc } from '../contexts/YorkieSource';
import { CloseIcon } from '../icons';

export function Document({ style }) {
export function Document({ style, hidePresenceTab, setHidePresenceTab }) {
const currentDocKey = useCurrentDocKey();
const [doc] = useYorkieDoc();
const [selectedNode, setSelectedNode] = useSelectedNode();
Expand Down Expand Up @@ -60,7 +60,18 @@ export function Document({ style }) {

return (
<div className="yorkie-root content-wrap" style={{ ...style }}>
<div className="title">{currentDocKey || 'Document'}</div>
<div className="devtools-tab-toolbar">
<span className="title">{currentDocKey || 'Document'}</span>
<button
className="toggle-tab-btn"
onClick={() => {
setHidePresenceTab((v: boolean) => !v);
}}
>
{hidePresenceTab ? '◂' : '▸'}
</button>
</div>

<div className="content">
<RootTree root={root} />
{selectedNode && (
Expand Down
Loading
Loading