dev-inspector 1.0.5 → 1.1.0
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/README.md +22 -1
- package/lib/init.d.ts +5 -2
- package/lib/init.js +5 -2
- package/lib/integrations/index.d.ts +2 -0
- package/lib/integrations/index.js +7 -0
- package/lib/integrations/jira/adf.d.ts +11 -0
- package/lib/integrations/jira/adf.js +17 -0
- package/lib/integrations/jira/client.d.ts +4 -0
- package/lib/integrations/jira/client.js +113 -0
- package/lib/integrations/jira/errors.d.ts +10 -0
- package/lib/integrations/jira/errors.js +13 -0
- package/lib/integrations/jira/index.d.ts +2 -0
- package/lib/integrations/jira/index.js +8 -0
- package/lib/integrations/types.d.ts +54 -0
- package/lib/integrations/types.js +2 -0
- package/lib/storage/localStorage.d.ts +30 -0
- package/lib/storage/localStorage.js +166 -0
- package/lib/ui/panel/behavior/setupPanelBehavior.d.ts +4 -1
- package/lib/ui/panel/behavior/setupPanelBehavior.js +48 -1
- package/lib/ui/panel/bindings/bindStorageToPanelView.d.ts +2 -0
- package/lib/ui/panel/bindings/bindStorageToPanelView.js +50 -6
- package/lib/ui/panel/bindings/matchesQuery.d.ts +2 -0
- package/lib/ui/panel/bindings/matchesQuery.js +54 -0
- package/lib/ui/panel/bindings/types.d.ts +2 -0
- package/lib/ui/panel/dom/buildPanelDOM.js +65 -6
- package/lib/ui/panel/dom/constants.d.ts +5 -1
- package/lib/ui/panel/dom/constants.js +35 -20
- package/lib/ui/panel/dom/types.d.ts +6 -0
- package/lib/ui/panel/index.d.ts +4 -0
- package/lib/ui/panel/index.js +8 -3
- package/lib/ui/panel/integrations/icons.d.ts +1 -0
- package/lib/ui/panel/integrations/icons.js +7 -0
- package/lib/ui/panel/integrations/index.d.ts +1 -0
- package/lib/ui/panel/integrations/index.js +5 -0
- package/lib/ui/panel/integrations/jiraDialog.d.ts +6 -0
- package/lib/ui/panel/integrations/jiraDialog.js +234 -0
- package/lib/ui/panel/integrations/screenshot.d.ts +4 -0
- package/lib/ui/panel/integrations/screenshot.js +51 -0
- package/lib/ui/panel/localStorage/createLocalStorageView.d.ts +4 -0
- package/lib/ui/panel/localStorage/createLocalStorageView.js +108 -0
- package/lib/ui/panel/localStorage/index.d.ts +3 -0
- package/lib/ui/panel/localStorage/index.js +7 -0
- package/lib/ui/panel/localStorage/setupLocalStorageSync.d.ts +9 -0
- package/lib/ui/panel/localStorage/setupLocalStorageSync.js +30 -0
- package/lib/ui/panel/localStorage/types.d.ts +6 -0
- package/lib/ui/panel/localStorage/types.js +2 -0
- package/lib/ui/panel/state/createPanelState.d.ts +2 -1
- package/lib/ui/panel/state/createPanelState.js +5 -1
- package/lib/ui/panel/state/types.d.ts +5 -0
- package/lib/ui/panel/theme/applyTheme.d.ts +2 -0
- package/lib/ui/panel/theme/applyTheme.js +6 -0
- package/lib/ui/panel/theme/index.d.ts +3 -0
- package/lib/ui/panel/theme/index.js +9 -0
- package/lib/ui/panel/theme/storage.d.ts +4 -0
- package/lib/ui/panel/theme/storage.js +45 -0
- package/lib/ui/panel/theme/types.d.ts +6 -0
- package/lib/ui/panel/theme/types.js +2 -0
- package/lib/ui/panelStyles.d.ts +1 -1
- package/lib/ui/panelStyles.js +514 -125
- package/lib/ui/shared/copy.d.ts +2 -0
- package/lib/ui/shared/copy.js +101 -0
- package/package.json +1 -1
- package/lib/ui/logList/networkDetails.d.ts +0 -2
- package/lib/ui/logList/networkDetails.js +0 -227
package/README.md
CHANGED
|
@@ -19,6 +19,10 @@ In-page devtools-style logger panel for web apps. Capture **console** and **netw
|
|
|
19
19
|
- Network interception: `fetch` + `XMLHttpRequest`
|
|
20
20
|
- In-memory storage with events (`EventTarget`)
|
|
21
21
|
- UI panel with tabs (Console / Network), counters, severity/status colors, and resize
|
|
22
|
+
- Search across console messages and network requests (URL, method, status, body)
|
|
23
|
+
- Auto-scroll lock with a "Latest" jump button that surfaces unread entries
|
|
24
|
+
- Light / dark theme with `localStorage` persistence (defaults to light)
|
|
25
|
+
- Configurable panel title (used in the header and the floating toggle)
|
|
22
26
|
- Vite demo playground
|
|
23
27
|
|
|
24
28
|
## Installation
|
|
@@ -35,10 +39,27 @@ import { initDevInspector } from "dev-inspector";
|
|
|
35
39
|
initDevInspector({
|
|
36
40
|
maxSize: 500,
|
|
37
41
|
networkOptions: { includeBodies: false },
|
|
38
|
-
panelOptions: {
|
|
42
|
+
panelOptions: {
|
|
43
|
+
initiallyOpen: true,
|
|
44
|
+
title: "My App Inspector",
|
|
45
|
+
theme: "light",
|
|
46
|
+
persistTheme: true,
|
|
47
|
+
},
|
|
39
48
|
});
|
|
40
49
|
```
|
|
41
50
|
|
|
51
|
+
### Panel options
|
|
52
|
+
|
|
53
|
+
| Option | Type | Default | Description |
|
|
54
|
+
| ----------------- | ------------------- | ----------------------- | ------------------------------------------------------------------------------------ |
|
|
55
|
+
| `title` | `string` | `"Dev Inspector"` | Label shown in the panel header **and** the floating toggle pill in the bottom-right |
|
|
56
|
+
| `initiallyOpen` | `boolean` | `true` | Open the panel on mount |
|
|
57
|
+
| `theme` | `"light" \| "dark"` | `"light"` | Initial theme. Overridden by a stored value when `persistTheme` is `true` |
|
|
58
|
+
| `persistTheme` | `boolean` | `true` | Persist the user's theme choice in `localStorage` |
|
|
59
|
+
| `themeStorageKey` | `string` | `"dev-inspector:theme"` | Key used by `localStorage` to remember the theme |
|
|
60
|
+
|
|
61
|
+
The theme can also be toggled at runtime via the sun/moon button in the panel header. When `persistTheme` is `true`, the choice is stored under `themeStorageKey` and restored on the next load.
|
|
62
|
+
|
|
42
63
|
## Important: Browser-only (SSR)
|
|
43
64
|
|
|
44
65
|
Dev Inspector’s UI (`createPanel()` and the default `initDevInspector()` flow) requires a **browser environment** (it needs `document`).
|
package/lib/init.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
type
|
|
1
|
+
export type Theme = "light" | "dark";
|
|
2
|
+
export type InitOptions = {
|
|
2
3
|
maxSize?: number;
|
|
3
4
|
panelOptions?: {
|
|
4
5
|
title?: string;
|
|
5
6
|
initiallyOpen?: boolean;
|
|
7
|
+
theme?: Theme;
|
|
8
|
+
persistTheme?: boolean;
|
|
9
|
+
themeStorageKey?: string;
|
|
6
10
|
};
|
|
7
11
|
networkOptions?: {
|
|
8
12
|
includeBodies?: boolean;
|
|
@@ -10,4 +14,3 @@ type InitOptions = {
|
|
|
10
14
|
};
|
|
11
15
|
};
|
|
12
16
|
export declare function initDevInspector(options?: InitOptions): void;
|
|
13
|
-
export {};
|
package/lib/init.js
CHANGED
|
@@ -12,7 +12,7 @@ function getGlobalBag() {
|
|
|
12
12
|
return globalThis;
|
|
13
13
|
}
|
|
14
14
|
function initDevInspector(options = {}) {
|
|
15
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
15
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
16
16
|
const key = getGlobalInstanceKey();
|
|
17
17
|
const bag = getGlobalBag();
|
|
18
18
|
const prev = bag[key];
|
|
@@ -20,7 +20,7 @@ function initDevInspector(options = {}) {
|
|
|
20
20
|
try {
|
|
21
21
|
prev.destroy();
|
|
22
22
|
}
|
|
23
|
-
catch (
|
|
23
|
+
catch (_o) {
|
|
24
24
|
void 0;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -35,6 +35,9 @@ function initDevInspector(options = {}) {
|
|
|
35
35
|
storage,
|
|
36
36
|
initiallyOpen: (_g = (_f = options.panelOptions) === null || _f === void 0 ? void 0 : _f.initiallyOpen) !== null && _g !== void 0 ? _g : true,
|
|
37
37
|
title: (_j = (_h = options.panelOptions) === null || _h === void 0 ? void 0 : _h.title) !== null && _j !== void 0 ? _j : "Dev Inspector",
|
|
38
|
+
theme: (_k = options.panelOptions) === null || _k === void 0 ? void 0 : _k.theme,
|
|
39
|
+
persistTheme: (_l = options.panelOptions) === null || _l === void 0 ? void 0 : _l.persistTheme,
|
|
40
|
+
themeStorageKey: (_m = options.panelOptions) === null || _m === void 0 ? void 0 : _m.themeStorageKey,
|
|
38
41
|
});
|
|
39
42
|
const destroy = () => {
|
|
40
43
|
try {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JiraIntegrationError = exports.fetchJiraIssueTypes = exports.createJiraIssue = void 0;
|
|
4
|
+
var jira_1 = require("./jira");
|
|
5
|
+
Object.defineProperty(exports, "createJiraIssue", { enumerable: true, get: function () { return jira_1.createJiraIssue; } });
|
|
6
|
+
Object.defineProperty(exports, "fetchJiraIssueTypes", { enumerable: true, get: function () { return jira_1.fetchJiraIssueTypes; } });
|
|
7
|
+
Object.defineProperty(exports, "JiraIntegrationError", { enumerable: true, get: function () { return jira_1.JiraIntegrationError; } });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.textToAdf = textToAdf;
|
|
4
|
+
function paragraph(text) {
|
|
5
|
+
return {
|
|
6
|
+
type: "paragraph",
|
|
7
|
+
content: text.length > 0 ? [{ type: "text", text }] : [],
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function textToAdf(text) {
|
|
11
|
+
const lines = text.split(/\r?\n/);
|
|
12
|
+
return {
|
|
13
|
+
type: "doc",
|
|
14
|
+
version: 1,
|
|
15
|
+
content: lines.map((l) => paragraph(l)),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CreateIssueInput, CreateIssueResult, JiraIntegrationConfig } from "../types";
|
|
2
|
+
import type { JiraIssueTypeOption } from "../types";
|
|
3
|
+
export declare function fetchJiraIssueTypes(config: JiraIntegrationConfig, projectKey?: string): Promise<JiraIssueTypeOption[]>;
|
|
4
|
+
export declare function createJiraIssue(input: CreateIssueInput, config: JiraIntegrationConfig): Promise<CreateIssueResult>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchJiraIssueTypes = fetchJiraIssueTypes;
|
|
4
|
+
exports.createJiraIssue = createJiraIssue;
|
|
5
|
+
const adf_1 = require("./adf");
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
async function readJsonSafe(res) {
|
|
8
|
+
try {
|
|
9
|
+
return (await res.json());
|
|
10
|
+
}
|
|
11
|
+
catch (_a) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function mapStatus(status) {
|
|
16
|
+
if (status === 400)
|
|
17
|
+
return { code: "bad_request", message: "Bad request. Check required fields and Jira configuration." };
|
|
18
|
+
if (status === 401)
|
|
19
|
+
return { code: "unauthorized", message: "Unauthorized. Check Jira credentials." };
|
|
20
|
+
if (status === 403)
|
|
21
|
+
return { code: "forbidden", message: "Forbidden. Check Jira permissions." };
|
|
22
|
+
if (status === 429)
|
|
23
|
+
return { code: "rate_limited", message: "Rate limited by Jira. Try again later." };
|
|
24
|
+
if (status >= 500)
|
|
25
|
+
return { code: "server_error", message: "Jira server error. Try again later." };
|
|
26
|
+
return { code: "unknown", message: "Unexpected Jira error." };
|
|
27
|
+
}
|
|
28
|
+
function isRecord(value) {
|
|
29
|
+
return typeof value === "object" && value !== null;
|
|
30
|
+
}
|
|
31
|
+
function toQuery(params) {
|
|
32
|
+
const sp = new URLSearchParams();
|
|
33
|
+
for (const [k, v] of Object.entries(params)) {
|
|
34
|
+
if (typeof v === "string" && v.length > 0)
|
|
35
|
+
sp.set(k, v);
|
|
36
|
+
}
|
|
37
|
+
const s = sp.toString();
|
|
38
|
+
return s.length > 0 ? `?${s}` : "";
|
|
39
|
+
}
|
|
40
|
+
async function fetchJiraIssueTypes(config, projectKey) {
|
|
41
|
+
var _a;
|
|
42
|
+
const proxyUrl = config.proxyUrl;
|
|
43
|
+
if (!proxyUrl)
|
|
44
|
+
throw new errors_1.JiraIntegrationError("Missing Jira proxyUrl.", "not_configured");
|
|
45
|
+
const url = `${proxyUrl}${toQuery({ op: "issueTypes", projectKey: (_a = projectKey !== null && projectKey !== void 0 ? projectKey : config.projectKey) !== null && _a !== void 0 ? _a : "" })}`;
|
|
46
|
+
let res;
|
|
47
|
+
try {
|
|
48
|
+
res = await fetch(url, { method: "GET", headers: { accept: "application/json" } });
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
throw new errors_1.JiraIntegrationError("Network error while loading Jira issue types.", "network_error", { details: String(e) });
|
|
52
|
+
}
|
|
53
|
+
const data = await readJsonSafe(res);
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
const mapped = mapStatus(res.status);
|
|
56
|
+
throw new errors_1.JiraIntegrationError(mapped.message, mapped.code, { status: res.status, details: data });
|
|
57
|
+
}
|
|
58
|
+
if (!Array.isArray(data))
|
|
59
|
+
return [];
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const it of data) {
|
|
62
|
+
if (!isRecord(it))
|
|
63
|
+
continue;
|
|
64
|
+
const id = typeof it.id === "string" ? it.id : undefined;
|
|
65
|
+
const name = typeof it.name === "string" ? it.name : undefined;
|
|
66
|
+
if (id && name)
|
|
67
|
+
out.push({ id, name });
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
async function createJiraIssue(input, config) {
|
|
72
|
+
var _a, _b, _c, _d, _e, _f;
|
|
73
|
+
const title = input.title.trim();
|
|
74
|
+
if (title.length === 0)
|
|
75
|
+
throw new errors_1.JiraIntegrationError("Title is required.", "bad_request");
|
|
76
|
+
const projectKey = (_b = (_a = input.jira) === null || _a === void 0 ? void 0 : _a.projectKey) !== null && _b !== void 0 ? _b : config.projectKey;
|
|
77
|
+
if (!projectKey)
|
|
78
|
+
throw new errors_1.JiraIntegrationError("Missing Jira project key.", "not_configured");
|
|
79
|
+
const issueType = (_e = (_d = (_c = input.jira) === null || _c === void 0 ? void 0 : _c.issueType) !== null && _d !== void 0 ? _d : config.issueType) !== null && _e !== void 0 ? _e : {};
|
|
80
|
+
if (!issueType.name && !issueType.id)
|
|
81
|
+
throw new errors_1.JiraIntegrationError("Missing Jira issue type (name or id).", "not_configured");
|
|
82
|
+
const descriptionText = (_f = input.description) !== null && _f !== void 0 ? _f : "";
|
|
83
|
+
const adf = (0, adf_1.textToAdf)(descriptionText);
|
|
84
|
+
const proxyUrl = config.proxyUrl;
|
|
85
|
+
if (!proxyUrl)
|
|
86
|
+
throw new errors_1.JiraIntegrationError("Missing Jira proxyUrl.", "not_configured");
|
|
87
|
+
const res = await fetch(proxyUrl, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: { "content-type": "application/json" },
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
provider: "jira",
|
|
92
|
+
input: {
|
|
93
|
+
title,
|
|
94
|
+
descriptionAdf: adf,
|
|
95
|
+
context: input.context,
|
|
96
|
+
screenshot: input.screenshot,
|
|
97
|
+
projectKey,
|
|
98
|
+
issueType,
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
});
|
|
102
|
+
const data = await readJsonSafe(res);
|
|
103
|
+
if (!res.ok) {
|
|
104
|
+
const mapped = mapStatus(res.status);
|
|
105
|
+
throw new errors_1.JiraIntegrationError(mapped.message, mapped.code, { status: res.status, details: data });
|
|
106
|
+
}
|
|
107
|
+
const rec = isRecord(data) ? data : {};
|
|
108
|
+
return {
|
|
109
|
+
id: typeof rec.id === "string" ? rec.id : undefined,
|
|
110
|
+
key: typeof rec.key === "string" ? rec.key : undefined,
|
|
111
|
+
url: typeof rec.url === "string" ? rec.url : undefined,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type JiraErrorCode = "not_configured" | "bad_request" | "unauthorized" | "forbidden" | "rate_limited" | "server_error" | "network_error" | "unknown";
|
|
2
|
+
export declare class JiraIntegrationError extends Error {
|
|
3
|
+
code: JiraErrorCode;
|
|
4
|
+
status?: number;
|
|
5
|
+
details?: unknown;
|
|
6
|
+
constructor(message: string, code: JiraErrorCode, options?: {
|
|
7
|
+
status?: number;
|
|
8
|
+
details?: unknown;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JiraIntegrationError = void 0;
|
|
4
|
+
class JiraIntegrationError extends Error {
|
|
5
|
+
constructor(message, code, options) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "JiraIntegrationError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.status = options === null || options === void 0 ? void 0 : options.status;
|
|
10
|
+
this.details = options === null || options === void 0 ? void 0 : options.details;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.JiraIntegrationError = JiraIntegrationError;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JiraIntegrationError = exports.fetchJiraIssueTypes = exports.createJiraIssue = void 0;
|
|
4
|
+
var client_1 = require("./client");
|
|
5
|
+
Object.defineProperty(exports, "createJiraIssue", { enumerable: true, get: function () { return client_1.createJiraIssue; } });
|
|
6
|
+
Object.defineProperty(exports, "fetchJiraIssueTypes", { enumerable: true, get: function () { return client_1.fetchJiraIssueTypes; } });
|
|
7
|
+
var errors_1 = require("./errors");
|
|
8
|
+
Object.defineProperty(exports, "JiraIntegrationError", { enumerable: true, get: function () { return errors_1.JiraIntegrationError; } });
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type IntegrationId = "jira";
|
|
2
|
+
export type JiraIssueType = {
|
|
3
|
+
name?: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
};
|
|
6
|
+
export type JiraIssueTypeOption = {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
export type JiraIntegrationConfig = {
|
|
11
|
+
proxyUrl?: string;
|
|
12
|
+
projectKey?: string;
|
|
13
|
+
issueType?: JiraIssueType;
|
|
14
|
+
screenshot?: {
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
mode?: "displayMedia" | "none";
|
|
17
|
+
};
|
|
18
|
+
user?: {
|
|
19
|
+
id?: string;
|
|
20
|
+
email?: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export type IntegrationsConfig = {
|
|
25
|
+
jira?: JiraIntegrationConfig;
|
|
26
|
+
};
|
|
27
|
+
export type IntegrationContext = {
|
|
28
|
+
url?: string;
|
|
29
|
+
userAgent?: string;
|
|
30
|
+
timestamp?: string;
|
|
31
|
+
user?: {
|
|
32
|
+
id?: string;
|
|
33
|
+
email?: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export type CreateIssueInput = {
|
|
38
|
+
title: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
context?: IntegrationContext;
|
|
41
|
+
screenshot?: {
|
|
42
|
+
dataUrl: string;
|
|
43
|
+
filename: string;
|
|
44
|
+
};
|
|
45
|
+
jira?: {
|
|
46
|
+
projectKey?: string;
|
|
47
|
+
issueType?: JiraIssueType;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
export type CreateIssueResult = {
|
|
51
|
+
id?: string;
|
|
52
|
+
key?: string;
|
|
53
|
+
url?: string;
|
|
54
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type LocalStorageItem = {
|
|
2
|
+
key: string;
|
|
3
|
+
value: string;
|
|
4
|
+
preview: string;
|
|
5
|
+
valueLength: number;
|
|
6
|
+
approxBytes: number;
|
|
7
|
+
};
|
|
8
|
+
export type LocalStorageSnapshot = {
|
|
9
|
+
items: LocalStorageItem[];
|
|
10
|
+
totalApproxBytes: number;
|
|
11
|
+
};
|
|
12
|
+
export type LocalStorageAccess = {
|
|
13
|
+
ok: true;
|
|
14
|
+
storage: Storage;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function getLocalStorageAccess(): LocalStorageAccess;
|
|
20
|
+
export declare function readLocalStorageSnapshot(storage: Storage, opts: {
|
|
21
|
+
maxPreviewLength: number;
|
|
22
|
+
}): LocalStorageSnapshot;
|
|
23
|
+
export declare function createLocalStorageWatcher(args: {
|
|
24
|
+
storage: Storage;
|
|
25
|
+
pollIntervalMs: number;
|
|
26
|
+
onChange: () => void;
|
|
27
|
+
}): {
|
|
28
|
+
destroy: () => void;
|
|
29
|
+
trigger: () => void;
|
|
30
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLocalStorageAccess = getLocalStorageAccess;
|
|
4
|
+
exports.readLocalStorageSnapshot = readLocalStorageSnapshot;
|
|
5
|
+
exports.createLocalStorageWatcher = createLocalStorageWatcher;
|
|
6
|
+
function safeTruncate(value, maxLen) {
|
|
7
|
+
if (maxLen <= 0)
|
|
8
|
+
return "";
|
|
9
|
+
if (value.length <= maxLen)
|
|
10
|
+
return value;
|
|
11
|
+
return `${value.slice(0, maxLen)}…`;
|
|
12
|
+
}
|
|
13
|
+
function getLocalStorageAccess() {
|
|
14
|
+
try {
|
|
15
|
+
const w = globalThis;
|
|
16
|
+
const storage = w.localStorage;
|
|
17
|
+
if (!storage)
|
|
18
|
+
return { ok: false, message: "localStorage is not available in this environment." };
|
|
19
|
+
// Some environments expose the object but throw on access (privacy modes / blocked cookies).
|
|
20
|
+
void storage.length;
|
|
21
|
+
return { ok: true, storage };
|
|
22
|
+
}
|
|
23
|
+
catch (_a) {
|
|
24
|
+
return { ok: false, message: "localStorage access is blocked in this environment (privacy settings / permissions)." };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function readLocalStorageSnapshot(storage, opts) {
|
|
28
|
+
var _a;
|
|
29
|
+
const items = [];
|
|
30
|
+
let totalApproxBytes = 0;
|
|
31
|
+
const len = storage.length;
|
|
32
|
+
for (let i = 0; i < len; i += 1) {
|
|
33
|
+
const key = storage.key(i);
|
|
34
|
+
if (!key)
|
|
35
|
+
continue;
|
|
36
|
+
const value = (_a = storage.getItem(key)) !== null && _a !== void 0 ? _a : "";
|
|
37
|
+
const approxBytes = (key.length + value.length) * 2; // UTF-16-ish approximation
|
|
38
|
+
totalApproxBytes += approxBytes;
|
|
39
|
+
items.push({
|
|
40
|
+
key,
|
|
41
|
+
value,
|
|
42
|
+
preview: safeTruncate(value, opts.maxPreviewLength),
|
|
43
|
+
valueLength: value.length,
|
|
44
|
+
approxBytes,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
items.sort((a, b) => a.key.localeCompare(b.key));
|
|
48
|
+
return { items, totalApproxBytes };
|
|
49
|
+
}
|
|
50
|
+
function buildSignature(storage) {
|
|
51
|
+
var _a;
|
|
52
|
+
try {
|
|
53
|
+
const parts = [String(storage.length)];
|
|
54
|
+
const len = storage.length;
|
|
55
|
+
for (let i = 0; i < len; i += 1) {
|
|
56
|
+
const k = storage.key(i);
|
|
57
|
+
if (!k)
|
|
58
|
+
continue;
|
|
59
|
+
const v = (_a = storage.getItem(k)) !== null && _a !== void 0 ? _a : "";
|
|
60
|
+
parts.push(`${k}:${v.length}`);
|
|
61
|
+
}
|
|
62
|
+
return parts.join("|");
|
|
63
|
+
}
|
|
64
|
+
catch (_b) {
|
|
65
|
+
return "err";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function createLocalStorageWatcher(args) {
|
|
69
|
+
var _a, _b, _c, _d;
|
|
70
|
+
const { storage, onChange } = args;
|
|
71
|
+
const pollIntervalMs = Math.max(250, args.pollIntervalMs);
|
|
72
|
+
let destroyed = false;
|
|
73
|
+
let lastSig = buildSignature(storage);
|
|
74
|
+
const trigger = () => {
|
|
75
|
+
if (destroyed)
|
|
76
|
+
return;
|
|
77
|
+
onChange();
|
|
78
|
+
};
|
|
79
|
+
const win = globalThis;
|
|
80
|
+
const onStorageEvent = () => {
|
|
81
|
+
const sig = buildSignature(storage);
|
|
82
|
+
if (sig !== lastSig) {
|
|
83
|
+
lastSig = sig;
|
|
84
|
+
trigger();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
try {
|
|
88
|
+
(_a = win.addEventListener) === null || _a === void 0 ? void 0 : _a.call(win, "storage", onStorageEvent);
|
|
89
|
+
}
|
|
90
|
+
catch (_e) {
|
|
91
|
+
void 0;
|
|
92
|
+
}
|
|
93
|
+
// Same-tab changes don't fire "storage" events. We try to patch methods; if that fails, polling still keeps UI fresh.
|
|
94
|
+
const orig = {
|
|
95
|
+
setItem: (_b = storage.setItem) === null || _b === void 0 ? void 0 : _b.bind(storage),
|
|
96
|
+
removeItem: (_c = storage.removeItem) === null || _c === void 0 ? void 0 : _c.bind(storage),
|
|
97
|
+
clear: (_d = storage.clear) === null || _d === void 0 ? void 0 : _d.bind(storage),
|
|
98
|
+
};
|
|
99
|
+
const patch = () => {
|
|
100
|
+
try {
|
|
101
|
+
storage.setItem = ((key, value) => {
|
|
102
|
+
var _a;
|
|
103
|
+
(_a = orig.setItem) === null || _a === void 0 ? void 0 : _a.call(orig, key, value);
|
|
104
|
+
const sig = buildSignature(storage);
|
|
105
|
+
if (sig !== lastSig) {
|
|
106
|
+
lastSig = sig;
|
|
107
|
+
trigger();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
storage.removeItem = ((key) => {
|
|
111
|
+
var _a;
|
|
112
|
+
(_a = orig.removeItem) === null || _a === void 0 ? void 0 : _a.call(orig, key);
|
|
113
|
+
const sig = buildSignature(storage);
|
|
114
|
+
if (sig !== lastSig) {
|
|
115
|
+
lastSig = sig;
|
|
116
|
+
trigger();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
storage.clear = (() => {
|
|
120
|
+
var _a;
|
|
121
|
+
(_a = orig.clear) === null || _a === void 0 ? void 0 : _a.call(orig);
|
|
122
|
+
const sig = buildSignature(storage);
|
|
123
|
+
if (sig !== lastSig) {
|
|
124
|
+
lastSig = sig;
|
|
125
|
+
trigger();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
catch (_a) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const patched = patch();
|
|
135
|
+
const timer = globalThis.setInterval(() => {
|
|
136
|
+
const sig = buildSignature(storage);
|
|
137
|
+
if (sig !== lastSig) {
|
|
138
|
+
lastSig = sig;
|
|
139
|
+
trigger();
|
|
140
|
+
}
|
|
141
|
+
}, patched ? Math.max(800, pollIntervalMs) : pollIntervalMs);
|
|
142
|
+
const destroy = () => {
|
|
143
|
+
var _a;
|
|
144
|
+
destroyed = true;
|
|
145
|
+
globalThis.clearInterval(timer);
|
|
146
|
+
try {
|
|
147
|
+
(_a = win.removeEventListener) === null || _a === void 0 ? void 0 : _a.call(win, "storage", onStorageEvent);
|
|
148
|
+
}
|
|
149
|
+
catch (_b) {
|
|
150
|
+
void 0;
|
|
151
|
+
}
|
|
152
|
+
// best-effort restore
|
|
153
|
+
try {
|
|
154
|
+
if (orig.setItem)
|
|
155
|
+
storage.setItem = orig.setItem;
|
|
156
|
+
if (orig.removeItem)
|
|
157
|
+
storage.removeItem = orig.removeItem;
|
|
158
|
+
if (orig.clear)
|
|
159
|
+
storage.clear = orig.clear;
|
|
160
|
+
}
|
|
161
|
+
catch (_c) {
|
|
162
|
+
void 0;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
return { destroy, trigger };
|
|
166
|
+
}
|
|
@@ -2,4 +2,7 @@ import type { LogStorage } from "../../../storage/logStorage";
|
|
|
2
2
|
import type { PanelHandle } from "..";
|
|
3
3
|
import type { PanelDOM } from "../dom/types";
|
|
4
4
|
import type { PanelState } from "../state/types";
|
|
5
|
-
export declare function setupPanelBehavior(dom: PanelDOM, state: PanelState, storage: LogStorage
|
|
5
|
+
export declare function setupPanelBehavior(dom: PanelDOM, state: PanelState, storage: LogStorage, themeConfig: {
|
|
6
|
+
storageKey: string;
|
|
7
|
+
persist: boolean;
|
|
8
|
+
}): PanelHandle;
|
|
@@ -3,7 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.setupPanelBehavior = setupPanelBehavior;
|
|
4
4
|
const bindings_1 = require("../bindings");
|
|
5
5
|
const resize_1 = require("../resize");
|
|
6
|
-
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
function setupPanelBehavior(dom, state, storage, themeConfig) {
|
|
7
8
|
const applyVisibility = () => {
|
|
8
9
|
if (state.open)
|
|
9
10
|
dom.panel.classList.remove("di-hidden");
|
|
@@ -20,17 +21,36 @@ function setupPanelBehavior(dom, state, storage) {
|
|
|
20
21
|
dom.consoleTab.classList.remove("di-tabActive");
|
|
21
22
|
}
|
|
22
23
|
};
|
|
24
|
+
const updateThemeButton = () => {
|
|
25
|
+
dom.themeBtn.setAttribute("aria-pressed", state.theme === "dark" ? "true" : "false");
|
|
26
|
+
dom.themeBtn.setAttribute("title", state.theme === "dark" ? "Switch to light theme" : "Switch to dark theme");
|
|
27
|
+
};
|
|
28
|
+
const setTheme = (next) => {
|
|
29
|
+
state.theme = next;
|
|
30
|
+
(0, theme_1.applyTheme)(dom.root, next);
|
|
31
|
+
updateThemeButton();
|
|
32
|
+
if (themeConfig.persist)
|
|
33
|
+
(0, theme_1.saveTheme)(themeConfig.storageKey, next);
|
|
34
|
+
};
|
|
35
|
+
(0, theme_1.applyTheme)(dom.root, state.theme);
|
|
36
|
+
updateThemeButton();
|
|
23
37
|
const bindings = (0, bindings_1.bindStorageToPanelView)({
|
|
24
38
|
storage,
|
|
25
39
|
state,
|
|
26
40
|
list: dom.list,
|
|
27
41
|
body: dom.body,
|
|
42
|
+
jumpBtn: dom.jumpBtn,
|
|
43
|
+
jumpBtnLabel: dom.jumpBtnLabel,
|
|
28
44
|
counters: dom.counters,
|
|
29
45
|
updateTabStyles,
|
|
30
46
|
});
|
|
31
47
|
applyVisibility();
|
|
32
48
|
updateTabStyles();
|
|
33
49
|
const resizeControls = (0, resize_1.attachResizeHandling)(dom.panel, dom.resizeHandle);
|
|
50
|
+
const updateSearchClearVisibility = () => {
|
|
51
|
+
dom.searchClearBtn.classList.toggle("di-searchClear--visible", state.query.length > 0);
|
|
52
|
+
};
|
|
53
|
+
updateSearchClearVisibility();
|
|
34
54
|
const open = () => {
|
|
35
55
|
state.open = true;
|
|
36
56
|
applyVisibility();
|
|
@@ -56,17 +76,44 @@ function setupPanelBehavior(dom, state, storage) {
|
|
|
56
76
|
updateTabStyles();
|
|
57
77
|
bindings.renderActiveTab();
|
|
58
78
|
};
|
|
79
|
+
const onThemeClick = () => setTheme(state.theme === "dark" ? "light" : "dark");
|
|
80
|
+
const onSearchInput = () => {
|
|
81
|
+
state.query = dom.searchInput.value;
|
|
82
|
+
updateSearchClearVisibility();
|
|
83
|
+
bindings.renderActiveTab();
|
|
84
|
+
};
|
|
85
|
+
const onSearchClear = () => {
|
|
86
|
+
if (state.query.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
state.query = "";
|
|
89
|
+
dom.searchInput.value = "";
|
|
90
|
+
updateSearchClearVisibility();
|
|
91
|
+
bindings.renderActiveTab();
|
|
92
|
+
dom.searchInput.focus();
|
|
93
|
+
};
|
|
94
|
+
const onBodyScroll = () => bindings.refreshScrollAffordances();
|
|
95
|
+
const onJumpClick = () => bindings.scrollToBottom();
|
|
59
96
|
dom.toggleBtn.addEventListener("click", onToggleClick);
|
|
60
97
|
dom.closeBtn.addEventListener("click", onCloseClick);
|
|
61
98
|
dom.clearBtn.addEventListener("click", onClearClick);
|
|
99
|
+
dom.themeBtn.addEventListener("click", onThemeClick);
|
|
62
100
|
dom.consoleTab.addEventListener("click", onConsoleTab);
|
|
63
101
|
dom.networkTab.addEventListener("click", onNetworkTab);
|
|
102
|
+
dom.searchInput.addEventListener("input", onSearchInput);
|
|
103
|
+
dom.searchClearBtn.addEventListener("click", onSearchClear);
|
|
104
|
+
dom.body.addEventListener("scroll", onBodyScroll, { passive: true });
|
|
105
|
+
dom.jumpBtn.addEventListener("click", onJumpClick);
|
|
64
106
|
const destroy = () => {
|
|
65
107
|
dom.toggleBtn.removeEventListener("click", onToggleClick);
|
|
66
108
|
dom.closeBtn.removeEventListener("click", onCloseClick);
|
|
67
109
|
dom.clearBtn.removeEventListener("click", onClearClick);
|
|
110
|
+
dom.themeBtn.removeEventListener("click", onThemeClick);
|
|
68
111
|
dom.consoleTab.removeEventListener("click", onConsoleTab);
|
|
69
112
|
dom.networkTab.removeEventListener("click", onNetworkTab);
|
|
113
|
+
dom.searchInput.removeEventListener("input", onSearchInput);
|
|
114
|
+
dom.searchClearBtn.removeEventListener("click", onSearchClear);
|
|
115
|
+
dom.body.removeEventListener("scroll", onBodyScroll);
|
|
116
|
+
dom.jumpBtn.removeEventListener("click", onJumpClick);
|
|
70
117
|
resizeControls.destroy();
|
|
71
118
|
bindings.destroy();
|
|
72
119
|
dom.root.remove();
|