Grabbed dndbeyond's source code
``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ```
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import React from "react";
|
||||
|
||||
import { CharacterTheme } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { GD_VehicleBlockProps } from "../../utils/Component";
|
||||
import VehicleBlockActionStations from "./VehicleBlockActionStations";
|
||||
import VehicleBlockActionSummaries from "./VehicleBlockActionSummaries";
|
||||
import VehicleBlockActions from "./VehicleBlockActions";
|
||||
import VehicleBlockComponents from "./VehicleBlockComponents";
|
||||
import VehicleBlockFeatures from "./VehicleBlockFeatures";
|
||||
import VehicleBlockHeader from "./VehicleBlockHeader";
|
||||
import VehicleBlockPrimary from "./VehicleBlockPrimary";
|
||||
import VehicleBlockShell from "./VehicleBlockShell";
|
||||
|
||||
interface Props extends GD_VehicleBlockProps {
|
||||
isInteractive: boolean;
|
||||
onComponentClick?: (componentId: number, vehicleId: number) => void;
|
||||
onActionStationClick?: (stationId: number, vehicleId: number) => void;
|
||||
shouldCoalesce: boolean;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
export default class VehicleBlock extends React.PureComponent<Props, {}> {
|
||||
static defaultProps = {
|
||||
isInteractive: false,
|
||||
shouldCoalesce: true,
|
||||
featuresProps: null,
|
||||
actionsProps: null,
|
||||
actionSummariesProps: null,
|
||||
actionStationsProps: null,
|
||||
componentsProps: null,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
headerProps,
|
||||
primaryProps,
|
||||
actionSummariesProps,
|
||||
featuresProps,
|
||||
actionsProps,
|
||||
actionStationsProps,
|
||||
componentsProps,
|
||||
shellProps,
|
||||
isInteractive,
|
||||
onComponentClick,
|
||||
onActionStationClick,
|
||||
shouldCoalesce,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<VehicleBlockShell {...shellProps}>
|
||||
<VehicleBlockHeader {...headerProps} />
|
||||
<VehicleBlockPrimary
|
||||
{...primaryProps}
|
||||
shouldCoalesce={shouldCoalesce}
|
||||
/>
|
||||
{featuresProps !== null && <VehicleBlockFeatures {...featuresProps} />}
|
||||
{actionSummariesProps !== null && (
|
||||
<VehicleBlockActionSummaries
|
||||
{...actionSummariesProps}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
{actionStationsProps !== null && (
|
||||
<VehicleBlockActionStations
|
||||
{...actionStationsProps}
|
||||
theme={theme}
|
||||
isInteractive={isInteractive}
|
||||
onActionStationClick={onActionStationClick}
|
||||
shouldCoalesce={shouldCoalesce}
|
||||
/>
|
||||
)}
|
||||
{componentsProps !== null && (
|
||||
<VehicleBlockComponents
|
||||
{...componentsProps}
|
||||
theme={theme}
|
||||
isInteractive={isInteractive}
|
||||
onComponentClick={onComponentClick}
|
||||
shouldCoalesce={shouldCoalesce}
|
||||
/>
|
||||
)}
|
||||
{actionsProps !== null && <VehicleBlockActions {...actionsProps} />}
|
||||
</VehicleBlockShell>
|
||||
);
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
|
||||
import { HtmlContent } from "~/components/HtmlContent";
|
||||
|
||||
import { VehicleBlockActionProp } from "../../../utils/Component";
|
||||
import InlineSeparatedNodes from "../../common/InlineSeparatedNodes";
|
||||
|
||||
interface Props {
|
||||
action: VehicleBlockActionProp;
|
||||
}
|
||||
export default class VehicleBlockAction extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { action } = this.props;
|
||||
|
||||
const { key, name, description, ammo } = action;
|
||||
|
||||
const actionText: Array<React.ReactNode> = [];
|
||||
|
||||
if (name) {
|
||||
actionText.push(
|
||||
<span className="ct-vehicle-block__action-name">{name}.</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (ammo !== null && ammo.length > 0) {
|
||||
let ammunitionText: Array<string> = [];
|
||||
ammo.forEach((ammoContract) => {
|
||||
let text: string = `${ammoContract.quantity}`;
|
||||
if (ammoContract.custom !== null) {
|
||||
text = `${text} ${ammoContract.custom}`;
|
||||
}
|
||||
|
||||
ammunitionText.push(text);
|
||||
});
|
||||
actionText.push(
|
||||
<React.Fragment>
|
||||
<span className="ct-vehicle-block__action-ammo-label">
|
||||
Ammunition:{" "}
|
||||
</span>
|
||||
<span className="ct-vehicle-block__action-ammo-description">
|
||||
{ammunitionText.join(", ")}.
|
||||
</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
if (description) {
|
||||
//All vehicle && component actions descriptions are currently formatted html,
|
||||
//should use action props derivers to generate the content displayed in this description in the future
|
||||
actionText.push(
|
||||
<React.Fragment>
|
||||
<HtmlContent
|
||||
className="ct-vehicle-block__action-description"
|
||||
html={description}
|
||||
withoutTooltips
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block__action" key={key}>
|
||||
<InlineSeparatedNodes nodes={actionText} sep={" "} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockAction from "./VehicleBlockAction";
|
||||
|
||||
export default VehicleBlockAction;
|
||||
export { VehicleBlockAction };
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
import React from "react";
|
||||
|
||||
import { ManageIcon } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
CharacterTheme,
|
||||
FormatUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { GD_VehicleBlockActionStationProps } from "../../../utils/Component";
|
||||
import InlineSeparatedNodes from "../../common/InlineSeparatedNodes";
|
||||
import VehicleBlockAction from "../VehicleBlockAction";
|
||||
import VehicleBlockPrimaryAttributes from "../VehicleBlockPrimaryAttributes";
|
||||
|
||||
interface Props extends GD_VehicleBlockActionStationProps {
|
||||
className: string;
|
||||
shouldCoalesce: boolean;
|
||||
onActionStationClick?: (id: number, vehicleId: number) => void;
|
||||
isInteractive: boolean;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
export default class VehicleBlockActionStation extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
isInteractive: false,
|
||||
shouldCoalesce: true,
|
||||
};
|
||||
|
||||
renderHeading = (): React.ReactNode => {
|
||||
const { name, requiredCrew, coverType, count } = this.props;
|
||||
|
||||
let displayText: Array<string> = [];
|
||||
|
||||
if (count > 1) {
|
||||
displayText.push(`${count}`);
|
||||
}
|
||||
|
||||
if (name !== null) {
|
||||
displayText.push(name);
|
||||
}
|
||||
|
||||
let extraText: Array<string> = [];
|
||||
if (requiredCrew !== null) {
|
||||
let requiredCrewText: string = `Requires ${requiredCrew} Crew`;
|
||||
if (count > 1) {
|
||||
requiredCrewText = `Each Station ${requiredCrewText}`;
|
||||
}
|
||||
extraText.push(requiredCrewText);
|
||||
}
|
||||
|
||||
if (coverType !== null) {
|
||||
extraText.push(`Grants ${coverType} Cover`);
|
||||
}
|
||||
|
||||
if (extraText.length > 0) {
|
||||
displayText.push(`(${FormatUtils.renderNonOxfordCommaList(extraText)})`);
|
||||
}
|
||||
|
||||
if (displayText.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="ct-vehicle-block-action-station__heading">
|
||||
{displayText.join(" ")}.
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
renderActions = (): React.ReactNode => {
|
||||
const { actions } = this.props;
|
||||
|
||||
if (actions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="ct-vehicle-block-action-station__actions">
|
||||
{actions.map((action) => (
|
||||
<VehicleBlockAction action={action} key={action.key} />
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
renderDescription = (): React.ReactNode => {
|
||||
const { description } = this.props;
|
||||
|
||||
if (description === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="ct-vehicle-block-action-station__description">
|
||||
{description}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
handleClick = (): void => {
|
||||
const { id, vehicleId, isInteractive, onActionStationClick } = this.props;
|
||||
|
||||
if (isInteractive && onActionStationClick) {
|
||||
onActionStationClick(id, vehicleId);
|
||||
}
|
||||
};
|
||||
|
||||
renderAttributes = (): React.ReactNode => {
|
||||
const {
|
||||
count,
|
||||
shouldCoalesce,
|
||||
armorClassInfo,
|
||||
hitPointInfo,
|
||||
speedInfos,
|
||||
isInteractive,
|
||||
theme,
|
||||
costInfos,
|
||||
displayType,
|
||||
width,
|
||||
length,
|
||||
isPrimaryComponent,
|
||||
} = this.props;
|
||||
|
||||
if (
|
||||
armorClassInfo === null &&
|
||||
hitPointInfo === null &&
|
||||
speedInfos.length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block-action-station__attributes">
|
||||
{isInteractive && (
|
||||
<div className="ct-vehicle-block-action-station__attributes-callout">
|
||||
<ManageIcon
|
||||
tooltip="Manage HP"
|
||||
showIconOnEdge={false}
|
||||
theme={theme}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<VehicleBlockPrimaryAttributes
|
||||
armorClassInfo={armorClassInfo}
|
||||
hitPointInfo={hitPointInfo}
|
||||
speedInfos={speedInfos}
|
||||
count={count}
|
||||
shouldCoalesce={shouldCoalesce}
|
||||
costInfos={costInfos}
|
||||
displayType={displayType}
|
||||
width={width}
|
||||
length={length}
|
||||
isPrimaryComponent={isPrimaryComponent}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isInteractive } = this.props;
|
||||
|
||||
let classNames: Array<string> = ["ct-vehicle-block-action-station"];
|
||||
if (isInteractive) {
|
||||
classNames.push("ct-vehicle-block-action-station--interactive");
|
||||
}
|
||||
|
||||
let nodes: Array<React.ReactNode> = [];
|
||||
const headingNode = this.renderHeading();
|
||||
if (headingNode !== null) {
|
||||
nodes.push(headingNode);
|
||||
}
|
||||
|
||||
const descriptionNode = this.renderDescription();
|
||||
if (descriptionNode !== null) {
|
||||
nodes.push(descriptionNode);
|
||||
}
|
||||
|
||||
const actionsNode = this.renderActions();
|
||||
if (actionsNode !== null) {
|
||||
nodes.push(actionsNode);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames.join(" ")} onClick={this.handleClick}>
|
||||
<div className="ct-vehicle-block-action-station__content">
|
||||
<InlineSeparatedNodes nodes={nodes} sep={" "} />
|
||||
{this.renderAttributes()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockActionStation from "./VehicleBlockActionStation";
|
||||
|
||||
export default VehicleBlockActionStation;
|
||||
export { VehicleBlockActionStation };
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import { values, sortBy } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
import { CharacterTheme } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import {
|
||||
GD_VehicleBlockActionStationListProps,
|
||||
GD_VehicleBlockActionStationProps,
|
||||
} from "../../../utils/Component";
|
||||
import VehicleBlockActionStation from "../VehicleBlockActionStation";
|
||||
import VehicleBlockActionStationsShell from "../VehicleBlockActionStationsShell";
|
||||
|
||||
interface Props extends GD_VehicleBlockActionStationListProps {
|
||||
isInteractive: boolean;
|
||||
shouldCoalesce: boolean;
|
||||
theme: CharacterTheme;
|
||||
onActionStationClick?: (stationId: number, vehicleId: number) => void;
|
||||
}
|
||||
export default class VehicleBlockActionStations extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
shouldCoalesce: true,
|
||||
isInteractive: false,
|
||||
};
|
||||
|
||||
getActionStationProps = (): Array<GD_VehicleBlockActionStationProps> => {
|
||||
const { actionStations, shouldCoalesce } = this.props;
|
||||
|
||||
let displayActionStations: Array<GD_VehicleBlockActionStationProps> =
|
||||
actionStations;
|
||||
|
||||
if (shouldCoalesce) {
|
||||
let uniquenessFactorLookup: Record<
|
||||
string,
|
||||
GD_VehicleBlockActionStationProps
|
||||
> = displayActionStations.reduce(
|
||||
(acc: Record<string, GD_VehicleBlockActionStationProps>, station) => {
|
||||
let uniqueness: string = station.uniquenessFactor;
|
||||
|
||||
if (!acc[uniqueness]) {
|
||||
acc[uniqueness] = station;
|
||||
} else {
|
||||
acc[uniqueness].count += 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
displayActionStations = values(uniquenessFactorLookup);
|
||||
}
|
||||
|
||||
return sortBy(
|
||||
displayActionStations,
|
||||
(actionStation) => actionStation.displayOrder
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
actionStations,
|
||||
shouldCoalesce,
|
||||
isInteractive,
|
||||
onActionStationClick,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
if (actionStations.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const actionStationsProps = this.getActionStationProps();
|
||||
|
||||
return (
|
||||
<VehicleBlockActionStationsShell>
|
||||
{actionStationsProps.map((stationProps) => (
|
||||
<VehicleBlockActionStation
|
||||
{...stationProps}
|
||||
theme={theme}
|
||||
isInteractive={
|
||||
stationProps.enableComponentManagement && isInteractive
|
||||
}
|
||||
onActionStationClick={onActionStationClick}
|
||||
shouldCoalesce={shouldCoalesce}
|
||||
/>
|
||||
))}
|
||||
</VehicleBlockActionStationsShell>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockActionStations from "./VehicleBlockActionStations";
|
||||
|
||||
export default VehicleBlockActionStations;
|
||||
export { VehicleBlockActionStations };
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
import VehicleBlockSectionHeader from "../VehicleBlockSectionHeader";
|
||||
|
||||
export default class VehicleBlockActionStationsShell extends React.PureComponent<{}> {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-vehicle-block__action-stations">
|
||||
<VehicleBlockSectionHeader label="Action Stations" />
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockActionStationsShell from "./VehicleBlockActionStationsShell";
|
||||
|
||||
export default VehicleBlockActionStationsShell;
|
||||
export { VehicleBlockActionStationsShell };
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import React from "react";
|
||||
|
||||
import { CharacterTheme } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { GD_VehicleBlockActionSummariesProps } from "../../../utils/Component";
|
||||
import VehicleBlockActionSummary from "../VehicleBlockActionSummary";
|
||||
import VehicleBlockSectionHeader from "../VehicleBlockSectionHeader";
|
||||
|
||||
interface Props extends GD_VehicleBlockActionSummariesProps {
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
export default class VehicleBlockActionSummaries extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { actionsText, actionsSummaries, theme } = this.props;
|
||||
|
||||
if (actionsText === null && actionsSummaries.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block__action-summaries">
|
||||
<VehicleBlockSectionHeader label="Actions" />
|
||||
{actionsText !== null && (
|
||||
<div className="ct-vehicle-block__action-summaries-content">
|
||||
{actionsText}
|
||||
</div>
|
||||
)}
|
||||
{actionsSummaries.length > 0 && (
|
||||
<div className="ct-vehicle-block__action-summaries-content">
|
||||
{actionsSummaries.map((summary, idx) => {
|
||||
const key = `${summary.name !== null ? summary.name : ""}-${idx}`;
|
||||
return (
|
||||
<VehicleBlockActionSummary
|
||||
theme={theme}
|
||||
actionSummary={summary}
|
||||
key={key}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockActionSummaries from "./VehicleBlockActionSummaries";
|
||||
|
||||
export default VehicleBlockActionSummaries;
|
||||
export { VehicleBlockActionSummaries };
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import React from "react";
|
||||
|
||||
import { CharacterTheme } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { Reference } from "~/components/Reference";
|
||||
|
||||
import { VehicleActionSummaryProps } from "../../../utils/Component";
|
||||
import InlineSeparatedNodes from "../../common/InlineSeparatedNodes";
|
||||
|
||||
interface Props {
|
||||
actionSummary: VehicleActionSummaryProps;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
export default class VehicleBlockActionSummary extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
sourceId,
|
||||
sourceName,
|
||||
sourceFullName,
|
||||
sourceChapterNumber,
|
||||
} = this.props.actionSummary;
|
||||
const { theme } = this.props;
|
||||
|
||||
let nodes: Array<React.ReactNode> = [];
|
||||
|
||||
if (name !== null) {
|
||||
nodes.push(
|
||||
<span className="ct-vehicle-block__action-summary-label">{name}.</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (description !== null) {
|
||||
nodes.push(
|
||||
<span className="ct-vehicle-block__action-summary-text">
|
||||
{description}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (sourceId !== null) {
|
||||
nodes.push(
|
||||
<React.Fragment>
|
||||
{"("}
|
||||
<Reference
|
||||
isDarkMode={theme?.isDarkMode}
|
||||
name={sourceName}
|
||||
tooltip={sourceFullName}
|
||||
chapter={sourceChapterNumber}
|
||||
/>
|
||||
{")."}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block__action-summary">
|
||||
<InlineSeparatedNodes nodes={nodes} sep={" "} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockActionSummary from "./VehicleBlockActionSummary";
|
||||
|
||||
export default VehicleBlockActionSummary;
|
||||
export { VehicleBlockActionSummary };
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
|
||||
import { GD_VehicleBlockActionsProps } from "../../../utils/Component";
|
||||
import VehicleBlockAction from "../VehicleBlockAction";
|
||||
import VehicleBlockSectionHeader from "../VehicleBlockSectionHeader";
|
||||
|
||||
interface Props extends GD_VehicleBlockActionsProps {}
|
||||
export default class VehicleBlockActions extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
reactions: [],
|
||||
bonusActions: [],
|
||||
special: [],
|
||||
};
|
||||
|
||||
render() {
|
||||
const { reactions, bonusActions, special } = this.props;
|
||||
|
||||
if (
|
||||
reactions.length === 0 &&
|
||||
bonusActions.length === 0 &&
|
||||
special.length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{reactions.length > 0 && (
|
||||
<React.Fragment>
|
||||
<VehicleBlockSectionHeader label="Reactions" />
|
||||
{reactions.map((action) => (
|
||||
<VehicleBlockAction action={action} key={action.key} />
|
||||
))}
|
||||
</React.Fragment>
|
||||
)}
|
||||
{bonusActions.length > 0 && (
|
||||
<React.Fragment>
|
||||
<VehicleBlockSectionHeader label="Bonus Actions" />
|
||||
{bonusActions.map((action) => (
|
||||
<VehicleBlockAction action={action} key={action.key} />
|
||||
))}
|
||||
</React.Fragment>
|
||||
)}
|
||||
{special.length > 0 && (
|
||||
<React.Fragment>
|
||||
<VehicleBlockSectionHeader label="Special Actions" />
|
||||
{special.map((action) => (
|
||||
<VehicleBlockAction action={action} key={action.key} />
|
||||
))}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockActions from "./VehicleBlockActions";
|
||||
|
||||
export default VehicleBlockActions;
|
||||
export { VehicleBlockActions };
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
value: React.ReactNode;
|
||||
extraValue: React.ReactNode;
|
||||
}
|
||||
export default class VehicleBlockAttribute extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
extraValue: null,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { label, value, extraValue } = this.props;
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block__attribute">
|
||||
<span className="ct-vehicle-block__attribute-label">{label}</span>
|
||||
<span className="ct-vehicle-block__attribute-data">
|
||||
<span className="ct-vehicle-block__attribute-data-value">
|
||||
{value}
|
||||
</span>
|
||||
{extraValue !== null && (
|
||||
<span className="ct-vehicle-block__attribute-data-extra">
|
||||
{extraValue}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
import React from "react";
|
||||
|
||||
import { ManageIcon } from "@dndbeyond/character-components/es";
|
||||
import {
|
||||
CharacterTheme,
|
||||
Constants,
|
||||
FormatUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { HtmlContent } from "~/components/HtmlContent";
|
||||
|
||||
import { GD_VehicleBlockComponentProps } from "../../../utils/Component";
|
||||
import VehicleBlockAction from "../VehicleBlockAction";
|
||||
import VehicleBlockPrimaryAttributes from "../VehicleBlockPrimaryAttributes";
|
||||
import VehicleBlockSectionHeader from "../VehicleBlockSectionHeader";
|
||||
|
||||
interface Props extends GD_VehicleBlockComponentProps {
|
||||
className: string;
|
||||
shouldCoalesce: boolean;
|
||||
onComponentClick?: (id: number, vehicleId: number) => void;
|
||||
isInteractive: boolean;
|
||||
theme: CharacterTheme;
|
||||
}
|
||||
export default class VehicleBlockComponent extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
isInteractive: false,
|
||||
shouldCoalesce: true,
|
||||
};
|
||||
|
||||
renderHeading = (): React.ReactNode => {
|
||||
const {
|
||||
typeNames,
|
||||
name,
|
||||
count,
|
||||
isInteractive,
|
||||
theme,
|
||||
displayType,
|
||||
requiredCrew,
|
||||
} = this.props;
|
||||
|
||||
const nameLabel: string = name !== null ? name : "";
|
||||
const isSpelljammer =
|
||||
displayType === Constants.VehicleConfigurationDisplayTypeEnum.SPELLJAMMER;
|
||||
|
||||
let componentCount: string = "";
|
||||
if (count > 1) {
|
||||
componentCount = `(${count})`;
|
||||
}
|
||||
|
||||
let primaryText: string = "";
|
||||
if (name !== null && typeNames.includes(name)) {
|
||||
primaryText = nameLabel;
|
||||
} else if (isSpelljammer) {
|
||||
primaryText = `${count > 1 ? `${count} ` : ""}${nameLabel}${
|
||||
requiredCrew ? ` (${requiredCrew} Crew${count > 1 ? " Each" : ""})` : ""
|
||||
}`;
|
||||
} else {
|
||||
let typesText = FormatUtils.renderNonOxfordCommaList(typeNames);
|
||||
primaryText = `${typesText}: ${nameLabel} ${componentCount}`;
|
||||
}
|
||||
|
||||
let callout: React.ReactNode = null;
|
||||
if (isInteractive) {
|
||||
callout = (
|
||||
<ManageIcon tooltip="Manage HP" showIconOnEdge={false} theme={theme} />
|
||||
);
|
||||
}
|
||||
|
||||
return <VehicleBlockSectionHeader label={primaryText} callout={callout} />;
|
||||
};
|
||||
|
||||
renderActions = (): React.ReactNode => {
|
||||
const { actions } = this.props;
|
||||
|
||||
if (actions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block-component__actions">
|
||||
{actions.map((action) => (
|
||||
<VehicleBlockAction action={action} key={action.key} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
renderDescription = (): React.ReactNode => {
|
||||
const { description } = this.props;
|
||||
|
||||
if (description === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<HtmlContent
|
||||
className="ct-vehicle-block-component__description"
|
||||
html={description}
|
||||
withoutTooltips
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
handleClick = (): void => {
|
||||
const { id, vehicleId, isInteractive, onComponentClick } = this.props;
|
||||
|
||||
if (isInteractive && onComponentClick) {
|
||||
onComponentClick(id, vehicleId);
|
||||
}
|
||||
};
|
||||
|
||||
renderAttributes = (): React.ReactNode => {
|
||||
const {
|
||||
count,
|
||||
costInfos,
|
||||
shouldCoalesce,
|
||||
armorClassInfo,
|
||||
hitPointInfo,
|
||||
speedInfos,
|
||||
coverType,
|
||||
requiredCrew,
|
||||
displayType,
|
||||
width,
|
||||
length,
|
||||
isPrimaryComponent,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block-component__attributes">
|
||||
<VehicleBlockPrimaryAttributes
|
||||
armorClassInfo={armorClassInfo}
|
||||
hitPointInfo={hitPointInfo}
|
||||
speedInfos={speedInfos}
|
||||
coverType={coverType}
|
||||
requiredCrew={requiredCrew}
|
||||
count={count}
|
||||
shouldCoalesce={shouldCoalesce}
|
||||
costInfos={costInfos}
|
||||
displayType={displayType}
|
||||
width={width}
|
||||
length={length}
|
||||
isPrimaryComponent={isPrimaryComponent}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
renderActionsAndDescription() {
|
||||
const { displayType } = this.props;
|
||||
const isSpelljammer =
|
||||
displayType === Constants.VehicleConfigurationDisplayTypeEnum.SPELLJAMMER;
|
||||
return isSpelljammer ? (
|
||||
<>
|
||||
{this.renderDescription()}
|
||||
{this.renderActions()}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{this.renderActions()}
|
||||
{this.renderDescription()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isInteractive, uniqueKey } = this.props;
|
||||
|
||||
let classNames: Array<string> = ["ct-vehicle-block-component"];
|
||||
if (isInteractive) {
|
||||
classNames.push("ct-vehicle-block-component--interactive");
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={uniqueKey}
|
||||
className={classNames.join(" ")}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{this.renderHeading()}
|
||||
<div className="ct-vehicle-block-component__content">
|
||||
{this.renderAttributes()}
|
||||
{this.renderActionsAndDescription()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockComponent from "./VehicleBlockComponent";
|
||||
|
||||
export default VehicleBlockComponent;
|
||||
export { VehicleBlockComponent };
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import { values, sortBy, has } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
CharacterTheme,
|
||||
Constants,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import {
|
||||
GD_VehicleBlockComponentListProps,
|
||||
GD_VehicleBlockComponentProps,
|
||||
} from "../../../utils/Component";
|
||||
import VehicleBlockComponent from "../VehicleBlockComponent";
|
||||
import VehicleBlockComponentsShell from "../VehicleBlockComponentsShell";
|
||||
|
||||
interface Props extends GD_VehicleBlockComponentListProps {
|
||||
shouldCoalesce: boolean;
|
||||
isInteractive: boolean;
|
||||
theme: CharacterTheme;
|
||||
onComponentClick?: (componentId: number, vehicleId: number) => void;
|
||||
}
|
||||
export default class VehicleBlockComponents extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
shouldCoalesce: true,
|
||||
};
|
||||
|
||||
getComponentInfos = (): Array<GD_VehicleBlockComponentProps> => {
|
||||
const { components, shouldCoalesce } = this.props;
|
||||
|
||||
let displayComponents: Array<GD_VehicleBlockComponentProps> = components;
|
||||
|
||||
if (shouldCoalesce) {
|
||||
const uniquenessFactorLookup: Record<
|
||||
string,
|
||||
GD_VehicleBlockComponentProps
|
||||
> = components.reduce(
|
||||
(acc: Record<string, GD_VehicleBlockComponentProps>, component) => {
|
||||
let uniqueness = component.uniquenessFactor;
|
||||
|
||||
if (!acc[uniqueness]) {
|
||||
acc[uniqueness] = component;
|
||||
} else {
|
||||
acc[uniqueness].count += 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
displayComponents = values(uniquenessFactorLookup);
|
||||
}
|
||||
|
||||
return sortBy(displayComponents, (component) => component.displayOrder);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { shouldCoalesce, isInteractive, onComponentClick, theme } =
|
||||
this.props;
|
||||
|
||||
let components = this.getComponentInfos();
|
||||
|
||||
if (components.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Components only not action stations (Currently due to Spelljammer)
|
||||
// Filters out helm (primary component) from Spelljammer
|
||||
components = components.filter(
|
||||
(component) =>
|
||||
!(
|
||||
component.displayType ===
|
||||
Constants.VehicleConfigurationDisplayTypeEnum.SPELLJAMMER &&
|
||||
component.isPrimaryComponent
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<VehicleBlockComponentsShell>
|
||||
{components.map((componentProps) => (
|
||||
<VehicleBlockComponent
|
||||
{...componentProps}
|
||||
theme={theme}
|
||||
isInteractive={
|
||||
componentProps.enableComponentManagement && isInteractive
|
||||
}
|
||||
onComponentClick={onComponentClick}
|
||||
shouldCoalesce={shouldCoalesce}
|
||||
/>
|
||||
))}
|
||||
</VehicleBlockComponentsShell>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockComponents from "./VehicleBlockComponents";
|
||||
|
||||
export default VehicleBlockComponents;
|
||||
export { VehicleBlockComponents };
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
|
||||
export default class VehicleBlockComponentsShell extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div className="ct-vehicle-block__components-block">
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockComponentsShell from "./VehicleBlockComponentsShell";
|
||||
|
||||
export default VehicleBlockComponentsShell;
|
||||
export { VehicleBlockComponentsShell };
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
|
||||
import { GD_VehicleBlockFeaturesProps } from "../../../utils/Component";
|
||||
|
||||
interface Props extends GD_VehicleBlockFeaturesProps {}
|
||||
export default class VehicleBlockFeatures extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
features: [],
|
||||
};
|
||||
|
||||
render() {
|
||||
const { features } = this.props;
|
||||
|
||||
if (features.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block__features">
|
||||
{features.map((feature, idx) => {
|
||||
return (
|
||||
<div
|
||||
className="ct-vehicle-block__features-feature"
|
||||
key={`${feature.name}-${idx}`}
|
||||
>
|
||||
<span className="ct-vehicle-block__features-feature-name">
|
||||
{feature.name}.
|
||||
</span>{" "}
|
||||
<span className="ct-vehicle-block__features-feature-description">
|
||||
{feature.description}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockFeatures from "./VehicleBlockFeatures";
|
||||
|
||||
export default VehicleBlockFeatures;
|
||||
export { VehicleBlockFeatures };
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
import React from "react";
|
||||
|
||||
import { Constants, FormatUtils } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { GD_VehicleBlockHeaderProps } from "../../../utils/Component";
|
||||
import InlineSeparatedNodes from "../../common/InlineSeparatedNodes";
|
||||
|
||||
interface Props extends GD_VehicleBlockHeaderProps {}
|
||||
export default class VehicleBlockHeader extends React.PureComponent<Props> {
|
||||
renderTypeInfo = (): React.ReactNode => {
|
||||
const { sizeName, typeName } = this.props;
|
||||
|
||||
if (sizeName === null && typeName === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let size: string = sizeName !== null ? sizeName : "";
|
||||
let type: string = typeName !== null ? typeName.toLowerCase() : "";
|
||||
|
||||
return [size, type].join(" ").trim();
|
||||
};
|
||||
|
||||
renderDimensions = (): React.ReactNode => {
|
||||
const { weight, width, length } = this.props;
|
||||
|
||||
let dimensions: Array<string> = [];
|
||||
|
||||
let lengthDisplay: string | null =
|
||||
length !== null ? FormatUtils.renderDistance(length) : null;
|
||||
if (lengthDisplay !== null) {
|
||||
dimensions.push(lengthDisplay);
|
||||
}
|
||||
|
||||
let widthDisplay: string | null =
|
||||
width !== null ? FormatUtils.renderDistance(width) : null;
|
||||
if (widthDisplay !== null) {
|
||||
dimensions.push(widthDisplay);
|
||||
}
|
||||
|
||||
let metaTextSizeNodes: Array<React.ReactNode> = [];
|
||||
|
||||
if (dimensions.length > 0) {
|
||||
metaTextSizeNodes.push(dimensions.join(" by "));
|
||||
}
|
||||
|
||||
let weightDisplay: string | null =
|
||||
weight !== null ? `${FormatUtils.renderWeight(weight)}.` : null;
|
||||
if (weightDisplay !== null) {
|
||||
metaTextSizeNodes.push(weightDisplay);
|
||||
}
|
||||
|
||||
if (metaTextSizeNodes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
(<InlineSeparatedNodes nodes={metaTextSizeNodes} />)
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { name, displayType } = this.props;
|
||||
|
||||
const typeInfo = this.renderTypeInfo();
|
||||
const dimensions = this.renderDimensions();
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block__header">
|
||||
<div className="ct-vehicle-block__name">
|
||||
{name !== null ? name : ""}
|
||||
</div>
|
||||
{displayType !==
|
||||
Constants.VehicleConfigurationDisplayTypeEnum.SPELLJAMMER && (
|
||||
<div className="ct-vehicle-block__meta">
|
||||
{typeInfo} {dimensions}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockHeader from "./VehicleBlockHeader";
|
||||
|
||||
export default VehicleBlockHeader;
|
||||
export { VehicleBlockHeader };
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
Constants,
|
||||
CoreUtils,
|
||||
FormatUtils,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { GD_VehicleBlockPrimaryProps } from "../../../utils/Component";
|
||||
import { VehicleBlockAttribute } from "../VehicleBlockAttribute";
|
||||
import VehicleBlockPrimaryAttributes from "../VehicleBlockPrimaryAttributes";
|
||||
import VehicleBlockSeparator from "../VehicleBlockSeparator";
|
||||
|
||||
interface Props extends GD_VehicleBlockPrimaryProps {
|
||||
shouldCoalesce: boolean;
|
||||
}
|
||||
|
||||
export default class VehicleBlockPrimary extends React.PureComponent<Props> {
|
||||
renderDamageImmunities = (): React.ReactNode => {
|
||||
const { damageImmunities } = this.props;
|
||||
|
||||
if (damageImmunities.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block__tidbit">
|
||||
<span className="ct-vehicle-block__tidbit-label">
|
||||
Damage Immunities
|
||||
</span>
|
||||
<span className="ct-vehicle-block__tidbit-data">
|
||||
{damageImmunities.join(", ")}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
renderConditionImmunities = (): React.ReactNode => {
|
||||
const { conditionImmunities } = this.props;
|
||||
|
||||
if (conditionImmunities.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block__tidbit">
|
||||
<span className="ct-vehicle-block__tidbit-label">
|
||||
Condition Immunities
|
||||
</span>
|
||||
<span className="ct-vehicle-block__tidbit-data">
|
||||
{conditionImmunities.join(", ")}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
renderStats = (): React.ReactNode => {
|
||||
const { stats, displayType } = this.props;
|
||||
|
||||
if (stats.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="ct-vehicle-block__abilities">
|
||||
{stats.map((stat) => {
|
||||
let modifier = stat.modifier !== null ? stat.modifier : 0;
|
||||
let statKey = stat.statKey !== null ? stat.statKey : "";
|
||||
let score = stat.score !== null ? stat.score : 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`ct-vehicle-block__ability-stat ct-vehicle-block__ability-stat--${statKey.toLowerCase()}`}
|
||||
key={statKey}
|
||||
>
|
||||
<div className="ct-vehicle-block__ability-heading">
|
||||
{statKey}
|
||||
</div>
|
||||
<div className="ct-vehicle-block__ability-data">
|
||||
<span className="ct-vehicle-block__ability-score">
|
||||
{score}
|
||||
</span>
|
||||
{score !== 0 && (
|
||||
<span className="ct-vehicle-block__ability-modifier">
|
||||
({FormatUtils.renderSignedNumber(modifier)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<VehicleBlockSeparator displayType={displayType} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderEffectiveTravelDistance = (): React.ReactNode => {
|
||||
const { travelPaceInfo } = this.props;
|
||||
|
||||
if (travelPaceInfo === null || travelPaceInfo.effectiveHours === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const effectiveDistance: number =
|
||||
CoreUtils.convertFeetToMiles(travelPaceInfo.pace) *
|
||||
travelPaceInfo.effectiveHours;
|
||||
|
||||
return `(${effectiveDistance} ${
|
||||
effectiveDistance === 1 ? "mile" : "miles"
|
||||
} per day)`;
|
||||
};
|
||||
|
||||
renderAttributes = (): React.ReactNode => {
|
||||
const {
|
||||
creatureCapacityDescriptions,
|
||||
cargoCapacityInfo,
|
||||
travelPaceInfo,
|
||||
primaryProperties,
|
||||
shouldCoalesce,
|
||||
displayType,
|
||||
} = this.props;
|
||||
|
||||
let cargoCapacity: Array<string> = [];
|
||||
if (cargoCapacityInfo.weight !== null) {
|
||||
let weight: string =
|
||||
cargoCapacityInfo.weight >= Constants.POUNDS_IN_TON
|
||||
? FormatUtils.renderWeightTons(cargoCapacityInfo.weight)
|
||||
: `${FormatUtils.renderWeight(cargoCapacityInfo.weight)}.`;
|
||||
|
||||
cargoCapacity.push(weight);
|
||||
}
|
||||
|
||||
if (cargoCapacityInfo.description) {
|
||||
cargoCapacity.push(cargoCapacityInfo.description);
|
||||
}
|
||||
|
||||
const isSpelljammer =
|
||||
displayType === Constants.VehicleConfigurationDisplayTypeEnum.SPELLJAMMER;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="ct-vehicle-block__attributes">
|
||||
{primaryProperties !== null && (
|
||||
<VehicleBlockPrimaryAttributes
|
||||
armorClassInfo={primaryProperties.armorClassInfo}
|
||||
hitPointInfo={primaryProperties.hitPointInfo}
|
||||
speedInfos={primaryProperties.speedInfos}
|
||||
costInfos={primaryProperties.costInfos}
|
||||
shouldCoalesce={shouldCoalesce}
|
||||
displayType={primaryProperties.displayType}
|
||||
width={primaryProperties.width}
|
||||
length={primaryProperties.length}
|
||||
isPrimaryComponent={primaryProperties.isPrimaryComponent}
|
||||
/>
|
||||
)}
|
||||
{travelPaceInfo !== null && (
|
||||
<VehicleBlockAttribute
|
||||
label="Travel Pace"
|
||||
value={`${FormatUtils.renderDistance(
|
||||
travelPaceInfo.pace
|
||||
)} per hour`}
|
||||
extraValue={this.renderEffectiveTravelDistance()}
|
||||
/>
|
||||
)}
|
||||
<VehicleBlockAttribute
|
||||
label={isSpelljammer ? "Crew" : "Creature Capacity"}
|
||||
value={creatureCapacityDescriptions.join(", ")}
|
||||
/>
|
||||
{cargoCapacity.length > 0 && (
|
||||
<VehicleBlockAttribute
|
||||
label={isSpelljammer ? "Cargo" : "Cargo Capacity"}
|
||||
value={cargoCapacity.join(", ")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!isSpelljammer && <VehicleBlockSeparator displayType={displayType} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderConditionsAndImmunities() {
|
||||
const { displayType, conditionImmunities, damageImmunities } = this.props;
|
||||
|
||||
return conditionImmunities.length === 0 &&
|
||||
damageImmunities.length === 0 ? null : (
|
||||
<>
|
||||
<div className="ct-vehicle-block__tidbits">
|
||||
{this.renderDamageImmunities()}
|
||||
{this.renderConditionImmunities()}
|
||||
</div>
|
||||
<VehicleBlockSeparator displayType={displayType} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { displayType } = this.props;
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block__primary">
|
||||
<VehicleBlockSeparator displayType={displayType} />
|
||||
{this.renderAttributes()}
|
||||
{this.renderStats()}
|
||||
{this.renderConditionsAndImmunities()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockPrimary from "./VehicleBlockPrimary";
|
||||
|
||||
export default VehicleBlockPrimary;
|
||||
export { VehicleBlockPrimary };
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
import { orderBy } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
Constants,
|
||||
FormatUtils,
|
||||
VehicleComponentSpeedModeInfo,
|
||||
} from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { GD_VehicleBlockPrimaryComponentProps } from "../../../utils/Component";
|
||||
import InlineSeparatedNodes from "../../common/InlineSeparatedNodes";
|
||||
import { VehicleBlockAttribute } from "../VehicleBlockAttribute";
|
||||
|
||||
interface Props extends GD_VehicleBlockPrimaryComponentProps {
|
||||
shouldCoalesce: boolean;
|
||||
coverType: string | null;
|
||||
requiredCrew?: number | null;
|
||||
count: number;
|
||||
}
|
||||
export default class VehicleBlockPrimaryAttributes extends React.PureComponent<Props> {
|
||||
//this class renders on VehicleBlock and VehicleComponent and VehicleActionStation
|
||||
static defaultProps = {
|
||||
shouldCoalesce: true,
|
||||
coverType: null,
|
||||
requiredCrew: null,
|
||||
count: 1,
|
||||
};
|
||||
|
||||
renderHitPointInfo = (): React.ReactNode => {
|
||||
const { hitPointInfo, count, shouldCoalesce } = this.props;
|
||||
|
||||
if (hitPointInfo === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hitPointInfo.totalHp === null) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
let currentHpNode: React.ReactNode = null;
|
||||
if (shouldCoalesce) {
|
||||
currentHpNode = hitPointInfo.totalHp;
|
||||
if (count > 1) {
|
||||
currentHpNode = `${hitPointInfo.totalHp} each`;
|
||||
}
|
||||
} else {
|
||||
let classNames: Array<string> = ["ct-vehicle-block-component__hp-value"];
|
||||
|
||||
const remainingHp: number =
|
||||
hitPointInfo.remainingHp !== null
|
||||
? hitPointInfo.remainingHp
|
||||
: hitPointInfo.totalHp;
|
||||
if (remainingHp < hitPointInfo.totalHp) {
|
||||
classNames.push("ct-vehicle-block-component__hp-value--is-damaged");
|
||||
}
|
||||
|
||||
let showFullHitPointInfo: boolean = !shouldCoalesce;
|
||||
|
||||
currentHpNode = (
|
||||
<React.Fragment>
|
||||
{showFullHitPointInfo && (
|
||||
<React.Fragment>
|
||||
<span className={classNames.join(" ")}>{remainingHp}</span>
|
||||
<span className="ct-vehicle-block-component__hp-sep">/</span>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<span className={classNames.join(" ")}>{hitPointInfo.totalHp}</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
let displayNodes: Array<React.ReactNode> = [currentHpNode];
|
||||
|
||||
if (hitPointInfo.hitPointSpeedAdjustments.length > 0) {
|
||||
hitPointInfo.hitPointSpeedAdjustments.forEach((adjustment) => {
|
||||
let speed: string = FormatUtils.renderDistance(
|
||||
adjustment.perDamageValue
|
||||
);
|
||||
displayNodes.push(
|
||||
`${speed} speed per ${adjustment.perDamageTaken} damage taken`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return <InlineSeparatedNodes nodes={displayNodes} sep={"; "} />;
|
||||
};
|
||||
|
||||
renderHitPointThresholdInfo = (): React.ReactNode => {
|
||||
const { hitPointInfo } = this.props;
|
||||
|
||||
if (hitPointInfo === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let displayStrings: Array<string> = [];
|
||||
|
||||
if (hitPointInfo.damageThreshold) {
|
||||
displayStrings.push(`damage threshold ${hitPointInfo.damageThreshold}`);
|
||||
}
|
||||
|
||||
if (hitPointInfo.mishapThreshold) {
|
||||
displayStrings.push(`mishap threshold ${hitPointInfo.mishapThreshold}`);
|
||||
}
|
||||
|
||||
if (displayStrings.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `(${displayStrings.join(", ")})`;
|
||||
};
|
||||
|
||||
renderSpeedInfo = (
|
||||
modes: Array<VehicleComponentSpeedModeInfo>
|
||||
): React.ReactNode => {
|
||||
let speedDisplayStrings: Array<string> = modes.map((mode) => {
|
||||
const { value, description, restrictionsText, movementInfo } = mode;
|
||||
|
||||
let stringParts: Array<string> = [];
|
||||
if (movementInfo) {
|
||||
stringParts.push(`${movementInfo.description} speed`);
|
||||
}
|
||||
stringParts.push(FormatUtils.renderDistance(value));
|
||||
if (description) {
|
||||
stringParts.push(description);
|
||||
}
|
||||
if (restrictionsText) {
|
||||
stringParts.push(`(${restrictionsText})`);
|
||||
}
|
||||
|
||||
return stringParts.join(" ");
|
||||
});
|
||||
|
||||
return <InlineSeparatedNodes nodes={speedDisplayStrings} sep={"; "} />;
|
||||
};
|
||||
|
||||
renderArmorClassAttribute = (): React.ReactNode => {
|
||||
const { armorClassInfo } = this.props;
|
||||
|
||||
if (armorClassInfo === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { moving, base, description } = armorClassInfo;
|
||||
|
||||
let armorClassValue: number | null = base;
|
||||
let motionlessArmorClassValue: string | null = null;
|
||||
|
||||
if (moving !== null) {
|
||||
armorClassValue = moving;
|
||||
|
||||
if (moving !== base) {
|
||||
motionlessArmorClassValue = `(${base} while motionless)`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<VehicleBlockAttribute
|
||||
label="Armor Class"
|
||||
value={
|
||||
description
|
||||
? `${armorClassValue} (${description})`
|
||||
: armorClassValue === 0
|
||||
? "--"
|
||||
: armorClassValue
|
||||
}
|
||||
extraValue={motionlessArmorClassValue}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
armorClassInfo,
|
||||
hitPointInfo,
|
||||
speedInfos,
|
||||
coverType,
|
||||
requiredCrew,
|
||||
costInfos,
|
||||
displayType,
|
||||
isPrimaryComponent,
|
||||
width,
|
||||
length,
|
||||
} = this.props;
|
||||
|
||||
//sort cost infos by type component then ammunition
|
||||
// const costInfos: Array<VehicleComponentCostContract> = [
|
||||
// {
|
||||
// description: 'cannon',
|
||||
// type: 'component',
|
||||
// value: null,
|
||||
// },
|
||||
// {
|
||||
// description: 'giant iron ball',
|
||||
// type: 'ammunition',
|
||||
// value: 1000,
|
||||
// },
|
||||
// ];
|
||||
|
||||
const sortedCosts = orderBy(
|
||||
costInfos,
|
||||
[
|
||||
(cost) => cost.type === Constants.ComponentCostTypeEnum.COMPONENT,
|
||||
(cost) => cost.type === Constants.ComponentCostTypeEnum.AMMUNITION,
|
||||
],
|
||||
"desc"
|
||||
);
|
||||
|
||||
const costValues: Array<string> = sortedCosts.map((cost) => {
|
||||
const value: string = cost.value ? `${cost.value} gp` : "--";
|
||||
const description: string = cost.description
|
||||
? `(${cost.description})`
|
||||
: "";
|
||||
|
||||
return [value, description].join(" ");
|
||||
});
|
||||
|
||||
const isSpelljammer =
|
||||
displayType === Constants.VehicleConfigurationDisplayTypeEnum.SPELLJAMMER;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{armorClassInfo !== null && this.renderArmorClassAttribute()}
|
||||
{hitPointInfo !== null && (
|
||||
<VehicleBlockAttribute
|
||||
label="Hit Points"
|
||||
value={this.renderHitPointInfo()}
|
||||
extraValue={
|
||||
!isSpelljammer ? this.renderHitPointThresholdInfo() : undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{isSpelljammer && !!hitPointInfo?.damageThreshold && (
|
||||
<VehicleBlockAttribute
|
||||
label="Damage Threshold"
|
||||
value={hitPointInfo?.damageThreshold}
|
||||
/>
|
||||
)}
|
||||
{speedInfos.map((speedInfo, idx) => {
|
||||
let speedAttributeLabel: string = "Speed";
|
||||
if (speedInfo.type !== null && !isSpelljammer) {
|
||||
speedAttributeLabel = `Speed (${speedInfo.type})`;
|
||||
}
|
||||
|
||||
let modes: Array<VehicleComponentSpeedModeInfo> =
|
||||
speedInfo.modes !== null ? speedInfo.modes : [];
|
||||
|
||||
return (
|
||||
<VehicleBlockAttribute
|
||||
key={`${speedInfo.type}-${idx}`}
|
||||
label={speedAttributeLabel}
|
||||
value={this.renderSpeedInfo(modes)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{coverType !== null && (
|
||||
<VehicleBlockAttribute
|
||||
label="Cover"
|
||||
value={`Grants ${coverType} Cover`}
|
||||
/>
|
||||
)}
|
||||
{requiredCrew !== null && !isSpelljammer && (
|
||||
<VehicleBlockAttribute label="Required Crew" value={requiredCrew} />
|
||||
)}
|
||||
{isPrimaryComponent &&
|
||||
isSpelljammer &&
|
||||
length !== null &&
|
||||
width !== null && (
|
||||
<VehicleBlockAttribute
|
||||
label="Keel/Beam"
|
||||
value={`${FormatUtils.renderDistance(
|
||||
length
|
||||
)} / ${FormatUtils.renderDistance(width)}`}
|
||||
/>
|
||||
)}
|
||||
{costValues.length > 0 && (
|
||||
<VehicleBlockAttribute label="Cost" value={costValues.join(", ")} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockPrimaryAttributes from "./VehicleBlockPrimaryAttributes";
|
||||
|
||||
export default VehicleBlockPrimaryAttributes;
|
||||
export { VehicleBlockPrimaryAttributes };
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
|
||||
interface Props {
|
||||
label: React.ReactNode;
|
||||
callout: React.ReactNode;
|
||||
}
|
||||
export default class VehicleBlockSectionHeader extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
callout: null,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { label, callout } = this.props;
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block__section-header">
|
||||
<div className="ct-vehicle-block__section-header-content">
|
||||
<div className="ct-vehicle-block__section-header-content-primary">
|
||||
{label}
|
||||
</div>
|
||||
{callout !== null && (
|
||||
<div className="ct-vehicle-block__section-header-content-callout">
|
||||
{callout}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockSectionHeader from "./VehicleBlockSectionHeader";
|
||||
|
||||
export default VehicleBlockSectionHeader;
|
||||
export { VehicleBlockSectionHeader };
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
|
||||
import { Constants } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { VehicleDisplayTypeComponentLookup } from "../../../utils/Component";
|
||||
import VehicleBlockSeparatorInfernal from "../VehicleBlockSeparatorInfernal";
|
||||
import VehicleBlockSeparatorShip from "../VehicleBlockSeparatorShip";
|
||||
|
||||
interface Props {
|
||||
displayType: Constants.VehicleConfigurationDisplayTypeEnum;
|
||||
}
|
||||
export default class VehicleBlockSeparator extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { displayType } = this.props;
|
||||
|
||||
let lookup: VehicleDisplayTypeComponentLookup = {
|
||||
[Constants.VehicleConfigurationDisplayTypeEnum.SHIP]:
|
||||
VehicleBlockSeparatorShip,
|
||||
[Constants.VehicleConfigurationDisplayTypeEnum.INFERNAL_WAR_MACHINE]:
|
||||
VehicleBlockSeparatorInfernal,
|
||||
[Constants.VehicleConfigurationDisplayTypeEnum.SPELLJAMMER]:
|
||||
VehicleBlockSeparatorShip,
|
||||
};
|
||||
|
||||
//sets Ship style as default
|
||||
let SeparatorComponent: React.ComponentType<any> =
|
||||
VehicleBlockSeparatorShip;
|
||||
|
||||
if (lookup.hasOwnProperty(displayType)) {
|
||||
SeparatorComponent = lookup[displayType];
|
||||
}
|
||||
|
||||
return <SeparatorComponent className="ct-vehicle-block__separator" />;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockSeparator from "./VehicleBlockSeparator";
|
||||
|
||||
export default VehicleBlockSeparator;
|
||||
export { VehicleBlockSeparator };
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
|
||||
import { BaseSvg } from "@dndbeyond/character-components/es";
|
||||
|
||||
interface Props {
|
||||
className: string;
|
||||
fillColor: string;
|
||||
}
|
||||
export default class VehicleBlockSeparatorInfernal extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
fillColor: "#9D3856",
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className, fillColor } = this.props;
|
||||
|
||||
let classNames: Array<string> = [
|
||||
"ct-vehicle-block__separator-infernal",
|
||||
className,
|
||||
];
|
||||
|
||||
return (
|
||||
<BaseSvg viewBox="0 0 1619.42 32.05" className={classNames.join(" ")}>
|
||||
<polygon
|
||||
fill={fillColor}
|
||||
points="998.33,16.03 1619.42,25.35 809.71,32.05 0,25.35 621.09,16.03 0,12.45 809.71,0 1619.42,12.45 "
|
||||
/>
|
||||
</BaseSvg>
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockSeparatorInfernal from "./VehicleBlockSeparatorInfernal";
|
||||
|
||||
export default VehicleBlockSeparatorInfernal;
|
||||
export { VehicleBlockSeparatorInfernal };
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
|
||||
interface Props {
|
||||
className: string;
|
||||
}
|
||||
export default class VehicleBlockSeparatorShip extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
|
||||
let classNames: Array<string> = [
|
||||
"ct-vehicle-block__separator-ship",
|
||||
className,
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={classNames.join(" ")}>
|
||||
<div className="ct-vehicle-block__separator-ship-border" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockSeparatorShip from "./VehicleBlockSeparatorShip";
|
||||
|
||||
export default VehicleBlockSeparatorShip;
|
||||
export { VehicleBlockSeparatorShip };
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
|
||||
import { Constants } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { GD_VehicleBlockShellProps } from "../../../utils/Component";
|
||||
import VehicleBlockShellCap from "../VehicleBlockShellCap";
|
||||
|
||||
interface Props extends GD_VehicleBlockShellProps {}
|
||||
|
||||
export default class VehicleBlockShell extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { children, displayType } = this.props;
|
||||
|
||||
let type: string | null = null;
|
||||
|
||||
switch (displayType) {
|
||||
case Constants.VehicleConfigurationDisplayTypeEnum.INFERNAL_WAR_MACHINE:
|
||||
type = "infernal";
|
||||
break;
|
||||
case Constants.VehicleConfigurationDisplayTypeEnum.SHIP:
|
||||
default:
|
||||
type = "ship";
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ct-vehicle-block">
|
||||
<VehicleBlockShellCap displayType={displayType} />
|
||||
<div
|
||||
className={`ct-vehicle-block__block ct-vehicle-block__block--${type}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<VehicleBlockShellCap displayType={displayType} invertY={true} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockShell from "./VehicleBlockShell";
|
||||
|
||||
export default VehicleBlockShell;
|
||||
export { VehicleBlockShell };
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
|
||||
import { Constants } from "@dndbeyond/character-rules-engine/es";
|
||||
|
||||
import { VehicleDisplayTypeComponentLookup } from "../../../utils/Component";
|
||||
import VehicleBlockShellCapInfernal from "../VehicleBlockShellCapInfernal";
|
||||
import VehicleBlockShellCapShip from "../VehicleBlockShellCapShip";
|
||||
|
||||
interface Props {
|
||||
displayType: Constants.VehicleConfigurationDisplayTypeEnum;
|
||||
invertY: boolean;
|
||||
invertX: boolean;
|
||||
}
|
||||
export default class VehicleBlockShellCap extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
invertX: false,
|
||||
invertY: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { displayType, ...restProps } = this.props;
|
||||
|
||||
let lookup: VehicleDisplayTypeComponentLookup = {
|
||||
[Constants.VehicleConfigurationDisplayTypeEnum.SHIP]:
|
||||
VehicleBlockShellCapShip,
|
||||
[Constants.VehicleConfigurationDisplayTypeEnum.INFERNAL_WAR_MACHINE]:
|
||||
VehicleBlockShellCapInfernal,
|
||||
[Constants.VehicleConfigurationDisplayTypeEnum.SPELLJAMMER]:
|
||||
VehicleBlockShellCapShip,
|
||||
};
|
||||
|
||||
//sets Ship style as default
|
||||
let CapComponent: React.ComponentType<any> = VehicleBlockShellCapShip;
|
||||
|
||||
if (lookup.hasOwnProperty(displayType)) {
|
||||
CapComponent = lookup[displayType];
|
||||
}
|
||||
|
||||
return (
|
||||
<CapComponent className="ct-vehicle-block__shell-cap" {...restProps} />
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockShellCap from "./VehicleBlockShellCap";
|
||||
|
||||
export default VehicleBlockShellCap;
|
||||
export { VehicleBlockShellCap };
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import React from "react";
|
||||
|
||||
import { BaseSvg } from "@dndbeyond/character-components/es";
|
||||
|
||||
interface Props {
|
||||
className: string;
|
||||
invertY: boolean;
|
||||
invertX: boolean;
|
||||
}
|
||||
export default class VehicleBlockShellCapInfernal extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
invertY: false,
|
||||
invertX: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className, invertY, invertX } = this.props;
|
||||
|
||||
let classNames: Array<string> = [
|
||||
"ct-vehicle-block__shell-cap-infernal",
|
||||
className,
|
||||
];
|
||||
|
||||
if (invertY) {
|
||||
classNames.push("ct-vehicle-block__shell-cap--invert-y");
|
||||
}
|
||||
if (invertX) {
|
||||
classNames.push("ct-vehicle-block__shell-cap--invert-x");
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseSvg viewBox="0 0 2711.9 133.1" className={classNames.join(" ")}>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="linear-gradient"
|
||||
x1="1355.95"
|
||||
y1="37.16"
|
||||
x2="1355.95"
|
||||
y2="138.66"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stopColor="#562c32" />
|
||||
<stop offset="1" stopColor="#88817b" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<polygon
|
||||
fill="#3c203c"
|
||||
points="2711.9 46.18 2711.9 133.1 0 133.1 0 46.18 37.9 76.19 308.9 76.19 1074.9 25.19 782.9 76.19 883.9 76.19 1260.38 0 1032.9 76.19 1169.9 76.19 1336.9 4.19 1253.9 76.19 1322.9 76.19 1355.9 8.19 1355.9 8.6 1356 8.19 1389 76.19 1458 76.19 1375 4.19 1542 76.19 1679 76.19 1451.52 0 1828 76.19 1929 76.19 1637 25.19 2403 76.19 2674 76.19 2711.9 46.18"
|
||||
/>
|
||||
<polygon
|
||||
fill="url(#linear-gradient)"
|
||||
points="1355.9 27.19 1382.24 86.99 1486.93 86.99 1434.72 41.7 1539.77 86.99 1745.25 86.99 1620.57 45.23 1826.92 86.99 2053.61 86.99 1838.59 49.44 2402.64 86.99 2677.76 86.99 2701.1 68.51 2701.1 122.3 10.8 122.3 10.8 68.51 34.14 86.99 309.26 86.99 873.31 49.44 658.29 86.99 884.98 86.99 1091.33 45.23 966.65 86.99 1172.13 86.99 1277.18 41.7 1224.97 86.99 1329.66 86.99 1355.9 27.19"
|
||||
/>
|
||||
</BaseSvg>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockShellCapInfernal from "./VehicleBlockShellCapInfernal";
|
||||
|
||||
export default VehicleBlockShellCapInfernal;
|
||||
export { VehicleBlockShellCapInfernal };
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
|
||||
interface Props {
|
||||
className: string;
|
||||
invertY?: boolean;
|
||||
invertX?: boolean;
|
||||
}
|
||||
export default class VehicleBlockShellCapShip extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: "",
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
|
||||
let classNames: Array<string> = [
|
||||
"ct-vehicle-block__shell-cap-ship",
|
||||
className,
|
||||
];
|
||||
|
||||
return <div className={classNames.join(" ")} />;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import VehicleBlockShellCapShip from "./VehicleBlockShellCapShip";
|
||||
|
||||
export default VehicleBlockShellCapShip;
|
||||
export { VehicleBlockShellCapShip };
|
||||
@@ -0,0 +1,52 @@
|
||||
import VehicleBlock from "./VehicleBlock";
|
||||
import VehicleBlockAction from "./VehicleBlockAction";
|
||||
import VehicleBlockActionStation from "./VehicleBlockActionStation";
|
||||
import VehicleBlockActionStations from "./VehicleBlockActionStations";
|
||||
import VehicleBlockActionStationsShell from "./VehicleBlockActionStationsShell";
|
||||
import VehicleBlockActionSummaries from "./VehicleBlockActionSummaries";
|
||||
import VehicleBlockActionSummary from "./VehicleBlockActionSummary";
|
||||
import VehicleBlockActions from "./VehicleBlockActions";
|
||||
import VehicleBlockAttribute from "./VehicleBlockAttribute";
|
||||
import VehicleBlockComponent from "./VehicleBlockComponent";
|
||||
import VehicleBlockComponents from "./VehicleBlockComponents";
|
||||
import VehicleBlockComponentsShell from "./VehicleBlockComponentsShell";
|
||||
import VehicleBlockFeatures from "./VehicleBlockFeatures";
|
||||
import VehicleBlockHeader from "./VehicleBlockHeader";
|
||||
import VehicleBlockPrimary from "./VehicleBlockPrimary";
|
||||
import VehicleBlockPrimaryAttributes from "./VehicleBlockPrimaryAttributes";
|
||||
import VehicleBlockSectionHeader from "./VehicleBlockSectionHeader";
|
||||
import VehicleBlockSeparator from "./VehicleBlockSeparator";
|
||||
import VehicleBlockSeparatorInfernal from "./VehicleBlockSeparatorInfernal";
|
||||
import VehicleBlockSeparatorShip from "./VehicleBlockSeparatorShip";
|
||||
import VehicleBlockShell from "./VehicleBlockShell";
|
||||
import VehicleBlockShellCap from "./VehicleBlockShellCap";
|
||||
import VehicleBlockShellCapInfernal from "./VehicleBlockShellCapInfernal";
|
||||
import VehicleBlockShellCapShip from "./VehicleBlockShellCapShip";
|
||||
|
||||
export default VehicleBlock;
|
||||
export {
|
||||
VehicleBlock,
|
||||
VehicleBlockAction,
|
||||
VehicleBlockActions,
|
||||
VehicleBlockActionStation,
|
||||
VehicleBlockActionStations,
|
||||
VehicleBlockActionStationsShell,
|
||||
VehicleBlockActionSummaries,
|
||||
VehicleBlockActionSummary,
|
||||
VehicleBlockAttribute,
|
||||
VehicleBlockComponent,
|
||||
VehicleBlockComponents,
|
||||
VehicleBlockComponentsShell,
|
||||
VehicleBlockFeatures,
|
||||
VehicleBlockHeader,
|
||||
VehicleBlockPrimary,
|
||||
VehicleBlockPrimaryAttributes,
|
||||
VehicleBlockSectionHeader,
|
||||
VehicleBlockSeparator,
|
||||
VehicleBlockSeparatorInfernal,
|
||||
VehicleBlockSeparatorShip,
|
||||
VehicleBlockShell,
|
||||
VehicleBlockShellCap,
|
||||
VehicleBlockShellCapInfernal,
|
||||
VehicleBlockShellCapShip,
|
||||
};
|
||||
Reference in New Issue
Block a user