Skip to content

Commit 8599c4a

Browse files
gwbaik9717hackerwins
authored andcommitted
Add Root-Only Filter Feature in History Tab (#872)
This commit adds a Root-Only Filter in the History tab, allowing users to focus exclusively on changes to the Root of the Document. Previously, the History tab logs both content changes and Presence events (such as cursor movements), which can result in a high volume of events that may not always be relevant for debugging content changes.
1 parent 8314097 commit 8599c4a

File tree

7 files changed

+189
-48
lines changed

7 files changed

+189
-48
lines changed

tools/devtools/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "yorkie-devtools",
33
"displayName": "Yorkie Devtools",
4-
"version": "0.4.23",
4+
"version": "0.4.27",
55
"description": "A browser extension that helps you debug Yorkie.",
66
"homepage": "https://yorkie.dev/",
77
"scripts": {
@@ -20,7 +20,7 @@
2020
"react-dom": "18.2.0",
2121
"react-resizable-layout": "^0.7.2",
2222
"use-resize-observer": "^9.1.0",
23-
"yorkie-js-sdk": "^0.4.23"
23+
"yorkie-js-sdk": "^0.4.27"
2424
},
2525
"devDependencies": {
2626
"@types/chrome": "0.0.251",

tools/devtools/src/devtools/contexts/YorkieSource.tsx

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,30 @@
1414
* limitations under the License.
1515
*/
1616

17-
import type { ReactNode } from 'react';
17+
import type { Dispatch, ReactNode, SetStateAction } from 'react';
1818
import {
1919
createContext,
2020
useCallback,
2121
useContext,
2222
useEffect,
23+
useMemo,
2324
useState,
2425
} from 'react';
2526

26-
import type { SDKToPanelMessage, TransactionEvent } from 'yorkie-js-sdk';
27+
import {
28+
DocEventType,
29+
type SDKToPanelMessage,
30+
type TransactionEvent,
31+
} from 'yorkie-js-sdk';
2732
import { connectPort, sendToSDK } from '../../port';
2833

2934
const DocKeyContext = createContext<string>(null);
3035
const YorkieDocContext = createContext(null);
31-
const TransactionEventsContext = createContext<Array<TransactionEvent>>(null);
36+
const TransactionEventsContext = createContext<{
37+
events: Array<TransactionEvent>;
38+
hidePresenceEvents: boolean;
39+
setHidePresenceEvents: Dispatch<SetStateAction<boolean>>;
40+
}>(null);
3241

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

53+
// filter out presence events
54+
const [hideTransactionPresenceEvents, setHideTransactionPresenceEvents] =
55+
useState(false);
56+
4457
const resetDocument = () => {
4558
setCurrentDocKey('');
4659
setTransactionEvents([]);
@@ -94,7 +107,13 @@ export function YorkieSourceProvider({ children }: Props) {
94107

95108
return (
96109
<DocKeyContext.Provider value={currentDocKey}>
97-
<TransactionEventsContext.Provider value={transactionEvents}>
110+
<TransactionEventsContext.Provider
111+
value={{
112+
events: transactionEvents,
113+
hidePresenceEvents: hideTransactionPresenceEvents,
114+
setHidePresenceEvents: setHideTransactionPresenceEvents,
115+
}}
116+
>
98117
<YorkieDocContext.Provider value={[doc, setDoc]}>
99118
{children}
100119
</YorkieDocContext.Provider>
@@ -121,12 +140,64 @@ export function useYorkieDoc() {
121140
return value;
122141
}
123142

143+
export enum TransactionEventType {
144+
Document = 'document',
145+
Presence = 'presence',
146+
}
147+
148+
export const getTransactionEventType = (
149+
event: TransactionEvent,
150+
): TransactionEventType => {
151+
for (const docEvent of event) {
152+
if (
153+
docEvent.type === DocEventType.StatusChanged ||
154+
docEvent.type === DocEventType.Snapshot ||
155+
docEvent.type === DocEventType.LocalChange ||
156+
docEvent.type === DocEventType.RemoteChange
157+
) {
158+
return TransactionEventType.Document;
159+
}
160+
}
161+
162+
return TransactionEventType.Presence;
163+
};
164+
124165
export function useTransactionEvents() {
125-
const value = useContext(TransactionEventsContext);
126-
if (value === undefined) {
166+
const { events, hidePresenceEvents, setHidePresenceEvents } = useContext(
167+
TransactionEventsContext,
168+
);
169+
170+
if (events === undefined) {
127171
throw new Error(
128172
'useTransactionEvents should be used within YorkieSourceProvider',
129173
);
130174
}
131-
return value;
175+
176+
// create an enhanced events with metadata
177+
const enhancedEvents = useMemo(() => {
178+
return events.map((event) => {
179+
const transactionEventType = getTransactionEventType(event);
180+
181+
return {
182+
event,
183+
transactionEventType,
184+
isFiltered:
185+
hidePresenceEvents &&
186+
transactionEventType === TransactionEventType.Presence,
187+
};
188+
});
189+
}, [hidePresenceEvents, events]);
190+
191+
// filter out presence events from the original events
192+
const presenceFilteredEvents = useMemo(() => {
193+
if (!hidePresenceEvents) return enhancedEvents;
194+
return enhancedEvents.filter((e) => !e.isFiltered);
195+
}, [enhancedEvents]);
196+
197+
return {
198+
originalEvents: enhancedEvents,
199+
presenceFilteredEvents,
200+
hidePresenceEvents,
201+
setHidePresenceEvents,
202+
};
132203
}

tools/devtools/src/devtools/panel/index.tsx

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { createRoot } from 'react-dom/client';
1818
import { useEffect, useState } from 'react';
1919
import yorkie from 'yorkie-js-sdk';
2020
import { useResizable } from 'react-resizable-layout';
21-
2221
import { SelectedNodeProvider } from '../contexts/SelectedNode';
2322
import { SelectedPresenceProvider } from '../contexts/SelectedPresence';
2423
import {
@@ -34,7 +33,8 @@ import { Separator } from '../components/ResizableSeparator';
3433

3534
const Panel = () => {
3635
const currentDocKey = useCurrentDocKey();
37-
const events = useTransactionEvents();
36+
const { originalEvents, presenceFilteredEvents, hidePresenceEvents } =
37+
useTransactionEvents();
3838
const [, setDoc] = useYorkieDoc();
3939
const [selectedEventIndexInfo, setSelectedEventIndexInfo] = useState({
4040
index: null,
@@ -57,6 +57,8 @@ const Panel = () => {
5757
axis: 'x',
5858
initial: 300,
5959
});
60+
const [hidePresenceTab, setHidePresenceTab] = useState(false);
61+
const events = hidePresenceEvents ? presenceFilteredEvents : originalEvents;
6062

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

7981
useEffect(() => {
8082
if (selectedEventIndexInfo.index === null) return;
83+
8184
const doc = new yorkie.Document(currentDocKey);
82-
for (let i = 0; i <= selectedEventIndexInfo.index; i++) {
83-
doc.applyTransactionEvent(events[i]);
85+
86+
let eventIndex = 0;
87+
let filteredEventIndex = 0;
88+
89+
while (filteredEventIndex <= selectedEventIndexInfo.index) {
90+
if (!originalEvents[eventIndex].isFiltered) {
91+
filteredEventIndex++;
92+
}
93+
94+
doc.applyTransactionEvent(originalEvents[eventIndex].event);
95+
eventIndex++;
8496
}
8597

8698
setDoc(doc);
87-
setSelectedEvent(events[selectedEventIndexInfo.index]);
99+
setSelectedEvent(events[selectedEventIndexInfo.index].event);
88100
}, [selectedEventIndexInfo]);
89101

90102
if (!currentDocKey) {
@@ -117,22 +129,40 @@ const Panel = () => {
117129
selectedEventIndexInfo={selectedEventIndexInfo}
118130
setSelectedEventIndexInfo={setSelectedEventIndexInfo}
119131
/>
132+
120133
<Separator
121134
dir={'horizontal'}
122135
isDragging={isHistoryDragging}
123136
{...historySeparatorProps}
124137
/>
138+
125139
<div className="devtools-data">
126140
<SelectedNodeProvider>
127-
<Document style={{ width: documentW }} />
141+
<Document
142+
style={{
143+
width: hidePresenceTab ? '100%' : documentW,
144+
maxWidth: hidePresenceTab ? '100%' : '90%',
145+
borderRight: hidePresenceTab
146+
? 'none'
147+
: '1px solid var(--gray-300)',
148+
}}
149+
hidePresenceTab={hidePresenceTab}
150+
setHidePresenceTab={setHidePresenceTab}
151+
/>
128152
</SelectedNodeProvider>
129-
<Separator
130-
isDragging={isDocumentDragging}
131-
{...documentSeparatorProps}
132-
/>
133-
<SelectedPresenceProvider>
134-
<Presence />
135-
</SelectedPresenceProvider>
153+
154+
{!hidePresenceTab && (
155+
<>
156+
<Separator
157+
isDragging={isDocumentDragging}
158+
{...documentSeparatorProps}
159+
/>
160+
161+
<SelectedPresenceProvider>
162+
<Presence />
163+
</SelectedPresenceProvider>
164+
</>
165+
)}
136166
</div>
137167
</div>
138168
);

tools/devtools/src/devtools/panel/slider.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,7 @@
9191
.rc-slider-mark-text-active .mark-remote {
9292
color: var(--blue-0);
9393
}
94+
95+
.history-slider-wrap[data-length='1'] .rc-slider-rail {
96+
display: none;
97+
}

tools/devtools/src/devtools/panel/styles.css

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@
8383
width: 100%;
8484
}
8585

86-
.devtools-history-toolbar {
86+
.devtools-tab-toolbar {
8787
display: flex;
8888
justify-content: space-between;
8989
align-items: center;
9090
}
9191

92-
.toggle-history-btn {
92+
.toggle-tab-btn {
9393
margin-left: 4px;
9494
padding: 2px 6px;
9595
border: 1px solid var(--gray-300);
@@ -100,7 +100,7 @@
100100
font-size: 10px;
101101
}
102102

103-
.toggle-history-btn:hover {
103+
.toggle-tab-btn:hover {
104104
background: var(--gray-200);
105105
}
106106

@@ -152,9 +152,6 @@
152152

153153
.yorkie-root {
154154
min-width: 10%;
155-
max-width: 90%;
156-
width: 60%;
157-
border-right: 1px solid var(--gray-300);
158155
}
159156

160157
.yorkie-presence {

tools/devtools/src/devtools/tabs/Document.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { useSelectedNode } from '../contexts/SelectedNode';
2323
import { useCurrentDocKey, useYorkieDoc } from '../contexts/YorkieSource';
2424
import { CloseIcon } from '../icons';
2525

26-
export function Document({ style }) {
26+
export function Document({ style, hidePresenceTab, setHidePresenceTab }) {
2727
const currentDocKey = useCurrentDocKey();
2828
const [doc] = useYorkieDoc();
2929
const [selectedNode, setSelectedNode] = useSelectedNode();
@@ -60,7 +60,18 @@ export function Document({ style }) {
6060

6161
return (
6262
<div className="yorkie-root content-wrap" style={{ ...style }}>
63-
<div className="title">{currentDocKey || 'Document'}</div>
63+
<div className="devtools-tab-toolbar">
64+
<span className="title">{currentDocKey || 'Document'}</span>
65+
<button
66+
className="toggle-tab-btn"
67+
onClick={() => {
68+
setHidePresenceTab((v: boolean) => !v);
69+
}}
70+
>
71+
{hidePresenceTab ? '◂' : '▸'}
72+
</button>
73+
</div>
74+
6475
<div className="content">
6576
<RootTree root={root} />
6677
{selectedNode && (

0 commit comments

Comments
 (0)