diff --git a/src/App.tsx b/src/App.tsx index 868e2b82..d15d0f53 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,7 +14,7 @@ import { Settings } from './types/Settings' export function App (): JSX.Element { const [roster, setRoster] = useState(null) - const [settings, setSettings] = useState({ showWoundTrack: true }) + const [settings, setSettings] = useState({ showWoundTrack: true, rosterSelection: false, printRosterList: false }) useEffect(() => { setSettings(loadSettingsFromLocalStorage()) @@ -31,9 +31,9 @@ export function App (): JSX.Element { const loadSettingsFromLocalStorage = (): Settings => { try { - return JSON.parse(localStorage.getItem('settings') ?? '{ showWoundTrack: true, touchscreenMode: false, dropboxSelector: false }') + return JSON.parse(localStorage.getItem('settings') ?? '{ showWoundTrack: true, rosterSelection: false, printRosterList: false }') } catch (e) { - return { showWoundTrack: false } + return { showWoundTrack: true, rosterSelection: false, printRosterList: false } } } @@ -76,7 +76,7 @@ export function App (): JSX.Element { {roster === null ? : <>} {(roster != null) && isRosterKT18(roster) ? : <>} - {(roster != null) && isRosterKT21(roster) ? : <>} + {(roster != null) && isRosterKT21(roster) ? : <>} diff --git a/src/components/KillTeam2021/Roster.tsx b/src/components/KillTeam2021/Roster.tsx index e349071f..3814246d 100644 --- a/src/components/KillTeam2021/Roster.tsx +++ b/src/components/KillTeam2021/Roster.tsx @@ -1,4 +1,4 @@ -import React, { MouseEvent } from 'react' +import React, { MouseEvent, useEffect, useState } from 'react' import { Col, Card } from 'react-bootstrap' import { CloseButton } from '../CloseButton' import { Operative, Datacard, PsychicPower } from '../../types/KillTeam2021' @@ -7,7 +7,9 @@ import { RuleList } from './RuleList' import { PowerList } from './PowerList' import hash from 'node-object-hash' import _ from 'lodash' + import { FactionSpecificData } from './FactionSpecificData/FactionSpecificData' +import { RosterSelection } from './RosterSelection' interface Props { name: string @@ -16,11 +18,15 @@ interface Props { psychicPowers: PsychicPower[] fireteams: string[] onClose: (event: MouseEvent) => void + isRoster?: boolean showWoundTrack: boolean + rosterSelection: boolean + printRosterList: boolean } -const groupByDatacard = (operatives: Operative[]): Datacard[] => { - const groupedOperatives = _.groupBy(operatives, (o) => (hash().hash({ datacard: o.datacard, weapons: o.weapons, equipment: o.equipment }))) +const groupByDatacard = (operatives: Operative[], selectedOperatives: string[]): Datacard[] => { + const filteredOperatives = operatives.filter((op) => { return selectedOperatives.includes(op.id) }) + const groupedOperatives = _.groupBy(filteredOperatives, (o) => (hash().hash({ datacard: o.datacard, weapons: o.weapons, equipment: o.equipment }))) return _.map(groupedOperatives, (ops, hash) => ({ ...ops[0], name: ops[0].datacard, @@ -36,7 +42,20 @@ export function Roster (props: Props): JSX.Element { width: '100%', display: 'flex' } - const datacards = groupByDatacard(props.operatives) + + const [selectedOperatives, setSelectedOperatives] = useState(props.operatives.map((operative, index) => { return operative.id })) + const [datacards, setDataCards] = useState([]) + + const updateSelectedOperatives = (operativeIds: string[]): void => { + setSelectedOperatives(operativeIds) + setDataCards(groupByDatacard(props.operatives, selectedOperatives)) + } + + useEffect(() => { + setDataCards(groupByDatacard(props.operatives, selectedOperatives)) + }, [props.operatives, selectedOperatives]) + + const rosterClassName = props.printRosterList ? '' : 'noprint' return ( <> @@ -48,8 +67,16 @@ export function Roster (props: Props): JSX.Element { + {(props.isRoster ?? false) && (props.rosterSelection) && ( + + Roster + + + + + )} {_.orderBy(datacards, ['leader', 'name'], ['desc', 'asc']).map((datacard: Datacard) => ( - + ))} Rules diff --git a/src/components/KillTeam2021/RosterSelection.tsx b/src/components/KillTeam2021/RosterSelection.tsx new file mode 100644 index 00000000..b18cf10a --- /dev/null +++ b/src/components/KillTeam2021/RosterSelection.tsx @@ -0,0 +1,79 @@ +import { Operative, Weapon } from '../../types/KillTeam2021' +import { Form, Row, Table } from 'react-bootstrap' +import React, { FC } from 'react' + +interface Props { + operatives: Operative[] + selectedOperatives: string[] + setSelectedOperatives: (opIds: string[]) => void +} + +const weaponNames = (weapons: Weapon[]): JSX.Element[] => { + return weapons.map((weapon, index) => { + return {weapon.name} + }) +} + +const operativeName = (operative: Operative): JSX.Element => { + // TODO: Hasn't been tested with a roster with actual names for units, I think this is only supported if you pay for battlescribe + const name = operative.name.includes(operative.datacard) ? operative.name : `${operative.name} [${operative.datacard}]` + return {name} +} + +const flipSelection = (selectedOperatives: string[], opId: string): string[] => { + return selectionChanged(selectedOperatives, opId, !selectedOperatives.includes(opId)) +} + +const selectionChanged = (selectedOperatives: string[], opId: string, selected: boolean): string[] => { + let selectedOps = [] + if (selected) { + selectedOps = [ + opId, + ...selectedOperatives + ] + } else { + const idx = selectedOperatives.indexOf(opId) + if (idx > -1) { + selectedOperatives.splice(idx, 1) + } + selectedOps = selectedOperatives + } + return selectedOps +} + +export const RosterSelection: FC = (props: Props) => { + return ( + + + + + + + + + {props.operatives.map((op) => { + const selected = props.selectedOperatives.includes(op.id) + const className = selected ? '' : 'unselected' + return ( + { props.setSelectedOperatives(flipSelection(props.selectedOperatives, op.id)) }} + className={className} + > + + + + + ) + })} + +
OperativeWeapons +
{operativeName(op)}{weaponNames(op.weapons)} + +
+ ) +} diff --git a/src/components/SettingsDialog.tsx b/src/components/SettingsDialog.tsx index 5162f421..24e47541 100644 --- a/src/components/SettingsDialog.tsx +++ b/src/components/SettingsDialog.tsx @@ -15,7 +15,27 @@ function SettingsDialog (props: Props): JSX.Element { const showWoundTrack: boolean = target.checked const newSettings = { ...props.settings, - showWoundTrack: showWoundTrack + showWoundTrack + } + props.setSettings(newSettings) + } + + const handlePrintRosterChange = (event: any): void => { + const target = event.target + const printRosterList: boolean = target.checked + const newSettings = { + ...props.settings, + printRosterList + } + props.setSettings(newSettings) + } + + const handleRosterSelectionChange = (event: any): void => { + const target = event.target + const rosterSelection: boolean = target.checked + const newSettings = { + ...props.settings, + rosterSelection } props.setSettings(newSettings) } @@ -43,6 +63,20 @@ function SettingsDialog (props: Props): JSX.Element { onChange={handleWoundTrackChange} checked={props.settings.showWoundTrack} /> + + diff --git a/src/index.scss b/src/index.scss index b1b0886c..b492a56b 100644 --- a/src/index.scss +++ b/src/index.scss @@ -41,6 +41,16 @@ h1, h2, h3, h4, h5 { } } +.unselected { + opacity: 0.25; +} + +.noprint { + @media print { + display: none; + } +} + .card { margin-bottom: 1em; break-inside: avoid; diff --git a/src/parsers/KillTeam2021/BattlescribeParser.ts b/src/parsers/KillTeam2021/BattlescribeParser.ts index b8f43871..f5009ce3 100644 --- a/src/parsers/KillTeam2021/BattlescribeParser.ts +++ b/src/parsers/KillTeam2021/BattlescribeParser.ts @@ -136,6 +136,8 @@ export const parseBattlescribeXML = (doc: Document): Roster => { const operatives = [] const name = xpSelect('string(/bs:roster/@name)', doc, true).toString() const faction = xpSelect('string(//bs:force/@catalogueName)', doc, true).toString() + const isRoster = xpSelect('string(//bs:force/@name)', doc, true).toString() === 'Roster' + for (const model of xpSelect('//bs:selection[@type=\'model\']', doc) as Element[]) { operatives.push(parseOperative(model)) } @@ -159,12 +161,14 @@ export const parseBattlescribeXML = (doc: Document): Roster => { o.name = o.datacard + ' ' + romanNumerals[counts[o.datacard]++] } } + return { system: 'KillTeam2021', name, faction, operatives, psychicPowers, - fireteams + fireteams, + isRoster } } diff --git a/src/types/KillTeam2021.ts b/src/types/KillTeam2021.ts index 6842b251..9eb3648b 100644 --- a/src/types/KillTeam2021.ts +++ b/src/types/KillTeam2021.ts @@ -95,6 +95,7 @@ export interface Roster { operatives: Operative[] psychicPowers: PsychicPower[] fireteams: string[] + isRoster?: boolean } export enum Archetype { diff --git a/src/types/Settings.ts b/src/types/Settings.ts index 99e1d7b9..cb413dd7 100644 --- a/src/types/Settings.ts +++ b/src/types/Settings.ts @@ -1,3 +1,5 @@ export interface Settings { showWoundTrack: boolean + printRosterList: boolean + rosterSelection: boolean }