From b89c3c9d231d240dbb32bdf481a182f1995c735d Mon Sep 17 00:00:00 2001 From: Vac1911 Date: Fri, 21 Jan 2022 16:42:12 -0500 Subject: [PATCH 01/15] improved implementation of customizing columns --- src/common/index.ts | 2 +- src/common/types.ts | 1 + src/ui/components/BoxScore.basketball.tsx | 4 +- src/ui/components/BoxScore.football.tsx | 23 +- src/ui/components/BoxScore.hockey.tsx | 15 +- src/ui/components/DataTable/Controls.tsx | 10 +- .../components/DataTable/CustomizeColumns.tsx | 247 +-- src/ui/components/DataTable/Header.tsx | 278 ++-- src/ui/components/DataTable/Row.tsx | 22 +- src/ui/components/DataTable/index.tsx | 332 ++-- .../DataTable/loadStateFromCache.ts | 21 +- src/ui/components/DataTable/updateSortBys.ts | 16 +- src/ui/util/TableConfig.ts | 95 ++ src/{common => ui/util/columns}/getCols.ts | 1464 ++++++++++++++--- src/ui/util/columns/getTemplate.ts | 12 + src/ui/util/columns/templates/Age.tsx | 4 + src/ui/util/columns/templates/AskingFor.tsx | 6 + src/ui/util/columns/templates/Attr.tsx | 8 + src/ui/util/columns/templates/College.tsx | 19 + src/ui/util/columns/templates/Contract.tsx | 6 + src/ui/util/columns/templates/Country.tsx | 20 + src/ui/util/columns/templates/DraftYear.tsx | 4 + src/ui/util/columns/templates/Exp.tsx | 4 + src/ui/util/columns/templates/Experience.tsx | 4 + src/ui/util/columns/templates/Height.tsx | 5 + .../util/columns/templates/InjuryLength.tsx | 4 + src/ui/util/columns/templates/InjuryType.tsx | 4 + src/ui/util/columns/templates/Mood.tsx | 6 + src/ui/util/columns/templates/MoodCurrent.tsx | 6 + src/ui/util/columns/templates/Name.tsx | 15 + src/ui/util/columns/templates/Negotiate.tsx | 14 + src/ui/util/columns/templates/Ovr.tsx | 14 + src/ui/util/columns/templates/Pick.tsx | 5 + src/ui/util/columns/templates/Pos.tsx | 4 + src/ui/util/columns/templates/Pot.tsx | 14 + src/ui/util/columns/templates/Projected.tsx | 6 + src/ui/util/columns/templates/Rating.tsx | 8 + src/ui/util/columns/templates/Stat.tsx | 11 + src/ui/util/columns/templates/Team.tsx | 11 + src/ui/util/columns/templates/Trade.tsx | 13 + src/ui/util/columns/templates/Weight.tsx | 5 + .../util/columns/templates/YearsWithTeam.tsx | 7 + src/ui/util/columns/templates/index.tsx | 27 + src/ui/util/index.ts | 2 +- src/ui/views/AllStarDraft/index.tsx | 2 +- src/ui/views/AllStarHistory.tsx | 2 +- src/ui/views/AwardRaces.tsx | 2 +- src/ui/views/AwardsRecords.tsx | 2 +- src/ui/views/Colleges.tsx | 2 +- src/ui/views/Dashboard.tsx | 25 +- src/ui/views/FreeAgents.tsx | 115 +- src/ui/views/PlayerRatings.tsx | 99 +- src/ui/views/PlayerStats.tsx | 128 +- src/ui/views/TradingBlock.tsx | 155 +- src/worker/api/index.ts | 5 + src/worker/db/connectMeta.ts | 5 + src/worker/views/freeAgents.ts | 45 +- src/worker/views/playerRatings.ts | 49 +- src/worker/views/playerStats.ts | 29 +- src/worker/views/tradingBlock.ts | 47 +- 60 files changed, 2511 insertions(+), 999 deletions(-) create mode 100644 src/ui/util/TableConfig.ts rename src/{common => ui/util/columns}/getCols.ts (70%) create mode 100644 src/ui/util/columns/getTemplate.ts create mode 100644 src/ui/util/columns/templates/Age.tsx create mode 100644 src/ui/util/columns/templates/AskingFor.tsx create mode 100644 src/ui/util/columns/templates/Attr.tsx create mode 100644 src/ui/util/columns/templates/College.tsx create mode 100644 src/ui/util/columns/templates/Contract.tsx create mode 100644 src/ui/util/columns/templates/Country.tsx create mode 100644 src/ui/util/columns/templates/DraftYear.tsx create mode 100644 src/ui/util/columns/templates/Exp.tsx create mode 100644 src/ui/util/columns/templates/Experience.tsx create mode 100644 src/ui/util/columns/templates/Height.tsx create mode 100644 src/ui/util/columns/templates/InjuryLength.tsx create mode 100644 src/ui/util/columns/templates/InjuryType.tsx create mode 100644 src/ui/util/columns/templates/Mood.tsx create mode 100644 src/ui/util/columns/templates/MoodCurrent.tsx create mode 100644 src/ui/util/columns/templates/Name.tsx create mode 100644 src/ui/util/columns/templates/Negotiate.tsx create mode 100644 src/ui/util/columns/templates/Ovr.tsx create mode 100644 src/ui/util/columns/templates/Pick.tsx create mode 100644 src/ui/util/columns/templates/Pos.tsx create mode 100644 src/ui/util/columns/templates/Pot.tsx create mode 100644 src/ui/util/columns/templates/Projected.tsx create mode 100644 src/ui/util/columns/templates/Rating.tsx create mode 100644 src/ui/util/columns/templates/Stat.tsx create mode 100644 src/ui/util/columns/templates/Team.tsx create mode 100644 src/ui/util/columns/templates/Trade.tsx create mode 100644 src/ui/util/columns/templates/Weight.tsx create mode 100644 src/ui/util/columns/templates/YearsWithTeam.tsx create mode 100644 src/ui/util/columns/templates/index.tsx diff --git a/src/common/index.ts b/src/common/index.ts index 717a821a3b..863baf464f 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -8,7 +8,7 @@ export { default as gameAttributeHasHistory } from "./gameAttributeHasHistory"; export { default as gameAttributesArrayToObject } from "./gameAttributesArrayToObject"; export { default as getAdjustedTicketPrice } from "./getAdjustedTicketPrice"; export { default as getDraftLotteryProbs } from "./getDraftLotteryProbs"; -export { default as getCols } from "./getCols"; +export { default as getCols } from "../ui/util/columns/getCols"; export { default as getPeriodName } from "./getPeriodName"; export { default as helpers } from "./helpers"; export { default as isSport } from "./isSport"; diff --git a/src/common/types.ts b/src/common/types.ts index 40015f15ac..5404b4121d 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1615,6 +1615,7 @@ export type UpdateEvents = ( | "account" | "allStarDunk" | "allStarThree" + | "customizeTable" | "firstRun" | "g.goatFormula" | "g.userTids" diff --git a/src/ui/components/BoxScore.basketball.tsx b/src/ui/components/BoxScore.basketball.tsx index f3ed2308dc..3636d2880d 100644 --- a/src/ui/components/BoxScore.basketball.tsx +++ b/src/ui/components/BoxScore.basketball.tsx @@ -21,13 +21,13 @@ const StatsTable = ({ }) => { const [sortBys, setSortBys] = useState([]); - const onClick = (event: MouseEvent, i: number) => { + const onClick = (event: MouseEvent, colKey: string) => { setSortBys(prevSortBys => { const newSortBys = updateSortBys({ cols, event, - i, + colKey, prevSortBys, }) ?? []; diff --git a/src/ui/components/BoxScore.football.tsx b/src/ui/components/BoxScore.football.tsx index 6892110038..84d2fe259d 100644 --- a/src/ui/components/BoxScore.football.tsx +++ b/src/ui/components/BoxScore.football.tsx @@ -38,7 +38,7 @@ export const StatsHeader = ({ sortable, }: { cols: Col[]; - onClick: (b: MouseEvent, a: number) => void; + onClick: (b: MouseEvent, a: string) => void; sortBys: SortBy[]; sortable: boolean; }) => { @@ -52,7 +52,7 @@ export const StatsHeader = ({ if (sortable) { className = "sorting"; for (const sortBy of sortBys) { - if (sortBy[0] === i) { + if (sortBy[0] === col.key) { className = sortBy[1] === "asc" ? "sorting_asc" : "sorting_desc"; break; } @@ -64,7 +64,7 @@ export const StatsHeader = ({ className={className} key={i} onClick={event => { - onClick(event, i); + onClick(event, col.key); }} title={desc} > @@ -83,7 +83,7 @@ export const sortByStats = ( ) => { return (a: any, b: any) => { for (const [index, order] of sortBys) { - const stat = stats[index]; + const stat = index.includes(":") ? index.split(":")[1] : index; const aValue = getValue?.(a, stat) ?? a.processed[stat]; const bValue = getValue?.(b, stat) ?? b.processed[stat]; @@ -114,20 +114,21 @@ const StatsTableIndividual = ({ const [sortBys, setSortBys] = useState(() => { return PLAYER_GAME_STATS[type].sortBy.map( - stat => [stats.indexOf(stat), "desc"] as SortBy, + stat => [`stat:${stat}`, "desc"] as SortBy, ); }); - const onClick = (event: MouseEvent, i: number) => { - setSortBys( - prevSortBys => + const onClick = (event: MouseEvent, colKey: string) => { + setSortBys(prevSortBys => { + return ( updateSortBys({ cols, event, - i, + colKey, prevSortBys, - }) ?? [], - ); + }) ?? [] + ); + }); }; const players = t.players diff --git a/src/ui/components/BoxScore.hockey.tsx b/src/ui/components/BoxScore.hockey.tsx index 4e91d5ea96..b0550b5e58 100644 --- a/src/ui/components/BoxScore.hockey.tsx +++ b/src/ui/components/BoxScore.hockey.tsx @@ -41,20 +41,21 @@ const StatsTable = ({ const [sortBys, setSortBys] = useState(() => { return PLAYER_GAME_STATS[type].sortBy.map( - stat => [stats.indexOf(stat), "desc"] as SortBy, + stat => [`stat:${stat}`, "desc"] as SortBy, ); }); - const onClick = (event: MouseEvent, i: number) => { - setSortBys( - prevSortBys => + const onClick = (event: MouseEvent, colKey: string) => { + setSortBys(prevSortBys => { + return ( updateSortBys({ cols, event, - i, + colKey, prevSortBys, - }) ?? [], - ); + }) ?? [] + ); + }); }; const players = t.players diff --git a/src/ui/components/DataTable/Controls.tsx b/src/ui/components/DataTable/Controls.tsx index 07d58ed981..0bf379d2bb 100644 --- a/src/ui/components/DataTable/Controls.tsx +++ b/src/ui/components/DataTable/Controls.tsx @@ -15,6 +15,7 @@ const style = { const Controls = ({ enableFilters, + enableCustomizeColumns, hideAllControls, name, onExportCSV, @@ -25,6 +26,7 @@ const Controls = ({ searchText, }: { enableFilters: boolean; + enableCustomizeColumns: boolean; hideAllControls?: boolean; name: string; onExportCSV: () => void; @@ -130,9 +132,11 @@ const Controls = ({ - - Customize Columns - + {enableCustomizeColumns ? ( + + Customize Columns + + ) : null} Download Spreadsheet diff --git a/src/ui/components/DataTable/CustomizeColumns.tsx b/src/ui/components/DataTable/CustomizeColumns.tsx index 927e150bd4..2b79942be8 100644 --- a/src/ui/components/DataTable/CustomizeColumns.tsx +++ b/src/ui/components/DataTable/CustomizeColumns.tsx @@ -1,134 +1,151 @@ -import type { Col } from "."; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Modal } from "react-bootstrap"; -import { SortableContainer, SortableElement } from "react-sortable-hoc"; -import classNames from "classnames"; +import { toWorker } from "../../util"; +import type { TableConfig } from "../../util/TableConfig"; +import { ColType, getAllCols } from "../../util/columns/getCols"; +import { difference, groupBy } from "lodash-es"; +import type { Col } from "./index"; -const Item = SortableElement( - ({ - col, - hidden, - onToggleHidden, - }: { - col?: Col; - hidden?: boolean; - onToggleHidden: () => void; - }) => { - let title; - if (col) { - title = col.title; - if (col.desc) { - title += ` (${col.desc})`; - } - if (title === "") { - title = "No Title"; - } - } else { - title = Not Currently Available; - } - - return ( -
- - -
- ); - }, -); - -const Container = SortableContainer( - ({ children, isDragged }: { children: any[]; isDragged: boolean }) => { - return ( -
    - {children} -
- ); - }, -); +export type ColConfig = Col & { + hidden: boolean; + cat: ColType; +}; const CustomizeColumns = ({ - colOrder, - cols, - hasSuperCols, onHide, - onReset, - onSortEnd, - onToggleHidden, + onSave, + config, show, }: { - colOrder: { - colIndex: number; - hidden?: boolean; - }[]; - cols: Col[]; - hasSuperCols: boolean; + config: TableConfig; onHide: () => void; - onReset: () => void; - onSortEnd: (arg: { oldIndex: number; newIndex: number }) => void; - onToggleHidden: (i: number) => () => void; + onSave: () => void; show: boolean; }) => { - const [isDragged, setIsDragged] = useState(false); + const initialColumns: ColConfig[] = getAllCols().map( + (c): ColConfig => ({ + ...c, + cat: c.cat || "Other", + hidden: !config.columns.some(col => col.key === c.key), + }), + ); + const [columns, setColumns] = useState(initialColumns); + + useEffect(() => { + const nextColumns = [...columns].map(c => ({ + ...c, + hidden: !config.columns.some(col => col.key === c.key), + })); + setColumns(nextColumns); + }, [config]); + + const onChange = (key: string) => () => { + const nextColumns = [...columns]; + const i = nextColumns.findIndex(c => c.key === key); + if (i !== -1) { + nextColumns[i] = { ...nextColumns[i] }; + nextColumns[i].hidden = !nextColumns[i].hidden; + setColumns(nextColumns); + } + }; + + const reset = () => setColumns(initialColumns); + + const exit = () => { + reset(); + onHide(); + }; + + const restore = async () => { + await toWorker("main", "updateColumns", { + columns: config.fallback, + key: config.tableName, + }); + onHide(); + onSave(); + }; + + const save = async () => { + const enabledColumns: string[] = columns + .filter(c => !c.hidden) + .map(c => c.key), + currentColumns: string[] = config.columns.map(c => c.key); + + // Find columns we need to remove, and columns we need to add + const removeColumns: string[] = difference(currentColumns, enabledColumns), + addColumns: string[] = difference(enabledColumns, currentColumns); + + // Apply removals and adding to currentColumns while trying to preserve the order of currentColumns + const nextColumns: string[] = currentColumns.filter( + c => !removeColumns.includes(c), + ); + nextColumns.push(...addColumns); + + await toWorker("main", "updateColumns", { + columns: nextColumns, + key: config.tableName, + }); + onHide(); + onSave(); + }; + + const colsGrouped: { [key: string]: ColConfig[] } = groupBy( + columns, + c => c.cat, + ); return ( - - Customize Columns + + + Customize Columns + -

- Click and drag to reorder columns, or use the checkboxes to show/hide - columns. -

- {hasSuperCols ? ( -

- This table has two header rows. That means you can enable/disable - columns, but not reorder them. -

- ) : null} - { - setIsDragged(true); - }} - onSortEnd={args => { - setIsDragged(false); - if (!hasSuperCols) { - onSortEnd(args); - } - }} - > - {colOrder.map(({ colIndex, hidden }, i) => { - const col = cols[colIndex]; - return ( - +
    + {Object.entries(colsGrouped).map(([group, cols]) => ( +
  • + {group} +
    + {cols.map((col, i) => ( +
    +
    + + +
    +
    + ))} +
    +
  • + ))} +
- - - +
diff --git a/src/ui/components/DataTable/Header.tsx b/src/ui/components/DataTable/Header.tsx index a38fb04384..3ee5258e31 100644 --- a/src/ui/components/DataTable/Header.tsx +++ b/src/ui/components/DataTable/Header.tsx @@ -1,35 +1,35 @@ import classNames from "classnames"; -import type { SyntheticEvent, MouseEvent } from "react"; -import type { Col, SortBy, SuperCol } from "."; +import PropTypes from "prop-types"; +import type { MouseEvent, SyntheticEvent } from "react"; +import { ReactNode, useCallback, useState } from "react"; +import type { Col, Filter, SortBy, SuperCol } from "."; +import { + SortableContainer, + SortableElement, + SortableHandle, +} from "react-sortable-hoc"; const FilterHeader = ({ - colOrder, cols, filters, handleFilterUpdate, }: { - colOrder: { - colIndex: number; - hidden?: boolean; - }[]; cols: Col[]; - filters: string[]; - handleFilterUpdate: (b: SyntheticEvent, a: number) => void; + filters: Filter[]; + handleFilterUpdate: (b: SyntheticEvent, a: string) => void; }) => { return ( - {colOrder.map(({ colIndex }) => { - const col = cols[colIndex]; - - const filter = filters[colIndex] ?? ""; + {cols.map((col, colIndex) => { + const filter = filters.find(f => col.key === f.col); return ( {col.noSearch ? null : ( handleFilterUpdate(event, colIndex)} + onChange={event => handleFilterUpdate(event, col.key ?? "")} type="text" - value={filter} + value={filter ? filter.value : ""} /> )} @@ -39,53 +39,30 @@ const FilterHeader = ({ ); }; +FilterHeader.propTypes = { + cols: PropTypes.arrayOf( + PropTypes.shape({ + title: PropTypes.string.isRequired, + }), + ).isRequired, + filters: PropTypes.arrayOf(PropTypes.object).isRequired, + handleFilterUpdate: PropTypes.func.isRequired, +}; + const SuperCols = ({ - colOrder, + cols, superCols, }: { - colOrder: { - colIndex: number; - }[]; + cols: Col[]; superCols: SuperCol[]; }) => { - const colIndexes = colOrder.map(x => x.colIndex); - const maxColIndex1 = Math.max(...colIndexes); - let maxColIndex2 = -1; - for (const superCol of superCols) { - maxColIndex2 += superCol.colspan; - } - const maxColIndex = Math.max(maxColIndex1, maxColIndex2); - - // Adjust colspan based on hidden columns from colOrder - const colspanAdjustments = superCols.map(() => 0); - let superColIndex = 0; - let currentSuperColCount = 0; - for (let i = 0; i <= maxColIndex; i++) { - const superCol = superCols[superColIndex]; - if (superCol) { - if (!colIndexes.includes(i)) { - colspanAdjustments[superColIndex] -= 1; - } - - currentSuperColCount += 1; - if (currentSuperColCount >= superCol.colspan) { - superColIndex += 1; - currentSuperColCount = 0; - } - } - } - return ( {superCols.map(({ colspan, desc, title }, i) => { - const adjustedColspan = colspan + colspanAdjustments[i]; - if (adjustedColspan <= 0) { - return null; - } return ( ); }; +const SortableColumnHandle = SortableHandle( + (props: { isDragged: boolean; selected: boolean; children: ReactNode }) => { + return ( + + {props.children} + + ); + }, +); +SortableColumnHandle.propTypes = { + isDragged: PropTypes.bool.isRequired, +}; + +const SortableColumn = SortableElement( + (props: { + isDragged: boolean; + selected: boolean; + col: Col; + sortBy: SortBy | undefined; + colIndex: number; + handleColClick: (b: MouseEvent, a: string) => void; + }) => { + return ( + +
+
{props.col.title}
+
{ + props.handleColClick(event, props.col.key); + }} + style={{ width: "19px" }} + className={classNames({ + sorting: !props.sortBy && !props.isDragged, + sorting_asc: props.sortBy && props.sortBy[1] === "asc", + sorting_desc: props.sortBy && props.sortBy[1] === "desc", + })} + /> +
+ + ); + }, +); +const SortableColumnHeader = SortableContainer( + (props: { + indexSelected: number | undefined; + isDragged: boolean; + cols: Col[]; + sortBys: SortBy[]; + handleColClick: (b: MouseEvent, a: string) => void; + }) => { + return ( + + {props.cols.map((col, index) => ( + sort[0] === col.key)} + /> + ))} + + ); + }, +); const Header = ({ - colOrder, cols, enableFilters, filters, handleColClick, + handleReorder, handleFilterUpdate, sortBys, superCols, }: { - colOrder: { - colIndex: number; - }[]; cols: Col[]; enableFilters: boolean; - filters: string[]; - handleColClick: (b: MouseEvent, a: number) => void; - handleFilterUpdate: (b: SyntheticEvent, a: number) => void; + filters: Filter[]; + handleColClick: (b: MouseEvent, a: string) => void; + handleReorder: (oldIndex: number, newIndex: number) => void; + handleFilterUpdate: (b: SyntheticEvent, a: string) => void; sortBys: SortBy[]; superCols?: SuperCol[]; }) => { - return ( - - {superCols ? ( - - ) : null} - - {colOrder.map(({ colIndex }) => { - const { - classNames: colClassNames, - desc, - sortSequence, - title, - width, - } = cols[colIndex]; + const [isDragged, setIsDragged] = useState(false); + const [indexSelected, setIndexSelected] = useState( + undefined, + ); - let className; - if (sortSequence && sortSequence.length === 0) { - className = null; - } else { - className = "sorting"; + const onSortStart = useCallback(({ index }) => { + setIsDragged(true); + setIndexSelected(index); + }, []); - for (const sortBy of sortBys) { - if (sortBy[0] === colIndex) { - className = - sortBy[1] === "asc" ? "sorting_asc" : "sorting_desc"; - break; - } - } - } + const onSortEnd = useCallback( + ({ oldIndex, newIndex }) => { + setIsDragged(false); + setIndexSelected(undefined); - return ( - { - handleColClick(event, colIndex); - }} - title={desc} - style={{ width }} - > - {title} - - ); - })} - + handleReorder(oldIndex, newIndex); + }, + [handleReorder], + ); + + return ( + + {superCols ? : null} + {enableFilters ? ( { const { clicked, toggleClicked } = useClickable(); return ( @@ -22,7 +22,9 @@ const Row = ({ })} onClick={clickable ? toggleClicked : undefined} > - {row.data.map((value = null, i) => { + {cols.map((col, i) => { + const key: string = col.key || ""; + const value = row.data[key] ?? null; // Value is either the value, or an object containing the value as a property const actualValue = value !== null && value.hasOwnProperty("value") ? value.value : value; @@ -80,4 +82,10 @@ const Row = ({ ); }; +Row.propTypes = { + row: PropTypes.shape({ + data: PropTypes.object.isRequired, + }).isRequired, +}; + export default Row; diff --git a/src/ui/components/DataTable/index.tsx b/src/ui/components/DataTable/index.tsx index cba739cb3a..cda794901c 100644 --- a/src/ui/components/DataTable/index.tsx +++ b/src/ui/components/DataTable/index.tsx @@ -1,16 +1,17 @@ +import type { Argument } from "classnames"; import classNames from "classnames"; import { csvFormatRows } from "d3-dsv"; import orderBy from "lodash-es/orderBy"; +import PropTypes from "prop-types"; import { - SyntheticEvent, MouseEvent, ReactNode, - useState, - useEffect, + SyntheticEvent, useCallback, + useEffect, + useState, } from "react"; import Controls from "./Controls"; -import CustomizeColumns from "./CustomizeColumns"; import Footer from "./Footer"; import Header from "./Header"; import Info from "./Info"; @@ -22,26 +23,36 @@ import getSearchVal from "./getSearchVal"; import getSortVal from "./getSortVal"; import loadStateFromCache from "./loadStateFromCache"; import ResponsiveTableWrapper from "../ResponsiveTableWrapper"; -import { downloadFile, helpers, safeLocalStorage } from "../../util"; +import { + downloadFile, + helpers, + realtimeUpdate, + safeLocalStorage, + toWorker, +} from "../../util"; import type { SortOrder, SortType } from "../../../common/types"; -import type { Argument } from "classnames"; -import { arrayMoveImmutable } from "array-move"; import type SettingsCache from "./SettingsCache"; import updateSortBys from "./updateSortBys"; +import { arrayMove } from "react-sortable-hoc"; +import CustomizeColumns from "./CustomizeColumns"; +import type { TableConfig } from "../../util/TableConfig"; -export type SortBy = [number, SortOrder]; +export type SortBy = [string, SortOrder]; export type Col = { + key: string; + title: string; classNames?: any; desc?: string; noSearch?: boolean; sortSequence?: SortOrder[]; sortType?: SortType; searchType?: SortType; - title: string; width?: string; }; +export type LegacyCol = Partial; + export type SuperCol = { colspan: number; desc?: string; @@ -49,6 +60,21 @@ export type SuperCol = { }; export type DataTableRow = { + key: number | string; + data: { + [key: string]: + | ReactNode + | { + classNames?: Argument; + value: ReactNode; + searchValue?: string; + sortValue?: string | number; + }; + }; + classNames?: Argument; +}; + +export type LegacyDataTableRow = { key: number | string; data: ( | ReactNode @@ -63,10 +89,12 @@ export type DataTableRow = { }; export type Props = { + bordered?: boolean; className?: string; clickable?: boolean; cols: Col[]; - defaultSort: SortBy; + config: TableConfig; + defaultSort?: SortBy; disableSettingsCache?: boolean; footer?: any[]; hideAllControls?: boolean; @@ -76,18 +104,30 @@ export type Props = { rankCol?: number; rows: DataTableRow[]; small?: boolean; + striped?: boolean; superCols?: SuperCol[]; addFilters?: (string | undefined)[]; }; +export type LegacyProps = Omit & { + cols: LegacyCol[]; +}; + +export type Filter = { + col: string; + value: string; +}; + export type State = { colOrder: { colIndex: number; hidden?: boolean; }[]; + cols: Col[]; + rows: DataTableRow[] | LegacyDataTableRow[]; currentPage: number; enableFilters: boolean; - filters: string[]; + filters: Filter[]; prevName: string; perPage: number; searchText: string; @@ -96,31 +136,67 @@ export type State = { settingsCache: SettingsCache; }; -const DataTable = ({ - className, - clickable = true, - cols, - defaultSort, - disableSettingsCache, - footer, - hideAllControls, - name, - nonfluid, - pagination, - rankCol, - rows, - small, - superCols, - addFilters, -}: Props) => { - const [state, setState] = useState(() => - loadStateFromCache({ +const DataTable = (props: Props | LegacyProps) => { + const { + bordered, + className, + clickable = true, + defaultSort, + disableSettingsCache, + footer, + hideAllControls, + name, + nonfluid, + pagination, + small, + striped, + superCols, + addFilters, + } = props; + + const enableCustomizeColumns: boolean = "config" in props && !superCols; + + // Convert LegacyCols to Cols for backwards compatability + const cols: Col[] = + "config" in props + ? props.cols + : props.cols.map((col, i) => ({ + title: "", + ...col, + key: `col${i + 1}`, + })); + + // Convert LegacyDataTableRows to DataTableRows for backwards compatability + const rows: DataTableRow[] = + props.rows.length && Array.isArray(props.rows[0].data) + ? props.rows.map( + (row): DataTableRow => ({ + ...row, + data: Array.isArray(row.data) + ? Object.fromEntries( + row.data.map((value, i) => [`col${i + 1}`, value]), + ) + : {}, + }), + ) + : props.rows; + + const [state, setState] = useState(() => ({ + ...loadStateFromCache({ cols, defaultSort, - disableSettingsCache, + disableSettingsCache: false, name, }), - ); + rows, + })); + + useEffect(() => { + if ("config" in props) { + setStatePartial({ cols }); + processedRows = processRows(); + } + }, [cols]); const setStatePartial = useCallback((newState: Partial) => { setState(state2 => ({ @@ -130,15 +206,20 @@ const DataTable = ({ }, []); const processRows = () => { - const filterFunctions = state.enableFilters - ? state.filters.map((filter, i) => - createFilterFunction( - filter, - cols[i] ? cols[i].sortType : undefined, - cols[i] ? cols[i].searchType : undefined, - ), - ) - : []; + const filterFunctions: [string, (value: any) => boolean][] = + state.enableFilters + ? state.filters.map(filter => { + const col = state.cols.find(f => filter.col === f.key); + return [ + filter.col, + createFilterFunction( + filter.value || "", + col?.sortType, + col?.searchType, + ), + ]; + }) + : []; const skipFiltering = state.searchText === "" && !state.enableFilters; const searchText = state.searchText.toLowerCase(); const rowsFiltered = skipFiltering @@ -148,12 +229,15 @@ const DataTable = ({ if (state.searchText !== "") { let found = false; - for (let i = 0; i < row.data.length; i++) { - if (cols[i].noSearch) { + for (const col of state.cols) { + if (col.noSearch) { continue; } - if (getSearchVal(row.data[i]).includes(searchText)) { + if ( + row.data[col.key ?? ""] && + getSearchVal(row.data[col.key ?? ""]).includes(searchText) + ) { found = true; break; } @@ -166,15 +250,13 @@ const DataTable = ({ // Filter if (state.enableFilters) { - for (let i = 0; i < row.data.length; i++) { - if (cols[i].noSearch) { + for (const [key, filterFunction] of filterFunctions) { + const col = state.cols.find(c => c.key === key); + if (!col || col.noSearch) { continue; } - if ( - filterFunctions[i] && - filterFunctions[i](row.data[i]) === false - ) { + if (!filterFunction(row.data[key])) { return false; } } @@ -186,36 +268,35 @@ const DataTable = ({ const rowsOrdered = orderBy( rowsFiltered, state.sortBys.map(sortBy => row => { - let i = sortBy[0]; - - if (typeof i !== "number" || i >= row.data.length || i >= cols.length) { - i = 0; - } - - return getSortVal(row.data[i], cols[i].sortType); + const key = sortBy[0]; + const col = state.cols.find(c => c.key === key); + return getSortVal(row.data[key], col?.sortType); }), state.sortBys.map(sortBy => sortBy[1]), ); - const colOrderFiltered = state.colOrder.filter( - ({ hidden, colIndex }) => !hidden && cols[colIndex], - ); + return rowsOrdered; + }; - return rowsOrdered.map((row, i) => { - return { - ...row, - data: colOrderFiltered.map(({ colIndex }) => - colIndex === rankCol ? i + 1 : row.data[colIndex], - ), - }; + const handleReorder = async (oldIndex: number, newIndex: number) => { + const nextCols = arrayMove(state.cols, oldIndex, newIndex); + setStatePartial({ + cols: nextCols, }); + if ("config" in props) { + await toWorker("main", "updateColumns", { + columns: nextCols.map(c => c.key), + key: props.config.tableName, + }); + // await realtimeUpdate(["customizeTable"]); + } }; - const handleColClick = (event: MouseEvent, i: number) => { + const handleColClick = (event: MouseEvent, colKey: string) => { const sortBys = updateSortBys({ cols, event, - i, + colKey, prevSortBys: state.sortBys, // eslint-disable-line react/no-access-state-in-setstate }); @@ -228,10 +309,12 @@ const DataTable = ({ const handleExportCSV = () => { const colOrderFiltered = state.colOrder.filter( - ({ hidden, colIndex }) => !hidden && cols[colIndex], + ({ hidden, colIndex }) => !hidden && state.cols[colIndex], ); - const columns = colOrderFiltered.map(({ colIndex }) => cols[colIndex]); - const colNames = columns.map(col => col.title); + const columns = colOrderFiltered.map( + ({ colIndex }) => state.cols[colIndex].title, + ); + const colNames = cols.map(col => col.title); const rows = processRows().map(row => row.data.map((val, i) => { const sortType = columns[i].sortType; @@ -282,11 +365,19 @@ const DataTable = ({ const handleFilterUpdate = ( event: SyntheticEvent, - i: number, + colKey: string, ) => { const filters = helpers.deepCopy(state.filters); // eslint-disable-line react/no-access-state-in-setstate + const filterIndex = filters.findIndex(f => colKey === f.col); + + if (filterIndex !== -1) + filters[filterIndex].value = event.currentTarget.value; + else + filters.push({ + col: colKey, + value: event.currentTarget.value, + }); - filters[i] = event.currentTarget.value; setStatePartial({ currentPage: 1, filters, @@ -323,7 +414,7 @@ const DataTable = ({ // If name changes, it means this is a whole new table and it has a different state (example: Player Stats switching between regular and advanced stats). // If colOrder does not match cols, need to run reconciliation code in loadStateFromCache (example: current vs past seasons in League Finances). - if (name !== state.prevName || cols.length > state.colOrder.length) { + if (name !== state.prevName || state.cols.length > state.colOrder.length) { setState( loadStateFromCache({ cols, @@ -384,59 +475,25 @@ const DataTable = ({ } const colOrderFiltered = state.colOrder.filter( - ({ hidden, colIndex }) => !hidden && cols[colIndex], + ({ hidden, colIndex }) => !hidden && state.cols[colIndex], ); return ( <> - { - setStatePartial({ - showSelectColumnsModal: false, - }); - }} - onReset={() => { - const newOrder = cols.map((col, i) => ({ - colIndex: i, - })); - setStatePartial({ - colOrder: newOrder, - }); - state.settingsCache.set("DataTableColOrder", newOrder); - }} - onSortEnd={({ oldIndex, newIndex }) => { - const newOrder = arrayMoveImmutable( - state.colOrder, - oldIndex, - newIndex, - ); - setStatePartial({ - colOrder: newOrder, - }); - state.settingsCache.set("DataTableColOrder", newOrder); - }} - onToggleHidden={(i: number) => () => { - const newOrder = [...state.colOrder]; - if (newOrder[i]) { - newOrder[i] = { - ...newOrder[i], - }; - if (newOrder[i].hidden) { - delete newOrder[i].hidden; - } else { - newOrder[i].hidden = true; - } + {"config" in props ? ( + { setStatePartial({ - colOrder: newOrder, + showSelectColumnsModal: false, }); - state.settingsCache.set("DataTableColOrder", newOrder); - } - }} - /> + }} + onSave={async () => { + await realtimeUpdate(["customizeTable"]); + }} + /> + ) : null}
{processedRows.map(row => ( - + ))}
@@ -512,4 +577,21 @@ const DataTable = ({ ); }; +DataTable.propTypes = { + bordered: PropTypes.bool, + className: PropTypes.string, + defaultSort: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + ).isRequired, + disableSettingsCache: PropTypes.bool, + footer: PropTypes.array, + name: PropTypes.string.isRequired, + nonfluid: PropTypes.bool, + hideAllControls: PropTypes.bool, + pagination: PropTypes.bool, + rows: PropTypes.array.isRequired, + small: PropTypes.bool, + superCols: PropTypes.array, +}; + export default DataTable; diff --git a/src/ui/components/DataTable/loadStateFromCache.ts b/src/ui/components/DataTable/loadStateFromCache.ts index c03716eee1..dc78054626 100644 --- a/src/ui/components/DataTable/loadStateFromCache.ts +++ b/src/ui/components/DataTable/loadStateFromCache.ts @@ -1,5 +1,5 @@ import { safeLocalStorage } from "../../util"; -import type { Props, State, SortBy } from "."; +import type { Props, State, SortBy, Filter } from "."; import SettingsCache from "./SettingsCache"; const loadStateFromCache = ({ @@ -24,19 +24,19 @@ const loadStateFromCache = ({ let sortBys: SortBy[]; if (sortBysFromStorage === undefined) { - sortBys = [defaultSort]; + sortBys = defaultSort ? [defaultSort] : []; } else { sortBys = sortBysFromStorage; } - // Don't let sortBy reference invalid col - sortBys = sortBys.filter(sortBy => sortBy[0] < cols.length); + // Don't let sortBy reference invalid + sortBys = sortBys.filter(sortBy => cols.find(col => col.key === sortBy[0])); if (sortBys.length === 0) { - sortBys = [defaultSort]; + sortBys = defaultSort ? [defaultSort] : []; } - const defaultFilters: string[] = cols.map(() => ""); + const defaultFilters: Filter[] = []; const filtersFromStorage = settingsCache.get("DataTableFilters"); let filters; @@ -47,11 +47,14 @@ const loadStateFromCache = ({ filters = filtersFromStorage; // Confirm valid filters - if (!Array.isArray(filters) || filters.length !== cols.length) { + if (!Array.isArray(filters)) { filters = defaultFilters; } else { for (const filter of filters) { - if (typeof filter !== "string") { + if ( + typeof filter.col !== "string" || + typeof filter.value !== "string" + ) { filters = defaultFilters; break; } @@ -81,6 +84,8 @@ const loadStateFromCache = ({ // If too many cols... who cares, will get filtered out return { + cols, + rows: [], colOrder, currentPage: 1, enableFilters: filters !== defaultFilters, diff --git a/src/ui/components/DataTable/updateSortBys.ts b/src/ui/components/DataTable/updateSortBys.ts index 23690b7783..96522d056b 100644 --- a/src/ui/components/DataTable/updateSortBys.ts +++ b/src/ui/components/DataTable/updateSortBys.ts @@ -5,18 +5,18 @@ import { helpers } from "../../util"; const updateSortBys = ({ cols, event, - i, + colKey, prevSortBys, }: { cols: Col[]; event: MouseEvent; - i: number; + colKey: string; prevSortBys: SortBy[]; }) => { - const col = cols[i]; + const col = cols.find(c => c.key === colKey); // Ignore click on unsortable column - if (col.sortSequence && col.sortSequence.length === 0) { + if (!col || (col.sortSequence && col.sortSequence.length === 0)) { return prevSortBys; } @@ -44,7 +44,7 @@ const updateSortBys = ({ // If this column is already in sortBys and shift is pressed, update if (event.shiftKey) { for (const sortBy of sortBys) { - if (sortBy[0] === i) { + if (sortBy[0] === colKey) { sortBy[1] = nextOrder(col, sortBy); found = true; break; @@ -53,20 +53,20 @@ const updateSortBys = ({ // If this column is not in sortBys and shift is pressed, append if (!found) { - sortBys.push([i, col.sortSequence ? col.sortSequence[0] : "asc"]); + sortBys.push([colKey, col.sortSequence ? col.sortSequence[0] : "asc"]); found = true; } } // If this column is the only one in sortBys, update order - if (!found && sortBys.length === 1 && sortBys[0][0] === i) { + if (!found && sortBys.length === 1 && sortBys[0][0] === colKey) { sortBys[0][1] = nextOrder(col, sortBys[0]); found = true; } // Otherwise, reset to sorting only by this column, default order if (!found) { - sortBys = [[i, col.sortSequence ? col.sortSequence[0] : "asc"]]; + sortBys = [[colKey, col.sortSequence ? col.sortSequence[0] : "asc"]]; } return sortBys; diff --git a/src/ui/util/TableConfig.ts b/src/ui/util/TableConfig.ts new file mode 100644 index 0000000000..7b4d40b2fa --- /dev/null +++ b/src/ui/util/TableConfig.ts @@ -0,0 +1,95 @@ +import { idb } from "../../worker/db"; +import getCols, { MetaCol } from "./columns/getCols"; +import { uniq } from "lodash-es"; +import { g } from "../../worker/util"; + +export class TableConfig { + get ratingsNeeded(): string[] { + return this._ratingsNeeded ?? []; + } + get statsNeeded(): string[] { + return this._statsNeeded ?? []; + } + get attrsNeeded(): string[] { + return this._attrsNeeded ?? []; + } + + public fallback: string[]; + public columns: MetaCol[]; + public tableName: string; + public vars: { [key: string]: any }; + + private _statsNeeded: string[] = []; + private _ratingsNeeded: string[] = []; + private _attrsNeeded: string[] = ["pid"]; + + constructor( + tableName: string, + fallback: string[], + columns: MetaCol[] = [], + vars: { [key: string]: any } = {}, + ) { + this.tableName = tableName; + this.fallback = fallback; + this.columns = columns; + this.vars = vars; + } + + addColumn(column: MetaCol, pos: number) { + const colIndex = this.columns.findIndex(c => c.key === column.key); + if (colIndex) { + Object.assign(this.columns[colIndex], column); + } else { + this.columns.splice(pos, 0, column); + } + } + + static unserialize(_config: TableConfig) { + return new TableConfig( + _config.tableName, + _config.fallback, + _config.columns, + _config.vars, + ); + } + + public async load() { + const colOptions: string[] | undefined = await idb.meta.get( + "tables", + this.tableName, + ); + this.columns = getCols(colOptions ?? this.fallback); + this._statsNeeded = uniq( + this.columns.reduce( + (needed: string[], c: MetaCol) => needed.concat(c.stats ?? []), + [], + ), + ); + this._ratingsNeeded = uniq( + this.columns.reduce( + (needed: string[], c: MetaCol) => needed.concat(c.ratings ?? []), + [], + ), + ); + this._attrsNeeded = uniq( + this.columns.reduce( + (needed: string[], c: MetaCol) => needed.concat(c.attrs ?? []), + [], + ), + ); + this.vars = { + userTid: g.get("userTid"), + godMode: g.get("godMode"), + spectator: g.get("spectator"), + phase: g.get("phase"), + challengeNoRatings: g.get("challengeNoRatings"), + challengeNoDraftPicks: g.get("challengeNoDraftPicks"), + challengeNoFreeAgents: g.get("challengeNoFreeAgents"), + challengeNoTrades: g.get("challengeNoTrades"), + salaryCapType: g.get("salaryCapType"), + salaryCap: g.get("salaryCap"), + maxContract: g.get("maxContract"), + minContract: g.get("minContract"), + }; + } +} diff --git a/src/common/getCols.ts b/src/ui/util/columns/getCols.ts similarity index 70% rename from src/common/getCols.ts rename to src/ui/util/columns/getCols.ts index 1269b711af..afc1d39333 100644 --- a/src/common/getCols.ts +++ b/src/ui/util/columns/getCols.ts @@ -1,9 +1,30 @@ -import type { Col } from "../ui/components/DataTable"; -import bySport from "./bySport"; -import isSport from "./isSport"; +import bySport from "../../../common/bySport"; +import isSport from "../../../common/isSport"; +import type { Player } from "../../../common/types"; +import type { Col } from "../../components/DataTable"; -type ColTemp = Omit & { +export type ColType = + | "General" + | "Position" + | "Rating" + | "Stat" + | "Other" + | null; + +export type MetaCol = Col & { + cat?: ColType; + ratings?: string[]; + stats?: string[]; + attrs?: string[]; + template: + | string + | ((p: Player, c: ColTemp, vars: object) => JSX.Element | string); + options?: { [key: string]: any }; +}; + +export type ColTemp = Omit & { title?: string; + template?: string; }; const gp = isSport("hockey") ? "GP" : "G"; @@ -13,1918 +34,3091 @@ const sportSpecificCols = bySport<{ }>({ basketball: { "rating:fg": { + cat: "Rating", desc: "Mid Range", + ratings: ["fg"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "2Pt", }, "rating:tp": { + cat: "Rating", desc: "Three Pointers", + ratings: ["tp"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "3Pt", }, "rating:oiq": { + cat: "Rating", desc: "Offensive IQ", + ratings: ["oiq"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "oIQ", }, "rating:dnk": { + cat: "Rating", desc: "Dunks/Layups", + ratings: ["dnk"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Dnk", }, "rating:drb": { + cat: "Rating", desc: "Dribbling", + ratings: ["drb"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Drb", }, "rating:ins": { + cat: "Rating", desc: "Inside Scoring", + ratings: ["ins"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Ins", }, "rating:jmp": { + cat: "Rating", desc: "Jumping", + ratings: ["jmp"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Jmp", }, "rating:ft": { + cat: "Rating", desc: "Free Throws", + ratings: ["ft"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "FT", }, "rating:pss": { + cat: "Rating", desc: "Passing", + ratings: ["pss"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Pss", }, "rating:reb": { + cat: "Rating", desc: "Rebounding", + ratings: ["reb"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Reb", }, "rating:diq": { + cat: "Rating", desc: "Defensive IQ", + ratings: ["diq"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "dIQ", }, "stat:2pp": { + cat: "Stat", desc: "Two Point Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["2pp"], + template: "Stat", title: "2P%", }, "stat:2p": { + cat: "Stat", desc: "Two Pointers Made", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["2p"], + template: "Stat", title: "2P", }, "stat:2pa": { + cat: "Stat", desc: "Two Pointers Attempted", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["2pa"], + template: "Stat", title: "2PA", }, "stat:pm": { + cat: "Stat", desc: "Plus/Minus", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pm"], + template: "Stat", title: "+/-", }, "stat:tpp": { + cat: "Stat", desc: "Three Point Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["tpp"], + template: "Stat", title: "3P%", }, "stat:tp": { + cat: "Stat", desc: "Three Pointers Made", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["tp"], + template: "Stat", title: "3P", }, "stat:tpa": { + cat: "Stat", desc: "Three Pointers Attempted", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["tpa"], + template: "Stat", title: "3PA", }, "stat:tpar": { + cat: "Stat", desc: "Three Point Attempt Rate (3PA / FGA)", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["tpar"], + template: "Stat", title: "3PAr", }, "stat:astp": { + cat: "Stat", desc: "Percentage of teammate field goals a player assisted while on the floor", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["astp"], + template: "Stat", title: "AST%", }, "stat:ast": { + cat: "Stat", desc: "Assists", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ast"], + template: "Stat", title: "AST", }, "stat:ba": { + cat: "Stat", desc: "Blocks Against", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ba"], + template: "Stat", title: "BA", }, "stat:blk": { + cat: "Stat", desc: "Blocks", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["blk"], + template: "Stat", title: "BLK", }, "stat:blkp": { + cat: "Stat", desc: "Percentage of opponent two-pointers blocked", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["blkp"], + template: "Stat", title: "BLK%", }, "stat:drb": { + cat: "Stat", desc: "Defensive Rebounds", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["drb"], + template: "Stat", title: "DRB", }, "stat:drbp": { + cat: "Stat", desc: "Percentage of available defensive rebounds grabbed", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["drbp"], + template: "Stat", title: "DRB%", }, "stat:drtg": { + cat: "Stat", desc: "Defensive Rating (points allowed per 100 possessions)", sortSequence: ["asc", "desc"], sortType: "number", + stats: ["drtg"], + template: "Stat", title: "DRtg", }, "stat:dws": { + cat: "Stat", desc: "Defensive Win Shares", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["dws"], + template: "Stat", title: "DWS", }, "stat:ewa": { + cat: "Stat", desc: "Estimated Wins Added", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ewa"], + template: "Stat", title: "EWA", }, "stat:efg": { + cat: "Stat", desc: "Effective Field Goal Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["efg"], + template: "Stat", title: "eFG%", }, "stat:fgp": { + cat: "Stat", desc: "Field Goal Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fgp"], + template: "Stat", title: "FG%", }, "stat:fg": { + cat: "Stat", desc: "Field Goals Made", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fg"], + template: "Stat", title: "FG", }, "stat:fga": { + cat: "Stat", desc: "Field Goals Attempted", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fga"], + template: "Stat", title: "FGA", }, "stat:ftp": { + cat: "Stat", desc: "Free Throw Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ftp"], + template: "Stat", title: "FT%", }, "stat:ft": { + cat: "Stat", desc: "Free Throws Made", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ft"], + template: "Stat", title: "FT", }, "stat:fta": { + cat: "Stat", desc: "Free Throws Attempted", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fta"], + template: "Stat", title: "FTA", }, "stat:ftpFga": { + cat: "Stat", desc: "Free Throws per Field Goal Attempted", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ftpFga"], + template: "Stat", title: "FTr", }, "stat:ftr": { + cat: "Stat", desc: "Free Throw Attempt Rate (FTA / FGA)", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ftr"], + template: "Stat", title: "FT/FGA", }, "stat:gmsc": { + cat: "Stat", desc: "Game Score", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["gmsc"], + template: "Stat", title: "GmSc", }, "stat:nrtg": { + cat: "Stat", desc: "Net Rating (point differential per 100 possessions)", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["nrtg"], + template: "Stat", title: "NRtg", }, "stat:orb": { + cat: "Stat", desc: "Offensive Rebounds", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["orb"], + template: "Stat", title: "ORB", }, "stat:orbp": { + cat: "Stat", desc: "Percentage of available offensive rebounds grabbed", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["orbp"], + template: "Stat", title: "ORB%", }, "stat:ortg": { + cat: "Stat", desc: "Offensive Rating (points produced/scored per 100 possessions)", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ortg"], + template: "Stat", title: "ORtg", }, "stat:ows": { + cat: "Stat", desc: "Offensive Win Shares", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ows"], + template: "Stat", title: "OWS", }, "stat:pace": { + cat: "Stat", desc: "Possessions Per Game", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pace"], + template: "Stat", title: "Pace", }, "stat:per": { + cat: "Stat", desc: "Player Efficiency Rating", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["per"], + template: "Stat", title: "PER", }, "stat:pf": { + cat: "Stat", desc: "Personal Fouls", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pf"], + template: "Stat", title: "PF", }, - "stat:pl": { - desc: "Pythagorean Losses (expected losses based on points scored and allowed)", - sortSequence: ["desc", "asc"], - sortType: "number", - title: "PL", - }, "stat:pts": { + cat: "Stat", desc: "Points", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pts"], + template: "Stat", title: "PTS", }, - "stat:pw": { - desc: "Pythagorean Wins (expected wins based on points scored and allowed)", - sortSequence: ["desc", "asc"], - sortType: "number", - title: "PW", - }, "stat:stl": { + cat: "Stat", desc: "Steals", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["stl"], + template: "Stat", title: "STL", }, "stat:stlp": { + cat: "Stat", desc: "Percentage of opponent possessions ending in steals", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["stlp"], + template: "Stat", title: "STL%", }, "stat:tovp": { + cat: "Stat", desc: "Turnovers per 100 plays", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["tovp"], + template: "Stat", title: "TOV%", }, "stat:trb": { + cat: "Stat", desc: "Total Rebounds", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["trb"], + template: "Stat", title: "TRB", }, "stat:trbp": { + cat: "Stat", desc: "Percentage of available rebounds grabbed", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["trbp"], + template: "Stat", title: "TRB%", }, "stat:tsp": { + cat: "Stat", desc: "True Shooting Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["tsp"], + template: "Stat", title: "TS%", }, "stat:tov": { + cat: "Stat", desc: "Turnovers", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["tov"], + template: "Stat", title: "TOV", }, "stat:usgp": { + cat: "Stat", desc: "Percentage of team plays used", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["usgp"], + template: "Stat", title: "USG%", }, "stat:ws": { + cat: "Stat", desc: "Win Shares", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ws"], + template: "Stat", title: "WS", }, - "stat:wsPerPlayer": { - desc: "Win Shares Per Player", - sortSequence: ["desc", "asc"], - sortType: "number", - title: "WS/Player", - }, "stat:ws48": { + cat: "Stat", desc: "Win Shares Per 48 Minutes", + options: { decimals: 3 }, sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ws48"], + template: "Stat", title: "WS/48", }, "stat:obpm": { + cat: "Stat", desc: "Offensive Box Plus-Minus", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["obpm"], + template: "Stat", title: "OBPM", }, "stat:dbpm": { + cat: "Stat", desc: "Defensive Box Plus-Minus", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["dbpm"], + template: "Stat", title: "DBPM", }, "stat:bpm": { + cat: "Stat", desc: "Box Plus-Minus", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["bpm"], + template: "Stat", title: "BPM", }, "stat:vorp": { + cat: "Stat", desc: "Value Over Replacement Player", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["vorp"], + template: "Stat", title: "VORP", }, "stat:fgAtRim": { + cat: "Stat", desc: "At Rim Made", sortSequence: ["desc", "asc"], sortType: "number", - title: "M", + stats: ["fgAtRim"], + template: "Stat", + title: "FG Rim", }, "stat:fgaAtRim": { + cat: "Stat", desc: "At Rim Attempted", sortSequence: ["desc", "asc"], sortType: "number", - title: "A", + stats: ["fgaAtRim"], + template: "Stat", + title: "FGA Rim", }, "stat:fgpAtRim": { + cat: "Stat", desc: "At Rim Percentage", sortSequence: ["desc", "asc"], sortType: "number", - title: "%", + stats: ["fgpAtRim"], + template: "Stat", + title: "FG% Rim", }, "stat:fgLowPost": { + cat: "Stat", desc: "Low Post Made", sortSequence: ["desc", "asc"], sortType: "number", - title: "M", + stats: ["fgLowPost"], + template: "Stat", + title: "FG Post", }, "stat:fgaLowPost": { + cat: "Stat", desc: "Low Post Attempted", sortSequence: ["desc", "asc"], sortType: "number", - title: "A", + stats: ["fgaLowPost"], + template: "Stat", + title: "FGA Post", }, "stat:fgpLowPost": { + cat: "Stat", desc: "Low Post Percentage", sortSequence: ["desc", "asc"], sortType: "number", - title: "%", + stats: ["fgpLowPost"], + template: "Stat", + title: "FG% Post", }, "stat:fgMidRange": { + cat: "Stat", desc: "Mid Range Made", sortSequence: ["desc", "asc"], sortType: "number", - title: "M", + stats: ["fgMidRange"], + template: "Stat", + title: "FG Mid", }, "stat:fgaMidRange": { + cat: "Stat", desc: "Mid Range Attempted", sortSequence: ["desc", "asc"], sortType: "number", - title: "A", + stats: ["fgaMidRange"], + template: "Stat", + title: "FGA Mid", }, "stat:fgpMidRange": { + cat: "Stat", desc: "Mid Range Percentage", sortSequence: ["desc", "asc"], sortType: "number", - title: "%", + stats: ["fgpMidRange"], + template: "Stat", + title: "FG% Mid", }, "stat:dd": { + cat: "Stat", desc: "Double Doubles", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["dd"], + template: "Stat", title: "DD", }, "stat:td": { + cat: "Stat", desc: "Triple Doubles", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["td"], + template: "Stat", title: "TD", }, "stat:qd": { + cat: "Stat", desc: "Quadruple Doubles", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["qd"], + template: "Stat", title: "QD", }, "stat:fxf": { + cat: "Stat", desc: "Five by Fives", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fxf"], + template: "Stat", title: "5x5", }, }, football: { "pos:QB": { + cat: "Position", desc: "Quarterback", sortType: "number", title: "QB", }, "pos:RB": { + cat: "Position", desc: "Running Back", sortType: "number", title: "RB", }, "pos:WR": { + cat: "Position", desc: "Wide Receiver", sortType: "number", title: "WR", }, "pos:TE": { + cat: "Position", desc: "Tight End", sortType: "number", title: "TE", }, "pos:OL": { + cat: "Position", desc: "Offensive Lineman", sortType: "number", title: "OL", }, "pos:DL": { + cat: "Position", desc: "Defensive Lineman", sortType: "number", title: "DL", }, "pos:LB": { + cat: "Position", desc: "Linebacker", sortType: "number", title: "LB", }, "pos:CB": { + cat: "Position", desc: "Cornerback", sortType: "number", title: "CB", }, "pos:S": { + cat: "Position", desc: "Safety", sortType: "number", title: "S", }, "pos:K": { + cat: "Position", desc: "Kicker", sortType: "number", title: "K", }, "pos:P": { + cat: "Position", desc: "Punter", sortType: "number", title: "P", }, "rating:thv": { + cat: "Rating", desc: "Throwing Vision", + ratings: ["thv"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "ThV", }, "rating:thp": { + cat: "Rating", desc: "Throwing Power", + ratings: ["thp"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "ThP", }, "rating:tha": { + cat: "Rating", desc: "Throwing Accuracy", + ratings: ["tha"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "ThA", }, "rating:bsc": { + cat: "Rating", desc: "Ball Security", + ratings: ["bsc"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "BSc", }, "rating:elu": { + cat: "Rating", desc: "Elusiveness", + ratings: ["elu"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Elu", }, "rating:rtr": { + cat: "Rating", desc: "Route Running", + ratings: ["rtr"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "RtR", }, "rating:hnd": { + cat: "Rating", desc: "Hands", + ratings: ["hnd"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Hnd", }, "rating:rbk": { + cat: "Rating", desc: "Run Blocking", + ratings: ["rbk"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "RBk", }, "rating:pbk": { + cat: "Rating", desc: "Pass Blocking", + ratings: ["pbk"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PBk", }, "rating:pcv": { + cat: "Rating", desc: "Pass Coverage", + ratings: ["pcv"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PCv", }, "rating:tck": { + cat: "Rating", desc: "Tackling", + ratings: ["tck"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Tck", }, "rating:prs": { + cat: "Rating", desc: "Pass Rushing", + ratings: ["prs"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PRs", }, "rating:rns": { + cat: "Rating", desc: "Run Stopping", + ratings: ["rns"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "RnS", }, "rating:kpw": { + cat: "Rating", desc: "Kicking Power", + ratings: ["kpw"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "KPw", }, "rating:kac": { + cat: "Rating", desc: "Kicking Accuracy", + ratings: ["kac"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "KAc", }, "rating:ppw": { + cat: "Rating", desc: "Punting Power", + ratings: ["ppw"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PPw", }, "rating:pac": { + cat: "Rating", desc: "Punting Accuracy", + ratings: ["pac"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PAc", }, "rating:ovrQB": { + cat: "Rating", desc: "Overall Rating (QB)", + ratings: ["ovrQB"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrQB", }, "rating:ovrRB": { + cat: "Rating", desc: "Overall Rating (RB)", + ratings: ["ovrRB"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrRB", }, "rating:ovrWR": { + cat: "Rating", desc: "Overall Rating (WR)", + ratings: ["ovrWR"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrWR", }, "rating:ovrTE": { + cat: "Rating", desc: "Overall Rating (TE)", + ratings: ["ovrTE"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrTE", }, "rating:ovrOL": { + cat: "Rating", desc: "Overall Rating (OL)", + ratings: ["ovrOL"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrOL", }, "rating:ovrDL": { + cat: "Rating", desc: "Overall Rating (DL)", + ratings: ["ovrDL"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrDL", }, "rating:ovrLB": { + cat: "Rating", desc: "Overall Rating (LB)", + ratings: ["ovrLB"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrLB", }, "rating:ovrCB": { + cat: "Rating", desc: "Overall Rating (CB)", + ratings: ["ovrCB"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrCB", }, "rating:ovrS": { + cat: "Rating", desc: "Overall Rating (S)", + ratings: ["ovrS"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrS", }, "rating:ovrK": { + cat: "Rating", desc: "Overall Rating (K)", + ratings: ["ovrK"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrK", }, "rating:ovrP": { + cat: "Rating", desc: "Overall Rating (P)", + ratings: ["ovrP"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrP", }, "rating:ovrKR": { + cat: "Rating", desc: "Overall Rating (KR)", + ratings: ["ovrKR"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrKR", }, "rating:ovrPR": { + cat: "Rating", desc: "Overall Rating (PR)", + ratings: ["ovrPR"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrPR", }, "rating:potQB": { + cat: "Rating", desc: "Potential Rating (QB)", + ratings: ["potQB"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotQB", }, "rating:potRB": { + cat: "Rating", desc: "Potential Rating (RB)", + ratings: ["potRB"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotRB", }, "rating:potWR": { + cat: "Rating", desc: "Potential Rating (WR)", + ratings: ["potWR"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotWR", }, "rating:potTE": { + cat: "Rating", desc: "Potential Rating (TE)", + ratings: ["potTE"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotTE", }, "rating:potOL": { + cat: "Rating", desc: "Potential Rating (OL)", + ratings: ["potOL"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotOL", }, "rating:potDL": { + cat: "Rating", desc: "Potential Rating (DL)", + ratings: ["potDL"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotDL", }, "rating:potLB": { + cat: "Rating", desc: "Potential Rating (LB)", + ratings: ["potLB"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotLB", }, "rating:potCB": { + cat: "Rating", desc: "Potential Rating (CB)", + ratings: ["potCB"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotCB", }, "rating:potS": { + cat: "Rating", desc: "Potential Rating (S)", + ratings: ["potS"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotS", }, "rating:potK": { + cat: "Rating", desc: "Potential Rating (K)", + ratings: ["potK"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotK", }, "rating:potP": { + cat: "Rating", desc: "Potential Rating (P)", + ratings: ["potP"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotP", }, "rating:potKR": { + cat: "Rating", desc: "Potential Rating (KR)", + ratings: ["potKR"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotKR", }, "rating:potPR": { + cat: "Rating", desc: "Potential Rating (PR)", + ratings: ["potPR"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotPR", }, "stat:fmb": { + cat: "Stat", desc: "Fumbles", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fmb"], + template: "Stat", title: "Fmb", }, "stat:fmbLost": { + cat: "Stat", desc: "Fumbles Lost", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fmbLost"], + template: "Stat", title: "FL", }, "stat:fp": { + cat: "Stat", desc: "Fantasy Points", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fp"], + template: "Stat", title: "FP", }, "stat:pssCmp": { + cat: "Stat", desc: "Completions", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssCmp"], + template: "Stat", title: "Cmp", }, "stat:pss": { + cat: "Stat", desc: "Passing Attempts", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pss"], + template: "Stat", title: "Att", }, "stat:pssYds": { + cat: "Stat", desc: "Passing Yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssYds"], + template: "Stat", title: "Yds", }, "stat:pssTD": { + cat: "Stat", desc: "Passing Touchdowns", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssTD"], + template: "Stat", title: "TD", }, "stat:pssInt": { + cat: "Stat", desc: "Interceptions", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssInt"], + template: "Stat", title: "Int", }, "stat:pssLng": { + cat: "Stat", desc: "Longest Pass", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssLng"], + template: "Stat", title: "Lng", }, "stat:pssSk": { + cat: "Stat", desc: "Times Sacked", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssSk"], + template: "Stat", title: "Sk", }, "stat:pssSkYds": { + cat: "Stat", desc: "Yards lost due to sacks", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssSkYds"], + template: "Stat", title: "Yds", }, "stat:rus": { + cat: "Stat", desc: "Rushing Attempts", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["rus"], + template: "Stat", title: "Rush", }, "stat:rusYds": { + cat: "Stat", desc: "Rushing Yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["rusYds"], + template: "Stat", title: "Yds", }, "stat:rusTD": { + cat: "Stat", desc: "Rushing Touchdowns", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["rusTD"], + template: "Stat", title: "TD", }, "stat:rusLng": { + cat: "Stat", desc: "Longest Run", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["rusLng"], + template: "Stat", title: "Lng", }, "stat:tgt": { + cat: "Stat", desc: "Targets", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["tgt"], + template: "Stat", title: "Tgt", }, "stat:rec": { + cat: "Stat", desc: "Receptions", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["rec"], + template: "Stat", title: "Rec", }, "stat:recYds": { + cat: "Stat", desc: "Receiving Yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["recYds"], + template: "Stat", title: "Yds", }, "stat:recTD": { + cat: "Stat", desc: "Receiving Touchdowns", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["recTD"], + template: "Stat", title: "TD", }, "stat:recLng": { + cat: "Stat", desc: "Longest Reception", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["recLng"], + template: "Stat", title: "Lng", }, "stat:pr": { + cat: "Stat", desc: "Punt Returns", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pr"], + template: "Stat", title: "PR", }, "stat:prYds": { + cat: "Stat", desc: "Punt Return Yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["prYds"], + template: "Stat", title: "Yds", }, "stat:prTD": { + cat: "Stat", desc: "Punts returned for touchdowns", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["prTD"], + template: "Stat", title: "TD", }, "stat:prLng": { + cat: "Stat", desc: "Longest Punt Return", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["prLng"], + template: "Stat", title: "Lng", }, "stat:kr": { + cat: "Stat", desc: "Kickoff Returns", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["kr"], + template: "Stat", title: "KR", }, "stat:krYds": { + cat: "Stat", + template: "Stat", + stats: ["krYds"], desc: "Kickoff Return Yards", sortSequence: ["desc", "asc"], sortType: "number", title: "Yds", }, "stat:krTD": { + cat: "Stat", desc: "Kickoffs returned for touchdowns", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["krTD"], + template: "Stat", title: "TD", }, "stat:krLng": { + cat: "Stat", desc: "Longest Kickoff Return", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["krLng"], + template: "Stat", title: "Lng", }, "stat:defInt": { + cat: "Stat", desc: "Interceptions", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defInt"], + template: "Stat", title: "Int", }, "stat:defIntYds": { + cat: "Stat", desc: "Yards interceptions were returned for", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defIntYds"], + template: "Stat", title: "Yds", }, "stat:defIntTD": { + cat: "Stat", desc: "Interceptions returned for touchdowns", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defIntTD"], + template: "Stat", title: "TD", }, "stat:defIntLng": { + cat: "Stat", desc: "Longest Interception Return", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defIntLng"], + template: "Stat", title: "Lng", }, "stat:defPssDef": { + cat: "Stat", desc: "Passes Defended", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defPssDef"], + template: "Stat", title: "PD", }, "stat:defFmbFrc": { + cat: "Stat", desc: "Forced Fumbles", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defFmbFrc"], + template: "Stat", title: "FF", }, "stat:defFmbRec": { + cat: "Stat", desc: "Fumbles Recovered", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defFmbRec"], + template: "Stat", title: "FR", }, "stat:defFmbYds": { + cat: "Stat", desc: "Yards fumbles were returned for", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defFmbYds"], + template: "Stat", title: "Yds", }, "stat:defFmbTD": { + cat: "Stat", desc: "Fumbles returned for touchdowns", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defFmbTD"], + template: "Stat", title: "TD", }, "stat:defFmbLng": { + cat: "Stat", desc: "Longest Fumble Return", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defFmbLng"], + template: "Stat", title: "Lng", }, "stat:defSk": { + cat: "Stat", desc: "Sacks", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defSk"], + template: "Stat", title: "Sk", }, "stat:defTckSolo": { + cat: "Stat", desc: "Solo Tackles", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defTckSolo"], + template: "Stat", title: "Solo", }, "stat:defTckAst": { + cat: "Stat", desc: "Assists On Tackles", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defTckAst"], + template: "Stat", title: "Ast", }, "stat:defTckLoss": { + cat: "Stat", desc: "Tackes For Loss", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defTckLoss"], + template: "Stat", title: "TFL", }, "stat:defSft": { + cat: "Stat", desc: "Safeties Scored", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defSft"], + template: "Stat", title: "Sfty", }, "stat:fg0": { + cat: "Stat", desc: "Field Goals Made, 19 yards and under", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fg0"], + template: "Stat", title: "FG10", }, "stat:fga0": { + cat: "Stat", desc: "Field Goals Attempted, 19 yards and under", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fga0"], + template: "Stat", title: "FGA10", }, "stat:fg20": { + cat: "Stat", desc: "Field Goals Made, 20-29 yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fg20"], + template: "Stat", title: "FG20", }, "stat:fga20": { + cat: "Stat", desc: "Field Goals Attempted, 20-29 yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fga20"], + template: "Stat", title: "FGA20", }, "stat:fg30": { + cat: "Stat", desc: "Field Goals Made, 30-39 yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fg30"], + template: "Stat", title: "FG30", }, "stat:fga30": { + cat: "Stat", desc: "Field Goals Attempted, 30-39 yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fga30"], + template: "Stat", title: "FGA30", }, "stat:fg40": { + cat: "Stat", desc: "Field Goals Made, 40-49 yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fg40"], + template: "Stat", title: "FG40", }, "stat:fga40": { + cat: "Stat", desc: "Field Goals Attempted, 40-49 yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fga40"], + template: "Stat", title: "FGA40", }, "stat:fg50": { + cat: "Stat", desc: "Field Goals Made, 50+ yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fg50"], + template: "Stat", title: "FG50", }, "stat:fga50": { + cat: "Stat", desc: "Field Goals Attempted, 50+ yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fga50"], + template: "Stat", title: "FGA50", }, "stat:fgLng": { + cat: "Stat", desc: "Longest Field Goal", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fgLng"], + template: "Stat", title: "Lng", }, "stat:xp": { + cat: "Stat", desc: "Extra Points Made", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["xp"], + template: "Stat", title: "XPM", }, "stat:xpa": { + cat: "Stat", desc: "Extra Points Attempted", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["xpa"], + template: "Stat", title: "XPA", }, "stat:pnt": { + cat: "Stat", desc: "Times Punted", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pnt"], + template: "Stat", title: "Pnt", }, "stat:pntYds": { + cat: "Stat", desc: "Total Punt Yardage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pntYds"], + template: "Stat", title: "Yds", }, "stat:pntLng": { + cat: "Stat", desc: "Longest Punt", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pntLng"], + template: "Stat", title: "Lng", }, "stat:pntBlk": { + cat: "Stat", desc: "Times Punts Blocked", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pntBlk"], + template: "Stat", title: "Blk", }, "stat:pen": { + cat: "Stat", desc: "Penalties", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pen"], + template: "Stat", title: "Pen", }, "stat:penYds": { + cat: "Stat", desc: "Penalty Yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["penYds"], + template: "Stat", title: "Yds", }, "stat:cmpPct": { + cat: "Stat", desc: "Completion Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["cmpPct"], + template: "Stat", title: "Pct", }, "stat:qbRat": { + cat: "Stat", desc: "Quarterback Rating", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["qbRat"], + template: "Stat", title: "QBRat", }, "stat:rusYdsPerAtt": { + cat: "Stat", desc: "Rushing Yards Per Attempt", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["rusYdsPerAtt"], + template: "Stat", title: "Y/A", }, "stat:recYdsPerAtt": { + cat: "Stat", desc: "Yards Per Catch", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["recYdsPerAtt"], + template: "Stat", title: "Y/A", }, "stat:fg": { + cat: "Stat", desc: "Field Goals Made", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fg"], + template: "Stat", title: "FGM", }, "stat:fga": { + cat: "Stat", desc: "Field Goals Attempted", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fga"], + template: "Stat", title: "FGA", }, "stat:fgPct": { + cat: "Stat", desc: "Field Goal Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fgPct"], + template: "Stat", title: "Pct", }, "stat:xpPct": { + cat: "Stat", desc: "Extra Point Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["xpPct"], + template: "Stat", title: "Pct", }, "stat:kickingPts": { + cat: "Stat", desc: "Kicking Points", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["kickingPts"], + template: "Stat", title: "Pts", }, "stat:pntYdsPerAtt": { + cat: "Stat", desc: "Yards Per Punt", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pntYdsPerAtt"], + template: "Stat", title: "Y/A", }, "stat:pntTB": { + cat: "Stat", desc: "Punt Touchbacks", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pntTB"], + template: "Stat", title: "TB", }, "stat:pntIn20": { + cat: "Stat", desc: "Punts Inside 20", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pntIn20"], + template: "Stat", title: "In20", }, "stat:krYdsPerAtt": { + cat: "Stat", desc: "Yards Per Kick Return", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["krYdsPerAtt"], + template: "Stat", title: "Y/A", }, "stat:prYdsPerAtt": { + cat: "Stat", desc: "Yards Per Punt Return", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["prYdsPerAtt"], + template: "Stat", title: "Y/A", }, "stat:defTck": { + cat: "Stat", desc: "Total Tackles", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["defTck"], + template: "Stat", title: "Tck", }, "stat:keyStats": { + cat: "Stat", desc: "Key Stats", sortSequence: ["desc", "asc"], sortType: "string", + stats: ["keyStats"], + template: "Stat", title: "Stats", }, "stat:pts": { + cat: "Stat", desc: "", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pts"], + template: "Stat", title: "Pts", }, "stat:yds": { + cat: "Stat", desc: "Offensive Yards", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["yds"], + template: "Stat", title: "Yds", }, "stat:ply": { + cat: "Stat", desc: "Plays", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ply"], + template: "Stat", title: "Ply", }, "stat:ydsPerPlay": { + cat: "Stat", desc: "Yards Per Play", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ydsPerPlay"], + template: "Stat", title: "Y/P", }, "stat:tov": { + cat: "Stat", desc: "Turnovers", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["tov"], + template: "Stat", title: "TO", }, "stat:drives": { + cat: "Stat", desc: "Number of Drives", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["drives"], + template: "Stat", title: "#Dr", }, "stat:drivesScoringPct": { + cat: "Stat", desc: "Percentage of drives ending in a score", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["drivesScoringPct"], + template: "Stat", title: "Sc%", }, "stat:drivesTurnoverPct": { + cat: "Stat", desc: "Percentage of drives ending in a turnover", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["drivesTurnoverPct"], + template: "Stat", title: "TO%", }, "stat:avgFieldPosition": { + cat: "Stat", desc: "Average Starting Field Position", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["avgFieldPosition"], + template: "Stat", title: "Start", }, "stat:timePerDrive": { + cat: "Stat", desc: "Time Per Drive (minutes)", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["timePerDrive"], + template: "Stat", title: "Tm/D", }, "stat:playsPerDrive": { + cat: "Stat", desc: "Number of Plays Per Drive", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["playsPerDrive"], + template: "Stat", title: "Ply/D", }, "stat:ydsPerDrive": { + cat: "Stat", desc: "Yards Per Drive", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ydsPerDrive"], + template: "Stat", title: "Y/D", }, "stat:ptsPerDrive": { + cat: "Stat", desc: "Points Per Drive", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ptsPerDrive"], + template: "Stat", title: "Pts/D", }, "stat:qbRec": { + cat: "Stat", desc: "Record as primary QB", sortSequence: ["desc", "asc"], sortType: "record", + stats: ["qbRec"], + template: "Stat", title: "QBRec", }, "stat:qbW": { + cat: "Stat", desc: "Wins as primary QB", sortSequence: ["desc", "asc"], sortType: "record", + stats: ["qbW"], + template: "Stat", title: "QBW", }, "stat:qbL": { + cat: "Stat", desc: "Losses as primary QB", sortSequence: ["desc", "asc"], sortType: "record", + stats: ["qbL"], + template: "Stat", title: "QBL", }, "stat:qbT": { + cat: "Stat", desc: "Ties as primary QB", sortSequence: ["desc", "asc"], sortType: "record", + stats: ["qbT"], + template: "Stat", title: "QBT", }, "stat:qbOTL": { + cat: "Stat", desc: "Overtime losses as primary QB", sortSequence: ["desc", "asc"], sortType: "record", + stats: ["qbOTL"], + template: "Stat", title: "QBOTL", }, "stat:pssTDPct": { + cat: "Stat", desc: "Percentage of passes that result in touchdowns", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssTDPct"], + template: "Stat", title: "TD%", }, "stat:pssIntPct": { + cat: "Stat", desc: "Percentage of passes that result in interceptions", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssIntPct"], + template: "Stat", title: "Int%", }, "stat:pssYdsPerAtt": { + cat: "Stat", desc: "Pass Yards Per Attempt", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssYdsPerAtt"], + template: "Stat", title: "Y/A", }, "stat:pssAdjYdsPerAtt": { + cat: "Stat", desc: "Adjusted Pass Yards Per Attempt ((yds + 20 * TD - 45 * int) / att)", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssAdjYdsPerAtt"], + template: "Stat", title: "AY/A", }, "stat:pssYdsPerCmp": { + cat: "Stat", desc: "Pass Yards Per Completion", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssYdsPerCmp"], + template: "Stat", title: "Y/C", }, "stat:pssYdsPerGame": { + cat: "Stat", desc: "Pass Yards Per Game", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssYdsPerGame"], + template: "Stat", title: "Y/G", }, "stat:pssNetYdsPerAtt": { + cat: "Stat", desc: "Net Pass Yards Per Attempt (passes and sacks)", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssNetYdsPerAtt"], + template: "Stat", title: "NY/A", }, "stat:pssAdjNetYdsPerAtt": { + cat: "Stat", desc: "Adjusted Net Pass Yards Per Attempt ((yds + 20 * TD - 45 * int - skYds) / (att + sk))", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssAdjNetYdsPerAtt"], + template: "Stat", title: "ANY/A", }, "stat:pssSkPct": { + cat: "Stat", desc: "Percentage of times sacked when attempting a pass", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pssSkPct"], + template: "Stat", title: "Sk%", }, "stat:rusYdsPerGame": { + cat: "Stat", desc: "Rushing Yards Per Game", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["rusYdsPerGame"], + template: "Stat", title: "Y/G", }, "stat:rusPerGame": { + cat: "Stat", desc: "Rushing Attempts Per Game", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["rusPerGame"], + template: "Stat", title: "A/G", }, "stat:recYdsPerRec": { + cat: "Stat", desc: "Yards Per Reception", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["recYdsPerRec"], + template: "Stat", title: "Y/R", }, "stat:recPerGame": { + cat: "Stat", desc: "Receptions Per Game", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["recPerGame"], + template: "Stat", title: "R/G", }, "stat:recYdsPerGame": { + cat: "Stat", desc: "Receiving Yards Per Game", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["recYdsPerGame"], + template: "Stat", title: "Y/G", }, "stat:recCatchPct": { + cat: "Stat", desc: "Catch Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["recCatchPct"], + template: "Stat", title: "Ctch%", }, "stat:touches": { + cat: "Stat", desc: "Touches (Rushing Attempts And Receptions)", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["touches"], + template: "Stat", title: "Tch", }, "stat:ydsPerTouch": { + cat: "Stat", desc: "Yards Per Touch", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ydsPerTouch"], + template: "Stat", title: "Y/Tch", }, "stat:ydsFromScrimmage": { + cat: "Stat", desc: "Total Rushing and Receiving Yards From Scrimmage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ydsFromScrimmage"], + template: "Stat", title: "YScm", }, "stat:rusRecTD": { + cat: "Stat", desc: "Total Rushing and Receiving Touchdowns", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["rusRecTD"], + template: "Stat", title: "RRTD", }, "stat:allPurposeYds": { + cat: "Stat", desc: "All Purpose Yards (Rushing, Receiving, and Kick/Punt/Fumble/Interception Returns)", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["allPurposeYds"], + template: "Stat", title: "APY", }, "stat:av": { + cat: "Stat", desc: "Approximate Value", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["av"], + template: "Stat", title: "AV", }, "stat:avPerPlayer": { + cat: "Stat", desc: "Approximate Value Per Player", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["avPerPlayer"], + template: "Stat", title: "AV/Player", }, }, hockey: { "pos:C": { + cat: "Position", desc: "Center", sortType: "number", title: "C", }, "pos:W": { + cat: "Position", desc: "Wing", sortType: "number", title: "W", }, "pos:D": { + cat: "Position", desc: "Defenseman", sortType: "number", title: "D", }, "pos:G": { + cat: "Position", desc: "Goalie", sortType: "number", title: "G", }, "rating:pss": { + cat: "Rating", desc: "Passing", + ratings: ["pss"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Pss", }, "rating:wst": { + cat: "Rating", desc: "Wristshot", + ratings: ["wst"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Wst", }, "rating:sst": { + cat: "Rating", desc: "Slapshot", + ratings: ["sst"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Sst", }, "rating:stk": { + cat: "Rating", desc: "Stickhandling", + ratings: ["stk"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Stk", }, "rating:oiq": { + cat: "Rating", desc: "Offensive IQ", + ratings: ["oiq"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "oIQ", }, "rating:chk": { + cat: "Rating", desc: "Checking", + ratings: ["chk"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Chk", }, "rating:blk": { + cat: "Rating", desc: "Shot Blocking", + ratings: ["blk"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Blk", }, "rating:fcf": { + cat: "Rating", desc: "Faceoffs", + ratings: ["fcf"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Fcf", }, "rating:diq": { + cat: "Rating", desc: "Defensive IQ", + ratings: ["diq"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "dIQ", }, "rating:glk": { + cat: "Rating", desc: "Goalkeeping", + ratings: ["glk"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "Glk", }, "rating:ovrC": { + cat: "Rating", desc: "Overall Rating (Center)", + ratings: ["ovrC"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrC", }, "rating:ovrW": { + cat: "Rating", desc: "Overall Rating (Winger)", + ratings: ["ovrW"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrW", }, "rating:ovrD": { + cat: "Rating", desc: "Overall Rating (Defenseman)", + ratings: ["ovrD"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrD", }, "rating:ovrG": { + cat: "Rating", desc: "Overall Rating (Goalie)", + ratings: ["ovrG"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "OvrG", }, "rating:potC": { + cat: "Rating", desc: "Potential Rating (Center)", + ratings: ["potC"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotC", }, "rating:potW": { + cat: "Rating", desc: "Potential Rating (Winger)", + ratings: ["potW"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotW", }, "rating:potD": { + cat: "Rating", desc: "Potential Rating (Defenseman)", + ratings: ["potD"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotD", }, "rating:potG": { + cat: "Rating", desc: "Potential Rating (Goalie)", + ratings: ["potG"], sortSequence: ["desc", "asc"], sortType: "number", + template: "Rating", title: "PotG", }, "stat:gpGoalie": { + cat: "Stat", desc: "Games Played (Goalie)", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["gpGoalie"], + template: "Stat", title: gp, }, "stat:gpSkater": { + cat: "Stat", desc: "Games Played (Skater)", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["gpSkater"], + template: "Stat", title: gp, }, "stat:pm": { + cat: "Stat", desc: "Plus/Minus", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pm"], + template: "Stat", title: "+/-", }, "stat:pim": { + cat: "Stat", desc: "Penalty Minutes", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pim"], + template: "Stat", title: "PIM", }, "stat:evG": { + cat: "Stat", desc: "Even Strength Goals", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["evG"], + template: "Stat", title: "evG", }, "stat:ppG": { + cat: "Stat", desc: "Power Play Goals", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ppG"], + template: "Stat", title: "ppG", }, "stat:shG": { + cat: "Stat", desc: "Short-Handed Goals", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["shG"], + template: "Stat", title: "shG", }, "stat:gwG": { + cat: "Stat", desc: "Game Winning Goals", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["gwG"], + template: "Stat", title: "gwG", }, "stat:evA": { + cat: "Stat", desc: "Even Strength Assists", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["evA"], + template: "Stat", title: "evA", }, "stat:ppA": { + cat: "Stat", desc: "Power Play Assists", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ppA"], + template: "Stat", title: "ppA", }, "stat:shA": { + cat: "Stat", desc: "Short-Handed Assists", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["shA"], + template: "Stat", title: "shA", }, "stat:gwA": { + cat: "Stat", desc: "Game Winning Assists", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["gwA"], + template: "Stat", title: "gwA", }, "stat:s": { + cat: "Stat", desc: "Shots on Goal", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["s"], + template: "Stat", title: "S", }, "stat:tsa": { + cat: "Stat", desc: "Total Shots Attempted", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["tsa"], + template: "Stat", title: "TSA", }, "stat:fow": { + cat: "Stat", desc: "Faceoff Wins", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fow"], + template: "Stat", title: "FOW", }, "stat:fol": { + cat: "Stat", desc: "Faceoff Losses", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["fol"], + template: "Stat", title: "FOL", }, "stat:foPct": { + cat: "Stat", desc: "Faceoff Win Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["foPct"], + template: "Stat", title: "FO%", }, "stat:blk": { + cat: "Stat", desc: "Blocks", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["blk"], + template: "Stat", title: "BLK", }, "stat:hit": { + cat: "Stat", desc: "Hits", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["hit"], + template: "Stat", title: "HIT", }, "stat:tk": { + cat: "Stat", desc: "Takeaways", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["tk"], + template: "Stat", title: "TK", }, "stat:gv": { + cat: "Stat", desc: "Giveaways", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["gv"], + template: "Stat", title: "GV", }, "stat:ga": { + cat: "Stat", desc: "Goals Against", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ga"], + template: "Stat", title: "GA", }, "stat:sv": { + cat: "Stat", desc: "Saves", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["sv"], + template: "Stat", title: "SV", }, "stat:so": { + cat: "Stat", desc: "Shutouts", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["so"], + template: "Stat", title: "SO", }, "stat:g": { + cat: "Stat", desc: "Goals", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["g"], + template: "Stat", title: "G", }, "stat:a": { + cat: "Stat", desc: "Assists", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["a"], + template: "Stat", title: "A", }, "stat:pts": { + cat: "Stat", desc: "Points", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["pts"], + template: "Stat", title: "PTS", }, "stat:sPct": { + cat: "Stat", desc: "Shooting Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["sPct"], + template: "Stat", title: "S%", }, "stat:svPct": { + cat: "Stat", desc: "Save Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["svPct"], + template: "Stat", title: "SV%", }, "stat:sa": { + cat: "Stat", desc: "Shots Against", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["sa"], + template: "Stat", title: "SA", }, "stat:gaa": { + cat: "Stat", desc: "Goals Against Average", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["gaa"], + template: "Stat", title: "GAA", }, "stat:keyStats": { + cat: "Stat", desc: "Key Stats", sortSequence: ["desc", "asc"], sortType: "string", + stats: ["keyStats"], + template: "Stat", title: "Stats", }, "stat:ps": { + cat: "Stat", desc: "Point Shares", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ps"], + template: "Stat", title: "PS", }, "stat:psPerPlayer": { + cat: "Stat", desc: "Point Shares Per Player", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["psPerPlayer"], + template: "Stat", title: "PS/Player", }, "stat:ops": { + cat: "Stat", desc: "Offensive Point Shares", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ops"], + template: "Stat", title: "OPS", }, "stat:dps": { + cat: "Stat", desc: "Defensive Point Shares", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["dps"], + template: "Stat", title: "DPS", }, "stat:gps": { + cat: "Stat", desc: "Goalie Point Shares", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["gps"], + template: "Stat", title: "GPS", }, "stat:gc": { + cat: "Stat", desc: "Goals Created", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["gc"], + template: "Stat", title: "GC", }, "stat:amin": { + cat: "Stat", desc: "Average Time On Ice", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["amin"], + template: "Stat", title: "ATOI", }, "stat:ppMin": { + cat: "Stat", desc: "Power Play Time On Ice", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ppMin"], + template: "Stat", title: "ppTOI", }, "stat:shMin": { + cat: "Stat", desc: "Short Handed Time On Ice", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["shMin"], + template: "Stat", title: "shTOI", }, "stat:ppo": { + cat: "Stat", desc: "Power Play Opportunities", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ppo"], + template: "Stat", title: "PPO", }, "stat:ppPct": { + cat: "Stat", desc: "Power Play Percentage", sortSequence: ["desc", "asc"], sortType: "number", + stats: ["ppPct"], + template: "Stat", title: "PP%", }, "stat:gRec": { + cat: "Stat", desc: "Record as primary G", sortSequence: ["desc", "asc"], sortType: "record", + stats: ["gRec"], + template: "Stat", title: "Rec", }, "stat:gW": { + cat: "Stat", desc: "Wins as primary G", sortSequence: ["desc", "asc"], sortType: "record", + stats: ["gW"], + template: "Stat", title: "GW", }, "stat:gL": { + cat: "Stat", desc: "Losses as primary G", sortSequence: ["desc", "asc"], sortType: "record", + stats: ["gL"], + template: "Stat", title: "GL", }, "stat:gT": { + cat: "Stat", desc: "Ties as primary G", sortSequence: ["desc", "asc"], sortType: "record", + stats: ["gT"], + template: "Stat", title: "GT", }, "stat:gOTL": { + cat: "Stat", desc: "Overtime losses as primary G", sortSequence: ["desc", "asc"], sortType: "record", + stats: ["gOTL"], + template: "Stat", title: "GOTL", }, }, }); const cols: { [key: string]: ColTemp; +} = { + Acquired: { + cat: "General", + desc: "How Player Was Acquired", + }, + Age: { + attrs: ["age"], + cat: "General", + sortType: "number", + template: "Age", + }, + "Asking For": { + attrs: ["contract", "mood"], + cat: "General", + sortSequence: ["desc", "asc"], + sortType: "currency", + template: "AskingFor", + }, + College: { + attrs: ["college"], + cat: "General", + template: "College", + }, + Contract: { + attrs: ["contract"], + cat: "General", + sortSequence: ["desc", "asc"], + sortType: "currency", + template: "Contract", + }, + Country: { + attrs: ["born"], + cat: "General", + template: "Country", + }, + Projected: { + attrs: ["contractDesired"], + cat: "General", + desc: "Projected Contract Demand", + sortSequence: ["desc", "asc"], + sortType: "currency", + template: "Projected", + }, + Mood: { + attrs: ["mood"], + cat: "General", + desc: "How Player Feels About Your Team", + sortSequence: ["desc", "asc"], + sortType: "number", + template: "Mood", + width: "1px", + }, + CurrentMood: { + attrs: ["mood"], + cat: "General", + desc: "How Player Feels About Their Current Team", + sortSequence: ["desc", "asc"], + sortType: "number", + template: "MoodCurrent", + title: "Current Mood", + width: "1px", + }, + DraftYear: { + attrs: ["draft"], + cat: "General", + sortType: "number", + template: "DraftYear", + title: "Draft Year", + }, + Name: { + cat: "General", + ratings: ["skills"], + attrs: ["pid", "name", "injury", "jerseyNumber", "watch"], + sortType: "name", + template: "Name", + }, + Exp: { + cat: "General", + desc: "Contract Expiration", + attrs: ["contract"], + sortSequence: ["asc", "desc"], + sortType: "number", + template: "Exp", + }, + Experience: { + cat: "General", + desc: "Number of Years in the League", + attrs: ["experience"], + sortSequence: ["desc", "asc"], + sortType: "number", + template: "Experience", + }, + Ovr: { + cat: "General", + desc: "Overall Rating", + ratings: ["dovr", "ovr"], + sortSequence: ["desc", "asc"], + sortType: "number", + template: "Ovr", + }, + "Ovr Drop": { + cat: "General", + desc: "Decrease in Overall Rating", + sortSequence: ["desc", "asc"], + sortType: "number", + template: "attr", + attrs: ["ovrDrop"], + }, + "Peak Ovr": { + cat: "General", + desc: "Peak Overall Rating", + sortSequence: ["desc", "asc"], + sortType: "number", + template: "attr", + attrs: ["peakOvr"], + }, + Pos: { + cat: "General", + desc: "Position", + ratings: ["pos"], + attrs: ["pos"], + template: "Pos", + }, + Pot: { + cat: "General", + desc: "Potential Rating", + ratings: ["dpot", "pot"], + sortSequence: ["desc", "asc"], + sortType: "number", + template: "Pot", + }, + "Pot Drop": { + cat: "General", + desc: "Decrease in Potential Rating", + sortSequence: ["desc", "asc"], + sortType: "number", + template: "attr", + attrs: ["potDrop"], + }, + Pick: { + attrs: ["draft"], + cat: "General", + desc: "Draft Pick", + sortType: "draftPick", + template: "Pick", + }, + Team: { + cat: "General", + sortType: "string", + stats: ["abbrev"], + template: "Team", + }, + Weight: { + attrs: ["weight"], + cat: "General", + desc: "Weight", + template: "Weight", + }, + Height: { + attrs: ["hgt"], + cat: "General", + desc: "Height", + template: "Height", + }, + InjuryType: { + cat: "General", + template: "InjuryType", + attrs: ["injury"], + desc: "Type of Injury", + title: "Type", + }, + InjuryLength: { + cat: "General", + template: "InjuryLength", + attrs: ["injury"], + title: "Games", + desc: "Number of Games", + sortSequence: ["desc", "asc"], + sortType: "number", + }, + "rating:endu": { + cat: "Rating", + desc: "Endurance", + ratings: ["endu"], + sortSequence: ["desc", "asc"], + sortType: "number", + template: "Rating", + title: "End", + }, + "rating:hgt": { + cat: "Rating", + desc: "Height", + ratings: ["hgt"], + sortSequence: ["desc", "asc"], + sortType: "number", + template: "Rating", + title: "Hgt", + }, + "rating:spd": { + cat: "Rating", + desc: "Speed", + ratings: ["spd"], + sortSequence: ["desc", "asc"], + sortType: "number", + template: "Rating", + title: "Spd", + }, + "rating:stre": { + cat: "Rating", + desc: "Strength", + ratings: ["stre"], + sortSequence: ["desc", "asc"], + sortType: "number", + template: "Rating", + title: "Str", + }, + "stat:gp": { + cat: "Stat", + desc: "Games Played", + options: { decimals: 0 }, + sortSequence: ["desc", "asc"], + sortType: "number", + stats: ["gp"], + template: "Stat", + title: gp, + }, + "stat:gs": { + cat: "Stat", + desc: "Games Started", + options: { decimals: 0 }, + sortSequence: ["desc", "asc"], + sortType: "number", + stats: ["gs"], + template: "Stat", + title: "GS", + }, + "stat:jerseyNumber": { + cat: "General", + desc: "Jersey Number", + options: { decimals: 0 }, + sortSequence: ["asc", "desc"], + sortType: "number", + stats: ["jerseyNumber"], + template: "Stat", + title: "#", + }, + "stat:min": { + cat: "Stat", + desc: isSport("hockey") ? "Time On Ice" : "Minutes", + sortSequence: ["desc", "asc"], + sortType: "number", + stats: ["min"], + template: "Stat", + title: isSport("hockey") ? "TOI" : "MP", + }, + "stat:yearsWithTeam": { + cat: "General", + desc: "Years With Team", + sortSequence: ["desc", "asc"], + sortType: "number", + stats: ["yearsWithTeam"], + template: "Stat", + title: "YWT", + }, + ...sportSpecificCols, +}; + +const legacyCols: { + [key: string]: Partial
; } = { "": { sortSequence: ["desc", "asc"], @@ -1987,21 +3181,7 @@ const cols: { sortSequence: ["desc", "asc"], sortType: "number", }, - Acquired: { - desc: "How Player Was Acquired", - }, Actions: {}, - Age: { - sortType: "number", - }, - Amount: { - sortSequence: ["desc", "asc"], - sortType: "currency", - }, - "Asking For": { - sortSequence: ["desc", "asc"], - sortType: "currency", - }, "Avg Attendance": { sortSequence: ["desc", "asc"], sortType: "number", @@ -2035,17 +3215,11 @@ const cols: { sortSequence: ["desc", "asc"], sortType: "number", }, - College: {}, Conference: {}, - Contract: { - sortSequence: ["desc", "asc"], - sortType: "currency", - }, Count: { sortSequence: ["desc", "asc"], sortType: "number", }, - Country: {}, Created: { desc: "Created Date", searchType: "string", @@ -2057,18 +3231,6 @@ const cols: { sortSequence: ["desc", "asc"], sortType: "number", }, - "Current Contract": { - desc: "Current Contract", - sortSequence: ["desc", "asc"], - sortType: "currency", - title: "Current", - }, - "Projected Contract": { - desc: "Projected Contract", - sortSequence: ["desc", "asc"], - sortType: "currency", - title: "Projected", - }, Details: {}, Died: { sortSequence: ["desc", "asc"], @@ -2088,31 +3250,12 @@ const cols: { }, "Draft Picks": { sortSequence: [], - }, - "Draft Year": { - sortType: "number", - }, - Drafted: { - sortType: "number", + sortType: "draftPick", }, "Dunk Winner": { desc: "Slam Dunk Contest Winner", sortType: "name", }, - End: { - sortSequence: ["desc", "asc"], - sortType: "number", - }, - Exp: { - desc: "Contract Expiration", - sortSequence: ["asc", "desc"], - sortType: "number", - }, - Experience: { - desc: "Number of Years in the League", - sortSequence: ["desc", "asc"], - sortType: "number", - }, Finals: { desc: "Finals Appearances", sortSequence: ["desc", "asc"], @@ -2149,10 +3292,6 @@ const cols: { sortSequence: ["desc", "asc"], sortType: "number", }, - Height: { - sortSequence: ["desc", "asc"], - sortType: "number", - }, HOF: { sortSequence: ["desc", "asc"], }, @@ -2195,11 +3334,6 @@ const cols: { sortSequence: ["desc", "asc"], sortType: "number", }, - Mood: { - width: "1px", - sortSequence: ["desc", "asc"], - sortType: "number", - }, MVP: { desc: "Most Valuable Player", sortType: "name", @@ -2234,16 +3368,6 @@ const cols: { Opp: { desc: "Opponent", }, - Ovr: { - desc: "Overall Rating", - sortSequence: ["desc", "asc"], - sortType: "number", - }, - "Ovr Drop": { - desc: "Decrease in Overall Rating", - sortSequence: ["desc", "asc"], - sortType: "number", - }, PA: { desc: `${isSport("hockey") ? "Goals" : "Points"} Against`, sortSequence: ["desc", "asc"], @@ -2272,11 +3396,6 @@ const cols: { sortSequence: ["desc", "asc"], sortType: "currency", }, - "Peak Ovr": { - desc: "Peak Overall Rating", - sortSequence: ["desc", "asc"], - sortType: "number", - }, Phase: { desc: "League Season and Phase", sortSequence: ["desc", "asc"], @@ -2295,19 +3414,6 @@ const cols: { sortSequence: ["desc", "asc"], sortType: "number", }, - Pos: { - desc: "Position", - }, - Pot: { - desc: "Potential Rating", - sortSequence: ["desc", "asc"], - sortType: "number", - }, - "Pot Drop": { - desc: "Decrease in Potential Rating", - sortSequence: ["desc", "asc"], - sortType: "number", - }, "Pre-Lottery": { desc: "Pre-lottery rank", sortSequence: ["desc", "asc"], @@ -2322,16 +3428,6 @@ const cols: { sortSequence: ["desc", "asc"], sortType: "currency", }, - PTS: { - desc: "Points", - sortSequence: ["desc", "asc"], - sortType: "number", - }, - "PTS%": { - desc: "Points Divided By Maximum Points", - sortSequence: ["desc", "asc"], - sortType: "number", - }, Received: { desc: "Assets Received in Trade", }, @@ -2372,7 +3468,6 @@ const cols: { desc: "Playoff Seed", sortType: "number", }, - Skills: {}, Start: { sortSequence: ["desc", "asc"], sortType: "number", @@ -2409,82 +3504,15 @@ const cols: { Type: { desc: "Type of Game", }, - TypeInjury: { - desc: "Type of Injury", - title: "Type", - }, W: { desc: "Wins", sortSequence: ["desc", "asc"], sortType: "number", }, - Weight: { - sortSequence: ["desc", "asc"], - sortType: "number", - }, - X: { - desc: "Exclude from counter offers", - noSearch: true, - sortSequence: [], - }, Year: { sortType: "number", }, Summary: {}, - "rating:endu": { - desc: "Endurance", - sortSequence: ["desc", "asc"], - sortType: "number", - title: "End", - }, - "rating:hgt": { - desc: "Height", - sortSequence: ["desc", "asc"], - sortType: "number", - title: "Hgt", - }, - "rating:spd": { - desc: "Speed", - sortSequence: ["desc", "asc"], - sortType: "number", - title: "Spd", - }, - "rating:stre": { - desc: "Strength", - sortSequence: ["desc", "asc"], - sortType: "number", - title: "Str", - }, - "stat:gp": { - desc: "Games Played", - sortSequence: ["desc", "asc"], - sortType: "number", - title: gp, - }, - "stat:gpPerPlayer": { - desc: "Games Played Per Player", - sortSequence: ["desc", "asc"], - sortType: "number", - title: `${gp}/Player`, - }, - "stat:gs": { - desc: "Games Started", - sortSequence: ["desc", "asc"], - sortType: "number", - title: "GS", - }, - "stat:jerseyNumber": { - desc: "Jersey Number", - sortSequence: ["asc", "desc"], - sortType: "number", - title: "#", - }, - "stat:min": { - desc: isSport("hockey") ? "Time On Ice" : "Minutes", - sortSequence: ["desc", "asc"], - sortType: "number", - title: isSport("hockey") ? "TOI" : "MP", - }, "stat:mov": { desc: "Average Margin of Victory", sortSequence: ["desc", "asc"], @@ -2497,12 +3525,6 @@ const cols: { sortType: "number", title: "Diff", }, - "stat:yearsWithTeam": { - desc: "Years With Team", - sortSequence: ["desc", "asc"], - sortType: "number", - title: "YWT", - }, "count:allDefense": { desc: "All-Defensive Team", sortSequence: ["desc", "asc"], @@ -2651,22 +3673,30 @@ const cols: { sortType: "number", title: "DROY", }, - ...sportSpecificCols, }; -export default ( - titles: string[], - overrides: Record> = {}, -): Col[] => { - return titles.map(title => { - if (!cols.hasOwnProperty(title)) { - throw new Error(`Unknown column: "${title}"`); - } - +export function getAllCols(): Col[] { + return Object.entries(cols).map(([name, col]): Col => { return { - ...cols[title], - title: cols[title].title ?? title, - ...overrides[title], + ...col, + title: col.title ?? name, + key: name, }; }); +} + +export default ( + keys: string[], + overrides: Record> = {}, +): MetaCol[] => { + return keys.flatMap(key => { + const col: MetaCol = { + title: key, + ...(cols[key] ?? legacyCols[key] ?? {}), + ...overrides[key], + key: key, + }; + + return [col]; + }); }; diff --git a/src/ui/util/columns/getTemplate.ts b/src/ui/util/columns/getTemplate.ts new file mode 100644 index 0000000000..5dd2a11972 --- /dev/null +++ b/src/ui/util/columns/getTemplate.ts @@ -0,0 +1,12 @@ +import * as templates from "./templates"; +import type { MetaCol } from "./getCols"; +import type { Player } from "../../../common/types"; +import type { TableConfig } from "../TableConfig"; + +export default function (p: Player, c: MetaCol, config: TableConfig) { + if (typeof c.template === "function") return c.template(p, c, config.vars); + if (!(c.template in templates)) return ""; + // @ts-ignore + // eslint-disable-next-line import/namespace + return templates[c.template](p, c, config.vars); +} diff --git a/src/ui/util/columns/templates/Age.tsx b/src/ui/util/columns/templates/Age.tsx new file mode 100644 index 0000000000..1b85bed0ac --- /dev/null +++ b/src/ui/util/columns/templates/Age.tsx @@ -0,0 +1,4 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => p.age; diff --git a/src/ui/util/columns/templates/AskingFor.tsx b/src/ui/util/columns/templates/AskingFor.tsx new file mode 100644 index 0000000000..52401c3b8e --- /dev/null +++ b/src/ui/util/columns/templates/AskingFor.tsx @@ -0,0 +1,6 @@ +import type { Player } from "../../../../common/types"; +import { helpers } from "../../index"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => + helpers.formatCurrency(p.mood.user.contractAmount / 1000, "M"); diff --git a/src/ui/util/columns/templates/Attr.tsx b/src/ui/util/columns/templates/Attr.tsx new file mode 100644 index 0000000000..d5642b8417 --- /dev/null +++ b/src/ui/util/columns/templates/Attr.tsx @@ -0,0 +1,8 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => { + const key: string = c.attrs[0]; + if (!(key in p)) return `${key} not found`; + return p[key]; +}; diff --git a/src/ui/util/columns/templates/College.tsx b/src/ui/util/columns/templates/College.tsx new file mode 100644 index 0000000000..3b39a9495f --- /dev/null +++ b/src/ui/util/columns/templates/College.tsx @@ -0,0 +1,19 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; +import { helpers } from "../../index"; + +export default (p: Player, c: ColTemp) => { + const college = p.college && p.college !== "" ? p.college : "None"; + return ( + + {college} + + ); +}; diff --git a/src/ui/util/columns/templates/Contract.tsx b/src/ui/util/columns/templates/Contract.tsx new file mode 100644 index 0000000000..2dfeb119e0 --- /dev/null +++ b/src/ui/util/columns/templates/Contract.tsx @@ -0,0 +1,6 @@ +import type { Player } from "../../../../common/types"; +import { helpers } from "../../index"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => + `${helpers.formatCurrency(p.contract.amount, "M")} thru ${p.contract.exp}`; diff --git a/src/ui/util/columns/templates/Country.tsx b/src/ui/util/columns/templates/Country.tsx new file mode 100644 index 0000000000..27830e3b32 --- /dev/null +++ b/src/ui/util/columns/templates/Country.tsx @@ -0,0 +1,20 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; +import { helpers } from "../../index"; +import { CountryFlag } from "../../../components"; + +export default (p: Player, c: ColTemp) => ( + <> + + + {p.born.loc} + + +); diff --git a/src/ui/util/columns/templates/DraftYear.tsx b/src/ui/util/columns/templates/DraftYear.tsx new file mode 100644 index 0000000000..0d2e49a7ef --- /dev/null +++ b/src/ui/util/columns/templates/DraftYear.tsx @@ -0,0 +1,4 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => p.draft.year; diff --git a/src/ui/util/columns/templates/Exp.tsx b/src/ui/util/columns/templates/Exp.tsx new file mode 100644 index 0000000000..f11e639f75 --- /dev/null +++ b/src/ui/util/columns/templates/Exp.tsx @@ -0,0 +1,4 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => p.contract.exp.toString(); diff --git a/src/ui/util/columns/templates/Experience.tsx b/src/ui/util/columns/templates/Experience.tsx new file mode 100644 index 0000000000..9c42d12dfa --- /dev/null +++ b/src/ui/util/columns/templates/Experience.tsx @@ -0,0 +1,4 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => p.experience.toString(); diff --git a/src/ui/util/columns/templates/Height.tsx b/src/ui/util/columns/templates/Height.tsx new file mode 100644 index 0000000000..d269e95cda --- /dev/null +++ b/src/ui/util/columns/templates/Height.tsx @@ -0,0 +1,5 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; +import { wrappedHeight } from "../../../components/Height"; + +export default (p: Player, c: ColTemp) => wrappedHeight(p.hgt); diff --git a/src/ui/util/columns/templates/InjuryLength.tsx b/src/ui/util/columns/templates/InjuryLength.tsx new file mode 100644 index 0000000000..6ee7b44b2f --- /dev/null +++ b/src/ui/util/columns/templates/InjuryLength.tsx @@ -0,0 +1,4 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => p.injury.gamesRemaining; diff --git a/src/ui/util/columns/templates/InjuryType.tsx b/src/ui/util/columns/templates/InjuryType.tsx new file mode 100644 index 0000000000..4eeeb68173 --- /dev/null +++ b/src/ui/util/columns/templates/InjuryType.tsx @@ -0,0 +1,4 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => p.injury.type; diff --git a/src/ui/util/columns/templates/Mood.tsx b/src/ui/util/columns/templates/Mood.tsx new file mode 100644 index 0000000000..65ae3fe858 --- /dev/null +++ b/src/ui/util/columns/templates/Mood.tsx @@ -0,0 +1,6 @@ +import type { Player } from "../../../../common/types"; +import { dataTableWrappedMood } from "../../../components/Mood"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => + dataTableWrappedMood({ defaultType: "user", maxWidth: true, p }); diff --git a/src/ui/util/columns/templates/MoodCurrent.tsx b/src/ui/util/columns/templates/MoodCurrent.tsx new file mode 100644 index 0000000000..0bb241565e --- /dev/null +++ b/src/ui/util/columns/templates/MoodCurrent.tsx @@ -0,0 +1,6 @@ +import type { Player } from "../../../../common/types"; +import { dataTableWrappedMood } from "../../../components/Mood"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => + dataTableWrappedMood({ defaultType: "current", maxWidth: true, p }); diff --git a/src/ui/util/columns/templates/Name.tsx b/src/ui/util/columns/templates/Name.tsx new file mode 100644 index 0000000000..d4be25ffd8 --- /dev/null +++ b/src/ui/util/columns/templates/Name.tsx @@ -0,0 +1,15 @@ +import type { Player } from "../../../../common/types"; +import { PlayerNameLabels } from "../../../components"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => ( + + {p.name} + +); diff --git a/src/ui/util/columns/templates/Negotiate.tsx b/src/ui/util/columns/templates/Negotiate.tsx new file mode 100644 index 0000000000..9d5f08cd1d --- /dev/null +++ b/src/ui/util/columns/templates/Negotiate.tsx @@ -0,0 +1,14 @@ +import type { Player } from "../../../../common/types"; +import { NegotiateButtons } from "../../../components"; +import { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp, vars: object) => ( + +); diff --git a/src/ui/util/columns/templates/Ovr.tsx b/src/ui/util/columns/templates/Ovr.tsx new file mode 100644 index 0000000000..a2df562d9d --- /dev/null +++ b/src/ui/util/columns/templates/Ovr.tsx @@ -0,0 +1,14 @@ +import type { Player } from "../../../../common/types"; +import { RatingWithChange } from "../../../components"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp, vars: object) => { + if (vars["challengeNoRatings"]) return ""; + else if (p.ratings["dovr"]) + return ( + + {p.ratings["ovr"]} + + ); + else return p.ratings["ovr"]; +}; diff --git a/src/ui/util/columns/templates/Pick.tsx b/src/ui/util/columns/templates/Pick.tsx new file mode 100644 index 0000000000..2c04c726df --- /dev/null +++ b/src/ui/util/columns/templates/Pick.tsx @@ -0,0 +1,5 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => + p.draft.round > 0 ? `${p.draft.round}-${p.draft.pick}` : null; diff --git a/src/ui/util/columns/templates/Pos.tsx b/src/ui/util/columns/templates/Pos.tsx new file mode 100644 index 0000000000..bd12a61d89 --- /dev/null +++ b/src/ui/util/columns/templates/Pos.tsx @@ -0,0 +1,4 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => p.ratings.pos; diff --git a/src/ui/util/columns/templates/Pot.tsx b/src/ui/util/columns/templates/Pot.tsx new file mode 100644 index 0000000000..fe5e45c346 --- /dev/null +++ b/src/ui/util/columns/templates/Pot.tsx @@ -0,0 +1,14 @@ +import type { Player } from "../../../../common/types"; +import { RatingWithChange } from "../../../components"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp, vars: object) => { + if (vars["challengeNoRatings"]) return ""; + else if (p.ratings["dpot"]) + return ( + + {p.ratings["pot"]} + + ); + else return p.ratings["pot"]; +}; diff --git a/src/ui/util/columns/templates/Projected.tsx b/src/ui/util/columns/templates/Projected.tsx new file mode 100644 index 0000000000..e2c545f7a8 --- /dev/null +++ b/src/ui/util/columns/templates/Projected.tsx @@ -0,0 +1,6 @@ +import type { Player } from "../../../../common/types"; +import { helpers } from "../../index"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => + `${helpers.formatCurrency(p.contractDesired.amount, "M")}`; diff --git a/src/ui/util/columns/templates/Rating.tsx b/src/ui/util/columns/templates/Rating.tsx new file mode 100644 index 0000000000..7bafdb57b1 --- /dev/null +++ b/src/ui/util/columns/templates/Rating.tsx @@ -0,0 +1,8 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp, vars: object) => { + if (vars["challengeNoRatings"]) return ""; + const key = c.ratings[0] ?? false; + return key && key in p.ratings ? p.ratings[key].toString() : ""; +}; diff --git a/src/ui/util/columns/templates/Stat.tsx b/src/ui/util/columns/templates/Stat.tsx new file mode 100644 index 0000000000..5b9433a616 --- /dev/null +++ b/src/ui/util/columns/templates/Stat.tsx @@ -0,0 +1,11 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => { + const key: string = c.stats[0]; + if (!(key in p.stats)) return `${key} not found`; + const value = p.stats[key]; + return typeof value == "number" + ? value.toFixed(c.options ? c.options.decimals : 1) + : value; +}; diff --git a/src/ui/util/columns/templates/Team.tsx b/src/ui/util/columns/templates/Team.tsx new file mode 100644 index 0000000000..23128f4952 --- /dev/null +++ b/src/ui/util/columns/templates/Team.tsx @@ -0,0 +1,11 @@ +import { helpers } from "../../index"; +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => { + return ( + + {p.stats.abbrev} + + ); +}; diff --git a/src/ui/util/columns/templates/Trade.tsx b/src/ui/util/columns/templates/Trade.tsx new file mode 100644 index 0000000000..59d4dd53ec --- /dev/null +++ b/src/ui/util/columns/templates/Trade.tsx @@ -0,0 +1,13 @@ +import type { Player } from "../../../../common/types"; +import { NegotiateButtons } from "../../../components"; +import { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp, vars: object) => ( + +); diff --git a/src/ui/util/columns/templates/Weight.tsx b/src/ui/util/columns/templates/Weight.tsx new file mode 100644 index 0000000000..2e79fcfc06 --- /dev/null +++ b/src/ui/util/columns/templates/Weight.tsx @@ -0,0 +1,5 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; +import { wrappedWeight } from "../../../components/Weight"; + +export default (p: Player, c: ColTemp) => wrappedWeight(p.weight); diff --git a/src/ui/util/columns/templates/YearsWithTeam.tsx b/src/ui/util/columns/templates/YearsWithTeam.tsx new file mode 100644 index 0000000000..3d991cc753 --- /dev/null +++ b/src/ui/util/columns/templates/YearsWithTeam.tsx @@ -0,0 +1,7 @@ +import type { Player } from "../../../../common/types"; +import type { ColTemp } from "../getCols"; + +export default (p: Player, c: ColTemp) => { + const key: string = c.stats[0]; + return key in p.stats ? p.stats[key].toFixed(1) : `${key} not found`; +}; diff --git a/src/ui/util/columns/templates/index.tsx b/src/ui/util/columns/templates/index.tsx new file mode 100644 index 0000000000..8befddfc3a --- /dev/null +++ b/src/ui/util/columns/templates/index.tsx @@ -0,0 +1,27 @@ +export { default as Age } from "./Age"; +export { default as AskingFor } from "./AskingFor"; +export { default as Attr } from "./Attr"; +export { default as Contract } from "./Contract"; +export { default as Country } from "./Country"; +export { default as College } from "./College"; +export { default as DraftYear } from "./DraftYear"; +export { default as Pick } from "./Pick"; +export { default as Exp } from "./Exp"; +export { default as Experience } from "./Experience"; +export { default as Height } from "./Height"; +export { default as InjuryLength } from "./InjuryLength"; +export { default as InjuryType } from "./InjuryType"; +export { default as Mood } from "./Mood"; +export { default as MoodCurrent } from "./MoodCurrent"; +export { default as Name } from "./Name"; +export { default as Negotiate } from "./Negotiate"; +export { default as Ovr } from "./Ovr"; +export { default as Pos } from "./Pos"; +export { default as Pot } from "./Pot"; +export { default as Projected } from "./Projected"; +export { default as Rating } from "./Rating"; +export { default as Stat } from "./Stat"; +export { default as Team } from "./Team"; +export { default as Trade } from "./Trade"; +export { default as Weight } from "./Weight"; +export { default as YearsWithTeam } from "./YearsWithTeam"; diff --git a/src/ui/util/index.ts b/src/ui/util/index.ts index 94a0eda494..eff39ccc98 100644 --- a/src/ui/util/index.ts +++ b/src/ui/util/index.ts @@ -32,7 +32,7 @@ export { default as confirm } from "./confirm"; export { default as confirmDeleteAllLeagues } from "./confirmDeleteAllLeagues"; export { default as downloadFile } from "./downloadFile"; export { default as genStaticPage } from "./genStaticPage"; -export { default as getCols } from "../../common/getCols"; +export { default as getCols } from "./columns/getCols"; export { default as getScript } from "./getScript"; export { default as gradientStyleFactory } from "./gradientStyleFactory"; export { default as groupAwards } from "./groupAwards"; diff --git a/src/ui/views/AllStarDraft/index.tsx b/src/ui/views/AllStarDraft/index.tsx index a26b7839c0..e878b4b93e 100644 --- a/src/ui/views/AllStarDraft/index.tsx +++ b/src/ui/views/AllStarDraft/index.tsx @@ -119,7 +119,7 @@ const PlayersTable = ({ return ( diff --git a/src/ui/views/AllStarHistory.tsx b/src/ui/views/AllStarHistory.tsx index 0a9a812826..55c22884d2 100644 --- a/src/ui/views/AllStarHistory.tsx +++ b/src/ui/views/AllStarHistory.tsx @@ -273,7 +273,7 @@ const AllStarHistory = ({ allAllStars, userTid }: View<"allStarHistory">) => { 0 ? ( ) => { {rows.length > 0 ? ( - + <> +
+ + + ) : null} ); diff --git a/src/ui/views/FreeAgents.tsx b/src/ui/views/FreeAgents.tsx index 2d252e9fad..83d3fdbcfa 100644 --- a/src/ui/views/FreeAgents.tsx +++ b/src/ui/views/FreeAgents.tsx @@ -1,39 +1,66 @@ +import PropTypes from "prop-types"; import { useCallback, useState } from "react"; import { PHASE } from "../../common"; import { DataTable, MoreLinks, NegotiateButtons, - PlayerNameLabels, RosterComposition, RosterSalarySummary, } from "../components"; import useTitleBar from "../hooks/useTitleBar"; -import { confirm, getCols, helpers, toWorker, useLocalShallow } from "../util"; +import { confirm, toWorker, useLocalShallow } from "../util"; import type { View } from "../../common/types"; -import { dataTableWrappedMood } from "../components/Mood"; +import getTemplate from "../util/columns/getTemplate"; +import { Player } from "../../common/types"; +import { MetaCol } from "../util/columns/getCols"; +import { TableConfig } from "../util/TableConfig"; const FreeAgents = ({ capSpace, challengeNoFreeAgents, challengeNoRatings, godMode, + salaryCapType, maxContract, minContract, numRosterSpots, spectator, phase, players, - salaryCapType, - stats, + config: _config, userPlayers, }: View<"freeAgents">) => { const [addFilters, setAddFilters] = useState< (string | undefined)[] | undefined >(); + const config = TableConfig.unserialize(_config); + + config.addColumn( + { + key: "negotiate", + title: "Negotiate", + template: (p: Player) => ( + // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20544 + // @ts-ignore + + ), + }, + 1, + ); + + const cols = [...config.columns]; + const showAfforablePlayers = useCallback(() => { - const newAddFilters: (string | undefined)[] = new Array(9 + stats.length); + const newAddFilters: (string | undefined)[] = new Array(9); if (capSpace * 1000 > minContract && !challengeNoFreeAgents) { newAddFilters[newAddFilters.length - 3] = `<${capSpace}`; } else { @@ -48,7 +75,7 @@ const FreeAgents = ({ setTimeout(() => { setAddFilters(undefined); }, 0); - }, [capSpace, challengeNoFreeAgents, minContract, stats]); + }, [capSpace, challengeNoFreeAgents, minContract]); useTitleBar({ title: "Free Agents" }); @@ -73,61 +100,12 @@ const FreeAgents = ({ ); } - const cols = getCols([ - "Name", - "Pos", - "Age", - "Ovr", - "Pot", - ...stats.map(stat => `stat:${stat}`), - "Mood", - "Asking For", - "Exp", - "Negotiate", - ]); - const rows = players.map(p => { return { key: p.pid, - data: [ - - {p.name} - , - p.ratings.pos, - p.age, - !challengeNoRatings ? p.ratings.ovr : null, - !challengeNoRatings ? p.ratings.pot : null, - ...stats.map(stat => helpers.roundStat(p.stats[stat], stat)), - dataTableWrappedMood({ - defaultType: "user", - maxWidth: true, - p, - }), - helpers.formatCurrency(p.mood.user.contractAmount / 1000, "M"), - p.contract.exp, - { - value: ( - // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20544 - // @ts-ignore - - ), - searchValue: p.mood.user.willing ? "Negotiate Sign" : "Refuses!", - }, - ], + data: Object.fromEntries( + cols.map(col => [col.key, getTemplate(p, col, config)]), + ), }; }); @@ -193,7 +171,8 @@ const FreeAgents = ({ ) => { @@ -21,84 +21,19 @@ const PlayerRatings = ({ dropdownFields: { teamsAndAllWatch: abbrev, seasons: season }, }); - const ovrsPotsColNames: string[] = []; - if (isSport("football") || isSport("hockey")) { - for (const pos of POSITIONS) { - for (const type of ["ovr", "pot"]) { - ovrsPotsColNames.push(`rating:${type}${pos}`); - } - } - } - - const cols = getCols([ - "Name", - "Pos", - "Team", - "Age", - "Contract", - "Exp", - "Ovr", - "Pot", - ...ratings.map(rating => `rating:${rating}`), - ...ovrsPotsColNames, - ]); + const cols = config.columns; const rows = players.map(p => { - const showRatings = !challengeNoRatings || p.tid === PLAYER.RETIRED; - - const ovrsPotsRatings: string[] = []; - if (isSport("football") || isSport("hockey")) { - for (const pos of POSITIONS) { - for (const type of ["ovrs", "pots"]) { - ovrsPotsRatings.push(showRatings ? p.ratings[type][pos] : null); - } - } - } - return { key: p.pid, - data: [ - - {p.name} - , - p.ratings.pos, - - {p.stats.abbrev} - , - p.age, - p.contract.amount > 0 - ? helpers.formatCurrency(p.contract.amount, "M") - : null, - p.contract.amount > 0 && season === currentSeason - ? p.contract.exp - : null, - showRatings ? p.ratings.ovr : null, - showRatings ? p.ratings.pot : null, - ...ratings.map(rating => (showRatings ? p.ratings[rating] : null)), - ...ovrsPotsRatings, - ], - classNames: { - "table-danger": p.hof, - "table-info": p.stats.tid === userTid, - }, + data: Object.fromEntries( + cols.map(col => [col.key, getTemplate(p, col, config)]), + ), }; }); return ( - <> +
{challengeNoRatings ? ( @@ -109,7 +44,7 @@ const PlayerRatings = ({ ) : null}

- Players on your team are{" "} + Players on your team are highlighted in blue. Players in the Hall of Fame are highlighted in red . @@ -117,13 +52,23 @@ const PlayerRatings = ({ - +

); }; +PlayerRatings.propTypes = { + abbrev: PropTypes.string.isRequired, + currentSeason: PropTypes.number.isRequired, + players: PropTypes.arrayOf(PropTypes.object).isRequired, + config: PropTypes.object.isRequired, + season: PropTypes.number.isRequired, + userTid: PropTypes.number.isRequired, +}; + export default PlayerRatings; diff --git a/src/ui/views/PlayerStats.tsx b/src/ui/views/PlayerStats.tsx index 3ebea99545..6432f4b7d6 100644 --- a/src/ui/views/PlayerStats.tsx +++ b/src/ui/views/PlayerStats.tsx @@ -1,9 +1,10 @@ -import { DataTable, MoreLinks, PlayerNameLabels } from "../components"; +import PropTypes from "prop-types"; +import { DataTable, MoreLinks } from "../components"; import useTitleBar from "../hooks/useTitleBar"; -import { getCols, helpers } from "../util"; -import type { View } from "../../common/types"; +import { helpers } from "../util"; +import type { SortOrder, View } from "../../common/types"; import { isSport } from "../../common"; -import { wrappedAgeAtDeath } from "../components/AgeAtDeath"; +import getTemplate from "../util/columns/getTemplate"; export const formatStatGameHigh = ( ps: any, @@ -47,7 +48,7 @@ const PlayerStats = ({ playoffs, season, statType, - stats, + config, superCols, userTid, }: View<"playerStats">) => { @@ -64,112 +65,44 @@ const PlayerStats = ({ }, }); - const cols = getCols([ - "Name", - "Pos", - "Age", - "Team", - ...(season === "all" ? ["Season"] : []), - ...stats.map( - stat => `stat:${stat.endsWith("Max") ? stat.replace("Max", "") : stat}`, - ), - ]); + const cols = config.columns; - if (statType === "shotLocations") { - cols[cols.length - 7].title = "M"; - cols[cols.length - 6].title = "A"; - cols[cols.length - 5].title = "%"; - } - - let sortCol = cols.length - 1; + let sortCol: string = cols[0].key ?? "col1"; + let sortDir: SortOrder = "asc"; if (isSport("football")) { if (statType === "passing") { - sortCol = 9; + sortCol = "stat:passYds"; + sortDir = "desc"; } else if (statType === "rushing") { - sortCol = cols.length - 3; + sortCol = "stat:rusRecTD"; + sortDir = "desc"; } else if (statType === "defense") { - sortCol = 16; + sortCol = "stat:defSk"; + sortDir = "desc"; } else if (statType === "kicking") { - sortCol = cols.length - 11; + sortCol = "stat:fgPct"; + sortDir = "desc"; } else if (statType === "returns") { - sortCol = 12; + sortCol = "stat:krYds"; + sortDir = "desc"; } } const rows = players.map(p => { - let pos; - if (Array.isArray(p.ratings) && p.ratings.length > 0) { - pos = p.ratings.at(-1).pos; - } else if (p.ratings.pos) { - pos = p.ratings.pos; - } else { - pos = "?"; - } - - // HACKS to show right stats, info - let actualAbbrev; - let actualTid; if (season === "career") { p.stats = p.careerStats; - actualAbbrev = p.abbrev; - actualTid = p.tid; if (playoffs === "playoffs") { p.stats = p.careerStatsPlayoffs; } - } else { - actualAbbrev = p.stats.abbrev; - actualTid = p.stats.tid; } - - const statsRow = stats.map(stat => - formatStatGameHigh(p.stats, stat, statType), - ); - - const key = season === "all" ? `${p.pid}-${p.stats.season}` : p.pid; - return { - key, - data: [ - { - value: ( - - {p.nameAbbrev} - - ), - sortValue: p.name, - searchValue: p.name, - }, - pos, - - // Only show age at death for career totals, otherwise just use current age - season === "career" - ? wrappedAgeAtDeath(p.age, p.ageAtDeath) - : p.stats.season - p.born.year, - - - {actualAbbrev} - , - - ...(season === "all" ? [p.stats.season] : []), - - ...statsRow, - ], + key: season === "all" ? `${p.pid}-${p.stats.season}` : p.pid, + data: Object.fromEntries( + cols.map(col => [col.key, getTemplate(p, col, config)]), + ), classNames: { "table-danger": p.hof, - "table-info": actualTid === userTid, + "table-info": p.stats.tid === userTid || p.tid === userTid, }, }; }); @@ -193,7 +126,8 @@ const PlayerStats = ({ >[0]; @@ -16,7 +21,7 @@ type OfferProps = { otherDpids: number[], ) => Promise; i: number; - stats: string[]; + config: TableConfig; } & OfferType; const Offer = (props: OfferProps) => { @@ -34,7 +39,7 @@ const Offer = (props: OfferProps) => { pids, players, region, - stats, + config, strategy, tid, tied, @@ -44,38 +49,14 @@ const Offer = (props: OfferProps) => { let offerPlayers: ReactNode = null; if (players.length > 0) { - const cols = getCols([ - "Name", - "Pos", - "Age", - "Ovr", - "Pot", - "Contract", - "Exp", - ...stats.map(stat => `stat:${stat}`), - ]); + const cols = config.columns; const rows = players.map(p => { return { key: p.pid, - data: [ - - {p.name} - , - p.ratings.pos, - p.age, - !challengeNoRatings ? p.ratings.ovr : null, - !challengeNoRatings ? p.ratings.pot : null, - helpers.formatCurrency(p.contract.amount, "M"), - p.contract.exp, - ...stats.map(stat => helpers.roundStat(p.stats[stat], stat)), - ], + data: Object.fromEntries( + cols.map(col => [col.key, getTemplate(p, col, config)]), + ), }; }); @@ -83,7 +64,8 @@ const Offer = (props: OfferProps) => {
{ if (picks.length > 0) { offerPicks = (
-
+
@@ -148,6 +130,26 @@ const Offer = (props: OfferProps) => { ); }; +Offer.propTypes = { + abbrev: PropTypes.string.isRequired, + dpids: PropTypes.arrayOf(PropTypes.number).isRequired, + handleClickNegotiate: PropTypes.func.isRequired, + i: PropTypes.number.isRequired, + lost: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + payroll: PropTypes.number.isRequired, + picks: PropTypes.arrayOf(PropTypes.object).isRequired, + pids: PropTypes.arrayOf(PropTypes.number).isRequired, + players: PropTypes.arrayOf(PropTypes.object).isRequired, + region: PropTypes.string.isRequired, + config: PropTypes.object.isRequired, + strategy: PropTypes.string.isRequired, + tid: PropTypes.number.isRequired, + tied: PropTypes.number, + warning: PropTypes.string, + won: PropTypes.number.isRequired, +}; + const pickCols = getCols(["", "Draft Picks"], { "": { sortSequence: [], @@ -242,7 +244,7 @@ const TradingBlock = (props: View<"tradingBlock">) => { gameOver, spectator, phase, - stats, + config, userPicks, userRoster, } = props; @@ -282,54 +284,35 @@ const TradingBlock = (props: View<"tradingBlock">) => { ); } - const cols = getCols( - [ - "", - "Name", - "Pos", - "Age", - "Ovr", - "Pot", - "Contract", - "Exp", - ...stats.map(stat => `stat:${stat}`), - ], - { - "": { - sortSequence: [], - noSearch: true, - }, - }, - ); + const cols = [...config.columns]; + + const includeColumn: MetaCol = { + title: "", + key: "include", + sortSequence: [], + noSearch: true, + template: (p: Player, c: MetaCol, vars: object) => ( + handleChangeAsset("pids", p.pid)} + title={p.untradableMsg} + /> + ), + }; + + const includeIndex = cols.findIndex(col => col.key == "include"); + + if (includeIndex === -1) cols.unshift(includeColumn); + else cols[includeIndex] = includeColumn; const rows = userRoster.map(p => { return { key: p.pid, - data: [ - handleChangeAsset("pids", p.pid)} - title={p.untradableMsg} - />, - - {p.name} - , - p.ratings.pos, - p.age, - !challengeNoRatings ? p.ratings.ovr : null, - !challengeNoRatings ? p.ratings.pot : null, - helpers.formatCurrency(p.contract.amount, "M"), - p.contract.exp, - ...stats.map(stat => helpers.roundStat(p.stats[stat], stat)), - ], + data: Object.fromEntries( + cols.map(col => [col.key, getTemplate(p, col, config)]), + ), }; }); @@ -361,7 +344,8 @@ const TradingBlock = (props: View<"tradingBlock">) => {
@@ -369,7 +353,7 @@ const TradingBlock = (props: View<"tradingBlock">) => {
) => { return ( ); @@ -418,4 +402,11 @@ const TradingBlock = (props: View<"tradingBlock">) => { ); }; +TradingBlock.propTypes = { + gameOver: PropTypes.bool.isRequired, + phase: PropTypes.number.isRequired, + userPicks: PropTypes.arrayOf(PropTypes.object).isRequired, + userRoster: PropTypes.arrayOf(PropTypes.object).isRequired, +}; + export default TradingBlock; diff --git a/src/worker/api/index.ts b/src/worker/api/index.ts index 8967a52da5..301d36c923 100644 --- a/src/worker/api/index.ts +++ b/src/worker/api/index.ts @@ -3001,6 +3001,10 @@ const updateMultiTeamMode = async (gameAttributes: { await toUI("realtimeUpdate", [["g.userTids"]]); }; +const updateColumns = async (data: { key: string; columns: string[] }) => { + await idb.meta.put("tables", data.columns, data.key); +}; + const updateOptions = async ( options: Options & { realPlayerPhotos: string; @@ -3781,6 +3785,7 @@ export default { updateKeepRosterSorted, updateLeague, updateMultiTeamMode, + updateColumns, updateOptions, updatePlayThroughInjuries, updatePlayerWatch, diff --git a/src/worker/db/connectMeta.ts b/src/worker/db/connectMeta.ts index e5cd368697..0e7d39c1c8 100644 --- a/src/worker/db/connectMeta.ts +++ b/src/worker/db/connectMeta.ts @@ -34,6 +34,10 @@ export interface MetaDB extends DBSchema { | "realTeamInfo" | "defaultSettingsOverrides"; }; + tables: { + key: string; + value: string[]; + }; leagues: { value: League; key: number; @@ -42,6 +46,7 @@ export interface MetaDB extends DBSchema { } const create = (db: IDBPDatabase) => { + db.createObjectStore("tables"); db.createObjectStore("achievements", { keyPath: "aid", autoIncrement: true, diff --git a/src/worker/views/freeAgents.ts b/src/worker/views/freeAgents.ts index 25a464f236..b6405d1a59 100644 --- a/src/worker/views/freeAgents.ts +++ b/src/worker/views/freeAgents.ts @@ -3,6 +3,7 @@ import type { Player } from "../../common/types"; import { player, team } from "../core"; import { idb } from "../db"; import { g } from "../util"; +import { TableConfig } from "../../ui/util/TableConfig"; export const addMood = async (players: Player[]) => { const moods: Awaited>[] = []; @@ -27,25 +28,37 @@ const updateFreeAgents = async () => { await idb.cache.players.indexGetAll("playersByTid", PLAYER.FREE_AGENT), ); const capSpace = (g.get("salaryCap") - payroll) / 1000; + const stats = bySport({ - basketball: ["min", "pts", "trb", "ast", "per"], - football: ["gp", "keyStats", "av"], - hockey: ["gp", "keyStats", "ops", "dps", "ps"], + basketball: [ + "stat:gp", + "stat:min", + "stat:pts", + "stat:trb", + "stat:ast", + "stat:per", + ], + football: ["stat:gp", "stat:keyStats", "stat:av"], + hockey: ["stat:gp", "stat:keyStats", "stat:ops", "stat:dps", "stat:ps"], }); + const config: TableConfig = new TableConfig("freeAgents", [ + "Name", + "Pos", + "Age", + "Ovr", + "Pot", + ...stats, + "Mood", + "Asking For", + "Exp", + ]); + await config.load(); + const players = await idb.getCopies.playersPlus(playersAll, { - attrs: [ - "pid", - "name", - "age", - "contract", - "injury", - "watch", - "jerseyNumber", - "mood", - ], - ratings: ["ovr", "pot", "skills", "pos"], - stats, + attrs: config.attrsNeeded, + ratings: config.ratingsNeeded, + stats: config.statsNeeded, season: g.get("season"), showNoStats: true, showRookies: true, @@ -73,8 +86,8 @@ const updateFreeAgents = async () => { numRosterSpots: g.get("maxRosterSize") - userPlayers.length, spectator: g.get("spectator"), phase: g.get("phase"), + config, players, - stats, userPlayers, }; }; diff --git a/src/worker/views/playerRatings.ts b/src/worker/views/playerRatings.ts index 980a7fb6f2..791784fe04 100644 --- a/src/worker/views/playerRatings.ts +++ b/src/worker/views/playerRatings.ts @@ -1,7 +1,8 @@ -import { bySport, PHASE, PLAYER } from "../../common"; +import { bySport, isSport, PHASE, PLAYER, POSITIONS } from "../../common"; import { idb } from "../db"; import { g } from "../util"; import type { UpdateEvents, ViewInput } from "../../common/types"; +import { TableConfig } from "../../ui/util/TableConfig"; export const getPlayers = async ( season: number, @@ -10,6 +11,7 @@ export const getPlayers = async ( ratings: string[], stats: string[], tid: number | undefined, + config: TableConfig, ) => { let playersAll; @@ -31,20 +33,9 @@ export const getPlayers = async ( } let players = await idb.getCopies.playersPlus(playersAll, { - attrs: [ - "pid", - "name", - "age", - "contract", - "injury", - "hof", - "watch", - "tid", - "abbrev", - ...attrs, - ], - ratings: ["ovr", "pot", "skills", "pos", ...ratings], - stats: ["abbrev", "tid", "jerseyNumber", ...stats], + attrs: config.attrsNeeded, + ratings: config.ratingsNeeded, + stats: config.statsNeeded, season: season, showNoStats: true, showRookies: true, @@ -76,6 +67,7 @@ const updatePlayers = async ( state: any, ) => { if ( + updateEvents.includes("customizeTable") || (inputs.season === g.get("season") && updateEvents.includes("playerMovement")) || (updateEvents.includes("newPhase") && g.get("phase") === PHASE.PRESEASON) || @@ -146,6 +138,30 @@ const updatePlayers = async ( hockey: ["ovrs", "pots"], }); + const ovrsPotsColNames: string[] = []; + if (isSport("football") || isSport("hockey")) { + for (const pos of POSITIONS) { + for (const type of ["ovr", "pot"]) { + ovrsPotsColNames.push(`rating:${type}${pos}`); + } + } + } + + const config: TableConfig = new TableConfig("playerRatings", [ + "Name", + "Pos", + "Team", + "Age", + "Contract", + "Exp", + "Ovr", + "Pot", + ...ratings.map(rating => `rating:${rating}`), + ...ovrsPotsColNames, + ]); + await config.load(); + console.log(config); + const players = await getPlayers( inputs.season, inputs.abbrev, @@ -153,6 +169,7 @@ const updatePlayers = async ( [...ratings, ...extraRatings], [], inputs.tid, + config, ); return { @@ -161,7 +178,7 @@ const updatePlayers = async ( currentSeason: g.get("season"), season: inputs.season, players, - ratings, + config, userTid: g.get("userTid"), }; } diff --git a/src/worker/views/playerStats.ts b/src/worker/views/playerStats.ts index 8feaea2018..20c51cae0c 100644 --- a/src/worker/views/playerStats.ts +++ b/src/worker/views/playerStats.ts @@ -6,6 +6,7 @@ import type { ViewInput, PlayerStatType, } from "../../common/types"; +import { TableConfig } from "../../ui/util/TableConfig"; const updatePlayers = async ( inputs: ViewInput<"playerStats">, @@ -13,6 +14,7 @@ const updatePlayers = async ( state: any, ) => { if ( + updateEvents.includes("customizeTable") || (inputs.season === g.get("season") && (updateEvents.includes("gameSim") || updateEvents.includes("playerMovement"))) || @@ -42,7 +44,6 @@ const updatePlayers = async ( throw new Error(`Invalid statType: "${inputs.statType}"`); } - const stats = statsTable.stats; let playersAll; if (g.get("season") === inputs.season && g.get("phase") <= PHASE.PLAYOFFS) { @@ -87,22 +88,16 @@ const updatePlayers = async ( playersAll = playersAll.filter(p => p.watch); } + const config: TableConfig = new TableConfig( + "playerStats." + inputs.statType, + ["Name", "Pos", "Team", "Age", ...statsTable.stats.map(s => `stat:${s}`)], + ); + await config.load(); + let players = await idb.getCopies.playersPlus(playersAll, { - attrs: [ - "pid", - "nameAbbrev", - "name", - "age", - "born", - "ageAtDeath", - "injury", - "tid", - "abbrev", - "hof", - "watch", - ], - ratings: ["skills", "pos", "season"], - stats: ["abbrev", "tid", "jerseyNumber", "season", ...stats], + attrs: config.attrsNeeded, + ratings: config.ratingsNeeded, + stats: config.statsNeeded, season: typeof inputs.season === "number" ? inputs.season : undefined, tid, statType, @@ -187,7 +182,7 @@ const updatePlayers = async ( season: inputs.season, statType: inputs.statType, playoffs: inputs.playoffs, - stats, + config, superCols: statsTable.superCols, userTid: g.get("userTid"), }; diff --git a/src/worker/views/tradingBlock.ts b/src/worker/views/tradingBlock.ts index ee9d58786d..2e2f55946e 100644 --- a/src/worker/views/tradingBlock.ts +++ b/src/worker/views/tradingBlock.ts @@ -2,41 +2,53 @@ import { idb } from "../db"; import { g, helpers } from "../util"; import type { UpdateEvents, ViewInput } from "../../common/types"; import { bySport } from "../../common"; +import { TableConfig } from "../../ui/util/TableConfig"; const updateUserRoster = async ( inputs: ViewInput<"tradingBlock">, updateEvents: UpdateEvents, ) => { if ( + updateEvents.includes("customizeTable") || updateEvents.includes("firstRun") || updateEvents.includes("playerMovement") || updateEvents.includes("gameSim") || updateEvents.includes("newPhase") ) { const stats = bySport({ - basketball: ["gp", "min", "pts", "trb", "ast", "per"], - football: ["gp", "keyStats", "av"], - hockey: ["gp", "keyStats", "ops", "dps", "ps"], + basketball: [ + "stat:gp", + "stat:min", + "stat:pts", + "stat:trb", + "stat:ast", + "stat:per", + ], + football: ["stat:gp", "stat:keyStats", "stat:av"], + hockey: ["stat:gp", "stat:keyStats", "stat:ops", "stat:dps", "stat:ps"], }); const userRosterAll = await idb.cache.players.indexGetAll( "playersByTid", g.get("userTid"), ); + + const config: TableConfig = new TableConfig("tradingBlock", [ + "Name", + "Pos", + "Age", + "Ovr", + "Pot", + "Contract", + "Exp", + ...stats, + ]); + await config.load(); const userRoster = await idb.getCopies.playersPlus(userRosterAll, { - attrs: [ - "pid", - "name", - "age", - "contract", - "injury", - "watch", - "untradable", - "jerseyNumber", - ], - ratings: ["ovr", "pot", "skills", "pos"], - stats, - season: g.get("season"), + attrs: [...config.attrsNeeded, "untradable", "pid"], + ratings: config.ratingsNeeded, + stats: config.statsNeeded, tid: g.get("userTid"), + season: g.get("season"), showNoStats: true, showRookies: true, fuzz: true, @@ -55,7 +67,6 @@ const updateUserRoster = async ( desc: helpers.pickDesc(dp), }; }); - return { challengeNoRatings: g.get("challengeNoRatings"), challengeNoTrades: g.get("challengeNoTrades"), @@ -63,7 +74,7 @@ const updateUserRoster = async ( initialPid: inputs.pid, spectator: g.get("spectator"), phase: g.get("phase"), - stats, + config, userPicks: userPicks2, userRoster, }; From 8f0f101fb61bc3ce1f5ee9dc4773b2405f00b03a Mon Sep 17 00:00:00 2001 From: Vac1911 Date: Fri, 21 Jan 2022 16:58:42 -0500 Subject: [PATCH 02/15] TradingBlock.tsx fix --- src/ui/util/TableConfig.ts | 2 +- src/ui/views/FreeAgents.tsx | 3 +-- src/ui/views/TradingBlock.tsx | 46 +++++++++++++++++------------------ 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/ui/util/TableConfig.ts b/src/ui/util/TableConfig.ts index 7b4d40b2fa..eef29ffb1f 100644 --- a/src/ui/util/TableConfig.ts +++ b/src/ui/util/TableConfig.ts @@ -37,7 +37,7 @@ export class TableConfig { addColumn(column: MetaCol, pos: number) { const colIndex = this.columns.findIndex(c => c.key === column.key); - if (colIndex) { + if (colIndex !== -1) { Object.assign(this.columns[colIndex], column); } else { this.columns.splice(pos, 0, column); diff --git a/src/ui/views/FreeAgents.tsx b/src/ui/views/FreeAgents.tsx index 83d3fdbcfa..2d702ca7d6 100644 --- a/src/ui/views/FreeAgents.tsx +++ b/src/ui/views/FreeAgents.tsx @@ -12,8 +12,7 @@ import useTitleBar from "../hooks/useTitleBar"; import { confirm, toWorker, useLocalShallow } from "../util"; import type { View } from "../../common/types"; import getTemplate from "../util/columns/getTemplate"; -import { Player } from "../../common/types"; -import { MetaCol } from "../util/columns/getCols"; +import type { Player } from "../../common/types"; import { TableConfig } from "../util/TableConfig"; const FreeAgents = ({ diff --git a/src/ui/views/TradingBlock.tsx b/src/ui/views/TradingBlock.tsx index 91925c0f3c..e3b0103b88 100644 --- a/src/ui/views/TradingBlock.tsx +++ b/src/ui/views/TradingBlock.tsx @@ -8,8 +8,8 @@ import type { View } from "../../common/types"; import type { Player } from "../../common/types"; import type api from "../../worker/api"; import getTemplate from "../util/columns/getTemplate"; -import type { ColTemp, MetaCol } from "../util/columns/getCols"; -import type { TableConfig } from "../util/TableConfig"; +import type { MetaCol } from "../util/columns/getCols"; +import { TableConfig } from "../util/TableConfig"; type OfferType = Awaited>[0]; @@ -244,7 +244,7 @@ const TradingBlock = (props: View<"tradingBlock">) => { gameOver, spectator, phase, - config, + config: _config, userPicks, userRoster, } = props; @@ -284,28 +284,28 @@ const TradingBlock = (props: View<"tradingBlock">) => { ); } - const cols = [...config.columns]; - - const includeColumn: MetaCol = { - title: "", - key: "include", - sortSequence: [], - noSearch: true, - template: (p: Player, c: MetaCol, vars: object) => ( - handleChangeAsset("pids", p.pid)} - title={p.untradableMsg} - /> - ), - }; + const config = TableConfig.unserialize(_config); - const includeIndex = cols.findIndex(col => col.key == "include"); + config.addColumn( + { + title: "", + key: "include", + sortSequence: [], + noSearch: true, + template: (p: Player, c: MetaCol, vars: object) => ( + handleChangeAsset("pids", p.pid)} + title={p.untradableMsg} + /> + ), + }, + 0, + ); - if (includeIndex === -1) cols.unshift(includeColumn); - else cols[includeIndex] = includeColumn; + const cols = [...config.columns]; const rows = userRoster.map(p => { return { From f390acca5b18e3eb160dfcaaff68c6b6a8fc8c77 Mon Sep 17 00:00:00 2001 From: Vac1911 Date: Mon, 24 Jan 2022 14:39:49 -0500 Subject: [PATCH 03/15] Improved Table Customization --- public/css/dark.scss | 3 +- public/css/light.scss | 3 +- src/ui/components/DataTable/Header.tsx | 15 +- src/ui/components/DataTable/index.tsx | 30 ++-- src/ui/util/TableConfig.ts | 18 ++- src/ui/util/columns/getCols.ts | 15 +- src/ui/util/columns/templates/Age.tsx | 4 +- src/ui/util/columns/templates/AskingFor.tsx | 4 +- src/ui/util/columns/templates/Attr.tsx | 4 +- src/ui/util/columns/templates/College.tsx | 4 +- src/ui/util/columns/templates/Contract.tsx | 7 +- src/ui/util/columns/templates/Country.tsx | 4 +- src/ui/util/columns/templates/DraftYear.tsx | 4 +- src/ui/util/columns/templates/Exp.tsx | 4 +- src/ui/util/columns/templates/Experience.tsx | 4 +- src/ui/util/columns/templates/Height.tsx | 4 +- .../util/columns/templates/InjuryLength.tsx | 4 +- src/ui/util/columns/templates/InjuryType.tsx | 4 +- src/ui/util/columns/templates/Mood.tsx | 4 +- src/ui/util/columns/templates/MoodCurrent.tsx | 4 +- src/ui/util/columns/templates/Name.tsx | 4 +- src/ui/util/columns/templates/Negotiate.tsx | 4 +- src/ui/util/columns/templates/Ovr.tsx | 4 +- src/ui/util/columns/templates/Pick.tsx | 4 +- src/ui/util/columns/templates/Pos.tsx | 4 +- src/ui/util/columns/templates/Pot.tsx | 4 +- src/ui/util/columns/templates/Projected.tsx | 4 +- src/ui/util/columns/templates/Rating.tsx | 4 +- src/ui/util/columns/templates/Stat.tsx | 4 +- src/ui/util/columns/templates/Team.tsx | 4 +- src/ui/util/columns/templates/Trade.tsx | 4 +- src/ui/util/columns/templates/Weight.tsx | 4 +- .../util/columns/templates/YearsWithTeam.tsx | 4 +- src/ui/views/FreeAgents.tsx | 11 +- src/ui/views/PlayerBios.tsx | 128 ++---------------- src/ui/views/Trade/AssetList.tsx | 128 ++++++++---------- src/ui/views/Trade/index.tsx | 16 +-- src/worker/api/index.ts | 39 ++++-- src/worker/views/playerBios.ts | 34 ++++- src/worker/views/playerRatings.ts | 1 - src/worker/views/roster.ts | 1 + src/worker/views/teamStats.ts | 1 + src/worker/views/trade.ts | 54 +++++--- 43 files changed, 274 insertions(+), 334 deletions(-) diff --git a/public/css/dark.scss b/public/css/dark.scss index 4cfd141d6a..3a4ac59cbb 100644 --- a/public/css/dark.scss +++ b/public/css/dark.scss @@ -327,7 +327,6 @@ table.table thead .sorting::after { } } -.sorting_asc, -.sorting_desc { +.sorted { background-color: #5f583c !important; } diff --git a/public/css/light.scss b/public/css/light.scss index a121690170..404e3b7775 100644 --- a/public/css/light.scss +++ b/public/css/light.scss @@ -1359,8 +1359,7 @@ code { transition: none !important; } -.sorting_asc, -.sorting_desc { +.sorted { background-color: #ffeeba !important; } diff --git a/src/ui/components/DataTable/Header.tsx b/src/ui/components/DataTable/Header.tsx index 3ee5258e31..2f262399e8 100644 --- a/src/ui/components/DataTable/Header.tsx +++ b/src/ui/components/DataTable/Header.tsx @@ -112,6 +112,7 @@ const SortableColumn = SortableElement( className={classNames(props.col.classNames, { sorted: props.sortBy, })} + style={{ width: props.col.width }} >
diff --git a/src/ui/components/DataTable/index.tsx b/src/ui/components/DataTable/index.tsx index cda794901c..f8e1ac496a 100644 --- a/src/ui/components/DataTable/index.tsx +++ b/src/ui/components/DataTable/index.tsx @@ -106,7 +106,7 @@ export type Props = { small?: boolean; striped?: boolean; superCols?: SuperCol[]; - addFilters?: (string | undefined)[]; + addFilters?: Filter[]; }; export type LegacyProps = Omit & { @@ -270,7 +270,7 @@ const DataTable = (props: Props | LegacyProps) => { state.sortBys.map(sortBy => row => { const key = sortBy[0]; const col = state.cols.find(c => c.key === key); - return getSortVal(row.data[key], col?.sortType); + if (key in row.data) return getSortVal(row.data[key], col?.sortType); }), state.sortBys.map(sortBy => sortBy[1]), ); @@ -288,7 +288,7 @@ const DataTable = (props: Props | LegacyProps) => { columns: nextCols.map(c => c.key), key: props.config.tableName, }); - // await realtimeUpdate(["customizeTable"]); + await realtimeUpdate(["customizeTable"]); } }; @@ -426,22 +426,26 @@ const DataTable = (props: Props | LegacyProps) => { } useEffect(() => { - if ( - addFilters !== undefined && - addFilters.length === state.filters.length - ) { + if (addFilters !== undefined) { // If addFilters is passed and contains a value, merge with prevState.filters and enable filters const filters = helpers.deepCopy(state.filters); let changed = false; for (let i = 0; i < addFilters.length; i++) { - const filter = addFilters[i]; - if (filter !== undefined) { - filters[i] = filter; + const { col, value } = addFilters[i]; + const filterIndex = filters.findIndex(f => col === f.col); + + if (filterIndex !== -1) { + if (filters[filterIndex].value != value) { + changed = true; + filters[filterIndex].value = value; + } + } else { changed = true; - } else if (!state.enableFilters) { - // If there is a saved but hidden filter, remove it - filters[i] = ""; + filters.push({ + col: col, + value: value, + }); } } diff --git a/src/ui/util/TableConfig.ts b/src/ui/util/TableConfig.ts index eef29ffb1f..1cff782e0c 100644 --- a/src/ui/util/TableConfig.ts +++ b/src/ui/util/TableConfig.ts @@ -1,6 +1,6 @@ import { idb } from "../../worker/db"; import getCols, { MetaCol } from "./columns/getCols"; -import { uniq } from "lodash-es"; +import { cloneDeep, uniq } from "lodash-es"; import { g } from "../../worker/util"; export class TableConfig { @@ -44,12 +44,20 @@ export class TableConfig { } } + updateColumn(column: Partial, key: string) { + const colIndex = this.columns.findIndex(c => c.key === key); + if (colIndex !== -1) { + Object.assign(this.columns[colIndex], column); + } + } + static unserialize(_config: TableConfig) { + const serialized = cloneDeep(_config); return new TableConfig( - _config.tableName, - _config.fallback, - _config.columns, - _config.vars, + serialized.tableName, + serialized.fallback, + serialized.columns, + serialized.vars, ); } diff --git a/src/ui/util/columns/getCols.ts b/src/ui/util/columns/getCols.ts index afc1d39333..5715c9c4a7 100644 --- a/src/ui/util/columns/getCols.ts +++ b/src/ui/util/columns/getCols.ts @@ -18,7 +18,7 @@ export type MetaCol = Col & { attrs?: string[]; template: | string - | ((p: Player, c: ColTemp, vars: object) => JSX.Element | string); + | ((p: Player, c: MetaCol, vars: object) => JSX.Element | string); options?: { [key: string]: any }; }; @@ -2879,6 +2879,16 @@ const cols: { sortSequence: ["desc", "asc"], sortType: "currency", template: "Contract", + options: { format: "compact" }, + }, + FullContract: { + title: "Full Contract", + attrs: ["contract"], + cat: "General", + sortSequence: ["desc", "asc"], + sortType: "currency", + template: "Contract", + options: { format: "full" }, }, Country: { attrs: ["born"], @@ -2997,9 +3007,10 @@ const cols: { template: "Pick", }, Team: { + attrs: ["abbrev", "tid"], cat: "General", sortType: "string", - stats: ["abbrev"], + stats: ["abbrev", "tid"], template: "Team", }, Weight: { diff --git a/src/ui/util/columns/templates/Age.tsx b/src/ui/util/columns/templates/Age.tsx index 1b85bed0ac..a57256cfd1 100644 --- a/src/ui/util/columns/templates/Age.tsx +++ b/src/ui/util/columns/templates/Age.tsx @@ -1,4 +1,4 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => p.age; +export default (p: Player, c: MetaCol) => p.age; diff --git a/src/ui/util/columns/templates/AskingFor.tsx b/src/ui/util/columns/templates/AskingFor.tsx index 52401c3b8e..a7c9bfe15f 100644 --- a/src/ui/util/columns/templates/AskingFor.tsx +++ b/src/ui/util/columns/templates/AskingFor.tsx @@ -1,6 +1,6 @@ import type { Player } from "../../../../common/types"; import { helpers } from "../../index"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => +export default (p: Player, c: MetaCol) => helpers.formatCurrency(p.mood.user.contractAmount / 1000, "M"); diff --git a/src/ui/util/columns/templates/Attr.tsx b/src/ui/util/columns/templates/Attr.tsx index d5642b8417..a0414a7a03 100644 --- a/src/ui/util/columns/templates/Attr.tsx +++ b/src/ui/util/columns/templates/Attr.tsx @@ -1,7 +1,7 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => { +export default (p: Player, c: MetaCol) => { const key: string = c.attrs[0]; if (!(key in p)) return `${key} not found`; return p[key]; diff --git a/src/ui/util/columns/templates/College.tsx b/src/ui/util/columns/templates/College.tsx index 3b39a9495f..33f145dab8 100644 --- a/src/ui/util/columns/templates/College.tsx +++ b/src/ui/util/columns/templates/College.tsx @@ -1,8 +1,8 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; import { helpers } from "../../index"; -export default (p: Player, c: ColTemp) => { +export default (p: Player, c: MetaCol) => { const college = p.college && p.college !== "" ? p.college : "None"; return ( - `${helpers.formatCurrency(p.contract.amount, "M")} thru ${p.contract.exp}`; +export default (p: Player, c: MetaCol) => + helpers.formatCurrency(p.contract.amount, "M") + + (c.options?.format == "full" ? ` thru ${p.contract.exp}` : ""); diff --git a/src/ui/util/columns/templates/Country.tsx b/src/ui/util/columns/templates/Country.tsx index 27830e3b32..31c059a4e8 100644 --- a/src/ui/util/columns/templates/Country.tsx +++ b/src/ui/util/columns/templates/Country.tsx @@ -1,9 +1,9 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; import { helpers } from "../../index"; import { CountryFlag } from "../../../components"; -export default (p: Player, c: ColTemp) => ( +export default (p: Player, c: MetaCol) => ( <> p.draft.year; +export default (p: Player, c: MetaCol) => p.draft.year; diff --git a/src/ui/util/columns/templates/Exp.tsx b/src/ui/util/columns/templates/Exp.tsx index f11e639f75..ed4fa4b35c 100644 --- a/src/ui/util/columns/templates/Exp.tsx +++ b/src/ui/util/columns/templates/Exp.tsx @@ -1,4 +1,4 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => p.contract.exp.toString(); +export default (p: Player, c: MetaCol) => p.contract.exp.toString(); diff --git a/src/ui/util/columns/templates/Experience.tsx b/src/ui/util/columns/templates/Experience.tsx index 9c42d12dfa..7855db5acd 100644 --- a/src/ui/util/columns/templates/Experience.tsx +++ b/src/ui/util/columns/templates/Experience.tsx @@ -1,4 +1,4 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => p.experience.toString(); +export default (p: Player, c: MetaCol) => p.experience.toString(); diff --git a/src/ui/util/columns/templates/Height.tsx b/src/ui/util/columns/templates/Height.tsx index d269e95cda..134faeae87 100644 --- a/src/ui/util/columns/templates/Height.tsx +++ b/src/ui/util/columns/templates/Height.tsx @@ -1,5 +1,5 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; import { wrappedHeight } from "../../../components/Height"; -export default (p: Player, c: ColTemp) => wrappedHeight(p.hgt); +export default (p: Player, c: MetaCol) => wrappedHeight(p.hgt); diff --git a/src/ui/util/columns/templates/InjuryLength.tsx b/src/ui/util/columns/templates/InjuryLength.tsx index 6ee7b44b2f..41efe80580 100644 --- a/src/ui/util/columns/templates/InjuryLength.tsx +++ b/src/ui/util/columns/templates/InjuryLength.tsx @@ -1,4 +1,4 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => p.injury.gamesRemaining; +export default (p: Player, c: MetaCol) => p.injury.gamesRemaining; diff --git a/src/ui/util/columns/templates/InjuryType.tsx b/src/ui/util/columns/templates/InjuryType.tsx index 4eeeb68173..d8c1492de1 100644 --- a/src/ui/util/columns/templates/InjuryType.tsx +++ b/src/ui/util/columns/templates/InjuryType.tsx @@ -1,4 +1,4 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => p.injury.type; +export default (p: Player, c: MetaCol) => p.injury.type; diff --git a/src/ui/util/columns/templates/Mood.tsx b/src/ui/util/columns/templates/Mood.tsx index 65ae3fe858..6452ef8b45 100644 --- a/src/ui/util/columns/templates/Mood.tsx +++ b/src/ui/util/columns/templates/Mood.tsx @@ -1,6 +1,6 @@ import type { Player } from "../../../../common/types"; import { dataTableWrappedMood } from "../../../components/Mood"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => +export default (p: Player, c: MetaCol) => dataTableWrappedMood({ defaultType: "user", maxWidth: true, p }); diff --git a/src/ui/util/columns/templates/MoodCurrent.tsx b/src/ui/util/columns/templates/MoodCurrent.tsx index 0bb241565e..f5ef8486ad 100644 --- a/src/ui/util/columns/templates/MoodCurrent.tsx +++ b/src/ui/util/columns/templates/MoodCurrent.tsx @@ -1,6 +1,6 @@ import type { Player } from "../../../../common/types"; import { dataTableWrappedMood } from "../../../components/Mood"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => +export default (p: Player, c: MetaCol) => dataTableWrappedMood({ defaultType: "current", maxWidth: true, p }); diff --git a/src/ui/util/columns/templates/Name.tsx b/src/ui/util/columns/templates/Name.tsx index d4be25ffd8..c2d8376805 100644 --- a/src/ui/util/columns/templates/Name.tsx +++ b/src/ui/util/columns/templates/Name.tsx @@ -1,8 +1,8 @@ import type { Player } from "../../../../common/types"; import { PlayerNameLabels } from "../../../components"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => ( +export default (p: Player, c: MetaCol) => ( ( +export default (p: Player, c: MetaCol, vars: object) => ( { +export default (p: Player, c: MetaCol, vars: object) => { if (vars["challengeNoRatings"]) return ""; else if (p.ratings["dovr"]) return ( diff --git a/src/ui/util/columns/templates/Pick.tsx b/src/ui/util/columns/templates/Pick.tsx index 2c04c726df..b028fd4372 100644 --- a/src/ui/util/columns/templates/Pick.tsx +++ b/src/ui/util/columns/templates/Pick.tsx @@ -1,5 +1,5 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => +export default (p: Player, c: MetaCol) => p.draft.round > 0 ? `${p.draft.round}-${p.draft.pick}` : null; diff --git a/src/ui/util/columns/templates/Pos.tsx b/src/ui/util/columns/templates/Pos.tsx index bd12a61d89..0721d2eb33 100644 --- a/src/ui/util/columns/templates/Pos.tsx +++ b/src/ui/util/columns/templates/Pos.tsx @@ -1,4 +1,4 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => p.ratings.pos; +export default (p: Player, c: MetaCol) => p.ratings.pos; diff --git a/src/ui/util/columns/templates/Pot.tsx b/src/ui/util/columns/templates/Pot.tsx index fe5e45c346..0dcc3426b0 100644 --- a/src/ui/util/columns/templates/Pot.tsx +++ b/src/ui/util/columns/templates/Pot.tsx @@ -1,8 +1,8 @@ import type { Player } from "../../../../common/types"; import { RatingWithChange } from "../../../components"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp, vars: object) => { +export default (p: Player, c: MetaCol, vars: object) => { if (vars["challengeNoRatings"]) return ""; else if (p.ratings["dpot"]) return ( diff --git a/src/ui/util/columns/templates/Projected.tsx b/src/ui/util/columns/templates/Projected.tsx index e2c545f7a8..253ff7d734 100644 --- a/src/ui/util/columns/templates/Projected.tsx +++ b/src/ui/util/columns/templates/Projected.tsx @@ -1,6 +1,6 @@ import type { Player } from "../../../../common/types"; import { helpers } from "../../index"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => +export default (p: Player, c: MetaCol) => `${helpers.formatCurrency(p.contractDesired.amount, "M")}`; diff --git a/src/ui/util/columns/templates/Rating.tsx b/src/ui/util/columns/templates/Rating.tsx index 7bafdb57b1..83d4596011 100644 --- a/src/ui/util/columns/templates/Rating.tsx +++ b/src/ui/util/columns/templates/Rating.tsx @@ -1,7 +1,7 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp, vars: object) => { +export default (p: Player, c: MetaCol, vars: object) => { if (vars["challengeNoRatings"]) return ""; const key = c.ratings[0] ?? false; return key && key in p.ratings ? p.ratings[key].toString() : ""; diff --git a/src/ui/util/columns/templates/Stat.tsx b/src/ui/util/columns/templates/Stat.tsx index 5b9433a616..38563fdb70 100644 --- a/src/ui/util/columns/templates/Stat.tsx +++ b/src/ui/util/columns/templates/Stat.tsx @@ -1,7 +1,7 @@ import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => { +export default (p: Player, c: MetaCol) => { const key: string = c.stats[0]; if (!(key in p.stats)) return `${key} not found`; const value = p.stats[key]; diff --git a/src/ui/util/columns/templates/Team.tsx b/src/ui/util/columns/templates/Team.tsx index 23128f4952..8248438d9c 100644 --- a/src/ui/util/columns/templates/Team.tsx +++ b/src/ui/util/columns/templates/Team.tsx @@ -1,8 +1,8 @@ import { helpers } from "../../index"; import type { Player } from "../../../../common/types"; -import type { ColTemp } from "../getCols"; +import type { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp) => { +export default (p: Player, c: MetaCol) => { return ( {p.stats.abbrev} diff --git a/src/ui/util/columns/templates/Trade.tsx b/src/ui/util/columns/templates/Trade.tsx index 59d4dd53ec..a04198012f 100644 --- a/src/ui/util/columns/templates/Trade.tsx +++ b/src/ui/util/columns/templates/Trade.tsx @@ -1,8 +1,8 @@ import type { Player } from "../../../../common/types"; import { NegotiateButtons } from "../../../components"; -import { ColTemp } from "../getCols"; +import { MetaCol } from "../getCols"; -export default (p: Player, c: ColTemp, vars: object) => ( +export default (p: Player, c: MetaCol, vars: object) => (

{userTeamName}

diff --git a/src/worker/api/index.ts b/src/worker/api/index.ts index 301d36c923..33c6c827a9 100644 --- a/src/worker/api/index.ts +++ b/src/worker/api/index.ts @@ -110,6 +110,7 @@ import type { PlayerRatings } from "../../common/types.basketball"; import createStreamFromLeagueObject from "../core/league/create/createStreamFromLeagueObject"; import type { IDBPIndex, IDBPObjectStore } from "idb"; import type { LeagueDB } from "../db/connectLeague"; +import { TableConfig } from "../../ui/util/TableConfig"; const acceptContractNegotiation = async ( pid: number, @@ -1554,10 +1555,28 @@ const getTradingBlockOffers = async (pids: number[], dpids: number[]) => { "noCopyCache", ); const stats = bySport({ - basketball: ["gp", "min", "pts", "trb", "ast", "per"], - football: ["gp", "keyStats", "av"], - hockey: ["gp", "keyStats", "ops", "dps", "ps"], + basketball: [ + "stat:gp", + "stat:min", + "stat:pts", + "stat:trb", + "stat:ast", + "stat:per", + ], + football: ["stat:gp", "stat:keyStats", "stat:av"], + hockey: ["stat:gp", "stat:keyStats", "stat:ops", "stat:dps", "stat:ps"], }); + const config: TableConfig = new TableConfig("tradingBlock", [ + "Name", + "Pos", + "Age", + "Ovr", + "Pot", + "Contract", + "Exp", + ...stats, + ]); + await config.load(); // Take the pids and dpids in each offer and get the info needed to display the offer return Promise.all( @@ -1574,17 +1593,9 @@ const getTradingBlockOffers = async (pids: number[], dpids: number[]) => { ); playersAll = playersAll.filter(p => offer.pids.includes(p.pid)); const players = await idb.getCopies.playersPlus(playersAll, { - attrs: [ - "pid", - "name", - "age", - "contract", - "injury", - "watch", - "jerseyNumber", - ], - ratings: ["ovr", "pot", "skills", "pos"], - stats, + attrs: config.attrsNeeded, + ratings: config.ratingsNeeded, + stats: config.statsNeeded, season: g.get("season"), tid, showNoStats: true, diff --git a/src/worker/views/playerBios.ts b/src/worker/views/playerBios.ts index 204b3755b1..a74c03f360 100644 --- a/src/worker/views/playerBios.ts +++ b/src/worker/views/playerBios.ts @@ -4,6 +4,7 @@ import type { UpdateEvents, ViewInput } from "../../common/types"; import { getPlayers } from "./playerRatings"; import { player } from "../core"; import { idb } from "../db"; +import { TableConfig } from "../../ui/util/TableConfig"; const updatePlayers = async ( inputs: ViewInput<"playerBios">, @@ -11,6 +12,7 @@ const updatePlayers = async ( state: any, ) => { if ( + updateEvents.includes("customizeTable") || (inputs.season === g.get("season") && (updateEvents.includes("gameSim") || updateEvents.includes("playerMovement"))) || @@ -24,15 +26,36 @@ const updatePlayers = async ( hockey: ["keyStats"], }); + const config: TableConfig = new TableConfig("playerBios", [ + "Name", + "Pos", + "stat:jerseyNumber", + "Team", + "Age", + "Height", + "Weight", + "Mood", + "Contract", + "Country", + "College", + "DraftYear", + "Pick", + "Experience", + "Ovr", + "Pot", + ...stats.map(s => `stat:${s}`), + ]); + await config.load(); + const players = await getPlayers( inputs.season, inputs.abbrev, - ["born", "college", "hgt", "weight", "draft", "experience"], - ["ovr", "pot"], - [...stats, "jerseyNumber"], + config.attrsNeeded, + config.ratingsNeeded, + config.statsNeeded, inputs.tid, + config, ); - const userTid = g.get("userTid"); for (const p of players) { @@ -46,11 +69,10 @@ const updatePlayers = async ( return { abbrev: inputs.abbrev, - challengeNoRatings: g.get("challengeNoRatings"), + config: config, currentSeason: g.get("season"), season: inputs.season, players, - stats, userTid, }; } diff --git a/src/worker/views/playerRatings.ts b/src/worker/views/playerRatings.ts index 791784fe04..b72cc996c4 100644 --- a/src/worker/views/playerRatings.ts +++ b/src/worker/views/playerRatings.ts @@ -160,7 +160,6 @@ const updatePlayers = async ( ...ovrsPotsColNames, ]); await config.load(); - console.log(config); const players = await getPlayers( inputs.season, diff --git a/src/worker/views/roster.ts b/src/worker/views/roster.ts index 24325dbd1a..d4b411a555 100644 --- a/src/worker/views/roster.ts +++ b/src/worker/views/roster.ts @@ -25,6 +25,7 @@ const updateRoster = async ( state: any, ) => { if ( + updateEvents.includes("customizeTable") || updateEvents.includes("watchList") || updateEvents.includes("gameAttributes") || updateEvents.includes("playerMovement") || diff --git a/src/worker/views/teamStats.ts b/src/worker/views/teamStats.ts index 31d8b3170e..da17d0a6e8 100644 --- a/src/worker/views/teamStats.ts +++ b/src/worker/views/teamStats.ts @@ -153,6 +153,7 @@ const updateTeams = async ( state: any, ) => { if ( + updateEvents.includes("customizeTable") || (inputs.season === g.get("season") && (updateEvents.includes("gameSim") || updateEvents.includes("playerMovement"))) || diff --git a/src/worker/views/trade.ts b/src/worker/views/trade.ts index 1390aafa0b..75be123da8 100644 --- a/src/worker/views/trade.ts +++ b/src/worker/views/trade.ts @@ -4,6 +4,7 @@ import { team, trade } from "../core"; import { idb } from "../db"; import { g, helpers } from "../util"; // This relies on vars being populated, so it can't be called in parallel with updateTrade import type { TradeTeams } from "../../common/types"; +import { TableConfig } from "../../ui/util/TableConfig"; const getSummary = async (teams: TradeTeams) => { const summary = await trade.summary(teams); @@ -89,26 +90,36 @@ const updateTrade = async () => { }, "noCopyCache", ); - const attrs = [ - "pid", - "name", - "age", - "contract", - "injury", - "watch", - "untradable", - "jerseyNumber", - ]; - const ratings = ["ovr", "pot", "skills", "pos"]; + const stats = bySport({ - basketball: ["gp", "min", "pts", "trb", "ast", "per"], - football: ["gp", "keyStats", "av"], - hockey: ["gp", "keyStats", "ops", "dps", "ps"], + basketball: [ + "stat:gp", + "stat:min", + "stat:pts", + "stat:trb", + "stat:ast", + "stat:per", + ], + football: ["stat:gp", "stat:keyStats", "stat:av"], + hockey: ["stat:gp", "stat:keyStats", "stat:ops", "stat:dps", "stat:ps"], }); + + const config: TableConfig = new TableConfig("trade", [ + "Name", + "Pos", + "Age", + "Ovr", + "Pot", + "Contract", + "Exp", + ...stats, + ]); + await config.load(); + const userRoster = await idb.getCopies.playersPlus(userRosterAll, { - attrs, - ratings, - stats, + attrs: [...config.attrsNeeded, "untradable"], + ratings: config.ratingsNeeded, + stats: config.statsNeeded, season: g.get("season"), tid: g.get("userTid"), showNoStats: true, @@ -161,9 +172,9 @@ const updateTrade = async () => { } const otherRoster = await idb.getCopies.playersPlus(otherRosterAll, { - attrs, - ratings, - stats, + attrs: [...config.attrsNeeded, "untradable"], + ratings: config.ratingsNeeded, + stats: config.statsNeeded, season: g.get("season"), tid: otherTid, showNoStats: true, @@ -210,8 +221,7 @@ const updateTrade = async () => { g.get("phase") > PHASE.PLAYOFFS && g.get("phase") < PHASE.FREE_AGENCY; return { - challengeNoRatings: g.get("challengeNoRatings"), - challengeNoTrades: g.get("challengeNoTrades"), + config, salaryCap: g.get("salaryCap") / 1000, salaryCapType: g.get("salaryCapType"), userDpids: teams[0].dpids, From 5367fa9bc875f09699fdec7d9fece81ea4d3c5e1 Mon Sep 17 00:00:00 2001 From: Vac1911 Date: Mon, 24 Jan 2022 15:46:33 -0500 Subject: [PATCH 04/15] highlightCol and footers --- public/css/dark.scss | 4 ---- public/css/light.scss | 4 ---- src/ui/components/DataTable/Footer.tsx | 20 +++++++++---------- src/ui/components/DataTable/Header.tsx | 27 +++++++++++++------------- src/ui/components/DataTable/Row.tsx | 5 ++--- src/ui/components/DataTable/index.tsx | 20 ++----------------- 6 files changed, 27 insertions(+), 53 deletions(-) diff --git a/public/css/dark.scss b/public/css/dark.scss index b9a8a119b8..3ad6445492 100644 --- a/public/css/dark.scss +++ b/public/css/dark.scss @@ -327,7 +327,3 @@ table.table thead .sorting::after { fill: lighten($secondary, 10%); } } - -.sorted { - background-color: #5f583c !important; -} diff --git a/public/css/light.scss b/public/css/light.scss index 4194d3ee1b..e9c9af7f7f 100644 --- a/public/css/light.scss +++ b/public/css/light.scss @@ -1363,10 +1363,6 @@ code { transition: none !important; } -.sorted { - background-color: #ffeeba !important; -} - hr { background-color: $black; opacity: 0.1; diff --git a/src/ui/components/DataTable/Footer.tsx b/src/ui/components/DataTable/Footer.tsx index c8682242b3..3316171590 100644 --- a/src/ui/components/DataTable/Footer.tsx +++ b/src/ui/components/DataTable/Footer.tsx @@ -1,16 +1,14 @@ import classNames from "classnames"; +import type { Col } from "./index"; const Footer = ({ - colOrder, + cols, footer, highlightCols, }: { - colOrder: { - colIndex: number; - hidden?: boolean; - }[]; + cols: Col[]; footer?: any[]; - highlightCols: number[]; + highlightCols: string[]; }) => { if (!footer) { return null; @@ -30,12 +28,12 @@ const Footer = ({ {footers.map((row, i) => ( - {colOrder.map(({ colIndex }, j) => { - const highlightColClassNames = highlightCols.includes(j) + {cols.map((col, j) => { + const highlightColClassNames = highlightCols.includes(col.key) ? "sorting_highlight" : undefined; - const value = row[colIndex]; + const value = row[j]; if (value != null && value.hasOwnProperty("value")) { return ( @@ -51,7 +49,7 @@ const Footer = ({ } return ( - ); diff --git a/src/ui/components/DataTable/Header.tsx b/src/ui/components/DataTable/Header.tsx index db818c2a10..16330b5318 100644 --- a/src/ui/components/DataTable/Header.tsx +++ b/src/ui/components/DataTable/Header.tsx @@ -144,17 +144,20 @@ const SortableColumn = SortableElement( colIndex: number; handleColClick: (b: MouseEvent, a: string) => void; }) => { - let className; - if (sortSequence && sortSequence.length === 0) { - className = null; - } else { - className = getSortClassName(sortBys, colIndex); - } + let className; + if (props.col.sortSequence && props.col.sortSequence.length === 0) { + className = null; + } else { + className = getSortClassName( + props.sortBy ? [props.sortBy] : [], + props.col.key, + ); + } return ( @@ -202,14 +205,12 @@ const SortableColumnHeader = SortableContainer( }, ); -export const getSortClassName = (sortBys: SortBy[], i: number) => { +export const getSortClassName = (sortBys: SortBy[], key: string | number) => { let className = "sorting"; for (const sortBy of sortBys) { - if (sortBy[0] === i) { - className = `sorting_highlight ${ - sortBy[1] === "asc" ? "sorting_asc" : "sorting_desc" - }`; + if (sortBy[0] === key) { + className = sortBy[1] === "asc" ? "sorting_asc" : "sorting_desc"; break; } } diff --git a/src/ui/components/DataTable/Row.tsx b/src/ui/components/DataTable/Row.tsx index 4657581cd4..fc97df6b98 100644 --- a/src/ui/components/DataTable/Row.tsx +++ b/src/ui/components/DataTable/Row.tsx @@ -10,12 +10,11 @@ const Row = ({ highlightCols, row, cols, - clickable, }: { row: DataTableRow; cols: Col[]; clickable?: boolean; - highlightCols: number[]; + highlightCols: string[]; }) => { const { clicked, toggleClicked } = useClickable(); return ( @@ -34,7 +33,7 @@ const Row = ({ const props: any = {}; - const highlightCol = highlightCols.includes(i); + const highlightCol = highlightCols.includes(col.key); if (value && value.classNames) { props.className = classNames( value.classNames, diff --git a/src/ui/components/DataTable/index.tsx b/src/ui/components/DataTable/index.tsx index 58a4239d8c..f417816cf0 100644 --- a/src/ui/components/DataTable/index.tsx +++ b/src/ui/components/DataTable/index.tsx @@ -482,23 +482,7 @@ const DataTable = (props: Props | LegacyProps) => { ({ hidden, colIndex }) => !hidden && state.cols[colIndex], ); - const highlightCols = state.sortBys - .map(sortBy => sortBy[0]) - .map(i => - colOrderFiltered.findIndex(({ colIndex }) => { - if (colIndex !== i) { - return false; - } - - // Make sure sortSequence is not an empty array - same code is in Header - const sortSequence = cols[colIndex].sortSequence; - if (sortSequence && sortSequence.length === 0) { - return false; - } - - return true; - }), - ); + const highlightCols = state.sortBys.map(sortBy => sortBy[0]); return ( <> @@ -571,8 +555,8 @@ const DataTable = (props: Props | LegacyProps) => { ))}
Draft Picks
{value.value} + {value} @@ -168,7 +171,7 @@ const SortableColumn = SortableElement( props.handleColClick(event, props.col.key); }} style={{ width: "19px" }} - className={classNames(col.classNames, className)} + className={classNames(props.col.classNames, className)} />
From 10e0d3c5bc9323ac9119f8ce153546a15e1b1d55 Mon Sep 17 00:00:00 2001 From: Vac1911 Date: Tue, 25 Jan 2022 08:55:22 -0500 Subject: [PATCH 05/15] backwards compatible supercols --- src/ui/components/DataTable/Header.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ui/components/DataTable/Header.tsx b/src/ui/components/DataTable/Header.tsx index 16330b5318..821b686af0 100644 --- a/src/ui/components/DataTable/Header.tsx +++ b/src/ui/components/DataTable/Header.tsx @@ -50,15 +50,13 @@ FilterHeader.propTypes = { }; const SuperCols = ({ - colOrder, + cols, superCols, }: { - colOrder: { - colIndex: number; - }[]; + cols: Col[]; superCols: SuperCol[]; }) => { - const colIndexes = colOrder.map(x => x.colIndex); + const colIndexes = Array.from({ length: cols.length }); const maxColIndex1 = Math.max(...colIndexes); let maxColIndex2 = -1; for (const superCol of superCols) { From 9d4cb5d68daa0aeb95afefc6e0b5bff9cf1c3336 Mon Sep 17 00:00:00 2001 From: Vac1911 Date: Tue, 25 Jan 2022 10:05:56 -0500 Subject: [PATCH 06/15] Frontend Changes to CustomizeColumns.tsx --- .../components/DataTable/CustomizeColumns.tsx | 55 +++++++++++++------ src/ui/util/TableConfig.ts | 16 +----- src/ui/views/TradingBlock.tsx | 1 - src/worker/db/connectMeta.ts | 3 + 4 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/ui/components/DataTable/CustomizeColumns.tsx b/src/ui/components/DataTable/CustomizeColumns.tsx index 2b79942be8..bf6078fa5e 100644 --- a/src/ui/components/DataTable/CustomizeColumns.tsx +++ b/src/ui/components/DataTable/CustomizeColumns.tsx @@ -1,10 +1,14 @@ import { useEffect, useState } from "react"; -import { Modal } from "react-bootstrap"; +import { Dropdown, Modal } from "react-bootstrap"; import { toWorker } from "../../util"; import type { TableConfig } from "../../util/TableConfig"; import { ColType, getAllCols } from "../../util/columns/getCols"; -import { difference, groupBy } from "lodash-es"; +import groupBy from "lodash-es/groupBy"; +import difference from "lodash-es/difference"; + import type { Col } from "./index"; +import { processingSpinner } from "../ActionButton"; +import { HelpPopover } from "../index"; export type ColConfig = Col & { hidden: boolean; @@ -105,12 +109,12 @@ const CustomizeColumns = ({ Customize Columns - +
    {Object.entries(colsGrouped).map(([group, cols]) => (
  • {group} -
    +
    {cols.map((col, i) => (
    @@ -122,12 +126,12 @@ const CustomizeColumns = ({ onChange={onChange(col.key)} />
    @@ -137,16 +141,35 @@ const CustomizeColumns = ({ ))}
- - - - + + + + Reset + + + Undo Changes + Restore Default + + +
+ +

+ Undo Changes: Undo all unsaved configration changes. +

+

+ Restore Default: Restores the table's default configration, + all saved changes to this table will be lost. +

+
+
+
+ + +
); diff --git a/src/ui/util/TableConfig.ts b/src/ui/util/TableConfig.ts index 1cff782e0c..00346c4d47 100644 --- a/src/ui/util/TableConfig.ts +++ b/src/ui/util/TableConfig.ts @@ -4,24 +4,14 @@ import { cloneDeep, uniq } from "lodash-es"; import { g } from "../../worker/util"; export class TableConfig { - get ratingsNeeded(): string[] { - return this._ratingsNeeded ?? []; - } - get statsNeeded(): string[] { - return this._statsNeeded ?? []; - } - get attrsNeeded(): string[] { - return this._attrsNeeded ?? []; - } - public fallback: string[]; public columns: MetaCol[]; public tableName: string; public vars: { [key: string]: any }; - private _statsNeeded: string[] = []; - private _ratingsNeeded: string[] = []; - private _attrsNeeded: string[] = ["pid"]; + public statsNeeded: string[] = []; + public ratingsNeeded: string[] = []; + public attrsNeeded: string[] = ["pid"]; constructor( tableName: string, diff --git a/src/ui/views/TradingBlock.tsx b/src/ui/views/TradingBlock.tsx index 2d025fd8fd..98be21398f 100644 --- a/src/ui/views/TradingBlock.tsx +++ b/src/ui/views/TradingBlock.tsx @@ -354,7 +354,6 @@ const TradingBlock = (props: View<"tradingBlock">) => {
Date: Tue, 25 Jan 2022 10:15:14 -0500 Subject: [PATCH 07/15] CustomizeColumns.tsx increased performance of getting initialColumns --- .../components/DataTable/CustomizeColumns.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ui/components/DataTable/CustomizeColumns.tsx b/src/ui/components/DataTable/CustomizeColumns.tsx index bf6078fa5e..1dff2d9fe8 100644 --- a/src/ui/components/DataTable/CustomizeColumns.tsx +++ b/src/ui/components/DataTable/CustomizeColumns.tsx @@ -26,14 +26,15 @@ const CustomizeColumns = ({ onSave: () => void; show: boolean; }) => { - const initialColumns: ColConfig[] = getAllCols().map( - (c): ColConfig => ({ - ...c, - cat: c.cat || "Other", - hidden: !config.columns.some(col => col.key === c.key), - }), - ); - const [columns, setColumns] = useState(initialColumns); + const initialColumns = (): ColConfig[] => + getAllCols().map( + (c): ColConfig => ({ + ...c, + cat: c.cat || "Other", + hidden: !config.columns.some(col => col.key === c.key), + }), + ); + const [columns, setColumns] = useState(initialColumns()); useEffect(() => { const nextColumns = [...columns].map(c => ({ From 1c106033f98b455659813d43fa29f3fd13d3eb48 Mon Sep 17 00:00:00 2001 From: Vac1911 Date: Tue, 25 Jan 2022 10:26:44 -0500 Subject: [PATCH 08/15] bug fixes --- src/ui/components/DataTable/CustomizeColumns.tsx | 2 +- src/ui/components/DataTable/index.tsx | 2 +- src/ui/util/TableConfig.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ui/components/DataTable/CustomizeColumns.tsx b/src/ui/components/DataTable/CustomizeColumns.tsx index 1dff2d9fe8..8003643081 100644 --- a/src/ui/components/DataTable/CustomizeColumns.tsx +++ b/src/ui/components/DataTable/CustomizeColumns.tsx @@ -54,7 +54,7 @@ const CustomizeColumns = ({ } }; - const reset = () => setColumns(initialColumns); + const reset = () => setColumns(initialColumns()); const exit = () => { reset(); diff --git a/src/ui/components/DataTable/index.tsx b/src/ui/components/DataTable/index.tsx index f417816cf0..de323329e9 100644 --- a/src/ui/components/DataTable/index.tsx +++ b/src/ui/components/DataTable/index.tsx @@ -592,7 +592,7 @@ DataTable.propTypes = { className: PropTypes.string, defaultSort: PropTypes.arrayOf( PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - ).isRequired, + ), disableSettingsCache: PropTypes.bool, footer: PropTypes.array, name: PropTypes.string.isRequired, diff --git a/src/ui/util/TableConfig.ts b/src/ui/util/TableConfig.ts index 00346c4d47..bc345c508d 100644 --- a/src/ui/util/TableConfig.ts +++ b/src/ui/util/TableConfig.ts @@ -57,19 +57,19 @@ export class TableConfig { this.tableName, ); this.columns = getCols(colOptions ?? this.fallback); - this._statsNeeded = uniq( + this.statsNeeded = uniq( this.columns.reduce( (needed: string[], c: MetaCol) => needed.concat(c.stats ?? []), [], ), ); - this._ratingsNeeded = uniq( + this.ratingsNeeded = uniq( this.columns.reduce( (needed: string[], c: MetaCol) => needed.concat(c.ratings ?? []), [], ), ); - this._attrsNeeded = uniq( + this.attrsNeeded = uniq( this.columns.reduce( (needed: string[], c: MetaCol) => needed.concat(c.attrs ?? []), [], From 97547ade915285e45c31d29284957e5efc731ef8 Mon Sep 17 00:00:00 2001 From: Vac1911 Date: Tue, 25 Jan 2022 11:22:44 -0500 Subject: [PATCH 09/15] created types LeagueVars and TemplateProps --- src/ui/util/TableConfig.ts | 4 +-- src/ui/util/columns/getCols.ts | 28 ++++++++++++++++--- src/ui/util/columns/getTemplate.ts | 5 ++-- src/ui/util/columns/templates/Age.tsx | 5 ++-- src/ui/util/columns/templates/AskingFor.tsx | 5 ++-- src/ui/util/columns/templates/Attr.tsx | 5 ++-- src/ui/util/columns/templates/College.tsx | 5 ++-- src/ui/util/columns/templates/Contract.tsx | 4 +-- src/ui/util/columns/templates/Country.tsx | 5 ++-- src/ui/util/columns/templates/DraftYear.tsx | 5 ++-- src/ui/util/columns/templates/Exp.tsx | 5 ++-- src/ui/util/columns/templates/Experience.tsx | 5 ++-- src/ui/util/columns/templates/Height.tsx | 5 ++-- .../util/columns/templates/InjuryLength.tsx | 5 ++-- src/ui/util/columns/templates/InjuryType.tsx | 5 ++-- src/ui/util/columns/templates/Mood.tsx | 5 ++-- src/ui/util/columns/templates/MoodCurrent.tsx | 5 ++-- src/ui/util/columns/templates/Name.tsx | 5 ++-- src/ui/util/columns/templates/Negotiate.tsx | 5 ++-- src/ui/util/columns/templates/Ovr.tsx | 5 ++-- src/ui/util/columns/templates/Pick.tsx | 5 ++-- src/ui/util/columns/templates/Pos.tsx | 5 ++-- src/ui/util/columns/templates/Pot.tsx | 5 ++-- src/ui/util/columns/templates/Projected.tsx | 5 ++-- src/ui/util/columns/templates/Rating.tsx | 5 ++-- src/ui/util/columns/templates/Stat.tsx | 5 ++-- src/ui/util/columns/templates/Team.tsx | 5 ++-- src/ui/util/columns/templates/Trade.tsx | 6 ++-- src/ui/util/columns/templates/Weight.tsx | 5 ++-- .../util/columns/templates/YearsWithTeam.tsx | 5 ++-- src/ui/views/FreeAgents.tsx | 11 ++++---- src/ui/views/Trade/AssetList.tsx | 4 +-- src/ui/views/TradingBlock.tsx | 2 +- 33 files changed, 91 insertions(+), 98 deletions(-) diff --git a/src/ui/util/TableConfig.ts b/src/ui/util/TableConfig.ts index bc345c508d..5b8e02dd4f 100644 --- a/src/ui/util/TableConfig.ts +++ b/src/ui/util/TableConfig.ts @@ -1,5 +1,5 @@ import { idb } from "../../worker/db"; -import getCols, { MetaCol } from "./columns/getCols"; +import getCols, { LeagueVars, MetaCol } from "./columns/getCols"; import { cloneDeep, uniq } from "lodash-es"; import { g } from "../../worker/util"; @@ -7,7 +7,7 @@ export class TableConfig { public fallback: string[]; public columns: MetaCol[]; public tableName: string; - public vars: { [key: string]: any }; + public vars: LeagueVars; public statsNeeded: string[] = []; public ratingsNeeded: string[] = []; diff --git a/src/ui/util/columns/getCols.ts b/src/ui/util/columns/getCols.ts index 5715c9c4a7..ccc27a073d 100644 --- a/src/ui/util/columns/getCols.ts +++ b/src/ui/util/columns/getCols.ts @@ -1,6 +1,6 @@ import bySport from "../../../common/bySport"; import isSport from "../../../common/isSport"; -import type { Player } from "../../../common/types"; +import type { GameAttributesLeague, Player } from "../../../common/types"; import type { Col } from "../../components/DataTable"; export type ColType = @@ -11,14 +11,34 @@ export type ColType = | "Other" | null; +export type LeagueVars = Pick< + GameAttributesLeague, + | "userTid" + | "godMode" + | "spectator" + | "phase" + | "challengeNoRatings" + | "challengeNoDraftPicks" + | "challengeNoFreeAgents" + | "challengeNoTrades" + | "salaryCapType" + | "salaryCap" + | "maxContract" + | "minContract" +>; + +export type TemplateProps = { + p: Player; + c: MetaCol; + vars: LeagueVars; +}; + export type MetaCol = Col & { cat?: ColType; ratings?: string[]; stats?: string[]; attrs?: string[]; - template: - | string - | ((p: Player, c: MetaCol, vars: object) => JSX.Element | string); + template?: string | ((props: TemplateProps) => JSX.Element | string); options?: { [key: string]: any }; }; diff --git a/src/ui/util/columns/getTemplate.ts b/src/ui/util/columns/getTemplate.ts index 5dd2a11972..9cc3d4baf1 100644 --- a/src/ui/util/columns/getTemplate.ts +++ b/src/ui/util/columns/getTemplate.ts @@ -4,9 +4,10 @@ import type { Player } from "../../../common/types"; import type { TableConfig } from "../TableConfig"; export default function (p: Player, c: MetaCol, config: TableConfig) { - if (typeof c.template === "function") return c.template(p, c, config.vars); + if (typeof c.template === "function") + return c.template({ p, c, vars: config.vars }); if (!(c.template in templates)) return ""; // @ts-ignore // eslint-disable-next-line import/namespace - return templates[c.template](p, c, config.vars); + return templates[c.template]({ p, c, vars: config.vars }); } diff --git a/src/ui/util/columns/templates/Age.tsx b/src/ui/util/columns/templates/Age.tsx index a57256cfd1..1530d4be14 100644 --- a/src/ui/util/columns/templates/Age.tsx +++ b/src/ui/util/columns/templates/Age.tsx @@ -1,4 +1,3 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; -export default (p: Player, c: MetaCol) => p.age; +export default ({ p, c, vars }: TemplateProps) => p.age; diff --git a/src/ui/util/columns/templates/AskingFor.tsx b/src/ui/util/columns/templates/AskingFor.tsx index a7c9bfe15f..ebcf42d3b9 100644 --- a/src/ui/util/columns/templates/AskingFor.tsx +++ b/src/ui/util/columns/templates/AskingFor.tsx @@ -1,6 +1,5 @@ -import type { Player } from "../../../../common/types"; import { helpers } from "../../index"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; -export default (p: Player, c: MetaCol) => +export default ({ p, c, vars }: TemplateProps) => helpers.formatCurrency(p.mood.user.contractAmount / 1000, "M"); diff --git a/src/ui/util/columns/templates/Attr.tsx b/src/ui/util/columns/templates/Attr.tsx index a0414a7a03..20a79a896a 100644 --- a/src/ui/util/columns/templates/Attr.tsx +++ b/src/ui/util/columns/templates/Attr.tsx @@ -1,7 +1,6 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; -export default (p: Player, c: MetaCol) => { +export default ({ p, c, vars }: TemplateProps) => { const key: string = c.attrs[0]; if (!(key in p)) return `${key} not found`; return p[key]; diff --git a/src/ui/util/columns/templates/College.tsx b/src/ui/util/columns/templates/College.tsx index 33f145dab8..135a9e542a 100644 --- a/src/ui/util/columns/templates/College.tsx +++ b/src/ui/util/columns/templates/College.tsx @@ -1,8 +1,7 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; import { helpers } from "../../index"; -export default (p: Player, c: MetaCol) => { +export default ({ p, c, vars }: TemplateProps) => { const college = p.college && p.college !== "" ? p.college : "None"; return ( +export default ({ p, c, vars }: TemplateProps) => helpers.formatCurrency(p.contract.amount, "M") + (c.options?.format == "full" ? ` thru ${p.contract.exp}` : ""); diff --git a/src/ui/util/columns/templates/Country.tsx b/src/ui/util/columns/templates/Country.tsx index 31c059a4e8..45a432e127 100644 --- a/src/ui/util/columns/templates/Country.tsx +++ b/src/ui/util/columns/templates/Country.tsx @@ -1,9 +1,8 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; import { helpers } from "../../index"; import { CountryFlag } from "../../../components"; -export default (p: Player, c: MetaCol) => ( +export default ({ p, c, vars }: TemplateProps) => ( <> p.draft.year; +export default ({ p, c, vars }: TemplateProps) => p.draft.year; diff --git a/src/ui/util/columns/templates/Exp.tsx b/src/ui/util/columns/templates/Exp.tsx index ed4fa4b35c..ad83f66466 100644 --- a/src/ui/util/columns/templates/Exp.tsx +++ b/src/ui/util/columns/templates/Exp.tsx @@ -1,4 +1,3 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; -export default (p: Player, c: MetaCol) => p.contract.exp.toString(); +export default ({ p, c, vars }: TemplateProps) => p.contract.exp.toString(); diff --git a/src/ui/util/columns/templates/Experience.tsx b/src/ui/util/columns/templates/Experience.tsx index 7855db5acd..b7e8378bfb 100644 --- a/src/ui/util/columns/templates/Experience.tsx +++ b/src/ui/util/columns/templates/Experience.tsx @@ -1,4 +1,3 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; -export default (p: Player, c: MetaCol) => p.experience.toString(); +export default ({ p, c, vars }: TemplateProps) => p.experience.toString(); diff --git a/src/ui/util/columns/templates/Height.tsx b/src/ui/util/columns/templates/Height.tsx index 134faeae87..0ecc412c2d 100644 --- a/src/ui/util/columns/templates/Height.tsx +++ b/src/ui/util/columns/templates/Height.tsx @@ -1,5 +1,4 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; import { wrappedHeight } from "../../../components/Height"; -export default (p: Player, c: MetaCol) => wrappedHeight(p.hgt); +export default ({ p, c, vars }: TemplateProps) => wrappedHeight(p.hgt); diff --git a/src/ui/util/columns/templates/InjuryLength.tsx b/src/ui/util/columns/templates/InjuryLength.tsx index 41efe80580..1aa36adbb5 100644 --- a/src/ui/util/columns/templates/InjuryLength.tsx +++ b/src/ui/util/columns/templates/InjuryLength.tsx @@ -1,4 +1,3 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; -export default (p: Player, c: MetaCol) => p.injury.gamesRemaining; +export default ({ p, c, vars }: TemplateProps) => p.injury.gamesRemaining; diff --git a/src/ui/util/columns/templates/InjuryType.tsx b/src/ui/util/columns/templates/InjuryType.tsx index d8c1492de1..10c229636b 100644 --- a/src/ui/util/columns/templates/InjuryType.tsx +++ b/src/ui/util/columns/templates/InjuryType.tsx @@ -1,4 +1,3 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; -export default (p: Player, c: MetaCol) => p.injury.type; +export default ({ p, c, vars }: TemplateProps) => p.injury.type; diff --git a/src/ui/util/columns/templates/Mood.tsx b/src/ui/util/columns/templates/Mood.tsx index 6452ef8b45..f3ecaa5f1e 100644 --- a/src/ui/util/columns/templates/Mood.tsx +++ b/src/ui/util/columns/templates/Mood.tsx @@ -1,6 +1,5 @@ -import type { Player } from "../../../../common/types"; +import type { TemplateProps } from "../getCols"; import { dataTableWrappedMood } from "../../../components/Mood"; -import type { MetaCol } from "../getCols"; -export default (p: Player, c: MetaCol) => +export default ({ p, c, vars }: TemplateProps) => dataTableWrappedMood({ defaultType: "user", maxWidth: true, p }); diff --git a/src/ui/util/columns/templates/MoodCurrent.tsx b/src/ui/util/columns/templates/MoodCurrent.tsx index f5ef8486ad..c8feda1deb 100644 --- a/src/ui/util/columns/templates/MoodCurrent.tsx +++ b/src/ui/util/columns/templates/MoodCurrent.tsx @@ -1,6 +1,5 @@ -import type { Player } from "../../../../common/types"; +import type { TemplateProps } from "../getCols"; import { dataTableWrappedMood } from "../../../components/Mood"; -import type { MetaCol } from "../getCols"; -export default (p: Player, c: MetaCol) => +export default ({ p, c, vars }: TemplateProps) => dataTableWrappedMood({ defaultType: "current", maxWidth: true, p }); diff --git a/src/ui/util/columns/templates/Name.tsx b/src/ui/util/columns/templates/Name.tsx index c2d8376805..3e1c1c81a7 100644 --- a/src/ui/util/columns/templates/Name.tsx +++ b/src/ui/util/columns/templates/Name.tsx @@ -1,8 +1,7 @@ -import type { Player } from "../../../../common/types"; +import type { TemplateProps } from "../getCols"; import { PlayerNameLabels } from "../../../components"; -import type { MetaCol } from "../getCols"; -export default (p: Player, c: MetaCol) => ( +export default ({ p, c, vars }: TemplateProps) => ( ( +export default ({ p, c, vars }: TemplateProps) => ( { +export default ({ p, c, vars }: TemplateProps) => { if (vars["challengeNoRatings"]) return ""; else if (p.ratings["dovr"]) return ( diff --git a/src/ui/util/columns/templates/Pick.tsx b/src/ui/util/columns/templates/Pick.tsx index b028fd4372..c29c955892 100644 --- a/src/ui/util/columns/templates/Pick.tsx +++ b/src/ui/util/columns/templates/Pick.tsx @@ -1,5 +1,4 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; -export default (p: Player, c: MetaCol) => +export default ({ p, c, vars }: TemplateProps) => p.draft.round > 0 ? `${p.draft.round}-${p.draft.pick}` : null; diff --git a/src/ui/util/columns/templates/Pos.tsx b/src/ui/util/columns/templates/Pos.tsx index 0721d2eb33..d8586a78cb 100644 --- a/src/ui/util/columns/templates/Pos.tsx +++ b/src/ui/util/columns/templates/Pos.tsx @@ -1,4 +1,3 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; -export default (p: Player, c: MetaCol) => p.ratings.pos; +export default ({ p, c, vars }: TemplateProps) => p.ratings.pos; diff --git a/src/ui/util/columns/templates/Pot.tsx b/src/ui/util/columns/templates/Pot.tsx index 0dcc3426b0..fd65fc83a7 100644 --- a/src/ui/util/columns/templates/Pot.tsx +++ b/src/ui/util/columns/templates/Pot.tsx @@ -1,8 +1,7 @@ -import type { Player } from "../../../../common/types"; +import type { TemplateProps } from "../getCols"; import { RatingWithChange } from "../../../components"; -import type { MetaCol } from "../getCols"; -export default (p: Player, c: MetaCol, vars: object) => { +export default ({ p, c, vars }: TemplateProps) => { if (vars["challengeNoRatings"]) return ""; else if (p.ratings["dpot"]) return ( diff --git a/src/ui/util/columns/templates/Projected.tsx b/src/ui/util/columns/templates/Projected.tsx index 253ff7d734..a1d37e9e54 100644 --- a/src/ui/util/columns/templates/Projected.tsx +++ b/src/ui/util/columns/templates/Projected.tsx @@ -1,6 +1,5 @@ -import type { Player } from "../../../../common/types"; +import type { TemplateProps } from "../getCols"; import { helpers } from "../../index"; -import type { MetaCol } from "../getCols"; -export default (p: Player, c: MetaCol) => +export default ({ p, c, vars }: TemplateProps) => `${helpers.formatCurrency(p.contractDesired.amount, "M")}`; diff --git a/src/ui/util/columns/templates/Rating.tsx b/src/ui/util/columns/templates/Rating.tsx index 83d4596011..c070617061 100644 --- a/src/ui/util/columns/templates/Rating.tsx +++ b/src/ui/util/columns/templates/Rating.tsx @@ -1,7 +1,6 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; -export default (p: Player, c: MetaCol, vars: object) => { +export default ({ p, c, vars }: TemplateProps) => { if (vars["challengeNoRatings"]) return ""; const key = c.ratings[0] ?? false; return key && key in p.ratings ? p.ratings[key].toString() : ""; diff --git a/src/ui/util/columns/templates/Stat.tsx b/src/ui/util/columns/templates/Stat.tsx index 38563fdb70..900f220dec 100644 --- a/src/ui/util/columns/templates/Stat.tsx +++ b/src/ui/util/columns/templates/Stat.tsx @@ -1,7 +1,6 @@ -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; -export default (p: Player, c: MetaCol) => { +export default ({ p, c, vars }: TemplateProps) => { const key: string = c.stats[0]; if (!(key in p.stats)) return `${key} not found`; const value = p.stats[key]; diff --git a/src/ui/util/columns/templates/Team.tsx b/src/ui/util/columns/templates/Team.tsx index 8248438d9c..c0614c2e27 100644 --- a/src/ui/util/columns/templates/Team.tsx +++ b/src/ui/util/columns/templates/Team.tsx @@ -1,8 +1,7 @@ +import type { TemplateProps } from "../getCols"; import { helpers } from "../../index"; -import type { Player } from "../../../../common/types"; -import type { MetaCol } from "../getCols"; -export default (p: Player, c: MetaCol) => { +export default ({ p, c, vars }: TemplateProps) => { return ( {p.stats.abbrev} diff --git a/src/ui/util/columns/templates/Trade.tsx b/src/ui/util/columns/templates/Trade.tsx index a04198012f..6f801acc09 100644 --- a/src/ui/util/columns/templates/Trade.tsx +++ b/src/ui/util/columns/templates/Trade.tsx @@ -1,8 +1,6 @@ -import type { Player } from "../../../../common/types"; -import { NegotiateButtons } from "../../../components"; -import { MetaCol } from "../getCols"; +import type { TemplateProps } from "../getCols"; -export default (p: Player, c: MetaCol, vars: object) => ( +export default ({ p, c, vars }: TemplateProps) => ( -); diff --git a/src/ui/util/columns/templates/YearsWithTeam.tsx b/src/ui/util/columns/templates/YearsWithTeam.tsx index 4c9d566e5c..eb56d8061e 100644 --- a/src/ui/util/columns/templates/YearsWithTeam.tsx +++ b/src/ui/util/columns/templates/YearsWithTeam.tsx @@ -1,6 +1,5 @@ import type { TemplateProps } from "../getCols"; export default ({ p, c, vars }: TemplateProps) => { - const key: string = c.stats[0]; - return key in p.stats ? p.stats[key].toFixed(1) : `${key} not found`; + return p.stats.yearsWithTeam.toFixed(1); }; diff --git a/src/ui/util/columns/templates/index.tsx b/src/ui/util/columns/templates/index.tsx index 8befddfc3a..ec667304e7 100644 --- a/src/ui/util/columns/templates/index.tsx +++ b/src/ui/util/columns/templates/index.tsx @@ -14,7 +14,6 @@ export { default as InjuryType } from "./InjuryType"; export { default as Mood } from "./Mood"; export { default as MoodCurrent } from "./MoodCurrent"; export { default as Name } from "./Name"; -export { default as Negotiate } from "./Negotiate"; export { default as Ovr } from "./Ovr"; export { default as Pos } from "./Pos"; export { default as Pot } from "./Pot"; @@ -22,6 +21,5 @@ export { default as Projected } from "./Projected"; export { default as Rating } from "./Rating"; export { default as Stat } from "./Stat"; export { default as Team } from "./Team"; -export { default as Trade } from "./Trade"; export { default as Weight } from "./Weight"; export { default as YearsWithTeam } from "./YearsWithTeam"; diff --git a/src/ui/views/Achievements.tsx b/src/ui/views/Achievements.tsx index ccf579841d..671e7d4200 100644 --- a/src/ui/views/Achievements.tsx +++ b/src/ui/views/Achievements.tsx @@ -233,7 +233,7 @@ const Category = ({ > 100} rows={rowsUndrafted} @@ -462,7 +462,7 @@ const Draft = ({ 100} rows={rowsDrafted} diff --git a/src/ui/views/DraftHistory.tsx b/src/ui/views/DraftHistory.tsx index 6f78e83709..6390023344 100644 --- a/src/ui/views/DraftHistory.tsx +++ b/src/ui/views/DraftHistory.tsx @@ -192,7 +192,7 @@ const DraftHistory = ({ diff --git a/src/ui/views/DraftTeamHistory.tsx b/src/ui/views/DraftTeamHistory.tsx index cd325ba1eb..df727a0164 100644 --- a/src/ui/views/DraftTeamHistory.tsx +++ b/src/ui/views/DraftTeamHistory.tsx @@ -182,7 +182,7 @@ const DraftTeamHistory = ({ ) => { { + ...teamsFiltered.map((t): LegacyCol => { return { classNames: classNames( "text-center", @@ -125,7 +125,7 @@ const HeadToHeadAll = ({ ) => { ) => { useTitleBar({ @@ -19,53 +18,12 @@ const Injuries = ({ dropdownFields: { teamsAndAllWatch: abbrev, seasonsAndCurrent: season }, }); - const cols = getCols([ - "Name", - "Pos", - "Team", - "Age", - "Ovr", - "Pot", - ...stats.map(stat => `stat:${stat}`), - "TypeInjury", - "Games", - "Ovr Drop", - "Pot Drop", - ]); - - const rows = injuries.map((p, i) => { - const showRatings = !challengeNoRatings || p.tid === PLAYER.RETIRED; - + const rows = injuries.map(p => { return { - key: season === "current" ? p.pid : i, - data: [ - - {p.name} - , - p.ratings.pos, - - {p.stats.abbrev} - , - p.age, - showRatings ? p.ratings.ovr : null, - showRatings ? p.ratings.pot : null, - ...stats.map(stat => helpers.roundStat(p.stats[stat], stat)), - p.type, - p.games, - showRatings ? p.ovrDrop : null, - showRatings ? p.potDrop : null, - ], + key: p.pid, + data: Object.fromEntries( + config.columns.map(col => [col.key, getTemplate(p, col, config)]), + ), classNames: { "table-danger": p.hof, "table-info": p.stats.tid === userTid, @@ -95,8 +53,9 @@ const Injuries = ({ {rows.length > 0 ? ( diff --git a/src/ui/views/Player/Injuries.tsx b/src/ui/views/Player/Injuries.tsx index fc27c023c2..217018621d 100644 --- a/src/ui/views/Player/Injuries.tsx +++ b/src/ui/views/Player/Injuries.tsx @@ -49,7 +49,7 @@ const Injuries = ({ { diff --git a/src/ui/views/Player/index.tsx b/src/ui/views/Player/index.tsx index 8c81067479..01639ee519 100644 --- a/src/ui/views/Player/index.tsx +++ b/src/ui/views/Player/index.tsx @@ -126,7 +126,7 @@ const StatsTable = ({ `rating:${rating}`), "Skills", ])} - defaultSort={[0, "asc"]} + defaultSort={["col1", "asc"]} hideAllControls name="Player:Ratings" rows={player.ratings.map((r, i) => { @@ -356,7 +356,7 @@ const Player2 = ({ `stat:${stat}`), ]); - const makeRow = (game: typeof gameLog[number], i: number): DataTableRow => { + const makeRow = ( + game: typeof gameLog[number], + i: number, + ): LegacyDataTableRow => { return { key: i, data: [ @@ -213,7 +216,7 @@ const PlayerGameLog = ({ <> 100} rows={rows} diff --git a/src/ui/views/ScheduledEvents.tsx b/src/ui/views/ScheduledEvents.tsx index ba2e600c70..dbacbe2e24 100644 --- a/src/ui/views/ScheduledEvents.tsx +++ b/src/ui/views/ScheduledEvents.tsx @@ -338,7 +338,7 @@ const ScheduledEvents = ({ scheduledEvents }: View<"scheduledEvents">) => { diff --git a/src/ui/views/TeamFinances.tsx b/src/ui/views/TeamFinances.tsx index 6ba9c4e4ae..3052e4899b 100644 --- a/src/ui/views/TeamFinances.tsx +++ b/src/ui/views/TeamFinances.tsx @@ -780,7 +780,7 @@ const TeamFinances = ({ ) => { salaryCapType, summary, showResigningMsg, - stats, strategy, teams, tied, diff --git a/src/ui/views/TradingBlock.tsx b/src/ui/views/TradingBlock.tsx index af17c05199..1891de09e1 100644 --- a/src/ui/views/TradingBlock.tsx +++ b/src/ui/views/TradingBlock.tsx @@ -5,10 +5,8 @@ import useTitleBar from "../hooks/useTitleBar"; import { getCols, helpers, toWorker } from "../util"; import { DataTable } from "../components"; import type { View } from "../../common/types"; -import type { Player } from "../../common/types"; import type api from "../../worker/api"; import getTemplate from "../util/columns/getTemplate"; -import type { MetaCol } from "../util/columns/getCols"; import { TableConfig } from "../util/TableConfig"; type OfferType = Awaited< @@ -29,7 +27,6 @@ type OfferProps = { const Offer = (props: OfferProps) => { const { abbrev, - challengeNoRatings, dpids, handleClickNegotiate, i, diff --git a/src/ui/views/TragicDeaths.tsx b/src/ui/views/TragicDeaths.tsx index d590e39cdf..01644702bf 100644 --- a/src/ui/views/TragicDeaths.tsx +++ b/src/ui/views/TragicDeaths.tsx @@ -3,6 +3,7 @@ import { getCols, helpers } from "../util"; import { DataTable, SafeHtml } from "../components"; import type { View } from "../../common/types"; import { frivolitiesMenu } from "./Frivolities"; +import type { LegacyDataTableRow } from "../components/DataTable"; const TragicDeaths = ({ players, stats, userTid }: View<"tragicDeaths">) => { useTitleBar({ title: "Tragic Deaths", customMenu: frivolitiesMenu }); @@ -45,7 +46,7 @@ const TragicDeaths = ({ players, stats, userTid }: View<"tragicDeaths">) => { "Details", ]); - const rows = players.map((p, i) => { + const rows: LegacyDataTableRow[] = players.map((p, i) => { const lastRatings = p.ratings.at(-1); const lastStats = p.stats.at(-1); @@ -97,7 +98,7 @@ const TragicDeaths = ({ players, stats, userTid }: View<"tragicDeaths">) => { , @@ -22,6 +23,20 @@ const updateInjuries = async ( hockey: ["gp", "keyStats"], }); + const config = new TableConfig("injuries", [ + "Name", + "Pos", + "Team", + "Age", + "Ovr", + "Pot", + ...stats.map(stat => `stat:${stat}`), + "TypeInjury", + "Games", + "Ovr Drop", + "Pot Drop", + ]); + const players = await getPlayers( inputs.season === "current" ? g.get("season") : inputs.season, inputs.abbrev, @@ -29,6 +44,7 @@ const updateInjuries = async ( ["ovr", "pot"], [...stats, "jerseyNumber"], inputs.tid, + config, ); const injuries = []; @@ -67,7 +83,7 @@ const updateInjuries = async ( godMode: g.get("godMode"), injuries, season: inputs.season, - stats, + config, userTid, }; } From dbf7b75aac96743ddaebad94713b6b602f719332 Mon Sep 17 00:00:00 2001 From: Vac1911 Date: Fri, 28 Jan 2022 09:05:23 -0500 Subject: [PATCH 14/15] Bugfix --- src/ui/util/TableConfig.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ui/util/TableConfig.ts b/src/ui/util/TableConfig.ts index de855467f6..194c8ead6f 100644 --- a/src/ui/util/TableConfig.ts +++ b/src/ui/util/TableConfig.ts @@ -1,8 +1,7 @@ import { idb } from "../../worker/db"; import getCols, { LeagueVars, MetaCol } from "./columns/getCols"; -import { uniq } from "lodash-es"; +import { cloneDeep, uniq } from "lodash-es"; import { g } from "../../worker/util"; -import { helpers } from "./index"; export class TableConfig { public fallback: string[]; @@ -51,7 +50,7 @@ export class TableConfig { } static unserialize(_config: TableConfig) { - const serialized = helpers.deepCopy(_config); + const serialized = cloneDeep(_config); return new TableConfig( serialized.tableName, serialized.fallback, From 0ce3e136112d7ad439b0b60e5e5b6bd4d5fc8d6c Mon Sep 17 00:00:00 2001 From: Vac1911 Date: Fri, 28 Jan 2022 17:00:38 -0500 Subject: [PATCH 15/15] getPlayerTable --- src/worker/api/getPlayerTable.ts | 212 +++++++++++++++++++++++++++++++ src/worker/api/index.ts | 4 +- 2 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 src/worker/api/getPlayerTable.ts diff --git a/src/worker/api/getPlayerTable.ts b/src/worker/api/getPlayerTable.ts new file mode 100644 index 0000000000..9b2bb57278 --- /dev/null +++ b/src/worker/api/getPlayerTable.ts @@ -0,0 +1,212 @@ +import { idb } from "../db"; +import { uniq } from "lodash-es"; +import { g } from "../../worker/util"; +import type { MetaCol } from "../../ui/util/columns/getCols"; +import { + getCols, + isSport, + PHASE, + PLAYER, + PLAYER_STATS_TABLES, +} from "../../common"; +import { PlayerStatType } from "../../common/types"; + +type PlayerTableInput = { + teamsAndAllWatch: string; + season: "career" | "all" | number; + statType: string; + playoffs: "playoffs" | "regularSeason"; +}; + +export default async function getPlayerTable( + inputs: PlayerTableInput, + tableName: string, + fallback: string[] = [], +) { + const colOptions: string[] | undefined = await idb.meta.get( + "tables", + tableName, + ); + fallback = [ + "Name", + "Pos", + "Team", + "Age", + ...PLAYER_STATS_TABLES.regular.stats.map(s => `stat:${s}`), + ]; + console.log(fallback); + const columns: MetaCol[] = getCols(colOptions ?? fallback); + const statsNeeded: string[] = uniq( + columns.reduce( + (needed: string[], c: MetaCol) => needed.concat(c.stats ?? []), + [], + ), + ); + const ratingsNeeded: string[] = uniq( + columns.reduce( + (needed: string[], c: MetaCol) => needed.concat(c.ratings ?? []), + [], + ), + ); + const attrsNeeded: string[] = uniq( + columns.reduce( + (needed: string[], c: MetaCol) => needed.concat(c.attrs ?? []), + [], + ), + ); + // const vars = { + // season: g.get("season"), + // userTid: g.get("userTid"), + // godMode: g.get("godMode"), + // spectator: g.get("spectator"), + // phase: g.get("phase"), + // challengeNoRatings: g.get("challengeNoRatings"), + // challengeNoDraftPicks: g.get("challengeNoDraftPicks"), + // challengeNoFreeAgents: g.get("challengeNoFreeAgents"), + // challengeNoTrades: g.get("challengeNoTrades"), + // salaryCapType: g.get("salaryCapType"), + // salaryCap: g.get("salaryCap"), + // maxContract: g.get("maxContract"), + // minContract: g.get("minContract"), + // }; + + return getPlayers(inputs, statsNeeded, ratingsNeeded, attrsNeeded); +} + +export const getPlayers = async ( + inputs: PlayerTableInput, + statsNeeded: string[], + ratingsNeeded: string[], + attrsNeeded: string[], +) => { + let playersAll; + let tid: number | undefined = g + .get("teamInfoCache") + .findIndex(t => t.abbrev === inputs.teamsAndAllWatch); + if (tid < 0) { + tid = undefined; + } + + if (g.get("season") === inputs.season) { + playersAll = await idb.cache.players.indexGetAll("playersByTid", [ + PLAYER.FREE_AGENT, + Infinity, + ]); + } else { + playersAll = await idb.getCopies.players( + { + activeSeason: + typeof inputs.season === "number" ? inputs.season : undefined, + }, + "noCopyCache", + ); + } + // Show all teams + if (tid === undefined && inputs.teamsAndAllWatch === "watch") { + playersAll = playersAll.filter(p => p.watch); + } + + return playersAll; + + // Show all teams + let statType: PlayerStatType; + + if (isSport("basketball")) { + if (inputs.statType === "totals") { + statType = "totals"; + } else if (inputs.statType === "per36") { + statType = "per36"; + } else { + statType = "perGame"; + } + } else { + statType = "totals"; + } + + // idb.getCopies.playersPlus `tid` option doesn't work well enough (factoring in showNoStats and showRookies), so let's do it manually + // For the current season, use the current abbrev (including FA), not the last stats abbrev + // For other seasons, use the stats abbrev for filtering + let players = await idb.getCopies.playersPlus(playersAll, { + attrs: [], + ratings: [], + stats: [], + season: typeof inputs.season === "number" ? inputs.season : undefined, + tid, + statType, + playoffs: inputs.playoffs === "playoffs", + regularSeason: inputs.playoffs !== "playoffs", + mergeStats: true, + }); + + if (inputs.season === "all") { + players = players + .map(p => + p.stats.map((ps: any) => { + const ratings = + p.ratings.find((pr: any) => pr.season === ps.season) ?? + p.ratings.at(-1); + + return { + ...p, + ratings, + stats: ps, + }; + }), + ) + .flat(); + } + + // Only keep players who actually played + if (inputs.abbrev !== "watch" && isSport("basketball")) { + players = players.filter(p => { + if (inputs.statType === "gameHighs") { + if (inputs.season !== "career") { + return p.stats.gp > 0; + } else if (inputs.playoffs !== "playoffs") { + return p.careerStats.gp > 0; + } + return p.careerStatsPlayoffs.gp > 0; + } + + if (inputs.season !== "career") { + return p.stats.gp > 0; + } else if (inputs.playoffs === "playoffs") { + return p.careerStatsPlayoffs.gp > 0; + } else if (inputs.playoffs !== "playoffs") { + return p.careerStats.gp > 0; + } + + return false; + }); + } else if ( + inputs.abbrev !== "watch" && + statsTable.onlyShowIf && + (isSport("football") || isSport("hockey")) + ) { + // Ensure some non-zero stat for this position + const onlyShowIf = statsTable.onlyShowIf; + + let obj: "careerStatsPlayoffs" | "careerStats" | "stats"; + if (inputs.season === "career") { + if (inputs.playoffs === "playoffs") { + obj = "careerStatsPlayoffs"; + } else { + obj = "careerStats"; + } + } else { + obj = "stats"; + } + + players = players.filter(p => { + for (const stat of onlyShowIf) { + if (typeof p[obj][stat] === "number" && p[obj][stat] > 0) { + return true; + } + } + + return false; + }); + } + + return players; +}; diff --git a/src/worker/api/index.ts b/src/worker/api/index.ts index 0b06ac5ab6..d8b19ba0e1 100644 --- a/src/worker/api/index.ts +++ b/src/worker/api/index.ts @@ -116,6 +116,7 @@ import { TableConfig } from "../../ui/util/TableConfig"; import playMenu from "./playMenu"; import toolsMenu from "./toolsMenu"; import omit from "lodash-es/omit"; +import getPlayerTable from "./getPlayerTable"; const acceptContractNegotiation = async ({ pid, @@ -3891,6 +3892,7 @@ export default { getPlayersCommandPalette, getLocal, getPlayerBioInfoDefaults, + getPlayerTable, getPlayerWatch, getRandomCollege, getRandomCountry, @@ -3950,7 +3952,7 @@ export default { updateLeague, updateMultiTeamMode, updateOptions, - updateColumns, + updateColumns, updatePlayThroughInjuries, updatePlayerWatch, updatePlayingTime,