// Copyright (C) 2019 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import {Draft, produce} from 'immer'; import * as m from 'mithril'; import {Actions} from '../common/actions'; import {RecordConfig} from '../common/state'; import {copyToClipboard} from './clipboard'; import {globals} from './globals'; import {assertExists} from '../base/logging'; declare type Setter = (draft: Draft, val: T) => void; declare type Getter = (cfg: RecordConfig) => T; // +---------------------------------------------------------------------------+ // | Probe: the rectangular box on the right-hand-side with a toggle box. | // +---------------------------------------------------------------------------+ export interface ProbeAttrs { title: string; img: string; descr: string; isEnabled: Getter; setEnabled: Setter; } export class Probe implements m.ClassComponent { view({attrs, children}: m.CVnode) { const onToggle = (enabled: boolean) => { const traceCfg = produce(globals.state.recordConfig, draft => { attrs.setEnabled(draft, enabled); }); globals.dispatch(Actions.setRecordConfig({config: traceCfg})); }; const enabled = attrs.isEnabled(globals.state.recordConfig); return m( `.probe${enabled ? '.enabled' : ''}`, m(`img[src=assets/${attrs.img}]`, {onclick: () => onToggle(!enabled)}), m('label', m(`input[type=checkbox]`, {checked: enabled, oninput: m.withAttr('checked', onToggle)}), m('span', attrs.title)), m('div', m('div', attrs.descr), m('.probe-config', children))); } } // +---------------------------------------------------------------------------+ // | Slider: draggable horizontal slider with numeric spinner. | // +---------------------------------------------------------------------------+ export interface SliderAttrs { title: string; icon?: string; cssClass?: string; isTime?: boolean; unit: string; values: number[]; get: Getter; set: Setter; } export class Slider implements m.ClassComponent { onValueChange(attrs: SliderAttrs, newVal: number) { const traceCfg = produce(globals.state.recordConfig, draft => { attrs.set(draft, newVal); }); globals.dispatch(Actions.setRecordConfig({config: traceCfg})); } onTimeValueChange(attrs: SliderAttrs, hms: string) { try { const date = new Date(`1970-01-01T${hms}.000Z`); this.onValueChange(attrs, date.getTime()); } catch { } } onSliderChange(attrs: SliderAttrs, newIdx: number) { this.onValueChange(attrs, attrs.values[newIdx]); } view({attrs}: m.CVnode) { const id = attrs.title.replace(/[^a-z0-9]/gmi, '_').toLowerCase(); const maxIdx = attrs.values.length - 1; const val = attrs.get(globals.state.recordConfig); // Find the index of the closest value in the slider. let idx = 0; for (; idx < attrs.values.length && attrs.values[idx] < val; idx++) { } let spinnerCfg = {}; if (attrs.isTime) { spinnerCfg = { type: 'text', pattern: '(0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}', // hh:mm:ss value: new Date(val).toISOString().substr(11, 8), oninput: m.withAttr('value', v => this.onTimeValueChange(attrs, v)) }; } else { spinnerCfg = { type: 'number', value: val, oninput: m.withAttr('value', v => this.onValueChange(attrs, v)) }; } return m( '.slider' + (attrs.cssClass || ''), m('header', attrs.title), attrs.icon !== undefined ? m('i.material-icons', attrs.icon) : [], m(`input[id="${id}"][type=range][min=0][max=${maxIdx}][value=${idx}]`, {oninput: m.withAttr('value', v => this.onSliderChange(attrs, v))}), m(`input.spinner[min=1][for=${id}]`, spinnerCfg), m('.unit', attrs.unit)); } } // +---------------------------------------------------------------------------+ // | Dropdown: wrapper around