MediaWiki:Gadget-countdown-timer.js: Difference between revisions
From IdleOn MMO Wiki
No edit summary |
No edit summary |
||
Line 113: | Line 113: | ||
// Get the actual value for the part. | // Get the actual value for the part. | ||
var value = Math.floor(duration / unit.divisor); | var value = Math.floor(duration / unit.divisor); | ||
if (index | if (index != self.maxUnit) { | ||
value %= unit.mod; | value %= unit.mod; | ||
} | } |
Revision as of 22:20, 22 May 2024
$(function() {
/**
* Names of all valid format modes.
*/
var FORMAT_MODES = [ 'clock', 'labeled' ];
var UNIT_NAMES = [ 's', 'm', 'h', 'd' ];
var UNITS = [
{
divisor: 1,
mod: 60,
},
{
divisor: 60,
mod: 60,
},
{
divisor: 3600,
mod: 24,
},
{
divisor: 86400,
mod: Number.MAX_SAFE_INTEGER,
}
];
/**
* The list of active timers.
*/
var timers = [];
/**
* The list of active formatters.
*/
var formatters = [];
/**
* Searches for a value in `container`.
* @param value The value to search for.
* @param container The container to search.
* @param defaultValue The value to default to if `value` could not be found.
* @returns `value` if it exists in `container`, otherwise `defaultValue`.
*/
function findOrElse(value, container, defaultValue) {
return container.includes(value) ? value : defaultValue;
}
/**
* Creates a formatter for `$elem`. Formatters will be reused for elements
* with the same configuration.
* @param $elem The element to format.
* @returns The formatter.
*/
function createFormatter($elem) {
var mode = findOrElse($elem.data('mode'), FORMAT_MODES, 'clock');
var padZeroes = findOrElse($elem.data('pad-zeroes'), [true, false], mode == 'clock');
var maxUnit = UNIT_NAMES.indexOf(findOrElse($elem.data('max-unit'), UNIT_NAMES, 'd'));
var minUnit = UNIT_NAMES.indexOf(findOrElse($elem.data('min-unit'), UNIT_NAMES, 's'));
var truncate = findOrElse($elem.data('truncate'), [true, false], false);
// Search for a formatter with matching configuration.
var formatter = formatters.find(function (fmt) {
return fmt.mode == mode
&& fmt.padZeroes == padZeroes
&& fmt.maxUnit == maxUnit
&& fmt.minUnit == minUnit
&& fmt.truncate == truncate;
});
// No suitable formatter was found so create and register a new one.
if (!formatter) {
formatter = new CountdownFormatter(mode, padZeroes, maxUnit, minUnit, truncate);
formatters.push(formatter);
}
return formatter;
}
/**
* The `CountdownFormatter` class is responsible for formatting a duration.
* @param mode The format mode, can be either `clock` or `labeled`.
* @param padZeroes Whether units should be padded with zeroes.
* @param maxUnit The largest unit to display.
* @param minUnit The smallest unit to display.
* @param truncate Whether units larger than the largest non-zero unit should be omitted.
*/
function CountdownFormatter(mode, padZeroes, maxUnit, minUnit, truncate) {
var self = this;
this.mode = mode;
this.padZeroes = padZeroes;
this.maxUnit = maxUnit;
this.minUnit = minUnit;
this.truncate = truncate;
/**
*
* @param duration
* @param index
* @param canTruncate
* @returns
*/
this.extractPart = function (duration, index, canTruncate) {
// Don't bother extracting parts outside of the visible range.
if (index < self.minUnit || index > self.maxUnit) {
return undefined;
}
// Get the unit metadata.
var unit = UNITS[index];
// Get the actual value for the part.
var value = Math.floor(duration / unit.divisor);
if (index != self.maxUnit) {
value %= unit.mod;
}
console.log(duration + " => " + value + "(" + UNIT_NAMES[index] + ")");
// Discard values if they can be truncated.
if (value == 0 && canTruncate) {
return undefined;
}
// Pad with zeroes if necessary.
var prefix = (self.padZeroes && value < 10) ? '0' : '';
return prefix + value.toString();
}
/**
* Format the specified `duration`.
* @param duration The duration to format.
* @returns The formatted string.
*/
this.format = function (duration) {
if (duration < 0) {
duration = 0;
}
var canTruncate = self.truncate;
var parts = [];
for (var index = self.maxUnit; index >= self.minUnit; index--) {
// The smallest unit can never be truncated.
canTruncate &= (index != self.minUnit);
var part = self.extractPart(duration, index, canTruncate);
if (part !== undefined) {
parts.push({
label: UNIT_NAMES[index],
value: part
})
}
// Can't truncate if a larger time unit is valid.
canTruncate &= (part === undefined);
}
switch (self.mode) {
case 'clock':
return self._formatClock(parts);
case 'labeled':
return self._formatLabeled(parts);
}
};
this._formatClock = function (parts) {
return parts.map(function(part) {
return part.value
}).join(':');
};
this._formatLabeled = function (parts) {
return parts.map(function(part) {
return part.value + part.label
}).join(' ');
};
}
/**
* The `CountdownTimer` class is responsible for applying formatting to an element.
* @param $elem
*/
function CountdownTimer($elem) {
var self = this;
this.$elem = $elem;
this.formatter = createFormatter($elem);
this.expiration = $elem.data('timestamp')
if (this.expiration === undefined) {
this.expiration = Date.now() + 90000;
}
/**
* Updates the timer's value.
* @returns true if the timer has not expired, otherwise false.
*/
this.tick = function() {
var duration = Math.floor((self.expiration - Date.now()) / 1000);
var text = self.formatter.format(duration);
$elem.text(text);
return duration > 0;
};
}
function tick() {
// Tick and automatically remove expired timers.
timers = timers.filter(function(timer) {
return timer.tick();
});
// Stop ticking if there are no timers remaining.
if (timers.length != 0) {
setTimeout(tick, 1000);
}
}
mw.hook('wikipage.content').add(function($content) {
var $targets = $content.find('time.countdown-timer:not(.managed)');
if ($targets.length == 0) {
return;
}
$targets
.addClass('managed')
.each(function() {
var timer = new CountdownTimer($(this));
timers.push(timer);
});
tick();
});
});