<commit_before>/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */

import { BooleanInput, coerceBooleanProperty, coerceNumberProperty, NumberInput } from '@angular/cdk/coercion';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Optional, Output, Self, ViewEncapsulation } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
import { Destroy } from '@deja-js/component/core';
import { set } from 'date-fns';
import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, takeUntil } from 'rxjs/operators';

export type TimePickerDisplayMode = 'fullTime' | 'fullTimeWithHoursDisabled' | 'fullTimeWithMinutesDisabled' | 'hoursOnly' | 'minutesOnly';

/**
 * Time-picker component for Angular
 */
@Component({
    changeDetection: ChangeDetectionStrategy.OnPush,
    selector: 'deja-time-picker',
    styleUrls: ['./time-picker.component.scss'],
    templateUrl: './time-picker.component.html',
    encapsulation: ViewEncapsulation.None
})
export class DejaTimePickerComponent extends Destroy implements ControlValueAccessor {
    @Output() public readonly timeChange = new EventEmitter<Date>();

    /** Display mode for the time-picker */
    @Input() public mode: TimePickerDisplayMode = 'fullTime';

    @Input()
    public set time(value: Date) {
        this.writeValue(value);
    }

    public get time(): Date {
        return this.value;
    }

    /** Step of the arrows */
    @Input()
    public set step(value: NumberInput) {
        this._step = coerceNumberProperty(value);
        this.changeDetectorRef.markForCheck();
    }

    /** To get the step of the minutes arrows */
    public get step(): NumberInput {
        return this._step;
    }

    /** Disabled property setter. Can be string or empty so you can use it like : <time-picker disabled></time-picker> */
    @Input()
    public set disabled(value: BooleanInput) {
        this._disabled = coerceBooleanProperty(value);
        this.changeDetectorRef.markForCheck();
    }

    /** To get disabled attribute. */
    public get disabled(): BooleanInput {
        return this._disabled;
    }

    public onHoursChange$ = new Subject<Event | number>();
    public onMinutesChange$ = new Subject<Event | number>();
    public _step = 1;
    private _disabled = false;
    private _value: Date;

    public constructor(
        private changeDetectorRef: ChangeDetectorRef,
        @Self() @Optional() public control: NgControl
    ) {
        super();

        if (this.control) {
            this.control.valueAccessor = this;
        }

        this.onHoursChange$.pipe(
            debounceTime(10),
            distinctUntilChanged(),
            map(hours => {
                if (typeof hours === 'object') {
                    return parseInt((hours.target as HTMLInputElement).value, 10);
                }
                return hours;
            }),
            takeUntil(this.destroyed$)
        ).subscribe(hours => {
            const value = this.value?.getTime();
            const clone = value ? new Date(value) : set(new Date(), { hours: 0, minutes: 0, seconds: 0 });
            clone.setHours(hours);

            this.value = clone;
            this.changeDetectorRef.markForCheck();
        });

        this.onMinutesChange$.pipe(
            debounceTime(10),
            distinctUntilChanged(),
            map(minutes => {
                if (typeof minutes === 'object') {
                    return parseInt((minutes.target as HTMLInputElement).value, 10);
                }
                return minutes;
            }),
            takeUntil(this.destroyed$)
        ).subscribe(minutes => {
            const value = this.value?.getTime();
            const clone = value ? new Date(value) : set(new Date(), { hours: 0, minutes: 0, seconds: 0 });
            if (minutes < 0) {
                minutes += 60;
            } else if (minutes >= 60) {
                minutes -= 60;
            }
            clone.setMinutes(minutes);

            this.value = clone;
            this.changeDetectorRef.markForCheck();
        });
    }

    // ************* ControlValueAccessor Implementation **************
    /** set accessor including call the onchange callback */<commit_msg>fix(time-picker): fix the infinite loop when unsetting hours or minutes (DEJS-1040)<commit_after>/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */

import { BooleanInput, coerceBooleanProperty, coerceNumberProperty, NumberInput } from '@angular/cdk/coercion';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Optional, Output, Self, ViewEncapsulation } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
import { Destroy } from '@deja-js/component/core';
import { set } from 'date-fns';
import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, takeUntil } from 'rxjs/operators';

export type TimePickerDisplayMode = 'fullTime' | 'fullTimeWithHoursDisabled' | 'fullTimeWithMinutesDisabled' | 'hoursOnly' | 'minutesOnly';

/**
 * Time-picker component for Angular
 */
@Component({
    changeDetection: ChangeDetectionStrategy.OnPush,
    selector: 'deja-time-picker',
    styleUrls: ['./time-picker.component.scss'],
    templateUrl: './time-picker.component.html',
    encapsulation: ViewEncapsulation.None
})
export class DejaTimePickerComponent extends Destroy implements ControlValueAccessor {
    @Output() public readonly timeChange = new EventEmitter<Date>();

    /** Display mode for the time-picker */
    @Input() public mode: TimePickerDisplayMode = 'fullTime';

    @Input()
    public set time(value: Date) {
        this.writeValue(value);
    }

    public get time(): Date {
        return this.value;
    }

    /** Step of the arrows */
    @Input()
    public set step(value: NumberInput) {
        this._step = coerceNumberProperty(value);
        this.changeDetectorRef.markForCheck();
    }

    /** To get the step of the minutes arrows */
    public get step(): NumberInput {
        return this._step;
    }

    /** Disabled property setter. Can be string or empty so you can use it like : <time-picker disabled></time-picker> */
    @Input()
    public set disabled(value: BooleanInput) {
        this._disabled = coerceBooleanProperty(value);
        this.changeDetectorRef.markForCheck();
    }

    /** To get disabled attribute. */
    public get disabled(): BooleanInput {
        return this._disabled;
    }

    public onHoursChange$ = new Subject<Event | number>();
    public onMinutesChange$ = new Subject<Event | number>();
    public _step = 1;
    private _disabled = false;
    private _value: Date;

    public constructor(
        private changeDetectorRef: ChangeDetectorRef,
        @Self() @Optional() public control: NgControl
    ) {
        super();

        if (this.control) {
            this.control.valueAccessor = this;
        }

        this.onHoursChange$.pipe(
            debounceTime(10),
            distinctUntilChanged(),
            map(hours => {
                if (typeof hours === 'object') {
                    const value = (hours.target as HTMLInputElement).value;
                    hours = value !== undefined ? parseInt(value, 10) : undefined as number;
                }
                return !isNaN(hours) ? hours : undefined;
            }),
            takeUntil(this.destroyed$)
        ).subscribe(hours => {
            if (hours !== undefined || this.mode === 'fullTimeWithMinutesDisabled') {
                const value = this.value?.getTime();
                const clone = value ? new Date(value) : set(new Date(), { hours: 0, minutes: 0, seconds: 0 });
                clone.setHours(hours || 0);
                this.value = clone;
            } else {
                this.value = undefined;
            }
            this.changeDetectorRef.markForCheck();
        });

        this.onMinutesChange$.pipe(
            debounceTime(10),
            distinctUntilChanged(),
            map(minutes => {
                if (typeof minutes === 'object') {
                    const value = (minutes.target as HTMLInputElement).value;
                    minutes = value !== undefined ? parseInt(value, 10) : undefined as number;
                }
                return !isNaN(minutes) ? minutes : undefined;
            }),
            takeUntil(this.destroyed$)
        ).subscribe(minutes => {
            if (minutes !== undefined || this.mode === 'fullTimeWithHoursDisabled') {
                const value = this.value?.getTime();
                const clone = value ? new Date(value) : set(new Date(), { hours: 0, minutes: 0, seconds: 0 });
                if (minutes < 0) {
                    minutes += 60;
                } else if (minutes >= 60) {
                    minutes -= 60;
                }
                clone.setMinutes(minutes || 0);
                this.value = clone;
            } else {
                this.value = undefined;
            }

            this.changeDetectorRef.markForCheck();
        });
    }

    // ************* ControlValueAccessor Implementation **************
    /** set accessor including call the onchange callback */
{'fraction_non_alphanumeric': 0.11979328165374677, 'fraction_numerical': 0.005581395348837209, 'mean_word_length': 2.313698630136986, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 23, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '677549', 'n_tokens_mistral': 2661, 'n_tokens_neox': 2525, 'n_words': 688}