lynx-console 0.0.1 → 0.1.1
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/dist/assets/src/components/{BottomSheet.css.ts.vanilla-D-1A77Ik.css → BottomSheet.css.ts.vanilla-CulwSDhG.css} +2 -2
- package/dist/assets/src/components/ConsolePanel.css.ts.vanilla-DWdhFBJq.css +337 -0
- package/dist/assets/src/components/FadeList.css.ts.vanilla-sppTKMZj.css +12 -0
- package/dist/assets/src/components/{FloatingButton.css.ts.vanilla-rPj35oLW.css → FloatingButton.css.ts.vanilla-BaG0OI6p.css} +15 -3
- package/dist/assets/src/components/{NetworkPanel.css.ts.vanilla-DFMduT0T.css → NetworkPanel.css.ts.vanilla-BSE4s40D.css} +2 -5
- package/dist/assets/src/components/{PerformancePanel.css.ts.vanilla-D35LuXlW.css → PerformancePanel.css.ts.vanilla-Bb3zG5G8.css} +2 -2
- package/dist/index.cjs +743 -84
- package/dist/index.mjs +743 -84
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/BottomSheet.css.ts +2 -2
- package/src/components/ConsolePanel.css.ts +104 -16
- package/src/components/ConsolePanel.tsx +2 -1
- package/src/components/FadeList.css.ts +16 -0
- package/src/components/FadeList.tsx +76 -0
- package/src/components/FloatingButton.css.ts +15 -4
- package/src/components/FloatingButton.tsx +46 -10
- package/src/components/LogPanel.tsx +128 -15
- package/src/components/NetworkPanel.css.ts +2 -5
- package/src/components/NetworkPanel.tsx +4 -4
- package/src/components/PerformancePanel.css.ts +2 -2
- package/src/components/PerformancePanel.tsx +5 -5
- package/src/components/Tabs.tsx +3 -0
- package/src/hooks/useLongPressDrag.ts +95 -0
- package/src/index.tsx +1 -1
- package/dist/assets/src/components/ConsolePanel.css.ts.vanilla-B3avfSlI.css +0 -246
|
@@ -33,31 +33,128 @@ export const logHeader = style({
|
|
|
33
33
|
flexDirection: "row",
|
|
34
34
|
alignItems: "center",
|
|
35
35
|
justifyContent: "space-between",
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
paddingBottom: 3,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const filterWrapper = style({
|
|
40
|
+
position: "relative",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const filterButton = style({
|
|
44
|
+
display: "flex",
|
|
45
|
+
flexDirection: "row",
|
|
46
|
+
alignItems: "center",
|
|
47
|
+
padding: "3px 6px",
|
|
48
|
+
backgroundColor: vars.$color.bg.neutralWeak,
|
|
49
|
+
borderRadius: 4,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const filterButtonText = style({
|
|
53
|
+
...typography("t3", "medium"),
|
|
54
|
+
color: vars.$color.fg.neutralMuted,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const filterDropdown = style({
|
|
58
|
+
position: "absolute",
|
|
59
|
+
top: "100%",
|
|
60
|
+
left: 0,
|
|
61
|
+
marginTop: 4,
|
|
62
|
+
backgroundColor: vars.$color.bg.layerFloating,
|
|
63
|
+
borderWidth: 1,
|
|
64
|
+
borderColor: vars.$color.stroke.neutralSubtle,
|
|
65
|
+
borderStyle: "solid",
|
|
66
|
+
borderRadius: 8,
|
|
67
|
+
padding: "4px 0",
|
|
68
|
+
zIndex: 100,
|
|
69
|
+
minWidth: 90,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export const filterOption = style({
|
|
73
|
+
display: "flex",
|
|
74
|
+
flexDirection: "row",
|
|
75
|
+
alignItems: "center",
|
|
76
|
+
gap: 4,
|
|
77
|
+
padding: "8px 12px",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const filterCheckbox = recipe({
|
|
81
|
+
base: {
|
|
82
|
+
...typography("t3", "medium"),
|
|
83
|
+
width: 16,
|
|
84
|
+
},
|
|
85
|
+
variants: {
|
|
86
|
+
level: {
|
|
87
|
+
log: { color: vars.$color.palette.green600 },
|
|
88
|
+
info: { color: vars.$color.palette.blue600 },
|
|
89
|
+
warn: { color: vars.$color.palette.yellow600 },
|
|
90
|
+
error: { color: vars.$color.palette.red600 },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export const filterLabel = recipe({
|
|
96
|
+
base: {
|
|
97
|
+
...typography("t3", "medium"),
|
|
98
|
+
},
|
|
99
|
+
variants: {
|
|
100
|
+
level: {
|
|
101
|
+
log: { color: vars.$color.palette.green600 },
|
|
102
|
+
info: { color: vars.$color.palette.blue600 },
|
|
103
|
+
warn: { color: vars.$color.palette.yellow600 },
|
|
104
|
+
error: { color: vars.$color.palette.red600 },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export const searchWrapper = style({
|
|
110
|
+
display: "flex",
|
|
111
|
+
flexDirection: "row",
|
|
112
|
+
alignItems: "center",
|
|
113
|
+
flex: 1,
|
|
114
|
+
marginLeft: 8,
|
|
115
|
+
marginRight: 8,
|
|
38
116
|
borderBottomWidth: 1,
|
|
39
117
|
borderBottomColor: vars.$color.stroke.neutralSubtle,
|
|
40
118
|
borderBottomStyle: "solid",
|
|
119
|
+
gap: 8,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export const searchPrompt = style({
|
|
123
|
+
...typography("t6", "medium"),
|
|
124
|
+
color: vars.$color.fg.placeholder,
|
|
41
125
|
});
|
|
42
126
|
|
|
43
|
-
export const
|
|
127
|
+
export const searchInput = style({
|
|
128
|
+
flex: 1,
|
|
44
129
|
...typography("t3", "regular"),
|
|
45
|
-
color: vars.$color.fg.
|
|
130
|
+
color: vars.$color.fg.neutral,
|
|
131
|
+
caretColor: vars.$color.palette.green600,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export const searchClear = style({
|
|
135
|
+
padding: "2px 4px",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export const searchClearText = style({
|
|
139
|
+
...typography("t3", "medium"),
|
|
140
|
+
color: vars.$color.fg.placeholder,
|
|
46
141
|
});
|
|
47
142
|
|
|
48
143
|
export const clearButton = style({
|
|
49
|
-
padding: "6px
|
|
144
|
+
padding: "3px 6px",
|
|
50
145
|
backgroundColor: vars.$color.bg.neutralWeak,
|
|
51
146
|
borderRadius: 4,
|
|
52
147
|
});
|
|
53
148
|
|
|
54
149
|
export const clearButtonText = style({
|
|
55
150
|
...typography("t3", "medium"),
|
|
56
|
-
color: vars.$color.fg.
|
|
151
|
+
color: vars.$color.fg.neutralMuted,
|
|
57
152
|
});
|
|
58
153
|
|
|
59
154
|
export const logList = style({
|
|
60
155
|
flex: 1,
|
|
156
|
+
paddingTop: 0,
|
|
157
|
+
paddingBottom: 0,
|
|
61
158
|
});
|
|
62
159
|
|
|
63
160
|
export const logItem = recipe({
|
|
@@ -221,17 +318,8 @@ export const replInputRow = style({
|
|
|
221
318
|
flexDirection: "row",
|
|
222
319
|
alignItems: "center",
|
|
223
320
|
gap: 8,
|
|
224
|
-
paddingTop:
|
|
321
|
+
paddingTop: 0,
|
|
225
322
|
paddingBottom: 8,
|
|
226
|
-
marginTop: -1,
|
|
227
|
-
borderTopWidth: 1,
|
|
228
|
-
borderTopColor: vars.$color.stroke.neutralSubtle,
|
|
229
|
-
borderTopStyle: "solid",
|
|
230
|
-
backgroundImage: `linear-gradient(to bottom, transparent, ${vars.$color.bg.layerDefault})`,
|
|
231
|
-
backgroundSize: "100% 32px",
|
|
232
|
-
backgroundRepeat: "no-repeat",
|
|
233
|
-
backgroundPosition: "top",
|
|
234
|
-
backgroundColor: vars.$color.bg.layerDefault,
|
|
235
323
|
});
|
|
236
324
|
|
|
237
325
|
export const replPrompt = style({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useConsole, useNetwork, usePerformance } from "../hooks";
|
|
2
2
|
import * as css from "./ConsolePanel.css";
|
|
3
|
-
import { LogPanel } from "./LogPanel";
|
|
3
|
+
import { LogPanel, dismissFilterDropdown } from "./LogPanel";
|
|
4
4
|
import { NetworkPanel } from "./NetworkPanel";
|
|
5
5
|
import { PerformancePanel } from "./PerformancePanel";
|
|
6
6
|
import Tabs from "./Tabs";
|
|
@@ -13,6 +13,7 @@ export const ConsolePanel = () => {
|
|
|
13
13
|
return (
|
|
14
14
|
<view className={css.container}>
|
|
15
15
|
<Tabs
|
|
16
|
+
onTabChange={dismissFilterDropdown}
|
|
16
17
|
items={[
|
|
17
18
|
{
|
|
18
19
|
key: "log",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { style } from "@vanilla-extract/css";
|
|
2
|
+
import { vars } from "../styles/vars";
|
|
3
|
+
|
|
4
|
+
export const fadeTop = style({
|
|
5
|
+
height: 20,
|
|
6
|
+
marginBottom: -20,
|
|
7
|
+
zIndex: 1,
|
|
8
|
+
background: `linear-gradient(to bottom, ${vars.$color.bg.layerFloating}, #ffffff00)`,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const fadeBottom = style({
|
|
12
|
+
height: 20,
|
|
13
|
+
marginTop: -20,
|
|
14
|
+
zIndex: 1,
|
|
15
|
+
background: `linear-gradient(to top, ${vars.$color.bg.layerFloating}, #ffffff00)`,
|
|
16
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useRef, useState } from "@lynx-js/react";
|
|
2
|
+
import type { BaseEvent, NodesRef } from "@lynx-js/types";
|
|
3
|
+
import { vars } from "../styles/vars";
|
|
4
|
+
import * as css from "./FadeList.css";
|
|
5
|
+
|
|
6
|
+
interface FadeListProps {
|
|
7
|
+
className?: string;
|
|
8
|
+
listRef?: React.RefObject<NodesRef>;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
"preload-buffer-count"?: number;
|
|
11
|
+
"initial-scroll-index"?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const FadeList = ({
|
|
15
|
+
className,
|
|
16
|
+
listRef: externalListRef,
|
|
17
|
+
children,
|
|
18
|
+
...listProps
|
|
19
|
+
}: FadeListProps) => {
|
|
20
|
+
const [fadeState, setFadeState] = useState({ atTop: true, atBottom: true });
|
|
21
|
+
const fadeRef = useRef({ atTop: true, atBottom: true });
|
|
22
|
+
const internalListRef = useRef<NodesRef>(null);
|
|
23
|
+
const listRef = externalListRef ?? internalListRef;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<view
|
|
28
|
+
className={css.fadeTop}
|
|
29
|
+
style={{
|
|
30
|
+
background: fadeState.atTop
|
|
31
|
+
? "linear-gradient(to bottom, #ffffff00, #ffffff00)"
|
|
32
|
+
: `linear-gradient(to bottom, ${vars.$color.bg.layerFloating}, #ffffff00)`,
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
<list
|
|
36
|
+
ref={listRef}
|
|
37
|
+
scroll-orientation="vertical"
|
|
38
|
+
className={className}
|
|
39
|
+
scroll-event-throttle={16}
|
|
40
|
+
bindscroll={(
|
|
41
|
+
e: BaseEvent<
|
|
42
|
+
"bindscroll",
|
|
43
|
+
{
|
|
44
|
+
scrollTop: number;
|
|
45
|
+
scrollHeight: number;
|
|
46
|
+
listHeight: number;
|
|
47
|
+
}
|
|
48
|
+
>,
|
|
49
|
+
) => {
|
|
50
|
+
const { scrollTop, scrollHeight, listHeight } = e.detail;
|
|
51
|
+
const atTop = scrollTop <= 10;
|
|
52
|
+
const atBottom = scrollTop + listHeight >= scrollHeight - 10;
|
|
53
|
+
if (
|
|
54
|
+
atTop !== fadeRef.current.atTop ||
|
|
55
|
+
atBottom !== fadeRef.current.atBottom
|
|
56
|
+
) {
|
|
57
|
+
fadeRef.current.atTop = atTop;
|
|
58
|
+
fadeRef.current.atBottom = atBottom;
|
|
59
|
+
setFadeState({ atTop, atBottom });
|
|
60
|
+
}
|
|
61
|
+
}}
|
|
62
|
+
{...listProps}
|
|
63
|
+
>
|
|
64
|
+
{children}
|
|
65
|
+
</list>
|
|
66
|
+
<view
|
|
67
|
+
className={css.fadeBottom}
|
|
68
|
+
style={{
|
|
69
|
+
background: fadeState.atBottom
|
|
70
|
+
? "linear-gradient(to top, #ffffff00, #ffffff00)"
|
|
71
|
+
: `linear-gradient(to top, ${vars.$color.bg.layerFloating}, #ffffff00)`,
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
</>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
@@ -4,18 +4,18 @@ import { vars } from "../styles/vars";
|
|
|
4
4
|
|
|
5
5
|
export const wrapper = style({
|
|
6
6
|
position: "fixed",
|
|
7
|
-
right: "16px",
|
|
8
|
-
bottom: "84px",
|
|
9
7
|
zIndex: 9999,
|
|
10
8
|
display: "flex",
|
|
11
9
|
flexDirection: "row",
|
|
12
10
|
alignItems: "center",
|
|
13
11
|
gap: "8px",
|
|
12
|
+
overflow: "visible",
|
|
13
|
+
transition: `transform ${vars.$duration.d4} cubic-bezier(0.4, 0, 0.2, 1)`,
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
export const container = style({});
|
|
17
|
-
|
|
18
16
|
export const button = style({
|
|
17
|
+
position: "relative",
|
|
18
|
+
overflow: "hidden",
|
|
19
19
|
paddingLeft: "8px",
|
|
20
20
|
paddingRight: "8px",
|
|
21
21
|
paddingTop: "4px",
|
|
@@ -30,6 +30,16 @@ export const button = style({
|
|
|
30
30
|
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
export const shineOverlay = style({
|
|
34
|
+
position: "absolute",
|
|
35
|
+
top: "-50%",
|
|
36
|
+
left: "-25%",
|
|
37
|
+
width: "150%",
|
|
38
|
+
height: "200%",
|
|
39
|
+
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
|
40
|
+
borderRadius: "9999px",
|
|
41
|
+
});
|
|
42
|
+
|
|
33
43
|
export const title = style({
|
|
34
44
|
...typography("t4", "regular"),
|
|
35
45
|
color: vars.$color.palette.staticWhite,
|
|
@@ -43,6 +53,7 @@ export const subtitle = style({
|
|
|
43
53
|
});
|
|
44
54
|
|
|
45
55
|
export const reloadButton = style({
|
|
56
|
+
overflow: "visible",
|
|
46
57
|
width: "32px",
|
|
47
58
|
height: "32px",
|
|
48
59
|
borderRadius: "16px",
|
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
import type { ReactNode } from "@lynx-js/react";
|
|
2
|
+
import { useLongPressDrag } from "../hooks/useLongPressDrag";
|
|
2
3
|
import * as css from "./FloatingButton.css";
|
|
3
4
|
|
|
4
5
|
interface FloatingButtonProps {
|
|
5
6
|
bindtap: () => void;
|
|
6
|
-
isVisible: boolean;
|
|
7
7
|
children: ReactNode;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
const SHINE_STYLES = {
|
|
11
|
+
idle: {
|
|
12
|
+
transform: "scale(0)",
|
|
13
|
+
opacity: 0,
|
|
14
|
+
},
|
|
15
|
+
dragging: {
|
|
16
|
+
transform: "scale(1)",
|
|
17
|
+
opacity: 1,
|
|
18
|
+
transition: "transform 300ms cubic-bezier(0.4, 0, 0.2, 1)",
|
|
19
|
+
},
|
|
20
|
+
releasing: {
|
|
21
|
+
transform: "scale(1)",
|
|
22
|
+
opacity: 0,
|
|
23
|
+
transition: "opacity 300ms cubic-bezier(0.4, 0, 0.2, 1)",
|
|
24
|
+
},
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
10
27
|
export const FloatingButton = ({
|
|
11
28
|
bindtap,
|
|
12
|
-
isVisible,
|
|
13
29
|
children,
|
|
14
30
|
}: FloatingButtonProps) => {
|
|
15
|
-
|
|
31
|
+
const { phase, right, bottom, clearTimer, handlers } =
|
|
32
|
+
useLongPressDrag(bindtap);
|
|
33
|
+
|
|
16
34
|
|
|
17
35
|
const handleReload = () => {
|
|
18
36
|
try {
|
|
@@ -24,14 +42,32 @@ export const FloatingButton = ({
|
|
|
24
42
|
}
|
|
25
43
|
};
|
|
26
44
|
|
|
45
|
+
const isDragging = phase === "dragging";
|
|
46
|
+
|
|
27
47
|
return (
|
|
28
|
-
|
|
29
|
-
<view
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
48
|
+
<>
|
|
49
|
+
<view
|
|
50
|
+
className={css.wrapper}
|
|
51
|
+
consume-slide-event={[[-180, 180]]}
|
|
52
|
+
style={{
|
|
53
|
+
right: `${right}px`,
|
|
54
|
+
bottom: `${bottom}px`,
|
|
55
|
+
transform: isDragging ? "scale(1.05)" : "scale(1)",
|
|
56
|
+
}}
|
|
57
|
+
{...handlers}
|
|
58
|
+
>
|
|
59
|
+
<view className={css.button}>
|
|
60
|
+
{children}
|
|
61
|
+
<view className={css.shineOverlay} style={SHINE_STYLES[phase]} />
|
|
62
|
+
</view>
|
|
63
|
+
<view
|
|
64
|
+
className={css.reloadButton}
|
|
65
|
+
catchtouchstart={() => clearTimer()}
|
|
66
|
+
bindtap={handleReload}
|
|
67
|
+
>
|
|
68
|
+
<text className={css.reloadIcon}>{"\u21BB"}</text>
|
|
69
|
+
</view>
|
|
34
70
|
</view>
|
|
35
|
-
|
|
71
|
+
</>
|
|
36
72
|
);
|
|
37
73
|
};
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from "@lynx-js/react";
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "@lynx-js/react";
|
|
2
2
|
import type { BaseEvent, InputInputEvent, NodesRef } from "@lynx-js/types";
|
|
3
3
|
import { stringify } from "javascript-stringify";
|
|
4
|
-
import type { LogEntry } from "../types";
|
|
4
|
+
import type { LogEntry, LogLevel } from "../types";
|
|
5
5
|
import * as css from "./ConsolePanel.css";
|
|
6
|
+
import { FadeList } from "./FadeList";
|
|
7
|
+
|
|
8
|
+
const LOG_LEVELS: LogLevel[] = ["log", "info", "warn", "error"];
|
|
9
|
+
|
|
10
|
+
let savedEnabledLevels: Set<LogLevel> | null = null;
|
|
11
|
+
let savedSearchQuery = "";
|
|
12
|
+
let closeFilterDropdown: (() => void) | null = null;
|
|
13
|
+
|
|
14
|
+
export const dismissFilterDropdown = () => closeFilterDropdown?.();
|
|
6
15
|
|
|
7
16
|
interface LogPanelProps {
|
|
8
17
|
logs: LogEntry[];
|
|
@@ -26,10 +35,62 @@ const runCode = (code: string) => {
|
|
|
26
35
|
export const LogPanel = ({ logs, clearLogs }: LogPanelProps) => {
|
|
27
36
|
const [expandedArgs, setExpandedArgs] = useState(new Set());
|
|
28
37
|
const [code, setCode] = useState("");
|
|
38
|
+
const [enabledLevels, setEnabledLevels] = useState<Set<LogLevel>>(
|
|
39
|
+
() => savedEnabledLevels ?? new Set(LOG_LEVELS),
|
|
40
|
+
);
|
|
41
|
+
const [filterOpen, setFilterOpen] = useState(false);
|
|
42
|
+
const [searchQuery, setSearchQuery] = useState(savedSearchQuery);
|
|
29
43
|
const inputRef = useRef<NodesRef>(null);
|
|
44
|
+
const searchInputRef = useRef<NodesRef>(null);
|
|
30
45
|
const listRef = useRef<NodesRef>(null);
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
savedEnabledLevels = enabledLevels;
|
|
49
|
+
}, [enabledLevels]);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
savedSearchQuery = searchQuery;
|
|
53
|
+
}, [searchQuery]);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (savedSearchQuery) {
|
|
57
|
+
searchInputRef.current
|
|
58
|
+
?.invoke({ method: "setValue", params: { value: savedSearchQuery } })
|
|
59
|
+
.exec();
|
|
60
|
+
}
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
closeFilterDropdown = () => setFilterOpen(false);
|
|
65
|
+
return () => { closeFilterDropdown = null; };
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
const filteredLogs = useMemo(
|
|
69
|
+
() =>
|
|
70
|
+
logs.filter((log) => {
|
|
71
|
+
if (!enabledLevels.has(log.level)) return false;
|
|
72
|
+
if (searchQuery) {
|
|
73
|
+
const query = searchQuery.toLowerCase();
|
|
74
|
+
return log.args.some((arg) => String(arg).toLowerCase().includes(query));
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}),
|
|
78
|
+
[logs, enabledLevels, searchQuery],
|
|
79
|
+
);
|
|
80
|
+
const logsRef = useRef(filteredLogs);
|
|
81
|
+
logsRef.current = filteredLogs;
|
|
82
|
+
|
|
83
|
+
const toggleLevel = (level: LogLevel) => {
|
|
84
|
+
setEnabledLevels((prev) => {
|
|
85
|
+
const next = new Set(prev);
|
|
86
|
+
if (next.has(level)) {
|
|
87
|
+
next.delete(level);
|
|
88
|
+
} else {
|
|
89
|
+
next.add(level);
|
|
90
|
+
}
|
|
91
|
+
return next;
|
|
92
|
+
});
|
|
93
|
+
};
|
|
33
94
|
|
|
34
95
|
const scrollToBottom = (smooth: boolean) => {
|
|
35
96
|
if (logsRef.current.length === 0) return;
|
|
@@ -43,7 +104,7 @@ export const LogPanel = ({ logs, clearLogs }: LogPanelProps) => {
|
|
|
43
104
|
|
|
44
105
|
useEffect(() => {
|
|
45
106
|
scrollToBottom(true);
|
|
46
|
-
}, [
|
|
107
|
+
}, [filteredLogs]);
|
|
47
108
|
|
|
48
109
|
const toggleArg = (key: string) => {
|
|
49
110
|
setExpandedArgs((prev) => {
|
|
@@ -165,22 +226,74 @@ export const LogPanel = ({ logs, clearLogs }: LogPanelProps) => {
|
|
|
165
226
|
};
|
|
166
227
|
|
|
167
228
|
return (
|
|
168
|
-
<view
|
|
229
|
+
<view
|
|
230
|
+
className={css.logContainer}
|
|
231
|
+
bindtap={() => { if (filterOpen) setFilterOpen(false); }}
|
|
232
|
+
>
|
|
169
233
|
<view className={css.logHeader}>
|
|
170
|
-
<
|
|
234
|
+
<view className={css.filterWrapper}>
|
|
235
|
+
<view
|
|
236
|
+
className={css.filterButton}
|
|
237
|
+
catchtap={() => setFilterOpen((v) => !v)}
|
|
238
|
+
>
|
|
239
|
+
<text className={css.filterButtonText}>Filter ▼</text>
|
|
240
|
+
</view>
|
|
241
|
+
{filterOpen && (
|
|
242
|
+
<view className={css.filterDropdown} catchtap={() => {}}>
|
|
243
|
+
{LOG_LEVELS.map((level) => (
|
|
244
|
+
<view
|
|
245
|
+
key={level}
|
|
246
|
+
className={css.filterOption}
|
|
247
|
+
bindtap={() => toggleLevel(level)}
|
|
248
|
+
>
|
|
249
|
+
<text className={css.filterCheckbox({ level })}>
|
|
250
|
+
{enabledLevels.has(level) ? "✅" : "⬜"}
|
|
251
|
+
</text>
|
|
252
|
+
<text className={css.filterLabel({ level })}>
|
|
253
|
+
{level.toUpperCase()}
|
|
254
|
+
</text>
|
|
255
|
+
</view>
|
|
256
|
+
))}
|
|
257
|
+
</view>
|
|
258
|
+
)}
|
|
259
|
+
</view>
|
|
260
|
+
<view className={css.searchWrapper}>
|
|
261
|
+
<text className={css.searchPrompt}>{"›"}</text>
|
|
262
|
+
<input
|
|
263
|
+
ref={searchInputRef}
|
|
264
|
+
className={css.searchInput}
|
|
265
|
+
placeholder="Search logs..."
|
|
266
|
+
bindinput={(e: BaseEvent<"bindinput", InputInputEvent>) =>
|
|
267
|
+
setSearchQuery(e.detail.value)
|
|
268
|
+
}
|
|
269
|
+
/>
|
|
270
|
+
{searchQuery.length > 0 && (
|
|
271
|
+
<view
|
|
272
|
+
className={css.searchClear}
|
|
273
|
+
bindtap={() => {
|
|
274
|
+
setSearchQuery("");
|
|
275
|
+
searchInputRef.current
|
|
276
|
+
?.invoke({ method: "setValue", params: { value: "" } })
|
|
277
|
+
.exec();
|
|
278
|
+
}}
|
|
279
|
+
>
|
|
280
|
+
<text className={css.searchClearText}>✕</text>
|
|
281
|
+
</view>
|
|
282
|
+
)}
|
|
283
|
+
</view>
|
|
171
284
|
<view style={{ display: "flex", flexDirection: "row", gap: 8 }}>
|
|
172
285
|
<view className={css.clearButton} bindtap={clearLogs}>
|
|
173
|
-
<text className={css.clearButtonText}
|
|
286
|
+
<text className={css.clearButtonText}>🗑</text>
|
|
174
287
|
</view>
|
|
175
288
|
</view>
|
|
176
289
|
</view>
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
scroll-orientation="vertical"
|
|
290
|
+
<FadeList
|
|
291
|
+
listRef={listRef}
|
|
180
292
|
className={css.logList}
|
|
181
|
-
|
|
293
|
+
preload-buffer-count={10}
|
|
294
|
+
initial-scroll-index={Math.max(0, filteredLogs.length - 1)}
|
|
182
295
|
>
|
|
183
|
-
{
|
|
296
|
+
{filteredLogs.length === 0 ? (
|
|
184
297
|
<list-item item-key="empty-state">
|
|
185
298
|
<view className={css.placeholder}>
|
|
186
299
|
<text className={css.placeholderText}>
|
|
@@ -189,7 +302,7 @@ export const LogPanel = ({ logs, clearLogs }: LogPanelProps) => {
|
|
|
189
302
|
</view>
|
|
190
303
|
</list-item>
|
|
191
304
|
) : (
|
|
192
|
-
|
|
305
|
+
filteredLogs.map((log) => {
|
|
193
306
|
return (
|
|
194
307
|
<list-item key={log.id} item-key={log.id}>
|
|
195
308
|
<view className={css.logItem({ level: log.level })}>
|
|
@@ -220,7 +333,7 @@ export const LogPanel = ({ logs, clearLogs }: LogPanelProps) => {
|
|
|
220
333
|
);
|
|
221
334
|
})
|
|
222
335
|
)}
|
|
223
|
-
</
|
|
336
|
+
</FadeList>
|
|
224
337
|
<view className={css.replInputRow}>
|
|
225
338
|
<text className={css.replPrompt}>{"›"}</text>
|
|
226
339
|
<input
|
|
@@ -17,9 +17,6 @@ export const header = style({
|
|
|
17
17
|
justifyContent: "space-between",
|
|
18
18
|
marginBottom: 8,
|
|
19
19
|
paddingBottom: 4,
|
|
20
|
-
borderBottomWidth: 1,
|
|
21
|
-
borderBottomColor: vars.$color.stroke.neutralSubtle,
|
|
22
|
-
borderBottomStyle: "solid",
|
|
23
20
|
});
|
|
24
21
|
|
|
25
22
|
export const count = style({
|
|
@@ -28,14 +25,14 @@ export const count = style({
|
|
|
28
25
|
});
|
|
29
26
|
|
|
30
27
|
export const clearButton = style({
|
|
31
|
-
padding: "6px
|
|
28
|
+
padding: "3px 6px",
|
|
32
29
|
backgroundColor: vars.$color.bg.neutralWeak,
|
|
33
30
|
borderRadius: 4,
|
|
34
31
|
});
|
|
35
32
|
|
|
36
33
|
export const clearButtonText = style({
|
|
37
34
|
...typography("t3", "medium"),
|
|
38
|
-
color: vars.$color.fg.
|
|
35
|
+
color: vars.$color.fg.neutralMuted,
|
|
39
36
|
});
|
|
40
37
|
|
|
41
38
|
export const list = style({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState } from "@lynx-js/react";
|
|
2
2
|
import type { NetworkEntry } from "../types";
|
|
3
|
+
import { FadeList } from "./FadeList";
|
|
3
4
|
import { NetworkDetailSection } from "./NetworkDetailSection";
|
|
4
5
|
import * as css from "./NetworkPanel.css";
|
|
5
6
|
|
|
@@ -16,7 +17,6 @@ export const NetworkPanel = ({
|
|
|
16
17
|
}: NetworkPanelProps) => {
|
|
17
18
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
18
19
|
const [activeTab, setActiveTab] = useState<TabType>("general");
|
|
19
|
-
|
|
20
20
|
const formatDuration = (duration?: number): string => {
|
|
21
21
|
if (!duration) return "-";
|
|
22
22
|
if (duration < 1000) return `${duration}ms`;
|
|
@@ -71,7 +71,7 @@ export const NetworkPanel = ({
|
|
|
71
71
|
<view className={css.header}>
|
|
72
72
|
<text className={css.count}>Total: {networks.length} requests</text>
|
|
73
73
|
<view className={css.clearButton} bindtap={clearNetworks}>
|
|
74
|
-
<text className={css.clearButtonText}
|
|
74
|
+
<text className={css.clearButtonText}>🗑</text>
|
|
75
75
|
</view>
|
|
76
76
|
</view>
|
|
77
77
|
|
|
@@ -80,7 +80,7 @@ export const NetworkPanel = ({
|
|
|
80
80
|
<text className={css.placeholderText}>No network requests yet</text>
|
|
81
81
|
</view>
|
|
82
82
|
) : (
|
|
83
|
-
<
|
|
83
|
+
<FadeList className={css.list}>
|
|
84
84
|
{networks.map((network) => (
|
|
85
85
|
<list-item key={network.id} item-key={network.id}>
|
|
86
86
|
<view className={css.item({ status: network.status })}>
|
|
@@ -215,7 +215,7 @@ export const NetworkPanel = ({
|
|
|
215
215
|
</view>
|
|
216
216
|
</list-item>
|
|
217
217
|
))}
|
|
218
|
-
</
|
|
218
|
+
</FadeList>
|
|
219
219
|
)}
|
|
220
220
|
</view>
|
|
221
221
|
);
|
|
@@ -28,14 +28,14 @@ export const count = style({
|
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
export const clearButton = style({
|
|
31
|
-
padding: "6px
|
|
31
|
+
padding: "3px 6px",
|
|
32
32
|
backgroundColor: vars.$color.bg.neutralWeak,
|
|
33
33
|
borderRadius: 4,
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
export const clearButtonText = style({
|
|
37
37
|
...typography("t3", "medium"),
|
|
38
|
-
color: vars.$color.fg.
|
|
38
|
+
color: vars.$color.fg.neutralMuted,
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
export const list = style({
|