@buoy-gg/storage 2.1.14 → 2.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/storage/components/DiffViewer/modes/ThemedSplitView.js +345 -173
- package/lib/commonjs/storage/components/StorageEventDetailContent.js +24 -23
- package/lib/commonjs/storage/components/StorageModalWithTabs.js +10 -8
- package/lib/commonjs/storage/stores/storageEventStore.js +13 -3
- package/lib/module/storage/components/DiffViewer/modes/ThemedSplitView.js +348 -176
- package/lib/module/storage/components/StorageEventDetailContent.js +25 -24
- package/lib/module/storage/components/StorageModalWithTabs.js +10 -8
- package/lib/module/storage/stores/storageEventStore.js +13 -3
- package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
- package/lib/typescript/storage/stores/storageEventStore.d.ts +6 -1
- package/lib/typescript/storage/stores/storageEventStore.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -409,9 +409,7 @@ function StorageModalWithTabs({
|
|
|
409
409
|
const END_REACHED_THRESHOLD = 0.8;
|
|
410
410
|
|
|
411
411
|
// keyExtractor for raw event FlatList
|
|
412
|
-
const eventKeyExtractor = (0, _react.useCallback)(
|
|
413
|
-
return `${item.timestamp.getTime()}-${item.data?.key || index}`;
|
|
414
|
-
}, []);
|
|
412
|
+
const eventKeyExtractor = (0, _react.useCallback)(item => item.id, []);
|
|
415
413
|
|
|
416
414
|
// When a raw event card is pressed, open that key's conversation detail at the matching event
|
|
417
415
|
const handleRawEventPress = (0, _react.useCallback)(item => {
|
|
@@ -474,8 +472,9 @@ function StorageModalWithTabs({
|
|
|
474
472
|
const ascSortedEvents = [...selectedHistoryConversation.events].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
475
473
|
const newestFirstEvents = [...ascSortedEvents].reverse();
|
|
476
474
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
|
|
475
|
+
style: styles.flexFill,
|
|
477
476
|
data: newestFirstEvents,
|
|
478
|
-
keyExtractor:
|
|
477
|
+
keyExtractor: item => item.id,
|
|
479
478
|
renderItem: ({
|
|
480
479
|
item
|
|
481
480
|
}) => {
|
|
@@ -497,8 +496,7 @@ function StorageModalWithTabs({
|
|
|
497
496
|
ItemSeparatorComponent: () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
498
497
|
style: styles.separator
|
|
499
498
|
}),
|
|
500
|
-
initialNumToRender: 20
|
|
501
|
-
scrollEnabled: false
|
|
499
|
+
initialNumToRender: 20
|
|
502
500
|
});
|
|
503
501
|
}
|
|
504
502
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageBrowserMode.StorageBrowserMode, {
|
|
@@ -542,6 +540,7 @@ function StorageModalWithTabs({
|
|
|
542
540
|
});
|
|
543
541
|
}
|
|
544
542
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
|
|
543
|
+
style: styles.flexFill,
|
|
545
544
|
data: visibleEvents,
|
|
546
545
|
renderItem: renderEventItem,
|
|
547
546
|
keyExtractor: eventKeyExtractor,
|
|
@@ -556,8 +555,7 @@ function StorageModalWithTabs({
|
|
|
556
555
|
}) : null,
|
|
557
556
|
initialNumToRender: 10,
|
|
558
557
|
maxToRenderPerBatch: 10,
|
|
559
|
-
windowSize: 10
|
|
560
|
-
scrollEnabled: false
|
|
558
|
+
windowSize: 10
|
|
561
559
|
});
|
|
562
560
|
};
|
|
563
561
|
const footerNode = selectedConversation ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageEventDetailContent.StorageEventDetailFooter, {
|
|
@@ -686,6 +684,7 @@ function StorageModalWithTabs({
|
|
|
686
684
|
styles: {},
|
|
687
685
|
footer: footerNode,
|
|
688
686
|
footerHeight: footerNode ? 68 : 0,
|
|
687
|
+
disableScrollWrapper: true,
|
|
689
688
|
children: renderContent()
|
|
690
689
|
})
|
|
691
690
|
});
|
|
@@ -694,6 +693,9 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
694
693
|
headerSearchContainer: {
|
|
695
694
|
flex: 1
|
|
696
695
|
},
|
|
696
|
+
flexFill: {
|
|
697
|
+
flex: 1
|
|
698
|
+
},
|
|
697
699
|
iconButton: {
|
|
698
700
|
width: 32,
|
|
699
701
|
height: 32,
|
|
@@ -30,9 +30,17 @@ var _MMKVListener = require("../utils/MMKVListener");
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* Unified storage event type combining AsyncStorage and MMKV events
|
|
33
|
+
* Unified storage event type combining AsyncStorage and MMKV events.
|
|
34
|
+
* `id` is assigned by the store when the event enters — needed because raw
|
|
35
|
+
* events have no unique identifier (rapid same-key writes can share a
|
|
36
|
+
* millisecond timestamp), and React keys must be unique per render.
|
|
34
37
|
*/
|
|
35
38
|
|
|
39
|
+
let storageEventIdCounter = 0;
|
|
40
|
+
function nextStorageEventId() {
|
|
41
|
+
return `se-${Date.now()}-${++storageEventIdCounter}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
/**
|
|
37
45
|
* Listener callback type for storage events array
|
|
38
46
|
*/
|
|
@@ -66,7 +74,8 @@ class StorageEventStore extends _sharedUi.BaseEventStore {
|
|
|
66
74
|
this.asyncStorageUnsubscribe = (0, _AsyncStorageListener.addListener)(event => {
|
|
67
75
|
const storageEvent = {
|
|
68
76
|
...event,
|
|
69
|
-
storageType: "async"
|
|
77
|
+
storageType: "async",
|
|
78
|
+
id: nextStorageEventId()
|
|
70
79
|
};
|
|
71
80
|
this.addEvent(storageEvent);
|
|
72
81
|
});
|
|
@@ -77,7 +86,8 @@ class StorageEventStore extends _sharedUi.BaseEventStore {
|
|
|
77
86
|
this.mmkvUnsubscribe = (0, _MMKVListener.addMMKVListener)(event => {
|
|
78
87
|
const storageEvent = {
|
|
79
88
|
...event,
|
|
80
|
-
storageType: "mmkv"
|
|
89
|
+
storageType: "mmkv",
|
|
90
|
+
id: nextStorageEventId()
|
|
81
91
|
};
|
|
82
92
|
this.addEvent(storageEvent);
|
|
83
93
|
});
|
|
@@ -1,189 +1,95 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { Fragment } from "react";
|
|
4
|
-
import { View, Text,
|
|
3
|
+
import { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { View, Text, FlatList, StyleSheet, TouchableOpacity } from "react-native";
|
|
5
5
|
import { computeLineDiff, DiffType } from "../../../utils/lineDiff";
|
|
6
6
|
import { DiffSummary } from "../components/DiffSummary";
|
|
7
|
-
import { jsx as _jsx,
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
const DEFAULT_OPTIONS = {
|
|
9
|
+
hideLineNumbers: false,
|
|
10
|
+
disableWordDiff: false,
|
|
11
|
+
showDiffOnly: false,
|
|
12
|
+
compareMethod: "words",
|
|
13
|
+
contextLines: 3,
|
|
14
|
+
lineOffset: 0
|
|
15
|
+
};
|
|
8
16
|
export function ThemedSplitView({
|
|
9
17
|
oldValue,
|
|
10
18
|
newValue,
|
|
11
19
|
theme,
|
|
12
|
-
options =
|
|
13
|
-
hideLineNumbers: false,
|
|
14
|
-
disableWordDiff: false,
|
|
15
|
-
showDiffOnly: false,
|
|
16
|
-
compareMethod: "words",
|
|
17
|
-
contextLines: 3,
|
|
18
|
-
lineOffset: 0
|
|
19
|
-
},
|
|
20
|
+
options = DEFAULT_OPTIONS,
|
|
20
21
|
showThemeName = false
|
|
21
22
|
}) {
|
|
22
|
-
|
|
23
|
-
const diffComputeOptions = {
|
|
23
|
+
const dynamicStyles = useMemo(() => createDynamicStyles(theme), [theme]);
|
|
24
|
+
const diffComputeOptions = useMemo(() => ({
|
|
24
25
|
compareMethod: options.compareMethod,
|
|
25
26
|
disableWordDiff: options.disableWordDiff,
|
|
26
27
|
showDiffOnly: options.showDiffOnly,
|
|
27
28
|
contextLines: options.contextLines
|
|
28
|
-
};
|
|
29
|
-
const lineDiffs = computeLineDiff(oldValue, newValue, diffComputeOptions);
|
|
29
|
+
}), [options.compareMethod, options.disableWordDiff, options.showDiffOnly, options.contextLines]);
|
|
30
|
+
const lineDiffs = useMemo(() => computeLineDiff(oldValue, newValue, diffComputeOptions), [oldValue, newValue, diffComputeOptions]);
|
|
30
31
|
|
|
31
|
-
//
|
|
32
|
-
const
|
|
32
|
+
// One pass for the summary instead of three .filter() calls per render.
|
|
33
|
+
const summary = useMemo(() => {
|
|
34
|
+
let added = 0;
|
|
35
|
+
let removed = 0;
|
|
36
|
+
let modified = 0;
|
|
37
|
+
for (const d of lineDiffs) {
|
|
38
|
+
if (d.type === DiffType.ADDED) added++;else if (d.type === DiffType.REMOVED) removed++;else if (d.type === DiffType.MODIFIED) modified++;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
added,
|
|
42
|
+
removed,
|
|
43
|
+
modified
|
|
44
|
+
};
|
|
45
|
+
}, [lineDiffs]);
|
|
33
46
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (word.type === DiffType.ADDED) {
|
|
39
|
-
backgroundColor = theme.addedWordHighlight;
|
|
40
|
-
} else if (word.type === DiffType.REMOVED) {
|
|
41
|
-
backgroundColor = theme.removedWordHighlight;
|
|
42
|
-
}
|
|
43
|
-
return /*#__PURE__*/_jsx(Text, {
|
|
44
|
-
style: [dynamicStyles.wordDiff, {
|
|
45
|
-
backgroundColor
|
|
46
|
-
}],
|
|
47
|
-
children: word.value
|
|
48
|
-
}, idx);
|
|
49
|
-
});
|
|
50
|
-
};
|
|
47
|
+
// Auto-collapse runs of unchanged lines that aren't near a change. Keeps
|
|
48
|
+
// CONTEXT_LINES on either side of every change visible (GitHub-style), folds
|
|
49
|
+
// the rest into a tappable header.
|
|
50
|
+
const [expandedFolds, setExpandedFolds] = useState(() => new Set());
|
|
51
51
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
// Reset fold expansion when the user actually switches comparisons. Parent
|
|
53
|
+
// memoizes oldValue/newValue, so this only fires on a real change — not on
|
|
54
|
+
// every re-render. First run is a no-op (initial state is already empty).
|
|
55
|
+
const hasMountedRef = useRef(false);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!hasMountedRef.current) {
|
|
58
|
+
hasMountedRef.current = true;
|
|
59
|
+
return;
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
text: theme.unchangedText,
|
|
84
|
-
markerBg: "transparent"
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// Render a single line side (left or right)
|
|
90
|
-
const renderLineSide = (lineNumber, content, type, marker, isEmpty = false) => {
|
|
91
|
-
if (isEmpty) {
|
|
92
|
-
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
93
|
-
children: [!options.hideLineNumbers && /*#__PURE__*/_jsx(View, {
|
|
94
|
-
style: [dynamicStyles.gutter, dynamicStyles.emptyGutter],
|
|
95
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
96
|
-
style: dynamicStyles.lineNumber,
|
|
97
|
-
children: " "
|
|
98
|
-
})
|
|
99
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
100
|
-
style: [dynamicStyles.marker, dynamicStyles.emptyMarker],
|
|
101
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
102
|
-
style: dynamicStyles.markerText,
|
|
103
|
-
children: " "
|
|
104
|
-
})
|
|
105
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
106
|
-
style: [dynamicStyles.contentCell, dynamicStyles.emptyContent],
|
|
107
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
108
|
-
style: dynamicStyles.content,
|
|
109
|
-
children: " "
|
|
110
|
-
})
|
|
111
|
-
})]
|
|
61
|
+
setExpandedFolds(new Set());
|
|
62
|
+
}, [oldValue, newValue]);
|
|
63
|
+
const items = useMemo(() => buildSplitItems(lineDiffs, expandedFolds), [lineDiffs, expandedFolds]);
|
|
64
|
+
const toggleFold = useCallback(foldId => {
|
|
65
|
+
setExpandedFolds(prev => {
|
|
66
|
+
const next = new Set(prev);
|
|
67
|
+
if (next.has(foldId)) next.delete(foldId);else next.add(foldId);
|
|
68
|
+
return next;
|
|
69
|
+
});
|
|
70
|
+
}, []);
|
|
71
|
+
const keyExtractor = useCallback(item => item.kind === "row" ? `r:${item.diffIndex}` : `f:${item.foldId}`, []);
|
|
72
|
+
const renderItem = useCallback(({
|
|
73
|
+
item
|
|
74
|
+
}) => {
|
|
75
|
+
if (item.kind === "fold") {
|
|
76
|
+
return /*#__PURE__*/_jsx(FoldHeader, {
|
|
77
|
+
foldId: item.foldId,
|
|
78
|
+
count: item.count,
|
|
79
|
+
expanded: item.expanded,
|
|
80
|
+
theme: theme,
|
|
81
|
+
styles: dynamicStyles,
|
|
82
|
+
onToggle: toggleFold
|
|
112
83
|
});
|
|
113
84
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
121
|
-
style: dynamicStyles.lineNumber,
|
|
122
|
-
children: lineNumber || " "
|
|
123
|
-
})
|
|
124
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
125
|
-
style: [dynamicStyles.marker, {
|
|
126
|
-
backgroundColor: colors.markerBg
|
|
127
|
-
}],
|
|
128
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
129
|
-
style: [dynamicStyles.markerText, {
|
|
130
|
-
color: theme.markerText
|
|
131
|
-
}],
|
|
132
|
-
children: marker
|
|
133
|
-
})
|
|
134
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
135
|
-
style: [dynamicStyles.contentCell, {
|
|
136
|
-
backgroundColor: colors.background
|
|
137
|
-
}],
|
|
138
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
139
|
-
style: [dynamicStyles.content, {
|
|
140
|
-
color: colors.text
|
|
141
|
-
}],
|
|
142
|
-
children: Array.isArray(content) ? renderWordDiff(content) : content || " "
|
|
143
|
-
})
|
|
144
|
-
})]
|
|
85
|
+
return /*#__PURE__*/_jsx(DiffRow, {
|
|
86
|
+
diff: item.diff,
|
|
87
|
+
showSeparator: false,
|
|
88
|
+
theme: theme,
|
|
89
|
+
options: options,
|
|
90
|
+
styles: dynamicStyles
|
|
145
91
|
});
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
// Check if we should show a separator (gap in line numbers)
|
|
149
|
-
const shouldShowSeparator = (idx, diffs) => {
|
|
150
|
-
if (!options.showDiffOnly || idx === 0) return false;
|
|
151
|
-
const prevDiff = diffs[idx - 1];
|
|
152
|
-
const currDiff = diffs[idx];
|
|
153
|
-
|
|
154
|
-
// Check for gap in line numbers
|
|
155
|
-
const leftGap = currDiff.leftLineNumber && prevDiff.leftLineNumber && currDiff.leftLineNumber - prevDiff.leftLineNumber > 1;
|
|
156
|
-
const rightGap = currDiff.rightLineNumber && prevDiff.rightLineNumber && currDiff.rightLineNumber - prevDiff.rightLineNumber > 1;
|
|
157
|
-
return leftGap || rightGap;
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// Render a complete row with both left and right sides
|
|
161
|
-
const renderDiffRow = (diff, idx, diffs) => {
|
|
162
|
-
const isRemoved = diff.type === DiffType.REMOVED;
|
|
163
|
-
const isAdded = diff.type === DiffType.ADDED;
|
|
164
|
-
const isModified = diff.type === DiffType.MODIFIED;
|
|
165
|
-
const isDefault = diff.type === DiffType.DEFAULT;
|
|
166
|
-
return /*#__PURE__*/_jsxs(Fragment, {
|
|
167
|
-
children: [shouldShowSeparator(idx, diffs) && /*#__PURE__*/_jsx(View, {
|
|
168
|
-
style: dynamicStyles.separator,
|
|
169
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
170
|
-
style: dynamicStyles.separatorText,
|
|
171
|
-
children: "\u2022 \u2022 \u2022"
|
|
172
|
-
})
|
|
173
|
-
}), /*#__PURE__*/_jsxs(View, {
|
|
174
|
-
style: dynamicStyles.row,
|
|
175
|
-
children: [/*#__PURE__*/_jsx(View, {
|
|
176
|
-
style: dynamicStyles.leftSide,
|
|
177
|
-
children: isRemoved || isModified || isDefault ? renderLineSide(diff.leftLineNumber, diff.leftContent, isModified ? DiffType.REMOVED : diff.type, isRemoved || isModified ? "-" : " ") : renderLineSide(undefined, undefined, DiffType.DEFAULT, " ", true)
|
|
178
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
179
|
-
style: dynamicStyles.centerDivider
|
|
180
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
181
|
-
style: dynamicStyles.rightSide,
|
|
182
|
-
children: isAdded || isModified || isDefault ? renderLineSide(diff.rightLineNumber, diff.rightContent, isModified ? DiffType.ADDED : diff.type, isAdded || isModified ? "+" : " ") : renderLineSide(undefined, undefined, DiffType.DEFAULT, " ", true)
|
|
183
|
-
})]
|
|
184
|
-
})]
|
|
185
|
-
}, idx);
|
|
186
|
-
};
|
|
92
|
+
}, [theme, options, dynamicStyles, toggleFold]);
|
|
187
93
|
return /*#__PURE__*/_jsxs(View, {
|
|
188
94
|
style: [dynamicStyles.container, theme.glowColor && {
|
|
189
95
|
shadowColor: theme.glowColor,
|
|
@@ -221,24 +127,276 @@ export function ThemedSplitView({
|
|
|
221
127
|
})
|
|
222
128
|
})]
|
|
223
129
|
}), /*#__PURE__*/_jsx(DiffSummary, {
|
|
224
|
-
added:
|
|
225
|
-
removed:
|
|
226
|
-
modified:
|
|
130
|
+
added: summary.added,
|
|
131
|
+
removed: summary.removed,
|
|
132
|
+
modified: summary.modified,
|
|
227
133
|
theme: theme
|
|
228
|
-
}), /*#__PURE__*/_jsx(
|
|
134
|
+
}), lineDiffs.length === 0 ? /*#__PURE__*/_jsx(View, {
|
|
135
|
+
style: dynamicStyles.emptyState,
|
|
136
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
137
|
+
style: dynamicStyles.emptyText,
|
|
138
|
+
children: options.showDiffOnly ? "No differences found" : "No content to display"
|
|
139
|
+
})
|
|
140
|
+
}) : /*#__PURE__*/_jsx(FlatList, {
|
|
229
141
|
style: dynamicStyles.scrollView,
|
|
230
|
-
showsVerticalScrollIndicator: false,
|
|
231
142
|
contentContainerStyle: dynamicStyles.scrollContent,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
143
|
+
data: items,
|
|
144
|
+
renderItem: renderItem,
|
|
145
|
+
keyExtractor: keyExtractor,
|
|
146
|
+
showsVerticalScrollIndicator: false,
|
|
147
|
+
initialNumToRender: 20,
|
|
148
|
+
maxToRenderPerBatch: 20,
|
|
149
|
+
windowSize: 7,
|
|
150
|
+
removeClippedSubviews: true
|
|
151
|
+
})]
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Number of context lines kept visible on either side of every change.
|
|
156
|
+
const CONTEXT_LINES = 3;
|
|
157
|
+
// Don't bother folding tiny gaps — toggling them is more friction than scroll.
|
|
158
|
+
const MIN_FOLD_SIZE = 4;
|
|
159
|
+
function buildSplitItems(lineDiffs, expandedFolds) {
|
|
160
|
+
const n = lineDiffs.length;
|
|
161
|
+
if (n === 0) return [];
|
|
162
|
+
|
|
163
|
+
// Mark every line that should always be visible: any change, plus
|
|
164
|
+
// CONTEXT_LINES on either side of it.
|
|
165
|
+
const visible = new Array(n).fill(false);
|
|
166
|
+
for (let i = 0; i < n; i++) {
|
|
167
|
+
if (lineDiffs[i].type !== DiffType.DEFAULT) {
|
|
168
|
+
const start = Math.max(0, i - CONTEXT_LINES);
|
|
169
|
+
const end = Math.min(n - 1, i + CONTEXT_LINES);
|
|
170
|
+
for (let k = start; k <= end; k++) visible[k] = true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const items = [];
|
|
174
|
+
let foldId = 0;
|
|
175
|
+
let i = 0;
|
|
176
|
+
while (i < n) {
|
|
177
|
+
if (visible[i]) {
|
|
178
|
+
items.push({
|
|
179
|
+
kind: "row",
|
|
180
|
+
diff: lineDiffs[i],
|
|
181
|
+
diffIndex: i
|
|
182
|
+
});
|
|
183
|
+
i++;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
let j = i;
|
|
187
|
+
while (j < n && !visible[j]) j++;
|
|
188
|
+
const runLength = j - i;
|
|
189
|
+
if (runLength < MIN_FOLD_SIZE) {
|
|
190
|
+
for (let k = i; k < j; k++) {
|
|
191
|
+
items.push({
|
|
192
|
+
kind: "row",
|
|
193
|
+
diff: lineDiffs[k],
|
|
194
|
+
diffIndex: k
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
const id = foldId++;
|
|
199
|
+
const expanded = expandedFolds.has(id);
|
|
200
|
+
items.push({
|
|
201
|
+
kind: "fold",
|
|
202
|
+
foldId: id,
|
|
203
|
+
count: runLength,
|
|
204
|
+
expanded
|
|
205
|
+
});
|
|
206
|
+
if (expanded) {
|
|
207
|
+
for (let k = i; k < j; k++) {
|
|
208
|
+
items.push({
|
|
209
|
+
kind: "row",
|
|
210
|
+
diff: lineDiffs[k],
|
|
211
|
+
diffIndex: k
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
i = j;
|
|
217
|
+
}
|
|
218
|
+
return items;
|
|
219
|
+
}
|
|
220
|
+
const FoldHeader = /*#__PURE__*/memo(function FoldHeader({
|
|
221
|
+
foldId,
|
|
222
|
+
count,
|
|
223
|
+
expanded,
|
|
224
|
+
theme,
|
|
225
|
+
styles,
|
|
226
|
+
onToggle
|
|
227
|
+
}) {
|
|
228
|
+
return /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
229
|
+
style: [styles.foldHeader, {
|
|
230
|
+
backgroundColor: theme.separatorBackground
|
|
231
|
+
}],
|
|
232
|
+
onPress: () => onToggle(foldId),
|
|
233
|
+
activeOpacity: 0.7,
|
|
234
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
235
|
+
style: [styles.foldHeaderText, {
|
|
236
|
+
color: theme.separatorText
|
|
237
|
+
}],
|
|
238
|
+
children: expanded ? `▼ ${count} unchanged line${count === 1 ? "" : "s"} — tap to hide` : `▶ ${count} unchanged line${count === 1 ? "" : "s"} — tap to expand`
|
|
239
|
+
})
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
const DiffRow = /*#__PURE__*/memo(function DiffRow({
|
|
243
|
+
diff,
|
|
244
|
+
showSeparator,
|
|
245
|
+
theme,
|
|
246
|
+
options,
|
|
247
|
+
styles
|
|
248
|
+
}) {
|
|
249
|
+
const isRemoved = diff.type === DiffType.REMOVED;
|
|
250
|
+
const isAdded = diff.type === DiffType.ADDED;
|
|
251
|
+
const isModified = diff.type === DiffType.MODIFIED;
|
|
252
|
+
const isDefault = diff.type === DiffType.DEFAULT;
|
|
253
|
+
return /*#__PURE__*/_jsxs(Fragment, {
|
|
254
|
+
children: [showSeparator && /*#__PURE__*/_jsx(View, {
|
|
255
|
+
style: styles.separator,
|
|
256
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
257
|
+
style: styles.separatorText,
|
|
258
|
+
children: "\u2022 \u2022 \u2022"
|
|
259
|
+
})
|
|
260
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
261
|
+
style: styles.row,
|
|
262
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
263
|
+
style: styles.leftSide,
|
|
264
|
+
children: isRemoved || isModified || isDefault ? /*#__PURE__*/_jsx(LineSide, {
|
|
265
|
+
lineNumber: diff.leftLineNumber,
|
|
266
|
+
content: diff.leftContent,
|
|
267
|
+
type: isModified ? DiffType.REMOVED : diff.type,
|
|
268
|
+
marker: isRemoved || isModified ? "-" : " ",
|
|
269
|
+
theme: theme,
|
|
270
|
+
options: options,
|
|
271
|
+
styles: styles
|
|
272
|
+
}) : /*#__PURE__*/_jsx(EmptyLineSide, {
|
|
273
|
+
options: options,
|
|
274
|
+
styles: styles
|
|
237
275
|
})
|
|
238
|
-
})
|
|
276
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
277
|
+
style: styles.centerDivider
|
|
278
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
279
|
+
style: styles.rightSide,
|
|
280
|
+
children: isAdded || isModified || isDefault ? /*#__PURE__*/_jsx(LineSide, {
|
|
281
|
+
lineNumber: diff.rightLineNumber,
|
|
282
|
+
content: diff.rightContent,
|
|
283
|
+
type: isModified ? DiffType.ADDED : diff.type,
|
|
284
|
+
marker: isAdded || isModified ? "+" : " ",
|
|
285
|
+
theme: theme,
|
|
286
|
+
options: options,
|
|
287
|
+
styles: styles
|
|
288
|
+
}) : /*#__PURE__*/_jsx(EmptyLineSide, {
|
|
289
|
+
options: options,
|
|
290
|
+
styles: styles
|
|
291
|
+
})
|
|
292
|
+
})]
|
|
293
|
+
})]
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
function LineSide({
|
|
297
|
+
lineNumber,
|
|
298
|
+
content,
|
|
299
|
+
type,
|
|
300
|
+
marker,
|
|
301
|
+
theme,
|
|
302
|
+
options,
|
|
303
|
+
styles
|
|
304
|
+
}) {
|
|
305
|
+
const colors = getDiffColors(type, theme);
|
|
306
|
+
return /*#__PURE__*/_jsxs(Fragment, {
|
|
307
|
+
children: [!options.hideLineNumbers && /*#__PURE__*/_jsx(View, {
|
|
308
|
+
style: [styles.gutter, {
|
|
309
|
+
backgroundColor: theme.lineNumberBackground
|
|
310
|
+
}],
|
|
311
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
312
|
+
style: styles.lineNumber,
|
|
313
|
+
children: lineNumber || " "
|
|
314
|
+
})
|
|
315
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
316
|
+
style: [styles.marker, {
|
|
317
|
+
backgroundColor: colors.markerBg
|
|
318
|
+
}],
|
|
319
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
320
|
+
style: [styles.markerText, {
|
|
321
|
+
color: theme.markerText
|
|
322
|
+
}],
|
|
323
|
+
children: marker
|
|
324
|
+
})
|
|
325
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
326
|
+
style: [styles.contentCell, {
|
|
327
|
+
backgroundColor: colors.background
|
|
328
|
+
}],
|
|
329
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
330
|
+
style: [styles.content, {
|
|
331
|
+
color: colors.text
|
|
332
|
+
}],
|
|
333
|
+
children: Array.isArray(content) ? content.map((word, idx) => {
|
|
334
|
+
let backgroundColor = "transparent";
|
|
335
|
+
if (word.type === DiffType.ADDED) {
|
|
336
|
+
backgroundColor = theme.addedWordHighlight;
|
|
337
|
+
} else if (word.type === DiffType.REMOVED) {
|
|
338
|
+
backgroundColor = theme.removedWordHighlight;
|
|
339
|
+
}
|
|
340
|
+
return /*#__PURE__*/_jsx(Text, {
|
|
341
|
+
style: [styles.wordDiff, {
|
|
342
|
+
backgroundColor
|
|
343
|
+
}],
|
|
344
|
+
children: word.value
|
|
345
|
+
}, idx);
|
|
346
|
+
}) : content || " "
|
|
347
|
+
})
|
|
348
|
+
})]
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
function EmptyLineSide({
|
|
352
|
+
options,
|
|
353
|
+
styles
|
|
354
|
+
}) {
|
|
355
|
+
return /*#__PURE__*/_jsxs(Fragment, {
|
|
356
|
+
children: [!options.hideLineNumbers && /*#__PURE__*/_jsx(View, {
|
|
357
|
+
style: [styles.gutter, styles.emptyGutter],
|
|
358
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
359
|
+
style: styles.lineNumber,
|
|
360
|
+
children: " "
|
|
361
|
+
})
|
|
362
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
363
|
+
style: [styles.marker, styles.emptyMarker],
|
|
364
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
365
|
+
style: styles.markerText,
|
|
366
|
+
children: " "
|
|
367
|
+
})
|
|
368
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
369
|
+
style: [styles.contentCell, styles.emptyContent],
|
|
370
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
371
|
+
style: styles.content,
|
|
372
|
+
children: " "
|
|
373
|
+
})
|
|
239
374
|
})]
|
|
240
375
|
});
|
|
241
376
|
}
|
|
377
|
+
function getDiffColors(type, theme) {
|
|
378
|
+
switch (type) {
|
|
379
|
+
case DiffType.ADDED:
|
|
380
|
+
return {
|
|
381
|
+
background: theme.addedBackground,
|
|
382
|
+
text: theme.addedText,
|
|
383
|
+
markerBg: theme.markerAddedBackground
|
|
384
|
+
};
|
|
385
|
+
case DiffType.REMOVED:
|
|
386
|
+
return {
|
|
387
|
+
background: theme.removedBackground,
|
|
388
|
+
text: theme.removedText,
|
|
389
|
+
markerBg: theme.markerRemovedBackground
|
|
390
|
+
};
|
|
391
|
+
case DiffType.DEFAULT:
|
|
392
|
+
default:
|
|
393
|
+
return {
|
|
394
|
+
background: theme.unchangedBackground,
|
|
395
|
+
text: theme.unchangedText,
|
|
396
|
+
markerBg: "transparent"
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
242
400
|
|
|
243
401
|
// Create dynamic styles based on theme
|
|
244
402
|
function createDynamicStyles(theme) {
|
|
@@ -400,6 +558,20 @@ function createDynamicStyles(theme) {
|
|
|
400
558
|
fontFamily: "monospace",
|
|
401
559
|
letterSpacing: 2
|
|
402
560
|
},
|
|
561
|
+
foldHeader: {
|
|
562
|
+
paddingVertical: 6,
|
|
563
|
+
paddingHorizontal: 12,
|
|
564
|
+
alignItems: "center",
|
|
565
|
+
justifyContent: "center",
|
|
566
|
+
borderTopWidth: 1,
|
|
567
|
+
borderBottomWidth: 1,
|
|
568
|
+
borderColor: theme.borderColor
|
|
569
|
+
},
|
|
570
|
+
foldHeaderText: {
|
|
571
|
+
fontSize: 10,
|
|
572
|
+
fontFamily: "monospace",
|
|
573
|
+
letterSpacing: 0.5
|
|
574
|
+
},
|
|
403
575
|
emptyState: {
|
|
404
576
|
padding: 40,
|
|
405
577
|
alignItems: "center",
|