diff --git a/packages/app-utils/src/components/AppBootstrap.tsx b/packages/app-utils/src/components/AppBootstrap.tsx index d0305d0125..3d7b1d0ed8 100644 --- a/packages/app-utils/src/components/AppBootstrap.tsx +++ b/packages/app-utils/src/components/AppBootstrap.tsx @@ -1,4 +1,6 @@ import React, { useCallback, useMemo, useState } from 'react'; +import { Provider } from 'react-redux'; +import { store } from '@deephaven/redux'; import '@deephaven/components/scss/BaseStyleSheet.scss'; import { ClientBootstrap } from '@deephaven/jsapi-bootstrap'; import { @@ -56,29 +58,34 @@ export function AppBootstrap({ }, []); useBroadcastLoginListener(onLogin, onLogout); return ( - - - - - - - - - - {children} - - - - - - - - - + + + + + + + + + + + {children} + + + + + + + + + + ); } diff --git a/packages/app-utils/src/components/ThemeBootstrap.tsx b/packages/app-utils/src/components/ThemeBootstrap.tsx index 9d683ad984..89e3807af8 100644 --- a/packages/app-utils/src/components/ThemeBootstrap.tsx +++ b/packages/app-utils/src/components/ThemeBootstrap.tsx @@ -4,6 +4,8 @@ import { MonacoThemeProvider } from '@deephaven/console'; import { ThemeProvider } from '@deephaven/components'; import { IrisGridThemeProvider } from '@deephaven/iris-grid'; import { getThemeDataFromPlugins, PluginsContext } from '@deephaven/plugin'; +import { getSettings } from '@deephaven/redux'; +import { useAppSelector } from '@deephaven/dashboard'; export interface ThemeBootstrapProps { children: React.ReactNode; @@ -21,11 +23,15 @@ export function ThemeBootstrap({ children }: ThemeBootstrapProps): JSX.Element { [pluginModules] ); + const settings = useAppSelector(getSettings); + return ( - {children} + + {children} + diff --git a/packages/app-utils/src/storage/LocalWorkspaceStorage.ts b/packages/app-utils/src/storage/LocalWorkspaceStorage.ts index 63d78a5f80..3a42ee1ae1 100644 --- a/packages/app-utils/src/storage/LocalWorkspaceStorage.ts +++ b/packages/app-utils/src/storage/LocalWorkspaceStorage.ts @@ -61,6 +61,7 @@ export class LocalWorkspaceStorage implements WorkspaceStorage { }, webgl: true, webglEditable: true, + gridDensity: 'regular' as const, }; const serverSettings = { defaultDateTimeFormat: serverConfigValues?.get('dateTimeFormat'), @@ -121,7 +122,9 @@ export class LocalWorkspaceStorage implements WorkspaceStorage { ), }; - const keys = Object.keys(serverSettings) as Array; + const keys = Object.keys(serverSettings) as Array< + keyof typeof serverSettings + >; for (let i = 0; i < keys.length; i += 1) { const key = keys[i]; if (serverSettings[key] !== undefined) { diff --git a/packages/code-studio/src/AppRoot.tsx b/packages/code-studio/src/AppRoot.tsx index 2da9605c64..b173017c38 100644 --- a/packages/code-studio/src/AppRoot.tsx +++ b/packages/code-studio/src/AppRoot.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import { Provider } from 'react-redux'; import { MonacoUtils } from '@deephaven/console'; -import { store } from '@deephaven/redux'; import { DownloadServiceWorkerUtils } from '@deephaven/iris-grid'; import MonacoWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'; import AppRouter from './main/AppRouter'; @@ -25,11 +23,7 @@ export function AppRoot(): JSX.Element { // @ts-ignore window['__react-beautiful-dnd-disable-dev-warnings'] = true; - return ( - - - - ); + return ; } export default AppRoot; diff --git a/packages/code-studio/src/index.tsx b/packages/code-studio/src/index.tsx index 5daf81ceb7..1e17b39045 100644 --- a/packages/code-studio/src/index.tsx +++ b/packages/code-studio/src/index.tsx @@ -1,8 +1,10 @@ import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; -import '@deephaven/components/scss/BaseStyleSheet.scss'; +import '@deephaven/components/scss/BaseStyleSheet.scss'; // Do NOT move any lower. This needs to be imported before any other styles +import { Provider } from 'react-redux'; import { LoadingOverlay, preloadTheme } from '@deephaven/components'; import { ApiBootstrap } from '@deephaven/jsapi-bootstrap'; +import { store } from '@deephaven/redux'; import logInit from './log/LogInit'; logInit(); @@ -59,13 +61,15 @@ async function getCorePlugins() { ReactDOM.render( }> - - - + + + + + , document.getElementById('root') diff --git a/packages/code-studio/src/settings/SettingsMenu.tsx b/packages/code-studio/src/settings/SettingsMenu.tsx index 93bf36a8ff..72567e42c2 100644 --- a/packages/code-studio/src/settings/SettingsMenu.tsx +++ b/packages/code-studio/src/settings/SettingsMenu.tsx @@ -16,8 +16,6 @@ import { CopyButton, GLOBAL_SHORTCUTS, Logo, - ThemeContext, - ThemePicker, Tooltip, } from '@deephaven/components'; import { ServerConfigValues, User } from '@deephaven/redux'; @@ -26,7 +24,6 @@ import { BROADCAST_LOGOUT_MESSAGE, makeMessage, } from '@deephaven/jsapi-utils'; -import { assertNotNull } from '@deephaven/utils'; import { PluginModuleMap } from '@deephaven/plugin'; import FormattingSectionContent from './FormattingSectionContent'; import LegalNotice from './LegalNotice'; @@ -40,6 +37,7 @@ import { getFormattedVersionInfo, } from './SettingsUtils'; import AdvancedSectionContent from './AdvancedSectionContent'; +import ThemeSectionContent from './ThemeSectionContent'; interface SettingsMenuProps { serverConfigValues: ServerConfigValues; @@ -258,33 +256,23 @@ export class SettingsMenu extends Component< - - {contextValue => { - assertNotNull(contextValue, 'ThemeContext value is null'); - - return contextValue.themes.length > 1 ? ( - - - Theme - - } - > - - - ) : null; - }} - + + + Theme + + } + > + + { + if ( + density !== 'regular' && + density !== 'compact' && + density !== 'spacious' + ) { + throw new Error(`Invalid grid density value: ${density}`); + } + dispatch(updateSettings({ gridDensity: density })); + }, + [dispatch] + ); + + const density = settings.gridDensity; + + assertNotNull(theme, 'ThemeContext value is null'); + + return ( + <> + + + Regular + Compact + Spacious + + + ); +} + +export default ThemeSectionContent; diff --git a/packages/code-studio/src/styleguide/Grids.tsx b/packages/code-studio/src/styleguide/Grids.tsx index 42dc1f461f..4ecb48f438 100644 --- a/packages/code-studio/src/styleguide/Grids.tsx +++ b/packages/code-studio/src/styleguide/Grids.tsx @@ -22,6 +22,12 @@ function Grids(): ReactElement { const [irisGridModel] = useState( new MockIrisGridTreeModel(dh, new MockTreeGridModel()) ); + const [irisGridCompactModel] = useState( + new MockIrisGridTreeModel(dh, new MockTreeGridModel()) + ); + const [irisGridSpaciousModel] = useState( + new MockIrisGridTreeModel(dh, new MockTreeGridModel()) + ); const [model] = useState(new MockGridModel()); const [theme] = useState>({ autoSelectRow: true, @@ -68,7 +74,15 @@ function Grids(): ReactElement {

Iris Grid

- + + +

Iris Grid Compact

+ + + +

Iris Grid Spacious

+ + diff --git a/packages/code-studio/src/styleguide/StyleGuide.tsx b/packages/code-studio/src/styleguide/StyleGuide.tsx index 336b8ab9c4..c2a24bfb31 100644 --- a/packages/code-studio/src/styleguide/StyleGuide.tsx +++ b/packages/code-studio/src/styleguide/StyleGuide.tsx @@ -74,8 +74,6 @@ function StyleGuide(): React.ReactElement { >

Deephaven UI Components

- - {/* {isIsolatedSection ? null : ( */} : null} - {/* )} */} - {/* {isIsolatedSection ? null : ( */} - {/* )} */} diff --git a/packages/embed-widget/src/index.tsx b/packages/embed-widget/src/index.tsx index 39fa8061b5..e4a93e8334 100644 --- a/packages/embed-widget/src/index.tsx +++ b/packages/embed-widget/src/index.tsx @@ -1,8 +1,6 @@ import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; -import { Provider } from 'react-redux'; -import { store } from '@deephaven/redux'; -import '@deephaven/components/scss/BaseStyleSheet.scss'; +import '@deephaven/components/scss/BaseStyleSheet.scss'; // Do NOT move any lower. This needs to be imported before any other styles import { LoadingOverlay, preloadTheme } from '@deephaven/components'; import { ApiBootstrap } from '@deephaven/jsapi-bootstrap'; import './index.scss'; @@ -56,9 +54,7 @@ ReactDOM.render( serverUrl={apiURL.origin} pluginsUrl={pluginsURL.href} > - - - + , diff --git a/packages/grid/src/GridMetricCalculator.ts b/packages/grid/src/GridMetricCalculator.ts index 59b5959beb..ec58635cb9 100644 --- a/packages/grid/src/GridMetricCalculator.ts +++ b/packages/grid/src/GridMetricCalculator.ts @@ -1844,6 +1844,14 @@ export class GridMetricCalculator { this.userColumnWidths = userColumnWidths; } + /** + * Resets all the calculated column widths + * Useful if the theme minimum column width changes + */ + resetCalculatedColumnWidths(): void { + this.calculatedColumnWidths = new Map(); + } + /** * Sets the width for the specified row * @param row The row model index to set @@ -1868,6 +1876,14 @@ export class GridMetricCalculator { this.userRowHeights = userRowHeights; this.calculatedRowHeights.delete(row); } + + /** + * Resets all the calculated row heights + * Useful if the theme row height changes + */ + resetCalculatedRowHeights(): void { + this.calculatedRowHeights = new Map(); + } } export default GridMetricCalculator; diff --git a/packages/grid/src/GridRenderer.ts b/packages/grid/src/GridRenderer.ts index 6db57a4804..429e2e29d1 100644 --- a/packages/grid/src/GridRenderer.ts +++ b/packages/grid/src/GridRenderer.ts @@ -1082,11 +1082,12 @@ export class GridRenderer { context.beginPath(); for (let i = 0; i < depth - depthDiff; i += 1) { const lineX = - columnX + - i * treeDepthIndent + - treeDepthIndent * 0.5 + - treeHorizontalPadding + - 0.5; + Math.floor( + columnX + + i * treeDepthIndent + + treeDepthIndent * 0.5 + + treeHorizontalPadding + ) + 0.5; // The 0.5 makes the line crisp https://stackoverflow.com/questions/9311428/draw-single-pixel-line-in-html5-canvas context.moveTo(lineX, rowY); context.lineTo(lineX, rowY + rowHeight); } @@ -1098,18 +1099,19 @@ export class GridRenderer { context.beginPath(); for (let i = depth - depthDiff; i < depth; i += 1) { const lineX = - columnX + - i * treeDepthIndent + - treeDepthIndent * 0.5 + - treeHorizontalPadding + - 0.5; + Math.floor( + columnX + + i * treeDepthIndent + + treeDepthIndent * 0.5 + + treeHorizontalPadding + ) + 0.5; context.moveTo(lineX, rowY); - context.lineTo(lineX, rowY + Math.ceil(rowHeight / 2)); + context.lineTo(lineX, rowY + Math.floor(rowHeight / 2)); // extra moveTo prevents halfpixel in corner - context.moveTo(lineX - 0.5, rowY + Math.ceil(rowHeight / 2) + 0.5); + context.moveTo(lineX - 0.5, rowY + Math.floor(rowHeight / 2) + 0.5); context.lineTo( lineX + treeDepthIndent - 0.5, - rowY + Math.ceil(rowHeight / 2) + 0.5 + rowY + Math.floor(rowHeight / 2) + 0.5 ); } context.stroke(); diff --git a/packages/iris-grid/src/IrisGrid.tsx b/packages/iris-grid/src/IrisGrid.tsx index e43b7a9220..4dfae04f68 100644 --- a/packages/iris-grid/src/IrisGrid.tsx +++ b/packages/iris-grid/src/IrisGrid.tsx @@ -352,6 +352,8 @@ export interface IrisGridProps { // Pass in a custom renderer to the grid for advanced use cases renderer?: IrisGridRenderer; + + density?: 'compact' | 'regular' | 'spacious'; } export interface IrisGridState { @@ -455,6 +457,9 @@ export interface IrisGridState { class IrisGrid extends Component { static contextType = IrisGridThemeContext; + // eslint-disable-next-line react/static-property-placement, react/sort-comp + declare context: React.ContextType; + static minDebounce = 150; static maxDebounce = 500; @@ -529,6 +534,8 @@ class IrisGrid extends Component { canDownloadCsv: true, frozenColumns: null, theme: null, + // Do not set a default density prop since we need to know if it overrides the global density setting + density: undefined, canToggleSearch: true, mouseHandlers: EMPTY_ARRAY, keyHandlers: EMPTY_ARRAY, @@ -1393,20 +1400,35 @@ class IrisGrid extends Component { contextTheme: IrisGridThemeType | null, theme: Partial | null, isEditable: boolean, - floatingRowCount: number + floatingRowCount: number, + density: 'compact' | 'regular' | 'spacious' ): IrisGridThemeType => { // If a theme is available via context, use that as the base theme. - // If iris-grid is standalone without a context, initialize a default theme. - const defaultTheme = contextTheme ?? createDefaultIrisGridTheme(); + // If iris-grid is standalone without a context, use the default theme. + const baseTheme = contextTheme ?? createDefaultIrisGridTheme(); // We only show the row footers when we have floating rows for aggregations const rowFooterWidth = floatingRowCount > 0 - ? theme?.rowFooterWidth ?? defaultTheme.rowFooterWidth + ? theme?.rowFooterWidth ?? baseTheme.rowFooterWidth : 0; + const { metricCalculator } = this.state; + if (metricCalculator != null) { + metricCalculator.resetCalculatedColumnWidths(); + metricCalculator.resetCalculatedRowHeights(); + } + + let densityTheme = {}; + if (density === 'compact') { + densityTheme = baseTheme.density.compact; + } else if (density === 'spacious') { + densityTheme = baseTheme.density.spacious; + } + return { - ...defaultTheme, + ...baseTheme, + ...densityTheme, ...theme, autoSelectRow: !isEditable, rowFooterWidth, @@ -1498,14 +1520,16 @@ class IrisGrid extends Component { return rowIndex != null ? modelRows.get(rowIndex) : null; } - getTheme(): Partial { - const { model, theme } = this.props; + getTheme(): IrisGridThemeType { + const { model, theme, density } = this.props; + const { theme: contextTheme, density: contextDensity } = this.context; return this.getCachedTheme( - this.context, + contextTheme, theme, (isEditableGridModel(model) && model.isEditable) ?? false, - model.floatingTopRowCount + model.floatingBottomRowCount + model.floatingTopRowCount + model.floatingBottomRowCount, + density ?? contextDensity ); } @@ -4231,6 +4255,7 @@ class IrisGrid extends Component { } const theme = this.getTheme(); + const { columnHeaderHeight: singleColumnHeaderHeight } = theme; const filter = this.getCachedFilter( customFilters, @@ -4812,7 +4837,13 @@ class IrisGrid extends Component { /> )} {!isMenuShown && ( -
+