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 {assertUnreachable} from '../base/logging'; 17import {Icons} from '../base/semantic_icons'; 18import {Timestamp} from '../components/widgets/timestamp'; 19import {TraceImpl} from '../core/trace_impl'; 20import {Note, SpanNote} from '../public/note'; 21import {NoteSelection} from '../public/selection'; 22import {Button} from '../widgets/button'; 23 24function getStartTimestamp(note: Note | SpanNote) { 25 const noteType = note.noteType; 26 switch (noteType) { 27 case 'SPAN': 28 return note.start; 29 case 'DEFAULT': 30 return note.timestamp; 31 default: 32 assertUnreachable(noteType); 33 } 34} 35 36interface NodeDetailsPanelAttrs { 37 readonly trace: TraceImpl; 38 readonly selection: NoteSelection; 39} 40 41export class NoteEditor implements m.ClassComponent<NodeDetailsPanelAttrs> { 42 view(vnode: m.CVnode<NodeDetailsPanelAttrs>) { 43 const {selection, trace} = vnode.attrs; 44 const id = selection.id; 45 const note = trace.notes.getNote(id); 46 if (note === undefined) { 47 return m('.', `No Note with id ${id}`); 48 } 49 const startTime = getStartTimestamp(note); 50 return m( 51 '.notes-editor-panel', 52 { 53 key: id, // Every note shoul get its own brand new DOM. 54 }, 55 m( 56 '.notes-editor-panel-heading-bar', 57 m( 58 '.notes-editor-panel-heading', 59 `Annotation at `, 60 m(Timestamp, {ts: startTime}), 61 ), 62 m('input[type=text]', { 63 oncreate: (v: m.VnodeDOM) => { 64 // NOTE: due to bad design decisions elsewhere this component is 65 // rendered every time the mouse moves on the canvas. We cannot set 66 // `value: note.text` as an input as that will clobber the input 67 // value as we move the mouse. 68 const inputElement = v.dom as HTMLInputElement; 69 inputElement.value = note.text; 70 }, 71 onchange: (e: InputEvent) => { 72 const newText = (e.target as HTMLInputElement).value; 73 trace.notes.changeNote(id, {text: newText}); 74 }, 75 }), 76 m( 77 'span.color-change', 78 `Change color: `, 79 m('input[type=color]', { 80 value: note.color, 81 onchange: (e: Event) => { 82 const newColor = (e.target as HTMLInputElement).value; 83 trace.notes.changeNote(id, {color: newColor}); 84 }, 85 }), 86 ), 87 m(Button, { 88 label: 'Remove', 89 icon: Icons.Delete, 90 onclick: () => trace.notes.removeNote(id), 91 }), 92 ), 93 ); 94 } 95} 96