juliethecao commented on code in PR #5675:
URL: https://github.com/apache/texera/pull/5675#discussion_r3603941291


##########
frontend/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.html:
##########
@@ -161,7 +161,40 @@ <h5 class="rightAlign"><span 
[innerHTML]="compare(column.header, 'other')"></spa
             class="table-cell"
             nzEllipsis
             (click)="open(i, row)">
-            <span class="cell-content">{{ column.getCell(row) }}</span>
+            <span class="cell-content">
+              <ng-container *ngIf="isVideoCell(column.getCell(row)); else 
checkAudio">

Review Comment:
   - Added `cellMediaTypes[rowIndex][columnIndex]` grid, built once in 
`setupResultTable()` whenever new row data arrives
   - Template now does a single O(1) [ngSwitch] lookup instead of 3 chained 
*ngIfs each calling isVideoCell/isAudioCell/isImageCell(column.getCell(row)), 
down from up to 4 getCell calls + 3 regex tests per cell per change-detection 
cycle to 1 lookup, with the regex work happening once per row on data arrival 



##########
frontend/src/app/workspace/component/result-panel/result-panel-modal.component.ts:
##########
@@ -42,29 +48,102 @@ import { NgxJsonViewerModule } from "ngx-json-viewer";
   selector: "texera-row-modal-content",
   templateUrl: "./result-panel-modal.component.html",
   styleUrls: ["./result-panel-model.component.scss"],
-  imports: [NgxJsonViewerModule],
+  imports: [CommonModule, NzButtonModule, NzIconModule],
 })
-export class RowModalComponent implements OnChanges {
+export class RowModalComponent implements OnChanges, OnDestroy {
+  rowEntries: { key: string; value: string; mediaSrc: string; isVideo: 
boolean; isImage: boolean; isAudio: boolean }[] =
+    [];
+  private readonly allocatedBlobUrls: string[] = [];
   // Index of current displayed row in currentResult
-  readonly operatorId: string = inject(NZ_MODAL_DATA).operatorId;
-  rowIndex: number = inject(NZ_MODAL_DATA).rowIndex;
+  private readonly modalData: { operatorId: string; rowIndex: number; 
rowData?: Record<string, unknown> } =
+    inject(NZ_MODAL_DATA);
+  readonly operatorId: string = this.modalData.operatorId;
+  rowIndex: number = this.modalData.rowIndex;
   currentDisplayRowData: Record<string, unknown> = {};
 
   constructor(
     public modal: NzModalRef<any, number>,
+    private http: HttpClient,
     private workflowResultService: WorkflowResultService,
-    private resizeService: PanelResizeService
+    private resizeService: PanelResizeService,
+    private notificationService: NotificationService
   ) {
+    if (this.modalData.rowData) {
+      this.currentDisplayRowData = this.modalData.rowData;
+      this.rowEntries = this.buildRowEntries(this.currentDisplayRowData);
+    }
     this.ngOnChanges();
   }
 
+  get prettyRowJson(): string {
+    return JSON.stringify(this.currentDisplayRowData, null, 2);
+  }
+
+  copyText(text: string): void {
+    navigator.clipboard.writeText(text).then(
+      () => this.notificationService.success("Copied to clipboard"),
+      () => this.notificationService.error("Failed to copy")
+    );
+  }
+
   ngOnChanges(): void {
     this.workflowResultService
       .getPaginatedResultService(this.operatorId)
       ?.selectTuple(this.rowIndex, this.resizeService.pageSize)
       .pipe(untilDestroyed(this))
       .subscribe(res => {
-        this.currentDisplayRowData = res.tuple;
+        if (res?.tuple) {
+          this.currentDisplayRowData = res.tuple;
+          this.rowEntries = this.buildRowEntries(this.currentDisplayRowData);
+        }
+      });
+  }
+
+  trackByEntryKey(_index: number, entry: { key: string }): string {
+    return entry.key;
+  }
+
+  ngOnDestroy(): void {
+    for (const url of this.allocatedBlobUrls) {
+      URL.revokeObjectURL(url);
+    }
+  }
+
+  private fetchBlobSrc(entry: { mediaSrc: string }, remoteUrl: string): void {
+    const proxyUrl = 
`${AppSettings.getApiEndpoint()}/huggingface/media-proxy?url=${encodeURIComponent(remoteUrl)}`;
+    this.http
+      .get(proxyUrl, { responseType: "blob" })
+      .pipe(untilDestroyed(this))
+      .subscribe({
+        next: blob => {
+          const blobUrl = URL.createObjectURL(blob);
+          this.allocatedBlobUrls.push(blobUrl);
+          entry.mediaSrc = blobUrl;
+        },
+        error: () => {
+          entry.mediaSrc = remoteUrl;

Review Comment:
   - On a proxy fetch error, fetchBlobSrc no longer falls back to 
`entry.mediaSrc = remoteUrl`. It now clears isVideo/isImage/isAudio on the 
entry so it falls through to the existing text view 
   - Updated the corresponding spec test to assert the new fallback behavior
   - Fixed one unrelated pre-existing test failure (bare filenames instead of 
full URLs) that surfaced while re-running the suite



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to