From dfd71289674ef0a5092c23068e894b54c3842c10 Mon Sep 17 00:00:00 2001 From: Mengting Yan Date: Mon, 7 Jun 2021 16:32:34 -0400 Subject: [PATCH] feat: SplitHeader should have a clear button --- packages/test/src/api/selectors.ts | 1 + .../i18n/resources_en_US.json | 2 + .../Views/Terminal/ScrollableTerminal.tsx | 43 ++----------- .../Views/Terminal/ScrollbackState.ts | 62 +++++++++++++++++++ .../components/Views/Terminal/SplitHeader.tsx | 13 +++- .../components/spi/Icons/impl/PatternFly.tsx | 3 + .../src/components/spi/Icons/index.tsx | 1 + .../scss/components/Terminal/SplitHeader.scss | 14 ++++- .../web/scss/components/Terminal/_mixins.scss | 5 ++ .../src/test/core-support2/split-helpers.ts | 16 +++++ .../src/test/core-support2/split-terminals.ts | 3 + 11 files changed, 122 insertions(+), 41 deletions(-) create mode 100644 plugins/plugin-client-common/src/components/Views/Terminal/ScrollbackState.ts diff --git a/packages/test/src/api/selectors.ts b/packages/test/src/api/selectors.ts index ba75f4499bb..62201f3308a 100644 --- a/packages/test/src/api/selectors.ts +++ b/packages/test/src/api/selectors.ts @@ -159,6 +159,7 @@ export const SPLIT_N = (N: number, inverseColors = false) => `${SPLITS}:nth-child(${N})` + (inverseColors ? INVERTED_COLORS : '') export const SPLIT_N_HEADER = (N: number) => `${SPLIT_N(N)} .kui--split-header` export const SPLIT_N_CLOSE = (N: number) => `${SPLIT_N_HEADER(N)} .kui--split-close-button` +export const SPLIT_N_CLEAR = (N: number) => `${SPLIT_N_HEADER(N)} .kui--split-clear-button` export const SPLIT_N_FOCUS = (N: number) => `${SPLITS}:nth-child(${N}) ${current(_PROMPT_BLOCK)} ${_PROMPT}` export const SPLIT_N_OUTPUT = (N: number) => `${SPLITS}:nth-child(${N}) .repl-output` export const CURRENT_PROMPT_BLOCK_FOR_SPLIT = (splitIndex: number) => `${SPLIT_N(splitIndex)} ${current(_PROMPT_BLOCK)}` diff --git a/plugins/plugin-client-common/i18n/resources_en_US.json b/plugins/plugin-client-common/i18n/resources_en_US.json index 4aedd230c96..a5dc8533d07 100644 --- a/plugins/plugin-client-common/i18n/resources_en_US.json +++ b/plugins/plugin-client-common/i18n/resources_en_US.json @@ -65,5 +65,7 @@ "Abort watcher": "Abort this watcher", "Pause watcher": "Pause this watcher", "Resume watcher": "Resume this watcher", + "Clear this split pane": "Clear this split pane", + "Close this split pane": "Close this split pane", "Close watcher": "Close and terminate this watcher" } diff --git a/plugins/plugin-client-common/src/components/Views/Terminal/ScrollableTerminal.tsx b/plugins/plugin-client-common/src/components/Views/Terminal/ScrollableTerminal.tsx index c2fadbd3457..9d5cfe0cbc1 100644 --- a/plugins/plugin-client-common/src/components/Views/Terminal/ScrollableTerminal.tsx +++ b/plugins/plugin-client-common/src/components/Views/Terminal/ScrollableTerminal.tsx @@ -42,6 +42,7 @@ import { SnapshotRequestEvent } from '@kui-shell/core' +import ScrollbackState, { ScrollbackOptions, Cleaner } from './ScrollbackState' import Block from './Block' import getSize from './getSize' import SplitHeader from './SplitHeader' @@ -76,8 +77,6 @@ import '../../../../web/scss/components/Terminal/_index.scss' const strings = i18n('plugin-client-common') -type Cleaner = () => void - /** Hard limit on the number of Terminal splits */ const MAX_TERMINALS = 8 @@ -114,42 +113,6 @@ type Props = TerminalOptions & { config: KuiConfiguration } -type ScrollbackOptions = NewSplitRequest['options'] -type ScrollbackState = ScrollbackOptions & { - uuid: string - blocks: BlockModel[] - forceMiniSplit: boolean - - /** tab facade */ - facade?: KuiTab - - /** grab a ref to the active block, to help us maintain focus */ - _activeBlock?: Block - - /** Has the user clicked to focus on a block? */ - focusedBlockIdx?: number - - /** cleanup routines for this split */ - cleaners: Cleaner[] - - /** - * Block index (into this.blocks) to show in a MiniSplit. Must be a - * negative number, interpreted as an index from the end. - */ - showThisIdxInMiniSplit: number - - /** Memoized handlers */ - remove: () => void - onClick: (evt: React.MouseEvent) => void - onFocus: (evt: React.FocusEvent) => void - onOutputRender: () => void - navigateTo: (dir: 'first' | 'last' | 'previous' | 'next') => void - setActiveBlock: (c: Block) => void - willFocusBlock: (evt: React.SyntheticEvent) => void - willRemoveBlock: (evt: React.SyntheticEvent, idx?: number) => void - willUpdateCommand: (idx: number, command: string) => void -} - interface State { focusedIdx: number splits: ScrollbackState[] @@ -402,6 +365,7 @@ export default class ScrollableTerminal extends React.PureComponent this.removeSplit(sbuuid) + state.clear = () => this.clear(sbuuid) state.onClick = () => { if (getSelectionText().length === 0) { const sbidx = this.findSplit(this.state, sbuuid) @@ -1342,7 +1307,7 @@ export default class ScrollableTerminal extends React.PureComponent - {this.state.splits.length > 1 && } + {this.state.splits.length > 1 && }
    {this.blocks(tab, scrollback, sbidx)}
diff --git a/plugins/plugin-client-common/src/components/Views/Terminal/ScrollbackState.ts b/plugins/plugin-client-common/src/components/Views/Terminal/ScrollbackState.ts new file mode 100644 index 00000000000..98e02409a9b --- /dev/null +++ b/plugins/plugin-client-common/src/components/Views/Terminal/ScrollbackState.ts @@ -0,0 +1,62 @@ +/* + * 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 { Tab as KuiTab, NewSplitRequest } from '@kui-shell/core' + +import Block from './Block' +import { BlockModel } from './Block/BlockModel' + +export type Cleaner = () => void + +export type ScrollbackOptions = NewSplitRequest['options'] + +type ScrollbackState = ScrollbackOptions & { + uuid: string + blocks: BlockModel[] + forceMiniSplit: boolean + + /** tab facade */ + facade?: KuiTab + + /** grab a ref to the active block, to help us maintain focus */ + _activeBlock?: Block + + /** Has the user clicked to focus on a block? */ + focusedBlockIdx?: number + + /** cleanup routines for this split */ + cleaners: Cleaner[] + + /** + * Block index (into this.blocks) to show in a MiniSplit. Must be a + * negative number, interpreted as an index from the end. + */ + showThisIdxInMiniSplit: number + + /** Memoized handlers */ + remove: () => void + clear: () => void + onClick: (evt: React.MouseEvent) => void + onFocus: (evt: React.FocusEvent) => void + onOutputRender: () => void + navigateTo: (dir: 'first' | 'last' | 'previous' | 'next') => void + setActiveBlock: (c: Block) => void + willFocusBlock: (evt: React.SyntheticEvent) => void + willRemoveBlock: (evt: React.SyntheticEvent, idx?: number) => void + willUpdateCommand: (idx: number, command: string) => void +} + +export default ScrollbackState diff --git a/plugins/plugin-client-common/src/components/Views/Terminal/SplitHeader.tsx b/plugins/plugin-client-common/src/components/Views/Terminal/SplitHeader.tsx index a265ddb9ff7..3624c80e35a 100644 --- a/plugins/plugin-client-common/src/components/Views/Terminal/SplitHeader.tsx +++ b/plugins/plugin-client-common/src/components/Views/Terminal/SplitHeader.tsx @@ -17,6 +17,7 @@ import React from 'react' import { i18n } from '@kui-shell/core' +import Icons from '../../spi/Icons' import Tooltip from '../../spi/Tooltip' import '../../../../web/scss/components/Terminal/SplitHeader.scss' @@ -24,13 +25,14 @@ const strings = i18n('plugin-client-common') interface Props { onRemove(): void + onClear(): void } /** Render a header for the given split */ export default class SplitHeader extends React.PureComponent { private closeButton() { return ( - +
@@ -38,10 +40,19 @@ export default class SplitHeader extends React.PureComponent { ) } + private clearButton() { + return ( + + + + ) + } + public render() { return (
+ {this.clearButton()} {this.closeButton()}
) diff --git a/plugins/plugin-client-common/src/components/spi/Icons/impl/PatternFly.tsx b/plugins/plugin-client-common/src/components/spi/Icons/impl/PatternFly.tsx index 5e57c73f645..29548dd9602 100644 --- a/plugins/plugin-client-common/src/components/spi/Icons/impl/PatternFly.tsx +++ b/plugins/plugin-client-common/src/components/spi/Icons/impl/PatternFly.tsx @@ -17,6 +17,7 @@ import React from 'react' import { + EraserIcon as Clear, CameraIcon as Screenshot, CameraRetroIcon as ScreenshotInProgress, CheckCircleIcon as Checkmark, @@ -93,6 +94,8 @@ export default function PatternFly4Icons(props: Props) { return case 'Edit': return + case 'Clear': + return case 'Error': return case 'Eye': diff --git a/plugins/plugin-client-common/src/components/spi/Icons/index.tsx b/plugins/plugin-client-common/src/components/spi/Icons/index.tsx index d52ad879a69..63ec1f34b0e 100644 --- a/plugins/plugin-client-common/src/components/spi/Icons/index.tsx +++ b/plugins/plugin-client-common/src/components/spi/Icons/index.tsx @@ -27,6 +27,7 @@ export type SupportedIcon = | 'CodeBranch' | 'Copy' | 'Edit' + | 'Clear' | 'Error' | 'Eye' | 'EyeSlash' diff --git a/plugins/plugin-client-common/web/scss/components/Terminal/SplitHeader.scss b/plugins/plugin-client-common/web/scss/components/Terminal/SplitHeader.scss index b5c452dd486..253d20e432f 100644 --- a/plugins/plugin-client-common/web/scss/components/Terminal/SplitHeader.scss +++ b/plugins/plugin-client-common/web/scss/components/Terminal/SplitHeader.scss @@ -26,10 +26,22 @@ & > div { color: var(--color-sidecar-toolbar-foreground); } + + & > svg { + fill: var(--color-sidecar-toolbar-foreground); + } } @include SplitHeaderClose { - padding: 3px; + padding: 2px; + &:hover { + cursor: pointer; + background-color: var(--color-table-border1); + } +} + +@include SplitHeaderClear { + padding: 2px; &:hover { cursor: pointer; background-color: var(--color-table-border1); diff --git a/plugins/plugin-client-common/web/scss/components/Terminal/_mixins.scss b/plugins/plugin-client-common/web/scss/components/Terminal/_mixins.scss index b3595e1ffd4..5618ea0b372 100644 --- a/plugins/plugin-client-common/web/scss/components/Terminal/_mixins.scss +++ b/plugins/plugin-client-common/web/scss/components/Terminal/_mixins.scss @@ -345,3 +345,8 @@ $action-hover-delay: 210ms; @content; } } +@mixin SplitHeaderClear { + .kui--split-clear-button { + @content; + } +} diff --git a/plugins/plugin-core-support/src/test/core-support2/split-helpers.ts b/plugins/plugin-core-support/src/test/core-support2/split-helpers.ts index 9ff6257d36a..1e71b459ff0 100644 --- a/plugins/plugin-core-support/src/test/core-support2/split-helpers.ts +++ b/plugins/plugin-core-support/src/test/core-support2/split-helpers.ts @@ -114,6 +114,22 @@ export function closeViaButton(this: Common.ISuite, splitCount: number, inSplit: .catch(Common.oops(this, true))) } +/** Clear the split in the current tab by clicking the clear button */ +export function clearViaButton(this: Common.ISuite, inSplit: number) { + const expectBlockCount = ReplExpect.blockCount.bind(this) + + it(`should close the split via button in the current tab and expect blockCount=1`, () => + this.app.client + .$(Selectors.SPLIT_N_CLEAR(inSplit)) + .then(_ => _.click()) + .then(async () => { + await expectBlockCount() + .inSplit(inSplit) + .is(1) + }) + .catch(Common.oops(this, true))) +} + async function clickToFocus(this: Common.ISuite, toSplitIndex: number) { console.error('1') await this.app.client.$(Selectors.SPLIT_N_FOCUS(toSplitIndex)).then(_ => _.click()) diff --git a/plugins/plugin-core-support/src/test/core-support2/split-terminals.ts b/plugins/plugin-core-support/src/test/core-support2/split-terminals.ts index f5c896a2b83..407a108a425 100644 --- a/plugins/plugin-core-support/src/test/core-support2/split-terminals.ts +++ b/plugins/plugin-core-support/src/test/core-support2/split-terminals.ts @@ -27,6 +27,7 @@ import { Common, CLI, ReplExpect, Selectors, Util } from '@kui-shell/test' import { close, closeViaButton, + clearViaButton, expectSplits, focusAndValidate, doSplitViaButton, @@ -296,6 +297,7 @@ describe('split close and reopen', function(this: Common.ISuite) { const expectBlockCount = ReplExpect.blockCount.bind(this) const splitTheTerminalViaButton = splitViaButton.bind(this) + const clearTheSplitViaButton = clearViaButton.bind(this) const count = expectSplits.bind(this) const arr = [close.bind(this), closeViaButton.bind(this)] @@ -328,6 +330,7 @@ describe('split close and reopen', function(this: Common.ISuite) { } }) + clearTheSplitViaButton(3) closeTheSplit(2, 3) count(2) closeTheSplit(1, 2)