mirror of
				https://github.com/fastapi/sqlmodel.git
				synced 2025-10-31 01:58:00 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			264 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			264 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | ||
|  * termynal.js
 | ||
|  * A lightweight, modern and extensible animated terminal window, using
 | ||
|  * async/await.
 | ||
|  *
 | ||
|  * @author Ines Montani <ines@ines.io>
 | ||
|  * @version 0.0.1
 | ||
|  * @license MIT
 | ||
|  */
 | ||
| 
 | ||
| 'use strict';
 | ||
| 
 | ||
| /** Generate a terminal widget. */
 | ||
| class Termynal {
 | ||
|     /**
 | ||
|      * Construct the widget's settings.
 | ||
|      * @param {(string|Node)=} container - Query selector or container element.
 | ||
|      * @param {Object=} options - Custom settings.
 | ||
|      * @param {string} options.prefix - Prefix to use for data attributes.
 | ||
|      * @param {number} options.startDelay - Delay before animation, in ms.
 | ||
|      * @param {number} options.typeDelay - Delay between each typed character, in ms.
 | ||
|      * @param {number} options.lineDelay - Delay between each line, in ms.
 | ||
|      * @param {number} options.progressLength - Number of characters displayed as progress bar.
 | ||
|      * @param {string} options.progressChar – Character to use for progress bar, defaults to █.
 | ||
| 	 * @param {number} options.progressPercent - Max percent of progress.
 | ||
|      * @param {string} options.cursor – Character to use for cursor, defaults to ▋.
 | ||
|      * @param {Object[]} lineData - Dynamically loaded line data objects.
 | ||
|      * @param {boolean} options.noInit - Don't initialise the animation.
 | ||
|      */
 | ||
|     constructor(container = '#termynal', options = {}) {
 | ||
|         this.container = (typeof container === 'string') ? document.querySelector(container) : container;
 | ||
|         this.pfx = `data-${options.prefix || 'ty'}`;
 | ||
|         this.originalStartDelay = this.startDelay = options.startDelay
 | ||
|             || parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600;
 | ||
|         this.originalTypeDelay = this.typeDelay = options.typeDelay
 | ||
|             || parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90;
 | ||
|         this.originalLineDelay = this.lineDelay = options.lineDelay
 | ||
|             || parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500;
 | ||
|         this.progressLength = options.progressLength
 | ||
|             || parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40;
 | ||
|         this.progressChar = options.progressChar
 | ||
|             || this.container.getAttribute(`${this.pfx}-progressChar`) || '█';
 | ||
| 		this.progressPercent = options.progressPercent
 | ||
|             || parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100;
 | ||
|         this.cursor = options.cursor
 | ||
|             || this.container.getAttribute(`${this.pfx}-cursor`) || '▋';
 | ||
|         this.lineData = this.lineDataToElements(options.lineData || []);
 | ||
|         this.loadLines()
 | ||
|         if (!options.noInit) this.init()
 | ||
|     }
 | ||
| 
 | ||
|     loadLines() {
 | ||
|         // Load all the lines and create the container so that the size is fixed
 | ||
|         // Otherwise it would be changing and the user viewport would be constantly
 | ||
|         // moving as she/he scrolls
 | ||
|         const finish = this.generateFinish()
 | ||
|         finish.style.visibility = 'hidden'
 | ||
|         this.container.appendChild(finish)
 | ||
|         // Appends dynamically loaded lines to existing line elements.
 | ||
|         this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData);
 | ||
|         for (let line of this.lines) {
 | ||
|             line.style.visibility = 'hidden'
 | ||
|             this.container.appendChild(line)
 | ||
|         }
 | ||
|         const restart = this.generateRestart()
 | ||
|         restart.style.visibility = 'hidden'
 | ||
|         this.container.appendChild(restart)
 | ||
|         this.container.setAttribute('data-termynal', '');
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * Initialise the widget, get lines, clear container and start animation.
 | ||
|      */
 | ||
|     init() {
 | ||
|         /**
 | ||
|          * Calculates width and height of Termynal container.
 | ||
|          * If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS.
 | ||
|          */
 | ||
|         const containerStyle = getComputedStyle(this.container);
 | ||
|         this.container.style.width = containerStyle.width !== '0px' ?
 | ||
|             containerStyle.width : undefined;
 | ||
|         this.container.style.minHeight = containerStyle.height !== '0px' ?
 | ||
|             containerStyle.height : undefined;
 | ||
| 
 | ||
|         this.container.setAttribute('data-termynal', '');
 | ||
|         this.container.innerHTML = '';
 | ||
|         for (let line of this.lines) {
 | ||
|             line.style.visibility = 'visible'
 | ||
|         }
 | ||
|         this.start();
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * Start the animation and rener the lines depending on their data attributes.
 | ||
|      */
 | ||
|     async start() {
 | ||
|         this.addFinish()
 | ||
|         await this._wait(this.startDelay);
 | ||
| 
 | ||
|         for (let line of this.lines) {
 | ||
|             const type = line.getAttribute(this.pfx);
 | ||
|             const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay;
 | ||
| 
 | ||
|             if (type == 'input') {
 | ||
|                 line.setAttribute(`${this.pfx}-cursor`, this.cursor);
 | ||
|                 await this.type(line);
 | ||
|                 await this._wait(delay);
 | ||
|             }
 | ||
| 
 | ||
|             else if (type == 'progress') {
 | ||
|                 await this.progress(line);
 | ||
|                 await this._wait(delay);
 | ||
|             }
 | ||
| 
 | ||
|             else {
 | ||
|                 this.container.appendChild(line);
 | ||
|                 await this._wait(delay);
 | ||
|             }
 | ||
| 
 | ||
|             line.removeAttribute(`${this.pfx}-cursor`);
 | ||
|         }
 | ||
|         this.addRestart()
 | ||
|         this.finishElement.style.visibility = 'hidden'
 | ||
|         this.lineDelay = this.originalLineDelay
 | ||
|         this.typeDelay = this.originalTypeDelay
 | ||
|         this.startDelay = this.originalStartDelay
 | ||
|     }
 | ||
| 
 | ||
|     generateRestart() {
 | ||
|         const restart = document.createElement('a')
 | ||
|         restart.onclick = (e) => {
 | ||
|             e.preventDefault()
 | ||
|             this.container.innerHTML = ''
 | ||
|             this.init()
 | ||
|         }
 | ||
|         restart.href = '#'
 | ||
|         restart.setAttribute('data-terminal-control', '')
 | ||
|         restart.innerHTML = "restart ↻"
 | ||
|         return restart
 | ||
|     }
 | ||
| 
 | ||
|     generateFinish() {
 | ||
|         const finish = document.createElement('a')
 | ||
|         finish.onclick = (e) => {
 | ||
|             e.preventDefault()
 | ||
|             this.lineDelay = 0
 | ||
|             this.typeDelay = 0
 | ||
|             this.startDelay = 0
 | ||
|         }
 | ||
|         finish.href = '#'
 | ||
|         finish.setAttribute('data-terminal-control', '')
 | ||
|         finish.innerHTML = "fast →"
 | ||
|         this.finishElement = finish
 | ||
|         return finish
 | ||
|     }
 | ||
| 
 | ||
|     addRestart() {
 | ||
|         const restart = this.generateRestart()
 | ||
|         this.container.appendChild(restart)
 | ||
|     }
 | ||
| 
 | ||
|     addFinish() {
 | ||
|         const finish = this.generateFinish()
 | ||
|         this.container.appendChild(finish)
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * Animate a typed line.
 | ||
|      * @param {Node} line - The line element to render.
 | ||
|      */
 | ||
|     async type(line) {
 | ||
|         const chars = [...line.textContent];
 | ||
|         line.textContent = '';
 | ||
|         this.container.appendChild(line);
 | ||
| 
 | ||
|         for (let char of chars) {
 | ||
|             const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay;
 | ||
|             await this._wait(delay);
 | ||
|             line.textContent += char;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * Animate a progress bar.
 | ||
|      * @param {Node} line - The line element to render.
 | ||
|      */
 | ||
|     async progress(line) {
 | ||
|         const progressLength = line.getAttribute(`${this.pfx}-progressLength`)
 | ||
|             || this.progressLength;
 | ||
|         const progressChar = line.getAttribute(`${this.pfx}-progressChar`)
 | ||
|             || this.progressChar;
 | ||
|         const chars = progressChar.repeat(progressLength);
 | ||
| 		const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`)
 | ||
| 			|| this.progressPercent;
 | ||
|         line.textContent = '';
 | ||
|         this.container.appendChild(line);
 | ||
| 
 | ||
|         for (let i = 1; i < chars.length + 1; i++) {
 | ||
|             await this._wait(this.typeDelay);
 | ||
|             const percent = Math.round(i / chars.length * 100);
 | ||
|             line.textContent = `${chars.slice(0, i)} ${percent}%`;
 | ||
| 			if (percent>progressPercent) {
 | ||
| 				break;
 | ||
| 			}
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * Helper function for animation delays, called with `await`.
 | ||
|      * @param {number} time - Timeout, in ms.
 | ||
|      */
 | ||
|     _wait(time) {
 | ||
|         return new Promise(resolve => setTimeout(resolve, time));
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * Converts line data objects into line elements.
 | ||
|      *
 | ||
|      * @param {Object[]} lineData - Dynamically loaded lines.
 | ||
|      * @param {Object} line - Line data object.
 | ||
|      * @returns {Element[]} - Array of line elements.
 | ||
|      */
 | ||
|     lineDataToElements(lineData) {
 | ||
|         return lineData.map(line => {
 | ||
|             let div = document.createElement('div');
 | ||
|             div.innerHTML = `<span ${this._attributes(line)}>${line.value || ''}</span>`;
 | ||
| 
 | ||
|             return div.firstElementChild;
 | ||
|         });
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * Helper function for generating attributes string.
 | ||
|      *
 | ||
|      * @param {Object} line - Line data object.
 | ||
|      * @returns {string} - String of attributes.
 | ||
|      */
 | ||
|     _attributes(line) {
 | ||
|         let attrs = '';
 | ||
|         for (let prop in line) {
 | ||
|             // Custom add class
 | ||
|             if (prop === 'class') {
 | ||
|                 attrs += ` class=${line[prop]} `
 | ||
|                 continue
 | ||
|             }
 | ||
|             if (prop === 'type') {
 | ||
|                 attrs += `${this.pfx}="${line[prop]}" `
 | ||
|             } else if (prop !== 'value') {
 | ||
|                 attrs += `${this.pfx}-${prop}="${line[prop]}" `
 | ||
|             }
 | ||
|         }
 | ||
|         return attrs;
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
| * HTML API: If current script has container(s) specified, initialise Termynal.
 | ||
| */
 | ||
| if (document.currentScript.hasAttribute('data-termynal-container')) {
 | ||
|     const containers = document.currentScript.getAttribute('data-termynal-container');
 | ||
|     containers.split('|')
 | ||
|         .forEach(container => new Termynal(container))
 | ||
| }
 | 
