-
Notifications
You must be signed in to change notification settings - Fork 27.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c61ec57
commit b291d84
Showing
78 changed files
with
9,579 additions
and
157 deletions.
There are no files selected for viewing
94 changes: 94 additions & 0 deletions
94
packages/next/src/client/components/react-dev-overlay/_experimental/app/ReactDevOverlay.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import type { OverlayState } from '../../shared' | ||
import type { Dispatcher } from '../../app/hot-reloader-client' | ||
|
||
import React from 'react' | ||
|
||
import { ShadowPortal } from '../internal/components/ShadowPortal' | ||
import { BuildError } from '../internal/container/BuildError' | ||
import { Errors } from '../internal/container/Errors' | ||
import { StaticIndicator } from '../internal/container/StaticIndicator' | ||
import { Base } from '../internal/styles/Base' | ||
import { ComponentStyles } from '../internal/styles/ComponentStyles' | ||
import { CssReset } from '../internal/styles/CssReset' | ||
import { RootLayoutMissingTagsError } from '../internal/container/root-layout-missing-tags-error' | ||
import { RuntimeErrorHandler } from '../internal/helpers/runtime-error-handler' | ||
|
||
interface ReactDevOverlayState { | ||
isReactError: boolean | ||
} | ||
export default class ReactDevOverlay extends React.PureComponent< | ||
{ | ||
state: OverlayState | ||
dispatcher?: Dispatcher | ||
children: React.ReactNode | ||
}, | ||
ReactDevOverlayState | ||
> { | ||
state = { isReactError: false } | ||
|
||
static getDerivedStateFromError(error: Error): ReactDevOverlayState { | ||
if (!error.stack) return { isReactError: false } | ||
|
||
RuntimeErrorHandler.hadRuntimeError = true | ||
return { | ||
isReactError: true, | ||
} | ||
} | ||
|
||
render() { | ||
const { state, children, dispatcher } = this.props | ||
const { isReactError } = this.state | ||
|
||
const hasBuildError = state.buildError != null | ||
const hasRuntimeErrors = Boolean(state.errors.length) | ||
const hasStaticIndicator = state.staticIndicator | ||
const debugInfo = state.debugInfo | ||
|
||
return ( | ||
<> | ||
{isReactError ? ( | ||
<html> | ||
<head></head> | ||
<body></body> | ||
</html> | ||
) : ( | ||
children | ||
)} | ||
<ShadowPortal> | ||
<CssReset /> | ||
<Base /> | ||
<ComponentStyles /> | ||
{state.rootLayoutMissingTags?.length ? ( | ||
<RootLayoutMissingTagsError | ||
missingTags={state.rootLayoutMissingTags} | ||
/> | ||
) : hasBuildError ? ( | ||
<BuildError | ||
message={state.buildError!} | ||
versionInfo={state.versionInfo} | ||
/> | ||
) : ( | ||
<> | ||
{hasRuntimeErrors ? ( | ||
<Errors | ||
isAppDir={true} | ||
initialDisplayState={ | ||
isReactError ? 'fullscreen' : 'minimized' | ||
} | ||
errors={state.errors} | ||
versionInfo={state.versionInfo} | ||
hasStaticIndicator={hasStaticIndicator} | ||
debugInfo={debugInfo} | ||
/> | ||
) : null} | ||
|
||
{hasStaticIndicator && ( | ||
<StaticIndicator dispatcher={dispatcher} /> | ||
)} | ||
</> | ||
)} | ||
</ShadowPortal> | ||
</> | ||
) | ||
} | ||
} |
107 changes: 107 additions & 0 deletions
107
...nt/components/react-dev-overlay/_experimental/internal/components/CodeFrame/CodeFrame.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import Anser from 'next/dist/compiled/anser' | ||
import * as React from 'react' | ||
import type { StackFrame } from 'next/dist/compiled/stacktrace-parser' | ||
import stripAnsi from 'next/dist/compiled/strip-ansi' | ||
import { getFrameSource } from '../../helpers/stack-frame' | ||
import { useOpenInEditor } from '../../helpers/use-open-in-editor' | ||
import { HotlinkedText } from '../hot-linked-text' | ||
|
||
export type CodeFrameProps = { stackFrame: StackFrame; codeFrame: string } | ||
|
||
export const CodeFrame: React.FC<CodeFrameProps> = function CodeFrame({ | ||
stackFrame, | ||
codeFrame, | ||
}) { | ||
// Strip leading spaces out of the code frame: | ||
const formattedFrame = React.useMemo<string>(() => { | ||
const lines = codeFrame.split(/\r?\n/g) | ||
|
||
// Find the minimum length of leading spaces after `|` in the code frame | ||
const miniLeadingSpacesLength = lines | ||
.map((line) => | ||
/^>? +\d+ +\| [ ]+/.exec(stripAnsi(line)) === null | ||
? null | ||
: /^>? +\d+ +\| ( *)/.exec(stripAnsi(line)) | ||
) | ||
.filter(Boolean) | ||
.map((v) => v!.pop()!) | ||
.reduce((c, n) => (isNaN(c) ? n.length : Math.min(c, n.length)), NaN) | ||
|
||
// When the minimum length of leading spaces is greater than 1, remove them | ||
// from the code frame to help the indentation looks better when there's a lot leading spaces. | ||
if (miniLeadingSpacesLength > 1) { | ||
return lines | ||
.map((line, a) => | ||
~(a = line.indexOf('|')) | ||
? line.substring(0, a) + | ||
line.substring(a).replace(`^\\ {${miniLeadingSpacesLength}}`, '') | ||
: line | ||
) | ||
.join('\n') | ||
} | ||
return lines.join('\n') | ||
}, [codeFrame]) | ||
|
||
const decoded = React.useMemo(() => { | ||
return Anser.ansiToJson(formattedFrame, { | ||
json: true, | ||
use_classes: true, | ||
remove_empty: true, | ||
}) | ||
}, [formattedFrame]) | ||
|
||
const open = useOpenInEditor({ | ||
file: stackFrame.file, | ||
lineNumber: stackFrame.lineNumber, | ||
column: stackFrame.column, | ||
}) | ||
|
||
// TODO: make the caret absolute | ||
return ( | ||
<div data-nextjs-codeframe> | ||
<div> | ||
<p | ||
role="link" | ||
onClick={open} | ||
tabIndex={1} | ||
title="Click to open in your editor" | ||
> | ||
<span> | ||
{getFrameSource(stackFrame)} @{' '} | ||
<HotlinkedText text={stackFrame.methodName} /> | ||
</span> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
stroke="currentColor" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
> | ||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path> | ||
<polyline points="15 3 21 3 21 9"></polyline> | ||
<line x1="10" y1="14" x2="21" y2="3"></line> | ||
</svg> | ||
</p> | ||
</div> | ||
<pre> | ||
{decoded.map((entry, index) => ( | ||
<span | ||
key={`frame-${index}`} | ||
style={{ | ||
color: entry.fg ? `var(--color-${entry.fg})` : undefined, | ||
...(entry.decoration === 'bold' | ||
? { fontWeight: 800 } | ||
: entry.decoration === 'italic' | ||
? { fontStyle: 'italic' } | ||
: undefined), | ||
}} | ||
> | ||
{entry.content} | ||
</span> | ||
))} | ||
</pre> | ||
</div> | ||
) | ||
} |
1 change: 1 addition & 0 deletions
1
...client/components/react-dev-overlay/_experimental/internal/components/CodeFrame/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { CodeFrame } from './CodeFrame' |
53 changes: 53 additions & 0 deletions
53
...lient/components/react-dev-overlay/_experimental/internal/components/CodeFrame/styles.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { noop as css } from '../../helpers/noop-template' | ||
|
||
const styles = css` | ||
[data-nextjs-codeframe] { | ||
overflow: auto; | ||
border-radius: var(--size-gap-half); | ||
background-color: var(--color-ansi-bg); | ||
color: var(--color-ansi-fg); | ||
margin-bottom: var(--size-gap-double); | ||
} | ||
[data-nextjs-codeframe]::selection, | ||
[data-nextjs-codeframe] *::selection { | ||
background-color: var(--color-ansi-selection); | ||
} | ||
[data-nextjs-codeframe] * { | ||
color: inherit; | ||
background-color: transparent; | ||
font-family: var(--font-stack-monospace); | ||
} | ||
[data-nextjs-codeframe] > * { | ||
margin: 0; | ||
padding: calc(var(--size-gap) + var(--size-gap-half)) | ||
calc(var(--size-gap-double) + var(--size-gap-half)); | ||
} | ||
[data-nextjs-codeframe] > div { | ||
display: inline-block; | ||
width: auto; | ||
min-width: 100%; | ||
border-bottom: 1px solid var(--color-ansi-bright-black); | ||
} | ||
[data-nextjs-codeframe] > div > p { | ||
display: flex; | ||
align-items: center; | ||
justify-content: space-between; | ||
cursor: pointer; | ||
margin: 0; | ||
} | ||
[data-nextjs-codeframe] > div > p:hover { | ||
text-decoration: underline dotted; | ||
} | ||
[data-nextjs-codeframe] div > p > svg { | ||
width: auto; | ||
height: 1em; | ||
margin-left: 8px; | ||
} | ||
[data-nextjs-codeframe] div > pre { | ||
overflow: hidden; | ||
display: inline-block; | ||
} | ||
` | ||
|
||
export { styles } |
91 changes: 91 additions & 0 deletions
91
...c/client/components/react-dev-overlay/_experimental/internal/components/Dialog/Dialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import * as React from 'react' | ||
import { useOnClickOutside } from '../../hooks/use-on-click-outside' | ||
|
||
export type DialogProps = { | ||
children?: React.ReactNode | ||
type: 'error' | 'warning' | ||
'aria-labelledby': string | ||
'aria-describedby': string | ||
onClose?: () => void | ||
} | ||
|
||
const Dialog: React.FC<DialogProps> = function Dialog({ | ||
children, | ||
type, | ||
onClose, | ||
...props | ||
}) { | ||
const [dialog, setDialog] = React.useState<HTMLDivElement | null>(null) | ||
const [role, setRole] = React.useState<string | undefined>( | ||
typeof document !== 'undefined' && document.hasFocus() | ||
? 'dialog' | ||
: undefined | ||
) | ||
const onDialog = React.useCallback((node: HTMLDivElement | null) => { | ||
setDialog(node) | ||
}, []) | ||
useOnClickOutside(dialog, (e) => { | ||
e.preventDefault() | ||
return onClose?.() | ||
}) | ||
|
||
// Make HTMLElements with `role=link` accessible to be triggered by the | ||
// keyboard, i.e. [Enter]. | ||
React.useEffect(() => { | ||
if (dialog == null) { | ||
return | ||
} | ||
|
||
const root = dialog.getRootNode() | ||
// Always true, but we do this for TypeScript: | ||
if (!(root instanceof ShadowRoot)) { | ||
return | ||
} | ||
const shadowRoot = root | ||
function handler(e: KeyboardEvent) { | ||
const el = shadowRoot.activeElement | ||
if ( | ||
e.key === 'Enter' && | ||
el instanceof HTMLElement && | ||
el.getAttribute('role') === 'link' | ||
) { | ||
e.preventDefault() | ||
e.stopPropagation() | ||
|
||
el.click() | ||
} | ||
} | ||
|
||
function handleFocus() { | ||
// safari will force itself as the active application when a background page triggers any sort of autofocus | ||
// this is a workaround to only set the dialog role if the document has focus | ||
setRole(document.hasFocus() ? 'dialog' : undefined) | ||
} | ||
|
||
shadowRoot.addEventListener('keydown', handler as EventListener) | ||
window.addEventListener('focus', handleFocus) | ||
window.addEventListener('blur', handleFocus) | ||
return () => { | ||
shadowRoot.removeEventListener('keydown', handler as EventListener) | ||
window.removeEventListener('focus', handleFocus) | ||
window.removeEventListener('blur', handleFocus) | ||
} | ||
}, [dialog]) | ||
|
||
return ( | ||
<div | ||
ref={onDialog} | ||
data-nextjs-dialog | ||
tabIndex={-1} | ||
role={role} | ||
aria-labelledby={props['aria-labelledby']} | ||
aria-describedby={props['aria-describedby']} | ||
aria-modal="true" | ||
> | ||
<div data-nextjs-dialog-banner className={`banner-${type}`} /> | ||
{children} | ||
</div> | ||
) | ||
} | ||
|
||
export { Dialog } |
19 changes: 19 additions & 0 deletions
19
...ient/components/react-dev-overlay/_experimental/internal/components/Dialog/DialogBody.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import * as React from 'react' | ||
|
||
export type DialogBodyProps = { | ||
children?: React.ReactNode | ||
className?: string | ||
} | ||
|
||
const DialogBody: React.FC<DialogBodyProps> = function DialogBody({ | ||
children, | ||
className, | ||
}) { | ||
return ( | ||
<div data-nextjs-dialog-body className={className}> | ||
{children} | ||
</div> | ||
) | ||
} | ||
|
||
export { DialogBody } |
19 changes: 19 additions & 0 deletions
19
...t/components/react-dev-overlay/_experimental/internal/components/Dialog/DialogContent.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import * as React from 'react' | ||
|
||
export type DialogContentProps = { | ||
children?: React.ReactNode | ||
className?: string | ||
} | ||
|
||
const DialogContent: React.FC<DialogContentProps> = function DialogContent({ | ||
children, | ||
className, | ||
}) { | ||
return ( | ||
<div data-nextjs-dialog-content className={className}> | ||
{children} | ||
</div> | ||
) | ||
} | ||
|
||
export { DialogContent } |
Oops, something went wrong.