diff --git a/client/packages/lowcoder/src/components/table/EditableCell.tsx b/client/packages/lowcoder/src/components/table/EditableCell.tsx index 936f1a047..3e68cfdff 100644 --- a/client/packages/lowcoder/src/components/table/EditableCell.tsx +++ b/client/packages/lowcoder/src/components/table/EditableCell.tsx @@ -7,6 +7,7 @@ import styled from "styled-components"; import { JSONValue } from "util/jsonTypes"; import ColumnTypeView from "./columnTypeView"; import { TableCellContext } from "comps/comps/tableComp/tableContext"; +import Tooltip from "antd/es/tooltip"; type StatusType = PresetStatusColorType | "none"; export const TABLE_EDITABLE_SWITCH_ON = true; @@ -35,6 +36,7 @@ export interface CellProps { candidateTags?: string[]; candidateStatus?: { text: string; status: StatusType }[]; textOverflow?: boolean; + cellTooltip?: string; onTableEvent?: (eventName: any) => void; } @@ -54,6 +56,25 @@ const BorderDiv = styled.div` left: 0; `; +const CellWrapper = ({ + children, + tooltipTitle, +}: { + children: ReactNode, + tooltipTitle?: string, +}) => { + if (tooltipTitle) { + return ( + + {children} + + ) + } + return ( + <>{children} + ) +}; + interface EditableCellProps extends CellProps { normalView: ReactNode; dispatch: DispatchType; @@ -123,27 +144,31 @@ export function EditableCell(props: EditableCellProps) { ); } - + return ( - - {status === "toSave" && !isEditing && } - {normalView} - {/* overlay on normal view to handle double click for editing */} - {editable && ( -
-
- )} -
+ + {status === "toSave" && !isEditing && } + + {normalView} + + {/* overlay on normal view to handle double click for editing */} + {editable && ( + +
+
+
+ )} +
); } diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnBooleanComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnBooleanComp.tsx index b98924193..8c53650a8 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnBooleanComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnBooleanComp.tsx @@ -21,9 +21,10 @@ const Wrapper = styled.div` padding: 0 8px; `; -const IconWrapper = styled.div<{ $style: CheckboxStyleType; $ifChecked: boolean }>` - pointer-events: none; +const IconWrapper = styled.span<{ $style: CheckboxStyleType; $ifChecked: boolean }>` + // pointer-events: none; height: 22px; + display: inline-block; svg { width: 14px; height: 22px; diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx index c3e35b13e..c09a74215 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx @@ -47,7 +47,9 @@ export const ColumnDropdownComp = (function () { const menu = ( items.find((o) => o.key === key)?.onEvent?.("click")} + onClick={({ key }) => { + items.find((o) => o.key === key)?.onEvent?.("click") + }} /> ); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx index e312c0011..04330e9c6 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx @@ -21,13 +21,17 @@ export const SimpleTextComp = (function () { childrenMap, (props, dispatch) => { const value = props.changeValue ?? getBaseValue(props, dispatch); - return <>{hasIcon(props.prefixIcon) && ( - {props.prefixIcon} - )} - {value} - {hasIcon(props.suffixIcon) && ( - {props.suffixIcon} - )} ; + return( + <> + {hasIcon(props.prefixIcon) && ( + {props.prefixIcon} + )} + {value} + {hasIcon(props.suffixIcon) && ( + {props.suffixIcon} + )} + + ); }, (nodeValue) => nodeValue.text.value, getBaseValue diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx index 613a0b910..c9057ed73 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx @@ -28,6 +28,7 @@ import { JSONValue } from "util/jsonTypes"; import styled from "styled-components"; import { TextOverflowControl } from "comps/controls/textOverflowControl"; import { default as Divider } from "antd/es/divider"; +import { ColumnValueTooltip } from "./simpleColumnTypeComps"; export type Render = ReturnType["getOriginalComp"]>; export const RenderComp = withSelectedMultiContext(ColumnTypeComp); @@ -83,11 +84,39 @@ export type CellColorViewType = (param: { currentCell: JSONValue | undefined; //number | string; }) => string; +const cellTooltipLabel = trans("table.columnTooltip"); +const CellTooltipTempComp = withContext( + new MultiCompBuilder({ tooltip: StringControl }, (props) => props.tooltip) + .setPropertyViewFn((children) => + children.tooltip.propertyView({ + label: cellTooltipLabel, + tooltip: ColumnValueTooltip, + }) + ) + .build(), + ["currentCell", "currentRow", "currentIndex"] as const +); + +// @ts-ignore +export class CellTooltipComp extends CellTooltipTempComp { + override getPropertyView() { + return controlItem({ filterText: cellTooltipLabel }, super.getPropertyView()); + } +} + +// fixme, should be infer from RowColorComp, but withContext type incorrect +export type CellTooltipViewType = (param: { + currentRow: any; + currentCell: JSONValue | undefined; //number | string; +}) => string; + + export const columnChildrenMap = { // column title title: StringControl, - tooltip: StringControl, + titleTooltip: StringControl, showTitle: withDefault(BoolControl, true), + cellTooltip: CellTooltipComp, // a custom column or a data column isCustom: valueComp(false), // If it is a data column, it must be the name of the column and cannot be duplicated as a react key @@ -158,6 +187,16 @@ export class ColumnComp extends ColumnInitComp { }) ) ); + comp = comp.setChild( + "cellTooltip", + comp.children.cellTooltip.reduce( + CellTooltipComp.changeContextDataAction({ + currentCell: undefined, + currentRow: {}, + currentIndex: 0, + }) + ) + ); } if (action.type === CompActionTypes.CHANGE_VALUE) { const title = comp.children.title.unevaledValue; @@ -208,9 +247,10 @@ export class ColumnComp extends ColumnInitComp { label: trans("table.columnTitle"), placeholder: this.children.dataIndex.getView(), })} - {this.children.tooltip.propertyView({ - label: trans("labelProp.tooltip"), + {this.children.titleTooltip.propertyView({ + label: trans("table.columnTitleTooltip"), })} + {this.children.cellTooltip.getPropertyView()} ({ stackType: "and", filters: [] }), position: dropdownControl(positionOptions, "below"), + columnSeparator: withDefault(StringControl, ','), }; return new ControlNodeCompBuilder(childrenMap, (props, dispatch) => { @@ -588,6 +589,10 @@ export const TableToolbarComp = (function () { children.showFilter.propertyView({ label: trans("table.showFilter") }), children.showRefresh.propertyView({ label: trans("table.showRefresh") }), children.showDownload.propertyView({ label: trans("table.showDownload") }), + children.showDownload.getView() && children.columnSeparator.propertyView({ + label: trans("table.columnSeparator"), + tooltip: trans("table.columnSeparatorTooltip"), + }), children.columnSetting.propertyView({ label: trans("table.columnSetting") }), /* children.searchText.propertyView({ label: trans("table.searchText"), diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx index 324ec8a1c..3d9328668 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx @@ -337,7 +337,7 @@ export function columnsToAntdFormat( text: string; status: StatusType; }[]; - const title = renderTitle({ title: column.title, tooltip: column.tooltip, editable: column.editable }); + const title = renderTitle({ title: column.title, tooltip: column.titleTooltip, editable: column.editable }); return { title: column.showTitle ? title : '', @@ -366,11 +366,12 @@ export function columnsToAntdFormat( cellColorFn: column.cellColor, onWidthResize: column.onWidthResize, render: (value: any, record: RecordType, index: number) => { + const row = _.omit(record, OB_ROW_ORI_INDEX); return column .render( { currentCell: value, - currentRow: _.omit(record, OB_ROW_ORI_INDEX), + currentRow: row, currentIndex: index, currentOriginalIndex: tryToNumber(record[OB_ROW_ORI_INDEX]), initialColumns, @@ -384,6 +385,11 @@ export function columnsToAntdFormat( candidateTags: tags, candidateStatus: status, textOverflow: column.textOverflow, + cellTooltip: column.cellTooltip({ + currentCell: value, + currentRow: row, + currentIndex: index, + }), onTableEvent, }); }, diff --git a/client/packages/lowcoder/src/i18n/locales/de.ts b/client/packages/lowcoder/src/i18n/locales/de.ts index 857193a35..9c4538827 100644 --- a/client/packages/lowcoder/src/i18n/locales/de.ts +++ b/client/packages/lowcoder/src/i18n/locales/de.ts @@ -1381,6 +1381,8 @@ export const de: typeof en = { "showFilter": "Schaltfläche Filter anzeigen", "showRefresh": "Schaltfläche \"Aktualisieren\" anzeigen", "showDownload": "Download-Schaltfläche anzeigen", + "columnSeparator": "Spaltentrennzeichen", + "columnSeparatorTooltip": "Spaltentrennzeichen („Trennzeichen“) in der heruntergeladenen CSV-Datei. \n\nEmpfehlungen:\n- Komma (,)\n- Semikolon (;)\n- Pipe (|)\n- Tabulator (\\t)", "columnSetting": "Schaltfläche Spalteneinstellung anzeigen", "searchText": "Text suchen", "searchTextTooltip": "Suche und Filterung der in der Tabelle dargestellten Daten", @@ -1413,10 +1415,12 @@ export const de: typeof en = { "action": "Aktion", "columnValue": "Spalte Wert", "columnValueTooltip": "\\'{{currentCell}}\\': Aktuelle Zelldaten\n \\'{{currentRow}}\\': Aktuelle Zeilendaten\n \\'{{currentIndex}}\\': Aktueller Datenindex (beginnend bei 0)\n Beispiel: \\'{{currentCell * 5}}\\' Show 5 Times the Original Value Data.", + "columnTooltip": "Spalten-Tooltip", "imageSrc": "Bildquelle", "imageSize": "Bildgröße", "columnTitle": "Titel anzeigen", - "showTitle": "Show Title", + "columnTitleTooltip": "Titel-Tooltip", + "showTitle": "Titel anzeigen", "showTitleTooltip": "Spaltentitel im Tabellenkopf ein-/ausblenden", "sortable": "Sortierbar", "align": "Ausrichtung", diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 4a818862b..bcddb753c 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -1836,6 +1836,8 @@ export const en = { "showFilter": "Show Filter Button", "showRefresh": "Show Refresh Button", "showDownload": "Show Download Button", + "columnSeparator": "Column Separator", + "columnSeparatorTooltip": "Column Separator (\"delimiter\") in downloaded CSV file. \n\nRecommendations:\n- Comma (,)\n- Semicolon (;)\n- Pipe (|)\n- Tab (\\t)", "columnSetting": "Show Column Setting Button", "searchText": "Search Text", "searchTextTooltip": "Search and Filter the Data Presented in the Table", @@ -1868,9 +1870,11 @@ export const en = { "action": "Action", "columnValue": "Column Value", "columnValueTooltip": "'{{currentCell}}': Current Cell Data\n '{{currentRow}}': Current Row Data\n '{{currentIndex}}': Current Data Index (Starting from 0)\n Example: '{{currentCell * 5}}' Show 5 Times the Original Value Data.", + "columnTooltip": "Column Tooltip", "imageSrc": "Image Source", "imageSize": "Image Size", "columnTitle": "Title", + "columnTitleTooltip": "Title Tooltip", "showTitle": "Show Title", "showTitleTooltip": "Show/Hide column title in table header", "sortable": "Sortable", diff --git a/client/packages/lowcoder/src/i18n/locales/pt.ts b/client/packages/lowcoder/src/i18n/locales/pt.ts index 4cbc223e7..f8f6c41a9 100644 --- a/client/packages/lowcoder/src/i18n/locales/pt.ts +++ b/client/packages/lowcoder/src/i18n/locales/pt.ts @@ -1903,6 +1903,8 @@ export const pt: typeof en = { "showFilter": "Mostrar Botão de Filtro", "showRefresh": "Mostrar Botão de Atualização", "showDownload": "Mostrar Botão de Download", + "columnSeparator": "Separador de colunas", + "columnSeparatorTooltip": "Separador de colunas (\"delimitador\") no arquivo CSV baixado. \n\nRecomendações:\n- Vírgula (,)\n- Ponto e vírgula (;)\n- Barra vertical (|)\n- Tabulação (\\t)", "columnSetting": "Mostrar Botão de Configuração de Coluna", "searchText": "Texto de Busca", "searchTextTooltip": "Pesquisar e filtrar os dados apresentados na tabela", @@ -1935,9 +1937,11 @@ export const pt: typeof en = { "action": "Ação", "columnValue": "Valor da Coluna", "columnValueTooltip": "'{{currentCell}}': Dados da Célula Atual\n '{{currentRow}}': Dados da Linha Atual\n '{{currentIndex}}': Índice de Dados Atual (Começando de 0)\n Exemplo: '{{currentCell * 5}}' Mostra 5 Vezes o Valor Original dos Dados.", + "columnTooltip": "Dica de coluna", "imageSrc": "Fonte da Imagem", "imageSize": "Tamanho da Imagem", "columnTitle": "Título", + "columnTitleTooltip": "Dica de título", "showTitle": "Mostrar Título", "showTitleTooltip": "Mostrar/Ocultar título da coluna no cabeçalho da tabela", "sortable": "Classificável", diff --git a/client/packages/lowcoder/src/i18n/locales/zh.ts b/client/packages/lowcoder/src/i18n/locales/zh.ts index 6e9207adf..cd54a777a 100644 --- a/client/packages/lowcoder/src/i18n/locales/zh.ts +++ b/client/packages/lowcoder/src/i18n/locales/zh.ts @@ -1403,6 +1403,8 @@ export const zh: typeof en = { showFilter: "显示筛选按钮", showRefresh: "显示刷新按钮", showDownload: "显示下载按钮", + columnSeparator: "柱分离器", + columnSeparatorTooltip: "下载的 CSV 文件中的列分隔符(\“分隔符\”)。 \n\n建议:\n- 逗号 (,)\n- 分号 (;)\n- 竖线 (|)\n- 制表符 (\\t)", columnSetting: "显示列设置按钮", searchText: "搜索文本", searchTextTooltip: "搜索和筛选在表格中呈现的数据", @@ -1435,9 +1437,11 @@ export const zh: typeof en = { action: "操作", columnValue: "列值", columnValueTooltip: "'{{currentCell}}': 当前单元格数据\n" + "'{{currentRow}}': 当前行数据\n" + "'{{currentIndex}}': 当前数据索引(从0开始)\n" + "示例: '{{currentCell * 5}}' 显示原始值数据的5倍.", + columnTooltip: "列工具提示", imageSrc: "图片链接", imageSize: "图片尺寸", columnTitle: "标题", + columnTitleTooltip: "标题工具提示", dataMapping: "数据映射", showTitle: "显示标题", showTitleTooltip: "显示/隐藏表标题中的列标题", diff --git a/client/packages/lowcoder/src/util/fileUtils.ts b/client/packages/lowcoder/src/util/fileUtils.ts index 7b98e21ff..716330748 100644 --- a/client/packages/lowcoder/src/util/fileUtils.ts +++ b/client/packages/lowcoder/src/util/fileUtils.ts @@ -31,9 +31,10 @@ interface SaveDataAsFileParams { filename: string; fileType: "empty" | "txt" | "json" | "csv" | "xlsx" | string; dataType?: "url" | "base64"; + delimiter?: string; } -export async function saveDataAsFile({ data, filename, fileType, dataType }: SaveDataAsFileParams) { +export async function saveDataAsFile({ data, filename, fileType, dataType, delimiter }: SaveDataAsFileParams) { if (dataType === "url") { return saveAs(data, filename, { autoBom: true }); } @@ -72,11 +73,15 @@ export async function saveDataAsFile({ data, filename, fileType, dataType }: Sav } else { wb = XLSX.read(data, { type: "string" }); } - blobData = XLSX.write(wb, { + let writeOptions: any = { bookType: finalFileType, bookSST: false, // whether to generate Shared String Table? setting true will slow down the generating speed, but more compatible for lower versioned iOS devices type: "buffer", - }); + } + if (finalFileType === 'csv' && delimiter) { + writeOptions['FS'] = delimiter; + } + blobData = XLSX.write(wb, writeOptions); break; } const blob = new Blob([blobData], {