├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .gitmodules
├── .prettierignore
├── .prettierrc
├── README.md
├── package.json
├── public
├── favicon.ico
├── favicon.png
├── index.html
├── manifest.json
└── robots.txt
├── scripts
└── createitems.mjs
├── src
├── App.css
├── App.test.tsx
├── App.tsx
├── Reddit.md
├── State
│ ├── CommonState.ts
│ ├── GameTypeState.ts
│ ├── PopupStatusState.ts
│ ├── SearchState.ts
│ ├── SpoilerState.ts
│ ├── Types.tsx
│ └── index.ts
├── components
│ ├── Firebase
│ │ ├── FirebaseProvider.tsx
│ │ ├── config.ts
│ │ └── index.tsx
│ ├── GameSelector.tsx
│ ├── Tabs
│ │ ├── About.tsx
│ │ ├── Account
│ │ │ ├── Account.tsx
│ │ │ ├── PasswordChangeDialog.tsx
│ │ │ ├── SignedInAccount.tsx
│ │ │ └── SignedOutAccount.tsx
│ │ ├── MainView
│ │ │ ├── ImportData.tsx
│ │ │ ├── Items
│ │ │ │ ├── Grid
│ │ │ │ │ ├── ItemCard.tsx
│ │ │ │ │ ├── ItemGrid.tsx
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── itemCard.scss
│ │ │ │ │ └── itemGrid.scss
│ │ │ │ ├── ItemList.tsx
│ │ │ │ ├── ItemManagement
│ │ │ │ │ ├── ItemManagementContainer.tsx
│ │ │ │ │ ├── NoItemManagement.tsx
│ │ │ │ │ ├── PartyItemManagement.tsx
│ │ │ │ │ ├── SimpleItemManagement.tsx
│ │ │ │ │ ├── items.ts
│ │ │ │ │ ├── partyItemManagement.scss
│ │ │ │ │ └── simpleItemManagement.scss
│ │ │ │ ├── ItemsView.tsx
│ │ │ │ ├── PurchaseItem.tsx
│ │ │ │ ├── Search
│ │ │ │ │ ├── Discount.tsx
│ │ │ │ │ ├── FilterAvailability.tsx
│ │ │ │ │ ├── FilterClass.tsx
│ │ │ │ │ ├── FilterResources.tsx
│ │ │ │ │ ├── FilterSlots.tsx
│ │ │ │ │ ├── FindItemSearchBar.tsx
│ │ │ │ │ ├── RenderAs.tsx
│ │ │ │ │ ├── SearchOptions.tsx
│ │ │ │ │ ├── SortItems.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Table
│ │ │ │ │ ├── ConsumptionPanel.tsx
│ │ │ │ │ ├── ItemCost.tsx
│ │ │ │ │ ├── ItemSummon.tsx
│ │ │ │ │ ├── ItemTable.tsx
│ │ │ │ │ ├── ItemTableRow.tsx
│ │ │ │ │ ├── ItemText.tsx
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── itemTable.scss
│ │ │ │ └── index.ts
│ │ │ └── MainView.tsx
│ │ ├── Share
│ │ │ ├── Share.tsx
│ │ │ └── UploadForm.tsx
│ │ └── SpoilerFilters
│ │ │ ├── Common
│ │ │ ├── ConfirmSpecialUnlockPanel.tsx
│ │ │ └── SpecialUnlockButton.tsx
│ │ │ ├── Games
│ │ │ ├── ConfirmGameRemoval.tsx
│ │ │ ├── GameFilterCheckbox.tsx
│ │ │ ├── GameFilters.tsx
│ │ │ ├── GameHelp.tsx
│ │ │ └── index.ts
│ │ │ ├── Items
│ │ │ ├── BuildingLevelFilter.tsx
│ │ │ ├── FHSpoilerFilter.tsx
│ │ │ ├── FilterCheckbox.tsx
│ │ │ ├── GHSpoilerFilter.tsx
│ │ │ ├── GameFilter.tsx
│ │ │ ├── JOTLSpoilerFilter.tsx
│ │ │ ├── ProsperityFilter.tsx
│ │ │ ├── ReputationPulldown.tsx
│ │ │ ├── ScenarioCompletedFilter.tsx
│ │ │ ├── SoloClassFilter.tsx
│ │ │ ├── SoloClassFilterBlock.tsx
│ │ │ ├── SpoilerFilterItemList.tsx
│ │ │ ├── ToggleAllButton.tsx
│ │ │ └── index.ts
│ │ │ ├── Party
│ │ │ ├── ClassList.tsx
│ │ │ ├── ConfirmClassDelete.tsx
│ │ │ ├── OwnedItemsList.tsx
│ │ │ ├── PartyManagementFilter.tsx
│ │ │ ├── PartySpoiler.tsx
│ │ │ ├── PartySpoilerList.tsx
│ │ │ └── index.ts
│ │ │ ├── Secrets
│ │ │ └── Secrets.tsx
│ │ │ └── SpoilerFilters.tsx
│ └── Utils
│ │ ├── ClassDropdown.tsx
│ │ ├── ClassIcon.tsx
│ │ ├── GHIcon.tsx
│ │ └── index.ts
├── constants
│ └── routes.ts
├── games
│ ├── GameData.ts
│ ├── GameInfo.ts
│ ├── GameType.ts
│ ├── fh
│ │ ├── FHGameData.ts
│ │ └── items.json
│ ├── gh
│ │ ├── GHGameData.ts
│ │ └── items.json
│ ├── index.ts
│ ├── jotl
│ │ ├── JOTlGameData.ts
│ │ └── items.json
│ └── useGameSort.ts
├── helpers.ts
├── hooks
│ ├── useIsItemShown.tsx
│ ├── useItems.tsx
│ ├── useRemovePlayer.tsx
│ └── useSetSorting.tsx
├── img
│ ├── class-tokens
│ │ ├── be.png
│ │ ├── br.png
│ │ ├── bt.png
│ │ ├── ch.png
│ │ ├── dm.png
│ │ ├── dr.png
│ │ ├── ds.png
│ │ ├── el.png
│ │ ├── ht.png
│ │ ├── mt.png
│ │ ├── ns.png
│ │ ├── ph.png
│ │ ├── qm.png
│ │ ├── rg.png
│ │ ├── sb.png
│ │ ├── sc.png
│ │ ├── sk.png
│ │ ├── ss.png
│ │ ├── su.png
│ │ ├── sw.png
│ │ ├── ti.png
│ │ ├── vw.png
│ │ └── xx.png
│ ├── classes
│ │ ├── BE.png
│ │ ├── BR.png
│ │ ├── BT.png
│ │ ├── CH.png
│ │ ├── CS1.png
│ │ ├── CS10.png
│ │ ├── CS11.png
│ │ ├── CS2.png
│ │ ├── CS3.png
│ │ ├── CS4.png
│ │ ├── CS5.png
│ │ ├── CS6.png
│ │ ├── CS7.png
│ │ ├── CS8.png
│ │ ├── CS9.png
│ │ ├── CSA1.png
│ │ ├── CSA2.png
│ │ ├── CSA3.png
│ │ ├── DM.png
│ │ ├── DR.png
│ │ ├── DS.png
│ │ ├── EL.png
│ │ ├── FH1.png
│ │ ├── FH10.png
│ │ ├── FH11.png
│ │ ├── FH12.png
│ │ ├── FH13.png
│ │ ├── FH14.png
│ │ ├── FH15.png
│ │ ├── FH16.png
│ │ ├── FH17.png
│ │ ├── FH2.png
│ │ ├── FH3.png
│ │ ├── FH4.png
│ │ ├── FH5.png
│ │ ├── FH6.png
│ │ ├── FH7.png
│ │ ├── FH8.png
│ │ ├── FH9.png
│ │ ├── HT.png
│ │ ├── MT.png
│ │ ├── NS.png
│ │ ├── PH.png
│ │ ├── QM.png
│ │ ├── RG.png
│ │ ├── SB.png
│ │ ├── SC.png
│ │ ├── SK.png
│ │ ├── SS.png
│ │ ├── SU.png
│ │ ├── SW.png
│ │ ├── TI.png
│ │ ├── VW.png
│ │ └── XX.png
│ ├── icons
│ │ ├── conditions
│ │ │ ├── bane.png
│ │ │ ├── bless.png
│ │ │ ├── brittle.png
│ │ │ ├── chill.png
│ │ │ ├── curse.png
│ │ │ ├── disarm.png
│ │ │ ├── dodge.png
│ │ │ ├── empower.png
│ │ │ ├── enfeeble.png
│ │ │ ├── fh-wound.png
│ │ │ ├── immobilize.png
│ │ │ ├── impair.png
│ │ │ ├── infect.png
│ │ │ ├── invisible.png
│ │ │ ├── muddle.png
│ │ │ ├── pierce.png
│ │ │ ├── poison.png
│ │ │ ├── pull.png
│ │ │ ├── push.png
│ │ │ ├── regenerate.png
│ │ │ ├── rolling.png
│ │ │ ├── rupture.png
│ │ │ ├── strengthen.png
│ │ │ ├── stun.png
│ │ │ ├── target.png
│ │ │ ├── ward.png
│ │ │ └── wound.png
│ │ ├── elements
│ │ │ ├── any.png
│ │ │ ├── dark.png
│ │ │ ├── earth.png
│ │ │ ├── fh-air-icon.png
│ │ │ ├── fh-air.png
│ │ │ ├── fh-consume.png
│ │ │ ├── fh-dark-icon.png
│ │ │ ├── fh-dark.png
│ │ │ ├── fh-earth-icon.png
│ │ │ ├── fh-earth.png
│ │ │ ├── fh-fire-icon.png
│ │ │ ├── fh-fire.png
│ │ │ ├── fh-ice-icon.png
│ │ │ ├── fh-ice.png
│ │ │ ├── fh-light-icon.png
│ │ │ ├── fh-light.png
│ │ │ ├── fh-wild-icon.png
│ │ │ ├── fh-wild.png
│ │ │ ├── fire.png
│ │ │ ├── ice.png
│ │ │ ├── light.png
│ │ │ ├── use.png
│ │ │ ├── wind.png
│ │ │ ├── x-any.png
│ │ │ ├── x-dark.png
│ │ │ ├── x-earth.png
│ │ │ ├── x-fire.png
│ │ │ ├── x-ice.png
│ │ │ ├── x-light.png
│ │ │ └── x-wind.png
│ │ ├── equipment_slot
│ │ │ ├── 1h.png
│ │ │ ├── 2h.png
│ │ │ ├── body.png
│ │ │ ├── head.png
│ │ │ ├── legs.png
│ │ │ └── small.png
│ │ ├── general
│ │ │ ├── Crystalize.png
│ │ │ ├── TOA6.png
│ │ │ ├── attack.png
│ │ │ ├── attack_tile.png
│ │ │ ├── check.png
│ │ │ ├── checkmark.png
│ │ │ ├── circle_x.png
│ │ │ ├── consumed.png
│ │ │ ├── consumed_white.png
│ │ │ ├── damage.png
│ │ │ ├── eot.png
│ │ │ ├── event_card_rip.png
│ │ │ ├── event_card_rip_white.png
│ │ │ ├── event_card_shuffle.png
│ │ │ ├── event_card_shuffle_white.png
│ │ │ ├── experience.png
│ │ │ ├── experience_1.png
│ │ │ ├── experience_2.png
│ │ │ ├── experience_white_1.png
│ │ │ ├── experience_white_2.png
│ │ │ ├── fh-anemone.png
│ │ │ ├── fh-astral.png
│ │ │ ├── fh-flying.png
│ │ │ ├── fh-geminate-left.png
│ │ │ ├── fh-geminate-right.png
│ │ │ ├── fh-heal.png
│ │ │ ├── fh-hourglass.png
│ │ │ ├── fh-jump.png
│ │ │ ├── fh-meter-blue.png
│ │ │ ├── fh-meter-red.png
│ │ │ ├── fh-meter-yellow.png
│ │ │ ├── fh-move.png
│ │ │ ├── fh-prism.png
│ │ │ ├── fh-range.png
│ │ │ ├── fh-shadow.png
│ │ │ ├── fh-shards.png
│ │ │ ├── fh-shield.png
│ │ │ ├── flip_back_white.png
│ │ │ ├── flip_white.png
│ │ │ ├── flying.png
│ │ │ ├── heal.png
│ │ │ ├── jump.png
│ │ │ ├── loot.png
│ │ │ ├── lost.png
│ │ │ ├── lost_white.png
│ │ │ ├── modifier_2x_circle.png
│ │ │ ├── modifier_minus_one.png
│ │ │ ├── modifier_minus_one_1.png
│ │ │ ├── modifier_minus_one_circle.png
│ │ │ ├── modifier_minus_one_cropped.png
│ │ │ ├── modifier_minus_one_white.png
│ │ │ ├── modifier_minus_two_circle.png
│ │ │ ├── modifier_no_damage.png
│ │ │ ├── modifier_plus_1.png
│ │ │ ├── modifier_plus_2.png
│ │ │ ├── modifier_zero_circle.png
│ │ │ ├── move.png
│ │ │ ├── ongoing.png
│ │ │ ├── player_tile.png
│ │ │ ├── range.png
│ │ │ ├── recover.png
│ │ │ ├── recover_white.png
│ │ │ ├── refresh.png
│ │ │ ├── refresh_white.png
│ │ │ ├── retaliate.png
│ │ │ ├── scrapx.png
│ │ │ ├── shield.png
│ │ │ ├── shuffle.png
│ │ │ ├── shuffle_white.png
│ │ │ ├── slot_empty.png
│ │ │ ├── slot_experience.png
│ │ │ ├── spent.png
│ │ │ ├── spent_white.png
│ │ │ ├── target.png
│ │ │ └── teleport.png
│ │ ├── multi_attack
│ │ │ ├── cleave_0_1.png
│ │ │ ├── cone_0_1.png
│ │ │ ├── cone_1_1.png
│ │ │ ├── cube_2_2.png
│ │ │ ├── line_0_1_1.png
│ │ │ └── line_1_1.png
│ │ └── resources
│ │ │ ├── arrowvine.png
│ │ │ ├── axenut.png
│ │ │ ├── corpsecap.png
│ │ │ ├── flamefruit.png
│ │ │ ├── hide.png
│ │ │ ├── item.png
│ │ │ ├── lumber.png
│ │ │ ├── metal.png
│ │ │ ├── rockroot.png
│ │ │ └── snowthistle.png
│ └── lost.png
├── index.css
├── index.tsx
├── logo.svg
├── react-app-env.d.ts
└── serviceWorker.ts
├── tsconfig.json
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | build
2 | worldhaven
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: "@typescript-eslint/parser", // Specifies the ESLint parser
3 | parserOptions: {
4 | ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
5 | sourceType: "module", // Allows for the use of imports
6 | ecmaFeatures: {
7 | jsx: true, // Allows for the parsing of JSX
8 | },
9 | },
10 | settings: {
11 | react: {
12 | version: "detect", // Tells eslint-plugin-react to automatically detect the version of React to use
13 | },
14 | },
15 | plugins: [
16 | // ...
17 | "react-hooks",
18 | ],
19 | extends: [
20 | // "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
21 | // "plugin:@typescript-eslint/recommended", // Uses the recommended rules from @typescript-eslint/eslint-plugin
22 | "plugin:react-hooks/recommended",
23 | ],
24 | rules: {
25 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
26 | // e.g. "@typescript-eslint/explicit-function-return-type": "off",
27 | "react-hooks/rules-of-hooks": "error",
28 | "react-hooks/exhaustive-deps": "warn",
29 | "@typescript-eslint/no-var-requires": "off",
30 | "@typescript-eslint/no-explicit-any": "off",
31 | },
32 | };
33 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy
2 | on:
3 | # Triggers the workflow on push or pull request events but only for the master branch
4 | push:
5 | branches: [master]
6 | jobs:
7 | build-and-deploy:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout 🛎️
11 | uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
12 | with:
13 | persist-credentials: false
14 |
15 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
16 | run: |
17 | npm i
18 | npm run build
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | /.vscode/settings.json
25 | /.eslintcache
26 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "worldhaven"]
2 | path = worldhaven
3 | url = https://github.com/any2cards/worldhaven.git
4 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | build
2 | node_modules
3 | worldhaven
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "arrowParens": "always",
4 | "useTabs": true
5 | }
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gloomhaven Item DB
2 |
3 | https://heisch.github.io/gloomhaven-item-db/
4 |
5 | ## Description
6 |
7 | a small item database for the game [Gloomhaven](http://www.cephalofair.com/gloomhaven) by [Cephalofair Games](http://www.cephalofair.com/) [Developer: Isaac Childres].
8 |
9 | ## Third Party
10 |
11 | this project is using the item-image scans from https://github.com/any2cards/gloomhaven
12 |
13 | ---
14 | _Gloomhaven: Gloomhaven and all related properties, images and text are owned by Cephalofair Games._
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gloomhaven-item-db",
3 | "homepage": "https://heisch.github.io/gloomhaven-item-db",
4 | "version": "1.0.0",
5 | "private": true,
6 | "dependencies": {
7 | "@testing-library/jest-dom": "^5.16.5",
8 | "@testing-library/react": "^13.4.0",
9 | "@testing-library/user-event": "^13.5.0",
10 | "@types/jest": "^27.5.2",
11 | "@types/node": "^16.18.21",
12 | "@types/react": "^18.0.30",
13 | "@types/react-dom": "^18.0.11",
14 | "firebase": "^9.18.0",
15 | "gh-pages": "^5.0.0",
16 | "lodash": "^4.17.21",
17 | "react": "^17.0.0",
18 | "react-dom": "^17.0.0",
19 | "react-firebaseui": "^6.0.0",
20 | "react-router-dom": "^6.9.0",
21 | "react-scripts": "5.0.1",
22 | "recoil": "^0.7.7",
23 | "sass": "^1.60.0",
24 | "semantic-ui-css": "^2.5.0",
25 | "semantic-ui-react": "^2.1.4",
26 | "typescript": "^4.9.5",
27 | "web-vitals": "^2.1.4"
28 | },
29 | "scripts": {
30 | "deploy": "gh-pages -d build",
31 | "start": "react-scripts start",
32 | "build": "react-scripts build",
33 | "test": "react-scripts test",
34 | "eject": "react-scripts eject",
35 | "lint": "eslint '*/**/*.{js,ts,tsx}' --fix --cache"
36 | },
37 | "eslintConfig": {
38 | "extends": [
39 | "react-app",
40 | "react-app/jest"
41 | ]
42 | },
43 | "browserslist": {
44 | "production": [
45 | ">0.2%",
46 | "not dead",
47 | "not op_mini all"
48 | ],
49 | "development": [
50 | "last 1 chrome version",
51 | "last 1 firefox version",
52 | "last 1 safari version"
53 | ]
54 | },
55 | "devDependencies": {
56 | "@types/lodash": "^4.14.192"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/public/favicon.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
23 | Gloomhaven Item DB
24 |
25 |
26 |
27 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/scripts/createitems.mjs:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | const folderName = "../worldhaven/images/items/frosthaven";
4 |
5 | const dirs = fs.readdirSync(folderName);
6 |
7 | let fileList = [];
8 | dirs.forEach((dir) => {
9 | const files = fs.readdirSync(`${folderName}/${dir}`);
10 | const filteredFiles = files.filter((name) => !name.includes("-back.png"));
11 | fileList = [
12 | ...fileList,
13 | ...filteredFiles
14 | .map((file) => {
15 | const splitName = file.split("-");
16 | const gameType = splitName[0];
17 | const idStr = splitName[1];
18 | if (
19 | idStr.endsWith("b") ||
20 | idStr.endsWith("c") ||
21 | idStr.endsWith("d")
22 | ) {
23 | return null;
24 | }
25 | const count = idStr.endsWith("a") ? 2 : 1;
26 | const imageSuffix = idStr.endsWith("a") ? "a" : undefined;
27 | const id = parseInt(splitName[1], 10);
28 | const name = splitName.slice(2).join(" ").replace(".png", "");
29 | const item = {
30 | id,
31 | gameType,
32 | name,
33 | count,
34 | cost: 50,
35 | slot: "head",
36 | source: "unknown",
37 | desc: "",
38 | folder: dir,
39 | imageSuffix,
40 | };
41 | return item;
42 | })
43 | .filter((item) => item),
44 | ];
45 | });
46 |
47 | fs.writeFileSync("./items.json", JSON.stringify(fileList, null, 2));
48 | console.log(fileList.length);
49 |
--------------------------------------------------------------------------------
/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Container } from "semantic-ui-react";
3 | import "semantic-ui-css/semantic.min.css";
4 | import "./App.css";
5 | import MainView from "./components/Tabs/MainView/MainView";
6 | import { GameSelector } from "./components/GameSelector";
7 |
8 | const App = () => {
9 | return (
10 |
11 |
12 |
13 |
14 | );
15 | };
16 |
17 | export default App;
18 |
--------------------------------------------------------------------------------
/src/Reddit.md:
--------------------------------------------------------------------------------
1 | ###**[Gloomhaven Item Database](https://heisch.github.io/gloomhaven-item-db/)**
2 |
3 | **Functionality:** Searchable/Filterable Database of all item cards.
4 |
5 |
6 | **Summary:** (12/11/2022) This is a simple web based app that can be used as a quick reference for viewing, searching and filtering items for
7 | - Gloomhaven
8 | - Forgotten Circles
9 | - Jaws of the Lion
10 | - Crimson Scales.
11 | - Frosthaven (Coming SOON!)
12 |
13 | **Pros:**
14 |
15 | - Simple UI, easy to interact with. (Searchable, Filterable)
16 | - Uses spoiler-friendly filtering to only show what items the party has unlocked to prevent spoilers.
17 | - Keeps track of what items are owned by what character
18 | - Share the database bewtween players so other team members can easily see what's available in the campaign
19 |
20 | **Cons:**
21 |
22 | - Only a reference tool
--------------------------------------------------------------------------------
/src/State/CommonState.ts:
--------------------------------------------------------------------------------
1 | import { atom, RecoilState, selector } from "recoil";
2 | import { gameTypeState } from ".";
3 | import { gameDataTypes, GameType } from "../games";
4 | import { gameInfo } from "../games/GameInfo";
5 |
6 | export const LOCAL_STORAGE_PREFIX = "ItemView:spoilerFilter_";
7 |
8 | export type AtomObject = {
9 | [key: string]: RecoilState;
10 | };
11 |
12 | export const dataDirtyState = atom({
13 | key: "data-dirty-state",
14 | default: false,
15 | });
16 |
17 | type FixUpFunction = (old: any, gameType: GameType, spoilerObj?: any) => T;
18 |
19 | function getDefaultValue(
20 | gameType: GameType,
21 | name: string,
22 | defaultValue: T,
23 | fixUp?: FixUpFunction
24 | ) {
25 | const key = LOCAL_STORAGE_PREFIX + gameType;
26 | const spoilerStorage = localStorage.getItem(key);
27 | const spoilerObj = spoilerStorage ? JSON.parse(spoilerStorage) : {};
28 | const value = spoilerObj[name] || defaultValue;
29 | if (fixUp) {
30 | const newValue = fixUp(value, gameType, spoilerObj);
31 | spoilerObj[name] = newValue;
32 | localStorage.setItem(key, JSON.stringify(spoilerObj));
33 | return newValue;
34 | }
35 | return value;
36 | }
37 |
38 | function storeValue(gameType: GameType, name: string, value: T) {
39 | const key = LOCAL_STORAGE_PREFIX + gameType;
40 | const spoilerStorage = localStorage.getItem(key);
41 | const spoilerObj = spoilerStorage ? JSON.parse(spoilerStorage) : {};
42 | spoilerObj[name] = value;
43 | localStorage.setItem(key, JSON.stringify(spoilerObj));
44 | }
45 |
46 | export function createState(name: string, defaultValue: T) {
47 | const atoms: AtomObject = {};
48 | Object.values(gameDataTypes).forEach(({ gameType }) => {
49 | const gameName = gameInfo[gameType].title;
50 | atoms[gameType] = atom({
51 | key: `${gameName}-${name}-state`,
52 | default: defaultValue,
53 | });
54 | });
55 |
56 | const stateSelector = selector({
57 | key: `${name}-state`,
58 | get: ({ get }) => {
59 | const gameType: GameType = get(gameTypeState);
60 | return atoms[gameType];
61 | },
62 | set: ({ set, get }, newValue) => {
63 | const gameType: GameType = get(gameTypeState);
64 | return set(atoms[gameType], newValue);
65 | },
66 | });
67 | return stateSelector;
68 | }
69 |
70 | export function createSpoilerState(
71 | name: string,
72 | defaultValue: T,
73 | fixUp?: FixUpFunction
74 | ) {
75 | const atoms: AtomObject = {};
76 | Object.values(gameDataTypes).forEach(({ gameType }) => {
77 | const gameName = gameInfo[gameType].title;
78 | atoms[gameType] = atom({
79 | key: `${gameName}-${name}-state`,
80 | default: getDefaultValue(gameType, name, defaultValue, fixUp),
81 | effects: [
82 | ({ onSet }) => {
83 | onSet((value) => {
84 | storeValue(gameType, name, value);
85 | });
86 | },
87 | ],
88 | });
89 | });
90 |
91 | const stateSelector = selector({
92 | key: `${name}-state`,
93 | get: ({ get }) => {
94 | const gameType: GameType = get(gameTypeState);
95 | return atoms[gameType];
96 | },
97 | set: ({ set, get }, newValue) => {
98 | set(dataDirtyState, true);
99 | const gameType: GameType = get(gameTypeState);
100 | return set(atoms[gameType], newValue);
101 | },
102 | });
103 |
104 | return stateSelector;
105 | }
106 |
--------------------------------------------------------------------------------
/src/State/GameTypeState.ts:
--------------------------------------------------------------------------------
1 | import { atom, selector } from "recoil";
2 | import { gameDataTypes, GameType } from "../games";
3 | import { GameData } from "../games/GameData";
4 | import QueryString from "qs";
5 |
6 | const getStartingGameType = () => {
7 | const urlParams = QueryString.parse(window.location.search.substr(1));
8 | const lastGameQSP = urlParams["lastGame"] as GameType;
9 | if (lastGameQSP) {
10 | if (Object.values(GameType).includes(lastGameQSP)) {
11 | localStorage.setItem("lastGame", lastGameQSP);
12 | return lastGameQSP;
13 | }
14 | }
15 |
16 | const lastGame = localStorage.getItem("lastGame") as GameType;
17 | if (!lastGame) {
18 | return GameType.Gloomhaven;
19 | }
20 | return lastGame;
21 | };
22 |
23 | export const gameTypeState = atom({
24 | key: "gameTypeState",
25 | default: getStartingGameType(),
26 | effects: [
27 | ({ onSet }) => {
28 | onSet((gameType) => {
29 | localStorage.setItem("lastGame", gameType);
30 | });
31 | },
32 | ],
33 | });
34 |
35 | export const gameDataState = selector({
36 | key: "gameDataState",
37 | get: ({ get }) => {
38 | const gameType: GameType = get(gameTypeState);
39 | return gameDataTypes[gameType];
40 | },
41 | });
42 |
--------------------------------------------------------------------------------
/src/State/PopupStatusState.ts:
--------------------------------------------------------------------------------
1 | import { createState } from "./CommonState";
2 | import { ClassesInUse, GloomhavenItem } from "./Types";
3 |
4 | export const classToDeleteState = createState(
5 | "classToDelete",
6 | undefined
7 | );
8 | export const confirmSpecialUnlockOpenState = createState(
9 | "ConfirmSpecialUnlockOpen",
10 | undefined
11 | );
12 | export const selectedItemState = createState(
13 | "SelectedItem",
14 | undefined
15 | );
16 |
--------------------------------------------------------------------------------
/src/State/SearchState.ts:
--------------------------------------------------------------------------------
1 | import { AllGames } from "../games/GameType";
2 | import { createState } from "./CommonState";
3 | import {
4 | ClassesInUse,
5 | GloomhavenItemSlot,
6 | ResourceTypes,
7 | SortDirection,
8 | SortProperty,
9 | } from "./Types";
10 |
11 | export const slotsState = createState("slotState", []);
12 | export const resourcesState = createState(
13 | "resourcesState",
14 | []
15 | );
16 | export const availableOnlyState = createState("availableOnly", false);
17 | export const searchState = createState("search", "");
18 | export const selectedClassState = createState(
19 | "selectedClass",
20 | undefined
21 | );
22 | export const sortDirectionState = createState(
23 | "sortDirection",
24 | SortDirection.ascending
25 | );
26 | export const sortPropertyState = createState(
27 | "sortProperty",
28 | SortProperty.Id
29 | );
30 | export const removingGameState = createState(
31 | "removingGame",
32 | undefined
33 | );
34 |
--------------------------------------------------------------------------------
/src/State/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./GameTypeState";
2 | export * from "./SearchState";
3 | export * from "./PopupStatusState";
4 | export * from "./SpoilerState";
5 | export * from "./Types";
6 |
--------------------------------------------------------------------------------
/src/components/Firebase/config.ts:
--------------------------------------------------------------------------------
1 | export const firebaseConfig = {
2 | apiKey: "AIzaSyDZjS32Jz6XHm56VxxvUyJHmdgEPFBvyU4",
3 | authDomain: "gloomhaven-item-db-db.firebaseapp.com",
4 | databaseURL: "https://gloomhaven-item-db-db.firebaseio.com",
5 | projectId: "gloomhaven-item-db-db",
6 | storageBucket: "gloomhaven-item-db-db.appspot.com",
7 | messagingSenderId: "1055959523566",
8 | appId: "1:1055959523566:web:c9e9dd38591be00ea493ff",
9 | };
10 |
--------------------------------------------------------------------------------
/src/components/Firebase/index.tsx:
--------------------------------------------------------------------------------
1 | export * from "./FirebaseProvider";
2 |
--------------------------------------------------------------------------------
/src/components/GameSelector.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState } from "recoil";
3 | import { DropdownProps, Form } from "semantic-ui-react";
4 | import { gameDataTypes, GameType } from "../games";
5 | import { gameInfo } from "../games/GameInfo";
6 | import { gameTypeState } from "../State";
7 |
8 | export const GameSelector = () => {
9 | const [gameType, setGameType] = useRecoilState(gameTypeState);
10 |
11 | const options: any[] = [];
12 | Object.values(gameDataTypes).forEach((gameData) => {
13 | const { gameType: value } = gameData;
14 | const text = gameInfo[value].title;
15 | options.push({ text, value });
16 | });
17 |
18 | return (
19 | {
23 | setGameType(e.value as GameType);
24 | }}
25 | />
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/src/components/Tabs/About.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const About = () => {
4 | return (
5 | <>
6 |
7 | Thanks for using the Gloomhaven Item Database. If you find any
8 | issues with the content or bugs with the db you can{" "}
9 |
14 | report issues here
15 |
16 | .
17 |
18 | >
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/src/components/Tabs/Account/Account.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Form } from "semantic-ui-react";
3 | import { SignedInAccount } from "./SignedInAccount";
4 | import { SignedOutAccount } from "./SignedOutAccount";
5 |
6 | export const Account = () => {
7 | // Create a provider for all this and lock spoiler
8 | return (
9 | <>
10 |
11 |
12 |
13 |
14 | >)
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/src/components/Tabs/Account/PasswordChangeDialog.tsx:
--------------------------------------------------------------------------------
1 | import React, { ChangeEvent, useState } from "react";
2 | import { Button, Form, List, Modal } from "semantic-ui-react";
3 | import { useFirebase } from "../../Firebase";
4 |
5 | type Props = {
6 | isOpen: boolean;
7 | onClose: () => void;
8 | };
9 |
10 | export const PasswordChangeDialog = (props: Props) => {
11 | const { isOpen, onClose } = props;
12 | const { passwordUpdate, error } = useFirebase();
13 | const [passwordOne, setPasswordOne] = useState("");
14 | const [passwordTwo, setPasswordTwo] = useState("");
15 |
16 | const onSubmit = (event: any) => {
17 | passwordUpdate(passwordOne);
18 | event.preventDefault();
19 | };
20 |
21 | const isInvalid = passwordOne !== passwordTwo || passwordOne === "";
22 |
23 | return (
24 |
25 | Change Password
26 |
27 |
55 |
56 |
57 |
60 |
68 |
69 |
70 | );
71 | };
72 |
--------------------------------------------------------------------------------
/src/components/Tabs/Account/SignedInAccount.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button, Form } from "semantic-ui-react";
3 | import { useFirebase } from "../../Firebase";
4 |
5 | export const SignedInAccount = (): JSX.Element | null => {
6 | const { user, signOut } = useFirebase();
7 | if (!user) {
8 | return null;
9 | }
10 | return (
11 | <>
12 | Account
13 | {`Signed in as: ${user.email}`}
14 |
23 | >
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/src/components/Tabs/Account/SignedOutAccount.tsx:
--------------------------------------------------------------------------------
1 | import { EmailAuthProvider, GoogleAuthProvider } from "@firebase/auth";
2 | import React from "react";
3 | import StyledFirebaseAuth from "react-firebaseui/StyledFirebaseAuth";
4 | import { Form } from "semantic-ui-react";
5 | import { auth, useFirebase } from "../../Firebase";
6 |
7 | const uiConfig = {
8 | signInFlow: "popup",
9 | signInOptions: [
10 | {
11 | provider: GoogleAuthProvider.PROVIDER_ID,
12 | customParameters: {
13 | prompt: "select_account",
14 | auth_type: "reauthenticate",
15 | },
16 | },
17 | EmailAuthProvider.PROVIDER_ID,
18 | ],
19 | };
20 |
21 | export const SignedOutAccount = (): JSX.Element | null => {
22 | const { user } = useFirebase();
23 |
24 | if (user) {
25 | return null;
26 | }
27 | return (
28 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Grid/ItemCard.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { GloomhavenItem } from "../../../../../State/Types";
3 | import { Label } from "semantic-ui-react";
4 | import { getItemPath } from "../../../../../games/GameData";
5 | import { GHIcon } from "../../../../Utils";
6 | import { getItemIdString } from "../../../../../helpers";
7 | import { ItemManagementContainer } from "../ItemManagement/ItemManagementContainer";
8 | import { NoItemManagement } from "../ItemManagement/NoItemManagement";
9 |
10 | import "./itemCard.scss";
11 |
12 | type Props = {
13 | item: GloomhavenItem;
14 | };
15 |
16 | const ItemId = (props: Props) => {
17 | const { item } = props;
18 | const id = getItemIdString(item);
19 | return {id}
;
20 | };
21 |
22 | const ItemCard = (props: Props) => {
23 | const { item } = props;
24 |
25 | const [draw, setDraw] = useState(false);
26 | const [showBackside, setShowBackside] = useState(false);
27 |
28 | return (
29 |
30 | {draw && (
31 |
32 |
33 | {item.backDesc && (
34 |
42 | setShowBackside((current) => !current)
43 | }
44 | />
45 | )}
46 |
47 | )}
48 |

setDraw(true)}
52 | className={"item-card"}
53 | />
54 | {draw && (
55 |
56 |
57 |
58 | )}
59 |
60 | );
61 | };
62 |
63 | export default ItemCard;
64 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Grid/ItemGrid.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { GloomhavenItem } from "../../../../../State/Types";
3 | import ItemCard from "./ItemCard";
4 |
5 | import "./itemGrid.scss";
6 |
7 | type Props = {
8 | items: GloomhavenItem[];
9 | };
10 |
11 | export const ItemGrid = (props: Props) => {
12 | const { items } = props;
13 | return (
14 |
15 | {items.map((item) => {
16 | let key = `${item.id}`;
17 | if (item.imageSuffix) {
18 | key += `-${item.imageSuffix}`;
19 | }
20 | return ;
21 | })}
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Grid/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ItemGrid";
2 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Grid/itemCard.scss:
--------------------------------------------------------------------------------
1 | .item-card-container {
2 | max-width: 20%;
3 | aspect-ratio: 0.63;
4 | position: relative;
5 | padding: 2px;
6 | display: flex;
7 | flex-direction: column;
8 |
9 | @media screen and (min-width: 768px) and (max-width: 990px) {
10 | max-width: 25%;
11 | }
12 |
13 |
14 | @media screen and (min-width: 560px) and (max-width: 768px) {
15 | max-width: 33.3333333333333%;
16 | }
17 |
18 | @media screen and (min-width: 390px) and (max-width: 560px) {
19 | max-width: 50%;
20 | }
21 |
22 | @media screen and (max-width: 390px) {
23 | max-width: 100%;
24 | }
25 |
26 |
27 | &-header {
28 | border-radius: 8px 8px 0px 0px;
29 | background: black;
30 | display: flex;
31 | justify-content: space-between;
32 | height: 7%;
33 | z-index: 1; // required because of the negative margin-top on .item-card
34 |
35 | .item-card-id {
36 | font-size: 1.1vh;
37 | font-weight: 700;
38 | width: 50%;
39 | padding-top: 0px;
40 | padding-left: 10px;
41 | color: white;
42 | border-radius: 8px 8px 0px 0px;
43 | }
44 | }
45 |
46 | .item-card {
47 | width: 100%;
48 | // height: auto;
49 | filter: brightness(105%) contrast(90%);
50 | height: 77%;
51 | margin-top: -4%; // you can get away with a bit of negative margins since there's no info you lose on the cards
52 | margin-bottom: -4%;
53 | }
54 |
55 | &-footer {
56 | display: flex;
57 | border-radius: 0px 0px 8px 8px;
58 | background: black;
59 | justify-content: space-between;
60 | margin: 0px;
61 | height: 16%;
62 | position: relative;
63 | }
64 |
65 | .ui.label.no-item-management-count {
66 | font-size: 1.1vh;
67 | width: 100%;
68 | color: white;
69 | display: flex;
70 | justify-content: center;
71 | align-items: center;
72 | border-radius: 0px 0px 8px 8px;
73 | background: transparent;
74 | }
75 | }
76 |
77 | .item-card-wrapper .ui.red.button {
78 | padding: 0;
79 | z-index: 2;
80 | position: absolute;
81 | }
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Grid/itemGrid.scss:
--------------------------------------------------------------------------------
1 | .item-grid {
2 | display: flex;
3 | flex-direction: row;
4 | flex-flow: wrap;
5 | }
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/ItemList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ItemManagementType } from "../../../../State/Types";
3 | import SearchOptions from "./Search/SearchOptions";
4 | import { Message, Icon } from "semantic-ui-react";
5 | import PurchaseItem from "./PurchaseItem";
6 | import { useRecoilValue } from "recoil";
7 | import {
8 | allState,
9 | itemManagementTypeState,
10 | dataMismatchState,
11 | } from "../../../../State";
12 | import { ItemsView } from "./ItemsView";
13 |
14 | export const ItemList = () => {
15 | const all = useRecoilValue(allState);
16 | const itemManagementType = useRecoilValue(itemManagementTypeState);
17 | const dataMismatch = useRecoilValue(dataMismatchState);
18 |
19 | return (
20 | <>
21 | {dataMismatch && (
22 |
23 |
24 |
25 | Data out of sync
26 |
27 | Spoiler configuration differs from cloud storage. Remember
28 | to export your data.
29 |
30 | )}
31 |
32 | {all && (
33 |
34 |
35 |
36 | Spoiler alert
37 |
38 | You are currently viewing all possible items.
39 |
40 | )}
41 |
42 |
43 | {itemManagementType === ItemManagementType.Party && (
44 |
45 | )}
46 | >
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/ItemManagement/ItemManagementContainer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { GloomhavenItem } from "../../../../../State/Types";
3 | import { NoItemManagement } from "./NoItemManagement";
4 | import { PartyItemManagement } from "./PartyItemManagement";
5 | import { SimpleItemManagement } from "./SimpleItemManagement";
6 |
7 | type Props = {
8 | item: GloomhavenItem;
9 | };
10 |
11 | export const ItemManagementContainer = (props: Props) => {
12 | const { item } = props;
13 |
14 | return (
15 | <>
16 |
17 |
18 |
19 | >
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/ItemManagement/NoItemManagement.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilValue } from "recoil";
3 | import { Label } from "semantic-ui-react";
4 | import { itemManagementTypeState } from "../../../../../State";
5 | import { GloomhavenItem, ItemManagementType } from "../../../../../State/Types";
6 |
7 | type Props = {
8 | item: GloomhavenItem;
9 | };
10 |
11 | export const NoItemManagement = (props: Props) => {
12 | const { item } = props;
13 | const itemManagementType = useRecoilValue(itemManagementTypeState);
14 | if (itemManagementType !== ItemManagementType.None) {
15 | return null;
16 | }
17 |
18 | return (
19 |
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/ItemManagement/PartyItemManagement.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilValue, useSetRecoilState } from "recoil";
3 | import { useRemovePlayerUtils } from "../../../../../hooks/useRemovePlayer";
4 | import {
5 | classesInUseState,
6 | itemManagementTypeState,
7 | itemsOwnedByState,
8 | lockSpoilerPanelState,
9 | selectedItemState,
10 | } from "../../../../../State";
11 | import {
12 | ClassesInUse,
13 | GloomhavenItem,
14 | ItemManagementType,
15 | } from "../../../../../State/Types";
16 | import { ClassIcon, GHIcon } from "../../../../Utils";
17 |
18 | import "./partyItemManagement.scss";
19 |
20 | type Props = {
21 | item: GloomhavenItem;
22 | };
23 |
24 | type OwnerProps = {
25 | item: GloomhavenItem;
26 | owner?: ClassesInUse;
27 | };
28 |
29 | const OwnerButton = (props: OwnerProps) => {
30 | const { removeItemsFromOwner } = useRemovePlayerUtils();
31 | const setSelectedItem = useSetRecoilState(selectedItemState);
32 | const lockSpoilerPanel = useRecoilValue(lockSpoilerPanelState);
33 |
34 | const { item, owner } = props;
35 | let classNames = `ownerButton`;
36 | const onClick = () => {
37 | if (owner) {
38 | removeItemsFromOwner(item.id, owner);
39 | } else {
40 | setSelectedItem(item);
41 | }
42 | };
43 |
44 | return (
45 |
59 | );
60 | };
61 |
62 | export const PartyItemManagement = (props: Props) => {
63 | const classesInUse = useRecoilValue(classesInUseState);
64 | const itemsOwnedBy = useRecoilValue(itemsOwnedByState);
65 | const itemManagementType = useRecoilValue(itemManagementTypeState);
66 | const { item } = props;
67 |
68 | if (itemManagementType !== ItemManagementType.Party) {
69 | return null;
70 | }
71 |
72 | if (item.lockToClasses) {
73 | const classesCount = item.lockToClasses.filter((c) =>
74 | classesInUse.includes(c)
75 | ).length;
76 | if (classesCount === 0) {
77 | return null;
78 | }
79 | }
80 | const owners = (itemsOwnedBy && itemsOwnedBy[item.id]) || [];
81 | const ownersLength = owners ? owners.length : 0;
82 | const classesAvailable = ownersLength
83 | ? classesInUse.filter((c) => !owners.includes(c))
84 | : classesInUse;
85 |
86 | const addButtonsToShow =
87 | classesAvailable.length > 0
88 | ? Math.min(item.count - ownersLength, 4)
89 | : 0;
90 | let buttonData: OwnerProps[] = owners.map((owner, index) => ({
91 | owner,
92 | item,
93 | }));
94 | buttonData = buttonData.concat(
95 | [...Array(addButtonsToShow).keys()].map((index) => ({
96 | item,
97 | owner: undefined,
98 | }))
99 | );
100 | return (
101 |
102 | {buttonData.map((data, i) => (
103 |
104 | ))}
105 |
106 | );
107 | };
108 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/ItemManagement/SimpleItemManagement.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState, useRecoilValue } from "recoil";
3 | import { Checkbox } from "semantic-ui-react";
4 | import {
5 | itemManagementTypeState,
6 | itemsInUseCountState,
7 | lockSpoilerPanelState,
8 | } from "../../../../../State";
9 | import { GloomhavenItem, ItemManagementType } from "../../../../../State/Types";
10 |
11 | import "./simpleItemManagement.scss";
12 |
13 | type Props = {
14 | item: GloomhavenItem;
15 | };
16 |
17 | export const SimpleItemManagement = (props: Props) => {
18 | const [itemsInUseCount, setItemsInUseCount] =
19 | useRecoilState(itemsInUseCountState);
20 | const lockSpoilerPanel = useRecoilValue(lockSpoilerPanelState);
21 | const itemManagementType = useRecoilValue(itemManagementTypeState);
22 | const { item } = props;
23 |
24 | if (itemManagementType !== ItemManagementType.Simple) {
25 | return null;
26 | }
27 |
28 | const count = itemsInUseCount[item.id] || 0;
29 |
30 | const onClick = (checked: boolean) => {
31 | setItemsInUseCount((current) => {
32 | const value = Object.assign({}, current);
33 | value[item.id] = (value[item.id] || 0) + (checked ? -1 : 1);
34 | if (value[item.id] === 0) {
35 | delete value[item.id];
36 | }
37 | return value;
38 | });
39 | };
40 |
41 | return (
42 |
43 | {[...Array(item.count).keys()].map((index) => (
44 | onClick(index < count)}
49 | disabled={lockSpoilerPanel}
50 | />
51 | ))}
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/ItemManagement/items.ts:
--------------------------------------------------------------------------------
1 | export * from "./NoItemManagement";
2 | export * from "./ItemManagementContainer";
3 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/ItemManagement/partyItemManagement.scss:
--------------------------------------------------------------------------------
1 | .item-card-container {
2 | .party-management-container {
3 | width: 100%;
4 | height: 100%;
5 | display: flex;
6 | flex-direction: row;
7 |
8 | .ownerButton {
9 | padding: 0;
10 | z-index: 2;
11 | width: 19%;
12 | aspect-ratio: 1;
13 | border-radius: 50%;
14 | border: unset;
15 | margin: 5px;
16 | position: relative;
17 | }
18 | }
19 |
20 | .addIcon {
21 | width: 100%;
22 | height: 100%;
23 | color: black;
24 | padding: 25%;
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | }
29 |
30 | .deleteIcon {
31 | height: 40%;
32 | width: 40%;
33 | position: absolute;
34 | z-index: 3;
35 | border-radius: 50%;
36 | background-color: white;
37 | right: 0;
38 | bottom: 0;
39 | }
40 |
41 | img.ui.image.ownerIcon {
42 | width: 100%;
43 | height: 100%;
44 | opacity: 1;
45 | }
46 |
47 | .flip {
48 | width: auto;
49 | height: 100%;
50 | vertical-align: baseline;
51 | margin-right: 10px;
52 | }
53 | }
54 |
55 | .store-inventory-col {
56 | .party-management-container {
57 | height: 75px;
58 | width: 75px;
59 | display: flex;
60 | flex-direction: column;
61 | flex-wrap: wrap;
62 | justify-content: space-between;
63 | position: relative;
64 | }
65 |
66 | .ownerButton {
67 | padding: 0;
68 | z-index: 2;
69 | width: 35px;
70 | height: 35px;
71 | border-radius: 50%;
72 | border: unset;
73 | position: relative;
74 | }
75 |
76 | img.ui.image.ownerIcon {
77 | width: 100%;
78 | height: 100%;
79 | opacity: 1;
80 | }
81 |
82 | .deleteIcon {
83 | height: 15px;
84 | width: 15px;
85 | position: absolute;
86 | z-index: 3;
87 | border-radius: 50%;
88 | background-color: white;
89 | right: 0;
90 | bottom: 0;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/ItemManagement/simpleItemManagement.scss:
--------------------------------------------------------------------------------
1 | .item-card-container {
2 | .simple-management-container {
3 | width: 100%;
4 | position: absolute;
5 | display: flex;
6 | flex-flow: wrap;
7 | align-content: center;
8 | height: 100%;
9 |
10 | .ui.fitted.checkbox{
11 | width: 25%;
12 | justify-content: center;
13 | display: flex;
14 | left: -7px;
15 | }
16 | }
17 | }
18 |
19 | .store-inventory-col {
20 | .simple-management-container {
21 | .ui.fitted.checkbox{
22 | width: 50%;
23 | padding: 0 5px;
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/ItemsView.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilValue } from "recoil";
3 | import { Message } from "semantic-ui-react";
4 | import useItems from "../../../../hooks/useItems";
5 | import { displayItemAsState } from "../../../../State";
6 | import { ItemViewDisplayType } from "../../../../State/Types";
7 | import { ItemGrid } from "./Grid";
8 | import { ItemTable } from "./Table";
9 |
10 | export const ItemsView = () => {
11 | const items = useItems();
12 | const displayAs = useRecoilValue(displayItemAsState);
13 | return (
14 | <>
15 | {items.length === 0 && (
16 |
17 | No items found matching your filters and/or search criteria
18 |
19 | )}
20 |
21 | {displayAs === ItemViewDisplayType.List ? (
22 |
23 | ) : (
24 |
25 | )}
26 | >
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Search/Discount.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilValue } from "recoil";
3 | import { Form } from "semantic-ui-react";
4 | import {
5 | discountState,
6 | displayItemAsState,
7 | ItemViewDisplayType,
8 | } from "../../../../../State";
9 |
10 | export const Discount = () => {
11 | const discount = useRecoilValue(discountState);
12 | const displayAs = useRecoilValue(displayItemAsState);
13 |
14 | if (displayAs !== ItemViewDisplayType.Images) {
15 | return null;
16 | }
17 | return (
18 |
19 |
20 | {`${discount}g`}
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Search/FilterAvailability.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState, useRecoilValue } from "recoil";
3 | import { Button, Form } from "semantic-ui-react";
4 | import {
5 | availableOnlyState,
6 | ItemManagementType,
7 | itemManagementTypeState,
8 | } from "../../../../../State";
9 |
10 | type Props = {
11 | available: boolean;
12 | text: string;
13 | };
14 |
15 | const FilterAvailabilityButton = (props: Props) => {
16 | const [availableOnly, setAvailableOnly] =
17 | useRecoilState(availableOnlyState);
18 | const { available, text } = props;
19 | return (
20 |
28 | );
29 | };
30 |
31 | export const FilterAvailability = () => {
32 | const itemManagementType = useRecoilValue(itemManagementTypeState);
33 | if (itemManagementType === ItemManagementType.None) {
34 | return null;
35 | }
36 | return (
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Search/FilterClass.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState, useRecoilValue } from "recoil";
3 | import { Form } from "semantic-ui-react";
4 | import {
5 | classesInUseState,
6 | itemManagementTypeState,
7 | selectedClassState,
8 | } from "../../../../../State";
9 | import { ClassesInUse, ItemManagementType } from "../../../../../State/Types";
10 | import { ClassList } from "../../../SpoilerFilters/Party/ClassList";
11 |
12 | export const FilterClass = () => {
13 | const itemManagementType = useRecoilValue(itemManagementTypeState);
14 | const classesInUse = useRecoilValue(classesInUseState);
15 | const [selectedClass, setSelectedClass] =
16 | useRecoilState(selectedClassState);
17 |
18 | if (itemManagementType !== ItemManagementType.Party) {
19 | return null;
20 | }
21 | return (
22 |
23 | {
27 | if (selectedClass === option) {
28 | setSelectedClass(undefined);
29 | } else {
30 | setSelectedClass(option);
31 | }
32 | }}
33 | isUsed={(options: ClassesInUse) => selectedClass === options}
34 | />
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Search/FilterResources.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 | import { useRecoilState, useRecoilValue } from "recoil";
3 | import { Form } from "semantic-ui-react";
4 | import { gameDataState, resourcesState } from "../../../../../State";
5 | import { ResourceTypes } from "../../../../../State/Types";
6 | import { GHIcon } from "../../../../Utils";
7 |
8 | export const FilterResorces = () => {
9 | const [resources, setResourcesState] = useRecoilState(resourcesState);
10 | const { resources: gameResource } = useRecoilValue(gameDataState);
11 |
12 | const setFilterResource = useCallback(
13 | (resource?: ResourceTypes) => {
14 | if (!resource) {
15 | setResourcesState([]);
16 | return;
17 | }
18 | const value = Object.assign([], resources);
19 | const index = value.indexOf(resource);
20 | if (index !== -1) {
21 | value.splice(index, 1);
22 | } else {
23 | value.push(resource);
24 | }
25 | setResourcesState(value);
26 | },
27 | [resources, setResourcesState]
28 | );
29 |
30 | if (!gameResource || gameResource.length === 0) {
31 | return null;
32 | }
33 | return (
34 |
35 |
36 | setFilterResource(undefined)}
40 | />
41 | {gameResource &&
42 | gameResource.map((resource) => (
43 |
50 | }
51 | checked={resources.includes(resource as ResourceTypes)}
52 | onChange={() =>
53 | setFilterResource(resource as ResourceTypes)
54 | }
55 | alt={resource}
56 | />
57 | ))}
58 |
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Search/FilterSlots.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 | import { useRecoilState, useRecoilValue } from "recoil";
3 | import { Form } from "semantic-ui-react";
4 | import { gameDataState, slotsState } from "../../../../../State";
5 | import { GloomhavenItemSlot } from "../../../../../State/Types";
6 | import { GHIcon } from "../../../../Utils";
7 |
8 | export const FilterSlots = () => {
9 | const [slots, setSlotsState] = useRecoilState(slotsState);
10 | const { filterSlots } = useRecoilValue(gameDataState);
11 | const setFilterSlot = useCallback(
12 | (slot?: GloomhavenItemSlot) => {
13 | if (!slot) {
14 | setSlotsState([]);
15 | return;
16 | }
17 | const value = Object.assign([], slots);
18 | const index = value.indexOf(slot);
19 | if (index !== -1) {
20 | value.splice(index, 1);
21 | } else {
22 | value.push(slot);
23 | }
24 | setSlotsState(value);
25 | },
26 | [slots, setSlotsState]
27 | );
28 | return (
29 |
30 |
31 | setFilterSlot(undefined)}
35 | />
36 | {filterSlots.map((itemSlot) => (
37 |
44 | }
45 | checked={slots.includes(itemSlot)}
46 | onChange={() => setFilterSlot(itemSlot)}
47 | alt={itemSlot}
48 | />
49 | ))}
50 |
51 | );
52 | };
53 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Search/FindItemSearchBar.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState } from "recoil";
3 | import { Form, Input } from "semantic-ui-react";
4 | import { searchState } from "../../../../../State";
5 |
6 | export const FindItemSearchBar = () => {
7 | const [searchString, setSearchString] = useRecoilState(searchState);
8 | return (
9 |
10 |
11 | {
14 | setSearchString(e.target.value);
15 | }}
16 | icon={{
17 | name: "close",
18 | link: true,
19 | onClick: () => setSearchString(""),
20 | }}
21 | placeholder={"Search..."}
22 | />
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Search/RenderAs.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState } from "recoil";
3 | import { Button, Form } from "semantic-ui-react";
4 | import { displayItemAsState } from "../../../../../State";
5 | import { ItemViewDisplayType } from "../../../../../State/Types";
6 |
7 | type Props = {
8 | type: ItemViewDisplayType;
9 | text: string;
10 | };
11 | const RenderAsButton = (props: Props) => {
12 | const { type, text } = props;
13 | const [displayAs, setDisplayAs] = useRecoilState(displayItemAsState);
14 | return (
15 |
23 | );
24 | };
25 |
26 | export const RenderAs = () => {
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
37 |
38 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Search/SearchOptions.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Form, Segment } from "semantic-ui-react";
3 | import {
4 | RenderAs,
5 | FilterSlots,
6 | FilterResorces,
7 | FindItemSearchBar,
8 | FilterClass,
9 | FilterAvailability,
10 | SortItems,
11 | Discount,
12 | } from ".";
13 |
14 | const SearchOptions = () => {
15 | return (
16 |
28 | );
29 | };
30 |
31 | export default SearchOptions;
32 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Search/SortItems.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 | import { useRecoilState, useRecoilValue } from "recoil";
3 | import { Button, Form, Icon } from "semantic-ui-react";
4 | import { useSetSorting } from "../../../../../hooks/useSetSorting";
5 | import {
6 | displayItemAsState,
7 | ItemViewDisplayType,
8 | SortDirection,
9 | sortDirectionState,
10 | SortProperty,
11 | sortPropertyState,
12 | } from "../../../../../State";
13 |
14 | export const SortItems = () => {
15 | const setSorting = useSetSorting();
16 |
17 | const displayAs = useRecoilValue(displayItemAsState);
18 | const sortProperty = useRecoilValue(sortPropertyState);
19 | const [sortDirection, setSortDirection] =
20 | useRecoilState(sortDirectionState);
21 |
22 | const toggleSortDirection = useCallback(() => {
23 | setSortDirection(
24 | sortDirection === SortDirection.ascending
25 | ? SortDirection.descending
26 | : SortDirection.ascending
27 | );
28 | }, [setSortDirection, sortDirection]);
29 |
30 | if (displayAs !== ItemViewDisplayType.Images) {
31 | return null;
32 | }
33 |
34 | return (
35 | <>
36 |
37 |
38 | setSorting(e.value as SortProperty)}
49 | />
50 |
59 | }
60 | checked={sortDirection === SortDirection.ascending}
61 | onClick={() => toggleSortDirection()}
62 | />
63 |
64 | >
65 | );
66 | };
67 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Search/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./FilterClass";
2 | export * from "./FilterResources";
3 | export * from "./FilterSlots";
4 | export * from "./FindItemSearchBar";
5 | export * from "./RenderAs";
6 | export * from "./FilterAvailability";
7 | export * from "./SortItems";
8 | export * from "./Discount";
9 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Table/ItemCost.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilValue } from "recoil";
3 | import { Divider } from "semantic-ui-react";
4 | import { GameType } from "../../../../../games";
5 | import { formatId } from "../../../../../helpers";
6 | import {
7 | discountState,
8 | gameTypeState,
9 | GloomhavenItem,
10 | } from "../../../../../State";
11 | import { GHIcon } from "../../../../Utils";
12 |
13 | interface Props {
14 | item: GloomhavenItem;
15 | showDiscount?: boolean;
16 | hideDivider?: boolean;
17 | }
18 |
19 | export const ItemCost = (props: Props) => {
20 | const {
21 | item: { cost, resources, id },
22 | showDiscount,
23 | hideDivider
24 | } = props;
25 | const discount = useRecoilValue(discountState);
26 | const gameType = useRecoilValue(gameTypeState);
27 |
28 | const costClass = discount < 0 ? "blue" : discount > 0 ? "red" : "";
29 |
30 | if (!cost && (!resources || Object.entries(resources).length === 0)) {
31 | return -;
32 | }
33 |
34 | return (
35 | <>
36 | {cost > 0 && (
37 |
38 | {showDiscount? `${cost + discount}g (${discount}g)` : `${cost + discount}g`}
39 |
40 | )}
41 | {gameType === GameType.Frosthaven &&
42 | resources &&
43 | Object.entries(resources).map(([resource, value], index) => {
44 | if (resource === "item") {
45 | return value.map(
46 | (itemId: number, itemIndex: number) => (
47 |
48 | <>
49 | {itemIndex > 0 &&
}
50 |
54 | {` : ${formatId(itemId)}`}
55 | >
56 |
57 | )
58 | );
59 | }
60 | if (resource === "any") {
61 | return value.map((count: number, anyIndex: number) => (
62 |
63 | {anyIndex > 0 &&
}
64 | {`any herb ${count > 1 ? `x ${count}` : ""}`}
65 |
66 | ));
67 | }
68 | return (
69 |
70 | {!hideDivider && index > 0 &&
}
71 | <>
72 |
76 | {value > 1 && ` x ${value}`}
77 | >
78 |
79 | );
80 | })}
81 | >
82 | );
83 | };
84 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Table/ItemSummon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Summon } from "../../../../../State/Types";
3 | import { GHIcon } from "../../../../Utils";
4 |
5 | type Props = {
6 | summon: Summon;
7 | };
8 |
9 | export const ItemSummon = (props: Props) => {
10 | const {
11 | summon: { hp, move, attack, range, desc },
12 | } = props;
13 | return (
14 |
15 |
16 | : {hp}
17 |
18 |
19 | : {move}
20 |
21 |
22 | : {attack}
23 |
24 |
25 | : {range || "-"}
26 |
27 | {desc && (
28 |
`,
31 | }}
32 | />
33 | )}
34 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Table/ItemTable.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | GloomhavenItem,
4 | ItemManagementType,
5 | SortProperty,
6 | } from "../../../../../State/Types";
7 | import { Table } from "semantic-ui-react";
8 | import { useRecoilValue } from "recoil";
9 | import {
10 | sortPropertyState,
11 | sortDirectionState,
12 | discountState,
13 | itemManagementTypeState,
14 | } from "../../../../../State";
15 | import { ItemTableRow } from "./ItemTableRow";
16 | import { useSetSorting } from "../../../../../hooks/useSetSorting";
17 |
18 | import "./itemTable.scss";
19 |
20 | type Props = {
21 | items: GloomhavenItem[];
22 | };
23 |
24 | export const ItemTable = (props: Props) => {
25 | const sortProperty = useRecoilValue(sortPropertyState);
26 | const sortDirection = useRecoilValue(sortDirectionState);
27 | const itemManagementType = useRecoilValue(itemManagementTypeState);
28 | const { items } = props;
29 | const discount = useRecoilValue(discountState);
30 | const setSorting = useSetSorting();
31 |
32 | const costClass = discount < 0 ? "blue" : discount > 0 ? "red" : "";
33 |
34 | const getCostTitle = () => {
35 | let cost = "Cost";
36 | if (discount !== 0) {
37 | cost += ` (${discount}g)`;
38 | }
39 |
40 | return {cost};
41 | };
42 |
43 | return (
44 |
45 |
46 |
47 | setSorting(SortProperty.Id)}
51 | sorted={
52 | sortProperty === SortProperty.Id
53 | ? sortDirection
54 | : undefined
55 | }
56 | >
57 | #
58 |
59 | setSorting(SortProperty.Name)}
63 | sorted={
64 | sortProperty === SortProperty.Name
65 | ? sortDirection
66 | : undefined
67 | }
68 | >
69 | Name
70 |
71 | setSorting(SortProperty.Slot)}
75 | sorted={
76 | sortProperty === SortProperty.Slot
77 | ? sortDirection
78 | : undefined
79 | }
80 | >
81 | Slot
82 |
83 | setSorting(SortProperty.Cost)}
88 | sorted={
89 | sortProperty === SortProperty.Cost
90 | ? sortDirection
91 | : undefined
92 | }
93 | >
94 | {getCostTitle()}
95 |
96 | setSorting(SortProperty.Use)}
99 | sorted={
100 | sortProperty === SortProperty.Use
101 | ? sortDirection
102 | : undefined
103 | }
104 | >
105 | Use
106 |
107 |
108 | Effect
109 |
110 |
111 | Source
112 |
113 |
114 | {itemManagementType !== ItemManagementType.None
115 | ? "In Use"
116 | : "Stock"}
117 |
118 |
119 |
120 |
121 | {items.map((item) => {
122 | return (
123 |
127 | );
128 | })}
129 |
130 |
131 | );
132 | };
133 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Table/ItemTableRow.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Table } from "semantic-ui-react";
3 | import { getItemIdString } from "../../../../../helpers";
4 | import { GloomhavenItem } from "../../../../../State/Types";
5 | import { GHIcon } from "../../../../Utils";
6 | import { ItemManagementContainer } from "../ItemManagement/ItemManagementContainer";
7 | import { NoItemManagement } from "../ItemManagement/NoItemManagement";
8 | import { ItemCost } from "./ItemCost";
9 | import { ItemText } from "./ItemText";
10 |
11 | type Props = {
12 | item: GloomhavenItem;
13 | };
14 |
15 | export const ItemTableRow = (props: Props) => {
16 | const {
17 | item,
18 | item: { id, name, slot, spent, consumed, lost, source },
19 | } = props;
20 |
21 | return (
22 |
23 |
24 | {getItemIdString(item)}
25 |
26 | {name}
27 |
28 | {slot && (
29 |
30 | )}
31 |
32 |
33 |
34 |
35 |
36 | {spent && }
37 | {consumed && }
38 | {lost && }
39 |
40 |
41 |
42 |
43 |
44 | {source.split("\n").map((s) => (
45 |
46 |
51 |
52 |
53 | ))}
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | );
62 | };
63 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Table/ItemText.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Icon, Popup } from "semantic-ui-react";
3 | import { GameType } from "../../../../../games";
4 | import { GloomhavenItem } from "../../../../../State";
5 | import { GHIcon } from "../../../../Utils";
6 | import { ConsumptionPanel } from "./ConsumptionPanel";
7 | import { ItemSummon } from "./ItemSummon";
8 |
9 | interface Props {
10 | item: GloomhavenItem;
11 | }
12 |
13 | const numberAmountToText = ["zero", "one", "two", "three", "four", "five"];
14 |
15 | export const ItemText = (props: Props) => {
16 | const {
17 | item: {
18 | gameType,
19 | descHTML,
20 | backDescHTML,
21 | minusOneCardsAdded,
22 | faq,
23 | faqImage,
24 | summon,
25 | consumption,
26 | },
27 | } = props;
28 |
29 | return (
30 | <>
31 | {backDescHTML ? (
32 | <>
33 | Front: ${descHTML}
`,
36 | }}
37 | />
38 | Back: ${backDescHTML}
`,
41 | }}
42 | />
43 | >
44 | ) : (
45 |
50 | )}
51 | {consumption && (
52 |
53 |
54 |
55 | )}
56 | {minusOneCardsAdded && (
57 | <>
58 |
59 |
60 | {`Add ${
61 | minusOneCardsAdded < numberAmountToText.length
62 | ? numberAmountToText[minusOneCardsAdded]
63 | : minusOneCardsAdded
64 | } `}
65 | {gameType === GameType.Frosthaven ? (
66 |
67 | ) : (
68 |
69 | )}
70 |
71 | {` to your attack modifier deck.`}
72 |
73 | >
74 | )}
75 | {faq && (
76 | }
80 | header={"FAQ"}
81 | content={faq}
82 | />
83 | )}
84 | {faqImage && (
85 | }
89 | header={"FAQ"}
90 | content={
91 |
96 | }
97 | />
98 | )}
99 | {summon && }
100 | >
101 | );
102 | };
103 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Table/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ItemTable";
2 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/Table/itemTable.scss:
--------------------------------------------------------------------------------
1 | .store-inventory-col {
2 | .ui.label.no-item-management-count {
3 | width: 100%;
4 | background: white;
5 | }
6 | }
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/Items/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ItemList";
2 |
--------------------------------------------------------------------------------
/src/components/Tabs/MainView/MainView.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from "react";
2 | import { Tab } from "semantic-ui-react";
3 | import { ItemList } from "./Items";
4 | import SpoilerFilters from "../SpoilerFilters/SpoilerFilters";
5 | import Share from "../Share/Share";
6 | import ImportData from "./ImportData";
7 | import { isFlagEnabled } from "../../../helpers";
8 | import { Account } from "../Account/Account";
9 | import { About } from "../About";
10 | import { useRecoilValue } from "recoil";
11 | import {
12 | allState,
13 | dataMismatchState,
14 | lockSpoilerPanelState,
15 | } from "../../../State";
16 |
17 | // .git ignore vscode file
18 | // List mode looks crappy
19 | // Buying uses the first element even if nothing is selected
20 | // Look at Object.assign calls
21 |
22 | type TabItem = {
23 | menuItem: string;
24 | component: JSX.Element;
25 | shouldShow: boolean;
26 | };
27 |
28 | const MainView = () => {
29 | const all = useRecoilValue(allState);
30 | const lockSpoilerPanel = useRecoilValue(lockSpoilerPanelState);
31 | const dataMismatch = useRecoilValue(dataMismatchState);
32 |
33 | const sharingEnabled = isFlagEnabled("sharing");
34 |
35 | const tabData: TabItem[] = [
36 | { menuItem: "Item List", component: , shouldShow: true },
37 | {
38 | menuItem: "Spoiler Configuration",
39 | component: ,
40 | shouldShow: !lockSpoilerPanel,
41 | },
42 | {
43 | menuItem: "Share",
44 | component: ,
45 | shouldShow: !lockSpoilerPanel,
46 | },
47 | {
48 | menuItem: "Account",
49 | component: ,
50 | shouldShow: !lockSpoilerPanel && sharingEnabled,
51 | },
52 | { menuItem: "About", component: , shouldShow: true },
53 | ];
54 | const panes = tabData
55 | .map(({ shouldShow, component, menuItem }) => {
56 | if (shouldShow) {
57 | return {
58 | menuItem,
59 | render: () => {component},
60 | };
61 | }
62 | return {};
63 | })
64 | .filter((pane) => pane.menuItem !== undefined);
65 |
66 | const outlineClass = useMemo(() => {
67 | if (all || dataMismatch) {
68 | return "spoiler";
69 | }
70 | return "";
71 | }, [all, dataMismatch]);
72 |
73 | return (
74 | <>
75 |
76 |
77 |
84 | localStorage.setItem(
85 | "lastTab",
86 | data.activeIndex ? data.activeIndex.toString() : "0"
87 | )
88 | }
89 | />
90 |
91 |
92 | Gloomhaven and all related properties, images and text are owned
93 | by{" "}
94 |
99 | Cephalofair Games
100 |
101 | .
102 |
103 | >
104 | );
105 | };
106 |
107 | export default MainView;
108 |
--------------------------------------------------------------------------------
/src/components/Tabs/Share/Share.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState } from "react";
2 | import { Form, Message, Icon } from "semantic-ui-react";
3 | import { isFlagEnabled } from "../../../helpers";
4 | import { getShareHash } from "../MainView/ImportData";
5 | import UploadForm from "./UploadForm";
6 |
7 | const Share = () => {
8 | const [lockSpoilerPanel, setLockSpoilerPanel] = useState(false);
9 | const shareEnabled = isFlagEnabled("sharing");
10 |
11 | const configHash = useCallback(() => {
12 | return getShareHash(lockSpoilerPanel);
13 | }, [lockSpoilerPanel]);
14 |
15 | const shareUrl =
16 | `${window.location.origin}${window.location.pathname}?lastGame=${localStorage.getItem("lastGame")}#${configHash()}`;
17 | return (
18 | <>
19 |
20 | Here you can generate a link to this app with your current spoiler
21 | configuration.
22 |
23 |
24 |
28 | setLockSpoilerPanel(!lockSpoilerPanel)}
34 | />
35 |
36 | {lockSpoilerPanel && (
37 |
38 |
39 | Do not open the link yourself or you will not be able to change any
40 | settings anymore
41 |
42 | )}
43 |
45 |
46 | {
49 | (
50 | document.getElementById("share-url-input") as HTMLInputElement
51 | ).select();
52 | document.execCommand("copy");
53 | }}
54 | >
55 | Copy
56 |
57 |
58 | {shareEnabled && }
59 |
60 | >
61 | );
62 | };
63 |
64 | export default Share;
65 |
--------------------------------------------------------------------------------
/src/components/Tabs/Share/UploadForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useRecoilValue } from "recoil";
3 | import { Form } from "semantic-ui-react";
4 | import { dataMismatchState } from "../../../State";
5 | import { useFirebase } from "../../Firebase";
6 |
7 | type Props = {
8 | configHash: string;
9 | };
10 |
11 | const UploadForm = (props: Props) => {
12 | const { configHash } = props;
13 | const [error, setError] = useState(undefined);
14 | const { user, exportData } = useFirebase();
15 | const dataMismatch = useRecoilValue(dataMismatchState);
16 |
17 | if (!user || user.isAnonymous) {
18 | return null;
19 | }
20 |
21 | const authUserId = user && !user.isAnonymous && user.uid;
22 |
23 | const shareUrl =
24 | `${window.location.origin}${window.location.pathname}?importFrom=${authUserId}&lastGame=${localStorage.getItem("lastGame")}`;
25 |
26 | return (
27 | <>
28 |
29 | Here you can generate a link to your account that others can export the
30 | data at any time.
31 |
32 |
33 |
34 | {
37 | (
38 | document.getElementById("export-url-input") as HTMLInputElement
39 | ).select();
40 | document.execCommand("copy");
41 | }}
42 | >
43 | Copy
44 |
45 |
46 |
47 | {user && !user.isAnonymous && (
48 | exportData(configHash)}>
49 | Export
50 |
51 | )}
52 | {dataMismatch && (
53 |
54 | Spoiler configuration differs from cloud storage. Remember to export
55 | your data.
56 |
57 | )}
58 |
59 | {error && {error.message}}
60 | >
61 | );
62 | };
63 |
64 | export default UploadForm;
65 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Common/ConfirmSpecialUnlockPanel.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode, useState } from "react";
2 | import {
3 | Button,
4 | ListItem,
5 | List,
6 | Modal,
7 | Input,
8 | InputProps,
9 | } from "semantic-ui-react";
10 | import { useRecoilState } from "recoil";
11 | import {
12 | confirmSpecialUnlockOpenState,
13 | specialUnlocksState,
14 | SpecialUnlockTypes,
15 | } from "../../../../State";
16 |
17 | type Props = {
18 | solutions: string[];
19 | specialUnlockType: SpecialUnlockTypes;
20 | children: ReactNode | ReactNode[];
21 | title: string;
22 | onConfirm?: () => {};
23 | };
24 |
25 | export const ConfirmSpecialUnlockPanel = (props: Props) => {
26 | const { solutions, specialUnlockType, children, title } = props;
27 | const [solutionCorrect, setSolutionCorrect] = useState(false);
28 | const [confirmSpecialUnlockOpen, setConfirmSpecialUnlockOpen] =
29 | useRecoilState(confirmSpecialUnlockOpenState);
30 | const [specialUnlocks, setSpecialUnlocks] =
31 | useRecoilState(specialUnlocksState);
32 |
33 | const onClose = () => {
34 | setConfirmSpecialUnlockOpen(undefined);
35 | };
36 |
37 | const checkSolution = (_e: any, data: InputProps) => {
38 | const solution = data.value || "";
39 | setSolutionCorrect(solutions.includes(btoa(solution.toLowerCase())));
40 | };
41 |
42 | const onApply = () => {
43 | if (solutionCorrect) {
44 | if (!specialUnlocks.includes(specialUnlockType)) {
45 | setSpecialUnlocks((current) => {
46 | return [...current, specialUnlockType];
47 | });
48 | }
49 | }
50 | onClose();
51 | };
52 |
53 | return (
54 |
59 | {title}
60 |
61 |
62 | {children}
63 |
64 |
65 |
66 |
67 |
68 |
69 |
72 |
80 |
81 |
82 | );
83 | };
84 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Common/SpecialUnlockButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilValue, useSetRecoilState } from "recoil";
3 | import { Button } from "semantic-ui-react";
4 | import { AllGames } from "../../../../games/GameType";
5 | import {
6 | specialUnlocksState,
7 | includeGameState,
8 | confirmSpecialUnlockOpenState,
9 | SpecialUnlockTypes,
10 | } from "../../../../State";
11 |
12 | type Props = {
13 | specialUnlockType: SpecialUnlockTypes;
14 | gameType: AllGames;
15 | text: string;
16 | };
17 |
18 | export const SpecialUnlocksButton = (props: Props) => {
19 | const { specialUnlockType, gameType, text } = props;
20 | const includeGames = useRecoilValue(includeGameState);
21 | const specialUnlocks = useRecoilValue(specialUnlocksState);
22 | const specialUnlocked = specialUnlocks.includes(specialUnlockType);
23 | const setConfirmSpecialUnlockOpen = useSetRecoilState(
24 | confirmSpecialUnlockOpenState
25 | );
26 | if (specialUnlocked || !includeGames.includes(gameType)) {
27 | return null;
28 | }
29 |
30 | return (
31 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Games/ConfirmGameRemoval.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button, Modal, Form, List } from "semantic-ui-react";
3 | import { useRecoilState } from "recoil";
4 | import { removingGameState, includeGameState } from "../../../../State";
5 | import { useRemovePlayerUtils } from "../../../../hooks/useRemovePlayer";
6 | import { gameInfo } from "../../../../games/GameInfo";
7 |
8 | export const ConfirmGameRemoval = () => {
9 | const { removeClasses, getClassesToRemove, getRemovingItemCount } =
10 | useRemovePlayerUtils();
11 | const [removingGame, setRemovingGame] = useRecoilState(removingGameState);
12 | const [includeGames, setIncludeGames] = useRecoilState(includeGameState);
13 |
14 | const onClose = () => {
15 | setRemovingGame(undefined);
16 | };
17 |
18 | const onApply = () => {
19 | if (!removingGame) {
20 | return;
21 | }
22 | // Go through all classes and see if any of them are used..
23 | // if so then remove their ownership
24 | const classesToRemove = getClassesToRemove(removingGame);
25 | if (classesToRemove.length > 0) {
26 | removeClasses(classesToRemove, removingGame);
27 | }
28 |
29 | // Remove the game
30 | const value = Object.assign([], includeGames);
31 | value.splice(value.indexOf(removingGame), 1);
32 | setIncludeGames(value);
33 | onClose();
34 | };
35 |
36 | if (!removingGame) {
37 | return null;
38 | }
39 |
40 | const { title } = gameInfo[removingGame];
41 |
42 | return (
43 |
44 | {`Remove ${title}`}
45 |
46 | {`Remove ${title} from the db?`}
48 | {removingGame && (
49 |
50 |
51 | Confirming this will:
52 | {getClassesToRemove(removingGame).length >
53 | 0 && (
54 |
55 | {`Remove this game's classes from the
56 | party`}
57 |
58 | )}
59 | {getRemovingItemCount(removingGame) > 0 && (
60 |
61 | {`Put any items owned by this game's
62 | classes back into available inventory`}
63 |
64 | )}
65 |
66 |
67 | )}
68 |
69 |
70 |
71 |
74 |
81 |
82 |
83 | );
84 | };
85 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Games/GameFilterCheckbox.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
3 | import { Form } from "semantic-ui-react";
4 | import { GameInfo } from "../../../../games/GameInfo";
5 | import { AllGames } from "../../../../games/GameType";
6 | import { useRemovePlayerUtils } from "../../../../hooks/useRemovePlayer";
7 | import {
8 | gameTypeState,
9 | includeGameState,
10 | removingGameState,
11 | } from "../../../../State";
12 |
13 | type Props = {
14 | allGameType: AllGames;
15 | } & GameInfo;
16 |
17 | export const GameFilterCheckbox = (props: Props) => {
18 | const { getClassesToRemove, getRemovingItemCount } = useRemovePlayerUtils();
19 | const { allGameType, title, gamesToFilterOn } = props;
20 | const [includeGames, setIncludeGames] = useRecoilState(includeGameState);
21 | const gameType = useRecoilValue(gameTypeState);
22 | const setRemovingGame = useSetRecoilState(removingGameState);
23 |
24 | const showConfirmation = (removingGame: AllGames) => {
25 | return (
26 | getClassesToRemove(removingGame).length ||
27 | getRemovingItemCount(removingGame) > 0
28 | );
29 | };
30 |
31 | const toggleItemFilter = (key: AllGames) => {
32 | const value = Object.assign([], includeGames);
33 | if (value.includes(key)) {
34 | if (showConfirmation(key)) {
35 | setRemovingGame(key);
36 | } else {
37 | value.splice(value.indexOf(key), 1);
38 | }
39 | } else {
40 | value.push(key);
41 | }
42 | setIncludeGames(value);
43 | };
44 |
45 | if (gamesToFilterOn && gamesToFilterOn.includes(gameType)) {
46 | return null;
47 | }
48 | return (
49 | toggleItemFilter(allGameType)}
54 | />
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Games/GameFilters.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Form, Icon, Popup, Segment } from "semantic-ui-react";
3 | import { gameInfo } from "../../../../games/GameInfo";
4 | import { AllGames } from "../../../../games/GameType";
5 | import { useGameSort } from "../../../../games/useGameSort";
6 | import { GameFilterCheckbox } from "./GameFilterCheckbox";
7 | import { GameHelp } from "./GameHelp";
8 |
9 | export const GameFilters = () => {
10 | const { withoutCurrent } = useGameSort();
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
}
21 | header={"Game Types"}
22 | content={
}
23 | />
24 |
25 | {withoutCurrent.map((gameType) => {
26 | const gi = gameInfo[gameType];
27 | return (
28 |
33 | );
34 | })}
35 |
36 |
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Games/GameHelp.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilValue } from "recoil";
3 | import { Form, List } from "semantic-ui-react";
4 | import { gameInfo, GameInfo } from "../../../../games/GameInfo";
5 | import { AllGames } from "../../../../games/GameType";
6 | import { useGameSort } from "../../../../games/useGameSort";
7 | import { gameTypeState } from "../../../../State";
8 |
9 | const constructHelpEntry = (
10 | title: string,
11 | gameType: AllGames,
12 | { addItemsToGames, gameClasses, soloGameType }: GameInfo
13 | ) => {
14 | const soloGameTitle = soloGameType ? gameInfo[soloGameType].title : undefined;
15 | return (
16 |
17 | {title}
18 |
19 | {gameClasses().length > 0 && (
20 | Add classes to party management
21 | )}
22 | {addItemsToGames && addItemsToGames.includes(gameType) && (
23 | Add Items for use
24 | )}
25 | {soloGameTitle && (
26 | {`Add solo scenario items for ${soloGameTitle}`}
27 | )}
28 |
29 |
30 | );
31 | };
32 |
33 | export const GameHelp = () => {
34 | const currentGameType = useRecoilValue(gameTypeState);
35 | const { withoutCurrent } = useGameSort();
36 |
37 | return (
38 |
39 |
40 | Which Games/Expansions are you playing with?
41 | {withoutCurrent.map((gameType) => {
42 | const gi = gameInfo[gameType];
43 | const { title, gamesToFilterOn } = gi;
44 | if (
45 | !gamesToFilterOn ||
46 | (gamesToFilterOn && !gamesToFilterOn.includes(currentGameType))
47 | ) {
48 | return constructHelpEntry(title, gameType, gi);
49 | }
50 | return null;
51 | })}
52 |
53 |
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Games/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./GameFilters";
2 | export * from "./ConfirmGameRemoval";
3 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/BuildingLevelFilter.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState, useRecoilValue } from "recoil";
3 | import { Form } from "semantic-ui-react";
4 | import { AllGames } from "../../../../games/GameType";
5 | import { buildingLevelState, includeGameState } from "../../../../State";
6 |
7 | export interface BuildingLevelFilterProps {
8 | label: string;
9 | lockedLabel?: string;
10 | startBuildingLevel?: number;
11 | endBuildingLevel?: number;
12 | buildingKey: string;
13 | gameType?: AllGames;
14 | }
15 |
16 | export const BuildingLevelFilter = (props: BuildingLevelFilterProps) => {
17 | const {
18 | label,
19 | lockedLabel,
20 | startBuildingLevel,
21 | endBuildingLevel,
22 | buildingKey,
23 | gameType,
24 | } = props;
25 | const includedGames = useRecoilValue(includeGameState);
26 | const [buildingLevels, setBuildingLevel] =
27 | useRecoilState(buildingLevelState);
28 |
29 | if (gameType && !includedGames.includes(gameType)) {
30 | return null;
31 | }
32 |
33 | const currentLevel = buildingLevels[buildingKey];
34 | let min = startBuildingLevel || 1;
35 | let max = endBuildingLevel || min;
36 | if (currentLevel < 0) {
37 | max = 0;
38 | }
39 |
40 | const levels = [];
41 | for (let i = min; i <= max; i++) {
42 | levels.push(i);
43 | }
44 |
45 | if (lockedLabel) {
46 | levels.unshift(...[-1, 0]);
47 | }
48 |
49 | const buttons = levels.map((i) => {
50 | let radioLabel = i.toString();
51 | if (i === -1) {
52 | radioLabel = currentLevel === -1 ? "Locked" : "Lock";
53 | } else if (i === 0) {
54 | radioLabel = currentLevel === -1 ? "Unlock" : "Unbuilt";
55 | }
56 | return (
57 |
62 | setBuildingLevel((current) => ({
63 | ...current,
64 | [buildingKey]: i,
65 | }))
66 | }
67 | />
68 | );
69 | });
70 |
71 | return (
72 |
73 |
74 | {buttons}
75 |
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/FilterCheckbox.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState } from "recoil";
3 | import { Form } from "semantic-ui-react";
4 | import { itemState } from "../../../../State";
5 |
6 | type Props = {
7 | id: number;
8 | offset?: number;
9 | prefix?: string;
10 | };
11 |
12 | const FilterCheckbox = (props: Props) => {
13 | const { id, offset = 0, prefix = "" } = props;
14 | const [item, setItem] = useRecoilState(itemState);
15 |
16 | const toggleItemFilter = (key: number) => {
17 | const value = Object.assign([], item);
18 | if (value.includes(key)) {
19 | value.splice(value.indexOf(key), 1);
20 | } else {
21 | value.push(key);
22 | }
23 | setItem(value);
24 | };
25 |
26 | return (
27 | toggleItemFilter(id + offset)}
32 | />
33 | );
34 | };
35 |
36 | export default FilterCheckbox;
37 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/GHSpoilerFilter.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Segment } from "semantic-ui-react";
3 | import SpoilerFilterItemList from "./SpoilerFilterItemList";
4 | import { useRecoilValue } from "recoil";
5 | import { prosperityState } from "../../../../State";
6 | import { Expansions } from "../../../../games/GameType";
7 | import { ReputationPulldown } from "./ReputationPulldown";
8 | import { ProsperityFilter } from "./ProsperityFilter";
9 | import { SoloClassFilterBlock } from "./SoloClassFilterBlock";
10 |
11 | export const GHSpoilerFilter = () => {
12 | const prosperity = useRecoilValue(prosperityState);
13 |
14 | return (
15 |
16 |
17 |
18 |
19 |
29 |
33 |
37 |
42 |
61 |
82 |
97 |
98 |
99 |
100 |
101 | );
102 | };
103 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/GameFilter.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilValue } from "recoil";
3 | import { GameType } from "../../../../games";
4 | import { gameDataState } from "../../../../State";
5 | import { FHSpoilerFilter } from "./FHSpoilerFilter";
6 | import { GHSpoilerFilter } from "./GHSpoilerFilter";
7 | import { JOTLSpoilerFilter } from "./JOTLSpoilerFilter";
8 |
9 | const filters = {
10 | [GameType.Gloomhaven]: ,
11 | [GameType.JawsOfTheLion]: ,
12 | [GameType.Frosthaven]: ,
13 | };
14 |
15 | export const GameFilter = () => {
16 | const { gameType } = useRecoilValue(gameDataState);
17 |
18 | return <>{filters[gameType]}>;
19 | };
20 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/JOTLSpoilerFilter.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SpoilerFilterItemList, { ItemRange } from "./SpoilerFilterItemList";
3 | import { ScenarioCompletedFilter } from "./ScenarioCompletedFilter";
4 | import { useRecoilValue } from "recoil";
5 | import { scenarioCompletedState } from "../../../../State";
6 | import { Segment } from "semantic-ui-react";
7 |
8 | const scenariosOfImportance = [2, 9, 15];
9 | export const JOTLSpoilerFilter = () => {
10 | const scenariosComplete = useRecoilValue(scenarioCompletedState);
11 | const ranges: ItemRange[] = [];
12 | if (!scenariosComplete.includes(2)) {
13 | ranges.push({ range: [{ start: 1, end: 13 }] });
14 | }
15 | ranges.push({ range: [14] });
16 | if (!scenariosComplete.includes(9)) {
17 | ranges.push({ range: [{ start: 15, end: 20 }] });
18 | }
19 | if (!scenariosComplete.includes(15)) {
20 | ranges.push({ range: [{ start: 21, end: 26 }] });
21 | }
22 | ranges.push({ range: [{ start: 27, end: 36 }] });
23 |
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/ProsperityFilter.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState } from "recoil";
3 | import { Form } from "semantic-ui-react";
4 | import { prosperityState } from "../../../../State";
5 |
6 | export const ProsperityFilter = () => {
7 | const [prosperity, setProsperity] = useRecoilState(prosperityState);
8 | return (
9 |
10 |
11 | {[...Array(9).keys()].map((index) => {
12 | const nextProsperity = index + 1;
13 | return (
14 | setProsperity(nextProsperity)}
19 | />
20 | );
21 | })}
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/ReputationPulldown.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState } from "recoil";
3 | import { Form } from "semantic-ui-react";
4 | import { discountState } from "../../../../State";
5 |
6 | export const ReputationPulldown = () => {
7 | const [discount, setDiscount] = useRecoilState(discountState);
8 | return (
9 |
10 |
11 | {
27 | setDiscount(parseInt(e.value as string));
28 | }}
29 | />
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/ScenarioCompletedFilter.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState } from "recoil";
3 | import { Form } from "semantic-ui-react";
4 | import { scenarioCompletedState } from "../../../../State";
5 |
6 | type Props = {
7 | scenarios: number[];
8 | };
9 |
10 | export const ScenarioCompletedFilter = (props: Props) => {
11 | const { scenarios } = props;
12 | const [scenarioCompleted, setScenarioComplete] = useRecoilState(
13 | scenarioCompletedState
14 | );
15 |
16 | const toggleScenarioCompleted = (key: number) => {
17 | const value = Object.assign([], scenarioCompleted);
18 | if (value.includes(key)) {
19 | value.splice(value.indexOf(key), 1);
20 | } else {
21 | value.push(key);
22 | }
23 | setScenarioComplete(value);
24 | };
25 |
26 | if (scenarios.length === 0) {
27 | return null;
28 | }
29 |
30 | return (
31 |
32 |
33 | {scenarios.map((id) => {
34 | return (
35 | toggleScenarioCompleted(id)}
40 | />
41 | );
42 | })}
43 |
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/SoloClassFilter.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState, useRecoilValue } from "recoil";
3 | import { Form } from "semantic-ui-react";
4 | import { gameInfo } from "../../../../games/GameInfo";
5 | import { AllGames } from "../../../../games/GameType";
6 | import { useRemovePlayerUtils } from "../../../../hooks/useRemovePlayer";
7 | import { includeGameState, soloClassState } from "../../../../State";
8 | import { ClassesInUse } from "../../../../State/Types";
9 | import { ClassList } from "../Party/ClassList";
10 |
11 | type Props = {
12 | gameType: AllGames;
13 | };
14 |
15 | export const SoloClassFilter = (props: Props) => {
16 | const { gameType } = props;
17 | const { soloGameType, title } = gameInfo[gameType];
18 | const [soloClass, setSoloClass] = useRecoilState(soloClassState);
19 | const { getClassesForGame } = useRemovePlayerUtils();
20 | const includeGames = useRecoilValue(includeGameState);
21 | if (!includeGames.includes(gameType)) {
22 | return null;
23 | }
24 | const classes = getClassesForGame(soloGameType || gameType);
25 | const soloGameTitle = soloGameType
26 | ? gameInfo[soloGameType].title
27 | : undefined;
28 |
29 | const toggleClassFilter = (key: ClassesInUse) => {
30 | const value = Object.assign([], soloClass);
31 | if (value.includes(key)) {
32 | value.splice(value.indexOf(key), 1);
33 | } else {
34 | value.push(key);
35 | }
36 | setSoloClass(value);
37 | };
38 | return (
39 |
40 |
41 |
43 | soloClass.includes(className)
44 | }
45 | label={soloGameTitle || title}
46 | classes={classes}
47 | onClick={toggleClassFilter}
48 | />
49 |
50 |
51 | );
52 | };
53 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/SoloClassFilterBlock.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilValue } from "recoil";
3 | import { Form, Segment } from "semantic-ui-react";
4 | import { gameInfo } from "../../../../games/GameInfo";
5 | import { gameTypeState, includeGameState } from "../../../../State";
6 | import { SoloClassFilter } from "./SoloClassFilter";
7 |
8 | export const SoloClassFilterBlock = () => {
9 | const currentGameType = useRecoilValue(gameTypeState);
10 | const { soloClassesToInclude } = gameInfo[currentGameType];
11 | const includeGames = useRecoilValue(includeGameState);
12 | const includeList =
13 | soloClassesToInclude &&
14 | soloClassesToInclude.filter((gameType) =>
15 | includeGames.includes(gameType)
16 | );
17 | if (!includeList || includeList.length === 0) {
18 | return null;
19 | }
20 |
21 | return (
22 |
23 |
24 |
25 |
26 | {includeList.map((gameType) => (
27 |
31 | ))}
32 |
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/SpoilerFilterItemList.tsx:
--------------------------------------------------------------------------------
1 | import { isNumber } from "lodash";
2 | import React, { useMemo } from "react";
3 | import { useRecoilState, useRecoilValue } from "recoil";
4 | import { Form } from "semantic-ui-react";
5 | import { AllGames } from "../../../../games/GameType";
6 | import { isLocalHost } from "../../../../helpers";
7 | import { includeGameState, itemState } from "../../../../State";
8 | import FilterCheckbox from "./FilterCheckbox";
9 |
10 | type Range = {
11 | start: number;
12 | end?: number;
13 | };
14 |
15 | export type ItemRange = {
16 | range: (Range | number)[];
17 | offset?: number;
18 | prefix?: string;
19 | };
20 |
21 | type Props = {
22 | ranges: ItemRange[];
23 | title: string;
24 | filterOn?: AllGames;
25 | };
26 |
27 | const peformAll = (
28 | ranges: ItemRange[],
29 | callback: (
30 | i: number,
31 | offset: number | undefined,
32 | prefix: string | undefined
33 | ) => void
34 | ) => {
35 | ranges.forEach(({ range, offset, prefix }: ItemRange) => {
36 | range.forEach((r) => {
37 | let first;
38 | let last;
39 | if (isNumber(r)) {
40 | first = r;
41 | last = r;
42 | } else {
43 | const { start, end } = r as Range;
44 | first = start;
45 | last = end || start;
46 | }
47 | for (let i = first; i <= last; i++) {
48 | callback(i, offset, prefix);
49 | }
50 | });
51 | });
52 | };
53 |
54 | const SpoilerFilterItemList = (props: Props) => {
55 | const { ranges, title, filterOn } = props;
56 | const includeGames = useRecoilValue(includeGameState);
57 | const [item, setItem] = useRecoilState(itemState);
58 |
59 | const turnThemOn = useMemo(() => {
60 | let offCount = 0;
61 | peformAll(ranges, (i: number, offset: number | undefined) => {
62 | if (!item.includes(i + (offset || 0))) {
63 | offCount++;
64 | }
65 | });
66 | return offCount > 0;
67 | }, [ranges, item]);
68 |
69 | if (filterOn && !includeGames.includes(filterOn)) {
70 | return null;
71 | }
72 |
73 | const toggleAll = () => {
74 | if (!isLocalHost) {
75 | return;
76 | }
77 | const value = Object.assign([], item);
78 | const toggleItemFilter = (key: number) => {
79 | const isOn = value.includes(key);
80 | if (turnThemOn) {
81 | if (!isOn) {
82 | value.push(key);
83 | }
84 | } else {
85 | if (isOn) {
86 | value.splice(value.indexOf(key), 1);
87 | }
88 | }
89 | };
90 | peformAll(ranges, (i: number, offset: number | undefined) => {
91 | toggleItemFilter(i + (offset || 0));
92 | });
93 |
94 | setItem(value);
95 | };
96 |
97 | const checkBoxes: Array = [];
98 | peformAll(
99 | ranges,
100 | (i: number, offset: number | undefined, prefix: string | undefined) => {
101 | checkBoxes.push(
102 |
108 | );
109 | }
110 | );
111 | if (checkBoxes.length === 0) {
112 | return null;
113 | }
114 |
115 | return (
116 |
117 |
118 |
119 | {title && }
120 | {isLocalHost && }
121 |
122 | {checkBoxes}
123 |
124 |
125 | );
126 | };
127 |
128 | export default SpoilerFilterItemList;
129 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/ToggleAllButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState } from "recoil";
3 | import { Button, Form, Icon } from "semantic-ui-react";
4 | import { allState } from "../../../../State";
5 |
6 | export const ToggleAllButton = () => {
7 | const [all, setAll] = useRecoilState(allState);
8 |
9 | return (
10 |
11 |
12 |
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Items/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ToggleAllButton";
2 | export * from "./GameFilter";
3 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Party/ClassList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Form } from "semantic-ui-react";
3 | import { ClassesInUse } from "../../../../State/Types";
4 | import { ClassIcon } from "../../../Utils";
5 |
6 | type Props = {
7 | label: string;
8 | classes: ClassesInUse[];
9 | onClick: (className: ClassesInUse) => void;
10 | isEnabled?: (className: ClassesInUse) => boolean;
11 | isUsed: (className: ClassesInUse) => boolean;
12 | };
13 |
14 | export const ClassList = (props: Props) => {
15 | const { label, classes, onClick, isEnabled, isUsed } = props;
16 |
17 | return (
18 |
19 |
20 | {classes.map((name) => {
21 | return (
22 |
30 | );
31 | })}
32 |
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Party/ConfirmClassDelete.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | Button,
4 | Modal,
5 | Form,
6 | Accordion,
7 | AccordionTitle,
8 | AccordionContent,
9 | Icon,
10 | } from "semantic-ui-react";
11 | import { ClassIcon } from "../../../Utils";
12 | import { useRecoilState, useRecoilValue } from "recoil";
13 | import { classToDeleteState, gameDataState } from "../../../../State";
14 | import { useRemovePlayerUtils } from "../../../../hooks/useRemovePlayer";
15 | import { itemGoldValue, OwnedItemList } from "./OwnedItemsList";
16 | import { useIsItemShown } from "../../../../hooks/useIsItemShown";
17 |
18 | export const ConfirmClassDelete = () => {
19 | const { removeClasses, itemsOwnedByClass } = useRemovePlayerUtils();
20 | const isItemShown = useIsItemShown();
21 | const [classToDelete, setClassToDelete] =
22 | useRecoilState(classToDeleteState);
23 | const [itemsOpen, setItemsOpen] = useState(false);
24 |
25 | const { items } = useRecoilValue(gameDataState);
26 |
27 | const onClose = () => {
28 | setClassToDelete(undefined);
29 | };
30 |
31 | const itemsOwned = itemsOwnedByClass(classToDelete);
32 | const itemsToList = itemsOwned
33 | .map((id) => items[id - 1])
34 | .filter(isItemShown);
35 |
36 | const goldAmount = () => {
37 | let totalGold = 0;
38 | itemsToList.forEach((item) => {
39 | totalGold += itemGoldValue(item);
40 | });
41 | return totalGold;
42 | };
43 |
44 | const onApply = () => {
45 | if (classToDelete) {
46 | removeClasses(classToDelete);
47 | setClassToDelete(undefined);
48 | }
49 | onClose();
50 | };
51 |
52 | return (
53 |
54 |
55 | Remove Class
56 | {classToDelete && (
57 |
61 | )}
62 |
63 |
64 | Remove this class from the party?
66 | {itemsOwned.length > 0 && (
67 | <>
68 |
69 |
70 | Warning: This will remove all the items
71 | assigned to this character.
72 |
73 |
74 |
75 |
76 | {`If you are retiring this character, they would get `}
77 | {`${goldAmount()}g`}
83 | {` for their items`}
84 |
85 |
86 |
87 |
90 | setItemsOpen((current) => !current)
91 | }
92 | >
93 |
94 | {`Items Owned - ${
95 | itemsOpen
96 | ? "Click to hide"
97 | : "Click to show"
98 | } items`}
99 |
100 |
101 |
105 |
106 |
107 | >
108 | )}
109 |
110 |
111 |
112 |
115 |
122 |
123 |
124 | );
125 | };
126 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Party/OwnedItemsList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Form,
4 | Table,
5 | TableHeader,
6 | TableHeaderCell,
7 | TableBody,
8 | TableRow,
9 | TableCell,
10 | Popup,
11 | Icon,
12 | } from "semantic-ui-react";
13 | import { getItemPath } from "../../../../games/GameData";
14 | import { getItemIdString } from "../../../../helpers";
15 | import { GloomhavenItem } from "../../../../State";
16 |
17 | type Props = {
18 | items: GloomhavenItem[];
19 | totalGold: string;
20 | };
21 |
22 | export const itemGoldValue = (item: GloomhavenItem) => {
23 | let totalGold = 0;
24 | const { cost, resources } = item;
25 | if (cost) {
26 | totalGold += Math.floor(cost / 2);
27 | }
28 | if (resources) {
29 | Object.entries(resources).forEach(([type, resource]) => {
30 | if (type === "item") {
31 | totalGold += resource.length * 2;
32 | } else if (type === "any") {
33 | resource.forEach((amount: number) => (totalGold += amount * 2));
34 | } else {
35 | totalGold += resource * 2;
36 | }
37 | });
38 | }
39 | return totalGold;
40 | };
41 |
42 | export const OwnedItemList = (props: Props) => {
43 | const { items, totalGold } = props;
44 |
45 | return (
46 |
47 |
48 |
49 |
50 | Id
51 | Item
52 | Cost
53 |
54 |
55 |
56 | {items.map((item) => {
57 | const { name } = item;
58 | const totalCost = itemGoldValue(item);
59 | const costStr = totalCost ? `${totalCost}g` : "-";
60 | return (
61 |
62 |
63 | {getItemIdString(item)}
64 |
65 |
66 |
67 | }
71 | content={
72 |
77 | }
78 | />
79 |
80 |
81 |
82 |
83 | {costStr}
84 |
85 |
86 | );
87 | })}
88 |
89 | Total
90 |
91 | {totalGold}
92 |
93 |
94 |
95 |
96 | );
97 | };
98 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Party/PartyManagementFilter.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilState } from "recoil";
3 | import { Form, Popup, Icon, List } from "semantic-ui-react";
4 | import { itemManagementTypeState } from "../../../../State";
5 | import { ItemManagementType } from "../../../../State/Types";
6 |
7 | const PartyManagementFilter = () => {
8 | const [itemManagementType, setItemManagementType] = useRecoilState(
9 | itemManagementTypeState
10 | );
11 |
12 | const onChangeItemManagement = (value: ItemManagementType) => {
13 | setItemManagementType(value);
14 | };
15 |
16 | return (
17 |
18 |
19 |
20 |
21 |
26 | }
27 | header={"Stock Management"}
28 | content={
29 |
30 |
31 | None
32 |
33 | No management
34 |
35 |
36 |
37 | Simple
38 |
39 |
40 | Indicate that an item has been
41 | purchased.
42 |
43 |
44 |
45 |
46 | Party
47 |
48 |
49 | Indicate which member of your party
50 | has the item
51 |
52 |
53 |
54 |
55 | }
56 | />
57 |
58 | {Object.entries(ItemManagementType).map(([key, value]) => {
59 | return (
60 | onChangeItemManagement(value)}
65 | />
66 | );
67 | })}
68 |
69 |
70 | );
71 | };
72 |
73 | export default PartyManagementFilter;
74 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Party/PartySpoiler.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRecoilValue } from "recoil";
3 | import { Form, Popup, Icon, Segment } from "semantic-ui-react";
4 | import { gameInfo } from "../../../../games/GameInfo";
5 | import { AllGames } from "../../../../games/GameType";
6 | import { useGameSort } from "../../../../games/useGameSort";
7 | import { itemManagementTypeState } from "../../../../State";
8 | import { ItemManagementType } from "../../../../State/Types";
9 | import PartyManagementFilter from "./PartyManagementFilter";
10 | import { PartySpoilerList } from "./PartySpoilerList";
11 |
12 | export const PartySpoiler = () => {
13 | const itemManagementType = useRecoilValue(itemManagementTypeState);
14 | const { allGames } = useGameSort();
15 |
16 | return (
17 |
18 |
19 | {itemManagementType === ItemManagementType.Party && (
20 |
21 |
22 |
23 |
24 |
32 | }
33 | header={"Party Members"}
34 | content={
35 | "Click on a class icon to add that class to you party. You can then assign items to any members in a party. Clicking on member a second time will remove all items."
36 | }
37 | />
38 |
39 |
40 | {allGames.map((gameType) => {
41 | const gi = gameInfo[gameType];
42 | const hasClasses = gi.gameClasses().length > 0;
43 | if (!hasClasses) {
44 | return null;
45 | }
46 | return (
47 |
51 | );
52 | })}
53 |
54 | )}
55 |
56 | );
57 | };
58 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Party/PartySpoilerList.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from "react";
2 | import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
3 | import { gameInfo } from "../../../../games/GameInfo";
4 | import { AllGames } from "../../../../games/GameType";
5 | import { useRemovePlayerUtils } from "../../../../hooks/useRemovePlayer";
6 | import {
7 | classesInUseState,
8 | classToDeleteState,
9 | gameTypeState,
10 | includeGameState,
11 | } from "../../../../State";
12 | import { ClassesInUse } from "../../../../State/Types";
13 | import { ClassList } from "./ClassList";
14 | type Props = {
15 | type: AllGames;
16 | };
17 |
18 | export const PartySpoilerList = (props: Props) => {
19 | const { type } = props;
20 | const gameType = useRecoilValue(gameTypeState);
21 | const includeGames = useRecoilValue(includeGameState);
22 | const [classesInUse, setClassesInUse] = useRecoilState(classesInUseState);
23 | const setClassToDelete = useSetRecoilState(classToDeleteState);
24 | const { getClassesForGame } = useRemovePlayerUtils();
25 |
26 | const isGameIncluded = () => {
27 | return gameType === type || includeGames.includes(type);
28 | };
29 |
30 | const toggleClassFilter = (key: ClassesInUse) => {
31 | if (classesInUse.includes(key)) {
32 | setClassToDelete(key);
33 | } else {
34 | const newClassesInUse = Object.assign([], classesInUse);
35 | newClassesInUse.push(key);
36 | setClassesInUse(newClassesInUse);
37 | }
38 | };
39 |
40 | const classes = useMemo(
41 | () => getClassesForGame(type),
42 | [getClassesForGame, type]
43 | );
44 |
45 | if (!isGameIncluded()) {
46 | return null;
47 | }
48 | const { title } = gameInfo[type];
49 |
50 | return (
51 |
56 | classesInUse.includes(className)
57 | }
58 | />
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/Party/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ConfirmClassDelete";
2 | export * from "./PartySpoiler";
3 |
--------------------------------------------------------------------------------
/src/components/Tabs/SpoilerFilters/SpoilerFilters.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Form } from "semantic-ui-react";
3 | import { ToggleAllButton, GameFilter } from "./Items";
4 | import { PartySpoiler, ConfirmClassDelete } from "./Party";
5 | import { GameFilters, ConfirmGameRemoval } from "./Games";
6 | import { Secrets } from "./Secrets/Secrets";
7 |
8 | const SpoilerFilters = () => {
9 | return (
10 |
19 | );
20 | };
21 |
22 | export default SpoilerFilters;
23 |
--------------------------------------------------------------------------------
/src/components/Utils/ClassDropdown.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Dropdown, DropdownProps } from "semantic-ui-react";
3 | import { ClassesInUse } from "../../State/Types";
4 | import { ClassIcon } from "./ClassIcon";
5 |
6 | type Props = {
7 | className?: string;
8 | optionsList: ClassesInUse[];
9 | onChange: (option: ClassesInUse) => void;
10 | disabled?: boolean;
11 | };
12 |
13 | const ClassDropdown = (props: Props) => {
14 | const { optionsList, onChange } = props;
15 | return (
16 | ,
19 | data: DropdownProps
20 | ) => onChange(data.value as ClassesInUse)}
21 | placeholder="Choose Class"
22 | clearable
23 | selection
24 | options={optionsList.map((option) => {
25 | return {
26 | key: option,
27 | value: option,
28 | image: ,
29 | };
30 | })}
31 | />
32 | );
33 | };
34 |
35 | export default ClassDropdown;
36 |
--------------------------------------------------------------------------------
/src/components/Utils/ClassIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Image } from "semantic-ui-react";
3 | import {
4 | ClassesInUse,
5 | CSClasses,
6 | CSAClasses,
7 | TOAClasses,
8 | } from "../../State/Types";
9 | type Props = {
10 | name: ClassesInUse | undefined;
11 | className?: string;
12 | onClick?: (name: ClassesInUse) => void;
13 | };
14 |
15 | export const getWorldHavenClass = (
16 | name: string,
17 | game: string,
18 | gamePrefix: string
19 | ) => {
20 | return require(`../../../worldhaven/images/tokens/${game}/character-tokens/${gamePrefix}-${name}-token.png`);
21 | };
22 |
23 | export const getCSClass = (name: string) => {
24 | return getWorldHavenClass(name, "crimson-scales", "cs");
25 | };
26 |
27 | export const getTOAClass = (name: string) => {
28 | return getWorldHavenClass(name, "trail-of-ashes", "toa");
29 | };
30 |
31 | const classIconFolder: Record = {
32 | [CSClasses.CS1]: getCSClass("bombard"),
33 | [CSClasses.CS2]: getCSClass("brightspark"),
34 | [CSClasses.CS3]: getCSClass("chainguard"),
35 | [CSClasses.CS4]: getCSClass("chieftain"),
36 | [CSClasses.CS5]: getCSClass("fire-knight"),
37 | [CSClasses.CS6]: getCSClass("hierophant"),
38 | [CSClasses.CS7]: getCSClass("hollowpact"),
39 | [CSClasses.CS8]: getCSClass("luminary"),
40 | [CSClasses.CS9]: getCSClass("mirefoot"),
41 | [CSClasses.CS10]: getCSClass("spirit-caller"),
42 | [CSClasses.CS11]: getCSClass("starslinger"),
43 | [CSAClasses.CSA1]: getCSClass("amber-aegis"),
44 | [CSAClasses.CSA2]: getCSClass("artificer"),
45 | [CSAClasses.CSA3]: getCSClass("ruinmaw"),
46 | [TOAClasses.TOA1]: getTOAClass("incarnate"),
47 | [TOAClasses.TOA2]: getTOAClass("rimehearth"),
48 | [TOAClasses.TOA3]: getTOAClass("shardrender"),
49 | [TOAClasses.TOA4]: getTOAClass("tempest"),
50 | [TOAClasses.TOA5]: getTOAClass("thornreaper"),
51 | [TOAClasses.TOA6]: getTOAClass("vanquisher"),
52 | };
53 |
54 | export const getClassIcon = (name: string) => {
55 | let classPath = classIconFolder[name];
56 | if (!classPath) {
57 | try {
58 | classPath = require(`../../img/class-tokens/${name.toLowerCase()}.png`);
59 | } catch {
60 | classPath = require(`../../img/classes/${name}.png`);
61 | }
62 | }
63 | return classPath;
64 | };
65 |
66 | export const ClassIcon = (props: Props) => {
67 | const { name, className = "soloClass", onClick } = props;
68 | if (!name) {
69 | return null;
70 | }
71 | const classPath = getClassIcon(name);
72 | return (
73 | onClick && onClick(name)}
78 | />
79 | );
80 | };
81 |
--------------------------------------------------------------------------------
/src/components/Utils/GHIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | type Props = {
4 | name: string;
5 | className?: string;
6 | folder?: string;
7 | onClick?: () => void;
8 | style?: any;
9 | };
10 |
11 | export const GHIcon = (props: Props) => {
12 | const {
13 | name,
14 | folder = "general",
15 | onClick,
16 | className = "icon",
17 | style,
18 | } = props;
19 |
20 | let filename = name;
21 | let src;
22 | if (filename.startsWith("wfh-")) {
23 | filename = filename.substring(1);
24 | src = require(`../../../worldhaven/images/tokens/frosthaven/${folder}/${filename}`);
25 | } else {
26 | src = require(`../../img/icons/${folder}/${name}`);
27 | }
28 |
29 | return (
30 |
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/src/components/Utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./GHIcon";
2 | export * from "./ClassIcon";
3 |
--------------------------------------------------------------------------------
/src/constants/routes.ts:
--------------------------------------------------------------------------------
1 | export const SIGN_UP = '/signup';
2 | export const SIGN_IN = '/signin';
3 | export const HOME = '/';
4 | export const ACCOUNT = '/account';
5 | export const ADMIN = '/admin';
6 | export const PASSWORD_FORGET = '/pw-forget';
7 |
--------------------------------------------------------------------------------
/src/games/GameData.ts:
--------------------------------------------------------------------------------
1 | import { getClassIcon } from "../components/Utils";
2 | import { Helpers } from "../helpers";
3 | import { GloomhavenItem, GloomhavenItemSlot } from "../State/Types";
4 | import { gameInfo } from "./GameInfo";
5 | import { GameType } from "./GameType";
6 |
7 | export type GameData = {
8 | gameType: GameType;
9 | items: GloomhavenItem[];
10 | filterSlots: GloomhavenItemSlot[];
11 | resources?: string[];
12 | };
13 |
14 | const deSpoilerItemSource = (source: string): string => {
15 | return source.replace(/{(.{2,})}/, (m, m1) => {
16 | const classPath = getClassIcon(m1);
17 | return `
`;
18 | });
19 | };
20 |
21 | export const getInitialItems = (gameType: GameType) => {
22 | const items: GloomhavenItem[] = require(`./${gameType}/items.json`);
23 | const filterSlots: GloomhavenItemSlot[] = [];
24 | const resources: string[] = [];
25 |
26 | items.forEach((item) => {
27 | item.descHTML = Helpers.parseEffectText(item.desc);
28 | if (item.backDesc) {
29 | item.backDescHTML = Helpers.parseEffectText(item.backDesc);
30 | }
31 | if (item.summon && item.summon.desc) {
32 | item.summon.desc = Helpers.parseEffectText(item.summon.desc);
33 | }
34 | if (item.consumption && item.consumption.effect) {
35 | item.consumption.effectHtml = Helpers.parseEffectText(
36 | item.consumption.effect
37 | );
38 | }
39 | const source = item.source
40 | .replace(/Reward from /gi, "")
41 | .replace(/ ?\((Treasure #\d+)\)/gi, "\n$1")
42 | .replace(/Solo Scenario #\d+ — /i, "Solo ");
43 | item.source = deSpoilerItemSource(source);
44 | if (item.slot && !filterSlots.includes(item.slot)) {
45 | filterSlots.push(item.slot);
46 | }
47 | if (item.resources) {
48 | Object.keys(item.resources).forEach((resource) => {
49 | if (resource !== "any" && !resources.includes(resource)) {
50 | resources.push(resource);
51 | }
52 | });
53 | }
54 | });
55 | return { items, filterSlots, resources };
56 | };
57 |
58 | const getItemFilename = (item: GloomhavenItem, backside?: boolean) => {
59 | const { folder, name, gameType, id, displayId, imagePrefix, imageSuffix } =
60 | item;
61 | const idToUse = displayId || id.toString();
62 | const { leadingZeros, prefix } = gameInfo[gameType];
63 | const filename = name.toLowerCase().replace(/\s/g, "-").replace(/'/, "");
64 | const itemNumber = `${imagePrefix || ""}${(idToUse + "").padStart(
65 | leadingZeros,
66 | "0"
67 | )}${imageSuffix || ""}`;
68 | const itemFolder = folder ? folder + "/" : "";
69 | return `${itemFolder}${prefix}-${itemNumber}-${filename}${
70 | backside ? "-back" : ""
71 | }.png`;
72 | };
73 |
74 | export const getItemPath = (item: GloomhavenItem, backside?: boolean) => {
75 | const { gameType } = item;
76 | const { folderName } = gameInfo[gameType];
77 | const itemName = getItemFilename(item, backside);
78 | return require(`../../worldhaven/images/items/${folderName}/${itemName}`);
79 | };
80 |
--------------------------------------------------------------------------------
/src/games/GameInfo.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ClassesInUse,
3 | CSAClasses,
4 | CSClasses,
5 | FCClasses,
6 | FHClasses,
7 | GHClasses,
8 | JOTLClasses,
9 | TOAClasses,
10 | } from "../State";
11 | import { AllGames, Expansions, GameType } from "./GameType";
12 |
13 | export interface GameInfo {
14 | addItemsToGames?: AllGames[];
15 | gamesToFilterOn?: AllGames[];
16 | title: string;
17 | folderName: string;
18 | prefix: string;
19 | leadingZeros: number;
20 | gameClasses: () => ClassesInUse[];
21 | soloGameType?: GameType;
22 | linkedGameTypes?: Expansions[];
23 | soloClassesToInclude?: AllGames[];
24 | }
25 |
26 | export const gameInfo: Record = {
27 | [GameType.Gloomhaven]: {
28 | folderName: "gloomhaven",
29 | prefix: "gh",
30 | leadingZeros: 3,
31 | title: "Gloomhaven",
32 | addItemsToGames: [GameType.Frosthaven],
33 | gameClasses: () => Object.values(GHClasses),
34 | linkedGameTypes: [
35 | Expansions.GHSoloScenarios,
36 | Expansions.ForgottenCircles,
37 | Expansions.CrimsonScales,
38 | Expansions.CrimsonScalesAddon,
39 | Expansions.TrailOfAshes,
40 | ],
41 | soloClassesToInclude: [
42 | Expansions.GHSoloScenarios,
43 | Expansions.ForgottenCircles,
44 | Expansions.CrimsonScales,
45 | Expansions.CrimsonScalesAddon,
46 | Expansions.TrailOfAshes,
47 | ],
48 | },
49 | [GameType.JawsOfTheLion]: {
50 | folderName: "jaws-of-the-lion",
51 | prefix: "jl",
52 | leadingZeros: 2,
53 | title: "Jaws of the Lion",
54 | gameClasses: () => Object.values(JOTLClasses),
55 | },
56 | [GameType.Frosthaven]: {
57 | folderName: "frosthaven",
58 | prefix: "fh",
59 | leadingZeros: 3,
60 | title: "Frosthaven",
61 | gameClasses: () => Object.values(FHClasses),
62 | linkedGameTypes: [Expansions.FHSoloScenarios],
63 | soloClassesToInclude: [
64 | Expansions.FHSoloScenarios,
65 | Expansions.GHSoloScenarios,
66 | Expansions.ForgottenCircles,
67 | Expansions.CrimsonScales,
68 | Expansions.CrimsonScalesAddon,
69 | Expansions.TrailOfAshes,
70 | ],
71 | },
72 | [Expansions.ForgottenCircles]: {
73 | folderName: "forgotten-circles",
74 | prefix: "fc",
75 | leadingZeros: 3,
76 | title: "Forgotten Circles",
77 | addItemsToGames: [GameType.Gloomhaven, GameType.Frosthaven],
78 | gameClasses: () => Object.values(FCClasses),
79 | },
80 | [Expansions.CrimsonScales]: {
81 | folderName: "crimson-scales",
82 | prefix: "cs",
83 | leadingZeros: 2,
84 | title: "Crimson Scales",
85 | addItemsToGames: [GameType.Gloomhaven],
86 | gameClasses: () => Object.values(CSClasses),
87 | },
88 | [Expansions.CrimsonScalesAddon]: {
89 | folderName: "crimson-scales",
90 | prefix: "cs",
91 | leadingZeros: 2,
92 | title: "Crimson Scales Addon",
93 | addItemsToGames: [GameType.Gloomhaven],
94 | gameClasses: () => Object.values(CSAClasses),
95 | },
96 | [Expansions.TrailOfAshes]: {
97 | folderName: "trail-of-ashes",
98 | prefix: "toa",
99 | leadingZeros: 2,
100 | title: "Trail of Ashes",
101 | addItemsToGames: [GameType.Gloomhaven],
102 | gameClasses: () => Object.values(TOAClasses),
103 | },
104 | [Expansions.GHSoloScenarios]: {
105 | folderName: "gloomhaven",
106 | prefix: "gh",
107 | leadingZeros: 0,
108 | gamesToFilterOn: [GameType.JawsOfTheLion],
109 | title: "Gloomhaven Solo Scenarios",
110 | gameClasses: () => [],
111 | soloGameType: GameType.Gloomhaven,
112 | },
113 | [Expansions.FHSoloScenarios]: {
114 | folderName: "frosthaven",
115 | prefix: "fh",
116 | leadingZeros: 0,
117 | gamesToFilterOn: [GameType.Gloomhaven, GameType.JawsOfTheLion],
118 | title: "Frosthaven Solo Scenarios",
119 | gameClasses: () => [],
120 | soloGameType: GameType.Frosthaven,
121 | },
122 | };
123 |
--------------------------------------------------------------------------------
/src/games/GameType.ts:
--------------------------------------------------------------------------------
1 | export enum GameType {
2 | Gloomhaven = "gh",
3 | JawsOfTheLion = "jotl",
4 | Frosthaven = "fh",
5 | }
6 |
7 | export enum Expansions {
8 | ForgottenCircles = "fc",
9 | CrimsonScales = "cs",
10 | CrimsonScalesAddon = "csa",
11 | GHSoloScenarios = "ghss",
12 | FHSoloScenarios = "fhss",
13 | TrailOfAshes = "toa",
14 | }
15 |
16 | export type AllGames = GameType | Expansions;
17 |
--------------------------------------------------------------------------------
/src/games/fh/FHGameData.ts:
--------------------------------------------------------------------------------
1 | import { Helpers } from "../../helpers";
2 | import { GloomhavenItem } from "../../State/Types";
3 | import { GameData, getInitialItems } from "../GameData";
4 | import { gameInfo } from "../GameInfo";
5 | import { GameType } from "../GameType";
6 |
7 | export const ghImportSets: number[][] = [
8 | [],
9 | [10, 25, 72, 105, 109, 116],
10 | [21, 37, 53, 93, 94, 106, 115],
11 | [46, 83, 84, 85, 86, 87, 88, 102, 110, 111, 120, 121, 122, 123, 126, 128],
12 | [
13 | 17, 35, 47, 51, 62, 74, 77, 78, 79, 80, 81, 82, 117, 118, 119, 127, 129,
14 | 131,
15 | ],
16 | ];
17 |
18 | export const fcImportSets: number[][] = [
19 | [153, 159, 161],
20 | [154, 155, 157, 163],
21 | ];
22 |
23 | export const sortById = (a: number, b: number) => a - b;
24 |
25 | export const ghItemToImport = ghImportSets
26 | .flatMap((groups) => [...groups])
27 | .sort(sortById);
28 |
29 | export const fcItemToImport = fcImportSets
30 | .flatMap((groups) => [...groups])
31 | .sort(sortById);
32 |
33 | let { items, filterSlots, resources } = getInitialItems(GameType.Frosthaven);
34 | export const ghItemOffset = 1000;
35 |
36 | const { items: ghItems, filterSlots: ghFilterSlots } = getInitialItems(
37 | GameType.Gloomhaven
38 | );
39 |
40 | const getTradingPostLevel = (id: number) => {
41 | const setIndex = ghImportSets.findIndex((set) => set.includes(id));
42 | if (setIndex !== -1) {
43 | return setIndex === 1 ? -1 : setIndex;
44 | }
45 | return undefined;
46 | };
47 |
48 | const getScenarioLevel = (id: number) => {
49 | if (fcImportSets[0].includes(id)) {
50 | return 82;
51 | }
52 | return undefined;
53 | };
54 |
55 | const getEnchancerLevel = (id: number) => {
56 | if (fcImportSets[1].includes(id)) {
57 | return 4;
58 | }
59 | return undefined;
60 | };
61 |
62 | const filteredGhItems = ghItems
63 | .filter(
64 | (item) =>
65 | ghItemToImport.includes(item.id) ||
66 | fcItemToImport.includes(item.id) ||
67 | item.soloItem
68 | )
69 | .map((item: GloomhavenItem) => {
70 | let lockToClasses = undefined;
71 | if (item.soloItem) {
72 | lockToClasses = [item.soloItem];
73 | }
74 | const unlockTradingPostLevel =
75 | getTradingPostLevel(item.id) || Number.MAX_VALUE;
76 | const unlockScenario = getScenarioLevel(item.id) || Number.MAX_VALUE;
77 | const unlockEnhancerLevel =
78 | getEnchancerLevel(item.id) || Number.MAX_VALUE;
79 | return {
80 | ...item,
81 | displayId: item.displayId || item.id.toString(),
82 | id: ghItemOffset + item.id,
83 | unlockProsperity: Number.MAX_VALUE,
84 | unlockTradingPostLevel,
85 | unlockEnhancerLevel,
86 | unlockScenario,
87 | lockToClasses,
88 | source: item.soloItem
89 | ? item.source
90 | : `${gameInfo[item.gameType].title} Import`,
91 | importedItem: true,
92 | };
93 | });
94 |
95 | items = items.concat(filteredGhItems);
96 |
97 | filterSlots = Helpers.uniqueArray(filterSlots.concat(ghFilterSlots));
98 |
99 | export const FHGameData: GameData = {
100 | gameType: GameType.Frosthaven,
101 | items,
102 | filterSlots,
103 | resources,
104 | };
105 |
--------------------------------------------------------------------------------
/src/games/gh/GHGameData.ts:
--------------------------------------------------------------------------------
1 | import { GameData, getInitialItems } from "../GameData";
2 | import { GameType } from "../GameType";
3 |
4 | const { items, filterSlots } = getInitialItems(GameType.Gloomhaven);
5 |
6 | export const GHGameData: GameData = {
7 | gameType: GameType.Gloomhaven,
8 | items,
9 | filterSlots,
10 | };
11 |
--------------------------------------------------------------------------------
/src/games/index.ts:
--------------------------------------------------------------------------------
1 | import { GameType, Expansions } from "./GameType";
2 | import { GHGameData } from "./gh/GHGameData";
3 | import { JOTLGameData } from "./jotl/JOTlGameData";
4 | import { FHGameData } from "./fh/FHGameData";
5 |
6 | const gameDataTypes = {
7 | [GameType.Gloomhaven]: GHGameData,
8 | [GameType.JawsOfTheLion]: JOTLGameData,
9 | [GameType.Frosthaven]: FHGameData,
10 | };
11 |
12 | export { gameDataTypes, GameType, Expansions };
13 |
--------------------------------------------------------------------------------
/src/games/jotl/JOTlGameData.ts:
--------------------------------------------------------------------------------
1 | import { GameData, getInitialItems } from "../GameData";
2 | import { GameType } from "../GameType";
3 |
4 | const { items, filterSlots } = getInitialItems(GameType.JawsOfTheLion);
5 |
6 | export const JOTLGameData: GameData = {
7 | gameType: GameType.JawsOfTheLion,
8 | items,
9 | filterSlots,
10 | };
11 |
--------------------------------------------------------------------------------
/src/games/useGameSort.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from "react";
2 | import { useRecoilValue } from "recoil";
3 | import { gameDataTypes } from ".";
4 | import { gameTypeState } from "../State";
5 | import { gameInfo } from "./GameInfo";
6 |
7 | export const useGameSort = () => {
8 | const currentGameType = useRecoilValue(gameTypeState);
9 |
10 | return useMemo(() => {
11 | const currentGameInfo = gameInfo[currentGameType];
12 | let games = [
13 | currentGameType,
14 | ...(currentGameInfo.linkedGameTypes || []),
15 | ];
16 | Object.values(gameDataTypes).forEach((gameData) => {
17 | const { gameType } = gameData;
18 | if (!games.includes(gameType)) {
19 | const gameSpecificInfo = gameInfo[gameType];
20 | games.push(
21 | gameType,
22 | ...(gameSpecificInfo.linkedGameTypes || [])
23 | );
24 | }
25 | });
26 | return {
27 | allGames: games,
28 | withoutCurrent: games.filter((game) => game !== currentGameType),
29 | };
30 | }, [currentGameType]);
31 | };
32 |
--------------------------------------------------------------------------------
/src/hooks/useItems.tsx:
--------------------------------------------------------------------------------
1 | import { GloomhavenItem, SortDirection, SortProperty } from "../State/Types";
2 | import { useRecoilValue } from "recoil";
3 | import { gameDataState, sortPropertyState, sortDirectionState } from "../State";
4 | import { useCallback, useEffect, useState } from "react";
5 | import { useIsItemShown } from "./useIsItemShown";
6 | import { useGameSort } from "../games/useGameSort";
7 |
8 | export function compareItems(a: T, b: T) {
9 | if (a === b) {
10 | return 0;
11 | } else {
12 | return a > b ? 1 : -1;
13 | }
14 | }
15 |
16 | const getItemUse = ({ consumed, spent, lost }: GloomhavenItem) => {
17 | if (spent) {
18 | return "a";
19 | }
20 | if (consumed) {
21 | return "b";
22 | }
23 | if (lost) {
24 | return "c";
25 | }
26 | return "d";
27 | };
28 |
29 | const useItems = (): Array => {
30 | const { items } = useRecoilValue(gameDataState);
31 | const isItemShown = useIsItemShown();
32 | const sortProperty = useRecoilValue(sortPropertyState);
33 | const sortDirection = useRecoilValue(sortDirectionState);
34 | const { allGames } = useGameSort();
35 |
36 | const [sortedItems, setSortedItems] = useState([]);
37 | const [filteredItems, setFilteredItems] = useState([]);
38 |
39 | const doSort = useCallback(
40 | (itemA: GloomhavenItem, itemB: GloomhavenItem) => {
41 | let value = 0;
42 | switch (sortProperty) {
43 | case SortProperty.Name:
44 | value = compareItems(itemA.name, itemB.name);
45 | break;
46 | case SortProperty.Slot:
47 | value = compareItems(itemA.slot, itemB.slot);
48 | break;
49 | case SortProperty.Cost:
50 | if (itemA.cost && itemB.cost) {
51 | value = compareItems(itemA.cost, itemB.cost);
52 | } else if (itemA.cost) {
53 | return -1;
54 | } else {
55 | return 1;
56 | }
57 | break;
58 | case SortProperty.Id:
59 | const itemAIndex = allGames.findIndex(
60 | (item) => item === itemA.gameType
61 | );
62 | const itemBIndex = allGames.findIndex(
63 | (item) => item === itemB.gameType
64 | );
65 | value = compareItems(itemAIndex, itemBIndex);
66 | if (value === 0) {
67 | value = compareItems(itemA.id, itemB.id);
68 | }
69 | break;
70 | case SortProperty.Use:
71 | value = compareItems(getItemUse(itemA), getItemUse(itemB));
72 | break;
73 | }
74 | return sortDirection === SortDirection.ascending ? value : value * -1;
75 | },
76 | [sortDirection, sortProperty, allGames]
77 | );
78 |
79 | useEffect(() => {
80 | if (!items) {
81 | return;
82 | }
83 | const itemsCopy = Object.assign([], items);
84 | itemsCopy.sort(doSort);
85 | setSortedItems(itemsCopy);
86 | }, [items, doSort]);
87 |
88 | useEffect(() => {
89 | setFilteredItems(sortedItems.filter(isItemShown));
90 | }, [sortedItems, isItemShown]);
91 |
92 | return filteredItems;
93 | };
94 |
95 | export default useItems;
96 |
--------------------------------------------------------------------------------
/src/hooks/useSetSorting.tsx:
--------------------------------------------------------------------------------
1 | import { useRecoilState } from "recoil";
2 | import { sortDirectionState, sortPropertyState } from "../State";
3 | import { SortDirection, SortProperty } from "../State/Types";
4 |
5 | export const useSetSorting = () => {
6 | const [sortProperty, setSortProperty] = useRecoilState(sortPropertyState);
7 | const [sortDirection, setSortDirection] =
8 | useRecoilState(sortDirectionState);
9 |
10 | const setSorting = (newProperty: SortProperty) => {
11 | let newDirection: SortDirection;
12 | if (sortProperty === newProperty) {
13 | newDirection =
14 | sortDirection === SortDirection.ascending
15 | ? SortDirection.descending
16 | : SortDirection.ascending;
17 | } else {
18 | newDirection = SortDirection.ascending;
19 | }
20 |
21 | setSortProperty(newProperty);
22 | setSortDirection(newDirection);
23 | };
24 | return setSorting;
25 | };
26 |
--------------------------------------------------------------------------------
/src/img/class-tokens/be.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/be.png
--------------------------------------------------------------------------------
/src/img/class-tokens/br.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/br.png
--------------------------------------------------------------------------------
/src/img/class-tokens/bt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/bt.png
--------------------------------------------------------------------------------
/src/img/class-tokens/ch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/ch.png
--------------------------------------------------------------------------------
/src/img/class-tokens/dm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/dm.png
--------------------------------------------------------------------------------
/src/img/class-tokens/dr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/dr.png
--------------------------------------------------------------------------------
/src/img/class-tokens/ds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/ds.png
--------------------------------------------------------------------------------
/src/img/class-tokens/el.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/el.png
--------------------------------------------------------------------------------
/src/img/class-tokens/ht.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/ht.png
--------------------------------------------------------------------------------
/src/img/class-tokens/mt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/mt.png
--------------------------------------------------------------------------------
/src/img/class-tokens/ns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/ns.png
--------------------------------------------------------------------------------
/src/img/class-tokens/ph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/ph.png
--------------------------------------------------------------------------------
/src/img/class-tokens/qm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/qm.png
--------------------------------------------------------------------------------
/src/img/class-tokens/rg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/rg.png
--------------------------------------------------------------------------------
/src/img/class-tokens/sb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/sb.png
--------------------------------------------------------------------------------
/src/img/class-tokens/sc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/sc.png
--------------------------------------------------------------------------------
/src/img/class-tokens/sk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/sk.png
--------------------------------------------------------------------------------
/src/img/class-tokens/ss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/ss.png
--------------------------------------------------------------------------------
/src/img/class-tokens/su.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/su.png
--------------------------------------------------------------------------------
/src/img/class-tokens/sw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/sw.png
--------------------------------------------------------------------------------
/src/img/class-tokens/ti.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/ti.png
--------------------------------------------------------------------------------
/src/img/class-tokens/vw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/vw.png
--------------------------------------------------------------------------------
/src/img/class-tokens/xx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/class-tokens/xx.png
--------------------------------------------------------------------------------
/src/img/classes/BE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/BE.png
--------------------------------------------------------------------------------
/src/img/classes/BR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/BR.png
--------------------------------------------------------------------------------
/src/img/classes/BT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/BT.png
--------------------------------------------------------------------------------
/src/img/classes/CH.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CH.png
--------------------------------------------------------------------------------
/src/img/classes/CS1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CS1.png
--------------------------------------------------------------------------------
/src/img/classes/CS10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CS10.png
--------------------------------------------------------------------------------
/src/img/classes/CS11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CS11.png
--------------------------------------------------------------------------------
/src/img/classes/CS2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CS2.png
--------------------------------------------------------------------------------
/src/img/classes/CS3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CS3.png
--------------------------------------------------------------------------------
/src/img/classes/CS4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CS4.png
--------------------------------------------------------------------------------
/src/img/classes/CS5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CS5.png
--------------------------------------------------------------------------------
/src/img/classes/CS6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CS6.png
--------------------------------------------------------------------------------
/src/img/classes/CS7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CS7.png
--------------------------------------------------------------------------------
/src/img/classes/CS8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CS8.png
--------------------------------------------------------------------------------
/src/img/classes/CS9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CS9.png
--------------------------------------------------------------------------------
/src/img/classes/CSA1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CSA1.png
--------------------------------------------------------------------------------
/src/img/classes/CSA2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CSA2.png
--------------------------------------------------------------------------------
/src/img/classes/CSA3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/CSA3.png
--------------------------------------------------------------------------------
/src/img/classes/DM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/DM.png
--------------------------------------------------------------------------------
/src/img/classes/DR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/DR.png
--------------------------------------------------------------------------------
/src/img/classes/DS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/DS.png
--------------------------------------------------------------------------------
/src/img/classes/EL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/EL.png
--------------------------------------------------------------------------------
/src/img/classes/FH1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH1.png
--------------------------------------------------------------------------------
/src/img/classes/FH10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH10.png
--------------------------------------------------------------------------------
/src/img/classes/FH11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH11.png
--------------------------------------------------------------------------------
/src/img/classes/FH12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH12.png
--------------------------------------------------------------------------------
/src/img/classes/FH13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH13.png
--------------------------------------------------------------------------------
/src/img/classes/FH14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH14.png
--------------------------------------------------------------------------------
/src/img/classes/FH15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH15.png
--------------------------------------------------------------------------------
/src/img/classes/FH16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH16.png
--------------------------------------------------------------------------------
/src/img/classes/FH17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH17.png
--------------------------------------------------------------------------------
/src/img/classes/FH2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH2.png
--------------------------------------------------------------------------------
/src/img/classes/FH3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH3.png
--------------------------------------------------------------------------------
/src/img/classes/FH4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH4.png
--------------------------------------------------------------------------------
/src/img/classes/FH5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH5.png
--------------------------------------------------------------------------------
/src/img/classes/FH6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH6.png
--------------------------------------------------------------------------------
/src/img/classes/FH7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH7.png
--------------------------------------------------------------------------------
/src/img/classes/FH8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH8.png
--------------------------------------------------------------------------------
/src/img/classes/FH9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/FH9.png
--------------------------------------------------------------------------------
/src/img/classes/HT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/HT.png
--------------------------------------------------------------------------------
/src/img/classes/MT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/MT.png
--------------------------------------------------------------------------------
/src/img/classes/NS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/NS.png
--------------------------------------------------------------------------------
/src/img/classes/PH.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/PH.png
--------------------------------------------------------------------------------
/src/img/classes/QM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/QM.png
--------------------------------------------------------------------------------
/src/img/classes/RG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/RG.png
--------------------------------------------------------------------------------
/src/img/classes/SB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/SB.png
--------------------------------------------------------------------------------
/src/img/classes/SC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/SC.png
--------------------------------------------------------------------------------
/src/img/classes/SK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/SK.png
--------------------------------------------------------------------------------
/src/img/classes/SS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/SS.png
--------------------------------------------------------------------------------
/src/img/classes/SU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/SU.png
--------------------------------------------------------------------------------
/src/img/classes/SW.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/SW.png
--------------------------------------------------------------------------------
/src/img/classes/TI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/TI.png
--------------------------------------------------------------------------------
/src/img/classes/VW.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/VW.png
--------------------------------------------------------------------------------
/src/img/classes/XX.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/classes/XX.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/bane.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/bane.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/bless.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/bless.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/brittle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/brittle.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/chill.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/chill.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/curse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/curse.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/disarm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/disarm.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/dodge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/dodge.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/empower.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/empower.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/enfeeble.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/enfeeble.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/fh-wound.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/fh-wound.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/immobilize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/immobilize.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/impair.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/impair.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/infect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/infect.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/invisible.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/invisible.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/muddle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/muddle.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/pierce.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/pierce.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/poison.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/poison.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/pull.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/pull.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/push.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/push.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/regenerate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/regenerate.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/rolling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/rolling.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/rupture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/rupture.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/strengthen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/strengthen.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/stun.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/stun.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/target.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/target.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/ward.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/ward.png
--------------------------------------------------------------------------------
/src/img/icons/conditions/wound.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/conditions/wound.png
--------------------------------------------------------------------------------
/src/img/icons/elements/any.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/any.png
--------------------------------------------------------------------------------
/src/img/icons/elements/dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/dark.png
--------------------------------------------------------------------------------
/src/img/icons/elements/earth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/earth.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-air-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-air-icon.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-air.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-air.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-consume.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-consume.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-dark-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-dark-icon.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-dark.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-earth-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-earth-icon.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-earth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-earth.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-fire-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-fire-icon.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-fire.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-ice-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-ice-icon.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-ice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-ice.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-light-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-light-icon.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-light.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-wild-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-wild-icon.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fh-wild.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fh-wild.png
--------------------------------------------------------------------------------
/src/img/icons/elements/fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/fire.png
--------------------------------------------------------------------------------
/src/img/icons/elements/ice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/ice.png
--------------------------------------------------------------------------------
/src/img/icons/elements/light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/light.png
--------------------------------------------------------------------------------
/src/img/icons/elements/use.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/use.png
--------------------------------------------------------------------------------
/src/img/icons/elements/wind.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/wind.png
--------------------------------------------------------------------------------
/src/img/icons/elements/x-any.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/x-any.png
--------------------------------------------------------------------------------
/src/img/icons/elements/x-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/x-dark.png
--------------------------------------------------------------------------------
/src/img/icons/elements/x-earth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/x-earth.png
--------------------------------------------------------------------------------
/src/img/icons/elements/x-fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/x-fire.png
--------------------------------------------------------------------------------
/src/img/icons/elements/x-ice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/x-ice.png
--------------------------------------------------------------------------------
/src/img/icons/elements/x-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/x-light.png
--------------------------------------------------------------------------------
/src/img/icons/elements/x-wind.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/elements/x-wind.png
--------------------------------------------------------------------------------
/src/img/icons/equipment_slot/1h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/equipment_slot/1h.png
--------------------------------------------------------------------------------
/src/img/icons/equipment_slot/2h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/equipment_slot/2h.png
--------------------------------------------------------------------------------
/src/img/icons/equipment_slot/body.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/equipment_slot/body.png
--------------------------------------------------------------------------------
/src/img/icons/equipment_slot/head.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/equipment_slot/head.png
--------------------------------------------------------------------------------
/src/img/icons/equipment_slot/legs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/equipment_slot/legs.png
--------------------------------------------------------------------------------
/src/img/icons/equipment_slot/small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/equipment_slot/small.png
--------------------------------------------------------------------------------
/src/img/icons/general/Crystalize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/Crystalize.png
--------------------------------------------------------------------------------
/src/img/icons/general/TOA6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/TOA6.png
--------------------------------------------------------------------------------
/src/img/icons/general/attack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/attack.png
--------------------------------------------------------------------------------
/src/img/icons/general/attack_tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/attack_tile.png
--------------------------------------------------------------------------------
/src/img/icons/general/check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/check.png
--------------------------------------------------------------------------------
/src/img/icons/general/checkmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/checkmark.png
--------------------------------------------------------------------------------
/src/img/icons/general/circle_x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/circle_x.png
--------------------------------------------------------------------------------
/src/img/icons/general/consumed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/consumed.png
--------------------------------------------------------------------------------
/src/img/icons/general/consumed_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/consumed_white.png
--------------------------------------------------------------------------------
/src/img/icons/general/damage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/damage.png
--------------------------------------------------------------------------------
/src/img/icons/general/eot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/eot.png
--------------------------------------------------------------------------------
/src/img/icons/general/event_card_rip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/event_card_rip.png
--------------------------------------------------------------------------------
/src/img/icons/general/event_card_rip_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/event_card_rip_white.png
--------------------------------------------------------------------------------
/src/img/icons/general/event_card_shuffle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/event_card_shuffle.png
--------------------------------------------------------------------------------
/src/img/icons/general/event_card_shuffle_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/event_card_shuffle_white.png
--------------------------------------------------------------------------------
/src/img/icons/general/experience.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/experience.png
--------------------------------------------------------------------------------
/src/img/icons/general/experience_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/experience_1.png
--------------------------------------------------------------------------------
/src/img/icons/general/experience_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/experience_2.png
--------------------------------------------------------------------------------
/src/img/icons/general/experience_white_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/experience_white_1.png
--------------------------------------------------------------------------------
/src/img/icons/general/experience_white_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/experience_white_2.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-anemone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-anemone.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-astral.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-astral.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-flying.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-flying.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-geminate-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-geminate-left.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-geminate-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-geminate-right.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-heal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-heal.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-hourglass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-hourglass.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-jump.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-jump.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-meter-blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-meter-blue.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-meter-red.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-meter-red.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-meter-yellow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-meter-yellow.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-move.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-move.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-prism.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-prism.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-range.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-range.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-shadow.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-shards.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-shards.png
--------------------------------------------------------------------------------
/src/img/icons/general/fh-shield.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/fh-shield.png
--------------------------------------------------------------------------------
/src/img/icons/general/flip_back_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/flip_back_white.png
--------------------------------------------------------------------------------
/src/img/icons/general/flip_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/flip_white.png
--------------------------------------------------------------------------------
/src/img/icons/general/flying.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/flying.png
--------------------------------------------------------------------------------
/src/img/icons/general/heal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/heal.png
--------------------------------------------------------------------------------
/src/img/icons/general/jump.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/jump.png
--------------------------------------------------------------------------------
/src/img/icons/general/loot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/loot.png
--------------------------------------------------------------------------------
/src/img/icons/general/lost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/lost.png
--------------------------------------------------------------------------------
/src/img/icons/general/lost_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/lost_white.png
--------------------------------------------------------------------------------
/src/img/icons/general/modifier_2x_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/modifier_2x_circle.png
--------------------------------------------------------------------------------
/src/img/icons/general/modifier_minus_one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/modifier_minus_one.png
--------------------------------------------------------------------------------
/src/img/icons/general/modifier_minus_one_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/modifier_minus_one_1.png
--------------------------------------------------------------------------------
/src/img/icons/general/modifier_minus_one_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/modifier_minus_one_circle.png
--------------------------------------------------------------------------------
/src/img/icons/general/modifier_minus_one_cropped.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/modifier_minus_one_cropped.png
--------------------------------------------------------------------------------
/src/img/icons/general/modifier_minus_one_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/modifier_minus_one_white.png
--------------------------------------------------------------------------------
/src/img/icons/general/modifier_minus_two_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/modifier_minus_two_circle.png
--------------------------------------------------------------------------------
/src/img/icons/general/modifier_no_damage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/modifier_no_damage.png
--------------------------------------------------------------------------------
/src/img/icons/general/modifier_plus_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/modifier_plus_1.png
--------------------------------------------------------------------------------
/src/img/icons/general/modifier_plus_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/modifier_plus_2.png
--------------------------------------------------------------------------------
/src/img/icons/general/modifier_zero_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/modifier_zero_circle.png
--------------------------------------------------------------------------------
/src/img/icons/general/move.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/move.png
--------------------------------------------------------------------------------
/src/img/icons/general/ongoing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/ongoing.png
--------------------------------------------------------------------------------
/src/img/icons/general/player_tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/player_tile.png
--------------------------------------------------------------------------------
/src/img/icons/general/range.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/range.png
--------------------------------------------------------------------------------
/src/img/icons/general/recover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/recover.png
--------------------------------------------------------------------------------
/src/img/icons/general/recover_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/recover_white.png
--------------------------------------------------------------------------------
/src/img/icons/general/refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/refresh.png
--------------------------------------------------------------------------------
/src/img/icons/general/refresh_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/refresh_white.png
--------------------------------------------------------------------------------
/src/img/icons/general/retaliate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/retaliate.png
--------------------------------------------------------------------------------
/src/img/icons/general/scrapx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/scrapx.png
--------------------------------------------------------------------------------
/src/img/icons/general/shield.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/shield.png
--------------------------------------------------------------------------------
/src/img/icons/general/shuffle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/shuffle.png
--------------------------------------------------------------------------------
/src/img/icons/general/shuffle_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/shuffle_white.png
--------------------------------------------------------------------------------
/src/img/icons/general/slot_empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/slot_empty.png
--------------------------------------------------------------------------------
/src/img/icons/general/slot_experience.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/slot_experience.png
--------------------------------------------------------------------------------
/src/img/icons/general/spent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/spent.png
--------------------------------------------------------------------------------
/src/img/icons/general/spent_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/spent_white.png
--------------------------------------------------------------------------------
/src/img/icons/general/target.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/target.png
--------------------------------------------------------------------------------
/src/img/icons/general/teleport.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/general/teleport.png
--------------------------------------------------------------------------------
/src/img/icons/multi_attack/cleave_0_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/multi_attack/cleave_0_1.png
--------------------------------------------------------------------------------
/src/img/icons/multi_attack/cone_0_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/multi_attack/cone_0_1.png
--------------------------------------------------------------------------------
/src/img/icons/multi_attack/cone_1_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/multi_attack/cone_1_1.png
--------------------------------------------------------------------------------
/src/img/icons/multi_attack/cube_2_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/multi_attack/cube_2_2.png
--------------------------------------------------------------------------------
/src/img/icons/multi_attack/line_0_1_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/multi_attack/line_0_1_1.png
--------------------------------------------------------------------------------
/src/img/icons/multi_attack/line_1_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/multi_attack/line_1_1.png
--------------------------------------------------------------------------------
/src/img/icons/resources/arrowvine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/resources/arrowvine.png
--------------------------------------------------------------------------------
/src/img/icons/resources/axenut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/resources/axenut.png
--------------------------------------------------------------------------------
/src/img/icons/resources/corpsecap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/resources/corpsecap.png
--------------------------------------------------------------------------------
/src/img/icons/resources/flamefruit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/resources/flamefruit.png
--------------------------------------------------------------------------------
/src/img/icons/resources/hide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/resources/hide.png
--------------------------------------------------------------------------------
/src/img/icons/resources/item.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/resources/item.png
--------------------------------------------------------------------------------
/src/img/icons/resources/lumber.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/resources/lumber.png
--------------------------------------------------------------------------------
/src/img/icons/resources/metal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/resources/metal.png
--------------------------------------------------------------------------------
/src/img/icons/resources/rockroot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/resources/rockroot.png
--------------------------------------------------------------------------------
/src/img/icons/resources/snowthistle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/icons/resources/snowthistle.png
--------------------------------------------------------------------------------
/src/img/lost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heisch/gloomhaven-item-db/05900d3c31968b2de6ba6f4be5f6acae30698298/src/img/lost.png
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | import * as serviceWorker from "./serviceWorker";
6 | import { FirebaseProvider } from "./components/Firebase";
7 | import { BrowserRouter as Router } from "react-router-dom";
8 | import { RecoilRoot } from "recoil";
9 |
10 | ReactDOM.render(
11 |
12 |
13 |
14 |
15 |
16 |
17 | ,
18 | document.getElementById("root")
19 | );
20 |
21 | // If you want your app to work offline and load faster, you can change
22 | // unregister() to register() below. Note this comes with some pitfalls.
23 | // Learn more about service workers: https://bit.ly/CRA-PWA
24 | serviceWorker.unregister();
25 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2015",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
--------------------------------------------------------------------------------