Add copy buttons for protolog source files.
buttons appear on hover
Bug: 406270438
Test: npm run test:unit:ci
Change-Id: I76a66a192eee4f8dedfd09a679438aaecd62a2e5
diff --git a/tools/winscope/src/viewers/common/ui_data_log.ts b/tools/winscope/src/viewers/common/ui_data_log.ts
index a41841e..d22a8bb 100644
--- a/tools/winscope/src/viewers/common/ui_data_log.ts
+++ b/tools/winscope/src/viewers/common/ui_data_log.ts
@@ -39,6 +39,7 @@
export interface ColumnSpec {
name: string;
cssClass: string;
+ canCopy?: boolean;
}
export class LogHeader {
diff --git a/tools/winscope/src/viewers/components/log_component.ts b/tools/winscope/src/viewers/components/log_component.ts
index 1f73e43..0acfbca 100644
--- a/tools/winscope/src/viewers/components/log_component.ts
+++ b/tools/winscope/src/viewers/components/log_component.ts
@@ -187,7 +187,7 @@
</button>
</div>
- <div [class]="field.spec.cssClass" *ngFor="let field of entry.fields; index as i">
+ <div [class]="field.spec.cssClass + ' cell'" *ngFor="let field of entry.fields; index as i">
<span class="mat-body-1" *ngIf="!showFieldButton(entry, field)">{{ field.value }}</span>
<button
*ngIf="showFieldButton(entry, field)"
@@ -201,6 +201,13 @@
*ngIf="field.icon"
aria-hidden="false"
[style]="{color: field.iconColor}"> {{field.icon}} </mat-icon>
+ <button
+ mat-icon-button
+ *ngIf="field.spec.canCopy"
+ class="copy-button"
+ [cdkCopyToClipboard]="field.value.toString()">
+ <mat-icon>content_copy</mat-icon>
+ </button>
</div>
</div>
</ng-template>
diff --git a/tools/winscope/src/viewers/components/log_component_test.ts b/tools/winscope/src/viewers/components/log_component_test.ts
index 40c7b9b..79a6d70 100644
--- a/tools/winscope/src/viewers/components/log_component_test.ts
+++ b/tools/winscope/src/viewers/components/log_component_test.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import {Clipboard, ClipboardModule} from '@angular/cdk/clipboard';
import {ScrollingModule} from '@angular/cdk/scrolling';
import {ComponentFixtureAutoDetect, TestBed} from '@angular/core/testing';
import {FormsModule} from '@angular/forms';
@@ -63,10 +64,15 @@
let component: LogComponent;
let dom: DOMTestHelper<LogComponent>;
+ let mockCopyText: jasmine.Spy;
beforeEach(async () => {
+ mockCopyText = jasmine.createSpy();
await TestBed.configureTestingModule({
- providers: [{provide: ComponentFixtureAutoDetect, useValue: true}],
+ providers: [
+ {provide: Clipboard, useValue: {copy: mockCopyText}},
+ {provide: ComponentFixtureAutoDetect, useValue: true},
+ ],
imports: [
ScrollingModule,
MatFormFieldModule,
@@ -80,6 +86,7 @@
MatPseudoCheckboxModule,
MatProgressSpinnerModule,
MatTooltipModule,
+ ClipboardModule,
],
declarations: [
LogComponent,
@@ -297,6 +304,19 @@
entry.checkTextExact('00:00:00.000 Test tag 21234 N/A');
});
+ it('shows copy button for spec that can be copied', () => {
+ const entry = dom.get('.scroll .entry .test-2');
+ expect(entry.find('.copy-button')).toBeUndefined();
+ component.entries[0].fields[1].spec = {
+ name: 'test2',
+ cssClass: 'test-2',
+ canCopy: true,
+ };
+ dom.detectChanges();
+ entry.findAndClick('.copy-button');
+ expect(mockCopyText).toHaveBeenCalledOnceWith('123');
+ });
+
function setComponentInputData(elapsed = true) {
let entryTime: Timestamp;
let fieldTime: Timestamp;
diff --git a/tools/winscope/src/viewers/components/styles/log_component.styles.ts b/tools/winscope/src/viewers/components/styles/log_component.styles.ts
index 8c4bd4f..916b916 100644
--- a/tools/winscope/src/viewers/components/styles/log_component.styles.ts
+++ b/tools/winscope/src/viewers/components/styles/log_component.styles.ts
@@ -34,10 +34,6 @@
display: flex;
flex-direction: row;
overflow-wrap: anywhere;
- }
-
- .headers div,
- .entries div {
padding: 4px;
}
@@ -45,6 +41,10 @@
align-content: center;
}
+ .header, .filter, .cell {
+ padding: 4px;
+ }
+
.time {
flex: 1;
min-width: 135px;
@@ -179,10 +179,25 @@
justify-content: end;
}
- .status .mat-icon {
+ .entry .source-file {
+ display: flex;
+ align-items: start;
+ justify-content: space-between;
+ }
+
+ .status .mat-icon, .copy-button, .copy-button .mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
+ line-height: 18px;
+ }
+
+ .cell:not(:hover) .copy-button {
+ visibility: hidden;
+ }
+
+ .copy-button .mat-icon {
+ min-width: 18px;
}
.input-type {
diff --git a/tools/winscope/src/viewers/viewer_input/presenter.ts b/tools/winscope/src/viewers/viewer_input/presenter.ts
index bf8b4a8..e7ffb3e 100644
--- a/tools/winscope/src/viewers/viewer_input/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_input/presenter.ts
@@ -367,10 +367,10 @@
): string {
const keyDetails =
'Keycode: ' +
- eventTree
- .getChildByName('keyCode')
- ?.formattedValue()
- ?.replace(/^KEYCODE_/, '') ?? '<?>';
+ (eventTree
+ .getChildByName('keyCode')
+ ?.formattedValue()
+ ?.replace(/^KEYCODE_/, '') ?? '<?>');
return keyDetails + ' ' + Presenter.extractDispatchDetails(dispatchTree);
}
diff --git a/tools/winscope/src/viewers/viewer_protolog/presenter.ts b/tools/winscope/src/viewers/viewer_protolog/presenter.ts
index dfbcb1c..ba4d957 100644
--- a/tools/winscope/src/viewers/viewer_protolog/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_protolog/presenter.ts
@@ -44,6 +44,7 @@
sourceFile: {
name: 'Source files',
cssClass: 'source-file',
+ canCopy: true,
},
text: {
name: 'Search text',
diff --git a/tools/winscope/src/viewers/viewer_protolog/presenter_test.ts b/tools/winscope/src/viewers/viewer_protolog/presenter_test.ts
index b33b45f..17cdda2 100644
--- a/tools/winscope/src/viewers/viewer_protolog/presenter_test.ts
+++ b/tools/winscope/src/viewers/viewer_protolog/presenter_test.ts
@@ -54,7 +54,7 @@
},
{
header: new LogHeader(
- {name: 'Source files', cssClass: 'source-file'},
+ {name: 'Source files', cssClass: 'source-file', canCopy: true},
new LogSelectFilter(Array.from({length: 3}, () => '')),
),
options: ['sourcefile0', 'sourcefile1', 'sourcefile2'],