summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/vue_shared/components/chronic_duration_input.vue
blob: ffbcdefc9243ed0153dc54b8cfd6342bb638509b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<script>
import * as Sentry from '@sentry/browser';
import { GlFormInput } from '@gitlab/ui';
import {
  DurationParseError,
  outputChronicDuration,
  parseChronicDuration,
} from '~/chronic_duration';
import { __ } from '~/locale';

export default {
  components: {
    GlFormInput,
  },
  model: {
    prop: 'value',
    event: 'change',
  },
  props: {
    value: {
      type: Number,
      required: false,
      default: null,
    },
    name: {
      type: String,
      required: false,
      default: null,
    },
    integerRequired: {
      type: Boolean,
      required: false,
      default: true,
    },
  },
  data() {
    return {
      numberData: this.value,
      humanReadableData: this.convertDuration(this.value),
      isValueValid: this.value === null ? null : true,
    };
  },
  computed: {
    numberValue: {
      get() {
        return this.numberData;
      },
      set(value) {
        if (this.numberData !== value) {
          this.numberData = value;
          this.humanReadableData = this.convertDuration(value);
          this.isValueValid = value === null ? null : true;
        }
        this.emitEvents();
      },
    },
    humanReadableValue: {
      get() {
        return this.humanReadableData;
      },
      set(value) {
        this.humanReadableData = value;
        try {
          if (value === '') {
            this.numberData = null;
            this.isValueValid = null;
          } else {
            this.numberData = parseChronicDuration(value, {
              keepZero: true,
              raiseExceptions: true,
            });
            this.isValueValid = true;
          }
        } catch (e) {
          if (e instanceof DurationParseError) {
            this.isValueValid = false;
          } else {
            Sentry.captureException(e);
          }
        }
        this.emitEvents(true);
      },
    },
    isValidDecimal() {
      return !this.integerRequired || this.numberData === null || Number.isInteger(this.numberData);
    },
    feedback() {
      if (this.isValueValid === false) {
        return this.$options.i18n.INVALID_INPUT_FEEDBACK;
      }
      if (!this.isValidDecimal) {
        return this.$options.i18n.INVALID_DECIMAL_FEEDBACK;
      }
      return '';
    },
  },
  i18n: {
    INVALID_INPUT_FEEDBACK: __('Please enter a valid time interval'),
    INVALID_DECIMAL_FEEDBACK: __('An integer value is required for seconds'),
  },
  watch: {
    value() {
      this.numberValue = this.value;
    },
  },
  mounted() {
    this.emitEvents();
  },
  methods: {
    convertDuration(value) {
      return value === null ? '' : outputChronicDuration(value);
    },
    emitEvents(emitChange = false) {
      if (emitChange && this.isValueValid !== false && this.isValidDecimal) {
        this.$emit('change', this.numberData);
      }
      const { feedback } = this;
      this.$refs.text.$el.setCustomValidity(feedback);
      this.$refs.hidden.setCustomValidity(feedback);
      this.$emit('valid', {
        valid: this.isValueValid && this.isValidDecimal,
        feedback,
      });
    },
  },
};
</script>
<template>
  <div>
    <gl-form-input ref="text" v-bind="$attrs" v-model="humanReadableValue" />
    <input ref="hidden" type="hidden" :name="name" :value="numberValue" />
  </div>
</template>