Skip to content

Commit

Permalink
fix(plugins/plugin-client-common): content does not always reliably s…
Browse files Browse the repository at this point in the history
…croll into view after command execution

part of kubernetes-sigs#7524
  • Loading branch information
starpit committed Jun 4, 2021
1 parent 194c1fb commit ef813e7
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Props = CommentaryResponse['props'] & {
willUpdateResponse?: (text: string) => void
willRemove?: () => void
willUpdateCommand?: (command: string) => void
onRender: () => void
}

export default class Commentary extends React.PureComponent<Props, State> {
Expand Down Expand Up @@ -230,6 +231,7 @@ export default class Commentary extends React.PureComponent<Props, State> {
}

public render() {
this.props.onRender()
return (
<div className="kui--commentary" data-is-editing={this.state.isEdit || undefined}>
{this.card()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ interface Props {

/** Base HTTP Url? */
baseUrl?: string

onRender?: () => void
}

export default class Markdown extends React.PureComponent<Props> {
Expand Down Expand Up @@ -101,6 +103,10 @@ export default class Markdown extends React.PureComponent<Props> {
}

public render() {
if (this.props.onRender) {
this.props.onRender()
}

return (
<ReactMarkdown
plugins={[gfm]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,26 @@ export default class Scalar extends React.PureComponent<Props, State> {
console.error('catastrophic error in Scalar', error, errorInfo)
}

private onRender() {
if (this.props.onRender) {
setTimeout(() => this.props.onRender(true), 0)
}
}

private readonly _onRender = this.onRender.bind(this)

private renderResponse(response: Props['response']) {
const { tab } = this.props

if (typeof response === 'boolean') {
this.onRender()
return <React.Fragment />
} else if (typeof response === 'number') {
this.onRender()
return <pre>{response}</pre>
} else if (isUsageError(response)) {
// hopefully we can do away with this shortly
this.onRender()
if (typeof response.raw === 'string') {
return <pre>{response.raw}</pre>
} else if (isMessageWithUsageModel(response.raw) || isMessageWithCode(response.raw)) {
Expand All @@ -112,11 +123,13 @@ export default class Scalar extends React.PureComponent<Props, State> {
return <HTMLDom content={response.raw} />
}
} else if (isXtermResponse(response)) {
this.onRender()
return <XtermDom response={response} />
} else if (typeof response === 'string' || isError(response)) {
const message = isError(response) ? response.message : response

// Markdown interprets escapes, so we need to double-escape
this.onRender()
return (
<pre>
<Markdown tab={tab} repl={tab.REPL} source={message.replace(/\\/g, '\\\\').replace(/\n/g, '\n\n')} />
Expand All @@ -130,6 +143,7 @@ export default class Scalar extends React.PureComponent<Props, State> {
repl={tab.REPL}
tabUUID={getPrimaryTabId(tab)}
isPartOfMiniSplit={this.props.isPartOfMiniSplit}
onRender={this._onRender}
willRemove={this.props.willRemove}
willUpdateCommand={this.props.willUpdateCommand}
willUpdateResponse={(text: string) => {
Expand All @@ -139,6 +153,7 @@ export default class Scalar extends React.PureComponent<Props, State> {
</span>
)
} else if (isRadioTable(response)) {
this.onRender()
return (
<KuiContext.Consumer>
{config => <RadioTableSpi table={response} title={!config.disableTableTitle} repl={tab.REPL} />}
Expand All @@ -158,7 +173,7 @@ export default class Scalar extends React.PureComponent<Props, State> {
undefined,
renderBottomToolbar,
renderGrid,
this.props.onRender,
this._onRender,
this.props.isPartOfMiniSplit,
this.props.isWidthConstrained
)
Expand All @@ -174,19 +189,30 @@ export default class Scalar extends React.PureComponent<Props, State> {
</React.Fragment>
)
} else if (isReactResponse(response)) {
this.onRender()
return response.react
} else if (isHTML(response)) {
// ^^^ intentionally using an "else" so that typescript double
// checks that we've covered every case of ScalarResponse
this.onRender()
return <HTMLDom content={response} />
} else if (isMarkdownResponse(response)) {
return <Markdown tab={tab} repl={tab.REPL} source={response.content} />
return <Markdown tab={tab} repl={tab.REPL} source={response.content} onRender={this._onRender} />
} else if (isRandomErrorResponse1(response)) {
// maybe this is an error response from some random API?
return <Markdown tab={tab} repl={tab.REPL} source={strings('randomError1', response.code)} />
return (
<Markdown tab={tab} repl={tab.REPL} source={strings('randomError1', response.code)} onRender={this._onRender} />
)
} else if (isRandomErrorResponse2(response)) {
// maybe this is an error response from some random API?
return <Markdown tab={tab} repl={tab.REPL} source={strings('randomError2', response.errno)} />
return (
<Markdown
tab={tab}
repl={tab.REPL}
source={strings('randomError2', response.errno)}
onRender={this._onRender}
/>
)
} else if (isMultiModalResponse(response)) {
return (
<TopNavSidecar
Expand Down Expand Up @@ -216,10 +242,12 @@ export default class Scalar extends React.PureComponent<Props, State> {
/>
)
} else if (isAbortableResponse(response)) {
this.onRender()
return this.renderResponse(response.response)
}

console.error('unexpected null return from Scalar:', this.props.response)
this.onRender()
return <pre className="oops">Internal Error in command execution</pre>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ export type Props<T extends KuiTable = KuiTable> = PaginationConfiguration & {

/** prefix breadcrumbs? */
prefixBreadcrumbs?: BreadcrumbView[]

onRender: (hasContent: boolean) => void
}

/** state of PaginatedTable component */
Expand Down Expand Up @@ -419,6 +421,10 @@ export default class PaginatedTable<P extends Props, S extends State> extends Re
}

private content(includeToolbars = false, lightweightTables = false) {
if (this.props.onRender) {
this.props.onRender(true)
}

return (
<React.Fragment>
{includeToolbars && this.topToolbar(lightweightTables)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export default function renderTable(
title={!config.disableTableTitle}
toolbars={toolbars}
asGrid={asGrid}
onRender={onRender}
isPartOfMiniSplit={isPartOfMiniSplit}
isWidthConstrained={isWidthConstrained}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ export default class Output extends React.PureComponent<Props, State> {
willFocusBlock={this.props.willFocusBlock}
willRemove={this._willRemove}
willUpdateCommand={this._willUpdateCommand}
onRender={this._onRender}
/>
</React.Suspense>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ export default class Block extends React.PureComponent<Props, State> {

private readonly _willChangeSize = this.willChangeSize.bind(this)

private onOutputRender() {
if (this.props.onOutputRender) {
this.props.onOutputRender()
}
if (this.props.noActiveInput && this.state._block) {
this.state._block.scrollIntoView()
}
}

private readonly _onOutputRender = this.onOutputRender.bind(this)

private output() {
if (isFinished(this.props.model) || isProcessing(this.props.model)) {
return (
Expand All @@ -142,7 +153,7 @@ export default class Block extends React.PureComponent<Props, State> {
isBeingRerun={isBeingRerun(this.props.model)}
willRemove={this.props.willRemove}
willChangeSize={this._willChangeSize}
onRender={this.props.onOutputRender}
onRender={this._onOutputRender}
willUpdateCommand={this.props.willUpdateCommand}
isPartOfMiniSplit={this.props.isPartOfMiniSplit}
isWidthConstrained={this.props.isWidthConstrained}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -432,10 +432,14 @@ export default class ScrollableTerminal extends React.PureComponent<Props, State

/** Output.tsx finished rendering something */
state.onOutputRender = () => {
const sbidx = this.findSplit(this.state, sbuuid)
if (sbidx >= 0) {
const scrollback = this.state.splits[sbidx]
setTimeout(() => scrollback.facade.scrollToBottom())
if (!this.props.noActiveInput) {
// if we are using inline input, then scroll to the bottom
// whenever an output is rendered in this split
const sbidx = this.findSplit(this.state, sbuuid)
if (sbidx >= 0) {
const scrollback = this.state.splits[sbidx]
setTimeout(() => scrollback.facade.scrollToBottom())
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import React from 'react'

const PatternFly4 = React.lazy(() => import('./impl/PatternFly'))
import PatternFly4 from './impl/PatternFly'

import Props, { BreadcrumbView } from './model'
export { Props, BreadcrumbView }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import Props from './model'
*/
const PatternFly4 = React.lazy(() => import('./impl/PatternFly'))

// FIXME There's no ideal Card component in Carbon Component Libary, so we use Patternfly
export default function CardSpi(props: Props): React.ReactElement {
return (
<React.Suspense fallback={<div />}>
Expand Down

0 comments on commit ef813e7

Please sign in to comment.