diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 00000000..3bd8fc3e --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,24 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react-hooks/recommended", + "plugin:prettier/recommended", + "prettier", + ], + ignorePatterns: ["dist", ".eslintrc.cjs"], + parser: "@typescript-eslint/parser", + plugins: ["react-refresh", "prettier"], + rules: { + "react-refresh/only-export-components": [ + "warn", + { allowConstantExport: true }, + ], + "prettier/prettier": "warn", + "@typescript-eslint/no-explicit-any": "warn", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "warn", + }, +}; diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index 60a031a3..76f5b84e --- a/.gitignore +++ b/.gitignore @@ -4,88 +4,24 @@ logs npm-debug.log* yarn-debug.log* yarn-error.log* -firebase-debug.log* -firebase-debug.*.log* +pnpm-debug.log* +lerna-debug.log* -# Firebase cache -.firebase/ +node_modules +dist +dist-ssr +*.local -# Firebase config +build -# Uncomment this if you'd like others to create their own Firebase project. -# For a team working on the same Firebase project(s), it is recommended to leave -# it commented so all members can deploy to the same project(s) in .firebaserc. -# .firebaserc - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea .DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* -codex-quest-firebase-adminsdk-sphk3-676cf2444c.json -serviceAccountKey.json +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +.env diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..c4664cb7 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "semi": true, + "singleQuote": false +} diff --git a/coverage/clover.xml b/coverage/clover.xml new file mode 100644 index 00000000..90de6d87 --- /dev/null +++ b/coverage/clover.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/coverage/coverage-final.json @@ -0,0 +1 @@ +{} diff --git a/coverage/lcov-report/base.css b/coverage/lcov-report/base.css new file mode 100644 index 00000000..f418035b --- /dev/null +++ b/coverage/lcov-report/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/coverage/lcov-report/block-navigation.js b/coverage/lcov-report/block-navigation.js new file mode 100644 index 00000000..cc121302 --- /dev/null +++ b/coverage/lcov-report/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selecter that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/coverage/lcov-report/favicon.png b/coverage/lcov-report/favicon.png new file mode 100644 index 00000000..c1525b81 Binary files /dev/null and b/coverage/lcov-report/favicon.png differ diff --git a/coverage/lcov-report/index.html b/coverage/lcov-report/index.html new file mode 100644 index 00000000..ff644674 --- /dev/null +++ b/coverage/lcov-report/index.html @@ -0,0 +1,101 @@ + + + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ Unknown% + Statements + 0/0 +
+ + +
+ Unknown% + Branches + 0/0 +
+ + +
+ Unknown% + Functions + 0/0 +
+ + +
+ Unknown% + Lines + 0/0 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/prettify.css b/coverage/lcov-report/prettify.css new file mode 100644 index 00000000..b317a7cd --- /dev/null +++ b/coverage/lcov-report/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/coverage/lcov-report/prettify.js b/coverage/lcov-report/prettify.js new file mode 100644 index 00000000..b3225238 --- /dev/null +++ b/coverage/lcov-report/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/coverage/lcov-report/sort-arrow-sprite.png b/coverage/lcov-report/sort-arrow-sprite.png new file mode 100644 index 00000000..6ed68316 Binary files /dev/null and b/coverage/lcov-report/sort-arrow-sprite.png differ diff --git a/coverage/lcov-report/sorter.js b/coverage/lcov-report/sorter.js new file mode 100644 index 00000000..2bb296a8 --- /dev/null +++ b/coverage/lcov-report/sorter.js @@ -0,0 +1,196 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + if ( + row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()) + ) { + row.style.display = ''; + } else { + row.style.display = 'none'; + } + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/coverage/lcov.info b/coverage/lcov.info new file mode 100644 index 00000000..e69de29b diff --git a/firebase.json b/firebase.json deleted file mode 100755 index cfbc74ce..00000000 --- a/firebase.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "hosting": { - "public": "build", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - } -} diff --git a/index.html b/index.html old mode 100755 new mode 100644 index 80753aba..f947045c --- a/index.html +++ b/index.html @@ -1,53 +1,38 @@ - - - - - + - - - - - - - - - - - CODEX.QUEST | Character Creator and Manager for BFRPG + + + CODEX.QUEST - - +
- - + diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index b413e106..00000000 --- a/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', -}; \ No newline at end of file diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 00000000..2b9b6dc1 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,199 @@ +/** + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +import type { Config } from "jest"; + +const config: Config = { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/private/var/folders/03/l85r_j512q59xgvgkxyf460m0000gn/T/jest_dx", + + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + coverageDirectory: "coverage", + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // The default configuration for fake timers + // fakeTimers: { + // "enableGlobally": false + // }, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "mjs", + // "cjs", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + preset: "ts-jest", + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state before every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state and implementation before every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: "node", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + // testMatch: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[tj]s?(x)" + // ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "/node_modules/" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // A map from regular expressions to paths to transformers + // transform: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/", + // "\\.pnp\\.[^\\/]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; + +export default config; diff --git a/package-lock.json b/package-lock.json index f1e8b72e..63dd57d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,52 +1,63 @@ { "name": "codex-quest", - "version": "1.16.0.0", + "version": "2.0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codex-quest", - "version": "1.16.0.0", + "version": "2.0.0.0", "license": "CC BY-SA 4.0", "dependencies": { "@ant-design/icons": "^5.2.6", - "@dice-roller/rpg-dice-roller": "^5.4.1", + "@dice-roller/rpg-dice-roller": "^5.5.0", "antd": "^5.10.0", "classnames": "^2.3.2", "dompurify": "^3.0.6", "firebase": "^10.5.0", "firebase-admin": "^11.11.0", - "marked": "^9.1.1", + "marked": "^9.1.2", + "modern-normalize": "^2.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.16.0", - "web-vitals": "^3.5.0" + "react-responsive": "^9.0.2", + "react-router-dom": "^6.16.0" }, "devDependencies": { - "@testing-library/dom": "^9.3.3", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.5.1", - "@types/dompurify": "^3.0.3", - "@types/jest": "^29.5.5", - "@types/marked": "^6.0.0", - "@types/node": "^20.8.5", - "@types/react": "^18.2.28", - "@types/react-dom": "^18.2.13", - "@vitejs/plugin-react": "^4.1.0", - "autoprefixer": "^10.4.15", - "husky": "^8.0.3", + "@types/dompurify": "^3.0.4", + "@types/jest": "^29.5.8", + "@types/node": "^20.9.0", + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", + "@vitejs/plugin-react-swc": "^3.3.2", + "autoprefixer": "^10.4.16", + "eslint": "^8.51.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", "jest": "^29.7.0", - "lint-staged": "^14.0.1", "plop": "^4.0.0", "postcss": "^8.4.31", "prettier": "^3.0.3", "tailwindcss": "^3.3.3", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", - "typescript": "5.2.2", - "vite": "^4.4.11", - "vite-plugin-svgr": "^4.1.0", - "vite-tsconfig-paths": "^4.2.1" + "typescript": "^5.0.2", + "vite": "^4.4.5", + "vite-plugin-svgr": "^4.1.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/@alloc/quick-lru": { @@ -83,9 +94,9 @@ } }, "node_modules/@ant-design/cssinjs": { - "version": "1.17.2", - "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.17.2.tgz", - "integrity": "sha512-vu7lnfEx4Mf8MPzZxn506Zen3Nt4fRr2uutwvdCuTCN5IiU0lDdQ0tiJ24/rmB8+pefwjluYsbyzbQSbgfJy+A==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.17.0.tgz", + "integrity": "sha512-MgGCZ6sfD3yQB0XW0hN4jgixMxApTlDYyct+pc7fRZNO4CaqWWm/9iXkkljNR27lyWLZmm+XiDfcIOo1bnrnMA==", "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", @@ -224,31 +235,31 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", - "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", - "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", + "@babel/generator": "^7.23.0", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.22.20", - "@babel/helpers": "^7.22.15", - "@babel/parser": "^7.22.16", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.20", - "@babel/types": "^7.22.19", - "convert-source-map": "^1.7.0", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", @@ -262,13 +273,22 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", - "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -293,6 +313,30 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", @@ -303,13 +347,13 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -419,14 +463,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", - "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dev": true, "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -518,9 +562,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", - "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "devOptional": true, "bin": { "parser": "bin/babel-parser.js" @@ -706,40 +750,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz", - "integrity": "sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz", - "integrity": "sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -762,19 +776,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.20.tgz", - "integrity": "sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", + "@babel/generator": "^7.23.0", "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.16", - "@babel/types": "^7.22.19", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -782,6 +796,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/types": { "version": "7.23.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", @@ -833,9 +856,9 @@ } }, "node_modules/@dice-roller/rpg-dice-roller": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/@dice-roller/rpg-dice-roller/-/rpg-dice-roller-5.4.1.tgz", - "integrity": "sha512-Tbi/ej65KoHhUPlb/zlj+V/4gMNhF3W6fP+KBF7+mHgFUWnJ1ADKHKHe4jygwn1y8pRlLDAprYCEvQTK2FoRgg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@dice-roller/rpg-dice-roller/-/rpg-dice-roller-5.5.0.tgz", + "integrity": "sha512-ip318MKhfRvqzpXNQaMyqJkOWbeOlaiIopfyluantEVF706fRP4Jy0WsTZrnKhRvhJTZTJa5dTTWqcoPmt1Xww==", "funding": [ "https://github.com/sponsors/dice-roller", "https://github.com/sponsors/GreenImp" @@ -1210,6 +1233,62 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", + "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", + "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@fastify/busboy": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", @@ -1716,15 +1795,15 @@ "integrity": "sha512-+ZplYUN3HOpgCfgInqgdDAbkGGVzES1cs32JJpeqoh87SkRobGXElJx+1GZSaDqzFL+bYiX18qEcBK76mYs8uA==" }, "node_modules/@google-cloud/firestore": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.7.0.tgz", - "integrity": "sha512-bkH2jb5KkQSUa+NAvpip9HQ+rpYhi77IaqHovWuN07adVmvNXX08gPpvPWEzoXYa/wDjEVI7LiAtCWkJJEYTNg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.8.0.tgz", + "integrity": "sha512-JRpk06SmZXLGz0pNx1x7yU3YhkUXheKgH5hbDZ4kMsdhtfV5qPLJLRI4wv69K0cZorIk+zTMOwptue7hizo0eA==", "optional": true, "dependencies": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", "google-gax": "^3.5.7", - "protobufjs": "^7.0.0" + "protobufjs": "^7.2.5" }, "engines": { "node": ">=12.0.0" @@ -1828,6 +1907,39 @@ "node": ">=6" } }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1853,13 +1965,17 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { @@ -1875,6 +1991,45 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -1957,77 +2112,6 @@ } } }, - "node_modules/@jest/core/node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/@jest/core/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -2143,64 +2227,10 @@ } } }, - "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@jest/reporters/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -2279,12 +2309,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, "node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", @@ -2363,10 +2387,13 @@ } }, "node_modules/@ljharb/through": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.9.tgz", - "integrity": "sha512-yN599ZBuMPPK4tdoToLlvgJB4CLK8fGl7ntfy0Wn7U6ttNvHYurd81bfUiK/6sMkiIwm65R6ck4L6+Y3DfVbNQ==", + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.11.tgz", + "integrity": "sha512-ccfcIDlogiXNq5KcbAwbaO7lMh3Tm1i3khMPYpxlK8hH/W53zN81KM9coerRLOnTGu3nfXIniAmQbRI9OxbC0w==", "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, "engines": { "node": ">= 0.4" } @@ -2406,6 +2433,26 @@ "node": ">= 8" } }, + "node_modules/@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2574,17 +2621,17 @@ } }, "node_modules/@remix-run/router": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz", - "integrity": "sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz", + "integrity": "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==", "engines": { "node": ">=14.0.0" } }, "node_modules/@rollup/pluginutils": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", - "integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", + "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", "dev": true, "dependencies": { "@types/estree": "^1.0.0", @@ -2595,7 +2642,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -2801,6 +2848,18 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@svgr/core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@svgr/hast-util-to-babel-ast": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", @@ -2852,56 +2911,216 @@ "@svgr/core": "*" } }, - "node_modules/@testing-library/dom": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", - "integrity": "sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==", + "node_modules/@swc/core": { + "version": "1.3.92", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.92.tgz", + "integrity": "sha512-vx0vUrf4YTEw59njOJ46Ha5i0cZTMYdRHQ7KXU29efN1MxcmJH2RajWLPlvQarOP1ab9iv9cApD7SMchDyx2vA==", "dev": true, + "hasInstallScript": true, "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" + "@swc/counter": "^0.1.1", + "@swc/types": "^0.1.5" }, "engines": { - "node": ">=14" + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.92", + "@swc/core-darwin-x64": "1.3.92", + "@swc/core-linux-arm-gnueabihf": "1.3.92", + "@swc/core-linux-arm64-gnu": "1.3.92", + "@swc/core-linux-arm64-musl": "1.3.92", + "@swc/core-linux-x64-gnu": "1.3.92", + "@swc/core-linux-x64-musl": "1.3.92", + "@swc/core-win32-arm64-msvc": "1.3.92", + "@swc/core-win32-ia32-msvc": "1.3.92", + "@swc/core-win32-x64-msvc": "1.3.92" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } } }, - "node_modules/@testing-library/react": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz", - "integrity": "sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==", + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.92", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.92.tgz", + "integrity": "sha512-v7PqZUBtIF6Q5Cp48gqUiG8zQQnEICpnfNdoiY3xjQAglCGIQCjJIDjreZBoeZQZspB27lQN4eZ43CX18+2SnA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^9.0.0", - "@types/react-dom": "^18.0.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "node": ">=10" } }, - "node_modules/@testing-library/user-event": { - "version": "14.5.1", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.1.tgz", - "integrity": "sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg==", + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.92", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.92.tgz", + "integrity": "sha512-Q3XIgQfXyxxxms3bPN+xGgvwk0TtG9l89IomApu+yTKzaIIlf051mS+lGngjnh9L0aUiCp6ICyjDLtutWP54fw==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12", - "npm": ">=6" - }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.92", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.92.tgz", + "integrity": "sha512-tnOCoCpNVXC+0FCfG84PBZJyLlz0Vfj9MQhyhCvlJz9hQmvpf8nTdKH7RHrOn8VfxtUBLdVi80dXgIFgbvl7qA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.92", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.92.tgz", + "integrity": "sha512-lFfGhX32w8h1j74Iyz0Wv7JByXIwX11OE9UxG+oT7lG0RyXkF4zKyxP8EoxfLrDXse4Oop434p95e3UNC3IfCw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.92", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.92.tgz", + "integrity": "sha512-rOZtRcLj57MSAbiecMsqjzBcZDuaCZ8F6l6JDwGkQ7u1NYR57cqF0QDyU7RKS1Jq27Z/Vg21z5cwqoH5fLN+Sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.92", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.92.tgz", + "integrity": "sha512-qptoMGnBL6v89x/Qpn+l1TH1Y0ed+v0qhNfAEVzZvCvzEMTFXphhlhYbDdpxbzRmCjH6GOGq7Y+xrWt9T1/ARg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.92", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.92.tgz", + "integrity": "sha512-g2KrJ43bZkCZHH4zsIV5ErojuV1OIpUHaEyW1gf7JWKaFBpWYVyubzFPvPkjcxHGLbMsEzO7w/NVfxtGMlFH/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.92", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.92.tgz", + "integrity": "sha512-3MCRGPAYDoQ8Yyd3WsCMc8eFSyKXY5kQLyg/R5zEqA0uthomo0m0F5/fxAJMZGaSdYkU1DgF73ctOWOf+Z/EzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.92", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.92.tgz", + "integrity": "sha512-zqTBKQhgfWm73SVGS8FKhFYDovyRl1f5dTX1IwSKynO0qHkRCqJwauFJv/yevkpJWsI2pFh03xsRs9HncTQKSA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.92", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.92.tgz", + "integrity": "sha512-41bE66ddr9o/Fi1FBh0sHdaKdENPTuDpv1IFHxSg0dJyM/jX8LbkjnpdInYXHBxhcLVAPraVRrNsC4SaoPw2Pg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" } }, + "node_modules/@swc/counter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", + "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", + "dev": true + }, + "node_modules/@swc/types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "dev": true + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -2935,12 +3154,6 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, - "node_modules/@types/aria-query": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", - "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", - "dev": true - }, "node_modules/@types/babel__core": { "version": "7.20.2", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", @@ -3009,15 +3222,15 @@ } }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", + "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", "dev": true }, "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.19.tgz", + "integrity": "sha512-UtOfBtzN9OvpZPPbnnYunfjM7XCI4jyk1NvnFhTVz5krYAnW4o5DCoIekvms+8ApqhB4+9wSge1kBijdfTSmfg==", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -3026,9 +3239,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.36", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz", - "integrity": "sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==", + "version": "4.17.37", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.37.tgz", + "integrity": "sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -3067,9 +3280,9 @@ "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==" }, "node_modules/@types/inquirer": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.3.tgz", - "integrity": "sha512-CzNkWqQftcmk2jaCWdBTf9Sm7xSw4rkI1zpU/Udw3HX5//adEZUIm9STtoRP1qgWj0CWQtJ9UTvqmO2NNjhMJw==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.4.tgz", + "integrity": "sha512-x8UgutCLm5tsp995aeYB8dlT+sGBCtv0zE43tHvo7OljtlA2Rn4+COyLKe9+YjB20uy0G14y0C9vCD2KtNtyGA==", "dev": true, "dependencies": { "@types/through": "*", @@ -3083,71 +3296,45 @@ "dev": true }, "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ==", "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A==", "dev": true, "dependencies": { "@types/istanbul-lib-report": "*" } }, "node_modules/@types/jest": { - "version": "29.5.6", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.6.tgz", - "integrity": "sha512-/t9NnzkOpXb4Nfvg17ieHE6EeSjDS2SGSpNYfoLbUAeL/EOueU/RSdOWFpfQTXBEM7BguYW1XQ0EbM+6RlIh6w==", + "version": "29.5.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz", + "integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==", "dev": true, "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, - "node_modules/@types/jest/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@types/jest/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jest/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-b0jGiOgHtZ2jqdPgPnP6WLCXZk1T8p06A/vPGzUvxpFGgKMbjXJDjC5m52ErqBnIuWZFgGoIJyRdeG5AyreJjA==", - "dependencies": { - "@types/node": "*" + "node_modules/@types/json-schema": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "dev": true + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-b0jGiOgHtZ2jqdPgPnP6WLCXZk1T8p06A/vPGzUvxpFGgKMbjXJDjC5m52ErqBnIuWZFgGoIJyRdeG5AyreJjA==", + "dependencies": { + "@types/node": "*" } }, "node_modules/@types/liftoff": { @@ -3182,26 +3369,16 @@ "@types/mdurl": "*" } }, - "node_modules/@types/marked": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-6.0.0.tgz", - "integrity": "sha512-jmjpa4BwUsmhxcfsgUit/7A9KbrC48Q0q8KvnY107ogcjGgTFDlIL3RpihNpx2Mu1hM4mdFQjoVc4O6JoGKHsA==", - "deprecated": "This is a stub types definition. marked provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "marked": "*" - } - }, "node_modules/@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.3.tgz", + "integrity": "sha512-T5k6kTXak79gwmIOaDF2UUQXFbnBE0zBUzF20pz7wDYu0RQMzWg+Ml/Pz50214NsFHBITkoi5VtdjFZnJ2ijjA==", "optional": true }, "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", + "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==" }, "node_modules/@types/minimatch": { "version": "5.1.2", @@ -3210,17 +3387,17 @@ "optional": true }, "node_modules/@types/node": { - "version": "20.8.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz", - "integrity": "sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", "dependencies": { - "undici-types": "~5.25.1" + "undici-types": "~5.26.4" } }, "node_modules/@types/prop-types": { - "version": "15.7.6", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.6.tgz", - "integrity": "sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg==", + "version": "15.7.8", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.8.tgz", + "integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==", "dev": true }, "node_modules/@types/qs": { @@ -3229,9 +3406,9 @@ "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==" }, "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz", + "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==" }, "node_modules/@types/react": { "version": "18.2.28", @@ -3264,24 +3441,30 @@ } }, "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", + "integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", "dev": true }, "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.2.tgz", + "integrity": "sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw==", "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", - "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.3.tgz", + "integrity": "sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg==", "dependencies": { "@types/http-errors": "*", "@types/mime": "*", @@ -3304,43 +3487,225 @@ } }, "node_modules/@types/trusted-types": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.4.tgz", - "integrity": "sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.5.tgz", + "integrity": "sha512-I3pkr8j/6tmQtKV/ZzHtuaqYSQvyjGRKH4go60Rr0IDLlFxuRT5V32uvB1mecM5G1EVAUyF/4r4QZ1GHgz+mxA==", "dev": true }, "node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "version": "17.0.28", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.28.tgz", + "integrity": "sha512-N3e3fkS86hNhtk6BEnc0rj3zcehaxx8QWhCROJkqpl5Zaoi7nAic3jH8q94jVD3zu5LGk+PUB6KAiDmimYOEQw==", "dev": true, "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==", "dev": true }, - "node_modules/@vitejs/plugin-react": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.1.0.tgz", - "integrity": "sha512-rM0SqazU9iqPUraQ2JlIvReeaxOoRj6n+PzB1C0cBzIbd8qP336nC39/R9yPi3wVcah7E7j/kdU1uCUqMEU4OQ==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz", + "integrity": "sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==", "dev": true, "dependencies": { - "@babel/core": "^7.22.20", - "@babel/plugin-transform-react-jsx-self": "^7.22.5", - "@babel/plugin-transform-react-jsx-source": "^7.22.5", - "@types/babel__core": "^7.20.2", - "react-refresh": "^0.14.0" + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/type-utils": "6.7.5", + "@typescript-eslint/utils": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.5.tgz", + "integrity": "sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz", + "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz", + "integrity": "sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/utils": "6.7.5", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz", + "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz", + "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.5.tgz", + "integrity": "sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/typescript-estree": "6.7.5", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz", + "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.5", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.4.0.tgz", + "integrity": "sha512-m7UaA4Uvz82N/0EOVpZL4XsFIakRqrFKeSNxa1FBLSXGvWrWRBwmZb4qxk+ZIVAZcW3c3dn5YosomDgx62XWcQ==", + "dev": true, + "dependencies": { + "@swc/core": "^1.3.85" }, "peerDependencies": { - "vite": "^4.2.0" + "vite": "^4" } }, "node_modules/abort-controller": { @@ -3371,7 +3736,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "optional": true, + "devOptional": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -3413,16 +3778,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "dependencies": { - "type-fest": "^1.0.2" + "type-fest": "^0.21.3" }, "engines": { - "node": ">=12" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3451,12 +3844,12 @@ } }, "node_modules/antd": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.10.1.tgz", - "integrity": "sha512-alcBmeH4oAdmEdBs6EORH3onRFRjGYRkWtVjPyJxlTIfLILb/+S5Y+ZqisV3AobC8mlj6T3RV8aKG9ic6PgtzQ==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.10.0.tgz", + "integrity": "sha512-qeyfMhcDK2QeuKaC/OCjNCPrJb+3vrBHvVK2swRHsxJvKFSpRerMCShOn/I3CXogrVJazPMluGhy0FQlcHQ4pw==", "dependencies": { "@ant-design/colors": "^7.0.0", - "@ant-design/cssinjs": "^1.17.2", + "@ant-design/cssinjs": "^1.17.0", "@ant-design/icons": "^5.2.6", "@ant-design/react-slick": "~1.0.2", "@babel/runtime": "^7.18.3", @@ -3475,7 +3868,7 @@ "rc-dialog": "~9.3.3", "rc-drawer": "~6.5.2", "rc-dropdown": "~4.1.0", - "rc-field-form": "~1.39.0", + "rc-field-form": "~1.38.2", "rc-image": "~7.3.1", "rc-input": "~1.2.1", "rc-input-number": "~8.1.0", @@ -3489,18 +3882,18 @@ "rc-rate": "~2.12.0", "rc-resize-observer": "^1.3.1", "rc-segmented": "~2.2.2", - "rc-select": "~14.9.1", + "rc-select": "~14.9.0", "rc-slider": "~10.3.0", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", "rc-table": "~7.34.4", "rc-tabs": "~12.12.1", "rc-textarea": "~1.4.0", - "rc-tooltip": "~6.1.1", + "rc-tooltip": "~6.1.0", "rc-tree": "~5.7.12", "rc-tree-select": "~5.13.0", "rc-upload": "~4.3.5", - "rc-util": "^5.38.0", + "rc-util": "^5.37.0", "scroll-into-view-if-needed": "^3.0.3", "throttle-debounce": "^5.0.0" }, @@ -3544,15 +3937,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "devOptional": true }, - "node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, - "dependencies": { - "deep-equal": "^2.0.5" - } - }, "node_modules/array-buffer-byte-length": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", @@ -3575,6 +3959,25 @@ "node": ">=0.10.0" } }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", @@ -3589,6 +3992,85 @@ "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", + "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", @@ -3612,6 +4094,15 @@ "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" }, + "node_modules/asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + } + }, "node_modules/autoprefixer": { "version": "10.4.16", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", @@ -3714,8 +4205,17 @@ "node": ">=8" } }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, @@ -3794,6 +4294,15 @@ } ] }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/bignumber.js": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", @@ -3829,13 +4338,26 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "optional": true }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "optional": true, + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "devOptional": true, "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { @@ -3851,9 +4373,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "funding": [ { @@ -3870,10 +4392,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -3938,6 +4460,21 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -3971,15 +4508,12 @@ } }, "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/camelcase-css": { @@ -3992,9 +4526,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001538", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", - "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", + "version": "1.0.30001549", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz", + "integrity": "sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==", "dev": true, "funding": [ { @@ -4125,9 +4659,9 @@ } }, "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, "funding": [ { @@ -4178,18 +4712,15 @@ } }, "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "dependencies": { - "restore-cursor": "^4.0.0" + "restore-cursor": "^3.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/cli-spinners": { @@ -4204,22 +4735,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-width": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", @@ -4242,43 +4757,6 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -4336,19 +4814,13 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, "node_modules/commander": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", - "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true, "engines": { - "node": ">=16" + "node": ">= 6" } }, "node_modules/complex.js": { @@ -4376,9 +4848,9 @@ } }, "node_modules/compute-scroll-into-view": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz", - "integrity": "sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", + "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==" }, "node_modules/concat-map": { "version": "0.0.1", @@ -4398,9 +4870,9 @@ } }, "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, "node_modules/copy-to-clipboard": { @@ -4478,6 +4950,11 @@ "node": ">= 8" } }, + "node_modules/css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -4535,40 +5012,11 @@ } } }, - "node_modules/deep-equal": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", - "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.1", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "optional": true + "devOptional": true }, "node_modules/deepmerge": { "version": "4.3.1", @@ -4579,6 +5027,40 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -4592,9 +5074,9 @@ } }, "node_modules/define-data-property": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz", - "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", "dev": true, "dependencies": { "get-intrinsic": "^1.2.1", @@ -4605,6 +5087,18 @@ "node": ">= 0.4" } }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -4644,6 +5138,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/del/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/del/node_modules/slash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", @@ -4716,11 +5241,17 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } }, "node_modules/dom-align": { "version": "1.12.4", @@ -4769,9 +5300,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.526", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.526.tgz", - "integrity": "sha512-tjjTMjmZAx1g6COrintLTa2/jcafYKxKoiEkdQOrVdbLaHh2wCt2nsAF8ZHweezkrP+dl/VG9T5nabcYoo0U5Q==", + "version": "1.4.554", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.554.tgz", + "integrity": "sha512-Q0umzPJjfBrrj8unkONTgbKQXzXRrH7sVV7D9ea2yBV3Oaogz991yhbpfvo2LMNkJItmruXTEzVpP9cp7vaIiQ==", "dev": true }, "node_modules/emittery": { @@ -4787,10 +5318,9 @@ } }, "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/end-of-stream": { "version": "1.4.4", @@ -4825,40 +5355,135 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "node_modules/es-abstract": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.2.tgz", + "integrity": "sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==", "dev": true, "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.1", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "node_modules/es-iterator-helpers": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", + "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.1", + "es-set-tostringtag": "^2.0.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.0.1" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", @@ -4896,12 +5521,15 @@ "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" }, "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "devOptional": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/escodegen": { @@ -4935,11 +5563,262 @@ "node": ">=4.0" } }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "optional": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "optional": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", + "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.51.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", + "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.33.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", + "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.12", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.3.tgz", + "integrity": "sha512-Hh0wv8bUNY877+sI0BlCUlsS0TYYQqvzEwJsJJPM2WF4RnTStSnSR3zdJYa2nPOJgg3UghXi54lVyMSmpCalzA==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "optional": true, + "devOptional": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -4951,7 +5830,7 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "optional": true, + "devOptional": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -4977,11 +5856,35 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "optional": true, + "devOptional": true, "engines": { "node": ">=4.0" } @@ -4996,7 +5899,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "optional": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -5010,12 +5913,6 @@ "node": ">=6" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true - }, "node_modules/execa": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", @@ -5051,6 +5948,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/execa/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -5108,23 +6032,17 @@ "node": ">=4" } }, - "node_modules/external-editor/node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "optional": true + "devOptional": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true }, "node_modules/fast-glob": { "version": "3.3.1", @@ -5164,7 +6082,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "optional": true + "devOptional": true }, "node_modules/fast-text-encoding": { "version": "1.0.6", @@ -5173,17 +6091,17 @@ "optional": true }, "node_modules/fast-xml-parser": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.0.tgz", - "integrity": "sha512-5Wln/SBrtlN37aboiNNFHfSALwLzpUx1vJhDgDVPKKG3JrNe8BWTUoNKqkeKk/HqNbKxC8nEAJaBydq30yHoLA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz", + "integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==", "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" } ], "optional": true, @@ -5251,6 +6169,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -5264,16 +6194,19 @@ } }, "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/findup-sync": { @@ -5406,6 +6339,26 @@ "node": ">= 10.13.0" } }, + "node_modules/flat-cache": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -5437,9 +6390,9 @@ } }, "node_modules/fraction.js": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", - "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, "engines": { "node": "*" @@ -5475,6 +6428,24 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -5591,20 +6562,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "optional": true, + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "devOptional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=12" + "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5665,51 +6653,55 @@ } }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", "dev": true, "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" + "define-properties": "^1.1.3" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true - }, "node_modules/google-auth-library": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", @@ -5730,24 +6722,6 @@ "node": ">=12" } }, - "node_modules/google-auth-library/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-auth-library/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true - }, "node_modules/google-gax": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.6.1.tgz", @@ -5848,6 +6822,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "devOptional": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/gtoken": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", @@ -5884,13 +6864,10 @@ } }, "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, "engines": { "node": ">= 0.4.0" } @@ -6033,20 +7010,10 @@ "node": ">=14.18.0" } }, - "node_modules/husky": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", - "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", - "dev": true, - "bin": { - "husky": "lib/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } + "node_modules/hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" }, "node_modules/iconv-lite": { "version": "0.4.24", @@ -6198,21 +7165,6 @@ "node": ">=14.18.0" } }, - "node_modules/inquirer/node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/inquirer/node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -6260,33 +7212,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/inquirer/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/inquirer/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/inquirer/node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -6340,30 +7265,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/inquirer/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/inquirer/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/inquirer/node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -6403,71 +7304,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/inquirer/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -6504,22 +7340,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -6540,6 +7360,21 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -6619,6 +7454,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6628,16 +7478,24 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", "dev": true, - "engines": { - "node": ">=12" + "dependencies": { + "call-bind": "^1.0.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" } }, "node_modules/is-generator-fn": { @@ -6649,6 +7507,21 @@ "node": ">=6" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6661,6 +7534,24 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", @@ -6682,6 +7573,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6719,15 +7622,12 @@ } }, "node_modules/is-path-inside": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/is-plain-object": { @@ -6884,6 +7784,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-weakset": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", @@ -6906,6 +7818,33 @@ "node": ">=0.10.0" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -6949,9 +7888,9 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", - "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", "dev": true, "dependencies": { "@babel/core": "^7.12.3", @@ -6964,39 +7903,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -7038,6 +7944,19 @@ "node": ">=8" } }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, "node_modules/javascript-natural-sort": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", @@ -7115,15 +8034,6 @@ "node": ">=10.17.0" } }, - "node_modules/jest-changed-files/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/jest-changed-files/node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -7136,21 +8046,6 @@ "node": ">=8" } }, - "node_modules/jest-changed-files/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-changed-files/node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -7191,38 +8086,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", @@ -7301,80 +8164,6 @@ } } }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/jest-config/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-config/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/jest-config/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-config/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/jest-diff": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", @@ -7390,38 +8179,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-diff/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/jest-docblock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", @@ -7450,38 +8207,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -7546,38 +8271,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-leak-detector/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-leak-detector/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/jest-matcher-utils": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", @@ -7593,38 +8286,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/jest-message-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", @@ -7645,38 +8306,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/jest-mock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", @@ -7813,146 +8442,39 @@ }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/jest-runtime/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-runtime/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + } }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -7987,38 +8509,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-validate/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/jest-watcher": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", @@ -8038,33 +8540,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-watcher/node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -8105,9 +8580,9 @@ } }, "node_modules/jose": { - "version": "4.14.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.6.tgz", - "integrity": "sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.3.tgz", + "integrity": "sha512-RZJdL9Qjd1sqNdyiVteRGV/bnWtik/+PJh1JP4kT6+x1QQMn+7ryueRys5BEueuayvSVY8CWGCisCDazeRLTuw==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -8167,6 +8642,15 @@ "node": ">=12.0.0" } }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/jsdoc/node_modules/marked": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", @@ -8179,6 +8663,18 @@ "node": ">= 12" } }, + "node_modules/jsdoc/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -8200,12 +8696,30 @@ "bignumber.js": "^9.0.0" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json2mq": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", @@ -8266,36 +8780,21 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/jsonwebtoken/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" }, "engines": { - "node": ">=10" + "node": ">=4.0" } }, - "node_modules/jsonwebtoken/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", @@ -8308,16 +8807,16 @@ } }, "node_modules/jwks-rsa": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", - "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz", + "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==", "dependencies": { - "@types/express": "^4.17.14", - "@types/jsonwebtoken": "^9.0.0", + "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", "debug": "^4.3.4", - "jose": "^4.10.4", + "jose": "^4.14.6", "limiter": "^1.1.5", - "lru-memoizer": "^2.1.4" + "lru-memoizer": "^2.2.0" }, "engines": { "node": ">=14" @@ -8333,6 +8832,15 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -8370,13 +8878,13 @@ } }, "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "optional": true, + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -8430,80 +8938,19 @@ "uc.micro": "^1.0.1" } }, - "node_modules/lint-staged": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-14.0.1.tgz", - "integrity": "sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "chalk": "5.3.0", - "commander": "11.0.0", - "debug": "4.3.4", - "execa": "7.2.0", - "lilconfig": "2.1.0", - "listr2": "6.6.1", - "micromatch": "4.0.5", - "pidtree": "0.6.0", - "string-argv": "0.3.2", - "yaml": "2.3.1" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "p-locate": "^5.0.0" }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/lint-staged/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/listr2": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz", - "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==", - "dev": true, - "dependencies": { - "cli-truncate": "^3.1.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^5.0.1", - "rfdc": "^1.3.0", - "wrap-ansi": "^8.1.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { @@ -8564,6 +9011,12 @@ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -8597,25 +9050,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/log-update": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", - "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", - "dev": true, - "dependencies": { - "ansi-escapes": "^5.0.0", - "cli-cursor": "^4.0.0", - "slice-ansi": "^5.0.0", - "strip-ansi": "^7.0.1", - "wrap-ansi": "^8.0.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", @@ -8642,12 +9076,14 @@ } }, "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dependencies": { - "yallist": "^3.0.2" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, "node_modules/lru-memoizer": { @@ -8673,63 +9109,21 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "bin": { - "lz-string": "bin/bin.js" - } - }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "semver": "^7.5.3" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -8803,12 +9197,20 @@ "node": ">= 16" } }, + "node_modules/matchmediaquery": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.3.1.tgz", + "integrity": "sha512-Hlk20WQHRIm9EE9luN1kjRjYXAQToHOIAHPJn9buxBwuhfTHoKUcX+lXBbxc85DVQfXYbEQ4HcwQdd128E3qHQ==", + "dependencies": { + "css-mediaquery": "^0.1.2" + } + }, "node_modules/mathjs": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.11.1.tgz", - "integrity": "sha512-uWrwMrhU31TCqHKmm1yFz0C352njGUVr/I1UnpMOxI/VBTTbCktx/mREUXx5Vyg11xrFdg/F3wnMM7Ql/csVsQ==", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.12.0.tgz", + "integrity": "sha512-UGhVw8rS1AyedyI55DGz9q1qZ0p98kyKPyc9vherBkoueLntPfKtPBh14x+V4cdUWK0NZV2TBwqRFlvadscSuw==", "dependencies": { - "@babel/runtime": "^7.22.15", + "@babel/runtime": "^7.23.2", "complex.js": "^2.1.1", "decimal.js": "^10.4.3", "escape-latex": "^1.2.0", @@ -8905,27 +9307,24 @@ } }, "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "optional": true, + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" + "node": "*" } }, "node_modules/minimist": { @@ -8938,15 +9337,29 @@ } }, "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "optional": true, + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, "bin": { - "mkdirp": "bin/cmd.js" + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/modern-normalize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/modern-normalize/-/modern-normalize-2.0.0.tgz", + "integrity": "sha512-CxBoEVKh5U4DH3XuNbc5ONLF6dQBc8dSc7pdZ1957FGbIO5JBqGqqchhET9dTexri8/pk9xBL6+5ceOtCIp1QA==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ms": { @@ -9071,19 +9484,35 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/node-plop/node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "node_modules/node-plop/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", "dev": true, - "bin": { - "mkdirp": "dist/cjs/src/bin.js" + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/node-plop/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/node-releases": { @@ -9141,7 +9570,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9164,22 +9592,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -9222,6 +9634,50 @@ "node": ">=0.10.0" } }, + "node_modules/object.entries": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object.map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", @@ -9247,6 +9703,23 @@ "node": ">=0.10.0" } }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -9257,32 +9730,50 @@ } }, "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "dependencies": { - "mimic-fn": "^4.0.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">=12" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "optional": true, + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -9311,6 +9802,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ora/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -9323,12 +9826,43 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/ora/node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ora/node_modules/emoji-regex": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz", "integrity": "sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==", "dev": true }, + "node_modules/ora/node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ora/node_modules/string-width": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", @@ -9346,6 +9880,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -9371,27 +9920,15 @@ } }, "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9581,20 +10118,8 @@ "engines": { "node": ">=8.6" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pify": { @@ -9627,6 +10152,58 @@ "node": ">=8" } }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/plop": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/plop/-/plop-4.0.0.tgz", @@ -9793,10 +10370,10 @@ "dev": true }, "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "optional": true, + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "engines": { "node": ">= 0.8.0" } @@ -9816,18 +10393,30 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -9842,6 +10431,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -9855,6 +10450,16 @@ "node": ">= 6" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proto3-json-serializer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz", @@ -9918,48 +10523,76 @@ "protobufjs": "^7.0.0" } }, - "node_modules/protobufjs-cli/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/protobufjs-cli/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "optional": true, "dependencies": { - "yallist": "^4.0.0" + "balanced-match": "^1.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/protobufjs-cli/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/protobufjs-cli/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "optional": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=10" } }, - "node_modules/protobufjs-cli/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true + "node_modules/protobufjs-cli/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "optional": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.3.tgz", - "integrity": "sha512-KddyFewCsO0j3+np81IQ+SweXLDnDQTs5s67BOnrYmYe/yNmUhttQyGsYzy8yUnoljGAQ9sl38YB4vH8ur7Y+w==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", "dev": true, "funding": [ { @@ -10115,9 +10748,9 @@ } }, "node_modules/rc-field-form": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.39.0.tgz", - "integrity": "sha512-V7Wk7uji1jBsUGGgP788H9rpFy55HLiD4lywTlktUGjK7EgW5dt+mq1MPbtCpPRMzs83vZBW4SOChOmCACz4WA==", + "version": "1.38.2", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.38.2.tgz", + "integrity": "sha512-O83Oi1qPyEv31Sg+Jwvsj6pXc8uQI2BtIAkURr5lvEYHVggXJhdU/nynK8wY1gbw0qR48k731sN5ON4egRCROA==", "dependencies": { "@babel/runtime": "^7.18.0", "async-validator": "^4.1.0", @@ -10372,9 +11005,9 @@ } }, "node_modules/rc-select": { - "version": "14.9.1", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.9.1.tgz", - "integrity": "sha512-vORdRgOXOIHLanzYi+vpDGydk6eyXT/XZS21PpRlhBTMaieK4JhkGQX2RO8QwFv/gFMjv0QInIgyM+0zmUeIjw==", + "version": "14.9.0", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.9.0.tgz", + "integrity": "sha512-vbIhK1MBA12MRdxXbiylSCTPKsWV8WmeN7OyATk9I0LsuIVwe/kBAUNH02am1ryjoylbK+AH309a6X1AflGRSw==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/trigger": "^1.5.0", @@ -10561,23 +11194,18 @@ } }, "node_modules/rc-util": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.38.0.tgz", - "integrity": "sha512-yV/YBNdFn+edyBpBdCqkPE29Su0jWcHNgwx2dJbRqMrMfrUcMJUjCRV+ZPhcvWyKFJ63GzEerPrz9JIVo0zXmA==", + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.37.0.tgz", + "integrity": "sha512-cPMV8DzaHI1KDaS7XPRXAf4J7mtBqjvjikLpQieaeOO7+cEbqY2j7Kso/T0R0OiEZTNcLS/8Zl9YrlXiO9UbjQ==", "dependencies": { "@babel/runtime": "^7.18.3", - "react-is": "^18.2.0" + "react-is": "^16.12.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, - "node_modules/rc-util/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, "node_modules/rc-virtual-list": { "version": "3.11.2", "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.11.2.tgz", @@ -10620,26 +11248,33 @@ } }, "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", - "dev": true, + "node_modules/react-responsive": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-9.0.2.tgz", + "integrity": "sha512-+4CCab7z8G8glgJoRjAwocsgsv6VA2w7JPxFWHRc7kvz8mec1/K5LutNC2MG28Mn8mu6+bu04XZxHv5gyfT7xQ==", + "dependencies": { + "hyphenate-style-name": "^1.0.0", + "matchmediaquery": "^0.3.0", + "prop-types": "^15.6.1", + "shallow-equal": "^1.2.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=0.10" + }, + "peerDependencies": { + "react": ">=16.8.0" } }, "node_modules/react-router": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.17.0.tgz", - "integrity": "sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz", + "integrity": "sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==", "dependencies": { - "@remix-run/router": "1.10.0" + "@remix-run/router": "1.9.0" }, "engines": { "node": ">=14.0.0" @@ -10649,12 +11284,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.17.0.tgz", - "integrity": "sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz", + "integrity": "sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==", "dependencies": { - "@remix-run/router": "1.10.0", - "react-router": "6.17.0" + "@remix-run/router": "1.9.0", + "react-router": "6.16.0" }, "engines": { "node": ">=14.0.0" @@ -10711,6 +11346,26 @@ "node": ">= 10.13.0" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", + "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", @@ -10756,9 +11411,9 @@ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" }, "node_modules/resolve": { - "version": "1.22.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", - "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { "is-core-module": "^2.13.0", @@ -10825,43 +11480,16 @@ } }, "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/restore-cursor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/retry": { @@ -10896,12 +11524,6 @@ "node": ">=0.10.0" } }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -10917,62 +11539,88 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "devOptional": true, + "node_modules/run-applescript/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": "*" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/run-applescript/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" } }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "node_modules/run-applescript/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "path-key": "^3.0.0" }, "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/rollup": { - "version": "3.29.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.2.tgz", - "integrity": "sha512-CJouHoZ27v6siztc21eEQGo0kIcE5D1gVPA571ez0mMYb25LGYGKnVNXpEj5MGlepmDWGXNjDB5q7uNiPHC11A==", + "node_modules/run-applescript/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">=6" } }, "node_modules/run-async": { @@ -11016,6 +11664,24 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -11035,6 +11701,20 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -11063,12 +11743,17 @@ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/sentence-case": { @@ -11096,6 +11781,11 @@ "node": ">= 0.4" } }, + "node_modules/shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -11152,34 +11842,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/snake-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", @@ -11236,6 +11898,15 @@ "node": ">=10" } }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/stdin-discarder": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", @@ -11251,18 +11922,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dev": true, - "dependencies": { - "internal-slot": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -11287,15 +11946,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true, - "engines": { - "node": ">=0.6.19" - } - }, "node_modules/string-convert": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", @@ -11314,60 +11964,93 @@ "node": ">=10" } }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { - "ansi-regex": "^5.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/string.prototype.matchall": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", + "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", "dev": true, "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", + "side-channel": "^1.0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", "dev": true, "dependencies": { - "ansi-regex": "^6.0.1" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", "dev": true, - "engines": { - "node": ">=12" + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/strip-bom": { @@ -11442,25 +12125,6 @@ "node": ">=8" } }, - "node_modules/sucrase/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/sucrase/node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -11481,18 +12145,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sucrase/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11523,6 +12175,22 @@ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", "dev": true }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tailwindcss": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", @@ -11578,58 +12246,16 @@ }, "node_modules/test-exclude": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" }, "engines": { - "node": "*" + "node": ">=8" } }, "node_modules/text-decoding": { @@ -11637,6 +12263,12 @@ "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -11680,16 +12312,28 @@ "tslib": "^2.0.3" } }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "optional": true, + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, "dependencies": { - "rimraf": "^3.0.0" + "os-tmpdir": "~1.0.2" }, "engines": { - "node": ">=8.17.0" + "node": ">=0.6.0" } }, "node_modules/tmpl": { @@ -11729,6 +12373,18 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -11778,39 +12434,6 @@ } } }, - "node_modules/ts-jest/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -11860,38 +12483,18 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, - "node_modules/tsconfck": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-2.1.2.tgz", - "integrity": "sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==", - "dev": true, - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^14.13.1 || ^16 || >=18" - }, - "peerDependencies": { - "typescript": "^4.3.5 || ^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "optional": true, + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "dependencies": { - "prelude-ls": "~1.1.2" + "prelude-ls": "^1.2.1" }, "engines": { "node": ">= 0.8.0" @@ -11907,9 +12510,9 @@ } }, "node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { "node": ">=10" @@ -11918,6 +12521,71 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typed-function": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.1.tgz", @@ -11957,6 +12625,21 @@ "node": ">=0.8.0" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -11973,14 +12656,23 @@ "optional": true }, "node_modules/undici-types": { - "version": "5.25.3", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", - "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } }, "node_modules/update-browserslist-db": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.12.tgz", - "integrity": "sha512-tE1smlR58jxbFMtrMpFNRmsrOXlpNXss965T1CrpwuZUzUAg/TBQc94SpyhDLSzrqrJS9xTRBthnZAGcE1oaxg==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { @@ -12025,6 +12717,15 @@ "tslib": "^2.0.3" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -12050,14 +12751,14 @@ "dev": true }, "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", + "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" @@ -12141,25 +12842,6 @@ "vite": "^2.6.0 || 3 || 4" } }, - "node_modules/vite-tsconfig-paths": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.2.1.tgz", - "integrity": "sha512-GNUI6ZgPqT3oervkvzU+qtys83+75N/OuDaQl7HmOqFTb0pjZsuARrRipsyJhJ3enqV8beI1xhGbToR4o78nSQ==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "globrex": "^0.1.2", - "tsconfck": "^2.1.0" - }, - "peerDependencies": { - "vite": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -12178,11 +12860,6 @@ "defaults": "^1.0.3" } }, - "node_modules/web-vitals": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.0.tgz", - "integrity": "sha512-f5YnCHVG9Y6uLCePD4tY8bO/Ge15NPEQWtvm3tPzDKygloiqtb4SVqRHBcrIAqo2ztqX5XueqDn97zHF0LdT6w==" - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -12249,6 +12926,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/which-collection": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", @@ -12299,32 +13002,17 @@ "dev": true }, "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=8" } }, "node_modules/wrappy": { @@ -12361,15 +13049,14 @@ } }, "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz", + "integrity": "sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==", "dev": true, "engines": { "node": ">= 14" @@ -12400,43 +13087,6 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json old mode 100755 new mode 100644 index 85457b38..d63fd1d1 --- a/package.json +++ b/package.json @@ -1,86 +1,61 @@ { "name": "codex-quest", - "title": "codex quest", - "version": "1.16.0.0", + "title": "Codex Quest", "bfrpgEdition": "4th", "bfrpgRelease": "137", - "private": true, "license": "CC BY-SA 4.0", + "version": "2.0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --logLevel info", + "plop": "plop", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview", + "format": "prettier --write './src/**/*.{js,ts,tsx}'", + "test": "jest" + }, "dependencies": { "@ant-design/icons": "^5.2.6", - "@dice-roller/rpg-dice-roller": "^5.4.1", + "@dice-roller/rpg-dice-roller": "^5.5.0", "antd": "^5.10.0", "classnames": "^2.3.2", "dompurify": "^3.0.6", "firebase": "^10.5.0", "firebase-admin": "^11.11.0", - "marked": "^9.1.1", + "marked": "^9.1.2", + "modern-normalize": "^2.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.16.0", - "web-vitals": "^3.5.0" - }, - "lint-staged": { - "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [ - "prettier --write" - ] - }, - "scripts": { - "start": "vite --logLevel info", - "plop": "plop", - "build": "tsc && vite build", - "serve": "vite preview", - "test": "jest --coverage" - }, - "eslintConfig": { - "extends": [ - "react-app" - ], - "rules": { - "react-hooks/exhaustive-deps": "off" - } - }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "react-responsive": "^9.0.2", + "react-router-dom": "^6.16.0" }, "devDependencies": { - "@testing-library/dom": "^9.3.3", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.5.1", - "@types/dompurify": "^3.0.3", - "@types/jest": "^29.5.5", - "@types/marked": "^6.0.0", - "@types/node": "^20.8.5", - "@types/react": "^18.2.28", - "@types/react-dom": "^18.2.13", - "@vitejs/plugin-react": "^4.1.0", - "autoprefixer": "^10.4.15", - "husky": "^8.0.3", + "@types/dompurify": "^3.0.4", + "@types/jest": "^29.5.8", + "@types/node": "^20.9.0", + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", + "@vitejs/plugin-react-swc": "^3.3.2", + "autoprefixer": "^10.4.16", + "eslint": "^8.51.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", "jest": "^29.7.0", - "lint-staged": "^14.0.1", "plop": "^4.0.0", "postcss": "^8.4.31", "prettier": "^3.0.3", "tailwindcss": "^3.3.3", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", - "typescript": "5.2.2", - "vite": "^4.4.11", - "vite-plugin-svgr": "^4.1.0", - "vite-tsconfig-paths": "^4.2.1" + "typescript": "^5.0.2", + "vite": "^4.4.5", + "vite-plugin-svgr": "^4.1.0" } } diff --git a/plop-templates/Component/Component.tsx.hbs b/plop-templates/Component/Component.tsx.hbs index dfba7d92..5b2f8c11 100644 --- a/plop-templates/Component/Component.tsx.hbs +++ b/plop-templates/Component/Component.tsx.hbs @@ -1,10 +1,9 @@ import React from 'react'; -interface {{pascalCase name}}Props { -// define your prop types here -} +interface {{pascalCase name}}Props {} -const {{pascalCase name}}: React.FC<{{pascalCase name}}Props> = ({}) => { - return
{{pascalCase name}}
}; +const {{pascalCase name}}: React.FC<{{pascalCase name}}Props & React.ComponentPropsWithRef<"div">> = ({ className }) => { + return
{{pascalCase name}}
+}; export default {{pascalCase name}}; \ No newline at end of file diff --git a/plopfile.js b/plopfile.js index 1e6df045..ba5381f6 100644 --- a/plopfile.js +++ b/plopfile.js @@ -1,4 +1,4 @@ -module.exports = function (plop) { +export default function (plop) { plop.setGenerator("component", { description: "Create a component dir/file", prompts: [ @@ -21,4 +21,4 @@ module.exports = function (plop) { // }, ], }); -}; +} diff --git a/postcss.config.js b/postcss.config.js old mode 100755 new mode 100644 index 33ad091d..2e7af2b7 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { plugins: { tailwindcss: {}, autoprefixer: {}, diff --git a/public/manifest.json b/public/manifest.json deleted file mode 100755 index 1a37689e..00000000 --- a/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "CODEX.QUEST", - "name": "CODEX.QUEST", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/public/robots.txt b/public/robots.txt deleted file mode 100755 index e9e57dc4..00000000 --- a/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/scripts/migrateCharacterClass.ts b/scripts/migrateCharacterClass.ts new file mode 100644 index 00000000..fe264416 --- /dev/null +++ b/scripts/migrateCharacterClass.ts @@ -0,0 +1,30 @@ +import firebase from "firebase/compat/app"; + +const db = firebase.firestore(); + +// Step 1: Get all users +const usersRef = db.collection("users"); +usersRef.get().then((userSnapshot) => { + userSnapshot.forEach((userDoc) => { + const userId = userDoc.id; + + // Step 2: For each user, get all characters + const charactersRef = db.collection(`users/${userId}/characters`); + charactersRef.get().then((characterSnapshot) => { + characterSnapshot.forEach((characterDoc) => { + const characterData = characterDoc.data(); + + // Step 3: Check & Update + if (typeof characterData.class === "string") { + const newClassArray = characterData.class.split(" "); + characterData.class = newClassArray; + + // Step 4: Update Firestore + charactersRef.doc(characterDoc.id).update({ + class: newClassArray, + }); + } + }); + }); + }); +}); diff --git a/src/.firebaserc b/src/.firebaserc new file mode 100755 index 00000000..b177cb54 --- /dev/null +++ b/src/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "codex-quest" + } +} diff --git a/src/App.tsx b/src/App.tsx old mode 100755 new mode 100644 index c07f9194..1b8353ed --- a/src/App.tsx +++ b/src/App.tsx @@ -1,40 +1,28 @@ +import "./firebase"; +import { ConfigProvider, Spin } from "antd"; +import React, { Suspense, lazy, useEffect, useState } from "react"; import { Route, Routes } from "react-router-dom"; +import { User, getAuth, onAuthStateChanged } from "firebase/auth"; +import "../node_modules/modern-normalize/modern-normalize.css"; import PageLayout from "./components/PageLayout/PageLayout"; -// import CharacterList from "./pages/CharacterList/CharacterList"; -import { - getAuth, - GoogleAuthProvider, - signInWithPopup, - onAuthStateChanged, - User, -} from "firebase/auth"; -import { Suspense, lazy, useEffect, useState } from "react"; -import { doc, setDoc } from "firebase/firestore"; -import { db } from "./firebase.js"; -import { ConfigProvider, Spin } from "antd"; -import GameSheet from "./pages/GameSheet/GameSheet"; -import GameList from "./pages/GameList/GameList"; -import { MODE, ModeType } from "./data/definitions"; -// import Welcome from "./pages/Welcome/Welcome"; -// import CharacterCreator from "./pages/CharacterCreator/CharacterCreator"; -// import Sources from "./pages/Sources/Sources"; - -// Lazy load all the components that are routed -const CharacterList = lazy(() => import("./pages/CharacterList/CharacterList")); -const Welcome = lazy(() => import("./pages/Welcome/Welcome")); -const CharacterCreator = lazy( - () => import("./pages/CharacterCreator/CharacterCreator") +import { cqTheme } from "./support/theme"; +const PageWelcome = lazy(() => import("./components/PageWelcome/PageWelcome")); +const PageCharacterSheet = lazy( + () => import("./components/PageCharacterSheet/PageCharacterSheet"), +); +const PageHome = lazy(() => import("./components/PageHome/PageHome")); +const PageGameSheet = lazy( + () => import("./components/PageGameSheet/PageGameSheet"), ); -const Sources = lazy(() => import("./pages/Sources/Sources")); -const CharacterSheet = lazy( - () => import("./pages/CharacterSheet/CharacterSheet") +const PageNewCharacter = lazy( + () => import("./components/PageNewCharacter/PageNewCharacter"), ); -const GMPortal = lazy(() => import("./pages/GameList/GameList")); +const PageNewGame = lazy(() => import("./components/PageNewGame/PageNewGame")); +const PageSources = lazy(() => import("./components/PageSources/PageSources")); -function App() { +const App: React.FC = () => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); - const [mode, setMode] = useState(MODE.PLAYER); const auth = getAuth(); @@ -45,88 +33,72 @@ function App() { }); return () => unsubscribe(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - - const handleLogin = async () => { - const provider = new GoogleAuthProvider(); - - try { - const result = await signInWithPopup(auth, provider); - const user = result.user; - - // Add this check for user being null - if (!user) { - console.error("User object is null after sign-in"); - return; - } - - // Create a document for the user in the 'users' collection - const userDocRef = doc(db, "users", user.uid); - - await setDoc( - userDocRef, - { - name: user.displayName, - email: user.email, - // add any additional fields here - }, - { merge: true } - ); // Merge the data if document already exists - } catch (error) { - console.error("Failed to login and set user doc:", error); - } - }; - + // DONE: standardize useNotification custom hook wherever notifications are used. + // DONE: Add regular equipment to character sheet. + // DONE: Click Saving Throws to roll dice. + // DONE: Click Special Abilities to roll dice. + // DONE: Character Creator: Show a list of equipment character has purchased. + // TODO: Verify race class filtering works when creating new character. + // TODO: There are race/class specific special parameters (like incrementing hit dice), that need to be checked. + // DONE: Rollinitiative button needs functionality. + // DONE: CHEAT SHEET button needs functionality. + // DONE: VIRTUAL DICE buttons need functionality. + // DONE: LEVEL UP MODAL needs functionality. + // DONE: ATTACK button needs functionality. + // DONE: Shortbow name is crunched in character sheet. + // DONE: Money needs weight + // DONE: Selecting Armor/Shield does not change AC value + // DONE: Selecting Armor/Shield does get saved + // DONE: Spells should be listed on character sheet + // DONE: GM SHEET: the rest of the special abilities should be fixed + // DONE: GM SHEET: notes panel + // DONE: makeChange into custom hook + // DONE: Site title and favicon + // DONE: Separate general equipment into sub categories https://ant.design/components/collapse#components-collapse-demo-mix + // DONE: make whole collapse bar clickable + // DONE: Welcome Page + // DONE: Sources Page + // TODO: Edit character name + // DONE: Switch (Options.tsx) to turn on/off supplemental classes/races + console.error("REMAINING TODOS!"); return ( - + }> - - } - > + }> + ) : user ? ( - mode === MODE.PLAYER ? ( - - ) : ( - - ) + ) : ( - + ) } /> + } + /> + } /> } + element={} + /> + } /> - } /> - } /> - } /> + } /> ); -} +}; export default App; diff --git "a/src/assets/images/DALL\302\267E 2023-12-13 18.54.19 - A simple full-body black and white line drawing of an Asian woman Paladin. The Paladin should be depicted as a noble and righteous warrior, embodying .png" "b/src/assets/images/DALL\302\267E 2023-12-13 18.54.19 - A simple full-body black and white line drawing of an Asian woman Paladin. The Paladin should be depicted as a noble and righteous warrior, embodying .png" new file mode 100644 index 00000000..769ecc34 Binary files /dev/null and "b/src/assets/images/DALL\302\267E 2023-12-13 18.54.19 - A simple full-body black and white line drawing of an Asian woman Paladin. The Paladin should be depicted as a noble and righteous warrior, embodying .png" differ diff --git "a/src/assets/images/DALL\302\267E 2023-12-13 18.54.50 - A simple black and white line drawing of an abstract symbol representing the 'Read Magic' spell, without any writing. The symbol should convey the con.png" "b/src/assets/images/DALL\302\267E 2023-12-13 18.54.50 - A simple black and white line drawing of an abstract symbol representing the 'Read Magic' spell, without any writing. The symbol should convey the con.png" new file mode 100644 index 00000000..71827f44 Binary files /dev/null and "b/src/assets/images/DALL\302\267E 2023-12-13 18.54.50 - A simple black and white line drawing of an abstract symbol representing the 'Read Magic' spell, without any writing. The symbol should convey the con.png" differ diff --git a/src/assets/images/char-sheet-v2.png b/src/assets/images/char-sheet-v2.png new file mode 100644 index 00000000..3b4c6c48 Binary files /dev/null and b/src/assets/images/char-sheet-v2.png differ diff --git a/src/assets/images/classes/assassin.jpg b/src/assets/images/classes/assassin.jpg new file mode 100644 index 00000000..3b858f4f Binary files /dev/null and b/src/assets/images/classes/assassin.jpg differ diff --git a/src/assets/images/classes/barbarian.jpg b/src/assets/images/classes/barbarian.jpg new file mode 100644 index 00000000..001630e4 Binary files /dev/null and b/src/assets/images/classes/barbarian.jpg differ diff --git a/src/assets/images/classes/cleric.jpg b/src/assets/images/classes/cleric.jpg new file mode 100644 index 00000000..b0664923 Binary files /dev/null and b/src/assets/images/classes/cleric.jpg differ diff --git a/src/assets/images/classes/druid.jpg b/src/assets/images/classes/druid.jpg new file mode 100644 index 00000000..e8660700 Binary files /dev/null and b/src/assets/images/classes/druid.jpg differ diff --git a/src/assets/images/classes/fighter.jpg b/src/assets/images/classes/fighter.jpg new file mode 100644 index 00000000..f8c2ef94 Binary files /dev/null and b/src/assets/images/classes/fighter.jpg differ diff --git a/src/assets/images/classes/illusionist.jpg b/src/assets/images/classes/illusionist.jpg new file mode 100644 index 00000000..82029ab2 Binary files /dev/null and b/src/assets/images/classes/illusionist.jpg differ diff --git a/src/assets/images/classes/magic-user.jpg b/src/assets/images/classes/magic-user.jpg new file mode 100644 index 00000000..f4dcd804 Binary files /dev/null and b/src/assets/images/classes/magic-user.jpg differ diff --git a/src/assets/images/classes/necromancer.jpg b/src/assets/images/classes/necromancer.jpg new file mode 100644 index 00000000..67ff5fbf Binary files /dev/null and b/src/assets/images/classes/necromancer.jpg differ diff --git a/src/assets/images/classes/paladin.jpg b/src/assets/images/classes/paladin.jpg new file mode 100644 index 00000000..fecfe4a0 Binary files /dev/null and b/src/assets/images/classes/paladin.jpg differ diff --git a/src/assets/images/classes/ranger.jpg b/src/assets/images/classes/ranger.jpg new file mode 100644 index 00000000..4a8413a5 Binary files /dev/null and b/src/assets/images/classes/ranger.jpg differ diff --git a/src/assets/images/classes/scout.jpg b/src/assets/images/classes/scout.jpg new file mode 100644 index 00000000..3cacaa0a Binary files /dev/null and b/src/assets/images/classes/scout.jpg differ diff --git a/src/assets/images/classes/spellcrafter.jpg b/src/assets/images/classes/spellcrafter.jpg new file mode 100644 index 00000000..ab5b2f47 Binary files /dev/null and b/src/assets/images/classes/spellcrafter.jpg differ diff --git a/src/assets/images/classes/thief.jpg b/src/assets/images/classes/thief.jpg new file mode 100644 index 00000000..aabfd6cb Binary files /dev/null and b/src/assets/images/classes/thief.jpg differ diff --git a/src/assets/images/dice.svg b/src/assets/images/dice.svg index 34434d3d..693da99f 100644 --- a/src/assets/images/dice.svg +++ b/src/assets/images/dice.svg @@ -1 +1,5 @@ - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/src/assets/images/dragon-head.png b/src/assets/images/dragon-head.png new file mode 100644 index 00000000..23efeb9f Binary files /dev/null and b/src/assets/images/dragon-head.png differ diff --git a/src/assets/images/dragon-head.svg b/src/assets/images/dragon-head.svg new file mode 100644 index 00000000..ede27ea3 --- /dev/null +++ b/src/assets/images/dragon-head.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/favicon/android-chrome-192x192.png b/src/assets/images/favicon/android-chrome-192x192.png new file mode 100644 index 00000000..d8c015cf Binary files /dev/null and b/src/assets/images/favicon/android-chrome-192x192.png differ diff --git a/src/assets/images/favicon/android-chrome-512x512.png b/src/assets/images/favicon/android-chrome-512x512.png new file mode 100644 index 00000000..dfe10a3c Binary files /dev/null and b/src/assets/images/favicon/android-chrome-512x512.png differ diff --git a/src/assets/images/favicon/apple-touch-icon.png b/src/assets/images/favicon/apple-touch-icon.png new file mode 100644 index 00000000..c346b393 Binary files /dev/null and b/src/assets/images/favicon/apple-touch-icon.png differ diff --git a/src/assets/images/favicon/favicon-16x16.png b/src/assets/images/favicon/favicon-16x16.png new file mode 100644 index 00000000..d7d43e35 Binary files /dev/null and b/src/assets/images/favicon/favicon-16x16.png differ diff --git a/src/assets/images/favicon/favicon-32x32.png b/src/assets/images/favicon/favicon-32x32.png new file mode 100644 index 00000000..eec07604 Binary files /dev/null and b/src/assets/images/favicon/favicon-32x32.png differ diff --git a/src/assets/images/favicon/favicon.ico b/src/assets/images/favicon/favicon.ico new file mode 100644 index 00000000..4eb255c8 Binary files /dev/null and b/src/assets/images/favicon/favicon.ico differ diff --git a/src/assets/images/favicon/site.webmanifest b/src/assets/images/favicon/site.webmanifest new file mode 100644 index 00000000..45dc8a20 --- /dev/null +++ b/src/assets/images/favicon/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/src/assets/images/inverted-dice-6.png b/src/assets/images/inverted-dice-6.png new file mode 100644 index 00000000..59fef879 Binary files /dev/null and b/src/assets/images/inverted-dice-6.png differ diff --git a/src/assets/images/ironhide_sheet.png b/src/assets/images/ironhide_sheet.png deleted file mode 100755 index 8ce53029..00000000 Binary files a/src/assets/images/ironhide_sheet.png and /dev/null differ diff --git a/src/assets/images/noise.svg b/src/assets/images/noise.svg new file mode 100644 index 00000000..1548b049 --- /dev/null +++ b/src/assets/images/noise.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/assets/images/races/bisren.jpg b/src/assets/images/races/bisren.jpg new file mode 100644 index 00000000..9df80293 Binary files /dev/null and b/src/assets/images/races/bisren.jpg differ diff --git a/src/assets/images/races/canein.jpg b/src/assets/images/races/canein.jpg new file mode 100644 index 00000000..ff777bdb Binary files /dev/null and b/src/assets/images/races/canein.jpg differ diff --git a/src/assets/images/races/chelonian.jpg b/src/assets/images/races/chelonian.jpg new file mode 100644 index 00000000..f20b9991 Binary files /dev/null and b/src/assets/images/races/chelonian.jpg differ diff --git a/src/assets/images/races/default.jpg b/src/assets/images/races/default.jpg new file mode 100644 index 00000000..ea11e4bd Binary files /dev/null and b/src/assets/images/races/default.jpg differ diff --git a/src/assets/images/races/dwarf.jpg b/src/assets/images/races/dwarf.jpg new file mode 100644 index 00000000..4be32c1c Binary files /dev/null and b/src/assets/images/races/dwarf.jpg differ diff --git a/src/assets/images/races/elf.jpg b/src/assets/images/races/elf.jpg new file mode 100644 index 00000000..f95d7288 Binary files /dev/null and b/src/assets/images/races/elf.jpg differ diff --git a/src/assets/images/races/faun.jpg b/src/assets/images/races/faun.jpg new file mode 100644 index 00000000..68b53ea3 Binary files /dev/null and b/src/assets/images/races/faun.jpg differ diff --git a/src/assets/images/races/gnome.jpg b/src/assets/images/races/gnome.jpg new file mode 100644 index 00000000..ba8ee6a1 Binary files /dev/null and b/src/assets/images/races/gnome.jpg differ diff --git a/src/assets/images/races/half-elf.jpg b/src/assets/images/races/half-elf.jpg new file mode 100644 index 00000000..36980494 Binary files /dev/null and b/src/assets/images/races/half-elf.jpg differ diff --git a/src/assets/images/races/half-ogre.jpg b/src/assets/images/races/half-ogre.jpg new file mode 100644 index 00000000..69fd7ce1 Binary files /dev/null and b/src/assets/images/races/half-ogre.jpg differ diff --git a/src/assets/images/races/half-orc.jpg b/src/assets/images/races/half-orc.jpg new file mode 100644 index 00000000..4053638d Binary files /dev/null and b/src/assets/images/races/half-orc.jpg differ diff --git a/src/assets/images/races/halfling.jpg b/src/assets/images/races/halfling.jpg new file mode 100644 index 00000000..8ad2330f Binary files /dev/null and b/src/assets/images/races/halfling.jpg differ diff --git a/src/assets/images/races/human.jpg b/src/assets/images/races/human.jpg new file mode 100644 index 00000000..b48fba22 Binary files /dev/null and b/src/assets/images/races/human.jpg differ diff --git a/src/assets/images/races/phaerim.jpg b/src/assets/images/races/phaerim.jpg new file mode 100644 index 00000000..f09871b8 Binary files /dev/null and b/src/assets/images/races/phaerim.jpg differ diff --git a/src/assets/images/spells/alarm.jpg b/src/assets/images/spells/alarm.jpg new file mode 100644 index 00000000..ef4d4814 Binary files /dev/null and b/src/assets/images/spells/alarm.jpg differ diff --git a/src/assets/images/spells/audible-glamer.jpg b/src/assets/images/spells/audible-glamer.jpg new file mode 100644 index 00000000..a992204c Binary files /dev/null and b/src/assets/images/spells/audible-glamer.jpg differ diff --git a/src/assets/images/spells/break-restrictions.jpg b/src/assets/images/spells/break-restrictions.jpg new file mode 100644 index 00000000..575ce8ff Binary files /dev/null and b/src/assets/images/spells/break-restrictions.jpg differ diff --git a/src/assets/images/spells/call-poltergeist.jpg b/src/assets/images/spells/call-poltergeist.jpg new file mode 100644 index 00000000..6bc366fa Binary files /dev/null and b/src/assets/images/spells/call-poltergeist.jpg differ diff --git a/src/assets/images/spells/change-self.jpg b/src/assets/images/spells/change-self.jpg new file mode 100644 index 00000000..0ebf46a6 Binary files /dev/null and b/src/assets/images/spells/change-self.jpg differ diff --git a/src/assets/images/spells/charm-person.jpg b/src/assets/images/spells/charm-person.jpg new file mode 100644 index 00000000..e6144e4b Binary files /dev/null and b/src/assets/images/spells/charm-person.jpg differ diff --git a/src/assets/images/spells/chill.jpg b/src/assets/images/spells/chill.jpg new file mode 100644 index 00000000..5f667762 Binary files /dev/null and b/src/assets/images/spells/chill.jpg differ diff --git a/src/assets/images/spells/color-spray.jpg b/src/assets/images/spells/color-spray.jpg new file mode 100644 index 00000000..2923940a Binary files /dev/null and b/src/assets/images/spells/color-spray.jpg differ diff --git a/src/assets/images/spells/corpse-servant.jpg b/src/assets/images/spells/corpse-servant.jpg new file mode 100644 index 00000000..74417b3a Binary files /dev/null and b/src/assets/images/spells/corpse-servant.jpg differ diff --git a/src/assets/images/spells/dancing-lights.jpg b/src/assets/images/spells/dancing-lights.jpg new file mode 100644 index 00000000..27aa9dff Binary files /dev/null and b/src/assets/images/spells/dancing-lights.jpg differ diff --git a/src/assets/images/spells/decay-flesh.jpg b/src/assets/images/spells/decay-flesh.jpg new file mode 100644 index 00000000..51444894 Binary files /dev/null and b/src/assets/images/spells/decay-flesh.jpg differ diff --git a/src/assets/images/spells/detect-illusion.jpg b/src/assets/images/spells/detect-illusion.jpg new file mode 100644 index 00000000..6b14baec Binary files /dev/null and b/src/assets/images/spells/detect-illusion.jpg differ diff --git a/src/assets/images/spells/detect-invisible.jpg b/src/assets/images/spells/detect-invisible.jpg new file mode 100644 index 00000000..bcbe146b Binary files /dev/null and b/src/assets/images/spells/detect-invisible.jpg differ diff --git a/src/assets/images/spells/detect-magic.jpg b/src/assets/images/spells/detect-magic.jpg new file mode 100644 index 00000000..732292f1 Binary files /dev/null and b/src/assets/images/spells/detect-magic.jpg differ diff --git a/src/assets/images/spells/enhance-armor.jpg b/src/assets/images/spells/enhance-armor.jpg new file mode 100644 index 00000000..4a07f3ba Binary files /dev/null and b/src/assets/images/spells/enhance-armor.jpg differ diff --git a/src/assets/images/spells/enhance-weapon.jpg b/src/assets/images/spells/enhance-weapon.jpg new file mode 100644 index 00000000..239a9428 Binary files /dev/null and b/src/assets/images/spells/enhance-weapon.jpg differ diff --git a/src/assets/images/spells/floating-disc.jpg b/src/assets/images/spells/floating-disc.jpg new file mode 100644 index 00000000..61582d9c Binary files /dev/null and b/src/assets/images/spells/floating-disc.jpg differ diff --git a/src/assets/images/spells/gaze-reflection.jpg b/src/assets/images/spells/gaze-reflection.jpg new file mode 100644 index 00000000..47e2a8be Binary files /dev/null and b/src/assets/images/spells/gaze-reflection.jpg differ diff --git a/src/assets/images/spells/hold-portal.jpg b/src/assets/images/spells/hold-portal.jpg new file mode 100644 index 00000000..c80d7d20 Binary files /dev/null and b/src/assets/images/spells/hold-portal.jpg differ diff --git a/src/assets/images/spells/light.jpg b/src/assets/images/spells/light.jpg new file mode 100644 index 00000000..da5759a9 Binary files /dev/null and b/src/assets/images/spells/light.jpg differ diff --git a/src/assets/images/spells/locate-corpse.jpg b/src/assets/images/spells/locate-corpse.jpg new file mode 100644 index 00000000..390d81b2 Binary files /dev/null and b/src/assets/images/spells/locate-corpse.jpg differ diff --git a/src/assets/images/spells/magic-missile.jpg b/src/assets/images/spells/magic-missile.jpg new file mode 100644 index 00000000..ddb36773 Binary files /dev/null and b/src/assets/images/spells/magic-missile.jpg differ diff --git a/src/assets/images/spells/magic-mouth.jpg b/src/assets/images/spells/magic-mouth.jpg new file mode 100644 index 00000000..af2fc46e Binary files /dev/null and b/src/assets/images/spells/magic-mouth.jpg differ diff --git a/src/assets/images/spells/mirror-image.jpg b/src/assets/images/spells/mirror-image.jpg new file mode 100644 index 00000000..de8a13bc Binary files /dev/null and b/src/assets/images/spells/mirror-image.jpg differ diff --git a/src/assets/images/spells/phantasmal-image.jpg b/src/assets/images/spells/phantasmal-image.jpg new file mode 100644 index 00000000..d0de6947 Binary files /dev/null and b/src/assets/images/spells/phantasmal-image.jpg differ diff --git a/src/assets/images/spells/protection-from-evil.jpg b/src/assets/images/spells/protection-from-evil.jpg new file mode 100644 index 00000000..f17e1bc1 Binary files /dev/null and b/src/assets/images/spells/protection-from-evil.jpg differ diff --git a/src/assets/images/spells/protection-from-undead.jpg b/src/assets/images/spells/protection-from-undead.jpg new file mode 100644 index 00000000..2ff90f69 Binary files /dev/null and b/src/assets/images/spells/protection-from-undead.jpg differ diff --git a/src/assets/images/spells/read-languages.jpg b/src/assets/images/spells/read-languages.jpg new file mode 100644 index 00000000..3e639a61 Binary files /dev/null and b/src/assets/images/spells/read-languages.jpg differ diff --git a/src/assets/images/spells/remove-fear.jpg b/src/assets/images/spells/remove-fear.jpg new file mode 100644 index 00000000..4490b56e Binary files /dev/null and b/src/assets/images/spells/remove-fear.jpg differ diff --git a/src/assets/images/spells/repair.jpg b/src/assets/images/spells/repair.jpg new file mode 100644 index 00000000..8134e417 Binary files /dev/null and b/src/assets/images/spells/repair.jpg differ diff --git a/src/assets/images/spells/resist-cold.jpg b/src/assets/images/spells/resist-cold.jpg new file mode 100644 index 00000000..47f65adb Binary files /dev/null and b/src/assets/images/spells/resist-cold.jpg differ diff --git a/src/assets/images/spells/shield.jpg b/src/assets/images/spells/shield.jpg new file mode 100644 index 00000000..88f8d455 Binary files /dev/null and b/src/assets/images/spells/shield.jpg differ diff --git a/src/assets/images/spells/sleep.jpg b/src/assets/images/spells/sleep.jpg new file mode 100644 index 00000000..c950f873 Binary files /dev/null and b/src/assets/images/spells/sleep.jpg differ diff --git a/src/assets/images/spells/stench.jpg b/src/assets/images/spells/stench.jpg new file mode 100644 index 00000000..cda9ede6 Binary files /dev/null and b/src/assets/images/spells/stench.jpg differ diff --git a/src/assets/images/spells/ventriloquism.jpg b/src/assets/images/spells/ventriloquism.jpg new file mode 100644 index 00000000..b577b950 Binary files /dev/null and b/src/assets/images/spells/ventriloquism.jpg differ diff --git a/src/components/AttackModal/AmmoSelect/AmmoSelect.tsx b/src/components/AttackModal/AmmoSelect/AmmoSelect.tsx deleted file mode 100755 index 5f85a786..00000000 --- a/src/components/AttackModal/AmmoSelect/AmmoSelect.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Select } from "antd"; -import { EquipmentItem } from "../../../data/definitions"; - -type AmmoSelectProps = { - ammo: string[] | undefined; - equipment: EquipmentItem[]; - setAmmo: (ammo: EquipmentItem) => void; -}; - -export default function AmmoSelect({ - ammo, - equipment, - setAmmo, -}: AmmoSelectProps) { - const options = - ammo && - ammo - .map((ammoItem) => { - const item = equipment.find((item) => item.name === ammoItem); - return item ? { value: ammoItem, label: `${ammoItem}` } : null; - }) - .filter( - (option): option is { value: string; label: string } => option !== null - ); - - const handleAmmoChange = (value: string) => { - const selectedAmmoItem = equipment.find((item) => item.name === value); - if (selectedAmmoItem) { - setAmmo(selectedAmmoItem); - } - }; - - return ( -
- - - - ); -} diff --git a/src/components/CharacterCreator/CharacterClass/CustomClassStartingSpells/CustomClassStartingSpells.tsx b/src/components/CharacterCreator/CharacterClass/CustomClassStartingSpells/CustomClassStartingSpells.tsx deleted file mode 100755 index 43f3eb7c..00000000 --- a/src/components/CharacterCreator/CharacterClass/CustomClassStartingSpells/CustomClassStartingSpells.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Checkbox, Typography } from "antd"; -import { CheckboxChangeEvent } from "antd/es/checkbox"; -import spellsData from "../../../../data/spells.json"; -import { CharacterDataStatePair, Spell } from "../../../../data/definitions"; - -interface CustomClassStartingSpellsProps extends CharacterDataStatePair {} - -export default function CustomClassStartingSpells({ - characterData, - setCharacterData, -}: CustomClassStartingSpellsProps) { - const handleCheckboxChange = (e: CheckboxChangeEvent, spell: Spell) => { - if (e.target.checked) { - setCharacterData({ - ...characterData, - spells: [...characterData.spells, spell], - }); - } else { - setCharacterData({ - ...characterData, - spells: characterData.spells.filter( - (prevSpell: Spell) => prevSpell.name !== spell.name - ), - }); - } - }; - - return ( - <> - Choose your starting spells -
- {spellsData.map((spell) => ( - handleCheckboxChange(e, spell)} - checked={characterData.spells.some( - (prevSpell: Spell) => prevSpell.name === spell.name - )} - className="text-shipGray" - > - {spell.name} - - ))} -
- - ); -} diff --git a/src/components/CharacterCreator/CharacterClass/SpellDescriptionModal/SpellDescriptionModal.tsx b/src/components/CharacterCreator/CharacterClass/SpellDescriptionModal/SpellDescriptionModal.tsx deleted file mode 100755 index e376b77c..00000000 --- a/src/components/CharacterCreator/CharacterClass/SpellDescriptionModal/SpellDescriptionModal.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Modal } from "antd"; -import CloseIcon from "../../../CloseIcon/CloseIcon"; - -type SpellDescriptionModalProps = { - title: string; - isModalOpen: boolean; - setIsModalOpen: (isModalOpen: boolean) => void; - description: string; -}; - -export default function SpellDescriptionModal({ - title, - isModalOpen, - setIsModalOpen, - description, -}: SpellDescriptionModalProps) { - const handleCancel = () => { - setIsModalOpen(false); - }; - - return ( - } - > -
- - ); -} diff --git a/src/components/CharacterCreator/CharacterClass/StartingSpells/StartingSpells.tsx b/src/components/CharacterCreator/CharacterClass/StartingSpells/StartingSpells.tsx deleted file mode 100755 index ab600c6f..00000000 --- a/src/components/CharacterCreator/CharacterClass/StartingSpells/StartingSpells.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { Button, Radio, RadioChangeEvent, Typography } from "antd"; -import { InfoCircleOutlined } from "@ant-design/icons"; -import { marked } from "marked"; -import spellsJson from "../../../../data/spells.json"; -import { classes } from "../../../../data/classes"; -import { - CharacterData, - ClassNames, - SetCharacterData, - Spell, -} from "../../../../data/definitions"; - -type StartingSpellsProps = { - characterData: CharacterData; - setCharacterData: SetCharacterData; - selectedSpell: Spell | null; - setSelectedSpell: (spell: Spell | null) => void; - setModalName: (modalName: string) => void; - setModalDescription: (modalDescription: string) => void; - setIsModalOpen: (isModalOpen: boolean) => void; -}; - -const readMagicSpell = spellsJson.filter( - (spell) => spell.name === "Read Magic" -); - -const getClassLevelOneSpells = (characterClassArray: string[]) => { - if (!characterClassArray) return []; - return characterClassArray.flatMap((className) => { - if (classes[className as ClassNames]?.spellBudget?.[0][0]) { - return spellsJson - .filter( - (spell) => - spell.level![ - className.toLowerCase() as keyof typeof spell.level - ] === 1 && spell.name !== "Read Magic" - ) - .sort((a, b) => a.name.localeCompare(b.name)); - } - return null; - }); -}; - -export default function StartingSpells({ - characterData, - setCharacterData, - selectedSpell, - setSelectedSpell, - setModalName, - setModalDescription, - setIsModalOpen, -}: StartingSpellsProps) { - const onSpellRadioChange = (e: RadioChangeEvent) => { - const foundSpell = spellsJson.find( - (spell) => spell.name === e.target.value - ); - if (foundSpell) { - setSelectedSpell(foundSpell); - setCharacterData({ - ...characterData, - spells: [...readMagicSpell, foundSpell], - }); - } - }; - - const showModal = (name: string, text: string) => { - setModalName(name); - setModalDescription(text); - setIsModalOpen(true); - }; - - return ( -
- - Choose your starting spell - - - {characterData.class.join(" ")}s begin with{" "} - Read Magic and can choose a second spell to start. - - - {getClassLevelOneSpells(characterData.class)?.map((spell) => { - return ( - spell && ( - - {spell.name} -
- ); -} diff --git a/src/components/CharacterCreator/CharacterHitPoints/CharacterHitPoints.tsx b/src/components/CharacterCreator/CharacterHitPoints/CharacterHitPoints.tsx deleted file mode 100755 index 5c2e5ee3..00000000 --- a/src/components/CharacterCreator/CharacterHitPoints/CharacterHitPoints.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { useEffect, useState } from "react"; -import { Divider } from "antd"; -import HomebrewWarning from "../../HomebrewWarning/HomebrewWarning"; -import { getClassType } from "../../../support/helpers"; -import { classes } from "../../../data/classes"; -import { races } from "../../../data/races"; -import HitPointsRoller from "./HitPointsRoller/HitPointsRoller"; -import CustomHitPointsPicker from "./CustomHitPointsPicker/CustomHitPointsPicker"; -import { - CharacterDataStatePair, - ClassNames, - RaceNames, -} from "../../../data/definitions"; -import { DiceTypes } from "../../../data/definitions"; - -interface CharacterHitPointsProps extends CharacterDataStatePair {} - -export default function CharacterHitPoints({ - characterData, - setCharacterData, -}: CharacterHitPointsProps) { - const [customHitDice, setCustomHitDice] = useState(""); - - useEffect(() => { - let dice = DiceTypes.D6; - - if (getClassType(characterData.class) === "custom") { - } - if (getClassType(characterData.class) === "combination") { - // Initialize a variable to hold the largest die - let largestDie = "d4"; - - // Iterate through each class and find the largest die - characterData.class.forEach((part) => { - const classDie = classes[part as ClassNames].hitDice; - if (classDie.split("d")[1] > largestDie.split("d")[1]) { - largestDie = classDie; - } - }); - - // Check for raceData.maximumHitDice and compare with the largest die - const raceData = races[characterData.race as RaceNames]; - if ( - raceData && - raceData.maximumHitDice !== undefined && - largestDie.split("d")[1] > raceData.maximumHitDice.split("d")[1] - ) { - dice = raceData.maximumHitDice; - } else { - dice = largestDie as DiceTypes; - } - } - if (getClassType(characterData.class) === "standard") { - const classDie = classes[characterData.class[0] as ClassNames].hitDice; - const raceData = races[characterData.race as RaceNames]; - if ( - raceData && - raceData.maximumHitDice !== undefined && - classDie.split("d")[1] > raceData.maximumHitDice.split("d")[1] - ) { - dice = raceData.maximumHitDice; - } else { - dice = classDie as DiceTypes; - } - } - // If Race increments OR decrements Class's Hit Die - if ( - races[characterData.race as RaceNames]?.incrementHitDie || - races[characterData.race as RaceNames]?.decrementHitDie - ) { - let index = Object.values(DiceTypes).indexOf(dice); - - // Increment the index, but make sure it doesn't exceed the bounds of the enum - races[characterData.race as RaceNames]?.incrementHitDie - ? (index = Math.min(index + 1, Object.values(DiceTypes).length - 1)) - : (index = Math.min(index - 1, Object.values(DiceTypes).length - 1)); - - // Assign the new dice value from the `DiceTypes` enum - dice = Object.values(DiceTypes)[index] as DiceTypes; - } - - setCharacterData({ - ...characterData, - hp: { - ...characterData.hp, - dice, - }, - }); - }, []); - - return ( - <> - {!characterData.class.some((part) => - Object.values(ClassNames).includes(part as ClassNames) - ) && ( - <> - - - - - )} - - - ); -} diff --git a/src/components/CharacterCreator/CharacterHitPoints/CustomHitPointsPicker/CustomHitPointsPicker.tsx b/src/components/CharacterCreator/CharacterHitPoints/CustomHitPointsPicker/CustomHitPointsPicker.tsx deleted file mode 100755 index 6fc831b1..00000000 --- a/src/components/CharacterCreator/CharacterHitPoints/CustomHitPointsPicker/CustomHitPointsPicker.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Radio, RadioChangeEvent } from "antd"; -import { - CharacterData, - DiceTypes, - SetCharacterData, -} from "../../../../data/definitions"; - -type CustomHitPointsPickerProps = { - characterData: CharacterData; - setCharacterData: SetCharacterData; - customHitDice: string; - setCustomHitDice: (customHitDice: string) => void; -}; - -export default function CustomHitPointsPicker({ - characterData, - setCharacterData, - customHitDice, - setCustomHitDice, -}: CustomHitPointsPickerProps) { - const handleChangeCustomHitDice = (event: RadioChangeEvent) => { - setCustomHitDice(event.target.value); - setCharacterData({ - ...characterData, - hp: { - ...characterData.hp, - dice: event.target.value, - }, - }); - }; - - return ( - - {Object.values(DiceTypes).map((die) => ( - - {die} - - ))} - - ); -} diff --git a/src/components/CharacterCreator/CharacterHitPoints/HitPointsRoller/HitPointsRoller.tsx b/src/components/CharacterCreator/CharacterHitPoints/HitPointsRoller/HitPointsRoller.tsx deleted file mode 100755 index b1823c8f..00000000 --- a/src/components/CharacterCreator/CharacterHitPoints/HitPointsRoller/HitPointsRoller.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useState, useEffect } from "react"; -import { Button, InputNumber, Space } from "antd"; -import { DiceRoller } from "@dice-roller/rpg-dice-roller"; -import { - CharacterData, - ClassNames, - SetCharacterData, -} from "../../../../data/definitions"; - -type HitPointsRollerProps = { - characterData: CharacterData; - setCharacterData: SetCharacterData; - customHitDice: string; -}; - -const roller = new DiceRoller(); - -export default function HitPointsRoller({ - characterData, - setCharacterData, - customHitDice, -}: HitPointsRollerProps) { - const [hitPoints, setHitPoints] = useState(characterData.hp.points); - - const handleFocus = (event: React.FocusEvent) => { - event.target.select(); - }; - - const rollHitPoints = (score?: number) => { - let result = - score || - roller.roll(characterData.hp.dice).total + - +characterData.abilities.modifiers.constitution; - result = result > 0 ? result : 1; - setHitPoints(result); - }; - - useEffect(() => { - setCharacterData({ - ...characterData, - hp: { ...characterData.hp, points: hitPoints, max: hitPoints }, - }); - }, [hitPoints]); - - return ( - - rollHitPoints(value || 1)} - type="number" - value={hitPoints} - /> - - - ); -} diff --git a/src/components/CharacterCreator/CharacterName/AvatarPicker/AvatarPicker.tsx b/src/components/CharacterCreator/CharacterName/AvatarPicker/AvatarPicker.tsx deleted file mode 100755 index d7ffa908..00000000 --- a/src/components/CharacterCreator/CharacterName/AvatarPicker/AvatarPicker.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { CharacterData, SetCharacterData } from "../../../../data/definitions"; -import { Radio, Typography, RadioChangeEvent } from "antd"; - -type AvatarPickerProps = { - characterData: CharacterData; - setCharacterData: SetCharacterData; - imageSource: number; - setImageSource: (imageSource: number) => void; -}; - -export default function AvatarPicker({ - characterData, - setCharacterData, - imageSource, - setImageSource, -}: AvatarPickerProps) { - const handleChangeImageSource = (e: RadioChangeEvent) => { - setCharacterData({ ...characterData, avatar: "" }); - setImageSource(e.target.value); - }; - - return ( - <> - - Avatar - - - None - Stock - Upload - - - ); -} diff --git a/src/components/CharacterCreator/CharacterName/CharacterName.tsx b/src/components/CharacterCreator/CharacterName/CharacterName.tsx deleted file mode 100755 index 6910c391..00000000 --- a/src/components/CharacterCreator/CharacterName/CharacterName.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { ChangeEvent, useState } from "react"; -import { PlusOutlined } from "@ant-design/icons"; -import { Button, Input, Modal, Upload } from "antd"; -import type { RcFile, UploadProps } from "antd/es/upload"; -import type { UploadFile } from "antd/es/upload/interface"; -import { storage, ref, uploadBytes } from "../../../firebase"; -import { getDownloadURL } from "firebase/storage"; -import { images } from "../../../assets/images/faces/imageAssets"; -import DOMPurify from "dompurify"; -import AvatarPicker from "./AvatarPicker/AvatarPicker"; -import { CharacterDataStatePair } from "../../../data/definitions"; - -interface CharacterNameProps extends CharacterDataStatePair {} - -const StockAvatars = ({ - setCharacterData, - characterData, -}: CharacterDataStatePair) => { - const [selectedAvatar, setSelectedAvatar] = useState(""); - - return ( -
- {images.map((image) => ( - - ))} -
- ); -}; - -const getBase64 = (file: RcFile): Promise => - new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => resolve(reader.result as string); - reader.onerror = (error) => reject(error); - }); - -export default function CharacterName({ - characterData, - setCharacterData, -}: CharacterNameProps) { - const [previewOpen, setPreviewOpen] = useState(false); - const [previewImage, setPreviewImage] = useState(""); - const [previewTitle, setPreviewTitle] = useState(""); - const [fileList, setFileList] = useState([]); - const [imageSource, setImageSource] = useState(0); - - const handleNameChange = (event: ChangeEvent) => { - const cleanInput = DOMPurify.sanitize(event.target.value); - setCharacterData({ ...characterData, name: cleanInput }); - }; - - const handleCancel = () => setPreviewOpen(false); - - const handlePreview = async (file: UploadFile) => { - if (!file.url && !file.preview) { - file.preview = await getBase64(file.originFileObj as RcFile); - } - - setPreviewImage(file.url || (file.preview as string)); - setPreviewOpen(true); - setPreviewTitle( - file.name || file.url!.substring(file.url!.lastIndexOf("/") + 1) - ); - }; - - const handleChange: UploadProps["onChange"] = async ({ - fileList: newFileList, - }) => { - // Validate file type, size, or dimensions here - const allowedTypes = ["image/jpeg", "image/png"]; // Allowed file types - const maxSize = 1 * 1024 * 1024; // Maximum file size in bytes (1MB) - - const filteredList = newFileList.filter((file) => { - const isAllowedType = file.type - ? allowedTypes.includes(file.type) - : false; - const isBelowMaxSize = file.size ? file.size <= maxSize : false; - // You can also add validation for dimensions here if needed - - if (!isAllowedType) { - console.error(`${file.name} is not an allowed file type.`); - } - if (!isBelowMaxSize) { - console.error(`${file.name} exceeds the maximum file size.`); - } - - return isAllowedType && isBelowMaxSize; - }); - - setFileList(filteredList); - - // Upload the file to Firebase Storage - for (const file of filteredList) { - if (!file.url && !file.preview) { - file.preview = await getBase64(file.originFileObj as RcFile); - } - - const directory = "avatars"; - const fileName = `${directory}/${file.name}`; - const storageRef = ref(storage, fileName); - const uploadTask = uploadBytes(storageRef, file.originFileObj as Blob); - - uploadTask - .then(() => { - // Upload completed successfully - getDownloadURL(storageRef).then((downloadURL) => { - setCharacterData({ ...characterData, avatar: downloadURL }); - }); - }) - .catch((error: Error) => { - console.error("Upload error:", error); - }); - } - }; - - const uploadButton = ( -
- -
Upload
-
- ); - - return ( - <> - - - {imageSource === 2 && ( - <> - - {fileList.length >= 1 ? null : uploadButton} - - - example - - - )} - {imageSource === 1 && ( - - )} - - ); -} diff --git a/src/components/CharacterCreator/CharacterRace/CharacterRace.tsx b/src/components/CharacterCreator/CharacterRace/CharacterRace.tsx deleted file mode 100755 index f404f8ba..00000000 --- a/src/components/CharacterCreator/CharacterRace/CharacterRace.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Input } from "antd"; -import { ChangeEvent, useState, useEffect, MouseEvent } from "react"; -import HomebrewWarning from "../../HomebrewWarning/HomebrewWarning"; -import DOMPurify from "dompurify"; -import DescriptionBubble from "../DescriptionBubble/DescriptionBubble"; -import { races } from "../../../data/races"; -import RaceOptions from "./RaceOptions/RaceOptions"; -import { CharacterDataStatePair, RaceNames } from "../../../data/definitions"; - -interface CharacterRaceProps extends CharacterDataStatePair { - setComboClass: (comboxClass: boolean) => void; - setCheckedClasses: (checkedClasses: string[]) => void; -} - -export default function CharacterRace({ - characterData, - setCharacterData, - setComboClass, - setCheckedClasses, -}: CharacterRaceProps) { - const [customRaceInput, setCustomRaceInput] = useState( - characterData.race || "" - ); - const [showCustomRaceInput, setShowCustomRaceInput] = useState(false); - - useEffect(() => { - // If the current race is not in the RaceNames enum and it's not an empty string, it's a custom race. - if ( - !Object.values(RaceNames).includes(characterData.race as RaceNames) && - characterData.race !== "" - ) { - setShowCustomRaceInput(true); - setCustomRaceInput(characterData.race); - } - }, []); - - const handleChangeCustomRaceInput = ( - event: ChangeEvent - ) => { - const cleanInput = DOMPurify.sanitize(event.target.value); - setCustomRaceInput(cleanInput); - setCharacterData({ ...characterData, race: cleanInput }); - }; - - const handleClickCustomRaceInput = (event: MouseEvent) => { - event.currentTarget.select(); - }; - - const raceDescription = - races[characterData.race as keyof typeof races]?.details?.description || ""; - - return ( - <> -
- - {characterData.race && - Object.values(RaceNames).includes(characterData.race as RaceNames) && - characterData.race !== RaceNames.CUSTOM && ( - - )} -
- {showCustomRaceInput && ( - <> - - - - )} - - ); -} diff --git a/src/components/CharacterCreator/CharacterRace/RaceOptions/RaceOptions.tsx b/src/components/CharacterCreator/CharacterRace/RaceOptions/RaceOptions.tsx deleted file mode 100755 index dd1cf14f..00000000 --- a/src/components/CharacterCreator/CharacterRace/RaceOptions/RaceOptions.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { isStandardRace } from "../../../../support/helpers"; -import type { RadioChangeEvent } from "antd"; -import { Radio } from "antd"; -import { races } from "../../../../data/races"; -import { - CharacterData, - RaceNames, - SetCharacterData, -} from "../../../../data/definitions"; -import classNames from "classnames"; - -type RaceOptionsProps = { - characterData: CharacterData; - setCharacterData: SetCharacterData; - setComboClass: (comboClass: boolean) => void; - setShowCustomRaceInput: (showCustomRaceInput: boolean) => void; - setCheckedClasses: (checkedClasses: string[]) => void; - customRaceInput: string; -}; - -export default function RaceOptions({ - characterData, - setCharacterData, - setComboClass, - setShowCustomRaceInput, - setCheckedClasses, - customRaceInput, -}: RaceOptionsProps) { - const onChange = (e: RadioChangeEvent) => { - if (e.target.value === "Custom") setShowCustomRaceInput(true); - else setShowCustomRaceInput(false); - const selectedRace = e.target.value.toString() as keyof typeof RaceNames; - setComboClass(false); - setCheckedClasses([]); - setCharacterData({ - ...characterData, - race: e.target.value !== "Custom" ? selectedRace : customRaceInput, - class: [], - hp: { dice: "", points: 0, max: 0, desc: "" }, - equipment: [], - }); - }; - const baseRaces = [ - RaceNames.DWARF, - RaceNames.ELF, - RaceNames.HALFLING, - RaceNames.HUMAN, - ]; - - return ( - - {Object.keys(races) - .sort((a, b) => - races[a as keyof typeof races].name > - races[b as keyof typeof races].name - ? 1 - : -1 - ) - .map((raceKey) => { - const choice = races[raceKey as keyof typeof races]; - if (!choice) return null; // Skip rendering if race is undefined - const radioClassNames = classNames( - "ps-2", - "pe-2", - "md:ps-4", - "md:pe-4", - "text-shipGray", - { "font-bold": baseRaces.includes(choice.name as RaceNames) } - ); - - const isDisabled = - (choice.minimumAbilityRequirements && - Object.entries(choice.minimumAbilityRequirements).some( - ([ability, requirement]) => - +characterData.abilities.scores[ - ability as keyof typeof characterData.abilities.scores - ] < (requirement as number) // Cast requirement to number - )) || - (choice.maximumAbilityRequirements && - Object.entries(choice.maximumAbilityRequirements).some( - ([ability, requirement]) => - +characterData.abilities.scores[ - ability as keyof typeof characterData.abilities.scores - ] > (requirement as number) // Cast requirement to number - )); - - return ( - - {choice.name} - - ); - })} - - ); -} diff --git a/src/components/CharacterCreator/DescriptionBubble/DescriptionBubble.tsx b/src/components/CharacterCreator/DescriptionBubble/DescriptionBubble.tsx deleted file mode 100755 index 463a9563..00000000 --- a/src/components/CharacterCreator/DescriptionBubble/DescriptionBubble.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Card, Switch, Typography } from "antd"; -import classNames from "classnames"; -import { marked } from "marked"; -import { classes } from "../../../data/classes"; - -type DescriptionBubbleProps = { - description: string; - title?: string; -}; - -export default function DescriptionBubble({ - description, - className, - title, -}: DescriptionBubbleProps & React.ComponentPropsWithRef<"div">) { - const descriptionBubbleClassNames = classNames( - className, - "bg-shipGray", - "text-springWood", - "rounded", - "shadow-md", - "[&>div:first-child]:text-springWood", - "[&>div:first-child]:font-enchant", - "[&>div:first-child]:text-4xl/[5rem]", - "[&>div:first-child]:tracking-wider" - ); - - const customRuleToggleClassNames = classNames( - "bg-springWood", - "mb-3.5", - "p-4", - "rounded", - "text-shipGray", - "inline-block" - ); - - const showCustomRuleToggle = Boolean( - classes[title as keyof typeof classes]?.customRules - ); - const customRules = classes[title as keyof typeof classes]?.customRules || []; - return ( - - {/* TEMPLATE FOR FIGHTER CUSTOM RULE */} - {/* {showCustomRuleToggle && - customRules.map((rule) => ( -
- - {rule.title} - - } - unCheckedChildren={} - /> - - Consult your GM before enabling this option. - -
- ))} */} -
- - ); -} diff --git a/src/components/CharacterSheet/Abilities/Abilities.tsx b/src/components/CharacterSheet/Abilities/Abilities.tsx deleted file mode 100755 index 10c849f3..00000000 --- a/src/components/CharacterSheet/Abilities/Abilities.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { toTitleCase } from "../../../support/stringSupport"; -import { Table, Typography } from "antd"; -import { CharacterData } from "../../../data/definitions"; - -type AbilitiesProps = { - characterData: CharacterData; -}; - -export default function Abilities({ characterData }: AbilitiesProps) { - const columns = [ - { title: "Ability", dataIndex: "ability", key: "ability" }, - { title: "Score", dataIndex: "score", key: "score" }, - { title: "Modifier", dataIndex: "modifier", key: "modifier" }, - ]; - - const abilityOrder = [ - "strength", - "intelligence", - "wisdom", - "dexterity", - "constitution", - "charisma", - ]; - - const dataSource = Object.entries(characterData.abilities.scores) - .sort(([a], [b]) => abilityOrder.indexOf(a) - abilityOrder.indexOf(b)) - .map(([key, value], index) => { - return { - key: index + 1, - ability: toTitleCase(key), - score: value, - modifier: - characterData.abilities.modifiers[ - key as keyof typeof characterData.abilities.modifiers - ], - }; - }); - - return ( -
- - Abilities - - - - ); -} diff --git a/src/components/CharacterSheet/AttackBonus/AttackBonus.tsx b/src/components/CharacterSheet/AttackBonus/AttackBonus.tsx deleted file mode 100755 index 5eafb553..00000000 --- a/src/components/CharacterSheet/AttackBonus/AttackBonus.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Table, Typography } from "antd"; -import HelpTooltip from "../../HelpTooltip/HelpTooltip"; -import { getClassType } from "../../../support/helpers"; -import { CharacterData, RaceNames } from "../../../data/definitions"; - -type AttackBonusProps = { - attackBonus: number; - characterData: CharacterData; -}; - -export default function AttackBonus({ - characterData, - attackBonus, -}: AttackBonusProps) { - // TODO: This is a temporary fix for the ranged attack bonus for halflings. It should be housed in the data file. - const rangedRaceBonus = characterData.race === RaceNames.HALFLING ? 1 : 0; - - const dataSource = [ - { key: 1, label: "Attack Bonus", bonus: attackBonus }, - { - key: 2, - label: "Melee Attack Bonus", - bonus: attackBonus + +characterData.abilities.modifiers.strength, - }, - { - key: 3, - label: "Ranged Attack Bonus", - bonus: - attackBonus + - +characterData.abilities.modifiers.dexterity + - rangedRaceBonus, - }, - ]; - - const columns = [ - { title: "Bonus", dataIndex: "label", key: "label" }, - { title: "Value", dataIndex: "bonus", key: "bonus" }, - ]; - - return ( -
-
- - Attack Bonuses - - -
-
- {getClassType(characterData.class) === "custom" && ( - - * Add your custom class's Attack Bonus to these numbers. - - )} - - ); -} diff --git a/src/components/CharacterSheet/BaseStats/BaseStats.tsx b/src/components/CharacterSheet/BaseStats/BaseStats.tsx deleted file mode 100755 index fe57dfb3..00000000 --- a/src/components/CharacterSheet/BaseStats/BaseStats.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { Avatar, Descriptions, Divider, Modal, Typography } from "antd"; -import { UserOutlined } from "@ant-design/icons"; -import ExperiencePoints from "./ExperiencePoints/ExperiencePoints"; -import { extractImageName } from "../../../support/stringSupport"; -import { images } from "../../../assets/images/faces/imageAssets"; -import classNames from "classnames"; -import { ReactNode, useState } from "react"; -import { marked } from "marked"; -import { isStandardClass, isStandardRace } from "../../../support/helpers"; -import { classes } from "../../../data/classes"; -import { - CharacterData, - ClassNames, - RaceNames, - SetCharacterData, -} from "../../../data/definitions"; -import CloseIcon from "../../CloseIcon/CloseIcon"; -import { races } from "../../../data/races"; - -type BaseStatsProps = { - characterData: CharacterData; - setCharacterData: SetCharacterData; - userIsOwner?: boolean; - showLevelUpModal?: () => void; -}; - -export default function BaseStats({ - characterData, - setCharacterData, - userIsOwner, - showLevelUpModal, -}: BaseStatsProps) { - const [isModalOpen, setIsModalOpen] = useState(false); - const [modalTitle, setModalTitle] = useState(""); - const [modalContent, setModalContent] = useState( - undefined - ); - - const showModal = () => { - setIsModalOpen(true); - }; - - const handleCancel = () => { - setIsModalOpen(false); - }; - - const getRaceModalContent = (raceName: RaceNames) => - setModalContent( -
- - {raceName} - - {isStandardRace(raceName) ? ( -
- ) : ( -
"{raceName}" is a custom race.
- )} -
- ); - - const getClassModalContent = (classNames: ClassNames[]) => - setModalContent( -
- {classNames.map((className: ClassNames) => { - return ( -
- - {className} - - {isStandardClass(className) ? ( -
- ) : ( -
- "{className}" is a custom class. -
- )} -
- ); - })} -
- ); - - // Legacy characters created while the site was using Create React App will have broken image links that start with "/static/media/" - // This code checks for that and replaces the broken link with the correct one - let image = ""; - if (characterData.avatar.startsWith("/static/media/")) { - const legacyImage = extractImageName(characterData.avatar); - if (legacyImage) { - // find the matching source images in `images` - // "/src/assets/images/faces/gnome-boy-1.jpg" matches gnome-boy-1 - image = images.find((image) => image.includes(legacyImage)) || ""; - } - } else { - image = characterData.avatar; - } - const modalDescriptionsClassNames = classNames("[&+td]:cursor-pointer"); - return ( -
-
- {characterData.avatar.length ? ( - - ) : ( - } - alt={characterData.name} - className="print:hidden shadow-md border-solid border-2 border-seaBuckthorn" - /> - )} - - {characterData.name} - -
- -
- - - - {characterData.level} - - -
{ - setModalTitle("Race"); - getRaceModalContent(characterData.race as RaceNames); - showModal(); - }} - > - {characterData.race} -
-
- -
{ - setModalTitle("Class"); - getClassModalContent(characterData.class as ClassNames[]); - showModal(); - }} - > - {characterData.class.join(" ")} -
-
-
-
- } - className="text-shipGray" - > - {modalContent} - -
- ); -} diff --git a/src/components/CharacterSheet/BaseStats/ExperiencePoints/ExperiencePoints.tsx b/src/components/CharacterSheet/BaseStats/ExperiencePoints/ExperiencePoints.tsx deleted file mode 100755 index 405e2ebe..00000000 --- a/src/components/CharacterSheet/BaseStats/ExperiencePoints/ExperiencePoints.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { Button, Input, Space } from "antd"; -import { useEffect, useState } from "react"; -import { doc, updateDoc } from "firebase/firestore"; -import { db } from "../../../../firebase"; -import { useParams } from "react-router-dom"; -import HelpTooltip from "../../../HelpTooltip/HelpTooltip"; -import { classes } from "../../../../data/classes"; -import { - CharacterData, - ClassNames, - SetCharacterData, -} from "../../../../data/definitions"; -import classNames from "classnames"; - -type ExperiencePointsProps = { - characterData: CharacterData; - setCharacterData?: SetCharacterData; - userIsOwner?: boolean; - showLevelUpModal?: () => void; -}; - -export default function ExperiencePoints({ - characterData, - setCharacterData, - userIsOwner, - showLevelUpModal, - className, -}: ExperiencePointsProps & React.ComponentPropsWithRef<"div">) { - const [prevValue, setPrevValue] = useState(characterData.xp.toString()); - - const [inputValue, setInputValue] = useState(characterData.xp.toString()); - - const handleInputChange = (event: React.ChangeEvent) => { - setInputValue(event.target.value); - }; - - const { uid, id } = useParams(); - const updateXP = async () => { - if (!uid || !id) { - console.error("User ID or Character ID is undefined"); - return; - } - - if (characterData.xp.toString() !== prevValue) { - const docRef = doc(db, "users", uid, "characters", id); - - try { - await updateDoc(docRef, { - xp: characterData.xp, - }); - setPrevValue(characterData.xp.toString()); - } catch (error) { - console.error("Error updating document: ", error); - } - } - }; - - const handleInputBlur = () => { - // Check if inputValue matches the expected format (optional '-' or '+', followed by numeric characters) - if (!/^[+-]?\d+$/.test(inputValue)) { - console.error("Invalid input"); - return; - } - - const newValue = inputValue; - if (newValue.startsWith("+")) { - const increment = parseInt(newValue.slice(1)); - if (!isNaN(increment)) { - const updatedXP = characterData.xp + increment; - if (setCharacterData) { - setCharacterData({ - ...characterData, - xp: updatedXP, - }); - } - setInputValue(updatedXP.toString()); - } - } else if (newValue.startsWith("-")) { - const decrement = parseInt(newValue.slice(1)); - if (!isNaN(decrement)) { - const updatedXP = characterData.xp - decrement; - if (setCharacterData) { - setCharacterData({ - ...characterData, - xp: updatedXP, - }); - } - setInputValue(updatedXP.toString()); - } - } else { - const value = parseInt(newValue); - if (!isNaN(value)) { - if (setCharacterData) { - setCharacterData({ - ...characterData, - xp: value, - }); - } - setInputValue(value.toString()); - } - } - }; - - const totalLevelRequirement = characterData.class - .map((className) => { - const classRequirements = - classes[className as ClassNames]?.experiencePoints; - return classRequirements ? classRequirements[characterData.level] : 0; // value if using a custom class - }) - .reduce((a, b) => a + b, 0); - - useEffect(() => { - updateXP(); - }, [characterData.xp]); - - const experiencePointsClassNames = classNames("flex", className); - - return ( -
- - event.target.select()} - onChange={handleInputChange} - onBlur={handleInputBlur} - onKeyDown={(event) => { - if (event.key === "Enter") { - handleInputBlur(); - } - }} - suffix={characterData.level < 20 && `/ ${totalLevelRequirement} XP`} - disabled={!userIsOwner} - name="Experience Points" - id="experience-points" - /> - - {characterData.level < 20 && ( - - )} - - -
- ); -} diff --git a/src/components/CharacterSheet/CharacterDescription/CharacterDescription.tsx b/src/components/CharacterSheet/CharacterDescription/CharacterDescription.tsx deleted file mode 100755 index 5477bf5a..00000000 --- a/src/components/CharacterSheet/CharacterDescription/CharacterDescription.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { Input, Typography } from "antd"; -import { useEffect, useState, useRef, FC } from "react"; -import { doc, updateDoc } from "firebase/firestore"; -import { db } from "../../../firebase"; -import { useParams } from "react-router-dom"; -import HelpTooltip from "../../HelpTooltip/HelpTooltip"; -import DOMPurify from "dompurify"; -import { MinusCircleOutlined, PlusCircleOutlined } from "@ant-design/icons"; -import { getClassType } from "../../../support/helpers"; -import DescriptionFieldButton from "./DescriptionFieldButton/DescriptionFieldButton"; -import { CharacterData, SetCharacterData } from "../../../data/definitions"; -import { User } from "firebase/auth"; - -type CharacterDescriptionProps = { - characterData: CharacterData; - setCharacterData: SetCharacterData; - userIsOwner: boolean; - user: User | null; -}; - -export default function CharacterDescription({ - characterData, - setCharacterData, - userIsOwner, - user, -}: CharacterDescriptionProps) { - // Hooks and state variables - const { uid, id } = useParams(); - const initialDesc = Array.isArray(characterData.desc) - ? characterData.desc - : [characterData.desc]; - const timeoutRef = useRef(null); - const [textAreaValues, setTextAreaValues] = useState(initialDesc); - const placeholderSavingThrows = `"${characterData.class}" SAVING THROWS\n----------\nDEATH RAY or POISON: 00\nMAGIC WANDS: 00\nPARALYSIS or PETRIFY: 00\nDRAGON BREATH: 00\nSPELLS: 00`; - - // Function to update the database - const updateDatabase = async () => { - if (!uid || !id) { - console.error("User ID or Character ID is undefined"); - return; - } - if (user?.uid !== uid) { - console.log("Not the owner of the character sheet."); - return; - } - const sanitizedValues = textAreaValues.map((value) => - DOMPurify.sanitize(value) - ); - if ( - JSON.stringify(characterData.desc) !== JSON.stringify(sanitizedValues) - ) { - const docRef = doc(db, "users", uid, "characters", id); - try { - await updateDoc(docRef, { - desc: sanitizedValues, - }); - } catch (error) { - console.error("Error updating document: ", error); - } - } - }; - - // Function to add a new description field - const handleAddDescriptionField = () => { - const newTextAreaValues = [...textAreaValues, ""]; - setTextAreaValues(newTextAreaValues); - }; - - // Function to delete a description field - const handleDeleteDescriptionField = (index: number) => { - const newTextAreaValues = textAreaValues.filter((_, i) => i !== index); - setTextAreaValues(newTextAreaValues); - }; - - // Function to handle text area changes - const handleTextAreaChange = (value: string, index: number) => { - const sanitizedValue = DOMPurify.sanitize(value); - const newTextAreaValues = [...textAreaValues]; - newTextAreaValues[index] = sanitizedValue; - setTextAreaValues(newTextAreaValues); - }; - - // Function to handle immediate database update on blur - const handleImmediateUpdate = async () => { - await updateDatabase(); - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - }; - - // Effect to initialize characterData.desc - useEffect(() => { - if (typeof characterData.desc === "string") { - setCharacterData({ - ...characterData, - desc: [characterData.desc], - }); - } - }, [characterData.desc]); - - // Effect to update characterData.desc when textAreaValues change - useEffect(() => { - setCharacterData({ - ...characterData, - desc: textAreaValues, - }); - }, [textAreaValues]); - - // Effect to handle database update with a delay - useEffect(() => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - timeoutRef.current = setTimeout(() => { - updateDatabase(); - }, 1000); - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - }; - }, [textAreaValues]); - - return ( -
-
- - Bio & Notes - - {getClassType(characterData.class) === "custom" && ( - - )} -
-
- {typeof characterData.desc === "object" && - characterData.desc.map((desc: string, index: number) => { - return ( -
-
- {index > 0 && ( - handleDeleteDescriptionField(index)} - icon={} - /> - )} - {index === characterData.desc.length - 1 && index < 9 && ( - } - // className={index > 0 ? "" : ""} - /> - )} -
- handleTextAreaChange(e.target.value, index)} - disabled={!userIsOwner} - onBlur={() => handleImmediateUpdate()} - /> -
- ); - })} -
-
- ); -} diff --git a/src/components/CharacterSheet/CharacterSheetModals/CharacterSheetModals.tsx b/src/components/CharacterSheet/CharacterSheetModals/CharacterSheetModals.tsx deleted file mode 100644 index 3182a875..00000000 --- a/src/components/CharacterSheet/CharacterSheetModals/CharacterSheetModals.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import AddCustomEquipmentModal from "../../../modals/AddCustomEquipmentModal"; -import AddEquipmentModal from "../../../modals/AddEquipmentModal"; -import CheatSheetModal from "../../../modals/CheatSheetModal"; -import DiceRollerModal from "../../../modals/DiceRollerModal"; -import LevelUpModal from "../../../modals/LevelUpModal"; -import { getAttackBonus, getHitDice } from "../../../support/helpers"; -import AttackModal from "../../AttackModal/AttackModal"; -import { - CharacterData, - SetCharacterData, - EquipmentItem, -} from "../../../data/definitions"; -import { User } from "firebase/auth"; - -type CharacterSheetModalsProps = { - characterData: CharacterData; - handleCancel: () => void; - isAddCustomEquipmentModalOpen: boolean; - isAddEquipmentModalOpen: boolean; - isAttackModalOpen: boolean; - isCheatSheetModalOpen: boolean; - isDiceRollerModalOpen: boolean; - isLevelUpModalOpen: boolean; - setCharacterData: SetCharacterData; - weapon: EquipmentItem | undefined; - user: User | null; -}; - -export default function CharacterSheetModals({ - characterData, - handleCancel, - isAddCustomEquipmentModalOpen, - isAddEquipmentModalOpen, - isAttackModalOpen, - isCheatSheetModalOpen, - isDiceRollerModalOpen, - isLevelUpModalOpen, - setCharacterData, - weapon, - user, -}: CharacterSheetModalsProps) { - return ( - <> - - - - - - - - ); -} diff --git a/src/components/CharacterSheet/EquipmentInfo/CharacterSpellList/CharacterSpellList.tsx b/src/components/CharacterSheet/EquipmentInfo/CharacterSpellList/CharacterSpellList.tsx deleted file mode 100755 index 16381f55..00000000 --- a/src/components/CharacterSheet/EquipmentInfo/CharacterSpellList/CharacterSpellList.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Button, Descriptions } from "antd"; -import { useState } from "react"; -import { marked } from "marked"; -import { Spell, CharacterData } from "../../../../data/definitions"; - -type CharacterSpellListProps = { - spells: CharacterData["spells"]; -}; - -export default function CharacterSpellList({ - spells, -}: CharacterSpellListProps) { - const [selectedSpell, setSelectedSpell] = useState(null); - const spellItems = spells.map((spell: Spell, index: number) => { - const spellDescription = ( -
- - {selectedSpell === index && ( -
- )} -
- ); - return [ - { key: "1", label: "Name", children: {spell.name} }, - { key: "2", label: "Range", children: spell.range }, - { key: "3", label: "Duration", children: spell.duration }, - { key: "4", label: "Description", children: spellDescription }, - ]; - }); - return ( -
- {spellItems.map((spellItem: any, index: number) => { - return ( - - ); - })} -
- ); -} diff --git a/src/components/CharacterSheet/EquipmentInfo/EquipmentInfo.tsx b/src/components/CharacterSheet/EquipmentInfo/EquipmentInfo.tsx deleted file mode 100755 index 2b58cdb4..00000000 --- a/src/components/CharacterSheet/EquipmentInfo/EquipmentInfo.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Button, Collapse, CollapseProps, Typography } from "antd"; -import HelpTooltip from "../../HelpTooltip/HelpTooltip"; -import { ExperimentOutlined, ToolOutlined } from "@ant-design/icons"; -import { CharacterData } from "../../../data/definitions"; - -type EquipmentInfoProps = { - userIsOwner: boolean; - showAddEquipmentModal: () => void; - showAddCustomEquipmentModal: () => void; - characterData: CharacterData; - collapseItems: CollapseProps["items"]; -}; - -export default function EquipmentInfo({ - userIsOwner, - showAddEquipmentModal, - showAddCustomEquipmentModal, - characterData, - className, - collapseItems, -}: EquipmentInfoProps & React.ComponentPropsWithRef<"div">) { - return ( -
-
- - Equipment - - -
-
- - -
-
- {characterData.equipment.map((item) => ( -
{item.name}
- ))} -
- -
- ); -} diff --git a/src/components/CharacterSheet/EquipmentInfo/EquipmentList/EquipmentList.tsx b/src/components/CharacterSheet/EquipmentInfo/EquipmentList/EquipmentList.tsx deleted file mode 100755 index 5f42094c..00000000 --- a/src/components/CharacterSheet/EquipmentInfo/EquipmentList/EquipmentList.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import equipmentItems from "../../../../data/equipmentItems.json"; -import { Button, Empty, Radio, Typography } from "antd"; -import { DeleteOutlined } from "@ant-design/icons"; -import WeaponKeys from "../../../WeaponKeys/WeaponKeys"; -import ItemWrapper from "./ItemWrapper/ItemWrapper"; -import ItemDescription from "./ItemDescription/ItemDescription"; -import { classes } from "../../../../data/classes"; -import { useEffect } from "react"; -import { - CharacterData, - ClassNames, - EquipmentItem, - RaceNames, - SetCharacterData, -} from "../../../../data/definitions"; -import { races } from "../../../../data/races"; - -type EquipmentListProps = { - characterData: CharacterData; - setCharacterData: SetCharacterData; - categories: string[]; - handleCustomDelete: (item: EquipmentItem) => void; - handleAttack?: boolean; - handleAttackClick?: (item: EquipmentItem) => void; - updateAC?: () => void; -}; - -const punchItem: EquipmentItem = { - name: "Punch**", - costValue: 0, - costCurrency: "gp", - category: "inherent", - damage: "1d3", - amount: 1, - type: "melee", - noDelete: true, -}; - -const kickItem: EquipmentItem = { - name: "Kick**", - costValue: 0, - costCurrency: "gp", - category: "inherent", - damage: "1d4", - amount: 1, - type: "melee", - noDelete: true, -}; - -export default function EquipmentList({ - characterData, - setCharacterData, - categories, - handleCustomDelete, - handleAttack, - handleAttackClick, - updateAC, -}: EquipmentListProps) { - const shownItems = characterData.equipment - .filter((item: EquipmentItem) => categories.includes(item.category)) - .sort((a: EquipmentItem, b: EquipmentItem) => a.name.localeCompare(b.name)); - - const handleUpdateAC = (item: string, type: string) => { - const oldArmor = characterData.wearing?.armor; - const oldShield = characterData.wearing?.shield; - - setCharacterData({ - ...characterData, - wearing: { - armor: characterData.wearing?.armor || "", - shield: characterData.wearing?.shield || "", - [type]: item, - }, - }); - - const newArmor = characterData.wearing?.armor; - const newShield = characterData.wearing?.shield; - - if (oldArmor !== newArmor || oldShield !== newShield) { - updateAC && updateAC(); - } - }; - - const EmptyRadio = ({ label }: { label: string }) => ( - - {label} - - ); - - useEffect(() => { - // Remove empty items from the equipment array. - const remainingEquipment = characterData.equipment.filter( - (item: EquipmentItem) => item.amount !== 0 - ); - if (remainingEquipment.length !== characterData.equipment.length) { - setCharacterData({ ...characterData, equipment: remainingEquipment }); - } - }, [characterData.equipment]); - - return categories.includes("armor") || categories.includes("shields") ? ( - { - const type = categories.includes("armor") ? "armor" : "shield"; - handleUpdateAC(e.target.value, type); - }} - > - {categories.includes("armor") && } - {categories.includes("shields") && } - {shownItems.map((item: EquipmentItem) => { - // Ignore previously existing "NO X" items in characters' equipment. - if (item.name === "No Shield" || item.name === "No Armor") return null; - return ( - -
- - {item.name} - - {!equipmentItems.some( - (equipmentItem) => equipmentItem.name === item.name - ) && - characterData.wearing && - item.name !== characterData.wearing.armor && - item.name !== characterData.wearing.shield && ( -
- -
- ); - })} -
- ) : ( - // Weapon Items -
- {categories.includes("weapons") && ( - <> - - - {races[characterData.race as RaceNames]?.uniqueAttacks?.map( - (attack) => ( - - ) - )} - {characterData.class.map( - (className) => - classes[className as ClassNames]?.powers?.map((power) => { - return ( - characterData.level >= (power.minLevel ?? 0) && ( - - ) - ); - }) - )} - - )} - {/* STARTING EQUIPMENT */} - {categories.includes("general-equipment") && - characterData.class.map( - (className) => - classes[className as ClassNames]?.startingEquipment?.map( - (item: EquipmentItem) => ( - - ) - ) - )} - {shownItems.length > 0 ? ( - shownItems.map((item: EquipmentItem) => ( - - )) - ) : ( - - )} - {categories.includes("weapons") && } -
- ); -} diff --git a/src/components/CharacterSheet/EquipmentInfo/EquipmentList/ItemDescription/ItemDescription.tsx b/src/components/CharacterSheet/EquipmentInfo/EquipmentList/ItemDescription/ItemDescription.tsx deleted file mode 100755 index c972f776..00000000 --- a/src/components/CharacterSheet/EquipmentInfo/EquipmentList/ItemDescription/ItemDescription.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Descriptions } from "antd"; -import { slugToTitleCase } from "../../../../../support/stringSupport"; -import { EquipmentItem } from "../../../../../data/definitions"; - -type ItemDescriptionProps = { - item: EquipmentItem; -}; - -export default function ItemDescription({ item }: ItemDescriptionProps) { - return ( - - {item.weight && ( - {item.weight} - )} - {item.size && ( - {item.size} - )} - {item.amount && item.name !== "Punch" && item.name !== "Kick" && ( - {item.amount} - )} - {item.AC && {item.AC}} - {item.missileAC && ( - - {item.missileAC} - - )} - {item.damage && ( - {item.damage} - )} - - {slugToTitleCase(item.category)} - - - ); -} diff --git a/src/components/CharacterSheet/EquipmentInfo/EquipmentList/ItemWrapper/ItemWrapper.tsx b/src/components/CharacterSheet/EquipmentInfo/EquipmentList/ItemWrapper/ItemWrapper.tsx deleted file mode 100755 index 297d1ac0..00000000 --- a/src/components/CharacterSheet/EquipmentInfo/EquipmentList/ItemWrapper/ItemWrapper.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Button, Typography } from "antd"; -import ItemDescription from "../ItemDescription/ItemDescription"; -import { DeleteOutlined } from "@ant-design/icons"; -import equipmentItems from "../../../../../data/equipmentItems.json"; -import { EquipmentItem } from "../../../../../data/definitions"; - -type ItemWrapperProps = { - item: EquipmentItem; - handleAttack?: boolean; - handleAttackClick?: (item: EquipmentItem) => void; - handleCustomDelete: (item: EquipmentItem) => void; -}; - -export default function ItemWrapper({ - item, - handleAttack, - handleAttackClick, - handleCustomDelete, -}: ItemWrapperProps) { - return ( -
-
- - {item.name} - - {!equipmentItems.some( - (equipmentItem) => equipmentItem.name === item.name - ) && - !item.noDelete && ( -
- - {handleAttack && handleAttackClick && ( - <> -
- -
- - )} -
- ); -} diff --git a/src/components/CharacterSheet/HitPoints/HitPoints.tsx b/src/components/CharacterSheet/HitPoints/HitPoints.tsx deleted file mode 100755 index 8a74a1db..00000000 --- a/src/components/CharacterSheet/HitPoints/HitPoints.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { Input, Typography } from "antd"; -import { useEffect, useState } from "react"; -import { doc, updateDoc } from "firebase/firestore"; -import { db } from "../../../firebase"; -import { useParams } from "react-router-dom"; -import DOMPurify from "dompurify"; -import { CharacterData, SetCharacterData } from "../../../data/definitions"; - -type HitPointsProps = { - characterData: CharacterData; - setCharacterData: SetCharacterData; - userIsOwner: boolean; -} - -export default function HitPoints({ - characterData, - setCharacterData, - className, - userIsOwner, -}: HitPointsProps & React.ComponentPropsWithRef<"div">) { - const [prevValue, setPrevValue] = useState( - characterData.hp.points.toString() - ); - const [inputValue, setInputValue] = useState( - characterData.hp.points.toString() - ); - const [descValue, setDescValue] = useState(characterData.hp.desc || ""); - - const { uid, id } = useParams(); - - const handleInputChange = (event: React.ChangeEvent) => { - setInputValue(event.target.value); - }; - - const handleDescChange = (event: React.ChangeEvent) => { - const sanitizedInput = DOMPurify.sanitize(event.target.value); - setDescValue(sanitizedInput); - }; - - const handleInputBlur = () => { - const newValue = inputValue; - if (newValue.startsWith("+")) { - const increment = parseInt(newValue.slice(1)); - if (!isNaN(increment)) { - let updatedHP = characterData.hp.points + increment; - // Ensure that the updated HP does not exceed the maximum HP - updatedHP = Math.min(updatedHP, characterData.hp.max); - setCharacterData({ - ...characterData, - hp: { - ...characterData.hp, - points: updatedHP, - }, - }); - setInputValue(updatedHP.toString()); - } - } else if (newValue.startsWith("-")) { - const decrement = parseInt(newValue.slice(1)); - if (!isNaN(decrement)) { - let updatedHP = characterData.hp.points - decrement; - // Ensure that the updated HP does not go below 0 - updatedHP = Math.max(updatedHP, 0); - setCharacterData({ - ...characterData, - hp: { - ...characterData.hp, - points: updatedHP, - }, - }); - setInputValue(updatedHP.toString()); - } - } else { - let value = parseInt(newValue); - if (!isNaN(value)) { - // Ensure that the value does not exceed the maximum HP or go below 0 - value = Math.min(Math.max(value, 0), characterData.hp.max); - setCharacterData({ - ...characterData, - hp: { - ...characterData.hp, - points: value, - }, - }); - setInputValue(value.toString()); - } - } - }; - - const updateHP = async () => { - if (!uid || !id) { - console.error("User ID or Character ID is undefined"); - return; - } - - if (characterData.hp.points.toString() !== prevValue) { - const docRef = doc(db, "users", uid, "characters", id); - - try { - await updateDoc(docRef, { - "hp.points": characterData.hp.points, - }); - setPrevValue(characterData.hp.points.toString()); - } catch (error) { - console.error("Error updating document: ", error); - } - } - }; - - const handleDescBlur = async () => { - if (!uid || !id) { - console.error("User ID or Character ID is undefined"); - return; - } - - if (descValue !== characterData.hp.desc) { - const docRef = doc(db, "users", uid, "characters", id); - - try { - await updateDoc(docRef, { - "hp.desc": descValue, - }); - setCharacterData({ - ...characterData, - hp: { - ...characterData.hp, - desc: descValue, - }, - }); - } catch (error) { - console.error("Error updating document: ", error); - } - } - }; - - useEffect(() => { - updateHP(); - }, [characterData.hp.points]); - - return ( -
- - Hit Points - - { - if (event.key === "Enter") { - handleInputBlur(); - } - }} - onFocus={(event) => event.target.select()} - name="Hit Points" - id="hit-points" - /> - - - - Character count: {descValue.length}/500 - -
- ); -} diff --git a/src/components/CharacterSheet/InitiativeRoller/InitiativeRoller.tsx b/src/components/CharacterSheet/InitiativeRoller/InitiativeRoller.tsx deleted file mode 100755 index 22686855..00000000 --- a/src/components/CharacterSheet/InitiativeRoller/InitiativeRoller.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { Button, Tooltip, notification } from "antd"; -import { DiceRoller } from "@dice-roller/rpg-dice-roller"; -import CloseIcon from "../../CloseIcon/CloseIcon"; -import { CharacterData, RaceNames } from "../../../data/definitions"; -import { NodeIndexOutlined } from "@ant-design/icons"; - -type InitiativeRollerProps = { - characterData: CharacterData; - buttonTextClassNames: string; -}; - -export default function InitiativeRoller({ - characterData, - buttonTextClassNames, -}: InitiativeRollerProps) { - const [api, contextHolder] = notification.useNotification(); - - const openNotification = (result: number) => { - api.open({ - message: "Initiative Roll", - description: result, - duration: 0, - className: "!bg-seaBuckthorn", - closeIcon: , - }); - }; - - const rollTooltip = `1d6 + DEX modifier ${ - characterData.race === RaceNames.HALFLING - ? `+ 1 as a ${RaceNames.HALFLING}` - : "" - }`; - - const roller = new DiceRoller(); - - const rollInitiative = () => { - let result = roller.roll( - `1d6${characterData.abilities.modifiers.dexterity}${ - characterData.race === RaceNames.HALFLING ? "+1" : "" - }` - ); - if (result.total === 0) result = 1; - openNotification(result.output); - }; - - return ( - <> - {contextHolder} - - - - - ); -} diff --git a/src/components/CharacterSheet/MoneyStats/MoneyStats.tsx b/src/components/CharacterSheet/MoneyStats/MoneyStats.tsx deleted file mode 100755 index b6622db7..00000000 --- a/src/components/CharacterSheet/MoneyStats/MoneyStats.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { Input, Space, Typography } from "antd"; -import React, { useEffect, useState } from "react"; -import { doc, updateDoc } from "firebase/firestore"; -import { db } from "../../../firebase"; -import { useParams } from "react-router-dom"; -import HelpTooltip from "../../HelpTooltip/HelpTooltip"; -import { CharacterData, SetCharacterData } from "../../../data/definitions"; - -type MoneyStatsProps = { - characterData: CharacterData; - setCharacterData: SetCharacterData; - userIsOwner: boolean; - makeChange: () => { gp: number; sp: number; cp: number }; -}; - -export default function MoneyStats({ - characterData, - setCharacterData, - userIsOwner, - makeChange, - className, -}: MoneyStatsProps & React.ComponentPropsWithRef<"div">) { - const { gp, sp, cp } = makeChange(); - const [goldValue, setGoldValue] = useState(gp.toString()); - const [silverValue, setSilverValue] = useState(sp.toString()); - const [copperValue, setCopperValue] = useState(cp.toString()); - - const { uid, id } = useParams<{ uid: string; id: string }>(); - - const handleInputChange = ( - event: React.ChangeEvent, - setFunc: React.Dispatch> - ) => { - setFunc(event.target.value); - }; - - const handleInputBlur = async ( - newValue: string, - originalValue: number, - setFunc: React.Dispatch>, - multiplier: number - ) => { - let valueToSet: number = NaN; - - if (newValue.startsWith("+")) { - const increment = parseInt(newValue.slice(1)); - if (!isNaN(increment)) { - valueToSet = originalValue + increment; - } - } else if (newValue.startsWith("-")) { - const decrement = parseInt(newValue.slice(1)); - if (!isNaN(decrement)) { - valueToSet = - originalValue - decrement >= 0 ? originalValue - decrement : 0; - } - } else { - const value = parseInt(newValue); - if (!isNaN(value) && value >= 0) { - valueToSet = value; - } - } - - if (!isNaN(valueToSet) && setCharacterData) { - setFunc(valueToSet.toString()); - setCharacterData({ - ...characterData, - gold: characterData.gold + (valueToSet - originalValue) / multiplier, - }); - - await updateMoney( - characterData.gold + (valueToSet - originalValue) / multiplier - ); - } - }; - - const updateMoney = async (gold: number) => { - if (!uid || !id) { - console.error("User ID or Character ID is undefined"); - return; - } - - const docRef = doc(db, "users", uid, "characters", id); - - try { - await updateDoc(docRef, { - gold, - }); - } catch (error) { - console.error("Error updating document: ", error); - } - }; - - useEffect(() => { - const { gp, sp, cp } = makeChange(); - - setGoldValue(gp.toString()); - setSilverValue(sp.toString()); - setCopperValue(cp.toString()); - }, [characterData.gold]); - - return ( -
-
- - Money - - -
- - {[ - ["gp", goldValue, setGoldValue, 1], - ["sp", silverValue, setSilverValue, 10], - ["cp", copperValue, setCopperValue, 100], - ].map(([key, value, setFunc, multiplier]) => ( - - event.target.select()} - onChange={(event) => - handleInputChange( - event, - setFunc as React.Dispatch> - ) - } - onBlur={() => - handleInputBlur( - value as string, - (makeChange() as { [key: string]: number })[ - key as "gp" | "sp" | "cp" - ], - setFunc as React.Dispatch>, - multiplier as number - ) - } - onKeyDown={(event) => { - if (event.key === "Enter") { - handleInputBlur( - value as string, - (makeChange() as { [key: string]: number })[ - key as "gp" | "sp" | "cp" - ], - setFunc as React.Dispatch>, - multiplier as number - ); - } - }} - addonAfter={key as string} - disabled={!userIsOwner} - id={key as string} - /> - - - ))} - -
- ); -} diff --git a/src/components/CharacterSheet/SavingThrows/SavingThrows.tsx b/src/components/CharacterSheet/SavingThrows/SavingThrows.tsx deleted file mode 100755 index 72b8ed77..00000000 --- a/src/components/CharacterSheet/SavingThrows/SavingThrows.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { - getClassType, - getSavingThrows, - getSavingThrowsWeight, -} from "../../../support/helpers"; -import { DiceRoller } from "@dice-roller/rpg-dice-roller"; -import CloseIcon from "../../CloseIcon/CloseIcon"; -import { Table, Typography, notification } from "antd"; -import { - camelCaseToTitleCase, - titleCaseToCamelCase, -} from "../../../support/stringSupport"; -import { races } from "../../../data/races"; -import { - CharacterData, - RaceNames, - SavingThrowsType, -} from "../../../data/definitions"; -import SavingThrowsFootnotes from "./SavingThrowsFootnotes/SavingThrowsFootnotes"; - -type TableCellRecord = { - score: number; - throw: string; -}; - -const roller = new DiceRoller(); - -const defaultSavingThrows: SavingThrowsType = { - deathRayOrPoison: 0, - magicWands: 0, - paralysisOrPetrify: 0, - dragonBreath: 0, - spells: 0, -}; - -export default function SavingThrows({ - characterData, - className, -}: { characterData: CharacterData } & React.ComponentPropsWithRef<"div">) { - const classType = getClassType(characterData.class); - const characterLevel = characterData.level; - - const [api, contextHolder] = notification.useNotification(); - - const rollSavingThrow = (score: number, title: string) => { - const raceModifier = - races[characterData.race as RaceNames]?.savingThrows?.[ - titleCaseToCamelCase(title) as keyof SavingThrowsType - ] || 0; - const result = roller.roll( - `d20${raceModifier > 0 ? `+${raceModifier}` : ""}` - ); - const passFail = result.total >= score ? "Pass" : "Fail"; - openNotification(result.output + " - " + passFail, title); - }; - - const openNotification = (result: string, specialAbilityTitle: string) => { - api.open({ - message: `${specialAbilityTitle} Roll`, - description: result, - duration: 0, - className: "bg-seaBuckthorn", - closeIcon: , - }); - }; - - // Set the default saving throws - let savingThrows: SavingThrowsType = defaultSavingThrows; - // if classType is standard, find saving throws for that class - if (classType === "standard") { - savingThrows = - getSavingThrows(characterData.class.join(), characterLevel) || - defaultSavingThrows; - // if classType is combination, find saving throws for each class and use the best - } else { - const [firstClass, secondClass] = characterData.class; - const firstClassSavingThrows = getSavingThrows(firstClass, characterLevel); - const secondClassSavingThrows = getSavingThrows( - secondClass, - characterLevel - ); - savingThrows = - getSavingThrowsWeight(firstClassSavingThrows) <= - getSavingThrowsWeight(secondClassSavingThrows) - ? firstClassSavingThrows - : secondClassSavingThrows; - } - - const dataSource: Array<{ key: number; throw: string; score: number }> = []; - Object.entries(savingThrows).forEach(([key, value], index) => { - dataSource.push({ - key: index + 1, - throw: camelCaseToTitleCase(key), - score: value, - }); - }); - - const columns = [ - { - title: "Saving Throw", - dataIndex: "throw", - key: "throw", - onCell: (record: TableCellRecord) => ({ - onClick: () => rollSavingThrow(record.score, record.throw), - }), - }, - { - title: "Value", - dataIndex: "score", - key: "score", - onCell: (record: TableCellRecord) => ({ - onClick: () => rollSavingThrow(record.score, record.throw), - }), - }, - ]; - - return ( - <> - {contextHolder} -
- - Saving Throws - -
- - - - ); -} diff --git a/src/components/CharacterSheet/SavingThrows/SavingThrowsFootnotes/SavingThrowsFootnotes.tsx b/src/components/CharacterSheet/SavingThrows/SavingThrowsFootnotes/SavingThrowsFootnotes.tsx deleted file mode 100644 index 807dfdda..00000000 --- a/src/components/CharacterSheet/SavingThrows/SavingThrowsFootnotes/SavingThrowsFootnotes.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Typography } from "antd"; -import { classes } from "../../../../data/classes"; -import { CharacterData, ClassNames } from "../../../../data/definitions"; -import { marked } from "marked"; - -export default function SavingThrowsFootnotes({ - characterData, -}: { - characterData: CharacterData; -}) { - return ( -
- {characterData.abilities.modifiers.constitution !== "+0" && ( - -
Posion saving throws.` - ), - }} - /> - - )} - {characterData.abilities.modifiers.intelligence !== "+0" && ( - -
- - )} - {characterData.abilities.modifiers.wisdom !== "+0" && ( - -
- - )} - {characterData.class.map( - (className) => - classes[className as ClassNames]?.savingThrowsNotes?.map((note) => ( - -
- - )) - )} -
- ); -} diff --git a/src/components/CharacterSheet/SimpleNumberStat/SimpleNumberStat.tsx b/src/components/CharacterSheet/SimpleNumberStat/SimpleNumberStat.tsx deleted file mode 100755 index 580c157b..00000000 --- a/src/components/CharacterSheet/SimpleNumberStat/SimpleNumberStat.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Statistic, Typography } from "antd"; -import HelpTooltip from "../../HelpTooltip/HelpTooltip"; - -type SimpleNumberStatProps = { - title: string; - value: string | number; - altValue?: string | number; - helpText?: string; -}; - -export default function SimpleNumberStat({ - title, - value, - helpText, - altValue, -}: SimpleNumberStatProps) { - return ( - - - {title} - - {helpText && helpText.length > 0 && } -
- } - value={value} - valueStyle={{ - fontSize: "3.75rem", - lineHeight: "1", - fontWeight: "bold", - color: "#3E3643", - }} - suffix={altValue &&
/ {altValue}
} - /> - ); -} diff --git a/src/components/CharacterSheet/SpecialAbilitiesTable/SpecialAbilitiesFootnotes/SpecialAbilitiesFootnotes.tsx b/src/components/CharacterSheet/SpecialAbilitiesTable/SpecialAbilitiesFootnotes/SpecialAbilitiesFootnotes.tsx deleted file mode 100644 index 20018e93..00000000 --- a/src/components/CharacterSheet/SpecialAbilitiesTable/SpecialAbilitiesFootnotes/SpecialAbilitiesFootnotes.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Typography } from "antd"; -import { getSpecialAbilityRaceOverrides } from "../../../../support/helpers"; -import { marked } from "marked"; -import { ClassNames, RaceNames } from "../../../../data/definitions"; - -type SpecialAbilitiesFootnotesProps = { - characterRace: RaceNames; - characterClass: ClassNames; -}; - -export default function SpecialAbilitiesFootnotes({ - characterRace, - characterClass, -}: SpecialAbilitiesFootnotesProps) { - const raceOverrides = getSpecialAbilityRaceOverrides(characterRace); - let matchingOverrides: [string, string][] | [] = []; - raceOverrides.map((raceOverride: any) => { - if (raceOverride[0] === characterClass) { - matchingOverrides = Object.entries(raceOverride[1]); - } - }); - return matchingOverrides.map((override: [string, string]) => ( -
- -
- -
- )); -} diff --git a/src/components/CharacterSheet/SpecialAbilitiesTable/SpecialAbilitiesTable.tsx b/src/components/CharacterSheet/SpecialAbilitiesTable/SpecialAbilitiesTable.tsx deleted file mode 100755 index 21de6855..00000000 --- a/src/components/CharacterSheet/SpecialAbilitiesTable/SpecialAbilitiesTable.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { Table, Typography, notification } from "antd"; -import HelpTooltip from "../../HelpTooltip/HelpTooltip"; -import { toTitleCase } from "../../../support/stringSupport"; -import { DiceRoller } from "@dice-roller/rpg-dice-roller"; -import CloseIcon from "../../CloseIcon/CloseIcon"; -import { classes } from "../../../data/classes"; -import { - CharacterData, - ClassNames, - RaceNames, -} from "../../../data/definitions"; -import React from "react"; -import SpecialAbilitiesFootnotes from "./SpecialAbilitiesFootnotes/SpecialAbilitiesFootnotes"; - -type SpecialAbilitiesTableProps = { - characterLevel: CharacterData["level"]; - characterClass: string; - characterRace: RaceNames; -}; - -export default function SpecialAbilitiesTable({ - characterLevel, - characterClass, - characterRace, - className, -}: SpecialAbilitiesTableProps & React.ComponentPropsWithRef<"div">) { - const dataSource: {}[] = []; - - const abilities = - classes[characterClass as ClassNames].specialAbilities?.stats[ - characterLevel - ] || []; - - classes[characterClass as ClassNames].specialAbilities?.titles.forEach( - (skill: string, index: number) => { - dataSource.push({ key: index + 1, skill, percentage: abilities[index] }); - } - ); - - const columns = [ - { - title: "Skill", - dataIndex: "skill", - key: "skill", - onCell: (record: any) => ({ - onClick: () => rollSpecialAbility(record.percentage, record.skill), - }), - }, - { - title: "Percentage", - dataIndex: "percentage", - key: "percentage", - onCell: (record: any) => ({ - onClick: () => rollSpecialAbility(record.percentage, record.skill), - }), - }, - ]; - - const roller = new DiceRoller(); - const [api, contextHolder] = notification.useNotification(); - const openNotification = (result: string, specialAbilityTitle: string) => { - api.open({ - message: `${specialAbilityTitle} Roll`, - description: result, - duration: 0, - className: "bg-seaBuckthorn", - closeIcon: , - }); - }; - const rollSpecialAbility = ( - specialAbilityScore: number, - specialAbilityTitle: string - ) => { - const result = roller.roll(`d%`); - const passFail = result.total <= specialAbilityScore ? "Pass" : "Fail"; - openNotification(result.output + " - " + passFail, specialAbilityTitle); - }; - - return ( - <> - {contextHolder} -
-
- - {toTitleCase(characterClass)} Special Abilities - - -
-
- - - - ); -} diff --git a/src/components/CharacterSheet/SpecialsRestrictions/SpecialsRestrictions.tsx b/src/components/CharacterSheet/SpecialsRestrictions/SpecialsRestrictions.tsx deleted file mode 100755 index f94bb747..00000000 --- a/src/components/CharacterSheet/SpecialsRestrictions/SpecialsRestrictions.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { List, Tabs, TabsProps, Typography } from "antd"; -import { marked } from "marked"; -import { classes } from "../../../data/classes"; -import { races } from "../../../data/races"; -import { - CharacterData, - ClassNames, - RaceNames, -} from "../../../data/definitions"; -import { titleCaseToCamelCase } from "../../../support/stringSupport"; - -// Ant Design's List component treats the input as a string and not as HTML. -// To render HTML, you need to use dangerouslySetInnerHTML prop in React. -// However, List.Item does not support dangerouslySetInnerHTML directly. -// To overcome this, HtmlRender component accepts the HTML string and renders it correctly. -const HtmlRender = ({ html }: { html: string }) => ( -
-); - -const SpecialsRestrictionsList = ({ dataSource }: { dataSource: any[] }) => ( - ( - - - - )} - className="print:border-0" - size="small" - /> -); - -export default function SpecialsRestrictions({ - characterData, - className, -}: { characterData: CharacterData } & React.ComponentPropsWithRef<"div">) { - const items: TabsProps["items"] = [ - { - key: titleCaseToCamelCase(characterData.race), - label: characterData.race, - children: ( - - ), - }, - ...characterData.class.map((cls) => ({ - key: titleCaseToCamelCase(cls), - label: cls, - children: ( - - ), - })), - ]; - - return ( -
- - Special Abilities & Restrictions - - -
- ); -} diff --git a/src/components/CharacterSheet/WeightStats/WeightStats.tsx b/src/components/CharacterSheet/WeightStats/WeightStats.tsx deleted file mode 100755 index 57d99223..00000000 --- a/src/components/CharacterSheet/WeightStats/WeightStats.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Descriptions, Divider } from "antd"; -import { getCarryingCapacity } from "../../../support/formatSupport"; -import SimpleNumberStat from "../SimpleNumberStat/SimpleNumberStat"; -import { - CharacterData, - EquipmentItem, - RaceNames, -} from "../../../data/definitions"; - -export default function WeightStats({ - characterData, - className, -}: { characterData: CharacterData } & React.ComponentPropsWithRef<"div">) { - const capacity = getCarryingCapacity( - +characterData.abilities.scores.strength, - characterData.race as RaceNames - ); - const weight = characterData.equipment.reduce( - (accumulator: number, currentValue: EquipmentItem) => - accumulator + (currentValue.weight ?? 0) * (currentValue.amount ?? 0), - 0 - ); - - return ( -
- - - - - {capacity.heavy} - - - {weight < capacity.light ? "Lightly Loaded" : "Heavily Loaded"} - - -
- ); -} diff --git a/src/components/CloseIcon/CloseIcon.tsx b/src/components/CloseIcon/CloseIcon.tsx deleted file mode 100755 index 338a1ad1..00000000 --- a/src/components/CloseIcon/CloseIcon.tsx +++ /dev/null @@ -1,21 +0,0 @@ -export default function CloseIcon() { - return ( -
- -
- ); -} diff --git a/src/components/ContentListWrapper/ContentListWrapper.tsx b/src/components/ContentListWrapper/ContentListWrapper.tsx new file mode 100644 index 00000000..533ff066 --- /dev/null +++ b/src/components/ContentListWrapper/ContentListWrapper.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import classNames from "classnames"; + +interface ContentListWrapperProps { + loading: boolean; + loadingContent: React.ReactNode; +} + +const ContentListWrapper: React.FC< + ContentListWrapperProps & React.ComponentPropsWithRef<"div"> +> = ({ className, children, loading, loadingContent }) => { + const contentListWrapperClassNames = classNames( + "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 items-start", + className, + ); + return ( +
+ {loading ? loadingContent : children} +
+ ); +}; + +export default ContentListWrapper; diff --git a/src/components/EquipmentStore/EquipmentAccordion/EquipmentAccordion.tsx b/src/components/EquipmentStore/EquipmentAccordion/EquipmentAccordion.tsx deleted file mode 100755 index f5cf2dd0..00000000 --- a/src/components/EquipmentStore/EquipmentAccordion/EquipmentAccordion.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { Collapse, CollapseProps } from "antd"; -import { slugToTitleCase } from "../../../support/stringSupport"; -import equipmentItems from "../../../data/equipmentItems.json"; -import WeaponKeys from "../../WeaponKeys/WeaponKeys"; -import { classes } from "../../../data/classes"; -import { - CharacterData, - EquipmentCategories, - EquipmentItem, -} from "../../../data/definitions"; -import { ClassNames } from "../../../data/definitions"; -import classNames from "classnames"; -import EquipmentCheckboxGroup from "../EquipmentCheckboxGroup/EquipmentCheckboxGroup"; - -type EquipmentAccordionProps = { - characterData: CharacterData; - onAmountChange: (item?: EquipmentItem) => void; - onCheckboxCheck: (item?: EquipmentItem) => void; - onRadioCheck: (item?: EquipmentItem) => void; -}; - -export default function EquipmentAccordion({ - onAmountChange, - onCheckboxCheck, - characterData, - className, -}: EquipmentAccordionProps & React.ComponentPropsWithRef<"div">) { - const classCategories = characterData.class.flatMap( - (classPiece) => - classes[classPiece as ClassNames]?.availableEquipmentCategories - ); - - const categories = classCategories.some((category) => category !== undefined) - ? Array.from(new Set(classCategories)) - : Object.values(EquipmentCategories); - - const equipmentAccordionClassNames = classNames( - className, - "bg-seaBuckthorn", - "h-fit" - ); - - const generalEquipmentName = "general-equipment"; - - const generalEquipmentItems: CollapseProps["items"] = [ - ...new Set( - equipmentItems - .filter((item) => item.category === generalEquipmentName) - .map((item) => item.subCategory) - ), - ].map((subCategory, index) => { - return { - key: index + 1 + "", - label: slugToTitleCase(subCategory || ""), - children: ( - - ), - }; - }); - - const items: CollapseProps["items"] = categories - .sort((a, b) => a.localeCompare(b)) - .map((category, index) => { - return { - key: index + 1 + "", - label: slugToTitleCase(category), - children: - category === "general-equipment" ? ( - - ) : ( - - ), - }; - }); - - return ( - <> - - - - ); -} diff --git a/src/components/EquipmentStore/EquipmentCheckbox/EquipmentCheckbox.tsx b/src/components/EquipmentStore/EquipmentCheckbox/EquipmentCheckbox.tsx deleted file mode 100755 index 68efb921..00000000 --- a/src/components/EquipmentStore/EquipmentCheckbox/EquipmentCheckbox.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Checkbox, InputNumber, Space } from "antd"; -import React, { ReactElement, useEffect, useState } from "react"; -import { CheckboxChangeEvent } from "antd/es/checkbox"; -import { EquipmentItem } from "../../../data/definitions"; - -type EquipmentCheckboxProps = { - disabled?: boolean; - item: EquipmentItem; - onCheckboxCheck: (item?: EquipmentItem, checked?: boolean) => void; - onAmountChange: (item?: EquipmentItem) => void; - playerHasItem: boolean; - equipmentItemDescription: ReactElement; - inputDisabled: boolean; - itemAmount: number; -}; - -export default function EquipmentCheckbox({ - disabled, - item, - className, - onCheckboxCheck, - onAmountChange, - playerHasItem, - equipmentItemDescription, - inputDisabled, - itemAmount, -}: EquipmentCheckboxProps & React.ComponentPropsWithRef<"div">) { - const [isChecked, setIsChecked] = useState(playerHasItem); - const [amount, setAmount] = useState(itemAmount); - - const handleCheckboxChange = ( - e: CheckboxChangeEvent, - item: EquipmentItem - ) => { - const checked = e.target.checked; - setIsChecked(checked); - onCheckboxCheck(item, checked); - if (!checked) setAmount(1); - }; - - const handleInputChange = (value: number | null, item: EquipmentItem) => { - setAmount(value || 1); - onAmountChange({ ...item, amount: value || 0 }); - }; - - useEffect(() => { - setAmount(itemAmount || 1); - }, [item.amount]); - - return ( - - handleCheckboxChange(e, item)} - checked={playerHasItem} - > - {equipmentItemDescription} - - handleInputChange(value, item)} - disabled={inputDisabled} - className={`${!playerHasItem && "hidden"} ml-6`} - value={amount} - /> - - ); -} diff --git a/src/components/EquipmentStore/EquipmentCheckboxGroup/EquipmentCheckboxGroup.tsx b/src/components/EquipmentStore/EquipmentCheckboxGroup/EquipmentCheckboxGroup.tsx deleted file mode 100644 index 2510bbf4..00000000 --- a/src/components/EquipmentStore/EquipmentCheckboxGroup/EquipmentCheckboxGroup.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Space } from "antd"; -import equipmentItems from "../../../data/equipmentItems.json"; -import { equipmentItemIsDisabled } from "../../../support/helpers"; -import { - CharacterData, - ClassNames, - EquipmentItem, - RaceNames, -} from "../../../data/definitions"; -import EquipmentCheckbox from "../EquipmentCheckbox/EquipmentCheckbox"; -import EquipmentItemDescription from "../EquipmentItemDescription/EquipmentItemDescription"; - -export default function EquipmentCheckboxGroup({ - category, - subCategory, - characterData, - onCheckboxCheck, - onAmountChange, -}: { - category: string; - subCategory?: string; - characterData: CharacterData; - onAmountChange: (item?: EquipmentItem) => void; - onCheckboxCheck: (item?: EquipmentItem) => void; -}) { - return ( - - {equipmentItems - .filter( - (categoryItem) => - categoryItem.category === category && - (!subCategory || categoryItem.subCategory === subCategory) - ) - .sort((a, b) => a.name.localeCompare(b.name)) - .map((categoryItem) => { - if ( - !equipmentItemIsDisabled( - characterData.class as ClassNames[], - characterData.race as RaceNames, - categoryItem - ) - ) { - return ( - invItem.name === categoryItem.name - )} - equipmentItemDescription={ - - } - inputDisabled={categoryItem.costValue > characterData.gold} - itemAmount={ - characterData.equipment.filter( - (invItem: EquipmentItem) => - invItem.name === categoryItem.name - )[0]?.amount - } - /> - ); - } else { - return null; - } - })} - - ); -} diff --git a/src/components/EquipmentStore/EquipmentInventory/EquipmentInventory.tsx b/src/components/EquipmentStore/EquipmentInventory/EquipmentInventory.tsx deleted file mode 100755 index d1c0ccf6..00000000 --- a/src/components/EquipmentStore/EquipmentInventory/EquipmentInventory.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Divider, List, Typography } from "antd"; -import { useMemo } from "react"; -import { toTitleCase } from "../../../support/stringSupport"; -import { getClassType } from "../../../support/helpers"; -import { - CharacterData, - ClassNames, - EquipmentItem, -} from "../../../data/definitions"; -import RenderEquipmentList from "./RenderEquipmentList/RenderEquipmentList"; - -export default function EquipmentInventory({ - className, - characterData, -}: { characterData: CharacterData } & React.ComponentPropsWithRef<"div">) { - const groupedEquipment = useMemo(() => { - return characterData.equipment.reduce( - (grouped: Record, item: EquipmentItem) => { - (grouped[item.category] = grouped[item.category] || []).push(item); - return grouped; - }, - {} - ); - }, [characterData.equipment]); - return ( -
- - Gold: {characterData.gold.toFixed(2)} | Weight:{" "} - {characterData.equipment - .reduce((total: number, item: EquipmentItem) => { - return total + (item.weight || 0) * item.amount; - }, 0) - .toFixed(2)} - - - Current Loadout - -
- {getClassType(characterData.class) !== "custom" && ( -
- {/* STARTING EQUIPMENT */} - -
- )} - {Object.entries(groupedEquipment).map( - ([category, categoryItems]: [any, any]) => ( -
- - {toTitleCase(category.replaceAll("-", " "))} - - } - bordered - dataSource={categoryItems.map( - (categoryItem: EquipmentItem) => ({ - name: categoryItem.name, - amount: categoryItem.amount, - }) - )} - renderItem={(item: EquipmentItem) => ( - - {item.name} - x{item.amount} - - )} - size="small" - /> -
- ) - )} -
-
- ); -} diff --git a/src/components/EquipmentStore/EquipmentInventory/RenderEquipmentList/RenderEquipmentList.tsx b/src/components/EquipmentStore/EquipmentInventory/RenderEquipmentList/RenderEquipmentList.tsx deleted file mode 100644 index c1819c6c..00000000 --- a/src/components/EquipmentStore/EquipmentInventory/RenderEquipmentList/RenderEquipmentList.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { List, Typography } from "antd"; -import { classes } from "../../../../data/classes"; -import { ClassNames, EquipmentItem } from "../../../../data/definitions"; - -export default function RenderEquipmentList({ - classNames, -}: { - classNames: ClassNames[]; -}) { - return classNames.map( - (classValue: ClassNames) => - classes[classValue].startingEquipment && ( - - Included w/ {classValue} - - } - bordered - dataSource={classes[classValue].startingEquipment?.map( - (item: EquipmentItem) => ({ - name: item.name, - amount: item.amount, - }) - )} - renderItem={(item) => ( - - {item.name} - x{item.amount} - - )} - size="small" - key={classValue} - /> - ) - ); -} diff --git a/src/components/EquipmentStore/EquipmentItemDescription/EquipmentItemDescription.tsx b/src/components/EquipmentStore/EquipmentItemDescription/EquipmentItemDescription.tsx deleted file mode 100644 index 72c180cf..00000000 --- a/src/components/EquipmentStore/EquipmentItemDescription/EquipmentItemDescription.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Descriptions, Typography } from "antd"; -import { EquipmentItem } from "../../../data/definitions"; - -export default function EquipmentItemDescription({ - item, -}: { - item: EquipmentItem; -}) { - return ( - <> - {item.name} - - - {`${item.costValue}${item.costCurrency}`} - - {item.weight && ( - {item.weight} - )} - {item.size && ( - {item.size} - )} - {item.AC && {item.AC}} - {item.damage && ( - {item.damage} - )} - - - ); -} diff --git a/src/components/EquipmentStore/EquipmentRadio/EquipmentRadio.tsx b/src/components/EquipmentStore/EquipmentRadio/EquipmentRadio.tsx deleted file mode 100755 index ea812dc7..00000000 --- a/src/components/EquipmentStore/EquipmentRadio/EquipmentRadio.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Radio } from "antd"; -import { EquipmentItem } from "../../../data/definitions"; -import { ReactElement } from "react"; - -type EquipmentRadioProps = { - item: EquipmentItem; - equipmentItemDescription: ReactElement; - disabled?: boolean; - inputDisabled: boolean; -}; - -export default function EquipmentRadio({ - item, - equipmentItemDescription, - disabled, - inputDisabled, -}: EquipmentRadioProps) { - return ( - - {equipmentItemDescription} - - ); -} diff --git a/src/components/EquipmentStore/EquipmentStore.tsx b/src/components/EquipmentStore/EquipmentStore.tsx old mode 100755 new mode 100644 index 7b7a55f4..4814c642 --- a/src/components/EquipmentStore/EquipmentStore.tsx +++ b/src/components/EquipmentStore/EquipmentStore.tsx @@ -1,212 +1,126 @@ -import EquipmentAccordion from "./EquipmentAccordion/EquipmentAccordion"; -import { useEffect, useState } from "react"; -import equipmentItems from "../../data/equipmentItems.json"; -import EquipmentInventory from "./EquipmentInventory/EquipmentInventory"; -import { useParams } from "react-router-dom"; -import { doc, updateDoc } from "firebase/firestore"; -import { db } from "../../firebase"; -import HomebrewWarning from "../HomebrewWarning/HomebrewWarning"; -import { getItemCost } from "../../support/formatSupport"; -import GoldRoller from "./GoldRoller/GoldRoller"; +import React from "react"; +import { EquipmentCategories, EquipmentItem } from "@/data/definitions"; import { - CharacterData, - ClassNames, - EquipmentItem, - RaceNames, - SetCharacterData, -} from "../../data/definitions"; - -type EquipmentStoreProps = { - characterData: CharacterData; - setCharacterData: SetCharacterData; - inBuilder?: boolean; -}; - -export default function EquipmentStore({ - characterData, - setCharacterData, - inBuilder, -}: EquipmentStoreProps) { - const [prevValue, setPrevValue] = useState(characterData.equipment); - const [goldInputValue, setGoldInputValue] = useState(characterData.gold); - - const { uid, id } = useParams(); - - const updateEquipment = async () => { - if (!uid || !id) { - console.error("User ID or Character ID is undefined"); - return; - } - - if (characterData.equipment !== prevValue) { - const docRef = doc(db, "users", uid, "characters", id); - - try { - await updateDoc(docRef, { - equipment: characterData.equipment, - gold: characterData.gold, - }); - setPrevValue(characterData.equipment); - } catch (error) { - console.error("Error updating document: ", error); - } - } - }; - - const inventoryChange = () => { - const newItems = characterData.equipment; - const oldItems = prevValue; - - let totalAddedCost = 0; - let totalRemovedCost = 0; - - newItems.forEach((newItem) => { - const oldItem = oldItems.find((item) => item.name === newItem.name); - - if (!oldItem) { - // The item is new, add its cost - totalAddedCost += getItemCost(newItem); - } else if (oldItem.amount !== newItem.amount) { - // The amount has changed, calculate the cost difference - const amountDifference = newItem.amount - oldItem.amount; - const costDifference = - amountDifference * getItemCost({ ...newItem, amount: 1 }); - - if (amountDifference > 0) { - // The amount has increased, add the cost difference - totalAddedCost += costDifference; - } else { - // The amount has decreased, subtract the cost difference - totalRemovedCost -= costDifference; - } - } - }); - - // Check for items that were removed entirely - oldItems.forEach((oldItem) => { - if (!newItems.find((item) => item.name === oldItem.name)) { - // The item was removed, subtract its cost - totalRemovedCost += getItemCost(oldItem); - } - }); - - const totalEquipmentWeight = characterData.equipment.reduce( - (total, equipmentItem) => { - return total + (equipmentItem.weight || 0) * equipmentItem.amount; - }, - 0 - ); - - let newGoldValue = characterData.gold; - - // Deduct the cost of added items and add the cost of removed items - newGoldValue -= totalAddedCost; - newGoldValue += totalRemovedCost; - - // Update characterData with new gold value - if (newGoldValue !== characterData.gold) { - setCharacterData({ - ...characterData, - gold: newGoldValue, - weight: totalEquipmentWeight, - }); - } - - setPrevValue(newItems); // Update previous equipment state - - // If this is not inside the Character Builder, update the character's equipment - if (!inBuilder) { - updateEquipment(); - } - }; + Alert, + Collapse, + CollapseProps, + Descriptions, + DescriptionsProps, + Flex, +} from "antd"; +import { + equipmentCategoryMap, + equipmentSubCategoryMap, +} from "@/support/equipmentSupport"; +import { slugToTitleCase } from "@/support/stringSupport"; +import EquipmentStoreItem from "./EquipmentStoreItem/EquipmentStoreItem"; +import { getItemCost } from "@/support/characterSupport"; + +interface EquipmentStoreProps { + equipment: EquipmentItem[]; + setEquipment: (equipment: EquipmentItem[]) => void; + gold: number; + setGold: (gold: number) => void; +} - const onCheckboxCheck = (item?: EquipmentItem, checked?: boolean) => { - if (checked) { - const updatedItems = characterData.equipment.concat(item || []); - setCharacterData({ ...characterData, equipment: updatedItems }); - } else { - const filteredItems = characterData.equipment.filter( - (equipmentItem) => equipmentItem.name !== item?.name - ); - setCharacterData({ ...characterData, equipment: filteredItems }); +const equipmentSymbolKeyItems: DescriptionsProps["items"] = [ + { + key: "1", + label: "**", + children: "This weapon only does subduing damage", + }, + { + key: "2", + label: "(E)", + children: "Entangling: This weapon may be used to snare or hold opponents.", + }, + { + key: "3", + label: "†", + children: "Silver tip or blade, for use against lycanthropes.", + }, +]; + +const EquipmentStore: React.FC< + EquipmentStoreProps & React.ComponentPropsWithRef<"div"> +> = ({ className, equipment, setEquipment, gold, setGold }) => { + const onChange = (value: number | null, item: EquipmentItem) => { + const newEquipment = equipment.filter((e) => e.name !== item.name); + if (!!value && value > 0) { + const newItem = { ...item, amount: value }; + newEquipment.push(newItem); } + const oldEquipmentCost = equipment + .reduce((acc, curr) => { + return acc + getItemCost(curr); + }, 0) + .toFixed(2); + const newEquipmentCost = newEquipment + .reduce((acc, curr) => { + return acc + getItemCost(curr); + }, 0) + .toFixed(2); + + setGold(+(gold + (+oldEquipmentCost - +newEquipmentCost)).toFixed(2)); + setEquipment(newEquipment); }; - const onAmountChange = (item?: EquipmentItem) => { - const filteredItems = characterData.equipment.filter( - (equipmentItem) => equipmentItem.name !== item?.name - ); - const updatedItems = filteredItems.concat(item || []); - setCharacterData({ ...characterData, equipment: updatedItems }); - }; - - const onRadioCheck = (item?: EquipmentItem) => { - const filteredItems = characterData.equipment.filter( - (equipmentItem) => - equipmentItem.name !== item?.name && - !equipmentItem.name.toLowerCase().includes("armor") && - !equipmentItem.name.toLowerCase().includes("mail") - ); - const updatedItems = filteredItems.concat(item || []); - setCharacterData({ ...characterData, equipment: updatedItems }); - }; - - // On page load, add "No Armor" - useEffect(() => { - if (inBuilder) { - // If this is inside the Character Builder, remove all armors and add "No Armor" - const filteredItems = characterData.equipment.filter( - (equipmentItem) => - !equipmentItem.name.toLowerCase().includes("armor") && - !equipmentItem.name.toLocaleLowerCase().includes("mail") && - !equipmentItem.name.toLowerCase().includes("shield") - ); - - const noArmor = equipmentItems.filter( - (equipmentItem) => equipmentItem.name.toLowerCase() === "no armor" - ); - const updatedItems = filteredItems.concat(noArmor); - - setCharacterData({ - ...characterData, - equipment: updatedItems, - }); - } - }, []); - - // On Equipment change, update gold and weight - useEffect(() => { - inventoryChange(); - }, [characterData.equipment]); - - // Update inputValue when characterData.gold changes - useEffect(() => { - setGoldInputValue(characterData.gold); - }, [characterData.gold]); - + const generalItems: CollapseProps["items"] = Object.entries( + equipmentSubCategoryMap(), + ).map((category, index) => { + return { + key: (index + 1).toString(), + label: slugToTitleCase(category[0]), + children: ( + + {category[1].map((item: EquipmentItem, itemIndex: number) => ( + onChange(e ? +e : 0, item)} + // disabled={item.amount === 0 && getItemCost(item) >= gold} + // max={getItemCost(item) >= gold} + gold={gold} + /> + ))} + + ), + }; + }); + + const items: CollapseProps["items"] = Object.entries(equipmentCategoryMap()) + .sort() + .map((category, index) => ({ + key: index + 1 + "", + label: slugToTitleCase(category[0]), + children: ( + + {category[0] === EquipmentCategories.GENERAL ? ( + + ) : ( + category[1].map((item: EquipmentItem, index: number) => ( + onChange(e ? +e : 0, item)} + // disabled={item.amount === 0 && getItemCost(item) >= gold} + // max={getItemCost(item) >= gold} + gold={gold} + /> + )) + )} + + ), + })); return ( - <> - {!Object.values(RaceNames).includes(characterData.race as RaceNames) && - !characterData.class.some((className) => - Object.values(ClassNames).includes(className as ClassNames) - ) && } -
- {inBuilder && ( - - )} - - -
- + + + } + /> + ); -} +}; + +export default EquipmentStore; diff --git a/src/components/EquipmentStore/EquipmentStoreItem/EquipmentStoreItem.tsx b/src/components/EquipmentStore/EquipmentStoreItem/EquipmentStoreItem.tsx new file mode 100644 index 00000000..e151a3c5 --- /dev/null +++ b/src/components/EquipmentStore/EquipmentStoreItem/EquipmentStoreItem.tsx @@ -0,0 +1,83 @@ +import React from "react"; +import { EquipmentItem } from "@/data/definitions"; +import { Descriptions, DescriptionsProps, InputNumber } from "antd"; +import { getItemCost } from "@/support/characterSupport"; + +interface EquipmentStoreItemProps { + item: EquipmentItem; + onChange: ((value: number | null) => void) | undefined; + disabled?: boolean; + gold: number; +} + +const EquipmentStoreItem: React.FC< + EquipmentStoreItemProps & React.ComponentPropsWithRef<"div"> +> = ({ className, item, onChange, disabled, gold }) => { + const maxItemsAffordable = Math.floor(gold / getItemCost(item)); + const damageItem = { + key: "damage", + label: "Damage", + children: item.damage, + }; + const sizeItem = { key: "size", label: "Size", children: item.size }; + const weightItem = { + key: "weight", + label: "Weight", + children: item.weight, + }; + const acItem = { key: "ac", label: "AC", children: item.AC }; + const missileACItem = { + key: "missileAC", + label: "Missile AC", + children: item.missileAC, + }; + const rangeItem = { + key: "range", + label: "Range", + children: item.range?.join(" / "), + }; + const ammoItem = { + key: "ammo", + label: "Ammo", + children: {item.ammo?.join(", ")}, + }; + const items: DescriptionsProps["items"] = [ + { + key: "name", + label: "Name", + children: {item.name}, + }, + { + key: "cost", + label: "Cost", + children: item.costValue + " " + item.costCurrency, + }, + ]; + + if (item.weight) items.push(weightItem); + if (item.size) items.push(sizeItem); + if (item.damage) items.push(damageItem); + if (item.AC) items.push(acItem); + if (item.missileAC) items.push(missileACItem); + if (item.range) items.push(rangeItem); + if (item.ammo) items.push(ammoItem); + items.push({ + key: "amount", + label: "Amount", + children: ( + onChange && onChange(value)} + disabled={disabled} + /> + ), + }); + + return ( + + ); +}; + +export default EquipmentStoreItem; diff --git a/src/components/EquipmentStore/GoldRoller/GoldRoller.tsx b/src/components/EquipmentStore/GoldRoller/GoldRoller.tsx deleted file mode 100755 index 7b56a1ac..00000000 --- a/src/components/EquipmentStore/GoldRoller/GoldRoller.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Button, InputNumber, Space } from "antd"; -import { DiceRoller } from "@dice-roller/rpg-dice-roller"; -import { CharacterData, SetCharacterData } from "../../../data/definitions"; - -type GoldRollerProps = { - characterData: CharacterData; - setCharacterData: SetCharacterData; - goldInputValue: number; - setGoldInputValue: (goldInputValue: number) => void; -}; - -const roller = new DiceRoller(); - -export default function GoldRoller({ - characterData, - setCharacterData, - goldInputValue, - setGoldInputValue, -}: GoldRollerProps) { - // Update inputValue when typing in the InputNumber field - const handleGoldInputChange = (value: number | null) => { - setGoldInputValue(value || 0); - setCharacterData({ ...characterData, gold: value || 0 }); - }; - - return ( - - event.target.select()} - onChange={handleGoldInputChange} - /> - - - ); -} diff --git a/src/components/GameCard/GameCard.tsx b/src/components/GameCard/GameCard.tsx deleted file mode 100644 index b8257c51..00000000 --- a/src/components/GameCard/GameCard.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from "react"; -import { GameData } from "../../data/definitions"; -import { Card, Popconfirm } from "antd"; -import { DeleteOutlined, SolutionOutlined } from "@ant-design/icons"; -import { useNavigate } from "react-router-dom"; -import { User } from "firebase/auth"; - -interface GameCardProps { - gameData: GameData; - user: User | null; - confirm: (gameId: string) => Promise; -} - -const commonCardStyles = { - backgroundColor: "#e2e8f0", - border: "1px solid rgba(62,53,67, 0.15)", - borderBottomWidth: "2px", -}; - -const GameCard: React.FC = ({ gameData, user }) => { - const navigate = useNavigate(); - return ( - - {gameData.name} - - } - headStyle={{ - ...commonCardStyles, - borderRadius: "0.5rem 0.5rem 0 0", - }} - bodyStyle={{ - ...commonCardStyles, - borderTop: "none", - borderBottom: "none", - borderRadius: "0", - }} - actions={[ - navigate(`/u/${user?.uid}/g/${gameData.id}`)} - title="Go to GM Screen" - aria-label="Go to GM Screen" - />, - gameData?.id && confirm(gameData.id)} - okText="Yes" - cancelText="No" - > - - , - ]} - > - Players: {gameData.players?.length || 0} - - ); -}; - -export default GameCard; diff --git a/src/components/GameSheet/AddPlayerForm/AddPlayerForm.tsx b/src/components/GameSheet/AddPlayerForm/AddPlayerForm.tsx deleted file mode 100644 index 56084c58..00000000 --- a/src/components/GameSheet/AddPlayerForm/AddPlayerForm.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React from "react"; -import { Form, Input, Button, message, Typography } from "antd"; -import DOMPurify from "dompurify"; -import { doc, getDoc, setDoc } from "firebase/firestore"; -import { db } from "../../../firebase"; -import { PlayerListObject } from "../../../data/definitions"; - -interface AddPlayerFormProps { - players: PlayerListObject[]; - setPlayers: (players: PlayerListObject[]) => void; - gmId: string; - gameId: string; -} - -async function updateGameWithNewPlayer( - gameId: string, - userId: string, - newPlayer: PlayerListObject -) { - const gameDocRef = doc(db, `users/${userId}/games/${gameId}`); - const gameDoc = await getDoc(gameDocRef); - - if (gameDoc.exists()) { - const gameData = gameDoc.data(); - const updatedPlayers = [...(gameData?.players || []), newPlayer]; - await setDoc(gameDocRef, { ...gameData, players: updatedPlayers }); - } else { - console.error("Game does not exist"); - } -} - -const AddPlayerForm: React.FC = ({ - players = [], - setPlayers, - gmId, - gameId, -}) => { - const [form] = Form.useForm(); - - const onFinish = async (values: any) => { - const sanitizedURL = DOMPurify.sanitize(values.url); - const regex = /\/u\/([a-zA-Z0-9]+)\/c\/([a-zA-Z0-9]+)/; - const match = sanitizedURL.match(regex); - - if (match) { - const userId = match[1]; - const characterId = match[2]; - - const docRef = doc(db, `users/${userId}/characters/${characterId}`); - const docSnap = await getDoc(docRef); - - if (docSnap.exists()) { - const characterData = { user: userId, character: characterId }; - if (Array.isArray(players)) { - setPlayers([...players, characterData]); - // Assuming you have the gameId - updateGameWithNewPlayer(gameId, gmId, characterData); - } else { - console.error("Players is not an array:", players); - } - message.success("Character added successfully."); - form.resetFields(); - } else { - message.error("No such character exists."); - } - } else { - message.error("Invalid URL format."); - } - }; - - return ( -
- -
- - - Enter a codex.quest character URL to add a player's character. - -
-
- - - - - - ); -}; - -export default AddPlayerForm; diff --git a/src/components/GameSheet/Clipboard/Clipboard.tsx b/src/components/GameSheet/Clipboard/Clipboard.tsx deleted file mode 100644 index 1b64ea50..00000000 --- a/src/components/GameSheet/Clipboard/Clipboard.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -interface ClipboardProps { - // define your prop types here -} - -const Clipboard: React.FC = ({}) => { - return
Clipboard: All the data a GM will need! coming soon!
; -}; - -export default Clipboard; diff --git a/src/components/GameSheet/PlayerList/PlayerList.tsx b/src/components/GameSheet/PlayerList/PlayerList.tsx deleted file mode 100644 index c52ec7c0..00000000 --- a/src/components/GameSheet/PlayerList/PlayerList.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { CharacterData, PlayerListObject } from "../../../data/definitions"; -import PlayerStats from "../PlayerStats/PlayerStats"; -import AddPlayerForm from "../AddPlayerForm/AddPlayerForm"; -import { db } from "../../../firebase"; -import { doc, getDoc } from "firebase/firestore"; -import { get } from "http"; - -type PlayerListProps = { - players: PlayerListObject[]; - setPlayers: (players: PlayerListObject[]) => void; - gameId: string; - userId: string; -}; - -const getCharacterData = async (userId: string, characterId: string) => { - const docRef = doc(db, `users/${userId}/characters/${characterId}`); - const docSnap = await getDoc(docRef); - - if (docSnap.exists()) { - return docSnap.data() as CharacterData; - } else { - console.error("No such document!"); - } -}; - -const PlayerList: React.FC = ({ - players, - setPlayers, - gameId, - userId, -}) => { - const [characterDataList, setCharacterDataList] = useState( - [] - ); - - useEffect(() => { - const fetchAllCharacterData = async () => { - const fetchedData: CharacterData[] = []; - for (const player of players) { - const data = await getCharacterData(player.user, player.character); - if (data) { - fetchedData.push(data); - } - } - setCharacterDataList(fetchedData); - }; - - fetchAllCharacterData(); - }, [players]); - - return ( -
-
- {characterDataList.map((characterData, index) => ( - - ))} -
- -
- ); -}; - -export default PlayerList; diff --git a/src/components/GameSheet/PlayerStats/PlayerStats.tsx b/src/components/GameSheet/PlayerStats/PlayerStats.tsx deleted file mode 100644 index d7c64bff..00000000 --- a/src/components/GameSheet/PlayerStats/PlayerStats.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from "react"; -import { Abilities, CharacterData } from "../../../data/definitions"; -import { Button, Card, Descriptions, Typography } from "antd"; -import { openInNewTab } from "../../../support/helpers"; - -interface PlayerStatsProps { - player: CharacterData | undefined; - userId: string; - characterId: string; -} - -const PlayerStats: React.FC = ({ - player, - userId, - characterId, -}) => { - return ( - <> - {player && ( - openInNewTab(`/u/${userId}/c/${characterId}`)} - > - Sheet - - } - > - -
- {Object.entries(player.abilities.scores).map((ability) => { - const key = ability[0] as keyof Abilities; - return ( -
- - {key.slice(0, 3)} - - - {ability[1]} - - - {player.abilities.modifiers[key]} - -
- ); - })} -
-
- )} - - ); -}; - -export default PlayerStats; diff --git a/src/components/HelpTooltip/HelpTooltip.tsx b/src/components/HelpTooltip/HelpTooltip.tsx old mode 100755 new mode 100644 index 28cdf3e6..0e9699a1 --- a/src/components/HelpTooltip/HelpTooltip.tsx +++ b/src/components/HelpTooltip/HelpTooltip.tsx @@ -1,16 +1,21 @@ import { Tooltip } from "antd"; -import { QuestionCircleOutlined } from "@ant-design/icons"; -import classNames from "classnames"; +import React from "react"; import { marked } from "marked"; +import classNames from "classnames"; +import { QuestionCircleOutlined } from "@ant-design/icons"; +import { ColorScheme } from "@/support/colorSupport"; -export default function HelpTooltip({ - text, - className, -}: { text: string } & React.ComponentPropsWithRef<"div">) { +interface HelpTooltipProps { + text: string; +} + +const HelpTooltip: React.FC< + HelpTooltipProps & React.ComponentPropsWithRef<"div"> +> = ({ className, text }) => { const tooltipClassNames = classNames( className, "print:hidden", - "cursor-help" + "cursor-help", ); return ( } - color="#3E3643" + color={ColorScheme.SHIPGRAY} > - + ); -} +}; + +export default HelpTooltip; diff --git a/src/components/HomebrewWarning/HomebrewWarning.tsx b/src/components/HomebrewWarning/HomebrewWarning.tsx old mode 100755 new mode 100644 index 74ef4980..73635be0 --- a/src/components/HomebrewWarning/HomebrewWarning.tsx +++ b/src/components/HomebrewWarning/HomebrewWarning.tsx @@ -1,16 +1,22 @@ import { Typography } from "antd"; +import React from "react"; -export default function HomebrewWarning({ - homebrew, - className, -}: { homebrew: string } & React.ComponentPropsWithRef<"div">) { +interface HomebrewWarningProps { + homebrew: string; +} + +const HomebrewWarning: React.FC< + HomebrewWarningProps & React.ComponentPropsWithRef<"div"> +> = ({ homebrew, className }) => { return ( Work closely with your GM when using a custom {homebrew}. ); -} +}; + +export default HomebrewWarning; diff --git a/src/components/ModalAttack/AmmoAttackForm/AmmoAttackForm.tsx b/src/components/ModalAttack/AmmoAttackForm/AmmoAttackForm.tsx new file mode 100644 index 00000000..83eed5e2 --- /dev/null +++ b/src/components/ModalAttack/AmmoAttackForm/AmmoAttackForm.tsx @@ -0,0 +1,165 @@ +import { CharData, EquipmentItem } from "@/data/definitions"; +import React from "react"; +import AttackForm from "../AttackForm/AttackForm"; +import { Checkbox, Flex, Form, Select, Typography } from "antd"; +import { CheckboxChangeEvent } from "antd/es/checkbox"; +import { + getAvailableAmmoOptions, + getAvailableAmmoSelectOptions, + noAmmoMessage, + sendAmmoAttackNotifications, +} from "../ModalAttackSupport"; +import { useAttack } from "@/hooks/useAttack"; + +interface AmmoAttackFormProps { + character: CharData; + setCharacter: (character: CharData) => void; + item: EquipmentItem; + equipment: EquipmentItem[]; + setModalIsOpen: (modalIsOpen: boolean) => void; +} + +const AmmoAttackForm: React.FC< + AmmoAttackFormProps & React.ComponentPropsWithRef<"div"> +> = ({ + className, + item, + equipment, + setModalIsOpen, + character, + setCharacter, +}) => { + // HOOKS + const { + range, + setRange, + contextHolder, + openNotification, + getRangeOptions, + handleRangeChange, + updateEquipmentAfterMissileAttack, + calculateMissileRollResults, + } = useAttack(); + const [ammoSelection, setAmmoSelection] = React.useState( + undefined, + ); + const [ammoSelectionMessage, setAmmoSelectionMessage] = + React.useState(noAmmoMessage); + const [isRecoveryChecked, setIsRecoveryChecked] = + React.useState(false); + // VARS + const rangeOptions = getRangeOptions(item.range); + const availableAmmoOptions = React.useMemo( + () => getAvailableAmmoOptions(item.ammo, equipment) ?? [], + [item, equipment], + ); + const availableAmmoSelectOptions = React.useMemo( + () => getAvailableAmmoSelectOptions(availableAmmoOptions) ?? [], + [availableAmmoOptions], + ); + // FUNCTIONS + const resetFormState = () => { + setRange(undefined); + setAmmoSelection(undefined); + setAmmoSelectionMessage(noAmmoMessage); + setIsRecoveryChecked(false); + setModalIsOpen(false); + }; + const onFinish = () => { + const weaponRecovered = updateEquipmentAfterMissileAttack( + ammoSelection, + equipment, + isRecoveryChecked, + character, + setCharacter, + ); + const { rollToHit, rollToDamage } = calculateMissileRollResults( + character, + range, + equipment, + ammoSelection, + ); + sendAmmoAttackNotifications( + weaponRecovered, + rollToHit, + rollToDamage, + openNotification, + ammoSelection, + item, + ); + resetFormState(); + }; + const handleSelectChange = (value: string) => { + setAmmoSelection(value); + setAmmoSelectionMessage(value); + }; + const handleRecoveryChange = (e: CheckboxChangeEvent) => { + setIsRecoveryChecked(e.target.checked); + }; + // EFFECTS + React.useEffect(() => { + if (availableAmmoOptions.length > 1) { + !ammoSelection && setAmmoSelectionMessage("No ammunition selected"); + } + if (availableAmmoOptions.length === 1) { + setAmmoSelection(availableAmmoOptions[0].name); + setAmmoSelectionMessage(availableAmmoOptions[0].name); + } + if (!availableAmmoOptions) { + setAmmoSelection(undefined); + setAmmoSelectionMessage(noAmmoMessage); + } + }, [ammoSelection, availableAmmoOptions]); + // JSX + return ( + <> + {contextHolder} + + Using: + + {!availableAmmoOptions?.length + ? "No ammunition available!" + : ammoSelectionMessage} + + + + {availableAmmoOptions?.length > 1 && ( + + + + )} + + + 25% chance to recover ammo + + + + + ); +}; + +export default AmmoAttackForm; diff --git a/src/components/ModalAttack/AttackForm/AttackForm.tsx b/src/components/ModalAttack/AttackForm/AttackForm.tsx new file mode 100644 index 00000000..c79af4ca --- /dev/null +++ b/src/components/ModalAttack/AttackForm/AttackForm.tsx @@ -0,0 +1,34 @@ +import { Button, Form } from "antd"; +import React from "react"; + +interface AttackFormProps { + onFinish: (values: any) => void; + submitDisabled?: boolean; + name: string; + initialValues?: any; +} + +const AttackForm: React.FC< + AttackFormProps & React.ComponentPropsWithRef<"div"> +> = ({ className, onFinish, children, submitDisabled, name }) => { + const [form] = Form.useForm(); + return ( +
+ {children} + + + + + ); +}; + +export default AttackForm; diff --git a/src/components/ModalAttack/MeleeAttackForm/MeleeAttackForm.tsx b/src/components/ModalAttack/MeleeAttackForm/MeleeAttackForm.tsx new file mode 100644 index 00000000..1d00a3bb --- /dev/null +++ b/src/components/ModalAttack/MeleeAttackForm/MeleeAttackForm.tsx @@ -0,0 +1,50 @@ +import { CharData, EquipmentItem } from "@/data/definitions"; +import React from "react"; +import AttackForm from "../AttackForm/AttackForm"; +import { useAttack } from "@/hooks/useAttack"; +import { getRollToHitResult } from "../ModalAttackSupport"; +import { rollDice } from "@/support/characterSupport"; + +interface MeleeAttackFormProps { + character: CharData; + item: EquipmentItem; + setModalIsOpen: (modalIsOpen: boolean) => void; +} + +const MeleeAttackForm: React.FC< + MeleeAttackFormProps & React.ComponentPropsWithRef<"div"> +> = ({ className, character, item, setModalIsOpen }) => { + const { contextHolder, openNotification } = useAttack(); + + const onFinish = () => { + // Melee to-hit roll: 1d20 + attack bonus + strength modifier + const rollToHit = getRollToHitResult(character); + // Assuming melee damage is on the item and includes the character's strength modifier + const rollToDamage = rollDice(item.damage || ""); + + // Handle notifications + openNotification( + `Attack with ${item.name}`, + `To-hit: ${rollToHit.total} Damage: ${rollToDamage}`, + ); + + // Reset modal state + setModalIsOpen(false); + }; + + return ( + <> + {contextHolder} + + {/* Additional form items for melee attack could be placed here */} + + + ); +}; + +export default MeleeAttackForm; diff --git a/src/components/ModalAttack/ModalAttack.tsx b/src/components/ModalAttack/ModalAttack.tsx new file mode 100644 index 00000000..7d199069 --- /dev/null +++ b/src/components/ModalAttack/ModalAttack.tsx @@ -0,0 +1,82 @@ +import { CharData, EquipmentItem } from "@/data/definitions"; +import { Flex, Switch } from "antd"; +import React from "react"; +import ThrownAttackForm from "./ThrownAttackForm/ThrownAttackForm"; +import AmmoAttackForm from "./AmmoAttackForm/AmmoAttackForm"; +import MeleeAttackForm from "./MeleeAttackForm/MeleeAttackForm"; + +interface ModalAttackProps { + character: CharData; + setCharacter: (character: CharData) => void; + item: EquipmentItem; + equipment: EquipmentItem[]; + setModalIsOpen: (modalIsOpen: boolean) => void; +} + +const ModalAttack: React.FC< + ModalAttackProps & React.ComponentPropsWithRef<"div"> +> = ({ + className, + item, + equipment, + setModalIsOpen, + character, + setCharacter, +}) => { + const [attackSwitch, setAttackWitch] = React.useState(false); + console.error("TODOS!"); + return ( + + {item.type === "both" && ( + setAttackWitch(checked)} + className="self-baseline" + /> + )} + {item.type === "missile" || (item.type === "both" && attackSwitch) ? ( + <> + {item.ammo ? ( + + ) : ( + + )} + + ) : ( + + )} + {/* + TODO: + x get item + x determine if melee or missile + x determine if thrown or ammo + determine if ammo is available + select ammo + if thrown, item is gone + if ammo, 75% chance of being gone + when closing modal, reset states (ex: range) + */} + + ); +}; + +export default ModalAttack; diff --git a/src/components/ModalAttack/ModalAttackSupport.tsx b/src/components/ModalAttack/ModalAttackSupport.tsx new file mode 100644 index 00000000..1dac7ad0 --- /dev/null +++ b/src/components/ModalAttack/ModalAttackSupport.tsx @@ -0,0 +1,122 @@ +import { CharData, EquipmentItem } from "@/data/definitions"; +import { getAttackBonus, rollDice } from "@/support/characterSupport"; +import { DiceRoller } from "@dice-roller/rpg-dice-roller"; + +export const noAmmoMessage = "No ammunition available"; + +export const getAvailableAmmoOptions = ( + ammo: EquipmentItem["ammo"], + equipment: EquipmentItem[], +) => + ammo?.flatMap((ammo: string) => { + return equipment.filter((item) => item.name === ammo); + }); + +export const getAvailableAmmoSelectOptions = ( + options: EquipmentItem[] | undefined, +) => + options?.map((ammo) => ({ + label: `${ammo.name} (${ammo.amount})`, + value: ammo.name, + })); + +const roller = new DiceRoller(); + +const checkCriticalFailure = (rollResult: any) => { + return rollResult.rolls[0].rolls[0].modifierFlags === "__"; +}; + +const checkCriticalSuccess = (rollResult: any) => { + return rollResult.rolls[0].rolls[0].modifierFlags === "**"; +}; + +export const getRollToHitResult = ( + character: CharData, + type: "melee" | "missile" = "melee", + range: null | undefined | string = null, +) => { + // Perform the roll + let rangeModifier = 0; + if (range === "S") { + rangeModifier = 1; + } else if (range === "M") { + rangeModifier = 0; + } else if (range === "L") { + rangeModifier = -2; + } + let rollResult; + if (type === "melee") { + rollResult = roller.roll( + `1d20cfcs+${getAttackBonus(character)}${ + character.abilities.modifiers.strength + }`, + ); + } else { + rollResult = roller.roll( + `1d20cfcs+${getAttackBonus(character)}${ + character.abilities.modifiers.dexterity + }${rangeModifier ? `+${rangeModifier}` : ""}`, + ); + } + + // Check for critical failure or success + if (checkCriticalFailure(rollResult)) { + return { total: "1 (Critical Failure)" }; + } else if (checkCriticalSuccess(rollResult)) { + return { total: "20 (Critical Success)" }; + } + + return rollResult; +}; + +export const getRollToThrownDamageResult = ( + equipment: EquipmentItem[], + ammoSelection: string | undefined, + character: CharData, +) => { + const weapon = getWeapon(ammoSelection ?? "", equipment); + let { damage } = weapon ?? { damage: "" }; + damage += character.abilities.modifiers.strength + ""; + return rollDice(damage ?? ""); +}; + +export const getRollToAmmoDamageResult = ( + equipment: EquipmentItem[], + ammoSelection: string | undefined, + character: CharData, +) => { + const weapon = getWeapon(ammoSelection ?? "", equipment); + let { damage } = weapon ?? { damage: "" }; + if ( + weapon?.name.toLowerCase().includes("bullet") || + weapon?.name.toLowerCase().includes("stone") + ) { + damage += character.abilities.modifiers.strength + ""; + } + return rollDice(damage ?? ""); +}; + +export const getWeapon = (name: string, equipment: EquipmentItem[]) => + equipment.find((e) => e.name === name); + +// export const calculateThrownRollResults = () + +export const sendAmmoAttackNotifications = ( + weaponRecovered: boolean, + rollToHit: any, + rollToDamage: any, + openNotification: any, + ammoSelection: string | undefined, + item: EquipmentItem, +) => { + if (weaponRecovered) { + openNotification( + "Ammo recovered", + `You recovered 1 ${ammoSelection} in the attack`, + ); + } + openNotification( + `Attack with ${item.name}`, + `To-hit: ${rollToHit.total} Damage: ${rollToDamage}`, + ); +}; diff --git a/src/components/ModalAttack/ThrownAttackForm/ThrownAttackForm.tsx b/src/components/ModalAttack/ThrownAttackForm/ThrownAttackForm.tsx new file mode 100644 index 00000000..dd0ebacc --- /dev/null +++ b/src/components/ModalAttack/ThrownAttackForm/ThrownAttackForm.tsx @@ -0,0 +1,96 @@ +import React from "react"; +import { CharData, EquipmentItem } from "@/data/definitions"; +import AttackForm from "../AttackForm/AttackForm"; +import { useAttack } from "@/hooks/useAttack"; +import { Flex, Form, Select, Typography } from "antd"; +import { sendAmmoAttackNotifications } from "../ModalAttackSupport"; + +interface ThrownAttackFormProps { + item: EquipmentItem; + setModalIsOpen: (modalIsOpen: boolean) => void; + character: CharData; + setCharacter: (character: CharData) => void; + equipment: EquipmentItem[]; +} + +const ThrownAttackForm: React.FC< + ThrownAttackFormProps & React.ComponentPropsWithRef<"div"> +> = ({ + className, + item, + setModalIsOpen, + equipment, + character, + setCharacter, +}) => { + const { + range, + setRange, + contextHolder, + openNotification, + getRangeOptions, + handleRangeChange, + updateEquipmentAfterMissileAttack, + calculateMissileRollResults, + } = useAttack(); + const ammoSelection = item.name; + const rangeOptions = getRangeOptions(item.range); + const resetFormState = () => { + setRange(undefined); + setModalIsOpen(false); + }; + const onFinish = () => { + const weaponRecovered = updateEquipmentAfterMissileAttack( + ammoSelection, + equipment, + false, + character, + setCharacter, + ); + const { rollToHit, rollToDamage } = calculateMissileRollResults( + character, + range, + equipment, + ammoSelection, + true, + ); + sendAmmoAttackNotifications( + weaponRecovered, + rollToHit, + rollToDamage, + openNotification, + ammoSelection, + item, + ); + resetFormState(); + }; + return ( + <> + {contextHolder} + + Using: + {item.name} + + + {item.range && ( + + + + ); +}; + +export default Ammo; diff --git a/src/components/ModalCustomEquipment/Amount/Amount.tsx b/src/components/ModalCustomEquipment/Amount/Amount.tsx new file mode 100644 index 00000000..4fe6f0be --- /dev/null +++ b/src/components/ModalCustomEquipment/Amount/Amount.tsx @@ -0,0 +1,14 @@ +import { Form, InputNumber } from "antd"; +import React from "react"; + +const Amount: React.FC> = ({ + className, +}) => { + return ( + + + + ); +}; + +export default Amount; diff --git a/src/components/ModalCustomEquipment/ArmorClass/ArmorClass.tsx b/src/components/ModalCustomEquipment/ArmorClass/ArmorClass.tsx new file mode 100644 index 00000000..4f389116 --- /dev/null +++ b/src/components/ModalCustomEquipment/ArmorClass/ArmorClass.tsx @@ -0,0 +1,19 @@ +import { Form, InputNumber } from "antd"; +import React from "react"; + +const ArmorClass: React.FC> = ({ + className, +}) => { + return ( + + + + ); +}; + +export default ArmorClass; diff --git a/src/components/ModalCustomEquipment/AttackType/AttackType.tsx b/src/components/ModalCustomEquipment/AttackType/AttackType.tsx new file mode 100644 index 00000000..0a8e24ff --- /dev/null +++ b/src/components/ModalCustomEquipment/AttackType/AttackType.tsx @@ -0,0 +1,46 @@ +import { Form, Select } from "antd"; +import React from "react"; +import { AttackTypes } from "@/support/stringSupport"; + +interface AttackTypeProps { + disabled?: boolean; +} + +const attackOptions = [ + { + value: AttackTypes.MELEE, + label: + AttackTypes.MELEE.charAt(0).toUpperCase() + AttackTypes.MELEE.slice(1), + }, + { + value: AttackTypes.MISSILE, + label: + AttackTypes.MISSILE.charAt(0).toUpperCase() + + AttackTypes.MISSILE.slice(1), + }, + { + value: AttackTypes.BOTH, + label: AttackTypes.BOTH.charAt(0).toUpperCase() + AttackTypes.BOTH.slice(1), + }, +]; + +const AttackType: React.FC< + AttackTypeProps & React.ComponentPropsWithRef<"div"> +> = ({ className, disabled }) => { + return ( + + + + ); +}; + +export default Category; diff --git a/src/components/ModalCustomEquipment/Cost/Cost.tsx b/src/components/ModalCustomEquipment/Cost/Cost.tsx new file mode 100644 index 00000000..f37716ac --- /dev/null +++ b/src/components/ModalCustomEquipment/Cost/Cost.tsx @@ -0,0 +1,25 @@ +import { Form, InputNumber, Select, SelectProps, Space } from "antd"; +import React from "react"; +import { CURRENCIES } from "@/support/characterSupport"; +import { CostCurrency } from "@/data/definitions"; + +const currencyOptions: SelectProps["options"] = CURRENCIES.map( + (currency) => ({ value: currency, label: currency.toUpperCase() }), +); + +const Cost: React.FC> = ({ className }) => { + return ( + + + + + + + + + ); +}; + +export default Damage; diff --git a/src/components/ModalCustomEquipment/MissileAc/MissileAc.tsx b/src/components/ModalCustomEquipment/MissileAc/MissileAc.tsx new file mode 100644 index 00000000..b18a2c73 --- /dev/null +++ b/src/components/ModalCustomEquipment/MissileAc/MissileAc.tsx @@ -0,0 +1,27 @@ +import { Form, InputNumber } from "antd"; +import React from "react"; + +interface MissileAcProps { + missileAcInputNumber: number; + handleMissileAcChange: (value: number) => void; +} + +const MissileAc: React.FC< + MissileAcProps & React.ComponentPropsWithRef<"div"> +> = ({ className, handleMissileAcChange, missileAcInputNumber }) => { + return ( + + handleMissileAcChange(e as number)} + /> + + ); +}; + +export default MissileAc; diff --git a/src/components/ModalCustomEquipment/ModalCustomEquipment.tsx b/src/components/ModalCustomEquipment/ModalCustomEquipment.tsx new file mode 100644 index 00000000..2414a220 --- /dev/null +++ b/src/components/ModalCustomEquipment/ModalCustomEquipment.tsx @@ -0,0 +1,253 @@ +import { Button, Flex, Form } from "antd"; +import classNames from "classnames"; +import React from "react"; +import Cost from "./Cost/Cost"; +import Purchased from "./Purchased/Purchased"; +import Name from "./Name/Name"; +import Category from "./Category/Category"; +import Amount from "./Amount/Amount"; +import Weight from "./Weight/Weight"; +import Size from "./Size/Size"; +import Damage from "./Damage/Damage"; +import MissileAc from "./MissileAc/MissileAc"; +import ArmorClass from "./ArmorClass/ArmorClass"; +import AttackType from "./AttackType/AttackType"; +import Range from "./Range/Range"; +import Ammo from "./Ammo/Ammo"; +import { + CharData, + EquipmentCategories, + EquipmentItem, +} from "@/data/definitions"; +import SubCategory from "./SubCategory/SubCategory"; + +interface ModalCustomEquipmentProps { + character: CharData; + setCharacter: (character: CharData) => void; + setModalIsOpen: (modalIsOpen: boolean) => void; +} + +type CategoryFieldMappings = { + [key in EquipmentCategories]?: string[]; +}; + +const categoryFieldMapping: CategoryFieldMappings = { + [EquipmentCategories.AMMUNITION]: ["Damage"], + [EquipmentCategories.ARMOR]: ["ArmorClass"], + [EquipmentCategories.AXES]: ["Size", "Damage", "AttackType", "Range"], + [EquipmentCategories.BARDING]: ["AnimalWeight", "ArmorClass"], + [EquipmentCategories.BOWS]: ["Size", "Ammo", "AttackType", "Range"], + [EquipmentCategories.CHAINFLAIL]: ["Size", "Damage", "AttackType"], + [EquipmentCategories.DAGGERS]: ["Size", "Damage", "AttackType", "Range"], + [EquipmentCategories.GENERAL]: ["SubCategory"], + [EquipmentCategories.HAMMERMACE]: ["Size", "Damage", "AttackType", "Range"], + [EquipmentCategories.IMPROVISED]: ["Size", "Damage", "AttackType", "Range"], + [EquipmentCategories.OTHERWEAPONS]: ["Size", "Damage", "AttackType", "Range"], + [EquipmentCategories.SHIELDS]: [ + "ArmorClass", + "MissileAc", + "AttackType", + "Range", + ], + [EquipmentCategories.SLINGHURLED]: ["Size", "Ammo", "AttackType", "Range"], + [EquipmentCategories.SPEARSPOLES]: [ + "Size", + "Damage", + "TwoHandedDamage", + "AttackType", + "Range", + ], + [EquipmentCategories.SWORDS]: ["Size", "Damage", "AttackType"], +}; + +const onFinishFailed = (errorInfo: object) => { + console.error("Failed:", errorInfo); +}; + +const ModalCustomEquipment: React.FC< + ModalCustomEquipmentProps & React.ComponentPropsWithRef<"div"> +> = ({ className, character, setCharacter, setModalIsOpen }) => { + const [form] = Form.useForm(); + const [categorySelect, setCategorySelect] = React.useState< + EquipmentCategories | "" + >(""); + const [purchasedCheckbox, setPurchasedCheckbox] = + React.useState(false); + const [missileAcInputNumber, setMissileAcInputNumber] = + React.useState(0); + + const [rangeArray, setRangeArray] = React.useState<[number, number, number]>([ + 0, 0, 0, + ]); + const [ammoSelect, setAmmoSelect] = React.useState( + undefined, + ); + const [showRange, setShowRange] = React.useState(false); + const [attackTypeDisabled, setAttackTypeDisabled] = + React.useState(false); + + const customEquipmentClassNames = classNames( + "flex", + "flex-col", + "gap-4", + className, + ); + + const handleCategoryChange = (value: string) => { + if ( + Object.values(EquipmentCategories).includes(value as EquipmentCategories) + ) { + setCategorySelect(value as EquipmentCategories); + } else { + // Handle the case where the value is not part of the EquipmentCategories, + setCategorySelect(""); + } + }; + + const handlePurchasedChange = (checked: boolean) => { + setPurchasedCheckbox(checked); + }; + + const handleMissileAcChange = (value: number) => { + setMissileAcInputNumber(value); + }; + + const handleRangeChange = (value: [number, number, number]) => { + setRangeArray(value); + }; + + const handleAmmoChange = (value: string) => { + setAmmoSelect(value); + }; + + const onFinish = (values: object) => { + setCharacter({ + ...character, + equipment: [...character.equipment, values as EquipmentItem], + }); + setModalIsOpen(false); + }; + + const handleFormValuesChange = (value: object) => { + if (Object.prototype.hasOwnProperty.call(value, "attack")) { + if ( + form.getFieldValue("attack") === "missile" || + form.getFieldValue("attack") === "both" + ) { + setShowRange(true); + } else { + setShowRange(false); + } + } + }; + + const renderFieldsForCategory = () => { + const fieldsToRender = + categoryFieldMapping[categorySelect as EquipmentCategories] || []; + return fieldsToRender.map((field: string, index) => { + switch (field) { + case "SubCategory": + return ; + case "Damage": + return ; + case "ArmorClass": + return ; + case "AttackType": + return ; + case "Range": + return ( + showRange && ( + + ) + ); + case "Size": + return ; + case "Ammo": + return ( + + ); + case "MissileAc": + return ( + + ); + // ... other cases for different fields + default: + return null; + } + }); + }; + + const showFieldCost = purchasedCheckbox; + + const costClassNames = classNames({ hidden: !showFieldCost }); + + React.useEffect(() => { + if (categorySelect === EquipmentCategories.BOWS) { + form.setFieldsValue({ range: "Missile" }); + setAttackTypeDisabled(true); + } else { + setAttackTypeDisabled(false); + } + }, [categorySelect, form]); + + React.useEffect(() => { + if (!showFieldCost) { + form.setFieldsValue({ costValue: 0 }); + form.setFieldsValue({ costCurrency: "gp" }); + } + }, [showFieldCost, form]); + + return ( +
+ + + + {renderFieldsForCategory()} + + + + + + + + + + + + + + ); +}; + +export default ModalCustomEquipment; diff --git a/src/components/ModalCustomEquipment/Name/Name.tsx b/src/components/ModalCustomEquipment/Name/Name.tsx new file mode 100644 index 00000000..8419825f --- /dev/null +++ b/src/components/ModalCustomEquipment/Name/Name.tsx @@ -0,0 +1,27 @@ +import { Form, Input } from "antd"; +import React from "react"; +import { equipmentNames } from "@/support/equipmentSupport"; + +const Name: React.FC> = ({ className }) => { + const validateName = async (_: unknown, value: string) => { + if (equipmentNames.includes(value)) { + throw new Error("This name already exists."); + } + }; + + return ( + + + + ); +}; + +export default Name; diff --git a/src/components/ModalCustomEquipment/Purchased/Purchased.tsx b/src/components/ModalCustomEquipment/Purchased/Purchased.tsx new file mode 100644 index 00000000..770cf610 --- /dev/null +++ b/src/components/ModalCustomEquipment/Purchased/Purchased.tsx @@ -0,0 +1,22 @@ +import { Checkbox, Form } from "antd"; +import React from "react"; + +interface PurchasedProps { + purchasedCheckbox: boolean; + handlePurchasedChange: (checked: boolean) => void; +} + +const Purchased: React.FC< + PurchasedProps & React.ComponentPropsWithRef<"div"> +> = ({ className, purchasedCheckbox, handlePurchasedChange }) => { + return ( + + handlePurchasedChange(e.target.checked)} + /> + + ); +}; + +export default Purchased; diff --git a/src/components/ModalCustomEquipment/Range/Range.tsx b/src/components/ModalCustomEquipment/Range/Range.tsx new file mode 100644 index 00000000..2c1373cf --- /dev/null +++ b/src/components/ModalCustomEquipment/Range/Range.tsx @@ -0,0 +1,45 @@ +import { Form, InputNumber, Space } from "antd"; +import React from "react"; + +interface RangeProps { + rangeArray: [number, number, number]; + handleRangeChange: (value: [number, number, number]) => void; +} + +const Range: React.FC> = ({ + className, +}) => { + return ( + + + + + + + + + + + + + + ); +}; + +export default Range; diff --git a/src/components/ModalCustomEquipment/Size/Size.tsx b/src/components/ModalCustomEquipment/Size/Size.tsx new file mode 100644 index 00000000..504f094c --- /dev/null +++ b/src/components/ModalCustomEquipment/Size/Size.tsx @@ -0,0 +1,18 @@ +import { Form, Select } from "antd"; +import React from "react"; +import { equipmentSizes } from "@/support/characterSupport"; + +const Size: React.FC> = ({ className }) => { + return ( + + + + ); +}; + +export default SubCategory; diff --git a/src/components/ModalCustomEquipment/Weight/Weight.tsx b/src/components/ModalCustomEquipment/Weight/Weight.tsx new file mode 100644 index 00000000..351868e8 --- /dev/null +++ b/src/components/ModalCustomEquipment/Weight/Weight.tsx @@ -0,0 +1,14 @@ +import { Form, InputNumber } from "antd"; +import React from "react"; + +const Weight: React.FC> = ({ + className, +}) => { + return ( + + + + ); +}; + +export default Weight; diff --git a/src/components/ModalLevelUp/ModalLevelUp.tsx b/src/components/ModalLevelUp/ModalLevelUp.tsx new file mode 100644 index 00000000..df013d9a --- /dev/null +++ b/src/components/ModalLevelUp/ModalLevelUp.tsx @@ -0,0 +1,84 @@ +import { classes } from "@/data/classes"; +import { CharData, Spell } from "@/data/definitions"; +import { classSplit, getHitDice, rollDice } from "@/support/characterSupport"; +import React from "react"; +import SpellOptionsContainer from "./SpellOptionsContainer/SpellOptionsContainer"; +import { Button, Flex } from "antd"; +import { getSelectedSpellsByLevel } from "@/support/spellSupport"; + +interface ModalLevelUpProps { + character: CharData; + setCharacter: (character: CharData) => void; + setModalIsOpen: (modalIsOpen: boolean) => void; +} + +const ModalLevelUp: React.FC< + ModalLevelUpProps & React.ComponentPropsWithRef<"div"> +> = ({ className, character, setCharacter, setModalIsOpen }) => { + const [magicClass, setMagicClass] = React.useState(""); + const [spellBudget, setSpellBudget] = React.useState([]); + const [selectedSpells, setSelectedSpells] = React.useState>( + [], + ); + + const newHitDiceValue = getHitDice( + character.level + 1, + character.class, + character.hp.dice, + ); + + const handleLevelUp = () => { + const result = rollDice(newHitDiceValue); + const newCharacter = { + ...character, + hp: { ...character.hp, max: result, dice: newHitDiceValue }, + level: character.level + 1, + spells: selectedSpells.flatMap((spells) => spells), + }; + setCharacter(newCharacter); + setModalIsOpen(false); + }; + + React.useEffect(() => { + // Set magicClass when character changes + const classArr = classSplit(character.class); + for (const cls of classArr) { + const key = cls as keyof typeof classes; + if (classes[key]?.spellBudget) { + setMagicClass(key); + break; + } + } + }, [character]); + + React.useEffect(() => { + // Update spellBudget and selectedSpells when character or magicClass changes + if (magicClass) { + setSpellBudget( + classes[magicClass as keyof typeof classes].spellBudget![ + character.level + ], + ); + setSelectedSpells(getSelectedSpellsByLevel(character, magicClass)); + } + }, [character, magicClass]); + + return ( + + {!!spellBudget.length && ( + + )} + + + ); +}; + +export default ModalLevelUp; diff --git a/src/components/ModalLevelUp/SpellOptionsContainer/SpellOptionsContainer.tsx b/src/components/ModalLevelUp/SpellOptionsContainer/SpellOptionsContainer.tsx new file mode 100644 index 00000000..63f9702e --- /dev/null +++ b/src/components/ModalLevelUp/SpellOptionsContainer/SpellOptionsContainer.tsx @@ -0,0 +1,48 @@ +import { CharData, Spell } from "@/data/definitions"; +import React from "react"; +import SpellOptionsList from "../SpellOptionsList/SpellOptionsList"; +import { getSpellsAtLevel } from "@/support/spellSupport"; + +interface SpellOptionsContainerProps { + spellBudget: number[]; + magicClass: string; + character: CharData; + selectedSpells: Spell[][]; + setSelectedSpells: (selectedSpells: Spell[][]) => void; +} + +const SpellOptionsContainer: React.FC< + SpellOptionsContainerProps & React.ComponentPropsWithRef<"div"> +> = ({ + className, + spellBudget, + magicClass, + character, + selectedSpells, + setSelectedSpells, +}) => { + return ( +
+ {spellBudget.map((max, index) => { + const spellLevel = index + 1; + const spellsAtLevel = getSpellsAtLevel(character).filter( + (spell) => spell.level[magicClass.toLowerCase()] === spellLevel, + ); + return ( + max > 0 && ( + + ) + ); + })} +
+ ); +}; + +export default SpellOptionsContainer; diff --git a/src/components/ModalLevelUp/SpellOptionsList/SpellOptionsList.tsx b/src/components/ModalLevelUp/SpellOptionsList/SpellOptionsList.tsx new file mode 100644 index 00000000..7ddea927 --- /dev/null +++ b/src/components/ModalLevelUp/SpellOptionsList/SpellOptionsList.tsx @@ -0,0 +1,72 @@ +import { Spell } from "@/data/definitions"; +import { getSpellFromName } from "@/support/spellSupport"; +import { Checkbox, Flex, Typography } from "antd"; +import { CheckboxValueType } from "antd/es/checkbox/Group"; +import React, { useState } from "react"; + +interface SpellOptionsListProps { + spellLevel: number; + spellsAtLevel: Spell[]; + selectedSpells: Spell[][]; + setSelectedSpells: (selectedSpells: Spell[][]) => void; + max: number; +} + +const SpellOptionsList: React.FC< + SpellOptionsListProps & React.ComponentPropsWithRef<"div"> +> = ({ + className, + spellLevel, + spellsAtLevel, + selectedSpells, + setSelectedSpells, + max, +}) => { + const index = spellLevel - 1; + const [checkedValues, setCheckedValues] = useState( + selectedSpells.length && selectedSpells[index] + ? selectedSpells[index].map((spell) => spell.name) + : [], + ); + + const handleCheckboxChange = (checkedList: Array) => { + setCheckedValues(checkedList.map((value) => String(value))); + }; + + React.useEffect(() => { + const newSelectedSpells = [...selectedSpells]; + if (!checkedValues.length) { + newSelectedSpells[index] = []; + } else { + const newCheckedSpells = checkedValues.reduce((acc, spellName) => { + const spell = getSpellFromName(spellName); + if (spell) { + acc.push(spell); + } + return acc; + }, [] as Spell[]); + newSelectedSpells[index] = newCheckedSpells; + } + setSelectedSpells(newSelectedSpells); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [checkedValues]); + + return ( + + Level {spellLevel} Spells + ({ + label: spell.name, + value: spell.name, + disabled: + checkedValues.length >= max && !checkedValues.includes(spell.name), + }))} + value={checkedValues} + onChange={handleCheckboxChange} + className="flex flex-col border border-stone border-solid rounded-md p-2" + /> + + ); +}; + +export default SpellOptionsList; diff --git a/src/components/ModalLoginSignup/LoginForm/LoginForm.tsx b/src/components/ModalLoginSignup/LoginForm/LoginForm.tsx new file mode 100644 index 00000000..a5ecfc91 --- /dev/null +++ b/src/components/ModalLoginSignup/LoginForm/LoginForm.tsx @@ -0,0 +1,95 @@ +import { Button, Checkbox, Form, Input } from "antd"; +import React from "react"; + +interface LoginFormProps { + setEmail: (email: string) => void; + setPassword: (password: string) => void; + onLogin: (e: React.FormEvent) => void; + errors: string[]; + handleCancel: () => void; +} + +type FieldType = { + email?: string; + password?: string; + remember?: string; +}; + +const LoginForm: React.FC< + LoginFormProps & React.ComponentPropsWithRef<"div"> +> = ({ setEmail, setPassword, onLogin, errors, handleCancel, className }) => { + const onFinishFailed = (errorInfo: any) => { + console.error("Failed:", errorInfo); + }; + const onFinish = (e: React.FormEvent) => { + handleCancel(); + onLogin(e); + }; + return ( +
+ + label="Email" + name="email" + rules={[{ required: true, message: "Please input your email!" }]} + > + setEmail(e.target.value)} + /> +
+ + + label="Password" + name="password" + rules={[{ required: true, message: "Please input your password!" }]} + > + setPassword(e.target.value)} + /> + + + + name="remember" + valuePropName="checked" + wrapperCol={{ span: 16 }} + > + Remember me + + + {errors.length > 0 && + errors.map((error) => ( +
+ {error} +
+ ))} + + + + + + ); +}; + +export default LoginForm; diff --git a/src/components/ModalLoginSignup/ModalLoginSignup.tsx b/src/components/ModalLoginSignup/ModalLoginSignup.tsx new file mode 100644 index 00000000..ddfab8a0 --- /dev/null +++ b/src/components/ModalLoginSignup/ModalLoginSignup.tsx @@ -0,0 +1,113 @@ +import { Button, Modal } from "antd"; +import { useNavigate } from "react-router-dom"; +import React from "react"; +import { + createUserWithEmailAndPassword, + signInWithEmailAndPassword, +} from "firebase/auth"; +import { auth } from "@/firebase"; +import LoginForm from "./LoginForm/LoginForm"; +import SignupForm from "./SignupForm/SignupForm"; + +interface ModalLoginSignupProps { + handleCancel: () => void; + isLoginSignupModalOpen: boolean; + handleLogin: () => Promise; +} + +const ModalLoginSignup: React.FC = ({ + handleCancel, + isLoginSignupModalOpen, + handleLogin, +}) => { + const navigate = useNavigate(); + const [email, setEmail] = React.useState(""); + const [password, setPassword] = React.useState(""); + const [signInForm, setSignInForm] = React.useState(true); + const [errors, setErrors] = React.useState([]); + + const onLogin = () => { + signInWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // Signed in + const user = userCredential.user; + console.info(user); + navigate("/"); + handleCancel(); + }) + .catch((error) => { + setErrors((prevErrors) => [ + ...prevErrors, + `${error.code}: ${error.message}`, + ]); + }); + }; + + const onSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + await createUserWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // Signed in + const user = userCredential.user; + console.info(user); + navigate("/"); + handleCancel(); + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + console.error(errorCode, errorMessage); + setErrors; + }); + }; + return ( + + {signInForm ? ( +
+ + + +
+ ) : ( +
+ + +
+ )} +
+ ); +}; + +export default ModalLoginSignup; diff --git a/src/components/ModalLoginSignup/SignupForm/SignupForm.tsx b/src/components/ModalLoginSignup/SignupForm/SignupForm.tsx new file mode 100644 index 00000000..c7a5ae58 --- /dev/null +++ b/src/components/ModalLoginSignup/SignupForm/SignupForm.tsx @@ -0,0 +1,68 @@ +import { Button, Form, Input } from "antd"; +import React from "react"; + +interface SignupFormProps { + email: string; + password: string; + setEmail: (email: string) => void; + setPassword: (password: string) => void; + onSubmit: (e: React.FormEvent) => void; +} + +const SignupForm: React.FC< + SignupFormProps & React.ComponentPropsWithRef<"div"> +> = ({ email, setEmail, password, setPassword, onSubmit, className }) => { + return ( +
+ + setEmail(e.target.value)} + /> + + + + setPassword(e.target.value)} + /> + + + + + + + ); +}; + +export default SignupForm; diff --git a/src/components/ModalVirtualDice/ModalVirtualDice.tsx b/src/components/ModalVirtualDice/ModalVirtualDice.tsx new file mode 100644 index 00000000..2dcd5f90 --- /dev/null +++ b/src/components/ModalVirtualDice/ModalVirtualDice.tsx @@ -0,0 +1,45 @@ +import { Button, Form, Input } from "antd"; +import React from "react"; +import { useNotification } from "@/hooks/useNotification"; +import { rollDice } from "@/support/characterSupport"; + +interface ModalVirtualDiceProps {} + +const ModalVirtualDice: React.FC< + ModalVirtualDiceProps & React.ComponentPropsWithRef<"div"> +> = ({ className }) => { + const { contextHolder, openNotification } = useNotification(); + const [inputValue, setInputValue] = React.useState(""); + const handleInputChange = (event: React.ChangeEvent) => { + setInputValue(event.target.value); + }; + const onFinish = () => { + openNotification("Virtual Dice", rollDice(inputValue)); + }; + return ( +
+ {contextHolder} + + + + + + + + ); +}; + +export default ModalVirtualDice; diff --git a/src/components/NewContentWrapper/NewContentWrapper.tsx b/src/components/NewContentWrapper/NewContentWrapper.tsx new file mode 100644 index 00000000..38a9153d --- /dev/null +++ b/src/components/NewContentWrapper/NewContentWrapper.tsx @@ -0,0 +1,32 @@ +import { Divider, Flex, Typography } from "antd"; +import classNames from "classnames"; +import React from "react"; +import { newGameCharacterPageTitleClassNames } from "@/support/classNameSupport"; + +interface NewContentWrapperProps { + title: string; + markedDesc: string; +} + +const NewContentWrapper: React.FC< + NewContentWrapperProps & React.ComponentPropsWithRef<"div"> +> = ({ className, title, markedDesc, children }) => { + const newContentWarpperClassNames = classNames(className, "mt-4"); + return ( + + + {title} + + + + + + {children} + + ); +}; + +export default NewContentWrapper; diff --git a/src/components/NewGameModal/NewGameModal.tsx b/src/components/NewGameModal/NewGameModal.tsx deleted file mode 100644 index b6b38a55..00000000 --- a/src/components/NewGameModal/NewGameModal.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Button, Form, Input, Modal } from "antd"; -import CloseIcon from "../CloseIcon/CloseIcon"; -import { GameData } from "../../data/definitions"; -import { useEffect } from "react"; - -export default function NewGameModal({ - isNewGameModalOpen, - handleCancel, - addGameData, -}: { - isNewGameModalOpen: boolean; - handleCancel: () => void; - addGameData: (gameData: GameData) => Promise; -}) { - const [form] = Form.useForm(); - - const onFinish = (values: any) => { - addGameData({ name: values.newGameTitle, players: [] }); - form.resetFields(); - handleCancel(); - }; - - return ( - } - footer={false} - > -
- - - - - - - - -
- ); -} diff --git a/src/components/PageCharacterSheet/AbilitiesTable/AbilitiesTable.tsx b/src/components/PageCharacterSheet/AbilitiesTable/AbilitiesTable.tsx new file mode 100644 index 00000000..dd5c1ebf --- /dev/null +++ b/src/components/PageCharacterSheet/AbilitiesTable/AbilitiesTable.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import { Abilities } from "@/data/definitions"; +import { Table } from "antd"; +import { ColumnType } from "antd/es/table"; +import { CharacterDataContext } from "@/contexts/CharacterContext"; + +interface DataType { + key: string; + ability: keyof Abilities; + score: number; + modifier: string; +} + +const columns: ColumnType[] = [ + { + title: "Ability", + dataIndex: "ability", + key: "ability", + }, + { + title: "Score", + dataIndex: "score", + key: "score", + }, + { + title: "Modifier", + dataIndex: "modifier", + key: "modifier", + }, +]; + +const abilityOrder = [ + "strength", + "intelligence", + "wisdom", + "dexterity", + "constitution", + "charisma", +]; + +const AbilitiesTable: React.FC> = ({ + className, +}) => { + const { character } = React.useContext(CharacterDataContext); + const dataSource: DataType[] = Object.entries(character.abilities.scores) + .sort(([a], [b]) => abilityOrder.indexOf(a) - abilityOrder.indexOf(b)) + .map(([abilityKey, scoreValue], index) => ({ + key: index + 1 + "", + ability: abilityKey.toUpperCase().slice(0, 3) as keyof Abilities, + score: +scoreValue, + modifier: + character.abilities.modifiers[abilityKey as keyof Abilities].toString(), + })); + + return ( +
+ ); +}; + +export default AbilitiesTable; diff --git a/src/components/PageCharacterSheet/AttackBonuses/AttackBonuses.tsx b/src/components/PageCharacterSheet/AttackBonuses/AttackBonuses.tsx new file mode 100644 index 00000000..faa771ed --- /dev/null +++ b/src/components/PageCharacterSheet/AttackBonuses/AttackBonuses.tsx @@ -0,0 +1,63 @@ +import React from "react"; +import { RaceNames } from "@/data/definitions"; +import { ColumnType } from "antd/es/table"; +import { Table } from "antd"; +import { getAttackBonus } from "@/support/characterSupport"; +import { races } from "@/data/races"; +import { CharacterDataContext } from "@/contexts/CharacterContext"; + +interface DataType { + key: string; + bonus: string; + score: number; +} + +const columns: ColumnType[] = [ + { + title: "Bonus", + dataIndex: "bonus", + key: "bonus", + }, + { + title: "Score", + dataIndex: "score", + key: "score", + }, +]; + +const AttackBonuses: React.FC> = ({ + className, +}) => { + const { character } = React.useContext(CharacterDataContext); + const baseAttackBonus = getAttackBonus(character); + const rangedRaceBonus = races[character.race as RaceNames]?.rangedBonus + ? races[character.race as RaceNames].rangedBonus + : 0; + const dataSource: DataType[] = [ + { key: "1", bonus: "Base Attack Bonus", score: baseAttackBonus }, + { + key: "2", + bonus: "Melee Attack Bonus", + score: baseAttackBonus + +character.abilities.modifiers.strength, + }, + { + key: "3", + bonus: "Ranged Attack Bonus", + score: + baseAttackBonus + + +character.abilities.modifiers.dexterity + + (rangedRaceBonus ?? 0), + }, + ]; + return ( +
+ ); +}; + +export default AttackBonuses; diff --git a/src/components/PageCharacterSheet/CharacterFloatButtons/CharacterFloatButtons.tsx b/src/components/PageCharacterSheet/CharacterFloatButtons/CharacterFloatButtons.tsx new file mode 100644 index 00000000..c43a1610 --- /dev/null +++ b/src/components/PageCharacterSheet/CharacterFloatButtons/CharacterFloatButtons.tsx @@ -0,0 +1,75 @@ +import { FileSearchOutlined, NodeIndexOutlined } from "@ant-design/icons"; +import Icon, { + CustomIconComponentProps, +} from "@ant-design/icons/lib/components/Icon"; +import { FloatButton } from "antd"; +import React from "react"; +import { CharacterDataContext } from "@/contexts/CharacterContext"; +import { useCharacterDice } from "@/hooks/useCharacterDice"; +import { ColorScheme } from "@/support/colorSupport"; +import ModalCheatSheet from "@/components/ModalCheatSheet/ModalCheatSheet"; +import ModalVirtualDice from "@/components/ModalVirtualDice/ModalVirtualDice"; + +interface CharacterFloatButtonsProps { + setModalIsOpen: (modalIsOpen: boolean) => void; + setModalTitle: (modalTitle: string) => void; + setModalContent: (modalContent: React.ReactNode) => void; + modalOk: (() => void | undefined) | null | undefined; +} + +const DiceSvg = () => ( + + + + +); + +const DiceIcon = (props: Partial) => ( + +); + +const CharacterFloatButtons: React.FC< + CharacterFloatButtonsProps & React.ComponentPropsWithRef<"div"> +> = ({ className, setModalContent, setModalIsOpen, setModalTitle }) => { + const { character } = React.useContext(CharacterDataContext); + const { rollInitiative, contextHolder } = useCharacterDice(character); + const handleCheatSheetClick = () => { + setModalContent(); + setModalTitle("Cheat Sheet"); + setModalIsOpen(true); + }; + const handleVirtualDiceClick = () => { + setModalContent(); + setModalTitle("Virtual Dice"); + setModalIsOpen(true); + }; + return ( + + {contextHolder} + } + tooltip={
Roll Initiative
} + onClick={rollInitiative} + /> + } + tooltip={
Cheat Sheet
} + onClick={handleCheatSheetClick} + /> + } + tooltip={
Virtual Dice
} + onClick={handleVirtualDiceClick} + /> +
+ ); +}; + +export default CharacterFloatButtons; diff --git a/src/components/PageCharacterSheet/CharacterStat/CharacterStat.tsx b/src/components/PageCharacterSheet/CharacterStat/CharacterStat.tsx new file mode 100644 index 00000000..a62bd2b2 --- /dev/null +++ b/src/components/PageCharacterSheet/CharacterStat/CharacterStat.tsx @@ -0,0 +1,28 @@ +import { Statistic } from "antd"; +import classNames from "classnames"; +import React from "react"; + +interface CharacterStatProps { + value: number | string; + altValue?: number | string; +} + +const CharacterStat: React.FC< + CharacterStatProps & React.ComponentPropsWithRef<"div"> +> = ({ className, value, altValue }) => { + const characterStatClassNames = classNames( + "text-center", + "font-bold", + "leading-none", + className, + ); + return ( + / {altValue}} + /> + ); +}; + +export default CharacterStat; diff --git a/src/components/PageCharacterSheet/CollapseEquipment/CollapseEquipment.tsx b/src/components/PageCharacterSheet/CollapseEquipment/CollapseEquipment.tsx new file mode 100644 index 00000000..6fdd6125 --- /dev/null +++ b/src/components/PageCharacterSheet/CollapseEquipment/CollapseEquipment.tsx @@ -0,0 +1,147 @@ +import { + Alert, + Collapse, + CollapseProps, + Descriptions, + DescriptionsProps, + Flex, + Radio, + RadioChangeEvent, +} from "antd"; +import React from "react"; +import { slugToTitleCase } from "@/support/stringSupport"; +import { equipmentCategoryMap } from "@/support/equipmentSupport"; +import { + CharData, + EquipmentCategories, + EquipmentCategoriesWeapons, + EquipmentItem, +} from "@/data/definitions"; +import EquipmentItemDescription from "./EquipmentItemDescription/EquipmentItemDescription"; +import WearingRadioGroup from "./WearingRadioGroup/WearingRadioGroup"; + +interface CollapseEquipmentProps { + character: CharData; + setCharacter: (character: CharData) => void; + onCharacterSheet?: boolean; + setModalIsOpen: (modalIsOpen: boolean) => void; + setModalTitle: (modalTitle: string) => void; + setModalContent: (modalContent: React.ReactNode) => void; +} + +const equipmentSymbolKeyItems: DescriptionsProps["items"] = [ + { + key: "1", + label: "**", + children: "This weapon only does subduing damage", + }, + { + key: "2", + label: "(E)", + children: "Entangling: This weapon may be used to snare or hold opponents.", + }, + { + key: "3", + label: "†", + children: "Silver tip or blade, for use against lycanthropes.", + }, +]; + +const CollapseEquipment: React.FC< + CollapseEquipmentProps & React.ComponentPropsWithRef<"div"> +> = ({ + className, + character, + setCharacter, + onCharacterSheet, + setModalIsOpen, + setModalTitle, + setModalContent, +}) => { + const onChangeWearing = (e: RadioChangeEvent, type: "armor" | "shield") => { + setCharacter({ + ...character, + wearing: { + armor: + type === "armor" + ? e.target.value || "" + : character.wearing?.armor || "", + shield: + type === "shield" + ? e.target.value || "" + : character.wearing?.shield || "", + }, + }); + }; + + const items: CollapseProps["items"] = Object.entries( + equipmentCategoryMap(character.equipment), + ) + .sort() + .map((category, index) => ({ + key: index + 1 + "", + label: slugToTitleCase(category[0]), + children: ( + + {category[0] === EquipmentCategories.ARMOR || + category[0] === EquipmentCategories.SHIELDS ? ( + { + if ( + category[0] === EquipmentCategories.ARMOR || + category[0] === EquipmentCategories.SHIELDS + ) { + onChangeWearing(e, category[0] as "armor" | "shield"); + } + }} + > + {category[1].map((item: EquipmentItem, index: number) => ( + + + + ))} + + ) : ( + category[1].map((item: EquipmentItem, index: number) => ( + + )) + )} + + ), + })); + + return ( + + + } + /> + + ); +}; + +export default CollapseEquipment; diff --git a/src/components/CharacterSheet/CharacterDescription/DescriptionFieldButton/DescriptionFieldButton.tsx b/src/components/PageCharacterSheet/CollapseEquipment/DescriptionFieldButton/DescriptionFieldButton.tsx similarity index 90% rename from src/components/CharacterSheet/CharacterDescription/DescriptionFieldButton/DescriptionFieldButton.tsx rename to src/components/PageCharacterSheet/CollapseEquipment/DescriptionFieldButton/DescriptionFieldButton.tsx index 38032086..f593f008 100644 --- a/src/components/CharacterSheet/CharacterDescription/DescriptionFieldButton/DescriptionFieldButton.tsx +++ b/src/components/PageCharacterSheet/CollapseEquipment/DescriptionFieldButton/DescriptionFieldButton.tsx @@ -13,7 +13,7 @@ export default function DescriptionFieldButton({ icon, className, }: DescriptionFieldButtonProps & React.ComponentPropsWithRef<"div">) { - const buttonClassNames = classNames(className, "mb-4"); + const buttonClassNames = classNames(className, "mb-4", "shadow-none"); return ( + + )} + {showAttackButton && ( + + )} + + + + ); +}; + +export default EquipmentItemDescription; diff --git a/src/components/PageCharacterSheet/CollapseEquipment/WearingRadioGroup/WearingRadioGroup.tsx b/src/components/PageCharacterSheet/CollapseEquipment/WearingRadioGroup/WearingRadioGroup.tsx new file mode 100644 index 00000000..5e048c78 --- /dev/null +++ b/src/components/PageCharacterSheet/CollapseEquipment/WearingRadioGroup/WearingRadioGroup.tsx @@ -0,0 +1,44 @@ +import { Radio, RadioChangeEvent, Typography } from "antd"; +import React from "react"; +import { EquipmentCategories } from "@/data/definitions"; +import { slugToTitleCase } from "@/support/stringSupport"; +import classNames from "classnames"; + +interface WearingRadioGroupProps { + category: EquipmentCategories; + value: string | undefined; + onChangeWearing: (e: RadioChangeEvent) => void; +} + +const WearingRadioGroup: React.FC< + WearingRadioGroupProps & React.ComponentPropsWithRef<"div"> +> = ({ className, children, category, value, onChangeWearing }) => { + const radioGroupClassNames = classNames( + "flex", + "flex-col", + "gap-4", + className, + ); + return ( + <> + + Wearing: + + + + + {`No ${slugToTitleCase(category)}`} + + + {children} + + + ); +}; + +export default WearingRadioGroup; diff --git a/src/components/PageCharacterSheet/Description/Description.tsx b/src/components/PageCharacterSheet/Description/Description.tsx new file mode 100644 index 00000000..bfad0524 --- /dev/null +++ b/src/components/PageCharacterSheet/Description/Description.tsx @@ -0,0 +1,133 @@ +import { Flex, Input } from "antd"; +import React from "react"; +import DOMPurify from "dompurify"; +import { MinusCircleOutlined, PlusCircleOutlined } from "@ant-design/icons"; +import { getClassType } from "@/support/characterSupport"; +import DescriptionFieldButton from "../CollapseEquipment/DescriptionFieldButton/DescriptionFieldButton"; +import { CharacterDataContext } from "@/contexts/CharacterContext"; + +type DescriptionProps = { + isMobile: boolean; +}; + +const Description: React.FC< + DescriptionProps & React.ComponentPropsWithRef<"div"> +> = ({ isMobile }) => { + const { character, setCharacter, userIsOwner } = + React.useContext(CharacterDataContext); + const initialDesc = Array.isArray(character.desc) + ? character.desc || [""] + : [character.desc || ""]; + const timeoutRef = React.useRef(null); + const [textAreaValues, setTextAreaValues] = + React.useState(initialDesc); + const placeholderSavingThrows = `"${character.class}" SAVING THROWS\n----------\nDEATH RAY or POISON: 00\nMAGIC WANDS: 00\nPARALYSIS or PETRIFY: 00\nDRAGON BREATH: 00\nSPELLS: 00`; + + // Function to add a new description field + const handleAddDescriptionField = () => { + const newTextAreaValues = [...textAreaValues, ""]; + setTextAreaValues(newTextAreaValues); + }; + + // Function to delete a description field + const handleDeleteDescriptionField = (index: number) => { + const newTextAreaValues = textAreaValues.filter((_, i) => i !== index); + setTextAreaValues(newTextAreaValues); + }; + + // Function to handle text area changes + const handleTextAreaChange = (value: string, index: number) => { + const sanitizedValue = DOMPurify.sanitize(value); + const newTextAreaValues = [...textAreaValues]; + newTextAreaValues[index] = sanitizedValue; + setTextAreaValues(newTextAreaValues); + }; + + // Function to handle immediate database update on blur + const handleImmediateUpdate = () => { + setCharacter({ + ...character, + desc: textAreaValues || "", + }); + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + + // Side-Effect to initialize character.desc + React.useEffect(() => { + if (typeof character.desc === "string") { + setCharacter({ + ...character, + desc: [character.desc || ""], + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [character.desc]); + + // Effect to handle database update with a delay + React.useEffect(() => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + setCharacter({ + ...character, + desc: textAreaValues || "", + }); + }, 1000); + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [textAreaValues]); + + return ( + + {textAreaValues.map((desc: string, index: number) => ( + + + {index > 0 && ( + handleDeleteDescriptionField(index)} + icon={} + /> + )} + {index === character.desc?.length - 1 && index < 9 && ( + } + /> + )} + + handleTextAreaChange(e.target.value, index)} + disabled={!userIsOwner} + onBlur={() => handleImmediateUpdate()} + className={ + !isMobile && index === 0 && textAreaValues.length > 1 + ? "ml-12" + : "" + } + /> + + ))} + + ); +}; + +export default Description; diff --git a/src/components/PageCharacterSheet/Equipment/Equipment.tsx b/src/components/PageCharacterSheet/Equipment/Equipment.tsx new file mode 100644 index 00000000..4990128b --- /dev/null +++ b/src/components/PageCharacterSheet/Equipment/Equipment.tsx @@ -0,0 +1,81 @@ +import { Button, Flex } from "antd"; +import React from "react"; +import CollapseEquipment from "../CollapseEquipment/CollapseEquipment"; +import EquipmentItemDescription from "../CollapseEquipment/EquipmentItemDescription/EquipmentItemDescription"; +import { kickItem, punchItem } from "@/support/equipmentSupport"; +import ModalCustomEquipment from "@/components/ModalCustomEquipment/ModalCustomEquipment"; +import StepEquipment from "@/components/PageNewCharacter/StepEquipment/StepEquipment"; +import { CharacterDataContext } from "@/contexts/CharacterContext"; + +interface EquipmentProps { + setModalIsOpen: (modalIsOpen: boolean) => void; + setModalTitle: (modalTitle: string) => void; + setModalContent: (modalContent: React.ReactNode) => void; +} + +const Equipment: React.FC< + EquipmentProps & React.ComponentPropsWithRef<"div"> +> = ({ className, setModalIsOpen, setModalTitle, setModalContent }) => { + const { character, setCharacter, userIsOwner } = + React.useContext(CharacterDataContext); + const handleEditEquipmentClick = () => { + setModalIsOpen(true); + setModalTitle("Add/Edit Equipment"); + setModalContent( + , + ); + }; + const handleCustomEquipmentClick = () => { + setModalIsOpen(true); + setModalTitle("Add Custom Equipment"); + setModalContent( + , + ); + }; + return ( + + + + + + + + + + + + ); +}; + +export default Equipment; diff --git a/src/components/PageCharacterSheet/Hero/Hero.tsx b/src/components/PageCharacterSheet/Hero/Hero.tsx new file mode 100644 index 00000000..3db23cea --- /dev/null +++ b/src/components/PageCharacterSheet/Hero/Hero.tsx @@ -0,0 +1,230 @@ +import React from "react"; +import { + EditOutlined, + SolutionOutlined, + UserOutlined, +} from "@ant-design/icons"; +import { avatarClassNames } from "@/support/classNameSupport"; +import { + Avatar, + Badge, + Breadcrumb, + BreadcrumbProps, + Button, + Descriptions, + DescriptionsProps, + Divider, + Flex, + Input, + Space, + Typography, +} from "antd"; +import { classSplit, getAvatar } from "@/support/characterSupport"; +import { ClassNames } from "@/data/definitions"; +import { classes } from "@/data/classes"; +import HelpTooltip from "@/components/HelpTooltip/HelpTooltip"; +import BreadcrumbHomeLink from "@/components/BreadcrumbHomeLink/BreadcrumbHomeLink"; +import classNames from "classnames"; +import AvatarPicker from "@/components/AvatarPicker/AvatarPicker"; +import { CharacterDataContext } from "@/contexts/CharacterContext"; +import ModalLevelUp from "@/components/ModalLevelUp/ModalLevelUp"; + +interface HeroProps { + setModalIsOpen: (modalIsOpen: boolean) => void; + setModalTitle: (modalTitle: string) => void; + setModalContent: (modalContent: React.ReactNode) => void; + isMobile: boolean; +} + +const Hero: React.FC> = ({ + className, + setModalIsOpen, + setModalTitle, + setModalContent, + isMobile, +}) => { + const { character, setCharacter, userIsOwner, uid, id } = + React.useContext(CharacterDataContext); + const [inputValue, setInputValue] = React.useState(`${character.xp}`); + const heroClassNames = classNames("w-full", className); + + const classArr = classSplit(character.class); + + const showLevelUpModal = () => { + setModalIsOpen(true); + setModalTitle("Level Up"); + setModalContent( + , + ); + }; + + const showAvatarModal = () => { + setModalIsOpen(true); + setModalTitle("Change Avatar"); + setModalContent( + , + ); + }; + + const handleInputChange = (event: React.ChangeEvent) => { + setInputValue(event.target.value); + }; + + const handleInputBlur = () => { + if (!uid || !id) { + return; + } + + // Check if inputValue matches the expected format (optional '-' or '+', followed by numeric characters) + if (!/^[+-]?\d+$/.test(inputValue)) { + console.error("Invalid input"); + return; + } + + // Determine the XP change + let xpChange = 0; + if (inputValue.startsWith("+")) { + xpChange = parseInt(inputValue.slice(1)); + } else if (inputValue.startsWith("-")) { + xpChange = -parseInt(inputValue.slice(1)); + } else { + xpChange = parseInt(inputValue) - character.xp; // Difference between new and old XP + } + + // Apply the XP change + if (!isNaN(xpChange)) { + const updatedXP = character.xp + xpChange; + setCharacter({ + ...character, + xp: updatedXP, + }); + setInputValue(updatedXP.toString()); + } + }; + + const totalLevelRequirement = classArr + .map((className) => { + const classRequirements = + classes[className as ClassNames]?.experiencePoints; + return classRequirements ? classRequirements[character.level] : 0; // value if using a custom class + }) + .reduce((a, b) => a + b, 0); + + const breadcrumbItems: BreadcrumbProps["items"] = [ + { + title: , + }, + { + title: ( +
+ + {character.name} +
+ ), + }, + ]; + + const descriptionsItems: DescriptionsProps["items"] = [ + { key: "1", label: "Level", children: character.level }, + { key: "2", label: "Race", children: character.race }, + { key: "3", label: "Class", children: classArr.join(", ") }, + ]; + + return ( + <> + + + {/* Avatar */} +
{ + console.error( + "You are not logged in as the owner of this character", + ); + } + } + > + }> + : undefined} + size={64} + className={avatarClassNames} + /> + +
+ {/* Name */} + + {character.name} + + + + {/* Level/Race/Class */} + + {/* Experience Points */} + + + { + setTimeout(() => { + e.target.select(); + }, 50); + }} + onChange={handleInputChange} + onBlur={handleInputBlur} + onKeyDown={(event) => { + if (event.key === "Enter") { + handleInputBlur(); + } + }} + name="Experience Points" + id="experience-points" + suffix={character.level < 20 && `/ ${totalLevelRequirement} XP`} + disabled={!userIsOwner} + /> + + {character.level < 20 && ( + + )} + + + + +
+ + ); +}; + +export default Hero; diff --git a/src/components/PageCharacterSheet/HitPoints/HitPoints.tsx b/src/components/PageCharacterSheet/HitPoints/HitPoints.tsx new file mode 100644 index 00000000..bd401e37 --- /dev/null +++ b/src/components/PageCharacterSheet/HitPoints/HitPoints.tsx @@ -0,0 +1,79 @@ +import { Flex, Input, InputNumber, Typography } from "antd"; +import React from "react"; +import DOMPurify from "dompurify"; +import { CharacterDataContext } from "@/contexts/CharacterContext"; + +const HitPoints: React.FC> = ({ + className, +}) => { + const { character, setCharacter, userIsOwner } = + React.useContext(CharacterDataContext); + const [inputValue, setInputValue] = React.useState(character.hp.points); + const [descValue, setDescValue] = React.useState(character.hp.desc || ""); + + const handleInputChange = (value: number | null) => { + if (value && !isNaN(value)) { + // Update local state + setInputValue(value); + + // Update character state + setCharacter({ + ...character, + hp: { + ...character.hp, + points: value, + }, + }); + } + }; + + const handleDescChange = (event: React.ChangeEvent) => { + const sanitizedInput = DOMPurify.sanitize(event.target.value); + setDescValue(sanitizedInput); + }; + + const handleDescBlur = () => { + setCharacter({ + ...character, + hp: { + ...character.hp, + desc: descValue, + }, + }); + }; + + return ( + + + +
+ + + Character count: {descValue.length}/500 + +
+
+ ); +}; + +export default HitPoints; diff --git a/src/components/PageCharacterSheet/Money/Money.tsx b/src/components/PageCharacterSheet/Money/Money.tsx new file mode 100644 index 00000000..a6c2ebeb --- /dev/null +++ b/src/components/PageCharacterSheet/Money/Money.tsx @@ -0,0 +1,76 @@ +import React from "react"; +import { Flex, Input } from "antd"; +import { CostCurrency } from "@/data/definitions"; +import { CharacterDataContext } from "@/contexts/CharacterContext"; +import { useMoney } from "@/hooks/useMoney"; +import { useDeviceType } from "@/hooks/useDeviceType"; + +const Money: React.FC> = ({ className }) => { + const { isMobile } = useDeviceType(); + const { character, setCharacter, userIsOwner } = + React.useContext(CharacterDataContext); + const { + goldValue, + setGoldValue, + silverValue, + setSilverValue, + copperValue, + setCopperValue, + handleBlurAndEnter, + handleChange, + handleFocus, + } = useMoney(character, setCharacter); + return ( + + {[ + ["gp", goldValue, setGoldValue, 1], + ["sp", silverValue, setSilverValue, 10], + ["cp", copperValue, setCopperValue, 100], + ].map(([key, value, setFunc, multiplier]) => { + const keyValue = key as CostCurrency; + const setFuncTyped = setFunc as React.Dispatch< + React.SetStateAction + >; + const multiplierTyped = multiplier as number; + + return ( + + handleChange(e, setFuncTyped)} + onBlur={() => + handleBlurAndEnter( + value as string, + setFuncTyped, + multiplierTyped, + keyValue, + ) + } + onKeyDown={(e) => { + if (e.key === "Enter") { + handleBlurAndEnter( + value as string, + setFuncTyped, + multiplierTyped, + keyValue, + ); + } + }} + addonAfter={keyValue} + disabled={!userIsOwner} + id={keyValue} + /> + + + ); + })} + + ); +}; + +export default Money; diff --git a/src/components/PageCharacterSheet/PageCharacterSheet.tsx b/src/components/PageCharacterSheet/PageCharacterSheet.tsx new file mode 100644 index 00000000..f427c570 --- /dev/null +++ b/src/components/PageCharacterSheet/PageCharacterSheet.tsx @@ -0,0 +1,257 @@ +import { User } from "firebase/auth"; +import React from "react"; +import { ClassNames } from "@/data/definitions"; +import Hero from "./Hero/Hero"; +import { Alert, Col, Divider, Flex, Row, Skeleton } from "antd"; +import AbilitiesTable from "./AbilitiesTable/AbilitiesTable"; +import Section from "./Section/Section"; +import AttackBonuses from "./AttackBonuses/AttackBonuses"; +import HitPoints from "./HitPoints/HitPoints"; +import { classes } from "@/data/classes"; +import { + classSplit, + getArmorClass, + getHitDice, + getMovement, + isStandardClass, +} from "@/support/characterSupport"; +import { marked } from "marked"; +import CharacterStat from "./CharacterStat/CharacterStat"; +import SpecialsRestrictions from "./SpecialsRestrictions/SpecialsRestrictions"; +import SpecialAbilitiesTable from "./SpecialAbilitiesTable/SpecialAbilitiesTable"; +import SavingThrows from "./SavingThrows/SavingThrows"; +import Money from "./Money/Money"; +import Weight from "./Weight/Weight"; +import Equipment from "./Equipment/Equipment"; +import Description from "./Description/Description"; +import CharacterFloatButtons from "./CharacterFloatButtons/CharacterFloatButtons"; +import ModalContainer from "@/components/ModalContainer/ModalContainer"; +import StepAbilities from "@/components/PageNewCharacter/StepAbilities/StepAbilities"; +import StepHitPoints from "@/components/PageNewCharacter/StepHitPoints/StepHitPoints"; +import { useCharacterData } from "@/hooks/useCharacterData"; +import { useModal } from "@/hooks/useModal"; +import { useDeviceType } from "@/hooks/useDeviceType"; +import { CharacterDataContext } from "@/contexts/CharacterContext"; +import { customClassString } from "@/support/stringSupport"; +import classNames from "classnames"; +import Spells from "./Spells/Spells"; +import { useSpellData } from "@/hooks/useSpellData"; + +interface PageCharacterSheetProps { + user: User | null; +} + +export interface Wearing { + armor: string | undefined; + shield: string | undefined; +} + +const PageCharacterSheet: React.FC< + PageCharacterSheetProps & React.ComponentPropsWithRef<"div"> +> = ({ className, user }) => { + const { character, setCharacter, userIsOwner, uid, id } = + useCharacterData(user); + const { + modalIsOpen, + setModalIsOpen, + modalTitle, + setModalTitle, + modalContent, + setModalContent, + modalOkRef, + } = useModal(); + const { isMobile, isTablet, isDesktop } = useDeviceType(); + const { isSpellCaster } = useSpellData(); + const classArr = character ? classSplit(character.class) : []; + const customClassMessage = marked(customClassString); + const moneyClassNames = classNames({ "w-1/3": !isMobile }); + + return character ? ( + + + + + + + +
+
} + editableComponent={ + + } + editable + /> + +
+ +
} + /> +
} + editable + editableComponent={ + + } + /> + + +
+ +
+ } + /> +
+ } + /> +
+ } + /> + + + + + {classArr.every((cls) => isStandardClass(cls)) ? ( + +
+
} + /> + +
+ + {classArr.map((cls) => { + const specialAbilities = + classes[cls as ClassNames]?.specialAbilities; + if (specialAbilities) { + return ( +
+ } + className={isMobile ? "mt-4" : ""} + /> + ); + } + })} +
} /> + + + + ) : ( + + )} + + +
+ +
} + /> +
} /> + + {isSpellCaster(character.class) && ( +
} /> + )} + +
+
+ } + /> + + + + +
+
} + /> + + + + + + ) : ( + + ); +}; + +export default PageCharacterSheet; diff --git a/src/components/PageCharacterSheet/SavingThrows/SavingThrows.tsx b/src/components/PageCharacterSheet/SavingThrows/SavingThrows.tsx new file mode 100644 index 00000000..668a5470 --- /dev/null +++ b/src/components/PageCharacterSheet/SavingThrows/SavingThrows.tsx @@ -0,0 +1,68 @@ +import { ColumnType } from "antd/es/table"; +import React from "react"; +import { Table } from "antd"; +import { + classSplit, + getBestSavingThrowList, + rollSavingThrow, +} from "@/support/characterSupport"; +import { camelCaseToTitleCase } from "@/support/stringSupport"; +import classNames from "classnames"; +import { CharacterDataContext } from "@/contexts/CharacterContext"; +import { useNotification } from "@/hooks/useNotification"; + +interface DataType { + key: number; + throw: string; + score: number; +} + +interface TableCellRecord { + score: number; + throw: string; +} + +const SavingThrows: React.FC> = ({ + className, +}) => { + const { character } = React.useContext(CharacterDataContext); + const { level, class: charClass, race } = character; + const savingThrows = getBestSavingThrowList(classSplit(charClass), level); + const dataSource: Array<{ key: number; throw: string; score: number }> = []; + const { contextHolder, openNotification } = useNotification(); + Object.entries(savingThrows).forEach(([key, value], index) => { + dataSource.push({ + key: index + 1, + throw: camelCaseToTitleCase(key), + score: value, + }); + }); + + const columns: ColumnType[] = [ + { + title: "Throw", + dataIndex: "throw", + key: "throw", + onCell: (record: TableCellRecord) => ({ + onClick: () => + rollSavingThrow(record.score, record.throw, race, openNotification), + }), + }, + { title: "Percentage", dataIndex: "score", key: "score" }, + ]; + const tableClassNames = classNames("[&_td]:cursor-pointer", className); + return ( + <> + {contextHolder} +
+ + ); +}; + +export default SavingThrows; diff --git a/src/components/PageCharacterSheet/Section/Section.tsx b/src/components/PageCharacterSheet/Section/Section.tsx new file mode 100644 index 00000000..9dc60e28 --- /dev/null +++ b/src/components/PageCharacterSheet/Section/Section.tsx @@ -0,0 +1,52 @@ +import { Button, Flex, Tooltip, Typography } from "antd"; +import React from "react"; +import HelpTooltip from "@/components/HelpTooltip/HelpTooltip"; +import { EditOutlined } from "@ant-design/icons"; + +interface SectionProps { + component: React.ReactNode; + title: string; + titleHelpText?: string; + editableComponent?: React.ReactNode; + editable?: boolean; +} + +const Section: React.FC> = ({ + className, + component, + title, + titleHelpText, + editable, + editableComponent, +}) => { + const [isEditing, setIsEditing] = React.useState(false); + + const handleEditClick = () => { + const editing = !isEditing; + setIsEditing(editing); + }; + + return ( + + + + {title} + + {editable && ( + +
+ + ); +}; + +export default SpecialAbilitiesTable; diff --git a/src/components/PageCharacterSheet/SpecialsRestrictions/SpecialsRestrictions.tsx b/src/components/PageCharacterSheet/SpecialsRestrictions/SpecialsRestrictions.tsx new file mode 100644 index 00000000..afc99b46 --- /dev/null +++ b/src/components/PageCharacterSheet/SpecialsRestrictions/SpecialsRestrictions.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { Tabs, type TabsProps } from "antd"; +import { ClassNames, RaceNames } from "@/data/definitions"; +import { races } from "@/data/races"; +import { classes } from "@/data/classes"; +import SpecialsRestrictionsList from "../SpecialsRestrictionsList/SpecialsRestrictionsList"; +import { titleCaseToCamelCase } from "@/support/stringSupport"; +import { classSplit } from "@/support/characterSupport"; +import { CharacterDataContext } from "@/contexts/CharacterContext"; + +const SpecialsRestrictions: React.FC> = ({ + className, +}) => { + const { character } = React.useContext(CharacterDataContext); + const classArr = classSplit(character.class); + const items: TabsProps["items"] = [ + { + key: titleCaseToCamelCase(character.race), + label: character.race, + children: ( + + ), + }, + ...classArr.map((cls) => ({ + key: titleCaseToCamelCase(cls), + label: cls, + children: ( + + ), + })), + ]; + return ; +}; + +export default SpecialsRestrictions; diff --git a/src/components/PageCharacterSheet/SpecialsRestrictionsList/SpecialsRestrictionsList.tsx b/src/components/PageCharacterSheet/SpecialsRestrictionsList/SpecialsRestrictionsList.tsx new file mode 100644 index 00000000..5d143a85 --- /dev/null +++ b/src/components/PageCharacterSheet/SpecialsRestrictionsList/SpecialsRestrictionsList.tsx @@ -0,0 +1,29 @@ +import { List } from "antd"; +import classNames from "classnames"; +import { marked } from "marked"; +import React from "react"; + +interface SpecialsRestrictionsListProps { + dataSource: string[]; +} + +const SpecialsRestrictionsList: React.FC< + SpecialsRestrictionsListProps & React.ComponentPropsWithRef<"div"> +> = ({ className, dataSource }) => { + const listClassNames = classNames("print:border-0", className); + return ( + ( + +
+ + )} + className={listClassNames} + size="small" + /> + ); +}; + +export default SpecialsRestrictionsList; diff --git a/src/components/PageCharacterSheet/Spells/SpellDescriptions/SpellDescriptions.tsx b/src/components/PageCharacterSheet/Spells/SpellDescriptions/SpellDescriptions.tsx new file mode 100644 index 00000000..5de28d4d --- /dev/null +++ b/src/components/PageCharacterSheet/Spells/SpellDescriptions/SpellDescriptions.tsx @@ -0,0 +1,47 @@ +import { Spell } from "@/data/definitions"; +import { useImages } from "@/hooks/useImages"; +import { toSlugCase } from "@/support/stringSupport"; +import { Descriptions, DescriptionsProps, Flex, Image, Typography } from "antd"; +import { marked } from "marked"; +import React from "react"; + +interface SpellDescriptionsProps { + spell: Spell; +} + +const SpellDescriptions: React.FC< + SpellDescriptionsProps & React.ComponentPropsWithRef<"div"> +> = ({ className, spell }) => { + const { getSpellImage } = useImages(); + const items: DescriptionsProps["items"] = [ + { + key: "1", + label: "Range", + children: spell.range, + }, + { + key: "2", + label: "Duration", + children: spell.duration, + }, + ]; + const spellImage = getSpellImage(toSlugCase(spell.name || "")); + return ( + <> + +
+ {spellImage && ( +
+ +
+ )} + +
+ + ); +}; + +export default SpellDescriptions; diff --git a/src/components/PageCharacterSheet/Spells/Spells.tsx b/src/components/PageCharacterSheet/Spells/Spells.tsx new file mode 100644 index 00000000..3a9fe07e --- /dev/null +++ b/src/components/PageCharacterSheet/Spells/Spells.tsx @@ -0,0 +1,22 @@ +import { CharacterDataContext } from "@/contexts/CharacterContext"; +import { Collapse, CollapseProps } from "antd"; +import React from "react"; +import SpellDescriptions from "./SpellDescriptions/SpellDescriptions"; + +interface SpellsProps {} + +const Spells: React.FC> = ({ + className, +}) => { + const { character } = React.useContext(CharacterDataContext); + const items: CollapseProps["items"] = character?.spells + .sort((a, b) => a.name.localeCompare(b.name)) + .map((spell) => ({ + key: spell.name, + label: spell.name, + children: , + })); + return ; +}; + +export default Spells; diff --git a/src/components/PageCharacterSheet/Weight/Weight.tsx b/src/components/PageCharacterSheet/Weight/Weight.tsx new file mode 100644 index 00000000..e9456dd6 --- /dev/null +++ b/src/components/PageCharacterSheet/Weight/Weight.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { RaceNames } from "@/data/definitions"; +import { Descriptions, DescriptionsProps, Flex } from "antd"; +import CharacterStat from "../CharacterStat/CharacterStat"; +import { + getCarryingCapacity, + getCharacterWeight, +} from "@/support/characterSupport"; +import { CharacterDataContext } from "@/contexts/CharacterContext"; +import { useDeviceType } from "@/hooks/useDeviceType"; + +const Weight: React.FC> = ({ + className, +}) => { + const { isMobile } = useDeviceType(); + const { character } = React.useContext(CharacterDataContext); + const capacity = getCarryingCapacity( + +character.abilities.scores.strength, + character.race as RaceNames, + ); + const [weight, setWeight] = React.useState(0); + const items: DescriptionsProps["items"] = [ + { key: "1", label: "Max Weight", children: capacity.heavy }, + { + key: "2", + label: "Load", + children: weight < capacity.light ? "Lightly Loaded" : "Heavily Loaded", + }, + ]; + + React.useEffect( + () => setWeight(getCharacterWeight(character)), + // eslint-disable-next-line react-hooks/exhaustive-deps + [character.gold, character.equipment], + ); + return ( + + + + + ); +}; + +export default Weight; diff --git a/src/components/PageGameSheet/GameBinder/GameBinder.tsx b/src/components/PageGameSheet/GameBinder/GameBinder.tsx new file mode 100644 index 00000000..a3000dc6 --- /dev/null +++ b/src/components/PageGameSheet/GameBinder/GameBinder.tsx @@ -0,0 +1,141 @@ +import { Alert, Flex, Table, Tabs, TabsProps } from "antd"; +import React from "react"; +import SpellList from "./SpellList/SpellList"; +import { ClassNames, GamePlayer } from "@/data/definitions"; +import { classes } from "@/data/classes"; +import { toSlugCase } from "@/support/stringSupport"; +import { ColumnType } from "antd/es/table"; +import Notes from "./Notes/Notes"; + +interface GameBinderProps { + players: GamePlayer[]; + showThiefAbilities: boolean; + showAssassinAbilities: boolean; + showRangerAbilities: boolean; + showScoutAbilities: boolean; + gameId: string; + uid: string; + notes?: string; +} + +const generateClassAbilities = (charClass: ClassNames) => { + const titles = classes[charClass].specialAbilities?.titles || []; + const stats = classes[charClass].specialAbilities?.stats.slice(1) || []; // Skip the first "level 0" entry + + const columns: ColumnType<{ [key: string]: number }>[] = [ + { title: "Level", dataIndex: "level", key: "level", fixed: "left" }, + ...titles.map((title) => ({ + title, + dataIndex: toSlugCase(title), + key: toSlugCase(title), + })), + ]; + + const dataSource = stats.map((statRow, index) => { + const row: { [key: string]: number } = { + key: index + 1, + level: index + 1, + }; + statRow.forEach((stat, statIndex) => { + row[toSlugCase(titles[statIndex])] = stat; + }); + return row; + }); + + return { + columns, + dataSource, + }; +}; + +const getAbilitiesTable = ( + charClass: string, + charData: ReturnType, +): TabsProps["items"] => [ + { + label: `${charClass} Abilities`, + key: `${charClass.toLowerCase()}-abilities`, + children: ( + + +
+ + ), + }, +]; + +const GameBinder: React.FC< + GameBinderProps & React.ComponentPropsWithRef<"div"> +> = ({ + notes, + uid, + gameId, + className, + showThiefAbilities, + showAssassinAbilities, + showRangerAbilities, + showScoutAbilities, +}) => { + const items: TabsProps["items"] = [ + { + label: "Notes", + key: "notes", + children: , + }, + { + label: "Spells", + key: "spells", + children: , + }, + { + label: "Monsters", + key: "monsters", + children: ( + + ), + }, + ]; + const assassinData = generateClassAbilities(ClassNames.ASSASSIN); + const rangerData = generateClassAbilities(ClassNames.RANGER); + const scoutData = generateClassAbilities(ClassNames.SCOUT); + const thiefData = generateClassAbilities(ClassNames.THIEF); + + const gameBinderItems: TabsProps["items"] = [ + ...items, + ...(showAssassinAbilities + ? getAbilitiesTable(ClassNames.ASSASSIN, assassinData) || [] + : []), + ...(showRangerAbilities + ? getAbilitiesTable(ClassNames.RANGER, rangerData) || [] + : []), + ...(showScoutAbilities + ? getAbilitiesTable(ClassNames.SCOUT, scoutData) || [] + : []), + ...(showThiefAbilities + ? getAbilitiesTable(ClassNames.THIEF, thiefData) || [] + : []), + ]; + + return ( + + ); +}; + +export default GameBinder; diff --git a/src/components/PageGameSheet/GameBinder/Notes/Notes.tsx b/src/components/PageGameSheet/GameBinder/Notes/Notes.tsx new file mode 100644 index 00000000..b365a987 --- /dev/null +++ b/src/components/PageGameSheet/GameBinder/Notes/Notes.tsx @@ -0,0 +1,68 @@ +import { Button, Flex, Input } from "antd"; +import React from "react"; +import { mobileBreakpoint } from "@/support/stringSupport"; +import { useMediaQuery } from "react-responsive"; +import { debounce, updateDocument } from "@/support/accountSupport"; +import { LoadingOutlined } from "@ant-design/icons"; + +interface NotesProps { + uid: string; + gameId: string; + notes?: string; +} + +const Notes: React.FC> = ({ + className, + uid, + gameId, + notes, +}) => { + const isMobile = useMediaQuery({ query: mobileBreakpoint }); + const [gameNotes, setGameNotes] = React.useState(notes || ""); + const [isSaving, setIsSaving] = React.useState(false); + + // Debounce function to save notes + // eslint-disable-next-line react-hooks/exhaustive-deps + const saveNotes = React.useCallback( + debounce(async (newNotes: string) => { + setIsSaving(true); + await updateDocument({ + collection: "users", // for example + docId: uid, + subCollection: "games", + subDocId: gameId, + data: { notes: newNotes }, + }); + setIsSaving(false); + }, 3000), + [gameId], + ); + + React.useEffect(() => { + const handler = setTimeout(() => saveNotes(gameNotes), 3000); + + return () => { + clearTimeout(handler); + }; + }, [gameNotes, saveNotes]); + + return ( + + setGameNotes(e.target.value)} + /> + + + ); +}; + +export default Notes; diff --git a/src/components/PageGameSheet/GameBinder/SpellList/SpellDescription/SpellDescription.tsx b/src/components/PageGameSheet/GameBinder/SpellList/SpellDescription/SpellDescription.tsx new file mode 100644 index 00000000..00e0c3fd --- /dev/null +++ b/src/components/PageGameSheet/GameBinder/SpellList/SpellDescription/SpellDescription.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { Spell } from "@/data/definitions"; +import { Descriptions, DescriptionsProps } from "antd"; +import { toTitleCase } from "@/support/stringSupport"; +import { marked } from "marked"; + +interface SpellDescriptionProps { + spell: Spell; +} + +const SpellDescription: React.FC< + SpellDescriptionProps & React.ComponentPropsWithRef<"div"> +> = ({ className, spell }) => { + const items: DescriptionsProps["items"] = [ + { + key: "range", + label: "Range", + children: spell.range, + }, + { + key: "level", + label: "Level", + children: Object.entries(spell.level) + .map(([key, value]) => !!value && `${toTitleCase(key)}: ${value}`) + .filter(Boolean) + .join(", "), + }, + { + key: "duration", + label: "Duration", + children: spell.duration, + }, + { + key: "description", + children: ( +
+ ), + }, + ]; + return ( + + ); +}; + +export default SpellDescription; diff --git a/src/components/PageGameSheet/GameBinder/SpellList/SpellList.tsx b/src/components/PageGameSheet/GameBinder/SpellList/SpellList.tsx new file mode 100644 index 00000000..f468b697 --- /dev/null +++ b/src/components/PageGameSheet/GameBinder/SpellList/SpellList.tsx @@ -0,0 +1,39 @@ +import { Collapse, CollapseProps, Flex, Input } from "antd"; +import React from "react"; +import spells from "@/data/spells.json"; +import SpellDescription from "./SpellDescription/SpellDescription"; + +interface SpellListProps {} + +const SpellList: React.FC< + SpellListProps & React.ComponentPropsWithRef<"div"> +> = ({ className }) => { + const [spellQuery, setSpellQuery] = React.useState(""); + + const handleInputChange = (event: React.ChangeEvent) => { + setSpellQuery(event.target.value); + }; + + const filteredSpells = spells.filter((spell) => + spell.name.toLowerCase().includes(spellQuery.toLowerCase()), + ); + + const items: CollapseProps["items"] = filteredSpells.map((spell) => ({ + key: spell.name, + label: spell.name, + children: , + })); + + return ( + + + + + ); +}; + +export default SpellList; diff --git a/src/components/PageGameSheet/PageGameSheet.tsx b/src/components/PageGameSheet/PageGameSheet.tsx new file mode 100644 index 00000000..9dcbbb00 --- /dev/null +++ b/src/components/PageGameSheet/PageGameSheet.tsx @@ -0,0 +1,95 @@ +import { User } from "firebase/auth"; +import React from "react"; +import { useParams } from "react-router-dom"; +import { GameData } from "@/data/definitions"; +import { fetchDocument } from "@/support/accountSupport"; +import { Breadcrumb, BreadcrumbProps, Flex, Skeleton } from "antd"; +import PlayerList from "./PlayerList/PlayerList"; +import GameBinder from "./GameBinder/GameBinder"; +import { useMediaQuery } from "react-responsive"; +import { mobileBreakpoint } from "@/support/stringSupport"; +import classNames from "classnames"; +import BreadcrumbHomeLink from "@/components/BreadcrumbHomeLink/BreadcrumbHomeLink"; +import { TeamOutlined } from "@ant-design/icons"; + +interface PageGameSheetProps { + user: User | null; +} + +const PageGameSheet: React.FC< + PageGameSheetProps & React.ComponentPropsWithRef<"div"> +> = ({ className, user }) => { + const { uid, id } = useParams(); + const userLoggedIn: User | null = user; + const userIsOwner = userLoggedIn?.uid === uid; + const [game, setGame] = React.useState(null); + const [showThiefAbilities, setShowThiefAbilities] = React.useState(false); + const [showAssassinAbilities, setShowAssassinAbilities] = + React.useState(false); + const [showRangerAbilities, setShowRangerAbilities] = React.useState(false); + const [showScoutAbilities, setShowScoutAbilities] = React.useState(false); + const isMobile = useMediaQuery({ query: mobileBreakpoint }); + + const gameBinderClassNames = classNames({ "w-1/2 shrink-0": !isMobile }); + + const breadcrumbItems: BreadcrumbProps["items"] = [ + { + title: , + }, + { + title: ( +
+ + {game?.name} +
+ ), + }, + ]; + + React.useEffect(() => { + const unsubscribe = fetchDocument(uid, id, setGame, "games"); + return () => unsubscribe(); + }, [uid, id]); + + return game ? ( + <> + + + {game.players && uid && id && ( + <> + + + + )} + + + ) : ( + + ); +}; + +export default PageGameSheet; diff --git a/src/components/PageGameSheet/PlayerList/AddPlayerForm/AddPlayerForm.tsx b/src/components/PageGameSheet/PlayerList/AddPlayerForm/AddPlayerForm.tsx new file mode 100644 index 00000000..60eee00a --- /dev/null +++ b/src/components/PageGameSheet/PlayerList/AddPlayerForm/AddPlayerForm.tsx @@ -0,0 +1,59 @@ +import { Button, Flex, Input, Space, Typography, message } from "antd"; +import React from "react"; +import { addPlayerToGame } from "@/support/accountSupport"; +import { UserAddOutlined } from "@ant-design/icons"; + +interface AddPlayerFormProps { + gmId: string; + gameId: string; + userIsOwner: boolean; +} + +const AddPlayerForm: React.FC< + AddPlayerFormProps & React.ComponentPropsWithRef<"div"> +> = ({ className, gmId, gameId, userIsOwner }) => { + const [playerUrl, setPlayerUrl] = React.useState(""); + + const handleUrlChange = (value: string) => setPlayerUrl(value); + + const onFinish = async () => { + try { + await addPlayerToGame(playerUrl, gameId, gmId); + // Handle additional UI logic like resetting form, updating local state, etc. + message.success("Character added successfully."); + setPlayerUrl(""); + // If you maintain a local state of players, update it here + // setPlayers([...players, characterData]); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + message.error(error.message); + } + }; + + return ( + + + handleUrlChange(e.target.value)} + placeholder="http://codex.quest/u/AsxtzoU61db5IAA6d9IrEFFjh6a2/c/qK3N1Oe0JChp1iWLduqW" + disabled={!userIsOwner} + /> + + + + Enter a character's codex.quest URL + + + ); +}; + +export default AddPlayerForm; diff --git a/src/components/PageGameSheet/PlayerList/PlayerList.tsx b/src/components/PageGameSheet/PlayerList/PlayerList.tsx new file mode 100644 index 00000000..f06d29b5 --- /dev/null +++ b/src/components/PageGameSheet/PlayerList/PlayerList.tsx @@ -0,0 +1,110 @@ +import React from "react"; +import { GamePlayerList } from "@/data/definitions"; +import { Button, Card, Descriptions, Flex, Spin, Tooltip } from "antd"; +import { openInNewTab } from "@/support/characterSupport"; +import classNames from "classnames"; +import { SolutionOutlined, UserDeleteOutlined } from "@ant-design/icons"; +import AddPlayerForm from "./AddPlayerForm/AddPlayerForm"; +import { useGameCharacters } from "@/hooks/useGameCharacters"; + +interface PlayerListProps { + players: GamePlayerList; + setShowThiefAbilities: (showThiefAbilities: boolean) => void; + setShowAssassinAbilities: (showAssassinAbilities: boolean) => void; + setShowRangerAbilities: (showRangerAbilities: boolean) => void; + setShowScoutAbilities: (showScoutAbilities: boolean) => void; + gmId: string; + gameId: string; + userIsOwner: boolean; +} + +const PlayerList: React.FC< + PlayerListProps & React.ComponentPropsWithRef<"div"> +> = ({ + className, + players, + setShowThiefAbilities, + setShowAssassinAbilities, + setShowRangerAbilities, + setShowScoutAbilities, + gmId, + gameId, + userIsOwner, +}) => { + const [ + characterList, + removePlayer, + generateAbilityItems, + generateDetailItems, + calculateClassAbilitiesToShow, + ] = useGameCharacters(players); + const playerListClassNames = classNames(className); + + React.useEffect(() => { + const { showThief, showAssassin, showRanger, showScout } = + calculateClassAbilitiesToShow(characterList); + setShowThiefAbilities(showThief); + setShowAssassinAbilities(showAssassin); + setShowRangerAbilities(showRanger); + setShowScoutAbilities(showScout); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [characterList]); + + const onRemoveButtonClick = (userId?: string, characterId?: string) => { + userId && characterId && removePlayer(gameId, userId, characterId); + }; + + return characterList.length ? ( + + + {characterList + .sort((a, b) => a.name.localeCompare(b.name)) + .map((character) => { + const { abilities, name, userId, charId } = character; + const items = generateAbilityItems(abilities.scores); + const subItems = generateDetailItems(character); + return ( + + + + + + + + + + + + + ); + })} + + ) : ( + + ); +}; + +export default PlayerList; diff --git a/src/components/PageHome/PageHome.tsx b/src/components/PageHome/PageHome.tsx new file mode 100644 index 00000000..5f0139ea --- /dev/null +++ b/src/components/PageHome/PageHome.tsx @@ -0,0 +1,75 @@ +import { Spin, Tabs, TabsProps } from "antd"; +import React from "react"; +import ContentListWrapper from "@/components/ContentListWrapper/ContentListWrapper"; +import { CharData, GameData } from "@/data/definitions"; +import { fetchCollection } from "@/support/accountSupport"; +import { User } from "firebase/auth"; +import CardCharacter from "@/components/CardCharacter/CardCharacter"; +import CardGame from "@/components/CardGame/CardGame"; + +interface PageHomeProps { + user: User | null; +} + +const PageHome: React.FC = ({ user }) => { + const [selectedKey, setSelectedKey] = React.useState("1"); + const [characters, setCharacters] = React.useState([]); + const [games, setGames] = React.useState([]); + const [charactersLoading, setCharactersLoading] = + React.useState(true); + const [gamesLoading, setGamesLoading] = React.useState(true); + + React.useEffect(() => { + fetchCollection( + user, + "characters", + setCharacters, + setCharactersLoading, + "Home", + ); + fetchCollection(user, "games", setGames, setGamesLoading, "Home"); + }, [user]); + + const handleTabChange = (key: string) => { + setSelectedKey(key); + }; + + const childrenCharacterList = ( + } + className="mx-auto" + > + {characters.map((character) => ( + + ))} + + ); + + const childrenGameList = ( + } + > + {games.map((game) => ( + + ))} + + ); + + const tabsItems: TabsProps["items"] = [ + { key: "1", label: "Characters", children: childrenCharacterList }, + { key: "2", label: "Games", children: childrenGameList }, + ]; + + return ( + + ); +}; + +export default PageHome; diff --git a/src/components/PageLayout/HeaderContent/HeaderContent.tsx b/src/components/PageLayout/HeaderContent/HeaderContent.tsx deleted file mode 100755 index c0189928..00000000 --- a/src/components/PageLayout/HeaderContent/HeaderContent.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { Link, useLocation } from "react-router-dom"; -import { Button, Switch, Tooltip, Typography } from "antd"; -import { LogoutOutlined } from "@ant-design/icons"; -import LoginSignupModal from "../../../modals/LoginSignupModal"; -import { useState } from "react"; -import { title } from "../../../../package.json"; -import classNames from "classnames"; -import DragonIcon from "../../../assets/images/spiked-dragon-head.png"; -import { Auth, User } from "firebase/auth"; -import { MODE, ModeType } from "../../../data/definitions"; - -type HeaderContentProps = { - user: User | null; - handleLogin: () => Promise; - auth: Auth; - mode: ModeType; - setMode: (mode: ModeType) => void; -}; - -export default function HeaderContent({ - auth, - handleLogin, - user, - className, - mode, - setMode, -}: HeaderContentProps & React.ComponentPropsWithRef<"div">) { - const [isLoginSignupModalOpen, setIsLoginSignupModalOpen] = useState(false); - const location = useLocation(); - const isHomePage = location.pathname === "/"; - - const handleCancel = () => setIsLoginSignupModalOpen(false); - const handleModeSwitchChange = (checked: boolean) => { - if (checked) { - setMode(MODE.PLAYER); - } else { - setMode(MODE.GM); - } - }; - - const headerContentClassNames = classNames( - "gap-y-2", - "grid", - "grid-cols-[1fr,auto]", - "grid-rows-[auto,auto]", - className - ); - const buttonTextClassNames = classNames("hidden", "md:inline"); - const titleClassNames = classNames( - "text-white/95", - "font-enchant", - "tracking-wider", - "text-4xl", - "md:text-5xl", - "flex", - "margin-x-auto", - "gap-2", - "justify-center", - "items-center" - ); - - const displayTitle = title.split(" "); - return ( -
- - - {displayTitle[0]} - Dragon Icon - {displayTitle[1]} - - - {user && isHomePage && ( -
- {MODE.GM} - } - defaultChecked - /> -
- )} - <> - {user ? ( -
- - {user.displayName || user.email} - - -
- ) : ( - - )} - - -
- ); -} diff --git a/src/components/PageLayout/FooterContent/FooterContent.tsx b/src/components/PageLayout/PageFooter/PageFooter.tsx old mode 100755 new mode 100644 similarity index 83% rename from src/components/PageLayout/FooterContent/FooterContent.tsx rename to src/components/PageLayout/PageFooter/PageFooter.tsx index 33727132..84d65565 --- a/src/components/PageLayout/FooterContent/FooterContent.tsx +++ b/src/components/PageLayout/PageFooter/PageFooter.tsx @@ -1,20 +1,20 @@ +import { Flex, Typography } from "antd"; import React from "react"; -import { Typography } from "antd"; -import { Link } from "react-router-dom"; -import classNames from "classnames"; import { version, bfrpgRelease, bfrpgEdition } from "../../../../package.json"; +import classNames from "classnames"; +import { Link } from "react-router-dom"; +interface PageFooterProps {} -export default function FooterContent({ - className, -}: React.ComponentPropsWithRef<"div">) { - const FooterContentClassNames = classNames( +const PageFooter: React.FC< + PageFooterProps & React.ComponentPropsWithRef<"div"> +> = ({ className }) => { + const footerClassNames = classNames( className, - "[&_*]:text-springWood", - "[&_a]:text-seaBuckthorn" + "[&_div]:text-springWood", + "[&_a]:text-seaBuckthorn", ); - return ( - + + ); -} +}; + +export default PageFooter; diff --git a/src/components/PageLayout/PageHeader/PageHeader.tsx b/src/components/PageLayout/PageHeader/PageHeader.tsx new file mode 100644 index 00000000..2d43e44d --- /dev/null +++ b/src/components/PageLayout/PageHeader/PageHeader.tsx @@ -0,0 +1,69 @@ +import { User } from "firebase/auth"; +import React from "react"; +import { handleLogin } from "@/support/accountSupport"; +import { title } from "../../../../package.json"; +import DragonIcon from "@/assets/images/dragon-head.png"; +import { Button, Flex, Tooltip, Typography } from "antd"; +import classNames from "classnames"; +import LoginSignupModal from "@/components/ModalLoginSignup/ModalLoginSignup"; +import { LoginOutlined, LogoutOutlined } from "@ant-design/icons"; +import { auth } from "@/firebase"; +import { Link } from "react-router-dom"; + +interface PageHeaderProps { + user: User | null; +} + +const PageHeader: React.FC< + PageHeaderProps & React.ComponentPropsWithRef<"div"> +> = ({ user, className }) => { + const [isLoginSignupModalOpen, setIsLoginSignupModalOpen] = + React.useState(false); + const handleCancel = () => setIsLoginSignupModalOpen(false); + + const siteTitle = title.split(" "); + + const pageHeaderContainerClassNames = classNames(className); + return ( + + + + {siteTitle[0]} + Dragon Icon + {siteTitle[1]} + + +
+ {user ? ( + +
+ +
+ ); +}; + +export default PageHeader; diff --git a/src/components/PageLayout/PageLayout.tsx b/src/components/PageLayout/PageLayout.tsx old mode 100755 new mode 100644 index d4d60192..93b18112 --- a/src/components/PageLayout/PageLayout.tsx +++ b/src/components/PageLayout/PageLayout.tsx @@ -1,76 +1,55 @@ -import { Alert, Layout } from "antd"; -import FooterContent from "./FooterContent/FooterContent"; -import { Outlet } from "react-router-dom"; -import HeaderContent from "./HeaderContent/HeaderContent"; +import { Flex, FloatButton, Layout } from "antd"; +import React from "react"; +import PageHeader from "./PageHeader/PageHeader"; +import PageFooter from "./PageFooter/PageFooter"; +import { Outlet, useLocation, useNavigate } from "react-router-dom"; +import { User } from "firebase/auth"; import classNames from "classnames"; -import { User, Auth } from "firebase/auth"; -import { ModeType } from "../../data/definitions"; +import { UserAddOutlined, UsergroupAddOutlined } from "@ant-design/icons"; -type PageLayoutProps = { +interface PageLayoutProps { user: User | null; - handleLogin: () => Promise; - auth: Auth; - mode: ModeType; - setMode: (mode: ModeType) => void; -}; +} -export default function PageLayout({ - auth, - handleLogin, - user, - mode, - setMode, -}: PageLayoutProps) { - const headerClassNames = classNames( - "bg-shipGray", +const PageLayout: React.FC = ({ user }) => { + const contentWidthClassName = "max-w-[1200px] mx-auto w-full"; + const layoutContentClassName = classNames( + contentWidthClassName, "p-4", - "md:p-6", - "h-auto", - "flex-[0_1_auto]", - "lg:px-8", - "print:hidden" - ); - const contentClassNames = classNames( - "bg-springWood", - "p-4", - "md:p-6", - "flex-[1_1_auto]", - "inline-table", - "lg:p-8", - "print:p-0" - ); - const footerClassNames = classNames( - "bg-shipGray", - "p-4", - "md:p-6", - "flex-[0_1_auto]", - "print:hidden" + "flex-[1_0_auto]", ); + const location = useLocation(); + const isHomePage = location.pathname === "/"; + const navigate = useNavigate(); return ( - - - + + + - - - + + + {isHomePage && ( + + } + tooltip={
Create New Character
} + onClick={() => navigate("/new-character")} + /> + } + tooltip={
Create New Game
} + onClick={() => navigate("/new-game")} + /> +
+ )} + +
- - + +
); -} +}; + +export default PageLayout; diff --git a/src/components/PageNewCharacter/PageNewCharacter.tsx b/src/components/PageNewCharacter/PageNewCharacter.tsx new file mode 100644 index 00000000..58206a9c --- /dev/null +++ b/src/components/PageNewCharacter/PageNewCharacter.tsx @@ -0,0 +1,128 @@ +import { + Breadcrumb, + BreadcrumbProps, + Button, + Col, + Flex, + Row, + Steps, + message, +} from "antd"; +import { User } from "firebase/auth"; +import React from "react"; +import BreadcrumbHomeLink from "@/components/BreadcrumbHomeLink/BreadcrumbHomeLink"; +import { UserAddOutlined } from "@ant-design/icons"; +import { CharData } from "@/data/definitions"; +import { useNavigate } from "react-router-dom"; +import NewContentWrapper from "@/components/NewContentWrapper/NewContentWrapper"; +import { + addCharacterData, + getStepsItems, + isNextButtonEnabled, +} from "@/support/pageNewCharacterSupport"; + +interface PageNewCharacterProps { + user: User | null; +} + +const breadcrumbItems: BreadcrumbProps["items"] = [ + { title: }, + { + title: ( +
+ + New Character +
+ ), + }, +]; + +const PageNewCharacter: React.FC< + PageNewCharacterProps & React.ComponentPropsWithRef<"div"> +> = ({ className }) => { + // HOOKS + const navigate = useNavigate(); + const [currentStep, setCurrentStep] = React.useState(0); + const [character, setCharacter] = React.useState({ + level: 1, + xp: 0, + } as CharData); + const [comboClass, setComboClass] = React.useState(false); + const [comboClassSwitch, setComboClassSwitch] = React.useState(false); + const [messageApi, contextHolder] = message.useMessage(); + // VARS + const next = () => setCurrentStep(currentStep + 1); + const prev = () => setCurrentStep(currentStep - 1); + const stepsItems = getStepsItems( + character, + setCharacter, + setComboClass, + setComboClassSwitch, + comboClass, + comboClassSwitch, + ); + const items = stepsItems.map((item) => ({ + key: item.title, + title: item.title, + })); + + return ( + <> + {contextHolder} + + +
+ + + + + + {currentStep > 0 && ( + + )} + {currentStep < stepsItems.length - 1 && ( + + )} + {currentStep === stepsItems.length - 1 && ( + + )} + + {stepsItems[currentStep].content} + + + + + ); +}; + +export default PageNewCharacter; diff --git a/src/components/PageNewCharacter/RaceClassDescription/RaceClassDescription.tsx b/src/components/PageNewCharacter/RaceClassDescription/RaceClassDescription.tsx new file mode 100644 index 00000000..66cce630 --- /dev/null +++ b/src/components/PageNewCharacter/RaceClassDescription/RaceClassDescription.tsx @@ -0,0 +1,44 @@ +import { Card, Flex, Image } from "antd"; +import { marked } from "marked"; +import React from "react"; +import { useDeviceType } from "@/hooks/useDeviceType"; +import classNames from "classnames"; +import { useImages } from "@/hooks/useImages"; + +interface RaceClassDescriptionProps { + name: string; + description: string; +} + +const RaceClassDescription: React.FC< + RaceClassDescriptionProps & React.ComponentPropsWithRef<"div"> +> = ({ className, name, description }) => { + const { getRaceClassImage } = useImages(); + const raceClassImage = getRaceClassImage(name); + const { isMobile } = useDeviceType(); + const descriptionClassNames = classNames({ "flex-col-reverse": isMobile }); + return ( + {name} + } + > + +
+ + + + ); +}; + +export default RaceClassDescription; diff --git a/src/components/PageNewCharacter/RaceClassSelector/RaceClassSelector.tsx b/src/components/PageNewCharacter/RaceClassSelector/RaceClassSelector.tsx new file mode 100644 index 00000000..e497f499 --- /dev/null +++ b/src/components/PageNewCharacter/RaceClassSelector/RaceClassSelector.tsx @@ -0,0 +1,66 @@ +import { Flex, Input, Select, SelectProps, Switch, Typography } from "antd"; +import React from "react"; +import HomebrewWarning from "@/components/HomebrewWarning/HomebrewWarning"; +import Checkbox, { CheckboxGroupProps } from "antd/es/checkbox"; +import spells from "@/data/spells.json"; + +interface RaceClassSelectorProps { + selectOptions: SelectProps["options"]; + selector: string; + handleSelectChange: (value: string) => void; + customInput: string; + handleCustomInputChange: (e: React.ChangeEvent) => void; + type: "race" | "class"; +} + +const RaceClassSelector: React.FC< + RaceClassSelectorProps & React.ComponentPropsWithRef<"div"> +> = ({ + selectOptions, + selector, + handleSelectChange, + customInput, + handleCustomInputChange, + type, +}) => { + const [showSpells, setShowSpells] = React.useState(false); + + const handleStartingSpellsSwitchChange = (checked: boolean) => { + setShowSpells(checked); + }; + + const options: CheckboxGroupProps["options"] = spells.map((spell) => ({ + label: spell.name, + value: spell.name, + })); + return ( + <> + + + + Add Starting Spells? + + + {showSpells && ( + + )} + + + )} + + ); +}; + +export default RaceClassSelector; diff --git a/src/components/CharacterCreator/CharacterAbilities/AbilityRoller/AbilityRoller.tsx b/src/components/PageNewCharacter/StepAbilities/AbilityRoller/AbilityRoller.tsx similarity index 83% rename from src/components/CharacterCreator/CharacterAbilities/AbilityRoller/AbilityRoller.tsx rename to src/components/PageNewCharacter/StepAbilities/AbilityRoller/AbilityRoller.tsx index 172ef443..809af081 100755 --- a/src/components/CharacterCreator/CharacterAbilities/AbilityRoller/AbilityRoller.tsx +++ b/src/components/PageNewCharacter/StepAbilities/AbilityRoller/AbilityRoller.tsx @@ -1,13 +1,13 @@ import { Space, InputNumber, Button } from "antd"; -import { AbilityRecord } from "../../../../data/definitions"; +import { AbilityRecord } from "@/data/definitions"; type AbilityRollerProps = { - rollDice: () => any; + rollDice: (dice: string) => any; abilityValue: number; getModifier: (score: number) => string; updateCharacterData: ( scores: Record, - modifiers: Record + modifiers: Record, ) => void; record: AbilityRecord; }; @@ -20,7 +20,7 @@ export default function AbilityRoller({ record, }: AbilityRollerProps) { const rollAbilityScore = (ability: string, score?: number) => { - const newScore = score || rollDice(); + const newScore = score || rollDice("3d6"); const modifier = getModifier(newScore); updateCharacterData({ [ability]: newScore }, { [ability]: modifier }); }; @@ -43,7 +43,7 @@ export default function AbilityRoller({ max={18} min={3} defaultValue={0} - onChange={onChange} + onChange={(event: any) => onChange(event)} onFocus={handleFocus} type="number" value={abilityValue} @@ -52,7 +52,7 @@ export default function AbilityRoller({ diff --git a/src/components/PageNewCharacter/StepAbilities/StepAbilities.tsx b/src/components/PageNewCharacter/StepAbilities/StepAbilities.tsx new file mode 100644 index 00000000..aa6df03a --- /dev/null +++ b/src/components/PageNewCharacter/StepAbilities/StepAbilities.tsx @@ -0,0 +1,176 @@ +import { Button, Flex, Table } from "antd"; +import React from "react"; +import { AbilityRecord, CharData } from "@/data/definitions"; +import { + getModifier, + isAbilityKey, + rollDice, +} from "@/support/characterSupport"; +import AbilityRoller from "./AbilityRoller/AbilityRoller"; + +interface StepAbilitiesProps { + character: CharData; + setCharacter: (character: CharData) => void; + setComboClass?: (comboClass: boolean) => void; + setComboClassSwitch?: (comboClassSwitch: boolean) => void; + hideRollAll?: boolean; +} + +const StepAbilities: React.FC< + StepAbilitiesProps & React.ComponentPropsWithRef<"div"> +> = ({ + className, + character, + setCharacter, + setComboClass, + hideRollAll, + setComboClassSwitch, +}) => { + const dataSource = [ + { + key: "1", + label: "STR", + ability: "Strength", + score: Number(character.abilities?.scores.strength) || 0, + modifier: character.abilities?.modifiers?.strength || "", + }, + { + key: "2", + label: "INT", + ability: "Intelligence", + score: Number(character.abilities?.scores.intelligence) || 0, + modifier: character.abilities?.modifiers?.intelligence || "", + }, + { + key: "3", + label: "WIS", + ability: "Wisdom", + score: Number(character.abilities?.scores.wisdom) || 0, + modifier: character.abilities?.modifiers?.wisdom || "", + }, + { + key: "4", + label: "DEX", + ability: "Dexterity", + score: Number(character.abilities?.scores.dexterity) || 0, + modifier: character.abilities?.modifiers?.dexterity || "", + }, + { + key: "5", + label: "CON", + ability: "Constitution", + score: Number(character.abilities?.scores.constitution) || 0, + modifier: character.abilities?.modifiers?.constitution || "", + }, + { + key: "6", + label: "CHA", + ability: "Charisma", + score: Number(character.abilities?.scores.charisma) || 0, + modifier: character.abilities?.modifiers?.charisma || "", + }, + ]; + + const columns = [ + { + title: "Ability", + dataIndex: "label", + key: "ability", + }, + { + title: "Score", + dataIndex: "score", + key: "score", + // eslint-disable-next-line @typescript-eslint/no-unused-vars + render: (_text: string, record: AbilityRecord, _index: number) => { + const abilityKey = record.ability.toLowerCase(); + let abilityValue = 0; + if (isAbilityKey(abilityKey, character)) { + abilityValue = + +character.abilities.scores[ + abilityKey as keyof typeof character.abilities.scores + ]; + } + + return ( + + ); + }, + }, + { + title: "Modifier", + dataIndex: "modifier", + key: "modifier", + }, + ]; + + const updateCharacter = ( + scores: Record, + modifiers: Record, + ) => { + setCharacter({ + ...character, + abilities: { + scores: { ...character.abilities?.scores, ...scores }, + modifiers: { ...character.abilities?.modifiers, ...modifiers }, + }, + class: character.class || [], + race: character.race || "", + hp: { + dice: character.hp?.dice || "", + points: character.hp?.points || 0, + max: character.hp?.max || 0, + desc: character.hp?.desc || "", + }, + equipment: character.equipment || [], + }); + setComboClass && setComboClass(false); + setComboClassSwitch && setComboClassSwitch(false); + }; + + const rollAllAbilities = () => { + const abilities = [ + "strength", + "intelligence", + "wisdom", + "dexterity", + "constitution", + "charisma", + ]; + const newScores: Record = {}; + const newModifiers: Record = {}; + + abilities.forEach((ability) => { + const score = rollDice("3d6"); + newScores[ability] = score; + newModifiers[ability] = getModifier(score); + }); + + updateCharacter(newScores, newModifiers); + }; + + return ( + + {!hideRollAll && ( + + )} +
+ + ); +}; + +export default StepAbilities; diff --git a/src/components/PageNewCharacter/StepClass/ComboClassOptions/ComboClassOptions.tsx b/src/components/PageNewCharacter/StepClass/ComboClassOptions/ComboClassOptions.tsx new file mode 100644 index 00000000..4e85e488 --- /dev/null +++ b/src/components/PageNewCharacter/StepClass/ComboClassOptions/ComboClassOptions.tsx @@ -0,0 +1,61 @@ +import React from "react"; +import { CharData, ClassNames, RaceNames } from "@/data/definitions"; +import { Flex, Select, Typography } from "antd"; +import { races } from "@/data/races"; +import { DefaultOptionType } from "antd/es/select"; + +interface ComboClassOptionsProps { + character: CharData; + setCharacter: (character: CharData) => void; + firstClass: ClassNames; + secondClass: ClassNames | undefined; + setSecondClass: (secondClass: ClassNames) => void; +} + +const ComboClassOptions: React.FC< + ComboClassOptionsProps & React.ComponentPropsWithRef<"div"> +> = ({ + className, + character, + // setCharacter, + firstClass, + secondClass, + setSecondClass, +}) => { + const comboClassOptions = races[ + character.race as RaceNames + ]?.allowedCombinationClasses + ?.map((className: ClassNames) => { + if (className !== ClassNames.MAGICUSER) { + return { value: className, label: className }; + } + return null; + }) + .filter(Boolean) as DefaultOptionType[]; + + const handleSecondClassChange = (value: ClassNames) => { + setSecondClass(value); + }; + + return ( + + + First Class + + + + ); +}; + +export default ComboClassOptions; diff --git a/src/components/PageNewCharacter/StepClass/Options/Options.tsx b/src/components/PageNewCharacter/StepClass/Options/Options.tsx new file mode 100644 index 00000000..cf64f0e6 --- /dev/null +++ b/src/components/PageNewCharacter/StepClass/Options/Options.tsx @@ -0,0 +1,41 @@ +import { useDeviceType } from "@/hooks/useDeviceType"; +import { Flex, Switch, Typography } from "antd"; +import React from "react"; + +interface OptionsProps { + comboClass?: boolean; + comboClassSwitch?: boolean; + setComboClassSwitch: (comboClassSwitch: boolean) => void; + supplementalContentSwitch: boolean; + setSupplementalContentSwitch: (supplementalContentSwitch: boolean) => void; +} + +const Options: React.FC> = ({ + className, + comboClass, + comboClassSwitch, + setComboClassSwitch, + supplementalContentSwitch, + setSupplementalContentSwitch, +}) => { + const { isMobile } = useDeviceType(); + return ( + + + Enable Supplemental Content + + + {comboClass && ( + + Combination Class + + + )} + + ); +}; + +export default Options; diff --git a/src/components/PageNewCharacter/StepClass/SpellOptions/SpellOptions.tsx b/src/components/PageNewCharacter/StepClass/SpellOptions/SpellOptions.tsx new file mode 100644 index 00000000..0433f1ea --- /dev/null +++ b/src/components/PageNewCharacter/StepClass/SpellOptions/SpellOptions.tsx @@ -0,0 +1,105 @@ +import React from "react"; +import spells from "@/data/spells.json"; +import { CharData, Spell } from "@/data/definitions"; +import { + Card, + Descriptions, + Flex, + Image, + Select, + SelectProps, + Typography, +} from "antd"; +import { marked } from "marked"; +import { toSlugCase } from "@/support/stringSupport"; +import { useDeviceType } from "@/hooks/useDeviceType"; +import classNames from "classnames"; +import { useImages } from "@/hooks/useImages"; +import { getSpellsAtLevel } from "@/support/spellSupport"; + +interface SpellOptionsProps { + character: CharData; + characterClass: string | undefined; + startingSpells: string[]; + setStartingSpells: (startingSpells: string[]) => void; +} + +const SpellDescription: React.FC<{ spell: Spell }> = ({ spell }) => ( +
+ +
+
+); + +const SpellOptions: React.FC< + SpellOptionsProps & React.ComponentPropsWithRef<"div"> +> = ({ character, characterClass, startingSpells, setStartingSpells }) => { + const { isMobile } = useDeviceType(); + const { getSpellImage } = useImages(); + const selectedSpell = + spells.find((spell: Spell) => spell.name === startingSpells[0]) || + ({ description: "", duration: "", range: "" } as Spell); + const levelOneSpells = getSpellsAtLevel(character); + const spellImage = getSpellImage(toSlugCase(startingSpells[0] || "")); + const descriptionClassNames = classNames({ "flex-col": isMobile }); + const selectOptions: SelectProps["options"] = levelOneSpells + .map((spell: Spell) => ({ value: spell.name, label: spell.name })) + .sort((a, b) => a.label.localeCompare(b.label)); + const handleStartingSpellChange = (value: string) => { + setStartingSpells([value]); + }; + return ( + + Choose a spell + + } + > + + +
+ + + + + ); +}; + +export default StepDetails; diff --git a/src/components/PageNewCharacter/StepEquipment/CharacterInventory/CharacterInventory.tsx b/src/components/PageNewCharacter/StepEquipment/CharacterInventory/CharacterInventory.tsx new file mode 100644 index 00000000..9ebe8002 --- /dev/null +++ b/src/components/PageNewCharacter/StepEquipment/CharacterInventory/CharacterInventory.tsx @@ -0,0 +1,31 @@ +import { EquipmentItem } from "@/data/definitions"; +import { Divider, List } from "antd"; +import React from "react"; +import CharacterInventoryItem from "../CharacterInventoryItem/CharacterInventoryItem"; +import classNames from "classnames"; + +interface CharacterInventoryProps { + equipment: EquipmentItem[]; +} + +const CharacterInventory: React.FC< + CharacterInventoryProps & React.ComponentPropsWithRef<"div"> +> = ({ className, equipment }) => { + const characterInventoryClassNames = classNames("sticky top-0", className); + const dataSource = [...equipment]; + return ( +
+
+ + Inventory + + } + /> +
+
+ ); +}; + +export default CharacterInventory; diff --git a/src/components/PageNewCharacter/StepEquipment/CharacterInventoryItem/CharacterInventoryItem.tsx b/src/components/PageNewCharacter/StepEquipment/CharacterInventoryItem/CharacterInventoryItem.tsx new file mode 100644 index 00000000..e5941cc0 --- /dev/null +++ b/src/components/PageNewCharacter/StepEquipment/CharacterInventoryItem/CharacterInventoryItem.tsx @@ -0,0 +1,20 @@ +import { EquipmentItem } from "@/data/definitions"; +import { Flex, Typography } from "antd"; +import React from "react"; + +interface CharacterInventoryItemProps { + item: EquipmentItem; +} + +const CharacterInventoryItem: React.FC< + CharacterInventoryItemProps & React.ComponentPropsWithRef<"div"> +> = ({ className, item }) => { + return ( + + {item.name} + {item.amount}x + + ); +}; + +export default CharacterInventoryItem; diff --git a/src/components/PageNewCharacter/StepEquipment/GoldRoller/GoldRoller.tsx b/src/components/PageNewCharacter/StepEquipment/GoldRoller/GoldRoller.tsx new file mode 100644 index 00000000..826cfaa3 --- /dev/null +++ b/src/components/PageNewCharacter/StepEquipment/GoldRoller/GoldRoller.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { Button, InputNumber, Space } from "antd"; +import { rollDice } from "@/support/characterSupport"; + +interface GoldRollerProps { + gold: number; + setGold: (gold: number) => void; +} + +const GoldRoller: React.FC< + GoldRollerProps & React.ComponentPropsWithRef<"div"> +> = ({ className, gold, setGold }) => { + const goldDiceFormula = "3d6*10"; + + const handleGoldChange = (value: number | null) => { + setGold(value || 0); + }; + const handleButtonClick = () => { + setGold(rollDice(goldDiceFormula)); + }; + + return ( + + + + + ); +}; + +export default GoldRoller; diff --git a/src/components/PageNewCharacter/StepEquipment/StepEquipment.tsx b/src/components/PageNewCharacter/StepEquipment/StepEquipment.tsx new file mode 100644 index 00000000..b1b054be --- /dev/null +++ b/src/components/PageNewCharacter/StepEquipment/StepEquipment.tsx @@ -0,0 +1,63 @@ +import { Flex, FloatButton } from "antd"; +import React from "react"; +import GoldRoller from "./GoldRoller/GoldRoller"; +import CharacterInventory from "./CharacterInventory/CharacterInventory"; +import { CharData, EquipmentItem } from "@/data/definitions"; +import EquipmentStore from "@/components/EquipmentStore/EquipmentStore"; +import { useDeviceType } from "@/hooks/useDeviceType"; +import classNames from "classnames"; + +interface StepEquipmentProps { + character: CharData; + setCharacter: (character: CharData) => void; + hideDiceButton?: boolean; + hideInventory?: boolean; +} + +// TODO: RACE/CLASS RESTRICTIONS +console.error("race/class equipment restrictions not implemented yet"); + +const StepEquipment: React.FC< + StepEquipmentProps & React.ComponentPropsWithRef<"div"> +> = ({ className, character, setCharacter, hideDiceButton, hideInventory }) => { + const [gold, setGold] = React.useState(character.gold || 0); + const [equipment, setEquipment] = React.useState( + character.equipment || [], + ); + const { isMobile } = useDeviceType(); + const flexClassNames = classNames({ + "[&>div]:flex-[0_1_50%]": !hideInventory, + }); + React.useEffect(() => { + setCharacter({ + ...character, + gold, + equipment, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [equipment, gold]); + + return ( + + {!hideDiceButton && } + + + {!hideInventory && ( + + )} + + + + ); +}; + +export default StepEquipment; diff --git a/src/components/PageNewCharacter/StepHitPoints/StepHitPoints.tsx b/src/components/PageNewCharacter/StepHitPoints/StepHitPoints.tsx new file mode 100644 index 00000000..33bcce42 --- /dev/null +++ b/src/components/PageNewCharacter/StepHitPoints/StepHitPoints.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import { CharData } from "@/data/definitions"; +import { Button, Flex, InputNumber, Space } from "antd"; +import { + classSplit, + isStandardClass, + rollDice, +} from "@/support/characterSupport"; + +interface StepHitPointsProps { + character: CharData; + setCharacter: (character: CharData) => void; +} + +const StepHitPoints: React.FC< + StepHitPointsProps & React.ComponentPropsWithRef<"div"> +> = ({ className, character, setCharacter }) => { + const [max, setMax] = React.useState(character.hp.max || 0); + + const classArr = classSplit(character.class); + + const hasCustomClass = classArr.some( + (className) => !isStandardClass(className), + ); + const handleButtonClick = () => { + let roll = + rollDice(character.hp.dice) + + parseInt(character.abilities.modifiers.constitution + ""); + roll = roll < 1 ? 1 : roll; + setMax(roll); + }; + + React.useEffect(() => { + setCharacter({ + ...character, + hp: { ...character.hp, max, points: max }, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [max]); + return ( + + {hasCustomClass ? ( +
hasCustomClass
+ ) : ( + + + + + )} +
+ ); +}; + +export default StepHitPoints; diff --git a/src/components/PageNewCharacter/StepRace/StepRace.tsx b/src/components/PageNewCharacter/StepRace/StepRace.tsx new file mode 100644 index 00000000..71d42157 --- /dev/null +++ b/src/components/PageNewCharacter/StepRace/StepRace.tsx @@ -0,0 +1,96 @@ +import React from "react"; +import { CharData, RaceNames } from "@/data/definitions"; +import { Flex, SelectProps } from "antd"; +import { getClassType, getRaceSelectOptions } from "@/support/characterSupport"; +import { races } from "@/data/races"; +import RaceClassSelector from "../RaceClassSelector/RaceClassSelector"; +import RaceClassDescription from "../RaceClassDescription/RaceClassDescription"; +import Options from "../StepClass/Options/Options"; + +interface StepRaceProps { + character: CharData; + setCharacter: (character: CharData) => void; + setComboClass: (comboClass: boolean) => void; + setComboClassSwitch: (comboClassSwitch: boolean) => void; +} + +const StepRace: React.FC< + StepRaceProps & React.ComponentPropsWithRef<"div"> +> = ({ + className, + character, + setCharacter, + setComboClass, + setComboClassSwitch, +}) => { + const [raceSelector, setRaceSelector] = React.useState( + character.race, + ); + const [customRaceInput, setCustomRaceInput] = React.useState(""); + const [supplementalContentSwitch, setSupplementalContentSwitch] = + React.useState(false); + + const raceSelectOptions: SelectProps["options"] = getRaceSelectOptions( + character, + !supplementalContentSwitch, + ); + + const handleSelectChange = (value: string) => { + setRaceSelector(value); + setComboClassSwitch(false); + }; + + const handleCustomRaceInputChange = ( + e: React.ChangeEvent, + ) => { + setCustomRaceInput(e.target.value); + setCharacter({ ...character, race: e.target.value }); + }; + + React.useEffect(() => { + if (raceSelector !== "custom" && raceSelector !== "") { + setCharacter({ ...character, race: raceSelector }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [raceSelector]); + + React.useEffect( + () => { + setComboClass( + !!races[character.race as keyof typeof races]?.allowedCombinationClasses + ?.length || false, + ); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [character], + ); + + return ( + + + + {!!raceSelector && + getClassType(character.class) !== "custom" && + getClassType(character.class) !== "none" && ( + + )} + + ); +}; + +export default StepRace; diff --git a/src/components/PageNewGame/PageNewGame.tsx b/src/components/PageNewGame/PageNewGame.tsx new file mode 100644 index 00000000..b5ae6741 --- /dev/null +++ b/src/components/PageNewGame/PageNewGame.tsx @@ -0,0 +1,60 @@ +import { User } from "firebase/auth"; +import React from "react"; +import NewContentWrapper from "../NewContentWrapper/NewContentWrapper"; +import { marked } from "marked"; +import { Breadcrumb, BreadcrumbProps, Button, Form, Input } from "antd"; +import BreadcrumbHomeLink from "../BreadcrumbHomeLink/BreadcrumbHomeLink"; +import { UsergroupAddOutlined } from "@ant-design/icons"; + +interface PageNewGameProps { + user: User | null; +} + +const breadcrumbItems: BreadcrumbProps["items"] = [ + { title: }, + { + title: ( +
+ + New Game +
+ ), + }, +]; + +const PageNewGame: React.FC< + PageNewGameProps & React.ComponentPropsWithRef<"div"> +> = ({ className }) => { + const [form] = Form.useForm(); + + const onFinish = (values: any) => { + console.info(values); + }; + + const newGameDescription = marked( + `Create a new Basic Fantasy RPG game. You will be the Game Master and will be able to add players to your game using their characters' unique URLs.`, + ); + return ( + <> + + +
+ + + + + + + +
+ + ); +}; + +export default PageNewGame; diff --git a/src/pages/Sources/Sources.tsx b/src/components/PageSources/PageSources.tsx old mode 100755 new mode 100644 similarity index 89% rename from src/pages/Sources/Sources.tsx rename to src/components/PageSources/PageSources.tsx index bd842d44..207d2410 --- a/src/pages/Sources/Sources.tsx +++ b/src/components/PageSources/PageSources.tsx @@ -1,12 +1,13 @@ import { List, Typography } from "antd"; -import classNames from "classnames"; -import { useOutletContext } from "react-router-dom"; +import React from "react"; -export default function Sources() { - const outletContext = useOutletContext() as { className: string }; - const sourcesClassNames = classNames(outletContext.className); +interface PageSourcesProps {} + +const PageSources: React.FC< + PageSourcesProps & React.ComponentPropsWithRef<"div"> +> = ({ className }) => { return ( -
+
Sources This site could not exist without the awesome work of so many talented @@ -74,8 +75,9 @@ export default function Sources() { Smoot, R. Kevin. "New Races: A Basic Fantasy RPG Supplement." Basic Fantasy Role-Playing Game, 2018, basicfantasy.org/downloads.html. - Spiked dragon head icon by Delapouite
); -} +}; + +export default PageSources; diff --git a/src/components/PageWelcome/PageWelcome.tsx b/src/components/PageWelcome/PageWelcome.tsx new file mode 100644 index 00000000..b02dca4b --- /dev/null +++ b/src/components/PageWelcome/PageWelcome.tsx @@ -0,0 +1,88 @@ +import { Flex, Image, Typography } from "antd"; +import React from "react"; +import CharacterSheet from "@/assets/images/char-sheet-v2.png"; +import SiteLogo from "@/assets/images/dragon-head.png"; +import { useDeviceType } from "@/hooks/useDeviceType"; + +const PageWelcome: React.FC = () => { + const { isMobile } = useDeviceType(); + return ( + +
+ + + Hail fellow well met! + + + Welcome to CODEX.QUEST, the ultimate companion for{" "} + + Basic Fantasy RPG + + . + + + Designed with the player in mind, this platform streamlines character + creation, providing an intuitive, user-friendly interface to craft + your unique character. From defining your character's abilities to + equipping them with the right gear, CODEX.QUEST ensures a seamless and + immersive gaming experience. + + + CODEX.QUEST is not just a tool; it's a companion designed to guide you + on your characters' adventures. Make the most of your gaming + experience by letting CODEX.QUEST handle the complexities of character + management, giving you more time to dive into the captivating + narratives and thrilling encounters that BFRPG offers. Best of all, + it's 100% FREE! + + + Bring Your Characters to Life + + +
    +
  • + Create, store, and play as an infinite number of characters. +
  • +
  • + Access all the official races, classes, and equipment as well as + virtually all of the supplemental releases. +
  • +
  • + Go even further and create custom races, classes, and equipment to + bring your unique character to life. +
  • +
  • + Roll initiative, attack, damage, saving throws, and special + abilities with our easy-to-use interface. Our Virtual Dice Tool + also lets you roll any other dice you need. +
  • +
  • + GM mode allows you to add players' characters and monitor their + stats for your whole game. +
  • +
  • Share your characters with anyone using unique URLs.
  • +
  • More features being added all the time!
  • +
+
+
+ + + ); +}; + +export default PageWelcome; diff --git a/src/components/WeaponKeys/WeaponKeys.tsx b/src/components/WeaponKeys/WeaponKeys.tsx deleted file mode 100755 index 8f3496da..00000000 --- a/src/components/WeaponKeys/WeaponKeys.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Descriptions } from "antd"; - -export default function WeaponKeys({ - className, -}: React.ComponentPropsWithRef<"div">) { - return ( - - - This weapon only does subduing damage - - - Entangling: This weapon may be used to snare or hold opponents. - - - Silver tip or blade, for use against lycanthropes. - - - ); -} diff --git a/src/contexts/CharacterContext.ts b/src/contexts/CharacterContext.ts new file mode 100644 index 00000000..e7658a23 --- /dev/null +++ b/src/contexts/CharacterContext.ts @@ -0,0 +1,62 @@ +import React from "react"; +import { CharData } from "../data/definitions"; + +interface CharacterDataContextProps { + character: CharData; + setCharacter: (character: CharData | null) => void; + userIsOwner: boolean; + uid: string | undefined; + id: string | undefined; +} + +const defaultCharacter = { + abilities: { + scores: { + strength: 0, + intelligence: 0, + wisdom: 0, + dexterity: 0, + constitution: 0, + charisma: 0, + }, + modifiers: { + strength: 0, + intelligence: 0, + wisdom: 0, + dexterity: 0, + constitution: 0, + charisma: 0, + }, + }, + avatar: "", + class: [], + desc: "", + equipment: [], + gold: 0, + hp: { dice: "", points: 0, max: 0, desc: "" }, + level: 0, + name: "", + race: "", + restrictions: { race: [], class: [] }, + savingThrows: { + deathRayOrPoison: 0, + magicWands: 0, + paralysisOrPetrify: 0, + dragonBreath: 0, + spells: 0, + }, + specials: { race: [], class: [] }, + spells: [], + weight: 0, + wearing: { armor: "", shield: "" }, + xp: 0, +}; + +export const CharacterDataContext: React.Context = + React.createContext({ + character: defaultCharacter, + setCharacter: () => {}, + userIsOwner: false, + uid: "", + id: "", + }); diff --git a/src/data/classes/cleric.ts b/src/data/classes/cleric.ts index 1b3dc087..4847d9dc 100755 --- a/src/data/classes/cleric.ts +++ b/src/data/classes/cleric.ts @@ -1,4 +1,4 @@ -import { DiceTypes } from "../definitions"; +import { DiceTypes, EquipmentItem } from "../definitions"; import { EquipmentCategories } from "../definitions"; import { ClassSetup } from "./definitions"; import equipmentItems from "../equipmentItems.json"; @@ -185,8 +185,8 @@ export const cleric: ClassSetup = { ], startingEquipment: [ equipmentItems.find((item) => - item.name.toLowerCase().startsWith("holy symbol") - )!, + item.name.toLowerCase().startsWith("holy symbol"), + )! as EquipmentItem, ], powers: [ { diff --git a/src/data/classes/illusionist.ts b/src/data/classes/illusionist.ts index ee7419dc..7d123514 100755 --- a/src/data/classes/illusionist.ts +++ b/src/data/classes/illusionist.ts @@ -1,4 +1,4 @@ -import { DiceTypes } from "../definitions"; +import { DiceTypes, EquipmentItem } from "../definitions"; import { EquipmentCategories } from "../definitions"; import { ClassSetup } from "./definitions"; import equipmentItems from "../equipmentItems.json"; @@ -160,8 +160,8 @@ export const illusionist: ClassSetup = { startingSpells: ["Read Magic"], startingEquipment: [ equipmentItems.find((item) => - item.name.toLowerCase().startsWith("spellbook") - )!, + item.name.toLowerCase().startsWith("spellbook"), + )! as EquipmentItem, ], details: { description: diff --git a/src/data/classes/magicUser.ts b/src/data/classes/magicUser.ts index 84c950b5..0c9831de 100755 --- a/src/data/classes/magicUser.ts +++ b/src/data/classes/magicUser.ts @@ -1,4 +1,4 @@ -import { DiceTypes } from "../definitions"; +import { DiceTypes, EquipmentItem } from "../definitions"; import { EquipmentCategories } from "../definitions"; import { ClassSetup } from "./definitions"; import equipmentItems from "../equipmentItems.json"; @@ -160,8 +160,8 @@ export const magicUser: ClassSetup = { startingSpells: ["Read Magic"], startingEquipment: [ equipmentItems.find((item) => - item.name.toLowerCase().startsWith("spellbook") - )!, + item.name.toLowerCase().startsWith("spellbook"), + )! as EquipmentItem, ], details: { description: diff --git a/src/data/classes/necromancer.ts b/src/data/classes/necromancer.ts index d60bf60b..cdcafc40 100755 --- a/src/data/classes/necromancer.ts +++ b/src/data/classes/necromancer.ts @@ -1,4 +1,4 @@ -import { DiceTypes } from "../definitions"; +import { DiceTypes, EquipmentItem } from "../definitions"; import { EquipmentCategories } from "../definitions"; import { ClassSetup } from "./definitions"; import equipmentItems from "../equipmentItems.json"; @@ -168,12 +168,12 @@ export const necromancer: ClassSetup = { startingSpells: ["Read Magic"], startingEquipment: [ equipmentItems.find((item) => - item.name.toLowerCase().startsWith("spellbook") - )!, + item.name.toLowerCase().startsWith("spellbook"), + )! as EquipmentItem, ], details: { description: - "(Necromancers Release 10)\n\nNecromancers are Magic-Users who practice necromancy, seeking expertise of the darker side of the arcane. Necromancers are rare due to the unsavory nature of their profession, often living in proximity to graveyards, burial mounds, and other places associated with the dead. They are sometimes known by other terms such as _Bokor_ or even _Death Master_. Regardless of what they are called or the culture they come from, they share certain traits.\n\nNecromancers are poor fighters, with fighting ability equivalent to normal Magic-Users. Likewise they are no more hardy than standard Magic-Users (d4 hit die). They may not wear armor of any sort or use shields, but unlike other Magic-Users they have expanded weapon choices. In addition to the dagger and walking staff, Necromancers can use sickles, scythes, spades, and scimitars, and they can likewise use magical weapons of those types. Otherwise, Necromancers can generally be treated as equivalent to Magic-Users for any situation not covered here.\n\nThe Prime Requisite for Necromancers is Intelligence. In addition to requiring an Intelligence score of 11 or higher, a Necromancer also must have a Wisdom score of at least 9 in order to qualify for the rigors of the class. Although not a requirement, most Necromancers do not score high in looks or Charisma. The class generally attracts those who are persecuted or otherwise disenfranchised with normal society.\n\nNecromancers produce magic much like other types of Magic-Users, but have different spell choices. They can learn spells from each other so long as the spells are available to both classes. Like other Magic-Users, a firstlevel Necromancer begins play knowing **read magic** and one other spell of first level, recorded within a spellbook. The GM may roll for the spell, assign it as he or she sees fit, or allow the player to choose it, at his or her option.", + "(Necromancers Release 10)\n\nNecromancers are Magic-Users who practice necromancy, seeking expertise of the darker side of the arcane. Necromancers are rare due to the unsavory nature of their profession, often living in proximity to graveyards, burial mounds, and other places associated with the dead. They are sometimes known by other terms such as _Bokor_ or even _Death Master_. Regardless of what they are called or the culture they come from, they share certain traits.\n\nNecromancers are poor fighters, with fighting ability equivalent to normal Magic-Users. Likewise they are no more hardy than standard Magic-Users (d4 hit die). They may not wear armor of any sort or use shields, but unlike other Magic-Users they have expanded weapon choices. In addition to the dagger and walking staff, Necromancers can use sickles, scythes, spades, and scimitars, and they can likewise use magical weapons of those types. Otherwise, Necromancers can generally be treated as equivalent to Magic-Users for any situation not covered here.\n\nThe Prime Requisite for Necromancers is Intelligence. In addition to requiring an Intelligence score of 11 or higher, a Necromancer also must have a Wisdom score of at least 9 in order to qualify for the rigors of the class. Although not a requirement, most Necromancers do not score high in looks or Charisma. The class generally attracts those who are persecuted or otherwise disenfranchised with normal society.\n\nNecromancers produce magic much like other types of Magic-Users, but have different spell choices. They can learn spells from each other so long as the spells are available to both classes. Like other Magic-Users, a first level Necromancer begins play knowing **read magic** and one other spell of first level, recorded within a spellbook. The GM may roll for the spell, assign it as he or she sees fit, or allow the player to choose it, at his or her option.", specials: [ "**Necromancers** can use sickles, scythes, spades, and scimitars", ], diff --git a/src/data/classes/spellcrafter.ts b/src/data/classes/spellcrafter.ts index 54e11499..1ce57a6a 100755 --- a/src/data/classes/spellcrafter.ts +++ b/src/data/classes/spellcrafter.ts @@ -1,4 +1,4 @@ -import { DiceTypes, EquipmentCategories } from "../definitions"; +import { DiceTypes, EquipmentCategories, EquipmentItem } from "../definitions"; import { ClassSetup } from "./definitions"; import equipmentItems from "../equipmentItems.json"; @@ -163,8 +163,8 @@ export const spellCrafter: ClassSetup = { startingSpells: ["Read Magic"], startingEquipment: [ equipmentItems.find((item) => - item.name.toLowerCase().startsWith("spellbook") - )!, + item.name.toLowerCase().startsWith("spellbook"), + )! as EquipmentItem, ], details: { description: diff --git a/src/data/definitions.ts b/src/data/definitions.ts index a2037ffa..8c48fe22 100755 --- a/src/data/definitions.ts +++ b/src/data/definitions.ts @@ -1,3 +1,5 @@ +import { AttackTypes } from "../support/stringSupport"; + export enum EquipmentCategories { GENERAL = "general-equipment", AXES = "axes", @@ -17,6 +19,19 @@ export enum EquipmentCategories { BEASTS = "beasts-of-burden", } +export enum EquipmentCategoriesWeapons { + AXES = "axes", + BOWS = "bows", + DAGGERS = "daggers", + SWORDS = "swords", + HAMMERMACE = "hammers-and-maces", + OTHERWEAPONS = "other-weapons", + SPEARSPOLES = "spears-and-polearms", + IMPROVISED = "improvised-weapons", + CHAINFLAIL = "chain-and-flail", + SLINGHURLED = "slings-and-hurled-weapons", +} + export enum ClassNames { ASSASSIN = "Assassin", BARBARIAN = "Barbarian", @@ -86,14 +101,22 @@ export type Abilities = { charisma: number | string; }; +export type CostCurrency = "gp" | "sp" | "cp"; +export type SizeOptions = "S" | "M" | "L"; +export type AttackTypeStrings = + | AttackTypes.MELEE + | AttackTypes.MISSILE + | AttackTypes.BOTH; + export type EquipmentItem = { name: string; costValue: number; - costCurrency: string; - category: string; + costCurrency: CostCurrency; + category: EquipmentCategories | "inherent" | "weapons"; amount: number; + subCategory?: string; weight?: number; - size?: string; + size?: SizeOptions; damage?: string; missileAC?: string; AC?: string | number; @@ -109,7 +132,7 @@ type SpecialRestriction = { class: string[]; }; -export type CharacterData = { +export type CharData = { abilities: { scores: Abilities; modifiers: Abilities; @@ -136,16 +159,11 @@ export type CharacterData = { wearing?: { armor: string; shield: string }; weight: number; xp: number; + userId?: string; + charId?: string; }; -export type SetCharacterData = (characterData: CharacterData) => void; - -export interface CharacterDataStatePair { - characterData: CharacterData; - setCharacterData: SetCharacterData; -} - -export type AttackType = "melee" | "missile"; +export type SetCharData = (characterData: CharData) => void; export type AbilityRecord = { key: string; @@ -157,12 +175,15 @@ export type GameData = { name: string; id?: string; players: PlayerListObject[]; + notes?: string; }; export type PlayerListObject = { user: string; character: string }; -export enum MODE { - PLAYER = "Player Mode", - GM = "GM Mode", -} -export type ModeType = MODE.PLAYER | MODE.GM; +export type GamePlayer = { + user: string; + character: string; +}; +export type GamePlayerList = GamePlayer[]; + +export type AvatarTypes = "none" | "stock" | "upload"; diff --git a/src/data/equipmentItems.json b/src/data/equipmentItems.json index e2af853c..d6a81909 100755 --- a/src/data/equipmentItems.json +++ b/src/data/equipmentItems.json @@ -6,7 +6,7 @@ "weight": 7, "category": "general-equipment", "subCategory": "cooking", - "amount": 1 + "amount": 0 }, { "name": "Fire grate", @@ -15,7 +15,7 @@ "weight": 5, "category": "general-equipment", "subCategory": "cooking", - "amount": 1 + "amount": 0 }, { "name": "Fishhook", @@ -24,7 +24,7 @@ "weight": 0, "category": "general-equipment", "subCategory": "cooking", - "amount": 1 + "amount": 0 }, { "name": "Iron Pan (Frying)", @@ -33,7 +33,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "cooking", - "amount": 1 + "amount": 0 }, { "name": "Iron Pot", @@ -42,7 +42,7 @@ "weight": 3, "category": "general-equipment", "subCategory": "cooking", - "amount": 1 + "amount": 0 }, { "name": "Backpack (Standard or Halfling)", @@ -51,7 +51,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "bags-and-pouches", - "amount": 1 + "amount": 0 }, { "name": "Belt Pouch", @@ -60,7 +60,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "bags-and-pouches", - "amount": 1 + "amount": 0 }, { "name": "Bell, small", @@ -69,7 +69,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Bellows", @@ -78,7 +78,7 @@ "weight": 3, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Block and tackle", @@ -87,7 +87,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Bucket (up to 5 gal)", @@ -96,7 +96,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Bit and bridle", @@ -105,7 +105,7 @@ "weight": 3, "category": "general-equipment", "subCategory": "tack-and-harness", - "amount": 1 + "amount": 0 }, { "name": "Bed Roll", @@ -114,7 +114,7 @@ "weight": 5, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Canvas (per sq. yard)", @@ -123,7 +123,7 @@ "weight": 5, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Chain, heavy", @@ -132,7 +132,7 @@ "weight": 10, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Chain, light", @@ -141,7 +141,7 @@ "weight": 5, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Chisel", @@ -150,7 +150,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Crowbar (3')", @@ -159,7 +159,7 @@ "weight": 10, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Fishing net, 10 ft. sq.", @@ -168,7 +168,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Grease Pot", @@ -177,7 +177,7 @@ "weight": 5, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Hammer or Mallet", @@ -186,7 +186,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Hand Drill", @@ -195,7 +195,7 @@ "weight": 3, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Hourglass (Hour)", @@ -204,7 +204,7 @@ "weight": 3, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Marbles, Bag", @@ -213,7 +213,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Candles, 12", @@ -222,7 +222,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Chalk, colored, small bag of pieces", @@ -231,7 +231,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Chalk, small bag of pieces", @@ -240,7 +240,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Charcoal sticks", @@ -249,7 +249,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Coal Keeper", @@ -258,7 +258,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Cord/Strap, per 3'", @@ -267,7 +267,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Flask, Silver", @@ -276,7 +276,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Flask, Steel", @@ -285,7 +285,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Cloak", @@ -294,7 +294,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "outerwear", - "amount": 1 + "amount": 0 }, { "name": "Clothing, common outfit", @@ -303,7 +303,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "outfit", - "amount": 1 + "amount": 0 }, { "name": "Vial, glass", @@ -312,7 +312,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Wax, beeswax", @@ -321,7 +321,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Wooden Stake", @@ -330,7 +330,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Grappling Hook", @@ -339,7 +339,7 @@ "weight": 4, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Hammock", @@ -348,7 +348,7 @@ "weight": 5, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Holy Symbol", @@ -357,7 +357,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "class-items", - "amount": 1 + "amount": 0 }, { "name": "Holy Symbol, Ornate", @@ -366,7 +366,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "class-items", - "amount": 1 + "amount": 0 }, { "name": "Holy Water, per vial", @@ -377,7 +377,7 @@ "subCategory": "class-items", "type": "missile", "range": [10, 30, 50], - "amount": 1 + "amount": 0 }, { "name": "Horseshoes & shoeing", @@ -386,7 +386,7 @@ "weight": 10, "category": "general-equipment", "subCategory": "tack-and-harness", - "amount": 1 + "amount": 0 }, { "name": "Writing Ink (per vial)", @@ -395,7 +395,7 @@ "weight": 0.5, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Iron Spikes, 12", @@ -404,7 +404,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Jar or Bottle, Ceramic", @@ -413,7 +413,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Jar or Bottle, Glass", @@ -422,7 +422,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Ladder, 10 ft.", @@ -431,7 +431,7 @@ "weight": 20, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Lantern", @@ -440,7 +440,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Lantern, Bullseye", @@ -449,7 +449,7 @@ "weight": 3, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Lantern, Hooded", @@ -458,7 +458,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Lens, small", @@ -467,7 +467,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Manacles (without padlock)", @@ -476,7 +476,7 @@ "weight": 4, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Map or scroll case", @@ -485,7 +485,7 @@ "weight": 0.5, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Mess Kit", @@ -494,7 +494,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Sealing Wax", @@ -503,7 +503,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Signet Ring or personal seal", @@ -512,7 +512,7 @@ "weight": 0, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Smoking Pipe", @@ -521,7 +521,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Smoking pouch", @@ -530,7 +530,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Oil (per flask)", @@ -541,7 +541,7 @@ "subCategory": "dungeon-exploration", "type": "missile", "range": [10, 30, 50], - "amount": 1 + "amount": 0 }, { "name": "Padlock (with 2 keys)", @@ -550,7 +550,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Fine Paper or Vellum, per sheet", @@ -559,7 +559,7 @@ "weight": 0, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Paper or Parchment, per sheet", @@ -568,7 +568,7 @@ "weight": 0, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Quill", @@ -577,7 +577,7 @@ "weight": 0, "category": "general-equipment", "subCategory": "wizards-wares", - "amount": 1 + "amount": 0 }, { "name": "Quill Knife", @@ -586,7 +586,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "wizards-wares", - "amount": 1 + "amount": 0 }, { "name": "Quiver or Bolt case (20)", @@ -595,7 +595,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "class-items", - "amount": 1 + "amount": 0 }, { "name": "Rations, Dry, one week", @@ -604,7 +604,7 @@ "weight": 14, "category": "general-equipment", "subCategory": "cooking", - "amount": 1 + "amount": 0 }, { "name": "Rations, Elven Waybread, one week", @@ -613,7 +613,7 @@ "weight": 7, "category": "general-equipment", "subCategory": "cooking", - "amount": 1 + "amount": 0 }, { "name": "Rations, Standard (perishable), one day", @@ -622,7 +622,7 @@ "weight": 4, "category": "general-equipment", "subCategory": "cooking", - "amount": 1 + "amount": 0 }, { "name": "Tea pot", @@ -631,7 +631,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "cooking", - "amount": 1 + "amount": 0 }, { "name": "Tripod, cooking", @@ -640,7 +640,7 @@ "weight": 10, "category": "general-equipment", "subCategory": "cooking", - "amount": 1 + "amount": 0 }, { "name": "Rope, Hemp (per 50 ft.)", @@ -649,7 +649,7 @@ "weight": 5, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Rope, Silk (per 50 ft.)", @@ -658,7 +658,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Sack, Large", @@ -667,7 +667,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "bags-and-pouches", - "amount": 1 + "amount": 0 }, { "name": "Sack, Small", @@ -676,7 +676,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "bags-and-pouches", - "amount": 1 + "amount": 0 }, { "name": "Saddle, Pack", @@ -685,7 +685,7 @@ "weight": 15, "category": "general-equipment", "subCategory": "bags-and-pouches", - "amount": 1 + "amount": 0 }, { "name": "Saddle, Riding", @@ -694,7 +694,7 @@ "weight": 35, "category": "general-equipment", "subCategory": "tack-and-harness", - "amount": 1 + "amount": 0 }, { "name": "Saddlebags, pair", @@ -703,7 +703,7 @@ "weight": 7, "category": "general-equipment", "subCategory": "bags-and-pouches", - "amount": 1 + "amount": 0 }, { "name": "Bandages", @@ -712,7 +712,7 @@ "weight": 0, "category": "general-equipment", "subCategory": "health", - "amount": 1 + "amount": 0 }, { "name": "Crutches", @@ -721,7 +721,7 @@ "weight": 4, "category": "general-equipment", "subCategory": "health", - "amount": 1 + "amount": 0 }, { "name": "Oil (per flask), scented/rubbing", @@ -730,7 +730,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "health", - "amount": 1 + "amount": 0 }, { "name": "Perfume (per vial)", @@ -739,7 +739,7 @@ "weight": 0.5, "category": "general-equipment", "subCategory": "health", - "amount": 1 + "amount": 0 }, { "name": "Razor", @@ -748,7 +748,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "health", - "amount": 1 + "amount": 0 }, { "name": "Soap (per lb)", @@ -757,7 +757,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "health", - "amount": 1 + "amount": 0 }, { "name": "Soap, perfumed (per lb)", @@ -766,7 +766,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "health", - "amount": 1 + "amount": 0 }, { "name": "Spellbook (128 pages)", @@ -775,7 +775,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "wizards-wares", - "amount": 1 + "amount": 0 }, { "name": "Air Bladder", @@ -784,7 +784,7 @@ "weight": 3, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Caltrops", @@ -793,7 +793,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Crampons", @@ -802,7 +802,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Hunter's Horn", @@ -811,7 +811,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Piton, climbing", @@ -820,7 +820,7 @@ "weight": 0, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Skates", @@ -829,7 +829,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Snow skis", @@ -838,7 +838,7 @@ "weight": 7, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Tent, Large (ten men)", @@ -847,7 +847,7 @@ "weight": 20, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Tent, Pavillion", @@ -856,7 +856,7 @@ "weight": 40, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Tent, Small (one man)", @@ -865,7 +865,7 @@ "weight": 10, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Trap, small animal", @@ -874,7 +874,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Trap, medium animal", @@ -883,7 +883,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Trap, large animal", @@ -892,7 +892,7 @@ "weight": 5, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Travois", @@ -901,7 +901,7 @@ "weight": 15, "category": "general-equipment", "subCategory": "outdoor", - "amount": 1 + "amount": 0 }, { "name": "Journal (blank)", @@ -910,7 +910,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Thieves' picks and tools", @@ -919,7 +919,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "class-items", - "amount": 1 + "amount": 0 }, { "name": "Tinderbox, flint and steel", @@ -928,7 +928,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Torches, 6", @@ -937,7 +937,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Whetstone", @@ -946,7 +946,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "class-items", - "amount": 1 + "amount": 0 }, { "name": "Magnet, small", @@ -955,7 +955,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Magnifying glass", @@ -964,7 +964,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Mirror, small metal", @@ -973,7 +973,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Mirror, small silver", @@ -982,7 +982,7 @@ "weight": 0.1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Nails, Iron (20)", @@ -991,7 +991,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Nails, Silver (20)", @@ -1000,7 +1000,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Needle, magnitized", @@ -1009,7 +1009,7 @@ "weight": 0, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Needle, sewing", @@ -1018,7 +1018,7 @@ "weight": 0, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Paint, per gallon", @@ -1027,7 +1027,7 @@ "weight": 4, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Paint, small pot", @@ -1036,7 +1036,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Pick Axe, mining", @@ -1045,7 +1045,7 @@ "weight": 7, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Pliers", @@ -1054,7 +1054,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Pole, 10' collapsing", @@ -1063,7 +1063,7 @@ "weight": 15, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Pole, 10' wooden", @@ -1072,7 +1072,7 @@ "weight": 10, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Scissors", @@ -1081,7 +1081,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "tools", - "amount": 1 + "amount": 0 }, { "name": "Signal whistle", @@ -1090,7 +1090,7 @@ "weight": 0, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "String/Twine, 100 ft.", @@ -1099,7 +1099,7 @@ "weight": 1, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "String/Twine, Silk, 100 ft.", @@ -1108,7 +1108,7 @@ "weight": 0.5, "category": "general-equipment", "subCategory": "dungeon-exploration", - "amount": 1 + "amount": 0 }, { "name": "Wineskin/Waterskin", @@ -1117,7 +1117,7 @@ "weight": 2, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Wineskin/Waterskin, gallon", @@ -1126,7 +1126,7 @@ "weight": 7, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Winter blanket", @@ -1135,7 +1135,7 @@ "weight": 3, "category": "general-equipment", "subCategory": "personal-equipment", - "amount": 1 + "amount": 0 }, { "name": "Hand Axe", @@ -1147,7 +1147,7 @@ "category": "axes", "type": "both", "range": [10, 20, 30], - "amount": 1 + "amount": 0 }, { "name": "Battle Axe", @@ -1158,7 +1158,7 @@ "damage": "1d8", "category": "axes", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Great Axe", @@ -1169,7 +1169,7 @@ "damage": "1d10", "category": "axes", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Pickaxe (Military Pick)", @@ -1180,7 +1180,7 @@ "damage": "1d6", "category": "axes", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Mattock (Footman's Pick)", @@ -1191,7 +1191,7 @@ "damage": "1d8", "category": "axes", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Shortbow", @@ -1202,7 +1202,7 @@ "category": "bows", "type": "missile", "range": [50, 100, 150], - "amount": 1, + "amount": 0, "ammo": ["Shortbow Arrow", "Silver† Shortbow Arrow"] }, { @@ -1214,7 +1214,7 @@ "category": "bows", "type": "missile", "range": [70, 140, 210], - "amount": 1, + "amount": 0, "ammo": ["Longbow Arrow", "Silver† Longbow Arrow"] }, { @@ -1226,7 +1226,7 @@ "category": "bows", "type": "missile", "range": [60, 120, 180], - "amount": 1, + "amount": 0, "ammo": ["Light Quarrel", "Silver† Light Quarrel"] }, { @@ -1238,7 +1238,7 @@ "category": "bows", "type": "missile", "range": [80, 160, 240], - "amount": 1, + "amount": 0, "ammo": ["Heavy Quarrel", "Silver† Heavy Quarrel"] }, { @@ -1250,7 +1250,7 @@ "category": "bows", "type": "missile", "range": [30, 60, 90], - "amount": 1, + "amount": 0, "ammo": ["Hand Quarrel", "Silver† Hand Quarrel"] }, { @@ -1261,7 +1261,7 @@ "weight": 7, "category": "bows", "type": "missile", - "amount": 1, + "amount": 0, "ammo": ["Bullet", "Silver† Bullet"] }, { @@ -1274,7 +1274,7 @@ "category": "daggers", "type": "both", "range": [10, 20, 30], - "amount": 1 + "amount": 0 }, { "name": "Defending Dagger", @@ -1285,7 +1285,7 @@ "damage": "1d4", "category": "daggers", "type": "both", - "amount": 1 + "amount": 0 }, { "name": "Silver† Dagger", @@ -1296,7 +1296,7 @@ "damage": "1d4", "category": "daggers", "type": "both", - "amount": 1 + "amount": 0 }, { "name": "Shortsword/Cutlass", @@ -1307,7 +1307,7 @@ "damage": "1d6", "category": "swords", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Longsword/Scimitar", @@ -1318,7 +1318,7 @@ "damage": "1d8", "category": "swords", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Two-Handed Sword", @@ -1329,7 +1329,7 @@ "damage": "1d10", "category": "swords", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Warhammer", @@ -1341,7 +1341,7 @@ "category": "hammers-and-maces", "type": "both", "range": [10, 20, 30], - "amount": 1 + "amount": 0 }, { "name": "Light Mace", @@ -1352,7 +1352,7 @@ "damage": "1d6", "category": "hammers-and-maces", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Mace", @@ -1363,7 +1363,7 @@ "damage": "1d8", "category": "hammers-and-maces", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Morningstar", @@ -1374,7 +1374,7 @@ "damage": "1d8", "category": "hammers-and-maces", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Maul/Great Mace", @@ -1385,7 +1385,7 @@ "damage": "1d10", "category": "hammers-and-maces", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Club/Cudgel/Walking Staff", @@ -1396,7 +1396,7 @@ "damage": "1d4", "category": "other-weapons", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Silver Walking Stick/Staff†", @@ -1407,7 +1407,7 @@ "damage": "1d4", "category": "other-weapons", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Greatclub", @@ -1418,7 +1418,7 @@ "damage": "1d8", "category": "other-weapons", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Sap/Blackjack**", @@ -1429,7 +1429,7 @@ "damage": "1d4", "category": "other-weapons", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Hook", @@ -1440,7 +1440,7 @@ "damage": "1d4", "category": "other-weapons", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Sickle", @@ -1451,7 +1451,7 @@ "damage": "1d6", "category": "other-weapons", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Scythe", @@ -1462,7 +1462,7 @@ "damage": "1d8", "category": "other-weapons", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Quarterstaff", @@ -1473,7 +1473,7 @@ "damage": "1d6", "category": "spears-and-polearms", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Pole Arm", @@ -1484,7 +1484,7 @@ "damage": "1d10", "category": "spears-and-polearms", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Spade", @@ -1495,7 +1495,7 @@ "damage": "1d6", "category": "improvised-weapons", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Crowbar", @@ -1506,7 +1506,7 @@ "damage": "1d6", "category": "improvised-weapons", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Pitchfork", @@ -1517,7 +1517,7 @@ "damage": "1d6", "category": "improvised-weapons", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Chain††", @@ -1528,7 +1528,7 @@ "damage": "1d4", "category": "chain-and-flail", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Flail", @@ -1539,7 +1539,7 @@ "damage": "1d8", "category": "chain-and-flail", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Great Flail", @@ -1550,7 +1550,7 @@ "damage": "1d10", "category": "chain-and-flail", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Whip", @@ -1561,7 +1561,7 @@ "damage": "1d3", "category": "chain-and-flail", "type": "melee", - "amount": 1 + "amount": 0 }, { "name": "Sling", @@ -1572,7 +1572,7 @@ "category": "slings-and-hurled-weapons", "type": "missile", "range": [30, 60, 90], - "amount": 1, + "amount": 0, "ammo": ["Stone"] }, { @@ -1585,7 +1585,7 @@ "damage": "1d3", "type": "missile", "range": [20, 40, 60], - "amount": 1 + "amount": 0 }, { "name": "Dart/Throwing Blade", @@ -1597,7 +1597,7 @@ "damage": "1d3", "type": "missile", "range": [10, 20, 30], - "amount": 1 + "amount": 0 }, { "name": "Javelin", @@ -1609,7 +1609,7 @@ "damage": "1d4", "type": "missile", "range": [20, 40, 60], - "amount": 1 + "amount": 0 }, { "name": "Blowgun", @@ -1620,7 +1620,7 @@ "category": "slings-and-hurled-weapons", "type": "missile", "range": [10, 20, 30], - "amount": 1, + "amount": 0, "ammo": ["Dart/Throwing Blade"] }, { @@ -1632,7 +1632,7 @@ "category": "slings-and-hurled-weapons", "type": "missile", "range": [10, 15, 20], - "amount": 1 + "amount": 0 }, { "name": "Spear", @@ -1645,7 +1645,7 @@ "twoHandedDamage": "1d8", "type": "both", "range": [10, 20, 30], - "amount": 1 + "amount": 0 }, { "name": "Fork/Trident (E)", @@ -1658,7 +1658,7 @@ "twoHandedDamage": "1d8", "type": "both", "range": [10, 20, 30], - "amount": 1 + "amount": 0 }, { "name": "Boar Spear", @@ -1671,7 +1671,7 @@ "twoHandedDamage": "1d8", "type": "both", "range": [10, 20, 30], - "amount": 1 + "amount": 0 }, { "name": "Lance", @@ -1682,7 +1682,7 @@ "category": "spears-and-polearms", "damage": "1d8", "type": "both", - "amount": 1 + "amount": 0 }, { "name": "Shortbow Arrow", @@ -1691,7 +1691,7 @@ "weight": 0.1, "damage": "1d6", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Silver† Shortbow Arrow", @@ -1700,7 +1700,7 @@ "weight": 0.1, "damage": "1d6", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Longbow Arrow", @@ -1709,7 +1709,7 @@ "weight": 0.1, "damage": "1d8", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Silver† Longbow Arrow", @@ -1718,7 +1718,7 @@ "weight": 0.1, "damage": "1d8", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Light Quarrel", @@ -1727,7 +1727,7 @@ "weight": 0.1, "damage": "1d6", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Silver† Light Quarrel", @@ -1736,7 +1736,7 @@ "weight": 0.1, "damage": "1d6", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Heavy Quarrel", @@ -1745,7 +1745,7 @@ "weight": 0.1, "damage": "1d8", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Silver† Heavy Quarrel", @@ -1754,7 +1754,7 @@ "weight": 0.1, "damage": "1d8", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Hand Quarrel", @@ -1763,7 +1763,7 @@ "weight": 0.1, "damage": "1d3", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Silver† Hand Quarrel", @@ -1772,7 +1772,7 @@ "weight": 0.1, "damage": "1d3", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Bullet", @@ -1781,7 +1781,7 @@ "weight": 0.1, "damage": "1d4", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Silver† Bullet", @@ -1790,7 +1790,7 @@ "weight": 0.1, "damage": "1d4", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Stone", @@ -1799,7 +1799,7 @@ "weight": 0.1, "damage": "1d3", "category": "ammunition", - "amount": 1 + "amount": 0 }, { "name": "Padded/Quilted Armor", @@ -1808,7 +1808,7 @@ "weight": 10, "AC": 12, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Hide Armor", @@ -1817,7 +1817,7 @@ "weight": 30, "AC": 13, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Leather Armor", @@ -1826,7 +1826,7 @@ "weight": 15, "AC": 13, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Studded Leather Armor", @@ -1835,7 +1835,7 @@ "weight": 25, "AC": 14, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Ring Mail", @@ -1844,7 +1844,7 @@ "weight": 30, "AC": 14, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Brigandine Armor", @@ -1853,7 +1853,7 @@ "weight": 30, "AC": 15, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Chain Mail", @@ -1862,7 +1862,7 @@ "weight": 40, "AC": 15, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Scale Mail", @@ -1871,7 +1871,7 @@ "weight": 55, "AC": 16, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Splint Mail", @@ -1880,7 +1880,7 @@ "weight": 45, "AC": 16, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Banded Mail", @@ -1889,7 +1889,7 @@ "weight": 35, "AC": 16, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Plate Mail", @@ -1898,7 +1898,7 @@ "weight": 50, "AC": 17, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Field Plate Mail", @@ -1907,7 +1907,7 @@ "weight": 70, "AC": 18, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Full Plate Mail", @@ -1916,7 +1916,7 @@ "weight": 80, "AC": 19, "category": "armor", - "amount": 1 + "amount": 0 }, { "name": "Buckler Shield", @@ -1926,7 +1926,7 @@ "AC": "+1", "missileAC": "+0", "category": "shields", - "amount": 1 + "amount": 0 }, { "name": "Shield", @@ -1936,7 +1936,7 @@ "AC": "+1", "missileAC": "+1", "category": "shields", - "amount": 1 + "amount": 0 }, { "name": "Tower Shield", @@ -1946,7 +1946,7 @@ "AC": "+1", "missileAC": "+3", "category": "shields", - "amount": 1 + "amount": 0 }, { "name": "Light Barding", @@ -1956,7 +1956,7 @@ "animalWeight": 50, "AC": "+2", "category": "barding", - "amount": 1 + "amount": 0 }, { "name": "Mail Barding", @@ -1966,7 +1966,7 @@ "animalWeight": 80, "AC": "+4", "category": "barding", - "amount": 1 + "amount": 0 }, { "name": "Plate Barding", @@ -1976,41 +1976,41 @@ "animalWeight": 100, "AC": "+6", "category": "barding", - "amount": 1 + "amount": 0 }, { "name": "Horse, Draft", "costValue": 120, "costCurrency": "gp", "category": "beasts-of-burden", - "amount": 1 + "amount": 0 }, { "name": "Horse, War", "costValue": 200, "costCurrency": "gp", "category": "beasts-of-burden", - "amount": 1 + "amount": 0 }, { "name": "Horse, Riding", "costValue": 75, "costCurrency": "gp", "category": "beasts-of-burden", - "amount": 1 + "amount": 0 }, { "name": "Pony*", "costValue": 40, "costCurrency": "gp", "category": "beasts-of-burden", - "amount": 1 + "amount": 0 }, { "name": "Pony, War*", "costValue": 80, "costCurrency": "gp", "category": "beasts-of-burden", - "amount": 1 + "amount": 0 } ] diff --git a/src/data/equipmentItems.test.ts b/src/data/equipmentItems.test.ts index 05f4f458..478bf230 100644 --- a/src/data/equipmentItems.test.ts +++ b/src/data/equipmentItems.test.ts @@ -1,3 +1,4 @@ +import { AttackTypes } from "../support/stringSupport"; import equipmentItems from "./equipmentItems.json"; describe("equipmentItems", () => { @@ -18,8 +19,8 @@ describe("equipmentItems", () => { const uniqueValues = [...new Set(values)]; expect(uniqueValues).toEqual( expect.arrayContaining( - values.filter((value) => typeof value === "number" && value > -1) - ) + values.filter((value) => typeof value === "number" && value > -1), + ), ); }); @@ -31,12 +32,12 @@ describe("equipmentItems", () => { test("every item that has a `category` value of 'general-equipment' also has a `subCategory` property with a string value", () => { const generalEquipment = equipmentItems.filter( - (item) => item.category === "general-equipment" + (item) => item.category === "general-equipment", ); const subCategories = generalEquipment.map((item) => item.subCategory); expect(subCategories.length).toBe(generalEquipment.length); expect( - subCategories.every((subCategory) => typeof subCategory === "string") + subCategories.every((subCategory) => typeof subCategory === "string"), ).toBe(true); }); @@ -49,7 +50,11 @@ describe("equipmentItems", () => { test("every item that has a `type` property has a value of 'melee', 'missile', or 'both'", () => { const types = equipmentItems.map((item) => item.type); const uniqueTypes = [...new Set(types)].sort(); - expect(uniqueTypes).toEqual(["both", "melee", "missile"]); + expect(uniqueTypes).toEqual([ + AttackTypes.BOTH, + AttackTypes.MELEE, + AttackTypes.MISSILE, + ]); }); test("every item that has a `weight` property has a value of typeof 'number' greater than -1", () => { @@ -61,7 +66,7 @@ describe("equipmentItems", () => { }); const uniqueWeights = [...new Set(weights)]; expect(uniqueWeights.every((weight) => typeof weight === "number")).toBe( - true + true, ); }); }); diff --git a/src/data/races/bisren.ts b/src/data/races/bisren.ts index 13e431a3..1d263ae2 100644 --- a/src/data/races/bisren.ts +++ b/src/data/races/bisren.ts @@ -1,4 +1,5 @@ -import { ClassNames } from "../definitions"; +import { AttackTypes } from "@/support/stringSupport"; +import { ClassNames } from "@/data/definitions"; import { RaceSetup } from "./definitions"; // TODO: Add Details (specials, restrictions) @@ -25,7 +26,7 @@ export const bisren: RaceSetup = { category: "inherent", damage: "1d6", amount: 1, - type: "melee", + type: AttackTypes.MELEE, noDelete: true, }, ], diff --git a/src/data/races/definitions.ts b/src/data/races/definitions.ts index 4e8f87b2..1273d171 100755 --- a/src/data/races/definitions.ts +++ b/src/data/races/definitions.ts @@ -19,6 +19,7 @@ export interface RaceSetup { minimumAbilityRequirements?: Record; name: string; noLargeEquipment?: boolean; + rangedBonus?: number; savingThrows?: Partial; specialAbilitiesOverride?: any; uniqueAttacks?: EquipmentItem[]; diff --git a/src/data/races/halfling.ts b/src/data/races/halfling.ts index eed55800..266497b8 100755 --- a/src/data/races/halfling.ts +++ b/src/data/races/halfling.ts @@ -14,6 +14,7 @@ export const halfling: RaceSetup = { ClassNames.PALADIN, ClassNames.SCOUT, ], + rangedBonus: 1, hasLowCapacity: true, additionalAttackBonus: "+1", minimumAbilityRequirements: { dexterity: 9 }, @@ -33,7 +34,7 @@ export const halfling: RaceSetup = { specials: [ "**Halflings** are unusually accurate with all sorts of ranged weapons, gaining a +1 attack bonus when employing them.", "When attacked in melee by creatures larger than man-sized, **Halflings** gain a +2 bonus to their Armor Class.", - "`**Halflings** are quick-witted, adding +1 to Initiative die rolls.", + "**Halflings** are quick-witted, adding +1 to Initiative die rolls.", "In their preferred forest terrain, **Halflings** are able to hide very effectively; so long as they remain still there is only a 10% chance they will be detected. Even indoors, in dungeons or in non- preferred terrain they are able to hide such that there is only a 30% chance of detection. Note that a **Halfling Thief** will roll only once, using either the **Thief** ability or the **Halfling** ability, whichever is better.", ], restrictions: [ diff --git a/src/declarations.d.ts b/src/declarations.d.ts new file mode 100644 index 00000000..a36e2601 --- /dev/null +++ b/src/declarations.d.ts @@ -0,0 +1,6 @@ +declare module "*.svg" { + import React = require("react"); + export const ReactComponent: React.FC>; + const src: string; + export default src; +} diff --git a/src/firebase.js b/src/firebase.ts similarity index 100% rename from src/firebase.js rename to src/firebase.ts diff --git a/src/hooks/useAttack.ts b/src/hooks/useAttack.ts new file mode 100644 index 00000000..9e98dc36 --- /dev/null +++ b/src/hooks/useAttack.ts @@ -0,0 +1,140 @@ +import React from "react"; +import { useNotification } from "./useNotification"; +import { CharData, EquipmentItem } from "@/data/definitions"; +import { + getRollToAmmoDamageResult, + getRollToHitResult, + getRollToThrownDamageResult, + getWeapon, +} from "@/components/ModalAttack/ModalAttackSupport"; + +interface UseAttackReturnType { + range: string | undefined; + setRange: (range: string | undefined) => void; + contextHolder: React.ReactElement< + any, + string | React.JSXElementConstructor + >; + openNotification: ( + title: string, + description: string, + duration?: number, + ) => void; + getRangeOptions: (range: EquipmentItem["range"]) => + | { + label: string; + value: string; + }[] + | undefined; + handleRangeChange: (value: string) => void; + updateEquipmentAfterMissileAttack: ( + ammoSelection: string | undefined, + equipment: EquipmentItem[], + isRecoveryChecked: boolean, + character: CharData, + setCharacter: (character: CharData) => void, + ) => boolean; + calculateMissileRollResults: ( + character: CharData, + range: string | undefined, + equipment: EquipmentItem[], + ammoSelection: string | undefined, + thrown?: boolean, + ) => { + rollToHit: any; + rollToDamage: any; + }; +} + +export const useAttack = (): UseAttackReturnType => { + const [range, setRange] = React.useState(undefined); + const { contextHolder, openNotification } = useNotification(); + const getRangeOptions = (range: EquipmentItem["range"]) => + range?.map((r, index) => { + const rangeLabel = () => { + switch (index) { + case 0: + return "S"; + case 1: + return "M"; + case 2: + return "L"; + default: + return ""; + } + }; + return { + label: `${rangeLabel()}/${r}'`, + value: rangeLabel(), + }; + }); + const handleRangeChange = (value: string) => { + setRange(value); + }; + const updateEquipmentAfterMissileAttack = ( + ammoSelection: string | undefined, + equipment: EquipmentItem[], + isRecoveryChecked: boolean, + character: CharData, + setCharacter: (character: CharData) => void, + ) => { + const weapon = getWeapon(ammoSelection ?? "", equipment); + if (weapon) { + const newEquipment = equipment.filter( + (item) => item.name !== ammoSelection, + ); + const weaponRecovered = isRecoveryChecked && Math.random() < 0.25; + + if (weaponRecovered) { + newEquipment.push(weapon); + } else { + weapon.amount = weapon.amount - 1; + if (weapon.amount > 0) newEquipment.push(weapon); + } + + setCharacter({ + ...character, + equipment: [...newEquipment], + }); + + return weaponRecovered; + } + return false; + }; + + const calculateMissileRollResults = ( + character: CharData, + range: string | undefined, + equipment: EquipmentItem[], + ammoSelection: string | undefined, + thrown: boolean = false, + ) => { + const rollToHit = getRollToHitResult(character, "missile", range); + let rollToDamage; + if (thrown) { + rollToDamage = getRollToThrownDamageResult( + equipment, + ammoSelection, + character, + ); + } else { + rollToDamage = getRollToAmmoDamageResult( + equipment, + ammoSelection, + character, + ); + } + return { rollToHit, rollToDamage }; + }; + + return { + range, + setRange, + contextHolder, + openNotification, + getRangeOptions, + handleRangeChange, + updateEquipmentAfterMissileAttack, + calculateMissileRollResults, + }; +}; diff --git a/src/hooks/useCharacterData.ts b/src/hooks/useCharacterData.ts new file mode 100644 index 00000000..adba379e --- /dev/null +++ b/src/hooks/useCharacterData.ts @@ -0,0 +1,33 @@ +import React from "react"; +import { useParams } from "react-router-dom"; +import { fetchDocument, updateDocument } from "../support/accountSupport"; +import { CharData } from "../data/definitions"; +import { User } from "firebase/auth"; + +export function useCharacterData(user: User | null) { + const { uid, id } = useParams(); + const [character, setCharacter] = React.useState(null); + const userIsOwner = user?.uid === uid; + + React.useEffect(() => { + let unsubscribe: () => void; + if (uid && id) { + unsubscribe = fetchDocument(uid, id, setCharacter, "characters"); + } + return () => unsubscribe && unsubscribe(); + }, [uid, id]); + + React.useEffect(() => { + if (uid && id && character) { + updateDocument({ + collection: "users", + docId: uid, + subCollection: "characters", + subDocId: id, + data: { ...character }, + }); + } + }, [uid, id, character]); + + return { character, setCharacter, userIsOwner, uid, id }; +} diff --git a/src/hooks/useCharacterDice.ts b/src/hooks/useCharacterDice.ts new file mode 100644 index 00000000..d08d6dbe --- /dev/null +++ b/src/hooks/useCharacterDice.ts @@ -0,0 +1,17 @@ +// Custom Hook for rolling dice for a character. + +import { CharData } from "@/data/definitions"; +import { rollDice } from "@/support/characterSupport"; +import { useNotification } from "./useNotification"; + +export function useCharacterDice(character: CharData) { + const { dexterity: dexBonus } = character.abilities.modifiers; + const { contextHolder, openNotification } = useNotification(); + + const rollInitiative = () => { + const result = rollDice(`1d6${dexBonus}`); + openNotification("Roll Initiative", result); + }; + + return { contextHolder, rollInitiative }; +} diff --git a/src/hooks/useDeviceType.ts b/src/hooks/useDeviceType.ts new file mode 100644 index 00000000..4a0229be --- /dev/null +++ b/src/hooks/useDeviceType.ts @@ -0,0 +1,14 @@ +import { useMediaQuery } from "react-responsive"; +import { + mobileBreakpoint, + tabletBreakpoint, + desktopBreakpoint, +} from "../support/stringSupport"; + +export const useDeviceType = () => { + const isMobile = useMediaQuery({ query: mobileBreakpoint }); + const isTablet = useMediaQuery({ query: tabletBreakpoint }); + const isDesktop = useMediaQuery({ query: desktopBreakpoint }); + + return { isMobile, isTablet, isDesktop }; +}; diff --git a/src/hooks/useGameCharacters.ts b/src/hooks/useGameCharacters.ts new file mode 100644 index 00000000..e9e6ad23 --- /dev/null +++ b/src/hooks/useGameCharacters.ts @@ -0,0 +1,143 @@ +import React from "react"; +import { + Abilities, + CharData, + ClassNames, + GamePlayerList, +} from "../data/definitions"; +import { removePlayerFromGame } from "../support/accountSupport"; +import { doc, getDoc } from "firebase/firestore"; +import { db } from "../firebase"; +import { DescriptionsProps } from "antd"; +import { classSplit } from "../support/characterSupport"; + +export function useGameCharacters(players: GamePlayerList): [ + CharData[], + (gameId: string, userId: string, characterId: string) => Promise, + (scores: Abilities) => DescriptionsProps["items"], + (character: CharData) => DescriptionsProps["items"], + (characters: CharData[]) => { + showThief: boolean; + showAssassin: boolean; + showRanger: boolean; + showScout: boolean; + }, +] { + const [characterList, setCharacterList] = React.useState([]); + + const getCharacter = async (userId: string, characterId: string) => { + const docRef = doc(db, `users/${userId}/characters/${characterId}`); + const docSnap = await getDoc(docRef); + if (docSnap.exists()) { + return docSnap.data() as CharData; + } else { + console.error("No such document!"); + } + }; + + const removePlayer = async ( + gameId: string, + userId: string, + characterId: string, + ) => { + await removePlayerFromGame(gameId, userId, characterId); + setCharacterList((prevCharacterList) => + prevCharacterList.filter((character) => character.id !== characterId), + ); + }; + + const generateAbilityItems = ( + scores: Abilities, + ): DescriptionsProps["items"] => [ + { key: "strength", label: "STR", children: scores.strength, span: 1 }, + { + key: "intelligence", + label: "INT", + children: scores.intelligence, + span: 1, + }, + { key: "wisdom", label: "WIS", children: scores.wisdom, span: 1 }, + { key: "dexterity", label: "DEX", children: scores.dexterity, span: 1 }, + { + key: "constitution", + label: "CON", + children: scores.constitution, + span: 1, + }, + { key: "charisma", label: "CHA", children: scores.charisma, span: 1 }, + ]; + + const generateDetailItems = ( + character: CharData, + ): DescriptionsProps["items"] => { + const { level, hp, class: charClass, race } = character; + return [ + { + key: "level", + label: "Level", + children: level, + span: 1, + }, + { + key: "hp", + label: "HP", + children: `${hp.points} / ${hp.max}`, + span: 1, + }, + { + key: "class", + label: "Class", + children: classSplit(charClass).join(", "), + span: 1, + }, + { key: "race", label: "Race", children: race, span: 1 }, + ]; + }; + + const calculateClassAbilitiesToShow = (characters: CharData[]) => { + const classAbilities = { + showThief: false, + showAssassin: false, + showRanger: false, + showScout: false, + }; + + characters.forEach((character) => { + const classes = classSplit(character.class); + classAbilities.showThief ||= classes.includes(ClassNames.THIEF); + classAbilities.showAssassin ||= classes.includes(ClassNames.ASSASSIN); + classAbilities.showRanger ||= classes.includes(ClassNames.RANGER); + classAbilities.showScout ||= classes.includes(ClassNames.SCOUT); + }); + + return classAbilities; + }; + + React.useEffect(() => { + const fetchAllCharacterData = async () => { + const fetchedData: CharData[] = []; + for (const player of players) { + const data = await getCharacter(player.user, player.character); + if (data) { + // Add the player's user ID to the character data for stable reference + fetchedData.push({ + ...data, + userId: player.user, + charId: player.character, + }); + } + } + setCharacterList(fetchedData); + }; + + fetchAllCharacterData(); + }, [players]); + + return [ + characterList, + removePlayer, + generateAbilityItems, + generateDetailItems, + calculateClassAbilitiesToShow, + ]; +} diff --git a/src/hooks/useImages.ts b/src/hooks/useImages.ts new file mode 100644 index 00000000..a0bf22af --- /dev/null +++ b/src/hooks/useImages.ts @@ -0,0 +1,20 @@ +import { toSlugCase } from "@/support/stringSupport"; + +export const useImages = () => { + const getRaceClassImage = (name: string) => { + const classImages = import.meta.glob("@/assets/images/classes/*.jpg"); + const raceImages = import.meta.glob("@/assets/images/races/*.jpg"); + + return ( + classImages[`/src/assets/images/classes/${toSlugCase(name)}.jpg`]?.name ?? + raceImages[`/src/assets/images/races/${toSlugCase(name)}.jpg`]?.name + ); + }; + + const getSpellImage = (name: string) => { + const spellImages = import.meta.glob("@/assets/images/spells/*.jpg"); + return spellImages[`/src/assets/images/spells/${toSlugCase(name)}.jpg`] + ?.name; + }; + return { getRaceClassImage, getSpellImage }; +}; diff --git a/src/hooks/useModal.ts b/src/hooks/useModal.ts new file mode 100644 index 00000000..c1ee3803 --- /dev/null +++ b/src/hooks/useModal.ts @@ -0,0 +1,33 @@ +// useModal.js +import React from "react"; + +interface UseModalReturnType { + modalIsOpen: boolean; + setModalIsOpen: (modalIsOpen: boolean) => void; + modalTitle: string; + setModalTitle: (modalTitle: string) => void; + modalContent: React.ReactNode | undefined; + setModalContent: (modalContent: React.ReactNode) => void; + modalOkRef: React.RefObject<(() => void | undefined) | undefined>; +} + +export const useModal = (): UseModalReturnType => { + const [modalIsOpen, setModalIsOpen] = React.useState(false); + const [modalTitle, setModalTitle] = React.useState(""); + const [modalContent, setModalContent] = React.useState< + React.ReactNode | undefined + >(undefined); + const modalOkRef = React.useRef<(() => void | undefined) | undefined>( + undefined, + ); + + return { + modalIsOpen, + setModalIsOpen, + modalTitle, + setModalTitle, + modalContent, + setModalContent, + modalOkRef, + }; +}; diff --git a/src/hooks/useMoney.ts b/src/hooks/useMoney.ts new file mode 100644 index 00000000..1c009c60 --- /dev/null +++ b/src/hooks/useMoney.ts @@ -0,0 +1,121 @@ +import { CharData, CostCurrency } from "@/data/definitions"; +import React from "react"; + +export function useMoney( + character: CharData | null, + setCharacter: (character: CharData) => void, +) { + const handleInputChange = ( + event: React.ChangeEvent, + setFunc: React.Dispatch>, + ) => { + setFunc(event.target.value); + }; + + const makeChange = () => { + if (character) { + let copper = character.gold * 100; + const goldPieces = Math.floor(copper / 100); + copper %= 100; + const silverPieces = Math.floor(copper / 10); + copper %= 10; + const copperPieces = copper; + + return { + gp: Math.round(goldPieces), + sp: Math.round(silverPieces), + cp: Math.round(copperPieces), + }; + } else { + // default object when characterData is null/undefined + return { gp: 0, sp: 0, cp: 0 }; + } + }; + const { gp, sp, cp } = makeChange(); + const [goldValue, setGoldValue] = React.useState(gp.toString()); + const [silverValue, setSilverValue] = React.useState(sp.toString()); + const [copperValue, setCopperValue] = React.useState(cp.toString()); + + const handleUpdate = async ( + valueToSet: number, + originalValue: number, + multiplier: number, + setFunc: React.Dispatch>, + ) => { + if (isNaN(valueToSet) || !setCharacter || !character) return; + + const newGoldValue = + character.gold + (valueToSet - originalValue) / multiplier; + setFunc(valueToSet.toString()); + setCharacter({ + ...character, + gold: newGoldValue, + }); + }; + + const handleInputBlur = async ( + newValue: string, + originalValue: number, + setFunc: React.Dispatch>, + multiplier: number, + ) => { + let valueToSet: number = NaN; + + if (newValue.startsWith("+") || newValue.startsWith("-")) { + const delta = parseInt(newValue.slice(1)); + if (isNaN(delta)) return; + valueToSet = newValue.startsWith("+") + ? originalValue + delta + : Math.max(originalValue - delta, 0); + } else { + const value = parseInt(newValue); + if (isNaN(value) || value < 0) return; + valueToSet = value; + } + + await handleUpdate(valueToSet, originalValue, multiplier, setFunc); + }; + + const handleFocus = (e: React.FocusEvent) => { + setTimeout(() => { + e.target.select(); + }, 50); + }; + + const handleChange = ( + e: React.ChangeEvent, + setFunc: React.Dispatch>, + ) => { + handleInputChange(e, setFunc); + }; + + const handleBlurAndEnter = ( + value: string, + setFunc: React.Dispatch>, + multiplier: number, + key: CostCurrency, + ) => { + const originalValue = (makeChange() as { [key: string]: number })[key]; + handleInputBlur(value, originalValue, setFunc, multiplier); + }; + + React.useEffect(() => { + const { gp, sp, cp } = makeChange(); + setGoldValue(gp.toString()); + setSilverValue(sp.toString()); + setCopperValue(cp.toString()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [character?.gold]); + + return { + goldValue, + setGoldValue, + silverValue, + setSilverValue, + copperValue, + setCopperValue, + handleFocus, + handleChange, + handleBlurAndEnter, + }; +} diff --git a/src/hooks/useNotification.ts b/src/hooks/useNotification.ts new file mode 100644 index 00000000..d28c1b9a --- /dev/null +++ b/src/hooks/useNotification.ts @@ -0,0 +1,18 @@ +import { notification } from "antd"; + +export function useNotification() { + const [api, contextHolder] = notification.useNotification(); + + const openNotification = ( + title: string, + description: string, + duration: number = 0, + ) => { + api.open({ + message: title, + description, + duration, + }); + }; + return { contextHolder, openNotification }; +} diff --git a/src/hooks/useSpellData.ts b/src/hooks/useSpellData.ts new file mode 100644 index 00000000..f285a66f --- /dev/null +++ b/src/hooks/useSpellData.ts @@ -0,0 +1,31 @@ +import { classes } from "@/data/classes"; +import { classSplit } from "@/support/characterSupport"; +import React from "react"; +import { ClassNames } from "@/data/definitions"; + +export function useSpellData() { + const [hasSpellBudget, setHasSpellBudget] = React.useState(false); + + const checkSpellCaster = (charClass: string | string[]) => { + const classArr = classSplit(charClass); + return classArr.some((c) => { + const classData = classes[c as ClassNames]; + return ( + classData && classData.spellBudget && classData.spellBudget.length > 0 + ); + }); + }; + + const isSpellCaster = React.useCallback( + (charClass: string | string[]) => { + const result = checkSpellCaster(charClass); + if (result !== hasSpellBudget) { + setHasSpellBudget(result); + } + return result; + }, + [hasSpellBudget], + ); + + return { isSpellCaster }; +} diff --git a/src/index.css b/src/index.css old mode 100755 new mode 100644 index 3981a1e8..c2268d1d --- a/src/index.css +++ b/src/index.css @@ -2,53 +2,6 @@ @tailwind components; @tailwind utilities; -button span { - color: theme("colors.shipGray") !important; -} - -.ant-layout > header .ant-typography, -.ant-table { - color: theme("colors.springWood") !important; -} - -.ant-table { - background-color: theme("colors.shipGray") !important; -} - -.ant-table-row td { - border-bottom-color: rgba(0, 0, 0, 2.5) !important; -} - -.ant-table-row:hover td { - background-color: rgba(0, 0, 0, 0.25) !important; -} - -.ant-collapse-content, -.ant-modal-content, -.ant-modal-header { - background-color: theme("colors.springWood") !important; -} - -.ant-card-actions { - background-color: theme("colors.seaBuckthorn") !important; - border-top-color: rgba(0, 0, 0, 0.35) !important; -} -.ant-card-actions li { - border-color: rgba(0, 0, 0, 0.35) !important; -} -.ant-card-actions li span .anticon:hover { - color: rgba(0, 0, 0, 0.75) !important; -} - -.ant-tabs-tab > div { - padding: 0.25rem 0.5rem; -} -.ant-tabs-tab-active > div { - background: theme("colors.shipGray") !important; - border-radius: 0.5rem; - color: theme("colors.springWood") !important; -} - @font-face { font-family: "Enchant"; src: url("assets/fonts/Enchant.ttf") format("truetype"); diff --git a/src/index.tsx b/src/index.tsx deleted file mode 100755 index 65f54e6a..00000000 --- a/src/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import App from "./App"; -import { BrowserRouter } from "react-router-dom"; -import "./index.css"; - -const root = ReactDOM.createRoot( - document.getElementById("root") as HTMLElement -); -root.render( - ( - - - - - - ) as any -); diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 00000000..0c02d41b --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App.tsx"; +import { BrowserRouter } from "react-router-dom"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + , +); diff --git a/src/modals/AddCustomEquipmentModal.tsx b/src/modals/AddCustomEquipmentModal.tsx deleted file mode 100755 index 29a5f597..00000000 --- a/src/modals/AddCustomEquipmentModal.tsx +++ /dev/null @@ -1,635 +0,0 @@ -import { useEffect, useState } from "react"; -import { - Button, - Checkbox, - Form, - Input, - InputNumber, - Modal, - Radio, - RadioChangeEvent, - Select, -} from "antd"; -import { useParams } from "react-router-dom"; -import { doc, updateDoc } from "firebase/firestore"; -import { db } from "../firebase"; -import CloseIcon from "../components/CloseIcon/CloseIcon"; -import { AddCustomEquipmentModalProps } from "./definitions"; -import equipmentItems from "../data/equipmentItems.json"; -import { slugToTitleCase } from "../support/stringSupport"; -import { getItemCost } from "../support/formatSupport"; -import HomebrewWarning from "../components/HomebrewWarning/HomebrewWarning"; -import DOMPurify from "dompurify"; -import { EquipmentItem } from "../data/definitions"; - -const initialFormState = { - name: undefined, - category: undefined, - subCategory: undefined, - costValue: undefined, - costCurrency: "gp", - armorOrShield: false, - purchased: true, - weight: undefined, - damage: undefined, - type: "both", - ac: undefined, - size: undefined, - amount: 1, -}; - -const categoriesSet = new Set(equipmentItems.map((item) => item.category)); - -const equipmentCategories = Array.from(categoriesSet) - .map((category) => { - return { value: category, label: slugToTitleCase(category) }; - }) - .sort((a, b) => a.label.localeCompare(b.label)); - -const onFinishFailed = (errorInfo: any) => { - console.error("Failed:", errorInfo); -}; - -export default function AddCustomEquipmentModal({ - isAddCustomEquipmentModalOpen, - handleCancel, - characterData, - setCharacterData, - user, -}: AddCustomEquipmentModalProps) { - const [formState, setFormState] = useState(initialFormState); - const [prevValue, setPrevValue] = useState(characterData.equipment); - - const [form] = Form.useForm(); - const type = Form.useWatch("type", { form }); - - const { uid, id } = useParams(); - - const updateEquipment = async () => { - if (!uid || !id) { - console.error("User ID or Character ID is undefined"); - return; - } - if (user?.uid !== uid) { - console.log("Not the owner of the character sheet."); - return; - } - - if (characterData.equipment !== prevValue) { - const docRef = doc(db, "users", uid, "characters", id); - - try { - await updateDoc(docRef, { - equipment: characterData.equipment, - gold: characterData.gold, - }); - setPrevValue(characterData.equipment); - } catch (error) { - console.error("Error updating document: ", error); - } - } - }; - - useEffect(() => { - updateEquipment(); - }, [characterData.equipment, characterData.gold, characterData.weight]); - - const onFinish = (values: any) => { - // Common properties - let newItem: EquipmentItem = { - name: values.name, - costValue: values.costValue, - costCurrency: values.costCurrency, - category: values.category, - amount: values.amount, - }; - - // Additional properties based on category - switch (values.category) { - case "ammunition": - newItem = { - ...newItem, - weight: values.weight, - damage: values.damage, - }; - break; - case "armor": - newItem = { - ...newItem, - AC: values["armor-ac"], - weight: values.weight, - }; - break; - case "shields": - newItem = { - ...newItem, - AC: values["shield-ac"], - weight: values.weight, - }; - break; - case "axes": - case "hammers-and-maces": - case "improvised-weapons": - case "other-weapons": - case "spears-and-polearms": - newItem = { - ...newItem, - size: values.size, - weight: values.weight, - damage: values.damage, - type: values.type, - range: values.range, - }; - break; - case "beasts-of-burden": - // No additional properties - break; - case "bows": - newItem = { - ...newItem, - size: values.size, - weight: values.weight, - type: "missile", - range: values.range, - }; - break; - case "slings-and-hurled-weapons": - newItem = { - ...newItem, - size: values.size, - weight: values.weight, - type: "missile", - damage: values.damage, - range: values.range, - }; - break; - case "brawling": - case "chain-and-flail": - case "swords": - newItem = { - ...newItem, - size: values.size, - weight: values.weight, - damage: values.damage, - type: "melee", - }; - break; - case "daggers": - newItem = { - ...newItem, - size: values.size, - weight: values.weight, - damage: values.damage, - type: "both", - range: values.range, - }; - break; - case "items": - newItem = { - ...newItem, - weight: values.weight, - }; - break; - default: - break; - } - - // Calculate the item cost - const itemCost = getItemCost(newItem); - - // Update the character's equipment and gold - const updatedEquipment = [...characterData.equipment, newItem]; - const updatedGold = characterData.gold - (values.purchased ? itemCost : 0); - - setCharacterData({ - ...characterData, - equipment: updatedEquipment, - gold: updatedGold, - }); - - handleCancel(); - setFormState(initialFormState); - }; - - const handleFormChange = (event: { target: { name: any; value: any } }) => { - const { name, value } = event.target; - setFormState({ - ...formState, - [name]: DOMPurify.sanitize(value), - }); - }; - - const handleNumberChange = ( - value: number | string | null, - fieldName: string - ): void => { - const numValue = typeof value === "string" ? Number(value) : value; - - setFormState({ - ...formState, - [fieldName]: numValue, - }); - }; - - const handleSelectChange = (value: string, name: string) => { - setFormState({ - ...formState, - [name]: value, - }); - }; - - const handleRadioChange = (event: RadioChangeEvent) => { - const { value } = event.target; - setFormState({ - ...formState, - type: value, - }); - }; - - const showAttackTypeCategories: string[] = [ - "axes", - "hammers-and-maces", - "improvised-weapons", - "other-weapons", - ]; - - const showDamageCategories: string[] = [ - "ammunition", - "axes", - "chain-and-flail", - "daggers", - "hammers-and-maces", - "improvised-weapons", - "other-weapons", - "slings-and-hurled-weapons", - "spears-and-polearms", - "swords", - ]; - - const showSizeCategories = [ - "axes", - "bows", - "chain-and-flail", - "daggers", - "hammers-and-maces", - "improvised-weapons", - "other-weapons", - "slings-and-hurled-weapons", - "spears-and-polearms", - "swords", - ]; - - return ( - } - > -
- -
- - - - - - value !== undefined && - handleSelectChange(value, "subCategory") - } - value={formState.subCategory} - /> - -
- - handleNumberChange(value, "costValue")} - placeholder="0" - value={formState.costValue} - /> - - - - -
-
formState.category === category - ) || - formState.category === "bows" || - formState.category === "daggers" || - formState.category === "slings-and-hurled-weapons") && - type !== "melee" - ? "" - : "hidden" - } - // rules={[ - // { - // required: showAttackTypeCategories.some( - // (category) => formState.category === category - // ), - // message: "Required", - // }, - // ]} - > - - handleNumberChange(value, "shortRange")} - > - - - handleNumberChange(value, "mediumRange")} - > - - - handleNumberChange(value, "longRange")} - > - -
-
-
- - - - - handleNumberChange(value, "ac")} - /> - -
-
-
- formState.category === category - ) - ? "hidden" - : "" - } - rules={[ - { - required: showSizeCategories.some( - (category) => formState.category === category - ), - message: "Required", - }, - ]} - > - { - if (event.key === "Enter") { - handleRollClick(); - } - }} - /> - - - - Type any dice notation in the input box. ex: 2d6+5 - - - - ); -} diff --git a/src/modals/LevelUpModal.tsx b/src/modals/LevelUpModal.tsx deleted file mode 100755 index d1c15427..00000000 --- a/src/modals/LevelUpModal.tsx +++ /dev/null @@ -1,269 +0,0 @@ -import { Button, Checkbox, Modal, Typography } from "antd"; -import { DiceRoller } from "@dice-roller/rpg-dice-roller"; -import { CheckboxValueType } from "antd/es/checkbox/Group"; -import CloseIcon from "../components/CloseIcon/CloseIcon"; -import { LevelUpModalProps } from "./definitions"; -import spellList from "../data/spells.json"; -import { getClassType } from "../support/helpers"; -import { doc, updateDoc } from "firebase/firestore"; -import { db } from "../firebase"; -import { useParams } from "react-router-dom"; -import { InfoCircleOutlined } from "@ant-design/icons"; -import { useState } from "react"; -import { marked } from "marked"; -import DescriptionBubble from "../components/CharacterCreator/DescriptionBubble/DescriptionBubble"; -import { classes } from "../data/classes"; -import { ClassNames, Spell } from "../data/definitions"; - -const roller = new DiceRoller(); - -export default function LevelUpModal({ - characterData, - handleCancel, - isLevelUpModalOpen, - setCharacterData, - hitDice, -}: LevelUpModalProps) { - const { uid, id } = useParams(); - const [spellDescription, setSpellDescription] = useState(""); - const [spellName, setSpellName] = useState(""); - - const newHitDiceValue = hitDice( - characterData.level + 1, - characterData.class, - characterData.hp.dice - ); - const spells: Spell[] = spellList; - - const spellsOfLevel = (className: string[], level: number) => { - const classType = getClassType(className); - - let filteredSpells: Spell[] = []; - - switch (classType) { - case "standard": - filteredSpells = spells.filter( - (spell) => spell.level[className[0].toLowerCase()] === level - ); - break; - case "combination": - filteredSpells = spells.filter((spell) => - className.some((cls) => spell.level[cls.toLowerCase()] === level) - ); - break; - case "custom": - // If the level is 1, return all spells. Otherwise, return an empty array. - filteredSpells = level === 1 ? spells : []; - break; - default: - filteredSpells = []; - } - - return filteredSpells; - }; - - const shouldDisableCheckbox = ( - spell: string, - newSpellCounts: number[], - spellBudget: number[], - newSpells: Spell[], - index: number - ) => { - return ( - spell === "Read Magic" || - (newSpellCounts[index] >= spellBudget[index] && - !newSpells.some((knownSpell) => knownSpell.name === spell)) - ); - }; - - const showSpellDescription = (text: string, title?: string) => { - title && setSpellName(title); - setSpellDescription(text); - }; - - const SpellSelector = ({ className }: { className: string }) => { - let spellBudget: number[] = []; - const newSpells = characterData.spells; - const newSpellCounts = newSpells.reduce( - (acc: number[], spell: Spell) => { - // If it is a combination class, just use the magic-user level - const spellLevel = - getClassType(characterData.class) === "combination" - ? spell.level[ClassNames.MAGICUSER.toLowerCase()] - : spell.level[characterData.class[0].toLowerCase()]; - if (spellLevel !== null && !isNaN(spellLevel)) { - acc[spellLevel - 1] += 1; - } - return acc; - }, - [0, 0, 0, 0, 0, 0] - ); - if ( - classes[characterData.class[0] as ClassNames]?.spellBudget && - getClassType(characterData.class) !== "custom" - ) { - if (getClassType(characterData.class) === "standard") { - spellBudget = - classes[characterData.class[0] as ClassNames].spellBudget![ - characterData.level - ]; - } else { - // If a combination class, use the magic-user spell budget - spellBudget = classes[ClassNames.MAGICUSER as ClassNames].spellBudget?.[ - characterData.level - ] ?? [0]; - } - // If the character is a custom class, allow them to choose any spells - } else if (getClassType(characterData.class) === "custom") { - spellBudget = new Array(6).fill(Infinity); - } - - const handleSpellChange = - (level: number) => (checkedValues: CheckboxValueType[]) => { - let newCheckedSpells: Spell[] = []; - - const classType = getClassType(characterData.class); - - if (classType === "custom" && level === 1) { - // For custom classes, handle all spells - newCheckedSpells = spells.filter((spell) => - checkedValues.includes(spell.name) - ); - } else { - // For standard and combination classes, filter out spells of the specific level - const classNameToCheck = - classType === "combination" - ? ClassNames.MAGICUSER.toLowerCase() - : characterData.class[0].toLowerCase(); - - newCheckedSpells = characterData.spells.filter( - (spell: Spell) => spell.level[classNameToCheck] !== level - ); - - checkedValues.forEach((value) => { - const foundSpell = spells.find((spell) => spell.name === value); - if (foundSpell) { - newCheckedSpells.push(foundSpell); - } - }); - } - - setCharacterData({ ...characterData, spells: newCheckedSpells }); - }; - - const isCustomClass = getClassType(characterData.class) === "custom"; - - return characterData.level < 20 && spellBudget?.length ? ( -
- {spellBudget.map((max, index) => { - if ((isCustomClass && index !== 0) || max === 0) return null; - - return ( -
- - {isCustomClass - ? "Select your Custom Class Spells" - : `Select Level ${index + 1} Spells`} - - spell.name)} - onChange={handleSpellChange(index + 1)} - > - {spellsOfLevel(characterData.class, index + 1) - .sort((a, b) => a.name.localeCompare(b.name)) - .map((spell) => { - const description = marked(spell.description); - return ( -
- - {spell.name} -
- ); - })} -
-
- ); - })} -
- ) : null; - }; - - const handleLevelUp = async () => { - const result = roller.roll(newHitDiceValue).total; - const newCharacterData = { - ...characterData, - hp: { ...characterData.hp, max: result, dice: newHitDiceValue }, - level: characterData.level + 1, - }; - - // Update the character in the component state - setCharacterData(newCharacterData); - - // Update the character in Firebase - try { - if (!uid || !id) { - console.error("User ID or Character ID is undefined"); - return; - } - - const docRef = doc(db, "users", uid, "characters", id); - await updateDoc(docRef, { - "hp.max": newCharacterData.hp.max, - "hp.dice": newCharacterData.hp.dice, - level: newCharacterData.level, - spells: newCharacterData.spells, - }); - } catch (error) { - console.error("Error updating document: ", error); - } - - handleCancel(); - }; - - return ( - } - width={800} - > -
- - {spellDescription !== "" && ( - - )} -
- -
- ); -} diff --git a/src/modals/LoginSignupModal.tsx b/src/modals/LoginSignupModal.tsx deleted file mode 100755 index 24e26445..00000000 --- a/src/modals/LoginSignupModal.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { Button, Input, Modal } from "antd"; -import CloseIcon from "../components/CloseIcon/CloseIcon"; -import { - LoginFormProps, - LoginSignupModalProps, - SignupFormProps, -} from "./definitions"; -import { useNavigate } from "react-router-dom"; -import { FC, FormEvent, useState } from "react"; -import { - createUserWithEmailAndPassword, - signInWithEmailAndPassword, -} from "firebase/auth"; -import { auth } from "../firebase"; -import classNames from "classnames"; - -const formClassNames = classNames("my-4", "[&>div+div]:mt-4"); - -const LoginForm: FC = ({ setEmail, setPassword, onLogin }) => { - return ( -
-
- - setEmail(e.target.value)} - /> -
-
- - setPassword(e.target.value)} - /> -
-
- -
-
- ); -}; - -const SignupForm: FC = ({ - email, - setEmail, - password, - setPassword, - onSubmit, -}) => { - return ( -
-
- - setEmail(e.target.value)} - required - placeholder="Email address" - /> -
-
- - setPassword(e.target.value)} - required - placeholder="Password" - /> -
-
- -
-
- ); -}; - -export default function LoginSignupModal({ - handleCancel, - isLoginSignupModalOpen, - handleLogin, -}: LoginSignupModalProps) { - const navigate = useNavigate(); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [signInForm, setSignInForm] = useState(true); - - const onLogin = (e: FormEvent) => { - e.preventDefault(); - signInWithEmailAndPassword(auth, email, password) - .then((userCredential) => { - // Signed in - const user = userCredential.user; - navigate("/"); - handleCancel(); - }) - .catch((error) => { - const errorCode = error.code; - const errorMessage = error.message; - // Here, you can also show a message to the user about the failed login - // but the modal will remain open - }); - }; - - const onSubmit = async (e: FormEvent) => { - e.preventDefault(); - - await createUserWithEmailAndPassword(auth, email, password) - .then((userCredential) => { - // Signed in - const user = userCredential.user; - navigate("/"); - handleCancel(); - }) - .catch((error) => { - const errorCode = error.code; - const errorMessage = error.message; - console.error(errorCode, errorMessage); - // .. - }); - }; - return ( - } - > - {signInForm ? ( -
- - - -
- ) : ( -
- - -
- )} -
- ); -} diff --git a/src/modals/definitions.ts b/src/modals/definitions.ts deleted file mode 100755 index 466cf7c1..00000000 --- a/src/modals/definitions.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { FormEvent } from "react"; -import { CharacterData } from "../data/definitions"; -import { User } from "firebase/auth"; - -export type ModalProps = { - handleCancel: () => void; - characterData?: CharacterData; -}; - -export interface LevelUpModalProps extends ModalProps { - isLevelUpModalOpen: boolean; - hitDice: (level: number, className: string[], dice: string) => string; - setCharacterData: (character: CharacterData) => void; - characterData: CharacterData; -} - -export interface DiceRollerModalProps extends ModalProps { - isDiceRollerModalOpen: boolean; -} - -export interface AddEquipmentModalProps extends ModalProps { - isAddEquipmentModalOpen: boolean; - setCharacterData: (character: CharacterData) => void; - user: User | null; -} - -export interface CheatSheetModalProps extends ModalProps { - isCheatSheetModalOpen: boolean; -} - -export interface AddCustomEquipmentModalProps extends ModalProps { - isAddCustomEquipmentModalOpen: boolean; - setCharacterData: (character: CharacterData) => void; - characterData: CharacterData; - user: User | null; -} - -export interface LoginSignupModalProps extends ModalProps { - isLoginSignupModalOpen: boolean; - handleLogin: () => Promise; -} - -export interface LoginFormProps { - setEmail: (email: string) => void; - setPassword: (password: string) => void; - onLogin: (e: FormEvent) => void; -} - -export interface SignupFormProps { - email: string; - setEmail: (email: string) => void; - password: string; - setPassword: (password: string) => void; - onSubmit: (e: FormEvent) => void; -} - -export interface SpellCheckboxGroupProps { - characterClass: string; - level: number; - max: number; - checkedSpells: string[]; - setCheckedSpells: (checkedSpells: string[]) => void; - checkedSpellsCount: number[]; - setCheckedSpellsCount: (checkedSpellsCount: number[]) => void; -} - -export interface CreateCharacterModalProps { - isModalOpen: boolean; - setIsModalOpen: (isOpen: boolean) => void; - onCharacterAdded: () => void; -} diff --git a/src/pages/CharacterCreator/CharacterCreator.tsx b/src/pages/CharacterCreator/CharacterCreator.tsx deleted file mode 100755 index 16af4d5c..00000000 --- a/src/pages/CharacterCreator/CharacterCreator.tsx +++ /dev/null @@ -1,360 +0,0 @@ -import { marked } from "marked"; -import { useState } from "react"; -import { Button, Divider, Steps, Typography, message } from "antd"; -import CharacterAbilities from "../../components/CharacterCreator/CharacterAbilities/CharacterAbilities"; -import CharacterRace from "../../components/CharacterCreator/CharacterRace/CharacterRace"; -import CharacterClass from "../../components/CharacterCreator/CharacterClass/CharacterClass"; -import CharacterHitPoints from "../../components/CharacterCreator/CharacterHitPoints/CharacterHitPoints"; -import EquipmentStore from "../../components/EquipmentStore/EquipmentStore"; -import CharacterName from "../../components/CharacterCreator/CharacterName/CharacterName"; -import { useNavigate, useOutletContext } from "react-router-dom"; -import { collection, doc, setDoc } from "firebase/firestore"; -import { auth, db } from "../../firebase"; -import { classes } from "../../data/classes"; -import { - Abilities, - CharacterData, - ClassNames, - Spell, -} from "../../data/definitions"; - -const abilityDescription = marked( - `Roll for your character's Abilities. **You can click the "Roll" buttons or use your own dice and record your scores**. Afterward your character will have a score ranging from 3 to 18 in each of the Abilities below. A bonus (or penalty) Modifier is then associated with each score. Your character's Abilities will begin to determine the options available to them in the next steps as well, so good luck! - - BFRPG Character Ability documentation` -); - -const raceDescription = marked( - `Choose your character's Race. **Some options may be unavailable due to your character's Ability Scores**. Each Race except Humans has a minimum and maximum value for specific Abilities that your character's Ability Scores must meet in order to select them. Consider that each Race has specific restrictions, special abilities, and Saving Throws. Choose wisely. **Races included in the base game rules are in bold**. - - BFRPG Character Race documentation` -); - -const classDescription = marked( - `Choose your character's Class. **Your character's Race and Ability Scores will determine which Class options are available**. Your Class choice determines your character's background and how they will progress through the game as they level up. **Classes included in the base game rules are in bold**. - - BFRPG Character Class documentation` -); - -const hitPointsDescription = marked( - `Roll for your character's Hit Points. **Your character's Race may place restrictions on the Hit Dice available to them, but generally this is determined by their chosen Class**. Additionally, your character's Constitution modifier is added/subtracted from this value with a minimum value of 1. The end result is the amount of Hit Points your character will start with and determines how much damage your character can take in battle. - - BFRPG Character Hit Points documentation` -); - -const equipmentDescription = marked( - `Roll for your character's starting gold and purchase their equipment. **Keep in mind that your character's Race and Class selections may limit types and amounts of equipment they can have**. - - BFRPG Character Equipment documentation` -); - -const nameDescription = marked( - `You're almost done! Personalize your newly minted character by giving them an identity. Optionally, upload a portrait image if you'd like. Image must be PNG/JPG and <= 1mb` -); - -const emptyCharacter = { - abilities: { - scores: { - strength: 0, - intelligence: 0, - wisdom: 0, - constitution: 0, - dexterity: 0, - charisma: 0, - }, - modifiers: { - strength: "", - intelligence: "", - wisdom: "", - constitution: "", - dexterity: "", - charisma: "", - }, - }, - class: [], - race: "", - hp: { - dice: "", - points: 0, - max: 0, - desc: "", - }, - spells: [], - gold: 0, - equipment: [], - weight: 0, - name: "", - avatar: "", - level: 1, - specials: { race: [], class: [] }, - restrictions: { race: [], class: [] }, - savingThrows: { - deathRayOrPoison: 0, - magicWands: 0, - paralysisOrPetrify: 0, - dragonBreath: 0, - spells: 0, - }, - xp: 0, - desc: "", - wearing: { - armor: "", - shield: "", - }, -}; - -export default function CharacterCreator() { - const outletContext = useOutletContext() as { className: string }; - const navigate = useNavigate(); - const [current, setCurrent] = useState(0); - const [comboClass, setComboClass] = useState(false); - const [checkedClasses, setCheckedClasses] = useState([]); - const [characterData, setCharacterData] = - useState(emptyCharacter); - const [selectedSpell, setSelectedSpell] = useState(null); - const [messageApi, contextHolder] = message.useMessage(); - - const steps = [ - { - title: "Abilities", - fullTitle: "Roll for Ability Scores", - description: abilityDescription, - content: ( - - ), - }, - { - title: "Race", - fullTitle: "Choose a Race", - description: raceDescription, - content: ( - - ), - }, - { - title: "Class", - fullTitle: "Choose a Class", - description: classDescription, - content: ( - - ), - }, - { - title: "Hit Points", - fullTitle: "Roll for Hit Points", - description: hitPointsDescription, - content: ( - - ), - }, - { - title: "Equipment", - fullTitle: "Buy Equipment", - description: equipmentDescription, - content: ( - - ), - }, - { - title: "Name", - fullTitle: "Name your character", - description: nameDescription, - content: ( - - ), - }, - ]; - - const next = () => { - setCurrent(current + 1); - }; - - const prev = () => { - setCurrent(current - 1); - }; - - const items = steps.map((item) => ({ - key: item.title, - title: item.title, - })); - - function areAllAbilitiesSet(abilities: Abilities) { - for (let key in abilities) { - const value = +abilities[key as keyof typeof abilities]; - if (value <= 0 || isNaN(value)) { - return false; - } - } - return true; - } - - function isNextButtonEnabled(currentStep: number) { - switch (currentStep) { - case 0: - return areAllAbilitiesSet(characterData.abilities.scores); - case 1: - return characterData.race !== ""; - case 2: - // Check if the character has a class - // AND IF SO, any value in their class has a spell budget - // AND IF SO, they have more than 1 spell - if (characterData.class.length === 0) { - return false; - } else if ( - characterData.class.some((className) => { - const spellBudget = classes[className as ClassNames]?.spellBudget; - return spellBudget && spellBudget[0] && spellBudget[0][0] > 0; - }) - ) { - return characterData.spells.length > 1; - } - return true; - case 3: - return characterData.hp.points !== 0; - case 4: - return characterData.equipment.length !== 0; - case 5: - return characterData.name; - default: - return true; - } - } - - const success = (name: string) => { - messageApi.open({ - type: "success", - content: `${name} successfully saved!`, - }); - }; - - const errorMessage = (message: string) => { - messageApi.open({ - type: "error", - content: "This is an error message", - }); - }; - - async function addCharacterData(characterData: CharacterData) { - // Check if a user is currently logged in - if (auth.currentUser) { - // Get the current user's UID - const uid = auth.currentUser.uid; - - // Get a reference to the Firestore document - const docRef = doc(collection(db, `users/${uid}/characters`)); - - // Set the character data for the current user - try { - await setDoc(docRef, characterData); - success(characterData.name); - // Go back to Home - navigate("/"); - // Reset characterData - setCharacterData(emptyCharacter); - // Reset step - setCurrent(0); - } catch (error) { - console.error("Error writing document: ", error); - errorMessage(`Error writing document (see console)`); - } - } else { - console.error("No user is currently logged in."); - errorMessage(`No user is currently logged in.`); - } - } - - const FloatingGold = () => { - return ( -
- {`Gold: ${characterData.gold.toFixed(2)}`} -
- ); - }; - - return ( -
- {contextHolder} -
- -
- {current === 4 && } - - {steps[current].fullTitle} - - -
- - - {steps[current].content} -
-
- {current > 0 && ( - - )} - {current < steps.length - 1 && ( - - )} - {current === steps.length - 1 && ( - - )} -
-
-
- ); -} diff --git a/src/pages/CharacterList/CharacterList.tsx b/src/pages/CharacterList/CharacterList.tsx deleted file mode 100755 index d1f04f68..00000000 --- a/src/pages/CharacterList/CharacterList.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { Button, Col, Empty, Row, Spin, Typography } from "antd"; -import { useNavigate, useOutletContext } from "react-router-dom"; -import { collection, deleteDoc, doc, onSnapshot } from "firebase/firestore"; -import { db } from "../../firebase"; -import { useEffect, useState } from "react"; -import classNames from "classnames"; -import CharacterCard from "../../components/CharacterCard/CharacterCard"; -import { images } from "../../assets/images/faces/imageAssets"; -import { extractImageName } from "../../support/stringSupport"; -import { CharacterData } from "../../data/definitions"; -import { User } from "firebase/auth"; -import { UserAddOutlined } from "@ant-design/icons"; - -export default function CharacterList({ - user, - className, -}: { user: User | null } & React.ComponentPropsWithRef<"div">) { - const outletContext = useOutletContext() as { className: string }; - const [characters, setCharacters] = useState([]); - const [loading, setLoading] = useState(true); - - const navigate = useNavigate(); - - useEffect(() => { - const fetchCharacters = async () => { - try { - if (user) { - const uid = user.uid; - const charactersCollectionRef = collection( - db, - `users/${uid}/characters` - ); - - // Listen to real-time updates - const unsubscribe = onSnapshot( - charactersCollectionRef, - (snapshot) => { - const userCharacters = snapshot.docs.map((doc) => { - const data = doc.data() as CharacterData; - data.id = doc.id; - return data; - }); - setCharacters(userCharacters); - document.title = `CODEX.QUEST`; - setLoading(false); - } - ); - - // Return the unsubscribe function to clean up the listener - return () => unsubscribe(); - } - } catch (error) { - console.error("Failed to fetch characters:", error); - } - }; - - // Call the fetchCharacters function when the component mounts - fetchCharacters(); - }, [user]); - - const confirm = async (characterId: string) => { - if (user) { - const characterDoc = doc( - db, - `users/${user.uid}/characters/${characterId}` - ); - await deleteDoc(characterDoc); - } - }; - - useEffect(() => { - setLoading(false); - }, [characters]); - - const characterListClassNames = classNames( - outletContext.className, - className, - "text-shipGray", - "[&>*+*]:mt-4" - ); - - return ( -
-
- - Characters - - -
- {loading ? ( - - ) : characters.length > 0 ? ( - - {characters - .sort((a, b) => a.name.localeCompare(b.name)) - .map((characterData) => { - // Legacy characters created while the site was using Create React App will have broken image links that start with "/static/media/" - // This code checks for that and replaces the broken link with the correct one - let image = ""; - if (characterData.avatar.startsWith("/static/media/")) { - const legacyImage = extractImageName(characterData.avatar); - if (legacyImage) { - // find the matching source images in `images` - // "/src/assets/images/faces/gnome-boy-1.jpg" matches gnome-boy-1 - image = - images.find((image) => image.includes(legacyImage)) || ""; - } - } else { - image = characterData.avatar; - } - return ( -
- - - ); - })} - - ) : ( - - )} - - ); -} diff --git a/src/pages/CharacterSheet/CharacterSheet.tsx b/src/pages/CharacterSheet/CharacterSheet.tsx deleted file mode 100755 index 421f9098..00000000 --- a/src/pages/CharacterSheet/CharacterSheet.tsx +++ /dev/null @@ -1,515 +0,0 @@ -// REACT -import React, { useEffect, useState } from "react"; -// REACT ROUTER -import { Link, useOutletContext, useParams } from "react-router-dom"; -// FIREBASE -import { doc, onSnapshot, updateDoc } from "firebase/firestore"; -import { db } from "../../firebase"; -// DEFINITIONS -import { User } from "firebase/auth"; -// ANTD COMPONENTS -import { - Breadcrumb, - Button, - Col, - Divider, - Row, - Skeleton, - Typography, -} from "antd"; -import { - FileSearchOutlined, - HomeOutlined, - SolutionOutlined, -} from "@ant-design/icons"; -// CHARACTER SHEET COMPONENTS -import BaseStats from "../../components/CharacterSheet/BaseStats/BaseStats"; -import InitiativeRoller from "../../components/CharacterSheet/InitiativeRoller/InitiativeRoller"; -import Abilities from "../../components/CharacterSheet/Abilities/Abilities"; -import AttackBonus from "../../components/CharacterSheet/AttackBonus/AttackBonus"; -import HitPoints from "../../components/CharacterSheet/HitPoints/HitPoints"; -import SimpleNumberStat from "../../components/CharacterSheet/SimpleNumberStat/SimpleNumberStat"; -import SpecialsRestrictions from "../../components/CharacterSheet/SpecialsRestrictions/SpecialsRestrictions"; -import SpecialAbilitiesTable from "../../components/CharacterSheet/SpecialAbilitiesTable/SpecialAbilitiesTable"; -import SavingThrows from "../../components/CharacterSheet/SavingThrows/SavingThrows"; -import MoneyStats from "../../components/CharacterSheet/MoneyStats/MoneyStats"; -import WeightStats from "../../components/CharacterSheet/WeightStats/WeightStats"; -import EquipmentInfo from "../../components/CharacterSheet/EquipmentInfo/EquipmentInfo"; -import CharacterSpellList from "../../components/CharacterSheet/EquipmentInfo/CharacterSpellList/CharacterSpellList"; -import CharacterDescription from "../../components/CharacterSheet/CharacterDescription/CharacterDescription"; -import EquipmentList from "../../components/CharacterSheet/EquipmentInfo/EquipmentList/EquipmentList"; -import CharacterSheetModals from "../../components/CharacterSheet/CharacterSheetModals/CharacterSheetModals"; -// DATA -import { classes } from "../../data/classes"; -import { - CharacterData, - ClassNames, - EquipmentCategories, - EquipmentItem, - RaceNames, -} from "../../data/definitions"; -// SUPPORT -import { makeChange } from "../../support/formatSupport"; -import { - getArmorClass, - getAttackBonus, - getClassType, - getHitDice, - getMovement, - isStandardClass, -} from "../../support/helpers"; -import DiceSvg from "../../assets/images/dice.svg"; -import classNames from "classnames"; - -export default function CharacterSheet({ user }: { user: User | null }) { - const { uid, id } = useParams(); - const [characterData, setCharacterData] = useState( - null - ); - const [weapon, setWeapon] = useState(undefined); - - const userLoggedIn: User | null = user; - const userIsOwner = userLoggedIn?.uid === uid; - - const outletContext = useOutletContext() as { className: string }; - - const [isLevelUpModalOpen, setIsLevelUpModalOpen] = useState(false); - const showLevelUpModal = () => { - setIsLevelUpModalOpen(true); - }; - - const [isDiceRollerModalOpen, setIsDiceRollerModalOpen] = useState(false); - const showDiceRollerModal = () => { - setIsDiceRollerModalOpen(true); - }; - - const [isCheatSheetModalOpen, setIsCheatSheetModalOpen] = useState(false); - const showCheatSheetModal = () => { - setIsCheatSheetModalOpen(true); - }; - - const [isAddEquipmentModalOpen, setIsAddEquipmentModalOpen] = useState(false); - const showAddEquipmentModal = () => { - setIsAddEquipmentModalOpen(true); - }; - - const [isAddCustomEquipmentModalOpen, setIsAddCustomEquipmentModalOpen] = - useState(false); - const showAddCustomEquipmentModal = () => { - setIsAddCustomEquipmentModalOpen(true); - }; - - const [isAttackModalOpen, setIsAttackModalOpen] = useState(false); - const showAttackModal = () => { - setIsAttackModalOpen(true); - }; - - const handleCancel = () => { - setIsLevelUpModalOpen(false); - setIsDiceRollerModalOpen(false); - setIsAddEquipmentModalOpen(false); - setIsAddCustomEquipmentModalOpen(false); - setIsAttackModalOpen(false); - setIsCheatSheetModalOpen(false); - }; - - const handleCustomDelete = (item: EquipmentItem) => { - if (!characterData) return; - - const newEquipment = characterData.equipment.filter( - (e: EquipmentItem) => e.name !== item.name - ); - setCharacterData({ ...characterData, equipment: newEquipment }); - }; - - const updateAC = async () => { - if (!uid || !id) { - console.error("User ID or Character ID is undefined"); - return; - } - if (user?.uid !== uid) { - console.log("Not the owner of the character sheet."); - return; - } - if (!characterData || !characterData.wearing) { - console.error("Character data or wearing is undefined"); - return; - } - - const docRef = doc(db, "users", uid, "characters", id); - - const updateData = { - "wearing.armor": characterData.wearing.armor, - "wearing.shield": characterData.wearing.shield, - }; - - try { - await updateDoc(docRef, updateData); - } catch (error) { - console.error("Error updating character AC: ", error); - } - }; - - useEffect(() => { - updateAC(); - }, [characterData?.wearing]); - - const showMissileAC = - characterData && - getArmorClass(characterData, setCharacterData, "missile") !== - getArmorClass(characterData, setCharacterData); - - // GET CHARACTERDATA - useEffect(() => { - const characterDocRef = doc(db, `users/${uid}/characters/${id}`); - - // Listen to real-time updates - const unsubscribe = onSnapshot(characterDocRef, (snapshot) => { - if (snapshot.exists()) { - let characterData = snapshot.data() as CharacterData; - // Make sure legacy characters' class value is converted to an array - if (typeof characterData.class === "string") { - characterData.class = [characterData.class]; - // Make sure the string is not two standard classes with a space between them - if (characterData.class[0].indexOf(" ") > -1) { - const newArr = characterData.class[0].split(" "); - // Make sure every value in the array is in the ClassNames enum - // That way you know if it is a proper combination class and not a custom class with a space. - if (newArr.every((className) => isStandardClass(className))) - characterData.class = newArr; - } - } - setCharacterData(characterData); - document.title = `${characterData.name} | CODEX.QUEST`; - } else { - console.error("Character not found"); - } - }); - - // Return the unsubscribe function to clean up the listener - return () => unsubscribe(); - }, [uid, id]); - - const buttonTextClassNames = classNames("hidden", "md:inline"); - - const equipmentListCategories = { - weapons: [ - "weapons", - "brawling", - EquipmentCategories.AXES, - EquipmentCategories.BOWS, - EquipmentCategories.DAGGERS, - EquipmentCategories.SWORDS, - EquipmentCategories.HAMMERMACE, - EquipmentCategories.IMPROVISED, - EquipmentCategories.CHAINFLAIL, - EquipmentCategories.OTHERWEAPONS, - EquipmentCategories.SLINGHURLED, - EquipmentCategories.SPEARSPOLES, - ], - general: [EquipmentCategories.GENERAL, "items"], - armor: [EquipmentCategories.ARMOR, "armor-and-shields"], - shields: [EquipmentCategories.SHIELDS, "armor-and-shields"], - beasts: [EquipmentCategories.BEASTS, EquipmentCategories.BARDING], - ammo: [EquipmentCategories.AMMUNITION], - }; - - const handleAttackClick = (item: EquipmentItem) => { - if (setWeapon) { - setWeapon(item); - } - if (showAttackModal) { - showAttackModal(); - } - }; - - const equipmentInfoCollapseItems = [ - { - key: "1", - label: "Spells", - children: characterData && ( - - ), - }, - { - key: "2", - label: "Weapons", - children: characterData && ( - - ), - }, - { - key: "3", - label: "Ammunition", - children: characterData && ( - - ), - }, - { - key: "4", - label: "General Equipment", - children: characterData && ( - - ), - }, - { - key: "5", - label: "Armor", - children: characterData && ( - - ), - }, - { - key: "6", - label: "Shields", - children: characterData && ( - - ), - }, - { - key: "7", - label: "Beasts of Burden", - children: characterData && ( - - ), - }, - ]; - - return characterData ? ( -
*+*]:mt-4`}> - - - Home - - ), - }, - { - title: ( -
- - {characterData.name} -
- ), - }, - ]} - /> - {/* NAME / RACE / CLASS / LEVEL / XP */} - - {/* ROLLING BUTTONS */} -
- {/* TODO: THIS SHOULD BE THREE OF THE SAME component with different labels, */} - {/* -
- - -
-
- -
- {/* ABILITY SCORES */} - - - - {/* ATTACK BONUSES */} - - {/* HIT POINTS */} - - - - {/* ARMOR CLASS */} - - {/* MOVEMENT */} - - {/* HIT DICE */} - - - - - {/* Hide these if using a custom Class */} - {getClassType(characterData.class) !== "custom" ? ( -
- {/* SPECIALS / RESTRICTIONS */} - - {/* SPECIAL ABILITIES TABLE */} - {characterData.class.map((cls) => { - if (classes[cls as ClassNames]?.specialAbilities) { - return ( - - ); - } - return null; // Return null if the condition is not met - })} - {/* SAVING THROWS */} - -
- ) : ( - - You are using a custom Class. Use the "Bio & Notes" field below to - calculate your character's Saving Throws, Special Abilities, and - Restrictions. - - )} - -
- {/* MONEY */} - makeChange(characterData)} - className="col-span-1" - /> - {/* WEIGHT */} - - {/* EQUIPMENT */} - -
- - {/* BIO & NOTES */} - - {/* MODALS */} - - - ) : ( - - ); -} diff --git a/src/pages/GameList/GameList.tsx b/src/pages/GameList/GameList.tsx deleted file mode 100644 index 970526fc..00000000 --- a/src/pages/GameList/GameList.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { UsergroupAddOutlined } from "@ant-design/icons"; -import { Button, Col, Empty, Row, Spin, Typography, message } from "antd"; -import { User } from "firebase/auth"; -import { - collection, - deleteDoc, - doc, - onSnapshot, - setDoc, -} from "firebase/firestore"; -import { useEffect, useState } from "react"; -import { useOutletContext } from "react-router-dom"; -import { auth, db } from "../../firebase"; -import { GameData } from "../../data/definitions"; -import classNames from "classnames"; -import NewGameModal from "../../components/NewGameModal/NewGameModal"; -import GameCard from "../../components/GameCard/GameCard"; - -const emptyGame: GameData = { name: "", id: "", players: [] }; - -export default function GameList({ - user, - className, -}: { user: User | null } & React.ComponentPropsWithRef<"div">) { - const outletContext = useOutletContext() as { className: string }; - const [games, setGames] = useState([]); - const [gameData, setGameData] = useState(emptyGame); - const [loading, setLoading] = useState(true); - const [isNewGameModalOpen, setIsNewGameModalOpen] = useState(false); - const [messageApi, contextHolder] = message.useMessage(); - - const success = (name: string) => { - messageApi.open({ - type: "success", - content: `${name} successfully saved!`, - }); - }; - - const errorMessage = (message: string) => { - messageApi.open({ - type: "error", - content: "This is an error message", - }); - }; - - async function addGameData(gameData: GameData) { - // Check if a user is currently logged in - if (auth.currentUser) { - // Get the current user's UID - const uid = auth.currentUser.uid; - - // Get a reference to the Firestore document - const docRef = doc(collection(db, `users/${uid}/games`)); - - // Set the game data for the current user - try { - await setDoc(docRef, gameData); - success(gameData.name); - // Reset gameData - setGameData(emptyGame); - } catch (error) { - console.error("Error writing document: ", error); - errorMessage(`Error writing document (see console)`); - } - } else { - console.error("No user is currently logged in."); - errorMessage(`No user is currently logged in.`); - } - } - - const handleCancel = () => { - setIsNewGameModalOpen(false); - }; - - useEffect(() => { - const fetchGames = async () => { - try { - if (user) { - const uid = user.uid; - const gamesCollectionRef = collection(db, `users/${uid}/games`); - - // Listen to real-time updates - const unsubscribe = onSnapshot(gamesCollectionRef, (snapshot) => { - const userGames = snapshot.docs.map((doc) => { - const data = doc.data() as GameData; - data.id = doc.id; - return data; - }); - setGames(userGames); - document.title = `CODEX.QUEST | Game List`; - setLoading(false); - }); - - // Return the unsubscribe function to clean up the listener - return () => unsubscribe(); - } - } catch (error) { - console.error("Failed to fetch games:", error); - } - }; - - // Call the fetchGames function when the component mounts - fetchGames(); - }, [user]); - - const confirm = async (gameId: string) => { - if (user) { - const gameDoc = doc(db, `users/${user.uid}/games/${gameId}`); - await deleteDoc(gameDoc); - } - }; - - useEffect(() => { - setLoading(false); - }, [games]); - - const gameListClassNames = classNames( - outletContext.className, - className, - "text-shipGray", - "[&>*+*]:mt-4" - ); - return ( -
- {contextHolder} -
- - Games (beta) - - -
- {loading ? ( - - ) : games.length > 0 ? ( - - {games - .sort((a, b) => a.name.localeCompare(b.name)) - .map((gameData) => { - return ( -
- - - ); - })} - - ) : ( - - )} - - - ); -} diff --git a/src/pages/GameSheet/GameSheet.tsx b/src/pages/GameSheet/GameSheet.tsx deleted file mode 100644 index a3c0491e..00000000 --- a/src/pages/GameSheet/GameSheet.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { User } from "firebase/auth"; -import { Link, useParams } from "react-router-dom"; -import PlayerList from "../../components/GameSheet/PlayerList/PlayerList"; -import Clipboard from "../../components/GameSheet/Clipboard/Clipboard"; -import classNames from "classnames"; -import { Breadcrumb, Skeleton, Typography, message } from "antd"; -import { useEffect, useState } from "react"; -import { doc, onSnapshot } from "firebase/firestore"; -import { db } from "../../firebase"; -import { GameData } from "../../data/definitions"; -import { HomeOutlined, SolutionOutlined } from "@ant-design/icons"; - -type GameSheetProps = { user: User | null }; - -export default function GameSheet({ user }: GameSheetProps) { - const [gameData, setGameData] = useState(null); - const [players, setPlayers] = useState([]); - const [messageApi, contextHolder] = message.useMessage(); - - useEffect(() => { - setPlayers(gameData?.players || []); - }, [gameData]); - - const { uid, id } = useParams(); - - const gameSheetClassNames = classNames( - "flex", - "flex-col", - "lg:flex-row", - "gap-4", - "[&>div]:flex-[1_0_0%]", - "[&>div]:p-2" - ); - - // GET GameDATA - useEffect(() => { - const gameDocRef = doc(db, `users/${uid}/games/${id}`); - - // Listen to real-time updates - const unsubscribe = onSnapshot(gameDocRef, (snapshot) => { - if (snapshot.exists()) { - let gameData = snapshot.data() as GameData; - setGameData(gameData); - document.title = `${gameData.name} | CODEX.QUEST`; - } else { - console.error("Game not found"); - } - }); - - // Return the unsubscribe function to clean up the listener - return () => unsubscribe(); - }, [uid, id]); - - return ( -
- {contextHolder} -
- - - Home - - ), - }, - { - title: ( -
- - {!!gameData && gameData.name} -
- ), - }, - ]} - /> - - {gameData ? gameData.name : } - -
-
- {gameData && id && uid ? ( - - ) : ( - - )} - {gameData ? : } -
-
- ); -} diff --git a/src/pages/Welcome/Welcome.tsx b/src/pages/Welcome/Welcome.tsx deleted file mode 100755 index 2669ee8f..00000000 --- a/src/pages/Welcome/Welcome.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Image, Typography } from "antd"; -import { useOutletContext } from "react-router-dom"; -import IronhideSheet from "../../assets/images/ironhide_sheet.png"; -import classNames from "classnames"; - -export default function Welcome() { - const outletContext = useOutletContext() as { className: string }; - const welcomeClassNames = classNames( - outletContext.className, - "grid", - "grid-cols-1", - "md:grid-cols-2", - "gap-8" - ); - return ( -
-
-
- - Welcome to CODEX.QUEST! - - - Welcome to CODEX.QUEST, your ultimate companion for character - creation in the world of{" "} - - Basic Fantasy RPG - - . - - - Designed with the player in mind, this platform streamlines - character creation, providing an intuitive, user-friendly interface - to craft your unique character. From defining your character's - abilities to equipping them with the right gear, CODEX.QUEST ensures - a seamless and immersive gaming experience. - - - CODEX.QUEST is not just a tool; it's a companion designed to guide - you on your characters' adventures. Make the most of your gaming - experience by letting CODEX.QUEST handle the complexities of - character management, giving you more time to dive into the - captivating narratives and thrilling encounters that BFRPG offers. - Best of all, it's 100% FREE! - -
-
- - Bring Your Characters to Life - - -
    -
  • - Create, store, and play as an infinite number of characters. -
  • -
  • - Access all the official races, classes, and equipment as well as - virtually all of the supplemental releases. -
  • -
  • - Go even further and create custom races and classes to bring - your unique character to life. -
  • -
  • - Create and use custom equipment from your character's - adventures. -
  • -
  • - Roll initiative, attack, damage, saving throws, and special - abilities with our easy-to-use interface. Our Virtual Dice Tool - also lets you roll any other dice you need. -
  • -
  • - Manage your equipment, money, HP, bio, XP, conditions/wounds, - spells, and more. -
  • -
  • Share your characters with anyone using unique URLs.
  • -
  • More features being added all the time!
  • -
-
-
-
- -
- ); -} diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts deleted file mode 100755 index 4ea89a5c..00000000 --- a/src/react-app-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -declare module "*.png"; -declare module "*.svg"; -declare module "*.jpeg"; -declare module "*.jpg"; diff --git a/src/rpg-dice-roller.d.ts b/src/rpg-dice-roller.d.ts index ec87a323..0b20fac8 100755 --- a/src/rpg-dice-roller.d.ts +++ b/src/rpg-dice-roller.d.ts @@ -1 +1 @@ -declare module '@dice-roller/rpg-dice-roller'; +declare module "@dice-roller/rpg-dice-roller"; diff --git a/src/support/accountSupport.ts b/src/support/accountSupport.ts new file mode 100644 index 00000000..d9136277 --- /dev/null +++ b/src/support/accountSupport.ts @@ -0,0 +1,291 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + GoogleAuthProvider, + User, + getAuth, + signInWithPopup, +} from "firebase/auth"; +import { + addDoc, + collection, + deleteDoc, + doc, + getDoc, + onSnapshot, + setDoc, + updateDoc, +} from "firebase/firestore"; +import { db } from "../firebase"; +import DOMPurify from "dompurify"; +import { GamePlayer, PlayerListObject } from "../data/definitions"; +import { RcFile } from "antd/es/upload"; + +type DocumentType = "characters" | "games"; + +type UpdatePayload = { + collection: string; + docId: string; + subCollection?: string; + subDocId?: string; + data: any; +}; + +export const handleLogin = async () => { + const auth = getAuth(); + const provider = new GoogleAuthProvider(); + + try { + const result = await signInWithPopup(auth, provider); + const user = result.user; + + // Add this check for user being null + if (!user) { + console.error("User object is null after sign-in"); + return; + } + + // Create a document for the user in the 'users' collection + const userDocRef = doc(db, "users", user.uid); + + await setDoc( + userDocRef, + { + name: user.displayName, + email: user.email, + // add any additional fields here + }, + { merge: true }, + ); // Merge the data if document already exists + } catch (error) { + console.error("Failed to login and set user doc:", error); + } +}; + +export const fetchCollection = async ( + user: User | null, + collectionName: DocumentType, + setContent: (content: any) => void, + setLoading: (loading: boolean) => void, + pageTitle?: string, +) => { + try { + if (user) { + const uid = user.uid; + const contentCollectionRef = collection( + db, + `users/${uid}/${collectionName}`, + ); + + // Listen to real-time updates + const unsubscribe = onSnapshot(contentCollectionRef, (snapshot) => { + const userContent = snapshot.docs.map((doc) => { + const data = doc.data() as any; + data.id = doc.id; + return data; + }); + setContent(userContent.sort((a, b) => a.name.localeCompare(b.name))); + document.title = `CODEX.QUEST${!!pageTitle && ` | ${pageTitle}`}`; + setLoading(false); + }); + + // Return the unsubscribe function to clean up the listener + return () => unsubscribe(); + } + } catch (error) { + console.error(`Failed to fetch ${collectionName}:`, error); + } +}; + +export const fetchDocument = ( + uid: string | undefined, + id: string | undefined, + setContent: (content: any) => void, + collectionName: DocumentType, +): (() => void) => { + const docRef = doc(db, `users/${uid}/${collectionName}/${id}`); + + // Listen to real-time updates + const unsubscribe = onSnapshot(docRef, (snapshot) => { + if (snapshot.exists()) { + const documentData = snapshot.data(); + setContent(documentData); + if (collectionName === "characters") { + document.title = `${documentData.name} | CODEX.QUEST`; + } + } else { + console.error(`${collectionName.slice(0, -1).toUpperCase()} not found`); // Outputs "Character not found" or "Game not found" + } + }); + + return () => unsubscribe(); +}; + +export const updateDocument = async ({ + collection, + docId, + subCollection, + subDocId, + data, +}: UpdatePayload) => { + if (!docId) { + console.error("Document ID is undefined"); + return; + } + + let docRef; + if (subCollection && subDocId) { + docRef = doc(db, collection, docId, subCollection, subDocId); + } else { + docRef = doc(db, collection, docId); + } + + try { + await updateDoc(docRef, data); + } catch (error) { + console.error("Error updating document: ", error); + } +}; + +export const createDocument = async ( + user: User | null, + collectionName: DocumentType, + data: any, + successCallback?: (name: string) => void, + errorCallback?: (error: any) => void, + navigateCallback?: () => void, +) => { + try { + if (user) { + const uid = user.uid; + const colRef = collection(db, `users/${uid}/${collectionName}`); + + await addDoc(colRef, data); + + // Callback for successful addition + if (successCallback) successCallback(data.name); + + // Navigation if needed + if (navigateCallback) navigateCallback(); + } else { + console.error("No user is currently logged in."); + if (errorCallback) errorCallback("No user is currently logged in."); + } + } catch (error) { + console.error("Error writing document: ", error); + if (errorCallback) errorCallback(error); + } +}; + +export const updateGameWithNewPlayer = async ( + gameId: string, + userId: string, + newPlayer: GamePlayer, +) => { + const gameDocRef = doc(db, `users/${userId}/games/${gameId}`); + const gameDoc = await getDoc(gameDocRef); + + if (gameDoc.exists()) { + const gameData = gameDoc.data(); + const updatedPlayers = [...(gameData?.players || []), newPlayer]; + await setDoc(gameDocRef, { ...gameData, players: updatedPlayers }); + } else { + console.error("Game does not exist"); + } +}; + +export const addPlayerToGame = async ( + url: string, + gameId: string, + gmId: string, +) => { + const sanitizedURL = DOMPurify.sanitize(url); + const regex = /\/u\/([a-zA-Z0-9]+)\/c\/([a-zA-Z0-9]+)/; + const match = sanitizedURL.match(regex); + if (!match) { + throw new Error("Invalid URL format."); + } + + const userId = match[1]; + const characterId = match[2]; + const docRef = doc(db, `users/${userId}/characters/${characterId}`); + const docSnap = await getDoc(docRef); + + if (!docSnap.exists()) { + throw new Error("No such character exists."); + } + + const characterData = { user: userId, character: characterId }; + await updateGameWithNewPlayer(gameId, gmId, characterData); + return characterData; +}; + +export async function removePlayerFromGame( + gameId: string, + userId: string, + characterId: string, +) { + const gameDocRef = doc(db, `users/${userId}/games/${gameId}`); + const gameDoc = await getDoc(gameDocRef); + + if (gameDoc.exists()) { + const gameData = gameDoc.data(); + const updatedPlayers = gameData.players.filter( + (player: PlayerListObject) => player.character !== characterId, + ); + await setDoc(gameDocRef, { ...gameData, players: updatedPlayers }); + } else { + console.error("Game does not exist"); + } +} + +export const getBase64 = (file: RcFile): Promise => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result as string); + reader.onerror = (error) => reject(error); + }); + +type DeletePayload = { + collection: string; + docId: string; + uid: string; +}; + +export const deleteDocument = async ({ + collection, + docId, + uid, +}: DeletePayload) => { + if (!docId) { + console.error("Document ID is undefined"); + return; + } + + try { + const docRef = doc(db, `users/${uid}/${collection}/${docId}`); + await deleteDoc(docRef); + console.info(`Document ${docId} deleted successfully.`); + } catch (error) { + console.error("Error deleting document: ", error); + } +}; + +export function debounce( + func: (...args: any[]) => void, + wait: number, +): (...args: any[]) => void { + let timeout: NodeJS.Timeout | null; + + return function executedFunction(...args: any[]) { + const later = () => { + clearTimeout(timeout as NodeJS.Timeout); + func(...args); + }; + + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(later, wait); + }; +} diff --git a/src/support/characterSupport.ts b/src/support/characterSupport.ts new file mode 100644 index 00000000..d92148af --- /dev/null +++ b/src/support/characterSupport.ts @@ -0,0 +1,483 @@ +import { images } from "../assets/images/faces/imageAssets"; +import { classes } from "../data/classes"; +import { + Abilities, + CharData, + ClassNames, + CostCurrency, + EquipmentItem, + RaceNames, + SavingThrowsType, + SetCharData, +} from "../data/definitions"; +import { races } from "../data/races"; +import equipmentItems from "../data/equipmentItems.json"; +import { DiceRoller } from "@dice-roller/rpg-dice-roller"; +import { RaceSetup } from "../data/races/definitions"; +import { SelectProps } from "antd"; +import { AttackTypes, titleCaseToCamelCase } from "./stringSupport"; + +export function extractImageName(url: string) { + const regex = /\/static\/media\/(.*[^-])\..*?\.jpg/; + const match = url.match(regex); + return match ? match[1] : undefined; +} +export const isStandardClass = (className: string) => + Object.values(ClassNames).includes(className as ClassNames); + +export const getAvatar = (avatar: string) => { + let image = ""; + if (avatar.startsWith("/static/media/")) { + const legacyImage = extractImageName(avatar); + if (legacyImage) { + // find the matching source images in `images` + // "/src/assets/images/faces/gnome-boy-1.jpg" matches gnome-boy-1 + image = images.find((image) => image.includes(legacyImage)) || ""; + } + } else { + image = avatar; + } + return image; +}; + +export const classSplit = (characterClass: string | string[]) => { + if (typeof characterClass === "string") { + return characterClass.split(" "); + } + return characterClass; +}; + +export const getClassType = (characterClass: string[]) => { + const classArr = classSplit(characterClass); + + // NONE + if (classArr.length === 0 || classArr.every((className) => className === "")) + return "none"; + // STANDARD + if ( + classArr.length === 1 && + classArr.every((className) => isStandardClass(className)) + ) { + return "standard"; + } + // COMBINATION + if ( + classArr.length === 1 && + classArr[0].split(" ").every((className) => isStandardClass(className)) + ) { + return "combination"; + } + if ( + classArr.length > 1 && + classArr.every((className) => isStandardClass(className)) + ) { + return "combination"; + } + // CUSTOM + return "custom"; +}; + +export const getAttackBonus = (character: CharData) => { + const classArr = classSplit(character.class); + if (getClassType(classArr) === "custom") return 0; + let maxAttackBonus = 0; + + classArr.forEach((classPiece) => { + const classAttackBonus = + classes[classPiece as ClassNames]?.attackBonus[character.level]; + if (classAttackBonus > maxAttackBonus) { + maxAttackBonus = classAttackBonus; + } + }); + + return maxAttackBonus; +}; + +export const isStandardRace = (raceName: string) => + Object.values(RaceNames).includes(raceName as RaceNames); + +const findACValue = ( + itemList: EquipmentItem[], + itemName: string, + acType: "AC" | "missileAC", +) => { + const foundItem = itemList.find((item) => item.name === itemName); + return Number(foundItem?.[acType] || 0); +}; + +export const getArmorClass = ( + characterData: CharData, + setCharacterData: SetCharData, + type: AttackTypes.MISSILE | AttackTypes.MELEE = AttackTypes.MELEE, +) => { + if (!characterData) return; + + const { race, wearing, equipment } = characterData; + const armorClass = races[race as RaceNames]?.altBaseAC || 11; + + if (!wearing) { + setCharacterData({ ...characterData, wearing: { armor: "", shield: "" } }); + return armorClass; + } + + const acType = type === AttackTypes.MELEE ? "AC" : "missileAC"; + // Calculate Armor AC and Shield AC from equipmentItems or character's equipment + const armorAC = + findACValue(equipmentItems as EquipmentItem[], wearing.armor, "AC") || + findACValue(equipment, wearing.armor, "AC"); + const shieldAC = + findACValue(equipmentItems as EquipmentItem[], wearing.shield, acType) || + findACValue(equipment, wearing.shield, acType); + + return Math.max(armorClass + shieldAC, armorAC + shieldAC); +}; + +type ArmorCategory = "lightArmor" | "mediumArmor" | "heavyArmor"; + +export const getMovement = (characterData: CharData) => { + if (!characterData) return; + + const carryingCapacity = getCarryingCapacity( + +characterData.abilities.scores.strength, + characterData.race as RaceNames, + ); + + const armorSpeedMap: Record = { + lightArmor: [40, 30], + mediumArmor: [30, 20], + heavyArmor: [20, 10], + }; + + const armorCategoryMap: Record = { + "No Armor": "lightArmor", + "Magic Leather Armor": "lightArmor", + "": "lightArmor", + "Studded Leather Armor": "mediumArmor", + "Hide Armor": "mediumArmor", + "Leather Armor": "mediumArmor", + "Magic Metal Armor": "mediumArmor", + "Metal Armor": "heavyArmor", + "Chain Mail": "heavyArmor", + "Ring Mail": "heavyArmor", + "Brigandine Armor": "heavyArmor", + "Scale Mail": "heavyArmor", + "Splint Mail": "heavyArmor", + "Banded Mail": "heavyArmor", + "Plate Mail": "heavyArmor", + "Field Plate Mail": "heavyArmor", + "Full Plate Mail": "heavyArmor", + }; + + const currentArmor = characterData?.wearing?.armor || ""; + const currentCategory = armorCategoryMap[currentArmor]; + const [lightSpeed, heavySpeed] = + armorSpeedMap[currentCategory || "lightArmor"]; + + return characterData.weight <= carryingCapacity.light + ? lightSpeed + : heavySpeed; +}; + +// Helper function to get the strength range +const getStrengthRange = (strength: number): string => { + if (strength === 3) return "3"; + if (strength >= 4 && strength <= 5) return "4-5"; + if (strength >= 6 && strength <= 8) return "6-8"; + if (strength >= 9 && strength <= 12) return "9-12"; + if (strength >= 13 && strength <= 15) return "13-15"; + if (strength >= 16 && strength <= 17) return "16-17"; + if (strength === 18) return "18"; + return ""; // Handle out-of-range strength values +}; + +type Capacity = { light: number; heavy: number }; +type CapacityMap = Record; + +export const getCarryingCapacity = ( + strength: number, + race: RaceNames, +): Capacity => { + const capacities: CapacityMap = { + "3": { light: 25, heavy: 60 }, + "4-5": { light: 35, heavy: 90 }, + "6-8": { light: 50, heavy: 120 }, + "9-12": { light: 60, heavy: 150 }, + "13-15": { light: 65, heavy: 165 }, + "16-17": { light: 70, heavy: 180 }, + "18": { light: 80, heavy: 195 }, + }; + + const lowCapacities: CapacityMap = { + "3": { light: 20, heavy: 40 }, + "4-5": { light: 30, heavy: 60 }, + "6-8": { light: 40, heavy: 80 }, + "9-12": { light: 50, heavy: 100 }, + "13-15": { light: 55, heavy: 110 }, + "16-17": { light: 60, heavy: 120 }, + "18": { light: 65, heavy: 130 }, + }; + + const range = getStrengthRange(strength); + + return races[race]?.hasLowCapacity ? lowCapacities[range] : capacities[range]; +}; + +export const getHitPointsModifier = (classArr: string[]) => { + let modifier = 0; + for (const className of classArr) { + const classHitDiceModifier = + classes[className as ClassNames]?.hitDiceModifier; + if (classHitDiceModifier > modifier) { + modifier = classHitDiceModifier; + } + } + return modifier; +}; + +export const getHitDice = ( + level: number, + className: string[], + dice: string, +) => { + const dieType = dice.split("d")[1].split("+")[0]; + const prefix = Math.min(level, 9); + + // Calculate the suffix + const suffix = (level > 9 ? level - 9 : 0) * getHitPointsModifier(className); + + // Combine to create the result + const result = `${prefix}d${dieType}${suffix > 0 ? "+" + suffix : ""}`; + return result; +}; + +export const getSavingThrows = (className: string, level: number) => + classes[className as ClassNames]?.savingThrows.find( + (savingThrow) => (savingThrow[0] as number) >= level, + )?.[1] as SavingThrowsType; + +export const getSavingThrowsWeight = (savingThrows: SavingThrowsType) => + Object.values(savingThrows).reduce((prev, curr) => prev + curr, 0); + +export const getBestSavingThrowList = (charClass: string[], level: number) => { + const defaultSavingThrows: SavingThrowsType = { + deathRayOrPoison: 0, + magicWands: 0, + paralysisOrPetrify: 0, + dragonBreath: 0, + spells: 0, + }; + const classType = getClassType(charClass); + // if classType is standard, find saving throws for that class + if (classType === "standard") { + return getSavingThrows(charClass.join(), level) || defaultSavingThrows; + // if classType is combination, find saving throws for each class and use the best + } else { + const [firstClass, secondClass] = charClass; + const firstClassSavingThrows = getSavingThrows(firstClass, level); + const secondClassSavingThrows = getSavingThrows(secondClass, level); + return getSavingThrowsWeight(firstClassSavingThrows) <= + getSavingThrowsWeight(secondClassSavingThrows) + ? firstClassSavingThrows + : secondClassSavingThrows; + } +}; + +export const isAbilityKey = ( + key: string, + characterData: CharData, +): key is keyof typeof characterData.abilities.scores => { + return ( + characterData && + characterData.abilities && + key in characterData.abilities.scores + ); +}; + +const roller = new DiceRoller(); +export const rollDice = (dice: string) => roller.roll(dice).total; + +export const getModifier = (score: number): string => { + const modifierMapping: Record = { + 3: "-3", + 5: "-2", + 8: "-1", + 12: "+0", + 15: "+1", + 17: "+2", + 18: "+3", + }; + + for (const key in modifierMapping) { + if (score <= Number(key)) { + return modifierMapping[Number(key)]; + } + } + return "+0"; // Default value +}; + +export const raceIsDisabled = (choice: RaceSetup, character: CharData) => + (choice.minimumAbilityRequirements && + Object.entries(choice.minimumAbilityRequirements).some( + ([ability, requirement]) => + +character.abilities?.scores[ + ability as keyof typeof character.abilities.scores + ] < (requirement as number), // Cast requirement to number + )) || + (choice.maximumAbilityRequirements && + Object.entries(choice.maximumAbilityRequirements).some( + ([ability, requirement]) => + +character.abilities?.scores[ + ability as keyof typeof character.abilities.scores + ] > (requirement as number), // Cast requirement to number + )); + +export const getRaceSelectOptions = ( + character: CharData, + useBase: boolean = true, +) => { + return Object.keys(races) + .filter((race) => !raceIsDisabled(races[race as RaceNames], character)) + .filter((race) => + useBase + ? [ + RaceNames.DWARF, + RaceNames.ELF, + RaceNames.HALFLING, + RaceNames.HUMAN, + ].includes(race as RaceNames) + : race, + ) + .sort((a, b) => + races[a as keyof typeof races].name > races[b as keyof typeof races].name + ? 1 + : -1, + ) + .map((race) => ({ + value: race, + label: race, + })); +}; + +export const getClassSelectOptions = ( + character: CharData, + useBase: boolean = true, +) => { + // Extract required properties from character + const raceKey = character.race; + const abilityScores = character.abilities?.scores; + + // Get the list of enabled classes + const baseClasses = [ + ClassNames.CLERIC, + ClassNames.FIGHTER, + ClassNames.MAGICUSER, + ClassNames.THIEF, + ]; + const enabledClasses = getEnabledClasses( + raceKey as RaceNames, + abilityScores, + ).filter((className) => + useBase ? baseClasses.includes(className) : className, + ); + + return Object.keys(classes) + .filter((className) => enabledClasses.includes(className as ClassNames)) + .sort((a, b) => + classes[a as keyof typeof classes].name > + classes[b as keyof typeof classes].name + ? 1 + : -1, + ) + .map((className) => ({ value: className, label: className })); +}; + +export const getEnabledClasses = ( + raceKey: RaceNames, + abilityScores: Abilities, +) => { + const race = isStandardRace(raceKey) ? races[raceKey] : undefined; + let classList = Object.values(ClassNames); + if (!race) return classList; + classList = classList + .filter((className) => race.allowedStandardClasses.indexOf(className) > -1) + .filter((className) => { + const classSetup = classes[className]; + if (classSetup.minimumAbilityRequirements) { + for (const ability of Object.keys( + classSetup.minimumAbilityRequirements, + ) as (keyof Abilities)[]) { + const requirement = classSetup.minimumAbilityRequirements[ability]; + if (requirement && +abilityScores[ability] < requirement) { + return false; + } + } + } + return true; + }); + return classList; +}; + +export const getItemCost = (item: EquipmentItem) => { + let cost = item.costValue; + if (item.costCurrency === "sp") cost *= 0.1; + if (item.costCurrency === "cp") cost *= 0.01; + return cost * item.amount; +}; + +export const openInNewTab = (url: string) => { + const newWindow = window.open(url, "_blank", "noopener,noreferrer"); + if (newWindow) newWindow.opener = null; +}; + +export const CURRENCIES: CostCurrency[] = ["gp", "sp", "cp"]; +export const equipmentSizes: SelectProps["options"] = [ + { + label: "S", + value: "S", + }, + { + label: "M", + value: "M", + }, + { + label: "L", + value: "L", + }, +]; + +export const rollSavingThrow = ( + score: number, + title: string, + race: string, + openNotification: (result: string, savingThrowTitle: string) => void, +) => { + const raceModifier = + races[race as RaceNames]?.savingThrows?.[ + titleCaseToCamelCase(title) as keyof SavingThrowsType + ] || 0; + const result = roller.roll( + `d20${raceModifier > 0 ? `+${raceModifier}` : ""}`, + ); + const passFail = result.total >= score ? "Pass" : "Fail"; + openNotification(result.output + " - " + passFail, title); +}; + +export const rollSpecialAbility = ( + score: number, + title: string, + openNotification: (result: string, specialAbilityTable: string) => void, +) => { + const result = roller.roll(`d%`); + const passFail = result.total <= score ? "Pass" : "Fail"; + openNotification(result.output + " - " + passFail, title); +}; + +export const getCharacterWeight = (character: CharData) => { + const equipmentWeight = character.equipment.reduce( + (accumulator: number, currentValue: EquipmentItem) => + accumulator + (currentValue.weight ?? 0) * (currentValue.amount ?? 0), + 0, + ); + const coinsWeight = character.gold * 0.05; + return equipmentWeight + coinsWeight; +}; diff --git a/src/support/classNameSupport.ts b/src/support/classNameSupport.ts new file mode 100644 index 00000000..b7d44858 --- /dev/null +++ b/src/support/classNameSupport.ts @@ -0,0 +1,5 @@ +export const avatarClassNames = + "shadow-md border-solid border-2 border-seaBuckthorn"; + +export const newGameCharacterPageTitleClassNames = + "m-0 font-enchant leading-none tracking-wide"; diff --git a/src/support/colorSupport.ts b/src/support/colorSupport.ts new file mode 100644 index 00000000..cf1337a1 --- /dev/null +++ b/src/support/colorSupport.ts @@ -0,0 +1,73 @@ +export enum ColorScheme { + SEABUCKTHORN = "#F9B32A", + SUSHI = "#80B045", + CALIFORNIA = "#FDA00D", + POMEGRANATE = "#F44336", + SHIPGRAY = "#3E3643", + SPRINGWOOD = "#F4F5EB", + STONE = "#9F9B8F", + RUST = "#965247", +} + +export const hexToRgba = (hex: string, alpha = 1) => { + // Ensure the hex value starts with a hash symbol + const sanitizedHex = hex.startsWith("#") ? hex : "#" + hex; + + // Extract red, green, and blue values + const [r, g, b] = + sanitizedHex.length === 7 + ? [ + parseInt(sanitizedHex.slice(1, 3), 16), + parseInt(sanitizedHex.slice(3, 5), 16), + parseInt(sanitizedHex.slice(5, 7), 16), + ] + : [ + parseInt(sanitizedHex.slice(1, 2).repeat(2), 16), + parseInt(sanitizedHex.slice(2, 3).repeat(2), 16), + parseInt(sanitizedHex.slice(3, 4).repeat(2), 16), + ]; + + // Return the RGBA string + return `rgba(${r}, ${g}, ${b}, ${alpha})`; +}; + +export const darkenHexColor = (hex: string, percent: number): string => { + // Convert hex to RGB first + let r = parseInt(hex.substring(1, 3), 16); + let g = parseInt(hex.substring(3, 5), 16); + let b = parseInt(hex.substring(5, 7), 16); + + // Decrease each RGB component by the percentage provided + r = Math.floor(r * (1 - percent / 100)); + g = Math.floor(g * (1 - percent / 100)); + b = Math.floor(b * (1 - percent / 100)); + + // Convert RGB back to hex + return ( + "#" + + ((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1).toUpperCase() + ); +}; + +export const lightenHexColor = (hex: string, percent: number): string => { + // Convert hex to RGB first + let r = parseInt(hex.substring(1, 3), 16); + let g = parseInt(hex.substring(3, 5), 16); + let b = parseInt(hex.substring(5, 7), 16); + + // Increase each RGB component by the percentage provided + r = Math.floor(r + ((255 - r) * percent) / 100); + g = Math.floor(g + ((255 - g) * percent) / 100); + b = Math.floor(b + ((255 - b) * percent) / 100); + + // Ensure values don't exceed 255 + r = r > 255 ? 255 : r; + g = g > 255 ? 255 : g; + b = b > 255 ? 255 : b; + + // Convert RGB back to hex + return ( + "#" + + ((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1).toUpperCase() + ); +}; diff --git a/src/support/equipmentSupport.ts b/src/support/equipmentSupport.ts new file mode 100644 index 00000000..ca0f5506 --- /dev/null +++ b/src/support/equipmentSupport.ts @@ -0,0 +1,66 @@ +import equipmentItems from "../data/equipmentItems.json"; +import { EquipmentCategories, EquipmentItem } from "../data/definitions"; +import { AttackTypes } from "./stringSupport"; + +// Get an object with properties for every equipment category. Those properties are arrays of equipment items. +// If no equipment list is provided, the default equipment list is used. +export const equipmentCategoryMap = ( + equipmentList: EquipmentItem[] = equipmentItems as EquipmentItem[], +) => { + const equipmentCategories: Record = {}; + + equipmentList.forEach((item: EquipmentItem) => { + if (equipmentCategories[item.category]) { + equipmentCategories[item.category].push(item); + } else { + equipmentCategories[item.category] = [item]; + } + }); + + return equipmentCategories; +}; + +export const equipmentSubCategoryMap = () => { + const equipmentSubCategories: Record = {}; + + (equipmentItems as EquipmentItem[]) + .filter((item) => item.category === EquipmentCategories.GENERAL) + .forEach((item: EquipmentItem) => { + if (item.subCategory) { + if (equipmentSubCategories[item.subCategory]) { + equipmentSubCategories[item.subCategory].push(item); + } else { + equipmentSubCategories[item.subCategory] = [item]; + } + } + }); + + return equipmentSubCategories; +}; + +export const getItemsByCategory = (category: string) => + equipmentCategoryMap()[category]; + +export const punchItem: EquipmentItem = { + name: "Punch**", + costValue: 0, + costCurrency: "gp", + category: "inherent", + damage: "1d3", + amount: 1, + type: AttackTypes.MELEE, + noDelete: true, +}; + +export const kickItem: EquipmentItem = { + name: "Kick**", + costValue: 0, + costCurrency: "gp", + category: "inherent", + damage: "1d4", + amount: 1, + type: AttackTypes.MELEE, + noDelete: true, +}; + +export const equipmentNames = equipmentItems.map((item) => item.name); diff --git a/src/support/formatSupport.test.ts b/src/support/formatSupport.test.ts deleted file mode 100644 index b85861e2..00000000 --- a/src/support/formatSupport.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { RaceNames } from "../data/definitions"; -import { getItemCost, getCarryingCapacity } from "./formatSupport"; - -describe("getItemCost", () => { - let item = { - costValue: 10, - costCurrency: "gp", - amount: 2, - name: "foo", - category: "bar", - }; - - test("should calculate cost for items in gold", () => { - expect(getItemCost(item)).toBe(20); - }); - - test("should calculate cost for items in silver", () => { - item.costCurrency = "sp"; - expect(getItemCost(item)).toBe(2); - }); - - test("should calculate cost for items in copper", () => { - item.costCurrency = "cp"; - expect(getItemCost(item)).toBe(0.2); - }); - - test("should handle zero amount", () => { - item.amount = 0; - expect(getItemCost(item)).toBe(0); - }); -}); - -describe("getCarryingCapacity", () => { - test("should return a capacity object", () => { - expect(getCarryingCapacity(3, RaceNames.ELF)).toEqual({ - heavy: 60, - light: 25, - }); - }); - - test("should return a capacity object", () => { - expect(getCarryingCapacity(5, RaceNames.ELF)).toEqual({ - heavy: 90, - light: 35, - }); - }); - - test("should return a capacity object", () => { - expect(getCarryingCapacity(8, RaceNames.ELF)).toEqual({ - heavy: 120, - light: 50, - }); - }); - - test("should return a capacity object", () => { - expect(getCarryingCapacity(12, RaceNames.ELF)).toEqual({ - heavy: 150, - light: 60, - }); - }); - - test("should return a capacity object", () => { - expect(getCarryingCapacity(15, RaceNames.ELF)).toEqual({ - heavy: 165, - light: 65, - }); - }); - - test("should return a capacity object", () => { - expect(getCarryingCapacity(17, RaceNames.ELF)).toEqual({ - heavy: 180, - light: 70, - }); - }); - - test("should return a capacity object", () => { - expect(getCarryingCapacity(18, RaceNames.ELF)).toEqual({ - heavy: 195, - light: 80, - }); - }); - - test("should return a smaller capacity object for a race with `hasLowCapacities`", () => { - expect(getCarryingCapacity(5, RaceNames.GNOME)).toEqual({ - heavy: 60, - light: 30, - }); - }); -}); diff --git a/src/support/formatSupport.ts b/src/support/formatSupport.ts deleted file mode 100755 index 436e6d6b..00000000 --- a/src/support/formatSupport.ts +++ /dev/null @@ -1,78 +0,0 @@ -// import { Capacity, CapacityMap } from "../components/definitions"; -import { CharacterData, EquipmentItem, RaceNames } from "../data/definitions"; -import { races } from "../data/races"; - -type Capacity = { light: number; heavy: number }; -type CapacityMap = Record; - -export const getItemCost = (item: EquipmentItem) => { - let cost = item.costValue; - if (item.costCurrency === "sp") cost *= 0.1; - if (item.costCurrency === "cp") cost *= 0.01; - return cost * item.amount; -}; - -export const getCarryingCapacity = ( - strength: number, - race: RaceNames -): Capacity => { - const capacities: CapacityMap = { - "3": { light: 25, heavy: 60 }, - "4-5": { light: 35, heavy: 90 }, - "6-8": { light: 50, heavy: 120 }, - "9-12": { light: 60, heavy: 150 }, - "13-15": { light: 65, heavy: 165 }, - "16-17": { light: 70, heavy: 180 }, - "18": { light: 80, heavy: 195 }, - }; - - const lowCapacities: CapacityMap = { - "3": { light: 20, heavy: 40 }, - "4-5": { light: 30, heavy: 60 }, - "6-8": { light: 40, heavy: 80 }, - "9-12": { light: 50, heavy: 100 }, - "13-15": { light: 55, heavy: 110 }, - "16-17": { light: 60, heavy: 120 }, - "18": { light: 65, heavy: 130 }, - }; - - let range = ""; - - if (strength === 3) { - range = "3"; - } else if (strength >= 4 && strength <= 5) { - range = "4-5"; - } else if (strength >= 6 && strength <= 8) { - range = "6-8"; - } else if (strength >= 9 && strength <= 12) { - range = "9-12"; - } else if (strength >= 13 && strength <= 15) { - range = "13-15"; - } else if (strength >= 16 && strength <= 17) { - range = "16-17"; - } else if (strength === 18) { - range = "18"; - } - - return races[race]?.hasLowCapacity ? lowCapacities[range] : capacities[range]; -}; - -export function makeChange(characterData: CharacterData) { - if (characterData) { - let copper = characterData.gold * 100; - let goldPieces = Math.floor(copper / 100); - copper %= 100; - let silverPieces = Math.floor(copper / 10); - copper %= 10; - let copperPieces = copper; - - return { - gp: Math.round(goldPieces), - sp: Math.round(silverPieces), - cp: Math.round(copperPieces), - }; - } else { - // default object when characterData is null/undefined - return { gp: 0, sp: 0, cp: 0 }; - } -} diff --git a/src/support/helpers.test.ts b/src/support/helpers.test.ts deleted file mode 100644 index e3a7798c..00000000 --- a/src/support/helpers.test.ts +++ /dev/null @@ -1,322 +0,0 @@ -import { CharacterData, ClassNames, RaceNames } from "../data/definitions"; -import { - equipmentItemIsDisabled, - getArmorClass, - getClassType, - getEnabledClasses, - getHitDice, - getHitPointsModifier, - getSavingThrows, - getSavingThrowsWeight, - getSpecialAbilityRaceOverrides, - isStandardRace, - // useDebounce, -} from "./helpers"; -// import React, { useState, useEffect } from "react"; -// import { renderHook } from "@testing-library/react"; -import equipmentItems from "../data/equipmentItems.json"; - -let characterData: CharacterData = { - savingThrows: { - dragonBreath: 0, - paralysisOrPetrify: 0, - magicWands: 0, - spells: 0, - deathRayOrPoison: 0, - }, - desc: [""], - xp: 0, - // Commented out for testing purposes - // wearing: { - // armor: "", - // shield: "", - // }, - name: "asd", - equipment: [ - { - AC: 15, - costValue: 80, - category: "armor", - name: "Brigandine Armor", - weight: 30, - amount: 1, - costCurrency: "gp", - }, - ], - weight: 30, - hp: { - points: 8, - dice: "d8", - desc: "", - max: 8, - }, - race: "Foo", - restrictions: { - class: [], - race: [], - }, - gold: 0, - spells: [ - { - level: { - paladin: 2, - "magic-user": null, - cleric: 2, - necromancer: null, - illusionist: null, - druid: 2, - }, - description: - "This spell allows the caster to charm one or more animals, in much the same fashion as charm person, at a rate of 1 hit die per caster level. The caster may decide which individual animals out of a mixed group are to be affected first; excess hit dice of effect are ignored. No saving throw is allowed, either for normal or giant-sized animals, but creatures of more fantastic nature (as determined by the GM) are allowed a save vs. Spells to resist. When the duration expires, the animals will resume normal activity immediately. \n\nThis spell does not grant the caster any special means of communication with the affected animals; if combined with speak with animals, this spell becomes significantly more useful.", - duration: "level+1d4 rounds", - range: "60'", - name: "Charm Animal", - }, - ], - avatar: "", - level: 1, - specials: { - class: [], - race: [], - }, - class: ["Bar"], - abilities: { - modifiers: { - charisma: "+0", - constitution: "+0", - wisdom: "+0", - strength: "+1", - intelligence: "+1", - dexterity: "+0", - }, - scores: { - intelligence: 13, - strength: 13, - charisma: 12, - constitution: 10, - dexterity: 10, - wisdom: 11, - }, - }, -}; - -describe("getClassType", () => { - test('should return "none" if characterClass is an empty array', () => { - expect(getClassType([])).toBe("none"); - }); - - test('should return "none" if characterClass is an array with an empty string', () => { - expect(getClassType([""])).toBe("none"); - }); - - test('should return "combination" if characterClass is an array with more than one element of documented classes', () => { - expect(getClassType(["Fighter", "Magic-User"])).toBe("combination"); - }); - - test('should return "custom" if characterClass is an array with at least one element of undocumented classes', () => { - expect(getClassType(["Fighter", "Poodle"])).toBe("custom"); - }); - - test('should return "combination" if characterClass is a string with a space, and each piece is a documented class', () => { - expect(getClassType(["Fighter Magic-User"])).toBe("combination"); - }); - - test('should return "custom" if characterClass is a string with a space, and any piece is not a documented class', () => { - expect(getClassType(["Fighter Garrett"])).toBe("custom"); - }); - - test('should return "standard" if characterClass is a string that is a documented class', () => { - expect(getClassType(["Fighter"])).toBe("standard"); - }); - - test('should return "custom" if characterClass is a string that is not a documented class', () => { - expect(getClassType(["foo"])).toBe("custom"); - }); -}); - -describe("isStandardRace", () => { - test('should return "true" if a characterRace is a string that is a documented class', () => { - expect(isStandardRace("Human")).toBe(true); - }); - - test('should return "false" if a characterRace is a string that is not a documented class', () => { - expect(isStandardRace("Banana")).toBe(false); - }); -}); - -describe("getEnabledClasses", () => { - // This test will break any time a new class is added, so... maybe not the best test. - test("should return a list of enabled classNames based on character race selection and ability scores", () => { - expect( - getEnabledClasses(RaceNames.DWARF, { - strength: 9, - intelligence: 9, - wisdom: 6, - constitution: 11, - dexterity: 11, - charisma: 8, - }) - ).toEqual([ - ClassNames.BARBARIAN, - ClassNames.FIGHTER, - ClassNames.THIEF, - ClassNames.CUSTOM, - ]); - }); -}); - -describe("getSavingThrows", () => { - test("should return a list of saving throws based on character class and level", () => { - expect(getSavingThrows(ClassNames.FIGHTER, 1)).toEqual({ - deathRayOrPoison: 12, - dragonBreath: 15, - magicWands: 13, - paralysisOrPetrify: 14, - spells: 17, - }); - }); -}); - -describe("getSavingThrowsWeight", () => { - test("should return a number, or weight, of all the saving throw values for a class at a certain level", () => { - expect(getSavingThrowsWeight(getSavingThrows(ClassNames.FIGHTER, 1))).toBe( - 71 - ); - }); -}); - -// jest.mock("react", () => ({ -// useState: jest.fn(), -// useEffect: jest.fn(), -// })); - -// it("should debounce the value", async () => { -// const { result } = renderHook(() => { -// const debouncedValue = useDebounce("initial value", 100); - -// return debouncedValue; -// }); - -// // Wait for the delay to pass. -// await new Promise((resolve) => setTimeout(resolve, 100)); - -// // Assert that the debouncedValue state is set correctly. -// expect(result.current).toBe("initial value"); -// }); - -describe("getHitPointsModifier", () => { - test("should return an appropriate modifier given an array of one standard class name", () => { - expect(getHitPointsModifier([ClassNames.FIGHTER])).toBe(2); - }); - - test("should return an appropriate modifier given an array of multiple standard class name", () => { - expect( - getHitPointsModifier([ClassNames.FIGHTER, ClassNames.MAGICUSER]) - ).toBe(2); - }); - - test("should return a '0' modifier given an array of non-standard class name", () => { - expect(getHitPointsModifier(["foo"])).toBe(0); - }); - - test("should return a '0' modifier given an empty array", () => { - expect(getHitPointsModifier([])).toBe(0); - }); -}); - -describe("getSpecialAbilityRaceOverrides", () => { - test("should provide the overrides for a race that has them", () => { - expect(getSpecialAbilityRaceOverrides(RaceNames.BISREN)).toEqual([ - [ - "Thief", - { - "Move Silently": "ADD 20% to this roll if IN DOORS/URBAN SETTING", - "Open Locks": "ADD 10% to this roll", - "Pick Pockets": "ADD 10% to this roll", - "Remove Traps": - "ADD 10% to this roll IF INDOORS, DEDUCT 20% IF OUTDOORS", - }, - ], - ]); - }); -}); - -describe("getArmorClass", () => { - test("a normal starting AC", () => { - expect(getArmorClass(characterData, () => console.log("ignore"))).toBe(11); - }); - - test("a race with a non-normal starting AC", () => { - characterData.race = RaceNames.CHELONIAN; - expect(getArmorClass(characterData, () => console.log("ignore"))).toBe(13); - }); - - test("a character with a properly formatted `wearing` property", () => { - characterData.wearing = { - armor: "", - shield: "", - }; - expect(getArmorClass(characterData, () => console.log("ignore"))).toBe(13); - }); - - test("a 'melee' type", () => { - expect( - getArmorClass(characterData, () => console.log("ignore"), "melee") - ).toBe(13); - }); - - test("a 'missile' type", () => { - expect( - getArmorClass(characterData, () => console.log("ignore"), "missile") - ).toBe(13); - }); -}); - -describe("equipmentItemIsDisabled", () => { - const longbow = equipmentItems.filter((item) => item.name === "Longbow")[0]; - test("custom class has nothing disabled", () => { - expect(equipmentItemIsDisabled(["foo"], RaceNames.HUMAN, longbow)).toBe( - false - ); - }); - - test("races with `noLargeEquipment`", () => { - expect( - equipmentItemIsDisabled([ClassNames.CLERIC], RaceNames.GNOME, longbow) - ).toBe(true); - }); - - test("classes with `noLargeEquipment`", () => { - expect( - equipmentItemIsDisabled([ClassNames.SCOUT], RaceNames.HUMAN, longbow) - ).toBe(true); - }); - - test("classes with specific items restrictions (`specificEquipmentItems`)", () => { - expect( - equipmentItemIsDisabled([ClassNames.CLERIC], RaceNames.HUMAN, longbow) - ).toBe(true); - }); -}); - -describe("getHitDice", () => { - test("hit dice for level one standard class", () => { - expect(getHitDice(1, [ClassNames.FIGHTER], "d8")).toBe("1d8"); - }); - - test("high level hit dice for class with x2 multiplier suffix", () => { - expect(getHitDice(11, [ClassNames.FIGHTER], "d8")).toBe("9d8+4"); - }); - - test("high level hit dice for class with x1 multiplier suffix", () => { - expect(getHitDice(11, [ClassNames.MAGICUSER], "d4")).toBe("9d4+2"); - }); - - test("get hit dice for custom class", () => { - expect(getHitDice(11, ["foo"], "d8")).toBe("9d8"); - }); -}); - -describe("getAttackBonus", () => {}); - -describe("getMovement", () => {}); diff --git a/src/support/helpers.ts b/src/support/helpers.ts deleted file mode 100755 index 4177991b..00000000 --- a/src/support/helpers.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { useEffect, useState } from "react"; -import { classes } from "../data/classes"; -import { - Abilities, - CharacterData, - ClassNames, - EquipmentItem, - RaceNames, - SavingThrowsType, - SetCharacterData, -} from "../data/definitions"; -import { races } from "../data/races"; -import equipmentItems from "../data/equipmentItems.json"; -import { getCarryingCapacity } from "./formatSupport"; - -export const getClassType = (characterClass: string[]) => { - // NONE - if ( - characterClass.length === 0 || - characterClass.every((className) => className === "") - ) - return "none"; - - // STANDARD - if ( - characterClass.length === 1 && - characterClass.every((className) => isStandardClass(className)) - ) { - return "standard"; - } - - // COMBINATION - if ( - characterClass.length === 1 && - characterClass[0] - .split(" ") - .every((className) => isStandardClass(className)) - ) { - return "combination"; - } - - if ( - characterClass.length > 1 && - characterClass.every((className) => isStandardClass(className)) - ) { - return "combination"; - } - - // CUSTOM - return "custom"; -}; - -export const isStandardClass = (className: string) => - Object.values(ClassNames).includes(className as ClassNames); - -export const isStandardRace = (raceName: string) => - Object.values(RaceNames).includes(raceName as RaceNames); - -export function getEnabledClasses( - raceKey: RaceNames, - abilityScores: Abilities -) { - const race = isStandardRace(raceKey) ? races[raceKey] : undefined; - let classList = Object.values(ClassNames); - if (!race) return classList; - classList = classList - .filter((className) => race.allowedStandardClasses.indexOf(className) > -1) - .filter((className) => { - const classSetup = classes[className]; - if (classSetup.minimumAbilityRequirements) { - for (const ability of Object.keys( - classSetup.minimumAbilityRequirements - ) as (keyof Abilities)[]) { - const requirement = classSetup.minimumAbilityRequirements[ability]; - if (requirement && +abilityScores[ability] < requirement) { - return false; - } - } - } - return true; - }); - return classList; -} - -// Get the saving throws for a class at a given level -export const getSavingThrows = (className: string, level: number) => - classes[className as ClassNames]?.savingThrows.find( - (savingThrow) => (savingThrow[0] as number) >= level - )?.[1] as SavingThrowsType; - -// Get the total weight of a saving throw object in order to determine "best" -export const getSavingThrowsWeight = (savingThrows: SavingThrowsType) => - Object.values(savingThrows).reduce((prev, curr) => prev + curr, 0); - -export function useDebounce(value: any, delay: number) { - const [debouncedValue, setDebouncedValue] = useState(value); - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); - - return () => { - clearTimeout(handler); - }; - }, [value, delay]); - - return debouncedValue; -} - -export const getHitPointsModifier = (classArr: string[]) => { - let modifier = 0; - for (const className of classArr) { - const classHitDiceModifier = - classes[className as ClassNames]?.hitDiceModifier; - if (classHitDiceModifier > modifier) { - modifier = classHitDiceModifier; - } - } - return modifier; -}; - -export const getSpecialAbilityRaceOverrides = (raceName: RaceNames) => - races[raceName]?.specialAbilitiesOverride ?? []; - -export const getArmorClass = ( - characterData: CharacterData, - setCharacterData: SetCharacterData, - type: "missile" | "melee" = "melee" -) => { - if (!characterData) return; - - let armorClass = races[characterData.race as RaceNames]?.altBaseAC || 11; - let armorAC = 0; - let shieldAC = 0; - - if (!characterData.wearing) { - setCharacterData({ - ...characterData, - wearing: { armor: "", shield: "" }, - }); - } else { - armorAC = Number( - equipmentItems.filter( - (item) => item.name === characterData.wearing?.armor - )[0]?.AC || - characterData.equipment.filter( - (item) => item.name === characterData.wearing?.armor - )[0]?.AC || - 0 - ); - if (type === "melee") { - shieldAC = Number( - equipmentItems.filter( - (item) => item.name === characterData.wearing?.shield - )[0]?.AC || - characterData.equipment.filter( - (item) => item.name === characterData.wearing?.shield - )[0]?.AC || - 0 - ); - } else { - shieldAC = Number( - equipmentItems.filter( - (item) => item.name === characterData.wearing?.shield - )[0]?.missileAC || - characterData.equipment.filter( - (item) => item.name === characterData.wearing?.shield - )[0]?.missileAC || - 0 - ); - } - armorClass = - armorAC + shieldAC > armorClass + shieldAC - ? armorAC + shieldAC - : armorClass + shieldAC; - } - - return armorClass; -}; - -export const getHitDice = ( - level: number, - className: string[], - dice: string -) => { - const dieType = dice.split("d")[1].split("+")[0]; - const prefix = Math.min(level, 9); - - // Calculate the suffix - let suffix = (level > 9 ? level - 9 : 0) * getHitPointsModifier(className); - - // Combine to create the result - const result = `${prefix}d${dieType}${suffix > 0 ? "+" + suffix : ""}`; - return result; -}; - -export const getAttackBonus = function (characterData: CharacterData) { - if (getClassType(characterData.class) === "custom") return 0; - let maxAttackBonus = 0; - - characterData.class.forEach((classPiece) => { - const classAttackBonus = - classes[classPiece as ClassNames]?.attackBonus[characterData.level]; - if (classAttackBonus > maxAttackBonus) { - maxAttackBonus = classAttackBonus; - } - }); - - return maxAttackBonus; -}; - -export const getMovement = (characterData: CharacterData) => { - if (!characterData) return; - - const carryingCapacity = getCarryingCapacity( - +characterData.abilities.scores.strength, - characterData.race as RaceNames - ); - - const isWearing = (armorNames: string[]) => { - return armorNames.includes(characterData?.wearing?.armor || ""); - }; - - // This checks if there is armor being worn or not and adjusts movement. - // TODO: Better way to do this? - if (isWearing(["No Armor", "Magic Leather Armor", ""])) { - return characterData.weight <= carryingCapacity.light ? 40 : 30; - } else if ( - isWearing([ - "Studded Leather Armor", - "Hide Armor", - "Leather Armor", - "Magic Metal Armor", - "Hide Armor", - ]) - ) { - return characterData.weight <= carryingCapacity.light ? 30 : 20; - } else if ( - isWearing([ - "Metal Armor", - "Chain Mail", - "Ring Mail", - "Brigandine Armor", - "Scale Mail", - "Splint Mail", - "Banded Mail", - "Plate Mail", - "Field Plate Mail", - "Full Plate Mail", - ]) - ) { - return characterData.weight <= carryingCapacity.light ? 20 : 10; - } -}; - -export const equipmentItemIsDisabled = ( - classNames: string[], - raceName: RaceNames, - item: EquipmentItem -) => { - // Nothing disabled for custom classes - if (getClassType(classNames) === "custom") return false; - // Races that do not allow large equipment - if (races[raceName]?.noLargeEquipment && item.size === "L") return true; - // Classes that do not allow large equipment - if ( - classNames.some( - (className) => classes[className as ClassNames].noLargeEquipment - ) && - item.size === "L" - ) { - return true; - } - let disabled = false; - classNames.forEach((className) => { - if (classes[className as ClassNames].specificEquipmentItems) { - const specificEquipmentItems = classes[className as ClassNames] - .specificEquipmentItems || [[], []]; - - // if the item category is listed in specificEquipmentItems[0] AND the string in specificEquipmentItems[1] is not in the item name - if ( - specificEquipmentItems[0].includes(item.category) && - specificEquipmentItems[1].every( - (string) => !item.name.toLowerCase().includes(string) - ) - ) { - disabled = true; - } - } - }); - - return disabled; -}; - -export const openInNewTab = (url: string) => { - const newWindow = window.open(url, "_blank", "noopener,noreferrer"); - if (newWindow) newWindow.opener = null; -}; diff --git a/src/support/pageNewCharacterSupport.tsx b/src/support/pageNewCharacterSupport.tsx new file mode 100644 index 00000000..a6bda6f7 --- /dev/null +++ b/src/support/pageNewCharacterSupport.tsx @@ -0,0 +1,196 @@ +import StepAbilities from "@/components/PageNewCharacter/StepAbilities/StepAbilities"; +import StepClass from "@/components/PageNewCharacter/StepClass/StepClass"; +import StepDetails from "@/components/PageNewCharacter/StepDetails/StepDetails"; +import StepEquipment from "@/components/PageNewCharacter/StepEquipment/StepEquipment"; +import StepHitPoints from "@/components/PageNewCharacter/StepHitPoints/StepHitPoints"; +import StepRace from "@/components/PageNewCharacter/StepRace/StepRace"; +import { classes } from "@/data/classes"; +import { Abilities, CharData, ClassNames } from "@/data/definitions"; +import { MessageInstance } from "antd/es/message/interface"; +import { marked } from "marked"; +import { createDocument } from "@/support/accountSupport"; +import { auth } from "@/firebase"; +import { NavigateFunction } from "react-router-dom"; + +const newCharacterStepDescriptions = { + abilities: + marked(`Roll for your character's Abilities. **You can click the "Roll" buttons or use your own dice and record your scores**. Afterward your character will have a score ranging from 3 to 18 in each of the Abilities below. A bonus (or penalty) Modifier is then associated with each score. Your character's Abilities will begin to determine the options available to them in the next steps as well, so good luck! + + BFRPG Character Ability documentation`), + race: marked(`Choose your character's Race. **Some options may be unavailable due to your character's Ability Scores**. Each Race except Humans has a minimum and maximum value for specific Abilities that your character's Ability Scores must meet in order to select them. Consider that each Race has specific restrictions, special abilities, and Saving Throws. Choose wisely. **Races included in the base game rules are in bold**. + + BFRPG Character Race documentation`), + class: + marked(`Choose your character's Class. **Your character's Race and Ability Scores will determine which Class options are available**. Your Class choice determines your character's background and how they will progress through the game as they level up. **Classes included in the base game rules are in bold**. + + BFRPG Character Class documentation`), + hp: marked(`Roll for your character's Hit Points. **Your character's Race may place restrictions on the Hit Dice available to them, but generally this is determined by their chosen Class**. Additionally, your character's Constitution modifier is added/subtracted from this value with a minimum value of 1. The end result is the amount of Hit Points your character will start with and determines how much damage your character can take in battle. + + BFRPG Character Hit Points documentation`), + equipment: marked( + `Roll for your character's starting gold and purchase their equipment. **Keep in mind that your character's Race and Class selections may limit types and amounts of equipment they can have**. + + BFRPG Character Equipment documentation`, + ), + name: marked( + `You're almost done! Personalize your newly minted character by giving them an identity. Optionally, upload a portrait image if you'd like. Image must be PNG/JPG and <= 1mb`, + ), +}; + +export const getStepsItems = ( + character: CharData, + setCharacter: (character: CharData) => void, + setComboClass: (comboClass: boolean) => void, + setComboClassSwitch: (comboClassSwitch: boolean) => void, + comboClass: boolean, + comboClassSwitch: boolean, +) => [ + { + title: "Abilities", + fulltitle: "Roll for Ability Scores", + description: newCharacterStepDescriptions.abilities, + content: ( + + ), + }, + { + title: "Race", + fulltitle: "Choose a Race", + description: newCharacterStepDescriptions.race, + content: ( + + ), + }, + { + title: "Class", + fulltitle: "Choose a Class", + description: newCharacterStepDescriptions.class, + content: ( + + ), + }, + { + title: "Hit Points", + fulltitle: "Roll for Hit Points", + description: newCharacterStepDescriptions.hp, + content: ( + + ), + }, + { + title: "Equipment", + fulltitle: "Buy Equipment", + description: newCharacterStepDescriptions.equipment, + content: ( + + ), + }, + { + title: "Name", + fulltitle: "Name Your Character", + description: newCharacterStepDescriptions.name, + content: , + }, +]; + +const areAllAbilitiesSet = (abilities: Abilities) => { + if (!abilities) return false; + for (const key in abilities) { + const value = +abilities[key as keyof typeof abilities]; + if (value <= 0 || isNaN(value)) { + return false; + } + } + if (Object.entries(abilities).length === 6) return true; +}; + +export const isNextButtonEnabled = ( + currentStep: number, + character: CharData, +) => { + switch (currentStep) { + case 0: + return areAllAbilitiesSet(character.abilities?.scores); + case 1: + return character.race !== ""; + case 2: + // Check if the character has a class + // AND IF SO, any value in their class has a spell budget + // AND IF SO, they have more than 1 spell + if (character.class?.length === 0) { + return false; + } else if ( + character.class?.some((className: string) => { + const spellBudget = classes[className as ClassNames]?.spellBudget; + return spellBudget && spellBudget[0] && spellBudget[0][0] > 0; + }) + ) { + return character.spells?.length > 1; + } + return true; + case 3: + return character.hp?.points !== 0; + case 4: + return character.equipment?.length !== 0; + case 5: + return character.name; + default: + return true; + } +}; + +const success = (name: string, messageApi: MessageInstance) => { + messageApi.open({ + type: "success", + content: `${name} successfully saved!`, + }); +}; + +const errorMessage = (message: string, messageApi: MessageInstance) => { + messageApi.open({ + type: "error", + content: `This is an error message: ${message}`, + }); +}; + +export const addCharacterData = async ( + character: CharData, + messageApi: MessageInstance, + setCharacter: (character: CharData) => void, + setCurrentStep: (currentStep: number) => void, + navigate: NavigateFunction, +) => { + await createDocument( + auth.currentUser, + "characters", + character, + (name) => { + success(name, messageApi); + // Reset characterData and step + setCharacter({} as CharData); + setCurrentStep(0); + }, + (error) => { + errorMessage(`Error writing document: ${error}`, messageApi); + }, + () => { + navigate("/"); + }, + ); +}; diff --git a/src/support/spellSupport.ts b/src/support/spellSupport.ts new file mode 100644 index 00000000..95f4af7e --- /dev/null +++ b/src/support/spellSupport.ts @@ -0,0 +1,54 @@ +import { CharData, Spell } from "@/data/definitions"; +import spells from "@/data/spells.json"; +import { classSplit } from "./characterSupport"; + +export const getSpellFromName = (name: string) => { + return spells.find((spell) => spell.name === name); +}; + +export const getSelectedSpellsByLevel = ( + character: CharData, + magicClass: string, +) => { + return character.spells.reduce((acc, spell) => { + const level = spell.level[magicClass.toLowerCase()]; + if (level) { + if (acc[level - 1]) { + acc[level - 1].push(spell); + } else { + acc[level - 1] = [spell]; + } + } + return acc; + }, [] as Spell[][]); +}; + +// Get all the spells available to a Character at a given level +export const getSpellsAtLevel = (character: CharData, level?: number) => { + const characterClass = classSplit(character?.class || []); + const characterLevel = character?.level || 1; + const filteredSpells: Spell[] = []; + characterClass.forEach((className) => { + filteredSpells.push( + ...spells.filter((spell: Spell) => { + const levelValue = spell.level?.[className.toLowerCase()]; + if ( + levelValue !== null && + levelValue !== undefined && + spell.name !== "Read Magic" + ) { + if (level) { + if (levelValue === level) { + return spell; + } + } else { + if (levelValue <= characterLevel) { + return spell; + } + } + } + }), + ); + }); + return filteredSpells; +}; diff --git a/src/support/stringSupport.test.ts b/src/support/stringSupport.test.ts deleted file mode 100644 index a548ce89..00000000 --- a/src/support/stringSupport.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - camelCaseToTitleCase, - extractImageName, - slugToTitleCase, - titleCaseToCamelCase, - toTitleCase, -} from "./stringSupport"; - -describe("toTitleCase", () => { - test("should return a title cased string", () => { - expect(toTitleCase("hello world")).toBe("Hello World"); - }); - - test('should return a title cased string with "of" in lowercase', () => { - expect(toTitleCase("the lord of the rings")).toBe("The Lord of the Rings"); - }); -}); - -describe("camelCaseToTitleCase", () => { - test("should return a title cased string", () => { - expect(camelCaseToTitleCase("helloWorld")).toBe("Hello World"); - }); -}); - -describe("slugToTitleCase", () => { - test('should return a title cased string with "of" in lowercase', () => { - expect(slugToTitleCase("the-lord-of-the-rings")).toBe( - "The Lord of the Rings" - ); - }); -}); - -describe("titleCaseToCamelCase", () => { - test("should return a camel cased string", () => { - expect(titleCaseToCamelCase("Hello World")).toBe("helloWorld"); - }); -}); - -describe("extractImageName", () => { - test("should return the image name", () => { - expect(extractImageName("/static/media/elf.1f3f9b2f.jpg")).toBe("elf"); - }); - - test("should return undefined if no match", () => { - expect(extractImageName("/static/media/elf.jpg")).toBe(undefined); - }); -}); diff --git a/src/support/stringSupport.ts b/src/support/stringSupport.ts old mode 100755 new mode 100644 index e1516dd0..94b5e2a7 --- a/src/support/stringSupport.ts +++ b/src/support/stringSupport.ts @@ -17,7 +17,8 @@ const titleCaseExceptions = [ ]; export function toTitleCase(input: string): string { - const words = input.toLowerCase().split(" "); + const separators = /[-\s]/; + const words = input.toLowerCase().split(separators); const titleCaseWords = words.map((word, index) => { if ( index === 0 || @@ -29,15 +30,19 @@ export function toTitleCase(input: string): string { return word; } }); - return titleCaseWords.join(" "); -} -export function camelCaseToTitleCase(input: string) { - return toTitleCase(input.replace(/([A-Z])/g, " $1").toLowerCase()); -} + // Preserve original separators + let currentIndex = 0; + const result = []; + for (const word of words) { + result.push(titleCaseWords[currentIndex]); + currentIndex++; + if (input.indexOf(word) + word.length < input.length) { + result.push(input[input.indexOf(word) + word.length]); + } + } -export function slugToTitleCase(input: string) { - return toTitleCase(input.replace(/-/g, " ")); + return result.join(""); } export function titleCaseToCamelCase(input: string) { @@ -51,8 +56,41 @@ export function titleCaseToCamelCase(input: string) { }); } -export function extractImageName(url: string) { - const regex = /\/static\/media\/(.*[^-])\..*?\.jpg/; - const match = url.match(regex); - return match ? match[1] : undefined; +export function toCamelCase(input: string) { + return input + .trim() + .toLowerCase() + .replace(/[-_\s]+(.)?/g, (_, nextChar) => + nextChar ? nextChar.toUpperCase() : "", + ) + .replace(/^(.)/, (firstChar) => firstChar.toLowerCase()); +} + +export function camelCaseToTitleCase(input: string) { + return toTitleCase(input.replace(/([A-Z])/g, " $1").toLowerCase()); } + +export function slugToTitleCase(input: string) { + return toTitleCase(input.replace(/-/g, " ")); +} + +export function toSlugCase(input: string): string { + return input + .toLowerCase() + .replace(/[\W_]+/g, "-") + .replace(/-+/g, "-") + .replace(/^-+|-+$/g, ""); +} + +export const mobileBreakpoint = "(max-width: 639px)"; +export const tabletBreakpoint = "(min-width: 768px) and (max-width: 1023px)"; +export const desktopBreakpoint = "(min-width: 1024px)"; + +export enum AttackTypes { + MELEE = "melee", + MISSILE = "missile", + BOTH = "both", +} + +export const customClassString = + 'You are using a custom class. Use the "Bio & Notes" field below to calculate your character\'s Saving Throws, Special Abilities, and Restrictions. For standard classes, these values will be calculated here automatically.'; diff --git a/src/support/theme.ts b/src/support/theme.ts new file mode 100644 index 00000000..94e2496e --- /dev/null +++ b/src/support/theme.ts @@ -0,0 +1,112 @@ +import { theme } from "antd"; +import { ColorScheme, darkenHexColor, lightenHexColor } from "./colorSupport"; + +export const cqTheme = { + algorithm: theme.darkAlgorithm, + token: { + colorBgBase: ColorScheme.SPRINGWOOD, + colorBgContainer: ColorScheme.SPRINGWOOD, + colorBgLayout: ColorScheme.SPRINGWOOD, + colorBorderSecondary: darkenHexColor(ColorScheme.STONE, 10), + colorFill: ColorScheme.SHIPGRAY, + colorError: ColorScheme.POMEGRANATE, + colorErrorActive: lightenHexColor(ColorScheme.POMEGRANATE, 10), + colorErrorBg: darkenHexColor(ColorScheme.POMEGRANATE, 10), + colorErrorBgHover: lightenHexColor(ColorScheme.POMEGRANATE, 5), + colorErrorBorder: darkenHexColor(ColorScheme.POMEGRANATE, 5), + colorLink: ColorScheme.RUST, + colorPrimary: ColorScheme.SEABUCKTHORN, + colorPrimaryBg: darkenHexColor(ColorScheme.SEABUCKTHORN, 20), + colorSuccess: ColorScheme.SUSHI, + colorTextBase: ColorScheme.SHIPGRAY, + colorWarning: ColorScheme.CALIFORNIA, + screenXS: 320, + screenXSMin: 320, + screenXSMax: 639, + screenSM: 640, + screenSMMin: 640, + screenSMMax: 767, + }, + components: { + Alert: { + colorInfoBg: ColorScheme.SPRINGWOOD, + }, + Badge: { + colorBgContainer: ColorScheme.SPRINGWOOD, + colorBorderBg: ColorScheme.SPRINGWOOD, + colorError: ColorScheme.SUSHI, + }, + Button: { + primaryColor: ColorScheme.SHIPGRAY, + textHoverBg: ColorScheme.SHIPGRAY, + borderColorDisabled: lightenHexColor(ColorScheme.STONE, 50), + defaultBg: lightenHexColor(ColorScheme.SHIPGRAY, 90), + defaultBorderColor: lightenHexColor(ColorScheme.SHIPGRAY, 50), + }, + Card: { + actionsBg: ColorScheme.SEABUCKTHORN, + }, + Checkbox: { + colorBorder: ColorScheme.STONE, + }, + Collapse: { + headerBg: ColorScheme.SEABUCKTHORN, + colorBorder: ColorScheme.CALIFORNIA, + }, + Descriptions: { + padding: 8, + titleMarginBottom: 8, + }, + Divider: { + colorSplit: ColorScheme.SEABUCKTHORN, + lineWidth: 2, + verticalMarginInline: 0, + margin: 0, + marginLG: 0, + }, + FloatButton: { + colorBgElevated: ColorScheme.SEABUCKTHORN, + }, + Form: { + itemMarginBottom: 8, + colorError: ColorScheme.POMEGRANATE, + }, + Input: { + colorBorder: lightenHexColor(ColorScheme.STONE, 50), + activeBg: "#FFF", + colorBgContainer: "#FFF", + colorText: ColorScheme.SHIPGRAY, + }, + InputNumber: { + colorBorder: ColorScheme.STONE, + }, + Layout: { + headerBg: ColorScheme.SHIPGRAY, + headerPadding: "16px", + footerBg: ColorScheme.SHIPGRAY, + footerPadding: "16px", + }, + List: { + colorBorder: ColorScheme.STONE, + }, + Modal: { + colorIconHover: ColorScheme.SHIPGRAY, + }, + Notification: { + colorBgElevated: ColorScheme.SEABUCKTHORN, + paddingMD: 16, + paddingContentHorizontalLG: 16, + }, + Radio: { + colorBorder: ColorScheme.STONE, + }, + Select: { + colorBorder: ColorScheme.STONE, + }, + Statistic: { contentFontSize: 72 }, + Tooltip: { + colorBgSpotlight: ColorScheme.SHIPGRAY, + colorText: ColorScheme.SEABUCKTHORN, + }, + }, +}; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 00000000..b1f45c78 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/tailwind.config.js b/tailwind.config.js old mode 100755 new mode 100644 index cd21757b..dc7914fb --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,31 +1,30 @@ /** @type {import('tailwindcss').Config} */ -module.exports = { + +export default { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { + colors: { + california: "#FDA00D", + pomegranite: "#44336", + rust: "#965247", + seaBuckthorn: "#F9B32A", + shipGray: "#3E3643", + springWood: "#F4F5EB", + stone: "#9F9B8F", + sushi: "#80B045", + }, extend: { - colors: { - springWood: "#F4F5EB", - zorba: "#9F9B8F", - seaBuckthorn: "#F9B32A", - copperRust: "#965247", - shipGray: "#3E3643", - }, - flex: { - 25: "1 1 25%", - }, - screens: { - lg: "992px", - sm: "450px", - print: { raw: "print" }, - }, - fontFamily: { - enchant: ["Enchant", "serif"], + backgroundImage: { + noise: "url('assets/images/noise.svg')", }, }, + fontFamily: { + enchant: ["Enchant", "serif"], + }, }, - plugins: ["tailwindcss ,autoprefixer"], + plugins: [], + important: true, corePlugins: { preflight: false, }, - important: true, }; diff --git a/tailwind.config.js_orig b/tailwind.config.js_orig deleted file mode 100755 index 47684320..00000000 --- a/tailwind.config.js_orig +++ /dev/null @@ -1,31 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ["./src/**/*.{js,jsx,ts,tsx}"], - theme: { - extend: { - colors: { - springWood: "#F4F5EB", - zorba: "#9F9B8F", - seaBuckthorn: "#F9B32A", - copperRust: "#965247", - shipGray: "#3E3643", - }, - flex: { - 25: "1 1 25%", - }, - screens: { - lg: "992px", - sm: "450px", - print: { raw: "print" }, - }, - }, - }, - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, - corePlugins: { - preflight: false, - }, - important: true, -}; diff --git a/tsconfig.json b/tsconfig.json old mode 100755 new mode 100644 index 7f2103bd..83d436e9 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,28 @@ { "compilerOptions": { - "target": "ESNext", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", + "types": ["vite-plugin-svgr/client", "vite/client", "jest", "node"], + "baseUrl": ".", + "paths": { "@/*": ["src/*"] }, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "downlevelIteration": true - // "types": ["vite/client", "vite-plugin-svgr/client"] + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true }, - "include": ["src"], - "types": ["vite/client", "vite-plugin-svgr/client"] + "include": ["src", "src/declarations.d.ts", "scripts", "jest.config.ts"], + "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 00000000..42872c59 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite-env.d.ts b/vite-env.d.ts deleted file mode 100755 index 11f02fe2..00000000 --- a/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/vite.config.ts b/vite.config.ts old mode 100755 new mode 100644 index fdca8158..4aeb59f5 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,15 +1,15 @@ import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; -import viteTsconfigPaths from "vite-tsconfig-paths"; -import svgrPlugin from "vite-plugin-svgr"; +import react from "@vitejs/plugin-react-swc"; +import svgr from "vite-plugin-svgr"; +import path from "path"; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react(), viteTsconfigPaths(), svgrPlugin()], + plugins: [react(), svgr()], build: { outDir: "build", }, - server: { - open: true, + resolve: { + alias: { "@": path.resolve(__dirname, "./src") }, }, });