@contember/react-devbar 2.1.0-alpha.3 → 2.1.0-alpha.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/development/src/hooks/useCopyErrorMarkdown.cjs +101 -0
- package/dist/development/src/hooks/useCopyErrorMarkdown.cjs.map +1 -0
- package/dist/development/src/hooks/useCopyErrorMarkdown.js +101 -0
- package/dist/development/src/hooks/useCopyErrorMarkdown.js.map +1 -0
- package/dist/development/src/ui/DevBar/DevBar.cjs +9 -7
- package/dist/development/src/ui/DevBar/DevBar.cjs.map +1 -1
- package/dist/development/src/ui/DevBar/DevBar.js +9 -7
- package/dist/development/src/ui/DevBar/DevBar.js.map +1 -1
- package/dist/development/src/ui/DevError/DevErrorCopy.cjs +36 -0
- package/dist/development/src/ui/DevError/DevErrorCopy.cjs.map +1 -0
- package/dist/development/src/ui/DevError/DevErrorCopy.js +36 -0
- package/dist/development/src/ui/DevError/DevErrorCopy.js.map +1 -0
- package/dist/development/src/ui/DevError/DevErrorList.cjs +30 -22
- package/dist/development/src/ui/DevError/DevErrorList.cjs.map +1 -1
- package/dist/development/src/ui/DevError/DevErrorList.js +30 -22
- package/dist/development/src/ui/DevError/DevErrorList.js.map +1 -1
- package/dist/production/src/hooks/useCopyErrorMarkdown.cjs +101 -0
- package/dist/production/src/hooks/useCopyErrorMarkdown.cjs.map +1 -0
- package/dist/production/src/hooks/useCopyErrorMarkdown.js +101 -0
- package/dist/production/src/hooks/useCopyErrorMarkdown.js.map +1 -0
- package/dist/production/src/ui/DevBar/DevBar.cjs +2 -0
- package/dist/production/src/ui/DevBar/DevBar.cjs.map +1 -1
- package/dist/production/src/ui/DevBar/DevBar.js +2 -0
- package/dist/production/src/ui/DevBar/DevBar.js.map +1 -1
- package/dist/production/src/ui/DevError/DevErrorCopy.cjs +28 -0
- package/dist/production/src/ui/DevError/DevErrorCopy.cjs.map +1 -0
- package/dist/production/src/ui/DevError/DevErrorCopy.js +28 -0
- package/dist/production/src/ui/DevError/DevErrorCopy.js.map +1 -0
- package/dist/production/src/ui/DevError/DevErrorList.cjs +10 -6
- package/dist/production/src/ui/DevError/DevErrorList.cjs.map +1 -1
- package/dist/production/src/ui/DevError/DevErrorList.js +10 -6
- package/dist/production/src/ui/DevError/DevErrorList.js.map +1 -1
- package/dist/types/hooks/useCopyErrorMarkdown.d.ts +12 -0
- package/dist/types/hooks/useCopyErrorMarkdown.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ui/DevBar/DevBar.d.ts +1 -1
- package/dist/types/ui/DevBar/DevBar.d.ts.map +1 -1
- package/dist/types/ui/DevError/DevErrorCopy.d.ts +6 -0
- package/dist/types/ui/DevError/DevErrorCopy.d.ts.map +1 -0
- package/dist/types/ui/DevError/DevErrorList.d.ts +1 -1
- package/dist/types/ui/DevError/DevErrorList.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/hooks/useCopyErrorMarkdown.ts +128 -0
- package/src/index.css +27 -0
- package/src/ui/DevBar/DevBar.tsx +3 -1
- package/src/ui/DevError/DevErrorCopy.tsx +31 -0
- package/src/ui/DevError/DevErrorList.tsx +12 -7
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import type { ParsedStackTrace, ProcessedError } from '../ui/DevError/types'
|
|
3
|
+
|
|
4
|
+
const convertStackTraceToMarkdown = (stackTrace: ParsedStackTrace, lines: string[]) => {
|
|
5
|
+
stackTrace.forEach((frame, index) => {
|
|
6
|
+
lines.push(`**Frame ${index + 1}:**`)
|
|
7
|
+
lines.push('')
|
|
8
|
+
lines.push(`- **File:** ${frame.filename}${frame.line ? `:${frame.line}` : ''}`)
|
|
9
|
+
|
|
10
|
+
if (frame.callee) {
|
|
11
|
+
lines.push(`- **Function:** ${frame.callee}`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
lines.push('')
|
|
15
|
+
|
|
16
|
+
if (frame.line !== undefined && frame.sourceCodeLines) {
|
|
17
|
+
lines.push('**Source Code:**')
|
|
18
|
+
lines.push('')
|
|
19
|
+
lines.push('```typescript')
|
|
20
|
+
|
|
21
|
+
frame.sourceCodeLines.forEach((codeLine, lineIndex) => {
|
|
22
|
+
const lineNumber = lineIndex + 1
|
|
23
|
+
// Show context around the error line (similar to the UI logic)
|
|
24
|
+
if (frame.line && lineNumber + 5 >= frame.line && lineNumber - 3 <= frame.line) {
|
|
25
|
+
const isErrorLine = lineNumber === frame.line
|
|
26
|
+
const marker = isErrorLine ? '>>> ' : ' '
|
|
27
|
+
lines.push(`${marker}${lineNumber.toString().padStart(3)}: ${codeLine}`)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
lines.push('```')
|
|
32
|
+
lines.push('')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
lines.push('---')
|
|
36
|
+
lines.push('')
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const convertErrorToMarkdownRecursive = (error: ProcessedError, lines: string[], level: number) => {
|
|
41
|
+
const errorObject = error.error instanceof Error ? error.error : undefined
|
|
42
|
+
const prefix = '#'.repeat(level + 1)
|
|
43
|
+
|
|
44
|
+
if (errorObject) {
|
|
45
|
+
lines.push(`${prefix} ${errorObject.name}`)
|
|
46
|
+
lines.push('')
|
|
47
|
+
|
|
48
|
+
if (errorObject.message) {
|
|
49
|
+
lines.push(`**Message:** ${errorObject.message}`)
|
|
50
|
+
lines.push('')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if ('details' in errorObject && (errorObject as Error & { details?: unknown }).details) {
|
|
54
|
+
lines.push(`**Details:** ${(errorObject as Error & { details: string }).details}`)
|
|
55
|
+
lines.push('')
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
lines.push(`${prefix} Unknown Error`)
|
|
59
|
+
lines.push('')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (error.parsedStackStrace) {
|
|
63
|
+
lines.push(`${'#'.repeat(level + 2)} Stack Trace`)
|
|
64
|
+
lines.push('')
|
|
65
|
+
convertStackTraceToMarkdown(error.parsedStackStrace, lines)
|
|
66
|
+
} else if (errorObject?.stack) {
|
|
67
|
+
lines.push(`${'#'.repeat(level + 2)} Stack Trace`)
|
|
68
|
+
lines.push('')
|
|
69
|
+
lines.push('```')
|
|
70
|
+
lines.push(errorObject.stack)
|
|
71
|
+
lines.push('```')
|
|
72
|
+
lines.push('')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (error.cause) {
|
|
76
|
+
lines.push(`${'#'.repeat(level + 2)} Caused By`)
|
|
77
|
+
lines.push('')
|
|
78
|
+
convertErrorToMarkdownRecursive(error.cause, lines, level + 1)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const convertErrorToMarkdown = (error: ProcessedError, source: string): string => {
|
|
83
|
+
const lines: string[] = []
|
|
84
|
+
|
|
85
|
+
lines.push('# Contember Error Report')
|
|
86
|
+
lines.push('')
|
|
87
|
+
lines.push(`**Source:** ${source}`)
|
|
88
|
+
lines.push('')
|
|
89
|
+
|
|
90
|
+
convertErrorToMarkdownRecursive(error, lines, 1)
|
|
91
|
+
|
|
92
|
+
return lines.join('\n')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type CopyMarkdownProps = {
|
|
96
|
+
currentError: ProcessedError
|
|
97
|
+
currentErrorSource: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const useCopyErrorMarkdown = ({ currentError, currentErrorSource }: CopyMarkdownProps) => {
|
|
101
|
+
const [copyStatus, setCopyStatus] = useState<'idle' | 'copying' | 'success' | 'error'>('idle')
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
setCopyStatus('idle')
|
|
105
|
+
}, [currentError])
|
|
106
|
+
|
|
107
|
+
const handleCopyMarkdown = async () => {
|
|
108
|
+
setCopyStatus('copying')
|
|
109
|
+
try {
|
|
110
|
+
const markdown = convertErrorToMarkdown(currentError, currentErrorSource)
|
|
111
|
+
await navigator.clipboard.writeText(markdown)
|
|
112
|
+
setCopyStatus('success')
|
|
113
|
+
} catch (err) {
|
|
114
|
+
console.error('Failed to copy markdown:', err)
|
|
115
|
+
setCopyStatus('error')
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
const isClipboardSupported = !!navigator?.clipboard
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
copyStatus,
|
|
125
|
+
handleCopyMarkdown,
|
|
126
|
+
isClipboardSupported,
|
|
127
|
+
}
|
|
128
|
+
}
|
package/src/index.css
CHANGED
|
@@ -235,6 +235,12 @@
|
|
|
235
235
|
overflow: auto;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
.cui-devError-errorSource {
|
|
239
|
+
display: flex;
|
|
240
|
+
gap: 20px;
|
|
241
|
+
align-items: center;
|
|
242
|
+
}
|
|
243
|
+
|
|
238
244
|
.cui-devError-errorMessage,
|
|
239
245
|
.cui-devError-errorName,
|
|
240
246
|
.cui-devError-errorDetails {
|
|
@@ -284,6 +290,7 @@
|
|
|
284
290
|
color: #eeeeee;
|
|
285
291
|
display: flex;
|
|
286
292
|
align-items: center;
|
|
293
|
+
justify-content: space-between;
|
|
287
294
|
}
|
|
288
295
|
|
|
289
296
|
.cui-devError-causedBy {
|
|
@@ -294,6 +301,26 @@
|
|
|
294
301
|
align-items: center;
|
|
295
302
|
}
|
|
296
303
|
|
|
304
|
+
.cui-devError-copyButton {
|
|
305
|
+
background: transparent;
|
|
306
|
+
border: 1px solid #cbcbcb;
|
|
307
|
+
border-radius: 0.375rem;
|
|
308
|
+
color: #ffffff;
|
|
309
|
+
cursor: pointer;
|
|
310
|
+
font-size: 0.875rem;
|
|
311
|
+
font-weight: 500;
|
|
312
|
+
padding: 0.375rem 0.75rem;
|
|
313
|
+
transition: all 200ms;
|
|
314
|
+
display: flex;
|
|
315
|
+
align-items: center;
|
|
316
|
+
gap: 0.25rem;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.cui-devError-copyButton:hover:not(:disabled) {
|
|
320
|
+
background: #910f0f;
|
|
321
|
+
border-color: #910f0f;
|
|
322
|
+
}
|
|
323
|
+
|
|
297
324
|
.cui-devError-actions {
|
|
298
325
|
margin-left: auto;
|
|
299
326
|
display: flex;
|
package/src/ui/DevBar/DevBar.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
useWindowSize,
|
|
6
6
|
} from '@contember/react-utils'
|
|
7
7
|
import { dataAttribute } from '@contember/utilities'
|
|
8
|
-
import { PropsWithChildren, ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
|
8
|
+
import { type PropsWithChildren, type ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
|
9
9
|
|
|
10
10
|
const className = (cls: string | null) => cls ? `cui-devBar-${cls}` : 'cui-devBar'
|
|
11
11
|
|
|
@@ -126,6 +126,7 @@ export const DevPanel = ({ heading, icon, children, preview }: DevPanelProps) =>
|
|
|
126
126
|
return (
|
|
127
127
|
<div className={className('trigger')}>
|
|
128
128
|
<button
|
|
129
|
+
type="button"
|
|
129
130
|
ref={triggerRef}
|
|
130
131
|
tabIndex={0}
|
|
131
132
|
aria-haspopup="dialog"
|
|
@@ -144,6 +145,7 @@ export const DevPanel = ({ heading, icon, children, preview }: DevPanelProps) =>
|
|
|
144
145
|
<div className={className('panel-header')}>
|
|
145
146
|
<h2 className="h4">{heading}</h2>
|
|
146
147
|
<button
|
|
148
|
+
type="button"
|
|
147
149
|
onClick={handleClose}
|
|
148
150
|
className={className('close-button')}
|
|
149
151
|
aria-label="Close"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useCopyErrorMarkdown } from '../../hooks/useCopyErrorMarkdown'
|
|
2
|
+
import { ProcessedError } from './types'
|
|
3
|
+
|
|
4
|
+
export const DevErrorCopy = ({ currentError, currentErrorSource }: {
|
|
5
|
+
currentError: ProcessedError
|
|
6
|
+
currentErrorSource: string
|
|
7
|
+
}) => {
|
|
8
|
+
const { copyStatus, handleCopyMarkdown, isClipboardSupported } = useCopyErrorMarkdown({ currentError, currentErrorSource })
|
|
9
|
+
|
|
10
|
+
if (!isClipboardSupported) {
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<button
|
|
16
|
+
type="button"
|
|
17
|
+
className="cui-devError-copyButton"
|
|
18
|
+
onClick={handleCopyMarkdown}
|
|
19
|
+
disabled={copyStatus === 'copying'}
|
|
20
|
+
title="Copy current error as Markdown"
|
|
21
|
+
>
|
|
22
|
+
{({
|
|
23
|
+
success: 'Copied!',
|
|
24
|
+
copying: 'Copying...',
|
|
25
|
+
error: '❌ Copy Failed',
|
|
26
|
+
idle: 'Copy as Markdown',
|
|
27
|
+
}[copyStatus])}
|
|
28
|
+
</button>
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { DevErrorInner } from './DevErrorInner'
|
|
2
|
-
import { ProcessedError } from './types'
|
|
3
1
|
import { useEffect } from 'react'
|
|
2
|
+
import { DevErrorInner } from './DevErrorInner'
|
|
3
|
+
import type { ProcessedError } from './types'
|
|
4
|
+
import { DevErrorCopy } from './DevErrorCopy'
|
|
4
5
|
|
|
5
6
|
export interface DevErrorListProps {
|
|
6
7
|
currentError: ProcessedError
|
|
@@ -25,6 +26,7 @@ export function DevErrorList({
|
|
|
25
26
|
onPrevious,
|
|
26
27
|
onClear,
|
|
27
28
|
}: DevErrorListProps) {
|
|
29
|
+
|
|
28
30
|
useEffect(() => {
|
|
29
31
|
document.body.classList.add('cui-devError-body')
|
|
30
32
|
return () => {
|
|
@@ -36,7 +38,10 @@ export function DevErrorList({
|
|
|
36
38
|
<div className={className()}>
|
|
37
39
|
<div className={className('in')}>
|
|
38
40
|
<div className={className('bar')}>
|
|
39
|
-
<div className={className('errorSource')}>
|
|
41
|
+
<div className={className('errorSource')}>
|
|
42
|
+
{currentErrorSource}
|
|
43
|
+
<DevErrorCopy currentError={currentError} currentErrorSource={currentErrorSource} />
|
|
44
|
+
</div>
|
|
40
45
|
<div className={className('actions')}>
|
|
41
46
|
{errorCount > 1 ? (
|
|
42
47
|
<div className={className('switcher')}>
|
|
@@ -44,23 +49,23 @@ export function DevErrorList({
|
|
|
44
49
|
Error {currentErrorIndex + 1} of {errorCount}
|
|
45
50
|
</p>
|
|
46
51
|
<div className={className('switcherButtons')}>
|
|
47
|
-
<button className={className('switcherButton')} onClick={onPrevious}>
|
|
52
|
+
<button type="button" className={className('switcherButton')} onClick={onPrevious}>
|
|
48
53
|
←
|
|
49
54
|
</button>
|
|
50
|
-
<button className={className('switcherButton')} onClick={onNext}>
|
|
55
|
+
<button type="button" className={className('switcherButton')} onClick={onNext}>
|
|
51
56
|
→
|
|
52
57
|
</button>
|
|
53
58
|
</div>
|
|
54
59
|
</div>
|
|
55
60
|
) : null}
|
|
56
61
|
<div className={className('close')}>
|
|
57
|
-
<button className={className('closeButton')} onClick={onClose}>
|
|
62
|
+
<button type="button" className={className('closeButton')} onClick={onClose}>
|
|
58
63
|
✕
|
|
59
64
|
</button>
|
|
60
65
|
</div>
|
|
61
66
|
</div>
|
|
62
67
|
<div className={className('close')}>
|
|
63
|
-
<button className={className('closeButton')} onClick={onClear}>
|
|
68
|
+
<button type="button" className={className('closeButton')} onClick={onClear}>
|
|
64
69
|
Clear errors
|
|
65
70
|
</button>
|
|
66
71
|
</div>
|