diff --git a/src/public/app/widgets/type_widgets/options/time_selector.ts b/src/public/app/widgets/type_widgets/options/time_selector.ts new file mode 100644 index 000000000..49c149f75 --- /dev/null +++ b/src/public/app/widgets/type_widgets/options/time_selector.ts @@ -0,0 +1,126 @@ +import OptionsWidget from "./options_widget.js"; +import toastService from "../../../services/toast.js"; +import { t } from "../../../services/i18n.js"; +import type { OptionDefinitions, OptionMap } from "../../../../../services/options_interface.js"; + + +type TimeSelectorConstructor = { + widgetId: string; + widgetLabelId: string; + optionValueId: keyof OptionDefinitions; + optionTimeScaleId: keyof OptionDefinitions; +}; + + +const TPL = (options: Pick) => ` +
+ +
+ + +
+
+ + +`; + +export default class TimeSelector extends OptionsWidget { + + private $timeValueInput!: JQuery; + private $timeScaleSelect!: JQuery; + private internalTimeInSeconds!: string | number; + private widgetId: string; + private widgetLabelId: string; + private optionValueId: keyof OptionDefinitions; + private optionTimeScaleId: keyof OptionDefinitions; + + constructor(options: TimeSelectorConstructor) { + super(); + this.widgetId = options.widgetId; + this.widgetLabelId = options.widgetLabelId; + this.optionValueId = options.optionValueId; + this.optionTimeScaleId = options.optionTimeScaleId; + } + + doRender() { + this.$widget = $(TPL({ + widgetId: this.widgetId, + widgetLabelId: this.widgetLabelId + })); + + this.$timeValueInput = this.$widget.find(`#${this.widgetId}`); + this.$timeScaleSelect = this.$widget.find(`#${this.widgetId}-time-scale`); + + this.$timeValueInput.on("change", () => { + const time = this.$timeValueInput.val(); + const timeScale = this.$timeScaleSelect.val(); + + if (!this.handleTimeValidation() || typeof timeScale !== "string" || !time) return; + + this.internalTimeInSeconds = this.convertTime(time, timeScale).toOption(); + this.updateOption(this.optionValueId, this.internalTimeInSeconds); + + }); + + this.$timeScaleSelect.on("change", () => { + + const timeScale = this.$timeScaleSelect.val(); + + if (!this.handleTimeValidation() || typeof timeScale !== "string") return; + + //calculate the new displayed value + const displayedTime = this.convertTime(this.internalTimeInSeconds, timeScale).toDisplay(); + + this.updateOption(this.optionTimeScaleId, timeScale); + this.$timeValueInput.val(displayedTime).trigger("change"); + + }); + + } + + async optionsLoaded(options: OptionMap) { + this.internalTimeInSeconds = options[this.optionValueId]; + const displayedTime = this.convertTime(options[this.optionValueId], options[this.optionTimeScaleId]).toDisplay(); + this.$timeValueInput.val(displayedTime); + this.$timeScaleSelect.val(options[this.optionTimeScaleId]); + } + + + convertTime(time: string | number, timeScale: string | number) { + + const value = typeof time === "number" ? time : parseInt(time); + if (Number.isNaN(value)) { + throw new Error(`Time needs to be a valid integer, but received: ${time}`); + } + + const operand = typeof timeScale === "number" ? timeScale : parseInt(timeScale); + if (Number.isNaN(operand) || operand < 1) { + throw new Error(`TimeScale needs to be a valid integer >= 1, but received: ${timeScale}`); + } + + return { + toOption: () => Math.ceil(value * operand), + toDisplay: () => Math.ceil(value / operand), + } + + } + + handleTimeValidation() { + if (this.$timeValueInput.is(":invalid")) { + // TriliumNextTODO: i18n + toastService.showMessage("The entered time value is not a valid number."); + return false + } + return true; + } + +} \ No newline at end of file