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:
2025-05-28 11:50:03 -07:00
commit 8df9031d27
3592 changed files with 319051 additions and 0 deletions
@@ -0,0 +1,242 @@
import React from "react";
export interface ButtonProps {
onClick?: ((evt: React.MouseEvent) => void) | (() => void);
clsNames?: Array<string>;
className: string;
disabled?: boolean;
styledDisabled: boolean;
block?: boolean;
size?: string;
style?: string;
active?: boolean;
stopPropagation: boolean;
enableConfirm: boolean;
confirmText: string;
confirmDuration: number;
confirmSuccessDuration: number;
isInteractive: boolean;
role: string;
}
interface ButtonState {
isConfirming: boolean;
isConfirmed: boolean;
currentDuration: number;
confirmText: string;
}
export class Button extends React.PureComponent<ButtonProps, ButtonState> {
static defaultProps = {
className: "",
styledDisabled: false,
stopPropagation: true,
enableConfirm: false,
confirmText: "Confirm",
confirmDuration: 3000,
confirmSuccessDuration: 1000,
isInteractive: true,
role: "button",
};
confirmIntervalId: number;
confirmSuccessTimeoutId: number;
constructor(props: ButtonProps) {
super(props);
this.state = {
isConfirming: false,
isConfirmed: false,
currentDuration: 0,
confirmText: "",
};
}
componentWillUnmount() {
clearTimeout(this.confirmSuccessTimeoutId);
clearInterval(this.confirmIntervalId);
}
handleConfirmClick = (evt: React.MouseEvent<HTMLButtonElement>): void => {
const { isConfirming, isConfirmed } = this.state;
const {
confirmText,
stopPropagation,
confirmDuration,
confirmSuccessDuration,
onClick,
} = this.props;
if (stopPropagation) {
evt.stopPropagation();
evt.nativeEvent.stopImmediatePropagation();
}
if (isConfirmed) {
this.setState({
isConfirmed: false,
});
clearTimeout(this.confirmSuccessTimeoutId);
return;
}
if (isConfirming) {
this.setState({
isConfirming: false,
currentDuration: 0,
isConfirmed: true,
});
if (onClick) {
onClick(evt);
}
clearInterval(this.confirmIntervalId);
this.confirmSuccessTimeoutId = window.setTimeout(() => {
this.setState({
isConfirmed: false,
});
}, confirmSuccessDuration);
} else {
this.setState({
isConfirming: true,
confirmText: `${confirmText} (${Math.round(confirmDuration / 1000)})`,
currentDuration: 1000,
});
this.confirmIntervalId = window.setInterval(() => {
if (this.state.currentDuration >= confirmDuration) {
clearInterval(this.confirmIntervalId);
this.setState({
isConfirming: false,
currentDuration: 0,
});
} else {
this.setState((prevState) => ({
confirmText: `${confirmText} (${Math.round(
(confirmDuration - prevState.currentDuration) / 1000
)})`,
currentDuration: prevState.currentDuration + 1000,
}));
}
}, 1000);
}
};
handleClick = (evt: React.MouseEvent<HTMLButtonElement>): void => {
const { onClick, stopPropagation, isInteractive } = this.props;
if (onClick && isInteractive) {
if (stopPropagation) {
evt.stopPropagation();
evt.nativeEvent.stopImmediatePropagation();
}
onClick(evt);
}
};
render() {
const { isConfirming, isConfirmed, confirmText } = this.state;
const {
children,
className,
clsNames,
disabled,
styledDisabled,
size,
block,
active,
style,
enableConfirm,
role,
confirmSuccessDuration, // to be ignored on props spread
confirmDuration, // to be ignored on props spread
confirmText: _confirmText, // to be ignored on props spread
isInteractive, // to be ignored on props spread
stopPropagation, // to be ignored on props spread
...restProps
} = this.props;
let buttonBase: string = "character-button";
let btnClassNames: Array<string> = [
className,
"ct-button",
buttonBase,
"ddbc-button",
];
let buttonSizeBase: string = buttonBase;
if (block) {
buttonSizeBase += "-block";
}
switch (size) {
case "oversized":
case "large":
case "medium":
case "small":
btnClassNames.push(`${buttonSizeBase}-${size}`);
break;
default:
btnClassNames.push(`${buttonSizeBase}`);
}
let buttonStyleBase: string = buttonBase;
switch (style) {
case "dark":
btnClassNames.push(
active ? `${buttonStyleBase}-dark-active` : `${buttonStyleBase}-dark`
);
break;
case "modal":
case "modal-cancel":
btnClassNames.push(`${buttonStyleBase}-${style}`);
break;
case "outline":
btnClassNames.push(`${buttonStyleBase}-outline`);
break;
default:
if (active) {
btnClassNames.push(`${buttonStyleBase}-active`);
}
}
if (styledDisabled && disabled) {
btnClassNames.push(`${buttonBase}--disabled`);
}
if (clsNames) {
btnClassNames = [...btnClassNames, ...clsNames];
}
if (enableConfirm) {
btnClassNames.push("ct-button--confirm");
if (isConfirming) {
btnClassNames.push("ct-button--is-confirming");
}
if (isConfirmed) {
btnClassNames.push("ct-button--is-confirmed");
}
}
return (
<button
{...restProps}
disabled={!styledDisabled && disabled}
onClick={enableConfirm ? this.handleConfirmClick : this.handleClick}
className={btnClassNames.join(" ")}
role={role}
>
<span className="ct-button__content">{children}</span>
{enableConfirm && (
<React.Fragment>
<span className="ct-button__confirming">{confirmText}</span>
<span className="ct-button__confirmed" />
</React.Fragment>
)}
</button>
);
}
}
export default Button;
@@ -0,0 +1,39 @@
import React from "react";
import Button, { ButtonProps } from "../Button";
//Duplicated from CharacterToolsClient
// ToolsClient implements as a ThemeButton wrapper
// eventually RemoveButton usage should be replaced with this one with specialized style overrides
export interface RemoveButtonProps extends Partial<ButtonProps> {}
const RemoveButton: React.FunctionComponent<RemoveButtonProps> = ({
size = "small",
style = "outline",
stopPropagation = true,
children,
className,
...restProps
}) => {
let classNames: Array<string> = [
"ddbc-remove-button",
"character-button-remove",
];
if (className) {
classNames.push(className);
}
return (
<Button
{...restProps}
size={size}
style={style}
stopPropagation={stopPropagation}
className={classNames.join(" ")}
>
{children ? children : "Remove"}
</Button>
);
};
export default RemoveButton;
@@ -0,0 +1,4 @@
import RemoveButton from "./RemoveButton";
export default RemoveButton;
export { RemoveButton };
@@ -0,0 +1,244 @@
import React from "react";
import {
ApiAdapterPromise,
ApiAdapterUtils,
ApiResponse,
} from "@dndbeyond/character-rules-engine/es";
interface Props<T extends any = any, TData extends any = any> {
loadOptions?: () => ApiAdapterPromise<ApiResponse<Array<TData>>>;
clsNames: Array<string>;
id?: any; //TODO type?
options: Array<any>; //TODO type Options
disabled: boolean;
value: React.ReactText | null;
placeholder: string;
onChange?: (value: string) => void;
onChangePromise?: (
newValue: string,
oldValue: string,
accept: () => void,
reject: () => void
) => void;
parseLoadedOptions?: (data: Array<TData>) => Array<T>;
resetAfterChoice: boolean;
initialOptionRemoved: boolean;
preventClickPropagating: boolean;
isReadonly?: boolean;
className: string;
}
interface State {
isLazy: boolean;
lazyOptions: Array<any>;
value: React.ReactText;
resetAfterChoiceValues: Array<string>;
}
export default class Select extends React.Component<Props, State> {
static defaultProps = {
clsNames: [],
className: "",
disabled: false,
resetAfterChoice: false,
initialOptionRemoved: false,
placeholder: "-- Choose an Option --",
options: [],
preventClickPropagating: false,
};
constructor(props: Props) {
super(props);
this.state = {
isLazy: !!props.loadOptions,
lazyOptions: [],
value: props.value === null ? "" : props.value,
resetAfterChoiceValues: this.getResetAfterValueChoices(props),
};
}
componentDidUpdate(
prevProps: Readonly<Props>,
prevState: Readonly<State>,
snapshot?: any
): void {
const { value } = this.props;
if (value !== prevProps.value) {
this.setState({
value: value === null ? "" : value,
});
}
}
componentDidMount() {
const { isLazy } = this.state;
const { loadOptions, parseLoadedOptions } = this.props;
if (isLazy && loadOptions && parseLoadedOptions) {
loadOptions().then((response) => {
const data = ApiAdapterUtils.getResponseData(response);
if (data === null) {
return;
}
this.setState({
lazyOptions: parseLoadedOptions(data),
});
});
}
}
getResetAfterValueChoices = (props: Props): Array<string> => {
const { options, resetAfterChoice } = props;
if (resetAfterChoice) {
return options.reduce((acc, option) => {
if (option.options) {
acc.push(...option.options.map((opt) => "" + opt.value));
} else {
acc.push("" + option.value);
}
return acc;
}, []);
}
return options.reduce((acc, option) => {
if (option.options && option.resetAfterChoice) {
acc.push(...option.options.map((opt) => "" + opt.value));
}
return acc;
}, []);
};
handleChange = (evt: React.ChangeEvent<HTMLSelectElement>): void => {
const { value, resetAfterChoiceValues } = this.state;
const { onChangePromise, onChange, resetAfterChoice } = this.props;
const newValue: string = evt.target.value;
if (onChangePromise) {
onChangePromise(
newValue,
String(value),
() => {
this.setState({
value: newValue,
});
if (onChange) {
onChange(newValue);
}
if (resetAfterChoice) {
evt.target.selectedIndex = 0;
}
},
() => {}
);
} else {
if (resetAfterChoice || resetAfterChoiceValues.includes(newValue)) {
evt.target.selectedIndex = 0;
} else {
this.setState({
value: newValue,
});
}
if (onChange) {
onChange(newValue);
}
}
};
handleClick = (evt: React.MouseEvent): void => {
const { preventClickPropagating } = this.props;
if (preventClickPropagating) {
evt.stopPropagation();
evt.nativeEvent.stopImmediatePropagation();
}
};
renderOption = (option: any): React.ReactNode => {
return (
<option
key={option.value}
value={String(option.value)}
disabled={option.disabled}
>
{option.label}
</option>
);
};
renderOptGroup = (
label: string,
options: Array<any>,
resetAfterChoice: boolean
) => {
if (!options.length) {
return null;
}
return (
<optgroup label={label} key={label}>
{options.map((option) => this.renderOption(option))}
</optgroup>
);
};
render() {
const { isLazy, lazyOptions, value } = this.state;
const {
options,
placeholder,
initialOptionRemoved,
id,
disabled,
clsNames,
isReadonly,
className,
} = this.props;
let displayOptions: Array<any> = options;
if (isLazy) {
displayOptions = lazyOptions;
}
const conClassNames: Array<string> = [
"ddbc-select",
...clsNames,
className,
];
return (
<select
id={id}
className={conClassNames.join(" ")}
value={value}
onChange={this.handleChange}
onClick={this.handleClick}
disabled={disabled || isReadonly}
>
<option
value=""
disabled={initialOptionRemoved}
hidden={initialOptionRemoved}
>
{placeholder}
</option>
{displayOptions.map((option) => {
if (option.options) {
return this.renderOptGroup(
option.optGroupLabel,
option.options,
option.resetAfterChoice
);
} else {
return this.renderOption(option);
}
})}
</select>
);
}
}
@@ -0,0 +1,4 @@
import Select from "./Select";
export default Select;
export { Select };