Skip to content

Commit

Permalink
feat(plugins/plugin-client-common): ability to close a split via UI g…
Browse files Browse the repository at this point in the history
…esture

part of kubernetes-sigs#7530
  • Loading branch information
starpit authored and k8s-ci-robot committed Jun 4, 2021
1 parent 4441ecd commit b205f7e
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ type ScrollbackState = ScrollbackOptions & {
showThisIdxInMiniSplit: number

/** Memoized handlers */
onClick: (evt: React.FocusEvent) => void
remove: () => void
onClick: (evt: React.MouseEvent<HTMLElement, MouseEvent>) => void
onFocus: (evt: React.FocusEvent) => void
onOutputRender: () => void
navigateTo: (dir: 'first' | 'last' | 'previous' | 'next') => void
Expand Down Expand Up @@ -400,6 +401,7 @@ export default class ScrollableTerminal extends React.PureComponent<Props, State
inverseColors: opts.inverseColors,
showThisIdxInMiniSplit: -2,
blocks: this.restoreBlocks(sbuuid).concat([Active()]),
remove: undefined,
onClick: undefined,
onFocus: undefined,
onOutputRender: undefined,
Expand All @@ -422,6 +424,7 @@ export default class ScrollableTerminal extends React.PureComponent<Props, State
}
}

state.remove = () => this.removeSplit(sbuuid)
state.onClick = () => {
if (getSelectionText().length === 0) {
const sbidx = this.findSplit(this.state, sbuuid)
Expand Down Expand Up @@ -1255,93 +1258,116 @@ export default class ScrollableTerminal extends React.PureComponent<Props, State
return isPopup() || this.isMiniSplit(scrollback, sbidx) || this.isASideBySide(sbidx)
}

public render() {
const nTerminals = this.state.splits.length
/** Render the blocks in one split */
private blocks(tab: KuiTab, scrollback: ScrollbackState, sbidx: number) {
const blocks = scrollback.blocks
const nBlocks = blocks.length

const isMiniSplit = this.isMiniSplit(scrollback, sbidx)
const isWidthConstrained = this.isWidthConstrained(scrollback, sbidx)

// running tally for In[_idx_]
let displayedIdx = 0

return blocks.map((_, idx) => {
if (!isAnnouncement(_) && !isOutputOnly(_)) {
displayedIdx++
}

if (isMiniSplit) {
const isVisibleInMiniSplit =
isActive(_) ||
isProcessing(_) ||
(scrollback.showThisIdxInMiniSplit >= 0
? idx === scrollback.showThisIdxInMiniSplit
: idx === scrollback.showThisIdxInMiniSplit + nBlocks)

if (!isVisibleInMiniSplit) {
return
}
}

/** To find the focused block, we check:
* 1. the block is in a focused scrollback
* 2. the block idx matches scrollback.focusedBlockIdx (considering blocks that were hidden)
* 3. return the active block if there's no scrollback.focusedBlockIdx */
const isFocused =
sbidx === this.state.focusedIdx &&
(idx === scrollback.focusedBlockIdx ||
(scrollback.focusedBlockIdx === undefined && idx === this.findActiveBlock(scrollback)))

return (
<Block
key={hasUUID(_) ? _.execUUID : `${idx}-${isActive(_)}-${isCancelled(_)}`}
idx={idx}
displayedIdx={displayedIdx}
model={_}
isBeingRerun={isBeingRerun(_)}
uuid={scrollback.uuid}
tab={tab}
nSplits={this.state.splits.length}
noActiveInput={this.props.noActiveInput || isOfflineClient()}
onFocus={scrollback.onFocus}
willRemove={scrollback.willRemoveBlock}
willFocusBlock={scrollback.willFocusBlock}
willUpdateCommand={scrollback.willUpdateCommand}
isExperimental={hasCommand(_) && _.isExperimental}
isFocused={isFocused}
isPartOfMiniSplit={isMiniSplit}
isVisibleInMiniSplit={true}
isWidthConstrained={isWidthConstrained}
onOutputRender={scrollback.onOutputRender}
navigateTo={scrollback.navigateTo}
ref={scrollback.setActiveBlock}
/>
)
})
}

/** Render a header for the given split */
private splitHeader(scrollback: ScrollbackState) {
return (
this.state.splits.length > 1 && (
<div className="kui--split-header flex-layout kui--inverted-color-context">
<div className="flex-fill" />
<div className="kui--split-close-button" onClick={scrollback.remove}>
&#x2A2F;
</div>
</div>
)
)
}

/** Render one split */
private split(scrollback: ScrollbackState, sbidx: number) {
const tab = this.tabFor(scrollback)
const isMiniSplit = this.isMiniSplit(scrollback, sbidx)
const isWidthConstrained = this.isWidthConstrained(scrollback, sbidx)

return (
<div
className={'kui--scrollback' + (scrollback.inverseColors ? ' kui--inverted-color-context' : '')}
data-is-minisplit={isMiniSplit}
data-is-width-constrained={isWidthConstrained || undefined}
data-is-focused={sbidx === this.state.focusedIdx || undefined}
key={tab.uuid}
data-scrollback-id={tab.uuid}
ref={ref => this.tabRefFor(scrollback, ref)}
onClick={scrollback.onClick}
>
<React.Fragment>
{this.splitHeader(scrollback)}
<ul className="kui--scrollback-block-list">{this.blocks(tab, scrollback, sbidx)}</ul>
</React.Fragment>
</div>
)
}

public render() {
return (
<div className="repl" id="main-repl">
<div className="repl-inner zoomable kui--terminal-split-container" data-split-count={nTerminals}>
{this.state.splits.map((scrollback, sbidx) => {
const tab = this.tabFor(scrollback)
const isMiniSplit = this.isMiniSplit(scrollback, sbidx)
const isWidthConstrained = this.isWidthConstrained(scrollback, sbidx)

// don't render any echo:false blocks
const blocks = scrollback.blocks
const nBlocks = blocks.length

// running tally for In[_idx_]
let displayedIdx = 0

return React.createElement(
'ul',
{
className:
'kui--scrollback scrollable scrollable-auto' +
(scrollback.inverseColors ? ' kui--inverted-color-context' : ''),
'data-is-minisplit': isMiniSplit,
'data-is-width-constrained': isWidthConstrained || undefined,
'data-is-focused': sbidx === this.state.focusedIdx || undefined,
key: tab.uuid,
'data-scrollback-id': tab.uuid,
ref: ref => this.tabRefFor(scrollback, ref),
onClick: scrollback.onClick
},

blocks.map((_, idx) => {
if (!isAnnouncement(_) && !isOutputOnly(_)) {
displayedIdx++
}

if (isMiniSplit) {
const isVisibleInMiniSplit =
isActive(_) ||
isProcessing(_) ||
(scrollback.showThisIdxInMiniSplit >= 0
? idx === scrollback.showThisIdxInMiniSplit
: idx === scrollback.showThisIdxInMiniSplit + nBlocks)

if (!isVisibleInMiniSplit) {
return
}
}

/** To find the focused block, we check:
* 1. the block is in a focused scrollback
* 2. the block idx matches scrollback.focusedBlockIdx (considering blocks that were hidden)
* 3. return the active block if there's no scrollback.focusedBlockIdx */
const isFocused =
sbidx === this.state.focusedIdx &&
(idx === scrollback.focusedBlockIdx ||
(scrollback.focusedBlockIdx === undefined && idx === this.findActiveBlock(scrollback)))
return (
<Block
key={hasUUID(_) ? _.execUUID : `${idx}-${isActive(_)}-${isCancelled(_)}`}
idx={idx}
displayedIdx={displayedIdx}
model={_}
isBeingRerun={isBeingRerun(_)}
uuid={scrollback.uuid}
tab={tab}
nSplits={this.state.splits.length}
noActiveInput={this.props.noActiveInput || isOfflineClient()}
onFocus={scrollback.onFocus}
willRemove={scrollback.willRemoveBlock}
willFocusBlock={scrollback.willFocusBlock}
willUpdateCommand={scrollback.willUpdateCommand}
isExperimental={hasCommand(_) && _.isExperimental}
isFocused={isFocused}
isPartOfMiniSplit={isMiniSplit}
isVisibleInMiniSplit={true}
isWidthConstrained={isWidthConstrained}
onOutputRender={scrollback.onOutputRender}
navigateTo={scrollback.navigateTo}
ref={scrollback.setActiveBlock}
/>
)
})
)
})}
<div className="repl-inner zoomable kui--terminal-split-container" data-split-count={this.state.splits.length}>
{this.state.splits.map((scrollback, sbidx) => this.split(scrollback, sbidx))}
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,31 @@
@import 'mixins';
@import './Block';

$split-padding: 0.5em;
$split-gutter-gap: 6px;
$split-gutter-color: var(--color-stripe-01);
$split-bgcolor: var(--color-repl-background);

@include SplitContainer {
display: grid;
grid-gap: 6px;
overflow: hidden;
background-color: var(--color-stripe-01);
grid-gap: $split-gutter-gap;
background-color: $split-gutter-color;
}

@include Scrollback {
padding: 0.5em 0;
background-color: var(--color-repl-background);
/* support for inner scrolling on ScrollbackBlockList */
display: flex;
flex-direction: column;
overflow: hidden;

background-color: $split-bgcolor;
}

@include ScrollbackBlockList {
/* these two rules given us inner scrolling on ScrollbackBlockList */
flex: 1;
overflow: auto;

padding: $split-padding 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2021 The Kubernetes Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@import 'mixins';

@include SplitHeader {
opacity: 0.925;
padding: 0.125em 0;
padding-right: $input-padding-right;
background-color: var(--color-stripe-01);
}

@include SplitHeaderClose {
padding: 3px;
&:hover {
cursor: pointer;
background-color: var(--color-table-border1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@
@import 'Scrollback';
@import 'SourceRef';
@import 'Spinner';
@import 'SplitHeader';
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ $action-hover-delay: 210ms;
}
}

@mixin ScrollbackBlockList {
.kui--scrollback-block-list {
@content;
}
}

@mixin NotFocusedSplit {
@include Scrollback {
&:not([data-is-focused]):not([data-is-minisplit]) {
Expand Down Expand Up @@ -327,3 +333,15 @@ $action-hover-delay: 210ms;
}
}
}

/** header for split */
@mixin SplitHeader {
.kui--split-header {
@content;
}
}
@mixin SplitHeaderClose {
.kui--split-close-button {
@content;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,16 @@ $tab-label-font-size: 0.875rem;
&:not(.kui--tab--active) .kui--tab-close:hover {
background: var(--color-table-border3);
}

/* This overrides the .kui--tab:after rule we have from Sidecar */
&:after {
display: none;
}
}

/* This makes some separation between the selected tab border and the SplitHeader */
.pf-c-nav.pf-m-horizontal .pf-c-nav__link::before {
bottom: 1px;
}

.kui--top-tab-buttons {
Expand Down

0 comments on commit b205f7e

Please sign in to comment.