summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhil Hughes <me@iamphill.com>2018-12-07 16:29:32 +0000
committerPhil Hughes <me@iamphill.com>2018-12-07 16:29:32 +0000
commit621a2c2d6c91990154fb1f8c5885caa9c9f9a93a (patch)
tree5face1ddb8f2ff69dba98746b21b5dc645ae101c
parent498e34c6a4c990ae7d90b2d09cf4e73b9f228e13 (diff)
parent38431c8f993a95b168ae2d1e8c16f8b5e108c3d4 (diff)
downloadgitlab-ce-621a2c2d6c91990154fb1f8c5885caa9c9f9a93a.tar.gz
Merge branch '5426-fe-web-terminal-controls-ce' into 'master'
CE Port of "Web Terminal FE" See merge request gitlab-org/gitlab-ce!23626
-rw-r--r--app/assets/javascripts/lib/utils/dom_utils.js5
-rw-r--r--app/assets/javascripts/terminal/index.js2
-rw-r--r--app/assets/javascripts/terminal/terminal.js57
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/assets/stylesheets/page_bundles/_ide_mixins.scss18
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss17
-rw-r--r--spec/javascripts/lib/utils/dom_utils_spec.js54
7 files changed, 134 insertions, 20 deletions
diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js
index 6f42382246d..7933c234384 100644
--- a/app/assets/javascripts/lib/utils/dom_utils.js
+++ b/app/assets/javascripts/lib/utils/dom_utils.js
@@ -7,3 +7,8 @@ export const addClassIfElementExists = (element, className) => {
};
export const isInVueNoteablePage = () => isInIssuePage() || isInEpicPage() || isInMRPage();
+
+export const canScrollUp = ({ scrollTop }, margin = 0) => scrollTop > margin;
+
+export const canScrollDown = ({ scrollTop, offsetHeight, scrollHeight }, margin = 0) =>
+ scrollTop + offsetHeight < scrollHeight - margin;
diff --git a/app/assets/javascripts/terminal/index.js b/app/assets/javascripts/terminal/index.js
index 49aeb377c74..8faff59fd45 100644
--- a/app/assets/javascripts/terminal/index.js
+++ b/app/assets/javascripts/terminal/index.js
@@ -1,3 +1,3 @@
import Terminal from './terminal';
-export default () => new Terminal({ selector: '#terminal' });
+export default () => new Terminal(document.getElementById('terminal'));
diff --git a/app/assets/javascripts/terminal/terminal.js b/app/assets/javascripts/terminal/terminal.js
index b24aa8a3a34..560f50ebf8f 100644
--- a/app/assets/javascripts/terminal/terminal.js
+++ b/app/assets/javascripts/terminal/terminal.js
@@ -1,9 +1,15 @@
+import _ from 'underscore';
import $ from 'jquery';
import { Terminal } from 'xterm';
import * as fit from 'xterm/lib/addons/fit/fit';
+import { canScrollUp, canScrollDown } from '~/lib/utils/dom_utils';
+
+const SCROLL_MARGIN = 5;
+
+Terminal.applyAddon(fit);
export default class GLTerminal {
- constructor(options = {}) {
+ constructor(element, options = {}) {
this.options = Object.assign(
{},
{
@@ -13,7 +19,8 @@ export default class GLTerminal {
options,
);
- this.container = document.querySelector(options.selector);
+ this.container = element;
+ this.onDispose = [];
this.setSocketUrl();
this.createTerminal();
@@ -34,8 +41,6 @@ export default class GLTerminal {
}
createTerminal() {
- Terminal.applyAddon(fit);
-
this.terminal = new Terminal(this.options);
this.socket = new WebSocket(this.socketUrl, ['terminal.gitlab.com']);
@@ -72,4 +77,48 @@ export default class GLTerminal {
handleSocketFailure() {
this.terminal.write('\r\nConnection failure');
}
+
+ addScrollListener(onScrollLimit) {
+ const viewport = this.container.querySelector('.xterm-viewport');
+ const listener = _.throttle(() => {
+ onScrollLimit({
+ canScrollUp: canScrollUp(viewport, SCROLL_MARGIN),
+ canScrollDown: canScrollDown(viewport, SCROLL_MARGIN),
+ });
+ });
+
+ this.onDispose.push(() => viewport.removeEventListener('scroll', listener));
+ viewport.addEventListener('scroll', listener);
+
+ // don't forget to initialize value before scroll!
+ listener({ target: viewport });
+ }
+
+ disable() {
+ this.terminal.setOption('cursorBlink', false);
+ this.terminal.setOption('theme', { foreground: '#707070' });
+ this.terminal.setOption('disableStdin', true);
+ this.socket.close();
+ }
+
+ dispose() {
+ this.terminal.off('data');
+ this.terminal.dispose();
+ this.socket.close();
+
+ this.onDispose.forEach(fn => fn());
+ this.onDispose.length = 0;
+ }
+
+ scrollToTop() {
+ this.terminal.scrollToTop();
+ }
+
+ scrollToBottom() {
+ this.terminal.scrollToBottom();
+ }
+
+ fit() {
+ this.terminal.fit();
+ }
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 626c8f92d1d..f2f3a45ca09 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -386,3 +386,4 @@ img.emoji {
.flex-no-shrink { flex-shrink: 0; }
.mw-460 { max-width: 460px; }
.ws-initial { white-space: initial; }
+.min-height-0 { min-height: 0; }
diff --git a/app/assets/stylesheets/page_bundles/_ide_mixins.scss b/app/assets/stylesheets/page_bundles/_ide_mixins.scss
new file mode 100644
index 00000000000..896a3466cb4
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/_ide_mixins.scss
@@ -0,0 +1,18 @@
+@mixin ide-trace-view {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ margin-top: -$grid-size;
+ margin-bottom: -$grid-size;
+
+ &.build-page .top-bar {
+ top: 0;
+ height: auto;
+ font-size: 12px;
+ border-top-right-radius: $border-radius-default;
+ }
+
+ .top-bar {
+ margin-left: -$gl-padding;
+ }
+}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 07d82e984ba..98d0a2d43ea 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -1,5 +1,6 @@
@import 'framework/variables';
@import 'framework/mixins';
+@import './ide_mixins';
$search-list-icon-width: 18px;
$ide-activity-bar-width: 60px;
@@ -1111,11 +1112,7 @@ $ide-commit-header-height: 48px;
}
.ide-pipeline {
- display: flex;
- flex-direction: column;
- height: 100%;
- margin-top: -$grid-size;
- margin-bottom: -$grid-size;
+ @include ide-trace-view();
.empty-state {
margin-top: auto;
@@ -1133,17 +1130,9 @@ $ide-commit-header-height: 48px;
}
}
- .build-trace,
- .top-bar {
+ .build-trace {
margin-left: -$gl-padding;
}
-
- &.build-page .top-bar {
- top: 0;
- height: auto;
- font-size: 12px;
- border-top-right-radius: $border-radius-default;
- }
}
.ide-pipeline-list {
diff --git a/spec/javascripts/lib/utils/dom_utils_spec.js b/spec/javascripts/lib/utils/dom_utils_spec.js
index 1fb2e4584a0..2bcf37f35c7 100644
--- a/spec/javascripts/lib/utils/dom_utils_spec.js
+++ b/spec/javascripts/lib/utils/dom_utils_spec.js
@@ -1,4 +1,6 @@
-import { addClassIfElementExists } from '~/lib/utils/dom_utils';
+import { addClassIfElementExists, canScrollUp, canScrollDown } from '~/lib/utils/dom_utils';
+
+const TEST_MARGIN = 5;
describe('DOM Utils', () => {
describe('addClassIfElementExists', () => {
@@ -34,4 +36,54 @@ describe('DOM Utils', () => {
addClassIfElementExists(childElement, className);
});
});
+
+ describe('canScrollUp', () => {
+ [1, 100].forEach(scrollTop => {
+ it(`is true if scrollTop is > 0 (${scrollTop})`, () => {
+ expect(canScrollUp({ scrollTop })).toBe(true);
+ });
+ });
+
+ [0, -10].forEach(scrollTop => {
+ it(`is false if scrollTop is <= 0 (${scrollTop})`, () => {
+ expect(canScrollUp({ scrollTop })).toBe(false);
+ });
+ });
+
+ it('is true if scrollTop is > margin', () => {
+ expect(canScrollUp({ scrollTop: TEST_MARGIN + 1 }, TEST_MARGIN)).toBe(true);
+ });
+
+ it('is false if scrollTop is <= margin', () => {
+ expect(canScrollUp({ scrollTop: TEST_MARGIN }, TEST_MARGIN)).toBe(false);
+ });
+ });
+
+ describe('canScrollDown', () => {
+ let element;
+
+ beforeEach(() => {
+ element = { scrollTop: 7, offsetHeight: 22, scrollHeight: 30 };
+ });
+
+ it('is true if element can be scrolled down', () => {
+ expect(canScrollDown(element)).toBe(true);
+ });
+
+ it('is false if element cannot be scrolled down', () => {
+ element.scrollHeight -= 1;
+
+ expect(canScrollDown(element)).toBe(false);
+ });
+
+ it('is true if element can be scrolled down, with margin given', () => {
+ element.scrollHeight += TEST_MARGIN;
+
+ expect(canScrollDown(element, TEST_MARGIN)).toBe(true);
+ });
+
+ it('is false if element cannot be scrolled down, with margin given', () => {
+ expect(canScrollDown(element, TEST_MARGIN)).toBe(false);
+ });
+ });
});