From 0e7e0a946cee5df0960651f81b780a007237ac4c Mon Sep 17 00:00:00 2001 From: Kunwoo Park Date: Tue, 24 Sep 2024 00:45:02 -0700 Subject: [PATCH 1/3] Add a download button on each cell --- .../web/service/ResultExportService.scala | 60 +++++++------------ .../result-exportation.component.html | 2 +- .../result-table-frame.component.html | 5 +- .../result-table-frame.component.ts | 13 +--- 4 files changed, 29 insertions(+), 51 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ResultExportService.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ResultExportService.scala index 87b4d949a34..45f8b074f09 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ResultExportService.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ResultExportService.scala @@ -1,6 +1,7 @@ package edu.uci.ics.texera.web.service import java.io.{ByteArrayInputStream, ByteArrayOutputStream} +import java.nio.charset.StandardCharsets import java.util import java.util.concurrent.{Executors, ThreadPoolExecutor} import com.github.tototoshi.csv.CSVWriter @@ -74,8 +75,8 @@ class ResultExportService(opResultStorage: OpResultStorage, wId: UInteger) { handleGoogleSheetRequest(cache, request, results, attributeNames) case "csv" => handleCSVRequest(user, request, results, attributeNames) - case "binary" => - handleBinaryRequest(user, request, results) + case "data" => + handleDataRequest(user, request, results) case _ => ResultExportResponse("error", s"Unknown export type: ${request.exportType}") } @@ -176,7 +177,7 @@ class ResultExportService(opResultStorage: OpResultStorage, wId: UInteger) { targetSheet.getSpreadsheetId } - private def handleBinaryRequest( + private def handleDataRequest( user: User, request: ResultExportRequest, results: Iterable[Tuple] @@ -191,47 +192,32 @@ class ResultExportService(opResultStorage: OpResultStorage, wId: UInteger) { } val selectedRow = results.toSeq(rowIndex) - val field: Any = selectedRow.getField(columnIndex) - // Ensure the field is of type byte[] - val binaryData: Array[Byte] = field match { + // Convert the field to a byte array, regardless of its type + val dataBytes: Array[Byte] = field match { case data: Array[Byte] => data - case data: AnyRef - if data.getClass.isArray && data.getClass.getComponentType == classOf[Byte] => - data.asInstanceOf[Array[Byte]] - case _ => - return ResultExportResponse( - "error", - s"Expected binary data (Array[Byte]), but got: ${field.getClass}" - ) + case data: String => data.getBytes(StandardCharsets.UTF_8) + case data => data.toString.getBytes(StandardCharsets.UTF_8) } - // Save the binary file (similar to how files are saved in the CSV handler) - binaryData match { - case data: Array[Byte] => - val byteArray = data - val fileStream = new ByteArrayInputStream(byteArray) - - // Save the binary file - request.datasetIds.foreach { did => - val datasetPath = PathUtils.getDatasetPath(UInteger.valueOf(did)) - val filePath = datasetPath.resolve(filename) - createNewDatasetVersionByAddingFiles( - UInteger.valueOf(did), - user, - Map(filePath -> fileStream) - ) - } - - ResultExportResponse( - "success", - s"Binary file $filename saved to Datasets ${request.datasetIds.mkString(",")}" - ) + // Save the data file + val fileStream = new ByteArrayInputStream(dataBytes) - case _ => - ResultExportResponse("error", s"Selected field is not binary data") + request.datasetIds.foreach { did => + val datasetPath = PathUtils.getDatasetPath(UInteger.valueOf(did)) + val filePath = datasetPath.resolve(filename) + createNewDatasetVersionByAddingFiles( + UInteger.valueOf(did), + user, + Map(filePath -> fileStream) + ) } + + ResultExportResponse( + "success", + s"Data file $filename saved to Datasets ${request.datasetIds.mkString(",")}" + ) } /** diff --git a/core/gui/src/app/workspace/component/result-exportation/result-exportation.component.html b/core/gui/src/app/workspace/component/result-exportation/result-exportation.component.html index 092dd357b0b..1fe8284b2e0 100644 --- a/core/gui/src/app/workspace/component/result-exportation/result-exportation.component.html +++ b/core/gui/src/app/workspace/component/result-exportation/result-exportation.component.html @@ -1,6 +1,6 @@
Filename diff --git a/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.html b/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.html index 4ee60ebe4ce..2f2c82feb3a 100644 --- a/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.html +++ b/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.html @@ -106,12 +106,11 @@
- {{ column.getCell(row) }} diff --git a/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.scss b/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.scss index af1d072c066..fb3e0b042d5 100644 --- a/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.scss +++ b/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.scss @@ -60,3 +60,39 @@ th.header-size { background-color: rgba(0, 0, 0, 0.05); } } + +.table-cell { + position: relative; + padding-right: 24px; // Make room for the download button +} + +.cell-content { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.download-button { + position: absolute; + right: 4px; + top: 50%; + transform: translateY(-50%); + opacity: 0; + transition: opacity 0.2s ease-in-out; + padding: 4px 8px; + + i { + font-size: 14px; + } +} + +.table-row-hover:hover { + .download-button { + opacity: 0.5; + + &:hover { + opacity: 1; + } + } +} From fa2467b4e771df63cc5fb73c93997d13edbf307a Mon Sep 17 00:00:00 2001 From: Kunwoo Park Date: Tue, 24 Sep 2024 09:42:45 -0700 Subject: [PATCH 3/3] formatting files --- .../texera/web/service/ResultExportService.scala | 4 ++-- .../result-table-frame.component.html | 8 ++++++-- .../result-table-frame.component.scss | 16 ++++++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ResultExportService.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ResultExportService.scala index 45f8b074f09..783505424f4 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ResultExportService.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ResultExportService.scala @@ -197,8 +197,8 @@ class ResultExportService(opResultStorage: OpResultStorage, wId: UInteger) { // Convert the field to a byte array, regardless of its type val dataBytes: Array[Byte] = field match { case data: Array[Byte] => data - case data: String => data.getBytes(StandardCharsets.UTF_8) - case data => data.toString.getBytes(StandardCharsets.UTF_8) + case data: String => data.getBytes(StandardCharsets.UTF_8) + case data => data.toString.getBytes(StandardCharsets.UTF_8) } // Save the data file diff --git a/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.html b/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.html index 4fdb5bbf248..5b9711eeae8 100644 --- a/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.html +++ b/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.html @@ -98,7 +98,9 @@
- + - + diff --git a/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.scss b/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.scss index fb3e0b042d5..ed2883e9690 100644 --- a/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.scss +++ b/core/gui/src/app/workspace/component/result-panel/result-table-frame/result-table-frame.component.scss @@ -63,7 +63,7 @@ th.header-size { .table-cell { position: relative; - padding-right: 24px; // Make room for the download button + padding-right: 28px; } .cell-content { @@ -80,17 +80,21 @@ th.header-size { transform: translateY(-50%); opacity: 0; transition: opacity 0.2s ease-in-out; - padding: 4px 8px; - + padding: 4px; + background: transparent; + border: none; + cursor: pointer; + i { - font-size: 14px; + font-size: 16px; // Slightly larger to match the cloud icon + color: #1890ff; // Ant Design's primary blue color } } .table-row-hover:hover { .download-button { - opacity: 0.5; - + opacity: 0.7; + &:hover { opacity: 1; }