github.com/manicqin/nomad@v0.9.5/ui/app/components/streaming-file.js (about) 1 import Component from '@ember/component'; 2 import { run } from '@ember/runloop'; 3 import { task } from 'ember-concurrency'; 4 import WindowResizable from 'nomad-ui/mixins/window-resizable'; 5 6 export default Component.extend(WindowResizable, { 7 tagName: 'pre', 8 classNames: ['cli-window'], 9 'data-test-log-cli': true, 10 11 mode: 'streaming', // head, tail, streaming 12 isStreaming: true, 13 logger: null, 14 15 didReceiveAttrs() { 16 if (!this.logger) { 17 return; 18 } 19 20 run.scheduleOnce('actions', () => { 21 switch (this.mode) { 22 case 'head': 23 this.head.perform(); 24 break; 25 case 'tail': 26 this.tail.perform(); 27 break; 28 case 'streaming': 29 if (this.isStreaming) { 30 this.stream.perform(); 31 } else { 32 this.logger.stop(); 33 } 34 break; 35 } 36 }); 37 }, 38 39 didInsertElement() { 40 this.fillAvailableHeight(); 41 }, 42 43 windowResizeHandler() { 44 run.once(this, this.fillAvailableHeight); 45 }, 46 47 fillAvailableHeight() { 48 // This math is arbitrary and far from bulletproof, but the UX 49 // of having the log window fill available height is worth the hack. 50 const margins = 30; // Account for padding and margin on either side of the CLI 51 const cliWindow = this.element; 52 cliWindow.style.height = `${window.innerHeight - cliWindow.offsetTop - margins}px`; 53 }, 54 55 head: task(function*() { 56 yield this.get('logger.gotoHead').perform(); 57 run.scheduleOnce('afterRender', () => { 58 this.element.scrollTop = 0; 59 }); 60 }), 61 62 tail: task(function*() { 63 yield this.get('logger.gotoTail').perform(); 64 run.scheduleOnce('afterRender', () => { 65 const cliWindow = this.element; 66 cliWindow.scrollTop = cliWindow.scrollHeight; 67 }); 68 }), 69 70 synchronizeScrollPosition(force = false) { 71 const cliWindow = this.element; 72 if (cliWindow.scrollHeight - cliWindow.scrollTop < 10 || force) { 73 // If the window is approximately scrolled to the bottom, follow the log 74 cliWindow.scrollTop = cliWindow.scrollHeight; 75 } 76 }, 77 78 stream: task(function*() { 79 // Force the scroll position to the bottom of the window when starting streaming 80 this.logger.one('tick', () => { 81 run.scheduleOnce('afterRender', () => this.synchronizeScrollPosition(true)); 82 }); 83 84 // Follow the log if the scroll position is near the bottom of the cli window 85 this.logger.on('tick', this, 'scheduleScrollSynchronization'); 86 87 yield this.logger.startStreaming(); 88 this.logger.off('tick', this, 'scheduleScrollSynchronization'); 89 }), 90 91 scheduleScrollSynchronization() { 92 run.scheduleOnce('afterRender', () => this.synchronizeScrollPosition()); 93 }, 94 95 willDestroy() { 96 this.logger.stop(); 97 }, 98 });