diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 0f1b04d..6dac2c7 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -1,15 +1,15 @@ -name: Gitea Auto Deploy -run-name: ${{ gitea.actor }} pushed code 🚀 - -on: [push] - -jobs: - Deploy-Container: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Deploy to Container - run: | +name: Gitea Auto Deploy +run-name: ${{ gitea.actor }} pushed code 🚀 + +on: [push] + +jobs: + Deploy-Container: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Deploy to Container + run: | docker compose up -d --build --remove-orphans \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 120a327..0eea4aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,56 +1,56 @@ -FROM oven/bun:canary-alpine AS base - -# Install dependencies only when needed -FROM base AS deps - -WORKDIR /app - -# Install dependencies -COPY package.json bun.lock ./ -RUN bun install --frozen-lockfile - -# Rebuild the source code only when needed -FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . - -# Next.js collects completely anonymous telemetry data about general usage. -# Learn more here: https://nextjs.org/telemetry -# Disable telemetry during the build -ENV NEXT_TELEMETRY_DISABLED=1 - -RUN bun run build - -# Production image, copy all the files and run next -FROM base AS runner -WORKDIR /app - -ENV NODE_ENV=production - -# Disable telemetry -ENV NEXT_TELEMETRY_DISABLED=1 - -RUN adduser --system --uid 1001 nextjs - -COPY --from=builder /app/public ./public -COPY --from=builder /app/messages ./messages -# Set the correct permission for prerender cache -RUN mkdir .next -RUN chown nextjs:bun .next - -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=builder --chown=nextjs:bun /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:bun /app/.next/static ./.next/static - -USER nextjs - -EXPOSE 3000 - -ENV PORT=3000 - -# Set hostname to localhost -ENV HOSTNAME=0.0.0.0 - -CMD ["bun", "server.js"] +FROM oven/bun:canary-alpine AS base + +# Install dependencies only when needed +FROM base AS deps + +WORKDIR /app + +# Install dependencies +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Disable telemetry during the build +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN bun run build + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production + +# Disable telemetry +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public +COPY --from=builder /app/messages ./messages +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:bun .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:bun /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:bun /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 + +# Set hostname to localhost +ENV HOSTNAME=0.0.0.0 + +CMD ["bun", "server.js"] diff --git a/data/avatar_basic.json.br b/data/avatar_basic.json.br index ea2eeee..922797f 100644 Binary files a/data/avatar_basic.json.br and b/data/avatar_basic.json.br differ diff --git a/data/monster_basic.json.br b/data/monster_basic.json.br index e61c65d..f24e4fe 100644 Binary files a/data/monster_basic.json.br and b/data/monster_basic.json.br differ diff --git a/docker-compose.yml b/docker-compose.yml index 5f587b5..ce9dd55 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,15 @@ -services: - sranalysis: - build: - context: . - dockerfile: Dockerfile - container_name: sranalysis - restart: unless-stopped - ports: - - "3005:3000" - networks: - - sranalysis-network - -networks: - sranalysis-network: - driver: bridge +services: + sranalysis: + build: + context: . + dockerfile: Dockerfile + container_name: sranalysis + restart: unless-stopped + ports: + - "3005:3000" + networks: + - sranalysis-network + +networks: + sranalysis-network: + driver: bridge diff --git a/eslint.config.mjs b/eslint.config.mjs index 3fc8cde..b26e3de 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,13 +1,13 @@ -import nextCoreWebVitals from "eslint-config-next/core-web-vitals"; -import nextTypescript from "eslint-config-next/typescript"; -import { dirname } from "path"; -import { fileURLToPath } from "url"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const eslintConfig = [...nextCoreWebVitals, ...nextTypescript, { - ignores: ["node_modules/**", ".next/**", "out/**", "build/**", "next-env.d.ts"] -}]; - -export default eslintConfig; +import nextCoreWebVitals from "eslint-config-next/core-web-vitals"; +import nextTypescript from "eslint-config-next/typescript"; +import { dirname } from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const eslintConfig = [...nextCoreWebVitals, ...nextTypescript, { + ignores: ["node_modules/**", ".next/**", "out/**", "build/**", "next-env.d.ts"] +}]; + +export default eslintConfig; diff --git a/i18n/request.ts b/i18n/request.ts index bb494e3..b1b9d8a 100644 --- a/i18n/request.ts +++ b/i18n/request.ts @@ -1,11 +1,11 @@ -import { getRequestConfig } from "next-intl/server"; -import { cookies } from "next/headers"; - -export default getRequestConfig( async () => { - const locale = (await cookies()).get("MYNEXTAPP_LOCALE")?.value || "en"; - - return { - locale, - messages: (await import(`../messages/${locale}.json`)).default - } +import { getRequestConfig } from "next-intl/server"; +import { cookies } from "next/headers"; + +export default getRequestConfig( async () => { + const locale = (await cookies()).get("MYNEXTAPP_LOCALE")?.value || "en"; + + return { + locale, + messages: (await import(`../messages/${locale}.json`)).default + } }) \ No newline at end of file diff --git a/messages/cn.json b/messages/cn.json index 3da0d67..2372f3e 100644 --- a/messages/cn.json +++ b/messages/cn.json @@ -1,111 +1,111 @@ -{ - "TabTitle": { - "title": "Firefly 分析器", - "description": "Veritas 分析工具" - }, - "DataAnalysisPage": { - "useSkill": "使用技能", - "totalDamage": "总伤害", - "damagePerAV": "每行动值伤害", - "totalAV": "总行动值", - "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": "尚未有回合", - "type": "类型 ", - "warrior": "毁灭", - "knight": "存护", - "mage": "智识", - "priest": "丰饶", - "rogue": "巡猎", - "shaman": "同协", - "warlock": "虚无", - "memory": "记忆", - "elation": "欢愉", - "fire": "火", - "ice": "冰", - "imaginary": "虚数", - "physical": "物理", - "quantum": "量子", - "thunder": "雷", - "wind": "风", - "cycle": "回合", - "wave": "波次", - "hp": "生命值", - "atk": "攻击力", - "speed": "速度", - "critRate": "暴击率", - "critDmg": "暴击伤害", - "breakEffect": "击破特攻", - "effectRes": "效果抵抗", - "energyRegenerationRate": "能量回复效率", - "effectHitRate": "效果命中", - "outgoingHealingBoost": "治疗量加成", - "fireDmgBoost": "火属性伤害提高", - "iceDmgBoost": "冰属性伤害提高", - "imaginaryDmgBoost": "虚数属性伤害提高", - "physicalDmgBoost": "物理属性伤害提高", - "quantumDmgBoost": "量子属性伤害提高", - "thunderDmgBoost": "雷属性伤害提高", - "windDmgBoost": "风属性伤害提高", - "pursued": "附加伤害", - "true damage": "真实伤害", - "elation damage": "欢愉伤害", - "follow-up": "追加攻击", - "elemental damage": "击破与超击破伤害", - "dot": "持续伤害 ", - "damagePerCycle": "每轮伤害", - "damagePerCycleAndWave": "每轮/波伤害", - "damagePerWave": "每波伤害", - "lastTurn": "最后一回合", - "qte": "QTE技能", - "mazenormal": "普通迷宫", - "level": "等级", - "relics": "遗器", - "eidolons": "星魂", - "lightcones": "光锥", - "trailblazer": "开拓者" - } -} +{ + "TabTitle": { + "title": "Firefly 分析器", + "description": "Veritas 分析工具" + }, + "DataAnalysisPage": { + "useSkill": "使用技能", + "totalDamage": "总伤害", + "damagePerAV": "每行动值伤害", + "totalAV": "总行动值", + "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": "尚未有回合", + "type": "类型 ", + "warrior": "毁灭", + "knight": "存护", + "mage": "智识", + "priest": "丰饶", + "rogue": "巡猎", + "shaman": "同协", + "warlock": "虚无", + "memory": "记忆", + "elation": "欢愉", + "fire": "火", + "ice": "冰", + "imaginary": "虚数", + "physical": "物理", + "quantum": "量子", + "thunder": "雷", + "wind": "风", + "cycle": "回合", + "wave": "波次", + "hp": "生命值", + "atk": "攻击力", + "speed": "速度", + "critRate": "暴击率", + "critDmg": "暴击伤害", + "breakEffect": "击破特攻", + "effectRes": "效果抵抗", + "energyRegenerationRate": "能量回复效率", + "effectHitRate": "效果命中", + "outgoingHealingBoost": "治疗量加成", + "fireDmgBoost": "火属性伤害提高", + "iceDmgBoost": "冰属性伤害提高", + "imaginaryDmgBoost": "虚数属性伤害提高", + "physicalDmgBoost": "物理属性伤害提高", + "quantumDmgBoost": "量子属性伤害提高", + "thunderDmgBoost": "雷属性伤害提高", + "windDmgBoost": "风属性伤害提高", + "pursued": "附加伤害", + "true damage": "真实伤害", + "elation damage": "欢愉伤害", + "follow-up": "追加攻击", + "elemental damage": "击破与超击破伤害", + "dot": "持续伤害 ", + "damagePerCycle": "每轮伤害", + "damagePerCycleAndWave": "每轮/波伤害", + "damagePerWave": "每波伤害", + "lastTurn": "最后一回合", + "qte": "QTE技能", + "mazenormal": "普通迷宫", + "level": "等级", + "relics": "遗器", + "eidolons": "星魂", + "lightcones": "光锥", + "trailblazer": "开拓者" + } +} diff --git a/messages/en.json b/messages/en.json index b49d056..f6e1590 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1,111 +1,111 @@ -{ - "TabTitle": { - "title": "Firely Analytics", - "description": "Analytics tool for Veritas" - }, - "DataAnalysisPage": { - "useSkill": "Use Ability", - "totalDamage": "Total damage", - "damagePerAV": "Damage/Action value", - "totalAV": "Total action value", - "skillType": "Skill Type", - "skillName": "Skill Name", - "actionValue": "Action Value", - "character": "Character", - "id": "Id", - "path": "Path", - "rarity": "Rarity", - "element": "Element", - "totalTurn": "Total Turn", - "technique": "Technique", - "talent": "Talent", - "basic": "Basic Attack", - "skill": "Skill", - "ultimate": "Ultimate", - "servant": "Servant", - "skillDamageBreakdown": "Skill Damage Breakdown", - "skillUsageDistribution": "Skill Usage Distribution", - "damageOverTime": "Damage Over Time", - "damage": "Damage", - "cumulativeDamage": "Cumulative Damage", - "damageByActionValue": "Damage by Action Value", - "characterInformation": "Character Information", - "turnDetail": "Turn Details", - "damageDetails": "Damage Details", - "cycleCount": "Cycle Count", - "chartInfo": "Chart info", - "actionBar": "Action time line", - "lineupInfo": "Lineup info", - "loadData": "Load data battle", - "exportData": "Export data battle", - "connectSetting": "Connection Setting", - "connected": "Connected", - "unconnected": "Unconnected", - "socketConnection": "Socket Connection", - "connectionType": "Connection Type", - "status": "Status", - "connect": "Connect", - "checkGameConnect": "Check game connection", - "other": "Other", - "host": "Host", - "port": "Port", - "hostPlaceHolder": "Enter Host", - "portPlaceHolder": "Enter port number", - "noDamageDetail": "No damage details available", - "noCharactersInLineup": "No characters in lineup", - "noTurns": "No turns yet", - "type": "Type", - "warrior": "The Destruction", - "knight": "The Preservation", - "mage": "The Erudition", - "priest": "The Abundance", - "rogue": "The Hunt", - "shaman": "The Harmony", - "warlock": "The Nihility", - "memory": "The Remembrance", - "elation": "The Elation", - "fire": "Fire", - "ice": "Ice", - "imaginary": "Imaginary", - "physical": "Physical", - "quantum": "Quantum", - "thunder": "Thunder", - "wind": "Wind", - "cycle": "Cycle", - "wave": "Wave", - "hp": "Hp", - "atk": "Atk", - "speed": "Speed", - "critRate": "Crit Rate", - "critDmg": "Crit Dmg", - "breakEffect": "Break Effect", - "effectRes": "Effect Res", - "energyRegenerationRate": "Energy Regeneration Rate", - "effectHitRate": "Effect Hit Rate", - "outgoingHealingBoost": "Outgoing Healing Boost", - "fireDmgBoost": "Fire damage boost", - "iceDmgBoost": "Ice damage Boost", - "imaginaryDmgBoost": "Imaginary damage boost", - "physicalDmgBoost": "Physical damage boost", - "quantumDmgBoost": "Quantum damage boost", - "thunderDmgBoost": "Thunder damage boost", - "windDmgBoost": "Wind damage boost", - "pursued": "Additional damage", - "true damage": "True damage", - "elation damage": "Elation Damage", - "follow-up": "Follow-up Damage", - "elemental damage": "Break and Super break damage", - "dot": "Damage over time ", - "damagePerCycle": "Damage per Cycle", - "damagePerCycleAndWave": "Damage per Cycle and Wave", - "damagePerWave": "Damage per Wave", - "lastTurn": "Last turn", - "qte": "QTE Skill", - "mazenormal": "MazeNormal", - "level": "Level", - "relics": "Relics", - "eidolons": "Eidolons", - "lightcones": "Lightcones", - "trailblazer": "Trailblazer" - } -} +{ + "TabTitle": { + "title": "Firely Analytics", + "description": "Analytics tool for Veritas" + }, + "DataAnalysisPage": { + "useSkill": "Use Ability", + "totalDamage": "Total damage", + "damagePerAV": "Damage/Action value", + "totalAV": "Total action value", + "skillType": "Skill Type", + "skillName": "Skill Name", + "actionValue": "Action Value", + "character": "Character", + "id": "Id", + "path": "Path", + "rarity": "Rarity", + "element": "Element", + "totalTurn": "Total Turn", + "technique": "Technique", + "talent": "Talent", + "basic": "Basic Attack", + "skill": "Skill", + "ultimate": "Ultimate", + "servant": "Servant", + "skillDamageBreakdown": "Skill Damage Breakdown", + "skillUsageDistribution": "Skill Usage Distribution", + "damageOverTime": "Damage Over Time", + "damage": "Damage", + "cumulativeDamage": "Cumulative Damage", + "damageByActionValue": "Damage by Action Value", + "characterInformation": "Character Information", + "turnDetail": "Turn Details", + "damageDetails": "Damage Details", + "cycleCount": "Cycle Count", + "chartInfo": "Chart info", + "actionBar": "Action time line", + "lineupInfo": "Lineup info", + "loadData": "Load data battle", + "exportData": "Export data battle", + "connectSetting": "Connection Setting", + "connected": "Connected", + "unconnected": "Unconnected", + "socketConnection": "Socket Connection", + "connectionType": "Connection Type", + "status": "Status", + "connect": "Connect", + "checkGameConnect": "Check game connection", + "other": "Other", + "host": "Host", + "port": "Port", + "hostPlaceHolder": "Enter Host", + "portPlaceHolder": "Enter port number", + "noDamageDetail": "No damage details available", + "noCharactersInLineup": "No characters in lineup", + "noTurns": "No turns yet", + "type": "Type", + "warrior": "The Destruction", + "knight": "The Preservation", + "mage": "The Erudition", + "priest": "The Abundance", + "rogue": "The Hunt", + "shaman": "The Harmony", + "warlock": "The Nihility", + "memory": "The Remembrance", + "elation": "The Elation", + "fire": "Fire", + "ice": "Ice", + "imaginary": "Imaginary", + "physical": "Physical", + "quantum": "Quantum", + "thunder": "Thunder", + "wind": "Wind", + "cycle": "Cycle", + "wave": "Wave", + "hp": "Hp", + "atk": "Atk", + "speed": "Speed", + "critRate": "Crit Rate", + "critDmg": "Crit Dmg", + "breakEffect": "Break Effect", + "effectRes": "Effect Res", + "energyRegenerationRate": "Energy Regeneration Rate", + "effectHitRate": "Effect Hit Rate", + "outgoingHealingBoost": "Outgoing Healing Boost", + "fireDmgBoost": "Fire damage boost", + "iceDmgBoost": "Ice damage Boost", + "imaginaryDmgBoost": "Imaginary damage boost", + "physicalDmgBoost": "Physical damage boost", + "quantumDmgBoost": "Quantum damage boost", + "thunderDmgBoost": "Thunder damage boost", + "windDmgBoost": "Wind damage boost", + "pursued": "Additional damage", + "true damage": "True damage", + "elation damage": "Elation Damage", + "follow-up": "Follow-up Damage", + "elemental damage": "Break and Super break damage", + "dot": "Damage over time ", + "damagePerCycle": "Damage per Cycle", + "damagePerCycleAndWave": "Damage per Cycle and Wave", + "damagePerWave": "Damage per Wave", + "lastTurn": "Last turn", + "qte": "QTE Skill", + "mazenormal": "MazeNormal", + "level": "Level", + "relics": "Relics", + "eidolons": "Eidolons", + "lightcones": "Lightcones", + "trailblazer": "Trailblazer" + } +} diff --git a/messages/ja.json b/messages/ja.json index a9c3224..91fea0b 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -1,111 +1,111 @@ -{ - "TabTitle": { - "title": "Firefly Analytics", - "description": "Veritasの分析ツール" - }, - "DataAnalysisPage": { - "useSkill": "スキルを使用", - "totalDamage": "総ダメージ", - "damagePerAV": "ダメージ/行動値", - "totalAV": "総行動値", - "skillType": "スキルタイプ", - "skillName": "スキル名", - "actionValue": "行動値", - "character": "キャラクター", - "id": "ID", - "path": "運命", - "rarity": "レアリティ", - "element": "属性", - "totalTurn": "総ターン数", - "technique": "秘技", - "talent": "天賦", - "basic": "通常攻撃", - "skill": "スキル", - "ultimate": "必殺技", - "servant": "召喚", - "skillDamageBreakdown": "スキルダメージ内訳", - "skillUsageDistribution": "スキル使用分布", - "damageOverTime": "継続ダメージ", - "damage": "ダメージ", - "cumulativeDamage": "累積ダメージ", - "damageByActionValue": "行動値別ダメージ", - "characterInformation": "キャラクター情報", - "turnDetail": "ターン詳細", - "damageDetails": "ダメージ詳細", - "cycleCount": "サイクル数", - "chartInfo": "チャート情報", - "actionBar": "アクションタイムライン", - "lineupInfo": "編成情報", - "loadData": "戦闘データを読み込む", - "exportData": "戦闘データをエクスポート", - "connectSetting": "接続設定", - "connected": "接続済み", - "unconnected": "未接続", - "socketConnection": "ソケット接続", - "connectionType": "接続タイプ", - "status": "ステータス", - "connect": "接続する", - "checkGameConnect": "ゲーム接続を確認", - "other": "その他", - "host": "ホスト", - "port": "ポート", - "hostPlaceHolder": "ホストを入力", - "portPlaceHolder": "ポート番号を入力", - "noDamageDetail": "ダメージ詳細がありません", - "noCharactersInLineup": "編成にキャラクターがいません", - "noTurns": "まだターンがありません", - "type": "タイプ", - "warrior": "壊滅", - "knight": "存護", - "mage": "知恵", - "priest": "豊穣", - "rogue": "巡狩", - "shaman": "調和", - "warlock": "虚無", - "memory": "記憶", - "elation": "愉悦", - "fire": "炎", - "ice": "氷", - "imaginary": "虚数", - "physical": "物理", - "quantum": "量子", - "thunder": "雷", - "wind": "風", - "cycle": "サイクル", - "wave": "ウェーブ", - "hp": "HP", - "atk": "攻撃力", - "speed": "速度", - "critRate": "会心率", - "critDmg": "会心ダメージ", - "breakEffect": "撃破特効", - "effectRes": "効果抵抗", - "energyRegenerationRate": "エネルギー回復効率", - "effectHitRate": "効果命中率", - "outgoingHealingBoost": "治癒量上昇", - "fireDmgBoost": "炎属性ダメージ上昇", - "iceDmgBoost": "氷属性ダメージ上昇", - "imaginaryDmgBoost": "虚数属性ダメージ上昇", - "physicalDmgBoost": "物理属性ダメージ上昇", - "quantumDmgBoost": "量子属性ダメージ上昇", - "thunderDmgBoost": "雷属性ダメージ上昇", - "windDmgBoost": "風属性ダメージ上昇", - "pursued": "追加ダメージ", - "true damage": "貫通ダメージ", - "elation damage": "愉悦ダメージ", - "follow-up": "追撃ダメージ", - "elemental damage": "撃破・超撃破ダメージ", - "dot": "継続ダメージ", - "damagePerCycle": "サイクル毎のダメージ", - "damagePerCycleAndWave": "サイクル・ウェーブ毎のダメージ", - "damagePerWave": "ウェーブ毎のダメージ", - "lastTurn": "最後のターン", - "qte": "QTEスキル", - "mazenormal": "通常迷宮", - "level": "レベル", - "relics": "遺物", - "eidolons": "星魂", - "lightcones": "光円錐", - "trailblazer": "開拓者" - } -} +{ + "TabTitle": { + "title": "Firefly Analytics", + "description": "Veritasの分析ツール" + }, + "DataAnalysisPage": { + "useSkill": "スキルを使用", + "totalDamage": "総ダメージ", + "damagePerAV": "ダメージ/行動値", + "totalAV": "総行動値", + "skillType": "スキルタイプ", + "skillName": "スキル名", + "actionValue": "行動値", + "character": "キャラクター", + "id": "ID", + "path": "運命", + "rarity": "レアリティ", + "element": "属性", + "totalTurn": "総ターン数", + "technique": "秘技", + "talent": "天賦", + "basic": "通常攻撃", + "skill": "スキル", + "ultimate": "必殺技", + "servant": "召喚", + "skillDamageBreakdown": "スキルダメージ内訳", + "skillUsageDistribution": "スキル使用分布", + "damageOverTime": "継続ダメージ", + "damage": "ダメージ", + "cumulativeDamage": "累積ダメージ", + "damageByActionValue": "行動値別ダメージ", + "characterInformation": "キャラクター情報", + "turnDetail": "ターン詳細", + "damageDetails": "ダメージ詳細", + "cycleCount": "サイクル数", + "chartInfo": "チャート情報", + "actionBar": "アクションタイムライン", + "lineupInfo": "編成情報", + "loadData": "戦闘データを読み込む", + "exportData": "戦闘データをエクスポート", + "connectSetting": "接続設定", + "connected": "接続済み", + "unconnected": "未接続", + "socketConnection": "ソケット接続", + "connectionType": "接続タイプ", + "status": "ステータス", + "connect": "接続する", + "checkGameConnect": "ゲーム接続を確認", + "other": "その他", + "host": "ホスト", + "port": "ポート", + "hostPlaceHolder": "ホストを入力", + "portPlaceHolder": "ポート番号を入力", + "noDamageDetail": "ダメージ詳細がありません", + "noCharactersInLineup": "編成にキャラクターがいません", + "noTurns": "まだターンがありません", + "type": "タイプ", + "warrior": "壊滅", + "knight": "存護", + "mage": "知恵", + "priest": "豊穣", + "rogue": "巡狩", + "shaman": "調和", + "warlock": "虚無", + "memory": "記憶", + "elation": "愉悦", + "fire": "炎", + "ice": "氷", + "imaginary": "虚数", + "physical": "物理", + "quantum": "量子", + "thunder": "雷", + "wind": "風", + "cycle": "サイクル", + "wave": "ウェーブ", + "hp": "HP", + "atk": "攻撃力", + "speed": "速度", + "critRate": "会心率", + "critDmg": "会心ダメージ", + "breakEffect": "撃破特効", + "effectRes": "効果抵抗", + "energyRegenerationRate": "エネルギー回復効率", + "effectHitRate": "効果命中率", + "outgoingHealingBoost": "治癒量上昇", + "fireDmgBoost": "炎属性ダメージ上昇", + "iceDmgBoost": "氷属性ダメージ上昇", + "imaginaryDmgBoost": "虚数属性ダメージ上昇", + "physicalDmgBoost": "物理属性ダメージ上昇", + "quantumDmgBoost": "量子属性ダメージ上昇", + "thunderDmgBoost": "雷属性ダメージ上昇", + "windDmgBoost": "風属性ダメージ上昇", + "pursued": "追加ダメージ", + "true damage": "貫通ダメージ", + "elation damage": "愉悦ダメージ", + "follow-up": "追撃ダメージ", + "elemental damage": "撃破・超撃破ダメージ", + "dot": "継続ダメージ", + "damagePerCycle": "サイクル毎のダメージ", + "damagePerCycleAndWave": "サイクル・ウェーブ毎のダメージ", + "damagePerWave": "ウェーブ毎のダメージ", + "lastTurn": "最後のターン", + "qte": "QTEスキル", + "mazenormal": "通常迷宮", + "level": "レベル", + "relics": "遺物", + "eidolons": "星魂", + "lightcones": "光円錐", + "trailblazer": "開拓者" + } +} diff --git a/messages/ko.json b/messages/ko.json index 000f91f..15d4685 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -1,111 +1,111 @@ -{ - "TabTitle": { - "title": "Firefly 분석", - "description": "Veritas의 분석 도구" - }, - "DataAnalysisPage": { - "useSkill": "스킬 사용", - "totalDamage": "총 피해량", - "damagePerAV": "피해량/행동값", - "totalAV": "총 행동값", - "skillType": "스킬 유형", - "skillName": "스킬 이름", - "actionValue": "행동값", - "character": "캐릭터", - "id": "ID", - "path": "운명", - "rarity": "희귀도", - "element": "속성", - "totalTurn": "총 턴 수", - "technique": "비전", - "talent": "재능", - "basic": "기본 공격", - "skill": "스킬", - "ultimate": "필살기", - "servant": "소환", - "skillDamageBreakdown": "스킬 피해 세부사항", - "skillUsageDistribution": "스킬 사용 분포", - "damageOverTime": "시간당 피해", - "damage": "피해", - "cumulativeDamage": "누적 피해", - "damageByActionValue": "행동값별 피해", - "characterInformation": "캐릭터 정보", - "turnDetail": "턴 상세 정보", - "damageDetails": "피해 세부사항", - "cycleCount": "사이클 수", - "chartInfo": "차트 정보", - "actionBar": "행동 타임라인", - "lineupInfo": "편성 정보", - "loadData": "전투 데이터 불러오기", - "exportData": "전투 데이터 내보내기", - "connectSetting": "연결 설정", - "connected": "연결됨", - "unconnected": "연결되지 않음", - "socketConnection": "소켓 연결", - "connectionType": "연결 유형", - "status": "상태", - "connect": "연결", - "checkGameConnect": "게임 연결 확인", - "other": "기타", - "host": "호스트", - "port": "포트", - "hostPlaceHolder": "호스트 입력", - "portPlaceHolder": "포트 번호 입력", - "noDamageDetail": "피해 세부사항 없음", - "noCharactersInLineup": "편성에 캐릭터 없음", - "noTurns": "아직 턴이 없습니다", - "type": "유형", - "warrior": "파괴", - "knight": "보존", - "mage": "지혜", - "priest": "풍요", - "rogue": "사냥", - "shaman": "조화", - "warlock": "허무", - "memory": "기억", - "elation": "환락", - "fire": "불", - "ice": "얼음", - "imaginary": "허수", - "physical": "물리", - "quantum": "양자", - "thunder": "번개", - "wind": "바람", - "cycle": "사이클", - "wave": "웨이브", - "hp": "HP", - "atk": "공격력", - "speed": "속도", - "critRate": "치명타 확률", - "critDmg": "치명타 피해", - "breakEffect": "파괴 효과", - "effectRes": "효과 저항", - "energyRegenerationRate": "에너지 회복 속도", - "effectHitRate": "효과 적중률", - "outgoingHealingBoost": "치유량 증가", - "fireDmgBoost": "불 속성 피해 증가", - "iceDmgBoost": "얼음 속성 피해 증가", - "imaginaryDmgBoost": "허수 속성 피해 증가", - "physicalDmgBoost": "물리 속성 피해 증가", - "quantumDmgBoost": "양자 속성 피해 증가", - "thunderDmgBoost": "번개 속성 피해 증가", - "windDmgBoost": "바람 속성 피해 증가", - "pursued": "추가 피해", - "true damage": "관통 피해", - "elation damage": "환락 피해", - "follow-up": "추가 공격 피해", - "elemental damage": "파괴 및 초파괴 피해", - "dot": "지속 피해", - "damagePerCycle": "사이클당 피해", - "damagePerCycleAndWave": "사이클 및 웨이브당 피해", - "damagePerWave": "웨이브당 피해", - "lastTurn": "마지막 턴", - "qte": "QTE 스킬", - "mazenormal": "일반 미궁", - "level": "레벨", - "relics": "유물", - "eidolons": "에이돌론", - "lightcones": "광추", - "trailblazer": "개척자" - } -} +{ + "TabTitle": { + "title": "Firefly 분석", + "description": "Veritas의 분석 도구" + }, + "DataAnalysisPage": { + "useSkill": "스킬 사용", + "totalDamage": "총 피해량", + "damagePerAV": "피해량/행동값", + "totalAV": "총 행동값", + "skillType": "스킬 유형", + "skillName": "스킬 이름", + "actionValue": "행동값", + "character": "캐릭터", + "id": "ID", + "path": "운명", + "rarity": "희귀도", + "element": "속성", + "totalTurn": "총 턴 수", + "technique": "비전", + "talent": "재능", + "basic": "기본 공격", + "skill": "스킬", + "ultimate": "필살기", + "servant": "소환", + "skillDamageBreakdown": "스킬 피해 세부사항", + "skillUsageDistribution": "스킬 사용 분포", + "damageOverTime": "시간당 피해", + "damage": "피해", + "cumulativeDamage": "누적 피해", + "damageByActionValue": "행동값별 피해", + "characterInformation": "캐릭터 정보", + "turnDetail": "턴 상세 정보", + "damageDetails": "피해 세부사항", + "cycleCount": "사이클 수", + "chartInfo": "차트 정보", + "actionBar": "행동 타임라인", + "lineupInfo": "편성 정보", + "loadData": "전투 데이터 불러오기", + "exportData": "전투 데이터 내보내기", + "connectSetting": "연결 설정", + "connected": "연결됨", + "unconnected": "연결되지 않음", + "socketConnection": "소켓 연결", + "connectionType": "연결 유형", + "status": "상태", + "connect": "연결", + "checkGameConnect": "게임 연결 확인", + "other": "기타", + "host": "호스트", + "port": "포트", + "hostPlaceHolder": "호스트 입력", + "portPlaceHolder": "포트 번호 입력", + "noDamageDetail": "피해 세부사항 없음", + "noCharactersInLineup": "편성에 캐릭터 없음", + "noTurns": "아직 턴이 없습니다", + "type": "유형", + "warrior": "파괴", + "knight": "보존", + "mage": "지혜", + "priest": "풍요", + "rogue": "사냥", + "shaman": "조화", + "warlock": "허무", + "memory": "기억", + "elation": "환락", + "fire": "불", + "ice": "얼음", + "imaginary": "허수", + "physical": "물리", + "quantum": "양자", + "thunder": "번개", + "wind": "바람", + "cycle": "사이클", + "wave": "웨이브", + "hp": "HP", + "atk": "공격력", + "speed": "속도", + "critRate": "치명타 확률", + "critDmg": "치명타 피해", + "breakEffect": "파괴 효과", + "effectRes": "효과 저항", + "energyRegenerationRate": "에너지 회복 속도", + "effectHitRate": "효과 적중률", + "outgoingHealingBoost": "치유량 증가", + "fireDmgBoost": "불 속성 피해 증가", + "iceDmgBoost": "얼음 속성 피해 증가", + "imaginaryDmgBoost": "허수 속성 피해 증가", + "physicalDmgBoost": "물리 속성 피해 증가", + "quantumDmgBoost": "양자 속성 피해 증가", + "thunderDmgBoost": "번개 속성 피해 증가", + "windDmgBoost": "바람 속성 피해 증가", + "pursued": "추가 피해", + "true damage": "관통 피해", + "elation damage": "환락 피해", + "follow-up": "추가 공격 피해", + "elemental damage": "파괴 및 초파괴 피해", + "dot": "지속 피해", + "damagePerCycle": "사이클당 피해", + "damagePerCycleAndWave": "사이클 및 웨이브당 피해", + "damagePerWave": "웨이브당 피해", + "lastTurn": "마지막 턴", + "qte": "QTE 스킬", + "mazenormal": "일반 미궁", + "level": "레벨", + "relics": "유물", + "eidolons": "에이돌론", + "lightcones": "광추", + "trailblazer": "개척자" + } +} diff --git a/messages/vi.json b/messages/vi.json index 5639767..b454d74 100644 --- a/messages/vi.json +++ b/messages/vi.json @@ -1,111 +1,111 @@ -{ - "TabTitle": { - "title": "Firefly Analytics", - "description": "Công cụ phân tích cho Veritas" - }, - "DataAnalysisPage": { - "useSkill": "Sử dụng kỹ năng", - "totalDamage": "Tổng sát thương", - "damagePerAV": "Sát thương/giá trị hành động", - "totalAV": "Tổng giá trị hành động", - "skillType": "Loại kỹ năng", - "skillName": "Tên kỹ năng", - "actionValue": "Giá trị hành động", - "character": "Nhân vật", - "id": "ID", - "path": "Vận mệnh", - "rarity": "Số sao", - "element": "Nguyên tố", - "totalTurn": "Tổng lượt", - "damageByActionValue": "Sát thương theo giá trị hành động", - "technique": "Bĩ kỹ", - "talent": "Thiên phú", - "basic": "Tấn công thường", - "skill": "Chiến kỹ", - "ultimate": "Tuyệt Kĩ", - "servant": "Vật triệu hồi", - "skillDamageBreakdown": "Phân tích sát thương kỹ năng", - "skillUsageDistribution": "Phân phối sử dụng kỹ năng", - "damageOverTime": "Sát thương theo thời gian", - "damage": "Sát thương", - "cumulativeDamage": "Sát thương tích lũy", - "characterInformation": "Thông tin nhân vật", - "turnDetail": "Chi tiết lượt", - "damageDetails": "Chi tiết sát thương", - "cycleCount": "Số vòng", - "chartInfo": "Thông tin biểu đồ", - "actionBar": "Thanh hành động", - "lineupInfo": "Thông tin đội hình", - "loadData": "Tải dữ liệu trận đấu", - "exportData": "Xuất dữ liệu trận đấu", - "connectSetting": "Cài đặt kết nối", - "connected": "Đã kết nối", - "unconnected": "Chưa kết nối", - "socketConnection": "Kết nối socket", - "connectionType": "Loại kết nối", - "status": "Trạng thái", - "connect": "Kết nối", - "checkGameConnect": "Kiểm tra kết nối game", - "other": "Khác", - "host": "Địa chỉ kết nối", - "port": "Cổng kết nối", - "hostPlaceHolder": "Nhập địa chỉ kết nối", - "portPlaceHolder": "Nhâp cổng kết nối", - "noDamageDetail": "Không có chi tiết sát thương", - "noCharactersInLineup": "Không có nhân vật trong đội hình", - "noTurns": "Chưa có lượt hành động", - "type": "Loại", - "warrior": "Hủy Diệt", - "knight": "Bảo Hộ", - "mage": "Tri Thức", - "priest": "Phong Phú", - "rogue": "Săn Bắn", - "shaman": "Hài Hòa", - "warlock": "Hư Vô", - "memory": "Ký Ức", - "elation": "Vui vẻ", - "fire": "Lửa", - "ice": "Băng", - "imaginary": "Số ảo", - "physical": "Vật Lý", - "quantum": "Lượng Tử", - "thunder": "Lôi", - "wind": "Phong", - "cycle": "Vòng", - "wave": "Đợt quái", - "hp": "Máu", - "atk": "Tấn công", - "speed": "Tốc độ", - "critRate": "Tỉ lệ chí mạng", - "critDmg": "Sát thương chí mạng", - "breakEffect": "Tấn công kích phá", - "effectRes": "Kháng hiệu ứng", - "energyRegenerationRate": "Hiệu quả nạp", - "effectHitRate": "Chinh xác hiệu ứng", - "outgoingHealingBoost": "Hiệu quả trị liệu", - "fireDmgBoost": "Tăng sát thương hỏa", - "iceDmgBoost": "Tăng sát thương băng", - "imaginaryDmgBoost": "Tăng sát thương số ảo", - "physicalDmgBoost": "Tăng sát thương vật lý", - "quantumDmgBoost": "Tăng sát thương lượng tử", - "thunderDmgBoost": "Tăng sát thương lôi", - "windDmgBoost": "Tăng sát thương phong", - "pursued": "Sát thương kèm theo", - "true damage": "Sát thương chuẩn", - "elation damage": "Sát thương vui vẻ", - "follow-up": "Sát thương theo sau", - "elemental damage": "Sát thương phá vỡ và siêu phá vỡ", - "dot": "Sát Thương Duy Trì", - "damagePerCycle": "Sát thương mỗi vòng", - "damagePerCycleAndWave": "Sát thương mỗi vòng và đợt", - "damagePerWave": "Sát thương mỗi đợt", - "lastTurn": "Lượt cuối", - "qte": "Kỹ năng QTE", - "mazenormal": "Mê cung (Thường)", - "level": "Cấp độ", - "relics": "Di vật", - "eidolons": "Tinh hồn", - "lightcones": "Nón Ánh sáng", - "trailblazer": "Nhà khai phá" - } -} +{ + "TabTitle": { + "title": "Firefly Analytics", + "description": "Công cụ phân tích cho Veritas" + }, + "DataAnalysisPage": { + "useSkill": "Sử dụng kỹ năng", + "totalDamage": "Tổng sát thương", + "damagePerAV": "Sát thương/giá trị hành động", + "totalAV": "Tổng giá trị hành động", + "skillType": "Loại kỹ năng", + "skillName": "Tên kỹ năng", + "actionValue": "Giá trị hành động", + "character": "Nhân vật", + "id": "ID", + "path": "Vận mệnh", + "rarity": "Số sao", + "element": "Nguyên tố", + "totalTurn": "Tổng lượt", + "damageByActionValue": "Sát thương theo giá trị hành động", + "technique": "Bĩ kỹ", + "talent": "Thiên phú", + "basic": "Tấn công thường", + "skill": "Chiến kỹ", + "ultimate": "Tuyệt Kĩ", + "servant": "Vật triệu hồi", + "skillDamageBreakdown": "Phân tích sát thương kỹ năng", + "skillUsageDistribution": "Phân phối sử dụng kỹ năng", + "damageOverTime": "Sát thương theo thời gian", + "damage": "Sát thương", + "cumulativeDamage": "Sát thương tích lũy", + "characterInformation": "Thông tin nhân vật", + "turnDetail": "Chi tiết lượt", + "damageDetails": "Chi tiết sát thương", + "cycleCount": "Số vòng", + "chartInfo": "Thông tin biểu đồ", + "actionBar": "Thanh hành động", + "lineupInfo": "Thông tin đội hình", + "loadData": "Tải dữ liệu trận đấu", + "exportData": "Xuất dữ liệu trận đấu", + "connectSetting": "Cài đặt kết nối", + "connected": "Đã kết nối", + "unconnected": "Chưa kết nối", + "socketConnection": "Kết nối socket", + "connectionType": "Loại kết nối", + "status": "Trạng thái", + "connect": "Kết nối", + "checkGameConnect": "Kiểm tra kết nối game", + "other": "Khác", + "host": "Địa chỉ kết nối", + "port": "Cổng kết nối", + "hostPlaceHolder": "Nhập địa chỉ kết nối", + "portPlaceHolder": "Nhâp cổng kết nối", + "noDamageDetail": "Không có chi tiết sát thương", + "noCharactersInLineup": "Không có nhân vật trong đội hình", + "noTurns": "Chưa có lượt hành động", + "type": "Loại", + "warrior": "Hủy Diệt", + "knight": "Bảo Hộ", + "mage": "Tri Thức", + "priest": "Phong Phú", + "rogue": "Săn Bắn", + "shaman": "Hài Hòa", + "warlock": "Hư Vô", + "memory": "Ký Ức", + "elation": "Vui vẻ", + "fire": "Lửa", + "ice": "Băng", + "imaginary": "Số ảo", + "physical": "Vật Lý", + "quantum": "Lượng Tử", + "thunder": "Lôi", + "wind": "Phong", + "cycle": "Vòng", + "wave": "Đợt quái", + "hp": "Máu", + "atk": "Tấn công", + "speed": "Tốc độ", + "critRate": "Tỉ lệ chí mạng", + "critDmg": "Sát thương chí mạng", + "breakEffect": "Tấn công kích phá", + "effectRes": "Kháng hiệu ứng", + "energyRegenerationRate": "Hiệu quả nạp", + "effectHitRate": "Chinh xác hiệu ứng", + "outgoingHealingBoost": "Hiệu quả trị liệu", + "fireDmgBoost": "Tăng sát thương hỏa", + "iceDmgBoost": "Tăng sát thương băng", + "imaginaryDmgBoost": "Tăng sát thương số ảo", + "physicalDmgBoost": "Tăng sát thương vật lý", + "quantumDmgBoost": "Tăng sát thương lượng tử", + "thunderDmgBoost": "Tăng sát thương lôi", + "windDmgBoost": "Tăng sát thương phong", + "pursued": "Sát thương kèm theo", + "true damage": "Sát thương chuẩn", + "elation damage": "Sát thương vui vẻ", + "follow-up": "Sát thương theo sau", + "elemental damage": "Sát thương phá vỡ và siêu phá vỡ", + "dot": "Sát Thương Duy Trì", + "damagePerCycle": "Sát thương mỗi vòng", + "damagePerCycleAndWave": "Sát thương mỗi vòng và đợt", + "damagePerWave": "Sát thương mỗi đợt", + "lastTurn": "Lượt cuối", + "qte": "Kỹ năng QTE", + "mazenormal": "Mê cung (Thường)", + "level": "Cấp độ", + "relics": "Di vật", + "eidolons": "Tinh hồn", + "lightcones": "Nón Ánh sáng", + "trailblazer": "Nhà khai phá" + } +} diff --git a/messages/zh.json b/messages/zh.json index 3da0d67..2372f3e 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -1,111 +1,111 @@ -{ - "TabTitle": { - "title": "Firefly 分析器", - "description": "Veritas 分析工具" - }, - "DataAnalysisPage": { - "useSkill": "使用技能", - "totalDamage": "总伤害", - "damagePerAV": "每行动值伤害", - "totalAV": "总行动值", - "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": "尚未有回合", - "type": "类型 ", - "warrior": "毁灭", - "knight": "存护", - "mage": "智识", - "priest": "丰饶", - "rogue": "巡猎", - "shaman": "同协", - "warlock": "虚无", - "memory": "记忆", - "elation": "欢愉", - "fire": "火", - "ice": "冰", - "imaginary": "虚数", - "physical": "物理", - "quantum": "量子", - "thunder": "雷", - "wind": "风", - "cycle": "回合", - "wave": "波次", - "hp": "生命值", - "atk": "攻击力", - "speed": "速度", - "critRate": "暴击率", - "critDmg": "暴击伤害", - "breakEffect": "击破特攻", - "effectRes": "效果抵抗", - "energyRegenerationRate": "能量回复效率", - "effectHitRate": "效果命中", - "outgoingHealingBoost": "治疗量加成", - "fireDmgBoost": "火属性伤害提高", - "iceDmgBoost": "冰属性伤害提高", - "imaginaryDmgBoost": "虚数属性伤害提高", - "physicalDmgBoost": "物理属性伤害提高", - "quantumDmgBoost": "量子属性伤害提高", - "thunderDmgBoost": "雷属性伤害提高", - "windDmgBoost": "风属性伤害提高", - "pursued": "附加伤害", - "true damage": "真实伤害", - "elation damage": "欢愉伤害", - "follow-up": "追加攻击", - "elemental damage": "击破与超击破伤害", - "dot": "持续伤害 ", - "damagePerCycle": "每轮伤害", - "damagePerCycleAndWave": "每轮/波伤害", - "damagePerWave": "每波伤害", - "lastTurn": "最后一回合", - "qte": "QTE技能", - "mazenormal": "普通迷宫", - "level": "等级", - "relics": "遗器", - "eidolons": "星魂", - "lightcones": "光锥", - "trailblazer": "开拓者" - } -} +{ + "TabTitle": { + "title": "Firefly 分析器", + "description": "Veritas 分析工具" + }, + "DataAnalysisPage": { + "useSkill": "使用技能", + "totalDamage": "总伤害", + "damagePerAV": "每行动值伤害", + "totalAV": "总行动值", + "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": "尚未有回合", + "type": "类型 ", + "warrior": "毁灭", + "knight": "存护", + "mage": "智识", + "priest": "丰饶", + "rogue": "巡猎", + "shaman": "同协", + "warlock": "虚无", + "memory": "记忆", + "elation": "欢愉", + "fire": "火", + "ice": "冰", + "imaginary": "虚数", + "physical": "物理", + "quantum": "量子", + "thunder": "雷", + "wind": "风", + "cycle": "回合", + "wave": "波次", + "hp": "生命值", + "atk": "攻击力", + "speed": "速度", + "critRate": "暴击率", + "critDmg": "暴击伤害", + "breakEffect": "击破特攻", + "effectRes": "效果抵抗", + "energyRegenerationRate": "能量回复效率", + "effectHitRate": "效果命中", + "outgoingHealingBoost": "治疗量加成", + "fireDmgBoost": "火属性伤害提高", + "iceDmgBoost": "冰属性伤害提高", + "imaginaryDmgBoost": "虚数属性伤害提高", + "physicalDmgBoost": "物理属性伤害提高", + "quantumDmgBoost": "量子属性伤害提高", + "thunderDmgBoost": "雷属性伤害提高", + "windDmgBoost": "风属性伤害提高", + "pursued": "附加伤害", + "true damage": "真实伤害", + "elation damage": "欢愉伤害", + "follow-up": "追加攻击", + "elemental damage": "击破与超击破伤害", + "dot": "持续伤害 ", + "damagePerCycle": "每轮伤害", + "damagePerCycleAndWave": "每轮/波伤害", + "damagePerWave": "每波伤害", + "lastTurn": "最后一回合", + "qte": "QTE技能", + "mazenormal": "普通迷宫", + "level": "等级", + "relics": "遗器", + "eidolons": "星魂", + "lightcones": "光锥", + "trailblazer": "开拓者" + } +} diff --git a/src/app/api/data/[name]/route.ts b/src/app/api/data/[name]/route.ts index c8b7583..25ee82a 100644 --- a/src/app/api/data/[name]/route.ts +++ b/src/app/api/data/[name]/route.ts @@ -1,25 +1,25 @@ -import { getDataCache } from "@/lib/cache/cache" - -export async function GET( - req: Request, - { params }: { params: Promise<{ name: string }> } -) { - const { name } = await params - - const item = getDataCache(name) - - if (!item) { - return new Response("Not found", { status: 404 }) - } - - const headers: Record = { - "Content-Type": "application/json", - "Cache-Control": "public, max-age=3600" - } - - if (item.type === "br") { - headers["Content-Encoding"] = "br" - } - - return new Response(new Uint8Array(item.buf), { headers }) +import { getDataCache } from "@/lib/cache/cache" + +export async function GET( + req: Request, + { params }: { params: Promise<{ name: string }> } +) { + const { name } = await params + + const item = getDataCache(name) + + if (!item) { + return new Response("Not found", { status: 404 }) + } + + const headers: Record = { + "Content-Type": "application/json", + "Cache-Control": "public, max-age=3600" + } + + if (item.type === "br") { + headers["Content-Encoding"] = "br" + } + + return new Response(new Uint8Array(item.buf), { headers }) } \ No newline at end of file diff --git a/src/components/actionbar/index.tsx b/src/components/actionbar/index.tsx index 57781d6..bbc187b 100644 --- a/src/components/actionbar/index.tsx +++ b/src/components/actionbar/index.tsx @@ -1,283 +1,283 @@ -"use client"; -import useAvatarDataStore from "@/stores/avatarDataStore"; -import useBattleDataStore from "@/stores/battleDataStore"; -import useLocaleStore from "@/stores/localeStore"; -import { attackTypeToString, CharacterBasic } from "@/types"; -import { SkillBattleInfo } from "@/types/mics"; -import { useEffect, useState, useRef } from "react"; -import { useTranslations } from "next-intl"; -import { motion } from "framer-motion"; -import { getNameChar } from "@/helper"; -import Image from "next/image"; -import NameAvatar from "../nameAvatar"; - -export default function ActionBar() { - const [selectTurn, setSelectTurn] = useState(null); - const [selectAvatar, setSelectAvatar] = useState(null); - const [isModalOpen, setIsModalOpen] = useState(false); - const { skillHistory, turnHistory, cycleIndex, waveIndex, maxWave } = useBattleDataStore(); - const { listAvatar } = useAvatarDataStore(); - const { locale } = useLocaleStore(); - const transI18n = useTranslations("DataAnalysisPage"); - const turnListRef = useRef(null); - - - const contentStyle: React.CSSProperties = { - - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - }; - - const handleShow = (modalId: string, avatar: CharacterBasic, turn: SkillBattleInfo) => { - const modal = document.getElementById(modalId) as HTMLDialogElement | null; - if (modal) { - setSelectAvatar(avatar); - setSelectTurn(turn); - setIsModalOpen(true); - modal.showModal(); - } - }; - - // Close modal handler - const handleCloseModal = (modalId: string) => { - setIsModalOpen(false); - setSelectAvatar(null); - const modal = document.getElementById(modalId) as HTMLDialogElement | null; - if (modal) { - modal.close(); - } - }; - - // Handle ESC key to close modal - useEffect(() => { - const handleEscKey = (event: KeyboardEvent) => { - if (event.key === 'Escape' && isModalOpen) { - handleCloseModal("action_detail_modal"); - } - }; - - window.addEventListener('keydown', handleEscKey); - return () => window.removeEventListener('keydown', handleEscKey); - }, [isModalOpen]); - - // Scroll to the bottom when new turns are added - useEffect(() => { - if (turnListRef.current && skillHistory.length > 0) { - turnListRef.current.scrollTop = turnListRef.current.scrollHeight; - } - }, [skillHistory.length]); - - return ( -
- - {transI18n("actionBar")} - -
-
- - - - {waveIndex}/{maxWave} -
-
- - - - {cycleIndex} -
-
-
-
- {skillHistory.length === 0 ? ( -
-

{transI18n("noTurns")}

-
- ) : ( - skillHistory.map((turn, index) => { - const data = listAvatar.find(it => it.id === turn.avatarId.toString()); - if (!data) return null; - const text = getNameChar(locale, transI18n, data); - - return ( -
-
handleShow("action_detail_modal", data, turn)} - 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-50 sm:min-w-62.5 md:min-w-0" - > -
-
-
- - {text} -
-
- -
-
-
- {`${transI18n("useSkill")}: ${transI18n(attackTypeToString(turn.skillType).toLowerCase())}`} -
-
- {`${transI18n("totalDamage")}: ${Number(turn.totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}`} -
-
- {`${transI18n("actionValue")}: ${Number(turnHistory[turn.turnBattleId]?.actionValue || 0).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}`} -
-
-
-
- ); - - }) - )} -
-
- - - {/* Character Detail Modal */} - -
-
- handleCloseModal("action_detail_modal")} - > - ✕ - -
- -
-

- {transI18n("turnDetail").toUpperCase()} -

-
- - {selectAvatar && selectTurn && ( - -
-

{transI18n("characterInformation")}

-
-
-
- {transI18n("id")}: - {selectAvatar.id} -
-
- {transI18n("character")}: - -
-
-
- {getNameChar(locale, -
-
-
- -
-
-

{transI18n("skillType")}

-

{transI18n(attackTypeToString(selectTurn?.skillType).toLowerCase())}

-
-
-

{transI18n("skillName")}

-

{selectTurn?.skillName}

-
-
- -
-
-

{transI18n("cycle")}

-

{turnHistory[selectTurn?.turnBattleId].cycleIndex}

-
-
-

{transI18n("wave")}

-

{turnHistory[selectTurn?.turnBattleId].waveIndex}

-
-
- -
-
-

{transI18n("actionValue")}

-

{Number(turnHistory[selectTurn?.turnBattleId].actionValue).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}

-
-
-

{transI18n("totalDamage")}

-

{Number(selectTurn?.totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}

-
-
- -
-

{transI18n("damageDetails")}

- {selectTurn?.damageDetail?.length > 0 ? ( -
- {selectTurn.damageDetail.map((detail, idx) => ( -
- - {Number(detail.damage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })} - - - {transI18n(attackTypeToString(detail?.damage_type).toLowerCase())} - -
- ))} -
- ) : ( -

{transI18n("noDamageDetail")}

- )} - -
-
- )} - -
- -
-
- ); +"use client"; +import useAvatarDataStore from "@/stores/avatarDataStore"; +import useBattleDataStore from "@/stores/battleDataStore"; +import useLocaleStore from "@/stores/localeStore"; +import { attackTypeToString, CharacterBasic } from "@/types"; +import { SkillBattleInfo } from "@/types/mics"; +import { useEffect, useState, useRef } from "react"; +import { useTranslations } from "next-intl"; +import { motion } from "framer-motion"; +import { getNameChar } from "@/helper"; +import Image from "next/image"; +import NameAvatar from "../nameAvatar"; + +export default function ActionBar() { + const [selectTurn, setSelectTurn] = useState(null); + const [selectAvatar, setSelectAvatar] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + const { skillHistory, turnHistory, cycleIndex, waveIndex, maxWave } = useBattleDataStore(); + const { listAvatar } = useAvatarDataStore(); + const { locale } = useLocaleStore(); + const transI18n = useTranslations("DataAnalysisPage"); + const turnListRef = useRef(null); + + + const contentStyle: React.CSSProperties = { + + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }; + + const handleShow = (modalId: string, avatar: CharacterBasic, turn: SkillBattleInfo) => { + const modal = document.getElementById(modalId) as HTMLDialogElement | null; + if (modal) { + setSelectAvatar(avatar); + setSelectTurn(turn); + setIsModalOpen(true); + modal.showModal(); + } + }; + + // Close modal handler + const handleCloseModal = (modalId: string) => { + setIsModalOpen(false); + setSelectAvatar(null); + const modal = document.getElementById(modalId) as HTMLDialogElement | null; + if (modal) { + modal.close(); + } + }; + + // Handle ESC key to close modal + useEffect(() => { + const handleEscKey = (event: KeyboardEvent) => { + if (event.key === 'Escape' && isModalOpen) { + handleCloseModal("action_detail_modal"); + } + }; + + window.addEventListener('keydown', handleEscKey); + return () => window.removeEventListener('keydown', handleEscKey); + }, [isModalOpen]); + + // Scroll to the bottom when new turns are added + useEffect(() => { + if (turnListRef.current && skillHistory.length > 0) { + turnListRef.current.scrollTop = turnListRef.current.scrollHeight; + } + }, [skillHistory.length]); + + return ( +
+ + {transI18n("actionBar")} + +
+
+ + + + {waveIndex}/{maxWave} +
+
+ + + + {cycleIndex} +
+
+
+
+ {skillHistory.length === 0 ? ( +
+

{transI18n("noTurns")}

+
+ ) : ( + skillHistory.map((turn, index) => { + const data = listAvatar.find(it => it.id === turn.avatarId.toString()); + if (!data) return null; + const text = getNameChar(locale, transI18n, data); + + return ( +
+
handleShow("action_detail_modal", data, turn)} + 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-50 sm:min-w-62.5 md:min-w-0" + > +
+
+
+ + {text} +
+
+ +
+
+
+ {`${transI18n("useSkill")}: ${transI18n(attackTypeToString(turn.skillType).toLowerCase())}`} +
+
+ {`${transI18n("totalDamage")}: ${Number(turn.totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}`} +
+
+ {`${transI18n("actionValue")}: ${Number(turnHistory[turn.turnBattleId]?.actionValue || 0).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}`} +
+
+
+
+ ); + + }) + )} +
+
+ + + {/* Character Detail Modal */} + +
+
+ handleCloseModal("action_detail_modal")} + > + ✕ + +
+ +
+

+ {transI18n("turnDetail").toUpperCase()} +

+
+ + {selectAvatar && selectTurn && ( + +
+

{transI18n("characterInformation")}

+
+
+
+ {transI18n("id")}: + {selectAvatar.id} +
+
+ {transI18n("character")}: + +
+
+
+ {getNameChar(locale, +
+
+
+ +
+
+

{transI18n("skillType")}

+

{transI18n(attackTypeToString(selectTurn?.skillType).toLowerCase())}

+
+
+

{transI18n("skillName")}

+

{selectTurn?.skillName}

+
+
+ +
+
+

{transI18n("cycle")}

+

{turnHistory[selectTurn?.turnBattleId].cycleIndex}

+
+
+

{transI18n("wave")}

+

{turnHistory[selectTurn?.turnBattleId].waveIndex}

+
+
+ +
+
+

{transI18n("actionValue")}

+

{Number(turnHistory[selectTurn?.turnBattleId].actionValue).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}

+
+
+

{transI18n("totalDamage")}

+

{Number(selectTurn?.totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}

+
+
+ +
+

{transI18n("damageDetails")}

+ {selectTurn?.damageDetail?.length > 0 ? ( +
+ {selectTurn.damageDetail.map((detail, idx) => ( +
+ + {Number(detail.damage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })} + + + {transI18n(attackTypeToString(detail?.damage_type).toLowerCase())} + +
+ ))} +
+ ) : ( +

{transI18n("noDamageDetail")}

+ )} + +
+
+ )} + +
+ +
+
+ ); } \ No newline at end of file diff --git a/src/components/card/characterCard.tsx b/src/components/card/characterCard.tsx index e97aa86..5cca49b 100644 --- a/src/components/card/characterCard.tsx +++ b/src/components/card/characterCard.tsx @@ -1,99 +1,99 @@ -"use client"; - -import { getNameChar } from '@/helper'; -import useLocaleStore from '@/stores/localeStore'; -import { CharacterBasic } from '@/types'; -import NameAvatar from '../nameAvatar'; -import useBattleDataStore from '@/stores/battleDataStore'; -import Image from 'next/image'; -import { useTranslations } from 'next-intl'; - -interface CharacterCardProps { - data: CharacterBasic -} - -export default function CharacterCard({ data }: CharacterCardProps) { - const { locale } = useLocaleStore(); - const { avatarDetail } = useBattleDataStore() - const transI18n = useTranslations("DataAnalysisPage"); - const text = getNameChar(locale, transI18n, data) - - return ( -
  • -
    - -
    - ALT - {data?.damageType?.toLowerCase()} - {data.baseType.toLowerCase()} -
    -
    - - - {avatarDetail && ( -
    -
    - HP: - - - {Number(avatarDetail?.[Number(data.id)]?.stats?.HP ?? 0).toLocaleString(undefined, { maximumFractionDigits: 0 })} - - / - - {Number(avatarDetail?.[Number(data.id)]?.stats?.MaxHP ?? 100).toLocaleString(undefined, { maximumFractionDigits: 0 })} - - -
    - -
    -
    - - {Math.round(((avatarDetail?.[Number(data.id)]?.stats?.HP || 0) / (avatarDetail?.[Number(data.id)]?.stats?.MaxHP || 100)) * 100)}% - -
    - -
    - )} - -
  • - - ); -} +"use client"; + +import { getNameChar } from '@/helper'; +import useLocaleStore from '@/stores/localeStore'; +import { CharacterBasic } from '@/types'; +import NameAvatar from '../nameAvatar'; +import useBattleDataStore from '@/stores/battleDataStore'; +import Image from 'next/image'; +import { useTranslations } from 'next-intl'; + +interface CharacterCardProps { + data: CharacterBasic +} + +export default function CharacterCard({ data }: CharacterCardProps) { + const { locale } = useLocaleStore(); + const { avatarDetail } = useBattleDataStore() + const transI18n = useTranslations("DataAnalysisPage"); + const text = getNameChar(locale, transI18n, data) + + return ( +
  • +
    + +
    + ALT + {data?.damageType?.toLowerCase()} + {data.baseType.toLowerCase()} +
    +
    + + + {avatarDetail && ( +
    +
    + HP: + + + {Number(avatarDetail?.[Number(data.id)]?.stats?.HP ?? 0).toLocaleString(undefined, { maximumFractionDigits: 0 })} + + / + + {Number(avatarDetail?.[Number(data.id)]?.stats?.MaxHP ?? 100).toLocaleString(undefined, { maximumFractionDigits: 0 })} + + +
    + +
    +
    + + {Math.round(((avatarDetail?.[Number(data.id)]?.stats?.HP || 0) / (avatarDetail?.[Number(data.id)]?.stats?.MaxHP || 100)) * 100)}% + +
    + +
    + )} + +
  • + + ); +} diff --git a/src/components/chart/damageLineForAll.tsx b/src/components/chart/damageLineForAll.tsx index 6a3593f..245b8ae 100644 --- a/src/components/chart/damageLineForAll.tsx +++ b/src/components/chart/damageLineForAll.tsx @@ -1,84 +1,84 @@ -'use client'; -import { - Chart as ChartJS, - LineElement, - CategoryScale, - LinearScale, - PointElement, - Tooltip, - Legend, -} from 'chart.js'; -import { Line } from 'react-chartjs-2'; -import { useDamageLinesForAll } from '@/hooks/useDamageLinesForAll'; -import useAvatarDataStore from '@/stores/avatarDataStore'; -import useLocaleStore from '@/stores/localeStore'; -import { getNameChar } from '@/helper'; -import { useState } from 'react'; -import { useTranslations } from 'next-intl'; - -ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement, Tooltip, Legend); - -const colors = ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c']; - -export default function MultiCharLineChart() { - const [mode, setMode] = useState<1 | 2>(2); - const dataByAvatar = useDamageLinesForAll(mode); - const avatarIds = Object.keys(dataByAvatar).map(Number); - const { mapAvatar } = useAvatarDataStore() - const { locale } = useLocaleStore(); - const transI18n = useTranslations("DataAnalysisPage") - - const data = { - datasets: avatarIds.map((id, idx) => ({ - label: getNameChar(locale, transI18n, mapAvatar?.[id.toString()]), - data: dataByAvatar[id].map(({ x, y }: { x: number; y: number }) => { - return { - x: x.toFixed(2), - y: y.toFixed(2) - } - }), - borderColor: colors[idx % colors.length], - backgroundColor: colors[idx % colors.length], - fill: false, - tension: 0.3, - })), - }; - - const options = { - responsive: true, - scales: { - x: { - type: 'linear' as const, - position: 'bottom' as "bottom" | "center" | "left" | "top" | "right", - ticks: { - stepSize: 1, - }, - }, - y: { - beginAtZero: true, - }, - }, - }; - - return ( -
    -
    - {[ - { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, - { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, - ].map(({ mode: m, label, className }) => ( - - ))} -
    - - -
    - - ) -} +'use client'; +import { + Chart as ChartJS, + LineElement, + CategoryScale, + LinearScale, + PointElement, + Tooltip, + Legend, +} from 'chart.js'; +import { Line } from 'react-chartjs-2'; +import { useDamageLinesForAll } from '@/hooks/useDamageLinesForAll'; +import useAvatarDataStore from '@/stores/avatarDataStore'; +import useLocaleStore from '@/stores/localeStore'; +import { getNameChar } from '@/helper'; +import { useState } from 'react'; +import { useTranslations } from 'next-intl'; + +ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement, Tooltip, Legend); + +const colors = ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c']; + +export default function MultiCharLineChart() { + const [mode, setMode] = useState<1 | 2>(2); + const dataByAvatar = useDamageLinesForAll(mode); + const avatarIds = Object.keys(dataByAvatar).map(Number); + const { mapAvatar } = useAvatarDataStore() + const { locale } = useLocaleStore(); + const transI18n = useTranslations("DataAnalysisPage") + + const data = { + datasets: avatarIds.map((id, idx) => ({ + label: getNameChar(locale, transI18n, mapAvatar?.[id.toString()]), + data: dataByAvatar[id].map(({ x, y }: { x: number; y: number }) => { + return { + x: x.toFixed(2), + y: y.toFixed(2) + } + }), + borderColor: colors[idx % colors.length], + backgroundColor: colors[idx % colors.length], + fill: false, + tension: 0.3, + })), + }; + + const options = { + responsive: true, + scales: { + x: { + type: 'linear' as const, + position: 'bottom' as "bottom" | "center" | "left" | "top" | "right", + ticks: { + stepSize: 1, + }, + }, + y: { + beginAtZero: true, + }, + }, + }; + + return ( +
    +
    + {[ + { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
    + + +
    + + ) +} diff --git a/src/components/chart/damageLineForOne.tsx b/src/components/chart/damageLineForOne.tsx index 8f95719..0056a85 100644 --- a/src/components/chart/damageLineForOne.tsx +++ b/src/components/chart/damageLineForOne.tsx @@ -1,80 +1,80 @@ -"use client"; -import { Line } from "react-chartjs-2"; -import { ChartOptions } from "chart.js"; -import { useDamageLineForOne } from "@/hooks/useDamageLineForOne"; -import { - Chart as ChartJS, - LineElement, - PointElement, - CategoryScale, - LinearScale, - Title, - Tooltip, - Legend, - Filler, -} from "chart.js"; -import { useTranslations } from "next-intl"; -import { useState } from "react"; - -ChartJS.register( - LineElement, - PointElement, - CategoryScale, - LinearScale, - Title, - Tooltip, - Legend, - Filler -); - - -export function DamageLineForOne({ avatarId }: { avatarId: number; }) { - const [mode, setMode] = useState<0 | 1>(1); - const dataRaw = useDamageLineForOne(avatarId, mode); - const transI18n = useTranslations("DataAnalysisPage"); - const data = { - labels: dataRaw.map(d => Number(d.x.toFixed(2))), - datasets: [ - { - label: mode === 1 ? transI18n("cumulativeDamage") : transI18n("damageByActionValue"), - data: dataRaw.map(d => Number(d.y.toFixed(2))), - borderColor: "rgba(75,192,192,1)", - backgroundColor: "rgba(75,192,192,0.2)", - tension: 0.3, - fill: true, - }, - ], - }; - - const options: ChartOptions<"line"> = { - responsive: true, - plugins: { - legend: { display: true }, - }, - scales: { - x: { title: { display: true, text: transI18n("actionValue") } }, - y: { title: { display: true, text: transI18n("damage") }, beginAtZero: true }, - }, - - }; - - return ( -
    -
    - {[ - { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, - { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, - ].map(({ mode: m, label, className }) => ( - - ))} -
    - -
    - ) -} +"use client"; +import { Line } from "react-chartjs-2"; +import { ChartOptions } from "chart.js"; +import { useDamageLineForOne } from "@/hooks/useDamageLineForOne"; +import { + Chart as ChartJS, + LineElement, + PointElement, + CategoryScale, + LinearScale, + Title, + Tooltip, + Legend, + Filler, +} from "chart.js"; +import { useTranslations } from "next-intl"; +import { useState } from "react"; + +ChartJS.register( + LineElement, + PointElement, + CategoryScale, + LinearScale, + Title, + Tooltip, + Legend, + Filler +); + + +export function DamageLineForOne({ avatarId }: { avatarId: number; }) { + const [mode, setMode] = useState<0 | 1>(1); + const dataRaw = useDamageLineForOne(avatarId, mode); + const transI18n = useTranslations("DataAnalysisPage"); + const data = { + labels: dataRaw.map(d => Number(d.x.toFixed(2))), + datasets: [ + { + label: mode === 1 ? transI18n("cumulativeDamage") : transI18n("damageByActionValue"), + data: dataRaw.map(d => Number(d.y.toFixed(2))), + borderColor: "rgba(75,192,192,1)", + backgroundColor: "rgba(75,192,192,0.2)", + tension: 0.3, + fill: true, + }, + ], + }; + + const options: ChartOptions<"line"> = { + responsive: true, + plugins: { + legend: { display: true }, + }, + scales: { + x: { title: { display: true, text: transI18n("actionValue") } }, + y: { title: { display: true, text: transI18n("damage") }, beginAtZero: true }, + }, + + }; + + return ( +
    +
    + {[ + { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
    + +
    + ) +} diff --git a/src/components/chart/damagePerAvatarForAll.tsx b/src/components/chart/damagePerAvatarForAll.tsx index 84c4d4f..9533ff4 100644 --- a/src/components/chart/damagePerAvatarForAll.tsx +++ b/src/components/chart/damagePerAvatarForAll.tsx @@ -1,177 +1,177 @@ -'use client'; -import { useState } from 'react'; -import { getNameChar } from '@/helper'; -import useAvatarDataStore from '@/stores/avatarDataStore'; -import useBattleDataStore from '@/stores/battleDataStore'; -import useLocaleStore from '@/stores/localeStore'; -import { - Chart as ChartJS, - BarElement, - CategoryScale, - LinearScale, - Tooltip, - Legend -} from 'chart.js'; -import { Bar } from 'react-chartjs-2'; -import { useTranslations } from 'next-intl'; -import {attackTypeToString} from "@/types"; - -ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend); - -const colorPalette = [ - 'rgba(255, 99, 132, 0.6)', - 'rgba(54, 162, 235, 0.6)', - 'rgba(255, 206, 86, 0.6)', - 'rgba(75, 192, 192, 0.6)', - 'rgba(153, 102, 255, 0.6)', - 'rgba(255, 159, 64, 0.6)', - 'rgba(199, 199, 199, 0.6)', - 'rgba(255, 99, 255, 0.6)', - 'rgba(0, 200, 83, 0.6)', - 'rgba(255, 112, 67, 0.6)', - 'rgba(63, 81, 181, 0.6)', - 'rgba(0, 188, 212, 0.6)', - 'rgba(233, 30, 99, 0.6)', - 'rgba(124, 179, 66, 0.6)', - 'rgba(255, 235, 59, 0.6)', - 'rgba(158, 158, 158, 0.6)', - 'rgba(121, 85, 72, 0.6)', - 'rgba(96, 125, 139, 0.6)', - 'rgba(100, 181, 246, 0.6)', - 'rgba(255, 171, 145, 0.6)', - 'rgba(174, 213, 129, 0.6)', - 'rgba(255, 245, 157, 0.6)' - ]; - -const borderPalette = colorPalette.map((c) => c.replace('0.6', '1')); - -export default function DamagePerAvatarForAll() { - const transI18n = useTranslations("DataAnalysisPage"); - const { lineup, skillHistory } = useBattleDataStore(); - const { mapAvatar } = useAvatarDataStore(); - const { locale } = useLocaleStore(); - - const [mode, setMode] = useState(2); - - const avatarMap = lineup.map((avatar) => { - if (!mapAvatar?.[avatar.avatarId.toString()]) return undefined; - return { - avatarId: avatar.avatarId, - avatarName: getNameChar(locale, transI18n, mapAvatar?.[avatar.avatarId.toString()]) - }; - }).filter(Boolean) as { avatarId: number, avatarName: string }[]; - - const labels = avatarMap.map(a => a.avatarName); - - let datasets: Array<{ - label: string; - data: number[]; - backgroundColor: string | string[]; - borderColor: string | string[]; - borderWidth: number; - stack?: string; - }> = []; - - if (mode === 1) { - datasets = [ - { - label: transI18n("totalDamage"), - data: avatarMap.map(({ avatarId }) => { - const total = skillHistory - .filter(skill => skill.avatarId === avatarId) - .reduce((sum, s) => sum + s.totalDamage, 0); - return total; - }), - backgroundColor: avatarMap.map((_, i) => colorPalette[i % colorPalette.length]), - borderColor: avatarMap.map((_, i) => borderPalette[i % borderPalette.length]), - borderWidth: 1 - } - ]; - } - - if (mode === 2) { - const damageTypesSet = new Set(); - avatarMap.forEach(({ avatarId }) => { - skillHistory.filter(s => s.avatarId === avatarId).forEach(s => - s.damageDetail?.forEach(d => d.damage_type && damageTypesSet.add(transI18n(attackTypeToString(d?.damage_type).toLowerCase()))) - ); - }); - const damageTypes = Array.from(damageTypesSet); - - datasets = damageTypes.map((dt, i) => ({ - label: dt, - data: avatarMap.map(({ avatarId }) => { - const total = skillHistory - .filter(skill => skill.avatarId === avatarId) - .flatMap(s => s.damageDetail || []) - .filter(d => transI18n(attackTypeToString(d?.damage_type).toLowerCase()) === dt) - .reduce((sum, d) => sum + d.damage, 0); - return total; - }), - backgroundColor: colorPalette[i % colorPalette.length], - borderColor: borderPalette[i % borderPalette.length], - borderWidth: 1, - stack: transI18n('damage') - })); - } - - if (mode === 3) { - const skillTypesSet = new Set(); - skillHistory.forEach(s => skillTypesSet.add(transI18n(attackTypeToString(s?.skillType)?.toLowerCase()))); - const skillTypes = Array.from(skillTypesSet); - - datasets = skillTypes.map((st, i) => ({ - label: st, - data: avatarMap.map(({ avatarId }) => { - const total = skillHistory - .filter(skill => skill.avatarId === avatarId && transI18n(attackTypeToString(skill?.skillType).toLowerCase()) === st) - .reduce((sum, s) => sum + s.totalDamage, 0); - return total; - }), - backgroundColor: colorPalette[i % colorPalette.length], - borderColor: borderPalette[i % borderPalette.length], - borderWidth: 1, - stack: transI18n('skill') - })); - } - - const options = { - responsive: true, - scales: { - y: { - beginAtZero: true, - stacked: mode !== 1, - ticks: { - precision: 0, - }, - }, - x: { - stacked: mode !== 1, - }, - }, - - }; - - return ( -
    -
    - {[ - { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, - { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, - { mode: 3, label: `${transI18n("type")} 3`, className: "btn-accent" }, - ].map(({ mode: m, label, className }) => ( - - ))} -
    - - -
    - - ); -} +'use client'; +import { useState } from 'react'; +import { getNameChar } from '@/helper'; +import useAvatarDataStore from '@/stores/avatarDataStore'; +import useBattleDataStore from '@/stores/battleDataStore'; +import useLocaleStore from '@/stores/localeStore'; +import { + Chart as ChartJS, + BarElement, + CategoryScale, + LinearScale, + Tooltip, + Legend +} from 'chart.js'; +import { Bar } from 'react-chartjs-2'; +import { useTranslations } from 'next-intl'; +import {attackTypeToString} from "@/types"; + +ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend); + +const colorPalette = [ + 'rgba(255, 99, 132, 0.6)', + 'rgba(54, 162, 235, 0.6)', + 'rgba(255, 206, 86, 0.6)', + 'rgba(75, 192, 192, 0.6)', + 'rgba(153, 102, 255, 0.6)', + 'rgba(255, 159, 64, 0.6)', + 'rgba(199, 199, 199, 0.6)', + 'rgba(255, 99, 255, 0.6)', + 'rgba(0, 200, 83, 0.6)', + 'rgba(255, 112, 67, 0.6)', + 'rgba(63, 81, 181, 0.6)', + 'rgba(0, 188, 212, 0.6)', + 'rgba(233, 30, 99, 0.6)', + 'rgba(124, 179, 66, 0.6)', + 'rgba(255, 235, 59, 0.6)', + 'rgba(158, 158, 158, 0.6)', + 'rgba(121, 85, 72, 0.6)', + 'rgba(96, 125, 139, 0.6)', + 'rgba(100, 181, 246, 0.6)', + 'rgba(255, 171, 145, 0.6)', + 'rgba(174, 213, 129, 0.6)', + 'rgba(255, 245, 157, 0.6)' + ]; + +const borderPalette = colorPalette.map((c) => c.replace('0.6', '1')); + +export default function DamagePerAvatarForAll() { + const transI18n = useTranslations("DataAnalysisPage"); + const { lineup, skillHistory } = useBattleDataStore(); + const { mapAvatar } = useAvatarDataStore(); + const { locale } = useLocaleStore(); + + const [mode, setMode] = useState(2); + + const avatarMap = lineup.map((avatar) => { + if (!mapAvatar?.[avatar.avatarId.toString()]) return undefined; + return { + avatarId: avatar.avatarId, + avatarName: getNameChar(locale, transI18n, mapAvatar?.[avatar.avatarId.toString()]) + }; + }).filter(Boolean) as { avatarId: number, avatarName: string }[]; + + const labels = avatarMap.map(a => a.avatarName); + + let datasets: Array<{ + label: string; + data: number[]; + backgroundColor: string | string[]; + borderColor: string | string[]; + borderWidth: number; + stack?: string; + }> = []; + + if (mode === 1) { + datasets = [ + { + label: transI18n("totalDamage"), + data: avatarMap.map(({ avatarId }) => { + const total = skillHistory + .filter(skill => skill.avatarId === avatarId) + .reduce((sum, s) => sum + s.totalDamage, 0); + return total; + }), + backgroundColor: avatarMap.map((_, i) => colorPalette[i % colorPalette.length]), + borderColor: avatarMap.map((_, i) => borderPalette[i % borderPalette.length]), + borderWidth: 1 + } + ]; + } + + if (mode === 2) { + const damageTypesSet = new Set(); + avatarMap.forEach(({ avatarId }) => { + skillHistory.filter(s => s.avatarId === avatarId).forEach(s => + s.damageDetail?.forEach(d => d.damage_type && damageTypesSet.add(transI18n(attackTypeToString(d?.damage_type).toLowerCase()))) + ); + }); + const damageTypes = Array.from(damageTypesSet); + + datasets = damageTypes.map((dt, i) => ({ + label: dt, + data: avatarMap.map(({ avatarId }) => { + const total = skillHistory + .filter(skill => skill.avatarId === avatarId) + .flatMap(s => s.damageDetail || []) + .filter(d => transI18n(attackTypeToString(d?.damage_type).toLowerCase()) === dt) + .reduce((sum, d) => sum + d.damage, 0); + return total; + }), + backgroundColor: colorPalette[i % colorPalette.length], + borderColor: borderPalette[i % borderPalette.length], + borderWidth: 1, + stack: transI18n('damage') + })); + } + + if (mode === 3) { + const skillTypesSet = new Set(); + skillHistory.forEach(s => skillTypesSet.add(transI18n(attackTypeToString(s?.skillType)?.toLowerCase()))); + const skillTypes = Array.from(skillTypesSet); + + datasets = skillTypes.map((st, i) => ({ + label: st, + data: avatarMap.map(({ avatarId }) => { + const total = skillHistory + .filter(skill => skill.avatarId === avatarId && transI18n(attackTypeToString(skill?.skillType).toLowerCase()) === st) + .reduce((sum, s) => sum + s.totalDamage, 0); + return total; + }), + backgroundColor: colorPalette[i % colorPalette.length], + borderColor: borderPalette[i % borderPalette.length], + borderWidth: 1, + stack: transI18n('skill') + })); + } + + const options = { + responsive: true, + scales: { + y: { + beginAtZero: true, + stacked: mode !== 1, + ticks: { + precision: 0, + }, + }, + x: { + stacked: mode !== 1, + }, + }, + + }; + + return ( +
    +
    + {[ + { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, + { mode: 3, label: `${transI18n("type")} 3`, className: "btn-accent" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
    + + +
    + + ); +} diff --git a/src/components/chart/damagePerCycleForAll.tsx b/src/components/chart/damagePerCycleForAll.tsx index f3767b9..75b0e6e 100644 --- a/src/components/chart/damagePerCycleForAll.tsx +++ b/src/components/chart/damagePerCycleForAll.tsx @@ -1,60 +1,60 @@ -"use client"; -import { Bar } from "react-chartjs-2"; -import { ChartOptions } from "chart.js"; -import { useDamagePerCycleForAll } from "@/hooks/useDamagePerCycle"; -import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js"; -import { useTranslations } from "next-intl"; -import { useState } from "react"; - -ChartJS.register(BarElement, ArcElement, RadialLinearScale); -export default function DamagePerCycleForAll() { - const [mode, setMode] = useState<0 | 1 | 2>(0); - const dataRaw = useDamagePerCycleForAll(mode); - const transI18n = useTranslations("DataAnalysisPage"); - const data = { - labels: dataRaw.map(d => d.x), - datasets: [ - { - label: mode === 0 ? `${transI18n("damagePerCycleAndWave")}` : mode === 1 ? `${transI18n("damagePerCycle")}` : `${transI18n("damagePerWave")}`, - data: dataRaw.map(d => d.y), - backgroundColor: dataRaw.map((_, i) => - ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'][i % 7] - ), - }, - ], - - }; - - const options: ChartOptions<"bar"> = { - responsive: true, - plugins: { - legend: { display: true }, - }, - scales: { - x: { title: { display: true, text: transI18n("cycleCount") } }, - y: { title: { display: true, text: transI18n("totalDamage") }, beginAtZero: true }, - }, - - }; - - return ( -
    -
    - {[ - { mode: 0, label: `${transI18n("type")} 1`, className: "btn-primary" }, - { mode: 1, label: `${transI18n("type")} 2`, className: "btn-warning" }, - { mode: 2, label: `${transI18n("type")} 3`, className: "btn-accent" }, - ].map(({ mode: m, label, className }) => ( - - ))} -
    - -
    - ) -} +"use client"; +import { Bar } from "react-chartjs-2"; +import { ChartOptions } from "chart.js"; +import { useDamagePerCycleForAll } from "@/hooks/useDamagePerCycle"; +import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js"; +import { useTranslations } from "next-intl"; +import { useState } from "react"; + +ChartJS.register(BarElement, ArcElement, RadialLinearScale); +export default function DamagePerCycleForAll() { + const [mode, setMode] = useState<0 | 1 | 2>(0); + const dataRaw = useDamagePerCycleForAll(mode); + const transI18n = useTranslations("DataAnalysisPage"); + const data = { + labels: dataRaw.map(d => d.x), + datasets: [ + { + label: mode === 0 ? `${transI18n("damagePerCycleAndWave")}` : mode === 1 ? `${transI18n("damagePerCycle")}` : `${transI18n("damagePerWave")}`, + data: dataRaw.map(d => d.y), + backgroundColor: dataRaw.map((_, i) => + ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'][i % 7] + ), + }, + ], + + }; + + const options: ChartOptions<"bar"> = { + responsive: true, + plugins: { + legend: { display: true }, + }, + scales: { + x: { title: { display: true, text: transI18n("cycleCount") } }, + y: { title: { display: true, text: transI18n("totalDamage") }, beginAtZero: true }, + }, + + }; + + return ( +
    +
    + {[ + { mode: 0, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 1, label: `${transI18n("type")} 2`, className: "btn-warning" }, + { mode: 2, label: `${transI18n("type")} 3`, className: "btn-accent" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
    + +
    + ) +} diff --git a/src/components/chart/damagePerCycleForOne.tsx b/src/components/chart/damagePerCycleForOne.tsx index 21f80a2..970e6ff 100644 --- a/src/components/chart/damagePerCycleForOne.tsx +++ b/src/components/chart/damagePerCycleForOne.tsx @@ -1,64 +1,64 @@ -"use client"; -import { Bar } from "react-chartjs-2"; -import { ChartOptions } from "chart.js"; -import { useDamagePerCycleForOne } from "@/hooks/useDamagePerCycle"; - -import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js"; -import { useTranslations } from "next-intl"; -import { useState } from "react"; - -ChartJS.register(BarElement, ArcElement, RadialLinearScale); -export function DamagePerCycleForOne({ - avatarId, -}: { - avatarId: number; -}) { - const [mode, setMode] = useState<0 | 1 | 2>(0); - const dataRaw = useDamagePerCycleForOne(avatarId, mode); - const transI18n = useTranslations("DataAnalysisPage"); - const data = { - labels: dataRaw.map(d => d.x), - datasets: [ - { - label: mode === 0 ? `${transI18n("damagePerCycleAndWave")}` : mode === 1 ? `${transI18n("damagePerCycle")}` : `${transI18n("damagePerWave")}`, - data: dataRaw.map(d => d.y), - backgroundColor: dataRaw.map((_, i) => - ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'][i % 7] - ), - }, - ], - }; - - const options: ChartOptions<"bar"> = { - responsive: true, - plugins: { - legend: { display: true }, - }, - scales: { - x: { title: { display: true, text: transI18n("cycleCount") } }, - y: { title: { display: true, text: transI18n("totalDamage") }, beginAtZero: true }, - }, - - }; - - return ( -
    -
    - {[ - { mode: 0, label: `${transI18n("type")} 1`, className: "btn-primary" }, - { mode: 1, label: `${transI18n("type")} 2`, className: "btn-warning" }, - { mode: 2, label: `${transI18n("type")} 3`, className: "btn-warning" }, - ].map(({ mode: m, label, className }) => ( - - ))} -
    - -
    - ) -} +"use client"; +import { Bar } from "react-chartjs-2"; +import { ChartOptions } from "chart.js"; +import { useDamagePerCycleForOne } from "@/hooks/useDamagePerCycle"; + +import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js"; +import { useTranslations } from "next-intl"; +import { useState } from "react"; + +ChartJS.register(BarElement, ArcElement, RadialLinearScale); +export function DamagePerCycleForOne({ + avatarId, +}: { + avatarId: number; +}) { + const [mode, setMode] = useState<0 | 1 | 2>(0); + const dataRaw = useDamagePerCycleForOne(avatarId, mode); + const transI18n = useTranslations("DataAnalysisPage"); + const data = { + labels: dataRaw.map(d => d.x), + datasets: [ + { + label: mode === 0 ? `${transI18n("damagePerCycleAndWave")}` : mode === 1 ? `${transI18n("damagePerCycle")}` : `${transI18n("damagePerWave")}`, + data: dataRaw.map(d => d.y), + backgroundColor: dataRaw.map((_, i) => + ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'][i % 7] + ), + }, + ], + }; + + const options: ChartOptions<"bar"> = { + responsive: true, + plugins: { + legend: { display: true }, + }, + scales: { + x: { title: { display: true, text: transI18n("cycleCount") } }, + y: { title: { display: true, text: transI18n("totalDamage") }, beginAtZero: true }, + }, + + }; + + return ( +
    +
    + {[ + { mode: 0, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 1, label: `${transI18n("type")} 2`, className: "btn-warning" }, + { mode: 2, label: `${transI18n("type")} 3`, className: "btn-warning" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
    + +
    + ) +} diff --git a/src/components/chart/damagePercentForAll.tsx b/src/components/chart/damagePercentForAll.tsx index d6f6572..2b2a356 100644 --- a/src/components/chart/damagePercentForAll.tsx +++ b/src/components/chart/damagePercentForAll.tsx @@ -1,91 +1,91 @@ -'use client'; -import { - Chart as ChartJS, - ArcElement, - Tooltip, - Legend -} from 'chart.js'; -import { Pie } from 'react-chartjs-2'; -import { useDamagePercentPerAvatar } from '@/hooks/useDamagePercentPerAvatar'; -import useAvatarDataStore from '@/stores/avatarDataStore'; -import useLocaleStore from '@/stores/localeStore'; -import { getNameChar } from '@/helper'; -import { useTranslations } from 'next-intl'; -import { useState } from 'react'; -import { useDamagePercentByType } from '@/hooks/useDamagePercentForDamgeType'; - -ChartJS.register(ArcElement, Tooltip, Legend); - -const colors = [ - '#f87171', '#34d399', '#60a5fa', '#facc15', - '#a78bfa', '#fb923c', '#f472b6', '#10b981', - '#fcd34d', '#c084fc', '#f97316', '#ec4899', - '#4ade80', '#fbbf24', '#8b5cf6', '#f43f5e' -]; - -export default function DamagePercentChartForAll() { - const [mode, setMode] = useState<1 | 2>(1); - const damageByAvatar = useDamagePercentPerAvatar(); - const damageByType = useDamagePercentByType(); - const { mapAvatar } = useAvatarDataStore(); - const { locale } = useLocaleStore(); - const transI18n = useTranslations("DataAnalysisPage"); - - const chartData = { - labels: (mode === 1 - ? damageByAvatar.map(d => - getNameChar(locale, transI18n, mapAvatar?.[d.avatarId.toString()]) - ) - : damageByType.map(d => transI18n(d.type.toLowerCase())) - ), - datasets: [ - { - label: '% ' + transI18n("damage"), - data: (mode === 1 - ? damageByAvatar.map(d => d.percent.toFixed(2)) - : damageByType.map(d => d.percent.toFixed(2)) - ), - backgroundColor: colors, - borderWidth: 1, - }, - ], - }; - - const options = { - responsive: true, - plugins: { - tooltip: { - callbacks: { - label: (ctx: import('chart.js').TooltipItem<'pie'>) => `${ctx.label}: ${ctx.raw}%`, - }, - }, - datalabels: { - display: false, - }, - }, - }; - - return ( -
    -
    - {[ - { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, - { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, - ].map(({ mode: m, label, className }) => ( - - ))} -
    - -
    - -
    -
    - ); - -} +'use client'; +import { + Chart as ChartJS, + ArcElement, + Tooltip, + Legend +} from 'chart.js'; +import { Pie } from 'react-chartjs-2'; +import { useDamagePercentPerAvatar } from '@/hooks/useDamagePercentPerAvatar'; +import useAvatarDataStore from '@/stores/avatarDataStore'; +import useLocaleStore from '@/stores/localeStore'; +import { getNameChar } from '@/helper'; +import { useTranslations } from 'next-intl'; +import { useState } from 'react'; +import { useDamagePercentByType } from '@/hooks/useDamagePercentForDamgeType'; + +ChartJS.register(ArcElement, Tooltip, Legend); + +const colors = [ + '#f87171', '#34d399', '#60a5fa', '#facc15', + '#a78bfa', '#fb923c', '#f472b6', '#10b981', + '#fcd34d', '#c084fc', '#f97316', '#ec4899', + '#4ade80', '#fbbf24', '#8b5cf6', '#f43f5e' +]; + +export default function DamagePercentChartForAll() { + const [mode, setMode] = useState<1 | 2>(1); + const damageByAvatar = useDamagePercentPerAvatar(); + const damageByType = useDamagePercentByType(); + const { mapAvatar } = useAvatarDataStore(); + const { locale } = useLocaleStore(); + const transI18n = useTranslations("DataAnalysisPage"); + + const chartData = { + labels: (mode === 1 + ? damageByAvatar.map(d => + getNameChar(locale, transI18n, mapAvatar?.[d.avatarId.toString()]) + ) + : damageByType.map(d => transI18n(d.type.toLowerCase())) + ), + datasets: [ + { + label: '% ' + transI18n("damage"), + data: (mode === 1 + ? damageByAvatar.map(d => d.percent.toFixed(2)) + : damageByType.map(d => d.percent.toFixed(2)) + ), + backgroundColor: colors, + borderWidth: 1, + }, + ], + }; + + const options = { + responsive: true, + plugins: { + tooltip: { + callbacks: { + label: (ctx: import('chart.js').TooltipItem<'pie'>) => `${ctx.label}: ${ctx.raw}%`, + }, + }, + datalabels: { + display: false, + }, + }, + }; + + return ( +
    +
    + {[ + { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
    + +
    + +
    +
    + ); + +} diff --git a/src/components/chart/skillBarChart.tsx b/src/components/chart/skillBarChart.tsx index 240c70e..65f04fa 100644 --- a/src/components/chart/skillBarChart.tsx +++ b/src/components/chart/skillBarChart.tsx @@ -1,70 +1,70 @@ -'use client'; -import { useState } from 'react'; -import { Bar } from 'react-chartjs-2'; -import { Chart as ChartJS, BarElement, CategoryScale, LinearScale, Tooltip, Legend } from 'chart.js'; -import { useTranslations } from 'next-intl'; -import { useSkillDamageForAvatar } from '@/hooks/useSkillDamageForAvatar'; -import { useDamageByTypeForAvatar } from '@/hooks/useDamageTypeForAvatar'; - - -ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend); - -export default function SkillBarChart({ avatarId }: { avatarId: number }) { - const [mode, setMode] = useState<1 | 2>(2); - - const transI18n = useTranslations('DataAnalysisPage'); - const { labels: skillLabels, damageValues: skillDamageValues } = useSkillDamageForAvatar(avatarId); - const { labels: typeLabels, damageValues: typeDamageValues } = useDamageByTypeForAvatar(avatarId); - - const colors = ['#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c']; - - const data = { - labels: mode === 1 ? skillLabels.map(it => transI18n(it)) : typeLabels.map(it => transI18n(it)), - datasets: [ - { - label: mode === 1 - ? transI18n('skill') - : transI18n('damage'), - data: mode === 1 ? skillDamageValues : typeDamageValues, - backgroundColor: colors.slice(0, (mode === 1 ? skillLabels : typeLabels).length), - borderColor: '#111', - borderWidth: 1, - }, - ], - }; - - const options = { - responsive: true, - scales: { - y: { - beginAtZero: true, - ticks: { precision: 0 }, - }, - }, - plugins: { - legend: { - display: false, - }, - }, - }; - - return ( -
    -
    - {[ - { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, - { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, - ].map(({ mode: m, label, className }) => ( - - ))} -
    - -
    - ); -} +'use client'; +import { useState } from 'react'; +import { Bar } from 'react-chartjs-2'; +import { Chart as ChartJS, BarElement, CategoryScale, LinearScale, Tooltip, Legend } from 'chart.js'; +import { useTranslations } from 'next-intl'; +import { useSkillDamageForAvatar } from '@/hooks/useSkillDamageForAvatar'; +import { useDamageByTypeForAvatar } from '@/hooks/useDamageTypeForAvatar'; + + +ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend); + +export default function SkillBarChart({ avatarId }: { avatarId: number }) { + const [mode, setMode] = useState<1 | 2>(2); + + const transI18n = useTranslations('DataAnalysisPage'); + const { labels: skillLabels, damageValues: skillDamageValues } = useSkillDamageForAvatar(avatarId); + const { labels: typeLabels, damageValues: typeDamageValues } = useDamageByTypeForAvatar(avatarId); + + const colors = ['#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c']; + + const data = { + labels: mode === 1 ? skillLabels.map(it => transI18n(it)) : typeLabels.map(it => transI18n(it)), + datasets: [ + { + label: mode === 1 + ? transI18n('skill') + : transI18n('damage'), + data: mode === 1 ? skillDamageValues : typeDamageValues, + backgroundColor: colors.slice(0, (mode === 1 ? skillLabels : typeLabels).length), + borderColor: '#111', + borderWidth: 1, + }, + ], + }; + + const options = { + responsive: true, + scales: { + y: { + beginAtZero: true, + ticks: { precision: 0 }, + }, + }, + plugins: { + legend: { + display: false, + }, + }, + }; + + return ( +
    +
    + {[ + { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
    + +
    + ); +} diff --git a/src/components/chart/skillPieChart.tsx b/src/components/chart/skillPieChart.tsx index 49bd61e..477a555 100644 --- a/src/components/chart/skillPieChart.tsx +++ b/src/components/chart/skillPieChart.tsx @@ -1,75 +1,75 @@ -'use client'; -import { useState } from 'react'; -import { Pie } from 'react-chartjs-2'; -import { - Chart as ChartJS, - ArcElement, - Tooltip, - Legend, -} from 'chart.js'; -import { useTranslations } from 'next-intl'; -import { useSkillDamageForAvatar } from '@/hooks/useSkillDamageForAvatar'; -import { useDamageByTypeForAvatar } from '@/hooks/useDamageTypeForAvatar'; - -ChartJS.register(ArcElement, Tooltip, Legend); - -const COLORS = [ - '#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c', - '#f472b6', '#38bdf8', '#a3e635', '#fdba74', '#c084fc', '#4ade80', - '#fcd34d', '#818cf8', '#f43f5e', '#14b8a6', '#e879f9', '#86efac', -]; - -export default function SkillPieChart({ avatarId }: { avatarId: number }) { - const [mode, setMode] = useState<1 | 2>(2); - const transI18n = useTranslations("DataAnalysisPage"); - - const skillData = useSkillDamageForAvatar(avatarId); - const typeData = useDamageByTypeForAvatar(avatarId); - - const { labels, damageValues } = mode === 1 ? skillData : typeData; - const total = damageValues.reduce((sum, val) => sum + val, 0); - - const labelsWithPercent = labels.map((label, index) => { - const value = damageValues[index]; - const percent = total ? ((value / total) * 100).toFixed(1) : '0.0'; - return `${transI18n(label.toLowerCase())} (${percent}%)`; - }); - - const data = { - labels: labelsWithPercent, - datasets: [ - { - data: damageValues, - backgroundColor: COLORS.slice(0, labels.length), - borderColor: '#fff', - borderWidth: 1, - }, - ], - }; - - return ( -
    -
    - {[ - { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, - { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, - ].map(({ mode: m, label, className }) => ( - - ))} -
    - -
    - ); -} +'use client'; +import { useState } from 'react'; +import { Pie } from 'react-chartjs-2'; +import { + Chart as ChartJS, + ArcElement, + Tooltip, + Legend, +} from 'chart.js'; +import { useTranslations } from 'next-intl'; +import { useSkillDamageForAvatar } from '@/hooks/useSkillDamageForAvatar'; +import { useDamageByTypeForAvatar } from '@/hooks/useDamageTypeForAvatar'; + +ChartJS.register(ArcElement, Tooltip, Legend); + +const COLORS = [ + '#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c', + '#f472b6', '#38bdf8', '#a3e635', '#fdba74', '#c084fc', '#4ade80', + '#fcd34d', '#818cf8', '#f43f5e', '#14b8a6', '#e879f9', '#86efac', +]; + +export default function SkillPieChart({ avatarId }: { avatarId: number }) { + const [mode, setMode] = useState<1 | 2>(2); + const transI18n = useTranslations("DataAnalysisPage"); + + const skillData = useSkillDamageForAvatar(avatarId); + const typeData = useDamageByTypeForAvatar(avatarId); + + const { labels, damageValues } = mode === 1 ? skillData : typeData; + const total = damageValues.reduce((sum, val) => sum + val, 0); + + const labelsWithPercent = labels.map((label, index) => { + const value = damageValues[index]; + const percent = total ? ((value / total) * 100).toFixed(1) : '0.0'; + return `${transI18n(label.toLowerCase())} (${percent}%)`; + }); + + const data = { + labels: labelsWithPercent, + datasets: [ + { + data: damageValues, + backgroundColor: COLORS.slice(0, labels.length), + borderColor: '#fff', + borderWidth: 1, + }, + ], + }; + + return ( +
    +
    + {[ + { mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" }, + { mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" }, + ].map(({ mode: m, label, className }) => ( + + ))} +
    + +
    + ); +} diff --git a/src/components/enemybar/index.tsx b/src/components/enemybar/index.tsx index ea294df..940e98e 100644 --- a/src/components/enemybar/index.tsx +++ b/src/components/enemybar/index.tsx @@ -1,79 +1,79 @@ -"use client" - -import useBattleDataStore from "@/stores/battleDataStore"; -import Image from "next/image"; -import useAvatarDataStore from "@/stores/avatarDataStore"; -import { getNameEnemy } from "@/helper/getNameChar"; - -import useLocaleStore from "@/stores/localeStore"; -import NameAvatar from "../nameAvatar"; - -export default function EnemyBar() { - const { enemyDetail } = useBattleDataStore() - const { mapEnemy } = useAvatarDataStore() - const { locale } = useLocaleStore() - - return ( -
    -
    - {enemyDetail && Object.values(enemyDetail).filter((enemy) => ( - enemy.stats.HP <= enemy.maxHP)).map((enemy, uid) => ( -
    -
    -
    - {mapEnemy?.[enemy.id.toString()]?.icon && ( - {enemy.name} - )} - -
    - -

    Level {enemy.level || 1}

    -
    -
    -
    - -
    -
    -
    HP:
    -
    -
    - {Number(enemy?.stats?.HP ?? 0).toLocaleString(undefined, { maximumFractionDigits: 0 })} -
    -
    /
    -
    - {Number(enemy?.maxHP ?? 100).toLocaleString(undefined, { maximumFractionDigits: 0 })} -
    -
    -
    - -
    -
    -
    - {Math.round(((enemy.stats?.HP || 0) / (enemy.maxHP || 100)) * 100)}% -
    -
    - -
    -
    - ))} -
    -
    - ) +"use client" + +import useBattleDataStore from "@/stores/battleDataStore"; +import Image from "next/image"; +import useAvatarDataStore from "@/stores/avatarDataStore"; +import { getNameEnemy } from "@/helper/getNameChar"; + +import useLocaleStore from "@/stores/localeStore"; +import NameAvatar from "../nameAvatar"; + +export default function EnemyBar() { + const { enemyDetail } = useBattleDataStore() + const { mapEnemy } = useAvatarDataStore() + const { locale } = useLocaleStore() + + return ( +
    +
    + {enemyDetail && Object.values(enemyDetail).filter((enemy) => ( + enemy.stats.HP <= enemy.maxHP)).map((enemy, uid) => ( +
    +
    +
    + {mapEnemy?.[enemy.id.toString()]?.icon && ( + {enemy.name} + )} + +
    + +

    Level {enemy.level || 1}

    +
    +
    +
    + +
    +
    +
    HP:
    +
    +
    + {Number(enemy?.stats?.HP ?? 0).toLocaleString(undefined, { maximumFractionDigits: 0 })} +
    +
    /
    +
    + {Number(enemy?.maxHP ?? 100).toLocaleString(undefined, { maximumFractionDigits: 0 })} +
    +
    +
    + +
    +
    +
    + {Math.round(((enemy.stats?.HP || 0) / (enemy.maxHP || 100)) * 100)}% +
    +
    + +
    +
    + ))} +
    +
    + ) } \ No newline at end of file diff --git a/src/components/footer/index.tsx b/src/components/footer/index.tsx index b3372bd..3761b67 100644 --- a/src/components/footer/index.tsx +++ b/src/components/footer/index.tsx @@ -1,11 +1,11 @@ - -export default function Footer() { - return( -
    - - -
    - ) -} + +export default function Footer() { + return( +
    + + +
    + ) +} diff --git a/src/components/header/index.tsx b/src/components/header/index.tsx index 5c8cefe..494645a 100644 --- a/src/components/header/index.tsx +++ b/src/components/header/index.tsx @@ -1,499 +1,499 @@ -"use client" -import { exportBattleData, importBattleData } from "@/helper"; -import { useChangeTheme } from "@/hooks/useChangeTheme"; -import { checkConnectTcpApi } from "@/lib/api"; -import { listCurrentLanguage } from "@/lib/constant"; -import { connectSocket, disconnectSocket, getSocket, isSocketConnected } from "@/lib/socket"; -import useBattleDataStore from "@/stores/battleDataStore"; -import useLocaleStore from "@/stores/localeStore"; -import useSocketStore from "@/stores/socketSettingStore"; -import { motion } from "framer-motion"; -import { useTranslations } from "next-intl"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; -import Image from "next/image"; - -const themes = [ - { label: "Winter" }, - { label: "Night" }, - { label: "Cupcake" }, - { label: "Coffee" }, -]; - -export default function Header() { - const { changeTheme } = useChangeTheme() - const { locale, setLocale } = useLocaleStore() - const { loadBattleDataFromJSON, version } = useBattleDataStore() - const router = useRouter() - const transI18n = useTranslations("DataAnalysisPage") - const { host, port, status, connectionType, setHost, setPort, setStatus, setConnectionType } = useSocketStore(); - const [message, setMessage] = useState({ text: '', type: '' }); - const [isModalOpen, setIsModalOpen] = useState(false); - - - useEffect(() => { - - const cookieLocale = document.cookie.split("; ") - .find((row) => row.startsWith("MYNEXTAPP_LOCALE")) - ?.split("=")[1]; - - if (cookieLocale) { - if (!listCurrentLanguage.hasOwnProperty(cookieLocale)) { - setLocale("en") - } else { - setLocale(cookieLocale) - } - - } else { - let browserLocale = navigator.language.slice(0, 2); - - if (!listCurrentLanguage.hasOwnProperty(browserLocale)) { - browserLocale = "en" - } - setLocale(browserLocale); - document.cookie = `MYNEXTAPP_LOCALE=${browserLocale};` - router.refresh() - } - }, [router, setLocale]) - - const changeLocale = (newLocale: string) => { - setLocale(newLocale) - document.cookie = `MYNEXTAPP_LOCALE=${newLocale};` - router.refresh() - } - - useEffect(() => { - const checkStatus = () => { - const connected = isSocketConnected(); - if (connected !== status) { - setStatus(connected); - } - }; - - checkStatus(); - const intervalId = setInterval(checkStatus, 5000); - - return () => clearInterval(intervalId); - }, [status, setStatus]); - - - useEffect(() => { - const socket = getSocket(); - - if (!socket) return; - - return () => { - disconnectSocket() - }; - }, []); - - const handleConnect = () => { - if (!host || !port) { - setMessage({ text: 'IP and Port are required', type: 'error' }); - return; - } - connectSocket(); - - setTimeout(() => { - const connected = isSocketConnected(); - setStatus(connected); - - if (connected) { - setMessage({ text: 'Socket connected successfully!', type: 'success' }); - } else { - setMessage({ text: 'Failed to connect. Please check IP and port.', type: 'error' }); - } - }, 2000); - }; - - const checkConnection = async () => { - setMessage({ text: 'Checking connection...', type: 'info' }); - let isConnect = false - try { - isConnect = await checkConnectTcpApi() - } catch { - - } - - if (!isConnect) { - setMessage({ text: 'Connection bridge to TCP Server failed', type: 'error' }) - } else { - setMessage({ text: 'Connection OK!', type: 'success' }); - } - } - - const handleShow = (modalId: string) => { - const modal = document.getElementById(modalId) as HTMLDialogElement | null; - if (modal) { - setIsModalOpen(true); - modal.showModal(); - } - }; - - // Close modal handler - const handleCloseModal = (modalId: string) => { - setIsModalOpen(false); - const modal = document.getElementById(modalId) as HTMLDialogElement | null; - if (modal) { - modal.close() - } - }; - - // Handle ESC key to close modal - useEffect(() => { - const handleEscKey = (event: KeyboardEvent) => { - if (event.key === 'Escape' && isModalOpen) { - handleCloseModal("character_detail_modal"); - } - }; - - window.addEventListener('keydown', handleEscKey); - return () => window.removeEventListener('keydown', handleEscKey); - }, [isModalOpen]); - - return ( -
    -
    - {/* Mobile menu dropdown */} -
    -
    - - - -
    -
      -
    • - <> - { - 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)); - } - }} - /> - - -
    • -
    • - -
    • -
    • - -
    • -
    -
    - - {/* Logo */} - - -
    - Logo -
    -

    - Firefly Analy - - sis - -

    -

    For Veritas

    -
    -
    -
    - {version && ( -
    -
    -
    -
    - {version} -
    -
    -
    - )} - -
    - - {/* Desktop navigation */} -
    -
      -
    • - <> - { - 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)); - } - }} - /> - - -
    • -
    • - -
    • -
    • - -
    • -
    -
    - - - {/* Right side items */} -
    -
    -
    -
    - {status ? transI18n("connected") : transI18n("unconnected")} -
    -
    -
    -
    - - {/* Language selector - REFINED */} -
    -
    - - - - - -
    -
    - -
    -
    - - - - - - -
    - -
    -
      - {themes.map((theme) => ( -
    • { - if (changeTheme) changeTheme(theme.label.toLowerCase()); - }} - > - -
    • - ))} -
    -
    -
    - - {/* GitHub Link */} - - - - - -
    - - -
    -
    - handleCloseModal("socket_connection_modal")} - > - ✕ - -
    - -
    -

    - {transI18n("socketConnection").toUpperCase()} -

    -
    - -
    - {/* Select connection type */} -
    - - -
    - - {/* Show host/port if Other */} - {connectionType === "Other" && ( -
    -
    - - setHost(e.target.value)} - /> -
    - -
    - - setPort(parseInt(e.target.value) || 0)} - /> -
    -
    - )} - - {message.text && ( -
    - {message.text} -
    - )} - -
    -
    - {transI18n("status")}: - - {status ? transI18n("connected") : transI18n("unconnected")} - -
    - -
    - - {connectionType !== "Native" && ( - - )} -
    -
    -
    -
    -
    -
    - ) +"use client" +import { exportBattleData, importBattleData } from "@/helper"; +import { useChangeTheme } from "@/hooks/useChangeTheme"; +import { checkConnectTcpApi } from "@/lib/api"; +import { listCurrentLanguage } from "@/lib/constant"; +import { connectSocket, disconnectSocket, getSocket, isSocketConnected } from "@/lib/socket"; +import useBattleDataStore from "@/stores/battleDataStore"; +import useLocaleStore from "@/stores/localeStore"; +import useSocketStore from "@/stores/socketSettingStore"; +import { motion } from "framer-motion"; +import { useTranslations } from "next-intl"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import Image from "next/image"; + +const themes = [ + { label: "Winter" }, + { label: "Night" }, + { label: "Cupcake" }, + { label: "Coffee" }, +]; + +export default function Header() { + const { changeTheme } = useChangeTheme() + const { locale, setLocale } = useLocaleStore() + const { loadBattleDataFromJSON, version } = useBattleDataStore() + const router = useRouter() + const transI18n = useTranslations("DataAnalysisPage") + const { host, port, status, connectionType, setHost, setPort, setStatus, setConnectionType } = useSocketStore(); + const [message, setMessage] = useState({ text: '', type: '' }); + const [isModalOpen, setIsModalOpen] = useState(false); + + + useEffect(() => { + + const cookieLocale = document.cookie.split("; ") + .find((row) => row.startsWith("MYNEXTAPP_LOCALE")) + ?.split("=")[1]; + + if (cookieLocale) { + if (!listCurrentLanguage.hasOwnProperty(cookieLocale)) { + setLocale("en") + } else { + setLocale(cookieLocale) + } + + } else { + let browserLocale = navigator.language.slice(0, 2); + + if (!listCurrentLanguage.hasOwnProperty(browserLocale)) { + browserLocale = "en" + } + setLocale(browserLocale); + document.cookie = `MYNEXTAPP_LOCALE=${browserLocale};` + router.refresh() + } + }, [router, setLocale]) + + const changeLocale = (newLocale: string) => { + setLocale(newLocale) + document.cookie = `MYNEXTAPP_LOCALE=${newLocale};` + router.refresh() + } + + useEffect(() => { + const checkStatus = () => { + const connected = isSocketConnected(); + if (connected !== status) { + setStatus(connected); + } + }; + + checkStatus(); + const intervalId = setInterval(checkStatus, 5000); + + return () => clearInterval(intervalId); + }, [status, setStatus]); + + + useEffect(() => { + const socket = getSocket(); + + if (!socket) return; + + return () => { + disconnectSocket() + }; + }, []); + + const handleConnect = () => { + if (!host || !port) { + setMessage({ text: 'IP and Port are required', type: 'error' }); + return; + } + connectSocket(); + + setTimeout(() => { + const connected = isSocketConnected(); + setStatus(connected); + + if (connected) { + setMessage({ text: 'Socket connected successfully!', type: 'success' }); + } else { + setMessage({ text: 'Failed to connect. Please check IP and port.', type: 'error' }); + } + }, 2000); + }; + + const checkConnection = async () => { + setMessage({ text: 'Checking connection...', type: 'info' }); + let isConnect = false + try { + isConnect = await checkConnectTcpApi() + } catch { + + } + + if (!isConnect) { + setMessage({ text: 'Connection bridge to TCP Server failed', type: 'error' }) + } else { + setMessage({ text: 'Connection OK!', type: 'success' }); + } + } + + const handleShow = (modalId: string) => { + const modal = document.getElementById(modalId) as HTMLDialogElement | null; + if (modal) { + setIsModalOpen(true); + modal.showModal(); + } + }; + + // Close modal handler + const handleCloseModal = (modalId: string) => { + setIsModalOpen(false); + const modal = document.getElementById(modalId) as HTMLDialogElement | null; + if (modal) { + modal.close() + } + }; + + // Handle ESC key to close modal + useEffect(() => { + const handleEscKey = (event: KeyboardEvent) => { + if (event.key === 'Escape' && isModalOpen) { + handleCloseModal("character_detail_modal"); + } + }; + + window.addEventListener('keydown', handleEscKey); + return () => window.removeEventListener('keydown', handleEscKey); + }, [isModalOpen]); + + return ( +
    +
    + {/* Mobile menu dropdown */} +
    +
    + + + +
    +
      +
    • + <> + { + 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)); + } + }} + /> + + +
    • +
    • + +
    • +
    • + +
    • +
    +
    + + {/* Logo */} + + +
    + Logo +
    +

    + Firefly Analy + + sis + +

    +

    For Veritas

    +
    +
    +
    + {version && ( +
    +
    +
    +
    + {version} +
    +
    +
    + )} + +
    + + {/* Desktop navigation */} +
    +
      +
    • + <> + { + 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)); + } + }} + /> + + +
    • +
    • + +
    • +
    • + +
    • +
    +
    + + + {/* Right side items */} +
    +
    +
    +
    + {status ? transI18n("connected") : transI18n("unconnected")} +
    +
    +
    +
    + + {/* Language selector - REFINED */} +
    +
    + + + + + +
    +
    + +
    +
    + + + + + + +
    + +
    +
      + {themes.map((theme) => ( +
    • { + if (changeTheme) changeTheme(theme.label.toLowerCase()); + }} + > + +
    • + ))} +
    +
    +
    + + {/* GitHub Link */} + + + + + +
    + + +
    +
    + handleCloseModal("socket_connection_modal")} + > + ✕ + +
    + +
    +

    + {transI18n("socketConnection").toUpperCase()} +

    +
    + +
    + {/* Select connection type */} +
    + + +
    + + {/* Show host/port if Other */} + {connectionType === "Other" && ( +
    +
    + + setHost(e.target.value)} + /> +
    + +
    + + setPort(parseInt(e.target.value) || 0)} + /> +
    +
    + )} + + {message.text && ( +
    + {message.text} +
    + )} + +
    +
    + {transI18n("status")}: + + {status ? transI18n("connected") : transI18n("unconnected")} + +
    + +
    + + {connectionType !== "Native" && ( + + )} +
    +
    +
    +
    +
    +
    + ) } \ No newline at end of file diff --git a/src/components/lineupbar/index.tsx b/src/components/lineupbar/index.tsx index 18d6408..3540b09 100644 --- a/src/components/lineupbar/index.tsx +++ b/src/components/lineupbar/index.tsx @@ -1,307 +1,307 @@ -"use client"; -import useAvatarDataStore from "@/stores/avatarDataStore"; -import useBattleDataStore from "@/stores/battleDataStore"; -import CharacterCard from "../card/characterCard"; -import { useTranslations } from "next-intl"; -import { useState, useEffect } from "react"; -import { CharacterBasic } from "@/types"; -import useLocaleStore from "@/stores/localeStore"; -import { getNameChar } from '@/helper/getNameChar'; -import SkillBarChart from "../chart/skillBarChart"; -import SkillPieChart from "../chart/skillPieChart"; -import { motion } from "framer-motion"; -import { DamageLineForOne } from "../chart/damageLineForOne"; -import { DamagePerCycleForOne } from "../chart/damagePerCycleForOne"; -import { useCalcTotalDmgAvatar, useCalcTotalTurnAvatar } from "@/hooks/useCalcAvatarData"; -import Image from "next/image"; -import NameAvatar from "../nameAvatar"; -// import ShowCaseInfo from "../card/showCaseCard"; - -export default function LineupBar() { - const [selectedCharacter, setSelectedCharacter] = useState(undefined); - const [isModalOpen, setIsModalOpen] = useState(false); - - const transI18n = useTranslations("DataAnalysisPage"); - const { lineup, turnHistory, dataAvatar } = useBattleDataStore(); - const { listAvatar, mapAvatar } = useAvatarDataStore(); - const { locale } = useLocaleStore(); - const totalDamage = useCalcTotalDmgAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0); - const totalTurn = useCalcTotalTurnAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0) - - - const lineupAvatars = listAvatar?.filter(item => - lineup?.some(av => av?.avatarId?.toString() === item.id) - ); - - const handleShow = (modalId: string, item: CharacterBasic) => { - const modal = document.getElementById(modalId) as HTMLDialogElement | null; - if (modal) { - setSelectedCharacter(item); - setIsModalOpen(true); - modal.showModal(); - } - }; - - // Close modal handler - const handleCloseModal = (modalId: string) => { - setIsModalOpen(false); - setSelectedCharacter(undefined); - const modal = document.getElementById(modalId) as HTMLDialogElement | null; - if (modal) { - modal.close() - } - }; - - // Handle ESC key to close modal - useEffect(() => { - const handleEscKey = (event: KeyboardEvent) => { - if (event.key === 'Escape' && isModalOpen) { - handleCloseModal("character_detail_modal"); - } - }; - - window.addEventListener('keydown', handleEscKey); - return () => window.removeEventListener('keydown', handleEscKey); - }, [isModalOpen]); - - return ( -
    - - {transI18n("lineupInfo")} - - -
    -
    - - - - - -
    - {transI18n("lastTurn")}: -
    - - i?.avatarId)?.avatarId?.toString() || ""])} - /> -
    -
    -
    - - -
    - {lineupAvatars.length === 0 ? ( -
    -

    {transI18n("noCharactersInLineup")}

    -
    - ) : ( -
    -
    - {lineupAvatars?.map((item, index) => { - const lastTurnAvatarId = turnHistory.findLast(i => i?.avatarId)?.avatarId || -1; - const isLastTurn = item.id === lastTurnAvatarId.toString(); - - return ( - handleShow("character_detail_modal", item)} - className={`cursor-pointer shrink-0 justify-items-center ${isLastTurn ? "shadow-[inset_0_0_10px_2px_rgba(59,130,246,0.7),0_0_20px_5px_rgba(59,130,246,0.3)]" : "" - }`} - > - - - ); - })} - -
    -
    - )} - - {/* Character Detail Modal */} - -
    -
    - handleCloseModal("character_detail_modal")} - > - ✕ - -
    - -
    - - -
    - - {selectedCharacter && ( - -
    -

    {transI18n("characterInformation")}

    -
    -
    -
    -

    - {transI18n("id")}: {selectedCharacter.id} -

    -

    - {transI18n("path")}: - {transI18n(selectedCharacter.baseType.toLowerCase())} - {selectedCharacter.baseType && ( - {selectedCharacter.baseType.toLowerCase()} - )} -

    -

    - {transI18n("rarity")}: {selectedCharacter.rank === "CombatPowerAvatarRarityType5" ? "5*" : "4*"} -

    -

    - {transI18n("element")}: - {selectedCharacter.damageType.toLowerCase()} -

    - - {(() => { - const avatar = dataAvatar.find(it => it.avatar_id.toString() === selectedCharacter.id); - if (!avatar) return null; - const relicIds = avatar.relics.map(item => { - const relicIdStr = item.relic_id.toString().slice(1); - const slot = relicIdStr.slice(-1); - return `${item.relic_set_id}_${slot}`; - }); - return ( - <> -

    - {transI18n("level")}: {avatar.level} -

    -

    - {transI18n("eidolons")}: {avatar?.data?.rank} -

    -
    - {transI18n("lightcones")}: - {avatar?.Lightcone?.item_id?.toString() -
    -
    - {transI18n("relics")}: -
    - {relicIds.map(it => ( - {avatar?.Lightcone?.item_id?.toString() - ))} -
    -
    - - ); - })()} - -
    - - -
    - {getNameChar(locale, -
    -
    - {/*
    -

    {transI18n("characterInformation")}

    - -
    */} -
    -

    {transI18n("totalTurn")}: {Number(totalTurn).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}

    -
    -
    -

    {transI18n("totalDamage")}: {Number(totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}

    -
    - -
    -

    {transI18n("skillDamageBreakdown")}

    - -
    - -
    -
    -

    {transI18n("damageOverTime")}

    - -
    - - -
    - -
    -

    {transI18n("skillUsageDistribution")}

    - -
    - -
    -
    -

    {transI18n("damagePerCycle")}

    -
    - -
    - -
    - )} -
    -
    -
    -
    - ); +"use client"; +import useAvatarDataStore from "@/stores/avatarDataStore"; +import useBattleDataStore from "@/stores/battleDataStore"; +import CharacterCard from "../card/characterCard"; +import { useTranslations } from "next-intl"; +import { useState, useEffect } from "react"; +import { CharacterBasic } from "@/types"; +import useLocaleStore from "@/stores/localeStore"; +import { getNameChar } from '@/helper/getNameChar'; +import SkillBarChart from "../chart/skillBarChart"; +import SkillPieChart from "../chart/skillPieChart"; +import { motion } from "framer-motion"; +import { DamageLineForOne } from "../chart/damageLineForOne"; +import { DamagePerCycleForOne } from "../chart/damagePerCycleForOne"; +import { useCalcTotalDmgAvatar, useCalcTotalTurnAvatar } from "@/hooks/useCalcAvatarData"; +import Image from "next/image"; +import NameAvatar from "../nameAvatar"; +// import ShowCaseInfo from "../card/showCaseCard"; + +export default function LineupBar() { + const [selectedCharacter, setSelectedCharacter] = useState(undefined); + const [isModalOpen, setIsModalOpen] = useState(false); + + const transI18n = useTranslations("DataAnalysisPage"); + const { lineup, turnHistory, dataAvatar } = useBattleDataStore(); + const { listAvatar, mapAvatar } = useAvatarDataStore(); + const { locale } = useLocaleStore(); + const totalDamage = useCalcTotalDmgAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0); + const totalTurn = useCalcTotalTurnAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0) + + + const lineupAvatars = listAvatar?.filter(item => + lineup?.some(av => av?.avatarId?.toString() === item.id) + ); + + const handleShow = (modalId: string, item: CharacterBasic) => { + const modal = document.getElementById(modalId) as HTMLDialogElement | null; + if (modal) { + setSelectedCharacter(item); + setIsModalOpen(true); + modal.showModal(); + } + }; + + // Close modal handler + const handleCloseModal = (modalId: string) => { + setIsModalOpen(false); + setSelectedCharacter(undefined); + const modal = document.getElementById(modalId) as HTMLDialogElement | null; + if (modal) { + modal.close() + } + }; + + // Handle ESC key to close modal + useEffect(() => { + const handleEscKey = (event: KeyboardEvent) => { + if (event.key === 'Escape' && isModalOpen) { + handleCloseModal("character_detail_modal"); + } + }; + + window.addEventListener('keydown', handleEscKey); + return () => window.removeEventListener('keydown', handleEscKey); + }, [isModalOpen]); + + return ( +
    + + {transI18n("lineupInfo")} + + +
    +
    + + + + + +
    + {transI18n("lastTurn")}: +
    + + i?.avatarId)?.avatarId?.toString() || ""])} + /> +
    +
    +
    + + +
    + {lineupAvatars.length === 0 ? ( +
    +

    {transI18n("noCharactersInLineup")}

    +
    + ) : ( +
    +
    + {lineupAvatars?.map((item, index) => { + const lastTurnAvatarId = turnHistory.findLast(i => i?.avatarId)?.avatarId || -1; + const isLastTurn = item.id === lastTurnAvatarId.toString(); + + return ( + handleShow("character_detail_modal", item)} + className={`cursor-pointer shrink-0 justify-items-center ${isLastTurn ? "shadow-[inset_0_0_10px_2px_rgba(59,130,246,0.7),0_0_20px_5px_rgba(59,130,246,0.3)]" : "" + }`} + > + + + ); + })} + +
    +
    + )} + + {/* Character Detail Modal */} + +
    +
    + handleCloseModal("character_detail_modal")} + > + ✕ + +
    + +
    + + +
    + + {selectedCharacter && ( + +
    +

    {transI18n("characterInformation")}

    +
    +
    +
    +

    + {transI18n("id")}: {selectedCharacter.id} +

    +

    + {transI18n("path")}: + {transI18n(selectedCharacter.baseType.toLowerCase())} + {selectedCharacter.baseType && ( + {selectedCharacter.baseType.toLowerCase()} + )} +

    +

    + {transI18n("rarity")}: {selectedCharacter.rank === "CombatPowerAvatarRarityType5" ? "5*" : "4*"} +

    +

    + {transI18n("element")}: + {selectedCharacter.damageType.toLowerCase()} +

    + + {(() => { + const avatar = dataAvatar.find(it => it.avatar_id.toString() === selectedCharacter.id); + if (!avatar) return null; + const relicIds = avatar.relics.map(item => { + const relicIdStr = item.relic_id.toString().slice(1); + const slot = relicIdStr.slice(-1); + return `${item.relic_set_id}_${slot}`; + }); + return ( + <> +

    + {transI18n("level")}: {avatar.level} +

    +

    + {transI18n("eidolons")}: {avatar?.data?.rank} +

    +
    + {transI18n("lightcones")}: + {avatar?.Lightcone?.item_id?.toString() +
    +
    + {transI18n("relics")}: +
    + {relicIds.map(it => ( + {avatar?.Lightcone?.item_id?.toString() + ))} +
    +
    + + ); + })()} + +
    + + +
    + {getNameChar(locale, +
    +
    + {/*
    +

    {transI18n("characterInformation")}

    + +
    */} +
    +

    {transI18n("totalTurn")}: {Number(totalTurn).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}

    +
    +
    +

    {transI18n("totalDamage")}: {Number(totalDamage).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}

    +
    + +
    +

    {transI18n("skillDamageBreakdown")}

    + +
    + +
    +
    +

    {transI18n("damageOverTime")}

    + +
    + + +
    + +
    +

    {transI18n("skillUsageDistribution")}

    + +
    + +
    +
    +

    {transI18n("damagePerCycle")}

    +
    + +
    + +
    + )} +
    +
    +
    +
    + ); } \ No newline at end of file diff --git a/src/components/nameAvatar/index.tsx b/src/components/nameAvatar/index.tsx index fb238c3..b73a6bc 100644 --- a/src/components/nameAvatar/index.tsx +++ b/src/components/nameAvatar/index.tsx @@ -1,16 +1,16 @@ -import { parseRuby } from "@/helper"; - - - -interface TextProps { - text: string; - locale: string; - className?: string; -} - -export default function NameAvatar({ text, locale, className }: TextProps) { - if (locale === "ja") { - return
    ; - } - return
    {text}
    ; +import { parseRuby } from "@/helper"; + + + +interface TextProps { + text: string; + locale: string; + className?: string; +} + +export default function NameAvatar({ text, locale, className }: TextProps) { + if (locale === "ja") { + return
    ; + } + return
    {text}
    ; } \ No newline at end of file diff --git a/src/components/themeController/clientThemeWrapper.tsx b/src/components/themeController/clientThemeWrapper.tsx index 704e8f5..09f456b 100644 --- a/src/components/themeController/clientThemeWrapper.tsx +++ b/src/components/themeController/clientThemeWrapper.tsx @@ -1,8 +1,8 @@ -"use client"; -import { PropsWithChildren, useContext } from "react"; -import { ThemeContext } from "./themeContext"; - -export function ClientThemeWrapper({ children }: PropsWithChildren) { - const { theme } = useContext(ThemeContext); - return
    {children}
    ; +"use client"; +import { PropsWithChildren, useContext } from "react"; +import { ThemeContext } from "./themeContext"; + +export function ClientThemeWrapper({ children }: PropsWithChildren) { + const { theme } = useContext(ThemeContext); + return
    {children}
    ; } \ No newline at end of file diff --git a/src/components/themeController/index.ts b/src/components/themeController/index.ts index 5600303..fbba780 100644 --- a/src/components/themeController/index.ts +++ b/src/components/themeController/index.ts @@ -1,2 +1,2 @@ -export * from "./clientThemeWrapper" -export * from "./themeContext" +export * from "./clientThemeWrapper" +export * from "./themeContext" diff --git a/src/components/themeController/themeContext.tsx b/src/components/themeController/themeContext.tsx index 714eefb..aaf9e5e 100644 --- a/src/components/themeController/themeContext.tsx +++ b/src/components/themeController/themeContext.tsx @@ -1,27 +1,27 @@ -"use client"; -import useLocaleStore from "@/stores/localeStore"; -import { createContext, PropsWithChildren } from "react"; - -interface ThemeContextType { - theme?: string; - changeTheme?: (nextTheme: string | null) => void; -} -export const ThemeContext = createContext({}); - -export const ThemeProvider = ({ children }: PropsWithChildren) => { - const { theme, setTheme } = useLocaleStore() - - const changeTheme = (nextTheme: string | null) => { - if (nextTheme) { - setTheme(nextTheme); - } else { - setTheme(theme === "winter" ? "night" : "winter"); - } - }; - - return ( - - {children} - - ); +"use client"; +import useLocaleStore from "@/stores/localeStore"; +import { createContext, PropsWithChildren } from "react"; + +interface ThemeContextType { + theme?: string; + changeTheme?: (nextTheme: string | null) => void; +} +export const ThemeContext = createContext({}); + +export const ThemeProvider = ({ children }: PropsWithChildren) => { + const { theme, setTheme } = useLocaleStore() + + const changeTheme = (nextTheme: string | null) => { + if (nextTheme) { + setTheme(nextTheme); + } else { + setTheme(theme === "winter" ? "night" : "winter"); + } + }; + + return ( + + {children} + + ); }; \ No newline at end of file diff --git a/src/global.d.ts b/src/global.d.ts index 444f793..5e279b7 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,4 +1,4 @@ -declare module '*.css' { - const content: { [className: string]: string }; - export default content; -} +declare module '*.css' { + const content: { [className: string]: string }; + export default content; +} diff --git a/src/helper/exportDataBattle.ts b/src/helper/exportDataBattle.ts index ac599fd..7617974 100644 --- a/src/helper/exportDataBattle.ts +++ b/src/helper/exportDataBattle.ts @@ -1,48 +1,48 @@ -import useBattleDataStore from "@/stores/battleDataStore"; -import { BattleDataStateJson } from "@/types/mics"; - -export const exportBattleData = ( - filename = 'battle_data.json' -) => { - const { lineup, - turnHistory, - skillHistory, - totalAV, - totalDamage, - damagePerAV, - cycleIndex, - waveIndex, - dataAvatar, - maxWave, - maxCycle, - version, - avatarDetail, - enemyDetail - } = useBattleDataStore.getState(); - - const data: BattleDataStateJson = { - lineup, - turnHistory, - skillHistory, - dataAvatar, - totalAV, - totalDamage, - damagePerAV, - cycleIndex, - waveIndex, - maxWave, - maxCycle, - version, - avatarDetail, - enemyDetail - } - - const dataStr = JSON.stringify(data, null, 2); - const blob = new Blob([dataStr], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = filename; - a.click(); - URL.revokeObjectURL(url); +import useBattleDataStore from "@/stores/battleDataStore"; +import { BattleDataStateJson } from "@/types/mics"; + +export const exportBattleData = ( + filename = 'battle_data.json' +) => { + const { lineup, + turnHistory, + skillHistory, + totalAV, + totalDamage, + damagePerAV, + cycleIndex, + waveIndex, + dataAvatar, + maxWave, + maxCycle, + version, + avatarDetail, + enemyDetail + } = useBattleDataStore.getState(); + + const data: BattleDataStateJson = { + lineup, + turnHistory, + skillHistory, + dataAvatar, + totalAV, + totalDamage, + damagePerAV, + cycleIndex, + waveIndex, + maxWave, + maxCycle, + version, + avatarDetail, + enemyDetail + } + + const dataStr = JSON.stringify(data, null, 2); + const blob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); }; \ No newline at end of file diff --git a/src/helper/getNameChar.ts b/src/helper/getNameChar.ts index 078f5a3..820f680 100644 --- a/src/helper/getNameChar.ts +++ b/src/helper/getNameChar.ts @@ -1,63 +1,63 @@ -import { listCurrentLanguage } from "@/lib/constant"; -import { CharacterBasic, MonsterBasic } from "@/types"; -import { useTranslations } from "next-intl"; - - -type TFunc = ReturnType - -function cleanText(text: string): string { - if (!text) return "" - return text.replace(/(.*?)<\/unbreak>/g, "$1") -} - -export function getNameChar( - locale: string, - t: TFunc, - data: CharacterBasic | undefined -): string { - if (!data) return ""; - - if (!Object.prototype.hasOwnProperty.call(listCurrentLanguage, locale)) { - return ""; - } - - const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase(); - - let text = data.lang[langKey] ?? ""; - - if (!text) { - text = data.lang["en"] ?? ""; - } - - if (Number(data.id) > 8000) { - text = `${t("trailblazer")} • ${t(data?.baseType?.toLowerCase() ?? "")}`; - } - - return cleanText(text); -} - -export function getNameEnemy(locale: string, data: MonsterBasic | undefined): string { - if (!data) { - return "" - } - if (!Object.prototype.hasOwnProperty.call(listCurrentLanguage, locale)) { - return "" - } - - const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase(); - - - let text = data.lang[langKey] ?? ""; - - if (!text) { - text = data.lang["en"] ?? ""; - } - return text -} - -export function parseRuby(text: string): string { - const rubyRegex = /\{RUBY_B#(.*?)\}(.*?)\{RUBY_E#\}/gs; - return text.replace(rubyRegex, (_match, furigana, kanji) => { - return `${kanji}${furigana}`; - }); +import { listCurrentLanguage } from "@/lib/constant"; +import { CharacterBasic, MonsterBasic } from "@/types"; +import { useTranslations } from "next-intl"; + + +type TFunc = ReturnType + +function cleanText(text: string): string { + if (!text) return "" + return text.replace(/(.*?)<\/unbreak>/g, "$1") +} + +export function getNameChar( + locale: string, + t: TFunc, + data: CharacterBasic | undefined +): string { + if (!data) return ""; + + if (!Object.prototype.hasOwnProperty.call(listCurrentLanguage, locale)) { + return ""; + } + + const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase(); + + let text = data.lang[langKey] ?? ""; + + if (!text) { + text = data.lang["en"] ?? ""; + } + + if (Number(data.id) > 8000) { + text = `${t("trailblazer")} • ${t(data?.baseType?.toLowerCase() ?? "")}`; + } + + return cleanText(text); +} + +export function getNameEnemy(locale: string, data: MonsterBasic | undefined): string { + if (!data) { + return "" + } + if (!Object.prototype.hasOwnProperty.call(listCurrentLanguage, locale)) { + return "" + } + + const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase(); + + + let text = data.lang[langKey] ?? ""; + + if (!text) { + text = data.lang["en"] ?? ""; + } + return text +} + +export function parseRuby(text: string): string { + const rubyRegex = /\{RUBY_B#(.*?)\}(.*?)\{RUBY_E#\}/gs; + return text.replace(rubyRegex, (_match, furigana, kanji) => { + return `${kanji}${furigana}`; + }); } \ No newline at end of file diff --git a/src/helper/importDataBattle.ts b/src/helper/importDataBattle.ts index ccefa99..9c9da96 100644 --- a/src/helper/importDataBattle.ts +++ b/src/helper/importDataBattle.ts @@ -1,21 +1,21 @@ -import { BattleDataStateJson } from "@/types/mics"; - -export const importBattleData = (file: File, loadBattleDataFromJSON: (data: BattleDataStateJson) => void) => { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => { - try { - const result = reader.result as string; - const parsed = JSON.parse(result) as BattleDataStateJson; - loadBattleDataFromJSON(parsed); - resolve(); - } catch (err) { - reject(err); - } - }; - reader.onerror = () => reject(reader.error); - reader.readAsText(file); - }); -}; - +import { BattleDataStateJson } from "@/types/mics"; + +export const importBattleData = (file: File, loadBattleDataFromJSON: (data: BattleDataStateJson) => void) => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + try { + const result = reader.result as string; + const parsed = JSON.parse(result) as BattleDataStateJson; + loadBattleDataFromJSON(parsed); + resolve(); + } catch (err) { + reject(err); + } + }; + reader.onerror = () => reject(reader.error); + reader.readAsText(file); + }); +}; + \ No newline at end of file diff --git a/src/helper/index.ts b/src/helper/index.ts index f1cf26d..340d120 100644 --- a/src/helper/index.ts +++ b/src/helper/index.ts @@ -1,3 +1,3 @@ -export * from "./getNameChar" -export * from "./exportDataBattle" +export * from "./getNameChar" +export * from "./exportDataBattle" export * from "./importDataBattle" \ No newline at end of file diff --git a/src/hooks/useCalcAvatarData.ts b/src/hooks/useCalcAvatarData.ts index bd7aefc..ae66843 100644 --- a/src/hooks/useCalcAvatarData.ts +++ b/src/hooks/useCalcAvatarData.ts @@ -1,28 +1,28 @@ -import useBattleDataStore from "@/stores/battleDataStore"; -import { useMemo } from "react"; - -export function useCalcTotalDmgAvatar(avatarId: number) { - const { skillHistory } = useBattleDataStore.getState(); - - return useMemo(() => { - return skillHistory - .filter(t => t.avatarId === avatarId) - .reduce((sum, turn) => sum + turn.totalDamage, 0); - }, [avatarId, skillHistory]); -} - - -export function useCalcTotalTurnAvatar(avatarId: number) { - const { turnHistory } = useBattleDataStore.getState(); - - return useMemo(() => { - const uniqueActionValues = new Set(); - turnHistory.forEach(turn => { - if (turn.avatarId === avatarId) { - uniqueActionValues.add(turn.actionValue); - } - }); - - return uniqueActionValues.size; - }, [avatarId, turnHistory]); +import useBattleDataStore from "@/stores/battleDataStore"; +import { useMemo } from "react"; + +export function useCalcTotalDmgAvatar(avatarId: number) { + const { skillHistory } = useBattleDataStore.getState(); + + return useMemo(() => { + return skillHistory + .filter(t => t.avatarId === avatarId) + .reduce((sum, turn) => sum + turn.totalDamage, 0); + }, [avatarId, skillHistory]); +} + + +export function useCalcTotalTurnAvatar(avatarId: number) { + const { turnHistory } = useBattleDataStore.getState(); + + return useMemo(() => { + const uniqueActionValues = new Set(); + turnHistory.forEach(turn => { + if (turn.avatarId === avatarId) { + uniqueActionValues.add(turn.actionValue); + } + }); + + return uniqueActionValues.size; + }, [avatarId, turnHistory]); } \ No newline at end of file diff --git a/src/hooks/useChangeTheme.ts b/src/hooks/useChangeTheme.ts index 9007ded..ef0e42a 100644 --- a/src/hooks/useChangeTheme.ts +++ b/src/hooks/useChangeTheme.ts @@ -1,4 +1,4 @@ -import { useContext } from "react"; -import { ThemeContext } from "@/components/themeController"; - +import { useContext } from "react"; +import { ThemeContext } from "@/components/themeController"; + export const useChangeTheme = () => useContext(ThemeContext); \ No newline at end of file diff --git a/src/hooks/useDamageLineForOne.ts b/src/hooks/useDamageLineForOne.ts index d34fded..141fb91 100644 --- a/src/hooks/useDamageLineForOne.ts +++ b/src/hooks/useDamageLineForOne.ts @@ -1,31 +1,31 @@ -import useBattleDataStore from "@/stores/battleDataStore"; -import { useMemo } from "react"; - -export function useDamageLineForOne(avatarId: number, mode: 0 | 1 = 0) { - const { skillHistory, turnHistory } = useBattleDataStore.getState(); - - return useMemo(() => { - const map = new Map(); - - for (const skill of skillHistory) { - if (skill.avatarId !== avatarId) continue; - const actionValue = turnHistory[skill.turnBattleId].actionValue - const prev = map.get(actionValue) || 0; - map.set(actionValue, prev + skill.totalDamage); - } - - const points = Array.from(map.entries()) - .map(([x, y]) => ({ x, y })) - .sort((a, b) => a.x - b.x); - - if (mode === 1) { - let cumulative = 0; - return points.map(p => { - cumulative += p.y; - return { x: p.x, y: Number(cumulative.toFixed(2)) }; - }); - } - - return points.map(p => ({ x: p.x, y: Number(p.y.toFixed(2)) })); - }, [avatarId, skillHistory, turnHistory, mode]); -} +import useBattleDataStore from "@/stores/battleDataStore"; +import { useMemo } from "react"; + +export function useDamageLineForOne(avatarId: number, mode: 0 | 1 = 0) { + const { skillHistory, turnHistory } = useBattleDataStore.getState(); + + return useMemo(() => { + const map = new Map(); + + for (const skill of skillHistory) { + if (skill.avatarId !== avatarId) continue; + const actionValue = turnHistory[skill.turnBattleId].actionValue + const prev = map.get(actionValue) || 0; + map.set(actionValue, prev + skill.totalDamage); + } + + const points = Array.from(map.entries()) + .map(([x, y]) => ({ x, y })) + .sort((a, b) => a.x - b.x); + + if (mode === 1) { + let cumulative = 0; + return points.map(p => { + cumulative += p.y; + return { x: p.x, y: Number(cumulative.toFixed(2)) }; + }); + } + + return points.map(p => ({ x: p.x, y: Number(p.y.toFixed(2)) })); + }, [avatarId, skillHistory, turnHistory, mode]); +} diff --git a/src/hooks/useDamageLinesForAll.ts b/src/hooks/useDamageLinesForAll.ts index 4431be3..7c4c0da 100644 --- a/src/hooks/useDamageLinesForAll.ts +++ b/src/hooks/useDamageLinesForAll.ts @@ -1,44 +1,44 @@ -import useBattleDataStore from "@/stores/battleDataStore"; -import { useMemo } from "react"; - -export function useDamageLinesForAll(mode: 1 | 2 = 1) { - const { turnHistory, skillHistory } = useBattleDataStore.getState(); - - return useMemo(() => { - const avatarMap = new Map>(); - - for (const skill of skillHistory) { - if (!avatarMap.has(skill.avatarId)) { - avatarMap.set(skill.avatarId, new Map()); - } - - const charMap = avatarMap.get(skill.avatarId)!; - const actionValue = turnHistory[skill.turnBattleId].actionValue - const prev = charMap.get(actionValue) || 0; - charMap.set(actionValue, prev + skill.totalDamage); - } - - const result: Record = {}; - - for (const [avatarId, damageMap] of avatarMap.entries()) { - const points = Array.from(damageMap.entries()) - .map(([x, y]) => ({ x, y })) - .sort((a, b) => a.x - b.x); - - if (mode === 1) { - let cumulative = 0; - result[avatarId] = points.map(p => { - cumulative += Number(p.y); - return { x: p.x, y: Number(cumulative.toFixed(2)) }; - }); - } else { - result[avatarId] = points.map(p => ({ - x: p.x, - y: Number(p.y.toFixed(2)), - })); - } - } - - return result; - }, [turnHistory, skillHistory, mode]); -} +import useBattleDataStore from "@/stores/battleDataStore"; +import { useMemo } from "react"; + +export function useDamageLinesForAll(mode: 1 | 2 = 1) { + const { turnHistory, skillHistory } = useBattleDataStore.getState(); + + return useMemo(() => { + const avatarMap = new Map>(); + + for (const skill of skillHistory) { + if (!avatarMap.has(skill.avatarId)) { + avatarMap.set(skill.avatarId, new Map()); + } + + const charMap = avatarMap.get(skill.avatarId)!; + const actionValue = turnHistory[skill.turnBattleId].actionValue + const prev = charMap.get(actionValue) || 0; + charMap.set(actionValue, prev + skill.totalDamage); + } + + const result: Record = {}; + + for (const [avatarId, damageMap] of avatarMap.entries()) { + const points = Array.from(damageMap.entries()) + .map(([x, y]) => ({ x, y })) + .sort((a, b) => a.x - b.x); + + if (mode === 1) { + let cumulative = 0; + result[avatarId] = points.map(p => { + cumulative += Number(p.y); + return { x: p.x, y: Number(cumulative.toFixed(2)) }; + }); + } else { + result[avatarId] = points.map(p => ({ + x: p.x, + y: Number(p.y.toFixed(2)), + })); + } + } + + return result; + }, [turnHistory, skillHistory, mode]); +} diff --git a/src/hooks/useDamagePerCycle.ts b/src/hooks/useDamagePerCycle.ts index ae20b2d..f778255 100644 --- a/src/hooks/useDamagePerCycle.ts +++ b/src/hooks/useDamagePerCycle.ts @@ -1,69 +1,69 @@ -import useBattleDataStore from "@/stores/battleDataStore"; -import { useTranslations } from "next-intl"; -import { useMemo } from "react"; - - -type Mode = 0 | 1 | 2; - -export function useDamagePerCycleForOne(avatarId: number, mode: Mode) { - const { skillHistory, turnHistory } = useBattleDataStore.getState(); - const transI18n = useTranslations("DataAnalysisPage"); - return useMemo(() => { - const damageMap = new Map(); - - skillHistory - .filter(s => s.avatarId === avatarId) - .forEach(s => { - const turn = turnHistory[s.turnBattleId]; - if (!turn) return; - - let key = ''; - if (mode === 0) { - key = `${transI18n('cycle')} ${turn.cycleIndex} - ${transI18n('wave')} ${turn.waveIndex}`; - } else if (mode === 1) { - key = `${transI18n('cycle')} ${turn.cycleIndex}`; - } else if (mode === 2) { - key = `${transI18n('wave')} ${turn.waveIndex}`; - } - - damageMap.set(key, (damageMap.get(key) || 0) + s.totalDamage); - }); - - const result = Array.from(damageMap.entries()) - .map(([x, y]) => ({ x, y })) - .sort((a, b) => a.x.localeCompare(b.x, undefined, { numeric: true })); - - return result; - }, [avatarId, mode, skillHistory, turnHistory, transI18n]); -} - - -export function useDamagePerCycleForAll(mode: Mode) { - const { skillHistory, turnHistory } = useBattleDataStore.getState(); - const transI18n = useTranslations("DataAnalysisPage"); - return useMemo(() => { - const damageMap = new Map(); - - skillHistory.forEach(s => { - const turn = turnHistory[s.turnBattleId]; - if (!turn) return; - - let key = ''; - if (mode === 0) { - key = `${transI18n('cycle')} ${turn.cycleIndex} - ${transI18n('wave')} ${turn.waveIndex}`; - } else if (mode === 1) { - key = `${transI18n('cycle')} ${turn.cycleIndex}`; - } else if (mode === 2) { - key = `${transI18n('wave')} ${turn.waveIndex}`; - } - - damageMap.set(key, (damageMap.get(key) || 0) + s.totalDamage); - }); - - const result = Array.from(damageMap.entries()) - .map(([x, y]) => ({ x, y })) - .sort((a, b) => a.x.localeCompare(b.x, undefined, { numeric: true })); - - return result; - }, [mode, skillHistory, turnHistory, transI18n]); +import useBattleDataStore from "@/stores/battleDataStore"; +import { useTranslations } from "next-intl"; +import { useMemo } from "react"; + + +type Mode = 0 | 1 | 2; + +export function useDamagePerCycleForOne(avatarId: number, mode: Mode) { + const { skillHistory, turnHistory } = useBattleDataStore.getState(); + const transI18n = useTranslations("DataAnalysisPage"); + return useMemo(() => { + const damageMap = new Map(); + + skillHistory + .filter(s => s.avatarId === avatarId) + .forEach(s => { + const turn = turnHistory[s.turnBattleId]; + if (!turn) return; + + let key = ''; + if (mode === 0) { + key = `${transI18n('cycle')} ${turn.cycleIndex} - ${transI18n('wave')} ${turn.waveIndex}`; + } else if (mode === 1) { + key = `${transI18n('cycle')} ${turn.cycleIndex}`; + } else if (mode === 2) { + key = `${transI18n('wave')} ${turn.waveIndex}`; + } + + damageMap.set(key, (damageMap.get(key) || 0) + s.totalDamage); + }); + + const result = Array.from(damageMap.entries()) + .map(([x, y]) => ({ x, y })) + .sort((a, b) => a.x.localeCompare(b.x, undefined, { numeric: true })); + + return result; + }, [avatarId, mode, skillHistory, turnHistory, transI18n]); +} + + +export function useDamagePerCycleForAll(mode: Mode) { + const { skillHistory, turnHistory } = useBattleDataStore.getState(); + const transI18n = useTranslations("DataAnalysisPage"); + return useMemo(() => { + const damageMap = new Map(); + + skillHistory.forEach(s => { + const turn = turnHistory[s.turnBattleId]; + if (!turn) return; + + let key = ''; + if (mode === 0) { + key = `${transI18n('cycle')} ${turn.cycleIndex} - ${transI18n('wave')} ${turn.waveIndex}`; + } else if (mode === 1) { + key = `${transI18n('cycle')} ${turn.cycleIndex}`; + } else if (mode === 2) { + key = `${transI18n('wave')} ${turn.waveIndex}`; + } + + damageMap.set(key, (damageMap.get(key) || 0) + s.totalDamage); + }); + + const result = Array.from(damageMap.entries()) + .map(([x, y]) => ({ x, y })) + .sort((a, b) => a.x.localeCompare(b.x, undefined, { numeric: true })); + + return result; + }, [mode, skillHistory, turnHistory, transI18n]); } \ No newline at end of file diff --git a/src/hooks/useDamagePercentForDamgeType.ts b/src/hooks/useDamagePercentForDamgeType.ts index 74b2451..25489f5 100644 --- a/src/hooks/useDamagePercentForDamgeType.ts +++ b/src/hooks/useDamagePercentForDamgeType.ts @@ -1,26 +1,26 @@ -import { useMemo } from "react"; -import useBattleDataStore from "@/stores/battleDataStore"; -import {attackTypeToString} from "@/types"; - -export function useDamagePercentByType() { - const { skillHistory } = useBattleDataStore.getState(); - - return useMemo(() => { - const dmgByType = new Map(); - - skillHistory.forEach(skill => { - skill.damageDetail.forEach(detail => { - const type = detail.damage_type; - if (!type) return; - dmgByType.set(attackTypeToString(type), (dmgByType.get(attackTypeToString(type)) || 0) + detail.damage); - }); - }); - - const totalDmg = Array.from(dmgByType.values()).reduce((sum, val) => sum + val, 0); - - return Array.from(dmgByType.entries()).map(([type, dmg]) => ({ - type, - percent: totalDmg > 0 ? (dmg / totalDmg) * 100 : 0, - })); - }, [skillHistory]); -} +import { useMemo } from "react"; +import useBattleDataStore from "@/stores/battleDataStore"; +import {attackTypeToString} from "@/types"; + +export function useDamagePercentByType() { + const { skillHistory } = useBattleDataStore.getState(); + + return useMemo(() => { + const dmgByType = new Map(); + + skillHistory.forEach(skill => { + skill.damageDetail.forEach(detail => { + const type = detail.damage_type; + if (!type) return; + dmgByType.set(attackTypeToString(type), (dmgByType.get(attackTypeToString(type)) || 0) + detail.damage); + }); + }); + + const totalDmg = Array.from(dmgByType.values()).reduce((sum, val) => sum + val, 0); + + return Array.from(dmgByType.entries()).map(([type, dmg]) => ({ + type, + percent: totalDmg > 0 ? (dmg / totalDmg) * 100 : 0, + })); + }, [skillHistory]); +} diff --git a/src/hooks/useDamagePercentPerAvatar.ts b/src/hooks/useDamagePercentPerAvatar.ts index 71aee1b..7848adb 100644 --- a/src/hooks/useDamagePercentPerAvatar.ts +++ b/src/hooks/useDamagePercentPerAvatar.ts @@ -1,21 +1,21 @@ -import useBattleDataStore from "@/stores/battleDataStore"; -import { useMemo } from "react"; - -export function useDamagePercentPerAvatar() { - const { skillHistory } = useBattleDataStore.getState(); - - return useMemo(() => { - const dmgByAvatar = new Map(); - - skillHistory.forEach(t => { - dmgByAvatar.set(t.avatarId, (dmgByAvatar.get(t.avatarId) || 0) + t.totalDamage); - }); - - const totalDmg = Array.from(dmgByAvatar.values()).reduce((sum, v) => sum + v, 0); - - return Array.from(dmgByAvatar.entries()).map(([avatarId, dmg]) => ({ - avatarId, - percent: totalDmg > 0 ? (dmg / totalDmg) * 100 : 0, - })); - }, [skillHistory]); -} +import useBattleDataStore from "@/stores/battleDataStore"; +import { useMemo } from "react"; + +export function useDamagePercentPerAvatar() { + const { skillHistory } = useBattleDataStore.getState(); + + return useMemo(() => { + const dmgByAvatar = new Map(); + + skillHistory.forEach(t => { + dmgByAvatar.set(t.avatarId, (dmgByAvatar.get(t.avatarId) || 0) + t.totalDamage); + }); + + const totalDmg = Array.from(dmgByAvatar.values()).reduce((sum, v) => sum + v, 0); + + return Array.from(dmgByAvatar.entries()).map(([avatarId, dmg]) => ({ + avatarId, + percent: totalDmg > 0 ? (dmg / totalDmg) * 100 : 0, + })); + }, [skillHistory]); +} diff --git a/src/hooks/useDamageTypeForAvatar.ts b/src/hooks/useDamageTypeForAvatar.ts index 42c8f0f..cba72d8 100644 --- a/src/hooks/useDamageTypeForAvatar.ts +++ b/src/hooks/useDamageTypeForAvatar.ts @@ -1,26 +1,26 @@ -"use client"; -import { useMemo } from "react"; -import useBattleDataStore from "@/stores/battleDataStore"; -import {attackTypeToString} from "@/types"; - -export function useDamageByTypeForAvatar(avatarId: number) { - const { skillHistory } = useBattleDataStore.getState(); - - return useMemo(() => { - const dmgMap = new Map(); - - for (const skill of skillHistory) { - if (skill.avatarId !== avatarId) continue; - - for (const damage of skill.damageDetail) { - const type = attackTypeToString(damage?.damage_type)?.toLowerCase() || ""; - dmgMap.set(type, (dmgMap.get(type) || 0) + damage.damage); - } - } - - const labels = Array.from(dmgMap.keys()); - const damageValues = Array.from(dmgMap.values()); - - return { labels, damageValues }; - }, [avatarId, skillHistory]); -} +"use client"; +import { useMemo } from "react"; +import useBattleDataStore from "@/stores/battleDataStore"; +import {attackTypeToString} from "@/types"; + +export function useDamageByTypeForAvatar(avatarId: number) { + const { skillHistory } = useBattleDataStore.getState(); + + return useMemo(() => { + const dmgMap = new Map(); + + for (const skill of skillHistory) { + if (skill.avatarId !== avatarId) continue; + + for (const damage of skill.damageDetail) { + const type = attackTypeToString(damage?.damage_type)?.toLowerCase() || ""; + dmgMap.set(type, (dmgMap.get(type) || 0) + damage.damage); + } + } + + const labels = Array.from(dmgMap.keys()); + const damageValues = Array.from(dmgMap.values()); + + return { labels, damageValues }; + }, [avatarId, skillHistory]); +} diff --git a/src/hooks/useSkillDamageForAvatar.ts b/src/hooks/useSkillDamageForAvatar.ts index 0b60df5..434dd35 100644 --- a/src/hooks/useSkillDamageForAvatar.ts +++ b/src/hooks/useSkillDamageForAvatar.ts @@ -1,24 +1,24 @@ -"use client"; -import { useMemo } from "react"; -import useBattleDataStore from "@/stores/battleDataStore"; -import {attackTypeToString} from "@/types"; - -export function useSkillDamageForAvatar(avatarId: number) { - const { skillHistory } = useBattleDataStore.getState(); - - return useMemo(() => { - const dmgMap = new Map(); - - for (const skill of skillHistory) { - if (skill.avatarId !== avatarId) continue; - - const type = attackTypeToString(skill.skillType).toLowerCase(); - dmgMap.set(type, (dmgMap.get(type) || 0) + skill.totalDamage); - } - - const labels = Array.from(dmgMap.keys()); - const damageValues = Array.from(dmgMap.values()); - - return { labels, damageValues }; - }, [avatarId, skillHistory]); -} +"use client"; +import { useMemo } from "react"; +import useBattleDataStore from "@/stores/battleDataStore"; +import {attackTypeToString} from "@/types"; + +export function useSkillDamageForAvatar(avatarId: number) { + const { skillHistory } = useBattleDataStore.getState(); + + return useMemo(() => { + const dmgMap = new Map(); + + for (const skill of skillHistory) { + if (skill.avatarId !== avatarId) continue; + + const type = attackTypeToString(skill.skillType).toLowerCase(); + dmgMap.set(type, (dmgMap.get(type) || 0) + skill.totalDamage); + } + + const labels = Array.from(dmgMap.keys()); + const damageValues = Array.from(dmgMap.values()); + + return { labels, damageValues }; + }, [avatarId, skillHistory]); +} diff --git a/src/lib/api.ts b/src/lib/api.ts index a8a605e..952c501 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,51 +1,51 @@ -import useSocketStore from "@/stores/socketSettingStore"; -import { CharacterBasic, MonsterBasic } from "@/types"; -import axios from 'axios'; - -export async function checkConnectTcpApi(): Promise { - const { host, port, connectionType } = useSocketStore.getState() - let url = `${host}:${port}/check-tcp` - if (connectionType === "PS") { - url = "http://localhost:21000/check-tcp" - } - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (response.ok) { - const data = await response.json(); - return data.status - } - return false -} - -export async function getCharacterListApi(): Promise { - try { - const res = await axios.get("/api/data/avatar_basic"); - return res.data - } catch (error: unknown) { - if (axios.isAxiosError(error)) { - console.log(`Error: ${error.response?.status} - ${error.message}`); - } else { - console.log(`Unexpected error: ${String(error)}`); - } - return []; - } -} - -export async function getEnemyListApi(): Promise { - try { - const res = await axios.get("/api/data/monster_basic"); - return res.data - } catch (error: unknown) { - if (axios.isAxiosError(error)) { - console.log(`Error: ${error.response?.status} - ${error.message}`); - } else { - console.log(`Unexpected error: ${String(error)}`); - } - return []; - } -} +import useSocketStore from "@/stores/socketSettingStore"; +import { CharacterBasic, MonsterBasic } from "@/types"; +import axios from 'axios'; + +export async function checkConnectTcpApi(): Promise { + const { host, port, connectionType } = useSocketStore.getState() + let url = `${host}:${port}/check-tcp` + if (connectionType === "PS") { + url = "http://localhost:21000/check-tcp" + } + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (response.ok) { + const data = await response.json(); + return data.status + } + return false +} + +export async function getCharacterListApi(): Promise { + try { + const res = await axios.get("/api/data/avatar_basic"); + return res.data + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + console.log(`Error: ${error.response?.status} - ${error.message}`); + } else { + console.log(`Unexpected error: ${String(error)}`); + } + return []; + } +} + +export async function getEnemyListApi(): Promise { + try { + const res = await axios.get("/api/data/monster_basic"); + return res.data + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + console.log(`Error: ${error.response?.status} - ${error.message}`); + } else { + console.log(`Unexpected error: ${String(error)}`); + } + return []; + } +} diff --git a/src/lib/cache/cache.ts b/src/lib/cache/cache.ts index de5b5a2..edb14c5 100644 --- a/src/lib/cache/cache.ts +++ b/src/lib/cache/cache.ts @@ -1,38 +1,38 @@ -import { readFileSync, readdirSync } from "fs" -import path from "path" - -type CacheItem = { - buf: Uint8Array - type: "json" | "br" -} - -const cache = new Map() - -const dir = path.join(process.cwd(), "data") - -for (const f of readdirSync(dir)) { - const file = path.join(dir, f) - - if (f.endsWith(".json.br")) { - const name = f.replace(".json.br", "") - const buf = new Uint8Array(readFileSync(file)) - cache.set(name, { - buf, - type: "br" - }) - } - - if (f.endsWith(".json")) { - const name = f.replace(".json", "") - const buf = new Uint8Array(readFileSync(file)) - - cache.set(name, { - buf, - type: "json" - }) - } -} - -export function getDataCache(name: string) { - return cache.get(name) +import { readFileSync, readdirSync } from "fs" +import path from "path" + +type CacheItem = { + buf: Uint8Array + type: "json" | "br" +} + +const cache = new Map() + +const dir = path.join(process.cwd(), "data") + +for (const f of readdirSync(dir)) { + const file = path.join(dir, f) + + if (f.endsWith(".json.br")) { + const name = f.replace(".json.br", "") + const buf = new Uint8Array(readFileSync(file)) + cache.set(name, { + buf, + type: "br" + }) + } + + if (f.endsWith(".json")) { + const name = f.replace(".json", "") + const buf = new Uint8Array(readFileSync(file)) + + cache.set(name, { + buf, + type: "json" + }) + } +} + +export function getDataCache(name: string) { + return cache.get(name) } \ No newline at end of file diff --git a/src/lib/constant.ts b/src/lib/constant.ts index 071fb5d..d51a842 100644 --- a/src/lib/constant.ts +++ b/src/lib/constant.ts @@ -1,7 +1,7 @@ -export const listCurrentLanguage = { - ja: "JP", - ko: "KR", - en: "US", - vi: "VN", - zh: "CN" +export const listCurrentLanguage = { + ja: "JP", + ko: "KR", + en: "US", + vi: "VN", + zh: "CN" }; \ No newline at end of file diff --git a/src/lib/socket.ts b/src/lib/socket.ts index 262a855..ec9f6fe 100644 --- a/src/lib/socket.ts +++ b/src/lib/socket.ts @@ -1,255 +1,255 @@ -import { io, Socket } from "socket.io-client"; -import useSocketStore from "@/stores/socketSettingStore"; -import { toast } from "react-toastify"; -import useBattleDataStore from "@/stores/battleDataStore"; -import type { - AvatarAnalysisJson, - BattleBeginType, - BattleEndType, - DamageType, - EntityDefeatedType, - InitializeEnemyType, - SetBattleLineupType, - StatChangeType, - TurnBeginType, - TurnEndType, - UpdateCycleType, - UpdateTeamFormationType, - UpdateWaveType, - UseSkillType, - VersionType, -} from "@/types"; - -let socket: Socket | null = null; - -const notify = (msg: string, type: "info" | "success" | "error" = "info") => { - if (type === "success") toast.success(msg); - else if (type === "error") toast.error(msg); - else toast.info(msg); -}; - -interface SocketEvents { - Connected: VersionType; - OnBattleBegin: BattleBeginType; - OnSetBattleLineup: SetBattleLineupType; - OnDamage: DamageType; - OnTurnBegin: TurnBeginType; - OnTurnEnd: TurnEndType; - OnEntityDefeated: EntityDefeatedType; - OnUseSkill: UseSkillType; - OnUpdateWave: UpdateWaveType; - OnUpdateCycle: UpdateCycleType; - OnStatChange: StatChangeType; - OnUpdateTeamFormation: UpdateTeamFormationType; - OnInitializeEnemy: InitializeEnemyType; - OnBattleEnd: BattleEndType; - OnCreateBattle: AvatarAnalysisJson[]; - Error: string; -} - -type ListenerMap = { - [K in keyof SocketEvents]: (payload: SocketEvents[K]) => void; -}; - -let listeners: ListenerMap | null = null; - -let workerPool: Worker[] | null = null; -let nextWorker = 0; -let reqId = 1; -const pendingMap = new Map void>(); -let workerBlobUrl: string | null = null; - -const createWorkerPool = (size = Math.max(1, (navigator.hardwareConcurrency || 4) - 1)) => { - if (workerPool) return workerPool; - const code = `self.onmessage = function(e) { try { const { id, raw } = e.data; const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw; self.postMessage({ id, parsed }); } catch (err) { self.postMessage({ id, err: err && err.message ? err.message : String(err) }); } }`; - - if (!workerBlobUrl) { - const blob = new Blob([code], { type: 'application/javascript' }); - workerBlobUrl = URL.createObjectURL(blob); - } - - workerPool = Array.from({ length: size }).map(() => new Worker(workerBlobUrl!)); - workerPool.forEach((w) => { - w.onmessage = (ev) => { - const { id, parsed, err } = ev.data as { id: number; parsed: unknown; err?: string }; - const cb = pendingMap.get(id); - if (cb) { - cb({ parsed: parsed ?? null, err }); - pendingMap.delete(id); - } - }; - w.onerror = (ev) => { - console.error('worker error', ev.message); - }; - }); - return workerPool; -}; - -const parseWithWorker = (raw: unknown): Promise<{ parsed: unknown | null; err?: string }> => { - if (!workerPool) createWorkerPool(); - if (!workerPool) return Promise.resolve({ parsed: null, err: 'no worker' }); - const id = reqId++; - return new Promise((resolve) => { - const timeout = setTimeout(() => { - pendingMap.delete(id); - resolve({ parsed: null, err: "worker timeout" }); - }, 3000); - - pendingMap.set(id, (res) => { - clearTimeout(timeout); - resolve(res); - }); - - const w = workerPool![nextWorker]; - nextWorker = (nextWorker + 1) % workerPool!.length; - try { - w.postMessage({ id, raw }); - } catch (err) { - pendingMap.delete(id); - resolve({ parsed: null, err: err && (err as Error).message ? (err as Error).message : String(err) }); - } - }); -}; - -let eventQueue: { name: keyof SocketEvents; data: unknown }[] = []; -let rafScheduled = false; -const MAX_QUEUE = 1000; - -const flushQueue = () => { - const items = eventQueue.splice(0, eventQueue.length); - if (items.length === 0) return; - const l = listeners; - if (!l) return; - for (const it of items) { - try { - l[it.name](it.data as never); - } catch (err) { - console.error('listener handler error', err); - } - } -}; - -const scheduleFlush = () => { - if (rafScheduled) return; - rafScheduled = true; - requestAnimationFrame(() => { - rafScheduled = false; - flushQueue(); - }); -}; - -const safeEnqueue = (name: keyof SocketEvents, data: unknown) => { - if (eventQueue.length > MAX_QUEUE) { - eventQueue.shift(); - } - eventQueue.push({ name, data }); - scheduleFlush(); -}; - -export const connectSocket = async (): Promise => { - const { host, port, connectionType, setStatus } = useSocketStore.getState(); - let url = `${host}:${port}`; - if (connectionType === "Native") url = "http://localhost:1305"; - else if (connectionType === "PS") url = "http://localhost:21000"; - - if (socket) socket.disconnect(); - - socket = io(url, { - reconnectionAttempts: 5, - timeout: 10000, - autoConnect: true, - }); - - socket.on("connect", () => { - setStatus(true); - notify(`Connected: ${socket?.id}`, "success"); - }); - - socket.on("disconnect", () => setStatus(false)); - socket.on("connect_error", () => setStatus(false)); - socket.on("connect_timeout", () => setStatus(false)); - socket.on("reconnect_failed", () => setStatus(false)); - - listeners = { - Connected: (payload) => useBattleDataStore.getState().onConnectedService(payload), - OnBattleBegin: (payload) => { - notify("Battle Started!", "info"); - useBattleDataStore.getState().onBattleBeginService(payload); - }, - OnSetBattleLineup: (payload) => useBattleDataStore.getState().onSetBattleLineupService(payload), - OnDamage: (payload) => useBattleDataStore.getState().onDamageService(payload), - OnTurnBegin: (payload) => useBattleDataStore.getState().onTurnBeginService(payload), - OnTurnEnd: (payload) => useBattleDataStore.getState().onTurnEndService(payload), - OnEntityDefeated: (payload) => useBattleDataStore.getState().onEntityDefeatedService(payload), - OnUseSkill: (payload) => useBattleDataStore.getState().onUseSkillService(payload), - OnUpdateWave: (payload) => useBattleDataStore.getState().onUpdateWaveService(payload), - OnUpdateCycle: (payload) => useBattleDataStore.getState().onUpdateCycleService(payload), - OnStatChange: (payload) => useBattleDataStore.getState().onStatChange(payload), - OnUpdateTeamFormation: (payload) => useBattleDataStore.getState().onUpdateTeamFormation(payload), - OnInitializeEnemy: (payload) => useBattleDataStore.getState().onInitializeEnemyService(payload), - OnBattleEnd: (payload) => useBattleDataStore.getState().onBattleEndService(payload), - OnCreateBattle: (payload) => useBattleDataStore.getState().onCreateBattleService(payload), - Error: (msg) => console.error("Server Error:", msg), - }; - - const register = (eventName: string) => { - socket?.on(eventName, async (raw: unknown) => { - if (!listeners) return; - if (workerPool) { - const res = await parseWithWorker(raw); - if (res.err) { - safeEnqueue("Error", `Worker parse error for ${eventName}: ${res.err}`); - } else if (res.parsed !== null) { - safeEnqueue(eventName as keyof SocketEvents, res.parsed); - } - } else { - try { - const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw; - safeEnqueue(eventName as keyof SocketEvents, parsed); - } catch (err) { - safeEnqueue("Error", `Parse error for ${eventName}: ${err && (err as Error).message ? (err as Error).message : String(err)}`); - } - } - }); - }; - - Object.keys(listeners).forEach((k) => register(k)); - - createWorkerPool(); - - return socket; -}; - -export const disconnectSocket = (): void => { - if (!socket) return; - if (listeners) { - Object.keys(listeners).forEach((eventName) => { - socket?.off(eventName); - }); - } - if (workerBlobUrl) { - URL.revokeObjectURL(workerBlobUrl); - workerBlobUrl = null; - } - socket.disconnect(); - useSocketStore.getState().setStatus(false); - listeners = null; - if (workerPool) { - workerPool.forEach((w) => w.terminate()); - workerPool = null; - } - pendingMap.clear(); - eventQueue = []; -}; - -export const isSocketConnected = (): boolean => socket?.connected || false; -export const getSocket = (): Socket | null => socket; - -export const setWorkerPoolSize = (size: number) => { - if (workerPool) { - workerPool.forEach((w) => w.terminate()); - workerPool = null; - } - createWorkerPool(Math.max(1, size)); -}; - +import { io, Socket } from "socket.io-client"; +import useSocketStore from "@/stores/socketSettingStore"; +import { toast } from "react-toastify"; +import useBattleDataStore from "@/stores/battleDataStore"; +import type { + AvatarAnalysisJson, + BattleBeginType, + BattleEndType, + DamageType, + EntityDefeatedType, + InitializeEnemyType, + SetBattleLineupType, + StatChangeType, + TurnBeginType, + TurnEndType, + UpdateCycleType, + UpdateTeamFormationType, + UpdateWaveType, + UseSkillType, + VersionType, +} from "@/types"; + +let socket: Socket | null = null; + +const notify = (msg: string, type: "info" | "success" | "error" = "info") => { + if (type === "success") toast.success(msg); + else if (type === "error") toast.error(msg); + else toast.info(msg); +}; + +interface SocketEvents { + Connected: VersionType; + OnBattleBegin: BattleBeginType; + OnSetBattleLineup: SetBattleLineupType; + OnDamage: DamageType; + OnTurnBegin: TurnBeginType; + OnTurnEnd: TurnEndType; + OnEntityDefeated: EntityDefeatedType; + OnUseSkill: UseSkillType; + OnUpdateWave: UpdateWaveType; + OnUpdateCycle: UpdateCycleType; + OnStatChange: StatChangeType; + OnUpdateTeamFormation: UpdateTeamFormationType; + OnInitializeEnemy: InitializeEnemyType; + OnBattleEnd: BattleEndType; + OnCreateBattle: AvatarAnalysisJson[]; + Error: string; +} + +type ListenerMap = { + [K in keyof SocketEvents]: (payload: SocketEvents[K]) => void; +}; + +let listeners: ListenerMap | null = null; + +let workerPool: Worker[] | null = null; +let nextWorker = 0; +let reqId = 1; +const pendingMap = new Map void>(); +let workerBlobUrl: string | null = null; + +const createWorkerPool = (size = Math.max(1, (navigator.hardwareConcurrency || 4) - 1)) => { + if (workerPool) return workerPool; + const code = `self.onmessage = function(e) { try { const { id, raw } = e.data; const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw; self.postMessage({ id, parsed }); } catch (err) { self.postMessage({ id, err: err && err.message ? err.message : String(err) }); } }`; + + if (!workerBlobUrl) { + const blob = new Blob([code], { type: 'application/javascript' }); + workerBlobUrl = URL.createObjectURL(blob); + } + + workerPool = Array.from({ length: size }).map(() => new Worker(workerBlobUrl!)); + workerPool.forEach((w) => { + w.onmessage = (ev) => { + const { id, parsed, err } = ev.data as { id: number; parsed: unknown; err?: string }; + const cb = pendingMap.get(id); + if (cb) { + cb({ parsed: parsed ?? null, err }); + pendingMap.delete(id); + } + }; + w.onerror = (ev) => { + console.error('worker error', ev.message); + }; + }); + return workerPool; +}; + +const parseWithWorker = (raw: unknown): Promise<{ parsed: unknown | null; err?: string }> => { + if (!workerPool) createWorkerPool(); + if (!workerPool) return Promise.resolve({ parsed: null, err: 'no worker' }); + const id = reqId++; + return new Promise((resolve) => { + const timeout = setTimeout(() => { + pendingMap.delete(id); + resolve({ parsed: null, err: "worker timeout" }); + }, 3000); + + pendingMap.set(id, (res) => { + clearTimeout(timeout); + resolve(res); + }); + + const w = workerPool![nextWorker]; + nextWorker = (nextWorker + 1) % workerPool!.length; + try { + w.postMessage({ id, raw }); + } catch (err) { + pendingMap.delete(id); + resolve({ parsed: null, err: err && (err as Error).message ? (err as Error).message : String(err) }); + } + }); +}; + +let eventQueue: { name: keyof SocketEvents; data: unknown }[] = []; +let rafScheduled = false; +const MAX_QUEUE = 1000; + +const flushQueue = () => { + const items = eventQueue.splice(0, eventQueue.length); + if (items.length === 0) return; + const l = listeners; + if (!l) return; + for (const it of items) { + try { + l[it.name](it.data as never); + } catch (err) { + console.error('listener handler error', err); + } + } +}; + +const scheduleFlush = () => { + if (rafScheduled) return; + rafScheduled = true; + requestAnimationFrame(() => { + rafScheduled = false; + flushQueue(); + }); +}; + +const safeEnqueue = (name: keyof SocketEvents, data: unknown) => { + if (eventQueue.length > MAX_QUEUE) { + eventQueue.shift(); + } + eventQueue.push({ name, data }); + scheduleFlush(); +}; + +export const connectSocket = async (): Promise => { + const { host, port, connectionType, setStatus } = useSocketStore.getState(); + let url = `${host}:${port}`; + if (connectionType === "Native") url = "http://localhost:1305"; + else if (connectionType === "PS") url = "http://localhost:21000"; + + if (socket) socket.disconnect(); + + socket = io(url, { + reconnectionAttempts: 5, + timeout: 10000, + autoConnect: true, + }); + + socket.on("connect", () => { + setStatus(true); + notify(`Connected: ${socket?.id}`, "success"); + }); + + socket.on("disconnect", () => setStatus(false)); + socket.on("connect_error", () => setStatus(false)); + socket.on("connect_timeout", () => setStatus(false)); + socket.on("reconnect_failed", () => setStatus(false)); + + listeners = { + Connected: (payload) => useBattleDataStore.getState().onConnectedService(payload), + OnBattleBegin: (payload) => { + notify("Battle Started!", "info"); + useBattleDataStore.getState().onBattleBeginService(payload); + }, + OnSetBattleLineup: (payload) => useBattleDataStore.getState().onSetBattleLineupService(payload), + OnDamage: (payload) => useBattleDataStore.getState().onDamageService(payload), + OnTurnBegin: (payload) => useBattleDataStore.getState().onTurnBeginService(payload), + OnTurnEnd: (payload) => useBattleDataStore.getState().onTurnEndService(payload), + OnEntityDefeated: (payload) => useBattleDataStore.getState().onEntityDefeatedService(payload), + OnUseSkill: (payload) => useBattleDataStore.getState().onUseSkillService(payload), + OnUpdateWave: (payload) => useBattleDataStore.getState().onUpdateWaveService(payload), + OnUpdateCycle: (payload) => useBattleDataStore.getState().onUpdateCycleService(payload), + OnStatChange: (payload) => useBattleDataStore.getState().onStatChange(payload), + OnUpdateTeamFormation: (payload) => useBattleDataStore.getState().onUpdateTeamFormation(payload), + OnInitializeEnemy: (payload) => useBattleDataStore.getState().onInitializeEnemyService(payload), + OnBattleEnd: (payload) => useBattleDataStore.getState().onBattleEndService(payload), + OnCreateBattle: (payload) => useBattleDataStore.getState().onCreateBattleService(payload), + Error: (msg) => console.error("Server Error:", msg), + }; + + const register = (eventName: string) => { + socket?.on(eventName, async (raw: unknown) => { + if (!listeners) return; + if (workerPool) { + const res = await parseWithWorker(raw); + if (res.err) { + safeEnqueue("Error", `Worker parse error for ${eventName}: ${res.err}`); + } else if (res.parsed !== null) { + safeEnqueue(eventName as keyof SocketEvents, res.parsed); + } + } else { + try { + const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw; + safeEnqueue(eventName as keyof SocketEvents, parsed); + } catch (err) { + safeEnqueue("Error", `Parse error for ${eventName}: ${err && (err as Error).message ? (err as Error).message : String(err)}`); + } + } + }); + }; + + Object.keys(listeners).forEach((k) => register(k)); + + createWorkerPool(); + + return socket; +}; + +export const disconnectSocket = (): void => { + if (!socket) return; + if (listeners) { + Object.keys(listeners).forEach((eventName) => { + socket?.off(eventName); + }); + } + if (workerBlobUrl) { + URL.revokeObjectURL(workerBlobUrl); + workerBlobUrl = null; + } + socket.disconnect(); + useSocketStore.getState().setStatus(false); + listeners = null; + if (workerPool) { + workerPool.forEach((w) => w.terminate()); + workerPool = null; + } + pendingMap.clear(); + eventQueue = []; +}; + +export const isSocketConnected = (): boolean => socket?.connected || false; +export const getSocket = (): Socket | null => socket; + +export const setWorkerPoolSize = (size: number) => { + if (workerPool) { + workerPool.forEach((w) => w.terminate()); + workerPool = null; + } + createWorkerPool(Math.max(1, size)); +}; + diff --git a/src/stores/avatarDataStore.ts b/src/stores/avatarDataStore.ts index ac453e1..c20e640 100644 --- a/src/stores/avatarDataStore.ts +++ b/src/stores/avatarDataStore.ts @@ -1,30 +1,30 @@ -import { CharacterBasic, MonsterBasic } from '@/types'; -import { create } from 'zustand' - - -interface AvatarDataState { - listAvatar: CharacterBasic[]; - mapAvatar: Record; - listEnemy: MonsterBasic[]; - mapEnemy: Record; - setListAvatar: (list: CharacterBasic[]) => void; - setMapAvatar: (data: Record) => void; - setListEnemy: (list: MonsterBasic[]) => void; - setMapEnemy: (data: Record) => void; - - -} - -const useAvatarDataStore = create((set) => ({ - listAvatar: [], - listEnemy: [], - mapAvatar: {}, - mapEnemy: {}, - setListAvatar: (list: CharacterBasic[]) => set({ listAvatar: list }), - setMapAvatar: (data: Record) => set({ mapAvatar: data }), - setListEnemy: (list: MonsterBasic[]) => set({ listEnemy: list }), - setMapEnemy: (data: Record) => set({ mapEnemy: data }) - -})); - +import { CharacterBasic, MonsterBasic } from '@/types'; +import { create } from 'zustand' + + +interface AvatarDataState { + listAvatar: CharacterBasic[]; + mapAvatar: Record; + listEnemy: MonsterBasic[]; + mapEnemy: Record; + setListAvatar: (list: CharacterBasic[]) => void; + setMapAvatar: (data: Record) => void; + setListEnemy: (list: MonsterBasic[]) => void; + setMapEnemy: (data: Record) => void; + + +} + +const useAvatarDataStore = create((set) => ({ + listAvatar: [], + listEnemy: [], + mapAvatar: {}, + mapEnemy: {}, + setListAvatar: (list: CharacterBasic[]) => set({ listAvatar: list }), + setMapAvatar: (data: Record) => set({ mapAvatar: data }), + setListEnemy: (list: MonsterBasic[]) => set({ listEnemy: list }), + setMapEnemy: (data: Record) => set({ mapEnemy: data }) + +})); + export default useAvatarDataStore; \ No newline at end of file diff --git a/src/stores/battleDataStore.ts b/src/stores/battleDataStore.ts index 709724b..e78df35 100644 --- a/src/stores/battleDataStore.ts +++ b/src/stores/battleDataStore.ts @@ -1,369 +1,369 @@ -import { DamageType, AvatarAnalysisJson, UseSkillType, BattleBeginType, BattleEndType, DamageDetailType, EntityDefeatedType, SetBattleLineupType, TurnBeginType, TurnEndType, UpdateCycleType, UpdateWaveType, VersionType, StatChangeType, UpdateTeamFormationType, Team, ParseAttackType } from '@/types'; -import { InitializeEnemyType } from '@/types/enemy'; -import { AvatarBattleInfo, AvatarInfo, BattleDataStateJson, EnemyInfo, SkillBattleInfo, TurnBattleInfo } from '@/types/mics'; -import { create } from 'zustand' - - -interface BattleDataState { - lineup: AvatarBattleInfo[]; - turnHistory: TurnBattleInfo[] - skillHistory: SkillBattleInfo[] - dataAvatar: AvatarAnalysisJson[] - totalAV: number; - totalDamage: number; - damagePerAV: number; - cycleIndex: number; - waveIndex: number; - maxWave: number; - maxCycle: number; - version?: string; - avatarDetail?: Record; - enemyDetail?: Record; - - onConnectedService: (data: VersionType) => void - onBattleBeginService: (data: BattleBeginType) => void; - onSetBattleLineupService: (data: SetBattleLineupType) => void; - onDamageService: (data: DamageType) => void; - onTurnBeginService: (data: TurnBeginType) => void; - onTurnEndService: (data: TurnEndType) => void; - onEntityDefeatedService: (data: EntityDefeatedType) => void; - onUseSkillService: (data: UseSkillType) => void; - onUpdateWaveService: (data: UpdateWaveType) => void; - onUpdateCycleService: (data: UpdateCycleType) => void; - onStatChange: (data: StatChangeType) => void; - onUpdateTeamFormation: (data: UpdateTeamFormationType) => void; - onInitializeEnemyService: (data: InitializeEnemyType) => void; - onBattleEndService: (data: BattleEndType) => void; - onCreateBattleService: (data: AvatarAnalysisJson[]) => void; - loadBattleDataFromJSON: (data: BattleDataStateJson) => void; -} - -const useBattleDataStore = create((set, get) => ({ - lineup: [], - turnHistory: [], - skillHistory: [], - dataAvatar: [], - totalAV: 0, - totalDamage: 0, - damagePerAV: 0, - cycleIndex: 0, - waveIndex: 1, - maxWave: Infinity, - maxCycle: Infinity, - version: undefined, - avatarDetail: undefined, - enemyDetail: undefined, - loadBattleDataFromJSON: (data: BattleDataStateJson) => { - const skillHistory = data.skillHistory.map(it => { - it.damageDetail = it.damageDetail.map(it => { - return { - ...it, - damage_type: ParseAttackType(it.damage_type) - } - }) - return it - }) - set({ - lineup: data.lineup, - turnHistory: data.turnHistory, - skillHistory: skillHistory, - dataAvatar: data.dataAvatar, - totalAV: data.totalAV, - totalDamage: data.totalDamage, - damagePerAV: data.damagePerAV, - cycleIndex: data.cycleIndex, - waveIndex: data.waveIndex, - maxWave: data.maxWave, - maxCycle: data.maxCycle, - version: data.version, - avatarDetail: data.avatarDetail, - enemyDetail: data.enemyDetail - }) - }, - onConnectedService: (data: VersionType) => { - set({ - version: data.version - }) - }, - onCreateBattleService: (data: AvatarAnalysisJson[]) => { - set({ - dataAvatar: data - }) - }, - onBattleBeginService: (data: BattleBeginType) => { - set({ - maxWave: data.max_waves, - maxCycle: data.max_cycles - }) - }, - - onSetBattleLineupService: (data: SetBattleLineupType) => { - const lineups: AvatarBattleInfo[] = [] - for (const avatar of data.avatars) { - lineups.push({ avatarId: avatar.id, isDie: false } as AvatarBattleInfo) - } - set(() => ({ - lineup: lineups, - turnHistory: [{ - avatarId: -1, - actionValue: 0, - waveIndex: 1, - cycleIndex: 0, - } as TurnBattleInfo], - skillHistory: [], - totalAV: 0, - totalDamage: 0, - damagePerAV: 0, - cycleIndex: 0, - maxWave: Infinity, - maxCycle: Infinity, - waveIndex: 1, - })); - }, - - onDamageService: (data: DamageType) => { - const skillHistory = get().skillHistory - - const skillIdx = skillHistory.findLastIndex(it => it.avatarId === data.attacker.uid) - if (skillIdx === -1) { - return - } - const newTh = [...skillHistory] - newTh[skillIdx].damageDetail.push({ - damage: data.damage, - overkill_damage: data.overkill_damage, - damage_type: ParseAttackType(data.damage_type ? data.damage_type : data.type), - } as DamageDetailType) - newTh[skillIdx].totalDamage += data.damage - set({ - skillHistory: newTh, - totalDamage: get().totalDamage + data.damage, - damagePerAV: (get().totalDamage + data.damage) / (get().totalAV === 0 ? 1 : get().totalAV) - }) - }, - - onTurnBeginService: (data: TurnBeginType) => { - set((state) => ({ - totalAV: data.action_value, - damagePerAV: state.totalDamage / (data.action_value === 0 ? 1 : data.action_value), - turnHistory: [...state.turnHistory, { - avatarId: data?.turn_owner?.uid, - actionValue: data.action_value, - waveIndex: state.waveIndex, - cycleIndex: state.cycleIndex - } as TurnBattleInfo] - })) - }, - - onTurnEndService: (data: TurnEndType) => { - set((state) => ({ - totalDamage: state.totalDamage === data.turn_info.total_damage ? data.turn_info.total_damage : state.totalDamage, - currentAV: data.turn_info.action_value, - damagePerAV: (state.totalDamage === data.turn_info.total_damage ? data.turn_info.total_damage : state.totalDamage) - / (data.turn_info.action_value === 0 ? 1 : data.turn_info.action_value) - })); - }, - - onEntityDefeatedService: (data: EntityDefeatedType) => { - let avatarDetail = get().avatarDetail - let enemyDetail = get().enemyDetail - if (!enemyDetail) { - enemyDetail = {} as Record - } - if (!avatarDetail) { - avatarDetail = {} as Record - } - if (data.killer.team === "Player" && enemyDetail[data.entity_defeated.uid]) { - enemyDetail[data.entity_defeated.uid].isDie = true - enemyDetail[data.entity_defeated.uid].killer_uid = data.killer.uid - } else if (data.killer.team === "Enemy" && avatarDetail[data.entity_defeated.uid]) { - avatarDetail[data.entity_defeated.uid].isDie = true - avatarDetail[data.entity_defeated.uid].killer_uid = data.killer.uid - } - set({ - avatarDetail: avatarDetail, - enemyDetail: enemyDetail - }) - }, - - onUseSkillService: (data: UseSkillType) => { - set((state) => ({ - skillHistory: [...state.skillHistory, { - avatarId: data.avatar.uid, - damageDetail: [], - totalDamage: 0, - skillType: ParseAttackType(data.skill.type), - skillName: data.skill.name, - turnBattleId: state.turnHistory.length - 1 - } as SkillBattleInfo] - })) - }, - - onUpdateWaveService: (data: UpdateWaveType) => { - set({ - waveIndex: data.wave - }) - }, - - onUpdateCycleService: (data: UpdateCycleType) => { - set({ - cycleIndex: data.cycle - }) - }, - - onStatChange: (data: StatChangeType) => { - let avatarDetail = get().avatarDetail - let enemyDetail = get().enemyDetail - if (!enemyDetail) { - enemyDetail = {} as Record - } - if (!avatarDetail) { - avatarDetail = {} as Record - } - - let key: string; - let value: number; - if (data.property) { - key = data.property.type; - value = data.property.value; - } else if (data.stat) { - if ( - data.stat && - typeof data.stat === 'object' && - 'type' in data.stat && - 'value' in data.stat && - typeof data.stat.type === 'string' - ) { - key = data.stat.type; - value = Number(data.stat.value); - } else { - const entries = Object.entries(data.stat); - if (entries.length === 0) return; - [key, value] = entries[0] as [string, number]; - } - } else { - return; - } - - if (key === "CurrentHP") key = "HP"; - - if (data.entity.team === "Player") { - const uid = data.entity.uid; - - if (!avatarDetail[uid]) { - avatarDetail[uid] = { - id: uid, - isDie: false, - killer_uid: -1, - stats: {}, - statsHistory: [] - }; - } - avatarDetail[uid].stats[key] = value - avatarDetail[uid].statsHistory.push({ - stats: { [key]: value }, - turnBattleId: get().turnHistory.length - 1 - }) - } else { - const uid = data.entity.uid; - - if (!enemyDetail[uid]) { - enemyDetail[uid] = { - id: uid, - isDie: false, - killer_uid: -1, - name: "", - positionIndex: Object.keys(get().enemyDetail || {}).length, - maxHP: 0, - waveIndex: 0, - level: 0, - stats: {}, - statsHistory: [] - }; - } - enemyDetail[uid].stats[key] = value - enemyDetail[uid].statsHistory.push({ - stats: { [key]: value }, - turnBattleId: get().turnHistory.length - 1 - }) - } - set({ - avatarDetail: avatarDetail, - enemyDetail: enemyDetail - }) - }, - - onUpdateTeamFormation: (data: UpdateTeamFormationType) => { - let avatarDetail = get().avatarDetail - let enemyDetail = get().enemyDetail - if (!enemyDetail) { - enemyDetail = {} as Record - } - - if (!avatarDetail) { - avatarDetail = {} as Record - } - if (data.team === Team.Enemy) { - for (let i = 0; i < data.entities.length; i++) { - const entity = data.entities[i]; - if (entity.team === Team.Enemy && enemyDetail?.[entity.uid]) { - enemyDetail[entity.uid].positionIndex = i - enemyDetail[entity.uid].waveIndex = get().waveIndex - } - } - } - - - set({ - avatarDetail: avatarDetail, - enemyDetail: enemyDetail - }) - }, - - onInitializeEnemyService: (data: InitializeEnemyType) => { - const enemyDetail = get().enemyDetail - if (!enemyDetail) { - return - } - let maxHP = 0; - let level = 0; - if ('properties' in data.enemy.base_stats) { - maxHP = data.enemy.base_stats.properties["MaxHP"] || 0; - level = data.enemy.base_stats.properties["Level"] || 0; - } else { - maxHP = data.enemy.base_stats.hp || data.enemy.base_stats.CurrentHP || 0; - level = data.enemy.base_stats.level; - } - - enemyDetail[data.enemy.uid] = { - id: data.enemy.id, - isDie: false, - killer_uid: -1, - positionIndex: enemyDetail?.[data.enemy.uid]?.positionIndex || 0, - waveIndex: get().waveIndex, - name: data.enemy.name, - maxHP: maxHP, - level: level, - stats: {}, - statsHistory: [] - } - set({ - enemyDetail: enemyDetail - }) - }, - - onBattleEndService: (data: BattleEndType) => { - const lineups: AvatarBattleInfo[] = [] - for (const avatar of data.avatars) { - lineups.push({ avatarId: avatar.id, isDie: false } as AvatarBattleInfo) - } - set({ - lineup: lineups, - totalDamage: data.total_damage, - totalAV: data.action_value, - damagePerAV: data.total_damage / (data.action_value === 0 ? 1 : data.action_value) - }) - }, -})); - +import { DamageType, AvatarAnalysisJson, UseSkillType, BattleBeginType, BattleEndType, DamageDetailType, EntityDefeatedType, SetBattleLineupType, TurnBeginType, TurnEndType, UpdateCycleType, UpdateWaveType, VersionType, StatChangeType, UpdateTeamFormationType, Team, ParseAttackType } from '@/types'; +import { InitializeEnemyType } from '@/types/enemy'; +import { AvatarBattleInfo, AvatarInfo, BattleDataStateJson, EnemyInfo, SkillBattleInfo, TurnBattleInfo } from '@/types/mics'; +import { create } from 'zustand' + + +interface BattleDataState { + lineup: AvatarBattleInfo[]; + turnHistory: TurnBattleInfo[] + skillHistory: SkillBattleInfo[] + dataAvatar: AvatarAnalysisJson[] + totalAV: number; + totalDamage: number; + damagePerAV: number; + cycleIndex: number; + waveIndex: number; + maxWave: number; + maxCycle: number; + version?: string; + avatarDetail?: Record; + enemyDetail?: Record; + + onConnectedService: (data: VersionType) => void + onBattleBeginService: (data: BattleBeginType) => void; + onSetBattleLineupService: (data: SetBattleLineupType) => void; + onDamageService: (data: DamageType) => void; + onTurnBeginService: (data: TurnBeginType) => void; + onTurnEndService: (data: TurnEndType) => void; + onEntityDefeatedService: (data: EntityDefeatedType) => void; + onUseSkillService: (data: UseSkillType) => void; + onUpdateWaveService: (data: UpdateWaveType) => void; + onUpdateCycleService: (data: UpdateCycleType) => void; + onStatChange: (data: StatChangeType) => void; + onUpdateTeamFormation: (data: UpdateTeamFormationType) => void; + onInitializeEnemyService: (data: InitializeEnemyType) => void; + onBattleEndService: (data: BattleEndType) => void; + onCreateBattleService: (data: AvatarAnalysisJson[]) => void; + loadBattleDataFromJSON: (data: BattleDataStateJson) => void; +} + +const useBattleDataStore = create((set, get) => ({ + lineup: [], + turnHistory: [], + skillHistory: [], + dataAvatar: [], + totalAV: 0, + totalDamage: 0, + damagePerAV: 0, + cycleIndex: 0, + waveIndex: 1, + maxWave: Infinity, + maxCycle: Infinity, + version: undefined, + avatarDetail: undefined, + enemyDetail: undefined, + loadBattleDataFromJSON: (data: BattleDataStateJson) => { + const skillHistory = data.skillHistory.map(it => { + it.damageDetail = it.damageDetail.map(it => { + return { + ...it, + damage_type: ParseAttackType(it.damage_type) + } + }) + return it + }) + set({ + lineup: data.lineup, + turnHistory: data.turnHistory, + skillHistory: skillHistory, + dataAvatar: data.dataAvatar, + totalAV: data.totalAV, + totalDamage: data.totalDamage, + damagePerAV: data.damagePerAV, + cycleIndex: data.cycleIndex, + waveIndex: data.waveIndex, + maxWave: data.maxWave, + maxCycle: data.maxCycle, + version: data.version, + avatarDetail: data.avatarDetail, + enemyDetail: data.enemyDetail + }) + }, + onConnectedService: (data: VersionType) => { + set({ + version: data.version + }) + }, + onCreateBattleService: (data: AvatarAnalysisJson[]) => { + set({ + dataAvatar: data + }) + }, + onBattleBeginService: (data: BattleBeginType) => { + set({ + maxWave: data.max_waves, + maxCycle: data.max_cycles + }) + }, + + onSetBattleLineupService: (data: SetBattleLineupType) => { + const lineups: AvatarBattleInfo[] = [] + for (const avatar of data.avatars) { + lineups.push({ avatarId: avatar.id, isDie: false } as AvatarBattleInfo) + } + set(() => ({ + lineup: lineups, + turnHistory: [{ + avatarId: -1, + actionValue: 0, + waveIndex: 1, + cycleIndex: 0, + } as TurnBattleInfo], + skillHistory: [], + totalAV: 0, + totalDamage: 0, + damagePerAV: 0, + cycleIndex: 0, + maxWave: Infinity, + maxCycle: Infinity, + waveIndex: 1, + })); + }, + + onDamageService: (data: DamageType) => { + const skillHistory = get().skillHistory + + const skillIdx = skillHistory.findLastIndex(it => it.avatarId === data.attacker.uid) + if (skillIdx === -1) { + return + } + const newTh = [...skillHistory] + newTh[skillIdx].damageDetail.push({ + damage: data.damage, + overkill_damage: data.overkill_damage, + damage_type: ParseAttackType(data.damage_type ? data.damage_type : data.type), + } as DamageDetailType) + newTh[skillIdx].totalDamage += data.damage + set({ + skillHistory: newTh, + totalDamage: get().totalDamage + data.damage, + damagePerAV: (get().totalDamage + data.damage) / (get().totalAV === 0 ? 1 : get().totalAV) + }) + }, + + onTurnBeginService: (data: TurnBeginType) => { + set((state) => ({ + totalAV: data.action_value, + damagePerAV: state.totalDamage / (data.action_value === 0 ? 1 : data.action_value), + turnHistory: [...state.turnHistory, { + avatarId: data?.turn_owner?.uid, + actionValue: data.action_value, + waveIndex: state.waveIndex, + cycleIndex: state.cycleIndex + } as TurnBattleInfo] + })) + }, + + onTurnEndService: (data: TurnEndType) => { + set((state) => ({ + totalDamage: state.totalDamage === data.turn_info.total_damage ? data.turn_info.total_damage : state.totalDamage, + currentAV: data.turn_info.action_value, + damagePerAV: (state.totalDamage === data.turn_info.total_damage ? data.turn_info.total_damage : state.totalDamage) + / (data.turn_info.action_value === 0 ? 1 : data.turn_info.action_value) + })); + }, + + onEntityDefeatedService: (data: EntityDefeatedType) => { + let avatarDetail = get().avatarDetail + let enemyDetail = get().enemyDetail + if (!enemyDetail) { + enemyDetail = {} as Record + } + if (!avatarDetail) { + avatarDetail = {} as Record + } + if (data.killer.team === "Player" && enemyDetail[data.entity_defeated.uid]) { + enemyDetail[data.entity_defeated.uid].isDie = true + enemyDetail[data.entity_defeated.uid].killer_uid = data.killer.uid + } else if (data.killer.team === "Enemy" && avatarDetail[data.entity_defeated.uid]) { + avatarDetail[data.entity_defeated.uid].isDie = true + avatarDetail[data.entity_defeated.uid].killer_uid = data.killer.uid + } + set({ + avatarDetail: avatarDetail, + enemyDetail: enemyDetail + }) + }, + + onUseSkillService: (data: UseSkillType) => { + set((state) => ({ + skillHistory: [...state.skillHistory, { + avatarId: data.avatar.uid, + damageDetail: [], + totalDamage: 0, + skillType: ParseAttackType(data.skill.type), + skillName: data.skill.name, + turnBattleId: state.turnHistory.length - 1 + } as SkillBattleInfo] + })) + }, + + onUpdateWaveService: (data: UpdateWaveType) => { + set({ + waveIndex: data.wave + }) + }, + + onUpdateCycleService: (data: UpdateCycleType) => { + set({ + cycleIndex: data.cycle + }) + }, + + onStatChange: (data: StatChangeType) => { + let avatarDetail = get().avatarDetail + let enemyDetail = get().enemyDetail + if (!enemyDetail) { + enemyDetail = {} as Record + } + if (!avatarDetail) { + avatarDetail = {} as Record + } + + let key: string; + let value: number; + if (data.property) { + key = data.property.type; + value = data.property.value; + } else if (data.stat) { + if ( + data.stat && + typeof data.stat === 'object' && + 'type' in data.stat && + 'value' in data.stat && + typeof data.stat.type === 'string' + ) { + key = data.stat.type; + value = Number(data.stat.value); + } else { + const entries = Object.entries(data.stat); + if (entries.length === 0) return; + [key, value] = entries[0] as [string, number]; + } + } else { + return; + } + + if (key === "CurrentHP") key = "HP"; + + if (data.entity.team === "Player") { + const uid = data.entity.uid; + + if (!avatarDetail[uid]) { + avatarDetail[uid] = { + id: uid, + isDie: false, + killer_uid: -1, + stats: {}, + statsHistory: [] + }; + } + avatarDetail[uid].stats[key] = value + avatarDetail[uid].statsHistory.push({ + stats: { [key]: value }, + turnBattleId: get().turnHistory.length - 1 + }) + } else { + const uid = data.entity.uid; + + if (!enemyDetail[uid]) { + enemyDetail[uid] = { + id: uid, + isDie: false, + killer_uid: -1, + name: "", + positionIndex: Object.keys(get().enemyDetail || {}).length, + maxHP: 0, + waveIndex: 0, + level: 0, + stats: {}, + statsHistory: [] + }; + } + enemyDetail[uid].stats[key] = value + enemyDetail[uid].statsHistory.push({ + stats: { [key]: value }, + turnBattleId: get().turnHistory.length - 1 + }) + } + set({ + avatarDetail: avatarDetail, + enemyDetail: enemyDetail + }) + }, + + onUpdateTeamFormation: (data: UpdateTeamFormationType) => { + let avatarDetail = get().avatarDetail + let enemyDetail = get().enemyDetail + if (!enemyDetail) { + enemyDetail = {} as Record + } + + if (!avatarDetail) { + avatarDetail = {} as Record + } + if (data.team === Team.Enemy) { + for (let i = 0; i < data.entities.length; i++) { + const entity = data.entities[i]; + if (entity.team === Team.Enemy && enemyDetail?.[entity.uid]) { + enemyDetail[entity.uid].positionIndex = i + enemyDetail[entity.uid].waveIndex = get().waveIndex + } + } + } + + + set({ + avatarDetail: avatarDetail, + enemyDetail: enemyDetail + }) + }, + + onInitializeEnemyService: (data: InitializeEnemyType) => { + const enemyDetail = get().enemyDetail + if (!enemyDetail) { + return + } + let maxHP = 0; + let level = 0; + if ('properties' in data.enemy.base_stats) { + maxHP = data.enemy.base_stats.properties["MaxHP"] || 0; + level = data.enemy.base_stats.properties["Level"] || 0; + } else { + maxHP = data.enemy.base_stats.hp || data.enemy.base_stats.CurrentHP || 0; + level = data.enemy.base_stats.level; + } + + enemyDetail[data.enemy.uid] = { + id: data.enemy.id, + isDie: false, + killer_uid: -1, + positionIndex: enemyDetail?.[data.enemy.uid]?.positionIndex || 0, + waveIndex: get().waveIndex, + name: data.enemy.name, + maxHP: maxHP, + level: level, + stats: {}, + statsHistory: [] + } + set({ + enemyDetail: enemyDetail + }) + }, + + onBattleEndService: (data: BattleEndType) => { + const lineups: AvatarBattleInfo[] = [] + for (const avatar of data.avatars) { + lineups.push({ avatarId: avatar.id, isDie: false } as AvatarBattleInfo) + } + set({ + lineup: lineups, + totalDamage: data.total_damage, + totalAV: data.action_value, + damagePerAV: data.total_damage / (data.action_value === 0 ? 1 : data.action_value) + }) + }, +})); + export default useBattleDataStore; \ No newline at end of file diff --git a/src/stores/localeStore.ts b/src/stores/localeStore.ts index 75183a7..fc7557b 100644 --- a/src/stores/localeStore.ts +++ b/src/stores/localeStore.ts @@ -1,25 +1,25 @@ -import { create } from 'zustand' -import { createJSONStorage, persist } from 'zustand/middleware'; - - -interface LocaleState { - locale: string; - theme: string - setTheme: (newTheme: string) => void; - setLocale: (newLocale: string) => void; -} - -const useLocaleStore = create()(persist( - (set) => ({ - locale: "en", - theme: "night", - setTheme: (newTheme: string) => set({ theme: newTheme }), - setLocale: (newLocale: string) => set({ locale: newLocale }), - }), - { - name: 'locale-storage', - storage: createJSONStorage(() => localStorage), - } -)); - +import { create } from 'zustand' +import { createJSONStorage, persist } from 'zustand/middleware'; + + +interface LocaleState { + locale: string; + theme: string + setTheme: (newTheme: string) => void; + setLocale: (newLocale: string) => void; +} + +const useLocaleStore = create()(persist( + (set) => ({ + locale: "en", + theme: "night", + setTheme: (newTheme: string) => set({ theme: newTheme }), + setLocale: (newLocale: string) => set({ locale: newLocale }), + }), + { + name: 'locale-storage', + storage: createJSONStorage(() => localStorage), + } +)); + export default useLocaleStore; \ No newline at end of file diff --git a/src/stores/socketSettingStore.ts b/src/stores/socketSettingStore.ts index 209ed4d..f442af6 100644 --- a/src/stores/socketSettingStore.ts +++ b/src/stores/socketSettingStore.ts @@ -1,33 +1,33 @@ -import { create } from 'zustand'; -import { persist } from 'zustand/middleware'; - -interface SocketState { - host: string; - port: number; - status: boolean; - connectionType: string; - setHost: (host: string) => void; - setConnectionType: (host: string) => void; - setPort: (port: number) => void; - setStatus: (status: boolean) => void; -} - -const useSocketStore = create()( - persist( - (set) => ({ - host: "http://localhost", - port: 3443, - status: false, - connectionType: "Native", - setHost: (host: string) => set({ host }), - setConnectionType: (connectionType: string) => set({ connectionType }), - setPort: (port: number) => set({ port }), - setStatus: (status: boolean) => set({ status }), - }), - { - name: 'socket-storage', - } - ) -); - -export default useSocketStore; +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +interface SocketState { + host: string; + port: number; + status: boolean; + connectionType: string; + setHost: (host: string) => void; + setConnectionType: (host: string) => void; + setPort: (port: number) => void; + setStatus: (status: boolean) => void; +} + +const useSocketStore = create()( + persist( + (set) => ({ + host: "http://localhost", + port: 3443, + status: false, + connectionType: "Native", + setHost: (host: string) => set({ host }), + setConnectionType: (connectionType: string) => set({ connectionType }), + setPort: (port: number) => set({ port }), + setStatus: (status: boolean) => set({ status }), + }), + { + name: 'socket-storage', + } + ) +); + +export default useSocketStore; diff --git a/src/types/attack.ts b/src/types/attack.ts index a94343b..1415347 100644 --- a/src/types/attack.ts +++ b/src/types/attack.ts @@ -1,101 +1,101 @@ -import { EntityType } from "./entity"; - -export interface DamageType { - attacker: EntityType; - damage: number; - overkill_damage?: number; - damage_type?: AttackType | string; - type?: AttackType | string; -} - -export interface DamageDetailType { - damage: number; - overkill_damage?: number; - damage_type?: AttackType; -} - - -export enum AttackType { - Unknown = 0, - Normal = 1, - BPSkill = 2, - Ultra = 3, - QTE = 4, - DOT = 5, - Pursued = 6, - Maze = 7, - MazeNormal = 8, - Insert = 9, - ElementDamage = 10, - Level = 11, - Servant = 12, - TrueDamage = 13, - ElationDamage = 14 -} - - -const attackTypeMap: Record = { - Talent: AttackType.Unknown, - Basic: AttackType.Normal, - Skill: AttackType.BPSkill, - Ultimate: AttackType.Ultra, - QTE: AttackType.QTE, - DOT: AttackType.DOT, - DoT: AttackType.DOT, - Pursued: AttackType.Pursued, - Additional: AttackType.Pursued, - Technique: AttackType.Maze, - MazeNormal: AttackType.MazeNormal, - "Follow-up": AttackType.Insert, - "Follow-Up": AttackType.Insert, - "Elemental Damage": AttackType.ElementDamage, - Break: AttackType.ElementDamage, - Level: AttackType.Level, - Servant: AttackType.Servant, - "True Damage": AttackType.TrueDamage, - True: AttackType.TrueDamage, - "Elation Damage": AttackType.ElationDamage, - Elation: AttackType.ElationDamage, -}; - -export function ParseAttackType(type: AttackType | string | undefined): AttackType { - if (type === undefined || type === null) { - return AttackType.Unknown; - } - - if (typeof type === "number") { - return type in AttackType ? type : AttackType.Unknown; - } - - const num = Number(type); - if (!isNaN(num)) { - return num in AttackType ? num as AttackType : AttackType.Unknown; - } - - return attackTypeMap[type] ?? AttackType.Unknown; -} - -export function attackTypeToString(type: AttackType | undefined): string { - if (type === undefined) { - return "" - } - - switch (type) { - case AttackType.Unknown: return "Talent"; - case AttackType.Normal: return "Basic"; - case AttackType.BPSkill: return "Skill"; - case AttackType.Ultra: return "Ultimate"; - case AttackType.QTE: return "QTE"; - case AttackType.DOT: return "DOT"; - case AttackType.Pursued: return "Pursued"; - case AttackType.Maze: return "Technique"; - case AttackType.MazeNormal: return "MazeNormal"; - case AttackType.Insert: return "Follow-up"; - case AttackType.ElementDamage: return "Elemental Damage"; - case AttackType.Level: return "Level"; - case AttackType.Servant: return "Servant"; - case AttackType.TrueDamage: return "True Damage"; - case AttackType.ElationDamage: return "Elation Damage"; - default: return "Unknown"; - } -} +import { EntityType } from "./entity"; + +export interface DamageType { + attacker: EntityType; + damage: number; + overkill_damage?: number; + damage_type?: AttackType | string; + type?: AttackType | string; +} + +export interface DamageDetailType { + damage: number; + overkill_damage?: number; + damage_type?: AttackType; +} + + +export enum AttackType { + Unknown = 0, + Normal = 1, + BPSkill = 2, + Ultra = 3, + QTE = 4, + DOT = 5, + Pursued = 6, + Maze = 7, + MazeNormal = 8, + Insert = 9, + ElementDamage = 10, + Level = 11, + Servant = 12, + TrueDamage = 13, + ElationDamage = 14 +} + + +const attackTypeMap: Record = { + Talent: AttackType.Unknown, + Basic: AttackType.Normal, + Skill: AttackType.BPSkill, + Ultimate: AttackType.Ultra, + QTE: AttackType.QTE, + DOT: AttackType.DOT, + DoT: AttackType.DOT, + Pursued: AttackType.Pursued, + Additional: AttackType.Pursued, + Technique: AttackType.Maze, + MazeNormal: AttackType.MazeNormal, + "Follow-up": AttackType.Insert, + "Follow-Up": AttackType.Insert, + "Elemental Damage": AttackType.ElementDamage, + Break: AttackType.ElementDamage, + Level: AttackType.Level, + Servant: AttackType.Servant, + "True Damage": AttackType.TrueDamage, + True: AttackType.TrueDamage, + "Elation Damage": AttackType.ElationDamage, + Elation: AttackType.ElationDamage, +}; + +export function ParseAttackType(type: AttackType | string | undefined): AttackType { + if (type === undefined || type === null) { + return AttackType.Unknown; + } + + if (typeof type === "number") { + return type in AttackType ? type : AttackType.Unknown; + } + + const num = Number(type); + if (!isNaN(num)) { + return num in AttackType ? num as AttackType : AttackType.Unknown; + } + + return attackTypeMap[type] ?? AttackType.Unknown; +} + +export function attackTypeToString(type: AttackType | undefined): string { + if (type === undefined) { + return "" + } + + switch (type) { + case AttackType.Unknown: return "Talent"; + case AttackType.Normal: return "Basic"; + case AttackType.BPSkill: return "Skill"; + case AttackType.Ultra: return "Ultimate"; + case AttackType.QTE: return "QTE"; + case AttackType.DOT: return "DOT"; + case AttackType.Pursued: return "Pursued"; + case AttackType.Maze: return "Technique"; + case AttackType.MazeNormal: return "MazeNormal"; + case AttackType.Insert: return "Follow-up"; + case AttackType.ElementDamage: return "Elemental Damage"; + case AttackType.Level: return "Level"; + case AttackType.Servant: return "Servant"; + case AttackType.TrueDamage: return "True Damage"; + case AttackType.ElationDamage: return "Elation Damage"; + default: return "Unknown"; + } +} diff --git a/src/types/avatar.ts b/src/types/avatar.ts index 32503a0..c8ebe74 100644 --- a/src/types/avatar.ts +++ b/src/types/avatar.ts @@ -1,8 +1,8 @@ -export interface CharacterBasic { - id: string; - icon: string; - rank: string; - baseType: string; - damageType: string; - lang: Record; -} +export interface CharacterBasic { + id: string; + icon: string; + rank: string; + baseType: string; + damageType: string; + lang: Record; +} diff --git a/src/types/battle.ts b/src/types/battle.ts index 79b7bdc..84a415e 100644 --- a/src/types/battle.ts +++ b/src/types/battle.ts @@ -1,18 +1,18 @@ -import { AvatarType } from "./lineup"; -import { TurnInfoType } from "./turn"; - -export interface BattleEndType { - avatars: AvatarType[]; - turn_history: TurnInfoType[]; - av_history: TurnInfoType[]; - turn_count: number; - total_damage: number; - action_value: number; - stage_id: number; -} - -export interface BattleBeginType { - max_waves: number - max_cycles: number - stage_id: number +import { AvatarType } from "./lineup"; +import { TurnInfoType } from "./turn"; + +export interface BattleEndType { + avatars: AvatarType[]; + turn_history: TurnInfoType[]; + av_history: TurnInfoType[]; + turn_count: number; + total_damage: number; + action_value: number; + stage_id: number; +} + +export interface BattleBeginType { + max_waves: number + max_cycles: number + stage_id: number } \ No newline at end of file diff --git a/src/types/enemy.ts b/src/types/enemy.ts index 292a8ad..ca470ea 100644 --- a/src/types/enemy.ts +++ b/src/types/enemy.ts @@ -1,26 +1,26 @@ -import { StatsType } from "./stat"; - -export interface BattleStatsType { - properties: Record; -} - -export interface EnemyType { - id: number; - uid: number; - name: string; - base_stats: BattleStatsType | StatsType; -} - -export interface InitializeEnemyType { - enemy: EnemyType -} - -export interface MonsterBasic { - id: string; - rank: string; - icon: string; - image: string; - weak: string[]; - desc: Record; - lang: Record; -} +import { StatsType } from "./stat"; + +export interface BattleStatsType { + properties: Record; +} + +export interface EnemyType { + id: number; + uid: number; + name: string; + base_stats: BattleStatsType | StatsType; +} + +export interface InitializeEnemyType { + enemy: EnemyType +} + +export interface MonsterBasic { + id: string; + rank: string; + icon: string; + image: string; + weak: string[]; + desc: Record; + lang: Record; +} diff --git a/src/types/entity.ts b/src/types/entity.ts index 09dd1d1..b2c780b 100644 --- a/src/types/entity.ts +++ b/src/types/entity.ts @@ -1,11 +1,11 @@ -import { Team } from "./team"; - -export interface EntityType { - uid: number; - team: Team; -} - -export interface EntityDefeatedType { - killer: EntityType, - entity_defeated: EntityType +import { Team } from "./team"; + +export interface EntityType { + uid: number; + team: Team; +} + +export interface EntityDefeatedType { + killer: EntityType, + entity_defeated: EntityType } \ No newline at end of file diff --git a/src/types/error.ts b/src/types/error.ts index ac8fd6f..2591b0b 100644 --- a/src/types/error.ts +++ b/src/types/error.ts @@ -1,3 +1,3 @@ -export interface ErrorType { - msg: string +export interface ErrorType { + msg: string } \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 9602ad6..8325890 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,13 +1,13 @@ -export * from "./attack" -export * from "./avatar" -export * from "./battle" -export * from "./lineup" -export * from "./skill" -export * from "./turn" -export * from "./waveAndCycle" -export * from "./srtools" -export * from "./version" -export * from "./entity" -export * from "./stat" -export * from "./enemy" -export * from "./team" +export * from "./attack" +export * from "./avatar" +export * from "./battle" +export * from "./lineup" +export * from "./skill" +export * from "./turn" +export * from "./waveAndCycle" +export * from "./srtools" +export * from "./version" +export * from "./entity" +export * from "./stat" +export * from "./enemy" +export * from "./team" diff --git a/src/types/lineup.ts b/src/types/lineup.ts index b35a078..2d31f98 100644 --- a/src/types/lineup.ts +++ b/src/types/lineup.ts @@ -1,17 +1,17 @@ -import { EntityType } from "./entity"; -import { Team } from "./team"; - -export interface AvatarType{ - id: number; - name: string; -} - -export interface SetBattleLineupType { - avatars: AvatarType[]; -} - - -export interface UpdateTeamFormationType { - entities: EntityType[], - team: Team +import { EntityType } from "./entity"; +import { Team } from "./team"; + +export interface AvatarType{ + id: number; + name: string; +} + +export interface SetBattleLineupType { + avatars: AvatarType[]; +} + + +export interface UpdateTeamFormationType { + entities: EntityType[], + team: Team } \ No newline at end of file diff --git a/src/types/mics.ts b/src/types/mics.ts index 5529134..9a2ac99 100644 --- a/src/types/mics.ts +++ b/src/types/mics.ts @@ -1,68 +1,68 @@ -import {AttackType, DamageDetailType} from "./attack"; -import { AvatarAnalysisJson } from "./srtools"; -import { StatType } from "./stat"; - - -export interface AvatarBattleInfo { - avatarId: number; - isDie?: boolean; -} - -export interface SkillBattleInfo { - avatarId: number; - damageDetail: DamageDetailType[]; - totalDamage: number; - skillType: AttackType; - skillName: string; - turnBattleId: number; -} - -export interface TurnBattleInfo { - avatarId: number; - actionValue: number; - waveIndex: number; - cycleIndex: number; -} - -export interface BattleDataStateJson { - lineup: AvatarBattleInfo[]; - turnHistory: TurnBattleInfo[] - skillHistory: SkillBattleInfo[] - dataAvatar: AvatarAnalysisJson[] - totalAV: number; - totalDamage: number; - damagePerAV: number; - maxWave: number; - cycleIndex: number, - waveIndex: number, - maxCycle: number, - version?: string, - avatarDetail?: Record; - enemyDetail?: Record; -} - -export interface StatsHistoryType { - stats: StatType - turnBattleId: number; -} - -export interface EnemyInfo { - id: number; - name: string; - maxHP: number; - level: number; - isDie: boolean; - positionIndex: number; - waveIndex: number; - killer_uid: number; - stats: Record; - statsHistory: StatsHistoryType[]; -} -export interface AvatarInfo { - id: number; - isDie: boolean; - killer_uid: number; - stats: Record; - statsHistory: StatsHistoryType[]; -} - +import {AttackType, DamageDetailType} from "./attack"; +import { AvatarAnalysisJson } from "./srtools"; +import { StatType } from "./stat"; + + +export interface AvatarBattleInfo { + avatarId: number; + isDie?: boolean; +} + +export interface SkillBattleInfo { + avatarId: number; + damageDetail: DamageDetailType[]; + totalDamage: number; + skillType: AttackType; + skillName: string; + turnBattleId: number; +} + +export interface TurnBattleInfo { + avatarId: number; + actionValue: number; + waveIndex: number; + cycleIndex: number; +} + +export interface BattleDataStateJson { + lineup: AvatarBattleInfo[]; + turnHistory: TurnBattleInfo[] + skillHistory: SkillBattleInfo[] + dataAvatar: AvatarAnalysisJson[] + totalAV: number; + totalDamage: number; + damagePerAV: number; + maxWave: number; + cycleIndex: number, + waveIndex: number, + maxCycle: number, + version?: string, + avatarDetail?: Record; + enemyDetail?: Record; +} + +export interface StatsHistoryType { + stats: StatType + turnBattleId: number; +} + +export interface EnemyInfo { + id: number; + name: string; + maxHP: number; + level: number; + isDie: boolean; + positionIndex: number; + waveIndex: number; + killer_uid: number; + stats: Record; + statsHistory: StatsHistoryType[]; +} +export interface AvatarInfo { + id: number; + isDie: boolean; + killer_uid: number; + stats: Record; + statsHistory: StatsHistoryType[]; +} + diff --git a/src/types/skill.ts b/src/types/skill.ts index a579fc7..12d02c9 100644 --- a/src/types/skill.ts +++ b/src/types/skill.ts @@ -1,14 +1,14 @@ - -import { AttackType } from "@/types/attack"; -import { EntityType } from "./entity"; - -export interface SkillInfo { - name: string; - type: AttackType | string; - skill_config_id: number; -} - -export interface UseSkillType { - avatar: EntityType; - skill: SkillInfo; + +import { AttackType } from "@/types/attack"; +import { EntityType } from "./entity"; + +export interface SkillInfo { + name: string; + type: AttackType | string; + skill_config_id: number; +} + +export interface UseSkillType { + avatar: EntityType; + skill: SkillInfo; } \ No newline at end of file diff --git a/src/types/srtools.ts b/src/types/srtools.ts index 0fce82e..b18dc44 100644 --- a/src/types/srtools.ts +++ b/src/types/srtools.ts @@ -1,42 +1,42 @@ -export interface AvatarAnalysisJson { - owner_uid: number; - avatar_id: number; - data: AvatarData | null; - level: number; - promotion: number; - techniques: number[]; - relics: RelicJson[]; - Lightcone: LightconeJson | null; - sp_value: number; - sp_max: number; -} - -export interface AvatarData { - rank: number; - skills: Record; -} - -export interface SubAffix { - sub_affix_id: number; - count: number; - step: number; -} - -export interface RelicJson { - level: number; - relic_id: number; - relic_set_id: number; - main_affix_id: number; - sub_affixes: SubAffix[]; - internal_uid: number; - equip_avatar: number; -} - -export interface LightconeJson { - level: number; - item_id: number; - equip_avatar: number; - rank: number; - promotion: number; - internal_uid: number; -} +export interface AvatarAnalysisJson { + owner_uid: number; + avatar_id: number; + data: AvatarData | null; + level: number; + promotion: number; + techniques: number[]; + relics: RelicJson[]; + Lightcone: LightconeJson | null; + sp_value: number; + sp_max: number; +} + +export interface AvatarData { + rank: number; + skills: Record; +} + +export interface SubAffix { + sub_affix_id: number; + count: number; + step: number; +} + +export interface RelicJson { + level: number; + relic_id: number; + relic_set_id: number; + main_affix_id: number; + sub_affixes: SubAffix[]; + internal_uid: number; + equip_avatar: number; +} + +export interface LightconeJson { + level: number; + item_id: number; + equip_avatar: number; + rank: number; + promotion: number; + internal_uid: number; +} diff --git a/src/types/team.ts b/src/types/team.ts index 16adc38..8b8932f 100644 --- a/src/types/team.ts +++ b/src/types/team.ts @@ -1,4 +1,4 @@ -export enum Team { - Player = "Player", - Enemy = "Enemy" +export enum Team { + Player = "Player", + Enemy = "Enemy" } \ No newline at end of file diff --git a/src/types/turn.ts b/src/types/turn.ts index a69badd..4aa2f54 100644 --- a/src/types/turn.ts +++ b/src/types/turn.ts @@ -1,18 +1,18 @@ -import { EntityType } from "./entity"; - -export interface TurnInfoType { - avatars_turn_damage: number[]; - total_damage: number; - action_value: number, - cycle: number, - wave: number, -} - -export interface TurnBeginType { - action_value: number; - turn_owner?: EntityType | null -} - -export interface TurnEndType { - turn_info: TurnInfoType +import { EntityType } from "./entity"; + +export interface TurnInfoType { + avatars_turn_damage: number[]; + total_damage: number; + action_value: number, + cycle: number, + wave: number, +} + +export interface TurnBeginType { + action_value: number; + turn_owner?: EntityType | null +} + +export interface TurnEndType { + turn_info: TurnInfoType } \ No newline at end of file diff --git a/src/types/version.ts b/src/types/version.ts index dd25046..18f9def 100644 --- a/src/types/version.ts +++ b/src/types/version.ts @@ -1,3 +1,3 @@ -export interface VersionType { - version: string +export interface VersionType { + version: string } \ No newline at end of file diff --git a/src/types/waveAndCycle.ts b/src/types/waveAndCycle.ts index b0f5c3d..6c7d92a 100644 --- a/src/types/waveAndCycle.ts +++ b/src/types/waveAndCycle.ts @@ -1,7 +1,7 @@ -export interface UpdateWaveType { - wave: number -} - -export interface UpdateCycleType { - cycle: number +export interface UpdateWaveType { + wave: number +} + +export interface UpdateCycleType { + cycle: number } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 2169551..a86c232 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -38,4 +38,4 @@ "exclude": [ "node_modules" ] -} +}