Skip to content

Commit f2a084e

Browse files
AlinaVarkkiDevtools-frontend LUCI CQ
authored andcommitted
[RPP] Update the Entry Label Annotation in the ModificationsManager when the label changes
Update the state of the ModificationsManager annotation which, in turn, updates the label in the sidebar as the label is edited. Bug: 349530077 Change-Id: Iadcb1768e5673a3840348a8b861c1695943aa08e Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5687112 Reviewed-by: Jack Franklin <jacktfranklin@chromium.org> Commit-Queue: Alina Varkki <alinavarkki@chromium.org>
1 parent efce5da commit f2a084e

6 files changed

Lines changed: 123 additions & 55 deletions

File tree

‎front_end/panels/timeline/ModificationsManager.ts‎

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,16 @@ import {type EntryLabel, type TimelineOverlay} from './Overlays.js';
1111
const modificationsManagerByTraceIndex: ModificationsManager[] = [];
1212
let activeManager: ModificationsManager|null;
1313

14-
// Event dispatched after an annotation was added.
15-
// The event argument is the Overlay that needs to be created by `Overlays.ts`.
16-
export class AnnotationAddedEvent extends Event {
17-
static readonly eventName = 'annotationaddedevent';
14+
export type UpdateAction = 'Remove'|'Add'|'UpdateLabel';
1815

19-
constructor(public addedAnnotationOverlay: TimelineOverlay) {
20-
super(AnnotationAddedEvent.eventName);
21-
}
22-
}
23-
24-
// Event dispatched after an annotation was removed.
25-
// The event argument is the Overlay that needs to be removed from `Overlays.ts`.
26-
export class AnnotationRemovedEvent extends Event {
27-
static readonly eventName = 'annotationremovedevent';
16+
// Event dispatched after an annotation was added, removed or updated.
17+
// The event argument is the Overlay that needs to be created,removed
18+
// or updated by `Overlays.ts` and the action that needs to be applied to it.
19+
export class AnnotationModifiedEvent extends Event {
20+
static readonly eventName = 'annotationmodifiedevent';
2821

29-
constructor(public removedAnnotationOverlay: TimelineOverlay) {
30-
super(AnnotationRemovedEvent.eventName);
22+
constructor(public overlay: TimelineOverlay, public action: UpdateAction) {
23+
super(AnnotationModifiedEvent.eventName);
3124
}
3225
}
3326

@@ -125,7 +118,7 @@ export class ModificationsManager extends EventTarget {
125118
this.#overlayForAnnotation.set(newAnnotation, newOverlay);
126119

127120
// TODO: When we have more annotations, check the annotation type and create the appropriate one
128-
this.dispatchEvent(new AnnotationAddedEvent(newOverlay));
121+
this.dispatchEvent(new AnnotationModifiedEvent(newOverlay, 'Add'));
129122
}
130123

131124
removeAnnotationOverlay(removedOverlay: TimelineOverlay): void {
@@ -135,7 +128,20 @@ export class ModificationsManager extends EventTarget {
135128
return;
136129
}
137130
this.#overlayForAnnotation.delete(annotationForRemovedOverlay);
138-
this.dispatchEvent(new AnnotationRemovedEvent(removedOverlay));
131+
this.dispatchEvent(new AnnotationModifiedEvent(removedOverlay, 'Remove'));
132+
}
133+
134+
updateAnnotationOverlay(updatedOverlay: TimelineOverlay): void {
135+
const annotationForUpdatedOverlay = this.#getAnnotationByOverlay(updatedOverlay);
136+
if (!annotationForUpdatedOverlay) {
137+
console.warn('Annotation for updated Overlay does not exist');
138+
return;
139+
}
140+
141+
if (updatedOverlay.type === 'ENTRY_LABEL') {
142+
annotationForUpdatedOverlay.label = updatedOverlay.label;
143+
}
144+
this.dispatchEvent(new AnnotationModifiedEvent(updatedOverlay, 'UpdateLabel'));
139145
}
140146

141147
#getAnnotationByOverlay(overlay: TimelineOverlay): TraceEngine.Types.File.Annotation|null {

‎front_end/panels/timeline/Overlays.test.ts‎

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {TraceLoader} from '../../testing/TraceLoader.js';
1313
import * as RenderCoordinator from '../../ui/components/render_coordinator/render_coordinator.js';
1414
import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
1515

16+
import * as Components from './components/components.js';
1617
import * as Timeline from './timeline.js';
1718

1819
const coordinator = RenderCoordinator.RenderCoordinator.RenderCoordinator.instance();
@@ -217,16 +218,20 @@ describeWithEnvironment('Overlays', () => {
217218

218219
const currManager = Timeline.ModificationsManager.ModificationsManager.activeManager();
219220
// The Annotations Overlays are added through the ModificationsManager listener
220-
currManager?.addEventListener(Timeline.ModificationsManager.AnnotationAddedEvent.eventName, event => {
221-
const addedOverlay = (event as Timeline.ModificationsManager.AnnotationAddedEvent).addedAnnotationOverlay;
222-
overlays.add(addedOverlay);
221+
currManager?.addEventListener(Timeline.ModificationsManager.AnnotationModifiedEvent.eventName, event => {
222+
const {overlay, action} = (event as Timeline.ModificationsManager.AnnotationModifiedEvent);
223+
if (action === 'Add') {
224+
overlays.add(overlay);
225+
}
223226
overlays.update();
224227
});
225228

226229
// When an annotation overlay is remomved, this event is dispatched to the Modifications Manager.
227-
overlays.addEventListener(Timeline.Overlays.AnnotationOverlayRemoveEvent.eventName, event => {
228-
const addedOverlay = (event as Timeline.Overlays.AnnotationOverlayRemoveEvent).overlay;
229-
overlays.remove(addedOverlay);
230+
overlays.addEventListener(Timeline.Overlays.AnnotationOverlayActionEvent.eventName, event => {
231+
const {overlay, action} = (event as Timeline.Overlays.AnnotationOverlayActionEvent);
232+
if (action === 'Remove') {
233+
overlays.remove(overlay);
234+
}
230235
overlays.update();
231236
});
232237

@@ -393,6 +398,35 @@ describeWithEnvironment('Overlays', () => {
393398
assert.strictEqual(overlays.overlaysForEntry(event).length, 0);
394399
});
395400

401+
it('Update label overlay when the label changes', async function() {
402+
const traceParsedData = await TraceLoader.traceEngine(this, 'web-dev.json.gz');
403+
const {overlays, container, charts} = setupChartWithDimensionsAndAnnotationOverlayListeners(traceParsedData);
404+
const event = charts.mainProvider.eventByIndex(50);
405+
assert.isOk(event);
406+
407+
// Create an entry label overlay
408+
Timeline.ModificationsManager.ModificationsManager.activeManager()?.createAnnotation({
409+
type: 'ENTRY_LABEL',
410+
entry: event as TraceEngine.Types.TraceEvents.TraceEventData,
411+
label: '',
412+
});
413+
overlays.update();
414+
415+
// Ensure that the overlay was created.
416+
const overlayDOM = container.querySelector<HTMLElement>('.overlay-type-ENTRY_LABEL');
417+
assert.isOk(overlayDOM);
418+
const component = overlayDOM?.querySelector('devtools-entry-label-overlay');
419+
assert.isOk(component?.shadowRoot);
420+
421+
component.connectedCallback();
422+
component.dispatchEvent(new Components.EntryLabelOverlay.EntryLabelChangeEvent('new label'));
423+
424+
const updatedOverlay = overlays.overlaysForEntry(event)[0] as Timeline.Overlays.EntryLabel;
425+
assert.isOk(updatedOverlay);
426+
// Make sure the label was updated in the Overlay Object
427+
assert.strictEqual(updatedOverlay.label, 'new label');
428+
});
429+
396430
it('can render an overlay for a time range', async function() {
397431
const traceParsedData = await TraceLoader.traceEngine(this, 'web-dev.json.gz');
398432
const {overlays, container} = setupChartWithDimensionsAndAnnotationOverlayListeners(traceParsedData);

‎front_end/panels/timeline/Overlays.ts‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,14 @@ export interface TimelineCharts {
122122
}
123123

124124
// An event dispatched when one of the Annotation Overlays (overlay created by the user,
125-
// ex. EntryLabel) is removed. When one of the Annotation Overlays is removed,
125+
// ex. EntryLabel) is removed or updated. When one of the Annotation Overlays is removed or updated,
126126
// ModificationsManager listens to this event and updates the current annotations.
127-
export class AnnotationOverlayRemoveEvent extends Event {
128-
static readonly eventName = 'annotationoverlayremoveevent';
127+
export type UpdateAction = 'Remove'|'Update';
128+
export class AnnotationOverlayActionEvent extends Event {
129+
static readonly eventName = 'annotationoverlayactionsevent';
129130

130-
constructor(public overlay: TimelineOverlay) {
131-
super(AnnotationOverlayRemoveEvent.eventName);
131+
constructor(public overlay: TimelineOverlay, public action: UpdateAction) {
132+
super(AnnotationOverlayActionEvent.eventName);
132133
}
133134
}
134135

@@ -656,7 +657,12 @@ export class Overlays extends EventTarget {
656657
case 'ENTRY_LABEL': {
657658
const component = new Components.EntryLabelOverlay.EntryLabelOverlay(overlay.label);
658659
component.addEventListener(Components.EntryLabelOverlay.EmptyEntryLabelRemoveEvent.eventName, () => {
659-
this.dispatchEvent(new AnnotationOverlayRemoveEvent(overlay));
660+
this.dispatchEvent(new AnnotationOverlayActionEvent(overlay, 'Remove'));
661+
});
662+
component.addEventListener(Components.EntryLabelOverlay.EntryLabelChangeEvent.eventName, event => {
663+
const newLabel = (event as Components.EntryLabelOverlay.EntryLabelChangeEvent).newLabel;
664+
overlay.label = newLabel;
665+
this.dispatchEvent(new AnnotationOverlayActionEvent(overlay, 'Update'));
660666
});
661667
div.appendChild(component);
662668
return div;

‎front_end/panels/timeline/TimelineFlameChartView.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {CountersGraph} from './CountersGraph.js';
1818
import {SHOULD_SHOW_EASTER_EGG} from './EasterEgg.js';
1919
import {ModificationsManager} from './ModificationsManager.js';
2020
import {
21-
AnnotationOverlayRemoveEvent,
21+
AnnotationOverlayActionEvent,
2222
Overlays,
2323
type TimelineOverlay,
2424
type TimeRangeLabel,
@@ -196,9 +196,13 @@ export class TimelineFlameChartView extends UI.Widget.VBox implements PerfUI.Fla
196196
},
197197
});
198198

199-
this.#overlays.addEventListener(AnnotationOverlayRemoveEvent.eventName, event => {
200-
const overlay = (event as AnnotationOverlayRemoveEvent).overlay;
201-
ModificationsManager.activeManager()?.removeAnnotationOverlay(overlay);
199+
this.#overlays.addEventListener(AnnotationOverlayActionEvent.eventName, event => {
200+
const {overlay, action} = (event as AnnotationOverlayActionEvent);
201+
if (action === 'Remove') {
202+
ModificationsManager.activeManager()?.removeAnnotationOverlay(overlay);
203+
} else if (action === 'Update') {
204+
ModificationsManager.activeManager()?.updateAnnotationOverlay(overlay);
205+
}
202206
});
203207

204208
this.networkPane = new UI.Widget.VBox();

‎front_end/panels/timeline/TimelinePanel.ts‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import {SHOULD_SHOW_EASTER_EGG} from './EasterEgg.js';
5858
import {Tracker} from './FreshRecording.js';
5959
import historyToolbarButtonStyles from './historyToolbarButton.css.js';
6060
import {IsolateSelector} from './IsolateSelector.js';
61-
import {AnnotationAddedEvent, AnnotationRemovedEvent, ModificationsManager} from './ModificationsManager.js';
61+
import {AnnotationModifiedEvent, ModificationsManager} from './ModificationsManager.js';
6262
import {cpuprofileJsonGenerator, traceJsonGenerator} from './SaveFileFormatter.js';
6363
import {NodeNamesUpdated, SourceMapsResolver} from './SourceMapsResolver.js';
6464
import {type Client, TimelineController} from './TimelineController.js';
@@ -1294,15 +1294,13 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod
12941294
}
12951295

12961296
// Add ModificationsManager listeners for annotations change to update the Annotation Overlays.
1297-
currentManager?.addEventListener(AnnotationAddedEvent.eventName, event => {
1298-
const addedOverlay = (event as AnnotationAddedEvent).addedAnnotationOverlay;
1299-
this.flameChart.addOverlay(addedOverlay);
1300-
this.#sideBar.setAnnotationsTabContent(currentManager.getAnnotations());
1301-
});
1302-
1303-
currentManager?.addEventListener(AnnotationRemovedEvent.eventName, event => {
1304-
const removedOverlay = (event as AnnotationRemovedEvent).removedAnnotationOverlay;
1305-
this.flameChart.removeOverlay(removedOverlay);
1297+
currentManager?.addEventListener(AnnotationModifiedEvent.eventName, event => {
1298+
const {overlay, action} = (event as AnnotationModifiedEvent);
1299+
if (action === 'Add') {
1300+
this.flameChart.addOverlay(overlay);
1301+
} else if (action === 'Remove') {
1302+
this.flameChart.removeOverlay(overlay);
1303+
}
13061304
this.#sideBar.setAnnotationsTabContent(currentManager.getAnnotations());
13071305
});
13081306

‎front_end/panels/timeline/components/EntryLabelOverlay.ts‎

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ export class EmptyEntryLabelRemoveEvent extends Event {
1414
}
1515
}
1616

17+
export class EntryLabelChangeEvent extends Event {
18+
static readonly eventName = 'entrylabelchangeevent';
19+
20+
constructor(public newLabel: string) {
21+
super(EntryLabelChangeEvent.eventName);
22+
}
23+
}
24+
1725
export class EntryLabelOverlay extends HTMLElement {
1826
// The label is angled on the left from the centre of the entry it belongs to.
1927
// `LABEL_AND_CONNECTOR_SHIFT_LENGTH` specifies how many pixels to the left it is shifted.
@@ -25,7 +33,7 @@ export class EntryLabelOverlay extends HTMLElement {
2533
static readonly LABEL_AND_CONNECTOR_HEIGHT =
2634
EntryLabelOverlay.LABEL_HEIGHT + EntryLabelOverlay.LABEL_PADDING * 2 + EntryLabelOverlay.LABEL_CONNECTOR_HEIGHT;
2735
// Set the max label length to avoid labels that could signicantly increase the file size.
28-
static readonly MAX_LABEL_LENGTH = 100;
36+
static readonly MAX_LABEL_LENGTH = 1;
2937

3038
static readonly litTagName = LitHtml.literal`devtools-entry-label-overlay`;
3139
readonly #shadow = this.attachShadow({mode: 'open'});
@@ -37,6 +45,7 @@ export class EntryLabelOverlay extends HTMLElement {
3745

3846
#labelPartsWrapper: HTMLElement|null = null;
3947
#labelBox: HTMLElement|null = null;
48+
#label: string;
4049

4150
/*
4251
The entry label overlay consists of 3 parts - the label part with the label string inside,
@@ -62,22 +71,29 @@ Otherwise, the entry label overlay object only gets repositioned.
6271
super();
6372
this.#render();
6473
this.#labelPartsWrapper = this.#shadow.querySelector<HTMLElement>('.label-parts-wrapper');
74+
this.#label = label;
6575
this.#drawLabel(label);
6676
this.#drawConnector();
6777
}
6878

6979
connectedCallback(): void {
7080
this.#shadow.adoptedStyleSheets = [styles];
71-
this.#labelBox?.addEventListener('keydown', this.#handleLabelInputKeyDown);
72-
this.#labelBox?.addEventListener('paste', this.#handleLabelInputPaste);
7381
}
7482

75-
disconnectedCallback(): void {
76-
this.#labelBox?.removeEventListener('keydown', this.#handleLabelInputKeyDown);
77-
this.#labelBox?.removeEventListener('paste', this.#handleLabelInputPaste);
83+
#handleLabelInputKeyUp(): void {
84+
// If the label changed on key up, dispatch label changed event
85+
const labelBoxTextContent = this.#labelBox?.textContent ?? '';
86+
if (labelBoxTextContent !== this.#label) {
87+
this.#label = labelBoxTextContent;
88+
this.dispatchEvent(new EntryLabelChangeEvent(this.#label));
89+
}
7890
}
7991

8092
#handleLabelInputKeyDown(event: KeyboardEvent): boolean {
93+
if (!this.#labelBox) {
94+
return false;
95+
}
96+
8197
const allowedKeysAfterReachingLenLimit = [
8298
'Backspace',
8399
'Delete',
@@ -89,12 +105,13 @@ Otherwise, the entry label overlay object only gets repositioned.
89105
// Therefore, if the new key is `Enter` key, treat it
90106
// as the end of the label input and blur the input field.
91107
if (event.key === 'Enter' || event.key === 'Escape') {
92-
this.dispatchEvent(new FocusEvent('blur', {bubbles: true}));
108+
this.#labelBox.dispatchEvent(new FocusEvent('blur', {bubbles: true}));
93109
return false;
94110
}
95111

96112
// If the max limit is not reached, return true
97-
if (!this.textContent || this.textContent.length <= EntryLabelOverlay.MAX_LABEL_LENGTH) {
113+
if (this.#labelBox.textContent !== null &&
114+
this.#labelBox.textContent.length <= EntryLabelOverlay.MAX_LABEL_LENGTH) {
98115
return true;
99116
}
100117

@@ -114,21 +131,21 @@ Otherwise, the entry label overlay object only gets repositioned.
114131
event.preventDefault();
115132

116133
const clipboardData = event.clipboardData;
117-
if (!clipboardData) {
134+
if (!clipboardData || !this.#labelBox) {
118135
return;
119136
}
120137

121138
const pastedText = clipboardData.getData('text');
122139

123-
const newText = this.textContent + pastedText;
140+
const newText = this.#labelBox.textContent + pastedText;
124141
const trimmedText = newText.slice(0, EntryLabelOverlay.MAX_LABEL_LENGTH + 1);
125142

126-
this.textContent = trimmedText;
143+
this.#labelBox.textContent = trimmedText;
127144

128145
// Reset the selection to the end
129146
const selection = window.getSelection();
130147
const range = document.createRange();
131-
range.selectNodeContents(this);
148+
range.selectNodeContents(this.#labelBox);
132149
range.collapse(false);
133150
selection?.removeAllRanges();
134151
selection?.addRange(range);
@@ -262,6 +279,9 @@ Otherwise, the entry label overlay object only gets repositioned.
262279
class="label-box"
263280
@dblclick=${() => this.#setLabelEditabilityAndRemoveEmptyLabel(true)}
264281
@blur=${() => this.#setLabelEditabilityAndRemoveEmptyLabel(false)}
282+
@keydown=${this.#handleLabelInputKeyDown}
283+
@paste=${this.#handleLabelInputPaste}
284+
@keyup=${this.#handleLabelInputKeyUp}
265285
contenteditable=${this.#isLabelEditable}>
266286
</span>
267287
<svg class="connectorContainer">

0 commit comments

Comments
 (0)