-
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: DurationController - A controller to display the duration that …
…has elapsed since a given timestamp. Similar to TimeDistanceController, but with numbers instead of words.
- Loading branch information
Showing
4 changed files
with
167 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
--- | ||
id: DurationController | ||
title: DurationController | ||
--- | ||
|
||
import NoEvents from "../_partials/no-events.md"; | ||
import NoActions from "../_partials/no-actions.md"; | ||
import NoClasses from "../_partials/no-classes.md"; | ||
import NoTargets from "../_partials/no-targets.md"; | ||
|
||
|
||
## Purpose | ||
|
||
A controller that displays the distance in numbers between the specified time and the current time. Works for past dates. | ||
Similar to TimeDistance controller, but outputs fully qualified numbers for the duration rather than trying to construct a sentence. | ||
|
||
Implemented using https://date-fns.org/v2.21.1/docs/formatDuration. | ||
|
||
Fire and forget. The only configuration this controller takes is the timestamp, and whether to show seconds/minutes. | ||
|
||
## [Actions](https://stimulus.hotwire.dev/reference/actions) | ||
|
||
<NoActions/> | ||
|
||
## [Targets](https://stimulus.hotwire.dev/reference/targets) | ||
|
||
<NoTargets/> | ||
|
||
## [Classes](https://stimulus.hotwire.dev/reference/classes) | ||
|
||
<NoClasses/> | ||
|
||
## [Values](https://stimulus.hotwire.dev/reference/values) | ||
|
||
| Value | Type | Description | Default | | ||
| --- | --- | --- | --- | | ||
| `timestamp` | String | The UNIX timestamp in seconds of the date/time to show "time elapsed" for | - | | ||
| `minutes` (Optional) | Number | Whether or not to show minutes in the outputted string | true | | ||
| `seconds` (Optional) | Number | Whether or not to show seconds in the outputted string | true | | ||
|
||
## Events | ||
|
||
<NoEvents/> | ||
|
||
## Side Effects | ||
|
||
The controller will intelligently set a `setTimeout` according to the fastest unit of time shown. If minutes/seconds are hidden, then the controller will update less frequently. | ||
|
||
All timeouts are cleaned up when the controller disconnects. | ||
|
||
## How to Use | ||
|
||
<iframe | ||
src="https://codesandbox.io/embed/durationcontroller-r8qxb?fontsize=14&hidenavigation=1&theme=dark" | ||
style={{width: "100%", height: "500px", border: "0", borderRadius: "4px", overflow: "hidden"}} | ||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" | ||
/> | ||
|
||
|
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,106 @@ | ||
import {Duration, formatDuration, intervalToDuration, toDate} from "date-fns"; | ||
import {BaseController} from "../../utilities/base_controller"; | ||
|
||
export class DurationController extends BaseController { | ||
static values = { | ||
timestamp: Number, | ||
minutes: Boolean, | ||
seconds: Boolean, | ||
}; | ||
|
||
declare readonly minutesValue: boolean; | ||
declare readonly hasMinutesValue: boolean; | ||
declare readonly secondsValue: boolean; | ||
declare readonly hasSecondsValue: boolean; | ||
declare timestampValue: number; | ||
declare readonly hasTimestampValue: boolean; | ||
_intervalHandle: number | null = null; | ||
|
||
get _format(): string[] { | ||
return [ | ||
"years", | ||
"months", | ||
"weeks", | ||
"days", | ||
"hours", | ||
...(this._minutes ? ["minutes"] : []), | ||
...(this._seconds ? ["seconds"] : []), | ||
]; | ||
} | ||
|
||
get _output(): string { | ||
let {years, months, weeks, days, hours, minutes, seconds} = this._duration; | ||
|
||
years ||= 0; | ||
months ||= 0; | ||
weeks ||= 0; | ||
days ||= 0; | ||
hours ||= 0; | ||
minutes ||= 0; | ||
seconds ||= 0; | ||
|
||
let largeDenominators = [years, months, weeks, days, hours]; | ||
|
||
if (!this._minutes && !this._seconds && largeDenominators.every((x) => x === 0)) { | ||
minutes = minutes + seconds / 60.0; | ||
return `${(minutes / 60).toFixed(1)} hours`; | ||
} | ||
|
||
return formatDuration(this._duration, {format: this._format, delimiter: ", "}); | ||
} | ||
|
||
get _seconds(): boolean { | ||
return this.hasSecondsValue ? this.secondsValue : true; | ||
} | ||
|
||
get _minutes(): boolean { | ||
return this.hasMinutesValue ? this.minutesValue : true; | ||
} | ||
|
||
get _timestamp(): Date { | ||
if (this.hasTimestampValue) { | ||
return toDate(this.timestampValue * 1000); | ||
} else { | ||
throw new Error("Expected `timestampValue` to be present"); | ||
} | ||
} | ||
|
||
get _duration(): Duration { | ||
return intervalToDuration({start: new Date(), end: this._timestamp}); | ||
} | ||
|
||
get _tickInterval() { | ||
if (this._seconds) { | ||
return 1000; // 1 seconds | ||
} else if (this._minutes) { | ||
return 15000; // 15 seconds | ||
} else { | ||
return 120000; // 2 minutes | ||
} | ||
} | ||
|
||
initialize() { | ||
this._update = this._update.bind(this); | ||
} | ||
|
||
connect() { | ||
this._intervalHandle = window.setInterval(this._update, this._tickInterval); | ||
this._update(); | ||
} | ||
|
||
disconnect() { | ||
if (this._intervalHandle) { | ||
window.clearInterval(this._intervalHandle); | ||
} | ||
} | ||
|
||
_update() { | ||
try { | ||
this.el.innerHTML = this._output; | ||
} catch { | ||
if (this._intervalHandle) { | ||
window.clearInterval(this._intervalHandle); | ||
} | ||
} | ||
} | ||
} |
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