Guide generator (#231)

* Replace sw, sns battlesuit images

* Add gen guide page

* Make supports configurable

* Extract DifficultySelect & Improve Ex Signet Ordering style

* Extract BattlesuitSelect

* Use BattlesuitSelect for support valk

* Swap support if duplicated

* Add sigil selector

* Set initial ex signets

* Change layout

* Add sigil icon to single value

* Add equipment select

* Fix difficulty box label

* Implement SignetBox

* Add Signet form control

* Improve rendering perf of stigma and weapon select

* Implement image converter

* Fix signet select

* Remove FontHead

* Remove wrong new line

* Fix warnings

* Add custom style configurator

* Make labels of DiffucltyBox and ValkBox configurable

* Add background and improve layout

* Improve ex signet fontsize

* Style boundaries of left bottom boxes

* Apply fonts directly

* Style signet box

* Add Signet group label

* Style ValkBox

* Add support valk label

* Style layout of SignetBox

* Add shadow to difficulty box

* Add sigil label

* Adjust signet group label

* Add equipment label

* Configure colors of left bottom boxes

* Add tag, rank, signature

* Add icon to current value of battlesuit select

* Adjust form styles

* Add signet group select

* Implement import/export data

* Fix dashed border of difficulty box

* Add gen link to elysian realm home

* Prefix types
This commit is contained in:
Sakura Knoll
2022-08-29 20:08:19 +09:00
committed by GitHub
Unverified
parent 2dd48df340
commit 5c8ed4f603
28 changed files with 3064 additions and 180 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 KiB

After

Width:  |  Height:  |  Size: 445 KiB

+4 -8
View File
@@ -16,14 +16,12 @@
- name: Active Skill (CN Live Data)
krName: 액티브 스킬 (CN Live Data)
description: |-
[SP Cost: 0][CD: 0] Use the bow to aim and shoot (max aiming duration:
3s), dealing 675.0% ATK of Ice DMG.
[SP Cost: 0][CD: 0] Use the bow to aim and shoot (max aiming duration: 3s), dealing 675.0% ATK of Ice DMG.
Herrscher of Human:Ego equip bonus: Inflicts Ego Blossom to the target, which makes the enemy to take 15.0% more Ice DMG for 10s (can be refreshed). In Herrscher of Human form, inflicting Ego Blossom deals 10 x 20.0% ATK of Ice DMG.
The weapon active's CD and charging time are based on the shooting mode.
krDescription: |-
[SP 소모: 0][재사용 대기 시간: 0초] 화살로 적을 조준 사격해(최대 3초 동안 조준), 목표에게
675.0%의 빙결 대미지를 가한다.
[SP 소모: 0][재사용 대기 시간: 0초] 화살로 적을 조준 사격해(최대 3초 동안 조준), 목표에게 675.0%의 빙결 대미지를 가한다.
진아·인류의 율자 장착 보너스: 진아 개화 효과를 목표에 발동시키며, 10초간 적이 받는 빙결 피해 15.0% 증가 (갱신 가능). 인간의 율자 형태에서, 진아 개화 발동 시, 공격력 10 x 20.0%의 빙결 대미지를 가한다.
무기 액티브 스킬의 재사용 대기시간과 충전 시간은 장착 캐릭터의 사격 유형에 따라 결정된다.
@@ -60,14 +58,12 @@
- name: Active Skill (CN Live Data)
krName: 액티브 스킬 (CN Live Data)
description: |-
[SP Cost: 0][CD: 0] Use the bow to aim and shoot (max aiming duration:
3s), dealing 550.0% ATK of Ice DMG.
[SP Cost: 0][CD: 0] Use the bow to aim and shoot (max aiming duration: 3s), dealing 550.0% ATK of Ice DMG.
Herrscher of Human:Ego equip bonus: Inflicts Ego Blossom to the target, which makes the enemy to take 12.0% more Ice DMG for 10s (can be refreshed). In Herrscher of Human form, inflicting Ego Blossom deals 10 x 15.0% ATK of Ice DMG.
The weapon active's CD and charging time are based on the shooting mode.
krDescription: |-
[SP 소모: 0][재사용 대기 시간: 0초] 화살로 적을 조준 사격해(최대 3초 동안 조준), 목표에게
555.0%의 빙결 대미지를 가한다.
[SP 소모: 0][재사용 대기 시간: 0초] 화살로 적을 조준 사격해(최대 3초 동안 조준), 목표에게 555.0%의 빙결 대미지를 가한다.
진아·인류의 율자 장착 보너스: 진아 개화 효과를 목표에 발동시키며, 10초간 적이 받는 빙결 피해 12.0% 증가 (갱신 가능). 인간의 율자 형태에서, 진아 개화 발동 시, 공격력 10 x 15.0%의 빙결 대미지를 가한다.
무기 액티브 스킬의 재사용 대기시간과 충전 시간은 장착 캐릭터의 사격 유형에 따라 결정된다.
+452 -170
View File
@@ -9,9 +9,14 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@dnd-kit/core": "^6.0.5",
"@dnd-kit/sortable": "^7.0.1",
"@dnd-kit/utilities": "^3.2.0",
"@mdi/js": "^6.5.95",
"@mdi/react": "^1.5.0",
"@types/file-saver": "^2.0.5",
"date-fns": "^2.27.0",
"html-to-image": "^1.10.6",
"next": "^12.1.6",
"next-i18next": "^10.1.0",
"nprogress": "^0.2.0",
@@ -20,6 +25,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-indiana-drag-scroll": "^2.1.0",
"react-select": "^5.4.0",
"theme-ui": "^0.12.1",
"yaml": "^2.1.1"
},
@@ -31,6 +37,7 @@
"cheerio": "^1.0.0-rc.10",
"eslint": "8.5.0",
"eslint-config-next": "12.0.7",
"file-saver": "^2.0.5",
"filenamify": "^4.3.0",
"http-server": "^14.1.0",
"node-fetch": "^2.6.7",
@@ -249,9 +256,9 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz",
"integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==",
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz",
"integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==",
"engines": {
"node": ">=6.9.0"
}
@@ -337,11 +344,11 @@
}
},
"node_modules/@babel/plugin-syntax-jsx": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz",
"integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==",
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz",
"integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
"@babel/helper-plugin-utils": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
@@ -351,9 +358,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.15.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz",
"integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==",
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz",
"integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==",
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
@@ -474,18 +481,87 @@
"node": ">=12"
}
},
"node_modules/@emotion/babel-plugin": {
"version": "11.7.1",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.1.tgz",
"integrity": "sha512-K3/6Y+J/sIAjplf3uIteWLhPuOyuMNnE+iyYnTF/m294vc6IL90kTHp7y8ldZYbpKlP17rpOWDKM9DvTcrOmNQ==",
"node_modules/@dnd-kit/accessibility": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz",
"integrity": "sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==",
"dependencies": {
"@babel/helper-module-imports": "^7.12.13",
"@babel/plugin-syntax-jsx": "^7.12.13",
"@babel/runtime": "^7.13.10",
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.5",
"@emotion/serialize": "^1.0.2",
"babel-plugin-macros": "^2.6.1",
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@dnd-kit/accessibility/node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"node_modules/@dnd-kit/core": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.0.5.tgz",
"integrity": "sha512-3nL+Zy5cT+1XwsWdlXIvGIFvbuocMyB4NBxTN74DeBaBqeWdH9JsnKwQv7buZQgAHmAH+eIENfS1ginkvW6bCw==",
"dependencies": {
"@dnd-kit/accessibility": "^3.0.0",
"@dnd-kit/utilities": "^3.2.0",
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@dnd-kit/core/node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"node_modules/@dnd-kit/sortable": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.1.tgz",
"integrity": "sha512-n77qAzJQtMMywu25sJzhz3gsHnDOUlEjTtnRl8A87rWIhnu32zuP+7zmFjwGgvqfXmRufqiHOSlH7JPC/tnJ8Q==",
"dependencies": {
"@dnd-kit/utilities": "^3.2.0",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@dnd-kit/core": "^6.0.4",
"react": ">=16.8.0"
}
},
"node_modules/@dnd-kit/sortable/node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"node_modules/@dnd-kit/utilities": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.0.tgz",
"integrity": "sha512-h65/pn2IPCCIWwdlR2BMLqRkDxpTEONA+HQW3n765HBijLYGyrnTCLa2YQt8VVjjSQD6EfFlTE6aS2Q/b6nb2g==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@dnd-kit/utilities/node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"node_modules/@emotion/babel-plugin": {
"version": "11.10.2",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.2.tgz",
"integrity": "sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA==",
"dependencies": {
"@babel/helper-module-imports": "^7.16.7",
"@babel/plugin-syntax-jsx": "^7.17.12",
"@babel/runtime": "^7.18.3",
"@emotion/hash": "^0.9.0",
"@emotion/memoize": "^0.8.0",
"@emotion/serialize": "^1.1.0",
"babel-plugin-macros": "^3.1.0",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^4.0.0",
"find-root": "^1.1.0",
@@ -496,6 +572,11 @@
"@babel/core": "^7.0.0"
}
},
"node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz",
"integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA=="
},
"node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -521,26 +602,31 @@
"integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
},
"node_modules/@emotion/cache": {
"version": "11.7.1",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz",
"integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==",
"version": "11.10.3",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.3.tgz",
"integrity": "sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ==",
"dependencies": {
"@emotion/memoize": "^0.7.4",
"@emotion/sheet": "^1.1.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"@emotion/memoize": "^0.8.0",
"@emotion/sheet": "^1.2.0",
"@emotion/utils": "^1.2.0",
"@emotion/weak-memoize": "^0.3.0",
"stylis": "4.0.13"
}
},
"node_modules/@emotion/cache/node_modules/@emotion/memoize": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz",
"integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA=="
},
"node_modules/@emotion/cache/node_modules/stylis": {
"version": "4.0.13",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
"integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
},
"node_modules/@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz",
"integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ=="
},
"node_modules/@emotion/is-prop-valid": {
"version": "1.1.1",
@@ -556,16 +642,16 @@
"integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
},
"node_modules/@emotion/react": {
"version": "11.7.1",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.7.1.tgz",
"integrity": "sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==",
"version": "11.10.0",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.0.tgz",
"integrity": "sha512-K6z9zlHxxBXwN8TcpwBKcEsBsOw4JWCCmR+BeeOWgqp8GIU1yA2Odd41bwdAAr0ssbQrbJbVnndvv7oiv1bZeQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@emotion/cache": "^11.7.1",
"@emotion/serialize": "^1.0.2",
"@emotion/sheet": "^1.1.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.10.0",
"@emotion/cache": "^11.10.0",
"@emotion/serialize": "^1.1.0",
"@emotion/utils": "^1.2.0",
"@emotion/weak-memoize": "^0.3.0",
"hoist-non-react-statics": "^3.3.1"
},
"peerDependencies": {
@@ -582,21 +668,26 @@
}
},
"node_modules/@emotion/serialize": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
"integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.0.tgz",
"integrity": "sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA==",
"dependencies": {
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.4",
"@emotion/unitless": "^0.7.5",
"@emotion/utils": "^1.0.0",
"@emotion/hash": "^0.9.0",
"@emotion/memoize": "^0.8.0",
"@emotion/unitless": "^0.8.0",
"@emotion/utils": "^1.2.0",
"csstype": "^3.0.2"
}
},
"node_modules/@emotion/serialize/node_modules/@emotion/memoize": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz",
"integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA=="
},
"node_modules/@emotion/sheet": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz",
"integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g=="
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.0.tgz",
"integrity": "sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w=="
},
"node_modules/@emotion/styled": {
"version": "11.6.0",
@@ -624,19 +715,19 @@
}
},
"node_modules/@emotion/unitless": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
"integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw=="
},
"node_modules/@emotion/utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
"integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz",
"integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw=="
},
"node_modules/@emotion/weak-memoize": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz",
"integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg=="
},
"node_modules/@eslint/eslintrc": {
"version": "1.0.5",
@@ -1293,6 +1384,11 @@
"integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
"dev": true
},
"node_modules/@types/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ=="
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
@@ -1359,6 +1455,14 @@
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
@@ -1733,13 +1837,17 @@
"dev": true
},
"node_modules/babel-plugin-macros": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
"integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
"dependencies": {
"@babel/runtime": "^7.7.2",
"cosmiconfig": "^6.0.0",
"resolve": "^1.12.0"
"@babel/runtime": "^7.12.5",
"cosmiconfig": "^7.0.0",
"resolve": "^1.19.0"
},
"engines": {
"node": ">=10",
"npm": ">=6"
}
},
"node_modules/balanced-match": {
@@ -1996,18 +2104,18 @@
}
},
"node_modules/cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
"integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
"dependencies": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
"import-fresh": "^3.2.1",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.7.2"
"yaml": "^1.10.0"
},
"engines": {
"node": ">=8"
"node": ">=10"
}
},
"node_modules/cosmiconfig/node_modules/yaml": {
@@ -2171,6 +2279,15 @@
"node": ">=6.0.0"
}
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/dom-serializer": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
@@ -2663,18 +2780,6 @@
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
}
},
"node_modules/eslint-plugin-jsx-a11y/node_modules/@babel/runtime": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
"dev": true,
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/eslint-plugin-react": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.27.1.tgz",
@@ -3050,6 +3155,12 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"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==",
"dev": true
},
"node_modules/filename-reserved-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
@@ -3404,6 +3515,11 @@
"void-elements": "3.1.0"
}
},
"node_modules/html-to-image": {
"version": "1.10.6",
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.10.6.tgz",
"integrity": "sha512-+iFUWRJDmRRB92IxEAVoiuYQQXRp91yuI4Om7+lswwL3OOzoQ+H7laGLuZFK6ks2Z6Nz0wYm0UzahIO+D1Zxag=="
},
"node_modules/htmlparser2": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
@@ -4011,6 +4127,11 @@
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"node_modules/memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
},
"node_modules/memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
@@ -4711,7 +4832,6 @@
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -4721,8 +4841,7 @@
"node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/punycode": {
"version": "2.1.1",
@@ -4838,6 +4957,39 @@
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/react-select": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.4.0.tgz",
"integrity": "sha512-CjE9RFLUvChd5SdlfG4vqxZd55AZJRrLrHzkQyTYeHlpOztqcgnyftYAolJ0SGsBev6zAs6qFrjm6KU3eo2hzg==",
"dependencies": {
"@babel/runtime": "^7.12.0",
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.8.1",
"@types/react-transition-group": "^4.4.0",
"memoize-one": "^5.0.0",
"prop-types": "^15.6.0",
"react-transition-group": "^4.3.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
@@ -5752,9 +5904,9 @@
}
},
"@babel/helper-plugin-utils": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz",
"integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ=="
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz",
"integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w=="
},
"@babel/helper-simple-access": {
"version": "7.18.6",
@@ -5813,17 +5965,17 @@
"peer": true
},
"@babel/plugin-syntax-jsx": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz",
"integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==",
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz",
"integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==",
"requires": {
"@babel/helper-plugin-utils": "^7.14.5"
"@babel/helper-plugin-utils": "^7.18.6"
}
},
"@babel/runtime": {
"version": "7.15.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz",
"integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==",
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz",
"integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
@@ -5914,18 +6066,81 @@
"@cspotcode/source-map-consumer": "0.8.0"
}
},
"@emotion/babel-plugin": {
"version": "11.7.1",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.1.tgz",
"integrity": "sha512-K3/6Y+J/sIAjplf3uIteWLhPuOyuMNnE+iyYnTF/m294vc6IL90kTHp7y8ldZYbpKlP17rpOWDKM9DvTcrOmNQ==",
"@dnd-kit/accessibility": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz",
"integrity": "sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==",
"requires": {
"@babel/helper-module-imports": "^7.12.13",
"@babel/plugin-syntax-jsx": "^7.12.13",
"@babel/runtime": "^7.13.10",
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.5",
"@emotion/serialize": "^1.0.2",
"babel-plugin-macros": "^2.6.1",
"tslib": "^2.0.0"
},
"dependencies": {
"tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
}
}
},
"@dnd-kit/core": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.0.5.tgz",
"integrity": "sha512-3nL+Zy5cT+1XwsWdlXIvGIFvbuocMyB4NBxTN74DeBaBqeWdH9JsnKwQv7buZQgAHmAH+eIENfS1ginkvW6bCw==",
"requires": {
"@dnd-kit/accessibility": "^3.0.0",
"@dnd-kit/utilities": "^3.2.0",
"tslib": "^2.0.0"
},
"dependencies": {
"tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
}
}
},
"@dnd-kit/sortable": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.1.tgz",
"integrity": "sha512-n77qAzJQtMMywu25sJzhz3gsHnDOUlEjTtnRl8A87rWIhnu32zuP+7zmFjwGgvqfXmRufqiHOSlH7JPC/tnJ8Q==",
"requires": {
"@dnd-kit/utilities": "^3.2.0",
"tslib": "^2.0.0"
},
"dependencies": {
"tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
}
}
},
"@dnd-kit/utilities": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.0.tgz",
"integrity": "sha512-h65/pn2IPCCIWwdlR2BMLqRkDxpTEONA+HQW3n765HBijLYGyrnTCLa2YQt8VVjjSQD6EfFlTE6aS2Q/b6nb2g==",
"requires": {
"tslib": "^2.0.0"
},
"dependencies": {
"tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
}
}
},
"@emotion/babel-plugin": {
"version": "11.10.2",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.2.tgz",
"integrity": "sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA==",
"requires": {
"@babel/helper-module-imports": "^7.16.7",
"@babel/plugin-syntax-jsx": "^7.17.12",
"@babel/runtime": "^7.18.3",
"@emotion/hash": "^0.9.0",
"@emotion/memoize": "^0.8.0",
"@emotion/serialize": "^1.1.0",
"babel-plugin-macros": "^3.1.0",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^4.0.0",
"find-root": "^1.1.0",
@@ -5933,6 +6148,11 @@
"stylis": "4.0.13"
},
"dependencies": {
"@emotion/memoize": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz",
"integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA=="
},
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -5951,17 +6171,22 @@
}
},
"@emotion/cache": {
"version": "11.7.1",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz",
"integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==",
"version": "11.10.3",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.3.tgz",
"integrity": "sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ==",
"requires": {
"@emotion/memoize": "^0.7.4",
"@emotion/sheet": "^1.1.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"@emotion/memoize": "^0.8.0",
"@emotion/sheet": "^1.2.0",
"@emotion/utils": "^1.2.0",
"@emotion/weak-memoize": "^0.3.0",
"stylis": "4.0.13"
},
"dependencies": {
"@emotion/memoize": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz",
"integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA=="
},
"stylis": {
"version": "4.0.13",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
@@ -5970,9 +6195,9 @@
}
},
"@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz",
"integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ=="
},
"@emotion/is-prop-valid": {
"version": "1.1.1",
@@ -5988,35 +6213,42 @@
"integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
},
"@emotion/react": {
"version": "11.7.1",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.7.1.tgz",
"integrity": "sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==",
"version": "11.10.0",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.0.tgz",
"integrity": "sha512-K6z9zlHxxBXwN8TcpwBKcEsBsOw4JWCCmR+BeeOWgqp8GIU1yA2Odd41bwdAAr0ssbQrbJbVnndvv7oiv1bZeQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@emotion/cache": "^11.7.1",
"@emotion/serialize": "^1.0.2",
"@emotion/sheet": "^1.1.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.10.0",
"@emotion/cache": "^11.10.0",
"@emotion/serialize": "^1.1.0",
"@emotion/utils": "^1.2.0",
"@emotion/weak-memoize": "^0.3.0",
"hoist-non-react-statics": "^3.3.1"
}
},
"@emotion/serialize": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
"integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.0.tgz",
"integrity": "sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA==",
"requires": {
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.4",
"@emotion/unitless": "^0.7.5",
"@emotion/utils": "^1.0.0",
"@emotion/hash": "^0.9.0",
"@emotion/memoize": "^0.8.0",
"@emotion/unitless": "^0.8.0",
"@emotion/utils": "^1.2.0",
"csstype": "^3.0.2"
},
"dependencies": {
"@emotion/memoize": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz",
"integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA=="
}
}
},
"@emotion/sheet": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz",
"integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g=="
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.0.tgz",
"integrity": "sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w=="
},
"@emotion/styled": {
"version": "11.6.0",
@@ -6031,19 +6263,19 @@
}
},
"@emotion/unitless": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
"integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw=="
},
"@emotion/utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
"integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz",
"integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw=="
},
"@emotion/weak-memoize": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz",
"integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg=="
},
"@eslint/eslintrc": {
"version": "1.0.5",
@@ -6532,6 +6764,11 @@
"integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
"dev": true
},
"@types/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ=="
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
@@ -6598,6 +6835,14 @@
"csstype": "^3.0.2"
}
},
"@types/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==",
"requires": {
"@types/react": "*"
}
},
"@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
@@ -6855,13 +7100,13 @@
"dev": true
},
"babel-plugin-macros": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
"integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
"requires": {
"@babel/runtime": "^7.7.2",
"cosmiconfig": "^6.0.0",
"resolve": "^1.12.0"
"@babel/runtime": "^7.12.5",
"cosmiconfig": "^7.0.0",
"resolve": "^1.19.0"
}
},
"balanced-match": {
@@ -7056,15 +7301,15 @@
"dev": true
},
"cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
"integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
"requires": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
"import-fresh": "^3.2.1",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.7.2"
"yaml": "^1.10.0"
},
"dependencies": {
"yaml": {
@@ -7190,6 +7435,15 @@
"esutils": "^2.0.2"
}
},
"dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"requires": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"dom-serializer": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
@@ -7663,17 +7917,6 @@
"jsx-ast-utils": "^3.2.1",
"language-tags": "^1.0.5",
"minimatch": "^3.0.4"
},
"dependencies": {
"@babel/runtime": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"eslint-plugin-react": {
@@ -7863,6 +8106,12 @@
"flat-cache": "^3.0.4"
}
},
"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==",
"dev": true
},
"filename-reserved-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
@@ -8122,6 +8371,11 @@
"void-elements": "3.1.0"
}
},
"html-to-image": {
"version": "1.10.6",
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.10.6.tgz",
"integrity": "sha512-+iFUWRJDmRRB92IxEAVoiuYQQXRp91yuI4Om7+lswwL3OOzoQ+H7laGLuZFK6ks2Z6Nz0wYm0UzahIO+D1Zxag=="
},
"htmlparser2": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
@@ -8576,6 +8830,11 @@
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
},
"memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
@@ -9083,7 +9342,6 @@
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -9093,8 +9351,7 @@
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
@@ -9163,6 +9420,31 @@
"easy-bem": "^1.1.1"
}
},
"react-select": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.4.0.tgz",
"integrity": "sha512-CjE9RFLUvChd5SdlfG4vqxZd55AZJRrLrHzkQyTYeHlpOztqcgnyftYAolJ0SGsBev6zAs6qFrjm6KU3eo2hzg==",
"requires": {
"@babel/runtime": "^7.12.0",
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.8.1",
"@types/react-transition-group": "^4.4.0",
"memoize-one": "^5.0.0",
"prop-types": "^15.6.0",
"react-transition-group": "^4.3.0"
}
},
"react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"requires": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
}
},
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+8 -1
View File
@@ -4,9 +4,14 @@
"description": "",
"main": "index.js",
"dependencies": {
"@dnd-kit/core": "^6.0.5",
"@dnd-kit/sortable": "^7.0.1",
"@dnd-kit/utilities": "^3.2.0",
"@mdi/js": "^6.5.95",
"@mdi/react": "^1.5.0",
"@types/file-saver": "^2.0.5",
"date-fns": "^2.27.0",
"html-to-image": "^1.10.6",
"next": "^12.1.6",
"next-i18next": "^10.1.0",
"nprogress": "^0.2.0",
@@ -15,6 +20,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-indiana-drag-scroll": "^2.1.0",
"react-select": "^5.4.0",
"theme-ui": "^0.12.1",
"yaml": "^2.1.1"
},
@@ -26,6 +32,7 @@
"cheerio": "^1.0.0-rc.10",
"eslint": "8.5.0",
"eslint-config-next": "12.0.7",
"file-saver": "^2.0.5",
"filenamify": "^4.3.0",
"http-server": "^14.1.0",
"node-fetch": "^2.6.7",
@@ -37,7 +44,7 @@
"scripts": {
"dev": "run-p dev:web dev:assets",
"dev:web": "next dev -p 4000",
"dev:assets": "http-server assets -p 4001 -s",
"dev:assets": "http-server assets -p 4001 -s --cors",
"build": "npm run compile:novels && next build",
"start": "next start",
"lint": "next lint",
Binary file not shown.

After

Width:  |  Height:  |  Size: 811 KiB

@@ -0,0 +1,104 @@
import { useMemo } from 'react'
import ReactSelect, { components } from 'react-select'
import { Flex, Image } from 'theme-ui'
import { assetsBucketBaseUrl } from '../../../lib/consts'
import { BattlesuitData } from '../../../lib/honkai3rd/battlesuits'
interface BattlesuitSelectProps {
instanceId: string
value: string
optionIds: string[]
battlesuits: BattlesuitData[]
onChange: (newValue: string) => void
}
const BattlesuitSelect = ({
instanceId,
optionIds,
value,
battlesuits,
onChange,
}: BattlesuitSelectProps) => {
const battlesuitOptions = useMemo(() => {
return optionIds.map((battlesuitId) => {
const battlesuit = battlesuits.find((aBattlesuit) => {
return aBattlesuit.id === battlesuitId
})
if (battlesuit == null) {
return {
value: 'unknown',
label: 'unknown',
}
}
return {
value: battlesuit.id,
label: battlesuit.name,
}
})
}, [battlesuits, optionIds])
const battlesuit = battlesuits.find((battlesuit) => {
return battlesuit.id === value
})
return (
<ReactSelect
instanceId={instanceId}
value={
battlesuit != null
? {
label: battlesuit.name,
value: battlesuit.id,
}
: null
}
onChange={(option) => {
if (option == null) {
return
}
onChange(option.value)
}}
options={battlesuitOptions}
components={{
SingleValue: (props) => {
return (
<>
<components.SingleValue {...props}>
<Flex sx={{ alignItems: 'center', color: 'black' }}>
<Image
width={20}
height={20}
alt={props.data.label}
src={`${assetsBucketBaseUrl}/honkai3rd/battlesuits/portrait-${props.data.value}.png`}
sx={{ flexShrink: 0, mr: 2 }}
/>
{props.children}
</Flex>
</components.SingleValue>
</>
)
},
Option: (props) => {
return (
<>
<components.Option {...props}>
<Flex sx={{ alignItems: 'center', color: 'black' }}>
<Image
width={20}
height={20}
alt={props.data.label}
src={`${assetsBucketBaseUrl}/honkai3rd/battlesuits/portrait-${props.data.value}.png`}
sx={{ mr: 2, flexShrink: 0 }}
/>
{props.children}
</Flex>
</components.Option>
</>
)
},
}}
/>
)
}
export default BattlesuitSelect
@@ -0,0 +1,509 @@
import { Box, Button, Flex, Input, Label, Select, Textarea } from 'theme-ui'
import { ERGGData, ERGGDataUpdater, ERGGExSignetType } from './types'
import {
erVersions,
isGeneralSigil,
PopulatedSignetGroup,
RemembranceSigil,
remembranceSigilIds,
supportBattlesuitIds,
} from '../../../lib/honkai3rd/elysianRealm'
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
} from '@dnd-kit/core'
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { BattlesuitData } from '../../../lib/honkai3rd/battlesuits'
import DifficultySelect from './DifficultySelect'
import { getExSignetLabel } from './utils'
import BattlesuitSelect from './BattlesuitSelect'
import SigilSelect from './SigilSelect'
import { WeaponData } from '../../../lib/honkai3rd/weapons'
import { StigmataData } from '../../../lib/honkai3rd/stigmata'
import { useMemo } from 'react'
import EquipmentSetControl from './EquipmentSetControl'
import SignetGroupSelect from './SignetGroupSelect'
interface DataFormProps {
updateData: ERGGDataUpdater
data: ERGGData
battlesuits: BattlesuitData[]
exSignetGroup: PopulatedSignetGroup
sigils: RemembranceSigil[]
weapons: WeaponData[]
stigmata: StigmataData[]
}
const DataForm = ({
updateData,
data,
battlesuits,
exSignetGroup,
sigils,
weapons,
stigmata,
}: DataFormProps) => {
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
)
const { topStigmaIds, midStigmaIds, botStigmaIds } = useMemo(() => {
return stigmata.reduce(
(map, stigma) => {
switch (stigma.type) {
case 'top':
map.topStigmaIds.push(stigma.id)
break
case 'mid':
map.midStigmaIds.push(stigma.id)
break
case 'bot':
map.botStigmaIds.push(stigma.id)
break
}
return map
},
{
topStigmaIds: [] as string[],
midStigmaIds: [] as string[],
botStigmaIds: [] as string[],
}
)
}, [stigmata])
return (
<Box sx={{ maxWidth: 960 }}>
<Flex>
<Box sx={{ flex: 1, p: 2 }}>
<Box>
<Label></Label>
<Textarea
value={data.signature}
placeholder='서명...'
onChange={(event) => {
updateData('signature', event.target.value)
}}
sx={{ resize: 'vertical', minHeight: 100 }}
/>
</Box>
<Box>
<Label></Label>
<Select
value={data.rank || 'na'}
onChange={(event) => {
switch (event.target.value) {
case 'na':
updateData('rank', undefined)
break
default:
updateData('rank', event.target.value)
}
}}
>
<option value='na'>N/A</option>
<option value='s'>s</option>
<option value='s1'>s1</option>
<option value='s2'>s2</option>
<option value='s3'>s3</option>
<option value='ss'>ss</option>
<option value='ss1'>ss1</option>
<option value='ss2'>ss2</option>
<option value='ss3'>ss3</option>
<option value='sss'>sss</option>
<option value='a'>a</option>
</Select>
</Box>
<Box>
<Label></Label>
<Input
value={data.tag}
placeholder='태그...(필살기, 평타)'
onChange={(event) => {
updateData('tag', event.target.value)
}}
/>
</Box>
<Box>
<Label></Label>
<DifficultySelect
onChange={(newValue) => updateData('difficulty', newValue)}
value={data.difficulty}
/>
</Box>
<Box>
<Label></Label>
<BattlesuitSelect
instanceId='battlesuit-select'
battlesuits={battlesuits}
optionIds={erVersions.reduce<string[]>(
(battlesuitIds, version) => {
return [...battlesuitIds, ...version.battlesuits]
},
[]
)}
value={data.battlesuitId}
onChange={(newValue) => {
updateData('battlesuitId', newValue)
const exSignetSet = exSignetGroup.sets.find((signetSet) => {
return signetSet.id === `elysia-${newValue}`
})
if (exSignetSet != null) {
updateData(
'exSignets',
exSignetSet.signets.map((signet, index) => {
return {
type:
data.exSignets[index] != null
? data.exSignets[index].type
: 'na',
name: signet.name,
}
})
)
}
}}
/>
</Box>
<Box>
<Label> </Label>
<Flex>
<Box>
{data.exSignets.map((signet, index) => {
return (
<Flex
key={index}
sx={{ p: 1, height: 40, alignItems: 'center', mb: 2 }}
>
<Select
value={signet.type}
sx={{ height: 40, width: 80, mr: 1, p: 2 }}
onChange={(event) => {
const newExSignets = data.exSignets.slice()
newExSignets[index] = {
...data.exSignets[index],
type: event.target.value as ERGGExSignetType,
}
updateData('exSignets', newExSignets)
}}
>
<option value='start'></option>
<option value='1st'></option>
<option value='2nd'></option>
<option value='backup'></option>
<option value='na'></option>
</Select>
</Flex>
)
})}
</Box>
<Box sx={{ flex: 1 }}>
<DndContext
id='exSignetOrdering'
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={(event) => {
const { active, over } = event
if (over == null) {
return
}
if (active.id !== over.id) {
const nameArray = data.exSignets.map(
(signet) => signet.name
)
const oldIndex = nameArray.indexOf(active.id as string)
const newIndex = nameArray.indexOf(over.id as string)
const newNameArray = arrayMove(
nameArray,
oldIndex,
newIndex
)
const newExSignets = data.exSignets.map(
(signet, index) => {
return {
...signet,
name: newNameArray[index],
}
}
)
updateData('exSignets', newExSignets)
}
}}
>
<SortableContext
items={data.exSignets.map((signet) => ({
id: signet.name,
}))}
strategy={verticalListSortingStrategy}
>
{data.exSignets.map((signet) => (
<SortableItem key={signet.name} name={signet.name} />
))}
</SortableContext>
</DndContext>
</Box>
</Flex>
</Box>
</Box>
<Box sx={{ flex: 1, p: 2 }}>
<Box>
{data.signets.map((signet, index) => {
return (
<Box key={index}>
<Label>{index + 1} </Label>
<Flex sx={{ alignItems: 'center', p: 1 }}>
<Box sx={{ flex: 1, flexShrink: 0, mr: 1 }}>
<SignetGroupSelect
value={signet.group}
onChange={(newValue) => {
const newSignets = data.signets.slice()
newSignets[index] = {
...signet,
group: newValue as any,
}
updateData('signets', newSignets)
}}
instanceId={`signet-select-${index}`}
/>
</Box>
<Box>
<Button
onClick={() => {
const newSignets = data.signets.slice()
newSignets[index] = {
...signet,
nexus: signet.nexus === 1 ? 2 : 1,
}
updateData('signets', newSignets)
}}
sx={{ mr: 1, width: 40 }}
>
{signet.nexus === 1 ? 'I' : 'II'}
</Button>
</Box>
<Box sx={{ width: 60 }}>
<Select
value={signet.type}
onChange={(event) => {
const newSignets = data.signets.slice()
newSignets[index] = {
...signet,
type: event.target.value as any,
}
updateData('signets', newSignets)
}}
>
<option value='start'></option>
<option value='core'></option>
<option value='sub'></option>
</Select>
</Box>
</Flex>
<Box sx={{ p: 1 }}>
<Textarea
value={signet.description}
onChange={(event) => {
const newSignets = data.signets.slice()
newSignets[index] = {
...signet,
description: event.target.value,
}
updateData('signets', newSignets)
}}
placeholder='각인 설명...'
sx={{ resize: 'vertical' }}
/>
</Box>
</Box>
)
})}
</Box>
</Box>
<Box sx={{ flex: 1, p: 2 }}>
<Box>
<Label> </Label>
{data.supportSets.map((supportSet, index) => {
return (
<Box key={index}>
<Box>{supportSet.type === 'util' ? '유틸' : '딜링'}</Box>
<Flex>
<Box sx={{ flex: 1, mr: 1 }}>
<BattlesuitSelect
instanceId={`battlesuit-support-select-${index}-1`}
value={supportSet.battlesuitIds[0]}
optionIds={supportBattlesuitIds}
battlesuits={battlesuits}
onChange={(newValue) => {
const newSupportSets = data.supportSets.slice()
newSupportSets[index] = {
...newSupportSets[index],
battlesuitIds: [
...newSupportSets[index].battlesuitIds,
],
}
newSupportSets[index].battlesuitIds[0] = newValue
if (
data.supportSets[index].battlesuitIds[1] ===
newValue
) {
newSupportSets[index].battlesuitIds[1] =
data.supportSets[index].battlesuitIds[0]
}
updateData('supportSets', newSupportSets)
}}
/>
</Box>
<Box sx={{ flex: 1 }}>
<BattlesuitSelect
instanceId={`battlesuit-support-select-${index}-2`}
value={supportSet.battlesuitIds[1]}
optionIds={supportBattlesuitIds}
battlesuits={battlesuits}
onChange={(newValue) => {
const newSupportSets = data.supportSets.slice()
newSupportSets[index] = {
...newSupportSets[index],
battlesuitIds: [
...newSupportSets[index].battlesuitIds,
],
}
newSupportSets[index].battlesuitIds[1] = newValue
if (
data.supportSets[index].battlesuitIds[0] ===
newValue
) {
newSupportSets[index].battlesuitIds[0] =
data.supportSets[index].battlesuitIds[1]
}
updateData('supportSets', newSupportSets)
}}
/>
</Box>
</Flex>
</Box>
)
})}
</Box>
<Box sx={{ flex: 1 }}>
{data.sigilSets.map((sigilSet, index) => {
return (
<Box key={index}>
<Box>
{sigilSet.type === 'start'
? '초반'
: sigilSet.type === 'mid'
? '중반'
: '후반'}{' '}
</Box>
<Flex>
<Box sx={{ flex: 1, mr: 1 }}>
<SigilSelect
instanceId={`sigil-select-${index}-general`}
value={sigilSet.sigilIds[0]}
optionIds={remembranceSigilIds.filter(isGeneralSigil)}
sigils={sigils}
onChange={(newValue) => {
const newSigilSets = data.sigilSets.slice()
newSigilSets[index] = {
...newSigilSets[index],
sigilIds: [
newValue,
newSigilSets[index].sigilIds[1],
],
}
updateData('sigilSets', newSigilSets)
}}
/>
</Box>
<Box>
<SigilSelect
instanceId={`sigil-select-${index}-support`}
value={sigilSet.sigilIds[1]}
optionIds={remembranceSigilIds.filter(
(sigilId) => !isGeneralSigil(sigilId)
)}
sigils={sigils}
onChange={(newValue) => {
const newSigilSets = data.sigilSets.slice()
newSigilSets[index] = {
...newSigilSets[index],
sigilIds: [
newSigilSets[index].sigilIds[0],
newValue,
],
}
updateData('sigilSets', newSigilSets)
}}
/>
</Box>
</Flex>
</Box>
)
})}
</Box>
<Box>
{data.equipmentSets.map((equipmentSet, index) => {
return (
<EquipmentSetControl
key={index}
equipmentSet={equipmentSet}
weapons={weapons}
stigmata={stigmata}
topStigmaIds={topStigmaIds}
midStigmaIds={midStigmaIds}
botStigmaIds={botStigmaIds}
index={index}
updateData={updateData}
/>
)
})}
</Box>
</Box>
</Flex>
</Box>
)
}
export default DataForm
export function SortableItem(props: { name: string }) {
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id: props.name })
const style = {
transform: CSS.Transform.toString(transform),
transition,
}
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<Flex
sx={{
height: 40,
mb: 2,
cursor: 'ns-resize',
alignItems: 'center',
p: 1,
}}
>
{getExSignetLabel(props.name)}
</Flex>
</div>
)
}
@@ -0,0 +1,83 @@
import { Box } from 'theme-ui'
import { colors } from './styles'
interface DifficultyBoxProps {
difficulty: 'abstinence' | 'corruption' | 'inferno'
}
const DifficultyBox = ({ difficulty }: DifficultyBoxProps) => {
return (
<Box
sx={{
position: 'absolute',
left: 15,
width: 60,
height: 105,
backgroundColor:
difficulty === 'corruption'
? colors.corruptionDifficultyColor
: difficulty === 'abstinence'
? colors.abstinenceDifficultyColor
: colors.infernoDifficultyColor,
padding: '5px',
borderBottomLeftRadius: 10,
borderBottomRightRadius: 10,
boxShadow: '5px 5px 10px rgba(0,0,0,0.5)',
}}
className='difficultyLabel'
>
<Box
sx={{
color: colors.backgroundColor,
fontWeight: 'bold',
textAlign: 'center',
fontSize: 16,
}}
>
</Box>
<Box
sx={{
borderColor: colors.backgroundColor,
borderWidth: '1px 0 1px',
borderStyle: 'dashed',
width: '100%',
}}
/>
<Box
sx={{
color: colors.backgroundColor,
fontWeight: 'bold',
fontSize: 25,
textAlign: 'center',
}}
>
{difficulty === 'corruption'
? '침식'
: difficulty === 'abstinence'
? '제약'
: '겁화'}
</Box>
<Box
sx={{
borderColor: colors.backgroundColor,
borderWidth: '1px 0 1px',
borderStyle: 'dashed',
width: '100%',
}}
/>
<Box
sx={{
color: colors.backgroundColor,
fontWeight: 'bold',
textAlign: 'center',
fontSize: 16,
}}
>
</Box>
</Box>
)
}
export default DifficultyBox
@@ -0,0 +1,30 @@
import { Select } from 'theme-ui'
import { ERGGDifficulty } from './types'
interface DifficultySelectProps {
onChange: (newValue: ERGGDifficulty) => void
value: ERGGDifficulty
}
const DifficultySelect = ({ onChange, value }: DifficultySelectProps) => {
return (
<Select
onChange={(event) => {
switch (event.target.value) {
case 'abstinence':
case 'corruption':
case 'inferno':
onChange(event.target.value)
break
}
}}
value={value}
>
<option value='corruption'></option>
<option value='abstinence'></option>
<option value='inferno'></option>
</Select>
)
}
export default DifficultySelect
@@ -0,0 +1,444 @@
/* eslint-disable @next/next/no-page-custom-font */
import { useCallback, useEffect, useRef, useState } from 'react'
import {
Box,
Button,
Flex,
Heading,
Image,
Input,
Label,
Text,
Textarea,
} from 'theme-ui'
import { BattlesuitData } from '../../../lib/honkai3rd/battlesuits'
import { WeaponData } from '../../../lib/honkai3rd/weapons'
import DifficultyBox from './DifficultyBox'
import SignetBox from './SignetBox'
import { colors } from './styles'
import ValkBox from './ValkBox'
import {
PopulatedSignetGroup,
RemembranceSigil,
} from '../../../lib/honkai3rd/elysianRealm'
import SigilBox from './SigilBox'
import SupportBox from './SupportBox'
import DataForm from './DataForm'
import EquipmentBox from './EquipmentBox'
import { ERGGData, ERGGDataUpdater, ERGGExSignetType } from './types'
import { StigmataData } from '../../../lib/honkai3rd/stigmata'
import { saveAs } from 'file-saver'
import { toBlob } from 'html-to-image'
import { assetsBucketBaseUrl } from '../../../lib/consts'
interface ERGuideGeneratorProps {
weapons: WeaponData[]
stigmata: StigmataData[]
battlesuits: BattlesuitData[]
exSignetGroup: PopulatedSignetGroup
sigils: RemembranceSigil[]
}
const exSignetTypes: ERGGExSignetType[] = [
'start',
'1st',
'2nd',
'backup',
'na',
]
const ERGuideGenerator = ({
weapons,
stigmata,
battlesuits,
exSignetGroup,
sigils,
}: ERGuideGeneratorProps) => {
const [data, setData] = useState<ERGGData>({
tag: '',
signature:
'작성자 : XXX\nAbyss Lab에서 생성됨\nhttps://abyss-lab.app/honkai3rd',
battlesuitId: 'vill-v',
difficulty: 'corruption',
exSignets:
exSignetGroup.sets
.find((set) => set.id === 'elysia-vill-v')
?.signets.map((signet, index) => {
return {
type: exSignetTypes[index],
name: signet.name,
}
}) || [],
supportSets: [
{ type: 'util', battlesuitIds: ['le', 'ae'] },
{ type: 'dps', battlesuitIds: ['br', 'ae'] },
],
sigilSets: [
{
type: 'start',
sigilIds: ['burden', 'it-will-be-written'],
},
{
type: 'mid',
sigilIds: ['burden', 'it-will-be-written'],
},
{
type: 'end',
sigilIds: ['burden', 'it-will-be-written'],
},
],
equipmentSets: [
{
type: 'best',
weapon: 'vill-v-pri-weapon',
top: 'carlo-collodi-top',
mid: 'carlo-collodi-mid',
bot: 'carlo-collodi-bot',
},
{
type: 'alt',
weapon: 'vill-v-pri-weapon',
top: 'carlo-collodi-top',
mid: 'carlo-collodi-mid',
bot: 'carlo-collodi-bot',
},
],
signets: [
{
type: 'core',
group: 'vill-v',
nexus: 2,
description: '',
},
{
type: 'core',
group: 'vill-v',
nexus: 2,
description: '',
},
{
type: 'sub',
group: 'vill-v',
nexus: 2,
description: '',
},
{
type: 'sub',
group: 'vill-v',
nexus: 2,
description: '',
},
{
type: 'sub',
group: 'vill-v',
nexus: 2,
description: '',
},
],
})
const [fileName, setFileName] = useState('guide')
const guideRef = useRef(null)
const updateData = useCallback<ERGGDataUpdater>(
(key, value) => {
setData((previousData) => {
const newData = {
...previousData,
[key]: typeof value === 'function' ? value(previousData[key]) : value,
}
return newData
})
},
[setData]
)
const [customStyle, setCustomStyle] = useState('')
const loadedRef = useRef(false)
useEffect(() => {
if (loadedRef.current) {
localStorage.setItem('ergg:customStyle', customStyle)
} else {
setCustomStyle(localStorage.getItem('ergg:customStyle') || '')
loadedRef.current = true
}
}, [customStyle])
return (
<Box
onDragOver={(event) => {
event.preventDefault()
}}
onDrop={async (event) => {
event.preventDefault()
const result = await new Promise<string>((resolve, reject) => {
const file = event.dataTransfer.files[0]
const reader = new FileReader()
reader.addEventListener(
'load',
() => {
resolve(reader.result as string)
},
{ once: true }
)
reader.addEventListener(
'error',
(error) => {
reject(error)
},
{ once: true }
)
reader.readAsText(file)
})
const parsedResult = JSON.parse(result)
setData(parsedResult)
}}
>
<Box sx={{ width: 960 }}>
<Box
ref={guideRef}
sx={{
width: 960,
height: 660,
position: 'relative',
backgroundColor: colors.backgroundColor,
color: '#FFF',
fontFamily: `'NanumSquare', sans-serif`,
}}
>
<style
dangerouslySetInnerHTML={{
__html: `@font-face {
font-family: 'GyeonggiTitleM';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/GyeonggiTitleM.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'YdestreetB';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2110@1.0/YdestreetB.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
@import url('https://cdn.rawgit.com/moonspam/NanumSquare/master/nanumsquare.css');
@font-face {
font-family: 'ghanachoco';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@1.0/ghanachoco.woff') format('woff');
font-weight: normal;
font-style: normal;
}
.exSignetLabel {
font-family: 'ghanachoco';
}
.difficultyLabel {
font-family: 'YdestreetB';
}
.signetTypeLabel {
font-family: 'YdestreetB';
}
.signetDescription {
font-family: 'GyeonggiTitleM';
}
.signetGroupLabel {
font-family: 'YdestreetB';
}
.tagLabel {
font-family: 'YdestreetB';
}
`,
}}
/>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
width: 960,
height: 660,
opacity: 0.15,
backgroundImage: `url('/assets/erbg.png')`,
backgroundSize: 1300,
backgroundPositionY: -55,
backgroundPositionX: -30,
}}
></Box>
<style
dangerouslySetInnerHTML={{
__html: customStyle,
}}
/>
<Flex
sx={{
position: 'absolute',
top: 15,
left: 85,
}}
>
{data.rank && (
<Image
alt={data.rank}
width={40}
height={40}
src={`${assetsBucketBaseUrl}/honkai3rd/rank-icons/${data.rank}-rank.png`}
sx={{ mr: '5px' }}
/>
)}
{data.tag && (
<Box
sx={{
padding: '5px',
border: 'solid 1px gray',
boxSizing: 'border-box',
backgroundColor: '#181614',
boxShadow: '5px 5px 10px rgba(0,0,0,0.5)',
}}
className='tagLabel'
>
{data.tag}
</Box>
)}
</Flex>
<Flex
sx={{
position: 'absolute',
right: 10,
padding: '5px 10px',
boxSizing: 'border-box',
bottom: 15,
maxWidth: 460,
alignItems: 'center',
whiteSpace: 'pre-wrap',
color: '#A59A9B',
fontStyle: 'italic',
textAlign: 'right',
}}
>
<Text>{data.signature}</Text>
</Flex>
<SignetBox signets={data.signets} />
<DifficultyBox difficulty={data.difficulty} />
<ValkBox
battlesuitId={data.battlesuitId}
exSignets={data.exSignets}
/>
<Box
sx={{
position: 'absolute',
bottom: 15 + (87 + 10) * 2,
left: 15,
}}
>
<SupportBox supportSets={data.supportSets} />
</Box>
<Box
sx={{
position: 'absolute',
bottom: 15 + 87 + 10,
left: 15,
}}
>
<SigilBox sigilSets={data.sigilSets} sigils={sigils} />
</Box>
<Box
sx={{
position: 'absolute',
bottom: '15px',
left: 15,
}}
>
<EquipmentBox
equipmentSets={data.equipmentSets}
weapons={weapons}
stigmata={stigmata}
/>
</Box>
</Box>
</Box>
<Box sx={{ m: 2 }}>
<Flex sx={{ alignItems: 'center' }}>
<Box sx={{ mr: 1 }}>
<Label>File Name</Label>
</Box>
<Box sx={{ mr: 1 }}>
<Input
value={fileName}
onChange={(event) => {
setFileName(event.target.value)
}}
/>
</Box>
<Button
sx={{ mr: 1 }}
onClick={async () => {
if (guideRef.current == null) {
return
}
const blob = await toBlob(guideRef.current)
if (blob == null) {
return
}
saveAs(blob, `${fileName}.png`)
}}
>
Download Image ({fileName}.png)
</Button>
<Button
onClick={() => {
console.log(JSON.stringify(data))
saveAs(new Blob([JSON.stringify(data)]), `${fileName}.json`)
}}
>
Download JSON Data ({fileName}.json)
</Button>
</Flex>
</Box>
<DataForm
exSignetGroup={exSignetGroup}
battlesuits={battlesuits}
updateData={updateData}
data={data}
sigils={sigils}
weapons={weapons}
stigmata={stigmata}
/>
<hr />
<Box sx={{ p: 2 }}>
<Heading as='h3'> </Heading>
<Box as='ul'>
<Box as='li'>
Y이드스트릿체 https://www.yspotlight.co.kr/brand/font?tabNo=1
</Box>
<Box as='li'> https://hangeul.naver.com/2017/nanum</Box>
<Box as='li'>
https://www.gg.go.kr/contents/contents.do?ciIdx=679&menuId=2457
</Box>
<Box as='li'>
릿 hhttps://www.lotteconf.co.kr/prcenter/gana
</Box>
</Box>
<Box sx={{ display: 'none' }}>
<Label>Custom Style(Don&apos;t paste any suspicious scripts!!)</Label>
<Textarea
value={customStyle}
onChange={(event) => {
setCustomStyle(event.target.value)
}}
/>
</Box>
</Box>
</Box>
)
}
export default ERGuideGenerator
@@ -0,0 +1,136 @@
import { Box, Flex, Image } from 'theme-ui'
import { assetsBucketBaseUrl } from '../../../lib/consts'
import { StigmataData } from '../../../lib/honkai3rd/stigmata'
import { WeaponData } from '../../../lib/honkai3rd/weapons'
import { ERGGEquipmentSet } from './types'
interface EquipmentBoxProps {
equipmentSets: ERGGEquipmentSet[]
weapons: WeaponData[]
stigmata: StigmataData[]
}
const EquipmentBox = ({
equipmentSets,
weapons,
stigmata,
}: EquipmentBoxProps) => {
return (
<Box
sx={{
padding: '5px',
border: 'solid 1px gray',
boxSizing: 'border-box',
backgroundColor: '#181614',
boxShadow: '5px 5px 10px rgba(0,0,0,0.5)',
}}
>
<Flex>
<Box
sx={{
backgroundColor: '#8E5B45',
p: '5px',
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
mr: '5px',
width: 55,
}}
>
<Box></Box>
<Box></Box>
</Box>
{equipmentSets.map((equipmentSet, index) => {
return (
<Box
key={index}
sx={{
backgroundColor: '#615559',
p: '15px 5px 0',
borderRadius: '5px',
position: 'relative',
mr: '5px',
'&:last-child': {
mr: 0,
},
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
backgroundColor:
equipmentSet.type === 'best' ? '#EE4E2C' : '#9082BD',
borderTopLeftRadius: '5px',
borderBottomRightRadius: '5px',
p: '0 7px',
fontSize: 12,
}}
>
{equipmentSet.type === 'best' ? '베스트' : '대체'}
</Box>
<Box sx={{ height: 40 }}>
<Image
alt=''
src={`${assetsBucketBaseUrl}/honkai3rd/weapons/${equipmentSet.weapon}.png`}
width={40}
height={40}
sx={{ borderRadius: '5px', mr: '5px' }}
/>
<Image
alt=''
src={`${assetsBucketBaseUrl}/honkai3rd/stigmata/icon-${equipmentSet.top}.png`}
width={40}
height={40}
sx={{ borderRadius: '5px', mr: '5px' }}
/>
<Image
alt=''
src={`${assetsBucketBaseUrl}/honkai3rd/stigmata/icon-${equipmentSet.mid}.png`}
width={40}
height={40}
sx={{ borderRadius: '5px', mr: '5px' }}
/>
<Image
alt=''
src={`${assetsBucketBaseUrl}/honkai3rd/stigmata/icon-${equipmentSet.bot}.png`}
width={40}
height={40}
sx={{ borderRadius: '5px' }}
/>
</Box>
<Flex
sx={{
height: 20,
fontSize: 12,
alignItems: 'center',
justifyContent: 'center',
}}
>
{getWeaponName(equipmentSet.weapon)}+
{getStigmataName(equipmentSet.top)}/
{getStigmataName(equipmentSet.mid)}/
{getStigmataName(equipmentSet.bot)}
</Flex>
</Box>
)
})}
</Flex>
</Box>
)
function getWeaponName(id: string) {
return weapons
.find((weapon) => weapon.id === id)
?.name.split(' ')[0]
.slice(0, 4)
.trim()
}
function getStigmataName(id: string) {
return stigmata.find((stigma) => stigma.id === id)?.name.slice(0, 3)
}
}
export default EquipmentBox
@@ -0,0 +1,150 @@
import { useCallback, useMemo } from 'react'
import { Box, Flex, Label } from 'theme-ui'
import { StigmataData } from '../../../lib/honkai3rd/stigmata'
import { WeaponData } from '../../../lib/honkai3rd/weapons'
import StigmaSelect from './StigmaSelect'
import { ERGGDataUpdater, ERGGEquipmentSet } from './types'
import WeaponSelect from './WeaponSelect'
interface EquipmentSetControlProps {
index: number
equipmentSet: ERGGEquipmentSet
updateData: ERGGDataUpdater
weapons: WeaponData[]
stigmata: StigmataData[]
topStigmaIds: string[]
midStigmaIds: string[]
botStigmaIds: string[]
}
const EquipmentSetControl = ({
index,
equipmentSet,
updateData,
weapons,
stigmata,
topStigmaIds,
midStigmaIds,
botStigmaIds,
}: EquipmentSetControlProps) => {
const handleWeaponChange = useCallback(
(newValue: string) => {
updateData('equipmentSets', (previousEquipmentSets) => {
const newEquipmentSets = previousEquipmentSets.slice()
newEquipmentSets[index] = {
...newEquipmentSets[index],
weapon: newValue,
}
return newEquipmentSets
})
},
[index, updateData]
)
const weaponIds = useMemo(() => weapons.map((weapon) => weapon.id), [weapons])
const handleTopStigmaChange = useCallback(
(newValue: string) => {
updateData('equipmentSets', (previousEquipmentSets) => {
const newEquipmentSets = previousEquipmentSets.slice()
newEquipmentSets[index] = {
...newEquipmentSets[index],
top: newValue,
}
return newEquipmentSets
})
},
[index, updateData]
)
const handleMidStigmaChange = useCallback(
(newValue: string) => {
updateData('equipmentSets', (previousEquipmentSets) => {
const newEquipmentSets = previousEquipmentSets.slice()
newEquipmentSets[index] = {
...newEquipmentSets[index],
mid: newValue,
}
return newEquipmentSets
})
},
[index, updateData]
)
const handleBotStigmaChange = useCallback(
(newValue: string) => {
updateData('equipmentSets', (previousEquipmentSets) => {
const newEquipmentSets = previousEquipmentSets.slice()
newEquipmentSets[index] = {
...newEquipmentSets[index],
bot: newValue,
}
return newEquipmentSets
})
},
[index, updateData]
)
return (
<Box>
<Box>{equipmentSet.type === 'best' ? '베스트' : '대체'}</Box>
<Flex sx={{ alignItems: 'center' }}>
<Box sx={{ mr: 2 }}>
<Label></Label>
</Box>
<Box sx={{ flexShrink: 0, flex: 1 }}>
<WeaponSelect
instanceId={`equipment-${index}-weapon`}
value={equipmentSet.weapon}
onChange={handleWeaponChange}
weapons={weapons}
optionIds={weaponIds}
/>
</Box>
</Flex>
<Flex sx={{ alignItems: 'center' }}>
<Box sx={{ mr: 2 }}>
<Label></Label>
</Box>
<Box sx={{ flexShrink: 0, flex: 1 }}>
<StigmaSelect
instanceId={`equipment-${index}-top`}
value={equipmentSet.top}
onChange={handleTopStigmaChange}
stigmata={stigmata}
optionIds={topStigmaIds}
/>
</Box>
</Flex>
<Flex sx={{ alignItems: 'center' }}>
<Box sx={{ mr: 2 }}>
<Label></Label>
</Box>
<Box sx={{ flexShrink: 0, flex: 1 }}>
<StigmaSelect
instanceId={`equipment-${index}-mid`}
value={equipmentSet.mid}
onChange={handleMidStigmaChange}
stigmata={stigmata}
optionIds={midStigmaIds}
/>
</Box>
</Flex>
<Flex sx={{ alignItems: 'center' }}>
<Box sx={{ mr: 2 }}>
<Label></Label>
</Box>
<Box sx={{ flexShrink: 0, flex: 1 }}>
<StigmaSelect
instanceId={`equipment-${index}-bot`}
value={equipmentSet.bot}
onChange={handleBotStigmaChange}
stigmata={stigmata}
optionIds={botStigmaIds}
/>
</Box>
</Flex>
</Box>
)
}
export default EquipmentSetControl
@@ -0,0 +1,116 @@
import { memo } from 'react'
import { Box, Flex, Image } from 'theme-ui'
import { assetsBucketBaseUrl } from '../../../lib/consts'
import { RemembranceSigil } from '../../../lib/honkai3rd/elysianRealm'
import { ERGGSigilSet } from './types'
interface SigilBoxProps {
sigilSets: ERGGSigilSet[]
sigils: RemembranceSigil[]
}
const SigilBox = ({ sigilSets, sigils }: SigilBoxProps) => {
return (
<Box
sx={{
padding: '5px',
border: 'solid 1px gray',
boxSizing: 'border-box',
backgroundColor: 'rgba(0,0,0,0.5)',
boxShadow: '5px 5px 10px rgba(0,0,0,0.5)',
}}
>
<Flex>
<Box
sx={{
backgroundColor: '#8E5B45',
p: '5px',
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
mr: '5px',
width: 55,
}}
>
<Box></Box>
<Box></Box>
</Box>
{sigilSets.map((sigilSet, index) => {
return (
<Box
key={index}
sx={{
backgroundColor: '#615559',
p: '15px 5px 0',
borderRadius: '5px',
position: 'relative',
mr: '5px',
'&:last-child': {
mr: 0,
},
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
backgroundColor:
sigilSet.type === 'start'
? '#666480'
: sigilSet.type === 'mid'
? '#8A6A5C'
: '#C06848',
borderTopLeftRadius: '5px',
borderBottomRightRadius: '5px',
p: '0 7px',
fontSize: 12,
}}
>
{sigilSet.type === 'start'
? '초반'
: sigilSet.type === 'mid'
? '중반'
: '후반'}
</Box>
<Box sx={{ height: 40 }}>
<Image
alt=''
src={`${assetsBucketBaseUrl}/honkai3rd/elysian-realm/remembrance-sigils/${sigilSet.sigilIds[0]}.png`}
width={40}
height={40}
sx={{ mr: '5px', borderRadius: 5 }}
/>
<Image
alt=''
src={`${assetsBucketBaseUrl}/honkai3rd/elysian-realm/remembrance-sigils/${sigilSet.sigilIds[1]}.png`}
width={40}
height={40}
sx={{ borderRadius: 5 }}
/>
</Box>
<Flex
sx={{
height: 20,
fontSize: 12,
alignItems: 'center',
justifyContent: 'center',
}}
>
{getSigilName(sigilSet.sigilIds[0])}+
{getSigilName(sigilSet.sigilIds[1])}
</Flex>
</Box>
)
})}
</Flex>
</Box>
)
function getSigilName(id: string) {
return sigils.find((sigil) => sigil.id === id)?.name.slice(0, 3)
}
}
export default memo(SigilBox)
@@ -0,0 +1,108 @@
import { RemembranceSigil } from '../../../lib/honkai3rd/elysianRealm'
import ReactSelect, { components } from 'react-select'
import { Flex, Image } from 'theme-ui'
import { memo, useMemo } from 'react'
import { assetsBucketBaseUrl } from '../../../lib/consts'
interface SigilSelectProps {
instanceId: string
value: string
optionIds: string[]
sigils: RemembranceSigil[]
onChange: (newValue: string) => void
}
const SigilSelect = ({
instanceId,
optionIds,
sigils,
onChange,
value,
}: SigilSelectProps) => {
const sigilOptions = useMemo(() => {
return optionIds.map((sigilId) => {
const sigil = sigils.find((aSigil) => {
return aSigil.id === sigilId
})
if (sigil == null) {
return {
value: 'unknown',
label: 'unknown',
}
}
return {
value: sigil.id,
label: sigil.name,
}
})
}, [optionIds, sigils])
const sigil = useMemo(
() =>
sigils.find((sigil) => {
return sigil.id === value
}),
[sigils, value]
)
return (
<ReactSelect
instanceId={instanceId}
value={
sigil != null
? {
label: sigil.name,
value: sigil.id,
}
: null
}
onChange={(option) => {
if (option == null) {
return
}
onChange(option.value)
}}
options={sigilOptions}
components={{
SingleValue: (props) => {
return (
<>
<components.SingleValue {...props}>
<Flex sx={{ alignItems: 'center', color: 'black' }}>
<Image
width={20}
height={20}
alt={props.data.label}
src={`${assetsBucketBaseUrl}/honkai3rd/elysian-realm/remembrance-sigils/${props.data.value}.png`}
mr={2}
/>
{props.children}
</Flex>
</components.SingleValue>
</>
)
},
Option: (props) => {
return (
<>
<components.Option {...props}>
<Flex sx={{ alignItems: 'center', color: 'black' }}>
<Image
width={20}
height={20}
alt={props.data.label}
src={`${assetsBucketBaseUrl}/honkai3rd/elysian-realm/remembrance-sigils/${props.data.value}.png`}
mr={2}
/>
{props.children}
</Flex>
</components.Option>
</>
)
},
}}
/>
)
}
export default memo(SigilSelect)
@@ -0,0 +1,156 @@
import React from 'react'
import { Box, Flex, Image, Paragraph } from 'theme-ui'
import { assetsBucketBaseUrl } from '../../../lib/consts'
import { signetGroupMap } from '../../../lib/honkai3rd/elysianRealm'
import { ERGGSignet } from './types'
interface SignetBoxProps {
signets: ERGGSignet[]
}
const SignetBox = ({ signets }: SignetBoxProps) => {
return (
<>
{signets.map((signet, index) => {
const dia = 460
const radius = dia / 2
const margin = 30
const descriptionTop = 40 + margin + ((dia - margin * 2) / 4) * index
const top =
40 + radius + radius * Math.sin((30 / 180) * Math.PI * (index - 2))
const angle = Math.asin((radius - (top - 40)) / radius)
const left = Math.cos(angle) * radius + 100 + radius
const signetRadius = 45
return (
<React.Fragment key={index}>
<Flex
sx={{
position: 'absolute',
zIndex: 1,
top: `${top - signetRadius}px`,
left: `${left - signetRadius}px`,
backgroundColor: '#10131C',
height: signetRadius * 2,
width: signetRadius * 2,
justifyContent: 'center',
alignItems: 'center',
borderRadius: signetRadius,
border: '1px solid gray',
boxShadow: '5px 5px 10px rgba(0,0,0,0.5)',
flexDirection: 'column',
}}
>
<Image
alt=''
src={`${assetsBucketBaseUrl}/honkai3rd/elysian-realm/signets/${signet.group}.png`}
width={45}
height={45}
/>
<Box
className='signetGroupLabel'
sx={{
transform: 'translateX(-3px)',
fontSize: 18,
}}
>
{getSignetAltName(signet.group)}
</Box>
<Box
sx={{
backgroundColor: 'black',
border: '1px solid gray',
width: 20,
height: 20,
textAlign: 'center',
borderRadius: 4,
position: 'absolute',
bottom: '12px',
fontSize: 16,
lineHeight: '20px',
right: `${5}px`,
}}
>
{signet.nexus === 1 ? 'I' : 'II'}
</Box>
</Flex>
<Box
sx={{
position: 'absolute',
borderBottom: '2px dashed gray',
height: 0,
top: `${top}px`,
left: `${left}px`,
right: 340,
}}
/>
<Flex
sx={{
top: `${descriptionTop - 40}px`,
height: 80,
border: 'solid 1px gray',
boxSizing: 'border-box',
backgroundColor: 'rgba(0,0,0,0.5)',
boxShadow: '5px 5px 10px rgba(0,0,0,0.5)',
width: 330,
position: 'absolute',
right: 10,
alignItems: 'center',
}}
>
<Flex
sx={{
width: 80,
height: 60,
borderRight: 'solid 1px gray',
justifyContent: 'center',
alignItems: 'center',
mr: '5px',
flexShrink: 0,
}}
>
<Box
sx={{
fontSize: 4,
lineHeight: 1.3,
fontWeight: 'bold',
color:
signet.type === 'core'
? '#E39070'
: signet.type === 'start'
? '#FEDEC2'
: '#A59A9B',
}}
className='signetTypeLabel'
>
<Box>
<Box>
{signet.type === 'core'
? '핵심'
: signet.type === 'start'
? '과도'
: '보조'}
</Box>
<Box></Box>
</Box>
</Box>
</Flex>
<Paragraph
className='signetDescription'
sx={{ padding: '5px', whiteSpace: 'pre-wrap' }}
>
{signet.description}
</Paragraph>
</Flex>
</React.Fragment>
)
})}
</>
)
}
export default SignetBox
function getSignetAltName(id: string) {
return signetGroupMap.get(id)?.krAltName
}
@@ -0,0 +1,82 @@
import { useMemo } from 'react'
import ReactSelect, { components } from 'react-select'
import { Flex, Image } from 'theme-ui'
import { assetsBucketBaseUrl } from '../../../lib/consts'
import { signetGroups } from '../../../lib/honkai3rd/elysianRealm'
interface SignetGroupSelectProps {
instanceId: string
value: string
onChange: (newValue: string) => void
}
const SignetGroupSelect = ({
instanceId,
value,
onChange,
}: SignetGroupSelectProps) => {
const options = useMemo(() => {
return signetGroups.slice(1).map((signetGroup) => {
return {
value: signetGroup.id,
label: signetGroup.krAltName,
}
})
}, [])
const currentOption = options.find((option) => option.value === value)
return (
<ReactSelect
instanceId={instanceId}
value={currentOption}
onChange={(option) => {
if (option == null) {
return
}
onChange(option.value)
}}
options={options}
components={{
SingleValue: (props) => {
return (
<>
<components.SingleValue {...props}>
<Flex sx={{ alignItems: 'center', color: 'black' }}>
<Image
width={20}
height={20}
alt={props.data.label}
src={`${assetsBucketBaseUrl}/honkai3rd/elysian-realm/signets/${props.data.value}.png`}
sx={{ flexShrink: 0, mr: 2 }}
/>
{props.children}
</Flex>
</components.SingleValue>
</>
)
},
Option: (props) => {
return (
<>
<components.Option {...props}>
<Flex sx={{ alignItems: 'center', color: 'black' }}>
<Image
width={20}
height={20}
alt={props.data.label}
src={`${assetsBucketBaseUrl}/honkai3rd/elysian-realm/signets/${props.data.value}.png`}
sx={{ mr: 2, flexShrink: 0 }}
/>
{props.children}
</Flex>
</components.Option>
</>
)
},
}}
/>
)
}
export default SignetGroupSelect
@@ -0,0 +1,108 @@
import ReactSelect, { components } from 'react-select'
import { Flex, Image } from 'theme-ui'
import { memo, useMemo } from 'react'
import { assetsBucketBaseUrl } from '../../../lib/consts'
import { StigmataData } from '../../../lib/honkai3rd/stigmata'
interface StigmaSelectProps {
instanceId: string
value: string
optionIds: string[]
stigmata: StigmataData[]
onChange: (newValue: string) => void
}
const StigmaSelect = ({
instanceId,
optionIds,
stigmata,
onChange,
value,
}: StigmaSelectProps) => {
const stigmaOptions = useMemo(() => {
return optionIds.map((stigmaId) => {
const stigma = stigmata.find((aStigma) => {
return aStigma.id === stigmaId
})
if (stigma == null) {
return {
value: 'unknown',
label: 'unknown',
}
}
return {
value: stigma.id,
label: stigma.name,
}
})
}, [optionIds, stigmata])
const stigma = useMemo(
() =>
stigmata.find((aStigma) => {
return aStigma.id === value
}),
[value, stigmata]
)
return (
<ReactSelect
instanceId={instanceId}
value={
stigma != null
? {
label: stigma.name,
value: stigma.id,
}
: null
}
onChange={(option) => {
if (option == null) {
return
}
onChange(option.value)
}}
options={stigmaOptions}
components={{
SingleValue: (props) => {
return (
<>
<components.SingleValue {...props}>
<Flex sx={{ alignItems: 'center', color: 'black' }}>
<Image
width={20}
height={20}
alt={props.data.label}
src={`${assetsBucketBaseUrl}/honkai3rd/stigmata/icon-${props.data.value}.png`}
sx={{ mr: 2, flexShrink: 0 }}
/>
{props.children}
</Flex>
</components.SingleValue>
</>
)
},
Option: (props) => {
return (
<>
<components.Option {...props}>
<Flex sx={{ alignItems: 'center', color: 'black' }}>
<Image
width={20}
height={20}
alt={props.data.label}
src={`${assetsBucketBaseUrl}/honkai3rd/stigmata/icon-${props.data.value}.png`}
sx={{ mr: 2, flexShrink: 0 }}
/>
{props.children}
</Flex>
</components.Option>
</>
)
},
}}
/>
)
}
export default memo(StigmaSelect)
@@ -0,0 +1,132 @@
import { Box, Flex, Image } from 'theme-ui'
import { assetsBucketBaseUrl } from '../../../lib/consts'
import { ERGGSupportSet } from './types'
interface SupportBoxProps {
supportSets: ERGGSupportSet[]
}
const SupportBox = ({ supportSets }: SupportBoxProps) => {
return (
<Box
sx={{
padding: '5px',
border: 'solid 1px gray',
boxSizing: 'border-box',
backgroundColor: 'rgba(0,0,0,0.5)',
boxShadow: '5px 5px 10px rgba(0,0,0,0.5)',
}}
>
<Flex>
<Box
sx={{
backgroundColor: '#8E5B45',
p: '5px',
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
width: 55,
mr: '5px',
}}
>
<Box></Box>
<Box></Box>
</Box>
{supportSets.map((supportSet, index) => {
return (
<Box
key={index}
sx={{
backgroundColor: '#615559',
p: '15px 5px 0',
borderRadius: '5px',
position: 'relative',
mr: '5px',
'&:last-child': {
mr: 0,
},
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
backgroundColor:
supportSet.type === 'util' ? '#356D7E' : '#EE4D2A',
borderTopLeftRadius: '5px',
borderBottomRightRadius: '5px',
p: '0 7px',
fontSize: 12,
}}
>
{supportSet.type === 'util' ? '유틸' : '딜링'}
</Box>
<Box sx={{ height: 40 }}>
{supportSet.battlesuitIds.map((battlesuitId) => {
return (
<Image
key={battlesuitId}
alt=''
src={`${assetsBucketBaseUrl}/honkai3rd/battlesuits/portrait-${battlesuitId}.png`}
width={40}
height={40}
sx={{
borderRadius: '5px',
mr: '5px',
'&:last-child': { mr: 0 },
}}
/>
)
})}
</Box>
<Flex
sx={{
height: 20,
fontSize: 12,
alignItems: 'center',
justifyContent: 'center',
}}
>
{getSupportName(supportSet.battlesuitIds[0])}+
{getSupportName(supportSet.battlesuitIds[1])}
</Flex>
</Box>
)
})}
</Flex>
</Box>
)
}
export default SupportBox
function getSupportName(id: string) {
switch (id) {
case 'le':
return '귀메'
case 'vc':
return '삐로냐'
case 'ss':
return '빙로냐'
case 'sn':
return '쩨레'
case 'br':
return '스메코'
case 'ae':
return '선인'
case 'hb':
return '브로니'
case 'ma':
return '레이븐'
case 'dp':
return '성녀'
case 'bke':
return '월백'
case 'rc':
return '파르도'
case 'sa':
return '테갈'
}
}
@@ -0,0 +1,149 @@
import { Box, Flex, Text } from 'theme-ui'
import { assetsBucketBaseUrl } from '../../../lib/consts'
import { colors } from './styles'
import { ERGGExSignet, ERGGExSignetType } from './types'
import { getExSignetLabel } from './utils'
interface ValkBoxProps {
battlesuitId: string
exSignets: ERGGExSignet[]
}
const ValkBox = ({ battlesuitId, exSignets }: ValkBoxProps) => {
return (
<Box
sx={{
position: 'absolute',
left: 100,
top: 40,
width: 460,
height: 460,
}}
>
{exSignets.map(({ type, name }, index) => {
return (
<ValkExSignetBox key={index} index={index} type={type} name={name} />
)
})}
<Box
sx={{
borderRadius: '50%',
border: 'solid 1px gray',
boxShadow: '5px 5px 10px rgba(0,0,0,0.5)',
width: 460,
height: 460,
backgroundImage: `url('${assetsBucketBaseUrl}/honkai3rd/battlesuits/${battlesuitId}.png')`,
backgroundSize: 'cover',
backgroundPosition: getBattlesuitImagePosition(battlesuitId),
backgroundRepeat: 'no-repeat',
backgroundColor: 'rgba(0,0,0,0.2)',
}}
></Box>
</Box>
)
}
function getBattlesuitImagePosition(battlesuitId: string) {
switch (battlesuitId) {
case 'meme':
return '0 10px'
default:
return 'center'
}
}
const ValkExSignetBox = ({
index,
type,
name,
}: {
index: number
type: ERGGExSignetType
name: string
}) => {
const label = getExSignetLabel(name)
const intervalAngle = 15
const angle = -intervalAngle * (index + 1) - 24
return (
<Box
sx={{
position: 'absolute',
top: 230 - 230 * Math.cos((angle / 180) * Math.PI) - 25,
left: 230 + 230 * Math.sin((angle / 180) * Math.PI) - 25,
width: 50,
height: 50,
}}
>
<Box
sx={{
position: 'absolute',
backgroundColor:
type === 'start'
? '#C52CC5'
: type === '1st'
? '#EE4D2A'
: type === '2nd'
? '#9082BD'
: type === 'backup'
? '#C4C4C4'
: 'transparent',
borderWidth: 1,
borderStyle: 'solid',
color: type === 'backup' ? 'black' : '#fff',
borderColor: type === 'na' ? '#AA9FA3' : 'transparent',
top: '5px',
right: 25,
whiteSpace: 'nowrap',
paddingRight: '30px',
paddingLeft: '10px',
textAlign: 'right',
lineHeight: 1.3,
zIndex: 1,
borderTopLeftRadius: '4px',
borderBottomLeftRadius: '4px',
}}
>
{type === 'start'
? '시작'
: type === '1st'
? '우선'
: type === '2nd'
? '차선'
: type === 'backup'
? '땜빵'
: '미선택'}
</Box>
<Flex
sx={{
position: 'absolute',
zIndex: 2,
border: 'solid 1px gray',
borderRadius: '50%',
backgroundColor: colors.backgroundColor,
boxShadow: '5px 5px 10px rgba(0,0,0,0.5)',
alignItems: 'center',
justifyContent: 'center',
fontSize:
label.length > 3
? label.indexOf('\n') >= 0
? '14px'
: '11px'
: label.length > 2
? 16
: 20,
width: 50,
height: 50,
lineHeight: 1.2,
textAlign: 'center',
whiteSpace: 'pre',
}}
className='exSignetLabel'
>
<Text>{label}</Text>
</Flex>
</Box>
)
}
export default ValkBox
@@ -0,0 +1,105 @@
import ReactSelect, { components } from 'react-select'
import { Flex, Image } from 'theme-ui'
import { memo, useMemo } from 'react'
import { assetsBucketBaseUrl } from '../../../lib/consts'
import { WeaponData } from '../../../lib/honkai3rd/weapons'
interface WeaponSelectProps {
instanceId: string
value: string
optionIds: string[]
weapons: WeaponData[]
onChange: (newValue: string) => void
}
const WeaponSelect = ({
instanceId,
optionIds,
weapons,
onChange,
value,
}: WeaponSelectProps) => {
const weaponOptions = useMemo(() => {
return optionIds.map((weaponId) => {
const weapon = weapons.find((aWeapon) => {
return aWeapon.id === weaponId
})
if (weapon == null) {
return {
value: 'unknown',
label: 'unknown',
}
}
return {
value: weapon.id,
label: weapon.name,
}
})
}, [optionIds, weapons])
const weaponValue = useMemo(() => {
const weapon = weapons.find((aWeapon) => {
return aWeapon.id === value
})
return weapon != null
? {
label: weapon.name,
value: weapon.id,
}
: null
}, [value, weapons])
return (
<ReactSelect
instanceId={instanceId}
value={weaponValue}
onChange={(option) => {
if (option == null) {
return
}
onChange(option.value)
}}
options={weaponOptions}
components={{
SingleValue: (props) => {
return (
<>
<components.SingleValue {...props}>
<Flex sx={{ alignItems: 'center', color: 'black' }}>
<Image
width={20}
height={20}
alt={props.data.label}
src={`${assetsBucketBaseUrl}/honkai3rd/weapons/${props.data.value}.png`}
sx={{ mr: 2, flexShrink: 0 }}
/>
{props.children}
</Flex>
</components.SingleValue>
</>
)
},
Option: (props) => {
return (
<>
<components.Option {...props}>
<Flex sx={{ alignItems: 'center', color: 'black' }}>
<Image
width={20}
height={20}
alt={props.data.label}
src={`${assetsBucketBaseUrl}/honkai3rd/weapons/${props.data.value}.png`}
sx={{ mr: 2, flexShrink: 0 }}
/>
{props.children}
</Flex>
</components.Option>
</>
)
},
}}
/>
)
}
export default memo(WeaponSelect)
@@ -0,0 +1,6 @@
export const colors = {
backgroundColor: '#311E22',
abstinenceDifficultyColor: '#F09F85',
corruptionDifficultyColor: '#EE4E2C',
infernoDifficultyColor: '#9DA3D3',
}
@@ -0,0 +1,51 @@
export type ERGGExSignetType = 'start' | '1st' | '2nd' | 'backup' | 'na'
export interface ERGGExSignet {
type: ERGGExSignetType
name: string
}
export type ERGGDataUpdater = <T extends keyof ERGGData>(
key: T,
value: ERGGData[T] | ((previousData: ERGGData[T]) => ERGGData[T])
) => void
export interface ERGGSupportSet {
type: 'util' | 'dps'
battlesuitIds: [string, string]
}
export interface ERGGSigilSet {
type: 'start' | 'mid' | 'end'
sigilIds: [string, string]
}
export type ERGGDifficulty = 'abstinence' | 'corruption' | 'inferno'
export interface ERGGEquipmentSet {
type: 'best' | 'alt'
weapon: string
top: string
mid: string
bot: string
}
export interface ERGGSignet {
group: string
type: 'core' | 'start' | 'sub'
nexus: 1 | 2
description: string
}
export type ERGGData = {
rank?: string
tag: string
signature: string
battlesuitId: string
difficulty: ERGGDifficulty
exSignets: ERGGExSignet[]
supportSets: ERGGSupportSet[]
sigilSets: ERGGSigilSet[]
equipmentSets: ERGGEquipmentSet[]
signets: ERGGSignet[]
}
@@ -0,0 +1,6 @@
export function getExSignetLabel(name: string) {
return name
.replace('의 축복', '')
.replace(/\s/g, '\n')
.replace(/[\[\]]/g, '')
}
+22
View File
@@ -113,6 +113,26 @@ export const remembranceSigilIds = [
'key-to-the-deep',
]
export function isGeneralSigil(sigilId: string) {
switch (sigilId) {
case 'it-will-be-written':
case 'dreamful-gold':
case 'an-old-pal-s-legacy':
case 'empty-like-shala':
case 'tsukimi-himiko':
case 'boundless-logos':
case 'hometown':
case 'because-of-you':
case 'boundless-feeling':
case 'falling-in-past-light':
case 'out-of-reach':
case 'the-lonely-moon':
case 'awakening':
return false
}
return true
}
export interface SignetData {
id: string
name: string
@@ -127,6 +147,8 @@ export interface SignetGroup {
id: string
name: string
krName: string
altName: string
krAltName: string
setIds: string[]
}
+8
View File
@@ -121,6 +121,11 @@ export const theme: Theme = {
lineHeight: 'heading',
},
},
forms: {
checkbox: {
color: 'border',
},
},
buttons: {
primary: {
color: 'primary',
@@ -144,6 +149,9 @@ export const theme: Theme = {
'&.hidden': {
display: 'none',
},
'&:disabled': {
opacity: 0.5,
},
},
secondary: {
color: 'background',
+7 -1
View File
@@ -4,7 +4,6 @@ import Breadcrumb from '../../../components/organisms/Breadcrumb'
import { getBattlesuitMapByIds } from '../../../server/data/honkai3rd/battlesuits'
import { BattlesuitData } from '../../../lib/honkai3rd/battlesuits'
import BattlesuitCard from '../../../components/molecules/BattlesuitCard'
import { NextPageContext } from 'next'
import { getI18NProps } from '../../../server/i18n'
import { useTranslation } from '../../../lib/i18n'
@@ -19,6 +18,7 @@ import {
getRemembranceSigils as getRemembranceSigils,
getSupportBattlesuits,
} from '../../../server/data/honkai3rd/elysianRealm'
import Link from 'next/link'
type BattlesuitListItemData = Pick<
BattlesuitData,
@@ -152,6 +152,11 @@ const ElysianRealmIndexPage = ({
})}
</Flex>
</Box>
<Box>
<Link href='/honkai3rd/elysian-realm/utils/gen-guide'>
Guide Generator (KR Only)
</Link>
</Box>
</Box>
</Honkai3rdLayout>
)
@@ -180,6 +185,7 @@ export async function getStaticProps({ locale }: NextPageContext) {
name,
}
})
return {
props: {
battlesuitMap: getBattlesuitMapByIds(erBattlesuitIds, locale),
@@ -0,0 +1,88 @@
/* eslint-disable @next/next/no-page-custom-font */
import { NextPageContext } from 'next'
import { useRouter } from 'next/router'
import { pick } from 'ramda'
import { Box, Button, Flex, Heading } from 'theme-ui'
import ERGuideGenerator from '../../../../components/organisms/ERGuideGenerator/ERGuideGenerator'
import { BattlesuitData } from '../../../../lib/honkai3rd/battlesuits'
import {
PopulatedSignetGroup,
RemembranceSigil,
} from '../../../../lib/honkai3rd/elysianRealm'
import { WeaponData } from '../../../../lib/honkai3rd/weapons'
import { StigmataData } from '../../../../lib/honkai3rd/stigmata'
import { listBattlesuits } from '../../../../server/data/honkai3rd/battlesuits'
import {
getRemembranceSigils,
getSignetGroupById,
} from '../../../../server/data/honkai3rd/elysianRealm'
import { listStigmata } from '../../../../server/data/honkai3rd/stigmata'
import { listWeapons } from '../../../../server/data/honkai3rd/weapons'
import { getI18NProps } from '../../../../server/i18n'
interface GenerateGuidePageProps {
weapons: WeaponData[]
stigmata: StigmataData[]
battlesuits: BattlesuitData[]
exSignetGroup: PopulatedSignetGroup
sigils: RemembranceSigil[]
}
const GenerateGuidePage = ({
weapons,
stigmata,
battlesuits,
exSignetGroup,
sigils,
}: GenerateGuidePageProps) => {
const router = useRouter()
return (
<Box>
<Flex m={2} sx={{ alignItems: 'center' }}>
<Button
onClick={() => {
router.push('/honkai3rd')
}}
mr={2}
>
Back
</Button>
<Heading>ER Guide Generator</Heading>
</Flex>
<ERGuideGenerator
weapons={weapons}
stigmata={stigmata}
battlesuits={battlesuits}
exSignetGroup={exSignetGroup}
sigils={sigils}
/>
</Box>
)
}
export default GenerateGuidePage
export async function getStaticProps({ locale }: NextPageContext) {
return {
props: {
weapons: listWeapons('ko-KR').map((weapon) => {
return pick(['name', 'id', 'rarity', 'category'], weapon)
}),
stigmata: listStigmata('ko-KR').map((stigmata) => {
return pick(['name', 'id', 'type'], stigmata)
}),
battlesuits: listBattlesuits('ko-KR').map((battlesuit) => {
return {
id: battlesuit.id,
name: battlesuit.name,
type: battlesuit.type,
valkyrie: battlesuit.valkyrie,
}
}),
exSignetGroup: getSignetGroupById('elysia', 'ko-KR'),
sigils: getRemembranceSigils('ko-KR'),
...(await getI18NProps('ko-KR')),
},
}
}