This PR introduces animated rendering capabilities to Termwind, allowing for dynamic updates of CLI components like progress bars or any other live-rendered element. I've developed this feature to use in my own package (see: https://github.com/pdphilip/elasticlens) and would love to contribute it back to Termwind.
Why:
Termwind offers excellent flexibility in designing CLI interfaces, but the standard progress bars often look out of place. This PR enhances Termwind's potential by enabling real-time updates to rendered elements, leveraging all its existing design capabilities. This is useful for progress bars, loaders, or any component that requires continuous updates.
Real-world example:
2 New Functions:
1. liveRender(string $html = '', int $options): LiveHtmlRenderer
Enables real-time CLI components to re-render by tracking the current terminal position and refreshing the content when reRender() is called
Ideal for Progress Bars
$live = liveRender($view);
// Same as termwind's render() but returns the $live instance
// Methods:
$screenWidth = $live->getScreenWidth(); // Helper method to get terminal width
$live->reRender($view); // Re-renders the $live instance with $view
Example: Progress Bar
$max = 250;
$current = 0;
$live = liveRender(); //Doesnt render at this point, but creates a $live instance at this line
$live->reRender(view('cli.components.progress', [
'screenWidth' => $live->getScreenWidth(), //The view component needs this
'current' => 0,
'max' => $max,
]));
while ($current <= $max) {
$live->reRender(view('cli.components.progress', [
'screenWidth' => $live->getScreenWidth(),
'current' => $current,
'max' => $max,
]));
$current++;
usleep(1000);
}
A More Practical Example:
$max = User::count();
$i = 0;
$live = liveRender();
//Start at zero
$live->reRender(view('cli.components.progress', [
'screenWidth' => $live->getScreenWidth(),
'current' => $i,
'max' => $max,
]));
User::chunk(100, function ($users) use ($i, $max, $live) {
foreach ($users as $user) {
// Do something to $user
// Move progress +1
$i++;
$live->reRender(view('cli.components.progress', [
'screenWidth' => $live->getScreenWidth(),
'current' => $i,
'max' => $max,
]));
}
});
Demo*
Blade source for each example is shown in addendum below.
Runs an asynchronous task with an animated loader or spinner while the task is processed in the background.
Since PHP is synchronous this function creates a fork to run a given task in parallel.
If pcntl_fork() is unavailable, it falls back to synchronous behaviour. ie render -> task -> render
Ideal for Loaders/Spinners
$async = asyncFunction(callable $task);
// Initates the Async instance on the given line,
// And sets the task as a callback
// Methods:
$i = $async->getInterval(); // Returns the current interval
$isRunning = $async->getIsRunning(); // Helper to see if the task is still running
$screenWidth = $async->getScreenWidth(); // Helper method you can use in your view
$async->render($view); // (re)renders a view
$async->withFailOver($view); // Optional view that will be used if pcntl_fork is not available
$result = $async->run(callable $render, int $si = 1000) //Executes the $task and loops the $render in $si micro-sec intervals & returns the $result of the $task once it's done
Note:
If nothing is returned in the callable task, then $result will be true
If an exception was thrown in the task, then $result will be false
Usage:
//Initate the Async Instance (on this line) and define the task
$async = asyncFunction(function () {
//Run a task
sleep(5);
//Return the result
return [
'state' => 'success',
'message' => 'Completed',
'details' => 'Index migrated successfully',
];
});
//Set a failover view in case the terminal can't fork
$async->withFailOver(view('cli.components.loader', [
'state' => 'failover',
'message' => 'Migrating Index',
'i' => 1,
]));
// Run the task and re-render the view every 0.05s
// Once the task has been completed, it will return $result
$result = $async->run(function () use ($async) {
$async->render(view('cli.components.loader', [
'state' => 'running',
'message' => 'Migrating Index',
'i' => $async->getInterval(),
]));
},50000); //every 0.05 sec
// Use $result to render again
$async->render(view('cli.components.loader', [
'state' => $result['state'],
'message' => $result['message'],
'details' => $result['details'],
'i' => 0,
]));
Demo
Addendum
Below are the blade files used in the demo examples:
Progress Bar: Example 1
```php
{{$current}}/{{$max}}{{$percentage}}%
```
Progress Bar: Example 2
```php
{{$current}}/{{$max}}{{$percentage}}%
```
Progress Bar: Example 3
```php
25) {
$progressColor = "bg-sky-600 text-sky-400";
}
if ($max == $current) {
$progressColor = "bg-emerald-600 text-emerald-300";
}
?>
This PR introduces animated rendering capabilities to Termwind, allowing for dynamic updates of CLI components like progress bars or any other live-rendered element. I've developed this feature to use in my own package (see: https://github.com/pdphilip/elasticlens) and would love to contribute it back to Termwind.
Why:
Termwind offers excellent flexibility in designing CLI interfaces, but the standard progress bars often look out of place. This PR enhances Termwind's potential by enabling real-time updates to rendered elements, leveraging all its existing design capabilities. This is useful for progress bars, loaders, or any component that requires continuous updates.
Real-world example:
2 New Functions:
1.
liveRender(string $html = '', int $options): LiveHtmlRenderer
reRender()
is calledExample: Progress Bar
A More Practical Example:
Demo*
2.
asyncFunction(callable $task): AsyncHtmlRenderer
pcntl_fork()
is unavailable, it falls back to synchronous behaviour. ierender -> task -> render
Note:
$result
will betrue
$result
will befalse
Usage:
Demo
Addendum
Below are the blade files used in the demo examples:
Progress Bar: Example 1
```phpProgress Bar: Example 2
```phpProgress Bar: Example 3
```php 25) { $progressColor = "bg-sky-600 text-sky-400"; } if ($max == $current) { $progressColor = "bg-emerald-600 text-emerald-300"; } ?>Loader: Example 1
```php $intervals) { $i -= $intervals; } $show = $characters[$i]; $textColor = "text-amber-500"; switch ($state) { case 'success': $textColor = "text-emerald-500"; $show = "✔"; break; case 'warning': $textColor = "text-amber-500"; $show = "⚠"; break; case 'failover': $textColor = "text-amber-500"; $show = "◴"; break; case 'error': $textColor = "text-rose-500"; $show = "✘"; break; } ?>Loader: Example 2
```php $intervals) { $i -= $intervals; $j++; if ($j > 3) { $j = 0; } } $show = $characters[$i]; $textColor = $colors[$j]; switch ($state) { case 'success': $textColor = "text-emerald-500"; $show = "✔"; break; case 'warning': $textColor = "text-amber-500"; $show = "⚠"; break; case 'failover': $textColor = "text-amber-500"; $show = "◴"; break; case 'error': $textColor = "text-rose-500"; $show = "✘"; break; } ?>Loader: Example 3
(This one is insanely complicated, but just showing what’s possible) ```php $v ? $nextColorIndex : $colorIndex, $colorTransitions[$step]) : array_fill(0, 5, $nextColorIndex); $stateConfig = [ 'success' => ['text-emerald-500', '✔'], 'warning' => ['text-amber-500', '⚠'], 'failover' => ['text-amber-500', '◴'], 'error' => ['text-rose-500', '✘'] ]; [$textColor, $show] = $stateConfig[$state] ?? [null, false]; ?>