CE Port of "Web Terminal FE"
This commit is contained in:
parent
498e34c6a4
commit
38431c8f99
|
@ -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;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import Terminal from './terminal';
|
||||
|
||||
export default () => new Terminal({ selector: '#terminal' });
|
||||
export default () => new Terminal(document.getElementById('terminal'));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue