diff --git a/src/ui/dataTable.less b/src/ui/dataTable.less new file mode 100644 index 00000000..f2469320 --- /dev/null +++ b/src/ui/dataTable.less @@ -0,0 +1,67 @@ +@import "./theme.less"; + +.DataTable > * { + border: solid 1px @border-color-base; + background-color: lighten(@component-background, 3%); + + & * { + scrollbar-color: @table-scrollbar-thumb-color @table-scrollbar-color; + } + + & ::-webkit-scrollbar { + background-color: @table-scrollbar-color; + } + + & ::-webkit-scrollbar-track { + background-color: @table-scrollbar-color; + } + + & ::-webkit-scrollbar-thumb { + background-color: @table-scrollbar-thumb-color; + } + + & ::-webkit-scrollbar-corner { + background-color: @table-scrollbar-color; + } +} + +.DataTable-header { + background-color: lighten(@component-background, 12%); + font-weight: bold; + + & .DataTable-cell { + border-right: solid 1px @border-color-base; + } +} + +.DataTable-cell { + display: flex; + align-items: center; + box-sizing: border-box; + padding: 0 5px; + border-bottom: solid 1px @border-color-base; + border-right: solid 1px darken(@border-color-base, 11%); + + & > span { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &.last-in-row { + border-right: solid 1px @border-color-base; + } + + &.number { + justify-content: flex-end; + } + + &.footer-cell { + font-weight: bold; + } +} + +.DataTable-no-result { + margin: 20px; + color: @text-color-secondary; +} \ No newline at end of file diff --git a/src/ui/dataTable.tsx b/src/ui/dataTable.tsx new file mode 100644 index 00000000..5a8debb2 --- /dev/null +++ b/src/ui/dataTable.tsx @@ -0,0 +1,99 @@ +import React from "react"; +import { Index, MultiGrid, GridCellRenderer } from "react-virtualized"; +import "./dataTable.less"; + +export type Column = { + name: string, + width: number, + cellValue: (record: T) => string, + tooltip?: (record: T) => string, + footerValue?: string, + footerTooltip?: string, + className?: string, +} + +/** + * A table with a fixed header. Optionally has fixed columns and a footer. + * TODO: no-content message. + */ +export class DataTable extends React.Component<{ + width: number, + height: number, + rowCount: number, + columns: Array>, + fixedColumnCount?: number, + record: (index: Index) => T, + footer?: boolean, +}> { + render() { + return ( +
+ +
+ ); + } + + private columnWidth = ({ index }: Index): number => { + return this.props.columns[index].width; + } + + private cellRenderer: GridCellRenderer = ({ columnIndex, rowIndex, style }) => { + const column = this.props.columns[columnIndex]; + let text: string; + let title: string | undefined; + const classes = ['DataTable-cell']; + + if (columnIndex === this.props.columns.length - 1) { + classes.push('last-in-row'); + } + + if (rowIndex === 0) { + // Header row + text = title = column.name; + } else { + // Record or footer row + if (column.className) { + classes.push(column.className); + } + + if (this.props.footer && rowIndex === 1 + this.props.rowCount) { + // Footer row + classes.push('footer-cell'); + text = column.footerValue == null ? '' : column.footerValue; + title = column.footerTooltip == null ? '' : column.footerTooltip; + } else { + // Record row + const result = this.props.record({ index: rowIndex - 1 }); + + text = column.cellValue(result); + + if (column.tooltip) { + title = column.tooltip(result); + } + } + } + + return ( +
+ {text} +
+ ); + } +} diff --git a/src/ui/hunt-optimizer/MethodsComponent.tsx b/src/ui/hunt-optimizer/MethodsComponent.tsx index ec22062c..723e6f82 100644 --- a/src/ui/hunt-optimizer/MethodsComponent.tsx +++ b/src/ui/hunt-optimizer/MethodsComponent.tsx @@ -1,23 +1,17 @@ import { observer } from "mobx-react"; import React from "react"; -import { AutoSizer, GridCellRenderer, MultiGrid, Index } from "react-virtualized"; +import { AutoSizer, Index } from "react-virtualized"; import { HuntMethod } from "../../domain"; import { EnemyNpcTypes } from "../../domain/NpcType"; import { huntMethodStore } from "../../stores/HuntMethodStore"; import "./MethodsComponent.css"; - -type Column = { - name: string, - width: number, - cellValue: (method: HuntMethod) => string, - className?: string -} +import { DataTable, Column } from "../dataTable"; @observer export class MethodsComponent extends React.Component { - static columns: Array = (() => { + static columns: Array> = (() => { // Standard columns. - const columns: Column[] = [ + const columns: Column[] = [ { name: 'Method', width: 250, @@ -34,7 +28,7 @@ export class MethodsComponent extends React.Component { for (const enemy of EnemyNpcTypes) { columns.push({ name: enemy.name, - width: 50, + width: 75, cellValue: (method) => { const count = method.enemyCounts.get(enemy); return count == null ? '' : count.toString(); @@ -53,16 +47,13 @@ export class MethodsComponent extends React.Component {
{({ width, height }) => ( - width={width} height={height} - rowHeight={28} rowCount={methods.length} - fixedRowCount={1} - columnWidth={this.columnWidth} - columnCount={2 + EnemyNpcTypes.length} + columns={MethodsComponent.columns} fixedColumnCount={2} - cellRenderer={this.cellRenderer} + record={this.record} /> )} @@ -70,41 +61,7 @@ export class MethodsComponent extends React.Component { ); } - private columnWidth = ({ index }: Index): number => { - return MethodsComponent.columns[index].width; - } - - private cellRenderer: GridCellRenderer = ({ columnIndex, rowIndex, style }) => { - const column = MethodsComponent.columns[columnIndex]; - let text: string; - const classes = []; - - if (columnIndex === MethodsComponent.columns.length - 1) { - classes.push('last-in-row'); - } - - if (rowIndex === 0) { - // Header row - text = column.name; - } else { - // Method row - if (column.className) { - classes.push(column.className); - } - - const method = huntMethodStore.methods.current.value[rowIndex - 1]; - - text = column.cellValue(method); - } - - return ( -
- {text} -
- ); + private record = ({ index }: Index) => { + return huntMethodStore.methods.current.value[index]; } } \ No newline at end of file diff --git a/src/ui/hunt-optimizer/OptimizationResultComponent.less b/src/ui/hunt-optimizer/OptimizationResultComponent.less index fd85f47c..3098a9a4 100644 --- a/src/ui/hunt-optimizer/OptimizationResultComponent.less +++ b/src/ui/hunt-optimizer/OptimizationResultComponent.less @@ -7,59 +7,4 @@ .ho-OptimizationResultComponent-table { flex: 1; - overflow: hidden; - border: solid 1px @border-color-base; - background-color: lighten(@component-background, 3%); - - & * { - scrollbar-color: @table-scrollbar-thumb-color @table-scrollbar-color; - } - - & ::-webkit-scrollbar { - background-color: @table-scrollbar-color; - } - - & ::-webkit-scrollbar-track { - background-color: @table-scrollbar-color; - } - - & ::-webkit-scrollbar-thumb { - background-color: @table-scrollbar-thumb-color; - } - - & ::-webkit-scrollbar-corner { - background-color: @table-scrollbar-color; - } -} - -.ho-OptimizationResultComponent-table-header { - background-color: lighten(@component-background, 12%); - font-weight: bold; -} - -.ho-OptimizationResultComponent-cell { - display: flex; - align-items: center; - box-sizing: border-box; - padding: 0 5px; - border-bottom: solid 1px @border-color-base; -} - -.ho-OptimizationResultComponent-cell > span { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.ho-OptimizationResultComponent-cell.last-in-row { - border-right: solid 1px @border-color-base; -} - -.ho-OptimizationResultComponent-cell.number { - justify-content: flex-end; -} - -.ho-OptimizationResultComponent-no-result { - margin: 20px; - color: @text-color-secondary; } \ No newline at end of file diff --git a/src/ui/hunt-optimizer/OptimizationResultComponent.tsx b/src/ui/hunt-optimizer/OptimizationResultComponent.tsx index 01cad006..6781a13d 100644 --- a/src/ui/hunt-optimizer/OptimizationResultComponent.tsx +++ b/src/ui/hunt-optimizer/OptimizationResultComponent.tsx @@ -1,24 +1,15 @@ +import { computed } from "mobx"; import { observer } from "mobx-react"; import React from "react"; -import { AutoSizer, GridCellRenderer, MultiGrid, Index } from "react-virtualized"; +import { AutoSizer, Index } from "react-virtualized"; import { Item } from "../../domain"; import { huntOptimizerStore, OptimizationResult } from "../../stores/HuntOptimizerStore"; +import { Column, DataTable } from "../dataTable"; import "./OptimizationResultComponent.less"; -import { computed } from "mobx"; - -type Column = { - name: string, - width: number, - cellValue: (result: OptimizationResult) => string, - tooltip?: (result: OptimizationResult) => string, - total?: string, - totalTooltip?: string, - className?: string -} @observer export class OptimizationResultComponent extends React.Component { - @computed private get columns(): Column[] { + @computed private get columns(): Column[] { // Standard columns. const results = huntOptimizerStore.results; let totalRuns = 0; @@ -29,12 +20,12 @@ export class OptimizationResultComponent extends React.Component { totalTime += result.totalTime; } - const columns: Column[] = [ + const columns: Column[] = [ { name: 'Difficulty', width: 75, cellValue: (result) => result.difficulty, - total: 'Totals:', + footerValue: 'Totals:', }, { name: 'Method', @@ -59,8 +50,8 @@ export class OptimizationResultComponent extends React.Component { width: 60, cellValue: (result) => result.runs.toFixed(1), tooltip: (result) => result.runs.toString(), - total: totalRuns.toFixed(1), - totalTooltip: totalRuns.toString(), + footerValue: totalRuns.toFixed(1), + footerTooltip: totalRuns.toString(), className: 'number', }, { @@ -68,8 +59,8 @@ export class OptimizationResultComponent extends React.Component { width: 90, cellValue: (result) => result.totalTime.toFixed(1), tooltip: (result) => result.totalTime.toString(), - total: totalTime.toFixed(1), - totalTooltip: totalTime.toString(), + footerValue: totalTime.toFixed(1), + footerTooltip: totalTime.toString(), className: 'number', }, ]; @@ -101,8 +92,8 @@ export class OptimizationResultComponent extends React.Component { return count ? count.toString() : ''; }, className: 'number', - total: totalCount.toFixed(2), - totalTooltip: totalCount.toString() + footerValue: totalCount.toFixed(2), + footerTooltip: totalCount.toString() }); } @@ -112,10 +103,6 @@ export class OptimizationResultComponent extends React.Component { render() { // Make sure render is called when result changes. huntOptimizerStore.results.slice(0, 0); - // Always add a row for the header. Add a row for the totals only if we have results. - const rowCount = huntOptimizerStore.results.length - ? 2 + huntOptimizerStore.results.length - : 1; return (
@@ -123,18 +110,14 @@ export class OptimizationResultComponent extends React.Component {
{({ width, height }) => - 0} /> } @@ -143,54 +126,7 @@ export class OptimizationResultComponent extends React.Component { ); } - private columnWidth = ({ index }: Index): number => { - return this.columns[index].width; - } - - private cellRenderer: GridCellRenderer = ({ columnIndex, rowIndex, style }) => { - const column = this.columns[columnIndex]; - let text: string; - let title: string | undefined; - const classes = ['ho-OptimizationResultComponent-cell']; - - if (columnIndex === this.columns.length - 1) { - classes.push('last-in-row'); - } - - if (rowIndex === 0) { - // Header row - text = title = column.name; - } else { - // Method or totals row - if (column.className) { - classes.push(column.className); - } - - if (rowIndex === 1 + huntOptimizerStore.results.length) { - // Totals row - text = column.total == null ? '' : column.total; - title = column.totalTooltip == null ? '' : column.totalTooltip; - } else { - // Method row - const result = huntOptimizerStore.results[rowIndex - 1]; - - text = column.cellValue(result); - - if (column.tooltip) { - title = column.tooltip(result); - } - } - } - - return ( -
- {text} -
- ); + private record = ({ index }: Index): OptimizationResult => { + return huntOptimizerStore.results[index]; } }