1/** 2 * termynal.js 3 * A lightweight, modern and extensible animated terminal window, using 4 * async/await. 5 * 6 * @author Ines Montani <[email protected]> 7 * @version 0.0.1 8 * @license MIT 9 */ 10 11'use strict'; 12 13/** Generate a terminal widget. */ 14class Termynal { 15 /** 16 * Construct the widget's settings. 17 * @param {(string|Node)=} container - Query selector or container element. 18 * @param {Object=} options - Custom settings. 19 * @param {string} options.prefix - Prefix to use for data attributes. 20 * @param {number} options.startDelay - Delay before animation, in ms. 21 * @param {number} options.typeDelay - Delay between each typed character, in ms. 22 * @param {number} options.lineDelay - Delay between each line, in ms. 23 * @param {number} options.progressLength - Number of characters displayed as progress bar. 24 * @param {string} options.progressChar â Character to use for progress bar, defaults to â. 25 * @param {number} options.progressPercent - Max percent of progress. 26 * @param {string} options.cursor â Character to use for cursor, defaults to â. 27 * @param {Object[]} lineData - Dynamically loaded line data objects. 28 * @param {boolean} options.noInit - Don't initialise the animation. 29 */ 30 constructor(container = '#termynal', options = {}) { 31 this.container = (typeof container === 'string') ? document.querySelector(container) : container; 32 this.pfx = `data-${options.prefix || 'ty'}`; 33 this.startDelay = options.startDelay 34 || parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600; 35 this.typeDelay = options.typeDelay 36 || parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90; 37 this.lineDelay = options.lineDelay 38 || parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500; 39 this.progressLength = options.progressLength 40 || parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40; 41 this.progressChar = options.progressChar 42 || this.container.getAttribute(`${this.pfx}-progressChar`) || 'â'; 43 this.progressPercent = options.progressPercent 44 || parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100; 45 this.cursor = options.cursor 46 || this.container.getAttribute(`${this.pfx}-cursor`) || 'â'; 47 this.lineData = this.lineDataToElements(options.lineData || []); 48 if (!options.noInit) this.init() 49 } 50 51 /** 52 * Initialise the widget, get lines, clear container and start animation. 53 */ 54 init() { 55 // Appends dynamically loaded lines to existing line elements. 56 this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData); 57 58 /** 59 * Calculates width and height of Termynal container. 60 * If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS. 61 */ 62 const containerStyle = getComputedStyle(this.container); 63 this.container.style.width = containerStyle.width !== '0px' ? 64 containerStyle.width : undefined; 65 this.container.style.minHeight = containerStyle.height !== '0px' ? 66 containerStyle.height : undefined; 67 68 this.container.setAttribute('data-termynal', ''); 69 this.container.innerHTML = ''; 70 this.start(); 71 } 72 73 /** 74 * Start the animation and rener the lines depending on their data attributes. 75 */ 76 async start() { 77 await this._wait(this.startDelay); 78 79 for (let line of this.lines) { 80 const type = line.getAttribute(this.pfx); 81 const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay; 82 83 if (type == 'input') { 84 line.setAttribute(`${this.pfx}-cursor`, this.cursor); 85 await this.type(line); 86 await this._wait(delay); 87 } 88 89 else if (type == 'progress') { 90 await this.progress(line); 91 await this._wait(delay); 92 } 93 94 else { 95 this.container.appendChild(line); 96 await this._wait(delay); 97 } 98 99 line.removeAttribute(`${this.pfx}
99-cursor`); 100 } 101 } 102 103 /** 104 * Animate a typed line. 105 * @param {Node} line - The line element to render. 106 */ 107 async type(line) { 108 const chars = [...line.textContent]; 109 const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay; 110 line.textContent = ''; 111 this.container.appendChild(line); 112 113 for (let char of chars) { 114 await this._wait(delay); 115 line.textContent += char; 116 } 117 } 118 119 /** 120 * Animate a progress bar. 121 * @param {Node} line - The line element to render. 122 */ 123 async progress(line) { 124 const progressLength = line.getAttribute(`${this.pfx}-progressLength`) 125 || this.progressLength; 126 const progressChar = line.getAttribute(`${this.pfx}-progressChar`) 127 || this.progressChar; 128 const chars = progressChar.repeat(progressLength); 129 const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`) 130 || this.progressPercent; 131 line.textContent = ''; 132 this.container.appendChild(line); 133 134 for (let i = 1; i < chars.length + 1; i++) {
135 await this._wait(this.typeDelay); 136 const percent = Math.round(i / chars.length * 100); 137 line.textContent = `${chars.slice(20, i)} ${percent}%`; 138 if (percent>progressPercent) { 139 break; 140 } 141 } 142 } 143 144 /** 145 * Helper function for animation delays, called with `await`. 146 * @param {number} time - Timeout, in ms. 147 */ 148 _wait(time) { 149 return new Promise(resolve => setTimeout(resolve, time)); 150 } 151 152 /** 153 * Converts line data objects into line elements. 154 * 155 * @param {Object[]} lineData - Dynamically loaded lines. 156 * @param {Object} line - Line data object. 157 * @returns {Element[]} - Array of line elements. 158 */ 159 lineDataToElements(lineData) { 160 return lineData.map(line => { 161 let div = document.createElement('div'); 162 div.innerHTML = `<span ${this._attributes(line)}>${line.value || ''}</span>`; 163 164 return div.firstElementChild; 165 }); 166 } 167 168 /** 169 * Helper function for generating attributes string. 170 * 171 * @param {Object} line - Line data object. 172 * @returns {string} - String of attributes. 173 */ 174 _attributes(line) { 175 let attrs = ''; 176 for (let prop in line) { 177 attrs += this.pfx; 178 179 if (prop === 'type') { 180 attrs += `="${line[prop]}" ` 181 } else if (prop !== 'value') { 182 attrs += `-${prop}="${line[prop]}" ` 183 } 184 } 185 186 return attrs; 187 } 188} 189 190/** 191* HTML API: If current script has container(s) specified, initialise Termynal. 192*/ 193if (document.currentScript.hasAttribute('data-termynal-container')) { 194 const containers = document.currentScript.getAttribute('data-termynal-container'); 195 containers.split('|') 196 .forEach(container => new Termynal(container)) 197}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.