glitchgrab 1.30.0 → 1.31.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 +254 -2
- package/dist/index.d.mts +266 -3
- package/dist/index.d.ts +266 -3
- package/dist/index.js +1474 -233
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1413 -180
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -31,28 +31,37 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/index.ts
|
|
32
32
|
var index_exports = {};
|
|
33
33
|
__export(index_exports, {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
FeedbackButton: () => FeedbackButton,
|
|
35
|
+
GLITCHGRAB_SHORTCUT: () => GLITCHGRAB_SHORTCUT2,
|
|
36
|
+
GLITCHGRAB_SHORTCUT_MAC: () => GLITCHGRAB_SHORTCUT_MAC2,
|
|
36
37
|
GlitchgrabErrorBoundary: () => GlitchgrabErrorBoundary,
|
|
37
38
|
GlitchgrabProvider: () => GlitchgrabProvider,
|
|
38
39
|
ReportButton: () => ReportButton,
|
|
39
40
|
addBreadcrumb: () => addBreadcrumb,
|
|
40
41
|
captureContext: () => captureContext,
|
|
41
42
|
captureDeviceInfo: () => captureDeviceInfo,
|
|
43
|
+
captureError: () => captureError,
|
|
44
|
+
clearAppContext: () => clearAppContext,
|
|
42
45
|
clearBreadcrumbs: () => clearBreadcrumbs,
|
|
43
46
|
clearDedupCache: () => clearDedupCache,
|
|
44
47
|
computeSignature: () => computeSignature,
|
|
45
48
|
enhanceText: () => enhanceText,
|
|
49
|
+
fetchGlitchgrabFeedback: () => fetchGlitchgrabFeedback,
|
|
46
50
|
fetchGlitchgrabReports: () => fetchGlitchgrabReports,
|
|
51
|
+
getAppContext: () => getAppContext,
|
|
47
52
|
getBreadcrumbs: () => getBreadcrumbs,
|
|
48
|
-
getShortcutLabel: () =>
|
|
53
|
+
getShortcutLabel: () => getShortcutLabel2,
|
|
49
54
|
initBreadcrumbs: () => initBreadcrumbs,
|
|
50
55
|
matchesShortcut: () => matchesShortcut,
|
|
51
56
|
sanitizeUrl: () => sanitizeUrl,
|
|
57
|
+
sendFeedback: () => sendFeedback,
|
|
52
58
|
sendReport: () => sendReport,
|
|
59
|
+
setContext: () => setContext,
|
|
60
|
+
setContexts: () => setContexts,
|
|
53
61
|
shouldSkipDuplicate: () => shouldSkipDuplicate,
|
|
54
62
|
useGlitchgrab: () => useGlitchgrab,
|
|
55
63
|
useGlitchgrabActions: () => useGlitchgrabActions,
|
|
64
|
+
useGlitchgrabFeedback: () => useGlitchgrabFeedback,
|
|
56
65
|
useGlitchgrabReports: () => useGlitchgrabReports
|
|
57
66
|
});
|
|
58
67
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -63,18 +72,76 @@ var import_react3 = require("react");
|
|
|
63
72
|
// src/error-boundary.tsx
|
|
64
73
|
var React = __toESM(require("react"));
|
|
65
74
|
|
|
75
|
+
// src/redact.ts
|
|
76
|
+
var SENSITIVE_KEY_RE = /(pass|pwd|secret|token|auth|session|cookie|credential|apikey|api_key|access_key|private|signature|otp|pin|cvv|card|ssn|aadhaar|pan)/i;
|
|
77
|
+
var EMAIL_RE = /[\w.+-]+@[\w-]+\.[\w.-]+/g;
|
|
78
|
+
var JWT_RE = /\beyJ[\w-]{8,}\.[\w-]{8,}\.[\w-]{8,}\b/g;
|
|
79
|
+
var BEARER_RE = /\b(bearer\s+)[\w-.~+/]{12,}=*/gi;
|
|
80
|
+
var MAX_BODY_LENGTH = 500;
|
|
81
|
+
function redactString(value) {
|
|
82
|
+
return value.replace(JWT_RE, "[REDACTED]").replace(BEARER_RE, "$1[REDACTED]").replace(EMAIL_RE, "[REDACTED_EMAIL]");
|
|
83
|
+
}
|
|
84
|
+
function redactValue(value, depth) {
|
|
85
|
+
if (depth > 4) return "[DEPTH]";
|
|
86
|
+
if (value === null || value === void 0) return value;
|
|
87
|
+
if (typeof value === "string") return redactString(value);
|
|
88
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
89
|
+
if (Array.isArray(value)) return value.slice(0, 20).map((v) => redactValue(v, depth + 1));
|
|
90
|
+
if (typeof value === "object") {
|
|
91
|
+
const out = {};
|
|
92
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
93
|
+
out[key] = SENSITIVE_KEY_RE.test(key) ? "[REDACTED]" : redactValue(inner, depth + 1);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
return String(value);
|
|
98
|
+
}
|
|
99
|
+
function redactBody(body, maxLength = MAX_BODY_LENGTH) {
|
|
100
|
+
try {
|
|
101
|
+
const trimmed = body.trim();
|
|
102
|
+
if (!trimmed) return "";
|
|
103
|
+
let result;
|
|
104
|
+
try {
|
|
105
|
+
result = JSON.stringify(redactValue(JSON.parse(trimmed), 0));
|
|
106
|
+
} catch (e) {
|
|
107
|
+
result = redactString(trimmed);
|
|
108
|
+
}
|
|
109
|
+
return result.length > maxLength ? `${result.slice(0, maxLength)}\u2026[truncated]` : result;
|
|
110
|
+
} catch (e) {
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
66
115
|
// src/breadcrumbs.ts
|
|
67
116
|
var MAX_DEFAULT = 50;
|
|
68
117
|
var breadcrumbs = [];
|
|
69
118
|
var maxBreadcrumbs = MAX_DEFAULT;
|
|
70
119
|
var initialized = false;
|
|
71
|
-
|
|
120
|
+
var responseBodyOrigins = [];
|
|
121
|
+
function mayCaptureBody(url) {
|
|
122
|
+
try {
|
|
123
|
+
if (typeof window === "undefined") return false;
|
|
124
|
+
const origin = new URL(url, window.location.href).origin;
|
|
125
|
+
if (origin === window.location.origin) return true;
|
|
126
|
+
return responseBodyOrigins.includes(origin);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function initBreadcrumbs(max, options) {
|
|
132
|
+
var _a;
|
|
133
|
+
if ((_a = options == null ? void 0 : options.responseBodyOrigins) == null ? void 0 : _a.length) {
|
|
134
|
+
responseBodyOrigins = Array.from(
|
|
135
|
+
/* @__PURE__ */ new Set([...responseBodyOrigins, ...options.responseBodyOrigins])
|
|
136
|
+
);
|
|
137
|
+
}
|
|
72
138
|
if (initialized) return;
|
|
73
139
|
maxBreadcrumbs = max != null ? max : MAX_DEFAULT;
|
|
74
140
|
try {
|
|
75
141
|
if (typeof window === "undefined") return;
|
|
76
142
|
interceptConsole();
|
|
77
143
|
interceptFetch();
|
|
144
|
+
interceptXhr();
|
|
78
145
|
interceptNavigation();
|
|
79
146
|
interceptClicks();
|
|
80
147
|
initialized = true;
|
|
@@ -133,6 +200,16 @@ function argsToString(args) {
|
|
|
133
200
|
return "[unknown]";
|
|
134
201
|
}
|
|
135
202
|
}
|
|
203
|
+
async function readErrorBody(response, url) {
|
|
204
|
+
try {
|
|
205
|
+
if (response.ok) return "";
|
|
206
|
+
if (!mayCaptureBody(url)) return "";
|
|
207
|
+
const text = await response.clone().text();
|
|
208
|
+
return redactBody(text);
|
|
209
|
+
} catch (e) {
|
|
210
|
+
return "";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
136
213
|
function interceptFetch() {
|
|
137
214
|
const origFetch = window.fetch;
|
|
138
215
|
window.fetch = async function(input, init) {
|
|
@@ -145,10 +222,12 @@ function interceptFetch() {
|
|
|
145
222
|
input,
|
|
146
223
|
init
|
|
147
224
|
);
|
|
225
|
+
const body = await readErrorBody(response, url);
|
|
148
226
|
addBreadcrumb("api", `${method} ${url.slice(0, 100)} \u2192 ${response.status}`, {
|
|
149
227
|
method,
|
|
150
228
|
status: String(response.status),
|
|
151
|
-
duration: `${Date.now() - start}ms
|
|
229
|
+
duration: `${Date.now() - start}ms`,
|
|
230
|
+
...body ? { responseBody: body } : {}
|
|
152
231
|
});
|
|
153
232
|
return response;
|
|
154
233
|
} catch (err) {
|
|
@@ -161,6 +240,67 @@ function interceptFetch() {
|
|
|
161
240
|
}
|
|
162
241
|
};
|
|
163
242
|
}
|
|
243
|
+
function interceptXhr() {
|
|
244
|
+
if (typeof XMLHttpRequest === "undefined") return;
|
|
245
|
+
const origOpen = XMLHttpRequest.prototype.open;
|
|
246
|
+
const origSend = XMLHttpRequest.prototype.send;
|
|
247
|
+
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
|
|
248
|
+
try {
|
|
249
|
+
this.__ggMethod = method;
|
|
250
|
+
this.__ggUrl = typeof url === "string" ? url : url.toString();
|
|
251
|
+
const record = () => {
|
|
252
|
+
var _a, _b, _c, _d;
|
|
253
|
+
try {
|
|
254
|
+
const recordedMethod = (_a = this.__ggMethod) != null ? _a : "GET";
|
|
255
|
+
const recordedUrl = ((_b = this.__ggUrl) != null ? _b : "").slice(0, 100);
|
|
256
|
+
const duration = `${Date.now() - ((_c = this.__ggStart) != null ? _c : Date.now())}ms`;
|
|
257
|
+
if (!this.status) {
|
|
258
|
+
addBreadcrumb("api", `${recordedMethod} ${recordedUrl} \u2192 FAILED`, {
|
|
259
|
+
method: recordedMethod,
|
|
260
|
+
duration
|
|
261
|
+
});
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const failed = this.status < 200 || this.status >= 300;
|
|
265
|
+
const body = failed && mayCaptureBody((_d = this.__ggUrl) != null ? _d : "") ? readXhrBody(this) : "";
|
|
266
|
+
addBreadcrumb("api", `${recordedMethod} ${recordedUrl} \u2192 ${this.status}`, {
|
|
267
|
+
method: recordedMethod,
|
|
268
|
+
status: String(this.status),
|
|
269
|
+
duration,
|
|
270
|
+
...body ? { responseBody: body } : {}
|
|
271
|
+
});
|
|
272
|
+
} catch (e) {
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
if (!this.__ggListening) {
|
|
276
|
+
this.__ggListening = true;
|
|
277
|
+
this.addEventListener("loadend", record);
|
|
278
|
+
}
|
|
279
|
+
} catch (e) {
|
|
280
|
+
}
|
|
281
|
+
return origOpen.apply(this, [
|
|
282
|
+
method,
|
|
283
|
+
url,
|
|
284
|
+
...rest
|
|
285
|
+
]);
|
|
286
|
+
};
|
|
287
|
+
XMLHttpRequest.prototype.send = function(...args) {
|
|
288
|
+
try {
|
|
289
|
+
this.__ggStart = Date.now();
|
|
290
|
+
} catch (e) {
|
|
291
|
+
}
|
|
292
|
+
return origSend.apply(this, args);
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function readXhrBody(xhr) {
|
|
296
|
+
try {
|
|
297
|
+
if (xhr.responseType && xhr.responseType !== "text" && xhr.responseType !== "json") return "";
|
|
298
|
+
const raw = xhr.responseType === "json" ? JSON.stringify(xhr.response) : xhr.responseText;
|
|
299
|
+
return raw ? redactBody(raw) : "";
|
|
300
|
+
} catch (e) {
|
|
301
|
+
return "";
|
|
302
|
+
}
|
|
303
|
+
}
|
|
164
304
|
function interceptNavigation() {
|
|
165
305
|
const origPush = history.pushState.bind(history);
|
|
166
306
|
const origReplace = history.replaceState.bind(history);
|
|
@@ -195,6 +335,90 @@ function interceptClicks() {
|
|
|
195
335
|
);
|
|
196
336
|
}
|
|
197
337
|
|
|
338
|
+
// src/runtime.ts
|
|
339
|
+
var pageLoadedAt = Date.now();
|
|
340
|
+
var errorCount = 0;
|
|
341
|
+
function incrementErrorCount() {
|
|
342
|
+
errorCount += 1;
|
|
343
|
+
return errorCount;
|
|
344
|
+
}
|
|
345
|
+
var toMb = (bytes) => typeof bytes === "number" ? Math.round(bytes / 1048576 * 10) / 10 : void 0;
|
|
346
|
+
function captureRuntimeInfo() {
|
|
347
|
+
try {
|
|
348
|
+
if (typeof window === "undefined") return null;
|
|
349
|
+
const memory = performance == null ? void 0 : performance.memory;
|
|
350
|
+
const connection = navigator == null ? void 0 : navigator.connection;
|
|
351
|
+
return {
|
|
352
|
+
timeOnPageMs: Date.now() - pageLoadedAt,
|
|
353
|
+
errorCount,
|
|
354
|
+
visibility: typeof document !== "undefined" ? document.visibilityState : "unknown",
|
|
355
|
+
...memory ? {
|
|
356
|
+
jsHeapUsedMb: toMb(memory.usedJSHeapSize),
|
|
357
|
+
jsHeapLimitMb: toMb(memory.jsHeapSizeLimit)
|
|
358
|
+
} : {},
|
|
359
|
+
...connection ? {
|
|
360
|
+
connectionType: connection.effectiveType,
|
|
361
|
+
downlinkMbps: connection.downlink,
|
|
362
|
+
rttMs: connection.rtt,
|
|
363
|
+
saveData: connection.saveData
|
|
364
|
+
} : {}
|
|
365
|
+
};
|
|
366
|
+
} catch (e) {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/app-context.ts
|
|
372
|
+
var MAX_KEYS = 30;
|
|
373
|
+
var MAX_VALUE_LENGTH = 200;
|
|
374
|
+
var appContext = {};
|
|
375
|
+
var release;
|
|
376
|
+
function coerce(value) {
|
|
377
|
+
if (typeof value === "string") return value.slice(0, MAX_VALUE_LENGTH);
|
|
378
|
+
if (value === null || value === void 0) return "";
|
|
379
|
+
try {
|
|
380
|
+
return JSON.stringify(value).slice(0, MAX_VALUE_LENGTH);
|
|
381
|
+
} catch (e) {
|
|
382
|
+
return String(value).slice(0, MAX_VALUE_LENGTH);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function setContext(key, value) {
|
|
386
|
+
try {
|
|
387
|
+
if (!key) return;
|
|
388
|
+
if (value === null || value === void 0) {
|
|
389
|
+
delete appContext[key];
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (!(key in appContext) && Object.keys(appContext).length >= MAX_KEYS) return;
|
|
393
|
+
appContext[key] = coerce(value);
|
|
394
|
+
} catch (e) {
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function setContexts(values) {
|
|
398
|
+
try {
|
|
399
|
+
for (const [key, value] of Object.entries(values != null ? values : {})) setContext(key, value);
|
|
400
|
+
} catch (e) {
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function getAppContext() {
|
|
404
|
+
return { ...appContext };
|
|
405
|
+
}
|
|
406
|
+
function clearAppContext() {
|
|
407
|
+
appContext = {};
|
|
408
|
+
}
|
|
409
|
+
function setRelease(value) {
|
|
410
|
+
if (value) release = value;
|
|
411
|
+
}
|
|
412
|
+
function getRelease() {
|
|
413
|
+
if (release) return release;
|
|
414
|
+
try {
|
|
415
|
+
const fromEnv = process.env.NEXT_PUBLIC_APP_VERSION || process.env.NEXT_PUBLIC_RELEASE || process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA;
|
|
416
|
+
return fromEnv || void 0;
|
|
417
|
+
} catch (e) {
|
|
418
|
+
return void 0;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
198
422
|
// src/utils.ts
|
|
199
423
|
var SENSITIVE_PARAMS = [
|
|
200
424
|
"token",
|
|
@@ -263,7 +487,10 @@ function captureContext(visitedPages) {
|
|
|
263
487
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
264
488
|
visitedPages: visitedPages.map(sanitizeUrl),
|
|
265
489
|
breadcrumbs: getBreadcrumbs(),
|
|
266
|
-
deviceInfo: captureDeviceInfo()
|
|
490
|
+
deviceInfo: captureDeviceInfo(),
|
|
491
|
+
runtime: captureRuntimeInfo(),
|
|
492
|
+
appContext: getAppContext(),
|
|
493
|
+
release: getRelease()
|
|
267
494
|
};
|
|
268
495
|
} catch (e) {
|
|
269
496
|
return {
|
|
@@ -272,8 +499,40 @@ function captureContext(visitedPages) {
|
|
|
272
499
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
273
500
|
visitedPages: [],
|
|
274
501
|
breadcrumbs: [],
|
|
275
|
-
deviceInfo: null
|
|
502
|
+
deviceInfo: null,
|
|
503
|
+
runtime: null,
|
|
504
|
+
appContext: {}
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function contextMetadata(context) {
|
|
509
|
+
var _a;
|
|
510
|
+
try {
|
|
511
|
+
const runtime = context.runtime;
|
|
512
|
+
const appContext2 = Object.entries((_a = context.appContext) != null ? _a : {}).reduce(
|
|
513
|
+
(acc, [key, value]) => {
|
|
514
|
+
acc[`ctx_${key}`] = value;
|
|
515
|
+
return acc;
|
|
516
|
+
},
|
|
517
|
+
{}
|
|
518
|
+
);
|
|
519
|
+
return {
|
|
520
|
+
...context.release ? { release: context.release } : {},
|
|
521
|
+
...runtime ? {
|
|
522
|
+
timeOnPageMs: String(runtime.timeOnPageMs),
|
|
523
|
+
errorCount: String(runtime.errorCount),
|
|
524
|
+
visibility: runtime.visibility,
|
|
525
|
+
...runtime.jsHeapUsedMb !== void 0 ? { jsHeapUsedMb: String(runtime.jsHeapUsedMb) } : {},
|
|
526
|
+
...runtime.jsHeapLimitMb !== void 0 ? { jsHeapLimitMb: String(runtime.jsHeapLimitMb) } : {},
|
|
527
|
+
...runtime.connectionType ? { connectionType: runtime.connectionType } : {},
|
|
528
|
+
...runtime.downlinkMbps !== void 0 ? { downlinkMbps: String(runtime.downlinkMbps) } : {},
|
|
529
|
+
...runtime.rttMs !== void 0 ? { rttMs: String(runtime.rttMs) } : {},
|
|
530
|
+
...runtime.saveData !== void 0 ? { saveData: String(runtime.saveData) } : {}
|
|
531
|
+
} : {},
|
|
532
|
+
...appContext2
|
|
276
533
|
};
|
|
534
|
+
} catch (e) {
|
|
535
|
+
return {};
|
|
277
536
|
}
|
|
278
537
|
}
|
|
279
538
|
var DEFAULT_BASE_URL = "https://glitchgrab.dev";
|
|
@@ -323,6 +582,41 @@ async function sendReport(payload, baseUrl) {
|
|
|
323
582
|
return null;
|
|
324
583
|
}
|
|
325
584
|
}
|
|
585
|
+
async function sendFeedback(payload, baseUrl) {
|
|
586
|
+
var _a;
|
|
587
|
+
try {
|
|
588
|
+
const url = `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL}/api/v1/sdk/feedback`;
|
|
589
|
+
const body = JSON.stringify(payload);
|
|
590
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
591
|
+
try {
|
|
592
|
+
const response = await fetch(url, {
|
|
593
|
+
method: "POST",
|
|
594
|
+
headers: {
|
|
595
|
+
"Content-Type": "application/json",
|
|
596
|
+
Authorization: `Bearer ${payload.token}`
|
|
597
|
+
},
|
|
598
|
+
body
|
|
599
|
+
});
|
|
600
|
+
if (response.ok) {
|
|
601
|
+
const envelope = await response.json();
|
|
602
|
+
return { success: envelope.success, ...(_a = envelope.data) != null ? _a : {} };
|
|
603
|
+
}
|
|
604
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
} catch (e) {
|
|
608
|
+
}
|
|
609
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
610
|
+
await new Promise(
|
|
611
|
+
(resolve) => setTimeout(resolve, RETRY_BASE_MS * Math.pow(2, attempt))
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
} catch (e) {
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
326
620
|
async function enhanceText(text, token, baseUrl, screenshot, context) {
|
|
327
621
|
var _a;
|
|
328
622
|
try {
|
|
@@ -412,11 +706,12 @@ function stripUrlQuery(url) {
|
|
|
412
706
|
}
|
|
413
707
|
}
|
|
414
708
|
function computeSignature(params) {
|
|
415
|
-
var _a, _b;
|
|
709
|
+
var _a, _b, _c, _d;
|
|
416
710
|
const msg = (_b = (_a = params.errorMessage) == null ? void 0 : _a.trim()) != null ? _b : "";
|
|
417
711
|
const page = stripUrlQuery(params.pageUrl);
|
|
418
712
|
const frame = topStackFrame(params.errorStack);
|
|
419
|
-
|
|
713
|
+
const digest = (_d = (_c = params.digest) == null ? void 0 : _c.trim()) != null ? _d : "";
|
|
714
|
+
return hashString(digest ? `${msg}|${page}|${frame}|${digest}` : `${msg}|${page}|${frame}`);
|
|
420
715
|
}
|
|
421
716
|
function shouldSkipDuplicate(signature, windowMs = DEFAULT_WINDOW_MS, now = Date.now()) {
|
|
422
717
|
const last = seen.get(signature);
|
|
@@ -441,6 +736,78 @@ function clearDedupCache() {
|
|
|
441
736
|
seen.clear();
|
|
442
737
|
}
|
|
443
738
|
|
|
739
|
+
// src/capture.ts
|
|
740
|
+
var config = null;
|
|
741
|
+
function registerCaptureConfig(next) {
|
|
742
|
+
config = next;
|
|
743
|
+
}
|
|
744
|
+
function matchesIgnorePatterns(message, patterns) {
|
|
745
|
+
if (!patterns || patterns.length === 0) return false;
|
|
746
|
+
return patterns.some(
|
|
747
|
+
(pattern) => pattern instanceof RegExp ? pattern.test(message) : message.includes(pattern)
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
function sessionMetadata(session) {
|
|
751
|
+
return {
|
|
752
|
+
...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
|
|
753
|
+
...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
|
|
754
|
+
...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
|
|
755
|
+
...(session == null ? void 0 : session.phone) ? { sessionUserPhone: String(session.phone) } : {}
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function buildCapturedErrorPayload(error, options, cfg, capturedBy = "captureError") {
|
|
759
|
+
var _a, _b, _c;
|
|
760
|
+
const err = error instanceof Error ? error : void 0;
|
|
761
|
+
const errorMessage = err ? err.message : String(error);
|
|
762
|
+
if (!errorMessage) return null;
|
|
763
|
+
if (matchesIgnorePatterns(errorMessage, cfg.ignoreErrors)) return null;
|
|
764
|
+
incrementErrorCount();
|
|
765
|
+
const context = captureContext(cfg.getVisitedPages());
|
|
766
|
+
const digest = (_a = options == null ? void 0 : options.digest) != null ? _a : typeof (error == null ? void 0 : error.digest) === "string" ? error.digest : void 0;
|
|
767
|
+
const sig = computeSignature({
|
|
768
|
+
errorMessage,
|
|
769
|
+
pageUrl: context.url,
|
|
770
|
+
errorStack: err == null ? void 0 : err.stack,
|
|
771
|
+
digest
|
|
772
|
+
});
|
|
773
|
+
if (shouldSkipDuplicate(sig)) return null;
|
|
774
|
+
return {
|
|
775
|
+
token: cfg.token,
|
|
776
|
+
source: "SDK_AUTO",
|
|
777
|
+
type: "BUG",
|
|
778
|
+
errorMessage,
|
|
779
|
+
errorStack: err == null ? void 0 : err.stack,
|
|
780
|
+
componentStack: (_b = options == null ? void 0 : options.componentStack) != null ? _b : void 0,
|
|
781
|
+
pageUrl: context.url,
|
|
782
|
+
userAgent: context.userAgent,
|
|
783
|
+
breadcrumbs: context.breadcrumbs,
|
|
784
|
+
deviceInfo: (_c = context.deviceInfo) != null ? _c : void 0,
|
|
785
|
+
metadata: {
|
|
786
|
+
timestamp: context.timestamp,
|
|
787
|
+
visitedPages: JSON.stringify(context.visitedPages),
|
|
788
|
+
capturedBy,
|
|
789
|
+
...contextMetadata(context),
|
|
790
|
+
...(options == null ? void 0 : options.boundary) ? { boundary: options.boundary } : {},
|
|
791
|
+
...digest ? { digest } : {},
|
|
792
|
+
...sessionMetadata(cfg.session),
|
|
793
|
+
...options == null ? void 0 : options.metadata
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function captureError(error, options) {
|
|
798
|
+
try {
|
|
799
|
+
const cfg = config;
|
|
800
|
+
if (!(cfg == null ? void 0 : cfg.token)) return;
|
|
801
|
+
const payload = buildCapturedErrorPayload(error, options, cfg);
|
|
802
|
+
if (!payload) return;
|
|
803
|
+
void sendReport(payload, cfg.baseUrl).then((result) => {
|
|
804
|
+
if (result && cfg.onReportSent) cfg.onReportSent(result);
|
|
805
|
+
});
|
|
806
|
+
if (cfg.onError && error instanceof Error) cfg.onError(error);
|
|
807
|
+
} catch (e) {
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
444
811
|
// src/error-boundary.tsx
|
|
445
812
|
var GlitchgrabErrorBoundary = class extends React.Component {
|
|
446
813
|
constructor(props) {
|
|
@@ -451,40 +818,22 @@ var GlitchgrabErrorBoundary = class extends React.Component {
|
|
|
451
818
|
return { hasError: true };
|
|
452
819
|
}
|
|
453
820
|
componentDidCatch(error, errorInfo) {
|
|
454
|
-
var _a
|
|
821
|
+
var _a;
|
|
455
822
|
try {
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
823
|
+
const payload = buildCapturedErrorPayload(
|
|
824
|
+
error,
|
|
825
|
+
{ componentStack: (_a = errorInfo.componentStack) != null ? _a : void 0 },
|
|
826
|
+
{
|
|
827
|
+
token: this.props.token,
|
|
828
|
+
baseUrl: this.props.baseUrl,
|
|
829
|
+
session: this.props.session,
|
|
830
|
+
getVisitedPages: () => this.props.visitedPages
|
|
831
|
+
},
|
|
832
|
+
"GlitchgrabErrorBoundary"
|
|
833
|
+
);
|
|
834
|
+
if (payload) {
|
|
835
|
+
sendReport(payload, this.props.baseUrl);
|
|
465
836
|
}
|
|
466
|
-
const session = this.props.session;
|
|
467
|
-
const payload = {
|
|
468
|
-
token: this.props.token,
|
|
469
|
-
source: "SDK_AUTO",
|
|
470
|
-
type: "BUG",
|
|
471
|
-
errorMessage: error.message,
|
|
472
|
-
errorStack: error.stack,
|
|
473
|
-
componentStack: (_a = errorInfo.componentStack) != null ? _a : void 0,
|
|
474
|
-
pageUrl: context.url,
|
|
475
|
-
userAgent: context.userAgent,
|
|
476
|
-
breadcrumbs: context.breadcrumbs,
|
|
477
|
-
deviceInfo: (_b = context.deviceInfo) != null ? _b : void 0,
|
|
478
|
-
metadata: {
|
|
479
|
-
timestamp: context.timestamp,
|
|
480
|
-
visitedPages: JSON.stringify(context.visitedPages),
|
|
481
|
-
...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
|
|
482
|
-
...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
|
|
483
|
-
...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
|
|
484
|
-
...(session == null ? void 0 : session.phone) ? { sessionUserPhone: String(session.phone) } : {}
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
|
-
sendReport(payload, this.props.baseUrl);
|
|
488
837
|
if (this.props.onError) {
|
|
489
838
|
this.props.onError(error);
|
|
490
839
|
}
|
|
@@ -502,13 +851,41 @@ var GlitchgrabErrorBoundary = class extends React.Component {
|
|
|
502
851
|
}
|
|
503
852
|
};
|
|
504
853
|
|
|
505
|
-
//
|
|
506
|
-
var import_react2 = require("react");
|
|
507
|
-
var import_react_dom = require("react-dom");
|
|
508
|
-
|
|
509
|
-
// src/annotation-canvas.tsx
|
|
854
|
+
// ../report-ui/dist/index.mjs
|
|
510
855
|
var import_react = require("react");
|
|
856
|
+
var import_react_dom = require("react-dom");
|
|
857
|
+
var import_react2 = require("react");
|
|
511
858
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
859
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
860
|
+
var MAX_SCREENSHOT_DATA_URL_LENGTH = 18e5;
|
|
861
|
+
var QUALITY_LADDER = [0.92, 0.85, 0.75];
|
|
862
|
+
var DOWNSCALE_FACTOR = 0.7;
|
|
863
|
+
function downscale(canvas, factor) {
|
|
864
|
+
const next = document.createElement("canvas");
|
|
865
|
+
next.width = Math.max(1, Math.round(canvas.width * factor));
|
|
866
|
+
next.height = Math.max(1, Math.round(canvas.height * factor));
|
|
867
|
+
const ctx = next.getContext("2d");
|
|
868
|
+
if (!ctx) return null;
|
|
869
|
+
ctx.imageSmoothingEnabled = true;
|
|
870
|
+
ctx.imageSmoothingQuality = "high";
|
|
871
|
+
ctx.drawImage(canvas, 0, 0, next.width, next.height);
|
|
872
|
+
return next;
|
|
873
|
+
}
|
|
874
|
+
function encodeScreenshot(canvas, maxLength = MAX_SCREENSHOT_DATA_URL_LENGTH) {
|
|
875
|
+
let current = canvas;
|
|
876
|
+
for (let round = 0; round < 2; round++) {
|
|
877
|
+
let encoded = "";
|
|
878
|
+
for (const quality of QUALITY_LADDER) {
|
|
879
|
+
encoded = current.toDataURL("image/jpeg", quality);
|
|
880
|
+
if (encoded.length <= maxLength) return encoded;
|
|
881
|
+
}
|
|
882
|
+
if (round === 1) return encoded;
|
|
883
|
+
const smaller = downscale(current, DOWNSCALE_FACTOR);
|
|
884
|
+
if (!smaller) return encoded;
|
|
885
|
+
current = smaller;
|
|
886
|
+
}
|
|
887
|
+
return current.toDataURL("image/jpeg", QUALITY_LADDER[0]);
|
|
888
|
+
}
|
|
512
889
|
var PALETTE = ["#ef4444", "#f59e0b", "#22c55e", "#3b82f6", "#a855f7", "#ffffff"];
|
|
513
890
|
function drawStroke(ctx, s) {
|
|
514
891
|
ctx.strokeStyle = s.color;
|
|
@@ -528,15 +905,15 @@ function drawStroke(ctx, s) {
|
|
|
528
905
|
}
|
|
529
906
|
}
|
|
530
907
|
function AnnotationCanvas({ imageSrc, onSave, onCancel }) {
|
|
531
|
-
const canvasRef = (0,
|
|
532
|
-
const imgRef = (0,
|
|
533
|
-
const [ready, setReady] = (0,
|
|
534
|
-
const [tool, setTool] = (0,
|
|
535
|
-
const [color, setColor] = (0,
|
|
536
|
-
const [strokeWidth, setStrokeWidth] = (0,
|
|
537
|
-
const [strokes, setStrokes] = (0,
|
|
538
|
-
const [currentStroke, setCurrentStroke] = (0,
|
|
539
|
-
(0,
|
|
908
|
+
const canvasRef = (0, import_react2.useRef)(null);
|
|
909
|
+
const imgRef = (0, import_react2.useRef)(null);
|
|
910
|
+
const [ready, setReady] = (0, import_react2.useState)(false);
|
|
911
|
+
const [tool, setTool] = (0, import_react2.useState)("pen");
|
|
912
|
+
const [color, setColor] = (0, import_react2.useState)(PALETTE[0]);
|
|
913
|
+
const [strokeWidth, setStrokeWidth] = (0, import_react2.useState)(4);
|
|
914
|
+
const [strokes, setStrokes] = (0, import_react2.useState)([]);
|
|
915
|
+
const [currentStroke, setCurrentStroke] = (0, import_react2.useState)(null);
|
|
916
|
+
(0, import_react2.useEffect)(() => {
|
|
540
917
|
const img = new Image();
|
|
541
918
|
img.onload = () => {
|
|
542
919
|
imgRef.current = img;
|
|
@@ -549,7 +926,7 @@ function AnnotationCanvas({ imageSrc, onSave, onCancel }) {
|
|
|
549
926
|
};
|
|
550
927
|
img.src = imageSrc;
|
|
551
928
|
}, [imageSrc]);
|
|
552
|
-
(0,
|
|
929
|
+
(0, import_react2.useEffect)(() => {
|
|
553
930
|
if (!ready) return;
|
|
554
931
|
const canvas = canvasRef.current;
|
|
555
932
|
const img = imgRef.current;
|
|
@@ -594,7 +971,7 @@ function AnnotationCanvas({ imageSrc, onSave, onCancel }) {
|
|
|
594
971
|
const handleSave = () => {
|
|
595
972
|
const canvas = canvasRef.current;
|
|
596
973
|
if (!canvas) return;
|
|
597
|
-
onSave(canvas
|
|
974
|
+
onSave(encodeScreenshot(canvas));
|
|
598
975
|
};
|
|
599
976
|
const iconBtn = (active = false) => ({
|
|
600
977
|
width: "30px",
|
|
@@ -784,8 +1161,6 @@ function AnnotationCanvas({ imageSrc, onSave, onCancel }) {
|
|
|
784
1161
|
}
|
|
785
1162
|
);
|
|
786
1163
|
}
|
|
787
|
-
|
|
788
|
-
// src/shortcut.ts
|
|
789
1164
|
var GLITCHGRAB_SHORTCUT = "Ctrl+Shift+G";
|
|
790
1165
|
var GLITCHGRAB_SHORTCUT_MAC = "\u2318\u21E7G";
|
|
791
1166
|
function isMac() {
|
|
@@ -799,12 +1174,38 @@ function isMac() {
|
|
|
799
1174
|
function getShortcutLabel() {
|
|
800
1175
|
return isMac() ? GLITCHGRAB_SHORTCUT_MAC : GLITCHGRAB_SHORTCUT;
|
|
801
1176
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1177
|
+
var ATTACHMENT_ACCEPT = [
|
|
1178
|
+
"image/*",
|
|
1179
|
+
// text — inlined into the issue body
|
|
1180
|
+
".html",
|
|
1181
|
+
".htm",
|
|
1182
|
+
".txt",
|
|
1183
|
+
".md",
|
|
1184
|
+
".json",
|
|
1185
|
+
".xml",
|
|
1186
|
+
".log",
|
|
1187
|
+
".yml",
|
|
1188
|
+
".yaml",
|
|
1189
|
+
".csv",
|
|
1190
|
+
".har",
|
|
1191
|
+
".js",
|
|
1192
|
+
".mjs",
|
|
1193
|
+
".cjs",
|
|
1194
|
+
".ts",
|
|
1195
|
+
".tsx",
|
|
1196
|
+
".jsx",
|
|
1197
|
+
".css",
|
|
1198
|
+
".scss",
|
|
1199
|
+
".py",
|
|
1200
|
+
".sh",
|
|
1201
|
+
// binary documents
|
|
1202
|
+
".pdf",
|
|
1203
|
+
".doc",
|
|
1204
|
+
".docx",
|
|
1205
|
+
".xls",
|
|
1206
|
+
".xlsx",
|
|
1207
|
+
".zip"
|
|
1208
|
+
].join(",");
|
|
808
1209
|
function useIsDark() {
|
|
809
1210
|
if (typeof window === "undefined") return true;
|
|
810
1211
|
try {
|
|
@@ -917,6 +1318,98 @@ function detectHostAccent() {
|
|
|
917
1318
|
return null;
|
|
918
1319
|
}
|
|
919
1320
|
}
|
|
1321
|
+
function initialsOf(name) {
|
|
1322
|
+
return name.trim().split(/\s+/).slice(0, 2).map((part) => {
|
|
1323
|
+
var _a, _b;
|
|
1324
|
+
return (_b = (_a = part[0]) == null ? void 0 : _a.toUpperCase()) != null ? _b : "";
|
|
1325
|
+
}).join("");
|
|
1326
|
+
}
|
|
1327
|
+
function ReporterChip({
|
|
1328
|
+
reporter,
|
|
1329
|
+
t
|
|
1330
|
+
}) {
|
|
1331
|
+
var _a;
|
|
1332
|
+
const [avatarFailed, setAvatarFailed] = (0, import_react.useState)(false);
|
|
1333
|
+
const avatarUrl = (reporter == null ? void 0 : reporter.avatarUrl) || "";
|
|
1334
|
+
(0, import_react.useEffect)(() => {
|
|
1335
|
+
setAvatarFailed(false);
|
|
1336
|
+
}, [avatarUrl]);
|
|
1337
|
+
const name = ((_a = reporter == null ? void 0 : reporter.name) == null ? void 0 : _a.trim()) || "";
|
|
1338
|
+
const isAnonymous = !name;
|
|
1339
|
+
const initials = isAnonymous ? "" : initialsOf(name);
|
|
1340
|
+
const label = isAnonymous ? "Anonymous" : name;
|
|
1341
|
+
const title = isAnonymous ? "No reporter identity was passed to Glitchgrab \u2014 this report won't be attributed to anyone" : [name, reporter == null ? void 0 : reporter.email, reporter == null ? void 0 : reporter.role].filter(Boolean).join(" \xB7 ");
|
|
1342
|
+
const showAvatarImage = !!avatarUrl && !avatarFailed;
|
|
1343
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1344
|
+
"span",
|
|
1345
|
+
{
|
|
1346
|
+
title,
|
|
1347
|
+
style: { display: "flex", alignItems: "center", gap: "6px", minWidth: 0 },
|
|
1348
|
+
children: [
|
|
1349
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1350
|
+
"span",
|
|
1351
|
+
{
|
|
1352
|
+
"aria-hidden": "true",
|
|
1353
|
+
style: {
|
|
1354
|
+
width: "18px",
|
|
1355
|
+
height: "18px",
|
|
1356
|
+
flexShrink: 0,
|
|
1357
|
+
borderRadius: "50%",
|
|
1358
|
+
overflow: "hidden",
|
|
1359
|
+
display: "flex",
|
|
1360
|
+
alignItems: "center",
|
|
1361
|
+
justifyContent: "center",
|
|
1362
|
+
fontSize: "8px",
|
|
1363
|
+
fontWeight: 700,
|
|
1364
|
+
letterSpacing: "0.02em",
|
|
1365
|
+
background: isAnonymous ? t.inputBg : t.accent,
|
|
1366
|
+
color: isAnonymous ? t.textMuted : t.accentText,
|
|
1367
|
+
border: `1px solid ${isAnonymous ? t.inputBorder : "transparent"}`
|
|
1368
|
+
},
|
|
1369
|
+
children: showAvatarImage ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1370
|
+
"img",
|
|
1371
|
+
{
|
|
1372
|
+
src: avatarUrl,
|
|
1373
|
+
alt: "",
|
|
1374
|
+
width: 18,
|
|
1375
|
+
height: 18,
|
|
1376
|
+
onError: () => setAvatarFailed(true),
|
|
1377
|
+
style: { width: "100%", height: "100%", objectFit: "cover" }
|
|
1378
|
+
}
|
|
1379
|
+
) : initials ? initials : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "none", children: [
|
|
1380
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8", cy: "5.5", r: "2.75", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
1381
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1382
|
+
"path",
|
|
1383
|
+
{
|
|
1384
|
+
d: "M2.75 13.5c0-2.35 2.35-3.75 5.25-3.75s5.25 1.4 5.25 3.75",
|
|
1385
|
+
stroke: "currentColor",
|
|
1386
|
+
strokeWidth: "1.4",
|
|
1387
|
+
strokeLinecap: "round"
|
|
1388
|
+
}
|
|
1389
|
+
)
|
|
1390
|
+
] })
|
|
1391
|
+
}
|
|
1392
|
+
),
|
|
1393
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1394
|
+
"span",
|
|
1395
|
+
{
|
|
1396
|
+
style: {
|
|
1397
|
+
fontSize: "11px",
|
|
1398
|
+
color: t.textMuted,
|
|
1399
|
+
overflow: "hidden",
|
|
1400
|
+
textOverflow: "ellipsis",
|
|
1401
|
+
whiteSpace: "nowrap"
|
|
1402
|
+
},
|
|
1403
|
+
children: [
|
|
1404
|
+
label,
|
|
1405
|
+
(reporter == null ? void 0 : reporter.role) ? ` \xB7 ${reporter.role}` : ""
|
|
1406
|
+
]
|
|
1407
|
+
}
|
|
1408
|
+
)
|
|
1409
|
+
]
|
|
1410
|
+
}
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
920
1413
|
function getTheme(dark) {
|
|
921
1414
|
const hostAccent = detectHostAccent();
|
|
922
1415
|
const defaults = dark ? {
|
|
@@ -986,48 +1479,91 @@ function isLowQualityText(text) {
|
|
|
986
1479
|
}
|
|
987
1480
|
return null;
|
|
988
1481
|
}
|
|
1482
|
+
async function captureViaHtml2Canvas() {
|
|
1483
|
+
try {
|
|
1484
|
+
const { default: html2canvas } = await import("html2canvas-pro");
|
|
1485
|
+
const scale = Math.min(Math.max(window.devicePixelRatio || 1, 1), 2);
|
|
1486
|
+
const canvas = await html2canvas(document.body, {
|
|
1487
|
+
scale,
|
|
1488
|
+
logging: false,
|
|
1489
|
+
useCORS: true,
|
|
1490
|
+
allowTaint: true,
|
|
1491
|
+
x: window.scrollX,
|
|
1492
|
+
y: window.scrollY,
|
|
1493
|
+
width: window.innerWidth,
|
|
1494
|
+
height: window.innerHeight,
|
|
1495
|
+
windowWidth: window.innerWidth,
|
|
1496
|
+
windowHeight: window.innerHeight
|
|
1497
|
+
});
|
|
1498
|
+
return encodeScreenshot(canvas);
|
|
1499
|
+
} catch (e) {
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
989
1503
|
function ReportDialog({
|
|
990
1504
|
report,
|
|
1505
|
+
sendFeedback: sendFeedback2,
|
|
991
1506
|
enhanceText: enhanceText2,
|
|
992
1507
|
transcribeAudio: transcribeAudio2,
|
|
993
1508
|
types,
|
|
994
|
-
showSeverity = true
|
|
1509
|
+
showSeverity = true,
|
|
1510
|
+
captureScreenshot = captureViaHtml2Canvas,
|
|
1511
|
+
reporter
|
|
995
1512
|
}) {
|
|
996
|
-
const [isEnhancing, setIsEnhancing] = (0,
|
|
997
|
-
const [isEnhanced, setIsEnhanced] = (0,
|
|
998
|
-
const [originalDescription, setOriginalDescription] = (0,
|
|
1513
|
+
const [isEnhancing, setIsEnhancing] = (0, import_react.useState)(false);
|
|
1514
|
+
const [isEnhanced, setIsEnhanced] = (0, import_react.useState)(false);
|
|
1515
|
+
const [originalDescription, setOriginalDescription] = (0, import_react.useState)(
|
|
999
1516
|
null
|
|
1000
1517
|
);
|
|
1001
|
-
const [isListening, setIsListening] = (0,
|
|
1002
|
-
const [isTranscribing, setIsTranscribing] = (0,
|
|
1003
|
-
const [voiceError, setVoiceError] = (0,
|
|
1004
|
-
const [screenshotDragOver, setScreenshotDragOver] = (0,
|
|
1005
|
-
const [mounted, setMounted] = (0,
|
|
1006
|
-
(0,
|
|
1518
|
+
const [isListening, setIsListening] = (0, import_react.useState)(false);
|
|
1519
|
+
const [isTranscribing, setIsTranscribing] = (0, import_react.useState)(false);
|
|
1520
|
+
const [voiceError, setVoiceError] = (0, import_react.useState)(null);
|
|
1521
|
+
const [screenshotDragOver, setScreenshotDragOver] = (0, import_react.useState)(false);
|
|
1522
|
+
const [mounted, setMounted] = (0, import_react.useState)(false);
|
|
1523
|
+
(0, import_react.useEffect)(() => {
|
|
1007
1524
|
setMounted(true);
|
|
1008
1525
|
}, []);
|
|
1009
|
-
const [isOpen, setIsOpen] = (0,
|
|
1010
|
-
const [description, setDescription] = (0,
|
|
1011
|
-
const [isSubmitting, setIsSubmitting] = (0,
|
|
1012
|
-
const [submitted, setSubmitted] = (0,
|
|
1013
|
-
const [screenshots, setScreenshots] = (0,
|
|
1014
|
-
const [attachments, setAttachments] = (0,
|
|
1015
|
-
const [previewIndex, setPreviewIndex] = (0,
|
|
1016
|
-
const [annotatingIndex, setAnnotatingIndex] = (0,
|
|
1017
|
-
const
|
|
1018
|
-
const
|
|
1019
|
-
const
|
|
1020
|
-
const
|
|
1021
|
-
const
|
|
1022
|
-
const
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
|
|
1526
|
+
const [isOpen, setIsOpen] = (0, import_react.useState)(false);
|
|
1527
|
+
const [description, setDescription] = (0, import_react.useState)("");
|
|
1528
|
+
const [isSubmitting, setIsSubmitting] = (0, import_react.useState)(false);
|
|
1529
|
+
const [submitted, setSubmitted] = (0, import_react.useState)(false);
|
|
1530
|
+
const [screenshots, setScreenshots] = (0, import_react.useState)([]);
|
|
1531
|
+
const [attachments, setAttachments] = (0, import_react.useState)([]);
|
|
1532
|
+
const [previewIndex, setPreviewIndex] = (0, import_react.useState)(null);
|
|
1533
|
+
const [annotatingIndex, setAnnotatingIndex] = (0, import_react.useState)(null);
|
|
1534
|
+
const [previewZoomed, setPreviewZoomed] = (0, import_react.useState)(false);
|
|
1535
|
+
const [previewNatural, setPreviewNatural] = (0, import_react.useState)(null);
|
|
1536
|
+
const previewScrollRef = (0, import_react.useRef)(null);
|
|
1537
|
+
const previewImgElRef = (0, import_react.useRef)(null);
|
|
1538
|
+
const panRef = (0, import_react.useRef)(null);
|
|
1539
|
+
const zoomAnchorRef = (0, import_react.useRef)(null);
|
|
1540
|
+
const resetPreviewZoom = () => {
|
|
1541
|
+
setPreviewZoomed(false);
|
|
1542
|
+
setPreviewNatural(null);
|
|
1543
|
+
zoomAnchorRef.current = null;
|
|
1544
|
+
};
|
|
1545
|
+
const openPreview = (i) => {
|
|
1546
|
+
resetPreviewZoom();
|
|
1547
|
+
setPreviewIndex(i);
|
|
1548
|
+
};
|
|
1549
|
+
const closePreview = () => {
|
|
1550
|
+
resetPreviewZoom();
|
|
1551
|
+
setPreviewIndex(null);
|
|
1552
|
+
};
|
|
1553
|
+
const fileInputRef = (0, import_react.useRef)(null);
|
|
1554
|
+
const modalRef = (0, import_react.useRef)(null);
|
|
1555
|
+
const previewImgRef = (0, import_react.useRef)(null);
|
|
1556
|
+
const textareaRef = (0, import_react.useRef)(null);
|
|
1557
|
+
const recognitionRef = (0, import_react.useRef)(null);
|
|
1558
|
+
const voiceBaseRef = (0, import_react.useRef)("");
|
|
1559
|
+
const usingWebSpeechRef = (0, import_react.useRef)(false);
|
|
1560
|
+
const textBeforeVoiceRef = (0, import_react.useRef)("");
|
|
1561
|
+
const mediaRecorderRef = (0, import_react.useRef)(null);
|
|
1562
|
+
const streamRef = (0, import_react.useRef)(null);
|
|
1563
|
+
const sarvamChunksRef = (0, import_react.useRef)([]);
|
|
1564
|
+
const spaceTimerRef = (0, import_react.useRef)(null);
|
|
1565
|
+
const isPushToTalkRef = (0, import_react.useRef)(false);
|
|
1566
|
+
(0, import_react.useEffect)(() => {
|
|
1031
1567
|
if (!isOpen) return;
|
|
1032
1568
|
const dialogs = Array.from(
|
|
1033
1569
|
document.querySelectorAll(
|
|
@@ -1037,22 +1573,69 @@ function ReportDialog({
|
|
|
1037
1573
|
dialogs.forEach((d) => d.setAttribute("inert", ""));
|
|
1038
1574
|
return () => dialogs.forEach((d) => d.removeAttribute("inert"));
|
|
1039
1575
|
}, [isOpen]);
|
|
1040
|
-
const [step, setStep] = (0,
|
|
1041
|
-
const [reportType, setReportType] = (0,
|
|
1042
|
-
const [severity, setSeverity] = (0,
|
|
1043
|
-
const [validationError, setValidationError] = (0,
|
|
1044
|
-
const [placeholderIdx, setPlaceholderIdx] = (0,
|
|
1576
|
+
const [step, setStep] = (0, import_react.useState)(1);
|
|
1577
|
+
const [reportType, setReportType] = (0, import_react.useState)("BUG");
|
|
1578
|
+
const [severity, setSeverity] = (0, import_react.useState)("medium");
|
|
1579
|
+
const [validationError, setValidationError] = (0, import_react.useState)(null);
|
|
1580
|
+
const [placeholderIdx, setPlaceholderIdx] = (0, import_react.useState)(0);
|
|
1581
|
+
const [rating, setRating] = (0, import_react.useState)(0);
|
|
1582
|
+
const [hoveredStar, setHoveredStar] = (0, import_react.useState)(0);
|
|
1583
|
+
const isRating = reportType === "RATING";
|
|
1584
|
+
const submitDisabled = isSubmitting || (isRating ? rating < 1 : !description.trim());
|
|
1045
1585
|
const availableTypes = types != null ? types : [
|
|
1046
1586
|
"BUG",
|
|
1047
1587
|
"FEATURE_REQUEST",
|
|
1048
1588
|
"UI_IMPROVEMENT",
|
|
1049
1589
|
"PERFORMANCE",
|
|
1050
1590
|
"SECURITY",
|
|
1051
|
-
"QUESTION"
|
|
1052
|
-
"OTHER"
|
|
1591
|
+
"QUESTION"
|
|
1053
1592
|
];
|
|
1593
|
+
const tileGridRef = (0, import_react.useRef)(null);
|
|
1594
|
+
(0, import_react.useEffect)(() => {
|
|
1595
|
+
if (!isOpen || step !== 1) return;
|
|
1596
|
+
const onKey = (e) => {
|
|
1597
|
+
const el = e.target;
|
|
1598
|
+
const tag = el == null ? void 0 : el.tagName;
|
|
1599
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || (el == null ? void 0 : el.isContentEditable)) return;
|
|
1600
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
1601
|
+
if (sendFeedback2 && /^[1-5]$/.test(e.key)) {
|
|
1602
|
+
e.preventDefault();
|
|
1603
|
+
setRating(Number(e.key));
|
|
1604
|
+
setReportType("RATING");
|
|
1605
|
+
setStep(2);
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
const letter = e.key.toLowerCase();
|
|
1609
|
+
if (!/^[a-z]$/.test(letter)) return;
|
|
1610
|
+
const match = availableTypes.find(
|
|
1611
|
+
(type) => getTypeLabel(type).toLowerCase().startsWith(letter)
|
|
1612
|
+
);
|
|
1613
|
+
if (match) {
|
|
1614
|
+
e.preventDefault();
|
|
1615
|
+
setReportType(match);
|
|
1616
|
+
setStep(2);
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
window.addEventListener("keydown", onKey);
|
|
1620
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1621
|
+
}, [isOpen, step, sendFeedback2, availableTypes.join(",")]);
|
|
1622
|
+
const handleTileGridKeyDown = (e) => {
|
|
1623
|
+
var _a, _b, _c;
|
|
1624
|
+
const keys = ["ArrowRight", "ArrowLeft", "ArrowDown", "ArrowUp"];
|
|
1625
|
+
if (!keys.includes(e.key)) return;
|
|
1626
|
+
const tiles = Array.from(
|
|
1627
|
+
(_b = (_a = tileGridRef.current) == null ? void 0 : _a.querySelectorAll("button[data-gg-type]")) != null ? _b : []
|
|
1628
|
+
);
|
|
1629
|
+
if (tiles.length === 0) return;
|
|
1630
|
+
e.preventDefault();
|
|
1631
|
+
const current = tiles.indexOf(document.activeElement);
|
|
1632
|
+
const delta = e.key === "ArrowRight" ? 1 : e.key === "ArrowLeft" ? -1 : e.key === "ArrowDown" ? 2 : -2;
|
|
1633
|
+
const next = current === -1 ? 0 : Math.min(Math.max(current + delta, 0), tiles.length - 1);
|
|
1634
|
+
(_c = tiles[next]) == null ? void 0 : _c.focus();
|
|
1635
|
+
};
|
|
1054
1636
|
const isDark = useIsDark();
|
|
1055
1637
|
const t = getTheme(isDark);
|
|
1638
|
+
const canZoomPreview = previewNatural !== null && typeof window !== "undefined" && (previewNatural.w > window.innerWidth - 32 || previewNatural.h > window.innerHeight - 80);
|
|
1056
1639
|
const handleFileUpload = (e) => {
|
|
1057
1640
|
const files = e.target.files;
|
|
1058
1641
|
if (!files || files.length === 0) return;
|
|
@@ -1077,56 +1660,17 @@ function ReportDialog({
|
|
|
1077
1660
|
reader.readAsDataURL(file);
|
|
1078
1661
|
});
|
|
1079
1662
|
};
|
|
1080
|
-
const retakeScreenshot = async () => {
|
|
1081
|
-
setPreviewIndex(null);
|
|
1082
|
-
setIsOpen(false);
|
|
1083
|
-
setScreenshots([]);
|
|
1084
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
1085
|
-
try {
|
|
1086
|
-
const { default: html2canvas } = await import("html2canvas-pro");
|
|
1087
|
-
const canvas = await html2canvas(document.body, {
|
|
1088
|
-
scale: 0.5,
|
|
1089
|
-
logging: false,
|
|
1090
|
-
useCORS: true,
|
|
1091
|
-
allowTaint: true,
|
|
1092
|
-
x: window.scrollX,
|
|
1093
|
-
y: window.scrollY,
|
|
1094
|
-
width: window.innerWidth,
|
|
1095
|
-
height: window.innerHeight,
|
|
1096
|
-
windowWidth: window.innerWidth,
|
|
1097
|
-
windowHeight: window.innerHeight
|
|
1098
|
-
});
|
|
1099
|
-
setScreenshots([canvas.toDataURL("image/jpeg", 0.6)]);
|
|
1100
|
-
} catch (e) {
|
|
1101
|
-
}
|
|
1102
|
-
setIsOpen(true);
|
|
1103
|
-
};
|
|
1104
1663
|
const handleOpen = async () => {
|
|
1105
1664
|
setSubmitted(false);
|
|
1106
1665
|
if (availableTypes.length === 1) {
|
|
1107
1666
|
setReportType(availableTypes[0]);
|
|
1108
1667
|
setStep(2);
|
|
1109
1668
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
const canvas = await html2canvas(document.body, {
|
|
1113
|
-
scale: 0.5,
|
|
1114
|
-
logging: false,
|
|
1115
|
-
useCORS: true,
|
|
1116
|
-
allowTaint: true,
|
|
1117
|
-
x: window.scrollX,
|
|
1118
|
-
y: window.scrollY,
|
|
1119
|
-
width: window.innerWidth,
|
|
1120
|
-
height: window.innerHeight,
|
|
1121
|
-
windowWidth: window.innerWidth,
|
|
1122
|
-
windowHeight: window.innerHeight
|
|
1123
|
-
});
|
|
1124
|
-
setScreenshots([canvas.toDataURL("image/jpeg", 0.6)]);
|
|
1125
|
-
} catch (e) {
|
|
1126
|
-
}
|
|
1669
|
+
const shot = await captureScreenshot();
|
|
1670
|
+
if (shot) setScreenshots([shot]);
|
|
1127
1671
|
setIsOpen(true);
|
|
1128
1672
|
};
|
|
1129
|
-
(0,
|
|
1673
|
+
(0, import_react.useEffect)(() => {
|
|
1130
1674
|
const handler = (e) => {
|
|
1131
1675
|
if (isOpen) return;
|
|
1132
1676
|
const detail = e.detail;
|
|
@@ -1140,54 +1684,115 @@ function ReportDialog({
|
|
|
1140
1684
|
window.addEventListener("glitchgrab:open-report", handler);
|
|
1141
1685
|
return () => window.removeEventListener("glitchgrab:open-report", handler);
|
|
1142
1686
|
}, [isOpen, handleOpen]);
|
|
1143
|
-
(0,
|
|
1687
|
+
(0, import_react.useEffect)(() => {
|
|
1144
1688
|
if (!isOpen) return;
|
|
1145
1689
|
const handleEsc = (e) => {
|
|
1146
1690
|
if (e.key === "Escape") {
|
|
1147
|
-
if (
|
|
1691
|
+
if (previewZoomed) setPreviewZoomed(false);
|
|
1692
|
+
else if (previewIndex !== null) closePreview();
|
|
1148
1693
|
else handleClose();
|
|
1149
1694
|
}
|
|
1150
1695
|
};
|
|
1151
1696
|
document.addEventListener("keydown", handleEsc);
|
|
1152
1697
|
return () => document.removeEventListener("keydown", handleEsc);
|
|
1153
|
-
}, [isOpen, previewIndex]);
|
|
1154
|
-
(0,
|
|
1698
|
+
}, [isOpen, previewIndex, previewZoomed]);
|
|
1699
|
+
(0, import_react.useEffect)(() => {
|
|
1155
1700
|
if (previewIndex === null || annotatingIndex !== null) return;
|
|
1156
1701
|
const handlePointerDown = (e) => {
|
|
1157
1702
|
var _a;
|
|
1158
1703
|
if (!((_a = previewImgRef.current) == null ? void 0 : _a.contains(e.target))) {
|
|
1159
|
-
|
|
1704
|
+
closePreview();
|
|
1160
1705
|
}
|
|
1161
1706
|
};
|
|
1162
1707
|
document.addEventListener("pointerdown", handlePointerDown, true);
|
|
1163
1708
|
return () => document.removeEventListener("pointerdown", handlePointerDown, true);
|
|
1164
1709
|
}, [previewIndex, annotatingIndex]);
|
|
1165
|
-
(0,
|
|
1710
|
+
(0, import_react.useEffect)(() => {
|
|
1711
|
+
var _a;
|
|
1712
|
+
const el = previewScrollRef.current;
|
|
1713
|
+
if (!previewZoomed || !el) return;
|
|
1714
|
+
const anchor = (_a = zoomAnchorRef.current) != null ? _a : { fx: 0.5, fy: 0.5 };
|
|
1715
|
+
el.scrollLeft = anchor.fx * el.scrollWidth - el.clientWidth / 2;
|
|
1716
|
+
el.scrollTop = anchor.fy * el.scrollHeight - el.clientHeight / 2;
|
|
1717
|
+
}, [previewZoomed]);
|
|
1718
|
+
const togglePreviewZoom = (e) => {
|
|
1719
|
+
var _a;
|
|
1720
|
+
e.stopPropagation();
|
|
1721
|
+
if ((_a = panRef.current) == null ? void 0 : _a.moved) return;
|
|
1722
|
+
if (!previewZoomed && !canZoomPreview) return;
|
|
1723
|
+
const img = previewImgElRef.current;
|
|
1724
|
+
if (!previewZoomed && img) {
|
|
1725
|
+
const rect = img.getBoundingClientRect();
|
|
1726
|
+
zoomAnchorRef.current = {
|
|
1727
|
+
fx: (e.clientX - rect.left) / rect.width,
|
|
1728
|
+
fy: (e.clientY - rect.top) / rect.height
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
setPreviewZoomed((z) => !z);
|
|
1732
|
+
};
|
|
1733
|
+
const handlePanStart = (e) => {
|
|
1734
|
+
const el = previewScrollRef.current;
|
|
1735
|
+
if (!previewZoomed || !el) return;
|
|
1736
|
+
panRef.current = {
|
|
1737
|
+
x: e.clientX,
|
|
1738
|
+
y: e.clientY,
|
|
1739
|
+
left: el.scrollLeft,
|
|
1740
|
+
top: el.scrollTop,
|
|
1741
|
+
moved: false
|
|
1742
|
+
};
|
|
1743
|
+
el.setPointerCapture(e.pointerId);
|
|
1744
|
+
};
|
|
1745
|
+
const handlePanMove = (e) => {
|
|
1746
|
+
const el = previewScrollRef.current;
|
|
1747
|
+
const start = panRef.current;
|
|
1748
|
+
if (!el || !start) return;
|
|
1749
|
+
const dx = e.clientX - start.x;
|
|
1750
|
+
const dy = e.clientY - start.y;
|
|
1751
|
+
if (Math.abs(dx) > 4 || Math.abs(dy) > 4) start.moved = true;
|
|
1752
|
+
el.scrollLeft = start.left - dx;
|
|
1753
|
+
el.scrollTop = start.top - dy;
|
|
1754
|
+
};
|
|
1755
|
+
const handlePanEnd = (e) => {
|
|
1756
|
+
var _a, _b;
|
|
1757
|
+
const el = previewScrollRef.current;
|
|
1758
|
+
const moved = (_b = (_a = panRef.current) == null ? void 0 : _a.moved) != null ? _b : false;
|
|
1759
|
+
try {
|
|
1760
|
+
el == null ? void 0 : el.releasePointerCapture(e.pointerId);
|
|
1761
|
+
} catch (e2) {
|
|
1762
|
+
}
|
|
1763
|
+
if (moved) {
|
|
1764
|
+
panRef.current = { x: 0, y: 0, left: 0, top: 0, moved: true };
|
|
1765
|
+
setTimeout(() => {
|
|
1766
|
+
panRef.current = null;
|
|
1767
|
+
}, 0);
|
|
1768
|
+
} else {
|
|
1769
|
+
panRef.current = null;
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
(0, import_react.useEffect)(() => {
|
|
1166
1773
|
if (!isOpen) return;
|
|
1167
1774
|
const handlePaste = (e) => {
|
|
1168
1775
|
var _a;
|
|
1169
1776
|
try {
|
|
1170
1777
|
const items = (_a = e.clipboardData) == null ? void 0 : _a.items;
|
|
1171
1778
|
if (!items) return;
|
|
1172
|
-
|
|
1779
|
+
const pasted = [];
|
|
1173
1780
|
for (let i = 0; i < items.length; i++) {
|
|
1174
|
-
if (items[i].
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
}
|
|
1781
|
+
if (items[i].kind !== "file") continue;
|
|
1782
|
+
const file = items[i].getAsFile();
|
|
1783
|
+
if (file) pasted.push(file);
|
|
1784
|
+
}
|
|
1785
|
+
if (pasted.length > 0) {
|
|
1786
|
+
e.preventDefault();
|
|
1787
|
+
addFiles(pasted);
|
|
1182
1788
|
}
|
|
1183
|
-
if (matched) e.preventDefault();
|
|
1184
1789
|
} catch (e2) {
|
|
1185
1790
|
}
|
|
1186
1791
|
};
|
|
1187
1792
|
window.addEventListener("paste", handlePaste);
|
|
1188
1793
|
return () => window.removeEventListener("paste", handlePaste);
|
|
1189
1794
|
}, [isOpen]);
|
|
1190
|
-
(0,
|
|
1795
|
+
(0, import_react.useEffect)(() => {
|
|
1191
1796
|
if (!isOpen || !transcribeAudio2) return;
|
|
1192
1797
|
const id = setInterval(() => {
|
|
1193
1798
|
setPlaceholderIdx((i) => (i + 1) % 2);
|
|
@@ -1226,6 +1831,8 @@ function ReportDialog({
|
|
|
1226
1831
|
stopVoice();
|
|
1227
1832
|
setIsOpen(false);
|
|
1228
1833
|
setStep(1);
|
|
1834
|
+
setRating(0);
|
|
1835
|
+
setHoveredStar(0);
|
|
1229
1836
|
setReportType("BUG");
|
|
1230
1837
|
setSeverity("medium");
|
|
1231
1838
|
setValidationError(null);
|
|
@@ -1371,7 +1978,30 @@ function ReportDialog({
|
|
|
1371
1978
|
};
|
|
1372
1979
|
const handleSubmit = async () => {
|
|
1373
1980
|
try {
|
|
1374
|
-
if (
|
|
1981
|
+
if (isSubmitting) return;
|
|
1982
|
+
if (isRating) {
|
|
1983
|
+
if (rating < 1) {
|
|
1984
|
+
setValidationError("Pick a star rating first.");
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1987
|
+
if (!sendFeedback2) return;
|
|
1988
|
+
setIsSubmitting(true);
|
|
1989
|
+
const result2 = await sendFeedback2(rating, description.trim() || void 0);
|
|
1990
|
+
if (result2 == null ? void 0 : result2.success) {
|
|
1991
|
+
setSubmitted(true);
|
|
1992
|
+
setDescription("");
|
|
1993
|
+
setRating(0);
|
|
1994
|
+
setTimeout(() => {
|
|
1995
|
+
setSubmitted(false);
|
|
1996
|
+
handleClose();
|
|
1997
|
+
}, 2e3);
|
|
1998
|
+
} else {
|
|
1999
|
+
setValidationError("Could not send \u2014 please try again.");
|
|
2000
|
+
}
|
|
2001
|
+
setIsSubmitting(false);
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
if (!description.trim()) return;
|
|
1375
2005
|
const qualityError = isLowQualityText(description);
|
|
1376
2006
|
if (qualityError) {
|
|
1377
2007
|
setValidationError(qualityError);
|
|
@@ -1386,6 +2016,8 @@ function ReportDialog({
|
|
|
1386
2016
|
metadata.severity = severity;
|
|
1387
2017
|
}
|
|
1388
2018
|
const result = await report(
|
|
2019
|
+
// The RATING branch returned above, so anything reaching here is a real
|
|
2020
|
+
// ReportType — the widened tile union can't leak into the report API.
|
|
1389
2021
|
reportType,
|
|
1390
2022
|
description.trim(),
|
|
1391
2023
|
Object.keys(metadata).length > 0 ? metadata : void 0
|
|
@@ -1429,6 +2061,18 @@ function ReportDialog({
|
|
|
1429
2061
|
button[data-gg-type="SECURITY"] > svg{animation:gg-shield-pulse 2.2s ease-in-out infinite;transform-origin:top}
|
|
1430
2062
|
button[data-gg-type="QUESTION"] > svg{animation:gg-question-shake 2s ease-in-out infinite}
|
|
1431
2063
|
button[data-gg-type="OTHER"] > svg{animation:gg-chat-tilt 2.2s ease-in-out infinite}
|
|
2064
|
+
/* Lift on hover, settle on press. Cheaper and steadier than doing it
|
|
2065
|
+
through React state, and :focus-visible keeps the ring off mouse users
|
|
2066
|
+
while leaving it for the arrow-key path. */
|
|
2067
|
+
button[data-gg-type]{transition:transform .15s ease,border-color .15s ease,box-shadow .15s ease,background-color .15s ease}
|
|
2068
|
+
button[data-gg-type]:hover{transform:translateY(-2px);box-shadow:0 6px 16px rgba(0,0,0,.18)}
|
|
2069
|
+
button[data-gg-type]:active{transform:translateY(0) scale(.985);box-shadow:none}
|
|
2070
|
+
button[data-gg-type]:focus-visible{outline:2px solid ${t.accent};outline-offset:2px}
|
|
2071
|
+
@keyframes gg-star-pop{0%{transform:scale(.6);opacity:0}60%{transform:scale(1.12)}100%{transform:scale(1);opacity:1}}
|
|
2072
|
+
button[data-gg-star]{transition:transform .12s ease}
|
|
2073
|
+
button[data-gg-star]:hover{transform:scale(1.18)}
|
|
2074
|
+
button[data-gg-star]:active{transform:scale(.94)}
|
|
2075
|
+
button[data-gg-star]:focus-visible{outline:2px solid #f59e0b;outline-offset:3px;border-radius:4px}
|
|
1432
2076
|
` }),
|
|
1433
2077
|
document.body
|
|
1434
2078
|
),
|
|
@@ -1460,7 +2104,7 @@ function ReportDialog({
|
|
|
1460
2104
|
style: {
|
|
1461
2105
|
position: "relative",
|
|
1462
2106
|
zIndex: 2147483647,
|
|
1463
|
-
width: "
|
|
2107
|
+
width: "420px",
|
|
1464
2108
|
maxWidth: "calc(100% - 32px)",
|
|
1465
2109
|
maxHeight: "calc(100dvh - 32px)",
|
|
1466
2110
|
display: "flex",
|
|
@@ -1670,7 +2314,7 @@ function ReportDialog({
|
|
|
1670
2314
|
"div",
|
|
1671
2315
|
{
|
|
1672
2316
|
style: {
|
|
1673
|
-
padding: "
|
|
2317
|
+
padding: "18px",
|
|
1674
2318
|
overflowY: "auto",
|
|
1675
2319
|
flex: "1 1 auto",
|
|
1676
2320
|
minHeight: 0
|
|
@@ -1691,13 +2335,145 @@ function ReportDialog({
|
|
|
1691
2335
|
]
|
|
1692
2336
|
}
|
|
1693
2337
|
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
2338
|
+
step === 1 && sendFeedback2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2339
|
+
"div",
|
|
2340
|
+
{
|
|
2341
|
+
style: {
|
|
2342
|
+
border: "1px solid rgba(245,158,11,0.35)",
|
|
2343
|
+
background: "rgba(245,158,11,0.07)",
|
|
2344
|
+
borderRadius: "10px",
|
|
2345
|
+
padding: "14px 16px",
|
|
2346
|
+
marginBottom: "16px",
|
|
2347
|
+
display: "flex",
|
|
2348
|
+
alignItems: "center",
|
|
2349
|
+
justifyContent: "space-between",
|
|
2350
|
+
gap: "12px",
|
|
2351
|
+
flexWrap: "wrap"
|
|
2352
|
+
},
|
|
2353
|
+
onMouseLeave: () => setHoveredStar(0),
|
|
2354
|
+
children: [
|
|
2355
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2356
|
+
"span",
|
|
2357
|
+
{
|
|
2358
|
+
style: { fontSize: "13px", fontWeight: 600, color: t.text },
|
|
2359
|
+
children: "How are we doing?"
|
|
2360
|
+
}
|
|
2361
|
+
),
|
|
2362
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { display: "flex", gap: "4px" }, children: [1, 2, 3, 4, 5].map((star) => {
|
|
2363
|
+
const filled = star <= (hoveredStar || rating);
|
|
2364
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2365
|
+
"button",
|
|
2366
|
+
{
|
|
2367
|
+
type: "button",
|
|
2368
|
+
"data-gg-star": star,
|
|
2369
|
+
"aria-label": `${star} star${star > 1 ? "s" : ""}`,
|
|
2370
|
+
onMouseEnter: () => setHoveredStar(star),
|
|
2371
|
+
onFocus: () => setHoveredStar(star),
|
|
2372
|
+
onClick: () => {
|
|
2373
|
+
setRating(star);
|
|
2374
|
+
setReportType("RATING");
|
|
2375
|
+
setStep(2);
|
|
2376
|
+
},
|
|
2377
|
+
style: {
|
|
2378
|
+
border: "none",
|
|
2379
|
+
background: "transparent",
|
|
2380
|
+
padding: 0,
|
|
2381
|
+
cursor: "pointer",
|
|
2382
|
+
lineHeight: 0
|
|
2383
|
+
},
|
|
2384
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "26", height: "26", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2385
|
+
"path",
|
|
2386
|
+
{
|
|
2387
|
+
d: "M12 3l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.4 6.8 19.1l1-5.8L3.5 9.2l5.9-.9L12 3z",
|
|
2388
|
+
fill: filled ? "#f59e0b" : "transparent",
|
|
2389
|
+
stroke: "#f59e0b",
|
|
2390
|
+
strokeWidth: "1.5",
|
|
2391
|
+
strokeLinejoin: "round"
|
|
2392
|
+
}
|
|
2393
|
+
) })
|
|
2394
|
+
},
|
|
2395
|
+
star
|
|
2396
|
+
);
|
|
2397
|
+
}) })
|
|
2398
|
+
]
|
|
2399
|
+
}
|
|
2400
|
+
),
|
|
2401
|
+
step === 1 && sendFeedback2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2402
|
+
"div",
|
|
2403
|
+
{
|
|
2404
|
+
style: {
|
|
2405
|
+
display: "flex",
|
|
2406
|
+
alignItems: "center",
|
|
2407
|
+
gap: "10px",
|
|
2408
|
+
marginBottom: "16px"
|
|
2409
|
+
},
|
|
2410
|
+
children: [
|
|
2411
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { flex: 1, height: "1px", background: t.border } }),
|
|
2412
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: "11px", color: t.textMuted }, children: "or report something" }),
|
|
2413
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { flex: 1, height: "1px", background: t.border } })
|
|
2414
|
+
]
|
|
2415
|
+
}
|
|
2416
|
+
),
|
|
2417
|
+
step === 1 && screenshots.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2418
|
+
"div",
|
|
2419
|
+
{
|
|
2420
|
+
style: {
|
|
2421
|
+
display: "flex",
|
|
2422
|
+
alignItems: "center",
|
|
2423
|
+
gap: "10px",
|
|
2424
|
+
marginBottom: "14px",
|
|
2425
|
+
padding: "8px 10px",
|
|
2426
|
+
border: `1px solid ${t.inputBorder}`,
|
|
2427
|
+
borderRadius: "8px",
|
|
2428
|
+
background: t.inputBg
|
|
2429
|
+
},
|
|
2430
|
+
children: [
|
|
2431
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2432
|
+
"img",
|
|
2433
|
+
{
|
|
2434
|
+
src: screenshots[0],
|
|
2435
|
+
alt: "",
|
|
2436
|
+
style: {
|
|
2437
|
+
width: "34px",
|
|
2438
|
+
height: "24px",
|
|
2439
|
+
objectFit: "cover",
|
|
2440
|
+
borderRadius: "4px",
|
|
2441
|
+
border: `1px solid ${t.border}`,
|
|
2442
|
+
flexShrink: 0
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
),
|
|
2446
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: "11px", color: t.textMuted, flex: 1 }, children: "Screenshot attached" }),
|
|
2447
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2448
|
+
"button",
|
|
2449
|
+
{
|
|
2450
|
+
type: "button",
|
|
2451
|
+
onClick: () => setScreenshots([]),
|
|
2452
|
+
"aria-label": "Remove screenshot",
|
|
2453
|
+
style: {
|
|
2454
|
+
border: "none",
|
|
2455
|
+
background: "transparent",
|
|
2456
|
+
color: t.textMuted,
|
|
2457
|
+
cursor: "pointer",
|
|
2458
|
+
fontSize: "15px",
|
|
2459
|
+
lineHeight: 1,
|
|
2460
|
+
padding: "2px 4px"
|
|
2461
|
+
},
|
|
2462
|
+
children: "\xD7"
|
|
2463
|
+
}
|
|
2464
|
+
)
|
|
2465
|
+
]
|
|
2466
|
+
}
|
|
2467
|
+
),
|
|
1694
2468
|
step === 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1695
2469
|
"div",
|
|
1696
2470
|
{
|
|
2471
|
+
ref: tileGridRef,
|
|
2472
|
+
onKeyDown: handleTileGridKeyDown,
|
|
1697
2473
|
style: {
|
|
1698
2474
|
display: "grid",
|
|
1699
|
-
gridTemplateColumns: "1fr 1fr",
|
|
1700
|
-
gap: "
|
|
2475
|
+
gridTemplateColumns: "1fr 1fr 1fr",
|
|
2476
|
+
gap: "10px"
|
|
1701
2477
|
},
|
|
1702
2478
|
children: availableTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1703
2479
|
"button",
|
|
@@ -1712,15 +2488,14 @@ function ReportDialog({
|
|
|
1712
2488
|
display: "flex",
|
|
1713
2489
|
flexDirection: "column",
|
|
1714
2490
|
alignItems: "center",
|
|
1715
|
-
gap: "
|
|
1716
|
-
padding: "
|
|
2491
|
+
gap: "9px",
|
|
2492
|
+
padding: "18px 10px",
|
|
1717
2493
|
borderRadius: "8px",
|
|
1718
2494
|
border: `1px solid ${t.inputBorder}`,
|
|
1719
2495
|
background: t.inputBg,
|
|
1720
2496
|
cursor: "pointer",
|
|
1721
2497
|
color: t.text,
|
|
1722
|
-
fontFamily: "inherit"
|
|
1723
|
-
transition: "border-color 0.15s ease"
|
|
2498
|
+
fontFamily: "inherit"
|
|
1724
2499
|
},
|
|
1725
2500
|
onMouseEnter: (e) => {
|
|
1726
2501
|
e.currentTarget.style.borderColor = t.accent;
|
|
@@ -1729,17 +2504,17 @@ function ReportDialog({
|
|
|
1729
2504
|
e.currentTarget.style.borderColor = t.inputBorder;
|
|
1730
2505
|
},
|
|
1731
2506
|
children: [
|
|
1732
|
-
getTypeIcon(type, t.accent),
|
|
1733
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: "13px", fontWeight: 600 }, children: getTypeLabel(type) }),
|
|
2507
|
+
getTypeIcon(type, t.accent, 22),
|
|
1734
2508
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1735
2509
|
"span",
|
|
1736
2510
|
{
|
|
1737
2511
|
style: {
|
|
1738
|
-
fontSize: "
|
|
1739
|
-
|
|
1740
|
-
|
|
2512
|
+
fontSize: "12px",
|
|
2513
|
+
fontWeight: 600,
|
|
2514
|
+
textAlign: "center",
|
|
2515
|
+
lineHeight: "1.2"
|
|
1741
2516
|
},
|
|
1742
|
-
children:
|
|
2517
|
+
children: getTypeLabel(type)
|
|
1743
2518
|
}
|
|
1744
2519
|
)
|
|
1745
2520
|
]
|
|
@@ -1749,6 +2524,77 @@ function ReportDialog({
|
|
|
1749
2524
|
}
|
|
1750
2525
|
),
|
|
1751
2526
|
step === 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
2527
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2528
|
+
"button",
|
|
2529
|
+
{
|
|
2530
|
+
type: "button",
|
|
2531
|
+
onClick: () => setStep(1),
|
|
2532
|
+
style: {
|
|
2533
|
+
display: "inline-flex",
|
|
2534
|
+
alignItems: "center",
|
|
2535
|
+
gap: "6px",
|
|
2536
|
+
marginBottom: "10px",
|
|
2537
|
+
padding: "4px 10px 4px 8px",
|
|
2538
|
+
borderRadius: "999px",
|
|
2539
|
+
border: `1px solid ${isRating ? "rgba(245,158,11,0.4)" : t.inputBorder}`,
|
|
2540
|
+
background: isRating ? "rgba(245,158,11,0.1)" : t.inputBg,
|
|
2541
|
+
color: t.text,
|
|
2542
|
+
fontSize: "11px",
|
|
2543
|
+
fontWeight: 600,
|
|
2544
|
+
fontFamily: "inherit",
|
|
2545
|
+
cursor: "pointer"
|
|
2546
|
+
},
|
|
2547
|
+
children: [
|
|
2548
|
+
getTypeIcon(reportType, isRating ? "#f59e0b" : t.accent, 13),
|
|
2549
|
+
getTypeLabel(reportType),
|
|
2550
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: t.textMuted, fontWeight: 400 }, children: "\xB7 change" })
|
|
2551
|
+
]
|
|
2552
|
+
}
|
|
2553
|
+
),
|
|
2554
|
+
isRating && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2555
|
+
"div",
|
|
2556
|
+
{
|
|
2557
|
+
style: {
|
|
2558
|
+
display: "flex",
|
|
2559
|
+
gap: "6px",
|
|
2560
|
+
marginBottom: "12px"
|
|
2561
|
+
},
|
|
2562
|
+
onMouseLeave: () => setHoveredStar(0),
|
|
2563
|
+
children: [1, 2, 3, 4, 5].map((star) => {
|
|
2564
|
+
const filled = star <= (hoveredStar || rating);
|
|
2565
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2566
|
+
"button",
|
|
2567
|
+
{
|
|
2568
|
+
type: "button",
|
|
2569
|
+
"aria-label": `${star} star${star > 1 ? "s" : ""}`,
|
|
2570
|
+
onClick: () => {
|
|
2571
|
+
setRating(star);
|
|
2572
|
+
if (validationError) setValidationError(null);
|
|
2573
|
+
},
|
|
2574
|
+
onMouseEnter: () => setHoveredStar(star),
|
|
2575
|
+
style: {
|
|
2576
|
+
border: "none",
|
|
2577
|
+
background: "transparent",
|
|
2578
|
+
padding: 0,
|
|
2579
|
+
cursor: "pointer",
|
|
2580
|
+
lineHeight: 0
|
|
2581
|
+
},
|
|
2582
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "30", height: "30", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2583
|
+
"path",
|
|
2584
|
+
{
|
|
2585
|
+
d: "M12 3l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.4 6.8 19.1l1-5.8L3.5 9.2l5.9-.9L12 3z",
|
|
2586
|
+
fill: filled ? "#f59e0b" : "transparent",
|
|
2587
|
+
stroke: filled ? "#f59e0b" : t.inputBorder,
|
|
2588
|
+
strokeWidth: "1.5",
|
|
2589
|
+
strokeLinejoin: "round"
|
|
2590
|
+
}
|
|
2591
|
+
) })
|
|
2592
|
+
},
|
|
2593
|
+
star
|
|
2594
|
+
);
|
|
2595
|
+
})
|
|
2596
|
+
}
|
|
2597
|
+
),
|
|
1752
2598
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative" }, children: [
|
|
1753
2599
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1754
2600
|
"textarea",
|
|
@@ -2147,7 +2993,7 @@ function ReportDialog({
|
|
|
2147
2993
|
children: voiceError
|
|
2148
2994
|
}
|
|
2149
2995
|
),
|
|
2150
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginTop: "10px" }, children: [
|
|
2996
|
+
!isRating && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginTop: "10px" }, children: [
|
|
2151
2997
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2152
2998
|
"span",
|
|
2153
2999
|
{
|
|
@@ -2187,7 +3033,7 @@ function ReportDialog({
|
|
|
2187
3033
|
{
|
|
2188
3034
|
src,
|
|
2189
3035
|
alt: `Screenshot ${i + 1}`,
|
|
2190
|
-
onClick: () =>
|
|
3036
|
+
onClick: () => openPreview(i),
|
|
2191
3037
|
style: {
|
|
2192
3038
|
width: "100%",
|
|
2193
3039
|
height: "100%",
|
|
@@ -2591,37 +3437,54 @@ function ReportDialog({
|
|
|
2591
3437
|
{
|
|
2592
3438
|
type: "button",
|
|
2593
3439
|
onClick: handleSubmit,
|
|
2594
|
-
disabled:
|
|
3440
|
+
disabled: submitDisabled,
|
|
2595
3441
|
style: {
|
|
2596
3442
|
marginTop: "12px",
|
|
2597
3443
|
width: "100%",
|
|
2598
3444
|
padding: "10px",
|
|
2599
3445
|
borderRadius: "8px",
|
|
2600
3446
|
border: "none",
|
|
2601
|
-
backgroundColor:
|
|
2602
|
-
color:
|
|
3447
|
+
backgroundColor: submitDisabled ? t.bgSecondary : t.accent,
|
|
3448
|
+
color: submitDisabled ? t.textMuted : t.accentText,
|
|
2603
3449
|
fontSize: "14px",
|
|
2604
3450
|
fontWeight: 600,
|
|
2605
|
-
cursor:
|
|
3451
|
+
cursor: submitDisabled ? "not-allowed" : "pointer",
|
|
2606
3452
|
fontFamily: "inherit",
|
|
2607
3453
|
transition: "background-color 0.15s ease"
|
|
2608
3454
|
},
|
|
2609
|
-
children: isSubmitting ? "Sending..." : "Send Report"
|
|
3455
|
+
children: isSubmitting ? "Sending..." : isRating ? "Send Rating" : "Send Report"
|
|
2610
3456
|
}
|
|
2611
3457
|
)
|
|
2612
3458
|
] })
|
|
2613
3459
|
] })
|
|
2614
3460
|
}
|
|
2615
3461
|
),
|
|
2616
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
3462
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2617
3463
|
"div",
|
|
2618
3464
|
{
|
|
2619
3465
|
style: {
|
|
2620
3466
|
padding: "8px 16px 10px",
|
|
2621
3467
|
borderTop: `1px solid ${t.border}`,
|
|
2622
|
-
|
|
3468
|
+
display: "flex",
|
|
3469
|
+
alignItems: "center",
|
|
3470
|
+
justifyContent: "space-between",
|
|
3471
|
+
gap: "12px"
|
|
2623
3472
|
},
|
|
2624
|
-
children:
|
|
3473
|
+
children: [
|
|
3474
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReporterChip, { reporter, t }),
|
|
3475
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
3476
|
+
"span",
|
|
3477
|
+
{
|
|
3478
|
+
style: {
|
|
3479
|
+
fontSize: "11px",
|
|
3480
|
+
color: t.textMuted,
|
|
3481
|
+
whiteSpace: "nowrap",
|
|
3482
|
+
flexShrink: 0
|
|
3483
|
+
},
|
|
3484
|
+
children: "Powered by Glitchgrab"
|
|
3485
|
+
}
|
|
3486
|
+
)
|
|
3487
|
+
]
|
|
2625
3488
|
}
|
|
2626
3489
|
)
|
|
2627
3490
|
]
|
|
@@ -2636,7 +3499,7 @@ function ReportDialog({
|
|
|
2636
3499
|
{
|
|
2637
3500
|
ref: fileInputRef,
|
|
2638
3501
|
type: "file",
|
|
2639
|
-
accept:
|
|
3502
|
+
accept: ATTACHMENT_ACCEPT,
|
|
2640
3503
|
multiple: true,
|
|
2641
3504
|
onChange: handleFileUpload,
|
|
2642
3505
|
style: { display: "none" }
|
|
@@ -2660,7 +3523,7 @@ function ReportDialog({
|
|
|
2660
3523
|
},
|
|
2661
3524
|
onClick: (e) => {
|
|
2662
3525
|
e.stopPropagation();
|
|
2663
|
-
|
|
3526
|
+
closePreview();
|
|
2664
3527
|
},
|
|
2665
3528
|
onMouseDown: (e) => e.stopPropagation(),
|
|
2666
3529
|
children: [
|
|
@@ -2668,22 +3531,59 @@ function ReportDialog({
|
|
|
2668
3531
|
"div",
|
|
2669
3532
|
{
|
|
2670
3533
|
ref: previewImgRef,
|
|
2671
|
-
style: {
|
|
3534
|
+
style: {
|
|
3535
|
+
position: "relative",
|
|
3536
|
+
display: "flex",
|
|
3537
|
+
maxWidth: "100%",
|
|
3538
|
+
maxHeight: "calc(100vh - 80px)"
|
|
3539
|
+
},
|
|
2672
3540
|
onClick: (e) => e.stopPropagation(),
|
|
2673
3541
|
children: [
|
|
2674
3542
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2675
|
-
"
|
|
3543
|
+
"div",
|
|
2676
3544
|
{
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
3545
|
+
ref: previewScrollRef,
|
|
3546
|
+
onPointerDown: handlePanStart,
|
|
3547
|
+
onPointerMove: handlePanMove,
|
|
3548
|
+
onPointerUp: handlePanEnd,
|
|
3549
|
+
onPointerCancel: handlePanEnd,
|
|
3550
|
+
onClick: togglePreviewZoom,
|
|
3551
|
+
style: previewZoomed ? {
|
|
3552
|
+
// Fill the overlay so there is room to pan around a
|
|
3553
|
+
// full-resolution screenshot.
|
|
3554
|
+
width: "calc(100vw - 32px)",
|
|
3555
|
+
height: "calc(100vh - 80px)",
|
|
3556
|
+
overflow: "auto",
|
|
2682
3557
|
borderRadius: "8px",
|
|
2683
|
-
|
|
2684
|
-
cursor: "
|
|
2685
|
-
|
|
2686
|
-
|
|
3558
|
+
touchAction: "none",
|
|
3559
|
+
cursor: "grab"
|
|
3560
|
+
} : { maxWidth: "100%", lineHeight: 0 },
|
|
3561
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
3562
|
+
"img",
|
|
3563
|
+
{
|
|
3564
|
+
ref: previewImgElRef,
|
|
3565
|
+
src: screenshots[previewIndex],
|
|
3566
|
+
alt: "Screenshot preview",
|
|
3567
|
+
onLoad: (e) => setPreviewNatural({
|
|
3568
|
+
w: e.currentTarget.naturalWidth,
|
|
3569
|
+
h: e.currentTarget.naturalHeight
|
|
3570
|
+
}),
|
|
3571
|
+
style: previewZoomed ? {
|
|
3572
|
+
// Natural size — one image pixel per CSS pixel.
|
|
3573
|
+
maxWidth: "none",
|
|
3574
|
+
maxHeight: "none",
|
|
3575
|
+
display: "block",
|
|
3576
|
+
cursor: "zoom-out"
|
|
3577
|
+
} : {
|
|
3578
|
+
maxWidth: "100%",
|
|
3579
|
+
maxHeight: "calc(100vh - 80px)",
|
|
3580
|
+
borderRadius: "8px",
|
|
3581
|
+
objectFit: "contain",
|
|
3582
|
+
cursor: canZoomPreview ? "zoom-in" : "default",
|
|
3583
|
+
display: "block"
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
)
|
|
2687
3587
|
}
|
|
2688
3588
|
),
|
|
2689
3589
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
@@ -2747,7 +3647,11 @@ function ReportDialog({
|
|
|
2747
3647
|
color: t.textMuted,
|
|
2748
3648
|
fontSize: "12px"
|
|
2749
3649
|
},
|
|
2750
|
-
children:
|
|
3650
|
+
children: [
|
|
3651
|
+
screenshots.length > 1 ? `${previewIndex + 1} / ${screenshots.length}` : null,
|
|
3652
|
+
canZoomPreview ? previewZoomed ? "Drag to pan \xB7 click image to fit" : "Click image to zoom to full size" : null,
|
|
3653
|
+
previewZoomed ? "Esc to fit" : "Click outside to close"
|
|
3654
|
+
].filter(Boolean).join(" \xB7 ")
|
|
2751
3655
|
}
|
|
2752
3656
|
)
|
|
2753
3657
|
}
|
|
@@ -2777,6 +3681,8 @@ function ReportDialog({
|
|
|
2777
3681
|
}
|
|
2778
3682
|
function getTypeLabel(type) {
|
|
2779
3683
|
switch (type) {
|
|
3684
|
+
case "RATING":
|
|
3685
|
+
return "Rating";
|
|
2780
3686
|
case "BUG":
|
|
2781
3687
|
return "Bug Report";
|
|
2782
3688
|
case "FEATURE_REQUEST":
|
|
@@ -2793,25 +3699,8 @@ function getTypeLabel(type) {
|
|
|
2793
3699
|
return "Other";
|
|
2794
3700
|
}
|
|
2795
3701
|
}
|
|
2796
|
-
function getTypeSubtitle(type) {
|
|
2797
|
-
switch (type) {
|
|
2798
|
-
case "BUG":
|
|
2799
|
-
return "Something isn't working";
|
|
2800
|
-
case "FEATURE_REQUEST":
|
|
2801
|
-
return "Suggest an improvement";
|
|
2802
|
-
case "UI_IMPROVEMENT":
|
|
2803
|
-
return "Something looks off";
|
|
2804
|
-
case "PERFORMANCE":
|
|
2805
|
-
return "Something is slow";
|
|
2806
|
-
case "SECURITY":
|
|
2807
|
-
return "Report a security concern";
|
|
2808
|
-
case "QUESTION":
|
|
2809
|
-
return "Ask a question";
|
|
2810
|
-
case "OTHER":
|
|
2811
|
-
return "General feedback";
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
3702
|
function getPlaceholder(type, idx = 0, hasVoice = false) {
|
|
3703
|
+
if (type === "RATING") return "What made it good \u2014 or what let you down? (optional)";
|
|
2815
3704
|
if (hasVoice && idx === 1) return "Hold Space to speak \u2014 we'll transcribe it";
|
|
2816
3705
|
switch (type) {
|
|
2817
3706
|
case "BUG":
|
|
@@ -2839,6 +3728,16 @@ function getTypeIcon(type, color, size = 24) {
|
|
|
2839
3728
|
style: { flexShrink: 0 }
|
|
2840
3729
|
};
|
|
2841
3730
|
switch (type) {
|
|
3731
|
+
case "RATING":
|
|
3732
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
3733
|
+
"path",
|
|
3734
|
+
{
|
|
3735
|
+
d: "M12 3l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.4 6.8 19.1l1-5.8L3.5 9.2l5.9-.9L12 3z",
|
|
3736
|
+
stroke: color,
|
|
3737
|
+
strokeWidth: "1.5",
|
|
3738
|
+
strokeLinejoin: "round"
|
|
3739
|
+
}
|
|
3740
|
+
) });
|
|
2842
3741
|
case "BUG":
|
|
2843
3742
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2844
3743
|
"path",
|
|
@@ -2931,6 +3830,112 @@ function getTypeIcon(type, color, size = 24) {
|
|
|
2931
3830
|
}
|
|
2932
3831
|
}
|
|
2933
3832
|
|
|
3833
|
+
// src/rejection.ts
|
|
3834
|
+
var DETAILS_MAX_LENGTH = 1e3;
|
|
3835
|
+
var MESSAGE_MAX_LENGTH = 200;
|
|
3836
|
+
function isUnactionableRejection(description) {
|
|
3837
|
+
if (description.stack) return false;
|
|
3838
|
+
const message = description.message.trim();
|
|
3839
|
+
return message === "" || message === "[object Object]";
|
|
3840
|
+
}
|
|
3841
|
+
function readString(value) {
|
|
3842
|
+
if (typeof value !== "string") return void 0;
|
|
3843
|
+
const trimmed = value.trim();
|
|
3844
|
+
if (trimmed === "" || trimmed === "[object Object]") return void 0;
|
|
3845
|
+
return trimmed.slice(0, MESSAGE_MAX_LENGTH);
|
|
3846
|
+
}
|
|
3847
|
+
function readScalar(value) {
|
|
3848
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
3849
|
+
return readString(value);
|
|
3850
|
+
}
|
|
3851
|
+
function readStack(reason) {
|
|
3852
|
+
try {
|
|
3853
|
+
const stack = reason == null ? void 0 : reason.stack;
|
|
3854
|
+
return typeof stack === "string" && stack.trim() !== "" ? stack : void 0;
|
|
3855
|
+
} catch (e) {
|
|
3856
|
+
return void 0;
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
function serialize(reason) {
|
|
3860
|
+
try {
|
|
3861
|
+
const seen2 = /* @__PURE__ */ new WeakSet();
|
|
3862
|
+
const json = JSON.stringify(reason, (_key, value) => {
|
|
3863
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
3864
|
+
if (typeof value === "object" && value !== null) {
|
|
3865
|
+
if (seen2.has(value)) return "[Circular]";
|
|
3866
|
+
seen2.add(value);
|
|
3867
|
+
}
|
|
3868
|
+
return value;
|
|
3869
|
+
});
|
|
3870
|
+
if (!json || json === "{}" || json === "null") return void 0;
|
|
3871
|
+
return json.slice(0, DETAILS_MAX_LENGTH);
|
|
3872
|
+
} catch (e) {
|
|
3873
|
+
return void 0;
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
var SHAPE_MAX_KEYS = 5;
|
|
3877
|
+
function describeShape(reason) {
|
|
3878
|
+
try {
|
|
3879
|
+
const keys = Object.keys(reason);
|
|
3880
|
+
if (keys.length === 0) return "";
|
|
3881
|
+
const listed = keys.slice(0, SHAPE_MAX_KEYS).join(", ");
|
|
3882
|
+
const suffix = keys.length > SHAPE_MAX_KEYS ? ", \u2026" : "";
|
|
3883
|
+
return `Rejected object: ${listed}${suffix}`.slice(0, MESSAGE_MAX_LENGTH);
|
|
3884
|
+
} catch (e) {
|
|
3885
|
+
return "";
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
function describeRejection(reason) {
|
|
3889
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
3890
|
+
try {
|
|
3891
|
+
if (reason instanceof Error) {
|
|
3892
|
+
return {
|
|
3893
|
+
message: reason.message || reason.name || "",
|
|
3894
|
+
stack: reason.stack
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
const stack = readStack(reason);
|
|
3898
|
+
if (typeof reason === "string") {
|
|
3899
|
+
return { message: (_a = readString(reason)) != null ? _a : "", stack };
|
|
3900
|
+
}
|
|
3901
|
+
if (typeof reason === "number" || typeof reason === "boolean") {
|
|
3902
|
+
return { message: String(reason), stack };
|
|
3903
|
+
}
|
|
3904
|
+
if (reason === null || reason === void 0) {
|
|
3905
|
+
return { message: "" };
|
|
3906
|
+
}
|
|
3907
|
+
const source = reason;
|
|
3908
|
+
const nestedError = source.error;
|
|
3909
|
+
const message = (_h = (_g = (_f = (_e = (_d = (_c = (_b = readString(source.message)) != null ? _b : readString(nestedError)) != null ? _c : readString(nestedError == null ? void 0 : nestedError.message)) != null ? _d : readScalar(source.code)) != null ? _e : readString(source.name)) != null ? _f : readScalar(source.status)) != null ? _g : readString(source.statusText)) != null ? _h : "";
|
|
3910
|
+
const details = serialize(reason);
|
|
3911
|
+
return {
|
|
3912
|
+
message: message || describeShape(reason),
|
|
3913
|
+
stack,
|
|
3914
|
+
details
|
|
3915
|
+
};
|
|
3916
|
+
} catch (e) {
|
|
3917
|
+
return { message: "" };
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3921
|
+
// src/shortcut.ts
|
|
3922
|
+
var GLITCHGRAB_SHORTCUT2 = "Ctrl+Shift+G";
|
|
3923
|
+
var GLITCHGRAB_SHORTCUT_MAC2 = "\u2318\u21E7G";
|
|
3924
|
+
function isMac2() {
|
|
3925
|
+
try {
|
|
3926
|
+
if (typeof navigator === "undefined") return false;
|
|
3927
|
+
return /Mac|iPhone|iPad|iPod/i.test(navigator.platform || navigator.userAgent);
|
|
3928
|
+
} catch (e) {
|
|
3929
|
+
return false;
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
function getShortcutLabel2() {
|
|
3933
|
+
return isMac2() ? GLITCHGRAB_SHORTCUT_MAC2 : GLITCHGRAB_SHORTCUT2;
|
|
3934
|
+
}
|
|
3935
|
+
function matchesShortcut(e) {
|
|
3936
|
+
return (e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "g";
|
|
3937
|
+
}
|
|
3938
|
+
|
|
2934
3939
|
// src/provider.tsx
|
|
2935
3940
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
2936
3941
|
var DEFAULT_BASE_URL2 = "https://glitchgrab.dev";
|
|
@@ -2954,14 +3959,28 @@ function GlitchgrabProviderInner({
|
|
|
2954
3959
|
fallback,
|
|
2955
3960
|
types,
|
|
2956
3961
|
showSeverity,
|
|
2957
|
-
ignoreErrors
|
|
3962
|
+
ignoreErrors,
|
|
3963
|
+
release: release2,
|
|
3964
|
+
context: appContext2,
|
|
3965
|
+
responseBodyOrigins: responseBodyOrigins2
|
|
2958
3966
|
}) {
|
|
2959
3967
|
const visitedPagesRef = (0, import_react3.useRef)([]);
|
|
3968
|
+
setRelease(release2);
|
|
3969
|
+
if (appContext2) setContexts(appContext2);
|
|
3970
|
+
registerCaptureConfig({
|
|
3971
|
+
token,
|
|
3972
|
+
baseUrl,
|
|
3973
|
+
session,
|
|
3974
|
+
ignoreErrors,
|
|
3975
|
+
getVisitedPages: () => visitedPagesRef.current,
|
|
3976
|
+
onError,
|
|
3977
|
+
onReportSent
|
|
3978
|
+
});
|
|
2960
3979
|
(0, import_react3.useEffect)(() => {
|
|
2961
3980
|
if (enableBreadcrumbs) {
|
|
2962
|
-
initBreadcrumbs(maxBreadcrumbs2);
|
|
3981
|
+
initBreadcrumbs(maxBreadcrumbs2, { responseBodyOrigins: responseBodyOrigins2 });
|
|
2963
3982
|
}
|
|
2964
|
-
}, [enableBreadcrumbs, maxBreadcrumbs2]);
|
|
3983
|
+
}, [enableBreadcrumbs, maxBreadcrumbs2, responseBodyOrigins2 == null ? void 0 : responseBodyOrigins2.join(",")]);
|
|
2965
3984
|
(0, import_react3.useEffect)(() => {
|
|
2966
3985
|
try {
|
|
2967
3986
|
if (typeof window === "undefined") return;
|
|
@@ -3009,10 +4028,7 @@ function GlitchgrabProviderInner({
|
|
|
3009
4028
|
];
|
|
3010
4029
|
const matchesIgnore = (message) => {
|
|
3011
4030
|
if (KNOWN_NOISE_RE.some((pattern) => pattern.test(message))) return true;
|
|
3012
|
-
|
|
3013
|
-
return ignoreErrors.some(
|
|
3014
|
-
(pattern) => pattern instanceof RegExp ? pattern.test(message) : message.includes(pattern)
|
|
3015
|
-
);
|
|
4031
|
+
return matchesIgnorePatterns(message, ignoreErrors);
|
|
3016
4032
|
};
|
|
3017
4033
|
const handleError = (event) => {
|
|
3018
4034
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
@@ -3030,6 +4046,7 @@ function GlitchgrabProviderInner({
|
|
|
3030
4046
|
if (event.filename && EXTENSION_ORIGIN_RE.test(event.filename) || ((_a = event.error) == null ? void 0 : _a.stack) && EXTENSION_ORIGIN_RE.test(event.error.stack)) {
|
|
3031
4047
|
return;
|
|
3032
4048
|
}
|
|
4049
|
+
incrementErrorCount();
|
|
3033
4050
|
const context = captureContext(visitedPagesRef.current);
|
|
3034
4051
|
const sig = computeSignature({
|
|
3035
4052
|
errorMessage: event.message,
|
|
@@ -3050,6 +4067,7 @@ function GlitchgrabProviderInner({
|
|
|
3050
4067
|
metadata: {
|
|
3051
4068
|
timestamp: context.timestamp,
|
|
3052
4069
|
visitedPages: JSON.stringify(context.visitedPages),
|
|
4070
|
+
...contextMetadata(context),
|
|
3053
4071
|
filename: (_e = event.filename) != null ? _e : "",
|
|
3054
4072
|
lineno: String((_f = event.lineno) != null ? _f : ""),
|
|
3055
4073
|
colno: String((_g = event.colno) != null ? _g : ""),
|
|
@@ -3069,17 +4087,20 @@ function GlitchgrabProviderInner({
|
|
|
3069
4087
|
const handleRejection = (event) => {
|
|
3070
4088
|
var _a;
|
|
3071
4089
|
try {
|
|
3072
|
-
const context = captureContext(visitedPagesRef.current);
|
|
3073
4090
|
const reason = event.reason;
|
|
3074
|
-
const
|
|
4091
|
+
const description = describeRejection(reason);
|
|
4092
|
+
const errMsg = description.message;
|
|
4093
|
+
const errStack = description.stack;
|
|
3075
4094
|
if (errMsg === "Script error." || errMsg === "Script error") {
|
|
3076
4095
|
return;
|
|
3077
4096
|
}
|
|
3078
4097
|
if (matchesIgnore(errMsg)) {
|
|
3079
4098
|
return;
|
|
3080
4099
|
}
|
|
3081
|
-
const errStack = reason instanceof Error ? reason.stack : void 0;
|
|
3082
4100
|
if (errStack && EXTENSION_ORIGIN_RE.test(errStack)) return;
|
|
4101
|
+
if (isUnactionableRejection(description)) return;
|
|
4102
|
+
incrementErrorCount();
|
|
4103
|
+
const context = captureContext(visitedPagesRef.current);
|
|
3083
4104
|
const sig = computeSignature({
|
|
3084
4105
|
errorMessage: errMsg,
|
|
3085
4106
|
pageUrl: context.url,
|
|
@@ -3099,7 +4120,9 @@ function GlitchgrabProviderInner({
|
|
|
3099
4120
|
metadata: {
|
|
3100
4121
|
timestamp: context.timestamp,
|
|
3101
4122
|
visitedPages: JSON.stringify(context.visitedPages),
|
|
4123
|
+
...contextMetadata(context),
|
|
3102
4124
|
type: "unhandledrejection",
|
|
4125
|
+
...description.details ? { rejectionReason: description.details } : {},
|
|
3103
4126
|
...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
|
|
3104
4127
|
...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
|
|
3105
4128
|
...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
|
|
@@ -3139,6 +4162,7 @@ function GlitchgrabProviderInner({
|
|
|
3139
4162
|
metadata: {
|
|
3140
4163
|
timestamp: context.timestamp,
|
|
3141
4164
|
visitedPages: JSON.stringify(context.visitedPages),
|
|
4165
|
+
...contextMetadata(context),
|
|
3142
4166
|
...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
|
|
3143
4167
|
...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
|
|
3144
4168
|
...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
|
|
@@ -3159,6 +4183,53 @@ function GlitchgrabProviderInner({
|
|
|
3159
4183
|
(description, metadata) => report("BUG", description, metadata),
|
|
3160
4184
|
[report]
|
|
3161
4185
|
);
|
|
4186
|
+
const feedback = (0, import_react3.useCallback)(
|
|
4187
|
+
async (rating, message, metadata) => {
|
|
4188
|
+
try {
|
|
4189
|
+
const context = captureContext(visitedPagesRef.current);
|
|
4190
|
+
return await sendFeedback(
|
|
4191
|
+
{
|
|
4192
|
+
token,
|
|
4193
|
+
rating,
|
|
4194
|
+
message,
|
|
4195
|
+
pageUrl: context.url,
|
|
4196
|
+
userAgent: context.userAgent,
|
|
4197
|
+
metadata: {
|
|
4198
|
+
timestamp: context.timestamp,
|
|
4199
|
+
...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
|
|
4200
|
+
...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
|
|
4201
|
+
...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
|
|
4202
|
+
...(session == null ? void 0 : session.phone) ? { sessionUserPhone: String(session.phone) } : {},
|
|
4203
|
+
...metadata
|
|
4204
|
+
}
|
|
4205
|
+
},
|
|
4206
|
+
baseUrl
|
|
4207
|
+
);
|
|
4208
|
+
} catch (e) {
|
|
4209
|
+
return null;
|
|
4210
|
+
}
|
|
4211
|
+
},
|
|
4212
|
+
[token, baseUrl, session]
|
|
4213
|
+
);
|
|
4214
|
+
const openFeedbackDialog = (0, import_react3.useCallback)(() => {
|
|
4215
|
+
if (typeof window !== "undefined") {
|
|
4216
|
+
window.dispatchEvent(
|
|
4217
|
+
new CustomEvent("glitchgrab:open-report", { detail: { type: "RATING" } })
|
|
4218
|
+
);
|
|
4219
|
+
}
|
|
4220
|
+
}, []);
|
|
4221
|
+
const captureError2 = (0, import_react3.useCallback)(
|
|
4222
|
+
(error, options) => {
|
|
4223
|
+
captureError(error, options);
|
|
4224
|
+
},
|
|
4225
|
+
[]
|
|
4226
|
+
);
|
|
4227
|
+
const setContext2 = (0, import_react3.useCallback)((key, value) => {
|
|
4228
|
+
setContext(key, value);
|
|
4229
|
+
}, []);
|
|
4230
|
+
const setContexts2 = (0, import_react3.useCallback)((values) => {
|
|
4231
|
+
setContexts(values);
|
|
4232
|
+
}, []);
|
|
3162
4233
|
const addBreadcrumb2 = (0, import_react3.useCallback)(
|
|
3163
4234
|
(message, data) => {
|
|
3164
4235
|
addBreadcrumb("custom", message, data);
|
|
@@ -3210,9 +4281,19 @@ function GlitchgrabProviderInner({
|
|
|
3210
4281
|
} catch (e) {
|
|
3211
4282
|
}
|
|
3212
4283
|
}, [openReportDialog]);
|
|
3213
|
-
const
|
|
4284
|
+
const reporter = (0, import_react3.useMemo)(() => {
|
|
4285
|
+
var _a;
|
|
4286
|
+
if (!session) return null;
|
|
4287
|
+
const avatar = (_a = session.avatarUrl) != null ? _a : session.image;
|
|
4288
|
+
return {
|
|
4289
|
+
name: session.name,
|
|
4290
|
+
email: session.email,
|
|
4291
|
+
avatarUrl: typeof avatar === "string" ? avatar : null
|
|
4292
|
+
};
|
|
4293
|
+
}, [session]);
|
|
4294
|
+
const [shortcutLabel, setShortcutLabel] = (0, import_react3.useState)(GLITCHGRAB_SHORTCUT2);
|
|
3214
4295
|
(0, import_react3.useEffect)(() => {
|
|
3215
|
-
setShortcutLabel(
|
|
4296
|
+
setShortcutLabel(getShortcutLabel2());
|
|
3216
4297
|
}, []);
|
|
3217
4298
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
3218
4299
|
GlitchgrabContext.Provider,
|
|
@@ -3222,6 +4303,11 @@ function GlitchgrabProviderInner({
|
|
|
3222
4303
|
baseUrl: baseUrl != null ? baseUrl : DEFAULT_BASE_URL2,
|
|
3223
4304
|
reportBug,
|
|
3224
4305
|
report,
|
|
4306
|
+
captureError: captureError2,
|
|
4307
|
+
setContext: setContext2,
|
|
4308
|
+
setContexts: setContexts2,
|
|
4309
|
+
sendFeedback: feedback,
|
|
4310
|
+
openFeedbackDialog,
|
|
3225
4311
|
addBreadcrumb: addBreadcrumb2,
|
|
3226
4312
|
openReportDialog,
|
|
3227
4313
|
enhanceText: enhance,
|
|
@@ -3240,7 +4326,7 @@ function GlitchgrabProviderInner({
|
|
|
3240
4326
|
children
|
|
3241
4327
|
}
|
|
3242
4328
|
),
|
|
3243
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReportDialog, { report, enhanceText: enhance, transcribeAudio: transcribe, types, showSeverity })
|
|
4329
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReportDialog, { report, sendFeedback: feedback, enhanceText: enhance, transcribeAudio: transcribe, types, showSeverity, reporter })
|
|
3244
4330
|
]
|
|
3245
4331
|
}
|
|
3246
4332
|
);
|
|
@@ -3350,6 +4436,91 @@ function ReportButton({
|
|
|
3350
4436
|
);
|
|
3351
4437
|
}
|
|
3352
4438
|
|
|
4439
|
+
// src/feedback-button.tsx
|
|
4440
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
4441
|
+
function useGlitchgrabSafe2() {
|
|
4442
|
+
try {
|
|
4443
|
+
return useGlitchgrab();
|
|
4444
|
+
} catch (e) {
|
|
4445
|
+
return null;
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
function FeedbackButton({
|
|
4449
|
+
position = "bottom-left",
|
|
4450
|
+
label = "Feedback",
|
|
4451
|
+
className,
|
|
4452
|
+
children
|
|
4453
|
+
}) {
|
|
4454
|
+
var _a, _b;
|
|
4455
|
+
const glitchgrab = useGlitchgrabSafe2();
|
|
4456
|
+
if (!glitchgrab) return null;
|
|
4457
|
+
const handleClick = () => glitchgrab.openFeedbackDialog();
|
|
4458
|
+
if (children) {
|
|
4459
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: children({ onClick: handleClick }) });
|
|
4460
|
+
}
|
|
4461
|
+
const isTop = (_a = position == null ? void 0 : position.startsWith("top")) != null ? _a : false;
|
|
4462
|
+
const isLeft = (_b = position == null ? void 0 : position.endsWith("left")) != null ? _b : false;
|
|
4463
|
+
const positionStyles = {
|
|
4464
|
+
...isLeft ? { left: "16px" } : { right: "16px" },
|
|
4465
|
+
...isTop ? { top: "16px" } : { bottom: "16px" }
|
|
4466
|
+
};
|
|
4467
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
4468
|
+
"button",
|
|
4469
|
+
{
|
|
4470
|
+
type: "button",
|
|
4471
|
+
onClick: handleClick,
|
|
4472
|
+
className,
|
|
4473
|
+
style: {
|
|
4474
|
+
position: "fixed",
|
|
4475
|
+
...positionStyles,
|
|
4476
|
+
zIndex: 99999,
|
|
4477
|
+
padding: "10px 18px",
|
|
4478
|
+
borderRadius: "24px",
|
|
4479
|
+
border: "none",
|
|
4480
|
+
backgroundColor: "#18181b",
|
|
4481
|
+
color: "#fafafa",
|
|
4482
|
+
fontSize: "14px",
|
|
4483
|
+
fontWeight: 500,
|
|
4484
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
4485
|
+
cursor: "pointer",
|
|
4486
|
+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15), 0 1px 3px rgba(0, 0, 0, 0.1)",
|
|
4487
|
+
transition: "transform 0.15s ease, box-shadow 0.15s ease",
|
|
4488
|
+
display: "flex",
|
|
4489
|
+
alignItems: "center",
|
|
4490
|
+
gap: "6px"
|
|
4491
|
+
},
|
|
4492
|
+
onMouseEnter: (e) => {
|
|
4493
|
+
e.target.style.transform = "scale(1.05)";
|
|
4494
|
+
},
|
|
4495
|
+
onMouseLeave: (e) => {
|
|
4496
|
+
e.target.style.transform = "scale(1)";
|
|
4497
|
+
},
|
|
4498
|
+
"aria-label": label,
|
|
4499
|
+
children: [
|
|
4500
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
4501
|
+
"svg",
|
|
4502
|
+
{
|
|
4503
|
+
width: "16",
|
|
4504
|
+
height: "16",
|
|
4505
|
+
viewBox: "0 0 24 24",
|
|
4506
|
+
fill: "none",
|
|
4507
|
+
style: { flexShrink: 0 },
|
|
4508
|
+
"aria-hidden": "true",
|
|
4509
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
4510
|
+
"path",
|
|
4511
|
+
{
|
|
4512
|
+
d: "M12 2.5l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3 1.1-6.5L2.6 9.3l6.5-.9L12 2.5z",
|
|
4513
|
+
fill: "currentColor"
|
|
4514
|
+
}
|
|
4515
|
+
)
|
|
4516
|
+
}
|
|
4517
|
+
),
|
|
4518
|
+
label
|
|
4519
|
+
]
|
|
4520
|
+
}
|
|
4521
|
+
);
|
|
4522
|
+
}
|
|
4523
|
+
|
|
3353
4524
|
// src/use-reports.ts
|
|
3354
4525
|
var import_react5 = require("react");
|
|
3355
4526
|
var BASE_URL = "https://glitchgrab.dev";
|
|
@@ -3455,8 +4626,70 @@ function useGlitchgrabActions({
|
|
|
3455
4626
|
error
|
|
3456
4627
|
};
|
|
3457
4628
|
}
|
|
4629
|
+
|
|
4630
|
+
// src/use-feedback.ts
|
|
4631
|
+
var import_react6 = require("react");
|
|
4632
|
+
var DEFAULT_BASE_URL3 = "https://glitchgrab.dev";
|
|
4633
|
+
function buildUrl({
|
|
4634
|
+
approvedOnly,
|
|
4635
|
+
userId,
|
|
4636
|
+
minRating,
|
|
4637
|
+
limit = 50,
|
|
4638
|
+
baseUrl
|
|
4639
|
+
}) {
|
|
4640
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
4641
|
+
if (approvedOnly) params.set("approved", "true");
|
|
4642
|
+
if (userId) params.set("reporterPrimaryKey", userId);
|
|
4643
|
+
if (minRating) params.set("minRating", String(minRating));
|
|
4644
|
+
return `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL3}/api/v1/sdk/feedback?${params.toString()}`;
|
|
4645
|
+
}
|
|
4646
|
+
async function fetchGlitchgrabFeedback(query) {
|
|
4647
|
+
var _a, _b;
|
|
4648
|
+
const res = await fetch(buildUrl(query), {
|
|
4649
|
+
headers: { Authorization: `Bearer ${query.token}` }
|
|
4650
|
+
});
|
|
4651
|
+
const data = await res.json();
|
|
4652
|
+
if (!data.success) throw new Error((_a = data.error) != null ? _a : "Failed to fetch feedback");
|
|
4653
|
+
return (_b = data.data) != null ? _b : [];
|
|
4654
|
+
}
|
|
4655
|
+
function useGlitchgrabFeedback(query) {
|
|
4656
|
+
const { token, approvedOnly, userId, minRating, limit, baseUrl } = query;
|
|
4657
|
+
const [feedback, setFeedback] = (0, import_react6.useState)([]);
|
|
4658
|
+
const [isLoading, setIsLoading] = (0, import_react6.useState)(true);
|
|
4659
|
+
const [isFetching, setIsFetching] = (0, import_react6.useState)(false);
|
|
4660
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
4661
|
+
const load = (0, import_react6.useCallback)(async () => {
|
|
4662
|
+
if (!token) {
|
|
4663
|
+
setIsLoading(false);
|
|
4664
|
+
return;
|
|
4665
|
+
}
|
|
4666
|
+
setIsFetching(true);
|
|
4667
|
+
setError(null);
|
|
4668
|
+
try {
|
|
4669
|
+
const data = await fetchGlitchgrabFeedback({
|
|
4670
|
+
token,
|
|
4671
|
+
approvedOnly,
|
|
4672
|
+
userId,
|
|
4673
|
+
minRating,
|
|
4674
|
+
limit,
|
|
4675
|
+
baseUrl
|
|
4676
|
+
});
|
|
4677
|
+
setFeedback(data);
|
|
4678
|
+
} catch (e) {
|
|
4679
|
+
setError(e instanceof Error ? e.message : "Network error");
|
|
4680
|
+
} finally {
|
|
4681
|
+
setIsLoading(false);
|
|
4682
|
+
setIsFetching(false);
|
|
4683
|
+
}
|
|
4684
|
+
}, [token, approvedOnly, userId, minRating, limit, baseUrl]);
|
|
4685
|
+
(0, import_react6.useEffect)(() => {
|
|
4686
|
+
load();
|
|
4687
|
+
}, [load]);
|
|
4688
|
+
return { feedback, isLoading, isFetching, error, refetch: load };
|
|
4689
|
+
}
|
|
3458
4690
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3459
4691
|
0 && (module.exports = {
|
|
4692
|
+
FeedbackButton,
|
|
3460
4693
|
GLITCHGRAB_SHORTCUT,
|
|
3461
4694
|
GLITCHGRAB_SHORTCUT_MAC,
|
|
3462
4695
|
GlitchgrabErrorBoundary,
|
|
@@ -3465,20 +4698,28 @@ function useGlitchgrabActions({
|
|
|
3465
4698
|
addBreadcrumb,
|
|
3466
4699
|
captureContext,
|
|
3467
4700
|
captureDeviceInfo,
|
|
4701
|
+
captureError,
|
|
4702
|
+
clearAppContext,
|
|
3468
4703
|
clearBreadcrumbs,
|
|
3469
4704
|
clearDedupCache,
|
|
3470
4705
|
computeSignature,
|
|
3471
4706
|
enhanceText,
|
|
4707
|
+
fetchGlitchgrabFeedback,
|
|
3472
4708
|
fetchGlitchgrabReports,
|
|
4709
|
+
getAppContext,
|
|
3473
4710
|
getBreadcrumbs,
|
|
3474
4711
|
getShortcutLabel,
|
|
3475
4712
|
initBreadcrumbs,
|
|
3476
4713
|
matchesShortcut,
|
|
3477
4714
|
sanitizeUrl,
|
|
4715
|
+
sendFeedback,
|
|
3478
4716
|
sendReport,
|
|
4717
|
+
setContext,
|
|
4718
|
+
setContexts,
|
|
3479
4719
|
shouldSkipDuplicate,
|
|
3480
4720
|
useGlitchgrab,
|
|
3481
4721
|
useGlitchgrabActions,
|
|
4722
|
+
useGlitchgrabFeedback,
|
|
3482
4723
|
useGlitchgrabReports
|
|
3483
4724
|
});
|
|
3484
4725
|
//# sourceMappingURL=index.js.map
|