PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
-MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
-MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
+VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
+VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
LARASCORD_CLIENT_ID=
LARASCORD_CLIENT_SECRET=
LARASCORD_PREFIX=larascord
LARASCORD_SCOPE=identify
-MIX_DISCORD_CLIENT_ID="${LARASCORD_CLIENT_ID}"
+VITE_DISCORD_CLIENT_ID="${LARASCORD_CLIENT_ID}"
DISCORD_BOT_TOKEN=
DISCORD_BOT_CREATE_COMMANDS=
/node_modules
/public/alttp-seeds
/public/aos-seeds
+/public/build
/public/css/app.css
/public/css/app.css.map
/public/doortracker
--- /dev/null
+import js from '@eslint/js';
+import react from 'eslint-plugin-react';
+import globals from 'globals';
+
+export default [
+ js.configs.recommended,
+ react.configs.flat.recommended,
+ {
+ plugins: {
+ react,
+ },
+
+ settings: {
+ react: {
+ version: "detect",
+ },
+ },
+
+ languageOptions: {
+ globals: {
+ ...globals.browser,
+ },
+
+ ecmaVersion: 12,
+ sourceType: "module",
+
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ },
+];
"@tailwindcss/forms": "^0.5.6",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
+ "@vitejs/plugin-react": "^4.5.2",
"alpinejs": "^3.4.2",
"autoprefixer": "^10.4.2",
"axios": "^1.5.0",
- "babel-jest": "^29.7.0",
"bootstrap": "^5.1.3",
"eslint": "^8.10.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-react": "^7.29.3",
- "jest": "^29.7.0",
- "jest-environment-jsdom": "^29.7.0",
+ "globals": "^16.2.0",
+ "jsdom": "^26.1.0",
"laravel-mix": "^6.0.6",
+ "laravel-vite-plugin": "^1.3.0",
"lodash": "^4.17.19",
"postcss": "^8.4.6",
"postcss-import": "^15.1.0",
"resolve-url-loader": "^5.0.0",
"sass": "^1.32.11",
"sass-loader": "^13.3.2",
- "tailwindcss": "^3.0.18"
+ "tailwindcss": "^3.0.18",
+ "vite": "^6.3.5",
+ "vite-plugin-webpackchunkname": "^1.0.3",
+ "vitest": "^3.2.4"
}
},
"node_modules/@adobe/css-tools": {
"node": ">=6.0.0"
}
},
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
+ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.3",
+ "@csstools/css-color-parser": "^3.0.9",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "lru-cache": "^10.4.3"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/@babel/code-frame": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
- "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/highlight": "^7.24.7",
- "picocolors": "^1.0.0"
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/compat-data": {
- "version": "7.25.4",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz",
- "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==",
+ "version": "7.27.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz",
+ "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
- "version": "7.25.2",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz",
- "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==",
+ "version": "7.27.4",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz",
+ "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.24.7",
- "@babel/generator": "^7.25.0",
- "@babel/helper-compilation-targets": "^7.25.2",
- "@babel/helper-module-transforms": "^7.25.2",
- "@babel/helpers": "^7.25.0",
- "@babel/parser": "^7.25.0",
- "@babel/template": "^7.25.0",
- "@babel/traverse": "^7.25.2",
- "@babel/types": "^7.25.2",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.3",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.27.3",
+ "@babel/helpers": "^7.27.4",
+ "@babel/parser": "^7.27.4",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.27.4",
+ "@babel/types": "^7.27.3",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
}
},
"node_modules/@babel/generator": {
- "version": "7.25.6",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz",
- "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==",
+ "version": "7.27.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz",
+ "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/types": "^7.25.6",
+ "@babel/parser": "^7.27.5",
+ "@babel/types": "^7.27.3",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
- "jsesc": "^2.5.1"
+ "jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
- "version": "7.25.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz",
- "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==",
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.25.2",
- "@babel/helper-validator-option": "^7.24.8",
- "browserslist": "^4.23.1",
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
},
}
},
"node_modules/@babel/helper-module-imports": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
- "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.24.7",
- "@babel/types": "^7.24.7"
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.25.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz",
- "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
+ "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/helper-module-imports": "^7.24.7",
- "@babel/helper-simple-access": "^7.24.7",
- "@babel/helper-validator-identifier": "^7.24.7",
- "@babel/traverse": "^7.25.2"
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.3"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-plugin-utils": {
- "version": "7.24.8",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
- "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.9.0"
}
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.24.8",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
- "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
- "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
- "version": "7.24.8",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz",
- "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.9.0"
}
}
},
"node_modules/@babel/helpers": {
- "version": "7.25.6",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz",
- "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==",
- "dev": true,
- "dependencies": {
- "@babel/template": "^7.25.0",
- "@babel/types": "^7.25.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/highlight": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
- "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
+ "version": "7.27.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
+ "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.24.7",
- "chalk": "^2.4.2",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.27.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.25.6",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz",
- "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
+ "version": "7.27.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz",
+ "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/types": "^7.25.6"
+ "@babel/types": "^7.27.3"
},
"bin": {
"parser": "bin/babel-parser.js"
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-syntax-bigint": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
- "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
- "dev": true,
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
"node_modules/@babel/plugin-syntax-class-properties": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-syntax-typescript": {
- "version": "7.25.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz",
- "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==",
- "dev": true,
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.24.8"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
"node_modules/@babel/plugin-syntax-unicode-sets-regex": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-transform-classes/node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/@babel/plugin-transform-computed-properties": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz",
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/plugin-transform-react-pure-annotations": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.7.tgz",
}
},
"node_modules/@babel/template": {
- "version": "7.25.0",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
- "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.24.7",
- "@babel/parser": "^7.25.0",
- "@babel/types": "^7.25.0"
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.25.6",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz",
- "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==",
+ "version": "7.27.4",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz",
+ "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.24.7",
- "@babel/generator": "^7.25.6",
- "@babel/parser": "^7.25.6",
- "@babel/template": "^7.25.0",
- "@babel/types": "^7.25.6",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.3",
+ "@babel/parser": "^7.27.4",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.27.3",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/traverse/node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/@babel/types": {
- "version": "7.25.6",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
- "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
+ "version": "7.27.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz",
+ "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.24.8",
- "@babel/helper-validator-identifier": "^7.24.7",
- "to-fast-properties": "^2.0.0"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
- "node_modules/@bcoe/v8-coverage": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
- "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
- "dev": true
- },
"node_modules/@codemirror/autocomplete": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.0.tgz",
"node": ">=0.1.90"
}
},
- "node_modules/@discoveryjs/json-ext": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
- "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
+ "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
"engines": {
- "node": ">=10.0.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
- "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
"dev": true,
- "dependencies": {
- "eslint-visitor-keys": "^3.3.0"
- },
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": ">=18"
},
"peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
- "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz",
+ "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^5.0.2",
+ "@csstools/css-calc": "^2.1.4"
+ },
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": ">=18"
},
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
- "node_modules/@eslint-community/regexpp": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
- "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
"engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
- "node_modules/@eslint/eslintrc": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
- "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
"dev": true,
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^9.6.0",
- "globals": "^13.19.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "node": ">=18"
}
},
- "node_modules/@eslint/eslintrc/node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
- "node_modules/@eslint/eslintrc/node_modules/globals": {
- "version": "13.24.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "node_modules/@discoveryjs/json-ext": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
+ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
"dev": true,
- "dependencies": {
- "type-fest": "^0.20.2"
- },
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=10.0.0"
}
},
- "node_modules/@eslint/eslintrc/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
+ "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@eslint/eslintrc/node_modules/type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
+ "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=18"
}
},
- "node_modules/@eslint/js": {
- "version": "8.57.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
- "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
+ "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": ">=18"
}
},
- "node_modules/@fortawesome/fontawesome-common-types": {
- "version": "6.6.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
- "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==",
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
+ "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": ">=6"
+ "node": ">=18"
}
},
- "node_modules/@fortawesome/fontawesome-free": {
- "version": "6.6.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.6.0.tgz",
- "integrity": "sha512-60G28ke/sXdtS9KZCpZSHHkCbdsOGEhIUGlwq6yhY74UpTiToIh8np7A8yphhM4BWsvNFtIvLpi4co+h9Mr9Ow==",
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
+ "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">=6"
+ "node": ">=18"
}
},
- "node_modules/@fortawesome/fontawesome-svg-core": {
- "version": "6.6.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
- "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
- "dependencies": {
- "@fortawesome/fontawesome-common-types": "6.6.0"
- },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
+ "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">=6"
+ "node": ">=18"
}
},
- "node_modules/@fortawesome/free-brands-svg-icons": {
- "version": "6.6.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz",
- "integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==",
- "dependencies": {
- "@fortawesome/fontawesome-common-types": "6.6.0"
- },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
+ "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": ">=6"
+ "node": ">=18"
}
},
- "node_modules/@fortawesome/free-solid-svg-icons": {
- "version": "6.6.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
- "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
- "dependencies": {
- "@fortawesome/fontawesome-common-types": "6.6.0"
- },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
+ "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": ">=6"
+ "node": ">=18"
}
},
- "node_modules/@fortawesome/react-fontawesome": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz",
- "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==",
- "dependencies": {
- "prop-types": "^15.8.1"
- },
- "peerDependencies": {
- "@fortawesome/fontawesome-svg-core": "~1 || ~6",
- "react": ">=16.3"
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
+ "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@humanwhocodes/config-array": {
- "version": "0.11.14",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
- "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
- "deprecated": "Use @eslint/config-array instead",
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
+ "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "dependencies": {
- "@humanwhocodes/object-schema": "^2.0.2",
- "debug": "^4.3.1",
- "minimatch": "^3.0.5"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=10.10.0"
+ "node": ">=18"
}
},
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
+ "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
+ "node": ">=18"
}
},
- "node_modules/@humanwhocodes/object-schema": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
- "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
- "deprecated": "Use @eslint/object-schema instead",
- "dev": true
- },
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
+ "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
+ "cpu": [
+ "loong64"
+ ],
"dev": true,
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
- "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
- "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
+ "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
+ "cpu": [
+ "mips64el"
+ ],
"dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
+ "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
+ "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
},
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
+ "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
+ "cpu": [
+ "s390x"
+ ],
"dev": true,
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=18"
}
},
- "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
+ "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
+ "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/@istanbuljs/load-nyc-config": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
- "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
+ "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "dependencies": {
- "camelcase": "^5.3.1",
- "find-up": "^4.1.0",
- "get-package-type": "^0.1.0",
- "js-yaml": "^3.13.1",
- "resolve-from": "^5.0.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/@istanbuljs/schema": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
- "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
+ "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/@jest/console": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
- "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
+ "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "dependencies": {
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "slash": "^3.0.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=18"
}
},
- "node_modules/@jest/console/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
+ "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/@jest/console/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
+ "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/@jest/console/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=7.0.0"
+ "node": ">=18"
}
},
- "node_modules/@jest/console/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/@jest/console/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
+ "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/@jest/console/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
+ "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/@jest/core": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz",
- "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==",
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+ "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
"dev": true,
"dependencies": {
- "@jest/console": "^29.7.0",
- "@jest/reporters": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "ansi-escapes": "^4.2.1",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "exit": "^0.1.2",
- "graceful-fs": "^4.2.9",
- "jest-changed-files": "^29.7.0",
- "jest-config": "^29.7.0",
- "jest-haste-map": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-regex-util": "^29.6.3",
- "jest-resolve": "^29.7.0",
- "jest-resolve-dependencies": "^29.7.0",
- "jest-runner": "^29.7.0",
- "jest-runtime": "^29.7.0",
- "jest-snapshot": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "jest-watcher": "^29.7.0",
- "micromatch": "^4.0.4",
- "pretty-format": "^29.7.0",
- "slash": "^3.0.0",
- "strip-ansi": "^6.0.0"
+ "eslint-visitor-keys": "^3.3.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
- },
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
},
- "node_modules/@jest/core/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
"engines": {
- "node": ">=8"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/@jest/core/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
+ "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
"dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
- "node_modules/@jest/core/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true,
"dependencies": {
- "color-name": "~1.1.4"
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
},
"engines": {
- "node": ">=7.0.0"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/@jest/core/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "node_modules/@eslint/eslintrc/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
- "node_modules/@jest/core/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
"dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
"engines": {
"node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@jest/core/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "node_modules/@eslint/eslintrc/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "argparse": "^2.0.1"
},
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
}
},
- "node_modules/@jest/core/node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "node_modules/@eslint/eslintrc/node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@jest/core/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true
- },
- "node_modules/@jest/core/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/@eslint/js": {
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+ "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
"engines": {
- "node": ">=8"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
- "node_modules/@jest/environment": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
- "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
- "dev": true,
- "dependencies": {
- "@jest/fake-timers": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "jest-mock": "^29.7.0"
- },
+ "node_modules/@fortawesome/fontawesome-common-types": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
+ "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==",
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=6"
}
},
- "node_modules/@jest/expect": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
- "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
- "dev": true,
- "dependencies": {
- "expect": "^29.7.0",
- "jest-snapshot": "^29.7.0"
- },
+ "node_modules/@fortawesome/fontawesome-free": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.6.0.tgz",
+ "integrity": "sha512-60G28ke/sXdtS9KZCpZSHHkCbdsOGEhIUGlwq6yhY74UpTiToIh8np7A8yphhM4BWsvNFtIvLpi4co+h9Mr9Ow==",
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=6"
}
},
- "node_modules/@jest/expect-utils": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
- "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
- "dev": true,
+ "node_modules/@fortawesome/fontawesome-svg-core": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
+ "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
"dependencies": {
- "jest-get-type": "^29.6.3"
+ "@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=6"
}
},
- "node_modules/@jest/fake-timers": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
- "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
- "dev": true,
+ "node_modules/@fortawesome/free-brands-svg-icons": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz",
+ "integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==",
"dependencies": {
- "@jest/types": "^29.6.3",
- "@sinonjs/fake-timers": "^10.0.2",
- "@types/node": "*",
- "jest-message-util": "^29.7.0",
- "jest-mock": "^29.7.0",
- "jest-util": "^29.7.0"
+ "@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=6"
}
},
- "node_modules/@jest/globals": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
- "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==",
- "dev": true,
+ "node_modules/@fortawesome/free-solid-svg-icons": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
+ "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
"dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/expect": "^29.7.0",
- "@jest/types": "^29.6.3",
- "jest-mock": "^29.7.0"
+ "@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=6"
}
},
- "node_modules/@jest/reporters": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
- "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
- "dev": true,
+ "node_modules/@fortawesome/react-fontawesome": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz",
+ "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==",
"dependencies": {
- "@bcoe/v8-coverage": "^0.2.3",
- "@jest/console": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@jridgewell/trace-mapping": "^0.3.18",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "collect-v8-coverage": "^1.0.0",
- "exit": "^0.1.2",
- "glob": "^7.1.3",
- "graceful-fs": "^4.2.9",
- "istanbul-lib-coverage": "^3.0.0",
- "istanbul-lib-instrument": "^6.0.0",
- "istanbul-lib-report": "^3.0.0",
- "istanbul-lib-source-maps": "^4.0.0",
- "istanbul-reports": "^3.1.3",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-worker": "^29.7.0",
- "slash": "^3.0.0",
- "string-length": "^4.0.1",
- "strip-ansi": "^6.0.0",
- "v8-to-istanbul": "^9.0.1"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "prop-types": "^15.8.1"
},
"peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
- },
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
+ "@fortawesome/fontawesome-svg-core": "~1 || ~6",
+ "react": ">=16.3"
}
},
- "node_modules/@jest/reporters/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.14",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
+ "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
+ "deprecated": "Use @eslint/config-array instead",
"dev": true,
"dependencies": {
- "color-convert": "^2.0.1"
+ "@humanwhocodes/object-schema": "^2.0.2",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
},
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">=10.10.0"
}
},
- "node_modules/@jest/reporters/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
"dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
"engines": {
- "node": ">=10"
+ "node": ">=12.22"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@jest/reporters/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/@jest/reporters/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
"dev": true
},
- "node_modules/@jest/reporters/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
- "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
- "dev": true,
- "dependencies": {
- "@babel/core": "^7.23.9",
- "@babel/parser": "^7.23.9",
- "@istanbuljs/schema": "^0.1.3",
- "istanbul-lib-coverage": "^3.2.0",
- "semver": "^7.5.4"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@jest/reporters/node_modules/semver": {
- "version": "7.6.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
- "dev": true,
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@jest/reporters/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@jest/schemas": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
- "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
- "dev": true,
- "dependencies": {
- "@sinclair/typebox": "^0.27.8"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/source-map": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz",
- "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==",
- "dev": true,
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.18",
- "callsites": "^3.0.0",
- "graceful-fs": "^4.2.9"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/test-result": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
- "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
- "dev": true,
- "dependencies": {
- "@jest/console": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/istanbul-lib-coverage": "^2.0.0",
- "collect-v8-coverage": "^1.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/test-sequencer": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz",
- "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==",
- "dev": true,
- "dependencies": {
- "@jest/test-result": "^29.7.0",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/transform": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
- "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"dependencies": {
- "@babel/core": "^7.11.6",
- "@jest/types": "^29.6.3",
- "@jridgewell/trace-mapping": "^0.3.18",
- "babel-plugin-istanbul": "^6.1.1",
- "chalk": "^4.0.0",
- "convert-source-map": "^2.0.0",
- "fast-json-stable-stringify": "^2.1.0",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "jest-regex-util": "^29.6.3",
- "jest-util": "^29.7.0",
- "micromatch": "^4.0.4",
- "pirates": "^4.0.4",
- "slash": "^3.0.0",
- "write-file-atomic": "^4.0.2"
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=12"
}
},
- "node_modules/@jest/transform/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
"engines": {
- "node": ">=8"
+ "node": ">=12"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
- "node_modules/@jest/transform/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
"engines": {
- "node": ">=10"
+ "node": ">=12"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/@jest/transform/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/@jest/transform/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
- "node_modules/@jest/transform/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@jest/transform/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@jest/types": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
- "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
- "dev": true,
- "dependencies": {
- "@jest/schemas": "^29.6.3",
- "@types/istanbul-lib-coverage": "^2.0.0",
- "@types/istanbul-reports": "^3.0.0",
- "@types/node": "*",
- "@types/yargs": "^17.0.8",
- "chalk": "^4.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/types/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/@isaacs/cliui/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"dependencies": {
- "color-convert": "^2.0.1"
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
},
"engines": {
- "node": ">=8"
+ "node": ">=12"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@jest/types/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
+ "ansi-regex": "^6.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=12"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
- "node_modules/@jest/types/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"dependencies": {
- "color-name": "~1.1.4"
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
},
"engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/@jest/types/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/@jest/types/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@jest/types/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
+ "node": ">=12"
},
- "engines": {
- "node": ">=8"
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@jridgewell/gen-mapping": {
"react": ">=16.14.0"
}
},
- "node_modules/@sinclair/typebox": {
- "version": "0.27.8",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
- "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
- "dev": true
- },
- "node_modules/@sinonjs/commons": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
- "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz",
+ "integrity": "sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==",
"dev": true,
- "dependencies": {
- "type-detect": "4.0.8"
- }
+ "license": "MIT"
},
- "node_modules/@sinonjs/fake-timers": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
- "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "node_modules/@rollup/plugin-alias": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz",
+ "integrity": "sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==",
"dev": true,
- "dependencies": {
- "@sinonjs/commons": "^3.0.0"
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
}
},
- "node_modules/@swc/helpers": {
- "version": "0.5.12",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.12.tgz",
- "integrity": "sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g==",
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz",
+ "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "tslib": "^2.4.0"
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.0.tgz",
+ "integrity": "sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.0.tgz",
+ "integrity": "sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.0.tgz",
+ "integrity": "sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.0.tgz",
+ "integrity": "sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.0.tgz",
+ "integrity": "sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.0.tgz",
+ "integrity": "sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.0.tgz",
+ "integrity": "sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.0.tgz",
+ "integrity": "sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.0.tgz",
+ "integrity": "sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.0.tgz",
+ "integrity": "sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.0.tgz",
+ "integrity": "sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.0.tgz",
+ "integrity": "sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.0.tgz",
+ "integrity": "sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.0.tgz",
+ "integrity": "sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.0.tgz",
+ "integrity": "sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.0.tgz",
+ "integrity": "sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.0.tgz",
+ "integrity": "sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.0.tgz",
+ "integrity": "sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.0.tgz",
+ "integrity": "sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.0.tgz",
+ "integrity": "sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.12",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.12.tgz",
+ "integrity": "sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g==",
+ "dependencies": {
+ "tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/forms": {
"react-dom": "^18.0.0"
}
},
- "node_modules/@tootallnate/once": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
- "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
- "dev": true,
- "engines": {
- "node": ">= 10"
- }
- },
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
"@types/node": "*"
}
},
+ "node_modules/@types/chai": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz",
+ "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*"
+ }
+ },
"node_modules/@types/clean-css": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.11.tgz",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
},
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
- "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
- "dev": true
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@types/express": {
"version": "4.17.21",
"@types/node": "*"
}
},
- "node_modules/@types/graceful-fs": {
- "version": "4.1.9",
- "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
- "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
- "dev": true,
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
"@types/svgo": "^1"
}
},
- "node_modules/@types/istanbul-lib-coverage": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
- "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
- "dev": true
- },
- "node_modules/@types/istanbul-lib-report": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
- "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
- "dev": true,
- "dependencies": {
- "@types/istanbul-lib-coverage": "*"
- }
- },
- "node_modules/@types/istanbul-reports": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
- "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
- "dev": true,
- "dependencies": {
- "@types/istanbul-lib-report": "*"
- }
- },
- "node_modules/@types/jsdom": {
- "version": "20.0.1",
- "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
- "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==",
- "dev": true,
- "dependencies": {
- "@types/node": "*",
- "@types/tough-cookie": "*",
- "parse5": "^7.0.0"
- }
- },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"@types/node": "*"
}
},
- "node_modules/@types/stack-utils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
- "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
- "dev": true
- },
"node_modules/@types/svgo": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/@types/svgo/-/svgo-1.3.6.tgz",
"integrity": "sha512-AZU7vQcy/4WFEuwnwsNsJnFwupIpbllH1++LXScN6uxT1Z4zPzdrWG97w4/I7eFKFTvfy/bHFStWjdBAg2Vjug==",
"dev": true
},
- "node_modules/@types/tough-cookie": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
- "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
- "dev": true
- },
"node_modules/@types/warning": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
"@types/node": "*"
}
},
- "node_modules/@types/yargs": {
- "version": "17.0.33",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
- "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
- "dev": true,
- "dependencies": {
- "@types/yargs-parser": "*"
- }
- },
- "node_modules/@types/yargs-parser": {
- "version": "21.0.3",
- "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
- "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
- "dev": true
- },
"node_modules/@uiw/codemirror-extensions-basic-setup": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.23.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
- "node_modules/@vue/reactivity": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
- "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.2.tgz",
+ "integrity": "sha512-QNVT3/Lxx99nMQWJWF7K4N6apUEuT0KlZA3mx/mVaoGj3smm/8rc8ezz15J1pcbcjDK0V15rpHetVfya08r76Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@vue/shared": "3.1.5"
+ "@babel/core": "^7.27.4",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.11",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0"
}
},
- "node_modules/@vue/shared": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
- "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
- "dev": true
- },
- "node_modules/@webassemblyjs/ast": {
- "version": "1.12.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
- "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==",
+ "node_modules/@vitest/expect": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
+ "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@webassemblyjs/helper-numbers": "1.11.6",
- "@webassemblyjs/helper-wasm-bytecode": "1.11.6"
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/@webassemblyjs/floating-point-hex-parser": {
- "version": "1.11.6",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
- "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
- "dev": true
- },
- "node_modules/@webassemblyjs/helper-api-error": {
- "version": "1.11.6",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
- "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
- "dev": true
- },
- "node_modules/@webassemblyjs/helper-buffer": {
- "version": "1.12.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz",
- "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==",
- "dev": true
- },
- "node_modules/@webassemblyjs/helper-numbers": {
- "version": "1.11.6",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
- "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
+ "node_modules/@vitest/mocker": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
+ "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@webassemblyjs/floating-point-hex-parser": "1.11.6",
- "@webassemblyjs/helper-api-error": "1.11.6",
- "@xtuc/long": "4.2.2"
- }
- },
- "node_modules/@webassemblyjs/helper-wasm-bytecode": {
- "version": "1.11.6",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
+ "@vitest/spy": "3.2.4",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/mocker/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
+ "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
+ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "3.2.4",
+ "pathe": "^2.0.3",
+ "strip-literal": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
+ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.4",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
+ "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^4.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
+ "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.4",
+ "loupe": "^3.1.4",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
+ "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
+ "dev": true,
+ "dependencies": {
+ "@vue/shared": "3.1.5"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
+ "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/ast": {
+ "version": "1.12.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
+ "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/helper-numbers": "1.11.6",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.6"
+ }
+ },
+ "node_modules/@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
+ "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/helper-api-error": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
+ "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/helper-buffer": {
+ "version": "1.12.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz",
+ "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/helper-numbers": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
+ "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/floating-point-hex-parser": "1.11.6",
+ "@webassemblyjs/helper-api-error": "1.11.6",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
"dev": true
},
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true
},
- "node_modules/abab": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
- "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
- "deprecated": "Use your platform's native atob() and btoa() methods instead",
- "dev": true
- },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"node": ">=0.4.0"
}
},
- "node_modules/acorn-globals": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
- "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
- "dev": true,
- "dependencies": {
- "acorn": "^8.1.0",
- "acorn-walk": "^8.0.2"
- }
- },
"node_modules/acorn-import-attributes": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
- "node_modules/acorn-walk": {
- "version": "8.3.3",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz",
- "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==",
- "dev": true,
- "dependencies": {
- "acorn": "^8.11.0"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
"node_modules/adjust-sourcemap-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz",
}
},
"node_modules/agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
+ "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"dev": true,
- "dependencies": {
- "debug": "4"
- },
+ "license": "MIT",
"engines": {
- "node": ">= 6.0.0"
+ "node": ">= 14"
}
},
"node_modules/ajv": {
"@vue/reactivity": "~3.1.1"
}
},
- "node_modules/ansi-escapes": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
- "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
- "dev": true,
- "dependencies": {
- "type-fest": "^0.21.3"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/ansi-html-community": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz",
"node": ">=8"
}
},
- "node_modules/ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
- "dependencies": {
- "color-convert": "^1.9.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true
},
- "node_modules/argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "dev": true,
- "dependencies": {
- "sprintf-js": "~1.0.2"
- }
- },
"node_modules/aria-query": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
"inherits": "2.0.3"
}
},
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"proxy-from-env": "^1.1.0"
}
},
- "node_modules/babel-jest": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
- "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
- "dev": true,
- "dependencies": {
- "@jest/transform": "^29.7.0",
- "@types/babel__core": "^7.1.14",
- "babel-plugin-istanbul": "^6.1.1",
- "babel-preset-jest": "^29.6.3",
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.8.0"
- }
- },
- "node_modules/babel-jest/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/babel-jest/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/babel-jest/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/babel-jest/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/babel-jest/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/babel-jest/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/babel-loader": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz",
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/babel-plugin-istanbul": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
- "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
- "dev": true,
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@istanbuljs/load-nyc-config": "^1.0.0",
- "@istanbuljs/schema": "^0.1.2",
- "istanbul-lib-instrument": "^5.0.4",
- "test-exclude": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/babel-plugin-jest-hoist": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
- "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
- "dev": true,
- "dependencies": {
- "@babel/template": "^7.3.3",
- "@babel/types": "^7.3.3",
- "@types/babel__core": "^7.1.14",
- "@types/babel__traverse": "^7.0.6"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
"node_modules/babel-plugin-polyfill-corejs2": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz",
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
- "node_modules/babel-preset-current-node-syntax": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
- "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==",
- "dev": true,
- "dependencies": {
- "@babel/plugin-syntax-async-generators": "^7.8.4",
- "@babel/plugin-syntax-bigint": "^7.8.3",
- "@babel/plugin-syntax-class-properties": "^7.12.13",
- "@babel/plugin-syntax-class-static-block": "^7.14.5",
- "@babel/plugin-syntax-import-attributes": "^7.24.7",
- "@babel/plugin-syntax-import-meta": "^7.10.4",
- "@babel/plugin-syntax-json-strings": "^7.8.3",
- "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
- "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
- "@babel/plugin-syntax-numeric-separator": "^7.10.4",
- "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
- "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
- "@babel/plugin-syntax-optional-chaining": "^7.8.3",
- "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
- "@babel/plugin-syntax-top-level-await": "^7.14.5"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/babel-preset-jest": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
- "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
- "dev": true,
- "dependencies": {
- "babel-plugin-jest-hoist": "^29.6.3",
- "babel-preset-current-node-syntax": "^1.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
}
},
"node_modules/browserslist": {
- "version": "4.23.3",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
- "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
+ "version": "4.25.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz",
+ "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==",
"dev": true,
"funding": [
{
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
- "caniuse-lite": "^1.0.30001646",
- "electron-to-chromium": "^1.5.4",
- "node-releases": "^2.0.18",
- "update-browserslist-db": "^1.1.0"
+ "caniuse-lite": "^1.0.30001718",
+ "electron-to-chromium": "^1.5.160",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.3"
},
"bin": {
"browserslist": "cli.js"
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/bser": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
- "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
- "dev": true,
- "dependencies": {
- "node-int64": "^0.4.0"
- }
- },
"node_modules/buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
"node": ">= 0.8"
}
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"tslib": "^2.0.3"
}
},
- "node_modules/camelcase": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001655",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz",
- "integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==",
+ "version": "1.0.30001724",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001724.tgz",
+ "integrity": "sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
- ]
+ ],
+ "license": "CC-BY-4.0"
},
- "node_modules/chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "node_modules/chai": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
+ "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
},
"engines": {
- "node": ">=4"
- }
- },
- "node_modules/char-regex": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
- "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
- "dev": true,
- "engines": {
- "node": ">=10"
+ "node": ">=12"
}
},
"node_modules/charenc": {
"node": "*"
}
},
+ "node_modules/check-error": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"node": ">=6.0"
}
},
- "node_modules/ci-info": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
- "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
"safe-buffer": "^5.0.1"
}
},
- "node_modules/cjs-module-lexer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.0.tgz",
- "integrity": "sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==",
- "dev": true
- },
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"node": ">=6"
}
},
- "node_modules/co": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
- "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
- "dev": true,
- "engines": {
- "iojs": ">= 1.0.0",
- "node": ">= 0.12.0"
- }
- },
"node_modules/codemirror": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
"@codemirror/view": "^6.0.0"
}
},
- "node_modules/collect-v8-coverage": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
- "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
- "dev": true
- },
"node_modules/collect.js": {
"version": "4.36.1",
"resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.36.1.tgz",
"integrity": "sha512-jd97xWPKgHn6uvK31V6zcyPd40lUJd7gpYxbN2VOVxGWO4tyvS9Li4EpsFjXepGTo2tYcOTC4a8YsbQXMJ4XUw==",
"dev": true
},
- "node_modules/color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
- "dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "dev": true
- },
"node_modules/colord": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
"node": ">=10"
}
},
+ "node_modules/cosmiconfig/node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"sha.js": "^2.4.8"
}
},
- "node_modules/create-jest": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
- "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==",
- "dev": true,
- "dependencies": {
- "@jest/types": "^29.6.3",
- "chalk": "^4.0.0",
- "exit": "^0.1.2",
- "graceful-fs": "^4.2.9",
- "jest-config": "^29.7.0",
- "jest-util": "^29.7.0",
- "prompts": "^2.0.1"
- },
- "bin": {
- "create-jest": "bin/create-jest.js"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/create-jest/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/create-jest/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/create-jest/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/create-jest/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/create-jest/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/create-jest/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"postcss": "^8.2.15"
}
},
+ "node_modules/cssnano/node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/csso": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz",
"node": ">=8.0.0"
}
},
- "node_modules/cssom": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
- "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
- "dev": true
- },
"node_modules/cssstyle": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
- "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.5.0.tgz",
+ "integrity": "sha512-/7gw8TGrvH/0g564EnhgFZogTMVe+lifpB7LWU+PEsiq5o83TUXR3fDbzTRXOJhoJwck5IS9ez3Em5LNMMO2aw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "cssom": "~0.3.6"
+ "@asamuzakjp/css-color": "^3.2.0",
+ "rrweb-cssom": "^0.8.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/cssstyle/node_modules/cssom": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
- "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
- "dev": true
- },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
}
},
"node_modules/data-urls": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
- "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "abab": "^2.0.6",
- "whatwg-mimetype": "^3.0.0",
- "whatwg-url": "^11.0.0"
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/data-view-buffer": {
}
},
"node_modules/debug": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
- "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "ms": "2.1.2"
+ "ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/decimal.js": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
- "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
- "dev": true
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
+ "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
},
- "node_modules/dedent": {
- "version": "1.5.3",
- "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
- "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==",
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
"dev": true,
- "peerDependencies": {
- "babel-plugin-macros": "^3.1.0"
- },
- "peerDependenciesMeta": {
- "babel-plugin-macros": {
- "optional": true
- }
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
}
},
"node_modules/deep-equal": {
"npm": "1.2.8000 || >= 1.4.16"
}
},
- "node_modules/detect-newline": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
- "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/detect-node": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true
},
- "node_modules/diff-sequences": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
- "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
- "dev": true,
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
"node_modules/diffie-hellman": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
}
]
},
- "node_modules/domexception": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
- "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
- "deprecated": "Use your platform's native DOMException instead",
- "dev": true,
- "dependencies": {
- "webidl-conversions": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/domhandler": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz",
"dev": true
},
"node_modules/electron-to-chromium": {
- "version": "1.5.13",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz",
- "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==",
- "dev": true
+ "version": "1.5.171",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.171.tgz",
+ "integrity": "sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==",
+ "dev": true,
+ "license": "ISC"
},
"node_modules/elliptic": {
"version": "6.5.7",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true
},
- "node_modules/emittery": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
- "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/emittery?sponsor=1"
- }
- },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
}
},
"node_modules/es-module-lexer": {
- "version": "1.5.4",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
- "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
- "dev": true
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/es-object-atoms": {
"version": "1.0.0",
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/esbuild": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
+ "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.5",
+ "@esbuild/android-arm": "0.25.5",
+ "@esbuild/android-arm64": "0.25.5",
+ "@esbuild/android-x64": "0.25.5",
+ "@esbuild/darwin-arm64": "0.25.5",
+ "@esbuild/darwin-x64": "0.25.5",
+ "@esbuild/freebsd-arm64": "0.25.5",
+ "@esbuild/freebsd-x64": "0.25.5",
+ "@esbuild/linux-arm": "0.25.5",
+ "@esbuild/linux-arm64": "0.25.5",
+ "@esbuild/linux-ia32": "0.25.5",
+ "@esbuild/linux-loong64": "0.25.5",
+ "@esbuild/linux-mips64el": "0.25.5",
+ "@esbuild/linux-ppc64": "0.25.5",
+ "@esbuild/linux-riscv64": "0.25.5",
+ "@esbuild/linux-s390x": "0.25.5",
+ "@esbuild/linux-x64": "0.25.5",
+ "@esbuild/netbsd-arm64": "0.25.5",
+ "@esbuild/netbsd-x64": "0.25.5",
+ "@esbuild/openbsd-arm64": "0.25.5",
+ "@esbuild/openbsd-x64": "0.25.5",
+ "@esbuild/sunos-x64": "0.25.5",
+ "@esbuild/win32-arm64": "0.25.5",
+ "@esbuild/win32-ia32": "0.25.5",
+ "@esbuild/win32-x64": "0.25.5"
+ }
+ },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"dev": true
},
- "node_modules/escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "dev": true,
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/escodegen": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
- "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
- "dev": true,
- "dependencies": {
- "esprima": "^4.0.1",
- "estraverse": "^5.2.0",
- "esutils": "^2.0.2"
- },
- "bin": {
- "escodegen": "bin/escodegen.js",
- "esgenerate": "bin/esgenerate.js"
- },
- "engines": {
- "node": ">=6.0"
- },
- "optionalDependencies": {
- "source-map": "~0.6.1"
- }
- },
"node_modules/eslint": {
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true,
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/esquery": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
"node": ">=4.0"
}
},
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
- "node_modules/exit": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
- "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
- "dev": true,
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/expect": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
- "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+ "node_modules/expect-type": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz",
+ "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==",
"dev": true,
- "dependencies": {
- "@jest/expect-utils": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0"
- },
+ "license": "Apache-2.0",
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=12.0.0"
}
},
"node_modules/express": {
"node": ">=8.6.0"
}
},
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true
- },
- "node_modules/fast-uri": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz",
- "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==",
- "dev": true
- },
- "node_modules/fastest-levenshtein": {
- "version": "1.0.16",
- "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
- "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==",
- "dev": true,
- "engines": {
- "node": ">= 4.9.1"
- }
- },
- "node_modules/fastq": {
- "version": "1.17.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
- "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
- "dev": true,
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/faye-websocket": {
- "version": "0.11.4",
- "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
- "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
- "dev": true,
- "dependencies": {
- "websocket-driver": ">=0.5.1"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/fb-watchman": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
- "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
- "dev": true,
- "dependencies": {
- "bser": "2.1.1"
- }
- },
- "node_modules/file-entry-cache": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
- "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
- "dev": true,
- "dependencies": {
- "flat-cache": "^3.0.4"
- },
- "engines": {
- "node": "^10.12.0 || >=12.0.0"
- }
- },
- "node_modules/file-loader": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
- "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
- "dev": true,
- "dependencies": {
- "loader-utils": "^2.0.0",
- "schema-utils": "^3.0.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
- "peerDependencies": {
- "webpack": "^4.0.0 || ^5.0.0"
- }
- },
- "node_modules/file-loader/node_modules/schema-utils": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
- "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
- "dev": true,
- "dependencies": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/file-saver": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
- "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
- },
- "node_modules/file-type": {
- "version": "12.4.2",
- "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
- "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/finalhandler": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
- "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
- "dev": true,
- "dependencies": {
- "debug": "2.6.9",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "on-finished": "2.4.1",
- "parseurl": "~1.3.3",
- "statuses": "2.0.1",
- "unpipe": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/finalhandler/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/finalhandler/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true
- },
- "node_modules/find-cache-dir": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
- "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
- "dev": true,
- "dependencies": {
- "commondir": "^1.0.1",
- "make-dir": "^3.0.2",
- "pkg-dir": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
- }
- },
- "node_modules/find-cache-dir/node_modules/make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
- "dev": true,
- "dependencies": {
- "semver": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "dev": true,
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/flat": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
- "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
- "dev": true,
- "bin": {
- "flat": "cli.js"
- }
- },
- "node_modules/flat-cache": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
- "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
- "dev": true,
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.3",
- "rimraf": "^3.0.2"
- },
- "engines": {
- "node": "^10.12.0 || >=12.0.0"
- }
- },
- "node_modules/flatted": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
- "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
- "dev": true
- },
- "node_modules/follow-redirects": {
- "version": "1.15.6",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
- "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
- "dev": true,
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
- "engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
- }
- },
- "node_modules/for-each": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
- "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
- "dev": true,
- "dependencies": {
- "is-callable": "^1.1.3"
- }
- },
- "node_modules/foreground-child": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
- "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
- "dev": true,
- "dependencies": {
- "cross-spawn": "^7.0.0",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/foreground-child/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
- "dev": true,
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/formik": {
- "version": "2.4.6",
- "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz",
- "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==",
- "funding": [
- {
- "type": "individual",
- "url": "https://opencollective.com/formik"
- }
- ],
- "dependencies": {
- "@types/hoist-non-react-statics": "^3.3.1",
- "deepmerge": "^2.1.1",
- "hoist-non-react-statics": "^3.3.0",
- "lodash": "^4.17.21",
- "lodash-es": "^4.17.21",
- "react-fast-compare": "^2.0.1",
- "tiny-warning": "^1.0.2",
- "tslib": "^2.0.0"
- },
- "peerDependencies": {
- "react": ">=16.8.0"
- }
- },
- "node_modules/forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
- "dev": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fraction.js": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
- "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
- "dev": true,
- "engines": {
- "node": "*"
- },
- "funding": {
- "type": "patreon",
- "url": "https://github.com/sponsors/rawify"
- }
- },
- "node_modules/fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
- "dev": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fs-extra": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
- "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
- "dev": true,
- "dependencies": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^6.0.1",
- "universalify": "^2.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/fs-monkey": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz",
- "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==",
- "dev": true
- },
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/function.prototype.name": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz",
- "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.2.0",
- "es-abstract": "^1.22.1",
- "functions-have-names": "^1.2.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/functions-have-names": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
- "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
- "dev": true,
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/fuzzy-search": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/fuzzy-search/-/fuzzy-search-3.2.1.tgz",
- "integrity": "sha512-vAcPiyomt1ioKAsAL2uxSABHJ4Ju/e4UeDM+g1OlR0vV4YhLGMNsdLNvZTpEDY4JCSt0E4hASCNM5t2ETtsbyg=="
- },
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "dev": true,
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
- "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "has-proto": "^1.0.1",
- "has-symbols": "^1.0.3",
- "hasown": "^2.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-package-type": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
- "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
- "dev": true,
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/get-stream": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
- "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/get-symbol-description": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
- "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.5",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
- "dev": true,
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/glob-to-regexp": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
- "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
- "dev": true
- },
- "node_modules/globals": {
- "version": "11.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
- "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/globalthis": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
- "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
- "dev": true,
- "dependencies": {
- "define-properties": "^1.2.1",
- "gopd": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/globby": {
- "version": "10.0.2",
- "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz",
- "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==",
- "dev": true,
- "dependencies": {
- "@types/glob": "^7.1.1",
- "array-union": "^2.1.0",
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.0.3",
- "glob": "^7.1.3",
- "ignore": "^5.1.1",
- "merge2": "^1.2.3",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/gopd": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
- "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
- "dependencies": {
- "get-intrinsic": "^1.1.3"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true
- },
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true
- },
- "node_modules/growly": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
- "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==",
- "dev": true
- },
- "node_modules/handle-thing": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
- "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==",
- "dev": true
- },
- "node_modules/has-bigints": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
- "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
- "dev": true,
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/has-property-descriptors": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
- "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
- "dependencies": {
- "es-define-property": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-proto": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
- "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "dev": true,
- "dependencies": {
- "has-symbols": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hash-base": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
- "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==",
- "dev": true,
- "dependencies": {
- "inherits": "^2.0.1",
- "safe-buffer": "^5.0.1"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/hash-sum": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
- "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==",
- "dev": true
- },
- "node_modules/hash.js": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
- "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
- "dev": true,
- "dependencies": {
- "inherits": "^2.0.3",
- "minimalistic-assert": "^1.0.1"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/he": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
- "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
- "dev": true,
- "bin": {
- "he": "bin/he"
- }
- },
- "node_modules/hmac-drbg": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
- "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==",
- "dev": true,
- "dependencies": {
- "hash.js": "^1.0.3",
- "minimalistic-assert": "^1.0.0",
- "minimalistic-crypto-utils": "^1.0.1"
- }
- },
- "node_modules/hoist-non-react-statics": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
- "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
- "dependencies": {
- "react-is": "^16.7.0"
- }
- },
- "node_modules/hpack.js": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
- "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==",
- "dev": true,
- "dependencies": {
- "inherits": "^2.0.1",
- "obuf": "^1.0.0",
- "readable-stream": "^2.0.1",
- "wbuf": "^1.1.0"
- }
- },
- "node_modules/html-encoding-sniffer": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
- "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
- "dev": true,
- "dependencies": {
- "whatwg-encoding": "^2.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/html-entities": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
- "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/mdevils"
- },
- {
- "type": "patreon",
- "url": "https://patreon.com/mdevils"
- }
- ]
- },
- "node_modules/html-escaper": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
- "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
- "dev": true
- },
- "node_modules/html-loader": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-1.3.2.tgz",
- "integrity": "sha512-DEkUwSd0sijK5PF3kRWspYi56XP7bTNkyg5YWSzBdjaSDmvCufep5c4Vpb3PBf6lUL0YPtLwBfy9fL0t5hBAGA==",
- "dev": true,
- "dependencies": {
- "html-minifier-terser": "^5.1.1",
- "htmlparser2": "^4.1.0",
- "loader-utils": "^2.0.0",
- "schema-utils": "^3.0.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
- "peerDependencies": {
- "webpack": "^4.0.0 || ^5.0.0"
- }
- },
- "node_modules/html-loader/node_modules/schema-utils": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
- "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
- "dev": true,
- "dependencies": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/html-minifier-terser": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz",
- "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==",
- "dev": true,
- "dependencies": {
- "camel-case": "^4.1.1",
- "clean-css": "^4.2.3",
- "commander": "^4.1.1",
- "he": "^1.2.0",
- "param-case": "^3.0.3",
- "relateurl": "^0.2.7",
- "terser": "^4.6.3"
- },
- "bin": {
- "html-minifier-terser": "cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/html-minifier-terser/node_modules/clean-css": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz",
- "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==",
- "dev": true,
- "dependencies": {
- "source-map": "~0.6.0"
- },
- "engines": {
- "node": ">= 4.0"
- }
- },
- "node_modules/html-minifier-terser/node_modules/commander": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
- "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
- "dev": true,
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/html-minifier-terser/node_modules/terser": {
- "version": "4.8.1",
- "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz",
- "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==",
- "dev": true,
- "dependencies": {
- "commander": "^2.20.0",
- "source-map": "~0.6.1",
- "source-map-support": "~0.5.12"
- },
- "bin": {
- "terser": "bin/terser"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/html-minifier-terser/node_modules/terser/node_modules/commander": {
- "version": "2.20.3",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
- "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
- "dev": true
- },
- "node_modules/html-parse-stringify": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
- "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
- "dependencies": {
- "void-elements": "3.1.0"
- }
- },
- "node_modules/htmlparser2": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
- "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
- "dev": true,
- "dependencies": {
- "domelementtype": "^2.0.1",
- "domhandler": "^3.0.0",
- "domutils": "^2.0.0",
- "entities": "^2.0.0"
- }
- },
- "node_modules/http-deceiver": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
- "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==",
- "dev": true
- },
- "node_modules/http-errors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
- "dev": true,
- "dependencies": {
- "depd": "2.0.0",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "toidentifier": "1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/http-parser-js": {
- "version": "0.5.8",
- "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
- "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==",
- "dev": true
- },
- "node_modules/http-proxy": {
- "version": "1.18.1",
- "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
- "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
- "dev": true,
- "dependencies": {
- "eventemitter3": "^4.0.0",
- "follow-redirects": "^1.0.0",
- "requires-port": "^1.0.0"
- },
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/http-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
- "dev": true,
- "dependencies": {
- "@tootallnate/once": "2",
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/http-proxy-middleware": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz",
- "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==",
- "dev": true,
- "dependencies": {
- "@types/http-proxy": "^1.17.8",
- "http-proxy": "^1.18.1",
- "is-glob": "^4.0.1",
- "is-plain-obj": "^3.0.0",
- "micromatch": "^4.0.2"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "@types/express": "^4.17.13"
- },
- "peerDependenciesMeta": {
- "@types/express": {
- "optional": true
- }
- }
- },
- "node_modules/https-browserify": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
- "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==",
- "dev": true
- },
- "node_modules/https-proxy-agent": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
- "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
- "dev": true,
- "dependencies": {
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/human-signals": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
- "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
- "dev": true,
- "engines": {
- "node": ">=10.17.0"
- }
- },
- "node_modules/i18next": {
- "version": "23.14.0",
- "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.14.0.tgz",
- "integrity": "sha512-Y5GL4OdA8IU2geRrt2+Uc1iIhsjICdHZzT9tNwQ3TVqdNzgxHToGCKf/TPRP80vTCAP6svg2WbbJL+Gx5MFQVA==",
- "funding": [
- {
- "type": "individual",
- "url": "https://locize.com"
- },
- {
- "type": "individual",
- "url": "https://locize.com/i18next.html"
- },
- {
- "type": "individual",
- "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
- }
- ],
- "dependencies": {
- "@babel/runtime": "^7.23.2"
- }
- },
- "node_modules/i18next-browser-languagedetector": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
- "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
- "dependencies": {
- "@babel/runtime": "^7.23.2"
- }
- },
- "node_modules/iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "dev": true,
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/icss-utils": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
- "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
- "dev": true,
- "engines": {
- "node": "^10 || ^12 || >= 14"
- },
- "peerDependencies": {
- "postcss": "^8.1.0"
- }
- },
- "node_modules/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/imagemin": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.1.tgz",
- "integrity": "sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w==",
- "dev": true,
- "dependencies": {
- "file-type": "^12.0.0",
- "globby": "^10.0.0",
- "graceful-fs": "^4.2.2",
- "junk": "^3.1.0",
- "make-dir": "^3.0.0",
- "p-pipe": "^3.0.0",
- "replace-ext": "^1.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/imagemin/node_modules/make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
- "dev": true,
- "dependencies": {
- "semver": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/img-loader": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/img-loader/-/img-loader-4.0.0.tgz",
- "integrity": "sha512-UwRcPQdwdOyEHyCxe1V9s9YFwInwEWCpoO+kJGfIqDrBDqA8jZUsEZTxQ0JteNPGw/Gupmwesk2OhLTcnw6tnQ==",
- "dev": true,
- "dependencies": {
- "loader-utils": "^1.1.0"
- },
- "engines": {
- "node": ">=12"
- },
- "peerDependencies": {
- "imagemin": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/img-loader/node_modules/json5": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
- "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
- "dev": true,
- "dependencies": {
- "minimist": "^1.2.0"
- },
- "bin": {
- "json5": "lib/cli.js"
- }
- },
- "node_modules/img-loader/node_modules/loader-utils": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
- "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
- "dev": true,
- "dependencies": {
- "big.js": "^5.2.2",
- "emojis-list": "^3.0.0",
- "json5": "^1.0.1"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/immediate": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
- "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
- },
- "node_modules/immutable": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
- "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
- "dev": true
- },
- "node_modules/import-fresh": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
- "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
- "dev": true,
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/import-fresh/node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/import-local": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
- "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
- "dev": true,
- "dependencies": {
- "pkg-dir": "^4.2.0",
- "resolve-cwd": "^3.0.0"
- },
- "bin": {
- "import-local-fixture": "fixtures/cli.js"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/indent-string": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
- "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
- "dev": true,
- "dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "node_modules/internal-slot": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
- "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
- "dev": true,
- "dependencies": {
- "es-errors": "^1.3.0",
- "hasown": "^2.0.0",
- "side-channel": "^1.0.4"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/internmap": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
- "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/interpret": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
- "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
- "dev": true,
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/invariant": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
- "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
- "dependencies": {
- "loose-envify": "^1.0.0"
- }
- },
- "node_modules/ipaddr.js": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
- "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
- "dev": true,
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/is-arguments": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
- "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-array-buffer": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
- "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "get-intrinsic": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-arrayish": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
- "dev": true
- },
- "node_modules/is-async-function": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz",
- "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==",
- "dev": true,
- "dependencies": {
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-bigint": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
- "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
- "dev": true,
- "dependencies": {
- "has-bigints": "^1.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-boolean-object": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
- "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-buffer": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
- "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
- "dev": true
- },
- "node_modules/is-callable": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
- "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
- "dev": true,
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.15.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
- "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
- "dev": true,
- "dependencies": {
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-data-view": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz",
- "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==",
- "dev": true,
- "dependencies": {
- "is-typed-array": "^1.1.13"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-date-object": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
- "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
- "dev": true,
- "dependencies": {
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-docker": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
- "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
- "dev": true,
- "bin": {
- "is-docker": "cli.js"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-finalizationregistry": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz",
- "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-generator-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
- "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/is-generator-function": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
- "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
- "dev": true,
- "dependencies": {
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-map": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
- "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
- "dev": true,
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-negative-zero": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
- "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
- "dev": true,
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/is-number-object": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
- "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
- "dev": true,
- "dependencies": {
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-path-inside": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
- "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-plain-obj": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
- "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/is-plain-object": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
- "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
- "dev": true,
- "dependencies": {
- "isobject": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-potential-custom-element-name": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
- "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
- "dev": true
- },
- "node_modules/is-regex": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
- "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-set": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
- "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
- "dev": true,
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-shared-array-buffer": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
- "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.7"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
- "dev": true,
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/is-string": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
- "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
- "dev": true,
- "dependencies": {
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-symbol": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
- "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
- "dev": true,
- "dependencies": {
- "has-symbols": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-typed-array": {
- "version": "1.1.13",
- "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz",
- "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==",
- "dev": true,
- "dependencies": {
- "which-typed-array": "^1.1.14"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakmap": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
- "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
- "dev": true,
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakref": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
- "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakset": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
- "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.7",
- "get-intrinsic": "^1.2.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-wsl": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
- "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
- "is-docker": "^2.0.0"
+ "is-glob": "^4.0.1"
},
"engines": {
- "node": ">=8"
+ "node": ">= 6"
}
},
- "node_modules/isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
},
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
- "node_modules/isobject": {
+ "node_modules/fast-uri": {
"version": "3.0.1",
- "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
- "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz",
+ "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==",
+ "dev": true
},
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "node_modules/fastest-levenshtein": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
+ "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==",
"dev": true,
"engines": {
- "node": ">=8"
+ "node": ">= 4.9.1"
}
},
- "node_modules/istanbul-lib-instrument": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
- "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
+ "node_modules/fastq": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
"dev": true,
"dependencies": {
- "@babel/core": "^7.12.3",
- "@babel/parser": "^7.14.7",
- "@istanbuljs/schema": "^0.1.2",
- "istanbul-lib-coverage": "^3.2.0",
- "semver": "^6.3.0"
- },
- "engines": {
- "node": ">=8"
+ "reusify": "^1.0.4"
}
},
- "node_modules/istanbul-lib-report": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
- "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "node_modules/faye-websocket": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
+ "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
"dev": true,
"dependencies": {
- "istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^4.0.0",
- "supports-color": "^7.1.0"
+ "websocket-driver": ">=0.5.1"
},
"engines": {
- "node": ">=10"
- }
- },
- "node_modules/istanbul-lib-report/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
+ "node": ">=0.8.0"
}
},
- "node_modules/istanbul-lib-report/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
+ "flat-cache": "^3.0.4"
},
"engines": {
- "node": ">=8"
+ "node": "^10.12.0 || >=12.0.0"
}
},
- "node_modules/istanbul-lib-source-maps": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
- "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "node_modules/file-loader": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
+ "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
"dev": true,
"dependencies": {
- "debug": "^4.1.1",
- "istanbul-lib-coverage": "^3.0.0",
- "source-map": "^0.6.1"
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
},
"engines": {
- "node": ">=10"
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.0.0 || ^5.0.0"
}
},
- "node_modules/istanbul-reports": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
- "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
+ "node_modules/file-loader/node_modules/schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"dependencies": {
- "html-escaper": "^2.0.0",
- "istanbul-lib-report": "^3.0.0"
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
},
"engines": {
- "node": ">=8"
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
}
},
- "node_modules/iterator.prototype": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz",
- "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==",
- "dev": true,
- "dependencies": {
- "define-properties": "^1.2.1",
- "get-intrinsic": "^1.2.1",
- "has-symbols": "^1.0.3",
- "reflect.getprototypeof": "^1.0.4",
- "set-function-name": "^2.0.1"
- }
+ "node_modules/file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
- "node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "node_modules/file-type": {
+ "version": "12.4.2",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
+ "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==",
"dev": true,
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/jest": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
- "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
- "@jest/core": "^29.7.0",
- "@jest/types": "^29.6.3",
- "import-local": "^3.0.2",
- "jest-cli": "^29.7.0"
- },
- "bin": {
- "jest": "bin/jest.js"
+ "to-regex-range": "^5.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
- },
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
+ "node": ">=8"
}
},
- "node_modules/jest-changed-files": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
- "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==",
+ "node_modules/finalhandler": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+ "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"dev": true,
"dependencies": {
- "execa": "^5.0.0",
- "jest-util": "^29.7.0",
- "p-limit": "^3.1.0"
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.8"
}
},
- "node_modules/jest-circus": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz",
- "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==",
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/expect": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "co": "^4.6.0",
- "dedent": "^1.0.0",
- "is-generator-fn": "^2.0.0",
- "jest-each": "^29.7.0",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-runtime": "^29.7.0",
- "jest-snapshot": "^29.7.0",
- "jest-util": "^29.7.0",
- "p-limit": "^3.1.0",
- "pretty-format": "^29.7.0",
- "pure-rand": "^6.0.0",
- "slash": "^3.0.0",
- "stack-utils": "^2.0.3"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/jest-circus/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true
+ },
+ "node_modules/find-cache-dir": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
+ "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
"dev": true,
"dependencies": {
- "color-convert": "^2.0.1"
+ "commondir": "^1.0.1",
+ "make-dir": "^3.0.2",
+ "pkg-dir": "^4.1.0"
},
"engines": {
"node": ">=8"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
}
},
- "node_modules/jest-circus/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/find-cache-dir/node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dev": true,
"dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
+ "semver": "^6.0.0"
},
"engines": {
- "node": ">=10"
+ "node": ">=8"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-circus/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"dependencies": {
- "color-name": "~1.1.4"
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
},
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/jest-circus/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/jest-circus/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
"engines": {
"node": ">=8"
}
},
- "node_modules/jest-circus/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
- "dev": true,
- "dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/jest-circus/node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "node_modules/flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
"dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "bin": {
+ "flat": "cli.js"
}
},
- "node_modules/jest-circus/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true
- },
- "node_modules/jest-circus/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
},
"engines": {
- "node": ">=8"
+ "node": "^10.12.0 || >=12.0.0"
}
},
- "node_modules/jest-cli": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz",
- "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==",
+ "node_modules/flatted": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
+ "dev": true
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true,
- "dependencies": {
- "@jest/core": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/types": "^29.6.3",
- "chalk": "^4.0.0",
- "create-jest": "^29.7.0",
- "exit": "^0.1.2",
- "import-local": "^3.0.2",
- "jest-config": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "yargs": "^17.3.1"
- },
- "bin": {
- "jest": "bin/jest.js"
- },
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ "node": ">=4.0"
},
"peerDependenciesMeta": {
- "node-notifier": {
+ "debug": {
"optional": true
}
}
},
- "node_modules/jest-cli/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
"dev": true,
"dependencies": {
- "color-convert": "^2.0.1"
+ "is-callable": "^1.1.3"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
+ "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
},
"engines": {
- "node": ">=8"
+ "node": ">=14"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/jest-cli/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/foreground-child/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
"engines": {
- "node": ">=10"
+ "node": ">=14"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/jest-cli/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dev": true,
"dependencies": {
- "color-name": "~1.1.4"
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
},
"engines": {
- "node": ">=7.0.0"
+ "node": ">= 6"
}
},
- "node_modules/jest-cli/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
+ "node_modules/formik": {
+ "version": "2.4.6",
+ "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz",
+ "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://opencollective.com/formik"
+ }
+ ],
+ "dependencies": {
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "deepmerge": "^2.1.1",
+ "hoist-non-react-statics": "^3.3.0",
+ "lodash": "^4.17.21",
+ "lodash-es": "^4.17.21",
+ "react-fast-compare": "^2.0.1",
+ "tiny-warning": "^1.0.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
},
- "node_modules/jest-cli/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"dev": true,
"engines": {
- "node": ">=8"
+ "node": ">= 0.6"
}
},
- "node_modules/jest-cli/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
"dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
+ "engines": {
+ "node": "*"
},
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "dev": true,
"engines": {
- "node": ">=8"
+ "node": ">= 0.6"
}
},
- "node_modules/jest-config": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
- "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"dependencies": {
- "@babel/core": "^7.11.6",
- "@jest/test-sequencer": "^29.7.0",
- "@jest/types": "^29.6.3",
- "babel-jest": "^29.7.0",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "deepmerge": "^4.2.2",
- "glob": "^7.1.3",
- "graceful-fs": "^4.2.9",
- "jest-circus": "^29.7.0",
- "jest-environment-node": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-regex-util": "^29.6.3",
- "jest-resolve": "^29.7.0",
- "jest-runner": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "micromatch": "^4.0.4",
- "parse-json": "^5.2.0",
- "pretty-format": "^29.7.0",
- "slash": "^3.0.0",
- "strip-json-comments": "^3.1.1"
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "@types/node": "*",
- "ts-node": ">=9.0.0"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "ts-node": {
- "optional": true
- }
+ "node": ">=12"
}
},
- "node_modules/jest-config/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/fs-monkey": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz",
+ "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==",
+ "dev": true
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">=8"
- },
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-config/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/function.prototype.name": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz",
+ "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==",
"dev": true,
"dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "functions-have-names": "^1.2.3"
},
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-config/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
"dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-config/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
+ "node_modules/fuzzy-search": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/fuzzy-search/-/fuzzy-search-3.2.1.tgz",
+ "integrity": "sha512-vAcPiyomt1ioKAsAL2uxSABHJ4Ju/e4UeDM+g1OlR0vV4YhLGMNsdLNvZTpEDY4JCSt0E4hASCNM5t2ETtsbyg=="
},
- "node_modules/jest-config/node_modules/deepmerge": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
"engines": {
- "node": ">=0.10.0"
+ "node": ">=6.9.0"
}
},
- "node_modules/jest-config/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"engines": {
- "node": ">=8"
+ "node": "6.* || 8.* || >= 10.*"
}
},
- "node_modules/jest-config/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
- "dev": true,
+ "node_modules/get-intrinsic": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/jest-config/node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-config/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true
- },
- "node_modules/jest-config/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/jest-diff": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
- "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
- "dev": true,
- "dependencies": {
- "chalk": "^4.0.0",
- "diff-sequences": "^29.6.3",
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
+ "node": ">=10"
},
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-diff/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/get-symbol-description": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
+ "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==",
"dev": true,
"dependencies": {
- "color-convert": "^2.0.1"
+ "call-bind": "^1.0.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4"
},
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-diff/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
},
"engines": {
- "node": ">=10"
+ "node": "*"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/jest-diff/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"dependencies": {
- "color-name": "~1.1.4"
+ "is-glob": "^4.0.3"
},
"engines": {
- "node": ">=7.0.0"
+ "node": ">=10.13.0"
}
},
- "node_modules/jest-diff/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "node_modules/glob-to-regexp": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true
},
- "node_modules/jest-diff/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "node_modules/globals": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz",
+ "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-diff/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
"dev": true,
"dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-diff/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true
- },
- "node_modules/jest-diff/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/globby": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz",
+ "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
+ "@types/glob": "^7.1.1",
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.0.3",
+ "glob": "^7.1.3",
+ "ignore": "^5.1.1",
+ "merge2": "^1.2.3",
+ "slash": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
- "node_modules/jest-docblock": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz",
- "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==",
- "dev": true,
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dependencies": {
- "detect-newline": "^3.0.0"
+ "get-intrinsic": "^1.1.3"
},
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-each": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz",
- "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==",
- "dev": true,
- "dependencies": {
- "@jest/types": "^29.6.3",
- "chalk": "^4.0.0",
- "jest-get-type": "^29.6.3",
- "jest-util": "^29.7.0",
- "pretty-format": "^29.7.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
},
- "node_modules/jest-each/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
+ },
+ "node_modules/growly": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
+ "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==",
+ "dev": true
+ },
+ "node_modules/handle-thing": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
+ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==",
+ "dev": true
+ },
+ "node_modules/has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
"dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-each/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
+ "es-define-property": "^1.0.0"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-each/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
+ "node_modules/has-proto": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
"engines": {
- "node": ">=7.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-each/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/jest-each/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-each/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "has-symbols": "^1.0.3"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-each/node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "node_modules/hash-base": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
+ "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==",
"dev": true,
- "engines": {
- "node": ">=10"
+ "dependencies": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
},
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "engines": {
+ "node": ">=4"
}
},
- "node_modules/jest-each/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "node_modules/hash-sum": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
+ "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==",
"dev": true
},
- "node_modules/jest-each/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/hash.js": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+ "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
+ "inherits": "^2.0.3",
+ "minimalistic-assert": "^1.0.1"
}
},
- "node_modules/jest-environment-jsdom": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz",
- "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==",
- "dev": true,
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/fake-timers": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/jsdom": "^20.0.0",
- "@types/node": "*",
- "jest-mock": "^29.7.0",
- "jest-util": "^29.7.0",
- "jsdom": "^20.0.0"
+ "function-bind": "^1.1.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "canvas": "^2.5.0"
- },
- "peerDependenciesMeta": {
- "canvas": {
- "optional": true
- }
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "bin": {
+ "he": "bin/he"
}
},
- "node_modules/jest-environment-node": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
- "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
+ "node_modules/hmac-drbg": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+ "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==",
"dev": true,
"dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/fake-timers": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "jest-mock": "^29.7.0",
- "jest-util": "^29.7.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "hash.js": "^1.0.3",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.1"
}
},
- "node_modules/jest-get-type": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
- "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
- "dev": true,
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dependencies": {
+ "react-is": "^16.7.0"
}
},
- "node_modules/jest-haste-map": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
- "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
+ "node_modules/hpack.js": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
+ "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==",
"dev": true,
"dependencies": {
- "@jest/types": "^29.6.3",
- "@types/graceful-fs": "^4.1.3",
- "@types/node": "*",
- "anymatch": "^3.0.3",
- "fb-watchman": "^2.0.0",
- "graceful-fs": "^4.2.9",
- "jest-regex-util": "^29.6.3",
- "jest-util": "^29.7.0",
- "jest-worker": "^29.7.0",
- "micromatch": "^4.0.4",
- "walker": "^1.0.8"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "optionalDependencies": {
- "fsevents": "^2.3.2"
+ "inherits": "^2.0.1",
+ "obuf": "^1.0.0",
+ "readable-stream": "^2.0.1",
+ "wbuf": "^1.1.0"
}
},
- "node_modules/jest-leak-detector": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
- "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==",
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
+ "whatwg-encoding": "^3.1.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=18"
}
},
- "node_modules/jest-leak-detector/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "node_modules/html-entities": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
+ "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/mdevils"
+ },
+ {
+ "type": "patreon",
+ "url": "https://patreon.com/mdevils"
+ }
+ ]
+ },
+ "node_modules/html-loader": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-1.3.2.tgz",
+ "integrity": "sha512-DEkUwSd0sijK5PF3kRWspYi56XP7bTNkyg5YWSzBdjaSDmvCufep5c4Vpb3PBf6lUL0YPtLwBfy9fL0t5hBAGA==",
"dev": true,
+ "dependencies": {
+ "html-minifier-terser": "^5.1.1",
+ "htmlparser2": "^4.1.0",
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ },
"engines": {
- "node": ">=10"
+ "node": ">= 10.13.0"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.0.0 || ^5.0.0"
}
},
- "node_modules/jest-leak-detector/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "node_modules/html-loader/node_modules/schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
}
},
- "node_modules/jest-leak-detector/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true
- },
- "node_modules/jest-matcher-utils": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
- "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
+ "node_modules/html-minifier-terser": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz",
+ "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==",
"dev": true,
"dependencies": {
- "chalk": "^4.0.0",
- "jest-diff": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
+ "camel-case": "^4.1.1",
+ "clean-css": "^4.2.3",
+ "commander": "^4.1.1",
+ "he": "^1.2.0",
+ "param-case": "^3.0.3",
+ "relateurl": "^0.2.7",
+ "terser": "^4.6.3"
+ },
+ "bin": {
+ "html-minifier-terser": "cli.js"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=6"
}
},
- "node_modules/jest-matcher-utils/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/html-minifier-terser/node_modules/clean-css": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz",
+ "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==",
"dev": true,
"dependencies": {
- "color-convert": "^2.0.1"
+ "source-map": "~0.6.0"
},
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">= 4.0"
}
},
- "node_modules/jest-matcher-utils/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/html-minifier-terser/node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "node": ">= 6"
}
},
- "node_modules/jest-matcher-utils/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/html-minifier-terser/node_modules/terser": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz",
+ "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==",
"dev": true,
"dependencies": {
- "color-name": "~1.1.4"
+ "commander": "^2.20.0",
+ "source-map": "~0.6.1",
+ "source-map-support": "~0.5.12"
+ },
+ "bin": {
+ "terser": "bin/terser"
},
"engines": {
- "node": ">=7.0.0"
+ "node": ">=6.0.0"
}
},
- "node_modules/jest-matcher-utils/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "node_modules/html-minifier-terser/node_modules/terser/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
- "node_modules/jest-matcher-utils/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
+ "node_modules/htmlparser2": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
+ "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
"dev": true,
- "engines": {
- "node": ">=8"
+ "dependencies": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^3.0.0",
+ "domutils": "^2.0.0",
+ "entities": "^2.0.0"
}
},
- "node_modules/jest-matcher-utils/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "node_modules/http-deceiver": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
+ "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==",
+ "dev": true
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"dev": true,
"dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">= 0.8"
}
},
- "node_modules/jest-matcher-utils/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "node_modules/http-parser-js": {
+ "version": "0.5.8",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
+ "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==",
"dev": true
},
- "node_modules/jest-matcher-utils/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/http-proxy": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
+ "eventemitter3": "^4.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=8.0.0"
}
},
- "node_modules/jest-message-util": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
- "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.12.13",
- "@jest/types": "^29.6.3",
- "@types/stack-utils": "^2.0.0",
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
- "micromatch": "^4.0.4",
- "pretty-format": "^29.7.0",
- "slash": "^3.0.0",
- "stack-utils": "^2.0.3"
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 14"
}
},
- "node_modules/jest-message-util/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/http-proxy-middleware": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz",
+ "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==",
"dev": true,
"dependencies": {
- "color-convert": "^2.0.1"
+ "@types/http-proxy": "^1.17.8",
+ "http-proxy": "^1.18.1",
+ "is-glob": "^4.0.1",
+ "is-plain-obj": "^3.0.0",
+ "micromatch": "^4.0.2"
},
"engines": {
- "node": ">=8"
+ "node": ">=12.0.0"
},
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "peerDependencies": {
+ "@types/express": "^4.17.13"
+ },
+ "peerDependenciesMeta": {
+ "@types/express": {
+ "optional": true
+ }
}
},
- "node_modules/jest-message-util/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/https-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+ "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==",
+ "dev": true
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
+ "agent-base": "^7.1.2",
+ "debug": "4"
},
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "node": ">= 14"
}
},
- "node_modules/jest-message-util/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
"engines": {
- "node": ">=7.0.0"
+ "node": ">=10.17.0"
}
},
- "node_modules/jest-message-util/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
+ "node_modules/i18next": {
+ "version": "23.14.0",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.14.0.tgz",
+ "integrity": "sha512-Y5GL4OdA8IU2geRrt2+Uc1iIhsjICdHZzT9tNwQ3TVqdNzgxHToGCKf/TPRP80vTCAP6svg2WbbJL+Gx5MFQVA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://locize.com"
+ },
+ {
+ "type": "individual",
+ "url": "https://locize.com/i18next.html"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ }
+ ],
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ }
},
- "node_modules/jest-message-util/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
+ "node_modules/i18next-browser-languagedetector": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
+ "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
}
},
- "node_modules/jest-message-util/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=0.10.0"
}
},
- "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "node_modules/icss-utils": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
+ "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true,
"engines": {
- "node": ">=10"
+ "node": "^10 || ^12 || >= 14"
},
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "peerDependencies": {
+ "postcss": "^8.1.0"
}
},
- "node_modules/jest-message-util/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
},
- "node_modules/jest-message-util/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/imagemin": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.1.tgz",
+ "integrity": "sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
+ "file-type": "^12.0.0",
+ "globby": "^10.0.0",
+ "graceful-fs": "^4.2.2",
+ "junk": "^3.1.0",
+ "make-dir": "^3.0.0",
+ "p-pipe": "^3.0.0",
+ "replace-ext": "^1.0.0"
},
"engines": {
"node": ">=8"
}
},
- "node_modules/jest-mock": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
- "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
+ "node_modules/imagemin/node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dev": true,
"dependencies": {
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "jest-util": "^29.7.0"
+ "semver": "^6.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-pnp-resolver": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
- "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "node_modules/img-loader": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/img-loader/-/img-loader-4.0.0.tgz",
+ "integrity": "sha512-UwRcPQdwdOyEHyCxe1V9s9YFwInwEWCpoO+kJGfIqDrBDqA8jZUsEZTxQ0JteNPGw/Gupmwesk2OhLTcnw6tnQ==",
"dev": true,
+ "dependencies": {
+ "loader-utils": "^1.1.0"
+ },
"engines": {
- "node": ">=6"
+ "node": ">=12"
},
"peerDependencies": {
- "jest-resolve": "*"
- },
- "peerDependenciesMeta": {
- "jest-resolve": {
- "optional": true
- }
+ "imagemin": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
- "node_modules/jest-regex-util": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
- "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
+ "node_modules/img-loader/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
}
},
- "node_modules/jest-resolve": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz",
- "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==",
+ "node_modules/img-loader/node_modules/loader-utils": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
+ "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dev": true,
"dependencies": {
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "jest-pnp-resolver": "^1.2.2",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "resolve": "^1.20.0",
- "resolve.exports": "^2.0.0",
- "slash": "^3.0.0"
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^1.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=4.0.0"
}
},
- "node_modules/jest-resolve-dependencies": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz",
- "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==",
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+ },
+ "node_modules/immutable": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
+ "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
+ "dev": true
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
"dependencies": {
- "jest-regex-util": "^29.6.3",
- "jest-snapshot": "^29.7.0"
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-resolve/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/import-fresh/node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">=4"
}
},
- "node_modules/jest-resolve/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
"dev": true,
"dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
},
"engines": {
- "node": ">=10"
+ "node": ">=8"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-resolve/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
"engines": {
- "node": ">=7.0.0"
+ "node": ">=0.8.19"
}
},
- "node_modules/jest-resolve/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/jest-resolve/node_modules/has-flag": {
+ "node_modules/indent-string": {
"version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true,
"engines": {
"node": ">=8"
}
},
- "node_modules/jest-resolve/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
+ "once": "^1.3.0",
+ "wrappy": "1"
}
},
- "node_modules/jest-runner": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz",
- "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==",
- "dev": true,
- "dependencies": {
- "@jest/console": "^29.7.0",
- "@jest/environment": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "emittery": "^0.13.1",
- "graceful-fs": "^4.2.9",
- "jest-docblock": "^29.7.0",
- "jest-environment-node": "^29.7.0",
- "jest-haste-map": "^29.7.0",
- "jest-leak-detector": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-resolve": "^29.7.0",
- "jest-runtime": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-watcher": "^29.7.0",
- "jest-worker": "^29.7.0",
- "p-limit": "^3.1.0",
- "source-map-support": "0.5.13"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/jest-runner/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/internal-slot": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
+ "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
"dev": true,
"dependencies": {
- "color-convert": "^2.0.1"
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.0",
+ "side-channel": "^1.0.4"
},
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">= 0.4"
}
},
- "node_modules/jest-runner/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "node": ">=12"
}
},
- "node_modules/jest-runner/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/interpret": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
+ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
"dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
"engines": {
- "node": ">=7.0.0"
+ "node": ">= 0.10"
}
},
- "node_modules/jest-runner/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/jest-runner/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
}
},
- "node_modules/jest-runner/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/ipaddr.js": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
+ "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
"dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
"engines": {
- "node": ">=8"
+ "node": ">= 10"
}
},
- "node_modules/jest-runtime": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz",
- "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==",
+ "node_modules/is-arguments": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
+ "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"dev": true,
"dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/fake-timers": "^29.7.0",
- "@jest/globals": "^29.7.0",
- "@jest/source-map": "^29.6.3",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "cjs-module-lexer": "^1.0.0",
- "collect-v8-coverage": "^1.0.0",
- "glob": "^7.1.3",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-mock": "^29.7.0",
- "jest-regex-util": "^29.6.3",
- "jest-resolve": "^29.7.0",
- "jest-snapshot": "^29.7.0",
- "jest-util": "^29.7.0",
- "slash": "^3.0.0",
- "strip-bom": "^4.0.0"
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-runtime/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/is-array-buffer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
+ "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
"dev": true,
"dependencies": {
- "color-convert": "^2.0.1"
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.1"
},
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-runtime/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
+ "node_modules/is-async-function": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz",
+ "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==",
"dev": true,
"dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
+ "has-tostringtag": "^1.0.0"
},
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-runtime/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/is-bigint": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+ "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
"dev": true,
"dependencies": {
- "color-name": "~1.1.4"
+ "has-bigints": "^1.0.1"
},
- "engines": {
- "node": ">=7.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-runtime/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/jest-runtime/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
"engines": {
"node": ">=8"
}
},
- "node_modules/jest-runtime/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/is-boolean-object": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+ "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
},
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-snapshot": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz",
- "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==",
+ "node_modules/is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "dev": true
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"dev": true,
- "dependencies": {
- "@babel/core": "^7.11.6",
- "@babel/generator": "^7.7.2",
- "@babel/plugin-syntax-jsx": "^7.7.2",
- "@babel/plugin-syntax-typescript": "^7.7.2",
- "@babel/types": "^7.3.3",
- "@jest/expect-utils": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "babel-preset-current-node-syntax": "^1.0.0",
- "chalk": "^4.0.0",
- "expect": "^29.7.0",
- "graceful-fs": "^4.2.9",
- "jest-diff": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "natural-compare": "^1.4.0",
- "pretty-format": "^29.7.0",
- "semver": "^7.5.3"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-snapshot/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/is-core-module": {
+ "version": "2.15.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
+ "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
"dev": true,
"dependencies": {
- "color-convert": "^2.0.1"
+ "hasown": "^2.0.2"
},
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-snapshot/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/is-data-view": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz",
+ "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==",
"dev": true,
"dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
+ "is-typed-array": "^1.1.13"
},
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-snapshot/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/is-date-object": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+ "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dev": true,
"dependencies": {
- "color-name": "~1.1.4"
+ "has-tostringtag": "^1.0.0"
},
"engines": {
- "node": ">=7.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-snapshot/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/jest-snapshot/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"dev": true,
+ "bin": {
+ "is-docker": "cli.js"
+ },
"engines": {
"node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-snapshot/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
- "dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=0.10.0"
}
},
- "node_modules/jest-snapshot/node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "node_modules/is-finalizationregistry": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz",
+ "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==",
"dev": true,
- "engines": {
- "node": ">=10"
+ "dependencies": {
+ "call-bind": "^1.0.2"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-snapshot/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true
- },
- "node_modules/jest-snapshot/node_modules/semver": {
- "version": "7.6.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
- "bin": {
- "semver": "bin/semver.js"
- },
"engines": {
- "node": ">=10"
+ "node": ">=8"
}
},
- "node_modules/jest-snapshot/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/is-generator-function": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
+ "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
+ "has-tostringtag": "^1.0.0"
},
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-util": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
- "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": {
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "graceful-fs": "^4.2.9",
- "picomatch": "^2.2.3"
+ "is-extglob": "^2.1.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=0.10.0"
}
},
- "node_modules/jest-util/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
"dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-util/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/is-negative-zero": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
"dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-util/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
"engines": {
- "node": ">=7.0.0"
+ "node": ">=0.12.0"
}
},
- "node_modules/jest-util/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/jest-util/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "node_modules/is-number-object": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+ "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
"dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-util/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
"engines": {
"node": ">=8"
}
},
- "node_modules/jest-validate": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
- "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
+ "node_modules/is-plain-obj": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
+ "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==",
"dev": true,
- "dependencies": {
- "@jest/types": "^29.6.3",
- "camelcase": "^6.2.0",
- "chalk": "^4.0.0",
- "jest-get-type": "^29.6.3",
- "leven": "^3.1.0",
- "pretty-format": "^29.7.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-validate/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
"dev": true,
"dependencies": {
- "color-convert": "^2.0.1"
+ "isobject": "^3.0.1"
},
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">=0.10.0"
}
},
- "node_modules/jest-validate/node_modules/camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-regex": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-validate/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
"dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-validate/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
"dev": true,
"dependencies": {
- "color-name": "~1.1.4"
+ "call-bind": "^1.0.7"
},
"engines": {
- "node": ">=7.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-validate/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/jest-validate/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true,
"engines": {
"node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-validate/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "node_modules/is-string": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+ "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
"dev": true,
"dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "has-tostringtag": "^1.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "node_modules/is-symbol": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
"dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-validate/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true
- },
- "node_modules/jest-validate/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/is-typed-array": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz",
+ "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
+ "which-typed-array": "^1.1.14"
},
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-watcher": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
- "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
"dev": true,
- "dependencies": {
- "@jest/test-result": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "ansi-escapes": "^4.2.1",
- "chalk": "^4.0.0",
- "emittery": "^0.13.1",
- "jest-util": "^29.7.0",
- "string-length": "^4.0.1"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-watcher/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/is-weakref": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
"dev": true,
"dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
+ "call-bind": "^1.0.2"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-watcher/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/is-weakset": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
+ "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
"dev": true,
"dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
+ "call-bind": "^1.0.7",
+ "get-intrinsic": "^1.2.4"
},
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-watcher/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
"dependencies": {
- "color-name": "~1.1.4"
+ "is-docker": "^2.0.0"
},
"engines": {
- "node": ">=7.0.0"
+ "node": ">=8"
}
},
- "node_modules/jest-watcher/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true
},
- "node_modules/jest-watcher/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
},
- "node_modules/jest-watcher/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
"dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
"engines": {
- "node": ">=8"
+ "node": ">=0.10.0"
}
},
- "node_modules/jest-worker": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
- "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+ "node_modules/iterator.prototype": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz",
+ "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==",
"dev": true,
"dependencies": {
- "@types/node": "*",
- "jest-util": "^29.7.0",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/jest-worker/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
+ "define-properties": "^1.2.1",
+ "get-intrinsic": "^1.2.1",
+ "has-symbols": "^1.0.3",
+ "reflect.getprototypeof": "^1.0.4",
+ "set-function-name": "^2.0.1"
}
},
- "node_modules/jest-worker/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
+ "@isaacs/cliui": "^8.0.2"
},
"funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/jiti": {
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
- "node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
- "dev": true,
- "dependencies": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
"node_modules/jsdom": {
- "version": "20.0.3",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
- "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
- "dev": true,
- "dependencies": {
- "abab": "^2.0.6",
- "acorn": "^8.8.1",
- "acorn-globals": "^7.0.0",
- "cssom": "^0.5.0",
- "cssstyle": "^2.3.0",
- "data-urls": "^3.0.2",
- "decimal.js": "^10.4.2",
- "domexception": "^4.0.0",
- "escodegen": "^2.0.0",
- "form-data": "^4.0.0",
- "html-encoding-sniffer": "^3.0.0",
- "http-proxy-agent": "^5.0.0",
- "https-proxy-agent": "^5.0.1",
+ "version": "26.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
+ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssstyle": "^4.2.1",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.5.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
"is-potential-custom-element-name": "^1.0.1",
- "nwsapi": "^2.2.2",
- "parse5": "^7.1.1",
+ "nwsapi": "^2.2.16",
+ "parse5": "^7.2.1",
+ "rrweb-cssom": "^0.8.0",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
- "tough-cookie": "^4.1.2",
- "w3c-xmlserializer": "^4.0.0",
+ "tough-cookie": "^5.1.1",
+ "w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^7.0.0",
- "whatwg-encoding": "^2.0.0",
- "whatwg-mimetype": "^3.0.0",
- "whatwg-url": "^11.0.0",
- "ws": "^8.11.0",
- "xml-name-validator": "^4.0.0"
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.1.1",
+ "ws": "^8.18.0",
+ "xml-name-validator": "^5.0.0"
},
"engines": {
- "node": ">=14"
+ "node": ">=18"
},
"peerDependencies": {
- "canvas": "^2.5.0"
+ "canvas": "^3.0.0"
},
"peerDependenciesMeta": {
"canvas": {
}
},
"node_modules/jsesc": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
- "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
+ "license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
- "node": ">=4"
+ "node": ">=6"
}
},
"node_modules/json-buffer": {
"node": ">=0.10.0"
}
},
- "node_modules/kleur": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
- "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/klona": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz",
"node": ">=8"
}
},
+ "node_modules/laravel-vite-plugin": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.3.0.tgz",
+ "integrity": "sha512-P5qyG56YbYxM8OuYmK2OkhcKe0AksNVJUjq9LUZ5tOekU9fBn9LujYyctI4t9XoLjuMvHJXXpCoPntY1oKltuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picocolors": "^1.0.0",
+ "vite-plugin-full-reload": "^1.1.0"
+ },
+ "bin": {
+ "clean-orphaned-assets": "bin/clean.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0"
+ }
+ },
"node_modules/launch-editor": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.8.1.tgz",
"shell-quote": "^1.8.1"
}
},
- "node_modules/leven": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
- "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"loose-envify": "cli.js"
}
},
+ "node_modules/loupe": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz",
+ "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/lower-case": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/lz-string": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
- "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
- "dev": true,
- "bin": {
- "lz-string": "bin/bin.js"
- }
- },
- "node_modules/make-dir": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
- "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
- "dev": true,
- "dependencies": {
- "semver": "^7.5.3"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "yallist": "^3.0.2"
}
},
- "node_modules/make-dir/node_modules/semver": {
- "version": "7.6.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
+ "lz-string": "bin/bin.js"
}
},
- "node_modules/makeerror": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
- "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "tmpl": "1.0.5"
+ "@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/md5": {
}
},
"node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/multicast-dns": {
"version": "7.2.5",
}
},
"node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"node": ">= 6.13.0"
}
},
- "node_modules/node-int64": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
- "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
- "dev": true
- },
"node_modules/node-libs-browser": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
}
},
"node_modules/node-releases": {
- "version": "2.0.18",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
- "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
- "dev": true
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/normalize-path": {
"version": "3.0.0",
}
},
"node_modules/nwsapi": {
- "version": "2.2.12",
- "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz",
- "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==",
- "dev": true
+ "version": "2.2.20",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
+ "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/object-assign": {
"version": "4.1.1",
}
},
"node_modules/parse5": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
- "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "entities": "^4.4.0"
+ "entities": "^6.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5/node_modules/entities": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
- "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"dev": true,
+ "license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"node": ">=8"
}
},
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
+ "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
"node_modules/pbkdf2": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
}
},
"node_modules/picocolors": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
- "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
- "dev": true
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
}
},
"node_modules/postcss": {
- "version": "8.4.44",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.44.tgz",
- "integrity": "sha512-Aweb9unOEpQ3ezu4Q00DPvvM2ZTUitJdNKeP/+uQgr1IBIqu574IaZoURId7BKtWMREwzKa9OgzPzezWGPWFQw==",
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dev": true,
"funding": [
{
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
- "nanoid": "^3.3.7",
- "picocolors": "^1.0.1",
- "source-map-js": "^1.2.0"
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
}
},
+ "node_modules/postcss-load-config/node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/postcss-loader": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true
},
- "node_modules/prompts": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
- "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
- "dev": true,
- "dependencies": {
- "kleur": "^3.0.3",
- "sisteransi": "^1.0.5"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true
},
- "node_modules/psl": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
- "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
- "dev": true
- },
"node_modules/public-encrypt": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
"dev": true
},
- "node_modules/pure-rand": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
- "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
- "dev": true,
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/dubzzz"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fast-check"
- }
- ]
- },
"node_modules/pusher-js": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.3.0.tgz",
"node": ">=0.4.x"
}
},
- "node_modules/querystringify": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
- "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
- "dev": true
- },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/react-router": {
"version": "6.26.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.1.tgz",
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"dev": true
},
- "node_modules/resolve.exports": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz",
- "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==",
- "dev": true,
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
"inherits": "^2.0.1"
}
},
+ "node_modules/rollup": {
+ "version": "4.44.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.0.tgz",
+ "integrity": "sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.44.0",
+ "@rollup/rollup-android-arm64": "4.44.0",
+ "@rollup/rollup-darwin-arm64": "4.44.0",
+ "@rollup/rollup-darwin-x64": "4.44.0",
+ "@rollup/rollup-freebsd-arm64": "4.44.0",
+ "@rollup/rollup-freebsd-x64": "4.44.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.44.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.44.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.44.0",
+ "@rollup/rollup-linux-arm64-musl": "4.44.0",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.44.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.44.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.44.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.44.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.44.0",
+ "@rollup/rollup-linux-x64-gnu": "4.44.0",
+ "@rollup/rollup-linux-x64-musl": "4.44.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.44.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.44.0",
+ "@rollup/rollup-win32-x64-msvc": "4.44.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rrweb-cssom": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"xmlchars": "^2.2.0"
},
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
- "node_modules/send/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true
- },
"node_modules/serialize-javascript": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
},
- "node_modules/sisteransi": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
- "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
- "dev": true
- },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
}
},
"node_modules/source-map-js": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
- "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
+ "license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
"node": ">= 6"
}
},
- "node_modules/sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
- "dev": true
- },
"node_modules/stable": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
"deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility",
"dev": true
},
- "node_modules/stack-utils": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
- "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
- "dev": true,
- "dependencies": {
- "escape-string-regexp": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/stack-utils/node_modules/escape-string-regexp": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
- "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
"dev": true,
- "engines": {
- "node": ">=8"
- }
+ "license": "MIT"
},
"node_modules/statuses": {
"version": "2.0.1",
}
},
"node_modules/std-env": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
- "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
- "dev": true
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
+ "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/stop-iteration-iterator": {
"version": "1.0.0",
"safe-buffer": "~5.2.0"
}
},
- "node_modules/string-length": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
- "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
- "dev": true,
- "dependencies": {
- "char-regex": "^1.0.2",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"node": ">=8"
}
},
- "node_modules/strip-bom": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
- "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/strip-literal": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz",
+ "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/style-loader": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz",
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/tailwindcss": {
"version": "3.4.10",
"url": "https://github.com/sponsors/antonk52"
}
},
- "node_modules/tailwindcss/node_modules/yaml": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
- "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
- "dev": true,
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14"
- }
- },
"node_modules/tapable": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
"source-map": "^0.6.0"
}
},
- "node_modules/test-exclude": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
- "dev": true,
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"thenify": ">= 3.1.0 < 4"
},
"engines": {
- "node": ">=0.8"
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/thunky": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
+ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
+ "dev": true
+ },
+ "node_modules/timers-browserify": {
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz",
+ "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==",
+ "dev": true,
+ "dependencies": {
+ "setimmediate": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
+ "node_modules/tiny-case": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
+ "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
+ },
+ "node_modules/tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
}
},
- "node_modules/thunky": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
- "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
- "dev": true
+ "node_modules/tinyspy": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz",
+ "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
},
- "node_modules/timers-browserify": {
- "version": "2.0.12",
- "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz",
- "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==",
+ "node_modules/tldts": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
+ "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "setimmediate": "^1.0.4"
+ "tldts-core": "^6.1.86"
},
- "engines": {
- "node": ">=0.6.0"
+ "bin": {
+ "tldts": "bin/cli.js"
}
},
- "node_modules/tiny-case": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
- "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
- },
- "node_modules/tiny-invariant": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
- "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
- },
- "node_modules/tiny-warning": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
- "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
- },
- "node_modules/tmpl": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
- "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
- "dev": true
+ "node_modules/tldts-core": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
+ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/to-arraybuffer": {
"version": "1.0.1",
"integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==",
"dev": true
},
- "node_modules/to-fast-properties": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
- "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
},
"node_modules/tough-cookie": {
- "version": "4.1.4",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
- "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
+ "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
"dev": true,
+ "license": "BSD-3-Clause",
"dependencies": {
- "psl": "^1.1.33",
- "punycode": "^2.1.1",
- "universalify": "^0.2.0",
- "url-parse": "^1.5.3"
+ "tldts": "^6.1.32"
},
"engines": {
- "node": ">=6"
- }
- },
- "node_modules/tough-cookie/node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/tough-cookie/node_modules/universalify": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
- "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
- "dev": true,
- "engines": {
- "node": ">= 4.0.0"
+ "node": ">=16"
}
},
"node_modules/tr46": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
- "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "punycode": "^2.1.1"
+ "punycode": "^2.3.1"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/tr46/node_modules/punycode": {
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6"
}
"node": ">= 0.8.0"
}
},
- "node_modules/type-detect": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
- "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/type-fest": {
- "version": "0.21.3",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
- "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
}
},
"node_modules/update-browserslist-db": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
- "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
"dev": true,
"funding": [
{
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
- "escalade": "^3.1.2",
- "picocolors": "^1.0.1"
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
},
"bin": {
"update-browserslist-db": "cli.js"
"node": ">= 0.4"
}
},
- "node_modules/url-parse": {
- "version": "1.5.10",
- "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
- "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
- "dev": true,
- "dependencies": {
- "querystringify": "^2.1.1",
- "requires-port": "^1.0.0"
- }
- },
"node_modules/util": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
"uuid": "dist/bin/uuid"
}
},
- "node_modules/v8-to-istanbul": {
- "version": "9.3.0",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
- "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
- "dev": true,
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.12",
- "@types/istanbul-lib-coverage": "^2.0.1",
- "convert-source-map": "^2.0.0"
- },
- "engines": {
- "node": ">=10.12.0"
- }
- },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"d3-timer": "^3.0.1"
}
},
+ "node_modules/vite": {
+ "version": "6.3.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
+ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
+ "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.4.1",
+ "es-module-lexer": "^1.7.0",
+ "pathe": "^2.0.3",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vite-plugin-full-reload": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz",
+ "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picocolors": "^1.0.0",
+ "picomatch": "^2.3.1"
+ }
+ },
+ "node_modules/vite-plugin-webpackchunkname": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/vite-plugin-webpackchunkname/-/vite-plugin-webpackchunkname-1.0.3.tgz",
+ "integrity": "sha512-88lt6IrgCumnf4Up8eyaSJbmo4V0ZIaR4M94fbZvGGmK2aWMmPGVsiFBszYE7Kq04I9tGjLFnyremn+KEgEGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/plugin-alias": "*",
+ "@rollup/pluginutils": "*",
+ "es-module-lexer": "*",
+ "magic-string": "*"
+ }
+ },
+ "node_modules/vite/node_modules/fdir": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
+ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/expect": "3.2.4",
+ "@vitest/mocker": "3.2.4",
+ "@vitest/pretty-format": "^3.2.4",
+ "@vitest/runner": "3.2.4",
+ "@vitest/snapshot": "3.2.4",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "debug": "^4.4.1",
+ "expect-type": "^1.2.1",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
+ "std-env": "^3.9.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.14",
+ "tinypool": "^1.1.1",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
+ "vite-node": "3.2.4",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.2.4",
+ "@vitest/ui": "3.2.4",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
},
"node_modules/w3c-xmlserializer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
- "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "xml-name-validator": "^4.0.0"
+ "xml-name-validator": "^5.0.0"
},
"engines": {
- "node": ">=14"
- }
- },
- "node_modules/walker": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
- "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
- "dev": true,
- "dependencies": {
- "makeerror": "1.0.12"
+ "node": ">=18"
}
},
"node_modules/warning": {
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"dev": true,
+ "license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
}
},
"node_modules/whatwg-encoding": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
- "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/whatwg-encoding/node_modules/iconv-lite": {
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
}
},
"node_modules/whatwg-mimetype": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
- "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/whatwg-url": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
- "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "tr46": "^3.0.0",
+ "tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/which": {
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/wildcard": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
- "node_modules/write-file-atomic": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
- "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
- "dev": true,
- "dependencies": {
- "imurmurhash": "^0.1.4",
- "signal-exit": "^3.0.7"
- },
- "engines": {
- "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
- }
- },
"node_modules/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
}
},
"node_modules/xml-name-validator": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
- "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/xtend": {
"version": "4.0.2",
"dev": true
},
"node_modules/yaml": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
- "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
+ "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
"engines": {
- "node": ">= 6"
+ "node": ">= 14.6"
}
},
"node_modules/yargs": {
{
"private": true,
"scripts": {
- "dev": "npm run development",
- "development": "mix",
- "watch": "mix watch",
- "watch-poll": "mix watch -- --watch-options-poll=1000",
- "hot": "mix watch --hot",
- "prod": "npm run production",
- "production": "mix --production",
- "test": "NODE_ENV=test npx jest",
- "test-watch": "npm run test -- --watch --notify"
- },
- "eslintConfig": {
- "env": {
- "browser": true,
- "es6": true,
- "node": true
- },
- "extends": [
- "eslint:recommended",
- "plugin:import/recommended",
- "plugin:react/recommended"
- ],
- "parser": "@babel/eslint-parser",
- "parserOptions": {
- "babelOptions": {
- "presets": [
- "@babel/preset-react"
- ]
- },
- "ecmaFeatures": {
- "jsx": true
- },
- "ecmaVersion": 2018,
- "requireConfigFile": false,
- "sourceType": "module"
- },
- "rules": {
- "import/no-named-as-default-member": 0,
- "max-len": [
- "warn",
- {
- "code": 100,
- "tabWidth": 4
- }
- ],
- "no-use-before-define": "error",
- "no-extra-parens": [
- "warn",
- "all",
- {
- "nestedBinaryExpressions": false
- }
- ],
- "no-mixed-operators": "error",
- "no-trailing-spaces": "error",
- "semi": [
- "error",
- "always"
- ]
- },
- "overrides": [
- {
- "files": [
- "**/*.test.js"
- ],
- "env": {
- "jest": true
- },
- "settings": {
- "import/resolver": {
- "node": {
- "paths": [
- "resources/js"
- ]
- }
- }
- }
- }
- ]
- },
- "jest": {
- "moduleDirectories": [
- "node_modules",
- "resources/js"
- ],
- "roots": [
- "<rootDir>/resources/js",
- "<rootDir>/tests/js"
- ],
- "setupFilesAfterEnv": [
- "<rootDir>/resources/js/setup-jest.js"
- ],
- "testEnvironment": "jsdom"
+ "dev": "vite",
+ "build": "vite build",
+ "test": "vitest --run",
+ "test-watch": "vitest"
},
"devDependencies": {
"@babel/eslint-parser": "^7.22.11",
"@tailwindcss/forms": "^0.5.6",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
+ "@vitejs/plugin-react": "^4.5.2",
"alpinejs": "^3.4.2",
"autoprefixer": "^10.4.2",
"axios": "^1.5.0",
- "babel-jest": "^29.7.0",
"bootstrap": "^5.1.3",
"eslint": "^8.10.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-react": "^7.29.3",
- "jest": "^29.7.0",
- "jest-environment-jsdom": "^29.7.0",
+ "globals": "^16.2.0",
+ "jsdom": "^26.1.0",
"laravel-mix": "^6.0.6",
+ "laravel-vite-plugin": "^1.3.0",
"lodash": "^4.17.19",
"postcss": "^8.4.6",
"postcss-import": "^15.1.0",
"resolve-url-loader": "^5.0.0",
"sass": "^1.32.11",
"sass-loader": "^13.3.2",
- "tailwindcss": "^3.0.18"
+ "tailwindcss": "^3.0.18",
+ "vite": "^6.3.5",
+ "vite-plugin-webpackchunkname": "^1.0.3",
+ "vitest": "^3.2.4"
},
"dependencies": {
"@codemirror/lang-html": "^6.4.5",
"recharts": "^2.1.9",
"toastr": "^2.1.4",
"yup": "^1.2.0"
- }
+ },
+ "type": "module"
}
+++ /dev/null
-import React from 'react';
-import { Col, Nav, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-import { LinkContainer } from 'react-router-bootstrap';
-
-import PrivacyDialog from './PrivacyDialog';
-
-const Footer = () => {
- const [showDialog, setShowDialog] = React.useState(false);
-
- const { t } = useTranslation();
-
- return <div className="bg-dark mt-5 px-3 py-5">
- <Row>
- <Col md={4}>
- <h5>{t('footer.competitions')}</h5>
- <Nav as="ul" className="flex-column">
- <Nav.Item as="li">
- <LinkContainer to="/tournaments/6">
- <Nav.Link className="p-0 text-muted" href="/tournaments/6">
- {t('footer.sdw')}
- </Nav.Link>
- </LinkContainer>
- </Nav.Item>
- <Nav.Item as="li">
- <LinkContainer to="/tournaments/7">
- <Nav.Link className="p-0 text-muted" href="/tournaments/7">
- {t('footer.circus')}
- </Nav.Link>
- </LinkContainer>
- </Nav.Item>
- <Nav.Item as="li">
- <LinkContainer to="/schedule">
- <Nav.Link className="p-0 text-muted" href="/schedule">
- {t('footer.schedule')}
- </Nav.Link>
- </LinkContainer>
- </Nav.Item>
- <Nav.Item as="li">
- <LinkContainer to="/events">
- <Nav.Link className="p-0 text-muted" href="/events">
- {t('footer.events')}
- </Nav.Link>
- </LinkContainer>
- </Nav.Item>
- <Nav.Item as="li">
- <Nav.Link
- className="p-0 text-muted"
- href="https://alttprasyncs.com/"
- target="_blank"
- >
- {t('footer.alttprasyncs')}
- </Nav.Link>
- </Nav.Item>
- <Nav.Item as="li">
- <Nav.Link
- className="p-0 text-muted"
- href="https://smz3asyncs.com/"
- target="_blank"
- >
- {t('footer.smz3asyncs')}
- </Nav.Link>
- </Nav.Item>
- </Nav>
- </Col>
- <Col md={4}>
- <h5>{t('footer.resources')}</h5>
- <Nav as="ul" className="flex-column">
- <Nav.Item as="li">
- <Nav.Link
- className="p-0 text-muted"
- href="https://alttp-wiki.net/"
- target="_blank"
- >
- {t('footer.alttpwiki')}
- </Nav.Link>
- </Nav.Item>
- <Nav.Item as="li">
- <LinkContainer to="/tech">
- <Nav.Link className="p-0 text-muted" href="/tech">
- {t('footer.tech')}
- </Nav.Link>
- </LinkContainer>
- </Nav.Item>
- <Nav.Item as="li">
- <LinkContainer to="/map">
- <Nav.Link className="p-0 text-muted" href="/map">
- {t('footer.map')}
- </Nav.Link>
- </LinkContainer>
- </Nav.Item>
- <Nav.Item as="li">
- <Nav.Link
- className="p-0 text-muted"
- href="https://glitchmaps.mfns.dev/"
- target="_blank"
- >
- {t('footer.muffins')}
- </Nav.Link>
- </Nav.Item>
- <Nav.Item as="li">
- <Nav.Link
- className="p-0 text-muted"
- href="https://wiki.supermetroid.run/"
- target="_blank"
- >
- {t('footer.smwiki')}
- </Nav.Link>
- </Nav.Item>
- </Nav>
- </Col>
- <Col md={4}>
- <h5>{t('footer.info')}</h5>
- <Nav as="ul" className="flex-column">
- <Nav.Item as="li">
- <Nav.Link
- className="p-0 text-muted"
- onClick={() => { setShowDialog(true); }}
- >
- {t('footer.privacy')}
- </Nav.Link>
- </Nav.Item>
- <Nav.Item as="li">
- <Nav.Link
- className="p-0 text-muted"
- href="https://discord.gg/5zuANcS"
- target="_blank"
- >
- {t('footer.alttpde')}
- </Nav.Link>
- </Nav.Item>
- <Nav.Item as="li">
- <Nav.Link
- className="p-0 text-muted"
- href="https://discord.com/invite/GGdrbnQmVs"
- target="_blank"
- >
- {t('footer.smd')}
- </Nav.Link>
- </Nav.Item>
- <Nav.Item as="li">
- <Nav.Link
- className="p-0 text-muted"
- href="https://discord.gg/yVdTkEZhk6"
- target="_blank"
- >
- {t('footer.stepladder')}
- </Nav.Link>
- </Nav.Item>
- <Nav.Item as="li">
- <Nav.Link
- className="p-0 text-muted"
- href="https://discord.gg/cx6nZkekXz"
- target="_blank"
- >
- {t('footer.restreamCentral')}
- </Nav.Link>
- </Nav.Item>
- </Nav>
- </Col>
- </Row>
- <p className="pt-5 text-center text-muted">{t('footer.contact')}</p>
- <PrivacyDialog onHide={() => { setShowDialog(false); }} show={showDialog} />
- </div>;
-};
-
-export default Footer;
--- /dev/null
+import React from 'react';
+import { Col, Nav, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+import { LinkContainer } from 'react-router-bootstrap';
+
+import PrivacyDialog from './PrivacyDialog';
+
+const Footer = () => {
+ const [showDialog, setShowDialog] = React.useState(false);
+
+ const { t } = useTranslation();
+
+ return <div className="bg-dark mt-5 px-3 py-5">
+ <Row>
+ <Col md={4}>
+ <h5>{t('footer.competitions')}</h5>
+ <Nav as="ul" className="flex-column">
+ <Nav.Item as="li">
+ <LinkContainer to="/tournaments/6">
+ <Nav.Link className="p-0 text-muted" href="/tournaments/6">
+ {t('footer.sdw')}
+ </Nav.Link>
+ </LinkContainer>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <LinkContainer to="/tournaments/7">
+ <Nav.Link className="p-0 text-muted" href="/tournaments/7">
+ {t('footer.circus')}
+ </Nav.Link>
+ </LinkContainer>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <LinkContainer to="/schedule">
+ <Nav.Link className="p-0 text-muted" href="/schedule">
+ {t('footer.schedule')}
+ </Nav.Link>
+ </LinkContainer>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <LinkContainer to="/events">
+ <Nav.Link className="p-0 text-muted" href="/events">
+ {t('footer.events')}
+ </Nav.Link>
+ </LinkContainer>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <Nav.Link
+ className="p-0 text-muted"
+ href="https://alttprasyncs.com/"
+ target="_blank"
+ >
+ {t('footer.alttprasyncs')}
+ </Nav.Link>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <Nav.Link
+ className="p-0 text-muted"
+ href="https://smz3asyncs.com/"
+ target="_blank"
+ >
+ {t('footer.smz3asyncs')}
+ </Nav.Link>
+ </Nav.Item>
+ </Nav>
+ </Col>
+ <Col md={4}>
+ <h5>{t('footer.resources')}</h5>
+ <Nav as="ul" className="flex-column">
+ <Nav.Item as="li">
+ <Nav.Link
+ className="p-0 text-muted"
+ href="https://alttp-wiki.net/"
+ target="_blank"
+ >
+ {t('footer.alttpwiki')}
+ </Nav.Link>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <LinkContainer to="/tech">
+ <Nav.Link className="p-0 text-muted" href="/tech">
+ {t('footer.tech')}
+ </Nav.Link>
+ </LinkContainer>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <LinkContainer to="/map">
+ <Nav.Link className="p-0 text-muted" href="/map">
+ {t('footer.map')}
+ </Nav.Link>
+ </LinkContainer>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <Nav.Link
+ className="p-0 text-muted"
+ href="https://glitchmaps.mfns.dev/"
+ target="_blank"
+ >
+ {t('footer.muffins')}
+ </Nav.Link>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <Nav.Link
+ className="p-0 text-muted"
+ href="https://wiki.supermetroid.run/"
+ target="_blank"
+ >
+ {t('footer.smwiki')}
+ </Nav.Link>
+ </Nav.Item>
+ </Nav>
+ </Col>
+ <Col md={4}>
+ <h5>{t('footer.info')}</h5>
+ <Nav as="ul" className="flex-column">
+ <Nav.Item as="li">
+ <Nav.Link
+ className="p-0 text-muted"
+ onClick={() => { setShowDialog(true); }}
+ >
+ {t('footer.privacy')}
+ </Nav.Link>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <Nav.Link
+ className="p-0 text-muted"
+ href="https://discord.gg/5zuANcS"
+ target="_blank"
+ >
+ {t('footer.alttpde')}
+ </Nav.Link>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <Nav.Link
+ className="p-0 text-muted"
+ href="https://discord.com/invite/GGdrbnQmVs"
+ target="_blank"
+ >
+ {t('footer.smd')}
+ </Nav.Link>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <Nav.Link
+ className="p-0 text-muted"
+ href="https://discord.gg/yVdTkEZhk6"
+ target="_blank"
+ >
+ {t('footer.stepladder')}
+ </Nav.Link>
+ </Nav.Item>
+ <Nav.Item as="li">
+ <Nav.Link
+ className="p-0 text-muted"
+ href="https://discord.gg/cx6nZkekXz"
+ target="_blank"
+ >
+ {t('footer.restreamCentral')}
+ </Nav.Link>
+ </Nav.Item>
+ </Nav>
+ </Col>
+ </Row>
+ <p className="pt-5 text-center text-muted">{t('footer.contact')}</p>
+ <PrivacyDialog onHide={() => { setShowDialog(false); }} show={showDialog} />
+ </div>;
+};
+
+export default Footer;
+++ /dev/null
-import React from 'react';
-import { Outlet } from 'react-router-dom';
-
-import Footer from './Footer';
-import Header from './Header';
-
-const FullLayout = () => <>
- <header>
- <Header />
- </header>
- <main>
- <Outlet />
- </main>
- <footer>
- <Footer />
- </footer>
-</>;
-
-export default FullLayout;
--- /dev/null
+import React from 'react';
+import { Outlet } from 'react-router-dom';
+
+import Footer from './Footer';
+import Header from './Header';
+
+const FullLayout = () => <>
+ <header>
+ <Header />
+ </header>
+ <main>
+ <Outlet />
+ </main>
+ <footer>
+ <Footer />
+ </footer>
+</>;
+
+export default FullLayout;
+++ /dev/null
-import React from 'react';
-import { Container, Nav, Navbar } from 'react-bootstrap';
-import { LinkContainer } from 'react-router-bootstrap';
-import { useLocation } from 'react-router-dom';
-import { useTranslation } from 'react-i18next';
-
-import LanguageSwitcher from './LanguageSwitcher';
-import User from './User';
-import Icon from '../components/common/Icon';
-
-const Header = () => {
- const { pathname } = useLocation();
- const { t } = useTranslation();
-
- return <Navbar id="header" bg="dark" expand="md" variant="dark">
- <Container fluid>
- <LinkContainer to="/">
- <Navbar.Brand>
- ALttP
- </Navbar.Brand>
- </LinkContainer>
- <Navbar.Toggle aria-controls="header-nav" label={t('button.menu')}>
- <Icon.MENU title="" />
- </Navbar.Toggle>
- <Navbar.Collapse id="header-nav">
- <Nav activeKey={pathname}>
- <LinkContainer to="/tournaments/6">
- <Nav.Link href="/tournaments/6">
- {t('menu.sdw')}
- </Nav.Link>
- </LinkContainer>
- <LinkContainer to="/tournaments/7">
- <Nav.Link href="/tournaments/7">
- {t('menu.circus')}
- </Nav.Link>
- </LinkContainer>
- </Nav>
- <Nav activeKey={pathname} className="ms-auto">
- <LinkContainer to="/tech">
- <Nav.Link href="/tech">
- {t('menu.tech')}
- </Nav.Link>
- </LinkContainer>
- <LinkContainer to="/map/lw">
- <Nav.Link href="/map/lw">
- {t('menu.map')}
- </Nav.Link>
- </LinkContainer>
- </Nav>
- <div className="d-flex align-items-center">
- <Navbar.Text className="mx-2">
- <LanguageSwitcher />
- </Navbar.Text>
- <User />
- </div>
- </Navbar.Collapse>
- </Container>
- </Navbar>;
-};
-
-export default Header;
--- /dev/null
+import React from 'react';
+import { Container, Nav, Navbar } from 'react-bootstrap';
+import { LinkContainer } from 'react-router-bootstrap';
+import { useLocation } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+
+import LanguageSwitcher from './LanguageSwitcher';
+import User from './User';
+import Icon from '../components/common/Icon';
+
+const Header = () => {
+ const { pathname } = useLocation();
+ const { t } = useTranslation();
+
+ return <Navbar id="header" bg="dark" expand="md" variant="dark">
+ <Container fluid>
+ <LinkContainer to="/">
+ <Navbar.Brand>
+ ALttP
+ </Navbar.Brand>
+ </LinkContainer>
+ <Navbar.Toggle aria-controls="header-nav" label={t('button.menu')}>
+ <Icon.MENU title="" />
+ </Navbar.Toggle>
+ <Navbar.Collapse id="header-nav">
+ <Nav activeKey={pathname}>
+ <LinkContainer to="/tournaments/6">
+ <Nav.Link href="/tournaments/6">
+ {t('menu.sdw')}
+ </Nav.Link>
+ </LinkContainer>
+ <LinkContainer to="/tournaments/7">
+ <Nav.Link href="/tournaments/7">
+ {t('menu.circus')}
+ </Nav.Link>
+ </LinkContainer>
+ </Nav>
+ <Nav activeKey={pathname} className="ms-auto">
+ <LinkContainer to="/tech">
+ <Nav.Link href="/tech">
+ {t('menu.tech')}
+ </Nav.Link>
+ </LinkContainer>
+ <LinkContainer to="/map/lw">
+ <Nav.Link href="/map/lw">
+ {t('menu.map')}
+ </Nav.Link>
+ </LinkContainer>
+ </Nav>
+ <div className="d-flex align-items-center">
+ <Navbar.Text className="mx-2">
+ <LanguageSwitcher />
+ </Navbar.Text>
+ <User />
+ </div>
+ </Navbar.Collapse>
+ </Container>
+ </Navbar>;
+};
+
+export default Header;
+++ /dev/null
-import axios from 'axios';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import Icon from '../components/common/Icon';
-import { useUser } from '../hooks/user';
-import i18n from '../i18n';
-
-const setLanguage = (user, language) => {
- i18n.changeLanguage(language);
- if (user) {
- axios.post('/api/users/set-language', { language });
- }
-};
-
-const LanguageSwitcher = () => {
- const { user } = useUser();
-
- return <Button
- className="text-reset"
- href={`?lng=${i18n.language === 'de' ? 'en' : 'de'}`}
- onClick={e => {
- setLanguage(user, i18n.language === 'de' ? 'en' : 'de');
- e.preventDefault();
- }}
- title={i18n.language === 'de' ? 'Switch to english' : 'Auf deutsch wechseln'}
- variant="outline-secondary"
- >
- <Icon.LANGUAGE />
- {' '}
- {i18n.language === 'de' ? 'Deutsch' : 'English'}
- </Button>;
-};
-
-export default withTranslation()(LanguageSwitcher);
--- /dev/null
+import axios from 'axios';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import Icon from '../components/common/Icon';
+import { useUser } from '../hooks/user';
+import i18n from '../i18n';
+
+const setLanguage = (user, language) => {
+ i18n.changeLanguage(language);
+ if (user) {
+ axios.post('/api/users/set-language', { language });
+ }
+};
+
+const LanguageSwitcher = () => {
+ const { user } = useUser();
+
+ return <Button
+ className="text-reset"
+ href={`?lng=${i18n.language === 'de' ? 'en' : 'de'}`}
+ onClick={e => {
+ setLanguage(user, i18n.language === 'de' ? 'en' : 'de');
+ e.preventDefault();
+ }}
+ title={i18n.language === 'de' ? 'Switch to english' : 'Auf deutsch wechseln'}
+ variant="outline-secondary"
+ >
+ <Icon.LANGUAGE />
+ {' '}
+ {i18n.language === 'de' ? 'Deutsch' : 'English'}
+ </Button>;
+};
+
+export default withTranslation()(LanguageSwitcher);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Modal } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-const PrivacyDialog = ({
- onHide,
- show,
-}) => {
- const { t } = useTranslation();
-
- return <Modal onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {t('privacy.heading')}
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <p>{t('privacy.p1')}</p>
- <p>{t('privacy.p2')}</p>
- <p>{t('privacy.p3')}</p>
- </Modal.Body>
- <Modal.Footer>
- <Button onClick={onHide} variant="secondary">
- {t('button.close')}
- </Button>
- </Modal.Footer>
- </Modal>;
-};
-
-PrivacyDialog.propTypes = {
- onHide: PropTypes.func,
- show: PropTypes.bool,
-};
-
-export default PrivacyDialog;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+const PrivacyDialog = ({
+ onHide,
+ show,
+}) => {
+ const { t } = useTranslation();
+
+ return <Modal onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {t('privacy.heading')}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <p>{t('privacy.p1')}</p>
+ <p>{t('privacy.p2')}</p>
+ <p>{t('privacy.p3')}</p>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button onClick={onHide} variant="secondary">
+ {t('button.close')}
+ </Button>
+ </Modal.Footer>
+ </Modal>;
+};
+
+PrivacyDialog.propTypes = {
+ onHide: PropTypes.func,
+ show: PropTypes.bool,
+};
+
+export default PrivacyDialog;
+++ /dev/null
-import React from 'react';
-import {
- createBrowserRouter,
- createRoutesFromElements,
- Navigate,
- Route,
- RouterProvider,
-} from 'react-router-dom';
-
-import FullLayout from './FullLayout';
-import Front from '../pages/Front';
-import Technique from '../pages/Technique';
-import Techniques from '../pages/Techniques';
-import User from '../pages/User';
-
-const router = createBrowserRouter(
- createRoutesFromElements(
- <Route>
- <Route element={<FullLayout />}>
- <Route
- path="discord-bot"
- lazy={() => import(
- /* webpackChunkName: "admin" */
- '../pages/DiscordBot'
- )}
- />
- <Route
- path="dungeons"
- element={<Techniques namespace="dungeons" type="dungeon" />}
- />
- <Route
- path="dungeons/:name"
- element={<Technique basepath="dungeons" type="dungeon" />}
- />
- <Route
- path="events"
- lazy={() => import(
- /* webpackChunkName: "events" */
- '../pages/Events'
- )}
- />
- <Route
- path="events/:name"
- lazy={() => import(
- /* webpackChunkName: "events" */
- '../pages/Event'
- )}
- />
- <Route
- path="h/:hash"
- lazy={() => import(
- /* webpackChunkName: "seeds" */
- '../pages/AlttpSeed'
- )}
- />
- <Route
- path="horstielog"
- lazy={() => import(
- /* webpackChunkName: "horstie" */
- '../pages/HorstieLog'
- )}
- />
- <Route
- path="locations"
- element={<Techniques namespace="locations" type="location" />}
- />
- <Route
- path="locations/:name"
- element={<Technique basepath="locations" type="location" />}
- />
- <Route path="map">
- <Route index element={<Navigate replace to="lw" />} />
- <Route
- path=":activeMap"
- lazy={() => import(
- /* webpackChunkName: "map" */
- '../pages/Map'
- )}
- />
- </Route>
- <Route
- path="modes"
- element={<Techniques namespace="modes" type="mode" />}
- />
- <Route
- path="modes/:name"
- element={<Technique basepath="modes" type="mode" />}
- />
- <Route
- path="rulesets"
- element={<Techniques namespace="rulesets" type="ruleset" />}
- />
- <Route
- path="rulesets/:name"
- element={<Technique basepath="rulesets" type="ruleset" />}
- />
- <Route
- path="schedule"
- lazy={() => import(
- /* webpackChunkName: "events" */
- '../pages/Schedule'
- )}
- />
- <Route
- path="tech"
- element={<Techniques namespace="techniques" type="tech" />}
- />
- <Route
- path="tech/:name"
- element={<Technique basepath="tech" type="tech" />}
- />
- <Route
- path="tournaments/:id"
- lazy={() => import(
- /* webpackChunkName: "tournament" */
- '../pages/Tournament'
- )}
- />
- <Route
- path="twitch-bot"
- lazy={() => import(
- /* webpackChunkName: "admin" */
- '../pages/TwitchBot'
- )}
- />
- <Route
- path="twitch-legal"
- lazy={() => import(
- /* webpackChunkName: "twitch" */
- '../pages/TwitchLegal'
- )}
- />
- <Route path="users/:id" element={<User />} />
- <Route path="/" element={<Front />} />
- <Route path="*" element={<Navigate to="/" />} />
- </Route>
- <Route
- path="doors-tracker"
- lazy={() => import(
- /* webpackChunkName: "tracker" */
- '../pages/DoorsTracker'
- )}
- />
- <Route path="guessing-game">
- <Route
- path="controls/:channelId?"
- lazy={() => import(
- /* webpackChunkName: "guessing" */
- '../pages/GuessingGameControls'
- )}
- />
- <Route
- path="monitor/:key"
- lazy={() => import(
- /* webpackChunkName: "guessing" */
- '../pages/GuessingGameMonitor'
- )}
- />
- </Route>
- <Route
- path="tracker"
- lazy={() => import(
- /* webpackChunkName: "tracker" */
- '../pages/Tracker'
- )}
- />
- <Route
- path="zootr-mixed-pools-tracker"
- lazy={() => import(
- /* webpackChunkName: "zootr" */
- '../pages/ZootrMixedPoolsTracker'
- )}
- />
- </Route>
- )
-);
-
-const AppRoutes = () => <RouterProvider router={router} />;
-
-export default AppRoutes;
--- /dev/null
+import React from 'react';
+import {
+ createBrowserRouter,
+ createRoutesFromElements,
+ Navigate,
+ Route,
+ RouterProvider,
+} from 'react-router-dom';
+
+import FullLayout from './FullLayout';
+import Front from '../pages/Front';
+import Technique from '../pages/Technique';
+import Techniques from '../pages/Techniques';
+import User from '../pages/User';
+
+const router = createBrowserRouter(
+ createRoutesFromElements(
+ <Route>
+ <Route element={<FullLayout />}>
+ <Route
+ path="discord-bot"
+ lazy={() => import(
+ /* webpackChunkName: "admin" */
+ '../pages/DiscordBot'
+ )}
+ />
+ <Route
+ path="dungeons"
+ element={<Techniques namespace="dungeons" type="dungeon" />}
+ />
+ <Route
+ path="dungeons/:name"
+ element={<Technique basepath="dungeons" type="dungeon" />}
+ />
+ <Route
+ path="events"
+ lazy={() => import(
+ /* webpackChunkName: "events" */
+ '../pages/Events'
+ )}
+ />
+ <Route
+ path="events/:name"
+ lazy={() => import(
+ /* webpackChunkName: "events" */
+ '../pages/Event'
+ )}
+ />
+ <Route
+ path="h/:hash"
+ lazy={() => import(
+ /* webpackChunkName: "seeds" */
+ '../pages/AlttpSeed'
+ )}
+ />
+ <Route
+ path="horstielog"
+ lazy={() => import(
+ /* webpackChunkName: "horstie" */
+ '../pages/HorstieLog'
+ )}
+ />
+ <Route
+ path="locations"
+ element={<Techniques namespace="locations" type="location" />}
+ />
+ <Route
+ path="locations/:name"
+ element={<Technique basepath="locations" type="location" />}
+ />
+ <Route path="map">
+ <Route index element={<Navigate replace to="lw" />} />
+ <Route
+ path=":activeMap"
+ lazy={() => import(
+ /* webpackChunkName: "map" */
+ '../pages/Map'
+ )}
+ />
+ </Route>
+ <Route
+ path="modes"
+ element={<Techniques namespace="modes" type="mode" />}
+ />
+ <Route
+ path="modes/:name"
+ element={<Technique basepath="modes" type="mode" />}
+ />
+ <Route
+ path="rulesets"
+ element={<Techniques namespace="rulesets" type="ruleset" />}
+ />
+ <Route
+ path="rulesets/:name"
+ element={<Technique basepath="rulesets" type="ruleset" />}
+ />
+ <Route
+ path="schedule"
+ lazy={() => import(
+ /* webpackChunkName: "events" */
+ '../pages/Schedule'
+ )}
+ />
+ <Route
+ path="tech"
+ element={<Techniques namespace="techniques" type="tech" />}
+ />
+ <Route
+ path="tech/:name"
+ element={<Technique basepath="tech" type="tech" />}
+ />
+ <Route
+ path="tournaments/:id"
+ lazy={() => import(
+ /* webpackChunkName: "tournament" */
+ '../pages/Tournament'
+ )}
+ />
+ <Route
+ path="twitch-bot"
+ lazy={() => import(
+ /* webpackChunkName: "admin" */
+ '../pages/TwitchBot'
+ )}
+ />
+ <Route
+ path="twitch-legal"
+ lazy={() => import(
+ /* webpackChunkName: "twitch" */
+ '../pages/TwitchLegal'
+ )}
+ />
+ <Route path="users/:id" element={<User />} />
+ <Route path="/" element={<Front />} />
+ <Route path="*" element={<Navigate to="/" />} />
+ </Route>
+ <Route
+ path="doors-tracker"
+ lazy={() => import(
+ /* webpackChunkName: "tracker" */
+ '../pages/DoorsTracker'
+ )}
+ />
+ <Route path="guessing-game">
+ <Route
+ path="controls/:channelId?"
+ lazy={() => import(
+ /* webpackChunkName: "guessing" */
+ '../pages/GuessingGameControls'
+ )}
+ />
+ <Route
+ path="monitor/:key"
+ lazy={() => import(
+ /* webpackChunkName: "guessing" */
+ '../pages/GuessingGameMonitor'
+ )}
+ />
+ </Route>
+ <Route
+ path="tracker"
+ lazy={() => import(
+ /* webpackChunkName: "tracker" */
+ '../pages/Tracker'
+ )}
+ />
+ <Route
+ path="zootr-mixed-pools-tracker"
+ lazy={() => import(
+ /* webpackChunkName: "zootr" */
+ '../pages/ZootrMixedPoolsTracker'
+ )}
+ />
+ </Route>
+ )
+);
+
+const AppRoutes = () => <RouterProvider router={router} />;
+
+export default AppRoutes;
+++ /dev/null
-import React from 'react';
-import { Button, Nav } from 'react-bootstrap';
-import { LinkContainer } from 'react-router-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Icon from '../components/common/Icon';
-import { useUser } from '../hooks/user';
-import { getAvatarUrl } from '../helpers/User';
-
-const User = () => {
- const { t } = useTranslation();
- const { logout, user } = useUser();
-
- return user
- ? <>
- <Nav className="ms-auto">
- <LinkContainer to={`/users/${user.id}`}>
- <Nav.Link>
- <img alt="" src={getAvatarUrl(user)} />
- {user.username}
- {user.discriminator && user.discriminator !== '0' ?
- <span className="text-muted">#{user.discriminator}</span>
- : null}
- </Nav.Link>
- </LinkContainer>
- </Nav>
- <Button
- className="ms-2"
- onClick={logout}
- title={t('button.logout')}
- variant="outline-secondary"
- >
- <Icon.LOGOUT title="" />
- </Button>
- </>
- : <Button
- className="ms-auto"
- href="/login"
- onClick={() => {
- if (location.pathname.length > 1) {
- localStorage.setItem('returnPath', location.pathname.substr(1));
- }
- }}
- title={t('button.login')}
- variant="discord"
- >
- <Icon.DISCORD />
- {' '}
- {t('button.login')}
- </Button>;
-};
-
-export default User;
--- /dev/null
+import React from 'react';
+import { Button, Nav } from 'react-bootstrap';
+import { LinkContainer } from 'react-router-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Icon from '../components/common/Icon';
+import { useUser } from '../hooks/user';
+import { getAvatarUrl } from '../helpers/User';
+
+const User = () => {
+ const { t } = useTranslation();
+ const { logout, user } = useUser();
+
+ return user
+ ? <>
+ <Nav className="ms-auto">
+ <LinkContainer to={`/users/${user.id}`}>
+ <Nav.Link>
+ <img alt="" src={getAvatarUrl(user)} />
+ {user.username}
+ {user.discriminator && user.discriminator !== '0' ?
+ <span className="text-muted">#{user.discriminator}</span>
+ : null}
+ </Nav.Link>
+ </LinkContainer>
+ </Nav>
+ <Button
+ className="ms-2"
+ onClick={logout}
+ title={t('button.logout')}
+ variant="outline-secondary"
+ >
+ <Icon.LOGOUT title="" />
+ </Button>
+ </>
+ : <Button
+ className="ms-auto"
+ href="/login"
+ onClick={() => {
+ if (location.pathname.length > 1) {
+ localStorage.setItem('returnPath', location.pathname.substr(1));
+ }
+ }}
+ title={t('button.login')}
+ variant="discord"
+ >
+ <Icon.DISCORD />
+ {' '}
+ {t('button.login')}
+ </Button>;
+};
+
+export default User;
+++ /dev/null
-import React from 'react';
-import { Helmet } from 'react-helmet';
-import { useTranslation } from 'react-i18next';
-
-import Routes from './Routes';
-import AlttpBaseRomProvider from '../helpers/AlttpBaseRomContext';
-import { SNESProvider } from '../hooks/snes';
-import { UserProvider } from '../hooks/user';
-import i18n from '../i18n';
-
-const App = () => {
- const { t } = useTranslation();
-
- React.useEffect(() => {
- window.Echo.channel('App.Control')
- .listen('PleaseRefresh', () => {
- location.reload();
- });
- return () => {
- window.Echo.leave('App.Control');
- };
- }, []);
-
- return <AlttpBaseRomProvider>
- <SNESProvider>
- <UserProvider>
- <Helmet>
- <html lang={i18n.language} />
- <title>{t('general.appName')}</title>
- <meta name="description" content={t('general.appDescription')} />
- </Helmet>
- <Routes />
- </UserProvider>
- </SNESProvider>
- </AlttpBaseRomProvider>;
-};
-
-export default App;
--- /dev/null
+import React from 'react';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
+
+import Routes from './Routes';
+import AlttpBaseRomProvider from '../helpers/AlttpBaseRomContext';
+import { SNESProvider } from '../hooks/snes';
+import { UserProvider } from '../hooks/user';
+import i18n from '../i18n';
+
+const App = () => {
+ const { t } = useTranslation();
+
+ React.useEffect(() => {
+ window.Echo.channel('App.Control')
+ .listen('PleaseRefresh', () => {
+ location.reload();
+ });
+ return () => {
+ window.Echo.leave('App.Control');
+ };
+ }, []);
+
+ return <AlttpBaseRomProvider>
+ <SNESProvider>
+ <UserProvider>
+ <Helmet>
+ <html lang={i18n.language} />
+ <title>{t('general.appName')}</title>
+ <meta name="description" content={t('general.appDescription')} />
+ </Helmet>
+ <Routes />
+ </UserProvider>
+ </SNESProvider>
+ </AlttpBaseRomProvider>;
+};
+
+export default App;
-window._ = require('lodash');
-
-try {
- require('bootstrap');
-} catch (e) {
- // well...
-}
+import axios from 'axios';
+import Echo from 'laravel-echo';
+import Pusher from 'pusher-js';
+import qs from 'qs';
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
-window.axios = require('axios');
-
+window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
-
-import qs from 'qs';
window.axios.defaults.paramsSerializer = p => qs.stringify(p, { arrayFormat: 'brackets' });
/**
* allows your team to easily build robust real-time web applications.
*/
-import Echo from 'laravel-echo';
-
-window.Pusher = require('pusher-js');
+window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
- key: process.env.MIX_REVERB_APP_KEY,
+ key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: window.location.hostname,
wsPort: window.location.port,
forceTLS: false,
+++ /dev/null
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import i18n from '../../i18n';
-
-import { useAlttpBaseRom } from '../../helpers/AlttpBaseRomContext';
-
-const BaseRomButton = () => {
- const { rom, setRom } = useAlttpBaseRom();
-
- const handleFile = React.useCallback(async e => {
- if (e.target.files.length != 1) {
- setRom(null);
- } else {
- const buf = await e.target.files[0].arrayBuffer();
- setRom(buf);
- }
- }, [setRom]);
-
- if (rom) return null;
-
- return <span>
- <input
- accept=".sfc"
- className="d-none"
- id="alttp.baseRom"
- onChange={handleFile}
- type="file"
- />
- <label htmlFor="alttp.baseRom">
- <Button as="span" variant="primary">
- {i18n.t('alttp.setBaseRom')}
- </Button>
- </label>
- </span>;
-};
-
-export default withTranslation()(BaseRomButton);
--- /dev/null
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import i18n from '../../i18n';
+
+import { useAlttpBaseRom } from '../../helpers/AlttpBaseRomContext';
+
+const BaseRomButton = () => {
+ const { rom, setRom } = useAlttpBaseRom();
+
+ const handleFile = React.useCallback(async e => {
+ if (e.target.files.length != 1) {
+ setRom(null);
+ } else {
+ const buf = await e.target.files[0].arrayBuffer();
+ setRom(buf);
+ }
+ }, [setRom]);
+
+ if (rom) return null;
+
+ return <span>
+ <input
+ accept=".sfc"
+ className="d-none"
+ id="alttp.baseRom"
+ onChange={handleFile}
+ type="file"
+ />
+ <label htmlFor="alttp.baseRom">
+ <Button as="span" variant="primary">
+ {i18n.t('alttp.setBaseRom')}
+ </Button>
+ </label>
+ </span>;
+};
+
+export default withTranslation()(BaseRomButton);
+++ /dev/null
-import FileSaver from 'file-saver';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Container, Row } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import BaseRomButton from './BaseRomButton';
-import { useAlttpBaseRom } from '../../helpers/AlttpBaseRomContext';
-import BPS from '../../helpers/bps';
-import i18n from '../../i18n';
-
-const applyPatch = (rom, patch, filename) => {
- try {
- const bps = new BPS();
- bps.setPatch(patch);
- bps.setSource(rom);
- const result = bps.applyPatch();
- FileSaver.saveAs(new Blob([result], { type: 'application/octet-stream' }), filename);
- } catch (e) {
- toastr.error(i18n.t('alttpSeeds.patchError', { msg: e.message }));
- }
-};
-
-const isDefaultSetting = () => false;
-
-const Seed = ({ onRetry, patch, seed }) => {
- const { rom } = useAlttpBaseRom();
-
- return <Container>
- <h1>{i18n.t('alttpSeeds.heading')}</h1>
- <Row>
- <Col md={{ order: 2 }}>
- {rom ?
- <Button
- disabled={!seed || seed.status !== 'generated' || !patch}
- onClick={() => applyPatch(
- rom,
- patch,
- `${i18n.t('alttpSeeds.filename', {
- hash: seed.hash,
- preset: seed.preset,
- })}.sfc`,
- )}
- variant="primary"
- >
- {i18n.t(patch ? 'alttpSeeds.patch' : 'alttpSeeds.fetchingPatch')}
- </Button>
- :
- <BaseRomButton />
- }
- </Col>
- <Col md={{ order: 1 }}>
- <p>
- {i18n.t('alttpSeeds.preset')}:
- {' '}
- <strong>{i18n.t(`alttpSeeds.presets.${seed.preset}`)}</strong>
- </p>
- {seed.seed ?
- <p>
- {i18n.t('alttpSeeds.seed')}:
- {' '}
- <strong>{seed.seed}</strong>
- </p>
- : null}
- {seed.race ?
- <p>{i18n.t('alttpSeeds.race')}</p>
- : null}
- {seed.mystery ?
- <p>{i18n.t('alttpSeeds.mystery')}</p>
- : null}
- {seed.status === 'generated' ?
- <p>
- {i18n.t('alttpSeeds.generated')}:
- {' '}
- <strong>
- {i18n.t('alttpSeeds.date', { date: new Date(seed.updated_at) })}
- </strong>
- </p>
- :
- <p>
- {i18n.t('alttpSeeds.status')}:
- {' '}
- <strong>{i18n.t(`alttpSeeds.statuses.${seed.status}`)}</strong>
- </p>
- }
- {seed.status === 'error' ?
- <p>
- <Button
- onClick={onRetry}
- variant="secondary"
- >
- {i18n.t('button.retry')}
- </Button>
- </p>
- : null}
- </Col>
- </Row>
- <h2 className="mt-5">{i18n.t('alttpSeeds.generator')}</h2>
- <p>{i18n.t(`alttpSeeds.generators.${seed.generator}`)}</p>
- {seed.settings ? <>
- <h2 className="mt-5">{i18n.t('alttpSeeds.settings')}</h2>
- <Row>
- {Object.entries(seed.settings).map(([key, value]) =>
- <Col key={key} sm={4} md={3} lg={2} className="mb-2">
- <small className="text-muted">
- {i18n.t(`alttpSeeds.settingName.${key}`)}
- </small>
- <br />
- {isDefaultSetting(key, value) ?
- i18n.t(`alttpSeeds.settingValue.${key}.${value}`)
- :
- <strong>{i18n.t(`alttpSeeds.settingValue.${key}.${value}`)}</strong>
- }
- </Col>
- )}
- </Row>
- </> : null}
- </Container>;
-};
-
-Seed.propTypes = {
- onRetry: PropTypes.func,
- patch: PropTypes.instanceOf(ArrayBuffer),
- seed: PropTypes.shape({
- generator: PropTypes.string,
- hash: PropTypes.string,
- mystery: PropTypes.bool,
- preset: PropTypes.string,
- race: PropTypes.bool,
- seed: PropTypes.string,
- settings: PropTypes.shape({
- }),
- status: PropTypes.string,
- updated_at: PropTypes.string,
- }),
-};
-
-export default withTranslation()(Seed);
--- /dev/null
+import FileSaver from 'file-saver';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Container, Row } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import BaseRomButton from './BaseRomButton';
+import { useAlttpBaseRom } from '../../helpers/AlttpBaseRomContext';
+import BPS from '../../helpers/bps';
+import i18n from '../../i18n';
+
+const applyPatch = (rom, patch, filename) => {
+ try {
+ const bps = new BPS();
+ bps.setPatch(patch);
+ bps.setSource(rom);
+ const result = bps.applyPatch();
+ FileSaver.saveAs(new Blob([result], { type: 'application/octet-stream' }), filename);
+ } catch (e) {
+ toastr.error(i18n.t('alttpSeeds.patchError', { msg: e.message }));
+ }
+};
+
+const isDefaultSetting = () => false;
+
+const Seed = ({ onRetry, patch, seed }) => {
+ const { rom } = useAlttpBaseRom();
+
+ return <Container>
+ <h1>{i18n.t('alttpSeeds.heading')}</h1>
+ <Row>
+ <Col md={{ order: 2 }}>
+ {rom ?
+ <Button
+ disabled={!seed || seed.status !== 'generated' || !patch}
+ onClick={() => applyPatch(
+ rom,
+ patch,
+ `${i18n.t('alttpSeeds.filename', {
+ hash: seed.hash,
+ preset: seed.preset,
+ })}.sfc`,
+ )}
+ variant="primary"
+ >
+ {i18n.t(patch ? 'alttpSeeds.patch' : 'alttpSeeds.fetchingPatch')}
+ </Button>
+ :
+ <BaseRomButton />
+ }
+ </Col>
+ <Col md={{ order: 1 }}>
+ <p>
+ {i18n.t('alttpSeeds.preset')}:
+ {' '}
+ <strong>{i18n.t(`alttpSeeds.presets.${seed.preset}`)}</strong>
+ </p>
+ {seed.seed ?
+ <p>
+ {i18n.t('alttpSeeds.seed')}:
+ {' '}
+ <strong>{seed.seed}</strong>
+ </p>
+ : null}
+ {seed.race ?
+ <p>{i18n.t('alttpSeeds.race')}</p>
+ : null}
+ {seed.mystery ?
+ <p>{i18n.t('alttpSeeds.mystery')}</p>
+ : null}
+ {seed.status === 'generated' ?
+ <p>
+ {i18n.t('alttpSeeds.generated')}:
+ {' '}
+ <strong>
+ {i18n.t('alttpSeeds.date', { date: new Date(seed.updated_at) })}
+ </strong>
+ </p>
+ :
+ <p>
+ {i18n.t('alttpSeeds.status')}:
+ {' '}
+ <strong>{i18n.t(`alttpSeeds.statuses.${seed.status}`)}</strong>
+ </p>
+ }
+ {seed.status === 'error' ?
+ <p>
+ <Button
+ onClick={onRetry}
+ variant="secondary"
+ >
+ {i18n.t('button.retry')}
+ </Button>
+ </p>
+ : null}
+ </Col>
+ </Row>
+ <h2 className="mt-5">{i18n.t('alttpSeeds.generator')}</h2>
+ <p>{i18n.t(`alttpSeeds.generators.${seed.generator}`)}</p>
+ {seed.settings ? <>
+ <h2 className="mt-5">{i18n.t('alttpSeeds.settings')}</h2>
+ <Row>
+ {Object.entries(seed.settings).map(([key, value]) =>
+ <Col key={key} sm={4} md={3} lg={2} className="mb-2">
+ <small className="text-muted">
+ {i18n.t(`alttpSeeds.settingName.${key}`)}
+ </small>
+ <br />
+ {isDefaultSetting(key, value) ?
+ i18n.t(`alttpSeeds.settingValue.${key}.${value}`)
+ :
+ <strong>{i18n.t(`alttpSeeds.settingValue.${key}.${value}`)}</strong>
+ }
+ </Col>
+ )}
+ </Row>
+ </> : null}
+ </Container>;
+};
+
+Seed.propTypes = {
+ onRetry: PropTypes.func,
+ patch: PropTypes.instanceOf(ArrayBuffer),
+ seed: PropTypes.shape({
+ generator: PropTypes.string,
+ hash: PropTypes.string,
+ mystery: PropTypes.bool,
+ preset: PropTypes.string,
+ race: PropTypes.bool,
+ seed: PropTypes.string,
+ settings: PropTypes.shape({
+ }),
+ status: PropTypes.string,
+ updated_at: PropTypes.string,
+ }),
+};
+
+export default withTranslation()(Seed);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Badge, Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Dialog from './Dialog';
-import Icon from '../common/Icon';
-import { mayHandleApplications } from '../../helpers/permissions';
-import { getPendingApplications } from '../../helpers/Tournament';
-import { useUser } from '../../hooks/user';
-
-const ApplicationsButton = ({ tournament }) => {
- const [showDialog, setShowDialog] = React.useState(false);
-
- const { t } = useTranslation();
- const { user } = useUser();
-
- if (!user || !tournament.accept_applications || !mayHandleApplications(user, tournament)) {
- return null;
- }
-
- const pending = getPendingApplications(tournament);
-
- return <>
- <Button
- onClick={() => setShowDialog(true)}
- title={t('tournaments.applications')}
- variant="primary"
- >
- <Icon.APPLICATIONS title="" />
- {pending.length ?
- <>
- {' '}
- <Badge>{pending.length}</Badge>
- </>
- : null}
- </Button>
- <Dialog
- onHide={() => setShowDialog(false)}
- show={showDialog}
- tournament={tournament}
- />
- </>;
-};
-
-ApplicationsButton.propTypes = {
- tournament: PropTypes.shape({
- accept_applications: PropTypes.bool,
- id: PropTypes.number,
- }),
-};
-
-export default ApplicationsButton;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Badge, Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Dialog from './Dialog';
+import Icon from '../common/Icon';
+import { mayHandleApplications } from '../../helpers/permissions';
+import { getPendingApplications } from '../../helpers/Tournament';
+import { useUser } from '../../hooks/user';
+
+const ApplicationsButton = ({ tournament }) => {
+ const [showDialog, setShowDialog] = React.useState(false);
+
+ const { t } = useTranslation();
+ const { user } = useUser();
+
+ if (!user || !tournament.accept_applications || !mayHandleApplications(user, tournament)) {
+ return null;
+ }
+
+ const pending = getPendingApplications(tournament);
+
+ return <>
+ <Button
+ onClick={() => setShowDialog(true)}
+ title={t('tournaments.applications')}
+ variant="primary"
+ >
+ <Icon.APPLICATIONS title="" />
+ {pending.length ?
+ <>
+ {' '}
+ <Badge>{pending.length}</Badge>
+ </>
+ : null}
+ </Button>
+ <Dialog
+ onHide={() => setShowDialog(false)}
+ show={showDialog}
+ tournament={tournament}
+ />
+ </>;
+};
+
+ApplicationsButton.propTypes = {
+ tournament: PropTypes.shape({
+ accept_applications: PropTypes.bool,
+ id: PropTypes.number,
+ }),
+};
+
+export default ApplicationsButton;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Alert, Button, Modal } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import List from './List';
-import i18n from '../../i18n';
-
-const Dialog = ({ onHide, show, tournament }) =>
-<Modal className="applications-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {i18n.t('tournaments.applications')}
- </Modal.Title>
- </Modal.Header>
- <Modal.Body className="p-0">
- {tournament.applications && tournament.applications.length ?
- <List tournament={tournament} />
- :
- <Alert variant="info">
- {i18n.t('tournaments.noApplications')}
- </Alert>
- }
- </Modal.Body>
- <Modal.Footer>
- <Button onClick={onHide} variant="secondary">
- {i18n.t('button.close')}
- </Button>
- </Modal.Footer>
-</Modal>;
-
-Dialog.propTypes = {
- onHide: PropTypes.func,
- show: PropTypes.bool,
- tournament: PropTypes.shape({
- applications: PropTypes.arrayOf(PropTypes.shape({
- }))
- }),
-};
-
-export default withTranslation()(Dialog);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Alert, Button, Modal } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import List from './List';
+import i18n from '../../i18n';
+
+const Dialog = ({ onHide, show, tournament }) =>
+<Modal className="applications-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {i18n.t('tournaments.applications')}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body className="p-0">
+ {tournament.applications && tournament.applications.length ?
+ <List tournament={tournament} />
+ :
+ <Alert variant="info">
+ {i18n.t('tournaments.noApplications')}
+ </Alert>
+ }
+ </Modal.Body>
+ <Modal.Footer>
+ <Button onClick={onHide} variant="secondary">
+ {i18n.t('button.close')}
+ </Button>
+ </Modal.Footer>
+</Modal>;
+
+Dialog.propTypes = {
+ onHide: PropTypes.func,
+ show: PropTypes.bool,
+ tournament: PropTypes.shape({
+ applications: PropTypes.arrayOf(PropTypes.shape({
+ }))
+ }),
+};
+
+export default withTranslation()(Dialog);
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, ListGroup } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import Icon from '../common/Icon';
-import Box from '../users/Box';
-import i18n from '../../i18n';
-
-const accept = async (tournament, application) => {
- try {
- await axios.post(`/api/application/${application.id}/accept`);
- toastr.success(i18n.t('applications.acceptSuccess'));
- } catch (e) {
- toastr.error(i18n.t('applications.acceptError'));
- }
-};
-
-const reject = async (tournament, application) => {
- try {
- await axios.post(`/api/application/${application.id}/reject`);
- toastr.success(i18n.t('applications.rejectSuccess'));
- } catch (e) {
- toastr.error(i18n.t('applications.rejectError'));
- }
-};
-
-const Item = ({ application, tournament }) =>
-<ListGroup.Item className="d-flex justify-content-between align-items-center">
- <Box discriminator user={application.user} />
- <div className="button-bar">
- <Button
- onClick={() => accept(tournament, application)}
- title={i18n.t('applications.accept')}
- variant="success"
- >
- <Icon.ACCEPT title="" />
- </Button>
- <Button
- onClick={() => reject(tournament, application)}
- title={i18n.t('applications.reject')}
- variant="danger"
- >
- <Icon.REJECT title="" />
- </Button>
- </div>
-</ListGroup.Item>;
-
-Item.propTypes = {
- application: PropTypes.shape({
- user: PropTypes.shape({
- }),
- }),
- tournament: PropTypes.shape({
- }),
-};
-
-export default withTranslation()(Item);
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, ListGroup } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import Icon from '../common/Icon';
+import Box from '../users/Box';
+import i18n from '../../i18n';
+
+const accept = async (tournament, application) => {
+ try {
+ await axios.post(`/api/application/${application.id}/accept`);
+ toastr.success(i18n.t('applications.acceptSuccess'));
+ } catch (e) {
+ toastr.error(i18n.t('applications.acceptError'));
+ }
+};
+
+const reject = async (tournament, application) => {
+ try {
+ await axios.post(`/api/application/${application.id}/reject`);
+ toastr.success(i18n.t('applications.rejectSuccess'));
+ } catch (e) {
+ toastr.error(i18n.t('applications.rejectError'));
+ }
+};
+
+const Item = ({ application, tournament }) =>
+<ListGroup.Item className="d-flex justify-content-between align-items-center">
+ <Box discriminator user={application.user} />
+ <div className="button-bar">
+ <Button
+ onClick={() => accept(tournament, application)}
+ title={i18n.t('applications.accept')}
+ variant="success"
+ >
+ <Icon.ACCEPT title="" />
+ </Button>
+ <Button
+ onClick={() => reject(tournament, application)}
+ title={i18n.t('applications.reject')}
+ variant="danger"
+ >
+ <Icon.REJECT title="" />
+ </Button>
+ </div>
+</ListGroup.Item>;
+
+Item.propTypes = {
+ application: PropTypes.shape({
+ user: PropTypes.shape({
+ }),
+ }),
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(Item);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { ListGroup } from 'react-bootstrap';
-
-import Item from './Item';
-
-const List = ({ tournament }) =>
-<ListGroup variant="flush">
- {tournament.applications.map(application =>
- <Item application={application} key={application.id} tournament={tournament} />
- )}
-</ListGroup>;
-
-List.propTypes = {
- tournament: PropTypes.shape({
- applications: PropTypes.arrayOf(PropTypes.shape({
- })),
- }),
-};
-
-export default List;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { ListGroup } from 'react-bootstrap';
+
+import Item from './Item';
+
+const List = ({ tournament }) =>
+<ListGroup variant="flush">
+ {tournament.applications.map(application =>
+ <Item application={application} key={application.id} tournament={tournament} />
+ )}
+</ListGroup>;
+
+List.propTypes = {
+ tournament: PropTypes.shape({
+ applications: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ }),
+};
+
+export default List;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Icon from '../common/Icon';
-
-const Item = ({ channel }) => {
- const classNames = [
- 'channel-item',
- channel.twitch_live ? 'is-live' : 'not-live',
- ];
- return <a
- className={classNames.join(' ')}
- href={channel.stream_link}
- rel="noreferrer"
- target="_blank"
- >
- <div className="d-flex justify-content-between">
- <div className="channel-title fs-5">{channel.title}</div>
- {channel.twitch_live ?
- <div className="channel-viewers">
- <Icon.STREAM />
- {' '}
- {channel.twitch_viewers}
- </div>
- : null}
- </div>
- <div className="channel-stream-title">{channel.twitch_title}</div>
- <div className="channel-category text-muted">
- <small>{channel.twitch_category_name}</small>
- </div>
- </a>;
-};
-
-Item.propTypes = {
- channel: PropTypes.shape({
- stream_link: PropTypes.string,
- title: PropTypes.string,
- twitch_category_name: PropTypes.string,
- twitch_live: PropTypes.bool,
- twitch_title: PropTypes.string,
- twitch_viewers: PropTypes.number,
- }),
-};
-
-export default Item;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Icon from '../common/Icon';
+
+const Item = ({ channel }) => {
+ const classNames = [
+ 'channel-item',
+ channel.twitch_live ? 'is-live' : 'not-live',
+ ];
+ return <a
+ className={classNames.join(' ')}
+ href={channel.stream_link}
+ rel="noreferrer"
+ target="_blank"
+ >
+ <div className="d-flex justify-content-between">
+ <div className="channel-title fs-5">{channel.title}</div>
+ {channel.twitch_live ?
+ <div className="channel-viewers">
+ <Icon.STREAM />
+ {' '}
+ {channel.twitch_viewers}
+ </div>
+ : null}
+ </div>
+ <div className="channel-stream-title">{channel.twitch_title}</div>
+ <div className="channel-category text-muted">
+ <small>{channel.twitch_category_name}</small>
+ </div>
+ </a>;
+};
+
+Item.propTypes = {
+ channel: PropTypes.shape({
+ stream_link: PropTypes.string,
+ title: PropTypes.string,
+ twitch_category_name: PropTypes.string,
+ twitch_live: PropTypes.bool,
+ twitch_title: PropTypes.string,
+ twitch_viewers: PropTypes.number,
+ }),
+};
+
+export default Item;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-
-import Icon from '../common/Icon';
-
-const Link = ({ channel }) => {
- return <Button
- href={channel.stream_link}
- rel="noreferrer"
- target="_blank"
- title={channel.title}
- variant="outline-twitch"
- >
- <Icon.STREAM />
-
- {channel.short_name || channel.title}
- </Button>;
-};
-
-Link.propTypes = {
- channel: PropTypes.shape({
- short_name: PropTypes.string,
- stream_link: PropTypes.string,
- title: PropTypes.string,
- }),
-};
-
-export default Link;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+
+import Icon from '../common/Icon';
+
+const Link = ({ channel }) => {
+ return <Button
+ href={channel.stream_link}
+ rel="noreferrer"
+ target="_blank"
+ title={channel.title}
+ variant="outline-twitch"
+ >
+ <Icon.STREAM />
+
+ {channel.short_name || channel.title}
+ </Button>;
+};
+
+Link.propTypes = {
+ channel: PropTypes.shape({
+ short_name: PropTypes.string,
+ stream_link: PropTypes.string,
+ title: PropTypes.string,
+ }),
+};
+
+export default Link;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Item from './Item';
-
-const List = ({ channels = [] }) => {
- return <div className="channel-list">
- {channels.map(channel =>
- <Item channel={channel} key={channel.id} />
- )}
- </div>;
-};
-
-List.propTypes = {
- channels: PropTypes.arrayOf(PropTypes.shape({
- })),
-};
-
-export default List;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Item from './Item';
+
+const List = ({ channels = [] }) => {
+ return <div className="channel-list">
+ {channels.map(channel =>
+ <Item channel={channel} key={channel.id} />
+ )}
+ </div>;
+};
+
+List.propTypes = {
+ channels: PropTypes.arrayOf(PropTypes.shape({
+ })),
+};
+
+export default List;
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React, { useEffect, useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Dialog from './Dialog';
-import Icon from '../common/Icon';
-
-const ChatBotLog = ({ id }) => {
- const [showDialog, setShowDialog] = useState(false);
- const [log, setLog] = useState([]);
-
- const { t } = useTranslation();
-
- useEffect(() => {
- if (!showDialog) return;
- const ctrl = new AbortController();
- axios
- .get(`/api/channels/${id}/chat-bot-log`, { signal: ctrl.signal })
- .then(response => {
- setLog(response.data);
- });
- return () => {
- ctrl.abort();
- };
- }, [id, showDialog]);
-
- return (
- <>
- <Button
- onClick={() => setShowDialog(true)}
- title={t('button.protocol')}
- variant="outline-info"
- >
- <Icon.PROTOCOL title="" />
- </Button>
- <Dialog
- log={log}
- onHide={() => setShowDialog(false)}
- show={showDialog}
- />
- </>
- );
-};
-
-ChatBotLog.propTypes = {
- id: PropTypes.number,
-};
-
-export default ChatBotLog;
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React, { useEffect, useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Dialog from './Dialog';
+import Icon from '../common/Icon';
+
+const ChatBotLog = ({ id }) => {
+ const [showDialog, setShowDialog] = useState(false);
+ const [log, setLog] = useState([]);
+
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ if (!showDialog) return;
+ const ctrl = new AbortController();
+ axios
+ .get(`/api/channels/${id}/chat-bot-log`, { signal: ctrl.signal })
+ .then(response => {
+ setLog(response.data);
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [id, showDialog]);
+
+ return (
+ <>
+ <Button
+ onClick={() => setShowDialog(true)}
+ title={t('button.protocol')}
+ variant="outline-info"
+ >
+ <Icon.PROTOCOL title="" />
+ </Button>
+ <Dialog
+ log={log}
+ onHide={() => setShowDialog(false)}
+ show={showDialog}
+ />
+ </>
+ );
+};
+
+ChatBotLog.propTypes = {
+ id: PropTypes.number,
+};
+
+export default ChatBotLog;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Alert, Button, Modal } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import List from './List';
-import i18n from '../../i18n';
-
-class Dialog extends React.Component {
-
- componentDidMount() {
- this.timer = setInterval(() => {
- this.forceUpdate();
- }, 30000);
- }
-
- componentWillUnmount() {
- clearInterval(this.timer);
- }
-
- render() {
- const {
- log,
- onHide,
- show,
- } = this.props;
- return <Modal className="chat-bot-log-dialog" onHide={onHide} show={show} size="lg">
- <Modal.Header closeButton>
- <Modal.Title>
- {i18n.t('chatBotLog.heading')}
- </Modal.Title>
- </Modal.Header>
- {log && log.length ?
- <List log={log} />
- :
- <Modal.Body>
- <Alert variant="info">
- {i18n.t('chatBotLog.empty')}
- </Alert>
- </Modal.Body>
- }
- <Modal.Footer>
- <Button onClick={onHide} variant="secondary">
- {i18n.t('button.close')}
- </Button>
- </Modal.Footer>
- </Modal>;
- }
-
-}
-
-Dialog.propTypes = {
- log: PropTypes.arrayOf(PropTypes.shape({
- })),
- onHide: PropTypes.func,
- show: PropTypes.bool,
-};
-
-Dialog.defaultProps = {
- log: null,
- onHide: null,
- show: false,
-};
-
-export default withTranslation()(Dialog);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Alert, Button, Modal } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import List from './List';
+import i18n from '../../i18n';
+
+class Dialog extends React.Component {
+
+ componentDidMount() {
+ this.timer = setInterval(() => {
+ this.forceUpdate();
+ }, 30000);
+ }
+
+ componentWillUnmount() {
+ clearInterval(this.timer);
+ }
+
+ render() {
+ const {
+ log,
+ onHide,
+ show,
+ } = this.props;
+ return <Modal className="chat-bot-log-dialog" onHide={onHide} show={show} size="lg">
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {i18n.t('chatBotLog.heading')}
+ </Modal.Title>
+ </Modal.Header>
+ {log && log.length ?
+ <List log={log} />
+ :
+ <Modal.Body>
+ <Alert variant="info">
+ {i18n.t('chatBotLog.empty')}
+ </Alert>
+ </Modal.Body>
+ }
+ <Modal.Footer>
+ <Button onClick={onHide} variant="secondary">
+ {i18n.t('button.close')}
+ </Button>
+ </Modal.Footer>
+ </Modal>;
+ }
+
+}
+
+Dialog.propTypes = {
+ log: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ onHide: PropTypes.func,
+ show: PropTypes.bool,
+};
+
+Dialog.defaultProps = {
+ log: null,
+ onHide: null,
+ show: false,
+};
+
+export default withTranslation()(Dialog);
+++ /dev/null
-import axios from 'axios';
-import moment from 'moment';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, ListGroup, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import ChannelLink from '../channel/Link';
-import List from '../chat-logs/List';
-import Icon from '../common/Icon';
-import Loading from '../common/Loading';
-import { getUserName } from '../../helpers/User';
-
-const getEntryDate = entry => moment(entry.created_at).fromNow();
-
-const getEntryOrigin = (entry, t) => {
- return t('chatBotLog.origin.chatLog', {
- channel: entry.origin.params[0],
- date: new Date(entry.origin.created_at),
- nick: entry.origin.nick,
- });
-};
-
-const getEntryInfo = (entry, t) => {
- if (entry.user && entry.category) {
- return t('chatBotLog.info.userCat', {
- category: t(`twitchBot.chatCategories.${entry.category}`),
- date: getEntryDate(entry),
- user: getUserName(entry.user),
- });
- }
- if (entry.category) {
- return t('chatBotLog.info.cat', {
- category: t(`twitchBot.chatCategories.${entry.category}`),
- date: getEntryDate(entry),
- });
- }
- if (entry.user) {
- return t('chatBotLog.info.user', {
- date: getEntryDate(entry),
- user: getUserName(entry.user),
- });
- }
- return getEntryDate(entry);
-};
-
-const Item = ({ entry = {} }) => {
- const [context, setContext] = React.useState(null);
- const [contextLoading, setContextLoading] = React.useState(true);
- const [showContext, setShowContext] = React.useState(false);
-
- const { t } = useTranslation();
-
- React.useEffect(() => {
- if (context || !showContext) return;
- const ctrl = new AbortController();
- axios
- .get(`/api/chatbotlogs/${entry.id}/context`, {
- signal: ctrl.signal
- })
- .then(response => {
- setContextLoading(false);
- setContext(response.data);
- })
- .catch(error => {
- if (!axios.isCancel(error)) {
- setContextLoading(false);
- setContext(null);
- }
- });
- return () => {
- ctrl.abort();
- };
- }, [context, showContext]);
-
- return <ListGroup.Item>
- <div className="d-flex justify-content-between">
- <div>
- <div>
- {entry.text}
- </div>
- {entry.origin ?
- <div
- className="text-muted"
- >
- {getEntryOrigin(entry, t)}
- </div>
- : null}
- <div
- className="text-muted"
- title={moment(entry.created_at).format('LLLL')}
- >
- {getEntryInfo(entry, t)}
- </div>
- </div>
- <div>
- {entry.channel ?
- <ChannelLink channel={entry.channel} />
- : null}
- <Button
- className="ms-2"
- onClick={() => { setShowContext(c => !c); }}
- title={t('chatBotLog.showContext')}
- variant={showContext ? 'secondary' : 'outline-secondary'}
- >
- <Icon.PROTOCOL title="" />
- </Button>
- </div>
- </div>
- {showContext ?
- <div className="chat-bot-log-context mt-2">
- {contextLoading ?
- <Loading />
- : null}
- {context ?
- <Row>
- <Col sm={6}>
- <h3 className="fs-6">{t('chatBotLog.context')}</h3>
- <List log={context.current} />
- </Col>
- {context.original ?
- <Col sm={6}>
- <h3 className="fs-6">{t('chatBotLog.originalContext')}</h3>
- <List log={context.original} />
- </Col>
- : null}
- </Row>
- : null}
- </div>
- : null}
- </ListGroup.Item>;
-};
-
-Item.propTypes = {
- entry: PropTypes.shape({
- channel: PropTypes.shape({}),
- created_at: PropTypes.string,
- origin: PropTypes.shape({}),
- text: PropTypes.string,
- }),
-};
-
-export default Item;
--- /dev/null
+import axios from 'axios';
+import moment from 'moment';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, ListGroup, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import ChannelLink from '../channel/Link';
+import List from '../chat-logs/List';
+import Icon from '../common/Icon';
+import Loading from '../common/Loading';
+import { getUserName } from '../../helpers/User';
+
+const getEntryDate = entry => moment(entry.created_at).fromNow();
+
+const getEntryOrigin = (entry, t) => {
+ return t('chatBotLog.origin.chatLog', {
+ channel: entry.origin.params[0],
+ date: new Date(entry.origin.created_at),
+ nick: entry.origin.nick,
+ });
+};
+
+const getEntryInfo = (entry, t) => {
+ if (entry.user && entry.category) {
+ return t('chatBotLog.info.userCat', {
+ category: t(`twitchBot.chatCategories.${entry.category}`),
+ date: getEntryDate(entry),
+ user: getUserName(entry.user),
+ });
+ }
+ if (entry.category) {
+ return t('chatBotLog.info.cat', {
+ category: t(`twitchBot.chatCategories.${entry.category}`),
+ date: getEntryDate(entry),
+ });
+ }
+ if (entry.user) {
+ return t('chatBotLog.info.user', {
+ date: getEntryDate(entry),
+ user: getUserName(entry.user),
+ });
+ }
+ return getEntryDate(entry);
+};
+
+const Item = ({ entry = {} }) => {
+ const [context, setContext] = React.useState(null);
+ const [contextLoading, setContextLoading] = React.useState(true);
+ const [showContext, setShowContext] = React.useState(false);
+
+ const { t } = useTranslation();
+
+ React.useEffect(() => {
+ if (context || !showContext) return;
+ const ctrl = new AbortController();
+ axios
+ .get(`/api/chatbotlogs/${entry.id}/context`, {
+ signal: ctrl.signal
+ })
+ .then(response => {
+ setContextLoading(false);
+ setContext(response.data);
+ })
+ .catch(error => {
+ if (!axios.isCancel(error)) {
+ setContextLoading(false);
+ setContext(null);
+ }
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [context, showContext]);
+
+ return <ListGroup.Item>
+ <div className="d-flex justify-content-between">
+ <div>
+ <div>
+ {entry.text}
+ </div>
+ {entry.origin ?
+ <div
+ className="text-muted"
+ >
+ {getEntryOrigin(entry, t)}
+ </div>
+ : null}
+ <div
+ className="text-muted"
+ title={moment(entry.created_at).format('LLLL')}
+ >
+ {getEntryInfo(entry, t)}
+ </div>
+ </div>
+ <div>
+ {entry.channel ?
+ <ChannelLink channel={entry.channel} />
+ : null}
+ <Button
+ className="ms-2"
+ onClick={() => { setShowContext(c => !c); }}
+ title={t('chatBotLog.showContext')}
+ variant={showContext ? 'secondary' : 'outline-secondary'}
+ >
+ <Icon.PROTOCOL title="" />
+ </Button>
+ </div>
+ </div>
+ {showContext ?
+ <div className="chat-bot-log-context mt-2">
+ {contextLoading ?
+ <Loading />
+ : null}
+ {context ?
+ <Row>
+ <Col sm={6}>
+ <h3 className="fs-6">{t('chatBotLog.context')}</h3>
+ <List log={context.current} />
+ </Col>
+ {context.original ?
+ <Col sm={6}>
+ <h3 className="fs-6">{t('chatBotLog.originalContext')}</h3>
+ <List log={context.original} />
+ </Col>
+ : null}
+ </Row>
+ : null}
+ </div>
+ : null}
+ </ListGroup.Item>;
+};
+
+Item.propTypes = {
+ entry: PropTypes.shape({
+ channel: PropTypes.shape({}),
+ created_at: PropTypes.string,
+ origin: PropTypes.shape({}),
+ text: PropTypes.string,
+ }),
+};
+
+export default Item;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { ListGroup } from 'react-bootstrap';
-
-import Item from './Item';
-
-class List extends React.Component {
-
- componentDidMount() {
- this.timer = setInterval(() => {
- this.forceUpdate();
- }, 30000);
- }
-
- componentWillUnmount() {
- clearInterval(this.timer);
- }
-
- render() {
- const { log } = this.props;
-
- return <ListGroup variant="flush">
- {log ? log.map(entry =>
- <Item key={entry.id} entry={entry} />
- ) : null}
- </ListGroup>;
- }
-
-}
-
-List.propTypes = {
- log: PropTypes.arrayOf(PropTypes.shape({
- })),
-};
-
-List.defaultProps = {
- log: [],
-};
-
-export default List;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { ListGroup } from 'react-bootstrap';
+
+import Item from './Item';
+
+class List extends React.Component {
+
+ componentDidMount() {
+ this.timer = setInterval(() => {
+ this.forceUpdate();
+ }, 30000);
+ }
+
+ componentWillUnmount() {
+ clearInterval(this.timer);
+ }
+
+ render() {
+ const { log } = this.props;
+
+ return <ListGroup variant="flush">
+ {log ? log.map(entry =>
+ <Item key={entry.id} entry={entry} />
+ ) : null}
+ </ListGroup>;
+ }
+
+}
+
+List.propTypes = {
+ log: PropTypes.arrayOf(PropTypes.shape({
+ })),
+};
+
+List.defaultProps = {
+ log: [],
+};
+
+export default List;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-
-const getChatterColor = entry => {
- if (entry.tags && entry.tags['color']) {
- return entry.tags['color'];
- }
- return 'inherit';
-};
-
-const getChatterNick = entry => {
- if (entry.tags && entry.tags['display-name']) {
- return entry.tags['display-name'];
- }
- return entry.nick;
-};
-
-const getTextContent = entry => {
- if (entry.params && entry.params.length >= 2) {
- return entry.params[1];
- }
- return entry.text_content;
-};
-
-const getTimestamp = entry => {
- if (entry.tags && entry.tags['tmi-sent-ts']) {
- return new Date(parseInt(entry.tags['tmi-sent-ts'], 10));
- }
- return new Date(entry.created_at);
-};
-
-const Item = ({ entry }) => {
- const { t } = useTranslation();
-
- return <div className="chat-log-item">
- <div>
- <span className="text-muted me-2">
- {t('chatBotLog.shortTimestamp', { date: getTimestamp(entry) })}
- </span>
- <strong style={{ color: getChatterColor(entry) }}>{getChatterNick(entry)}</strong>
- </div>
- <div>{getTextContent(entry)}</div>
- </div>;
-};
-
-Item.propTypes = {
- entry: PropTypes.shape({
- text_content: PropTypes.string,
- }).isRequired,
-};
-
-export default Item;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+const getChatterColor = entry => {
+ if (entry.tags && entry.tags['color']) {
+ return entry.tags['color'];
+ }
+ return 'inherit';
+};
+
+const getChatterNick = entry => {
+ if (entry.tags && entry.tags['display-name']) {
+ return entry.tags['display-name'];
+ }
+ return entry.nick;
+};
+
+const getTextContent = entry => {
+ if (entry.params && entry.params.length >= 2) {
+ return entry.params[1];
+ }
+ return entry.text_content;
+};
+
+const getTimestamp = entry => {
+ if (entry.tags && entry.tags['tmi-sent-ts']) {
+ return new Date(parseInt(entry.tags['tmi-sent-ts'], 10));
+ }
+ return new Date(entry.created_at);
+};
+
+const Item = ({ entry }) => {
+ const { t } = useTranslation();
+
+ return <div className="chat-log-item">
+ <div>
+ <span className="text-muted me-2">
+ {t('chatBotLog.shortTimestamp', { date: getTimestamp(entry) })}
+ </span>
+ <strong style={{ color: getChatterColor(entry) }}>{getChatterNick(entry)}</strong>
+ </div>
+ <div>{getTextContent(entry)}</div>
+ </div>;
+};
+
+Item.propTypes = {
+ entry: PropTypes.shape({
+ text_content: PropTypes.string,
+ }).isRequired,
+};
+
+export default Item;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Item from './Item';
-
-const List = ({ log = [] }) => {
- return <div className="chat-log-list">
- {log.map(entry =>
- <Item key={entry.id} entry={entry} />
- )}
- </div>;
-};
-
-List.propTypes = {
- log: PropTypes.arrayOf(PropTypes.shape({
- })),
-};
-
-export default List;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Item from './Item';
+
+const List = ({ log = [] }) => {
+ return <div className="chat-log-list">
+ {log.map(entry =>
+ <Item key={entry.id} entry={entry} />
+ )}
+ </div>;
+};
+
+List.propTypes = {
+ log: PropTypes.arrayOf(PropTypes.shape({
+ })),
+};
+
+export default List;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-const AspectBox = ({ children = null, ratio = 1 }) =>
- <div className="aspect-box-container" style={{ paddingTop: `${1 / ratio * 100}%`}}>
- <div className="aspect-box-content">
- {children}
- </div>
- </div>;
-
-AspectBox.propTypes = {
- children: PropTypes.node,
- ratio: PropTypes.number,
-};
-
-export default AspectBox;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+const AspectBox = ({ children = null, ratio = 1 }) =>
+ <div className="aspect-box-container" style={{ paddingTop: `${1 / ratio * 100}%`}}>
+ <div className="aspect-box-content">
+ {children}
+ </div>
+ </div>;
+
+AspectBox.propTypes = {
+ children: PropTypes.node,
+ ratio: PropTypes.number,
+};
+
+export default AspectBox;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Helmet } from 'react-helmet';
-import { useTranslation } from 'react-i18next';
-
-const CanonicalLinks = ({ base, lang, langs }) => {
- const { i18n } = useTranslation();
-
- const activeLang = lang || i18n.language;
- const availableLangs = langs || ['de', 'en'];
-
- return <Helmet>
- <link
- href={`https://alttp.localhorst.tv${base}?lng=${activeLang}`}
- hrefLang={activeLang}
- rel="canonical"
- />
- <link
- href={`https://alttp.localhorst.tv${base}`}
- hrefLang="x-default"
- rel="alternate"
- />
- {availableLangs.filter(l => l !== activeLang).map(l =>
- <link
- key={l}
- href={`https://alttp.localhorst.tv${base}?lng=${l}`}
- hrefLang={l}
- rel="alternate"
- />
- )}
- </Helmet>;
-};
-
-CanonicalLinks.propTypes = {
- base: PropTypes.string.isRequired,
- lang: PropTypes.string,
- langs: PropTypes.arrayOf(PropTypes.string),
-};
-
-export default CanonicalLinks;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
+
+const CanonicalLinks = ({ base, lang, langs }) => {
+ const { i18n } = useTranslation();
+
+ const activeLang = lang || i18n.language;
+ const availableLangs = langs || ['de', 'en'];
+
+ return <Helmet>
+ <link
+ href={`https://alttp.localhorst.tv${base}?lng=${activeLang}`}
+ hrefLang={activeLang}
+ rel="canonical"
+ />
+ <link
+ href={`https://alttp.localhorst.tv${base}`}
+ hrefLang="x-default"
+ rel="alternate"
+ />
+ {availableLangs.filter(l => l !== activeLang).map(l =>
+ <link
+ key={l}
+ href={`https://alttp.localhorst.tv${base}?lng=${l}`}
+ hrefLang={l}
+ rel="alternate"
+ />
+ )}
+ </Helmet>;
+};
+
+CanonicalLinks.propTypes = {
+ base: PropTypes.string.isRequired,
+ lang: PropTypes.string,
+ langs: PropTypes.arrayOf(PropTypes.string),
+};
+
+export default CanonicalLinks;
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import { Alert, Button, Form, ListGroup } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Icon from './Icon';
-import debounce from '../../helpers/debounce';
-
-const ChannelSelect = ({
- autoSelect,
- joinable,
- manageable,
- onChange,
- readOnly,
- value,
-}) => {
- const [resolved, setResolved] = useState(null);
- const [results, setResults] = useState([]);
- const [search, setSearch] = useState('');
- const [showResults, setShowResults] = useState(false);
-
- const ref = useRef(null);
- const { t } = useTranslation();
-
- useEffect(() => {
- const handleEventOutside = e => {
- if (ref.current && !ref.current.contains(e.target)) {
- setShowResults(false);
- }
- };
- document.addEventListener('mousedown', handleEventOutside, true);
- document.addEventListener('focus', handleEventOutside, true);
- return () => {
- document.removeEventListener('mousedown', handleEventOutside, true);
- document.removeEventListener('focus', handleEventOutside, true);
- };
- }, []);
-
- let ctrl = null;
- const fetch = useCallback(debounce(async phrase => {
- if (ctrl) {
- ctrl.abort();
- }
- ctrl = new AbortController();
- try {
- const response = await axios.get(`/api/channels`, {
- params: {
- joinable: joinable ? 1 : 0,
- limit: 5,
- manageable: manageable ? 1 : 0,
- phrase,
- },
- signal: ctrl.signal,
- });
- ctrl = null;
- setResults(response.data);
- if (autoSelect && !phrase && response.data.length === 1) {
- onChange({
- channel: response.data[0],
- target: { value: response.data[0].id },
- });
- }
- } catch (e) {
- ctrl = null;
- console.error(e);
- }
- }, 300), [autoSelect, joinable, manageable]);
-
- useEffect(() => {
- fetch(search);
- }, [search]);
-
- useEffect(() => {
- if (value) {
- axios
- .get(`/api/channels/${value}`)
- .then(response => {
- setResolved(response.data);
- });
- } else {
- setResolved(null);
- }
- }, [value]);
-
- if (value) {
- return <div className="d-flex align-items-center justify-content-between">
- <span>{resolved ? resolved.title : value}</span>
- {!readOnly ?
- <Button
- className="ms-2"
- onClick={() => onChange({ channel: null, target: { value: '' }})}
- title={t('button.unset')}
- variant="outline-danger"
- >
- <Icon.REMOVE title="" />
- </Button>
- : null}
- </div>;
- }
- return <div className={`channel-select ${showResults ? 'expanded' : 'collapsed'}`} ref={ref}>
- <Form.Control
- className="search-input"
- name={Math.random().toString(20).substr(2, 10)}
- onChange={e => setSearch(e.target.value)}
- onFocus={() => setShowResults(true)}
- readOnly={readOnly}
- type="search"
- value={search}
- />
- <div className="search-results-holder">
- {results.length ?
- <ListGroup className="search-results">
- {results.map(result =>
- <ListGroup.Item
- action
- key={result.id}
- onClick={() => onChange({
- channel: result,
- target: { value: result.id },
- })}
- >
- {result.title}
- </ListGroup.Item>
- )}
- </ListGroup>
- :
- <Alert className="search-results" variant="info">
- {t('search.noResults')}
- </Alert>
- }
- </div>
- </div>;
-};
-
-ChannelSelect.propTypes = {
- autoSelect: PropTypes.bool,
- joinable: PropTypes.bool,
- manageable: PropTypes.bool,
- onChange: PropTypes.func,
- readOnly: PropTypes.bool,
- value: PropTypes.oneOfType([
- PropTypes.number,
- PropTypes.string,
- ]),
-};
-
-export default ChannelSelect;
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { Alert, Button, Form, ListGroup } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Icon from './Icon';
+import debounce from '../../helpers/debounce';
+
+const ChannelSelect = ({
+ autoSelect,
+ joinable,
+ manageable,
+ onChange,
+ readOnly,
+ value,
+}) => {
+ const [resolved, setResolved] = useState(null);
+ const [results, setResults] = useState([]);
+ const [search, setSearch] = useState('');
+ const [showResults, setShowResults] = useState(false);
+
+ const ref = useRef(null);
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ const handleEventOutside = e => {
+ if (ref.current && !ref.current.contains(e.target)) {
+ setShowResults(false);
+ }
+ };
+ document.addEventListener('mousedown', handleEventOutside, true);
+ document.addEventListener('focus', handleEventOutside, true);
+ return () => {
+ document.removeEventListener('mousedown', handleEventOutside, true);
+ document.removeEventListener('focus', handleEventOutside, true);
+ };
+ }, []);
+
+ let ctrl = null;
+ const fetch = useCallback(debounce(async phrase => {
+ if (ctrl) {
+ ctrl.abort();
+ }
+ ctrl = new AbortController();
+ try {
+ const response = await axios.get(`/api/channels`, {
+ params: {
+ joinable: joinable ? 1 : 0,
+ limit: 5,
+ manageable: manageable ? 1 : 0,
+ phrase,
+ },
+ signal: ctrl.signal,
+ });
+ ctrl = null;
+ setResults(response.data);
+ if (autoSelect && !phrase && response.data.length === 1) {
+ onChange({
+ channel: response.data[0],
+ target: { value: response.data[0].id },
+ });
+ }
+ } catch (e) {
+ ctrl = null;
+ console.error(e);
+ }
+ }, 300), [autoSelect, joinable, manageable]);
+
+ useEffect(() => {
+ fetch(search);
+ }, [search]);
+
+ useEffect(() => {
+ if (value) {
+ axios
+ .get(`/api/channels/${value}`)
+ .then(response => {
+ setResolved(response.data);
+ });
+ } else {
+ setResolved(null);
+ }
+ }, [value]);
+
+ if (value) {
+ return <div className="d-flex align-items-center justify-content-between">
+ <span>{resolved ? resolved.title : value}</span>
+ {!readOnly ?
+ <Button
+ className="ms-2"
+ onClick={() => onChange({ channel: null, target: { value: '' }})}
+ title={t('button.unset')}
+ variant="outline-danger"
+ >
+ <Icon.REMOVE title="" />
+ </Button>
+ : null}
+ </div>;
+ }
+ return <div className={`channel-select ${showResults ? 'expanded' : 'collapsed'}`} ref={ref}>
+ <Form.Control
+ className="search-input"
+ name={Math.random().toString(20).substr(2, 10)}
+ onChange={e => setSearch(e.target.value)}
+ onFocus={() => setShowResults(true)}
+ readOnly={readOnly}
+ type="search"
+ value={search}
+ />
+ <div className="search-results-holder">
+ {results.length ?
+ <ListGroup className="search-results">
+ {results.map(result =>
+ <ListGroup.Item
+ action
+ key={result.id}
+ onClick={() => onChange({
+ channel: result,
+ target: { value: result.id },
+ })}
+ >
+ {result.title}
+ </ListGroup.Item>
+ )}
+ </ListGroup>
+ :
+ <Alert className="search-results" variant="info">
+ {t('search.noResults')}
+ </Alert>
+ }
+ </div>
+ </div>;
+};
+
+ChannelSelect.propTypes = {
+ autoSelect: PropTypes.bool,
+ joinable: PropTypes.bool,
+ manageable: PropTypes.bool,
+ onChange: PropTypes.func,
+ readOnly: PropTypes.bool,
+ value: PropTypes.oneOfType([
+ PropTypes.number,
+ PropTypes.string,
+ ]),
+};
+
+export default ChannelSelect;
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React, { useCallback, useEffect, useState } from 'react';
-import { Alert, Button, Form, ListGroup } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Icon from './Icon';
-import ChannelBox from '../discord-guilds/ChannelBox';
-import debounce from '../../helpers/debounce';
-
-const DiscordChannelSelect = ({
- guild,
- name,
- onChange,
- types,
- value,
-}) => {
- const [resolved, setResolved] = useState(null);
- const [results, setResults] = useState([]);
- const [search, setSearch] = useState('');
- const [showResults, setShowResults] = useState(false);
-
- const ref = React.useRef(null);
- const { t } = useTranslation();
-
- useEffect(() => {
- const handleEventOutside = e => {
- if (ref.current && !ref.current.contains(e.target)) {
- setShowResults(false);
- }
- };
- document.addEventListener('mousedown', handleEventOutside, true);
- document.addEventListener('focus', handleEventOutside, true);
- return () => {
- document.removeEventListener('mousedown', handleEventOutside, true);
- document.removeEventListener('focus', handleEventOutside, true);
- };
- }, []);
-
- let ctrl = null;
- const fetch = useCallback(debounce(async (guild, phrase, types) => {
- if (ctrl) {
- ctrl.abort();
- }
- ctrl = new AbortController();
- try {
- const response = await axios.get(`/api/discord-guilds/${guild}/channels`, {
- params: {
- phrase,
- types,
- },
- signal: ctrl.signal,
- });
- ctrl = null;
- setResults(response.data);
- } catch (e) {
- ctrl = null;
- console.error(e);
- }
- return () => {
- if (ctrl) ctrl.abort();
- };
- }, 300), []);
-
- useEffect(() => {
- fetch(guild, search, types);
- }, [guild, search, ...types]);
-
- useEffect(() => {
- if (value) {
- axios
- .get(`/api/discord-channels/${value}`)
- .then(response => {
- setResolved(response.data);
- });
- } else {
- setResolved(null);
- }
- }, [value]);
-
- if (value) {
- return <div className="d-flex align-items-center justify-content-between">
- <span>{resolved ? <ChannelBox channel={resolved} /> : value}</span>
- <Button
- className="ms-2"
- onClick={() => onChange({ guild: null, target: { name, value: '' }})}
- title={t('button.unset')}
- variant="outline-danger"
- >
- <Icon.REMOVE title="" />
- </Button>
- </div>;
- }
- return <div className={`discord-select ${showResults ? 'expanded' : 'collapsed'}`} ref={ref}>
- <Form.Control
- className="search-input"
- name={Math.random().toString(20).substr(2, 10)}
- onChange={e => setSearch(e.target.value)}
- onFocus={() => setShowResults(true)}
- type="search"
- value={search}
- />
- <div className="search-results-holder">
- {results.length ?
- <ListGroup className="search-results">
- {results.map(result =>
- <ListGroup.Item
- action
- key={result.id}
- onClick={() => onChange({
- channel: result,
- target: { name, value: result.channel_id },
- })}
- >
- <ChannelBox channel={result} />
- </ListGroup.Item>
- )}
- </ListGroup>
- :
- <Alert className="search-results" variant="info">
- {t('search.noResults')}
- </Alert>
- }
- </div>
- </div>;
-};
-
-DiscordChannelSelect.propTypes = {
- guild: PropTypes.string,
- isInvalid: PropTypes.bool,
- name: PropTypes.string,
- onBlur: PropTypes.func,
- onChange: PropTypes.func,
- types: PropTypes.arrayOf(PropTypes.number),
- value: PropTypes.string,
-};
-
-export default DiscordChannelSelect;
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React, { useCallback, useEffect, useState } from 'react';
+import { Alert, Button, Form, ListGroup } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Icon from './Icon';
+import ChannelBox from '../discord-guilds/ChannelBox';
+import debounce from '../../helpers/debounce';
+
+const DiscordChannelSelect = ({
+ guild,
+ name,
+ onChange,
+ types,
+ value,
+}) => {
+ const [resolved, setResolved] = useState(null);
+ const [results, setResults] = useState([]);
+ const [search, setSearch] = useState('');
+ const [showResults, setShowResults] = useState(false);
+
+ const ref = React.useRef(null);
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ const handleEventOutside = e => {
+ if (ref.current && !ref.current.contains(e.target)) {
+ setShowResults(false);
+ }
+ };
+ document.addEventListener('mousedown', handleEventOutside, true);
+ document.addEventListener('focus', handleEventOutside, true);
+ return () => {
+ document.removeEventListener('mousedown', handleEventOutside, true);
+ document.removeEventListener('focus', handleEventOutside, true);
+ };
+ }, []);
+
+ let ctrl = null;
+ const fetch = useCallback(debounce(async (guild, phrase, types) => {
+ if (ctrl) {
+ ctrl.abort();
+ }
+ ctrl = new AbortController();
+ try {
+ const response = await axios.get(`/api/discord-guilds/${guild}/channels`, {
+ params: {
+ phrase,
+ types,
+ },
+ signal: ctrl.signal,
+ });
+ ctrl = null;
+ setResults(response.data);
+ } catch (e) {
+ ctrl = null;
+ console.error(e);
+ }
+ return () => {
+ if (ctrl) ctrl.abort();
+ };
+ }, 300), []);
+
+ useEffect(() => {
+ fetch(guild, search, types);
+ }, [guild, search, ...types]);
+
+ useEffect(() => {
+ if (value) {
+ axios
+ .get(`/api/discord-channels/${value}`)
+ .then(response => {
+ setResolved(response.data);
+ });
+ } else {
+ setResolved(null);
+ }
+ }, [value]);
+
+ if (value) {
+ return <div className="d-flex align-items-center justify-content-between">
+ <span>{resolved ? <ChannelBox channel={resolved} /> : value}</span>
+ <Button
+ className="ms-2"
+ onClick={() => onChange({ guild: null, target: { name, value: '' }})}
+ title={t('button.unset')}
+ variant="outline-danger"
+ >
+ <Icon.REMOVE title="" />
+ </Button>
+ </div>;
+ }
+ return <div className={`discord-select ${showResults ? 'expanded' : 'collapsed'}`} ref={ref}>
+ <Form.Control
+ className="search-input"
+ name={Math.random().toString(20).substr(2, 10)}
+ onChange={e => setSearch(e.target.value)}
+ onFocus={() => setShowResults(true)}
+ type="search"
+ value={search}
+ />
+ <div className="search-results-holder">
+ {results.length ?
+ <ListGroup className="search-results">
+ {results.map(result =>
+ <ListGroup.Item
+ action
+ key={result.id}
+ onClick={() => onChange({
+ channel: result,
+ target: { name, value: result.channel_id },
+ })}
+ >
+ <ChannelBox channel={result} />
+ </ListGroup.Item>
+ )}
+ </ListGroup>
+ :
+ <Alert className="search-results" variant="info">
+ {t('search.noResults')}
+ </Alert>
+ }
+ </div>
+ </div>;
+};
+
+DiscordChannelSelect.propTypes = {
+ guild: PropTypes.string,
+ isInvalid: PropTypes.bool,
+ name: PropTypes.string,
+ onBlur: PropTypes.func,
+ onChange: PropTypes.func,
+ types: PropTypes.arrayOf(PropTypes.number),
+ value: PropTypes.string,
+};
+
+export default DiscordChannelSelect;
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import { Alert, Button, Form, ListGroup } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Icon from './Icon';
-import GuildBox from '../discord-guilds/Box';
-import debounce from '../../helpers/debounce';
-
-const DiscordSelect = ({ onChange, value }) => {
- const [resolved, setResolved] = useState(null);
- const [results, setResults] = useState([]);
- const [search, setSearch] = useState('');
- const [showResults, setShowResults] = useState(false);
-
- const ref = useRef(null);
- const { t } = useTranslation();
-
- useEffect(() => {
- const handleEventOutside = e => {
- if (ref.current && !ref.current.contains(e.target)) {
- setShowResults(false);
- }
- };
- document.addEventListener('mousedown', handleEventOutside, true);
- document.addEventListener('focus', handleEventOutside, true);
- return () => {
- document.removeEventListener('mousedown', handleEventOutside, true);
- document.removeEventListener('focus', handleEventOutside, true);
- };
- }, []);
-
- let ctrl = null;
- const fetch = useCallback(debounce(async phrase => {
- if (ctrl) {
- ctrl.abort();
- }
- ctrl = new AbortController();
- try {
- const response = await axios.get(`/api/discord-guilds`, {
- params: {
- phrase,
- },
- signal: ctrl.signal,
- });
- ctrl = null;
- setResults(response.data);
- } catch (e) {
- ctrl = null;
- console.error(e);
- }
- }, 300), []);
-
- useEffect(() => {
- fetch(search);
- }, [search]);
-
- useEffect(() => {
- if (value) {
- axios
- .get(`/api/discord-guilds/${value}`)
- .then(response => {
- setResolved(response.data);
- });
- } else {
- setResolved(null);
- }
- }, [value]);
-
- if (value) {
- return <div className="d-flex align-items-center justify-content-between">
- <span>{resolved ? <GuildBox guild={resolved} /> : value}</span>
- <Button
- className="ms-2"
- onClick={() => onChange({ guild: null, target: { value: '' }})}
- title={t('button.unset')}
- variant="outline-danger"
- >
- <Icon.REMOVE title="" />
- </Button>
- </div>;
- }
- return <div className={`discord-select ${showResults ? 'expanded' : 'collapsed'}`} ref={ref}>
- <Form.Control
- className="search-input"
- name={Math.random().toString(20).substr(2, 10)}
- onChange={e => setSearch(e.target.value)}
- onFocus={() => setShowResults(true)}
- type="search"
- value={search}
- />
- <div className="search-results-holder">
- {results.length ?
- <ListGroup className="search-results">
- {results.map(result =>
- <ListGroup.Item
- action
- key={result.id}
- onClick={() => onChange({
- guild: result,
- target: { value: result.guild_id },
- })}
- >
- <GuildBox guild={result} />
- </ListGroup.Item>
- )}
- </ListGroup>
- :
- <Alert className="search-results" variant="info">
- {t('search.noResults')}
- </Alert>
- }
- </div>
- </div>;
-};
-
-DiscordSelect.propTypes = {
- onChange: PropTypes.func,
- value: PropTypes.string,
-};
-
-export default DiscordSelect;
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { Alert, Button, Form, ListGroup } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Icon from './Icon';
+import GuildBox from '../discord-guilds/Box';
+import debounce from '../../helpers/debounce';
+
+const DiscordSelect = ({ onChange, value }) => {
+ const [resolved, setResolved] = useState(null);
+ const [results, setResults] = useState([]);
+ const [search, setSearch] = useState('');
+ const [showResults, setShowResults] = useState(false);
+
+ const ref = useRef(null);
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ const handleEventOutside = e => {
+ if (ref.current && !ref.current.contains(e.target)) {
+ setShowResults(false);
+ }
+ };
+ document.addEventListener('mousedown', handleEventOutside, true);
+ document.addEventListener('focus', handleEventOutside, true);
+ return () => {
+ document.removeEventListener('mousedown', handleEventOutside, true);
+ document.removeEventListener('focus', handleEventOutside, true);
+ };
+ }, []);
+
+ let ctrl = null;
+ const fetch = useCallback(debounce(async phrase => {
+ if (ctrl) {
+ ctrl.abort();
+ }
+ ctrl = new AbortController();
+ try {
+ const response = await axios.get(`/api/discord-guilds`, {
+ params: {
+ phrase,
+ },
+ signal: ctrl.signal,
+ });
+ ctrl = null;
+ setResults(response.data);
+ } catch (e) {
+ ctrl = null;
+ console.error(e);
+ }
+ }, 300), []);
+
+ useEffect(() => {
+ fetch(search);
+ }, [search]);
+
+ useEffect(() => {
+ if (value) {
+ axios
+ .get(`/api/discord-guilds/${value}`)
+ .then(response => {
+ setResolved(response.data);
+ });
+ } else {
+ setResolved(null);
+ }
+ }, [value]);
+
+ if (value) {
+ return <div className="d-flex align-items-center justify-content-between">
+ <span>{resolved ? <GuildBox guild={resolved} /> : value}</span>
+ <Button
+ className="ms-2"
+ onClick={() => onChange({ guild: null, target: { value: '' }})}
+ title={t('button.unset')}
+ variant="outline-danger"
+ >
+ <Icon.REMOVE title="" />
+ </Button>
+ </div>;
+ }
+ return <div className={`discord-select ${showResults ? 'expanded' : 'collapsed'}`} ref={ref}>
+ <Form.Control
+ className="search-input"
+ name={Math.random().toString(20).substr(2, 10)}
+ onChange={e => setSearch(e.target.value)}
+ onFocus={() => setShowResults(true)}
+ type="search"
+ value={search}
+ />
+ <div className="search-results-holder">
+ {results.length ?
+ <ListGroup className="search-results">
+ {results.map(result =>
+ <ListGroup.Item
+ action
+ key={result.id}
+ onClick={() => onChange({
+ guild: result,
+ target: { value: result.guild_id },
+ })}
+ >
+ <GuildBox guild={result} />
+ </ListGroup.Item>
+ )}
+ </ListGroup>
+ :
+ <Alert className="search-results" variant="info">
+ {t('search.noResults')}
+ </Alert>
+ }
+ </div>
+ </div>;
+};
+
+DiscordSelect.propTypes = {
+ onChange: PropTypes.func,
+ value: PropTypes.string,
+};
+
+export default DiscordSelect;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import ErrorMessage from './ErrorMessage';
-
-class ErrorBoundary extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- error: null,
- };
- }
-
- static getDerivedStateFromError(error) {
- return { error };
- }
-
- componentDidCatch(error, errorInfo) {
- console.log(error, errorInfo);
- }
-
- render() {
- const { children } = this.props;
- const { error } = this.state;
- if (error) {
- return <ErrorMessage error={error} />;
- }
- return children;
- }
-}
-
-ErrorBoundary.propTypes = {
- children: PropTypes.node,
-};
-
-export default ErrorBoundary;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import ErrorMessage from './ErrorMessage';
+
+class ErrorBoundary extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ error: null,
+ };
+ }
+
+ static getDerivedStateFromError(error) {
+ return { error };
+ }
+
+ componentDidCatch(error, errorInfo) {
+ console.log(error, errorInfo);
+ }
+
+ render() {
+ const { children } = this.props;
+ const { error } = this.state;
+ if (error) {
+ return <ErrorMessage error={error} />;
+ }
+ return children;
+ }
+}
+
+ErrorBoundary.propTypes = {
+ children: PropTypes.node,
+};
+
+export default ErrorBoundary;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Alert } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import i18n from '../../i18n';
-
-const ErrorMessage = ({ error }) => {
- if (error.response) {
- return <Alert variant="danger">
- <Alert.Heading>{i18n.t(`error.${error.response.status}.heading`)}</Alert.Heading>
- <p className="mb-0">{i18n.t(`error.${error.response.status}.description`)}</p>
- </Alert>;
- }
- if (error.message) {
- return <Alert variant="danger">
- <Alert.Heading>Error</Alert.Heading>
- <p className="mb-0">{error.message}</p>
- </Alert>;
- }
- return <div className="error">Error</div>;
-};
-
-ErrorMessage.propTypes = {
- error: PropTypes.shape({
- message: PropTypes.string,
- request: PropTypes.shape({}),
- response: PropTypes.shape({
- status: PropTypes.number,
- }),
- }),
-};
-
-export default withTranslation()(ErrorMessage);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Alert } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import i18n from '../../i18n';
+
+const ErrorMessage = ({ error }) => {
+ if (error.response) {
+ return <Alert variant="danger">
+ <Alert.Heading>{i18n.t(`error.${error.response.status}.heading`)}</Alert.Heading>
+ <p className="mb-0">{i18n.t(`error.${error.response.status}.description`)}</p>
+ </Alert>;
+ }
+ if (error.message) {
+ return <Alert variant="danger">
+ <Alert.Heading>Error</Alert.Heading>
+ <p className="mb-0">{error.message}</p>
+ </Alert>;
+ }
+ return <div className="error">Error</div>;
+};
+
+ErrorMessage.propTypes = {
+ error: PropTypes.shape({
+ message: PropTypes.string,
+ request: PropTypes.shape({}),
+ response: PropTypes.shape({
+ status: PropTypes.number,
+ }),
+ }),
+};
+
+export default withTranslation()(ErrorMessage);
+++ /dev/null
-import { html } from '@codemirror/lang-html';
-import { EditorView } from '@codemirror/view';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { githubDark } from '@uiw/codemirror-theme-github';
-import CodeMirror from '@uiw/react-codemirror';
-
-const HTMLInput = ({
- name,
- onChange,
- value,
-}) => {
- const handleChange = React.useCallback((value) => {
- return onChange({ target: { name, value } });
- }, [name, onChange]);
-
- return <CodeMirror
- extensions={[html(), EditorView.lineWrapping]}
- onChange={handleChange}
- theme={githubDark}
- value={value}
- />;
-};
-
-HTMLInput.propTypes = {
- name: PropTypes.string,
- onChange: PropTypes.func,
- value: PropTypes.string,
-};
-
-export default HTMLInput;
--- /dev/null
+import { html } from '@codemirror/lang-html';
+import { EditorView } from '@codemirror/view';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { githubDark } from '@uiw/codemirror-theme-github';
+import CodeMirror from '@uiw/react-codemirror';
+
+const HTMLInput = ({
+ name,
+ onChange,
+ value,
+}) => {
+ const handleChange = React.useCallback((value) => {
+ return onChange({ target: { name, value } });
+ }, [name, onChange]);
+
+ return <CodeMirror
+ extensions={[html(), EditorView.lineWrapping]}
+ onChange={handleChange}
+ theme={githubDark}
+ value={value}
+ />;
+};
+
+HTMLInput.propTypes = {
+ name: PropTypes.string,
+ onChange: PropTypes.func,
+ value: PropTypes.string,
+};
+
+export default HTMLInput;
+++ /dev/null
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { fab } from '@fortawesome/free-brands-svg-icons';
-import { fas } from '@fortawesome/free-solid-svg-icons';
-import React from 'react';
-import PropTypes from 'prop-types';
-import { withTranslation } from 'react-i18next';
-
-import i18n from '../../i18n';
-
-library.add(fab);
-library.add(fas);
-
-const Icon = ({
- alt = null,
- className = '',
- name,
- size = null,
- title = null,
-}) =>
- <FontAwesomeIcon
- icon={name}
- alt={alt}
- className={name === Icon.LOADING ? `${className} fa-spin` : className}
- size={size}
- title={title}
- />
-;
-
-Icon.propTypes = {
- name: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.arrayOf(PropTypes.string),
- ]).isRequired,
- alt: PropTypes.string,
- className: PropTypes.string,
- size: PropTypes.string,
- title: PropTypes.string,
-};
-
-const makePreset = (presetDisplayName, presetName) => {
- const preset = ({ alt, className, name, size, title}) => <Icon
- alt={alt || i18n.t(`icon.${presetDisplayName}`)}
- className={className}
- name={name || presetName}
- size={size}
- title={title !== '' ? title || alt || i18n.t(`icon.${presetDisplayName}`) : null}
- />;
- preset.displayName = presetDisplayName;
- return withTranslation()(preset);
-};
-
-Icon.ACCEPT = makePreset('AcceptIcon', 'square-check');
-Icon.ADD = makePreset('AddIcon', 'circle-plus');
-Icon.ALLOWED = makePreset('AllowedIcon', 'square-check');
-Icon.APPLY = makePreset('ApplyIcon', 'right-to-bracket');
-Icon.APPLICATIONS = makePreset('ApplicationsIcon', 'person-running');
-Icon.BROWSER_SOURCE = makePreset('BrowserSourceIcon', 'tv');
-Icon.CHART = makePreset('ChartIcon', 'chart-line');
-Icon.CROSSHAIRS = makePreset('CrosshairsIcon', 'crosshairs');
-Icon.DELETE = makePreset('DeleteIcon', 'user-xmark');
-Icon.DISCORD = makePreset('DiscordIcon', ['fab', 'discord']);
-Icon.EDIT = makePreset('EditIcon', 'edit');
-Icon.FILTER = makePreset('FilterIcon', 'filter');
-Icon.FINISHED = makePreset('FinishedIcon', 'square-check');
-Icon.FIRST_PLACE = makePreset('FirstPlaceIcon', 'trophy');
-Icon.FORBIDDEN = makePreset('ForbiddenIcon', 'square-xmark');
-Icon.FORFEIT = makePreset('ForfeitIcon', 'square-xmark');
-Icon.HASH = makePreset('HashIcon', 'hashtag');
-Icon.INFO = makePreset('Info', 'circle-info');
-Icon.INVERT = makePreset('InvertIcon', 'circle-half-stroke');
-Icon.LANGUAGE = makePreset('LanguageIcon', 'language');
-Icon.LOAD = makePreset('LoadIcon', 'upload');
-Icon.LOCKED = makePreset('LockedIcon', 'lock');
-Icon.LOGOUT = makePreset('LogoutIcon', 'sign-out-alt');
-Icon.MENU = makePreset('MenuIcon', 'bars');
-Icon.MICROPHONE = makePreset('MicrophoneIcon', 'microphone');
-Icon.MONITOR = makePreset('MonitorIcon', 'tv');
-Icon.MOUSE = makePreset('MouseIcon', 'arrow-pointer');
-Icon.OPEN = makePreset('OpenIcon', 'arrow-up-right-from-square');
-Icon.PAUSE = makePreset('PauseIcon', 'pause');
-Icon.PENDING = makePreset('PendingIcon', 'clock');
-Icon.PIN = makePreset('PinIcon', 'location-pin');
-Icon.PLAY = makePreset('PlayIcon', 'play');
-Icon.PROTOCOL = makePreset('ProtocolIcon', 'file-alt');
-Icon.RACETIME = makePreset('RacetimeIcon', 'stopwatch');
-Icon.REJECT = makePreset('RejectIcon', 'square-xmark');
-Icon.REMOVE = makePreset('RemoveIcon', 'square-xmark');
-Icon.RESET = makePreset('ResetIcon', 'rotate-left');
-Icon.RESULT = makePreset('ResultIcon', 'clock');
-Icon.SAVE = makePreset('SaveIcon', 'download');
-Icon.SECOND_PLACE = makePreset('SecondPlaceIcon', 'medal');
-Icon.SETTINGS = makePreset('SettingsIcon', 'cog');
-Icon.SLASH = makePreset('SlashIcon', 'slash');
-Icon.STEP_BACKWARD = makePreset('StepBackwardIcon', 'backward-step');
-Icon.STEP_FORWARD = makePreset('StepForwardIcon', 'forward-step');
-Icon.STOP = makePreset('StopIcon', 'stop');
-Icon.STREAM = makePreset('StreamIcon', ['fab', 'twitch']);
-Icon.THIRD_PLACE = makePreset('ThirdPlaceIcon', 'award');
-Icon.TIME_REVERSE = makePreset('TimeReverseIcon', 'clock-rotate-left');
-Icon.TWITCH = makePreset('TwitchIcon', ['fab', 'twitch']);
-Icon.UNKNOWN = makePreset('UnknownIcon', 'square-question');
-Icon.UNLOCKED = makePreset('UnlockedIcon', 'lock-open');
-Icon.VIDEO = makePreset('VideoIcon', 'video');
-Icon.WARNING = makePreset('WarningIcon', 'triangle-exclamation');
-Icon.VOLUME = makePreset('VolumeIcon', 'volume-high');
-Icon.YOUTUBE = makePreset('YoutubeIcon', ['fab', 'youtube']);
-
-export default Icon;
--- /dev/null
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { fab } from '@fortawesome/free-brands-svg-icons';
+import { fas } from '@fortawesome/free-solid-svg-icons';
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+import i18n from '../../i18n';
+
+library.add(fab);
+library.add(fas);
+
+const Icon = ({
+ alt = null,
+ className = '',
+ name,
+ size = null,
+ title = null,
+}) =>
+ <FontAwesomeIcon
+ icon={name}
+ alt={alt}
+ className={name === Icon.LOADING ? `${className} fa-spin` : className}
+ size={size}
+ title={title}
+ />
+;
+
+Icon.propTypes = {
+ name: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.arrayOf(PropTypes.string),
+ ]).isRequired,
+ alt: PropTypes.string,
+ className: PropTypes.string,
+ size: PropTypes.string,
+ title: PropTypes.string,
+};
+
+const makePreset = (presetDisplayName, presetName) => {
+ const preset = ({ alt, className, name, size, title}) => <Icon
+ alt={alt || i18n.t(`icon.${presetDisplayName}`)}
+ className={className}
+ name={name || presetName}
+ size={size}
+ title={title !== '' ? title || alt || i18n.t(`icon.${presetDisplayName}`) : null}
+ />;
+ preset.displayName = presetDisplayName;
+ return withTranslation()(preset);
+};
+
+Icon.ACCEPT = makePreset('AcceptIcon', 'square-check');
+Icon.ADD = makePreset('AddIcon', 'circle-plus');
+Icon.ALLOWED = makePreset('AllowedIcon', 'square-check');
+Icon.APPLY = makePreset('ApplyIcon', 'right-to-bracket');
+Icon.APPLICATIONS = makePreset('ApplicationsIcon', 'person-running');
+Icon.BROWSER_SOURCE = makePreset('BrowserSourceIcon', 'tv');
+Icon.CHART = makePreset('ChartIcon', 'chart-line');
+Icon.CROSSHAIRS = makePreset('CrosshairsIcon', 'crosshairs');
+Icon.DELETE = makePreset('DeleteIcon', 'user-xmark');
+Icon.DISCORD = makePreset('DiscordIcon', ['fab', 'discord']);
+Icon.EDIT = makePreset('EditIcon', 'edit');
+Icon.FILTER = makePreset('FilterIcon', 'filter');
+Icon.FINISHED = makePreset('FinishedIcon', 'square-check');
+Icon.FIRST_PLACE = makePreset('FirstPlaceIcon', 'trophy');
+Icon.FORBIDDEN = makePreset('ForbiddenIcon', 'square-xmark');
+Icon.FORFEIT = makePreset('ForfeitIcon', 'square-xmark');
+Icon.HASH = makePreset('HashIcon', 'hashtag');
+Icon.INFO = makePreset('Info', 'circle-info');
+Icon.INVERT = makePreset('InvertIcon', 'circle-half-stroke');
+Icon.LANGUAGE = makePreset('LanguageIcon', 'language');
+Icon.LOAD = makePreset('LoadIcon', 'upload');
+Icon.LOCKED = makePreset('LockedIcon', 'lock');
+Icon.LOGOUT = makePreset('LogoutIcon', 'sign-out-alt');
+Icon.MENU = makePreset('MenuIcon', 'bars');
+Icon.MICROPHONE = makePreset('MicrophoneIcon', 'microphone');
+Icon.MONITOR = makePreset('MonitorIcon', 'tv');
+Icon.MOUSE = makePreset('MouseIcon', 'arrow-pointer');
+Icon.OPEN = makePreset('OpenIcon', 'arrow-up-right-from-square');
+Icon.PAUSE = makePreset('PauseIcon', 'pause');
+Icon.PENDING = makePreset('PendingIcon', 'clock');
+Icon.PIN = makePreset('PinIcon', 'location-pin');
+Icon.PLAY = makePreset('PlayIcon', 'play');
+Icon.PROTOCOL = makePreset('ProtocolIcon', 'file-alt');
+Icon.RACETIME = makePreset('RacetimeIcon', 'stopwatch');
+Icon.REJECT = makePreset('RejectIcon', 'square-xmark');
+Icon.REMOVE = makePreset('RemoveIcon', 'square-xmark');
+Icon.RESET = makePreset('ResetIcon', 'rotate-left');
+Icon.RESULT = makePreset('ResultIcon', 'clock');
+Icon.SAVE = makePreset('SaveIcon', 'download');
+Icon.SECOND_PLACE = makePreset('SecondPlaceIcon', 'medal');
+Icon.SETTINGS = makePreset('SettingsIcon', 'cog');
+Icon.SLASH = makePreset('SlashIcon', 'slash');
+Icon.STEP_BACKWARD = makePreset('StepBackwardIcon', 'backward-step');
+Icon.STEP_FORWARD = makePreset('StepForwardIcon', 'forward-step');
+Icon.STOP = makePreset('StopIcon', 'stop');
+Icon.STREAM = makePreset('StreamIcon', ['fab', 'twitch']);
+Icon.THIRD_PLACE = makePreset('ThirdPlaceIcon', 'award');
+Icon.TIME_REVERSE = makePreset('TimeReverseIcon', 'clock-rotate-left');
+Icon.TWITCH = makePreset('TwitchIcon', ['fab', 'twitch']);
+Icon.UNKNOWN = makePreset('UnknownIcon', 'square-question');
+Icon.UNLOCKED = makePreset('UnlockedIcon', 'lock-open');
+Icon.VIDEO = makePreset('VideoIcon', 'video');
+Icon.WARNING = makePreset('WarningIcon', 'triangle-exclamation');
+Icon.VOLUME = makePreset('VolumeIcon', 'volume-high');
+Icon.YOUTUBE = makePreset('YoutubeIcon', ['fab', 'youtube']);
+
+export default Icon;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Icon from './Icon';
-
-const LargeCheck = ({
- className = '',
- id = '',
- name = '',
- onBlur = null,
- onChange = null,
- value = false,
-}) => {
- let clsn = className ? `${className} custom-check` : 'custom-check';
- if (value) {
- clsn += ' checked';
- }
- return <span
- className={clsn}
- id={id}
- onBlur={onBlur ? () => onBlur({ target: { name, value } }) : null}
- onClick={onChange ? () => onChange({ target: { name, value: !value } }) : null}
- onKeyPress={onChange ? e => {
- if (e.key == 'Enter' || e.key == ' ') {
- e.preventDefault();
- e.stopPropagation();
- onChange({ target: { name, value: !value } });
- }
- } : null}
- tabIndex="0"
- >
- <Icon name="check" />
- </span>;
-};
-
-LargeCheck.propTypes = {
- className: PropTypes.string,
- id: PropTypes.string,
- name: PropTypes.string,
- onBlur: PropTypes.func,
- onChange: PropTypes.func,
- value: PropTypes.bool,
-};
-
-export default LargeCheck;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Icon from './Icon';
+
+const LargeCheck = ({
+ className = '',
+ id = '',
+ name = '',
+ onBlur = null,
+ onChange = null,
+ value = false,
+}) => {
+ let clsn = className ? `${className} custom-check` : 'custom-check';
+ if (value) {
+ clsn += ' checked';
+ }
+ return <span
+ className={clsn}
+ id={id}
+ onBlur={onBlur ? () => onBlur({ target: { name, value } }) : null}
+ onClick={onChange ? () => onChange({ target: { name, value: !value } }) : null}
+ onKeyPress={onChange ? e => {
+ if (e.key == 'Enter' || e.key == ' ') {
+ e.preventDefault();
+ e.stopPropagation();
+ onChange({ target: { name, value: !value } });
+ }
+ } : null}
+ tabIndex="0"
+ >
+ <Icon name="check" />
+ </span>;
+};
+
+LargeCheck.propTypes = {
+ className: PropTypes.string,
+ id: PropTypes.string,
+ name: PropTypes.string,
+ onBlur: PropTypes.func,
+ onChange: PropTypes.func,
+ value: PropTypes.bool,
+};
+
+export default LargeCheck;
+++ /dev/null
-import React from 'react';
-import { ProgressBar } from 'react-bootstrap';
-
-const Loading = () => <div className="loading">
- <ProgressBar animated now={100} variant="info" />
-</div>;
-
-export default Loading;
--- /dev/null
+import React from 'react';
+import { ProgressBar } from 'react-bootstrap';
+
+const Loading = () => <div className="loading">
+ <ProgressBar animated now={100} variant="info" />
+</div>;
+
+export default Loading;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Modal } from 'react-bootstrap';
-
-import Loading from './Loading';
-
-const PngPlayer = React.lazy(() => import('./PngPlayer'));
-
-const PngDialog = ({ onHide, show, src, title }) => <Modal onHide={onHide} show={show} size="lg">
- {title ?
- <Modal.Header closeButton>
- <Modal.Title>
- {title}
- </Modal.Title>
- </Modal.Header>
- : null}
- <Modal.Body>
- <React.Suspense fallback={<Loading />}>
- <PngPlayer src={src} />
- </React.Suspense>
- </Modal.Body>
-</Modal>;
-
-PngDialog.propTypes = {
- onHide: PropTypes.func,
- show: PropTypes.bool,
- src: PropTypes.string,
- title: PropTypes.string,
-};
-
-export default PngDialog;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+
+import Loading from './Loading';
+
+const PngPlayer = React.lazy(() => import('./PngPlayer'));
+
+const PngDialog = ({ onHide, show, src, title }) => <Modal onHide={onHide} show={show} size="lg">
+ {title ?
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {title}
+ </Modal.Title>
+ </Modal.Header>
+ : null}
+ <Modal.Body>
+ <React.Suspense fallback={<Loading />}>
+ <PngPlayer src={src} />
+ </React.Suspense>
+ </Modal.Body>
+</Modal>;
+
+PngDialog.propTypes = {
+ onHide: PropTypes.func,
+ show: PropTypes.bool,
+ src: PropTypes.string,
+ title: PropTypes.string,
+};
+
+export default PngDialog;
+++ /dev/null
-import parseApng from 'apng-js';
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Icon from './Icon';
-
-const createPlayer = async (apng, canvas) => {
- const context = canvas.getContext('2d', { willReadFrequently: true });
- const player = await apng.getPlayer(context);
- player.stop();
- return player;
-};
-
-const PngPlayer = ({ src }) => {
- const canvas = React.useRef();
- const { t } = useTranslation();
-
- const [apng, setApng] = React.useState(null);
- const [error, setError] = React.useState(null);
- const [frameInfo, setFrameInfo] = React.useState('');
- const [loading, setLoading] = React.useState(true);
- const [player, setPlayer] = React.useState(null);
-
- React.useEffect(() => {
- if (!src) return;
- setError(null);
- setLoading(true);
- const ctrl = new AbortController();
- const fetchPng = async () => {
- try {
- const response = await axios.get(src, {
- responseType: 'arraybuffer',
- signal: ctrl.signal,
- });
- const png = parseApng(response.data);
- await png.createImages();
- setApng(png);
- setLoading(false);
- } catch (e) {
- if (!axios.isCancel(e)) {
- setError(e);
- console.log(e);
- }
- }
- };
- fetchPng();
- return () => {
- ctrl.abort();
- };
- }, [src]);
-
- React.useEffect(() => {
- if (loading || !canvas.current) return;
- setFrameInfo(`1/${apng.frames.length}`);
- (async () => {
- const p = await createPlayer(apng, canvas.current);
- setPlayer(p);
- const updateFrame = (number) => {
- setFrameInfo(`${number + 1}/${apng.frames.length}`);
- };
- p.on('frame', updateFrame);
- })();
- }, [apng, canvas.current, loading]);
-
- const stop = React.useCallback(() => {
- if (player) player.stop();
- }, [player]);
-
- const toggle = React.useCallback(() => {
- if (!player) return;
- if (player.paused) {
- player.play();
- } else {
- player.pause();
- }
- }, [player]);
-
- const nextFrame = React.useCallback(() => {
- if (player) player.renderNextFrame();
- }, [player]);
-
- if (error) {
- return <div>Error</div>;
- }
- if (loading) {
- return <div>Loading</div>;
- }
-
- return <div className="png-player">
- <div className="screen">
- <canvas ref={canvas} width={apng.width} height={apng.height} />
- </div>
- <span className="ms-auto">{frameInfo}</span>
- <div className="button-bar controls">
- <Button
- onClick={stop}
- title={t('button.stop')}
- variant="outline-secondary"
- >
- <Icon.STOP title="" />
- </Button>
- <Button
- onClick={toggle}
- title={t('button.playPause')}
- variant="outline-secondary"
- >
- <Icon.PLAY title="" />
- {' '}
- <Icon.PAUSE title="" />
- </Button>
- <Button
- onClick={nextFrame}
- title={t('button.nextFrame')}
- variant="outline-secondary"
- >
- <Icon.STEP_FORWARD title="" />
- </Button>
- </div>
- </div>;
-};
-
-PngPlayer.propTypes = {
- src: PropTypes.string,
-};
-
-export default PngPlayer;
--- /dev/null
+import parseApng from 'apng-js';
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Icon from './Icon';
+
+const createPlayer = async (apng, canvas) => {
+ const context = canvas.getContext('2d', { willReadFrequently: true });
+ const player = await apng.getPlayer(context);
+ player.stop();
+ return player;
+};
+
+const PngPlayer = ({ src }) => {
+ const canvas = React.useRef();
+ const { t } = useTranslation();
+
+ const [apng, setApng] = React.useState(null);
+ const [error, setError] = React.useState(null);
+ const [frameInfo, setFrameInfo] = React.useState('');
+ const [loading, setLoading] = React.useState(true);
+ const [player, setPlayer] = React.useState(null);
+
+ React.useEffect(() => {
+ if (!src) return;
+ setError(null);
+ setLoading(true);
+ const ctrl = new AbortController();
+ const fetchPng = async () => {
+ try {
+ const response = await axios.get(src, {
+ responseType: 'arraybuffer',
+ signal: ctrl.signal,
+ });
+ const png = parseApng(response.data);
+ await png.createImages();
+ setApng(png);
+ setLoading(false);
+ } catch (e) {
+ if (!axios.isCancel(e)) {
+ setError(e);
+ console.log(e);
+ }
+ }
+ };
+ fetchPng();
+ return () => {
+ ctrl.abort();
+ };
+ }, [src]);
+
+ React.useEffect(() => {
+ if (loading || !canvas.current) return;
+ setFrameInfo(`1/${apng.frames.length}`);
+ (async () => {
+ const p = await createPlayer(apng, canvas.current);
+ setPlayer(p);
+ const updateFrame = (number) => {
+ setFrameInfo(`${number + 1}/${apng.frames.length}`);
+ };
+ p.on('frame', updateFrame);
+ })();
+ }, [apng, canvas.current, loading]);
+
+ const stop = React.useCallback(() => {
+ if (player) player.stop();
+ }, [player]);
+
+ const toggle = React.useCallback(() => {
+ if (!player) return;
+ if (player.paused) {
+ player.play();
+ } else {
+ player.pause();
+ }
+ }, [player]);
+
+ const nextFrame = React.useCallback(() => {
+ if (player) player.renderNextFrame();
+ }, [player]);
+
+ if (error) {
+ return <div>Error</div>;
+ }
+ if (loading) {
+ return <div>Loading</div>;
+ }
+
+ return <div className="png-player">
+ <div className="screen">
+ <canvas ref={canvas} width={apng.width} height={apng.height} />
+ </div>
+ <span className="ms-auto">{frameInfo}</span>
+ <div className="button-bar controls">
+ <Button
+ onClick={stop}
+ title={t('button.stop')}
+ variant="outline-secondary"
+ >
+ <Icon.STOP title="" />
+ </Button>
+ <Button
+ onClick={toggle}
+ title={t('button.playPause')}
+ variant="outline-secondary"
+ >
+ <Icon.PLAY title="" />
+ {' '}
+ <Icon.PAUSE title="" />
+ </Button>
+ <Button
+ onClick={nextFrame}
+ title={t('button.nextFrame')}
+ variant="outline-secondary"
+ >
+ <Icon.STEP_FORWARD title="" />
+ </Button>
+ </div>
+ </div>;
+};
+
+PngPlayer.propTypes = {
+ src: PropTypes.string,
+};
+
+export default PngPlayer;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { useNavigate } from 'react-router-dom';
-
-import PngDialog from './PngDialog';
-
-const isApng = el => el.nodeName === 'IMG' && el.getAttribute('type') === 'image/apng';
-
-const isLink = el => el.nodeName === 'A';
-
-const canClick = el => {
- if (isLink(el)) return true;
- if (isApng(el)) return true;
- return false;
-};
-
-const RawHTML = ({ html }) => {
- const navigate = useNavigate();
- const [apng, setApng] = React.useState(null);
- const [show, setShow] = React.useState(false);
- const [title, setTitle] = React.useState(null);
-
- const onClick = e => {
- if (e.defaultPrevented) return;
- if (e.metaKey || e.ctrlKey || e.shiftKey) return;
- if (e.button !== 0) return;
-
- let el = e.target;
- while (el && !canClick(el)) {
- el = el.parentNode;
- }
- if (!el) return;
-
- if (isLink(el)) {
- if (el.target && el.target !== '_self') return;
- if (el.attributes.download) return;
- if (el.rel && /(?:^|\s+)external(?:\s+|$)/.test(el.rel)) return;
-
- const href = el.getAttribute('href');
-
- if (href.startsWith('#')) return;
- if (href.startsWith('http')) return;
- if (href.startsWith('mailto')) return;
- if (href.startsWith('tel')) return;
-
- el.blur();
- e.preventDefault();
-
- setTimeout(() => {
- // scroll to top on location change
- scrollTo({ top: 0, behavior: 'smooth' });
- }, 50);
-
- navigate(href);
- return;
- }
-
- if (isApng(el)) {
- setApng(el.getAttribute('src'));
- setShow(true);
- setTitle(el.getAttribute('alt'));
- }
- };
-
- return <>
- <div className="raw-html" onClick={onClick} dangerouslySetInnerHTML={{ __html: html }} />
- <PngDialog onHide={() => setShow(false)} show={show} src={apng} title={title} />
- </>;
-};
-
-RawHTML.propTypes = {
- html: PropTypes.string,
-};
-
-export default RawHTML;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+
+import PngDialog from './PngDialog';
+
+const isApng = el => el.nodeName === 'IMG' && el.getAttribute('type') === 'image/apng';
+
+const isLink = el => el.nodeName === 'A';
+
+const canClick = el => {
+ if (isLink(el)) return true;
+ if (isApng(el)) return true;
+ return false;
+};
+
+const RawHTML = ({ html }) => {
+ const navigate = useNavigate();
+ const [apng, setApng] = React.useState(null);
+ const [show, setShow] = React.useState(false);
+ const [title, setTitle] = React.useState(null);
+
+ const onClick = e => {
+ if (e.defaultPrevented) return;
+ if (e.metaKey || e.ctrlKey || e.shiftKey) return;
+ if (e.button !== 0) return;
+
+ let el = e.target;
+ while (el && !canClick(el)) {
+ el = el.parentNode;
+ }
+ if (!el) return;
+
+ if (isLink(el)) {
+ if (el.target && el.target !== '_self') return;
+ if (el.attributes.download) return;
+ if (el.rel && /(?:^|\s+)external(?:\s+|$)/.test(el.rel)) return;
+
+ const href = el.getAttribute('href');
+
+ if (href.startsWith('#')) return;
+ if (href.startsWith('http')) return;
+ if (href.startsWith('mailto')) return;
+ if (href.startsWith('tel')) return;
+
+ el.blur();
+ e.preventDefault();
+
+ setTimeout(() => {
+ // scroll to top on location change
+ scrollTo({ top: 0, behavior: 'smooth' });
+ }, 50);
+
+ navigate(href);
+ return;
+ }
+
+ if (isApng(el)) {
+ setApng(el.getAttribute('src'));
+ setShow(true);
+ setTitle(el.getAttribute('alt'));
+ }
+ };
+
+ return <>
+ <div className="raw-html" onClick={onClick} dangerouslySetInnerHTML={{ __html: html }} />
+ <PngDialog onHide={() => setShow(false)} show={show} src={apng} title={title} />
+ </>;
+};
+
+RawHTML.propTypes = {
+ html: PropTypes.string,
+};
+
+export default RawHTML;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-const Slider = ({ children = null, duration = 2500, vertical = false }) => {
- const [index, setIndex] = React.useState(0);
-
- React.useEffect(() => {
- const interval = setInterval(() => {
- setIndex(i => (i + 1) % React.Children.count(children));
- }, duration);
- return () => {
- clearInterval(interval);
- };
- }, [React.Children.count(children), duration]);
-
- return <div className={`slider-container ${vertical ? 'vertical' : 'horizontal'}`}>
- <div className="slider-slides" style={{
- transform: vertical ? `translateY(${-index * 100}%)` : `translateX(${-index * 100}%)`
- }}>
- {children}
- </div>
- </div>;
-};
-
-Slider.propTypes = {
- children: PropTypes.node,
- duration: PropTypes.number,
- vertical: PropTypes.bool,
-};
-
-const Slide = ({ children }) => {
- return <div className="slider-slide">
- {children}
- </div>;
-};
-
-Slide.propTypes = {
- children: PropTypes.node,
-};
-
-Slider.Slide = Slide;
-
-export default Slider;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+const Slider = ({ children = null, duration = 2500, vertical = false }) => {
+ const [index, setIndex] = React.useState(0);
+
+ React.useEffect(() => {
+ const interval = setInterval(() => {
+ setIndex(i => (i + 1) % React.Children.count(children));
+ }, duration);
+ return () => {
+ clearInterval(interval);
+ };
+ }, [React.Children.count(children), duration]);
+
+ return <div className={`slider-container ${vertical ? 'vertical' : 'horizontal'}`}>
+ <div className="slider-slides" style={{
+ transform: vertical ? `translateY(${-index * 100}%)` : `translateX(${-index * 100}%)`
+ }}>
+ {children}
+ </div>
+ </div>;
+};
+
+Slider.propTypes = {
+ children: PropTypes.node,
+ duration: PropTypes.number,
+ vertical: PropTypes.bool,
+};
+
+const Slide = ({ children }) => {
+ return <div className="slider-slide">
+ {children}
+ </div>;
+};
+
+Slide.propTypes = {
+ children: PropTypes.node,
+};
+
+Slider.Slide = Slide;
+
+export default Slider;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-
-const Spoiler = ({ children }) => {
- const [show, setShow] = useState(false);
-
- return <span
- className={`spoiler ${show ? 'shown' : 'hidden'}`}
- onClick={() => setShow(true)}
- >
- <span className="content">{children}</span>
- </span>;
-};
-
-Spoiler.propTypes = {
- children: PropTypes.oneOfType([
- PropTypes.node,
- PropTypes.object,
- PropTypes.string,
- ]),
-};
-
-export default Spoiler;
--- /dev/null
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+
+const Spoiler = ({ children }) => {
+ const [show, setShow] = useState(false);
+
+ return <span
+ className={`spoiler ${show ? 'shown' : 'hidden'}`}
+ onClick={() => setShow(true)}
+ >
+ <span className="content">{children}</span>
+ </span>;
+};
+
+Spoiler.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.node,
+ PropTypes.object,
+ PropTypes.string,
+ ]),
+};
+
+export default Spoiler;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Icon from './Icon';
-
-const ToggleSwitch = ({
- isInvalid = false,
- isValid = false,
- name = '',
- offLabel = '',
- onBlur = null,
- onChange = null,
- onLabel = null,
- readonly = false,
- title = null,
- value = false,
-}) => {
- const toggle = () => {
- if (readonly) return;
- if (onChange) onChange({ target: { name, value: !value } });
- };
-
- const handleClick = event => {
- event.stopPropagation();
- toggle();
- };
-
- const handleKey = event => {
- if ([13, 32].includes(event.which)) {
- toggle();
- event.preventDefault();
- event.stopPropagation();
- }
- };
-
- const classNames = ['form-control', 'custom-toggle'];
- if (value) classNames.push('is-toggled');
- if (isInvalid) classNames.push('is-invalid');
- if (isValid) classNames.push('is-valid');
- if (readonly) classNames.push('readonly');
-
- return <div
- className={classNames.join(' ')}
- role="button"
- aria-pressed={value}
- tabIndex="0"
- title={title}
- onBlur={onBlur ? () => onBlur({ target: { name, value } }) : null}
- onClick={handleClick}
- onKeyDown={handleKey}
- >
- <div className="handle">
- <span className="handle-label">
- {value
- ? onLabel || <Icon name="check" />
- : offLabel || <Icon name="times" />
- }
- </span>
- </div>
- </div>;
-};
-
-ToggleSwitch.propTypes = {
- isInvalid: PropTypes.bool,
- isValid: PropTypes.bool,
- name: PropTypes.string,
- offLabel: PropTypes.string,
- onBlur: PropTypes.func,
- onChange: PropTypes.func,
- onLabel: PropTypes.string,
- readonly: PropTypes.bool,
- title: PropTypes.string,
- value: PropTypes.bool,
-};
-
-export default ToggleSwitch;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Icon from './Icon';
+
+const ToggleSwitch = ({
+ isInvalid = false,
+ isValid = false,
+ name = '',
+ offLabel = '',
+ onBlur = null,
+ onChange = null,
+ onLabel = null,
+ readonly = false,
+ title = null,
+ value = false,
+}) => {
+ const toggle = () => {
+ if (readonly) return;
+ if (onChange) onChange({ target: { name, value: !value } });
+ };
+
+ const handleClick = event => {
+ event.stopPropagation();
+ toggle();
+ };
+
+ const handleKey = event => {
+ if ([13, 32].includes(event.which)) {
+ toggle();
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ };
+
+ const classNames = ['form-control', 'custom-toggle'];
+ if (value) classNames.push('is-toggled');
+ if (isInvalid) classNames.push('is-invalid');
+ if (isValid) classNames.push('is-valid');
+ if (readonly) classNames.push('readonly');
+
+ return <div
+ className={classNames.join(' ')}
+ role="button"
+ aria-pressed={value}
+ tabIndex="0"
+ title={title}
+ onBlur={onBlur ? () => onBlur({ target: { name, value } }) : null}
+ onClick={handleClick}
+ onKeyDown={handleKey}
+ >
+ <div className="handle">
+ <span className="handle-label">
+ {value
+ ? onLabel || <Icon name="check" />
+ : offLabel || <Icon name="times" />
+ }
+ </span>
+ </div>
+ </div>;
+};
+
+ToggleSwitch.propTypes = {
+ isInvalid: PropTypes.bool,
+ isValid: PropTypes.bool,
+ name: PropTypes.string,
+ offLabel: PropTypes.string,
+ onBlur: PropTypes.func,
+ onChange: PropTypes.func,
+ onLabel: PropTypes.string,
+ readonly: PropTypes.bool,
+ title: PropTypes.string,
+ value: PropTypes.bool,
+};
+
+export default ToggleSwitch;
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import { Button, Form, ListGroup } from 'react-bootstrap';
-
-import Icon from '../common/Icon';
-import UserBox from '../users/Box';
-import debounce from '../../helpers/debounce';
-
-const UserSelect = ({ name, onChange, value }) => {
- const [resolved, setResolved] = useState(null);
- const [results, setResults] = useState([]);
- const [search, setSearch] = useState('');
- const [showResults, setShowResults] = useState(false);
-
- const ref = useRef(null);
-
- useEffect(() => {
- const handleEventOutside = e => {
- if (ref.current && !ref.current.contains(e.target)) {
- setShowResults(false);
- }
- };
- document.addEventListener('mousedown', handleEventOutside, true);
- document.addEventListener('focus', handleEventOutside, true);
- return () => {
- document.removeEventListener('mousedown', handleEventOutside, true);
- document.removeEventListener('focus', handleEventOutside, true);
- };
- }, []);
-
- let ctrl = null;
- const fetch = useCallback(debounce(async phrase => {
- if (ctrl) {
- ctrl.abort();
- }
- ctrl = new AbortController();
- if (!phrase || phrase.length < 3) {
- setResults([]);
- return;
- }
- try {
- const response = await axios.get(`/api/users`, {
- params: {
- phrase,
- },
- signal: ctrl.signal,
- });
- ctrl = null;
- setResults(response.data);
- } catch (e) {
- ctrl = null;
- console.error(e);
- }
- }, 300), []);
-
- useEffect(() => {
- fetch(search);
- }, [search]);
-
- useEffect(() => {
- if (value) {
- axios
- .get(`/api/users/${value}`)
- .then(response => {
- setResolved(response.data);
- });
- } else {
- setResolved(null);
- }
- }, [value]);
-
- if (value) {
- return <div className="d-flex justify-content-between">
- {resolved ? <UserBox discriminator noLink user={resolved} /> : <span>value</span>}
- <Button
- onClick={() => onChange({ target: { name, value: null }})}
- size="sm"
- variant="outline-danger"
- >
- <Icon.REMOVE />
- </Button>
- </div>;
- }
- return <div className={`user-select ${showResults ? 'expanded' : 'collapsed'}`} ref={ref}>
- <Form.Control
- className="search-input"
- name={Math.random().toString(20).substr(2, 10)}
- onChange={e => setSearch(e.target.value)}
- onFocus={() => setShowResults(true)}
- type="search"
- value={search}
- />
- <div className="search-results-holder">
- <ListGroup className="search-results">
- {results.map(result =>
- <ListGroup.Item
- action
- key={result.id}
- onClick={() => onChange({
- target: { name, value: result.id },
- })}
- >
- <UserBox discriminator noLink user={result} />
- </ListGroup.Item>
- )}
- </ListGroup>
- </div>
- </div>;
-};
-
-UserSelect.propTypes = {
- name: PropTypes.string,
- onChange: PropTypes.func,
- value: PropTypes.string,
-};
-
-export default UserSelect;
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { Button, Form, ListGroup } from 'react-bootstrap';
+
+import Icon from '../common/Icon';
+import UserBox from '../users/Box';
+import debounce from '../../helpers/debounce';
+
+const UserSelect = ({ name, onChange, value }) => {
+ const [resolved, setResolved] = useState(null);
+ const [results, setResults] = useState([]);
+ const [search, setSearch] = useState('');
+ const [showResults, setShowResults] = useState(false);
+
+ const ref = useRef(null);
+
+ useEffect(() => {
+ const handleEventOutside = e => {
+ if (ref.current && !ref.current.contains(e.target)) {
+ setShowResults(false);
+ }
+ };
+ document.addEventListener('mousedown', handleEventOutside, true);
+ document.addEventListener('focus', handleEventOutside, true);
+ return () => {
+ document.removeEventListener('mousedown', handleEventOutside, true);
+ document.removeEventListener('focus', handleEventOutside, true);
+ };
+ }, []);
+
+ let ctrl = null;
+ const fetch = useCallback(debounce(async phrase => {
+ if (ctrl) {
+ ctrl.abort();
+ }
+ ctrl = new AbortController();
+ if (!phrase || phrase.length < 3) {
+ setResults([]);
+ return;
+ }
+ try {
+ const response = await axios.get(`/api/users`, {
+ params: {
+ phrase,
+ },
+ signal: ctrl.signal,
+ });
+ ctrl = null;
+ setResults(response.data);
+ } catch (e) {
+ ctrl = null;
+ console.error(e);
+ }
+ }, 300), []);
+
+ useEffect(() => {
+ fetch(search);
+ }, [search]);
+
+ useEffect(() => {
+ if (value) {
+ axios
+ .get(`/api/users/${value}`)
+ .then(response => {
+ setResolved(response.data);
+ });
+ } else {
+ setResolved(null);
+ }
+ }, [value]);
+
+ if (value) {
+ return <div className="d-flex justify-content-between">
+ {resolved ? <UserBox discriminator noLink user={resolved} /> : <span>value</span>}
+ <Button
+ onClick={() => onChange({ target: { name, value: null }})}
+ size="sm"
+ variant="outline-danger"
+ >
+ <Icon.REMOVE />
+ </Button>
+ </div>;
+ }
+ return <div className={`user-select ${showResults ? 'expanded' : 'collapsed'}`} ref={ref}>
+ <Form.Control
+ className="search-input"
+ name={Math.random().toString(20).substr(2, 10)}
+ onChange={e => setSearch(e.target.value)}
+ onFocus={() => setShowResults(true)}
+ type="search"
+ value={search}
+ />
+ <div className="search-results-holder">
+ <ListGroup className="search-results">
+ {results.map(result =>
+ <ListGroup.Item
+ action
+ key={result.id}
+ onClick={() => onChange({
+ target: { name, value: result.id },
+ })}
+ >
+ <UserBox discriminator noLink user={result} />
+ </ListGroup.Item>
+ )}
+ </ListGroup>
+ </div>
+ </div>;
+};
+
+UserSelect.propTypes = {
+ name: PropTypes.string,
+ onChange: PropTypes.func,
+ value: PropTypes.string,
+};
+
+export default UserSelect;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-
-import Icon from './Icon';
-
-const ITEM_MAP = [
- 'aga',
- 'armos',
- 'arrghus',
- 'big-key',
- 'blind',
- 'blue-boomerang',
- 'blue-mail',
- 'blue-pendant',
- 'blue-potion',
- 'bombos',
- 'bomb',
- 'book',
- 'boots',
- 'bottle-bee',
- 'bottle-good-bee',
- 'bottle',
- 'bowless-silvers',
- 'bow',
- 'bugnet',
- 'bunny-head',
- 'byrna',
- 'cape',
- 'chest',
- 'compass',
- 'crystal',
- 'crystal-switch-blue',
- 'crystal-switch',
- 'crystal-switch-red',
- 'duck',
- 'ether',
- 'fairy',
- 'fighter-shield',
- 'fighter-sword',
- 'fire-rod',
- 'fire-shield',
- 'flippers',
- 'flute',
- 'ganon',
- 'glove',
- 'gold-sword',
- 'green-mail',
- 'green-pendant',
- 'green-potion',
- 'gt',
- 'half-magic',
- 'hammer',
- 'heart-0',
- 'heart-1',
- 'heart-2',
- 'heart-3',
- 'heart-container',
- 'heart-piece',
- 'helma',
- 'hookshot',
- 'ice-rod',
- 'kholdstare',
- 'lamp',
- 'lanmolas',
- 'link-head',
- 'map',
- 'master-sword',
- 'mirror',
- 'mirror-shield',
- 'mitts',
- 'moldorm',
- 'moonpearl',
- 'mothula',
- 'mushroom',
- 'open-chest',
- 'powder',
- 'quake',
- 'quarter-magic',
- 'red-bomb',
- 'red-boomerang',
- 'red-crystal',
- 'red-mail',
- 'red-pendant',
- 'red-potion',
- 'shovel',
- 'silvers',
- 'small-key',
- 'somaria',
- 'sword-1',
- 'sword-2',
- 'sword-3',
- 'sword-4',
- 'tempered-sword',
- 'triforce-piece',
- 'triforce',
- 'trinexx',
- 'vitreous',
-];
-
-const ITEM_MAP_WIDTH = 8;
-
-const ITEM_MAP_HEIGHT = Math.ceil(ITEM_MAP.length / ITEM_MAP_WIDTH);
-
-const ITEM_MAP_URL = '/items-v2.png';
-
-const isOnItemMap = name => ITEM_MAP.includes(name);
-
-const getItemMapX = name => ITEM_MAP.indexOf(name) % ITEM_MAP_WIDTH;
-
-const getItemMapY = name => Math.floor(ITEM_MAP.indexOf(name) / ITEM_MAP_WIDTH);
-
-const getItemMapStyle = name => {
- const x = getItemMapX(name);
- const y = getItemMapY(name);
- return {
- backgroundImage: `url(${ITEM_MAP_URL})`,
- backgroundPosition: `-${x * 100}% -${y * 100}%`,
- backgroundSize: `${ITEM_MAP_WIDTH * 100}% ${ITEM_MAP_HEIGHT * 100}%`,
- };
-};
-
-const getIconURL = name => {
- switch (name) {
- case 'dungeon-ct':
- case 'dungeon-dp':
- case 'dungeon-ep':
- case 'dungeon-gt':
- case 'dungeon-hc':
- case 'dungeon-ip':
- case 'dungeon-mm':
- case 'dungeon-pd':
- case 'dungeon-sp':
- case 'dungeon-sw':
- case 'dungeon-th':
- case 'dungeon-tr':
- case 'dungeon-tt':
- return `/dungeon/${name.substr(8)}.png`;
- default:
- return '';
- }
-};
-
-const isHalfWidth = name => [
- 'blue-boomerang',
- 'fire-rod',
- 'ice-rod',
- 'hookshot',
- 'red-boomerang',
-].includes(name);
-
-const ZeldaIcon = ({ name, svg, title }) => {
- const { t } = useTranslation();
-
- const invert = name.startsWith('not-');
- const strippedName = invert ? name.substr(4) : name;
- const src = getIconURL(strippedName);
- const alt = t(`icon.zelda.${name}`);
- const realTitle = title !== '' ? title || alt : null;
-
- if (svg) {
- const clipX = getItemMapX(strippedName);
- const clipY = getItemMapY(strippedName);
- const cropX = isHalfWidth(strippedName) ? 0.25 : 0.02;
- const cropY = 0.02;
- const cropW = 1 - (2 * cropX);
- const cropH = 1 - (2 * cropY);
- return <image
- href={isOnItemMap(strippedName) ? ITEM_MAP_URL : src}
- width={ITEM_MAP_WIDTH}
- height={ITEM_MAP_HEIGHT}
- x={`-${clipX + 0.5}`}
- y={`-${clipY + 0.5}`}
- clipPath={`xywh(${clipX + cropX} ${clipY + cropY} ${cropW} ${cropH})`}
- >
- {realTitle ?
- <title>{realTitle}</title>
- : null}
- </image>;
- }
-
- return <span className="zelda-icon">
- {isOnItemMap(strippedName) ?
- <span
- className="item-map-icon"
- style={getItemMapStyle(strippedName)}
- title={realTitle}
- />
- : null}
- {src ?
- <img
- alt={alt}
- src={src}
- title={realTitle}
- />
- : null}
- {invert ?
- <span className="strike">
- <Icon.SLASH title="" />
- </span>
- : null}
- </span>;
-};
-
-ZeldaIcon.propTypes = {
- name: PropTypes.string.isRequired,
- svg: PropTypes.bool,
- title: PropTypes.string,
-};
-
-export default ZeldaIcon;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import Icon from './Icon';
+
+const ITEM_MAP = [
+ 'aga',
+ 'armos',
+ 'arrghus',
+ 'big-key',
+ 'blind',
+ 'blue-boomerang',
+ 'blue-mail',
+ 'blue-pendant',
+ 'blue-potion',
+ 'bombos',
+ 'bomb',
+ 'book',
+ 'boots',
+ 'bottle-bee',
+ 'bottle-good-bee',
+ 'bottle',
+ 'bowless-silvers',
+ 'bow',
+ 'bugnet',
+ 'bunny-head',
+ 'byrna',
+ 'cape',
+ 'chest',
+ 'compass',
+ 'crystal',
+ 'crystal-switch-blue',
+ 'crystal-switch',
+ 'crystal-switch-red',
+ 'duck',
+ 'ether',
+ 'fairy',
+ 'fighter-shield',
+ 'fighter-sword',
+ 'fire-rod',
+ 'fire-shield',
+ 'flippers',
+ 'flute',
+ 'ganon',
+ 'glove',
+ 'gold-sword',
+ 'green-mail',
+ 'green-pendant',
+ 'green-potion',
+ 'gt',
+ 'half-magic',
+ 'hammer',
+ 'heart-0',
+ 'heart-1',
+ 'heart-2',
+ 'heart-3',
+ 'heart-container',
+ 'heart-piece',
+ 'helma',
+ 'hookshot',
+ 'ice-rod',
+ 'kholdstare',
+ 'lamp',
+ 'lanmolas',
+ 'link-head',
+ 'map',
+ 'master-sword',
+ 'mirror',
+ 'mirror-shield',
+ 'mitts',
+ 'moldorm',
+ 'moonpearl',
+ 'mothula',
+ 'mushroom',
+ 'open-chest',
+ 'powder',
+ 'quake',
+ 'quarter-magic',
+ 'red-bomb',
+ 'red-boomerang',
+ 'red-crystal',
+ 'red-mail',
+ 'red-pendant',
+ 'red-potion',
+ 'shovel',
+ 'silvers',
+ 'small-key',
+ 'somaria',
+ 'sword-1',
+ 'sword-2',
+ 'sword-3',
+ 'sword-4',
+ 'tempered-sword',
+ 'triforce-piece',
+ 'triforce',
+ 'trinexx',
+ 'vitreous',
+];
+
+const ITEM_MAP_WIDTH = 8;
+
+const ITEM_MAP_HEIGHT = Math.ceil(ITEM_MAP.length / ITEM_MAP_WIDTH);
+
+const ITEM_MAP_URL = '/items-v2.png';
+
+const isOnItemMap = name => ITEM_MAP.includes(name);
+
+const getItemMapX = name => ITEM_MAP.indexOf(name) % ITEM_MAP_WIDTH;
+
+const getItemMapY = name => Math.floor(ITEM_MAP.indexOf(name) / ITEM_MAP_WIDTH);
+
+const getItemMapStyle = name => {
+ const x = getItemMapX(name);
+ const y = getItemMapY(name);
+ return {
+ backgroundImage: `url(${ITEM_MAP_URL})`,
+ backgroundPosition: `-${x * 100}% -${y * 100}%`,
+ backgroundSize: `${ITEM_MAP_WIDTH * 100}% ${ITEM_MAP_HEIGHT * 100}%`,
+ };
+};
+
+const getIconURL = name => {
+ switch (name) {
+ case 'dungeon-ct':
+ case 'dungeon-dp':
+ case 'dungeon-ep':
+ case 'dungeon-gt':
+ case 'dungeon-hc':
+ case 'dungeon-ip':
+ case 'dungeon-mm':
+ case 'dungeon-pd':
+ case 'dungeon-sp':
+ case 'dungeon-sw':
+ case 'dungeon-th':
+ case 'dungeon-tr':
+ case 'dungeon-tt':
+ return `/dungeon/${name.substr(8)}.png`;
+ default:
+ return '';
+ }
+};
+
+const isHalfWidth = name => [
+ 'blue-boomerang',
+ 'fire-rod',
+ 'ice-rod',
+ 'hookshot',
+ 'red-boomerang',
+].includes(name);
+
+const ZeldaIcon = ({ name, svg, title }) => {
+ const { t } = useTranslation();
+
+ const invert = name.startsWith('not-');
+ const strippedName = invert ? name.substr(4) : name;
+ const src = getIconURL(strippedName);
+ const alt = t(`icon.zelda.${name}`);
+ const realTitle = title !== '' ? title || alt : null;
+
+ if (svg) {
+ const clipX = getItemMapX(strippedName);
+ const clipY = getItemMapY(strippedName);
+ const cropX = isHalfWidth(strippedName) ? 0.25 : 0.02;
+ const cropY = 0.02;
+ const cropW = 1 - (2 * cropX);
+ const cropH = 1 - (2 * cropY);
+ return <image
+ href={isOnItemMap(strippedName) ? ITEM_MAP_URL : src}
+ width={ITEM_MAP_WIDTH}
+ height={ITEM_MAP_HEIGHT}
+ x={`-${clipX + 0.5}`}
+ y={`-${clipY + 0.5}`}
+ clipPath={`xywh(${clipX + cropX} ${clipY + cropY} ${cropW} ${cropH})`}
+ >
+ {realTitle ?
+ <title>{realTitle}</title>
+ : null}
+ </image>;
+ }
+
+ return <span className="zelda-icon">
+ {isOnItemMap(strippedName) ?
+ <span
+ className="item-map-icon"
+ style={getItemMapStyle(strippedName)}
+ title={realTitle}
+ />
+ : null}
+ {src ?
+ <img
+ alt={alt}
+ src={src}
+ title={realTitle}
+ />
+ : null}
+ {invert ?
+ <span className="strike">
+ <Icon.SLASH title="" />
+ </span>
+ : null}
+ </span>;
+};
+
+ZeldaIcon.propTypes = {
+ name: PropTypes.string.isRequired,
+ svg: PropTypes.bool,
+ title: PropTypes.string,
+};
+
+export default ZeldaIcon;
+++ /dev/null
-import React from 'react';
-import { Col, Form, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import DiscordChannelSelect from '../common/DiscordChannelSelect';
-import DiscordSelect from '../common/DiscordSelect';
-
-const Controls = () => {
- const [channel, setChannel] = React.useState('');
- const [guild, setGuild] = React.useState(null);
-
- const { t } = useTranslation();
-
- return <>
- <Row>
- <Form.Group as={Col} md={6}>
- <Form.Label>{t('discordBot.guild')}</Form.Label>
- <Form.Control
- as={DiscordSelect}
- onChange={({ guild }) => { setGuild(guild); setChannel(''); }}
- value={guild ? guild.guild_id : ''}
- />
- </Form.Group>
- <Form.Group as={Col} md={6}>
- <Form.Label>{t('discordBot.channel')}</Form.Label>
- {guild ?
- <Form.Control
- as={DiscordChannelSelect}
- guild={guild.guild_id}
- onChange={({ target: { value } }) => setChannel(value)}
- types={[]}
- value={channel}
- />
- :
- <Form.Control plaintext readOnly defaultValue={t('discordBot.selectGuild')} />
- }
- </Form.Group>
- </Row>
- </>;
-};
-
-export default Controls;
--- /dev/null
+import React from 'react';
+import { Col, Form, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import DiscordChannelSelect from '../common/DiscordChannelSelect';
+import DiscordSelect from '../common/DiscordSelect';
+
+const Controls = () => {
+ const [channel, setChannel] = React.useState('');
+ const [guild, setGuild] = React.useState(null);
+
+ const { t } = useTranslation();
+
+ return <>
+ <Row>
+ <Form.Group as={Col} md={6}>
+ <Form.Label>{t('discordBot.guild')}</Form.Label>
+ <Form.Control
+ as={DiscordSelect}
+ onChange={({ guild }) => { setGuild(guild); setChannel(''); }}
+ value={guild ? guild.guild_id : ''}
+ />
+ </Form.Group>
+ <Form.Group as={Col} md={6}>
+ <Form.Label>{t('discordBot.channel')}</Form.Label>
+ {guild ?
+ <Form.Control
+ as={DiscordChannelSelect}
+ guild={guild.guild_id}
+ onChange={({ target: { value } }) => setChannel(value)}
+ types={[]}
+ value={channel}
+ />
+ :
+ <Form.Control plaintext readOnly defaultValue={t('discordBot.selectGuild')} />
+ }
+ </Form.Group>
+ </Row>
+ </>;
+};
+
+export default Controls;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-const getIconUrl = guild =>
- `https://cdn.discordapp.com/icons/${guild.guild_id}/${guild.icon_hash}.png`;
-
-const Box = ({ guild }) => <div className="guild-box">
- <img alt="" src={getIconUrl(guild)} />
- <span>{guild.name}</span>
- </div>;
-
-Box.propTypes = {
- guild: PropTypes.shape({
- guild_id: PropTypes.string,
- icon_hash: PropTypes.string,
- name: PropTypes.string,
- }),
-};
-
-export default Box;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+const getIconUrl = guild =>
+ `https://cdn.discordapp.com/icons/${guild.guild_id}/${guild.icon_hash}.png`;
+
+const Box = ({ guild }) => <div className="guild-box">
+ <img alt="" src={getIconUrl(guild)} />
+ <span>{guild.name}</span>
+ </div>;
+
+Box.propTypes = {
+ guild: PropTypes.shape({
+ guild_id: PropTypes.string,
+ icon_hash: PropTypes.string,
+ name: PropTypes.string,
+ }),
+};
+
+export default Box;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Icon from '../common/Icon';
-
-const getIcon = channel => {
- if (channel.type === 0) {
- return <Icon.HASH title="" />;
- }
- if (channel.type === 2) {
- return <Icon.VOLUME title="" />;
- }
- return null;
-};
-
-const Box = ({ channel }) => <div className="channel-box">
- {getIcon(channel)}
- <span>{channel.name}</span>
-</div>;
-
-Box.propTypes = {
- channel: PropTypes.shape({
- name: PropTypes.string,
- }),
-};
-
-export default Box;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Icon from '../common/Icon';
+
+const getIcon = channel => {
+ if (channel.type === 0) {
+ return <Icon.HASH title="" />;
+ }
+ if (channel.type === 2) {
+ return <Icon.VOLUME title="" />;
+ }
+ return null;
+};
+
+const Box = ({ channel }) => <div className="channel-box">
+ {getIcon(channel)}
+ <span>{channel.name}</span>
+</div>;
+
+Box.propTypes = {
+ channel: PropTypes.shape({
+ name: PropTypes.string,
+ }),
+};
+
+export default Box;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Modal } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import ApplyForm from './ApplyForm';
-
-const ApplyDialog = ({
- as,
- episode,
- onHide,
- onSubmit,
- show,
-}) => {
- const { t } = useTranslation();
-
- return <Modal className="apply-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {t('episodes.applyDialog.title')}
- </Modal.Title>
- </Modal.Header>
- <ApplyForm
- as={as}
- episode={episode}
- onCancel={onHide}
- onSubmit={onSubmit}
- />
- </Modal>;
-};
-
-ApplyDialog.propTypes = {
- as: PropTypes.string,
- episode: PropTypes.shape({
- }),
- onHide: PropTypes.func,
- onSubmit: PropTypes.func,
- show: PropTypes.bool,
-};
-
-export default ApplyDialog;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import ApplyForm from './ApplyForm';
+
+const ApplyDialog = ({
+ as,
+ episode,
+ onHide,
+ onSubmit,
+ show,
+}) => {
+ const { t } = useTranslation();
+
+ return <Modal className="apply-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {t('episodes.applyDialog.title')}
+ </Modal.Title>
+ </Modal.Header>
+ <ApplyForm
+ as={as}
+ episode={episode}
+ onCancel={onHide}
+ onSubmit={onSubmit}
+ />
+ </Modal>;
+};
+
+ApplyDialog.propTypes = {
+ as: PropTypes.string,
+ episode: PropTypes.shape({
+ }),
+ onHide: PropTypes.func,
+ onSubmit: PropTypes.func,
+ show: PropTypes.bool,
+};
+
+export default ApplyDialog;
+++ /dev/null
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Form, Modal } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import DialogEpisode from './DialogEpisode';
-import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
-import { applicableChannels } from '../../helpers/permissions';
-import { withUser } from '../../hooks/user';
-
-const ApplyForm = ({
- as,
- episode,
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- onCancel,
- touched,
- user,
- values,
-}) => {
- const { t } = useTranslation();
-
- const available_channels = React.useMemo(() => {
- return applicableChannels(user, episode, as);
- }, [as, episode, user]);
-
- return <Form noValidate onSubmit={handleSubmit}>
- <Modal.Body>
- <DialogEpisode episode={episode} />
- <Form.Group controlId="apply.role">
- <Form.Label>{t('episodes.applyDialog.signUpAs')}</Form.Label>
- <Form.Control
- plaintext
- readOnly
- value={t(`crew.roles.${as}`)}
- />
- </Form.Group>
- <Form.Group controlId="apply.channel_id">
- <Form.Label>{t('episodes.channel')}</Form.Label>
- <Form.Select
- isInvalid={!!(touched.channel_id && errors.channel_id)}
- name="channel_id"
- onBlur={handleBlur}
- onChange={handleChange}
- value={values.channel_id || 0}
- >
- <option disabled value={0}>{t('general.pleaseSelect')}</option>
- {available_channels.map(c =>
- <option key={c.id} value={c.id}>
- {c.title}
- </option>
- )}
- </Form.Select>
- {touched.channel_id && errors.channel_id ?
- <Form.Control.Feedback type="invalid">
- {t(errors.channel_id)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Modal.Body>
- <Modal.Footer>
- {onCancel ?
- <Button onClick={onCancel} variant="secondary">
- {t('button.cancel')}
- </Button>
- : null}
- <Button type="submit" variant="primary">
- {t('button.submit')}
- </Button>
- </Modal.Footer>
- </Form>;
-};
-
-ApplyForm.propTypes = {
- as: PropTypes.string,
- episode: PropTypes.shape({
- }),
- errors: PropTypes.shape({
- channel_id: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- onCancel: PropTypes.func,
- touched: PropTypes.shape({
- channel_id: PropTypes.bool,
- }),
- user: PropTypes.shape({
- }),
- values: PropTypes.shape({
- channel_id: PropTypes.number,
- }),
-};
-
-export default withUser(withFormik({
- displayName: 'ApplyForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { setErrors } = actions;
- const { onSubmit } = actions.props;
- try {
- await onSubmit(values);
- } catch (e) {
- if (e.response && e.response.data && e.response.data.errors) {
- setErrors(laravelErrorsToFormik(e.response.data.errors));
- }
- }
- },
- mapPropsToValues: ({ as, episode, user }) => {
- const channels = applicableChannels(user, episode, as);
- return {
- as,
- channel_id: channels.length ? channels[0].id : 0,
- episode_id: episode ? episode.id : 0,
- };
- },
-})(ApplyForm));
--- /dev/null
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Form, Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import DialogEpisode from './DialogEpisode';
+import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import { applicableChannels } from '../../helpers/permissions';
+import { withUser } from '../../hooks/user';
+
+const ApplyForm = ({
+ as,
+ episode,
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ onCancel,
+ touched,
+ user,
+ values,
+}) => {
+ const { t } = useTranslation();
+
+ const available_channels = React.useMemo(() => {
+ return applicableChannels(user, episode, as);
+ }, [as, episode, user]);
+
+ return <Form noValidate onSubmit={handleSubmit}>
+ <Modal.Body>
+ <DialogEpisode episode={episode} />
+ <Form.Group controlId="apply.role">
+ <Form.Label>{t('episodes.applyDialog.signUpAs')}</Form.Label>
+ <Form.Control
+ plaintext
+ readOnly
+ value={t(`crew.roles.${as}`)}
+ />
+ </Form.Group>
+ <Form.Group controlId="apply.channel_id">
+ <Form.Label>{t('episodes.channel')}</Form.Label>
+ <Form.Select
+ isInvalid={!!(touched.channel_id && errors.channel_id)}
+ name="channel_id"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.channel_id || 0}
+ >
+ <option disabled value={0}>{t('general.pleaseSelect')}</option>
+ {available_channels.map(c =>
+ <option key={c.id} value={c.id}>
+ {c.title}
+ </option>
+ )}
+ </Form.Select>
+ {touched.channel_id && errors.channel_id ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.channel_id)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Modal.Body>
+ <Modal.Footer>
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {t('button.cancel')}
+ </Button>
+ : null}
+ <Button type="submit" variant="primary">
+ {t('button.submit')}
+ </Button>
+ </Modal.Footer>
+ </Form>;
+};
+
+ApplyForm.propTypes = {
+ as: PropTypes.string,
+ episode: PropTypes.shape({
+ }),
+ errors: PropTypes.shape({
+ channel_id: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ onCancel: PropTypes.func,
+ touched: PropTypes.shape({
+ channel_id: PropTypes.bool,
+ }),
+ user: PropTypes.shape({
+ }),
+ values: PropTypes.shape({
+ channel_id: PropTypes.number,
+ }),
+};
+
+export default withUser(withFormik({
+ displayName: 'ApplyForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { setErrors } = actions;
+ const { onSubmit } = actions.props;
+ try {
+ await onSubmit(values);
+ } catch (e) {
+ if (e.response && e.response.data && e.response.data.errors) {
+ setErrors(laravelErrorsToFormik(e.response.data.errors));
+ }
+ }
+ },
+ mapPropsToValues: ({ as, episode, user }) => {
+ const channels = applicableChannels(user, episode, as);
+ return {
+ as,
+ channel_id: channels.length ? channels[0].id : 0,
+ episode_id: episode ? episode.id : 0,
+ };
+ },
+})(ApplyForm));
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-
-import Link from '../channel/Link';
-import Icon from '../common/Icon';
-import { mayEditRestream } from '../../helpers/permissions';
-import { useUser } from '../../hooks/user';
-
-const Channel = ({ channel, episode, onEditRestream }) => {
- const { user } = useUser();
-
- return <div className="episode-channel text-nowrap">
- <Link channel={channel} />
- {onEditRestream && mayEditRestream(user, episode, channel) ?
- <Button
- className="ms-1"
- onClick={() => onEditRestream(episode, channel)}
- variant="outline-secondary"
- >
- <Icon.SETTINGS />
- </Button>
- : null}
- </div>;
-};
-
-Channel.propTypes = {
- channel: PropTypes.shape({
- short_name: PropTypes.string,
- stream_link: PropTypes.string,
- title: PropTypes.string,
- }),
- episode: PropTypes.shape({
- }),
- onEditRestream: PropTypes.func,
-};
-
-export default Channel;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+
+import Link from '../channel/Link';
+import Icon from '../common/Icon';
+import { mayEditRestream } from '../../helpers/permissions';
+import { useUser } from '../../hooks/user';
+
+const Channel = ({ channel, episode, onEditRestream }) => {
+ const { user } = useUser();
+
+ return <div className="episode-channel text-nowrap">
+ <Link channel={channel} />
+ {onEditRestream && mayEditRestream(user, episode, channel) ?
+ <Button
+ className="ms-1"
+ onClick={() => onEditRestream(episode, channel)}
+ variant="outline-secondary"
+ >
+ <Icon.SETTINGS />
+ </Button>
+ : null}
+ </div>;
+};
+
+Channel.propTypes = {
+ channel: PropTypes.shape({
+ short_name: PropTypes.string,
+ stream_link: PropTypes.string,
+ title: PropTypes.string,
+ }),
+ episode: PropTypes.shape({
+ }),
+ onEditRestream: PropTypes.func,
+};
+
+export default Channel;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Channel from './Channel';
-
-const Channels = ({ channels, episode, onEditRestream }) =>
- channels.map(channel =>
- <Channel
- channel={channel}
- episode={episode}
- key={channel.id}
- onEditRestream={onEditRestream}
- />
- );
-
-Channels.propTypes = {
- channels: PropTypes.arrayOf(PropTypes.shape({
- })),
- episode: PropTypes.shape({
- }),
- onEditRestream: PropTypes.func,
-};
-
-export default Channels;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Channel from './Channel';
+
+const Channels = ({ channels, episode, onEditRestream }) =>
+ channels.map(channel =>
+ <Channel
+ channel={channel}
+ episode={episode}
+ key={channel.id}
+ onEditRestream={onEditRestream}
+ />
+ );
+
+Channels.propTypes = {
+ channels: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ episode: PropTypes.shape({
+ }),
+ onEditRestream: PropTypes.func,
+};
+
+export default Channels;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import CrewMember from './CrewMember';
-import Icon from '../common/Icon';
-import { compareCrew } from '../../helpers/Crew';
-import {
- getSGLanguages,
- getSGSignupLink,
- hasPassed,
- hasSGRestream,
-} from '../../helpers/Episode';
-import { canApplyForEpisode } from '../../helpers/permissions';
-import { useUser } from '../../hooks/user';
-
-const Crew = ({ episode, onApply }) => {
- const { t } = useTranslation();
- const { user } = useUser();
-
- const commentators = React.useMemo(() =>
- episode.crew.filter(c => c.role === 'commentary').sort(compareCrew)
- , [episode]);
- const trackers = React.useMemo(() =>
- episode.crew.filter(c => c.role === 'tracking').sort(compareCrew)
- , [episode]);
- const techies = React.useMemo(() =>
- episode.crew.filter(c => c.role === 'setup').sort(compareCrew)
- , [episode]);
-
- const sgLanguages = React.useMemo(() =>
- getSGLanguages(episode)
- , [episode]);
-
- const showCommentators = React.useMemo(() =>
- commentators.length || (!hasPassed(episode) && (
- canApplyForEpisode(user, episode, 'commentary') ||
- hasSGRestream(episode)
- ))
- , [commentators, episode, user]);
-
- const showTracker = React.useMemo(() =>
- trackers.length || (!hasPassed(episode) && (
- canApplyForEpisode(user, episode, 'tracking') ||
- hasSGRestream(episode)
- ))
- , [episode, trackers, user]);
-
- return <Row className="episode-crew">
- {showCommentators ?
- <Col xs={6} md>
- <div className="fs-6 fs-md-5">
- <Icon.MICROPHONE className="ms-3 me-2" title="" />
- {t('episodes.commentary')}
- </div>
- {commentators.map(c =>
- <CrewMember crew={c} key={c.id} />
- )}
- {onApply && canApplyForEpisode(user, episode, 'commentary') ?
- <div className="button-bar m-2">
- <Button
- onClick={() => onApply(episode, 'commentary')}
- variant="outline-secondary"
- >
- {t('button.signUp')}
- </Button>
- </div>
- : null}
- {hasSGRestream(episode) ?
- <div className="button-bar m-2">
- {sgLanguages.map(lang =>
- <Button
- href={getSGSignupLink(episode, lang, 'commentator')}
- key={lang}
- target="_blank"
- variant="outline-secondary"
- >
- {`${t('episodes.sgSignUp')} ${lang.toUpperCase()}`}
- </Button>
- )}
- </div>
- : null}
- </Col>
- : null}
- {showTracker ?
- <Col xs={6} md>
- <div className="fs-6 fs-md-5">
- <Icon.MOUSE className="ms-3 me-2" title="" />
- {t('episodes.tracking')}
- </div>
- {trackers.map(c =>
- <CrewMember crew={c} key={c.id} />
- )}
- {onApply && canApplyForEpisode(user, episode, 'tracking') ?
- <div className="button-bar m-2">
- <Button
- onClick={() => onApply(episode, 'tracking')}
- variant="outline-secondary"
- >
- {t('button.signUp')}
- </Button>
- </div>
- : null}
- {hasSGRestream(episode) ?
- <div className="button-bar m-2">
- {sgLanguages.map(lang =>
- <Button
- href={getSGSignupLink(episode, lang, 'tracker')}
- key={lang}
- target="_blank"
- variant="outline-secondary"
- >
- {`${t('episodes.sgSignUp')} ${lang.toUpperCase()}`}
- </Button>
- )}
- </div>
- : null}
- </Col>
- : null}
- {techies.length ?
- <Col xs={6} md>
- <div className="fs-6 fs-md-5">
- <Icon.MONITOR className="ms-3 me-2" title="" />
- {t('episodes.setup')}
- </div>
- {techies.map(c =>
- <CrewMember crew={c} key={c.id} />
- )}
- </Col>
- : null}
- </Row>;
-};
-
-Crew.propTypes = {
- episode: PropTypes.shape({
- channels: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.number,
- })),
- crew: PropTypes.arrayOf(PropTypes.shape({
- })),
- }),
- onApply: PropTypes.func,
-};
-
-export default Crew;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import CrewMember from './CrewMember';
+import Icon from '../common/Icon';
+import { compareCrew } from '../../helpers/Crew';
+import {
+ getSGLanguages,
+ getSGSignupLink,
+ hasPassed,
+ hasSGRestream,
+} from '../../helpers/Episode';
+import { canApplyForEpisode } from '../../helpers/permissions';
+import { useUser } from '../../hooks/user';
+
+const Crew = ({ episode, onApply }) => {
+ const { t } = useTranslation();
+ const { user } = useUser();
+
+ const commentators = React.useMemo(() =>
+ episode.crew.filter(c => c.role === 'commentary').sort(compareCrew)
+ , [episode]);
+ const trackers = React.useMemo(() =>
+ episode.crew.filter(c => c.role === 'tracking').sort(compareCrew)
+ , [episode]);
+ const techies = React.useMemo(() =>
+ episode.crew.filter(c => c.role === 'setup').sort(compareCrew)
+ , [episode]);
+
+ const sgLanguages = React.useMemo(() =>
+ getSGLanguages(episode)
+ , [episode]);
+
+ const showCommentators = React.useMemo(() =>
+ commentators.length || (!hasPassed(episode) && (
+ canApplyForEpisode(user, episode, 'commentary') ||
+ hasSGRestream(episode)
+ ))
+ , [commentators, episode, user]);
+
+ const showTracker = React.useMemo(() =>
+ trackers.length || (!hasPassed(episode) && (
+ canApplyForEpisode(user, episode, 'tracking') ||
+ hasSGRestream(episode)
+ ))
+ , [episode, trackers, user]);
+
+ return <Row className="episode-crew">
+ {showCommentators ?
+ <Col xs={6} md>
+ <div className="fs-6 fs-md-5">
+ <Icon.MICROPHONE className="ms-3 me-2" title="" />
+ {t('episodes.commentary')}
+ </div>
+ {commentators.map(c =>
+ <CrewMember crew={c} key={c.id} />
+ )}
+ {onApply && canApplyForEpisode(user, episode, 'commentary') ?
+ <div className="button-bar m-2">
+ <Button
+ onClick={() => onApply(episode, 'commentary')}
+ variant="outline-secondary"
+ >
+ {t('button.signUp')}
+ </Button>
+ </div>
+ : null}
+ {hasSGRestream(episode) ?
+ <div className="button-bar m-2">
+ {sgLanguages.map(lang =>
+ <Button
+ href={getSGSignupLink(episode, lang, 'commentator')}
+ key={lang}
+ target="_blank"
+ variant="outline-secondary"
+ >
+ {`${t('episodes.sgSignUp')} ${lang.toUpperCase()}`}
+ </Button>
+ )}
+ </div>
+ : null}
+ </Col>
+ : null}
+ {showTracker ?
+ <Col xs={6} md>
+ <div className="fs-6 fs-md-5">
+ <Icon.MOUSE className="ms-3 me-2" title="" />
+ {t('episodes.tracking')}
+ </div>
+ {trackers.map(c =>
+ <CrewMember crew={c} key={c.id} />
+ )}
+ {onApply && canApplyForEpisode(user, episode, 'tracking') ?
+ <div className="button-bar m-2">
+ <Button
+ onClick={() => onApply(episode, 'tracking')}
+ variant="outline-secondary"
+ >
+ {t('button.signUp')}
+ </Button>
+ </div>
+ : null}
+ {hasSGRestream(episode) ?
+ <div className="button-bar m-2">
+ {sgLanguages.map(lang =>
+ <Button
+ href={getSGSignupLink(episode, lang, 'tracker')}
+ key={lang}
+ target="_blank"
+ variant="outline-secondary"
+ >
+ {`${t('episodes.sgSignUp')} ${lang.toUpperCase()}`}
+ </Button>
+ )}
+ </div>
+ : null}
+ </Col>
+ : null}
+ {techies.length ?
+ <Col xs={6} md>
+ <div className="fs-6 fs-md-5">
+ <Icon.MONITOR className="ms-3 me-2" title="" />
+ {t('episodes.setup')}
+ </div>
+ {techies.map(c =>
+ <CrewMember crew={c} key={c.id} />
+ )}
+ </Col>
+ : null}
+ </Row>;
+};
+
+Crew.propTypes = {
+ episode: PropTypes.shape({
+ channels: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.number,
+ })),
+ crew: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ }),
+ onApply: PropTypes.func,
+};
+
+export default Crew;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Form } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import CrewMember from './CrewMember';
-import Icon from '../common/Icon';
-import UserSelect from '../common/UserSelect';
-
-const CrewManagement = ({
- channel,
- episode,
- manageCrew,
- role,
-}) => {
- const { t } = useTranslation();
-
- const crews = React.useMemo(() =>
- (episode.crew || [])
- .filter(c => c.channel_id === channel.id && c.role === role)
- , [channel, episode, role]);
-
- const addCrew = React.useCallback(user_id => {
- manageCrew({
- add: user_id,
- channel_id: channel.id,
- episode_id: episode.id,
- role,
- });
- }, [channel, episode, manageCrew, role]);
-
- const confirmCrew = React.useCallback(crew => {
- manageCrew({
- channel_id: channel.id,
- confirm: crew.id,
- episode_id: episode.id,
- role,
- });
- }, [channel, episode, manageCrew, role]);
-
- const removeCrew = React.useCallback(crew => {
- manageCrew({
- channel_id: channel.id,
- episode_id: episode.id,
- remove: crew.id,
- role,
- });
- }, [channel, episode, manageCrew, role]);
-
- const unconfirmCrew = React.useCallback(crew => {
- manageCrew({
- channel_id: channel.id,
- episode_id: episode.id,
- role,
- unconfirm: crew.id,
- });
- }, [channel, episode, manageCrew, role]);
-
- return <div className="mt-2">
- <div className="fs-4">{t(`crew.roles.${role}`)}</div>
- {crews.map(crew =>
- <div className="d-flex align-items-center justify-content-between" key={crew.id}>
- <CrewMember crew={crew} />
- <div className="button-bar">
- {crew.confirmed ?
- <Button
- onClick={() => unconfirmCrew(crew)}
- title={t('button.unconfirm')}
- variant="outline-danger"
- >
- <Icon.REJECT title="" />
- </Button>
- : null}
- {!crew.confirmed ?
- <Button
- onClick={() => confirmCrew(crew)}
- title={t('button.confirm')}
- variant="outline-success"
- >
- <Icon.ACCEPT />
- </Button>
- : null}
- <Button
- onClick={() => removeCrew(crew)}
- title={t('button.remove')}
- variant="outline-danger"
- >
- <Icon.DELETE title="" />
- </Button>
- </div>
- </div>
- )}
- <Form.Group controlId="crew.addUser">
- <Form.Label>{t('episodes.restreamDialog.addUser')}</Form.Label>
- <Form.Control
- as={UserSelect}
- onChange={e => addCrew(e.target.value)}
- value=""
- />
- </Form.Group>
- </div>;
-};
-
-CrewManagement.propTypes = {
- channel: PropTypes.shape({
- id: PropTypes.number,
- }),
- episode: PropTypes.shape({
- crew: PropTypes.arrayOf(PropTypes.shape({
- channel_id: PropTypes.number,
- role: PropTypes.string,
- })),
- id: PropTypes.number,
- }),
- manageCrew: PropTypes.func,
- role: PropTypes.string,
-};
-
-export default CrewManagement;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Form } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import CrewMember from './CrewMember';
+import Icon from '../common/Icon';
+import UserSelect from '../common/UserSelect';
+
+const CrewManagement = ({
+ channel,
+ episode,
+ manageCrew,
+ role,
+}) => {
+ const { t } = useTranslation();
+
+ const crews = React.useMemo(() =>
+ (episode.crew || [])
+ .filter(c => c.channel_id === channel.id && c.role === role)
+ , [channel, episode, role]);
+
+ const addCrew = React.useCallback(user_id => {
+ manageCrew({
+ add: user_id,
+ channel_id: channel.id,
+ episode_id: episode.id,
+ role,
+ });
+ }, [channel, episode, manageCrew, role]);
+
+ const confirmCrew = React.useCallback(crew => {
+ manageCrew({
+ channel_id: channel.id,
+ confirm: crew.id,
+ episode_id: episode.id,
+ role,
+ });
+ }, [channel, episode, manageCrew, role]);
+
+ const removeCrew = React.useCallback(crew => {
+ manageCrew({
+ channel_id: channel.id,
+ episode_id: episode.id,
+ remove: crew.id,
+ role,
+ });
+ }, [channel, episode, manageCrew, role]);
+
+ const unconfirmCrew = React.useCallback(crew => {
+ manageCrew({
+ channel_id: channel.id,
+ episode_id: episode.id,
+ role,
+ unconfirm: crew.id,
+ });
+ }, [channel, episode, manageCrew, role]);
+
+ return <div className="mt-2">
+ <div className="fs-4">{t(`crew.roles.${role}`)}</div>
+ {crews.map(crew =>
+ <div className="d-flex align-items-center justify-content-between" key={crew.id}>
+ <CrewMember crew={crew} />
+ <div className="button-bar">
+ {crew.confirmed ?
+ <Button
+ onClick={() => unconfirmCrew(crew)}
+ title={t('button.unconfirm')}
+ variant="outline-danger"
+ >
+ <Icon.REJECT title="" />
+ </Button>
+ : null}
+ {!crew.confirmed ?
+ <Button
+ onClick={() => confirmCrew(crew)}
+ title={t('button.confirm')}
+ variant="outline-success"
+ >
+ <Icon.ACCEPT />
+ </Button>
+ : null}
+ <Button
+ onClick={() => removeCrew(crew)}
+ title={t('button.remove')}
+ variant="outline-danger"
+ >
+ <Icon.DELETE title="" />
+ </Button>
+ </div>
+ </div>
+ )}
+ <Form.Group controlId="crew.addUser">
+ <Form.Label>{t('episodes.restreamDialog.addUser')}</Form.Label>
+ <Form.Control
+ as={UserSelect}
+ onChange={e => addCrew(e.target.value)}
+ value=""
+ />
+ </Form.Group>
+ </div>;
+};
+
+CrewManagement.propTypes = {
+ channel: PropTypes.shape({
+ id: PropTypes.number,
+ }),
+ episode: PropTypes.shape({
+ crew: PropTypes.arrayOf(PropTypes.shape({
+ channel_id: PropTypes.number,
+ role: PropTypes.string,
+ })),
+ id: PropTypes.number,
+ }),
+ manageCrew: PropTypes.func,
+ role: PropTypes.string,
+};
+
+export default CrewManagement;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-
-import { getName, getStreamLink } from '../../helpers/Crew';
-import { getAvatarUrl } from '../../helpers/User';
-
-const CrewMember = ({ crew }) => {
- const classNames = [
- 'crew-member',
- 'text-light',
- ];
- if (!crew.confirmed) {
- classNames.push('unconfirmed');
- }
- return <Button
- className={classNames.join(' ')}
- href={getStreamLink(crew) || null}
- key={crew.id}
- rel="noreferer"
- variant="outline-twitch"
- >
- <img alt="" src={getAvatarUrl(crew.user)} />
- <span>{getName(crew)}</span>
- </Button>;
-};
-
-CrewMember.propTypes = {
- crew: PropTypes.shape({
- confirmed: PropTypes.bool,
- id: PropTypes.number,
- user: PropTypes.shape({
- }),
- }),
-};
-
-export default CrewMember;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+
+import { getName, getStreamLink } from '../../helpers/Crew';
+import { getAvatarUrl } from '../../helpers/User';
+
+const CrewMember = ({ crew }) => {
+ const classNames = [
+ 'crew-member',
+ 'text-light',
+ ];
+ if (!crew.confirmed) {
+ classNames.push('unconfirmed');
+ }
+ return <Button
+ className={classNames.join(' ')}
+ href={getStreamLink(crew) || null}
+ key={crew.id}
+ rel="noreferer"
+ variant="outline-twitch"
+ >
+ <img alt="" src={getAvatarUrl(crew.user)} />
+ <span>{getName(crew)}</span>
+ </Button>;
+};
+
+CrewMember.propTypes = {
+ crew: PropTypes.shape({
+ confirmed: PropTypes.bool,
+ id: PropTypes.number,
+ user: PropTypes.shape({
+ }),
+ }),
+};
+
+export default CrewMember;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-
-import { getName } from '../../helpers/Crew';
-
-const DialogEpisode = ({ episode }) => {
- const { t } = useTranslation();
-
- if (!episode) return null;
-
- return <>
- <div>
- {episode.event.title}
- </div>
- <div>
- {t('episodes.startTime', { date: new Date(episode.start) })}
- </div>
- <div>
- {episode.players.map(p => getName(p)).join(', ')}
- </div>
- </>;
-};
-
-DialogEpisode.propTypes = {
- episode: PropTypes.shape({
- event: PropTypes.shape({
- title: PropTypes.string,
- }),
- players: PropTypes.arrayOf(PropTypes.shape({
- })),
- start: PropTypes.string,
- }),
-};
-
-export default DialogEpisode;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import { getName } from '../../helpers/Crew';
+
+const DialogEpisode = ({ episode }) => {
+ const { t } = useTranslation();
+
+ if (!episode) return null;
+
+ return <>
+ <div>
+ {episode.event.title}
+ </div>
+ <div>
+ {t('episodes.startTime', { date: new Date(episode.start) })}
+ </div>
+ <div>
+ {episode.players.map(p => getName(p)).join(', ')}
+ </div>
+ </>;
+};
+
+DialogEpisode.propTypes = {
+ episode: PropTypes.shape({
+ event: PropTypes.shape({
+ title: PropTypes.string,
+ }),
+ players: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ start: PropTypes.string,
+ }),
+};
+
+export default DialogEpisode;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-
-import { isEventSelected, toggleEventFilter } from '../../helpers/Episode';
-
-const Filter = ({ events, filter, setFilter }) => {
- const toggleEvent = React.useCallback(event => {
- setFilter(toggleEventFilter(events, filter, event));
- }, [events, filter, setFilter]);
-
- if (!events || !events.length) return null;
-
- return <div className="episode-filter button-bar text-end">
- {events.map(event =>
- <Button
- active={isEventSelected(filter, event)}
- key={event.id}
- onClick={() => toggleEvent(event)}
- title={event.short ? event.title : null}
- variant="outline-secondary"
- >
- {event.short || event.title}
- </Button>
- )}
- </div>;
-};
-
-Filter.propTypes = {
- events: PropTypes.arrayOf(PropTypes.shape({
- })),
- filter: PropTypes.shape(),
- setFilter: PropTypes.func,
-};
-
-export default Filter;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+
+import { isEventSelected, toggleEventFilter } from '../../helpers/Episode';
+
+const Filter = ({ events, filter, setFilter }) => {
+ const toggleEvent = React.useCallback(event => {
+ setFilter(toggleEventFilter(events, filter, event));
+ }, [events, filter, setFilter]);
+
+ if (!events || !events.length) return null;
+
+ return <div className="episode-filter button-bar text-end">
+ {events.map(event =>
+ <Button
+ active={isEventSelected(filter, event)}
+ key={event.id}
+ onClick={() => toggleEvent(event)}
+ title={event.short ? event.title : null}
+ variant="outline-secondary"
+ >
+ {event.short || event.title}
+ </Button>
+ )}
+ </div>;
+};
+
+Filter.propTypes = {
+ events: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ filter: PropTypes.shape(),
+ setFilter: PropTypes.func,
+};
+
+export default Filter;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-import { Link } from 'react-router-dom';
-
-import Channels from './Channels';
-import Crew from './Crew';
-import MultiLink from './MultiLink';
-import Players from './Players';
-import Icon from '../common/Icon';
-import { hasPassed, hasSGRestream, isActive } from '../../helpers/Episode';
-import { getLink } from '../../helpers/Event';
-import { canApplyForEpisode, canRestreamEpisode } from '../../helpers/permissions';
-import { useUser } from '../../hooks/user';
-
-const Item = ({ episode, onAddRestream, onApply, onEditRestream }) => {
- const { t } = useTranslation();
- const { user } = useUser();
-
- const classNames = [
- 'episodes-item',
- 'my-3',
- 'p-2',
- 'border',
- 'rounded',
- ];
- if (isActive(episode)) {
- classNames.push('is-active');
- }
-
- const style = React.useMemo(() => {
- if (episode.event && episode.event.corner) {
- return {
- backgroundImage: `url(${episode.event.corner})`,
- };
- }
- return null;
- }, [episode.event && episode.event.corner]);
-
- const hasChannels = episode.channels && episode.channels.length;
- const hasPlayers = episode.players && episode.players.length;
-
- return <div className={classNames.join(' ')} style={style}>
- <div className="d-flex align-items-stretch">
- <div className="episode-start me-3 fs-5 fs-md-4 text-end">
- {t('schedule.startTime', { date: new Date(episode.start) })}
- </div>
- <div className="episode-titlebar">
- {episode.title || episode.event ?
- <div className="episode-title fs-5 fs-md-4">
- {episode.title || episode.event.title}
- </div>
- : null}
- {episode.comment ?
- <div className="episode-comment">
- {episode.comment}
- </div>
- : null}
- </div>
- <div className="episode-channel-links ms-auto text-end">
- {hasChannels ?
- <Channels
- channels={episode.channels}
- episode={episode}
- onEditRestream={onEditRestream}
- />
- : null}
- {!hasChannels && hasPlayers ?
- <MultiLink players={episode.players} />
- : null}
- {episode.raceroom ?
- <div>
- <Button
- href={episode.raceroom}
- target="_blank"
- title={t('episodes.raceroom')}
- variant="outline-secondary"
- >
- <Icon.RACETIME title="" />
- {' '}
- {t('episodes.raceroom')}
- </Button>
- </div>
- : null}
- {onAddRestream && canRestreamEpisode(user, episode) ?
- <div>
- <Button
- onClick={() => onAddRestream(episode)}
- title={t('episodes.addRestream')}
- variant="outline-secondary"
- >
- <Icon.ADD title="" />
- </Button>
- </div>
- : null}
- </div>
- </div>
- <div className="episode-body d-flex flex-column flex-fill">
- {hasPlayers ?
- <Players players={episode.players} />
- : null}
- {(episode.crew && episode.crew.length) || (!hasPassed(episode) && (
- hasSGRestream(episode)
- || canApplyForEpisode(user, episode, 'commentary')
- || canApplyForEpisode(user, episode, 'tracking')
- )) ?
- <div className="mb-3">
- <Crew episode={episode} onApply={onApply} />
- </div>
- : null}
- {episode.event ?
- <div className="episode-event mt-auto">
- {episode.event.description_id ?
- <Link className="event-link" to={getLink(episode.event)}>
- {episode.event.title}
- </Link>
- :
- episode.event.title
- }
- </div>
- : null}
- </div>
- </div>;
-};
-
-Item.propTypes = {
- episode: PropTypes.shape({
- channels: PropTypes.arrayOf(PropTypes.shape({
- })),
- comment: PropTypes.string,
- crew: PropTypes.arrayOf(PropTypes.shape({
- })),
- event: PropTypes.shape({
- corner: PropTypes.string,
- description_id: PropTypes.number,
- name: PropTypes.string,
- title: PropTypes.string,
- }),
- players: PropTypes.arrayOf(PropTypes.shape({
- })),
- raceroom: PropTypes.string,
- start: PropTypes.string,
- title: PropTypes.string,
- }),
- onAddRestream: PropTypes.func,
- onApply: PropTypes.func,
- onEditRestream: PropTypes.func,
-};
-
-export default Item;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+import { Link } from 'react-router-dom';
+
+import Channels from './Channels';
+import Crew from './Crew';
+import MultiLink from './MultiLink';
+import Players from './Players';
+import Icon from '../common/Icon';
+import { hasPassed, hasSGRestream, isActive } from '../../helpers/Episode';
+import { getLink } from '../../helpers/Event';
+import { canApplyForEpisode, canRestreamEpisode } from '../../helpers/permissions';
+import { useUser } from '../../hooks/user';
+
+const Item = ({ episode, onAddRestream, onApply, onEditRestream }) => {
+ const { t } = useTranslation();
+ const { user } = useUser();
+
+ const classNames = [
+ 'episodes-item',
+ 'my-3',
+ 'p-2',
+ 'border',
+ 'rounded',
+ ];
+ if (isActive(episode)) {
+ classNames.push('is-active');
+ }
+
+ const style = React.useMemo(() => {
+ if (episode.event && episode.event.corner) {
+ return {
+ backgroundImage: `url(${episode.event.corner})`,
+ };
+ }
+ return null;
+ }, [episode.event && episode.event.corner]);
+
+ const hasChannels = episode.channels && episode.channels.length;
+ const hasPlayers = episode.players && episode.players.length;
+
+ return <div className={classNames.join(' ')} style={style}>
+ <div className="d-flex align-items-stretch">
+ <div className="episode-start me-3 fs-5 fs-md-4 text-end">
+ {t('schedule.startTime', { date: new Date(episode.start) })}
+ </div>
+ <div className="episode-titlebar">
+ {episode.title || episode.event ?
+ <div className="episode-title fs-5 fs-md-4">
+ {episode.title || episode.event.title}
+ </div>
+ : null}
+ {episode.comment ?
+ <div className="episode-comment">
+ {episode.comment}
+ </div>
+ : null}
+ </div>
+ <div className="episode-channel-links ms-auto text-end">
+ {hasChannels ?
+ <Channels
+ channels={episode.channels}
+ episode={episode}
+ onEditRestream={onEditRestream}
+ />
+ : null}
+ {!hasChannels && hasPlayers ?
+ <MultiLink players={episode.players} />
+ : null}
+ {episode.raceroom ?
+ <div>
+ <Button
+ href={episode.raceroom}
+ target="_blank"
+ title={t('episodes.raceroom')}
+ variant="outline-secondary"
+ >
+ <Icon.RACETIME title="" />
+ {' '}
+ {t('episodes.raceroom')}
+ </Button>
+ </div>
+ : null}
+ {onAddRestream && canRestreamEpisode(user, episode) ?
+ <div>
+ <Button
+ onClick={() => onAddRestream(episode)}
+ title={t('episodes.addRestream')}
+ variant="outline-secondary"
+ >
+ <Icon.ADD title="" />
+ </Button>
+ </div>
+ : null}
+ </div>
+ </div>
+ <div className="episode-body d-flex flex-column flex-fill">
+ {hasPlayers ?
+ <Players players={episode.players} />
+ : null}
+ {(episode.crew && episode.crew.length) || (!hasPassed(episode) && (
+ hasSGRestream(episode)
+ || canApplyForEpisode(user, episode, 'commentary')
+ || canApplyForEpisode(user, episode, 'tracking')
+ )) ?
+ <div className="mb-3">
+ <Crew episode={episode} onApply={onApply} />
+ </div>
+ : null}
+ {episode.event ?
+ <div className="episode-event mt-auto">
+ {episode.event.description_id ?
+ <Link className="event-link" to={getLink(episode.event)}>
+ {episode.event.title}
+ </Link>
+ :
+ episode.event.title
+ }
+ </div>
+ : null}
+ </div>
+ </div>;
+};
+
+Item.propTypes = {
+ episode: PropTypes.shape({
+ channels: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ comment: PropTypes.string,
+ crew: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ event: PropTypes.shape({
+ corner: PropTypes.string,
+ description_id: PropTypes.number,
+ name: PropTypes.string,
+ title: PropTypes.string,
+ }),
+ players: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ raceroom: PropTypes.string,
+ start: PropTypes.string,
+ title: PropTypes.string,
+ }),
+ onAddRestream: PropTypes.func,
+ onApply: PropTypes.func,
+ onEditRestream: PropTypes.func,
+};
+
+export default Item;
+++ /dev/null
-import moment from 'moment';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Item from './Item';
-
-const List = ({ episodes, onAddRestream, onApply, onEditRestream }) => {
- const grouped = React.useMemo(() => episodes.reduce((groups, episode) => {
- const day = moment(episode.start).format('YYYY-MM-DD');
- return {
- ...groups,
- [day]: [
- ...groups[day] || [],
- episode,
- ],
- };
- }, {}), [episodes]);
-
- return <div className="episodes-list">
- {Object.entries(grouped).map(([day, group]) => <div key={day}>
- <h2 className="text-center episodes-group-heading">
- {moment(day).format('dddd, L')}
- </h2>
- {group.map(episode =>
- <Item
- episode={episode}
- onAddRestream={onAddRestream}
- onApply={onApply}
- onEditRestream={onEditRestream}
- key={episode.id}
- />
- )}
- </div>)}
- </div>;
-};
-
-List.propTypes = {
- episodes: PropTypes.arrayOf(PropTypes.shape({
- start: PropTypes.string,
- })),
- onAddRestream: PropTypes.func,
- onApply: PropTypes.func,
- onEditRestream: PropTypes.func,
-};
-
-export default List;
--- /dev/null
+import moment from 'moment';
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Item from './Item';
+
+const List = ({ episodes, onAddRestream, onApply, onEditRestream }) => {
+ const grouped = React.useMemo(() => episodes.reduce((groups, episode) => {
+ const day = moment(episode.start).format('YYYY-MM-DD');
+ return {
+ ...groups,
+ [day]: [
+ ...groups[day] || [],
+ episode,
+ ],
+ };
+ }, {}), [episodes]);
+
+ return <div className="episodes-list">
+ {Object.entries(grouped).map(([day, group]) => <div key={day}>
+ <h2 className="text-center episodes-group-heading">
+ {moment(day).format('dddd, L')}
+ </h2>
+ {group.map(episode =>
+ <Item
+ episode={episode}
+ onAddRestream={onAddRestream}
+ onApply={onApply}
+ onEditRestream={onEditRestream}
+ key={episode.id}
+ />
+ )}
+ </div>)}
+ </div>;
+};
+
+List.propTypes = {
+ episodes: PropTypes.arrayOf(PropTypes.shape({
+ start: PropTypes.string,
+ })),
+ onAddRestream: PropTypes.func,
+ onApply: PropTypes.func,
+ onEditRestream: PropTypes.func,
+};
+
+export default List;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-
-import Icon from '../common/Icon';
-import { getStreamLink } from '../../helpers/Crew';
-
-const MultiLink = ({ players }) => {
- const streams = players.map(getStreamLink);
- const names = streams.map(s => s.split('/').pop());
- const url = `https://multistre.am/${names.join('/')}`;
-
- return <div className="episode-channel">
- <Button
- href={url}
- rel="noreferer"
- target="_blank"
- title="MultiTwitch"
- variant="outline-twitch"
- >
- <Icon.STREAM />
- {' MultiStream'}
- </Button>
- </div>;
-};
-
-MultiLink.propTypes = {
- players: PropTypes.arrayOf(PropTypes.shape({
- short_name: PropTypes.string,
- stream_link: PropTypes.string,
- title: PropTypes.string,
- })),
-};
-
-export default MultiLink;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+
+import Icon from '../common/Icon';
+import { getStreamLink } from '../../helpers/Crew';
+
+const MultiLink = ({ players }) => {
+ const streams = players.map(getStreamLink);
+ const names = streams.map(s => s.split('/').pop());
+ const url = `https://multistre.am/${names.join('/')}`;
+
+ return <div className="episode-channel">
+ <Button
+ href={url}
+ rel="noreferer"
+ target="_blank"
+ title="MultiTwitch"
+ variant="outline-twitch"
+ >
+ <Icon.STREAM />
+ {' MultiStream'}
+ </Button>
+ </div>;
+};
+
+MultiLink.propTypes = {
+ players: PropTypes.arrayOf(PropTypes.shape({
+ short_name: PropTypes.string,
+ stream_link: PropTypes.string,
+ title: PropTypes.string,
+ })),
+};
+
+export default MultiLink;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-
-import { getName, getStreamLink } from '../../helpers/Crew';
-import { getAvatarUrl } from '../../helpers/User';
-
-const Player = ({ player }) => {
- return <div className="episode-player my-3">
- <Button
- className="player-link"
- href={getStreamLink(player)}
- rel="noreferrer"
- target="_blank"
- variant="outline-twitch"
- >
- <img alt="" src={getAvatarUrl(player.user)} />
- <span className="text-light fs-5 fs-md-4">{getName(player)}</span>
- </Button>
- </div>;
-};
-
-Player.propTypes = {
- player: PropTypes.shape({
- id: PropTypes.number,
- name_override: PropTypes.string,
- stream_override: PropTypes.string,
- user: PropTypes.shape({
- }),
- }),
-};
-
-export default Player;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+
+import { getName, getStreamLink } from '../../helpers/Crew';
+import { getAvatarUrl } from '../../helpers/User';
+
+const Player = ({ player }) => {
+ return <div className="episode-player my-3">
+ <Button
+ className="player-link"
+ href={getStreamLink(player)}
+ rel="noreferrer"
+ target="_blank"
+ variant="outline-twitch"
+ >
+ <img alt="" src={getAvatarUrl(player.user)} />
+ <span className="text-light fs-5 fs-md-4">{getName(player)}</span>
+ </Button>
+ </div>;
+};
+
+Player.propTypes = {
+ player: PropTypes.shape({
+ id: PropTypes.number,
+ name_override: PropTypes.string,
+ stream_override: PropTypes.string,
+ user: PropTypes.shape({
+ }),
+ }),
+};
+
+export default Player;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Player from './Player';
-
-const Players = ({ players }) =>
- <div className="episode-players">
- {players.map(player =>
- <Player key={player.id} player={player} />
- )}
- </div>;
-
-Players.propTypes = {
- players: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.number,
- })),
-};
-
-export default Players;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Player from './Player';
+
+const Players = ({ players }) =>
+ <div className="episode-players">
+ {players.map(player =>
+ <Player key={player.id} player={player} />
+ )}
+ </div>;
+
+Players.propTypes = {
+ players: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.number,
+ })),
+};
+
+export default Players;
+++ /dev/null
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import DialogEpisode from './DialogEpisode';
-import ToggleSwitch from '../common/ToggleSwitch';
-import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
-import { withUser } from '../../hooks/user';
-
-const channelCompare = (a, b) => a.channel.title.localeCompare(b.channel.title);
-
-const RestreamAddForm = ({
- episode,
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- onCancel,
- touched,
- user,
- values,
-}) => {
- const { t } = useTranslation();
-
- return <Form noValidate onSubmit={handleSubmit}>
- <Modal.Body>
- <DialogEpisode episode={episode} />
- <Form.Group controlId="episodes.channel_id">
- <Form.Label>{t('episodes.channel')}</Form.Label>
- <Form.Select
- isInvalid={!!(touched.channel_id && errors.channel_id)}
- name="channel_id"
- onBlur={handleBlur}
- onChange={handleChange}
- value={values.channel_id || 0}
- >
- <option disabled value={0}>{t('general.pleaseSelect')}</option>
- {((user && user.channel_crews) || []).sort(channelCompare).map(c =>
- <option key={c.id} value={c.channel_id}>
- {c.channel.title}
- </option>
- )}
- </Form.Select>
- {touched.channel_id && errors.channel_id ?
- <Form.Control.Feedback type="invalid">
- {t(errors.channel_id)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Row>
- <Form.Group as={Col} sm={6} controlId="episodes.accept_comms">
- <Form.Label className="d-block">
- {t('episodes.restreamDialog.acceptComms')}
- </Form.Label>
- <Form.Control
- as={ToggleSwitch}
- isInvalid={!!(touched.accept_comms && errors.accept_comms)}
- name="accept_comms"
- onBlur={handleBlur}
- onChange={handleChange}
- value={!!values.accept_comms}
- />
- {touched.accept_comms && errors.accept_comms ?
- <Form.Control.Feedback type="invalid">
- {t(errors.accept_comms)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group as={Col} sm={6} controlId="episodes.accept_tracker">
- <Form.Label className="d-block">
- {t('episodes.restreamDialog.acceptTracker')}
- </Form.Label>
- <Form.Control
- as={ToggleSwitch}
- isInvalid={!!(touched.accept_tracker && errors.accept_tracker)}
- name="accept_tracker"
- onBlur={handleBlur}
- onChange={handleChange}
- value={!!values.accept_tracker}
- />
- {touched.accept_tracker && errors.accept_tracker ?
- <Form.Control.Feedback type="invalid">
- {t(errors.accept_tracker)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Row>
- </Modal.Body>
- <Modal.Footer>
- {onCancel ?
- <Button onClick={onCancel} variant="secondary">
- {t('button.cancel')}
- </Button>
- : null}
- <Button type="submit" variant="primary">
- {t('button.save')}
- </Button>
- </Modal.Footer>
- </Form>;
-};
-
-RestreamAddForm.propTypes = {
- episode: PropTypes.shape({
- event: PropTypes.shape({
- title: PropTypes.string,
- }),
- players: PropTypes.arrayOf(PropTypes.shape({
- })),
- start: PropTypes.string,
- }),
- errors: PropTypes.shape({
- accept_comms: PropTypes.string,
- accept_tracker: PropTypes.string,
- channel_id: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- onCancel: PropTypes.func,
- touched: PropTypes.shape({
- accept_comms: PropTypes.bool,
- accept_tracker: PropTypes.bool,
- channel_id: PropTypes.bool,
- }),
- user: PropTypes.shape({
- channel_crews: PropTypes.arrayOf(PropTypes.shape({
- })),
- }),
- values: PropTypes.shape({
- accept_comms: PropTypes.bool,
- accept_tracker: PropTypes.bool,
- channel_id: PropTypes.number,
- }),
-};
-
-export default withUser(withFormik({
- displayName: 'RestreamAddForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { setErrors } = actions;
- const { onSubmit } = actions.props;
- try {
- await onSubmit(values);
- } catch (e) {
- if (e.response && e.response.data && e.response.data.errors) {
- setErrors(laravelErrorsToFormik(e.response.data.errors));
- }
- }
- },
- mapPropsToValues: ({ episode, user }) => ({
- accept_comms: false,
- accept_tracker: false,
- channel_id: user && user.channel_crews && user.channel_crews.length
- ? user.channel_crews[0].channel_id : 0,
- episode_id: episode ? episode.id : 0,
- }),
-})(RestreamAddForm));
--- /dev/null
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import DialogEpisode from './DialogEpisode';
+import ToggleSwitch from '../common/ToggleSwitch';
+import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import { withUser } from '../../hooks/user';
+
+const channelCompare = (a, b) => a.channel.title.localeCompare(b.channel.title);
+
+const RestreamAddForm = ({
+ episode,
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ onCancel,
+ touched,
+ user,
+ values,
+}) => {
+ const { t } = useTranslation();
+
+ return <Form noValidate onSubmit={handleSubmit}>
+ <Modal.Body>
+ <DialogEpisode episode={episode} />
+ <Form.Group controlId="episodes.channel_id">
+ <Form.Label>{t('episodes.channel')}</Form.Label>
+ <Form.Select
+ isInvalid={!!(touched.channel_id && errors.channel_id)}
+ name="channel_id"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.channel_id || 0}
+ >
+ <option disabled value={0}>{t('general.pleaseSelect')}</option>
+ {((user && user.channel_crews) || []).sort(channelCompare).map(c =>
+ <option key={c.id} value={c.channel_id}>
+ {c.channel.title}
+ </option>
+ )}
+ </Form.Select>
+ {touched.channel_id && errors.channel_id ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.channel_id)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Row>
+ <Form.Group as={Col} sm={6} controlId="episodes.accept_comms">
+ <Form.Label className="d-block">
+ {t('episodes.restreamDialog.acceptComms')}
+ </Form.Label>
+ <Form.Control
+ as={ToggleSwitch}
+ isInvalid={!!(touched.accept_comms && errors.accept_comms)}
+ name="accept_comms"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={!!values.accept_comms}
+ />
+ {touched.accept_comms && errors.accept_comms ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.accept_comms)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group as={Col} sm={6} controlId="episodes.accept_tracker">
+ <Form.Label className="d-block">
+ {t('episodes.restreamDialog.acceptTracker')}
+ </Form.Label>
+ <Form.Control
+ as={ToggleSwitch}
+ isInvalid={!!(touched.accept_tracker && errors.accept_tracker)}
+ name="accept_tracker"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={!!values.accept_tracker}
+ />
+ {touched.accept_tracker && errors.accept_tracker ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.accept_tracker)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {t('button.cancel')}
+ </Button>
+ : null}
+ <Button type="submit" variant="primary">
+ {t('button.save')}
+ </Button>
+ </Modal.Footer>
+ </Form>;
+};
+
+RestreamAddForm.propTypes = {
+ episode: PropTypes.shape({
+ event: PropTypes.shape({
+ title: PropTypes.string,
+ }),
+ players: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ start: PropTypes.string,
+ }),
+ errors: PropTypes.shape({
+ accept_comms: PropTypes.string,
+ accept_tracker: PropTypes.string,
+ channel_id: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ onCancel: PropTypes.func,
+ touched: PropTypes.shape({
+ accept_comms: PropTypes.bool,
+ accept_tracker: PropTypes.bool,
+ channel_id: PropTypes.bool,
+ }),
+ user: PropTypes.shape({
+ channel_crews: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ }),
+ values: PropTypes.shape({
+ accept_comms: PropTypes.bool,
+ accept_tracker: PropTypes.bool,
+ channel_id: PropTypes.number,
+ }),
+};
+
+export default withUser(withFormik({
+ displayName: 'RestreamAddForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { setErrors } = actions;
+ const { onSubmit } = actions.props;
+ try {
+ await onSubmit(values);
+ } catch (e) {
+ if (e.response && e.response.data && e.response.data.errors) {
+ setErrors(laravelErrorsToFormik(e.response.data.errors));
+ }
+ }
+ },
+ mapPropsToValues: ({ episode, user }) => ({
+ accept_comms: false,
+ accept_tracker: false,
+ channel_id: user && user.channel_crews && user.channel_crews.length
+ ? user.channel_crews[0].channel_id : 0,
+ episode_id: episode ? episode.id : 0,
+ }),
+})(RestreamAddForm));
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Modal } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import RestreamAddForm from './RestreamAddForm';
-import RestreamEditForm from './RestreamEditForm';
-
-const RestreamDialog = ({
- channel,
- editRestream,
- episode,
- manageCrew,
- onHide,
- onRemoveRestream,
- onSubmit,
- show,
-}) => {
- const { t } = useTranslation();
-
- return <Modal className="restream-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {t('episodes.restreamDialog.title')}
- </Modal.Title>
- </Modal.Header>
- {channel ?
- <RestreamEditForm
- channel={channel}
- editRestream={editRestream}
- episode={episode}
- manageCrew={manageCrew}
- onCancel={onHide}
- onRemoveRestream={onRemoveRestream}
- />
- :
- <RestreamAddForm
- episode={episode}
- onCancel={onHide}
- onSubmit={onSubmit}
- />
- }
- </Modal>;
-};
-
-RestreamDialog.propTypes = {
- channel: PropTypes.shape({
- }),
- editRestream: PropTypes.func,
- episode: PropTypes.shape({
- }),
- manageCrew: PropTypes.func,
- onHide: PropTypes.func,
- onRemoveRestream: PropTypes.func,
- onSubmit: PropTypes.func,
- show: PropTypes.bool,
-};
-
-export default RestreamDialog;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import RestreamAddForm from './RestreamAddForm';
+import RestreamEditForm from './RestreamEditForm';
+
+const RestreamDialog = ({
+ channel,
+ editRestream,
+ episode,
+ manageCrew,
+ onHide,
+ onRemoveRestream,
+ onSubmit,
+ show,
+}) => {
+ const { t } = useTranslation();
+
+ return <Modal className="restream-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {t('episodes.restreamDialog.title')}
+ </Modal.Title>
+ </Modal.Header>
+ {channel ?
+ <RestreamEditForm
+ channel={channel}
+ editRestream={editRestream}
+ episode={episode}
+ manageCrew={manageCrew}
+ onCancel={onHide}
+ onRemoveRestream={onRemoveRestream}
+ />
+ :
+ <RestreamAddForm
+ episode={episode}
+ onCancel={onHide}
+ onSubmit={onSubmit}
+ />
+ }
+ </Modal>;
+};
+
+RestreamDialog.propTypes = {
+ channel: PropTypes.shape({
+ }),
+ editRestream: PropTypes.func,
+ episode: PropTypes.shape({
+ }),
+ manageCrew: PropTypes.func,
+ onHide: PropTypes.func,
+ onRemoveRestream: PropTypes.func,
+ onSubmit: PropTypes.func,
+ show: PropTypes.bool,
+};
+
+export default RestreamDialog;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import CrewManagement from './CrewManagement';
-import ToggleSwitch from '../common/ToggleSwitch';
-import { getName } from '../../helpers/Crew';
-
-const RestreamEditForm = ({
- channel,
- editRestream,
- episode,
- manageCrew,
- onCancel,
- onRemoveRestream,
-}) => {
- const { t } = useTranslation();
-
- const acceptToggle = React.useCallback(e => {
- editRestream({
- channel_id: channel.id,
- episode_id: episode.id,
- [e.target.name]: e.target.value,
- });
- }, [channel, editRestream, episode]);
-
- return <>
- <Modal.Body>
- {channel ?
- <div>
- {channel.title}
- </div>
- : null}
- {episode ? <>
- <div>
- {episode.event.title}
- </div>
- <div>
- {t('episodes.startTime', { date: new Date(episode.start) })}
- </div>
- <div>
- {episode.players.map(p => getName(p)).join(', ')}
- </div>
- </> : null}
- {channel && episode && editRestream ?
- <Row>
- <Form.Group as={Col} sm={6} controlId="episodes.accept_comms">
- <Form.Label className="d-block">
- {t('episodes.restreamDialog.acceptComms')}
- </Form.Label>
- <Form.Control
- as={ToggleSwitch}
- name="accept_comms"
- onChange={acceptToggle}
- value={!!channel.pivot.accept_comms}
- />
- </Form.Group>
- <Form.Group as={Col} sm={6} controlId="episodes.accept_tracker">
- <Form.Label className="d-block">
- {t('episodes.restreamDialog.acceptTracker')}
- </Form.Label>
- <Form.Control
- as={ToggleSwitch}
- name="accept_tracker"
- onChange={acceptToggle}
- value={!!channel.pivot.accept_tracker}
- />
- </Form.Group>
- </Row>
- : null}
- {channel && episode && manageCrew ? <>
- <CrewManagement
- channel={channel}
- episode={episode}
- manageCrew={manageCrew}
- role="commentary"
- />
- <CrewManagement
- channel={channel}
- episode={episode}
- manageCrew={manageCrew}
- role="tracking"
- />
- <CrewManagement
- channel={channel}
- episode={episode}
- manageCrew={manageCrew}
- role="setup"
- />
- </> : null}
- </Modal.Body>
- <Modal.Footer className="justify-content-between">
- {onRemoveRestream ?
- <Button onClick={() => onRemoveRestream(episode, channel)} variant="outline-danger">
- {t('button.remove')}
- </Button>
- : null}
- {onCancel ?
- <Button onClick={onCancel} variant="secondary">
- {t('button.close')}
- </Button>
- : null}
- </Modal.Footer>
- </>;
-};
-
-RestreamEditForm.propTypes = {
- channel: PropTypes.shape({
- id: PropTypes.number,
- pivot: PropTypes.shape({
- accept_comms: PropTypes.bool,
- accept_tracker: PropTypes.bool,
- }),
- title: PropTypes.string,
- }),
- editRestream: PropTypes.func,
- episode: PropTypes.shape({
- crew: PropTypes.arrayOf(PropTypes.shape({
- channel_id: PropTypes.number,
- role: PropTypes.string,
- })),
- event: PropTypes.shape({
- title: PropTypes.string,
- }),
- id: PropTypes.number,
- players: PropTypes.arrayOf(PropTypes.shape({
- })),
- start: PropTypes.string,
- }),
- manageCrew: PropTypes.func,
- onCancel: PropTypes.func,
- onRemoveRestream: PropTypes.func,
-};
-
-export default RestreamEditForm;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import CrewManagement from './CrewManagement';
+import ToggleSwitch from '../common/ToggleSwitch';
+import { getName } from '../../helpers/Crew';
+
+const RestreamEditForm = ({
+ channel,
+ editRestream,
+ episode,
+ manageCrew,
+ onCancel,
+ onRemoveRestream,
+}) => {
+ const { t } = useTranslation();
+
+ const acceptToggle = React.useCallback(e => {
+ editRestream({
+ channel_id: channel.id,
+ episode_id: episode.id,
+ [e.target.name]: e.target.value,
+ });
+ }, [channel, editRestream, episode]);
+
+ return <>
+ <Modal.Body>
+ {channel ?
+ <div>
+ {channel.title}
+ </div>
+ : null}
+ {episode ? <>
+ <div>
+ {episode.event.title}
+ </div>
+ <div>
+ {t('episodes.startTime', { date: new Date(episode.start) })}
+ </div>
+ <div>
+ {episode.players.map(p => getName(p)).join(', ')}
+ </div>
+ </> : null}
+ {channel && episode && editRestream ?
+ <Row>
+ <Form.Group as={Col} sm={6} controlId="episodes.accept_comms">
+ <Form.Label className="d-block">
+ {t('episodes.restreamDialog.acceptComms')}
+ </Form.Label>
+ <Form.Control
+ as={ToggleSwitch}
+ name="accept_comms"
+ onChange={acceptToggle}
+ value={!!channel.pivot.accept_comms}
+ />
+ </Form.Group>
+ <Form.Group as={Col} sm={6} controlId="episodes.accept_tracker">
+ <Form.Label className="d-block">
+ {t('episodes.restreamDialog.acceptTracker')}
+ </Form.Label>
+ <Form.Control
+ as={ToggleSwitch}
+ name="accept_tracker"
+ onChange={acceptToggle}
+ value={!!channel.pivot.accept_tracker}
+ />
+ </Form.Group>
+ </Row>
+ : null}
+ {channel && episode && manageCrew ? <>
+ <CrewManagement
+ channel={channel}
+ episode={episode}
+ manageCrew={manageCrew}
+ role="commentary"
+ />
+ <CrewManagement
+ channel={channel}
+ episode={episode}
+ manageCrew={manageCrew}
+ role="tracking"
+ />
+ <CrewManagement
+ channel={channel}
+ episode={episode}
+ manageCrew={manageCrew}
+ role="setup"
+ />
+ </> : null}
+ </Modal.Body>
+ <Modal.Footer className="justify-content-between">
+ {onRemoveRestream ?
+ <Button onClick={() => onRemoveRestream(episode, channel)} variant="outline-danger">
+ {t('button.remove')}
+ </Button>
+ : null}
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {t('button.close')}
+ </Button>
+ : null}
+ </Modal.Footer>
+ </>;
+};
+
+RestreamEditForm.propTypes = {
+ channel: PropTypes.shape({
+ id: PropTypes.number,
+ pivot: PropTypes.shape({
+ accept_comms: PropTypes.bool,
+ accept_tracker: PropTypes.bool,
+ }),
+ title: PropTypes.string,
+ }),
+ editRestream: PropTypes.func,
+ episode: PropTypes.shape({
+ crew: PropTypes.arrayOf(PropTypes.shape({
+ channel_id: PropTypes.number,
+ role: PropTypes.string,
+ })),
+ event: PropTypes.shape({
+ title: PropTypes.string,
+ }),
+ id: PropTypes.number,
+ players: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ start: PropTypes.string,
+ }),
+ manageCrew: PropTypes.func,
+ onCancel: PropTypes.func,
+ onRemoveRestream: PropTypes.func,
+};
+
+export default RestreamEditForm;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Alert, Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Icon from '../common/Icon';
-import RawHTML from '../common/RawHTML';
-import { hasConcluded } from '../../helpers/Event';
-import { getTranslation } from '../../helpers/Technique';
-import i18n from '../../i18n';
-
-const Detail = ({ actions, event }) => {
- const { t } = useTranslation();
-
- return <>
- <div className="d-flex align-items-center justify-content-between">
- <h1>
- {(event.description && getTranslation(event.description, 'title', i18n.language))
- || event.title}
- </h1>
- {event.description && actions.editContent ?
- <Button
- className="ms-3"
- onClick={() => actions.editContent(event.description)}
- size="sm"
- title={t('button.edit')}
- variant="outline-secondary"
- >
- <Icon.EDIT title="" />
- </Button>
- : null}
- </div>
- {event.description ?
- <RawHTML html={getTranslation(event.description, 'description', i18n.language)} />
- : null}
- {hasConcluded(event) ?
- <Alert variant="info">
- {t('events.concluded')}
- </Alert>
- : null}
- </>;
-};
-
-Detail.propTypes = {
- actions: PropTypes.shape({
- editContent: PropTypes.func,
- }),
- event: PropTypes.shape({
- description: PropTypes.shape({
- }),
- end: PropTypes.string,
- title: PropTypes.string,
- }),
-};
-
-export default Detail;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Alert, Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Icon from '../common/Icon';
+import RawHTML from '../common/RawHTML';
+import { hasConcluded } from '../../helpers/Event';
+import { getTranslation } from '../../helpers/Technique';
+import i18n from '../../i18n';
+
+const Detail = ({ actions, event }) => {
+ const { t } = useTranslation();
+
+ return <>
+ <div className="d-flex align-items-center justify-content-between">
+ <h1>
+ {(event.description && getTranslation(event.description, 'title', i18n.language))
+ || event.title}
+ </h1>
+ {event.description && actions.editContent ?
+ <Button
+ className="ms-3"
+ onClick={() => actions.editContent(event.description)}
+ size="sm"
+ title={t('button.edit')}
+ variant="outline-secondary"
+ >
+ <Icon.EDIT title="" />
+ </Button>
+ : null}
+ </div>
+ {event.description ?
+ <RawHTML html={getTranslation(event.description, 'description', i18n.language)} />
+ : null}
+ {hasConcluded(event) ?
+ <Alert variant="info">
+ {t('events.concluded')}
+ </Alert>
+ : null}
+ </>;
+};
+
+Detail.propTypes = {
+ actions: PropTypes.shape({
+ editContent: PropTypes.func,
+ }),
+ event: PropTypes.shape({
+ description: PropTypes.shape({
+ }),
+ end: PropTypes.string,
+ title: PropTypes.string,
+ }),
+};
+
+export default Detail;
+++ /dev/null
-import moment from 'moment';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-import { Link } from 'react-router-dom';
-
-import RawHTML from '../common/RawHTML';
-import {
- getLink,
-} from '../../helpers/Event';
-import {
- getTranslation,
-} from '../../helpers/Technique';
-import i18n from '../../i18n';
-
-const Item = ({ event }) => {
- const { t } = useTranslation();
-
- const style = React.useMemo(() => {
- if (event && event.corner) {
- return {
- backgroundImage: `url(${event.corner})`,
- };
- }
- return null;
- }, [event && event.corner]);
-
- return <li className="events-item my-3 p-2 pb-5 border rounded" style={style}>
- <h3>
- <Link to={getLink(event)}>
- {(event.description && getTranslation(event.description, 'title', i18n.language))
- || event.title}
- </Link>
- </h3>
- <div className="d-flex align-items-start justify-content-start">
- {event.start || event.end ?
- <div className="event-pane">
- {event.start ? <>
- <div><small>{t('events.start')}</small></div>
- <div className="mb-2">{moment(event.start).format('LL')}</div>
- </> : null}
- {event.end ? <>
- <div><small>{t('events.end')}</small></div>
- <div className="mb-2">{moment(event.end).format('LL')}</div>
- </> : null}
- </div>
- : null}
- {event.description?
- <div>
- <RawHTML
- html={getTranslation(event.description, 'description', i18n.language)}
- />
- </div>
- : null}
- </div>
- </li>;
-};
-
-Item.propTypes = {
- event: PropTypes.shape({
- corner: PropTypes.string,
- description: PropTypes.shape({
- }),
- end: PropTypes.string,
- id: PropTypes.number,
- name: PropTypes.string,
- start: PropTypes.string,
- title: PropTypes.string,
- }),
-};
-
-export default Item;
--- /dev/null
+import moment from 'moment';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { Link } from 'react-router-dom';
+
+import RawHTML from '../common/RawHTML';
+import {
+ getLink,
+} from '../../helpers/Event';
+import {
+ getTranslation,
+} from '../../helpers/Technique';
+import i18n from '../../i18n';
+
+const Item = ({ event }) => {
+ const { t } = useTranslation();
+
+ const style = React.useMemo(() => {
+ if (event && event.corner) {
+ return {
+ backgroundImage: `url(${event.corner})`,
+ };
+ }
+ return null;
+ }, [event && event.corner]);
+
+ return <li className="events-item my-3 p-2 pb-5 border rounded" style={style}>
+ <h3>
+ <Link to={getLink(event)}>
+ {(event.description && getTranslation(event.description, 'title', i18n.language))
+ || event.title}
+ </Link>
+ </h3>
+ <div className="d-flex align-items-start justify-content-start">
+ {event.start || event.end ?
+ <div className="event-pane">
+ {event.start ? <>
+ <div><small>{t('events.start')}</small></div>
+ <div className="mb-2">{moment(event.start).format('LL')}</div>
+ </> : null}
+ {event.end ? <>
+ <div><small>{t('events.end')}</small></div>
+ <div className="mb-2">{moment(event.end).format('LL')}</div>
+ </> : null}
+ </div>
+ : null}
+ {event.description?
+ <div>
+ <RawHTML
+ html={getTranslation(event.description, 'description', i18n.language)}
+ />
+ </div>
+ : null}
+ </div>
+ </li>;
+};
+
+Item.propTypes = {
+ event: PropTypes.shape({
+ corner: PropTypes.string,
+ description: PropTypes.shape({
+ }),
+ end: PropTypes.string,
+ id: PropTypes.number,
+ name: PropTypes.string,
+ start: PropTypes.string,
+ title: PropTypes.string,
+ }),
+};
+
+export default Item;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Item from './Item';
-
-const List = ({ events }) => <ul className="event-list">
- {events.map(event =>
- <Item event={event} key={event.id} />
- )}
-</ul>;
-
-List.propTypes = {
- events: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.number,
- name: PropTypes.string,
- })),
-};
-
-export default List;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Item from './Item';
+
+const List = ({ events }) => <ul className="event-list">
+ {events.map(event =>
+ <Item event={event} key={event.id} />
+ )}
+</ul>;
+
+List.propTypes = {
+ events: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.number,
+ name: PropTypes.string,
+ })),
+};
+
+export default List;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Form } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import { useOpenSeadragon } from './OpenSeadragon';
-
-const Buttons = ({ setUWOverlay, uwOverlay }) => {
- const { activeMap, setActiveMap } = useOpenSeadragon();
- const { t } = useTranslation();
-
- return <div className="mt-5">
- <div className="button-bar">
- {['lw', 'dw', 'sp', 'uw', 'uw2'].map(map =>
- <Button
- active={activeMap === map}
- key={map}
- onClick={() => setActiveMap(map)}
- title={t(`map.${map}Long`)}
- variant="outline-secondary"
- >
- {t(`map.${map}Short`)}
- </Button>
- )}
- </div>
- {activeMap === 'uw' ?
- <div className="mt-2">
- <Form.Check
- checked={uwOverlay}
- id="toggle-uw-overlay"
- inline
- onChange={e => setUWOverlay(e.target.checked)}
- type="checkbox"
- />
- <Form.Label className="mt-0" htmlFor="toggle-uw-overlay">
- {t('map.uwOverlay')}
- </Form.Label>
- </div>
- : null}
- </div>;
-};
-
-Buttons.propTypes = {
- setUWOverlay: PropTypes.func,
- uwOverlay: PropTypes.bool,
-};
-
-export default Buttons;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Form } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import { useOpenSeadragon } from './OpenSeadragon';
+
+const Buttons = ({ setUWOverlay, uwOverlay }) => {
+ const { activeMap, setActiveMap } = useOpenSeadragon();
+ const { t } = useTranslation();
+
+ return <div className="mt-5">
+ <div className="button-bar">
+ {['lw', 'dw', 'sp', 'uw', 'uw2'].map(map =>
+ <Button
+ active={activeMap === map}
+ key={map}
+ onClick={() => setActiveMap(map)}
+ title={t(`map.${map}Long`)}
+ variant="outline-secondary"
+ >
+ {t(`map.${map}Short`)}
+ </Button>
+ )}
+ </div>
+ {activeMap === 'uw' ?
+ <div className="mt-2">
+ <Form.Check
+ checked={uwOverlay}
+ id="toggle-uw-overlay"
+ inline
+ onChange={e => setUWOverlay(e.target.checked)}
+ type="checkbox"
+ />
+ <Form.Label className="mt-0" htmlFor="toggle-uw-overlay">
+ {t('map.uwOverlay')}
+ </Form.Label>
+ </div>
+ : null}
+ </div>;
+};
+
+Buttons.propTypes = {
+ setUWOverlay: PropTypes.func,
+ uwOverlay: PropTypes.bool,
+};
+
+export default Buttons;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-import { Link, useSearchParams } from 'react-router-dom';
-
-import { useOpenSeadragon } from './OpenSeadragon';
-import Icon from '../common/Icon';
-import Rulesets from '../techniques/Rulesets';
-import {
- getLink,
- getRelations,
- getTranslation,
- hasRelations,
- sorted,
-} from '../../helpers/Technique';
-import i18n from '../../i18n';
-
-const Item = ({ pin }) => {
- const { viewer } = useOpenSeadragon();
- const [, setSearchParams] = useSearchParams();
- const { t } = useTranslation();
-
- const goToLocation = React.useCallback(pin => {
- setSearchParams({ x: pin.x, y: pin.y, z: 4 });
- if (viewer && viewer.element) {
- viewer.element.scrollIntoView();
- }
- }, [viewer]);
-
- return <li className="d-flex align-items-start justify-content-between">
- <div className="flex-grow-1">
- {pin.technique.type === 'location' ? <>
- <h2>{getTranslation(pin.technique, 'title', i18n.language)}</h2>
- <p>{getTranslation(pin.technique, 'short', i18n.language)}</p>
- {hasRelations(pin.technique, 'related') ?
- sorted(getRelations(pin.technique, 'related')).map(r =>
- <div
- className="d-flex align-items-start justify-content-between"
- key={r.id}
- >
- <div className="me-auto">
- <h3>
- <Link to={getLink(r)}>
- {getTranslation(r, 'title', i18n.language)}
- </Link>
- </h3>
- <p>{getTranslation(r, 'short', i18n.language)}</p>
- </div>
- {r.rulesets ?
- <Rulesets technique={r} />
- : null}
- </div>
- )
- : null}
- </> : <div className="d-flex align-items-start justify-content-between">
- <div className="flex-grow-1">
- <h2>
- <Link to={getLink(pin.technique)}>
- {getTranslation(pin.technique, 'title', i18n.language)}
- </Link>
- </h2>
- <p>{getTranslation(pin.technique, 'short', i18n.language)}</p>
- </div>
- {pin.technique.rulesets ?
- <Rulesets technique={pin.technique} />
- : null}
- </div>}
- </div>
- <Button
- className="m-2"
- onClick={() => goToLocation(pin)}
- title={t('map.goToLocation')}
- variant="outline-secondary"
- >
- <Icon.CROSSHAIRS title="" />
- </Button>
- </li>;
-};
-
-Item.propTypes = {
- pin: PropTypes.shape({
- technique: PropTypes.shape({
- rulesets: PropTypes.shape({
- }),
- type: PropTypes.string,
- }),
- x: PropTypes.number,
- y: PropTypes.number,
- }),
-};
-
-export default Item;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+import { Link, useSearchParams } from 'react-router-dom';
+
+import { useOpenSeadragon } from './OpenSeadragon';
+import Icon from '../common/Icon';
+import Rulesets from '../techniques/Rulesets';
+import {
+ getLink,
+ getRelations,
+ getTranslation,
+ hasRelations,
+ sorted,
+} from '../../helpers/Technique';
+import i18n from '../../i18n';
+
+const Item = ({ pin }) => {
+ const { viewer } = useOpenSeadragon();
+ const [, setSearchParams] = useSearchParams();
+ const { t } = useTranslation();
+
+ const goToLocation = React.useCallback(pin => {
+ setSearchParams({ x: pin.x, y: pin.y, z: 4 });
+ if (viewer && viewer.element) {
+ viewer.element.scrollIntoView();
+ }
+ }, [viewer]);
+
+ return <li className="d-flex align-items-start justify-content-between">
+ <div className="flex-grow-1">
+ {pin.technique.type === 'location' ? <>
+ <h2>{getTranslation(pin.technique, 'title', i18n.language)}</h2>
+ <p>{getTranslation(pin.technique, 'short', i18n.language)}</p>
+ {hasRelations(pin.technique, 'related') ?
+ sorted(getRelations(pin.technique, 'related')).map(r =>
+ <div
+ className="d-flex align-items-start justify-content-between"
+ key={r.id}
+ >
+ <div className="me-auto">
+ <h3>
+ <Link to={getLink(r)}>
+ {getTranslation(r, 'title', i18n.language)}
+ </Link>
+ </h3>
+ <p>{getTranslation(r, 'short', i18n.language)}</p>
+ </div>
+ {r.rulesets ?
+ <Rulesets technique={r} />
+ : null}
+ </div>
+ )
+ : null}
+ </> : <div className="d-flex align-items-start justify-content-between">
+ <div className="flex-grow-1">
+ <h2>
+ <Link to={getLink(pin.technique)}>
+ {getTranslation(pin.technique, 'title', i18n.language)}
+ </Link>
+ </h2>
+ <p>{getTranslation(pin.technique, 'short', i18n.language)}</p>
+ </div>
+ {pin.technique.rulesets ?
+ <Rulesets technique={pin.technique} />
+ : null}
+ </div>}
+ </div>
+ <Button
+ className="m-2"
+ onClick={() => goToLocation(pin)}
+ title={t('map.goToLocation')}
+ variant="outline-secondary"
+ >
+ <Icon.CROSSHAIRS title="" />
+ </Button>
+ </li>;
+};
+
+Item.propTypes = {
+ pin: PropTypes.shape({
+ technique: PropTypes.shape({
+ rulesets: PropTypes.shape({
+ }),
+ type: PropTypes.string,
+ }),
+ x: PropTypes.number,
+ y: PropTypes.number,
+ }),
+};
+
+export default Item;
+++ /dev/null
-import React from 'react';
-import { Container } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Item from './Item';
-import { useOpenSeadragon } from './OpenSeadragon';
-import { compareTranslation } from '../../helpers/Technique';
-import i18n from '../../i18n';
-
-const List = () => {
- const { pins } = useOpenSeadragon();
- const { t } = useTranslation();
-
- const sortedPins = React.useMemo(() => {
- const compare = compareTranslation('title', i18n.language);
- return pins.sort((a, b) => compare(a.technique, b.technique));
- }, [pins, i18n.language]);
-
- if (!pins || !pins.length) return null;
-
- return <Container className="mt-3">
- <h2>{t('map.onThisMap')}</h2>
- <ul className="pin-list">
- {sortedPins.map(pin =>
- <Item key={pin.id} pin={pin} />
- )}
- </ul>
- </Container>;
-};
-
-export default List;
--- /dev/null
+import React from 'react';
+import { Container } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Item from './Item';
+import { useOpenSeadragon } from './OpenSeadragon';
+import { compareTranslation } from '../../helpers/Technique';
+import i18n from '../../i18n';
+
+const List = () => {
+ const { pins } = useOpenSeadragon();
+ const { t } = useTranslation();
+
+ const sortedPins = React.useMemo(() => {
+ const compare = compareTranslation('title', i18n.language);
+ return pins.sort((a, b) => compare(a.technique, b.technique));
+ }, [pins, i18n.language]);
+
+ if (!pins || !pins.length) return null;
+
+ return <Container className="mt-3">
+ <h2>{t('map.onThisMap')}</h2>
+ <ul className="pin-list">
+ {sortedPins.map(pin =>
+ <Item key={pin.id} pin={pin} />
+ )}
+ </ul>
+ </Container>;
+};
+
+export default List;
+++ /dev/null
-import axios from 'axios';
-import OpenSeadragon from 'openseadragon';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { useNavigate, useParams } from 'react-router';
-import { createSearchParams, useSearchParams } from 'react-router-dom';
-
-export const Context = React.createContext({});
-
-export const useOpenSeadragon = () => React.useContext(Context);
-
-export const Provider = ({ children, containerRef }) => {
- const { activeMap } = useParams();
- const navigate = useNavigate();
- const [searchParams, setSearchParams] = useSearchParams();
- const [pins, setPins] = React.useState([]);
- const [viewer, setViewer] = React.useState(null);
-
- const storePosition = React.useCallback(() => {
- if (!viewer || !viewer.viewport) return;
- const center = viewer.viewport.getCenter();
- const zoom = viewer.viewport.getZoom();
- setSearchParams({ x: center.x, y: center.y, z: zoom }, { replace: true });
- }, [setSearchParams, viewer]);
-
- const setActiveMap = React.useCallback(map => {
- if (viewer && viewer.viewport) {
- const center = viewer.viewport.getCenter();
- const zoom = viewer.viewport.getZoom();
- const params = { x: center.x, y: center.y, z: zoom };
- navigate(
- { pathname: `../${map}`, search: `?${createSearchParams(params)}` },
- { replace: true },
- );
- } else {
- navigate(`../${map}`, { replace: true });
- }
- }, [navigate, viewer]);
-
- React.useEffect(() => {
- if (!viewer || !viewer.viewport) return;
- if (searchParams.has('x') && searchParams.has('y')) {
- viewer.viewport.panTo(new OpenSeadragon.Point(
- parseFloat(searchParams.get('x')),
- parseFloat(searchParams.get('y')),
- ));
- }
- if (searchParams.has('z')) {
- viewer.viewport.zoomTo(parseFloat(searchParams.get('z')));
- }
- }, [searchParams, viewer]);
-
- React.useEffect(() => {
- if (!containerRef.current) return;
-
- const v = OpenSeadragon({
- element: containerRef.current,
- preserveViewport: true,
- sequenceMode: true,
- showNavigator: true,
- showNavigationControl: false,
- showSequenceControl: false,
- tileSources: [
- new OpenSeadragon.DziTileSource({
- width: 8192,
- height: 8192,
- tileSize: 256,
- tileOverlap: 0,
- minLevel: 8,
- maxLevel: 13,
- tilesUrl: '/media/alttp/map/lw_files/',
- fileFormat: 'png',
- }), new OpenSeadragon.DziTileSource({
- width: 8192,
- height: 8192,
- tileSize: 256,
- tileOverlap: 0,
- minLevel: 8,
- maxLevel: 13,
- tilesUrl: '/media/alttp/map/dw_files/',
- fileFormat: 'png',
- }), new OpenSeadragon.DziTileSource({
- width: 8192,
- height: 4096,
- tileSize: 256,
- tileOverlap: 0,
- minLevel: 8,
- maxLevel: 13,
- tilesUrl: '/media/alttp/map/sp_files/',
- fileFormat: 'png',
- }), new OpenSeadragon.DziTileSource({
- width: 16384,
- height: 16384,
- tileSize: 256,
- tileOverlap: 0,
- minLevel: 8,
- maxLevel: 14,
- tilesUrl: '/media/alttp/map/uw_files/',
- fileFormat: 'png',
- }), new OpenSeadragon.DziTileSource({
- width: 16384,
- height: 3072,
- tileSize: 256,
- tileOverlap: 0,
- minLevel: 8,
- maxLevel: 14,
- tilesUrl: '/media/alttp/map/uw2_files/',
- fileFormat: 'png',
- }),
- ],
- });
- v.addHandler('canvas-nonprimary-press', e => {
- if (e.button === 3) {
- navigate(-1);
- } else if (e.button === 4) {
- navigate(1);
- }
- });
- setViewer(v);
- return () => {
- v.destroy();
- };
- }, [containerRef.current]);
-
- React.useEffect(() => {
- if (!viewer) return;
- switch (activeMap) {
- case 'lw':
- viewer.goToPage(0);
- break;
- case 'dw':
- viewer.goToPage(1);
- break;
- case 'sp':
- viewer.goToPage(2);
- break;
- case 'uw':
- viewer.goToPage(3);
- break;
- case 'uw2':
- viewer.goToPage(4);
- break;
- }
- const controller = new AbortController();
- axios.get(`/api/markers/${activeMap}`, {
- signal: controller.signal,
- }).then(response => {
- setPins(response.data || []);
- }).catch(e => {
- if (!axios.isCancel(e)) {
- console.error(e);
- }
- });
- return () => {
- controller.abort();
- };
- }, [activeMap, viewer]);
-
- return <Context.Provider value={{ activeMap, pins, setActiveMap, storePosition, viewer }}>
- {children}
- </Context.Provider>;
-};
-
-Provider.displayName = 'OpenSeadragonProvider';
-
-Provider.propTypes = {
- children: PropTypes.node,
- containerRef: PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
-};
-
-export default Provider;
--- /dev/null
+import axios from 'axios';
+import OpenSeadragon from 'openseadragon';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useNavigate, useParams } from 'react-router';
+import { createSearchParams, useSearchParams } from 'react-router-dom';
+
+export const Context = React.createContext({});
+
+export const useOpenSeadragon = () => React.useContext(Context);
+
+export const Provider = ({ children, containerRef }) => {
+ const { activeMap } = useParams();
+ const navigate = useNavigate();
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [pins, setPins] = React.useState([]);
+ const [viewer, setViewer] = React.useState(null);
+
+ const storePosition = React.useCallback(() => {
+ if (!viewer || !viewer.viewport) return;
+ const center = viewer.viewport.getCenter();
+ const zoom = viewer.viewport.getZoom();
+ setSearchParams({ x: center.x, y: center.y, z: zoom }, { replace: true });
+ }, [setSearchParams, viewer]);
+
+ const setActiveMap = React.useCallback(map => {
+ if (viewer && viewer.viewport) {
+ const center = viewer.viewport.getCenter();
+ const zoom = viewer.viewport.getZoom();
+ const params = { x: center.x, y: center.y, z: zoom };
+ navigate(
+ { pathname: `../${map}`, search: `?${createSearchParams(params)}` },
+ { replace: true },
+ );
+ } else {
+ navigate(`../${map}`, { replace: true });
+ }
+ }, [navigate, viewer]);
+
+ React.useEffect(() => {
+ if (!viewer || !viewer.viewport) return;
+ if (searchParams.has('x') && searchParams.has('y')) {
+ viewer.viewport.panTo(new OpenSeadragon.Point(
+ parseFloat(searchParams.get('x')),
+ parseFloat(searchParams.get('y')),
+ ));
+ }
+ if (searchParams.has('z')) {
+ viewer.viewport.zoomTo(parseFloat(searchParams.get('z')));
+ }
+ }, [searchParams, viewer]);
+
+ React.useEffect(() => {
+ if (!containerRef.current) return;
+
+ const v = OpenSeadragon({
+ element: containerRef.current,
+ preserveViewport: true,
+ sequenceMode: true,
+ showNavigator: true,
+ showNavigationControl: false,
+ showSequenceControl: false,
+ tileSources: [
+ new OpenSeadragon.DziTileSource({
+ width: 8192,
+ height: 8192,
+ tileSize: 256,
+ tileOverlap: 0,
+ minLevel: 8,
+ maxLevel: 13,
+ tilesUrl: '/media/alttp/map/lw_files/',
+ fileFormat: 'png',
+ }), new OpenSeadragon.DziTileSource({
+ width: 8192,
+ height: 8192,
+ tileSize: 256,
+ tileOverlap: 0,
+ minLevel: 8,
+ maxLevel: 13,
+ tilesUrl: '/media/alttp/map/dw_files/',
+ fileFormat: 'png',
+ }), new OpenSeadragon.DziTileSource({
+ width: 8192,
+ height: 4096,
+ tileSize: 256,
+ tileOverlap: 0,
+ minLevel: 8,
+ maxLevel: 13,
+ tilesUrl: '/media/alttp/map/sp_files/',
+ fileFormat: 'png',
+ }), new OpenSeadragon.DziTileSource({
+ width: 16384,
+ height: 16384,
+ tileSize: 256,
+ tileOverlap: 0,
+ minLevel: 8,
+ maxLevel: 14,
+ tilesUrl: '/media/alttp/map/uw_files/',
+ fileFormat: 'png',
+ }), new OpenSeadragon.DziTileSource({
+ width: 16384,
+ height: 3072,
+ tileSize: 256,
+ tileOverlap: 0,
+ minLevel: 8,
+ maxLevel: 14,
+ tilesUrl: '/media/alttp/map/uw2_files/',
+ fileFormat: 'png',
+ }),
+ ],
+ });
+ v.addHandler('canvas-nonprimary-press', e => {
+ if (e.button === 3) {
+ navigate(-1);
+ } else if (e.button === 4) {
+ navigate(1);
+ }
+ });
+ setViewer(v);
+ return () => {
+ v.destroy();
+ };
+ }, [containerRef.current]);
+
+ React.useEffect(() => {
+ if (!viewer) return;
+ switch (activeMap) {
+ case 'lw':
+ viewer.goToPage(0);
+ break;
+ case 'dw':
+ viewer.goToPage(1);
+ break;
+ case 'sp':
+ viewer.goToPage(2);
+ break;
+ case 'uw':
+ viewer.goToPage(3);
+ break;
+ case 'uw2':
+ viewer.goToPage(4);
+ break;
+ }
+ const controller = new AbortController();
+ axios.get(`/api/markers/${activeMap}`, {
+ signal: controller.signal,
+ }).then(response => {
+ setPins(response.data || []);
+ }).catch(e => {
+ if (!axios.isCancel(e)) {
+ console.error(e);
+ }
+ });
+ return () => {
+ controller.abort();
+ };
+ }, [activeMap, viewer]);
+
+ return <Context.Provider value={{ activeMap, pins, setActiveMap, storePosition, viewer }}>
+ {children}
+ </Context.Provider>;
+};
+
+Provider.displayName = 'OpenSeadragonProvider';
+
+Provider.propTypes = {
+ children: PropTypes.node,
+ containerRef: PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
+};
+
+export default Provider;
+++ /dev/null
-import OpenSeadragon from 'openseadragon';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { createPortal } from 'react-dom';
-
-import { useOpenSeadragon } from './OpenSeadragon';
-
-const Overlay = ({ children, height, onClick, page, width, x, y }) => {
- const { viewer } = useOpenSeadragon();
- const [element] = React.useState(document.createElement('div'));
-
- React.useEffect(() => {
- if (!viewer) return;
- const add = () => {
- if (width && height) {
- viewer.addOverlay(
- element,
- new OpenSeadragon.Rect(x, y, width, height),
- );
- } else {
- viewer.addOverlay(
- element,
- new OpenSeadragon.Point(x, y),
- OpenSeadragon.Placement.CENTER,
- );
- }
- if (onClick) {
- new OpenSeadragon.MouseTracker({
- element,
- clickHandler: onClick,
- });
- }
- };
- const addPage = () => {
- if (viewer.currentPage() === page) {
- add();
- }
- };
- if (typeof page !== 'undefined') {
- viewer.addHandler('page', addPage);
- return () => {
- viewer.removeHandler('page', addPage);
- try {
- viewer.removeOverlay(element);
- } catch (e) {
- // bug in OSD?
- console.error(e);
- }
- };
- }
- if (viewer.isOpen()) {
- add();
- } else {
- viewer.addHandler('open', add);
- return () => {
- viewer.removeHandler('open', add);
- try {
- viewer.removeOverlay(element);
- } catch (e) {
- // bug in OSD?
- console.error(e);
- }
- };
- }
- }, [onClick, height, page, viewer, width, x, y]);
-
- return createPortal(children, element);
-};
-
-Overlay.propTypes = {
- children: PropTypes.node,
- height: PropTypes.number,
- onClick: PropTypes.func,
- page: PropTypes.number,
- width: PropTypes.number,
- x: PropTypes.number,
- y: PropTypes.number,
-};
-
-export default Overlay;
--- /dev/null
+import OpenSeadragon from 'openseadragon';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { createPortal } from 'react-dom';
+
+import { useOpenSeadragon } from './OpenSeadragon';
+
+const Overlay = ({ children, height, onClick, page, width, x, y }) => {
+ const { viewer } = useOpenSeadragon();
+ const [element] = React.useState(document.createElement('div'));
+
+ React.useEffect(() => {
+ if (!viewer) return;
+ const add = () => {
+ if (width && height) {
+ viewer.addOverlay(
+ element,
+ new OpenSeadragon.Rect(x, y, width, height),
+ );
+ } else {
+ viewer.addOverlay(
+ element,
+ new OpenSeadragon.Point(x, y),
+ OpenSeadragon.Placement.CENTER,
+ );
+ }
+ if (onClick) {
+ new OpenSeadragon.MouseTracker({
+ element,
+ clickHandler: onClick,
+ });
+ }
+ };
+ const addPage = () => {
+ if (viewer.currentPage() === page) {
+ add();
+ }
+ };
+ if (typeof page !== 'undefined') {
+ viewer.addHandler('page', addPage);
+ return () => {
+ viewer.removeHandler('page', addPage);
+ try {
+ viewer.removeOverlay(element);
+ } catch (e) {
+ // bug in OSD?
+ console.error(e);
+ }
+ };
+ }
+ if (viewer.isOpen()) {
+ add();
+ } else {
+ viewer.addHandler('open', add);
+ return () => {
+ viewer.removeHandler('open', add);
+ try {
+ viewer.removeOverlay(element);
+ } catch (e) {
+ // bug in OSD?
+ console.error(e);
+ }
+ };
+ }
+ }, [onClick, height, page, viewer, width, x, y]);
+
+ return createPortal(children, element);
+};
+
+Overlay.propTypes = {
+ children: PropTypes.node,
+ height: PropTypes.number,
+ onClick: PropTypes.func,
+ page: PropTypes.number,
+ width: PropTypes.number,
+ x: PropTypes.number,
+ y: PropTypes.number,
+};
+
+export default Overlay;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Link, useNavigate } from 'react-router-dom';
-
-import { useOpenSeadragon } from './OpenSeadragon';
-import Overlay from './Overlay';
-import Popover from './Popover';
-import Icon from '../common/Icon';
-import ZeldaIcon from '../common/ZeldaIcon';
-import { getLink, getTranslation } from '../../helpers/Technique';
-import i18n from '../../i18n';
-
-const Pin = ({ pin }) => {
- const { storePosition } = useOpenSeadragon();
- const [showPopover, setShowPopover] = React.useState(false);
- const ref = React.useRef();
-
- const navigate = useNavigate();
-
- const onClick = React.useCallback((e) => {
- if (ref.current && ref.current.contains(e.originalTarget)) {
- if (e.originalTarget.tagName === 'A') {
- storePosition();
- navigate(new URL(e.originalTarget.href).pathname);
- }
- } else {
- if (pin.technique.type === 'location') {
- setShowPopover(s => !s);
- } else {
- storePosition();
- navigate(getLink(pin.technique));
- }
- }
- }, [pin]);
-
- const title = React.useMemo(() => {
- return getTranslation(pin.technique, 'title', i18n.language);
- }, [pin, i18n.language]);
-
- return <Overlay onClick={onClick} x={pin.x} y={pin.y}>
- <div className="map-pin">
- <Link to={getLink(pin.technique)}>
- {pin.marker ?
- <ZeldaIcon title={title} name={pin.marker} />
- :
- <Icon.PIN title={title} />
- }
- </Link>
- {pin.technique.type === 'location' ?
- <div ref={ref}>
- <Popover show={showPopover} technique={pin.technique} />
- </div>
- : null}
- </div>
- </Overlay>;
-};
-
-Pin.propTypes = {
- pin: PropTypes.shape({
- marker: PropTypes.string,
- technique: PropTypes.shape({
- type: PropTypes.string,
- }),
- x: PropTypes.number,
- y: PropTypes.number,
- }),
-};
-
-export default Pin;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+
+import { useOpenSeadragon } from './OpenSeadragon';
+import Overlay from './Overlay';
+import Popover from './Popover';
+import Icon from '../common/Icon';
+import ZeldaIcon from '../common/ZeldaIcon';
+import { getLink, getTranslation } from '../../helpers/Technique';
+import i18n from '../../i18n';
+
+const Pin = ({ pin }) => {
+ const { storePosition } = useOpenSeadragon();
+ const [showPopover, setShowPopover] = React.useState(false);
+ const ref = React.useRef();
+
+ const navigate = useNavigate();
+
+ const onClick = React.useCallback((e) => {
+ if (ref.current && ref.current.contains(e.originalTarget)) {
+ if (e.originalTarget.tagName === 'A') {
+ storePosition();
+ navigate(new URL(e.originalTarget.href).pathname);
+ }
+ } else {
+ if (pin.technique.type === 'location') {
+ setShowPopover(s => !s);
+ } else {
+ storePosition();
+ navigate(getLink(pin.technique));
+ }
+ }
+ }, [pin]);
+
+ const title = React.useMemo(() => {
+ return getTranslation(pin.technique, 'title', i18n.language);
+ }, [pin, i18n.language]);
+
+ return <Overlay onClick={onClick} x={pin.x} y={pin.y}>
+ <div className="map-pin">
+ <Link to={getLink(pin.technique)}>
+ {pin.marker ?
+ <ZeldaIcon title={title} name={pin.marker} />
+ :
+ <Icon.PIN title={title} />
+ }
+ </Link>
+ {pin.technique.type === 'location' ?
+ <div ref={ref}>
+ <Popover show={showPopover} technique={pin.technique} />
+ </div>
+ : null}
+ </div>
+ </Overlay>;
+};
+
+Pin.propTypes = {
+ pin: PropTypes.shape({
+ marker: PropTypes.string,
+ technique: PropTypes.shape({
+ type: PropTypes.string,
+ }),
+ x: PropTypes.number,
+ y: PropTypes.number,
+ }),
+};
+
+export default Pin;
+++ /dev/null
-import React from 'react';
-
-import { useOpenSeadragon } from './OpenSeadragon';
-import Pin from './Pin';
-
-const Pins = () => {
- const { pins } = useOpenSeadragon();
-
- return pins.map(pin =>
- <Pin key={pin.id} pin={pin} />
- );
-};
-
-export default Pins;
--- /dev/null
+import React from 'react';
+
+import { useOpenSeadragon } from './OpenSeadragon';
+import Pin from './Pin';
+
+const Pins = () => {
+ const { pins } = useOpenSeadragon();
+
+ return pins.map(pin =>
+ <Pin key={pin.id} pin={pin} />
+ );
+};
+
+export default Pins;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Card, ListGroup } from 'react-bootstrap';
-import { Link } from 'react-router-dom';
-
-import ZeldaIcon from '../common/ZeldaIcon';
-
-import {
- getLink,
- getRelations,
- getTranslation,
- hasRelations,
- sorted,
-} from '../../helpers/Technique';
-import i18n from '../../i18n';
-
-const Popover = ({ show, technique }) =>
- <div className={`map-popover ${show ? 'shown' : 'hidden'}`}>
- <Card bg="dark">
- <Card.Header>
- <Card.Title>
- {getTranslation(technique, 'title', i18n.language)}
- </Card.Title>
- </Card.Header>
- {technique.short ?
- <Card.Body>
- <Card.Text>
- {getTranslation(technique, 'short', i18n.language)}
- </Card.Text>
- </Card.Body>
- : null}
- {hasRelations(technique, 'related') ?
- <ListGroup variant="flush">
- {sorted(getRelations(technique, 'related')).map(r =>
- <ListGroup.Item
- key={r.id}
- title={getTranslation(r, 'short', i18n.language)}
- >
- <Link to={getLink(r)}>
- {r.title_icons ?
- <span className="tech-title-icons">
- {r.title_icons.map(icon =>
- <ZeldaIcon key={icon} name={icon} />
- )}
- </span>
- : null}
- {getTranslation(r, 'title', i18n.language)}
- </Link>
- </ListGroup.Item>
- )}
- </ListGroup>
- : null}
- </Card>
- </div>;
-
-Popover.propTypes = {
- show: PropTypes.bool,
- technique: PropTypes.shape({
- short: PropTypes.string,
- }),
-};
-
-export default Popover;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Card, ListGroup } from 'react-bootstrap';
+import { Link } from 'react-router-dom';
+
+import ZeldaIcon from '../common/ZeldaIcon';
+
+import {
+ getLink,
+ getRelations,
+ getTranslation,
+ hasRelations,
+ sorted,
+} from '../../helpers/Technique';
+import i18n from '../../i18n';
+
+const Popover = ({ show, technique }) =>
+ <div className={`map-popover ${show ? 'shown' : 'hidden'}`}>
+ <Card bg="dark">
+ <Card.Header>
+ <Card.Title>
+ {getTranslation(technique, 'title', i18n.language)}
+ </Card.Title>
+ </Card.Header>
+ {technique.short ?
+ <Card.Body>
+ <Card.Text>
+ {getTranslation(technique, 'short', i18n.language)}
+ </Card.Text>
+ </Card.Body>
+ : null}
+ {hasRelations(technique, 'related') ?
+ <ListGroup variant="flush">
+ {sorted(getRelations(technique, 'related')).map(r =>
+ <ListGroup.Item
+ key={r.id}
+ title={getTranslation(r, 'short', i18n.language)}
+ >
+ <Link to={getLink(r)}>
+ {r.title_icons ?
+ <span className="tech-title-icons">
+ {r.title_icons.map(icon =>
+ <ZeldaIcon key={icon} name={icon} />
+ )}
+ </span>
+ : null}
+ {getTranslation(r, 'title', i18n.language)}
+ </Link>
+ </ListGroup.Item>
+ )}
+ </ListGroup>
+ : null}
+ </Card>
+ </div>;
+
+Popover.propTypes = {
+ show: PropTypes.bool,
+ technique: PropTypes.shape({
+ short: PropTypes.string,
+ }),
+};
+
+export default Popover;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { useSearchParams } from 'react-router-dom';
-
-import Overlay from './Overlay';
-import { useOpenSeadragon } from './OpenSeadragon';
-
-const dropMap = {
- '00': '10',
- '03': '02',
- '05': '05',
- '06': '05',
- '07': '17',
- '08': '07',
- '09': '4B',
- '0A': '09',
- '0B': '6A',
- '0D': '0B',
- '0F': '01',
- '10': '01',
- '12': '0D',
- '13': '0D',
- '14': '0D',
- '17': '27',
- '18': '12',
- '19': '07',
- '1E': '3E',
- '20': '01',
- '21': '0D',
- '22': '0D',
- '23': '0D',
- '24': '08',
- '27': '31',
- '29': '07',
- '2A': '07',
- '2C': '12',
- '2D': '06',
- '2E': '06',
- '2F': '02',
- '31': '77',
- '33': '08',
- '35': '08',
- '36': '08',
- '37': '08',
- '39': '29',
- '3A': '0A',
- '3C': '0E',
- '3D': '96',
- '43': '0A',
- '44': '0A',
- '46': '09',
- '47': '07',
- '48': '07',
- '49': '07',
- '4B': '09',
- '4D': 'A6',
- '4F': 'BE',
- '54': '34',
- '55': '09',
- '56': '09',
- '57': '09',
- '58': '09',
- '59': '07',
- '5A': '0E',
- '5B': '0E',
- '5E': '7E',
- '65': 'AC',
- '67': '09',
- '68': '07',
- '6D': '0B',
- '73': '05',
- '74': '05',
- '75': '08',
- '77': 'A7',
- '78': '9D',
- '79': '9D',
- '7A': '9D',
- '7B': '9D',
- '7C': '0E',
- '7D': '9B',
- '7E': '9E',
- '81': '05',
- '82': '05',
- '83': '05',
- '84': '05',
- '85': '05',
- '88': 'A9',
- '89': 'A9',
- '8A': '0E',
- '8B': '0E',
- '8C': '1C',
- '8D': '0B',
- '8F': '0C',
- '90': '0C',
- '92': '0C',
- '94': '0E',
- '95': '0E',
- '97': 'D1',
- '9A': '7D',
- '9B': '7D',
- '9C': '0E',
- '9D': '7B',
- '9E': 'BE',
- '9F': '0C',
- 'A1': '0C',
- 'A3': '0C',
- 'A4': '0E',
- 'A7': '17',
- 'A8': '05',
- 'A9': '89',
- 'AA': '0A',
- 'AC': '0B',
- 'AF': '02',
- 'B1': 'B2',
- 'B2': '0C',
- 'B3': '0D',
- 'B7': '0D',
- 'B8': '05',
- 'B9': '05',
- 'BA': '0A',
- 'BB': '0A',
- 'BD': '4F',
- 'BE': '4F',
- 'BF': '02',
- 'C1': '0C',
- 'C2': '0C',
- 'C3': '0D',
- 'C5': '0D',
- 'C6': '0D',
- 'C7': '05',
- 'C8': '05',
- 'C9': '0A',
- 'CA': '0B',
- 'CB': '0B',
- 'CC': '0B',
- 'CD': 'DE',
- 'CE': 'DE',
- 'D1': 'B1',
- 'D3': '05',
- 'D4': '05',
- 'D5': '0D',
- 'D6': '0D',
- 'D7': '05',
- 'D8': '05',
- 'D9': '05',
- 'DB': '0B',
- 'DC': '0B',
- 'DD': '06',
- 'DE': '06',
- 'E1': '06',
- 'E2': '06',
- 'E3': '14',
- 'E4': '06',
- 'E5': '06',
- 'E6': '06',
- 'E7': '06',
- 'E8': 'F8',
- 'E9': 'FA',
- 'EA': 'FA',
- 'EB': 'FB',
- 'EC': 'FD',
- 'ED': 'FD',
- 'EE': 'FE',
- 'EF': 'FF',
- 'F0': '06',
- 'F1': '06',
- 'F4': '06',
- 'F5': '06',
- 'F9': '06',
- 'FE': '06',
-};
-
-const strongEG = [
- '08',
- '0C',
- '15',
- '2F',
- '40',
- '51',
- '52',
- '59',
- '5B',
- '60',
- '62',
- '66',
- '71',
- '72',
- '81',
- 'A2',
- 'A8',
- 'A9',
- 'AA',
- 'B2',
- 'B3',
- 'B9',
- 'C2',
- 'C3',
- 'CB',
- 'CC',
- 'DB',
- 'DC',
- 'DF',
- 'E1',
- 'E3',
- 'FA',
-];
-
-const weakEG = [
- '07',
- '0A',
- '16',
- '28',
- '2A',
- '2B',
- '34',
- '35',
- '36',
- '37',
- '3A',
- '4D',
- '55',
- '61',
- '76',
- '99',
- 'A0',
- 'C9',
- 'E2',
- 'E4',
- 'F0',
- 'FD',
- 'FE',
-];
-
-const kick = [
- '1B',
- '29',
- '3E',
- '43',
- '97',
-];
-
-const dark = [
- '0B',
- '19',
- '21',
- '22',
- '32',
- '41',
- '42',
- '69',
- '6A',
- '92',
- '93',
- 'B5',
- 'BA',
- 'C0',
- 'D0',
- 'E5',
- 'E6',
- 'E7',
- 'F0',
- 'F1',
-];
-
-const camera = {
- '00': ['x', 'y'],
- '01': ['y'],
- '02': ['y'],
- '07': ['x', 'y'],
- '0A': ['x', 'y'],
- '0C': ['x', 'y'],
- '0D': ['x', 'y'],
- '0E': ['yu'],
- '10': ['x'],
- '11': ['x'],
- '12': ['x', 'y'],
- '13': ['x'],
- '14': ['x', 'y'],
- '15': ['x', 'y'],
- '16': ['y'],
- '17': ['x', 'y'],
- '18': ['x'],
- '19': ['xr'],
- '1A': ['xl'],
- '1B': ['yu'],
- '1D': ['y'],
- '1F': ['yu'],
- '20': ['x', 'y'],
- '21': ['y'],
- '22': ['y'],
- '26': ['yd'],
- '27': ['x', 'y'],
- '28': ['x', 'y'],
- '2A': ['x', 'y'],
- '2B': ['xl'],
- '2F': ['yd'],
- '31': ['yu'],
- '32': ['x', 'y'],
- '34': ['x', 'y'],
- '35': ['y'],
- '36': ['x', 'y'],
- '37': ['y'],
- '38': ['x'],
- '39': ['yu'],
- '3A': ['x', 'y'],
- '3B': ['x'],
- '3C': ['x', 'y'],
- '3E': ['yd'],
- '3F': ['yu'],
- '40': ['xl'],
- '41': ['x', 'y'],
- '42': ['y'],
- '43': ['yu'],
- '44': ['xr'],
- '45': ['xr'],
- '46': ['y'],
- '49': ['xr'],
- '4A': ['y'],
- '4B': ['yd'],
- '4C': ['x'],
- '4D': ['x', 'y'],
- '4E': ['yd'],
- '50': ['x'],
- '51': ['x', 'y'],
- '52': ['xl', 'yd'],
- '53': ['xr'],
- '54': ['x', 'y'],
- '55': ['y'],
- '56': ['xr'],
- '58': ['xr'],
- '59': ['x'],
- '5B': ['x'],
- '5C': ['yu'],
- '60': ['x'],
- '61': ['x', 'y'],
- '62': ['x', 'y'],
- '63': ['xr'],
- '64': ['yu'],
- '65': ['yu'],
- '66': ['yd'],
- '67': ['x'],
- '68': ['x', 'y'],
- '6A': ['x'],
- '6B': ['yu'],
- '6D': ['xr'],
- '72': ['y'],
- '74': ['y'],
- '75': ['xr'],
- '76': ['xl'],
- '77': ['x', 'y'],
- '7B': ['yu'],
- '7C': ['x'],
- '7D': ['yu'],
- '7E': ['xr'],
- '7F': ['xr'],
- '80': ['y'],
- '81': ['x', 'y'],
- '82': ['x', 'y'],
- '83': ['xr'],
- '84': ['x', 'y'],
- '85': ['xl'],
- '89': ['y'],
- '8B': ['xl'],
- '8D': ['xr'],
- '91': ['x'],
- '92': ['xr'],
- '93': ['yu'],
- '95': ['x'],
- '96': ['xl'],
- '97': ['xr'],
- '98': ['yd'],
- '99': ['yd'],
- '9B': ['yd'],
- '9C': ['x', 'y'],
- '9D': ['yd'],
- 'A0': ['y'],
- 'A1': ['xr', 'yu'],
- 'A2': ['x', 'y'],
- 'A3': ['x'],
- 'A5': ['yd'],
- 'A6': ['x', 'y'],
- 'A7': ['yd'],
- 'A8': ['xr'],
- 'A9': ['x', 'y'],
- 'AA': ['xl'],
- 'B1': ['xr'],
- 'B2': ['yu'],
- 'B3': ['x'],
- 'B4': ['x', 'y'],
- 'B5': ['x', 'y'],
- 'B7': ['x'],
- 'B8': ['x'],
- 'B9': ['x', 'y'],
- 'BB': ['xl'],
- 'BC': ['xr'],
- 'BE': ['xl'],
- 'C0': ['xl'],
- 'C2': ['x', 'y'],
- 'C3': ['x'],
- 'C4': ['x', 'y'],
- 'C5': ['x'],
- 'C6': ['x', 'y'],
- 'C7': ['x', 'y'],
- 'C9': ['y'],
- 'CB': ['x', 'y'],
- 'CC': ['x', 'y'],
- 'D0': ['xl'],
- 'D2': ['xr'],
- 'D5': ['x'],
- 'D6': ['x'],
- 'D8': ['xl'],
- 'D9': ['yu'],
- 'DA': ['yu'],
- 'DB': ['x', 'y'],
- 'DC': ['x', 'y'],
- 'DF': ['yd'],
- 'E1': ['x'],
- 'E2': ['x'],
- 'E4': ['x'],
- 'E5': ['x', 'y'],
- 'E6': ['xr', 'yd'],
- 'E7': ['xr', 'yu'],
- 'E8': ['x', 'yu'],
- 'EA': ['xl', 'yu'],
- 'EB': ['x'],
- 'ED': ['x', 'yu'],
- 'EE': ['x', 'y'],
- 'EF': ['yd'],
- 'F0': ['x', 'y'],
- 'F1': ['x', 'y'],
- 'F8': ['x', 'y'],
- 'F9': ['xr', 'yd'],
- 'FA': ['x', 'y'],
- 'FB': ['xr', 'yd'],
- 'FD': ['x', 'y'],
- 'FE': ['xr'],
- 'FF': ['yd'],
-};
-
-const getClassName = key => {
- const classNames = [];
- if (strongEG.includes(key)) {
- classNames.push('strong-eg');
- }
- if (weakEG.includes(key)) {
- classNames.push('weak-eg');
- }
- if (kick.includes(key)) {
- classNames.push('kick');
- }
- if (dark.includes(key)) {
- classNames.push('dark');
- }
- if (camera[key]) {
- camera[key].forEach(c => {
- classNames.push(`cam-${c}`);
- });
- }
- return classNames.join(' ');
-};
-
-const UWSuperTiles = ({ show }) => {
- const { storePosition, viewer } = useOpenSeadragon();
- const [, setSearchParams] = useSearchParams();
-
- const onClick = React.useCallback(e => {
- if (e.originalTarget.tagName !== 'A') return;
- if (e.originalTarget.className !== 'cell-link') return;
- const key = e.originalTarget.dataset.key;
-
- const x = (parseInt(key[1], 16) + 0.5) / 16;
- const y = (parseInt(key[0], 16) + 0.5) / 16;
- if (viewer && viewer.viewport) {
- storePosition();
- setSearchParams({ x, y, z: 4 });
- viewer.element.scrollIntoView();
- }
- }, [storePosition, viewer]);
-
- return <Overlay onClick={onClick} page={3} x={0} y={0} width={1} height={1}>
- <div className={`uw-super-tiles ${show ? '' : 'd-none'}`}>
- {[...Array(16).keys()].map(x =>
- [...Array(16).keys()].map(y => {
- const key = `${x.toString(16).toUpperCase()}${y.toString(16).toUpperCase()}`;
- return <div className={getClassName(key)} key={key}>
- <p className="cell-id">{key}</p>
- {dropMap[key] ?
- <p className="cell-drop">
- <a className="cell-link" data-key={dropMap[key]}>
- {`â–¶ ${dropMap[key]}`}
- </a>
- </p>
- : null}
- </div>;
- })
- )}
- </div>
- </Overlay>;
-};
-
-UWSuperTiles.propTypes = {
- show: PropTypes.bool,
-};
-
-export default UWSuperTiles;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useSearchParams } from 'react-router-dom';
+
+import Overlay from './Overlay';
+import { useOpenSeadragon } from './OpenSeadragon';
+
+const dropMap = {
+ '00': '10',
+ '03': '02',
+ '05': '05',
+ '06': '05',
+ '07': '17',
+ '08': '07',
+ '09': '4B',
+ '0A': '09',
+ '0B': '6A',
+ '0D': '0B',
+ '0F': '01',
+ '10': '01',
+ '12': '0D',
+ '13': '0D',
+ '14': '0D',
+ '17': '27',
+ '18': '12',
+ '19': '07',
+ '1E': '3E',
+ '20': '01',
+ '21': '0D',
+ '22': '0D',
+ '23': '0D',
+ '24': '08',
+ '27': '31',
+ '29': '07',
+ '2A': '07',
+ '2C': '12',
+ '2D': '06',
+ '2E': '06',
+ '2F': '02',
+ '31': '77',
+ '33': '08',
+ '35': '08',
+ '36': '08',
+ '37': '08',
+ '39': '29',
+ '3A': '0A',
+ '3C': '0E',
+ '3D': '96',
+ '43': '0A',
+ '44': '0A',
+ '46': '09',
+ '47': '07',
+ '48': '07',
+ '49': '07',
+ '4B': '09',
+ '4D': 'A6',
+ '4F': 'BE',
+ '54': '34',
+ '55': '09',
+ '56': '09',
+ '57': '09',
+ '58': '09',
+ '59': '07',
+ '5A': '0E',
+ '5B': '0E',
+ '5E': '7E',
+ '65': 'AC',
+ '67': '09',
+ '68': '07',
+ '6D': '0B',
+ '73': '05',
+ '74': '05',
+ '75': '08',
+ '77': 'A7',
+ '78': '9D',
+ '79': '9D',
+ '7A': '9D',
+ '7B': '9D',
+ '7C': '0E',
+ '7D': '9B',
+ '7E': '9E',
+ '81': '05',
+ '82': '05',
+ '83': '05',
+ '84': '05',
+ '85': '05',
+ '88': 'A9',
+ '89': 'A9',
+ '8A': '0E',
+ '8B': '0E',
+ '8C': '1C',
+ '8D': '0B',
+ '8F': '0C',
+ '90': '0C',
+ '92': '0C',
+ '94': '0E',
+ '95': '0E',
+ '97': 'D1',
+ '9A': '7D',
+ '9B': '7D',
+ '9C': '0E',
+ '9D': '7B',
+ '9E': 'BE',
+ '9F': '0C',
+ 'A1': '0C',
+ 'A3': '0C',
+ 'A4': '0E',
+ 'A7': '17',
+ 'A8': '05',
+ 'A9': '89',
+ 'AA': '0A',
+ 'AC': '0B',
+ 'AF': '02',
+ 'B1': 'B2',
+ 'B2': '0C',
+ 'B3': '0D',
+ 'B7': '0D',
+ 'B8': '05',
+ 'B9': '05',
+ 'BA': '0A',
+ 'BB': '0A',
+ 'BD': '4F',
+ 'BE': '4F',
+ 'BF': '02',
+ 'C1': '0C',
+ 'C2': '0C',
+ 'C3': '0D',
+ 'C5': '0D',
+ 'C6': '0D',
+ 'C7': '05',
+ 'C8': '05',
+ 'C9': '0A',
+ 'CA': '0B',
+ 'CB': '0B',
+ 'CC': '0B',
+ 'CD': 'DE',
+ 'CE': 'DE',
+ 'D1': 'B1',
+ 'D3': '05',
+ 'D4': '05',
+ 'D5': '0D',
+ 'D6': '0D',
+ 'D7': '05',
+ 'D8': '05',
+ 'D9': '05',
+ 'DB': '0B',
+ 'DC': '0B',
+ 'DD': '06',
+ 'DE': '06',
+ 'E1': '06',
+ 'E2': '06',
+ 'E3': '14',
+ 'E4': '06',
+ 'E5': '06',
+ 'E6': '06',
+ 'E7': '06',
+ 'E8': 'F8',
+ 'E9': 'FA',
+ 'EA': 'FA',
+ 'EB': 'FB',
+ 'EC': 'FD',
+ 'ED': 'FD',
+ 'EE': 'FE',
+ 'EF': 'FF',
+ 'F0': '06',
+ 'F1': '06',
+ 'F4': '06',
+ 'F5': '06',
+ 'F9': '06',
+ 'FE': '06',
+};
+
+const strongEG = [
+ '08',
+ '0C',
+ '15',
+ '2F',
+ '40',
+ '51',
+ '52',
+ '59',
+ '5B',
+ '60',
+ '62',
+ '66',
+ '71',
+ '72',
+ '81',
+ 'A2',
+ 'A8',
+ 'A9',
+ 'AA',
+ 'B2',
+ 'B3',
+ 'B9',
+ 'C2',
+ 'C3',
+ 'CB',
+ 'CC',
+ 'DB',
+ 'DC',
+ 'DF',
+ 'E1',
+ 'E3',
+ 'FA',
+];
+
+const weakEG = [
+ '07',
+ '0A',
+ '16',
+ '28',
+ '2A',
+ '2B',
+ '34',
+ '35',
+ '36',
+ '37',
+ '3A',
+ '4D',
+ '55',
+ '61',
+ '76',
+ '99',
+ 'A0',
+ 'C9',
+ 'E2',
+ 'E4',
+ 'F0',
+ 'FD',
+ 'FE',
+];
+
+const kick = [
+ '1B',
+ '29',
+ '3E',
+ '43',
+ '97',
+];
+
+const dark = [
+ '0B',
+ '19',
+ '21',
+ '22',
+ '32',
+ '41',
+ '42',
+ '69',
+ '6A',
+ '92',
+ '93',
+ 'B5',
+ 'BA',
+ 'C0',
+ 'D0',
+ 'E5',
+ 'E6',
+ 'E7',
+ 'F0',
+ 'F1',
+];
+
+const camera = {
+ '00': ['x', 'y'],
+ '01': ['y'],
+ '02': ['y'],
+ '07': ['x', 'y'],
+ '0A': ['x', 'y'],
+ '0C': ['x', 'y'],
+ '0D': ['x', 'y'],
+ '0E': ['yu'],
+ '10': ['x'],
+ '11': ['x'],
+ '12': ['x', 'y'],
+ '13': ['x'],
+ '14': ['x', 'y'],
+ '15': ['x', 'y'],
+ '16': ['y'],
+ '17': ['x', 'y'],
+ '18': ['x'],
+ '19': ['xr'],
+ '1A': ['xl'],
+ '1B': ['yu'],
+ '1D': ['y'],
+ '1F': ['yu'],
+ '20': ['x', 'y'],
+ '21': ['y'],
+ '22': ['y'],
+ '26': ['yd'],
+ '27': ['x', 'y'],
+ '28': ['x', 'y'],
+ '2A': ['x', 'y'],
+ '2B': ['xl'],
+ '2F': ['yd'],
+ '31': ['yu'],
+ '32': ['x', 'y'],
+ '34': ['x', 'y'],
+ '35': ['y'],
+ '36': ['x', 'y'],
+ '37': ['y'],
+ '38': ['x'],
+ '39': ['yu'],
+ '3A': ['x', 'y'],
+ '3B': ['x'],
+ '3C': ['x', 'y'],
+ '3E': ['yd'],
+ '3F': ['yu'],
+ '40': ['xl'],
+ '41': ['x', 'y'],
+ '42': ['y'],
+ '43': ['yu'],
+ '44': ['xr'],
+ '45': ['xr'],
+ '46': ['y'],
+ '49': ['xr'],
+ '4A': ['y'],
+ '4B': ['yd'],
+ '4C': ['x'],
+ '4D': ['x', 'y'],
+ '4E': ['yd'],
+ '50': ['x'],
+ '51': ['x', 'y'],
+ '52': ['xl', 'yd'],
+ '53': ['xr'],
+ '54': ['x', 'y'],
+ '55': ['y'],
+ '56': ['xr'],
+ '58': ['xr'],
+ '59': ['x'],
+ '5B': ['x'],
+ '5C': ['yu'],
+ '60': ['x'],
+ '61': ['x', 'y'],
+ '62': ['x', 'y'],
+ '63': ['xr'],
+ '64': ['yu'],
+ '65': ['yu'],
+ '66': ['yd'],
+ '67': ['x'],
+ '68': ['x', 'y'],
+ '6A': ['x'],
+ '6B': ['yu'],
+ '6D': ['xr'],
+ '72': ['y'],
+ '74': ['y'],
+ '75': ['xr'],
+ '76': ['xl'],
+ '77': ['x', 'y'],
+ '7B': ['yu'],
+ '7C': ['x'],
+ '7D': ['yu'],
+ '7E': ['xr'],
+ '7F': ['xr'],
+ '80': ['y'],
+ '81': ['x', 'y'],
+ '82': ['x', 'y'],
+ '83': ['xr'],
+ '84': ['x', 'y'],
+ '85': ['xl'],
+ '89': ['y'],
+ '8B': ['xl'],
+ '8D': ['xr'],
+ '91': ['x'],
+ '92': ['xr'],
+ '93': ['yu'],
+ '95': ['x'],
+ '96': ['xl'],
+ '97': ['xr'],
+ '98': ['yd'],
+ '99': ['yd'],
+ '9B': ['yd'],
+ '9C': ['x', 'y'],
+ '9D': ['yd'],
+ 'A0': ['y'],
+ 'A1': ['xr', 'yu'],
+ 'A2': ['x', 'y'],
+ 'A3': ['x'],
+ 'A5': ['yd'],
+ 'A6': ['x', 'y'],
+ 'A7': ['yd'],
+ 'A8': ['xr'],
+ 'A9': ['x', 'y'],
+ 'AA': ['xl'],
+ 'B1': ['xr'],
+ 'B2': ['yu'],
+ 'B3': ['x'],
+ 'B4': ['x', 'y'],
+ 'B5': ['x', 'y'],
+ 'B7': ['x'],
+ 'B8': ['x'],
+ 'B9': ['x', 'y'],
+ 'BB': ['xl'],
+ 'BC': ['xr'],
+ 'BE': ['xl'],
+ 'C0': ['xl'],
+ 'C2': ['x', 'y'],
+ 'C3': ['x'],
+ 'C4': ['x', 'y'],
+ 'C5': ['x'],
+ 'C6': ['x', 'y'],
+ 'C7': ['x', 'y'],
+ 'C9': ['y'],
+ 'CB': ['x', 'y'],
+ 'CC': ['x', 'y'],
+ 'D0': ['xl'],
+ 'D2': ['xr'],
+ 'D5': ['x'],
+ 'D6': ['x'],
+ 'D8': ['xl'],
+ 'D9': ['yu'],
+ 'DA': ['yu'],
+ 'DB': ['x', 'y'],
+ 'DC': ['x', 'y'],
+ 'DF': ['yd'],
+ 'E1': ['x'],
+ 'E2': ['x'],
+ 'E4': ['x'],
+ 'E5': ['x', 'y'],
+ 'E6': ['xr', 'yd'],
+ 'E7': ['xr', 'yu'],
+ 'E8': ['x', 'yu'],
+ 'EA': ['xl', 'yu'],
+ 'EB': ['x'],
+ 'ED': ['x', 'yu'],
+ 'EE': ['x', 'y'],
+ 'EF': ['yd'],
+ 'F0': ['x', 'y'],
+ 'F1': ['x', 'y'],
+ 'F8': ['x', 'y'],
+ 'F9': ['xr', 'yd'],
+ 'FA': ['x', 'y'],
+ 'FB': ['xr', 'yd'],
+ 'FD': ['x', 'y'],
+ 'FE': ['xr'],
+ 'FF': ['yd'],
+};
+
+const getClassName = key => {
+ const classNames = [];
+ if (strongEG.includes(key)) {
+ classNames.push('strong-eg');
+ }
+ if (weakEG.includes(key)) {
+ classNames.push('weak-eg');
+ }
+ if (kick.includes(key)) {
+ classNames.push('kick');
+ }
+ if (dark.includes(key)) {
+ classNames.push('dark');
+ }
+ if (camera[key]) {
+ camera[key].forEach(c => {
+ classNames.push(`cam-${c}`);
+ });
+ }
+ return classNames.join(' ');
+};
+
+const UWSuperTiles = ({ show }) => {
+ const { storePosition, viewer } = useOpenSeadragon();
+ const [, setSearchParams] = useSearchParams();
+
+ const onClick = React.useCallback(e => {
+ if (e.originalTarget.tagName !== 'A') return;
+ if (e.originalTarget.className !== 'cell-link') return;
+ const key = e.originalTarget.dataset.key;
+
+ const x = (parseInt(key[1], 16) + 0.5) / 16;
+ const y = (parseInt(key[0], 16) + 0.5) / 16;
+ if (viewer && viewer.viewport) {
+ storePosition();
+ setSearchParams({ x, y, z: 4 });
+ viewer.element.scrollIntoView();
+ }
+ }, [storePosition, viewer]);
+
+ return <Overlay onClick={onClick} page={3} x={0} y={0} width={1} height={1}>
+ <div className={`uw-super-tiles ${show ? '' : 'd-none'}`}>
+ {[...Array(16).keys()].map(x =>
+ [...Array(16).keys()].map(y => {
+ const key = `${x.toString(16).toUpperCase()}${y.toString(16).toUpperCase()}`;
+ return <div className={getClassName(key)} key={key}>
+ <p className="cell-id">{key}</p>
+ {dropMap[key] ?
+ <p className="cell-drop">
+ <a className="cell-link" data-key={dropMap[key]}>
+ {`â–¶ ${dropMap[key]}`}
+ </a>
+ </p>
+ : null}
+ </div>;
+ })
+ )}
+ </div>
+ </Overlay>;
+};
+
+UWSuperTiles.propTypes = {
+ show: PropTypes.bool,
+};
+
+export default UWSuperTiles;
+++ /dev/null
-import OpenSeadragon from 'openseadragon';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-const Viewer = () => {
- const [viewer, setViewer] = React.useState(null);
-
- const container = React.useRef();
- const { t } = useTranslation();
-
- React.useEffect(() => {
- if (!container.current) return;
-
- const v = OpenSeadragon({
- element: container.current,
- preserveViewport: true,
- sequenceMode: true,
- showNavigator: true,
- showNavigationControl: false,
- showSequenceControl: false,
- //tileSources: [
- // new OpenSeadragon.DziTileSource({
- // width: 8192,
- // height: 8192,
- // tileSize: 256,
- // tileOverlap: 0,
- // minLevel: 8,
- // maxLevel: 13,
- // tilesUrl: '/media/alttp/map/lw_files/',
- // fileFormat: 'png',
- // }), new OpenSeadragon.DziTileSource({
- // width: 8192,
- // height: 8192,
- // tileSize: 256,
- // tileOverlap: 0,
- // minLevel: 8,
- // maxLevel: 13,
- // tilesUrl: '/media/alttp/map/dw_files/',
- // fileFormat: 'png',
- // }), new OpenSeadragon.DziTileSource({
- // width: 8192,
- // height: 8192,
- // tileSize: 256,
- // tileOverlap: 0,
- // minLevel: 8,
- // maxLevel: 13,
- // tilesUrl: '/media/alttp/map/sp_files/',
- // fileFormat: 'png',
- // }), new OpenSeadragon.DziTileSource({
- // width: 16384,
- // height: 16384,
- // tileSize: 256,
- // tileOverlap: 0,
- // minLevel: 8,
- // maxLevel: 14,
- // tilesUrl: '/media/alttp/map/uw_files/',
- // fileFormat: 'png',
- // }),
- //],
- });
- setViewer(v);
- return () => {
- v.destroy();
- };
- }, [container.current]);
-
- const goToPage = React.useCallback((p) => {
- if (viewer) viewer.goToPage(p);
- }, [viewer]);
-
- return <>
- <div className="d-flex align-items-center justify-content-between">
- <div className="button-bar">
- <Button
- onClick={() => goToPage(0)}
- title={t('map.lwLong')}
- variant="outline-secondary"
- >
- {t('map.lwShort')}
- </Button>
- <Button
- onClick={() => goToPage(1)}
- title={t('map.dwLong')}
- variant="outline-secondary"
- >
- {t('map.dwShort')}
- </Button>
- <Button
- onClick={() => goToPage(2)}
- title={t('map.spLong')}
- variant="outline-secondary"
- >
- {t('map.spShort')}
- </Button>
- <Button
- onClick={() => goToPage(3)}
- title={t('map.uwLong')}
- variant="outline-secondary"
- >
- {t('map.uwShort')}
- </Button>
- </div>
- </div>
- <div ref={container} style={{ height: '80vh' }} />
- </>;
-};
-
-export default Viewer;
--- /dev/null
+import OpenSeadragon from 'openseadragon';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+const Viewer = () => {
+ const [viewer, setViewer] = React.useState(null);
+
+ const container = React.useRef();
+ const { t } = useTranslation();
+
+ React.useEffect(() => {
+ if (!container.current) return;
+
+ const v = OpenSeadragon({
+ element: container.current,
+ preserveViewport: true,
+ sequenceMode: true,
+ showNavigator: true,
+ showNavigationControl: false,
+ showSequenceControl: false,
+ //tileSources: [
+ // new OpenSeadragon.DziTileSource({
+ // width: 8192,
+ // height: 8192,
+ // tileSize: 256,
+ // tileOverlap: 0,
+ // minLevel: 8,
+ // maxLevel: 13,
+ // tilesUrl: '/media/alttp/map/lw_files/',
+ // fileFormat: 'png',
+ // }), new OpenSeadragon.DziTileSource({
+ // width: 8192,
+ // height: 8192,
+ // tileSize: 256,
+ // tileOverlap: 0,
+ // minLevel: 8,
+ // maxLevel: 13,
+ // tilesUrl: '/media/alttp/map/dw_files/',
+ // fileFormat: 'png',
+ // }), new OpenSeadragon.DziTileSource({
+ // width: 8192,
+ // height: 8192,
+ // tileSize: 256,
+ // tileOverlap: 0,
+ // minLevel: 8,
+ // maxLevel: 13,
+ // tilesUrl: '/media/alttp/map/sp_files/',
+ // fileFormat: 'png',
+ // }), new OpenSeadragon.DziTileSource({
+ // width: 16384,
+ // height: 16384,
+ // tileSize: 256,
+ // tileOverlap: 0,
+ // minLevel: 8,
+ // maxLevel: 14,
+ // tilesUrl: '/media/alttp/map/uw_files/',
+ // fileFormat: 'png',
+ // }),
+ //],
+ });
+ setViewer(v);
+ return () => {
+ v.destroy();
+ };
+ }, [container.current]);
+
+ const goToPage = React.useCallback((p) => {
+ if (viewer) viewer.goToPage(p);
+ }, [viewer]);
+
+ return <>
+ <div className="d-flex align-items-center justify-content-between">
+ <div className="button-bar">
+ <Button
+ onClick={() => goToPage(0)}
+ title={t('map.lwLong')}
+ variant="outline-secondary"
+ >
+ {t('map.lwShort')}
+ </Button>
+ <Button
+ onClick={() => goToPage(1)}
+ title={t('map.dwLong')}
+ variant="outline-secondary"
+ >
+ {t('map.dwShort')}
+ </Button>
+ <Button
+ onClick={() => goToPage(2)}
+ title={t('map.spLong')}
+ variant="outline-secondary"
+ >
+ {t('map.spShort')}
+ </Button>
+ <Button
+ onClick={() => goToPage(3)}
+ title={t('map.uwLong')}
+ variant="outline-secondary"
+ >
+ {t('map.uwShort')}
+ </Button>
+ </div>
+ </div>
+ <div ref={container} style={{ height: '80vh' }} />
+ </>;
+};
+
+export default Viewer;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Alert, Col, Row } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import Box from '../users/Box';
-import i18n from '../../i18n';
-
-const List = ({ participants }) => participants && participants.length ?
- <Row className="participants">
- {participants.map(participant =>
- <Col md={4} lg={3} key={participant.id}>
- <Box user={participant.user} />
- </Col>
- )}
- </Row>
-:
- <Alert variant="info">
- {i18n.t('participants.empty')}
- </Alert>
-;
-
-List.propTypes = {
- participants: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.number,
- user: PropTypes.shape({
- discriminator: PropTypes.string,
- username: PropTypes.string,
- }),
- })),
-};
-
-export default withTranslation()(List);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Alert, Col, Row } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import Box from '../users/Box';
+import i18n from '../../i18n';
+
+const List = ({ participants }) => participants && participants.length ?
+ <Row className="participants">
+ {participants.map(participant =>
+ <Col md={4} lg={3} key={participant.id}>
+ <Box user={participant.user} />
+ </Col>
+ )}
+ </Row>
+:
+ <Alert variant="info">
+ {i18n.t('participants.empty')}
+ </Alert>
+;
+
+List.propTypes = {
+ participants: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.number,
+ user: PropTypes.shape({
+ discriminator: PropTypes.string,
+ username: PropTypes.string,
+ }),
+ })),
+};
+
+export default withTranslation()(List);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Alert, Button, Modal } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import List from './List';
-
-const Dialog = ({
- onHide = null,
- protocol = null,
- show = false,
-}) => {
- const { t } = useTranslation();
-
- return <Modal className="protocol-dialog" onHide={onHide} show={show} size="lg">
- <Modal.Header closeButton>
- <Modal.Title>
- {t('protocol.heading')}
- </Modal.Title>
- </Modal.Header>
- {protocol && protocol.length ?
- <List protocol={protocol} />
- :
- <Modal.Body>
- <Alert variant="info">
- {t('protocol.empty')}
- </Alert>
- </Modal.Body>
- }
- <Modal.Footer>
- <Button onClick={onHide} variant="secondary">
- {t('button.close')}
- </Button>
- </Modal.Footer>
- </Modal>;
-};
-
-Dialog.propTypes = {
- onHide: PropTypes.func,
- protocol: PropTypes.arrayOf(PropTypes.shape({
- type: PropTypes.string,
- })),
- show: PropTypes.bool,
-};
-
-export default Dialog;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Alert, Button, Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import List from './List';
+
+const Dialog = ({
+ onHide = null,
+ protocol = null,
+ show = false,
+}) => {
+ const { t } = useTranslation();
+
+ return <Modal className="protocol-dialog" onHide={onHide} show={show} size="lg">
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {t('protocol.heading')}
+ </Modal.Title>
+ </Modal.Header>
+ {protocol && protocol.length ?
+ <List protocol={protocol} />
+ :
+ <Modal.Body>
+ <Alert variant="info">
+ {t('protocol.empty')}
+ </Alert>
+ </Modal.Body>
+ }
+ <Modal.Footer>
+ <Button onClick={onHide} variant="secondary">
+ {t('button.close')}
+ </Button>
+ </Modal.Footer>
+ </Modal>;
+};
+
+Dialog.propTypes = {
+ onHide: PropTypes.func,
+ protocol: PropTypes.arrayOf(PropTypes.shape({
+ type: PropTypes.string,
+ })),
+ show: PropTypes.bool,
+};
+
+export default Dialog;
+++ /dev/null
-import moment from 'moment';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { ListGroup } from 'react-bootstrap';
-import { Trans, useTranslation } from 'react-i18next';
-
-import Icon from '../common/Icon';
-import Spoiler from '../common/Spoiler';
-import { formatTime } from '../../helpers/Result';
-import { getUserName } from '../../helpers/User';
-
-const getEntryDate = entry => {
- const dateStr = moment(entry.created_at).fromNow();
- return entry.user
- ? `${entry.user.username} ${dateStr}`
- : dateStr;
-};
-
-const getEntryDetailsUsername = entry => {
- if (!entry || !entry.details || !entry.details.user) return 'Anonymous';
- return getUserName(entry.details.user);
-};
-
-const getEntryRoundNumber = entry =>
- (entry && entry.details && entry.details.round && entry.details.round.number) || '?';
-
-const getEntryResultComment = entry => {
- if (!entry || !entry.details || !entry.details.result || !entry.details.result.comment) {
- return '';
- }
- return entry.details.result.comment;
-};
-
-const getEntryResultTime = entry => {
- if (!entry || !entry.details || !entry.details.result) return 'ERROR';
- const result = entry.details.result;
- if (result.forfeit) return 'DNF XX';
- return formatTime(result);
-};
-
-const getEntryDescription = (entry, t) => {
- switch (entry.type) {
- case 'application.accepted':
- case 'application.received':
- case 'application.rejected':
- return t(
- `protocol.description.${entry.type}`,
- {
- ...entry,
- username: getEntryDetailsUsername(entry),
- },
- );
- case 'result.comment': {
- const comment = getEntryResultComment(entry);
- const number = getEntryRoundNumber(entry);
- return <Trans i18nKey={`protocol.description.${entry.type}`}>
- {{number}}
- <Spoiler>{{comment}}</Spoiler>,
- </Trans>;
- }
- case 'result.report': {
- const number = getEntryRoundNumber(entry);
- const time = getEntryResultTime(entry);
- return <Trans i18nKey={`protocol.description.${entry.type}`}>
- {{number}}
- <Spoiler>{{time}}</Spoiler>,
- </Trans>;
- }
- case 'round.create':
- case 'round.delete':
- case 'round.edit':
- case 'round.lock':
- case 'round.seed':
- case 'round.unlock':
- return t(
- `protocol.description.${entry.type}`,
- {
- ...entry,
- number: getEntryRoundNumber(entry),
- },
- );
- case 'tournament.close':
- case 'tournament.discord':
- case 'tournament.lock':
- case 'tournament.open':
- case 'tournament.settings':
- case 'tournament.unlock':
- return t(
- `protocol.description.${entry.type}`,
- entry,
- );
- default:
- return t('protocol.description.unknown', entry);
- }
-};
-
-const getEntryIcon = entry => {
- switch (entry.type) {
- case 'result.report':
- return <Icon.RESULT />;
- case 'round.create':
- return <Icon.ADD />;
- case 'round.delete':
- return <Icon.REMOVE />;
- case 'round.lock':
- case 'tournament.close':
- case 'tournament.lock':
- return <Icon.LOCKED />;
- case 'round.unlock':
- case 'tournament.open':
- case 'tournament.unlock':
- return <Icon.UNLOCKED />;
- case 'tournament.discord':
- return <Icon.DISCORD />;
- default:
- return <Icon.PROTOCOL />;
- }
-};
-
-const Item = ({ entry = {} }) => {
- const { t } = useTranslation();
-
- const icon = React.useMemo(() => getEntryIcon(entry), [entry]);
- const description = React.useMemo(() => getEntryDescription(entry, t), [entry, t]);
- const [date, setDate] = React.useState(getEntryDate(entry));
-
- React.useEffect(() => {
- setDate(getEntryDate(entry));
- const timer = setInterval(() => {
- setDate(getEntryDate(entry));
- }, 30_000);
- return () => {
- clearInterval(timer);
- };
- }, [entry]);
-
- return <ListGroup.Item className="d-flex align-items-center">
- <div className="pe-3 text-muted">
- {icon}
- </div>
- <div>
- <div>
- {description}
- </div>
- <div
- className="text-muted"
- title={moment(entry.created_at).format('LLLL')}
- >
- {date}
- </div>
- </div>
- </ListGroup.Item>;
-};
-
-Item.propTypes = {
- entry: PropTypes.shape({
- created_at: PropTypes.string,
- }),
-};
-
-export default Item;
--- /dev/null
+import moment from 'moment';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { ListGroup } from 'react-bootstrap';
+import { Trans, useTranslation } from 'react-i18next';
+
+import Icon from '../common/Icon';
+import Spoiler from '../common/Spoiler';
+import { formatTime } from '../../helpers/Result';
+import { getUserName } from '../../helpers/User';
+
+const getEntryDate = entry => {
+ const dateStr = moment(entry.created_at).fromNow();
+ return entry.user
+ ? `${entry.user.username} ${dateStr}`
+ : dateStr;
+};
+
+const getEntryDetailsUsername = entry => {
+ if (!entry || !entry.details || !entry.details.user) return 'Anonymous';
+ return getUserName(entry.details.user);
+};
+
+const getEntryRoundNumber = entry =>
+ (entry && entry.details && entry.details.round && entry.details.round.number) || '?';
+
+const getEntryResultComment = entry => {
+ if (!entry || !entry.details || !entry.details.result || !entry.details.result.comment) {
+ return '';
+ }
+ return entry.details.result.comment;
+};
+
+const getEntryResultTime = entry => {
+ if (!entry || !entry.details || !entry.details.result) return 'ERROR';
+ const result = entry.details.result;
+ if (result.forfeit) return 'DNF XX';
+ return formatTime(result);
+};
+
+const getEntryDescription = (entry, t) => {
+ switch (entry.type) {
+ case 'application.accepted':
+ case 'application.received':
+ case 'application.rejected':
+ return t(
+ `protocol.description.${entry.type}`,
+ {
+ ...entry,
+ username: getEntryDetailsUsername(entry),
+ },
+ );
+ case 'result.comment': {
+ const comment = getEntryResultComment(entry);
+ const number = getEntryRoundNumber(entry);
+ return <Trans i18nKey={`protocol.description.${entry.type}`}>
+ {{number}}
+ <Spoiler>{{comment}}</Spoiler>,
+ </Trans>;
+ }
+ case 'result.report': {
+ const number = getEntryRoundNumber(entry);
+ const time = getEntryResultTime(entry);
+ return <Trans i18nKey={`protocol.description.${entry.type}`}>
+ {{number}}
+ <Spoiler>{{time}}</Spoiler>,
+ </Trans>;
+ }
+ case 'round.create':
+ case 'round.delete':
+ case 'round.edit':
+ case 'round.lock':
+ case 'round.seed':
+ case 'round.unlock':
+ return t(
+ `protocol.description.${entry.type}`,
+ {
+ ...entry,
+ number: getEntryRoundNumber(entry),
+ },
+ );
+ case 'tournament.close':
+ case 'tournament.discord':
+ case 'tournament.lock':
+ case 'tournament.open':
+ case 'tournament.settings':
+ case 'tournament.unlock':
+ return t(
+ `protocol.description.${entry.type}`,
+ entry,
+ );
+ default:
+ return t('protocol.description.unknown', entry);
+ }
+};
+
+const getEntryIcon = entry => {
+ switch (entry.type) {
+ case 'result.report':
+ return <Icon.RESULT />;
+ case 'round.create':
+ return <Icon.ADD />;
+ case 'round.delete':
+ return <Icon.REMOVE />;
+ case 'round.lock':
+ case 'tournament.close':
+ case 'tournament.lock':
+ return <Icon.LOCKED />;
+ case 'round.unlock':
+ case 'tournament.open':
+ case 'tournament.unlock':
+ return <Icon.UNLOCKED />;
+ case 'tournament.discord':
+ return <Icon.DISCORD />;
+ default:
+ return <Icon.PROTOCOL />;
+ }
+};
+
+const Item = ({ entry = {} }) => {
+ const { t } = useTranslation();
+
+ const icon = React.useMemo(() => getEntryIcon(entry), [entry]);
+ const description = React.useMemo(() => getEntryDescription(entry, t), [entry, t]);
+ const [date, setDate] = React.useState(getEntryDate(entry));
+
+ React.useEffect(() => {
+ setDate(getEntryDate(entry));
+ const timer = setInterval(() => {
+ setDate(getEntryDate(entry));
+ }, 30_000);
+ return () => {
+ clearInterval(timer);
+ };
+ }, [entry]);
+
+ return <ListGroup.Item className="d-flex align-items-center">
+ <div className="pe-3 text-muted">
+ {icon}
+ </div>
+ <div>
+ <div>
+ {description}
+ </div>
+ <div
+ className="text-muted"
+ title={moment(entry.created_at).format('LLLL')}
+ >
+ {date}
+ </div>
+ </div>
+ </ListGroup.Item>;
+};
+
+Item.propTypes = {
+ entry: PropTypes.shape({
+ created_at: PropTypes.string,
+ }),
+};
+
+export default Item;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { ListGroup } from 'react-bootstrap';
-
-import Item from './Item';
-
-const List = ({ protocol = [] }) =>
- <ListGroup variant="flush">
- {protocol ? protocol.map(entry =>
- <Item key={entry.id} entry={entry} />
- ) : null}
- </ListGroup>;
-
-List.propTypes = {
- protocol: PropTypes.arrayOf(PropTypes.shape({
- })),
-};
-
-export default List;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { ListGroup } from 'react-bootstrap';
+
+import Item from './Item';
+
+const List = ({ protocol = [] }) =>
+ <ListGroup variant="flush">
+ {protocol ? protocol.map(entry =>
+ <Item key={entry.id} entry={entry} />
+ ) : null}
+ </ListGroup>;
+
+List.propTypes = {
+ protocol: PropTypes.arrayOf(PropTypes.shape({
+ })),
+};
+
+export default List;
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React, { useEffect, useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Dialog from './Dialog';
-import Icon from '../common/Icon';
-
-const Protocol = ({ id }) => {
- const [showDialog, setShowDialog] = useState(false);
- const [protocol, setProtocol] = useState([]);
-
- const { t } = useTranslation();
-
- useEffect(() => {
- const ctrl = new AbortController();
- axios
- .get(`/api/protocol/${id}`, { signal: ctrl.signal })
- .then(response => {
- setProtocol(response.data);
- });
- return () => {
- ctrl.abort();
- };
- }, [id]);
-
- useEffect(() => {
- window.Echo.private(`Protocol.${id}`)
- .listen('ProtocolAdded', e => {
- if (e.protocol) {
- setProtocol(protocol => [e.protocol, ...protocol]);
- }
- });
- return () => {
- window.Echo.leave(`Protocol.${id}`);
- };
- }, [id]);
-
- return (
- <>
- <Button
- onClick={() => setShowDialog(true)}
- title={t('button.protocol')}
- variant="outline-info"
- >
- <Icon.PROTOCOL title="" />
- </Button>
- <Dialog
- onHide={() => setShowDialog(false)}
- protocol={protocol}
- show={showDialog}
- />
- </>
- );
-};
-
-Protocol.propTypes = {
- id: PropTypes.number,
-};
-
-export default Protocol;
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React, { useEffect, useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Dialog from './Dialog';
+import Icon from '../common/Icon';
+
+const Protocol = ({ id }) => {
+ const [showDialog, setShowDialog] = useState(false);
+ const [protocol, setProtocol] = useState([]);
+
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ const ctrl = new AbortController();
+ axios
+ .get(`/api/protocol/${id}`, { signal: ctrl.signal })
+ .then(response => {
+ setProtocol(response.data);
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [id]);
+
+ useEffect(() => {
+ window.Echo.private(`Protocol.${id}`)
+ .listen('ProtocolAdded', e => {
+ if (e.protocol) {
+ setProtocol(protocol => [e.protocol, ...protocol]);
+ }
+ });
+ return () => {
+ window.Echo.leave(`Protocol.${id}`);
+ };
+ }, [id]);
+
+ return (
+ <>
+ <Button
+ onClick={() => setShowDialog(true)}
+ title={t('button.protocol')}
+ variant="outline-info"
+ >
+ <Icon.PROTOCOL title="" />
+ </Button>
+ <Dialog
+ onHide={() => setShowDialog(false)}
+ protocol={protocol}
+ show={showDialog}
+ />
+ </>
+ );
+};
+
+Protocol.propTypes = {
+ id: PropTypes.number,
+};
+
+export default Protocol;
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React, { useEffect, useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Dialog from './Dialog';
-import Icon from '../common/Icon';
-
-const RoundProtocol = ({ roundId, tournamentId }) => {
- const [showDialog, setShowDialog] = useState(false);
- const [protocol, setProtocol] = useState([]);
-
- const { t } = useTranslation();
-
- useEffect(() => {
- if (!showDialog) return;
- const ctrl = new AbortController();
- axios
- .get(`/api/protocol/${tournamentId}/${roundId}`, { signal: ctrl.signal })
- .then(response => {
- setProtocol(response.data);
- });
- return () => {
- ctrl.abort();
- };
- }, [roundId, showDialog, tournamentId]);
-
- return (
- <>
- <Button
- onClick={() => setShowDialog(true)}
- size="sm"
- title={t('button.protocol')}
- variant="outline-info"
- >
- <Icon.PROTOCOL title="" />
- </Button>
- <Dialog
- onHide={() => setShowDialog(false)}
- protocol={protocol}
- show={showDialog}
- />
- </>
- );
-};
-
-RoundProtocol.propTypes = {
- roundId: PropTypes.number,
- tournamentId: PropTypes.number,
-};
-
-export default RoundProtocol;
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React, { useEffect, useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Dialog from './Dialog';
+import Icon from '../common/Icon';
+
+const RoundProtocol = ({ roundId, tournamentId }) => {
+ const [showDialog, setShowDialog] = useState(false);
+ const [protocol, setProtocol] = useState([]);
+
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ if (!showDialog) return;
+ const ctrl = new AbortController();
+ axios
+ .get(`/api/protocol/${tournamentId}/${roundId}`, { signal: ctrl.signal })
+ .then(response => {
+ setProtocol(response.data);
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [roundId, showDialog, tournamentId]);
+
+ return (
+ <>
+ <Button
+ onClick={() => setShowDialog(true)}
+ size="sm"
+ title={t('button.protocol')}
+ variant="outline-info"
+ >
+ <Icon.PROTOCOL title="" />
+ </Button>
+ <Dialog
+ onHide={() => setShowDialog(false)}
+ protocol={protocol}
+ show={showDialog}
+ />
+ </>
+ );
+};
+
+RoundProtocol.propTypes = {
+ roundId: PropTypes.number,
+ tournamentId: PropTypes.number,
+};
+
+export default RoundProtocol;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Box from '../users/Box';
-import { getTime } from '../../helpers/Result';
-import { maySeeResult } from '../../helpers/permissions';
-import { findResult } from '../../helpers/User';
-import { useUser } from '../../hooks/user';
-
-const getPlacement = (result, t) =>
- `${result.placement}. (${t('results.points', { count: result.score })})`;
-
-const DetailDialog = ({
- onHide,
- round,
- show,
- tournament,
- user,
-}) => {
- const { t } = useTranslation();
- const { user: authUser } = useUser();
-
- const result = React.useMemo(
- () => findResult(user, round),
- [round, user],
- );
- const maySee = React.useMemo(
- () => maySeeResult(authUser, tournament, round, result),
- [authUser, result, round, tournament],
- );
-
- return <Modal className="result-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {t('results.details')}
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <Row>
- <Form.Group as={Col} sm={6}>
- <Form.Label>{t('results.round')}</Form.Label>
- <div>
- #{round.number || '?'}
- {' '}
- {t('rounds.date', { date: new Date(round.created_at) })}
- </div>
- </Form.Group>
- <Form.Group as={Col} sm={6}>
- <Form.Label>{t('results.runner')}</Form.Label>
- <div><Box user={user} /></div>
- </Form.Group>
- <Form.Group as={Col} sm={6}>
- <Form.Label>{t('results.result')}</Form.Label>
- <div>
- {maySee && result && result.has_finished
- ? getTime(result, maySee)
- : t('results.pending')}
- </div>
- </Form.Group>
- <Form.Group as={Col} sm={6}>
- <Form.Label>{t('results.placement')}</Form.Label>
- <div>
- {maySee && result && result.placement
- ? getPlacement(result, t)
- : t('results.pending')}
- </div>
- </Form.Group>
- {maySee && result && result.comment ?
- <Form.Group as={Col} sm={12}>
- <Form.Label>{t('results.comment')}</Form.Label>
- <div>{result.comment}</div>
- </Form.Group>
- : null}
- </Row>
- </Modal.Body>
- <Modal.Footer>
- <Button onClick={onHide} variant="secondary">
- {t('button.close')}
- </Button>
- </Modal.Footer>
- </Modal>;
-};
-
-DetailDialog.propTypes = {
- onHide: PropTypes.func,
- round: PropTypes.shape({
- created_at: PropTypes.string,
- number: PropTypes.number,
- }),
- show: PropTypes.bool,
- tournament: PropTypes.shape({
- }),
- user: PropTypes.shape({
- }),
-};
-
-export default DetailDialog;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Box from '../users/Box';
+import { getTime } from '../../helpers/Result';
+import { maySeeResult } from '../../helpers/permissions';
+import { findResult } from '../../helpers/User';
+import { useUser } from '../../hooks/user';
+
+const getPlacement = (result, t) =>
+ `${result.placement}. (${t('results.points', { count: result.score })})`;
+
+const DetailDialog = ({
+ onHide,
+ round,
+ show,
+ tournament,
+ user,
+}) => {
+ const { t } = useTranslation();
+ const { user: authUser } = useUser();
+
+ const result = React.useMemo(
+ () => findResult(user, round),
+ [round, user],
+ );
+ const maySee = React.useMemo(
+ () => maySeeResult(authUser, tournament, round, result),
+ [authUser, result, round, tournament],
+ );
+
+ return <Modal className="result-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {t('results.details')}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Form.Group as={Col} sm={6}>
+ <Form.Label>{t('results.round')}</Form.Label>
+ <div>
+ #{round.number || '?'}
+ {' '}
+ {t('rounds.date', { date: new Date(round.created_at) })}
+ </div>
+ </Form.Group>
+ <Form.Group as={Col} sm={6}>
+ <Form.Label>{t('results.runner')}</Form.Label>
+ <div><Box user={user} /></div>
+ </Form.Group>
+ <Form.Group as={Col} sm={6}>
+ <Form.Label>{t('results.result')}</Form.Label>
+ <div>
+ {maySee && result && result.has_finished
+ ? getTime(result, maySee)
+ : t('results.pending')}
+ </div>
+ </Form.Group>
+ <Form.Group as={Col} sm={6}>
+ <Form.Label>{t('results.placement')}</Form.Label>
+ <div>
+ {maySee && result && result.placement
+ ? getPlacement(result, t)
+ : t('results.pending')}
+ </div>
+ </Form.Group>
+ {maySee && result && result.comment ?
+ <Form.Group as={Col} sm={12}>
+ <Form.Label>{t('results.comment')}</Form.Label>
+ <div>{result.comment}</div>
+ </Form.Group>
+ : null}
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button onClick={onHide} variant="secondary">
+ {t('button.close')}
+ </Button>
+ </Modal.Footer>
+ </Modal>;
+};
+
+DetailDialog.propTypes = {
+ onHide: PropTypes.func,
+ round: PropTypes.shape({
+ created_at: PropTypes.string,
+ number: PropTypes.number,
+ }),
+ show: PropTypes.bool,
+ tournament: PropTypes.shape({
+ }),
+ user: PropTypes.shape({
+ }),
+};
+
+export default DetailDialog;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import DetailDialog from './DetailDialog';
-import Icon from '../common/Icon';
-import Box from '../users/Box';
-import { getIcon, getTime } from '../../helpers/Result';
-import { maySeeResult } from '../../helpers/permissions';
-import { findResult } from '../../helpers/User';
-import { useUser } from '../../hooks/user';
-
-const getClassName = result => {
- const classNames = ['status'];
- if (result && result.has_finished) {
- classNames.push('finished');
- if (result.comment) {
- classNames.push('has-comment');
- }
- } else {
- classNames.push('pending');
- }
- return classNames.join(' ');
-};
-
-const twitchReg = /^https?:\/\/(www\.)?twitch\.tv/;
-const youtubeReg = /^https?:\/\/(www\.)?youtu(\.be|be\.)/;
-
-const getVoDVariant = result => {
- if (!result || !result.vod) return 'outline-secondary';
- if (twitchReg.test(result.vod)) {
- return 'twitch';
- }
- if (youtubeReg.test(result.vod)) {
- return 'outline-youtube';
- }
- return 'outline-secondary';
-};
-
-const getVoDIcon = result => {
- const variant = getVoDVariant(result);
- if (variant === 'twitch') {
- return <Icon.TWITCH title="" />;
- }
- if (variant === 'outline-youtube') {
- return <Icon.YOUTUBE title="" />;
- }
- return <Icon.VIDEO title="" />;
-};
-
-const Item = ({
- round,
- tournament,
- user,
-}) => {
- const [showDialog, setShowDialog] = useState(false);
-
- const { t } = useTranslation();
- const { user: authUser } = useUser();
-
- const result = React.useMemo(
- () => findResult(user, round),
- [round, user],
- );
- const maySee = React.useMemo(
- () => maySeeResult(authUser, tournament, round, result),
- [authUser, result, round, tournament],
- );
-
- return <div className="result">
- <Box user={user} />
- <div className="d-flex align-items-center justify-content-between">
- <Button
- className={getClassName(result)}
- onClick={() => setShowDialog(true)}
- title={maySee && result && result.comment ? result.comment : null}
- >
- <span className="time">
- {getTime(result, maySee)}
- </span>
- {getIcon(result, maySee)}
- </Button>
- {maySee && result && result.vod ?
- <Button
- className="vod-link"
- href={result.vod}
- size="sm"
- target="_blank"
- title={t('results.vod')}
- variant={getVoDVariant(result)}
- >
- {getVoDIcon(result)}
- </Button>
- : null}
- </div>
- <DetailDialog
- onHide={() => setShowDialog(false)}
- round={round}
- show={showDialog}
- tournament={tournament}
- user={user}
- />
- </div>;
-};
-
-Item.propTypes = {
- round: PropTypes.shape({
- }),
- tournament: PropTypes.shape({
- }),
- user: PropTypes.shape({
- }),
-};
-
-export default Item;
--- /dev/null
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import DetailDialog from './DetailDialog';
+import Icon from '../common/Icon';
+import Box from '../users/Box';
+import { getIcon, getTime } from '../../helpers/Result';
+import { maySeeResult } from '../../helpers/permissions';
+import { findResult } from '../../helpers/User';
+import { useUser } from '../../hooks/user';
+
+const getClassName = result => {
+ const classNames = ['status'];
+ if (result && result.has_finished) {
+ classNames.push('finished');
+ if (result.comment) {
+ classNames.push('has-comment');
+ }
+ } else {
+ classNames.push('pending');
+ }
+ return classNames.join(' ');
+};
+
+const twitchReg = /^https?:\/\/(www\.)?twitch\.tv/;
+const youtubeReg = /^https?:\/\/(www\.)?youtu(\.be|be\.)/;
+
+const getVoDVariant = result => {
+ if (!result || !result.vod) return 'outline-secondary';
+ if (twitchReg.test(result.vod)) {
+ return 'twitch';
+ }
+ if (youtubeReg.test(result.vod)) {
+ return 'outline-youtube';
+ }
+ return 'outline-secondary';
+};
+
+const getVoDIcon = result => {
+ const variant = getVoDVariant(result);
+ if (variant === 'twitch') {
+ return <Icon.TWITCH title="" />;
+ }
+ if (variant === 'outline-youtube') {
+ return <Icon.YOUTUBE title="" />;
+ }
+ return <Icon.VIDEO title="" />;
+};
+
+const Item = ({
+ round,
+ tournament,
+ user,
+}) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ const { t } = useTranslation();
+ const { user: authUser } = useUser();
+
+ const result = React.useMemo(
+ () => findResult(user, round),
+ [round, user],
+ );
+ const maySee = React.useMemo(
+ () => maySeeResult(authUser, tournament, round, result),
+ [authUser, result, round, tournament],
+ );
+
+ return <div className="result">
+ <Box user={user} />
+ <div className="d-flex align-items-center justify-content-between">
+ <Button
+ className={getClassName(result)}
+ onClick={() => setShowDialog(true)}
+ title={maySee && result && result.comment ? result.comment : null}
+ >
+ <span className="time">
+ {getTime(result, maySee)}
+ </span>
+ {getIcon(result, maySee)}
+ </Button>
+ {maySee && result && result.vod ?
+ <Button
+ className="vod-link"
+ href={result.vod}
+ size="sm"
+ target="_blank"
+ title={t('results.vod')}
+ variant={getVoDVariant(result)}
+ >
+ {getVoDIcon(result)}
+ </Button>
+ : null}
+ </div>
+ <DetailDialog
+ onHide={() => setShowDialog(false)}
+ round={round}
+ show={showDialog}
+ tournament={tournament}
+ user={user}
+ />
+ </div>;
+};
+
+Item.propTypes = {
+ round: PropTypes.shape({
+ }),
+ tournament: PropTypes.shape({
+ }),
+ user: PropTypes.shape({
+ }),
+};
+
+export default Item;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Item from './Item';
-import { sortByFinished, sortByResult } from '../../helpers/Participant';
-import { maySeeResults } from '../../helpers/permissions';
-import { getRunners } from '../../helpers/Tournament';
-import { sortByTime, sortByUsername } from '../../helpers/Result';
-import { useUser } from '../../hooks/user';
-
-const List = ({ round, tournament }) => {
- const { user } = useUser();
-
- if (tournament.type === 'open-async') {
- const results = maySeeResults(user, tournament, round)
- ? sortByTime(round.results || [])
- : sortByUsername(round.results || []);
- return <div className="results d-flex flex-wrap">
- {results.map(result =>
- <Item
- key={result.id}
- round={round}
- tournament={tournament}
- user={result.user}
- />
- )}
- </div>;
- }
- const runners = maySeeResults(user, tournament, round)
- ? sortByResult(getRunners(tournament), round)
- : sortByFinished(getRunners(tournament), round);
- return <div className="results d-flex flex-wrap">
- {runners.map(participant =>
- <Item
- key={participant.id}
- round={round}
- tournament={tournament}
- user={participant.user}
- />
- )}
- </div>;
-};
-
-List.propTypes = {
- round: PropTypes.shape({
- results: PropTypes.arrayOf(PropTypes.shape({
- })),
- }),
- tournament: PropTypes.shape({
- participants: PropTypes.arrayOf(PropTypes.shape({
- })),
- type: PropTypes.string,
- users: PropTypes.arrayOf(PropTypes.shape({
- })),
- }),
-};
-
-export default List;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Item from './Item';
+import { sortByFinished, sortByResult } from '../../helpers/Participant';
+import { maySeeResults } from '../../helpers/permissions';
+import { getRunners } from '../../helpers/Tournament';
+import { sortByTime, sortByUsername } from '../../helpers/Result';
+import { useUser } from '../../hooks/user';
+
+const List = ({ round, tournament }) => {
+ const { user } = useUser();
+
+ if (tournament.type === 'open-async') {
+ const results = maySeeResults(user, tournament, round)
+ ? sortByTime(round.results || [])
+ : sortByUsername(round.results || []);
+ return <div className="results d-flex flex-wrap">
+ {results.map(result =>
+ <Item
+ key={result.id}
+ round={round}
+ tournament={tournament}
+ user={result.user}
+ />
+ )}
+ </div>;
+ }
+ const runners = maySeeResults(user, tournament, round)
+ ? sortByResult(getRunners(tournament), round)
+ : sortByFinished(getRunners(tournament), round);
+ return <div className="results d-flex flex-wrap">
+ {runners.map(participant =>
+ <Item
+ key={participant.id}
+ round={round}
+ tournament={tournament}
+ user={participant.user}
+ />
+ )}
+ </div>;
+};
+
+List.propTypes = {
+ round: PropTypes.shape({
+ results: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ }),
+ tournament: PropTypes.shape({
+ participants: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ type: PropTypes.string,
+ users: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ }),
+};
+
+export default List;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import ReportDialog from './ReportDialog';
-import Icon from '../common/Icon';
-import { findResult } from '../../helpers/User';
-import i18n from '../../i18n';
-
-const getButtonLabel = (user, round) => {
- const result = findResult(user, round);
- if (round.locked) {
- if (result && result.comment) {
- return i18n.t('results.editComment');
- } else {
- return i18n.t('results.addComment');
- }
- } else {
- if (result && (result.time || result.forfeit)) {
- return i18n.t('results.edit');
- } else {
- return i18n.t('results.report');
- }
- }
-};
-
-const ReportButton = ({ round, user }) => {
- const [showDialog, setShowDialog] = useState(false);
-
- if (round.locked && !findResult(user, round)) {
- return null;
- }
-
- return <>
- <Button
- onClick={() => setShowDialog(true)}
- variant="secondary"
- >
- {getButtonLabel(user, round)}
- {' '}
- <Icon.EDIT title="" />
- </Button>
- <ReportDialog
- onHide={() => setShowDialog(false)}
- round={round}
- show={showDialog}
- user={user}
- />
- </>;
-};
-
-ReportButton.propTypes = {
- round: PropTypes.shape({
- locked: PropTypes.bool,
- }),
- tournament: PropTypes.shape({
- }),
- user: PropTypes.shape({
- }),
-};
-
-export default withTranslation()(ReportButton);
--- /dev/null
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import ReportDialog from './ReportDialog';
+import Icon from '../common/Icon';
+import { findResult } from '../../helpers/User';
+import i18n from '../../i18n';
+
+const getButtonLabel = (user, round) => {
+ const result = findResult(user, round);
+ if (round.locked) {
+ if (result && result.comment) {
+ return i18n.t('results.editComment');
+ } else {
+ return i18n.t('results.addComment');
+ }
+ } else {
+ if (result && (result.time || result.forfeit)) {
+ return i18n.t('results.edit');
+ } else {
+ return i18n.t('results.report');
+ }
+ }
+};
+
+const ReportButton = ({ round, user }) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ if (round.locked && !findResult(user, round)) {
+ return null;
+ }
+
+ return <>
+ <Button
+ onClick={() => setShowDialog(true)}
+ variant="secondary"
+ >
+ {getButtonLabel(user, round)}
+ {' '}
+ <Icon.EDIT title="" />
+ </Button>
+ <ReportDialog
+ onHide={() => setShowDialog(false)}
+ round={round}
+ show={showDialog}
+ user={user}
+ />
+ </>;
+};
+
+ReportButton.propTypes = {
+ round: PropTypes.shape({
+ locked: PropTypes.bool,
+ }),
+ tournament: PropTypes.shape({
+ }),
+ user: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(ReportButton);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Modal } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import ReportForm from './ReportForm';
-import i18n from '../../i18n';
-
-const ReportDialog = ({
- onHide,
- round,
- show,
- user,
-}) =>
-<Modal className="report-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {i18n.t('results.report')}
- </Modal.Title>
- </Modal.Header>
- <ReportForm
- onCancel={onHide}
- round={round}
- user={user}
- />
-</Modal>;
-
-ReportDialog.propTypes = {
- onHide: PropTypes.func,
- round: PropTypes.shape({
- }),
- show: PropTypes.bool,
- tournament: PropTypes.shape({
- }),
- user: PropTypes.shape({
- }),
-};
-
-export default withTranslation()(ReportDialog);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import ReportForm from './ReportForm';
+import i18n from '../../i18n';
+
+const ReportDialog = ({
+ onHide,
+ round,
+ show,
+ user,
+}) =>
+<Modal className="report-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {i18n.t('results.report')}
+ </Modal.Title>
+ </Modal.Header>
+ <ReportForm
+ onCancel={onHide}
+ round={round}
+ user={user}
+ />
+</Modal>;
+
+ReportDialog.propTypes = {
+ onHide: PropTypes.func,
+ round: PropTypes.shape({
+ }),
+ show: PropTypes.bool,
+ tournament: PropTypes.shape({
+ }),
+ user: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(ReportDialog);
+++ /dev/null
-import axios from 'axios';
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import LargeCheck from '../common/LargeCheck';
-import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
-import { findResult } from '../../helpers/User';
-import { formatTime, parseTime } from '../../helpers/Result';
-import i18n from '../../i18n';
-import yup from '../../schema/yup';
-
-const ReportForm = ({
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- onCancel,
- round,
- touched,
- values,
-}) =>
-<Form noValidate onSubmit={handleSubmit}>
- <Modal.Body>
- {!round.locked ?
- <Row>
- <Form.Group as={Col} sm={9} controlId="report.time">
- <Form.Label>{i18n.t('results.reportTime')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.time && errors.time)}
- name="time"
- onBlur={handleBlur}
- onChange={handleChange}
- placeholder={values.forfeit ? 'DNF' : '1:22:59'}
- type="text"
- value={values.time || ''}
- />
- {touched.time && errors.time ?
- <Form.Control.Feedback type="invalid">
- {i18n.t(errors.time)}
- </Form.Control.Feedback>
- :
- <Form.Text muted>
- {parseTime(values.time) ?
- i18n.t(
- 'results.reportPreview',
- { time: formatTime({ time: parseTime(values.time) })},
- )
- : null}
- </Form.Text>
- }
- </Form.Group>
- <Form.Group as={Col} sm={3} controlId="report.forfeit">
- <Form.Label>{i18n.t('results.forfeit')}</Form.Label>
- <Form.Control
- as={LargeCheck}
- isInvalid={!!(touched.forfeit && errors.forfeit)}
- name="forfeit"
- onBlur={handleBlur}
- onChange={handleChange}
- value={!!values.forfeit}
- />
- </Form.Group>
- </Row>
- : null}
- <Form.Group controlId="report.vod">
- <Form.Label>{i18n.t('results.vod')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.vod && errors.vod)}
- name="vod"
- onBlur={handleBlur}
- onChange={handleChange}
- placeholder="https://twitch.tv/youtube"
- type="text"
- value={values.vod || ''}
- />
- {touched.vod && errors.vod ?
- <Form.Control.Feedback type="invalid">
- {i18n.t(errors.vod)}
- </Form.Control.Feedback>
- :
- <Form.Text muted>
- {i18n.t('results.vodNote')}
- </Form.Text>
- }
- </Form.Group>
- <Form.Group controlId="report.comment">
- <Form.Label>{i18n.t('results.comment')}</Form.Label>
- <Form.Control
- as="textarea"
- isInvalid={!!(touched.comment && errors.comment)}
- name="comment"
- onBlur={handleBlur}
- onChange={handleChange}
- rows="6"
- value={values.comment || ''}
- />
- </Form.Group>
- </Modal.Body>
- <Modal.Footer>
- {onCancel ?
- <Button onClick={onCancel} variant="secondary">
- {i18n.t('button.cancel')}
- </Button>
- : null}
- <Button type="submit" variant="primary">
- {i18n.t('button.save')}
- </Button>
- </Modal.Footer>
-</Form>;
-
-ReportForm.propTypes = {
- errors: PropTypes.shape({
- comment: PropTypes.string,
- forfeit: PropTypes.string,
- time: PropTypes.string,
- vod: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- onCancel: PropTypes.func,
- round: PropTypes.shape({
- locked: PropTypes.bool,
- }),
- touched: PropTypes.shape({
- comment: PropTypes.bool,
- forfeit: PropTypes.bool,
- time: PropTypes.bool,
- vod: PropTypes.bool,
- }),
- values: PropTypes.shape({
- comment: PropTypes.string,
- forfeit: PropTypes.bool,
- time: PropTypes.string,
- vod: PropTypes.string,
- }),
-};
-
-export default withFormik({
- displayName: 'ReportForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { comment, forfeit, round_id, time, user_id, vod } = values;
- const { setErrors } = actions;
- const { onCancel } = actions.props;
- try {
- await axios.post('/api/results', {
- comment,
- forfeit,
- round_id,
- time: parseTime(time) || 0,
- user_id,
- vod,
- });
- toastr.success(i18n.t('results.reportSuccess'));
- if (onCancel) {
- onCancel();
- }
- } catch (e) {
- toastr.error(i18n.t('results.reportError'));
- if (e.response && e.response.data && e.response.data.errors) {
- setErrors(laravelErrorsToFormik(e.response.data.errors));
- }
- }
- },
- mapPropsToValues: ({ round, user }) => {
- const result = findResult(user, round);
- return {
- comment: result && result.comment ? result.comment : '',
- forfeit: result ? !!result.forfeit : false,
- round_id: round.id,
- time: result && result.time ? formatTime(result) : '',
- user_id: user.id,
- vod: result && result.vod ? result.vod : '',
- };
- },
- validationSchema: yup.object().shape({
- comment: yup.string(),
- forfeit: yup.boolean().required(),
- time: yup.string().time().when('forfeit', {
- is: false,
- then: () => yup.string().required().time(),
- }),
- vod: yup.string().url(),
- }),
-})(withTranslation()(ReportForm));
--- /dev/null
+import axios from 'axios';
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import LargeCheck from '../common/LargeCheck';
+import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import { findResult } from '../../helpers/User';
+import { formatTime, parseTime } from '../../helpers/Result';
+import i18n from '../../i18n';
+import yup from '../../schema/yup';
+
+const ReportForm = ({
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ onCancel,
+ round,
+ touched,
+ values,
+}) =>
+<Form noValidate onSubmit={handleSubmit}>
+ <Modal.Body>
+ {!round.locked ?
+ <Row>
+ <Form.Group as={Col} sm={9} controlId="report.time">
+ <Form.Label>{i18n.t('results.reportTime')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.time && errors.time)}
+ name="time"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ placeholder={values.forfeit ? 'DNF' : '1:22:59'}
+ type="text"
+ value={values.time || ''}
+ />
+ {touched.time && errors.time ?
+ <Form.Control.Feedback type="invalid">
+ {i18n.t(errors.time)}
+ </Form.Control.Feedback>
+ :
+ <Form.Text muted>
+ {parseTime(values.time) ?
+ i18n.t(
+ 'results.reportPreview',
+ { time: formatTime({ time: parseTime(values.time) })},
+ )
+ : null}
+ </Form.Text>
+ }
+ </Form.Group>
+ <Form.Group as={Col} sm={3} controlId="report.forfeit">
+ <Form.Label>{i18n.t('results.forfeit')}</Form.Label>
+ <Form.Control
+ as={LargeCheck}
+ isInvalid={!!(touched.forfeit && errors.forfeit)}
+ name="forfeit"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={!!values.forfeit}
+ />
+ </Form.Group>
+ </Row>
+ : null}
+ <Form.Group controlId="report.vod">
+ <Form.Label>{i18n.t('results.vod')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.vod && errors.vod)}
+ name="vod"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ placeholder="https://twitch.tv/youtube"
+ type="text"
+ value={values.vod || ''}
+ />
+ {touched.vod && errors.vod ?
+ <Form.Control.Feedback type="invalid">
+ {i18n.t(errors.vod)}
+ </Form.Control.Feedback>
+ :
+ <Form.Text muted>
+ {i18n.t('results.vodNote')}
+ </Form.Text>
+ }
+ </Form.Group>
+ <Form.Group controlId="report.comment">
+ <Form.Label>{i18n.t('results.comment')}</Form.Label>
+ <Form.Control
+ as="textarea"
+ isInvalid={!!(touched.comment && errors.comment)}
+ name="comment"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ rows="6"
+ value={values.comment || ''}
+ />
+ </Form.Group>
+ </Modal.Body>
+ <Modal.Footer>
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {i18n.t('button.cancel')}
+ </Button>
+ : null}
+ <Button type="submit" variant="primary">
+ {i18n.t('button.save')}
+ </Button>
+ </Modal.Footer>
+</Form>;
+
+ReportForm.propTypes = {
+ errors: PropTypes.shape({
+ comment: PropTypes.string,
+ forfeit: PropTypes.string,
+ time: PropTypes.string,
+ vod: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ onCancel: PropTypes.func,
+ round: PropTypes.shape({
+ locked: PropTypes.bool,
+ }),
+ touched: PropTypes.shape({
+ comment: PropTypes.bool,
+ forfeit: PropTypes.bool,
+ time: PropTypes.bool,
+ vod: PropTypes.bool,
+ }),
+ values: PropTypes.shape({
+ comment: PropTypes.string,
+ forfeit: PropTypes.bool,
+ time: PropTypes.string,
+ vod: PropTypes.string,
+ }),
+};
+
+export default withFormik({
+ displayName: 'ReportForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { comment, forfeit, round_id, time, user_id, vod } = values;
+ const { setErrors } = actions;
+ const { onCancel } = actions.props;
+ try {
+ await axios.post('/api/results', {
+ comment,
+ forfeit,
+ round_id,
+ time: parseTime(time) || 0,
+ user_id,
+ vod,
+ });
+ toastr.success(i18n.t('results.reportSuccess'));
+ if (onCancel) {
+ onCancel();
+ }
+ } catch (e) {
+ toastr.error(i18n.t('results.reportError'));
+ if (e.response && e.response.data && e.response.data.errors) {
+ setErrors(laravelErrorsToFormik(e.response.data.errors));
+ }
+ }
+ },
+ mapPropsToValues: ({ round, user }) => {
+ const result = findResult(user, round);
+ return {
+ comment: result && result.comment ? result.comment : '',
+ forfeit: result ? !!result.forfeit : false,
+ round_id: round.id,
+ time: result && result.time ? formatTime(result) : '',
+ user_id: user.id,
+ vod: result && result.vod ? result.vod : '',
+ };
+ },
+ validationSchema: yup.object().shape({
+ comment: yup.string(),
+ forfeit: yup.boolean().required(),
+ time: yup.string().time().when('forfeit', {
+ is: false,
+ then: () => yup.string().required().time(),
+ }),
+ vod: yup.string().url(),
+ }),
+})(withTranslation()(ReportForm));
+++ /dev/null
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import DeleteDialog from './DeleteDialog';
-import Icon from '../common/Icon';
-
-const DeleteButton = ({
- round,
- tournament,
-}) => {
- const [showDialog, setShowDialog] = useState(false);
-
- const { t } = useTranslation();
-
- return <>
- <DeleteDialog
- onHide={() => setShowDialog(false)}
- round={round}
- show={showDialog}
- tournament={tournament}
- />
- <Button
- onClick={() => setShowDialog(true)}
- size="sm"
- title={t('rounds.delete')}
- variant="outline-danger"
- >
- <Icon.REMOVE title="" />
- </Button>
- </>;
-};
-
-DeleteButton.propTypes = {
- round: PropTypes.shape({
- locked: PropTypes.bool,
- }),
- tournament: PropTypes.shape({
- }),
-};
-
-export default DeleteButton;
--- /dev/null
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import DeleteDialog from './DeleteDialog';
+import Icon from '../common/Icon';
+
+const DeleteButton = ({
+ round,
+ tournament,
+}) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ const { t } = useTranslation();
+
+ return <>
+ <DeleteDialog
+ onHide={() => setShowDialog(false)}
+ round={round}
+ show={showDialog}
+ tournament={tournament}
+ />
+ <Button
+ onClick={() => setShowDialog(true)}
+ size="sm"
+ title={t('rounds.delete')}
+ variant="outline-danger"
+ >
+ <Icon.REMOVE title="" />
+ </Button>
+ </>;
+};
+
+DeleteButton.propTypes = {
+ round: PropTypes.shape({
+ locked: PropTypes.bool,
+ }),
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default DeleteButton;
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Alert, Button, Modal } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-const EditDialog = ({
- onHide,
- round,
- show,
-}) => {
- const { t } = useTranslation();
-
- const handleDelete = React.useCallback(async() => {
- try {
- await axios.delete(`/api/rounds/${round.id}`);
- onHide();
- } catch (e) {
- toastr.error(t('rounds.deleteError'));
- }
- }, [onHide, round, t]);
-
- return <Modal className="edit-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {t('rounds.delete')}
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <Alert variant="danger">
- {t('rounds.deleteConfirmMessage', {
- ...round,
- date: new Date(round.created_at),
- })}
- </Alert>
- </Modal.Body>
- <Modal.Footer>
- <Button onClick={onHide} variant="secondary">
- {t('button.cancel')}
- </Button>
- <Button onClick={handleDelete} variant="danger">
- {t('button.delete')}
- </Button>
- </Modal.Footer>
- </Modal>;
-};
-
-EditDialog.propTypes = {
- onHide: PropTypes.func,
- round: PropTypes.shape({
- created_at: PropTypes.string,
- id: PropTypes.number,
- }),
- show: PropTypes.bool,
- tournament: PropTypes.shape({
- }),
-};
-
-export default EditDialog;
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Alert, Button, Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+const EditDialog = ({
+ onHide,
+ round,
+ show,
+}) => {
+ const { t } = useTranslation();
+
+ const handleDelete = React.useCallback(async() => {
+ try {
+ await axios.delete(`/api/rounds/${round.id}`);
+ onHide();
+ } catch (e) {
+ toastr.error(t('rounds.deleteError'));
+ }
+ }, [onHide, round, t]);
+
+ return <Modal className="edit-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {t('rounds.delete')}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Alert variant="danger">
+ {t('rounds.deleteConfirmMessage', {
+ ...round,
+ date: new Date(round.created_at),
+ })}
+ </Alert>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button onClick={onHide} variant="secondary">
+ {t('button.cancel')}
+ </Button>
+ <Button onClick={handleDelete} variant="danger">
+ {t('button.delete')}
+ </Button>
+ </Modal.Footer>
+ </Modal>;
+};
+
+EditDialog.propTypes = {
+ onHide: PropTypes.func,
+ round: PropTypes.shape({
+ created_at: PropTypes.string,
+ id: PropTypes.number,
+ }),
+ show: PropTypes.bool,
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default EditDialog;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import EditDialog from './EditDialog';
-import Icon from '../common/Icon';
-
-const EditButton = ({
- round,
- tournament,
-}) => {
- const [showDialog, setShowDialog] = useState(false);
-
- const { t } = useTranslation();
-
- return <>
- <EditDialog
- onHide={() => setShowDialog(false)}
- round={round}
- show={showDialog}
- tournament={tournament}
- />
- <Button
- onClick={() => setShowDialog(true)}
- size="sm"
- title={t('rounds.edit')}
- variant="outline-secondary"
- >
- <Icon.EDIT title="" />
- </Button>
- </>;
-};
-
-EditButton.propTypes = {
- round: PropTypes.shape({
- locked: PropTypes.bool,
- }),
- tournament: PropTypes.shape({
- }),
-};
-
-export default EditButton;
--- /dev/null
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import EditDialog from './EditDialog';
+import Icon from '../common/Icon';
+
+const EditButton = ({
+ round,
+ tournament,
+}) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ const { t } = useTranslation();
+
+ return <>
+ <EditDialog
+ onHide={() => setShowDialog(false)}
+ round={round}
+ show={showDialog}
+ tournament={tournament}
+ />
+ <Button
+ onClick={() => setShowDialog(true)}
+ size="sm"
+ title={t('rounds.edit')}
+ variant="outline-secondary"
+ >
+ <Icon.EDIT title="" />
+ </Button>
+ </>;
+};
+
+EditButton.propTypes = {
+ round: PropTypes.shape({
+ locked: PropTypes.bool,
+ }),
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default EditButton;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Modal } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import EditForm from './EditForm';
-
-const EditDialog = ({
- onHide,
- round,
- show,
-}) => {
- const { t } = useTranslation();
-
- return <Modal className="edit-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {t('rounds.edit')}
- </Modal.Title>
- </Modal.Header>
- <EditForm
- onCancel={onHide}
- round={round}
- />
- </Modal>;
-};
-
-EditDialog.propTypes = {
- onHide: PropTypes.func,
- round: PropTypes.shape({
- }),
- show: PropTypes.bool,
- tournament: PropTypes.shape({
- }),
-};
-
-export default EditDialog;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import EditForm from './EditForm';
+
+const EditDialog = ({
+ onHide,
+ round,
+ show,
+}) => {
+ const { t } = useTranslation();
+
+ return <Modal className="edit-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {t('rounds.edit')}
+ </Modal.Title>
+ </Modal.Header>
+ <EditForm
+ onCancel={onHide}
+ round={round}
+ />
+ </Modal>;
+};
+
+EditDialog.propTypes = {
+ onHide: PropTypes.func,
+ round: PropTypes.shape({
+ }),
+ show: PropTypes.bool,
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default EditDialog;
+++ /dev/null
-import axios from 'axios';
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import SeedCodeInput from './SeedCodeInput';
-import UserSelect from '../common/UserSelect';
-import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
-import i18n from '../../i18n';
-import yup from '../../schema/yup';
-
-const EditForm = ({
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- onCancel,
- touched,
- values,
-}) =>
-<Form noValidate onSubmit={handleSubmit}>
- <Modal.Body>
- <Row>
- <Form.Group as={Col} controlId="round.title">
- <Form.Label>{i18n.t('rounds.title')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.title && errors.title)}
- name="title"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.title || ''}
- />
- {touched.title && errors.title ?
- <Form.Control.Feedback type="invalid">
- {i18n.t(errors.title)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Row>
- <Row>
- <Form.Group as={Col} controlId="round.seed">
- <Form.Label>{i18n.t('rounds.seed')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.seed && errors.seed)}
- name="seed"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.seed || ''}
- />
- {touched.seed && errors.seed ?
- <Form.Control.Feedback type="invalid">
- {i18n.t(errors.seed)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Row>
- <Row>
- <Form.Group as={Col} controlId="round.spoiler">
- <Form.Label>{i18n.t('rounds.spoiler')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.spoiler && errors.spoiler)}
- name="spoiler"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.spoiler || ''}
- />
- {touched.spoiler && errors.spoiler ?
- <Form.Control.Feedback type="invalid">
- {i18n.t(errors.spoiler)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Row>
- <Row>
- <Form.Group as={Col}>
- <Form.Label>{i18n.t('rounds.code')}</Form.Label>
- <Form.Control
- as={SeedCodeInput}
- game={values.game || 'mixed'}
- isInvalid={!!(touched.code && errors.code)}
- name="code"
- onBlur={handleBlur}
- onChange={handleChange}
- value={values.code || []}
- />
- {touched.code && errors.code ?
- <Form.Control.Feedback type="invalid">
- {i18n.t(errors.code)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Row>
- <Row>
- <Form.Group as={Col}>
- <Form.Label>{i18n.t('rounds.rolled_by')}</Form.Label>
- <Form.Control
- as={UserSelect}
- isInvalid={!!(touched.rolled_by && errors.rolled_by)}
- name="rolled_by"
- onBlur={handleBlur}
- onChange={handleChange}
- value={values.rolled_by || null}
- />
- {touched.rolled_by && errors.rolled_by ?
- <Form.Control.Feedback type="invalid">
- {i18n.t(errors.rolled_by)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Row>
- </Modal.Body>
- <Modal.Footer>
- {onCancel ?
- <Button onClick={onCancel} variant="secondary">
- {i18n.t('button.cancel')}
- </Button>
- : null}
- <Button type="submit" variant="primary">
- {i18n.t('button.save')}
- </Button>
- </Modal.Footer>
-</Form>;
-
-EditForm.propTypes = {
- errors: PropTypes.shape({
- code: PropTypes.arrayOf(PropTypes.string),
- rolled_by: PropTypes.string,
- seed: PropTypes.string,
- spoiler: PropTypes.string,
- title: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- onCancel: PropTypes.func,
- touched: PropTypes.shape({
- code: PropTypes.arrayOf(PropTypes.bool),
- rolled_by: PropTypes.bool,
- seed: PropTypes.bool,
- spoiler: PropTypes.bool,
- title: PropTypes.bool,
- }),
- values: PropTypes.shape({
- code: PropTypes.arrayOf(PropTypes.string),
- game: PropTypes.string,
- rolled_by: PropTypes.string,
- seed: PropTypes.string,
- spoiler: PropTypes.string,
- title: PropTypes.string,
- }),
-};
-
-export default withFormik({
- displayName: 'EditForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { round_id } = values;
- const { setErrors } = actions;
- const { onCancel } = actions.props;
- try {
- await axios.put(`/api/rounds/${round_id}`, values);
- toastr.success(i18n.t('rounds.editSuccess'));
- if (onCancel) {
- onCancel();
- }
- } catch (e) {
- toastr.error(i18n.t('rounds.editError'));
- if (e.response && e.response.data && e.response.data.errors) {
- setErrors(laravelErrorsToFormik(e.response.data.errors));
- }
- }
- },
- mapPropsToValues: ({ round }) => ({
- code: round.code || [],
- game: round.game || 'mixed',
- rolled_by: round.rolled_by || null,
- round_id: round.id,
- seed: round.seed || '',
- spoiler: round.spoiler || '',
- title: round.title || '',
- }),
- validationSchema: yup.object().shape({
- code: yup.array().of(yup.string()),
- rolled_by: yup.string().nullable(),
- seed: yup.string().url(),
- spoiler: yup.string().url(),
- title: yup.string(),
- }),
-})(withTranslation()(EditForm));
--- /dev/null
+import axios from 'axios';
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import SeedCodeInput from './SeedCodeInput';
+import UserSelect from '../common/UserSelect';
+import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import i18n from '../../i18n';
+import yup from '../../schema/yup';
+
+const EditForm = ({
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ onCancel,
+ touched,
+ values,
+}) =>
+<Form noValidate onSubmit={handleSubmit}>
+ <Modal.Body>
+ <Row>
+ <Form.Group as={Col} controlId="round.title">
+ <Form.Label>{i18n.t('rounds.title')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.title && errors.title)}
+ name="title"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.title || ''}
+ />
+ {touched.title && errors.title ?
+ <Form.Control.Feedback type="invalid">
+ {i18n.t(errors.title)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Row>
+ <Row>
+ <Form.Group as={Col} controlId="round.seed">
+ <Form.Label>{i18n.t('rounds.seed')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.seed && errors.seed)}
+ name="seed"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.seed || ''}
+ />
+ {touched.seed && errors.seed ?
+ <Form.Control.Feedback type="invalid">
+ {i18n.t(errors.seed)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Row>
+ <Row>
+ <Form.Group as={Col} controlId="round.spoiler">
+ <Form.Label>{i18n.t('rounds.spoiler')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.spoiler && errors.spoiler)}
+ name="spoiler"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.spoiler || ''}
+ />
+ {touched.spoiler && errors.spoiler ?
+ <Form.Control.Feedback type="invalid">
+ {i18n.t(errors.spoiler)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Row>
+ <Row>
+ <Form.Group as={Col}>
+ <Form.Label>{i18n.t('rounds.code')}</Form.Label>
+ <Form.Control
+ as={SeedCodeInput}
+ game={values.game || 'mixed'}
+ isInvalid={!!(touched.code && errors.code)}
+ name="code"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.code || []}
+ />
+ {touched.code && errors.code ?
+ <Form.Control.Feedback type="invalid">
+ {i18n.t(errors.code)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Row>
+ <Row>
+ <Form.Group as={Col}>
+ <Form.Label>{i18n.t('rounds.rolled_by')}</Form.Label>
+ <Form.Control
+ as={UserSelect}
+ isInvalid={!!(touched.rolled_by && errors.rolled_by)}
+ name="rolled_by"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.rolled_by || null}
+ />
+ {touched.rolled_by && errors.rolled_by ?
+ <Form.Control.Feedback type="invalid">
+ {i18n.t(errors.rolled_by)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {i18n.t('button.cancel')}
+ </Button>
+ : null}
+ <Button type="submit" variant="primary">
+ {i18n.t('button.save')}
+ </Button>
+ </Modal.Footer>
+</Form>;
+
+EditForm.propTypes = {
+ errors: PropTypes.shape({
+ code: PropTypes.arrayOf(PropTypes.string),
+ rolled_by: PropTypes.string,
+ seed: PropTypes.string,
+ spoiler: PropTypes.string,
+ title: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ onCancel: PropTypes.func,
+ touched: PropTypes.shape({
+ code: PropTypes.arrayOf(PropTypes.bool),
+ rolled_by: PropTypes.bool,
+ seed: PropTypes.bool,
+ spoiler: PropTypes.bool,
+ title: PropTypes.bool,
+ }),
+ values: PropTypes.shape({
+ code: PropTypes.arrayOf(PropTypes.string),
+ game: PropTypes.string,
+ rolled_by: PropTypes.string,
+ seed: PropTypes.string,
+ spoiler: PropTypes.string,
+ title: PropTypes.string,
+ }),
+};
+
+export default withFormik({
+ displayName: 'EditForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { round_id } = values;
+ const { setErrors } = actions;
+ const { onCancel } = actions.props;
+ try {
+ await axios.put(`/api/rounds/${round_id}`, values);
+ toastr.success(i18n.t('rounds.editSuccess'));
+ if (onCancel) {
+ onCancel();
+ }
+ } catch (e) {
+ toastr.error(i18n.t('rounds.editError'));
+ if (e.response && e.response.data && e.response.data.errors) {
+ setErrors(laravelErrorsToFormik(e.response.data.errors));
+ }
+ }
+ },
+ mapPropsToValues: ({ round }) => ({
+ code: round.code || [],
+ game: round.game || 'mixed',
+ rolled_by: round.rolled_by || null,
+ round_id: round.id,
+ seed: round.seed || '',
+ spoiler: round.spoiler || '',
+ title: round.title || '',
+ }),
+ validationSchema: yup.object().shape({
+ code: yup.array().of(yup.string()),
+ rolled_by: yup.string().nullable(),
+ seed: yup.string().url(),
+ spoiler: yup.string().url(),
+ title: yup.string(),
+ }),
+})(withTranslation()(EditForm));
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-
-import DeleteButton from './DeleteButton';
-import EditButton from './EditButton';
-import LockButton from './LockButton';
-import SeedButton from './SeedButton';
-import SeedCode from './SeedCode';
-import SeedRolledBy from './SeedRolledBy';
-import RoundProtocol from '../protocol/RoundProtocol';
-import List from '../results/List';
-import ReportButton from '../results/ReportButton';
-import {
- mayDeleteRound,
- mayEditRound,
- mayReportResult,
- mayViewProtocol,
- isRunner,
-} from '../../helpers/permissions';
-import { isComplete } from '../../helpers/Round';
-import { hasFinishedRound } from '../../helpers/User';
-import { useUser } from '../../hooks/user';
-
-const getClassName = (round, tournament, user) => {
- const classNames = ['round'];
- if (round.locked) {
- classNames.push('is-locked');
- } else {
- classNames.push('is-unlocked');
- }
- if (isComplete(tournament, round)) {
- classNames.push('is-complete');
- } else {
- classNames.push('is-incomplete');
- }
- if (hasFinishedRound(user, round)) {
- classNames.push('has-finished');
- } else if (isRunner(user, tournament)) {
- classNames.push('has-not-finished');
- }
- return classNames.join(' ');
-};
-
-const Item = ({
- round,
- tournament,
-}) => {
- const { t } = useTranslation();
- const { user } = useUser();
-
- return <li className={getClassName(round, tournament, user)}>
- {round.title ?
- <h3>{round.title}</h3>
- : null}
- <div className="d-flex">
- <div className="info">
- <p className="date">
- {tournament.show_numbers && round.number ? `#${round.number} ` : ''}
- {t('rounds.date', { date: new Date(round.created_at) })}
- </p>
- <p className="seed">
- {round.code && round.code.length ?
- <>
- <SeedCode code={round.code} game={round.game || 'alttpr'} />
- <br />
- </>
- : null}
- <SeedButton
- round={round}
- tournament={tournament}
- />
- {' '}
- <SeedRolledBy round={round} />
- </p>
- {mayReportResult(user, tournament) ?
- <p className="report">
- <ReportButton
- round={round}
- tournament={tournament}
- user={user}
- />
- </p>
- : null}
- <div className="bottom-half">
- {tournament.type === 'open-async' && round.results && round.results.length ?
- <p>{t('rounds.numberOfResults', { count: round.results.length })}</p>
- : null}
- <div className="button-bar">
- <LockButton round={round} tournament={tournament} />
- {mayEditRound(user, tournament, round) ?
- <EditButton round={round} tournament={tournament} />
- : null}
- {mayViewProtocol(user, tournament, round) ?
- <RoundProtocol roundId={round.id} tournamentId={tournament.id} />
- : null}
- {mayDeleteRound(user, tournament, round) ?
- <DeleteButton round={round} tournament={tournament} />
- : null}
- </div>
- </div>
- </div>
- <List round={round} tournament={tournament} />
- </div>
- </li>;
-};
-
-Item.propTypes = {
- round: PropTypes.shape({
- code: PropTypes.arrayOf(PropTypes.string),
- created_at: PropTypes.string,
- game: PropTypes.string,
- id: PropTypes.number,
- locked: PropTypes.bool,
- number: PropTypes.number,
- results: PropTypes.arrayOf(PropTypes.shape({
- })),
- seed: PropTypes.string,
- title: PropTypes.string,
- }),
- tournament: PropTypes.shape({
- participants: PropTypes.arrayOf(PropTypes.shape({
- })),
- id: PropTypes.number,
- show_numbers: PropTypes.bool,
- type: PropTypes.string,
- }),
-};
-
-export default Item;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import DeleteButton from './DeleteButton';
+import EditButton from './EditButton';
+import LockButton from './LockButton';
+import SeedButton from './SeedButton';
+import SeedCode from './SeedCode';
+import SeedRolledBy from './SeedRolledBy';
+import RoundProtocol from '../protocol/RoundProtocol';
+import List from '../results/List';
+import ReportButton from '../results/ReportButton';
+import {
+ mayDeleteRound,
+ mayEditRound,
+ mayReportResult,
+ mayViewProtocol,
+ isRunner,
+} from '../../helpers/permissions';
+import { isComplete } from '../../helpers/Round';
+import { hasFinishedRound } from '../../helpers/User';
+import { useUser } from '../../hooks/user';
+
+const getClassName = (round, tournament, user) => {
+ const classNames = ['round'];
+ if (round.locked) {
+ classNames.push('is-locked');
+ } else {
+ classNames.push('is-unlocked');
+ }
+ if (isComplete(tournament, round)) {
+ classNames.push('is-complete');
+ } else {
+ classNames.push('is-incomplete');
+ }
+ if (hasFinishedRound(user, round)) {
+ classNames.push('has-finished');
+ } else if (isRunner(user, tournament)) {
+ classNames.push('has-not-finished');
+ }
+ return classNames.join(' ');
+};
+
+const Item = ({
+ round,
+ tournament,
+}) => {
+ const { t } = useTranslation();
+ const { user } = useUser();
+
+ return <li className={getClassName(round, tournament, user)}>
+ {round.title ?
+ <h3>{round.title}</h3>
+ : null}
+ <div className="d-flex">
+ <div className="info">
+ <p className="date">
+ {tournament.show_numbers && round.number ? `#${round.number} ` : ''}
+ {t('rounds.date', { date: new Date(round.created_at) })}
+ </p>
+ <p className="seed">
+ {round.code && round.code.length ?
+ <>
+ <SeedCode code={round.code} game={round.game || 'alttpr'} />
+ <br />
+ </>
+ : null}
+ <SeedButton
+ round={round}
+ tournament={tournament}
+ />
+ {' '}
+ <SeedRolledBy round={round} />
+ </p>
+ {mayReportResult(user, tournament) ?
+ <p className="report">
+ <ReportButton
+ round={round}
+ tournament={tournament}
+ user={user}
+ />
+ </p>
+ : null}
+ <div className="bottom-half">
+ {tournament.type === 'open-async' && round.results && round.results.length ?
+ <p>{t('rounds.numberOfResults', { count: round.results.length })}</p>
+ : null}
+ <div className="button-bar">
+ <LockButton round={round} tournament={tournament} />
+ {mayEditRound(user, tournament, round) ?
+ <EditButton round={round} tournament={tournament} />
+ : null}
+ {mayViewProtocol(user, tournament, round) ?
+ <RoundProtocol roundId={round.id} tournamentId={tournament.id} />
+ : null}
+ {mayDeleteRound(user, tournament, round) ?
+ <DeleteButton round={round} tournament={tournament} />
+ : null}
+ </div>
+ </div>
+ </div>
+ <List round={round} tournament={tournament} />
+ </div>
+ </li>;
+};
+
+Item.propTypes = {
+ round: PropTypes.shape({
+ code: PropTypes.arrayOf(PropTypes.string),
+ created_at: PropTypes.string,
+ game: PropTypes.string,
+ id: PropTypes.number,
+ locked: PropTypes.bool,
+ number: PropTypes.number,
+ results: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ seed: PropTypes.string,
+ title: PropTypes.string,
+ }),
+ tournament: PropTypes.shape({
+ participants: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ id: PropTypes.number,
+ show_numbers: PropTypes.bool,
+ type: PropTypes.string,
+ }),
+};
+
+export default Item;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Alert } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import Item from './Item';
-import LoadMore from './LoadMore';
-import i18n from '../../i18n';
-
-const List = ({
- loadMore,
- rounds,
- tournament,
-}) => rounds && rounds.length ? <>
- <ol className="rounds">
- {rounds.map(round =>
- <Item
- key={round.id}
- round={round}
- tournament={tournament}
- />
- )}
- </ol>
- {loadMore ?
- <LoadMore loadMore={loadMore} />
- : null}
-</> :
- <Alert variant="info">
- {i18n.t('rounds.empty')}
- </Alert>
-;
-
-List.propTypes = {
- loadMore: PropTypes.func,
- rounds: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.number,
- })),
- tournament: PropTypes.shape({
- }),
-};
-
-export default withTranslation()(List);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Alert } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import Item from './Item';
+import LoadMore from './LoadMore';
+import i18n from '../../i18n';
+
+const List = ({
+ loadMore,
+ rounds,
+ tournament,
+}) => rounds && rounds.length ? <>
+ <ol className="rounds">
+ {rounds.map(round =>
+ <Item
+ key={round.id}
+ round={round}
+ tournament={tournament}
+ />
+ )}
+ </ol>
+ {loadMore ?
+ <LoadMore loadMore={loadMore} />
+ : null}
+</> :
+ <Alert variant="info">
+ {i18n.t('rounds.empty')}
+ </Alert>
+;
+
+List.propTypes = {
+ loadMore: PropTypes.func,
+ rounds: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.number,
+ })),
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(List);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-const LoadMore = ({ loadMore }) => {
- const [loading, setLoading] = React.useState(false);
-
- const { t } = useTranslation();
-
- const handleLoadMore = React.useCallback(async () => {
- setLoading(true);
- try {
- await loadMore();
- } finally {
- setLoading(false);
- }
- }, [loadMore]);
-
- return <div className="d-grid mt-2">
- <Button disabled={loading} onClick={handleLoadMore} variant="outline-secondary">
- {t('rounds.loadMore')}
- </Button>
- </div>;
-};
-
-LoadMore.propTypes = {
- loadMore: PropTypes.func,
-};
-
-export default LoadMore;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+const LoadMore = ({ loadMore }) => {
+ const [loading, setLoading] = React.useState(false);
+
+ const { t } = useTranslation();
+
+ const handleLoadMore = React.useCallback(async () => {
+ setLoading(true);
+ try {
+ await loadMore();
+ } finally {
+ setLoading(false);
+ }
+ }, [loadMore]);
+
+ return <div className="d-grid mt-2">
+ <Button disabled={loading} onClick={handleLoadMore} variant="outline-secondary">
+ {t('rounds.loadMore')}
+ </Button>
+ </div>;
+};
+
+LoadMore.propTypes = {
+ loadMore: PropTypes.func,
+};
+
+export default LoadMore;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import LockDialog from './LockDialog';
-import Icon from '../common/Icon';
-import { mayLockRound } from '../../helpers/permissions';
-import { useUser } from '../../hooks/user';
-
-const LockButton = ({
- round,
- tournament,
-}) => {
- const [showDialog, setShowDialog] = useState(false);
-
- const { t } = useTranslation();
- const { user } = useUser();
-
- if (!mayLockRound(user, tournament, round)) {
- if (round.locked) {
- return <Icon.LOCKED title={t('rounds.locked')} />;
- } else {
- return <Icon.UNLOCKED title={t('rounds.unlocked')} />;
- }
- }
-
- return <>
- <LockDialog
- onHide={() => setShowDialog(false)}
- round={round}
- show={showDialog}
- tournament={tournament}
- />
- <Button
- onClick={() => setShowDialog(true)}
- size="sm"
- title={t(round.locked ? 'rounds.locked' : 'rounds.unlocked')}
- variant="outline-secondary"
- >
- {round.locked ?
- <Icon.LOCKED title="" />
- :
- <Icon.UNLOCKED title="" />
- }
- </Button>
- </>;
-};
-
-LockButton.propTypes = {
- round: PropTypes.shape({
- locked: PropTypes.bool,
- }),
- tournament: PropTypes.shape({
- }),
-};
-
-export default LockButton;
--- /dev/null
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import LockDialog from './LockDialog';
+import Icon from '../common/Icon';
+import { mayLockRound } from '../../helpers/permissions';
+import { useUser } from '../../hooks/user';
+
+const LockButton = ({
+ round,
+ tournament,
+}) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ const { t } = useTranslation();
+ const { user } = useUser();
+
+ if (!mayLockRound(user, tournament, round)) {
+ if (round.locked) {
+ return <Icon.LOCKED title={t('rounds.locked')} />;
+ } else {
+ return <Icon.UNLOCKED title={t('rounds.unlocked')} />;
+ }
+ }
+
+ return <>
+ <LockDialog
+ onHide={() => setShowDialog(false)}
+ round={round}
+ show={showDialog}
+ tournament={tournament}
+ />
+ <Button
+ onClick={() => setShowDialog(true)}
+ size="sm"
+ title={t(round.locked ? 'rounds.locked' : 'rounds.unlocked')}
+ variant="outline-secondary"
+ >
+ {round.locked ?
+ <Icon.LOCKED title="" />
+ :
+ <Icon.UNLOCKED title="" />
+ }
+ </Button>
+ </>;
+};
+
+LockButton.propTypes = {
+ round: PropTypes.shape({
+ locked: PropTypes.bool,
+ }),
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default LockButton;
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Alert, Button, Modal } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import { isComplete } from '../../helpers/Round';
-import i18n from '../../i18n';
-
-const LockDialog = ({
- onHide,
- round,
- show,
- tournament,
-}) =>
-<Modal className="lock-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {i18n.t(round.locked ? 'rounds.unlock' : 'rounds.lock')}
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <p>{i18n.t(round.locked
- ? 'rounds.unlockDescription'
- : 'rounds.lockDescription')}
- </p>
- {!round.locked && tournament.type === 'signup-async' && !isComplete(tournament, round) ?
- <Alert variant="warning">
- {i18n.t('rounds.lockIncompleteWarning')}
- </Alert>
- : null}
- </Modal.Body>
- <Modal.Footer>
- {onHide ?
- <Button onClick={onHide} variant="secondary">
- {i18n.t('button.cancel')}
- </Button>
- : null}
- <Button
- onClick={async () => {
- if (round.locked) {
- try {
- await axios.post(`/api/rounds/${round.id}/unlock`);
- toastr.success(i18n.t('rounds.unlockSuccess'));
- onHide();
- } catch (e) {
- toastr.error(i18n.t('rounds.unlockError'));
- }
- } else {
- try {
- await axios.post(`/api/rounds/${round.id}/lock`);
- toastr.success(i18n.t('rounds.lockSuccess'));
- onHide();
- } catch (e) {
- toastr.error(i18n.t('rounds.lockError'));
- }
- }
- }}
- variant="primary"
- >
- {i18n.t(round.locked ? 'rounds.unlock' : 'rounds.lock')}
- </Button>
- </Modal.Footer>
-</Modal>;
-
-LockDialog.propTypes = {
- onHide: PropTypes.func,
- round: PropTypes.shape({
- id: PropTypes.number,
- locked: PropTypes.bool,
- }),
- show: PropTypes.bool,
- tournament: PropTypes.shape({
- type: PropTypes.string,
- }),
-};
-
-export default withTranslation()(LockDialog);
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Alert, Button, Modal } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import { isComplete } from '../../helpers/Round';
+import i18n from '../../i18n';
+
+const LockDialog = ({
+ onHide,
+ round,
+ show,
+ tournament,
+}) =>
+<Modal className="lock-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {i18n.t(round.locked ? 'rounds.unlock' : 'rounds.lock')}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <p>{i18n.t(round.locked
+ ? 'rounds.unlockDescription'
+ : 'rounds.lockDescription')}
+ </p>
+ {!round.locked && tournament.type === 'signup-async' && !isComplete(tournament, round) ?
+ <Alert variant="warning">
+ {i18n.t('rounds.lockIncompleteWarning')}
+ </Alert>
+ : null}
+ </Modal.Body>
+ <Modal.Footer>
+ {onHide ?
+ <Button onClick={onHide} variant="secondary">
+ {i18n.t('button.cancel')}
+ </Button>
+ : null}
+ <Button
+ onClick={async () => {
+ if (round.locked) {
+ try {
+ await axios.post(`/api/rounds/${round.id}/unlock`);
+ toastr.success(i18n.t('rounds.unlockSuccess'));
+ onHide();
+ } catch (e) {
+ toastr.error(i18n.t('rounds.unlockError'));
+ }
+ } else {
+ try {
+ await axios.post(`/api/rounds/${round.id}/lock`);
+ toastr.success(i18n.t('rounds.lockSuccess'));
+ onHide();
+ } catch (e) {
+ toastr.error(i18n.t('rounds.lockError'));
+ }
+ }
+ }}
+ variant="primary"
+ >
+ {i18n.t(round.locked ? 'rounds.unlock' : 'rounds.lock')}
+ </Button>
+ </Modal.Footer>
+</Modal>;
+
+LockDialog.propTypes = {
+ onHide: PropTypes.func,
+ round: PropTypes.shape({
+ id: PropTypes.number,
+ locked: PropTypes.bool,
+ }),
+ show: PropTypes.bool,
+ tournament: PropTypes.shape({
+ type: PropTypes.string,
+ }),
+};
+
+export default withTranslation()(LockDialog);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import SeedDialog from './SeedDialog';
-import { maySetSeed } from '../../helpers/permissions';
-import { useUser } from '../../hooks/user';
-
-const SeedButton = ({ round, tournament }) => {
- const [showDialog, setShowDialog] = useState(false);
-
- const { t } = useTranslation();
- const { user } = useUser();
-
- if (round.seed) {
- return <>
- <Button href={round.seed} target="_blank" variant="primary">
- {t('rounds.seed')}
- </Button>
- {round.spoiler ?
- <Button
- className="ms-2"
- href={round.spoiler}
- target="_blank"
- variant="outline-primary"
- >
- {t('rounds.spoiler')}
- </Button>
- : null}
- </>;
- }
- if (maySetSeed(user, tournament, round)) {
- return <>
- <SeedDialog
- onHide={() => setShowDialog(false)}
- round={round}
- show={showDialog}
- />
- <Button onClick={() => setShowDialog(true)} variant="outline-primary">
- {t('rounds.setSeed')}
- </Button>
- </>;
- }
- return t('rounds.noSeed');
-};
-
-SeedButton.propTypes = {
- round: PropTypes.shape({
- seed: PropTypes.string,
- spoiler: PropTypes.string,
- }),
- tournament: PropTypes.shape({
- }),
-};
-
-export default SeedButton;
--- /dev/null
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import SeedDialog from './SeedDialog';
+import { maySetSeed } from '../../helpers/permissions';
+import { useUser } from '../../hooks/user';
+
+const SeedButton = ({ round, tournament }) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ const { t } = useTranslation();
+ const { user } = useUser();
+
+ if (round.seed) {
+ return <>
+ <Button href={round.seed} target="_blank" variant="primary">
+ {t('rounds.seed')}
+ </Button>
+ {round.spoiler ?
+ <Button
+ className="ms-2"
+ href={round.spoiler}
+ target="_blank"
+ variant="outline-primary"
+ >
+ {t('rounds.spoiler')}
+ </Button>
+ : null}
+ </>;
+ }
+ if (maySetSeed(user, tournament, round)) {
+ return <>
+ <SeedDialog
+ onHide={() => setShowDialog(false)}
+ round={round}
+ show={showDialog}
+ />
+ <Button onClick={() => setShowDialog(true)} variant="outline-primary">
+ {t('rounds.setSeed')}
+ </Button>
+ </>;
+ }
+ return t('rounds.noSeed');
+};
+
+SeedButton.propTypes = {
+ round: PropTypes.shape({
+ seed: PropTypes.string,
+ spoiler: PropTypes.string,
+ }),
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default SeedButton;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import ZeldaIcon from '../common/ZeldaIcon';
-
-const SeedCode = ({ code, game }) =>
-<span className={`seed-code game-${game}`}>
- {code.map(game === 'smr'
- ? (symbol, index) => <span key={`${symbol}.${index}`}>{symbol}</span>
- : (symbol, index) => <ZeldaIcon key={`${symbol}.${index}`} name={symbol} />
- )}
-</span>;
-
-SeedCode.propTypes = {
- code: PropTypes.arrayOf(PropTypes.string),
- game: PropTypes.string,
-};
-
-export default SeedCode;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import ZeldaIcon from '../common/ZeldaIcon';
+
+const SeedCode = ({ code, game }) =>
+<span className={`seed-code game-${game}`}>
+ {code.map(game === 'smr'
+ ? (symbol, index) => <span key={`${symbol}.${index}`}>{symbol}</span>
+ : (symbol, index) => <ZeldaIcon key={`${symbol}.${index}`} name={symbol} />
+ )}
+</span>;
+
+SeedCode.propTypes = {
+ code: PropTypes.arrayOf(PropTypes.string),
+ game: PropTypes.string,
+};
+
+export default SeedCode;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Form } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import i18n from '../../i18n';
-
-const ALTTPR_CODES = [
- 'big-key',
- 'blue-boomerang',
- 'bomb',
- 'bombos',
- 'book',
- 'boots',
- 'bottle',
- 'bow',
- 'bugnet',
- 'cape',
- 'compass',
- 'ether',
- 'flippers',
- 'flute',
- 'glove',
- 'green-mail',
- 'green-pendant',
- 'green-potion',
- 'hammer',
- 'heart-container',
- 'hookshot',
- 'ice-rod',
- 'lamp',
- 'map',
- 'mirror',
- 'mirror-shield',
- 'moonpearl',
- 'mushroom',
- 'powder',
- 'quake',
- 'shovel',
- 'somaria',
-];
-
-const SMR_CODES = [
- 'ALCOON',
- 'ATOMIC',
- 'BEETOM',
- 'BOYON',
- 'BULL',
- 'CHOOT',
- 'COVERN',
- 'EVIR',
- 'FUNE',
- 'GAMET',
- 'GEEMER',
- 'GERUTA',
- 'HOLTZ',
- 'KAGO',
- 'NAMIHE',
- 'OUM',
- 'OWTCH',
- 'POWAMP',
- 'PUROMI',
- 'PUYO',
- 'RINKA',
- 'RIPPER',
- 'SCISER',
- 'SKREE',
- 'SOVA',
- 'TATORI',
- 'VIOLA',
- 'WAVER',
- 'YARD',
- 'ZEBBO',
- 'ZEELA',
- 'ZOA',
-];
-
-const SeedCodeInput = ({
- className = '',
- game = '',
- name = '',
- onBlur = null,
- onChange = null,
- value = [],
-}) => {
- if (game === 'alttpr') {
- const code_trans = ALTTPR_CODES
- .map(code => ({ code, label: i18n.t(`icon.zelda.${code}`)}))
- .sort((a, b) => a.label.localeCompare(b.label));
- return <div
- className={`${className} seed-code-input game-alttpr`}
- >
- {[0, 1, 2, 3, 4].map(num =>
- <Form.Select
- key={num}
- onBlur={onBlur}
- onChange={onChange}
- name={`${name}[${num}]`}
- value={(value && value[num]) || ''}
- >
- <option value=""></option>
- {code_trans.map(({ code, label }) =>
- <option key={code} value={code}>{label}</option>
- )}
- </Form.Select>
- )}
- </div>;
- }
- if (game === 'smr') {
- return <div
- className={`${className} seed-code-input game-smr`}
- >
- {[0, 1, 2, 3].map(num =>
- <Form.Select
- key={num}
- onBlur={onBlur}
- onChange={onChange}
- name={`${name}[${num}]`}
- value={(value && value[num]) || ''}
- >
- <option value=""></option>
- {SMR_CODES.sort((a, b) => a.localeCompare(b)).map(code =>
- <option key={code} value={code}>{code}</option>
- )}
- </Form.Select>
- )}
- </div>;
- }
- return <div
- className={`${className} seed-code-input`}
- >
- {[0, 1, 2, 3, 4].map(num =>
- <Form.Control
- key={num}
- onBlur={onBlur}
- onChange={onChange}
- name={`${name}[${num}]`}
- value={(value && value[num]) || ''}
- />
- )}
- </div>;
-};
-
-SeedCodeInput.propTypes = {
- className: PropTypes.string,
- game: PropTypes.string,
- name: PropTypes.string,
- onBlur: PropTypes.func,
- onChange: PropTypes.func,
- value: PropTypes.arrayOf(PropTypes.string),
-};
-
-export default withTranslation()(SeedCodeInput);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Form } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import i18n from '../../i18n';
+
+const ALTTPR_CODES = [
+ 'big-key',
+ 'blue-boomerang',
+ 'bomb',
+ 'bombos',
+ 'book',
+ 'boots',
+ 'bottle',
+ 'bow',
+ 'bugnet',
+ 'cape',
+ 'compass',
+ 'ether',
+ 'flippers',
+ 'flute',
+ 'glove',
+ 'green-mail',
+ 'green-pendant',
+ 'green-potion',
+ 'hammer',
+ 'heart-container',
+ 'hookshot',
+ 'ice-rod',
+ 'lamp',
+ 'map',
+ 'mirror',
+ 'mirror-shield',
+ 'moonpearl',
+ 'mushroom',
+ 'powder',
+ 'quake',
+ 'shovel',
+ 'somaria',
+];
+
+const SMR_CODES = [
+ 'ALCOON',
+ 'ATOMIC',
+ 'BEETOM',
+ 'BOYON',
+ 'BULL',
+ 'CHOOT',
+ 'COVERN',
+ 'EVIR',
+ 'FUNE',
+ 'GAMET',
+ 'GEEMER',
+ 'GERUTA',
+ 'HOLTZ',
+ 'KAGO',
+ 'NAMIHE',
+ 'OUM',
+ 'OWTCH',
+ 'POWAMP',
+ 'PUROMI',
+ 'PUYO',
+ 'RINKA',
+ 'RIPPER',
+ 'SCISER',
+ 'SKREE',
+ 'SOVA',
+ 'TATORI',
+ 'VIOLA',
+ 'WAVER',
+ 'YARD',
+ 'ZEBBO',
+ 'ZEELA',
+ 'ZOA',
+];
+
+const SeedCodeInput = ({
+ className = '',
+ game = '',
+ name = '',
+ onBlur = null,
+ onChange = null,
+ value = [],
+}) => {
+ if (game === 'alttpr') {
+ const code_trans = ALTTPR_CODES
+ .map(code => ({ code, label: i18n.t(`icon.zelda.${code}`)}))
+ .sort((a, b) => a.label.localeCompare(b.label));
+ return <div
+ className={`${className} seed-code-input game-alttpr`}
+ >
+ {[0, 1, 2, 3, 4].map(num =>
+ <Form.Select
+ key={num}
+ onBlur={onBlur}
+ onChange={onChange}
+ name={`${name}[${num}]`}
+ value={(value && value[num]) || ''}
+ >
+ <option value=""></option>
+ {code_trans.map(({ code, label }) =>
+ <option key={code} value={code}>{label}</option>
+ )}
+ </Form.Select>
+ )}
+ </div>;
+ }
+ if (game === 'smr') {
+ return <div
+ className={`${className} seed-code-input game-smr`}
+ >
+ {[0, 1, 2, 3].map(num =>
+ <Form.Select
+ key={num}
+ onBlur={onBlur}
+ onChange={onChange}
+ name={`${name}[${num}]`}
+ value={(value && value[num]) || ''}
+ >
+ <option value=""></option>
+ {SMR_CODES.sort((a, b) => a.localeCompare(b)).map(code =>
+ <option key={code} value={code}>{code}</option>
+ )}
+ </Form.Select>
+ )}
+ </div>;
+ }
+ return <div
+ className={`${className} seed-code-input`}
+ >
+ {[0, 1, 2, 3, 4].map(num =>
+ <Form.Control
+ key={num}
+ onBlur={onBlur}
+ onChange={onChange}
+ name={`${name}[${num}]`}
+ value={(value && value[num]) || ''}
+ />
+ )}
+ </div>;
+};
+
+SeedCodeInput.propTypes = {
+ className: PropTypes.string,
+ game: PropTypes.string,
+ name: PropTypes.string,
+ onBlur: PropTypes.func,
+ onChange: PropTypes.func,
+ value: PropTypes.arrayOf(PropTypes.string),
+};
+
+export default withTranslation()(SeedCodeInput);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Modal } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import SeedForm from './SeedForm';
-import i18n from '../../i18n';
-
-const SeedDialog = ({
- onHide,
- round,
- show,
-}) =>
-<Modal className="seed-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {i18n.t('rounds.setSeed')}
- </Modal.Title>
- </Modal.Header>
- <SeedForm
- onCancel={onHide}
- round={round}
- />
-</Modal>;
-
-SeedDialog.propTypes = {
- onHide: PropTypes.func,
- round: PropTypes.shape({
- }),
- show: PropTypes.bool,
- tournament: PropTypes.shape({
- }),
-};
-
-export default withTranslation()(SeedDialog);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import SeedForm from './SeedForm';
+import i18n from '../../i18n';
+
+const SeedDialog = ({
+ onHide,
+ round,
+ show,
+}) =>
+<Modal className="seed-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {i18n.t('rounds.setSeed')}
+ </Modal.Title>
+ </Modal.Header>
+ <SeedForm
+ onCancel={onHide}
+ round={round}
+ />
+</Modal>;
+
+SeedDialog.propTypes = {
+ onHide: PropTypes.func,
+ round: PropTypes.shape({
+ }),
+ show: PropTypes.bool,
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(SeedDialog);
+++ /dev/null
-import axios from 'axios';
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
-import i18n from '../../i18n';
-import yup from '../../schema/yup';
-
-const SeedForm = ({
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- onCancel,
- touched,
- values,
-}) =>
-<Form noValidate onSubmit={handleSubmit}>
- <Modal.Body>
- <Row>
- <Form.Group as={Col} controlId="round.seed">
- <Form.Label>{i18n.t('rounds.seed')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.seed && errors.seed)}
- name="seed"
- onBlur={handleBlur}
- onChange={handleChange}
- placeholder="https://alttprpatch.synack.live/patcher.html?patch=https://sahasrahbot.s3.amazonaws.com/patch/DR_XXXXXXXXXXX.bps"
- type="text"
- value={values.seed || ''}
- />
- {touched.seed && errors.seed ?
- <Form.Control.Feedback type="invalid">
- {i18n.t(errors.seed)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Row>
- </Modal.Body>
- <Modal.Footer>
- {onCancel ?
- <Button onClick={onCancel} variant="secondary">
- {i18n.t('button.cancel')}
- </Button>
- : null}
- <Button type="submit" variant="primary">
- {i18n.t('button.save')}
- </Button>
- </Modal.Footer>
-</Form>;
-
-SeedForm.propTypes = {
- errors: PropTypes.shape({
- seed: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- onCancel: PropTypes.func,
- touched: PropTypes.shape({
- seed: PropTypes.bool,
- }),
- values: PropTypes.shape({
- seed: PropTypes.string,
- }),
-};
-
-export default withFormik({
- displayName: 'SeedForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { round_id, seed } = values;
- const { setErrors } = actions;
- const { onCancel } = actions.props;
- try {
- await axios.post(`/api/rounds/${round_id}/setSeed`, {
- seed,
- });
- toastr.success(i18n.t('rounds.setSeedSuccess'));
- if (onCancel) {
- onCancel();
- }
- } catch (e) {
- toastr.error(i18n.t('rounds.setSeedError'));
- if (e.response && e.response.data && e.response.data.errors) {
- setErrors(laravelErrorsToFormik(e.response.data.errors));
- }
- }
- },
- mapPropsToValues: ({ round }) => ({
- round_id: round.id,
- seed: round.seed || '',
- }),
- validationSchema: yup.object().shape({
- seed: yup.string().required().url(),
- }),
-})(withTranslation()(SeedForm));
--- /dev/null
+import axios from 'axios';
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import i18n from '../../i18n';
+import yup from '../../schema/yup';
+
+const SeedForm = ({
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ onCancel,
+ touched,
+ values,
+}) =>
+<Form noValidate onSubmit={handleSubmit}>
+ <Modal.Body>
+ <Row>
+ <Form.Group as={Col} controlId="round.seed">
+ <Form.Label>{i18n.t('rounds.seed')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.seed && errors.seed)}
+ name="seed"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ placeholder="https://alttprpatch.synack.live/patcher.html?patch=https://sahasrahbot.s3.amazonaws.com/patch/DR_XXXXXXXXXXX.bps"
+ type="text"
+ value={values.seed || ''}
+ />
+ {touched.seed && errors.seed ?
+ <Form.Control.Feedback type="invalid">
+ {i18n.t(errors.seed)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {i18n.t('button.cancel')}
+ </Button>
+ : null}
+ <Button type="submit" variant="primary">
+ {i18n.t('button.save')}
+ </Button>
+ </Modal.Footer>
+</Form>;
+
+SeedForm.propTypes = {
+ errors: PropTypes.shape({
+ seed: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ onCancel: PropTypes.func,
+ touched: PropTypes.shape({
+ seed: PropTypes.bool,
+ }),
+ values: PropTypes.shape({
+ seed: PropTypes.string,
+ }),
+};
+
+export default withFormik({
+ displayName: 'SeedForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { round_id, seed } = values;
+ const { setErrors } = actions;
+ const { onCancel } = actions.props;
+ try {
+ await axios.post(`/api/rounds/${round_id}/setSeed`, {
+ seed,
+ });
+ toastr.success(i18n.t('rounds.setSeedSuccess'));
+ if (onCancel) {
+ onCancel();
+ }
+ } catch (e) {
+ toastr.error(i18n.t('rounds.setSeedError'));
+ if (e.response && e.response.data && e.response.data.errors) {
+ setErrors(laravelErrorsToFormik(e.response.data.errors));
+ }
+ }
+ },
+ mapPropsToValues: ({ round }) => ({
+ round_id: round.id,
+ seed: round.seed || '',
+ }),
+ validationSchema: yup.object().shape({
+ seed: yup.string().required().url(),
+ }),
+})(withTranslation()(SeedForm));
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import { getAvatarUrl, getUserName } from '../../helpers/User';
-import i18n from '../../i18n';
-
-const SeedRolledBy = ({ round }) => round.rolled_by_user ?
- <span
- className="rolled-by"
- title={i18n.t('rounds.rolledBy', { name: getUserName(round.rolled_by_user) })}
- >
- <img alt={getUserName(round.rolled_by_user)} src={getAvatarUrl(round.rolled_by_user)} />
- </span>
-: null;
-
-SeedRolledBy.propTypes = {
- round: PropTypes.shape({
- rolled_by_user: PropTypes.shape({
- }),
- }),
-};
-
-export default SeedRolledBy;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import { getAvatarUrl, getUserName } from '../../helpers/User';
+import i18n from '../../i18n';
+
+const SeedRolledBy = ({ round }) => round.rolled_by_user ?
+ <span
+ className="rolled-by"
+ title={i18n.t('rounds.rolledBy', { name: getUserName(round.rolled_by_user) })}
+ >
+ <img alt={getUserName(round.rolled_by_user)} src={getAvatarUrl(round.rolled_by_user)} />
+ </span>
+: null;
+
+SeedRolledBy.propTypes = {
+ round: PropTypes.shape({
+ rolled_by_user: PropTypes.shape({
+ }),
+ }),
+};
+
+export default SeedRolledBy;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Modal } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import SettingsForm from './SettingsForm';
-
-const SettingsDialog = ({
- deviceList,
- onHide,
- onSubmit,
- settings,
- show,
-}) => {
- const { t } = useTranslation();
-
- return <Modal className="snes-settings-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {t('snes.settings')}
- </Modal.Title>
- </Modal.Header>
- <SettingsForm
- deviceList={deviceList}
- onCancel={onHide}
- onSubmit={onSubmit}
- settings={settings}
- />
- </Modal>;
-};
-
-SettingsDialog.propTypes = {
- deviceList: PropTypes.arrayOf(PropTypes.string),
- onHide: PropTypes.func,
- onSubmit: PropTypes.func,
- settings: PropTypes.shape({
- }),
- show: PropTypes.bool,
-};
-
-export default SettingsDialog;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import SettingsForm from './SettingsForm';
+
+const SettingsDialog = ({
+ deviceList,
+ onHide,
+ onSubmit,
+ settings,
+ show,
+}) => {
+ const { t } = useTranslation();
+
+ return <Modal className="snes-settings-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {t('snes.settings')}
+ </Modal.Title>
+ </Modal.Header>
+ <SettingsForm
+ deviceList={deviceList}
+ onCancel={onHide}
+ onSubmit={onSubmit}
+ settings={settings}
+ />
+ </Modal>;
+};
+
+SettingsDialog.propTypes = {
+ deviceList: PropTypes.arrayOf(PropTypes.string),
+ onHide: PropTypes.func,
+ onSubmit: PropTypes.func,
+ settings: PropTypes.shape({
+ }),
+ show: PropTypes.bool,
+};
+
+export default SettingsDialog;
+++ /dev/null
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import yup from '../../schema/yup';
-
-const SettingsForm = ({
- deviceList,
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- onCancel,
- settings,
- touched,
- values,
-}) => {
- const { t } = useTranslation();
-
- return <Form noValidate onSubmit={handleSubmit}>
- <Modal.Body>
- <Row>
- <Form.Group as={Col} sm={3} controlId="snes.proto">
- <Form.Label>{t('snes.proto')}</Form.Label>
- <Form.Select
- isInvalid={!!(touched.proto && errors.proto)}
- name="proto"
- onBlur={handleBlur}
- onChange={handleChange}
- value={values.proto || 'ws'}
- >
- <option value="ws">ws://</option>
- <option value="wss">wss://</option>
- </Form.Select>
- {touched.proto && errors.proto ?
- <Form.Control.Feedback type="invalid">
- {t(errors.proto)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group as={Col} sm={6} controlId="snes.host">
- <Form.Label>{t('snes.host')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.host && errors.host)}
- name="host"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.host || 'localhost'}
- />
- {touched.host && errors.host ?
- <Form.Control.Feedback type="invalid">
- {t(errors.host)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group as={Col} sm={3} controlId="snes.port">
- <Form.Label>{t('snes.port')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.port && errors.port)}
- min="1"
- max="65665"
- name="port"
- onBlur={handleBlur}
- onChange={handleChange}
- type="number"
- value={values.port || 8080}
- />
- {touched.port && errors.port ?
- <Form.Control.Feedback type="invalid">
- {t(errors.port)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group as={Col} sm={12} controlId="snes.device">
- <Form.Label>{t('snes.device')}</Form.Label>
- <Form.Select
- isInvalid={!!(touched.device && errors.device)}
- name="device"
- onBlur={handleBlur}
- onChange={handleChange}
- value={values.device || ''}
- >
- <option value="">Auto</option>
- {settings.device && !deviceList.includes(settings.device) ?
- <option value={settings.device}>{settings.device}</option>
- : null}
- {deviceList.map(device =>
- <option key={device} value={device}>{device}</option>
- )}
- </Form.Select>
- {touched.device && errors.device ?
- <Form.Control.Feedback type="invalid">
- {t(errors.device)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Row>
- </Modal.Body>
- <Modal.Footer>
- {onCancel ?
- <Button onClick={onCancel} variant="secondary">
- {t('button.cancel')}
- </Button>
- : null}
- <Button type="submit" variant="primary">
- {t('button.save')}
- </Button>
- </Modal.Footer>
- </Form>;
-};
-
-SettingsForm.propTypes = {
- deviceList: PropTypes.arrayOf(PropTypes.string),
- errors: PropTypes.shape({
- device: PropTypes.string,
- host: PropTypes.string,
- port: PropTypes.string,
- proto: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- onCancel: PropTypes.func,
- settings: PropTypes.shape({
- device: PropTypes.string,
- host: PropTypes.string,
- port: PropTypes.number,
- proto: PropTypes.string,
- }),
- touched: PropTypes.shape({
- device: PropTypes.bool,
- host: PropTypes.bool,
- port: PropTypes.bool,
- proto: PropTypes.bool,
- }),
- values: PropTypes.shape({
- device: PropTypes.string,
- host: PropTypes.string,
- port: PropTypes.number,
- proto: PropTypes.string,
- }),
-};
-
-export default withFormik({
- displayName: 'SettingsForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { onSubmit } = actions.props;
- onSubmit(values);
- },
- mapPropsToValues: ({ settings }) => settings,
- validationSchema: yup.object().shape({
- device: yup.string(),
- host: yup.string(),
- port: yup.number().min(1).max(65665),
- proto: yup.string(),
- }),
-})(SettingsForm);
--- /dev/null
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import yup from '../../schema/yup';
+
+const SettingsForm = ({
+ deviceList,
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ onCancel,
+ settings,
+ touched,
+ values,
+}) => {
+ const { t } = useTranslation();
+
+ return <Form noValidate onSubmit={handleSubmit}>
+ <Modal.Body>
+ <Row>
+ <Form.Group as={Col} sm={3} controlId="snes.proto">
+ <Form.Label>{t('snes.proto')}</Form.Label>
+ <Form.Select
+ isInvalid={!!(touched.proto && errors.proto)}
+ name="proto"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.proto || 'ws'}
+ >
+ <option value="ws">ws://</option>
+ <option value="wss">wss://</option>
+ </Form.Select>
+ {touched.proto && errors.proto ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.proto)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group as={Col} sm={6} controlId="snes.host">
+ <Form.Label>{t('snes.host')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.host && errors.host)}
+ name="host"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.host || 'localhost'}
+ />
+ {touched.host && errors.host ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.host)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group as={Col} sm={3} controlId="snes.port">
+ <Form.Label>{t('snes.port')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.port && errors.port)}
+ min="1"
+ max="65665"
+ name="port"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="number"
+ value={values.port || 8080}
+ />
+ {touched.port && errors.port ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.port)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group as={Col} sm={12} controlId="snes.device">
+ <Form.Label>{t('snes.device')}</Form.Label>
+ <Form.Select
+ isInvalid={!!(touched.device && errors.device)}
+ name="device"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.device || ''}
+ >
+ <option value="">Auto</option>
+ {settings.device && !deviceList.includes(settings.device) ?
+ <option value={settings.device}>{settings.device}</option>
+ : null}
+ {deviceList.map(device =>
+ <option key={device} value={device}>{device}</option>
+ )}
+ </Form.Select>
+ {touched.device && errors.device ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.device)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {t('button.cancel')}
+ </Button>
+ : null}
+ <Button type="submit" variant="primary">
+ {t('button.save')}
+ </Button>
+ </Modal.Footer>
+ </Form>;
+};
+
+SettingsForm.propTypes = {
+ deviceList: PropTypes.arrayOf(PropTypes.string),
+ errors: PropTypes.shape({
+ device: PropTypes.string,
+ host: PropTypes.string,
+ port: PropTypes.string,
+ proto: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ onCancel: PropTypes.func,
+ settings: PropTypes.shape({
+ device: PropTypes.string,
+ host: PropTypes.string,
+ port: PropTypes.number,
+ proto: PropTypes.string,
+ }),
+ touched: PropTypes.shape({
+ device: PropTypes.bool,
+ host: PropTypes.bool,
+ port: PropTypes.bool,
+ proto: PropTypes.bool,
+ }),
+ values: PropTypes.shape({
+ device: PropTypes.string,
+ host: PropTypes.string,
+ port: PropTypes.number,
+ proto: PropTypes.string,
+ }),
+};
+
+export default withFormik({
+ displayName: 'SettingsForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { onSubmit } = actions.props;
+ onSubmit(values);
+ },
+ mapPropsToValues: ({ settings }) => settings,
+ validationSchema: yup.object().shape({
+ device: yup.string(),
+ host: yup.string(),
+ port: yup.number().min(1).max(65665),
+ proto: yup.string(),
+ }),
+})(SettingsForm);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Alert, Button, Container } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import List from './List';
-import Outline from './Outline';
-import Requirements from './Requirements';
-import Rulesets from './Rulesets';
-import Icon from '../common/Icon';
-import RawHTML from '../common/RawHTML';
-import nl2br from '../../helpers/nl2br';
-import {
- getRelations,
- getTranslation,
- hasRelations,
- sorted,
-} from '../../helpers/Technique';
-import i18n from '../../i18n';
-
-const Detail = ({ actions, technique }) => {
- const { t } = useTranslation();
-
- return <Container as="article">
- <div className="d-flex align-items-center justify-content-between">
- <h1>
- {getTranslation(technique, 'title', i18n.language)}
- {actions.editContent ?
- <Button
- className="ms-3"
- onClick={() => actions.editContent(technique)}
- size="sm"
- title={t('button.edit')}
- variant="outline-secondary"
- >
- <Icon.EDIT title="" />
- </Button>
- : null}
- </h1>
- {technique && technique.rulesets ?
- <Rulesets technique={technique} />
- : null}
- </div>
- <Outline technique={technique} />
- <Requirements technique={technique} />
- <RawHTML html={getTranslation(technique, 'description', i18n.language)} />
- {technique.chapters ? technique.chapters.map(chapter =>
- <section id={`c${chapter.id}`} key={`c${chapter.id}`}>
- {chapter.pivot.level ?
- React.createElement(
- `h${chapter.pivot.level}`,
- {},
- getTranslation(chapter, 'title', i18n.language),
- actions.editContent ?
- <Button
- className="ms-3"
- onClick={() => actions.editContent(chapter)}
- size="sm"
- title={t('button.edit')}
- variant="outline-secondary"
- >
- <Icon.EDIT title="" />
- </Button>
- : null,
- )
- : null}
- <RawHTML html={getTranslation(chapter, 'description', i18n.language)} />
- </section>
- ) : null}
- {hasRelations(technique, 'related') ? <>
- <h2 className="mt-5">{i18n.t('techniques.seeAlso')}</h2>
- <List techniques={sorted(getRelations(technique, 'related'))} />
- </> : null}
- {getTranslation(technique, 'attribution', i18n.language) ?
- <Alert variant="dark">
- {nl2br(getTranslation(technique, 'attribution', i18n.language))}
- </Alert>
- : null}
- </Container>;
-};
-
-Detail.propTypes = {
- actions: PropTypes.shape({
- editContent: PropTypes.func,
- }),
- technique: PropTypes.shape({
- chapters: PropTypes.arrayOf(PropTypes.shape({
- })),
- description: PropTypes.string,
- rulesets: PropTypes.shape({
- }),
- title: PropTypes.string,
- }),
-};
-
-export default Detail;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Alert, Button, Container } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import List from './List';
+import Outline from './Outline';
+import Requirements from './Requirements';
+import Rulesets from './Rulesets';
+import Icon from '../common/Icon';
+import RawHTML from '../common/RawHTML';
+import nl2br from '../../helpers/nl2br';
+import {
+ getRelations,
+ getTranslation,
+ hasRelations,
+ sorted,
+} from '../../helpers/Technique';
+import i18n from '../../i18n';
+
+const Detail = ({ actions, technique }) => {
+ const { t } = useTranslation();
+
+ return <Container as="article">
+ <div className="d-flex align-items-center justify-content-between">
+ <h1>
+ {getTranslation(technique, 'title', i18n.language)}
+ {actions.editContent ?
+ <Button
+ className="ms-3"
+ onClick={() => actions.editContent(technique)}
+ size="sm"
+ title={t('button.edit')}
+ variant="outline-secondary"
+ >
+ <Icon.EDIT title="" />
+ </Button>
+ : null}
+ </h1>
+ {technique && technique.rulesets ?
+ <Rulesets technique={technique} />
+ : null}
+ </div>
+ <Outline technique={technique} />
+ <Requirements technique={technique} />
+ <RawHTML html={getTranslation(technique, 'description', i18n.language)} />
+ {technique.chapters ? technique.chapters.map(chapter =>
+ <section id={`c${chapter.id}`} key={`c${chapter.id}`}>
+ {chapter.pivot.level ?
+ React.createElement(
+ `h${chapter.pivot.level}`,
+ {},
+ getTranslation(chapter, 'title', i18n.language),
+ actions.editContent ?
+ <Button
+ className="ms-3"
+ onClick={() => actions.editContent(chapter)}
+ size="sm"
+ title={t('button.edit')}
+ variant="outline-secondary"
+ >
+ <Icon.EDIT title="" />
+ </Button>
+ : null,
+ )
+ : null}
+ <RawHTML html={getTranslation(chapter, 'description', i18n.language)} />
+ </section>
+ ) : null}
+ {hasRelations(technique, 'related') ? <>
+ <h2 className="mt-5">{i18n.t('techniques.seeAlso')}</h2>
+ <List techniques={sorted(getRelations(technique, 'related'))} />
+ </> : null}
+ {getTranslation(technique, 'attribution', i18n.language) ?
+ <Alert variant="dark">
+ {nl2br(getTranslation(technique, 'attribution', i18n.language))}
+ </Alert>
+ : null}
+ </Container>;
+};
+
+Detail.propTypes = {
+ actions: PropTypes.shape({
+ editContent: PropTypes.func,
+ }),
+ technique: PropTypes.shape({
+ chapters: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ description: PropTypes.string,
+ rulesets: PropTypes.shape({
+ }),
+ title: PropTypes.string,
+ }),
+};
+
+export default Detail;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Modal } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Loading from '../common/Loading';
-import LanguageSwitcher from '../../app/LanguageSwitcher';
-
-const Form = React.lazy(() => import('./Form'));
-
-const Dialog = ({
- content,
- language,
- onHide,
- onSubmit,
- show,
-}) => {
- const { t } = useTranslation();
-
- return <Modal onHide={onHide} show={show} size="lg">
- <Modal.Header closeButton>
- <Modal.Title>
- {t('content.edit')}
- </Modal.Title>
- <div className="mx-3">
- <LanguageSwitcher />
- </div>
- </Modal.Header>
- <React.Suspense fallback={<Loading />}>
- <Form
- content={content}
- language={language}
- onCancel={onHide}
- onSubmit={onSubmit}
- />
- </React.Suspense>
- </Modal>;
-};
-
-Dialog.propTypes = {
- content: PropTypes.shape({
- }),
- language: PropTypes.string,
- onHide: PropTypes.func,
- onSubmit: PropTypes.func,
- show: PropTypes.bool,
-};
-
-export default Dialog;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Loading from '../common/Loading';
+import LanguageSwitcher from '../../app/LanguageSwitcher';
+
+const Form = React.lazy(() => import('./Form'));
+
+const Dialog = ({
+ content,
+ language,
+ onHide,
+ onSubmit,
+ show,
+}) => {
+ const { t } = useTranslation();
+
+ return <Modal onHide={onHide} show={show} size="lg">
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {t('content.edit')}
+ </Modal.Title>
+ <div className="mx-3">
+ <LanguageSwitcher />
+ </div>
+ </Modal.Header>
+ <React.Suspense fallback={<Loading />}>
+ <Form
+ content={content}
+ language={language}
+ onCancel={onHide}
+ onSubmit={onSubmit}
+ />
+ </React.Suspense>
+ </Modal>;
+};
+
+Dialog.propTypes = {
+ content: PropTypes.shape({
+ }),
+ language: PropTypes.string,
+ onHide: PropTypes.func,
+ onSubmit: PropTypes.func,
+ show: PropTypes.bool,
+};
+
+export default Dialog;
+++ /dev/null
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import HTMLInput from '../common/HTMLInput';
-import { getTranslation } from '../../helpers/Technique';
-import yup from '../../schema/yup';
-
-const ContentForm = ({
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- onCancel,
- touched,
- values,
-}) => {
- const { t } = useTranslation();
-
- return <Form noValidate onSubmit={handleSubmit}>
- <Modal.Body>
- <Row>
- <Form.Group as={Col} md={6} controlId="content.title">
- <Form.Label>{t('content.title')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.title && errors.title)}
- name="title"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.title || ''}
- />
- </Form.Group>
- </Row>
- <Form.Group controlId="content.short">
- <Form.Label>{t('content.short')}</Form.Label>
- <Form.Control
- as="textarea"
- isInvalid={!!(touched.short && errors.short)}
- name="short"
- onBlur={handleBlur}
- onChange={handleChange}
- rows={3}
- value={values.short || ''}
- />
- </Form.Group>
- <Form.Group controlId="content.description">
- <Form.Label>{t('content.description')}</Form.Label>
- <Form.Control
- as={HTMLInput}
- isInvalid={!!(touched.description && errors.description)}
- name="description"
- onBlur={handleBlur}
- onChange={handleChange}
- rows={10}
- value={values.description || ''}
- />
- </Form.Group>
- <Form.Group controlId="content.attribution">
- <Form.Label>{t('content.attribution')}</Form.Label>
- <Form.Control
- as="textarea"
- isInvalid={!!(touched.attribution && errors.attribution)}
- name="attribution"
- onBlur={handleBlur}
- onChange={handleChange}
- rows={3}
- value={values.attribution || ''}
- />
- </Form.Group>
- </Modal.Body>
- <Modal.Footer>
- {onCancel ?
- <Button onClick={onCancel} variant="secondary">
- {t('button.cancel')}
- </Button>
- : null}
- <Button type="submit" variant="primary">
- {t('button.save')}
- </Button>
- </Modal.Footer>
- </Form>;
-};
-
-ContentForm.propTypes = {
- errors: PropTypes.shape({
- attribution: PropTypes.string,
- description: PropTypes.string,
- short: PropTypes.string,
- title: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- onCancel: PropTypes.func,
- touched: PropTypes.shape({
- attribution: PropTypes.bool,
- description: PropTypes.bool,
- short: PropTypes.bool,
- title: PropTypes.bool,
- }),
- values: PropTypes.shape({
- attribution: PropTypes.string,
- description: PropTypes.string,
- short: PropTypes.string,
- title: PropTypes.string,
- }),
-};
-
-export default withFormik({
- displayName: 'ContentForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { onSubmit } = actions.props;
- await onSubmit(values);
- },
- mapPropsToValues: ({ content, language }) => ({
- attribution: getTranslation(content, 'attribution', language),
- description: getTranslation(content, 'description', language),
- id: (content && content.id) || null,
- language,
- short: getTranslation(content, 'short', language),
- title: getTranslation(content, 'title', language),
- }),
- validationSchema: yup.object().shape({
- attribution: yup.string(),
- description: yup.string(),
- short: yup.string(),
- title: yup.string(),
- }),
-})(ContentForm);
--- /dev/null
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import HTMLInput from '../common/HTMLInput';
+import { getTranslation } from '../../helpers/Technique';
+import yup from '../../schema/yup';
+
+const ContentForm = ({
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ onCancel,
+ touched,
+ values,
+}) => {
+ const { t } = useTranslation();
+
+ return <Form noValidate onSubmit={handleSubmit}>
+ <Modal.Body>
+ <Row>
+ <Form.Group as={Col} md={6} controlId="content.title">
+ <Form.Label>{t('content.title')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.title && errors.title)}
+ name="title"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.title || ''}
+ />
+ </Form.Group>
+ </Row>
+ <Form.Group controlId="content.short">
+ <Form.Label>{t('content.short')}</Form.Label>
+ <Form.Control
+ as="textarea"
+ isInvalid={!!(touched.short && errors.short)}
+ name="short"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ rows={3}
+ value={values.short || ''}
+ />
+ </Form.Group>
+ <Form.Group controlId="content.description">
+ <Form.Label>{t('content.description')}</Form.Label>
+ <Form.Control
+ as={HTMLInput}
+ isInvalid={!!(touched.description && errors.description)}
+ name="description"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ rows={10}
+ value={values.description || ''}
+ />
+ </Form.Group>
+ <Form.Group controlId="content.attribution">
+ <Form.Label>{t('content.attribution')}</Form.Label>
+ <Form.Control
+ as="textarea"
+ isInvalid={!!(touched.attribution && errors.attribution)}
+ name="attribution"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ rows={3}
+ value={values.attribution || ''}
+ />
+ </Form.Group>
+ </Modal.Body>
+ <Modal.Footer>
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {t('button.cancel')}
+ </Button>
+ : null}
+ <Button type="submit" variant="primary">
+ {t('button.save')}
+ </Button>
+ </Modal.Footer>
+ </Form>;
+};
+
+ContentForm.propTypes = {
+ errors: PropTypes.shape({
+ attribution: PropTypes.string,
+ description: PropTypes.string,
+ short: PropTypes.string,
+ title: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ onCancel: PropTypes.func,
+ touched: PropTypes.shape({
+ attribution: PropTypes.bool,
+ description: PropTypes.bool,
+ short: PropTypes.bool,
+ title: PropTypes.bool,
+ }),
+ values: PropTypes.shape({
+ attribution: PropTypes.string,
+ description: PropTypes.string,
+ short: PropTypes.string,
+ title: PropTypes.string,
+ }),
+};
+
+export default withFormik({
+ displayName: 'ContentForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { onSubmit } = actions.props;
+ await onSubmit(values);
+ },
+ mapPropsToValues: ({ content, language }) => ({
+ attribution: getTranslation(content, 'attribution', language),
+ description: getTranslation(content, 'description', language),
+ id: (content && content.id) || null,
+ language,
+ short: getTranslation(content, 'short', language),
+ title: getTranslation(content, 'title', language),
+ }),
+ validationSchema: yup.object().shape({
+ attribution: yup.string(),
+ description: yup.string(),
+ short: yup.string(),
+ title: yup.string(),
+ }),
+})(ContentForm);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Link } from 'react-router-dom';
-
-import Rulesets from './Rulesets';
-import {
- getLink,
- getTranslation,
-} from '../../helpers/Technique';
-import i18n from '../../i18n';
-
-const List = ({ techniques }) => <ul className="tech-list">
- {techniques.map(tech =>
- <li className="d-flex align-items-start justify-content-between" key={tech.id}>
- <div>
- <h2>
- <Link to={getLink(tech)}>
- {getTranslation(tech, 'title', i18n.language)}
- </Link>
- </h2>
- <p>{getTranslation(tech, 'short', i18n.language)}</p>
- </div>
- {tech.rulesets ?
- <Rulesets technique={tech} />
- : null}
- </li>
- )}
-</ul>;
-
-List.propTypes = {
- techniques: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.number,
- name: PropTypes.string,
- })),
-};
-
-export default List;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+import Rulesets from './Rulesets';
+import {
+ getLink,
+ getTranslation,
+} from '../../helpers/Technique';
+import i18n from '../../i18n';
+
+const List = ({ techniques }) => <ul className="tech-list">
+ {techniques.map(tech =>
+ <li className="d-flex align-items-start justify-content-between" key={tech.id}>
+ <div>
+ <h2>
+ <Link to={getLink(tech)}>
+ {getTranslation(tech, 'title', i18n.language)}
+ </Link>
+ </h2>
+ <p>{getTranslation(tech, 'short', i18n.language)}</p>
+ </div>
+ {tech.rulesets ?
+ <Rulesets technique={tech} />
+ : null}
+ </li>
+ )}
+</ul>;
+
+List.propTypes = {
+ techniques: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.number,
+ name: PropTypes.string,
+ })),
+};
+
+export default List;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { ListGroup } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import { getTranslation } from '../../helpers/Technique';
-import i18n from '../../i18n';
-
-const Outline = ({ technique }) => technique.chapters && technique.chapters.length ?
- <aside className="tech-outline mb-3 ms-3">
- <ListGroup>
- {technique.chapters.map(chapter => chapter.pivot.level ?
- <ListGroup.Item
- action
- href={`#c${chapter.id}`}
- key={`c${chapter.id}`}
- title={getTranslation(chapter, 'short', i18n.language) || null}
- >
- {getTranslation(chapter, 'title', i18n.language)}
- </ListGroup.Item>
- : null)}
- </ListGroup>
- </aside>
-: null;
-
-Outline.propTypes = {
- technique: PropTypes.shape({
- chapters: PropTypes.arrayOf(PropTypes.shape({
- })),
- }),
-};
-
-export default withTranslation()(Outline);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { ListGroup } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import { getTranslation } from '../../helpers/Technique';
+import i18n from '../../i18n';
+
+const Outline = ({ technique }) => technique.chapters && technique.chapters.length ?
+ <aside className="tech-outline mb-3 ms-3">
+ <ListGroup>
+ {technique.chapters.map(chapter => chapter.pivot.level ?
+ <ListGroup.Item
+ action
+ href={`#c${chapter.id}`}
+ key={`c${chapter.id}`}
+ title={getTranslation(chapter, 'short', i18n.language) || null}
+ >
+ {getTranslation(chapter, 'title', i18n.language)}
+ </ListGroup.Item>
+ : null)}
+ </ListGroup>
+ </aside>
+: null;
+
+Outline.propTypes = {
+ technique: PropTypes.shape({
+ chapters: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ }),
+};
+
+export default withTranslation()(Outline);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Container } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import List from './List';
-import TechFilter from './TechFilter';
-import i18n from '../../i18n';
-
-const Overview = ({
- filter,
- namespace,
- setFilter,
- techniques,
- type,
-}) => <Container>
- <div className="d-flex align-items-center justify-content-between">
- <h1>{i18n.t(`${namespace}.heading`)}</h1>
- {type === 'tech' ?
- <TechFilter filter={filter} setFilter={setFilter} />
- : null}
- </div>
- <List techniques={techniques} />
-</Container>;
-
-Overview.propTypes = {
- filter: PropTypes.shape({}),
- namespace: PropTypes.string,
- setFilter: PropTypes.func,
- techniques: PropTypes.arrayOf(PropTypes.shape({
- })),
- type: PropTypes.string,
-};
-
-export default withTranslation()(Overview);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Container } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import List from './List';
+import TechFilter from './TechFilter';
+import i18n from '../../i18n';
+
+const Overview = ({
+ filter,
+ namespace,
+ setFilter,
+ techniques,
+ type,
+}) => <Container>
+ <div className="d-flex align-items-center justify-content-between">
+ <h1>{i18n.t(`${namespace}.heading`)}</h1>
+ {type === 'tech' ?
+ <TechFilter filter={filter} setFilter={setFilter} />
+ : null}
+ </div>
+ <List techniques={techniques} />
+</Container>;
+
+Overview.propTypes = {
+ filter: PropTypes.shape({}),
+ namespace: PropTypes.string,
+ setFilter: PropTypes.func,
+ techniques: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ type: PropTypes.string,
+};
+
+export default withTranslation()(Overview);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import ZeldaIcon from '../common/ZeldaIcon';
-
-const Requirement = ({ requirement }) =>
- <div className="requirement">
- {requirement.map(r =>
- <ZeldaIcon key={r} name={r} />
- )}
- </div>;
-
-Requirement.propTypes = {
- requirement: PropTypes.arrayOf(PropTypes.string),
-};
-
-export default Requirement;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import ZeldaIcon from '../common/ZeldaIcon';
+
+const Requirement = ({ requirement }) =>
+ <div className="requirement">
+ {requirement.map(r =>
+ <ZeldaIcon key={r} name={r} />
+ )}
+ </div>;
+
+Requirement.propTypes = {
+ requirement: PropTypes.arrayOf(PropTypes.string),
+};
+
+export default Requirement;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-
-import Requirement from './Requirement';
-
-const Requirements = ({ technique }) => {
- const { t } = useTranslation();
-
- if (!technique.requirements || !technique.requirements.length) {
- return null;
- }
-
- return <div className="tech-requirements mb-3">
- {t('techniques.requirements')}
- <ul>
- {technique.requirements.map((r, i) =>
- <li key={i}>
- <Requirement requirement={r} />
- </li>
- )}
- </ul>
- </div>;
-};
-
-Requirements.propTypes = {
- technique: PropTypes.shape({
- requirements: PropTypes.arrayOf(
- PropTypes.arrayOf(PropTypes.string),
- ),
- }),
-};
-
-export default Requirements;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import Requirement from './Requirement';
+
+const Requirements = ({ technique }) => {
+ const { t } = useTranslation();
+
+ if (!technique.requirements || !technique.requirements.length) {
+ return null;
+ }
+
+ return <div className="tech-requirements mb-3">
+ {t('techniques.requirements')}
+ <ul>
+ {technique.requirements.map((r, i) =>
+ <li key={i}>
+ <Requirement requirement={r} />
+ </li>
+ )}
+ </ul>
+ </div>;
+};
+
+Requirements.propTypes = {
+ technique: PropTypes.shape({
+ requirements: PropTypes.arrayOf(
+ PropTypes.arrayOf(PropTypes.string),
+ ),
+ }),
+};
+
+export default Requirements;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-
-import Icon from '../common/Icon';
-
-const Rulesets = ({ technique }) => {
- const { t } = useTranslation();
-
- return <div className="ruleset-box">
- {['competitive', 'owg', 'mg', 'nl'].map(r =>
- <span key={r} title={t(`techniques.rulesetDescriptions.${r}`)}>
- {technique && technique.rulesets && technique.rulesets[r] ?
- <Icon.ALLOWED className="text-success" />
- : null}
- {technique && technique.rulesets && !technique.rulesets[r] ?
- <Icon.FORBIDDEN className="text-danger" />
- : null}
- {!technique || !technique.rulesets ?
- <Icon.UNKNOWN />
- : null}
- {' '}
- {t(`techniques.rulesetCodes.${r}`)}
- </span>
- )}
- </div>;
-};
-
-Rulesets.propTypes = {
- technique: PropTypes.shape({
- rulesets: PropTypes.shape({
- }),
- }),
-};
-
-export default Rulesets;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import Icon from '../common/Icon';
+
+const Rulesets = ({ technique }) => {
+ const { t } = useTranslation();
+
+ return <div className="ruleset-box">
+ {['competitive', 'owg', 'mg', 'nl'].map(r =>
+ <span key={r} title={t(`techniques.rulesetDescriptions.${r}`)}>
+ {technique && technique.rulesets && technique.rulesets[r] ?
+ <Icon.ALLOWED className="text-success" />
+ : null}
+ {technique && technique.rulesets && !technique.rulesets[r] ?
+ <Icon.FORBIDDEN className="text-danger" />
+ : null}
+ {!technique || !technique.rulesets ?
+ <Icon.UNKNOWN />
+ : null}
+ {' '}
+ {t(`techniques.rulesetCodes.${r}`)}
+ </span>
+ )}
+ </div>;
+};
+
+Rulesets.propTypes = {
+ technique: PropTypes.shape({
+ rulesets: PropTypes.shape({
+ }),
+ }),
+};
+
+export default Rulesets;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Form } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-const TechFilter = ({ filter, setFilter }) => {
- const { t } = useTranslation();
-
- const handleChange = React.useCallback(e => {
- if (e.target.name.startsWith('ruleset.')) {
- const r = e.target.name.substring(8);
- setFilter({
- ...filter,
- ruleset: {
- ...filter.ruleset || {},
- [r]: e.target.checked ? '1' : '0',
- },
- });
- }
- }, [filter]);
-
- return <div className="tech-filter">
- <div>{t('techniques.rulesetFilterHeading')}</div>
- <div className="ruleset-box">
- {['competitive', 'owg', 'mg', 'nl'].map(r =>
- <Form.Check
- checked={!!(filter && filter.ruleset && filter.ruleset[r] === '1')}
- key={r}
- id={`tech.filter.ruleset.${r}`}
- name={`ruleset.${r}`}
- label={t(`techniques.rulesetCodes.${r}`)}
- onChange={handleChange}
- title={t(`techniques.rulesetDescriptions.${r}`)}
- type="checkbox"
- />
- )}
- </div>
- </div>;
-};
-
-TechFilter.propTypes = {
- filter: PropTypes.shape({
- ruleset: PropTypes.shape({
- }),
- }),
- setFilter: PropTypes.func,
-};
-
-export default TechFilter;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Form } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+const TechFilter = ({ filter, setFilter }) => {
+ const { t } = useTranslation();
+
+ const handleChange = React.useCallback(e => {
+ if (e.target.name.startsWith('ruleset.')) {
+ const r = e.target.name.substring(8);
+ setFilter({
+ ...filter,
+ ruleset: {
+ ...filter.ruleset || {},
+ [r]: e.target.checked ? '1' : '0',
+ },
+ });
+ }
+ }, [filter]);
+
+ return <div className="tech-filter">
+ <div>{t('techniques.rulesetFilterHeading')}</div>
+ <div className="ruleset-box">
+ {['competitive', 'owg', 'mg', 'nl'].map(r =>
+ <Form.Check
+ checked={!!(filter && filter.ruleset && filter.ruleset[r] === '1')}
+ key={r}
+ id={`tech.filter.ruleset.${r}`}
+ name={`ruleset.${r}`}
+ label={t(`techniques.rulesetCodes.${r}`)}
+ onChange={handleChange}
+ title={t(`techniques.rulesetDescriptions.${r}`)}
+ type="checkbox"
+ />
+ )}
+ </div>
+ </div>;
+};
+
+TechFilter.propTypes = {
+ filter: PropTypes.shape({
+ ruleset: PropTypes.shape({
+ }),
+ }),
+ setFilter: PropTypes.func,
+};
+
+export default TechFilter;
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import Icon from '../common/Icon';
-import { isApplicant, isDeniedApplicant, isRunner, mayApply } from '../../helpers/permissions';
-import { useUser } from '../../hooks/user';
-import i18n from '../../i18n';
-
-const apply = async tournament => {
- try {
- await axios.post(`/api/tournaments/${tournament.id}/apply`);
- toastr.success(i18n.t('tournaments.applySuccess'));
- } catch (e) {
- toastr.error(i18n.t('tournaments.applyError'));
- }
-};
-
-const getTitle = (user, tournament) => {
- if (isDeniedApplicant(user, tournament)) {
- return i18n.t('tournaments.applicationDenied');
- }
- if (isApplicant(user, tournament)) {
- return i18n.t('tournaments.applicationPending');
- }
- return i18n.t('tournaments.apply');
-};
-
-const ApplyButton = ({ tournament }) => {
- const { user } = useUser();
-
- if (!user || !tournament.accept_applications || isRunner(user, tournament)) return null;
-
- return <span className="d-inline-block" title={getTitle(user, tournament)}>
- <Button
- disabled={!mayApply(user, tournament)}
- onClick={() => apply(tournament)}
- variant="primary"
- >
- <Icon.APPLY title="" />
- </Button>
- </span>;
-};
-
-ApplyButton.propTypes = {
- tournament: PropTypes.shape({
- accept_applications: PropTypes.bool,
- id: PropTypes.number,
- }),
-};
-
-export default withTranslation()(ApplyButton);
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import Icon from '../common/Icon';
+import { isApplicant, isDeniedApplicant, isRunner, mayApply } from '../../helpers/permissions';
+import { useUser } from '../../hooks/user';
+import i18n from '../../i18n';
+
+const apply = async tournament => {
+ try {
+ await axios.post(`/api/tournaments/${tournament.id}/apply`);
+ toastr.success(i18n.t('tournaments.applySuccess'));
+ } catch (e) {
+ toastr.error(i18n.t('tournaments.applyError'));
+ }
+};
+
+const getTitle = (user, tournament) => {
+ if (isDeniedApplicant(user, tournament)) {
+ return i18n.t('tournaments.applicationDenied');
+ }
+ if (isApplicant(user, tournament)) {
+ return i18n.t('tournaments.applicationPending');
+ }
+ return i18n.t('tournaments.apply');
+};
+
+const ApplyButton = ({ tournament }) => {
+ const { user } = useUser();
+
+ if (!user || !tournament.accept_applications || isRunner(user, tournament)) return null;
+
+ return <span className="d-inline-block" title={getTitle(user, tournament)}>
+ <Button
+ disabled={!mayApply(user, tournament)}
+ onClick={() => apply(tournament)}
+ variant="primary"
+ >
+ <Icon.APPLY title="" />
+ </Button>
+ </span>;
+};
+
+ApplyButton.propTypes = {
+ tournament: PropTypes.shape({
+ accept_applications: PropTypes.bool,
+ id: PropTypes.number,
+ }),
+};
+
+export default withTranslation()(ApplyButton);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Container, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import ApplyButton from './ApplyButton';
-import Scoreboard from './Scoreboard';
-import ScoreChartButton from './ScoreChartButton';
-import SettingsButton from './SettingsButton';
-import ApplicationsButton from '../applications/Button';
-import Icon from '../common/Icon';
-import RawHTML from '../common/RawHTML';
-import Protocol from '../protocol/Protocol';
-import Rounds from '../rounds/List';
-import Box from '../users/Box';
-import {
- isRunner,
- mayAddRounds,
- mayUpdateTournament,
- mayViewProtocol,
-} from '../../helpers/permissions';
-import { getTranslation } from '../../helpers/Technique';
-import {
- getTournamentAdmins,
- getTournamentMonitors,
- hasRunners,
- hasScoreboard,
- hasTournamentAdmins,
- hasTournamentMonitors,
-} from '../../helpers/Tournament';
-import { useUser } from '../../hooks/user';
-import i18n from '../../i18n';
-
-const getClassName = (tournament, user) => {
- const classNames = ['tournament'];
- if (tournament.locked) {
- classNames.push('is-locked');
- } else {
- classNames.push('is-active');
- }
- if (isRunner(user, tournament)) {
- classNames.push('is-runner');
- }
- return classNames.join(' ');
-};
-
-const Detail = ({
- actions,
- tournament,
-}) => {
- const { t } = useTranslation();
- const { user } = useUser();
-
- return <Container className={getClassName(tournament, user)} fluid>
- <Row>
- <Col lg={8} xl={9}>
- <div className="d-flex align-items-center justify-content-between">
- <h1>
- {(tournament.description
- && getTranslation(tournament.description, 'title', i18n.language))
- || tournament.title}
- </h1>
- <div className="button-bar">
- {tournament.description && actions.editContent ?
- <Button
- className="ms-3"
- onClick={() => actions.editContent(tournament.description)}
- title={t('button.edit')}
- variant="outline-secondary"
- >
- <Icon.EDIT title="" />
- </Button>
- : null}
- <ApplicationsButton tournament={tournament} />
- <ApplyButton tournament={tournament} />
- {mayUpdateTournament(user, tournament) ?
- <SettingsButton tournament={tournament} />
- : null}
- {mayViewProtocol(user, tournament) ?
- <Protocol id={tournament.id} />
- : null}
- </div>
- </div>
- {tournament.description ?
- <RawHTML
- html={getTranslation(tournament.description, 'description', i18n.language)}
- />
- : null}
- </Col>
- </Row>
- <Row>
- <Col lg={{ order: 2, span: 4 }} xl={{ order: 2, span: 3 }}>
- <div className="tournament-sidebar">
- {hasScoreboard(tournament) ? <>
- <div className="d-flex align-items-center justify-content-between">
- <h2>{t('tournaments.scoreboard')}</h2>
- {hasRunners(tournament) && tournament.rounds.length > 2 ?
- <ScoreChartButton tournament={tournament} />
- : null}
- </div>
- {hasRunners(tournament) ?
- <Scoreboard tournament={tournament} />
- : null}
- </> : null}
- {hasTournamentAdmins(tournament) ?
- <>
- <div className="d-flex align-items-center justify-content-between">
- <h2>{t('tournaments.admins')}</h2>
- </div>
- {getTournamentAdmins(tournament).map(p =>
- <p key={p.id}><Box user={p.user} /></p>
- )}
- </>
- : null}
- {hasTournamentMonitors(tournament) ?
- <>
- <div className="d-flex align-items-center justify-content-between">
- <h2>{t('tournaments.monitors')}</h2>
- </div>
- {getTournamentMonitors(tournament).map(p =>
- <p key={p.id}><Box user={p.user} /></p>
- )}
- </>
- : null}
- </div>
- </Col>
- <Col lg={{ order: 1, span: 8 }} xl={{ order: 1, span: 9 }}>
- <div className="d-flex align-items-center justify-content-between">
- <h2>{t('rounds.heading')}</h2>
- {actions.addRound && mayAddRounds(user, tournament) ?
- <Button onClick={actions.addRound}>
- {t('rounds.new')}
- </Button>
- : null}
- </div>
- {tournament.rounds ?
- <Rounds
- loadMore={actions.moreRounds}
- rounds={tournament.rounds}
- tournament={tournament}
- />
- : null}
- </Col>
- </Row>
- </Container>;
-};
-
-Detail.propTypes = {
- actions: PropTypes.shape({
- addRound: PropTypes.func,
- editContent: PropTypes.func,
- moreRounds: PropTypes.func,
- }).isRequired,
- tournament: PropTypes.shape({
- description: PropTypes.shape({
- }),
- id: PropTypes.number,
- participants: PropTypes.arrayOf(PropTypes.shape({
- })),
- rounds: PropTypes.arrayOf(PropTypes.shape({
- })),
- title: PropTypes.string,
- }),
-};
-
-export default Detail;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Container, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import ApplyButton from './ApplyButton';
+import Scoreboard from './Scoreboard';
+import ScoreChartButton from './ScoreChartButton';
+import SettingsButton from './SettingsButton';
+import ApplicationsButton from '../applications/Button';
+import Icon from '../common/Icon';
+import RawHTML from '../common/RawHTML';
+import Protocol from '../protocol/Protocol';
+import Rounds from '../rounds/List';
+import Box from '../users/Box';
+import {
+ isRunner,
+ mayAddRounds,
+ mayUpdateTournament,
+ mayViewProtocol,
+} from '../../helpers/permissions';
+import { getTranslation } from '../../helpers/Technique';
+import {
+ getTournamentAdmins,
+ getTournamentMonitors,
+ hasRunners,
+ hasScoreboard,
+ hasTournamentAdmins,
+ hasTournamentMonitors,
+} from '../../helpers/Tournament';
+import { useUser } from '../../hooks/user';
+import i18n from '../../i18n';
+
+const getClassName = (tournament, user) => {
+ const classNames = ['tournament'];
+ if (tournament.locked) {
+ classNames.push('is-locked');
+ } else {
+ classNames.push('is-active');
+ }
+ if (isRunner(user, tournament)) {
+ classNames.push('is-runner');
+ }
+ return classNames.join(' ');
+};
+
+const Detail = ({
+ actions,
+ tournament,
+}) => {
+ const { t } = useTranslation();
+ const { user } = useUser();
+
+ return <Container className={getClassName(tournament, user)} fluid>
+ <Row>
+ <Col lg={8} xl={9}>
+ <div className="d-flex align-items-center justify-content-between">
+ <h1>
+ {(tournament.description
+ && getTranslation(tournament.description, 'title', i18n.language))
+ || tournament.title}
+ </h1>
+ <div className="button-bar">
+ {tournament.description && actions.editContent ?
+ <Button
+ className="ms-3"
+ onClick={() => actions.editContent(tournament.description)}
+ title={t('button.edit')}
+ variant="outline-secondary"
+ >
+ <Icon.EDIT title="" />
+ </Button>
+ : null}
+ <ApplicationsButton tournament={tournament} />
+ <ApplyButton tournament={tournament} />
+ {mayUpdateTournament(user, tournament) ?
+ <SettingsButton tournament={tournament} />
+ : null}
+ {mayViewProtocol(user, tournament) ?
+ <Protocol id={tournament.id} />
+ : null}
+ </div>
+ </div>
+ {tournament.description ?
+ <RawHTML
+ html={getTranslation(tournament.description, 'description', i18n.language)}
+ />
+ : null}
+ </Col>
+ </Row>
+ <Row>
+ <Col lg={{ order: 2, span: 4 }} xl={{ order: 2, span: 3 }}>
+ <div className="tournament-sidebar">
+ {hasScoreboard(tournament) ? <>
+ <div className="d-flex align-items-center justify-content-between">
+ <h2>{t('tournaments.scoreboard')}</h2>
+ {hasRunners(tournament) && tournament.rounds.length > 2 ?
+ <ScoreChartButton tournament={tournament} />
+ : null}
+ </div>
+ {hasRunners(tournament) ?
+ <Scoreboard tournament={tournament} />
+ : null}
+ </> : null}
+ {hasTournamentAdmins(tournament) ?
+ <>
+ <div className="d-flex align-items-center justify-content-between">
+ <h2>{t('tournaments.admins')}</h2>
+ </div>
+ {getTournamentAdmins(tournament).map(p =>
+ <p key={p.id}><Box user={p.user} /></p>
+ )}
+ </>
+ : null}
+ {hasTournamentMonitors(tournament) ?
+ <>
+ <div className="d-flex align-items-center justify-content-between">
+ <h2>{t('tournaments.monitors')}</h2>
+ </div>
+ {getTournamentMonitors(tournament).map(p =>
+ <p key={p.id}><Box user={p.user} /></p>
+ )}
+ </>
+ : null}
+ </div>
+ </Col>
+ <Col lg={{ order: 1, span: 8 }} xl={{ order: 1, span: 9 }}>
+ <div className="d-flex align-items-center justify-content-between">
+ <h2>{t('rounds.heading')}</h2>
+ {actions.addRound && mayAddRounds(user, tournament) ?
+ <Button onClick={actions.addRound}>
+ {t('rounds.new')}
+ </Button>
+ : null}
+ </div>
+ {tournament.rounds ?
+ <Rounds
+ loadMore={actions.moreRounds}
+ rounds={tournament.rounds}
+ tournament={tournament}
+ />
+ : null}
+ </Col>
+ </Row>
+ </Container>;
+};
+
+Detail.propTypes = {
+ actions: PropTypes.shape({
+ addRound: PropTypes.func,
+ editContent: PropTypes.func,
+ moreRounds: PropTypes.func,
+ }).isRequired,
+ tournament: PropTypes.shape({
+ description: PropTypes.shape({
+ }),
+ id: PropTypes.number,
+ participants: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ rounds: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ title: PropTypes.string,
+ }),
+};
+
+export default Detail;
+++ /dev/null
-import axios from 'axios';
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Form } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import DiscordChannelSelect from '../common/DiscordChannelSelect';
-import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
-import i18n from '../../i18n';
-import yup from '../../schema/yup';
-
-const DiscordForm = ({
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- touched,
- tournament,
- values,
-}) =>
-<Form noValidate onSubmit={handleSubmit}>
- <fieldset>
- <legend>{i18n.t('tournaments.discordSettings')}</legend>
- <Form.Group controlId="tournament.discord_round_category">
- <Form.Label>
- {i18n.t('tournaments.discordRoundCategory')}
- </Form.Label>
- <DiscordChannelSelect
- guild={tournament.discord}
- isInvalid={!!(touched.round_category && errors.round_category)}
- name="round_category"
- onBlur={handleBlur}
- onChange={handleChange}
- types={[4]}
- value={values.round_category || ''}
- />
- </Form.Group>
- <Form.Group controlId="tournament.discord_round_template">
- <Form.Label>
- {i18n.t('tournaments.discordRoundTemplate')}
- </Form.Label>
- <Form.Control
- isInvalid={!!(touched.round_template && errors.round_template)}
- name="round_template"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.round_template || ''}
- />
- </Form.Group>
- <Button className="mt-3" type="submit" variant="primary">
- {i18n.t('button.save')}
- </Button>
- </fieldset>
-</Form>;
-
-DiscordForm.propTypes = {
- errors: PropTypes.shape({
- round_category: PropTypes.string,
- round_template: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- touched: PropTypes.shape({
- round_category: PropTypes.bool,
- round_template: PropTypes.bool,
- }),
- tournament: PropTypes.shape({
- discord: PropTypes.string,
- }),
- values: PropTypes.shape({
- round_category: PropTypes.string,
- round_template: PropTypes.string,
- }),
-};
-
-export default withFormik({
- displayName: 'DiscordForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { round_category, round_template } = values;
- const { setErrors } = actions;
- const { tournament } = actions.props;
- try {
- await axios.post(`/api/tournaments/${tournament.id}/discord-settings`, {
- round_category,
- round_template,
- });
- toastr.success(i18n.t('tournaments.discordSettingsSuccess'));
- } catch (e) {
- toastr.error(i18n.t('tournaments.discordSettingsError'));
- if (e.response && e.response.data && e.response.data.errors) {
- setErrors(laravelErrorsToFormik(e.response.data.errors));
- }
- }
- },
- mapPropsToValues: ({ tournament }) => ({
- round_category: tournament.discord_round_category || '',
- round_template: tournament.discord_round_template || '',
- }),
- validationSchema: yup.object().shape({
- round_category: yup.string(),
- round_template: yup.string(),
- }),
-})(withTranslation()(DiscordForm));
--- /dev/null
+import axios from 'axios';
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Form } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import DiscordChannelSelect from '../common/DiscordChannelSelect';
+import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import i18n from '../../i18n';
+import yup from '../../schema/yup';
+
+const DiscordForm = ({
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ touched,
+ tournament,
+ values,
+}) =>
+<Form noValidate onSubmit={handleSubmit}>
+ <fieldset>
+ <legend>{i18n.t('tournaments.discordSettings')}</legend>
+ <Form.Group controlId="tournament.discord_round_category">
+ <Form.Label>
+ {i18n.t('tournaments.discordRoundCategory')}
+ </Form.Label>
+ <DiscordChannelSelect
+ guild={tournament.discord}
+ isInvalid={!!(touched.round_category && errors.round_category)}
+ name="round_category"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ types={[4]}
+ value={values.round_category || ''}
+ />
+ </Form.Group>
+ <Form.Group controlId="tournament.discord_round_template">
+ <Form.Label>
+ {i18n.t('tournaments.discordRoundTemplate')}
+ </Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.round_template && errors.round_template)}
+ name="round_template"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.round_template || ''}
+ />
+ </Form.Group>
+ <Button className="mt-3" type="submit" variant="primary">
+ {i18n.t('button.save')}
+ </Button>
+ </fieldset>
+</Form>;
+
+DiscordForm.propTypes = {
+ errors: PropTypes.shape({
+ round_category: PropTypes.string,
+ round_template: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ touched: PropTypes.shape({
+ round_category: PropTypes.bool,
+ round_template: PropTypes.bool,
+ }),
+ tournament: PropTypes.shape({
+ discord: PropTypes.string,
+ }),
+ values: PropTypes.shape({
+ round_category: PropTypes.string,
+ round_template: PropTypes.string,
+ }),
+};
+
+export default withFormik({
+ displayName: 'DiscordForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { round_category, round_template } = values;
+ const { setErrors } = actions;
+ const { tournament } = actions.props;
+ try {
+ await axios.post(`/api/tournaments/${tournament.id}/discord-settings`, {
+ round_category,
+ round_template,
+ });
+ toastr.success(i18n.t('tournaments.discordSettingsSuccess'));
+ } catch (e) {
+ toastr.error(i18n.t('tournaments.discordSettingsError'));
+ if (e.response && e.response.data && e.response.data.errors) {
+ setErrors(laravelErrorsToFormik(e.response.data.errors));
+ }
+ }
+ },
+ mapPropsToValues: ({ tournament }) => ({
+ round_category: tournament.discord_round_category || '',
+ round_template: tournament.discord_round_template || '',
+ }),
+ validationSchema: yup.object().shape({
+ round_category: yup.string(),
+ round_template: yup.string(),
+ }),
+})(withTranslation()(DiscordForm));
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
-
-import { getUserName } from '../../helpers/Participant';
-import { getRunners, getScoreTable } from '../../helpers/Tournament';
-
-const COLORS = [
- '#7cb5ec',
- '#434348',
- '#90ed7d',
- '#f7a35c',
- '#8085e9',
- '#f15c80',
- '#e4d354',
- '#2b908f',
- '#f45b5b',
- '#91e8e1',
-];
-
-const ScoreChart = ({
- tournament,
-}) =>
-<ResponsiveContainer height="100%" width="100%">
- <LineChart data={getScoreTable(tournament)} height={720} width={1280}>
- <XAxis dataKey="number" />
- <YAxis />
- <Tooltip />
- <Legend />
- {getRunners(tournament).map((runner, index) =>
- <Line
- dataKey={getUserName(runner)}
- key={runner.id}
- stroke={COLORS[index % COLORS.length]}
- type="monotone"
- />
- )}
- </LineChart>
-</ResponsiveContainer>;
-
-ScoreChart.propTypes = {
- tournament: PropTypes.shape({
- }),
-};
-
-export default ScoreChart;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
+
+import { getUserName } from '../../helpers/Participant';
+import { getRunners, getScoreTable } from '../../helpers/Tournament';
+
+const COLORS = [
+ '#7cb5ec',
+ '#434348',
+ '#90ed7d',
+ '#f7a35c',
+ '#8085e9',
+ '#f15c80',
+ '#e4d354',
+ '#2b908f',
+ '#f45b5b',
+ '#91e8e1',
+];
+
+const ScoreChart = ({
+ tournament,
+}) =>
+<ResponsiveContainer height="100%" width="100%">
+ <LineChart data={getScoreTable(tournament)} height={720} width={1280}>
+ <XAxis dataKey="number" />
+ <YAxis />
+ <Tooltip />
+ <Legend />
+ {getRunners(tournament).map((runner, index) =>
+ <Line
+ dataKey={getUserName(runner)}
+ key={runner.id}
+ stroke={COLORS[index % COLORS.length]}
+ type="monotone"
+ />
+ )}
+ </LineChart>
+</ResponsiveContainer>;
+
+ScoreChart.propTypes = {
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default ScoreChart;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import ScoreChartDialog from './ScoreChartDialog';
-import Icon from '../common/Icon';
-import i18n from '../../i18n';
-
-const ScoreChartButton = ({ tournament }) => {
- const [showDialog, setShowDialog] = useState(false);
-
- return <>
- <Button
- onClick={() => setShowDialog(true)}
- title={i18n.t('button.chart')}
- variant="info"
- >
- <Icon.CHART title="" />
- </Button>
- <ScoreChartDialog
- onHide={() => setShowDialog(false)}
- tournament={tournament}
- show={showDialog}
- />
- </>;
-};
-
-ScoreChartButton.propTypes = {
- tournament: PropTypes.shape({
- }),
-};
-
-export default withTranslation()(ScoreChartButton);
--- /dev/null
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import ScoreChartDialog from './ScoreChartDialog';
+import Icon from '../common/Icon';
+import i18n from '../../i18n';
+
+const ScoreChartButton = ({ tournament }) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ return <>
+ <Button
+ onClick={() => setShowDialog(true)}
+ title={i18n.t('button.chart')}
+ variant="info"
+ >
+ <Icon.CHART title="" />
+ </Button>
+ <ScoreChartDialog
+ onHide={() => setShowDialog(false)}
+ tournament={tournament}
+ show={showDialog}
+ />
+ </>;
+};
+
+ScoreChartButton.propTypes = {
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(ScoreChartButton);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Modal } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import Loading from '../common/Loading';
-import i18n from '../../i18n';
-
-const ScoreChart = React.lazy(() => import('./ScoreChart'));
-
-const ScoreChartDialog = ({
- onHide,
- show,
- tournament,
-}) =>
-<Modal className="score-chart-dialog" dialogClassName="modal-90w" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {i18n.t('tournaments.scoreChart')}
- </Modal.Title>
- </Modal.Header>
- <Modal.Body style={{ height: '80vh' }}>
- <React.Suspense fallback={<Loading />}>
- <ScoreChart tournament={tournament} />
- </React.Suspense>
- </Modal.Body>
- <Modal.Footer>
- <Button onClick={onHide} variant="secondary">
- {i18n.t('button.close')}
- </Button>
- </Modal.Footer>
-</Modal>;
-
-ScoreChartDialog.propTypes = {
- onHide: PropTypes.func,
- show: PropTypes.bool,
- tournament: PropTypes.shape({
- }),
-};
-
-export default withTranslation()(ScoreChartDialog);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Modal } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import Loading from '../common/Loading';
+import i18n from '../../i18n';
+
+const ScoreChart = React.lazy(() => import('./ScoreChart'));
+
+const ScoreChartDialog = ({
+ onHide,
+ show,
+ tournament,
+}) =>
+<Modal className="score-chart-dialog" dialogClassName="modal-90w" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {i18n.t('tournaments.scoreChart')}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body style={{ height: '80vh' }}>
+ <React.Suspense fallback={<Loading />}>
+ <ScoreChart tournament={tournament} />
+ </React.Suspense>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button onClick={onHide} variant="secondary">
+ {i18n.t('button.close')}
+ </Button>
+ </Modal.Footer>
+</Modal>;
+
+ScoreChartDialog.propTypes = {
+ onHide: PropTypes.func,
+ show: PropTypes.bool,
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(ScoreChartDialog);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Table } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Icon from '../common/Icon';
-import Box from '../users/Box';
-import { comparePlacement } from '../../helpers/Participant';
-import { getRunners } from '../../helpers/Tournament';
-import { useUser } from '../../hooks/user';
-
-const getRowClassName = (tournament, participant, user) => {
- const classNames = ['score'];
- if (participant && user && participant.user_id == user.id) {
- classNames.push('is-self');
- }
- return classNames.join(' ');
-};
-
-const getPlacementDisplay = participant => {
- if (participant.placement === 1) {
- return <Icon.FIRST_PLACE className="text-gold" size="lg" />;
- }
- if (participant.placement === 2) {
- return <Icon.SECOND_PLACE className="text-silver" size="lg" />;
- }
- if (participant.placement === 3) {
- return <Icon.THIRD_PLACE className="text-bronze" size="lg" />;
- }
- return participant.placement;
-};
-
-const twitchReg = /^https?:\/\/(www\.)?twitch\.tv/;
-const youtubeReg = /^https?:\/\/(www\.)?youtu(\.be|be\.)/;
-
-const getStreamVariant = participant => {
- if (!participant || !participant.user || !participant.user.stream_link) {
- return 'outline-secondary';
- }
- if (twitchReg.test(participant.user.stream_link)) {
- return 'outline-twitch';
- }
- if (youtubeReg.test(participant.user.stream_link)) {
- return 'outline-youtube';
- }
- return 'outline-secondary';
-};
-
-const getStreamIcon = participant => {
- const variant = getStreamVariant(participant);
- if (variant === 'outline-twitch') {
- return <Icon.TWITCH title="" />;
- }
- if (variant === 'outline-youtube') {
- return <Icon.YOUTUBE title="" />;
- }
- return <Icon.VIDEO title="" />;
-};
-
-const Scoreboard = ({ tournament }) => {
- const { t } = useTranslation();
- const { user } = useUser();
-
- return <Table striped className="scoreboard align-middle">
- <thead>
- <tr>
- <th className="text-center">{t('participants.placementShort')}</th>
- <th>{t('participants.participant')}</th>
- <th className="text-end">{t('participants.scoreShort')}</th>
- </tr>
- </thead>
- <tbody>
- {getRunners(tournament).sort(comparePlacement).map(participant =>
- <tr className={getRowClassName(tournament, participant, user)} key={participant.id}>
- <td className="text-center">
- {getPlacementDisplay(participant)}
- </td>
- <td>
- <div className="d-flex align-items-center justify-content-between">
- <Box user={participant.user} />
- {participant.user.stream_link ?
- <Button
- href={participant.user.stream_link}
- size="sm"
- target="_blank"
- title={t('users.stream')}
- variant={getStreamVariant(participant)}
- >
- {getStreamIcon(participant)}
- </Button>
- : null}
- </div>
- </td>
- <td className="text-end">{participant.score}</td>
- </tr>
- )}
- </tbody>
- </Table>;
-};
-
-Scoreboard.propTypes = {
- tournament: PropTypes.shape({
- }),
-};
-
-export default Scoreboard;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Table } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Icon from '../common/Icon';
+import Box from '../users/Box';
+import { comparePlacement } from '../../helpers/Participant';
+import { getRunners } from '../../helpers/Tournament';
+import { useUser } from '../../hooks/user';
+
+const getRowClassName = (tournament, participant, user) => {
+ const classNames = ['score'];
+ if (participant && user && participant.user_id == user.id) {
+ classNames.push('is-self');
+ }
+ return classNames.join(' ');
+};
+
+const getPlacementDisplay = participant => {
+ if (participant.placement === 1) {
+ return <Icon.FIRST_PLACE className="text-gold" size="lg" />;
+ }
+ if (participant.placement === 2) {
+ return <Icon.SECOND_PLACE className="text-silver" size="lg" />;
+ }
+ if (participant.placement === 3) {
+ return <Icon.THIRD_PLACE className="text-bronze" size="lg" />;
+ }
+ return participant.placement;
+};
+
+const twitchReg = /^https?:\/\/(www\.)?twitch\.tv/;
+const youtubeReg = /^https?:\/\/(www\.)?youtu(\.be|be\.)/;
+
+const getStreamVariant = participant => {
+ if (!participant || !participant.user || !participant.user.stream_link) {
+ return 'outline-secondary';
+ }
+ if (twitchReg.test(participant.user.stream_link)) {
+ return 'outline-twitch';
+ }
+ if (youtubeReg.test(participant.user.stream_link)) {
+ return 'outline-youtube';
+ }
+ return 'outline-secondary';
+};
+
+const getStreamIcon = participant => {
+ const variant = getStreamVariant(participant);
+ if (variant === 'outline-twitch') {
+ return <Icon.TWITCH title="" />;
+ }
+ if (variant === 'outline-youtube') {
+ return <Icon.YOUTUBE title="" />;
+ }
+ return <Icon.VIDEO title="" />;
+};
+
+const Scoreboard = ({ tournament }) => {
+ const { t } = useTranslation();
+ const { user } = useUser();
+
+ return <Table striped className="scoreboard align-middle">
+ <thead>
+ <tr>
+ <th className="text-center">{t('participants.placementShort')}</th>
+ <th>{t('participants.participant')}</th>
+ <th className="text-end">{t('participants.scoreShort')}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {getRunners(tournament).sort(comparePlacement).map(participant =>
+ <tr className={getRowClassName(tournament, participant, user)} key={participant.id}>
+ <td className="text-center">
+ {getPlacementDisplay(participant)}
+ </td>
+ <td>
+ <div className="d-flex align-items-center justify-content-between">
+ <Box user={participant.user} />
+ {participant.user.stream_link ?
+ <Button
+ href={participant.user.stream_link}
+ size="sm"
+ target="_blank"
+ title={t('users.stream')}
+ variant={getStreamVariant(participant)}
+ >
+ {getStreamIcon(participant)}
+ </Button>
+ : null}
+ </div>
+ </td>
+ <td className="text-end">{participant.score}</td>
+ </tr>
+ )}
+ </tbody>
+ </Table>;
+};
+
+Scoreboard.propTypes = {
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default Scoreboard;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import SettingsDialog from './SettingsDialog';
-import Icon from '../common/Icon';
-import i18n from '../../i18n';
-
-const SettingsButton = ({ tournament }) => {
- const [showDialog, setShowDialog] = useState(false);
-
- return <>
- <Button
- onClick={() => setShowDialog(true)}
- title={i18n.t('button.settings')}
- variant="outline-secondary"
- >
- <Icon.SETTINGS title="" />
- </Button>
- <SettingsDialog
- onHide={() => setShowDialog(false)}
- tournament={tournament}
- show={showDialog}
- />
- </>;
-};
-
-SettingsButton.propTypes = {
- tournament: PropTypes.shape({
- }),
-};
-
-export default withTranslation()(SettingsButton);
--- /dev/null
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import SettingsDialog from './SettingsDialog';
+import Icon from '../common/Icon';
+import i18n from '../../i18n';
+
+const SettingsButton = ({ tournament }) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ return <>
+ <Button
+ onClick={() => setShowDialog(true)}
+ title={i18n.t('button.settings')}
+ variant="outline-secondary"
+ >
+ <Icon.SETTINGS title="" />
+ </Button>
+ <SettingsDialog
+ onHide={() => setShowDialog(false)}
+ tournament={tournament}
+ show={showDialog}
+ />
+ </>;
+};
+
+SettingsButton.propTypes = {
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(SettingsButton);
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import DiscordForm from './DiscordForm';
-import DiscordSelect from '../common/DiscordSelect';
-import Icon from '../common/Icon';
-import ToggleSwitch from '../common/ToggleSwitch';
-import Tournament from '../../helpers/Tournament';
-import i18n from '../../i18n';
-
-const open = async tournament => {
- try {
- await axios.post(`/api/tournaments/${tournament.id}/open`);
- toastr.success(i18n.t('tournaments.openSuccess'));
- } catch (e) {
- toastr.error(i18n.t('tournaments.openError'));
- }
-};
-
-const close = async tournament => {
- try {
- await axios.post(`/api/tournaments/${tournament.id}/close`);
- toastr.success(i18n.t('tournaments.closeSuccess'));
- } catch (e) {
- toastr.error(i18n.t('tournaments.closeError'));
- }
-};
-
-const lock = async tournament => {
- try {
- await axios.post(`/api/tournaments/${tournament.id}/lock`);
- toastr.success(i18n.t('tournaments.lockSuccess'));
- } catch (e) {
- toastr.error(i18n.t('tournaments.lockError'));
- }
-};
-
-const unlock = async tournament => {
- try {
- await axios.post(`/api/tournaments/${tournament.id}/unlock`);
- toastr.success(i18n.t('tournaments.unlockSuccess'));
- } catch (e) {
- toastr.error(i18n.t('tournaments.unlockError'));
- }
-};
-
-const setDiscord = async (tournament, guild_id) => {
- try {
- await axios.post(`/api/tournaments/${tournament.id}/discord`, { guild_id });
- toastr.success(i18n.t('tournaments.discordSuccess'));
- } catch (e) {
- toastr.error(i18n.t('tournaments.discordError'));
- }
-};
-
-const settings = async (tournament, params) => {
- try {
- await axios.post(`/api/tournaments/${tournament.id}/settings`, params);
- toastr.success(i18n.t('tournaments.settingsSuccess'));
- } catch (e) {
- toastr.error(i18n.t('tournaments.settingsError'));
- }
-};
-
-const inviteUrl = 'https://discordapp.com/oauth2/authorize?client_id=951113702839549982&scope=bot';
-
-const SettingsDialog = ({
- onHide,
- show,
- tournament,
-}) =>
-<Modal
- className="settings-dialog"
- onHide={onHide}
- show={show}
- size={tournament.discord ? 'lg' : 'md'}
->
- <Modal.Header closeButton>
- <Modal.Title>
- {i18n.t('tournaments.settings')}
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <Row>
- <Col sm={tournament.discord ? 6 : 12}>
- {Tournament.hasSignup(tournament) ?
- <div className="d-flex align-items-center justify-content-between mb-3">
- <span>{i18n.t('tournaments.open')}</span>
- <ToggleSwitch
- onChange={({ target: { value } }) => value
- ? open(tournament) : close(tournament)}
- value={tournament.accept_applications}
- />
- </div>
- : null}
- <div className="d-flex align-items-center justify-content-between mb-3">
- <span>{i18n.t('tournaments.locked')}</span>
- <ToggleSwitch
- onChange={({ target: { value } }) => value
- ? lock(tournament) : unlock(tournament)}
- value={tournament.locked}
- />
- </div>
- <div className="d-flex align-items-center justify-content-between mb-3">
- <span>{i18n.t('tournaments.showNumbers')}</span>
- <ToggleSwitch
- onChange={({ target: { value } }) =>
- settings(tournament, { show_numbers: value })}
- value={tournament.show_numbers}
- />
- </div>
- <div className="d-flex align-items-center justify-content-between mb-3">
- <span title={i18n.t('tournaments.resultRevealDescription')}>
- {i18n.t('tournaments.resultReveal')}
- </span>
- <Form.Select
- onChange={({ target: { value } }) =>
- settings(tournament, { result_reveal: value })}
- style={{ width: '50%' }}
- value={tournament.result_reveal}
- >
- {['never', 'finishers', 'participants', 'always'].map((key) =>
- <option
- key={key}
- title={i18n.t(`tournaments.resultRevealOptionDescription.${key}`)}
- value={key}
- >
- {i18n.t(`tournaments.resultRevealOption.${key}`)}
- </option>
- )}
- </Form.Select>
- </div>
- <div className="d-flex align-items-center justify-content-between">
- <div>
- <p>{i18n.t('tournaments.discord')}</p>
- {!tournament.discord ?
- <div>
- <Button
- href={inviteUrl}
- target="_blank"
- variant="discord"
- >
- <Icon.DISCORD />
- {' '}
- {i18n.t('tournaments.inviteBot')}
- </Button>
- </div>
- : null}
- </div>
- <DiscordSelect
- onChange={({ target: { value } }) => setDiscord(tournament, value)}
- value={tournament.discord}
- />
- </div>
- </Col>
- {tournament.discord ?
- <Col sm={6}>
- <DiscordForm tournament={tournament} />
- </Col>
- : null}
- </Row>
- </Modal.Body>
- <Modal.Footer>
- <Button onClick={onHide} variant="secondary">
- {i18n.t('button.close')}
- </Button>
- </Modal.Footer>
-</Modal>;
-
-SettingsDialog.propTypes = {
- onHide: PropTypes.func,
- show: PropTypes.bool,
- tournament: PropTypes.shape({
- accept_applications: PropTypes.bool,
- discord: PropTypes.string,
- locked: PropTypes.bool,
- result_reveal: PropTypes.string,
- show_numbers: PropTypes.bool,
- }),
-};
-
-export default withTranslation()(SettingsDialog);
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import DiscordForm from './DiscordForm';
+import DiscordSelect from '../common/DiscordSelect';
+import Icon from '../common/Icon';
+import ToggleSwitch from '../common/ToggleSwitch';
+import Tournament from '../../helpers/Tournament';
+import i18n from '../../i18n';
+
+const open = async tournament => {
+ try {
+ await axios.post(`/api/tournaments/${tournament.id}/open`);
+ toastr.success(i18n.t('tournaments.openSuccess'));
+ } catch (e) {
+ toastr.error(i18n.t('tournaments.openError'));
+ }
+};
+
+const close = async tournament => {
+ try {
+ await axios.post(`/api/tournaments/${tournament.id}/close`);
+ toastr.success(i18n.t('tournaments.closeSuccess'));
+ } catch (e) {
+ toastr.error(i18n.t('tournaments.closeError'));
+ }
+};
+
+const lock = async tournament => {
+ try {
+ await axios.post(`/api/tournaments/${tournament.id}/lock`);
+ toastr.success(i18n.t('tournaments.lockSuccess'));
+ } catch (e) {
+ toastr.error(i18n.t('tournaments.lockError'));
+ }
+};
+
+const unlock = async tournament => {
+ try {
+ await axios.post(`/api/tournaments/${tournament.id}/unlock`);
+ toastr.success(i18n.t('tournaments.unlockSuccess'));
+ } catch (e) {
+ toastr.error(i18n.t('tournaments.unlockError'));
+ }
+};
+
+const setDiscord = async (tournament, guild_id) => {
+ try {
+ await axios.post(`/api/tournaments/${tournament.id}/discord`, { guild_id });
+ toastr.success(i18n.t('tournaments.discordSuccess'));
+ } catch (e) {
+ toastr.error(i18n.t('tournaments.discordError'));
+ }
+};
+
+const settings = async (tournament, params) => {
+ try {
+ await axios.post(`/api/tournaments/${tournament.id}/settings`, params);
+ toastr.success(i18n.t('tournaments.settingsSuccess'));
+ } catch (e) {
+ toastr.error(i18n.t('tournaments.settingsError'));
+ }
+};
+
+const inviteUrl = 'https://discordapp.com/oauth2/authorize?client_id=951113702839549982&scope=bot';
+
+const SettingsDialog = ({
+ onHide,
+ show,
+ tournament,
+}) =>
+<Modal
+ className="settings-dialog"
+ onHide={onHide}
+ show={show}
+ size={tournament.discord ? 'lg' : 'md'}
+>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {i18n.t('tournaments.settings')}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={tournament.discord ? 6 : 12}>
+ {Tournament.hasSignup(tournament) ?
+ <div className="d-flex align-items-center justify-content-between mb-3">
+ <span>{i18n.t('tournaments.open')}</span>
+ <ToggleSwitch
+ onChange={({ target: { value } }) => value
+ ? open(tournament) : close(tournament)}
+ value={tournament.accept_applications}
+ />
+ </div>
+ : null}
+ <div className="d-flex align-items-center justify-content-between mb-3">
+ <span>{i18n.t('tournaments.locked')}</span>
+ <ToggleSwitch
+ onChange={({ target: { value } }) => value
+ ? lock(tournament) : unlock(tournament)}
+ value={tournament.locked}
+ />
+ </div>
+ <div className="d-flex align-items-center justify-content-between mb-3">
+ <span>{i18n.t('tournaments.showNumbers')}</span>
+ <ToggleSwitch
+ onChange={({ target: { value } }) =>
+ settings(tournament, { show_numbers: value })}
+ value={tournament.show_numbers}
+ />
+ </div>
+ <div className="d-flex align-items-center justify-content-between mb-3">
+ <span title={i18n.t('tournaments.resultRevealDescription')}>
+ {i18n.t('tournaments.resultReveal')}
+ </span>
+ <Form.Select
+ onChange={({ target: { value } }) =>
+ settings(tournament, { result_reveal: value })}
+ style={{ width: '50%' }}
+ value={tournament.result_reveal}
+ >
+ {['never', 'finishers', 'participants', 'always'].map((key) =>
+ <option
+ key={key}
+ title={i18n.t(`tournaments.resultRevealOptionDescription.${key}`)}
+ value={key}
+ >
+ {i18n.t(`tournaments.resultRevealOption.${key}`)}
+ </option>
+ )}
+ </Form.Select>
+ </div>
+ <div className="d-flex align-items-center justify-content-between">
+ <div>
+ <p>{i18n.t('tournaments.discord')}</p>
+ {!tournament.discord ?
+ <div>
+ <Button
+ href={inviteUrl}
+ target="_blank"
+ variant="discord"
+ >
+ <Icon.DISCORD />
+ {' '}
+ {i18n.t('tournaments.inviteBot')}
+ </Button>
+ </div>
+ : null}
+ </div>
+ <DiscordSelect
+ onChange={({ target: { value } }) => setDiscord(tournament, value)}
+ value={tournament.discord}
+ />
+ </div>
+ </Col>
+ {tournament.discord ?
+ <Col sm={6}>
+ <DiscordForm tournament={tournament} />
+ </Col>
+ : null}
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button onClick={onHide} variant="secondary">
+ {i18n.t('button.close')}
+ </Button>
+ </Modal.Footer>
+</Modal>;
+
+SettingsDialog.propTypes = {
+ onHide: PropTypes.func,
+ show: PropTypes.bool,
+ tournament: PropTypes.shape({
+ accept_applications: PropTypes.bool,
+ discord: PropTypes.string,
+ locked: PropTypes.bool,
+ result_reveal: PropTypes.string,
+ show_numbers: PropTypes.bool,
+ }),
+};
+
+export default withTranslation()(SettingsDialog);
+++ /dev/null
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Icon from '../common/Icon';
-import ToggleSwitch from '../common/ToggleSwitch';
-import {
- IN_GAME_MODES,
- RAM_ADDR,
- SRAM_ADDR,
- WRAM_ADDR,
- buildPrizeMap,
-} from '../../helpers/alttp-ram';
-import { computeState } from '../../helpers/tracker';
-import { useSNES } from '../../hooks/snes';
-import { useTracker } from '../../hooks/tracker';
-
-const AutoTracking = () => {
- const [enabled, setEnabled] = React.useState(false);
- const [prizeMap, setPrizeMap] = React.useState(buildPrizeMap());
-
- const {
- disable: disableSNES,
- enable: enableSNES,
- openSettings,
- sock,
- status,
- } = useSNES();
- const { config, setAutoState } = useTracker();
- const { t } = useTranslation();
-
- const enable = React.useCallback(() => {
- enableSNES();
- setEnabled(true);
- }, []);
-
- const disable = React.useCallback(() => {
- disableSNES();
- setEnabled(false);
- }, []);
-
- React.useEffect(() => {
- const savedSettings = localStorage.getItem('tracker.settings');
- if (savedSettings) {
- const settings = JSON.parse(savedSettings);
- if (settings.autoTrack) {
- enable();
- }
- }
- }, []);
-
- const saveSettings = React.useCallback((newSettings) => {
- const savedSettings = localStorage.getItem('tracker.settings');
- const settings = savedSettings
- ? { ...JSON.parse(savedSettings), ...newSettings }
- : newSettings;
- localStorage.setItem('tracker.settings', JSON.stringify(settings));
- }, []);
-
- const toggle = React.useCallback(() => {
- if (enabled) {
- disable();
- saveSettings({ autoTrack: false });
- } else {
- enable();
- saveSettings({ autoTrack: true });
- }
- }, [enabled]);
-
- // poll game and push state
- React.useEffect(() => {
- if (!enabled || status.error || !status.connected || !status.device) return;
- const updateState = () => {
- const saveStart = WRAM_ADDR.SAVE_DATA;
- const saveSize = SRAM_ADDR.INV_END;
- sock.current.readWRAM(saveStart, saveSize, (data) => {
- const computed = computeState(config, data, prizeMap);
- setAutoState(computed);
- });
- };
- const fetchPrizes = () => {
- sock.current.readBytes(RAM_ADDR.PRIZE_MAP, 13, (prizes) => {
- sock.current.readBytes(RAM_ADDR.CRYSTAL_MAP, 13, (crystals) => {
- setPrizeMap(m => {
- const newMap = buildPrizeMap(prizes, crystals);
- return JSON.stringify(m) === JSON.stringify(newMap) ? m : newMap;
- });
- });
- });
- };
- const checkInGame = () => {
- sock.current.readWRAM(WRAM_ADDR.GAME_MODE, 1, (data) => {
- if (IN_GAME_MODES.includes(data[0])) {
- fetchPrizes();
- updateState();
- }
- });
- };
- const timer = setInterval(checkInGame, 1000);
- return () => {
- clearInterval(timer);
- };
- }, [enabled && !status.error && status.connected && status.device, config, prizeMap, sock]);
-
- const statusMsg = React.useMemo(() => {
- if (!enabled) {
- return 'disabled';
- }
- if (status.error) {
- return 'error';
- }
- if (!status.connected) {
- return 'disconnected';
- }
- if (!status.device) {
- return 'no-device';
- }
- return 'tracking';
- }, [enabled, status]);
-
- return <div className="auto-tracking">
- {['disconnected', 'error', 'no-device'].includes(statusMsg) ?
- <Icon.WARNING
- className="me-2 text-warning"
- size="lg"
- title={t(`autoTracking.statusMsg.${statusMsg}`, { device: status.device })}
- />
- : null}
- {['not-applicable', 'not-in-game'].includes(statusMsg) ?
- <Icon.INFO
- className="me-2 text-info"
- size="lg"
- title={t(`autoTracking.statusMsg.${statusMsg}`, { device: status.device })}
- />
- : null}
- <Button
- className="me-2"
- onClick={openSettings}
- size="sm"
- title={t('snes.settings')}
- variant="outline-secondary"
- >
- <Icon.SETTINGS title="" />
- </Button>
- <ToggleSwitch
- onChange={toggle}
- title={t('autoTracking.heading')}
- value={enabled}
- />
- </div>;
-};
-
-export default AutoTracking;
--- /dev/null
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Icon from '../common/Icon';
+import ToggleSwitch from '../common/ToggleSwitch';
+import {
+ IN_GAME_MODES,
+ RAM_ADDR,
+ SRAM_ADDR,
+ WRAM_ADDR,
+ buildPrizeMap,
+} from '../../helpers/alttp-ram';
+import { computeState } from '../../helpers/tracker';
+import { useSNES } from '../../hooks/snes';
+import { useTracker } from '../../hooks/tracker';
+
+const AutoTracking = () => {
+ const [enabled, setEnabled] = React.useState(false);
+ const [prizeMap, setPrizeMap] = React.useState(buildPrizeMap());
+
+ const {
+ disable: disableSNES,
+ enable: enableSNES,
+ openSettings,
+ sock,
+ status,
+ } = useSNES();
+ const { config, setAutoState } = useTracker();
+ const { t } = useTranslation();
+
+ const enable = React.useCallback(() => {
+ enableSNES();
+ setEnabled(true);
+ }, []);
+
+ const disable = React.useCallback(() => {
+ disableSNES();
+ setEnabled(false);
+ }, []);
+
+ React.useEffect(() => {
+ const savedSettings = localStorage.getItem('tracker.settings');
+ if (savedSettings) {
+ const settings = JSON.parse(savedSettings);
+ if (settings.autoTrack) {
+ enable();
+ }
+ }
+ }, []);
+
+ const saveSettings = React.useCallback((newSettings) => {
+ const savedSettings = localStorage.getItem('tracker.settings');
+ const settings = savedSettings
+ ? { ...JSON.parse(savedSettings), ...newSettings }
+ : newSettings;
+ localStorage.setItem('tracker.settings', JSON.stringify(settings));
+ }, []);
+
+ const toggle = React.useCallback(() => {
+ if (enabled) {
+ disable();
+ saveSettings({ autoTrack: false });
+ } else {
+ enable();
+ saveSettings({ autoTrack: true });
+ }
+ }, [enabled]);
+
+ // poll game and push state
+ React.useEffect(() => {
+ if (!enabled || status.error || !status.connected || !status.device) return;
+ const updateState = () => {
+ const saveStart = WRAM_ADDR.SAVE_DATA;
+ const saveSize = SRAM_ADDR.INV_END;
+ sock.current.readWRAM(saveStart, saveSize, (data) => {
+ const computed = computeState(config, data, prizeMap);
+ setAutoState(computed);
+ });
+ };
+ const fetchPrizes = () => {
+ sock.current.readBytes(RAM_ADDR.PRIZE_MAP, 13, (prizes) => {
+ sock.current.readBytes(RAM_ADDR.CRYSTAL_MAP, 13, (crystals) => {
+ setPrizeMap(m => {
+ const newMap = buildPrizeMap(prizes, crystals);
+ return JSON.stringify(m) === JSON.stringify(newMap) ? m : newMap;
+ });
+ });
+ });
+ };
+ const checkInGame = () => {
+ sock.current.readWRAM(WRAM_ADDR.GAME_MODE, 1, (data) => {
+ if (IN_GAME_MODES.includes(data[0])) {
+ fetchPrizes();
+ updateState();
+ }
+ });
+ };
+ const timer = setInterval(checkInGame, 1000);
+ return () => {
+ clearInterval(timer);
+ };
+ }, [enabled && !status.error && status.connected && status.device, config, prizeMap, sock]);
+
+ const statusMsg = React.useMemo(() => {
+ if (!enabled) {
+ return 'disabled';
+ }
+ if (status.error) {
+ return 'error';
+ }
+ if (!status.connected) {
+ return 'disconnected';
+ }
+ if (!status.device) {
+ return 'no-device';
+ }
+ return 'tracking';
+ }, [enabled, status]);
+
+ return <div className="auto-tracking">
+ {['disconnected', 'error', 'no-device'].includes(statusMsg) ?
+ <Icon.WARNING
+ className="me-2 text-warning"
+ size="lg"
+ title={t(`autoTracking.statusMsg.${statusMsg}`, { device: status.device })}
+ />
+ : null}
+ {['not-applicable', 'not-in-game'].includes(statusMsg) ?
+ <Icon.INFO
+ className="me-2 text-info"
+ size="lg"
+ title={t(`autoTracking.statusMsg.${statusMsg}`, { device: status.device })}
+ />
+ : null}
+ <Button
+ className="me-2"
+ onClick={openSettings}
+ size="sm"
+ title={t('snes.settings')}
+ variant="outline-secondary"
+ >
+ <Icon.SETTINGS title="" />
+ </Button>
+ <ToggleSwitch
+ onChange={toggle}
+ title={t('autoTracking.heading')}
+ value={enabled}
+ />
+ </div>;
+};
+
+export default AutoTracking;
+++ /dev/null
-import { drag } from 'd3-drag';
-import { select } from 'd3-selection';
-import React from 'react';
-
-import Dungeons from './Dungeons';
-import Items from './Items';
-import Map from './Map';
-import ToggleIcon from './ToggleIcon';
-import ZeldaIcon from '../common/ZeldaIcon';
-import { shouldShowDungeonItem } from '../../helpers/tracker';
-import { useTracker } from '../../hooks/tracker';
-
-const LAYOUTS = {
- defaultHorizontal: {
- width: 100,
- height: 60,
- itemsTransform: 'translate(1 1) scale(22)',
- dungeonColumns: 4,
- dungeonsTransform: 'translate(1 39) scale(98)',
- mapTransform: 'translate(24 0) scale(76)',
- },
- defaultVertical: {
- width: 100,
- height: 100,
- itemsTransform: 'translate(10 1) scale(30)',
- dungeonColumns: 2,
- dungeonsTransform: 'translate(1 51) scale(48)',
- mapTransform: 'translate(50 0) scale(50)',
- },
- manyDungeonItemsVertical: {
- width: 80,
- height: 100,
- itemsTransform: 'translate(1 1) scale(27)',
- dungeonColumns: 1,
- dungeonsTransform: 'translate(1 48) scale(24)',
- mapTransform: 'translate(30 0) scale(50)',
- },
-};
-
-const Canvas = () => {
- const [dragging, setDragging] = React.useState(null);
- const { addPin, config, pins, removePin } = useTracker();
-
- const layout = React.useMemo(() => {
- if (config.mapLayout === 'vertical') {
- let count = 0;
- if (shouldShowDungeonItem(config, 'Map')) {
- ++count;
- }
- if (shouldShowDungeonItem(config, 'Compass')) {
- ++count;
- }
- if (shouldShowDungeonItem(config, 'Small')) {
- ++count;
- }
- if (shouldShowDungeonItem(config, 'Big')) {
- ++count;
- }
- const compact = config.compactKeysanity && count === 4;
- return !compact && count > 2
- ? LAYOUTS.manyDungeonItemsVertical : LAYOUTS.defaultVertical;
- } else {
- return LAYOUTS.defaultHorizontal;
- }
- }, [config]);
-
- React.useEffect(() => {
- const canvas = select('.canvas');
- const bbox = canvas.select('.background');
- const start = { x: 0, y: 0 };
- const onStart = function (e) {
- start.x = e.x;
- start.y = e.y;
- };
- const onDrag = function (e) {
- const bounds = bbox.node().getBoundingClientRect();
- const distance = Math.max(Math.abs(e.x - start.x), Math.abs(e.y - start.y));
- if (distance > 5) {
- setDragging({
- icon: this.dataset['icon'],
- x: (e.x - bounds.x) / bounds.width,
- y: (e.y - bounds.y) / bounds.height,
- });
- } else {
- setDragging(null);
- }
- };
- const onEnd = function (e) {
- const bounds = bbox.node().getBoundingClientRect();
- setDragging(null);
- const distance = Math.max(Math.abs(e.x - start.x), Math.abs(e.y - start.y));
- if (distance > 5) {
- addPin({
- icon: this.dataset['icon'],
- x: (e.x - bounds.x) / bounds.width,
- y: (e.y - bounds.y) / bounds.height,
- });
- if (this.classList.contains('map-pin')) {
- let id = 0;
- this.classList.forEach(name => {
- if (name.startsWith('map-pin-')) {
- id = parseInt(name.substr(8), 10);
- }
- });
- removePin({ id });
- }
- }
- };
- const selection = canvas.selectAll('.toggle-icon');
- const draggable = drag()
- .container(bbox)
- .clickDistance(5)
- .on('start', onStart)
- .on('drag', onDrag)
- .on('end', onEnd);
- selection.call(draggable);
- return () => {
- selection.on('.drag', null);
- };
- }, [pins, removePin]);
-
- return <svg
- xmlns="http://www.w3.org/2000/svg"
- className="canvas"
- width={layout.width}
- height={layout.height}
- viewBox={`0 0 ${layout.width} ${layout.height}`}
- onContextMenu={(e) => {
- e.preventDefault();
- e.stopPropagation();
- }}
- >
- <rect
- className="background"
- fill="transparent"
- x="0" y="0"
- width={layout.width}
- height={layout.height}
- />
- <g className="items" transform={layout.itemsTransform}>
- <Items />
- </g>
- <g className="dungeons" transform={layout.dungeonsTransform}>
- <Dungeons columns={layout.dungeonColumns} />
- </g>
- <g className="tracker-map" transform={layout.mapTransform}>
- <Map />
- </g>
- <g className="pins">
- {pins.map(pin =>
- <ToggleIcon
- key={pin.id}
- className={`map-pin map-pin-${pin.id}`}
- controller={ToggleIcon.pinController(pin, removePin)}
- icons={[pin.icon]}
- svg
- transform={
- `translate(${pin.x * layout.width} ${pin.y * layout.height}) scale(3)`
- }
- />
- )}
- </g>
- {dragging ?
- <g transform={
- `translate(${dragging.x * layout.width} ${dragging.y * layout.height}) scale(4)`
- }>
- <ZeldaIcon name={dragging.icon} svg />
- </g>
- : null}
- </svg>;
-};
-
-export default Canvas;
--- /dev/null
+import { drag } from 'd3-drag';
+import { select } from 'd3-selection';
+import React from 'react';
+
+import Dungeons from './Dungeons';
+import Items from './Items';
+import Map from './Map';
+import ToggleIcon from './ToggleIcon';
+import ZeldaIcon from '../common/ZeldaIcon';
+import { shouldShowDungeonItem } from '../../helpers/tracker';
+import { useTracker } from '../../hooks/tracker';
+
+const LAYOUTS = {
+ defaultHorizontal: {
+ width: 100,
+ height: 60,
+ itemsTransform: 'translate(1 1) scale(22)',
+ dungeonColumns: 4,
+ dungeonsTransform: 'translate(1 39) scale(98)',
+ mapTransform: 'translate(24 0) scale(76)',
+ },
+ defaultVertical: {
+ width: 100,
+ height: 100,
+ itemsTransform: 'translate(10 1) scale(30)',
+ dungeonColumns: 2,
+ dungeonsTransform: 'translate(1 51) scale(48)',
+ mapTransform: 'translate(50 0) scale(50)',
+ },
+ manyDungeonItemsVertical: {
+ width: 80,
+ height: 100,
+ itemsTransform: 'translate(1 1) scale(27)',
+ dungeonColumns: 1,
+ dungeonsTransform: 'translate(1 48) scale(24)',
+ mapTransform: 'translate(30 0) scale(50)',
+ },
+};
+
+const Canvas = () => {
+ const [dragging, setDragging] = React.useState(null);
+ const { addPin, config, pins, removePin } = useTracker();
+
+ const layout = React.useMemo(() => {
+ if (config.mapLayout === 'vertical') {
+ let count = 0;
+ if (shouldShowDungeonItem(config, 'Map')) {
+ ++count;
+ }
+ if (shouldShowDungeonItem(config, 'Compass')) {
+ ++count;
+ }
+ if (shouldShowDungeonItem(config, 'Small')) {
+ ++count;
+ }
+ if (shouldShowDungeonItem(config, 'Big')) {
+ ++count;
+ }
+ const compact = config.compactKeysanity && count === 4;
+ return !compact && count > 2
+ ? LAYOUTS.manyDungeonItemsVertical : LAYOUTS.defaultVertical;
+ } else {
+ return LAYOUTS.defaultHorizontal;
+ }
+ }, [config]);
+
+ React.useEffect(() => {
+ const canvas = select('.canvas');
+ const bbox = canvas.select('.background');
+ const start = { x: 0, y: 0 };
+ const onStart = function (e) {
+ start.x = e.x;
+ start.y = e.y;
+ };
+ const onDrag = function (e) {
+ const bounds = bbox.node().getBoundingClientRect();
+ const distance = Math.max(Math.abs(e.x - start.x), Math.abs(e.y - start.y));
+ if (distance > 5) {
+ setDragging({
+ icon: this.dataset['icon'],
+ x: (e.x - bounds.x) / bounds.width,
+ y: (e.y - bounds.y) / bounds.height,
+ });
+ } else {
+ setDragging(null);
+ }
+ };
+ const onEnd = function (e) {
+ const bounds = bbox.node().getBoundingClientRect();
+ setDragging(null);
+ const distance = Math.max(Math.abs(e.x - start.x), Math.abs(e.y - start.y));
+ if (distance > 5) {
+ addPin({
+ icon: this.dataset['icon'],
+ x: (e.x - bounds.x) / bounds.width,
+ y: (e.y - bounds.y) / bounds.height,
+ });
+ if (this.classList.contains('map-pin')) {
+ let id = 0;
+ this.classList.forEach(name => {
+ if (name.startsWith('map-pin-')) {
+ id = parseInt(name.substr(8), 10);
+ }
+ });
+ removePin({ id });
+ }
+ }
+ };
+ const selection = canvas.selectAll('.toggle-icon');
+ const draggable = drag()
+ .container(bbox)
+ .clickDistance(5)
+ .on('start', onStart)
+ .on('drag', onDrag)
+ .on('end', onEnd);
+ selection.call(draggable);
+ return () => {
+ selection.on('.drag', null);
+ };
+ }, [pins, removePin]);
+
+ return <svg
+ xmlns="http://www.w3.org/2000/svg"
+ className="canvas"
+ width={layout.width}
+ height={layout.height}
+ viewBox={`0 0 ${layout.width} ${layout.height}`}
+ onContextMenu={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ >
+ <rect
+ className="background"
+ fill="transparent"
+ x="0" y="0"
+ width={layout.width}
+ height={layout.height}
+ />
+ <g className="items" transform={layout.itemsTransform}>
+ <Items />
+ </g>
+ <g className="dungeons" transform={layout.dungeonsTransform}>
+ <Dungeons columns={layout.dungeonColumns} />
+ </g>
+ <g className="tracker-map" transform={layout.mapTransform}>
+ <Map />
+ </g>
+ <g className="pins">
+ {pins.map(pin =>
+ <ToggleIcon
+ key={pin.id}
+ className={`map-pin map-pin-${pin.id}`}
+ controller={ToggleIcon.pinController(pin, removePin)}
+ icons={[pin.icon]}
+ svg
+ transform={
+ `translate(${pin.x * layout.width} ${pin.y * layout.height}) scale(3)`
+ }
+ />
+ )}
+ </g>
+ {dragging ?
+ <g transform={
+ `translate(${dragging.x * layout.width} ${dragging.y * layout.height}) scale(4)`
+ }>
+ <ZeldaIcon name={dragging.icon} svg />
+ </g>
+ : null}
+ </svg>;
+};
+
+export default Canvas;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Col, Form, Modal, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import LargeCheck from '../common/LargeCheck';
-import { getConfigValue } from '../../helpers/tracker';
-import { useTracker } from '../../hooks/tracker';
-
-const ConfigDialog = ({
- onHide,
- show,
-}) => {
- const { config, saveConfig } = useTracker();
- const { t } = useTranslation();
-
- const handleChange = React.useCallback(({ target: { name, value } }) => {
- saveConfig({ [name]: value });
- }, [saveConfig]);
-
- return <Modal className="tracker-config-dialog" onHide={onHide} show={show} size="lg">
- <Modal.Header closeButton>
- <Modal.Title>
- {t('tracker.config.title')}
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <Row>
- <Col sm={6}>
- <h3>{t('tracker.config.logic')}</h3>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.worldState"
- >
- <Form.Label>{t('tracker.config.worldState')}</Form.Label>
- <Form.Select
- className="w-auto"
- name="worldState"
- onChange={handleChange}
- value={getConfigValue(config, 'worldState', 'open')}
- >
- {['open', 'inverted'].map(n =>
- <option key={n} value={n}>
- {t(`tracker.config.worldStates.${n}`)}
- </option>
- )}
- </Form.Select>
- </Form.Group>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.glitches"
- >
- <Form.Label>{t('tracker.config.glitches')}</Form.Label>
- <Form.Select
- className="w-auto"
- name="glitches"
- onChange={handleChange}
- value={getConfigValue(config, 'glitches', 'none')}
- >
- {['none', 'owg', 'hmg', 'mg', 'nl'].map(n =>
- <option key={n} value={n}>
- {t(`tracker.config.glitchRules.${n}`)}
- </option>
- )}
- </Form.Select>
- </Form.Group>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.bossShuffle"
- >
- <Form.Label>{t('tracker.config.bossShuffle')}</Form.Label>
- <Form.Control
- as={LargeCheck}
- name="bossShuffle"
- onChange={handleChange}
- value={!!config.bossShuffle}
- />
- </Form.Group>
- </Col>
- <Col sm={6}>
- <h3>{t('tracker.config.goal')}</h3>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.gtCrystals"
- >
- <Form.Label>{t('tracker.config.gtCrystals')}</Form.Label>
- <Form.Select
- className="w-auto"
- name="gt-crystals"
- onChange={handleChange}
- value={getConfigValue(config, 'gt-crystals', 7)}
- >
- {['?', 0, 1, 2, 3, 4, 5, 6, 7].map(n =>
- <option key={n} value={n}>
- {n}
- </option>
- )}
- </Form.Select>
- </Form.Group>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.ganonCrystals"
- >
- <Form.Label>{t('tracker.config.ganonCrystals')}</Form.Label>
- <Form.Select
- className="w-auto"
- name="ganon-crystals"
- onChange={handleChange}
- value={getConfigValue(config, 'ganon-crystals', 7)}
- >
- {['?', 0, 1, 2, 3, 4, 5, 6, 7].map(n =>
- <option key={n} value={n}>
- {n}
- </option>
- )}
- </Form.Select>
- </Form.Group>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.goal"
- >
- <Form.Label>{t('tracker.config.goal')}</Form.Label>
- <Form.Select
- className="w-auto"
- name="goal"
- onChange={handleChange}
- value={getConfigValue(config, 'goal', 'ganon')}
- >
- {['ganon', 'fast', 'ad', 'ped', 'trinity', 'thunt', 'ghunt'].map(n =>
- <option key={n} value={n}>
- {t(`tracker.config.goals.${n}`)}
- </option>
- )}
- </Form.Select>
- </Form.Group>
- </Col>
- </Row>
- <Row className="mt-3">
- <Col sm={6}>
- <h3>{t('tracker.config.wildItems')}</h3>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.wildMap"
- >
- <Form.Label>{t('tracker.config.wildMap')}</Form.Label>
- <Form.Control
- as={LargeCheck}
- name="wildMap"
- onChange={handleChange}
- value={!!config.wildMap}
- />
- </Form.Group>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.wildCompass"
- >
- <Form.Label>{t('tracker.config.wildCompass')}</Form.Label>
- <Form.Control
- as={LargeCheck}
- name="wildCompass"
- onChange={handleChange}
- value={!!config.wildCompass}
- />
- </Form.Group>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.wildSmall"
- >
- <Form.Label>{t('tracker.config.wildSmall')}</Form.Label>
- <Form.Control
- as={LargeCheck}
- name="wildSmall"
- onChange={handleChange}
- value={!!config.wildSmall}
- />
- </Form.Group>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.wildBig"
- >
- <Form.Label>{t('tracker.config.wildBig')}</Form.Label>
- <Form.Control
- as={LargeCheck}
- name="wildBig"
- onChange={handleChange}
- value={!!config.wildBig}
- />
- </Form.Group>
- </Col>
- <Col sm={6}>
- <h3>{t('tracker.config.showItems')}</h3>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.showMap"
- >
- <Form.Label>{t('tracker.config.showMap')}</Form.Label>
- <Form.Select
- className="w-auto"
- name="showMap"
- onChange={handleChange}
- value={config.showMap || 'always'}
- >
- <option value="never">
- {t('tracker.config.showItemOptions.never')}
- </option>
- <option value="situational">
- {t('tracker.config.showItemOptions.situational')}
- </option>
- <option value="always">
- {t('tracker.config.showItemOptions.always')}
- </option>
- </Form.Select>
- </Form.Group>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.showCompass"
- >
- <Form.Label>{t('tracker.config.showCompass')}</Form.Label>
- <Form.Select
- className="w-auto"
- name="showCompass"
- onChange={handleChange}
- value={config.showCompass || 'always'}
- >
- <option value="never">
- {t('tracker.config.showItemOptions.never')}
- </option>
- <option value="situational">
- {t('tracker.config.showItemOptions.situational')}
- </option>
- <option value="always">
- {t('tracker.config.showItemOptions.always')}
- </option>
- </Form.Select>
- </Form.Group>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.showSmall"
- >
- <Form.Label>{t('tracker.config.showSmall')}</Form.Label>
- <Form.Select
- className="w-auto"
- name="showSmall"
- onChange={handleChange}
- value={config.showSmall || 'always'}
- >
- <option value="never">
- {t('tracker.config.showItemOptions.never')}
- </option>
- <option value="situational">
- {t('tracker.config.showItemOptions.situational')}
- </option>
- <option value="always">
- {t('tracker.config.showItemOptions.always')}
- </option>
- </Form.Select>
- </Form.Group>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.showBig"
- >
- <Form.Label>{t('tracker.config.showBig')}</Form.Label>
- <Form.Select
- className="w-auto"
- name="showBig"
- onChange={handleChange}
- value={config.showBig || 'always'}
- >
- <option value="never">
- {t('tracker.config.showItemOptions.never')}
- </option>
- <option value="situational">
- {t('tracker.config.showItemOptions.situational')}
- </option>
- <option value="always">
- {t('tracker.config.showItemOptions.always')}
- </option>
- </Form.Select>
- </Form.Group>
- </Col>
- </Row>
- <Row>
- <Col sm={6}>
- <h3>{t('tracker.config.layout')}</h3>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.mapLayout"
- >
- <Form.Label>{t('tracker.config.mapLayout')}</Form.Label>
- <Form.Select
- className="w-auto"
- name="mapLayout"
- onChange={handleChange}
- value={getConfigValue(config, 'mapLayout', 'horizontal')}
- >
- {['horizontal', 'vertical'].map(n =>
- <option key={n} value={n}>
- {t(`tracker.config.mapLayouts.${n}`)}
- </option>
- )}
- </Form.Select>
- </Form.Group>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.compactKeysanity"
- >
- <Form.Label>{t('tracker.config.compactKeysanity')}</Form.Label>
- <Form.Control
- as={LargeCheck}
- name="compactKeysanity"
- onChange={handleChange}
- value={!!config.compactKeysanity}
- />
- </Form.Group>
- </Col>
- <Col sm={6}>
- <h3>{t('tracker.config.calculation')}</h3>
- <Form.Group
- className="d-flex justify-content-between my-2"
- controlId="tracker.checkCalculation"
- >
- <Form.Label>{t('tracker.config.checkCalculation')}</Form.Label>
- <Form.Select
- className="w-auto"
- name="checkCalculation"
- onChange={handleChange}
- value={getConfigValue(config, 'checkCalculation', 'room-data')}
- >
- {['inventory', 'room-data'].map(n =>
- <option key={n} value={n}>
- {t(`tracker.config.checkCalculations.${n}`)}
- </option>
- )}
- </Form.Select>
- </Form.Group>
- </Col>
- </Row>
- </Modal.Body>
- </Modal>;
-};
-
-ConfigDialog.propTypes = {
- onHide: PropTypes.func,
- show: PropTypes.bool,
-};
-
-export default ConfigDialog;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Col, Form, Modal, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import LargeCheck from '../common/LargeCheck';
+import { getConfigValue } from '../../helpers/tracker';
+import { useTracker } from '../../hooks/tracker';
+
+const ConfigDialog = ({
+ onHide,
+ show,
+}) => {
+ const { config, saveConfig } = useTracker();
+ const { t } = useTranslation();
+
+ const handleChange = React.useCallback(({ target: { name, value } }) => {
+ saveConfig({ [name]: value });
+ }, [saveConfig]);
+
+ return <Modal className="tracker-config-dialog" onHide={onHide} show={show} size="lg">
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {t('tracker.config.title')}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={6}>
+ <h3>{t('tracker.config.logic')}</h3>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.worldState"
+ >
+ <Form.Label>{t('tracker.config.worldState')}</Form.Label>
+ <Form.Select
+ className="w-auto"
+ name="worldState"
+ onChange={handleChange}
+ value={getConfigValue(config, 'worldState', 'open')}
+ >
+ {['open', 'inverted'].map(n =>
+ <option key={n} value={n}>
+ {t(`tracker.config.worldStates.${n}`)}
+ </option>
+ )}
+ </Form.Select>
+ </Form.Group>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.glitches"
+ >
+ <Form.Label>{t('tracker.config.glitches')}</Form.Label>
+ <Form.Select
+ className="w-auto"
+ name="glitches"
+ onChange={handleChange}
+ value={getConfigValue(config, 'glitches', 'none')}
+ >
+ {['none', 'owg', 'hmg', 'mg', 'nl'].map(n =>
+ <option key={n} value={n}>
+ {t(`tracker.config.glitchRules.${n}`)}
+ </option>
+ )}
+ </Form.Select>
+ </Form.Group>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.bossShuffle"
+ >
+ <Form.Label>{t('tracker.config.bossShuffle')}</Form.Label>
+ <Form.Control
+ as={LargeCheck}
+ name="bossShuffle"
+ onChange={handleChange}
+ value={!!config.bossShuffle}
+ />
+ </Form.Group>
+ </Col>
+ <Col sm={6}>
+ <h3>{t('tracker.config.goal')}</h3>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.gtCrystals"
+ >
+ <Form.Label>{t('tracker.config.gtCrystals')}</Form.Label>
+ <Form.Select
+ className="w-auto"
+ name="gt-crystals"
+ onChange={handleChange}
+ value={getConfigValue(config, 'gt-crystals', 7)}
+ >
+ {['?', 0, 1, 2, 3, 4, 5, 6, 7].map(n =>
+ <option key={n} value={n}>
+ {n}
+ </option>
+ )}
+ </Form.Select>
+ </Form.Group>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.ganonCrystals"
+ >
+ <Form.Label>{t('tracker.config.ganonCrystals')}</Form.Label>
+ <Form.Select
+ className="w-auto"
+ name="ganon-crystals"
+ onChange={handleChange}
+ value={getConfigValue(config, 'ganon-crystals', 7)}
+ >
+ {['?', 0, 1, 2, 3, 4, 5, 6, 7].map(n =>
+ <option key={n} value={n}>
+ {n}
+ </option>
+ )}
+ </Form.Select>
+ </Form.Group>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.goal"
+ >
+ <Form.Label>{t('tracker.config.goal')}</Form.Label>
+ <Form.Select
+ className="w-auto"
+ name="goal"
+ onChange={handleChange}
+ value={getConfigValue(config, 'goal', 'ganon')}
+ >
+ {['ganon', 'fast', 'ad', 'ped', 'trinity', 'thunt', 'ghunt'].map(n =>
+ <option key={n} value={n}>
+ {t(`tracker.config.goals.${n}`)}
+ </option>
+ )}
+ </Form.Select>
+ </Form.Group>
+ </Col>
+ </Row>
+ <Row className="mt-3">
+ <Col sm={6}>
+ <h3>{t('tracker.config.wildItems')}</h3>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.wildMap"
+ >
+ <Form.Label>{t('tracker.config.wildMap')}</Form.Label>
+ <Form.Control
+ as={LargeCheck}
+ name="wildMap"
+ onChange={handleChange}
+ value={!!config.wildMap}
+ />
+ </Form.Group>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.wildCompass"
+ >
+ <Form.Label>{t('tracker.config.wildCompass')}</Form.Label>
+ <Form.Control
+ as={LargeCheck}
+ name="wildCompass"
+ onChange={handleChange}
+ value={!!config.wildCompass}
+ />
+ </Form.Group>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.wildSmall"
+ >
+ <Form.Label>{t('tracker.config.wildSmall')}</Form.Label>
+ <Form.Control
+ as={LargeCheck}
+ name="wildSmall"
+ onChange={handleChange}
+ value={!!config.wildSmall}
+ />
+ </Form.Group>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.wildBig"
+ >
+ <Form.Label>{t('tracker.config.wildBig')}</Form.Label>
+ <Form.Control
+ as={LargeCheck}
+ name="wildBig"
+ onChange={handleChange}
+ value={!!config.wildBig}
+ />
+ </Form.Group>
+ </Col>
+ <Col sm={6}>
+ <h3>{t('tracker.config.showItems')}</h3>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.showMap"
+ >
+ <Form.Label>{t('tracker.config.showMap')}</Form.Label>
+ <Form.Select
+ className="w-auto"
+ name="showMap"
+ onChange={handleChange}
+ value={config.showMap || 'always'}
+ >
+ <option value="never">
+ {t('tracker.config.showItemOptions.never')}
+ </option>
+ <option value="situational">
+ {t('tracker.config.showItemOptions.situational')}
+ </option>
+ <option value="always">
+ {t('tracker.config.showItemOptions.always')}
+ </option>
+ </Form.Select>
+ </Form.Group>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.showCompass"
+ >
+ <Form.Label>{t('tracker.config.showCompass')}</Form.Label>
+ <Form.Select
+ className="w-auto"
+ name="showCompass"
+ onChange={handleChange}
+ value={config.showCompass || 'always'}
+ >
+ <option value="never">
+ {t('tracker.config.showItemOptions.never')}
+ </option>
+ <option value="situational">
+ {t('tracker.config.showItemOptions.situational')}
+ </option>
+ <option value="always">
+ {t('tracker.config.showItemOptions.always')}
+ </option>
+ </Form.Select>
+ </Form.Group>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.showSmall"
+ >
+ <Form.Label>{t('tracker.config.showSmall')}</Form.Label>
+ <Form.Select
+ className="w-auto"
+ name="showSmall"
+ onChange={handleChange}
+ value={config.showSmall || 'always'}
+ >
+ <option value="never">
+ {t('tracker.config.showItemOptions.never')}
+ </option>
+ <option value="situational">
+ {t('tracker.config.showItemOptions.situational')}
+ </option>
+ <option value="always">
+ {t('tracker.config.showItemOptions.always')}
+ </option>
+ </Form.Select>
+ </Form.Group>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.showBig"
+ >
+ <Form.Label>{t('tracker.config.showBig')}</Form.Label>
+ <Form.Select
+ className="w-auto"
+ name="showBig"
+ onChange={handleChange}
+ value={config.showBig || 'always'}
+ >
+ <option value="never">
+ {t('tracker.config.showItemOptions.never')}
+ </option>
+ <option value="situational">
+ {t('tracker.config.showItemOptions.situational')}
+ </option>
+ <option value="always">
+ {t('tracker.config.showItemOptions.always')}
+ </option>
+ </Form.Select>
+ </Form.Group>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={6}>
+ <h3>{t('tracker.config.layout')}</h3>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.mapLayout"
+ >
+ <Form.Label>{t('tracker.config.mapLayout')}</Form.Label>
+ <Form.Select
+ className="w-auto"
+ name="mapLayout"
+ onChange={handleChange}
+ value={getConfigValue(config, 'mapLayout', 'horizontal')}
+ >
+ {['horizontal', 'vertical'].map(n =>
+ <option key={n} value={n}>
+ {t(`tracker.config.mapLayouts.${n}`)}
+ </option>
+ )}
+ </Form.Select>
+ </Form.Group>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.compactKeysanity"
+ >
+ <Form.Label>{t('tracker.config.compactKeysanity')}</Form.Label>
+ <Form.Control
+ as={LargeCheck}
+ name="compactKeysanity"
+ onChange={handleChange}
+ value={!!config.compactKeysanity}
+ />
+ </Form.Group>
+ </Col>
+ <Col sm={6}>
+ <h3>{t('tracker.config.calculation')}</h3>
+ <Form.Group
+ className="d-flex justify-content-between my-2"
+ controlId="tracker.checkCalculation"
+ >
+ <Form.Label>{t('tracker.config.checkCalculation')}</Form.Label>
+ <Form.Select
+ className="w-auto"
+ name="checkCalculation"
+ onChange={handleChange}
+ value={getConfigValue(config, 'checkCalculation', 'room-data')}
+ >
+ {['inventory', 'room-data'].map(n =>
+ <option key={n} value={n}>
+ {t(`tracker.config.checkCalculations.${n}`)}
+ </option>
+ )}
+ </Form.Select>
+ </Form.Group>
+ </Col>
+ </Row>
+ </Modal.Body>
+ </Modal>;
+};
+
+ConfigDialog.propTypes = {
+ onHide: PropTypes.func,
+ show: PropTypes.bool,
+};
+
+export default ConfigDialog;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-const CountDisplay = ({ className, count, full }) => {
- const classNames = ['count-display'];
- if (className) {
- classNames.push(className);
- }
- if (!count) {
- classNames.push('is-zero');
- }
- if (full && count >= full) {
- classNames.push('is-full');
- }
- return <text className={classNames.join(' ')}>
- {count}
- </text>;
-};
-
-CountDisplay.propTypes = {
- className: PropTypes.string,
- count: PropTypes.number,
- full: PropTypes.number,
-};
-
-export default CountDisplay;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+const CountDisplay = ({ className, count, full }) => {
+ const classNames = ['count-display'];
+ if (className) {
+ classNames.push(className);
+ }
+ if (!count) {
+ classNames.push('is-zero');
+ }
+ if (full && count >= full) {
+ classNames.push('is-full');
+ }
+ return <text className={classNames.join(' ')}>
+ {count}
+ </text>;
+};
+
+CountDisplay.propTypes = {
+ className: PropTypes.string,
+ count: PropTypes.number,
+ full: PropTypes.number,
+};
+
+export default CountDisplay;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import CountDisplay from './CountDisplay';
-import ToggleIcon from './ToggleIcon';
-import {
- BOSSES,
- getDungeonAcquiredSKs,
- getDungeonRemainingItems,
- shouldCompactKeysanity,
- shouldShowDungeonItem,
-} from '../../helpers/tracker';
-import { useTracker } from '../../hooks/tracker';
-
-const Dungeons = ({ columns = 4 }) => {
- const { config, dungeons, state } = useTracker();
-
- const layout = React.useMemo(() => {
- const compact = shouldCompactKeysanity(config);
- const mapX = 1;
- const compassX = shouldShowDungeonItem(config, 'Map') ? mapX + 1 : mapX;
- const smallX = shouldShowDungeonItem(config, 'Compass') ? compassX + 1 : compassX;
- const bigX = shouldShowDungeonItem(config, 'Small') ? smallX + 1 : smallX;
- const countX = compact ? 2 : shouldShowDungeonItem(config, 'Big') ? bigX + 1 : bigX;
- const bossX = countX + 1;
- const prizeX = bossX + 1;
- const dungeonWidth = Math.max(5, prizeX + 1);
- const width = (columns * dungeonWidth) + Math.max(0, columns - 1);
- const height = 4;
-
- const transform = (col, row) =>
- `scale(${1 / width}) translate(${
- (col * dungeonWidth) + 0.5 + (col ? col - (columns > 3 ? 1 : 0) : 0)
- } ${row + 0.5})`;
-
- const transforms = {
- tag: null,
- map: compact
- ? 'translate(0.75 -0.25) scale(0.45)' : `translate(${mapX} 0) scale(0.9)`,
- compass: compact
- ? 'translate(1.25 -0.25) scale(0.45)' : `translate(${compassX} 0) scale(0.9)`,
- small: compact
- ? 'translate(0.75 0.25) scale(0.45)' : `translate(${smallX} 0) scale(0.9)`,
- big: compact
- ? 'translate(1.25 0.25) scale(0.45)' : `translate(${bigX} 0) scale(0.9)`,
- checks: `translate(${countX} 0) scale(0.9)`,
- boss: `translate(${bossX} 0)`,
- prize: `translate(${prizeX} 0)`,
- hc: transform(0, 0),
- ct: transform(0, 1),
- gt: transform(0, 2),
- gtBoss1: `translate(${bossX - 2} 1)`,
- gtBoss2: `translate(${bossX - 1} 1)`,
- gtBoss3: `translate(${bossX} 1)`,
- };
-
- if (columns === 1) {
- transforms.ep = transform(0, 4);
- transforms.dp = transform(0, 5);
- transforms.th = transform(0, 6);
- transforms.pd = transform(0, 8);
- transforms.sp = transform(0, 9);
- transforms.sw = transform(0, 10);
- transforms.tt = transform(0, 11);
- transforms.ip = transform(0, 12);
- transforms.mm = transform(0, 13);
- transforms.tr = transform(0, 14);
- } else if (columns === 2) {
- transforms.ep = transform(0, 4);
- transforms.dp = transform(0, 5);
- transforms.th = transform(0, 6);
- transforms.pd = transform(1, 0);
- transforms.sp = transform(1, 1);
- transforms.sw = transform(1, 2);
- transforms.tt = transform(1, 3);
- transforms.ip = transform(1, 4);
- transforms.mm = transform(1, 5);
- transforms.tr = transform(1, 6);
- } else {
- transforms.ep = transform(1, 0);
- transforms.dp = transform(1, 1);
- transforms.th = transform(1, 2);
- transforms.pd = transform(2, 0);
- transforms.sp = transform(2, 1);
- transforms.sw = transform(2, 2);
- transforms.tt = transform(2, 3);
- transforms.ip = transform(3, 0);
- transforms.mm = transform(3, 1);
- transforms.tr = transform(3, 2);
- }
-
- return {
- width,
- height,
- transforms,
- };
- }, [config, dungeons]);
-
- return <>
- {dungeons.map(dungeon =>
- <g
- className={`dungeon dungeon-${dungeon.id}`}
- key={dungeon.id}
- transform={layout.transforms[dungeon.id]}
- >
- <g transform={layout.transforms.tag}>
- <text className="dungeon-tag">{dungeon.id.toUpperCase()}</text>
- </g>
- {shouldShowDungeonItem(config, 'Map') ?
- <ToggleIcon
- controller={ToggleIcon.dungeonController(dungeon)}
- icons={['map']}
- svg
- transform={layout.transforms.map}
- />
- : null}
- {shouldShowDungeonItem(config, 'Compass') ?
- <ToggleIcon
- controller={ToggleIcon.dungeonController(dungeon)}
- icons={['compass']}
- svg
- transform={layout.transforms.compass}
- />
- : null}
- {shouldShowDungeonItem(config, 'Small') ?
- <g transform={layout.transforms.small}>
- <ToggleIcon
- controller={ToggleIcon.dungeonCountController(dungeon, dungeon.sk)}
- icons={['small-key']}
- svg
- />
- <CountDisplay
- count={getDungeonAcquiredSKs(state, dungeon)}
- full={dungeon.sk}
- />
- </g>
- : null}
- {shouldShowDungeonItem(config, 'Big') ?
- <ToggleIcon
- controller={ToggleIcon.dungeonController(dungeon)}
- icons={['big-key']}
- svg
- transform={layout.transforms.big}
- />
- : null}
- <g transform={layout.transforms.checks}>
- <ToggleIcon
- controller={ToggleIcon.dungeonCheckController(dungeon)}
- icons={['open-chest', 'chest']}
- svg
- />
- <CountDisplay count={getDungeonRemainingItems(state, dungeon)} />
- </g>
- {dungeon.boss ?
- <ToggleIcon
- controller={ToggleIcon.dungeonBossController(dungeon)}
- icons={dungeon.bosses}
- svg
- transform={layout.transforms.boss}
- />
- : null}
- {dungeon.prize ?
- <ToggleIcon
- controller={ToggleIcon.dungeonPrizeController(dungeon)}
- icons={[
- 'crystal',
- 'red-crystal',
- 'green-pendant',
- 'red-pendant',
- ]}
- svg
- transform={layout.transforms.prize}
- />
- : null}
- {dungeon.id === 'gt' && config.bossShuffle ? <>
- <ToggleIcon
- controller={ToggleIcon.gtBossController('bot')}
- icons={BOSSES}
- svg
- transform={layout.transforms.gtBoss1}
- />
- <ToggleIcon
- controller={ToggleIcon.gtBossController('mid')}
- icons={BOSSES}
- svg
- transform={layout.transforms.gtBoss2}
- />
- <ToggleIcon
- controller={ToggleIcon.gtBossController('top')}
- icons={BOSSES}
- svg
- transform={layout.transforms.gtBoss3}
- />
- </> : null}
- </g>
- )}
- </>;
-};
-
-Dungeons.propTypes = {
- columns: PropTypes.number,
-};
-
-export default Dungeons;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import CountDisplay from './CountDisplay';
+import ToggleIcon from './ToggleIcon';
+import {
+ BOSSES,
+ getDungeonAcquiredSKs,
+ getDungeonRemainingItems,
+ shouldCompactKeysanity,
+ shouldShowDungeonItem,
+} from '../../helpers/tracker';
+import { useTracker } from '../../hooks/tracker';
+
+const Dungeons = ({ columns = 4 }) => {
+ const { config, dungeons, state } = useTracker();
+
+ const layout = React.useMemo(() => {
+ const compact = shouldCompactKeysanity(config);
+ const mapX = 1;
+ const compassX = shouldShowDungeonItem(config, 'Map') ? mapX + 1 : mapX;
+ const smallX = shouldShowDungeonItem(config, 'Compass') ? compassX + 1 : compassX;
+ const bigX = shouldShowDungeonItem(config, 'Small') ? smallX + 1 : smallX;
+ const countX = compact ? 2 : shouldShowDungeonItem(config, 'Big') ? bigX + 1 : bigX;
+ const bossX = countX + 1;
+ const prizeX = bossX + 1;
+ const dungeonWidth = Math.max(5, prizeX + 1);
+ const width = (columns * dungeonWidth) + Math.max(0, columns - 1);
+ const height = 4;
+
+ const transform = (col, row) =>
+ `scale(${1 / width}) translate(${
+ (col * dungeonWidth) + 0.5 + (col ? col - (columns > 3 ? 1 : 0) : 0)
+ } ${row + 0.5})`;
+
+ const transforms = {
+ tag: null,
+ map: compact
+ ? 'translate(0.75 -0.25) scale(0.45)' : `translate(${mapX} 0) scale(0.9)`,
+ compass: compact
+ ? 'translate(1.25 -0.25) scale(0.45)' : `translate(${compassX} 0) scale(0.9)`,
+ small: compact
+ ? 'translate(0.75 0.25) scale(0.45)' : `translate(${smallX} 0) scale(0.9)`,
+ big: compact
+ ? 'translate(1.25 0.25) scale(0.45)' : `translate(${bigX} 0) scale(0.9)`,
+ checks: `translate(${countX} 0) scale(0.9)`,
+ boss: `translate(${bossX} 0)`,
+ prize: `translate(${prizeX} 0)`,
+ hc: transform(0, 0),
+ ct: transform(0, 1),
+ gt: transform(0, 2),
+ gtBoss1: `translate(${bossX - 2} 1)`,
+ gtBoss2: `translate(${bossX - 1} 1)`,
+ gtBoss3: `translate(${bossX} 1)`,
+ };
+
+ if (columns === 1) {
+ transforms.ep = transform(0, 4);
+ transforms.dp = transform(0, 5);
+ transforms.th = transform(0, 6);
+ transforms.pd = transform(0, 8);
+ transforms.sp = transform(0, 9);
+ transforms.sw = transform(0, 10);
+ transforms.tt = transform(0, 11);
+ transforms.ip = transform(0, 12);
+ transforms.mm = transform(0, 13);
+ transforms.tr = transform(0, 14);
+ } else if (columns === 2) {
+ transforms.ep = transform(0, 4);
+ transforms.dp = transform(0, 5);
+ transforms.th = transform(0, 6);
+ transforms.pd = transform(1, 0);
+ transforms.sp = transform(1, 1);
+ transforms.sw = transform(1, 2);
+ transforms.tt = transform(1, 3);
+ transforms.ip = transform(1, 4);
+ transforms.mm = transform(1, 5);
+ transforms.tr = transform(1, 6);
+ } else {
+ transforms.ep = transform(1, 0);
+ transforms.dp = transform(1, 1);
+ transforms.th = transform(1, 2);
+ transforms.pd = transform(2, 0);
+ transforms.sp = transform(2, 1);
+ transforms.sw = transform(2, 2);
+ transforms.tt = transform(2, 3);
+ transforms.ip = transform(3, 0);
+ transforms.mm = transform(3, 1);
+ transforms.tr = transform(3, 2);
+ }
+
+ return {
+ width,
+ height,
+ transforms,
+ };
+ }, [config, dungeons]);
+
+ return <>
+ {dungeons.map(dungeon =>
+ <g
+ className={`dungeon dungeon-${dungeon.id}`}
+ key={dungeon.id}
+ transform={layout.transforms[dungeon.id]}
+ >
+ <g transform={layout.transforms.tag}>
+ <text className="dungeon-tag">{dungeon.id.toUpperCase()}</text>
+ </g>
+ {shouldShowDungeonItem(config, 'Map') ?
+ <ToggleIcon
+ controller={ToggleIcon.dungeonController(dungeon)}
+ icons={['map']}
+ svg
+ transform={layout.transforms.map}
+ />
+ : null}
+ {shouldShowDungeonItem(config, 'Compass') ?
+ <ToggleIcon
+ controller={ToggleIcon.dungeonController(dungeon)}
+ icons={['compass']}
+ svg
+ transform={layout.transforms.compass}
+ />
+ : null}
+ {shouldShowDungeonItem(config, 'Small') ?
+ <g transform={layout.transforms.small}>
+ <ToggleIcon
+ controller={ToggleIcon.dungeonCountController(dungeon, dungeon.sk)}
+ icons={['small-key']}
+ svg
+ />
+ <CountDisplay
+ count={getDungeonAcquiredSKs(state, dungeon)}
+ full={dungeon.sk}
+ />
+ </g>
+ : null}
+ {shouldShowDungeonItem(config, 'Big') ?
+ <ToggleIcon
+ controller={ToggleIcon.dungeonController(dungeon)}
+ icons={['big-key']}
+ svg
+ transform={layout.transforms.big}
+ />
+ : null}
+ <g transform={layout.transforms.checks}>
+ <ToggleIcon
+ controller={ToggleIcon.dungeonCheckController(dungeon)}
+ icons={['open-chest', 'chest']}
+ svg
+ />
+ <CountDisplay count={getDungeonRemainingItems(state, dungeon)} />
+ </g>
+ {dungeon.boss ?
+ <ToggleIcon
+ controller={ToggleIcon.dungeonBossController(dungeon)}
+ icons={dungeon.bosses}
+ svg
+ transform={layout.transforms.boss}
+ />
+ : null}
+ {dungeon.prize ?
+ <ToggleIcon
+ controller={ToggleIcon.dungeonPrizeController(dungeon)}
+ icons={[
+ 'crystal',
+ 'red-crystal',
+ 'green-pendant',
+ 'red-pendant',
+ ]}
+ svg
+ transform={layout.transforms.prize}
+ />
+ : null}
+ {dungeon.id === 'gt' && config.bossShuffle ? <>
+ <ToggleIcon
+ controller={ToggleIcon.gtBossController('bot')}
+ icons={BOSSES}
+ svg
+ transform={layout.transforms.gtBoss1}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.gtBossController('mid')}
+ icons={BOSSES}
+ svg
+ transform={layout.transforms.gtBoss2}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.gtBossController('top')}
+ icons={BOSSES}
+ svg
+ transform={layout.transforms.gtBoss3}
+ />
+ </> : null}
+ </g>
+ )}
+ </>;
+};
+
+Dungeons.propTypes = {
+ columns: PropTypes.number,
+};
+
+export default Dungeons;
+++ /dev/null
-import React from 'react';
-
-import ToggleIcon from './ToggleIcon';
-
-const Equipment = () => {
- return <div className="equipment">
- <div className="item">
- <ToggleIcon controller={ToggleIcon.simpleController} icons={['boots']} />
- </div>
- <div className="item">
- <ToggleIcon
- controller={ToggleIcon.progressiveController('lift', 0, 2)}
- icons={['glove', 'mitts']}
- />
- </div>
- <div className="item">
- <ToggleIcon controller={ToggleIcon.simpleController} icons={['flippers']} />
- </div>
- <div className="item">
- <ToggleIcon controller={ToggleIcon.simpleController} icons={['moonpearl']} />
- </div>
- <div className="item">
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['half-magic', 'quarter-magic']}
- />
- </div>
- <div className="item">
- <ToggleIcon
- controller={ToggleIcon.progressiveController('sword', 0, 4)}
- icons={['sword-1', 'sword-2', 'sword-3', 'sword-4']}
- />
- </div>
- <div className="item">
- <ToggleIcon
- controller={ToggleIcon.progressiveController('shield', 0, 3)}
- icons={['fighter-shield', 'fire-shield', 'mirror-shield']}
- />
- </div>
- <div className="item">
- <ToggleIcon
- controller={ToggleIcon.progressiveController('mail', 1, 3)}
- icons={['green-mail', 'blue-mail', 'red-mail']}
- />
- </div>
- <div className="item">
- <ToggleIcon
- controller={ToggleIcon.modulusController('heart-piece')}
- icons={['heart-0', 'heart-1', 'heart-2', 'heart-3']}
- />
- </div>
- </div>;
-};
-
-export default Equipment;
--- /dev/null
+import React from 'react';
+
+import ToggleIcon from './ToggleIcon';
+
+const Equipment = () => {
+ return <div className="equipment">
+ <div className="item">
+ <ToggleIcon controller={ToggleIcon.simpleController} icons={['boots']} />
+ </div>
+ <div className="item">
+ <ToggleIcon
+ controller={ToggleIcon.progressiveController('lift', 0, 2)}
+ icons={['glove', 'mitts']}
+ />
+ </div>
+ <div className="item">
+ <ToggleIcon controller={ToggleIcon.simpleController} icons={['flippers']} />
+ </div>
+ <div className="item">
+ <ToggleIcon controller={ToggleIcon.simpleController} icons={['moonpearl']} />
+ </div>
+ <div className="item">
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['half-magic', 'quarter-magic']}
+ />
+ </div>
+ <div className="item">
+ <ToggleIcon
+ controller={ToggleIcon.progressiveController('sword', 0, 4)}
+ icons={['sword-1', 'sword-2', 'sword-3', 'sword-4']}
+ />
+ </div>
+ <div className="item">
+ <ToggleIcon
+ controller={ToggleIcon.progressiveController('shield', 0, 3)}
+ icons={['fighter-shield', 'fire-shield', 'mirror-shield']}
+ />
+ </div>
+ <div className="item">
+ <ToggleIcon
+ controller={ToggleIcon.progressiveController('mail', 1, 3)}
+ icons={['green-mail', 'blue-mail', 'red-mail']}
+ />
+ </div>
+ <div className="item">
+ <ToggleIcon
+ controller={ToggleIcon.modulusController('heart-piece')}
+ icons={['heart-0', 'heart-1', 'heart-2', 'heart-3']}
+ />
+ </div>
+ </div>;
+};
+
+export default Equipment;
+++ /dev/null
-import React from 'react';
-
-import ToggleIcon from './ToggleIcon';
-import { BOTTLE_CONTENTS } from '../../helpers/tracker';
-import { useTracker } from '../../hooks/tracker';
-
-const transform = (x, y, s) => `translate(${x * 0.2} ${y * 0.2}) scale(${(s || 0.85) * 0.2})`;
-
-const Items = () => {
- const { state } = useTracker();
-
- return <>
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['bow', 'silvers']}
- svg
- transform={transform(0.5, 0.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['blue-boomerang']}
- svg
- transform={transform(1.35, 0.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['red-boomerang']}
- svg
- transform={transform(1.85, 0.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['hookshot']}
- svg
- transform={transform(2.5, 0.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['bomb']}
- svg
- transform={transform(3.5, 0.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['powder']}
- svg
- transform={transform(4.5, 0.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['fire-rod']}
- svg
- transform={transform(0.5, 1.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['ice-rod']}
- svg
- transform={transform(1.5, 1.5)}
- />
- <g transform={transform(2.5, 1.5)}>
- <ToggleIcon controller={ToggleIcon.medallionController} icons={['bombos']} svg />
- {state['mm-medallion'] === 'bombos' ?
- <text className="med-display bottom-left">MM</text>
- : null}
- {state['tr-medallion'] === 'bombos' ?
- <text className="med-display bottom-right">TR</text>
- : null}
- </g>
- <g transform={transform(3.5, 1.5)}>
- <ToggleIcon controller={ToggleIcon.medallionController} icons={['ether']} svg />
- {state['mm-medallion'] === 'ether' ?
- <text className="med-display bottom-left">MM</text>
- : null}
- {state['tr-medallion'] === 'ether' ?
- <text className="med-display bottom-right">TR</text>
- : null}
- </g>
- <g transform={transform(4.5, 1.5)}>
- <ToggleIcon controller={ToggleIcon.medallionController} icons={['quake']} svg />
- {state['mm-medallion'] === 'quake' ?
- <text className="med-display bottom-left">MM</text>
- : null}
- {state['tr-medallion'] === 'quake' ?
- <text className="med-display bottom-right">TR</text>
- : null}
- </g>
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['lamp']}
- svg
- transform={transform(0.5, 2.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['hammer']}
- svg
- transform={transform(1.5, 2.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['flute']}
- svg
- transform={transform(2.5, 2.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['duck']}
- svg
- transform={transform(2.75, 2.75, 0.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['bugnet']}
- svg
- transform={transform(3.5, 2.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['book']}
- svg
- transform={transform(4.5, 2.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['shovel']}
- svg
- transform={transform(0.5, 3.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['somaria']}
- svg
- transform={transform(1.5, 3.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['byrna']}
- svg
- transform={transform(2.5, 3.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['cape']}
- svg
- transform={transform(3.5, 3.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['mirror']}
- svg
- transform={transform(4.5, 3.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.bottleController('bottle-1')}
- icons={BOTTLE_CONTENTS}
- svg
- transform={transform(0.5, 5)}
- />
- <ToggleIcon
- controller={ToggleIcon.bottleController('bottle-2')}
- icons={BOTTLE_CONTENTS}
- svg
- transform={transform(1.5, 5)}
- />
- <ToggleIcon
- controller={ToggleIcon.bottleController('bottle-3')}
- icons={BOTTLE_CONTENTS}
- svg
- transform={transform(2.5, 5)}
- />
- <ToggleIcon
- controller={ToggleIcon.bottleController('bottle-4')}
- icons={BOTTLE_CONTENTS}
- svg
- transform={transform(3.5, 5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['mushroom']}
- svg
- transform={transform(4.5, 5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['boots']}
- svg
- transform={transform(0.5, 6.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.progressiveController('lift', 0, 2)}
- icons={['glove', 'mitts']}
- svg
- transform={transform(1.5, 6.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['flippers']}
- svg
- transform={transform(2.5, 6.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['moonpearl']}
- svg
- transform={transform(3.5, 6.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.simpleController}
- icons={['half-magic', 'quarter-magic']}
- svg
- transform={transform(4.5, 6.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.progressiveController('sword', 0, 4)}
- icons={['sword-1', 'sword-2', 'sword-3', 'sword-4']}
- svg
- transform={transform(0.5, 7.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.progressiveController('shield', 0, 3)}
- icons={['fighter-shield', 'fire-shield', 'mirror-shield']}
- svg
- transform={transform(1.5, 7.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.progressiveController('mail', 1, 3)}
- icons={['green-mail', 'blue-mail', 'red-mail']}
- svg
- transform={transform(2.5, 7.5)}
- />
- <ToggleIcon
- controller={ToggleIcon.modulusController('heart-piece')}
- icons={['heart-0', 'heart-1', 'heart-2', 'heart-3']}
- svg
- transform={transform(3.5, 7.5)}
- />
- </>;
-};
-
-export default Items;
--- /dev/null
+import React from 'react';
+
+import ToggleIcon from './ToggleIcon';
+import { BOTTLE_CONTENTS } from '../../helpers/tracker';
+import { useTracker } from '../../hooks/tracker';
+
+const transform = (x, y, s) => `translate(${x * 0.2} ${y * 0.2}) scale(${(s || 0.85) * 0.2})`;
+
+const Items = () => {
+ const { state } = useTracker();
+
+ return <>
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['bow', 'silvers']}
+ svg
+ transform={transform(0.5, 0.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['blue-boomerang']}
+ svg
+ transform={transform(1.35, 0.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['red-boomerang']}
+ svg
+ transform={transform(1.85, 0.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['hookshot']}
+ svg
+ transform={transform(2.5, 0.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['bomb']}
+ svg
+ transform={transform(3.5, 0.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['powder']}
+ svg
+ transform={transform(4.5, 0.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['fire-rod']}
+ svg
+ transform={transform(0.5, 1.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['ice-rod']}
+ svg
+ transform={transform(1.5, 1.5)}
+ />
+ <g transform={transform(2.5, 1.5)}>
+ <ToggleIcon controller={ToggleIcon.medallionController} icons={['bombos']} svg />
+ {state['mm-medallion'] === 'bombos' ?
+ <text className="med-display bottom-left">MM</text>
+ : null}
+ {state['tr-medallion'] === 'bombos' ?
+ <text className="med-display bottom-right">TR</text>
+ : null}
+ </g>
+ <g transform={transform(3.5, 1.5)}>
+ <ToggleIcon controller={ToggleIcon.medallionController} icons={['ether']} svg />
+ {state['mm-medallion'] === 'ether' ?
+ <text className="med-display bottom-left">MM</text>
+ : null}
+ {state['tr-medallion'] === 'ether' ?
+ <text className="med-display bottom-right">TR</text>
+ : null}
+ </g>
+ <g transform={transform(4.5, 1.5)}>
+ <ToggleIcon controller={ToggleIcon.medallionController} icons={['quake']} svg />
+ {state['mm-medallion'] === 'quake' ?
+ <text className="med-display bottom-left">MM</text>
+ : null}
+ {state['tr-medallion'] === 'quake' ?
+ <text className="med-display bottom-right">TR</text>
+ : null}
+ </g>
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['lamp']}
+ svg
+ transform={transform(0.5, 2.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['hammer']}
+ svg
+ transform={transform(1.5, 2.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['flute']}
+ svg
+ transform={transform(2.5, 2.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['duck']}
+ svg
+ transform={transform(2.75, 2.75, 0.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['bugnet']}
+ svg
+ transform={transform(3.5, 2.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['book']}
+ svg
+ transform={transform(4.5, 2.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['shovel']}
+ svg
+ transform={transform(0.5, 3.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['somaria']}
+ svg
+ transform={transform(1.5, 3.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['byrna']}
+ svg
+ transform={transform(2.5, 3.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['cape']}
+ svg
+ transform={transform(3.5, 3.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['mirror']}
+ svg
+ transform={transform(4.5, 3.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.bottleController('bottle-1')}
+ icons={BOTTLE_CONTENTS}
+ svg
+ transform={transform(0.5, 5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.bottleController('bottle-2')}
+ icons={BOTTLE_CONTENTS}
+ svg
+ transform={transform(1.5, 5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.bottleController('bottle-3')}
+ icons={BOTTLE_CONTENTS}
+ svg
+ transform={transform(2.5, 5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.bottleController('bottle-4')}
+ icons={BOTTLE_CONTENTS}
+ svg
+ transform={transform(3.5, 5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['mushroom']}
+ svg
+ transform={transform(4.5, 5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['boots']}
+ svg
+ transform={transform(0.5, 6.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.progressiveController('lift', 0, 2)}
+ icons={['glove', 'mitts']}
+ svg
+ transform={transform(1.5, 6.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['flippers']}
+ svg
+ transform={transform(2.5, 6.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['moonpearl']}
+ svg
+ transform={transform(3.5, 6.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.simpleController}
+ icons={['half-magic', 'quarter-magic']}
+ svg
+ transform={transform(4.5, 6.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.progressiveController('sword', 0, 4)}
+ icons={['sword-1', 'sword-2', 'sword-3', 'sword-4']}
+ svg
+ transform={transform(0.5, 7.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.progressiveController('shield', 0, 3)}
+ icons={['fighter-shield', 'fire-shield', 'mirror-shield']}
+ svg
+ transform={transform(1.5, 7.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.progressiveController('mail', 1, 3)}
+ icons={['green-mail', 'blue-mail', 'red-mail']}
+ svg
+ transform={transform(2.5, 7.5)}
+ />
+ <ToggleIcon
+ controller={ToggleIcon.modulusController('heart-piece')}
+ icons={['heart-0', 'heart-1', 'heart-2', 'heart-3']}
+ svg
+ transform={transform(3.5, 7.5)}
+ />
+ </>;
+};
+
+export default Items;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-
-import {
- addDungeonCheck,
- aggregateDungeonStatus,
- aggregateLocationStatus,
- clearAll,
- completeDungeonChecks,
- countRemainingLocations,
- getDungeonClearedItems,
- getDungeonRemainingItems,
- hasDungeonBoss,
- hasDungeonPrize,
- isDungeonCleared,
- removeDungeonCheck,
- resetDungeonChecks,
- setBossDefeated,
- setPrizeAcquired,
- unclearAll,
-} from '../../../helpers/tracker';
-import { useTracker } from '../../../hooks/tracker';
-
-const GENERIC_LW_DUNGEONS = [
- {
- id: 'hc',
- x: 0.5,
- y: 0.5,
- },
- {
- id: 'ep',
- x: 0.95,
- y: 0.42,
- },
- {
- id: 'dp',
- x: 0.075,
- y: 0.8,
- },
- {
- id: 'th',
- x: 0.56,
- y: 0.05,
- },
-];
-
-const LW_DUNGEONS = [
- ...GENERIC_LW_DUNGEONS,
- {
- id: 'ct',
- x: 0.5,
- y: 0.4,
- },
-];
-
-const INVERTED_LW_DUNGEONS = [
- ...GENERIC_LW_DUNGEONS,
- {
- id: 'gt',
- x: 0.5,
- y: 0.4,
- },
-];
-
-const GENERIC_LW_LOCATIONS = [
- {
- id: 'aginah',
- checks: [
- 'aginah',
- ],
- x: 0.2,
- y: 0.83,
- },
- {
- id: 'blinds-hut',
- checks: [
- 'blinds-hut-top',
- 'blinds-hut-left',
- 'blinds-hut-right',
- 'blinds-hut-far-left',
- 'blinds-hut-far-right',
- ],
- x: 0.14,
- y: 0.42,
- },
- {
- id: 'bombos-tablet',
- checks: [
- 'bombos-tablet',
- ],
- x: 0.225,
- y: 0.925,
- },
- {
- id: 'bonk-rocks',
- checks: [
- 'bonk-rocks',
- ],
- x: 0.4,
- y: 0.3,
- },
- {
- id: 'bottle-vendor',
- checks: [
- 'bottle-vendor',
- ],
- x: 0.1,
- y: 0.475,
- },
- {
- id: 'cave-45',
- checks: [
- 'cave-45',
- ],
- x: 0.27,
- y: 0.83,
- },
- {
- id: 'checkerboard',
- checks: [
- 'checkerboard',
- ],
- x: 0.18,
- y: 0.78,
- },
- {
- id: 'chicken-house',
- checks: [
- 'chicken-house',
- ],
- x: 0.1,
- y: 0.53,
- },
- {
- id: 'dam',
- checks: [
- 'flooded-chest',
- 'sunken-treasure',
- ],
- x: 0.4675,
- y: 0.9375,
- },
- {
- id: 'desert-ledge',
- checks: [
- 'desert-ledge',
- ],
- x: 0.025,
- y: 0.9,
- },
- {
- id: 'ether-tablet',
- checks: [
- 'ether-tablet',
- ],
- x: 0.425,
- y: 0.025,
- },
- {
- id: 'floating-island',
- checks: [
- 'floating-island',
- ],
- x: 0.8,
- y: 0.025,
- },
- {
- id: 'flute-spot',
- checks: [
- 'flute-spot',
- ],
- x: 0.3,
- y: 0.675,
- },
- {
- id: 'graveyard-ledge',
- checks: [
- 'graveyard-ledge',
- ],
- x: 0.57,
- y: 0.28,
- },
- {
- id: 'hobo',
- checks: [
- 'hobo',
- ],
- x: 0.7,
- y: 0.7,
- },
- {
- id: 'ice-rod-cave',
- checks: [
- 'ice-rod-cave',
- ],
- x: 0.9,
- y: 0.76,
- },
- {
- id: 'kak-well',
- checks: [
- 'kak-well-top',
- 'kak-well-left',
- 'kak-well-mid',
- 'kak-well-right',
- 'kak-well-bottom',
- ],
- x: 0.04,
- y: 0.425,
- },
- {
- id: 'kings-tomb',
- checks: [
- 'kings-tomb',
- ],
- x: 0.62,
- y: 0.3,
- },
- {
- id: 'lake-hylia-island',
- checks: [
- 'lake-hylia-island',
- ],
- x: 0.725,
- y: 0.8375,
- },
- {
- id: 'library',
- checks: [
- 'library',
- ],
- x: 0.15,
- y: 0.65,
- },
- {
- id: 'lost-woods-hideout',
- checks: [
- 'lost-woods-hideout',
- ],
- x: 0.19,
- y: 0.14,
- },
- {
- id: 'lumberjack',
- checks: [
- 'lumberjack',
- ],
- x: 0.3,
- y: 0.07,
- },
- {
- id: 'magic-bat',
- checks: [
- 'magic-bat',
- ],
- x: 0.325,
- y: 0.55,
- },
- {
- id: 'mimic-cave',
- checks: [
- 'mimic-cave',
- ],
- x: 0.85,
- y: 0.1,
- },
- {
- id: 'mini-moldorm-cave',
- checks: [
- 'mini-moldorm-left',
- 'mini-moldorm-right',
- 'mini-moldorm-far-left',
- 'mini-moldorm-far-right',
- 'mini-moldorm-npc',
- ],
- x: 0.65,
- y: 0.95,
- },
- {
- id: 'mushroom-spot',
- checks: [
- 'mushroom-spot',
- ],
- x: 0.125,
- y: 0.08,
- },
- {
- id: 'old-man',
- checks: [
- 'old-man',
- ],
- x: 0.405,
- y: 0.195,
- },
- {
- id: 'paradox-cave',
- checks: [
- 'paradox-lower-far-left',
- 'paradox-lower-left',
- 'paradox-lower-right',
- 'paradox-lower-far-right',
- 'paradox-lower-mid',
- 'paradox-upper-left',
- 'paradox-upper-right',
- ],
- x: 0.85,
- y: 0.2,
- },
- {
- id: 'pedestal',
- checks: [
- 'pedestal',
- ],
- x: 0.03,
- y: 0.05,
- },
- {
- id: 'potion-shop',
- checks: [
- 'potion-shop',
- ],
- x: 0.8,
- y: 0.325,
- },
- {
- id: 'race-game',
- checks: [
- 'race-game',
- ],
- x: 0.025,
- y: 0.7,
- },
- {
- id: 'saha',
- checks: [
- 'saha',
- ],
- x: 0.815,
- y: 0.465,
- },
- {
- id: 'saha-hut',
- checks: [
- 'saha-left',
- 'saha-mid',
- 'saha-right',
- ],
- x: 0.815,
- y: 0.42,
- },
- {
- id: 'sick-kid',
- checks: [
- 'sick-kid',
- ],
- x: 0.155,
- y: 0.525,
- },
- {
- id: 'uncle',
- checks: [
- 'uncle',
- 'secret-passage',
- ],
- x: 0.6,
- y: 0.4,
- },
- {
- id: 'spec-rock',
- checks: [
- 'spec-rock',
- ],
- x: 0.48,
- y: 0.09,
- },
- {
- id: 'spec-rock-cave',
- checks: [
- 'spec-rock-cave',
- ],
- x: 0.48,
- y: 0.14,
- },
- {
- id: 'spiral-cave',
- checks: [
- 'spiral-cave',
- ],
- x: 0.8,
- y: 0.1,
- },
- {
- id: 'tavern',
- checks: [
- 'tavern',
- ],
- x: 0.16,
- y: 0.58,
- },
- {
- id: 'waterfall-fairy',
- checks: [
- 'waterfall-fairy-left',
- 'waterfall-fairy-right',
- ],
- x: 0.9,
- y: 0.15,
- },
- {
- id: 'zora',
- checks: [
- 'zora',
- ],
- x: 0.975,
- y: 0.12,
- },
- {
- id: 'zora-ledge',
- checks: [
- 'zora-ledge',
- ],
- x: 0.975,
- y: 0.165,
- },
-];
-
-const LW_LOCATIONS = [
- ...GENERIC_LW_LOCATIONS,
- {
- id: 'links-house',
- checks: [
- 'links-house',
- ],
- x: 0.55,
- y: 0.6875,
- },
-];
-
-const INVERTED_LW_LOCATIONS = GENERIC_LW_LOCATIONS;
-
-const GENERIC_DW_DUNGEONS = [
- {
- id: 'pd',
- x: 0.95,
- y: 0.42,
- },
- {
- id: 'sp',
- x: 0.4675,
- y: 0.9375,
- },
- {
- id: 'sw',
- x: 0.05,
- y: 0.05,
- },
- {
- id: 'tt',
- x: 0.125,
- y: 0.475,
- },
- {
- id: 'ip',
- x: 0.7975,
- y: 0.86,
- },
- {
- id: 'mm',
- x: 0.12,
- y: 0.82,
- },
- {
- id: 'tr',
- x: 0.94,
- y: 0.06,
- },
-];
-
-const DW_DUNGEONS = [
- ...GENERIC_DW_DUNGEONS,
- {
- id: 'gt',
- x: 0.56,
- y: 0.05,
- },
-];
-
-const INVERTED_DW_DUNGEONS = [
- ...GENERIC_DW_DUNGEONS,
- {
- id: 'ct',
- x: 0.56,
- y: 0.05,
- },
-];
-
-const GENERIC_DW_LOCATIONS = [
- {
- id: 'blacksmith',
- checks: [
- 'blacksmith',
- ],
- x: 0.15,
- y: 0.65,
- },
- {
- id: 'brewery',
- checks: [
- 'brewery',
- ],
- x: 0.1,
- y: 0.6,
- },
- {
- id: 'bumper-cave',
- checks: [
- 'bumper-cave',
- ],
- x: 0.325,
- y: 0.15,
- },
- {
- id: 'c-house',
- checks: [
- 'c-house',
- ],
- x: 0.2,
- y: 0.5,
- },
- {
- id: 'catfish',
- checks: [
- 'catfish',
- ],
- x: 0.9,
- y: 0.175,
- },
- {
- id: 'chest-game',
- checks: [
- 'chest-game',
- ],
- x: 0.05,
- y: 0.45,
- },
- {
- id: 'digging-game',
- checks: [
- 'digging-game',
- ],
- x: 0.05,
- y: 0.7,
- },
- {
- id: 'hammer-pegs',
- checks: [
- 'hammer-pegs',
- ],
- x: 0.3125,
- y: 0.6,
- },
- {
- id: 'hookshot-cave',
- checks: [
- 'hookshot-cave-tl',
- 'hookshot-cave-tr',
- 'hookshot-cave-bl',
- ],
- x: 0.85,
- y: 0.02,
- },
- {
- id: 'hookshot-cave-bonk',
- checks: [
- 'hookshot-cave-br',
- ],
- x: 0.85,
- y: 0.065,
- },
- {
- id: 'hype-cave',
- checks: [
- 'hype-cave-top',
- 'hype-cave-left',
- 'hype-cave-right',
- 'hype-cave-bottom',
- 'hype-cave-npc',
- ],
- x: 0.6,
- y: 0.75,
- },
- {
- id: 'mire-shed',
- checks: [
- 'mire-shed-left',
- 'mire-shed-right',
- ],
- x: 0.04,
- y: 0.8,
- },
- {
- id: 'purple-chest',
- checks: [
- 'purple-chest',
- ],
- x: 0.3125,
- y: 0.525,
- },
- {
- id: 'pyramid',
- checks: [
- 'pyramid',
- ],
- x: 0.575,
- y: 0.45,
- },
- {
- id: 'pyramid-fairy',
- checks: [
- 'pyramid-fairy-left',
- 'pyramid-fairy-right',
- ],
- x: 0.45,
- y: 0.5,
- },
- {
- id: 'spike-cave',
- checks: [
- 'spike-cave',
- ],
- x: 0.575,
- y: 0.15,
- },
- {
- id: 'stumpy',
- checks: [
- 'stumpy',
- ],
- x: 0.3125,
- y: 0.6875,
- },
- {
- id: 'super-bunny',
- checks: [
- 'super-bunny-top',
- 'super-bunny-bottom',
- ],
- x: 0.85,
- y: 0.15,
- },
-];
-
-const DW_LOCATIONS = GENERIC_DW_LOCATIONS;
-
-const INVERTED_DW_LOCATIONS = [
- ...GENERIC_DW_LOCATIONS,
- {
- id: 'links-house',
- checks: [
- 'links-house',
- ],
- x: 0.55,
- y: 0.6875,
- },
-];
-
-const Location = ({ number, l, size }) => {
- const { t } = useTranslation();
-
- const classNames = ['location', `status-${l.status}`];
- if (size) {
- classNames.push(`size-${size}`);
- }
- if (l.handlePrimary) {
- classNames.push('clickable');
- }
-
- return <g
- className={classNames.join(' ')}
- onClick={(e) => {
- l.handlePrimary();
- e.preventDefault();
- e.stopPropagation();
- }}
- onContextMenu={(e) => {
- l.handleSecondary();
- e.preventDefault();
- e.stopPropagation();
- }}
- transform={`translate(${l.x} ${l.y})`}
- >
- <title>{t(`tracker.location.${l.id}`)}</title>
- <rect className="box" x="0" y="0" />
- {number && l.remaining ?
- <text className="text" x="0" y="0">{l.remaining}</text>
- : null}
- </g>;
-};
-
-Location.propTypes = {
- number: PropTypes.bool,
- l: PropTypes.shape({
- id: PropTypes.string,
- x: PropTypes.number,
- y: PropTypes.number,
- number: PropTypes.number,
- remaining: PropTypes.number,
- status: PropTypes.string,
- handlePrimary: PropTypes.func,
- handleSecondary: PropTypes.func,
- }),
- size: PropTypes.string,
-};
-
-const makeBackground = (src, level) => {
- const amount = Math.pow(2, Math.max(0, level - 8));
- const size = 1 / amount;
- const tiles = [];
- for (let y = 0; y < amount; ++y) {
- for (let x = 0; x < amount; ++x) {
- tiles.push(<image
- key={`${x}-${y}`}
- x={x * size}
- y={y * size}
- width={size * 1.002}
- height={size * 1.002}
- href={`/media/alttp/map/${src}/${level}/${x}_${y}.png`}
- />);
- }
- }
- return tiles;
-};
-
-const Overworld = () => {
- const { config, dungeons, logic, setManualState, state } = useTracker();
-
- const mapDungeon = React.useCallback(dungeon => {
- const definition = dungeons.find(d => d.id === dungeon.id);
- const remaining = getDungeonRemainingItems(state, definition);
- const status = aggregateDungeonStatus(definition, logic, state);
- return {
- ...dungeon,
- status,
- remaining,
- handlePrimary: () => {
- if (getDungeonRemainingItems(state, definition)) {
- setManualState(addDungeonCheck(definition));
- } else if (
- !hasDungeonBoss(state, definition) || !hasDungeonPrize(state, definition)
- ) {
- if (definition.boss) {
- setManualState(setBossDefeated(definition, true));
- }
- if (definition.prize) {
- setManualState(setPrizeAcquired(definition, true));
- }
- } else {
- setManualState(resetDungeonChecks(definition));
- if (definition.boss) {
- setManualState(setBossDefeated(definition, false));
- }
- if (definition.prize) {
- setManualState(setPrizeAcquired(definition, false));
- }
- }
- },
- handleSecondary: () => {
- if (isDungeonCleared(state, definition)) {
- if (definition.items) {
- setManualState(removeDungeonCheck(definition));
- }
- if (definition.boss) {
- setManualState(setBossDefeated(definition, false));
- }
- if (definition.prize) {
- setManualState(setPrizeAcquired(definition, false));
- }
- } else if (getDungeonClearedItems(state, definition)) {
- setManualState(removeDungeonCheck(definition));
- } else {
- setManualState(completeDungeonChecks(definition));
- if (definition.boss) {
- setManualState(setBossDefeated(definition, true));
- }
- if (definition.prize) {
- setManualState(setPrizeAcquired(definition, true));
- }
- }
- },
- };
- }, [dungeons, logic, setManualState, state]);
-
- const mapLocation = React.useCallback(loc => {
- const remaining = countRemainingLocations(state, loc.checks);
- const status = aggregateLocationStatus(loc.checks, logic, state);
- return {
- ...loc,
- remaining,
- status,
- handlePrimary: () => {
- if (remaining) {
- setManualState(clearAll(loc.checks));
- } else {
- setManualState(unclearAll(loc.checks));
- }
- },
- handleSecondary: () => {
- if (remaining) {
- setManualState(clearAll(loc.checks));
- } else {
- setManualState(unclearAll(loc.checks));
- }
- },
- };
- }, [logic, setManualState, state]);
-
- const lwDungeons = React.useMemo(() =>
- (config.worldState === 'inverted' ? INVERTED_LW_DUNGEONS : LW_DUNGEONS)
- .map(mapDungeon)
- , [mapDungeon]);
- const lwLocations = React.useMemo(() =>
- (config.worldState === 'inverted' ? INVERTED_LW_LOCATIONS : LW_LOCATIONS)
- .map(mapLocation)
- , [mapLocation]);
-
- const dwDungeons = React.useMemo(() =>
- (config.worldState === 'inverted' ? INVERTED_DW_DUNGEONS : DW_DUNGEONS)
- .map(mapDungeon)
- , [mapDungeon]);
- const dwLocations = React.useMemo(() =>
- (config.worldState === 'inverted' ? INVERTED_DW_LOCATIONS : DW_LOCATIONS)
- .map(mapLocation)
- , [mapLocation]);
-
- const layout = React.useMemo(() => {
- if (config.mapLayout === 'vertical') {
- return {
- lwTransform: '',
- dwTransform: 'translate(0 1)',
- };
- } else {
- return {
- lwTransform: 'scale(0.5)',
- dwTransform: 'scale(0.5) translate(1 0)',
- };
- }
- }, [config]);
-
- return <>
- <g className="light-world" transform={layout.lwTransform}>
- <g className="background">
- {makeBackground('lw_files', 10)}
- </g>
- <g className="locations">
- {lwLocations.map(l =>
- <Location key={l.id} l={l} />
- )}
- {lwDungeons.map(l =>
- <Location key={l.id} number l={l} size="lg" />
- )}
- </g>
- </g>
- <g className="dark-world" transform={layout.dwTransform}>
- <g className="background">
- {makeBackground('dw_files', 10)}
- </g>
- <g className="locations">
- {dwLocations.map(l =>
- <Location key={l.id} l={l} />
- )}
- {dwDungeons.map(l =>
- <Location key={l.id} number l={l} size="lg" />
- )}
- </g>
- </g>
- </>;
-};
-
-export default Overworld;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import {
+ addDungeonCheck,
+ aggregateDungeonStatus,
+ aggregateLocationStatus,
+ clearAll,
+ completeDungeonChecks,
+ countRemainingLocations,
+ getDungeonClearedItems,
+ getDungeonRemainingItems,
+ hasDungeonBoss,
+ hasDungeonPrize,
+ isDungeonCleared,
+ removeDungeonCheck,
+ resetDungeonChecks,
+ setBossDefeated,
+ setPrizeAcquired,
+ unclearAll,
+} from '../../../helpers/tracker';
+import { useTracker } from '../../../hooks/tracker';
+
+const GENERIC_LW_DUNGEONS = [
+ {
+ id: 'hc',
+ x: 0.5,
+ y: 0.5,
+ },
+ {
+ id: 'ep',
+ x: 0.95,
+ y: 0.42,
+ },
+ {
+ id: 'dp',
+ x: 0.075,
+ y: 0.8,
+ },
+ {
+ id: 'th',
+ x: 0.56,
+ y: 0.05,
+ },
+];
+
+const LW_DUNGEONS = [
+ ...GENERIC_LW_DUNGEONS,
+ {
+ id: 'ct',
+ x: 0.5,
+ y: 0.4,
+ },
+];
+
+const INVERTED_LW_DUNGEONS = [
+ ...GENERIC_LW_DUNGEONS,
+ {
+ id: 'gt',
+ x: 0.5,
+ y: 0.4,
+ },
+];
+
+const GENERIC_LW_LOCATIONS = [
+ {
+ id: 'aginah',
+ checks: [
+ 'aginah',
+ ],
+ x: 0.2,
+ y: 0.83,
+ },
+ {
+ id: 'blinds-hut',
+ checks: [
+ 'blinds-hut-top',
+ 'blinds-hut-left',
+ 'blinds-hut-right',
+ 'blinds-hut-far-left',
+ 'blinds-hut-far-right',
+ ],
+ x: 0.14,
+ y: 0.42,
+ },
+ {
+ id: 'bombos-tablet',
+ checks: [
+ 'bombos-tablet',
+ ],
+ x: 0.225,
+ y: 0.925,
+ },
+ {
+ id: 'bonk-rocks',
+ checks: [
+ 'bonk-rocks',
+ ],
+ x: 0.4,
+ y: 0.3,
+ },
+ {
+ id: 'bottle-vendor',
+ checks: [
+ 'bottle-vendor',
+ ],
+ x: 0.1,
+ y: 0.475,
+ },
+ {
+ id: 'cave-45',
+ checks: [
+ 'cave-45',
+ ],
+ x: 0.27,
+ y: 0.83,
+ },
+ {
+ id: 'checkerboard',
+ checks: [
+ 'checkerboard',
+ ],
+ x: 0.18,
+ y: 0.78,
+ },
+ {
+ id: 'chicken-house',
+ checks: [
+ 'chicken-house',
+ ],
+ x: 0.1,
+ y: 0.53,
+ },
+ {
+ id: 'dam',
+ checks: [
+ 'flooded-chest',
+ 'sunken-treasure',
+ ],
+ x: 0.4675,
+ y: 0.9375,
+ },
+ {
+ id: 'desert-ledge',
+ checks: [
+ 'desert-ledge',
+ ],
+ x: 0.025,
+ y: 0.9,
+ },
+ {
+ id: 'ether-tablet',
+ checks: [
+ 'ether-tablet',
+ ],
+ x: 0.425,
+ y: 0.025,
+ },
+ {
+ id: 'floating-island',
+ checks: [
+ 'floating-island',
+ ],
+ x: 0.8,
+ y: 0.025,
+ },
+ {
+ id: 'flute-spot',
+ checks: [
+ 'flute-spot',
+ ],
+ x: 0.3,
+ y: 0.675,
+ },
+ {
+ id: 'graveyard-ledge',
+ checks: [
+ 'graveyard-ledge',
+ ],
+ x: 0.57,
+ y: 0.28,
+ },
+ {
+ id: 'hobo',
+ checks: [
+ 'hobo',
+ ],
+ x: 0.7,
+ y: 0.7,
+ },
+ {
+ id: 'ice-rod-cave',
+ checks: [
+ 'ice-rod-cave',
+ ],
+ x: 0.9,
+ y: 0.76,
+ },
+ {
+ id: 'kak-well',
+ checks: [
+ 'kak-well-top',
+ 'kak-well-left',
+ 'kak-well-mid',
+ 'kak-well-right',
+ 'kak-well-bottom',
+ ],
+ x: 0.04,
+ y: 0.425,
+ },
+ {
+ id: 'kings-tomb',
+ checks: [
+ 'kings-tomb',
+ ],
+ x: 0.62,
+ y: 0.3,
+ },
+ {
+ id: 'lake-hylia-island',
+ checks: [
+ 'lake-hylia-island',
+ ],
+ x: 0.725,
+ y: 0.8375,
+ },
+ {
+ id: 'library',
+ checks: [
+ 'library',
+ ],
+ x: 0.15,
+ y: 0.65,
+ },
+ {
+ id: 'lost-woods-hideout',
+ checks: [
+ 'lost-woods-hideout',
+ ],
+ x: 0.19,
+ y: 0.14,
+ },
+ {
+ id: 'lumberjack',
+ checks: [
+ 'lumberjack',
+ ],
+ x: 0.3,
+ y: 0.07,
+ },
+ {
+ id: 'magic-bat',
+ checks: [
+ 'magic-bat',
+ ],
+ x: 0.325,
+ y: 0.55,
+ },
+ {
+ id: 'mimic-cave',
+ checks: [
+ 'mimic-cave',
+ ],
+ x: 0.85,
+ y: 0.1,
+ },
+ {
+ id: 'mini-moldorm-cave',
+ checks: [
+ 'mini-moldorm-left',
+ 'mini-moldorm-right',
+ 'mini-moldorm-far-left',
+ 'mini-moldorm-far-right',
+ 'mini-moldorm-npc',
+ ],
+ x: 0.65,
+ y: 0.95,
+ },
+ {
+ id: 'mushroom-spot',
+ checks: [
+ 'mushroom-spot',
+ ],
+ x: 0.125,
+ y: 0.08,
+ },
+ {
+ id: 'old-man',
+ checks: [
+ 'old-man',
+ ],
+ x: 0.405,
+ y: 0.195,
+ },
+ {
+ id: 'paradox-cave',
+ checks: [
+ 'paradox-lower-far-left',
+ 'paradox-lower-left',
+ 'paradox-lower-right',
+ 'paradox-lower-far-right',
+ 'paradox-lower-mid',
+ 'paradox-upper-left',
+ 'paradox-upper-right',
+ ],
+ x: 0.85,
+ y: 0.2,
+ },
+ {
+ id: 'pedestal',
+ checks: [
+ 'pedestal',
+ ],
+ x: 0.03,
+ y: 0.05,
+ },
+ {
+ id: 'potion-shop',
+ checks: [
+ 'potion-shop',
+ ],
+ x: 0.8,
+ y: 0.325,
+ },
+ {
+ id: 'race-game',
+ checks: [
+ 'race-game',
+ ],
+ x: 0.025,
+ y: 0.7,
+ },
+ {
+ id: 'saha',
+ checks: [
+ 'saha',
+ ],
+ x: 0.815,
+ y: 0.465,
+ },
+ {
+ id: 'saha-hut',
+ checks: [
+ 'saha-left',
+ 'saha-mid',
+ 'saha-right',
+ ],
+ x: 0.815,
+ y: 0.42,
+ },
+ {
+ id: 'sick-kid',
+ checks: [
+ 'sick-kid',
+ ],
+ x: 0.155,
+ y: 0.525,
+ },
+ {
+ id: 'uncle',
+ checks: [
+ 'uncle',
+ 'secret-passage',
+ ],
+ x: 0.6,
+ y: 0.4,
+ },
+ {
+ id: 'spec-rock',
+ checks: [
+ 'spec-rock',
+ ],
+ x: 0.48,
+ y: 0.09,
+ },
+ {
+ id: 'spec-rock-cave',
+ checks: [
+ 'spec-rock-cave',
+ ],
+ x: 0.48,
+ y: 0.14,
+ },
+ {
+ id: 'spiral-cave',
+ checks: [
+ 'spiral-cave',
+ ],
+ x: 0.8,
+ y: 0.1,
+ },
+ {
+ id: 'tavern',
+ checks: [
+ 'tavern',
+ ],
+ x: 0.16,
+ y: 0.58,
+ },
+ {
+ id: 'waterfall-fairy',
+ checks: [
+ 'waterfall-fairy-left',
+ 'waterfall-fairy-right',
+ ],
+ x: 0.9,
+ y: 0.15,
+ },
+ {
+ id: 'zora',
+ checks: [
+ 'zora',
+ ],
+ x: 0.975,
+ y: 0.12,
+ },
+ {
+ id: 'zora-ledge',
+ checks: [
+ 'zora-ledge',
+ ],
+ x: 0.975,
+ y: 0.165,
+ },
+];
+
+const LW_LOCATIONS = [
+ ...GENERIC_LW_LOCATIONS,
+ {
+ id: 'links-house',
+ checks: [
+ 'links-house',
+ ],
+ x: 0.55,
+ y: 0.6875,
+ },
+];
+
+const INVERTED_LW_LOCATIONS = GENERIC_LW_LOCATIONS;
+
+const GENERIC_DW_DUNGEONS = [
+ {
+ id: 'pd',
+ x: 0.95,
+ y: 0.42,
+ },
+ {
+ id: 'sp',
+ x: 0.4675,
+ y: 0.9375,
+ },
+ {
+ id: 'sw',
+ x: 0.05,
+ y: 0.05,
+ },
+ {
+ id: 'tt',
+ x: 0.125,
+ y: 0.475,
+ },
+ {
+ id: 'ip',
+ x: 0.7975,
+ y: 0.86,
+ },
+ {
+ id: 'mm',
+ x: 0.12,
+ y: 0.82,
+ },
+ {
+ id: 'tr',
+ x: 0.94,
+ y: 0.06,
+ },
+];
+
+const DW_DUNGEONS = [
+ ...GENERIC_DW_DUNGEONS,
+ {
+ id: 'gt',
+ x: 0.56,
+ y: 0.05,
+ },
+];
+
+const INVERTED_DW_DUNGEONS = [
+ ...GENERIC_DW_DUNGEONS,
+ {
+ id: 'ct',
+ x: 0.56,
+ y: 0.05,
+ },
+];
+
+const GENERIC_DW_LOCATIONS = [
+ {
+ id: 'blacksmith',
+ checks: [
+ 'blacksmith',
+ ],
+ x: 0.15,
+ y: 0.65,
+ },
+ {
+ id: 'brewery',
+ checks: [
+ 'brewery',
+ ],
+ x: 0.1,
+ y: 0.6,
+ },
+ {
+ id: 'bumper-cave',
+ checks: [
+ 'bumper-cave',
+ ],
+ x: 0.325,
+ y: 0.15,
+ },
+ {
+ id: 'c-house',
+ checks: [
+ 'c-house',
+ ],
+ x: 0.2,
+ y: 0.5,
+ },
+ {
+ id: 'catfish',
+ checks: [
+ 'catfish',
+ ],
+ x: 0.9,
+ y: 0.175,
+ },
+ {
+ id: 'chest-game',
+ checks: [
+ 'chest-game',
+ ],
+ x: 0.05,
+ y: 0.45,
+ },
+ {
+ id: 'digging-game',
+ checks: [
+ 'digging-game',
+ ],
+ x: 0.05,
+ y: 0.7,
+ },
+ {
+ id: 'hammer-pegs',
+ checks: [
+ 'hammer-pegs',
+ ],
+ x: 0.3125,
+ y: 0.6,
+ },
+ {
+ id: 'hookshot-cave',
+ checks: [
+ 'hookshot-cave-tl',
+ 'hookshot-cave-tr',
+ 'hookshot-cave-bl',
+ ],
+ x: 0.85,
+ y: 0.02,
+ },
+ {
+ id: 'hookshot-cave-bonk',
+ checks: [
+ 'hookshot-cave-br',
+ ],
+ x: 0.85,
+ y: 0.065,
+ },
+ {
+ id: 'hype-cave',
+ checks: [
+ 'hype-cave-top',
+ 'hype-cave-left',
+ 'hype-cave-right',
+ 'hype-cave-bottom',
+ 'hype-cave-npc',
+ ],
+ x: 0.6,
+ y: 0.75,
+ },
+ {
+ id: 'mire-shed',
+ checks: [
+ 'mire-shed-left',
+ 'mire-shed-right',
+ ],
+ x: 0.04,
+ y: 0.8,
+ },
+ {
+ id: 'purple-chest',
+ checks: [
+ 'purple-chest',
+ ],
+ x: 0.3125,
+ y: 0.525,
+ },
+ {
+ id: 'pyramid',
+ checks: [
+ 'pyramid',
+ ],
+ x: 0.575,
+ y: 0.45,
+ },
+ {
+ id: 'pyramid-fairy',
+ checks: [
+ 'pyramid-fairy-left',
+ 'pyramid-fairy-right',
+ ],
+ x: 0.45,
+ y: 0.5,
+ },
+ {
+ id: 'spike-cave',
+ checks: [
+ 'spike-cave',
+ ],
+ x: 0.575,
+ y: 0.15,
+ },
+ {
+ id: 'stumpy',
+ checks: [
+ 'stumpy',
+ ],
+ x: 0.3125,
+ y: 0.6875,
+ },
+ {
+ id: 'super-bunny',
+ checks: [
+ 'super-bunny-top',
+ 'super-bunny-bottom',
+ ],
+ x: 0.85,
+ y: 0.15,
+ },
+];
+
+const DW_LOCATIONS = GENERIC_DW_LOCATIONS;
+
+const INVERTED_DW_LOCATIONS = [
+ ...GENERIC_DW_LOCATIONS,
+ {
+ id: 'links-house',
+ checks: [
+ 'links-house',
+ ],
+ x: 0.55,
+ y: 0.6875,
+ },
+];
+
+const Location = ({ number, l, size }) => {
+ const { t } = useTranslation();
+
+ const classNames = ['location', `status-${l.status}`];
+ if (size) {
+ classNames.push(`size-${size}`);
+ }
+ if (l.handlePrimary) {
+ classNames.push('clickable');
+ }
+
+ return <g
+ className={classNames.join(' ')}
+ onClick={(e) => {
+ l.handlePrimary();
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ onContextMenu={(e) => {
+ l.handleSecondary();
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ transform={`translate(${l.x} ${l.y})`}
+ >
+ <title>{t(`tracker.location.${l.id}`)}</title>
+ <rect className="box" x="0" y="0" />
+ {number && l.remaining ?
+ <text className="text" x="0" y="0">{l.remaining}</text>
+ : null}
+ </g>;
+};
+
+Location.propTypes = {
+ number: PropTypes.bool,
+ l: PropTypes.shape({
+ id: PropTypes.string,
+ x: PropTypes.number,
+ y: PropTypes.number,
+ number: PropTypes.number,
+ remaining: PropTypes.number,
+ status: PropTypes.string,
+ handlePrimary: PropTypes.func,
+ handleSecondary: PropTypes.func,
+ }),
+ size: PropTypes.string,
+};
+
+const makeBackground = (src, level) => {
+ const amount = Math.pow(2, Math.max(0, level - 8));
+ const size = 1 / amount;
+ const tiles = [];
+ for (let y = 0; y < amount; ++y) {
+ for (let x = 0; x < amount; ++x) {
+ tiles.push(<image
+ key={`${x}-${y}`}
+ x={x * size}
+ y={y * size}
+ width={size * 1.002}
+ height={size * 1.002}
+ href={`/media/alttp/map/${src}/${level}/${x}_${y}.png`}
+ />);
+ }
+ }
+ return tiles;
+};
+
+const Overworld = () => {
+ const { config, dungeons, logic, setManualState, state } = useTracker();
+
+ const mapDungeon = React.useCallback(dungeon => {
+ const definition = dungeons.find(d => d.id === dungeon.id);
+ const remaining = getDungeonRemainingItems(state, definition);
+ const status = aggregateDungeonStatus(definition, logic, state);
+ return {
+ ...dungeon,
+ status,
+ remaining,
+ handlePrimary: () => {
+ if (getDungeonRemainingItems(state, definition)) {
+ setManualState(addDungeonCheck(definition));
+ } else if (
+ !hasDungeonBoss(state, definition) || !hasDungeonPrize(state, definition)
+ ) {
+ if (definition.boss) {
+ setManualState(setBossDefeated(definition, true));
+ }
+ if (definition.prize) {
+ setManualState(setPrizeAcquired(definition, true));
+ }
+ } else {
+ setManualState(resetDungeonChecks(definition));
+ if (definition.boss) {
+ setManualState(setBossDefeated(definition, false));
+ }
+ if (definition.prize) {
+ setManualState(setPrizeAcquired(definition, false));
+ }
+ }
+ },
+ handleSecondary: () => {
+ if (isDungeonCleared(state, definition)) {
+ if (definition.items) {
+ setManualState(removeDungeonCheck(definition));
+ }
+ if (definition.boss) {
+ setManualState(setBossDefeated(definition, false));
+ }
+ if (definition.prize) {
+ setManualState(setPrizeAcquired(definition, false));
+ }
+ } else if (getDungeonClearedItems(state, definition)) {
+ setManualState(removeDungeonCheck(definition));
+ } else {
+ setManualState(completeDungeonChecks(definition));
+ if (definition.boss) {
+ setManualState(setBossDefeated(definition, true));
+ }
+ if (definition.prize) {
+ setManualState(setPrizeAcquired(definition, true));
+ }
+ }
+ },
+ };
+ }, [dungeons, logic, setManualState, state]);
+
+ const mapLocation = React.useCallback(loc => {
+ const remaining = countRemainingLocations(state, loc.checks);
+ const status = aggregateLocationStatus(loc.checks, logic, state);
+ return {
+ ...loc,
+ remaining,
+ status,
+ handlePrimary: () => {
+ if (remaining) {
+ setManualState(clearAll(loc.checks));
+ } else {
+ setManualState(unclearAll(loc.checks));
+ }
+ },
+ handleSecondary: () => {
+ if (remaining) {
+ setManualState(clearAll(loc.checks));
+ } else {
+ setManualState(unclearAll(loc.checks));
+ }
+ },
+ };
+ }, [logic, setManualState, state]);
+
+ const lwDungeons = React.useMemo(() =>
+ (config.worldState === 'inverted' ? INVERTED_LW_DUNGEONS : LW_DUNGEONS)
+ .map(mapDungeon)
+ , [mapDungeon]);
+ const lwLocations = React.useMemo(() =>
+ (config.worldState === 'inverted' ? INVERTED_LW_LOCATIONS : LW_LOCATIONS)
+ .map(mapLocation)
+ , [mapLocation]);
+
+ const dwDungeons = React.useMemo(() =>
+ (config.worldState === 'inverted' ? INVERTED_DW_DUNGEONS : DW_DUNGEONS)
+ .map(mapDungeon)
+ , [mapDungeon]);
+ const dwLocations = React.useMemo(() =>
+ (config.worldState === 'inverted' ? INVERTED_DW_LOCATIONS : DW_LOCATIONS)
+ .map(mapLocation)
+ , [mapLocation]);
+
+ const layout = React.useMemo(() => {
+ if (config.mapLayout === 'vertical') {
+ return {
+ lwTransform: '',
+ dwTransform: 'translate(0 1)',
+ };
+ } else {
+ return {
+ lwTransform: 'scale(0.5)',
+ dwTransform: 'scale(0.5) translate(1 0)',
+ };
+ }
+ }, [config]);
+
+ return <>
+ <g className="light-world" transform={layout.lwTransform}>
+ <g className="background">
+ {makeBackground('lw_files', 10)}
+ </g>
+ <g className="locations">
+ {lwLocations.map(l =>
+ <Location key={l.id} l={l} />
+ )}
+ {lwDungeons.map(l =>
+ <Location key={l.id} number l={l} size="lg" />
+ )}
+ </g>
+ </g>
+ <g className="dark-world" transform={layout.dwTransform}>
+ <g className="background">
+ {makeBackground('dw_files', 10)}
+ </g>
+ <g className="locations">
+ {dwLocations.map(l =>
+ <Location key={l.id} l={l} />
+ )}
+ {dwDungeons.map(l =>
+ <Location key={l.id} number l={l} size="lg" />
+ )}
+ </g>
+ </g>
+ </>;
+};
+
+export default Overworld;
+++ /dev/null
-import React from 'react';
-
-import Overworld from './Overworld';
-
-const Map = () => {
- return <>
- <Overworld />
- </>;
-};
-
-export default Map;
--- /dev/null
+import React from 'react';
+
+import Overworld from './Overworld';
+
+const Map = () => {
+ return <>
+ <Overworld />
+ </>;
+};
+
+export default Map;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import ZeldaIcon from '../common/ZeldaIcon';
-import {
- addDungeonCheck,
- decrement,
- getDungeonBoss,
- getDungeonRemainingItems,
- getDungeonPrize,
- getGTBoss,
- hasDungeonBoss,
- hasDungeonPrize,
- highestActive,
- increment,
- removeDungeonCheck,
- toggleBoolean,
- toggleBossDefeated,
-} from '../../helpers/tracker';
-import { useTracker } from '../../hooks/tracker';
-
-const ToggleIcon = ({ controller, className, icons, svg, title, transform }) => {
- const { setManualState, state } = useTracker();
- const activeController = controller || ToggleIcon.nullController;
- const active = activeController.getActive(state, icons);
- const defaultIcon = activeController.getDefault(state, icons);
- const icon = active || defaultIcon || icons[0];
- const classNames = ['toggle-icon'];
- if (active) {
- classNames.push('active');
- } else {
- classNames.push('inactive');
- }
- if (className) {
- classNames.push(className);
- }
- if (svg) {
- return <g
- className={classNames.join(' ')}
- data-icon={icon}
- onClick={(e) => {
- activeController.handlePrimary(state, setManualState, icons);
- e.preventDefault();
- e.stopPropagation();
- }}
- onContextMenu={(e) => {
- activeController.handleSecondary(state, setManualState, icons);
- e.preventDefault();
- e.stopPropagation();
- }}
- transform={transform}
- >
- <ZeldaIcon name={icon} svg title={title} />
- </g>;
- }
- return <span
- className={classNames.join(' ')}
- onClick={(e) => {
- activeController.handlePrimary(state, setManualState, icons);
- e.preventDefault();
- e.stopPropagation();
- }}
- onContextMenu={(e) => {
- activeController.handleSecondary(state, setManualState, icons);
- e.preventDefault();
- e.stopPropagation();
- }}
- >
- <ZeldaIcon name={active || defaultIcon || icons[0]} title={title} />
- </span>;
-};
-
-const doNothing = () => { };
-
-const firstIcon = (state, icons) => icons[0];
-
-const nextIcon = (state, setState, icons) => {
- const highest = highestActive(state, icons);
- const highestIndex = highest ? icons.indexOf(highest) : -1;
- if (highestIndex + 1 < icons.length) {
- setState(toggleBoolean(icons[highestIndex + 1]));
- } else {
- const changes = {};
- icons.forEach(icon => {
- changes[icon] = false;
- });
- setState(s => ({ ...s, ...changes }));
- }
-};
-
-const previousIcon = (state, setState, icons) => {
- const highest = highestActive(state, icons);
- const highestIndex = highest ? icons.indexOf(highest) : -1;
- if (highestIndex >= 0) {
- setState(toggleBoolean(icons[highestIndex]));
- } else {
- const changes = {};
- icons.forEach(icon => {
- changes[icon] = true;
- });
- setState(s => ({ ...s, ...changes }));
- }
-};
-
-const nextString = property => (state, setState, icons) => {
- const current = state[property] || icons[0];
- const currentIndex = icons.indexOf(current);
- const nextIndex = (currentIndex + 1) % icons.length;
- const next = icons[nextIndex];
- setState(s => ({ ...s, [property]: next }));
-};
-
-const previousString = property => (state, setState, icons) => {
- const current = state[property] || icons[0];
- const currentIndex = icons.indexOf(current);
- const previousIndex = (currentIndex + icons.length - 1) % icons.length;
- const previous = icons[previousIndex];
- setState(s => ({ ...s, [property]: previous }));
-};
-
-ToggleIcon.bottleController = ctrl => ({
- getActive: (state, icons) => state[ctrl] ? icons[state[ctrl] - 1] : null,
- getDefault: () => 'bottle',
- handlePrimary: (state, setState, icons) => {
- if (state[ctrl] === 0) {
- // skip over mushroom
- setState(s => ({ ...s, [ctrl]: 2 }));
- } else {
- setState(increment(ctrl, icons.length));
- }
- },
- handleSecondary: (state, setState, icons) => {
- if (state[ctrl] === 2) {
- // skip over mushroom
- setState(s => ({ ...s, [ctrl]: 0 }));
- } else {
- setState(decrement(ctrl, icons.length));
- }
- },
-});
-
-ToggleIcon.countController = max => ({
- getActive: highestActive,
- getDefault: firstIcon,
- handlePrimary: (state, setState, icons) => {
- setState(increment(icons[0], max));
- },
- handleSecondary: (state, setState, icons) => {
- setState(decrement(icons[0], max));
- },
-});
-
-ToggleIcon.dungeonBossController = (dungeon) => ({
- getActive: (state) => hasDungeonBoss(state, dungeon) ? getDungeonBoss(state, dungeon) : null,
- getDefault: (state) => getDungeonBoss(state, dungeon),
- handlePrimary: dungeon.bosses.length > 1
- ? nextString(`${dungeon.id}-boss`)
- : (state, setState) => {
- setState(toggleBossDefeated(dungeon));
- },
- handleSecondary: dungeon.bosses.length > 1 ?
- previousString(`${dungeon.id}-boss`)
- : (state, setState) => {
- setState(toggleBossDefeated(dungeon));
- },
-});
-
-ToggleIcon.dungeonCheckController = (dungeon) => ({
- getActive: (state, icons) => getDungeonRemainingItems(state, dungeon) ? icons[1] : null,
- getDefault: firstIcon,
- handlePrimary: (state, setState) => {
- setState(addDungeonCheck(dungeon));
- },
- handleSecondary: (state, setState) => {
- setState(removeDungeonCheck(dungeon));
- },
-});
-
-ToggleIcon.dungeonController = dungeon => ({
- getActive: (state, icons) => state[`${dungeon.id}-${icons[0]}`] ? icons[0] : null,
- getDefault: firstIcon,
- handlePrimary: (state, setState, icons) => {
- setState(toggleBoolean(`${dungeon.id}-${icons[0]}`));
- },
- handleSecondary: (state, setState, icons) => {
- setState(toggleBoolean(`${dungeon.id}-${icons[0]}`));
- },
-});
-
-ToggleIcon.dungeonCountController = (dungeon, max) => ({
- getActive: (state, icons) => state[`${dungeon.id}-${icons[0]}`] ? icons[0] : null,
- getDefault: firstIcon,
- handlePrimary: (state, setState, icons) => {
- setState(increment(`${dungeon.id}-${icons[0]}`, max));
- },
- handleSecondary: (state, setState, icons) => {
- setState(decrement(`${dungeon.id}-${icons[0]}`, max));
- },
-});
-
-ToggleIcon.dungeonPrizeController = (dungeon) => ({
- getActive: (state) => hasDungeonPrize(state, dungeon) ? getDungeonPrize(state, dungeon) : null,
- getDefault: (state) => getDungeonPrize(state, dungeon),
- handlePrimary: nextString(`${dungeon.id}-prize`),
- handleSecondary: previousString(`${dungeon.id}-prize`),
-});
-
-ToggleIcon.gtBossController = (which) => ({
- getActive: (state) => getGTBoss(state, which),
- getDefault: (state) => getGTBoss(state, which),
- handlePrimary: nextString(`gt-${which}-boss`),
- handleSecondary: previousString(`gt-${which}-boss`),
-});
-
-ToggleIcon.medallionController = {
- getActive: highestActive,
- getDefault: firstIcon,
- handlePrimary: nextIcon,
- handleSecondary: (state, setState, icons) => {
- const mm = state['mm-medallion'];
- const tr = state['tr-medallion'];
- const isMM = mm === icons[0];
- const isTR = tr === icons[0];
- console.log({ mm, isMM, tr, isTR });
- if (!isMM && !isTR) {
- // empty: set as MM if mire is unset, else set as TR if TR is unset
- if (!mm) {
- setState(s => ({ ...s, 'mm-medallion': icons[0] }));
- } else if (!tr) {
- setState(s => ({ ...s, 'tr-medallion': icons[0] }));
- }
- } else if (isMM && !isTR) {
- // MM: if TR is free, switch to TR, otherwise remove MM
- if (!tr) {
- setState(s => ({ ...s, 'mm-medallion': null, 'tr-medallion': icons[0] }));
- } else {
- setState(s => ({ ...s, 'mm-medallion': null }));
- }
- } else if (!isMM && isTR) {
- // TR: if MM is free, switch to both, otherwise remove TR
- if (!mm) {
- setState(s => ({ ...s, 'mm-medallion': icons[0] }));
- } else {
- setState(s => ({ ...s, 'tr-medallion': null }));
- }
- } else {
- // both: remove both
- setState(s => ({ ...s, 'mm-medallion': null, 'tr-medallion': null }));
- }
- },
-};
-
-ToggleIcon.modulusController = ctrl => ({
- getActive: (state, icons) => icons[(state[ctrl] || 0) % icons.length],
- getDefault: firstIcon,
- handlePrimary: (state, setState, icons) => {
- setState(increment(ctrl, icons.length));
- },
- handleSecondary: (state, setState, icons) => {
- setState(decrement(ctrl, icons.length));
- },
-});
-
-ToggleIcon.nullController = {
- getActive: () => null,
- getDefault: firstIcon,
- handlePrimary: doNothing,
- handleSecondary: doNothing,
-};
-
-ToggleIcon.pinController = (pin, removePin) => ({
- getActive: firstIcon,
- getDefault: firstIcon,
- handlePrimary: doNothing,
- handleSecondary: () => removePin(pin),
-});
-
-ToggleIcon.simpleController = {
- getActive: highestActive,
- getDefault: firstIcon,
- handlePrimary: nextIcon,
- handleSecondary: previousIcon,
-};
-
-ToggleIcon.progressiveController = (master, min, max) => ({
- getActive: (state, icons) => {
- const count = Math.max(min, Math.min(max, state[master] || 0));
- return count ? icons[count - 1] : null;
- },
- getDefault: firstIcon,
- handlePrimary: (state, setState) => {
- setState(increment(master, max, min));
- },
- handleSecondary: (state, setState) => {
- setState(decrement(master, max, min));
- },
-});
-
-ToggleIcon.propTypes = {
- active: PropTypes.string,
- className: PropTypes.string,
- controller: PropTypes.shape({
- handlePrimary: PropTypes.func,
- handleSecondary: PropTypes.func,
- }),
- icons: PropTypes.arrayOf(PropTypes.string),
- svg: PropTypes.bool,
- title: PropTypes.string,
- transform: PropTypes.string,
-};
-
-export default ToggleIcon;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import ZeldaIcon from '../common/ZeldaIcon';
+import {
+ addDungeonCheck,
+ decrement,
+ getDungeonBoss,
+ getDungeonRemainingItems,
+ getDungeonPrize,
+ getGTBoss,
+ hasDungeonBoss,
+ hasDungeonPrize,
+ highestActive,
+ increment,
+ removeDungeonCheck,
+ toggleBoolean,
+ toggleBossDefeated,
+} from '../../helpers/tracker';
+import { useTracker } from '../../hooks/tracker';
+
+const ToggleIcon = ({ controller, className, icons, svg, title, transform }) => {
+ const { setManualState, state } = useTracker();
+ const activeController = controller || ToggleIcon.nullController;
+ const active = activeController.getActive(state, icons);
+ const defaultIcon = activeController.getDefault(state, icons);
+ const icon = active || defaultIcon || icons[0];
+ const classNames = ['toggle-icon'];
+ if (active) {
+ classNames.push('active');
+ } else {
+ classNames.push('inactive');
+ }
+ if (className) {
+ classNames.push(className);
+ }
+ if (svg) {
+ return <g
+ className={classNames.join(' ')}
+ data-icon={icon}
+ onClick={(e) => {
+ activeController.handlePrimary(state, setManualState, icons);
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ onContextMenu={(e) => {
+ activeController.handleSecondary(state, setManualState, icons);
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ transform={transform}
+ >
+ <ZeldaIcon name={icon} svg title={title} />
+ </g>;
+ }
+ return <span
+ className={classNames.join(' ')}
+ onClick={(e) => {
+ activeController.handlePrimary(state, setManualState, icons);
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ onContextMenu={(e) => {
+ activeController.handleSecondary(state, setManualState, icons);
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ >
+ <ZeldaIcon name={active || defaultIcon || icons[0]} title={title} />
+ </span>;
+};
+
+const doNothing = () => { };
+
+const firstIcon = (state, icons) => icons[0];
+
+const nextIcon = (state, setState, icons) => {
+ const highest = highestActive(state, icons);
+ const highestIndex = highest ? icons.indexOf(highest) : -1;
+ if (highestIndex + 1 < icons.length) {
+ setState(toggleBoolean(icons[highestIndex + 1]));
+ } else {
+ const changes = {};
+ icons.forEach(icon => {
+ changes[icon] = false;
+ });
+ setState(s => ({ ...s, ...changes }));
+ }
+};
+
+const previousIcon = (state, setState, icons) => {
+ const highest = highestActive(state, icons);
+ const highestIndex = highest ? icons.indexOf(highest) : -1;
+ if (highestIndex >= 0) {
+ setState(toggleBoolean(icons[highestIndex]));
+ } else {
+ const changes = {};
+ icons.forEach(icon => {
+ changes[icon] = true;
+ });
+ setState(s => ({ ...s, ...changes }));
+ }
+};
+
+const nextString = property => (state, setState, icons) => {
+ const current = state[property] || icons[0];
+ const currentIndex = icons.indexOf(current);
+ const nextIndex = (currentIndex + 1) % icons.length;
+ const next = icons[nextIndex];
+ setState(s => ({ ...s, [property]: next }));
+};
+
+const previousString = property => (state, setState, icons) => {
+ const current = state[property] || icons[0];
+ const currentIndex = icons.indexOf(current);
+ const previousIndex = (currentIndex + icons.length - 1) % icons.length;
+ const previous = icons[previousIndex];
+ setState(s => ({ ...s, [property]: previous }));
+};
+
+ToggleIcon.bottleController = ctrl => ({
+ getActive: (state, icons) => state[ctrl] ? icons[state[ctrl] - 1] : null,
+ getDefault: () => 'bottle',
+ handlePrimary: (state, setState, icons) => {
+ if (state[ctrl] === 0) {
+ // skip over mushroom
+ setState(s => ({ ...s, [ctrl]: 2 }));
+ } else {
+ setState(increment(ctrl, icons.length));
+ }
+ },
+ handleSecondary: (state, setState, icons) => {
+ if (state[ctrl] === 2) {
+ // skip over mushroom
+ setState(s => ({ ...s, [ctrl]: 0 }));
+ } else {
+ setState(decrement(ctrl, icons.length));
+ }
+ },
+});
+
+ToggleIcon.countController = max => ({
+ getActive: highestActive,
+ getDefault: firstIcon,
+ handlePrimary: (state, setState, icons) => {
+ setState(increment(icons[0], max));
+ },
+ handleSecondary: (state, setState, icons) => {
+ setState(decrement(icons[0], max));
+ },
+});
+
+ToggleIcon.dungeonBossController = (dungeon) => ({
+ getActive: (state) => hasDungeonBoss(state, dungeon) ? getDungeonBoss(state, dungeon) : null,
+ getDefault: (state) => getDungeonBoss(state, dungeon),
+ handlePrimary: dungeon.bosses.length > 1
+ ? nextString(`${dungeon.id}-boss`)
+ : (state, setState) => {
+ setState(toggleBossDefeated(dungeon));
+ },
+ handleSecondary: dungeon.bosses.length > 1 ?
+ previousString(`${dungeon.id}-boss`)
+ : (state, setState) => {
+ setState(toggleBossDefeated(dungeon));
+ },
+});
+
+ToggleIcon.dungeonCheckController = (dungeon) => ({
+ getActive: (state, icons) => getDungeonRemainingItems(state, dungeon) ? icons[1] : null,
+ getDefault: firstIcon,
+ handlePrimary: (state, setState) => {
+ setState(addDungeonCheck(dungeon));
+ },
+ handleSecondary: (state, setState) => {
+ setState(removeDungeonCheck(dungeon));
+ },
+});
+
+ToggleIcon.dungeonController = dungeon => ({
+ getActive: (state, icons) => state[`${dungeon.id}-${icons[0]}`] ? icons[0] : null,
+ getDefault: firstIcon,
+ handlePrimary: (state, setState, icons) => {
+ setState(toggleBoolean(`${dungeon.id}-${icons[0]}`));
+ },
+ handleSecondary: (state, setState, icons) => {
+ setState(toggleBoolean(`${dungeon.id}-${icons[0]}`));
+ },
+});
+
+ToggleIcon.dungeonCountController = (dungeon, max) => ({
+ getActive: (state, icons) => state[`${dungeon.id}-${icons[0]}`] ? icons[0] : null,
+ getDefault: firstIcon,
+ handlePrimary: (state, setState, icons) => {
+ setState(increment(`${dungeon.id}-${icons[0]}`, max));
+ },
+ handleSecondary: (state, setState, icons) => {
+ setState(decrement(`${dungeon.id}-${icons[0]}`, max));
+ },
+});
+
+ToggleIcon.dungeonPrizeController = (dungeon) => ({
+ getActive: (state) => hasDungeonPrize(state, dungeon) ? getDungeonPrize(state, dungeon) : null,
+ getDefault: (state) => getDungeonPrize(state, dungeon),
+ handlePrimary: nextString(`${dungeon.id}-prize`),
+ handleSecondary: previousString(`${dungeon.id}-prize`),
+});
+
+ToggleIcon.gtBossController = (which) => ({
+ getActive: (state) => getGTBoss(state, which),
+ getDefault: (state) => getGTBoss(state, which),
+ handlePrimary: nextString(`gt-${which}-boss`),
+ handleSecondary: previousString(`gt-${which}-boss`),
+});
+
+ToggleIcon.medallionController = {
+ getActive: highestActive,
+ getDefault: firstIcon,
+ handlePrimary: nextIcon,
+ handleSecondary: (state, setState, icons) => {
+ const mm = state['mm-medallion'];
+ const tr = state['tr-medallion'];
+ const isMM = mm === icons[0];
+ const isTR = tr === icons[0];
+ console.log({ mm, isMM, tr, isTR });
+ if (!isMM && !isTR) {
+ // empty: set as MM if mire is unset, else set as TR if TR is unset
+ if (!mm) {
+ setState(s => ({ ...s, 'mm-medallion': icons[0] }));
+ } else if (!tr) {
+ setState(s => ({ ...s, 'tr-medallion': icons[0] }));
+ }
+ } else if (isMM && !isTR) {
+ // MM: if TR is free, switch to TR, otherwise remove MM
+ if (!tr) {
+ setState(s => ({ ...s, 'mm-medallion': null, 'tr-medallion': icons[0] }));
+ } else {
+ setState(s => ({ ...s, 'mm-medallion': null }));
+ }
+ } else if (!isMM && isTR) {
+ // TR: if MM is free, switch to both, otherwise remove TR
+ if (!mm) {
+ setState(s => ({ ...s, 'mm-medallion': icons[0] }));
+ } else {
+ setState(s => ({ ...s, 'tr-medallion': null }));
+ }
+ } else {
+ // both: remove both
+ setState(s => ({ ...s, 'mm-medallion': null, 'tr-medallion': null }));
+ }
+ },
+};
+
+ToggleIcon.modulusController = ctrl => ({
+ getActive: (state, icons) => icons[(state[ctrl] || 0) % icons.length],
+ getDefault: firstIcon,
+ handlePrimary: (state, setState, icons) => {
+ setState(increment(ctrl, icons.length));
+ },
+ handleSecondary: (state, setState, icons) => {
+ setState(decrement(ctrl, icons.length));
+ },
+});
+
+ToggleIcon.nullController = {
+ getActive: () => null,
+ getDefault: firstIcon,
+ handlePrimary: doNothing,
+ handleSecondary: doNothing,
+};
+
+ToggleIcon.pinController = (pin, removePin) => ({
+ getActive: firstIcon,
+ getDefault: firstIcon,
+ handlePrimary: doNothing,
+ handleSecondary: () => removePin(pin),
+});
+
+ToggleIcon.simpleController = {
+ getActive: highestActive,
+ getDefault: firstIcon,
+ handlePrimary: nextIcon,
+ handleSecondary: previousIcon,
+};
+
+ToggleIcon.progressiveController = (master, min, max) => ({
+ getActive: (state, icons) => {
+ const count = Math.max(min, Math.min(max, state[master] || 0));
+ return count ? icons[count - 1] : null;
+ },
+ getDefault: firstIcon,
+ handlePrimary: (state, setState) => {
+ setState(increment(master, max, min));
+ },
+ handleSecondary: (state, setState) => {
+ setState(decrement(master, max, min));
+ },
+});
+
+ToggleIcon.propTypes = {
+ active: PropTypes.string,
+ className: PropTypes.string,
+ controller: PropTypes.shape({
+ handlePrimary: PropTypes.func,
+ handleSecondary: PropTypes.func,
+ }),
+ icons: PropTypes.arrayOf(PropTypes.string),
+ svg: PropTypes.bool,
+ title: PropTypes.string,
+ transform: PropTypes.string,
+};
+
+export default ToggleIcon;
+++ /dev/null
-import React from 'react';
-import { Button, Container, Form, Navbar } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import AutoTracking from './AutoTracking';
-import ConfigDialog from './ConfigDialog';
-import ToggleIcon from './ToggleIcon';
-import Icon from '../common/Icon';
-import ZeldaIcon from '../common/ZeldaIcon';
-import { getConfigValue } from '../../helpers/tracker';
-import { useTracker } from '../../hooks/tracker';
-
-const mapWild = {
- map: 'wildMap',
- compass: 'wildCompass',
- 'small-key': 'wildSmall',
- 'big-key': 'wildBig',
-};
-
-const Toolbar = () => {
- const [showConfigDialog, setShowConfigDialog] = React.useState(false);
- const { config, saveConfig } = useTracker();
- const { t } = useTranslation();
-
- const handleConfigChange = React.useCallback(({ target: { name, value } }) => {
- saveConfig({ [name]: value });
- }, [saveConfig]);
-
- const bossController = React.useMemo(() => ({
- getActive: (state, icons) => config.bossShuffle ? icons[0] : null,
- getDefault: (state, icons) => icons[0],
- handlePrimary: () => {
- saveConfig({ bossShuffle: !config.bossShuffle});
- },
- handleSecondary: () => null,
- }), [config, saveConfig]);
-
- const wildController = React.useMemo(() => ({
- getActive: (state, icons) => config[mapWild[icons[0]]] ? icons[0] : null,
- getDefault: (state, icons) => icons[0],
- handlePrimary: (state, setState, icons) => {
- const prop = mapWild[icons[0]];
- saveConfig({ [prop]: !config[prop] });
- },
- handleSecondary: () => null,
- }), [config, saveConfig]);
-
- const worldController = React.useMemo(() => ({
- getActive: (state, icons) => config.worldState === 'inverted' ? icons[1] : icons[0],
- getDefault: (state, icons) => icons[0],
- handlePrimary: () => {
- saveConfig({ worldState: config.worldState == 'inverted' ? 'open' : 'inverted' });
- },
- handleSecondary: () => null,
- }), [config, saveConfig]);
-
- return <Navbar bg="dark" className="tracker-toolbar" variant="dark">
- <Container fluid>
- <div className="button-bar">
- <Button
- className="me-3"
- onClick={() => setShowConfigDialog(true)}
- title={t('button.settings')}
- variant="outline-secondary"
- >
- <Icon.SETTINGS title="" />
- </Button>
- <ToggleIcon
- controller={wildController}
- icons={['map']}
- title={t('tracker.config.shuffleMap')}
- />
- <ToggleIcon
- controller={wildController}
- icons={['compass']}
- title={t('tracker.config.shuffleCompass')}
- />
- <ToggleIcon
- controller={wildController}
- icons={['small-key']}
- title={t('tracker.config.shuffleSmall')}
- />
- <ToggleIcon
- controller={wildController}
- icons={['big-key']}
- title={t('tracker.config.shuffleBig')}
- />
- <ToggleIcon
- className="ms-3"
- controller={bossController}
- icons={['armos']}
- title={t('tracker.config.bossShuffle')}
- />
- <ToggleIcon
- controller={worldController}
- icons={['link-head', 'bunny-head']}
- title={t('tracker.config.inverted')}
- />
- </div>
- <div>
- <Form.Group
- className="d-inline-flex align-items-center justify-content-between"
- controlId="tracker.gtCrystals"
- >
- <Form.Label className="me-1">
- <ZeldaIcon name="gt" title={t('tracker.config.gtCrystals')} />
- </Form.Label>
- <Form.Select
- className="w-auto bg-dark"
- name="gt-crystals"
- onChange={handleConfigChange}
- value={getConfigValue(config, 'gt-crystals', 7)}
- >
- {['?', 0, 1, 2, 3, 4, 5, 6, 7].map(n =>
- <option key={n} value={n}>
- {n}
- </option>
- )}
- </Form.Select>
- </Form.Group>
- <Form.Group
- className="d-inline-flex align-items-center justify-content-between"
- controlId="tracker.ganonCrystals"
- >
- <Form.Label className="me-1">
- <ZeldaIcon name="ganon" title={t('tracker.config.ganonCrystals')} />
- </Form.Label>
- <Form.Select
- className="w-auto bg-dark"
- name="ganon-crystals"
- onChange={handleConfigChange}
- value={getConfigValue(config, 'ganon-crystals', 7)}
- >
- {['?', 0, 1, 2, 3, 4, 5, 6, 7].map(n =>
- <option key={n} value={n}>
- {n}
- </option>
- )}
- </Form.Select>
- </Form.Group>
- <Form.Group
- className="d-inline-flex align-items-center justify-content-between"
- controlId="tracker.goal"
- >
- <Form.Label className="me-1">
- <ZeldaIcon name="triforce" title={t('tracker.config.goal')} />
- </Form.Label>
- <Form.Select
- className="w-auto bg-dark"
- name="goal"
- onChange={handleConfigChange}
- value={getConfigValue(config, 'goal', 'ganon')}
- >
- {['ganon', 'fast', 'ad', 'ped', 'trinity', 'thunt', 'ghunt'].map(n =>
- <option key={n} value={n}>
- {t(`tracker.config.goals.${n}`)}
- </option>
- )}
- </Form.Select>
- </Form.Group>
- </div>
- <div>
- <AutoTracking />
- </div>
- </Container>
- <ConfigDialog onHide={() => setShowConfigDialog(false)} show={showConfigDialog} />
- </Navbar>;
-};
-
-export default Toolbar;
--- /dev/null
+import React from 'react';
+import { Button, Container, Form, Navbar } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import AutoTracking from './AutoTracking';
+import ConfigDialog from './ConfigDialog';
+import ToggleIcon from './ToggleIcon';
+import Icon from '../common/Icon';
+import ZeldaIcon from '../common/ZeldaIcon';
+import { getConfigValue } from '../../helpers/tracker';
+import { useTracker } from '../../hooks/tracker';
+
+const mapWild = {
+ map: 'wildMap',
+ compass: 'wildCompass',
+ 'small-key': 'wildSmall',
+ 'big-key': 'wildBig',
+};
+
+const Toolbar = () => {
+ const [showConfigDialog, setShowConfigDialog] = React.useState(false);
+ const { config, saveConfig } = useTracker();
+ const { t } = useTranslation();
+
+ const handleConfigChange = React.useCallback(({ target: { name, value } }) => {
+ saveConfig({ [name]: value });
+ }, [saveConfig]);
+
+ const bossController = React.useMemo(() => ({
+ getActive: (state, icons) => config.bossShuffle ? icons[0] : null,
+ getDefault: (state, icons) => icons[0],
+ handlePrimary: () => {
+ saveConfig({ bossShuffle: !config.bossShuffle});
+ },
+ handleSecondary: () => null,
+ }), [config, saveConfig]);
+
+ const wildController = React.useMemo(() => ({
+ getActive: (state, icons) => config[mapWild[icons[0]]] ? icons[0] : null,
+ getDefault: (state, icons) => icons[0],
+ handlePrimary: (state, setState, icons) => {
+ const prop = mapWild[icons[0]];
+ saveConfig({ [prop]: !config[prop] });
+ },
+ handleSecondary: () => null,
+ }), [config, saveConfig]);
+
+ const worldController = React.useMemo(() => ({
+ getActive: (state, icons) => config.worldState === 'inverted' ? icons[1] : icons[0],
+ getDefault: (state, icons) => icons[0],
+ handlePrimary: () => {
+ saveConfig({ worldState: config.worldState == 'inverted' ? 'open' : 'inverted' });
+ },
+ handleSecondary: () => null,
+ }), [config, saveConfig]);
+
+ return <Navbar bg="dark" className="tracker-toolbar" variant="dark">
+ <Container fluid>
+ <div className="button-bar">
+ <Button
+ className="me-3"
+ onClick={() => setShowConfigDialog(true)}
+ title={t('button.settings')}
+ variant="outline-secondary"
+ >
+ <Icon.SETTINGS title="" />
+ </Button>
+ <ToggleIcon
+ controller={wildController}
+ icons={['map']}
+ title={t('tracker.config.shuffleMap')}
+ />
+ <ToggleIcon
+ controller={wildController}
+ icons={['compass']}
+ title={t('tracker.config.shuffleCompass')}
+ />
+ <ToggleIcon
+ controller={wildController}
+ icons={['small-key']}
+ title={t('tracker.config.shuffleSmall')}
+ />
+ <ToggleIcon
+ controller={wildController}
+ icons={['big-key']}
+ title={t('tracker.config.shuffleBig')}
+ />
+ <ToggleIcon
+ className="ms-3"
+ controller={bossController}
+ icons={['armos']}
+ title={t('tracker.config.bossShuffle')}
+ />
+ <ToggleIcon
+ controller={worldController}
+ icons={['link-head', 'bunny-head']}
+ title={t('tracker.config.inverted')}
+ />
+ </div>
+ <div>
+ <Form.Group
+ className="d-inline-flex align-items-center justify-content-between"
+ controlId="tracker.gtCrystals"
+ >
+ <Form.Label className="me-1">
+ <ZeldaIcon name="gt" title={t('tracker.config.gtCrystals')} />
+ </Form.Label>
+ <Form.Select
+ className="w-auto bg-dark"
+ name="gt-crystals"
+ onChange={handleConfigChange}
+ value={getConfigValue(config, 'gt-crystals', 7)}
+ >
+ {['?', 0, 1, 2, 3, 4, 5, 6, 7].map(n =>
+ <option key={n} value={n}>
+ {n}
+ </option>
+ )}
+ </Form.Select>
+ </Form.Group>
+ <Form.Group
+ className="d-inline-flex align-items-center justify-content-between"
+ controlId="tracker.ganonCrystals"
+ >
+ <Form.Label className="me-1">
+ <ZeldaIcon name="ganon" title={t('tracker.config.ganonCrystals')} />
+ </Form.Label>
+ <Form.Select
+ className="w-auto bg-dark"
+ name="ganon-crystals"
+ onChange={handleConfigChange}
+ value={getConfigValue(config, 'ganon-crystals', 7)}
+ >
+ {['?', 0, 1, 2, 3, 4, 5, 6, 7].map(n =>
+ <option key={n} value={n}>
+ {n}
+ </option>
+ )}
+ </Form.Select>
+ </Form.Group>
+ <Form.Group
+ className="d-inline-flex align-items-center justify-content-between"
+ controlId="tracker.goal"
+ >
+ <Form.Label className="me-1">
+ <ZeldaIcon name="triforce" title={t('tracker.config.goal')} />
+ </Form.Label>
+ <Form.Select
+ className="w-auto bg-dark"
+ name="goal"
+ onChange={handleConfigChange}
+ value={getConfigValue(config, 'goal', 'ganon')}
+ >
+ {['ganon', 'fast', 'ad', 'ped', 'trinity', 'thunt', 'ghunt'].map(n =>
+ <option key={n} value={n}>
+ {t(`tracker.config.goals.${n}`)}
+ </option>
+ )}
+ </Form.Select>
+ </Form.Group>
+ </div>
+ <div>
+ <AutoTracking />
+ </div>
+ </Container>
+ <ConfigDialog onHide={() => setShowConfigDialog(false)} show={showConfigDialog} />
+ </Navbar>;
+};
+
+export default Toolbar;
+++ /dev/null
-import React from 'react';
-
-import Canvas from './Canvas';
-import Toolbar from './Toolbar';
-
-const Tracker = () => {
- return <div className="tracker">
- <Toolbar />
- <Canvas />
- </div>;
-};
-
-export default Tracker;
--- /dev/null
+import React from 'react';
+
+import Canvas from './Canvas';
+import Toolbar from './Toolbar';
+
+const Tracker = () => {
+ return <div className="tracker">
+ <Toolbar />
+ <Canvas />
+ </div>;
+};
+
+export default Tracker;
+++ /dev/null
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import { formatTime, parseTime } from '../../helpers/Result';
-import yup from '../../schema/yup';
-
-const ChatSettingsForm = ({
- dirty,
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- isSubmitting,
- touched,
- values,
-}) => {
- const { t } = useTranslation();
-
- return <Form noValidate onSubmit={handleSubmit}>
- <Row>
- <Form.Group as={Col} md={6} controlId="chatSettings.wait_msgs_min">
- <Form.Label>{t('twitchBot.chatWaitMsgsMin')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.wait_msgs_min && errors.wait_msgs_min)}
- name="wait_msgs_min"
- min="1"
- onBlur={handleBlur}
- onChange={handleChange}
- type="number"
- value={values.wait_msgs_min || 1}
- />
- </Form.Group>
- <Form.Group as={Col} md={6} controlId="chatSettings.wait_msgs_max">
- <Form.Label>{t('twitchBot.chatWaitMsgsMax')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.wait_msgs_max && errors.wait_msgs_max)}
- name="wait_msgs_max"
- min="1"
- onBlur={handleBlur}
- onChange={handleChange}
- type="number"
- value={values.wait_msgs_max || 10}
- />
- </Form.Group>
- <Form.Group as={Col} md={6} controlId="chatSettings.wait_time_min">
- <Form.Label>{t('twitchBot.chatWaitTimeMin')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.wait_time_min && errors.wait_time_min)}
- name="wait_time_min"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.wait_time_min || '0'}
- />
- {touched.wait_time_min && errors.wait_time_min ?
- <Form.Control.Feedback type="invalid">
- {t(errors.wait_time_min)}
- </Form.Control.Feedback>
- :
- <Form.Text muted>
- {formatTime({ time: parseTime(values.wait_time_min)})}
- </Form.Text>
- }
- </Form.Group>
- <Form.Group as={Col} md={6} controlId="chatSettings.wait_time_max">
- <Form.Label>{t('twitchBot.chatWaitTimeMax')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.wait_time_max && errors.wait_time_max)}
- name="wait_time_max"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.wait_time_max || '15:00'}
- />
- {touched.wait_time_max && errors.wait_time_max ?
- <Form.Control.Feedback type="invalid">
- {t(errors.wait_time_max)}
- </Form.Control.Feedback>
- :
- <Form.Text muted>
- {formatTime({ time: parseTime(values.wait_time_max)})}
- </Form.Text>
- }
- </Form.Group>
- <Form.Group as={Col} md={6} controlId="chatSettings.language">
- <Form.Label>{t('twitchBot.language')}</Form.Label>
- <Form.Select
- isInvalid={!!(touched.language && errors.language)}
- name="language"
- onBlur={handleBlur}
- onChange={handleChange}
- value={values.language || 'de'}
- >
- {['de', 'en', 'es', 'fr'].map(lang =>
- <option key={lang} value={lang}>
- {t(`general.languages.${lang}`)}
- </option>
- )}
- </Form.Select>
- {touched.language && errors.language ?
- <Form.Control.Feedback type="invalid">
- {t(errors.language)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group as={Col} md={6} controlId="chatSettings.respond">
- <Form.Label>{t('twitchBot.respond')}</Form.Label>
- <Form.Select
- isInvalid={!!(touched.respond && errors.respond)}
- name="respond"
- onBlur={handleBlur}
- onChange={handleChange}
- value={values.respond || 'yes'}
- >
- {['yes', '50', 'no'].map(value =>
- <option key={value} value={value}>
- {t(`twitchBot.respondOptions.${value}`)}
- </option>
- )}
- </Form.Select>
- {touched.respond && errors.respond ?
- <Form.Control.Feedback type="invalid">
- {t(errors.respond)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group as={Col} md={6} controlId="chatSettings.min_age">
- <Form.Label>{t('twitchBot.chatMinAge')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.min_age && errors.min_age)}
- name="min_age"
- min="1"
- onBlur={handleBlur}
- onChange={handleChange}
- type="number"
- value={values.min_age || 1}
- />
- </Form.Group>
- <Form.Group as={Col} md={6} controlId="chatSettings.source">
- <Form.Label>{t('twitchBot.chatSource')}</Form.Label>
- <Form.Select
- isInvalid={!!(touched.source && errors.source)}
- name="source"
- onBlur={handleBlur}
- onChange={handleChange}
- value={values.source || 'any'}
- >
- {['any', 'cat', 'chan', 'catchan'].map(value =>
- <option key={value} value={value}>
- {t(`twitchBot.chatSources.${value}`)}
- </option>
- )}
- </Form.Select>
- {touched.respond && errors.respond ?
- <Form.Control.Feedback type="invalid">
- {t(errors.respond)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group as={Col} md={6} controlId="chatSettings.adlib">
- <Form.Label>{t('twitchBot.chatAdlibChance')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.adlib && errors.adlib)}
- name="adlib"
- min="0"
- max="100"
- onBlur={handleBlur}
- onChange={handleChange}
- type="number"
- value={Object.prototype.hasOwnProperty.call(values, 'adlib')
- ? values.adlib : 50}
- />
- </Form.Group>
- </Row>
- <div className="button-bar mt-3">
- <Button disabled={!dirty || isSubmitting} type="submit" variant="primary">
- {t('button.save')}
- </Button>
- </div>
- </Form>;
-};
-
-ChatSettingsForm.propTypes = {
- dirty: PropTypes.bool,
- errors: PropTypes.shape({
- adlib: PropTypes.string,
- language: PropTypes.string,
- min_age: PropTypes.string,
- respond: PropTypes.string,
- source: PropTypes.string,
- wait_msgs_max: PropTypes.string,
- wait_msgs_min: PropTypes.string,
- wait_time_min: PropTypes.string,
- wait_time_max: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- isSubmitting: PropTypes.bool,
- touched: PropTypes.shape({
- adlib: PropTypes.bool,
- language: PropTypes.bool,
- min_age: PropTypes.bool,
- respond: PropTypes.bool,
- source: PropTypes.bool,
- wait_msgs_max: PropTypes.bool,
- wait_msgs_min: PropTypes.bool,
- wait_time_min: PropTypes.bool,
- wait_time_max: PropTypes.bool,
- }),
- values: PropTypes.shape({
- adlib: PropTypes.number,
- language: PropTypes.string,
- min_age: PropTypes.number,
- respond: PropTypes.string,
- source: PropTypes.string,
- wait_msgs_max: PropTypes.number,
- wait_msgs_min: PropTypes.number,
- wait_time_min: PropTypes.string,
- wait_time_max: PropTypes.string,
- }),
-};
-
-export default withFormik({
- displayName: 'ChatSettingsForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { onSubmit } = actions.props;
- await onSubmit({
- ...values,
- wait_time_min: parseTime(values.wait_time_min) || 0,
- wait_time_max: parseTime(values.wait_time_max) || 0,
- });
- },
- mapPropsToValues: ({ channel }) => ({
- adlib: Object.prototype.hasOwnProperty.call(channel.chat_settings, 'adlib')
- ? channel.chat_settings.adlib : 50,
- language: channel.chat_settings.language || channel.languages[0] || 'de',
- min_age: channel.chat_settings.min_age || 1,
- respond: channel.chat_settings.respond || 'yes',
- source: channel.chat_settings.source || 'any',
- wait_msgs_min: channel.chat_settings.wait_msgs_min || 1,
- wait_msgs_max: channel.chat_settings.wait_msgs_max || 10,
- wait_time_min: channel.chat_settings.wait_time_min
- ? formatTime({ time: channel.chat_settings.wait_time_min }) : '0',
- wait_time_max: channel.chat_settings.wait_time_max
- ? formatTime({ time: channel.chat_settings.wait_time_max }) : '15:00',
- }),
- validationSchema: yup.object().shape({
- adlib: yup.number().min(0).max(100),
- language: yup.string(),
- min_age: yup.number().min(1),
- respond: yup.string(),
- wait_msgs_min: yup.number().min(1),
- wait_msgs_max: yup.number().min(1),
- wait_time_min: yup.string().time(),
- wait_time_max: yup.string().time(),
- }),
-})(ChatSettingsForm);
--- /dev/null
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import { formatTime, parseTime } from '../../helpers/Result';
+import yup from '../../schema/yup';
+
+const ChatSettingsForm = ({
+ dirty,
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ isSubmitting,
+ touched,
+ values,
+}) => {
+ const { t } = useTranslation();
+
+ return <Form noValidate onSubmit={handleSubmit}>
+ <Row>
+ <Form.Group as={Col} md={6} controlId="chatSettings.wait_msgs_min">
+ <Form.Label>{t('twitchBot.chatWaitMsgsMin')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.wait_msgs_min && errors.wait_msgs_min)}
+ name="wait_msgs_min"
+ min="1"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="number"
+ value={values.wait_msgs_min || 1}
+ />
+ </Form.Group>
+ <Form.Group as={Col} md={6} controlId="chatSettings.wait_msgs_max">
+ <Form.Label>{t('twitchBot.chatWaitMsgsMax')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.wait_msgs_max && errors.wait_msgs_max)}
+ name="wait_msgs_max"
+ min="1"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="number"
+ value={values.wait_msgs_max || 10}
+ />
+ </Form.Group>
+ <Form.Group as={Col} md={6} controlId="chatSettings.wait_time_min">
+ <Form.Label>{t('twitchBot.chatWaitTimeMin')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.wait_time_min && errors.wait_time_min)}
+ name="wait_time_min"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.wait_time_min || '0'}
+ />
+ {touched.wait_time_min && errors.wait_time_min ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.wait_time_min)}
+ </Form.Control.Feedback>
+ :
+ <Form.Text muted>
+ {formatTime({ time: parseTime(values.wait_time_min)})}
+ </Form.Text>
+ }
+ </Form.Group>
+ <Form.Group as={Col} md={6} controlId="chatSettings.wait_time_max">
+ <Form.Label>{t('twitchBot.chatWaitTimeMax')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.wait_time_max && errors.wait_time_max)}
+ name="wait_time_max"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.wait_time_max || '15:00'}
+ />
+ {touched.wait_time_max && errors.wait_time_max ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.wait_time_max)}
+ </Form.Control.Feedback>
+ :
+ <Form.Text muted>
+ {formatTime({ time: parseTime(values.wait_time_max)})}
+ </Form.Text>
+ }
+ </Form.Group>
+ <Form.Group as={Col} md={6} controlId="chatSettings.language">
+ <Form.Label>{t('twitchBot.language')}</Form.Label>
+ <Form.Select
+ isInvalid={!!(touched.language && errors.language)}
+ name="language"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.language || 'de'}
+ >
+ {['de', 'en', 'es', 'fr'].map(lang =>
+ <option key={lang} value={lang}>
+ {t(`general.languages.${lang}`)}
+ </option>
+ )}
+ </Form.Select>
+ {touched.language && errors.language ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.language)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group as={Col} md={6} controlId="chatSettings.respond">
+ <Form.Label>{t('twitchBot.respond')}</Form.Label>
+ <Form.Select
+ isInvalid={!!(touched.respond && errors.respond)}
+ name="respond"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.respond || 'yes'}
+ >
+ {['yes', '50', 'no'].map(value =>
+ <option key={value} value={value}>
+ {t(`twitchBot.respondOptions.${value}`)}
+ </option>
+ )}
+ </Form.Select>
+ {touched.respond && errors.respond ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.respond)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group as={Col} md={6} controlId="chatSettings.min_age">
+ <Form.Label>{t('twitchBot.chatMinAge')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.min_age && errors.min_age)}
+ name="min_age"
+ min="1"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="number"
+ value={values.min_age || 1}
+ />
+ </Form.Group>
+ <Form.Group as={Col} md={6} controlId="chatSettings.source">
+ <Form.Label>{t('twitchBot.chatSource')}</Form.Label>
+ <Form.Select
+ isInvalid={!!(touched.source && errors.source)}
+ name="source"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.source || 'any'}
+ >
+ {['any', 'cat', 'chan', 'catchan'].map(value =>
+ <option key={value} value={value}>
+ {t(`twitchBot.chatSources.${value}`)}
+ </option>
+ )}
+ </Form.Select>
+ {touched.respond && errors.respond ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.respond)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group as={Col} md={6} controlId="chatSettings.adlib">
+ <Form.Label>{t('twitchBot.chatAdlibChance')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.adlib && errors.adlib)}
+ name="adlib"
+ min="0"
+ max="100"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="number"
+ value={Object.prototype.hasOwnProperty.call(values, 'adlib')
+ ? values.adlib : 50}
+ />
+ </Form.Group>
+ </Row>
+ <div className="button-bar mt-3">
+ <Button disabled={!dirty || isSubmitting} type="submit" variant="primary">
+ {t('button.save')}
+ </Button>
+ </div>
+ </Form>;
+};
+
+ChatSettingsForm.propTypes = {
+ dirty: PropTypes.bool,
+ errors: PropTypes.shape({
+ adlib: PropTypes.string,
+ language: PropTypes.string,
+ min_age: PropTypes.string,
+ respond: PropTypes.string,
+ source: PropTypes.string,
+ wait_msgs_max: PropTypes.string,
+ wait_msgs_min: PropTypes.string,
+ wait_time_min: PropTypes.string,
+ wait_time_max: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ isSubmitting: PropTypes.bool,
+ touched: PropTypes.shape({
+ adlib: PropTypes.bool,
+ language: PropTypes.bool,
+ min_age: PropTypes.bool,
+ respond: PropTypes.bool,
+ source: PropTypes.bool,
+ wait_msgs_max: PropTypes.bool,
+ wait_msgs_min: PropTypes.bool,
+ wait_time_min: PropTypes.bool,
+ wait_time_max: PropTypes.bool,
+ }),
+ values: PropTypes.shape({
+ adlib: PropTypes.number,
+ language: PropTypes.string,
+ min_age: PropTypes.number,
+ respond: PropTypes.string,
+ source: PropTypes.string,
+ wait_msgs_max: PropTypes.number,
+ wait_msgs_min: PropTypes.number,
+ wait_time_min: PropTypes.string,
+ wait_time_max: PropTypes.string,
+ }),
+};
+
+export default withFormik({
+ displayName: 'ChatSettingsForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { onSubmit } = actions.props;
+ await onSubmit({
+ ...values,
+ wait_time_min: parseTime(values.wait_time_min) || 0,
+ wait_time_max: parseTime(values.wait_time_max) || 0,
+ });
+ },
+ mapPropsToValues: ({ channel }) => ({
+ adlib: Object.prototype.hasOwnProperty.call(channel.chat_settings, 'adlib')
+ ? channel.chat_settings.adlib : 50,
+ language: channel.chat_settings.language || channel.languages[0] || 'de',
+ min_age: channel.chat_settings.min_age || 1,
+ respond: channel.chat_settings.respond || 'yes',
+ source: channel.chat_settings.source || 'any',
+ wait_msgs_min: channel.chat_settings.wait_msgs_min || 1,
+ wait_msgs_max: channel.chat_settings.wait_msgs_max || 10,
+ wait_time_min: channel.chat_settings.wait_time_min
+ ? formatTime({ time: channel.chat_settings.wait_time_min }) : '0',
+ wait_time_max: channel.chat_settings.wait_time_max
+ ? formatTime({ time: channel.chat_settings.wait_time_max }) : '15:00',
+ }),
+ validationSchema: yup.object().shape({
+ adlib: yup.number().min(0).max(100),
+ language: yup.string(),
+ min_age: yup.number().min(1),
+ respond: yup.string(),
+ wait_msgs_min: yup.number().min(1),
+ wait_msgs_max: yup.number().min(1),
+ wait_time_min: yup.string().time(),
+ wait_time_max: yup.string().time(),
+ }),
+})(ChatSettingsForm);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Icon from '../common/Icon';
-
-const Command = ({
- name,
- onEditCommand,
- onRemoveCommand,
- settings,
-}) => {
- const { t } = useTranslation();
-
- const type = (settings && settings.command) || 'none';
-
- return <tr>
- <td>{`!${name}`}</td>
- <td>{t(`twitchBot.commandRestrictions.${(settings && settings.restrict) || 'none'}`)}</td>
- <td>{t(`twitchBot.commandTypes.${type}`)}</td>
- <td className="text-end">
- <div className="button-bar">
- {onEditCommand ?
- <Button
- onClick={() => onEditCommand(name, settings)}
- title={t('button.edit')}
- variant="outline-secondary"
- >
- <Icon.EDIT title="" />
- </Button>
- : null}
- {onRemoveCommand ?
- <Button
- onClick={() => onRemoveCommand(name)}
- title={t('button.remove')}
- variant="outline-danger"
- >
- <Icon.REMOVE title="" />
- </Button>
- : null}
- </div>
- </td>
- </tr>;
-};
-
-Command.propTypes = {
- name: PropTypes.string,
- onEditCommand: PropTypes.func,
- onRemoveCommand: PropTypes.func,
- settings: PropTypes.shape({
- command: PropTypes.string,
- restrict: PropTypes.string,
- }),
-};
-
-export default Command;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Icon from '../common/Icon';
+
+const Command = ({
+ name,
+ onEditCommand,
+ onRemoveCommand,
+ settings,
+}) => {
+ const { t } = useTranslation();
+
+ const type = (settings && settings.command) || 'none';
+
+ return <tr>
+ <td>{`!${name}`}</td>
+ <td>{t(`twitchBot.commandRestrictions.${(settings && settings.restrict) || 'none'}`)}</td>
+ <td>{t(`twitchBot.commandTypes.${type}`)}</td>
+ <td className="text-end">
+ <div className="button-bar">
+ {onEditCommand ?
+ <Button
+ onClick={() => onEditCommand(name, settings)}
+ title={t('button.edit')}
+ variant="outline-secondary"
+ >
+ <Icon.EDIT title="" />
+ </Button>
+ : null}
+ {onRemoveCommand ?
+ <Button
+ onClick={() => onRemoveCommand(name)}
+ title={t('button.remove')}
+ variant="outline-danger"
+ >
+ <Icon.REMOVE title="" />
+ </Button>
+ : null}
+ </div>
+ </td>
+ </tr>;
+};
+
+Command.propTypes = {
+ name: PropTypes.string,
+ onEditCommand: PropTypes.func,
+ onRemoveCommand: PropTypes.func,
+ settings: PropTypes.shape({
+ command: PropTypes.string,
+ restrict: PropTypes.string,
+ }),
+};
+
+export default Command;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Modal } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import CommandForm from './CommandForm';
-
-const CommandDialog = ({
- name,
- onHide,
- onSubmit,
- settings,
- show,
-}) => {
- const { t } = useTranslation();
-
- return <Modal className="report-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {t(name ? 'twitchBot.commandDialog' : 'twitchBot.addCommand')}
- </Modal.Title>
- </Modal.Header>
- <CommandForm
- name={name}
- onCancel={onHide}
- onSubmit={onSubmit}
- settings={settings}
- />
- </Modal>;
-};
-
-CommandDialog.propTypes = {
- name: PropTypes.string,
- onHide: PropTypes.func,
- onSubmit: PropTypes.func,
- settings: PropTypes.shape({
- }),
- show: PropTypes.bool,
-};
-
-export default CommandDialog;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import CommandForm from './CommandForm';
+
+const CommandDialog = ({
+ name,
+ onHide,
+ onSubmit,
+ settings,
+ show,
+}) => {
+ const { t } = useTranslation();
+
+ return <Modal className="report-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {t(name ? 'twitchBot.commandDialog' : 'twitchBot.addCommand')}
+ </Modal.Title>
+ </Modal.Header>
+ <CommandForm
+ name={name}
+ onCancel={onHide}
+ onSubmit={onSubmit}
+ settings={settings}
+ />
+ </Modal>;
+};
+
+CommandDialog.propTypes = {
+ name: PropTypes.string,
+ onHide: PropTypes.func,
+ onSubmit: PropTypes.func,
+ settings: PropTypes.shape({
+ }),
+ show: PropTypes.bool,
+};
+
+export default CommandDialog;
+++ /dev/null
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Form, Modal } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
-import yup from '../../schema/yup';
-
-const CommandForm = ({
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- name,
- onCancel,
- touched,
- values,
-}) => {
- const { t } = useTranslation();
-
- const COMMANDS = [
- 'none',
- 'runner',
- 'crew',
- 'guessing-start',
- 'guessing-stop',
- 'guessing-solve',
- 'guessing-cancel',
- 'guessing-leaderboard',
- ];
- const RESTRICTIONS = [
- 'none',
- 'mod',
- 'owner',
- ];
-
- return <Form noValidate onSubmit={handleSubmit}>
- <Modal.Body>
- <Form.Group controlId="command.name">
- <Form.Label>{t('twitchBot.commandName')}</Form.Label>
- <Form.Control
- disabled={!!name}
- isInvalid={!!(touched.name && errors.name)}
- name="name"
- onBlur={handleBlur}
- onChange={handleChange}
- plaintext={!!name}
- readOnly={!!name}
- type="text"
- value={values.name || ''}
- />
- {touched.name && errors.name ?
- <Form.Control.Feedback type="invalid">
- {t(errors.name)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group controlId="command.restrict">
- <Form.Label>{t('twitchBot.commandRestriction')}</Form.Label>
- <Form.Select
- isInvalid={!!(touched.restrict && errors.restrict)}
- name="restrict"
- onBlur={handleBlur}
- onChange={handleChange}
- value={values.restrict || 'none'}
- >
- {RESTRICTIONS.map(r =>
- <option key={r} value={r}>
- {t(`twitchBot.commandRestrictions.${r}`)}
- </option>
- )}
- </Form.Select>
- {touched.restrict && errors.restrict ?
- <Form.Control.Feedback type="invalid">
- {t(errors.restrict)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group controlId="command.command">
- <Form.Label>{t('twitchBot.commandType')}</Form.Label>
- <Form.Select
- isInvalid={!!(touched.command && errors.command)}
- name="command"
- onBlur={handleBlur}
- onChange={handleChange}
- value={values.command || 'none'}
- >
- {COMMANDS.map(c =>
- <option key={c} value={c}>
- {t(`twitchBot.commandTypes.${c}`)}
- </option>
- )}
- </Form.Select>
- {touched.command && errors.command ?
- <Form.Control.Feedback type="invalid">
- {t(errors.command)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Modal.Body>
- <Modal.Footer>
- {onCancel ?
- <Button onClick={onCancel} variant="secondary">
- {t('button.cancel')}
- </Button>
- : null}
- <Button type="submit" variant="primary">
- {t('button.save')}
- </Button>
- </Modal.Footer>
- </Form>;
-};
-
-CommandForm.propTypes = {
- errors: PropTypes.shape({
- command: PropTypes.string,
- name: PropTypes.string,
- restrict: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- name: PropTypes.string,
- onCancel: PropTypes.func,
- touched: PropTypes.shape({
- command: PropTypes.bool,
- name: PropTypes.bool,
- restrict: PropTypes.bool,
- }),
- values: PropTypes.shape({
- command: PropTypes.string,
- name: PropTypes.string,
- restrict: PropTypes.string,
- }),
-};
-
-export default withFormik({
- displayName: 'CommandForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { setErrors } = actions;
- const { onSubmit } = actions.props;
- try {
- await onSubmit(values);
- } catch (e) {
- if (e.response && e.response.data && e.response.data.errors) {
- setErrors(laravelErrorsToFormik(e.response.data.errors));
- }
- }
- },
- mapPropsToValues: ({ name, settings }) => {
- return {
- command: (settings && settings.command) || 'none',
- name: name || '',
- restrict: (settings && settings.restrict) || 'none',
- };
- },
- validationSchema: yup.object().shape({
- command: yup.string(),
- name: yup.string().required(),
- restrict: yup.string(),
- }),
-})(CommandForm);
--- /dev/null
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Form, Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import yup from '../../schema/yup';
+
+const CommandForm = ({
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ name,
+ onCancel,
+ touched,
+ values,
+}) => {
+ const { t } = useTranslation();
+
+ const COMMANDS = [
+ 'none',
+ 'runner',
+ 'crew',
+ 'guessing-start',
+ 'guessing-stop',
+ 'guessing-solve',
+ 'guessing-cancel',
+ 'guessing-leaderboard',
+ ];
+ const RESTRICTIONS = [
+ 'none',
+ 'mod',
+ 'owner',
+ ];
+
+ return <Form noValidate onSubmit={handleSubmit}>
+ <Modal.Body>
+ <Form.Group controlId="command.name">
+ <Form.Label>{t('twitchBot.commandName')}</Form.Label>
+ <Form.Control
+ disabled={!!name}
+ isInvalid={!!(touched.name && errors.name)}
+ name="name"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ plaintext={!!name}
+ readOnly={!!name}
+ type="text"
+ value={values.name || ''}
+ />
+ {touched.name && errors.name ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.name)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group controlId="command.restrict">
+ <Form.Label>{t('twitchBot.commandRestriction')}</Form.Label>
+ <Form.Select
+ isInvalid={!!(touched.restrict && errors.restrict)}
+ name="restrict"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.restrict || 'none'}
+ >
+ {RESTRICTIONS.map(r =>
+ <option key={r} value={r}>
+ {t(`twitchBot.commandRestrictions.${r}`)}
+ </option>
+ )}
+ </Form.Select>
+ {touched.restrict && errors.restrict ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.restrict)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group controlId="command.command">
+ <Form.Label>{t('twitchBot.commandType')}</Form.Label>
+ <Form.Select
+ isInvalid={!!(touched.command && errors.command)}
+ name="command"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.command || 'none'}
+ >
+ {COMMANDS.map(c =>
+ <option key={c} value={c}>
+ {t(`twitchBot.commandTypes.${c}`)}
+ </option>
+ )}
+ </Form.Select>
+ {touched.command && errors.command ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.command)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Modal.Body>
+ <Modal.Footer>
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {t('button.cancel')}
+ </Button>
+ : null}
+ <Button type="submit" variant="primary">
+ {t('button.save')}
+ </Button>
+ </Modal.Footer>
+ </Form>;
+};
+
+CommandForm.propTypes = {
+ errors: PropTypes.shape({
+ command: PropTypes.string,
+ name: PropTypes.string,
+ restrict: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ name: PropTypes.string,
+ onCancel: PropTypes.func,
+ touched: PropTypes.shape({
+ command: PropTypes.bool,
+ name: PropTypes.bool,
+ restrict: PropTypes.bool,
+ }),
+ values: PropTypes.shape({
+ command: PropTypes.string,
+ name: PropTypes.string,
+ restrict: PropTypes.string,
+ }),
+};
+
+export default withFormik({
+ displayName: 'CommandForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { setErrors } = actions;
+ const { onSubmit } = actions.props;
+ try {
+ await onSubmit(values);
+ } catch (e) {
+ if (e.response && e.response.data && e.response.data.errors) {
+ setErrors(laravelErrorsToFormik(e.response.data.errors));
+ }
+ }
+ },
+ mapPropsToValues: ({ name, settings }) => {
+ return {
+ command: (settings && settings.command) || 'none',
+ name: name || '',
+ restrict: (settings && settings.restrict) || 'none',
+ };
+ },
+ validationSchema: yup.object().shape({
+ command: yup.string(),
+ name: yup.string().required(),
+ restrict: yup.string(),
+ }),
+})(CommandForm);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Table } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Command from './Command';
-
-const Commands = ({
- channel,
- onEditCommand,
- onRemoveCommand,
-}) => {
- const { t } = useTranslation();
-
- return channel.chat_commands ?
- <Table>
- <thead>
- <tr>
- <th>{t('twitchBot.commandName')}</th>
- <th>{t('twitchBot.commandRestriction')}</th>
- <th>{t('twitchBot.commandType')}</th>
- <th className="text-end">{t('general.actions')}</th>
- </tr>
- </thead>
- <tbody>
- {Object.entries(channel.chat_commands).map(([name, settings]) =>
- <Command
- key={name}
- name={name}
- onEditCommand={onEditCommand}
- onRemoveCommand={onRemoveCommand}
- settings={settings}
- />
- )}
- </tbody>
- </Table>
- : null;
-};
-
-Commands.propTypes = {
- channel: PropTypes.shape({
- chat_commands: PropTypes.shape({
- }),
- }),
- onEditCommand: PropTypes.func,
- onRemoveCommand: PropTypes.func,
-};
-
-export default Commands;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Table } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Command from './Command';
+
+const Commands = ({
+ channel,
+ onEditCommand,
+ onRemoveCommand,
+}) => {
+ const { t } = useTranslation();
+
+ return channel.chat_commands ?
+ <Table>
+ <thead>
+ <tr>
+ <th>{t('twitchBot.commandName')}</th>
+ <th>{t('twitchBot.commandRestriction')}</th>
+ <th>{t('twitchBot.commandType')}</th>
+ <th className="text-end">{t('general.actions')}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {Object.entries(channel.chat_commands).map(([name, settings]) =>
+ <Command
+ key={name}
+ name={name}
+ onEditCommand={onEditCommand}
+ onRemoveCommand={onRemoveCommand}
+ settings={settings}
+ />
+ )}
+ </tbody>
+ </Table>
+ : null;
+};
+
+Commands.propTypes = {
+ channel: PropTypes.shape({
+ chat_commands: PropTypes.shape({
+ }),
+ }),
+ onEditCommand: PropTypes.func,
+ onRemoveCommand: PropTypes.func,
+};
+
+export default Commands;
+++ /dev/null
-import axios from 'axios';
-import React from 'react';
-import { Alert, Button, Col, Form, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import ChatSettingsForm from './ChatSettingsForm';
-import CommandDialog from './CommandDialog';
-import Commands from './Commands';
-import GuessingSettingsForm from './GuessingSettingsForm';
-import ChatBotLog from '../chat-bot-logs/ChatBotLog';
-import ChannelSelect from '../common/ChannelSelect';
-import Icon from '../common/Icon';
-import ToggleSwitch from '../common/ToggleSwitch';
-
-const CHAT_CATEGORIES = [
- 'unclassified',
- 'hi',
- 'gl',
- 'gg',
- 'eyes',
- 'love',
- 'lol',
- 'yes',
- 'no',
- 'rage',
- 'sad',
- 'sweat',
- 'wtf',
- 'pog',
- 'hype',
- 'kappa',
- 'o7',
- 'question',
- 'thx',
-];
-
-const Controls = () => {
- const [channel, setChannel] = React.useState(null);
- const [chatText, setChatText] = React.useState('');
- const [editCommand, setEditCommand] = React.useState('');
- const [editCommandSettings, setEditCommandSettings] = React.useState({});
- const [showCommandDialog, setShowCommandDialog] = React.useState(false);
-
- const { t } = useTranslation();
-
- const chat = React.useCallback(async (text, bot_nick) => {
- try {
- await axios.post(`/api/channels/${channel.id}/chat`, {
- text,
- bot_nick,
- });
- toastr.success(t('twitchBot.chatSuccess'));
- } catch (e) {
- toastr.error(t('twitchBot.chatError'));
- }
- }, [channel, chatText, t]);
-
- const randomChat = React.useCallback(async (category) => {
- try {
- await axios.post(`/api/channels/${channel.id}/chat`, {
- bot_nick: 'horstiebot',
- category,
- });
- toastr.success(t('twitchBot.chatSuccess'));
- } catch (e) {
- toastr.error(t('twitchBot.chatError'));
- }
- }, [channel, chatText, t]);
-
- const adlibChat = React.useCallback(async () => {
- try {
- await axios.post(`/api/channels/${channel.id}/chat`, {
- bot_nick: 'horstiebot',
- adlib: true,
- });
- toastr.success(t('twitchBot.chatSuccess'));
- } catch (e) {
- toastr.error(t('twitchBot.chatError'));
- }
- }, [channel, chatText, t]);
-
- const join = React.useCallback(async (bot_nick) => {
- try {
- const rsp = await axios.post(`/api/channels/${channel.id}/join`, { bot_nick });
- setChannel(rsp.data);
- toastr.success(t('twitchBot.joinSuccess'));
- } catch (e) {
- toastr.error(t('twitchBot.joinError'));
- }
- }, [channel, t]);
-
- const part = React.useCallback(async (bot_nick) => {
- try {
- const rsp = await axios.post(`/api/channels/${channel.id}/part`, { bot_nick });
- setChannel(rsp.data);
- toastr.success(t('twitchBot.partSuccess'));
- } catch (e) {
- toastr.error(t('twitchBot.partError'));
- }
- }, [channel, t]);
-
- const saveChatSettings = React.useCallback(async (values) => {
- try {
- const rsp = await axios.post(`/api/channels/${channel.id}/chat-settings`, values);
- setChannel(rsp.data);
- toastr.success(t('twitchBot.saveSuccess'));
- } catch (e) {
- toastr.error(t('twitchBot.saveError'));
- }
- }, [channel, t]);
-
- const onAddCommand = React.useCallback(() => {
- setEditCommand('');
- setEditCommandSettings({});
- setShowCommandDialog(true);
- }, [channel]);
-
- const onEditCommand = React.useCallback((name, settings) => {
- setEditCommand(name);
- setEditCommandSettings(settings);
- setShowCommandDialog(true);
- }, [channel]);
-
- const onRemoveCommand = React.useCallback(async (name) => {
- try {
- const rsp = await axios.delete(`/api/channels/${channel.id}/commands/${name}`);
- setChannel(rsp.data);
- toastr.success(t('twitchBot.saveSuccess'));
- } catch (e) {
- toastr.error(t('twitchBot.saveError'));
- }
- }, [channel]);
-
- const saveCommand = React.useCallback(async (values) => {
- try {
- const rsp = await axios.put(
- `/api/channels/${channel.id}/commands/${values.name}`,
- values,
- );
- setChannel(rsp.data);
- setShowCommandDialog(false);
- setEditCommand('');
- setEditCommandSettings({});
- toastr.success(t('twitchBot.saveSuccess'));
- } catch (e) {
- toastr.error(t('twitchBot.saveError'));
- throw e;
- }
- }, [channel]);
-
- const saveGuessingGame = React.useCallback(async (values) => {
- try {
- const rsp = await axios.put(
- `/api/channels/${channel.id}/guessing-game/${values.name}`,
- values,
- );
- setChannel(rsp.data);
- toastr.success(t('twitchBot.saveSuccess'));
- } catch (e) {
- toastr.error(t('twitchBot.saveError'));
- throw e;
- }
- }, [channel]);
-
- return <>
- <Row className="mb-4">
- <Form.Group as={Col} md={6}>
- <Form.Label>{t('twitchBot.channel')}</Form.Label>
- <Form.Control
- as={ChannelSelect}
- autoSelect
- joinable
- manageable
- onChange={({ channel }) => { setChannel(channel); }}
- value={channel ? channel.id : ''}
- />
- </Form.Group>
- {channel ? <>
- <Form.Group as={Col} md={3}>
- <Form.Label>{t('twitchBot.joinApp')}</Form.Label>
- <div>
- <Form.Control
- as={ToggleSwitch}
- onChange={({ target: { value } }) => {
- if (value) {
- join('localhorsttv');
- } else {
- part('localhorsttv');
- }
- }}
- value={channel.join}
- />
- </div>
- </Form.Group>
- <Form.Group as={Col} md={3}>
- <Form.Label>{t('twitchBot.joinChat')}</Form.Label>
- <div>
- <Form.Control
- as={ToggleSwitch}
- onChange={({ target: { value } }) => {
- if (value) {
- join('horstiebot');
- } else {
- part('horstiebot');
- }
- }}
- value={channel.chat}
- />
- </div>
- </Form.Group>
- </> : null}
- </Row>
- {channel ?
- <Row>
- <Col className="mt-5" md={6}>
- <h3>{t('twitchBot.chat')}</h3>
- <Form.Group>
- <Form.Label>{t('twitchBot.chat')}</Form.Label>
- <Form.Control
- as="textarea"
- onChange={({ target: { value } }) => {
- setChatText(value);
- }}
- value={chatText}
- />
- <div className="button-bar">
- <Button
- className="mt-2"
- disabled={!chatText || !channel.join}
- onClick={() => {
- if (chatText) chat(chatText, 'localhorsttv');
- }}
- variant="twitch"
- >
- {t('twitchBot.sendApp')}
- </Button>
- <Button
- className="mt-2"
- disabled={!chatText || !channel.chat}
- onClick={() => {
- if (chatText) chat(chatText, 'horstiebot');
- }}
- variant="twitch"
- >
- {t('twitchBot.sendChat')}
- </Button>
- </div>
- </Form.Group>
- <h3 className="mt-3">{t('twitchBot.randomChat')}</h3>
- <div className="button-bar">
- {CHAT_CATEGORIES.map(category =>
- <Button
- key={category}
- onClick={() => { randomChat(category); }}
- variant="outline-secondary"
- >
- {t(`twitchBot.chatCategories.${category}`)}
- </Button>
- )}
- </div>
- <div className="mt-3">
- <Button
- onClick={() => { adlibChat(); }}
- title={t('twitchBot.adlibChatDesc')}
- variant="outline-secondary"
- >
- {t('twitchBot.adlibChat')}
- </Button>
- <p className="text-muted">{t('twitchBot.adlibChatNote')}</p>
- </div>
- </Col>
- <Col className="mt-5" md={6}>
- <div className="d-flex justify-content-between">
- <h3>{t('twitchBot.chatSettings')}</h3>
- <div className="button-bar">
- <ChatBotLog id={channel.id} />
- </div>
- </div>
- <ChatSettingsForm channel={channel} onSubmit={saveChatSettings} />
- </Col>
- <Col className="mt-5" md={12}>
- <h3>{t('twitchBot.commands')}</h3>
- <Commands
- channel={channel}
- onEditCommand={onEditCommand}
- onRemoveCommand={onRemoveCommand}
- />
- <CommandDialog
- name={editCommand}
- onHide={() => {
- setShowCommandDialog(false);
- setEditCommand('');
- setEditCommandSettings({});
- }}
- onSubmit={saveCommand}
- settings={editCommandSettings}
- show={showCommandDialog}
- />
- <div>
- <Button onClick={onAddCommand} variant="primary">
- {t('twitchBot.addCommand')}
- </Button>
- </div>
- </Col>
- <Col className="mt-5" md={12}>
- <div className="d-flex align-items-end justify-content-between">
- <h3>{t('twitchBot.guessingGame.settings')}</h3>
- <div className="button-bar">
- {channel.access_key ?
- <Button
- href={`/guessing-game/monitor/${channel.access_key}`}
- target="_blank"
- title={t('button.browserSource')}
- variant="outline-secondary"
- >
- <Icon.BROWSER_SOURCE title="" />
- </Button>
- : null}
- <Button
- onClick={() => {
- window.open(
- `/guessing-game/controls/${channel.id}`,
- '',
- 'width=640,height=800,titlebar=0,menubar=0,toolbar=0',
- );
- }}
- title={t('twitchBot.guessingGame.popoutControls')}
- variant="outline-secondary"
- >
- <Icon.OPEN title="" />
- </Button>
- </div>
- </div>
- <GuessingSettingsForm
- name="gtbk"
- onSubmit={saveGuessingGame}
- settings={channel.guessing_settings?.gtbk || {}}
- />
- </Col>
- </Row>
- :
- <Alert variant="info">
- {t('twitchBot.selectChannel')}
- </Alert>
- }
- </>;
-};
-
-export default Controls;
--- /dev/null
+import axios from 'axios';
+import React from 'react';
+import { Alert, Button, Col, Form, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import ChatSettingsForm from './ChatSettingsForm';
+import CommandDialog from './CommandDialog';
+import Commands from './Commands';
+import GuessingSettingsForm from './GuessingSettingsForm';
+import ChatBotLog from '../chat-bot-logs/ChatBotLog';
+import ChannelSelect from '../common/ChannelSelect';
+import Icon from '../common/Icon';
+import ToggleSwitch from '../common/ToggleSwitch';
+
+const CHAT_CATEGORIES = [
+ 'unclassified',
+ 'hi',
+ 'gl',
+ 'gg',
+ 'eyes',
+ 'love',
+ 'lol',
+ 'yes',
+ 'no',
+ 'rage',
+ 'sad',
+ 'sweat',
+ 'wtf',
+ 'pog',
+ 'hype',
+ 'kappa',
+ 'o7',
+ 'question',
+ 'thx',
+];
+
+const Controls = () => {
+ const [channel, setChannel] = React.useState(null);
+ const [chatText, setChatText] = React.useState('');
+ const [editCommand, setEditCommand] = React.useState('');
+ const [editCommandSettings, setEditCommandSettings] = React.useState({});
+ const [showCommandDialog, setShowCommandDialog] = React.useState(false);
+
+ const { t } = useTranslation();
+
+ const chat = React.useCallback(async (text, bot_nick) => {
+ try {
+ await axios.post(`/api/channels/${channel.id}/chat`, {
+ text,
+ bot_nick,
+ });
+ toastr.success(t('twitchBot.chatSuccess'));
+ } catch (e) {
+ toastr.error(t('twitchBot.chatError'));
+ }
+ }, [channel, chatText, t]);
+
+ const randomChat = React.useCallback(async (category) => {
+ try {
+ await axios.post(`/api/channels/${channel.id}/chat`, {
+ bot_nick: 'horstiebot',
+ category,
+ });
+ toastr.success(t('twitchBot.chatSuccess'));
+ } catch (e) {
+ toastr.error(t('twitchBot.chatError'));
+ }
+ }, [channel, chatText, t]);
+
+ const adlibChat = React.useCallback(async () => {
+ try {
+ await axios.post(`/api/channels/${channel.id}/chat`, {
+ bot_nick: 'horstiebot',
+ adlib: true,
+ });
+ toastr.success(t('twitchBot.chatSuccess'));
+ } catch (e) {
+ toastr.error(t('twitchBot.chatError'));
+ }
+ }, [channel, chatText, t]);
+
+ const join = React.useCallback(async (bot_nick) => {
+ try {
+ const rsp = await axios.post(`/api/channels/${channel.id}/join`, { bot_nick });
+ setChannel(rsp.data);
+ toastr.success(t('twitchBot.joinSuccess'));
+ } catch (e) {
+ toastr.error(t('twitchBot.joinError'));
+ }
+ }, [channel, t]);
+
+ const part = React.useCallback(async (bot_nick) => {
+ try {
+ const rsp = await axios.post(`/api/channels/${channel.id}/part`, { bot_nick });
+ setChannel(rsp.data);
+ toastr.success(t('twitchBot.partSuccess'));
+ } catch (e) {
+ toastr.error(t('twitchBot.partError'));
+ }
+ }, [channel, t]);
+
+ const saveChatSettings = React.useCallback(async (values) => {
+ try {
+ const rsp = await axios.post(`/api/channels/${channel.id}/chat-settings`, values);
+ setChannel(rsp.data);
+ toastr.success(t('twitchBot.saveSuccess'));
+ } catch (e) {
+ toastr.error(t('twitchBot.saveError'));
+ }
+ }, [channel, t]);
+
+ const onAddCommand = React.useCallback(() => {
+ setEditCommand('');
+ setEditCommandSettings({});
+ setShowCommandDialog(true);
+ }, [channel]);
+
+ const onEditCommand = React.useCallback((name, settings) => {
+ setEditCommand(name);
+ setEditCommandSettings(settings);
+ setShowCommandDialog(true);
+ }, [channel]);
+
+ const onRemoveCommand = React.useCallback(async (name) => {
+ try {
+ const rsp = await axios.delete(`/api/channels/${channel.id}/commands/${name}`);
+ setChannel(rsp.data);
+ toastr.success(t('twitchBot.saveSuccess'));
+ } catch (e) {
+ toastr.error(t('twitchBot.saveError'));
+ }
+ }, [channel]);
+
+ const saveCommand = React.useCallback(async (values) => {
+ try {
+ const rsp = await axios.put(
+ `/api/channels/${channel.id}/commands/${values.name}`,
+ values,
+ );
+ setChannel(rsp.data);
+ setShowCommandDialog(false);
+ setEditCommand('');
+ setEditCommandSettings({});
+ toastr.success(t('twitchBot.saveSuccess'));
+ } catch (e) {
+ toastr.error(t('twitchBot.saveError'));
+ throw e;
+ }
+ }, [channel]);
+
+ const saveGuessingGame = React.useCallback(async (values) => {
+ try {
+ const rsp = await axios.put(
+ `/api/channels/${channel.id}/guessing-game/${values.name}`,
+ values,
+ );
+ setChannel(rsp.data);
+ toastr.success(t('twitchBot.saveSuccess'));
+ } catch (e) {
+ toastr.error(t('twitchBot.saveError'));
+ throw e;
+ }
+ }, [channel]);
+
+ return <>
+ <Row className="mb-4">
+ <Form.Group as={Col} md={6}>
+ <Form.Label>{t('twitchBot.channel')}</Form.Label>
+ <Form.Control
+ as={ChannelSelect}
+ autoSelect
+ joinable
+ manageable
+ onChange={({ channel }) => { setChannel(channel); }}
+ value={channel ? channel.id : ''}
+ />
+ </Form.Group>
+ {channel ? <>
+ <Form.Group as={Col} md={3}>
+ <Form.Label>{t('twitchBot.joinApp')}</Form.Label>
+ <div>
+ <Form.Control
+ as={ToggleSwitch}
+ onChange={({ target: { value } }) => {
+ if (value) {
+ join('localhorsttv');
+ } else {
+ part('localhorsttv');
+ }
+ }}
+ value={channel.join}
+ />
+ </div>
+ </Form.Group>
+ <Form.Group as={Col} md={3}>
+ <Form.Label>{t('twitchBot.joinChat')}</Form.Label>
+ <div>
+ <Form.Control
+ as={ToggleSwitch}
+ onChange={({ target: { value } }) => {
+ if (value) {
+ join('horstiebot');
+ } else {
+ part('horstiebot');
+ }
+ }}
+ value={channel.chat}
+ />
+ </div>
+ </Form.Group>
+ </> : null}
+ </Row>
+ {channel ?
+ <Row>
+ <Col className="mt-5" md={6}>
+ <h3>{t('twitchBot.chat')}</h3>
+ <Form.Group>
+ <Form.Label>{t('twitchBot.chat')}</Form.Label>
+ <Form.Control
+ as="textarea"
+ onChange={({ target: { value } }) => {
+ setChatText(value);
+ }}
+ value={chatText}
+ />
+ <div className="button-bar">
+ <Button
+ className="mt-2"
+ disabled={!chatText || !channel.join}
+ onClick={() => {
+ if (chatText) chat(chatText, 'localhorsttv');
+ }}
+ variant="twitch"
+ >
+ {t('twitchBot.sendApp')}
+ </Button>
+ <Button
+ className="mt-2"
+ disabled={!chatText || !channel.chat}
+ onClick={() => {
+ if (chatText) chat(chatText, 'horstiebot');
+ }}
+ variant="twitch"
+ >
+ {t('twitchBot.sendChat')}
+ </Button>
+ </div>
+ </Form.Group>
+ <h3 className="mt-3">{t('twitchBot.randomChat')}</h3>
+ <div className="button-bar">
+ {CHAT_CATEGORIES.map(category =>
+ <Button
+ key={category}
+ onClick={() => { randomChat(category); }}
+ variant="outline-secondary"
+ >
+ {t(`twitchBot.chatCategories.${category}`)}
+ </Button>
+ )}
+ </div>
+ <div className="mt-3">
+ <Button
+ onClick={() => { adlibChat(); }}
+ title={t('twitchBot.adlibChatDesc')}
+ variant="outline-secondary"
+ >
+ {t('twitchBot.adlibChat')}
+ </Button>
+ <p className="text-muted">{t('twitchBot.adlibChatNote')}</p>
+ </div>
+ </Col>
+ <Col className="mt-5" md={6}>
+ <div className="d-flex justify-content-between">
+ <h3>{t('twitchBot.chatSettings')}</h3>
+ <div className="button-bar">
+ <ChatBotLog id={channel.id} />
+ </div>
+ </div>
+ <ChatSettingsForm channel={channel} onSubmit={saveChatSettings} />
+ </Col>
+ <Col className="mt-5" md={12}>
+ <h3>{t('twitchBot.commands')}</h3>
+ <Commands
+ channel={channel}
+ onEditCommand={onEditCommand}
+ onRemoveCommand={onRemoveCommand}
+ />
+ <CommandDialog
+ name={editCommand}
+ onHide={() => {
+ setShowCommandDialog(false);
+ setEditCommand('');
+ setEditCommandSettings({});
+ }}
+ onSubmit={saveCommand}
+ settings={editCommandSettings}
+ show={showCommandDialog}
+ />
+ <div>
+ <Button onClick={onAddCommand} variant="primary">
+ {t('twitchBot.addCommand')}
+ </Button>
+ </div>
+ </Col>
+ <Col className="mt-5" md={12}>
+ <div className="d-flex align-items-end justify-content-between">
+ <h3>{t('twitchBot.guessingGame.settings')}</h3>
+ <div className="button-bar">
+ {channel.access_key ?
+ <Button
+ href={`/guessing-game/monitor/${channel.access_key}`}
+ target="_blank"
+ title={t('button.browserSource')}
+ variant="outline-secondary"
+ >
+ <Icon.BROWSER_SOURCE title="" />
+ </Button>
+ : null}
+ <Button
+ onClick={() => {
+ window.open(
+ `/guessing-game/controls/${channel.id}`,
+ '',
+ 'width=640,height=800,titlebar=0,menubar=0,toolbar=0',
+ );
+ }}
+ title={t('twitchBot.guessingGame.popoutControls')}
+ variant="outline-secondary"
+ >
+ <Icon.OPEN title="" />
+ </Button>
+ </div>
+ </div>
+ <GuessingSettingsForm
+ name="gtbk"
+ onSubmit={saveGuessingGame}
+ settings={channel.guessing_settings?.gtbk || {}}
+ />
+ </Col>
+ </Row>
+ :
+ <Alert variant="info">
+ {t('twitchBot.selectChannel')}
+ </Alert>
+ }
+ </>;
+};
+
+export default Controls;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import Icon from '../common/Icon';
-import ToggleSwitch from '../common/ToggleSwitch';
-import {
- compareGTBasementState,
- countGTBasementState,
- getGTBasementState,
- IN_GAME_MODES,
- INV_ADDR,
- RAM_ADDR,
- SRAM_ADDR,
- WRAM_ADDR,
-} from '../../helpers/alttp-ram';
-import { useSNES } from '../../hooks/snes';
-
-const GT_TYPES = [
- 0x02, // all dungeons
- 0x03, // defeat ganon
- 0x04, // fast ganon (the default and used for defeat ganon for some reason)
- 0x07, // crystals & bosses
- 0x08, // bosses
- 0x09, // all dungeons, no aga 1
- 0x0B, // completionist
-];
-
-const GT_ENTRANCE_ID = 55;
-
-const GuessingGameAutoTracking = ({ onSolve, onStart, onStop }) => {
- const [enabled, setEnabled] = React.useState(false);
- const controls = React.useRef({
- onSolve,
- onStart,
- onStop,
- });
-
- const [inGame, setInGame] = React.useState(false);
- const [seedType, setSeedType] = React.useState(0);
- const [gtCrystals, setGTCrystals] = React.useState(0);
- const [ganonType, setGanonType] = React.useState(0);
- const [freeItemMenu, setFreeItemMenu] = React.useState(0);
- const [pyramidOpen, setPyramidOpen] = React.useState(false);
-
- const [ownedCrystals, setOwnedCrystals] = React.useState(0);
- const [lastEntrance, setLastEntrance] = React.useState(0);
- const [hasEntered, setHasEntered] = React.useState(false);
- const [basement, setBasement] = React.useState({
- state: getGTBasementState(),
- last: '',
- count: 0,
- torch: 0,
- });
- const [hasBigKey, setHasBigKey] = React.useState(false);
-
- const {
- disable: disableSNES,
- enable: enableSNES,
- openSettings,
- sock,
- status,
- } = useSNES();
- const { t } = useTranslation();
-
- React.useEffect(() => {
- controls.current = {
- onSolve,
- onStart,
- onStop,
- };
- }, [onSolve, onStart, onStop]);
-
- const resetState = React.useCallback(() => {
- setInGame(false);
- setSeedType(0);
- setGTCrystals(0);
- setGanonType(0);
- setFreeItemMenu(0);
-
- setOwnedCrystals(0);
- setLastEntrance(0);
- setHasEntered(false);
- setBasement({
- state: getGTBasementState(),
- last: '',
- count: 0,
- torch: 0,
- });
- setHasBigKey(false);
- }, []);
-
- const enable = React.useCallback(() => {
- enableSNES();
- setEnabled(true);
- }, []);
-
- const disable = React.useCallback(() => {
- disableSNES();
- setEnabled(false);
- resetState();
- }, []);
-
- React.useEffect(() => {
- const savedSettings = localStorage.getItem('guessingGame.settings');
- if (savedSettings) {
- const settings = JSON.parse(savedSettings);
- if (settings.autoTrack) {
- enable();
- }
- }
- }, []);
-
- const saveSettings = React.useCallback((newSettings) => {
- const savedSettings = localStorage.getItem('guessingGame.settings');
- const settings = savedSettings
- ? { ...JSON.parse(savedSettings), ...newSettings }
- : newSettings;
- localStorage.setItem('guessingGame.settings', JSON.stringify(settings));
- }, []);
-
- const toggle = React.useCallback(() => {
- if (enabled) {
- disable();
- saveSettings({ autoTrack: false });
- } else {
- enable();
- saveSettings({ autoTrack: true });
- }
- }, [enabled]);
-
- // game mode timer
- React.useEffect(() => {
- if (enabled && !status.error && status.connected && status.device) {
- const checkInGame = () => {
- sock.current.readWRAM(WRAM_ADDR.GAME_MODE, 1, (data) => {
- setInGame(IN_GAME_MODES.includes(data[0]));
- });
- };
- checkInGame();
- const timer = setInterval(checkInGame, 5000);
- return () => {
- clearInterval(timer);
- };
- } else {
- setInGame(false);
- }
- }, [enabled && !status.error && status.connected && status.device]);
-
- // refresh static game information
- React.useEffect(() => {
- if (!inGame) return;
- sock.current.readBytes(RAM_ADDR.SEED_TYPE, 1, (data) => {
- setSeedType(data[0]);
- });
- sock.current.readBytes(RAM_ADDR.GT_CRYSTALS, 1, (data) => {
- setGTCrystals(data[0]);
- });
- sock.current.readBytes(RAM_ADDR.GANON_TYPE, 1, (data) => {
- setGanonType(data[0]);
- });
- sock.current.readBytes(RAM_ADDR.FREE_ITEM_MENU, 1, (data) => {
- setFreeItemMenu(data[0]);
- });
- sock.current.readBytes(RAM_ADDR.INIT_SRAM + SRAM_ADDR.PYRAMID_SCREEN, 1, (data) => {
- setPyramidOpen(!!(data[0] & 0x20));
- });
- }, [inGame, sock]);
-
- const applicable = React.useMemo(() => {
- return !seedType &&
- gtCrystals &&
- GT_TYPES.includes(ganonType) &&
- !pyramidOpen &&
- !(freeItemMenu & 0x02);
- }, [freeItemMenu, ganonType, gtCrystals, pyramidOpen, seedType]);
-
- // update crystals information
- React.useEffect(() => {
- if (!applicable || !inGame || hasBigKey) return;
- const updateCrystals = () => {
- const crAddress = WRAM_ADDR.SAVE_DATA + SRAM_ADDR.INV_START + INV_ADDR.CRYSTALS;
- sock.current.readWRAM(crAddress, 1, (data) => {
- let owned = 0;
- for (let i = 0; i < 7; ++i) {
- if (data[0] & Math.pow(2, i)) {
- ++owned;
- }
- }
- setOwnedCrystals(owned);
- });
- };
- // increase frequency for the last
- const timer = setInterval(updateCrystals, ownedCrystals === gtCrystals - 1 ? 1000 : 15000);
- return () => {
- clearInterval(timer);
- };
- }, [applicable, gtCrystals, hasBigKey, inGame, ownedCrystals, sock]);
-
- // start game once all required crystals have been acquired
- React.useEffect(() => {
- if (!applicable || hasBigKey || ownedCrystals !== gtCrystals || hasEntered) return;
- controls.current.onStart();
- const updateDungeon = () => {
- sock.current.readWRAM(WRAM_ADDR.CURRENT_DUNGEON, 2, (data) => {
- setLastEntrance(data[0] + (data[1] * 256));
- });
- };
- const timer = setInterval(updateDungeon, 1000);
- return () => {
- clearInterval(timer);
- };
- }, [applicable, controls, gtCrystals, hasBigKey, hasEntered, ownedCrystals]);
-
- // stop game when GT has been entered
- React.useEffect(() => {
- if (!applicable || hasBigKey || ownedCrystals !== gtCrystals) return;
- if (lastEntrance === GT_ENTRANCE_ID) {
- controls.current.onStop();
- setHasEntered(true);
- }
- }, [applicable, controls, gtCrystals, hasBigKey, lastEntrance, ownedCrystals]);
-
- // watch GT state
- React.useEffect(() => {
- if (!applicable || !hasEntered || hasBigKey) return;
- const updateGTState = () => {
- const roomDataStart = WRAM_ADDR.SAVE_DATA + SRAM_ADDR.ROOM_DATA_START;
- const roomDataSize = SRAM_ADDR.ROOM_DATA_END - SRAM_ADDR.ROOM_DATA_START;
- sock.current.readWRAM(roomDataStart, roomDataSize, (data) => {
- const gtState = getGTBasementState(data);
- const gtCount = countGTBasementState(gtState);
- setBasement(old => {
- const cmp = compareGTBasementState(old.state, gtState);
- if (cmp) {
- return {
- state: gtState,
- last: cmp,
- count: gtCount,
- torch: cmp === 'torchSeen' ? gtCount : old.torch,
- };
- }
- return old;
- });
- });
- };
- const timer = setInterval(updateGTState, 500);
- return () => {
- clearInterval(timer);
- };
- }, [applicable, hasBigKey, hasEntered]);
-
- React.useEffect(() => {
- if (!applicable) return;
- if (hasBigKey) {
- const solution = basement.last === 'torch' ? basement.torch : basement.count;
- controls.current.onSolve(solution);
- } else {
- const bkAddr = WRAM_ADDR.SAVE_DATA + SRAM_ADDR.INV_START + INV_ADDR.BIG_KEY;
- sock.current.readWRAM(bkAddr, 1, (data) => {
- setHasBigKey(!!(data[0] & 0x04));
- });
- }
- }, [applicable, basement, controls, hasBigKey]);
-
- const statusMsg = React.useMemo(() => {
- if (!enabled) {
- return 'disabled';
- }
- if (status.error) {
- return 'error';
- }
- if (!status.connected) {
- return 'disconnected';
- }
- if (!status.device) {
- return 'no-device';
- }
- if (!inGame) {
- return 'not-in-game';
- }
- if (!applicable) {
- return 'not-applicable';
- }
- return 'tracking';
- }, [applicable, enabled, inGame, status]);
-
- return <div>
- {['disconnected', 'error', 'no-device'].includes(statusMsg) ?
- <Icon.WARNING
- className="me-2 text-warning"
- size="lg"
- title={t(`autoTracking.statusMsg.${statusMsg}`, { device: status.device })}
- />
- : null}
- {['not-applicable', 'not-in-game'].includes(statusMsg) ?
- <Icon.INFO
- className="me-2 text-info"
- size="lg"
- title={t(`autoTracking.statusMsg.${statusMsg}`, { device: status.device })}
- />
- : null}
- <Button
- className="me-2"
- onClick={openSettings}
- size="sm"
- title={t('snes.settings')}
- variant="outline-secondary"
- >
- <Icon.SETTINGS title="" />
- </Button>
- <ToggleSwitch
- onChange={toggle}
- title={t('autoTracking.heading')}
- value={enabled}
- />
- </div>;
-};
-
-GuessingGameAutoTracking.propTypes = {
- onSolve: PropTypes.func,
- onStart: PropTypes.func,
- onStop: PropTypes.func,
-};
-
-export default GuessingGameAutoTracking;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Icon from '../common/Icon';
+import ToggleSwitch from '../common/ToggleSwitch';
+import {
+ compareGTBasementState,
+ countGTBasementState,
+ getGTBasementState,
+ IN_GAME_MODES,
+ INV_ADDR,
+ RAM_ADDR,
+ SRAM_ADDR,
+ WRAM_ADDR,
+} from '../../helpers/alttp-ram';
+import { useSNES } from '../../hooks/snes';
+
+const GT_TYPES = [
+ 0x02, // all dungeons
+ 0x03, // defeat ganon
+ 0x04, // fast ganon (the default and used for defeat ganon for some reason)
+ 0x07, // crystals & bosses
+ 0x08, // bosses
+ 0x09, // all dungeons, no aga 1
+ 0x0B, // completionist
+];
+
+const GT_ENTRANCE_ID = 55;
+
+const GuessingGameAutoTracking = ({ onSolve, onStart, onStop }) => {
+ const [enabled, setEnabled] = React.useState(false);
+ const controls = React.useRef({
+ onSolve,
+ onStart,
+ onStop,
+ });
+
+ const [inGame, setInGame] = React.useState(false);
+ const [seedType, setSeedType] = React.useState(0);
+ const [gtCrystals, setGTCrystals] = React.useState(0);
+ const [ganonType, setGanonType] = React.useState(0);
+ const [freeItemMenu, setFreeItemMenu] = React.useState(0);
+ const [pyramidOpen, setPyramidOpen] = React.useState(false);
+
+ const [ownedCrystals, setOwnedCrystals] = React.useState(0);
+ const [lastEntrance, setLastEntrance] = React.useState(0);
+ const [hasEntered, setHasEntered] = React.useState(false);
+ const [basement, setBasement] = React.useState({
+ state: getGTBasementState(),
+ last: '',
+ count: 0,
+ torch: 0,
+ });
+ const [hasBigKey, setHasBigKey] = React.useState(false);
+
+ const {
+ disable: disableSNES,
+ enable: enableSNES,
+ openSettings,
+ sock,
+ status,
+ } = useSNES();
+ const { t } = useTranslation();
+
+ React.useEffect(() => {
+ controls.current = {
+ onSolve,
+ onStart,
+ onStop,
+ };
+ }, [onSolve, onStart, onStop]);
+
+ const resetState = React.useCallback(() => {
+ setInGame(false);
+ setSeedType(0);
+ setGTCrystals(0);
+ setGanonType(0);
+ setFreeItemMenu(0);
+
+ setOwnedCrystals(0);
+ setLastEntrance(0);
+ setHasEntered(false);
+ setBasement({
+ state: getGTBasementState(),
+ last: '',
+ count: 0,
+ torch: 0,
+ });
+ setHasBigKey(false);
+ }, []);
+
+ const enable = React.useCallback(() => {
+ enableSNES();
+ setEnabled(true);
+ }, []);
+
+ const disable = React.useCallback(() => {
+ disableSNES();
+ setEnabled(false);
+ resetState();
+ }, []);
+
+ React.useEffect(() => {
+ const savedSettings = localStorage.getItem('guessingGame.settings');
+ if (savedSettings) {
+ const settings = JSON.parse(savedSettings);
+ if (settings.autoTrack) {
+ enable();
+ }
+ }
+ }, []);
+
+ const saveSettings = React.useCallback((newSettings) => {
+ const savedSettings = localStorage.getItem('guessingGame.settings');
+ const settings = savedSettings
+ ? { ...JSON.parse(savedSettings), ...newSettings }
+ : newSettings;
+ localStorage.setItem('guessingGame.settings', JSON.stringify(settings));
+ }, []);
+
+ const toggle = React.useCallback(() => {
+ if (enabled) {
+ disable();
+ saveSettings({ autoTrack: false });
+ } else {
+ enable();
+ saveSettings({ autoTrack: true });
+ }
+ }, [enabled]);
+
+ // game mode timer
+ React.useEffect(() => {
+ if (enabled && !status.error && status.connected && status.device) {
+ const checkInGame = () => {
+ sock.current.readWRAM(WRAM_ADDR.GAME_MODE, 1, (data) => {
+ setInGame(IN_GAME_MODES.includes(data[0]));
+ });
+ };
+ checkInGame();
+ const timer = setInterval(checkInGame, 5000);
+ return () => {
+ clearInterval(timer);
+ };
+ } else {
+ setInGame(false);
+ }
+ }, [enabled && !status.error && status.connected && status.device]);
+
+ // refresh static game information
+ React.useEffect(() => {
+ if (!inGame) return;
+ sock.current.readBytes(RAM_ADDR.SEED_TYPE, 1, (data) => {
+ setSeedType(data[0]);
+ });
+ sock.current.readBytes(RAM_ADDR.GT_CRYSTALS, 1, (data) => {
+ setGTCrystals(data[0]);
+ });
+ sock.current.readBytes(RAM_ADDR.GANON_TYPE, 1, (data) => {
+ setGanonType(data[0]);
+ });
+ sock.current.readBytes(RAM_ADDR.FREE_ITEM_MENU, 1, (data) => {
+ setFreeItemMenu(data[0]);
+ });
+ sock.current.readBytes(RAM_ADDR.INIT_SRAM + SRAM_ADDR.PYRAMID_SCREEN, 1, (data) => {
+ setPyramidOpen(!!(data[0] & 0x20));
+ });
+ }, [inGame, sock]);
+
+ const applicable = React.useMemo(() => {
+ return !seedType &&
+ gtCrystals &&
+ GT_TYPES.includes(ganonType) &&
+ !pyramidOpen &&
+ !(freeItemMenu & 0x02);
+ }, [freeItemMenu, ganonType, gtCrystals, pyramidOpen, seedType]);
+
+ // update crystals information
+ React.useEffect(() => {
+ if (!applicable || !inGame || hasBigKey) return;
+ const updateCrystals = () => {
+ const crAddress = WRAM_ADDR.SAVE_DATA + SRAM_ADDR.INV_START + INV_ADDR.CRYSTALS;
+ sock.current.readWRAM(crAddress, 1, (data) => {
+ let owned = 0;
+ for (let i = 0; i < 7; ++i) {
+ if (data[0] & Math.pow(2, i)) {
+ ++owned;
+ }
+ }
+ setOwnedCrystals(owned);
+ });
+ };
+ // increase frequency for the last
+ const timer = setInterval(updateCrystals, ownedCrystals === gtCrystals - 1 ? 1000 : 15000);
+ return () => {
+ clearInterval(timer);
+ };
+ }, [applicable, gtCrystals, hasBigKey, inGame, ownedCrystals, sock]);
+
+ // start game once all required crystals have been acquired
+ React.useEffect(() => {
+ if (!applicable || hasBigKey || ownedCrystals !== gtCrystals || hasEntered) return;
+ controls.current.onStart();
+ const updateDungeon = () => {
+ sock.current.readWRAM(WRAM_ADDR.CURRENT_DUNGEON, 2, (data) => {
+ setLastEntrance(data[0] + (data[1] * 256));
+ });
+ };
+ const timer = setInterval(updateDungeon, 1000);
+ return () => {
+ clearInterval(timer);
+ };
+ }, [applicable, controls, gtCrystals, hasBigKey, hasEntered, ownedCrystals]);
+
+ // stop game when GT has been entered
+ React.useEffect(() => {
+ if (!applicable || hasBigKey || ownedCrystals !== gtCrystals) return;
+ if (lastEntrance === GT_ENTRANCE_ID) {
+ controls.current.onStop();
+ setHasEntered(true);
+ }
+ }, [applicable, controls, gtCrystals, hasBigKey, lastEntrance, ownedCrystals]);
+
+ // watch GT state
+ React.useEffect(() => {
+ if (!applicable || !hasEntered || hasBigKey) return;
+ const updateGTState = () => {
+ const roomDataStart = WRAM_ADDR.SAVE_DATA + SRAM_ADDR.ROOM_DATA_START;
+ const roomDataSize = SRAM_ADDR.ROOM_DATA_END - SRAM_ADDR.ROOM_DATA_START;
+ sock.current.readWRAM(roomDataStart, roomDataSize, (data) => {
+ const gtState = getGTBasementState(data);
+ const gtCount = countGTBasementState(gtState);
+ setBasement(old => {
+ const cmp = compareGTBasementState(old.state, gtState);
+ if (cmp) {
+ return {
+ state: gtState,
+ last: cmp,
+ count: gtCount,
+ torch: cmp === 'torchSeen' ? gtCount : old.torch,
+ };
+ }
+ return old;
+ });
+ });
+ };
+ const timer = setInterval(updateGTState, 500);
+ return () => {
+ clearInterval(timer);
+ };
+ }, [applicable, hasBigKey, hasEntered]);
+
+ React.useEffect(() => {
+ if (!applicable) return;
+ if (hasBigKey) {
+ const solution = basement.last === 'torch' ? basement.torch : basement.count;
+ controls.current.onSolve(solution);
+ } else {
+ const bkAddr = WRAM_ADDR.SAVE_DATA + SRAM_ADDR.INV_START + INV_ADDR.BIG_KEY;
+ sock.current.readWRAM(bkAddr, 1, (data) => {
+ setHasBigKey(!!(data[0] & 0x04));
+ });
+ }
+ }, [applicable, basement, controls, hasBigKey]);
+
+ const statusMsg = React.useMemo(() => {
+ if (!enabled) {
+ return 'disabled';
+ }
+ if (status.error) {
+ return 'error';
+ }
+ if (!status.connected) {
+ return 'disconnected';
+ }
+ if (!status.device) {
+ return 'no-device';
+ }
+ if (!inGame) {
+ return 'not-in-game';
+ }
+ if (!applicable) {
+ return 'not-applicable';
+ }
+ return 'tracking';
+ }, [applicable, enabled, inGame, status]);
+
+ return <div>
+ {['disconnected', 'error', 'no-device'].includes(statusMsg) ?
+ <Icon.WARNING
+ className="me-2 text-warning"
+ size="lg"
+ title={t(`autoTracking.statusMsg.${statusMsg}`, { device: status.device })}
+ />
+ : null}
+ {['not-applicable', 'not-in-game'].includes(statusMsg) ?
+ <Icon.INFO
+ className="me-2 text-info"
+ size="lg"
+ title={t(`autoTracking.statusMsg.${statusMsg}`, { device: status.device })}
+ />
+ : null}
+ <Button
+ className="me-2"
+ onClick={openSettings}
+ size="sm"
+ title={t('snes.settings')}
+ variant="outline-secondary"
+ >
+ <Icon.SETTINGS title="" />
+ </Button>
+ <ToggleSwitch
+ onChange={toggle}
+ title={t('autoTracking.heading')}
+ value={enabled}
+ />
+ </div>;
+};
+
+GuessingGameAutoTracking.propTypes = {
+ onSolve: PropTypes.func,
+ onStart: PropTypes.func,
+ onStop: PropTypes.func,
+};
+
+export default GuessingGameAutoTracking;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import GuessingGameAutoTracking from './GuessingGameAutoTracking';
-import {
- hasActiveGuessing,
- isAcceptingGuesses,
-} from '../../helpers/Channel';
-
-const GuessingGameControls = ({
- channel,
- onCancel,
- onSolve,
- onStart,
- onStop,
-}) => {
- const { t } = useTranslation();
-
- const solutions = [
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
- 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
- ];
-
- return <div>
- <div className="d-flex align-items-center justify-content-between mt-3">
- <div className="button-bar">
- <Button
- onClick={onStart}
- variant={hasActiveGuessing(channel) ? 'success' : 'outline-success'}
- >
- {t('button.start')}
- </Button>
- <Button
- onClick={onStop}
- variant={
- hasActiveGuessing(channel) && isAcceptingGuesses(channel)
- ? 'danger' : 'outline-danger'
- }
- >
- {t('button.stop')}
- </Button>
- <Button
- className="ms-3"
- onClick={onCancel}
- variant={hasActiveGuessing(channel) ? 'secondary' : 'outline-secondary'}
- >
- {t('button.cancel')}
- </Button>
- </div>
- <GuessingGameAutoTracking
- onSolve={onSolve}
- onStart={onStart}
- onStop={onStop}
- />
- </div>
- {hasActiveGuessing(channel) ?
- <div className="bkgg-buttons d-grid gap-3 my-3">
- {solutions.map(solution =>
- <Button
- key={solution}
- onClick={() => onSolve(solution)}
- size="lg"
- variant="outline-secondary"
- >
- {solution}
- </Button>
- )}
- </div>
- : null}
- </div>;
-};
-
-GuessingGameControls.propTypes = {
- channel: PropTypes.shape({
- }),
- onCancel: PropTypes.func,
- onSolve: PropTypes.func,
- onStart: PropTypes.func,
- onStop: PropTypes.func,
-};
-
-export default GuessingGameControls;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import GuessingGameAutoTracking from './GuessingGameAutoTracking';
+import {
+ hasActiveGuessing,
+ isAcceptingGuesses,
+} from '../../helpers/Channel';
+
+const GuessingGameControls = ({
+ channel,
+ onCancel,
+ onSolve,
+ onStart,
+ onStop,
+}) => {
+ const { t } = useTranslation();
+
+ const solutions = [
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+ ];
+
+ return <div>
+ <div className="d-flex align-items-center justify-content-between mt-3">
+ <div className="button-bar">
+ <Button
+ onClick={onStart}
+ variant={hasActiveGuessing(channel) ? 'success' : 'outline-success'}
+ >
+ {t('button.start')}
+ </Button>
+ <Button
+ onClick={onStop}
+ variant={
+ hasActiveGuessing(channel) && isAcceptingGuesses(channel)
+ ? 'danger' : 'outline-danger'
+ }
+ >
+ {t('button.stop')}
+ </Button>
+ <Button
+ className="ms-3"
+ onClick={onCancel}
+ variant={hasActiveGuessing(channel) ? 'secondary' : 'outline-secondary'}
+ >
+ {t('button.cancel')}
+ </Button>
+ </div>
+ <GuessingGameAutoTracking
+ onSolve={onSolve}
+ onStart={onStart}
+ onStop={onStop}
+ />
+ </div>
+ {hasActiveGuessing(channel) ?
+ <div className="bkgg-buttons d-grid gap-3 my-3">
+ {solutions.map(solution =>
+ <Button
+ key={solution}
+ onClick={() => onSolve(solution)}
+ size="lg"
+ variant="outline-secondary"
+ >
+ {solution}
+ </Button>
+ )}
+ </div>
+ : null}
+ </div>;
+};
+
+GuessingGameControls.propTypes = {
+ channel: PropTypes.shape({
+ }),
+ onCancel: PropTypes.func,
+ onSolve: PropTypes.func,
+ onStart: PropTypes.func,
+ onStop: PropTypes.func,
+};
+
+export default GuessingGameControls;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Col, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-const GuessingGuess = ({ guess }) => {
- const { t } = useTranslation();
-
- return <div className="my-2 p-2 border rounded">
- <Row>
- <Col xs={6}>
- <div>{guess.uname}</div>
- <div className="text-muted">
- {t('twitchBot.guessingGame.guessTimestamp', {
- timestamp: new Date(guess.created_at),
- })}
- </div>
- </Col>
- <Col xs={6}>
- <div className="fs-3 text-end">{guess.guess}</div>
- </Col>
- </Row>
- </div>;
-};
-
-GuessingGuess.propTypes = {
- guess: PropTypes.shape({
- created_at: PropTypes.string,
- guess: PropTypes.string,
- uname: PropTypes.string,
- }),
-};
-
-export default GuessingGuess;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Col, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+const GuessingGuess = ({ guess }) => {
+ const { t } = useTranslation();
+
+ return <div className="my-2 p-2 border rounded">
+ <Row>
+ <Col xs={6}>
+ <div>{guess.uname}</div>
+ <div className="text-muted">
+ {t('twitchBot.guessingGame.guessTimestamp', {
+ timestamp: new Date(guess.created_at),
+ })}
+ </div>
+ </Col>
+ <Col xs={6}>
+ <div className="fs-3 text-end">{guess.guess}</div>
+ </Col>
+ </Row>
+ </div>;
+};
+
+GuessingGuess.propTypes = {
+ guess: PropTypes.shape({
+ created_at: PropTypes.string,
+ guess: PropTypes.string,
+ uname: PropTypes.string,
+ }),
+};
+
+export default GuessingGuess;
+++ /dev/null
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
-import i18n from '../../i18n';
-import yup from '../../schema/yup';
-
-const GuessingSettingsForm = ({
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- onCancel,
- touched,
- values,
-}) => {
- const { t } = useTranslation();
-
- return <Form noValidate onSubmit={handleSubmit}>
- <Row>
- <Form.Group as={Col} controlId="gg.points_exact_first" md={6}>
- <Form.Label>{t('twitchBot.guessingGame.pointsExactFirst')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.points_exact_first && errors.points_exact_first)}
- max="5"
- min="1"
- name="points_exact_first"
- onBlur={handleBlur}
- onChange={handleChange}
- step="1"
- type="number"
- value={values.points_exact_first || 0}
- />
- {touched.points_exact_first && errors.points_exact_first ?
- <Form.Control.Feedback type="invalid">
- {t(errors.points_exact_first)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group as={Col} controlId="gg.points_exact_other" md={6}>
- <Form.Label>{t('twitchBot.guessingGame.pointsExactOther')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.points_exact_other && errors.points_exact_other)}
- max="5"
- min="0"
- name="points_exact_other"
- onBlur={handleBlur}
- onChange={handleChange}
- step="1"
- type="number"
- value={values.points_exact_other || 0}
- />
- {touched.points_exact_other && errors.points_exact_other ?
- <Form.Control.Feedback type="invalid">
- {t(errors.points_exact_other)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group as={Col} controlId="gg.points_close_first" md={6}>
- <Form.Label>{t('twitchBot.guessingGame.pointsCloseFirst')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.points_close_first && errors.points_close_first)}
- max="5"
- min="0"
- name="points_close_first"
- onBlur={handleBlur}
- onChange={handleChange}
- step="0.5"
- type="number"
- value={values.points_close_first || 0}
- />
- {touched.points_close_first && errors.points_close_first ?
- <Form.Control.Feedback type="invalid">
- {t(errors.points_close_first)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group as={Col} controlId="gg.points_close_other" md={6}>
- <Form.Label>{t('twitchBot.guessingGame.pointsCloseOther')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.points_close_other && errors.points_close_other)}
- max="5"
- min="0"
- name="points_close_other"
- onBlur={handleBlur}
- onChange={handleChange}
- step="0.5"
- type="number"
- value={values.points_close_other || 0}
- />
- {touched.points_close_other && errors.points_close_other ?
- <Form.Control.Feedback type="invalid">
- {t(errors.points_close_other)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group as={Col} controlId="gg.points_close_max" md={6}>
- <Form.Label>{t('twitchBot.guessingGame.pointsCloseMax')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.points_close_max && errors.points_close_max)}
- min="0"
- name="points_close_max"
- onBlur={handleBlur}
- onChange={handleChange}
- step="1"
- type="number"
- value={values.points_close_max || 0}
- />
- {touched.points_close_max && errors.points_close_max ?
- <Form.Control.Feedback type="invalid">
- {t(errors.points_close_max)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group as={Col} controlId="gg.leaderboard_type" md={6}>
- <Form.Label>{t('twitchBot.guessingGame.leaderboardType')}</Form.Label>
- <Form.Select
- isInvalid={!!(touched.leaderboard_type && errors.leaderboard_type)}
- name="leaderboard_type"
- onBlur={handleBlur}
- onChange={handleChange}
- value={values.leaderboard_type || 'all'}
- >
- {['all', 'year', '365', 'month', '30'].map(type =>
- <option key={type} value={type}>
- {t(`twitchBot.guessingGame.leaderboardTypes.${type}`)}
- </option>
- )}
- </Form.Select>
- {touched.leaderboard_type && errors.leaderboard_type ?
- <Form.Control.Feedback type="invalid">
- {t(errors.leaderboard_type)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Row>
- <Form.Group controlId="gg.start_message">
- <Form.Label>{t('twitchBot.guessingGame.startMessage')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.start_message && errors.start_message)}
- name="start_message"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.start_message || ''}
- />
- {touched.start_message && errors.start_message ?
- <Form.Control.Feedback type="invalid">
- {t(errors.start_message)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group controlId="gg.stop_message">
- <Form.Label>{t('twitchBot.guessingGame.stopMessage')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.stop_message && errors.stop_message)}
- name="stop_message"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.stop_message || ''}
- />
- {touched.stop_message && errors.stop_message ?
- <Form.Control.Feedback type="invalid">
- {t(errors.stop_message)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group controlId="gg.winners_message">
- <Form.Label>{t('twitchBot.guessingGame.winnersMessage')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.winners_message && errors.winners_message)}
- name="winners_message"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.winners_message || ''}
- />
- {touched.winners_message && errors.winners_message ?
- <Form.Control.Feedback type="invalid">
- {t(errors.winners_message)}
- </Form.Control.Feedback>
- :
- <Form.Text muted>
- {t('twitchBot.guessingGame.winnersMessageHint')}
- </Form.Text>
- }
- </Form.Group>
- <Form.Group controlId="gg.close_winners_message">
- <Form.Label>{t('twitchBot.guessingGame.closeWinnersMessage')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.close_winners_message && errors.close_winners_message)}
- name="close_winners_message"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.close_winners_message || ''}
- />
- {touched.close_winners_message && errors.close_winners_message ?
- <Form.Control.Feedback type="invalid">
- {t(errors.close_winners_message)}
- </Form.Control.Feedback>
- :
- <Form.Text muted>
- {t('twitchBot.guessingGame.closeWinnersMessageHint')}
- </Form.Text>
- }
- </Form.Group>
- <Form.Group controlId="gg.no_winners_message">
- <Form.Label>{t('twitchBot.guessingGame.noWinnersMessage')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.no_winners_message && errors.no_winners_message)}
- name="no_winners_message"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.no_winners_message || ''}
- />
- {touched.no_winners_message && errors.no_winners_message ?
- <Form.Control.Feedback type="invalid">
- {t(errors.no_winners_message)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group controlId="gg.cancel_message">
- <Form.Label>{t('twitchBot.guessingGame.cancelMessage')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.cancel_message && errors.cancel_message)}
- name="cancel_message"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.cancel_message || ''}
- />
- {touched.cancel_message && errors.cancel_message ?
- <Form.Control.Feedback type="invalid">
- {t(errors.cancel_message)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group controlId="gg.invalid_solution_message">
- <Form.Label>{t('twitchBot.guessingGame.invalidSolutionMessage')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.invalid_solution_message && errors.invalid_solution_message)}
- name="invalid_solution_message"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.invalid_solution_message || ''}
- />
- {touched.invalid_solution_message && errors.invalid_solution_message ?
- <Form.Control.Feedback type="invalid">
- {t(errors.invalid_solution_message)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group controlId="gg.active_message">
- <Form.Label>{t('twitchBot.guessingGame.activeMessage')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.active_message && errors.active_message)}
- name="active_message"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.active_message || ''}
- />
- {touched.active_message && errors.active_message ?
- <Form.Control.Feedback type="invalid">
- {t(errors.active_message)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <Form.Group controlId="gg.not_active_message">
- <Form.Label>{t('twitchBot.guessingGame.notActiveMessage')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.not_active_message && errors.not_active_message)}
- name="not_active_message"
- onBlur={handleBlur}
- onChange={handleChange}
- type="text"
- value={values.not_active_message || ''}
- />
- {touched.not_active_message && errors.not_active_message ?
- <Form.Control.Feedback type="invalid">
- {t(errors.not_active_message)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- <div className="button-bar mt-3">
- {onCancel ?
- <Button onClick={onCancel} variant="secondary">
- {t('button.cancel')}
- </Button>
- : null}
- <Button type="submit" variant="primary">
- {t('button.save')}
- </Button>
- </div>
- </Form>;
-};
-
-GuessingSettingsForm.propTypes = {
- errors: PropTypes.shape({
- active_message: PropTypes.string,
- cancel_message: PropTypes.string,
- close_winners_message: PropTypes.string,
- invalid_solution_message: PropTypes.string,
- leaderboard_type: PropTypes.string,
- name: PropTypes.string,
- no_winners_message: PropTypes.string,
- not_active_message: PropTypes.string,
- points_close_first: PropTypes.string,
- points_close_max: PropTypes.string,
- points_close_other: PropTypes.string,
- points_exact_first: PropTypes.string,
- points_exact_other: PropTypes.string,
- start_message: PropTypes.string,
- stop_message: PropTypes.string,
- winners_message: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- name: PropTypes.string,
- onCancel: PropTypes.func,
- touched: PropTypes.shape({
- active_message: PropTypes.bool,
- cancel_message: PropTypes.bool,
- close_winners_message: PropTypes.bool,
- invalid_solution_message: PropTypes.bool,
- leaderboard_type: PropTypes.bool,
- name: PropTypes.bool,
- no_winners_message: PropTypes.bool,
- not_active_message: PropTypes.bool,
- points_close_first: PropTypes.bool,
- points_close_max: PropTypes.bool,
- points_close_other: PropTypes.bool,
- points_exact_first: PropTypes.bool,
- points_exact_other: PropTypes.bool,
- start_message: PropTypes.bool,
- stop_message: PropTypes.bool,
- winners_message: PropTypes.bool,
- }),
- values: PropTypes.shape({
- active_message: PropTypes.string,
- cancel_message: PropTypes.string,
- close_winners_message: PropTypes.string,
- invalid_solution_message: PropTypes.string,
- leaderboard_type: PropTypes.string,
- name: PropTypes.string,
- no_winners_message: PropTypes.string,
- not_active_message: PropTypes.string,
- points_close_first: PropTypes.number,
- points_close_max: PropTypes.number,
- points_close_other: PropTypes.number,
- points_exact_first: PropTypes.number,
- points_exact_other: PropTypes.number,
- start_message: PropTypes.string,
- stop_message: PropTypes.string,
- winners_message: PropTypes.string,
- }),
-};
-
-export default withFormik({
- displayName: 'GuessingSettingsForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { setErrors } = actions;
- const { onSubmit } = actions.props;
- try {
- await onSubmit(values);
- } catch (e) {
- if (e.response && e.response.data && e.response.data.errors) {
- setErrors(laravelErrorsToFormik(e.response.data.errors));
- }
- }
- },
- mapPropsToValues: ({ name, settings }) => {
- const getNumericValue = (s, n, d) => s && Object.prototype.hasOwnProperty.call(s, n)
- ? s[n] : d;
- const getStringValue = (s, n, d) => s && Object.prototype.hasOwnProperty.call(s, n)
- ? s[n] : i18n.t(`twitchBot.guessingGame.default${d}`);
- return {
- active_message: getStringValue(settings, 'active_message', 'ActiveMessage'),
- cancel_message: getStringValue(settings, 'cancel_message', 'CancelMessage'),
- close_winners_message:
- getStringValue(settings, 'close_winners_message', 'CloseWinnersMessage'),
- invalid_solution_message:
- getStringValue(settings, 'invalid_solution_message', 'InvalidSolutionMessage'),
- leaderboard_type: (settings && settings.leaderboard_type) || 'all',
- name: name || '',
- no_winners_message: getStringValue(settings, 'no_winners_message', 'NoWinnersMessage'),
- not_active_message: getStringValue(settings, 'not_active_message', 'NotActiveMessage'),
- points_close_first: getNumericValue(settings, 'points_close_first', 1),
- points_close_max: getNumericValue(settings, 'points_close_max', 3),
- points_close_other: getNumericValue(settings, 'points_close_other', 1),
- points_exact_first: getNumericValue(settings, 'points_exact_first', 1),
- points_exact_other: getNumericValue(settings, 'points_exact_other', 1),
- start_message: getStringValue(settings, 'start_message', 'StartMessage'),
- stop_message: getStringValue(settings, 'stop_message', 'StopMessage'),
- winners_message: getStringValue(settings, 'winners_message', 'WinnersMessage'),
- };
- },
- validationSchema: yup.object().shape({
- active_message: yup.string(),
- cancel_message: yup.string(),
- close_winners_message: yup.string(),
- leaderboard_type: yup.string(),
- invalid_solution_message: yup.string(),
- name: yup.string().required(),
- no_winners_message: yup.string(),
- not_active_message: yup.string(),
- points_close_first: yup.number(),
- points_close_max: yup.number(),
- points_close_other: yup.number(),
- points_exact_first: yup.number(),
- points_exact_other: yup.number(),
- start_message: yup.string(),
- stop_message: yup.string(),
- winners_message: yup.string(),
- }),
-})(GuessingSettingsForm);
--- /dev/null
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import i18n from '../../i18n';
+import yup from '../../schema/yup';
+
+const GuessingSettingsForm = ({
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ onCancel,
+ touched,
+ values,
+}) => {
+ const { t } = useTranslation();
+
+ return <Form noValidate onSubmit={handleSubmit}>
+ <Row>
+ <Form.Group as={Col} controlId="gg.points_exact_first" md={6}>
+ <Form.Label>{t('twitchBot.guessingGame.pointsExactFirst')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.points_exact_first && errors.points_exact_first)}
+ max="5"
+ min="1"
+ name="points_exact_first"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ step="1"
+ type="number"
+ value={values.points_exact_first || 0}
+ />
+ {touched.points_exact_first && errors.points_exact_first ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.points_exact_first)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group as={Col} controlId="gg.points_exact_other" md={6}>
+ <Form.Label>{t('twitchBot.guessingGame.pointsExactOther')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.points_exact_other && errors.points_exact_other)}
+ max="5"
+ min="0"
+ name="points_exact_other"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ step="1"
+ type="number"
+ value={values.points_exact_other || 0}
+ />
+ {touched.points_exact_other && errors.points_exact_other ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.points_exact_other)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group as={Col} controlId="gg.points_close_first" md={6}>
+ <Form.Label>{t('twitchBot.guessingGame.pointsCloseFirst')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.points_close_first && errors.points_close_first)}
+ max="5"
+ min="0"
+ name="points_close_first"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ step="0.5"
+ type="number"
+ value={values.points_close_first || 0}
+ />
+ {touched.points_close_first && errors.points_close_first ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.points_close_first)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group as={Col} controlId="gg.points_close_other" md={6}>
+ <Form.Label>{t('twitchBot.guessingGame.pointsCloseOther')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.points_close_other && errors.points_close_other)}
+ max="5"
+ min="0"
+ name="points_close_other"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ step="0.5"
+ type="number"
+ value={values.points_close_other || 0}
+ />
+ {touched.points_close_other && errors.points_close_other ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.points_close_other)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group as={Col} controlId="gg.points_close_max" md={6}>
+ <Form.Label>{t('twitchBot.guessingGame.pointsCloseMax')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.points_close_max && errors.points_close_max)}
+ min="0"
+ name="points_close_max"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ step="1"
+ type="number"
+ value={values.points_close_max || 0}
+ />
+ {touched.points_close_max && errors.points_close_max ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.points_close_max)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group as={Col} controlId="gg.leaderboard_type" md={6}>
+ <Form.Label>{t('twitchBot.guessingGame.leaderboardType')}</Form.Label>
+ <Form.Select
+ isInvalid={!!(touched.leaderboard_type && errors.leaderboard_type)}
+ name="leaderboard_type"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.leaderboard_type || 'all'}
+ >
+ {['all', 'year', '365', 'month', '30'].map(type =>
+ <option key={type} value={type}>
+ {t(`twitchBot.guessingGame.leaderboardTypes.${type}`)}
+ </option>
+ )}
+ </Form.Select>
+ {touched.leaderboard_type && errors.leaderboard_type ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.leaderboard_type)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Row>
+ <Form.Group controlId="gg.start_message">
+ <Form.Label>{t('twitchBot.guessingGame.startMessage')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.start_message && errors.start_message)}
+ name="start_message"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.start_message || ''}
+ />
+ {touched.start_message && errors.start_message ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.start_message)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group controlId="gg.stop_message">
+ <Form.Label>{t('twitchBot.guessingGame.stopMessage')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.stop_message && errors.stop_message)}
+ name="stop_message"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.stop_message || ''}
+ />
+ {touched.stop_message && errors.stop_message ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.stop_message)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group controlId="gg.winners_message">
+ <Form.Label>{t('twitchBot.guessingGame.winnersMessage')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.winners_message && errors.winners_message)}
+ name="winners_message"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.winners_message || ''}
+ />
+ {touched.winners_message && errors.winners_message ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.winners_message)}
+ </Form.Control.Feedback>
+ :
+ <Form.Text muted>
+ {t('twitchBot.guessingGame.winnersMessageHint')}
+ </Form.Text>
+ }
+ </Form.Group>
+ <Form.Group controlId="gg.close_winners_message">
+ <Form.Label>{t('twitchBot.guessingGame.closeWinnersMessage')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.close_winners_message && errors.close_winners_message)}
+ name="close_winners_message"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.close_winners_message || ''}
+ />
+ {touched.close_winners_message && errors.close_winners_message ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.close_winners_message)}
+ </Form.Control.Feedback>
+ :
+ <Form.Text muted>
+ {t('twitchBot.guessingGame.closeWinnersMessageHint')}
+ </Form.Text>
+ }
+ </Form.Group>
+ <Form.Group controlId="gg.no_winners_message">
+ <Form.Label>{t('twitchBot.guessingGame.noWinnersMessage')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.no_winners_message && errors.no_winners_message)}
+ name="no_winners_message"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.no_winners_message || ''}
+ />
+ {touched.no_winners_message && errors.no_winners_message ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.no_winners_message)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group controlId="gg.cancel_message">
+ <Form.Label>{t('twitchBot.guessingGame.cancelMessage')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.cancel_message && errors.cancel_message)}
+ name="cancel_message"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.cancel_message || ''}
+ />
+ {touched.cancel_message && errors.cancel_message ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.cancel_message)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group controlId="gg.invalid_solution_message">
+ <Form.Label>{t('twitchBot.guessingGame.invalidSolutionMessage')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.invalid_solution_message && errors.invalid_solution_message)}
+ name="invalid_solution_message"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.invalid_solution_message || ''}
+ />
+ {touched.invalid_solution_message && errors.invalid_solution_message ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.invalid_solution_message)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group controlId="gg.active_message">
+ <Form.Label>{t('twitchBot.guessingGame.activeMessage')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.active_message && errors.active_message)}
+ name="active_message"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.active_message || ''}
+ />
+ {touched.active_message && errors.active_message ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.active_message)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <Form.Group controlId="gg.not_active_message">
+ <Form.Label>{t('twitchBot.guessingGame.notActiveMessage')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.not_active_message && errors.not_active_message)}
+ name="not_active_message"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.not_active_message || ''}
+ />
+ {touched.not_active_message && errors.not_active_message ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.not_active_message)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ <div className="button-bar mt-3">
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {t('button.cancel')}
+ </Button>
+ : null}
+ <Button type="submit" variant="primary">
+ {t('button.save')}
+ </Button>
+ </div>
+ </Form>;
+};
+
+GuessingSettingsForm.propTypes = {
+ errors: PropTypes.shape({
+ active_message: PropTypes.string,
+ cancel_message: PropTypes.string,
+ close_winners_message: PropTypes.string,
+ invalid_solution_message: PropTypes.string,
+ leaderboard_type: PropTypes.string,
+ name: PropTypes.string,
+ no_winners_message: PropTypes.string,
+ not_active_message: PropTypes.string,
+ points_close_first: PropTypes.string,
+ points_close_max: PropTypes.string,
+ points_close_other: PropTypes.string,
+ points_exact_first: PropTypes.string,
+ points_exact_other: PropTypes.string,
+ start_message: PropTypes.string,
+ stop_message: PropTypes.string,
+ winners_message: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ name: PropTypes.string,
+ onCancel: PropTypes.func,
+ touched: PropTypes.shape({
+ active_message: PropTypes.bool,
+ cancel_message: PropTypes.bool,
+ close_winners_message: PropTypes.bool,
+ invalid_solution_message: PropTypes.bool,
+ leaderboard_type: PropTypes.bool,
+ name: PropTypes.bool,
+ no_winners_message: PropTypes.bool,
+ not_active_message: PropTypes.bool,
+ points_close_first: PropTypes.bool,
+ points_close_max: PropTypes.bool,
+ points_close_other: PropTypes.bool,
+ points_exact_first: PropTypes.bool,
+ points_exact_other: PropTypes.bool,
+ start_message: PropTypes.bool,
+ stop_message: PropTypes.bool,
+ winners_message: PropTypes.bool,
+ }),
+ values: PropTypes.shape({
+ active_message: PropTypes.string,
+ cancel_message: PropTypes.string,
+ close_winners_message: PropTypes.string,
+ invalid_solution_message: PropTypes.string,
+ leaderboard_type: PropTypes.string,
+ name: PropTypes.string,
+ no_winners_message: PropTypes.string,
+ not_active_message: PropTypes.string,
+ points_close_first: PropTypes.number,
+ points_close_max: PropTypes.number,
+ points_close_other: PropTypes.number,
+ points_exact_first: PropTypes.number,
+ points_exact_other: PropTypes.number,
+ start_message: PropTypes.string,
+ stop_message: PropTypes.string,
+ winners_message: PropTypes.string,
+ }),
+};
+
+export default withFormik({
+ displayName: 'GuessingSettingsForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { setErrors } = actions;
+ const { onSubmit } = actions.props;
+ try {
+ await onSubmit(values);
+ } catch (e) {
+ if (e.response && e.response.data && e.response.data.errors) {
+ setErrors(laravelErrorsToFormik(e.response.data.errors));
+ }
+ }
+ },
+ mapPropsToValues: ({ name, settings }) => {
+ const getNumericValue = (s, n, d) => s && Object.prototype.hasOwnProperty.call(s, n)
+ ? s[n] : d;
+ const getStringValue = (s, n, d) => s && Object.prototype.hasOwnProperty.call(s, n)
+ ? s[n] : i18n.t(`twitchBot.guessingGame.default${d}`);
+ return {
+ active_message: getStringValue(settings, 'active_message', 'ActiveMessage'),
+ cancel_message: getStringValue(settings, 'cancel_message', 'CancelMessage'),
+ close_winners_message:
+ getStringValue(settings, 'close_winners_message', 'CloseWinnersMessage'),
+ invalid_solution_message:
+ getStringValue(settings, 'invalid_solution_message', 'InvalidSolutionMessage'),
+ leaderboard_type: (settings && settings.leaderboard_type) || 'all',
+ name: name || '',
+ no_winners_message: getStringValue(settings, 'no_winners_message', 'NoWinnersMessage'),
+ not_active_message: getStringValue(settings, 'not_active_message', 'NotActiveMessage'),
+ points_close_first: getNumericValue(settings, 'points_close_first', 1),
+ points_close_max: getNumericValue(settings, 'points_close_max', 3),
+ points_close_other: getNumericValue(settings, 'points_close_other', 1),
+ points_exact_first: getNumericValue(settings, 'points_exact_first', 1),
+ points_exact_other: getNumericValue(settings, 'points_exact_other', 1),
+ start_message: getStringValue(settings, 'start_message', 'StartMessage'),
+ stop_message: getStringValue(settings, 'stop_message', 'StopMessage'),
+ winners_message: getStringValue(settings, 'winners_message', 'WinnersMessage'),
+ };
+ },
+ validationSchema: yup.object().shape({
+ active_message: yup.string(),
+ cancel_message: yup.string(),
+ close_winners_message: yup.string(),
+ leaderboard_type: yup.string(),
+ invalid_solution_message: yup.string(),
+ name: yup.string().required(),
+ no_winners_message: yup.string(),
+ not_active_message: yup.string(),
+ points_close_first: yup.number(),
+ points_close_max: yup.number(),
+ points_close_other: yup.number(),
+ points_exact_first: yup.number(),
+ points_exact_other: yup.number(),
+ start_message: yup.string(),
+ stop_message: yup.string(),
+ winners_message: yup.string(),
+ }),
+})(GuessingSettingsForm);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Col, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-const GuessingWinner = ({ winner }) => {
- const { t } = useTranslation();
-
- const classNames = ['guessing-game-winner', 'my-2', 'p-2', 'border', 'rounded'];
- if (!winner.score) {
- classNames.push('no-points');
- }
-
- return <div className={classNames.join(' ')}>
- <Row>
- <Col xs={6}>
- <div>{winner.uname}</div>
- <div>{t(
- 'twitchBot.guessingGame.winnerScore',
- { count: winner.score, score: winner.score },
- )}</div>
- </Col>
- <Col xs={6}>
- <div className="fs-3 text-end">{winner.guess}</div>
- </Col>
- </Row>
- </div>;
-};
-
-GuessingWinner.propTypes = {
- winner: PropTypes.shape({
- guess: PropTypes.string,
- score: PropTypes.number,
- uname: PropTypes.string,
- }),
-};
-
-export default GuessingWinner;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Col, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+const GuessingWinner = ({ winner }) => {
+ const { t } = useTranslation();
+
+ const classNames = ['guessing-game-winner', 'my-2', 'p-2', 'border', 'rounded'];
+ if (!winner.score) {
+ classNames.push('no-points');
+ }
+
+ return <div className={classNames.join(' ')}>
+ <Row>
+ <Col xs={6}>
+ <div>{winner.uname}</div>
+ <div>{t(
+ 'twitchBot.guessingGame.winnerScore',
+ { count: winner.score, score: winner.score },
+ )}</div>
+ </Col>
+ <Col xs={6}>
+ <div className="fs-3 text-end">{winner.guess}</div>
+ </Col>
+ </Row>
+ </div>;
+};
+
+GuessingWinner.propTypes = {
+ winner: PropTypes.shape({
+ guess: PropTypes.string,
+ score: PropTypes.number,
+ uname: PropTypes.string,
+ }),
+};
+
+export default GuessingWinner;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import { useNavigate } from 'react-router-dom';
-
-import { getAvatarUrl, getUserName } from '../../helpers/User';
-import i18n from '../../i18n';
-
-const Box = ({ discriminator, noLink, user }) => {
- const navigate = useNavigate();
-
- if (!user) {
- return <span>{i18n.t('general.anonymous')}</span>;
- }
-
- const content = <>
- <img alt="" src={getAvatarUrl(user)} />
- <span>{discriminator ? user.username : getUserName(user)}</span>
- {discriminator && user.discriminator && user.discriminator !== '0' ?
- <span className="text-muted">
- {'#'}
- {user.discriminator}
- </span>
- : null}
- </>;
-
- if (noLink) {
- return <span className="user-box">{content}</span>;
- }
-
- return <Button
- className="user-box"
- onClick={() => navigate(`/users/${user.id}`)}
- variant="link"
- >
- {content}
- </Button>;
-};
-
-Box.propTypes = {
- discriminator: PropTypes.bool,
- noLink: PropTypes.bool,
- user: PropTypes.shape({
- discriminator: PropTypes.string,
- id: PropTypes.string,
- nickname: PropTypes.string,
- username: PropTypes.string,
- }),
-};
-
-export default withTranslation()(Box);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom';
+
+import { getAvatarUrl, getUserName } from '../../helpers/User';
+import i18n from '../../i18n';
+
+const Box = ({ discriminator, noLink, user }) => {
+ const navigate = useNavigate();
+
+ if (!user) {
+ return <span>{i18n.t('general.anonymous')}</span>;
+ }
+
+ const content = <>
+ <img alt="" src={getAvatarUrl(user)} />
+ <span>{discriminator ? user.username : getUserName(user)}</span>
+ {discriminator && user.discriminator && user.discriminator !== '0' ?
+ <span className="text-muted">
+ {'#'}
+ {user.discriminator}
+ </span>
+ : null}
+ </>;
+
+ if (noLink) {
+ return <span className="user-box">{content}</span>;
+ }
+
+ return <Button
+ className="user-box"
+ onClick={() => navigate(`/users/${user.id}`)}
+ variant="link"
+ >
+ {content}
+ </Button>;
+};
+
+Box.propTypes = {
+ discriminator: PropTypes.bool,
+ noLink: PropTypes.bool,
+ user: PropTypes.shape({
+ discriminator: PropTypes.string,
+ id: PropTypes.string,
+ nickname: PropTypes.string,
+ username: PropTypes.string,
+ }),
+};
+
+export default withTranslation()(Box);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import EditNicknameDialog from './EditNicknameDialog';
-import Icon from '../common/Icon';
-import { mayEditNickname } from '../../helpers/permissions';
-import { useUser } from '../../hooks/user';
-
-const EditNicknameButton = ({ user }) => {
- const [showDialog, setShowDialog] = useState(false);
-
- const { t } = useTranslation();
- const { user: authUser } = useUser();
-
- if (mayEditNickname(authUser, user)) {
- return <>
- <EditNicknameDialog
- onHide={() => setShowDialog(false)}
- show={showDialog}
- user={user}
- />
- <Button
- onClick={() => setShowDialog(true)}
- title={t('button.edit')}
- variant="outline-secondary"
- >
- <Icon.EDIT title="" />
- </Button>
- </>;
- }
- return null;
-};
-
-EditNicknameButton.propTypes = {
- user: PropTypes.shape({
- }),
-};
-
-export default EditNicknameButton;
--- /dev/null
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import EditNicknameDialog from './EditNicknameDialog';
+import Icon from '../common/Icon';
+import { mayEditNickname } from '../../helpers/permissions';
+import { useUser } from '../../hooks/user';
+
+const EditNicknameButton = ({ user }) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ const { t } = useTranslation();
+ const { user: authUser } = useUser();
+
+ if (mayEditNickname(authUser, user)) {
+ return <>
+ <EditNicknameDialog
+ onHide={() => setShowDialog(false)}
+ show={showDialog}
+ user={user}
+ />
+ <Button
+ onClick={() => setShowDialog(true)}
+ title={t('button.edit')}
+ variant="outline-secondary"
+ >
+ <Icon.EDIT title="" />
+ </Button>
+ </>;
+ }
+ return null;
+};
+
+EditNicknameButton.propTypes = {
+ user: PropTypes.shape({
+ }),
+};
+
+export default EditNicknameButton;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Modal } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import EditNicknameForm from './EditNicknameForm';
-import i18n from '../../i18n';
-
-const EditNicknameDialog = ({
- onHide,
- show,
- user,
-}) =>
-<Modal className="edit-stream-link-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {i18n.t('users.editNickname')}
- </Modal.Title>
- </Modal.Header>
- <EditNicknameForm
- onCancel={onHide}
- user={user}
- />
-</Modal>;
-
-EditNicknameDialog.propTypes = {
- onHide: PropTypes.func,
- show: PropTypes.bool,
- user: PropTypes.shape({
- }),
-};
-
-export default withTranslation()(EditNicknameDialog);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import EditNicknameForm from './EditNicknameForm';
+import i18n from '../../i18n';
+
+const EditNicknameDialog = ({
+ onHide,
+ show,
+ user,
+}) =>
+<Modal className="edit-stream-link-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {i18n.t('users.editNickname')}
+ </Modal.Title>
+ </Modal.Header>
+ <EditNicknameForm
+ onCancel={onHide}
+ user={user}
+ />
+</Modal>;
+
+EditNicknameDialog.propTypes = {
+ onHide: PropTypes.func,
+ show: PropTypes.bool,
+ user: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(EditNicknameDialog);
+++ /dev/null
-import axios from 'axios';
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
-import i18n from '../../i18n';
-import yup from '../../schema/yup';
-
-const EditStreamLinkForm = ({
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- onCancel,
- touched,
- user,
- values,
-}) =>
-<Form noValidate onSubmit={handleSubmit}>
- <Modal.Body>
- <Row>
- <Form.Group as={Col} controlId="user.nickname">
- <Form.Label>{i18n.t('users.nickname')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.nickname && errors.nickname)}
- name="nickname"
- onBlur={handleBlur}
- onChange={handleChange}
- placeholder={user.username}
- type="text"
- value={values.nickname || ''}
- />
- {touched.nickname && errors.nickname ?
- <Form.Control.Feedback type="invalid">
- {i18n.t(errors.nickname)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Row>
- </Modal.Body>
- <Modal.Footer>
- {onCancel ?
- <Button onClick={onCancel} variant="secondary">
- {i18n.t('button.cancel')}
- </Button>
- : null}
- <Button type="submit" variant="primary">
- {i18n.t('button.save')}
- </Button>
- </Modal.Footer>
-</Form>;
-
-EditStreamLinkForm.propTypes = {
- errors: PropTypes.shape({
- nickname: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- onCancel: PropTypes.func,
- touched: PropTypes.shape({
- nickname: PropTypes.bool,
- }),
- user: PropTypes.shape({
- username: PropTypes.string,
- }),
- values: PropTypes.shape({
- nickname: PropTypes.string,
- }),
-};
-
-export default withFormik({
- displayName: 'NicknameForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { user_id, nickname } = values;
- const { setErrors } = actions;
- const { onCancel } = actions.props;
- try {
- await axios.post(`/api/users/${user_id}/setNickname`, {
- nickname,
- });
- toastr.success(i18n.t('users.setNicknameSuccess'));
- if (onCancel) {
- onCancel();
- }
- } catch (e) {
- toastr.error(i18n.t('users.setNicknameError'));
- if (e.response && e.response.data && e.response.data.errors) {
- setErrors(laravelErrorsToFormik(e.response.data.errors));
- }
- }
- },
- mapPropsToValues: ({ user }) => ({
- user_id: user.id,
- nickname: user.nickname || '',
- }),
- validationSchema: yup.object().shape({
- nickname: yup.string(),
- }),
-})(withTranslation()(EditStreamLinkForm));
--- /dev/null
+import axios from 'axios';
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import i18n from '../../i18n';
+import yup from '../../schema/yup';
+
+const EditStreamLinkForm = ({
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ onCancel,
+ touched,
+ user,
+ values,
+}) =>
+<Form noValidate onSubmit={handleSubmit}>
+ <Modal.Body>
+ <Row>
+ <Form.Group as={Col} controlId="user.nickname">
+ <Form.Label>{i18n.t('users.nickname')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.nickname && errors.nickname)}
+ name="nickname"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ placeholder={user.username}
+ type="text"
+ value={values.nickname || ''}
+ />
+ {touched.nickname && errors.nickname ?
+ <Form.Control.Feedback type="invalid">
+ {i18n.t(errors.nickname)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {i18n.t('button.cancel')}
+ </Button>
+ : null}
+ <Button type="submit" variant="primary">
+ {i18n.t('button.save')}
+ </Button>
+ </Modal.Footer>
+</Form>;
+
+EditStreamLinkForm.propTypes = {
+ errors: PropTypes.shape({
+ nickname: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ onCancel: PropTypes.func,
+ touched: PropTypes.shape({
+ nickname: PropTypes.bool,
+ }),
+ user: PropTypes.shape({
+ username: PropTypes.string,
+ }),
+ values: PropTypes.shape({
+ nickname: PropTypes.string,
+ }),
+};
+
+export default withFormik({
+ displayName: 'NicknameForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { user_id, nickname } = values;
+ const { setErrors } = actions;
+ const { onCancel } = actions.props;
+ try {
+ await axios.post(`/api/users/${user_id}/setNickname`, {
+ nickname,
+ });
+ toastr.success(i18n.t('users.setNicknameSuccess'));
+ if (onCancel) {
+ onCancel();
+ }
+ } catch (e) {
+ toastr.error(i18n.t('users.setNicknameError'));
+ if (e.response && e.response.data && e.response.data.errors) {
+ setErrors(laravelErrorsToFormik(e.response.data.errors));
+ }
+ }
+ },
+ mapPropsToValues: ({ user }) => ({
+ user_id: user.id,
+ nickname: user.nickname || '',
+ }),
+ validationSchema: yup.object().shape({
+ nickname: yup.string(),
+ }),
+})(withTranslation()(EditStreamLinkForm));
+++ /dev/null
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import EditStreamLinkDialog from './EditStreamLinkDialog';
-import Icon from '../common/Icon';
-import { mayEditStreamLink } from '../../helpers/permissions';
-import { useUser } from '../../hooks/user';
-
-const EditStreamLinkButton = ({ user }) => {
- const [showDialog, setShowDialog] = useState(false);
-
- const { t } = useTranslation();
- const { user: authUser } = useUser();
-
- if (mayEditStreamLink(authUser, user)) {
- return <>
- <EditStreamLinkDialog
- onHide={() => setShowDialog(false)}
- show={showDialog}
- user={user}
- />
- <Button
- onClick={() => setShowDialog(true)}
- title={t('button.edit')}
- variant="outline-secondary"
- >
- <Icon.EDIT title="" />
- </Button>
- </>;
- }
- return null;
-};
-
-EditStreamLinkButton.propTypes = {
- user: PropTypes.shape({
- }),
-};
-
-export default EditStreamLinkButton;
--- /dev/null
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import EditStreamLinkDialog from './EditStreamLinkDialog';
+import Icon from '../common/Icon';
+import { mayEditStreamLink } from '../../helpers/permissions';
+import { useUser } from '../../hooks/user';
+
+const EditStreamLinkButton = ({ user }) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ const { t } = useTranslation();
+ const { user: authUser } = useUser();
+
+ if (mayEditStreamLink(authUser, user)) {
+ return <>
+ <EditStreamLinkDialog
+ onHide={() => setShowDialog(false)}
+ show={showDialog}
+ user={user}
+ />
+ <Button
+ onClick={() => setShowDialog(true)}
+ title={t('button.edit')}
+ variant="outline-secondary"
+ >
+ <Icon.EDIT title="" />
+ </Button>
+ </>;
+ }
+ return null;
+};
+
+EditStreamLinkButton.propTypes = {
+ user: PropTypes.shape({
+ }),
+};
+
+export default EditStreamLinkButton;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Modal } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import EditStreamLinkForm from './EditStreamLinkForm';
-import i18n from '../../i18n';
-
-const EditStreamLinkDialog = ({
- onHide,
- show,
- user,
-}) =>
-<Modal className="edit-stream-link-dialog" onHide={onHide} show={show}>
- <Modal.Header closeButton>
- <Modal.Title>
- {i18n.t('users.editStreamLink')}
- </Modal.Title>
- </Modal.Header>
- <EditStreamLinkForm
- onCancel={onHide}
- user={user}
- />
-</Modal>;
-
-EditStreamLinkDialog.propTypes = {
- onHide: PropTypes.func,
- show: PropTypes.bool,
- user: PropTypes.shape({
- }),
-};
-
-export default withTranslation()(EditStreamLinkDialog);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import EditStreamLinkForm from './EditStreamLinkForm';
+import i18n from '../../i18n';
+
+const EditStreamLinkDialog = ({
+ onHide,
+ show,
+ user,
+}) =>
+<Modal className="edit-stream-link-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {i18n.t('users.editStreamLink')}
+ </Modal.Title>
+ </Modal.Header>
+ <EditStreamLinkForm
+ onCancel={onHide}
+ user={user}
+ />
+</Modal>;
+
+EditStreamLinkDialog.propTypes = {
+ onHide: PropTypes.func,
+ show: PropTypes.bool,
+ user: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(EditStreamLinkDialog);
+++ /dev/null
-import axios from 'axios';
-import { withFormik } from 'formik';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
-import i18n from '../../i18n';
-import yup from '../../schema/yup';
-
-const EditStreamLinkForm = ({
- errors,
- handleBlur,
- handleChange,
- handleSubmit,
- onCancel,
- touched,
- values,
-}) =>
-<Form noValidate onSubmit={handleSubmit}>
- <Modal.Body>
- <Row>
- <Form.Group as={Col} controlId="user.stream_link">
- <Form.Label>{i18n.t('users.streamLink')}</Form.Label>
- <Form.Control
- isInvalid={!!(touched.stream_link && errors.stream_link)}
- name="stream_link"
- onBlur={handleBlur}
- onChange={handleChange}
- placeholder="https://www.twitch.tv/fgfm"
- type="text"
- value={values.stream_link || ''}
- />
- {touched.stream_link && errors.stream_link ?
- <Form.Control.Feedback type="invalid">
- {i18n.t(errors.stream_link)}
- </Form.Control.Feedback>
- : null}
- </Form.Group>
- </Row>
- </Modal.Body>
- <Modal.Footer>
- {onCancel ?
- <Button onClick={onCancel} variant="secondary">
- {i18n.t('button.cancel')}
- </Button>
- : null}
- <Button type="submit" variant="primary">
- {i18n.t('button.save')}
- </Button>
- </Modal.Footer>
-</Form>;
-
-EditStreamLinkForm.propTypes = {
- errors: PropTypes.shape({
- stream_link: PropTypes.string,
- }),
- handleBlur: PropTypes.func,
- handleChange: PropTypes.func,
- handleSubmit: PropTypes.func,
- onCancel: PropTypes.func,
- touched: PropTypes.shape({
- stream_link: PropTypes.bool,
- }),
- values: PropTypes.shape({
- stream_link: PropTypes.string,
- }),
-};
-
-export default withFormik({
- displayName: 'StreamLinkForm',
- enableReinitialize: true,
- handleSubmit: async (values, actions) => {
- const { user_id, stream_link } = values;
- const { setErrors } = actions;
- const { onCancel } = actions.props;
- try {
- await axios.post(`/api/users/${user_id}/setStreamLink`, {
- stream_link,
- });
- toastr.success(i18n.t('users.setStreamLinkSuccess'));
- if (onCancel) {
- onCancel();
- }
- } catch (e) {
- toastr.error(i18n.t('users.setStreamLinkError'));
- if (e.response && e.response.data && e.response.data.errors) {
- setErrors(laravelErrorsToFormik(e.response.data.errors));
- }
- }
- },
- mapPropsToValues: ({ user }) => ({
- user_id: user.id,
- stream_link: user.stream_link || '',
- }),
- validationSchema: yup.object().shape({
- stream_link: yup.string().required().url(),
- }),
-})(withTranslation()(EditStreamLinkForm));
--- /dev/null
+import axios from 'axios';
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import i18n from '../../i18n';
+import yup from '../../schema/yup';
+
+const EditStreamLinkForm = ({
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ onCancel,
+ touched,
+ values,
+}) =>
+<Form noValidate onSubmit={handleSubmit}>
+ <Modal.Body>
+ <Row>
+ <Form.Group as={Col} controlId="user.stream_link">
+ <Form.Label>{i18n.t('users.streamLink')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.stream_link && errors.stream_link)}
+ name="stream_link"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ placeholder="https://www.twitch.tv/fgfm"
+ type="text"
+ value={values.stream_link || ''}
+ />
+ {touched.stream_link && errors.stream_link ?
+ <Form.Control.Feedback type="invalid">
+ {i18n.t(errors.stream_link)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {i18n.t('button.cancel')}
+ </Button>
+ : null}
+ <Button type="submit" variant="primary">
+ {i18n.t('button.save')}
+ </Button>
+ </Modal.Footer>
+</Form>;
+
+EditStreamLinkForm.propTypes = {
+ errors: PropTypes.shape({
+ stream_link: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ onCancel: PropTypes.func,
+ touched: PropTypes.shape({
+ stream_link: PropTypes.bool,
+ }),
+ values: PropTypes.shape({
+ stream_link: PropTypes.string,
+ }),
+};
+
+export default withFormik({
+ displayName: 'StreamLinkForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { user_id, stream_link } = values;
+ const { setErrors } = actions;
+ const { onCancel } = actions.props;
+ try {
+ await axios.post(`/api/users/${user_id}/setStreamLink`, {
+ stream_link,
+ });
+ toastr.success(i18n.t('users.setStreamLinkSuccess'));
+ if (onCancel) {
+ onCancel();
+ }
+ } catch (e) {
+ toastr.error(i18n.t('users.setStreamLinkError'));
+ if (e.response && e.response.data && e.response.data.errors) {
+ setErrors(laravelErrorsToFormik(e.response.data.errors));
+ }
+ }
+ },
+ mapPropsToValues: ({ user }) => ({
+ user_id: user.id,
+ stream_link: user.stream_link || '',
+ }),
+ validationSchema: yup.object().shape({
+ stream_link: yup.string().required().url(),
+ }),
+})(withTranslation()(EditStreamLinkForm));
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Alert, Button, Table } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-import { useNavigate } from 'react-router-dom';
-
-import Icon from '../common/Icon';
-import { isRunner } from '../../helpers/Participant';
-import i18n from '../../i18n';
-
-const getIcon = participant => {
- if (!isRunner(participant)) {
- return '—';
- }
- if (participant.placement === 1) {
- return <Icon.FIRST_PLACE className="text-gold" size="lg" />;
- }
- if (participant.placement === 2) {
- return <Icon.SECOND_PLACE className="text-silver" size="lg" />;
- }
- if (participant.placement === 3) {
- return <Icon.THIRD_PLACE className="text-bronze" size="lg" />;
- }
- return participant.placement;
-};
-
-const Participation = ({ user }) => {
- const navigate = useNavigate();
-
- if (!user || !user.participation || !user.participation.length) {
- return <Alert variant="info">
- {i18n.t('users.participationEmpty')}
- </Alert>;
- }
- return <Table className="participation align-middle">
- <thead>
- <tr>
- <th>{i18n.t('participants.tournament')}</th>
- <th>{i18n.t('participants.placement')}</th>
- <th>{i18n.t('participants.roles')}</th>
- </tr>
- </thead>
- <tbody>
- {user.participation.map(p => <tr key={p.id}>
- <td>
- <Button
- onClick={() => navigate(`/tournaments/${p.tournament_id}`)}
- variant="link"
- >
- {p.tournament.title}
- </Button>
- </td>
- <td>
- {getIcon(p)}
- {!p.tournament.locked && isRunner(p) ?
- <span title={i18n.t('participants.placementSubjectToChange')}> *</span>
- : null}
- {p.tournament.no_record ?
- <span title={i18n.t('tournaments.noRecord')}> †</span>
- : null}
- </td>
- <td>
- {p.roles ? p.roles.map((role, index) =>
- <span key={role}>
- {index === 0 ? '' : ', '}
- {i18n.t(`participants.roleNames.${role}`)}
- </span>
- ) : null}
- </td>
- </tr>)}
- </tbody>
- </Table>;
-};
-
-Participation.propTypes = {
- user: PropTypes.shape({
- participation: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.number,
- })),
- }),
-};
-
-export default withTranslation()(Participation);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Alert, Button, Table } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom';
+
+import Icon from '../common/Icon';
+import { isRunner } from '../../helpers/Participant';
+import i18n from '../../i18n';
+
+const getIcon = participant => {
+ if (!isRunner(participant)) {
+ return '—';
+ }
+ if (participant.placement === 1) {
+ return <Icon.FIRST_PLACE className="text-gold" size="lg" />;
+ }
+ if (participant.placement === 2) {
+ return <Icon.SECOND_PLACE className="text-silver" size="lg" />;
+ }
+ if (participant.placement === 3) {
+ return <Icon.THIRD_PLACE className="text-bronze" size="lg" />;
+ }
+ return participant.placement;
+};
+
+const Participation = ({ user }) => {
+ const navigate = useNavigate();
+
+ if (!user || !user.participation || !user.participation.length) {
+ return <Alert variant="info">
+ {i18n.t('users.participationEmpty')}
+ </Alert>;
+ }
+ return <Table className="participation align-middle">
+ <thead>
+ <tr>
+ <th>{i18n.t('participants.tournament')}</th>
+ <th>{i18n.t('participants.placement')}</th>
+ <th>{i18n.t('participants.roles')}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {user.participation.map(p => <tr key={p.id}>
+ <td>
+ <Button
+ onClick={() => navigate(`/tournaments/${p.tournament_id}`)}
+ variant="link"
+ >
+ {p.tournament.title}
+ </Button>
+ </td>
+ <td>
+ {getIcon(p)}
+ {!p.tournament.locked && isRunner(p) ?
+ <span title={i18n.t('participants.placementSubjectToChange')}> *</span>
+ : null}
+ {p.tournament.no_record ?
+ <span title={i18n.t('tournaments.noRecord')}> †</span>
+ : null}
+ </td>
+ <td>
+ {p.roles ? p.roles.map((role, index) =>
+ <span key={role}>
+ {index === 0 ? '' : ', '}
+ {i18n.t(`participants.roleNames.${role}`)}
+ </span>
+ ) : null}
+ </td>
+ </tr>)}
+ </tbody>
+ </Table>;
+};
+
+Participation.propTypes = {
+ user: PropTypes.shape({
+ participation: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.number,
+ })),
+ }),
+};
+
+export default withTranslation()(Participation);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Alert, Button, Col, Container, Row } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
-
-import Box from './Box';
-import Records from './Records';
-import EditNicknameButton from './EditNicknameButton';
-import EditStreamLinkButton from './EditStreamLinkButton';
-import Participation from './Participation';
-import Icon from '../common/Icon';
-import i18n from '../../i18n';
-
-const Profile = ({ user }) => <Container>
- <h1>
- {user.nickname || user.username}
- {' '}
- <EditNicknameButton user={user} />
- </h1>
- {user.random_quote && user.random_quote.comment ?
- <Alert className="quote-alert" variant="dark">
- <blockquote className="blockquote mb-0">
- {user.random_quote.comment}
- </blockquote>
- </Alert>
- : null}
- <Row>
- <Col md={6} className="mb-5">
- <h2>{i18n.t('users.discordTag')}</h2>
- <Box discriminator user={user} />
- </Col>
- <Col md={6} className="mb-5">
- <h2>{i18n.t('users.streamLink')}</h2>
- <p>
- {user.stream_link ?
- <Button
- href={user.stream_link}
- target="_blank"
- variant="outline-twitch"
- >
- <Icon.STREAM />
- {' '}
- {user.stream_link}
- </Button>
- :
- i18n.t('users.noStream')
- }
- {' '}
- <EditStreamLinkButton user={user} />
- </p>
- </Col>
- <Col md={6} className="mb-5">
- <h2>{i18n.t('users.tournamentRecords')}</h2>
- <Records
- first={user.tournament_first_count}
- second={user.tournament_second_count}
- third={user.tournament_third_count}
- />
- </Col>
- <Col md={6} className="mb-5">
- <h2>{i18n.t('users.roundRecords')}</h2>
- <Records
- first={user.round_first_count}
- second={user.round_second_count}
- third={user.round_third_count}
- />
- </Col>
- <Col md={12} className="mb-5">
- <h2>{i18n.t('users.tournaments')}</h2>
- <Participation user={user} />
- </Col>
- </Row>
-</Container>;
-
-Profile.propTypes = {
- user: PropTypes.shape({
- nickname: PropTypes.string,
- participation: PropTypes.arrayOf(PropTypes.shape({
- })),
- random_quote: PropTypes.shape({
- comment: PropTypes.string,
- }),
- round_first_count: PropTypes.number,
- round_second_count: PropTypes.number,
- round_third_count: PropTypes.number,
- stream_link: PropTypes.string,
- tournament_first_count: PropTypes.number,
- tournament_second_count: PropTypes.number,
- tournament_third_count: PropTypes.number,
- username: PropTypes.string,
- }),
-};
-
-export default withTranslation()(Profile);
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Alert, Button, Col, Container, Row } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import Box from './Box';
+import Records from './Records';
+import EditNicknameButton from './EditNicknameButton';
+import EditStreamLinkButton from './EditStreamLinkButton';
+import Participation from './Participation';
+import Icon from '../common/Icon';
+import i18n from '../../i18n';
+
+const Profile = ({ user }) => <Container>
+ <h1>
+ {user.nickname || user.username}
+ {' '}
+ <EditNicknameButton user={user} />
+ </h1>
+ {user.random_quote && user.random_quote.comment ?
+ <Alert className="quote-alert" variant="dark">
+ <blockquote className="blockquote mb-0">
+ {user.random_quote.comment}
+ </blockquote>
+ </Alert>
+ : null}
+ <Row>
+ <Col md={6} className="mb-5">
+ <h2>{i18n.t('users.discordTag')}</h2>
+ <Box discriminator user={user} />
+ </Col>
+ <Col md={6} className="mb-5">
+ <h2>{i18n.t('users.streamLink')}</h2>
+ <p>
+ {user.stream_link ?
+ <Button
+ href={user.stream_link}
+ target="_blank"
+ variant="outline-twitch"
+ >
+ <Icon.STREAM />
+ {' '}
+ {user.stream_link}
+ </Button>
+ :
+ i18n.t('users.noStream')
+ }
+ {' '}
+ <EditStreamLinkButton user={user} />
+ </p>
+ </Col>
+ <Col md={6} className="mb-5">
+ <h2>{i18n.t('users.tournamentRecords')}</h2>
+ <Records
+ first={user.tournament_first_count}
+ second={user.tournament_second_count}
+ third={user.tournament_third_count}
+ />
+ </Col>
+ <Col md={6} className="mb-5">
+ <h2>{i18n.t('users.roundRecords')}</h2>
+ <Records
+ first={user.round_first_count}
+ second={user.round_second_count}
+ third={user.round_third_count}
+ />
+ </Col>
+ <Col md={12} className="mb-5">
+ <h2>{i18n.t('users.tournaments')}</h2>
+ <Participation user={user} />
+ </Col>
+ </Row>
+</Container>;
+
+Profile.propTypes = {
+ user: PropTypes.shape({
+ nickname: PropTypes.string,
+ participation: PropTypes.arrayOf(PropTypes.shape({
+ })),
+ random_quote: PropTypes.shape({
+ comment: PropTypes.string,
+ }),
+ round_first_count: PropTypes.number,
+ round_second_count: PropTypes.number,
+ round_third_count: PropTypes.number,
+ stream_link: PropTypes.string,
+ tournament_first_count: PropTypes.number,
+ tournament_second_count: PropTypes.number,
+ tournament_third_count: PropTypes.number,
+ username: PropTypes.string,
+ }),
+};
+
+export default withTranslation()(Profile);
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Col, Row } from 'react-bootstrap';
-
-import Icon from '../common/Icon';
-
-const Records = ({
- first,
- second,
- third,
-}) => <Row>
- <Col>
- <div className="record-box">
- <span className="icon">
- <Icon.FIRST_PLACE className="text-gold" />
- </span>
- <span className="count">
- {first}
- </span>
- </div>
- </Col>
- <Col>
- <div className="record-box">
- <span className="icon">
- <Icon.SECOND_PLACE className="text-silver" />
- </span>
- <span className="count">
- {second}
- </span>
- </div>
- </Col>
- <Col>
- <div className="record-box">
- <span className="icon">
- <Icon.THIRD_PLACE className="text-bronze" />
- </span>
- <span className="count">
- {third}
- </span>
- </div>
- </Col>
-</Row>;
-
-Records.propTypes = {
- first: PropTypes.number,
- second: PropTypes.number,
- third: PropTypes.number,
-};
-
-export default Records;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Col, Row } from 'react-bootstrap';
+
+import Icon from '../common/Icon';
+
+const Records = ({
+ first,
+ second,
+ third,
+}) => <Row>
+ <Col>
+ <div className="record-box">
+ <span className="icon">
+ <Icon.FIRST_PLACE className="text-gold" />
+ </span>
+ <span className="count">
+ {first}
+ </span>
+ </div>
+ </Col>
+ <Col>
+ <div className="record-box">
+ <span className="icon">
+ <Icon.SECOND_PLACE className="text-silver" />
+ </span>
+ <span className="count">
+ {second}
+ </span>
+ </div>
+ </Col>
+ <Col>
+ <div className="record-box">
+ <span className="icon">
+ <Icon.THIRD_PLACE className="text-bronze" />
+ </span>
+ <span className="count">
+ {third}
+ </span>
+ </div>
+ </Col>
+</Row>;
+
+Records.propTypes = {
+ first: PropTypes.number,
+ second: PropTypes.number,
+ third: PropTypes.number,
+};
+
+export default Records;
+++ /dev/null
-import FuzzySearch from 'fuzzy-search';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Button } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import Icon from '../common/Icon';
-
-const AREAS = [
- {
- id: 'kf',
- bgColor: '#6aa84f',
- fgColor: '#000000',
- name: 'Kokiri Forest',
- short: 'KF',
- map: {
- pos: { x: 210, y: 240 },
- size: { x: 150, y: 100, },
- bg: {
- src: '/media/oot/minimap/kf.png',
- off: { x: 0, y: 0 },
- scale: 1.3,
- },
- labelPos: { x: 90, y: 70 },
- },
- entrances: [
- {
- id: 'bro',
- bgColor: '#b6d7a8',
- fgColor: '#000000',
- name: 'Know-It-All Brothers\' House',
- short: 'Bros',
- type: 'Interior',
- pos: { x: 13, y: 57 },
- },
- {
- id: 'link',
- bgColor: '#b6d7a8',
- fgColor: '#000000',
- name: 'Link\'s House',
- short: 'Links',
- type: 'SpecialInterior',
- pos: { x: 36, y: 74 },
- },
- {
- id: 'sariah',
- bgColor: '#b6d7a8',
- fgColor: '#000000',
- name: 'Sariah\'s House',
- short: 'Sariahs',
- type: 'Interior',
- pos: { x: 47, y: 66 },
- },
- {
- id: 'twin',
- bgColor: '#b6d7a8',
- fgColor: '#000000',
- name: 'House of Twins',
- short: 'Twins',
- type: 'Interior',
- pos: { x: 58, y: 64 },
- },
- {
- id: 'shop',
- bgColor: '#b6d7a8',
- fgColor: '#000000',
- name: 'Kokiri Shop',
- short: 'Shop',
- type: 'Interior',
- pos: { x: 53, y: 42 },
- },
- {
- id: 'mido',
- bgColor: '#b6d7a8',
- fgColor: '#000000',
- name: 'Great Mido\'s House',
- short: 'Mido',
- type: 'Interior',
- pos: { x: 28, y: 38 },
- },
- {
- id: 'deku',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'Deku Tree',
- short: 'Deku',
- spacer: true,
- type: 'Dungeon',
- pos: { x: 114, y: 26 },
- },
- {
- id: 'storms',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Storms Grotto',
- short: 'Storms Grotto',
- type: 'Grotto',
- pos: { x: 25, y: 25 },
- },
- {
- id: 'hf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Hyrule Field',
- short: 'HF',
- type: 'Overworld',
- pos: { x: 4, y: 45 },
- },
- {
- id: 'lw',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Lost Woods',
- short: 'LW',
- type: 'Overworld',
- pos: { x: 31, y: 21 },
- },
- ],
- },
- {
- id: 'lw',
- bgColor: '#38761d',
- fgColor: '#000000',
- name: 'Lost Woods',
- short: 'LW',
- map: {
- pos: { x: 370, y: 220 },
- size: { x: 75, y: 100, },
- bg: {
- src: '/media/oot/minimap/lw.png',
- off: { x: 0, y: 0 },
- scale: 1.3,
- },
- labelPos: { x: 40, y: 85 },
- },
- entrances: [
- {
- id: 'gcg',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Goron City Grotto',
- short: 'GC Grotto',
- type: 'Grotto',
- pos: { x: 45, y: 40 },
- },
- {
- id: 'tg',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Theater Grotto',
- short: 'Theater Grotto',
- type: 'Grotto',
- pos: { x: 32, y: 25 },
- },
- {
- id: 'sfmg',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'SFM Grotto',
- short: 'SFM Grotto',
- type: 'Grotto',
- pos: { x: 40, y: 12 },
- },
- {
- id: 'kf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Kokiri Forest',
- short: 'KF',
- spacer: true,
- type: 'Overworld',
- pos: { x: 31, y: 60 },
- },
- {
- id: 'gc',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Goron City',
- short: 'GC',
- type: 'Overworld',
- pos: { x: 44, y: 33 },
- },
- {
- id: 'zr',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Zora\'s River',
- short: 'ZR',
- type: 'Overworld',
- pos: { x: 68, y: 38 },
- },
- {
- id: 'sfm',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Sacred Forest Maedows',
- short: 'SFM',
- type: 'Overworld',
- pos: { x: 45, y: 6 },
- },
- ],
- },
- {
- id: 'sfm',
- bgColor: '#274e13',
- fgColor: '#000000',
- name: 'Sacred Forest Maedows',
- short: 'SFM',
- map: {
- pos: { x: 460, y: 220 },
- size: { x: 35, y: 100, },
- bg: {
- src: '/media/oot/minimap/sfm.png',
- off: { x: -20, y: 0 },
- scale: 4.3,
- },
- labelPos: { x: 15, y: 110 },
- },
- entrances: [
- {
- id: 'forest',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Forest Temple',
- short: 'Forest Temple',
- type: 'Dungeon',
- pos: { x: 18, y: 5 },
- },
- {
- id: 'wolf',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Wolf Grotto',
- short: 'Wolf Grotto',
- type: 'Grotto',
- pos: { x: 14, y: 87 },
- },
- {
- id: 'fairy',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Fairy Grotto',
- short: 'Fairy Grotto',
- type: 'Grotto',
- pos: { x: 18, y: 60 },
- },
- {
- id: 'storms',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Storms Grotto',
- short: 'Storms Grotto',
- type: 'Grotto',
- pos: { x: 25, y: 18 },
- },
- {
- id: 'lw',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Lost Woods',
- short: 'LW',
- type: 'Overworld',
- pos: { x: 14, y: 95 },
- },
- ],
- },
- {
- id: 'gy',
- bgColor: '#a64d79',
- fgColor: '#000000',
- name: 'Graveyard',
- short: 'Grave',
- map: {
- pos: { x: 310, y: 170 },
- size: { x: 100, y: 35, },
- bg: {
- src: '/media/oot/minimap/gy.png',
- off: { x: 0, y: -5 },
- scale: 1.5,
- },
- labelPos: { x: 65, y: 35 },
- },
- entrances: [
- {
- id: 'dh',
- bgColor: '#c27ba0',
- fgColor: '#000000',
- name: 'Dampe Hut',
- short: 'Dampe Hut',
- type: 'Interior',
- pos: { x: 22, y: 26 },
- },
- {
- id: 'shadow',
- bgColor: '#c27ba0',
- fgColor: '#000000',
- name: 'Shadow Temple',
- short: 'Shadow',
- type: 'Dungeon',
- pos: { x: 90, y: 16 },
- },
- {
- id: 'shield',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Shield Grave',
- short: 'Shield Grave',
- type: 'Grave',
- pos: { x: 30, y: 19 },
- },
- {
- id: 'race',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Dampe Race',
- short: 'Dampe Race',
- type: 'Grave',
- pos: { x: 25, y: 7 },
- },
- {
- id: 'sun',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Sun Song Grave',
- short: 'Sun Song Gr',
- type: 'Grave',
- pos: { x: 41, y: 21 },
- },
- {
- id: 'family',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Family Tomb',
- short: 'Family Tomb',
- type: 'Grave',
- pos: { x: 52, y: 15 },
- },
- {
- id: 'kak',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Kakariko',
- short: 'Kak',
- type: 'Overworld',
- pos: { x: 5, y: 20 },
- },
- ],
- },
- {
- id: 'kak',
- bgColor: '#ff9900',
- fgColor: '#000000',
- name: 'Kakariko Village',
- short: 'Kak',
- map: {
- pos: { x: 190, y: 140 },
- size: { x: 125, y: 100, },
- bg: {
- src: '/media/oot/minimap/kak.png',
- off: { x: -7, y: 0 },
- scale: 1.7,
- },
- labelPos: { x: 20, y: 95 },
- },
- entrances: [
- {
- id: 'talon',
- bgColor: '#f9cb9c',
- fgColor: '#000000',
- name: 'Carpenter Boss House',
- short: 'Talons',
- type: 'Interior',
- pos: { x: 66, y: 51 },
- },
- {
- id: 'skull',
- bgColor: '#f9cb9c',
- fgColor: '#000000',
- name: 'Skulltula House',
- short: 'Skulltula',
- type: 'Interior',
- pos: { x: 58, y: 73 },
- },
- {
- id: 'impaf',
- bgColor: '#f9cb9c',
- fgColor: '#000000',
- name: 'Impa Front',
- short: 'Impa Front',
- type: 'Interior',
- pos: { x: 58, y: 92 },
- },
- {
- id: 'impab',
- bgColor: '#f9cb9c',
- fgColor: '#000000',
- name: 'Impa Back',
- short: 'Impa Back',
- type: 'Interior',
- pos: { x: 71, y: 91 },
- },
- {
- id: 'shield',
- bgColor: '#f9cb9c',
- fgColor: '#000000',
- name: 'Shield Shop (Bazaar)',
- short: 'Shield Shop',
- type: 'Interior',
- pos: { x: 60, y: 28 },
- },
- {
- id: 'potion',
- bgColor: '#f9cb9c',
- fgColor: '#000000',
- name: 'Potion Shop',
- short: 'Potion Shop',
- type: 'SpecialInterior',
- pos: { x: 70, y: 28 },
- },
- {
- id: 'back',
- bgColor: '#f9cb9c',
- fgColor: '#000000',
- name: 'Shop Back',
- short: 'Shop Back',
- type: 'SpecialInterior',
- pos: { x: 80, y: 28 },
- },
- {
- id: 'witch',
- bgColor: '#f9cb9c',
- fgColor: '#000000',
- name: 'Witch',
- short: 'Witch',
- type: 'Interior',
- pos: { x: 84, y: 46 },
- },
- {
- id: 'arch',
- bgColor: '#f9cb9c',
- fgColor: '#000000',
- name: 'Archery',
- short: 'Archery',
- type: 'Interior',
- pos: { x: 72, y: 67 },
- },
- {
- id: 'mill',
- bgColor: '#f9cb9c',
- fgColor: '#000000',
- name: 'Windmill',
- short: 'Windmill',
- type: 'SpecialInterior',
- pos: { x: 96, y: 59 },
- },
- {
- id: 'botw',
- bgColor: '#a64d79',
- fgColor: '#000000',
- name: 'Bottom of the Well',
- short: 'Bottom Well',
- spacer: true,
- type: 'Dungeon',
- pos: { x: 84, y: 59 },
- },
- {
- id: 'open',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Open Grotto',
- short: 'Open Grotto',
- type: 'Grotto',
- pos: { x: 86, y: 37 },
- },
- {
- id: 'redead',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Redead Grotto',
- short: 'Redead Grotto',
- type: 'Grotto',
- pos: { x: 58, y: 57 },
- },
- {
- id: 'hf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Hyrule Field',
- short: 'HF',
- type: 'Overworld',
- pos: { x: 10, y: 74 },
- },
- {
- id: 'dmt',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Death Mountain Trail',
- short: 'DMT',
- type: 'Overworld',
- pos: { x: 67, y: 8 },
- },
- {
- id: 'gy',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Graveyard',
- short: 'Grave',
- type: 'Overworld',
- pos: { x: 112, y: 83 },
- },
- ],
- },
- {
- id: 'm1',
- bgColor: '#9900ff',
- fgColor: '#000000',
- name: 'Market 1',
- short: 'M1',
- map: {
- pos: { x: 100, y: 80 },
- size: { x: 100, y: 60, },
- bg: {
- src: '/media/oot/minimap/m1.png',
- off: { x: 0, y: -6 },
- scale: 1.0,
- },
- labelPos: { x: 38, y: 30 },
- },
- entrances: [
- {
- id: 'shield',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Shield Shop',
- short: 'Shield Shop',
- type: 'Interior',
- pos: { x: 81, y: 36 },
- },
- {
- id: 'potion',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Potion Shop',
- short: 'Potion Shop',
- type: 'Interior',
- pos: { x: 81, y: 27 },
- },
- {
- id: 'mask',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Mask Shop',
- short: 'Mask Shop',
- type: 'Interior',
- pos: { x: 75, y: 14 },
- },
- {
- id: 'sling',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Slingshot Game',
- short: 'Sling Game',
- type: 'Interior',
- pos: { x: 60, y: 14 },
- },
- {
- id: 'chuu',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Bombchu Bowling',
- short: 'Bombchu',
- type: 'Interior',
- pos: { x: 54, y: 30 },
- },
- {
- id: 'tcg',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Treasure Chest Game',
- short: 'TCG',
- type: 'Interior',
- pos: { x: 52, y: 48 },
- },
- {
- id: 'alleyl',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Backalley Left',
- short: 'Alley L',
- type: 'Overworld',
- pos: { x: 26, y: 48 },
- },
- {
- id: 'alleyr',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Backalley Right',
- short: 'Alley R',
- type: 'Overworld',
- pos: { x: 18, y: 10 },
- },
- {
- id: 'tot',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Temple of Time',
- short: 'ToT',
- type: 'Overworld',
- pos: { x: 92, y: 15 },
- },
- {
- id: 'hc',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Hyrule Castle',
- short: 'HC',
- type: 'Overworld',
- pos: { x: 67, y: 7 },
- },
- {
- id: 'm2',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Market 2',
- short: 'M2',
- type: 'Overworld',
- pos: { x: 66, y: 50 },
- },
- ],
- },
- {
- id: 'm2',
- bgColor: '#9900ff',
- fgColor: '#000000',
- name: 'Market 2',
- short: 'M2',
- map: {
- pos: { x: 205, y: 85 },
- size: { x: 18, y: 50, },
- bg: {
- src: '/media/oot/minimap/m2.png',
- off: { x: -3, y: -3 },
- scale: 3.6,
- },
- labelPos: { x: -2, y: 25 },
- },
- entrances: [
- {
- id: 'bp',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Big Poe',
- short: 'Big Poe',
- type: 'Interior',
- pos: { x: 15.5, y: 32 },
- },
- {
- id: 'hf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Hyrule Field',
- short: 'HF',
- type: 'Overworld',
- pos: { x: 8.5, y: 47.5 },
- },
- {
- id: 'm1',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Market 1',
- short: 'M1',
- type: 'Overworld',
- pos: { x: 8.5, y: 3 },
- },
- ],
- },
- {
- id: 'hc',
- bgColor: '#bf9000',
- fgColor: '#000000',
- name: 'Hyrule Castle',
- short: 'HC',
- map: {
- pos: { x: 100, y: 0 },
- size: { x: 120, y: 70, },
- bg: {
- src: '/media/oot/minimap/hc-gc.png',
- off: { x: 0, y: 0 },
- scale: 1.2,
- },
- labelPos: { x: 25, y: 65 },
- },
- entrances: [
- {
- id: 'hcfairy',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Hyrule Castle Fairy',
- short: 'HC Fairy',
- type: 'Interior',
- pos: { x: 61, y: 23 },
- },
- {
- id: 'gfairy',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Ganon\'s Castle Fairy',
- short: 'Ganon Fairy',
- type: 'Interior',
- pos: { x: 116, y: 51 },
- },
- {
- id: 'igc',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Inside Ganon\'s Castle',
- short: 'IGC',
- type: 'DungeonSpecial',
- pos: { x: 83, y: 30 },
- },
- {
- id: 'storms',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Storms Grotto',
- short: 'Storms Grotto',
- type: 'Grotto',
- pos: { x: 30, y: 29 },
- },
- {
- id: 'm1',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Market 1',
- short: 'M1',
- type: 'Overworld',
- pos: { x: 60, y: 66 },
- },
- ],
- },
- {
- id: 'hf',
- bgColor: '#674ea7',
- fgColor: '#000000',
- name: 'Hyrule Field',
- short: 'HF',
- map: {
- pos: { x: 90, y: 150 },
- size: { x: 100, y: 100, },
- bg: {
- src: '/media/oot/minimap/hf.png',
- off: { x: -16, y: 0 },
- scale: 1.8,
- },
- labelPos: { x: 100, y: 10 },
- },
- entrances: [
- {
- id: 'destiny',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Destiny Grotto',
- short: 'Destiny Grotto',
- type: 'Grotto',
- pos: { x: 33, y: 7 },
- },
- {
- id: 'tektite',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Tektite Grotto',
- short: 'Tektite',
- type: 'Grotto',
- pos: { x: 30, y: 23 },
- },
- {
- id: 'nw',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Northwest Grotto (near Market)',
- short: 'NW Grotto',
- type: 'Grotto',
- pos: { x: 48, y: 13 },
- },
- {
- id: 'nk',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Grotto near Kakariko',
- short: 'near Kak Gro',
- type: 'Grotto',
- pos: { x: 68, y: 8 },
- },
- {
- id: 'se',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Southeast Grotto',
- short: 'SE Grotto',
- type: 'Grotto',
- pos: { x: 55, y: 78 },
- },
- {
- id: 'open',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Open Grotto',
- short: 'Open Grotto',
- type: 'Grotto',
- pos: { x: 35, y: 86 },
- },
- {
- id: 'sg',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'South Grotto',
- short: 'South Grotto',
- type: 'Grotto',
- pos: { x: 28, y: 87 },
- },
- {
- id: 'cow',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Cow Grotto',
- short: 'Cow Grotto',
- type: 'Grotto',
- pos: { x: 13, y: 47 },
- },
- {
- id: 'town',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Town',
- short: 'Town',
- spacer: true,
- type: 'Overworld',
- pos: { x: 57, y: 10 },
- },
- {
- id: 'llr',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Lon Lon Ranch',
- short: 'LLR',
- type: 'Overworld',
- pos: { x: 45, y: 44 },
- },
- {
- id: 'kak',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Kakariko',
- short: 'Kak',
- type: 'Overworld',
- pos: { x: 80, y: 10 },
- },
- {
- id: 'zr',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Zora\'s River',
- short: 'ZR',
- type: 'Overworld',
- pos: { x: 94, y: 30 },
- },
- {
- id: 'kf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Kokiri Forest',
- short: 'KF',
- type: 'Overworld',
- pos: { x: 87, y: 57 },
- },
- {
- id: 'lh',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Lake Hylia',
- short: 'LH',
- type: 'Overworld',
- pos: { x: 25, y: 95 },
- },
- {
- id: 'gv',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Gerudo Valley',
- short: 'GV',
- type: 'Overworld',
- pos: { x: 6, y: 51 },
- },
- ],
- },
- {
- id: 'zr',
- bgColor: '#3c78d8',
- fgColor: '#000000',
- name: 'Zora\'s River',
- short: 'ZR',
- map: {
- pos: { x: 330, y: 100 },
- size: { x: 100, y: 60, },
- bg: {
- src: '/media/oot/minimap/zr.png',
- off: { x: 0, y: 0 },
- scale: 1.3,
- },
- labelPos: { x: 60, y: 40 },
- },
- entrances: [
- {
- id: 'storms',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Storms Grotto',
- short: 'Storms Grotto',
- type: 'Grotto',
- pos: { x: 8, y: 28 },
- },
- {
- id: 'open',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Open Grotto',
- short: 'Open Grotto',
- type: 'Grotto',
- pos: { x: 36, y: 32 },
- },
- {
- id: 'boulder',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Boulder Grotto',
- short: 'Boulder Grotto',
- type: 'Grotto',
- pos: { x: 40, y: 24 },
- },
- {
- id: 'hf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Hyrule Field',
- short: 'HF',
- spacer: true,
- type: 'Overworld',
- pos: { x: 10, y: 52 },
- },
- {
- id: 'lw',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Lost Woods',
- short: 'LW',
- type: 'Overworld',
- pos: { x: 90.5, y: 18 },
- },
- {
- id: 'zd',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Zora\'s Domain',
- short: 'ZD',
- type: 'Overworld',
- pos: { x: 96, y: 9 },
- },
- ],
- },
- {
- id: 'zd',
- bgColor: '#3c78d8',
- fgColor: '#000000',
- name: 'Zora\'s Domain',
- short: 'ZD',
- map: {
- pos: { x: 410, y: 100 },
- size: { x: 80, y: 100, },
- bg: {
- src: '/media/oot/minimap/zd.png',
- off: { x: -15, y: -5 },
- scale: 2.3,
- },
- labelPos: { x: 18, y: 40 },
- },
- entrances: [
- {
- id: 'shop',
- bgColor: '#aac2f1',
- fgColor: '#000000',
- name: 'Shop',
- short: 'Shop',
- type: 'Interior',
- pos: { x: 55, y: 85 },
- },
- {
- id: 'storms',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Storms Grotto',
- short: 'Storms Grotto',
- type: 'Grotto',
- pos: { x: 16, y: 64 },
- },
- {
- id: 'zr',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Zora\'s River',
- short: 'ZR',
- type: 'Overworld',
- pos: { x: 6, y: 73 },
- },
- {
- id: 'lh',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Lake Hylia',
- short: 'LH',
- type: 'Overworld',
- pos: { x: 35, y: 72 },
- },
- {
- id: 'zf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Zora\'s Fountain',
- short: 'ZF',
- type: 'Overworld',
- pos: { x: 58, y: 15 },
- },
- ],
- },
- {
- id: 'zf',
- bgColor: '#3c78d8',
- fgColor: '#000000',
- name: 'Zora\'s Fountain',
- short: 'ZF',
- map: {
- pos: { x: 410, y: 0 },
- size: { x: 90, y: 100, },
- bg: {
- src: '/media/oot/minimap/zf.png',
- off: { x: -5, y: 0 },
- scale: 2.1,
- },
- labelPos: { x: 60, y: 35 },
- },
- entrances: [
- {
- id: 'wall',
- bgColor: '#aac2f1',
- fgColor: '#000000',
- name: 'Fairy Wall',
- short: 'Fairy Wall',
- type: 'Interior',
- pos: { x: 61, y: 90 },
- },
- {
- id: 'jabu',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'Jabu Jabu\'s Belly',
- short: 'Jabu',
- type: 'Dungeon',
- pos: { x: 34, y: 38 },
- },
- {
- id: 'ice',
- bgColor: '#a64d79',
- fgColor: '#000000',
- name: 'Ice Cavern',
- short: 'Ice Cavern',
- type: 'Dungeon',
- pos: { x: 52, y: 10 },
- },
- {
- id: 'zd',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Zora\'s Domain',
- short: 'ZD',
- type: 'Overworld',
- pos: { x: 12, y: 55 },
- },
- ],
- },
- {
- id: 'lh',
- bgColor: '#4a86e8',
- fgColor: '#000000',
- name: 'Lake Hylia',
- short: 'LH',
- map: {
- pos: { x: 0, y: 265 },
- size: { x: 90, y: 100, },
- bg: {
- src: '/media/oot/minimap/lh.png',
- off: { x: -15, y: -5 },
- scale: 1.9,
- },
- labelPos: { x: 70, y: 65 },
- },
- entrances: [
- {
- id: 'dive',
- bgColor: '#cfe2f3',
- fgColor: '#000000',
- name: 'Lab Diving',
- short: 'Lab Dive',
- type: 'Interior',
- pos: { x: 30, y: 43 },
- },
- {
- id: 'fishing',
- bgColor: '#cfe2f3',
- fgColor: '#000000',
- name: 'Fishing Game',
- short: 'Fishing',
- type: 'Interior',
- pos: { x: 68, y: 43 },
- },
- {
- id: 'water',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Water Temple',
- short: 'Water Temple',
- type: 'Dungeon',
- pos: { x: 45, y: 65 },
- },
- {
- id: 'owl',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Owl Grotto',
- short: 'Owl Grotto',
- type: 'Grotto',
- pos: { x: 24, y: 65 },
- },
- {
- id: 'hf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Hyrule Field',
- short: 'HF',
- type: 'Overworld',
- pos: { x: 35, y: 5 },
- },
- {
- id: 'zd',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Zora\'s Domain',
- short: 'ZD',
- type: 'Overworld',
- pos: { x: 45, y: 40 },
- },
- ],
- },
- {
- id: 'llr',
- bgColor: '#f1c232',
- fgColor: '#000000',
- name: 'Lon Lon Ranch',
- short: 'LLR',
- map: {
- pos: { x: 100, y: 260 },
- size: { x: 80, y: 100, },
- bg: {
- src: '/media/oot/minimap/llr.png',
- off: { x: -22, y: -6 },
- scale: 2.6,
- },
- labelPos: { x: 40, y: 50 },
- },
- entrances: [
- {
- id: 'chicken',
- bgColor: '#ffd966',
- fgColor: '#000000',
- name: 'Chicken Game',
- short: 'Chicken',
- type: 'Interior',
- pos: { x: 54, y: 13 },
- },
- {
- id: 'stable',
- bgColor: '#ffd966',
- fgColor: '#000000',
- name: 'Stable',
- short: 'Stable',
- type: 'Interior',
- pos: { x: 50, y: 19 },
- },
- {
- id: 'tower',
- bgColor: '#ffd966',
- fgColor: '#000000',
- name: 'Tower',
- short: 'Tower',
- type: 'Interior',
- pos: { x: 17, y: 82 },
- },
- {
- id: 'grotto',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Grotto',
- short: 'Grotto',
- type: 'Grotto',
- pos: { x: 60, y: 85 },
- },
- {
- id: 'hf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Hyrule Field',
- short: 'HF',
- type: 'Overworld',
- pos: { x: 60, y: 5 },
- },
- ],
- },
- {
- id: 'gv',
- bgColor: '#b45f06',
- fgColor: '#000000',
- name: 'Gerudo Valley',
- short: 'GV',
- map: {
- pos: { x: 0, y: 190 },
- size: { x: 100, y: 75, },
- bg: {
- src: '/media/oot/minimap/gv.png',
- off: { x: -17, y: -5 },
- scale: 1.9,
- },
- labelPos: { x: 20, y: 55 },
- },
- entrances: [
- {
- id: 'tent',
- bgColor: '#b45f06',
- fgColor: '#000000',
- name: 'Tent',
- short: 'Tent',
- type: 'Interior',
- pos: { x: 44, y: 33 },
- },
- {
- id: 'str2',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Strength 2 Grotto',
- short: 'Str2 Grotto',
- type: 'Grotto',
- pos: { x: 60, y: 60 },
- },
- {
- id: 'storms',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Storms Grotto',
- short: 'Storms Grotto',
- type: 'Grotto',
- pos: { x: 39, y: 27 },
- },
- {
- id: 'hf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Hyrule Field',
- short: 'HF',
- type: 'Overworld',
- pos: { x: 93, y: 45 },
- },
- {
- id: 'gf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Gerudo Fortress',
- short: 'GF',
- type: 'Overworld',
- pos: { x: 6, y: 22 },
- },
- {
- id: 'wf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Waterfall',
- short: 'Waterfall',
- oneway: true,
- type: 'OverworldOneWay',
- pos: { x: 58, y: 69 },
- },
- ],
- },
- {
- id: 'gf',
- bgColor: '#b45f06',
- fgColor: '#000000',
- name: 'Gerudo Fortress',
- short: 'GF',
- map: {
- pos: { x: 0, y: 90 },
- size: { x: 90, y: 100, },
- bg: {
- src: '/media/oot/minimap/gf.png',
- off: { x: -5, y: -35 },
- scale: 2.5,
- },
- labelPos: { x: 15, y: 50 },
- },
- entrances: [
- {
- id: 'gtg',
- bgColor: '#a64d79',
- fgColor: '#000000',
- name: 'Gerudo Training Grounds',
- short: 'GTG',
- type: 'Dungeon',
- pos: { x: 48, y: 60 },
- },
- {
- id: 'storms',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Storms Grotto',
- short: 'Storms Grotto',
- type: 'Grotto',
- pos: { x: 52, y: 47 },
- },
- {
- id: 'gv',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Gerudo Valley',
- short: 'GV',
- type: 'Overworld',
- pos: { x: 43, y: 93 },
- },
- {
- id: 'hw',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Haunted Wasteland',
- short: 'Waste',
- type: 'Overworld',
- pos: { x: 7, y: 13 },
- },
- ],
- },
- {
- id: 'dcol',
- bgColor: '#f1c232',
- fgColor: '#000000',
- name: 'Desert Colossus',
- short: 'DCol',
- map: {
- pos: { x: 0, y: 0 },
- size: { x: 100, y: 90, },
- bg: {
- src: '/media/oot/minimap/dcol.png',
- off: { x: 0, y: -2 },
- scale: 2.0,
- },
- labelPos: { x: 50, y: 50 },
- },
- entrances: [
- {
- id: 'spirit',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Spirit Temple',
- short: 'Spirit',
- type: 'Dungeon',
- pos: { x: 10, y: 43.5 },
- },
- {
- id: 'fairy',
- bgColor: '#ffd966',
- fgColor: '#000000',
- name: 'Fairy',
- short: 'Fairy',
- type: 'Interior',
- pos: { x: 62, y: 21 },
- },
- {
- id: 'str2',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Strength 2 Grotto',
- short: 'Str2 Grotto',
- type: 'Grotto',
- pos: { x: 32, y: 28 },
- },
- {
- id: 'hw',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Haunted Wasteland',
- short: 'Waste',
- type: 'Overworld',
- pos: { x: 85, y: 40 },
- },
- ],
- },
- {
- id: 'hw',
- bgColor: '#f1c232',
- fgColor: '#000000',
- name: 'Haunted Wasteland',
- short: 'Waste',
- entrances: [
- {
- id: 'gf',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Gerudo Fortress',
- short: 'GF',
- type: 'Overworld',
- throughway: 'hw.dcol',
- },
- {
- id: 'dcol',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Desert Colossus',
- short: 'DCol',
- type: 'Overworld',
- throughway: 'hw.gf',
- },
- ],
- },
- {
- id: 'dmc',
- bgColor: '#ff0000',
- fgColor: '#000000',
- name: 'Death Mountain Crater',
- short: 'DMC',
- map: {
- pos: { x: 320, y: 0 },
- size: { x: 90, y: 100, },
- bg: {
- src: '/media/oot/minimap/dmc.png',
- off: { x: -15, y: -3 },
- scale: 1.9,
- },
- labelPos: { x: 52, y: 62 },
- },
- entrances: [
- {
- id: 'fairy',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Fairy',
- short: 'Fairy',
- type: 'Interior',
- pos: { x: 17, y: 68 },
- },
- {
- id: 'fire',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Fire Temple',
- short: 'Fire',
- type: 'Dungeon',
- pos: { x: 48, y: 6 },
- },
- {
- id: 'boulder',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Boulder Grotto',
- short: 'Boulder Gro',
- type: 'Grotto',
- pos: { x: 49, y: 86 },
- },
- {
- id: 'hammer',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Hammer Grotto',
- short: 'Hammer Gro',
- type: 'Grotto',
- pos: { x: 12, y: 35 },
- },
- {
- id: 'gc',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Goron City',
- short: 'GC',
- type: 'Overworld',
- pos: { x: 7, y: 46 },
- },
- {
- id: 'dmt',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Death Mountain Trail',
- short: 'DMT',
- type: 'Overworld',
- pos: { x: 25, y: 90 },
- },
- ],
- },
- {
- id: 'dmt',
- bgColor: '#ff0000',
- fgColor: '#000000',
- name: 'Death Mountain Trail',
- short: 'DMT',
- map: {
- pos: { x: 280, y: 70 },
- size: { x: 40, y: 100, },
- bg: {
- src: '/media/oot/minimap/dmt.png',
- off: { x: -12, y: 0 },
- scale: 3.8,
- },
- labelPos: { x: -10, y: 50 },
- },
- entrances: [
- {
- id: 'dc',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Dodongo\'s Cavern',
- short: 'DC',
- type: 'Dungeon',
- pos: { x: 8, y: 53 },
- },
- {
- id: 'fairy',
- bgColor: '#ea9999',
- fgColor: '#000000',
- name: 'Fairy',
- short: 'Fairy',
- type: 'Interior',
- pos: { x: 22, y: 10 },
- },
- {
- id: 'storms',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Storms Grotto',
- short: 'Storms Grotto',
- type: 'Grotto',
- pos: { x: 23, y: 48 },
- },
- {
- id: 'cow',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Cow Grotto',
- short: 'Cow Grotto',
- type: 'Grotto',
- pos: { x: 20, y: 57 },
- },
- {
- id: 'kak',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Kakariko',
- short: 'Kak',
- spacer: true,
- type: 'Overworld',
- pos: { x: 15, y: 94 },
- },
- {
- id: 'gc',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Goron City',
- short: 'GC',
- type: 'Overworld',
- pos: { x: 24, y: 40 },
- },
- {
- id: 'dmc',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Death Mountain Crater',
- short: 'DMC',
- type: 'Overworld',
- pos: { x: 29, y: 5 },
- },
- ],
- },
- {
- id: 'gc',
- bgColor: '#ff0000',
- fgColor: '#000000',
- name: 'Goron City',
- short: 'GC',
- map: {
- pos: { x: 220, y: 0 },
- size: { x: 90, y: 100, },
- bg: {
- src: '/media/oot/minimap/gc.png',
- off: { x: -2, y: 0 },
- scale: 2.0,
- },
- labelPos: { x: 25, y: 105 },
- },
- entrances: [
- {
- id: 'shop',
- bgColor: '#ea9999',
- fgColor: '#000000',
- name: 'Shop',
- short: 'Shop',
- type: 'Interior',
- pos: { x: 42, y: 50 },
- },
- {
- id: 'times',
- bgColor: '#b7b7b7',
- fgColor: '#000000',
- name: 'Times Grotto',
- short: 'Times Grotto',
- type: 'Grotto',
- pos: { x: 80, y: 17 },
- },
- {
- id: 'dmt',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Death Mountain Trail',
- short: 'DMT',
- type: 'Overworld',
- pos: { x: 50.5, y: 95 },
- },
- {
- id: 'lw',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Lost Woods',
- short: 'LW',
- type: 'Overworld',
- pos: { x: 64, y: 93 },
- },
- {
- id: 'dmc',
- bgColor: '#ff6d01',
- fgColor: '#000000',
- name: 'Death Mountain Crater',
- short: 'DMC',
- type: 'Overworld',
- pos: { x: 51.5, y: 5 },
- },
- ],
- },
- {
- id: 'tot',
- bgColor: '#ffffff',
- fgColor: '#000000',
- name: 'Temple of Time',
- short: 'ToT',
- entrances: [
- {
- id: 'temple',
- bgColor: '#b4a7d6',
- fgColor: '#000000',
- name: 'Temple of Time',
- short: 'Temple',
- type: 'SpecialInterior',
- },
- {
- id: 'm1',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Market 1',
- short: 'M1',
- type: 'Overworld',
- },
- ],
- },
- {
- id: 'deku',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'Deku Tree',
- short: 'Deku',
- entrances: [
- {
- id: 'main',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'Main',
- short: 'Main',
- type: 'Dungeon',
- throughway: 'deku.end',
- },
- {
- id: 'end',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'End',
- short: 'End',
- type: 'ChildBoss',
- throughway: 'deku.main',
- },
- ],
- },
- {
- id: 'gohma',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'Gohma',
- short: 'Gohma',
- type: 'ChildBoss',
- },
- {
- id: 'dc',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'Dodongo\'s Cavern',
- short: 'DC',
- entrances: [
- {
- id: 'main',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'Main',
- short: 'Main',
- type: 'Dungeon',
- throughway: 'dc.end',
- },
- {
- id: 'end',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'End',
- short: 'End',
- type: 'ChildBoss',
- throughway: 'dc.main',
- },
- ],
- },
- {
- id: 'kd',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'King Dodongo',
- short: 'Dodongo',
- type: 'ChildBoss',
- },
- {
- id: 'jabu',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'Jabu Jabu\'s Belly',
- short: 'Jabu',
- entrances: [
- {
- id: 'main',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'Main',
- short: 'Main',
- type: 'Dungeon',
- throughway: 'jabu.end',
- },
- {
- id: 'end',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'End',
- short: 'End',
- type: 'ChildBoss',
- throughway: 'jabu.main',
- },
- ],
- },
- {
- id: 'barinade',
- bgColor: '#ead1dc',
- fgColor: '#000000',
- name: 'Barinade',
- short: 'Barinade',
- type: 'ChildBoss',
- },
- {
- id: 'forest',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Forest Temple',
- short: 'Forest',
- entrances: [
- {
- id: 'main',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Main',
- short: 'Main',
- spacer: true,
- type: 'Dungeon',
- throughway: 'forest.end',
- },
- {
- id: 'end',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'End',
- short: 'End',
- spacer: true,
- type: 'AdultBoss',
- throughway: 'forest.main',
- },
- ],
- },
- {
- id: 'pg',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Phantom Ganon',
- short: 'PG',
- spacer: true,
- type: 'AdultBoss',
- },
- {
- id: 'fire',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Fire Temple',
- short: 'Fire',
- entrances: [
- {
- id: 'main',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Main',
- short: 'Main',
- type: 'Dungeon',
- throughway: 'fire.end',
- },
- {
- id: 'end',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'End',
- short: 'End',
- type: 'AdultBoss',
- throughway: 'fire.main',
- },
- ],
- },
- {
- id: 'volvo',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Volvagia',
- short: 'Volvagia',
- type: 'AdultBoss',
- },
- {
- id: 'water',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Water Temple',
- short: 'Water',
- entrances: [
- {
- id: 'main',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Main',
- short: 'Main',
- type: 'Dungeon',
- throughway: 'water.end',
- },
- {
- id: 'end',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'End',
- short: 'End',
- type: 'AdultBoss',
- throughway: 'water.main',
- },
- ],
- },
- {
- id: 'morpha',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Morpha',
- short: 'Morpha',
- type: 'AdultBoss',
- },
- {
- id: 'shadow',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Shadow Temple',
- short: 'Shadow',
- entrances: [
- {
- id: 'main',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Main',
- short: 'Main',
- type: 'Dungeon',
- throughway: 'shadow.end',
- },
- {
- id: 'end',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'End',
- short: 'End',
- type: 'AdultBoss',
- throughway: 'shadow.main',
- },
- ],
- },
- {
- id: 'bongo',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Bongo Bongo',
- short: 'Bongo Bongo',
- type: 'AdultBoss',
- },
- {
- id: 'spirit',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Spirit Temple',
- short: 'Spirit',
- entrances: [
- {
- id: 'main',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Main',
- short: 'Main',
- type: 'Dungeon',
- throughway: 'spirit.end',
- },
- {
- id: 'end',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'End',
- short: 'End',
- type: 'AdultBoss',
- throughway: 'spirit.main',
- },
- ],
- },
- {
- id: 'tr',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Twinrova',
- short: 'Twinrova',
- type: 'AdultBoss',
- },
- {
- id: 'igc',
- bgColor: '#d5a6bd',
- fgColor: '#000000',
- name: 'Inside Ganon\'s Castle',
- short: 'IGC',
- entrances: [
- {
- id: 'main',
- bgColor: '#a64d79',
- fgColor: '#000000',
- name: 'Main',
- short: 'Main',
- spacer: true,
- type: 'Dungeon',
- throughway: 'igc.end',
- },
- {
- id: 'end',
- bgColor: '#a64d79',
- fgColor: '#000000',
- name: 'End',
- short: 'End',
- spacer: true,
- type: 'SpecialBoss',
- throughway: 'igc.main',
- },
- ],
- },
- {
- id: 'ganon',
- bgColor: '#a64d79',
- fgColor: '#000000',
- name: 'Ganon',
- short: 'Ganon',
- spacer: true,
- type: 'SpecialBoss',
- },
- {
- id: 'botw',
- bgColor: '#a64d79',
- fgColor: '#000000',
- name: 'Bottom of the Well',
- short: 'Bottom Well',
- type: 'Dungeon',
- },
- {
- id: 'ice',
- bgColor: '#a64d79',
- fgColor: '#000000',
- name: 'Ice Cavern',
- short: 'Ice Cavern',
- type: 'Dungeon',
- },
- {
- id: 'gtg',
- bgColor: '#a64d79',
- fgColor: '#000000',
- name: 'Gerudo Training Grounds',
- short: 'GTG',
- type: 'Dungeon',
- },
- {
- id: 'songs',
- bgColor: '#000000',
- fgColor: '#ffffff',
- name: 'Warp Songs',
- short: 'Songs',
- entrances: [
- {
- id: 'minuet',
- bgColor: '#38761d',
- fgColor: '#ffffff',
- name: 'Minuet of Forest',
- short: 'Minuet',
- oneway: true,
- type: 'WarpSong',
- icon: '/media/oot/icons/song-minuet.png',
- },
- {
- id: 'bolero',
- bgColor: '#38761d',
- fgColor: '#ffffff',
- name: 'Bolero of Fire',
- short: 'Bolero',
- oneway: true,
- type: 'WarpSong',
- icon: '/media/oot/icons/song-bolero.png',
- },
- {
- id: 'serenade',
- bgColor: '#38761d',
- fgColor: '#ffffff',
- name: 'Serenade of Water',
- short: 'Serenade',
- oneway: true,
- type: 'WarpSong',
- icon: '/media/oot/icons/song-serenade.png',
- },
- {
- id: 'nocturne',
- bgColor: '#38761d',
- fgColor: '#ffffff',
- name: 'Nocturne of Shadow',
- short: 'Nocturne',
- oneway: true,
- type: 'WarpSong',
- icon: '/media/oot/icons/song-nocturne.png',
- },
- {
- id: 'requiem',
- bgColor: '#38761d',
- fgColor: '#ffffff',
- name: 'Requiem of Spirit',
- short: 'Requiem',
- oneway: true,
- type: 'WarpSong',
- icon: '/media/oot/icons/song-requiem.png',
- },
- {
- id: 'prelude',
- bgColor: '#38761d',
- fgColor: '#ffffff',
- name: 'Prelude of Light',
- short: 'Prelude',
- oneway: true,
- type: 'WarpSong',
- icon: '/media/oot/icons/song-prelude.png',
- },
- ],
- },
- {
- id: 'spawns',
- bgColor: '#000000',
- fgColor: '#ffffff',
- name: 'Spawns',
- short: 'Spawns',
- entrances: [
- {
- id: 'child',
- bgColor: '#38761d',
- fgColor: '#ffffff',
- name: 'Child Spawn',
- short: 'Child',
- oneway: true,
- type: 'Spawn',
- icon: '/media/oot/icons/link-child.png',
- iconSize: 10,
- },
- {
- id: 'adult',
- bgColor: '#38761d',
- fgColor: '#ffffff',
- name: 'Adult Spawn',
- short: 'Adult',
- oneway: true,
- type: 'Spawn',
- icon: '/media/oot/icons/link-adult.png',
- },
- ],
- },
- {
- id: 'owls',
- bgColor: '#000000',
- fgColor: '#ffffff',
- name: 'Owl Drops',
- short: 'Owls',
- entrances: [
- {
- id: 'lhowl',
- bgColor: '#38761d',
- fgColor: '#ffffff',
- name: 'Lake Hylia Owl',
- short: 'LH Owl',
- oneway: true,
- type: 'OwlDrop',
- icon: '/media/oot/icons/owl-lake.png',
- },
- {
- id: 'dmtowl',
- bgColor: '#38761d',
- fgColor: '#ffffff',
- name: 'Death Mountain Trail Owl',
- short: 'Trail Owl',
- oneway: true,
- type: 'OwlDrop',
- icon: '/media/oot/icons/owl-trail.png',
- },
- ],
- },
-];
-
-const DUNGEONS = [
- {
- id: 'd1',
- bgColor: '#ff00ff',
- fgColor: '#000000',
- name: 'Dungeon Entrances',
- short: 'Dungeon Entrances',
- type: 'main',
- entrances: [
- 'deku.main',
- 'dc.main',
- 'jabu.main',
- 'forest.main',
- 'fire.main',
- 'water.main',
- 'shadow.main',
- 'spirit.main',
- 'igc.main',
- 'botw',
- 'ice',
- 'gtg',
- ],
- },
- {
- id: 'd2',
- bgColor: '#ff00ff',
- fgColor: '#000000',
- name: 'Dungeon Exits',
- short: 'Dungeon Exits',
- type: 'end',
- entrances: [
- 'deku.end',
- 'dc.end',
- 'jabu.end',
- 'forest.end',
- 'fire.end',
- 'water.end',
- 'shadow.end',
- 'spirit.end',
- 'igc.end',
- ],
- },
- {
- id: 'boss',
- bgColor: '#ff00ff',
- fgColor: '#000000',
- name: 'Bosses',
- short: 'Bosses',
- entrances: [
- 'gohma',
- 'kd',
- 'barinade',
- 'pg',
- 'volvo',
- 'morpha',
- 'bongo',
- 'tr',
- 'ganon',
- ],
- },
-];
-
-const ROOMS = [
- {
- id: 'trash',
- bgColor: '#333333',
- fgColor: '#dddddd',
- name: 'Trash',
- short: 'Trash',
- multi: true,
- },
- {
- id: 'shop',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Shop',
- short: 'Shop',
- multi: true,
- },
- {
- id: 'zlf',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'ZL Fairy',
- short: 'ZL Fairy',
- multi: true,
- },
- {
- id: 'toti',
- bgColor: '#ff0000',
- fgColor: '#000000',
- name: 'Temple of Time',
- short: 'Temple of Time',
- },
- {
- id: 'bcb',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Bombchu Bowling',
- short: 'Bombchu Bow',
- },
- {
- id: 'fish',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Fishing Game',
- short: 'Fishing',
- },
- {
- id: 'sling',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Slingshot Game',
- short: 'Slingshot Game',
- },
- {
- id: 'arch',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Archery Game',
- short: 'Archery Game',
- },
- {
- id: 'tow',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Lon Lon Ranch Tower',
- short: 'Ranch Tower',
- },
- {
- id: 'chick',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Chicken Game',
- short: 'Chicken Game',
- },
- {
- id: 'mill',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Windmill',
- short: 'Windmill',
- },
- {
- id: 'tomb',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Family Tomb',
- short: 'Family Tomb',
- },
- {
- id: 'ssg',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Sun Song Grave',
- short: 'Sun Song Grave',
- },
- {
- id: 'mask',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Mask Shop',
- short: 'Mask Shop',
- },
- {
- id: 'poe',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Big Poe',
- short: 'Big Poe',
- },
- {
- id: 'thtr',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Mask Theater',
- short: 'Mask Theater',
- },
- {
- id: 'skull',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Skulltula House',
- short: 'Skulltula House',
- },
- {
- id: 'tcg',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Treasute Chest Game',
- short: 'TCG',
- },
- {
- id: 'lab',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Lab Diving',
- short: 'Lab Dive',
- },
- {
- id: 'tek',
- bgColor: '#fffd00',
- fgColor: '#000000',
- name: 'Tektite Grotto',
- short: 'Tektite',
- },
-];
-
-const TYPE_RESTRICTIONS = {
- OverworldOneWay: [
- 'WarpSong',
- 'BlueWarp',
- 'OwlDrop',
- 'OverworldOneWay',
- 'Overworld',
- 'Extra',
- ],
- OwlDrop: [
- 'WarpSong',
- 'BlueWarp',
- 'OwlDrop',
- 'OverworldOneWay',
- 'Overworld',
- 'Extra',
- ],
- Spawn: [
- 'Spawn',
- 'WarpSong',
- 'BlueWarp',
- 'OwlDrop',
- 'OverworldOneWay',
- 'Overworld',
- 'Interior',
- 'SpecialInterior',
- 'Extra',
- ],
- WarpSong: [
- 'Spawn',
- 'WarpSong',
- 'BlueWarp',
- 'OwlDrop',
- 'OverworldOneWay',
- 'Overworld',
- 'Interior',
- 'SpecialInterior',
- 'Extra',
- ],
-};
-
-const CONTEXT = React.createContext({});
-
-const useTracker = () => React.useContext(CONTEXT);
-
-const mapEntrance = (area, entrance) => ({
- ...entrance,
- id: `${area.id}.${entrance.id}`,
- area,
-});
-
-const getArea = (id) => {
- return AREAS.find((area) => area.id === id);
-};
-
-const getEntranceOfArea = (area, entranceId) => {
- if (!area) return null;
- if (!area.entrances) return null;
- const entrance = area.entrances.find((entrance) => entrance.id === entranceId);
- return mapEntrance(area, entrance);
-};
-
-const getEntrance = (id) => {
- if (!id) return null;
- const dotPos = id.indexOf('.');
- if (dotPos === -1) {
- return getArea(id);
- }
- const areaId = id.substring(0, dotPos);
- const entranceId = id.substring(dotPos + 1);
- const area = getArea(areaId);
- const entrance = getEntranceOfArea(area, entranceId);
- return entrance;
-};
-
-const getRoom = (id) => {
- return ROOMS.find((room) => room.id === id);
-};
-
-const entranceShort = (entrance) => {
- if (!entrance) return null;
- return entrance.area
- ? `${entrance.area.short} (${entrance.short})`
- : entrance.short;
-};
-
-const entranceName = (entrance) => {
- if (!entrance) return null;
- return entrance.area
- ? `[${entrance.area.short}] ${entrance.name}`
- : entrance.name;
-};
-
-const entranceFull = (entrance) => {
- if (!entrance) return null;
- return entrance.area
- ? `${entrance.area.name} - ${entrance.name}`
- : entrance.name;
-};
-
-const entranceStyle = (entrance) => {
- if (!entrance) return null;
- return {
- backgroundColor: entrance.bgColor,
- color: entrance.fgColor,
- };
-};
-
-const resolvePath = (connections, from) => {
- if (!connections[from]) return { dst: null, via: [] };
- const dstEntrance = getEntrance(connections[from]);
- if (!dstEntrance || !dstEntrance.throughway) return { dst: connections[from], via: [] };
- const path = resolvePath(connections, dstEntrance.throughway);
- if (!path.dst) return { dst: connections[from], via: path.via };
- return { dst: path.dst, via: [connections[from], ...path.via] };
-};
-
-const vecAdd = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
-
-const vecMul = (v, f) => ({ x: v.x * f, y: v.y * f });
-
-const MAPS = AREAS
- .filter((area) => !!area.map)
- .map((area) => ({
- color: area.bgColor,
- id: area.id,
- labelPos: area.map.labelPos ? vecAdd(area.map.pos, area.map.labelPos) : null,
- name: area.name,
- pos: area.map.pos,
- short: area.short,
- size: area.map.size,
- bg: {
- src: area.map.bg.src,
- pos: vecAdd(area.map.pos, area.map.bg.off),
- size: vecMul(area.map.size, area.map.bg.scale),
- },
- entrances: area.entrances
- .filter((entrance) => entrance.pos)
- .map((entrance) => ({
- id: `${area.id}.${entrance.id}`,
- name: entranceFull({ ...entrance, area }),
- pos: vecAdd(area.map.pos, entrance.pos),
- color: entrance.bgColor,
- })),
- }));
-
-const getMapEntrance = (id) => {
- if (!id) return null;
- const dotPos = id.indexOf('.');
- if (dotPos === -1) {
- return null;
- }
- const areaId = id.substring(0, dotPos);
- const area = MAPS.find((a) => a.id === areaId);
- if (!area) return null;
- const entrance = area.entrances.find((e) => e.id === id);
- return entrance;
-};
-
-const SelectBox = ({ id, name, onChange, options, value }) => {
- const [open, setOpen] = React.useState(false);
- const [search, setSearch] = React.useState('');
-
- const ref = React.useRef();
- const searchRef = React.useRef();
-
- const valueEntrance = React.useMemo(() => getEntrance(value) || getRoom(value), [value]);
-
- const searcher = React.useMemo(() => {
- return new FuzzySearch(options, ['id', 'name', 'short', 'fullName'], { sort: true });
- }, [options]);
-
- const results = React.useMemo(() => {
- return searcher.search(search);
- }, [search, searcher]);
-
- React.useEffect(() => {
- const handleEventOutside = e => {
- if (ref.current && !ref.current.contains(e.target)) {
- setOpen(false);
- }
- };
- document.addEventListener('mousedown', handleEventOutside, true);
- document.addEventListener('focus', handleEventOutside, true);
- return () => {
- document.removeEventListener('mousedown', handleEventOutside, true);
- document.removeEventListener('focus', handleEventOutside, true);
- };
- }, []);
-
- const classNames = ['entrance-select'];
- if (open) classNames.push('is-open');
-
- return <div className={classNames.join(' ')} ref={ref}>
- <input
- className="entrance-search"
- id={id}
- onChange={({ target: { value } }) => setSearch(value)}
- onFocus={() => setOpen(true)}
- ref={searchRef}
- type="search"
- value={search}
- />
- <div
- aria-controls={`${id}.options`}
- aria-expanded={open ? 'true' : 'false'}
- aria-haspopup={`${id}.options`}
- className="entrance-value"
- onClick={() => {
- setOpen(true);
- searchRef.current.focus();
- searchRef.current.select();
- }}
- onContextMenu={(e) => {
- if (value) {
- onChange({ target: { name, value: null } });
- } else {
- onChange({ target: { name, value: 'trash' } });
- setSearch('');
- }
- e.preventDefault();
- e.stopPropagation();
- }}
- role="combobox"
- style={entranceStyle(valueEntrance)}
- title={entranceFull(valueEntrance)}
- >
- {entranceShort(valueEntrance)}
- </div>
- <div className="entrance-options" id={`${id}.options`} role="listbox">
- {results.map((entrance) =>
- <div
- className="entrance-option"
- key={entrance.id}
- onClick={() => {
- onChange({ target: { name, value: entrance.id } });
- setOpen(false);
- setSearch('');
- }}
- role="option"
- style={entranceStyle(entrance)}
- >
- {entranceName(entrance)}
- </div>
- )}
- </div>
- </div>;
-};
-
-SelectBox.propTypes = {
- className: PropTypes.string,
- id: PropTypes.string,
- name: PropTypes.string,
- onChange: PropTypes.func,
- options: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.string,
- name: PropTypes.string,
- })),
- value: PropTypes.string,
-};
-
-const EntranceGroup = ({ checked = 0, children, group, total = 0 }) => {
- return <div className="entrance-group">
- <h2 style={entranceStyle(group)}>
- {entranceName(group)}
- {checked && checked !== total ?
- <span className="checks">{checked}/{total}</span>
- : null}
- </h2>
- {children}
- </div>;
-};
-
-EntranceGroup.propTypes = {
- checked: PropTypes.number,
- children: PropTypes.node,
- group: PropTypes.shape({
- bgColor: PropTypes.string,
- fgColor: PropTypes.string,
- name: PropTypes.string,
- }),
- total: PropTypes.number,
-};
-
-const EntranceRow = ({ entranceId }) => {
- const entrance = React.useMemo(() => getEntrance(entranceId), [entranceId]);
-
- const { connections, entrances, setConnection } = useTracker();
-
- const options = React.useMemo(() => {
- if (entrance.type && TYPE_RESTRICTIONS[entrance.type]) {
- return entrances.filter((e) =>
- e.type && TYPE_RESTRICTIONS[entrance.type].includes(e.type));
- }
- return entrances;
- }, [entrances]);
-
- const className = React.useMemo(() => {
- const classNames = ['entrance-row'];
- if (entrance.spacer) classNames.push('mt-2');
- if (connections[entrance.id] === 'trash') classNames.push('is-trash');
- return classNames.join(' ');
- }, [entrance, connections]);
-
- return <div className={className}>
- <label
- className="entrance-label"
- htmlFor={entranceId}
- style={entranceStyle(entrance)}
- title={entranceFull(entrance)}
- >
- {entranceShort(entrance)}
- </label>
- <SelectBox
- id={entranceId}
- name={entranceId}
- onChange={({ target: { name, value } }) => setConnection(name, value)}
- options={options}
- value={connections[entranceId]}
- />
- </div>;
-};
-
-EntranceRow.propTypes = {
- entranceId: PropTypes.string,
-};
-
-const MapEntrance = ({ entrance }) => {
- const {
- connections,
- isDragging,
- onMapEntranceClick,
- setConnection,
- } = useTracker();
-
- const className = React.useMemo(() => {
- const cs = ['entrance'];
- if (connections[entrance.id] === 'trash') cs.push('is-trash');
- if (isDragging(entrance)) cs.push('is-dragging');
- return cs.join(' ');
- }, [connections, entrance, isDragging]);
-
- const path = React.useMemo(() => {
- return resolvePath(connections, entrance.id);
- }, [connections, entrance]);
-
- const destination = React.useMemo(() => {
- return getEntrance(path.dst) || getRoom(path.dst);
- }, [path]);
-
- const onClick = React.useCallback((e) => {
- onMapEntranceClick(entrance);
- e.preventDefault();
- e.stopPropagation();
- }, [entrance, onMapEntranceClick]);
-
- const onContext = React.useCallback((e) => {
- if (connections[entrance.id]) {
- setConnection(entrance.id, null);
- } else {
- setConnection(entrance.id, 'trash');
- }
- e.preventDefault();
- e.stopPropagation();
- }, [connections, entrance, setConnection]);
-
- const description = React.useMemo(() => {
- const parts = [
- entranceShort(destination),
- ...path.via.map((id) => `via ${entranceShort(getEntrance(id))}`),
- `@ ${entranceShort(getEntrance(entrance.id))}`,
- ];
- return parts.join(' ');
- }, [destination, entrance, path]);
-
- if (destination) {
- return <rect
- x={entrance.pos.x - 3}
- y={entrance.pos.y - 3}
- width={6}
- height={6}
- className={className}
- fill={destination.bgColor}
- stroke={destination.fgColor}
- onClick={onClick}
- onContextMenu={onContext}
- >
- <title>{description}</title>
- </rect>;
- }
-
- return <circle
- cx={entrance.pos.x}
- cy={entrance.pos.y}
- r="3"
- className={className}
- fill={entrance.color}
- stroke="#000000"
- onClick={onClick}
- onContextMenu={onContext}
- >
- <title>{entrance.name}</title>
- </circle>;
-};
-
-MapEntrance.propTypes = {
- entrance: PropTypes.shape({
- color: PropTypes.string,
- id: PropTypes.string,
- name: PropTypes.string,
- pos: PropTypes.shape({
- x: PropTypes.number,
- y: PropTypes.number,
- })
- }),
-};
-
-const MapConnector = ({ from, id, isTrash, to, via }) => {
- const { onConnectorClick } = useTracker();
-
- const className = React.useMemo(() => {
- const cs = ['connector'];
- if (isTrash) cs.push('is-trash');
- if (via.length) cs.push('is-via');
- return cs.join(' ');
- }, [isTrash, via]);
-
- return <line
- className={className}
- onClick={() => onConnectorClick(id)}
- x1={from.pos.x}
- y1={from.pos.y}
- x2={to.pos.x}
- y2={to.pos.y}
- />;
-};
-
-MapConnector.propTypes = {
- from: PropTypes.shape({
- pos: PropTypes.shape({
- x: PropTypes.number,
- y: PropTypes.number,
- })
- }),
- id: PropTypes.string,
- isTrash: PropTypes.bool,
- to: PropTypes.shape({
- pos: PropTypes.shape({
- x: PropTypes.number,
- y: PropTypes.number,
- })
- }),
- via: PropTypes.arrayOf(PropTypes.string),
-};
-
-const MapAnnotation = ({ annotation }) => {
- return <image
- className="annotation"
- href={annotation.icon}
- x={annotation.pos.x}
- y={annotation.pos.y}
- width={annotation.size}
- height={annotation.size}
- >
- <title>{annotation.name}</title>
- </image>;
-};
-
-MapAnnotation.propTypes = {
- annotation: PropTypes.shape({
- icon: PropTypes.string,
- name: PropTypes.string,
- pos: PropTypes.shape({
- x: PropTypes.number,
- y: PropTypes.number,
- }),
- size: PropTypes.number,
- }),
-};
-
-const initPrefs = () => {
- const dump = localStorage.getItem('zootr.mixed-pools-tracker-prefs');
- if (dump) {
- return JSON.parse(dump);
- }
- return {
- showConnectors: true,
- showEntrances: true,
- showLabels: true,
- showMaps: false,
- showWarps: true,
- };
-};
-
-const MixedPoolsTracker = () => {
- const { t } = useTranslation();
-
- const [connections, setConnections] = React.useState({});
- const [dragging, setDragging] = React.useState(null);
- const [prefs, setPrefs] = React.useState(initPrefs());
- const [trashConnectors, setTrashConnectors] = React.useState([]);
-
- const setConnection = React.useCallback((src, dst) => {
- setConnections((c) => {
- const newConn = { ...c };
- const srcEntrance = getEntrance(src);
- const oldTarget = getEntrance(c[src]);
- if (oldTarget && (!srcEntrance || !srcEntrance.oneway)) {
- // unset old connection
- newConn[c[src]] = null;
- }
- newConn[src] = dst;
- if (dst && srcEntrance && !srcEntrance.oneway) {
- newConn[dst] = src;
- }
- return newConn;
- });
- }, []);
-
- const entrances = React.useMemo(() => {
- const options = [];
- ROOMS.forEach((room) => {
- options.push(room);
- });
- AREAS.forEach((area) => {
- if (area.entrances) {
- area.entrances.forEach((entrance) => {
- if (entrance.oneway) return;
- options.push(getEntranceOfArea(area, entrance.id));
- });
- } else {
- options.push(getEntrance(area.id));
- }
- });
- return options.map((option) => ({
- ...option,
- fullName: entranceFull(option),
- }));
- }, []);
-
- const isDragging = React.useCallback((entrance) => {
- return dragging === entrance.id;
- }, [dragging]);
-
- const onMapEntranceClick = React.useCallback((entrance) => {
- if (dragging) {
- if (dragging !== entrance.id) {
- setConnection(dragging, entrance.id);
- }
- setDragging(null);
- } else {
- setDragging(entrance.id);
- }
- }, [dragging, setConnection, setDragging]);
-
- const onConnectorClick = React.useCallback((id) => {
- setTrashConnectors((tc) => {
- if (tc.includes(id)) {
- return tc.filter((tid) => tid !== id);
- }
- return [...tc, id];
- });
- }, []);
-
- const context = React.useMemo(() => ({
- connections,
- entrances,
- isDragging,
- onConnectorClick,
- onMapEntranceClick,
- setConnection,
- }), [
- connections,
- entrances,
- isDragging,
- onConnectorClick,
- onMapEntranceClick,
- setConnection,
- ]);
-
- const superGroups = React.useMemo(() => {
- const sg = [
- {
- key: 'one',
- groups: ['kf', 'lw', 'sfm', 'gy'],
- },
- {
- key: 'two',
- groups: ['kak', 'm1', 'm2', 'hc'],
- },
- {
- key: 'three',
- groups: ['hf', 'zr', 'zd', 'zf'],
- },
- {
- key: 'four',
- groups: ['lh', 'llr', 'gv', 'gf', 'dcol', 'hw'],
- },
- {
- key: 'five',
- groups: ['dmc', 'dmt', 'gc', 'tot'],
- },
- ];
- sg.forEach((superGroup) => {
- superGroup.groups = superGroup.groups.map((areaId) => {
- const area = getArea(areaId);
- const entranceIds = area.entrances.map((entrance) => `${area.id}.${entrance.id}`);
- const checked = entranceIds.filter((entranceId) =>
- Object.entries(connections).find(([a, b]) => b && a === entranceId)
- ).length;
- return {
- area,
- entranceIds,
- checked,
- total: area.entrances.length,
- };
- });
- });
- return sg;
- }, [connections]);
-
- const connectors = React.useMemo(() => {
- const cs = [];
- Object.entries(connections).forEach(([from]) => {
- const fromEntrance = getEntrance(from);
- if (!fromEntrance) return;
- const path = resolvePath(connections, from);
- if (!path.dst) return;
- if (from > path.dst && !fromEntrance.oneway) return;
- const fromMap = getMapEntrance(from);
- if (!fromMap) return;
- const toMap = getMapEntrance(path.dst);
- if (!toMap) return;
- const id = `${fromMap.id}-${toMap.id}`;
- const isTrash = trashConnectors.includes(id);
- cs.push({
- id,
- from: fromMap,
- to: toMap,
- via: path.via,
- isTrash,
- });
- });
- return cs;
- }, [connections, trashConnectors]);
-
- const annotations = React.useMemo(() => {
- const annotate = [
- 'songs.minuet',
- 'songs.bolero',
- 'songs.serenade',
- 'songs.nocturne',
- 'songs.requiem',
- 'songs.prelude',
- 'spawns.child',
- 'spawns.adult',
- 'owls.lhowl',
- 'owls.dmtowl',
- ];
- const ans = [];
- annotate.forEach((id) => {
- if (!connections[id]) return;
- const srcEntrance = getEntrance(id);
- if (!srcEntrance) return;
- const dstMap = getMapEntrance(connections[id]);
- if (!dstMap) return;
- ans.push({
- icon: srcEntrance.icon,
- name: srcEntrance.name,
- pos: vecAdd(dstMap.pos, dstMap.annotationOffset || { x: 0, y: 0 }),
- size: srcEntrance.iconSize || 8,
- });
- });
- return ans;
- }, [connections]);
-
- const save = React.useCallback(() => {
- try {
- const dump = JSON.stringify({ connections, trashConnectors });
- localStorage.setItem('zootr.mixed-pools-tracker-save', dump);
- toastr.success(t('general.saveSuccess'));
- } catch (e) {
- toastr.error(t('general.saveError'));
- console.error(e);
- }
- }, [connections, t, trashConnectors]);
-
- const load = React.useCallback(() => {
- try {
- const dump = localStorage.getItem('zootr.mixed-pools-tracker-save');
- if (!dump) {
- toastr.error(t('general.loadError'));
- return;
- }
- const { connections, trashConnectors } = JSON.parse(dump);
- if (connections) {
- setConnections(connections);
- } else {
- setConnections({});
- }
- if (trashConnectors) {
- setTrashConnectors(trashConnectors);
- } else {
- setTrashConnectors([]);
- }
- toastr.success(t('general.loadSuccess'));
- } catch (e) {
- toastr.error(t('general.loadError'));
- console.error(e);
- }
- }, [setConnections, t]);
-
- const reset = React.useCallback(() => {
- try {
- setConnections({});
- setTrashConnectors([]);
- toastr.success(t('general.resetSuccess'));
- } catch (e) {
- toastr.error(t('general.resetError'));
- }
- }, [t]);
-
- const togglePref = React.useCallback((which) => {
- setPrefs((oldPrefs) => {
- const newPrefs = {
- ...oldPrefs,
- [which]: !oldPrefs[which],
- };
- localStorage.setItem('zootr.mixed-pools-tracker-prefs', JSON.stringify(newPrefs));
- return newPrefs;
- });
- }, []);
-
- return <CONTEXT.Provider value={context}>
- <div className="mixed-pools-tracker">
- <div className="columns">
- {superGroups.map((sg) =>
- <div className="column" key={sg.key}>
- {sg.groups.map(group =>
- <EntranceGroup
- checked={group.checked}
- group={group.area}
- key={group.area.id}
- total={group.total}
- >
- {group.entranceIds.map((entranceId) =>
- <EntranceRow entranceId={entranceId} key={entranceId} />
- )}
- </EntranceGroup>
- )}
- </div>
- )}
- </div>
- <div className="columns">
- {DUNGEONS.map((area) =>
- <div className="column" key={area.id}>
- <EntranceGroup group={area}>
- {area.entrances.map((entranceId) =>
- <EntranceRow
- entranceId={entranceId}
- key={entranceId}
- />
- )}
- </EntranceGroup>
- </div>
- )}
- <div className="column">
- {AREAS.slice(43, 44).map((area) =>
- <EntranceGroup group={area} key={area.id}>
- {area.entrances.map((entrance) =>
- <EntranceRow
- entranceId={`${area.id}.${entrance.id}`}
- key={entrance.id}
- />
- )}
- </EntranceGroup>
- )}
- </div>
- <div className="column">
- {AREAS.slice(44, 46).map((area) =>
- <EntranceGroup group={area} key={area.id}>
- {area.entrances.map((entrance) =>
- <EntranceRow
- entranceId={`${area.id}.${entrance.id}`}
- key={entrance.id}
- />
- )}
- </EntranceGroup>
- )}
- </div>
- </div>
- <div className="map mt-5">
- <svg
- viewBox="0 0 500 370"
- onClick={() => { setDragging(null); }}
- onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); }}
- >
- <g className="background">
- {MAPS.map((map) =>
- <g className="area" key={map.id} title={map.name}>
- <image
- href={map.bg.src}
- pointerEvents="none"
- x={map.bg.pos.x} y={map.bg.pos.y}
- width={map.bg.size.x}
- style={{ opacity: prefs.showMaps ? 1 : 0.25 }}
- />
- {map.labelPos && prefs.showLabels ?
- <text
- className="area-label"
- x={map.labelPos.x}
- y={map.labelPos.y}
- fill={map.color}
- >
- {map.short}
- </text>
- : null}
- </g>
- )}
- </g>
- {prefs.showConnectors ?
- <g title="connectors">
- {connectors.map((c) =>
- <MapConnector
- key={c.id}
- from={c.from}
- id={c.id}
- isTrash={c.isTrash}
- to={c.to}
- via={c.via}
- />
- )}
- </g>
- : null}
- {MAPS.map((map) =>
- <g className="area" key={map.id} title={map.name}>
- {prefs.showEntrances ? map.entrances.map((entrance) =>
- <MapEntrance key={entrance.id} entrance={entrance} />
- ) : null}
- </g>
- )}
- {prefs.showWarps ?
- <g title="anotations">
- {annotations.map((a) =>
- <MapAnnotation
- key={`${a.id}`}
- annotation={a}
- />
- )}
- </g>
- : null}
- </svg>
- </div>
- <div className="menu-bar">
- <div className="button-bar">
- <Button
- onClick={() => togglePref('showConnectors')}
- size="sm"
- variant={prefs.showConnectors ? 'secondary' : 'outline-secondary'}
- >
- Connectors
- </Button>
- <Button
- onClick={() => togglePref('showEntrances')}
- size="sm"
- variant={prefs.showEntrances ? 'secondary' : 'outline-secondary'}
- >
- Entrances
- </Button>
- <Button
- onClick={() => togglePref('showLabels')}
- size="sm"
- variant={prefs.showLabels ? 'secondary' : 'outline-secondary'}
- >
- Labels
- </Button>
- <Button
- onClick={() => togglePref('showMaps')}
- size="sm"
- variant={prefs.showMaps ? 'secondary' : 'outline-secondary'}
- >
- Maps
- </Button>
- <Button
- onClick={() => togglePref('showWarps')}
- size="sm"
- variant={prefs.showWarps ? 'secondary' : 'outline-secondary'}
- >
- Warps
- </Button>
- </div>
- <div className="button-bar">
- <Button
- onClick={save}
- size="sm"
- title={t('button.save')}
- variant="outline-secondary"
- >
- <Icon.SAVE title="" />
- </Button>
- <Button
- onClick={load}
- size="sm"
- title={t('button.load')}
- variant="outline-secondary"
- >
- <Icon.LOAD title="" />
- </Button>
- <Button
- onClick={reset}
- size="sm"
- title={t('button.reset')}
- variant="outline-secondary"
- >
- <Icon.RESET title="" />
- </Button>
- </div>
- </div>
- </div>
- </CONTEXT.Provider>;
-};
-
-export default MixedPoolsTracker;
--- /dev/null
+import FuzzySearch from 'fuzzy-search';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import Icon from '../common/Icon';
+
+const AREAS = [
+ {
+ id: 'kf',
+ bgColor: '#6aa84f',
+ fgColor: '#000000',
+ name: 'Kokiri Forest',
+ short: 'KF',
+ map: {
+ pos: { x: 210, y: 240 },
+ size: { x: 150, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/kf.png',
+ off: { x: 0, y: 0 },
+ scale: 1.3,
+ },
+ labelPos: { x: 90, y: 70 },
+ },
+ entrances: [
+ {
+ id: 'bro',
+ bgColor: '#b6d7a8',
+ fgColor: '#000000',
+ name: 'Know-It-All Brothers\' House',
+ short: 'Bros',
+ type: 'Interior',
+ pos: { x: 13, y: 57 },
+ },
+ {
+ id: 'link',
+ bgColor: '#b6d7a8',
+ fgColor: '#000000',
+ name: 'Link\'s House',
+ short: 'Links',
+ type: 'SpecialInterior',
+ pos: { x: 36, y: 74 },
+ },
+ {
+ id: 'sariah',
+ bgColor: '#b6d7a8',
+ fgColor: '#000000',
+ name: 'Sariah\'s House',
+ short: 'Sariahs',
+ type: 'Interior',
+ pos: { x: 47, y: 66 },
+ },
+ {
+ id: 'twin',
+ bgColor: '#b6d7a8',
+ fgColor: '#000000',
+ name: 'House of Twins',
+ short: 'Twins',
+ type: 'Interior',
+ pos: { x: 58, y: 64 },
+ },
+ {
+ id: 'shop',
+ bgColor: '#b6d7a8',
+ fgColor: '#000000',
+ name: 'Kokiri Shop',
+ short: 'Shop',
+ type: 'Interior',
+ pos: { x: 53, y: 42 },
+ },
+ {
+ id: 'mido',
+ bgColor: '#b6d7a8',
+ fgColor: '#000000',
+ name: 'Great Mido\'s House',
+ short: 'Mido',
+ type: 'Interior',
+ pos: { x: 28, y: 38 },
+ },
+ {
+ id: 'deku',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'Deku Tree',
+ short: 'Deku',
+ spacer: true,
+ type: 'Dungeon',
+ pos: { x: 114, y: 26 },
+ },
+ {
+ id: 'storms',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Storms Grotto',
+ short: 'Storms Grotto',
+ type: 'Grotto',
+ pos: { x: 25, y: 25 },
+ },
+ {
+ id: 'hf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Hyrule Field',
+ short: 'HF',
+ type: 'Overworld',
+ pos: { x: 4, y: 45 },
+ },
+ {
+ id: 'lw',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Lost Woods',
+ short: 'LW',
+ type: 'Overworld',
+ pos: { x: 31, y: 21 },
+ },
+ ],
+ },
+ {
+ id: 'lw',
+ bgColor: '#38761d',
+ fgColor: '#000000',
+ name: 'Lost Woods',
+ short: 'LW',
+ map: {
+ pos: { x: 370, y: 220 },
+ size: { x: 75, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/lw.png',
+ off: { x: 0, y: 0 },
+ scale: 1.3,
+ },
+ labelPos: { x: 40, y: 85 },
+ },
+ entrances: [
+ {
+ id: 'gcg',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Goron City Grotto',
+ short: 'GC Grotto',
+ type: 'Grotto',
+ pos: { x: 45, y: 40 },
+ },
+ {
+ id: 'tg',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Theater Grotto',
+ short: 'Theater Grotto',
+ type: 'Grotto',
+ pos: { x: 32, y: 25 },
+ },
+ {
+ id: 'sfmg',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'SFM Grotto',
+ short: 'SFM Grotto',
+ type: 'Grotto',
+ pos: { x: 40, y: 12 },
+ },
+ {
+ id: 'kf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Kokiri Forest',
+ short: 'KF',
+ spacer: true,
+ type: 'Overworld',
+ pos: { x: 31, y: 60 },
+ },
+ {
+ id: 'gc',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Goron City',
+ short: 'GC',
+ type: 'Overworld',
+ pos: { x: 44, y: 33 },
+ },
+ {
+ id: 'zr',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Zora\'s River',
+ short: 'ZR',
+ type: 'Overworld',
+ pos: { x: 68, y: 38 },
+ },
+ {
+ id: 'sfm',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Sacred Forest Maedows',
+ short: 'SFM',
+ type: 'Overworld',
+ pos: { x: 45, y: 6 },
+ },
+ ],
+ },
+ {
+ id: 'sfm',
+ bgColor: '#274e13',
+ fgColor: '#000000',
+ name: 'Sacred Forest Maedows',
+ short: 'SFM',
+ map: {
+ pos: { x: 460, y: 220 },
+ size: { x: 35, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/sfm.png',
+ off: { x: -20, y: 0 },
+ scale: 4.3,
+ },
+ labelPos: { x: 15, y: 110 },
+ },
+ entrances: [
+ {
+ id: 'forest',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Forest Temple',
+ short: 'Forest Temple',
+ type: 'Dungeon',
+ pos: { x: 18, y: 5 },
+ },
+ {
+ id: 'wolf',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Wolf Grotto',
+ short: 'Wolf Grotto',
+ type: 'Grotto',
+ pos: { x: 14, y: 87 },
+ },
+ {
+ id: 'fairy',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Fairy Grotto',
+ short: 'Fairy Grotto',
+ type: 'Grotto',
+ pos: { x: 18, y: 60 },
+ },
+ {
+ id: 'storms',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Storms Grotto',
+ short: 'Storms Grotto',
+ type: 'Grotto',
+ pos: { x: 25, y: 18 },
+ },
+ {
+ id: 'lw',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Lost Woods',
+ short: 'LW',
+ type: 'Overworld',
+ pos: { x: 14, y: 95 },
+ },
+ ],
+ },
+ {
+ id: 'gy',
+ bgColor: '#a64d79',
+ fgColor: '#000000',
+ name: 'Graveyard',
+ short: 'Grave',
+ map: {
+ pos: { x: 310, y: 170 },
+ size: { x: 100, y: 35, },
+ bg: {
+ src: '/media/oot/minimap/gy.png',
+ off: { x: 0, y: -5 },
+ scale: 1.5,
+ },
+ labelPos: { x: 65, y: 35 },
+ },
+ entrances: [
+ {
+ id: 'dh',
+ bgColor: '#c27ba0',
+ fgColor: '#000000',
+ name: 'Dampe Hut',
+ short: 'Dampe Hut',
+ type: 'Interior',
+ pos: { x: 22, y: 26 },
+ },
+ {
+ id: 'shadow',
+ bgColor: '#c27ba0',
+ fgColor: '#000000',
+ name: 'Shadow Temple',
+ short: 'Shadow',
+ type: 'Dungeon',
+ pos: { x: 90, y: 16 },
+ },
+ {
+ id: 'shield',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Shield Grave',
+ short: 'Shield Grave',
+ type: 'Grave',
+ pos: { x: 30, y: 19 },
+ },
+ {
+ id: 'race',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Dampe Race',
+ short: 'Dampe Race',
+ type: 'Grave',
+ pos: { x: 25, y: 7 },
+ },
+ {
+ id: 'sun',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Sun Song Grave',
+ short: 'Sun Song Gr',
+ type: 'Grave',
+ pos: { x: 41, y: 21 },
+ },
+ {
+ id: 'family',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Family Tomb',
+ short: 'Family Tomb',
+ type: 'Grave',
+ pos: { x: 52, y: 15 },
+ },
+ {
+ id: 'kak',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Kakariko',
+ short: 'Kak',
+ type: 'Overworld',
+ pos: { x: 5, y: 20 },
+ },
+ ],
+ },
+ {
+ id: 'kak',
+ bgColor: '#ff9900',
+ fgColor: '#000000',
+ name: 'Kakariko Village',
+ short: 'Kak',
+ map: {
+ pos: { x: 190, y: 140 },
+ size: { x: 125, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/kak.png',
+ off: { x: -7, y: 0 },
+ scale: 1.7,
+ },
+ labelPos: { x: 20, y: 95 },
+ },
+ entrances: [
+ {
+ id: 'talon',
+ bgColor: '#f9cb9c',
+ fgColor: '#000000',
+ name: 'Carpenter Boss House',
+ short: 'Talons',
+ type: 'Interior',
+ pos: { x: 66, y: 51 },
+ },
+ {
+ id: 'skull',
+ bgColor: '#f9cb9c',
+ fgColor: '#000000',
+ name: 'Skulltula House',
+ short: 'Skulltula',
+ type: 'Interior',
+ pos: { x: 58, y: 73 },
+ },
+ {
+ id: 'impaf',
+ bgColor: '#f9cb9c',
+ fgColor: '#000000',
+ name: 'Impa Front',
+ short: 'Impa Front',
+ type: 'Interior',
+ pos: { x: 58, y: 92 },
+ },
+ {
+ id: 'impab',
+ bgColor: '#f9cb9c',
+ fgColor: '#000000',
+ name: 'Impa Back',
+ short: 'Impa Back',
+ type: 'Interior',
+ pos: { x: 71, y: 91 },
+ },
+ {
+ id: 'shield',
+ bgColor: '#f9cb9c',
+ fgColor: '#000000',
+ name: 'Shield Shop (Bazaar)',
+ short: 'Shield Shop',
+ type: 'Interior',
+ pos: { x: 60, y: 28 },
+ },
+ {
+ id: 'potion',
+ bgColor: '#f9cb9c',
+ fgColor: '#000000',
+ name: 'Potion Shop',
+ short: 'Potion Shop',
+ type: 'SpecialInterior',
+ pos: { x: 70, y: 28 },
+ },
+ {
+ id: 'back',
+ bgColor: '#f9cb9c',
+ fgColor: '#000000',
+ name: 'Shop Back',
+ short: 'Shop Back',
+ type: 'SpecialInterior',
+ pos: { x: 80, y: 28 },
+ },
+ {
+ id: 'witch',
+ bgColor: '#f9cb9c',
+ fgColor: '#000000',
+ name: 'Witch',
+ short: 'Witch',
+ type: 'Interior',
+ pos: { x: 84, y: 46 },
+ },
+ {
+ id: 'arch',
+ bgColor: '#f9cb9c',
+ fgColor: '#000000',
+ name: 'Archery',
+ short: 'Archery',
+ type: 'Interior',
+ pos: { x: 72, y: 67 },
+ },
+ {
+ id: 'mill',
+ bgColor: '#f9cb9c',
+ fgColor: '#000000',
+ name: 'Windmill',
+ short: 'Windmill',
+ type: 'SpecialInterior',
+ pos: { x: 96, y: 59 },
+ },
+ {
+ id: 'botw',
+ bgColor: '#a64d79',
+ fgColor: '#000000',
+ name: 'Bottom of the Well',
+ short: 'Bottom Well',
+ spacer: true,
+ type: 'Dungeon',
+ pos: { x: 84, y: 59 },
+ },
+ {
+ id: 'open',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Open Grotto',
+ short: 'Open Grotto',
+ type: 'Grotto',
+ pos: { x: 86, y: 37 },
+ },
+ {
+ id: 'redead',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Redead Grotto',
+ short: 'Redead Grotto',
+ type: 'Grotto',
+ pos: { x: 58, y: 57 },
+ },
+ {
+ id: 'hf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Hyrule Field',
+ short: 'HF',
+ type: 'Overworld',
+ pos: { x: 10, y: 74 },
+ },
+ {
+ id: 'dmt',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Death Mountain Trail',
+ short: 'DMT',
+ type: 'Overworld',
+ pos: { x: 67, y: 8 },
+ },
+ {
+ id: 'gy',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Graveyard',
+ short: 'Grave',
+ type: 'Overworld',
+ pos: { x: 112, y: 83 },
+ },
+ ],
+ },
+ {
+ id: 'm1',
+ bgColor: '#9900ff',
+ fgColor: '#000000',
+ name: 'Market 1',
+ short: 'M1',
+ map: {
+ pos: { x: 100, y: 80 },
+ size: { x: 100, y: 60, },
+ bg: {
+ src: '/media/oot/minimap/m1.png',
+ off: { x: 0, y: -6 },
+ scale: 1.0,
+ },
+ labelPos: { x: 38, y: 30 },
+ },
+ entrances: [
+ {
+ id: 'shield',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Shield Shop',
+ short: 'Shield Shop',
+ type: 'Interior',
+ pos: { x: 81, y: 36 },
+ },
+ {
+ id: 'potion',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Potion Shop',
+ short: 'Potion Shop',
+ type: 'Interior',
+ pos: { x: 81, y: 27 },
+ },
+ {
+ id: 'mask',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Mask Shop',
+ short: 'Mask Shop',
+ type: 'Interior',
+ pos: { x: 75, y: 14 },
+ },
+ {
+ id: 'sling',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Slingshot Game',
+ short: 'Sling Game',
+ type: 'Interior',
+ pos: { x: 60, y: 14 },
+ },
+ {
+ id: 'chuu',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Bombchu Bowling',
+ short: 'Bombchu',
+ type: 'Interior',
+ pos: { x: 54, y: 30 },
+ },
+ {
+ id: 'tcg',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Treasure Chest Game',
+ short: 'TCG',
+ type: 'Interior',
+ pos: { x: 52, y: 48 },
+ },
+ {
+ id: 'alleyl',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Backalley Left',
+ short: 'Alley L',
+ type: 'Overworld',
+ pos: { x: 26, y: 48 },
+ },
+ {
+ id: 'alleyr',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Backalley Right',
+ short: 'Alley R',
+ type: 'Overworld',
+ pos: { x: 18, y: 10 },
+ },
+ {
+ id: 'tot',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Temple of Time',
+ short: 'ToT',
+ type: 'Overworld',
+ pos: { x: 92, y: 15 },
+ },
+ {
+ id: 'hc',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Hyrule Castle',
+ short: 'HC',
+ type: 'Overworld',
+ pos: { x: 67, y: 7 },
+ },
+ {
+ id: 'm2',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Market 2',
+ short: 'M2',
+ type: 'Overworld',
+ pos: { x: 66, y: 50 },
+ },
+ ],
+ },
+ {
+ id: 'm2',
+ bgColor: '#9900ff',
+ fgColor: '#000000',
+ name: 'Market 2',
+ short: 'M2',
+ map: {
+ pos: { x: 205, y: 85 },
+ size: { x: 18, y: 50, },
+ bg: {
+ src: '/media/oot/minimap/m2.png',
+ off: { x: -3, y: -3 },
+ scale: 3.6,
+ },
+ labelPos: { x: -2, y: 25 },
+ },
+ entrances: [
+ {
+ id: 'bp',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Big Poe',
+ short: 'Big Poe',
+ type: 'Interior',
+ pos: { x: 15.5, y: 32 },
+ },
+ {
+ id: 'hf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Hyrule Field',
+ short: 'HF',
+ type: 'Overworld',
+ pos: { x: 8.5, y: 47.5 },
+ },
+ {
+ id: 'm1',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Market 1',
+ short: 'M1',
+ type: 'Overworld',
+ pos: { x: 8.5, y: 3 },
+ },
+ ],
+ },
+ {
+ id: 'hc',
+ bgColor: '#bf9000',
+ fgColor: '#000000',
+ name: 'Hyrule Castle',
+ short: 'HC',
+ map: {
+ pos: { x: 100, y: 0 },
+ size: { x: 120, y: 70, },
+ bg: {
+ src: '/media/oot/minimap/hc-gc.png',
+ off: { x: 0, y: 0 },
+ scale: 1.2,
+ },
+ labelPos: { x: 25, y: 65 },
+ },
+ entrances: [
+ {
+ id: 'hcfairy',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Hyrule Castle Fairy',
+ short: 'HC Fairy',
+ type: 'Interior',
+ pos: { x: 61, y: 23 },
+ },
+ {
+ id: 'gfairy',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Ganon\'s Castle Fairy',
+ short: 'Ganon Fairy',
+ type: 'Interior',
+ pos: { x: 116, y: 51 },
+ },
+ {
+ id: 'igc',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Inside Ganon\'s Castle',
+ short: 'IGC',
+ type: 'DungeonSpecial',
+ pos: { x: 83, y: 30 },
+ },
+ {
+ id: 'storms',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Storms Grotto',
+ short: 'Storms Grotto',
+ type: 'Grotto',
+ pos: { x: 30, y: 29 },
+ },
+ {
+ id: 'm1',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Market 1',
+ short: 'M1',
+ type: 'Overworld',
+ pos: { x: 60, y: 66 },
+ },
+ ],
+ },
+ {
+ id: 'hf',
+ bgColor: '#674ea7',
+ fgColor: '#000000',
+ name: 'Hyrule Field',
+ short: 'HF',
+ map: {
+ pos: { x: 90, y: 150 },
+ size: { x: 100, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/hf.png',
+ off: { x: -16, y: 0 },
+ scale: 1.8,
+ },
+ labelPos: { x: 100, y: 10 },
+ },
+ entrances: [
+ {
+ id: 'destiny',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Destiny Grotto',
+ short: 'Destiny Grotto',
+ type: 'Grotto',
+ pos: { x: 33, y: 7 },
+ },
+ {
+ id: 'tektite',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Tektite Grotto',
+ short: 'Tektite',
+ type: 'Grotto',
+ pos: { x: 30, y: 23 },
+ },
+ {
+ id: 'nw',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Northwest Grotto (near Market)',
+ short: 'NW Grotto',
+ type: 'Grotto',
+ pos: { x: 48, y: 13 },
+ },
+ {
+ id: 'nk',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Grotto near Kakariko',
+ short: 'near Kak Gro',
+ type: 'Grotto',
+ pos: { x: 68, y: 8 },
+ },
+ {
+ id: 'se',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Southeast Grotto',
+ short: 'SE Grotto',
+ type: 'Grotto',
+ pos: { x: 55, y: 78 },
+ },
+ {
+ id: 'open',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Open Grotto',
+ short: 'Open Grotto',
+ type: 'Grotto',
+ pos: { x: 35, y: 86 },
+ },
+ {
+ id: 'sg',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'South Grotto',
+ short: 'South Grotto',
+ type: 'Grotto',
+ pos: { x: 28, y: 87 },
+ },
+ {
+ id: 'cow',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Cow Grotto',
+ short: 'Cow Grotto',
+ type: 'Grotto',
+ pos: { x: 13, y: 47 },
+ },
+ {
+ id: 'town',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Town',
+ short: 'Town',
+ spacer: true,
+ type: 'Overworld',
+ pos: { x: 57, y: 10 },
+ },
+ {
+ id: 'llr',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Lon Lon Ranch',
+ short: 'LLR',
+ type: 'Overworld',
+ pos: { x: 45, y: 44 },
+ },
+ {
+ id: 'kak',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Kakariko',
+ short: 'Kak',
+ type: 'Overworld',
+ pos: { x: 80, y: 10 },
+ },
+ {
+ id: 'zr',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Zora\'s River',
+ short: 'ZR',
+ type: 'Overworld',
+ pos: { x: 94, y: 30 },
+ },
+ {
+ id: 'kf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Kokiri Forest',
+ short: 'KF',
+ type: 'Overworld',
+ pos: { x: 87, y: 57 },
+ },
+ {
+ id: 'lh',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Lake Hylia',
+ short: 'LH',
+ type: 'Overworld',
+ pos: { x: 25, y: 95 },
+ },
+ {
+ id: 'gv',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Gerudo Valley',
+ short: 'GV',
+ type: 'Overworld',
+ pos: { x: 6, y: 51 },
+ },
+ ],
+ },
+ {
+ id: 'zr',
+ bgColor: '#3c78d8',
+ fgColor: '#000000',
+ name: 'Zora\'s River',
+ short: 'ZR',
+ map: {
+ pos: { x: 330, y: 100 },
+ size: { x: 100, y: 60, },
+ bg: {
+ src: '/media/oot/minimap/zr.png',
+ off: { x: 0, y: 0 },
+ scale: 1.3,
+ },
+ labelPos: { x: 60, y: 40 },
+ },
+ entrances: [
+ {
+ id: 'storms',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Storms Grotto',
+ short: 'Storms Grotto',
+ type: 'Grotto',
+ pos: { x: 8, y: 28 },
+ },
+ {
+ id: 'open',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Open Grotto',
+ short: 'Open Grotto',
+ type: 'Grotto',
+ pos: { x: 36, y: 32 },
+ },
+ {
+ id: 'boulder',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Boulder Grotto',
+ short: 'Boulder Grotto',
+ type: 'Grotto',
+ pos: { x: 40, y: 24 },
+ },
+ {
+ id: 'hf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Hyrule Field',
+ short: 'HF',
+ spacer: true,
+ type: 'Overworld',
+ pos: { x: 10, y: 52 },
+ },
+ {
+ id: 'lw',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Lost Woods',
+ short: 'LW',
+ type: 'Overworld',
+ pos: { x: 90.5, y: 18 },
+ },
+ {
+ id: 'zd',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Zora\'s Domain',
+ short: 'ZD',
+ type: 'Overworld',
+ pos: { x: 96, y: 9 },
+ },
+ ],
+ },
+ {
+ id: 'zd',
+ bgColor: '#3c78d8',
+ fgColor: '#000000',
+ name: 'Zora\'s Domain',
+ short: 'ZD',
+ map: {
+ pos: { x: 410, y: 100 },
+ size: { x: 80, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/zd.png',
+ off: { x: -15, y: -5 },
+ scale: 2.3,
+ },
+ labelPos: { x: 18, y: 40 },
+ },
+ entrances: [
+ {
+ id: 'shop',
+ bgColor: '#aac2f1',
+ fgColor: '#000000',
+ name: 'Shop',
+ short: 'Shop',
+ type: 'Interior',
+ pos: { x: 55, y: 85 },
+ },
+ {
+ id: 'storms',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Storms Grotto',
+ short: 'Storms Grotto',
+ type: 'Grotto',
+ pos: { x: 16, y: 64 },
+ },
+ {
+ id: 'zr',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Zora\'s River',
+ short: 'ZR',
+ type: 'Overworld',
+ pos: { x: 6, y: 73 },
+ },
+ {
+ id: 'lh',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Lake Hylia',
+ short: 'LH',
+ type: 'Overworld',
+ pos: { x: 35, y: 72 },
+ },
+ {
+ id: 'zf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Zora\'s Fountain',
+ short: 'ZF',
+ type: 'Overworld',
+ pos: { x: 58, y: 15 },
+ },
+ ],
+ },
+ {
+ id: 'zf',
+ bgColor: '#3c78d8',
+ fgColor: '#000000',
+ name: 'Zora\'s Fountain',
+ short: 'ZF',
+ map: {
+ pos: { x: 410, y: 0 },
+ size: { x: 90, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/zf.png',
+ off: { x: -5, y: 0 },
+ scale: 2.1,
+ },
+ labelPos: { x: 60, y: 35 },
+ },
+ entrances: [
+ {
+ id: 'wall',
+ bgColor: '#aac2f1',
+ fgColor: '#000000',
+ name: 'Fairy Wall',
+ short: 'Fairy Wall',
+ type: 'Interior',
+ pos: { x: 61, y: 90 },
+ },
+ {
+ id: 'jabu',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'Jabu Jabu\'s Belly',
+ short: 'Jabu',
+ type: 'Dungeon',
+ pos: { x: 34, y: 38 },
+ },
+ {
+ id: 'ice',
+ bgColor: '#a64d79',
+ fgColor: '#000000',
+ name: 'Ice Cavern',
+ short: 'Ice Cavern',
+ type: 'Dungeon',
+ pos: { x: 52, y: 10 },
+ },
+ {
+ id: 'zd',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Zora\'s Domain',
+ short: 'ZD',
+ type: 'Overworld',
+ pos: { x: 12, y: 55 },
+ },
+ ],
+ },
+ {
+ id: 'lh',
+ bgColor: '#4a86e8',
+ fgColor: '#000000',
+ name: 'Lake Hylia',
+ short: 'LH',
+ map: {
+ pos: { x: 0, y: 265 },
+ size: { x: 90, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/lh.png',
+ off: { x: -15, y: -5 },
+ scale: 1.9,
+ },
+ labelPos: { x: 70, y: 65 },
+ },
+ entrances: [
+ {
+ id: 'dive',
+ bgColor: '#cfe2f3',
+ fgColor: '#000000',
+ name: 'Lab Diving',
+ short: 'Lab Dive',
+ type: 'Interior',
+ pos: { x: 30, y: 43 },
+ },
+ {
+ id: 'fishing',
+ bgColor: '#cfe2f3',
+ fgColor: '#000000',
+ name: 'Fishing Game',
+ short: 'Fishing',
+ type: 'Interior',
+ pos: { x: 68, y: 43 },
+ },
+ {
+ id: 'water',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Water Temple',
+ short: 'Water Temple',
+ type: 'Dungeon',
+ pos: { x: 45, y: 65 },
+ },
+ {
+ id: 'owl',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Owl Grotto',
+ short: 'Owl Grotto',
+ type: 'Grotto',
+ pos: { x: 24, y: 65 },
+ },
+ {
+ id: 'hf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Hyrule Field',
+ short: 'HF',
+ type: 'Overworld',
+ pos: { x: 35, y: 5 },
+ },
+ {
+ id: 'zd',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Zora\'s Domain',
+ short: 'ZD',
+ type: 'Overworld',
+ pos: { x: 45, y: 40 },
+ },
+ ],
+ },
+ {
+ id: 'llr',
+ bgColor: '#f1c232',
+ fgColor: '#000000',
+ name: 'Lon Lon Ranch',
+ short: 'LLR',
+ map: {
+ pos: { x: 100, y: 260 },
+ size: { x: 80, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/llr.png',
+ off: { x: -22, y: -6 },
+ scale: 2.6,
+ },
+ labelPos: { x: 40, y: 50 },
+ },
+ entrances: [
+ {
+ id: 'chicken',
+ bgColor: '#ffd966',
+ fgColor: '#000000',
+ name: 'Chicken Game',
+ short: 'Chicken',
+ type: 'Interior',
+ pos: { x: 54, y: 13 },
+ },
+ {
+ id: 'stable',
+ bgColor: '#ffd966',
+ fgColor: '#000000',
+ name: 'Stable',
+ short: 'Stable',
+ type: 'Interior',
+ pos: { x: 50, y: 19 },
+ },
+ {
+ id: 'tower',
+ bgColor: '#ffd966',
+ fgColor: '#000000',
+ name: 'Tower',
+ short: 'Tower',
+ type: 'Interior',
+ pos: { x: 17, y: 82 },
+ },
+ {
+ id: 'grotto',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Grotto',
+ short: 'Grotto',
+ type: 'Grotto',
+ pos: { x: 60, y: 85 },
+ },
+ {
+ id: 'hf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Hyrule Field',
+ short: 'HF',
+ type: 'Overworld',
+ pos: { x: 60, y: 5 },
+ },
+ ],
+ },
+ {
+ id: 'gv',
+ bgColor: '#b45f06',
+ fgColor: '#000000',
+ name: 'Gerudo Valley',
+ short: 'GV',
+ map: {
+ pos: { x: 0, y: 190 },
+ size: { x: 100, y: 75, },
+ bg: {
+ src: '/media/oot/minimap/gv.png',
+ off: { x: -17, y: -5 },
+ scale: 1.9,
+ },
+ labelPos: { x: 20, y: 55 },
+ },
+ entrances: [
+ {
+ id: 'tent',
+ bgColor: '#b45f06',
+ fgColor: '#000000',
+ name: 'Tent',
+ short: 'Tent',
+ type: 'Interior',
+ pos: { x: 44, y: 33 },
+ },
+ {
+ id: 'str2',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Strength 2 Grotto',
+ short: 'Str2 Grotto',
+ type: 'Grotto',
+ pos: { x: 60, y: 60 },
+ },
+ {
+ id: 'storms',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Storms Grotto',
+ short: 'Storms Grotto',
+ type: 'Grotto',
+ pos: { x: 39, y: 27 },
+ },
+ {
+ id: 'hf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Hyrule Field',
+ short: 'HF',
+ type: 'Overworld',
+ pos: { x: 93, y: 45 },
+ },
+ {
+ id: 'gf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Gerudo Fortress',
+ short: 'GF',
+ type: 'Overworld',
+ pos: { x: 6, y: 22 },
+ },
+ {
+ id: 'wf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Waterfall',
+ short: 'Waterfall',
+ oneway: true,
+ type: 'OverworldOneWay',
+ pos: { x: 58, y: 69 },
+ },
+ ],
+ },
+ {
+ id: 'gf',
+ bgColor: '#b45f06',
+ fgColor: '#000000',
+ name: 'Gerudo Fortress',
+ short: 'GF',
+ map: {
+ pos: { x: 0, y: 90 },
+ size: { x: 90, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/gf.png',
+ off: { x: -5, y: -35 },
+ scale: 2.5,
+ },
+ labelPos: { x: 15, y: 50 },
+ },
+ entrances: [
+ {
+ id: 'gtg',
+ bgColor: '#a64d79',
+ fgColor: '#000000',
+ name: 'Gerudo Training Grounds',
+ short: 'GTG',
+ type: 'Dungeon',
+ pos: { x: 48, y: 60 },
+ },
+ {
+ id: 'storms',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Storms Grotto',
+ short: 'Storms Grotto',
+ type: 'Grotto',
+ pos: { x: 52, y: 47 },
+ },
+ {
+ id: 'gv',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Gerudo Valley',
+ short: 'GV',
+ type: 'Overworld',
+ pos: { x: 43, y: 93 },
+ },
+ {
+ id: 'hw',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Haunted Wasteland',
+ short: 'Waste',
+ type: 'Overworld',
+ pos: { x: 7, y: 13 },
+ },
+ ],
+ },
+ {
+ id: 'dcol',
+ bgColor: '#f1c232',
+ fgColor: '#000000',
+ name: 'Desert Colossus',
+ short: 'DCol',
+ map: {
+ pos: { x: 0, y: 0 },
+ size: { x: 100, y: 90, },
+ bg: {
+ src: '/media/oot/minimap/dcol.png',
+ off: { x: 0, y: -2 },
+ scale: 2.0,
+ },
+ labelPos: { x: 50, y: 50 },
+ },
+ entrances: [
+ {
+ id: 'spirit',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Spirit Temple',
+ short: 'Spirit',
+ type: 'Dungeon',
+ pos: { x: 10, y: 43.5 },
+ },
+ {
+ id: 'fairy',
+ bgColor: '#ffd966',
+ fgColor: '#000000',
+ name: 'Fairy',
+ short: 'Fairy',
+ type: 'Interior',
+ pos: { x: 62, y: 21 },
+ },
+ {
+ id: 'str2',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Strength 2 Grotto',
+ short: 'Str2 Grotto',
+ type: 'Grotto',
+ pos: { x: 32, y: 28 },
+ },
+ {
+ id: 'hw',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Haunted Wasteland',
+ short: 'Waste',
+ type: 'Overworld',
+ pos: { x: 85, y: 40 },
+ },
+ ],
+ },
+ {
+ id: 'hw',
+ bgColor: '#f1c232',
+ fgColor: '#000000',
+ name: 'Haunted Wasteland',
+ short: 'Waste',
+ entrances: [
+ {
+ id: 'gf',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Gerudo Fortress',
+ short: 'GF',
+ type: 'Overworld',
+ throughway: 'hw.dcol',
+ },
+ {
+ id: 'dcol',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Desert Colossus',
+ short: 'DCol',
+ type: 'Overworld',
+ throughway: 'hw.gf',
+ },
+ ],
+ },
+ {
+ id: 'dmc',
+ bgColor: '#ff0000',
+ fgColor: '#000000',
+ name: 'Death Mountain Crater',
+ short: 'DMC',
+ map: {
+ pos: { x: 320, y: 0 },
+ size: { x: 90, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/dmc.png',
+ off: { x: -15, y: -3 },
+ scale: 1.9,
+ },
+ labelPos: { x: 52, y: 62 },
+ },
+ entrances: [
+ {
+ id: 'fairy',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Fairy',
+ short: 'Fairy',
+ type: 'Interior',
+ pos: { x: 17, y: 68 },
+ },
+ {
+ id: 'fire',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Fire Temple',
+ short: 'Fire',
+ type: 'Dungeon',
+ pos: { x: 48, y: 6 },
+ },
+ {
+ id: 'boulder',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Boulder Grotto',
+ short: 'Boulder Gro',
+ type: 'Grotto',
+ pos: { x: 49, y: 86 },
+ },
+ {
+ id: 'hammer',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Hammer Grotto',
+ short: 'Hammer Gro',
+ type: 'Grotto',
+ pos: { x: 12, y: 35 },
+ },
+ {
+ id: 'gc',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Goron City',
+ short: 'GC',
+ type: 'Overworld',
+ pos: { x: 7, y: 46 },
+ },
+ {
+ id: 'dmt',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Death Mountain Trail',
+ short: 'DMT',
+ type: 'Overworld',
+ pos: { x: 25, y: 90 },
+ },
+ ],
+ },
+ {
+ id: 'dmt',
+ bgColor: '#ff0000',
+ fgColor: '#000000',
+ name: 'Death Mountain Trail',
+ short: 'DMT',
+ map: {
+ pos: { x: 280, y: 70 },
+ size: { x: 40, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/dmt.png',
+ off: { x: -12, y: 0 },
+ scale: 3.8,
+ },
+ labelPos: { x: -10, y: 50 },
+ },
+ entrances: [
+ {
+ id: 'dc',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Dodongo\'s Cavern',
+ short: 'DC',
+ type: 'Dungeon',
+ pos: { x: 8, y: 53 },
+ },
+ {
+ id: 'fairy',
+ bgColor: '#ea9999',
+ fgColor: '#000000',
+ name: 'Fairy',
+ short: 'Fairy',
+ type: 'Interior',
+ pos: { x: 22, y: 10 },
+ },
+ {
+ id: 'storms',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Storms Grotto',
+ short: 'Storms Grotto',
+ type: 'Grotto',
+ pos: { x: 23, y: 48 },
+ },
+ {
+ id: 'cow',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Cow Grotto',
+ short: 'Cow Grotto',
+ type: 'Grotto',
+ pos: { x: 20, y: 57 },
+ },
+ {
+ id: 'kak',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Kakariko',
+ short: 'Kak',
+ spacer: true,
+ type: 'Overworld',
+ pos: { x: 15, y: 94 },
+ },
+ {
+ id: 'gc',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Goron City',
+ short: 'GC',
+ type: 'Overworld',
+ pos: { x: 24, y: 40 },
+ },
+ {
+ id: 'dmc',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Death Mountain Crater',
+ short: 'DMC',
+ type: 'Overworld',
+ pos: { x: 29, y: 5 },
+ },
+ ],
+ },
+ {
+ id: 'gc',
+ bgColor: '#ff0000',
+ fgColor: '#000000',
+ name: 'Goron City',
+ short: 'GC',
+ map: {
+ pos: { x: 220, y: 0 },
+ size: { x: 90, y: 100, },
+ bg: {
+ src: '/media/oot/minimap/gc.png',
+ off: { x: -2, y: 0 },
+ scale: 2.0,
+ },
+ labelPos: { x: 25, y: 105 },
+ },
+ entrances: [
+ {
+ id: 'shop',
+ bgColor: '#ea9999',
+ fgColor: '#000000',
+ name: 'Shop',
+ short: 'Shop',
+ type: 'Interior',
+ pos: { x: 42, y: 50 },
+ },
+ {
+ id: 'times',
+ bgColor: '#b7b7b7',
+ fgColor: '#000000',
+ name: 'Times Grotto',
+ short: 'Times Grotto',
+ type: 'Grotto',
+ pos: { x: 80, y: 17 },
+ },
+ {
+ id: 'dmt',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Death Mountain Trail',
+ short: 'DMT',
+ type: 'Overworld',
+ pos: { x: 50.5, y: 95 },
+ },
+ {
+ id: 'lw',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Lost Woods',
+ short: 'LW',
+ type: 'Overworld',
+ pos: { x: 64, y: 93 },
+ },
+ {
+ id: 'dmc',
+ bgColor: '#ff6d01',
+ fgColor: '#000000',
+ name: 'Death Mountain Crater',
+ short: 'DMC',
+ type: 'Overworld',
+ pos: { x: 51.5, y: 5 },
+ },
+ ],
+ },
+ {
+ id: 'tot',
+ bgColor: '#ffffff',
+ fgColor: '#000000',
+ name: 'Temple of Time',
+ short: 'ToT',
+ entrances: [
+ {
+ id: 'temple',
+ bgColor: '#b4a7d6',
+ fgColor: '#000000',
+ name: 'Temple of Time',
+ short: 'Temple',
+ type: 'SpecialInterior',
+ },
+ {
+ id: 'm1',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Market 1',
+ short: 'M1',
+ type: 'Overworld',
+ },
+ ],
+ },
+ {
+ id: 'deku',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'Deku Tree',
+ short: 'Deku',
+ entrances: [
+ {
+ id: 'main',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'Main',
+ short: 'Main',
+ type: 'Dungeon',
+ throughway: 'deku.end',
+ },
+ {
+ id: 'end',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'End',
+ short: 'End',
+ type: 'ChildBoss',
+ throughway: 'deku.main',
+ },
+ ],
+ },
+ {
+ id: 'gohma',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'Gohma',
+ short: 'Gohma',
+ type: 'ChildBoss',
+ },
+ {
+ id: 'dc',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'Dodongo\'s Cavern',
+ short: 'DC',
+ entrances: [
+ {
+ id: 'main',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'Main',
+ short: 'Main',
+ type: 'Dungeon',
+ throughway: 'dc.end',
+ },
+ {
+ id: 'end',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'End',
+ short: 'End',
+ type: 'ChildBoss',
+ throughway: 'dc.main',
+ },
+ ],
+ },
+ {
+ id: 'kd',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'King Dodongo',
+ short: 'Dodongo',
+ type: 'ChildBoss',
+ },
+ {
+ id: 'jabu',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'Jabu Jabu\'s Belly',
+ short: 'Jabu',
+ entrances: [
+ {
+ id: 'main',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'Main',
+ short: 'Main',
+ type: 'Dungeon',
+ throughway: 'jabu.end',
+ },
+ {
+ id: 'end',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'End',
+ short: 'End',
+ type: 'ChildBoss',
+ throughway: 'jabu.main',
+ },
+ ],
+ },
+ {
+ id: 'barinade',
+ bgColor: '#ead1dc',
+ fgColor: '#000000',
+ name: 'Barinade',
+ short: 'Barinade',
+ type: 'ChildBoss',
+ },
+ {
+ id: 'forest',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Forest Temple',
+ short: 'Forest',
+ entrances: [
+ {
+ id: 'main',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Main',
+ short: 'Main',
+ spacer: true,
+ type: 'Dungeon',
+ throughway: 'forest.end',
+ },
+ {
+ id: 'end',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'End',
+ short: 'End',
+ spacer: true,
+ type: 'AdultBoss',
+ throughway: 'forest.main',
+ },
+ ],
+ },
+ {
+ id: 'pg',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Phantom Ganon',
+ short: 'PG',
+ spacer: true,
+ type: 'AdultBoss',
+ },
+ {
+ id: 'fire',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Fire Temple',
+ short: 'Fire',
+ entrances: [
+ {
+ id: 'main',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Main',
+ short: 'Main',
+ type: 'Dungeon',
+ throughway: 'fire.end',
+ },
+ {
+ id: 'end',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'End',
+ short: 'End',
+ type: 'AdultBoss',
+ throughway: 'fire.main',
+ },
+ ],
+ },
+ {
+ id: 'volvo',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Volvagia',
+ short: 'Volvagia',
+ type: 'AdultBoss',
+ },
+ {
+ id: 'water',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Water Temple',
+ short: 'Water',
+ entrances: [
+ {
+ id: 'main',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Main',
+ short: 'Main',
+ type: 'Dungeon',
+ throughway: 'water.end',
+ },
+ {
+ id: 'end',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'End',
+ short: 'End',
+ type: 'AdultBoss',
+ throughway: 'water.main',
+ },
+ ],
+ },
+ {
+ id: 'morpha',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Morpha',
+ short: 'Morpha',
+ type: 'AdultBoss',
+ },
+ {
+ id: 'shadow',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Shadow Temple',
+ short: 'Shadow',
+ entrances: [
+ {
+ id: 'main',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Main',
+ short: 'Main',
+ type: 'Dungeon',
+ throughway: 'shadow.end',
+ },
+ {
+ id: 'end',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'End',
+ short: 'End',
+ type: 'AdultBoss',
+ throughway: 'shadow.main',
+ },
+ ],
+ },
+ {
+ id: 'bongo',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Bongo Bongo',
+ short: 'Bongo Bongo',
+ type: 'AdultBoss',
+ },
+ {
+ id: 'spirit',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Spirit Temple',
+ short: 'Spirit',
+ entrances: [
+ {
+ id: 'main',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Main',
+ short: 'Main',
+ type: 'Dungeon',
+ throughway: 'spirit.end',
+ },
+ {
+ id: 'end',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'End',
+ short: 'End',
+ type: 'AdultBoss',
+ throughway: 'spirit.main',
+ },
+ ],
+ },
+ {
+ id: 'tr',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Twinrova',
+ short: 'Twinrova',
+ type: 'AdultBoss',
+ },
+ {
+ id: 'igc',
+ bgColor: '#d5a6bd',
+ fgColor: '#000000',
+ name: 'Inside Ganon\'s Castle',
+ short: 'IGC',
+ entrances: [
+ {
+ id: 'main',
+ bgColor: '#a64d79',
+ fgColor: '#000000',
+ name: 'Main',
+ short: 'Main',
+ spacer: true,
+ type: 'Dungeon',
+ throughway: 'igc.end',
+ },
+ {
+ id: 'end',
+ bgColor: '#a64d79',
+ fgColor: '#000000',
+ name: 'End',
+ short: 'End',
+ spacer: true,
+ type: 'SpecialBoss',
+ throughway: 'igc.main',
+ },
+ ],
+ },
+ {
+ id: 'ganon',
+ bgColor: '#a64d79',
+ fgColor: '#000000',
+ name: 'Ganon',
+ short: 'Ganon',
+ spacer: true,
+ type: 'SpecialBoss',
+ },
+ {
+ id: 'botw',
+ bgColor: '#a64d79',
+ fgColor: '#000000',
+ name: 'Bottom of the Well',
+ short: 'Bottom Well',
+ type: 'Dungeon',
+ },
+ {
+ id: 'ice',
+ bgColor: '#a64d79',
+ fgColor: '#000000',
+ name: 'Ice Cavern',
+ short: 'Ice Cavern',
+ type: 'Dungeon',
+ },
+ {
+ id: 'gtg',
+ bgColor: '#a64d79',
+ fgColor: '#000000',
+ name: 'Gerudo Training Grounds',
+ short: 'GTG',
+ type: 'Dungeon',
+ },
+ {
+ id: 'songs',
+ bgColor: '#000000',
+ fgColor: '#ffffff',
+ name: 'Warp Songs',
+ short: 'Songs',
+ entrances: [
+ {
+ id: 'minuet',
+ bgColor: '#38761d',
+ fgColor: '#ffffff',
+ name: 'Minuet of Forest',
+ short: 'Minuet',
+ oneway: true,
+ type: 'WarpSong',
+ icon: '/media/oot/icons/song-minuet.png',
+ },
+ {
+ id: 'bolero',
+ bgColor: '#38761d',
+ fgColor: '#ffffff',
+ name: 'Bolero of Fire',
+ short: 'Bolero',
+ oneway: true,
+ type: 'WarpSong',
+ icon: '/media/oot/icons/song-bolero.png',
+ },
+ {
+ id: 'serenade',
+ bgColor: '#38761d',
+ fgColor: '#ffffff',
+ name: 'Serenade of Water',
+ short: 'Serenade',
+ oneway: true,
+ type: 'WarpSong',
+ icon: '/media/oot/icons/song-serenade.png',
+ },
+ {
+ id: 'nocturne',
+ bgColor: '#38761d',
+ fgColor: '#ffffff',
+ name: 'Nocturne of Shadow',
+ short: 'Nocturne',
+ oneway: true,
+ type: 'WarpSong',
+ icon: '/media/oot/icons/song-nocturne.png',
+ },
+ {
+ id: 'requiem',
+ bgColor: '#38761d',
+ fgColor: '#ffffff',
+ name: 'Requiem of Spirit',
+ short: 'Requiem',
+ oneway: true,
+ type: 'WarpSong',
+ icon: '/media/oot/icons/song-requiem.png',
+ },
+ {
+ id: 'prelude',
+ bgColor: '#38761d',
+ fgColor: '#ffffff',
+ name: 'Prelude of Light',
+ short: 'Prelude',
+ oneway: true,
+ type: 'WarpSong',
+ icon: '/media/oot/icons/song-prelude.png',
+ },
+ ],
+ },
+ {
+ id: 'spawns',
+ bgColor: '#000000',
+ fgColor: '#ffffff',
+ name: 'Spawns',
+ short: 'Spawns',
+ entrances: [
+ {
+ id: 'child',
+ bgColor: '#38761d',
+ fgColor: '#ffffff',
+ name: 'Child Spawn',
+ short: 'Child',
+ oneway: true,
+ type: 'Spawn',
+ icon: '/media/oot/icons/link-child.png',
+ iconSize: 10,
+ },
+ {
+ id: 'adult',
+ bgColor: '#38761d',
+ fgColor: '#ffffff',
+ name: 'Adult Spawn',
+ short: 'Adult',
+ oneway: true,
+ type: 'Spawn',
+ icon: '/media/oot/icons/link-adult.png',
+ },
+ ],
+ },
+ {
+ id: 'owls',
+ bgColor: '#000000',
+ fgColor: '#ffffff',
+ name: 'Owl Drops',
+ short: 'Owls',
+ entrances: [
+ {
+ id: 'lhowl',
+ bgColor: '#38761d',
+ fgColor: '#ffffff',
+ name: 'Lake Hylia Owl',
+ short: 'LH Owl',
+ oneway: true,
+ type: 'OwlDrop',
+ icon: '/media/oot/icons/owl-lake.png',
+ },
+ {
+ id: 'dmtowl',
+ bgColor: '#38761d',
+ fgColor: '#ffffff',
+ name: 'Death Mountain Trail Owl',
+ short: 'Trail Owl',
+ oneway: true,
+ type: 'OwlDrop',
+ icon: '/media/oot/icons/owl-trail.png',
+ },
+ ],
+ },
+];
+
+const DUNGEONS = [
+ {
+ id: 'd1',
+ bgColor: '#ff00ff',
+ fgColor: '#000000',
+ name: 'Dungeon Entrances',
+ short: 'Dungeon Entrances',
+ type: 'main',
+ entrances: [
+ 'deku.main',
+ 'dc.main',
+ 'jabu.main',
+ 'forest.main',
+ 'fire.main',
+ 'water.main',
+ 'shadow.main',
+ 'spirit.main',
+ 'igc.main',
+ 'botw',
+ 'ice',
+ 'gtg',
+ ],
+ },
+ {
+ id: 'd2',
+ bgColor: '#ff00ff',
+ fgColor: '#000000',
+ name: 'Dungeon Exits',
+ short: 'Dungeon Exits',
+ type: 'end',
+ entrances: [
+ 'deku.end',
+ 'dc.end',
+ 'jabu.end',
+ 'forest.end',
+ 'fire.end',
+ 'water.end',
+ 'shadow.end',
+ 'spirit.end',
+ 'igc.end',
+ ],
+ },
+ {
+ id: 'boss',
+ bgColor: '#ff00ff',
+ fgColor: '#000000',
+ name: 'Bosses',
+ short: 'Bosses',
+ entrances: [
+ 'gohma',
+ 'kd',
+ 'barinade',
+ 'pg',
+ 'volvo',
+ 'morpha',
+ 'bongo',
+ 'tr',
+ 'ganon',
+ ],
+ },
+];
+
+const ROOMS = [
+ {
+ id: 'trash',
+ bgColor: '#333333',
+ fgColor: '#dddddd',
+ name: 'Trash',
+ short: 'Trash',
+ multi: true,
+ },
+ {
+ id: 'shop',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Shop',
+ short: 'Shop',
+ multi: true,
+ },
+ {
+ id: 'zlf',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'ZL Fairy',
+ short: 'ZL Fairy',
+ multi: true,
+ },
+ {
+ id: 'toti',
+ bgColor: '#ff0000',
+ fgColor: '#000000',
+ name: 'Temple of Time',
+ short: 'Temple of Time',
+ },
+ {
+ id: 'bcb',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Bombchu Bowling',
+ short: 'Bombchu Bow',
+ },
+ {
+ id: 'fish',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Fishing Game',
+ short: 'Fishing',
+ },
+ {
+ id: 'sling',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Slingshot Game',
+ short: 'Slingshot Game',
+ },
+ {
+ id: 'arch',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Archery Game',
+ short: 'Archery Game',
+ },
+ {
+ id: 'tow',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Lon Lon Ranch Tower',
+ short: 'Ranch Tower',
+ },
+ {
+ id: 'chick',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Chicken Game',
+ short: 'Chicken Game',
+ },
+ {
+ id: 'mill',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Windmill',
+ short: 'Windmill',
+ },
+ {
+ id: 'tomb',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Family Tomb',
+ short: 'Family Tomb',
+ },
+ {
+ id: 'ssg',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Sun Song Grave',
+ short: 'Sun Song Grave',
+ },
+ {
+ id: 'mask',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Mask Shop',
+ short: 'Mask Shop',
+ },
+ {
+ id: 'poe',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Big Poe',
+ short: 'Big Poe',
+ },
+ {
+ id: 'thtr',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Mask Theater',
+ short: 'Mask Theater',
+ },
+ {
+ id: 'skull',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Skulltula House',
+ short: 'Skulltula House',
+ },
+ {
+ id: 'tcg',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Treasute Chest Game',
+ short: 'TCG',
+ },
+ {
+ id: 'lab',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Lab Diving',
+ short: 'Lab Dive',
+ },
+ {
+ id: 'tek',
+ bgColor: '#fffd00',
+ fgColor: '#000000',
+ name: 'Tektite Grotto',
+ short: 'Tektite',
+ },
+];
+
+const TYPE_RESTRICTIONS = {
+ OverworldOneWay: [
+ 'WarpSong',
+ 'BlueWarp',
+ 'OwlDrop',
+ 'OverworldOneWay',
+ 'Overworld',
+ 'Extra',
+ ],
+ OwlDrop: [
+ 'WarpSong',
+ 'BlueWarp',
+ 'OwlDrop',
+ 'OverworldOneWay',
+ 'Overworld',
+ 'Extra',
+ ],
+ Spawn: [
+ 'Spawn',
+ 'WarpSong',
+ 'BlueWarp',
+ 'OwlDrop',
+ 'OverworldOneWay',
+ 'Overworld',
+ 'Interior',
+ 'SpecialInterior',
+ 'Extra',
+ ],
+ WarpSong: [
+ 'Spawn',
+ 'WarpSong',
+ 'BlueWarp',
+ 'OwlDrop',
+ 'OverworldOneWay',
+ 'Overworld',
+ 'Interior',
+ 'SpecialInterior',
+ 'Extra',
+ ],
+};
+
+const CONTEXT = React.createContext({});
+
+const useTracker = () => React.useContext(CONTEXT);
+
+const mapEntrance = (area, entrance) => ({
+ ...entrance,
+ id: `${area.id}.${entrance.id}`,
+ area,
+});
+
+const getArea = (id) => {
+ return AREAS.find((area) => area.id === id);
+};
+
+const getEntranceOfArea = (area, entranceId) => {
+ if (!area) return null;
+ if (!area.entrances) return null;
+ const entrance = area.entrances.find((entrance) => entrance.id === entranceId);
+ return mapEntrance(area, entrance);
+};
+
+const getEntrance = (id) => {
+ if (!id) return null;
+ const dotPos = id.indexOf('.');
+ if (dotPos === -1) {
+ return getArea(id);
+ }
+ const areaId = id.substring(0, dotPos);
+ const entranceId = id.substring(dotPos + 1);
+ const area = getArea(areaId);
+ const entrance = getEntranceOfArea(area, entranceId);
+ return entrance;
+};
+
+const getRoom = (id) => {
+ return ROOMS.find((room) => room.id === id);
+};
+
+const entranceShort = (entrance) => {
+ if (!entrance) return null;
+ return entrance.area
+ ? `${entrance.area.short} (${entrance.short})`
+ : entrance.short;
+};
+
+const entranceName = (entrance) => {
+ if (!entrance) return null;
+ return entrance.area
+ ? `[${entrance.area.short}] ${entrance.name}`
+ : entrance.name;
+};
+
+const entranceFull = (entrance) => {
+ if (!entrance) return null;
+ return entrance.area
+ ? `${entrance.area.name} - ${entrance.name}`
+ : entrance.name;
+};
+
+const entranceStyle = (entrance) => {
+ if (!entrance) return null;
+ return {
+ backgroundColor: entrance.bgColor,
+ color: entrance.fgColor,
+ };
+};
+
+const resolvePath = (connections, from) => {
+ if (!connections[from]) return { dst: null, via: [] };
+ const dstEntrance = getEntrance(connections[from]);
+ if (!dstEntrance || !dstEntrance.throughway) return { dst: connections[from], via: [] };
+ const path = resolvePath(connections, dstEntrance.throughway);
+ if (!path.dst) return { dst: connections[from], via: path.via };
+ return { dst: path.dst, via: [connections[from], ...path.via] };
+};
+
+const vecAdd = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
+
+const vecMul = (v, f) => ({ x: v.x * f, y: v.y * f });
+
+const MAPS = AREAS
+ .filter((area) => !!area.map)
+ .map((area) => ({
+ color: area.bgColor,
+ id: area.id,
+ labelPos: area.map.labelPos ? vecAdd(area.map.pos, area.map.labelPos) : null,
+ name: area.name,
+ pos: area.map.pos,
+ short: area.short,
+ size: area.map.size,
+ bg: {
+ src: area.map.bg.src,
+ pos: vecAdd(area.map.pos, area.map.bg.off),
+ size: vecMul(area.map.size, area.map.bg.scale),
+ },
+ entrances: area.entrances
+ .filter((entrance) => entrance.pos)
+ .map((entrance) => ({
+ id: `${area.id}.${entrance.id}`,
+ name: entranceFull({ ...entrance, area }),
+ pos: vecAdd(area.map.pos, entrance.pos),
+ color: entrance.bgColor,
+ })),
+ }));
+
+const getMapEntrance = (id) => {
+ if (!id) return null;
+ const dotPos = id.indexOf('.');
+ if (dotPos === -1) {
+ return null;
+ }
+ const areaId = id.substring(0, dotPos);
+ const area = MAPS.find((a) => a.id === areaId);
+ if (!area) return null;
+ const entrance = area.entrances.find((e) => e.id === id);
+ return entrance;
+};
+
+const SelectBox = ({ id, name, onChange, options, value }) => {
+ const [open, setOpen] = React.useState(false);
+ const [search, setSearch] = React.useState('');
+
+ const ref = React.useRef();
+ const searchRef = React.useRef();
+
+ const valueEntrance = React.useMemo(() => getEntrance(value) || getRoom(value), [value]);
+
+ const searcher = React.useMemo(() => {
+ return new FuzzySearch(options, ['id', 'name', 'short', 'fullName'], { sort: true });
+ }, [options]);
+
+ const results = React.useMemo(() => {
+ return searcher.search(search);
+ }, [search, searcher]);
+
+ React.useEffect(() => {
+ const handleEventOutside = e => {
+ if (ref.current && !ref.current.contains(e.target)) {
+ setOpen(false);
+ }
+ };
+ document.addEventListener('mousedown', handleEventOutside, true);
+ document.addEventListener('focus', handleEventOutside, true);
+ return () => {
+ document.removeEventListener('mousedown', handleEventOutside, true);
+ document.removeEventListener('focus', handleEventOutside, true);
+ };
+ }, []);
+
+ const classNames = ['entrance-select'];
+ if (open) classNames.push('is-open');
+
+ return <div className={classNames.join(' ')} ref={ref}>
+ <input
+ className="entrance-search"
+ id={id}
+ onChange={({ target: { value } }) => setSearch(value)}
+ onFocus={() => setOpen(true)}
+ ref={searchRef}
+ type="search"
+ value={search}
+ />
+ <div
+ aria-controls={`${id}.options`}
+ aria-expanded={open ? 'true' : 'false'}
+ aria-haspopup={`${id}.options`}
+ className="entrance-value"
+ onClick={() => {
+ setOpen(true);
+ searchRef.current.focus();
+ searchRef.current.select();
+ }}
+ onContextMenu={(e) => {
+ if (value) {
+ onChange({ target: { name, value: null } });
+ } else {
+ onChange({ target: { name, value: 'trash' } });
+ setSearch('');
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ role="combobox"
+ style={entranceStyle(valueEntrance)}
+ title={entranceFull(valueEntrance)}
+ >
+ {entranceShort(valueEntrance)}
+ </div>
+ <div className="entrance-options" id={`${id}.options`} role="listbox">
+ {results.map((entrance) =>
+ <div
+ className="entrance-option"
+ key={entrance.id}
+ onClick={() => {
+ onChange({ target: { name, value: entrance.id } });
+ setOpen(false);
+ setSearch('');
+ }}
+ role="option"
+ style={entranceStyle(entrance)}
+ >
+ {entranceName(entrance)}
+ </div>
+ )}
+ </div>
+ </div>;
+};
+
+SelectBox.propTypes = {
+ className: PropTypes.string,
+ id: PropTypes.string,
+ name: PropTypes.string,
+ onChange: PropTypes.func,
+ options: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.string,
+ name: PropTypes.string,
+ })),
+ value: PropTypes.string,
+};
+
+const EntranceGroup = ({ checked = 0, children, group, total = 0 }) => {
+ return <div className="entrance-group">
+ <h2 style={entranceStyle(group)}>
+ {entranceName(group)}
+ {checked && checked !== total ?
+ <span className="checks">{checked}/{total}</span>
+ : null}
+ </h2>
+ {children}
+ </div>;
+};
+
+EntranceGroup.propTypes = {
+ checked: PropTypes.number,
+ children: PropTypes.node,
+ group: PropTypes.shape({
+ bgColor: PropTypes.string,
+ fgColor: PropTypes.string,
+ name: PropTypes.string,
+ }),
+ total: PropTypes.number,
+};
+
+const EntranceRow = ({ entranceId }) => {
+ const entrance = React.useMemo(() => getEntrance(entranceId), [entranceId]);
+
+ const { connections, entrances, setConnection } = useTracker();
+
+ const options = React.useMemo(() => {
+ if (entrance.type && TYPE_RESTRICTIONS[entrance.type]) {
+ return entrances.filter((e) =>
+ e.type && TYPE_RESTRICTIONS[entrance.type].includes(e.type));
+ }
+ return entrances;
+ }, [entrances]);
+
+ const className = React.useMemo(() => {
+ const classNames = ['entrance-row'];
+ if (entrance.spacer) classNames.push('mt-2');
+ if (connections[entrance.id] === 'trash') classNames.push('is-trash');
+ return classNames.join(' ');
+ }, [entrance, connections]);
+
+ return <div className={className}>
+ <label
+ className="entrance-label"
+ htmlFor={entranceId}
+ style={entranceStyle(entrance)}
+ title={entranceFull(entrance)}
+ >
+ {entranceShort(entrance)}
+ </label>
+ <SelectBox
+ id={entranceId}
+ name={entranceId}
+ onChange={({ target: { name, value } }) => setConnection(name, value)}
+ options={options}
+ value={connections[entranceId]}
+ />
+ </div>;
+};
+
+EntranceRow.propTypes = {
+ entranceId: PropTypes.string,
+};
+
+const MapEntrance = ({ entrance }) => {
+ const {
+ connections,
+ isDragging,
+ onMapEntranceClick,
+ setConnection,
+ } = useTracker();
+
+ const className = React.useMemo(() => {
+ const cs = ['entrance'];
+ if (connections[entrance.id] === 'trash') cs.push('is-trash');
+ if (isDragging(entrance)) cs.push('is-dragging');
+ return cs.join(' ');
+ }, [connections, entrance, isDragging]);
+
+ const path = React.useMemo(() => {
+ return resolvePath(connections, entrance.id);
+ }, [connections, entrance]);
+
+ const destination = React.useMemo(() => {
+ return getEntrance(path.dst) || getRoom(path.dst);
+ }, [path]);
+
+ const onClick = React.useCallback((e) => {
+ onMapEntranceClick(entrance);
+ e.preventDefault();
+ e.stopPropagation();
+ }, [entrance, onMapEntranceClick]);
+
+ const onContext = React.useCallback((e) => {
+ if (connections[entrance.id]) {
+ setConnection(entrance.id, null);
+ } else {
+ setConnection(entrance.id, 'trash');
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ }, [connections, entrance, setConnection]);
+
+ const description = React.useMemo(() => {
+ const parts = [
+ entranceShort(destination),
+ ...path.via.map((id) => `via ${entranceShort(getEntrance(id))}`),
+ `@ ${entranceShort(getEntrance(entrance.id))}`,
+ ];
+ return parts.join(' ');
+ }, [destination, entrance, path]);
+
+ if (destination) {
+ return <rect
+ x={entrance.pos.x - 3}
+ y={entrance.pos.y - 3}
+ width={6}
+ height={6}
+ className={className}
+ fill={destination.bgColor}
+ stroke={destination.fgColor}
+ onClick={onClick}
+ onContextMenu={onContext}
+ >
+ <title>{description}</title>
+ </rect>;
+ }
+
+ return <circle
+ cx={entrance.pos.x}
+ cy={entrance.pos.y}
+ r="3"
+ className={className}
+ fill={entrance.color}
+ stroke="#000000"
+ onClick={onClick}
+ onContextMenu={onContext}
+ >
+ <title>{entrance.name}</title>
+ </circle>;
+};
+
+MapEntrance.propTypes = {
+ entrance: PropTypes.shape({
+ color: PropTypes.string,
+ id: PropTypes.string,
+ name: PropTypes.string,
+ pos: PropTypes.shape({
+ x: PropTypes.number,
+ y: PropTypes.number,
+ })
+ }),
+};
+
+const MapConnector = ({ from, id, isTrash, to, via }) => {
+ const { onConnectorClick } = useTracker();
+
+ const className = React.useMemo(() => {
+ const cs = ['connector'];
+ if (isTrash) cs.push('is-trash');
+ if (via.length) cs.push('is-via');
+ return cs.join(' ');
+ }, [isTrash, via]);
+
+ return <line
+ className={className}
+ onClick={() => onConnectorClick(id)}
+ x1={from.pos.x}
+ y1={from.pos.y}
+ x2={to.pos.x}
+ y2={to.pos.y}
+ />;
+};
+
+MapConnector.propTypes = {
+ from: PropTypes.shape({
+ pos: PropTypes.shape({
+ x: PropTypes.number,
+ y: PropTypes.number,
+ })
+ }),
+ id: PropTypes.string,
+ isTrash: PropTypes.bool,
+ to: PropTypes.shape({
+ pos: PropTypes.shape({
+ x: PropTypes.number,
+ y: PropTypes.number,
+ })
+ }),
+ via: PropTypes.arrayOf(PropTypes.string),
+};
+
+const MapAnnotation = ({ annotation }) => {
+ return <image
+ className="annotation"
+ href={annotation.icon}
+ x={annotation.pos.x}
+ y={annotation.pos.y}
+ width={annotation.size}
+ height={annotation.size}
+ >
+ <title>{annotation.name}</title>
+ </image>;
+};
+
+MapAnnotation.propTypes = {
+ annotation: PropTypes.shape({
+ icon: PropTypes.string,
+ name: PropTypes.string,
+ pos: PropTypes.shape({
+ x: PropTypes.number,
+ y: PropTypes.number,
+ }),
+ size: PropTypes.number,
+ }),
+};
+
+const initPrefs = () => {
+ const dump = localStorage.getItem('zootr.mixed-pools-tracker-prefs');
+ if (dump) {
+ return JSON.parse(dump);
+ }
+ return {
+ showConnectors: true,
+ showEntrances: true,
+ showLabels: true,
+ showMaps: false,
+ showWarps: true,
+ };
+};
+
+const MixedPoolsTracker = () => {
+ const { t } = useTranslation();
+
+ const [connections, setConnections] = React.useState({});
+ const [dragging, setDragging] = React.useState(null);
+ const [prefs, setPrefs] = React.useState(initPrefs());
+ const [trashConnectors, setTrashConnectors] = React.useState([]);
+
+ const setConnection = React.useCallback((src, dst) => {
+ setConnections((c) => {
+ const newConn = { ...c };
+ const srcEntrance = getEntrance(src);
+ const oldTarget = getEntrance(c[src]);
+ if (oldTarget && (!srcEntrance || !srcEntrance.oneway)) {
+ // unset old connection
+ newConn[c[src]] = null;
+ }
+ newConn[src] = dst;
+ if (dst && srcEntrance && !srcEntrance.oneway) {
+ newConn[dst] = src;
+ }
+ return newConn;
+ });
+ }, []);
+
+ const entrances = React.useMemo(() => {
+ const options = [];
+ ROOMS.forEach((room) => {
+ options.push(room);
+ });
+ AREAS.forEach((area) => {
+ if (area.entrances) {
+ area.entrances.forEach((entrance) => {
+ if (entrance.oneway) return;
+ options.push(getEntranceOfArea(area, entrance.id));
+ });
+ } else {
+ options.push(getEntrance(area.id));
+ }
+ });
+ return options.map((option) => ({
+ ...option,
+ fullName: entranceFull(option),
+ }));
+ }, []);
+
+ const isDragging = React.useCallback((entrance) => {
+ return dragging === entrance.id;
+ }, [dragging]);
+
+ const onMapEntranceClick = React.useCallback((entrance) => {
+ if (dragging) {
+ if (dragging !== entrance.id) {
+ setConnection(dragging, entrance.id);
+ }
+ setDragging(null);
+ } else {
+ setDragging(entrance.id);
+ }
+ }, [dragging, setConnection, setDragging]);
+
+ const onConnectorClick = React.useCallback((id) => {
+ setTrashConnectors((tc) => {
+ if (tc.includes(id)) {
+ return tc.filter((tid) => tid !== id);
+ }
+ return [...tc, id];
+ });
+ }, []);
+
+ const context = React.useMemo(() => ({
+ connections,
+ entrances,
+ isDragging,
+ onConnectorClick,
+ onMapEntranceClick,
+ setConnection,
+ }), [
+ connections,
+ entrances,
+ isDragging,
+ onConnectorClick,
+ onMapEntranceClick,
+ setConnection,
+ ]);
+
+ const superGroups = React.useMemo(() => {
+ const sg = [
+ {
+ key: 'one',
+ groups: ['kf', 'lw', 'sfm', 'gy'],
+ },
+ {
+ key: 'two',
+ groups: ['kak', 'm1', 'm2', 'hc'],
+ },
+ {
+ key: 'three',
+ groups: ['hf', 'zr', 'zd', 'zf'],
+ },
+ {
+ key: 'four',
+ groups: ['lh', 'llr', 'gv', 'gf', 'dcol', 'hw'],
+ },
+ {
+ key: 'five',
+ groups: ['dmc', 'dmt', 'gc', 'tot'],
+ },
+ ];
+ sg.forEach((superGroup) => {
+ superGroup.groups = superGroup.groups.map((areaId) => {
+ const area = getArea(areaId);
+ const entranceIds = area.entrances.map((entrance) => `${area.id}.${entrance.id}`);
+ const checked = entranceIds.filter((entranceId) =>
+ Object.entries(connections).find(([a, b]) => b && a === entranceId)
+ ).length;
+ return {
+ area,
+ entranceIds,
+ checked,
+ total: area.entrances.length,
+ };
+ });
+ });
+ return sg;
+ }, [connections]);
+
+ const connectors = React.useMemo(() => {
+ const cs = [];
+ Object.entries(connections).forEach(([from]) => {
+ const fromEntrance = getEntrance(from);
+ if (!fromEntrance) return;
+ const path = resolvePath(connections, from);
+ if (!path.dst) return;
+ if (from > path.dst && !fromEntrance.oneway) return;
+ const fromMap = getMapEntrance(from);
+ if (!fromMap) return;
+ const toMap = getMapEntrance(path.dst);
+ if (!toMap) return;
+ const id = `${fromMap.id}-${toMap.id}`;
+ const isTrash = trashConnectors.includes(id);
+ cs.push({
+ id,
+ from: fromMap,
+ to: toMap,
+ via: path.via,
+ isTrash,
+ });
+ });
+ return cs;
+ }, [connections, trashConnectors]);
+
+ const annotations = React.useMemo(() => {
+ const annotate = [
+ 'songs.minuet',
+ 'songs.bolero',
+ 'songs.serenade',
+ 'songs.nocturne',
+ 'songs.requiem',
+ 'songs.prelude',
+ 'spawns.child',
+ 'spawns.adult',
+ 'owls.lhowl',
+ 'owls.dmtowl',
+ ];
+ const ans = [];
+ annotate.forEach((id) => {
+ if (!connections[id]) return;
+ const srcEntrance = getEntrance(id);
+ if (!srcEntrance) return;
+ const dstMap = getMapEntrance(connections[id]);
+ if (!dstMap) return;
+ ans.push({
+ icon: srcEntrance.icon,
+ name: srcEntrance.name,
+ pos: vecAdd(dstMap.pos, dstMap.annotationOffset || { x: 0, y: 0 }),
+ size: srcEntrance.iconSize || 8,
+ });
+ });
+ return ans;
+ }, [connections]);
+
+ const save = React.useCallback(() => {
+ try {
+ const dump = JSON.stringify({ connections, trashConnectors });
+ localStorage.setItem('zootr.mixed-pools-tracker-save', dump);
+ toastr.success(t('general.saveSuccess'));
+ } catch (e) {
+ toastr.error(t('general.saveError'));
+ console.error(e);
+ }
+ }, [connections, t, trashConnectors]);
+
+ const load = React.useCallback(() => {
+ try {
+ const dump = localStorage.getItem('zootr.mixed-pools-tracker-save');
+ if (!dump) {
+ toastr.error(t('general.loadError'));
+ return;
+ }
+ const { connections, trashConnectors } = JSON.parse(dump);
+ if (connections) {
+ setConnections(connections);
+ } else {
+ setConnections({});
+ }
+ if (trashConnectors) {
+ setTrashConnectors(trashConnectors);
+ } else {
+ setTrashConnectors([]);
+ }
+ toastr.success(t('general.loadSuccess'));
+ } catch (e) {
+ toastr.error(t('general.loadError'));
+ console.error(e);
+ }
+ }, [setConnections, t]);
+
+ const reset = React.useCallback(() => {
+ try {
+ setConnections({});
+ setTrashConnectors([]);
+ toastr.success(t('general.resetSuccess'));
+ } catch (e) {
+ toastr.error(t('general.resetError'));
+ }
+ }, [t]);
+
+ const togglePref = React.useCallback((which) => {
+ setPrefs((oldPrefs) => {
+ const newPrefs = {
+ ...oldPrefs,
+ [which]: !oldPrefs[which],
+ };
+ localStorage.setItem('zootr.mixed-pools-tracker-prefs', JSON.stringify(newPrefs));
+ return newPrefs;
+ });
+ }, []);
+
+ return <CONTEXT.Provider value={context}>
+ <div className="mixed-pools-tracker">
+ <div className="columns">
+ {superGroups.map((sg) =>
+ <div className="column" key={sg.key}>
+ {sg.groups.map(group =>
+ <EntranceGroup
+ checked={group.checked}
+ group={group.area}
+ key={group.area.id}
+ total={group.total}
+ >
+ {group.entranceIds.map((entranceId) =>
+ <EntranceRow entranceId={entranceId} key={entranceId} />
+ )}
+ </EntranceGroup>
+ )}
+ </div>
+ )}
+ </div>
+ <div className="columns">
+ {DUNGEONS.map((area) =>
+ <div className="column" key={area.id}>
+ <EntranceGroup group={area}>
+ {area.entrances.map((entranceId) =>
+ <EntranceRow
+ entranceId={entranceId}
+ key={entranceId}
+ />
+ )}
+ </EntranceGroup>
+ </div>
+ )}
+ <div className="column">
+ {AREAS.slice(43, 44).map((area) =>
+ <EntranceGroup group={area} key={area.id}>
+ {area.entrances.map((entrance) =>
+ <EntranceRow
+ entranceId={`${area.id}.${entrance.id}`}
+ key={entrance.id}
+ />
+ )}
+ </EntranceGroup>
+ )}
+ </div>
+ <div className="column">
+ {AREAS.slice(44, 46).map((area) =>
+ <EntranceGroup group={area} key={area.id}>
+ {area.entrances.map((entrance) =>
+ <EntranceRow
+ entranceId={`${area.id}.${entrance.id}`}
+ key={entrance.id}
+ />
+ )}
+ </EntranceGroup>
+ )}
+ </div>
+ </div>
+ <div className="map mt-5">
+ <svg
+ viewBox="0 0 500 370"
+ onClick={() => { setDragging(null); }}
+ onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); }}
+ >
+ <g className="background">
+ {MAPS.map((map) =>
+ <g className="area" key={map.id} title={map.name}>
+ <image
+ href={map.bg.src}
+ pointerEvents="none"
+ x={map.bg.pos.x} y={map.bg.pos.y}
+ width={map.bg.size.x}
+ style={{ opacity: prefs.showMaps ? 1 : 0.25 }}
+ />
+ {map.labelPos && prefs.showLabels ?
+ <text
+ className="area-label"
+ x={map.labelPos.x}
+ y={map.labelPos.y}
+ fill={map.color}
+ >
+ {map.short}
+ </text>
+ : null}
+ </g>
+ )}
+ </g>
+ {prefs.showConnectors ?
+ <g title="connectors">
+ {connectors.map((c) =>
+ <MapConnector
+ key={c.id}
+ from={c.from}
+ id={c.id}
+ isTrash={c.isTrash}
+ to={c.to}
+ via={c.via}
+ />
+ )}
+ </g>
+ : null}
+ {MAPS.map((map) =>
+ <g className="area" key={map.id} title={map.name}>
+ {prefs.showEntrances ? map.entrances.map((entrance) =>
+ <MapEntrance key={entrance.id} entrance={entrance} />
+ ) : null}
+ </g>
+ )}
+ {prefs.showWarps ?
+ <g title="anotations">
+ {annotations.map((a) =>
+ <MapAnnotation
+ key={`${a.id}`}
+ annotation={a}
+ />
+ )}
+ </g>
+ : null}
+ </svg>
+ </div>
+ <div className="menu-bar">
+ <div className="button-bar">
+ <Button
+ onClick={() => togglePref('showConnectors')}
+ size="sm"
+ variant={prefs.showConnectors ? 'secondary' : 'outline-secondary'}
+ >
+ Connectors
+ </Button>
+ <Button
+ onClick={() => togglePref('showEntrances')}
+ size="sm"
+ variant={prefs.showEntrances ? 'secondary' : 'outline-secondary'}
+ >
+ Entrances
+ </Button>
+ <Button
+ onClick={() => togglePref('showLabels')}
+ size="sm"
+ variant={prefs.showLabels ? 'secondary' : 'outline-secondary'}
+ >
+ Labels
+ </Button>
+ <Button
+ onClick={() => togglePref('showMaps')}
+ size="sm"
+ variant={prefs.showMaps ? 'secondary' : 'outline-secondary'}
+ >
+ Maps
+ </Button>
+ <Button
+ onClick={() => togglePref('showWarps')}
+ size="sm"
+ variant={prefs.showWarps ? 'secondary' : 'outline-secondary'}
+ >
+ Warps
+ </Button>
+ </div>
+ <div className="button-bar">
+ <Button
+ onClick={save}
+ size="sm"
+ title={t('button.save')}
+ variant="outline-secondary"
+ >
+ <Icon.SAVE title="" />
+ </Button>
+ <Button
+ onClick={load}
+ size="sm"
+ title={t('button.load')}
+ variant="outline-secondary"
+ >
+ <Icon.LOAD title="" />
+ </Button>
+ <Button
+ onClick={reset}
+ size="sm"
+ title={t('button.reset')}
+ variant="outline-secondary"
+ >
+ <Icon.RESET title="" />
+ </Button>
+ </div>
+ </div>
+ </div>
+ </CONTEXT.Provider>;
+};
+
+export default MixedPoolsTracker;
+++ /dev/null
-import CRC32 from 'crc-32';
-import localforage from 'localforage';
-import PropTypes from 'prop-types';
-import React from 'react';
-import toastr from 'toastr';
-
-import i18n from '../i18n';
-
-const AlttpBaseRomContext = React.createContext(null);
-
-const AlttpBaseRomProvider = ({ children }) => {
- const [rom, setRom] = React.useState(null);
-
- const setRomCallback = React.useCallback(buffer => {
- if (buffer) {
- const crc = CRC32.buf(new Uint8Array(buffer));
- if (crc === 0x3322EFFC) {
- setRom(buffer);
- localforage.setItem('alttpBaseRom', buffer);
- toastr.success(i18n.t('alttp.baseRomSet'));
- } else {
- toastr.error(i18n.t('alttp.baseRomInvalid'));
- }
- } else {
- setRom(null);
- localforage.removeItem('alttpBaseRom');
- toastr.success(i18n.t('alttp.baseRomRemoved'));
- }
- }, [setRom]);
-
- React.useEffect(() => {
- (async () => {
- const stored = await localforage.getItem('alttpBaseRom');
- if (stored) {
- const crc = CRC32.buf(new Uint8Array(stored));
- if (crc == 0x3322EFFC) {
- setRom(stored);
- }
- }
- })();
- }, []);
-
- return <AlttpBaseRomContext.Provider value={{ rom, setRom: setRomCallback }}>
- {children}
- </AlttpBaseRomContext.Provider>;
-};
-
-AlttpBaseRomProvider.propTypes = {
- children: PropTypes.node,
-};
-
-export const useAlttpBaseRom = () => React.useContext(AlttpBaseRomContext);
-
-export default AlttpBaseRomProvider;
--- /dev/null
+import CRC32 from 'crc-32';
+import localforage from 'localforage';
+import PropTypes from 'prop-types';
+import React from 'react';
+import toastr from 'toastr';
+
+import i18n from '../i18n';
+
+const AlttpBaseRomContext = React.createContext(null);
+
+const AlttpBaseRomProvider = ({ children }) => {
+ const [rom, setRom] = React.useState(null);
+
+ const setRomCallback = React.useCallback(buffer => {
+ if (buffer) {
+ const crc = CRC32.buf(new Uint8Array(buffer));
+ if (crc === 0x3322EFFC) {
+ setRom(buffer);
+ localforage.setItem('alttpBaseRom', buffer);
+ toastr.success(i18n.t('alttp.baseRomSet'));
+ } else {
+ toastr.error(i18n.t('alttp.baseRomInvalid'));
+ }
+ } else {
+ setRom(null);
+ localforage.removeItem('alttpBaseRom');
+ toastr.success(i18n.t('alttp.baseRomRemoved'));
+ }
+ }, [setRom]);
+
+ React.useEffect(() => {
+ (async () => {
+ const stored = await localforage.getItem('alttpBaseRom');
+ if (stored) {
+ const crc = CRC32.buf(new Uint8Array(stored));
+ if (crc == 0x3322EFFC) {
+ setRom(stored);
+ }
+ }
+ })();
+ }, []);
+
+ return <AlttpBaseRomContext.Provider value={{ rom, setRom: setRomCallback }}>
+ {children}
+ </AlttpBaseRomContext.Provider>;
+};
+
+AlttpBaseRomProvider.propTypes = {
+ children: PropTypes.node,
+};
+
+export const useAlttpBaseRom = () => React.useContext(AlttpBaseRomContext);
+
+export default AlttpBaseRomProvider;
+++ /dev/null
-import React from 'react';
-
-import Icon from '../components/common/Icon';
-import { getUserName } from './User';
-
-export const compareUsername = (a, b) => {
- const a_name = (a && getUserName(a.user)) || '';
- const b_name = (b && getUserName(b.user)) || '';
- return a_name.localeCompare(b_name);
-};
-
-export const compareResult = (a, b) => {
- const a_placement = a && a.placement ? a.placement : 0;
- const b_placement = b && b.placement ? b.placement : 0;
- if (a_placement) {
- if (b_placement) {
- if (a_placement < b_placement) return -1;
- if (b_placement < a_placement) return 1;
- return compareUsername(a, b);
- }
- return -1;
- }
- if (b_placement) {
- return 1;
- }
- return compareUsername(a, b);
-};
-
-export const formatTime = result => {
- const hours = `${Math.floor(result.time / 60 / 60)}`;
- let minutes = `${Math.floor((result.time / 60) % 60)}`;
- let seconds = `${Math.floor(result.time % 60)}`;
- while (minutes.length < 2) {
- minutes = `0${minutes}`;
- }
- while (seconds.length < 2) {
- seconds = `0${seconds}`;
- }
- return `${hours}:${minutes}:${seconds}`;
-};
-
-export const getIcon = (result, maySee) => {
- if (!result || !result.has_finished) {
- return <Icon.PENDING className="text-muted" size="lg" />;
- }
- if (result.forfeit && maySee) {
- return <Icon.FORFEIT className="text-danger" size="lg" />;
- }
- if (result.placement === 1 && maySee) {
- return <Icon.FIRST_PLACE className="text-gold" size="lg" />;
- }
- if (result.placement === 2 && maySee) {
- return <Icon.SECOND_PLACE className="text-silver" size="lg" />;
- }
- if (result.placement === 3 && maySee) {
- return <Icon.THIRD_PLACE className="text-bronze" size="lg" />;
- }
- return <Icon.FINISHED className="text-success" size="lg" />;
-};
-
-export const getTime = (result, maySee) => {
- if (!result || !maySee) {
- return null;
- }
- if (result.time) {
- return formatTime(result);
- }
- if (result.forfeit) {
- return 'DNF';
- }
- return '?';
-};
-
-export const parseTime = str => {
- if (!str) return null;
- return `${str}`.trim().split(/[-.: ]+/).reduce((acc,time) => (60 * acc) + +time, 0);
-};
-
-export const sortByTime = (results) => [...results].sort(compareResult);
-
-export const sortByUsername = (results) => [...results].sort(compareUsername);
-
-export default {
- compareResult,
- compareUsername,
- formatTime,
- getIcon,
- getTime,
- parseTime,
-};
--- /dev/null
+import React from 'react';
+
+import Icon from '../components/common/Icon';
+import { getUserName } from './User';
+
+export const compareUsername = (a, b) => {
+ const a_name = (a && getUserName(a.user)) || '';
+ const b_name = (b && getUserName(b.user)) || '';
+ return a_name.localeCompare(b_name);
+};
+
+export const compareResult = (a, b) => {
+ const a_placement = a && a.placement ? a.placement : 0;
+ const b_placement = b && b.placement ? b.placement : 0;
+ if (a_placement) {
+ if (b_placement) {
+ if (a_placement < b_placement) return -1;
+ if (b_placement < a_placement) return 1;
+ return compareUsername(a, b);
+ }
+ return -1;
+ }
+ if (b_placement) {
+ return 1;
+ }
+ return compareUsername(a, b);
+};
+
+export const formatTime = result => {
+ const hours = `${Math.floor(result.time / 60 / 60)}`;
+ let minutes = `${Math.floor((result.time / 60) % 60)}`;
+ let seconds = `${Math.floor(result.time % 60)}`;
+ while (minutes.length < 2) {
+ minutes = `0${minutes}`;
+ }
+ while (seconds.length < 2) {
+ seconds = `0${seconds}`;
+ }
+ return `${hours}:${minutes}:${seconds}`;
+};
+
+export const getIcon = (result, maySee) => {
+ if (!result || !result.has_finished) {
+ return <Icon.PENDING className="text-muted" size="lg" />;
+ }
+ if (result.forfeit && maySee) {
+ return <Icon.FORFEIT className="text-danger" size="lg" />;
+ }
+ if (result.placement === 1 && maySee) {
+ return <Icon.FIRST_PLACE className="text-gold" size="lg" />;
+ }
+ if (result.placement === 2 && maySee) {
+ return <Icon.SECOND_PLACE className="text-silver" size="lg" />;
+ }
+ if (result.placement === 3 && maySee) {
+ return <Icon.THIRD_PLACE className="text-bronze" size="lg" />;
+ }
+ return <Icon.FINISHED className="text-success" size="lg" />;
+};
+
+export const getTime = (result, maySee) => {
+ if (!result || !maySee) {
+ return null;
+ }
+ if (result.time) {
+ return formatTime(result);
+ }
+ if (result.forfeit) {
+ return 'DNF';
+ }
+ return '?';
+};
+
+export const parseTime = str => {
+ if (!str) return null;
+ return `${str}`.trim().split(/[-.: ]+/).reduce((acc,time) => (60 * acc) + +time, 0);
+};
+
+export const sortByTime = (results) => [...results].sort(compareResult);
+
+export const sortByUsername = (results) => [...results].sort(compareUsername);
+
+export default {
+ compareResult,
+ compareUsername,
+ formatTime,
+ getIcon,
+ getTime,
+ parseTime,
+};
+++ /dev/null
-import React from 'react';
-
-const nl2br = str => {
- if (typeof str !== 'string') {
- return str;
- }
- const nl = /(\r\n|\r|\n)/g;
- return str.split(nl).map((line, index) => {
- if (line.match(nl)) {
- return <br key={index} />;
- }
- return line;
- });
-};
-
-export default nl2br;
--- /dev/null
+import React from 'react';
+
+const nl2br = str => {
+ if (typeof str !== 'string') {
+ return str;
+ }
+ const nl = /(\r\n|\r|\n)/g;
+ return str.split(nl).map((line, index) => {
+ if (line.match(nl)) {
+ return <br key={index} />;
+ }
+ return line;
+ });
+};
+
+export default nl2br;
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import SettingsDialog from '../components/snes/SettingsDialog';
-import SNESSocket from '../helpers/SNESSocket';
-
-const context = React.createContext({});
-
-export const useSNES = () => React.useContext(context);
-
-export const SNESProvider = ({ children }) => {
- const [enabled, setEnabled] = React.useState(false);
- const [showSettingsDialog, setShowSettingsDialog] = React.useState(false);
-
- const sock = React.useRef(null);
-
- const [settings, setSettings] = React.useState({
- proto: 'ws',
- host: 'localhost',
- port: 23074,
- device: '',
- });
-
- const [status, setStatus] = React.useState({
- connected: false,
- device: '',
- deviceList: [],
- error: false,
- });
-
- React.useEffect(() => {
- if (sock.current) {
- sock.current.close();
- sock.current = null;
- }
- if (enabled) {
- const tryAttach = () => {
- const { deviceList } = sock.current;
- let device = '';
- if (deviceList.includes(settings.device)) {
- device = settings.device;
- } else if (deviceList.length > 0) {
- device = deviceList[0];
- }
- setStatus(s => ({ ...s, device, deviceList }));
- if (device) {
- sock.current.attachDevice(device);
- }
- };
- sock.current = new SNESSocket(`${settings.proto}://${settings.host}:${settings.port}`);
- sock.current.onclose = () => {
- setStatus({
- connected: false,
- device: '',
- deviceList: [],
- error: false,
- });
- };
- sock.current.onerror = (e) => {
- setStatus({
- connected: false,
- device: '',
- deviceList: [],
- error: e,
- });
- };
- sock.current.onopen = () => {
- setStatus({
- connected: true,
- device: '',
- deviceList: [],
- error: false,
- });
- sock.current.requestDeviceList(() => {
- tryAttach();
- });
- };
- const watchdog = setInterval(() => {
- if (!sock.current.isOpen()) {
- sock.current.open();
- return;
- }
- if (!sock.current.device) {
- sock.current.requestDeviceList(() => {
- tryAttach();
- });
- }
- }, 5000);
- return () => {
- clearInterval(watchdog);
- };
- }
- }, [enabled, settings]);
-
- const enable = React.useCallback(() => {
- setEnabled(prevEnabled => {
- if (prevEnabled) return true;
- return true;
- });
- }, []);
-
- const disable = React.useCallback(() => {
- setEnabled(prevEnabled => {
- if (!prevEnabled) return false;
- return false;
- });
- }, []);
-
- const openSettings = React.useCallback(() => {
- setShowSettingsDialog(true);
- }, []);
-
- const closeSettings = React.useCallback(() => {
- setShowSettingsDialog(false);
- }, []);
-
- const saveSettings = React.useCallback((values) => {
- setSettings(s => {
- const newSettings = { ...s, ...values };
- localStorage.setItem('snes.settings', JSON.stringify(newSettings));
- return newSettings;
- });
- setShowSettingsDialog(false);
- }, []);
-
- React.useEffect(() => {
- const savedSettings = localStorage.getItem('snes.settings');
- if (savedSettings) {
- setSettings(JSON.parse(savedSettings));
- }
- }, []);
-
- const value = React.useMemo(() => {
- return { disable, enable, enabled, openSettings, settings, sock, status };
- }, [disable, enable, enabled, openSettings, settings, sock, status]);
-
- return <context.Provider value={value}>
- {children}
- <SettingsDialog
- deviceList={status.deviceList}
- onHide={closeSettings}
- onSubmit={saveSettings}
- settings={settings}
- show={showSettingsDialog}
- />
- </context.Provider>;
-};
-
-SNESProvider.propTypes = {
- children: PropTypes.node,
-};
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import SettingsDialog from '../components/snes/SettingsDialog';
+import SNESSocket from '../helpers/SNESSocket';
+
+const context = React.createContext({});
+
+export const useSNES = () => React.useContext(context);
+
+export const SNESProvider = ({ children }) => {
+ const [enabled, setEnabled] = React.useState(false);
+ const [showSettingsDialog, setShowSettingsDialog] = React.useState(false);
+
+ const sock = React.useRef(null);
+
+ const [settings, setSettings] = React.useState({
+ proto: 'ws',
+ host: 'localhost',
+ port: 23074,
+ device: '',
+ });
+
+ const [status, setStatus] = React.useState({
+ connected: false,
+ device: '',
+ deviceList: [],
+ error: false,
+ });
+
+ React.useEffect(() => {
+ if (sock.current) {
+ sock.current.close();
+ sock.current = null;
+ }
+ if (enabled) {
+ const tryAttach = () => {
+ const { deviceList } = sock.current;
+ let device = '';
+ if (deviceList.includes(settings.device)) {
+ device = settings.device;
+ } else if (deviceList.length > 0) {
+ device = deviceList[0];
+ }
+ setStatus(s => ({ ...s, device, deviceList }));
+ if (device) {
+ sock.current.attachDevice(device);
+ }
+ };
+ sock.current = new SNESSocket(`${settings.proto}://${settings.host}:${settings.port}`);
+ sock.current.onclose = () => {
+ setStatus({
+ connected: false,
+ device: '',
+ deviceList: [],
+ error: false,
+ });
+ };
+ sock.current.onerror = (e) => {
+ setStatus({
+ connected: false,
+ device: '',
+ deviceList: [],
+ error: e,
+ });
+ };
+ sock.current.onopen = () => {
+ setStatus({
+ connected: true,
+ device: '',
+ deviceList: [],
+ error: false,
+ });
+ sock.current.requestDeviceList(() => {
+ tryAttach();
+ });
+ };
+ const watchdog = setInterval(() => {
+ if (!sock.current.isOpen()) {
+ sock.current.open();
+ return;
+ }
+ if (!sock.current.device) {
+ sock.current.requestDeviceList(() => {
+ tryAttach();
+ });
+ }
+ }, 5000);
+ return () => {
+ clearInterval(watchdog);
+ };
+ }
+ }, [enabled, settings]);
+
+ const enable = React.useCallback(() => {
+ setEnabled(prevEnabled => {
+ if (prevEnabled) return true;
+ return true;
+ });
+ }, []);
+
+ const disable = React.useCallback(() => {
+ setEnabled(prevEnabled => {
+ if (!prevEnabled) return false;
+ return false;
+ });
+ }, []);
+
+ const openSettings = React.useCallback(() => {
+ setShowSettingsDialog(true);
+ }, []);
+
+ const closeSettings = React.useCallback(() => {
+ setShowSettingsDialog(false);
+ }, []);
+
+ const saveSettings = React.useCallback((values) => {
+ setSettings(s => {
+ const newSettings = { ...s, ...values };
+ localStorage.setItem('snes.settings', JSON.stringify(newSettings));
+ return newSettings;
+ });
+ setShowSettingsDialog(false);
+ }, []);
+
+ React.useEffect(() => {
+ const savedSettings = localStorage.getItem('snes.settings');
+ if (savedSettings) {
+ setSettings(JSON.parse(savedSettings));
+ }
+ }, []);
+
+ const value = React.useMemo(() => {
+ return { disable, enable, enabled, openSettings, settings, sock, status };
+ }, [disable, enable, enabled, openSettings, settings, sock, status]);
+
+ return <context.Provider value={value}>
+ {children}
+ <SettingsDialog
+ deviceList={status.deviceList}
+ onHide={closeSettings}
+ onSubmit={saveSettings}
+ settings={settings}
+ show={showSettingsDialog}
+ />
+ </context.Provider>;
+};
+
+SNESProvider.propTypes = {
+ children: PropTypes.node,
+};
+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import {
- CONFIG,
- DUNGEONS,
- applyLogic,
- configureDungeons,
- makeEmptyState,
- mergeStates,
-} from '../helpers/tracker';
-
-const context = React.createContext({});
-
-export const useTracker = () => React.useContext(context);
-
-export const TrackerProvider = ({ children }) => {
- const [config, setConfig] = React.useState(CONFIG);
- const [state, setState] = React.useState(makeEmptyState());
- const [autoState, setAutoState] = React.useState(makeEmptyState());
- const [manualState, setManualState] = React.useState(makeEmptyState());
- const [dungeons, setDungeons] = React.useState(DUNGEONS);
- const [logic, setLogic] = React.useState({});
- const [pins, setPins] = React.useState([]);
-
- const saveConfig = React.useCallback((values) => {
- setConfig(s => {
- const newConfig = { ...s, ...values };
- localStorage.setItem('tracker.config', JSON.stringify(newConfig));
- return newConfig;
- });
- }, []);
-
- const addPin = React.useCallback((pin) => {
- setPins(ps => {
- const id = ps.length ? ps[ps.length - 1].id + 1 : 1;
- return [...ps, { ...pin, id }];
- });
- }, []);
-
- const removePin = React.useCallback((pin) => {
- setPins(ps => ps.filter(p => p.id !== pin.id));
- }, []);
-
- React.useEffect(() => {
- const savedConfig = localStorage.getItem('tracker.config');
- if (savedConfig) {
- setConfig(c => ({ ...c, ...JSON.parse(savedConfig) }));
- }
- }, []);
-
- React.useEffect(() => {
- const newDungeons = configureDungeons(config);
- setDungeons(newDungeons);
- }, [config]);
-
- React.useEffect(() => {
- setState(mergeStates(autoState, manualState));
- }, [autoState, manualState]);
-
- React.useEffect(() => {
- setLogic(applyLogic(config, dungeons, state));
- }, [config, dungeons, state]);
-
- const value = React.useMemo(() => {
- return {
- addPin,
- config,
- dungeons,
- logic,
- pins,
- removePin,
- saveConfig,
- setAutoState,
- setManualState,
- state,
- };
- }, [config, dungeons, logic, pins, state]);
-
- return <context.Provider value={value}>
- {children}
- </context.Provider>;
-};
-
-TrackerProvider.propTypes = {
- children: PropTypes.node,
-};
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import {
+ CONFIG,
+ DUNGEONS,
+ applyLogic,
+ configureDungeons,
+ makeEmptyState,
+ mergeStates,
+} from '../helpers/tracker';
+
+const context = React.createContext({});
+
+export const useTracker = () => React.useContext(context);
+
+export const TrackerProvider = ({ children }) => {
+ const [config, setConfig] = React.useState(CONFIG);
+ const [state, setState] = React.useState(makeEmptyState());
+ const [autoState, setAutoState] = React.useState(makeEmptyState());
+ const [manualState, setManualState] = React.useState(makeEmptyState());
+ const [dungeons, setDungeons] = React.useState(DUNGEONS);
+ const [logic, setLogic] = React.useState({});
+ const [pins, setPins] = React.useState([]);
+
+ const saveConfig = React.useCallback((values) => {
+ setConfig(s => {
+ const newConfig = { ...s, ...values };
+ localStorage.setItem('tracker.config', JSON.stringify(newConfig));
+ return newConfig;
+ });
+ }, []);
+
+ const addPin = React.useCallback((pin) => {
+ setPins(ps => {
+ const id = ps.length ? ps[ps.length - 1].id + 1 : 1;
+ return [...ps, { ...pin, id }];
+ });
+ }, []);
+
+ const removePin = React.useCallback((pin) => {
+ setPins(ps => ps.filter(p => p.id !== pin.id));
+ }, []);
+
+ React.useEffect(() => {
+ const savedConfig = localStorage.getItem('tracker.config');
+ if (savedConfig) {
+ setConfig(c => ({ ...c, ...JSON.parse(savedConfig) }));
+ }
+ }, []);
+
+ React.useEffect(() => {
+ const newDungeons = configureDungeons(config);
+ setDungeons(newDungeons);
+ }, [config]);
+
+ React.useEffect(() => {
+ setState(mergeStates(autoState, manualState));
+ }, [autoState, manualState]);
+
+ React.useEffect(() => {
+ setLogic(applyLogic(config, dungeons, state));
+ }, [config, dungeons, state]);
+
+ const value = React.useMemo(() => {
+ return {
+ addPin,
+ config,
+ dungeons,
+ logic,
+ pins,
+ removePin,
+ saveConfig,
+ setAutoState,
+ setManualState,
+ state,
+ };
+ }, [config, dungeons, logic, pins, state]);
+
+ return <context.Provider value={value}>
+ {children}
+ </context.Provider>;
+};
+
+TrackerProvider.propTypes = {
+ children: PropTypes.node,
+};
+++ /dev/null
-import axios from 'axios';
-import { isEqual } from 'lodash';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-const context = React.createContext({
- login: () => false,
- logout: () => false,
- user: null,
-});
-
-export const useUser = () => React.useContext(context);
-
-export const withUser = (WrappedComponent, as) => function WithUserContext(props) {
- return <context.Consumer>
- {ctx => <WrappedComponent {...{[as || 'user']: ctx.user, ...props}} />}
- </context.Consumer>;
-};
-
-export const UserProvider = ({ children }) => {
- const [user, setUser] = React.useState(null);
-
- const fetchUser = React.useCallback(async () => {
- try {
- const response = await axios.get('/api/user');
- setUser(user => isEqual(user, response.data) ? user : response.data);
- } catch (e) {
- setUser(null);
- }
- }, []);
-
- React.useEffect(() => {
- let timer = null;
- axios
- .get('/sanctum/csrf-cookie')
- .then(() => {
- fetchUser();
- timer = setInterval(fetchUser, 5 * 60 * 1000);
- });
- return () => {
- if (timer) clearInterval(timer);
- };
- }, []);
-
- const login = React.useCallback(async (creds) => {
- try {
- await axios.post('/login', {
- ...creds,
- remember: 'on',
- });
- await fetchUser();
- } catch (error) {
- if (error.response && error.response.status === 419) {
- await axios.get('/sanctum/csrf-cookie');
- await axios.post('/login', {
- ...creds,
- remember: 'on',
- });
- await fetchUser();
- } else {
- throw error;
- }
- }
- }, []);
-
- const logout = React.useCallback(async () => {
- await axios.post('/logout');
- setUser(null);
- }, []);
-
- return <context.Provider value={{ login, logout, user }}>
- {children}
- </context.Provider>;
-};
-
-UserProvider.propTypes = {
- children: PropTypes.node,
-};
--- /dev/null
+import axios from 'axios';
+import { isEqual } from 'lodash';
+import PropTypes from 'prop-types';
+import React from 'react';
+
+const context = React.createContext({
+ login: () => false,
+ logout: () => false,
+ user: null,
+});
+
+export const useUser = () => React.useContext(context);
+
+export const withUser = (WrappedComponent, as) => function WithUserContext(props) {
+ return <context.Consumer>
+ {ctx => <WrappedComponent {...{[as || 'user']: ctx.user, ...props}} />}
+ </context.Consumer>;
+};
+
+export const UserProvider = ({ children }) => {
+ const [user, setUser] = React.useState(null);
+
+ const fetchUser = React.useCallback(async () => {
+ try {
+ const response = await axios.get('/api/user');
+ setUser(user => isEqual(user, response.data) ? user : response.data);
+ } catch (e) {
+ setUser(null);
+ }
+ }, []);
+
+ React.useEffect(() => {
+ let timer = null;
+ axios
+ .get('/sanctum/csrf-cookie')
+ .then(() => {
+ fetchUser();
+ timer = setInterval(fetchUser, 5 * 60 * 1000);
+ });
+ return () => {
+ if (timer) clearInterval(timer);
+ };
+ }, []);
+
+ const login = React.useCallback(async (creds) => {
+ try {
+ await axios.post('/login', {
+ ...creds,
+ remember: 'on',
+ });
+ await fetchUser();
+ } catch (error) {
+ if (error.response && error.response.status === 419) {
+ await axios.get('/sanctum/csrf-cookie');
+ await axios.post('/login', {
+ ...creds,
+ remember: 'on',
+ });
+ await fetchUser();
+ } else {
+ throw error;
+ }
+ }
+ }, []);
+
+ const logout = React.useCallback(async () => {
+ await axios.post('/logout');
+ setUser(null);
+ }, []);
+
+ return <context.Provider value={{ login, logout, user }}>
+ {children}
+ </context.Provider>;
+};
+
+UserProvider.propTypes = {
+ children: PropTypes.node,
+};
+++ /dev/null
-/**
- * First we will load all of this project's JavaScript dependencies which
- * includes React and other helpers. It's a great starting point while
- * building robust, powerful web applications using React + Laravel.
- */
-
-import './bootstrap';
-
-import React from 'react';
-import { createRoot } from 'react-dom/client';
-
-import toastr from 'toastr';
-toastr.options.positionClass = 'toast-bottom-right';
-
-/**
- * Next, we will create a fresh React component instance and attach it to
- * the page. Then, you may begin adding components to this application
- * or customize the JavaScript scaffolding to fit your unique needs.
- */
-
-import App from './app';
-
-if (document.getElementById('react-root')) {
- const root = createRoot(document.getElementById('react-root'));
- root.render(<App />);
-}
--- /dev/null
+/**
+ * First we will load all of this project's JavaScript dependencies which
+ * includes React and other helpers. It's a great starting point while
+ * building robust, powerful web applications using React + Laravel.
+ */
+
+import './bootstrap';
+import '../sass/app.scss';
+
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+
+import toastr from 'toastr';
+toastr.options.positionClass = 'toast-bottom-right';
+
+/**
+ * Next, we will create a fresh React component instance and attach it to
+ * the page. Then, you may begin adding components to this application
+ * or customize the JavaScript scaffolding to fit your unique needs.
+ */
+
+import App from './app';
+
+if (document.getElementById('react-root')) {
+ const root = createRoot(document.getElementById('react-root'));
+ root.render(<App />);
+}
+++ /dev/null
-import axios from 'axios';
-import React, { useCallback, useEffect, useState } from 'react';
-import { Helmet } from 'react-helmet';
-import { useParams } from 'react-router-dom';
-
-import NotFound from './NotFound';
-import Seed from '../components/alttp-seeds/Seed';
-import ErrorBoundary from '../components/common/ErrorBoundary';
-import ErrorMessage from '../components/common/ErrorMessage';
-import Loading from '../components/common/Loading';
-
-export const Component = () => {
- const params = useParams();
- const { hash } = params;
-
- const [error, setError] = useState(null);
- const [loading, setLoading] = useState(true);
- const [patch, setPatch] = useState(null);
- const [seed, setSeed] = useState(null);
-
- const loadSeed = useCallback((hash, ctrl) => {
- axios
- .get(`/api/alttp-seed/${hash}`, { signal: ctrl.signal })
- .then(response => {
- setError(null);
- setLoading(false);
- setSeed(response.data);
- })
- .catch(error => {
- setError(error);
- setLoading(false);
- setSeed(null);
- });
- }, []);
-
- useEffect(() => {
- setLoading(true);
- const ctrl = new AbortController();
- loadSeed(hash, ctrl);
- return () => {
- ctrl.abort();
- };
- }, [hash]);
-
- useEffect(() => {
- if (!seed || seed.status !== 'pending') {
- return;
- }
- const ctrl = new AbortController();
- const timer = setTimeout(() => {
- loadSeed(seed.hash, ctrl);
- }, 2000);
- return () => {
- clearTimeout(timer);
- ctrl.abort();
- };
- }, [seed]);
-
- useEffect(() => {
- setPatch(null);
- if (!seed || seed.status !== 'generated') {
- return;
- }
- const ctrl = new AbortController();
- axios
- .get(`/alttp-seeds/${hash}.bps`, {
- responseType: 'arraybuffer',
- signal: ctrl.signal,
- })
- .then(response => {
- setPatch(response.data);
- })
- .catch(error => {
- setError(error);
- });
- return () => {
- ctrl.abort();
- };
- }, [hash, seed]);
-
- const retry = useCallback(async () => {
- await axios.post(`/api/alttp-seed/${hash}/retry`);
- setSeed(seed => ({ ...seed, status: 'pending' }));
- });
-
- if (loading) {
- return <Loading />;
- }
-
- if (error) {
- return <ErrorMessage error={error} />;
- }
-
- if (!seed) {
- return <NotFound />;
- }
-
- return <ErrorBoundary>
- <Helmet>
- {seed ?
- <title>{seed.hash}</title>
- : null}
- </Helmet>
- <Seed onRetry={retry} patch={patch} seed={seed} />
- </ErrorBoundary>;
-};
--- /dev/null
+import axios from 'axios';
+import React, { useCallback, useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet';
+import { useParams } from 'react-router-dom';
+
+import NotFound from './NotFound';
+import Seed from '../components/alttp-seeds/Seed';
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import ErrorMessage from '../components/common/ErrorMessage';
+import Loading from '../components/common/Loading';
+
+export const Component = () => {
+ const params = useParams();
+ const { hash } = params;
+
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [patch, setPatch] = useState(null);
+ const [seed, setSeed] = useState(null);
+
+ const loadSeed = useCallback((hash, ctrl) => {
+ axios
+ .get(`/api/alttp-seed/${hash}`, { signal: ctrl.signal })
+ .then(response => {
+ setError(null);
+ setLoading(false);
+ setSeed(response.data);
+ })
+ .catch(error => {
+ setError(error);
+ setLoading(false);
+ setSeed(null);
+ });
+ }, []);
+
+ useEffect(() => {
+ setLoading(true);
+ const ctrl = new AbortController();
+ loadSeed(hash, ctrl);
+ return () => {
+ ctrl.abort();
+ };
+ }, [hash]);
+
+ useEffect(() => {
+ if (!seed || seed.status !== 'pending') {
+ return;
+ }
+ const ctrl = new AbortController();
+ const timer = setTimeout(() => {
+ loadSeed(seed.hash, ctrl);
+ }, 2000);
+ return () => {
+ clearTimeout(timer);
+ ctrl.abort();
+ };
+ }, [seed]);
+
+ useEffect(() => {
+ setPatch(null);
+ if (!seed || seed.status !== 'generated') {
+ return;
+ }
+ const ctrl = new AbortController();
+ axios
+ .get(`/alttp-seeds/${hash}.bps`, {
+ responseType: 'arraybuffer',
+ signal: ctrl.signal,
+ })
+ .then(response => {
+ setPatch(response.data);
+ })
+ .catch(error => {
+ setError(error);
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [hash, seed]);
+
+ const retry = useCallback(async () => {
+ await axios.post(`/api/alttp-seed/${hash}/retry`);
+ setSeed(seed => ({ ...seed, status: 'pending' }));
+ });
+
+ if (loading) {
+ return <Loading />;
+ }
+
+ if (error) {
+ return <ErrorMessage error={error} />;
+ }
+
+ if (!seed) {
+ return <NotFound />;
+ }
+
+ return <ErrorBoundary>
+ <Helmet>
+ {seed ?
+ <title>{seed.hash}</title>
+ : null}
+ </Helmet>
+ <Seed onRetry={retry} patch={patch} seed={seed} />
+ </ErrorBoundary>;
+};
+++ /dev/null
-import React from 'react';
-import { Button, Container } from 'react-bootstrap';
-import { Helmet } from 'react-helmet';
-import { useTranslation } from 'react-i18next';
-
-import Icon from '../components/common/Icon';
-import Controls from '../components/discord-bot/Controls';
-
-const authEndpoint = 'https://discord.com/oauth2/authorize';
-const clientId = process.env.MIX_DISCORD_CLIENT_ID;
-
-export const Component = () => {
- const { t } = useTranslation();
-
- return <Container>
- <h1>{t('discordBot.heading')}</h1>
- <Helmet>
- <title>{t('discordBot.heading')}</title>
- </Helmet>
- <p>
- <span className="button-bar">
- <Button
- href={`${authEndpoint}?client_id=${clientId}&scope=bot%20applications.commands`}
- target="_blank"
- variant="discord"
- >
- <Icon.DISCORD title="" />
- {' '}
- {t('discordBot.invite')}
- </Button>
- </span>
- </p>
- <h2>{t('discordBot.controls')}</h2>
- <Controls />
- </Container>;
-};
--- /dev/null
+import React from 'react';
+import { Button, Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
+
+import Icon from '../components/common/Icon';
+import Controls from '../components/discord-bot/Controls';
+
+const authEndpoint = 'https://discord.com/oauth2/authorize';
+const clientId = import.meta.env.VITE_DISCORD_CLIENT_ID;
+
+export const Component = () => {
+ const { t } = useTranslation();
+
+ return <Container>
+ <h1>{t('discordBot.heading')}</h1>
+ <Helmet>
+ <title>{t('discordBot.heading')}</title>
+ </Helmet>
+ <p>
+ <span className="button-bar">
+ <Button
+ href={`${authEndpoint}?client_id=${clientId}&scope=bot%20applications.commands`}
+ target="_blank"
+ variant="discord"
+ >
+ <Icon.DISCORD title="" />
+ {' '}
+ {t('discordBot.invite')}
+ </Button>
+ </span>
+ </p>
+ <h2>{t('discordBot.controls')}</h2>
+ <Controls />
+ </Container>;
+};
+++ /dev/null
-import React from 'react';
-import { Helmet } from 'react-helmet';
-
-import ZeldaIcon from '../components/common/ZeldaIcon';
-
-const DUNGEONS = [
- 'hc',
- 'ct',
- 'ep',
- 'dp',
- 'th',
- 'pd',
- 'sp',
- 'sw',
- 'tt',
- 'ip',
- 'mm',
- 'tr',
- 'gt',
-];
-
-const ITEMS = [
- 'compass',
- 'map',
- 'big-key',
- 'bow',
- 'hookshot',
- 'fire-rod',
- 'lamp',
- 'hammer',
- 'somaria',
- 'fighter-sword',
- 'boots',
- 'glove',
- 'flippers',
-];
-
-const ITEM_CLASSES = {
- 'compass': 'dungeon-item',
- 'map': 'dungeon-item',
- 'big-key': 'dungeon-item',
- 'bow': 'item',
- 'hookshot': 'item',
- 'fire-rod': 'item',
- 'lamp': 'item',
- 'hammer': 'item',
- 'somaria': 'item',
- 'fighter-sword': 'item',
- 'boots': 'item',
- 'glove': 'item',
- 'flippers': 'item',
-};
-
-const nextCSwitch = cur => {
- switch (cur) {
- case 'blue':
- return 'red';
- case 'red':
- return '';
- default:
- return 'blue';
- }
-};
-
-const prevCSwitch = cur => nextCSwitch(nextCSwitch(cur));
-
-export const Component = () => {
- const [state, setState] = React.useState(DUNGEONS.reduce((state, dungeon) => ({
- ...state,
- [dungeon]: ITEMS.reduce((items, item) => ({
- ...items,
- [item]: false,
- }), {
- boss: true,
- cswitch: '',
- keys: 1,
- }),
- }), {}));
-
- const handleItemClick = React.useCallback((dungeon, item) => e => {
- setState(state => ({
- ...state,
- [dungeon]: {
- ...state[dungeon],
- [item]: !state[dungeon][item],
- },
- }));
- e.preventDefault();
- e.stopPropagation();
- });
-
- const handleCSwitchClick = React.useCallback(dungeon => e => {
- setState(state => ({
- ...state,
- [dungeon]: {
- ...state[dungeon],
- cswitch: nextCSwitch(state[dungeon].cswitch),
- },
- }));
- e.preventDefault();
- e.stopPropagation();
- });
-
- const handleCSwitchRightClick = React.useCallback(dungeon => e => {
- setState(state => ({
- ...state,
- [dungeon]: {
- ...state[dungeon],
- cswitch: prevCSwitch(state[dungeon].cswitch),
- },
- }));
- e.preventDefault();
- e.stopPropagation();
- });
-
- const handleKeysClick = React.useCallback(dungeon => e => {
- setState(state => ({
- ...state,
- [dungeon]: {
- ...state[dungeon],
- keys: state[dungeon].keys + 1,
- },
- }));
- e.preventDefault();
- e.stopPropagation();
- });
-
- const handleKeysRightClick = React.useCallback(dungeon => e => {
- setState(state => ({
- ...state,
- [dungeon]: {
- ...state[dungeon],
- keys: Math.max(state[dungeon].keys - 1, 0),
- },
- }));
- e.preventDefault();
- e.stopPropagation();
- });
-
- return <>
- <Helmet>
- <title>Doors Tracker</title>
- <meta name="description" content="Doors Tracker" />
- </Helmet>
- <div className="doors-tracker d-flex flex-column">
- {DUNGEONS.map(dungeon =>
- <div className="d-flex flex-row" key={dungeon}>
- <div
- className={`cell ${state[dungeon].boss ? 'on' : 'off'} dungeon`}
- onClick={handleItemClick(dungeon, 'boss')}
- >
- <ZeldaIcon name={`dungeon-${dungeon}`} />
- </div>
- <div
- className={`cell ${state[dungeon].keys ? 'on' : 'off'} keys`}
- onClick={handleKeysClick(dungeon)}
- onContextMenu={handleKeysRightClick(dungeon)}
- >
- {state[dungeon].keys}
- </div>
- <div
- className={`cell ${state[dungeon].cswitch ? 'on' : 'off'} cswitch`}
- onClick={handleCSwitchClick(dungeon)}
- onContextMenu={handleCSwitchRightClick(dungeon)}
- >
- <ZeldaIcon name={state[dungeon].cswitch
- ? `crystal-switch-${state[dungeon].cswitch}`
- : 'crystal-switch'
- } />
- </div>
- {ITEMS.map(item =>
- <div
- className={
- `cell ${state[dungeon][item] ? 'on' : 'off'} ${ITEM_CLASSES[item]}`
- }
- key={item}
- onClick={handleItemClick(dungeon, item)}
- >
- <ZeldaIcon name={item} />
- </div>
- )}
- </div>
- )}
- </div>
- </>;
-};
--- /dev/null
+import React from 'react';
+import { Helmet } from 'react-helmet';
+
+import ZeldaIcon from '../components/common/ZeldaIcon';
+
+const DUNGEONS = [
+ 'hc',
+ 'ct',
+ 'ep',
+ 'dp',
+ 'th',
+ 'pd',
+ 'sp',
+ 'sw',
+ 'tt',
+ 'ip',
+ 'mm',
+ 'tr',
+ 'gt',
+];
+
+const ITEMS = [
+ 'compass',
+ 'map',
+ 'big-key',
+ 'bow',
+ 'hookshot',
+ 'fire-rod',
+ 'lamp',
+ 'hammer',
+ 'somaria',
+ 'fighter-sword',
+ 'boots',
+ 'glove',
+ 'flippers',
+];
+
+const ITEM_CLASSES = {
+ 'compass': 'dungeon-item',
+ 'map': 'dungeon-item',
+ 'big-key': 'dungeon-item',
+ 'bow': 'item',
+ 'hookshot': 'item',
+ 'fire-rod': 'item',
+ 'lamp': 'item',
+ 'hammer': 'item',
+ 'somaria': 'item',
+ 'fighter-sword': 'item',
+ 'boots': 'item',
+ 'glove': 'item',
+ 'flippers': 'item',
+};
+
+const nextCSwitch = cur => {
+ switch (cur) {
+ case 'blue':
+ return 'red';
+ case 'red':
+ return '';
+ default:
+ return 'blue';
+ }
+};
+
+const prevCSwitch = cur => nextCSwitch(nextCSwitch(cur));
+
+export const Component = () => {
+ const [state, setState] = React.useState(DUNGEONS.reduce((state, dungeon) => ({
+ ...state,
+ [dungeon]: ITEMS.reduce((items, item) => ({
+ ...items,
+ [item]: false,
+ }), {
+ boss: true,
+ cswitch: '',
+ keys: 1,
+ }),
+ }), {}));
+
+ const handleItemClick = React.useCallback((dungeon, item) => e => {
+ setState(state => ({
+ ...state,
+ [dungeon]: {
+ ...state[dungeon],
+ [item]: !state[dungeon][item],
+ },
+ }));
+ e.preventDefault();
+ e.stopPropagation();
+ });
+
+ const handleCSwitchClick = React.useCallback(dungeon => e => {
+ setState(state => ({
+ ...state,
+ [dungeon]: {
+ ...state[dungeon],
+ cswitch: nextCSwitch(state[dungeon].cswitch),
+ },
+ }));
+ e.preventDefault();
+ e.stopPropagation();
+ });
+
+ const handleCSwitchRightClick = React.useCallback(dungeon => e => {
+ setState(state => ({
+ ...state,
+ [dungeon]: {
+ ...state[dungeon],
+ cswitch: prevCSwitch(state[dungeon].cswitch),
+ },
+ }));
+ e.preventDefault();
+ e.stopPropagation();
+ });
+
+ const handleKeysClick = React.useCallback(dungeon => e => {
+ setState(state => ({
+ ...state,
+ [dungeon]: {
+ ...state[dungeon],
+ keys: state[dungeon].keys + 1,
+ },
+ }));
+ e.preventDefault();
+ e.stopPropagation();
+ });
+
+ const handleKeysRightClick = React.useCallback(dungeon => e => {
+ setState(state => ({
+ ...state,
+ [dungeon]: {
+ ...state[dungeon],
+ keys: Math.max(state[dungeon].keys - 1, 0),
+ },
+ }));
+ e.preventDefault();
+ e.stopPropagation();
+ });
+
+ return <>
+ <Helmet>
+ <title>Doors Tracker</title>
+ <meta name="description" content="Doors Tracker" />
+ </Helmet>
+ <div className="doors-tracker d-flex flex-column">
+ {DUNGEONS.map(dungeon =>
+ <div className="d-flex flex-row" key={dungeon}>
+ <div
+ className={`cell ${state[dungeon].boss ? 'on' : 'off'} dungeon`}
+ onClick={handleItemClick(dungeon, 'boss')}
+ >
+ <ZeldaIcon name={`dungeon-${dungeon}`} />
+ </div>
+ <div
+ className={`cell ${state[dungeon].keys ? 'on' : 'off'} keys`}
+ onClick={handleKeysClick(dungeon)}
+ onContextMenu={handleKeysRightClick(dungeon)}
+ >
+ {state[dungeon].keys}
+ </div>
+ <div
+ className={`cell ${state[dungeon].cswitch ? 'on' : 'off'} cswitch`}
+ onClick={handleCSwitchClick(dungeon)}
+ onContextMenu={handleCSwitchRightClick(dungeon)}
+ >
+ <ZeldaIcon name={state[dungeon].cswitch
+ ? `crystal-switch-${state[dungeon].cswitch}`
+ : 'crystal-switch'
+ } />
+ </div>
+ {ITEMS.map(item =>
+ <div
+ className={
+ `cell ${state[dungeon][item] ? 'on' : 'off'} ${ITEM_CLASSES[item]}`
+ }
+ key={item}
+ onClick={handleItemClick(dungeon, item)}
+ >
+ <ZeldaIcon name={item} />
+ </div>
+ )}
+ </div>
+ )}
+ </div>
+ </>;
+};
+++ /dev/null
-import axios from 'axios';
-import moment from 'moment';
-import React from 'react';
-import { Alert, Button, Container } from 'react-bootstrap';
-import { Helmet } from 'react-helmet';
-import { useTranslation } from 'react-i18next';
-import { useParams } from 'react-router-dom';
-import toastr from 'toastr';
-
-import NotFound from './NotFound';
-import CanonicalLinks from '../components/common/CanonicalLinks';
-import ErrorBoundary from '../components/common/ErrorBoundary';
-import ErrorMessage from '../components/common/ErrorMessage';
-import Icon from '../components/common/Icon';
-import Loading from '../components/common/Loading';
-import EpisodeList from '../components/episodes/List';
-import Detail from '../components/events/Detail';
-import Dialog from '../components/techniques/Dialog';
-import { hasConcluded } from '../helpers/Event';
-import {
- mayEditContent,
-} from '../helpers/permissions';
-import { getTranslation } from '../helpers/Technique';
-import { useUser } from '../hooks/user';
-import i18n from '../i18n';
-
-export const Component = () => {
- const params = useParams();
- const { name } = params;
- const { user } = useUser();
- const { t } = useTranslation();
-
- const [error, setError] = React.useState(null);
- const [loading, setLoading] = React.useState(true);
- const [event, setEvent] = React.useState(null);
-
- const [editContent, setEditContent] = React.useState(null);
- const [episodes, setEpisodes] = React.useState([]);
- const [pastMode, setPastMode] = React.useState(false);
- const [showContentDialog, setShowContentDialog] = React.useState(false);
-
- const actions = React.useMemo(() => ({
- editContent: mayEditContent(user) ? content => {
- setEditContent(content);
- setShowContentDialog(true);
- } : null,
- }), [user]);
-
- const fetchEpisodes = React.useCallback((controller, event) => {
- if (!event) {
- setEpisodes([]);
- return;
- }
- const params = {
- event: [event.id],
- };
- if (hasConcluded(event)) {
- params.limit = 25;
- params.reverse = '1';
- } else if (pastMode) {
- params.before = moment().add(3, 'hours').toISOString();
- params.limit = 25;
- params.reverse = '1';
- } else {
- params.after = moment().subtract(3, 'hours').toISOString();
- params.before = moment().add(14, 'days').toISOString();
- }
- axios.get(`/api/episodes`, {
- signal: controller.signal,
- params,
- }).then(response => {
- setEpisodes(response.data || []);
- }).catch(e => {
- if (!axios.isCancel(e)) {
- console.error(e);
- }
- });
- }, [pastMode]);
-
- const saveContent = React.useCallback(async values => {
- try {
- const response = await axios.put(`/api/content/${values.id}`, {
- parent_id: event.description_id,
- ...values,
- });
- toastr.success(t('content.saveSuccess'));
- setEvent(event => ({
- ...event,
- description: response.data,
- }));
- setShowContentDialog(false);
- } catch (e) {
- toastr.error(t('content.saveError'));
- }
- }, [event && event.description_id]);
-
- React.useEffect(() => {
- const ctrl = new AbortController();
- setLoading(true);
- axios
- .get(`/api/events/${name}`, { signal: ctrl.signal })
- .then(response => {
- setError(null);
- setLoading(false);
- setEvent(response.data);
- })
- .catch(error => {
- setError(error);
- setLoading(false);
- setEvent(null);
- });
- return () => {
- ctrl.abort();
- };
- }, [name]);
-
- React.useEffect(() => {
- const controller = new AbortController();
- fetchEpisodes(controller, event);
- const timer = setInterval(() => {
- fetchEpisodes(controller, event);
- }, 1.5 * 60 * 1000);
- return () => {
- controller.abort();
- clearInterval(timer);
- };
- }, [event, fetchEpisodes]);
-
- if (loading) {
- return <Loading />;
- }
-
- if (error) {
- return <ErrorMessage error={error} />;
- }
-
- if (!event) {
- return <NotFound />;
- }
-
- return <ErrorBoundary>
- <Helmet>
- <title>
- {(event.description && getTranslation(event.description, 'title', i18n.language))
- || event.title}
- </title>
- </Helmet>
- {event.description ? <Helmet>
- <meta
- name="description"
- content={getTranslation(event.description, 'short', i18n.language)}
- />
- </Helmet> : null}
- <CanonicalLinks base={`/events/${event.name}`} />
- <Container>
- <Detail actions={actions} event={event} />
- <div className="d-flex align-items-center justify-content-between">
- <h2 className="mt-4">
- {t(pastMode || hasConcluded(event)
- ? 'events.pastEpisodes'
- : 'events.upcomingEpisodes'
- )}
- </h2>
- <div className="button-bar">
- {!hasConcluded(event) ?
- <Button
- className="ms-3"
- onClick={() => setPastMode(!pastMode)}
- title={t(pastMode ? 'events.setFutureMode' : 'events.setPastMode')}
- variant="outline-secondary"
- >
- <Icon.TIME_REVERSE title="" />
- </Button>
- : null}
- </div>
- </div>
- {episodes.length ?
- <EpisodeList episodes={episodes} />
- :
- <Alert variant="info">
- {t(pastMode ? 'events.noPastEpisodes' : 'events.noUpcomingEpisodes')}
- </Alert>
- }
- </Container>
- <Dialog
- content={editContent}
- language={i18n.language}
- onHide={() => { setShowContentDialog(false); }}
- onSubmit={saveContent}
- show={showContentDialog}
- />
- </ErrorBoundary>;
-};
--- /dev/null
+import axios from 'axios';
+import moment from 'moment';
+import React from 'react';
+import { Alert, Button, Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
+import { useParams } from 'react-router-dom';
+import toastr from 'toastr';
+
+import NotFound from './NotFound';
+import CanonicalLinks from '../components/common/CanonicalLinks';
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import ErrorMessage from '../components/common/ErrorMessage';
+import Icon from '../components/common/Icon';
+import Loading from '../components/common/Loading';
+import EpisodeList from '../components/episodes/List';
+import Detail from '../components/events/Detail';
+import Dialog from '../components/techniques/Dialog';
+import { hasConcluded } from '../helpers/Event';
+import {
+ mayEditContent,
+} from '../helpers/permissions';
+import { getTranslation } from '../helpers/Technique';
+import { useUser } from '../hooks/user';
+import i18n from '../i18n';
+
+export const Component = () => {
+ const params = useParams();
+ const { name } = params;
+ const { user } = useUser();
+ const { t } = useTranslation();
+
+ const [error, setError] = React.useState(null);
+ const [loading, setLoading] = React.useState(true);
+ const [event, setEvent] = React.useState(null);
+
+ const [editContent, setEditContent] = React.useState(null);
+ const [episodes, setEpisodes] = React.useState([]);
+ const [pastMode, setPastMode] = React.useState(false);
+ const [showContentDialog, setShowContentDialog] = React.useState(false);
+
+ const actions = React.useMemo(() => ({
+ editContent: mayEditContent(user) ? content => {
+ setEditContent(content);
+ setShowContentDialog(true);
+ } : null,
+ }), [user]);
+
+ const fetchEpisodes = React.useCallback((controller, event) => {
+ if (!event) {
+ setEpisodes([]);
+ return;
+ }
+ const params = {
+ event: [event.id],
+ };
+ if (hasConcluded(event)) {
+ params.limit = 25;
+ params.reverse = '1';
+ } else if (pastMode) {
+ params.before = moment().add(3, 'hours').toISOString();
+ params.limit = 25;
+ params.reverse = '1';
+ } else {
+ params.after = moment().subtract(3, 'hours').toISOString();
+ params.before = moment().add(14, 'days').toISOString();
+ }
+ axios.get(`/api/episodes`, {
+ signal: controller.signal,
+ params,
+ }).then(response => {
+ setEpisodes(response.data || []);
+ }).catch(e => {
+ if (!axios.isCancel(e)) {
+ console.error(e);
+ }
+ });
+ }, [pastMode]);
+
+ const saveContent = React.useCallback(async values => {
+ try {
+ const response = await axios.put(`/api/content/${values.id}`, {
+ parent_id: event.description_id,
+ ...values,
+ });
+ toastr.success(t('content.saveSuccess'));
+ setEvent(event => ({
+ ...event,
+ description: response.data,
+ }));
+ setShowContentDialog(false);
+ } catch (e) {
+ toastr.error(t('content.saveError'));
+ }
+ }, [event && event.description_id]);
+
+ React.useEffect(() => {
+ const ctrl = new AbortController();
+ setLoading(true);
+ axios
+ .get(`/api/events/${name}`, { signal: ctrl.signal })
+ .then(response => {
+ setError(null);
+ setLoading(false);
+ setEvent(response.data);
+ })
+ .catch(error => {
+ setError(error);
+ setLoading(false);
+ setEvent(null);
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [name]);
+
+ React.useEffect(() => {
+ const controller = new AbortController();
+ fetchEpisodes(controller, event);
+ const timer = setInterval(() => {
+ fetchEpisodes(controller, event);
+ }, 1.5 * 60 * 1000);
+ return () => {
+ controller.abort();
+ clearInterval(timer);
+ };
+ }, [event, fetchEpisodes]);
+
+ if (loading) {
+ return <Loading />;
+ }
+
+ if (error) {
+ return <ErrorMessage error={error} />;
+ }
+
+ if (!event) {
+ return <NotFound />;
+ }
+
+ return <ErrorBoundary>
+ <Helmet>
+ <title>
+ {(event.description && getTranslation(event.description, 'title', i18n.language))
+ || event.title}
+ </title>
+ </Helmet>
+ {event.description ? <Helmet>
+ <meta
+ name="description"
+ content={getTranslation(event.description, 'short', i18n.language)}
+ />
+ </Helmet> : null}
+ <CanonicalLinks base={`/events/${event.name}`} />
+ <Container>
+ <Detail actions={actions} event={event} />
+ <div className="d-flex align-items-center justify-content-between">
+ <h2 className="mt-4">
+ {t(pastMode || hasConcluded(event)
+ ? 'events.pastEpisodes'
+ : 'events.upcomingEpisodes'
+ )}
+ </h2>
+ <div className="button-bar">
+ {!hasConcluded(event) ?
+ <Button
+ className="ms-3"
+ onClick={() => setPastMode(!pastMode)}
+ title={t(pastMode ? 'events.setFutureMode' : 'events.setPastMode')}
+ variant="outline-secondary"
+ >
+ <Icon.TIME_REVERSE title="" />
+ </Button>
+ : null}
+ </div>
+ </div>
+ {episodes.length ?
+ <EpisodeList episodes={episodes} />
+ :
+ <Alert variant="info">
+ {t(pastMode ? 'events.noPastEpisodes' : 'events.noUpcomingEpisodes')}
+ </Alert>
+ }
+ </Container>
+ <Dialog
+ content={editContent}
+ language={i18n.language}
+ onHide={() => { setShowContentDialog(false); }}
+ onSubmit={saveContent}
+ show={showContentDialog}
+ />
+ </ErrorBoundary>;
+};
+++ /dev/null
-import axios from 'axios';
-import React from 'react';
-import { Container } from 'react-bootstrap';
-import { Helmet } from 'react-helmet';
-import { useTranslation } from 'react-i18next';
-
-import CanonicalLinks from '../components/common/CanonicalLinks';
-import ErrorBoundary from '../components/common/ErrorBoundary';
-import ErrorMessage from '../components/common/ErrorMessage';
-import Loading from '../components/common/Loading';
-import List from '../components/events/List';
-import { compareStart, hasConcluded, isEvergreen, isOngoing } from '../helpers/Event';
-
-export const Component = () => {
- const { t } = useTranslation();
-
- const [error, setError] = React.useState(null);
- const [loading, setLoading] = React.useState(true);
- const [events, setEvents] = React.useState([]);
-
- const fetchEvents = React.useCallback(async (controller) => {
- const params = {
- order: 'recency',
- with: ['description'],
- };
- try {
- const response = await axios.get(`/api/events`, {
- signal: controller.signal,
- params,
- });
- return response.data || [];
- } catch (error) {
- if (!axios.isCancel(error)) {
- throw error;
- }
- return [];
- }
- }, []);
-
- React.useEffect(() => {
- const controller = new AbortController();
- setLoading(true);
- fetchEvents(controller)
- .then(events => {
- setError(null);
- setLoading(false);
- setEvents(events);
- })
- .catch(error => {
- setError(error);
- setLoading(false);
- setEvents([]);
- });
- return () => {
- controller.abort();
- };
- }, [fetchEvents]);
-
- const evergreen = React.useMemo(() =>
- events.filter(isEvergreen)
- , [events]);
- const ongoing = React.useMemo(() =>
- events.filter(isOngoing).sort((a, b) => compareStart(a, b) * -1)
- , [events]);
- const past = React.useMemo(() =>
- events.filter(hasConcluded)
- , [events]);
-
- if (loading) {
- return <Loading />;
- }
-
- if (error) {
- return <ErrorMessage error={error} />;
- }
-
- return <ErrorBoundary>
- <Helmet>
- <title>
- {t('events.heading')}
- </title>
- </Helmet>
- <CanonicalLinks base={`/events`} />
- <Container>
- <h1>{t('events.ongoing')}</h1>
- <List events={ongoing} />
- <h1>{t('events.evergreen')}</h1>
- <List events={evergreen} />
- <h1>{t('events.past')}</h1>
- <List events={past} />
- </Container>
- </ErrorBoundary>;
-};
--- /dev/null
+import axios from 'axios';
+import React from 'react';
+import { Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
+
+import CanonicalLinks from '../components/common/CanonicalLinks';
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import ErrorMessage from '../components/common/ErrorMessage';
+import Loading from '../components/common/Loading';
+import List from '../components/events/List';
+import { compareStart, hasConcluded, isEvergreen, isOngoing } from '../helpers/Event';
+
+export const Component = () => {
+ const { t } = useTranslation();
+
+ const [error, setError] = React.useState(null);
+ const [loading, setLoading] = React.useState(true);
+ const [events, setEvents] = React.useState([]);
+
+ const fetchEvents = React.useCallback(async (controller) => {
+ const params = {
+ order: 'recency',
+ with: ['description'],
+ };
+ try {
+ const response = await axios.get(`/api/events`, {
+ signal: controller.signal,
+ params,
+ });
+ return response.data || [];
+ } catch (error) {
+ if (!axios.isCancel(error)) {
+ throw error;
+ }
+ return [];
+ }
+ }, []);
+
+ React.useEffect(() => {
+ const controller = new AbortController();
+ setLoading(true);
+ fetchEvents(controller)
+ .then(events => {
+ setError(null);
+ setLoading(false);
+ setEvents(events);
+ })
+ .catch(error => {
+ setError(error);
+ setLoading(false);
+ setEvents([]);
+ });
+ return () => {
+ controller.abort();
+ };
+ }, [fetchEvents]);
+
+ const evergreen = React.useMemo(() =>
+ events.filter(isEvergreen)
+ , [events]);
+ const ongoing = React.useMemo(() =>
+ events.filter(isOngoing).sort((a, b) => compareStart(a, b) * -1)
+ , [events]);
+ const past = React.useMemo(() =>
+ events.filter(hasConcluded)
+ , [events]);
+
+ if (loading) {
+ return <Loading />;
+ }
+
+ if (error) {
+ return <ErrorMessage error={error} />;
+ }
+
+ return <ErrorBoundary>
+ <Helmet>
+ <title>
+ {t('events.heading')}
+ </title>
+ </Helmet>
+ <CanonicalLinks base={`/events`} />
+ <Container>
+ <h1>{t('events.ongoing')}</h1>
+ <List events={ongoing} />
+ <h1>{t('events.evergreen')}</h1>
+ <List events={evergreen} />
+ <h1>{t('events.past')}</h1>
+ <List events={past} />
+ </Container>
+ </ErrorBoundary>;
+};
+++ /dev/null
-import React from 'react';
-import { Button, Col, Container, Image, Row } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-import { Link, useNavigate } from 'react-router-dom';
-
-import CanonicalLinks from '../components/common/CanonicalLinks';
-
-const Front = () => {
- const navigate = useNavigate();
- const { t } = useTranslation();
-
- React.useEffect(() => {
- const returnPath = localStorage.getItem('returnPath');
- if (returnPath) {
- localStorage.removeItem('returnPath');
- navigate(returnPath);
- }
- }, []);
-
- return <Container className="front-page">
- <CanonicalLinks base="/" />
- <h1>{t('front.title')}</h1>
- <h2>{t('front.tournaments')}</h2>
- <Row>
- <Col sm={6}>
- <Link className="front-panel" to="/tournaments/6">
- <Image alt="" className="image" src="/media/alttp/front.png" />
- <div className="title">
- {t('front.sdw')}
- </div>
- </Link>
- </Col>
- <Col sm={6}>
- <Link className="front-panel" to="/tournaments/6">
- <Image alt="" className="image" src="/media/alttp/front.png" />
- <div className="title">
- {t('front.circus')}
- </div>
- </Link>
- </Col>
- </Row>
- <h2>{t('front.events')}</h2>
- <Row>
- <Col sm={6}>
- <Link className="front-panel" to="/events">
- <Image alt="" className="image" src="/media/alttp/front.png" />
- <div className="title">
- {t('front.eventlist')}
- </div>
- </Link>
- </Col>
- <Col sm={6}>
- <Link className="front-panel" to="/schedule">
- <Image alt="" className="image" src="/media/alttp/front.png" />
- <div className="title">
- {t('front.schedule')}
- </div>
- </Link>
- </Col>
- </Row>
- <h2>{t('front.resources')}</h2>
- <Row>
- <Col sm={6}>
- <Link className="front-panel" to="/tech">
- <Image alt="" className="image" src="/media/alttp/front.png" />
- <div className="title">
- {t('front.tech')}
- </div>
- </Link>
- </Col>
- <Col sm={6}>
- <Link className="front-panel" to="/map/lw">
- <Image alt="" className="image" src="/media/alttp/front.png" />
- <div className="title">
- {t('front.map')}
- </div>
- </Link>
- </Col>
- </Row>
- </Container>;
-};
-
-export default Front;
--- /dev/null
+import React from 'react';
+import { Button, Col, Container, Image, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+import { Link, useNavigate } from 'react-router-dom';
+
+import CanonicalLinks from '../components/common/CanonicalLinks';
+
+const Front = () => {
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+
+ React.useEffect(() => {
+ const returnPath = localStorage.getItem('returnPath');
+ if (returnPath) {
+ localStorage.removeItem('returnPath');
+ navigate(returnPath);
+ }
+ }, []);
+
+ return <Container className="front-page">
+ <CanonicalLinks base="/" />
+ <h1>{t('front.title')}</h1>
+ <h2>{t('front.tournaments')}</h2>
+ <Row>
+ <Col sm={6}>
+ <Link className="front-panel" to="/tournaments/6">
+ <Image alt="" className="image" src="/media/alttp/front.png" />
+ <div className="title">
+ {t('front.sdw')}
+ </div>
+ </Link>
+ </Col>
+ <Col sm={6}>
+ <Link className="front-panel" to="/tournaments/6">
+ <Image alt="" className="image" src="/media/alttp/front.png" />
+ <div className="title">
+ {t('front.circus')}
+ </div>
+ </Link>
+ </Col>
+ </Row>
+ <h2>{t('front.events')}</h2>
+ <Row>
+ <Col sm={6}>
+ <Link className="front-panel" to="/events">
+ <Image alt="" className="image" src="/media/alttp/front.png" />
+ <div className="title">
+ {t('front.eventlist')}
+ </div>
+ </Link>
+ </Col>
+ <Col sm={6}>
+ <Link className="front-panel" to="/schedule">
+ <Image alt="" className="image" src="/media/alttp/front.png" />
+ <div className="title">
+ {t('front.schedule')}
+ </div>
+ </Link>
+ </Col>
+ </Row>
+ <h2>{t('front.resources')}</h2>
+ <Row>
+ <Col sm={6}>
+ <Link className="front-panel" to="/tech">
+ <Image alt="" className="image" src="/media/alttp/front.png" />
+ <div className="title">
+ {t('front.tech')}
+ </div>
+ </Link>
+ </Col>
+ <Col sm={6}>
+ <Link className="front-panel" to="/map/lw">
+ <Image alt="" className="image" src="/media/alttp/front.png" />
+ <div className="title">
+ {t('front.map')}
+ </div>
+ </Link>
+ </Col>
+ </Row>
+ </Container>;
+};
+
+export default Front;
+++ /dev/null
-import axios from 'axios';
-import React from 'react';
-import { Alert, Col, Container, Form, Navbar, Row } from 'react-bootstrap';
-import { Helmet } from 'react-helmet';
-import { useTranslation } from 'react-i18next';
-import { useParams } from 'react-router-dom';
-import toastr from 'toastr';
-
-import User from '../app/User';
-import ChannelSelect from '../components/common/ChannelSelect';
-import GuessingGameControls from '../components/twitch-bot/GuessingGameControls';
-import GuessingGuess from '../components/twitch-bot/GuessingGuess';
-import GuessingWinner from '../components/twitch-bot/GuessingWinner';
-import { patchGuess, patchWinner } from '../helpers/Channel';
-
-export const Component = () => {
- const [channel, setChannel] = React.useState(null);
- const [guesses, setGuesses] = React.useState([]);
- const [winners, setWinners] = React.useState([]);
-
- const { channelId } = useParams();
- const { t } = useTranslation();
-
- React.useEffect(() => {
- if (!channelId) return;
- const fetchChannel = async () => {
- const response = await axios.get(`/api/channels`, {
- params: {
- id: [channelId],
- manageable: 1,
- },
- });
- if (response.data.length) {
- setChannel(response.data[0]);
- }
- };
- fetchChannel();
- }, [channelId]);
-
- React.useEffect(() => {
- if (!channel) {
- setGuesses([]);
- setWinners([]);
- return;
- }
- if (channel.guessing_type) {
- axios.get(`/api/channels/${channel.id}/guessing-game/${channel.guessing_type}`)
- .then(res => {
- res.data.guesses.forEach(g => {
- setGuesses(gs => patchGuess(gs, g));
- });
- res.data.winners.forEach(w => {
- setWinners(ws => patchGuess(ws, w));
- });
- });
- }
- window.Echo.private(`Channel.${channel.id}`)
- .listen('.GuessingGuessCreated', (e) => {
- setGuesses(gs => patchGuess(gs, e.model));
- })
- .listen('.GuessingWinnerCreated', (e) => {
- setWinners(ws => patchWinner(ws, e.model));
- })
- .listen('.ChannelUpdated', (e) => {
- setChannel(c => ({ ...c, ...e.model }));
- });
- return () => {
- window.Echo.leave(`Channel.${channel.id}`);
- };
- }, [channel && channel.id]);
-
- React.useEffect(() => {
- const cutoff = channel && channel.guessing_start;
- if (cutoff) {
- setGuesses(gs => gs.filter(g => g.created_at >= cutoff));
- setWinners(ws => ws.filter(w => w.created_at >= cutoff));
- }
- }, [channel && channel.guessing_start]);
-
- const onCancel = React.useCallback(async () => {
- try {
- const rsp = await axios.post(
- `/api/channels/${channel.id}/guessing-game/gtbk`,
- { action: 'cancel' },
- );
- setChannel(rsp.data);
- } catch (e) {
- toastr.error(t('twitchBot.controlError'));
- }
- }, [channel]);
-
- const onSolve = React.useCallback(async (solution) => {
- try {
- const rsp = await axios.post(
- `/api/channels/${channel.id}/guessing-game/gtbk`,
- { action: 'solve', solution },
- );
- setChannel(rsp.data);
- } catch (e) {
- toastr.error(t('twitchBot.controlError'));
- }
- }, [channel]);
-
- const onStart = React.useCallback(async () => {
- try {
- const rsp = await axios.post(
- `/api/channels/${channel.id}/guessing-game/gtbk`,
- { action: 'start' },
- );
- setChannel(rsp.data);
- } catch (e) {
- toastr.error(t('twitchBot.controlError'));
- }
- }, [channel]);
-
- const onStop = React.useCallback(async () => {
- try {
- const rsp = await axios.post(
- `/api/channels/${channel.id}/guessing-game/gtbk`,
- { action: 'stop' },
- );
- setChannel(rsp.data);
- } catch (e) {
- toastr.error(t('twitchBot.controlError'));
- }
- }, [channel]);
-
- return <>
- <Helmet>
- <title>Guessing Game Controls</title>
- </Helmet>
- <Navbar id="header" bg="dark" variant="dark">
- <Container fluid>
- <Form.Control
- as={ChannelSelect}
- autoSelect
- joinable
- manageable
- onChange={({ channel }) => { setChannel(channel); }}
- readOnly={!!(channelId && channel)}
- value={channel ? channel.id : channelId}
- />
- <User />
- </Container>
- </Navbar>
- <Container fluid>
- {channel ? <Row>
- <Col md={12} lg={6}>
- <GuessingGameControls
- channel={channel}
- onCancel={onCancel}
- onSolve={onSolve}
- onStart={onStart}
- onStop={onStop}
- />
- </Col>
- <Col md={6} lg={3}>
- <h3 className="mt-3">{t('twitchBot.guessingGame.winners')}</h3>
- {winners.map(winner =>
- <GuessingWinner key={winner.id} winner={winner} />
- )}
- </Col>
- <Col md={6} lg={3}>
- <h3 className="mt-3">{t('twitchBot.guessingGame.guesses')}</h3>
- {guesses.map(guess =>
- <GuessingGuess guess={guess} key={guess.id} />
- )}
- </Col>
- </Row> :
- <Alert variant="info">
- {t('twitchBot.selectChannel')}
- </Alert>
- }
- </Container>
- </>;
-};
--- /dev/null
+import axios from 'axios';
+import React from 'react';
+import { Alert, Col, Container, Form, Navbar, Row } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
+import { useParams } from 'react-router-dom';
+import toastr from 'toastr';
+
+import User from '../app/User';
+import ChannelSelect from '../components/common/ChannelSelect';
+import GuessingGameControls from '../components/twitch-bot/GuessingGameControls';
+import GuessingGuess from '../components/twitch-bot/GuessingGuess';
+import GuessingWinner from '../components/twitch-bot/GuessingWinner';
+import { patchGuess, patchWinner } from '../helpers/Channel';
+
+export const Component = () => {
+ const [channel, setChannel] = React.useState(null);
+ const [guesses, setGuesses] = React.useState([]);
+ const [winners, setWinners] = React.useState([]);
+
+ const { channelId } = useParams();
+ const { t } = useTranslation();
+
+ React.useEffect(() => {
+ if (!channelId) return;
+ const fetchChannel = async () => {
+ const response = await axios.get(`/api/channels`, {
+ params: {
+ id: [channelId],
+ manageable: 1,
+ },
+ });
+ if (response.data.length) {
+ setChannel(response.data[0]);
+ }
+ };
+ fetchChannel();
+ }, [channelId]);
+
+ React.useEffect(() => {
+ if (!channel) {
+ setGuesses([]);
+ setWinners([]);
+ return;
+ }
+ if (channel.guessing_type) {
+ axios.get(`/api/channels/${channel.id}/guessing-game/${channel.guessing_type}`)
+ .then(res => {
+ res.data.guesses.forEach(g => {
+ setGuesses(gs => patchGuess(gs, g));
+ });
+ res.data.winners.forEach(w => {
+ setWinners(ws => patchGuess(ws, w));
+ });
+ });
+ }
+ window.Echo.private(`Channel.${channel.id}`)
+ .listen('.GuessingGuessCreated', (e) => {
+ setGuesses(gs => patchGuess(gs, e.model));
+ })
+ .listen('.GuessingWinnerCreated', (e) => {
+ setWinners(ws => patchWinner(ws, e.model));
+ })
+ .listen('.ChannelUpdated', (e) => {
+ setChannel(c => ({ ...c, ...e.model }));
+ });
+ return () => {
+ window.Echo.leave(`Channel.${channel.id}`);
+ };
+ }, [channel && channel.id]);
+
+ React.useEffect(() => {
+ const cutoff = channel && channel.guessing_start;
+ if (cutoff) {
+ setGuesses(gs => gs.filter(g => g.created_at >= cutoff));
+ setWinners(ws => ws.filter(w => w.created_at >= cutoff));
+ }
+ }, [channel && channel.guessing_start]);
+
+ const onCancel = React.useCallback(async () => {
+ try {
+ const rsp = await axios.post(
+ `/api/channels/${channel.id}/guessing-game/gtbk`,
+ { action: 'cancel' },
+ );
+ setChannel(rsp.data);
+ } catch (e) {
+ toastr.error(t('twitchBot.controlError'));
+ }
+ }, [channel]);
+
+ const onSolve = React.useCallback(async (solution) => {
+ try {
+ const rsp = await axios.post(
+ `/api/channels/${channel.id}/guessing-game/gtbk`,
+ { action: 'solve', solution },
+ );
+ setChannel(rsp.data);
+ } catch (e) {
+ toastr.error(t('twitchBot.controlError'));
+ }
+ }, [channel]);
+
+ const onStart = React.useCallback(async () => {
+ try {
+ const rsp = await axios.post(
+ `/api/channels/${channel.id}/guessing-game/gtbk`,
+ { action: 'start' },
+ );
+ setChannel(rsp.data);
+ } catch (e) {
+ toastr.error(t('twitchBot.controlError'));
+ }
+ }, [channel]);
+
+ const onStop = React.useCallback(async () => {
+ try {
+ const rsp = await axios.post(
+ `/api/channels/${channel.id}/guessing-game/gtbk`,
+ { action: 'stop' },
+ );
+ setChannel(rsp.data);
+ } catch (e) {
+ toastr.error(t('twitchBot.controlError'));
+ }
+ }, [channel]);
+
+ return <>
+ <Helmet>
+ <title>Guessing Game Controls</title>
+ </Helmet>
+ <Navbar id="header" bg="dark" variant="dark">
+ <Container fluid>
+ <Form.Control
+ as={ChannelSelect}
+ autoSelect
+ joinable
+ manageable
+ onChange={({ channel }) => { setChannel(channel); }}
+ readOnly={!!(channelId && channel)}
+ value={channel ? channel.id : channelId}
+ />
+ <User />
+ </Container>
+ </Navbar>
+ <Container fluid>
+ {channel ? <Row>
+ <Col md={12} lg={6}>
+ <GuessingGameControls
+ channel={channel}
+ onCancel={onCancel}
+ onSolve={onSolve}
+ onStart={onStart}
+ onStop={onStop}
+ />
+ </Col>
+ <Col md={6} lg={3}>
+ <h3 className="mt-3">{t('twitchBot.guessingGame.winners')}</h3>
+ {winners.map(winner =>
+ <GuessingWinner key={winner.id} winner={winner} />
+ )}
+ </Col>
+ <Col md={6} lg={3}>
+ <h3 className="mt-3">{t('twitchBot.guessingGame.guesses')}</h3>
+ {guesses.map(guess =>
+ <GuessingGuess guess={guess} key={guess.id} />
+ )}
+ </Col>
+ </Row> :
+ <Alert variant="info">
+ {t('twitchBot.selectChannel')}
+ </Alert>
+ }
+ </Container>
+ </>;
+};
+++ /dev/null
-import axios from 'axios';
-import moment from 'moment';
-import React from 'react';
-import { Container } from 'react-bootstrap';
-import { Helmet } from 'react-helmet';
-import { useParams } from 'react-router-dom';
-
-import {
- hasActiveGuessing,
- isAcceptingGuesses,
- patchGuess,
- patchWinner,
-} from '../helpers/Channel';
-import ErrorBoundary from '../components/common/ErrorBoundary';
-import Icon from '../components/common/Icon';
-import Slider from '../components/common/Slider';
-
-export const Component = () => {
- const [channel, setChannel] = React.useState({});
- const [guesses, setGuesses] = React.useState([]);
- const [winnerExpiry, setWinnerExpiry] = React.useState(moment().subtract(15, 'second'));
- const [winners, setWinners] = React.useState([]);
-
- const params = useParams();
- const { key } = params;
-
- React.useEffect(() => {
- if (!key) return;
- axios.get(`/api/guessing-game-monitor/${key}`)
- .then(res => {
- setChannel(res.data.channel);
- res.data.guesses.forEach(g => {
- setGuesses(gs => patchGuess(gs, g));
- });
- res.data.winners.forEach(w => {
- setWinners(ws => patchGuess(ws, w));
- });
- });
- window.Echo.channel(`ChannelKey.${key}`)
- .listen('.GuessingGuessCreated', (e) => {
- setGuesses(gs => patchGuess(gs, e.model));
- })
- .listen('.GuessingWinnerCreated', (e) => {
- setWinners(ws => patchWinner(ws, e.model));
- })
- .listen('.ChannelUpdated', (e) => {
- setChannel(c => ({ ...c, ...e.model }));
- });
- return () => {
- window.Echo.leave(`ChannelKey.${key}`);
- };
- }, [key]);
-
- React.useEffect(() => {
- if (isAcceptingGuesses(channel)) {
- setGuesses(gs => gs.filter(g => g.created_at >= channel.guessing_start));
- setWinners([]);
- }
- }, [channel]);
-
- React.useEffect(() => {
- const interval = setInterval(() => {
- setWinnerExpiry(moment().subtract(15, 'second'));
- }, 1000);
- return () => {
- clearInterval(interval);
- };
- }, []);
-
- const guessingStats = React.useMemo(() => {
- const stats = {
- counts: [],
- lastWin: null,
- max: 0,
- wins: [],
- winners: [],
- };
- for (let i = 0; i < 22; ++i) {
- stats.counts.push(0);
- stats.wins.push(false);
- }
- const seen = [];
- guesses.forEach(guess => {
- if (seen[guess.uid]) {
- --stats.counts[parseInt(seen[guess.uid].guess, 10) - 1];
- }
- ++stats.counts[parseInt(guess.guess, 10) - 1];
- seen[guess.uid] = guess;
- });
- winners.forEach(winner => {
- if (winner.score) {
- stats.wins[parseInt(winner.guess, 10) - 1] = true;
- stats.winners.push(winner.uname);
- }
- if (!stats.lastWin || stats.lastWin < winner.created_at) {
- stats.lastWin = winner.created_at;
- }
- });
- for (let i = 0; i < 22; ++i) {
- if (stats.counts[i] > stats.max) {
- stats.max = stats.counts[i];
- }
- }
- return stats;
- }, [guesses, winners]);
-
- const getNumberHeight = React.useCallback((number) => {
- if (!guessingStats || !guessingStats.max) return 3;
- if (!number) return 3;
- return Math.max(0.05, number / guessingStats.max) * 100;
- }, [guessingStats]);
-
- const getStatClass = React.useCallback((index) => {
- const names = ['guessing-stat'];
- if (guessingStats.wins[index]) {
- names.push('has-won');
- }
- return names.join(' ');
- }, [guessingStats]);
-
- const showOpen = React.useMemo(() => {
- return isAcceptingGuesses(channel);
- }, [channel]);
-
- const showClosed = React.useMemo(() => {
- return hasActiveGuessing(channel) && !isAcceptingGuesses(channel);
- }, [channel]);
-
- const showWinners = React.useMemo(() => {
- return !hasActiveGuessing(channel) && (
- guessingStats?.lastWin &&
- moment(guessingStats.lastWin).isAfter(winnerExpiry));
- }, [channel, guessingStats, winnerExpiry]);
-
- return <ErrorBoundary>
- <Helmet>
- <title>Guessing Game</title>
- </Helmet>
- <Container className="guessing-game-monitor" fluid>
- {showOpen || showClosed || showWinners ?
- <div className="message-box">
- {showOpen ?
- <div className="message-title accepting-guesses">
- <Icon.WARNING className="message-icon" />
- <div className="message-text">
- <Slider duration={3500}>
- <Slider.Slide>GT Big Key Guessing Game</Slider.Slide>
- <Slider.Slide>Zahlen von 1 bis 22 in den Chat!</Slider.Slide>
- </Slider>
- </div>
- <Icon.WARNING className="message-icon" />
- </div>
- : null}
- {showClosed ?
- <div className="message-title guessing-closed">
- <div className="message-text">
- Anmeldung geschlossen
- </div>
- </div>
- : null}
- {showWinners ?
- <div className="message-title guessing-winners">
- <div className="message-text">
- {guessingStats.winners.length ?
- <Slider duration={2500}>
- <Slider.Slide>Herzlichen Glückwunsch!</Slider.Slide>
- {guessingStats.winners.map(winner =>
- <Slider.Slide key={winner}>{winner}</Slider.Slide>
- )}
- </Slider>
- :
- 'Leider keiner richtig'
- }
- </div>
- </div>
- : null}
- <div className="guessing-stats">
- {guessingStats.counts.map((number, index) =>
- <div className={getStatClass(index)} key={index}>
- <div className="guessing-box">
- <div
- className="guessing-box-bar"
- style={{ height: `${getNumberHeight(number)}%` }}
- />
- </div>
- <div className="guessing-number">{index + 1}</div>
- </div>
- )}
- </div>
- </div>
- : null}
- </Container>
- </ErrorBoundary>;
-};
--- /dev/null
+import axios from 'axios';
+import moment from 'moment';
+import React from 'react';
+import { Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
+import { useParams } from 'react-router-dom';
+
+import {
+ hasActiveGuessing,
+ isAcceptingGuesses,
+ patchGuess,
+ patchWinner,
+} from '../helpers/Channel';
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import Icon from '../components/common/Icon';
+import Slider from '../components/common/Slider';
+
+export const Component = () => {
+ const [channel, setChannel] = React.useState({});
+ const [guesses, setGuesses] = React.useState([]);
+ const [winnerExpiry, setWinnerExpiry] = React.useState(moment().subtract(15, 'second'));
+ const [winners, setWinners] = React.useState([]);
+
+ const params = useParams();
+ const { key } = params;
+
+ React.useEffect(() => {
+ if (!key) return;
+ axios.get(`/api/guessing-game-monitor/${key}`)
+ .then(res => {
+ setChannel(res.data.channel);
+ res.data.guesses.forEach(g => {
+ setGuesses(gs => patchGuess(gs, g));
+ });
+ res.data.winners.forEach(w => {
+ setWinners(ws => patchGuess(ws, w));
+ });
+ });
+ window.Echo.channel(`ChannelKey.${key}`)
+ .listen('.GuessingGuessCreated', (e) => {
+ setGuesses(gs => patchGuess(gs, e.model));
+ })
+ .listen('.GuessingWinnerCreated', (e) => {
+ setWinners(ws => patchWinner(ws, e.model));
+ })
+ .listen('.ChannelUpdated', (e) => {
+ setChannel(c => ({ ...c, ...e.model }));
+ });
+ return () => {
+ window.Echo.leave(`ChannelKey.${key}`);
+ };
+ }, [key]);
+
+ React.useEffect(() => {
+ if (isAcceptingGuesses(channel)) {
+ setGuesses(gs => gs.filter(g => g.created_at >= channel.guessing_start));
+ setWinners([]);
+ }
+ }, [channel]);
+
+ React.useEffect(() => {
+ const interval = setInterval(() => {
+ setWinnerExpiry(moment().subtract(15, 'second'));
+ }, 1000);
+ return () => {
+ clearInterval(interval);
+ };
+ }, []);
+
+ const guessingStats = React.useMemo(() => {
+ const stats = {
+ counts: [],
+ lastWin: null,
+ max: 0,
+ wins: [],
+ winners: [],
+ };
+ for (let i = 0; i < 22; ++i) {
+ stats.counts.push(0);
+ stats.wins.push(false);
+ }
+ const seen = [];
+ guesses.forEach(guess => {
+ if (seen[guess.uid]) {
+ --stats.counts[parseInt(seen[guess.uid].guess, 10) - 1];
+ }
+ ++stats.counts[parseInt(guess.guess, 10) - 1];
+ seen[guess.uid] = guess;
+ });
+ winners.forEach(winner => {
+ if (winner.score) {
+ stats.wins[parseInt(winner.guess, 10) - 1] = true;
+ stats.winners.push(winner.uname);
+ }
+ if (!stats.lastWin || stats.lastWin < winner.created_at) {
+ stats.lastWin = winner.created_at;
+ }
+ });
+ for (let i = 0; i < 22; ++i) {
+ if (stats.counts[i] > stats.max) {
+ stats.max = stats.counts[i];
+ }
+ }
+ return stats;
+ }, [guesses, winners]);
+
+ const getNumberHeight = React.useCallback((number) => {
+ if (!guessingStats || !guessingStats.max) return 3;
+ if (!number) return 3;
+ return Math.max(0.05, number / guessingStats.max) * 100;
+ }, [guessingStats]);
+
+ const getStatClass = React.useCallback((index) => {
+ const names = ['guessing-stat'];
+ if (guessingStats.wins[index]) {
+ names.push('has-won');
+ }
+ return names.join(' ');
+ }, [guessingStats]);
+
+ const showOpen = React.useMemo(() => {
+ return isAcceptingGuesses(channel);
+ }, [channel]);
+
+ const showClosed = React.useMemo(() => {
+ return hasActiveGuessing(channel) && !isAcceptingGuesses(channel);
+ }, [channel]);
+
+ const showWinners = React.useMemo(() => {
+ return !hasActiveGuessing(channel) && (
+ guessingStats?.lastWin &&
+ moment(guessingStats.lastWin).isAfter(winnerExpiry));
+ }, [channel, guessingStats, winnerExpiry]);
+
+ return <ErrorBoundary>
+ <Helmet>
+ <title>Guessing Game</title>
+ </Helmet>
+ <Container className="guessing-game-monitor" fluid>
+ {showOpen || showClosed || showWinners ?
+ <div className="message-box">
+ {showOpen ?
+ <div className="message-title accepting-guesses">
+ <Icon.WARNING className="message-icon" />
+ <div className="message-text">
+ <Slider duration={3500}>
+ <Slider.Slide>GT Big Key Guessing Game</Slider.Slide>
+ <Slider.Slide>Zahlen von 1 bis 22 in den Chat!</Slider.Slide>
+ </Slider>
+ </div>
+ <Icon.WARNING className="message-icon" />
+ </div>
+ : null}
+ {showClosed ?
+ <div className="message-title guessing-closed">
+ <div className="message-text">
+ Anmeldung geschlossen
+ </div>
+ </div>
+ : null}
+ {showWinners ?
+ <div className="message-title guessing-winners">
+ <div className="message-text">
+ {guessingStats.winners.length ?
+ <Slider duration={2500}>
+ <Slider.Slide>Herzlichen Glückwunsch!</Slider.Slide>
+ {guessingStats.winners.map(winner =>
+ <Slider.Slide key={winner}>{winner}</Slider.Slide>
+ )}
+ </Slider>
+ :
+ 'Leider keiner richtig'
+ }
+ </div>
+ </div>
+ : null}
+ <div className="guessing-stats">
+ {guessingStats.counts.map((number, index) =>
+ <div className={getStatClass(index)} key={index}>
+ <div className="guessing-box">
+ <div
+ className="guessing-box-bar"
+ style={{ height: `${getNumberHeight(number)}%` }}
+ />
+ </div>
+ <div className="guessing-number">{index + 1}</div>
+ </div>
+ )}
+ </div>
+ </div>
+ : null}
+ </Container>
+ </ErrorBoundary>;
+};
+++ /dev/null
-import axios from 'axios';
-import React from 'react';
-import { Col, Container, Row } from 'react-bootstrap';
-import { Helmet } from 'react-helmet';
-import { useTranslation } from 'react-i18next';
-
-import ChannelList from '../components/channel/List';
-import List from '../components/chat-bot-logs/List';
-import ErrorBoundary from '../components/common/ErrorBoundary';
-import ErrorMessage from '../components/common/ErrorMessage';
-import Loading from '../components/common/Loading';
-import { compareHorstieLog } from '../helpers/Channel';
-
-export const Component = () => {
- const [channels, setChannels] = React.useState([]);
- const [error, setError] = React.useState(null);
- const [loading, setLoading] = React.useState(true);
- const [log, setLog] = React.useState([]);
-
- const { t } = useTranslation();
-
- React.useEffect(() => {
- const ctrl = new AbortController();
- if (!log.length) {
- setLoading(true);
- }
- axios
- .get(`/api/chatbotlogs/`, {
- signal: ctrl.signal,
- })
- .then(response => {
- setError(null);
- setLoading(false);
- setLog(response.data);
- })
- .catch(error => {
- if (!axios.isCancel(error)) {
- setError(error);
- setLoading(false);
- setLog([]);
- }
- });
- window.Echo.channel(`ChatBotLog`)
- .listen('.ChatBotLogCreated', (e) => {
- setLog(l => [e.model, ...l]);
- });
- return () => {
- ctrl.abort();
- window.Echo.leave(`ChatBotLog`);
- };
- }, []);
-
- React.useEffect(() => {
- const ctrl = new AbortController();
- axios
- .get(`/api/channels`, {
- signal: ctrl.signal,
- params: {
- chatting: 1,
- },
- })
- .then(response => {
- setChannels(response.data.sort(compareHorstieLog));
- })
- .catch(error => {
- if (!axios.isCancel(error)) {
- setChannels([]);
- }
- });
- window.Echo.channel(`Channel`)
- .listen('.ChannelCreated', (e) => {
- if (e.model.chat) {
- setChannels(cs => [e.model, ...cs].sort(compareHorstieLog));
- }
- })
- .listen('.ChannelUpdated', (e) => {
- if (e.model.chat) {
- setChannels(cs => {
- if (cs.find(c => c.id === e.model.id)) {
- return cs
- .map(c => c.id === e.model.id ? e.model : c)
- .sort(compareHorstieLog);
- } else {
- return [e.model, ...cs].sort(compareHorstieLog);
- }
- });
- } else {
- setChannels(cs => cs.filter(c => c.id !== e.model.id));
- }
- });
- return () => {
- ctrl.abort();
- };
- }, []);
-
- if (loading) {
- return <Loading />;
- }
-
- if (error) {
- return <ErrorMessage error={error} />;
- }
-
- return <Container>
- <h1>Horstie Log</h1>
- <Helmet>
- <title>Horstie Log</title>
- </Helmet>
- <ErrorBoundary>
- <Row>
- <Col md={9}>
- <List log={log} />
- </Col>
- <Col className="horstielog-channels">
- <h2 className="fs-4">{t('twitchBot.chatChannels')}</h2>
- <ChannelList channels={channels} />
- </Col>
- </Row>
- </ErrorBoundary>
- </Container>;
-};
--- /dev/null
+import axios from 'axios';
+import React from 'react';
+import { Col, Container, Row } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
+
+import ChannelList from '../components/channel/List';
+import List from '../components/chat-bot-logs/List';
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import ErrorMessage from '../components/common/ErrorMessage';
+import Loading from '../components/common/Loading';
+import { compareHorstieLog } from '../helpers/Channel';
+
+export const Component = () => {
+ const [channels, setChannels] = React.useState([]);
+ const [error, setError] = React.useState(null);
+ const [loading, setLoading] = React.useState(true);
+ const [log, setLog] = React.useState([]);
+
+ const { t } = useTranslation();
+
+ React.useEffect(() => {
+ const ctrl = new AbortController();
+ if (!log.length) {
+ setLoading(true);
+ }
+ axios
+ .get(`/api/chatbotlogs/`, {
+ signal: ctrl.signal,
+ })
+ .then(response => {
+ setError(null);
+ setLoading(false);
+ setLog(response.data);
+ })
+ .catch(error => {
+ if (!axios.isCancel(error)) {
+ setError(error);
+ setLoading(false);
+ setLog([]);
+ }
+ });
+ window.Echo.channel(`ChatBotLog`)
+ .listen('.ChatBotLogCreated', (e) => {
+ setLog(l => [e.model, ...l]);
+ });
+ return () => {
+ ctrl.abort();
+ window.Echo.leave(`ChatBotLog`);
+ };
+ }, []);
+
+ React.useEffect(() => {
+ const ctrl = new AbortController();
+ axios
+ .get(`/api/channels`, {
+ signal: ctrl.signal,
+ params: {
+ chatting: 1,
+ },
+ })
+ .then(response => {
+ setChannels(response.data.sort(compareHorstieLog));
+ })
+ .catch(error => {
+ if (!axios.isCancel(error)) {
+ setChannels([]);
+ }
+ });
+ window.Echo.channel(`Channel`)
+ .listen('.ChannelCreated', (e) => {
+ if (e.model.chat) {
+ setChannels(cs => [e.model, ...cs].sort(compareHorstieLog));
+ }
+ })
+ .listen('.ChannelUpdated', (e) => {
+ if (e.model.chat) {
+ setChannels(cs => {
+ if (cs.find(c => c.id === e.model.id)) {
+ return cs
+ .map(c => c.id === e.model.id ? e.model : c)
+ .sort(compareHorstieLog);
+ } else {
+ return [e.model, ...cs].sort(compareHorstieLog);
+ }
+ });
+ } else {
+ setChannels(cs => cs.filter(c => c.id !== e.model.id));
+ }
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, []);
+
+ if (loading) {
+ return <Loading />;
+ }
+
+ if (error) {
+ return <ErrorMessage error={error} />;
+ }
+
+ return <Container>
+ <h1>Horstie Log</h1>
+ <Helmet>
+ <title>Horstie Log</title>
+ </Helmet>
+ <ErrorBoundary>
+ <Row>
+ <Col md={9}>
+ <List log={log} />
+ </Col>
+ <Col className="horstielog-channels">
+ <h2 className="fs-4">{t('twitchBot.chatChannels')}</h2>
+ <ChannelList channels={channels} />
+ </Col>
+ </Row>
+ </ErrorBoundary>
+ </Container>;
+};
+++ /dev/null
-import React from 'react';
-import { Container } from 'react-bootstrap';
-import { Helmet } from 'react-helmet';
-import { useTranslation } from 'react-i18next';
-import { useParams } from 'react-router';
-
-import CanonicalLinks from '../components/common/CanonicalLinks';
-import Buttons from '../components/map/Buttons';
-import List from '../components/map/List';
-import OpenSeadragon from '../components/map/OpenSeadragon';
-import Pins from '../components/map/Pins';
-import UWSuperTiles from '../components/map/UWSuperTiles';
-
-export const Component = () => {
- const [uwOverlay, setUWOverlay] = React.useState(false);
-
- const { activeMap } = useParams();
- const container = React.useRef();
- const { t } = useTranslation();
-
- return <Container fluid>
- <Helmet>
- <title>{t('map.heading')} - {t(`map.${activeMap}Long`)}</title>
- <meta name="description" content={t('map.description')} />
- </Helmet>
- <CanonicalLinks base={`/map/${activeMap}`} />
- <OpenSeadragon containerRef={container}>
- <div className="d-flex align-items-start justify-content-between">
- <h1>{t('map.heading')} - {t(`map.${activeMap}Long`)}</h1>
- <Buttons setUWOverlay={setUWOverlay} uwOverlay={uwOverlay} />
- </div>
- <div ref={container} style={{ height: '80vh' }} />
- <Pins />
- <UWSuperTiles show={uwOverlay} />
- <List />
- </OpenSeadragon>
- </Container>;
-};
--- /dev/null
+import React from 'react';
+import { Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
+import { useParams } from 'react-router';
+
+import CanonicalLinks from '../components/common/CanonicalLinks';
+import Buttons from '../components/map/Buttons';
+import List from '../components/map/List';
+import OpenSeadragon from '../components/map/OpenSeadragon';
+import Pins from '../components/map/Pins';
+import UWSuperTiles from '../components/map/UWSuperTiles';
+
+export const Component = () => {
+ const [uwOverlay, setUWOverlay] = React.useState(false);
+
+ const { activeMap } = useParams();
+ const container = React.useRef();
+ const { t } = useTranslation();
+
+ return <Container fluid>
+ <Helmet>
+ <title>{t('map.heading')} - {t(`map.${activeMap}Long`)}</title>
+ <meta name="description" content={t('map.description')} />
+ </Helmet>
+ <CanonicalLinks base={`/map/${activeMap}`} />
+ <OpenSeadragon containerRef={container}>
+ <div className="d-flex align-items-start justify-content-between">
+ <h1>{t('map.heading')} - {t(`map.${activeMap}Long`)}</h1>
+ <Buttons setUWOverlay={setUWOverlay} uwOverlay={uwOverlay} />
+ </div>
+ <div ref={container} style={{ height: '80vh' }} />
+ <Pins />
+ <UWSuperTiles show={uwOverlay} />
+ <List />
+ </OpenSeadragon>
+ </Container>;
+};
+++ /dev/null
-import React from 'react';
-import { Helmet } from 'react-helmet';
-
-const NotFound = () =>
- <div>
- <Helmet>
- <title>Not Found</title>
- </Helmet>
- <h1>Not Found</h1>
- <p>Sorry</p>
- </div>;
-
-export default NotFound;
--- /dev/null
+import React from 'react';
+import { Helmet } from 'react-helmet';
+
+const NotFound = () =>
+ <div>
+ <Helmet>
+ <title>Not Found</title>
+ </Helmet>
+ <h1>Not Found</h1>
+ <p>Sorry</p>
+ </div>;
+
+export default NotFound;
+++ /dev/null
-import axios from 'axios';
-import moment from 'moment';
-import React from 'react';
-import { Alert, Button, Container } from 'react-bootstrap';
-import { Helmet } from 'react-helmet';
-import { useTranslation } from 'react-i18next';
-import toastr from 'toastr';
-
-import CanonicalLinks from '../components/common/CanonicalLinks';
-import ErrorBoundary from '../components/common/ErrorBoundary';
-import Icon from '../components/common/Icon';
-import ApplyDialog from '../components/episodes/ApplyDialog';
-import Filter from '../components/episodes/Filter';
-import List from '../components/episodes/List';
-import RestreamDialog from '../components/episodes/RestreamDialog';
-import { invertEventFilter, toggleEventFilter } from '../helpers/Episode';
-import { useUser } from '../hooks/user';
-
-export const Component = () => {
- const [ahead] = React.useState(14);
- const [applyAs, setApplyAs] = React.useState('commentary');
- const [behind] = React.useState(0);
- const [episodes, setEpisodes] = React.useState([]);
- const [events, setEvents] = React.useState([]);
- const [filter, setFilter] = React.useState({});
- const [restreamChannel, setRestreamChannel] = React.useState(null);
- const [restreamEpisode, setRestreamEpisode] = React.useState(null);
- const [showApplyDialog, setShowApplyDialog] = React.useState(false);
- const [showRestreamDialog, setShowRestreamDialog] = React.useState(false);
- const [showFilter, setShowFilter] = React.useState(false);
-
- const { t } = useTranslation();
- const { user } = useUser();
-
- React.useEffect(() => {
- const savedFilter = localStorage.getItem('episodes.filter.schedule');
- if (savedFilter) {
- setFilter(JSON.parse(savedFilter));
- } else {
- setFilter(filter => filter ? {} : filter);
- }
- }, []);
-
- const fetchEvents = React.useCallback((controller) => {
- axios.get(`/api/events`, {
- signal: controller.signal,
- params: {
- after: moment().startOf('day').subtract(1, 'days').toISOString(),
- before: moment().startOf('day').add(8, 'days').toISOString(),
- },
- }).then(response => {
- const newEvents = (response.data || []).sort(
- (a, b) => (a.short || a.title).localeCompare(b.short || b.title)
- );
- setEvents(newEvents);
- }).catch(e => {
- if (!axios.isCancel(e)) {
- console.error(e);
- }
- });
- });
-
- React.useEffect(() => {
- const controller = new AbortController();
- fetchEvents(controller);
- const timer = setInterval(() => {
- fetchEvents(controller);
- clearInterval(timer);
- }, 15 * 60 * 1000);
- return () => {
- controller.abort();
- };
- }, []);
-
- const updateFilter = React.useCallback(newFilter => {
- localStorage.setItem('episodes.filter.schedule', JSON.stringify(newFilter));
- setFilter(newFilter);
- }, []);
-
- const invertFilter = React.useCallback(() => {
- updateFilter(invertEventFilter(filter));
- }, [filter, updateFilter]);
-
- const fetchEpisodes = React.useCallback((controller, ahead, behind, filter) => {
- axios.get(`/api/episodes`, {
- signal: controller.signal,
- params: {
- after: moment().subtract(2, 'hours').subtract(behind, 'days').toISOString(),
- before: moment().add(16, 'hours').add(ahead, 'days').toISOString(),
- ...filter,
- },
- }).then(response => {
- setEpisodes(response.data || []);
- }).catch(e => {
- if (!axios.isCancel(e)) {
- console.error(e);
- }
- });
- }, []);
-
- const onAddRestream = React.useCallback(episode => {
- setRestreamEpisode(episode);
- setShowRestreamDialog(true);
- }, []);
-
- const onAddRestreamSubmit = React.useCallback(async values => {
- try {
- const response = await axios.post(
- `/api/episodes/${values.episode_id}/add-restream`, values);
- const newEpisode = response.data;
- setEpisodes(episodes => episodes.map(episode =>
- episode.id === newEpisode.id ? {
- ...episode,
- ...newEpisode,
- } : episode
- ));
- toastr.success(t('episodes.restreamDialog.addSuccess'));
- } catch (e) {
- toastr.error(t('episodes.restreamDialog.addError'));
- throw e;
- }
- setRestreamEpisode(null);
- setShowRestreamDialog(false);
- }, []);
-
- const onRemoveRestream = React.useCallback(async (episode, channel) => {
- try {
- const response = await axios.post(
- `/api/episodes/${episode.id}/remove-restream`, { channel_id: channel.id });
- const newEpisode = response.data;
- setEpisodes(episodes => episodes.map(episode =>
- episode.id === newEpisode.id ? {
- ...episode,
- ...newEpisode,
- } : episode
- ));
- toastr.success(t('episodes.restreamDialog.removeSuccess'));
- setRestreamChannel(null);
- setRestreamEpisode(null);
- setShowRestreamDialog(false);
- } catch (e) {
- toastr.error(t('episodes.restreamDialog.removeError'));
- }
- }, []);
-
- const onEditRestream = React.useCallback((episode, channel) => {
- setRestreamChannel(channel);
- setRestreamEpisode(episode);
- setShowRestreamDialog(true);
- }, []);
-
- const editRestream = React.useCallback(async values => {
- try {
- const response = await axios.post(
- `/api/episodes/${values.episode_id}/edit-restream`, values);
- const newEpisode = response.data;
- setEpisodes(episodes => episodes.map(episode =>
- episode.id === newEpisode.id ? {
- ...episode,
- ...newEpisode,
- } : episode
- ));
- setRestreamEpisode(episode => ({
- ...episode,
- ...newEpisode,
- }));
- const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
- setRestreamChannel(channel => ({
- ...channel,
- ...newChannel,
- }));
- toastr.success(t('episodes.restreamDialog.editSuccess'));
- } catch (e) {
- toastr.error(t('episodes.restreamDialog.editError'));
- }
- }, []);
-
- const manageCrew = React.useCallback(async values => {
- try {
- const response = await axios.post(
- `/api/episodes/${values.episode_id}/crew-manage`, values);
- const newEpisode = response.data;
- setEpisodes(episodes => episodes.map(episode =>
- episode.id === newEpisode.id ? {
- ...episode,
- ...newEpisode,
- } : episode
- ));
- setRestreamEpisode(episode => ({
- ...episode,
- ...newEpisode,
- }));
- const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
- setRestreamChannel(channel => ({
- ...channel,
- ...newChannel,
- }));
- toastr.success(t('episodes.restreamDialog.crewSuccess'));
- } catch (e) {
- toastr.error(t('episodes.restreamDialog.crewError'));
- }
- }, []);
-
- const onHideRestreamDialog = React.useCallback(() => {
- setShowRestreamDialog(false);
- setRestreamChannel(null);
- setRestreamEpisode(null);
- }, []);
-
- const onApply = React.useCallback((episode, as) => {
- setShowApplyDialog(true);
- setRestreamEpisode(episode);
- setApplyAs(as);
- }, []);
-
- const onSubmitApplyDialog = React.useCallback(async values => {
- try {
- const response = await axios.post(
- `/api/episodes/${values.episode_id}/crew-signup`, values);
- const newEpisode = response.data;
- setEpisodes(episodes => episodes.map(episode =>
- episode.id === newEpisode.id ? {
- ...episode,
- ...newEpisode,
- } : episode
- ));
- toastr.success(t('episodes.applyDialog.applySuccess'));
- } catch (e) {
- toastr.error(t('episodes.applyDialog.applyError'));
- throw e;
- }
- setRestreamEpisode(null);
- setShowApplyDialog(false);
- }, []);
-
- const onHideApplyDialog = React.useCallback(() => {
- setShowApplyDialog(false);
- setRestreamEpisode(null);
- }, []);
-
- React.useEffect(() => {
- const controller = new AbortController();
- fetchEpisodes(controller, ahead, behind, filter);
- const timer = setInterval(() => {
- fetchEpisodes(controller, ahead, behind, filter);
- }, 1.5 * 60 * 1000);
- return () => {
- controller.abort();
- clearInterval(timer);
- };
- }, [ahead, behind, fetchEpisodes, filter]);
-
- const toggleFilter = React.useCallback(() => {
- setShowFilter(show => !show);
- }, []);
-
- const filterButtonVariant = React.useMemo(() => {
- const outline = showFilter ? '' : 'outline-';
- const filterActive = filter && filter.event && filter.event.length;
- return `${outline}${filterActive ? 'info' : 'secondary'}`;
- }, [filter, showFilter]);
-
- return <Container>
- <Helmet>
- <title>{t('schedule.heading')}</title>
- <meta name="description" content={t('schedule.description')} />
- </Helmet>
- <CanonicalLinks base="/schedule" />
- <div className="d-flex align-items-end justify-content-between">
- <h1 className="mb-0">{t('schedule.heading')}</h1>
- <div className="button-bar">
- {showFilter ?
- <Button
- onClick={invertFilter}
- title={t('button.invert')}
- variant="outline-secondary"
- >
- <Icon.INVERT title="" />
- </Button>
- : null}
- <Button
- onClick={toggleFilter}
- title={t('button.filter')}
- variant={filterButtonVariant}
- >
- <Icon.FILTER title="" />
- </Button>
- </div>
- </div>
- {showFilter ?
- <div className="my-2">
- <Filter events={events} filter={filter} setFilter={updateFilter} />
- </div>
- : null}
- <ErrorBoundary>
- {episodes.length ?
- <List
- episodes={episodes}
- onAddRestream={onAddRestream}
- onApply={onApply}
- onEditRestream={onEditRestream}
- />
- :
- <Alert variant="info">
- {t('episodes.empty')}
- </Alert>
- }
- </ErrorBoundary>
- {user ? <>
- <ApplyDialog
- as={applyAs}
- episode={restreamEpisode}
- onHide={onHideApplyDialog}
- onSubmit={onSubmitApplyDialog}
- show={showApplyDialog}
- />
- <RestreamDialog
- channel={restreamChannel}
- editRestream={editRestream}
- episode={restreamEpisode}
- manageCrew={manageCrew}
- onRemoveRestream={onRemoveRestream}
- onHide={onHideRestreamDialog}
- onSubmit={onAddRestreamSubmit}
- show={showRestreamDialog}
- />
- </> : null}
- </Container>;
-};
--- /dev/null
+import axios from 'axios';
+import moment from 'moment';
+import React from 'react';
+import { Alert, Button, Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import CanonicalLinks from '../components/common/CanonicalLinks';
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import Icon from '../components/common/Icon';
+import ApplyDialog from '../components/episodes/ApplyDialog';
+import Filter from '../components/episodes/Filter';
+import List from '../components/episodes/List';
+import RestreamDialog from '../components/episodes/RestreamDialog';
+import { invertEventFilter, toggleEventFilter } from '../helpers/Episode';
+import { useUser } from '../hooks/user';
+
+export const Component = () => {
+ const [ahead] = React.useState(14);
+ const [applyAs, setApplyAs] = React.useState('commentary');
+ const [behind] = React.useState(0);
+ const [episodes, setEpisodes] = React.useState([]);
+ const [events, setEvents] = React.useState([]);
+ const [filter, setFilter] = React.useState({});
+ const [restreamChannel, setRestreamChannel] = React.useState(null);
+ const [restreamEpisode, setRestreamEpisode] = React.useState(null);
+ const [showApplyDialog, setShowApplyDialog] = React.useState(false);
+ const [showRestreamDialog, setShowRestreamDialog] = React.useState(false);
+ const [showFilter, setShowFilter] = React.useState(false);
+
+ const { t } = useTranslation();
+ const { user } = useUser();
+
+ React.useEffect(() => {
+ const savedFilter = localStorage.getItem('episodes.filter.schedule');
+ if (savedFilter) {
+ setFilter(JSON.parse(savedFilter));
+ } else {
+ setFilter(filter => filter ? {} : filter);
+ }
+ }, []);
+
+ const fetchEvents = React.useCallback((controller) => {
+ axios.get(`/api/events`, {
+ signal: controller.signal,
+ params: {
+ after: moment().startOf('day').subtract(1, 'days').toISOString(),
+ before: moment().startOf('day').add(8, 'days').toISOString(),
+ },
+ }).then(response => {
+ const newEvents = (response.data || []).sort(
+ (a, b) => (a.short || a.title).localeCompare(b.short || b.title)
+ );
+ setEvents(newEvents);
+ }).catch(e => {
+ if (!axios.isCancel(e)) {
+ console.error(e);
+ }
+ });
+ });
+
+ React.useEffect(() => {
+ const controller = new AbortController();
+ fetchEvents(controller);
+ const timer = setInterval(() => {
+ fetchEvents(controller);
+ clearInterval(timer);
+ }, 15 * 60 * 1000);
+ return () => {
+ controller.abort();
+ };
+ }, []);
+
+ const updateFilter = React.useCallback(newFilter => {
+ localStorage.setItem('episodes.filter.schedule', JSON.stringify(newFilter));
+ setFilter(newFilter);
+ }, []);
+
+ const invertFilter = React.useCallback(() => {
+ updateFilter(invertEventFilter(filter));
+ }, [filter, updateFilter]);
+
+ const fetchEpisodes = React.useCallback((controller, ahead, behind, filter) => {
+ axios.get(`/api/episodes`, {
+ signal: controller.signal,
+ params: {
+ after: moment().subtract(2, 'hours').subtract(behind, 'days').toISOString(),
+ before: moment().add(16, 'hours').add(ahead, 'days').toISOString(),
+ ...filter,
+ },
+ }).then(response => {
+ setEpisodes(response.data || []);
+ }).catch(e => {
+ if (!axios.isCancel(e)) {
+ console.error(e);
+ }
+ });
+ }, []);
+
+ const onAddRestream = React.useCallback(episode => {
+ setRestreamEpisode(episode);
+ setShowRestreamDialog(true);
+ }, []);
+
+ const onAddRestreamSubmit = React.useCallback(async values => {
+ try {
+ const response = await axios.post(
+ `/api/episodes/${values.episode_id}/add-restream`, values);
+ const newEpisode = response.data;
+ setEpisodes(episodes => episodes.map(episode =>
+ episode.id === newEpisode.id ? {
+ ...episode,
+ ...newEpisode,
+ } : episode
+ ));
+ toastr.success(t('episodes.restreamDialog.addSuccess'));
+ } catch (e) {
+ toastr.error(t('episodes.restreamDialog.addError'));
+ throw e;
+ }
+ setRestreamEpisode(null);
+ setShowRestreamDialog(false);
+ }, []);
+
+ const onRemoveRestream = React.useCallback(async (episode, channel) => {
+ try {
+ const response = await axios.post(
+ `/api/episodes/${episode.id}/remove-restream`, { channel_id: channel.id });
+ const newEpisode = response.data;
+ setEpisodes(episodes => episodes.map(episode =>
+ episode.id === newEpisode.id ? {
+ ...episode,
+ ...newEpisode,
+ } : episode
+ ));
+ toastr.success(t('episodes.restreamDialog.removeSuccess'));
+ setRestreamChannel(null);
+ setRestreamEpisode(null);
+ setShowRestreamDialog(false);
+ } catch (e) {
+ toastr.error(t('episodes.restreamDialog.removeError'));
+ }
+ }, []);
+
+ const onEditRestream = React.useCallback((episode, channel) => {
+ setRestreamChannel(channel);
+ setRestreamEpisode(episode);
+ setShowRestreamDialog(true);
+ }, []);
+
+ const editRestream = React.useCallback(async values => {
+ try {
+ const response = await axios.post(
+ `/api/episodes/${values.episode_id}/edit-restream`, values);
+ const newEpisode = response.data;
+ setEpisodes(episodes => episodes.map(episode =>
+ episode.id === newEpisode.id ? {
+ ...episode,
+ ...newEpisode,
+ } : episode
+ ));
+ setRestreamEpisode(episode => ({
+ ...episode,
+ ...newEpisode,
+ }));
+ const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
+ setRestreamChannel(channel => ({
+ ...channel,
+ ...newChannel,
+ }));
+ toastr.success(t('episodes.restreamDialog.editSuccess'));
+ } catch (e) {
+ toastr.error(t('episodes.restreamDialog.editError'));
+ }
+ }, []);
+
+ const manageCrew = React.useCallback(async values => {
+ try {
+ const response = await axios.post(
+ `/api/episodes/${values.episode_id}/crew-manage`, values);
+ const newEpisode = response.data;
+ setEpisodes(episodes => episodes.map(episode =>
+ episode.id === newEpisode.id ? {
+ ...episode,
+ ...newEpisode,
+ } : episode
+ ));
+ setRestreamEpisode(episode => ({
+ ...episode,
+ ...newEpisode,
+ }));
+ const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
+ setRestreamChannel(channel => ({
+ ...channel,
+ ...newChannel,
+ }));
+ toastr.success(t('episodes.restreamDialog.crewSuccess'));
+ } catch (e) {
+ toastr.error(t('episodes.restreamDialog.crewError'));
+ }
+ }, []);
+
+ const onHideRestreamDialog = React.useCallback(() => {
+ setShowRestreamDialog(false);
+ setRestreamChannel(null);
+ setRestreamEpisode(null);
+ }, []);
+
+ const onApply = React.useCallback((episode, as) => {
+ setShowApplyDialog(true);
+ setRestreamEpisode(episode);
+ setApplyAs(as);
+ }, []);
+
+ const onSubmitApplyDialog = React.useCallback(async values => {
+ try {
+ const response = await axios.post(
+ `/api/episodes/${values.episode_id}/crew-signup`, values);
+ const newEpisode = response.data;
+ setEpisodes(episodes => episodes.map(episode =>
+ episode.id === newEpisode.id ? {
+ ...episode,
+ ...newEpisode,
+ } : episode
+ ));
+ toastr.success(t('episodes.applyDialog.applySuccess'));
+ } catch (e) {
+ toastr.error(t('episodes.applyDialog.applyError'));
+ throw e;
+ }
+ setRestreamEpisode(null);
+ setShowApplyDialog(false);
+ }, []);
+
+ const onHideApplyDialog = React.useCallback(() => {
+ setShowApplyDialog(false);
+ setRestreamEpisode(null);
+ }, []);
+
+ React.useEffect(() => {
+ const controller = new AbortController();
+ fetchEpisodes(controller, ahead, behind, filter);
+ const timer = setInterval(() => {
+ fetchEpisodes(controller, ahead, behind, filter);
+ }, 1.5 * 60 * 1000);
+ return () => {
+ controller.abort();
+ clearInterval(timer);
+ };
+ }, [ahead, behind, fetchEpisodes, filter]);
+
+ const toggleFilter = React.useCallback(() => {
+ setShowFilter(show => !show);
+ }, []);
+
+ const filterButtonVariant = React.useMemo(() => {
+ const outline = showFilter ? '' : 'outline-';
+ const filterActive = filter && filter.event && filter.event.length;
+ return `${outline}${filterActive ? 'info' : 'secondary'}`;
+ }, [filter, showFilter]);
+
+ return <Container>
+ <Helmet>
+ <title>{t('schedule.heading')}</title>
+ <meta name="description" content={t('schedule.description')} />
+ </Helmet>
+ <CanonicalLinks base="/schedule" />
+ <div className="d-flex align-items-end justify-content-between">
+ <h1 className="mb-0">{t('schedule.heading')}</h1>
+ <div className="button-bar">
+ {showFilter ?
+ <Button
+ onClick={invertFilter}
+ title={t('button.invert')}
+ variant="outline-secondary"
+ >
+ <Icon.INVERT title="" />
+ </Button>
+ : null}
+ <Button
+ onClick={toggleFilter}
+ title={t('button.filter')}
+ variant={filterButtonVariant}
+ >
+ <Icon.FILTER title="" />
+ </Button>
+ </div>
+ </div>
+ {showFilter ?
+ <div className="my-2">
+ <Filter events={events} filter={filter} setFilter={updateFilter} />
+ </div>
+ : null}
+ <ErrorBoundary>
+ {episodes.length ?
+ <List
+ episodes={episodes}
+ onAddRestream={onAddRestream}
+ onApply={onApply}
+ onEditRestream={onEditRestream}
+ />
+ :
+ <Alert variant="info">
+ {t('episodes.empty')}
+ </Alert>
+ }
+ </ErrorBoundary>
+ {user ? <>
+ <ApplyDialog
+ as={applyAs}
+ episode={restreamEpisode}
+ onHide={onHideApplyDialog}
+ onSubmit={onSubmitApplyDialog}
+ show={showApplyDialog}
+ />
+ <RestreamDialog
+ channel={restreamChannel}
+ editRestream={editRestream}
+ episode={restreamEpisode}
+ manageCrew={manageCrew}
+ onRemoveRestream={onRemoveRestream}
+ onHide={onHideRestreamDialog}
+ onSubmit={onAddRestreamSubmit}
+ show={showRestreamDialog}
+ />
+ </> : null}
+ </Container>;
+};
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React, { useEffect, useState } from 'react';
-import { Helmet } from 'react-helmet';
-import { withTranslation } from 'react-i18next';
-import { useParams } from 'react-router-dom';
-import toastr from 'toastr';
-
-import CanonicalLinks from '../components/common/CanonicalLinks';
-import ErrorBoundary from '../components/common/ErrorBoundary';
-import ErrorMessage from '../components/common/ErrorMessage';
-import Loading from '../components/common/Loading';
-import NotFound from '../pages/NotFound';
-import Detail from '../components/techniques/Detail';
-import Dialog from '../components/techniques/Dialog';
-import {
- mayEditContent,
-} from '../helpers/permissions';
-import { getLanguages, getMatchedLocale, getTranslation } from '../helpers/Technique';
-import { useUser } from '../hooks/user';
-import i18n from '../i18n';
-
-const Technique = ({ basepath, type }) => {
- const params = useParams();
- const { name } = params;
- const { user } = useUser();
-
- const [error, setError] = useState(null);
- const [loading, setLoading] = useState(true);
- const [technique, setTechnique] = useState(null);
-
- const [editContent, setEditContent] = useState(null);
- const [showContentDialog, setShowContentDialog] = useState(false);
-
- const actions = React.useMemo(() => ({
- editContent: mayEditContent(user) ? content => {
- setEditContent(content);
- setShowContentDialog(true);
- } : null,
- }), [user]);
-
- const saveContent = React.useCallback(async values => {
- try {
- const response = await axios.put(`/api/content/${values.id}`, {
- parent_id: technique.id,
- ...values,
- });
- toastr.success(i18n.t('content.saveSuccess'));
- setTechnique(response.data);
- setShowContentDialog(false);
- } catch (e) {
- toastr.error(i18n.t('content.saveError'));
- }
- }, [technique && technique.id]);
-
- useEffect(() => {
- const ctrl = new AbortController();
- setLoading(true);
- axios
- .get(`/api/pages/${type}/${name}`, { signal: ctrl.signal })
- .then(response => {
- setError(null);
- setLoading(false);
- setTechnique(response.data);
- })
- .catch(error => {
- setError(error);
- setLoading(false);
- setTechnique(null);
- });
- return () => {
- ctrl.abort();
- };
- }, [name, type]);
-
- if (loading) {
- return <Loading />;
- }
-
- if (error) {
- return <ErrorMessage error={error} />;
- }
-
- if (!technique) {
- return <NotFound />;
- }
-
- return <ErrorBoundary>
- <Helmet>
- <title>{getTranslation(technique, 'title', i18n.language)}</title>
- <meta name="description" content={getTranslation(technique, 'short', i18n.language)} />
- </Helmet>
- {technique.image ? <Helmet>
- <meta property="og:image" content={technique.image} />
- <meta property="twitter:image" content={technique.image} />
- </Helmet> : null}
- {!technique.image && technique.gif ? <Helmet>
- <meta property="og:image" content={technique.gif} />
- <meta property="twitter:image" content={technique.gif} />
- </Helmet> : null}
- <CanonicalLinks
- base={`/${basepath}/${technique.name}`}
- lang={getMatchedLocale(technique, i18n.language)}
- langs={getLanguages(technique)}
- />
- <Detail actions={actions} technique={technique} />
- <Dialog
- content={editContent}
- language={i18n.language}
- onHide={() => { setShowContentDialog(false); }}
- onSubmit={saveContent}
- show={showContentDialog}
- />
- </ErrorBoundary>;
-};
-
-Technique.propTypes = {
- basepath: PropTypes.string,
- type: PropTypes.string,
-};
-
-export default withTranslation()(Technique);
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React, { useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet';
+import { withTranslation } from 'react-i18next';
+import { useParams } from 'react-router-dom';
+import toastr from 'toastr';
+
+import CanonicalLinks from '../components/common/CanonicalLinks';
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import ErrorMessage from '../components/common/ErrorMessage';
+import Loading from '../components/common/Loading';
+import NotFound from '../pages/NotFound';
+import Detail from '../components/techniques/Detail';
+import Dialog from '../components/techniques/Dialog';
+import {
+ mayEditContent,
+} from '../helpers/permissions';
+import { getLanguages, getMatchedLocale, getTranslation } from '../helpers/Technique';
+import { useUser } from '../hooks/user';
+import i18n from '../i18n';
+
+const Technique = ({ basepath, type }) => {
+ const params = useParams();
+ const { name } = params;
+ const { user } = useUser();
+
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [technique, setTechnique] = useState(null);
+
+ const [editContent, setEditContent] = useState(null);
+ const [showContentDialog, setShowContentDialog] = useState(false);
+
+ const actions = React.useMemo(() => ({
+ editContent: mayEditContent(user) ? content => {
+ setEditContent(content);
+ setShowContentDialog(true);
+ } : null,
+ }), [user]);
+
+ const saveContent = React.useCallback(async values => {
+ try {
+ const response = await axios.put(`/api/content/${values.id}`, {
+ parent_id: technique.id,
+ ...values,
+ });
+ toastr.success(i18n.t('content.saveSuccess'));
+ setTechnique(response.data);
+ setShowContentDialog(false);
+ } catch (e) {
+ toastr.error(i18n.t('content.saveError'));
+ }
+ }, [technique && technique.id]);
+
+ useEffect(() => {
+ const ctrl = new AbortController();
+ setLoading(true);
+ axios
+ .get(`/api/pages/${type}/${name}`, { signal: ctrl.signal })
+ .then(response => {
+ setError(null);
+ setLoading(false);
+ setTechnique(response.data);
+ })
+ .catch(error => {
+ setError(error);
+ setLoading(false);
+ setTechnique(null);
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [name, type]);
+
+ if (loading) {
+ return <Loading />;
+ }
+
+ if (error) {
+ return <ErrorMessage error={error} />;
+ }
+
+ if (!technique) {
+ return <NotFound />;
+ }
+
+ return <ErrorBoundary>
+ <Helmet>
+ <title>{getTranslation(technique, 'title', i18n.language)}</title>
+ <meta name="description" content={getTranslation(technique, 'short', i18n.language)} />
+ </Helmet>
+ {technique.image ? <Helmet>
+ <meta property="og:image" content={technique.image} />
+ <meta property="twitter:image" content={technique.image} />
+ </Helmet> : null}
+ {!technique.image && technique.gif ? <Helmet>
+ <meta property="og:image" content={technique.gif} />
+ <meta property="twitter:image" content={technique.gif} />
+ </Helmet> : null}
+ <CanonicalLinks
+ base={`/${basepath}/${technique.name}`}
+ lang={getMatchedLocale(technique, i18n.language)}
+ langs={getLanguages(technique)}
+ />
+ <Detail actions={actions} technique={technique} />
+ <Dialog
+ content={editContent}
+ language={i18n.language}
+ onHide={() => { setShowContentDialog(false); }}
+ onSubmit={saveContent}
+ show={showContentDialog}
+ />
+ </ErrorBoundary>;
+};
+
+Technique.propTypes = {
+ basepath: PropTypes.string,
+ type: PropTypes.string,
+};
+
+export default withTranslation()(Technique);
+++ /dev/null
-import axios from 'axios';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Helmet } from 'react-helmet';
-import { withTranslation } from 'react-i18next';
-
-import NotFound from './NotFound';
-import CanonicalLinks from '../components/common/CanonicalLinks';
-import ErrorBoundary from '../components/common/ErrorBoundary';
-import ErrorMessage from '../components/common/ErrorMessage';
-import Loading from '../components/common/Loading';
-import Overview from '../components/techniques/Overview';
-import { compareTranslation } from '../helpers/Technique';
-import i18n from '../i18n';
-
-const Techniques = ({ namespace, type }) => {
- const [error, setError] = React.useState(null);
- const [filter, setFilter] = React.useState({});
- const [loading, setLoading] = React.useState(true);
- const [techniques, setTechniques] = React.useState([]);
-
- React.useEffect(() => {
- const savedFilter = localStorage.getItem(`content.filter.${type}`);
- if (savedFilter) {
- setFilter(JSON.parse(savedFilter));
- } else {
- setFilter(filter => filter ? {} : filter);
- }
- }, [type]);
-
- const updateFilter = React.useCallback(newFilter => {
- localStorage.setItem(`content.filter.${type}`, JSON.stringify(newFilter));
- setFilter(newFilter);
- }, [type]);
-
- React.useEffect(() => {
- const ctrl = new AbortController();
- if (!techniques.length) {
- setLoading(true);
- }
- axios
- .get(`/api/pages/${type}`, {
- params: filter,
- signal: ctrl.signal
- })
- .then(response => {
- setError(null);
- setLoading(false);
- setTechniques(response.data.sort(compareTranslation('title', i18n.language)));
- })
- .catch(error => {
- if (!axios.isCancel(error)) {
- setError(error);
- setLoading(false);
- setTechniques([]);
- }
- });
- return () => {
- ctrl.abort();
- };
- }, [filter, namespace, type]);
-
- React.useEffect(() => {
- setTechniques(t => [...t].sort(compareTranslation('title', i18n.language)));
- }, [namespace, i18n.language]);
-
- if (loading) {
- return <Loading />;
- }
-
- if (error) {
- return <ErrorMessage error={error} />;
- }
-
- if (!techniques || !techniques.length) {
- return <NotFound />;
- }
-
- return <ErrorBoundary>
- <Helmet>
- <title>{i18n.t(`${namespace}.heading`)}</title>
- <meta name="description" content={i18n.t(`${namespace}.description`)} />
- </Helmet>
- <CanonicalLinks base="/tech" />
- <Overview
- filter={filter}
- namespace={namespace}
- setFilter={updateFilter}
- techniques={techniques}
- type={type}
- />
- </ErrorBoundary>;
-};
-
-Techniques.propTypes = {
- namespace: PropTypes.string,
- type: PropTypes.string,
-};
-
-export default withTranslation()(Techniques);
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Helmet } from 'react-helmet';
+import { withTranslation } from 'react-i18next';
+
+import NotFound from './NotFound';
+import CanonicalLinks from '../components/common/CanonicalLinks';
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import ErrorMessage from '../components/common/ErrorMessage';
+import Loading from '../components/common/Loading';
+import Overview from '../components/techniques/Overview';
+import { compareTranslation } from '../helpers/Technique';
+import i18n from '../i18n';
+
+const Techniques = ({ namespace, type }) => {
+ const [error, setError] = React.useState(null);
+ const [filter, setFilter] = React.useState({});
+ const [loading, setLoading] = React.useState(true);
+ const [techniques, setTechniques] = React.useState([]);
+
+ React.useEffect(() => {
+ const savedFilter = localStorage.getItem(`content.filter.${type}`);
+ if (savedFilter) {
+ setFilter(JSON.parse(savedFilter));
+ } else {
+ setFilter(filter => filter ? {} : filter);
+ }
+ }, [type]);
+
+ const updateFilter = React.useCallback(newFilter => {
+ localStorage.setItem(`content.filter.${type}`, JSON.stringify(newFilter));
+ setFilter(newFilter);
+ }, [type]);
+
+ React.useEffect(() => {
+ const ctrl = new AbortController();
+ if (!techniques.length) {
+ setLoading(true);
+ }
+ axios
+ .get(`/api/pages/${type}`, {
+ params: filter,
+ signal: ctrl.signal
+ })
+ .then(response => {
+ setError(null);
+ setLoading(false);
+ setTechniques(response.data.sort(compareTranslation('title', i18n.language)));
+ })
+ .catch(error => {
+ if (!axios.isCancel(error)) {
+ setError(error);
+ setLoading(false);
+ setTechniques([]);
+ }
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [filter, namespace, type]);
+
+ React.useEffect(() => {
+ setTechniques(t => [...t].sort(compareTranslation('title', i18n.language)));
+ }, [namespace, i18n.language]);
+
+ if (loading) {
+ return <Loading />;
+ }
+
+ if (error) {
+ return <ErrorMessage error={error} />;
+ }
+
+ if (!techniques || !techniques.length) {
+ return <NotFound />;
+ }
+
+ return <ErrorBoundary>
+ <Helmet>
+ <title>{i18n.t(`${namespace}.heading`)}</title>
+ <meta name="description" content={i18n.t(`${namespace}.description`)} />
+ </Helmet>
+ <CanonicalLinks base="/tech" />
+ <Overview
+ filter={filter}
+ namespace={namespace}
+ setFilter={updateFilter}
+ techniques={techniques}
+ type={type}
+ />
+ </ErrorBoundary>;
+};
+
+Techniques.propTypes = {
+ namespace: PropTypes.string,
+ type: PropTypes.string,
+};
+
+export default withTranslation()(Techniques);
+++ /dev/null
-import axios from 'axios';
-import React, { useEffect, useState } from 'react';
-import { Helmet } from 'react-helmet';
-import { useTranslation } from 'react-i18next';
-import { useParams } from 'react-router-dom';
-import toastr from 'toastr';
-
-import NotFound from './NotFound';
-import CanonicalLinks from '../components/common/CanonicalLinks';
-import ErrorBoundary from '../components/common/ErrorBoundary';
-import ErrorMessage from '../components/common/ErrorMessage';
-import Loading from '../components/common/Loading';
-import Dialog from '../components/techniques/Dialog';
-import Detail from '../components/tournament/Detail';
-import {
- mayEditContent,
-} from '../helpers/permissions';
-import { getTranslation } from '../helpers/Technique';
-import {
- canLoadMoreRounds,
- getLastRound,
- patchApplication,
- patchParticipant,
- patchResult,
- patchRound,
- patchUser,
- removeApplication,
- sortParticipants,
-} from '../helpers/Tournament';
-import { useUser } from '../hooks/user';
-import i18n from '../i18n';
-
-export const Component = () => {
- const params = useParams();
- const { id } = params;
- const { user } = useUser();
- const { t } = useTranslation();
-
- const [error, setError] = useState(null);
- const [loading, setLoading] = useState(true);
- const [tournament, setTournament] = useState(null);
-
- const [editContent, setEditContent] = React.useState(null);
- const [showContentDialog, setShowContentDialog] = React.useState(false);
-
- useEffect(() => {
- const ctrl = new AbortController();
- setLoading(true);
- axios
- .get(`/api/tournaments/${id}`, { signal: ctrl.signal })
- .then(response => {
- setError(null);
- setLoading(false);
- setTournament(sortParticipants(response.data));
- })
- .catch(error => {
- setError(error);
- setLoading(false);
- setTournament(null);
- });
- return () => {
- ctrl.abort();
- };
- }, [id]);
-
- useEffect(() => {
- window.Echo.channel(`Tournament.${id}`)
- .listen('ApplicationAdded', e => {
- if (e.application) {
- setTournament(tournament => patchApplication(tournament, e.application));
- }
- })
- .listen('ApplicationChanged', e => {
- if (e.application) {
- setTournament(tournament => patchApplication(tournament, e.application));
- }
- })
- .listen('ApplicationRemoved', e => {
- if (e.application_id) {
- setTournament(tournament => removeApplication(tournament, e.application_id));
- }
- })
- .listen('ParticipantChanged', e => {
- if (e.participant) {
- setTournament(tournament => patchParticipant(tournament, e.participant));
- }
- })
- .listen('ResultChanged', e => {
- if (e.result) {
- setTournament(tournament => patchResult(tournament, e.result));
- }
- })
- .listen('RoundAdded', e => {
- if (e.round) {
- setTournament(tournament => ({
- ...tournament,
- rounds: [e.round, ...tournament.rounds],
- }));
- }
- })
- .listen('RoundChanged', e => {
- if (e.round) {
- setTournament(tournament => patchRound(tournament, e.round));
- }
- })
- .listen('.RoundDeleted', e => {
- if (e.model) {
- setTournament(tournament => ({
- ...tournament,
- rounds: tournament.rounds.filter((r) => r.id !== e.model.id),
- }));
- }
- })
- .listen('TournamentChanged', e => {
- if (e.tournament) {
- setTournament(tournament => ({ ...tournament, ...e.tournament }));
- }
- });
- return () => {
- window.Echo.leave(`Tournament.${id}`);
- };
- }, [id]);
-
- const addRound = React.useCallback(async () => {
- await axios.post('/api/rounds', { tournament_id: id });
- }, [id]);
-
- const moreRounds = React.useCallback(async () => {
- const last_round = getLastRound(tournament);
- if (!last_round) return;
- console.log(last_round);
- const last_known = last_round.number;
- const rsp = await axios.get(
- `/api/tournaments/${id}/more-rounds`,
- { params: { last_known } },
- );
- setTournament(tournament => ({
- ...tournament,
- rounds: [...tournament.rounds, ...rsp.data],
- }));
- }, [id, tournament]);
-
- const saveContent = React.useCallback(async values => {
- try {
- const response = await axios.put(`/api/content/${values.id}`, {
- parent_id: event.description_id,
- ...values,
- });
- toastr.success(t('content.saveSuccess'));
- setTournament(tournament => ({
- ...tournament,
- description: response.data,
- }));
- setShowContentDialog(false);
- } catch (e) {
- toastr.error(t('content.saveError'));
- }
- }, [tournament && tournament.description_id]);
-
- const actions = React.useMemo(() => ({
- addRound,
- editContent: mayEditContent(user) ? content => {
- setEditContent(content);
- setShowContentDialog(true);
- } : null,
- moreRounds: canLoadMoreRounds(tournament) ? moreRounds : null,
- }), [addRound, moreRounds, tournament, user]);
-
- useEffect(() => {
- const cb = (e) => {
- if (e.user) {
- setTournament(tournament => patchUser(tournament, e.user));
- }
- };
- window.Echo.channel('App.Control')
- .listen('UserChanged', cb);
- return () => {
- window.Echo.channel('App.Control')
- .stopListening('UserChanged', cb);
- };
- }, []);
-
- if (loading) {
- return <Loading />;
- }
-
- if (error) {
- return <ErrorMessage error={error} />;
- }
-
- if (!tournament) {
- return <NotFound />;
- }
-
- return <ErrorBoundary>
- <Helmet>
- <title>
- {(tournament.description
- && getTranslation(tournament.description, 'title', i18n.language))
- || tournament.title}
- </title>
- </Helmet>
- {tournament.description ? <Helmet>
- <meta
- name="description"
- content={getTranslation(tournament.description, 'short', i18n.language)}
- />
- </Helmet> : null}
- <CanonicalLinks base={`/tournaments/${tournament.id}`} />
- <Detail
- actions={actions}
- tournament={tournament}
- />
- <Dialog
- content={editContent}
- language={i18n.language}
- onHide={() => { setShowContentDialog(false); }}
- onSubmit={saveContent}
- show={showContentDialog}
- />
- </ErrorBoundary>;
-};
--- /dev/null
+import axios from 'axios';
+import React, { useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
+import { useParams } from 'react-router-dom';
+import toastr from 'toastr';
+
+import NotFound from './NotFound';
+import CanonicalLinks from '../components/common/CanonicalLinks';
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import ErrorMessage from '../components/common/ErrorMessage';
+import Loading from '../components/common/Loading';
+import Dialog from '../components/techniques/Dialog';
+import Detail from '../components/tournament/Detail';
+import {
+ mayEditContent,
+} from '../helpers/permissions';
+import { getTranslation } from '../helpers/Technique';
+import {
+ canLoadMoreRounds,
+ getLastRound,
+ patchApplication,
+ patchParticipant,
+ patchResult,
+ patchRound,
+ patchUser,
+ removeApplication,
+ sortParticipants,
+} from '../helpers/Tournament';
+import { useUser } from '../hooks/user';
+import i18n from '../i18n';
+
+export const Component = () => {
+ const params = useParams();
+ const { id } = params;
+ const { user } = useUser();
+ const { t } = useTranslation();
+
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [tournament, setTournament] = useState(null);
+
+ const [editContent, setEditContent] = React.useState(null);
+ const [showContentDialog, setShowContentDialog] = React.useState(false);
+
+ useEffect(() => {
+ const ctrl = new AbortController();
+ setLoading(true);
+ axios
+ .get(`/api/tournaments/${id}`, { signal: ctrl.signal })
+ .then(response => {
+ setError(null);
+ setLoading(false);
+ setTournament(sortParticipants(response.data));
+ })
+ .catch(error => {
+ setError(error);
+ setLoading(false);
+ setTournament(null);
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [id]);
+
+ useEffect(() => {
+ window.Echo.channel(`Tournament.${id}`)
+ .listen('ApplicationAdded', e => {
+ if (e.application) {
+ setTournament(tournament => patchApplication(tournament, e.application));
+ }
+ })
+ .listen('ApplicationChanged', e => {
+ if (e.application) {
+ setTournament(tournament => patchApplication(tournament, e.application));
+ }
+ })
+ .listen('ApplicationRemoved', e => {
+ if (e.application_id) {
+ setTournament(tournament => removeApplication(tournament, e.application_id));
+ }
+ })
+ .listen('ParticipantChanged', e => {
+ if (e.participant) {
+ setTournament(tournament => patchParticipant(tournament, e.participant));
+ }
+ })
+ .listen('ResultChanged', e => {
+ if (e.result) {
+ setTournament(tournament => patchResult(tournament, e.result));
+ }
+ })
+ .listen('RoundAdded', e => {
+ if (e.round) {
+ setTournament(tournament => ({
+ ...tournament,
+ rounds: [e.round, ...tournament.rounds],
+ }));
+ }
+ })
+ .listen('RoundChanged', e => {
+ if (e.round) {
+ setTournament(tournament => patchRound(tournament, e.round));
+ }
+ })
+ .listen('.RoundDeleted', e => {
+ if (e.model) {
+ setTournament(tournament => ({
+ ...tournament,
+ rounds: tournament.rounds.filter((r) => r.id !== e.model.id),
+ }));
+ }
+ })
+ .listen('TournamentChanged', e => {
+ if (e.tournament) {
+ setTournament(tournament => ({ ...tournament, ...e.tournament }));
+ }
+ });
+ return () => {
+ window.Echo.leave(`Tournament.${id}`);
+ };
+ }, [id]);
+
+ const addRound = React.useCallback(async () => {
+ await axios.post('/api/rounds', { tournament_id: id });
+ }, [id]);
+
+ const moreRounds = React.useCallback(async () => {
+ const last_round = getLastRound(tournament);
+ if (!last_round) return;
+ console.log(last_round);
+ const last_known = last_round.number;
+ const rsp = await axios.get(
+ `/api/tournaments/${id}/more-rounds`,
+ { params: { last_known } },
+ );
+ setTournament(tournament => ({
+ ...tournament,
+ rounds: [...tournament.rounds, ...rsp.data],
+ }));
+ }, [id, tournament]);
+
+ const saveContent = React.useCallback(async values => {
+ try {
+ const response = await axios.put(`/api/content/${values.id}`, {
+ parent_id: event.description_id,
+ ...values,
+ });
+ toastr.success(t('content.saveSuccess'));
+ setTournament(tournament => ({
+ ...tournament,
+ description: response.data,
+ }));
+ setShowContentDialog(false);
+ } catch (e) {
+ toastr.error(t('content.saveError'));
+ }
+ }, [tournament && tournament.description_id]);
+
+ const actions = React.useMemo(() => ({
+ addRound,
+ editContent: mayEditContent(user) ? content => {
+ setEditContent(content);
+ setShowContentDialog(true);
+ } : null,
+ moreRounds: canLoadMoreRounds(tournament) ? moreRounds : null,
+ }), [addRound, moreRounds, tournament, user]);
+
+ useEffect(() => {
+ const cb = (e) => {
+ if (e.user) {
+ setTournament(tournament => patchUser(tournament, e.user));
+ }
+ };
+ window.Echo.channel('App.Control')
+ .listen('UserChanged', cb);
+ return () => {
+ window.Echo.channel('App.Control')
+ .stopListening('UserChanged', cb);
+ };
+ }, []);
+
+ if (loading) {
+ return <Loading />;
+ }
+
+ if (error) {
+ return <ErrorMessage error={error} />;
+ }
+
+ if (!tournament) {
+ return <NotFound />;
+ }
+
+ return <ErrorBoundary>
+ <Helmet>
+ <title>
+ {(tournament.description
+ && getTranslation(tournament.description, 'title', i18n.language))
+ || tournament.title}
+ </title>
+ </Helmet>
+ {tournament.description ? <Helmet>
+ <meta
+ name="description"
+ content={getTranslation(tournament.description, 'short', i18n.language)}
+ />
+ </Helmet> : null}
+ <CanonicalLinks base={`/tournaments/${tournament.id}`} />
+ <Detail
+ actions={actions}
+ tournament={tournament}
+ />
+ <Dialog
+ content={editContent}
+ language={i18n.language}
+ onHide={() => { setShowContentDialog(false); }}
+ onSubmit={saveContent}
+ show={showContentDialog}
+ />
+ </ErrorBoundary>;
+};
+++ /dev/null
-import React from 'react';
-import { Helmet } from 'react-helmet';
-
-import ErrorBoundary from '../components/common/ErrorBoundary';
-import Tracker from '../components/tracker';
-import { TrackerProvider } from '../hooks/tracker';
-
-export const Component = () => {
- return <ErrorBoundary>
- <Helmet>
- <title>Tracker</title>
- </Helmet>
- <TrackerProvider>
- <Tracker />
- </TrackerProvider>
- </ErrorBoundary>;
-};
--- /dev/null
+import React from 'react';
+import { Helmet } from 'react-helmet';
+
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import Tracker from '../components/tracker';
+import { TrackerProvider } from '../hooks/tracker';
+
+export const Component = () => {
+ return <ErrorBoundary>
+ <Helmet>
+ <title>Tracker</title>
+ </Helmet>
+ <TrackerProvider>
+ <Tracker />
+ </TrackerProvider>
+ </ErrorBoundary>;
+};
+++ /dev/null
-import React from 'react';
-import { Alert, Container } from 'react-bootstrap';
-import { Helmet } from 'react-helmet';
-import { useTranslation } from 'react-i18next';
-
-import Controls from '../components/twitch-bot/Controls';
-import { mayManageTwitchBot } from '../helpers/permissions';
-import { useUser } from '../hooks/user';
-
-export const Component = () => {
- const { t } = useTranslation();
- const { user } = useUser();
-
- return <Container>
- <h1>{t('twitchBot.heading')}</h1>
- <Helmet>
- <title>{t('twitchBot.heading')}</title>
- </Helmet>
- {mayManageTwitchBot(user) ? <>
- <h2>{t('twitchBot.controls')}</h2>
- <Controls />
- </> :
- <Alert variant="info">
- {t('twitchBot.noManagePermission')}
- </Alert>
- }
- </Container>;
-};
--- /dev/null
+import React from 'react';
+import { Alert, Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
+
+import Controls from '../components/twitch-bot/Controls';
+import { mayManageTwitchBot } from '../helpers/permissions';
+import { useUser } from '../hooks/user';
+
+export const Component = () => {
+ const { t } = useTranslation();
+ const { user } = useUser();
+
+ return <Container>
+ <h1>{t('twitchBot.heading')}</h1>
+ <Helmet>
+ <title>{t('twitchBot.heading')}</title>
+ </Helmet>
+ {mayManageTwitchBot(user) ? <>
+ <h2>{t('twitchBot.controls')}</h2>
+ <Controls />
+ </> :
+ <Alert variant="info">
+ {t('twitchBot.noManagePermission')}
+ </Alert>
+ }
+ </Container>;
+};
+++ /dev/null
-import React from 'react';
-import { Container } from 'react-bootstrap';
-import { Helmet } from 'react-helmet';
-import { useTranslation } from 'react-i18next';
-
-export const Component = () => {
- const { t } = useTranslation();
-
- return <Container>
- <h1>{t('twitchLegal.heading')}</h1>
- <Helmet>
- <title>{t('twitchLegal.heading')}</title>
- </Helmet>
- <p>{t('twitchLegal.p1')}</p>
- <p>{t('twitchLegal.p2')}</p>
- </Container>;
-};
--- /dev/null
+import React from 'react';
+import { Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
+
+export const Component = () => {
+ const { t } = useTranslation();
+
+ return <Container>
+ <h1>{t('twitchLegal.heading')}</h1>
+ <Helmet>
+ <title>{t('twitchLegal.heading')}</title>
+ </Helmet>
+ <p>{t('twitchLegal.p1')}</p>
+ <p>{t('twitchLegal.p2')}</p>
+ </Container>;
+};
+++ /dev/null
-import axios from 'axios';
-import React, { useEffect, useState } from 'react';
-import { Helmet } from 'react-helmet';
-import { useParams } from 'react-router-dom';
-
-import NotFound from './NotFound';
-import CanonicalLinks from '../components/common/CanonicalLinks';
-import ErrorBoundary from '../components/common/ErrorBoundary';
-import ErrorMessage from '../components/common/ErrorMessage';
-import Loading from '../components/common/Loading';
-import Profile from '../components/users/Profile';
-
-const User = () => {
- const params = useParams();
- const { id } = params;
-
- const [error, setError] = useState(null);
- const [loading, setLoading] = useState(true);
- const [user, setUser] = useState(null);
-
- useEffect(() => {
- setLoading(true);
- const ctrl = new AbortController();
- axios
- .get(`/api/users/${id}`, { signal: ctrl.signal })
- .then(response => {
- setError(null);
- setLoading(false);
- setUser(response.data);
- })
- .catch(error => {
- setError(error);
- setLoading(false);
- setUser(null);
- });
- return () => {
- ctrl.abort();
- };
- }, [id]);
-
- useEffect(() => {
- const cb = (e) => {
- if (e.user) {
- setUser(user => e.user.id === user.id ? { ...user, ...e.user } : user);
- }
- };
- window.Echo.channel('App.Control')
- .listen('UserChanged', cb);
- return () => {
- window.Echo.channel('App.Control')
- .stopListening('UserChanged', cb);
- };
- }, []);
-
- if (loading) {
- return <Loading />;
- }
-
- if (error) {
- return <ErrorMessage error={error} />;
- }
-
- if (!user) {
- return <NotFound />;
- }
-
- return <ErrorBoundary>
- <Helmet>
- <title>{user.nickname || user.username}</title>
- </Helmet>
- <CanonicalLinks base={`/users/${user.id}`} />
- <Profile user={user} />
- </ErrorBoundary>;
-};
-
-export default User;
--- /dev/null
+import axios from 'axios';
+import React, { useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet';
+import { useParams } from 'react-router-dom';
+
+import NotFound from './NotFound';
+import CanonicalLinks from '../components/common/CanonicalLinks';
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import ErrorMessage from '../components/common/ErrorMessage';
+import Loading from '../components/common/Loading';
+import Profile from '../components/users/Profile';
+
+const User = () => {
+ const params = useParams();
+ const { id } = params;
+
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [user, setUser] = useState(null);
+
+ useEffect(() => {
+ setLoading(true);
+ const ctrl = new AbortController();
+ axios
+ .get(`/api/users/${id}`, { signal: ctrl.signal })
+ .then(response => {
+ setError(null);
+ setLoading(false);
+ setUser(response.data);
+ })
+ .catch(error => {
+ setError(error);
+ setLoading(false);
+ setUser(null);
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [id]);
+
+ useEffect(() => {
+ const cb = (e) => {
+ if (e.user) {
+ setUser(user => e.user.id === user.id ? { ...user, ...e.user } : user);
+ }
+ };
+ window.Echo.channel('App.Control')
+ .listen('UserChanged', cb);
+ return () => {
+ window.Echo.channel('App.Control')
+ .stopListening('UserChanged', cb);
+ };
+ }, []);
+
+ if (loading) {
+ return <Loading />;
+ }
+
+ if (error) {
+ return <ErrorMessage error={error} />;
+ }
+
+ if (!user) {
+ return <NotFound />;
+ }
+
+ return <ErrorBoundary>
+ <Helmet>
+ <title>{user.nickname || user.username}</title>
+ </Helmet>
+ <CanonicalLinks base={`/users/${user.id}`} />
+ <Profile user={user} />
+ </ErrorBoundary>;
+};
+
+export default User;
+++ /dev/null
-import React from 'react';
-import { Helmet } from 'react-helmet';
-
-import ErrorBoundary from '../components/common/ErrorBoundary';
-import Tracker from '../components/zootr/MixedPoolsTracker';
-
-export const Component = () => {
- return <ErrorBoundary>
- <Helmet>
- <title>ZOOTR Mixed Pools Tracker</title>
- </Helmet>
- <Tracker />
- </ErrorBoundary>;
-};
--- /dev/null
+import React from 'react';
+import { Helmet } from 'react-helmet';
+
+import ErrorBoundary from '../components/common/ErrorBoundary';
+import Tracker from '../components/zootr/MixedPoolsTracker';
+
+export const Component = () => {
+ return <ErrorBoundary>
+ <Helmet>
+ <title>ZOOTR Mixed Pools Tracker</title>
+ </Helmet>
+ <Tracker />
+ </ErrorBoundary>;
+};
+++ /dev/null
-import '@testing-library/jest-dom';
-@import '~bootstrap/scss/functions';
+@import 'bootstrap/scss/functions';
@import 'variables';
-@import "~bootstrap/scss/variables";
-@import "~bootstrap/scss/variables-dark";
+@import 'bootstrap/scss/variables';
+@import 'bootstrap/scss/variables-dark';
$theme-colors: map-merge($theme-colors, $custom-colors);
-@import "~bootstrap/scss/maps";
-@import "~bootstrap/scss/mixins";
-@import "~bootstrap/scss/root";
+@import 'bootstrap/scss/maps';
+@import 'bootstrap/scss/mixins';
+@import 'bootstrap/scss/root';
-@import "~bootstrap/scss/utilities";
+@import 'bootstrap/scss/utilities';
$utilities: map-merge($utilities, ("font-size": map-merge(map-get($utilities, "font-size"), (responsive: true))));
-@import '~bootstrap/scss/bootstrap';
+@import 'bootstrap/scss/bootstrap';
<meta property="twitter:image" content="{{ $image }}">
@endisset
- <script src="{{ mix('js/manifest.js') }}" defer></script>
- <script src="{{ mix('js/vendor.js') }}" defer></script>
- <script src="{{ mix('js/index.js') }}" defer></script>
-
- <link href="{{ mix('css/app.css') }}" rel="stylesheet">
+ @viteReactRefresh
+ @vite(['resources/js/index.jsx'])
</head>
<body>
<div id="react-root" class="title m-b-md">
+import { describe, expect, test } from 'vitest';
+
import {
parseTime,
-} from 'helpers/Result';
+} from '@/helpers/Result';
describe('parseTime', () => {
test('null on empty', () => {
+import { describe, expect, test } from 'vitest';
+
import {
getUserName,
-} from 'helpers/User';
+} from '@/helpers/User';
describe('getUserName', () => {
test('empty on missing user', () => {
+import { describe, expect, test } from 'vitest';
+
import {
CONFIG,
applyLogic,
configureDungeons,
getLocationStatus,
makeEmptyState,
-} from 'helpers/tracker';
+} from '@/helpers/tracker';
describe('base reachability', () => {
const config = {
+import { describe, expect, test } from 'vitest';
+
import {
CONFIG,
applyLogic,
configureDungeons,
getLocationStatus,
makeEmptyState,
-} from 'helpers/tracker';
+} from '@/helpers/tracker';
describe('base reachability', () => {
const config = { ...CONFIG };
+import { describe, expect, test } from 'vitest';
+
import {
CONFIG,
configureDungeons,
getDungeonBoss,
getDungeonRemainingItems,
makeEmptyState,
-} from 'helpers/tracker';
+} from '@/helpers/tracker';
describe('default dungeon configuration', () => {
const dungeons = configureDungeons(CONFIG);
--- /dev/null
+import { defineConfig } from 'vite';
+import laravel from 'laravel-vite-plugin';
+import react from '@vitejs/plugin-react';
+import { manualChunksPlugin } from 'vite-plugin-webpackchunkname';
+
+export default defineConfig({
+ plugins: [
+ laravel([
+ 'resources/js/index.jsx',
+ ]),
+ react(),
+ manualChunksPlugin(),
+ ],
+ test: {
+ environment: 'jsdom',
+ },
+});
+++ /dev/null
-const mix = require('laravel-mix');
-
-/*
- |--------------------------------------------------------------------------
- | Mix Asset Management
- |--------------------------------------------------------------------------
- |
- | Mix provides a clean, fluent API for defining some Webpack build steps
- | for your Laravel application. By default, we are compiling the Sass
- | file for the application as well as bundling up all the JS files.
- |
- */
-
-mix.js('resources/js/index.js', 'public/js')
- .react()
- .sass('resources/sass/app.scss', 'public/css')
- .extract([
- '@babel/runtime',
- '@fortawesome/fontawesome-free',
- '@fortawesome/fontawesome-svg-core',
- '@fortawesome/free-brands-svg-icons',
- '@fortawesome/free-solid-svg-icons',
- '@fortawesome/react-fontawesome',
- '@popperjs/core',
- '@restart/hooks',
- '@restart/ui',
- 'axios',
- 'bootstrap',
- 'call-bind',
- 'classnames',
- 'formik',
- 'history',
- 'i18next',
- 'i18next-browser-languagedetector',
- 'invariant',
- 'laravel-echo',
- 'lodash',
- 'lodash-es',
- 'moment',
- 'numeral',
- 'property-expr',
- 'pusher-js',
- 'qs',
- 'react',
- 'react-bootstrap',
- 'react-dom',
- 'react-fast-compare',
- 'react-i18next',
- 'react-is',
- 'react-lifecycles-compat',
- 'react-resize-detector',
- 'react-router',
- 'react-router-bootstrap',
- 'react-router-dom',
- 'react-smooth',
- 'react-transition-group',
- 'reduce-css-calc',
- 'regenerator-runtime',
- 'resize-observer-polyfill',
- 'scheduler',
- 'side-channel',
- 'tiny-warning',
- 'toastr',
- 'toposort',
- 'uncontrollable',
- 'void-elements',
- 'warning',
- 'yup',
- ])
- .sourceMaps(true)
- .version();
-//if (mix.inProduction()) {
- mix.webpackConfig({
- output: {
- chunkFilename: 'js/chunks/[name].[chunkhash].js',
- },
- });
-//} else {
-// mix.webpackConfig({
-// output: {
-// asyncChunks: false,
-// },
-// });
-//}