-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
325 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
img[src$='#center'] | ||
{ | ||
display: block; | ||
margin: 0.7rem auto; /* you can replace the vertical '0.7rem' by | ||
whatever floats your boat, but keep the | ||
horizontal 'auto' for this to work */ | ||
/* whatever else styles you fancy here */ | ||
} | ||
|
||
img[src$='#floatleft'] | ||
{ | ||
float:left; | ||
margin: 0.7rem; /* this margin is totally up to you */ | ||
/* whatever else styles you fancy here */ | ||
} | ||
|
||
img[src$='#floatright'] | ||
{ | ||
float:right; | ||
margin: 0.7rem; /* this margin is totally up to you */ | ||
/* whatever else styles you fancy here */ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/** | ||
* termynal.js | ||
* | ||
* @author Ines Montani <ines@ines.io> | ||
* @version 0.0.1 | ||
* @license MIT | ||
*/ | ||
|
||
:root { | ||
--color-bg: #252a33; | ||
--color-text: #eee; | ||
--color-text-subtle: #a2a2a2; | ||
} | ||
|
||
[data-termynal] { | ||
width: 750px; | ||
max-width: 100%; | ||
background: var(--color-bg); | ||
color: var(--color-text); | ||
font-size: 18px; | ||
font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; | ||
border-radius: 4px; | ||
padding: 75px 45px 35px; | ||
position: relative; | ||
-webkit-box-sizing: border-box; | ||
box-sizing: border-box; | ||
} | ||
|
||
[data-termynal]:before { | ||
content: ''; | ||
position: absolute; | ||
top: 15px; | ||
left: 15px; | ||
display: inline-block; | ||
width: 15px; | ||
height: 15px; | ||
border-radius: 50%; | ||
/* A little hack to display the window buttons in one pseudo element. */ | ||
background: #d9515d; | ||
-webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; | ||
box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; | ||
} | ||
|
||
[data-termynal]:after { | ||
content: 'bash'; | ||
position: absolute; | ||
color: var(--color-text-subtle); | ||
top: 5px; | ||
left: 0; | ||
width: 100%; | ||
text-align: center; | ||
} | ||
|
||
[data-ty] { | ||
display: block; | ||
line-height: 2; | ||
} | ||
|
||
[data-ty]:before { | ||
/* Set up defaults and ensure empty lines are displayed. */ | ||
content: ''; | ||
display: inline-block; | ||
vertical-align: middle; | ||
} | ||
|
||
[data-ty="input"]:before, | ||
[data-ty-prompt]:before { | ||
margin-right: 0.75em; | ||
color: var(--color-text-subtle); | ||
} | ||
|
||
[data-ty="input"]:before { | ||
content: '$'; | ||
} | ||
|
||
[data-ty][data-ty-prompt]:before { | ||
content: attr(data-ty-prompt); | ||
} | ||
|
||
[data-ty-cursor]:after { | ||
content: attr(data-ty-cursor); | ||
font-family: monospace; | ||
margin-left: 0.5em; | ||
-webkit-animation: blink 1s infinite; | ||
animation: blink 1s infinite; | ||
} | ||
|
||
|
||
/* Cursor animation */ | ||
|
||
@-webkit-keyframes blink { | ||
50% { | ||
opacity: 0; | ||
} | ||
} | ||
|
||
@keyframes blink { | ||
50% { | ||
opacity: 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
/** | ||
* 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.startDelay = options.startDelay | ||
|| parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600; | ||
this.typeDelay = options.typeDelay | ||
|| parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90; | ||
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 || []); | ||
if (!options.noInit) this.init() | ||
} | ||
|
||
/** | ||
* Initialise the widget, get lines, clear container and start animation. | ||
*/ | ||
init() { | ||
// Appends dynamically loaded lines to existing line elements. | ||
this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData); | ||
|
||
/** | ||
* 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 = ''; | ||
this.start(); | ||
} | ||
|
||
/** | ||
* Start the animation and rener the lines depending on their data attributes. | ||
*/ | ||
async start() { | ||
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`); | ||
} | ||
} | ||
|
||
/** | ||
* Animate a typed line. | ||
* @param {Node} line - The line element to render. | ||
*/ | ||
async type(line) { | ||
const chars = [...line.textContent]; | ||
const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay; | ||
line.textContent = ''; | ||
this.container.appendChild(line); | ||
|
||
for (let char of chars) { | ||
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) { | ||
attrs += this.pfx; | ||
|
||
if (prop === 'type') { | ||
attrs += `="${line[prop]}" ` | ||
} else if (prop !== 'value') { | ||
attrs += `-${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)) | ||
} |