-
Notifications
You must be signed in to change notification settings - Fork 505
7855 cb add setting use the users os formatting for numbersdates #4020
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
95ff5eb
880ff06
507f577
cf92bab
96a044d
94f5493
48b74d8
fa54ba5
efdac7c
27240c5
f391586
9902a6e
543c687
01b1054
7ce2168
bf3dc5e
af2257b
c3694e7
3966098
167de1e
7c82e1a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /* | ||
| * CloudBeaver - Cloud Database Manager | ||
| * Copyright (C) 2020-2024 DBeaver Corp and others | ||
| * | ||
| * Licensed under the Apache License, Version 2.0. | ||
| * you may not use this file except in compliance with the License. | ||
| */ | ||
|
|
||
| .dateFormatter { | ||
| display: flex; | ||
| align-items: center; | ||
| overflow: hidden; | ||
| } | ||
|
|
||
| .dateFormatterValue { | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| /* | ||
| * CloudBeaver - Cloud Database Manager | ||
| * Copyright (C) 2020-2025 DBeaver Corp and others | ||
| * | ||
| * Licensed under the Apache License, Version 2.0. | ||
| * you may not use this file except in compliance with the License. | ||
| */ | ||
| import { observer } from 'mobx-react-lite'; | ||
| import { useContext } from 'react'; | ||
|
|
||
| import { getComputed, s, useS } from '@cloudbeaver/core-blocks'; | ||
| import { NullFormatter as GridNullFormatter } from '@cloudbeaver/plugin-data-grid'; | ||
|
|
||
| import { CellContext } from '../../CellRenderer/CellContext.js'; | ||
| import { TableDataContext, DateTimeKind } from '../../TableDataContext.js'; | ||
| import styles from './DateTimeFormatter.module.css'; | ||
| import type { ICellFormatterProps } from '../ICellFormatterProps.js'; | ||
|
|
||
| export const DateTimeFormatter = observer<ICellFormatterProps>(function DateTimeFormatter() { | ||
| const tableDataContext = useContext(TableDataContext); | ||
| const cellContext = useContext(CellContext); | ||
| const style = useS(styles); | ||
|
|
||
| if (!cellContext.cell) { | ||
| return null; | ||
| } | ||
|
|
||
| const formatter = tableDataContext.format; | ||
| const valueHolder = getComputed(() => formatter.get(cellContext.cell!)); | ||
| const nullValue = getComputed(() => formatter.isNull(valueHolder)); | ||
| const displayValue = getComputed(() => formatter.getDisplayString(valueHolder)); | ||
|
|
||
| if (nullValue) { | ||
| return <GridNullFormatter />; | ||
| } | ||
|
|
||
| let value = displayValue; | ||
|
|
||
| const extendedDateKind = tableDataContext.getExtendedDateKind(cellContext.cell.column); | ||
|
|
||
| let dateFormatter; | ||
| switch (extendedDateKind) { | ||
| case DateTimeKind.DateTime: | ||
| case DateTimeKind.TimeOnly: | ||
| dateFormatter = tableDataContext.useUserFormatting!.dateTime; | ||
| break; | ||
| case DateTimeKind.DateOnly: | ||
| dateFormatter = tableDataContext.useUserFormatting!.dateOnly; | ||
| break; | ||
| } | ||
| const date = new Date(displayValue); | ||
| value = dateFormatter.format(date); | ||
|
|
||
| return ( | ||
| <div className={s(style, { dateFormatter: true })}> | ||
| <div className={s(style, { dateFormatterValue: true })}>{value}</div> | ||
| </div> | ||
| ); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /* | ||
| * CloudBeaver - Cloud Database Manager | ||
| * Copyright (C) 2020-2024 DBeaver Corp and others | ||
| * | ||
| * Licensed under the Apache License, Version 2.0. | ||
| * you may not use this file except in compliance with the License. | ||
| */ | ||
|
|
||
| .numberFormatter { | ||
| display: flex; | ||
| align-items: center; | ||
| overflow: hidden; | ||
| } | ||
|
|
||
| .numberFormatterValue { | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| } | ||
Wroud marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /* | ||
| * CloudBeaver - Cloud Database Manager | ||
| * Copyright (C) 2020-2025 DBeaver Corp and others | ||
| * | ||
| * Licensed under the Apache License, Version 2.0. | ||
| * you may not use this file except in compliance with the License. | ||
| */ | ||
| import { observer } from 'mobx-react-lite'; | ||
| import { useContext } from 'react'; | ||
|
|
||
| import { getComputed, s, useS } from '@cloudbeaver/core-blocks'; | ||
| import { NullFormatter as GridNullFormatter } from '@cloudbeaver/plugin-data-grid'; | ||
|
|
||
| import { CellContext } from '../../CellRenderer/CellContext.js'; | ||
| import { TableDataContext } from '../../TableDataContext.js'; | ||
| import type { ICellFormatterProps } from '../ICellFormatterProps.js'; | ||
|
|
||
| import styles from './NumberFormatter.module.css'; | ||
|
|
||
| export const NumberFormatter = observer<ICellFormatterProps>(function NumberFormatter() { | ||
| const tableDataContext = useContext(TableDataContext); | ||
| const cellContext = useContext(CellContext); | ||
| const style = useS(styles); | ||
|
|
||
| if (!cellContext.cell) { | ||
| return null; | ||
| } | ||
|
|
||
| const formatter = tableDataContext.format; | ||
| const valueHolder = getComputed(() => formatter.get(cellContext.cell!)); | ||
| const nullValue = getComputed(() => formatter.isNull(valueHolder)); | ||
| const displayValue = getComputed(() => formatter.getDisplayString(valueHolder)); | ||
|
|
||
| if (nullValue) { | ||
| return <GridNullFormatter />; | ||
| } | ||
|
|
||
| let value = displayValue; | ||
|
|
||
| if (tableDataContext.useUserFormatting) { | ||
| const numberValue = Number(displayValue); | ||
|
|
||
| if (!isNaN(numberValue) && displayValue.trim() !== '') { | ||
| value = tableDataContext.useUserFormatting.number.format(numberValue); | ||
| } | ||
| } | ||
|
|
||
| return ( | ||
| <div className={s(style, { numberFormatter: true })}> | ||
| <div className={s(style, { numberFormatterValue: true })}>{value}</div> | ||
| </div> | ||
| ); | ||
| }); | ||
sergeyteleshev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| /* | ||
| * CloudBeaver - Cloud Database Manager | ||
| * Copyright (C) 2020-2025 DBeaver Corp and others | ||
| * | ||
| * Licensed under the Apache License, Version 2.0. | ||
| * you may not use this file except in compliance with the License. | ||
| */ | ||
| import { describe, expect, test } from 'vitest'; | ||
|
|
||
| import { DateTimeKind } from '../TableDataContext.js'; | ||
| import { detectDateTimeKind } from './detectDateTimeKind.js'; | ||
|
|
||
| describe('detectDateTimeKind', () => { | ||
| test('should detect date-only format (YYYY-MM-DD)', () => { | ||
| expect(detectDateTimeKind('2025-12-29')).toBe(DateTimeKind.DateOnly); | ||
| expect(detectDateTimeKind('2024-01-01')).toBe(DateTimeKind.DateOnly); | ||
| expect(detectDateTimeKind('1999-12-31')).toBe(DateTimeKind.DateOnly); | ||
| }); | ||
|
|
||
| test('should detect time-only format (HH:MM:SS)', () => { | ||
| expect(detectDateTimeKind('14:30:00')).toBe(DateTimeKind.TimeOnly); | ||
| expect(detectDateTimeKind('00:00:00')).toBe(DateTimeKind.TimeOnly); | ||
| expect(detectDateTimeKind('23:59:59')).toBe(DateTimeKind.TimeOnly); | ||
| expect(detectDateTimeKind('14:30:00.123')).toBe(DateTimeKind.TimeOnly); | ||
| }); | ||
|
|
||
| test('should detect datetime format', () => { | ||
| expect(detectDateTimeKind('2025-12-29 14:30:00')).toBe(DateTimeKind.DateTime); | ||
| expect(detectDateTimeKind('2024-01-01 00:00:00')).toBe(DateTimeKind.DateTime); | ||
| expect(detectDateTimeKind('2025-12-29T14:30:00')).toBe(DateTimeKind.DateTime); | ||
| expect(detectDateTimeKind('2025-12-29T14:30:00.123Z')).toBe(DateTimeKind.DateTime); | ||
| }); | ||
|
|
||
| test('should default to DateTime for unrecognized formats', () => { | ||
| expect(detectDateTimeKind('')).toBe(DateTimeKind.DateTime); | ||
| expect(detectDateTimeKind('invalid')).toBe(DateTimeKind.DateTime); | ||
| expect(detectDateTimeKind('2025/12/29')).toBe(DateTimeKind.DateTime); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| /* | ||
| * CloudBeaver - Cloud Database Manager | ||
| * Copyright (C) 2020-2025 DBeaver Corp and others | ||
| * | ||
| * Licensed under the Apache License, Version 2.0. | ||
| * you may not use this file except in compliance with the License. | ||
| */ | ||
|
|
||
| import { DateTimeKind } from '../TableDataContext.js'; | ||
|
|
||
| export function detectDateTimeKind(displayValue: string): DateTimeKind { | ||
| if (/^\d{4}-\d{2}-\d{2}$/.test(displayValue)) { | ||
| return DateTimeKind.DateOnly; | ||
| } | ||
|
|
||
| if (/^\d{2}:\d{2}:\d{2}/.test(displayValue) && !displayValue.includes('-')) { | ||
Wroud marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return DateTimeKind.TimeOnly; | ||
| } | ||
|
|
||
| return DateTimeKind.DateTime; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,15 +28,17 @@ import { | |
| type IGridDataKey, | ||
| } from '@cloudbeaver/plugin-data-viewer'; | ||
|
|
||
| import type { IColumnInfo, ITableData } from './TableDataContext.js'; | ||
| import { type IColumnInfo, type ITableData, type IDataGridFormatters, DateTimeKind } from './TableDataContext.js'; | ||
| import { useService } from '@cloudbeaver/core-di'; | ||
| import { DataGridSettingsService } from '../DataGridSettingsService.js'; | ||
| import { DataGridSettingsService, NO_FORMAT, OS_FORMAT } from '../DataGridSettingsService.js'; | ||
| import type { SqlResultColumn } from '@cloudbeaver/core-sdk'; | ||
| import { GridConditionalFormattingAction } from '@cloudbeaver/plugin-data-viewer-conditional-formatting'; | ||
| import { detectDateTimeKind } from './helpers/detectDateTimeKind.js'; | ||
|
|
||
| interface ITableDataPrivate extends ITableData { | ||
| dataGridSettingsService: DataGridSettingsService; | ||
| gridDIVElement: React.RefObject<HTMLDivElement | null>; | ||
| extendedDateKinds: Map<number, DateTimeKind>; | ||
Wroud marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| export function useTableData( | ||
|
|
@@ -83,6 +85,33 @@ export function useTableData( | |
| // TODO: fix column abstraction | ||
| return Boolean(this.data?.columns?.some(column => (column as SqlResultColumn).description)); | ||
| }, | ||
| get useUserFormatting(): IDataGridFormatters | null { | ||
| const setting = this.dataGridSettingsService.useUserFormatting; | ||
|
|
||
| if (setting === NO_FORMAT) { | ||
| return null; | ||
| } | ||
|
|
||
| const locale = setting === OS_FORMAT ? new Intl.DateTimeFormat().resolvedOptions().locale : setting; | ||
Wroud marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return { | ||
| locale, | ||
| dateTime: new Intl.DateTimeFormat(locale, { | ||
| year: 'numeric', | ||
| month: 'numeric', | ||
| day: 'numeric', | ||
| hour: 'numeric', | ||
| minute: 'numeric', | ||
| second: 'numeric', | ||
| }), | ||
| dateOnly: new Intl.DateTimeFormat(locale, { | ||
| year: 'numeric', | ||
| month: 'numeric', | ||
| day: 'numeric', | ||
| timeZone: 'UTC', | ||
| }), | ||
| number: new Intl.NumberFormat(locale), | ||
| }; | ||
| }, | ||
| getRow(rowIndex) { | ||
| return this.rows[rowIndex]; | ||
| }, | ||
|
|
@@ -140,19 +169,42 @@ export function useTableData( | |
|
|
||
| return model.isReadonly(resultIndex) || (this.format.isReadOnly(key) && this.editor?.getElementState(key) !== DatabaseEditChangeType.add); | ||
| }, | ||
| getExtendedDateKind(columnKey: IGridColumnKey): DateTimeKind { | ||
| if (this.extendedDateKinds.has(columnKey.index)) { | ||
| return this.extendedDateKinds.get(columnKey.index) as DateTimeKind; | ||
| } | ||
|
||
|
|
||
| for (const row of this.rows) { | ||
| const cellKey = { column: columnKey, row }; | ||
| const holder = this.getCellHolder(cellKey); | ||
|
|
||
| if (!this.format.isNull(holder)) { | ||
| const displayValue = this.format.getDisplayString(holder); | ||
| const kind = detectDateTimeKind(displayValue); | ||
| this.extendedDateKinds.set(columnKey.index, kind); | ||
| return kind; | ||
| } | ||
| } | ||
|
|
||
| const defaultKind = DateTimeKind.DateTime; | ||
| this.extendedDateKinds.set(columnKey.index, defaultKind); | ||
| return defaultKind; | ||
| }, | ||
| }), | ||
| { | ||
| columns: computed, | ||
| rows: computed, | ||
| columnKeys: computed, | ||
| hasDescription: computed, | ||
| useUserFormatting: computed, | ||
| formatting: observable.ref, | ||
| format: observable.ref, | ||
| dataContent: observable.ref, | ||
| data: observable.ref, | ||
| editor: observable.ref, | ||
| view: observable.ref, | ||
| gridDIVElement: observable.ref, | ||
| extendedDateKinds: observable, | ||
| }, | ||
| { | ||
| formatting, | ||
|
|
@@ -163,6 +215,7 @@ export function useTableData( | |
| view, | ||
| gridDIVElement, | ||
| dataGridSettingsService, | ||
| extendedDateKinds: new Map(), | ||
| }, | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.