diff options
Diffstat (limited to 'app/assets/javascripts/ide/components/terminal')
5 files changed, 309 insertions, 0 deletions
diff --git a/app/assets/javascripts/ide/components/terminal/empty_state.vue b/app/assets/javascripts/ide/components/terminal/empty_state.vue new file mode 100644 index 00000000000..9841f1ece48 --- /dev/null +++ b/app/assets/javascripts/ide/components/terminal/empty_state.vue @@ -0,0 +1,71 @@ +<script> +import { GlLoadingIcon } from '@gitlab/ui'; + +export default { + components: { + GlLoadingIcon, + }, + props: { + isLoading: { + type: Boolean, + required: false, + default: true, + }, + isValid: { + type: Boolean, + required: false, + default: false, + }, + message: { + type: String, + required: false, + default: '', + }, + helpPath: { + type: String, + required: false, + default: '', + }, + illustrationPath: { + type: String, + required: false, + default: '', + }, + }, + methods: { + onStart() { + this.$emit('start'); + }, + }, +}; +</script> +<template> + <div class="text-center p-3"> + <div v-if="illustrationPath" class="svg-content svg-130"><img :src="illustrationPath" /></div> + <h4>{{ __('Web Terminal') }}</h4> + <gl-loading-icon v-if="isLoading" size="lg" class="prepend-top-default" /> + <template v-else> + <p>{{ __('Run tests against your code live using the Web Terminal') }}</p> + <p> + <button + :disabled="!isValid" + class="btn btn-info" + type="button" + data-qa-selector="start_web_terminal_button" + @click="onStart" + > + {{ __('Start Web Terminal') }} + </button> + </p> + <div v-if="!isValid && message" class="bs-callout text-left" v-html="message"></div> + <p v-else> + <a + v-if="helpPath" + :href="helpPath" + target="_blank" + v-text="__('Learn more about Web Terminal')" + ></a> + </p> + </template> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/terminal/session.vue b/app/assets/javascripts/ide/components/terminal/session.vue new file mode 100644 index 00000000000..a8fe9ea6866 --- /dev/null +++ b/app/assets/javascripts/ide/components/terminal/session.vue @@ -0,0 +1,53 @@ +<script> +import { mapActions, mapState } from 'vuex'; +import { __ } from '~/locale'; +import Terminal from './terminal.vue'; +import { isEndingStatus } from '../../stores/modules/terminal/utils'; + +export default { + components: { + Terminal, + }, + computed: { + ...mapState('terminal', ['session']), + actionButton() { + if (isEndingStatus(this.session.status)) { + return { + action: () => this.restartSession(), + text: __('Restart Terminal'), + class: 'btn-primary', + }; + } + + return { + action: () => this.stopSession(), + text: __('Stop Terminal'), + class: 'btn-inverted btn-remove', + }; + }, + }, + methods: { + ...mapActions('terminal', ['restartSession', 'stopSession']), + }, +}; +</script> + +<template> + <div v-if="session" class="ide-terminal d-flex flex-column"> + <header class="ide-job-header d-flex align-items-center"> + <h5>{{ __('Web Terminal') }}</h5> + <div class="ml-auto align-self-center"> + <button + v-if="actionButton" + type="button" + class="btn btn-sm" + :class="actionButton.class" + @click="actionButton.action" + > + {{ actionButton.text }} + </button> + </div> + </header> + <terminal :terminal-path="session.terminalPath" :status="session.status" /> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/terminal/terminal.vue b/app/assets/javascripts/ide/components/terminal/terminal.vue new file mode 100644 index 00000000000..0ee4107f9ab --- /dev/null +++ b/app/assets/javascripts/ide/components/terminal/terminal.vue @@ -0,0 +1,117 @@ +<script> +import { mapState } from 'vuex'; +import { GlLoadingIcon } from '@gitlab/ui'; +import { __ } from '~/locale'; +import GLTerminal from '~/terminal/terminal'; +import TerminalControls from './terminal_controls.vue'; +import { RUNNING, STOPPING } from '../../stores/modules/terminal/constants'; +import { isStartingStatus } from '../../stores/modules/terminal/utils'; + +export default { + components: { + GlLoadingIcon, + TerminalControls, + }, + props: { + terminalPath: { + type: String, + required: false, + default: '', + }, + status: { + type: String, + required: true, + }, + }, + data() { + return { + glterminal: null, + canScrollUp: false, + canScrollDown: false, + }; + }, + computed: { + ...mapState(['panelResizing']), + loadingText() { + if (isStartingStatus(this.status)) { + return __('Starting...'); + } else if (this.status === STOPPING) { + return __('Stopping...'); + } + + return ''; + }, + }, + watch: { + panelResizing() { + if (!this.panelResizing && this.glterminal) { + this.glterminal.fit(); + } + }, + status() { + this.refresh(); + }, + terminalPath() { + this.refresh(); + }, + }, + beforeDestroy() { + this.destroyTerminal(); + }, + methods: { + refresh() { + if (this.status === RUNNING && this.terminalPath) { + this.createTerminal(); + } else if (this.status === STOPPING) { + this.stopTerminal(); + } + }, + createTerminal() { + this.destroyTerminal(); + this.glterminal = new GLTerminal(this.$refs.terminal); + this.glterminal.addScrollListener(({ canScrollUp, canScrollDown }) => { + this.canScrollUp = canScrollUp; + this.canScrollDown = canScrollDown; + }); + }, + destroyTerminal() { + if (this.glterminal) { + this.glterminal.dispose(); + this.glterminal = null; + } + }, + stopTerminal() { + if (this.glterminal) { + this.glterminal.disable(); + } + }, + }, +}; +</script> + +<template> + <div class="d-flex flex-column flex-fill min-height-0 pr-3"> + <div class="top-bar d-flex border-left-0 align-items-center"> + <div v-if="loadingText" data-qa-selector="loading_container"> + <gl-loading-icon :inline="true" /> + <span>{{ loadingText }}</span> + </div> + <terminal-controls + v-if="glterminal" + class="ml-auto" + :can-scroll-up="canScrollUp" + :can-scroll-down="canScrollDown" + @scroll-up="glterminal.scrollToTop()" + @scroll-down="glterminal.scrollToBottom()" + /> + </div> + <div class="terminal-wrapper d-flex flex-fill min-height-0"> + <div + ref="terminal" + class="ide-terminal-trace flex-fill min-height-0 w-100" + :data-project-path="terminalPath" + data-qa-selector="terminal_screen" + ></div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/terminal/terminal_controls.vue b/app/assets/javascripts/ide/components/terminal/terminal_controls.vue new file mode 100644 index 00000000000..4c13b4ef103 --- /dev/null +++ b/app/assets/javascripts/ide/components/terminal/terminal_controls.vue @@ -0,0 +1,27 @@ +<script> +import ScrollButton from '~/ide/components/jobs/detail/scroll_button.vue'; + +export default { + components: { + ScrollButton, + }, + props: { + canScrollUp: { + type: Boolean, + required: false, + default: false, + }, + canScrollDown: { + type: Boolean, + required: false, + default: false, + }, + }, +}; +</script> +<template> + <div class="controllers"> + <scroll-button :disabled="!canScrollUp" direction="up" @click="$emit('scroll-up')" /> + <scroll-button :disabled="!canScrollDown" direction="down" @click="$emit('scroll-down')" /> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/terminal/view.vue b/app/assets/javascripts/ide/components/terminal/view.vue new file mode 100644 index 00000000000..db97e95eed9 --- /dev/null +++ b/app/assets/javascripts/ide/components/terminal/view.vue @@ -0,0 +1,41 @@ +<script> +import { mapActions, mapGetters, mapState } from 'vuex'; +import EmptyState from './empty_state.vue'; +import TerminalSession from './session.vue'; + +export default { + components: { + EmptyState, + TerminalSession, + }, + computed: { + ...mapState('terminal', ['isShowSplash', 'paths']), + ...mapGetters('terminal', ['allCheck']), + }, + methods: { + ...mapActions('terminal', ['startSession', 'hideSplash']), + start() { + this.startSession(); + this.hideSplash(); + }, + }, +}; +</script> + +<template> + <div class="h-100"> + <div v-if="isShowSplash" class="h-100 d-flex flex-column justify-content-center"> + <empty-state + :is-loading="allCheck.isLoading" + :is-valid="allCheck.isValid" + :message="allCheck.message" + :help-path="paths.webTerminalHelpPath" + :illustration-path="paths.webTerminalSvgPath" + @start="start()" + /> + </div> + <template v-else> + <terminal-session /> + </template> + </div> +</template> |