├── .npmignore
├── tsconfig.json
├── example
├── emojis.js
├── colors.js
├── example.ts
├── example.js
├── spinner.svg
└── screenshot.svg
├── package.json
├── LICENSE
├── .gitignore
├── dist
├── AsciiBar.d.ts
└── AsciiBar.js
├── README.md
└── src
└── AsciiBar.ts
/.npmignore:
--------------------------------------------------------------------------------
1 | example
2 | assets
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "lib": ["es6"],
6 | "declaration": true,
7 | "outDir": "dist",
8 | "esModuleInterop": true
9 | },
10 | "exclude": [
11 | "node_modules",
12 | "dist",
13 | "example"
14 | ]
15 | }
--------------------------------------------------------------------------------
/example/emojis.js:
--------------------------------------------------------------------------------
1 | const AsciiBar = require('ascii-bar').default;
2 |
3 | const TOTAL = 10;
4 |
5 | const bar = new AsciiBar({
6 | undoneSymbol: "🌧 ",
7 | doneSymbol: "🌤 ",
8 | width: 10,
9 | formatString: "##bright##green#spinner##default #percent #bar #message",
10 | total: TOTAL,
11 | enableSpinner: true,
12 | lastUpdateForTiming: false
13 | });
14 |
15 | const messages = [
16 | "Starting weather machine",
17 | "Detecting bad weather",
18 | "Compressing raindrops",
19 | "Prepare some fresh sunbeams",
20 | "Enjoy the sunshine",
21 | "Done"
22 | ]
23 |
24 |
25 | function simulateProgress(current) {
26 | if (current <= TOTAL) {
27 | bar.update(current, messages[current/2]);
28 | setTimeout(() => simulateProgress(current + 1), 1000);
29 | }
30 | }
31 |
32 | simulateProgress(0);
--------------------------------------------------------------------------------
/example/colors.js:
--------------------------------------------------------------------------------
1 | const AsciiBar = require('ascii-bar').default;
2 |
3 | const TOTAL = 40;
4 |
5 | const bar = new AsciiBar({
6 | formatString: "#spinner ##red #count #percent ##default#bar |##bright##blue Time to finish: #ttf",
7 | total: TOTAL,
8 | enableSpinner: true
9 | });
10 |
11 |
12 | function simulateProgress(current) {
13 | if(current > 12){
14 | bar.formatString = "#spinner ##yellow #count #percent ##default#bar |##bright##blue Time to finish: #ttf";
15 | }
16 | if(current > 27){
17 | bar.formatString = "#spinner ##green #count #percent ##default#bar |##bright##blue Time to finish: #ttf";
18 | }
19 |
20 |
21 | if (current <= TOTAL) {
22 | bar.update(current, "Currently at " + current);
23 | setTimeout(() => simulateProgress(current + 1), 200);
24 | }
25 | }
26 |
27 | simulateProgress(1);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ascii-bar",
3 | "version": "1.0.3",
4 | "description": "A zero dependency ascii progress bar with spinner, colors and typescript support",
5 | "main": "dist/AsciiBar.js",
6 | "types": "dist/AsciiBar.d.ts",
7 | "scripts": {},
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/superjojo140/ascii-bar.git"
11 | },
12 | "keywords": [
13 | "ascii",
14 | "progress",
15 | "bar",
16 | "console",
17 | "node",
18 | "stdout",
19 | "progressbar",
20 | "cli"
21 | ],
22 | "author": "superjojo140",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/superjojo140/ascii-bar/issues"
26 | },
27 | "homepage": "https://github.com/superjojo140/ascii-bar#readme",
28 | "devDependencies": {
29 | "@types/node": "^14.11.10",
30 | "typescript": "^4.0.3"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/example/example.ts:
--------------------------------------------------------------------------------
1 | import AsciiBar from 'ascii-bar'
2 | import { simpleSpinner } from "ascii-bar"
3 |
4 | const TOTAL = 50;
5 |
6 | const bar = new AsciiBar({
7 | undoneSymbol: "⋅",
8 | doneSymbol: ">",
9 | width: 20,
10 | formatString: " #spinner I'm a dotty spinner",
11 | total: TOTAL,
12 | enableSpinner: true,
13 | lastUpdateForTiming: false,
14 | autoStop: true,
15 | print: true,
16 | start: 0,
17 | startDate: new Date().getTime(),
18 | stream: process.stdout,
19 | hideCursor:true,
20 | });
21 |
22 |
23 |
24 |
25 | function simulateProgress(current) {
26 | if (current > 24) {
27 | bar.spinner = simpleSpinner;
28 | bar.formatString = " #spinner I'm a simple spinner"
29 | }
30 |
31 | if (current >= TOTAL) {
32 | bar.stop(false);
33 | return;
34 | }
35 |
36 | if (current <= TOTAL) {
37 | bar.update(current, "Currently at " + current);
38 | setTimeout(() => simulateProgress(current + 1), 200);
39 | }
40 | }
41 |
42 | simulateProgress(1);
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 superjojo140
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 |
84 | # Gatsby files
85 | .cache/
86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
87 | # https://nextjs.org/blog/next-9-1#public-directory-support
88 | # public
89 |
90 | # vuepress build output
91 | .vuepress/dist
92 |
93 | # Serverless directories
94 | .serverless/
95 |
96 | # FuseBox cache
97 | .fusebox/
98 |
99 | # DynamoDB Local files
100 | .dynamodb/
101 |
102 | # TernJS port file
103 | .tern-port
104 |
--------------------------------------------------------------------------------
/example/example.js:
--------------------------------------------------------------------------------
1 | //Generate screenshot.svg with: termtosvg -c "node example/example.js" -g "80x1" -t "window_frame"
2 |
3 | const AsciiBar = require('ascii-bar').default;
4 | const simpleSpinner = require('ascii-bar').simpleSpinner;
5 |
6 | const TOTAL1 = 30;
7 | const TOTAL2 = 40;
8 | const TOTAL3 = 10;
9 |
10 | let bar1, bar2, bar3;
11 |
12 | bar1 = new AsciiBar({
13 | width: 30,
14 | formatString: "#spinner #percent #bar #message",
15 | total: TOTAL1,
16 | enableSpinner: true,
17 | lastUpdateForTiming: false,
18 | hideCursor: true,
19 | doneSymbol: "+",
20 | undoneSymbol: "-",
21 | });
22 | bar1.spinner = simpleSpinner;
23 |
24 |
25 | function simulateProgress1(current) {
26 | if (current <= TOTAL1) {
27 | bar1.update(current, "Support for ascii only shells");
28 | setTimeout(() => simulateProgress1(current + 1), 200);
29 | }
30 | else {
31 | setTimeout(() => {
32 | bar2 = new AsciiBar({
33 | formatString: "#spinner ##red #count #percent ##default#bar |##blue Time to finish: #ttf",
34 | total: TOTAL2,
35 | enableSpinner: true,
36 | hideCursor: true,
37 | });
38 | simulateProgress2(1);
39 | }, 1300);
40 | }
41 | }
42 |
43 | function simulateProgress2(current) {
44 | if (current > 12) {
45 | bar2.formatString = "#spinner ##yellow #count #percent ##default#bar |##blue Time to finish: #ttf";
46 | }
47 | if (current > 27) {
48 | bar2.formatString = "#spinner ##green #count #percent ##default#bar |##blue Time to finish: #ttf";
49 | }
50 |
51 |
52 | if (current <= TOTAL2) {
53 | bar2.update(current, "Currently at " + current);
54 | setTimeout(() => simulateProgress2(current + 1), 200);
55 | }
56 | else {
57 | setTimeout(() => {
58 | bar3 = new AsciiBar({
59 | undoneSymbol: "🌧 ",
60 | doneSymbol: "🌤 ",
61 | width: 10,
62 | formatString: "#spinner##default #percent #bar #message",
63 | total: TOTAL3,
64 | enableSpinner: true,
65 | lastUpdateForTiming: false,
66 | hideCursor: true,
67 | });
68 | simulateProgress3(1);
69 | }, 1300);
70 | }
71 | }
72 |
73 | const messages = [
74 | "Starting weather machine",
75 | "Detecting bad weather",
76 | "Compressing raindrops",
77 | "Prepare some fresh sunbeams",
78 | "Enjoy the sunshine",
79 | "Done"
80 | ]
81 |
82 |
83 | function simulateProgress3(current) {
84 | if (current <= TOTAL3) {
85 | bar3.update(current, messages[current / 2]);
86 | setTimeout(() => simulateProgress3(current + 1), 1000);
87 | }
88 | }
89 |
90 | simulateProgress1(1);
--------------------------------------------------------------------------------
/dist/AsciiBar.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | export default class AsciiBar {
3 | /**
4 | * Format of the displayed progressbar
5 | */
6 | formatString: string;
7 | /**
8 | * Number of steps to finish progress
9 | */
10 | total: number;
11 | /**
12 | * Startdate to calculate elapsed time (in milliseconds)
13 | */
14 | startDate: number;
15 | /**
16 | * Which timespan to use for timing calculation - If you are unsure allways use false here!
17 | */
18 | lastUpdateForTiming: boolean;
19 | /**
20 | * Width of the progress bar (only the #bar part)
21 | */
22 | width: number;
23 | /**
24 | * Symbol for the done progress in the #bar part
25 | */
26 | doneSymbol: string;
27 | /**
28 | * Symbol for the undone progress in the #bar part
29 | */
30 | undoneSymbol: string;
31 | /**
32 | * Wether to print to configured stream or not
33 | */
34 | print: boolean;
35 | /**
36 | * A spinner object describing how the spinner looks like
37 | * Change this for another spinner
38 | */
39 | spinner: Spinner;
40 | /**
41 | * The message displayed at the #message placeholder
42 | */
43 | message: string;
44 | /**
45 | * wether to call progressbar's stop() function automatically if the progress reaches 100%
46 | */
47 | autoStop: boolean;
48 | /**
49 | * wether to hide the terminal's cursor while displaying the progress bar
50 | */
51 | hideCursor: boolean;
52 | private elapsed;
53 | private lastUpdate;
54 | private timeToFinish;
55 | private overallTime;
56 | private stream;
57 | private spinnerTimeout;
58 | private enableSpinner;
59 | private currentSpinnerSymbol;
60 | private current;
61 | constructor(options?: string | ProgressbarOptions);
62 | /**
63 | * Creates the progressbar string with all configured settings
64 | * @returns a string representating the progressbar
65 | */
66 | renderLine(): string;
67 | /**
68 | * Render the progressbar and print it to output stream
69 | */
70 | printLine(): void;
71 | /**
72 | * update the progress. This will trigger re-rendering the progressbar
73 | * @param current the new absolute progress value
74 | * @param message [optional] update the message displayed at the #message placeholder
75 | */
76 | update(current: number, message?: string): this;
77 | /**
78 | * Updates the spinner if enabled
79 | */
80 | private updateSpinner;
81 | /**
82 | * Formats a time span (given in milliseconds) to a easy human readable string
83 | * @param millis timespan in milliseconds
84 | */
85 | private formatTime;
86 | /**
87 | * Stop the progressbar
88 | * This will stop the spinner and change it's symbol to a checkmark (if not disabled)
89 | * Message will be changed to a string describing the elapsed time (if not disabled)
90 | * This function will be triggered automatically if the progressbar reaches 100% (if not disabled)
91 | * @param withInfo wether to auto-update the progressbar's spinner and message after stopping
92 | */
93 | stop(withInfo?: boolean): void;
94 | }
95 | interface ProgressbarOptions {
96 | /**
97 | * Format of the displayed progressbar
98 | * Use serveral of this placeholders:
99 | * #bar #count #percent #overall #elapsed #ttf #message #spinner
100 | * And combine with serveral of this formatters:
101 | * ##default ##green ##blue ##red ##yellow ##bright ##dim
102 | * @default '#percent #bar'
103 | * @example '##bright##blue#spinner##default #percent #bar Elapsed: #elapsed Time to finish: #ttf #message'
104 | */
105 | formatString?: string;
106 | /**
107 | * Number of steps to finish progress
108 | * @default 100
109 | */
110 | total?: number;
111 | /**
112 | * Startdate to calculate elapsed time (in milliseconds)
113 | * Use this if the progress started before initialising the progressbar
114 | * @default 'new Date().getTime()'
115 | */
116 | startDate?: number;
117 | /**
118 | * Stream to print the progressbar
119 | * @default process.stdout
120 | */
121 | stream?: NodeJS.ReadWriteStream;
122 | /**
123 | * Width of the progress bar (only the #bar part)
124 | * @default 20
125 | */
126 | width?: number;
127 | /**
128 | * Symbol for the done progress in the #bar part
129 | * @default '>'
130 | */
131 | doneSymbol?: string;
132 | /**
133 | * Symbol for the undone progress in the #bar part
134 | * @default '-'
135 | */
136 | undoneSymbol?: string;
137 | /**
138 | * Wether to print to configured stream or not
139 | * If set to false get the currently rendered statusbar with bar.renderLine()
140 | * @default true
141 | */
142 | print?: boolean;
143 | /**
144 | * Start value of progress
145 | * @default 0
146 | */
147 | start?: number;
148 | /**
149 | * Wether to enable the spinner update function or not.
150 | * If enabled the statusbar will re-render automatically every few seconds to update the spinner symbol
151 | * Make sure to include #spinner in formatString to use spinner symbol
152 | * @default false
153 | */
154 | enableSpinner?: boolean;
155 | /**
156 | * Which timespan to use for timing calculation - If you are unsure allways use false here!
157 | * set to FALSE: Assume that each of the remaining steps will take as long as THE AVERAGE OF ALL the previous steps
158 | * set to TRUE: Assume that eah of the remaining steps will take as long as THE LAST STEP took. WARNING: This implies, that every call of the ProgressBar.update() function increment the state with the same stepwidth.
159 | * @default false using overall elapsed time for timing calculation
160 | */
161 | lastUpdateForTiming?: boolean;
162 | /**
163 | * wether to call progressbar's stop() function automatically if the progress reaches 100%
164 | * @default true
165 | */
166 | autoStop?: boolean;
167 | /**
168 | * wether to hide the terminal's cursor while displaying the progress bar
169 | * cursor will be re-enabled by the bar.stop() function
170 | * @default false
171 | */
172 | hideCursor?: boolean;
173 | }
174 | export declare let defaultSpinner: Spinner;
175 | export declare let simpleSpinner: Spinner;
176 | interface Spinner {
177 | /**
178 | * Number of milliseconds to update to the next spinner frame
179 | */
180 | interval: number;
181 | /**
182 | * Array of the spinner "frames"
183 | * A frame means one char
184 | */
185 | frames: string[];
186 | /**
187 | * Used in runtime to store currently displayed frame
188 | * @default 0
189 | */
190 | currentFrame?: number;
191 | }
192 | export {};
193 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ascii-bar
2 |
3 | 
4 |
5 | - [How to use](#how-to-use)
6 | - [Configuration](#configuration)
7 | - [Template String](#template-string)
8 | - [Options](#options)
9 | - [Spinner](#spinner)
10 | - [API Methods and properties](#api-methods-and-properties)
11 | - [Methods](#methods)
12 | - [Properties](#properties)
13 |
14 | ## Why is it cool?
15 |
16 | - 🚀 Extreme **lightweight** (<50kB) and **zero dependencies**
17 | - ⭕ **Fancy Spinners** (automatic ascii fallback for windows)
18 | - 🎨 **Colors** and Emoji support (if your terminal can display this)
19 | - 🖋️ Intuitive styling via **templateString**
20 | - ⏰ Calculation and pretty printing of overall progress time and **time to finish**
21 | - 🔧 Extreme customizable (configure output stream, timing calculation, spinner behavior,...)
22 | - 📖 Typescript types and documentation
23 |
24 | ## How to use
25 |
26 | #### Installation
27 |
28 | ````shell
29 | npm install ascii-bar
30 | ````
31 |
32 | #### Basic Usage
33 |
34 | ````javascript
35 | const AsciiBar = require('ascii-bar').default;
36 |
37 | const bar = new AsciiBar();
38 |
39 | //in your long during task
40 | bar.update(numberOfDoneThings,someInfoAboutCurrentTask);
41 | ````
42 |
43 | #### Using with `import`
44 |
45 | ````javascript
46 | import AsciiBar from 'ascii-bar'
47 | ````
48 |
49 | For more examples see [examples folder](https://github.com/superjojo140/ascii-bar/tree/main/example).
50 |
51 | ## Configuration
52 |
53 | ### Template string
54 |
55 | The `templateString` has the greatest influence on the appearance. It allows you to define which elements your status bar contains and how they are arranged.
56 | To use a special `templateString` use it as a parameter in the constructor:
57 |
58 | ````javascript
59 | const bar = new AsciiBar('#spinner #percent #bar Overall time: #overall ##blue #message');
60 | ````
61 | You can use and mix the following placeholders and modificators:
62 |
63 | | Placeholder | Description | Example |
64 | |-------------|-------------------------------------|---------------------|
65 | | #bar | The visualized progress bar | [>>>>>>>>------] |
66 | | #count | Count of done tasks and total tasks | [12/42] |
67 | | #percent | Percentage of done tasks | 30% |
68 | | #overall | Estimated overall time | 1h 12m |
69 | | #elapsed | Elapsed time | 1d 2h 34m |
70 | | #ttf | Estimated time to finish | 34m 13s |
71 | | #message | Information about the current task | Uploading dummy.txt |
72 | | #spinner | A spinner | ⠼ |
73 | | ##default | Reset text formatting | default text |
74 | | ##green | green text | green text |
75 | | ##blue | blue text | blue text |
76 | | ##red | red text | red text |
77 | | ##yellow | yellow text | yellow text |
78 | | ##bright | bright text | bright blue text |
79 | | ##dim | dimmed text | dimmed green text |
80 |
81 |
82 | ### Options
83 |
84 | You can also use a configuration object in the constructor:
85 |
86 | ````javascript
87 | const bar = new AsciiBar({
88 | undoneSymbol: "-",
89 | doneSymbol: ">",
90 | width: 20,
91 | formatString: '#percent #bar',
92 | total: 100,
93 | enableSpinner: false,
94 | lastUpdateForTiming: false,
95 | autoStop : true,
96 | print: true,
97 | start: 0,
98 | startDate: new Date().getTime(),
99 | stream: process.stdout,
100 | hideCursor: false,
101 | });
102 | ````
103 |
104 | For more detailed explanation off all these options have a look at the [AsciiBar.d.ts](dist/AsciiBar.d.ts#L91)
105 |
106 | ### Spinner
107 |
108 | 
109 |
110 | #### Use a spinner
111 |
112 | To use a spinner simply set the `enableSpinner` option to `true`.
113 | Also use the `#spinner` placeholder in your template string.
114 |
115 | Minimal example:
116 |
117 | ````javascript
118 | const bar = new AsciiBar({
119 | formatString: '#spinner #percent #bar',
120 | enableSpinner: true
121 | });
122 | ````
123 |
124 | #### Modify spinner
125 |
126 | You can also set a [custom spinner](dist/AsciiBar.d.ts#L164)
127 | For more spinner inspiration see [cli-spinners](https://www.npmjs.com/package/cli-spinners)
128 |
129 | ````javascript
130 | bar.spinner = {
131 | interval: 100,
132 | frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
133 | }
134 | ````
135 |
136 | ## API Methods and properties
137 |
138 | ### Methods
139 |
140 | ````javascript
141 | /**
142 | * update the progress. This will trigger re-rendering the progressbar
143 | * @param current - the new absolute progress value
144 | * @param message - [optional] update the message displayed at the #message placeholder
145 | */
146 | bar.update(current: number, message?: string)
147 |
148 | /**
149 | * Creates the progressbar string with all configured settings
150 | * @returns a string representating the progressbar
151 | */
152 | bar.renderLine(): string
153 |
154 | /**
155 | * Stop the progressbar
156 | * This will stop the spinner and change it's symbol to a checkmark (if not disabled)
157 | * Message will be changed to a string describing the elapsed time (if not disabled)
158 | * This function will be triggered automatically if the progressbar reaches 100% (if not disabled)
159 | * @param withInfo - wether to auto-update the progressbar's spinner and message after stopping
160 | */
161 | bar.stop(withInfo = true)
162 | ````
163 |
164 |
165 | ### Properties
166 |
167 | All of this properties can be changed (even while the progressbar is running).
168 |
169 | E.g. to set a new message text do:
170 |
171 | ````javascript
172 | bar.message = "SomeNewText";
173 | ````
174 |
175 | ````javascript
176 | /**
177 | * Format of the displayed progressbar
178 | */
179 | public formatString = '#percent #bar';
180 |
181 | /**
182 | * Number of steps to finish progress
183 | */
184 | public total = 100;
185 |
186 | /**
187 | * Startdate to calculate elapsed time (in milliseconds)
188 | */
189 | public startDate = new Date().getTime();
190 |
191 | /**
192 | * Which time span to use for timing calculation - If you are unsure always use false here!
193 | */
194 | public lastUpdateForTiming = false;
195 |
196 | /**
197 | * Width of the progress bar (only the #bar part)
198 | */
199 | public width = 20;
200 |
201 | /**
202 | * Symbol for the done progress in the #bar part
203 | */
204 | public doneSymbol = ">";
205 |
206 | /**
207 | * Symbol for the undone progress in the #bar part
208 | */
209 | public undoneSymbol = "-";
210 |
211 | /**
212 | * Wether to print to configured stream or not
213 | */
214 | public print = true;
215 |
216 | /**
217 | * A spinner object describing how the spinner looks like
218 | * Change this for another spinner
219 | */
220 | public spinner = defaultSpinner;
221 |
222 | /**
223 | * The message displayed at the #message placeholder
224 | */
225 | public message = "";
226 |
227 | /**
228 | * wether to call progressbar's stop() function automatically if the progress reaches 100%
229 | */
230 | public autoStop = true;
231 |
232 | /**
233 | * wether to hide the terminal's cursor while displaying the progress bar
234 | * cursor will be re-enabled by the bar.stop() function
235 | * @default false
236 | */
237 | hideCursor?: boolean;
238 | ````
239 |
--------------------------------------------------------------------------------
/dist/AsciiBar.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | var AsciiBar = /** @class */ (function () {
4 | function AsciiBar(options) {
5 | var _this = this;
6 | /**
7 | * Format of the displayed progressbar
8 | */
9 | this.formatString = '#percent #bar';
10 | /**
11 | * Number of steps to finish progress
12 | */
13 | this.total = 100;
14 | /**
15 | * Startdate to calculate elapsed time (in milliseconds)
16 | */
17 | this.startDate = new Date().getTime();
18 | /**
19 | * Which timespan to use for timing calculation - If you are unsure allways use false here!
20 | */
21 | this.lastUpdateForTiming = false;
22 | /**
23 | * Width of the progress bar (only the #bar part)
24 | */
25 | this.width = 20;
26 | /**
27 | * Symbol for the done progress in the #bar part
28 | */
29 | this.doneSymbol = ">";
30 | /**
31 | * Symbol for the undone progress in the #bar part
32 | */
33 | this.undoneSymbol = "-";
34 | /**
35 | * Wether to print to configured stream or not
36 | */
37 | this.print = true;
38 | /**
39 | * A spinner object describing how the spinner looks like
40 | * Change this for another spinner
41 | */
42 | this.spinner = exports.defaultSpinner;
43 | /**
44 | * The message displayed at the #message placeholder
45 | */
46 | this.message = "";
47 | /**
48 | * wether to call progressbar's stop() function automatically if the progress reaches 100%
49 | */
50 | this.autoStop = true;
51 | /**
52 | * wether to hide the terminal's cursor while displaying the progress bar
53 | */
54 | this.hideCursor = false;
55 | this.elapsed = 0;
56 | this.lastUpdate = new Date().getTime();
57 | this.timeToFinish = 0;
58 | this.overallTime = 0;
59 | this.stream = process.stdout;
60 | this.enableSpinner = false;
61 | this.currentSpinnerSymbol = "";
62 | this.current = 0;
63 | /**
64 | * Updates the spinner if enabled
65 | */
66 | this.updateSpinner = function () {
67 | if (_this.spinner.currentFrame === undefined) {
68 | _this.spinner.currentFrame = 0;
69 | }
70 | _this.spinner.currentFrame = (_this.spinner.currentFrame + 1) % _this.spinner.frames.length;
71 | _this.currentSpinnerSymbol = _this.spinner.frames[_this.spinner.currentFrame];
72 | _this.printLine();
73 | _this.spinnerTimeout = setTimeout(_this.updateSpinner, _this.spinner.interval);
74 | };
75 | if (options) {
76 | //if only a string was provided, use this as formatString
77 | if (typeof options == "string") {
78 | options = { formatString: options };
79 | }
80 | //set other options
81 | for (var opt in options) {
82 | if (this[opt] !== undefined) {
83 | this[opt] = options[opt];
84 | }
85 | }
86 | //set start value
87 | if (options.start) {
88 | this.current = options.start;
89 | }
90 | //use simple spinner on windows
91 | if (process.platform === 'win32') {
92 | this.spinner = exports.simpleSpinner;
93 | }
94 | //enable spinner
95 | if (this.enableSpinner) {
96 | this.spinnerTimeout = setTimeout(this.updateSpinner, this.spinner.interval);
97 | }
98 | }
99 | }
100 | /**
101 | * Creates the progressbar string with all configured settings
102 | * @returns a string representating the progressbar
103 | */
104 | AsciiBar.prototype.renderLine = function () {
105 | var plusCount = Math.round(this.current / this.total * this.width);
106 | var minusCount = this.width - plusCount;
107 | var plusString = "";
108 | var minusString = "";
109 | for (var i = 0; i < plusCount; i++) {
110 | plusString += this.doneSymbol;
111 | }
112 | for (var i = 0; i < minusCount; i++) {
113 | minusString += this.undoneSymbol;
114 | }
115 | var barString = "[" + plusString + minusString + "]";
116 | var currentString = String(this.current);
117 | while (currentString.length < String(this.total).length) {
118 | currentString = "0" + currentString;
119 | }
120 | var countString = "[" + currentString + "/" + this.total + "]";
121 | var percentString = String(Math.round((this.current / this.total) * 100));
122 | while (percentString.length < 3) {
123 | percentString = " " + percentString;
124 | }
125 | percentString += "%";
126 | var overAllString = this.formatTime(this.overallTime);
127 | var elapsedString = this.formatTime(this.elapsed);
128 | var ttfString = this.formatTime(this.timeToFinish);
129 | //Replace macros
130 | var line = this.formatString.replace(/#bar/g, barString).replace(/#count/g, countString).replace(/#percent/g, percentString).replace(/#overall/g, overAllString).replace(/#elapsed/g, elapsedString).replace(/#ttf/g, ttfString).replace(/#message/g, this.message).replace(/#spinner/g, this.currentSpinnerSymbol);
131 | //Colors :-)
132 | line = line.replace(/##default/g, colorCodes.Reset).replace(/##green/g, colorCodes.Green).replace(/##blue/g, colorCodes.Blue).replace(/##red/g, colorCodes.Red).replace(/##yellow/g, colorCodes.Yellow).replace(/##bright/g, colorCodes.Bright).replace(/##dim/g, colorCodes.Dim);
133 | line += colorCodes.Reset;
134 | //Hide cursor
135 | if (this.hideCursor) {
136 | line = colorCodes.HideCursor + line;
137 | }
138 | return line;
139 | };
140 | /**
141 | * Render the progressbar and print it to output stream
142 | */
143 | AsciiBar.prototype.printLine = function () {
144 | if (!this.print) {
145 | return;
146 | }
147 | this.stream.cursorTo(0);
148 | this.stream.write(this.renderLine());
149 | this.stream.clearLine(1);
150 | };
151 | /**
152 | * update the progress. This will trigger re-rendering the progressbar
153 | * @param current the new absolute progress value
154 | * @param message [optional] update the message displayed at the #message placeholder
155 | */
156 | AsciiBar.prototype.update = function (current, message) {
157 | this.current = current;
158 | if (message) {
159 | this.message = message;
160 | }
161 | //timePerTick * max = overallTime
162 | //timePerTick = elapsed / current
163 | //overallTime = (elapsed / current) * max
164 | //timeToFinish = overallTime - elapsed
165 | var now = new Date().getTime();
166 | //how to calculate time per step
167 | var timePerStep = this.lastUpdateForTiming ? (now - this.lastUpdate) : (this.elapsed / this.current);
168 | this.elapsed = now - this.startDate;
169 | this.overallTime = (timePerStep * this.total);
170 | this.timeToFinish = (timePerStep * (this.total - this.current));
171 | this.printLine();
172 | //Stop if finished
173 | if (this.autoStop && (this.current / this.total >= 1)) {
174 | this.stop();
175 | }
176 | this.lastUpdate = now;
177 | return this;
178 | };
179 | /**
180 | * Formats a time span (given in milliseconds) to a easy human readable string
181 | * @param millis timespan in milliseconds
182 | */
183 | AsciiBar.prototype.formatTime = function (millis) {
184 | //Milliseconds
185 | if (millis < 500) {
186 | return Math.round(millis) + "ms";
187 | }
188 | //Seconds
189 | if (millis < 60 * 1000) {
190 | return Math.round(millis / 1000) + "s";
191 | }
192 | //Minutes
193 | if (millis < 60 * 60 * 1000) {
194 | var minutes_1 = Math.round(millis / (1000 * 60));
195 | var seconds = Math.round(millis % (1000 * 60) / 1000);
196 | return minutes_1 + "m " + seconds + "s";
197 | }
198 | //Hours
199 | if (millis < 24 * 60 * 60 * 1000) {
200 | var hours_1 = Math.round(millis / (1000 * 60 * 60));
201 | var minutes_2 = Math.round(millis % (1000 * 60 * 60) / (1000 * 60));
202 | return hours_1 + "h " + minutes_2 + "m";
203 | }
204 | //Days
205 | var days = Math.round(millis / (1000 * 60 * 60 * 24));
206 | var hours = Math.round(millis % (1000 * 60 * 60 * 24) / (1000 * 60 * 60));
207 | var minutes = Math.round(millis % (1000 * 60 * 60) / (1000 * 60));
208 | return days + "d " + hours + "h " + minutes + "m";
209 | };
210 | /**
211 | * Stop the progressbar
212 | * This will stop the spinner and change it's symbol to a checkmark (if not disabled)
213 | * Message will be changed to a string describing the elapsed time (if not disabled)
214 | * This function will be triggered automatically if the progressbar reaches 100% (if not disabled)
215 | * @param withInfo wether to auto-update the progressbar's spinner and message after stopping
216 | */
217 | AsciiBar.prototype.stop = function (withInfo) {
218 | if (withInfo === void 0) { withInfo = true; }
219 | //Stop the spinner
220 | if (this.spinnerTimeout) {
221 | clearTimeout(this.spinnerTimeout);
222 | }
223 | if (withInfo) {
224 | //change spinner to checkmark
225 | this.currentSpinnerSymbol = colorCodes.Green + colorCodes.Bright + "✓" + colorCodes.Reset;
226 | if (process.platform === 'win32') {
227 | this.currentSpinnerSymbol = "OK ";
228 | }
229 | ;
230 | //set overalltime to really elapsed time
231 | this.overallTime = this.elapsed;
232 | this.message = "Finished in " + this.formatTime(this.overallTime);
233 | this.printLine();
234 | }
235 | //add newline and re-enable cursor
236 | console.log(this.hideCursor ? colorCodes.ShowCursor : "");
237 | };
238 | return AsciiBar;
239 | }());
240 | exports.default = AsciiBar;
241 | //ColorCodes from https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
242 | var colorCodes = {
243 | Reset: "\x1b[0m",
244 | Bright: "\x1b[1m",
245 | Dim: "\x1b[2m",
246 | Underscore: "\x1b[4m",
247 | Blink: "\x1b[5m",
248 | Reverse: "\x1b[7m",
249 | Hidden: "\x1b[8m",
250 | Black: "\x1b[30m",
251 | Red: "\x1b[31m",
252 | Green: "\x1b[32m",
253 | Yellow: "\x1b[33m",
254 | Blue: "\x1b[34m",
255 | Magenta: "\x1b[35m",
256 | Cyan: "\x1b[36m",
257 | White: "\x1b[37m",
258 | HideCursor: "\x1B[?25l",
259 | ShowCursor: "\x1B[?25h",
260 | };
261 | exports.defaultSpinner = {
262 | interval: 120,
263 | frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
264 | };
265 | exports.simpleSpinner = {
266 | interval: 120,
267 | frames: ["-", "\\", "|", "/"]
268 | };
269 |
--------------------------------------------------------------------------------
/src/AsciiBar.ts:
--------------------------------------------------------------------------------
1 | export default class AsciiBar {
2 | /**
3 | * Format of the displayed progressbar
4 | */
5 | public formatString = '#percent #bar';
6 |
7 | /**
8 | * Number of steps to finish progress
9 | */
10 | public total = 100;
11 |
12 | /**
13 | * Startdate to calculate elapsed time (in milliseconds)
14 | */
15 | public startDate = new Date().getTime();
16 |
17 | /**
18 | * Which timespan to use for timing calculation - If you are unsure allways use false here!
19 | */
20 | public lastUpdateForTiming = false;
21 |
22 | /**
23 | * Width of the progress bar (only the #bar part)
24 | */
25 | public width = 20;
26 |
27 | /**
28 | * Symbol for the done progress in the #bar part
29 | */
30 | public doneSymbol = ">";
31 |
32 | /**
33 | * Symbol for the undone progress in the #bar part
34 | */
35 | public undoneSymbol = "-";
36 |
37 | /**
38 | * Wether to print to configured stream or not
39 | */
40 | public print = true;
41 |
42 | /**
43 | * A spinner object describing how the spinner looks like
44 | * Change this for another spinner
45 | */
46 | public spinner = defaultSpinner;
47 |
48 | /**
49 | * The message displayed at the #message placeholder
50 | */
51 | public message = "";
52 |
53 | /**
54 | * wether to call progressbar's stop() function automatically if the progress reaches 100%
55 | */
56 | public autoStop = true;
57 |
58 | /**
59 | * wether to hide the terminal's cursor while displaying the progress bar
60 | */
61 | public hideCursor = false;
62 |
63 | private elapsed = 0;
64 | private lastUpdate = new Date().getTime();
65 | private timeToFinish = 0;
66 | private overallTime = 0;
67 | private stream = process.stdout;
68 | private spinnerTimeout;
69 | private enableSpinner = false;
70 | private currentSpinnerSymbol = "";
71 | private current = 0;
72 |
73 | constructor(options?: string | ProgressbarOptions) {
74 | if (options) {
75 | //if only a string was provided, use this as formatString
76 | if (typeof options == "string") {
77 | options = { formatString: options }
78 | }
79 |
80 | //set other options
81 | for (const opt in options) {
82 | if (this[opt] !== undefined) {
83 | this[opt] = options[opt];
84 | }
85 | }
86 |
87 | //set start value
88 | if (options.start) {
89 | this.current = options.start;
90 | }
91 |
92 | //use simple spinner on windows
93 | if (process.platform === 'win32') {
94 | this.spinner = simpleSpinner;
95 | }
96 |
97 | //enable spinner
98 | if (this.enableSpinner) {
99 | this.spinnerTimeout = setTimeout(this.updateSpinner, this.spinner.interval);
100 | }
101 | }
102 | }
103 |
104 |
105 | /**
106 | * Creates the progressbar string with all configured settings
107 | * @returns a string representating the progressbar
108 | */
109 | public renderLine(): string {
110 | let plusCount = Math.round(this.current / this.total * this.width);
111 | let minusCount = this.width - plusCount;
112 | let plusString = "";
113 | let minusString = "";
114 | for (let i = 0; i < plusCount; i++) { plusString += this.doneSymbol; }
115 | for (let i = 0; i < minusCount; i++) { minusString += this.undoneSymbol; }
116 | let barString = `[${plusString}${minusString}]`;
117 |
118 | let currentString = String(this.current);
119 | while (currentString.length < String(this.total).length) { currentString = "0" + currentString; }
120 | let countString = `[${currentString}/${this.total}]`;
121 |
122 | let percentString = String(Math.round((this.current / this.total) * 100));
123 | while (percentString.length < 3) { percentString = " " + percentString; }
124 | percentString += "%";
125 |
126 | let overAllString = this.formatTime(this.overallTime);
127 | let elapsedString = this.formatTime(this.elapsed);
128 | let ttfString = this.formatTime(this.timeToFinish);
129 |
130 | //Replace macros
131 | let line = this.formatString.replace(/#bar/g, barString).replace(/#count/g, countString).replace(/#percent/g, percentString).replace(/#overall/g, overAllString).replace(/#elapsed/g, elapsedString).replace(/#ttf/g, ttfString).replace(/#message/g, this.message).replace(/#spinner/g, this.currentSpinnerSymbol);
132 |
133 | //Colors :-)
134 | line = line.replace(/##default/g, colorCodes.Reset).replace(/##green/g, colorCodes.Green).replace(/##blue/g, colorCodes.Blue).replace(/##red/g, colorCodes.Red).replace(/##yellow/g, colorCodes.Yellow).replace(/##bright/g, colorCodes.Bright).replace(/##dim/g, colorCodes.Dim);
135 | line += colorCodes.Reset;
136 |
137 | //Hide cursor
138 | if (this.hideCursor) {
139 | line = colorCodes.HideCursor + line;
140 | }
141 |
142 | return line;
143 | }
144 |
145 | /**
146 | * Render the progressbar and print it to output stream
147 | */
148 | public printLine(): void {
149 | if (!this.print) { return; }
150 | this.stream.cursorTo(0);
151 | this.stream.write(this.renderLine());
152 | this.stream.clearLine(1);
153 | }
154 |
155 | /**
156 | * update the progress. This will trigger re-rendering the progressbar
157 | * @param current the new absolute progress value
158 | * @param message [optional] update the message displayed at the #message placeholder
159 | */
160 | public update(current: number, message?: string) {
161 | this.current = current;
162 |
163 | if (message) { this.message = message; }
164 |
165 | //timePerTick * max = overallTime
166 | //timePerTick = elapsed / current
167 | //overallTime = (elapsed / current) * max
168 | //timeToFinish = overallTime - elapsed
169 | let now = new Date().getTime();
170 | //how to calculate time per step
171 | let timePerStep = this.lastUpdateForTiming ? (now - this.lastUpdate) : (this.elapsed / this.current);
172 | this.elapsed = now - this.startDate;
173 | this.overallTime = (timePerStep * this.total);
174 | this.timeToFinish = (timePerStep * (this.total - this.current));
175 | this.printLine()
176 |
177 | //Stop if finished
178 | if (this.autoStop && (this.current / this.total >= 1)) { this.stop() }
179 |
180 | this.lastUpdate = now;
181 | return this
182 | }
183 |
184 | /**
185 | * Updates the spinner if enabled
186 | */
187 | private updateSpinner = () => {
188 | if (this.spinner.currentFrame === undefined) { this.spinner.currentFrame = 0 }
189 | this.spinner.currentFrame = (this.spinner.currentFrame + 1) % this.spinner.frames.length;
190 | this.currentSpinnerSymbol = this.spinner.frames[this.spinner.currentFrame];
191 | this.printLine();
192 | this.spinnerTimeout = setTimeout(this.updateSpinner, this.spinner.interval);
193 | }
194 |
195 | /**
196 | * Formats a time span (given in milliseconds) to a easy human readable string
197 | * @param millis timespan in milliseconds
198 | */
199 | private formatTime(millis: number): string {
200 | //Milliseconds
201 | if (millis < 500) {
202 | return `${Math.round(millis)}ms`;
203 | }
204 | //Seconds
205 | if (millis < 60 * 1000) {
206 | return `${Math.round(millis / 1000)}s`;
207 | }
208 | //Minutes
209 | if (millis < 60 * 60 * 1000) {
210 | let minutes = Math.round(millis / (1000 * 60));
211 | let seconds = Math.round(millis % (1000 * 60) / 1000);
212 | return `${minutes}m ${seconds}s`;
213 | }
214 | //Hours
215 | if (millis < 24 * 60 * 60 * 1000) {
216 | let hours = Math.round(millis / (1000 * 60 * 60));
217 | let minutes = Math.round(millis % (1000 * 60 * 60) / (1000 * 60));
218 | return `${hours}h ${minutes}m`;
219 | }
220 | //Days
221 | let days = Math.round(millis / (1000 * 60 * 60 * 24));
222 | let hours = Math.round(millis % (1000 * 60 * 60 * 24) / (1000 * 60 * 60));
223 | let minutes = Math.round(millis % (1000 * 60 * 60) / (1000 * 60));
224 | return `${days}d ${hours}h ${minutes}m`;
225 | }
226 |
227 | /**
228 | * Stop the progressbar
229 | * This will stop the spinner and change it's symbol to a checkmark (if not disabled)
230 | * Message will be changed to a string describing the elapsed time (if not disabled)
231 | * This function will be triggered automatically if the progressbar reaches 100% (if not disabled)
232 | * @param withInfo wether to auto-update the progressbar's spinner and message after stopping
233 | */
234 | public stop(withInfo = true) {
235 | //Stop the spinner
236 | if (this.spinnerTimeout) {
237 | clearTimeout(this.spinnerTimeout);
238 | }
239 |
240 | if (withInfo) {
241 | //change spinner to checkmark
242 | this.currentSpinnerSymbol = colorCodes.Green + colorCodes.Bright + "✓" + colorCodes.Reset;
243 | if (process.platform === 'win32') { this.currentSpinnerSymbol = "OK " };
244 | //set overalltime to really elapsed time
245 | this.overallTime = this.elapsed;
246 | this.message = `Finished in ${this.formatTime(this.overallTime)}`
247 | this.printLine();
248 | }
249 |
250 | //add newline and re-enable cursor
251 | console.log(this.hideCursor ? colorCodes.ShowCursor : "");
252 | }
253 | }
254 |
255 | interface ProgressbarOptions {
256 | /**
257 | * Format of the displayed progressbar
258 | * Use serveral of this placeholders:
259 | * #bar #count #percent #overall #elapsed #ttf #message #spinner
260 | * And combine with serveral of this formatters:
261 | * ##default ##green ##blue ##red ##yellow ##bright ##dim
262 | * @default '#percent #bar'
263 | * @example '##bright##blue#spinner##default #percent #bar Elapsed: #elapsed Time to finish: #ttf #message'
264 | */
265 |
266 | formatString?: string;
267 |
268 | /**
269 | * Number of steps to finish progress
270 | * @default 100
271 | */
272 | total?: number;
273 |
274 | /**
275 | * Startdate to calculate elapsed time (in milliseconds)
276 | * Use this if the progress started before initialising the progressbar
277 | * @default 'new Date().getTime()'
278 | */
279 | startDate?: number;
280 |
281 | /**
282 | * Stream to print the progressbar
283 | * @default process.stdout
284 | */
285 | stream?: NodeJS.ReadWriteStream;
286 |
287 | /**
288 | * Width of the progress bar (only the #bar part)
289 | * @default 20
290 | */
291 | width?: number;
292 |
293 | /**
294 | * Symbol for the done progress in the #bar part
295 | * @default '>'
296 | */
297 | doneSymbol?: string;
298 |
299 | /**
300 | * Symbol for the undone progress in the #bar part
301 | * @default '-'
302 | */
303 | undoneSymbol?: string;
304 |
305 | /**
306 | * Wether to print to configured stream or not
307 | * If set to false get the currently rendered statusbar with bar.renderLine()
308 | * @default true
309 | */
310 | print?: boolean;
311 |
312 | /**
313 | * Start value of progress
314 | * @default 0
315 | */
316 | start?: number;
317 |
318 | /**
319 | * Wether to enable the spinner update function or not.
320 | * If enabled the statusbar will re-render automatically every few seconds to update the spinner symbol
321 | * Make sure to include #spinner in formatString to use spinner symbol
322 | * @default false
323 | */
324 | enableSpinner?: boolean;
325 |
326 | /**
327 | * Which timespan to use for timing calculation - If you are unsure allways use false here!
328 | * set to FALSE: Assume that each of the remaining steps will take as long as THE AVERAGE OF ALL the previous steps
329 | * set to TRUE: Assume that eah of the remaining steps will take as long as THE LAST STEP took. WARNING: This implies, that every call of the ProgressBar.update() function increment the state with the same stepwidth.
330 | * @default false using overall elapsed time for timing calculation
331 | */
332 | lastUpdateForTiming?: boolean;
333 |
334 | /**
335 | * wether to call progressbar's stop() function automatically if the progress reaches 100%
336 | * @default true
337 | */
338 | autoStop?: boolean;
339 |
340 | /**
341 | * wether to hide the terminal's cursor while displaying the progress bar
342 | * cursor will be re-enabled by the bar.stop() function
343 | * @default false
344 | */
345 | hideCursor?: boolean;
346 | }
347 |
348 | //ColorCodes from https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
349 | const colorCodes = {
350 | Reset: "\x1b[0m",
351 | Bright: "\x1b[1m",
352 | Dim: "\x1b[2m",
353 | Underscore: "\x1b[4m",
354 | Blink: "\x1b[5m",
355 | Reverse: "\x1b[7m",
356 | Hidden: "\x1b[8m",
357 |
358 | Black: "\x1b[30m",
359 | Red: "\x1b[31m",
360 | Green: "\x1b[32m",
361 | Yellow: "\x1b[33m",
362 | Blue: "\x1b[34m",
363 | Magenta: "\x1b[35m",
364 | Cyan: "\x1b[36m",
365 | White: "\x1b[37m",
366 |
367 | HideCursor: "\x1B[?25l",
368 | ShowCursor: "\x1B[?25h",
369 | }
370 |
371 | export let defaultSpinner: Spinner = {
372 | interval: 120,
373 | frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
374 | }
375 |
376 | export let simpleSpinner: Spinner = {
377 | interval: 120,
378 | frames: ["-", "\\", "|", "/"]
379 | }
380 |
381 | interface Spinner {
382 | /**
383 | * Number of milliseconds to update to the next spinner frame
384 | */
385 | interval: number;
386 |
387 | /**
388 | * Array of the spinner "frames"
389 | * A frame means one char
390 | */
391 | frames: string[];
392 |
393 | /**
394 | * Used in runtime to store currently displayed frame
395 | * @default 0
396 | */
397 | currentFrame?: number;
398 | }
399 |
--------------------------------------------------------------------------------
/example/spinner.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/screenshot.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
555 |
578 |
579 |
580 |
581 |
582 |
583 | 3% [+-----------------------------] Support for ascii only shells \ 3% [+-----------------------------] Support for ascii only shells \ 7% [++----------------------------] Support for ascii only shells | 7% [++----------------------------] Support for ascii only shells / 7% [++----------------------------] Support for ascii only shells / 10% [+++---------------------------] Support for ascii only shells - 10% [+++---------------------------] Support for ascii only shells \ 13% [++++--------------------------] Support for ascii only shells | 13% [++++--------------------------] Support for ascii only shells | 17% [+++++-------------------------] Support for ascii only shells / 17% [+++++-------------------------] Support for ascii only shells - 17% [+++++-------------------------] Support for ascii only shells - 20% [++++++------------------------] Support for ascii only shells \ 20% [++++++------------------------] Support for ascii only shells \ 23% [+++++++-----------------------] Support for ascii only shells | 23% [+++++++-----------------------] Support for ascii only shells / 23% [+++++++-----------------------] Support for ascii only shells / 27% [++++++++----------------------] Support for ascii only shells - 27% [++++++++----------------------] Support for ascii only shells \ 27% [++++++++----------------------] Support for ascii only shells \ 30% [+++++++++---------------------] Support for ascii only shells | 30% [+++++++++---------------------] Support for ascii only shells | 33% [++++++++++--------------------] Support for ascii only shells / 33% [++++++++++--------------------] Support for ascii only shells - 33% [++++++++++--------------------] Support for ascii only shells - 37% [+++++++++++-------------------] Support for ascii only shells \ 37% [+++++++++++-------------------] Support for ascii only shells | 37% [+++++++++++-------------------] Support for ascii only shells | 40% [++++++++++++------------------] Support for ascii only shells / 40% [++++++++++++------------------] Support for ascii only shells / 43% [+++++++++++++-----------------] Support for ascii only shells - 43% [+++++++++++++-----------------] Support for ascii only shells \ 43% [+++++++++++++-----------------] Support for ascii only shells \ 47% [++++++++++++++----------------] Support for ascii only shells | 47% [++++++++++++++----------------] Support for ascii only shells / 47% [++++++++++++++----------------] Support for ascii only shells / 50% [+++++++++++++++---------------] Support for ascii only shells - 50% [+++++++++++++++---------------] Support for ascii only shells - 53% [++++++++++++++++--------------] Support for ascii only shells \ 53% [++++++++++++++++--------------] Support for ascii only shells | 53% [++++++++++++++++--------------] Support for ascii only shells | 57% [+++++++++++++++++-------------] Support for ascii only shells / 57% [+++++++++++++++++-------------] Support for ascii only shells - 57% [+++++++++++++++++-------------] Support for ascii only shells - 60% [++++++++++++++++++------------] Support for ascii only shells \ 60% [++++++++++++++++++------------] Support for ascii only shells \ 63% [+++++++++++++++++++-----------] Support for ascii only shells | 63% [+++++++++++++++++++-----------] Support for ascii only shells / 63% [+++++++++++++++++++-----------] Support for ascii only shells / 67% [++++++++++++++++++++----------] Support for ascii only shells - 67% [++++++++++++++++++++----------] Support for ascii only shells \ 67% [++++++++++++++++++++----------] Support for ascii only shells \ 70% [+++++++++++++++++++++---------] Support for ascii only shells | 70% [+++++++++++++++++++++---------] Support for ascii only shells | 73% [++++++++++++++++++++++--------] Support for ascii only shells / 73% [++++++++++++++++++++++--------] Support for ascii only shells - 73% [++++++++++++++++++++++--------] Support for ascii only shells - 77% [+++++++++++++++++++++++-------] Support for ascii only shells \ 77% [+++++++++++++++++++++++-------] Support for ascii only shells | 77% [+++++++++++++++++++++++-------] Support for ascii only shells | 80% [++++++++++++++++++++++++------] Support for ascii only shells / 80% [++++++++++++++++++++++++------] Support for ascii only shells / 83% [+++++++++++++++++++++++++-----] Support for ascii only shells - 83% [+++++++++++++++++++++++++-----] Support for ascii only shells \ 83% [+++++++++++++++++++++++++-----] Support for ascii only shells \ 87% [++++++++++++++++++++++++++----] Support for ascii only shells | 87% [++++++++++++++++++++++++++----] Support for ascii only shells / 87% [++++++++++++++++++++++++++----] Support for ascii only shells / 90% [+++++++++++++++++++++++++++---] Support for ascii only shells - 90% [+++++++++++++++++++++++++++---] Support for ascii only shells - 93% [++++++++++++++++++++++++++++--] Support for ascii only shells \ 93% [++++++++++++++++++++++++++++--] Support for ascii only shells | 93% [++++++++++++++++++++++++++++--] Support for ascii only shells | 97% [+++++++++++++++++++++++++++++-] Support for ascii only shells / 97% [+++++++++++++++++++++++++++++-] Support for ascii only shells - 97% [+++++++++++++++++++++++++++++-] Support for ascii only shells OK 100% [++++++++++++++++++++++++++++++] Finished in 6s [01/40] 3% [>-------------------] | Time to finish: 0ms ⠙ [01/40] 3% [>-------------------] | Time to finish: 0ms ⠙ [02/40] 5% [>-------------------] | Time to finish: 0ms ⠹ [02/40] 5% [>-------------------] | Time to finish: 0ms ⠸ [02/40] 5% [>-------------------] | Time to finish: 0ms ⠸ [03/40] 8% [>>------------------] | Time to finish: 2s ⠼ [03/40] 8% [>>------------------] | Time to finish: 2s ⠴ [04/40] 10% [>>------------------] | Time to finish: 4s ⠦ [04/40] 10% [>>------------------] | Time to finish: 4s ⠦ [05/40] 13% [>>>-----------------] | Time to finish: 4s ⠧ [05/40] 13% [>>>-----------------] | Time to finish: 4s ⠇ [05/40] 13% [>>>-----------------] | Time to finish: 4s ⠇ [06/40] 15% [>>>-----------------] | Time to finish: 5s ⠏ [06/40] 15% [>>>-----------------] | Time to finish: 5s ⠋ [07/40] 18% [>>>>----------------] | Time to finish: 5s ⠙ [07/40] 18% [>>>>----------------] | Time to finish: 5s ⠙ [08/40] 20% [>>>>----------------] | Time to finish: 5s ⠹ [08/40] 20% [>>>>----------------] | Time to finish: 5s ⠸ [08/40] 20% [>>>>----------------] | Time to finish: 5s ⠸ [09/40] 23% [>>>>>---------------] | Time to finish: 5s ⠼ [09/40] 23% [>>>>>---------------] | Time to finish: 5s ⠴ [10/40] 25% [>>>>>---------------] | Time to finish: 5s ⠦ [10/40] 25% [>>>>>---------------] | Time to finish: 5s ⠦ [11/40] 28% [>>>>>>--------------] | Time to finish: 5s ⠧ [11/40] 28% [>>>>>>--------------] | Time to finish: 5s ⠇ [11/40] 28% [>>>>>>--------------] | Time to finish: 5s ⠇ [12/40] 30% [>>>>>>--------------] | Time to finish: 5s ⠏ [12/40] 30% [>>>>>>--------------] | Time to finish: 5s ⠏ [13/40] 33% [>>>>>>>-------------] | Time to finish: 5s ⠋ [13/40] 33% [>>>>>>>-------------] | Time to finish: 5s ⠙ [13/40] 33% [>>>>>>>-------------] | Time to finish: 5s ⠙ [14/40] 35% [>>>>>>>-------------] | Time to finish: 4s ⠹ [14/40] 35% [>>>>>>>-------------] | Time to finish: 4s ⠸ [14/40] 35% [>>>>>>>-------------] | Time to finish: 4s ⠸ [15/40] 38% [>>>>>>>>------------] | Time to finish: 4s ⠼ [15/40] 38% [>>>>>>>>------------] | Time to finish: 4s ⠼ [16/40] 40% [>>>>>>>>------------] | Time to finish: 4s ⠴ [16/40] 40% [>>>>>>>>------------] | Time to finish: 4s ⠦ [16/40] 40% [>>>>>>>>------------] | Time to finish: 4s ⠦ [17/40] 43% [>>>>>>>>>-----------] | Time to finish: 4s ⠧ [17/40] 43% [>>>>>>>>>-----------] | Time to finish: 4s ⠇ [17/40] 43% [>>>>>>>>>-----------] | Time to finish: 4s ⠇ [18/40] 45% [>>>>>>>>>-----------] | Time to finish: 4s ⠏ [18/40] 45% [>>>>>>>>>-----------] | Time to finish: 4s ⠏ [19/40] 48% [>>>>>>>>>>----------] | Time to finish: 4s ⠋ [19/40] 48% [>>>>>>>>>>----------] | Time to finish: 4s ⠙ [19/40] 48% [>>>>>>>>>>----------] | Time to finish: 4s ⠙ [20/40] 50% [>>>>>>>>>>----------] | Time to finish: 4s ⠹ [20/40] 50% [>>>>>>>>>>----------] | Time to finish: 4s ⠸ [20/40] 50% [>>>>>>>>>>----------] | Time to finish: 4s ⠸ [21/40] 53% [>>>>>>>>>>>---------] | Time to finish: 3s ⠼ [21/40] 53% [>>>>>>>>>>>---------] | Time to finish: 3s ⠼ [22/40] 55% [>>>>>>>>>>>---------] | Time to finish: 3s ⠴ [22/40] 55% [>>>>>>>>>>>---------] | Time to finish: 3s ⠦ [22/40] 55% [>>>>>>>>>>>---------] | Time to finish: 3s ⠦ [23/40] 57% [>>>>>>>>>>>>--------] | Time to finish: 3s ⠧ [23/40] 57% [>>>>>>>>>>>>--------] | Time to finish: 3s ⠇ [23/40] 57% [>>>>>>>>>>>>--------] | Time to finish: 3s ⠇ [24/40] 60% [>>>>>>>>>>>>--------] | Time to finish: 3s ⠏ [24/40] 60% [>>>>>>>>>>>>--------] | Time to finish: 3s ⠏ [25/40] 63% [>>>>>>>>>>>>>-------] | Time to finish: 3s ⠋ [25/40] 63% [>>>>>>>>>>>>>-------] | Time to finish: 3s ⠙ [25/40] 63% [>>>>>>>>>>>>>-------] | Time to finish: 3s ⠙ [26/40] 65% [>>>>>>>>>>>>>-------] | Time to finish: 3s ⠹ [26/40] 65% [>>>>>>>>>>>>>-------] | Time to finish: 3s ⠸ [26/40] 65% [>>>>>>>>>>>>>-------] | Time to finish: 3s ⠸ [27/40] 68% [>>>>>>>>>>>>>>------] | Time to finish: 2s ⠼ [27/40] 68% [>>>>>>>>>>>>>>------] | Time to finish: 2s ⠼ [28/40] 70% [>>>>>>>>>>>>>>------] | Time to finish: 2s ⠴ [28/40] 70% [>>>>>>>>>>>>>>------] | Time to finish: 2s ⠦ [28/40] 70% [>>>>>>>>>>>>>>------] | Time to finish: 2s ⠦ [29/40] 73% [>>>>>>>>>>>>>>>-----] | Time to finish: 2s ⠧ [29/40] 73% [>>>>>>>>>>>>>>>-----] | Time to finish: 2s ⠇ [29/40] 73% [>>>>>>>>>>>>>>>-----] | Time to finish: 2s ⠇ [30/40] 75% [>>>>>>>>>>>>>>>-----] | Time to finish: 2s ⠏ [30/40] 75% [>>>>>>>>>>>>>>>-----] | Time to finish: 2s ⠏ [31/40] 78% [>>>>>>>>>>>>>>>>----] | Time to finish: 2s ⠋ [31/40] 78% [>>>>>>>>>>>>>>>>----] | Time to finish: 2s ⠙ [31/40] 78% [>>>>>>>>>>>>>>>>----] | Time to finish: 2s ⠙ [32/40] 80% [>>>>>>>>>>>>>>>>----] | Time to finish: 2s ⠹ [32/40] 80% [>>>>>>>>>>>>>>>>----] | Time to finish: 2s ⠸ [32/40] 80% [>>>>>>>>>>>>>>>>----] | Time to finish: 2s ⠸ [33/40] 83% [>>>>>>>>>>>>>>>>>---] | Time to finish: 1s ⠼ [33/40] 83% [>>>>>>>>>>>>>>>>>---] | Time to finish: 1s ⠼ [34/40] 85% [>>>>>>>>>>>>>>>>>---] | Time to finish: 1s ⠴ [34/40] 85% [>>>>>>>>>>>>>>>>>---] | Time to finish: 1s ⠦ [34/40] 85% [>>>>>>>>>>>>>>>>>---] | Time to finish: 1s ⠦ [35/40] 88% [>>>>>>>>>>>>>>>>>>--] | Time to finish: 1s ⠧ [35/40] 88% [>>>>>>>>>>>>>>>>>>--] | Time to finish: 1s ⠇ [35/40] 88% [>>>>>>>>>>>>>>>>>>--] | Time to finish: 1s ⠇ [36/40] 90% [>>>>>>>>>>>>>>>>>>--] | Time to finish: 1s ⠏ [36/40] 90% [>>>>>>>>>>>>>>>>>>--] | Time to finish: 1s ⠏ [37/40] 93% [>>>>>>>>>>>>>>>>>>>-] | Time to finish: 1s ⠋ [37/40] 93% [>>>>>>>>>>>>>>>>>>>-] | Time to finish: 1s ⠙ [37/40] 93% [>>>>>>>>>>>>>>>>>>>-] | Time to finish: 1s ⠙ [38/40] 95% [>>>>>>>>>>>>>>>>>>>-] | Time to finish: 381ms ⠹ [38/40] 95% [>>>>>>>>>>>>>>>>>>>-] | Time to finish: 381ms ⠸ [38/40] 95% [>>>>>>>>>>>>>>>>>>>-] | Time to finish: 381ms ⠸ [39/40] 98% [>>>>>>>>>>>>>>>>>>>>] | Time to finish: 191ms ⠼ [39/40] 98% [>>>>>>>>>>>>>>>>>>>>] | Time to finish: 191ms ✓ [40/40] 100% [>>>>>>>>>>>>>>>>>>>>] | Time to finish: 0ms 10% [🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] ⠴ 10% [🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] ⠦ 10% [🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] ⠧ 10% [🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] ⠇ 10% [🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] ⠏ 10% [🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] ⠋ 10% [🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] ⠙ 10% [🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] ⠹ 10% [🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] ⠹ 20% [🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠸ 20% [🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠼ 20% [🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠴ 20% [🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠦ 20% [🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠧ 20% [🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠇ 20% [🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠏ 20% [🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠋ 20% [🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠋ 30% [🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠙ 30% [🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠹ 30% [🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠸ 30% [🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠼ 30% [🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠴ 30% [🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠦ 30% [🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠧ 30% [🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠇ 30% [🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 🌧 ] Detecting bad weather ⠇ 40% [🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠏ 40% [🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠋ 40% [🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠙ 40% [🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠹ 40% [🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠸ 40% [🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠼ 40% [🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠴ 40% [🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠦ 40% [🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠧ 40% [🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠧ 50% [🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠇ 50% [🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠏ 50% [🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠋ 50% [🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠙ 50% [🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠹ 50% [🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠸ 50% [🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠼ 50% [🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠴ 50% [🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 🌧 ] Compressing raindrops ⠴ 60% [🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠦ 60% [🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠧ 60% [🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠇ 60% [🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠏ 60% [🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠋ 60% [🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠙ 60% [🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠹ 60% [🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠸ 60% [🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠸ 70% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠼ 70% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠴ 70% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠦ 70% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠧ 70% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠇ 70% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠏ 70% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠋ 70% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠙ 70% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 🌧 ] Prepare some fresh sunbeams ⠙ 80% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 ] Enjoy the sunshine ⠹ 80% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 ] Enjoy the sunshine ⠸ 80% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 ] Enjoy the sunshine ⠼ 80% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 ] Enjoy the sunshine ⠴ 80% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 ] Enjoy the sunshine ⠦ 80% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 ] Enjoy the sunshine ⠧ 80% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 ] Enjoy the sunshine ⠇ 80% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 ] Enjoy the sunshine ⠏ 80% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 ] Enjoy the sunshine ⠋ 80% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 🌧 ] Enjoy the sunshine ⠋ 90% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 ] Enjoy the sunshine ⠙ 90% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 ] Enjoy the sunshine ⠹ 90% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 ] Enjoy the sunshine ⠸ 90% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 ] Enjoy the sunshine ⠼ 90% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 ] Enjoy the sunshine ⠴ 90% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 ] Enjoy the sunshine ⠦ 90% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 ] Enjoy the sunshine ⠧ 90% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 ] Enjoy the sunshine ⠇ 90% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌧 ] Enjoy the sunshine ✓ 100% [🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 🌤 ] Finished in 9s
584 |
--------------------------------------------------------------------------------