@swrpg-online/react-dice
Version:
React components for displaying Star Wars RPG narrative and numeric dice assets
249 lines (240 loc) • 10.1 kB
JavaScript
import * as React from 'react';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/**
* @fileoverview A React component for rendering various types of dice (numeric and narrative)
* with support for different themes and formats.
*/
/** Default theme for dice rendering */
var DEFAULT_THEME = 'white-arabic';
/** Default format for dice assets */
var DEFAULT_FORMAT = 'svg';
/** Default variant for D4 dice */
var DEFAULT_D4_VARIANT = 'standard';
/** Array of valid numeric die types */
var VALID_NUMERIC_DICE = ['d4', 'd6', 'd8', 'd12', 'd20', 'd100'];
/**
* Gets the maximum face value for a given die type
*/
var getMaxFace = function (type) {
switch (type) {
case 'd4':
return 4;
case 'd6':
return 6;
case 'd8':
return 8;
case 'd12':
return 12;
case 'd20':
return 20;
case 'd100':
return 90; // d100 uses 0-90 in steps of 10
default:
return 1;
}
};
/**
* Formats the face number as a two-digit string
*/
var formatFaceNumber = function (face) {
return face.toString().padStart(2, '0');
};
/**
* Determines the configuration for D4 dice based on the variant.
*
* @param variant - The D4 variant type ('apex', 'base', or 'standard')
* @returns The configuration string for the D4 variant
*/
var getD4Config = function (variant) {
switch (variant) {
case 'apex':
return 'D4Apex';
case 'base':
return 'D4Base';
default:
return 'D4';
}
};
/**
* Extracts theme components from the theme string.
* For example, 'white-arabic' becomes { style: 'White', script: 'Arabic' }
*
* Note: The actual filenames use script-style order (e.g., 'Arabic-White')
* rather than style-script order, which is handled when constructing the import path.
*/
var parseTheme = function (theme) {
var _a = theme.split('-'), style = _a[0], script = _a[1];
return {
style: style.charAt(0).toUpperCase() + style.slice(1),
script: script.charAt(0).toUpperCase() + script.slice(1)
};
};
/**
* Gets the proper die type name for the asset path
*/
var getDieTypeName = function (type) {
switch (type) {
case 'boost':
return 'Boost';
case 'proficiency':
return 'Proficiency';
case 'ability':
return 'Ability';
case 'setback':
return 'Setback';
case 'challenge':
return 'Challenge';
case 'difficulty':
return 'Difficulty';
default:
return type.replace('d', 'D');
}
};
/**
* Constructs the image path for a die based on its properties
*/
var constructImagePath = function (type, face, theme, format, variant) {
var isNumericDie = VALID_NUMERIC_DICE.includes(type);
var _a = parseTheme(theme), themeStyle = _a.style, themeScript = _a.script;
// Import from @swrpg-online/art package - Updated to use a root-relative path
var basePath = '/assets/@swrpg-online/art/dice';
if (isNumericDie) {
var faceStr = formatFaceNumber(face);
if (type === 'd4') {
var d4Config = getD4Config(variant);
return "".concat(basePath, "/numeric/").concat(theme, "/").concat(d4Config, "-").concat(faceStr, "-").concat(themeScript, "-").concat(themeStyle, ".").concat(format);
}
else {
var dieType = getDieTypeName(type);
return "".concat(basePath, "/numeric/").concat(theme, "/").concat(dieType, "-").concat(faceStr, "-").concat(themeScript, "-").concat(themeStyle, ".").concat(format);
}
}
else {
var dieTypeName = getDieTypeName(type);
return "".concat(basePath, "/narrative/").concat(dieTypeName, "/").concat(dieTypeName, "-").concat(face, ".").concat(format);
}
};
/**
* A React component that renders dice for tabletop gaming applications.
* Supports both numeric (d4, d6, etc.) and narrative dice types with various themes and formats.
*
* Features:
* - Supports SVG and image formats
* - Handles multiple D4 variants
* - Provides loading and error states
* - Supports custom styling and theming
*
* @component
* @example
* // Render a standard d20 showing face 20
* <Die type="d20" face={20} />
*
* @example
* // Render a boost die showing two advantages
* <Die
* type="boost"
* face="Advantage-Advantage"
* className="large-die"
* style={{ width: '100px' }}
* />
*/
var Die = function (_a) {
var type = _a.type, _b = _a.face, face = _b === void 0 ? 1 : _b, _c = _a.format, format = _c === void 0 ? DEFAULT_FORMAT : _c, _d = _a.theme, theme = _d === void 0 ? DEFAULT_THEME : _d, _e = _a.variant, variant = _e === void 0 ? DEFAULT_D4_VARIANT : _e, className = _a.className, style = _a.style;
// State to track loading and error states
var _f = React.useState('loading'), loadingState = _f[0], setLoadingState = _f[1];
var _g = React.useState(null), error = _g[0], setError = _g[1];
var _h = React.useState(null), imgSrc = _h[0], setImgSrc = _h[1];
// On mount and when props change, validate and set the image path
React.useEffect(function () {
setLoadingState('loading');
setError(null);
try {
// Validate die type
var isNumericDie = VALID_NUMERIC_DICE.includes(type);
if (!isNumericDie && !['boost', 'proficiency', 'ability', 'setback', 'challenge', 'difficulty'].includes(type)) {
throw new Error("Invalid die type: ".concat(type));
}
// Validate face value
if (isNumericDie) {
if (typeof face !== 'number') {
throw new Error("Numeric dice require a number for the face value");
}
var maxFace = getMaxFace(type);
if (type === 'd100') {
if (face < 0 || face > maxFace || face % 10 !== 0) {
throw new Error("Invalid face for d100: Must be between 0 and 90 in steps of 10");
}
}
else if (face < 1 || face > maxFace) {
throw new Error("Invalid face for ".concat(type, ": Must be between 1 and ").concat(maxFace));
}
}
else {
if (typeof face !== 'string') {
throw new Error("Narrative dice require a string for the face value");
}
}
// All validation passed, construct the image path
var imagePath = constructImagePath(type, face, theme, format, variant);
setImgSrc(imagePath);
}
catch (validationError) {
var errorMessage = validationError instanceof Error ? validationError.message : 'Unknown error';
setError(errorMessage);
setLoadingState('error');
}
}, [type, face, format, theme, variant]);
// Handle successful image load
var handleImageLoad = function () {
setLoadingState('success');
};
// Handle image load error
var handleImageError = function () {
// If SVG fails, try PNG
if (format === 'svg' && imgSrc) {
var pngPath = imgSrc.replace(".".concat(format), '.png');
setImgSrc(pngPath);
return;
}
setError("Failed to load die image");
setLoadingState('error');
};
// Render loading state
if (loadingState === 'loading' && !imgSrc) {
return (React.createElement("div", { className: "die-loading ".concat(className || ''), style: __assign({ width: '50px', height: '50px', backgroundColor: '#e9ecef', border: '1px solid #dee2e6', borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#495057' }, style), role: "status" }, "Loading..."));
}
// Render error state
if (loadingState === 'error') {
return (React.createElement("div", { className: "die-error ".concat(className || ''), style: __assign({ width: '50px', height: '50px', backgroundColor: '#f8d7da', border: '1px solid #f5c6cb', borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#721c24', fontSize: '12px', padding: '4px', textAlign: 'center' }, style), role: "alert", title: error || 'Error loading die' }, "!"));
}
// Render the image
return (React.createElement("img", { src: imgSrc || '', alt: "".concat(type, " die showing ").concat(face), className: className, style: style, onLoad: handleImageLoad, onError: handleImageError }));
};
export { Die };
//# sourceMappingURL=index.esm.js.map