// Copyright (c) Vidar Tonaas Fauske // Distributed under the terms of the Modified BSD License. import { ISerializers } from '@jupyter-widgets/base'; import { uuid } from './utils'; import { DescriptionView } from './widget_description'; import { CoreDescriptionModel } from './widget_core'; const PARSER = /(\d\d):(\d\d)(:(\d\d)(.(\d{1,3})\d*)?)?/; export interface ISerializedTime { /** * Integer hour (24H format) */ hours: number; /** * Integer minutes */ minutes: number; /** * Integer seconds */ seconds: number; /** * Millisconds */ milliseconds: number; } export function serialize_time(value: string): ISerializedTime | null { if (value === null) { return null; } else { const res = PARSER.exec(value); if (res === null) { return null; } return { hours: Math.min(23, parseInt(res[1], 10)), minutes: Math.min(59, parseInt(res[2], 10)), seconds: res[4] ? Math.min(59, parseInt(res[4], 10)) : 0, milliseconds: res[6] ? parseInt(res[6], 10) : 0, }; } } export function deserialize_time(value: ISerializedTime): string | null { if (value === null) { return null; } else { const parts = [ `${value.hours.toString().padStart(2, '0')}:${value.minutes .toString() .padStart(2, '0')}`, ]; if (value.seconds > 0 || value.milliseconds > 0) { parts.push(`:${value.seconds.toString().padStart(2, '0')}`); if (value.milliseconds > 0) { parts.push(`.${value.milliseconds.toString().padStart(3, '0')}`); } } return parts.join(''); } } export const time_serializers = { serialize: serialize_time, deserialize: deserialize_time, }; export class TimeModel extends CoreDescriptionModel { defaults(): Backbone.ObjectHash { return { ...super.defaults(), _model_name: TimeModel.model_name, _view_name: TimeModel.view_name, value: null, disabled: false, min: null, max: null, step: 60, }; } static serializers: ISerializers = { ...CoreDescriptionModel.serializers, value: time_serializers, min: time_serializers, max: time_serializers, }; static model_name = 'TimeModel'; static view_name = 'TimeView'; } export class TimeView extends DescriptionView { render(): void { super.render(); this.el.classList.add('jupyter-widgets'); this.el.classList.add('widget-inline-hbox'); this.el.classList.add('widget-timepicker'); this._timepicker = document.createElement('input'); this._timepicker.setAttribute('type', 'time'); this._timepicker.id = this.label.htmlFor = uuid(); this.el.appendChild(this._timepicker); this.listenTo(this.model, 'change:value', this._update_value); this.listenTo(this.model, 'change', this.update2); this._update_value(); this.update2(); } /** * Update the contents of this view * * Called when the model is changed. The model may have been * changed by another view or by a state update from the back-end. */ update2(model?: Backbone.Model, options?: any): void { if (options === undefined || options.updated_view !== this) { this._timepicker!.disabled = this.model.get('disabled'); this._timepicker!.min = this.model.get('min'); this._timepicker!.max = this.model.get('max'); this._timepicker!.step = this.model.get('step'); } return super.update(); } events(): { [e: string]: string } { // Typescript doesn't understand that these functions are called, so we // specifically use them here so it knows they are being used. void this._picker_change; void this._picker_focusout; return { 'change [type="time"]': '_picker_change', 'focusout [type="time"]': '_picker_focusout', }; } private _update_value( model?: Backbone.Model, newValue?: any, options?: any ): void { if (options === undefined || options.updated_view !== this) { this._timepicker!.value = this.model.get('value'); } } private _picker_change(): void { if (!this._timepicker!.validity.badInput) { this.model.set('value', this._timepicker!.value, { updated_view: this }); this.touch(); } } private _picker_focusout(): void { if (this._timepicker!.validity.badInput) { this.model.set('value', null, { updated_view: this }); this.touch(); } } private _timepicker: HTMLInputElement | undefined; }