• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2024 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import m from 'mithril';
16import {ProbeSetting} from '../../config/config_interfaces';
17import {assertTrue} from '../../../../base/logging';
18import {exists} from '../../../../base/utils';
19
20export interface SliderAttrs {
21  title: string;
22  values: number[];
23  default?: number;
24  icon?: string;
25  cssClass?: string;
26  isTime?: boolean;
27  unit: string;
28  min?: number;
29  description?: string;
30  disabled?: boolean;
31  zeroIsDefault?: boolean;
32  onChange?: (value: number) => void;
33}
34
35export class Slider implements ProbeSetting {
36  private _value: number;
37
38  constructor(readonly attrs: SliderAttrs) {
39    assertTrue(attrs.values.length > 0);
40    this._value = this.setValue(undefined);
41  }
42
43  serialize() {
44    return this._value;
45  }
46
47  deserialize(state: unknown): void {
48    if (typeof state === 'number') {
49      this._value = state;
50    }
51  }
52
53  get value(): number {
54    return this._value;
55  }
56
57  setValue(value: number | null | undefined) {
58    // Logic if value is null/undefined: try first the .default, if provided,
59    // otherwise fall back on the first value of the fixed range... otherwise 0.
60    this._value = exists(value)
61      ? value
62      : this.attrs.default ?? this.attrs.values[0] ?? 0;
63    return this._value;
64  }
65
66  private onValueChange(newVal: number) {
67    this._value = newVal;
68    this.attrs.onChange?.(newVal);
69  }
70
71  onTimeValueChange(hms: string) {
72    try {
73      const date = new Date(`1970-01-01T${hms}.000Z`);
74      if (isNaN(date.getTime())) return;
75      this.onValueChange(date.getTime());
76    } catch {}
77  }
78
79  onSliderChange(newIdx: number) {
80    this.onValueChange(this.attrs.values[newIdx]);
81  }
82
83  render() {
84    const attrs = this.attrs;
85    const id = attrs.title.replace(/[^a-z0-9]/gim, '_').toLowerCase();
86    const maxIdx = attrs.values.length - 1;
87    const val = this._value;
88    let min = attrs.min ?? 1;
89    if (attrs.zeroIsDefault) {
90      min = Math.min(0, min);
91    }
92    const description = attrs.description;
93    const disabled = attrs.disabled;
94
95    // Find the index of the closest value in the slider.
96    let idx = 0;
97    for (; idx < attrs.values.length && attrs.values[idx] < val; idx++) {}
98
99    let spinnerCfg = {};
100    if (attrs.isTime) {
101      spinnerCfg = {
102        type: 'text',
103        pattern: '(0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}', // hh:mm:ss
104        value: new Date(val).toISOString().substring(11, 11 + 8),
105        oninput: (e: InputEvent) => {
106          this.onTimeValueChange((e.target as HTMLInputElement).value);
107        },
108      };
109    } else {
110      const isDefault = attrs.zeroIsDefault && val === 0;
111      spinnerCfg = {
112        type: 'number',
113        value: isDefault ? '' : val,
114        placeholder: isDefault ? '(default)' : '',
115        oninput: (e: InputEvent) => {
116          this.onValueChange(+(e.target as HTMLInputElement).value);
117        },
118      };
119    }
120    return m(
121      '.slider' + (attrs.cssClass ?? ''),
122      m('header', attrs.title),
123      description ? m('header.descr', attrs.description) : '',
124      attrs.icon !== undefined ? m('i.material-icons', attrs.icon) : [],
125      m(`input[id="${id}"][type=range][min=0][max=${maxIdx}][value=${idx}]`, {
126        disabled,
127        oninput: (e: InputEvent) => {
128          this.onSliderChange(+(e.target as HTMLInputElement).value);
129        },
130      }),
131      m(`input.spinner[min=${min}][for=${id}]`, spinnerCfg),
132      m('.unit', attrs.unit),
133    );
134  }
135}
136
137export const POLL_INTERVAL_SLIDER: SliderAttrs = {
138  title: 'Poll interval',
139  values: [250, 500, 1000, 2500, 5000, 30000, 60000],
140  cssClass: '.thin',
141  unit: 'ms',
142};
143