update for v4

This commit is contained in:
2025-04-27 13:53:19 +07:00
parent e1e876a4e8
commit 6a9731b882
15 changed files with 470 additions and 170 deletions

View File

@@ -1,75 +0,0 @@
{
"TabTitle": {
"title": "Firefly 分析器",
"description": "Veritas 分析工具"
},
"DataAnalysisPage": {
"useSkill": "使用技能",
"totalDamage": "总伤害",
"damagePerAV": "每行动值伤害",
"totalAV": "总行动值",
"damagerPerCycle": "每轮伤害",
"skillType": "技能类型",
"skillName": "技能名称",
"actionValue": "行动值",
"character": "角色",
"id": "ID",
"path": "命途",
"rarity": "稀有度",
"damageByActionValue": "此行动值的伤害",
"element": "属性",
"totalTurn": "总轮次",
"technique": "密技",
"talent": "天赋",
"basic": "普攻",
"skill": "战技",
"ultimate": "终结技",
"servant": "忆灵",
"skillDamageBreakdown": "技能伤害细分",
"skillUsageDistribution": "技能使用分布",
"damageOverTime": "累计伤害",
"damage": "伤害",
"cumulativeDamage": "累计伤害量",
"characterInformation": "角色信息",
"turnDetail": "轮次详情",
"damageDetails": "伤害详情",
"cycleCount": "轮次",
"chartInfo": "图表信息",
"actionBar": "行动轴",
"lineupInfo": "阵容信息",
"loadData": "加载战斗数据",
"exportData": "导出战斗数据",
"connectSetting": "连接设置",
"connected": "已连接",
"unconnected": "未连接",
"socketConnection": "socket连接",
"connectionType": "连接类型",
"status": "状态",
"connect": "连接",
"checkGameConnect": "检查游戏连接",
"other": "其他",
"host": "主机",
"port": "端口",
"hostPlaceHolder": "输入主机地址",
"portPlaceHolder": "输入端口号",
"noDamageDetail": "没有可用的伤害详情",
"noCharactersInLineup": "队伍中没有角色",
"noTurns": "尚未有回合",
"style": "风格",
"warrior": "毁灭",
"knight": "存护",
"mage": "智识",
"priest": "丰饶",
"rouge": "巡猎",
"shaman": "同协",
"warlock": "虚无",
"memory": "记忆",
"fire": "火",
"ice": "冰",
"imaginary": "虚数",
"physical": "物理",
"quantum": "量子",
"thunder": "雷",
"wind": "风"
}
}

View File

@@ -55,7 +55,7 @@
"noDamageDetail": "No damage details available", "noDamageDetail": "No damage details available",
"noCharactersInLineup": "No characters in lineup", "noCharactersInLineup": "No characters in lineup",
"noTurns": "No turns yet", "noTurns": "No turns yet",
"style": "Style", "type": "Type",
"warrior": "Destruction", "warrior": "Destruction",
"knight": "Preservation", "knight": "Preservation",
"mage": "Erudition", "mage": "Erudition",

View File

@@ -55,7 +55,7 @@
"noDamageDetail": "ダメージの詳細は利用できません", "noDamageDetail": "ダメージの詳細は利用できません",
"noCharactersInLineup": "編成にキャラクターがいません", "noCharactersInLineup": "編成にキャラクターがいません",
"noTurns": "まだターンがありません", "noTurns": "まだターンがありません",
"style": "タイ", "type": "タイ",
"warrior": "破壊", "warrior": "破壊",
"knight": "保護", "knight": "保護",
"mage": "博識", "mage": "博識",

View File

@@ -55,7 +55,7 @@
"noDamageDetail": "데미지 세부 정보가 없습니다", "noDamageDetail": "데미지 세부 정보가 없습니다",
"noCharactersInLineup": "라인업에 캐릭터가 없습니다", "noCharactersInLineup": "라인업에 캐릭터가 없습니다",
"noTurns": "아직 턴이 없습니다", "noTurns": "아직 턴이 없습니다",
"style": "스타일", "type": "타입 ",
"warrior": "파괴", "warrior": "파괴",
"knight": "보존", "knight": "보존",
"mage": "지식", "mage": "지식",

View File

@@ -55,7 +55,7 @@
"noDamageDetail": "Không có chi tiết sát thương", "noDamageDetail": "Không có chi tiết sát thương",
"noCharactersInLineup": "Không có nhân vật trong đội hình", "noCharactersInLineup": "Không có nhân vật trong đội hình",
"noTurns": "Chưa có lượt hành động", "noTurns": "Chưa có lượt hành động",
"style": "Kiểu", "type": "Loại",
"warrior": "Hủy Diệt", "warrior": "Hủy Diệt",
"knight": "Bảo Hộ", "knight": "Bảo Hộ",
"mage": "Tri Thức", "mage": "Tri Thức",

View File

@@ -18,14 +18,14 @@
"rarity": "稀有度", "rarity": "稀有度",
"damageByActionValue": "此行动值的伤害", "damageByActionValue": "此行动值的伤害",
"element": "属性", "element": "属性",
"totalTurn": "总轮次", "totalTurn": "总回合",
"technique": "密技", "technique": "密技",
"talent": "天赋", "talent": "天赋",
"basic": "普攻", "basic": "普攻",
"skill": "战技", "skill": "战技",
"ultimate": "终结技", "ultimate": "终结技",
"servant": "忆灵", "servant": "忆灵",
"skillDamageBreakdown": "技能伤害细分", "skillDamageBreakdown": "技能类别",
"skillUsageDistribution": "技能使用分布", "skillUsageDistribution": "技能使用分布",
"damageOverTime": "累计伤害", "damageOverTime": "累计伤害",
"damage": "伤害", "damage": "伤害",
@@ -55,7 +55,7 @@
"noDamageDetail": "没有可用的伤害详情", "noDamageDetail": "没有可用的伤害详情",
"noCharactersInLineup": "队伍中没有角色", "noCharactersInLineup": "队伍中没有角色",
"noTurns": "尚未有回合", "noTurns": "尚未有回合",
"style": "风格", "type": "类型 ",
"warrior": "毁灭", "warrior": "毁灭",
"knight": "存护", "knight": "存护",
"mage": "智识", "mage": "智识",

331
package-lock.json generated
View File

@@ -24,6 +24,7 @@
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/jest": "^29.5.14",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
@@ -47,6 +48,31 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@babel/code-frame": {
"version": "7.26.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.25.9",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@emnapi/core": { "node_modules/@emnapi/core": {
"version": "1.4.3", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
@@ -759,6 +785,50 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
} }
}, },
"node_modules/@jest/expect-utils": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
"integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
"dev": true,
"license": "MIT",
"dependencies": {
"jest-get-type": "^29.6.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/schemas": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
"integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.27.8"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/types": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
"integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jest/schemas": "^29.6.3",
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^3.0.0",
"@types/node": "*",
"@types/yargs": "^17.0.8",
"chalk": "^4.0.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@kurkle/color": { "node_modules/@kurkle/color": {
"version": "0.3.4", "version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
@@ -1399,6 +1469,13 @@
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
"dev": true,
"license": "MIT"
},
"node_modules/@socket.io/component-emitter": { "node_modules/@socket.io/component-emitter": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
@@ -1706,6 +1783,44 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/istanbul-lib-report": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
"integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/istanbul-lib-coverage": "*"
}
},
"node_modules/@types/istanbul-reports": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
"integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/istanbul-lib-report": "*"
}
},
"node_modules/@types/jest": {
"version": "29.5.14",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz",
"integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"expect": "^29.0.0",
"pretty-format": "^29.0.0"
}
},
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.15", "version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -1750,6 +1865,30 @@
"@types/react": "^19.0.0" "@types/react": "^19.0.0"
} }
}, },
"node_modules/@types/stack-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/yargs": {
"version": "17.0.33",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
"integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/yargs-parser": "*"
}
},
"node_modules/@types/yargs-parser": {
"version": "21.0.3",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.30.1", "version": "8.30.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz",
@@ -2657,6 +2796,22 @@
"chart.js": ">=3.0.0" "chart.js": ">=3.0.0"
} }
}, },
"node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/client-only": { "node_modules/client-only": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -2894,6 +3049,16 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/diff-sequences": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
"integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/doctrine": { "node_modules/doctrine": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -3595,6 +3760,23 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/expect": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
"integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jest/expect-utils": "^29.7.0",
"jest-get-type": "^29.6.3",
"jest-matcher-utils": "^29.7.0",
"jest-message-util": "^29.7.0",
"jest-util": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4529,6 +4711,87 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/jest-diff": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
"integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "^4.0.0",
"diff-sequences": "^29.6.3",
"jest-get-type": "^29.6.3",
"pretty-format": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-get-type": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
"integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-matcher-utils": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
"integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "^4.0.0",
"jest-diff": "^29.7.0",
"jest-get-type": "^29.6.3",
"pretty-format": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-message-util": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
"integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.12.13",
"@jest/types": "^29.6.3",
"@types/stack-utils": "^2.0.0",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.9",
"micromatch": "^4.0.4",
"pretty-format": "^29.7.0",
"slash": "^3.0.0",
"stack-utils": "^2.0.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-util": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
"integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3",
"@types/node": "*",
"chalk": "^4.0.0",
"ci-info": "^3.2.0",
"graceful-fs": "^4.2.9",
"picomatch": "^2.2.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jiti": { "node_modules/jiti": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
@@ -5467,6 +5730,41 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jest/schemas": "^29.6.3",
"ansi-styles": "^5.0.0",
"react-is": "^18.0.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/pretty-format/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/pretty-format/node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"dev": true,
"license": "MIT"
},
"node_modules/prop-types": { "node_modules/prop-types": {
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -5954,6 +6252,16 @@
"is-arrayish": "^0.3.1" "is-arrayish": "^0.3.1"
} }
}, },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/socket.io-client": { "node_modules/socket.io-client": {
"version": "4.8.1", "version": "4.8.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
@@ -6032,6 +6340,29 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/stack-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
"integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"escape-string-regexp": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/stack-utils/node_modules/escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/streamsearch": { "node_modules/streamsearch": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",

View File

@@ -25,6 +25,7 @@
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/jest": "^29.5.14",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",

View File

@@ -44,14 +44,14 @@ export default function Home() {
}, [expandedCharts]); }, [expandedCharts]);
return ( return (
<div className="flex flex-col h-full w-full mt-5 min-h-[74vh] bg-base-100 font-[family-name:var(--font-geist-sans)]"> <div className="flex flex-col px-2 h-full w-full mt-5 min-h-[74vh] bg-base-100 font-[family-name:var(--font-geist-sans)]">
<div className="h-full"> <div className="h-full">
<div className="grid grid-cols-12 gap-2 lg:gap-3 h-full min-h-full"> <div className="grid grid-cols-12 gap-2 lg:gap-3 h-full min-h-full">
<div className="col-span-12 md:col-span-3 lg:col-span-2 xl:col-span-2 h-full"> <div className="col-span-12 md:col-span-3 lg:col-span-2 xl:col-span-2 h-full">
<ActionBar /> <ActionBar />
</div> </div>
<div className="col-span-12 md:col-span-6 lg:col-span-8 xl:col-span-8 flex flex-col h-full"> <div className="col-span-12 md:col-span-6 lg:col-span-8 xl:col-span-8 max-h-[90vh] flex flex-col h-full overflow-auto">
<div className="grid grid-cols-3 gap-2 mb-3"> <div className="grid grid-cols-3 gap-2 mb-3">
<div className="p-2 text-base lg:text-lg xl:text-xl rounded bg-primary text-primary-content text-center shadow-md"> <div className="p-2 text-base lg:text-lg xl:text-xl rounded bg-primary text-primary-content text-center shadow-md">
{transI18n("totalDamage")}: {Number(totalDamage).toFixed(2)} {transI18n("totalDamage")}: {Number(totalDamage).toFixed(2)}
@@ -64,7 +64,7 @@ export default function Home() {
</div> </div>
</div> </div>
<div className="bg-base-200 rounded-lg p-2 shadow-md flex-grow"> <div className="rounded-lg p-2 shadow-md flex-grow">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{expandedCharts.includes('chart1') ? ( {expandedCharts.includes('chart1') ? (
@@ -112,7 +112,7 @@ export default function Home() {
onClick={() => setModeLine(m as 0 | 1)} onClick={() => setModeLine(m as 0 | 1)}
className={`btn btn-sm ${modeLine === m ? "btn-accent" : "btn-ghost"}`} className={`btn btn-sm ${modeLine === m ? "btn-accent" : "btn-ghost"}`}
> >
{transI18n("style")} {m} {transI18n("type")} {m}
</button> </button>
))} ))}
</div> </div>
@@ -120,7 +120,7 @@ export default function Home() {
</div> </div>
{expandedCharts.includes('chart3') ? ( {expandedCharts.includes('chart3') ? (
<div key="chart3-expanded" className="lg:col-span-2 bg-base-200 rounded-lg p-2 shadow-md relative"> <div key="chart3-expanded" className="lg:col-span-2 max-h-[70vh] bg-base-200 rounded-lg p-2 shadow-md relative">
<div className="absolute top-2 left-2 z-10"> <div className="absolute top-2 left-2 z-10">
<button <button
className="btn btn-sm btn-circle btn-ghost" className="btn btn-sm btn-circle btn-ghost"
@@ -132,7 +132,7 @@ export default function Home() {
<DamagePercentChartForAll /> <DamagePercentChartForAll />
</div> </div>
) : ( ) : (
<div key="chart3-normal" className="bg-base-200 rounded-lg p-2 shadow-md relative"> <div key="chart3-normal" className="bg-base-200 max-h-[40vh] rounded-lg p-2 shadow-md relative">
<div className="absolute top-2 left-2 z-10"> <div className="absolute top-2 left-2 z-10">
<button <button
className="btn btn-sm btn-circle btn-ghost" className="btn btn-sm btn-circle btn-ghost"
@@ -164,7 +164,7 @@ export default function Home() {
onClick={() => setModeBar(m as 0 | 1 | 2)} onClick={() => setModeBar(m as 0 | 1 | 2)}
className={`btn btn-sm ${modeBar === m ? "btn-accent" : "btn-ghost"}`} className={`btn btn-sm ${modeBar === m ? "btn-accent" : "btn-ghost"}`}
> >
{transI18n("style")} {m} {transI18n("type")} {m}
</button> </button>
))} ))}
</div> </div>

View File

@@ -19,13 +19,9 @@ export default function ActionBar() {
const transI18n = useTranslations("DataAnalysisPage"); const transI18n = useTranslations("DataAnalysisPage");
const turnListRef = useRef<HTMLDivElement>(null); const turnListRef = useRef<HTMLDivElement>(null);
const parallelogramStyle: React.CSSProperties = {
transform: 'skew(-9deg)',
overflow: 'hidden',
};
const contentStyle: React.CSSProperties = { const contentStyle: React.CSSProperties = {
transform: 'skew(9deg)',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
@@ -71,7 +67,7 @@ export default function ActionBar() {
}, [turnHistory.length]); }, [turnHistory.length]);
return ( return (
<div className="p-4 rounded-lg shadow-lg w-full h-full min-h-[74vh]"> <div className="p-4 md:p-1 rounded-lg shadow-lg w-full h-full">
<motion.h2 <motion.h2
className="text-center text-xl lg:text-2xl mb-2 font-bold text-transparent bg-clip-text bg-gradient-to-r from-pink-500 via-purple-500 to-cyan-500" className="text-center text-xl lg:text-2xl mb-2 font-bold text-transparent bg-clip-text bg-gradient-to-r from-pink-500 via-purple-500 to-cyan-500"
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, y: -20 }}
@@ -82,43 +78,43 @@ export default function ActionBar() {
</motion.h2> </motion.h2>
<div <div
ref={turnListRef} ref={turnListRef}
className="flex px-2 w-full max-h-[90vh] pt-2 border-t-2 border-accent overflow-y-auto custom-scrollbar overflow-x-hidden" className="flex md:block px-2 md:px-0 w-full pt-2 border-t-2 border-accent overflow-x-auto md:overflow-x-hidden md:overflow-y-auto max-h-[90vh] custom-scrollbar"
> >
<style jsx>{` <style jsx>{`
.custom-scrollbar { .custom-scrollbar {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: hsl(var(--p)) hsl(var(--b3)); scrollbar-color: hsl(var(--p)) hsl(var(--b3));
} }
.custom-scrollbar::-webkit-scrollbar { .custom-scrollbar::-webkit-scrollbar {
width: 8px; width: 8px;
height: 8px; height: 8px;
} }
.custom-scrollbar::-webkit-scrollbar-track { .custom-scrollbar::-webkit-scrollbar-track {
background: hsl(var(--b3)); background: hsl(var(--b3));
border-radius: 10px; border-radius: 10px;
} }
.custom-scrollbar::-webkit-scrollbar-thumb { .custom-scrollbar::-webkit-scrollbar-thumb {
background: hsl(var(--p)); background: hsl(var(--p));
border-radius: 10px; border-radius: 10px;
} }
.custom-scrollbar::-webkit-scrollbar-thumb:hover { .custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: hsl(var(--pf)); background: hsl(var(--pf));
} }
.custom-scrollbar::-webkit-scrollbar-button { .custom-scrollbar::-webkit-scrollbar-button {
display: none; display: none;
height: 0; height: 0;
width: 0; width: 0;
} }
`}</style> `}</style>
<div className="w-full h-fit grid grid-cols-1 gap-1 "> <div className="flex flex-nowrap md:grid md:grid-cols-1 gap-2 w-fit md:w-full">
{turnHistory.length === 0 ? ( {turnHistory.length === 0 ? (
<div className="flex items-center justify-center h-full"> <div className="flex items-center justify-center h-full w-full">
<p className="text-base-content opacity-50">{transI18n("noTurns")}</p> <p className="text-base-content opacity-50">{transI18n("noTurns")}</p>
</div> </div>
) : ( ) : (
@@ -128,45 +124,48 @@ export default function ActionBar() {
const text = getNameChar(locale, data); const text = getNameChar(locale, data);
return ( return (
<div key={index}> <div key={index} className="h-full md:w-full">
<div <div
onClick={() => handleShow("action_detail_modal", data, turn)} onClick={() => handleShow("action_detail_modal", data, turn)}
style={parallelogramStyle} className="h-full grid grid-cols-2 gap-2 border bg-base-100 w-full hover:bg-base-200 transition-colors duration-200 border-cyan-400 border-l-4 cursor-pointer min-w-[200px] sm:min-w-[250px] md:min-w-0"
className="flex border bg-base-100 w-full hover:bg-base-200 transition-colors duration-200 border-cyan-400 border-l-4 cursor-pointer"
> >
<div <div
style={contentStyle} style={contentStyle}
className="flex flex-col items-center justify-center py-2 px-3" className="lg:col-span-1 grid grid-cols-1 items-center justify-center py-2"
> >
<div className="avatar"> <div className="avatar">
<div className="w-12 h-12 rounded-full border-2 flex items-center justify-center bg-base-300 border-cyan-400 border-l-4"> <div className="w-12 h-12 rounded-full border-2 flex items-center justify-center bg-base-300 border-cyan-400 border-l-4">
<img <img
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${data.id}.webp`} src={`https://api.hakush.in/hsr/UI/avatarshopicon/${data.id}.webp`}
alt={text} alt={text}
loading="lazy"
className="w-8 h-8 object-contain" className="w-8 h-8 object-contain"
/> />
</div> </div>
</div> </div>
<div className="text-base-content text-sm mt-1 font-medium">{text}</div>
<div className="text-base-content text-center text-sm mt-1 font-medium">{getNameChar(locale, data)}</div>
</div> </div>
<div className="flex flex-col justify-center gap-2 px-3 py-2"> <div className="grid grid-cols-1 justify-center gap-2 py-2 w-full">
<div className="text-primary text-xs"> <div className="bg-local text-primary text-xs max-w-full">
{`${transI18n("useSkill")}: ${turn.skillType}`} {`${transI18n("useSkill")}: ${transI18n(turn.skillType.toLowerCase())}`}
</div> </div>
<div className="text-primary text-xs"> <div className="text-primary text-xs max-w-full">
{`${transI18n("totalDamage")}: ${turn.totalDamage.toFixed(2)}`} {`${transI18n("totalDamage")}: ${turn.totalDamage.toFixed(2)}`}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); );
}) })
)} )}
</div> </div>
</div> </div>
{/* Character Detail Modal */} {/* Character Detail Modal */}
<dialog id="action_detail_modal" className="modal modal-bottom sm:modal-middle backdrop-blur-sm"> <dialog id="action_detail_modal" className="modal sm:modal-middle backdrop-blur-sm">
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10"> <div className="sticky top-0 z-10">
<motion.button <motion.button
@@ -181,7 +180,7 @@ export default function ActionBar() {
<div className="border-b border-purple-500/30 px-6 py-4 mb-4"> <div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400"> <h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
{transI18n("turnDetail").toUpperCase()} {transI18n("turnDetail").toUpperCase()}
</h3> </h3>
</div> </div>
@@ -207,6 +206,7 @@ export default function ActionBar() {
</div> </div>
<div className="flex justify-center items-center"> <div className="flex justify-center items-center">
<img <img
loading="lazy"
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${selectAvatar.id}.webp`} src={`https://api.hakush.in/hsr/UI/avatarshopicon/${selectAvatar.id}.webp`}
alt={getNameChar(locale, selectAvatar)} alt={getNameChar(locale, selectAvatar)}
className="h-20 w-20 object-cover rounded-full border-2 border-purple-500 shadow-lg shadow-purple-500/20" className="h-20 w-20 object-cover rounded-full border-2 border-purple-500 shadow-lg shadow-purple-500/20"

View File

@@ -2,6 +2,7 @@
import { exportBattleData, importBattleData } from "@/helper"; import { exportBattleData, importBattleData } from "@/helper";
import { useChangeTheme } from "@/hooks/useChangeTheme"; import { useChangeTheme } from "@/hooks/useChangeTheme";
import { checkConnectTcpApi } from "@/lib/api"; import { checkConnectTcpApi } from "@/lib/api";
import { listCurrentLanguage } from "@/lib/constant";
import { connectSocket, disconnectSocket, getSocket, isSocketConnected } from "@/lib/socket"; import { connectSocket, disconnectSocket, getSocket, isSocketConnected } from "@/lib/socket";
import useBattleDataStore from "@/stores/battleDataStore"; import useBattleDataStore from "@/stores/battleDataStore";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
@@ -38,7 +39,9 @@ export default function Header() {
const [message, setMessage] = useState({ text: '', type: '' }); const [message, setMessage] = useState({ text: '', type: '' });
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
useEffect(() => { useEffect(() => {
console.log(navigator.language.slice(0, 2))
const cookieLocale = document.cookie.split("; ") const cookieLocale = document.cookie.split("; ")
.find((row) => row.startsWith("MYNEXTAPP_LOCALE")) .find((row) => row.startsWith("MYNEXTAPP_LOCALE"))
?.split("=")[1]; ?.split("=")[1];
@@ -47,8 +50,8 @@ export default function Header() {
setLocale(cookieLocale) setLocale(cookieLocale)
} else { } else {
let browserLocale = navigator.language.slice(0, 2); let browserLocale = navigator.language.slice(0, 2);
const listCurrentLanguage = ["jp", "kr", "en", "vi", "zh", "cn"]
if(!listCurrentLanguage.includes(browserLocale)) { if (!listCurrentLanguage.hasOwnProperty(browserLocale)) {
browserLocale = "en" browserLocale = "en"
} }
setLocale(browserLocale); setLocale(browserLocale);
@@ -167,15 +170,33 @@ export default function Header() {
className="menu menu-sm dropdown-content bg-base-100 rounded-box z-10 mt-3 w-52 p-2 shadow-md border border-base-200" className="menu menu-sm dropdown-content bg-base-100 rounded-box z-10 mt-3 w-52 p-2 shadow-md border border-base-200"
> >
<li> <li>
<button <>
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium" <input
> type="file"
{transI18n("loadData")} accept="application/json"
</button> id="battle-data-upload"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
importBattleData(file, loadBattleDataFromJSON)
.then(() => console.log('Data loaded'))
.catch(err => alert('Failed to load data: ' + err.message));
}
}}
/>
<button
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
onClick={() => document.getElementById('battle-data-upload')?.click()}
>
{transI18n("loadData")}
</button>
</>
</li> </li>
<li> <li>
<button <button
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium" className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
onClick={() => exportBattleData({ totalAV, totalDamage, turnHistory, damagePerAV, lineup } as BattleDataStateJson)}
> >
{transI18n("exportData")} {transI18n("exportData")}
</button> </button>
@@ -193,7 +214,7 @@ export default function Header() {
{/* Logo */} {/* Logo */}
<a className=" flex flex-col items-start text-left gap-0 hover:scale-105 px-2"> <a className="hidden sm:grid sm:grid-cols-1 items-start text-left gap-0 hover:scale-105 px-2">
<h1 className="text-xl font-bold"> <h1 className="text-xl font-bold">
<span className="text-emerald-500">Firefly Analy</span> <span className="text-emerald-500">Firefly Analy</span>
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500"> <span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500">
@@ -256,12 +277,13 @@ export default function Header() {
<div className="navbar-end gap-2"> <div className="navbar-end gap-2">
<div className="px-2"> <div className="px-2">
<div className="flex items-center space-x-2 p-1.5 rounded-full shadow-md"> <div className="flex items-center space-x-2 p-1.5 rounded-full shadow-md">
<div className={`text-sm italic ${status ? 'text-green-500' : 'text-red-500'}`}> <div className={`hidden lg:block text-sm italic ${status ? 'text-green-500' : 'text-red-500'}`}>
{status ? transI18n("connected") : transI18n("unconnected")} {status ? transI18n("connected") : transI18n("unconnected")}
</div> </div>
<div className={`w-3 h-3 rounded-full ${status ? 'bg-green-500' : 'bg-red-500'}`}></div> <div className={`w-3 h-3 rounded-full ${status ? 'bg-green-500' : 'bg-red-500'}`}></div>
</div> </div>
</div> </div>
{/* Language selector - REFINED */} {/* Language selector - REFINED */}
<div className="dropdown dropdown-end"> <div className="dropdown dropdown-end">
<div className="flex items-center gap-1 border border-base-300 rounded text-sm px-1.5 py-0.5 hover:bg-base-200 cursor-pointer transition-all duration-200"> <div className="flex items-center gap-1 border border-base-300 rounded text-sm px-1.5 py-0.5 hover:bg-base-200 cursor-pointer transition-all duration-200">
@@ -274,11 +296,10 @@ export default function Header() {
value={locale} value={locale}
onChange={(e) => changeLocale(e.target.value)} onChange={(e) => changeLocale(e.target.value)}
> >
<option value="vi">VI</option> {Object.entries(listCurrentLanguage).map(([key, value]) => (
<option value="en">EN</option> <option key={key} value={key}>{value}</option>
<option value="cn">CN</option> ))}
<option value="jp">JP</option>
<option value="kr">KR</option>
</select> </select>
</div> </div>
</div> </div>
@@ -347,7 +368,7 @@ export default function Header() {
{/* GitHub Link */} {/* GitHub Link */}
<Link <Link
className='btn btn-ghost btn-sm btn-circle hover:bg-base-200 transition-all duration-200' className='hidden sm:block btn btn-ghost btn-sm btn-circle hover:bg-base-200 transition-all duration-200'
href={"https://github.com/AzenKain/SR-Analysis"} href={"https://github.com/AzenKain/SR-Analysis"}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"

View File

@@ -66,7 +66,7 @@ export default function LineupBar() {
}, [isModalOpen]); }, [isModalOpen]);
return ( return (
<div className="p-4 rounded-lg shadow-lg w-full h-full min-h-[74vh]"> <div className="p-4 md:p-1 rounded-lg shadow-lg w-full h-full">
<motion.h2 <motion.h2
className="text-center text-xl lg:text-2xl pb-2 font-bold text-transparent bg-clip-text bg-gradient-to-r from-pink-500 via-purple-500 to-cyan-500" className="text-center text-xl lg:text-2xl pb-2 font-bold text-transparent bg-clip-text bg-gradient-to-r from-pink-500 via-purple-500 to-cyan-500"
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, y: -20 }}
@@ -82,39 +82,40 @@ export default function LineupBar() {
<p className="text-base-content opacity-50">{transI18n("noCharactersInLineup")}</p> <p className="text-base-content opacity-50">{transI18n("noCharactersInLineup")}</p>
</div> </div>
) : ( ) : (
<div className="h-full w-full overflow-y-auto overflow-x-hidden custom-scrollbar rounded-lg"> <div className="h-full w-full overflow-x-auto md:overflow-x-hidden md:overflow-y-auto custom-scrollbar rounded-lg">
<style jsx>{` <style jsx>{`
.custom-scrollbar { .custom-scrollbar {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: hsl(var(--p)) hsl(var(--b3)); scrollbar-color: hsl(var(--p)) hsl(var(--b3));
} }
.custom-scrollbar::-webkit-scrollbar { .custom-scrollbar::-webkit-scrollbar {
width: 8px; width: 8px;
height: 8px; height: 8px;
} }
.custom-scrollbar::-webkit-scrollbar-track { .custom-scrollbar::-webkit-scrollbar-track {
background: hsl(var(--b3)); background: hsl(var(--b3));
border-radius: 10px; border-radius: 10px;
} }
.custom-scrollbar::-webkit-scrollbar-thumb { .custom-scrollbar::-webkit-scrollbar-thumb {
background: hsl(var(--p)); background: hsl(var(--p));
border-radius: 10px; border-radius: 10px;
} }
.custom-scrollbar::-webkit-scrollbar-thumb:hover { .custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: hsl(var(--pf)); background: hsl(var(--pf));
} }
.custom-scrollbar::-webkit-scrollbar-button { .custom-scrollbar::-webkit-scrollbar-button {
display: none; display: none;
height: 0; height: 0;
width: 0; width: 0;
} }
`}</style> `}</style>
<div className="grid grid-cols-1 w-full justify-items-center items-start">
<div className="flex flex-nowrap md:grid md:grid-cols-1 w-fit md:w-full justify-items-center items-start gap-2">
{lineupAvatars.map((item, index) => ( {lineupAvatars.map((item, index) => (
<motion.div <motion.div
key={item.id} key={item.id}
@@ -122,7 +123,7 @@ export default function LineupBar() {
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.3, delay: index * 0.1 }} transition={{ duration: 0.3, delay: index * 0.1 }}
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
className="cursor-pointer" className="cursor-pointer flex-shrink-0 md:w-full justify-items-center"
onClick={() => handleShow("character_detail_modal", item)} onClick={() => handleShow("character_detail_modal", item)}
> >
<CharacterCard data={item} /> <CharacterCard data={item} />
@@ -130,6 +131,7 @@ export default function LineupBar() {
))} ))}
</div> </div>
</div> </div>
)} )}
{/* Character Detail Modal */} {/* Character Detail Modal */}
@@ -165,7 +167,7 @@ export default function LineupBar() {
<div className="grid grid-cols-1 sm:grid-cols-2"> <div className="grid grid-cols-1 sm:grid-cols-2">
<div className="flex flex-col space-y-2 relative"> <div className="flex flex-col space-y-2 relative">
<p> <p>
{transI18n("id")}: <span className="font-bold">{selectedCharacter.id}</span> {transI18n("id")}: <span className="font-bold">{selectedCharacter.id}</span>
</p> </p>
<p className="flex items-center space-x-2"> <p className="flex items-center space-x-2">
<span>{transI18n("path")}:</span> <span>{transI18n("path")}:</span>
@@ -180,7 +182,7 @@ export default function LineupBar() {
)} )}
</p> </p>
<p> <p>
{transI18n("rarity")}: <span className="font-bold">{selectedCharacter.rank === "CombatPowerAvatarRarityType5" ? "5*" : "4*"}</span> {transI18n("rarity")}: <span className="font-bold">{selectedCharacter.rank === "CombatPowerAvatarRarityType5" ? "5*" : "4*"}</span>
</p> </p>
<p className="flex items-center space-x-2"> <p className="flex items-center space-x-2">
<span>{transI18n("element")}:</span> <span>{transI18n("element")}:</span>
@@ -232,7 +234,7 @@ export default function LineupBar() {
onClick={() => setModeLine(m as 0 | 1)} onClick={() => setModeLine(m as 0 | 1)}
className={`btn btn-sm ${modeLine === m ? "btn-accent" : "btn-ghost"}`} className={`btn btn-sm ${modeLine === m ? "btn-accent" : "btn-ghost"}`}
> >
{transI18n("style")} {m} {transI18n("type")} {m}
</button> </button>
))} ))}
</div> </div>
@@ -256,7 +258,7 @@ export default function LineupBar() {
onClick={() => setModeBar(m as 0 | 1 | 2)} onClick={() => setModeBar(m as 0 | 1 | 2)}
className={`btn btn-sm ${modeBar === m ? "btn-accent" : "btn-ghost"}`} className={`btn btn-sm ${modeBar === m ? "btn-accent" : "btn-ghost"}`}
> >
{transI18n("style")} {m} {transI18n("type")} {m}
</button> </button>
))} ))}
</div> </div>

View File

@@ -1,3 +1,4 @@
import { listCurrentLanguage } from "@/lib/constant";
import { AvatarType } from "@/types"; import { AvatarType } from "@/types";
@@ -5,7 +6,11 @@ export function getNameChar(locale: string, data: AvatarType | undefined): strin
if (!data) { if (!data) {
return "" return ""
} }
let text = data.lang.get(locale) ?? ""; if (!listCurrentLanguage.hasOwnProperty(locale)) {
return ""
}
let text = data.lang.get(listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase()) ?? "";
if (!text) { if (!text) {
text = data.lang.get("en") ?? ""; text = data.lang.get("en") ?? "";
} }

7
src/lib/constant.ts Normal file
View File

@@ -0,0 +1,7 @@
export const listCurrentLanguage = {
ja: "JP",
ko: "KR",
en: "US",
vi: "VN",
zh: "CN"
};

View File

@@ -77,12 +77,16 @@ export const connectSocket = (): Socket => {
socket.on("SetBattleLineup", (json) => onSetBattleLineupService(JSON.parse(json))); socket.on("SetBattleLineup", (json) => onSetBattleLineupService(JSON.parse(json)));
socket.on("TurnEnd", (json) => onTurnEndService(JSON.parse(json))); socket.on("TurnEnd", (json) => onTurnEndService(JSON.parse(json)));
socket.on("OnTurnEnd", (json) => onTurnEndService(JSON.parse(json)));
socket.on("OnUseSkill", (json) => onUseSkillService(JSON.parse(json))); socket.on("OnUseSkill", (json) => onUseSkillService(JSON.parse(json)));
socket.on("OnKill", (json) => onKillService(JSON.parse(json))); socket.on("OnKill", (json) => onKillService(JSON.parse(json)));
socket.on("OnDamage", (json) => onDamageService(JSON.parse(json))); socket.on("OnDamage", (json) => onDamageService(JSON.parse(json)));
socket.on('BattleBegin', () => onBattleBegin()); socket.on('BattleBegin', () => onBattleBegin());
socket.on('OnBattleBegin', () => onBattleBegin());
socket.on('TurnBegin', (json) => onTurnBeginService(JSON.parse(json))); socket.on('TurnBegin', (json) => onTurnBeginService(JSON.parse(json)));
socket.on('OnTurnBegin', (json) => onTurnBeginService(JSON.parse(json)));
socket.on('BattleEnd', (json) => onBattleEndService(JSON.parse(json))); socket.on('BattleEnd', (json) => onBattleEndService(JSON.parse(json)));
socket.on('OnBattleEnd', (json) => onBattleEndService(JSON.parse(json)));
socket.on("Error", (msg: string) => { socket.on("Error", (msg: string) => {
console.error("Server Error:", msg); console.error("Server Error:", msg);
@@ -104,12 +108,16 @@ export const disconnectSocket = (): void => {
if (socket) { if (socket) {
socket.off("SetBattleLineup", (json) => onSetBattleLineupService(JSON.parse(json))); socket.off("SetBattleLineup", (json) => onSetBattleLineupService(JSON.parse(json)));
socket.off("TurnEnd", (json) => onTurnEndService(JSON.parse(json))); socket.off("TurnEnd", (json) => onTurnEndService(JSON.parse(json)));
socket.off("OnTurnEnd", (json) => onTurnEndService(JSON.parse(json)));
socket.off("OnUseSkill", (json) => onUseSkillService(JSON.parse(json))); socket.off("OnUseSkill", (json) => onUseSkillService(JSON.parse(json)));
socket.off("OnKill", (json) => onKillService(JSON.parse(json))); socket.off("OnKill", (json) => onKillService(JSON.parse(json)));
socket.off("OnDamage", (json) => onDamageService(JSON.parse(json))); socket.off("OnDamage", (json) => onDamageService(JSON.parse(json)));
socket.off('BattleBegin', () => onBattleBegin()); socket.off('BattleBegin', () => onBattleBegin());
socket.off('OnBattleBegin', () => onBattleBegin());
socket.off('TurnBegin', (json) => onTurnBeginService(JSON.parse(json))); socket.off('TurnBegin', (json) => onTurnBeginService(JSON.parse(json)));
socket.off('OnTurnBegin', (json) => onTurnBeginService(JSON.parse(json)));
socket.off('BattleEnd', (json) => onBattleEndService(JSON.parse(json))); socket.off('BattleEnd', (json) => onBattleEndService(JSON.parse(json)));
socket.off('OnBattleEnd', (json) => onBattleEndService(JSON.parse(json)));
socket.offAny(); socket.offAny();
socket.disconnect(); socket.disconnect();
useSocketStore.getState().setStatus(false); useSocketStore.getState().setStatus(false);