@peers-app/peers-ui 0.8.2 → 0.8.4
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/screens/console-logs/console-logs-list.js +11 -5
- package/dist/screens/console-logs/mobile-log-card.d.ts +8 -0
- package/dist/screens/console-logs/mobile-log-card.js +96 -0
- package/dist/tabs-layout/tabs-layout.js +10 -10
- package/package.json +3 -3
- package/src/screens/console-logs/console-logs-list.tsx +21 -8
- package/src/screens/console-logs/mobile-log-card.tsx +154 -0
- package/src/tabs-layout/tabs-layout.tsx +11 -11
|
@@ -48,6 +48,8 @@ const color_mode_dropdown_1 = require("../settings/color-mode-dropdown");
|
|
|
48
48
|
const log_display_1 = require("./log-display");
|
|
49
49
|
const log_filters_1 = require("./log-filters");
|
|
50
50
|
const resizable_table_header_1 = require("./resizable-table-header");
|
|
51
|
+
const globals_1 = require("../../globals");
|
|
52
|
+
const mobile_log_card_1 = require("./mobile-log-card");
|
|
51
53
|
const windowHeight = () => window.innerHeight;
|
|
52
54
|
const DEFAULT_COLUMNS = [
|
|
53
55
|
{ key: 'timestamp', label: 'Timestamp', width: 150 },
|
|
@@ -68,6 +70,8 @@ const ConsoleLogsList = () => {
|
|
|
68
70
|
const [_colorMode] = (0, hooks_1.useObservable)(color_mode_dropdown_1.colorMode);
|
|
69
71
|
const logsEndRef = react_1.default.useRef(null);
|
|
70
72
|
const containerRef = react_1.default.useRef(null);
|
|
73
|
+
(0, hooks_1.useObservable)(globals_1.isDesktop);
|
|
74
|
+
const isMobile = !(0, globals_1.isDesktop)();
|
|
71
75
|
const batchSize = 50;
|
|
72
76
|
// Track fixed column widths to trigger message column recalculation
|
|
73
77
|
const fixedColumnsWidthKey = (0, react_1.useMemo)(() => {
|
|
@@ -225,7 +229,7 @@ const ConsoleLogsList = () => {
|
|
|
225
229
|
marginBottom: '50px',
|
|
226
230
|
overflow: 'hidden',
|
|
227
231
|
} },
|
|
228
|
-
react_1.default.createElement(resizable_table_header_1.ResizableTableHeader, { columns: columns, onColumnsChange: setColumns, colorMode: _colorMode }),
|
|
232
|
+
!isMobile && (react_1.default.createElement(resizable_table_header_1.ResizableTableHeader, { columns: columns, onColumnsChange: setColumns, colorMode: _colorMode })),
|
|
229
233
|
react_1.default.createElement("div", { id: "scrollableLogsDiv", style: {
|
|
230
234
|
flex: 1,
|
|
231
235
|
overflow: 'auto',
|
|
@@ -239,10 +243,12 @@ const ConsoleLogsList = () => {
|
|
|
239
243
|
react_1.default.createElement("div", { ref: logsEndRef }),
|
|
240
244
|
_logs.length === 0 && allLogsLoaded ? (react_1.default.createElement("div", { className: "text-center p-5 text-muted" },
|
|
241
245
|
react_1.default.createElement("i", { className: "bi bi-inbox display-1" }),
|
|
242
|
-
react_1.default.createElement("p", { className: "mt-2" }, "No logs found"))) :
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
+
react_1.default.createElement("p", { className: "mt-2" }, "No logs found"))) : isMobile ? (
|
|
247
|
+
/* Mobile: Card-based layout */
|
|
248
|
+
react_1.default.createElement("div", null, _logs.map((log) => (react_1.default.createElement(mobile_log_card_1.MobileLogCard, { key: log.logId, log: log, colorMode: _colorMode }))))) : (
|
|
249
|
+
/* Desktop: Table layout */
|
|
250
|
+
react_1.default.createElement("table", { className: "table table-sm table-hover mb-0", style: { fontSize: '0.85rem', tableLayout: 'fixed', width: '100%' } },
|
|
251
|
+
react_1.default.createElement("tbody", null, _logs.map((log) => (react_1.default.createElement(log_display_1.LogDisplay, { key: log.logId, log: log, columns: columns }))))))))),
|
|
246
252
|
react_1.default.createElement("div", { style: {
|
|
247
253
|
position: 'fixed',
|
|
248
254
|
bottom: 0,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IConsoleLog } from "@peers-app/peers-sdk";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
interface MobileLogCardProps {
|
|
4
|
+
log: IConsoleLog;
|
|
5
|
+
colorMode: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const MobileLogCard: ({ log, colorMode }: MobileLogCardProps) => React.JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MobileLogCard = void 0;
|
|
7
|
+
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
8
|
+
const moment_1 = __importDefault(require("moment"));
|
|
9
|
+
const react_1 = __importDefault(require("react"));
|
|
10
|
+
const getLevelColor = (level) => {
|
|
11
|
+
switch (level) {
|
|
12
|
+
case 'error': return '#dc3545';
|
|
13
|
+
case 'warn': return '#ffc107';
|
|
14
|
+
case 'info': return '#0dcaf0';
|
|
15
|
+
case 'log': return '#6c757d';
|
|
16
|
+
case 'debug': return '#6c757d';
|
|
17
|
+
default: return '#6c757d';
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const getProcessColor = (process) => {
|
|
21
|
+
switch (process) {
|
|
22
|
+
case 'ui':
|
|
23
|
+
case 'renderer': return '#0dcaf0';
|
|
24
|
+
case 'electron':
|
|
25
|
+
case 'main': return '#d946ef';
|
|
26
|
+
case 'react-native': return '#61dafb';
|
|
27
|
+
default: return '#6c757d';
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const getLevelIcon = (level) => {
|
|
31
|
+
switch (level) {
|
|
32
|
+
case 'error': return 'bi-x-circle-fill';
|
|
33
|
+
case 'warn': return 'bi-exclamation-triangle-fill';
|
|
34
|
+
case 'info': return 'bi-info-circle-fill';
|
|
35
|
+
case 'debug': return 'bi-bug-fill';
|
|
36
|
+
default: return 'bi-chat-square-text';
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const MobileLogCard = ({ log, colorMode }) => {
|
|
40
|
+
const isDark = colorMode === 'dark';
|
|
41
|
+
// Parse context if it's a string
|
|
42
|
+
let context = log.context;
|
|
43
|
+
if (typeof context === 'string') {
|
|
44
|
+
try {
|
|
45
|
+
context = (0, peers_sdk_1.fromJSONString)(context);
|
|
46
|
+
}
|
|
47
|
+
catch (err) { }
|
|
48
|
+
}
|
|
49
|
+
return (react_1.default.createElement("div", { style: {
|
|
50
|
+
padding: '10px 12px',
|
|
51
|
+
borderBottom: `1px solid ${isDark ? '#333' : '#e9ecef'}`,
|
|
52
|
+
backgroundColor: isDark ? '#1a1a1a' : '#fff',
|
|
53
|
+
} },
|
|
54
|
+
react_1.default.createElement("div", { className: "d-flex align-items-center gap-2 mb-1", style: { flexWrap: 'wrap' } },
|
|
55
|
+
react_1.default.createElement("span", { style: { fontSize: '0.7rem', color: isDark ? '#888' : '#666' } }, (0, moment_1.default)(log.timestamp).format('HH:mm:ss.SSS')),
|
|
56
|
+
react_1.default.createElement("span", { className: "badge text-white", style: {
|
|
57
|
+
backgroundColor: getLevelColor(log.level),
|
|
58
|
+
fontSize: '0.65rem',
|
|
59
|
+
padding: '2px 6px'
|
|
60
|
+
} },
|
|
61
|
+
react_1.default.createElement("i", { className: `${getLevelIcon(log.level)} me-1` }),
|
|
62
|
+
log.level),
|
|
63
|
+
react_1.default.createElement("span", { className: "badge text-white", style: {
|
|
64
|
+
backgroundColor: getProcessColor(log.process),
|
|
65
|
+
fontSize: '0.65rem',
|
|
66
|
+
padding: '2px 6px'
|
|
67
|
+
} }, log.process),
|
|
68
|
+
log.source && (react_1.default.createElement("span", { style: { fontSize: '0.65rem', color: isDark ? '#666' : '#999' } }, log.source))),
|
|
69
|
+
react_1.default.createElement("div", { style: {
|
|
70
|
+
fontSize: '0.8rem',
|
|
71
|
+
wordBreak: 'break-word',
|
|
72
|
+
color: isDark ? '#e0e0e0' : '#333'
|
|
73
|
+
} }, log.message),
|
|
74
|
+
context && (react_1.default.createElement("details", { className: "mt-1" },
|
|
75
|
+
react_1.default.createElement("summary", { className: "text-muted", style: { cursor: 'pointer', fontSize: '0.7rem' } },
|
|
76
|
+
react_1.default.createElement("i", { className: "bi bi-code-square me-1" }),
|
|
77
|
+
"Context"),
|
|
78
|
+
react_1.default.createElement("pre", { className: "mt-1 p-2 rounded", style: {
|
|
79
|
+
fontSize: '0.65rem',
|
|
80
|
+
backgroundColor: isDark ? '#252525' : '#f8f9fa',
|
|
81
|
+
color: isDark ? '#aaa' : '#333',
|
|
82
|
+
overflow: 'auto',
|
|
83
|
+
maxHeight: '150px'
|
|
84
|
+
} }, JSON.stringify(context, null, 2)))),
|
|
85
|
+
log.stackTrace && (react_1.default.createElement("details", { className: "mt-1" },
|
|
86
|
+
react_1.default.createElement("summary", { className: "text-danger", style: { cursor: 'pointer', fontSize: '0.7rem' } },
|
|
87
|
+
react_1.default.createElement("i", { className: "bi bi-bug me-1" }),
|
|
88
|
+
"Stack Trace"),
|
|
89
|
+
react_1.default.createElement("pre", { className: "mt-1 p-2 rounded text-danger", style: {
|
|
90
|
+
fontSize: '0.6rem',
|
|
91
|
+
backgroundColor: isDark ? '#2a1a1a' : '#fff0f0',
|
|
92
|
+
overflow: 'auto',
|
|
93
|
+
maxHeight: '150px'
|
|
94
|
+
} }, log.stackTrace)))));
|
|
95
|
+
};
|
|
96
|
+
exports.MobileLogCard = MobileLogCard;
|
|
@@ -100,7 +100,7 @@ function TabsLayoutInternal() {
|
|
|
100
100
|
sub = userContext.currentlyActiveGroupId.subscribe(async (groupId) => {
|
|
101
101
|
setCurrentlyActiveGroupId(userContext.currentlyActiveGroupId() || userContext.userId);
|
|
102
102
|
// below reloading logic is a kludge to deal with different groups having different packages installed
|
|
103
|
-
await (0, peers_sdk_1.sleep)(
|
|
103
|
+
await (0, peers_sdk_1.sleep)(250);
|
|
104
104
|
window.location.reload();
|
|
105
105
|
});
|
|
106
106
|
});
|
|
@@ -177,28 +177,28 @@ function MobileTabsHeader({ tabs, activeTab, onSwitch, onClose, colorMode }) {
|
|
|
177
177
|
react_1.default.createElement("button", { className: `btn btn-sm ${colorMode === 'light' ? 'btn-outline-dark' : 'btn-outline-light'}`, onClick: () => setShowMenuDropdown(!showMenuDropdown), style: { minWidth: '36px' } },
|
|
178
178
|
react_1.default.createElement("i", { className: "bi-list" }),
|
|
179
179
|
nonLauncherTabs.length > 0 && (react_1.default.createElement("span", { className: "ms-1" }, nonLauncherTabs.length))),
|
|
180
|
-
showMenuDropdown && (react_1.default.createElement("div", { className: `dropdown-menu show position-absolute ${colorMode === 'light' ? '' : 'dropdown-menu-dark'}`, style: { right: 0, top: '100%', zIndex: 1000, minWidth: '
|
|
181
|
-
react_1.default.createElement("div", { className: `dropdown-item d-flex align-items-center ${activeTab === 'launcher' ? 'active' : ''}`, style: { cursor: 'pointer' }, onClick: () => {
|
|
180
|
+
showMenuDropdown && (react_1.default.createElement("div", { className: `dropdown-menu show position-absolute ${colorMode === 'light' ? '' : 'dropdown-menu-dark'}`, style: { right: 0, top: '100%', zIndex: 1000, minWidth: '280px', maxHeight: 'calc(100vh - 60px)', overflowY: 'auto' } },
|
|
181
|
+
react_1.default.createElement("div", { className: `dropdown-item d-flex align-items-center ${activeTab === 'launcher' ? 'active' : ''}`, style: { cursor: 'pointer', padding: '14px 18px', fontSize: '17px' }, onClick: () => {
|
|
182
182
|
onSwitch('launcher');
|
|
183
183
|
setShowMenuDropdown(false);
|
|
184
184
|
} },
|
|
185
|
-
react_1.default.createElement("i", { className: "bi-grid-3x3-gap me-
|
|
185
|
+
react_1.default.createElement("i", { className: "bi-grid-3x3-gap me-3", style: { fontSize: '20px' } }),
|
|
186
186
|
react_1.default.createElement("span", null, "Apps")),
|
|
187
|
-
react_1.default.createElement("div", { className: "dropdown-item d-flex align-items-center", style: { cursor: 'pointer' }, onClick: () => {
|
|
187
|
+
react_1.default.createElement("div", { className: "dropdown-item d-flex align-items-center", style: { cursor: 'pointer', padding: '14px 18px', fontSize: '17px' }, onClick: () => {
|
|
188
188
|
(0, command_palette_1.openCommandPalette)();
|
|
189
189
|
setShowMenuDropdown(false);
|
|
190
190
|
} },
|
|
191
|
-
react_1.default.createElement("i", { className: "bi-search me-
|
|
191
|
+
react_1.default.createElement("i", { className: "bi-search me-3", style: { fontSize: '20px' } }),
|
|
192
192
|
react_1.default.createElement("span", null, "Search")),
|
|
193
|
-
nonLauncherTabs.length > 0 && (react_1.default.createElement("div", { className: "dropdown-divider" })),
|
|
194
|
-
nonLauncherTabs.slice().reverse().map(tab => (react_1.default.createElement("div", { key: tab.tabId, className: `dropdown-item d-flex align-items-center justify-content-between ${activeTab === tab.tabId ? 'active' : ''}`, style: { cursor: 'pointer' }, onClick: () => {
|
|
193
|
+
nonLauncherTabs.length > 0 && (react_1.default.createElement("div", { className: "dropdown-divider", style: { margin: '8px 0' } })),
|
|
194
|
+
nonLauncherTabs.slice().reverse().map(tab => (react_1.default.createElement("div", { key: tab.tabId, className: `dropdown-item d-flex align-items-center justify-content-between ${activeTab === tab.tabId ? 'active' : ''}`, style: { cursor: 'pointer', padding: '14px 18px', fontSize: '17px' }, onClick: () => {
|
|
195
195
|
onSwitch(tab.tabId);
|
|
196
196
|
setShowMenuDropdown(false);
|
|
197
197
|
} },
|
|
198
198
|
react_1.default.createElement("div", { className: "d-flex align-items-center" },
|
|
199
|
-
tab.iconClassName && react_1.default.createElement("i", { className: `${tab.iconClassName} me-
|
|
199
|
+
tab.iconClassName && react_1.default.createElement("i", { className: `${tab.iconClassName} me-3`, style: { fontSize: '20px' } }),
|
|
200
200
|
react_1.default.createElement("span", null, tab.title)),
|
|
201
|
-
react_1.default.createElement("button", { className: "btn btn-sm p-0 ms-
|
|
201
|
+
react_1.default.createElement("button", { className: "btn btn-sm p-0 ms-3", style: { width: '32px', height: '32px', fontSize: '20px' }, onClick: (e) => {
|
|
202
202
|
e.stopPropagation();
|
|
203
203
|
onClose(tab.tabId);
|
|
204
204
|
} },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peers-app/peers-ui",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/peers-app/peers-ui.git"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"bootstrap": "^5.3.3",
|
|
30
|
-
"@peers-app/peers-sdk": "^0.8.
|
|
30
|
+
"@peers-app/peers-sdk": "^0.8.4",
|
|
31
31
|
"react": "^18.0.0",
|
|
32
32
|
"react-dom": "^18.0.0"
|
|
33
33
|
},
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"jest": "^29.7.0",
|
|
58
58
|
"jest-environment-jsdom": "^30.0.5",
|
|
59
59
|
"path-browserify": "^1.0.1",
|
|
60
|
-
"@peers-app/peers-sdk": "0.8.
|
|
60
|
+
"@peers-app/peers-sdk": "0.8.4",
|
|
61
61
|
"react": "^18.0.0",
|
|
62
62
|
"react-dom": "^18.0.0",
|
|
63
63
|
"string-width": "^7.1.0",
|
|
@@ -9,6 +9,8 @@ import { colorMode } from '../settings/color-mode-dropdown';
|
|
|
9
9
|
import { LogDisplay } from './log-display';
|
|
10
10
|
import { LogFilters } from './log-filters';
|
|
11
11
|
import { ResizableTableHeader } from './resizable-table-header';
|
|
12
|
+
import { isDesktop } from '../../globals';
|
|
13
|
+
import { MobileLogCard } from './mobile-log-card';
|
|
12
14
|
|
|
13
15
|
const windowHeight = () => window.innerHeight;
|
|
14
16
|
|
|
@@ -37,7 +39,9 @@ export const ConsoleLogsList = () => {
|
|
|
37
39
|
const [totalLogCount, setTotalLogCount] = useState<number>(0);
|
|
38
40
|
const [_colorMode] = useObservable(colorMode);
|
|
39
41
|
const logsEndRef = React.useRef<HTMLDivElement>(null);
|
|
40
|
-
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
42
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
43
|
+
useObservable(isDesktop);
|
|
44
|
+
const isMobile = !isDesktop();
|
|
41
45
|
|
|
42
46
|
const batchSize = 50;
|
|
43
47
|
|
|
@@ -226,12 +230,14 @@ export const ConsoleLogsList = () => {
|
|
|
226
230
|
overflow: 'hidden',
|
|
227
231
|
}}
|
|
228
232
|
>
|
|
229
|
-
{/* Resizable table header outside scroll area */}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
{/* Desktop: Resizable table header outside scroll area */}
|
|
234
|
+
{!isMobile && (
|
|
235
|
+
<ResizableTableHeader
|
|
236
|
+
columns={columns}
|
|
237
|
+
onColumnsChange={setColumns}
|
|
238
|
+
colorMode={_colorMode}
|
|
239
|
+
/>
|
|
240
|
+
)}
|
|
235
241
|
|
|
236
242
|
{/* Scrollable content area */}
|
|
237
243
|
<div
|
|
@@ -267,11 +273,18 @@ export const ConsoleLogsList = () => {
|
|
|
267
273
|
<i className="bi bi-inbox display-1"></i>
|
|
268
274
|
<p className="mt-2">No logs found</p>
|
|
269
275
|
</div>
|
|
276
|
+
) : isMobile ? (
|
|
277
|
+
/* Mobile: Card-based layout */
|
|
278
|
+
<div>
|
|
279
|
+
{_logs.map((log) => (
|
|
280
|
+
<MobileLogCard key={log.logId} log={log} colorMode={_colorMode} />
|
|
281
|
+
))}
|
|
282
|
+
</div>
|
|
270
283
|
) : (
|
|
284
|
+
/* Desktop: Table layout */
|
|
271
285
|
<table className="table table-sm table-hover mb-0" style={{ fontSize: '0.85rem', tableLayout: 'fixed', width: '100%' }}>
|
|
272
286
|
<tbody>
|
|
273
287
|
{_logs.map((log) => (
|
|
274
|
-
// log.logId
|
|
275
288
|
<LogDisplay key={log.logId} log={log} columns={columns} />
|
|
276
289
|
))}
|
|
277
290
|
</tbody>
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { fromJSONString, IConsoleLog } from "@peers-app/peers-sdk";
|
|
2
|
+
import moment from 'moment';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
interface MobileLogCardProps {
|
|
6
|
+
log: IConsoleLog;
|
|
7
|
+
colorMode: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const getLevelColor = (level: string) => {
|
|
11
|
+
switch (level) {
|
|
12
|
+
case 'error': return '#dc3545';
|
|
13
|
+
case 'warn': return '#ffc107';
|
|
14
|
+
case 'info': return '#0dcaf0';
|
|
15
|
+
case 'log': return '#6c757d';
|
|
16
|
+
case 'debug': return '#6c757d';
|
|
17
|
+
default: return '#6c757d';
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getProcessColor = (process: string) => {
|
|
22
|
+
switch (process) {
|
|
23
|
+
case 'ui':
|
|
24
|
+
case 'renderer': return '#0dcaf0';
|
|
25
|
+
case 'electron':
|
|
26
|
+
case 'main': return '#d946ef';
|
|
27
|
+
case 'react-native': return '#61dafb';
|
|
28
|
+
default: return '#6c757d';
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getLevelIcon = (level: string) => {
|
|
33
|
+
switch (level) {
|
|
34
|
+
case 'error': return 'bi-x-circle-fill';
|
|
35
|
+
case 'warn': return 'bi-exclamation-triangle-fill';
|
|
36
|
+
case 'info': return 'bi-info-circle-fill';
|
|
37
|
+
case 'debug': return 'bi-bug-fill';
|
|
38
|
+
default: return 'bi-chat-square-text';
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const MobileLogCard = ({ log, colorMode }: MobileLogCardProps) => {
|
|
43
|
+
const isDark = colorMode === 'dark';
|
|
44
|
+
|
|
45
|
+
// Parse context if it's a string
|
|
46
|
+
let context = log.context;
|
|
47
|
+
if (typeof context === 'string') {
|
|
48
|
+
try {
|
|
49
|
+
context = fromJSONString(context);
|
|
50
|
+
} catch (err) {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
style={{
|
|
56
|
+
padding: '10px 12px',
|
|
57
|
+
borderBottom: `1px solid ${isDark ? '#333' : '#e9ecef'}`,
|
|
58
|
+
backgroundColor: isDark ? '#1a1a1a' : '#fff',
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
{/* Header row: timestamp, level, process */}
|
|
62
|
+
<div className="d-flex align-items-center gap-2 mb-1" style={{ flexWrap: 'wrap' }}>
|
|
63
|
+
<span style={{ fontSize: '0.7rem', color: isDark ? '#888' : '#666' }}>
|
|
64
|
+
{moment(log.timestamp).format('HH:mm:ss.SSS')}
|
|
65
|
+
</span>
|
|
66
|
+
<span
|
|
67
|
+
className="badge text-white"
|
|
68
|
+
style={{
|
|
69
|
+
backgroundColor: getLevelColor(log.level),
|
|
70
|
+
fontSize: '0.65rem',
|
|
71
|
+
padding: '2px 6px'
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
<i className={`${getLevelIcon(log.level)} me-1`}></i>
|
|
75
|
+
{log.level}
|
|
76
|
+
</span>
|
|
77
|
+
<span
|
|
78
|
+
className="badge text-white"
|
|
79
|
+
style={{
|
|
80
|
+
backgroundColor: getProcessColor(log.process),
|
|
81
|
+
fontSize: '0.65rem',
|
|
82
|
+
padding: '2px 6px'
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
{log.process}
|
|
86
|
+
</span>
|
|
87
|
+
{log.source && (
|
|
88
|
+
<span style={{ fontSize: '0.65rem', color: isDark ? '#666' : '#999' }}>
|
|
89
|
+
{log.source}
|
|
90
|
+
</span>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{/* Message */}
|
|
95
|
+
<div style={{
|
|
96
|
+
fontSize: '0.8rem',
|
|
97
|
+
wordBreak: 'break-word',
|
|
98
|
+
color: isDark ? '#e0e0e0' : '#333'
|
|
99
|
+
}}>
|
|
100
|
+
{log.message}
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{/* Context (collapsible) */}
|
|
104
|
+
{context && (
|
|
105
|
+
<details className="mt-1">
|
|
106
|
+
<summary
|
|
107
|
+
className="text-muted"
|
|
108
|
+
style={{ cursor: 'pointer', fontSize: '0.7rem' }}
|
|
109
|
+
>
|
|
110
|
+
<i className="bi bi-code-square me-1"></i>
|
|
111
|
+
Context
|
|
112
|
+
</summary>
|
|
113
|
+
<pre
|
|
114
|
+
className="mt-1 p-2 rounded"
|
|
115
|
+
style={{
|
|
116
|
+
fontSize: '0.65rem',
|
|
117
|
+
backgroundColor: isDark ? '#252525' : '#f8f9fa',
|
|
118
|
+
color: isDark ? '#aaa' : '#333',
|
|
119
|
+
overflow: 'auto',
|
|
120
|
+
maxHeight: '150px'
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
{JSON.stringify(context, null, 2)}
|
|
124
|
+
</pre>
|
|
125
|
+
</details>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
{/* Stack trace (collapsible) */}
|
|
129
|
+
{log.stackTrace && (
|
|
130
|
+
<details className="mt-1">
|
|
131
|
+
<summary
|
|
132
|
+
className="text-danger"
|
|
133
|
+
style={{ cursor: 'pointer', fontSize: '0.7rem' }}
|
|
134
|
+
>
|
|
135
|
+
<i className="bi bi-bug me-1"></i>
|
|
136
|
+
Stack Trace
|
|
137
|
+
</summary>
|
|
138
|
+
<pre
|
|
139
|
+
className="mt-1 p-2 rounded text-danger"
|
|
140
|
+
style={{
|
|
141
|
+
fontSize: '0.6rem',
|
|
142
|
+
backgroundColor: isDark ? '#2a1a1a' : '#fff0f0',
|
|
143
|
+
overflow: 'auto',
|
|
144
|
+
maxHeight: '150px'
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
{log.stackTrace}
|
|
148
|
+
</pre>
|
|
149
|
+
</details>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
@@ -74,7 +74,7 @@ function TabsLayoutInternal() {
|
|
|
74
74
|
sub = userContext.currentlyActiveGroupId.subscribe(async groupId => {
|
|
75
75
|
setCurrentlyActiveGroupId(userContext.currentlyActiveGroupId() || userContext.userId);
|
|
76
76
|
// below reloading logic is a kludge to deal with different groups having different packages installed
|
|
77
|
-
await sleep(
|
|
77
|
+
await sleep(250);
|
|
78
78
|
window.location.reload();
|
|
79
79
|
});
|
|
80
80
|
});
|
|
@@ -252,37 +252,37 @@ function MobileTabsHeader({ tabs, activeTab, onSwitch, onClose, colorMode }: Mob
|
|
|
252
252
|
{showMenuDropdown && (
|
|
253
253
|
<div
|
|
254
254
|
className={`dropdown-menu show position-absolute ${colorMode === 'light' ? '' : 'dropdown-menu-dark'}`}
|
|
255
|
-
style={{ right: 0, top: '100%', zIndex: 1000, minWidth: '
|
|
255
|
+
style={{ right: 0, top: '100%', zIndex: 1000, minWidth: '280px', maxHeight: 'calc(100vh - 60px)', overflowY: 'auto' }}
|
|
256
256
|
>
|
|
257
257
|
{/* Apps Option */}
|
|
258
258
|
<div
|
|
259
259
|
className={`dropdown-item d-flex align-items-center ${activeTab === 'launcher' ? 'active' : ''}`}
|
|
260
|
-
style={{ cursor: 'pointer' }}
|
|
260
|
+
style={{ cursor: 'pointer', padding: '14px 18px', fontSize: '17px' }}
|
|
261
261
|
onClick={() => {
|
|
262
262
|
onSwitch('launcher');
|
|
263
263
|
setShowMenuDropdown(false);
|
|
264
264
|
}}
|
|
265
265
|
>
|
|
266
|
-
<i className="bi-grid-3x3-gap me-
|
|
266
|
+
<i className="bi-grid-3x3-gap me-3" style={{ fontSize: '20px' }} />
|
|
267
267
|
<span>Apps</span>
|
|
268
268
|
</div>
|
|
269
269
|
|
|
270
270
|
{/* Search Option */}
|
|
271
271
|
<div
|
|
272
272
|
className="dropdown-item d-flex align-items-center"
|
|
273
|
-
style={{ cursor: 'pointer' }}
|
|
273
|
+
style={{ cursor: 'pointer', padding: '14px 18px', fontSize: '17px' }}
|
|
274
274
|
onClick={() => {
|
|
275
275
|
openCommandPalette();
|
|
276
276
|
setShowMenuDropdown(false);
|
|
277
277
|
}}
|
|
278
278
|
>
|
|
279
|
-
<i className="bi-search me-
|
|
279
|
+
<i className="bi-search me-3" style={{ fontSize: '20px' }} />
|
|
280
280
|
<span>Search</span>
|
|
281
281
|
</div>
|
|
282
282
|
|
|
283
283
|
{/* Divider if there are open tabs */}
|
|
284
284
|
{nonLauncherTabs.length > 0 && (
|
|
285
|
-
<div className="dropdown-divider" />
|
|
285
|
+
<div className="dropdown-divider" style={{ margin: '8px 0' }} />
|
|
286
286
|
)}
|
|
287
287
|
|
|
288
288
|
{/* Open Tabs */}
|
|
@@ -290,19 +290,19 @@ function MobileTabsHeader({ tabs, activeTab, onSwitch, onClose, colorMode }: Mob
|
|
|
290
290
|
<div
|
|
291
291
|
key={tab.tabId}
|
|
292
292
|
className={`dropdown-item d-flex align-items-center justify-content-between ${activeTab === tab.tabId ? 'active' : ''}`}
|
|
293
|
-
style={{ cursor: 'pointer' }}
|
|
293
|
+
style={{ cursor: 'pointer', padding: '14px 18px', fontSize: '17px' }}
|
|
294
294
|
onClick={() => {
|
|
295
295
|
onSwitch(tab.tabId);
|
|
296
296
|
setShowMenuDropdown(false);
|
|
297
297
|
}}
|
|
298
298
|
>
|
|
299
299
|
<div className="d-flex align-items-center">
|
|
300
|
-
{tab.iconClassName && <i className={`${tab.iconClassName} me-
|
|
300
|
+
{tab.iconClassName && <i className={`${tab.iconClassName} me-3`} style={{ fontSize: '20px' }} />}
|
|
301
301
|
<span>{tab.title}</span>
|
|
302
302
|
</div>
|
|
303
303
|
<button
|
|
304
|
-
className="btn btn-sm p-0 ms-
|
|
305
|
-
style={{ width: '
|
|
304
|
+
className="btn btn-sm p-0 ms-3"
|
|
305
|
+
style={{ width: '32px', height: '32px', fontSize: '20px' }}
|
|
306
306
|
onClick={(e) => {
|
|
307
307
|
e.stopPropagation();
|
|
308
308
|
onClose(tab.tabId);
|