Refactor TypeScript interfaces and types for consistency and readability
Gitea Auto Deploy / Deploy-Container (push) Successful in 49s

- Reformatted multiple TypeScript interfaces across various files to ensure consistent spacing and indentation.
- Updated `changelog.ts`, `enka.ts`, `extraData.ts`, `gloval.d.ts`, `index.ts`, `lightconeDetail.ts`, `metaData.ts`, `mics.ts`, `modelConfig.ts`, `monsterDetail.ts`, `peakDetail.ts`, `pfDetail.ts`, `psConnect.ts`, `relicDetail.ts`, `showcase.ts`, `srtools.ts`, and `zod/index.ts` for improved code clarity.
- Ensured all interfaces are properly formatted with consistent line breaks and spacing.
- Made minor adjustments to the `tsconfig.json` file for formatting consistency.
This commit is contained in:
2026-07-15 11:04:18 +07:00
parent 2b9f4c3a69
commit 7699ac7139
129 changed files with 16426 additions and 16418 deletions
+14 -14
View File
@@ -1,15 +1,15 @@
name: Gitea Auto Deploy name: Gitea Auto Deploy
run-name: ${{ gitea.actor }} pushed code 🚀 run-name: ${{ gitea.actor }} pushed code 🚀
on: [push] on: [push]
jobs: jobs:
Deploy-Container: Deploy-Container:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Deploy to Container - name: Deploy to Container
run: | run: |
docker compose up -d --build --remove-orphans docker compose up -d --build --remove-orphans
+55 -55
View File
@@ -1,55 +1,55 @@
FROM oven/bun:canary-alpine AS base FROM oven/bun:canary-alpine AS base
FROM base AS deps FROM base AS deps
WORKDIR /app WORKDIR /app
COPY package.json bun.lock ./ COPY package.json bun.lock ./
RUN bun install --frozen-lockfile RUN bun install --frozen-lockfile
# Rebuild the source code only when needed # Rebuild the source code only when needed
FROM base AS builder FROM base AS builder
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
# Next.js collects completely anonymous telemetry data about general usage. # Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry # Learn more here: https://nextjs.org/telemetry
# Disable telemetry during the build # Disable telemetry during the build
ENV NEXT_TELEMETRY_DISABLED=1 ENV NEXT_TELEMETRY_DISABLED=1
RUN bun run build RUN bun run build
# Production image, copy all the files and run next # Production image, copy all the files and run next
FROM base AS runner FROM base AS runner
WORKDIR /app WORKDIR /app
ENV NODE_ENV=production ENV NODE_ENV=production
# Disable telemetry # Disable telemetry
ENV NEXT_TELEMETRY_DISABLED=1 ENV NEXT_TELEMETRY_DISABLED=1
RUN adduser --system --uid 1001 nextjs RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
COPY --from=builder /app/messages ./messages COPY --from=builder /app/messages ./messages
# Set the correct permission for prerender cache # Set the correct permission for prerender cache
RUN mkdir .next RUN mkdir .next
RUN chown nextjs:bun .next RUN chown nextjs:bun .next
# Automatically leverage output traces to reduce image size # Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing # 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/standalone ./
COPY --from=builder --chown=nextjs:bun /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:bun /app/.next/static ./.next/static
USER nextjs USER nextjs
EXPOSE 3000 EXPOSE 3000
ENV PORT=3000 ENV PORT=3000
# Set hostname to localhost # Set hostname to localhost
ENV HOSTNAME=0.0.0.0 ENV HOSTNAME=0.0.0.0
CMD ["bun", "server.js"] CMD ["bun", "server.js"]
+62 -62
View File
@@ -1,62 +1,62 @@
# Firefly SrTools # Firefly SrTools
![Next.js](https://img.shields.io/badge/Next.js-black?style=for-the-badge&logo=next.js&logoColor=white) ![Next.js](https://img.shields.io/badge/Next.js-black?style=for-the-badge&logo=next.js&logoColor=white)
![Bun](https://img.shields.io/badge/Bun-%23000000.svg?style=for-the-badge&logo=bun&logoColor=white) ![Bun](https://img.shields.io/badge/Bun-%23000000.svg?style=for-the-badge&logo=bun&logoColor=white)
![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white) ![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)
![License](https://img.shields.io/badge/License-MIT-blue?style=for-the-badge) ![License](https://img.shields.io/badge/License-MIT-blue?style=for-the-badge)
**Firefly SrTools** is a modern, high-performance web toolkit designed to streamline workflows and provide essential services. This project is built upon the powerful synergy of the **Next.js** framework and the ultra-fast **Bun.js** runtime, ensuring lightning-fast development and deployment. **Firefly SrTools** is a modern, high-performance web toolkit designed to streamline workflows and provide essential services. This project is built upon the powerful synergy of the **Next.js** framework and the ultra-fast **Bun.js** runtime, ensuring lightning-fast development and deployment.
--- ---
## Live Sites ## Live Sites
The application is deployed and available at the following URLs: The application is deployed and available at the following URLs:
| Role | URL | | Role | URL |
| :--- | :--- | | :--- | :--- |
| **Main Site** | [srtools.punklorde.org](https://srtools.punklorde.org) | | **Main Site** | [srtools.punklorde.org](https://srtools.punklorde.org) |
--- ---
## Key Features ## Key Features
* **Optimized Performance:** Leveraging Bun for dependency management, scripting, and development server speed. * **Optimized Performance:** Leveraging Bun for dependency management, scripting, and development server speed.
* **Next.js App Router:** Modern routing and data fetching using the latest Next.js architecture. * **Next.js App Router:** Modern routing and data fetching using the latest Next.js architecture.
* **Server-Side Rendering (SSR):** Enhanced SEO and faster initial load times for improved user experience. * **Server-Side Rendering (SSR):** Enhanced SEO and faster initial load times for improved user experience.
* **Robust Type Safety:** Built with TypeScript for reliable and maintainable code. * **Robust Type Safety:** Built with TypeScript for reliable and maintainable code.
* **Modular Design:** Clean separation of concerns with reusable components and utilities. * **Modular Design:** Clean separation of concerns with reusable components and utilities.
## Technology Stack ## Technology Stack
* **Frontend Framework:** [Next.js 15](https://nextjs.org/) (React) * **Frontend Framework:** [Next.js 15](https://nextjs.org/) (React)
* **Runtime & Package Manager:** [Bun](https://bun.sh/) * **Runtime & Package Manager:** [Bun](https://bun.sh/)
* **Language:** TypeScript * **Language:** TypeScript
* **Styling:** (Tailwind CSS, DaisyUI) * **Styling:** (Tailwind CSS, DaisyUI)
* **State Management:** (Zustand) * **State Management:** (Zustand)
--- ---
## Prerequisites ## Prerequisites
Ensure you have **Bun** installed on your system. Ensure you have **Bun** installed on your system.
Install Bun (macOS, WSL, Linux): Install Bun (macOS, WSL, Linux):
```bash ```bash
curl -fsSL [https://bun.sh/install](https://bun.sh/install) | bash curl -fsSL [https://bun.sh/install](https://bun.sh/install) | bash
``` ```
## Development ## Development
```bash ```bash
bun install bun install
``` ```
```bash ```bash
bun run dev bun run dev
``` ```
```bash ```bash
bun run build bun run build
bun run start bun run start
``` ```
BIN
View File
Binary file not shown.
Binary file not shown.
+10 -2
View File
@@ -1,10 +1,18 @@
[ [
{ {
"version": "4.2.5", "version": "4.4.0",
"date": "15/07/2026",
"type": "update",
"items": [
"New data for 4.4.0"
]
},
{
"version": "4.3.0",
"date": "01/05/2026", "date": "01/05/2026",
"type": "update", "type": "update",
"items": [ "items": [
"New data for 4.2.5" "New data for 4.3.0"
] ]
}, },
{ {
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+15 -15
View File
@@ -1,15 +1,15 @@
services: services:
srtools-live: srtools-live:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: srtools-live container_name: srtools-live
restart: unless-stopped restart: unless-stopped
ports: ports:
- "3009:3000" - "3009:3000"
networks: networks:
- srtools-live-network - srtools-live-network
networks: networks:
srtools-live-network: srtools-live-network:
driver: bridge driver: bridge
+289 -289
View File
@@ -1,290 +1,290 @@
{ {
"TabTitle": { "TabTitle": {
"title": "Firefly Tools", "title": "Firefly Tools",
"description": "Firefly-Tools von Firefly Shelter" "description": "Firefly-Tools von Firefly Shelter"
}, },
"DataPage": { "DataPage": {
"skillType": "Fähigkeitstyp", "skillType": "Fähigkeitstyp",
"skillName": "Fähigkeitsname", "skillName": "Fähigkeitsname",
"character": "Charakter", "character": "Charakter",
"id": "ID", "id": "ID",
"path": "Pfad", "path": "Pfad",
"rarity": "Seltenheit", "rarity": "Seltenheit",
"element": "Element", "element": "Element",
"technique": "Technik", "technique": "Technik",
"talent": "Talent", "talent": "Talent",
"basic": "Standardangriff", "basic": "Standardangriff",
"skill": "Fähigkeit", "skill": "Fähigkeit",
"ultimate": "Ultimate", "ultimate": "Ultimate",
"servant": "Diener", "servant": "Diener",
"damage": "Schaden", "damage": "Schaden",
"type": "Typ", "type": "Typ",
"warrior": "Die Zerstörung", "warrior": "Die Zerstörung",
"knight": "Die Bewahrung", "knight": "Die Bewahrung",
"mage": "Die Gelehrsamkeit", "mage": "Die Gelehrsamkeit",
"priest": "Der Überfluss", "priest": "Der Überfluss",
"rogue": "Die Jagd", "rogue": "Die Jagd",
"shaman": "Die Harmonie", "shaman": "Die Harmonie",
"warlock": "Die Nichtigkeit", "warlock": "Die Nichtigkeit",
"memory": "Die Erinnerung", "memory": "Die Erinnerung",
"elation": "Die Freude", "elation": "Die Freude",
"fire": "Feuer", "fire": "Feuer",
"ice": "Eis", "ice": "Eis",
"imaginary": "Imaginär", "imaginary": "Imaginär",
"physical": "Physisch", "physical": "Physisch",
"quantum": "Quanten", "quantum": "Quanten",
"thunder": "Blitz", "thunder": "Blitz",
"wind": "Wind", "wind": "Wind",
"hp": "LP", "hp": "LP",
"atk": "ANG", "atk": "ANG",
"speed": "GES", "speed": "GES",
"critRate": "Krit. Rate", "critRate": "Krit. Rate",
"critDmg": "Krit. SCH", "critDmg": "Krit. SCH",
"breakEffect": "Brucheffekt", "breakEffect": "Brucheffekt",
"effectRes": "Effekt-WDS", "effectRes": "Effekt-WDS",
"energyRegenerationRate": "Energie-Regenerationsrate", "energyRegenerationRate": "Energie-Regenerationsrate",
"effectHitRate": "Effekttrefferquote", "effectHitRate": "Effekttrefferquote",
"outgoingHealingBoost": "Ausgehender Heilungsbonus", "outgoingHealingBoost": "Ausgehender Heilungsbonus",
"fireDmgBoost": "Feuer-SCH-Bonus", "fireDmgBoost": "Feuer-SCH-Bonus",
"iceDmgBoost": "Eis-SCH-Bonus", "iceDmgBoost": "Eis-SCH-Bonus",
"imaginaryDmgBoost": "Imaginär-SCH-Bonus", "imaginaryDmgBoost": "Imaginär-SCH-Bonus",
"physicalDmgBoost": "Physisch-SCH-Bonus", "physicalDmgBoost": "Physisch-SCH-Bonus",
"quantumDmgBoost": "Quanten-SCH-Bonus", "quantumDmgBoost": "Quanten-SCH-Bonus",
"thunderDmgBoost": "Blitz-SCH-Bonus", "thunderDmgBoost": "Blitz-SCH-Bonus",
"windDmgBoost": "Wind-SCH-Bonus", "windDmgBoost": "Wind-SCH-Bonus",
"pursued": "Zusätzlicher Schaden", "pursued": "Zusätzlicher Schaden",
"true damage": "Wahrer Schaden", "true damage": "Wahrer Schaden",
"elationdamage": "Freude-Schaden", "elationdamage": "Freude-Schaden",
"follow-up": "Folgeangriff-Schaden", "follow-up": "Folgeangriff-Schaden",
"elemental damage": "Bruch- und Superbruch-Schaden", "elemental damage": "Bruch- und Superbruch-Schaden",
"dot": "Schaden über Zeit", "dot": "Schaden über Zeit",
"qte": "QTE-Fähigkeit", "qte": "QTE-Fähigkeit",
"level": "Stufe", "level": "Stufe",
"relics": "Relikte", "relics": "Relikte",
"eidolons": "Eidolons", "eidolons": "Eidolons",
"lightcones": "Lichtkegel", "lightcones": "Lichtkegel",
"loadData": "Daten laden", "loadData": "Daten laden",
"exportData": "Daten exportieren", "exportData": "Daten exportieren",
"connectSetting": "Verbindungseinstellungen", "connectSetting": "Verbindungseinstellungen",
"connected": "Verbunden", "connected": "Verbunden",
"unconnected": "Nicht verbunden", "unconnected": "Nicht verbunden",
"psConnection": "PS-Verbindung", "psConnection": "PS-Verbindung",
"connectionType": "Verbindungstyp", "connectionType": "Verbindungstyp",
"status": "Status", "status": "Status",
"connectPs": "Mit PS verbinden", "connectPs": "Mit PS verbinden",
"disconnect": "Trennen", "disconnect": "Trennen",
"other": "Andere", "other": "Andere",
"freeSr": "FreeSR", "freeSr": "FreeSR",
"database": "Datenbank", "database": "Datenbank",
"enka": "Enka", "enka": "Enka",
"monsterSetting": "Monstereinstellungen", "monsterSetting": "Monstereinstellungen",
"serverUrl": "Server-URL", "serverUrl": "Server-URL",
"privateType": "Privater Typ", "privateType": "Privater Typ",
"local": "Lokal", "local": "Lokal",
"server": "Server", "server": "Server",
"username": "Benutzername", "username": "Benutzername",
"password": "Passwort", "password": "Passwort",
"placeholderServerUrl": "Server-URL eingeben", "placeholderServerUrl": "Server-URL eingeben",
"placeholderUsername": "Benutzernamen eingeben", "placeholderUsername": "Benutzernamen eingeben",
"placeholderPassword": "Passwort eingeben", "placeholderPassword": "Passwort eingeben",
"connectedSuccess": "Erfolgreich mit PS verbunden", "connectedSuccess": "Erfolgreich mit PS verbunden",
"connectedFailed": "Verbindung zu PS fehlgeschlagen", "connectedFailed": "Verbindung zu PS fehlgeschlagen",
"syncSuccess": "Daten erfolgreich mit PS synchronisiert", "syncSuccess": "Daten erfolgreich mit PS synchronisiert",
"syncFailed": "Fehler beim Synchronisieren", "syncFailed": "Fehler beim Synchronisieren",
"sync": "Synchronisieren", "sync": "Synchronisieren",
"importSetting": "Import-Einstellungen", "importSetting": "Import-Einstellungen",
"profile": "Profil", "profile": "Profil",
"default": "Standard", "default": "Standard",
"copyProfiles": "Profile kopieren", "copyProfiles": "Profile kopieren",
"addNewProfile": "Neues Profil hinzufügen", "addNewProfile": "Neues Profil hinzufügen",
"createNewProfile": "Neues Profil erstellen", "createNewProfile": "Neues Profil erstellen",
"editProfile": "Profil bearbeiten", "editProfile": "Profil bearbeiten",
"placeholderProfileName": "Profilnamen eingeben", "placeholderProfileName": "Profilnamen eingeben",
"profileName": "Profilname", "profileName": "Profilname",
"create": "Erstellen", "create": "Erstellen",
"update": "Aktualisieren", "update": "Aktualisieren",
"characterInformation": "Charakterinformationen", "characterInformation": "Charakterinformationen",
"skills": "Fähigkeiten", "skills": "Fähigkeiten",
"showcaseCard": "Showcase-Karte", "showcaseCard": "Showcase-Karte",
"comingSoon": "Demnächst", "comingSoon": "Demnächst",
"characterName": "Charaktername", "characterName": "Charaktername",
"placeholderCharacter": "Charakternamen eingeben", "placeholderCharacter": "Charakternamen eingeben",
"characterSettings": "Charaktereinstellungen", "characterSettings": "Charaktereinstellungen",
"levelConfiguration": "Stufenkonfiguration", "levelConfiguration": "Stufenkonfiguration",
"characterLevel": "Charakterstufe", "characterLevel": "Charakterstufe",
"max": "MAX", "max": "MAX",
"ultimateEnergy": "Ultimate-Energie", "ultimateEnergy": "Ultimate-Energie",
"currentEnergy": "Aktuelle Energie", "currentEnergy": "Aktuelle Energie",
"setTo50": "Auf 50% setzen", "setTo50": "Auf 50% setzen",
"battleConfiguration": "Kampfkonfiguration", "battleConfiguration": "Kampfkonfiguration",
"useTechnique": "Technik verwenden", "useTechnique": "Technik verwenden",
"techniqueNote": "Technikeffekte vor dem Kampf aktivieren", "techniqueNote": "Technikeffekte vor dem Kampf aktivieren",
"enhancement": "Verbesserung", "enhancement": "Verbesserung",
"enhancementLevel": "Verbesserungsstufe", "enhancementLevel": "Verbesserungsstufe",
"origin": "Ursprung", "origin": "Ursprung",
"enhancedNote": "Höhere Verbesserungen schalten Fähigkeiten frei", "enhancedNote": "Höhere Verbesserungen schalten Fähigkeiten frei",
"lightconeEquipment": "Lichtkegel-Ausrüstung", "lightconeEquipment": "Lichtkegel-Ausrüstung",
"lightconeSettings": "Lichtkegel-Einstellungen", "lightconeSettings": "Lichtkegel-Einstellungen",
"placeholderLevel": "Stufe eingeben", "placeholderLevel": "Stufe eingeben",
"superimpositionRank": "Überlagerungsrang", "superimpositionRank": "Überlagerungsrang",
"ranksNote": "Höhere Ränge bieten stärkere Effekte", "ranksNote": "Höhere Ränge bieten stärkere Effekte",
"changeLightcone": "Lichtkegel wechseln", "changeLightcone": "Lichtkegel wechseln",
"removeLightcone": "Lichtkegel entfernen", "removeLightcone": "Lichtkegel entfernen",
"equipLightcone": "Lichtkegel ausrüsten", "equipLightcone": "Lichtkegel ausrüsten",
"noLightconeEquipped": "Kein Lichtkegel ausgerüstet", "noLightconeEquipped": "Kein Lichtkegel ausgerüstet",
"equipLightconeNote": "Rüste einen Lichtkegel aus, um deinen Charakter zu stärken", "equipLightconeNote": "Rüste einen Lichtkegel aus, um deinen Charakter zu stärken",
"filter": "Filter", "filter": "Filter",
"selectedCharacters": "Ausgewählte Charaktere", "selectedCharacters": "Ausgewählte Charaktere",
"selectedProfiles": "Ausgewählte Profile", "selectedProfiles": "Ausgewählte Profile",
"clearAll": "Alles löschen", "clearAll": "Alles löschen",
"selectAll": "Alles auswählen", "selectAll": "Alles auswählen",
"copy": "Kopieren", "copy": "Kopieren",
"copied": "Kopiert", "copied": "Kopiert",
"noAvatarSelected": "Kein Charakter ausgewählt", "noAvatarSelected": "Kein Charakter ausgewählt",
"noAvatarToCopySelected": "Kein Charakter zum Kopieren ausgewählt", "noAvatarToCopySelected": "Kein Charakter zum Kopieren ausgewählt",
"pleaseSelectAtLeastOneProfile": "Bitte wähle mindestens ein Profil aus", "pleaseSelectAtLeastOneProfile": "Bitte wähle mindestens ein Profil aus",
"pleaseEnterUid": "Bitte UID eingeben", "pleaseEnterUid": "Bitte UID eingeben",
"failedToFetchEnkaData": "Fehler beim Abrufen der Enka-Daten", "failedToFetchEnkaData": "Fehler beim Abrufen der Enka-Daten",
"pleaseSelectAtLeastOneCharacter": "Bitte wähle mindestens einen Charakter aus", "pleaseSelectAtLeastOneCharacter": "Bitte wähle mindestens einen Charakter aus",
"noDataToImport": "Keine Daten zum Importieren", "noDataToImport": "Keine Daten zum Importieren",
"pleaseSelectAFile": "Bitte wähle eine Datei", "pleaseSelectAFile": "Bitte wähle eine Datei",
"fileMustBeAValidJsonFile": "Die Datei muss eine gültige JSON-Datei sein", "fileMustBeAValidJsonFile": "Die Datei muss eine gültige JSON-Datei sein",
"importEnkaDataSuccess": "Enka-Daten erfolgreich importiert", "importEnkaDataSuccess": "Enka-Daten erfolgreich importiert",
"importFreeSRDataSuccess": "FreeSR-Daten erfolgreich importiert", "importFreeSRDataSuccess": "FreeSR-Daten erfolgreich importiert",
"importDatabaseSuccess": "Datenbank erfolgreich importiert", "importDatabaseSuccess": "Datenbank erfolgreich importiert",
"getData": "Daten abrufen", "getData": "Daten abrufen",
"import": "Importieren", "import": "Importieren",
"freeSRImport": "FreeSR-Import", "freeSRImport": "FreeSR-Import",
"onlySupportFreeSRJsonFile": "Unterstützt nur FreeSR-JSON-Dateien", "onlySupportFreeSRJsonFile": "Unterstützt nur FreeSR-JSON-Dateien",
"pickAFile": "Datei auswählen", "pickAFile": "Datei auswählen",
"lightConeSetting": "Lichtkegel-Einstellung", "lightConeSetting": "Lichtkegel-Einstellung",
"relicMaker": "Relikt-Ersteller", "relicMaker": "Relikt-Ersteller",
"pleaseSelectAllOptions": "Bitte wähle alle Optionen", "pleaseSelectAllOptions": "Bitte wähle alle Optionen",
"relicSavedSuccessfully": "Relikt erfolgreich gespeichert", "relicSavedSuccessfully": "Relikt erfolgreich gespeichert",
"mainSettings": "Haupteinstellungen", "mainSettings": "Haupteinstellungen",
"mainStat": "Hauptwert", "mainStat": "Hauptwert",
"set": "Set", "set": "Set",
"pleaseSelectASet": "Bitte wähle ein Set", "pleaseSelectASet": "Bitte wähle ein Set",
"effectBonus": "Effektbonus", "effectBonus": "Effektbonus",
"totalRoll": "Gesamte Aufwertungen", "totalRoll": "Gesamte Aufwertungen",
"randomizeStats": "Werte zufällig", "randomizeStats": "Werte zufällig",
"randomizeRolls": "Aufwertungen zufällig", "randomizeRolls": "Aufwertungen zufällig",
"selectASubStat": "Wähle einen Nebenwert", "selectASubStat": "Wähle einen Nebenwert",
"selectASet": "Wähle ein Set", "selectASet": "Wähle ein Set",
"selectAMainStat": "Wähle einen Hauptwert", "selectAMainStat": "Wähle einen Hauptwert",
"save": "Speichern", "save": "Speichern",
"reset": "Zurücksetzen", "reset": "Zurücksetzen",
"roll": "Aufwertung", "roll": "Aufwertung",
"step": "Schritt", "step": "Schritt",
"memoryOfChaos": "Vergessene Halle", "memoryOfChaos": "Vergessene Halle",
"pureFiction": "Reine Fiktion", "pureFiction": "Reine Fiktion",
"apocalypticShadow": "Apokalyptischer Schatten", "apocalypticShadow": "Apokalyptischer Schatten",
"customEnemy": "Benutzerdefinierter Feind", "customEnemy": "Benutzerdefinierter Feind",
"simulatedUniverse": "Universum-Simulation", "simulatedUniverse": "Universum-Simulation",
"floor": "Ebene", "floor": "Ebene",
"side": "Seite", "side": "Seite",
"wave": "Welle", "wave": "Welle",
"stage": "Phase", "stage": "Phase",
"useCycleCount": "Zyklen-Zählung verwenden?", "useCycleCount": "Zyklen-Zählung verwenden?",
"useTurbulenceBuff": "Turbulenz-Buff verwenden?", "useTurbulenceBuff": "Turbulenz-Buff verwenden?",
"firstHalfEnemies": "Gegner erste Hälfte", "firstHalfEnemies": "Gegner erste Hälfte",
"secondHalfEnemies": "Gegner zweite Hälfte", "secondHalfEnemies": "Gegner zweite Hälfte",
"firstNodeEnemies": "Gegner Knoten 1", "firstNodeEnemies": "Gegner Knoten 1",
"secondNodeEnemies": "Gegner Knoten 2", "secondNodeEnemies": "Gegner Knoten 2",
"thirdNodeEnemies": "Gegner Knoten 3", "thirdNodeEnemies": "Gegner Knoten 3",
"firstNode": "Knoten 1", "firstNode": "Knoten 1",
"secondNode": "Knoten 2", "secondNode": "Knoten 2",
"thirdNode": "Knoten 3", "thirdNode": "Knoten 3",
"listEnemies": "Gegnerliste", "listEnemies": "Gegnerliste",
"turbulenceBuff": "Turbulenz-Buff", "turbulenceBuff": "Turbulenz-Buff",
"noEventSelected": "Kein Ereignis ausgewählt", "noEventSelected": "Kein Ereignis ausgewählt",
"noTurbulenceBuff": "Kein Turbulenz-Buff", "noTurbulenceBuff": "Kein Turbulenz-Buff",
"upper": "Oben", "upper": "Oben",
"lower": "Unten", "lower": "Unten",
"upperToLower": "Oben -> Unten", "upperToLower": "Oben -> Unten",
"lowerToUpper": "Unten -> Oben", "lowerToUpper": "Unten -> Oben",
"selectMOCEvent": "MOC-Ereignis wählen", "selectMOCEvent": "MOC-Ereignis wählen",
"selectPFEvent": "PF-Ereignis wählen", "selectPFEvent": "PF-Ereignis wählen",
"selectASEvent": "AS-Ereignis wählen", "selectASEvent": "AS-Ereignis wählen",
"selectCEEvent": "CE-Ereignis wählen", "selectCEEvent": "CE-Ereignis wählen",
"selectEvent": "Ereignis wählen", "selectEvent": "Ereignis wählen",
"selectFloor": "Ebene wählen", "selectFloor": "Ebene wählen",
"selectSide": "Seite wählen", "selectSide": "Seite wählen",
"selectBuff": "Buff wählen", "selectBuff": "Buff wählen",
"selectStage": "Phase wählen", "selectStage": "Phase wählen",
"previous": "Zurück", "previous": "Zurück",
"next": "Weiter", "next": "Weiter",
"noMonstersFound": "Keine Monster gefunden", "noMonstersFound": "Keine Monster gefunden",
"addNewWave": "Neue Welle hinzufügen", "addNewWave": "Neue Welle hinzufügen",
"searchStage": "Phase suchen...", "searchStage": "Phase suchen...",
"noStageFound": "Keine Phase gefunden", "noStageFound": "Keine Phase gefunden",
"searchMonster": "Monster suchen...", "searchMonster": "Monster suchen...",
"changeRelic": "Relikt wechseln", "changeRelic": "Relikt wechseln",
"deleteRelic": "Relikt löschen", "deleteRelic": "Relikt löschen",
"deleteRelicConfirm": "Möchtest du das Relikt auf diesem Platz wirklich löschen?", "deleteRelicConfirm": "Möchtest du das Relikt auf diesem Platz wirklich löschen?",
"setEffects": "Effekte einstellen", "setEffects": "Effekte einstellen",
"details": "Details", "details": "Details",
"normal": "Standardangriff", "normal": "Standardangriff",
"bpskill": "Fähigkeit", "bpskill": "Fähigkeit",
"maze": "Technik", "maze": "Technik",
"ultra": "Ultimate", "ultra": "Ultimate",
"servantskill": "Memosprite-Fähigkeit", "servantskill": "Memosprite-Fähigkeit",
"severaltalent": "Memosprite-Talent", "severaltalent": "Memosprite-Talent",
"singleattack": "Einzelangriff", "singleattack": "Einzelangriff",
"enhance": "Verbessern", "enhance": "Verbessern",
"summon": "Beschwören", "summon": "Beschwören",
"mazeattack": "Technik-Angriff", "mazeattack": "Technik-Angriff",
"blast": "Explosion", "blast": "Explosion",
"restore": "Wiederherstellen", "restore": "Wiederherstellen",
"support": "Unterstützung", "support": "Unterstützung",
"aoeattack": "Flächenangriff", "aoeattack": "Flächenangriff",
"impair": "Beeinträchtigen", "impair": "Beeinträchtigen",
"bounce": "Abprallen", "bounce": "Abprallen",
"active": "Aktiv", "active": "Aktiv",
"defence": "Verteidigung", "defence": "Verteidigung",
"inactive": "Inaktiv", "inactive": "Inaktiv",
"maxAll": "Alles maximieren", "maxAll": "Alles maximieren",
"maxAllSuccess": "Fähigkeiten erfolgreich maximiert.", "maxAllSuccess": "Fähigkeiten erfolgreich maximiert.",
"maxAllFailed": "Fehler beim Maximieren.", "maxAllFailed": "Fehler beim Maximieren.",
"noRelicEquipped": "Kein Relikt ausgerüstet", "noRelicEquipped": "Kein Relikt ausgerüstet",
"anomalyArbitration": "Anomalie-Schiedsgericht", "anomalyArbitration": "Anomalie-Schiedsgericht",
"normalMode": "Normaler Modus", "normalMode": "Normaler Modus",
"hardMode": "Schwerer Modus", "hardMode": "Schwerer Modus",
"selectPEAKEvent": "PEAK-Ereignis wählen", "selectPEAKEvent": "PEAK-Ereignis wählen",
"mode": "Modus", "mode": "Modus",
"selectMode": "Modus wählen", "selectMode": "Modus wählen",
"rollBack": "Rückgängig", "rollBack": "Rückgängig",
"upRoll": "Aufwertung hoch", "upRoll": "Aufwertung hoch",
"downRoll": "Aufwertung runter", "downRoll": "Aufwertung runter",
"actions": "Aktionen", "actions": "Aktionen",
"avatars": "Avatare", "avatars": "Avatare",
"quickView": "Schnellansicht", "quickView": "Schnellansicht",
"extraSetting": "Extra-Einstellungen", "extraSetting": "Extra-Einstellungen",
"disableCensorship": "Zensur deaktivieren", "disableCensorship": "Zensur deaktivieren",
"hideUI": "UI verstecken", "hideUI": "UI verstecken",
"theoryCraftMode": "Theorycraft-Modus", "theoryCraftMode": "Theorycraft-Modus",
"cycleCount": "Zyklen-Anzahl", "cycleCount": "Zyklen-Anzahl",
"pleaseSelectAllSubStats": "Bitte alle Nebenwerte auswählen", "pleaseSelectAllSubStats": "Bitte alle Nebenwerte auswählen",
"subStatRollCountCannotBeZero": "Nebenwert-Aufwertungen dürfen nicht null sein", "subStatRollCountCannotBeZero": "Nebenwert-Aufwertungen dürfen nicht null sein",
"theoryCraft": "Theorycraft", "theoryCraft": "Theorycraft",
"multipathCharacter": "Mehrpfad-Charakter", "multipathCharacter": "Mehrpfad-Charakter",
"mainPath": "Hauptpfad", "mainPath": "Hauptpfad",
"march7Path": "Pfad 7. März", "march7Path": "Pfad 7. März",
"challenge": "Herausforderung", "challenge": "Herausforderung",
"skipNode": "Knoten überspringen", "skipNode": "Knoten überspringen",
"disableSkip": "Überspringen deaktivieren", "disableSkip": "Überspringen deaktivieren",
"skipNode1": "Knoten 1 überspringen", "skipNode1": "Knoten 1 überspringen",
"skipNode2": "Knoten 2 überspringen", "skipNode2": "Knoten 2 überspringen",
"extraFeatures": "Zusatzfunktionen", "extraFeatures": "Zusatzfunktionen",
"detailTheoryCraft": "Ermöglicht die Anpassung der Zykluszahl und der Gegner-LP.", "detailTheoryCraft": "Ermöglicht die Anpassung der Zykluszahl und der Gegner-LP.",
"detailSkipNode": "Ermöglicht das Überspringen (Knoten 1/2) im Memory of Chaos oder Pure Fiction.", "detailSkipNode": "Ermöglicht das Überspringen (Knoten 1/2) im Memory of Chaos oder Pure Fiction.",
"detailChallengePeak": "Ändert die Peak-Saison in der aktuellen Anomalie.", "detailChallengePeak": "Ändert die Peak-Saison in der aktuellen Anomalie.",
"detailHiddenUi": "Versteckt die Spiel-Benutzeroberfläche.", "detailHiddenUi": "Versteckt die Spiel-Benutzeroberfläche.",
"detailDisableCensorship": "Deaktiviert die Zensur im Spiel.", "detailDisableCensorship": "Deaktiviert die Zensur im Spiel.",
"detailMultipathCharacter": "Ermöglicht das Ändern des Pfades bestimmter Charaktere.", "detailMultipathCharacter": "Ermöglicht das Ändern des Pfades bestimmter Charaktere.",
"trailblazer": "Trailblazer", "trailblazer": "Trailblazer",
"listExtraEffect": "Liste Zusatzeffekte", "listExtraEffect": "Liste Zusatzeffekte",
"extra": "Extra", "extra": "Extra",
"customLineup": "Benutzerdefinierte Aufstellung" "customLineup": "Benutzerdefinierte Aufstellung"
} }
} }
+289 -289
View File
@@ -1,290 +1,290 @@
{ {
"TabTitle": { "TabTitle": {
"title": "Firefly Tools", "title": "Firefly Tools",
"description": "Firefly tools by Firefly Shelter" "description": "Firefly tools by Firefly Shelter"
}, },
"DataPage": { "DataPage": {
"skillType": "Skill Type", "skillType": "Skill Type",
"skillName": "Skill Name", "skillName": "Skill Name",
"character": "Character", "character": "Character",
"id": "Id", "id": "Id",
"path": "Path", "path": "Path",
"rarity": "Rarity", "rarity": "Rarity",
"element": "Element", "element": "Element",
"technique": "Technique", "technique": "Technique",
"talent": "Talent", "talent": "Talent",
"basic": "Basic Attack", "basic": "Basic Attack",
"skill": "Skill", "skill": "Skill",
"ultimate": "Ultimate", "ultimate": "Ultimate",
"servant": "Servant", "servant": "Servant",
"damage": "Damage", "damage": "Damage",
"type": "Type", "type": "Type",
"warrior": "The Destruction", "warrior": "The Destruction",
"knight": "The Preservation", "knight": "The Preservation",
"mage": "The Erudition", "mage": "The Erudition",
"priest": "The Abundance", "priest": "The Abundance",
"rogue": "The Hunt", "rogue": "The Hunt",
"shaman": "The Harmony", "shaman": "The Harmony",
"warlock": "The Nihility", "warlock": "The Nihility",
"memory": "The Remembrance", "memory": "The Remembrance",
"elation": "The Elation", "elation": "The Elation",
"fire": "Fire", "fire": "Fire",
"ice": "Ice", "ice": "Ice",
"imaginary": "Imaginary", "imaginary": "Imaginary",
"physical": "Physical", "physical": "Physical",
"quantum": "Quantum", "quantum": "Quantum",
"thunder": "Thunder", "thunder": "Thunder",
"wind": "Wind", "wind": "Wind",
"hp": "Hp", "hp": "Hp",
"atk": "Atk", "atk": "Atk",
"speed": "Speed", "speed": "Speed",
"critRate": "Crit Rate", "critRate": "Crit Rate",
"critDmg": "Crit Dmg", "critDmg": "Crit Dmg",
"breakEffect": "Break Effect", "breakEffect": "Break Effect",
"effectRes": "Effect Res", "effectRes": "Effect Res",
"energyRegenerationRate": "Energy Regeneration Rate", "energyRegenerationRate": "Energy Regeneration Rate",
"effectHitRate": "Effect Hit Rate", "effectHitRate": "Effect Hit Rate",
"outgoingHealingBoost": "Outgoing Healing Boost", "outgoingHealingBoost": "Outgoing Healing Boost",
"fireDmgBoost": "Fire damage boost", "fireDmgBoost": "Fire damage boost",
"iceDmgBoost": "Ice damage Boost", "iceDmgBoost": "Ice damage Boost",
"imaginaryDmgBoost": "Imaginary damage boost", "imaginaryDmgBoost": "Imaginary damage boost",
"physicalDmgBoost": "Physical damage boost", "physicalDmgBoost": "Physical damage boost",
"quantumDmgBoost": "Quantum damage boost", "quantumDmgBoost": "Quantum damage boost",
"thunderDmgBoost": "Thunder damage boost", "thunderDmgBoost": "Thunder damage boost",
"windDmgBoost": "Wind damage boost", "windDmgBoost": "Wind damage boost",
"pursued": "Additional damage", "pursued": "Additional damage",
"true damage": "True damage", "true damage": "True damage",
"elationdamage": "Elation damage", "elationdamage": "Elation damage",
"follow-up": "Follow-up Damage", "follow-up": "Follow-up Damage",
"elemental damage": "Break and Super break damage", "elemental damage": "Break and Super break damage",
"dot": "Damage over time ", "dot": "Damage over time ",
"qte": "QTE Skill", "qte": "QTE Skill",
"level": "Level", "level": "Level",
"relics": "Relics", "relics": "Relics",
"eidolons": "Eidolons", "eidolons": "Eidolons",
"lightcones": "Lightcones", "lightcones": "Lightcones",
"loadData": "Load data", "loadData": "Load data",
"exportData": "Export data", "exportData": "Export data",
"connectSetting": "Connection Setting", "connectSetting": "Connection Setting",
"connected": "Connected", "connected": "Connected",
"unconnected": "Unconnected", "unconnected": "Unconnected",
"psConnection": "PS Connection", "psConnection": "PS Connection",
"connectionType": "Connection Type", "connectionType": "Connection Type",
"status": "Status", "status": "Status",
"connectPs": "Connect PS", "connectPs": "Connect PS",
"disconnect": "Disconnect", "disconnect": "Disconnect",
"other": "Other", "other": "Other",
"freeSr": "FreeSR", "freeSr": "FreeSR",
"database": "Database", "database": "Database",
"enka": "Enka", "enka": "Enka",
"monsterSetting": "Monster Setting", "monsterSetting": "Monster Setting",
"serverUrl": "Server URL", "serverUrl": "Server URL",
"privateType": "Private Type", "privateType": "Private Type",
"local": "Local", "local": "Local",
"server": "Server", "server": "Server",
"username": "Username", "username": "Username",
"password": "Password", "password": "Password",
"placeholderServerUrl": "Enter server URL", "placeholderServerUrl": "Enter server URL",
"placeholderUsername": "Enter username", "placeholderUsername": "Enter username",
"placeholderPassword": "Enter password", "placeholderPassword": "Enter password",
"connectedSuccess": "Connected to PS successfully", "connectedSuccess": "Connected to PS successfully",
"connectedFailed": "Failed to connect to PS", "connectedFailed": "Failed to connect to PS",
"syncSuccess": "Synced data to PS successfully", "syncSuccess": "Synced data to PS successfully",
"syncFailed": "Failed to sync data to PS", "syncFailed": "Failed to sync data to PS",
"sync": "Sync", "sync": "Sync",
"importSetting": "Import Setting", "importSetting": "Import Setting",
"profile": "Profile", "profile": "Profile",
"default": "Default", "default": "Default",
"copyProfiles": "Copy profiles", "copyProfiles": "Copy profiles",
"addNewProfile": "Add new profile", "addNewProfile": "Add new profile",
"createNewProfile": "Create new profile", "createNewProfile": "Create new profile",
"editProfile": "Edit profile", "editProfile": "Edit profile",
"placeholderProfileName": "Enter profile name", "placeholderProfileName": "Enter profile name",
"profileName": "Profile name", "profileName": "Profile name",
"create": "Create", "create": "Create",
"update": "Update", "update": "Update",
"characterInformation": "Character Information", "characterInformation": "Character Information",
"skills": "Skills", "skills": "Skills",
"showcaseCard": "Showcase Card", "showcaseCard": "Showcase Card",
"comingSoon": "Coming soon", "comingSoon": "Coming soon",
"characterName": "Character name", "characterName": "Character name",
"placeholderCharacter": "Enter character name", "placeholderCharacter": "Enter character name",
"characterSettings": "Character Settings", "characterSettings": "Character Settings",
"levelConfiguration": "Level Configuration", "levelConfiguration": "Level Configuration",
"characterLevel": "Character Level", "characterLevel": "Character Level",
"max": "MAX", "max": "MAX",
"ultimateEnergy": "Ultimate Energy", "ultimateEnergy": "Ultimate Energy",
"currentEnergy": "Current Energy", "currentEnergy": "Current Energy",
"setTo50": "Set to 50%", "setTo50": "Set to 50%",
"battleConfiguration": "Battle Configuration", "battleConfiguration": "Battle Configuration",
"useTechnique": "Use Technique", "useTechnique": "Use Technique",
"techniqueNote": "Enable pre-battle technique effects", "techniqueNote": "Enable pre-battle technique effects",
"enhancement": "Enhancement", "enhancement": "Enhancement",
"enhancementLevel": "Enhancement Level", "enhancementLevel": "Enhancement Level",
"origin": "Origin", "origin": "Origin",
"enhancedNote": "Higher enhanced unlock additional abilities", "enhancedNote": "Higher enhanced unlock additional abilities",
"lightconeEquipment": "Lightcone Equipment", "lightconeEquipment": "Lightcone Equipment",
"lightconeSettings": "Lightcone Settings", "lightconeSettings": "Lightcone Settings",
"placeholderLevel": "Enter level", "placeholderLevel": "Enter level",
"superimpositionRank": "Superimposition Rank", "superimpositionRank": "Superimposition Rank",
"ranksNote": "Higher ranks provide stronger effects", "ranksNote": "Higher ranks provide stronger effects",
"changeLightcone": "Change Lightcone", "changeLightcone": "Change Lightcone",
"removeLightcone": "Remove Lightcone", "removeLightcone": "Remove Lightcone",
"equipLightcone": "Equip Lightcone", "equipLightcone": "Equip Lightcone",
"noLightconeEquipped": "No Lightcone Equipped", "noLightconeEquipped": "No Lightcone Equipped",
"equipLightconeNote": "Equip a lightcone to enhance your character's abilities", "equipLightconeNote": "Equip a lightcone to enhance your character's abilities",
"filter": "Filter", "filter": "Filter",
"selectedCharacters": "Selected Characters", "selectedCharacters": "Selected Characters",
"selectedProfiles": "Selected Profiles", "selectedProfiles": "Selected Profiles",
"clearAll": "Clear All", "clearAll": "Clear All",
"selectAll": "Select All", "selectAll": "Select All",
"copy": "Copy", "copy": "Copy",
"copied": "Copied", "copied": "Copied",
"noAvatarSelected": "No avatar selected", "noAvatarSelected": "No avatar selected",
"noAvatarToCopySelected": "No avatar to copy selected", "noAvatarToCopySelected": "No avatar to copy selected",
"pleaseSelectAtLeastOneProfile": "Please select at least one profile", "pleaseSelectAtLeastOneProfile": "Please select at least one profile",
"pleaseEnterUid": "Please enter UID", "pleaseEnterUid": "Please enter UID",
"failedToFetchEnkaData": "Failed to fetch enka data", "failedToFetchEnkaData": "Failed to fetch enka data",
"pleaseSelectAtLeastOneCharacter": "Please select at least one character", "pleaseSelectAtLeastOneCharacter": "Please select at least one character",
"noDataToImport": "No data to import", "noDataToImport": "No data to import",
"pleaseSelectAFile": "Please select a file", "pleaseSelectAFile": "Please select a file",
"fileMustBeAValidJsonFile": "File must be a valid json file", "fileMustBeAValidJsonFile": "File must be a valid json file",
"importEnkaDataSuccess": "Import Enka data success", "importEnkaDataSuccess": "Import Enka data success",
"importFreeSRDataSuccess": "Import FreeSR data success", "importFreeSRDataSuccess": "Import FreeSR data success",
"importDatabaseSuccess": "Import database success", "importDatabaseSuccess": "Import database success",
"getData": "Get Data", "getData": "Get Data",
"import": "Import", "import": "Import",
"freeSRImport": "FreeSR Import", "freeSRImport": "FreeSR Import",
"onlySupportFreeSRJsonFile": "Only support FreeSR json file", "onlySupportFreeSRJsonFile": "Only support FreeSR json file",
"pickAFile": "Pick a file", "pickAFile": "Pick a file",
"lightConeSetting": "LightCone Setting", "lightConeSetting": "LightCone Setting",
"relicMaker": "Relic Maker", "relicMaker": "Relic Maker",
"pleaseSelectAllOptions": "Please select all options", "pleaseSelectAllOptions": "Please select all options",
"relicSavedSuccessfully": "Relic saved successfully", "relicSavedSuccessfully": "Relic saved successfully",
"mainSettings": "Main Settings", "mainSettings": "Main Settings",
"mainStat": "Main Stat", "mainStat": "Main Stat",
"set": "Set", "set": "Set",
"pleaseSelectASet": "Please select a set", "pleaseSelectASet": "Please select a set",
"effectBonus": "Effect Bonus", "effectBonus": "Effect Bonus",
"totalRoll": "Total Roll", "totalRoll": "Total Roll",
"randomizeStats": "Randomize Stats", "randomizeStats": "Randomize Stats",
"randomizeRolls": "Randomize Rolls", "randomizeRolls": "Randomize Rolls",
"selectASubStat": "Select a sub stat", "selectASubStat": "Select a sub stat",
"selectASet": "Select a set", "selectASet": "Select a set",
"selectAMainStat": "Select a main stat", "selectAMainStat": "Select a main stat",
"save": "Save", "save": "Save",
"reset": "Reset", "reset": "Reset",
"roll": "Roll", "roll": "Roll",
"step": "Step", "step": "Step",
"memoryOfChaos": "Memory of Chaos", "memoryOfChaos": "Memory of Chaos",
"pureFiction": "Pure Fiction", "pureFiction": "Pure Fiction",
"apocalypticShadow": "Apocalyptic Shadow", "apocalypticShadow": "Apocalyptic Shadow",
"customEnemy": "Custom Enemy", "customEnemy": "Custom Enemy",
"simulatedUniverse": "Simulated Universe", "simulatedUniverse": "Simulated Universe",
"floor": "Floor", "floor": "Floor",
"side": "Side", "side": "Side",
"wave": "Wave", "wave": "Wave",
"stage": "Stage", "stage": "Stage",
"useCycleCount": "Use cycle count?", "useCycleCount": "Use cycle count?",
"useTurbulenceBuff": "Use turbulence buff?", "useTurbulenceBuff": "Use turbulence buff?",
"firstHalfEnemies": "First half enemies", "firstHalfEnemies": "First half enemies",
"secondHalfEnemies": "Second half enemies", "secondHalfEnemies": "Second half enemies",
"firstNodeEnemies": "First node enemies", "firstNodeEnemies": "First node enemies",
"secondNodeEnemies": "Second node enemies", "secondNodeEnemies": "Second node enemies",
"thirdNodeEnemies": "Third node enemies", "thirdNodeEnemies": "Third node enemies",
"firstNode": "First Node", "firstNode": "First Node",
"secondNode": "Second Node", "secondNode": "Second Node",
"thirdNode": "Third Node", "thirdNode": "Third Node",
"listEnemies": "List enemies", "listEnemies": "List enemies",
"turbulenceBuff": "Turbulence Buff", "turbulenceBuff": "Turbulence Buff",
"noEventSelected": "No event selected", "noEventSelected": "No event selected",
"noTurbulenceBuff": "No Turbulence Buff", "noTurbulenceBuff": "No Turbulence Buff",
"upper": "Upper", "upper": "Upper",
"lower": "Lower", "lower": "Lower",
"upperToLower": "Upper -> Lower", "upperToLower": "Upper -> Lower",
"lowerToUpper": "Lower -> Upper", "lowerToUpper": "Lower -> Upper",
"selectMOCEvent": "Select MOC Event", "selectMOCEvent": "Select MOC Event",
"selectPFEvent": "Select PF Event", "selectPFEvent": "Select PF Event",
"selectASEvent": "Select AS Event", "selectASEvent": "Select AS Event",
"selectCEEvent": "Select CE Event", "selectCEEvent": "Select CE Event",
"selectEvent": "Select Event", "selectEvent": "Select Event",
"selectFloor": "Select a Floor", "selectFloor": "Select a Floor",
"selectSide": "Select a Side", "selectSide": "Select a Side",
"selectBuff": "Select a Buff", "selectBuff": "Select a Buff",
"selectStage": "Select a Stage", "selectStage": "Select a Stage",
"previous": "Previous", "previous": "Previous",
"next": "Next", "next": "Next",
"noMonstersFound": "No monsters found", "noMonstersFound": "No monsters found",
"addNewWave": "Add New Wave", "addNewWave": "Add New Wave",
"searchStage": "Search stage...", "searchStage": "Search stage...",
"noStageFound": "No stage found", "noStageFound": "No stage found",
"searchMonster": "Search monster...", "searchMonster": "Search monster...",
"changeRelic": "Change relic", "changeRelic": "Change relic",
"deleteRelic": "Delete relic", "deleteRelic": "Delete relic",
"deleteRelicConfirm": "Are you sure you want to delete relic in slot", "deleteRelicConfirm": "Are you sure you want to delete relic in slot",
"setEffects": "Set Effects", "setEffects": "Set Effects",
"details": "Details", "details": "Details",
"normal": "Basic ATK", "normal": "Basic ATK",
"bpskill": "Skill", "bpskill": "Skill",
"maze": "Technique", "maze": "Technique",
"ultra": "Ultimate", "ultra": "Ultimate",
"servantskill": "Memosprite Skill", "servantskill": "Memosprite Skill",
"severaltalent": "Memosprite Talent", "severaltalent": "Memosprite Talent",
"singleattack": "Single Attack", "singleattack": "Single Attack",
"enhance": "Enhance", "enhance": "Enhance",
"summon": "Summon", "summon": "Summon",
"mazeattack": "Technique Attack", "mazeattack": "Technique Attack",
"blast": "Blast", "blast": "Blast",
"restore": "Restore", "restore": "Restore",
"support": "Support", "support": "Support",
"aoeattack": "AoE Attack", "aoeattack": "AoE Attack",
"impair": "Impair", "impair": "Impair",
"bounce": "Bounce", "bounce": "Bounce",
"active": "Active", "active": "Active",
"defence": "Defence", "defence": "Defence",
"inactive": "Inactive", "inactive": "Inactive",
"maxAll": "Max All", "maxAll": "Max All",
"maxAllSuccess": "Successfully set skill level to max.", "maxAllSuccess": "Successfully set skill level to max.",
"maxAllFailed": "Failed to set skill level to max.", "maxAllFailed": "Failed to set skill level to max.",
"noRelicEquipped": "No relic equipped", "noRelicEquipped": "No relic equipped",
"anomalyArbitration": "Anomaly Arbitration", "anomalyArbitration": "Anomaly Arbitration",
"normalMode": "Normal Mode", "normalMode": "Normal Mode",
"hardMode": "Hard Mode", "hardMode": "Hard Mode",
"selectPEAKEvent": "Select PEAK Event", "selectPEAKEvent": "Select PEAK Event",
"mode": "Mode", "mode": "Mode",
"selectMode": "Select a mode", "selectMode": "Select a mode",
"rollBack": "Roll Back", "rollBack": "Roll Back",
"upRoll": "Up Roll", "upRoll": "Up Roll",
"downRoll": "Down Roll", "downRoll": "Down Roll",
"actions": "Actions", "actions": "Actions",
"avatars": "Avatars", "avatars": "Avatars",
"quickView": "Quick View", "quickView": "Quick View",
"extraSetting": "Extra Settings", "extraSetting": "Extra Settings",
"disableCensorship": "Disable Censorship", "disableCensorship": "Disable Censorship",
"hideUI": "Hide UI", "hideUI": "Hide UI",
"theoryCraftMode": "Theorycraft Mode", "theoryCraftMode": "Theorycraft Mode",
"cycleCount": "Cycle Count", "cycleCount": "Cycle Count",
"pleaseSelectAllSubStats": "Please select all sub stats", "pleaseSelectAllSubStats": "Please select all sub stats",
"subStatRollCountCannotBeZero": "Sub stat roll count cannot be zero", "subStatRollCountCannotBeZero": "Sub stat roll count cannot be zero",
"theoryCraft": "Theorycraft", "theoryCraft": "Theorycraft",
"multipathCharacter": "Multipath Character", "multipathCharacter": "Multipath Character",
"mainPath": "Main Path", "mainPath": "Main Path",
"march7Path": "March 7 Path", "march7Path": "March 7 Path",
"challenge": "Challenge", "challenge": "Challenge",
"skipNode": "Skip Node", "skipNode": "Skip Node",
"disableSkip": "Disable skip", "disableSkip": "Disable skip",
"skipNode1": "Skip node 1", "skipNode1": "Skip node 1",
"skipNode2": "Skip node 2", "skipNode2": "Skip node 2",
"extraFeatures": "Extra Features", "extraFeatures": "Extra Features",
"detailTheoryCraft": "Enabling this feature allows you to customize the cycle count and adjust enemy HP in the enemy settings.", "detailTheoryCraft": "Enabling this feature allows you to customize the cycle count and adjust enemy HP in the enemy settings.",
"detailSkipNode": "Enabling this feature allows you to skip (Node 1/Node 2) in Memory of Chaos or Pure Fiction.", "detailSkipNode": "Enabling this feature allows you to skip (Node 1/Node 2) in Memory of Chaos or Pure Fiction.",
"detailChallengePeak": "Allows changing the Peak season in the current Anomaly.", "detailChallengePeak": "Allows changing the Peak season in the current Anomaly.",
"detailHiddenUi": "Enabling this feature will hide the game UI.", "detailHiddenUi": "Enabling this feature will hide the game UI.",
"detailDisableCensorship": "Enabling this feature will disable in-game censorship.", "detailDisableCensorship": "Enabling this feature will disable in-game censorship.",
"detailMultipathCharacter": "Allows changing the Path of certain characters.", "detailMultipathCharacter": "Allows changing the Path of certain characters.",
"trailblazer": "Trailblazer", "trailblazer": "Trailblazer",
"listExtraEffect": "List Extra Effect", "listExtraEffect": "List Extra Effect",
"extra": "Extra", "extra": "Extra",
"customLineup": "Custom Lineup" "customLineup": "Custom Lineup"
} }
} }
+289 -289
View File
@@ -1,290 +1,290 @@
{ {
"TabTitle": { "TabTitle": {
"title": "Firefly Tools", "title": "Firefly Tools",
"description": "Herramientas Firefly por Firefly Shelter" "description": "Herramientas Firefly por Firefly Shelter"
}, },
"DataPage": { "DataPage": {
"skillType": "Tipo de habilidad", "skillType": "Tipo de habilidad",
"skillName": "Nombre de la habilidad", "skillName": "Nombre de la habilidad",
"character": "Personaje", "character": "Personaje",
"id": "Id", "id": "Id",
"path": "Vía", "path": "Vía",
"rarity": "Rareza", "rarity": "Rareza",
"element": "Elemento", "element": "Elemento",
"technique": "Técnica", "technique": "Técnica",
"talent": "Talento", "talent": "Talento",
"basic": "Ataque Básico", "basic": "Ataque Básico",
"skill": "Habilidad", "skill": "Habilidad",
"ultimate": "Habilidad Definitiva", "ultimate": "Habilidad Definitiva",
"servant": "Mnemoduende", "servant": "Mnemoduende",
"damage": "Daño", "damage": "Daño",
"type": "Tipo", "type": "Tipo",
"warrior": "La Destrucción", "warrior": "La Destrucción",
"knight": "La Conservación", "knight": "La Conservación",
"mage": "La Erudición", "mage": "La Erudición",
"priest": "La Abundancia", "priest": "La Abundancia",
"rogue": "La Cacería", "rogue": "La Cacería",
"shaman": "La Armonía", "shaman": "La Armonía",
"warlock": "La Nihilidad", "warlock": "La Nihilidad",
"memory": "La Reminiscencia", "memory": "La Reminiscencia",
"elation": "La Exultación", "elation": "La Exultación",
"fire": "Fuego", "fire": "Fuego",
"ice": "Hielo", "ice": "Hielo",
"imaginary": "Imaginario", "imaginary": "Imaginario",
"physical": "Físico", "physical": "Físico",
"quantum": "Cuántico", "quantum": "Cuántico",
"thunder": "Rayo", "thunder": "Rayo",
"wind": "Viento", "wind": "Viento",
"hp": "PV", "hp": "PV",
"atk": "ATQ", "atk": "ATQ",
"speed": "VEL", "speed": "VEL",
"critRate": "Prob. CRIT", "critRate": "Prob. CRIT",
"critDmg": "Daño CRIT", "critDmg": "Daño CRIT",
"breakEffect": "Efecto de Ruptura", "breakEffect": "Efecto de Ruptura",
"effectRes": "RES a Efecto", "effectRes": "RES a Efecto",
"energyRegenerationRate": "Recuperación de Energía", "energyRegenerationRate": "Recuperación de Energía",
"effectHitRate": "Acierto de Efecto", "effectHitRate": "Acierto de Efecto",
"outgoingHealingBoost": "Bono de Curación", "outgoingHealingBoost": "Bono de Curación",
"fireDmgBoost": "Aumento de Daño de Fuego", "fireDmgBoost": "Aumento de Daño de Fuego",
"iceDmgBoost": "Aumento de Daño de Hielo", "iceDmgBoost": "Aumento de Daño de Hielo",
"imaginaryDmgBoost": "Aumento de Daño Imaginario", "imaginaryDmgBoost": "Aumento de Daño Imaginario",
"physicalDmgBoost": "Aumento de Daño Físico", "physicalDmgBoost": "Aumento de Daño Físico",
"quantumDmgBoost": "Aumento de Daño Cuántico", "quantumDmgBoost": "Aumento de Daño Cuántico",
"thunderDmgBoost": "Aumento de Daño de Rayo", "thunderDmgBoost": "Aumento de Daño de Rayo",
"windDmgBoost": "Aumento de Daño de Viento", "windDmgBoost": "Aumento de Daño de Viento",
"pursued": "Daño adicional", "pursued": "Daño adicional",
"true damage": "Daño verdadero", "true damage": "Daño verdadero",
"elationdamage": "Daño de Exultación", "elationdamage": "Daño de Exultación",
"follow-up": "Daño de Ataque Adicional", "follow-up": "Daño de Ataque Adicional",
"elemental damage": "Daño de Ruptura y Superruptura", "elemental damage": "Daño de Ruptura y Superruptura",
"dot": "Daño con el tiempo", "dot": "Daño con el tiempo",
"qte": "Habilidad QTE", "qte": "Habilidad QTE",
"level": "Nivel", "level": "Nivel",
"relics": "Artefactos", "relics": "Artefactos",
"eidolons": "Eidolones", "eidolons": "Eidolones",
"lightcones": "Conos de Luz", "lightcones": "Conos de Luz",
"loadData": "Cargar datos", "loadData": "Cargar datos",
"exportData": "Exportar datos", "exportData": "Exportar datos",
"connectSetting": "Ajustes de conexión", "connectSetting": "Ajustes de conexión",
"connected": "Conectado", "connected": "Conectado",
"unconnected": "Desconectado", "unconnected": "Desconectado",
"psConnection": "Conexión PS", "psConnection": "Conexión PS",
"connectionType": "Tipo de conexión", "connectionType": "Tipo de conexión",
"status": "Estado", "status": "Estado",
"connectPs": "Conectar PS", "connectPs": "Conectar PS",
"disconnect": "Desconectar", "disconnect": "Desconectar",
"other": "Otro", "other": "Otro",
"freeSr": "FreeSR", "freeSr": "FreeSR",
"database": "Base de datos", "database": "Base de datos",
"enka": "Enka", "enka": "Enka",
"monsterSetting": "Ajuste de Monstruos", "monsterSetting": "Ajuste de Monstruos",
"serverUrl": "URL del Servidor", "serverUrl": "URL del Servidor",
"privateType": "Tipo privado", "privateType": "Tipo privado",
"local": "Local", "local": "Local",
"server": "Servidor", "server": "Servidor",
"username": "Usuario", "username": "Usuario",
"password": "Contraseña", "password": "Contraseña",
"placeholderServerUrl": "Introducir URL del servidor", "placeholderServerUrl": "Introducir URL del servidor",
"placeholderUsername": "Introducir usuario", "placeholderUsername": "Introducir usuario",
"placeholderPassword": "Introducir contraseña", "placeholderPassword": "Introducir contraseña",
"connectedSuccess": "Conectado a PS exitosamente", "connectedSuccess": "Conectado a PS exitosamente",
"connectedFailed": "Fallo al conectar a PS", "connectedFailed": "Fallo al conectar a PS",
"syncSuccess": "Datos sincronizados a PS exitosamente", "syncSuccess": "Datos sincronizados a PS exitosamente",
"syncFailed": "Fallo al sincronizar datos a PS", "syncFailed": "Fallo al sincronizar datos a PS",
"sync": "Sincronizar", "sync": "Sincronizar",
"importSetting": "Ajustes de Importación", "importSetting": "Ajustes de Importación",
"profile": "Perfil", "profile": "Perfil",
"default": "Por defecto", "default": "Por defecto",
"copyProfiles": "Copiar perfiles", "copyProfiles": "Copiar perfiles",
"addNewProfile": "Añadir nuevo perfil", "addNewProfile": "Añadir nuevo perfil",
"createNewProfile": "Crear nuevo perfil", "createNewProfile": "Crear nuevo perfil",
"editProfile": "Editar perfil", "editProfile": "Editar perfil",
"placeholderProfileName": "Introducir nombre de perfil", "placeholderProfileName": "Introducir nombre de perfil",
"profileName": "Nombre del perfil", "profileName": "Nombre del perfil",
"create": "Crear", "create": "Crear",
"update": "Actualizar", "update": "Actualizar",
"characterInformation": "Información del personaje", "characterInformation": "Información del personaje",
"skills": "Habilidades", "skills": "Habilidades",
"showcaseCard": "Tarjeta de presentación del personaje", "showcaseCard": "Tarjeta de presentación del personaje",
"comingSoon": "Próximamente", "comingSoon": "Próximamente",
"characterName": "Nombre del personaje", "characterName": "Nombre del personaje",
"placeholderCharacter": "Introducir nombre del personaje", "placeholderCharacter": "Introducir nombre del personaje",
"characterSettings": "Ajustes del Personaje", "characterSettings": "Ajustes del Personaje",
"levelConfiguration": "Configuración de Nivel", "levelConfiguration": "Configuración de Nivel",
"characterLevel": "Nivel de Personaje", "characterLevel": "Nivel de Personaje",
"max": "MÁX", "max": "MÁX",
"ultimateEnergy": "Energía de Habilidad Definitiva", "ultimateEnergy": "Energía de Habilidad Definitiva",
"currentEnergy": "Energía al inicio del combate", "currentEnergy": "Energía al inicio del combate",
"setTo50": "Ajustar al 50%", "setTo50": "Ajustar al 50%",
"battleConfiguration": "Configuración de Batalla", "battleConfiguration": "Configuración de Batalla",
"useTechnique": "Usar Técnica", "useTechnique": "Usar Técnica",
"techniqueNote": "Activar efectos de técnica pre-batalla", "techniqueNote": "Activar efectos de técnica pre-batalla",
"enhancement": "Mejora", "enhancement": "Mejora",
"enhancementLevel": "Nivel de Mejora", "enhancementLevel": "Nivel de Mejora",
"origin": "Original", "origin": "Original",
"enhancedNote": "Niveles superiores desbloquean más habilidades", "enhancedNote": "Niveles superiores desbloquean más habilidades",
"lightconeEquipment": "Equipamiento de Cono de Luz", "lightconeEquipment": "Equipamiento de Cono de Luz",
"lightconeSettings": "Ajustes de Cono de Luz", "lightconeSettings": "Ajustes de Cono de Luz",
"placeholderLevel": "Introducir nivel", "placeholderLevel": "Introducir nivel",
"superimpositionRank": "Rango de Superposición", "superimpositionRank": "Rango de Superposición",
"ranksNote": "Rangos mayores ofrecen efectos más fuertes", "ranksNote": "Rangos mayores ofrecen efectos más fuertes",
"changeLightcone": "Cambiar Cono de Luz", "changeLightcone": "Cambiar Cono de Luz",
"removeLightcone": "Quitar Cono de Luz", "removeLightcone": "Quitar Cono de Luz",
"equipLightcone": "Equipar Cono de Luz", "equipLightcone": "Equipar Cono de Luz",
"noLightconeEquipped": "Sin Cono de Luz", "noLightconeEquipped": "Sin Cono de Luz",
"equipLightconeNote": "Equipa un cono de luz para mejorar tu personaje", "equipLightconeNote": "Equipa un cono de luz para mejorar tu personaje",
"filter": "Filtro", "filter": "Filtro",
"selectedCharacters": "Personajes Seleccionados", "selectedCharacters": "Personajes Seleccionados",
"selectedProfiles": "Perfiles Seleccionados", "selectedProfiles": "Perfiles Seleccionados",
"clearAll": "Limpiar Todo", "clearAll": "Limpiar Todo",
"selectAll": "Seleccionar Todo", "selectAll": "Seleccionar Todo",
"copy": "Copiar", "copy": "Copiar",
"copied": "Copiado", "copied": "Copiado",
"noAvatarSelected": "Ningún personaje seleccionado", "noAvatarSelected": "Ningún personaje seleccionado",
"noAvatarToCopySelected": "Ningún personaje seleccionado para copiar", "noAvatarToCopySelected": "Ningún personaje seleccionado para copiar",
"pleaseSelectAtLeastOneProfile": "Por favor, selecciona al menos un perfil", "pleaseSelectAtLeastOneProfile": "Por favor, selecciona al menos un perfil",
"pleaseEnterUid": "Por favor, introduce UID", "pleaseEnterUid": "Por favor, introduce UID",
"failedToFetchEnkaData": "Fallo al obtener datos de Enka", "failedToFetchEnkaData": "Fallo al obtener datos de Enka",
"pleaseSelectAtLeastOneCharacter": "Por favor, selecciona al menos un personaje", "pleaseSelectAtLeastOneCharacter": "Por favor, selecciona al menos un personaje",
"noDataToImport": "Sin datos para importar", "noDataToImport": "Sin datos para importar",
"pleaseSelectAFile": "Por favor, selecciona un archivo", "pleaseSelectAFile": "Por favor, selecciona un archivo",
"fileMustBeAValidJsonFile": "El archivo debe ser un JSON válido", "fileMustBeAValidJsonFile": "El archivo debe ser un JSON válido",
"importEnkaDataSuccess": "Datos de Enka importados con éxito", "importEnkaDataSuccess": "Datos de Enka importados con éxito",
"importFreeSRDataSuccess": "Datos de FreeSR importados con éxito", "importFreeSRDataSuccess": "Datos de FreeSR importados con éxito",
"importDatabaseSuccess": "Base de datos importada con éxito", "importDatabaseSuccess": "Base de datos importada con éxito",
"getData": "Obtener Datos", "getData": "Obtener Datos",
"import": "Importar", "import": "Importar",
"freeSRImport": "Importar FreeSR", "freeSRImport": "Importar FreeSR",
"onlySupportFreeSRJsonFile": "Solo soporta archivos JSON de FreeSR", "onlySupportFreeSRJsonFile": "Solo soporta archivos JSON de FreeSR",
"pickAFile": "Elegir un archivo", "pickAFile": "Elegir un archivo",
"lightConeSetting": "Ajuste de Cono de Luz", "lightConeSetting": "Ajuste de Cono de Luz",
"relicMaker": "Creador de Artefactos", "relicMaker": "Creador de Artefactos",
"pleaseSelectAllOptions": "Por favor selecciona todas las opciones", "pleaseSelectAllOptions": "Por favor selecciona todas las opciones",
"relicSavedSuccessfully": "Artefacto guardado exitosamente", "relicSavedSuccessfully": "Artefacto guardado exitosamente",
"mainSettings": "Ajustes Principales", "mainSettings": "Ajustes Principales",
"mainStat": "Estadística Principal", "mainStat": "Estadística Principal",
"set": "Conjunto", "set": "Conjunto",
"pleaseSelectASet": "Por favor, selecciona un conjunto", "pleaseSelectASet": "Por favor, selecciona un conjunto",
"effectBonus": "Bono de Efecto", "effectBonus": "Bono de Efecto",
"totalRoll": "Total de Mejoras", "totalRoll": "Total de Mejoras",
"randomizeStats": "Estadísticas Aleatorias", "randomizeStats": "Estadísticas Aleatorias",
"randomizeRolls": "Mejoras Aleatorias", "randomizeRolls": "Mejoras Aleatorias",
"selectASubStat": "Seleccionar estadística secundaria", "selectASubStat": "Seleccionar estadística secundaria",
"selectASet": "Seleccionar conjunto", "selectASet": "Seleccionar conjunto",
"selectAMainStat": "Seleccionar estadística principal", "selectAMainStat": "Seleccionar estadística principal",
"save": "Guardar", "save": "Guardar",
"reset": "Reiniciar", "reset": "Reiniciar",
"roll": "Mejora", "roll": "Mejora",
"step": "Paso", "step": "Paso",
"memoryOfChaos": "Memoria del Caos", "memoryOfChaos": "Memoria del Caos",
"pureFiction": "Pura Ficción", "pureFiction": "Pura Ficción",
"apocalypticShadow": "Sombra Apocalíptica", "apocalypticShadow": "Sombra Apocalíptica",
"customEnemy": "Enemigo Personalizado", "customEnemy": "Enemigo Personalizado",
"simulatedUniverse": "Universo Simulado", "simulatedUniverse": "Universo Simulado",
"floor": "Piso", "floor": "Piso",
"side": "Lado", "side": "Lado",
"wave": "Oleada", "wave": "Oleada",
"stage": "Etapa", "stage": "Etapa",
"useCycleCount": "¿Usar conteo de ciclos?", "useCycleCount": "¿Usar conteo de ciclos?",
"useTurbulenceBuff": "¿Usar buff de turbulencia?", "useTurbulenceBuff": "¿Usar buff de turbulencia?",
"firstHalfEnemies": "Enemigos primera mitad", "firstHalfEnemies": "Enemigos primera mitad",
"secondHalfEnemies": "Enemigos segunda mitad", "secondHalfEnemies": "Enemigos segunda mitad",
"firstNodeEnemies": "Enemigos del nodo 1", "firstNodeEnemies": "Enemigos del nodo 1",
"secondNodeEnemies": "Enemigos del nodo 2", "secondNodeEnemies": "Enemigos del nodo 2",
"thirdNodeEnemies": "Enemigos del nodo 3", "thirdNodeEnemies": "Enemigos del nodo 3",
"firstNode": "Nodo 1", "firstNode": "Nodo 1",
"secondNode": "Nodo 2", "secondNode": "Nodo 2",
"thirdNode": "Nodo 3", "thirdNode": "Nodo 3",
"listEnemies": "Lista de enemigos", "listEnemies": "Lista de enemigos",
"turbulenceBuff": "Buff de Turbulencia", "turbulenceBuff": "Buff de Turbulencia",
"noEventSelected": "Ningún evento seleccionado", "noEventSelected": "Ningún evento seleccionado",
"noTurbulenceBuff": "Sin Buff de Turbulencia", "noTurbulenceBuff": "Sin Buff de Turbulencia",
"upper": "Superior", "upper": "Superior",
"lower": "Inferior", "lower": "Inferior",
"upperToLower": "Superior -> Inferior", "upperToLower": "Superior -> Inferior",
"lowerToUpper": "Inferior -> Superior", "lowerToUpper": "Inferior -> Superior",
"selectMOCEvent": "Seleccionar evento de MOC", "selectMOCEvent": "Seleccionar evento de MOC",
"selectPFEvent": "Seleccionar evento de PF", "selectPFEvent": "Seleccionar evento de PF",
"selectASEvent": "Seleccionar evento de AS", "selectASEvent": "Seleccionar evento de AS",
"selectCEEvent": "Seleccionar evento de CE", "selectCEEvent": "Seleccionar evento de CE",
"selectEvent": "Seleccionar Evento", "selectEvent": "Seleccionar Evento",
"selectFloor": "Seleccionar Piso", "selectFloor": "Seleccionar Piso",
"selectSide": "Seleccionar Lado", "selectSide": "Seleccionar Lado",
"selectBuff": "Seleccionar Buff", "selectBuff": "Seleccionar Buff",
"selectStage": "Seleccionar Etapa", "selectStage": "Seleccionar Etapa",
"previous": "Anterior", "previous": "Anterior",
"next": "Siguiente", "next": "Siguiente",
"noMonstersFound": "No se encontraron monstruos", "noMonstersFound": "No se encontraron monstruos",
"addNewWave": "Añadir Nueva Oleada", "addNewWave": "Añadir Nueva Oleada",
"searchStage": "Buscar etapa...", "searchStage": "Buscar etapa...",
"noStageFound": "No se encontró etapa", "noStageFound": "No se encontró etapa",
"searchMonster": "Buscar monstruo...", "searchMonster": "Buscar monstruo...",
"changeRelic": "Cambiar artefacto", "changeRelic": "Cambiar artefacto",
"deleteRelic": "Borrar artefacto", "deleteRelic": "Borrar artefacto",
"deleteRelicConfirm": "¿Seguro que quieres borrar el artefacto en este espacio?", "deleteRelicConfirm": "¿Seguro que quieres borrar el artefacto en este espacio?",
"setEffects": "Configurar Efectos", "setEffects": "Configurar Efectos",
"details": "Detalles", "details": "Detalles",
"normal": "Ataque Básico", "normal": "Ataque Básico",
"bpskill": "Habilidad", "bpskill": "Habilidad",
"maze": "Técnica", "maze": "Técnica",
"ultra": "Habilidad Definitiva", "ultra": "Habilidad Definitiva",
"servantskill": "Habilidad de Mnemoduende", "servantskill": "Habilidad de Mnemoduende",
"severaltalent": "Talento de Mnemoduende", "severaltalent": "Talento de Mnemoduende",
"singleattack": "Ataque Individual", "singleattack": "Ataque Individual",
"enhance": "Mejorar", "enhance": "Mejorar",
"summon": "Invocar", "summon": "Invocar",
"mazeattack": "Ataque de Técnica", "mazeattack": "Ataque de Técnica",
"blast": "Ráfaga", "blast": "Ráfaga",
"restore": "Restaurar", "restore": "Restaurar",
"support": "Soporte", "support": "Soporte",
"aoeattack": "Ataque en Área", "aoeattack": "Ataque en Área",
"impair": "Debilitación", "impair": "Debilitación",
"bounce": "Rebote", "bounce": "Rebote",
"active": "Activo", "active": "Activo",
"defence": "Defensa", "defence": "Defensa",
"inactive": "Inactivo", "inactive": "Inactivo",
"maxAll": "Maximizar Todo", "maxAll": "Maximizar Todo",
"maxAllSuccess": "Habilidades maximizadas con éxito.", "maxAllSuccess": "Habilidades maximizadas con éxito.",
"maxAllFailed": "Fallo al maximizar habilidades.", "maxAllFailed": "Fallo al maximizar habilidades.",
"noRelicEquipped": "Sin artefacto equipado", "noRelicEquipped": "Sin artefacto equipado",
"anomalyArbitration": "Arbitraje Atípico", "anomalyArbitration": "Arbitraje Atípico",
"normalMode": "Modo Normal", "normalMode": "Modo Normal",
"hardMode": "Modo Difícil", "hardMode": "Modo Difícil",
"selectPEAKEvent": "Seleccionar evento PEAK", "selectPEAKEvent": "Seleccionar evento PEAK",
"mode": "Modo", "mode": "Modo",
"selectMode": "Seleccionar un modo", "selectMode": "Seleccionar un modo",
"rollBack": "Retroceder", "rollBack": "Retroceder",
"upRoll": "Subir Mejora", "upRoll": "Subir Mejora",
"downRoll": "Bajar Mejora", "downRoll": "Bajar Mejora",
"actions": "Acciones", "actions": "Acciones",
"avatars": "Avatares", "avatars": "Avatares",
"quickView": "Vista Rápida", "quickView": "Vista Rápida",
"extraSetting": "Ajustes Extra", "extraSetting": "Ajustes Extra",
"disableCensorship": "Desactivar Censura", "disableCensorship": "Desactivar Censura",
"hideUI": "Ocultar UI", "hideUI": "Ocultar UI",
"theoryCraftMode": "Modo Theorycraft", "theoryCraftMode": "Modo Theorycraft",
"cycleCount": "Conteo de Ciclos", "cycleCount": "Conteo de Ciclos",
"pleaseSelectAllSubStats": "Por favor selecciona todas las sub-estadísticas", "pleaseSelectAllSubStats": "Por favor selecciona todas las sub-estadísticas",
"subStatRollCountCannotBeZero": "El conteo de mejoras no puede ser cero", "subStatRollCountCannotBeZero": "El conteo de mejoras no puede ser cero",
"theoryCraft": "Theorycraft", "theoryCraft": "Theorycraft",
"multipathCharacter": "Personaje Multivía", "multipathCharacter": "Personaje Multivía",
"mainPath": "Vía Principal", "mainPath": "Vía Principal",
"march7Path": "Vía de Siete de Marzo", "march7Path": "Vía de Siete de Marzo",
"challenge": "Desafío", "challenge": "Desafío",
"skipNode": "Saltar Nodo", "skipNode": "Saltar Nodo",
"disableSkip": "Desactivar salto", "disableSkip": "Desactivar salto",
"skipNode1": "Saltar nodo 1", "skipNode1": "Saltar nodo 1",
"skipNode2": "Saltar nodo 2", "skipNode2": "Saltar nodo 2",
"extraFeatures": "Características Extra", "extraFeatures": "Características Extra",
"detailTheoryCraft": "Permite personalizar los ciclos y la vida del enemigo en los ajustes.", "detailTheoryCraft": "Permite personalizar los ciclos y la vida del enemigo en los ajustes.",
"detailSkipNode": "Permite saltar el Nodo 1 o 2 en Memoria del Caos o Pura Ficción.", "detailSkipNode": "Permite saltar el Nodo 1 o 2 en Memoria del Caos o Pura Ficción.",
"detailChallengePeak": "Permite cambiar la temporada actual de Arbitraje Atípico.", "detailChallengePeak": "Permite cambiar la temporada actual de Arbitraje Atípico.",
"detailHiddenUi": "Oculta la interfaz del juego.", "detailHiddenUi": "Oculta la interfaz del juego.",
"detailDisableCensorship": "Desactiva la censura dentro del juego.", "detailDisableCensorship": "Desactiva la censura dentro del juego.",
"detailMultipathCharacter": "Permite cambiar la Vía de algunos personajes.", "detailMultipathCharacter": "Permite cambiar la Vía de algunos personajes.",
"trailblazer": "Trazacaminos", "trailblazer": "Trazacaminos",
"listExtraEffect": "Lista de Efectos Extra", "listExtraEffect": "Lista de Efectos Extra",
"extra": "Extra", "extra": "Extra",
"customLineup": "Alineación de equipo personalizada" "customLineup": "Alineación de equipo personalizada"
} }
} }
+289 -289
View File
@@ -1,290 +1,290 @@
{ {
"TabTitle": { "TabTitle": {
"title": "Firefly Tools", "title": "Firefly Tools",
"description": "Outils Firefly par Firefly Shelter" "description": "Outils Firefly par Firefly Shelter"
}, },
"DataPage": { "DataPage": {
"skillType": "Type de Compétence", "skillType": "Type de Compétence",
"skillName": "Nom de Compétence", "skillName": "Nom de Compétence",
"character": "Personnage", "character": "Personnage",
"id": "Id", "id": "Id",
"path": "Voie", "path": "Voie",
"rarity": "Rareté", "rarity": "Rareté",
"element": "Élément", "element": "Élément",
"technique": "Technique", "technique": "Technique",
"talent": "Talent", "talent": "Talent",
"basic": "Attaque Normale", "basic": "Attaque Normale",
"skill": "Compétence", "skill": "Compétence",
"ultimate": "Ultime", "ultimate": "Ultime",
"servant": "Serviteur", "servant": "Serviteur",
"damage": "Dégâts", "damage": "Dégâts",
"type": "Type", "type": "Type",
"warrior": "La Destruction", "warrior": "La Destruction",
"knight": "La Préservation", "knight": "La Préservation",
"mage": "L'Érudition", "mage": "L'Érudition",
"priest": "L'Abondance", "priest": "L'Abondance",
"rogue": "La Chasse", "rogue": "La Chasse",
"shaman": "L'Harmonie", "shaman": "L'Harmonie",
"warlock": "La Nihilité", "warlock": "La Nihilité",
"memory": "Le Souvenir", "memory": "Le Souvenir",
"elation": "L'Allégresse", "elation": "L'Allégresse",
"fire": "Feu", "fire": "Feu",
"ice": "Glace", "ice": "Glace",
"imaginary": "Imaginaire", "imaginary": "Imaginaire",
"physical": "Physique", "physical": "Physique",
"quantum": "Quantique", "quantum": "Quantique",
"thunder": "Foudre", "thunder": "Foudre",
"wind": "Vent", "wind": "Vent",
"hp": "PV", "hp": "PV",
"atk": "ATQ", "atk": "ATQ",
"speed": "VIT", "speed": "VIT",
"critRate": "Taux CRIT", "critRate": "Taux CRIT",
"critDmg": "DGT CRIT", "critDmg": "DGT CRIT",
"breakEffect": "Effet de Rupture", "breakEffect": "Effet de Rupture",
"effectRes": "RÉS aux effets", "effectRes": "RÉS aux effets",
"energyRegenerationRate": "Taux de régénération d'énergie", "energyRegenerationRate": "Taux de régénération d'énergie",
"effectHitRate": "Chances d'application d'effets", "effectHitRate": "Chances d'application d'effets",
"outgoingHealingBoost": "Augmentation des soins", "outgoingHealingBoost": "Augmentation des soins",
"fireDmgBoost": "Bonus de dégâts de Feu", "fireDmgBoost": "Bonus de dégâts de Feu",
"iceDmgBoost": "Bonus de dégâts de Glace", "iceDmgBoost": "Bonus de dégâts de Glace",
"imaginaryDmgBoost": "Bonus de dégâts Imaginaire", "imaginaryDmgBoost": "Bonus de dégâts Imaginaire",
"physicalDmgBoost": "Bonus de dégâts Physique", "physicalDmgBoost": "Bonus de dégâts Physique",
"quantumDmgBoost": "Bonus de dégâts Quantique", "quantumDmgBoost": "Bonus de dégâts Quantique",
"thunderDmgBoost": "Bonus de dégâts de Foudre", "thunderDmgBoost": "Bonus de dégâts de Foudre",
"windDmgBoost": "Bonus de dégâts de Vent", "windDmgBoost": "Bonus de dégâts de Vent",
"pursued": "Dégâts additionnels", "pursued": "Dégâts additionnels",
"true damage": "Dégâts bruts", "true damage": "Dégâts bruts",
"elationdamage": "Dégâts d'Allégresse", "elationdamage": "Dégâts d'Allégresse",
"follow-up": "Dégâts d'attaque de suivi", "follow-up": "Dégâts d'attaque de suivi",
"elemental damage": "Dégâts de Rupture et Super Rupture", "elemental damage": "Dégâts de Rupture et Super Rupture",
"dot": "Dégâts sur la durée", "dot": "Dégâts sur la durée",
"qte": "Compétence QTE", "qte": "Compétence QTE",
"level": "Niveau", "level": "Niveau",
"relics": "Reliques", "relics": "Reliques",
"eidolons": "Eidolons", "eidolons": "Eidolons",
"lightcones": "Cônes de Lumière", "lightcones": "Cônes de Lumière",
"loadData": "Charger les données", "loadData": "Charger les données",
"exportData": "Exporter les données", "exportData": "Exporter les données",
"connectSetting": "Paramètres de connexion", "connectSetting": "Paramètres de connexion",
"connected": "Connecté", "connected": "Connecté",
"unconnected": "Déconnecté", "unconnected": "Déconnecté",
"psConnection": "Connexion PS", "psConnection": "Connexion PS",
"connectionType": "Type de connexion", "connectionType": "Type de connexion",
"status": "Statut", "status": "Statut",
"connectPs": "Connecter PS", "connectPs": "Connecter PS",
"disconnect": "Déconnecter", "disconnect": "Déconnecter",
"other": "Autre", "other": "Autre",
"freeSr": "FreeSR", "freeSr": "FreeSR",
"database": "Base de données", "database": "Base de données",
"enka": "Enka", "enka": "Enka",
"monsterSetting": "Paramètre de monstres", "monsterSetting": "Paramètre de monstres",
"serverUrl": "URL du Serveur", "serverUrl": "URL du Serveur",
"privateType": "Type privé", "privateType": "Type privé",
"local": "Local", "local": "Local",
"server": "Serveur", "server": "Serveur",
"username": "Nom d'utilisateur", "username": "Nom d'utilisateur",
"password": "Mot de passe", "password": "Mot de passe",
"placeholderServerUrl": "Entrer l'URL du serveur", "placeholderServerUrl": "Entrer l'URL du serveur",
"placeholderUsername": "Entrer le nom d'utilisateur", "placeholderUsername": "Entrer le nom d'utilisateur",
"placeholderPassword": "Entrer le mot de passe", "placeholderPassword": "Entrer le mot de passe",
"connectedSuccess": "Connecté au PS avec succès", "connectedSuccess": "Connecté au PS avec succès",
"connectedFailed": "Échec de connexion au PS", "connectedFailed": "Échec de connexion au PS",
"syncSuccess": "Données synchronisées au PS avec succès", "syncSuccess": "Données synchronisées au PS avec succès",
"syncFailed": "Échec de synchronisation au PS", "syncFailed": "Échec de synchronisation au PS",
"sync": "Synchroniser", "sync": "Synchroniser",
"importSetting": "Paramètres d'importation", "importSetting": "Paramètres d'importation",
"profile": "Profil", "profile": "Profil",
"default": "Défaut", "default": "Défaut",
"copyProfiles": "Copier les profils", "copyProfiles": "Copier les profils",
"addNewProfile": "Ajouter un nouveau profil", "addNewProfile": "Ajouter un nouveau profil",
"createNewProfile": "Créer un nouveau profil", "createNewProfile": "Créer un nouveau profil",
"editProfile": "Éditer le profil", "editProfile": "Éditer le profil",
"placeholderProfileName": "Entrer le nom du profil", "placeholderProfileName": "Entrer le nom du profil",
"profileName": "Nom du profil", "profileName": "Nom du profil",
"create": "Créer", "create": "Créer",
"update": "Mettre à jour", "update": "Mettre à jour",
"characterInformation": "Informations du personnage", "characterInformation": "Informations du personnage",
"skills": "Compétences", "skills": "Compétences",
"showcaseCard": "Carte de présentation", "showcaseCard": "Carte de présentation",
"comingSoon": "Bientôt disponible", "comingSoon": "Bientôt disponible",
"characterName": "Nom du personnage", "characterName": "Nom du personnage",
"placeholderCharacter": "Entrer le nom du personnage", "placeholderCharacter": "Entrer le nom du personnage",
"characterSettings": "Paramètres du personnage", "characterSettings": "Paramètres du personnage",
"levelConfiguration": "Configuration de niveau", "levelConfiguration": "Configuration de niveau",
"characterLevel": "Niveau du personnage", "characterLevel": "Niveau du personnage",
"max": "MAX", "max": "MAX",
"ultimateEnergy": "Énergie de l'Ultime", "ultimateEnergy": "Énergie de l'Ultime",
"currentEnergy": "Énergie actuelle", "currentEnergy": "Énergie actuelle",
"setTo50": "Régler sur 50%", "setTo50": "Régler sur 50%",
"battleConfiguration": "Configuration de combat", "battleConfiguration": "Configuration de combat",
"useTechnique": "Utiliser Technique", "useTechnique": "Utiliser Technique",
"techniqueNote": "Activer les effets de technique avant le combat", "techniqueNote": "Activer les effets de technique avant le combat",
"enhancement": "Amélioration", "enhancement": "Amélioration",
"enhancementLevel": "Niveau d'amélioration", "enhancementLevel": "Niveau d'amélioration",
"origin": "Origine", "origin": "Origine",
"enhancedNote": "Les améliorations élevées débloquent des compétences", "enhancedNote": "Les améliorations élevées débloquent des compétences",
"lightconeEquipment": "Équipement de Cône de Lumière", "lightconeEquipment": "Équipement de Cône de Lumière",
"lightconeSettings": "Paramètres de Cône de Lumière", "lightconeSettings": "Paramètres de Cône de Lumière",
"placeholderLevel": "Entrer le niveau", "placeholderLevel": "Entrer le niveau",
"superimpositionRank": "Rang de Superposition", "superimpositionRank": "Rang de Superposition",
"ranksNote": "Les rangs élevés offrent des effets plus forts", "ranksNote": "Les rangs élevés offrent des effets plus forts",
"changeLightcone": "Changer de Cône de Lumière", "changeLightcone": "Changer de Cône de Lumière",
"removeLightcone": "Retirer le Cône de Lumière", "removeLightcone": "Retirer le Cône de Lumière",
"equipLightcone": "Équiper un Cône de Lumière", "equipLightcone": "Équiper un Cône de Lumière",
"noLightconeEquipped": "Aucun Cône de Lumière", "noLightconeEquipped": "Aucun Cône de Lumière",
"equipLightconeNote": "Équipez un cône pour renforcer votre personnage", "equipLightconeNote": "Équipez un cône pour renforcer votre personnage",
"filter": "Filtre", "filter": "Filtre",
"selectedCharacters": "Personnages sélectionnés", "selectedCharacters": "Personnages sélectionnés",
"selectedProfiles": "Profils sélectionnés", "selectedProfiles": "Profils sélectionnés",
"clearAll": "Tout effacer", "clearAll": "Tout effacer",
"selectAll": "Tout sélectionner", "selectAll": "Tout sélectionner",
"copy": "Copier", "copy": "Copier",
"copied": "Copié", "copied": "Copié",
"noAvatarSelected": "Aucun personnage sélectionné", "noAvatarSelected": "Aucun personnage sélectionné",
"noAvatarToCopySelected": "Aucun personnage à copier sélectionné", "noAvatarToCopySelected": "Aucun personnage à copier sélectionné",
"pleaseSelectAtLeastOneProfile": "Veuillez sélectionner au moins un profil", "pleaseSelectAtLeastOneProfile": "Veuillez sélectionner au moins un profil",
"pleaseEnterUid": "Veuillez entrer un UID", "pleaseEnterUid": "Veuillez entrer un UID",
"failedToFetchEnkaData": "Échec de récupération des données Enka", "failedToFetchEnkaData": "Échec de récupération des données Enka",
"pleaseSelectAtLeastOneCharacter": "Veuillez sélectionner au moins un personnage", "pleaseSelectAtLeastOneCharacter": "Veuillez sélectionner au moins un personnage",
"noDataToImport": "Aucune donnée à importer", "noDataToImport": "Aucune donnée à importer",
"pleaseSelectAFile": "Veuillez sélectionner un fichier", "pleaseSelectAFile": "Veuillez sélectionner un fichier",
"fileMustBeAValidJsonFile": "Le fichier doit être un JSON valide", "fileMustBeAValidJsonFile": "Le fichier doit être un JSON valide",
"importEnkaDataSuccess": "Importation Enka réussie", "importEnkaDataSuccess": "Importation Enka réussie",
"importFreeSRDataSuccess": "Importation FreeSR réussie", "importFreeSRDataSuccess": "Importation FreeSR réussie",
"importDatabaseSuccess": "Importation de base de données réussie", "importDatabaseSuccess": "Importation de base de données réussie",
"getData": "Obtenir des données", "getData": "Obtenir des données",
"import": "Importer", "import": "Importer",
"freeSRImport": "Importation FreeSR", "freeSRImport": "Importation FreeSR",
"onlySupportFreeSRJsonFile": "Supporte uniquement les fichiers JSON FreeSR", "onlySupportFreeSRJsonFile": "Supporte uniquement les fichiers JSON FreeSR",
"pickAFile": "Choisir un fichier", "pickAFile": "Choisir un fichier",
"lightConeSetting": "Paramètre de Cône de Lumière", "lightConeSetting": "Paramètre de Cône de Lumière",
"relicMaker": "Créateur de Reliques", "relicMaker": "Créateur de Reliques",
"pleaseSelectAllOptions": "Veuillez sélectionner toutes les options", "pleaseSelectAllOptions": "Veuillez sélectionner toutes les options",
"relicSavedSuccessfully": "Relique sauvegardée avec succès", "relicSavedSuccessfully": "Relique sauvegardée avec succès",
"mainSettings": "Paramètres principaux", "mainSettings": "Paramètres principaux",
"mainStat": "Statistique principale", "mainStat": "Statistique principale",
"set": "Set", "set": "Set",
"pleaseSelectASet": "Veuillez sélectionner un set", "pleaseSelectASet": "Veuillez sélectionner un set",
"effectBonus": "Bonus d'effet", "effectBonus": "Bonus d'effet",
"totalRoll": "Total d'améliorations", "totalRoll": "Total d'améliorations",
"randomizeStats": "Statistiques aléatoires", "randomizeStats": "Statistiques aléatoires",
"randomizeRolls": "Améliorations aléatoires", "randomizeRolls": "Améliorations aléatoires",
"selectASubStat": "Sélectionner une stat secondaire", "selectASubStat": "Sélectionner une stat secondaire",
"selectASet": "Sélectionner un set", "selectASet": "Sélectionner un set",
"selectAMainStat": "Sélectionner une stat principale", "selectAMainStat": "Sélectionner une stat principale",
"save": "Sauvegarder", "save": "Sauvegarder",
"reset": "Réinitialiser", "reset": "Réinitialiser",
"roll": "Amélioration", "roll": "Amélioration",
"step": "Étape", "step": "Étape",
"memoryOfChaos": "Mémoire du Chaos", "memoryOfChaos": "Mémoire du Chaos",
"pureFiction": "Pure Fiction", "pureFiction": "Pure Fiction",
"apocalypticShadow": "Ombre Apocalyptique", "apocalypticShadow": "Ombre Apocalyptique",
"customEnemy": "Ennemi personnalisé", "customEnemy": "Ennemi personnalisé",
"simulatedUniverse": "Univers Simulé", "simulatedUniverse": "Univers Simulé",
"floor": "Étage", "floor": "Étage",
"side": "Côté", "side": "Côté",
"wave": "Vague", "wave": "Vague",
"stage": "Niveau", "stage": "Niveau",
"useCycleCount": "Utiliser le compteur de cycles ?", "useCycleCount": "Utiliser le compteur de cycles ?",
"useTurbulenceBuff": "Utiliser le buff de turbulence ?", "useTurbulenceBuff": "Utiliser le buff de turbulence ?",
"firstHalfEnemies": "Ennemis première moitié", "firstHalfEnemies": "Ennemis première moitié",
"secondHalfEnemies": "Ennemis deuxième moitié", "secondHalfEnemies": "Ennemis deuxième moitié",
"firstNodeEnemies": "Ennemis du nœud 1", "firstNodeEnemies": "Ennemis du nœud 1",
"secondNodeEnemies": "Ennemis du nœud 2", "secondNodeEnemies": "Ennemis du nœud 2",
"thirdNodeEnemies": "Ennemis du nœud 3", "thirdNodeEnemies": "Ennemis du nœud 3",
"firstNode": "Nœud 1", "firstNode": "Nœud 1",
"secondNode": "Nœud 2", "secondNode": "Nœud 2",
"thirdNode": "Nœud 3", "thirdNode": "Nœud 3",
"listEnemies": "Liste des ennemis", "listEnemies": "Liste des ennemis",
"turbulenceBuff": "Buff de Turbulence", "turbulenceBuff": "Buff de Turbulence",
"noEventSelected": "Aucun événement sélectionné", "noEventSelected": "Aucun événement sélectionné",
"noTurbulenceBuff": "Aucun Buff de Turbulence", "noTurbulenceBuff": "Aucun Buff de Turbulence",
"upper": "Supérieur", "upper": "Supérieur",
"lower": "Inférieur", "lower": "Inférieur",
"upperToLower": "Supérieur -> Inférieur", "upperToLower": "Supérieur -> Inférieur",
"lowerToUpper": "Inférieur -> Supérieur", "lowerToUpper": "Inférieur -> Supérieur",
"selectMOCEvent": "Sélectionner un événement MOC", "selectMOCEvent": "Sélectionner un événement MOC",
"selectPFEvent": "Sélectionner un événement PF", "selectPFEvent": "Sélectionner un événement PF",
"selectASEvent": "Sélectionner un événement AS", "selectASEvent": "Sélectionner un événement AS",
"selectCEEvent": "Sélectionner un événement CE", "selectCEEvent": "Sélectionner un événement CE",
"selectEvent": "Sélectionner un événement", "selectEvent": "Sélectionner un événement",
"selectFloor": "Sélectionner un étage", "selectFloor": "Sélectionner un étage",
"selectSide": "Sélectionner un côté", "selectSide": "Sélectionner un côté",
"selectBuff": "Sélectionner un buff", "selectBuff": "Sélectionner un buff",
"selectStage": "Sélectionner un niveau", "selectStage": "Sélectionner un niveau",
"previous": "Précédent", "previous": "Précédent",
"next": "Suivant", "next": "Suivant",
"noMonstersFound": "Aucun monstre trouvé", "noMonstersFound": "Aucun monstre trouvé",
"addNewWave": "Ajouter une nouvelle vague", "addNewWave": "Ajouter une nouvelle vague",
"searchStage": "Chercher un niveau...", "searchStage": "Chercher un niveau...",
"noStageFound": "Aucun niveau trouvé", "noStageFound": "Aucun niveau trouvé",
"searchMonster": "Chercher un monstre...", "searchMonster": "Chercher un monstre...",
"changeRelic": "Changer de relique", "changeRelic": "Changer de relique",
"deleteRelic": "Supprimer la relique", "deleteRelic": "Supprimer la relique",
"deleteRelicConfirm": "Êtes-vous sûr de vouloir supprimer la relique de cet emplacement ?", "deleteRelicConfirm": "Êtes-vous sûr de vouloir supprimer la relique de cet emplacement ?",
"setEffects": "Définir les effets", "setEffects": "Définir les effets",
"details": "Détails", "details": "Détails",
"normal": "Attaque Normale", "normal": "Attaque Normale",
"bpskill": "Compétence", "bpskill": "Compétence",
"maze": "Technique", "maze": "Technique",
"ultra": "Ultime", "ultra": "Ultime",
"servantskill": "Compétence de Mémolutin", "servantskill": "Compétence de Mémolutin",
"severaltalent": "Talent de Mémolutin", "severaltalent": "Talent de Mémolutin",
"singleattack": "Attaque à cible unique", "singleattack": "Attaque à cible unique",
"enhance": "Renforcement", "enhance": "Renforcement",
"summon": "Invocation", "summon": "Invocation",
"mazeattack": "Attaque de Technique", "mazeattack": "Attaque de Technique",
"blast": "Diffusion", "blast": "Diffusion",
"restore": "Restauration", "restore": "Restauration",
"support": "Soutien", "support": "Soutien",
"aoeattack": "Attaque de zone", "aoeattack": "Attaque de zone",
"impair": "Malus", "impair": "Malus",
"bounce": "Rebond", "bounce": "Rebond",
"active": "Actif", "active": "Actif",
"defence": "Défense", "defence": "Défense",
"inactive": "Inactif", "inactive": "Inactif",
"maxAll": "Tout maximiser", "maxAll": "Tout maximiser",
"maxAllSuccess": "Compétences maximisées avec succès.", "maxAllSuccess": "Compétences maximisées avec succès.",
"maxAllFailed": "Échec de maximisation.", "maxAllFailed": "Échec de maximisation.",
"noRelicEquipped": "Aucune relique équipée", "noRelicEquipped": "Aucune relique équipée",
"anomalyArbitration": "Arbitrage d'Anomalie", "anomalyArbitration": "Arbitrage d'Anomalie",
"normalMode": "Mode Normal", "normalMode": "Mode Normal",
"hardMode": "Mode Difficile", "hardMode": "Mode Difficile",
"selectPEAKEvent": "Sélectionner un événement PEAK", "selectPEAKEvent": "Sélectionner un événement PEAK",
"mode": "Mode", "mode": "Mode",
"selectMode": "Sélectionner un mode", "selectMode": "Sélectionner un mode",
"rollBack": "Annuler l'étape", "rollBack": "Annuler l'étape",
"upRoll": "Augmenter l'amélioration", "upRoll": "Augmenter l'amélioration",
"downRoll": "Diminuer l'amélioration", "downRoll": "Diminuer l'amélioration",
"actions": "Actions", "actions": "Actions",
"avatars": "Avatars", "avatars": "Avatars",
"quickView": "Aperçu rapide", "quickView": "Aperçu rapide",
"extraSetting": "Paramètres supplémentaires", "extraSetting": "Paramètres supplémentaires",
"disableCensorship": "Désactiver la censure", "disableCensorship": "Désactiver la censure",
"hideUI": "Cacher l'UI", "hideUI": "Cacher l'UI",
"theoryCraftMode": "Mode Theorycraft", "theoryCraftMode": "Mode Theorycraft",
"cycleCount": "Nombre de cycles", "cycleCount": "Nombre de cycles",
"pleaseSelectAllSubStats": "Veuillez sélectionner toutes les stats secondaires", "pleaseSelectAllSubStats": "Veuillez sélectionner toutes les stats secondaires",
"subStatRollCountCannotBeZero": "Les améliorations de stat secondaire ne peuvent être à zéro", "subStatRollCountCannotBeZero": "Les améliorations de stat secondaire ne peuvent être à zéro",
"theoryCraft": "Theorycraft", "theoryCraft": "Theorycraft",
"multipathCharacter": "Personnage Multi-Voies", "multipathCharacter": "Personnage Multi-Voies",
"mainPath": "Voie Principale", "mainPath": "Voie Principale",
"march7Path": "Voie de March 7th", "march7Path": "Voie de March 7th",
"challenge": "Défi", "challenge": "Défi",
"skipNode": "Passer le nœud", "skipNode": "Passer le nœud",
"disableSkip": "Désactiver le passage", "disableSkip": "Désactiver le passage",
"skipNode1": "Passer le nœud 1", "skipNode1": "Passer le nœud 1",
"skipNode2": "Passer le nœud 2", "skipNode2": "Passer le nœud 2",
"extraFeatures": "Fonctionnalités supplémentaires", "extraFeatures": "Fonctionnalités supplémentaires",
"detailTheoryCraft": "Permet de personnaliser le nombre de cycles et d'ajuster les PV ennemis.", "detailTheoryCraft": "Permet de personnaliser le nombre de cycles et d'ajuster les PV ennemis.",
"detailSkipNode": "Permet de passer le nœud (1 ou 2) dans Mémoire du Chaos ou Pure Fiction.", "detailSkipNode": "Permet de passer le nœud (1 ou 2) dans Mémoire du Chaos ou Pure Fiction.",
"detailChallengePeak": "Permet de changer la saison de Peak dans l'anomalie actuelle.", "detailChallengePeak": "Permet de changer la saison de Peak dans l'anomalie actuelle.",
"detailHiddenUi": "Cachera l'interface du jeu.", "detailHiddenUi": "Cachera l'interface du jeu.",
"detailDisableCensorship": "Désactivera la censure du jeu.", "detailDisableCensorship": "Désactivera la censure du jeu.",
"detailMultipathCharacter": "Permet de changer la Voie de certains personnages.", "detailMultipathCharacter": "Permet de changer la Voie de certains personnages.",
"trailblazer": "Pionnier", "trailblazer": "Pionnier",
"listExtraEffect": "Liste des effets supplémentaires", "listExtraEffect": "Liste des effets supplémentaires",
"extra": "Extra", "extra": "Extra",
"customLineup": "Composition personnalisée" "customLineup": "Composition personnalisée"
} }
} }
+289 -289
View File
@@ -1,290 +1,290 @@
{ {
"TabTitle": { "TabTitle": {
"title": "Firefly Tools", "title": "Firefly Tools",
"description": "Alat Firefly oleh Firefly Shelter" "description": "Alat Firefly oleh Firefly Shelter"
}, },
"DataPage": { "DataPage": {
"skillType": "Tipe Skill", "skillType": "Tipe Skill",
"skillName": "Nama Skill", "skillName": "Nama Skill",
"character": "Karakter", "character": "Karakter",
"id": "Id", "id": "Id",
"path": "Path", "path": "Path",
"rarity": "Rarity", "rarity": "Rarity",
"element": "Elemen", "element": "Elemen",
"technique": "Technique", "technique": "Technique",
"talent": "Talent", "talent": "Talent",
"basic": "Basic Attack", "basic": "Basic Attack",
"skill": "Skill", "skill": "Skill",
"ultimate": "Ultimate", "ultimate": "Ultimate",
"servant": "Servant", "servant": "Servant",
"damage": "Damage", "damage": "Damage",
"type": "Tipe", "type": "Tipe",
"warrior": "The Destruction", "warrior": "The Destruction",
"knight": "The Preservation", "knight": "The Preservation",
"mage": "The Erudition", "mage": "The Erudition",
"priest": "The Abundance", "priest": "The Abundance",
"rogue": "The Hunt", "rogue": "The Hunt",
"shaman": "The Harmony", "shaman": "The Harmony",
"warlock": "The Nihility", "warlock": "The Nihility",
"memory": "The Remembrance", "memory": "The Remembrance",
"elation": "The Elation", "elation": "The Elation",
"fire": "Fire", "fire": "Fire",
"ice": "Ice", "ice": "Ice",
"imaginary": "Imaginary", "imaginary": "Imaginary",
"physical": "Physical", "physical": "Physical",
"quantum": "Quantum", "quantum": "Quantum",
"thunder": "Lightning", "thunder": "Lightning",
"wind": "Wind", "wind": "Wind",
"hp": "HP", "hp": "HP",
"atk": "ATK", "atk": "ATK",
"speed": "SPD", "speed": "SPD",
"critRate": "CRIT Rate", "critRate": "CRIT Rate",
"critDmg": "CRIT DMG", "critDmg": "CRIT DMG",
"breakEffect": "Break Effect", "breakEffect": "Break Effect",
"effectRes": "Effect RES", "effectRes": "Effect RES",
"energyRegenerationRate": "Energy Regeneration Rate", "energyRegenerationRate": "Energy Regeneration Rate",
"effectHitRate": "Effect Hit Rate", "effectHitRate": "Effect Hit Rate",
"outgoingHealingBoost": "Outgoing Healing Boost", "outgoingHealingBoost": "Outgoing Healing Boost",
"fireDmgBoost": "Fire DMG Boost", "fireDmgBoost": "Fire DMG Boost",
"iceDmgBoost": "Ice DMG Boost", "iceDmgBoost": "Ice DMG Boost",
"imaginaryDmgBoost": "Imaginary DMG Boost", "imaginaryDmgBoost": "Imaginary DMG Boost",
"physicalDmgBoost": "Physical DMG Boost", "physicalDmgBoost": "Physical DMG Boost",
"quantumDmgBoost": "Quantum DMG Boost", "quantumDmgBoost": "Quantum DMG Boost",
"thunderDmgBoost": "Lightning DMG Boost", "thunderDmgBoost": "Lightning DMG Boost",
"windDmgBoost": "Wind DMG Boost", "windDmgBoost": "Wind DMG Boost",
"pursued": "Additional Damage", "pursued": "Additional Damage",
"true damage": "True Damage", "true damage": "True Damage",
"elationdamage": "Elation Damage", "elationdamage": "Elation Damage",
"follow-up": "Follow-up Attack Damage", "follow-up": "Follow-up Attack Damage",
"elemental damage": "Break dan Super Break Damage", "elemental damage": "Break dan Super Break Damage",
"dot": "Damage Over Time", "dot": "Damage Over Time",
"qte": "QTE Skill", "qte": "QTE Skill",
"level": "Level", "level": "Level",
"relics": "Relic", "relics": "Relic",
"eidolons": "Eidolon", "eidolons": "Eidolon",
"lightcones": "Light Cone", "lightcones": "Light Cone",
"loadData": "Muat data", "loadData": "Muat data",
"exportData": "Ekspor data", "exportData": "Ekspor data",
"connectSetting": "Pengaturan Koneksi", "connectSetting": "Pengaturan Koneksi",
"connected": "Terhubung", "connected": "Terhubung",
"unconnected": "Tidak terhubung", "unconnected": "Tidak terhubung",
"psConnection": "Koneksi PS", "psConnection": "Koneksi PS",
"connectionType": "Tipe Koneksi", "connectionType": "Tipe Koneksi",
"status": "Status", "status": "Status",
"connectPs": "Hubungkan PS", "connectPs": "Hubungkan PS",
"disconnect": "Putuskan sambungan", "disconnect": "Putuskan sambungan",
"other": "Lainnya", "other": "Lainnya",
"freeSr": "FreeSR", "freeSr": "FreeSR",
"database": "Database", "database": "Database",
"enka": "Enka", "enka": "Enka",
"monsterSetting": "Pengaturan Monster", "monsterSetting": "Pengaturan Monster",
"serverUrl": "URL Server", "serverUrl": "URL Server",
"privateType": "Tipe Private", "privateType": "Tipe Private",
"local": "Lokal", "local": "Lokal",
"server": "Server", "server": "Server",
"username": "Username", "username": "Username",
"password": "Password", "password": "Password",
"placeholderServerUrl": "Masukkan URL server", "placeholderServerUrl": "Masukkan URL server",
"placeholderUsername": "Masukkan username", "placeholderUsername": "Masukkan username",
"placeholderPassword": "Masukkan password", "placeholderPassword": "Masukkan password",
"connectedSuccess": "Berhasil terhubung ke PS", "connectedSuccess": "Berhasil terhubung ke PS",
"connectedFailed": "Gagal terhubung ke PS", "connectedFailed": "Gagal terhubung ke PS",
"syncSuccess": "Berhasil sinkronisasi data ke PS", "syncSuccess": "Berhasil sinkronisasi data ke PS",
"syncFailed": "Gagal sinkronisasi data ke PS", "syncFailed": "Gagal sinkronisasi data ke PS",
"sync": "Sinkronisasi", "sync": "Sinkronisasi",
"importSetting": "Pengaturan Impor", "importSetting": "Pengaturan Impor",
"profile": "Profil", "profile": "Profil",
"default": "Default", "default": "Default",
"copyProfiles": "Salin profil", "copyProfiles": "Salin profil",
"addNewProfile": "Tambah profil baru", "addNewProfile": "Tambah profil baru",
"createNewProfile": "Buat profil baru", "createNewProfile": "Buat profil baru",
"editProfile": "Edit profil", "editProfile": "Edit profil",
"placeholderProfileName": "Masukkan nama profil", "placeholderProfileName": "Masukkan nama profil",
"profileName": "Nama profil", "profileName": "Nama profil",
"create": "Buat", "create": "Buat",
"update": "Perbarui", "update": "Perbarui",
"characterInformation": "Informasi Karakter", "characterInformation": "Informasi Karakter",
"skills": "Skill", "skills": "Skill",
"showcaseCard": "Showcase Card", "showcaseCard": "Showcase Card",
"comingSoon": "Segera hadir", "comingSoon": "Segera hadir",
"characterName": "Nama Karakter", "characterName": "Nama Karakter",
"placeholderCharacter": "Masukkan nama karakter", "placeholderCharacter": "Masukkan nama karakter",
"characterSettings": "Pengaturan Karakter", "characterSettings": "Pengaturan Karakter",
"levelConfiguration": "Konfigurasi Level", "levelConfiguration": "Konfigurasi Level",
"characterLevel": "Level Karakter", "characterLevel": "Level Karakter",
"max": "MAX", "max": "MAX",
"ultimateEnergy": "Energy Ultimate", "ultimateEnergy": "Energy Ultimate",
"currentEnergy": "Energy Saat Ini", "currentEnergy": "Energy Saat Ini",
"setTo50": "Atur ke 50%", "setTo50": "Atur ke 50%",
"battleConfiguration": "Konfigurasi Pertempuran", "battleConfiguration": "Konfigurasi Pertempuran",
"useTechnique": "Gunakan Technique", "useTechnique": "Gunakan Technique",
"techniqueNote": "Aktifkan efek technique sebelum pertempuran", "techniqueNote": "Aktifkan efek technique sebelum pertempuran",
"enhancement": "Enhancement", "enhancement": "Enhancement",
"enhancementLevel": "Level Enhancement", "enhancementLevel": "Level Enhancement",
"origin": "Asli", "origin": "Asli",
"enhancedNote": "Enhancement lebih tinggi membuka kemampuan tambahan", "enhancedNote": "Enhancement lebih tinggi membuka kemampuan tambahan",
"lightconeEquipment": "Peralatan Light Cone", "lightconeEquipment": "Peralatan Light Cone",
"lightconeSettings": "Pengaturan Light Cone", "lightconeSettings": "Pengaturan Light Cone",
"placeholderLevel": "Masukkan level", "placeholderLevel": "Masukkan level",
"superimpositionRank": "Superimposition Rank", "superimpositionRank": "Superimposition Rank",
"ranksNote": "Rank lebih tinggi memberikan efek lebih kuat", "ranksNote": "Rank lebih tinggi memberikan efek lebih kuat",
"changeLightcone": "Ganti Light Cone", "changeLightcone": "Ganti Light Cone",
"removeLightcone": "Lepas Light Cone", "removeLightcone": "Lepas Light Cone",
"equipLightcone": "Pakai Light Cone", "equipLightcone": "Pakai Light Cone",
"noLightconeEquipped": "Tidak Ada Light Cone", "noLightconeEquipped": "Tidak Ada Light Cone",
"equipLightconeNote": "Pakai light cone untuk memperkuat karaktermu", "equipLightconeNote": "Pakai light cone untuk memperkuat karaktermu",
"filter": "Filter", "filter": "Filter",
"selectedCharacters": "Karakter Dipilih", "selectedCharacters": "Karakter Dipilih",
"selectedProfiles": "Profil Dipilih", "selectedProfiles": "Profil Dipilih",
"clearAll": "Bersihkan Semua", "clearAll": "Bersihkan Semua",
"selectAll": "Pilih Semua", "selectAll": "Pilih Semua",
"copy": "Salin", "copy": "Salin",
"copied": "Tersalin", "copied": "Tersalin",
"noAvatarSelected": "Tidak ada karakter dipilih", "noAvatarSelected": "Tidak ada karakter dipilih",
"noAvatarToCopySelected": "Tidak ada karakter untuk disalin", "noAvatarToCopySelected": "Tidak ada karakter untuk disalin",
"pleaseSelectAtLeastOneProfile": "Pilih setidaknya satu profil", "pleaseSelectAtLeastOneProfile": "Pilih setidaknya satu profil",
"pleaseEnterUid": "Silakan masukkan UID", "pleaseEnterUid": "Silakan masukkan UID",
"failedToFetchEnkaData": "Gagal mengambil data Enka", "failedToFetchEnkaData": "Gagal mengambil data Enka",
"pleaseSelectAtLeastOneCharacter": "Pilih setidaknya satu karakter", "pleaseSelectAtLeastOneCharacter": "Pilih setidaknya satu karakter",
"noDataToImport": "Tidak ada data untuk diimpor", "noDataToImport": "Tidak ada data untuk diimpor",
"pleaseSelectAFile": "Silakan pilih file", "pleaseSelectAFile": "Silakan pilih file",
"fileMustBeAValidJsonFile": "File harus berupa file JSON yang valid", "fileMustBeAValidJsonFile": "File harus berupa file JSON yang valid",
"importEnkaDataSuccess": "Impor data Enka berhasil", "importEnkaDataSuccess": "Impor data Enka berhasil",
"importFreeSRDataSuccess": "Impor data FreeSR berhasil", "importFreeSRDataSuccess": "Impor data FreeSR berhasil",
"importDatabaseSuccess": "Impor database berhasil", "importDatabaseSuccess": "Impor database berhasil",
"getData": "Dapatkan Data", "getData": "Dapatkan Data",
"import": "Impor", "import": "Impor",
"freeSRImport": "Impor FreeSR", "freeSRImport": "Impor FreeSR",
"onlySupportFreeSRJsonFile": "Hanya mendukung file JSON FreeSR", "onlySupportFreeSRJsonFile": "Hanya mendukung file JSON FreeSR",
"pickAFile": "Pilih sebuah file", "pickAFile": "Pilih sebuah file",
"lightConeSetting": "Pengaturan Light Cone", "lightConeSetting": "Pengaturan Light Cone",
"relicMaker": "Pembuat Relic", "relicMaker": "Pembuat Relic",
"pleaseSelectAllOptions": "Silakan pilih semua opsi", "pleaseSelectAllOptions": "Silakan pilih semua opsi",
"relicSavedSuccessfully": "Relic berhasil disimpan", "relicSavedSuccessfully": "Relic berhasil disimpan",
"mainSettings": "Pengaturan Utama", "mainSettings": "Pengaturan Utama",
"mainStat": "Statistik Utama", "mainStat": "Statistik Utama",
"set": "Set", "set": "Set",
"pleaseSelectASet": "Pilih sebuah set", "pleaseSelectASet": "Pilih sebuah set",
"effectBonus": "Bonus Efek", "effectBonus": "Bonus Efek",
"totalRoll": "Total Roll", "totalRoll": "Total Roll",
"randomizeStats": "Acak Statistik", "randomizeStats": "Acak Statistik",
"randomizeRolls": "Acak Roll", "randomizeRolls": "Acak Roll",
"selectASubStat": "Pilih sub stat", "selectASubStat": "Pilih sub stat",
"selectASet": "Pilih sebuah set", "selectASet": "Pilih sebuah set",
"selectAMainStat": "Pilih stat utama", "selectAMainStat": "Pilih stat utama",
"save": "Simpan", "save": "Simpan",
"reset": "Reset", "reset": "Reset",
"roll": "Roll", "roll": "Roll",
"step": "Langkah", "step": "Langkah",
"memoryOfChaos": "Memory of Chaos", "memoryOfChaos": "Memory of Chaos",
"pureFiction": "Pure Fiction", "pureFiction": "Pure Fiction",
"apocalypticShadow": "Apocalyptic Shadow", "apocalypticShadow": "Apocalyptic Shadow",
"customEnemy": "Musuh Kustom", "customEnemy": "Musuh Kustom",
"simulatedUniverse": "Simulated Universe", "simulatedUniverse": "Simulated Universe",
"floor": "Lantai", "floor": "Lantai",
"side": "Sisi", "side": "Sisi",
"wave": "Gelombang", "wave": "Gelombang",
"stage": "Tahap", "stage": "Tahap",
"useCycleCount": "Gunakan hitungan siklus?", "useCycleCount": "Gunakan hitungan siklus?",
"useTurbulenceBuff": "Gunakan buff turbulence?", "useTurbulenceBuff": "Gunakan buff turbulence?",
"firstHalfEnemies": "Musuh paruh pertama", "firstHalfEnemies": "Musuh paruh pertama",
"secondHalfEnemies": "Musuh paruh kedua", "secondHalfEnemies": "Musuh paruh kedua",
"firstNodeEnemies": "Musuh Node 1", "firstNodeEnemies": "Musuh Node 1",
"secondNodeEnemies": "Musuh Node 2", "secondNodeEnemies": "Musuh Node 2",
"thirdNodeEnemies": "Musuh Node 3", "thirdNodeEnemies": "Musuh Node 3",
"firstNode": "Node 1", "firstNode": "Node 1",
"secondNode": "Node 2", "secondNode": "Node 2",
"thirdNode": "Node 3", "thirdNode": "Node 3",
"listEnemies": "Daftar musuh", "listEnemies": "Daftar musuh",
"turbulenceBuff": "Turbulence Buff", "turbulenceBuff": "Turbulence Buff",
"noEventSelected": "Tidak ada event dipilih", "noEventSelected": "Tidak ada event dipilih",
"noTurbulenceBuff": "Tanpa Turbulence Buff", "noTurbulenceBuff": "Tanpa Turbulence Buff",
"upper": "Atas", "upper": "Atas",
"lower": "Bawah", "lower": "Bawah",
"upperToLower": "Atas -> Bawah", "upperToLower": "Atas -> Bawah",
"lowerToUpper": "Bawah -> Atas", "lowerToUpper": "Bawah -> Atas",
"selectMOCEvent": "Pilih Event MOC", "selectMOCEvent": "Pilih Event MOC",
"selectPFEvent": "Pilih Event PF", "selectPFEvent": "Pilih Event PF",
"selectASEvent": "Pilih Event AS", "selectASEvent": "Pilih Event AS",
"selectCEEvent": "Pilih Event CE", "selectCEEvent": "Pilih Event CE",
"selectEvent": "Pilih Event", "selectEvent": "Pilih Event",
"selectFloor": "Pilih Lantai", "selectFloor": "Pilih Lantai",
"selectSide": "Pilih Sisi", "selectSide": "Pilih Sisi",
"selectBuff": "Pilih Buff", "selectBuff": "Pilih Buff",
"selectStage": "Pilih Tahap", "selectStage": "Pilih Tahap",
"previous": "Sebelumnya", "previous": "Sebelumnya",
"next": "Selanjutnya", "next": "Selanjutnya",
"noMonstersFound": "Tidak ada monster ditemukan", "noMonstersFound": "Tidak ada monster ditemukan",
"addNewWave": "Tambah Gelombang Baru", "addNewWave": "Tambah Gelombang Baru",
"searchStage": "Cari tahap...", "searchStage": "Cari tahap...",
"noStageFound": "Tahap tidak ditemukan", "noStageFound": "Tahap tidak ditemukan",
"searchMonster": "Cari monster...", "searchMonster": "Cari monster...",
"changeRelic": "Ganti relic", "changeRelic": "Ganti relic",
"deleteRelic": "Hapus relic", "deleteRelic": "Hapus relic",
"deleteRelicConfirm": "Yakin ingin menghapus relic di slot ini?", "deleteRelicConfirm": "Yakin ingin menghapus relic di slot ini?",
"setEffects": "Atur Efek", "setEffects": "Atur Efek",
"details": "Detail", "details": "Detail",
"normal": "Basic ATK", "normal": "Basic ATK",
"bpskill": "Skill", "bpskill": "Skill",
"maze": "Technique", "maze": "Technique",
"ultra": "Ultimate", "ultra": "Ultimate",
"servantskill": "Skill Memosprite", "servantskill": "Skill Memosprite",
"severaltalent": "Talent Memosprite", "severaltalent": "Talent Memosprite",
"singleattack": "Single Attack", "singleattack": "Single Attack",
"enhance": "Enhance", "enhance": "Enhance",
"summon": "Summon", "summon": "Summon",
"mazeattack": "Technique Attack", "mazeattack": "Technique Attack",
"blast": "Blast", "blast": "Blast",
"restore": "Restore", "restore": "Restore",
"support": "Support", "support": "Support",
"aoeattack": "AoE Attack", "aoeattack": "AoE Attack",
"impair": "Impair", "impair": "Impair",
"bounce": "Bounce", "bounce": "Bounce",
"active": "Aktif", "active": "Aktif",
"defence": "Defence", "defence": "Defence",
"inactive": "Nonaktif", "inactive": "Nonaktif",
"maxAll": "Maksimalkan Semua", "maxAll": "Maksimalkan Semua",
"maxAllSuccess": "Berhasil memaksimalkan level skill.", "maxAllSuccess": "Berhasil memaksimalkan level skill.",
"maxAllFailed": "Gagal memaksimalkan level skill.", "maxAllFailed": "Gagal memaksimalkan level skill.",
"noRelicEquipped": "Tidak ada relic dipakai", "noRelicEquipped": "Tidak ada relic dipakai",
"anomalyArbitration": "Anomaly Arbitration", "anomalyArbitration": "Anomaly Arbitration",
"normalMode": "Mode Normal", "normalMode": "Mode Normal",
"hardMode": "Mode Sulit", "hardMode": "Mode Sulit",
"selectPEAKEvent": "Pilih Event PEAK", "selectPEAKEvent": "Pilih Event PEAK",
"mode": "Mode", "mode": "Mode",
"selectMode": "Pilih Mode", "selectMode": "Pilih Mode",
"rollBack": "Roll Back", "rollBack": "Roll Back",
"upRoll": "Up Roll", "upRoll": "Up Roll",
"downRoll": "Down Roll", "downRoll": "Down Roll",
"actions": "Aksi", "actions": "Aksi",
"avatars": "Avatar", "avatars": "Avatar",
"quickView": "Tampilan Cepat", "quickView": "Tampilan Cepat",
"extraSetting": "Pengaturan Ekstra", "extraSetting": "Pengaturan Ekstra",
"disableCensorship": "Nonaktifkan Sensor", "disableCensorship": "Nonaktifkan Sensor",
"hideUI": "Sembunyikan UI", "hideUI": "Sembunyikan UI",
"theoryCraftMode": "Mode Theorycraft", "theoryCraftMode": "Mode Theorycraft",
"cycleCount": "Hitungan Siklus", "cycleCount": "Hitungan Siklus",
"pleaseSelectAllSubStats": "Pilih semua sub statistik", "pleaseSelectAllSubStats": "Pilih semua sub statistik",
"subStatRollCountCannotBeZero": "Hitungan roll sub stat tidak boleh nol", "subStatRollCountCannotBeZero": "Hitungan roll sub stat tidak boleh nol",
"theoryCraft": "Theorycraft", "theoryCraft": "Theorycraft",
"multipathCharacter": "Karakter Multipath", "multipathCharacter": "Karakter Multipath",
"mainPath": "Path Utama", "mainPath": "Path Utama",
"march7Path": "Path March 7th", "march7Path": "Path March 7th",
"challenge": "Tantangan", "challenge": "Tantangan",
"skipNode": "Lewati Node", "skipNode": "Lewati Node",
"disableSkip": "Nonaktifkan lewati", "disableSkip": "Nonaktifkan lewati",
"skipNode1": "Lewati node 1", "skipNode1": "Lewati node 1",
"skipNode2": "Lewati node 2", "skipNode2": "Lewati node 2",
"extraFeatures": "Fitur Ekstra", "extraFeatures": "Fitur Ekstra",
"detailTheoryCraft": "Mengaktifkan fitur ini memungkinkan Anda menyesuaikan jumlah siklus dan menyesuaikan HP musuh.", "detailTheoryCraft": "Mengaktifkan fitur ini memungkinkan Anda menyesuaikan jumlah siklus dan menyesuaikan HP musuh.",
"detailSkipNode": "Memungkinkan Anda melewati (Node 1/Node 2) di Memory of Chaos atau Pure Fiction.", "detailSkipNode": "Memungkinkan Anda melewati (Node 1/Node 2) di Memory of Chaos atau Pure Fiction.",
"detailChallengePeak": "Memungkinkan perubahan musim Peak dalam Anomaly saat ini.", "detailChallengePeak": "Memungkinkan perubahan musim Peak dalam Anomaly saat ini.",
"detailHiddenUi": "Mengaktifkan fitur ini akan menyembunyikan UI game.", "detailHiddenUi": "Mengaktifkan fitur ini akan menyembunyikan UI game.",
"detailDisableCensorship": "Mengaktifkan fitur ini akan mematikan sensor dalam game.", "detailDisableCensorship": "Mengaktifkan fitur ini akan mematikan sensor dalam game.",
"detailMultipathCharacter": "Memungkinkan perubahan Path untuk karakter tertentu.", "detailMultipathCharacter": "Memungkinkan perubahan Path untuk karakter tertentu.",
"trailblazer": "Trailblazer", "trailblazer": "Trailblazer",
"listExtraEffect": "Daftar Efek Ekstra", "listExtraEffect": "Daftar Efek Ekstra",
"extra": "Ekstra", "extra": "Ekstra",
"customLineup": "Lineup Kustom" "customLineup": "Lineup Kustom"
} }
} }
+288 -288
View File
@@ -1,289 +1,289 @@
{ {
"TabTitle": { "TabTitle": {
"title": "Firefly Tools", "title": "Firefly Tools",
"description": "Firefly tools by Firefly Shelter" "description": "Firefly tools by Firefly Shelter"
}, },
"DataPage": { "DataPage": {
"skillType": "スキルタイプ", "skillType": "スキルタイプ",
"skillName": "スキル名", "skillName": "スキル名",
"character": "キャラクター", "character": "キャラクター",
"id": "ID", "id": "ID",
"path": "運命", "path": "運命",
"rarity": "レアリティ", "rarity": "レアリティ",
"element": "属性", "element": "属性",
"technique": "秘技", "technique": "秘技",
"talent": "天賦", "talent": "天賦",
"basic": "通常攻撃", "basic": "通常攻撃",
"skill": "スキル", "skill": "スキル",
"ultimate": "アルティメット", "ultimate": "アルティメット",
"servant": "サーバント", "servant": "サーバント",
"damage": "ダメージ", "damage": "ダメージ",
"type": "タイプ", "type": "タイプ",
"warrior": "壊滅", "warrior": "壊滅",
"knight": "存護", "knight": "存護",
"mage": "知恵", "mage": "知恵",
"priest": "豊穣", "priest": "豊穣",
"rogue": "巡狩", "rogue": "巡狩",
"shaman": "調和", "shaman": "調和",
"warlock": "虚無", "warlock": "虚無",
"memory": "記憶", "memory": "記憶",
"elation": "愉悦", "elation": "愉悦",
"fire": "火", "fire": "火",
"ice": "氷", "ice": "氷",
"imaginary": "虚数", "imaginary": "虚数",
"physical": "物理", "physical": "物理",
"quantum": "量子", "quantum": "量子",
"thunder": "雷", "thunder": "雷",
"wind": "風", "wind": "風",
"hp": "HP", "hp": "HP",
"atk": "攻撃", "atk": "攻撃",
"speed": "速度", "speed": "速度",
"critRate": "会心率", "critRate": "会心率",
"critDmg": "会心ダメージ", "critDmg": "会心ダメージ",
"breakEffect": "撃破特効", "breakEffect": "撃破特効",
"effectRes": "効果抵抗", "effectRes": "効果抵抗",
"energyRegenerationRate": "EP回復効率", "energyRegenerationRate": "EP回復効率",
"effectHitRate": "効果命中率", "effectHitRate": "効果命中率",
"outgoingHealingBoost": "回復増加", "outgoingHealingBoost": "回復増加",
"fireDmgBoost": "火属性ダメージ強化", "fireDmgBoost": "火属性ダメージ強化",
"iceDmgBoost": "氷属性ダメージ強化", "iceDmgBoost": "氷属性ダメージ強化",
"imaginaryDmgBoost": "幻影ダメージ強化", "imaginaryDmgBoost": "幻影ダメージ強化",
"physicalDmgBoost": "物理ダメージ強化", "physicalDmgBoost": "物理ダメージ強化",
"quantumDmgBoost": "量子ダメージ強化", "quantumDmgBoost": "量子ダメージ強化",
"thunderDmgBoost": "雷属性ダメージ強化", "thunderDmgBoost": "雷属性ダメージ強化",
"windDmgBoost": "風属性ダメージ強化", "windDmgBoost": "風属性ダメージ強化",
"pursued": "追加ダメージ", "pursued": "追加ダメージ",
"true damage": "確定ダメージ", "true damage": "確定ダメージ",
"elationdamage": "愉悦ダメージ", "elationdamage": "愉悦ダメージ",
"follow-up": "追撃ダメージ", "follow-up": "追撃ダメージ",
"elemental damage": "属性ダメージ", "elemental damage": "属性ダメージ",
"dot": "継続ダメージ", "dot": "継続ダメージ",
"qte": "QTEスキル", "qte": "QTEスキル",
"level": "レベル", "level": "レベル",
"relics": "遺物", "relics": "遺物",
"eidolons": "エイドロン", "eidolons": "エイドロン",
"lightcones": "光円錐", "lightcones": "光円錐",
"loadData": "データ読み込み", "loadData": "データ読み込み",
"exportData": "データ出力", "exportData": "データ出力",
"connectSetting": "接続設定", "connectSetting": "接続設定",
"connected": "接続済み", "connected": "接続済み",
"unconnected": "未接続", "unconnected": "未接続",
"psConnection": "PS接続", "psConnection": "PS接続",
"connectionType": "接続タイプ", "connectionType": "接続タイプ",
"status": "ステータス", "status": "ステータス",
"connectPs": "PS接続", "connectPs": "PS接続",
"disconnect": "切断", "disconnect": "切断",
"other": "その他", "other": "その他",
"freeSr": "FreeSR", "freeSr": "FreeSR",
"database": "データベース", "database": "データベース",
"enka": "Enka", "enka": "Enka",
"monsterSetting": "モンスター設定", "monsterSetting": "モンスター設定",
"serverUrl": "サーバー URL", "serverUrl": "サーバー URL",
"privateType": "プライベートタイプ", "privateType": "プライベートタイプ",
"local": "ローカル", "local": "ローカル",
"server": "サーバー", "server": "サーバー",
"username": "ユーザー名", "username": "ユーザー名",
"password": "パスワード", "password": "パスワード",
"placeholderServerUrl": "サーバー URL を入力", "placeholderServerUrl": "サーバー URL を入力",
"placeholderUsername": "ユーザー名を入力", "placeholderUsername": "ユーザー名を入力",
"placeholderPassword": "パスワードを入力", "placeholderPassword": "パスワードを入力",
"connectedSuccess": "PS接続成功", "connectedSuccess": "PS接続成功",
"connectedFailed": "PS接続失敗", "connectedFailed": "PS接続失敗",
"syncSuccess": "PSへのデータ同期に成功", "syncSuccess": "PSへのデータ同期に成功",
"syncFailed": "PSへのデータ同期に失敗", "syncFailed": "PSへのデータ同期に失敗",
"sync": "同期", "sync": "同期",
"importSetting": "インポート設定", "importSetting": "インポート設定",
"profile": "プロファイル", "profile": "プロファイル",
"default": "デフォルト", "default": "デフォルト",
"copyProfiles": "プロファイルをコピー", "copyProfiles": "プロファイルをコピー",
"addNewProfile": "プロファイルを追加", "addNewProfile": "プロファイルを追加",
"createNewProfile": "新しいプロファイルを作成", "createNewProfile": "新しいプロファイルを作成",
"editProfile": "プロファイル編集", "editProfile": "プロファイル編集",
"placeholderProfileName": "プロファイル名を入力", "placeholderProfileName": "プロファイル名を入力",
"profileName": "プロファイル名", "profileName": "プロファイル名",
"create": "作成", "create": "作成",
"update": "更新", "update": "更新",
"characterInformation": "キャラクター情報", "characterInformation": "キャラクター情報",
"skills": "スキル", "skills": "スキル",
"showcaseCard": "ショーケースカード", "showcaseCard": "ショーケースカード",
"comingSoon": "近日公開", "comingSoon": "近日公開",
"characterName": "キャラクター名", "characterName": "キャラクター名",
"placeholderCharacter": "キャラクター名を入力", "placeholderCharacter": "キャラクター名を入力",
"characterSettings": "キャラクター設定", "characterSettings": "キャラクター設定",
"levelConfiguration": "レベル構成", "levelConfiguration": "レベル構成",
"characterLevel": "キャラクターレベル", "characterLevel": "キャラクターレベル",
"max": "最大", "max": "最大",
"ultimateEnergy": "アルティメットエネルギー", "ultimateEnergy": "アルティメットエネルギー",
"currentEnergy": "現在のエネルギー", "currentEnergy": "現在のエネルギー",
"setTo50": "50%に設定", "setTo50": "50%に設定",
"battleConfiguration": "バトル構成", "battleConfiguration": "バトル構成",
"useTechnique": "秘技を使用", "useTechnique": "秘技を使用",
"techniqueNote": "バトル前に秘技効果を有効化", "techniqueNote": "バトル前に秘技効果を有効化",
"enhancement": "強化", "enhancement": "強化",
"enhancementLevel": "強化レベル", "enhancementLevel": "強化レベル",
"origin": "由来", "origin": "由来",
"enhancedNote": "強化レベルに応じて追加スキル解放", "enhancedNote": "強化レベルに応じて追加スキル解放",
"lightconeEquipment": "光円錐装備", "lightconeEquipment": "光円錐装備",
"lightconeSettings": "光円錐の設定", "lightconeSettings": "光円錐の設定",
"placeholderLevel": "レベルを入力", "placeholderLevel": "レベルを入力",
"superimpositionRank": "重ねランク", "superimpositionRank": "重ねランク",
"ranksNote": "ランクが高いほど効果強化", "ranksNote": "ランクが高いほど効果強化",
"changeLightcone": "光円錐を変更", "changeLightcone": "光円錐を変更",
"removeLightcone": "光円錐を外す", "removeLightcone": "光円錐を外す",
"equipLightcone": "光円錐を装備", "equipLightcone": "光円錐を装備",
"noLightconeEquipped": "光円錐が装備されていません", "noLightconeEquipped": "光円錐が装備されていません",
"equipLightconeNote": "強化のため光円錐を装備してください", "equipLightconeNote": "強化のため光円錐を装備してください",
"filter": "フィルター", "filter": "フィルター",
"selectedCharacters": "選択中のキャラクター", "selectedCharacters": "選択中のキャラクター",
"selectedProfiles": "選択中のプロファイル", "selectedProfiles": "選択中のプロファイル",
"clearAll": "全てクリア", "clearAll": "全てクリア",
"selectAll": "全て選択", "selectAll": "全て選択",
"copy": "コピー", "copy": "コピー",
"copied": "コピー済み", "copied": "コピー済み",
"noAvatarSelected": "キャラクターが選択されていません", "noAvatarSelected": "キャラクターが選択されていません",
"noAvatarToCopySelected": "コピーするキャラクター選択されていません", "noAvatarToCopySelected": "コピーするキャラクター選択されていません",
"pleaseSelectAtLeastOneProfile": "プロファイルを少なくとも1つ選んでください", "pleaseSelectAtLeastOneProfile": "プロファイルを少なくとも1つ選んでください",
"pleaseEnterUid": "UIDを入力してください", "pleaseEnterUid": "UIDを入力してください",
"failedToFetchEnkaData": "Enka のデータ取得に失敗", "failedToFetchEnkaData": "Enka のデータ取得に失敗",
"pleaseSelectAtLeastOneCharacter": "キャラクターを最低1つ選択してください", "pleaseSelectAtLeastOneCharacter": "キャラクターを最低1つ選択してください",
"noDataToImport": "インポートするデータがありません", "noDataToImport": "インポートするデータがありません",
"pleaseSelectAFile": "ファイルを選択してください", "pleaseSelectAFile": "ファイルを選択してください",
"fileMustBeAValidJsonFile": "有効な JSON ファイルである必要があります", "fileMustBeAValidJsonFile": "有効な JSON ファイルである必要があります",
"importEnkaDataSuccess": "Enka データのインポート成功", "importEnkaDataSuccess": "Enka データのインポート成功",
"importFreeSRDataSuccess": "FreeSR データのインポート成功", "importFreeSRDataSuccess": "FreeSR データのインポート成功",
"importDatabaseSuccess": "データベースのインポート成功", "importDatabaseSuccess": "データベースのインポート成功",
"getData": "データ取得", "getData": "データ取得",
"import": "インポート", "import": "インポート",
"freeSRImport": "FreeSR インポート", "freeSRImport": "FreeSR インポート",
"onlySupportFreeSRJsonFile": "FreeSR JSON ファイルのみ対応", "onlySupportFreeSRJsonFile": "FreeSR JSON ファイルのみ対応",
"pickAFile": "ファイル選択", "pickAFile": "ファイル選択",
"lightConeSetting": "ライトコーン設定", "lightConeSetting": "ライトコーン設定",
"relicMaker": "遺物製作", "relicMaker": "遺物製作",
"pleaseSelectAllOptions": "すべてのオプションを選択してください", "pleaseSelectAllOptions": "すべてのオプションを選択してください",
"relicSavedSuccessfully": "遺物の保存に成功しました", "relicSavedSuccessfully": "遺物の保存に成功しました",
"mainSettings": "メイン設定", "mainSettings": "メイン設定",
"mainStat": "メインステータス", "mainStat": "メインステータス",
"set": "セット", "set": "セット",
"pleaseSelectASet": "セットを選択してください", "pleaseSelectASet": "セットを選択してください",
"effectBonus": "効果ボーナス", "effectBonus": "効果ボーナス",
"totalRoll": "総ロール数", "totalRoll": "総ロール数",
"randomizeStats": "ステータスランダム化", "randomizeStats": "ステータスランダム化",
"randomizeRolls": "ロール回数ランダム化", "randomizeRolls": "ロール回数ランダム化",
"selectASubStat": "サブステータスを選択", "selectASubStat": "サブステータスを選択",
"selectASet": "セットを選択", "selectASet": "セットを選択",
"selectAMainStat": "メインステータスを選んでください", "selectAMainStat": "メインステータスを選んでください",
"save": "保存", "save": "保存",
"reset": "リセット", "reset": "リセット",
"roll": "ロール", "roll": "ロール",
"step": "ステップ", "step": "ステップ",
"memoryOfChaos": "混沌の記憶", "memoryOfChaos": "混沌の記憶",
"pureFiction": "虚構叙事", "pureFiction": "虚構叙事",
"apocalypticShadow": "末日の幻影", "apocalypticShadow": "末日の幻影",
"customEnemy": "カスタム敵", "customEnemy": "カスタム敵",
"simulatedUniverse": "模擬宇宙", "simulatedUniverse": "模擬宇宙",
"floor": "階層", "floor": "階層",
"side": "前半/後半", "side": "前半/後半",
"wave": "ウェーブ", "wave": "ウェーブ",
"stage": "ステージ", "stage": "ステージ",
"useCycleCount": "サイクル数を使用しますか?", "useCycleCount": "サイクル数を使用しますか?",
"useTurbulenceBuff": "乱気流バフを使用しますか?", "useTurbulenceBuff": "乱気流バフを使用しますか?",
"firstHalfEnemies": "前半の敵", "firstHalfEnemies": "前半の敵",
"secondHalfEnemies": "後半の敵", "secondHalfEnemies": "後半の敵",
"firstNodeEnemies": "ノード 1 の敵", "firstNodeEnemies": "ノード 1 の敵",
"secondNodeEnemies": "ノード 2 の敵", "secondNodeEnemies": "ノード 2 の敵",
"thirdNodeEnemies": "ノード 3 の敵", "thirdNodeEnemies": "ノード 3 の敵",
"firstNode": "ノード 1", "firstNode": "ノード 1",
"secondNode": "ノード 2", "secondNode": "ノード 2",
"thirdNode": "ノード 3", "thirdNode": "ノード 3",
"turbulenceBuff": "乱気流バフ", "turbulenceBuff": "乱気流バフ",
"noEventSelected": "イベントが選択されていません", "noEventSelected": "イベントが選択されていません",
"noTurbulenceBuff": "乱気流バフがありません", "noTurbulenceBuff": "乱気流バフがありません",
"upper": "上半", "upper": "上半",
"lower": "下半", "lower": "下半",
"upperToLower": "上半 -> 下半", "upperToLower": "上半 -> 下半",
"lowerToUpper": "下半 -> 上半", "lowerToUpper": "下半 -> 上半",
"selectMOCEvent": "MOC イベントを選択", "selectMOCEvent": "MOC イベントを選択",
"selectPFEvent": "PF イベントを選択", "selectPFEvent": "PF イベントを選択",
"selectASEvent": "AS イベントを選択", "selectASEvent": "AS イベントを選択",
"selectCEEvent": "CE イベントを選択", "selectCEEvent": "CE イベントを選択",
"selectEvent": "イベントを選択", "selectEvent": "イベントを選択",
"selectFloor": "階層を選択", "selectFloor": "階層を選択",
"selectSide": "前半/後半を選択", "selectSide": "前半/後半を選択",
"selectBuff": "バフを選択", "selectBuff": "バフを選択",
"selectStage": "ステージを選択", "selectStage": "ステージを選択",
"previous": "前へ", "previous": "前へ",
"next": "次へ", "next": "次へ",
"noMonstersFound": "敵が見つかりません", "noMonstersFound": "敵が見つかりません",
"addNewWave": "新しいウェーブを追加", "addNewWave": "新しいウェーブを追加",
"searchStage": "ステージを検索...", "searchStage": "ステージを検索...",
"noStageFound": "ステージが見つかりません", "noStageFound": "ステージが見つかりません",
"searchMonster": "敵を検索...", "searchMonster": "敵を検索...",
"changeRelic": "遺物を変更", "changeRelic": "遺物を変更",
"deleteRelic": "遺物を削除", "deleteRelic": "遺物を削除",
"deleteRelicConfirm": "この遺物を削除してもよろしいですか?", "deleteRelicConfirm": "この遺物を削除してもよろしいですか?",
"setEffects": "効果を設定", "setEffects": "効果を設定",
"details": "詳細", "details": "詳細",
"normal": "通常攻撃", "normal": "通常攻撃",
"bpskill": "戦闘スキル", "bpskill": "戦闘スキル",
"maze": "秘技", "maze": "秘技",
"ultra": "必殺技", "ultra": "必殺技",
"servantskill": "メモスプライトスキル", "servantskill": "メモスプライトスキル",
"severaltalent": "メモスプライトの才能", "severaltalent": "メモスプライトの才能",
"singleattack": "単体攻撃", "singleattack": "単体攻撃",
"enhance": "強化", "enhance": "強化",
"summon": "召喚", "summon": "召喚",
"blast": "爆発", "blast": "爆発",
"restore": "回復", "restore": "回復",
"support": "支援", "support": "支援",
"aoeattack": "範囲攻撃", "aoeattack": "範囲攻撃",
"mazeattack": "迷宮秘技", "mazeattack": "迷宮秘技",
"impair": "弱体化", "impair": "弱体化",
"bounce": "弾跳", "bounce": "弾跳",
"active": "アクティブ", "active": "アクティブ",
"inactive": "非アクティブ", "inactive": "非アクティブ",
"defence": "防御", "defence": "防御",
"maxAll": "すべて最大化", "maxAll": "すべて最大化",
"maxAllSuccess": "スキルレベルを最大に設定しました。", "maxAllSuccess": "スキルレベルを最大に設定しました。",
"maxAllFailed": "スキルレベルの最大設定に失敗しました。", "maxAllFailed": "スキルレベルの最大設定に失敗しました。",
"noRelicEquipped": "遺物が装備されていません", "noRelicEquipped": "遺物が装備されていません",
"anomalyArbitration": "異相の仲裁", "anomalyArbitration": "異相の仲裁",
"normalMode": "通常モード", "normalMode": "通常モード",
"hardMode": "困難モード", "hardMode": "困難モード",
"selectPEAKEvent": "PEAK イベントを選択", "selectPEAKEvent": "PEAK イベントを選択",
"mode": "モード", "mode": "モード",
"selectMode": "モードを選択", "selectMode": "モードを選択",
"rollBack": "前の状態に戻る", "rollBack": "前の状態に戻る",
"upRoll": "サブステータスを増やす", "upRoll": "サブステータスを増やす",
"downRoll": "サブステータスを減らす", "downRoll": "サブステータスを減らす",
"actions": "アクション", "actions": "アクション",
"avatars": "アバター", "avatars": "アバター",
"quickView": "クイックビュー", "quickView": "クイックビュー",
"extraSetting": "追加設定", "extraSetting": "追加設定",
"disableCensorship": "検閲を無効化", "disableCensorship": "検閲を無効化",
"hideUI": "UIを非表示", "hideUI": "UIを非表示",
"theoryCraftMode": "シアリークラフトモード", "theoryCraftMode": "シアリークラフトモード",
"cycleCount": "サイクル数", "cycleCount": "サイクル数",
"pleaseSelectAllSubStats": "すべてのサブステータスを選択してください", "pleaseSelectAllSubStats": "すべてのサブステータスを選択してください",
"subStatRollCountCannotBeZero": "サブステータスの行数は0にできません", "subStatRollCountCannotBeZero": "サブステータスの行数は0にできません",
"theoryCraft": "シアリークラフト", "theoryCraft": "シアリークラフト",
"multipathCharacter": "複数運命キャラ", "multipathCharacter": "複数運命キャラ",
"mainPath": "主人公の運命", "mainPath": "主人公の運命",
"march7Path": "三月なのかの運命", "march7Path": "三月なのかの運命",
"challenge": "挑戦", "challenge": "挑戦",
"skipNode": "ノードをスキップ", "skipNode": "ノードをスキップ",
"disableSkip": "スキップ無効", "disableSkip": "スキップ無効",
"skipNode1": "ノード1をスキップ", "skipNode1": "ノード1をスキップ",
"skipNode2": "ノード2をスキップ", "skipNode2": "ノード2をスキップ",
"extraFeatures": "追加機能", "extraFeatures": "追加機能",
"detailTheoryCraft": "この機能を有効にすると、サイクル数の調整や敵設定でHPの調整が可能になります。", "detailTheoryCraft": "この機能を有効にすると、サイクル数の調整や敵設定でHPの調整が可能になります。",
"detailSkipNode": "この機能を有効にすると、混沌の記憶または虚構叙事の(ノード1/ノード2)をスキップできます。", "detailSkipNode": "この機能を有効にすると、混沌の記憶または虚構叙事の(ノード1/ノード2)をスキップできます。",
"detailChallengePeak": "現在の異相における「頂」のシーズンを変更できます。", "detailChallengePeak": "現在の異相における「頂」のシーズンを変更できます。",
"detailHiddenUi": "この機能を有効にすると、ゲームのUIを非表示にします。", "detailHiddenUi": "この機能を有効にすると、ゲームのUIを非表示にします。",
"detailDisableCensorship": "この機能を有効にすると、ゲーム内の検閲を無効にします。", "detailDisableCensorship": "この機能を有効にすると、ゲーム内の検閲を無効にします。",
"detailMultipathCharacter": "一部キャラクターの運命を変更できます。", "detailMultipathCharacter": "一部キャラクターの運命を変更できます。",
"trailblazer": "開拓者", "trailblazer": "開拓者",
"listExtraEffect": "追加効果一覧", "listExtraEffect": "追加効果一覧",
"extra": "追加", "extra": "追加",
"customLineup": "カスタム編成" "customLineup": "カスタム編成"
} }
} }
+288 -288
View File
@@ -1,289 +1,289 @@
{ {
"TabTitle": { "TabTitle": {
"title": "Firefly Tools", "title": "Firefly Tools",
"description": "Firefly tools by Firefly Shelter" "description": "Firefly tools by Firefly Shelter"
}, },
"DataPage": { "DataPage": {
"skillType": "스킬 유형", "skillType": "스킬 유형",
"skillName": "스킬 이름", "skillName": "스킬 이름",
"character": "캐릭터", "character": "캐릭터",
"id": "ID", "id": "ID",
"path": "운명", "path": "운명",
"rarity": "희귀도", "rarity": "희귀도",
"element": "속성", "element": "속성",
"technique": "비기", "technique": "비기",
"talent": "탈렌트", "talent": "탈렌트",
"basic": "기본 공격", "basic": "기본 공격",
"skill": "스킬", "skill": "스킬",
"ultimate": "궁극기", "ultimate": "궁극기",
"servant": "서번트", "servant": "서번트",
"damage": "데미지", "damage": "데미지",
"type": "종류", "type": "종류",
"warrior": "파괴", "warrior": "파괴",
"knight": "보호", "knight": "보호",
"mage": "박학", "mage": "박학",
"priest": "풍요", "priest": "풍요",
"rogue": "사냥", "rogue": "사냥",
"shaman": "조화", "shaman": "조화",
"warlock": "허무", "warlock": "허무",
"memory": "기억", "memory": "기억",
"elation": "환락", "elation": "환락",
"fire": "화염", "fire": "화염",
"ice": "얼음", "ice": "얼음",
"imaginary": "환상", "imaginary": "환상",
"physical": "물리", "physical": "물리",
"quantum": "양자", "quantum": "양자",
"thunder": "번개", "thunder": "번개",
"wind": "바람", "wind": "바람",
"hp": "HP", "hp": "HP",
"atk": "공격력", "atk": "공격력",
"speed": "속도", "speed": "속도",
"critRate": "치명타 확률", "critRate": "치명타 확률",
"critDmg": "치명타 피해", "critDmg": "치명타 피해",
"breakEffect": "파괴 피해", "breakEffect": "파괴 피해",
"effectRes": "효과 저항", "effectRes": "효과 저항",
"energyRegenerationRate": "에너지 회복속도", "energyRegenerationRate": "에너지 회복속도",
"effectHitRate": "효과 적중률", "effectHitRate": "효과 적중률",
"outgoingHealingBoost": "치유 강화", "outgoingHealingBoost": "치유 강화",
"fireDmgBoost": "화염 피해 증가", "fireDmgBoost": "화염 피해 증가",
"iceDmgBoost": "얼음 피해 증가", "iceDmgBoost": "얼음 피해 증가",
"imaginaryDmgBoost": "환상 피해 증가", "imaginaryDmgBoost": "환상 피해 증가",
"physicalDmgBoost": "물리 피해 증가", "physicalDmgBoost": "물리 피해 증가",
"quantumDmgBoost": "양자 피해 증가", "quantumDmgBoost": "양자 피해 증가",
"thunderDmgBoost": "번개 피해 증가", "thunderDmgBoost": "번개 피해 증가",
"windDmgBoost": "바람 피해 증가", "windDmgBoost": "바람 피해 증가",
"pursued": "추가 피해", "pursued": "추가 피해",
"true damage": "진실한 피해", "true damage": "진실한 피해",
"elationdamage": "환락 피해", "elationdamage": "환락 피해",
"follow-up": "후속 피해", "follow-up": "후속 피해",
"elemental damage": "파괴 및 초파괴 피해", "elemental damage": "파괴 및 초파괴 피해",
"dot": "시간 경과 피해", "dot": "시간 경과 피해",
"qte": "QTE 스킬", "qte": "QTE 스킬",
"level": "레벨", "level": "레벨",
"relics": "유물", "relics": "유물",
"eidolons": "에이돌론", "eidolons": "에이돌론",
"lightcones": "라이트콘", "lightcones": "라이트콘",
"loadData": "데이터 불러오기", "loadData": "데이터 불러오기",
"exportData": "데이터 내보내기", "exportData": "데이터 내보내기",
"connectSetting": "연결 설정", "connectSetting": "연결 설정",
"connected": "연결됨", "connected": "연결됨",
"unconnected": "연결 안됨", "unconnected": "연결 안됨",
"psConnection": "PS 연결", "psConnection": "PS 연결",
"connectionType": "연결 타입", "connectionType": "연결 타입",
"status": "상태", "status": "상태",
"connectPs": "PS 연결", "connectPs": "PS 연결",
"disconnect": "연결 끊기", "disconnect": "연결 끊기",
"other": "기타", "other": "기타",
"freeSr": "FreeSR", "freeSr": "FreeSR",
"database": "데이터베이스", "database": "데이터베이스",
"enka": "Enka", "enka": "Enka",
"monsterSetting": "몬스터 설정", "monsterSetting": "몬스터 설정",
"serverUrl": "서버 URL", "serverUrl": "서버 URL",
"privateType": "프라이빗 타입", "privateType": "프라이빗 타입",
"local": "로컬", "local": "로컬",
"server": "서버", "server": "서버",
"username": "사용자명", "username": "사용자명",
"password": "비밀번호", "password": "비밀번호",
"placeholderServerUrl": "서버 URL 입력", "placeholderServerUrl": "서버 URL 입력",
"placeholderUsername": "사용자명 입력", "placeholderUsername": "사용자명 입력",
"placeholderPassword": "비밀번호 입력", "placeholderPassword": "비밀번호 입력",
"connectedSuccess": "PS에 성공적으로 연결됨", "connectedSuccess": "PS에 성공적으로 연결됨",
"connectedFailed": "PS 연결 실패", "connectedFailed": "PS 연결 실패",
"syncSuccess": "PS에 데이터 동기화 성공", "syncSuccess": "PS에 데이터 동기화 성공",
"syncFailed": "PS에 데이터 동기화 실패", "syncFailed": "PS에 데이터 동기화 실패",
"sync": "동기화", "sync": "동기화",
"importSetting": "설정 가져오기", "importSetting": "설정 가져오기",
"profile": "프로필", "profile": "프로필",
"default": "기본", "default": "기본",
"copyProfiles": "프로필 복사", "copyProfiles": "프로필 복사",
"addNewProfile": "새 프로필 추가", "addNewProfile": "새 프로필 추가",
"createNewProfile": "새 프로필 생성", "createNewProfile": "새 프로필 생성",
"editProfile": "프로필 편집", "editProfile": "프로필 편집",
"placeholderProfileName": "프로필 이름 입력", "placeholderProfileName": "프로필 이름 입력",
"profileName": "프로필 이름", "profileName": "프로필 이름",
"create": "생성", "create": "생성",
"update": "업데이트", "update": "업데이트",
"characterInformation": "캐릭터 정보", "characterInformation": "캐릭터 정보",
"skills": "스킬", "skills": "스킬",
"showcaseCard": "쇼케이스 카드", "showcaseCard": "쇼케이스 카드",
"comingSoon": "곧 출시", "comingSoon": "곧 출시",
"characterName": "캐릭터 이름", "characterName": "캐릭터 이름",
"placeholderCharacter": "캐릭터 이름 입력", "placeholderCharacter": "캐릭터 이름 입력",
"characterSettings": "캐릭터 설정", "characterSettings": "캐릭터 설정",
"levelConfiguration": "레벨 구성", "levelConfiguration": "레벨 구성",
"characterLevel": "캐릭터 레벨", "characterLevel": "캐릭터 레벨",
"max": "최대", "max": "최대",
"ultimateEnergy": "궁극 에너지", "ultimateEnergy": "궁극 에너지",
"currentEnergy": "현재 에너지", "currentEnergy": "현재 에너지",
"setTo50": "50%로 설정", "setTo50": "50%로 설정",
"battleConfiguration": "전투 구성", "battleConfiguration": "전투 구성",
"useTechnique": "비기 사용", "useTechnique": "비기 사용",
"techniqueNote": "전투 전 비기 효과 활성화", "techniqueNote": "전투 전 비기 효과 활성화",
"enhancement": "강화", "enhancement": "강화",
"enhancementLevel": "강화 레벨", "enhancementLevel": "강화 레벨",
"origin": "출처", "origin": "출처",
"enhancedNote": "강화 레벨이 높을수록 추가 효과 해제", "enhancedNote": "강화 레벨이 높을수록 추가 효과 해제",
"lightconeEquipment": "라이트콘 장비", "lightconeEquipment": "라이트콘 장비",
"lightconeSettings": "라이트콘 설정", "lightconeSettings": "라이트콘 설정",
"placeholderLevel": "레벨 입력", "placeholderLevel": "레벨 입력",
"superimpositionRank": "중첩 등급", "superimpositionRank": "중첩 등급",
"ranksNote": "등급이 높을수록 효과가 강함", "ranksNote": "등급이 높을수록 효과가 강함",
"changeLightcone": "라이트콘 변경", "changeLightcone": "라이트콘 변경",
"removeLightcone": "라이트콘 제거", "removeLightcone": "라이트콘 제거",
"equipLightcone": "라이트콘 장착", "equipLightcone": "라이트콘 장착",
"noLightconeEquipped": "라이트콘 미장착", "noLightconeEquipped": "라이트콘 미장착",
"equipLightconeNote": "캐릭터 능력 향상을 위해 장착하세요", "equipLightconeNote": "캐릭터 능력 향상을 위해 장착하세요",
"filter": "필터", "filter": "필터",
"selectedCharacters": "선택된 캐릭터", "selectedCharacters": "선택된 캐릭터",
"selectedProfiles": "선택된 프로필", "selectedProfiles": "선택된 프로필",
"clearAll": "전체 지우기", "clearAll": "전체 지우기",
"selectAll": "전체 선택", "selectAll": "전체 선택",
"copy": "복사", "copy": "복사",
"copied": "복사됨", "copied": "복사됨",
"noAvatarSelected": "캐릭터가 선택되지 않음", "noAvatarSelected": "캐릭터가 선택되지 않음",
"noAvatarToCopySelected": "복사할 캐릭터가 선택되지 않음", "noAvatarToCopySelected": "복사할 캐릭터가 선택되지 않음",
"pleaseSelectAtLeastOneProfile": "프로필을 최소 하나 선택하세요", "pleaseSelectAtLeastOneProfile": "프로필을 최소 하나 선택하세요",
"pleaseEnterUid": "UID를 입력해주세요", "pleaseEnterUid": "UID를 입력해주세요",
"failedToFetchEnkaData": "Enka 데이터 가져오기 실패", "failedToFetchEnkaData": "Enka 데이터 가져오기 실패",
"pleaseSelectAtLeastOneCharacter": "캐릭터를 최소 하나 선택하세요", "pleaseSelectAtLeastOneCharacter": "캐릭터를 최소 하나 선택하세요",
"noDataToImport": "가져올 데이터가 없습니다", "noDataToImport": "가져올 데이터가 없습니다",
"pleaseSelectAFile": "파일을 선택하세요", "pleaseSelectAFile": "파일을 선택하세요",
"fileMustBeAValidJsonFile": "유효한 JSON 파일이어야 합니다", "fileMustBeAValidJsonFile": "유효한 JSON 파일이어야 합니다",
"importEnkaDataSuccess": "Enka 데이터 가져오기 성공", "importEnkaDataSuccess": "Enka 데이터 가져오기 성공",
"importFreeSRDataSuccess": "FreeSR 데이터 가져오기 성공", "importFreeSRDataSuccess": "FreeSR 데이터 가져오기 성공",
"importDatabaseSuccess": "데이터베이스 가져오기 성공", "importDatabaseSuccess": "데이터베이스 가져오기 성공",
"getData": "데이터 가져오기", "getData": "데이터 가져오기",
"import": "가져오기", "import": "가져오기",
"freeSRImport": "FreeSR 가져오기", "freeSRImport": "FreeSR 가져오기",
"onlySupportFreeSRJsonFile": "FreeSR JSON 파일만 지원", "onlySupportFreeSRJsonFile": "FreeSR JSON 파일만 지원",
"pickAFile": "파일 선택", "pickAFile": "파일 선택",
"lightConeSetting": "라이트콘 설정", "lightConeSetting": "라이트콘 설정",
"relicMaker": "유물 제작", "relicMaker": "유물 제작",
"pleaseSelectAllOptions": "모든 옵션을 선택해주세요", "pleaseSelectAllOptions": "모든 옵션을 선택해주세요",
"relicSavedSuccessfully": "유물 저장 성공", "relicSavedSuccessfully": "유물 저장 성공",
"mainSettings": "메인 설정", "mainSettings": "메인 설정",
"mainStat": "주 속성", "mainStat": "주 속성",
"set": "세트", "set": "세트",
"pleaseSelectASet": "세트 하나를 선택해주세요", "pleaseSelectASet": "세트 하나를 선택해주세요",
"effectBonus": "효과 보너스", "effectBonus": "효과 보너스",
"totalRoll": "총 횟수", "totalRoll": "총 횟수",
"randomizeStats": "속성 랜덤화", "randomizeStats": "속성 랜덤화",
"randomizeRolls": "횟수 랜덤화", "randomizeRolls": "횟수 랜덤화",
"selectASubStat": "부 속성 선택", "selectASubStat": "부 속성 선택",
"selectASet": "세트 선택", "selectASet": "세트 선택",
"selectAMainStat": "주 속성 선택", "selectAMainStat": "주 속성 선택",
"save": "저장", "save": "저장",
"reset": "초기화", "reset": "초기화",
"roll": "굴리기", "roll": "굴리기",
"step": "단계", "step": "단계",
"memoryOfChaos": "망각의 정원", "memoryOfChaos": "망각의 정원",
"pureFiction": "허구 이야기", "pureFiction": "허구 이야기",
"apocalypticShadow": "종말의 환영", "apocalypticShadow": "종말의 환영",
"customEnemy": "커스텀 적", "customEnemy": "커스텀 적",
"simulatedUniverse": "시뮬레이티드 유니버스", "simulatedUniverse": "시뮬레이티드 유니버스",
"floor": "층수", "floor": "층수",
"side": "전반/후반", "side": "전반/후반",
"wave": "웨이브", "wave": "웨이브",
"stage": "스테이지", "stage": "스테이지",
"useCycleCount": "사이클 수 사용?", "useCycleCount": "사이클 수 사용?",
"useTurbulenceBuff": "난류 버프 사용?", "useTurbulenceBuff": "난류 버프 사용?",
"firstHalfEnemies": "전반 적", "firstHalfEnemies": "전반 적",
"secondHalfEnemies": "후반 적", "secondHalfEnemies": "후반 적",
"firstNodeEnemies": "노드 1 적", "firstNodeEnemies": "노드 1 적",
"secondNodeEnemies": "노드 2 적", "secondNodeEnemies": "노드 2 적",
"thirdNodeEnemies": "노드 3 적", "thirdNodeEnemies": "노드 3 적",
"firstNode": "노드 1", "firstNode": "노드 1",
"secondNode": "노드 2", "secondNode": "노드 2",
"thirdNode": "노드 3", "thirdNode": "노드 3",
"turbulenceBuff": "난류 버프", "turbulenceBuff": "난류 버프",
"noEventSelected": "이벤트가 선택되지 않음", "noEventSelected": "이벤트가 선택되지 않음",
"noTurbulenceBuff": "난류 버프가 없음", "noTurbulenceBuff": "난류 버프가 없음",
"upper": "상반", "upper": "상반",
"lower": "하반", "lower": "하반",
"upperToLower": "상반 -> 하반", "upperToLower": "상반 -> 하반",
"lowerToUpper": "하반 -> 상반", "lowerToUpper": "하반 -> 상반",
"selectMOCEvent": "MOC 이벤트 선택", "selectMOCEvent": "MOC 이벤트 선택",
"selectPFEvent": "PF 이벤트 선택", "selectPFEvent": "PF 이벤트 선택",
"selectASEvent": "AS 이벤트 선택", "selectASEvent": "AS 이벤트 선택",
"selectCEEvent": "CE 이벤트 선택", "selectCEEvent": "CE 이벤트 선택",
"selectEvent": "이벤트 선택", "selectEvent": "이벤트 선택",
"selectFloor": "층 선택", "selectFloor": "층 선택",
"selectSide": "전반/후반 선택", "selectSide": "전반/후반 선택",
"selectBuff": "버프 선택", "selectBuff": "버프 선택",
"selectStage": "스테이지 선택", "selectStage": "스테이지 선택",
"previous": "이전", "previous": "이전",
"next": "다음", "next": "다음",
"noMonstersFound": "적을 찾을 수 없음", "noMonstersFound": "적을 찾을 수 없음",
"addNewWave": "새 웨이브 추가", "addNewWave": "새 웨이브 추가",
"searchStage": "스테이지 검색...", "searchStage": "스테이지 검색...",
"noStageFound": "스테이지를 찾을 수 없음", "noStageFound": "스테이지를 찾을 수 없음",
"searchMonster": "적 검색...", "searchMonster": "적 검색...",
"changeRelic": "유물 변경", "changeRelic": "유물 변경",
"deleteRelic": "유물 삭제", "deleteRelic": "유물 삭제",
"deleteRelicConfirm": "이 슬롯의 유물을 삭제하시겠습니까?", "deleteRelicConfirm": "이 슬롯의 유물을 삭제하시겠습니까?",
"setEffects": "효과 설정", "setEffects": "효과 설정",
"details": "상세", "details": "상세",
"normal": "기본 공격", "normal": "기본 공격",
"bpskill": "스킬", "bpskill": "스킬",
"maze": "전술", "maze": "전술",
"ultra": "필살기", "ultra": "필살기",
"servantskill": "메모스프라이트 스킬", "servantskill": "메모스프라이트 스킬",
"severaltalent": "메모스프라이트 재능", "severaltalent": "메모스프라이트 재능",
"singleattack": "단일 공격", "singleattack": "단일 공격",
"enhance": "강화", "enhance": "강화",
"summon": "소환", "summon": "소환",
"blast": "광역 공격", "blast": "광역 공격",
"restore": "회복", "restore": "회복",
"support": "지원", "support": "지원",
"aoeattack": "광역 공격", "aoeattack": "광역 공격",
"mazeattack": "미궁 비기", "mazeattack": "미궁 비기",
"impair": "약화", "impair": "약화",
"bounce": "튕김", "bounce": "튕김",
"active": "활성", "active": "활성",
"inactive": "비활성", "inactive": "비활성",
"defence": "방어", "defence": "방어",
"maxAll": "모두 최대치", "maxAll": "모두 최대치",
"maxAllSuccess": "스킬 레벨이 최대치로 설정되었습니다.", "maxAllSuccess": "스킬 레벨이 최대치로 설정되었습니다.",
"maxAllFailed": "스킬 레벨을 최대치로 설정하지 못했습니다.", "maxAllFailed": "스킬 레벨을 최대치로 설정하지 못했습니다.",
"noRelicEquipped": "성유물이 장착되지 않음", "noRelicEquipped": "성유물이 장착되지 않음",
"anomalyArbitration": "이상 중재", "anomalyArbitration": "이상 중재",
"normalMode": "일반 모드", "normalMode": "일반 모드",
"hardMode": "어려움 모드", "hardMode": "어려움 모드",
"selectPEAKEvent": "PEAK 이벤트 선택", "selectPEAKEvent": "PEAK 이벤트 선택",
"mode": "모드", "mode": "모드",
"selectMode": "모드를 선택", "selectMode": "모드를 선택",
"rollBack": "이전 상태로 되돌리기", "rollBack": "이전 상태로 되돌리기",
"upRoll": "부옵션 추가", "upRoll": "부옵션 추가",
"downRoll": "부옵션 감소", "downRoll": "부옵션 감소",
"actions": "동작", "actions": "동작",
"avatars": "아바타", "avatars": "아바타",
"quickView": "빠른 조회", "quickView": "빠른 조회",
"extraSetting": "추가 설정", "extraSetting": "추가 설정",
"disableCensorship": "검열 비활성화", "disableCensorship": "검열 비활성화",
"hideUI": "UI 숨기기", "hideUI": "UI 숨기기",
"theoryCraftMode": "Theory Craft 모드", "theoryCraftMode": "Theory Craft 모드",
"cycleCount": "사이클 수", "cycleCount": "사이클 수",
"pleaseSelectAllSubStats": "모든 부옵션을 선택하세요", "pleaseSelectAllSubStats": "모든 부옵션을 선택하세요",
"subStatRollCountCannotBeZero": "부옵션의 줄 수는 0일 수 없습니다", "subStatRollCountCannotBeZero": "부옵션의 줄 수는 0일 수 없습니다",
"theoryCraft": "Theory Craft", "theoryCraft": "Theory Craft",
"multipathCharacter": "다중 운명 캐릭터", "multipathCharacter": "다중 운명 캐릭터",
"mainPath": "개척자 운명", "mainPath": "개척자 운명",
"march7Path": "삼월칠일 운명", "march7Path": "삼월칠일 운명",
"challenge": "도전", "challenge": "도전",
"skipNode": "노드 건너뛰기", "skipNode": "노드 건너뛰기",
"disableSkip": "건너뛰기 비활성화", "disableSkip": "건너뛰기 비활성화",
"skipNode1": "노드 1 건너뛰기", "skipNode1": "노드 1 건너뛰기",
"skipNode2": "노드 2 건너뛰기", "skipNode2": "노드 2 건너뛰기",
"extraFeatures": "추가 기능", "extraFeatures": "추가 기능",
"detailTheoryCraft": "이 기능을 활성화하면 사이클 수를 조정하고 적 설정에서 HP를 변경할 수 있습니다.", "detailTheoryCraft": "이 기능을 활성화하면 사이클 수를 조정하고 적 설정에서 HP를 변경할 수 있습니다.",
"detailSkipNode": "이 기능을 활성화하면 혼돈의 기억 또는 허구 서사의 (노드 1/노드 2)를 건너뛸 수 있습니다.", "detailSkipNode": "이 기능을 활성화하면 혼돈의 기억 또는 허구 서사의 (노드 1/노드 2)를 건너뛸 수 있습니다.",
"detailChallengePeak": "현재 이형에서의 피크 시즌을 변경할 수 있습니다.", "detailChallengePeak": "현재 이형에서의 피크 시즌을 변경할 수 있습니다.",
"detailHiddenUi": "이 기능을 활성화하면 게임 UI가 숨겨집니다.", "detailHiddenUi": "이 기능을 활성화하면 게임 UI가 숨겨집니다.",
"detailDisableCensorship": "이 기능을 활성화하면 게임 내 검열이 비활성화됩니다.", "detailDisableCensorship": "이 기능을 활성화하면 게임 내 검열이 비활성화됩니다.",
"detailMultipathCharacter": "일부 캐릭터의 운명을 변경할 수 있습니다.", "detailMultipathCharacter": "일부 캐릭터의 운명을 변경할 수 있습니다.",
"trailblazer": "개척자", "trailblazer": "개척자",
"listExtraEffect": "추가 효과 목록", "listExtraEffect": "추가 효과 목록",
"extra": "추가", "extra": "추가",
"customLineup": "커스텀 편성" "customLineup": "커스텀 편성"
} }
} }
+289 -289
View File
@@ -1,290 +1,290 @@
{ {
"TabTitle": { "TabTitle": {
"title": "Firefly Tools", "title": "Firefly Tools",
"description": "Ferramentas Firefly por Firefly Shelter" "description": "Ferramentas Firefly por Firefly Shelter"
}, },
"DataPage": { "DataPage": {
"skillType": "Tipo de Habilidade", "skillType": "Tipo de Habilidade",
"skillName": "Nome da Habilidade", "skillName": "Nome da Habilidade",
"character": "Personagem", "character": "Personagem",
"id": "Id", "id": "Id",
"path": "Caminho", "path": "Caminho",
"rarity": "Raridade", "rarity": "Raridade",
"element": "Elemento", "element": "Elemento",
"technique": "Técnica", "technique": "Técnica",
"talent": "Talento", "talent": "Talento",
"basic": "Ataque Básico", "basic": "Ataque Básico",
"skill": "Perícia", "skill": "Perícia",
"ultimate": "Perícia Suprema", "ultimate": "Perícia Suprema",
"servant": "Servo", "servant": "Servo",
"damage": "Dano", "damage": "Dano",
"type": "Tipo", "type": "Tipo",
"warrior": "A Destruição", "warrior": "A Destruição",
"knight": "A Preservação", "knight": "A Preservação",
"mage": "A Erudição", "mage": "A Erudição",
"priest": "A Abundância", "priest": "A Abundância",
"rogue": "A Caça", "rogue": "A Caça",
"shaman": "A Harmonia", "shaman": "A Harmonia",
"warlock": "A Inexistência", "warlock": "A Inexistência",
"memory": "A Lembrança", "memory": "A Lembrança",
"elation": "A Alegria", "elation": "A Alegria",
"fire": "Fogo", "fire": "Fogo",
"ice": "Gelo", "ice": "Gelo",
"imaginary": "Imaginário", "imaginary": "Imaginário",
"physical": "Físico", "physical": "Físico",
"quantum": "Quântico", "quantum": "Quântico",
"thunder": "Raio", "thunder": "Raio",
"wind": "Vento", "wind": "Vento",
"hp": "PV", "hp": "PV",
"atk": "ATQ", "atk": "ATQ",
"speed": "VEL", "speed": "VEL",
"critRate": "Taxa CRIT", "critRate": "Taxa CRIT",
"critDmg": "Dano CRIT", "critDmg": "Dano CRIT",
"breakEffect": "Efeito de Quebra", "breakEffect": "Efeito de Quebra",
"effectRes": "RES a Efeito", "effectRes": "RES a Efeito",
"energyRegenerationRate": "Taxa de Regeneração de Energia", "energyRegenerationRate": "Taxa de Regeneração de Energia",
"effectHitRate": "Taxa de Acerto de Efeito", "effectHitRate": "Taxa de Acerto de Efeito",
"outgoingHealingBoost": "Bônus de Cura", "outgoingHealingBoost": "Bônus de Cura",
"fireDmgBoost": "Bônus de Dano de Fogo", "fireDmgBoost": "Bônus de Dano de Fogo",
"iceDmgBoost": "Bônus de Dano de Gelo", "iceDmgBoost": "Bônus de Dano de Gelo",
"imaginaryDmgBoost": "Bônus de Dano Imaginário", "imaginaryDmgBoost": "Bônus de Dano Imaginário",
"physicalDmgBoost": "Bônus de Dano Físico", "physicalDmgBoost": "Bônus de Dano Físico",
"quantumDmgBoost": "Bônus de Dano Quântico", "quantumDmgBoost": "Bônus de Dano Quântico",
"thunderDmgBoost": "Bônus de Dano de Raio", "thunderDmgBoost": "Bônus de Dano de Raio",
"windDmgBoost": "Bônus de Dano de Vento", "windDmgBoost": "Bônus de Dano de Vento",
"pursued": "Dano Adicional", "pursued": "Dano Adicional",
"true damage": "Dano Verdadeiro", "true damage": "Dano Verdadeiro",
"elationdamage": "Dano de Alegria", "elationdamage": "Dano de Alegria",
"follow-up": "Dano de Ataque Extra", "follow-up": "Dano de Ataque Extra",
"elemental damage": "Dano de Quebra e Superquebra", "elemental damage": "Dano de Quebra e Superquebra",
"dot": "Dano Contínuo", "dot": "Dano Contínuo",
"qte": "Habilidade QTE", "qte": "Habilidade QTE",
"level": "Nível", "level": "Nível",
"relics": "Relíquias", "relics": "Relíquias",
"eidolons": "Eidolons", "eidolons": "Eidolons",
"lightcones": "Cones de Luz", "lightcones": "Cones de Luz",
"loadData": "Carregar dados", "loadData": "Carregar dados",
"exportData": "Exportar dados", "exportData": "Exportar dados",
"connectSetting": "Configuração de Conexão", "connectSetting": "Configuração de Conexão",
"connected": "Conectado", "connected": "Conectado",
"unconnected": "Desconectado", "unconnected": "Desconectado",
"psConnection": "Conexão PS", "psConnection": "Conexão PS",
"connectionType": "Tipo de Conexão", "connectionType": "Tipo de Conexão",
"status": "Status", "status": "Status",
"connectPs": "Conectar PS", "connectPs": "Conectar PS",
"disconnect": "Desconectar", "disconnect": "Desconectar",
"other": "Outro", "other": "Outro",
"freeSr": "FreeSR", "freeSr": "FreeSR",
"database": "Banco de dados", "database": "Banco de dados",
"enka": "Enka", "enka": "Enka",
"monsterSetting": "Configuração de Monstros", "monsterSetting": "Configuração de Monstros",
"serverUrl": "URL do Servidor", "serverUrl": "URL do Servidor",
"privateType": "Tipo Privado", "privateType": "Tipo Privado",
"local": "Local", "local": "Local",
"server": "Servidor", "server": "Servidor",
"username": "Usuário", "username": "Usuário",
"password": "Senha", "password": "Senha",
"placeholderServerUrl": "Digite o URL do servidor", "placeholderServerUrl": "Digite o URL do servidor",
"placeholderUsername": "Digite o usuário", "placeholderUsername": "Digite o usuário",
"placeholderPassword": "Digite a senha", "placeholderPassword": "Digite a senha",
"connectedSuccess": "Conectado ao PS com sucesso", "connectedSuccess": "Conectado ao PS com sucesso",
"connectedFailed": "Falha ao conectar ao PS", "connectedFailed": "Falha ao conectar ao PS",
"syncSuccess": "Dados sincronizados com o PS com sucesso", "syncSuccess": "Dados sincronizados com o PS com sucesso",
"syncFailed": "Falha ao sincronizar dados com o PS", "syncFailed": "Falha ao sincronizar dados com o PS",
"sync": "Sincronizar", "sync": "Sincronizar",
"importSetting": "Configuração de Importação", "importSetting": "Configuração de Importação",
"profile": "Perfil", "profile": "Perfil",
"default": "Padrão", "default": "Padrão",
"copyProfiles": "Copiar perfis", "copyProfiles": "Copiar perfis",
"addNewProfile": "Adicionar novo perfil", "addNewProfile": "Adicionar novo perfil",
"createNewProfile": "Criar novo perfil", "createNewProfile": "Criar novo perfil",
"editProfile": "Editar perfil", "editProfile": "Editar perfil",
"placeholderProfileName": "Digite o nome do perfil", "placeholderProfileName": "Digite o nome do perfil",
"profileName": "Nome do perfil", "profileName": "Nome do perfil",
"create": "Criar", "create": "Criar",
"update": "Atualizar", "update": "Atualizar",
"characterInformation": "Informação do Personagem", "characterInformation": "Informação do Personagem",
"skills": "Habilidades", "skills": "Habilidades",
"showcaseCard": "Cartão de Exibição", "showcaseCard": "Cartão de Exibição",
"comingSoon": "Em breve", "comingSoon": "Em breve",
"characterName": "Nome do personagem", "characterName": "Nome do personagem",
"placeholderCharacter": "Digite o nome do personagem", "placeholderCharacter": "Digite o nome do personagem",
"characterSettings": "Configurações de Personagem", "characterSettings": "Configurações de Personagem",
"levelConfiguration": "Configuração de Nível", "levelConfiguration": "Configuração de Nível",
"characterLevel": "Nível do Personagem", "characterLevel": "Nível do Personagem",
"max": "MÁX", "max": "MÁX",
"ultimateEnergy": "Energia da Perícia Suprema", "ultimateEnergy": "Energia da Perícia Suprema",
"currentEnergy": "Energia Atual", "currentEnergy": "Energia Atual",
"setTo50": "Definir para 50%", "setTo50": "Definir para 50%",
"battleConfiguration": "Configuração de Batalha", "battleConfiguration": "Configuração de Batalha",
"useTechnique": "Usar Técnica", "useTechnique": "Usar Técnica",
"techniqueNote": "Ativar efeitos de técnica pré-batalha", "techniqueNote": "Ativar efeitos de técnica pré-batalha",
"enhancement": "Aprimoramento", "enhancement": "Aprimoramento",
"enhancementLevel": "Nível de Aprimoramento", "enhancementLevel": "Nível de Aprimoramento",
"origin": "Origem", "origin": "Origem",
"enhancedNote": "Níveis altos desbloqueiam habilidades", "enhancedNote": "Níveis altos desbloqueiam habilidades",
"lightconeEquipment": "Equipamento de Cone de Luz", "lightconeEquipment": "Equipamento de Cone de Luz",
"lightconeSettings": "Configurações de Cone de Luz", "lightconeSettings": "Configurações de Cone de Luz",
"placeholderLevel": "Digite o nível", "placeholderLevel": "Digite o nível",
"superimpositionRank": "Rank de Sobreposição", "superimpositionRank": "Rank de Sobreposição",
"ranksNote": "Ranks maiores dão efeitos mais fortes", "ranksNote": "Ranks maiores dão efeitos mais fortes",
"changeLightcone": "Mudar Cone de Luz", "changeLightcone": "Mudar Cone de Luz",
"removeLightcone": "Remover Cone de Luz", "removeLightcone": "Remover Cone de Luz",
"equipLightcone": "Equipar Cone de Luz", "equipLightcone": "Equipar Cone de Luz",
"noLightconeEquipped": "Sem Cone de Luz", "noLightconeEquipped": "Sem Cone de Luz",
"equipLightconeNote": "Equipe um cone de luz para melhorar o personagem", "equipLightconeNote": "Equipe um cone de luz para melhorar o personagem",
"filter": "Filtro", "filter": "Filtro",
"selectedCharacters": "Personagens Selecionados", "selectedCharacters": "Personagens Selecionados",
"selectedProfiles": "Perfis Selecionados", "selectedProfiles": "Perfis Selecionados",
"clearAll": "Limpar Tudo", "clearAll": "Limpar Tudo",
"selectAll": "Selecionar Tudo", "selectAll": "Selecionar Tudo",
"copy": "Copiar", "copy": "Copiar",
"copied": "Copiado", "copied": "Copiado",
"noAvatarSelected": "Nenhum personagem selecionado", "noAvatarSelected": "Nenhum personagem selecionado",
"noAvatarToCopySelected": "Nenhum personagem para copiar", "noAvatarToCopySelected": "Nenhum personagem para copiar",
"pleaseSelectAtLeastOneProfile": "Por favor, selecione ao menos um perfil", "pleaseSelectAtLeastOneProfile": "Por favor, selecione ao menos um perfil",
"pleaseEnterUid": "Por favor, insira o UID", "pleaseEnterUid": "Por favor, insira o UID",
"failedToFetchEnkaData": "Falha ao obter dados Enka", "failedToFetchEnkaData": "Falha ao obter dados Enka",
"pleaseSelectAtLeastOneCharacter": "Selecione ao menos um personagem", "pleaseSelectAtLeastOneCharacter": "Selecione ao menos um personagem",
"noDataToImport": "Sem dados para importar", "noDataToImport": "Sem dados para importar",
"pleaseSelectAFile": "Por favor, selecione um arquivo", "pleaseSelectAFile": "Por favor, selecione um arquivo",
"fileMustBeAValidJsonFile": "O arquivo deve ser um JSON válido", "fileMustBeAValidJsonFile": "O arquivo deve ser um JSON válido",
"importEnkaDataSuccess": "Dados Enka importados com sucesso", "importEnkaDataSuccess": "Dados Enka importados com sucesso",
"importFreeSRDataSuccess": "Dados FreeSR importados com sucesso", "importFreeSRDataSuccess": "Dados FreeSR importados com sucesso",
"importDatabaseSuccess": "Banco de dados importado com sucesso", "importDatabaseSuccess": "Banco de dados importado com sucesso",
"getData": "Obter Dados", "getData": "Obter Dados",
"import": "Importar", "import": "Importar",
"freeSRImport": "Importação FreeSR", "freeSRImport": "Importação FreeSR",
"onlySupportFreeSRJsonFile": "Suporta apenas arquivos JSON FreeSR", "onlySupportFreeSRJsonFile": "Suporta apenas arquivos JSON FreeSR",
"pickAFile": "Escolher um arquivo", "pickAFile": "Escolher um arquivo",
"lightConeSetting": "Configuração de Cone de Luz", "lightConeSetting": "Configuração de Cone de Luz",
"relicMaker": "Criador de Relíquias", "relicMaker": "Criador de Relíquias",
"pleaseSelectAllOptions": "Por favor selecione todas as opções", "pleaseSelectAllOptions": "Por favor selecione todas as opções",
"relicSavedSuccessfully": "Relíquia salva com sucesso", "relicSavedSuccessfully": "Relíquia salva com sucesso",
"mainSettings": "Configurações Principais", "mainSettings": "Configurações Principais",
"mainStat": "Atributo Principal", "mainStat": "Atributo Principal",
"set": "Conjunto", "set": "Conjunto",
"pleaseSelectASet": "Selecione um conjunto", "pleaseSelectASet": "Selecione um conjunto",
"effectBonus": "Bônus de Efeito", "effectBonus": "Bônus de Efeito",
"totalRoll": "Total de Melhorias", "totalRoll": "Total de Melhorias",
"randomizeStats": "Atributos Aleatórios", "randomizeStats": "Atributos Aleatórios",
"randomizeRolls": "Melhorias Aleatórias", "randomizeRolls": "Melhorias Aleatórias",
"selectASubStat": "Selecionar sub-atributo", "selectASubStat": "Selecionar sub-atributo",
"selectASet": "Selecionar conjunto", "selectASet": "Selecionar conjunto",
"selectAMainStat": "Selecionar atributo principal", "selectAMainStat": "Selecionar atributo principal",
"save": "Salvar", "save": "Salvar",
"reset": "Redefinir", "reset": "Redefinir",
"roll": "Melhoria", "roll": "Melhoria",
"step": "Passo", "step": "Passo",
"memoryOfChaos": "Memória do Caos", "memoryOfChaos": "Memória do Caos",
"pureFiction": "Pura Ficção", "pureFiction": "Pura Ficção",
"apocalypticShadow": "Sombra Apocalíptica", "apocalypticShadow": "Sombra Apocalíptica",
"customEnemy": "Inimigo Personalizado", "customEnemy": "Inimigo Personalizado",
"simulatedUniverse": "Universo Simulado", "simulatedUniverse": "Universo Simulado",
"floor": "Andar", "floor": "Andar",
"side": "Lado", "side": "Lado",
"wave": "Onda", "wave": "Onda",
"stage": "Fase", "stage": "Fase",
"useCycleCount": "Usar contagem de ciclos?", "useCycleCount": "Usar contagem de ciclos?",
"useTurbulenceBuff": "Usar buff de turbulência?", "useTurbulenceBuff": "Usar buff de turbulência?",
"firstHalfEnemies": "Inimigos da primeira metade", "firstHalfEnemies": "Inimigos da primeira metade",
"secondHalfEnemies": "Inimigos da segunda metade", "secondHalfEnemies": "Inimigos da segunda metade",
"firstNodeEnemies": "Inimigos do nodo 1", "firstNodeEnemies": "Inimigos do nodo 1",
"secondNodeEnemies": "Inimigos do nodo 2", "secondNodeEnemies": "Inimigos do nodo 2",
"thirdNodeEnemies": "Inimigos do nodo 3", "thirdNodeEnemies": "Inimigos do nodo 3",
"firstNode": "Nodo 1", "firstNode": "Nodo 1",
"secondNode": "Nodo 2", "secondNode": "Nodo 2",
"thirdNode": "Nodo 3", "thirdNode": "Nodo 3",
"listEnemies": "Lista de inimigos", "listEnemies": "Lista de inimigos",
"turbulenceBuff": "Buff de Turbulência", "turbulenceBuff": "Buff de Turbulência",
"noEventSelected": "Nenhum evento selecionado", "noEventSelected": "Nenhum evento selecionado",
"noTurbulenceBuff": "Sem Buff de Turbulência", "noTurbulenceBuff": "Sem Buff de Turbulência",
"upper": "Superior", "upper": "Superior",
"lower": "Inferior", "lower": "Inferior",
"upperToLower": "Superior -> Inferior", "upperToLower": "Superior -> Inferior",
"lowerToUpper": "Inferior -> Superior", "lowerToUpper": "Inferior -> Superior",
"selectMOCEvent": "Selecionar Evento MOC", "selectMOCEvent": "Selecionar Evento MOC",
"selectPFEvent": "Selecionar Evento PF", "selectPFEvent": "Selecionar Evento PF",
"selectASEvent": "Selecionar Evento AS", "selectASEvent": "Selecionar Evento AS",
"selectCEEvent": "Selecionar Evento CE", "selectCEEvent": "Selecionar Evento CE",
"selectEvent": "Selecionar Evento", "selectEvent": "Selecionar Evento",
"selectFloor": "Selecionar Andar", "selectFloor": "Selecionar Andar",
"selectSide": "Selecionar Lado", "selectSide": "Selecionar Lado",
"selectBuff": "Selecionar Buff", "selectBuff": "Selecionar Buff",
"selectStage": "Selecionar Fase", "selectStage": "Selecionar Fase",
"previous": "Anterior", "previous": "Anterior",
"next": "Próximo", "next": "Próximo",
"noMonstersFound": "Nenhum monstro encontrado", "noMonstersFound": "Nenhum monstro encontrado",
"addNewWave": "Adicionar Nova Onda", "addNewWave": "Adicionar Nova Onda",
"searchStage": "Buscar fase...", "searchStage": "Buscar fase...",
"noStageFound": "Fase não encontrada", "noStageFound": "Fase não encontrada",
"searchMonster": "Buscar monstro...", "searchMonster": "Buscar monstro...",
"changeRelic": "Mudar relíquia", "changeRelic": "Mudar relíquia",
"deleteRelic": "Deletar relíquia", "deleteRelic": "Deletar relíquia",
"deleteRelicConfirm": "Tem certeza que deseja deletar a relíquia neste slot?", "deleteRelicConfirm": "Tem certeza que deseja deletar a relíquia neste slot?",
"setEffects": "Definir Efeitos", "setEffects": "Definir Efeitos",
"details": "Detalhes", "details": "Detalhes",
"normal": "Ataque Básico", "normal": "Ataque Básico",
"bpskill": "Perícia", "bpskill": "Perícia",
"maze": "Técnica", "maze": "Técnica",
"ultra": "Perícia Suprema", "ultra": "Perícia Suprema",
"servantskill": "Habilidade do Memoespirito", "servantskill": "Habilidade do Memoespirito",
"severaltalent": "Talento do Memoespirito", "severaltalent": "Talento do Memoespirito",
"singleattack": "Ataque Único", "singleattack": "Ataque Único",
"enhance": "Aprimorar", "enhance": "Aprimorar",
"summon": "Invocar", "summon": "Invocar",
"mazeattack": "Ataque de Técnica", "mazeattack": "Ataque de Técnica",
"blast": "Explosão", "blast": "Explosão",
"restore": "Restaurar", "restore": "Restaurar",
"support": "Suporte", "support": "Suporte",
"aoeattack": "Ataque em Área", "aoeattack": "Ataque em Área",
"impair": "Enfraquecer", "impair": "Enfraquecer",
"bounce": "Rebote", "bounce": "Rebote",
"active": "Ativo", "active": "Ativo",
"defence": "Defesa", "defence": "Defesa",
"inactive": "Inativo", "inactive": "Inativo",
"maxAll": "Maximizar Tudo", "maxAll": "Maximizar Tudo",
"maxAllSuccess": "Habilidades maximizadas com sucesso.", "maxAllSuccess": "Habilidades maximizadas com sucesso.",
"maxAllFailed": "Falha ao maximizar habilidades.", "maxAllFailed": "Falha ao maximizar habilidades.",
"noRelicEquipped": "Sem relíquia equipada", "noRelicEquipped": "Sem relíquia equipada",
"anomalyArbitration": "Arbitragem de Anomalia", "anomalyArbitration": "Arbitragem de Anomalia",
"normalMode": "Modo Normal", "normalMode": "Modo Normal",
"hardMode": "Modo Difícil", "hardMode": "Modo Difícil",
"selectPEAKEvent": "Selecionar Evento PEAK", "selectPEAKEvent": "Selecionar Evento PEAK",
"mode": "Modo", "mode": "Modo",
"selectMode": "Selecionar um modo", "selectMode": "Selecionar um modo",
"rollBack": "Reverter", "rollBack": "Reverter",
"upRoll": "Aumentar Melhoria", "upRoll": "Aumentar Melhoria",
"downRoll": "Diminuir Melhoria", "downRoll": "Diminuir Melhoria",
"actions": "Ações", "actions": "Ações",
"avatars": "Avatares", "avatars": "Avatares",
"quickView": "Visualização Rápida", "quickView": "Visualização Rápida",
"extraSetting": "Configurações Extras", "extraSetting": "Configurações Extras",
"disableCensorship": "Desativar Censura", "disableCensorship": "Desativar Censura",
"hideUI": "Ocultar UI", "hideUI": "Ocultar UI",
"theoryCraftMode": "Modo Theorycraft", "theoryCraftMode": "Modo Theorycraft",
"cycleCount": "Contagem de Ciclos", "cycleCount": "Contagem de Ciclos",
"pleaseSelectAllSubStats": "Selecione todos os sub-atributos", "pleaseSelectAllSubStats": "Selecione todos os sub-atributos",
"subStatRollCountCannotBeZero": "As melhorias do sub-atributo não podem ser zero", "subStatRollCountCannotBeZero": "As melhorias do sub-atributo não podem ser zero",
"theoryCraft": "Theorycraft", "theoryCraft": "Theorycraft",
"multipathCharacter": "Personagem Multicaminho", "multipathCharacter": "Personagem Multicaminho",
"mainPath": "Caminho Principal", "mainPath": "Caminho Principal",
"march7Path": "Caminho de 7 de Março", "march7Path": "Caminho de 7 de Março",
"challenge": "Desafio", "challenge": "Desafio",
"skipNode": "Pular Nó", "skipNode": "Pular Nó",
"disableSkip": "Desativar pulo", "disableSkip": "Desativar pulo",
"skipNode1": "Pular nó 1", "skipNode1": "Pular nó 1",
"skipNode2": "Pular nó 2", "skipNode2": "Pular nó 2",
"extraFeatures": "Recursos Extras", "extraFeatures": "Recursos Extras",
"detailTheoryCraft": "Ativar isso permite personalizar os ciclos e ajustar o HP dos inimigos.", "detailTheoryCraft": "Ativar isso permite personalizar os ciclos e ajustar o HP dos inimigos.",
"detailSkipNode": "Permite pular (Nó 1/Nó 2) em Memória do Caos ou Pura Ficção.", "detailSkipNode": "Permite pular (Nó 1/Nó 2) em Memória do Caos ou Pura Ficção.",
"detailChallengePeak": "Permite mudar a temporada de Peak na Anomalia atual.", "detailChallengePeak": "Permite mudar a temporada de Peak na Anomalia atual.",
"detailHiddenUi": "Irá esconder a interface do jogo.", "detailHiddenUi": "Irá esconder a interface do jogo.",
"detailDisableCensorship": "Desativa a censura no jogo.", "detailDisableCensorship": "Desativa a censura no jogo.",
"detailMultipathCharacter": "Permite mudar o Caminho de certos personagens.", "detailMultipathCharacter": "Permite mudar o Caminho de certos personagens.",
"trailblazer": "Desbravador", "trailblazer": "Desbravador",
"listExtraEffect": "Lista de Efeitos Extras", "listExtraEffect": "Lista de Efeitos Extras",
"extra": "Extra", "extra": "Extra",
"customLineup": "Formação Personalizada" "customLineup": "Formação Personalizada"
} }
} }
+289 -289
View File
@@ -1,290 +1,290 @@
{ {
"TabTitle": { "TabTitle": {
"title": "Firefly Tools", "title": "Firefly Tools",
"description": "Инструменты Firefly от Firefly Shelter" "description": "Инструменты Firefly от Firefly Shelter"
}, },
"DataPage": { "DataPage": {
"skillType": "Тип навыка", "skillType": "Тип навыка",
"skillName": "Название навыка", "skillName": "Название навыка",
"character": "Персонаж", "character": "Персонаж",
"id": "ID", "id": "ID",
"path": "Путь", "path": "Путь",
"rarity": "Редкость", "rarity": "Редкость",
"element": "Элемент", "element": "Элемент",
"technique": "Техника", "technique": "Техника",
"talent": "Талант", "talent": "Талант",
"basic": "Базовая атака", "basic": "Базовая атака",
"skill": "Навык", "skill": "Навык",
"ultimate": "Сверхспособность", "ultimate": "Сверхспособность",
"servant": "Слуга", "servant": "Слуга",
"damage": "Урон", "damage": "Урон",
"type": "Тип", "type": "Тип",
"warrior": "Разрушение", "warrior": "Разрушение",
"knight": "Сохранение", "knight": "Сохранение",
"mage": "Эрудиция", "mage": "Эрудиция",
"priest": "Изобилие", "priest": "Изобилие",
"rogue": "Охота", "rogue": "Охота",
"shaman": "Гармония", "shaman": "Гармония",
"warlock": "Небытие", "warlock": "Небытие",
"memory": "Память", "memory": "Память",
"elation": "Радость", "elation": "Радость",
"fire": "Огонь", "fire": "Огонь",
"ice": "Лед", "ice": "Лед",
"imaginary": "Мнимый", "imaginary": "Мнимый",
"physical": "Физический", "physical": "Физический",
"quantum": "Квантовый", "quantum": "Квантовый",
"thunder": "Электрический", "thunder": "Электрический",
"wind": "Ветряной", "wind": "Ветряной",
"hp": "HP", "hp": "HP",
"atk": "Сила атаки", "atk": "Сила атаки",
"speed": "Скорость", "speed": "Скорость",
"critRate": "Крит. шанс", "critRate": "Крит. шанс",
"critDmg": "Крит. урон", "critDmg": "Крит. урон",
"breakEffect": "Эффект пробития", "breakEffect": "Эффект пробития",
"effectRes": "Сопротивление эффектам", "effectRes": "Сопротивление эффектам",
"energyRegenerationRate": "Скорость восст. энергии", "energyRegenerationRate": "Скорость восст. энергии",
"effectHitRate": "Шанс попадания эффектов", "effectHitRate": "Шанс попадания эффектов",
"outgoingHealingBoost": "Бонус исходящего исцеления", "outgoingHealingBoost": "Бонус исходящего исцеления",
"fireDmgBoost": "Бонус огненного урона", "fireDmgBoost": "Бонус огненного урона",
"iceDmgBoost": "Бонус ледяного урона", "iceDmgBoost": "Бонус ледяного урона",
"imaginaryDmgBoost": "Бонус мнимого урона", "imaginaryDmgBoost": "Бонус мнимого урона",
"physicalDmgBoost": "Бонус физ. урона", "physicalDmgBoost": "Бонус физ. урона",
"quantumDmgBoost": "Бонус квантового урона", "quantumDmgBoost": "Бонус квантового урона",
"thunderDmgBoost": "Бонус электр. урона", "thunderDmgBoost": "Бонус электр. урона",
"windDmgBoost": "Бонус ветряного урона", "windDmgBoost": "Бонус ветряного урона",
"pursued": "Дополнительный урон", "pursued": "Дополнительный урон",
"true damage": "Истинный урон", "true damage": "Истинный урон",
"elationdamage": "Урон радости", "elationdamage": "Урон радости",
"follow-up": "Урон бонус-атаки", "follow-up": "Урон бонус-атаки",
"elemental damage": "Урон пробития и суперпробития", "elemental damage": "Урон пробития и суперпробития",
"dot": "Периодический урон", "dot": "Периодический урон",
"qte": "QTE Навык", "qte": "QTE Навык",
"level": "Уровень", "level": "Уровень",
"relics": "Реликвии", "relics": "Реликвии",
"eidolons": "Эйдолоны", "eidolons": "Эйдолоны",
"lightcones": "Световые конусы", "lightcones": "Световые конусы",
"loadData": "Загрузить данные", "loadData": "Загрузить данные",
"exportData": "Экспортировать данные", "exportData": "Экспортировать данные",
"connectSetting": "Настройки подключения", "connectSetting": "Настройки подключения",
"connected": "Подключено", "connected": "Подключено",
"unconnected": "Не подключено", "unconnected": "Не подключено",
"psConnection": "Подключение к PS", "psConnection": "Подключение к PS",
"connectionType": "Тип подключения", "connectionType": "Тип подключения",
"status": "Статус", "status": "Статус",
"connectPs": "Подключить PS", "connectPs": "Подключить PS",
"disconnect": "Отключить", "disconnect": "Отключить",
"other": "Другое", "other": "Другое",
"freeSr": "FreeSR", "freeSr": "FreeSR",
"database": "База данных", "database": "База данных",
"enka": "Enka", "enka": "Enka",
"monsterSetting": "Настройки монстров", "monsterSetting": "Настройки монстров",
"serverUrl": "URL сервера", "serverUrl": "URL сервера",
"privateType": "Приватный тип", "privateType": "Приватный тип",
"local": "Локальный", "local": "Локальный",
"server": "Сервер", "server": "Сервер",
"username": "Имя пользователя", "username": "Имя пользователя",
"password": "Пароль", "password": "Пароль",
"placeholderServerUrl": "Введите URL сервера", "placeholderServerUrl": "Введите URL сервера",
"placeholderUsername": "Введите имя пользователя", "placeholderUsername": "Введите имя пользователя",
"placeholderPassword": "Введите пароль", "placeholderPassword": "Введите пароль",
"connectedSuccess": "Успешное подключение к PS", "connectedSuccess": "Успешное подключение к PS",
"connectedFailed": "Ошибка подключения к PS", "connectedFailed": "Ошибка подключения к PS",
"syncSuccess": "Данные успешно синхронизированы с PS", "syncSuccess": "Данные успешно синхронизированы с PS",
"syncFailed": "Ошибка синхронизации данных", "syncFailed": "Ошибка синхронизации данных",
"sync": "Синхронизация", "sync": "Синхронизация",
"importSetting": "Настройки импорта", "importSetting": "Настройки импорта",
"profile": "Профиль", "profile": "Профиль",
"default": "По умолчанию", "default": "По умолчанию",
"copyProfiles": "Копировать профили", "copyProfiles": "Копировать профили",
"addNewProfile": "Добавить новый профиль", "addNewProfile": "Добавить новый профиль",
"createNewProfile": "Создать новый профиль", "createNewProfile": "Создать новый профиль",
"editProfile": "Редактировать профиль", "editProfile": "Редактировать профиль",
"placeholderProfileName": "Введите имя профиля", "placeholderProfileName": "Введите имя профиля",
"profileName": "Имя профиля", "profileName": "Имя профиля",
"create": "Создать", "create": "Создать",
"update": "Обновить", "update": "Обновить",
"characterInformation": "Информация о персонаже", "characterInformation": "Информация о персонаже",
"skills": "Навыки", "skills": "Навыки",
"showcaseCard": "Карточка персонажа", "showcaseCard": "Карточка персонажа",
"comingSoon": "Скоро", "comingSoon": "Скоро",
"characterName": "Имя персонажа", "characterName": "Имя персонажа",
"placeholderCharacter": "Введите имя персонажа", "placeholderCharacter": "Введите имя персонажа",
"characterSettings": "Настройки персонажа", "characterSettings": "Настройки персонажа",
"levelConfiguration": "Конфигурация уровня", "levelConfiguration": "Конфигурация уровня",
"characterLevel": "Уровень персонажа", "characterLevel": "Уровень персонажа",
"max": "МАКС", "max": "МАКС",
"ultimateEnergy": "Энергия сверхспособности", "ultimateEnergy": "Энергия сверхспособности",
"currentEnergy": "Текущая энергия", "currentEnergy": "Текущая энергия",
"setTo50": "Установить на 50%", "setTo50": "Установить на 50%",
"battleConfiguration": "Конфигурация боя", "battleConfiguration": "Конфигурация боя",
"useTechnique": "Использовать технику", "useTechnique": "Использовать технику",
"techniqueNote": "Включить эффекты техники до боя", "techniqueNote": "Включить эффекты техники до боя",
"enhancement": "Усиление", "enhancement": "Усиление",
"enhancementLevel": "Уровень усиления", "enhancementLevel": "Уровень усиления",
"origin": "Оригинал", "origin": "Оригинал",
"enhancedNote": "Высокие уровни открывают новые способности", "enhancedNote": "Высокие уровни открывают новые способности",
"lightconeEquipment": "Снаряжение конуса", "lightconeEquipment": "Снаряжение конуса",
"lightconeSettings": "Настройки светового конуса", "lightconeSettings": "Настройки светового конуса",
"placeholderLevel": "Введите уровень", "placeholderLevel": "Введите уровень",
"superimpositionRank": "Уровень наложения", "superimpositionRank": "Уровень наложения",
"ranksNote": "Высокие ранги дают более сильные эффекты", "ranksNote": "Высокие ранги дают более сильные эффекты",
"changeLightcone": "Изменить световой конус", "changeLightcone": "Изменить световой конус",
"removeLightcone": "Снять световой конус", "removeLightcone": "Снять световой конус",
"equipLightcone": "Экипировать конус", "equipLightcone": "Экипировать конус",
"noLightconeEquipped": "Световой конус не экипирован", "noLightconeEquipped": "Световой конус не экипирован",
"equipLightconeNote": "Наденьте конус для усиления персонажа", "equipLightconeNote": "Наденьте конус для усиления персонажа",
"filter": "Фильтр", "filter": "Фильтр",
"selectedCharacters": "Выбранные персонажи", "selectedCharacters": "Выбранные персонажи",
"selectedProfiles": "Выбранные профили", "selectedProfiles": "Выбранные профили",
"clearAll": "Очистить всё", "clearAll": "Очистить всё",
"selectAll": "Выбрать всё", "selectAll": "Выбрать всё",
"copy": "Копировать", "copy": "Копировать",
"copied": "Скопировано", "copied": "Скопировано",
"noAvatarSelected": "Персонаж не выбран", "noAvatarSelected": "Персонаж не выбран",
"noAvatarToCopySelected": "Нет персонажа для копирования", "noAvatarToCopySelected": "Нет персонажа для копирования",
"pleaseSelectAtLeastOneProfile": "Пожалуйста, выберите хотя бы один профиль", "pleaseSelectAtLeastOneProfile": "Пожалуйста, выберите хотя бы один профиль",
"pleaseEnterUid": "Пожалуйста, введите UID", "pleaseEnterUid": "Пожалуйста, введите UID",
"failedToFetchEnkaData": "Не удалось получить данные Enka", "failedToFetchEnkaData": "Не удалось получить данные Enka",
"pleaseSelectAtLeastOneCharacter": "Пожалуйста, выберите хотя бы одного персонажа", "pleaseSelectAtLeastOneCharacter": "Пожалуйста, выберите хотя бы одного персонажа",
"noDataToImport": "Нет данных для импорта", "noDataToImport": "Нет данных для импорта",
"pleaseSelectAFile": "Пожалуйста, выберите файл", "pleaseSelectAFile": "Пожалуйста, выберите файл",
"fileMustBeAValidJsonFile": "Файл должен быть в формате JSON", "fileMustBeAValidJsonFile": "Файл должен быть в формате JSON",
"importEnkaDataSuccess": "Успешный импорт из Enka", "importEnkaDataSuccess": "Успешный импорт из Enka",
"importFreeSRDataSuccess": "Успешный импорт из FreeSR", "importFreeSRDataSuccess": "Успешный импорт из FreeSR",
"importDatabaseSuccess": "Успешный импорт базы данных", "importDatabaseSuccess": "Успешный импорт базы данных",
"getData": "Получить данные", "getData": "Получить данные",
"import": "Импорт", "import": "Импорт",
"freeSRImport": "Импорт FreeSR", "freeSRImport": "Импорт FreeSR",
"onlySupportFreeSRJsonFile": "Поддерживаются только JSON от FreeSR", "onlySupportFreeSRJsonFile": "Поддерживаются только JSON от FreeSR",
"pickAFile": "Выберите файл", "pickAFile": "Выберите файл",
"lightConeSetting": "Настройки конуса", "lightConeSetting": "Настройки конуса",
"relicMaker": "Создатель реликвий", "relicMaker": "Создатель реликвий",
"pleaseSelectAllOptions": "Пожалуйста, выберите все параметры", "pleaseSelectAllOptions": "Пожалуйста, выберите все параметры",
"relicSavedSuccessfully": "Реликвия успешно сохранена", "relicSavedSuccessfully": "Реликвия успешно сохранена",
"mainSettings": "Основные настройки", "mainSettings": "Основные настройки",
"mainStat": "Основная характеристика", "mainStat": "Основная характеристика",
"set": "Набор", "set": "Набор",
"pleaseSelectASet": "Выберите набор", "pleaseSelectASet": "Выберите набор",
"effectBonus": "Бонус эффекта", "effectBonus": "Бонус эффекта",
"totalRoll": "Всего улучшений", "totalRoll": "Всего улучшений",
"randomizeStats": "Случайные статы", "randomizeStats": "Случайные статы",
"randomizeRolls": "Случайные улучшения", "randomizeRolls": "Случайные улучшения",
"selectASubStat": "Выберите доп. стат", "selectASubStat": "Выберите доп. стат",
"selectASet": "Выберите набор", "selectASet": "Выберите набор",
"selectAMainStat": "Выберите основной стат", "selectAMainStat": "Выберите основной стат",
"save": "Сохранить", "save": "Сохранить",
"reset": "Сброс", "reset": "Сброс",
"roll": "Улучшение", "roll": "Улучшение",
"step": "Шаг", "step": "Шаг",
"memoryOfChaos": "Зал забвения", "memoryOfChaos": "Зал забвения",
"pureFiction": "Чистый вымысел", "pureFiction": "Чистый вымысел",
"apocalypticShadow": "Апокалиптическая тень", "apocalypticShadow": "Апокалиптическая тень",
"customEnemy": "Свой противник", "customEnemy": "Свой противник",
"simulatedUniverse": "Виртуальная вселенная", "simulatedUniverse": "Виртуальная вселенная",
"floor": "Этаж", "floor": "Этаж",
"side": "Половина", "side": "Половина",
"wave": "Волна", "wave": "Волна",
"stage": "Стадия", "stage": "Стадия",
"useCycleCount": "Использовать счетчик циклов?", "useCycleCount": "Использовать счетчик циклов?",
"useTurbulenceBuff": "Использовать бафф турбулентности?", "useTurbulenceBuff": "Использовать бафф турбулентности?",
"firstHalfEnemies": "Враги первой половины", "firstHalfEnemies": "Враги первой половины",
"secondHalfEnemies": "Враги второй половины", "secondHalfEnemies": "Враги второй половины",
"firstNodeEnemies": "Враги узла 1", "firstNodeEnemies": "Враги узла 1",
"secondNodeEnemies": "Враги узла 2", "secondNodeEnemies": "Враги узла 2",
"thirdNodeEnemies": "Враги узла 3", "thirdNodeEnemies": "Враги узла 3",
"firstNode": "Узел 1", "firstNode": "Узел 1",
"secondNode": "Узел 2", "secondNode": "Узел 2",
"thirdNode": "Узел 3", "thirdNode": "Узел 3",
"listEnemies": "Список врагов", "listEnemies": "Список врагов",
"turbulenceBuff": "Бафф турбулентности", "turbulenceBuff": "Бафф турбулентности",
"noEventSelected": "Событие не выбрано", "noEventSelected": "Событие не выбрано",
"noTurbulenceBuff": "Нет баффа турбулентности", "noTurbulenceBuff": "Нет баффа турбулентности",
"upper": "Верх", "upper": "Верх",
"lower": "Низ", "lower": "Низ",
"upperToLower": "Верх -> Низ", "upperToLower": "Верх -> Низ",
"lowerToUpper": "Низ -> Верх", "lowerToUpper": "Низ -> Верх",
"selectMOCEvent": "Выбрать событие MOC", "selectMOCEvent": "Выбрать событие MOC",
"selectPFEvent": "Выбрать событие PF", "selectPFEvent": "Выбрать событие PF",
"selectASEvent": "Выбрать событие AS", "selectASEvent": "Выбрать событие AS",
"selectCEEvent": "Выбрать событие CE", "selectCEEvent": "Выбрать событие CE",
"selectEvent": "Выбрать событие", "selectEvent": "Выбрать событие",
"selectFloor": "Выбрать этаж", "selectFloor": "Выбрать этаж",
"selectSide": "Выбрать половину", "selectSide": "Выбрать половину",
"selectBuff": "Выбрать бафф", "selectBuff": "Выбрать бафф",
"selectStage": "Выбрать стадию", "selectStage": "Выбрать стадию",
"previous": "Назад", "previous": "Назад",
"next": "Вперед", "next": "Вперед",
"noMonstersFound": "Монстры не найдены", "noMonstersFound": "Монстры не найдены",
"addNewWave": "Добавить новую волну", "addNewWave": "Добавить новую волну",
"searchStage": "Поиск стадии...", "searchStage": "Поиск стадии...",
"noStageFound": "Стадия не найдена", "noStageFound": "Стадия не найдена",
"searchMonster": "Поиск монстра...", "searchMonster": "Поиск монстра...",
"changeRelic": "Изменить реликвию", "changeRelic": "Изменить реликвию",
"deleteRelic": "Удалить реликвию", "deleteRelic": "Удалить реликвию",
"deleteRelicConfirm": "Удалить реликвию в этом слоте?", "deleteRelicConfirm": "Удалить реликвию в этом слоте?",
"setEffects": "Настроить эффекты", "setEffects": "Настроить эффекты",
"details": "Детали", "details": "Детали",
"normal": "Базовая атака", "normal": "Базовая атака",
"bpskill": "Навык", "bpskill": "Навык",
"maze": "Техника", "maze": "Техника",
"ultra": "Сверхспособность", "ultra": "Сверхспособность",
"servantskill": "Навык мемоспрайта", "servantskill": "Навык мемоспрайта",
"severaltalent": "Талант мемоспрайта", "severaltalent": "Талант мемоспрайта",
"singleattack": "Одиночная атака", "singleattack": "Одиночная атака",
"enhance": "Усиление", "enhance": "Усиление",
"summon": "Призыв", "summon": "Призыв",
"mazeattack": "Атака в технике", "mazeattack": "Атака в технике",
"blast": "Взрыв", "blast": "Взрыв",
"restore": "Восстановление", "restore": "Восстановление",
"support": "Поддержка", "support": "Поддержка",
"aoeattack": "АоЕ атака", "aoeattack": "АоЕ атака",
"impair": "Ослабление", "impair": "Ослабление",
"bounce": "Отскок", "bounce": "Отскок",
"active": "Активен", "active": "Активен",
"defence": "Защита", "defence": "Защита",
"inactive": "Неактивен", "inactive": "Неактивен",
"maxAll": "Улучшить всё", "maxAll": "Улучшить всё",
"maxAllSuccess": "Уровни навыков улучшены до максимума.", "maxAllSuccess": "Уровни навыков улучшены до максимума.",
"maxAllFailed": "Не удалось улучшить навыки.", "maxAllFailed": "Не удалось улучшить навыки.",
"noRelicEquipped": "Нет экипированных реликвий", "noRelicEquipped": "Нет экипированных реликвий",
"anomalyArbitration": "Аномальный арбитраж", "anomalyArbitration": "Аномальный арбитраж",
"normalMode": "Обычный режим", "normalMode": "Обычный режим",
"hardMode": "Сложный режим", "hardMode": "Сложный режим",
"selectPEAKEvent": "Выбрать событие PEAK", "selectPEAKEvent": "Выбрать событие PEAK",
"mode": "Режим", "mode": "Режим",
"selectMode": "Выбрать режим", "selectMode": "Выбрать режим",
"rollBack": "Отменить шаг", "rollBack": "Отменить шаг",
"upRoll": "Повысить улучшение", "upRoll": "Повысить улучшение",
"downRoll": "Понизить улучшение", "downRoll": "Понизить улучшение",
"actions": "Действия", "actions": "Действия",
"avatars": "Персонажи", "avatars": "Персонажи",
"quickView": "Быстрый просмотр", "quickView": "Быстрый просмотр",
"extraSetting": "Дополнительные настройки", "extraSetting": "Дополнительные настройки",
"disableCensorship": "Отключить цензуру", "disableCensorship": "Отключить цензуру",
"hideUI": "Скрыть интерфейс", "hideUI": "Скрыть интерфейс",
"theoryCraftMode": "Режим Theorycraft", "theoryCraftMode": "Режим Theorycraft",
"cycleCount": "Счетчик циклов", "cycleCount": "Счетчик циклов",
"pleaseSelectAllSubStats": "Пожалуйста, выберите все саб-статы", "pleaseSelectAllSubStats": "Пожалуйста, выберите все саб-статы",
"subStatRollCountCannotBeZero": "Количество улучшений саб-стата не может быть нулем", "subStatRollCountCannotBeZero": "Количество улучшений саб-стата не может быть нулем",
"theoryCraft": "Theorycraft", "theoryCraft": "Theorycraft",
"multipathCharacter": "Многопутевой персонаж", "multipathCharacter": "Многопутевой персонаж",
"mainPath": "Основной Путь", "mainPath": "Основной Путь",
"march7Path": "Путь Март 7", "march7Path": "Путь Март 7",
"challenge": "Испытание", "challenge": "Испытание",
"skipNode": "Пропустить узел", "skipNode": "Пропустить узел",
"disableSkip": "Отключить пропуск", "disableSkip": "Отключить пропуск",
"skipNode1": "Пропустить узел 1", "skipNode1": "Пропустить узел 1",
"skipNode2": "Пропустить узел 2", "skipNode2": "Пропустить узел 2",
"extraFeatures": "Дополнительные функции", "extraFeatures": "Дополнительные функции",
"detailTheoryCraft": "Включение этой функции позволяет настроить количество циклов и здоровье противников.", "detailTheoryCraft": "Включение этой функции позволяет настроить количество циклов и здоровье противников.",
"detailSkipNode": "Позволяет пропустить (Узел 1/Узел 2) в Зале забвения или Чистом вымысле.", "detailSkipNode": "Позволяет пропустить (Узел 1/Узел 2) в Зале забвения или Чистом вымысле.",
"detailChallengePeak": "Позволяет изменить сезон Peak в текущей Аномалии.", "detailChallengePeak": "Позволяет изменить сезон Peak в текущей Аномалии.",
"detailHiddenUi": "Скрывает игровой интерфейс.", "detailHiddenUi": "Скрывает игровой интерфейс.",
"detailDisableCensorship": "Отключает внутриигровую цензуру.", "detailDisableCensorship": "Отключает внутриигровую цензуру.",
"detailMultipathCharacter": "Позволяет изменить Путь некоторых персонажей.", "detailMultipathCharacter": "Позволяет изменить Путь некоторых персонажей.",
"trailblazer": "Первопроходец", "trailblazer": "Первопроходец",
"listExtraEffect": "Список доп. эффектов", "listExtraEffect": "Список доп. эффектов",
"extra": "Экстра", "extra": "Экстра",
"customLineup": "Пользовательский отряд" "customLineup": "Пользовательский отряд"
} }
} }
+289 -289
View File
@@ -1,290 +1,290 @@
{ {
"TabTitle": { "TabTitle": {
"title": "Firefly Tools", "title": "Firefly Tools",
"description": "เครื่องมือ Firefly โดย Firefly Shelter" "description": "เครื่องมือ Firefly โดย Firefly Shelter"
}, },
"DataPage": { "DataPage": {
"skillType": "ประเภทสกิล", "skillType": "ประเภทสกิล",
"skillName": "ชื่อสกิล", "skillName": "ชื่อสกิล",
"character": "ตัวละคร", "character": "ตัวละคร",
"id": "ไอดี", "id": "ไอดี",
"path": "Path", "path": "Path",
"rarity": "ระดับความหายาก", "rarity": "ระดับความหายาก",
"element": "ธาตุ", "element": "ธาตุ",
"technique": "เทคนิค", "technique": "เทคนิค",
"talent": "พรสวรรค์", "talent": "พรสวรรค์",
"basic": "โจมตีปกติ", "basic": "โจมตีปกติ",
"skill": "สกิลต่อสู้", "skill": "สกิลต่อสู้",
"ultimate": "ท่าไม้ตาย", "ultimate": "ท่าไม้ตาย",
"servant": "ผู้ติดตาม", "servant": "ผู้ติดตาม",
"damage": "ความเสียหาย", "damage": "ความเสียหาย",
"type": "ประเภท", "type": "ประเภท",
"warrior": "ทำลายล้าง", "warrior": "ทำลายล้าง",
"knight": "อนุรักษ์", "knight": "อนุรักษ์",
"mage": "ปัญญา", "mage": "ปัญญา",
"priest": "เฟื่องฟู", "priest": "เฟื่องฟู",
"rogue": "ล่าสังหาร", "rogue": "ล่าสังหาร",
"shaman": "ประสาน", "shaman": "ประสาน",
"warlock": "ลบล้าง", "warlock": "ลบล้าง",
"memory": "ลบล้าง", "memory": "ลบล้าง",
"elation": "ปิติสุข", "elation": "ปิติสุข",
"fire": "ไฟ", "fire": "ไฟ",
"ice": "น้ำแข็ง", "ice": "น้ำแข็ง",
"imaginary": "จินตภาพ", "imaginary": "จินตภาพ",
"physical": "กายภาพ", "physical": "กายภาพ",
"quantum": "ควอนตัม", "quantum": "ควอนตัม",
"thunder": "สายฟ้า", "thunder": "สายฟ้า",
"wind": "ลม", "wind": "ลม",
"hp": "HP", "hp": "HP",
"atk": "ATK", "atk": "ATK",
"speed": "ความเร็ว", "speed": "ความเร็ว",
"critRate": "อัตราคริติคอล", "critRate": "อัตราคริติคอล",
"critDmg": "ความเสียหายคริติคอล", "critDmg": "ความเสียหายคริติคอล",
"breakEffect": "เอฟเฟกต์ทำลายล้าง", "breakEffect": "เอฟเฟกต์ทำลายล้าง",
"effectRes": "ต้านทานสถานะ", "effectRes": "ต้านทานสถานะ",
"energyRegenerationRate": "อัตราฟื้นฟูพลังงาน", "energyRegenerationRate": "อัตราฟื้นฟูพลังงาน",
"effectHitRate": "อัตราสร้างสถานะ", "effectHitRate": "อัตราสร้างสถานะ",
"outgoingHealingBoost": "โบนัสการรักษา", "outgoingHealingBoost": "โบนัสการรักษา",
"fireDmgBoost": "โบนัสความเสียหายไฟ", "fireDmgBoost": "โบนัสความเสียหายไฟ",
"iceDmgBoost": "โบนัสความเสียหายน้ำแข็ง", "iceDmgBoost": "โบนัสความเสียหายน้ำแข็ง",
"imaginaryDmgBoost": "โบนัสความเสียหายจินตภาพ", "imaginaryDmgBoost": "โบนัสความเสียหายจินตภาพ",
"physicalDmgBoost": "โบนัสความเสียหายกายภาพ", "physicalDmgBoost": "โบนัสความเสียหายกายภาพ",
"quantumDmgBoost": "โบนัสความเสียหายควอนตัม", "quantumDmgBoost": "โบนัสความเสียหายควอนตัม",
"thunderDmgBoost": "โบนัสความเสียหายสายฟ้า", "thunderDmgBoost": "โบนัสความเสียหายสายฟ้า",
"windDmgBoost": "โบนัสความเสียหายลม", "windDmgBoost": "โบนัสความเสียหายลม",
"pursued": "ความเสียหายเพิ่มเติม", "pursued": "ความเสียหายเพิ่มเติม",
"true damage": "ความเสียหายจริง", "true damage": "ความเสียหายจริง",
"elationdamage": "ความเสียหายปิติสุข", "elationdamage": "ความเสียหายปิติสุข",
"follow-up": "ความเสียหายโจมตีต่อเนื่อง", "follow-up": "ความเสียหายโจมตีต่อเนื่อง",
"elemental damage": "ความเสียหายทำลายล้างและซูเปอร์", "elemental damage": "ความเสียหายทำลายล้างและซูเปอร์",
"dot": "ความเสียหายต่อเนื่อง", "dot": "ความเสียหายต่อเนื่อง",
"qte": "สกิล QTE", "qte": "สกิล QTE",
"level": "เลเวล", "level": "เลเวล",
"relics": "รีลิกส์", "relics": "รีลิกส์",
"eidolons": "Eidolon", "eidolons": "Eidolon",
"lightcones": "Light Cone", "lightcones": "Light Cone",
"loadData": "โหลดข้อมูล", "loadData": "โหลดข้อมูล",
"exportData": "ส่งออกข้อมูล", "exportData": "ส่งออกข้อมูล",
"connectSetting": "การตั้งค่าการเชื่อมต่อ", "connectSetting": "การตั้งค่าการเชื่อมต่อ",
"connected": "เชื่อมต่อแล้ว", "connected": "เชื่อมต่อแล้ว",
"unconnected": "ไม่ได้เชื่อมต่อ", "unconnected": "ไม่ได้เชื่อมต่อ",
"psConnection": "การเชื่อมต่อ PS", "psConnection": "การเชื่อมต่อ PS",
"connectionType": "ประเภทการเชื่อมต่อ", "connectionType": "ประเภทการเชื่อมต่อ",
"status": "สถานะ", "status": "สถานะ",
"connectPs": "เชื่อมต่อ PS", "connectPs": "เชื่อมต่อ PS",
"disconnect": "ยกเลิกการเชื่อมต่อ", "disconnect": "ยกเลิกการเชื่อมต่อ",
"other": "อื่นๆ", "other": "อื่นๆ",
"freeSr": "FreeSR", "freeSr": "FreeSR",
"database": "ฐานข้อมูล", "database": "ฐานข้อมูล",
"enka": "Enka", "enka": "Enka",
"monsterSetting": "ตั้งค่ามอนสเตอร์", "monsterSetting": "ตั้งค่ามอนสเตอร์",
"serverUrl": "URL เซิร์ฟเวอร์", "serverUrl": "URL เซิร์ฟเวอร์",
"privateType": "ประเภทเซิร์ฟเวอร์ส่วนตัว", "privateType": "ประเภทเซิร์ฟเวอร์ส่วนตัว",
"local": "ในเครื่อง", "local": "ในเครื่อง",
"server": "เซิร์ฟเวอร์", "server": "เซิร์ฟเวอร์",
"username": "ชื่อผู้ใช้", "username": "ชื่อผู้ใช้",
"password": "รหัสผ่าน", "password": "รหัสผ่าน",
"placeholderServerUrl": "ป้อน URL เซิร์ฟเวอร์", "placeholderServerUrl": "ป้อน URL เซิร์ฟเวอร์",
"placeholderUsername": "ป้อนชื่อผู้ใช้", "placeholderUsername": "ป้อนชื่อผู้ใช้",
"placeholderPassword": "ป้อนรหัสผ่าน", "placeholderPassword": "ป้อนรหัสผ่าน",
"connectedSuccess": "เชื่อมต่อกับ PS สำเร็จ", "connectedSuccess": "เชื่อมต่อกับ PS สำเร็จ",
"connectedFailed": "ล้มเหลวในการเชื่อมต่อกับ PS", "connectedFailed": "ล้มเหลวในการเชื่อมต่อกับ PS",
"syncSuccess": "ซิงค์ข้อมูลกับ PS สำเร็จ", "syncSuccess": "ซิงค์ข้อมูลกับ PS สำเร็จ",
"syncFailed": "ล้มเหลวในการซิงค์ข้อมูลกับ PS", "syncFailed": "ล้มเหลวในการซิงค์ข้อมูลกับ PS",
"sync": "ซิงค์", "sync": "ซิงค์",
"importSetting": "การตั้งค่าการนำเข้า", "importSetting": "การตั้งค่าการนำเข้า",
"profile": "โปรไฟล์", "profile": "โปรไฟล์",
"default": "ค่าเริ่มต้น", "default": "ค่าเริ่มต้น",
"copyProfiles": "คัดลอกโปรไฟล์", "copyProfiles": "คัดลอกโปรไฟล์",
"addNewProfile": "เพิ่มโปรไฟล์ใหม่", "addNewProfile": "เพิ่มโปรไฟล์ใหม่",
"createNewProfile": "สร้างโปรไฟล์ใหม่", "createNewProfile": "สร้างโปรไฟล์ใหม่",
"editProfile": "แก้ไขโปรไฟล์", "editProfile": "แก้ไขโปรไฟล์",
"placeholderProfileName": "ป้อนชื่อโปรไฟล์", "placeholderProfileName": "ป้อนชื่อโปรไฟล์",
"profileName": "ชื่อโปรไฟล์", "profileName": "ชื่อโปรไฟล์",
"create": "สร้าง", "create": "สร้าง",
"update": "อัปเดต", "update": "อัปเดต",
"characterInformation": "ข้อมูลตัวละคร", "characterInformation": "ข้อมูลตัวละคร",
"skills": "สกิล", "skills": "สกิล",
"showcaseCard": "การ์ดแสดง", "showcaseCard": "การ์ดแสดง",
"comingSoon": "เร็วๆ นี้", "comingSoon": "เร็วๆ นี้",
"characterName": "ชื่อตัวละคร", "characterName": "ชื่อตัวละคร",
"placeholderCharacter": "ป้อนชื่อตัวละคร", "placeholderCharacter": "ป้อนชื่อตัวละคร",
"characterSettings": "การตั้งค่าตัวละคร", "characterSettings": "การตั้งค่าตัวละคร",
"levelConfiguration": "การกำหนดระดับเลเวล", "levelConfiguration": "การกำหนดระดับเลเวล",
"characterLevel": "เลเวลตัวละคร", "characterLevel": "เลเวลตัวละคร",
"max": "สูงสุด", "max": "สูงสุด",
"ultimateEnergy": "พลังงานท่าไม้ตาย", "ultimateEnergy": "พลังงานท่าไม้ตาย",
"currentEnergy": "พลังงานปัจจุบัน", "currentEnergy": "พลังงานปัจจุบัน",
"setTo50": "ตั้งค่าเป็น 50%", "setTo50": "ตั้งค่าเป็น 50%",
"battleConfiguration": "การกำหนดค่าการต่อสู้", "battleConfiguration": "การกำหนดค่าการต่อสู้",
"useTechnique": "ใช้เทคนิค", "useTechnique": "ใช้เทคนิค",
"techniqueNote": "เปิดใช้เอฟเฟกต์เทคนิคก่อนการต่อสู้", "techniqueNote": "เปิดใช้เอฟเฟกต์เทคนิคก่อนการต่อสู้",
"enhancement": "การเสริมพลัง", "enhancement": "การเสริมพลัง",
"enhancementLevel": "ระดับการเสริมพลัง", "enhancementLevel": "ระดับการเสริมพลัง",
"origin": "ดั้งเดิม", "origin": "ดั้งเดิม",
"enhancedNote": "การเสริมพลังที่สูงขึ้นจะปลดล็อกความสามารถ", "enhancedNote": "การเสริมพลังที่สูงขึ้นจะปลดล็อกความสามารถ",
"lightconeEquipment": "สวมใส่ Light Cone", "lightconeEquipment": "สวมใส่ Light Cone",
"lightconeSettings": "การตั้งค่า Light Cone", "lightconeSettings": "การตั้งค่า Light Cone",
"placeholderLevel": "ป้อนเลเวล", "placeholderLevel": "ป้อนเลเวล",
"superimpositionRank": "ระดับการขัดเกลา", "superimpositionRank": "ระดับการขัดเกลา",
"ranksNote": "ระดับที่สูงขึ้นให้เอฟเฟกต์ที่แข็งแกร่งกว่า", "ranksNote": "ระดับที่สูงขึ้นให้เอฟเฟกต์ที่แข็งแกร่งกว่า",
"changeLightcone": "เปลี่ยน Light Cone", "changeLightcone": "เปลี่ยน Light Cone",
"removeLightcone": "ถอด Light Cone", "removeLightcone": "ถอด Light Cone",
"equipLightcone": "สวมใส่ Light Cone", "equipLightcone": "สวมใส่ Light Cone",
"noLightconeEquipped": "ไม่ได้สวมใส่ Light Cone", "noLightconeEquipped": "ไม่ได้สวมใส่ Light Cone",
"equipLightconeNote": "สวมใส่ Light Cone เพื่อเพิ่มพลัง", "equipLightconeNote": "สวมใส่ Light Cone เพื่อเพิ่มพลัง",
"filter": "กรอง", "filter": "กรอง",
"selectedCharacters": "ตัวละครที่เลือก", "selectedCharacters": "ตัวละครที่เลือก",
"selectedProfiles": "โปรไฟล์ที่เลือก", "selectedProfiles": "โปรไฟล์ที่เลือก",
"clearAll": "ล้างทั้งหมด", "clearAll": "ล้างทั้งหมด",
"selectAll": "เลือกทั้งหมด", "selectAll": "เลือกทั้งหมด",
"copy": "คัดลอก", "copy": "คัดลอก",
"copied": "คัดลอกแล้ว", "copied": "คัดลอกแล้ว",
"noAvatarSelected": "ไม่ได้เลือกตัวละคร", "noAvatarSelected": "ไม่ได้เลือกตัวละคร",
"noAvatarToCopySelected": "ไม่ได้เลือกตัวละครที่จะคัดลอก", "noAvatarToCopySelected": "ไม่ได้เลือกตัวละครที่จะคัดลอก",
"pleaseSelectAtLeastOneProfile": "โปรดเลือกอย่างน้อยหนึ่งโปรไฟล์", "pleaseSelectAtLeastOneProfile": "โปรดเลือกอย่างน้อยหนึ่งโปรไฟล์",
"pleaseEnterUid": "โปรดป้อน UID", "pleaseEnterUid": "โปรดป้อน UID",
"failedToFetchEnkaData": "ไม่สามารถดึงข้อมูล Enka ได้", "failedToFetchEnkaData": "ไม่สามารถดึงข้อมูล Enka ได้",
"pleaseSelectAtLeastOneCharacter": "โปรดเลือกอย่างน้อยหนึ่งตัวละคร", "pleaseSelectAtLeastOneCharacter": "โปรดเลือกอย่างน้อยหนึ่งตัวละคร",
"noDataToImport": "ไม่มีข้อมูลที่จะนำเข้า", "noDataToImport": "ไม่มีข้อมูลที่จะนำเข้า",
"pleaseSelectAFile": "โปรดเลือกไฟล์", "pleaseSelectAFile": "โปรดเลือกไฟล์",
"fileMustBeAValidJsonFile": "ไฟล์ต้องเป็นไฟล์ JSON ที่ถูกต้อง", "fileMustBeAValidJsonFile": "ไฟล์ต้องเป็นไฟล์ JSON ที่ถูกต้อง",
"importEnkaDataSuccess": "นำเข้าข้อมูล Enka สำเร็จ", "importEnkaDataSuccess": "นำเข้าข้อมูล Enka สำเร็จ",
"importFreeSRDataSuccess": "นำเข้าข้อมูล FreeSR สำเร็จ", "importFreeSRDataSuccess": "นำเข้าข้อมูล FreeSR สำเร็จ",
"importDatabaseSuccess": "นำเข้าฐานข้อมูลสำเร็จ", "importDatabaseSuccess": "นำเข้าฐานข้อมูลสำเร็จ",
"getData": "รับข้อมูล", "getData": "รับข้อมูล",
"import": "นำเข้า", "import": "นำเข้า",
"freeSRImport": "นำเข้า FreeSR", "freeSRImport": "นำเข้า FreeSR",
"onlySupportFreeSRJsonFile": "รองรับเฉพาะไฟล์ JSON ของ FreeSR", "onlySupportFreeSRJsonFile": "รองรับเฉพาะไฟล์ JSON ของ FreeSR",
"pickAFile": "เลือกไฟล์", "pickAFile": "เลือกไฟล์",
"lightConeSetting": "การตั้งค่า Light Cone", "lightConeSetting": "การตั้งค่า Light Cone",
"relicMaker": "สร้างรีลิกส์", "relicMaker": "สร้างรีลิกส์",
"pleaseSelectAllOptions": "โปรดเลือกตัวเลือกทั้งหมด", "pleaseSelectAllOptions": "โปรดเลือกตัวเลือกทั้งหมด",
"relicSavedSuccessfully": "บันทึกรีลิกส์สำเร็จ", "relicSavedSuccessfully": "บันทึกรีลิกส์สำเร็จ",
"mainSettings": "การตั้งค่าหลัก", "mainSettings": "การตั้งค่าหลัก",
"mainStat": "ค่าสเตตัสหลัก", "mainStat": "ค่าสเตตัสหลัก",
"set": "เซ็ต", "set": "เซ็ต",
"pleaseSelectASet": "โปรดเลือกเซ็ต", "pleaseSelectASet": "โปรดเลือกเซ็ต",
"effectBonus": "โบนัสเอฟเฟกต์", "effectBonus": "โบนัสเอฟเฟกต์",
"totalRoll": "จำนวนการอัปเกรด", "totalRoll": "จำนวนการอัปเกรด",
"randomizeStats": "สุ่มสเตตัส", "randomizeStats": "สุ่มสเตตัส",
"randomizeRolls": "สุ่มอัปเกรด", "randomizeRolls": "สุ่มอัปเกรด",
"selectASubStat": "เลือกสเตตัสรอง", "selectASubStat": "เลือกสเตตัสรอง",
"selectASet": "เลือกเซ็ต", "selectASet": "เลือกเซ็ต",
"selectAMainStat": "เลือกสเตตัสหลัก", "selectAMainStat": "เลือกสเตตัสหลัก",
"save": "บันทึก", "save": "บันทึก",
"reset": "รีเซ็ต", "reset": "รีเซ็ต",
"roll": "อัปเกรด", "roll": "อัปเกรด",
"step": "ขั้น", "step": "ขั้น",
"memoryOfChaos": "Memory of Chaos", "memoryOfChaos": "Memory of Chaos",
"pureFiction": "Pure Fiction", "pureFiction": "Pure Fiction",
"apocalypticShadow": "Apocalyptic Shadow", "apocalypticShadow": "Apocalyptic Shadow",
"customEnemy": "ศัตรูกำหนดเอง", "customEnemy": "ศัตรูกำหนดเอง",
"simulatedUniverse": "Simulated Universe", "simulatedUniverse": "Simulated Universe",
"floor": "ชั้น", "floor": "ชั้น",
"side": "ฝั่ง", "side": "ฝั่ง",
"wave": "เวฟ", "wave": "เวฟ",
"stage": "ด่าน", "stage": "ด่าน",
"useCycleCount": "ใช้การนับรอบหรือไม่?", "useCycleCount": "ใช้การนับรอบหรือไม่?",
"useTurbulenceBuff": "ใช้บัฟบรรยากาศหรือไม่?", "useTurbulenceBuff": "ใช้บัฟบรรยากาศหรือไม่?",
"firstHalfEnemies": "ศัตรูครึ่งแรก", "firstHalfEnemies": "ศัตรูครึ่งแรก",
"secondHalfEnemies": "ศัตรูครึ่งหลัง", "secondHalfEnemies": "ศัตรูครึ่งหลัง",
"firstNodeEnemies": "ศัตรูโหนด 1", "firstNodeEnemies": "ศัตรูโหนด 1",
"secondNodeEnemies": "ศัตรูโหนด 2", "secondNodeEnemies": "ศัตรูโหนด 2",
"thirdNodeEnemies": "ศัตรูโหนด 3", "thirdNodeEnemies": "ศัตรูโหนด 3",
"firstNode": "โหนด 1", "firstNode": "โหนด 1",
"secondNode": "โหนด 2", "secondNode": "โหนด 2",
"thirdNode": "โหนด 3", "thirdNode": "โหนด 3",
"listEnemies": "รายการศัตรู", "listEnemies": "รายการศัตรู",
"turbulenceBuff": "บัฟบรรยากาศ", "turbulenceBuff": "บัฟบรรยากาศ",
"noEventSelected": "ไม่ได้เลือกอีเวนต์", "noEventSelected": "ไม่ได้เลือกอีเวนต์",
"noTurbulenceBuff": "ไม่มีบัฟบรรยากาศ", "noTurbulenceBuff": "ไม่มีบัฟบรรยากาศ",
"upper": "บน", "upper": "บน",
"lower": "ล่าง", "lower": "ล่าง",
"upperToLower": "บน -> ล่าง", "upperToLower": "บน -> ล่าง",
"lowerToUpper": "ล่าง -> บน", "lowerToUpper": "ล่าง -> บน",
"selectMOCEvent": "เลือกอีเวนต์ MOC", "selectMOCEvent": "เลือกอีเวนต์ MOC",
"selectPFEvent": "เลือกอีเวนต์ PF", "selectPFEvent": "เลือกอีเวนต์ PF",
"selectASEvent": "เลือกอีเวนต์ AS", "selectASEvent": "เลือกอีเวนต์ AS",
"selectCEEvent": "เลือกอีเวนต์ CE", "selectCEEvent": "เลือกอีเวนต์ CE",
"selectEvent": "เลือกอีเวนต์", "selectEvent": "เลือกอีเวนต์",
"selectFloor": "เลือกชั้น", "selectFloor": "เลือกชั้น",
"selectSide": "เลือกฝั่ง", "selectSide": "เลือกฝั่ง",
"selectBuff": "เลือกบัฟ", "selectBuff": "เลือกบัฟ",
"selectStage": "เลือกด่าน", "selectStage": "เลือกด่าน",
"previous": "ก่อนหน้า", "previous": "ก่อนหน้า",
"next": "ถัดไป", "next": "ถัดไป",
"noMonstersFound": "ไม่พบมอนสเตอร์", "noMonstersFound": "ไม่พบมอนสเตอร์",
"addNewWave": "เพิ่มเวฟใหม่", "addNewWave": "เพิ่มเวฟใหม่",
"searchStage": "ค้นหาด่าน...", "searchStage": "ค้นหาด่าน...",
"noStageFound": "ไม่พบด่าน", "noStageFound": "ไม่พบด่าน",
"searchMonster": "ค้นหามอนสเตอร์...", "searchMonster": "ค้นหามอนสเตอร์...",
"changeRelic": "เปลี่ยนรีลิกส์", "changeRelic": "เปลี่ยนรีลิกส์",
"deleteRelic": "ลบรีลิกส์", "deleteRelic": "ลบรีลิกส์",
"deleteRelicConfirm": "คุณแน่ใจหรือไม่ว่าต้องการลบรีลิกส์ในช่องนี้?", "deleteRelicConfirm": "คุณแน่ใจหรือไม่ว่าต้องการลบรีลิกส์ในช่องนี้?",
"setEffects": "ตั้งค่าเอฟเฟกต์", "setEffects": "ตั้งค่าเอฟเฟกต์",
"details": "รายละเอียด", "details": "รายละเอียด",
"normal": "โจมตีปกติ", "normal": "โจมตีปกติ",
"bpskill": "สกิลต่อสู้", "bpskill": "สกิลต่อสู้",
"maze": "เทคนิค", "maze": "เทคนิค",
"ultra": "ท่าไม้ตาย", "ultra": "ท่าไม้ตาย",
"servantskill": "สกิล Memosprite", "servantskill": "สกิล Memosprite",
"severaltalent": "พรสวรรค์ Memosprite", "severaltalent": "พรสวรรค์ Memosprite",
"singleattack": "โจมตีเป้าหมายเดียว", "singleattack": "โจมตีเป้าหมายเดียว",
"enhance": "เสริมพลัง", "enhance": "เสริมพลัง",
"summon": "อัญเชิญ", "summon": "อัญเชิญ",
"mazeattack": "โจมตีแบบเทคนิค", "mazeattack": "โจมตีแบบเทคนิค",
"blast": "กระจาย", "blast": "กระจาย",
"restore": "ฟื้นฟู", "restore": "ฟื้นฟู",
"support": "สนับสนุน", "support": "สนับสนุน",
"aoeattack": "โจมตีหมู่", "aoeattack": "โจมตีหมู่",
"impair": "ป่วน", "impair": "ป่วน",
"bounce": "ชิ่ง", "bounce": "ชิ่ง",
"active": "เปิดใช้งาน", "active": "เปิดใช้งาน",
"defence": "ป้องกัน", "defence": "ป้องกัน",
"inactive": "ปิดใช้งาน", "inactive": "ปิดใช้งาน",
"maxAll": "อัปเกรดทั้งหมด", "maxAll": "อัปเกรดทั้งหมด",
"maxAllSuccess": "อัปเกรดสกิลถึงระดับสูงสุดสำเร็จ", "maxAllSuccess": "อัปเกรดสกิลถึงระดับสูงสุดสำเร็จ",
"maxAllFailed": "อัปเกรดสกิลล้มเหลว", "maxAllFailed": "อัปเกรดสกิลล้มเหลว",
"noRelicEquipped": "ไม่ได้สวมใส่รีลิกส์", "noRelicEquipped": "ไม่ได้สวมใส่รีลิกส์",
"anomalyArbitration": "Anomaly Arbitration", "anomalyArbitration": "Anomaly Arbitration",
"normalMode": "โหมดปกติ", "normalMode": "โหมดปกติ",
"hardMode": "โหมดยาก", "hardMode": "โหมดยาก",
"selectPEAKEvent": "เลือกอีเวนต์ PEAK", "selectPEAKEvent": "เลือกอีเวนต์ PEAK",
"mode": "โหมด", "mode": "โหมด",
"selectMode": "เลือกโหมด", "selectMode": "เลือกโหมด",
"rollBack": "ย้อนกลับ", "rollBack": "ย้อนกลับ",
"upRoll": "เพิ่มอัปเกรด", "upRoll": "เพิ่มอัปเกรด",
"downRoll": "ลดอัปเกรด", "downRoll": "ลดอัปเกรด",
"actions": "แอ็กชัน", "actions": "แอ็กชัน",
"avatars": "อวตาร", "avatars": "อวตาร",
"quickView": "ดูแบบด่วน", "quickView": "ดูแบบด่วน",
"extraSetting": "การตั้งค่าเพิ่มเติม", "extraSetting": "การตั้งค่าเพิ่มเติม",
"disableCensorship": "ปิดการเซ็นเซอร์", "disableCensorship": "ปิดการเซ็นเซอร์",
"hideUI": "ซ่อน UI", "hideUI": "ซ่อน UI",
"theoryCraftMode": "โหมด Theorycraft", "theoryCraftMode": "โหมด Theorycraft",
"cycleCount": "จำนวนรอบ", "cycleCount": "จำนวนรอบ",
"pleaseSelectAllSubStats": "โปรดเลือกสเตตัสรองทั้งหมด", "pleaseSelectAllSubStats": "โปรดเลือกสเตตัสรองทั้งหมด",
"subStatRollCountCannotBeZero": "จำนวนการอัปเกรดสเตตัสรองต้องไม่เป็นศูนย์", "subStatRollCountCannotBeZero": "จำนวนการอัปเกรดสเตตัสรองต้องไม่เป็นศูนย์",
"theoryCraft": "Theorycraft", "theoryCraft": "Theorycraft",
"multipathCharacter": "ตัวละครหลาย Path", "multipathCharacter": "ตัวละครหลาย Path",
"mainPath": "Path หลัก", "mainPath": "Path หลัก",
"march7Path": "Path March 7th", "march7Path": "Path March 7th",
"challenge": "ท้าทาย", "challenge": "ท้าทาย",
"skipNode": "ข้ามโหนด", "skipNode": "ข้ามโหนด",
"disableSkip": "ปิดใช้งานการข้าม", "disableSkip": "ปิดใช้งานการข้าม",
"skipNode1": "ข้ามโหนด 1", "skipNode1": "ข้ามโหนด 1",
"skipNode2": "ข้ามโหนด 2", "skipNode2": "ข้ามโหนด 2",
"extraFeatures": "คุณสมบัติพิเศษ", "extraFeatures": "คุณสมบัติพิเศษ",
"detailTheoryCraft": "การเปิดคุณสมบัตินี้จะช่วยให้คุณปรับแต่งจำนวนรอบและปรับ HP ของศัตรูได้", "detailTheoryCraft": "การเปิดคุณสมบัตินี้จะช่วยให้คุณปรับแต่งจำนวนรอบและปรับ HP ของศัตรูได้",
"detailSkipNode": "ช่วยให้ข้าม (โหนด 1/โหนด 2) ใน Memory of Chaos หรือ Pure Fiction", "detailSkipNode": "ช่วยให้ข้าม (โหนด 1/โหนด 2) ใน Memory of Chaos หรือ Pure Fiction",
"detailChallengePeak": "อนุญาตให้เปลี่ยนฤดูกาล Peak ใน Anomaly ปัจจุบัน", "detailChallengePeak": "อนุญาตให้เปลี่ยนฤดูกาล Peak ใน Anomaly ปัจจุบัน",
"detailHiddenUi": "ซ่อนอินเทอร์เฟซของเกม", "detailHiddenUi": "ซ่อนอินเทอร์เฟซของเกม",
"detailDisableCensorship": "ปิดการเซ็นเซอร์ในเกม", "detailDisableCensorship": "ปิดการเซ็นเซอร์ในเกม",
"detailMultipathCharacter": "อนุญาตให้เปลี่ยน Path ของตัวละครบางตัว", "detailMultipathCharacter": "อนุญาตให้เปลี่ยน Path ของตัวละครบางตัว",
"trailblazer": "ผู้บุกเบิก", "trailblazer": "ผู้บุกเบิก",
"listExtraEffect": "รายการเอฟเฟกต์พิเศษ", "listExtraEffect": "รายการเอฟเฟกต์พิเศษ",
"extra": "พิเศษ", "extra": "พิเศษ",
"customLineup": "จัดทีมแบบกำหนดเอง" "customLineup": "จัดทีมแบบกำหนดเอง"
} }
} }
+288 -288
View File
@@ -1,289 +1,289 @@
{ {
"TabTitle": { "TabTitle": {
"title": "Firefly Tools", "title": "Firefly Tools",
"description": "Firefly tools by Firefly Shelter" "description": "Firefly tools by Firefly Shelter"
}, },
"DataPage": { "DataPage": {
"skillType": "Loại kỹ năng", "skillType": "Loại kỹ năng",
"skillName": "Tên kỹ năng", "skillName": "Tên kỹ năng",
"character": "Nhân vật", "character": "Nhân vật",
"id": "ID", "id": "ID",
"path": "Vận mệnh", "path": "Vận mệnh",
"rarity": "Độ hiếm", "rarity": "Độ hiếm",
"element": "Nguyên tố", "element": "Nguyên tố",
"technique": "Bí kỹ", "technique": "Bí kỹ",
"talent": "Thiên phú", "talent": "Thiên phú",
"basic": "Đánh thường", "basic": "Đánh thường",
"skill": "Kỹ năng", "skill": "Kỹ năng",
"ultimate": "Tuyệt kỹ", "ultimate": "Tuyệt kỹ",
"servant": "Phụ trợ", "servant": "Phụ trợ",
"damage": "Sát thương", "damage": "Sát thương",
"type": "Loại", "type": "Loại",
"warrior": "Hủy Diệt", "warrior": "Hủy Diệt",
"knight": "Bảo Hộ", "knight": "Bảo Hộ",
"mage": "Tri Thức", "mage": "Tri Thức",
"priest": "Trù phú", "priest": "Trù phú",
"rogue": "Săn Bắn", "rogue": "Săn Bắn",
"shaman": "Hòa Hợp", "shaman": "Hòa Hợp",
"warlock": "Hư Vô", "warlock": "Hư Vô",
"memory": "Ký Ức", "memory": "Ký Ức",
"elation": "Vui vẻ", "elation": "Vui vẻ",
"fire": "Hỏa", "fire": "Hỏa",
"ice": "Băng", "ice": "Băng",
"imaginary": "Ảo Ảnh", "imaginary": "Ảo Ảnh",
"physical": "Vật Lý", "physical": "Vật Lý",
"quantum": "Lượng Tử", "quantum": "Lượng Tử",
"thunder": "Lôi", "thunder": "Lôi",
"wind": "Phong", "wind": "Phong",
"hp": "HP", "hp": "HP",
"atk": "Tấn công", "atk": "Tấn công",
"speed": "Tốc độ", "speed": "Tốc độ",
"critRate": "Tỷ lệ bạo", "critRate": "Tỷ lệ bạo",
"critDmg": "ST bạo", "critDmg": "ST bạo",
"breakEffect": "sát thương kích phá", "breakEffect": "sát thương kích phá",
"effectRes": "Kháng hiệu ứng", "effectRes": "Kháng hiệu ứng",
"energyRegenerationRate": "Tốc độ hồi năng lượng", "energyRegenerationRate": "Tốc độ hồi năng lượng",
"effectHitRate": "Tỷ lệ trúng hiệu ứng", "effectHitRate": "Tỷ lệ trúng hiệu ứng",
"outgoingHealingBoost": "Tăng hồi phục", "outgoingHealingBoost": "Tăng hồi phục",
"fireDmgBoost": "Tăng sát thương Hỏa", "fireDmgBoost": "Tăng sát thương Hỏa",
"iceDmgBoost": "Tăng sát thương Băng", "iceDmgBoost": "Tăng sát thương Băng",
"imaginaryDmgBoost": "Tăng sát thương Ảo Ảnh", "imaginaryDmgBoost": "Tăng sát thương Ảo Ảnh",
"physicalDmgBoost": "Tăng sát thương Vật Lý", "physicalDmgBoost": "Tăng sát thương Vật Lý",
"quantumDmgBoost": "Tăng sát thương Lượng Tử", "quantumDmgBoost": "Tăng sát thương Lượng Tử",
"thunderDmgBoost": "Tăng sát thương Lôi", "thunderDmgBoost": "Tăng sát thương Lôi",
"windDmgBoost": "Tăng sát thương Phong", "windDmgBoost": "Tăng sát thương Phong",
"pursued": "Sát thương thêm", "pursued": "Sát thương thêm",
"true damage": "Sát thương chuẩn", "true damage": "Sát thương chuẩn",
"elationdamage": "Sát thương vui vẻ", "elationdamage": "Sát thương vui vẻ",
"follow-up": "Sát thương phản kích", "follow-up": "Sát thương phản kích",
"elemental damage": "Sát thương kích phá và siêu kích phá", "elemental damage": "Sát thương kích phá và siêu kích phá",
"dot": "Sát thương theo thời gian", "dot": "Sát thương theo thời gian",
"qte": "Kỹ năng QTE", "qte": "Kỹ năng QTE",
"level": "Cấp độ", "level": "Cấp độ",
"relics": "Thánh di vật", "relics": "Thánh di vật",
"eidolons": "Tinh hồn", "eidolons": "Tinh hồn",
"lightcones": "Nón ánh sáng", "lightcones": "Nón ánh sáng",
"loadData": "Tải dữ liệu", "loadData": "Tải dữ liệu",
"exportData": "Xuất dữ liệu", "exportData": "Xuất dữ liệu",
"connectSetting": "Cài đặt kết nối", "connectSetting": "Cài đặt kết nối",
"connected": "Đã kết nối", "connected": "Đã kết nối",
"unconnected": "Chưa kết nối", "unconnected": "Chưa kết nối",
"psConnection": "Kết nối PS", "psConnection": "Kết nối PS",
"connectionType": "Loại kết nối", "connectionType": "Loại kết nối",
"status": "Trạng thái", "status": "Trạng thái",
"connectPs": "Kết nối PS", "connectPs": "Kết nối PS",
"disconnect": "Ngắt kết nối", "disconnect": "Ngắt kết nối",
"other": "Khác", "other": "Khác",
"freeSr": "FreeSR", "freeSr": "FreeSR",
"database": "Database", "database": "Database",
"enka": "Enka", "enka": "Enka",
"monsterSetting": "Cài đặt quái", "monsterSetting": "Cài đặt quái",
"serverUrl": "Địa chỉ server", "serverUrl": "Địa chỉ server",
"privateType": "Loại riêng tư", "privateType": "Loại riêng tư",
"local": "Cục bộ", "local": "Cục bộ",
"server": "Máy chủ", "server": "Máy chủ",
"username": "Tên người dùng", "username": "Tên người dùng",
"password": "Mật khẩu", "password": "Mật khẩu",
"placeholderServerUrl": "Nhập địa chỉ server", "placeholderServerUrl": "Nhập địa chỉ server",
"placeholderUsername": "Nhập tên người dùng", "placeholderUsername": "Nhập tên người dùng",
"placeholderPassword": "Nhập mật khẩu", "placeholderPassword": "Nhập mật khẩu",
"connectedSuccess": "Kết nối PS thành công", "connectedSuccess": "Kết nối PS thành công",
"connectedFailed": "Kết nối PS thất bại", "connectedFailed": "Kết nối PS thất bại",
"syncSuccess": "Đồng bộ dữ liệu với PS thành công", "syncSuccess": "Đồng bộ dữ liệu với PS thành công",
"syncFailed": "Đồng bộ dữ liệu với PS thất bại", "syncFailed": "Đồng bộ dữ liệu với PS thất bại",
"sync": "Đồng bộ", "sync": "Đồng bộ",
"importSetting": "Cài đặt nhập", "importSetting": "Cài đặt nhập",
"profile": "Hồ sơ", "profile": "Hồ sơ",
"default": "Mặc định", "default": "Mặc định",
"copyProfiles": "Sao chép hồ sơ", "copyProfiles": "Sao chép hồ sơ",
"addNewProfile": "Thêm hồ sơ mới", "addNewProfile": "Thêm hồ sơ mới",
"createNewProfile": "Tạo hồ sơ mới", "createNewProfile": "Tạo hồ sơ mới",
"editProfile": "Chỉnh sửa hồ sơ", "editProfile": "Chỉnh sửa hồ sơ",
"placeholderProfileName": "Nhập tên hồ sơ", "placeholderProfileName": "Nhập tên hồ sơ",
"profileName": "Tên hồ sơ", "profileName": "Tên hồ sơ",
"create": "Tạo", "create": "Tạo",
"update": "Cập nhật", "update": "Cập nhật",
"characterInformation": "Thông tin nhân vật", "characterInformation": "Thông tin nhân vật",
"skills": "Kỹ năng", "skills": "Kỹ năng",
"showcaseCard": "Thẻ trưng bày", "showcaseCard": "Thẻ trưng bày",
"comingSoon": "Sắp ra mắt", "comingSoon": "Sắp ra mắt",
"characterName": "Tên nhân vật", "characterName": "Tên nhân vật",
"placeholderCharacter": "Nhập tên nhân vật", "placeholderCharacter": "Nhập tên nhân vật",
"characterSettings": "Cài đặt nhân vật", "characterSettings": "Cài đặt nhân vật",
"levelConfiguration": "Cấu hình cấp độ", "levelConfiguration": "Cấu hình cấp độ",
"characterLevel": "Cấp độ nhân vật", "characterLevel": "Cấp độ nhân vật",
"max": "Tối đa", "max": "Tối đa",
"ultimateEnergy": "Năng lượng tuyệt kỹ", "ultimateEnergy": "Năng lượng tuyệt kỹ",
"currentEnergy": "Năng lượng hiện tại", "currentEnergy": "Năng lượng hiện tại",
"setTo50": "Đặt thành 50%", "setTo50": "Đặt thành 50%",
"battleConfiguration": "Cấu hình trận đấu", "battleConfiguration": "Cấu hình trận đấu",
"useTechnique": "Dùng bí kỹ", "useTechnique": "Dùng bí kỹ",
"techniqueNote": "Bật hiệu ứng bí kỹ trước trận", "techniqueNote": "Bật hiệu ứng bí kỹ trước trận",
"enhancement": "Cường hóa", "enhancement": "Cường hóa",
"enhancementLevel": "Cấp độ cường hóa", "enhancementLevel": "Cấp độ cường hóa",
"origin": "Nguyên gốc", "origin": "Nguyên gốc",
"enhancedNote": "Cường hóa cao mở thêm kỹ năng", "enhancedNote": "Cường hóa cao mở thêm kỹ năng",
"lightconeEquipment": "Trang bị nón ánh sáng", "lightconeEquipment": "Trang bị nón ánh sáng",
"lightconeSettings": "Cài đặt nón ánh sáng", "lightconeSettings": "Cài đặt nón ánh sáng",
"placeholderLevel": "Nhập cấp độ", "placeholderLevel": "Nhập cấp độ",
"superimpositionRank": "Bậc chồng kỹ năng", "superimpositionRank": "Bậc chồng kỹ năng",
"ranksNote": "Bậc càng cao hiệu ứng càng mạnh", "ranksNote": "Bậc càng cao hiệu ứng càng mạnh",
"changeLightcone": "Thay đổi nón ánh sáng", "changeLightcone": "Thay đổi nón ánh sáng",
"removeLightcone": "Gỡ nón ánh sáng", "removeLightcone": "Gỡ nón ánh sáng",
"equipLightcone": "Trang bị nón ánh sáng", "equipLightcone": "Trang bị nón ánh sáng",
"noLightconeEquipped": "Chưa trang bị nón ánh sáng", "noLightconeEquipped": "Chưa trang bị nón ánh sáng",
"equipLightconeNote": "Trang bị nón để tăng sức mạnh cho nhân vật", "equipLightconeNote": "Trang bị nón để tăng sức mạnh cho nhân vật",
"filter": "Lọc", "filter": "Lọc",
"selectedCharacters": "Nhân vật đã chọn", "selectedCharacters": "Nhân vật đã chọn",
"selectedProfiles": "Hồ sơ đã chọn", "selectedProfiles": "Hồ sơ đã chọn",
"clearAll": "Xóa tất cả", "clearAll": "Xóa tất cả",
"selectAll": "Chọn tất cả", "selectAll": "Chọn tất cả",
"copy": "Sao chép", "copy": "Sao chép",
"copied": "Đã sao chép", "copied": "Đã sao chép",
"noAvatarSelected": "Chưa chọn nhân vật", "noAvatarSelected": "Chưa chọn nhân vật",
"noAvatarToCopySelected": "Chưa chọn nhân vật để sao chép", "noAvatarToCopySelected": "Chưa chọn nhân vật để sao chép",
"pleaseSelectAtLeastOneProfile": "Vui lòng chọn ít nhất một hồ sơ", "pleaseSelectAtLeastOneProfile": "Vui lòng chọn ít nhất một hồ sơ",
"pleaseEnterUid": "Vui lòng nhập UID", "pleaseEnterUid": "Vui lòng nhập UID",
"failedToFetchEnkaData": "Lấy dữ liệu Enka thất bại", "failedToFetchEnkaData": "Lấy dữ liệu Enka thất bại",
"pleaseSelectAtLeastOneCharacter": "Vui lòng chọn ít nhất một nhân vật", "pleaseSelectAtLeastOneCharacter": "Vui lòng chọn ít nhất một nhân vật",
"noDataToImport": "Không có dữ liệu để nhập", "noDataToImport": "Không có dữ liệu để nhập",
"pleaseSelectAFile": "Vui lòng chọn một tệp", "pleaseSelectAFile": "Vui lòng chọn một tệp",
"fileMustBeAValidJsonFile": "Tệp phải là tệp JSON hợp lệ", "fileMustBeAValidJsonFile": "Tệp phải là tệp JSON hợp lệ",
"importEnkaDataSuccess": "Nhập dữ liệu Enka thành công", "importEnkaDataSuccess": "Nhập dữ liệu Enka thành công",
"importFreeSRDataSuccess": "Nhập dữ liệu FreeSR thành công", "importFreeSRDataSuccess": "Nhập dữ liệu FreeSR thành công",
"importDatabaseSuccess": "Nhập cơ sở dữ liệu thành công", "importDatabaseSuccess": "Nhập cơ sở dữ liệu thành công",
"getData": "Lấy dữ liệu", "getData": "Lấy dữ liệu",
"import": "Nhập", "import": "Nhập",
"freeSRImport": "Nhập FreeSR", "freeSRImport": "Nhập FreeSR",
"onlySupportFreeSRJsonFile": "Chỉ hỗ trợ tệp JSON từ FreeSR", "onlySupportFreeSRJsonFile": "Chỉ hỗ trợ tệp JSON từ FreeSR",
"pickAFile": "Chọn tệp", "pickAFile": "Chọn tệp",
"lightConeSetting": "Cài đặt Nón Ánh Sáng", "lightConeSetting": "Cài đặt Nón Ánh Sáng",
"relicMaker": "Trình tạo Thánh Di Vật", "relicMaker": "Trình tạo Thánh Di Vật",
"pleaseSelectAllOptions": "Vui lòng chọn tất cả tùy chọn", "pleaseSelectAllOptions": "Vui lòng chọn tất cả tùy chọn",
"relicSavedSuccessfully": "Lưu thánh di vật thành công", "relicSavedSuccessfully": "Lưu thánh di vật thành công",
"mainSettings": "Cài đặt chính", "mainSettings": "Cài đặt chính",
"mainStat": "Chỉ số chính", "mainStat": "Chỉ số chính",
"set": "Bộ", "set": "Bộ",
"pleaseSelectASet": "Vui lòng chọn một bộ", "pleaseSelectASet": "Vui lòng chọn một bộ",
"effectBonus": "Hiệu ứng cộng thêm", "effectBonus": "Hiệu ứng cộng thêm",
"totalRoll": "Tổng số dòng", "totalRoll": "Tổng số dòng",
"randomizeStats": "Ngẫu nhiên chỉ số", "randomizeStats": "Ngẫu nhiên chỉ số",
"randomizeRolls": "Ngẫu nhiên số dòng", "randomizeRolls": "Ngẫu nhiên số dòng",
"selectASubStat": "Chọn chỉ số phụ", "selectASubStat": "Chọn chỉ số phụ",
"selectASet": "Chọn một bộ", "selectASet": "Chọn một bộ",
"selectAMainStat": "Chọn chỉ số chính", "selectAMainStat": "Chọn chỉ số chính",
"save": "Lưu", "save": "Lưu",
"reset": "Đặt lại toàn bộ", "reset": "Đặt lại toàn bộ",
"roll": "Số dòng", "roll": "Số dòng",
"step": "Bước nhảy", "step": "Bước nhảy",
"memoryOfChaos": "Hồi ức hỗn độn", "memoryOfChaos": "Hồi ức hỗn độn",
"pureFiction": "Kể chuyện hư cấu", "pureFiction": "Kể chuyện hư cấu",
"apocalypticShadow": "Ảo ảnh tận thế", "apocalypticShadow": "Ảo ảnh tận thế",
"customEnemy": "Kẻ địch tùy chỉnh", "customEnemy": "Kẻ địch tùy chỉnh",
"simulatedUniverse": "Vũ trụ mô phỏng", "simulatedUniverse": "Vũ trụ mô phỏng",
"floor": "Tầng", "floor": "Tầng",
"side": "Nửa trận", "side": "Nửa trận",
"wave": "Đợt", "wave": "Đợt",
"stage": "Màn", "stage": "Màn",
"useCycleCount": "Dùng dếm chu kỳ?", "useCycleCount": "Dùng dếm chu kỳ?",
"useTurbulenceBuff": "Dùng buff hỗn loạn?", "useTurbulenceBuff": "Dùng buff hỗn loạn?",
"firstHalfEnemies": "Địch nửa đầu", "firstHalfEnemies": "Địch nửa đầu",
"secondHalfEnemies": "Địch nửa sau", "secondHalfEnemies": "Địch nửa sau",
"firstNodeEnemies": "Địch Node 1", "firstNodeEnemies": "Địch Node 1",
"secondNodeEnemies": "Địch Node 2", "secondNodeEnemies": "Địch Node 2",
"thirdNodeEnemies": "Địch Node 3", "thirdNodeEnemies": "Địch Node 3",
"firstNode": "Node 1", "firstNode": "Node 1",
"secondNode": "Node 2", "secondNode": "Node 2",
"thirdNode": "Node 3", "thirdNode": "Node 3",
"turbulenceBuff": "Buff hỗn loạn", "turbulenceBuff": "Buff hỗn loạn",
"noEventSelected": "Không có sự kiện", "noEventSelected": "Không có sự kiện",
"noTurbulenceBuff": "Không có buff hỗn loạn", "noTurbulenceBuff": "Không có buff hỗn loạn",
"upper": "Nửa trên", "upper": "Nửa trên",
"lower": "Nửa dưới", "lower": "Nửa dưới",
"upperToLower": "Nửa trên -> Nửa dưới", "upperToLower": "Nửa trên -> Nửa dưới",
"lowerToUpper": "Nửa dưới -> Nửa trên", "lowerToUpper": "Nửa dưới -> Nửa trên",
"selectMOCEvent": "Chọn sự kiện MOC", "selectMOCEvent": "Chọn sự kiện MOC",
"selectPFEvent": "Chọn sự kiện PF", "selectPFEvent": "Chọn sự kiện PF",
"selectASEvent": "Chọn sự kiện AS", "selectASEvent": "Chọn sự kiện AS",
"selectCEEvent": "Chọn sự kiện CE", "selectCEEvent": "Chọn sự kiện CE",
"selectPEAKEvent": "Chọn sự kiện PEAK", "selectPEAKEvent": "Chọn sự kiện PEAK",
"selectEvent": "Chọn sự kiện", "selectEvent": "Chọn sự kiện",
"selectFloor": "Chọn tầng", "selectFloor": "Chọn tầng",
"selectSide": "Chọn nửa trận", "selectSide": "Chọn nửa trận",
"selectBuff": "Chọn buff", "selectBuff": "Chọn buff",
"selectStage": "Chọn màn", "selectStage": "Chọn màn",
"previous": "Trước", "previous": "Trước",
"next": "Tiếp", "next": "Tiếp",
"noMonstersFound": "Không tìm thấy quái", "noMonstersFound": "Không tìm thấy quái",
"addNewWave": "Thêm đợt mới", "addNewWave": "Thêm đợt mới",
"searchStage": "Tìm màn...", "searchStage": "Tìm màn...",
"noStageFound": "Không tìm thấy màn", "noStageFound": "Không tìm thấy màn",
"searchMonster": "Tìm quái...", "searchMonster": "Tìm quái...",
"changeRelic": "Thay đổi di vật", "changeRelic": "Thay đổi di vật",
"deleteRelic": "Xóa di vật", "deleteRelic": "Xóa di vật",
"deleteRelicConfirm": "Bạn có chắc chắn muốn xóa di vật trong ô này không?", "deleteRelicConfirm": "Bạn có chắc chắn muốn xóa di vật trong ô này không?",
"setEffects": "Thiết lập hiệu ứng", "setEffects": "Thiết lập hiệu ứng",
"details": "Chi tiết", "details": "Chi tiết",
"normal": "Đánh thường", "normal": "Đánh thường",
"bpskill": "Chiến kỹ", "bpskill": "Chiến kỹ",
"maze": "Bí kỹ", "maze": "Bí kỹ",
"ultra": "Tuyệt kỹ", "ultra": "Tuyệt kỹ",
"servantskill": "Kỹ năng vật triệu hồi", "servantskill": "Kỹ năng vật triệu hồi",
"severaltalent": "Thiên phú vật triệu hồi", "severaltalent": "Thiên phú vật triệu hồi",
"singleattack": "Tấn công đơn", "singleattack": "Tấn công đơn",
"enhance": "Cường hóa", "enhance": "Cường hóa",
"summon": "Triệu hồi", "summon": "Triệu hồi",
"blast": "Tấn công 3 mục tiêu", "blast": "Tấn công 3 mục tiêu",
"restore": "Hồi phục", "restore": "Hồi phục",
"support": "Hỗ trợ", "support": "Hỗ trợ",
"aoeattack": "Tấn công đa mục tiêu", "aoeattack": "Tấn công đa mục tiêu",
"mazeattack": "Bí kỹ tấn công", "mazeattack": "Bí kỹ tấn công",
"impair": "Suy yếu", "impair": "Suy yếu",
"bounce": "Nảy bật", "bounce": "Nảy bật",
"active": "Kích hoạt", "active": "Kích hoạt",
"inactive": "Không kích hoạt", "inactive": "Không kích hoạt",
"defence": "Phòng thủ", "defence": "Phòng thủ",
"maxAll": "Tối đa tất cả", "maxAll": "Tối đa tất cả",
"maxAllSuccess": "Đã thiết lập cấp độ kỹ năng tối đa thành công.", "maxAllSuccess": "Đã thiết lập cấp độ kỹ năng tối đa thành công.",
"maxAllFailed": "Thiết lập cấp độ kỹ năng tối đa thất bại.", "maxAllFailed": "Thiết lập cấp độ kỹ năng tối đa thất bại.",
"noRelicEquipped": "Không có di vật", "noRelicEquipped": "Không có di vật",
"anomalyArbitration": "Trọng tài dị tướng", "anomalyArbitration": "Trọng tài dị tướng",
"normalMode": "Chế độ thường", "normalMode": "Chế độ thường",
"hardMode": "Chế độ khó", "hardMode": "Chế độ khó",
"mode": "Chế độ", "mode": "Chế độ",
"selectMode": "Chọn chế độ", "selectMode": "Chọn chế độ",
"rollBack": "Quay lại bước trước", "rollBack": "Quay lại bước trước",
"upRoll": "Tăng dòng", "upRoll": "Tăng dòng",
"downRoll": "Giảm dòng", "downRoll": "Giảm dòng",
"actions": "Hành động", "actions": "Hành động",
"avatars": "Nhân vật", "avatars": "Nhân vật",
"quickView": "Xem nhanh", "quickView": "Xem nhanh",
"extraSetting": "Cài đặt bổ sung", "extraSetting": "Cài đặt bổ sung",
"disableCensorship": "Tắt kiểm duyệt", "disableCensorship": "Tắt kiểm duyệt",
"hideUI": "Ẩn giao diện", "hideUI": "Ẩn giao diện",
"theoryCraftMode": "Chế độ Theory Craft", "theoryCraftMode": "Chế độ Theory Craft",
"cycleCount": "Số vòng", "cycleCount": "Số vòng",
"pleaseSelectAllSubStats": "Vui lòng chọn tất cả chỉ số phụ", "pleaseSelectAllSubStats": "Vui lòng chọn tất cả chỉ số phụ",
"subStatRollCountCannotBeZero": "Số dòng của chỉ số phụ không thể bằng 0", "subStatRollCountCannotBeZero": "Số dòng của chỉ số phụ không thể bằng 0",
"theoryCraft": "Theory Craft", "theoryCraft": "Theory Craft",
"multipathCharacter": "Nhân vật đa Vận Mệnh", "multipathCharacter": "Nhân vật đa Vận Mệnh",
"mainPath": "Vận Mệnh Nhân Vật Chính", "mainPath": "Vận Mệnh Nhân Vật Chính",
"march7Path": "Vận Mệnh March 7", "march7Path": "Vận Mệnh March 7",
"challenge": "Thử thách", "challenge": "Thử thách",
"skipNode": "Bỏ qua node", "skipNode": "Bỏ qua node",
"disableSkip": "Tắt bỏ qua", "disableSkip": "Tắt bỏ qua",
"skipNode1": "Bỏ qua node 1", "skipNode1": "Bỏ qua node 1",
"skipNode2": "Bỏ qua node 2", "skipNode2": "Bỏ qua node 2",
"extraFeatures": "Tính năng bổ sung", "extraFeatures": "Tính năng bổ sung",
"detailTheoryCraft": "Khi bật tính năng này sẽ cho phép tùy chỉnh số cycle và trong mục kẻ địch tủy chỉnh sẽ cho phép điều chỉnh số hp.", "detailTheoryCraft": "Khi bật tính năng này sẽ cho phép tùy chỉnh số cycle và trong mục kẻ địch tủy chỉnh sẽ cho phép điều chỉnh số hp.",
"detailSkipNode": "Khi bật tính năng này sẽ cho phép bỏ qua (node 1/node 2) của Hồi ức hỗn độn hoặc Kể chuyện hư cấu.", "detailSkipNode": "Khi bật tính năng này sẽ cho phép bỏ qua (node 1/node 2) của Hồi ức hỗn độn hoặc Kể chuyện hư cấu.",
"detailChallengePeak": "Cho phép thay đổi mùa Trọng tại dị tướng hiện tại.", "detailChallengePeak": "Cho phép thay đổi mùa Trọng tại dị tướng hiện tại.",
"detailHiddenUi": "Khi bật tính năng này sẽ ẩn giao diện của game.", "detailHiddenUi": "Khi bật tính năng này sẽ ẩn giao diện của game.",
"detailDisableCensorship": "Khi bật tính năng này sẽ tắt kiểm duyệt của game.", "detailDisableCensorship": "Khi bật tính năng này sẽ tắt kiểm duyệt của game.",
"detailMultipathCharacter": "Cho phép thay đổi Vận Mệnh của một vài nhân vật.", "detailMultipathCharacter": "Cho phép thay đổi Vận Mệnh của một vài nhân vật.",
"trailblazer": "Nhà khai phá", "trailblazer": "Nhà khai phá",
"listExtraEffect": "Danh sách hiệu ứng bổ sung", "listExtraEffect": "Danh sách hiệu ứng bổ sung",
"extra": "Bổ sung", "extra": "Bổ sung",
"customLineup": "Đội hình tùy chỉnh" "customLineup": "Đội hình tùy chỉnh"
} }
} }
+13 -13
View File
@@ -1,13 +1,13 @@
@echo off @echo off
echo [INFO] Create folder src\zod if not exist... echo [INFO] Create folder src\zod if not exist...
if not exist src\zod ( if not exist src\zod (
mkdir src\zod mkdir src\zod
) )
echo [INFO] Start convert file .ts from src\types to Zod schemas... echo [INFO] Start convert file .ts from src\types to Zod schemas...
for %%f in (src\types\*.ts) do ( for %%f in (src\types\*.ts) do (
echo [ZOD] Chuyển %%f -> src\zod\%%~nf.zod.ts echo [ZOD] Chuyển %%f -> src\zod\%%~nf.zod.ts
npx ts-to-zod src\types\%%~nxf src\zod\%%~nf.zod.ts npx ts-to-zod src\types\%%~nxf src\zod\%%~nf.zod.ts
) )
echo [DONE] ✅ Done echo [DONE] ✅ Done
+24 -24
View File
@@ -1,25 +1,25 @@
import { getDataCache } from "@/lib/cache/cache" import { getDataCache } from "@/lib/cache/cache"
export async function GET( export async function GET(
req: Request, req: Request,
{ params }: { params: Promise<{ name: string }> } { params }: { params: Promise<{ name: string }> }
) { ) {
const { name } = await params const { name } = await params
const item = getDataCache(name) const item = getDataCache(name)
if (!item) { if (!item) {
return new Response("Not found", { status: 404 }) return new Response("Not found", { status: 404 })
} }
const headers: Record<string, string> = { const headers: Record<string, string> = {
"Content-Type": "application/json", "Content-Type": "application/json",
"Cache-Control": "public, max-age=3600" "Cache-Control": "public, max-age=3600"
} }
if (item.type === "br") { if (item.type === "br") {
headers["Content-Encoding"] = "br" headers["Content-Encoding"] = "br"
} }
return new Response(new Uint8Array(item.buf), { headers }) return new Response(new Uint8Array(item.buf), { headers })
} }
+70 -70
View File
@@ -1,79 +1,79 @@
import { NextRequest, NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import axios from 'axios' import axios from 'axios'
import net from 'net' import net from 'net'
function isPrivateHost(hostname: string): boolean { function isPrivateHost(hostname: string): boolean {
if ( if (
hostname === 'localhost' || hostname === 'localhost' ||
hostname.startsWith('127.') || hostname.startsWith('127.') ||
hostname.startsWith('10.') || hostname.startsWith('10.') ||
hostname.startsWith('192.168.') || hostname.startsWith('192.168.') ||
/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname) /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)
) { ) {
return true return true
} }
if (net.isIP(hostname)) { if (net.isIP(hostname)) {
return true return true
} }
return false return false
} }
export async function GET(req: NextRequest) { export async function GET(req: NextRequest) {
try { try {
const { searchParams } = new URL(req.url) const { searchParams } = new URL(req.url)
const targetUrl = searchParams.get("url") const targetUrl = searchParams.get("url")
if (!targetUrl) { if (!targetUrl) {
return NextResponse.json({ error: "Missing url" }, { status: 400 }) return NextResponse.json({ error: "Missing url" }, { status: 400 })
} }
const response = await fetch(targetUrl) const response = await fetch(targetUrl)
if (!response.ok) { if (!response.ok) {
return NextResponse.json({ error: "Failed to fetch image" }, { status: 500 }) return NextResponse.json({ error: "Failed to fetch image" }, { status: 500 })
} }
const buffer = await response.arrayBuffer() const buffer = await response.arrayBuffer()
return new NextResponse(buffer, { return new NextResponse(buffer, {
headers: { headers: {
"Content-Type": response.headers.get("content-type") || "image/png", "Content-Type": response.headers.get("content-type") || "image/png",
"Cache-Control": "public, max-age=3600", "Cache-Control": "public, max-age=3600",
"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Origin": "*",
}, },
}) })
} catch (e: unknown) { } catch (e: unknown) {
return NextResponse.json({ error: (e as Error)?.message }, { status: 500 }) return NextResponse.json({ error: (e as Error)?.message }, { status: 500 })
} }
} }
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
const body = await request.json() const body = await request.json()
const { serverUrl, method, ...payload } = body const { serverUrl, method, ...payload } = body
if (!serverUrl) { if (!serverUrl) {
return NextResponse.json({ error: 'Missing serverUrl' }, { status: 400 }) return NextResponse.json({ error: 'Missing serverUrl' }, { status: 400 })
} }
if (!method) { if (!method) {
return NextResponse.json({ error: 'Missing method' }, { status: 400 }) return NextResponse.json({ error: 'Missing method' }, { status: 400 })
} }
let url = serverUrl.trim() let url = serverUrl.trim()
if (!url.startsWith('http://') && !url.startsWith('https://')) { if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = `http://${url}` url = `http://${url}`
} }
const parsed = new URL(url) const parsed = new URL(url)
if (isPrivateHost(parsed.hostname)) { if (isPrivateHost(parsed.hostname)) {
return NextResponse.json( return NextResponse.json(
{ error: `Connection to private/internal address (${parsed.hostname}) is not allowed` }, { error: `Connection to private/internal address (${parsed.hostname}) is not allowed` },
{ status: 403 } { status: 403 }
) )
} }
let response let response
switch (method.toUpperCase()) { switch (method.toUpperCase()) {
+9 -9
View File
@@ -1,9 +1,9 @@
"use client" "use client"
import EidolonsInfo from "@/components/eidolonsInfo"; import EidolonsInfo from "@/components/eidolonsInfo";
export default function EidolonsInfoPage() { export default function EidolonsInfoPage() {
return ( return (
<div className="w-full"> <div className="w-full">
<EidolonsInfo/> <EidolonsInfo/>
</div> </div>
); );
} }
+11 -11
View File
@@ -1,11 +1,11 @@
"use client"; "use client";
import RelicsInfo from "@/components/relicsInfo"; import RelicsInfo from "@/components/relicsInfo";
export default function RelicsInfoPage() { export default function RelicsInfoPage() {
return ( return (
<div className="w-full"> <div className="w-full">
<RelicsInfo></RelicsInfo> <RelicsInfo></RelicsInfo>
</div> </div>
); );
} }
+11 -11
View File
@@ -1,11 +1,11 @@
"use client" "use client"
import ShowCaseInfo from "@/components/showcaseCard" import ShowCaseInfo from "@/components/showcaseCard"
export default function ShowcaseCard() { export default function ShowcaseCard() {
return ( return (
<div> <div>
<ShowCaseInfo /> <ShowCaseInfo />
</div> </div>
) )
} }
+11 -11
View File
@@ -1,11 +1,11 @@
"use client"; "use client";
import SkillsInfo from "@/components/skillsInfo"; import SkillsInfo from "@/components/skillsInfo";
export default function SkillsInfoPage() { export default function SkillsInfoPage() {
return ( return (
<div className="w-full"> <div className="w-full">
<SkillsInfo></SkillsInfo> <SkillsInfo></SkillsInfo>
</div> </div>
); );
} }
+444 -444
View File
@@ -1,445 +1,445 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
"use client"; "use client";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import ParseText from "../parseText"; import ParseText from "../parseText";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import { getNameChar } from "@/helper/getName"; import { getNameChar } from "@/helper/getName";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import useModelStore from "@/stores/modelStore"; import useModelStore from "@/stores/modelStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import { ModalConfig, RelicStore } from "@/types"; import { ModalConfig, RelicStore } from "@/types";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import useGlobalStore from "@/stores/globalStore"; import useGlobalStore from "@/stores/globalStore";
import { connectToPS, syncDataToPS } from "@/helper"; import { connectToPS, syncDataToPS } from "@/helper";
import CopyImport from "../importBar/copy"; import CopyImport from "../importBar/copy";
import useCopyProfileStore from "@/stores/copyProfile"; import useCopyProfileStore from "@/stores/copyProfile";
import AvatarBar from "../avatarBar"; import AvatarBar from "../avatarBar";
import useCurrentDataStore from "@/stores/currentDataStore"; import useCurrentDataStore from "@/stores/currentDataStore";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
export default function ActionBar() { export default function ActionBar() {
const router = useRouter() const router = useRouter()
const { avatarSelected } = useCurrentDataStore() const { avatarSelected } = useCurrentDataStore()
const { damageType, baseType } = useDetailDataStore() const { damageType, baseType } = useDetailDataStore()
const { setResetData } = useCopyProfileStore() const { setResetData } = useCopyProfileStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
isOpenCreateProfile, isOpenCreateProfile,
setIsOpenCreateProfile, setIsOpenCreateProfile,
isOpenCopy, isOpenCopy,
setIsOpenCopy, setIsOpenCopy,
isOpenAvatars, isOpenAvatars,
setIsOpenAvatars setIsOpenAvatars
} = useModelStore() } = useModelStore()
const { avatars, setAvatar } = useUserDataStore() const { avatars, setAvatar } = useUserDataStore()
const [profileName, setProfileName] = useState(""); const [profileName, setProfileName] = useState("");
const [formState, setFormState] = useState("EDIT"); const [formState, setFormState] = useState("EDIT");
const [profileEdit, setProfileEdit] = useState(-1); const [profileEdit, setProfileEdit] = useState(-1);
const { isConnectPS } = useGlobalStore() const { isConnectPS } = useGlobalStore()
const profileCurrent = useMemo(() => { const profileCurrent = useMemo(() => {
if (!avatarSelected) return null; if (!avatarSelected) return null;
const avatar = avatars[avatarSelected.ID]; const avatar = avatars[avatarSelected.ID];
return avatar?.profileList[avatar.profileSelect] || null; return avatar?.profileList[avatar.profileSelect] || null;
}, [avatarSelected, avatars]); }, [avatarSelected, avatars]);
const listProfile = useMemo(() => { const listProfile = useMemo(() => {
if (!avatarSelected) return []; if (!avatarSelected) return [];
const avatar = avatars[avatarSelected.ID]; const avatar = avatars[avatarSelected.ID];
return avatar?.profileList || []; return avatar?.profileList || [];
}, [avatarSelected, avatars]); }, [avatarSelected, avatars]);
const handleUpdateProfile = () => { const handleUpdateProfile = () => {
if (!profileName.trim()) return; if (!profileName.trim()) return;
if (formState === "CREATE" && avatarSelected && avatars[avatarSelected.ID]) { if (formState === "CREATE" && avatarSelected && avatars[avatarSelected.ID]) {
const newListProfile = [...listProfile] const newListProfile = [...listProfile]
const newProfile = { const newProfile = {
profile_name: profileName, profile_name: profileName,
lightcone: null, lightcone: null,
relics: {} as Record<string, RelicStore> relics: {} as Record<string, RelicStore>
} }
newListProfile.push(newProfile) newListProfile.push(newProfile)
setAvatar({ ...avatars[avatarSelected.ID], profileList: newListProfile, profileSelect: newListProfile.length - 1 }) setAvatar({ ...avatars[avatarSelected.ID], profileList: newListProfile, profileSelect: newListProfile.length - 1 })
toast.success("Profile created successfully") toast.success("Profile created successfully")
} else if (formState === "EDIT" && profileCurrent && avatarSelected && profileEdit !== -1) { } else if (formState === "EDIT" && profileCurrent && avatarSelected && profileEdit !== -1) {
const newListProfile = [...listProfile] const newListProfile = [...listProfile]
newListProfile[profileEdit].profile_name = profileName; newListProfile[profileEdit].profile_name = profileName;
setAvatar({ ...avatars[avatarSelected.ID], profileList: newListProfile }) setAvatar({ ...avatars[avatarSelected.ID], profileList: newListProfile })
toast.success("Profile updated successfully") toast.success("Profile updated successfully")
} }
handleCloseModal("update_profile_modal"); handleCloseModal("update_profile_modal");
}; };
const handleShow = (modalId: string) => { const handleShow = (modalId: string) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null; const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) { if (modal) {
modal.showModal(); modal.showModal();
} }
}; };
// Close modal handler // Close modal handler
const handleCloseModal = (modalId: string) => { const handleCloseModal = (modalId: string) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null; const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) { if (modal) {
modal.close(); modal.close();
} }
}; };
const actionMove = (path: string) => { const actionMove = (path: string) => {
router.push(`/${path}`) router.push(`/${path}`)
} }
const handleProfileSelect = (profileId: number) => { const handleProfileSelect = (profileId: number) => {
if (!avatarSelected) return; if (!avatarSelected) return;
if (avatars[avatarSelected.ID].profileSelect === profileId) return; if (avatars[avatarSelected.ID].profileSelect === profileId) return;
setAvatar({ ...avatars[avatarSelected.ID], profileSelect: profileId }) setAvatar({ ...avatars[avatarSelected.ID], profileSelect: profileId })
toast.success(`Profile changed to Profile: ${avatars[avatarSelected.ID].profileList[profileId].profile_name}`) toast.success(`Profile changed to Profile: ${avatars[avatarSelected.ID].profileList[profileId].profile_name}`)
} }
const handleDeleteProfile = (profileId: number, e: React.MouseEvent) => { const handleDeleteProfile = (profileId: number, e: React.MouseEvent) => {
e.stopPropagation() e.stopPropagation()
if (!avatarSelected || profileId == 0) return; if (!avatarSelected || profileId == 0) return;
if (window.confirm(`Are you sure you want to delete profile: ${avatars[avatarSelected.ID].profileList[profileId].profile_name}?`)) { if (window.confirm(`Are you sure you want to delete profile: ${avatars[avatarSelected.ID].profileList[profileId].profile_name}?`)) {
const newListProfile = [...listProfile] const newListProfile = [...listProfile]
newListProfile.splice(profileId, 1) newListProfile.splice(profileId, 1)
setAvatar({ ...avatars[avatarSelected.ID], profileList: newListProfile, profileSelect: profileId - 1 }) setAvatar({ ...avatars[avatarSelected.ID], profileList: newListProfile, profileSelect: profileId - 1 })
toast.success(`Profile ${avatars[avatarSelected.ID].profileList[profileId].profile_name} deleted successfully`) toast.success(`Profile ${avatars[avatarSelected.ID].profileList[profileId].profile_name} deleted successfully`)
} }
} }
const handleConnectOrSyncPS = async () => { const handleConnectOrSyncPS = async () => {
if (isConnectPS) { if (isConnectPS) {
const res = await syncDataToPS() const res = await syncDataToPS()
if (res.success) { if (res.success) {
toast.success(transI18n("syncSuccess")) toast.success(transI18n("syncSuccess"))
} else { } else {
toast.error(`${transI18n("syncFailed")}: ${res.message}`) toast.error(`${transI18n("syncFailed")}: ${res.message}`)
} }
} else { } else {
const res = await connectToPS() const res = await connectToPS()
if (res.success) { if (res.success) {
toast.success(transI18n("connectedSuccess")) toast.success(transI18n("connectedSuccess"))
} else { } else {
toast.error(`${transI18n("connectedFailed")}: ${res.message}`) toast.error(`${transI18n("connectedFailed")}: ${res.message}`)
} }
} }
} }
const modalConfigs: ModalConfig[] = [ const modalConfigs: ModalConfig[] = [
{ {
id: "update_profile_modal", id: "update_profile_modal",
title: formState === "CREATE" ? transI18n("createNewProfile") : transI18n("editProfile"), title: formState === "CREATE" ? transI18n("createNewProfile") : transI18n("editProfile"),
isOpen: isOpenCreateProfile, isOpen: isOpenCreateProfile,
onClose: () => { onClose: () => {
setIsOpenCreateProfile(false) setIsOpenCreateProfile(false)
handleCloseModal("update_profile_modal") handleCloseModal("update_profile_modal")
}, },
content: ( content: (
<div className="px-6 space-y-4"> <div className="px-6 space-y-4">
<div className="form-control"> <div className="form-control">
<label className="label"> <label className="label">
<span className="label-text text-primary font-semibold text-lg"> <span className="label-text text-primary font-semibold text-lg">
{transI18n("profileName")} {transI18n("profileName")}
</span> </span>
</label> </label>
<input <input
type="text" type="text"
placeholder={transI18n("placeholderProfileName")} placeholder={transI18n("placeholderProfileName")}
className="input input-warning mt-1 w-full" className="input input-warning mt-1 w-full"
value={profileName} value={profileName}
onChange={(e) => setProfileName(e.target.value)} onChange={(e) => setProfileName(e.target.value)}
/> />
</div> </div>
<div className="modal-action"> <div className="modal-action">
<button className="btn btn-success btn-sm sm:btn-md" onClick={handleUpdateProfile}> <button className="btn btn-success btn-sm sm:btn-md" onClick={handleUpdateProfile}>
{formState === "CREATE" ? transI18n("create") : transI18n("update")} {formState === "CREATE" ? transI18n("create") : transI18n("update")}
</button> </button>
</div> </div>
</div> </div>
) )
}, },
{ {
id: "copy_profile_modal", id: "copy_profile_modal",
title: transI18n("copyProfiles").toUpperCase(), title: transI18n("copyProfiles").toUpperCase(),
isOpen: isOpenCopy, isOpen: isOpenCopy,
onClose: () => { onClose: () => {
setIsOpenCopy(false) setIsOpenCopy(false)
handleCloseModal("copy_profile_modal") handleCloseModal("copy_profile_modal")
}, },
content: <CopyImport /> content: <CopyImport />
}, },
{ {
id: "avatars_modal", id: "avatars_modal",
title: transI18n("avatars").toUpperCase(), title: transI18n("avatars").toUpperCase(),
isOpen: isOpenAvatars, isOpen: isOpenAvatars,
onClose: () => { onClose: () => {
setIsOpenAvatars(false) setIsOpenAvatars(false)
handleCloseModal("avatars_modal") handleCloseModal("avatars_modal")
}, },
content: <AvatarBar onClose={() => { setIsOpenAvatars(false); handleCloseModal("avatars_modal") }} /> content: <AvatarBar onClose={() => { setIsOpenAvatars(false); handleCloseModal("avatars_modal") }} />
} }
] ]
// Handle ESC key to close modal // Handle ESC key to close modal
useEffect(() => { useEffect(() => {
for (const item of modalConfigs) { for (const item of modalConfigs) {
if (!item?.isOpen) { if (!item?.isOpen) {
handleCloseModal(item?.id || "") handleCloseModal(item?.id || "")
} }
} }
const handleEscKey = (event: KeyboardEvent) => { const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape') { if (event.key === 'Escape') {
for (const item of modalConfigs) { for (const item of modalConfigs) {
handleCloseModal(item?.id || "") handleCloseModal(item?.id || "")
} }
} }
}; };
window.addEventListener('keydown', handleEscKey); window.addEventListener('keydown', handleEscKey);
return () => window.removeEventListener('keydown', handleEscKey); return () => window.removeEventListener('keydown', handleEscKey);
}, [isOpenCopy, isOpenCreateProfile, isOpenAvatars]); }, [isOpenCopy, isOpenCreateProfile, isOpenAvatars]);
return ( return (
<div className="w-full px-4 pb-4 bg-base-200"> <div className="w-full px-4 pb-4 bg-base-200">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 items-center justify-items-center"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 items-center justify-items-center">
<div className="flex flex-col justify-center w-full"> <div className="flex flex-col justify-center w-full">
<div className="flex flex-wrap items-center gap-2 "> <div className="flex flex-wrap items-center gap-2 ">
<div className="flex flex-wrap items-center h-full opacity-80 lg:hover:opacity-100 cursor-pointer text-base md:text-lg lg:text-xl"> <div className="flex flex-wrap items-center h-full opacity-80 lg:hover:opacity-100 cursor-pointer text-base md:text-lg lg:text-xl">
{avatarSelected && ( {avatarSelected && (
<div className="flex flex-wrap items-center justify-start h-full w-full"> <div className="flex flex-wrap items-center justify-start h-full w-full">
<Image <Image
src={`${process.env.CDN_URL}/${damageType?.[avatarSelected?.DamageType]?.Icon}`} src={`${process.env.CDN_URL}/${damageType?.[avatarSelected?.DamageType]?.Icon}`}
alt={'damage type'} alt={'damage type'}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
className="h-10 w-10 object-contain" className="h-10 w-10 object-contain"
width={100} width={100}
height={100} height={100}
/> />
<p className="text-center font-bold text-lg"> <p className="text-center font-bold text-lg">
{transI18n(avatarSelected.BaseType.toLowerCase())} {transI18n(avatarSelected.BaseType.toLowerCase())}
</p> </p>
<div className="text-center font-bold text-lg">{" / "}</div> <div className="text-center font-bold text-lg">{" / "}</div>
<ParseText <ParseText
locale={locale} locale={locale}
text={getNameChar(locale, transI18n, avatarSelected).toWellFormed()} text={getNameChar(locale, transI18n, avatarSelected).toWellFormed()}
className={"font-bold text-lg"} className={"font-bold text-lg"}
/> />
<div className="text-center italic text-sm ml-2"> {`(${avatarSelected.ID})`}</div> <div className="text-center italic text-sm ml-2"> {`(${avatarSelected.ID})`}</div>
</div> </div>
)} )}
</div> </div>
</div> </div>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<span className="text-base opacity-70 font-bold w-16">{transI18n("profile")}:</span> <span className="text-base opacity-70 font-bold w-16">{transI18n("profile")}:</span>
<div className="dropdown dropdown-center md:dropdown-start"> <div className="dropdown dropdown-center md:dropdown-start">
<div <div
tabIndex={0} tabIndex={0}
role="button" role="button"
className="btn btn-warning border-info btn-soft gap-1" className="btn btn-warning border-info btn-soft gap-1"
> >
<span className="truncate max-w-48 font-bold"> <span className="truncate max-w-48 font-bold">
{profileCurrent?.profile_name} {profileCurrent?.profile_name}
</span> </span>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg> </svg>
</div> </div>
<ul className="dropdown-content z-10 menu p-2 shadow bg-base-100 rounded-box min-w-max w-full mt-1 border border-base-300 max-h-60 overflow-y-auto"> <ul className="dropdown-content z-10 menu p-2 shadow bg-base-100 rounded-box min-w-max w-full mt-1 border border-base-300 max-h-60 overflow-y-auto">
{listProfile.map((profile, index) => ( {listProfile.map((profile, index) => (
<li key={index} className="grid grid-cols-12"> <li key={index} className="grid grid-cols-12">
<button <button
className={`col-span-8 btn btn-ghost`} className={`col-span-8 btn btn-ghost`}
onClick={() => handleProfileSelect(index)} onClick={() => handleProfileSelect(index)}
> >
<span className="flex-1 truncate text-left"> <span className="flex-1 truncate text-left">
{profile.profile_name} {profile.profile_name}
{index === 0 && ( {index === 0 && (
<span className="text-xs text-base-content/60 ml-1">({transI18n("default")})</span> <span className="text-xs text-base-content/60 ml-1">({transI18n("default")})</span>
)} )}
</span> </span>
</button> </button>
{index !== 0 && ( {index !== 0 && (
<> <>
<button <button
className="col-span-2 flex items-center justify-center text-lg text-warning hover:bg-warning/20 z-20 w-full h-full" className="col-span-2 flex items-center justify-center text-lg text-warning hover:bg-warning/20 z-20 w-full h-full"
onClick={() => { onClick={() => {
setFormState("EDIT"); setFormState("EDIT");
setProfileName(profile.profile_name) setProfileName(profile.profile_name)
setProfileEdit(index) setProfileEdit(index)
setIsOpenCreateProfile(true) setIsOpenCreateProfile(true)
handleShow("update_profile_modal") handleShow("update_profile_modal")
}} }}
title="Edit Profile" title="Edit Profile"
> >
<svg className="w-4 h-4 " fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 " fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536M4 20h4l10.293-10.293a1 1 0 000-1.414l-2.586-2.586a1 1 0 00-1.414 0L4 16v4z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536M4 20h4l10.293-10.293a1 1 0 000-1.414l-2.586-2.586a1 1 0 00-1.414 0L4 16v4z" />
</svg> </svg>
</button> </button>
<button <button
className="col-span-2 flex items-center justify-center text-error hover:bg-error/20 z-20 w-full h-full text-lg" className="col-span-2 flex items-center justify-center text-error hover:bg-error/20 z-20 w-full h-full text-lg"
onClick={(e) => { onClick={(e) => {
handleDeleteProfile(index, e) handleDeleteProfile(index, e)
}} }}
title="Delete Profile" title="Delete Profile"
> >
<svg className="w-4 h-4 " fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 " fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6M9 7V4h6v3m5 0H4" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6M9 7V4h6v3m5 0H4" />
</svg> </svg>
</button> </button>
</> </>
)} )}
</li> </li>
))} ))}
<li className="border-t border-base-300 mt-2 pt-2 z-10"> <li className="border-t border-base-300 mt-2 pt-2 z-10">
<button <button
onClick={() => { onClick={() => {
setIsOpenCopy(true) setIsOpenCopy(true)
setResetData(baseType, damageType) setResetData(baseType, damageType)
handleShow("copy_profile_modal") handleShow("copy_profile_modal")
}} }}
className="btn btn-ghost flex justify-start px-3 py-2 h-full w-full hover:bg-base-200 cursor-pointer text-primary z-20" className="btn btn-ghost flex justify-start px-3 py-2 h-full w-full hover:bg-base-200 cursor-pointer text-primary z-20"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg> </svg>
{transI18n("copyProfiles")} {transI18n("copyProfiles")}
</button> </button>
<button <button
className="btn btn-ghost flex justify-start px-3 py-2 h-full w-full hover:bg-base-200 cursor-pointer text-primary z-20" className="btn btn-ghost flex justify-start px-3 py-2 h-full w-full hover:bg-base-200 cursor-pointer text-primary z-20"
onClick={() => { onClick={() => {
setIsOpenCreateProfile(true) setIsOpenCreateProfile(true)
setFormState("CREATE"); setFormState("CREATE");
setProfileName("") setProfileName("")
handleShow("update_profile_modal") handleShow("update_profile_modal")
}} }}
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg> </svg>
{transI18n("addNewProfile")} {transI18n("addNewProfile")}
</button> </button>
</li> </li>
</ul> </ul>
</div> </div>
<div className=" grid grid-cols-2 w-full sm:hidden gap-2"> <div className=" grid grid-cols-2 w-full sm:hidden gap-2">
<button <button
onClick={() => { onClick={() => {
setIsOpenAvatars(true) setIsOpenAvatars(true)
handleShow("avatars_modal") handleShow("avatars_modal")
}} }}
className="col-span-1 btn btn-warning btn-sm w-full"> className="col-span-1 btn btn-warning btn-sm w-full">
{transI18n("avatars")} {transI18n("avatars")}
</button> </button>
<div className="col-span-1 dropdown dropdown-center w-full"> <div className="col-span-1 dropdown dropdown-center w-full">
<label tabIndex={0} className="btn btn-info btn-sm w-full"> <label tabIndex={0} className="btn btn-info btn-sm w-full">
{transI18n("actions")} {transI18n("actions")}
</label> </label>
<ul <ul
tabIndex={0} tabIndex={0}
className="dropdown-content z-10 menu p-2 shadow bg-base-100 rounded-box min-w-max w-full mt-1 border border-base-300 max-h-60 overflow-y-auto" className="dropdown-content z-10 menu p-2 shadow bg-base-100 rounded-box min-w-max w-full mt-1 border border-base-300 max-h-60 overflow-y-auto"
> >
<li> <li>
<button onClick={() => actionMove('')}> <button onClick={() => actionMove('')}>
{transI18n("characterInformation")} {transI18n("characterInformation")}
</button> </button>
</li> </li>
<li> <li>
<button onClick={() => actionMove('relics-info')}> <button onClick={() => actionMove('relics-info')}>
{transI18n("relics")} {transI18n("relics")}
</button> </button>
</li> </li>
<li> <li>
<button onClick={() => actionMove('eidolons-info')}> <button onClick={() => actionMove('eidolons-info')}>
{transI18n("eidolons")} {transI18n("eidolons")}
</button> </button>
</li> </li>
<li> <li>
<button onClick={() => actionMove('skills-info')}> <button onClick={() => actionMove('skills-info')}>
{transI18n("skills")} {transI18n("skills")}
</button> </button>
</li> </li>
<li> <li>
<button onClick={() => actionMove('showcase-card')}> <button onClick={() => actionMove('showcase-card')}>
{transI18n("showcaseCard")} {transI18n("showcaseCard")}
</button> </button>
</li> </li>
<li> <li>
<button onClick={handleConnectOrSyncPS} className="btn btn-primary btn-sm"> <button onClick={handleConnectOrSyncPS} className="btn btn-primary btn-sm">
{isConnectPS ? transI18n("sync") : transI18n("connectPs")} {isConnectPS ? transI18n("sync") : transI18n("connectPs")}
</button> </button>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="hidden sm:grid grid-cols-3 gap-2 w-full"> <div className="hidden sm:grid grid-cols-3 gap-2 w-full">
<button className="btn btn-success btn-sm" onClick={() => actionMove("")}> <button className="btn btn-success btn-sm" onClick={() => actionMove("")}>
{transI18n("characterInformation")} {transI18n("characterInformation")}
</button> </button>
<button className="btn btn-success btn-sm" onClick={() => actionMove("relics-info")}> <button className="btn btn-success btn-sm" onClick={() => actionMove("relics-info")}>
{transI18n("relics")} {transI18n("relics")}
</button> </button>
<button className="btn btn-success btn-sm" onClick={() => actionMove("eidolons-info")}> <button className="btn btn-success btn-sm" onClick={() => actionMove("eidolons-info")}>
{transI18n("eidolons")} {transI18n("eidolons")}
</button> </button>
<button className="btn btn-success btn-sm" onClick={() => actionMove("skills-info")}> <button className="btn btn-success btn-sm" onClick={() => actionMove("skills-info")}>
{transI18n("skills")} {transI18n("skills")}
</button> </button>
<button className="btn btn-success btn-sm" onClick={() => actionMove("showcase-card")}> <button className="btn btn-success btn-sm" onClick={() => actionMove("showcase-card")}>
{transI18n("showcaseCard")} {transI18n("showcaseCard")}
</button> </button>
<button onClick={handleConnectOrSyncPS} className="btn btn-primary btn-sm"> <button onClick={handleConnectOrSyncPS} className="btn btn-primary btn-sm">
{isConnectPS ? transI18n("sync") : transI18n("connectPs")} {isConnectPS ? transI18n("sync") : transI18n("connectPs")}
</button> </button>
</div> </div>
{modalConfigs.map(({ id, title, onClose, content }) => ( {modalConfigs.map(({ id, title, onClose, content }) => (
<dialog key={id} id={id} className="modal"> <dialog key={id} id={id} className="modal">
<div className="modal-box w-11/12 max-w-[90%] max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="modal-box w-11/12 max-w-[90%] max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10"> <div className="sticky top-0 z-10">
<motion.button <motion.button
whileHover={{ scale: 1.1, rotate: 90 }} whileHover={{ scale: 1.1, rotate: 90 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none" className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
onClick={onClose} onClick={onClose}
> >
</motion.button> </motion.button>
</div> </div>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4"> <div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400"> <h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400">
{title} {title}
</h3> </h3>
</div> </div>
{content} {content}
</div> </div>
</dialog> </dialog>
))} ))}
</div> </div>
</div> </div>
); );
} }
+140 -140
View File
@@ -1,140 +1,140 @@
"use client" "use client"
import Image from "next/image" import Image from "next/image"
import { useMemo } from "react" import { useMemo } from "react"
import CharacterCard from "../card/characterCard" import CharacterCard from "../card/characterCard"
import useLocaleStore from "@/stores/localeStore" import useLocaleStore from "@/stores/localeStore"
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import useDetailDataStore from "@/stores/detailDataStore" import useDetailDataStore from "@/stores/detailDataStore"
import useCurrentDataStore from '@/stores/currentDataStore'; import useCurrentDataStore from '@/stores/currentDataStore';
import { calcRarity, getNameChar } from "@/helper" import { calcRarity, getNameChar } from "@/helper"
export default function AvatarBar({ onClose }: { onClose?: () => void }) { export default function AvatarBar({ onClose }: { onClose?: () => void }) {
const { const {
avatarSearch, avatarSearch,
mapAvatarElementActive, mapAvatarElementActive,
mapAvatarPathActive, mapAvatarPathActive,
setAvatarSearch, setAvatarSearch,
setAvatarSelected, setAvatarSelected,
setMapAvatarElementActive, setMapAvatarElementActive,
setMapAvatarPathActive, setMapAvatarPathActive,
setSkillIDSelected, setSkillIDSelected,
} = useCurrentDataStore() } = useCurrentDataStore()
const { mapAvatar, baseType, damageType } = useDetailDataStore() const { mapAvatar, baseType, damageType } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const {locale} = useLocaleStore() const {locale} = useLocaleStore()
const listAvatar = useMemo(() => { const listAvatar = useMemo(() => {
if (!mapAvatar || !locale || !transI18n) return [] if (!mapAvatar || !locale || !transI18n) return []
let list = Object.values(mapAvatar) let list = Object.values(mapAvatar)
if (avatarSearch) { if (avatarSearch) {
list = list.filter(item => list = list.filter(item =>
getNameChar(locale, transI18n, item) getNameChar(locale, transI18n, item)
.toLowerCase() .toLowerCase()
.includes(avatarSearch.toLowerCase()) .includes(avatarSearch.toLowerCase())
) )
} }
const allElementFalse = !Object.values(mapAvatarElementActive).some(v => v) const allElementFalse = !Object.values(mapAvatarElementActive).some(v => v)
const allPathFalse = !Object.values(mapAvatarPathActive).some(v => v) const allPathFalse = !Object.values(mapAvatarPathActive).some(v => v)
list = list.filter(item => list = list.filter(item =>
(allElementFalse || mapAvatarElementActive[item.DamageType]) && (allElementFalse || mapAvatarElementActive[item.DamageType]) &&
(allPathFalse || mapAvatarPathActive[item.BaseType]) (allPathFalse || mapAvatarPathActive[item.BaseType])
) )
list.sort((a, b) => { list.sort((a, b) => {
const r = calcRarity(b.Rarity) - calcRarity(a.Rarity) const r = calcRarity(b.Rarity) - calcRarity(a.Rarity)
if (r !== 0) return r if (r !== 0) return r
return b.ID - a.ID return b.ID - a.ID
}) })
return list return list
}, [mapAvatar, mapAvatarElementActive, mapAvatarPathActive, avatarSearch, locale, transI18n]) }, [mapAvatar, mapAvatarElementActive, mapAvatarPathActive, avatarSearch, locale, transI18n])
return ( return (
<div className="grid grid-flow-row h-full auto-rows-max w-full"> <div className="grid grid-flow-row h-full auto-rows-max w-full">
<div className="h-full rounded-lg mx-2 py-2"> <div className="h-full rounded-lg mx-2 py-2">
<div className="container"> <div className="container">
<div className="flex flex-col h-full gap-2"> <div className="flex flex-col h-full gap-2">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex justify-center"> <div className="flex justify-center">
<input type="text" <input type="text"
placeholder={transI18n("placeholderCharacter")} placeholder={transI18n("placeholderCharacter")}
className="input input-bordered input-primary w-full" className="input input-bordered input-primary w-full"
value={avatarSearch} value={avatarSearch}
onChange={(e) => setAvatarSearch(e.target.value)} onChange={(e) => setAvatarSearch(e.target.value)}
/> />
</div> </div>
<div className="grid grid-cols-7 sm:grid-cols-4 lg:grid-cols-7 mb-1 mx-1 gap-2 w-full max-h-[17vh] min-h-[5vh] overflow-y-auto"> <div className="grid grid-cols-7 sm:grid-cols-4 lg:grid-cols-7 mb-1 mx-1 gap-2 w-full max-h-[17vh] min-h-[5vh] overflow-y-auto">
{Object.entries(damageType).filter(([key]) => key !== "").map(([key, value]) => ( {Object.entries(damageType).filter(([key]) => key !== "").map(([key, value]) => (
<div <div
key={key} key={key}
onClick={() => { onClick={() => {
setMapAvatarElementActive({ ...mapAvatarElementActive, [key]: !mapAvatarElementActive[key] }) setMapAvatarElementActive({ ...mapAvatarElementActive, [key]: !mapAvatarElementActive[key] })
}} }}
className="hover:bg-gray-600 grid items-center justify-items-center cursor-pointer rounded-md shadow-lg" className="hover:bg-gray-600 grid items-center justify-items-center cursor-pointer rounded-md shadow-lg"
style={{ style={{
backgroundColor: mapAvatarElementActive[key] ? "#374151" : "#6B7280" backgroundColor: mapAvatarElementActive[key] ? "#374151" : "#6B7280"
}}> }}>
<Image <Image
src={`${process.env.CDN_URL}/${value.Icon}`} src={`${process.env.CDN_URL}/${value.Icon}`}
alt={key} alt={key}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md" className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md"
width={200} width={200}
height={200} /> height={200} />
</div> </div>
))} ))}
</div> </div>
<div className="grid grid-cols-9 sm:grid-cols-5 lg:grid-cols-9 mb-1 mx-1 gap-2 overflow-y-auto w-full max-h-[17vh] min-h-[5vh]"> <div className="grid grid-cols-9 sm:grid-cols-5 lg:grid-cols-9 mb-1 mx-1 gap-2 overflow-y-auto w-full max-h-[17vh] min-h-[5vh]">
{Object.entries(baseType).filter(([key]) => key !== "").map(([key, value]) => ( {Object.entries(baseType).filter(([key]) => key !== "").map(([key, value]) => (
<div <div
key={key} key={key}
onClick={() => { onClick={() => {
setMapAvatarPathActive({ ...mapAvatarPathActive, [key]: !mapAvatarPathActive[key] }) setMapAvatarPathActive({ ...mapAvatarPathActive, [key]: !mapAvatarPathActive[key] })
}} }}
className="hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-lg cursor-pointer" className="hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-lg cursor-pointer"
style={{ style={{
backgroundColor: mapAvatarPathActive[key] ? "#374151" : "#6B7280" backgroundColor: mapAvatarPathActive[key] ? "#374151" : "#6B7280"
}} }}
> >
<Image <Image
src={`${process.env.CDN_URL}/${value.Icon}`} src={`${process.env.CDN_URL}/${value.Icon}`}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
alt={key} alt={key}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md" className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md"
width={200} width={200}
height={200} /> height={200} />
</div> </div>
))} ))}
</div> </div>
</div> </div>
<div className="flex items-start h-full"> <div className="flex items-start h-full">
<ul className="grid grid-cols-3 sm:grid-cols-2 lg:grid-cols-3 gap-2 w-full h-[65vh] overflow-y-scroll overflow-x-hidden"> <ul className="grid grid-cols-3 sm:grid-cols-2 lg:grid-cols-3 gap-2 w-full h-[65vh] overflow-y-scroll overflow-x-hidden">
{listAvatar.map((item, index) => ( {listAvatar.map((item, index) => (
<div key={index} onClick={() => { <div key={index} onClick={() => {
setAvatarSelected(item); setAvatarSelected(item);
setSkillIDSelected(null) setSkillIDSelected(null)
if (onClose) onClose() if (onClose) onClose()
}}> }}>
<CharacterCard data={item} /> <CharacterCard data={item} />
</div> </div>
))} ))}
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
) )
} }
File diff suppressed because it is too large Load Diff
+83 -83
View File
@@ -1,83 +1,83 @@
"use client"; "use client";
import { getNameChar } from '@/helper'; import { getNameChar } from '@/helper';
import useLocaleStore from '@/stores/localeStore'; import useLocaleStore from '@/stores/localeStore';
import { AvatarDetail } from '@/types'; import { AvatarDetail } from '@/types';
import ParseText from '../parseText'; import ParseText from '../parseText';
import Image from 'next/image'; import Image from 'next/image';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import useDetailDataStore from '@/stores/detailDataStore'; import useDetailDataStore from '@/stores/detailDataStore';
interface CharacterCardProps { interface CharacterCardProps {
data: AvatarDetail data: AvatarDetail
} }
export default function CharacterCard({ data }: CharacterCardProps) { export default function CharacterCard({ data }: CharacterCardProps) {
const { locale } = useLocaleStore(); const { locale } = useLocaleStore();
const transI18n = useTranslations("DataPage"); const transI18n = useTranslations("DataPage");
const { baseType, damageType } = useDetailDataStore() const { baseType, damageType } = useDetailDataStore()
return ( return (
<li <li
className="z-10 flex flex-col items-center rounded-xl shadow-xl className="z-10 flex flex-col items-center rounded-xl shadow-xl
bg-linear-to-br from-base-300 via-base-100 to-warning/70 bg-linear-to-br from-base-300 via-base-100 to-warning/70
transform transition-transform duration-300 ease-in-out transform transition-transform duration-300 ease-in-out
hover:scale-105 cursor-pointer min-h-45 sm:min-h-45 md:min-h-52.5 lg:min-h-55 xl:min-h-60 2xl:min-h-65" hover:scale-105 cursor-pointer min-h-45 sm:min-h-45 md:min-h-52.5 lg:min-h-55 xl:min-h-60 2xl:min-h-65"
> >
<div <div
className={`w-full rounded-md bg-linear-to-br ${data.Rarity === "CombatPowerAvatarRarityType5" className={`w-full rounded-md bg-linear-to-br ${data.Rarity === "CombatPowerAvatarRarityType5"
? "from-yellow-400 via-yellow-600/70 to-yellow-800/50" ? "from-yellow-400 via-yellow-600/70 to-yellow-800/50"
: "from-purple-400 via-purple-600/70 to-purple-800/50" : "from-purple-400 via-purple-600/70 to-purple-800/50"
}`} }`}
> >
<div className="relative w-full h-32 lg:h-26 xl:h-36"> <div className="relative w-full h-32 lg:h-26 xl:h-36">
<Image <Image
width={376} width={376}
height={512} height={512}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${data.Image.AvatarIconPath}`} src={`${process.env.CDN_URL}/${data.Image.AvatarIconPath}`}
priority={true} priority={true}
className="rounded-md w-full h-full object-contain" className="rounded-md w-full h-full object-contain"
alt="ALT" alt="ALT"
/> />
<Image <Image
width={32} width={32}
height={32} height={32}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType?.[data.DamageType].Icon}`} src={`${process.env.CDN_URL}/${damageType?.[data.DamageType].Icon}`}
className="absolute top-0 left-0 w-6 h-6 rounded-full" className="absolute top-0 left-0 w-6 h-6 rounded-full"
alt={data.DamageType.toLowerCase()} alt={data.DamageType.toLowerCase()}
/> />
<Image <Image
width={32} width={32}
height={32} height={32}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${baseType?.[data.BaseType].Icon}`} src={`${process.env.CDN_URL}/${baseType?.[data.BaseType].Icon}`}
className="absolute top-0 right-0 w-6 h-6 rounded-full" className="absolute top-0 right-0 w-6 h-6 rounded-full"
alt={data.BaseType.toLowerCase()} alt={data.BaseType.toLowerCase()}
style={{ style={{
boxShadow: "inset 0 0 8px 4px #9CA3AF" boxShadow: "inset 0 0 8px 4px #9CA3AF"
}} }}
/> />
</div> </div>
</div> </div>
<ParseText <ParseText
locale={locale} locale={locale}
text={getNameChar(locale, transI18n, data)} text={getNameChar(locale, transI18n, data)}
className=" className="
w-full px-0.5 w-full px-0.5
my-1 my-1
text-center font-bold text-shadow-white text-center font-bold text-shadow-white
leading-tight leading-tight
wrap-break-word wrap-break-word
text-sm sm:text-base 2xl:text-lg text-sm sm:text-base 2xl:text-lg
" "
/> />
</li> </li>
); );
} }
+124 -124
View File
@@ -1,125 +1,125 @@
"use client"; "use client";
import React from 'react'; import React from 'react';
import { CharacterInfoCardType } from '@/types'; import { CharacterInfoCardType } from '@/types';
import useLocaleStore from '@/stores/localeStore'; import useLocaleStore from '@/stores/localeStore';
import Image from 'next/image'; import Image from 'next/image';
import ParseText from '../parseText'; import ParseText from '../parseText';
import useDetailDataStore from '@/stores/detailDataStore'; import useDetailDataStore from '@/stores/detailDataStore';
import { getLocaleName } from '@/helper/getName'; import { getLocaleName } from '@/helper/getName';
export default function CharacterInfoCard({ character, selectedCharacters, onCharacterToggle }: { character: CharacterInfoCardType, selectedCharacters: CharacterInfoCardType[], onCharacterToggle: (characterId: CharacterInfoCardType) => void }) { export default function CharacterInfoCard({ character, selectedCharacters, onCharacterToggle }: { character: CharacterInfoCardType, selectedCharacters: CharacterInfoCardType[], onCharacterToggle: (characterId: CharacterInfoCardType) => void }) {
const isSelected = selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatar_id); const isSelected = selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatar_id);
const { mapAvatar, mapLightCone, baseType, damageType } = useDetailDataStore(); const { mapAvatar, mapLightCone, baseType, damageType } = useDetailDataStore();
const { locale } = useLocaleStore(); const { locale } = useLocaleStore();
return ( return (
<div <div
className={`bg-base-200/60 rounded-xl p-4 border cursor-pointer transition-all duration-200 ${isSelected className={`bg-base-200/60 rounded-xl p-4 border cursor-pointer transition-all duration-200 ${isSelected
? 'border-blue-400 ring-2 ring-blue-400/50' ? 'border-blue-400 ring-2 ring-blue-400/50'
: 'border-base-300/50 hover:border-base-300 opacity-75' : 'border-base-300/50 hover:border-base-300 opacity-75'
}`} }`}
onClick={() => onCharacterToggle(character)} onClick={() => onCharacterToggle(character)}
> >
{/* Character Portrait */} {/* Character Portrait */}
<div className="relative mb-4"> <div className="relative mb-4">
<div className="w-full h-48 rounded-lg overflow-hidden relative"> <div className="w-full h-48 rounded-lg overflow-hidden relative">
<Image <Image
src={`${process.env.CDN_URL}/${mapAvatar?.[character.avatar_id.toString()]?.Image?.AvatarIconPath}`} src={`${process.env.CDN_URL}/${mapAvatar?.[character.avatar_id.toString()]?.Image?.AvatarIconPath}`}
alt={getLocaleName(locale, mapAvatar?.[character.avatar_id.toString()]?.Name)} alt={getLocaleName(locale, mapAvatar?.[character.avatar_id.toString()]?.Name)}
width={376} width={376}
height={512} height={512}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
className="w-full h-full object-contain" className="w-full h-full object-contain"
/> />
<Image <Image
width={48} width={48}
height={48} height={48}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType?.[mapAvatar?.[character.avatar_id.toString()]?.DamageType || ""]?.Icon}`} src={`${process.env.CDN_URL}/${damageType?.[mapAvatar?.[character.avatar_id.toString()]?.DamageType || ""]?.Icon}`}
className="absolute top-0 left-0 w-10 h-10 rounded-full" className="absolute top-0 left-0 w-10 h-10 rounded-full"
alt={mapAvatar[character.avatar_id.toString()]?.DamageType.toLowerCase()} alt={mapAvatar[character.avatar_id.toString()]?.DamageType.toLowerCase()}
/> />
<Image <Image
width={48} width={48}
height={48} height={48}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${baseType?.[mapAvatar?.[character.avatar_id.toString()]?.BaseType || ""]?.Icon}`} src={`${process.env.CDN_URL}/${baseType?.[mapAvatar?.[character.avatar_id.toString()]?.BaseType || ""]?.Icon}`}
className="absolute top-0 right-0 w-10 h-10 rounded-full" className="absolute top-0 right-0 w-10 h-10 rounded-full"
alt={mapAvatar[character.avatar_id.toString()]?.BaseType.toLowerCase()} alt={mapAvatar[character.avatar_id.toString()]?.BaseType.toLowerCase()}
style={{ style={{
boxShadow: "inset 0 0 8px 4px #9CA3AF" boxShadow: "inset 0 0 8px 4px #9CA3AF"
}} }}
/> />
</div> </div>
</div> </div>
{/* Character Name and Level */} {/* Character Name and Level */}
<div className="w-full rounded-lg flex items-center justify-center mb-2"> <div className="w-full rounded-lg flex items-center justify-center mb-2">
<div className="text-center"> <div className="text-center">
<ParseText className="text-lg font-bold" <ParseText className="text-lg font-bold"
text={getLocaleName(locale, mapAvatar[character.avatar_id.toString()]?.Name)} text={getLocaleName(locale, mapAvatar[character.avatar_id.toString()]?.Name)}
locale={locale} locale={locale}
/> />
<div className="text-base mb-1">Lv.{character.level} E{character.rank}</div> <div className="text-base mb-1">Lv.{character.level} E{character.rank}</div>
</div> </div>
</div> </div>
{character.relics.length > 0 && ( {character.relics.length > 0 && (
<div className="flex flex-wrap items-center justify-center gap-2 mb-4"> <div className="flex flex-wrap items-center justify-center gap-2 mb-4">
{character.relics.map((relic, index) => ( {character.relics.map((relic, index) => (
<div key={index} className="relative"> <div key={index} className="relative">
<div className="w-9 h-9 rounded-lg flex items-center justify-center border border-amber-500/50"> <div className="w-9 h-9 rounded-lg flex items-center justify-center border border-amber-500/50">
<Image <Image
src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.png`} src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.png`}
alt="Relic" alt="Relic"
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
width={124} width={124}
height={124} height={124}
className="w-14 h-14 object-contain" className="w-14 h-14 object-contain"
/> />
</div> </div>
<div className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 bg-slate-800 text-white text-xs px-1 rounded"> <div className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 bg-slate-800 text-white text-xs px-1 rounded">
+{relic.level} +{relic.level}
</div> </div>
</div> </div>
))} ))}
</div> </div>
)} )}
{/* Light Cone */} {/* Light Cone */}
{character.lightcone.item_id && ( {character.lightcone.item_id && (
<div className=""> <div className="">
<div className="rounded-lg h-42 flex items-center justify-center"> <div className="rounded-lg h-42 flex items-center justify-center">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapLightCone?.[character.lightcone.item_id.toString()]?.Image?.ImagePath}`} src={`${process.env.CDN_URL}/${mapLightCone?.[character.lightcone.item_id.toString()]?.Image?.ImagePath}`}
alt={getLocaleName(locale, mapLightCone?.[character.lightcone.item_id.toString()]?.Name)} alt={getLocaleName(locale, mapLightCone?.[character.lightcone.item_id.toString()]?.Name)}
width={348} width={348}
height={408} height={408}
className="w-full h-full object-contain rounded-lg" className="w-full h-full object-contain rounded-lg"
/> />
</div> </div>
<div className="w-full h-full rounded-lg flex items-center justify-center"> <div className="w-full h-full rounded-lg flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="text-lg font-bold"> <div className="text-lg font-bold">
<ParseText <ParseText
text={getLocaleName(locale, mapLightCone[character.lightcone.item_id.toString()]?.Name)} text={getLocaleName(locale, mapLightCone[character.lightcone.item_id.toString()]?.Name)}
locale={locale} locale={locale}
/> />
</div> </div>
<div className="text-base mb-1">Lv.{character.lightcone.level} S{character.lightcone.rank}</div> <div className="text-base mb-1">Lv.{character.lightcone.level} S{character.lightcone.rank}</div>
</div> </div>
</div> </div>
</div> </div>
)} )}
</div> </div>
); );
}; };
+53 -53
View File
@@ -1,53 +1,53 @@
"use client"; "use client";
import { getLocaleName } from '@/helper'; import { getLocaleName } from '@/helper';
import useLocaleStore from '@/stores/localeStore'; import useLocaleStore from '@/stores/localeStore';
import ParseText from '../parseText'; import ParseText from '../parseText';
import Image from 'next/image'; import Image from 'next/image';
import { LightConeDetail } from '@/types'; import { LightConeDetail } from '@/types';
interface LightconeCardProps { interface LightconeCardProps {
data: LightConeDetail data: LightConeDetail
} }
export default function LightconeCard({ data }: LightconeCardProps) { export default function LightconeCard({ data }: LightconeCardProps) {
const { locale } = useLocaleStore(); const { locale } = useLocaleStore();
const text = getLocaleName(locale, data.Name) const text = getLocaleName(locale, data.Name)
return ( return (
<li className="z-10 flex flex-col items-center rounded-md shadow-lg <li className="z-10 flex flex-col items-center rounded-md shadow-lg
bg-linear-to-b from-customStart to-customEnd transform transition-transform duration-300 bg-linear-to-b from-customStart to-customEnd transform transition-transform duration-300
hover:scale-105 cursor-pointer min-h-55" hover:scale-105 cursor-pointer min-h-55"
> >
<div <div
className={`w-full rounded-md bg-linear-to-br ${data.Rarity === "CombatPowerLightconeRarity5" className={`w-full rounded-md bg-linear-to-br ${data.Rarity === "CombatPowerLightconeRarity5"
? "from-yellow-400 via-yellow-600/70 to-yellow-800/50" ? "from-yellow-400 via-yellow-600/70 to-yellow-800/50"
: data.Rarity === "CombatPowerLightconeRarity4" ? "from-purple-400 via-purple-600/70 to-purple-800/50" : : data.Rarity === "CombatPowerLightconeRarity4" ? "from-purple-400 via-purple-600/70 to-purple-800/50" :
"from-blue-400 via-blue-600/70 to-blue-800/50" "from-blue-400 via-blue-600/70 to-blue-800/50"
}`} }`}
> >
<div className="relative w-full h-full"> <div className="relative w-full h-full">
<Image <Image
loading="lazy" loading="lazy"
src={`${process.env.CDN_URL}/${data?.Image?.ThumbnailPath}`} src={`${process.env.CDN_URL}/${data?.Image?.ThumbnailPath}`}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
width={348} width={348}
height={408} height={408}
className="w-full h-full rounded-md object-cover" className="w-full h-full rounded-md object-cover"
alt="ALT" alt="ALT"
/> />
</div> </div>
</div> </div>
<ParseText <ParseText
locale={locale} locale={locale}
text={text} text={text}
className="mt-2 px-1 text-center text-sm sm:text-base font-bold leading-tight" className="mt-2 px-1 text-center text-sm sm:text-base font-bold leading-tight"
/> />
</li> </li>
); );
} }
+78 -78
View File
@@ -1,79 +1,79 @@
"use client"; "use client";
import React from 'react'; import React from 'react';
import { AvatarProfileCardType } from '@/types'; import { AvatarProfileCardType } from '@/types';
import useLocaleStore from '@/stores/localeStore'; import useLocaleStore from '@/stores/localeStore';
import Image from 'next/image'; import Image from 'next/image';
import ParseText from '../parseText'; import ParseText from '../parseText';
import useDetailDataStore from '@/stores/detailDataStore'; import useDetailDataStore from '@/stores/detailDataStore';
import { getLocaleName } from '@/helper/getName'; import { getLocaleName } from '@/helper/getName';
export default function ProfileCard({ profile, selectedProfile, onProfileToggle }: { profile: AvatarProfileCardType, selectedProfile: AvatarProfileCardType[], onProfileToggle: (profileId: AvatarProfileCardType) => void }) { export default function ProfileCard({ profile, selectedProfile, onProfileToggle }: { profile: AvatarProfileCardType, selectedProfile: AvatarProfileCardType[], onProfileToggle: (profileId: AvatarProfileCardType) => void }) {
const isSelected = selectedProfile.some((selectedProfile) => selectedProfile.key === profile.key); const isSelected = selectedProfile.some((selectedProfile) => selectedProfile.key === profile.key);
const { mapLightCone } = useDetailDataStore(); const { mapLightCone } = useDetailDataStore();
const { locale } = useLocaleStore(); const { locale } = useLocaleStore();
return ( return (
<div <div
className={`bg-base-200/60 rounded-xl p-4 border cursor-pointer transition-all duration-200 ${isSelected className={`bg-base-200/60 rounded-xl p-4 border cursor-pointer transition-all duration-200 ${isSelected
? 'border-blue-400 ring-2 ring-blue-400/50' ? 'border-blue-400 ring-2 ring-blue-400/50'
: 'border-base-300/50 hover:border-base-300 opacity-75' : 'border-base-300/50 hover:border-base-300 opacity-75'
}`} }`}
onClick={() => onProfileToggle(profile)} onClick={() => onProfileToggle(profile)}
> >
{/* Light Cone */} {/* Light Cone */}
{profile.lightcone && ( {profile.lightcone && (
<div className=""> <div className="">
<div className="rounded-lg h-42 flex items-center justify-center"> <div className="rounded-lg h-42 flex items-center justify-center">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/spriteoutput/lightconemaxfigures/${profile.lightcone.item_id}.png`} src={`${process.env.CDN_URL}/spriteoutput/lightconemaxfigures/${profile.lightcone.item_id}.png`}
alt={getLocaleName(locale, mapLightCone[profile.lightcone.item_id.toString()]?.Name)} alt={getLocaleName(locale, mapLightCone[profile.lightcone.item_id.toString()]?.Name)}
width={348} width={348}
height={408} height={408}
className="w-full h-full object-contain rounded-lg" className="w-full h-full object-contain rounded-lg"
/> />
</div> </div>
<div className="w-full h-full rounded-lg flex items-center justify-center"> <div className="w-full h-full rounded-lg flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="text-lg font-bold"> <div className="text-lg font-bold">
<ParseText <ParseText
text={getLocaleName(locale, mapLightCone[profile.lightcone.item_id.toString()]?.Name)} text={getLocaleName(locale, mapLightCone[profile.lightcone.item_id.toString()]?.Name)}
locale={locale} locale={locale}
/> />
</div> </div>
<div className="text-base mb-1">Lv.{profile.lightcone.level} S{profile.lightcone.rank}</div> <div className="text-base mb-1">Lv.{profile.lightcone.level} S{profile.lightcone.rank}</div>
</div> </div>
</div> </div>
</div> </div>
)} )}
{Object.keys(profile.relics).length > 0 && ( {Object.keys(profile.relics).length > 0 && (
<div className="flex flex-wrap items-center justify-center gap-2 mb-4"> <div className="flex flex-wrap items-center justify-center gap-2 mb-4">
{Object.values(profile.relics).map((relic, index) => ( {Object.values(profile.relics).map((relic, index) => (
<div key={index} className="relative"> <div key={index} className="relative">
<div className="w-9 h-9 rounded-lg flex items-center justify-center border border-amber-500/50"> <div className="w-9 h-9 rounded-lg flex items-center justify-center border border-amber-500/50">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.png`} src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.png`}
alt="Relic" alt="Relic"
width={124} width={124}
height={124} height={124}
className="w-14 h-14 object-contain" className="w-14 h-14 object-contain"
/> />
</div> </div>
<div className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 bg-slate-800 text-white text-xs px-1 rounded"> <div className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 bg-slate-800 text-white text-xs px-1 rounded">
+{relic.level} +{relic.level}
</div> </div>
</div> </div>
))} ))}
</div> </div>
)} )}
</div> </div>
); );
}; };
+195 -195
View File
@@ -1,196 +1,196 @@
"use client"; "use client";
import useRelicMakerStore from "@/stores/relicMakerStore"; import useRelicMakerStore from "@/stores/relicMakerStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import Image from "next/image"; import Image from "next/image";
import { useMemo } from "react"; import { useMemo } from "react";
interface RelicCardProps { interface RelicCardProps {
slot: string slot: string
avatarId: string avatarId: string
} }
const getRarityColor = (rarity: string) => { const getRarityColor = (rarity: string) => {
switch (rarity) { switch (rarity) {
case '3': return 'border-green-500 shadow-green-500/50 bg-linear-to-br from-green-700 via-green-400 to-green-500'; case '3': return 'border-green-500 shadow-green-500/50 bg-linear-to-br from-green-700 via-green-400 to-green-500';
case '4': return 'border-blue-500 shadow-blue-500/50 bg-linear-to-br from-blue-700 via-blue-400 to-blue-500'; case '4': return 'border-blue-500 shadow-blue-500/50 bg-linear-to-br from-blue-700 via-blue-400 to-blue-500';
case '5': return 'border-purple-500 shadow-purple-500/50 bg-linear-to-br from-purple-700 via-purple-400 to-purple-500'; case '5': return 'border-purple-500 shadow-purple-500/50 bg-linear-to-br from-purple-700 via-purple-400 to-purple-500';
case '6': return 'border-yellow-500 shadow-yellow-500/50 bg-linear-to-br from-yellow-700 via-yellow-400 to-yellow-500'; case '6': return 'border-yellow-500 shadow-yellow-500/50 bg-linear-to-br from-yellow-700 via-yellow-400 to-yellow-500';
default: return 'border-gray-500 shadow-gray-500/50'; default: return 'border-gray-500 shadow-gray-500/50';
} }
}; };
const getRarityName = (slot: string) => { const getRarityName = (slot: string) => {
switch (slot) { switch (slot) {
case '1': return ( case '1': return (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src="/relics/HEAD.png" src="/relics/HEAD.png"
alt="Head" alt="Head"
width={20} width={20}
height={20} height={20}
className="bg-black/50 rounded-full" className="bg-black/50 rounded-full"
/> />
<h2>Head</h2> <h2>Head</h2>
</div> </div>
); );
case '2': return ( case '2': return (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src="/relics/HAND.png" src="/relics/HAND.png"
alt="Hand" alt="Hand"
width={20} width={20}
height={20} height={20}
className="bg-black/50 rounded-full" className="bg-black/50 rounded-full"
/> />
<h2>Hands</h2> <h2>Hands</h2>
</div> </div>
); );
case '3': return ( case '3': return (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src="/relics/BODY.png" src="/relics/BODY.png"
alt="Body" alt="Body"
width={20} width={20}
height={20} height={20}
className="bg-black/50 rounded-full" className="bg-black/50 rounded-full"
/> />
<h2>Body</h2> <h2>Body</h2>
</div> </div>
); );
case '4': return ( case '4': return (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src="/relics/FOOT.png" src="/relics/FOOT.png"
alt="Foot" alt="Foot"
width={20} width={20}
height={20} height={20}
className="bg-black/50 rounded-full" className="bg-black/50 rounded-full"
/> />
<h2>Feet</h2> <h2>Feet</h2>
</div> </div>
); );
case '5': return ( case '5': return (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src="/relics/NECK.png" src="/relics/NECK.png"
alt="Neck" alt="Neck"
width={20} width={20}
height={20} height={20}
className="bg-black/50 rounded-full" className="bg-black/50 rounded-full"
/> />
<h2>Planar sphere</h2> <h2>Planar sphere</h2>
</div> </div>
); );
case '6': return ( case '6': return (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src="/relics/OBJECT.png" src="/relics/OBJECT.png"
alt="Object" alt="Object"
width={20} width={20}
height={20} height={20}
className="bg-black/50 rounded-full" className="bg-black/50 rounded-full"
/> />
<h2>Link rope</h2> <h2>Link rope</h2>
</div> </div>
); );
default: return ''; default: return '';
} }
}; };
export default function RelicCard({ slot, avatarId }: RelicCardProps) { export default function RelicCard({ slot, avatarId }: RelicCardProps) {
const { avatars } = useUserDataStore() const { avatars } = useUserDataStore()
const { selectedRelicSlot } = useRelicMakerStore() const { selectedRelicSlot } = useRelicMakerStore()
const relicDetail = useMemo(() => { const relicDetail = useMemo(() => {
const avatar = avatars[avatarId]; const avatar = avatars[avatarId];
if (avatar) { if (avatar) {
if (avatar.profileList[avatar.profileSelect].relics[slot]) { if (avatar.profileList[avatar.profileSelect].relics[slot]) {
return avatar.profileList[avatar.profileSelect].relics[slot]; return avatar.profileList[avatar.profileSelect].relics[slot];
} }
return null; return null;
} }
return null; return null;
}, [avatars, avatarId, slot]); }, [avatars, avatarId, slot]);
return ( return (
<div> <div>
{relicDetail ? ( {relicDetail ? (
<div <div
className="flex flex-col items-center cursor-pointer "> className="flex flex-col items-center cursor-pointer ">
<div <div
className={` className={`
relative w-24 h-24 rounded-full relative w-24 h-24 rounded-full
${getRarityColor(relicDetail.relic_id.toString()[0])} ${getRarityColor(relicDetail.relic_id.toString()[0])}
shadow-xl shadow-xl
flex items-center justify-center flex items-center justify-center
cursor-pointer transition-transform cursor-pointer transition-transform
${selectedRelicSlot === slot ? 'ring-5 ring-success scale-105' : 'ring-3 ring-primary'} ${selectedRelicSlot === slot ? 'ring-5 ring-success scale-105' : 'ring-3 ring-primary'}
`} `}
> >
<span> <span>
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relicDetail.relic_set_id}_${slot}.png`} src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relicDetail.relic_set_id}_${slot}.png`}
alt="Relic" alt="Relic"
width={124} width={124}
height={124} height={124}
className="w-14 h-14 object-contain" className="w-14 h-14 object-contain"
/> />
</span> </span>
{/* Level Badge */} {/* Level Badge */}
<div className="absolute -bottom-2 bg-base-100 border-2 border-base-300 rounded-full px-2 py-1"> <div className="absolute -bottom-2 bg-base-100 border-2 border-base-300 rounded-full px-2 py-1">
<span className="text-sm font-bold text-primary">+{relicDetail.level}</span> <span className="text-sm font-bold text-primary">+{relicDetail.level}</span>
</div> </div>
</div> </div>
<div className="mt-3 text-center"> <div className="mt-3 text-center">
<div className="text-sm font-medium text-base-content">{getRarityName(slot)}</div> <div className="text-sm font-medium text-base-content">{getRarityName(slot)}</div>
</div> </div>
</div> </div>
) : ( ) : (
<div <div
className="flex flex-col items-center cursor-pointer"> className="flex flex-col items-center cursor-pointer">
<div <div
className={` className={`
relative w-24 h-24 rounded-full border-4 relative w-24 h-24 rounded-full border-4
${getRarityColor("None")} ${getRarityColor("None")}
bg-base-300 shadow-xl bg-base-300 shadow-xl
flex items-center justify-center flex items-center justify-center
cursor-pointer hover:scale-105 transition-transform cursor-pointer hover:scale-105 transition-transform
ring-4 ring-primary ring-4 ring-primary
`} `}
> >
<span className="text-3xl"> <span className="text-3xl">
<svg className="w-12 h-12 text-base-content/40" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-12 h-12 text-base-content/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg> </svg>
</span> </span>
{/* Level Badge */} {/* Level Badge */}
<div className="absolute -bottom-2 bg-base-100 border-2 border-base-300 rounded-full px-2 py-1"> <div className="absolute -bottom-2 bg-base-100 border-2 border-base-300 rounded-full px-2 py-1">
<span className="text-sm font-bold text-primary">+{0}</span> <span className="text-sm font-bold text-primary">+{0}</span>
</div> </div>
</div> </div>
<div className="mt-3 text-center"> <div className="mt-3 text-center">
<div>{getRarityName(slot)}</div> <div>{getRarityName(slot)}</div>
</div> </div>
</div> </div>
)} )}
</div> </div>
) )
} }
+68 -68
View File
@@ -1,69 +1,69 @@
"use client"; "use client";
import Image from 'next/image'; import Image from 'next/image';
import { AvatarDetail } from '@/types'; import { AvatarDetail } from '@/types';
import useDetailDataStore from '@/stores/detailDataStore'; import useDetailDataStore from '@/stores/detailDataStore';
interface SimpleAvatarCardProps { interface SimpleAvatarCardProps {
data: AvatarDetail; data: AvatarDetail;
isSelected?: boolean; isSelected?: boolean;
onClick?: () => void; onClick?: () => void;
showRemoveHover?: boolean; showRemoveHover?: boolean;
} }
export const SimpleAvatarCard = ({ data, isSelected, onClick, showRemoveHover }: SimpleAvatarCardProps) => { export const SimpleAvatarCard = ({ data, isSelected, onClick, showRemoveHover }: SimpleAvatarCardProps) => {
const { baseType, damageType } = useDetailDataStore() const { baseType, damageType } = useDetailDataStore()
return ( return (
<div <div
onClick={onClick} onClick={onClick}
className={`relative w-16 h-16 sm:w-20 sm:h-20 rounded-md cursor-pointer transition-transform duration-200 ease-in-out hover:scale-105 shadow-md shrink-0 className={`relative w-16 h-16 sm:w-20 sm:h-20 rounded-md cursor-pointer transition-transform duration-200 ease-in-out hover:scale-105 shadow-md shrink-0
${isSelected ? 'ring-2 ring-success opacity-60' : ''} ${isSelected ? 'ring-2 ring-success opacity-60' : ''}
bg-linear-to-br ${data.Rarity === "CombatPowerAvatarRarityType5" bg-linear-to-br ${data.Rarity === "CombatPowerAvatarRarityType5"
? "from-yellow-400 via-yellow-600/70 to-yellow-800/50" ? "from-yellow-400 via-yellow-600/70 to-yellow-800/50"
: "from-purple-400 via-purple-600/70 to-purple-800/50" : "from-purple-400 via-purple-600/70 to-purple-800/50"
}`} }`}
> >
<Image <Image
width={80} width={80}
height={80} height={80}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${data.Image.ActionAvatarHeadIconPath}`} src={`${process.env.CDN_URL}/${data.Image.ActionAvatarHeadIconPath}`}
priority={true} priority={true}
className="w-full h-full object-contain" className="w-full h-full object-contain"
alt="Avatar" alt="Avatar"
/> />
<div className="absolute top-0 left-0 w-5 h-5 bg-black/40 rounded-full flex items-center justify-center p-0.5"> <div className="absolute top-0 left-0 w-5 h-5 bg-black/40 rounded-full flex items-center justify-center p-0.5">
<Image <Image
width={20} width={20}
height={20} height={20}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType?.[data.DamageType]?.Icon}`} src={`${process.env.CDN_URL}/${damageType?.[data.DamageType]?.Icon}`}
className="w-full h-full object-contain" className="w-full h-full object-contain"
alt="Element" alt="Element"
/> />
</div> </div>
<div className="absolute top-0 right-0 w-5 h-5 bg-black/40 rounded-full flex items-center justify-center p-0.5"> <div className="absolute top-0 right-0 w-5 h-5 bg-black/40 rounded-full flex items-center justify-center p-0.5">
<Image <Image
width={20} width={20}
height={20} height={20}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${baseType?.[data.BaseType]?.Icon}`} src={`${process.env.CDN_URL}/${baseType?.[data.BaseType]?.Icon}`}
className="w-full h-full object-contain" className="w-full h-full object-contain"
alt="Path" alt="Path"
/> />
</div> </div>
{showRemoveHover && ( {showRemoveHover && (
<div className="absolute inset-0 bg-error/80 rounded-md flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity"> <div className="absolute inset-0 bg-error/80 rounded-md flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity">
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg> </svg>
</div> </div>
)} )}
</div> </div>
); );
}; };
+90 -90
View File
@@ -1,91 +1,91 @@
import useLocaleStore from '@/stores/localeStore'; import useLocaleStore from '@/stores/localeStore';
import { Check, Sparkles, Bug, Zap, Package, Calendar } from 'lucide-react'; import { Check, Sparkles, Bug, Zap, Package, Calendar } from 'lucide-react';
export default function ChangelogBar() { export default function ChangelogBar() {
const { changelog } = useLocaleStore() const { changelog } = useLocaleStore()
const getIcon = (type: string) => { const getIcon = (type: string) => {
switch (type) { switch (type) {
case 'feature': case 'feature':
return <Sparkles className="w-3 h-3 md:w-4 md:h-4" />; return <Sparkles className="w-3 h-3 md:w-4 md:h-4" />;
case 'fix': case 'fix':
return <Bug className="w-3 h-3 md:w-4 md:h-4" />; return <Bug className="w-3 h-3 md:w-4 md:h-4" />;
case 'improvement': case 'improvement':
return <Zap className="w-3 h-3 md:w-4 md:h-4" />; return <Zap className="w-3 h-3 md:w-4 md:h-4" />;
default: default:
return <Package className="w-3 h-3 md:w-4 md:h-4" />; return <Package className="w-3 h-3 md:w-4 md:h-4" />;
} }
}; };
const getBadgeClass = (type: string) => { const getBadgeClass = (type: string) => {
switch (type) { switch (type) {
case 'feature': case 'feature':
return 'badge-success'; return 'badge-success';
case 'fix': case 'fix':
return 'badge-error'; return 'badge-error';
case 'improvement': case 'improvement':
return 'badge-warning'; return 'badge-warning';
default: default:
return 'badge-info'; return 'badge-info';
} }
}; };
const getTypeLabel = (type: string) => { const getTypeLabel = (type: string) => {
switch (type) { switch (type) {
case 'feature': case 'feature':
return 'Feature'; return 'Feature';
case 'fix': case 'fix':
return 'Fix'; return 'Fix';
case 'improvement': case 'improvement':
return 'Improvement'; return 'Improvement';
default: default:
return 'Update'; return 'Update';
} }
}; };
return ( return (
<div className="md:p-4"> <div className="md:p-4">
{/* Alert */} {/* Alert */}
{/* <div className="alert alert-info mb-4 md:p-2 p-1"> {/* <div className="alert alert-info mb-4 md:p-2 p-1">
<AlertCircle className="w-6 h-6" /> <AlertCircle className="w-6 h-6" />
</div> */} </div> */}
{/* Timeline */} {/* Timeline */}
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
{changelog.map((change, index) => ( {changelog.map((change, index) => (
<div key={index} className="bg-base-100 shadow-sm"> <div key={index} className="bg-base-100 shadow-sm">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{/* Version Header */} {/* Version Header */}
<div className="flex flex-row items-center gap-2 md:gap-3"> <div className="flex flex-row items-center gap-2 md:gap-3">
<div className={`badge ${getBadgeClass(change.type)} gap-1 md:gap-2 p-1`}> <div className={`badge ${getBadgeClass(change.type)} gap-1 md:gap-2 p-1`}>
{getIcon(change.type)} {getIcon(change.type)}
{getTypeLabel(change.type)} {getTypeLabel(change.type)}
</div> </div>
<h2 className="card-title text-sm md:text-lg"> <h2 className="card-title text-sm md:text-lg">
Version {change.version} Version {change.version}
</h2> </h2>
<div className="flex items-center gap-1 md:gap-2 text-base-content/60 ml-auto"> <div className="flex items-center gap-1 md:gap-2 text-base-content/60 ml-auto">
<Calendar className="w-3 h-3 md:w-4 md:h-4" /> <Calendar className="w-3 h-3 md:w-4 md:h-4" />
<span className="text-sm">{change.date}</span> <span className="text-sm">{change.date}</span>
</div> </div>
</div> </div>
{/* Changes List */} {/* Changes List */}
<ul className="space-y-3"> <ul className="space-y-3">
{change.items.map((item, itemIndex) => ( {change.items.map((item, itemIndex) => (
<li key={itemIndex} className="flex items-start gap-3"> <li key={itemIndex} className="flex items-start gap-3">
<div className="mt-1"> <div className="mt-1">
<Check className="w-5 h-5 text-success" /> <Check className="w-5 h-5 text-success" />
</div> </div>
<span className="text-base-content">{item}</span> <span className="text-base-content">{item}</span>
</li> </li>
))} ))}
</ul> </ul>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div> </div>
); );
} }
+55 -55
View File
@@ -1,56 +1,56 @@
"use client" "use client"
import { import {
useFetchASGroupData, useFetchASGroupData,
useFetchAvatarData, useFetchAvatarData,
useFetchChangelog, useFetchChangelog,
useFetchConfigData, useFetchConfigData,
useFetchLightconeData, useFetchLightconeData,
useFetchMOCGroupData, useFetchMOCGroupData,
useFetchMonsterData, useFetchMonsterData,
useFetchPeakGroupData, useFetchPeakGroupData,
useFetchPFGroupData, useFetchPFGroupData,
useFetchRelicSetData useFetchRelicSetData
} from "@/lib/hooks" } from "@/lib/hooks"
export default function ClientDataFetcher({ export default function ClientDataFetcher({
children children
}: { }: {
children: React.ReactNode children: React.ReactNode
}) { }) {
const q1 = useFetchConfigData() const q1 = useFetchConfigData()
const q2 = useFetchAvatarData() const q2 = useFetchAvatarData()
const q3 = useFetchLightconeData() const q3 = useFetchLightconeData()
const q4 = useFetchRelicSetData() const q4 = useFetchRelicSetData()
const q5 = useFetchMonsterData() const q5 = useFetchMonsterData()
const q6 = useFetchPFGroupData() const q6 = useFetchPFGroupData()
const q7 = useFetchMOCGroupData() const q7 = useFetchMOCGroupData()
const q8 = useFetchASGroupData() const q8 = useFetchASGroupData()
const q9 = useFetchPeakGroupData() const q9 = useFetchPeakGroupData()
const q10 = useFetchChangelog() const q10 = useFetchChangelog()
const queries = [q1,q2,q3,q4,q5,q6,q7,q8,q9,q10] const queries = [q1,q2,q3,q4,q5,q6,q7,q8,q9,q10]
const loading = queries.some(q => q.isLoading) const loading = queries.some(q => q.isLoading)
const progress = const progress =
(queries.filter(q => q.isSuccess).length / queries.length) * 100 (queries.filter(q => q.isSuccess).length / queries.length) * 100
if (loading) { if (loading) {
return ( return (
<div className="flex h-screen flex-col items-center justify-center gap-4"> <div className="flex h-screen flex-col items-center justify-center gap-4">
<div className="text-lg font-semibold">Loading data...</div> <div className="text-lg font-semibold">Loading data...</div>
<progress <progress
className="progress progress-primary w-56" className="progress progress-primary w-56"
value={progress} value={progress}
max="100" max="100"
/> />
<div>{Math.floor(progress)}%</div> <div>{Math.floor(progress)}%</div>
</div> </div>
) )
} }
return <>{children}</> return <>{children}</>
} }
+168 -168
View File
@@ -1,169 +1,169 @@
"use client" "use client"
import { connectToPS, syncDataToPS } from "@/helper" import { connectToPS, syncDataToPS } from "@/helper"
import useConnectStore from "@/stores/connectStore" import useConnectStore from "@/stores/connectStore"
import useGlobalStore from "@/stores/globalStore" import useGlobalStore from "@/stores/globalStore"
import { PSConnectType } from "@/types" import { PSConnectType } from "@/types"
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import { useState } from "react" import { useState } from "react"
export default function ConnectBar() { export default function ConnectBar() {
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const [message, setMessage] = useState({ text: '', type: '' }); const [message, setMessage] = useState({ text: '', type: '' });
const { const {
connectionType, connectionType,
privateType, privateType,
serverUrl, serverUrl,
username, username,
password, password,
setConnectionType, setConnectionType,
setPrivateType, setPrivateType,
setServerUrl, setServerUrl,
setUsername, setUsername,
setPassword setPassword
} = useConnectStore() } = useConnectStore()
const { isConnectPS, setIsConnectPS } = useGlobalStore() const { isConnectPS, setIsConnectPS } = useGlobalStore()
return ( return (
<div className="px-6 py-4"> <div className="px-6 py-4">
<div className="form-control grid grid-cols-1 w-full mb-6"> <div className="form-control grid grid-cols-1 w-full mb-6">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-purple-300">{transI18n("connectionType")}</span> <span className="label-text font-semibold text-purple-300">{transI18n("connectionType")}</span>
</label> </label>
<select <select
className="select w-full select-bordered border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1" className="select w-full select-bordered border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1"
value={connectionType} value={connectionType}
onChange={(e) => { onChange={(e) => {
setIsConnectPS(false) setIsConnectPS(false)
setConnectionType(e.target.value) setConnectionType(e.target.value)
}} }}
> >
<option value={PSConnectType.FireflyGo}>FireflyGo</option> <option value={PSConnectType.FireflyGo}>FireflyGo</option>
<option value={PSConnectType.RobinSR}>RobinSR</option> <option value={PSConnectType.RobinSR}>RobinSR</option>
<option value={PSConnectType.Other}>{transI18n("other")}</option> <option value={PSConnectType.Other}>{transI18n("other")}</option>
</select> </select>
</div> </div>
{connectionType === PSConnectType.Other && ( {connectionType === PSConnectType.Other && (
<div className="flex flex-col md:space-x-4 mb-6 gap-2"> <div className="flex flex-col md:space-x-4 mb-6 gap-2">
<div className="form-control w-full mb-4 md:mb-0"> <div className="form-control w-full mb-4 md:mb-0">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-purple-300">{transI18n("serverUrl")}</span> <span className="label-text font-semibold text-purple-300">{transI18n("serverUrl")}</span>
</label> </label>
<input <input
type="text" type="text"
placeholder={transI18n("placeholderServerUrl")} placeholder={transI18n("placeholderServerUrl")}
className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1" className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1"
value={serverUrl} value={serverUrl}
onChange={(e) => setServerUrl(e.target.value)} onChange={(e) => setServerUrl(e.target.value)}
/> />
</div> </div>
<div className="form-control w-full mb-4 md:mb-0"> <div className="form-control w-full mb-4 md:mb-0">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-purple-300">{transI18n("privateType")}</span> <span className="label-text font-semibold text-purple-300">{transI18n("privateType")}</span>
</label> </label>
<select <select
className="select w-full select-bordered border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1" className="select w-full select-bordered border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1"
value={privateType} value={privateType}
onChange={(e) => setPrivateType(e.target.value)} onChange={(e) => setPrivateType(e.target.value)}
> >
<option value="Local">{transI18n("local")}</option> <option value="Local">{transI18n("local")}</option>
<option value="Server">{transI18n("server")}</option> <option value="Server">{transI18n("server")}</option>
</select> </select>
</div> </div>
<div className="form-control w-full mb-4 md:mb-0"> <div className="form-control w-full mb-4 md:mb-0">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-purple-300">{transI18n("username")}</span> <span className="label-text font-semibold text-purple-300">{transI18n("username")}</span>
</label> </label>
<input <input
type="text" type="text"
placeholder={transI18n("placeholderUsername")} placeholder={transI18n("placeholderUsername")}
className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1" className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1"
value={username} value={username}
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
/> />
</div> </div>
<div className="form-control w-full mb-4 md:mb-0"> <div className="form-control w-full mb-4 md:mb-0">
<label className="label"> <label className="label">
<span className="label-text font-semibold text-purple-300">{transI18n("password")}</span> <span className="label-text font-semibold text-purple-300">{transI18n("password")}</span>
</label> </label>
<input <input
type="password" type="password"
placeholder={transI18n("placeholderPassword")} placeholder={transI18n("placeholderPassword")}
className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1" className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
/> />
</div> </div>
</div> </div>
)} )}
{message.text && ( {message.text && (
<div className={`alert ${message.type === 'success' ? 'alert-success' : <div className={`alert ${message.type === 'success' ? 'alert-success' :
message.type === 'error' ? 'alert-error' : 'alert-info' message.type === 'error' ? 'alert-error' : 'alert-info'
} mb-6`}> } mb-6`}>
<span>{message.text}</span> <span>{message.text}</span>
</div> </div>
)} )}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6 mb-2"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6 mb-2">
<div className="flex items-center justify-center md:justify-start"> <div className="flex items-center justify-center md:justify-start">
<span className="text-md mr-2">{transI18n("status")}:</span> <span className="text-md mr-2">{transI18n("status")}:</span>
<span <span
className={`badge ${isConnectPS ? "badge-success" : "badge-error" className={`badge ${isConnectPS ? "badge-success" : "badge-error"
} badge-lg`} } badge-lg`}
> >
{isConnectPS ? transI18n("connected") : transI18n("unconnected")} {isConnectPS ? transI18n("connected") : transI18n("unconnected")}
</span> </span>
</div> </div>
{/* Buttons */} {/* Buttons */}
<div className="flex flex-col sm:flex-row gap-2 w-full justify-center md:justify-end"> <div className="flex flex-col sm:flex-row gap-2 w-full justify-center md:justify-end">
<button <button
onClick={async () => { onClick={async () => {
const response = await connectToPS(); const response = await connectToPS();
if (response.success) { if (response.success) {
setMessage({ setMessage({
type: "success", type: "success",
text: transI18n("connectedSuccess"), text: transI18n("connectedSuccess"),
}); });
} else { } else {
setMessage({ setMessage({
type: "error", type: "error",
text: response.message, text: response.message,
}); });
} }
}} }}
className="btn btn-primary w-full sm:w-auto" className="btn btn-primary w-full sm:w-auto"
> >
{transI18n("connectPs")} {transI18n("connectPs")}
</button> </button>
{isConnectPS && ( {isConnectPS && (
<button <button
onClick={async () => { onClick={async () => {
const response = await syncDataToPS(); const response = await syncDataToPS();
if (response.success) { if (response.success) {
setMessage({ setMessage({
type: "success", type: "success",
text: transI18n("syncSuccess"), text: transI18n("syncSuccess"),
}); });
} else { } else {
setMessage({ setMessage({
type: "error", type: "error",
text: `${transI18n("syncFailed")}: ${response.message}`, text: `${transI18n("syncFailed")}: ${response.message}`,
}); });
} }
}} }}
className="btn btn-success w-full sm:w-auto" className="btn btn-success w-full sm:w-auto"
> >
{transI18n("sync")} {transI18n("sync")}
</button> </button>
)} )}
</div> </div>
</div> </div>
</div> </div>
) )
} }
+85 -85
View File
@@ -1,85 +1,85 @@
"use client" "use client"
import { replaceByParam, getLocaleName } from '@/helper'; import { replaceByParam, getLocaleName } from '@/helper';
import Image from "next/image"; import Image from "next/image";
import ParseText from "../parseText"; import ParseText from "../parseText";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import { useMemo } from "react"; import { useMemo } from "react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useCurrentDataStore from "@/stores/currentDataStore"; import useCurrentDataStore from "@/stores/currentDataStore";
import ExtraEffectList from '../extraInfo'; import ExtraEffectList from '../extraInfo';
export default function EidolonsInfo() { export default function EidolonsInfo() {
const { avatarSelected } = useCurrentDataStore() const { avatarSelected } = useCurrentDataStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { setAvatars, avatars } = useUserDataStore() const { setAvatars, avatars } = useUserDataStore()
const charRank = useMemo(() => { const charRank = useMemo(() => {
if (!avatarSelected) return null; if (!avatarSelected) return null;
const avatar = avatars[avatarSelected.ID]; const avatar = avatars[avatarSelected.ID];
if (avatar?.enhanced != "") { if (avatar?.enhanced != "") {
return avatarSelected?.Enhanced?.[avatar?.enhanced]?.Ranks return avatarSelected?.Enhanced?.[avatar?.enhanced]?.Ranks
} }
return avatarSelected?.Ranks return avatarSelected?.Ranks
}, [avatarSelected, avatars]); }, [avatarSelected, avatars]);
return ( return (
<div className="bg-base-100 rounded-xl p-6 shadow-lg"> <div className="bg-base-100 rounded-xl p-6 shadow-lg">
<h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content"> <h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content">
<div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div> <div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div>
{transI18n("eidolons")} {transI18n("eidolons")}
</h2> </h2>
<div className="grid grid-cols-1 m-4 p-4 font-bold gap-4 w-fit max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden"> <div className="grid grid-cols-1 m-4 p-4 font-bold gap-4 w-fit max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden">
{charRank && avatars[avatarSelected?.ID || ""] && ( {charRank && avatars[avatarSelected?.ID || ""] && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{Object.entries(charRank || {}).map(([key, rank]) => ( {Object.entries(charRank || {}).map(([key, rank]) => (
<div key={key} <div key={key}
className="flex flex-col items-center" className="flex flex-col items-center"
> >
<div <div
className="cursor-pointer" className="cursor-pointer"
onClick={() => { onClick={() => {
let newRank = Number(rank.Rank) let newRank = Number(rank.Rank)
if (avatars[avatarSelected?.ID || ""]?.data?.rank == Number(rank.Rank)) { if (avatars[avatarSelected?.ID || ""]?.data?.rank == Number(rank.Rank)) {
newRank = Number(rank.Rank) - 1 newRank = Number(rank.Rank) - 1
} }
setAvatars({ ...avatars, [avatarSelected?.ID || ""]: { ...avatars[avatarSelected?.ID || ""], data: { ...avatars[avatarSelected?.ID || ""].data, rank: newRank } } }) setAvatars({ ...avatars, [avatarSelected?.ID || ""]: { ...avatars[avatarSelected?.ID || ""], data: { ...avatars[avatarSelected?.ID || ""].data, rank: newRank } } })
}} }}
> >
<Image <Image
className={`w-60 object-contain mb-2 ${Number(rank.Rank) <= avatars[avatarSelected?.ID.toString() || ""]?.data?.rank ? "" : "grayscale"}`} className={`w-60 object-contain mb-2 ${Number(rank.Rank) <= avatars[avatarSelected?.ID.toString() || ""]?.data?.rank ? "" : "grayscale"}`}
src={`${process.env.CDN_URL}/ui/ui3d/rank/_dependencies/textures/${avatarSelected?.ID}/${avatarSelected?.ID}_Rank_${rank.Rank}.png`} src={`${process.env.CDN_URL}/ui/ui3d/rank/_dependencies/textures/${avatarSelected?.ID}/${avatarSelected?.ID}_Rank_${rank.Rank}.png`}
alt={`Rank ${rank.Rank} Image`} alt={`Rank ${rank.Rank} Image`}
priority priority
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
width={240} width={240}
height={240} height={240}
/> />
<div className="text-lg pb-1 flex items-center justify-items-center gap-2"> <div className="text-lg pb-1 flex items-center justify-items-center gap-2">
<span className="inline-block text-indigo-500">{rank.Rank}.</span> <span className="inline-block text-indigo-500">{rank.Rank}.</span>
<ParseText <ParseText
locale={locale} locale={locale}
text={getLocaleName(locale, rank.Name)} text={getLocaleName(locale, rank.Name)}
className="text-center text-base font-normal leading-tight" className="text-center text-base font-normal leading-tight"
/> />
</div> </div>
</div> </div>
<div className="text-sm font-normal"> <div className="text-sm font-normal">
<div dangerouslySetInnerHTML={{ __html: replaceByParam(getLocaleName(locale, rank.Desc), rank.Param) }} /> <div dangerouslySetInnerHTML={{ __html: replaceByParam(getLocaleName(locale, rank.Desc), rank.Param) }} />
<ExtraEffectList extras={rank?.Extra} locale={locale} /> <ExtraEffectList extras={rank?.Extra} locale={locale} />
</div> </div>
</div> </div>
))} ))}
</div> </div>
)} )}
</div> </div>
</div> </div>
); );
} }
+98 -98
View File
@@ -1,99 +1,99 @@
"use client" "use client"
import { useState } from "react" import { useState } from "react"
import { replaceByParam, getLocaleName } from "@/helper" import { replaceByParam, getLocaleName } from "@/helper"
import { ExtraEffect } from "@/types" import { ExtraEffect } from "@/types"
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
type Props = { type Props = {
extras: Record<string, ExtraEffect> | undefined extras: Record<string, ExtraEffect> | undefined
locale: string locale: string
} }
export default function ExtraEffectList({ extras, locale }: Props) { export default function ExtraEffectList({ extras, locale }: Props) {
const [openList, setOpenList] = useState(false) const [openList, setOpenList] = useState(false)
const [openId, setOpenId] = useState<number | null>(null) const [openId, setOpenId] = useState<number | null>(null)
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
if (!extras || Object.keys(extras).length === 0) return null if (!extras || Object.keys(extras).length === 0) return null
return ( return (
<div className="mt-3"> <div className="mt-3">
<div <div
className="flex items-center justify-between cursor-pointer bg-primary/10 px-3 py-2 rounded-md" className="flex items-center justify-between cursor-pointer bg-primary/10 px-3 py-2 rounded-md"
onClick={() => setOpenList(!openList)} onClick={() => setOpenList(!openList)}
> >
<span className="text-sm font-semibold text-primary"> <span className="text-sm font-semibold text-primary">
{transI18n("listExtraEffect")} ({Object.keys(extras).length}) {transI18n("listExtraEffect")} ({Object.keys(extras).length})
</span> </span>
<span <span
className={`transition-transform ${ className={`transition-transform ${
openList ? "rotate-90" : "" openList ? "rotate-90" : ""
}`} }`}
> >
</span> </span>
</div> </div>
<div <div
className={`overflow-hidden transition-all duration-300 ${ className={`overflow-hidden transition-all duration-300 ${
openList ? "max-h-125 mt-2" : "max-h-0" openList ? "max-h-125 mt-2" : "max-h-0"
}`} }`}
> >
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{Object.values(extras).map((extra) => { {Object.values(extras).map((extra) => {
const isOpen = openId === extra.ID const isOpen = openId === extra.ID
return ( return (
<div <div
key={extra.ID} key={extra.ID}
className="bg-primary/5 rounded-md" className="bg-primary/5 rounded-md"
> >
<div <div
className="flex items-center justify-between px-3 py-2 cursor-pointer" className="flex items-center justify-between px-3 py-2 cursor-pointer"
onClick={() => onClick={() =>
setOpenId(isOpen ? null : extra.ID) setOpenId(isOpen ? null : extra.ID)
} }
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-[10px] uppercase font-bold bg-primary/50 px-1.5 py-0.5 rounded"> <span className="text-[10px] uppercase font-bold bg-primary/50 px-1.5 py-0.5 rounded">
{transI18n("extra")} {transI18n("extra")}
</span> </span>
<span className="text-sm font-medium text-primary/80"> <span className="text-sm font-medium text-primary/80">
{getLocaleName(locale, extra.Name)} {getLocaleName(locale, extra.Name)}
</span> </span>
</div> </div>
<span <span
className={`transition-transform ${ className={`transition-transform ${
isOpen ? "rotate-90" : "" isOpen ? "rotate-90" : ""
}`} }`}
> >
</span> </span>
</div> </div>
<div <div
className={`overflow-hidden transition-all duration-300 ${ className={`overflow-hidden transition-all duration-300 ${
isOpen ? "max-h-40 px-3 pb-2" : "max-h-0" isOpen ? "max-h-40 px-3 pb-2" : "max-h-0"
}`} }`}
> >
<div <div
className="text-sm opacity-90" className="text-sm opacity-90"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
getLocaleName(locale, extra.Desc), getLocaleName(locale, extra.Desc),
extra.Param extra.Param
) )
}} }}
/> />
</div> </div>
</div> </div>
) )
})} })}
</div> </div>
</div> </div>
</div> </div>
) )
} }
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -1,9 +1,9 @@
export default function Footer() { export default function Footer() {
return ( return (
<footer className="footer footer-horizontal footer-center bg-base-200 text-base-content rounded p-10"> <footer className="footer footer-horizontal footer-center bg-base-200 text-base-content rounded p-10">
<aside> <aside>
<p>Copyright © {new Date().getFullYear()} - Firefly Shelter (Powered by Nextjs & DaisyUi)</p> <p>Copyright © {new Date().getFullYear()} - Firefly Shelter (Powered by Nextjs & DaisyUi)</p>
</aside> </aside>
</footer> </footer>
) )
} }
File diff suppressed because it is too large Load Diff
+265 -265
View File
@@ -1,74 +1,74 @@
"use client" "use client"
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import { useState, useMemo } from "react"; import { useState, useMemo } from "react";
import useCopyProfileStore from "@/stores/copyProfile"; import useCopyProfileStore from "@/stores/copyProfile";
import ProfileCard from "../card/profileCard"; import ProfileCard from "../card/profileCard";
import { AvatarProfileCardType, AvatarProfileStore } from "@/types"; import { AvatarProfileCardType, AvatarProfileStore } from "@/types";
import Image from "next/image"; import Image from "next/image";
import { getNameChar, calcRarity } from "@/helper"; import { getNameChar, calcRarity } from "@/helper";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import SelectCustomImage from "../select/customSelectImage"; import SelectCustomImage from "../select/customSelectImage";
import useCurrentDataStore from "@/stores/currentDataStore"; import useCurrentDataStore from "@/stores/currentDataStore";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
export default function CopyImport() { export default function CopyImport() {
const { avatars, setAvatar } = useUserDataStore(); const { avatars, setAvatar } = useUserDataStore();
const { avatarSelected } = useCurrentDataStore() const { avatarSelected } = useCurrentDataStore()
const { mapAvatar, baseType, damageType } = useDetailDataStore() const { mapAvatar, baseType, damageType } = useDetailDataStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
selectedProfiles, selectedProfiles,
avatarCopySelected, avatarCopySelected,
setSelectedProfiles, setSelectedProfiles,
setAvatarCopySelected, setAvatarCopySelected,
listElement, listElement,
listPath, listPath,
listRank, listRank,
setListElement, setListElement,
setListPath, setListPath,
setListRank setListRank
} = useCopyProfileStore() } = useCopyProfileStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const [message, setMessage] = useState({ const [message, setMessage] = useState({
type: "", type: "",
text: "" text: ""
}) })
const listAvatar = useMemo(() => { const listAvatar = useMemo(() => {
if (!mapAvatar || !locale || !transI18n) return [] if (!mapAvatar || !locale || !transI18n) return []
let list = Object.values(mapAvatar); let list = Object.values(mapAvatar);
const allElementFalse = !Object.values(listElement).some(v => v) const allElementFalse = !Object.values(listElement).some(v => v)
const allPathFalse = !Object.values(listPath).some(v => v) const allPathFalse = !Object.values(listPath).some(v => v)
const allRarityFalse = !Object.values(listRank).some(v => v) const allRarityFalse = !Object.values(listRank).some(v => v)
list = list.filter(item => (allElementFalse || listElement[item.DamageType]) && (allPathFalse || listPath[item.BaseType]) && (allRarityFalse || listRank[calcRarity(item.Rarity)])) list = list.filter(item => (allElementFalse || listElement[item.DamageType]) && (allPathFalse || listPath[item.BaseType]) && (allRarityFalse || listRank[calcRarity(item.Rarity)]))
list.sort((a, b) => { list.sort((a, b) => {
const r = calcRarity(b.Rarity) - calcRarity(a.Rarity) const r = calcRarity(b.Rarity) - calcRarity(a.Rarity)
if (r !== 0) return r if (r !== 0) return r
return a.ID - b.ID return a.ID - b.ID
}) })
return list return list
}, [mapAvatar, listElement, listPath, listRank, locale, transI18n]) }, [mapAvatar, listElement, listPath, listRank, locale, transI18n])
const handleProfileToggle = (profile: AvatarProfileCardType) => { const handleProfileToggle = (profile: AvatarProfileCardType) => {
if (selectedProfiles.some((selectedProfile) => selectedProfile.key === profile.key)) { if (selectedProfiles.some((selectedProfile) => selectedProfile.key === profile.key)) {
setSelectedProfiles(selectedProfiles.filter((selectedProfile) => selectedProfile.key !== profile.key)); setSelectedProfiles(selectedProfiles.filter((selectedProfile) => selectedProfile.key !== profile.key));
return; return;
} }
setSelectedProfiles([...selectedProfiles, profile]); setSelectedProfiles([...selectedProfiles, profile]);
}; };
const clearSelection = () => { const clearSelection = () => {
setSelectedProfiles([]); setSelectedProfiles([]);
setMessage({ setMessage({
type: "success", type: "success",
text: transI18n("clearAll") text: transI18n("clearAll")
}) })
}; };
const selectAll = () => { const selectAll = () => {
if (avatarCopySelected) { if (avatarCopySelected) {
const sourceAvatar = avatars[avatarCopySelected.ID.toString()] const sourceAvatar = avatars[avatarCopySelected.ID.toString()]
@@ -79,32 +79,32 @@ export default function CopyImport() {
return null; return null;
} }
return { return {
key: index, key: index,
...profile, ...profile,
} as AvatarProfileCardType } as AvatarProfileCardType
}).filter((profile) => profile !== null)); }).filter((profile) => profile !== null));
} }
setMessage({ setMessage({
type: "success", type: "success",
text: transI18n("selectAll") text: transI18n("selectAll")
}) })
}; };
const handleCopy = () => { const handleCopy = () => {
if (selectedProfiles.length === 0) { if (selectedProfiles.length === 0) {
setMessage({ setMessage({
type: "error", type: "error",
text: transI18n("pleaseSelectAtLeastOneProfile") text: transI18n("pleaseSelectAtLeastOneProfile")
}); });
return; return;
} }
if (!avatarCopySelected) { if (!avatarCopySelected) {
setMessage({ setMessage({
type: "error", type: "error",
text: transI18n("noAvatarToCopySelected") text: transI18n("noAvatarToCopySelected")
}); });
return; return;
} }
if (!avatarSelected) { if (!avatarSelected) {
setMessage({ setMessage({
type: "error", type: "error",
@@ -130,180 +130,180 @@ export default function CopyImport() {
return null; return null;
} }
return { return {
...profile, ...profile,
profile_name: profile.profile_name + ` - Copy: ${avatarCopySelected?.ID}`, profile_name: profile.profile_name + ` - Copy: ${avatarCopySelected?.ID}`,
} as AvatarProfileStore } as AvatarProfileStore
}).filter((profile) => profile !== null); }).filter((profile) => profile !== null);
const newAvatar = { const newAvatar = {
...targetAvatar, ...targetAvatar,
profileList: targetAvatar.profileList.concat(newListProfile), profileList: targetAvatar.profileList.concat(newListProfile),
profileSelect: targetAvatar.profileList.length + newListProfile.length - 1, profileSelect: targetAvatar.profileList.length + newListProfile.length - 1,
} }
setAvatar(newAvatar); setAvatar(newAvatar);
setSelectedProfiles([]); setSelectedProfiles([]);
setMessage({ setMessage({
type: "success", type: "success",
text: transI18n("copied") text: transI18n("copied")
}); });
}; };
return ( return (
<div className="rounded-lg px-2 min-h-[60vh]"> <div className="rounded-lg px-2 min-h-[60vh]">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
{/* Header */} {/* Header */}
<div className="mb-2"> <div className="mb-2">
<div className="mt-2 w-full"> <div className="mt-2 w-full">
<div className="flex items-start flex-col gap-2 mt-2 w-full"> <div className="flex items-start flex-col gap-2 mt-2 w-full">
<div>{transI18n("filter")}</div> <div>{transI18n("filter")}</div>
<div className="flex flex-wrap justify-start gap-5 md:gap-10 mt-2 w-full"> <div className="flex flex-wrap justify-start gap-5 md:gap-10 mt-2 w-full">
{/* Path */} {/* Path */}
<div> <div>
<div className="flex flex-wrap gap-2 justify-start items-center"> <div className="flex flex-wrap gap-2 justify-start items-center">
{Object.entries(baseType).filter(([key]) => key !== "").map(([key, value]) => ( {Object.entries(baseType).filter(([key]) => key !== "").map(([key, value]) => (
<div <div
key={key} key={key}
onClick={() => { onClick={() => {
setListPath({ ...listPath, [key]: !listPath[key] }) setListPath({ ...listPath, [key]: !listPath[key] })
}} }}
className="w-12.5 h-12.5 hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer" className="w-12.5 h-12.5 hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer"
style={{ style={{
backgroundColor: listPath[key] ? "#374151" : "#6B7280" backgroundColor: listPath[key] ? "#374151" : "#6B7280"
}}> }}>
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${value.Icon}`} src={`${process.env.CDN_URL}/${value.Icon}`}
alt={key} alt={key}
className="h-8 w-8 object-contain rounded-md" className="h-8 w-8 object-contain rounded-md"
width={200} width={200}
height={200} height={200}
/> />
</div> </div>
))} ))}
</div> </div>
</div> </div>
{/* Element */} {/* Element */}
<div> <div>
<div className="flex flex-wrap gap-2 justify-start items-center"> <div className="flex flex-wrap gap-2 justify-start items-center">
{Object.entries(damageType).filter(([key]) => key !== "").map(([key, value]) => ( {Object.entries(damageType).filter(([key]) => key !== "").map(([key, value]) => (
<div <div
key={key} key={key}
onClick={() => { onClick={() => {
setListElement({ ...listElement, [key]: !listElement[key] }) setListElement({ ...listElement, [key]: !listElement[key] })
}} }}
className="w-12.5 h-12.5 hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer" className="w-12.5 h-12.5 hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer"
style={{ style={{
backgroundColor: listElement[key] ? "#374151" : "#6B7280" backgroundColor: listElement[key] ? "#374151" : "#6B7280"
}}> }}>
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${value.Icon}`} src={`${process.env.CDN_URL}/${value.Icon}`}
alt={key} alt={key}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md" className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md"
width={200} width={200}
height={200} height={200}
/> />
</div> </div>
))} ))}
</div> </div>
</div> </div>
{/* Rank */} {/* Rank */}
<div> <div>
<div className="flex flex-wrap gap-2 justify-end items-center"> <div className="flex flex-wrap gap-2 justify-end items-center">
{Object.entries(listRank).map(([key], index) => ( {Object.entries(listRank).map(([key], index) => (
<div <div
key={index} key={index}
onClick={() => { onClick={() => {
setListRank({ ...listRank, [key]: !listRank[key] }) setListRank({ ...listRank, [key]: !listRank[key] })
}} }}
className="w-12.5 h-12.5 hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer" className="w-12.5 h-12.5 hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer"
style={{ style={{
backgroundColor: listRank[key] ? "#374151" : "#6B7280" backgroundColor: listRank[key] ? "#374151" : "#6B7280"
}}> }}>
<div className="font-bold text-white h-8 w-8 text-center flex items-center justify-center">{key}*</div> <div className="font-bold text-white h-8 w-8 text-center flex items-center justify-center">{key}*</div>
</div> </div>
))} ))}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="grid grid-cols-1 gap-2"> <div className="grid grid-cols-1 gap-2">
{listAvatar.length > 0 && ( {listAvatar.length > 0 && (
<div> <div>
<div>{transI18n("characterName")}</div> <div>{transI18n("characterName")}</div>
<SelectCustomImage <SelectCustomImage
customSet={listAvatar.map((avatar) => ({ customSet={listAvatar.map((avatar) => ({
value: avatar.ID.toString(), value: avatar.ID.toString(),
label: getNameChar(locale, transI18n, avatar), label: getNameChar(locale, transI18n, avatar),
imageUrl: `${process.env.CDN_URL}/${avatar.Image.AvatarIconPath}` imageUrl: `${process.env.CDN_URL}/${avatar.Image.AvatarIconPath}`
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={avatarCopySelected?.ID.toString() || ""} selectedCustomSet={avatarCopySelected?.ID.toString() || ""}
placeholder="Character Select" placeholder="Character Select"
setSelectedCustomSet={(value) => setAvatarCopySelected(mapAvatar[value] || null)} setSelectedCustomSet={(value) => setAvatarCopySelected(mapAvatar[value] || null)}
/> />
</div> </div>
)} )}
</div> </div>
</div> </div>
{/* Selection Info */} {/* Selection Info */}
<div className="mt-6 flex items-center gap-4 bg-slate-800/50 p-4 rounded-lg"> <div className="mt-6 flex items-center gap-4 bg-slate-800/50 p-4 rounded-lg">
<span className="text-blue-400 font-medium"> <span className="text-blue-400 font-medium">
{transI18n("selectedProfiles")}: {selectedProfiles.length} {transI18n("selectedProfiles")}: {selectedProfiles.length}
</span> </span>
<button <button
onClick={clearSelection} onClick={clearSelection}
className="text-error/75 hover:text-error text-sm font-medium px-3 py-1 border border-error/75 rounded hover:bg-error/10 transition-colors cursor-pointer" className="text-error/75 hover:text-error text-sm font-medium px-3 py-1 border border-error/75 rounded hover:bg-error/10 transition-colors cursor-pointer"
> >
{transI18n("clearAll")} {transI18n("clearAll")}
</button> </button>
<button <button
onClick={selectAll} onClick={selectAll}
className="text-primary/75 hover:text-primary text-sm font-medium px-3 py-1 border border-primary/75 rounded hover:bg-primary/10 transition-colors cursor-pointer"> className="text-primary/75 hover:text-primary text-sm font-medium px-3 py-1 border border-primary/75 rounded hover:bg-primary/10 transition-colors cursor-pointer">
{transI18n("selectAll")} {transI18n("selectAll")}
</button> </button>
{selectedProfiles.length > 0 && ( {selectedProfiles.length > 0 && (
<button <button
onClick={handleCopy} onClick={handleCopy}
className="text-success/75 hover:text-success text-sm font-medium px-3 py-1 border border-success/75 rounded hover:bg-success/10 transition-colors cursor-pointer"> className="text-success/75 hover:text-success text-sm font-medium px-3 py-1 border border-success/75 rounded hover:bg-success/10 transition-colors cursor-pointer">
{transI18n("copy")} {transI18n("copy")}
</button> </button>
)} )}
</div> </div>
{message.type && message.text && ( {message.type && message.text && (
<div className={(message.type == "error" ? "text-error" : "text-success") + " text-lg mt-2"} >{message.type == "error" ? "😭" : "🎉"} {message.text}</div> <div className={(message.type == "error" ? "text-error" : "text-success") + " text-lg mt-2"} >{message.type == "error" ? "😭" : "🎉"} {message.text}</div>
)} )}
</div> </div>
{/* Character Grid */} {/* Character Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{avatarCopySelected && avatars[avatarCopySelected?.ID.toString()]?.profileList.map((profile, index) => { {avatarCopySelected && avatars[avatarCopySelected?.ID.toString()]?.profileList.map((profile, index) => {
if (!profile.lightcone?.item_id && Object.keys(profile.relics ?? {}).length == 0) { if (!profile.lightcone?.item_id && Object.keys(profile.relics ?? {}).length == 0) {
return null; return null;
} }
return ( return (
<ProfileCard <ProfileCard
key={index} key={index}
profile={{ ...profile, key: index }} profile={{ ...profile, key: index }}
selectedProfile={selectedProfiles} selectedProfile={selectedProfiles}
onProfileToggle={handleProfileToggle} onProfileToggle={handleProfileToggle}
/> />
) )
})} })}
</div> </div>
</div> </div>
</div> </div>
) )
} }
+124 -124
View File
@@ -1,10 +1,10 @@
"use client" "use client"
import { useState } from "react"; import { useState } from "react";
import CharacterInfoCard from "../card/characterInfoCard"; import CharacterInfoCard from "../card/characterInfoCard";
import useEnkaStore from "@/stores/enkaStore"; import useEnkaStore from "@/stores/enkaStore";
import { SendDataThroughProxy } from "@/lib/api/api"; import { SendDataThroughProxy } from "@/lib/api/api";
import { CharacterInfoCardType, EnkaResponse } from "@/types"; import { CharacterInfoCardType, EnkaResponse } from "@/types";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import { converterOneEnkaDataToAvatarStore } from "@/helper"; import { converterOneEnkaDataToAvatarStore } from "@/helper";
import useModelStore from "@/stores/modelStore"; import useModelStore from "@/stores/modelStore";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -31,24 +31,24 @@ const toCharacterInfoCard = (character: AvatarEnkaDetailInput): CharacterInfoCar
relic_set_id: parseInt(relic.tid.toString().slice(1, -1), 10), relic_set_id: parseInt(relic.tid.toString().slice(1, -1), 10),
})), })),
}); });
export default function EnkaImport() { export default function EnkaImport() {
const { const {
uidInput, uidInput,
setUidInput, setUidInput,
enkaData, enkaData,
selectedCharacters, selectedCharacters,
setSelectedCharacters, setSelectedCharacters,
setEnkaData, setEnkaData,
} = useEnkaStore(); } = useEnkaStore();
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { avatars, setAvatar } = useUserDataStore(); const { avatars, setAvatar } = useUserDataStore();
const { setIsOpenImport } = useModelStore() const { setIsOpenImport } = useModelStore()
const { mapAvatar } = useDetailDataStore() const { mapAvatar } = useDetailDataStore()
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [Error, setError] = useState("") const [Error, setError] = useState("")
const validCharacters = enkaData?.detailInfo.avatarDetailList.filter((character) => mapAvatar?.[character.avatarId]) ?? [] const validCharacters = enkaData?.detailInfo.avatarDetailList.filter((character) => mapAvatar?.[character.avatarId]) ?? []
const handlerFetchData = async () => { const handlerFetchData = async () => {
if (!uidInput) { if (!uidInput) {
setError(transI18n("pleaseEnterUid")) setError(transI18n("pleaseEnterUid"))
@@ -77,36 +77,36 @@ export default function EnkaImport() {
setIsLoading(false) setIsLoading(false)
} }
} }
const handleCharacterToggle = (character: CharacterInfoCardType) => { const handleCharacterToggle = (character: CharacterInfoCardType) => {
if (selectedCharacters.some((selectedCharacter) => selectedCharacter.key === character.key)) { if (selectedCharacters.some((selectedCharacter) => selectedCharacter.key === character.key)) {
setSelectedCharacters(selectedCharacters.filter((selectedCharacter) => selectedCharacter.key !== character.key)); setSelectedCharacters(selectedCharacters.filter((selectedCharacter) => selectedCharacter.key !== character.key));
return; return;
} }
setSelectedCharacters([...selectedCharacters, character]); setSelectedCharacters([...selectedCharacters, character]);
}; };
const clearSelection = () => { const clearSelection = () => {
setSelectedCharacters([]); setSelectedCharacters([]);
}; };
const selectAll = () => { const selectAll = () => {
if (enkaData) { if (enkaData) {
setSelectedCharacters(validCharacters.map(toCharacterInfoCard)); setSelectedCharacters(validCharacters.map(toCharacterInfoCard));
} }
}; };
const handleImport = () => { const handleImport = () => {
if (selectedCharacters.length === 0) { if (selectedCharacters.length === 0) {
setError(transI18n("pleaseSelectAtLeastOneCharacter")); setError(transI18n("pleaseSelectAtLeastOneCharacter"));
return; return;
} }
if (!enkaData) { if (!enkaData) {
setError(transI18n("noDataToImport")); setError(transI18n("noDataToImport"));
return; return;
} }
setError(""); setError("");
const listAvatars = { ...avatars } const listAvatars = { ...avatars }
const parsed = enkaResponseSchema.safeParse(enkaData) const parsed = enkaResponseSchema.safeParse(enkaData)
if (!parsed.success) { if (!parsed.success) {
setError(transI18n("failedToFetchEnkaData")); setError(transI18n("failedToFetchEnkaData"));
@@ -125,80 +125,80 @@ export default function EnkaImport() {
acc[skill.pointId] = skill.level; acc[skill.pointId] = skill.level;
return acc; return acc;
}, {} as Record<string, number>), }, {} as Record<string, number>),
} }
const newProfile = converterOneEnkaDataToAvatarStore(character, newAvatar.profileList.length) const newProfile = converterOneEnkaDataToAvatarStore(character, newAvatar.profileList.length)
if (newProfile) { if (newProfile) {
newAvatar.profileList.push(newProfile) newAvatar.profileList.push(newProfile)
newAvatar.profileSelect = newAvatar.profileList.length - 1 newAvatar.profileSelect = newAvatar.profileList.length - 1
} }
setAvatar(newAvatar) setAvatar(newAvatar)
} }
}) })
setIsOpenImport(false) setIsOpenImport(false)
toast.success(transI18n("importEnkaDataSuccess")) toast.success(transI18n("importEnkaDataSuccess"))
}; };
return ( return (
<div className="bg-slate-900 p-6"> <div className="bg-slate-900 p-6">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
{/* Header */} {/* Header */}
<div className="mb-8"> <div className="mb-8">
<h1 className="text-white text-2xl font-bold mb-6">HSR UID</h1> <h1 className="text-white text-2xl font-bold mb-6">HSR UID</h1>
<div className="flex gap-4 items-center mb-6"> <div className="flex gap-4 items-center mb-6">
<input <input
type="text" type="text"
value={uidInput} value={uidInput}
onChange={(e) => setUidInput(e.target.value)} onChange={(e) => setUidInput(e.target.value)}
className="bg-slate-800 text-white px-4 py-3 rounded-lg border border-slate-700 flex-1 max-w-md focus:outline-none focus:border-blue-500" className="bg-slate-800 text-white px-4 py-3 rounded-lg border border-slate-700 flex-1 max-w-md focus:outline-none focus:border-blue-500"
placeholder="Enter UID" placeholder="Enter UID"
/> />
<button onClick={handlerFetchData} className="btn btn-success rounded-lg transition-colors"> <button onClick={handlerFetchData} className="btn btn-success rounded-lg transition-colors">
{transI18n("getData")} {transI18n("getData")}
</button> </button>
{selectedCharacters.length > 0 && ( {selectedCharacters.length > 0 && (
<button onClick={handleImport} className="btn btn-primary rounded-lg transition-colors"> <button onClick={handleImport} className="btn btn-primary rounded-lg transition-colors">
{transI18n("import")} {transI18n("import")}
</button> </button>
)} )}
</div> </div>
{Error && ( {Error && (
<div className="text-error text-base mt-2">😭 {Error}</div> <div className="text-error text-base mt-2">😭 {Error}</div>
)} )}
{/* Player Info */} {/* Player Info */}
{enkaData && ( {enkaData && (
<div className="text-white space-y-2"> <div className="text-white space-y-2">
<div className="text-lg">Name: {enkaData.detailInfo.nickname}</div> <div className="text-lg">Name: {enkaData.detailInfo.nickname}</div>
<div className="text-lg">UID: {enkaData.detailInfo.uid}</div> <div className="text-lg">UID: {enkaData.detailInfo.uid}</div>
<div className="text-lg">Level: {enkaData.detailInfo.level}</div> <div className="text-lg">Level: {enkaData.detailInfo.level}</div>
</div> </div>
)} )}
{/* Selection Info */} {/* Selection Info */}
<div className="mt-6 flex items-center gap-4 bg-slate-800/50 p-4 rounded-lg"> <div className="mt-6 flex items-center gap-4 bg-slate-800/50 p-4 rounded-lg">
<span className="text-blue-400 font-medium"> <span className="text-blue-400 font-medium">
{transI18n("selectedCharacters")}: {selectedCharacters.length} {transI18n("selectedCharacters")}: {selectedCharacters.length}
</span> </span>
<button <button
onClick={clearSelection} onClick={clearSelection}
className="text-error/75 hover:text-error text-sm font-medium px-3 py-1 border border-error/75 rounded hover:bg-error/10 transition-colors cursor-pointer" className="text-error/75 hover:text-error text-sm font-medium px-3 py-1 border border-error/75 rounded hover:bg-error/10 transition-colors cursor-pointer"
> >
{transI18n("clearAll")} {transI18n("clearAll")}
</button> </button>
<button onClick={selectAll} className="text-primary/75 hover:text-primary text-sm font-medium px-3 py-1 border border-primary/75 rounded hover:bg-primary/10 transition-colors cursor-pointer"> <button onClick={selectAll} className="text-primary/75 hover:text-primary text-sm font-medium px-3 py-1 border border-primary/75 rounded hover:bg-primary/10 transition-colors cursor-pointer">
{transI18n("selectAll")} {transI18n("selectAll")}
</button> </button>
</div> </div>
</div> </div>
{isLoading && ( {isLoading && (
<div className="flex justify-center items-center h-64"> <div className="flex justify-center items-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white"></div> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white"></div>
</div> </div>
)} )}
{/* Character Grid */} {/* Character Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{validCharacters.map((character) => ( {validCharacters.map((character) => (
<CharacterInfoCard <CharacterInfoCard
key={character.avatarId} key={character.avatarId}
@@ -206,9 +206,9 @@ export default function EnkaImport() {
selectedCharacters={selectedCharacters} selectedCharacters={selectedCharacters}
onCharacterToggle={handleCharacterToggle} onCharacterToggle={handleCharacterToggle}
/> />
))} ))}
</div> </div>
</div> </div>
</div> </div>
); );
} }
+134 -134
View File
@@ -1,15 +1,15 @@
"use client" "use client"
import useFreeSRStore from "@/stores/freesrStore"; import useFreeSRStore from "@/stores/freesrStore";
import useModelStore from "@/stores/modelStore"; import useModelStore from "@/stores/modelStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import { CharacterInfoCardType } from "@/types"; import { CharacterInfoCardType } from "@/types";
import { useState } from "react"; import { useState } from "react";
import CharacterInfoCard from "../card/characterInfoCard"; import CharacterInfoCard from "../card/characterInfoCard";
import { freeSrJsonSchema } from "@/zod"; import { freeSrJsonSchema } from "@/zod";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { converterOneFreeSRDataToAvatarStore } from "@/helper"; import { converterOneFreeSRDataToAvatarStore } from "@/helper";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
import { z } from "zod"; import { z } from "zod";
@@ -36,55 +36,55 @@ const toCharacterInfoCard = (data: FreeSRJsonInput, character: FreeSRJsonInput["
})), })),
} }
} }
export default function FreeSRImport() { export default function FreeSRImport() {
const { avatars, setAvatar } = useUserDataStore(); const { avatars, setAvatar } = useUserDataStore();
const { mapAvatar } = useDetailDataStore(); const { mapAvatar } = useDetailDataStore();
const { setIsOpenImport } = useModelStore() const { setIsOpenImport } = useModelStore()
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [Error, setError] = useState("") const [Error, setError] = useState("")
const { freeSRData, setFreeSRData, selectedCharacters, setSelectedCharacters } = useFreeSRStore() const { freeSRData, setFreeSRData, selectedCharacters, setSelectedCharacters } = useFreeSRStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const parsedFreeSRData = freeSrJsonSchema.safeParse(freeSRData) const parsedFreeSRData = freeSrJsonSchema.safeParse(freeSRData)
const validFreeSRData = parsedFreeSRData.success ? parsedFreeSRData.data : null const validFreeSRData = parsedFreeSRData.success ? parsedFreeSRData.data : null
const handleCharacterToggle = (character: CharacterInfoCardType) => { const handleCharacterToggle = (character: CharacterInfoCardType) => {
if (selectedCharacters.some((selectedCharacter) => selectedCharacter.key === character.key)) { if (selectedCharacters.some((selectedCharacter) => selectedCharacter.key === character.key)) {
setSelectedCharacters(selectedCharacters.filter((selectedCharacter) => selectedCharacter.key !== character.key)); setSelectedCharacters(selectedCharacters.filter((selectedCharacter) => selectedCharacter.key !== character.key));
return; return;
} }
setSelectedCharacters([...selectedCharacters, character]); setSelectedCharacters([...selectedCharacters, character]);
}; };
const clearSelection = () => { const clearSelection = () => {
setSelectedCharacters([]); setSelectedCharacters([]);
}; };
const selectAll = () => { const selectAll = () => {
const parsed = freeSrJsonSchema.safeParse(freeSRData) const parsed = freeSrJsonSchema.safeParse(freeSRData)
if (parsed.success) { if (parsed.success) {
setSelectedCharacters(Object.values(parsed.data.avatars).filter(it => mapAvatar?.[it.avatar_id]).map((character) => toCharacterInfoCard(parsed.data, character))); setSelectedCharacters(Object.values(parsed.data.avatars).filter(it => mapAvatar?.[it.avatar_id]).map((character) => toCharacterInfoCard(parsed.data, character)));
} }
}; };
const handlerReadFile = (event: React.ChangeEvent<HTMLInputElement>) => { const handlerReadFile = (event: React.ChangeEvent<HTMLInputElement>) => {
setIsLoading(true) setIsLoading(true)
const file = event.target.files?.[0]; const file = event.target.files?.[0];
if (!file) { if (!file) {
setSelectedCharacters([]) setSelectedCharacters([])
setFreeSRData(null) setFreeSRData(null)
setError(transI18n("pleaseSelectAFile")) setError(transI18n("pleaseSelectAFile"))
setIsLoading(false) setIsLoading(false)
return return
} }
if (!file.name.endsWith(".json") || file.type !== "application/json") { if (!file.name.endsWith(".json") || file.type !== "application/json") {
setSelectedCharacters([]) setSelectedCharacters([])
setFreeSRData(null) setFreeSRData(null)
setError(transI18n("fileMustBeAValidJsonFile")) setError(transI18n("fileMustBeAValidJsonFile"))
setIsLoading(false) setIsLoading(false)
return return
} }
if (file) { if (file) {
const reader = new FileReader(); const reader = new FileReader();
@@ -121,18 +121,18 @@ export default function FreeSRImport() {
reader.readAsText(file); reader.readAsText(file);
} }
}; };
const handleImport = () => { const handleImport = () => {
if (selectedCharacters.length === 0) { if (selectedCharacters.length === 0) {
setError(transI18n("pleaseSelectAtLeastOneCharacter")); setError(transI18n("pleaseSelectAtLeastOneCharacter"));
return; return;
} }
if (!freeSRData) { if (!freeSRData) {
setError(transI18n("noDataToImport")); setError(transI18n("noDataToImport"));
return; return;
} }
setError(""); setError("");
const parsed = freeSrJsonSchema.safeParse(freeSRData) const parsed = freeSrJsonSchema.safeParse(freeSRData)
if (!parsed.success) { if (!parsed.success) {
setError(transI18n("fileMustBeAValidJsonFile")); setError(transI18n("fileMustBeAValidJsonFile"));
@@ -144,73 +144,73 @@ export default function FreeSRImport() {
filterData.forEach((character) => { filterData.forEach((character) => {
const newAvatar = { ...listAvatars[character.avatar_id] } const newAvatar = { ...listAvatars[character.avatar_id] }
if (Object.keys(newAvatar).length !== 0) { if (Object.keys(newAvatar).length !== 0) {
newAvatar.level = character.level newAvatar.level = character.level
newAvatar.promotion = character.promotion newAvatar.promotion = character.promotion
newAvatar.data = { newAvatar.data = {
rank: character.data.rank ?? 0, rank: character.data.rank ?? 0,
skills: character.data.skills skills: character.data.skills
} }
const newProfile = converterOneFreeSRDataToAvatarStore(parsed.data, newAvatar.profileList.length, character.avatar_id) const newProfile = converterOneFreeSRDataToAvatarStore(parsed.data, newAvatar.profileList.length, character.avatar_id)
if (newProfile) { if (newProfile) {
newAvatar.profileList.push(newProfile) newAvatar.profileList.push(newProfile)
newAvatar.profileSelect = newAvatar.profileList.length - 1 newAvatar.profileSelect = newAvatar.profileList.length - 1
} }
setAvatar(newAvatar) setAvatar(newAvatar)
} }
}) })
setIsOpenImport(false) setIsOpenImport(false)
toast.success(transI18n("importFreeSRDataSuccess")) toast.success(transI18n("importFreeSRDataSuccess"))
} }
return ( return (
<div className="bg-slate-900 p-6"> <div className="bg-slate-900 p-6">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
{/* Header */} {/* Header */}
<div className="mb-8"> <div className="mb-8">
<h1 className="text-white text-2xl font-bold mb-6">{transI18n("freeSRImport")}</h1> <h1 className="text-white text-2xl font-bold mb-6">{transI18n("freeSRImport")}</h1>
<div className="flex gap-4 items-center mb-6"> <div className="flex gap-4 items-center mb-6">
<fieldset className="fieldset"> <fieldset className="fieldset">
<legend className="fieldset-legend">{transI18n("pickAFile")}</legend> <legend className="fieldset-legend">{transI18n("pickAFile")}</legend>
<input type="file" accept=".json" className="file-input file-input-info" onChange={handlerReadFile} /> <input type="file" accept=".json" className="file-input file-input-info" onChange={handlerReadFile} />
<label className="label">{transI18n("onlySupportFreeSRJsonFile")}</label> <label className="label">{transI18n("onlySupportFreeSRJsonFile")}</label>
</fieldset> </fieldset>
{selectedCharacters.length > 0 && ( {selectedCharacters.length > 0 && (
<button onClick={handleImport} className="btn btn-primary rounded-lg transition-colors"> <button onClick={handleImport} className="btn btn-primary rounded-lg transition-colors">
{transI18n("import")} {transI18n("import")}
</button> </button>
)} )}
</div> </div>
{Error && ( {Error && (
<div className="text-error text-base mt-2">😭 {Error}</div> <div className="text-error text-base mt-2">😭 {Error}</div>
)} )}
{/* Selection Info */} {/* Selection Info */}
<div className="mt-6 flex items-center gap-4 bg-slate-800/50 p-4 rounded-lg"> <div className="mt-6 flex items-center gap-4 bg-slate-800/50 p-4 rounded-lg">
<span className="text-blue-400 font-medium"> <span className="text-blue-400 font-medium">
{transI18n("selectedCharacters")}: {selectedCharacters.length} {transI18n("selectedCharacters")}: {selectedCharacters.length}
</span> </span>
<button <button
onClick={clearSelection} onClick={clearSelection}
className="text-error/75 hover:text-error text-sm font-medium px-3 py-1 border border-error/75 rounded hover:bg-error/10 transition-colors cursor-pointer" className="text-error/75 hover:text-error text-sm font-medium px-3 py-1 border border-error/75 rounded hover:bg-error/10 transition-colors cursor-pointer"
> >
{transI18n("clearAll")} {transI18n("clearAll")}
</button> </button>
<button onClick={selectAll} className="text-primary/75 hover:text-primary text-sm font-medium px-3 py-1 border border-primary/75 rounded hover:bg-primary/10 transition-colors cursor-pointer"> <button onClick={selectAll} className="text-primary/75 hover:text-primary text-sm font-medium px-3 py-1 border border-primary/75 rounded hover:bg-primary/10 transition-colors cursor-pointer">
{transI18n("selectAll")} {transI18n("selectAll")}
</button> </button>
</div> </div>
</div> </div>
{isLoading && ( {isLoading && (
<div className="flex justify-center items-center h-64"> <div className="flex justify-center items-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white"></div> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white"></div>
</div> </div>
)} )}
{/* Character Grid */} {/* Character Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{validFreeSRData && Object.values(validFreeSRData.avatars).filter(it => mapAvatar?.[it.avatar_id]).map((character) => { {validFreeSRData && Object.values(validFreeSRData.avatars).filter(it => mapAvatar?.[it.avatar_id]).map((character) => {
return ( return (
<CharacterInfoCard <CharacterInfoCard
@@ -219,10 +219,10 @@ export default function FreeSRImport() {
selectedCharacters={selectedCharacters} selectedCharacters={selectedCharacters}
onCharacterToggle={handleCharacterToggle} onCharacterToggle={handleCharacterToggle}
/> />
) )
})} })}
</div> </div>
</div> </div>
</div> </div>
) )
} }
+144 -144
View File
@@ -1,145 +1,145 @@
"use client" "use client"
import { useMemo } from "react" import { useMemo } from "react"
import Image from "next/image"; import Image from "next/image";
import useLocaleStore from "@/stores/localeStore" import useLocaleStore from "@/stores/localeStore"
import LightconeCard from "../card/lightconeCard"; import LightconeCard from "../card/lightconeCard";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import useModelStore from "@/stores/modelStore"; import useModelStore from "@/stores/modelStore";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useCurrentDataStore from "@/stores/currentDataStore"; import useCurrentDataStore from "@/stores/currentDataStore";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
import { calcRarity, getLocaleName } from "@/helper"; import { calcRarity, getLocaleName } from "@/helper";
export default function LightconeBar() { export default function LightconeBar() {
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
avatarSelected, avatarSelected,
mapLightconePathActive, mapLightconePathActive,
mapLightconeRankActive, mapLightconeRankActive,
setMapLightconePathActive, setMapLightconePathActive,
setMapLightconeRankActive, setMapLightconeRankActive,
lightconeSearch, lightconeSearch,
setLightconeSearch setLightconeSearch
} = useCurrentDataStore() } = useCurrentDataStore()
const { setAvatar, avatars } = useUserDataStore() const { setAvatar, avatars } = useUserDataStore()
const { setIsOpenLightcone } = useModelStore() const { setIsOpenLightcone } = useModelStore()
const { mapLightCone, baseType } = useDetailDataStore() const { mapLightCone, baseType } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const listLightcone = useMemo(() => { const listLightcone = useMemo(() => {
if (!mapLightCone || !locale) return [] if (!mapLightCone || !locale) return []
let list = Object.values(mapLightCone) let list = Object.values(mapLightCone)
if (lightconeSearch) { if (lightconeSearch) {
list = list.filter(item => getLocaleName(locale, item.Name).toLowerCase().includes(lightconeSearch.toLowerCase())) list = list.filter(item => getLocaleName(locale, item.Name).toLowerCase().includes(lightconeSearch.toLowerCase()))
} }
const allRankFalse = !Object.values(mapLightconeRankActive).some(v => v) const allRankFalse = !Object.values(mapLightconeRankActive).some(v => v)
const allPathFalse = !Object.values(mapLightconePathActive).some(v => v) const allPathFalse = !Object.values(mapLightconePathActive).some(v => v)
list = list.filter(item => list = list.filter(item =>
(allRankFalse || mapLightconeRankActive[item.Rarity]) && (allRankFalse || mapLightconeRankActive[item.Rarity]) &&
(allPathFalse || mapLightconePathActive[item.BaseType]) (allPathFalse || mapLightconePathActive[item.BaseType])
) )
list.sort((a, b) => { list.sort((a, b) => {
const r = calcRarity(b.Rarity) - calcRarity(a.Rarity) const r = calcRarity(b.Rarity) - calcRarity(a.Rarity)
if (r !== 0) return r if (r !== 0) return r
return b.ID - a.ID return b.ID - a.ID
}) })
return list return list
}, [mapLightCone, mapLightconePathActive, mapLightconeRankActive, lightconeSearch, locale]) }, [mapLightCone, mapLightconePathActive, mapLightconeRankActive, lightconeSearch, locale])
return ( return (
<div> <div>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4"> <div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400"> <h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400">
{transI18n("lightConeSetting")} {transI18n("lightConeSetting")}
</h3> </h3>
</div> </div>
<div className="mt-2 w-full"> <div className="mt-2 w-full">
<div className="flex items-start flex-col gap-2"> <div className="flex items-start flex-col gap-2">
<div>Search</div> <div>Search</div>
<input <input
value={lightconeSearch} value={lightconeSearch}
onChange={(e) => setLightconeSearch(e.target.value)} onChange={(e) => setLightconeSearch(e.target.value)}
type="text" placeholder="LightCone Name" className="input input-accent mt-1 w-full" type="text" placeholder="LightCone Name" className="input input-accent mt-1 w-full"
/> />
</div> </div>
<div className="flex items-start flex-col gap-2 mt-2 w-full"> <div className="flex items-start flex-col gap-2 mt-2 w-full">
<div>Filter</div> <div>Filter</div>
<div className="flex flex-row flex-wrap justify-between mt-1 w-full"> <div className="flex flex-row flex-wrap justify-between mt-1 w-full">
<div className="flex flex-wrap mb-1 mx-1 gap-2"> <div className="flex flex-wrap mb-1 mx-1 gap-2">
{Object.entries(baseType).filter(([key]) => key !== "").map(([key, value]) => ( {Object.entries(baseType).filter(([key]) => key !== "").map(([key, value]) => (
<div <div
key={key} key={key}
onClick={() => { onClick={() => {
setMapLightconePathActive({ ...mapLightconePathActive, [key]: !mapLightconePathActive[key] }) setMapLightconePathActive({ ...mapLightconePathActive, [key]: !mapLightconePathActive[key] })
}} }}
className="h-9.5 w-9.5 md:h-12.5 md:w-12.5 hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer" className="h-9.5 w-9.5 md:h-12.5 md:w-12.5 hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer"
style={{ style={{
backgroundColor: mapLightconePathActive[key] ? "#374151" : "#6B7280" backgroundColor: mapLightconePathActive[key] ? "#374151" : "#6B7280"
}} }}
> >
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${value.Icon}`} src={`${process.env.CDN_URL}/${value.Icon}`}
alt={key} alt={key}
className="h-7 w-7 md:h-8 md:w-8 object-contain rounded-md" className="h-7 w-7 md:h-8 md:w-8 object-contain rounded-md"
width={200} width={200}
height={200} height={200}
/> />
</div> </div>
))} ))}
</div> </div>
<div className="flex flex-wrap mb-1 mx-1 gap-2"> <div className="flex flex-wrap mb-1 mx-1 gap-2">
{Object.keys(mapLightconeRankActive).map((key, index) => ( {Object.keys(mapLightconeRankActive).map((key, index) => (
<div <div
key={index} key={index}
onClick={() => { onClick={() => {
setMapLightconeRankActive({ ...mapLightconeRankActive, [key]: !mapLightconeRankActive[key] }) setMapLightconeRankActive({ ...mapLightconeRankActive, [key]: !mapLightconeRankActive[key] })
}} }}
className="h-9.5 w-9.5 md:h-12.5 md:w-12.5 hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer" className="h-9.5 w-9.5 md:h-12.5 md:w-12.5 hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer"
style={{ style={{
backgroundColor: mapLightconeRankActive[key] ? "#374151" : "#6B7280" backgroundColor: mapLightconeRankActive[key] ? "#374151" : "#6B7280"
}} }}
> >
<div className="font-bold text-white h-8 w-8 text-center flex items-center justify-center"> <div className="font-bold text-white h-8 w-8 text-center flex items-center justify-center">
{key}* {key}*
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="grid grid-cols-3 md:grid-cols-6 lg:grid-cols-8 mt-2 gap-2"> <div className="grid grid-cols-3 md:grid-cols-6 lg:grid-cols-8 mt-2 gap-2">
{listLightcone.map((item, index) => ( {listLightcone.map((item, index) => (
<div key={index} onClick={() => { <div key={index} onClick={() => {
if (avatarSelected) { if (avatarSelected) {
const avatar = avatars[avatarSelected?.ID?.toString()] const avatar = avatars[avatarSelected?.ID?.toString()]
avatar.profileList[avatar.profileSelect].lightcone = { avatar.profileList[avatar.profileSelect].lightcone = {
level: 80, level: 80,
item_id: item.ID, item_id: item.ID,
rank: 1, rank: 1,
promotion: 6 promotion: 6
} }
setAvatar({ ...avatar }) setAvatar({ ...avatar })
setIsOpenLightcone(false) setIsOpenLightcone(false)
} }
}}> }}>
<LightconeCard data={item} /> <LightconeCard data={item} />
</div> </div>
))} ))}
</div> </div>
</div> </div>
) )
} }
+340 -340
View File
@@ -1,341 +1,341 @@
"use client" "use client"
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import SelectCustomText from "../select/customSelectText"; import SelectCustomText from "../select/customSelectText";
import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper"; import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import Image from "next/image"; import Image from "next/image";
import { ASEvent, MonsterStore } from "@/types"; import { ASEvent, MonsterStore } from "@/types";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
export default function AsBar() { export default function AsBar() {
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
as_config, as_config,
setAsConfig setAsConfig
} = useUserDataStore() } = useUserDataStore()
const { mapMonster, mapAS, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore() const { mapMonster, mapAS, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const challengeSelected = useMemo(() => { const challengeSelected = useMemo(() => {
return mapAS[as_config.event_id.toString()]?.Level.find((as) => as.ID === as_config.challenge_id) return mapAS[as_config.event_id.toString()]?.Level.find((as) => as.ID === as_config.challenge_id)
}, [as_config, mapAS]) }, [as_config, mapAS])
const eventSelected = useMemo(() => { const eventSelected = useMemo(() => {
return mapAS[as_config.event_id.toString()] return mapAS[as_config.event_id.toString()]
}, [as_config, mapAS]) }, [as_config, mapAS])
const floorSideList = useMemo(() => { const floorSideList = useMemo(() => {
if (!eventSelected) return []; if (!eventSelected) return [];
const floorList = [ const floorList = [
{ {
id: "firstNode", id: "firstNode",
name: transI18n("firstNode"), name: transI18n("firstNode"),
wave: transI18n("firstNodeEnemies") wave: transI18n("firstNodeEnemies")
}, },
{ {
id: "secondNode", id: "secondNode",
name: transI18n("secondNode"), name: transI18n("secondNode"),
wave: transI18n("secondNodeEnemies") wave: transI18n("secondNodeEnemies")
}, },
] ]
if (eventSelected?.Tierce && eventSelected.Tierce.PreChallenge === as_config.challenge_id) { if (eventSelected?.Tierce && eventSelected.Tierce.PreChallenge === as_config.challenge_id) {
floorList.push({ floorList.push({
id: "thirdNode", id: "thirdNode",
name: transI18n("thirdNode"), name: transI18n("thirdNode"),
wave: transI18n("thirdNodeEnemies") wave: transI18n("thirdNodeEnemies")
}) })
} }
return floorList return floorList
}, [as_config.challenge_id, eventSelected, transI18n]) }, [as_config.challenge_id, eventSelected, transI18n])
const buffList = useMemo(() => { const buffList = useMemo(() => {
if (!eventSelected) return []; if (!eventSelected) return [];
if (as_config.floor_side === "firstNode") { if (as_config.floor_side === "firstNode") {
return eventSelected?.BuffList1 ?? []; return eventSelected?.BuffList1 ?? [];
} }
if (as_config.floor_side === "secondNode") { if (as_config.floor_side === "secondNode") {
return eventSelected?.BuffList2 ?? []; return eventSelected?.BuffList2 ?? [];
} }
if (as_config.floor_side === "thirdNode" && eventSelected?.BuffList3) { if (as_config.floor_side === "thirdNode" && eventSelected?.BuffList3) {
return eventSelected?.BuffList3 ?? []; return eventSelected?.BuffList3 ?? [];
} }
return []; return [];
}, [as_config.floor_side, eventSelected]); }, [as_config.floor_side, eventSelected]);
useEffect(() => { useEffect(() => {
if (!challengeSelected || as_config.event_id === 0 || as_config.challenge_id === 0) return if (!challengeSelected || as_config.event_id === 0 || as_config.challenge_id === 0) return
const newBattleConfig = structuredClone(as_config) const newBattleConfig = structuredClone(as_config)
newBattleConfig.cycle_count = challengeSelected.TurnLimit newBattleConfig.cycle_count = challengeSelected.TurnLimit
newBattleConfig.blessings = [] newBattleConfig.blessings = []
if (as_config.buff_id !== 0) { if (as_config.buff_id !== 0) {
newBattleConfig.blessings.push({ newBattleConfig.blessings.push({
id: as_config.buff_id, id: as_config.buff_id,
level: 1 level: 1
}) })
} }
if (challengeSelected) { if (challengeSelected) {
challengeSelected.MazeBuff.map((item) => { challengeSelected.MazeBuff.map((item) => {
newBattleConfig.blessings.push({ newBattleConfig.blessings.push({
id: item.ID, id: item.ID,
level: 1 level: 1
}) })
}) })
} }
newBattleConfig.monsters = [] newBattleConfig.monsters = []
newBattleConfig.stage_id = 0 newBattleConfig.stage_id = 0
let targetEventList: ASEvent[] = [] let targetEventList: ASEvent[] = []
if (as_config.floor_side === "firstNode" && challengeSelected.EventList1.length > 0) { if (as_config.floor_side === "firstNode" && challengeSelected.EventList1.length > 0) {
targetEventList = challengeSelected.EventList1 targetEventList = challengeSelected.EventList1
} else if (as_config.floor_side === "secondNode" && challengeSelected.EventList2.length > 0) { } else if (as_config.floor_side === "secondNode" && challengeSelected.EventList2.length > 0) {
targetEventList = challengeSelected.EventList2 targetEventList = challengeSelected.EventList2
} else if (as_config.floor_side === "thirdNode" && eventSelected?.Tierce && eventSelected.Tierce.EventList.length > 0) { } else if (as_config.floor_side === "thirdNode" && eventSelected?.Tierce && eventSelected.Tierce.EventList.length > 0) {
targetEventList = eventSelected.Tierce.EventList targetEventList = eventSelected.Tierce.EventList
} }
if (targetEventList.length > 0) { if (targetEventList.length > 0) {
newBattleConfig.stage_id = targetEventList[0].ID newBattleConfig.stage_id = targetEventList[0].ID
for (const wave of targetEventList[0].MonsterList) { for (const wave of targetEventList[0].MonsterList) {
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: value, monster_id: value,
level: targetEventList[0].Level, level: targetEventList[0].Level,
amount: 1, amount: 1,
}) })
} }
newBattleConfig.monsters.push(newWave) newBattleConfig.monsters.push(newWave)
} }
} }
setAsConfig(newBattleConfig) setAsConfig(newBattleConfig)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
challengeSelected, challengeSelected,
eventSelected, eventSelected,
mapAS, mapAS,
as_config.event_id, as_config.event_id,
as_config.challenge_id, as_config.challenge_id,
as_config.floor_side, as_config.floor_side,
as_config.buff_id, as_config.buff_id,
]) ])
if (!mapAS) return null if (!mapAS) return null
return ( return (
<div className="py-8 relative"> <div className="py-8 relative">
{/* Title Card */} {/* Title Card */}
<div className="rounded-xl p-4 mb-2 border border-warning"> <div className="rounded-xl p-4 mb-2 border border-warning">
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={Object.values(mapAS).sort((a, b) => b.ID - a.ID).map((as) => ({ customSet={Object.values(mapAS).sort((a, b) => b.ID - a.ID).map((as) => ({
id: as.ID.toString(), id: as.ID.toString(),
name: getLocaleName(locale, as.Name), name: getLocaleName(locale, as.Name),
time: `${as.BeginTime} - ${as.EndTime}`, time: `${as.BeginTime} - ${as.EndTime}`,
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={as_config.event_id.toString()} selectedCustomSet={as_config.event_id.toString()}
placeholder={transI18n("selectASEvent")} placeholder={transI18n("selectASEvent")}
setSelectedCustomSet={(id) => setAsConfig({ setSelectedCustomSet={(id) => setAsConfig({
...as_config, ...as_config,
event_id: Number(id), event_id: Number(id),
challenge_id: mapAS[Number(id)]?.Level.at(-1)?.ID || 0, challenge_id: mapAS[Number(id)]?.Level.at(-1)?.ID || 0,
buff_id: 0 buff_id: 0
})} })}
/> />
</div> </div>
{/* Settings */} {/* Settings */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-2"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<label className="label"> <label className="label">
<span className="label-text font-bold text-success">{transI18n("floor")}:{" "}</span> <span className="label-text font-bold text-success">{transI18n("floor")}:{" "}</span>
</label> </label>
<select <select
value={as_config.challenge_id} value={as_config.challenge_id}
className="select select-success" className="select select-success"
onChange={(e) => setAsConfig({ ...as_config, challenge_id: Number(e.target.value) })} onChange={(e) => setAsConfig({ ...as_config, challenge_id: Number(e.target.value) })}
> >
<option value={0} disabled={true}>{transI18n("selectFloor")}</option> <option value={0} disabled={true}>{transI18n("selectFloor")}</option>
{eventSelected?.Level.map((as) => ( {eventSelected?.Level.map((as) => (
<option key={as.ID} value={as.ID}>{getLocaleName(locale, as.Name)}</option> <option key={as.ID} value={as.ID}>{getLocaleName(locale, as.Name)}</option>
))} ))}
</select> </select>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<label className="label"> <label className="label">
<span className="label-text font-bold text-success">{transI18n("side")}:{" "}</span> <span className="label-text font-bold text-success">{transI18n("side")}:{" "}</span>
</label> </label>
<select <select
value={as_config.floor_side} value={as_config.floor_side}
className="select select-success" className="select select-success"
onChange={(e) => setAsConfig({ ...as_config, floor_side: e.target.value })} onChange={(e) => setAsConfig({ ...as_config, floor_side: e.target.value })}
> >
<option value={0} disabled={true}>{transI18n("selectSide")}</option> <option value={0} disabled={true}>{transI18n("selectSide")}</option>
{floorSideList.map((side) => { {floorSideList.map((side) => {
return <option key={side.id} value={side.id}>{side.name}</option> return <option key={side.id} value={side.id}>{side.name}</option>
})} })}
</select> </select>
</div> </div>
</div> </div>
<div className="label-text font-bold text-success mb-2">StageId: {as_config?.stage_id}</div> <div className="label-text font-bold text-success mb-2">StageId: {as_config?.stage_id}</div>
{eventSelected && ( {eventSelected && (
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={buffList.map((buff) => ({ customSet={buffList.map((buff) => ({
id: buff.ID?.toString() || "", id: buff.ID?.toString() || "",
name: getLocaleName(locale, buff?.Name) || "", name: getLocaleName(locale, buff?.Name) || "",
description: replaceByParam(getLocaleName(locale, buff?.Desc) || "", buff?.Param || []), description: replaceByParam(getLocaleName(locale, buff?.Desc) || "", buff?.Param || []),
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={as_config?.buff_id?.toString()} selectedCustomSet={as_config?.buff_id?.toString()}
placeholder={transI18n("selectBuff")} placeholder={transI18n("selectBuff")}
setSelectedCustomSet={(id) => setAsConfig({ ...as_config, buff_id: Number(id) })} setSelectedCustomSet={(id) => setAsConfig({ ...as_config, buff_id: Number(id) })}
/> />
</div> </div>
)} )}
{/* Turbulence Buff */} {/* Turbulence Buff */}
<div className="bg-base-200/20 rounded-lg p-4 border border-purple-500/20"> <div className="bg-base-200/20 rounded-lg p-4 border border-purple-500/20">
<h2 className="text-2xl font-bold mb-2 text-info">{transI18n("turbulenceBuff")}</h2> <h2 className="text-2xl font-bold mb-2 text-info">{transI18n("turbulenceBuff")}</h2>
{challengeSelected ? ( {challengeSelected ? (
challengeSelected.MazeBuff.map((buff, i) => ( challengeSelected.MazeBuff.map((buff, i) => (
<div <div
key={i} key={i}
className="text-base" className="text-base"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
getLocaleName(locale, buff?.Desc) || "", getLocaleName(locale, buff?.Desc) || "",
buff?.Param || [] buff?.Param || []
) )
}} }}
/> />
)) ))
) : ( ) : (
<div className="text-base">{transI18n("noTurbulenceBuff")}</div> <div className="text-base">{transI18n("noTurbulenceBuff")}</div>
)} )}
</div> </div>
</div> </div>
{/* Enemy Waves */} {/* Enemy Waves */}
{(as_config?.challenge_id ?? 0) !== 0 && ( {(as_config?.challenge_id ?? 0) !== 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{floorSideList.map((side, i) => { {floorSideList.map((side, i) => {
const eventList = side.id === "firstNode" const eventList = side.id === "firstNode"
? challengeSelected?.EventList1 ? challengeSelected?.EventList1
: side.id === "secondNode" : side.id === "secondNode"
? challengeSelected?.EventList2 ? challengeSelected?.EventList2
: side.id === "thirdNode" : side.id === "thirdNode"
? eventSelected?.Tierce?.EventList ? eventSelected?.Tierce?.EventList
: []; : [];
if (!eventList || eventList.length === 0) return null; if (!eventList || eventList.length === 0) return null;
const targetEvent = eventList[0]; const targetEvent = eventList[0];
return ( return (
<div key={i} className="rounded-xl p-4 mt-2 border border-warning"> <div key={i} className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{side.wave}</h2> <h2 className="text-2xl font-bold mb-6 text-info">{side.wave}</h2>
{targetEvent?.MonsterList?.map((wave, waveIndex) => ( {targetEvent?.MonsterList?.map((wave, waveIndex) => (
<div key={waveIndex} className="mb-6"> <div key={waveIndex} className="mb-6">
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="flex flex-wrap gap-2 mt-2"> <div className="flex flex-wrap gap-2 mt-2">
{Object.values(wave).map((waveValue, enemyIndex) => { {Object.values(wave).map((waveValue, enemyIndex) => {
const monsterStats = calcMonsterStats( const monsterStats = calcMonsterStats(
mapMonster?.[waveValue.toString()], mapMonster?.[waveValue.toString()],
targetEvent?.EliteGroup, targetEvent?.EliteGroup,
targetEvent?.HardLevelGroup, targetEvent?.HardLevelGroup,
targetEvent?.Level, targetEvent?.Level,
hardLevelConfig, hardLevelConfig,
eliteConfig eliteConfig
); );
return ( return (
<div <div
key={enemyIndex} key={enemyIndex}
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md" className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
> >
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm"> <div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
Lv. {targetEvent.Level} Lv. {targetEvent.Level}
</div> </div>
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
{mapMonster?.[waveValue.toString()]?.Image?.IconPath && ( {mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
<div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100"> <div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`} src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
alt="Enemy Icon" alt="Enemy Icon"
width={150} width={150}
height={150} height={150}
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
</div> </div>
)} )}
</div> </div>
<div className="flex flex-col px-1 pb-2 pt-2"> <div className="flex flex-col px-1 pb-2 pt-2">
<div className="flex flex-col space-y-1.5"> <div className="flex flex-col space-y-1.5">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-error">HP</span> <span className="text-xs font-semibold text-error">HP</span>
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div> </div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-info">Speed</span> <span className="text-xs font-semibold text-info">Speed</span>
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div> </div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-base-content/70">Toughness</span> <span className="text-xs font-semibold text-base-content/70">Toughness</span>
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div> </div>
</div> </div>
<div className="mt-3 pt-2 border-t border-base-300 flex flex-col items-center"> <div className="mt-3 pt-2 border-t border-base-300 flex flex-col items-center">
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5"> <span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
Weakness Weakness
</span> </span>
<div className="flex items-center justify-center gap-1.5 flex-wrap"> <div className="flex items-center justify-center gap-1.5 flex-wrap">
{mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => ( {mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image <Image
key={iconIndex} key={iconIndex}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`} src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
alt={icon} alt={icon}
width={40} width={40}
height={40} height={40}
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform" className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
/> />
))} ))}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
) )
})} })}
</div> </div>
</div> </div>
))} ))}
</div> </div>
)})} )})}
</div> </div>
)} )}
</div> </div>
) )
} }
File diff suppressed because it is too large Load Diff
+99 -99
View File
@@ -1,100 +1,100 @@
import MocBar from "./moc"; import MocBar from "./moc";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import PfBar from "./pf"; import PfBar from "./pf";
import Image from "next/image"; import Image from "next/image";
import AsBar from "./as"; import AsBar from "./as";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import CeBar from "./ce"; import CeBar from "./ce";
import PeakBar from "./peak"; import PeakBar from "./peak";
export default function MonsterBar() { export default function MonsterBar() {
const { battle_type, setBattleType } = useUserDataStore() const { battle_type, setBattleType } = useUserDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const navItems = [ const navItems = [
{ name: transI18n("memoryOfChaos"), icon: 'AbyssIcon01', value: 'MOC' }, { name: transI18n("memoryOfChaos"), icon: 'AbyssIcon01', value: 'MOC' },
{ name: transI18n("pureFiction"), icon: 'ChallengeStory', value: 'PF' }, { name: transI18n("pureFiction"), icon: 'ChallengeStory', value: 'PF' },
{ name: transI18n("apocalypticShadow"), icon: 'ChallengeBoss', value: 'AS' }, { name: transI18n("apocalypticShadow"), icon: 'ChallengeBoss', value: 'AS' },
{ name: transI18n("anomalyArbitration"), icon: 'ChallengePeakIcon', value: 'PEAK' }, { name: transI18n("anomalyArbitration"), icon: 'ChallengePeakIcon', value: 'PEAK' },
{ name: transI18n("customEnemy"), icon: 'MonsterIcon', value: 'CE' }, { name: transI18n("customEnemy"), icon: 'MonsterIcon', value: 'CE' },
{ name: transI18n("simulatedUniverse"), icon: 'SimulatedUniverse', value: 'SU' }, { name: transI18n("simulatedUniverse"), icon: 'SimulatedUniverse', value: 'SU' },
]; ];
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
{/* Header Navigation */} {/* Header Navigation */}
<nav className="border-b border-warning/30 relative pb-2"> <nav className="border-b border-warning/30 relative pb-2">
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">
{/* Mobile Select */} {/* Mobile Select */}
<div className="block md:hidden w-full"> <div className="block md:hidden w-full">
<select <select
className="select select-bordered w-full" className="select select-bordered w-full"
value={battle_type.toUpperCase()} value={battle_type.toUpperCase()}
onChange={(e) => setBattleType(e.target.value.toUpperCase())} onChange={(e) => setBattleType(e.target.value.toUpperCase())}
> >
{navItems.map((item) => ( {navItems.map((item) => (
<option key={item.name} value={item.value.toUpperCase()}> <option key={item.name} value={item.value.toUpperCase()}>
{item.name} {item.name}
</option> </option>
))} ))}
</select> </select>
</div> </div>
{/* Desktop Tabs */} {/* Desktop Tabs */}
<div className="hidden md:grid grid-cols-3 lg:grid-cols-6 gap-1"> <div className="hidden md:grid grid-cols-3 lg:grid-cols-6 gap-1">
{navItems.map((item) => ( {navItems.map((item) => (
<button <button
key={item.name} key={item.name}
onClick={() => setBattleType(item.value.toUpperCase())} onClick={() => setBattleType(item.value.toUpperCase())}
className={`px-4 py-2 rounded-lg transition-all cursor-pointer duration-300 flex items-center space-x-2 ${battle_type.toUpperCase() === item.value.toUpperCase() className={`px-4 py-2 rounded-lg transition-all cursor-pointer duration-300 flex items-center space-x-2 ${battle_type.toUpperCase() === item.value.toUpperCase()
? 'bg-success/30 shadow-lg' ? 'bg-success/30 shadow-lg'
: 'bg-base-200/20 hover:bg-base-200/40 ' : 'bg-base-200/20 hover:bg-base-200/40 '
}`} }`}
> >
<span <span
style={ style={
battle_type.toUpperCase() === item.value.toUpperCase() battle_type.toUpperCase() === item.value.toUpperCase()
? { ? {
filter: filter:
'brightness(0) saturate(100%) invert(63%) sepia(78%) saturate(643%) hue-rotate(1deg) brightness(93%) contrast(89%)', 'brightness(0) saturate(100%) invert(63%) sepia(78%) saturate(643%) hue-rotate(1deg) brightness(93%) contrast(89%)',
} }
: undefined : undefined
} }
> >
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${item.icon}.webp`} src={`/icon/${item.icon}.webp`}
alt={item.name} alt={item.name}
width={24} width={24}
height={24} height={24}
/> />
</span> </span>
<span>{item.name}</span> <span>{item.name}</span>
</button> </button>
))} ))}
</div> </div>
</div> </div>
</nav> </nav>
{(battle_type.toUpperCase() === "DEFAULT" || battle_type.toUpperCase() === "") && ( {(battle_type.toUpperCase() === "DEFAULT" || battle_type.toUpperCase() === "") && (
<div className="container mx-auto px-4 py-8 text-center font-bold text-3xl"> <div className="container mx-auto px-4 py-8 text-center font-bold text-3xl">
{transI18n("noEventSelected")} {transI18n("noEventSelected")}
</div> </div>
)} )}
{battle_type.toUpperCase() === 'MOC' && <MocBar />} {battle_type.toUpperCase() === 'MOC' && <MocBar />}
{battle_type.toUpperCase() === 'PF' && <PfBar />} {battle_type.toUpperCase() === 'PF' && <PfBar />}
{battle_type.toUpperCase() === 'AS' && <AsBar />} {battle_type.toUpperCase() === 'AS' && <AsBar />}
{battle_type.toUpperCase() === 'CE' && <CeBar />} {battle_type.toUpperCase() === 'CE' && <CeBar />}
{battle_type.toUpperCase() === 'PEAK' && <PeakBar />} {battle_type.toUpperCase() === 'PEAK' && <PeakBar />}
{battle_type.toUpperCase() === 'SU' && ( {battle_type.toUpperCase() === 'SU' && (
<div className="container mx-auto px-4 py-8 text-center font-bold text-3xl"> <div className="container mx-auto px-4 py-8 text-center font-bold text-3xl">
{transI18n("comingSoon")} {transI18n("comingSoon")}
</div> </div>
)} )}
</div> </div>
) )
} }
+341 -341
View File
@@ -1,342 +1,342 @@
"use client" "use client"
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import SelectCustomText from "../select/customSelectText"; import SelectCustomText from "../select/customSelectText";
import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper"; import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import Image from "next/image"; import Image from "next/image";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { MoCEvent, MonsterStore } from "@/types"; import { MoCEvent, MonsterStore } from "@/types";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
export default function MocBar() { export default function MocBar() {
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
moc_config, moc_config,
setMocConfig setMocConfig
} = useUserDataStore() } = useUserDataStore()
const { mapMonster, mapMoc, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore() const { mapMonster, mapMoc, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const challengeSelected = useMemo(() => { const challengeSelected = useMemo(() => {
return mapMoc[moc_config.event_id.toString()]?.Level.find((moc) => moc.ID === moc_config.challenge_id) return mapMoc[moc_config.event_id.toString()]?.Level.find((moc) => moc.ID === moc_config.challenge_id)
}, [moc_config, mapMoc]) }, [moc_config, mapMoc])
const eventSelected = useMemo(() => { const eventSelected = useMemo(() => {
return mapMoc[moc_config.event_id.toString()] return mapMoc[moc_config.event_id.toString()]
}, [moc_config, mapMoc]) }, [moc_config, mapMoc])
const floorSideList = useMemo(() => { const floorSideList = useMemo(() => {
if (!eventSelected) return []; if (!eventSelected) return [];
const floorList = [ const floorList = [
{ {
id: "firstNode", id: "firstNode",
name: transI18n("firstNode"), name: transI18n("firstNode"),
wave: transI18n("firstNodeEnemies") wave: transI18n("firstNodeEnemies")
}, },
{ {
id: "secondNode", id: "secondNode",
name: transI18n("secondNode"), name: transI18n("secondNode"),
wave: transI18n("secondNodeEnemies") wave: transI18n("secondNodeEnemies")
}, },
] ]
if (eventSelected?.Tierce && eventSelected.Tierce.PreChallenge === moc_config.challenge_id) { if (eventSelected?.Tierce && eventSelected.Tierce.PreChallenge === moc_config.challenge_id) {
floorList.push({ floorList.push({
id: "thirdNode", id: "thirdNode",
name: transI18n("thirdNode"), name: transI18n("thirdNode"),
wave: transI18n("thirdNodeEnemies") wave: transI18n("thirdNodeEnemies")
}) })
} }
return floorList return floorList
}, [moc_config.challenge_id, eventSelected, transI18n]) }, [moc_config.challenge_id, eventSelected, transI18n])
useEffect(() => { useEffect(() => {
if (!challengeSelected || moc_config.event_id === 0 || moc_config.challenge_id === 0) return if (!challengeSelected || moc_config.event_id === 0 || moc_config.challenge_id === 0) return
const newBattleConfig = structuredClone(moc_config) const newBattleConfig = structuredClone(moc_config)
newBattleConfig.cycle_count = 0 newBattleConfig.cycle_count = 0
if (moc_config.use_cycle_count) { if (moc_config.use_cycle_count) {
newBattleConfig.cycle_count = challengeSelected.TurnLimit newBattleConfig.cycle_count = challengeSelected.TurnLimit
} }
newBattleConfig.blessings = [] newBattleConfig.blessings = []
if (moc_config.use_turbulence_buff && challengeSelected) { if (moc_config.use_turbulence_buff && challengeSelected) {
challengeSelected.MazeBuff.map((item) => { challengeSelected.MazeBuff.map((item) => {
newBattleConfig.blessings.push({ newBattleConfig.blessings.push({
id: item.ID, id: item.ID,
level: 1 level: 1
}) })
}) })
} }
newBattleConfig.monsters = [] newBattleConfig.monsters = []
newBattleConfig.stage_id = 0 newBattleConfig.stage_id = 0
let targetEventList: MoCEvent[] = [] let targetEventList: MoCEvent[] = []
if (moc_config.floor_side === "firstNode" && challengeSelected.EventList1.length > 0) { if (moc_config.floor_side === "firstNode" && challengeSelected.EventList1.length > 0) {
targetEventList = challengeSelected.EventList1 targetEventList = challengeSelected.EventList1
} else if (moc_config.floor_side === "secondNode" && challengeSelected.EventList2.length > 0) { } else if (moc_config.floor_side === "secondNode" && challengeSelected.EventList2.length > 0) {
targetEventList = challengeSelected.EventList2 targetEventList = challengeSelected.EventList2
} else if (moc_config.floor_side === "thirdNode" && eventSelected?.Tierce && eventSelected.Tierce.EventList.length > 0) { } else if (moc_config.floor_side === "thirdNode" && eventSelected?.Tierce && eventSelected.Tierce.EventList.length > 0) {
targetEventList = eventSelected.Tierce.EventList targetEventList = eventSelected.Tierce.EventList
} }
if (targetEventList.length > 0) { if (targetEventList.length > 0) {
newBattleConfig.stage_id = targetEventList[0].ID newBattleConfig.stage_id = targetEventList[0].ID
for (const wave of targetEventList[0].MonsterList) { for (const wave of targetEventList[0].MonsterList) {
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: value, monster_id: value,
level: targetEventList[0].Level, level: targetEventList[0].Level,
amount: 1, amount: 1,
}) })
} }
newBattleConfig.monsters.push(newWave) newBattleConfig.monsters.push(newWave)
} }
} }
setMocConfig(newBattleConfig) setMocConfig(newBattleConfig)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
challengeSelected, challengeSelected,
eventSelected, eventSelected,
moc_config.event_id, moc_config.event_id,
moc_config.challenge_id, moc_config.challenge_id,
moc_config.floor_side, moc_config.floor_side,
moc_config.use_cycle_count, moc_config.use_cycle_count,
moc_config.use_turbulence_buff, moc_config.use_turbulence_buff,
mapMoc, mapMoc,
]) ])
if (!mapMoc) return null if (!mapMoc) return null
return ( return (
<div className="py-8 relative"> <div className="py-8 relative">
{/* Title Card */} {/* Title Card */}
<div className="rounded-xl p-4 mb-2 border border-warning"> <div className="rounded-xl p-4 mb-2 border border-warning">
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={Object.values(mapMoc).sort((a, b) => b.ID - a.ID).map((moc) => ({ customSet={Object.values(mapMoc).sort((a, b) => b.ID - a.ID).map((moc) => ({
id: moc.ID.toString(), id: moc.ID.toString(),
name: getLocaleName(locale, moc.Name), name: getLocaleName(locale, moc.Name),
time: `${moc.BeginTime} - ${moc.EndTime}`, time: `${moc.BeginTime} - ${moc.EndTime}`,
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={moc_config.event_id.toString()} selectedCustomSet={moc_config.event_id.toString()}
placeholder={transI18n("selectMOCEvent")} placeholder={transI18n("selectMOCEvent")}
setSelectedCustomSet={(id) => setMocConfig({ setSelectedCustomSet={(id) => setMocConfig({
...moc_config, ...moc_config,
event_id: Number(id), event_id: Number(id),
challenge_id: mapMoc[Number(id)]?.Level.at(-1)?.ID || 0, challenge_id: mapMoc[Number(id)]?.Level.at(-1)?.ID || 0,
})} })}
/> />
</div> </div>
{/* Settings */} {/* Settings */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-2"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<label className="label"> <label className="label">
<span className="label-text font-bold text-success">{transI18n("floor")}:{" "}</span> <span className="label-text font-bold text-success">{transI18n("floor")}:{" "}</span>
</label> </label>
<select <select
value={moc_config.challenge_id} value={moc_config.challenge_id}
className="select select-success" className="select select-success"
onChange={(e) => setMocConfig({ onChange={(e) => setMocConfig({
...moc_config, ...moc_config,
challenge_id: Number(e.target.value) challenge_id: Number(e.target.value)
})} })}
> >
<option value={0} disabled={true}>Select a Floor</option> <option value={0} disabled={true}>Select a Floor</option>
{eventSelected?.Level?.map((moc) => ( {eventSelected?.Level?.map((moc) => (
<option key={moc.ID} value={moc.ID}>{getLocaleName(locale, moc.Name)}</option> <option key={moc.ID} value={moc.ID}>{getLocaleName(locale, moc.Name)}</option>
))} ))}
</select> </select>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<label className="label"> <label className="label">
<span className="label-text font-bold text-success">{transI18n("side")}:{" "}</span> <span className="label-text font-bold text-success">{transI18n("side")}:{" "}</span>
</label> </label>
<select <select
value={moc_config.floor_side} value={moc_config.floor_side}
className="select select-success" className="select select-success"
onChange={(e) => setMocConfig({ ...moc_config, floor_side: e.target.value })} onChange={(e) => setMocConfig({ ...moc_config, floor_side: e.target.value })}
> >
<option value={0} disabled={true}>{transI18n("selectSide")}</option> <option value={0} disabled={true}>{transI18n("selectSide")}</option>
{floorSideList.map((side) => ( {floorSideList.map((side) => (
<option key={side.id} value={side.id}>{side.name}</option> <option key={side.id} value={side.id}>{side.name}</option>
))} ))}
</select> </select>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<label className="label cursor-pointer"> <label className="label cursor-pointer">
<span <span
onClick={() => setMocConfig({ ...moc_config, use_cycle_count: !moc_config.use_cycle_count })} onClick={() => setMocConfig({ ...moc_config, use_cycle_count: !moc_config.use_cycle_count })}
className="label-text font-bold text-success cursor-pointer" className="label-text font-bold text-success cursor-pointer"
> >
{transI18n("useCycleCount")} {" "} {transI18n("useCycleCount")} {" "}
</span> </span>
<input <input
type="checkbox" type="checkbox"
checked={moc_config.use_cycle_count} checked={moc_config.use_cycle_count}
onChange={(e) => setMocConfig({ ...moc_config, use_cycle_count: e.target.checked })} onChange={(e) => setMocConfig({ ...moc_config, use_cycle_count: e.target.checked })}
className="checkbox checkbox-primary" className="checkbox checkbox-primary"
/> />
</label> </label>
</div> </div>
</div> </div>
<div className="label-text font-bold text-success mb-2">StageId: {moc_config?.stage_id}</div> <div className="label-text font-bold text-success mb-2">StageId: {moc_config?.stage_id}</div>
{/* Turbulence Buff */} {/* Turbulence Buff */}
<div className="bg-base-200/20 rounded-lg p-4 border border-purple-500/20"> <div className="bg-base-200/20 rounded-lg p-4 border border-purple-500/20">
<div className="flex items-center space-x-2 mb-2"> <div className="flex items-center space-x-2 mb-2">
<input <input
type="checkbox" type="checkbox"
checked={moc_config.use_turbulence_buff} checked={moc_config.use_turbulence_buff}
onChange={(e) => setMocConfig({ ...moc_config, use_turbulence_buff: e.target.checked })} onChange={(e) => setMocConfig({ ...moc_config, use_turbulence_buff: e.target.checked })}
className="checkbox checkbox-primary" className="checkbox checkbox-primary"
/> />
<span <span
onClick={() => setMocConfig({ ...moc_config, use_turbulence_buff: !moc_config.use_turbulence_buff })} onClick={() => setMocConfig({ ...moc_config, use_turbulence_buff: !moc_config.use_turbulence_buff })}
className="font-bold text-success cursor-pointer"> className="font-bold text-success cursor-pointer">
{transI18n("useTurbulenceBuff")} {transI18n("useTurbulenceBuff")}
</span> </span>
</div> </div>
{challengeSelected ? ( {challengeSelected ? (
challengeSelected.MazeBuff.map((buff, i) => ( challengeSelected.MazeBuff.map((buff, i) => (
<div <div
key={i} key={i}
className="text-base" className="text-base"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
getLocaleName(locale, buff?.Desc) || "", getLocaleName(locale, buff?.Desc) || "",
buff?.Param || [] buff?.Param || []
) )
}} }}
/> />
)) ))
) : ( ) : (
<div className="text-base">{transI18n("noTurbulenceBuff")}</div> <div className="text-base">{transI18n("noTurbulenceBuff")}</div>
)} )}
</div> </div>
</div> </div>
{/* Enemy Waves */} {/* Enemy Waves */}
{(moc_config?.challenge_id ?? 0) !== 0 && ( {(moc_config?.challenge_id ?? 0) !== 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{floorSideList.map((side, i) => { {floorSideList.map((side, i) => {
const eventList = side.id === "firstNode" const eventList = side.id === "firstNode"
? challengeSelected?.EventList1 ? challengeSelected?.EventList1
: side.id === "secondNode" : side.id === "secondNode"
? challengeSelected?.EventList2 ? challengeSelected?.EventList2
: side.id === "thirdNode" : side.id === "thirdNode"
? eventSelected?.Tierce?.EventList ? eventSelected?.Tierce?.EventList
: []; : [];
if (!eventList || eventList.length === 0) return null; if (!eventList || eventList.length === 0) return null;
const targetEvent = eventList[0]; const targetEvent = eventList[0];
return ( return (
<div key={i} className="rounded-xl p-4 mt-2 border border-warning"> <div key={i} className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{side.wave}</h2> <h2 className="text-2xl font-bold mb-6 text-info">{side.wave}</h2>
{targetEvent?.MonsterList?.map((wave, waveIndex) => ( {targetEvent?.MonsterList?.map((wave, waveIndex) => (
<div key={waveIndex} className="mb-6"> <div key={waveIndex} className="mb-6">
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="flex flex-wrap gap-2 mt-2"> <div className="flex flex-wrap gap-2 mt-2">
{Object.values(wave).map((waveValue, enemyIndex) => { {Object.values(wave).map((waveValue, enemyIndex) => {
const monsterStats = calcMonsterStats( const monsterStats = calcMonsterStats(
mapMonster?.[waveValue.toString()], mapMonster?.[waveValue.toString()],
targetEvent?.EliteGroup, targetEvent?.EliteGroup,
targetEvent?.HardLevelGroup, targetEvent?.HardLevelGroup,
targetEvent?.Level, targetEvent?.Level,
hardLevelConfig, hardLevelConfig,
eliteConfig eliteConfig
); );
return ( return (
<div <div
key={enemyIndex} key={enemyIndex}
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md" className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
> >
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm"> <div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
Lv. {targetEvent.Level} Lv. {targetEvent.Level}
</div> </div>
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
{mapMonster?.[waveValue.toString()]?.Image?.IconPath && ( {mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
<div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100"> <div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`} src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
alt="Enemy Icon" alt="Enemy Icon"
width={150} width={150}
height={150} height={150}
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
</div> </div>
)} )}
</div> </div>
<div className="flex flex-col px-1 pb-2 pt-2"> <div className="flex flex-col px-1 pb-2 pt-2">
<div className="flex flex-col space-y-1.5"> <div className="flex flex-col space-y-1.5">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-error">HP</span> <span className="text-xs font-semibold text-error">HP</span>
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div> </div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-info">Speed</span> <span className="text-xs font-semibold text-info">Speed</span>
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div> </div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-base-content/70">Toughness</span> <span className="text-xs font-semibold text-base-content/70">Toughness</span>
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div> </div>
</div> </div>
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center"> <div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5"> <span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
Weakness Weakness
</span> </span>
<div className="flex items-center justify-center gap-1.5 flex-wrap"> <div className="flex items-center justify-center gap-1.5 flex-wrap">
{mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => ( {mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image <Image
key={iconIndex} key={iconIndex}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`} src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
alt={icon} alt={icon}
width={40} width={40}
height={40} height={40}
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform" className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
/> />
))} ))}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
) )
})} })}
</div> </div>
</div> </div>
))} ))}
</div> </div>
) )
})} })}
</div> </div>
)} )}
</div> </div>
) )
} }
+307 -307
View File
@@ -1,308 +1,308 @@
"use client" "use client"
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import SelectCustomText from "../select/customSelectText"; import SelectCustomText from "../select/customSelectText";
import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper"; import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore";; import useUserDataStore from "@/stores/userDataStore";;
import Image from "next/image"; import Image from "next/image";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { MonsterStore } from "@/types"; import { MonsterStore } from "@/types";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
export default function PeakBar() { export default function PeakBar() {
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
peak_config, peak_config,
setPeakConfig setPeakConfig
} = useUserDataStore() } = useUserDataStore()
const { mapMonster, mapPeak, damageType, eliteConfig, hardLevelConfig } = useDetailDataStore() const { mapMonster, mapPeak, damageType, eliteConfig, hardLevelConfig } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const listFloor = useMemo(() => { const listFloor = useMemo(() => {
const peak = mapPeak?.[peak_config?.event_id?.toString()] const peak = mapPeak?.[peak_config?.event_id?.toString()]
if (!peak) return [] if (!peak) return []
return [...peak.PreLevel, peak.BossLevel].filter(it => it != null) return [...peak.PreLevel, peak.BossLevel].filter(it => it != null)
}, [peak_config, mapPeak]) }, [peak_config, mapPeak])
const eventSelected = useMemo(() => { const eventSelected = useMemo(() => {
return mapPeak?.[peak_config?.event_id?.toString()] return mapPeak?.[peak_config?.event_id?.toString()]
}, [peak_config, mapPeak]) }, [peak_config, mapPeak])
const bossConfig = useMemo(() => { const bossConfig = useMemo(() => {
return mapPeak?.[peak_config?.event_id?.toString()]?.BossConfig; return mapPeak?.[peak_config?.event_id?.toString()]?.BossConfig;
}, [peak_config, mapPeak]) }, [peak_config, mapPeak])
const challengeSelected = useMemo(() => { const challengeSelected = useMemo(() => {
const challenge = structuredClone(listFloor?.find((peak) => peak?.ID === peak_config.challenge_id)) const challenge = structuredClone(listFloor?.find((peak) => peak?.ID === peak_config.challenge_id))
if ( if (
challenge challenge
&& challenge.ID === mapPeak?.[peak_config?.event_id?.toString()]?.BossLevel?.ID && challenge.ID === mapPeak?.[peak_config?.event_id?.toString()]?.BossLevel?.ID
&& bossConfig && bossConfig
&& peak_config?.boss_mode === "Hard" && peak_config?.boss_mode === "Hard"
) { ) {
return { challenge: bossConfig, isBoss: true } return { challenge: bossConfig, isBoss: true }
} }
return { return {
challenge: challenge, challenge: challenge,
isBoss: challenge?.ID === mapPeak?.[peak_config?.event_id?.toString()]?.BossLevel?.ID, isBoss: challenge?.ID === mapPeak?.[peak_config?.event_id?.toString()]?.BossLevel?.ID,
} }
}, [peak_config, listFloor, mapPeak, bossConfig]) }, [peak_config, listFloor, mapPeak, bossConfig])
useEffect(() => { useEffect(() => {
if (!challengeSelected.challenge) return if (!challengeSelected.challenge) return
if (peak_config.event_id !== 0 && peak_config.challenge_id !== 0 && challengeSelected) { if (peak_config.event_id !== 0 && peak_config.challenge_id !== 0 && challengeSelected) {
const newBattleConfig = structuredClone(peak_config) const newBattleConfig = structuredClone(peak_config)
newBattleConfig.cycle_count = 6 newBattleConfig.cycle_count = 6
newBattleConfig.blessings = [] newBattleConfig.blessings = []
for (const value of challengeSelected?.challenge?.MazeBuff) { for (const value of challengeSelected?.challenge?.MazeBuff) {
newBattleConfig.blessings.push({ newBattleConfig.blessings.push({
id: value.ID, id: value.ID,
level: 1 level: 1
}) })
} }
if (peak_config.buff_id !== 0 && challengeSelected.isBoss) { if (peak_config.buff_id !== 0 && challengeSelected.isBoss) {
newBattleConfig.blessings.push({ newBattleConfig.blessings.push({
id: peak_config.buff_id, id: peak_config.buff_id,
level: 1 level: 1
}) })
} }
newBattleConfig.monsters = [] newBattleConfig.monsters = []
newBattleConfig.stage_id = challengeSelected.challenge.EventList[0].ID newBattleConfig.stage_id = challengeSelected.challenge.EventList[0].ID
for (const wave of challengeSelected.challenge.EventList[0].MonsterList) { for (const wave of challengeSelected.challenge.EventList[0].MonsterList) {
if (!wave) continue if (!wave) continue
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
if (!value) continue if (!value) continue
newWave.push({ newWave.push({
monster_id: value, monster_id: value,
level: challengeSelected.challenge.EventList[0].Level, level: challengeSelected.challenge.EventList[0].Level,
amount: 1, amount: 1,
}) })
} }
newBattleConfig.monsters.push(newWave) newBattleConfig.monsters.push(newWave)
} }
setPeakConfig(newBattleConfig) setPeakConfig(newBattleConfig)
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
peak_config.event_id, peak_config.event_id,
peak_config.challenge_id, peak_config.challenge_id,
peak_config.buff_id, peak_config.buff_id,
peak_config.boss_mode, peak_config.boss_mode,
mapPeak, mapPeak,
]) ])
if (!mapPeak) return null if (!mapPeak) return null
return ( return (
<div className="py-8 relative"> <div className="py-8 relative">
{/* Title Card */} {/* Title Card */}
<div className="rounded-xl p-4 mb-2 border border-warning"> <div className="rounded-xl p-4 mb-2 border border-warning">
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={Object.values(mapPeak).sort((a, b) => b.ID - a.ID).map((peak) => ({ customSet={Object.values(mapPeak).sort((a, b) => b.ID - a.ID).map((peak) => ({
id: peak.ID.toString(), id: peak.ID.toString(),
name: `${getLocaleName(locale, peak.Name)} (${peak.ID})`, name: `${getLocaleName(locale, peak.Name)} (${peak.ID})`,
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={peak_config.event_id.toString()} selectedCustomSet={peak_config.event_id.toString()}
placeholder={transI18n("selectPEAKEvent")} placeholder={transI18n("selectPEAKEvent")}
setSelectedCustomSet={(id) => setPeakConfig({ ...peak_config, event_id: Number(id), challenge_id: 0, buff_id: 0 })} setSelectedCustomSet={(id) => setPeakConfig({ ...peak_config, event_id: Number(id), challenge_id: 0, buff_id: 0 })}
/> />
</div> </div>
{/* Settings */} {/* Settings */}
<div className={ <div className={
`grid grid-cols-1 `grid grid-cols-1
${eventSelected && eventSelected.BossLevel?.ID === peak_config.challenge_id ? "md:grid-cols-2" : ""} ${eventSelected && eventSelected.BossLevel?.ID === peak_config.challenge_id ? "md:grid-cols-2" : ""}
gap-4 mb-2 justify-items-center items-center w-full`} gap-4 mb-2 justify-items-center items-center w-full`}
> >
<div className="flex items-center gap-2 w-full"> <div className="flex items-center gap-2 w-full">
<label className="label"> <label className="label">
<span className="label-text font-bold text-success">{transI18n("floor")}:{" "}</span> <span className="label-text font-bold text-success">{transI18n("floor")}:{" "}</span>
</label> </label>
<select <select
value={peak_config.challenge_id} value={peak_config.challenge_id}
className="select select-success w-full" className="select select-success w-full"
onChange={(e) => setPeakConfig({ ...peak_config, challenge_id: Number(e.target.value) })} onChange={(e) => setPeakConfig({ ...peak_config, challenge_id: Number(e.target.value) })}
> >
<option value={0} disabled={true}>{transI18n("selectFloor")}</option> <option value={0} disabled={true}>{transI18n("selectFloor")}</option>
{listFloor.map((peak) => ( {listFloor.map((peak) => (
<option key={peak.ID} value={peak.ID}>{getLocaleName(locale, peak.Name)}</option> <option key={peak.ID} value={peak.ID}>{getLocaleName(locale, peak.Name)}</option>
))} ))}
</select> </select>
</div> </div>
{eventSelected && eventSelected.BossLevel?.ID === peak_config.challenge_id && ( {eventSelected && eventSelected.BossLevel?.ID === peak_config.challenge_id && (
<div className="flex items-center gap-2 w-full"> <div className="flex items-center gap-2 w-full">
<label className="label"> <label className="label">
<span className="label-text font-bold text-success">{transI18n("mode")}:{" "}</span> <span className="label-text font-bold text-success">{transI18n("mode")}:{" "}</span>
</label> </label>
<select <select
value={peak_config.boss_mode} value={peak_config.boss_mode}
className="select select-success w-full" className="select select-success w-full"
onChange={(e) => setPeakConfig({ ...peak_config, boss_mode: e.target.value })} onChange={(e) => setPeakConfig({ ...peak_config, boss_mode: e.target.value })}
> >
<option value={0} disabled={true}>{transI18n("selectSide")}</option> <option value={0} disabled={true}>{transI18n("selectSide")}</option>
<option value="Normal">{transI18n("normalMode")}</option> <option value="Normal">{transI18n("normalMode")}</option>
<option value="Hard">{transI18n("hardMode")}</option> <option value="Hard">{transI18n("hardMode")}</option>
</select> </select>
</div> </div>
)} )}
</div> </div>
<div className="label-text font-bold text-success mb-2">StageId: {peak_config?.stage_id}</div> <div className="label-text font-bold text-success mb-2">StageId: {peak_config?.stage_id}</div>
{ {
eventSelected eventSelected
&& eventSelected.BossLevel?.ID === peak_config.challenge_id && eventSelected.BossLevel?.ID === peak_config.challenge_id
&& bossConfig && bossConfig
&& ( && (
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={ customSet={
bossConfig.BuffList.map((buff) => ({ bossConfig.BuffList.map((buff) => ({
id: buff.ID.toString(), id: buff.ID.toString(),
name: getLocaleName(locale, buff?.Name || ""), name: getLocaleName(locale, buff?.Name || ""),
description: replaceByParam(getLocaleName(locale, buff?.Desc || ""), buff?.Param || []), description: replaceByParam(getLocaleName(locale, buff?.Desc || ""), buff?.Param || []),
})) }))
} }
excludeSet={[]} excludeSet={[]}
selectedCustomSet={peak_config?.buff_id?.toString()} selectedCustomSet={peak_config?.buff_id?.toString()}
placeholder={transI18n("selectBuff")} placeholder={transI18n("selectBuff")}
setSelectedCustomSet={(id) => setPeakConfig({ ...peak_config, buff_id: Number(id) })} setSelectedCustomSet={(id) => setPeakConfig({ ...peak_config, buff_id: Number(id) })}
/> />
</div> </div>
) )
} }
{/* Turbulence Buff */} {/* Turbulence Buff */}
<div className="bg-base-200/20 rounded-lg p-4 border border-purple-500/20"> <div className="bg-base-200/20 rounded-lg p-4 border border-purple-500/20">
<h2 className="text-2xl font-bold mb-2 text-info"> <h2 className="text-2xl font-bold mb-2 text-info">
{transI18n("turbulenceBuff")} {transI18n("turbulenceBuff")}
</h2> </h2>
{challengeSelected?.challenge && challengeSelected?.challenge?.MazeBuff?.length > 0 ? ( {challengeSelected?.challenge && challengeSelected?.challenge?.MazeBuff?.length > 0 ? (
challengeSelected.challenge.MazeBuff.map((subOption, index) => ( challengeSelected.challenge.MazeBuff.map((subOption, index) => (
<div key={index}> <div key={index}>
<label className="label"> <label className="label">
<span className="label-text font-bold text-success"> <span className="label-text font-bold text-success">
{index + 1}. {getLocaleName(locale, subOption.Name)} {index + 1}. {getLocaleName(locale, subOption.Name)}
</span> </span>
</label> </label>
<div <div
className="text-base" className="text-base"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
getLocaleName(locale, subOption.Desc), getLocaleName(locale, subOption.Desc),
subOption.Param || [] subOption.Param || []
) )
}} }}
/> />
</div> </div>
)) ))
) : ( ) : (
<div className="text-base">{transI18n("noTurbulenceBuff")}</div> <div className="text-base">{transI18n("noTurbulenceBuff")}</div>
)} )}
</div> </div>
</div> </div>
{/* Enemy Waves */} {/* Enemy Waves */}
{(peak_config?.challenge_id ?? 0) !== 0 && ( {(peak_config?.challenge_id ?? 0) !== 0 && (
<div className="grid grid-cols-1 gap-4"> <div className="grid grid-cols-1 gap-4">
<div className="rounded-xl p-4 mt-2 border border-warning"> <div className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-2 text-info">{getLocaleName(locale, challengeSelected?.challenge?.Name)}</h2> <h2 className="text-2xl font-bold mb-2 text-info">{getLocaleName(locale, challengeSelected?.challenge?.Name)}</h2>
{challengeSelected?.challenge && Object.values(challengeSelected?.challenge?.EventList?.[0]?.Infinite || []).map((waveValue, waveIndex) => ( {challengeSelected?.challenge && Object.values(challengeSelected?.challenge?.EventList?.[0]?.Infinite || []).map((waveValue, waveIndex) => (
<div key={waveIndex} className="mb-6"> <div key={waveIndex} className="mb-6">
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="flex flex-wrap gap-2 mt-2"> <div className="flex flex-wrap gap-2 mt-2">
{Array.from(new Set(waveValue.MonsterList)).map((monsterId, enemyIndex) => { {Array.from(new Set(waveValue.MonsterList)).map((monsterId, enemyIndex) => {
const monsterStats = calcMonsterStats( const monsterStats = calcMonsterStats(
mapMonster?.[monsterId.toString()], mapMonster?.[monsterId.toString()],
waveValue.EliteGroup, waveValue.EliteGroup,
challengeSelected?.challenge?.EventList?.[0]?.HardLevelGroup || 0, challengeSelected?.challenge?.EventList?.[0]?.HardLevelGroup || 0,
challengeSelected?.challenge?.EventList?.[0]?.Level || 0, challengeSelected?.challenge?.EventList?.[0]?.Level || 0,
hardLevelConfig, hardLevelConfig,
eliteConfig eliteConfig
); );
return ( return (
<div <div
key={enemyIndex} key={enemyIndex}
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md" className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
> >
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm"> <div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
Lv. {challengeSelected?.challenge?.EventList?.[0]?.Level} Lv. {challengeSelected?.challenge?.EventList?.[0]?.Level}
</div> </div>
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
{mapMonster?.[monsterId.toString()]?.Image?.IconPath && ( {mapMonster?.[monsterId.toString()]?.Image?.IconPath && (
<div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100"> <div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.Image?.IconPath}`} src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.Image?.IconPath}`}
alt="Enemy Icon" alt="Enemy Icon"
width={150} width={150}
height={150} height={150}
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
</div> </div>
)} )}
</div> </div>
<div className="flex flex-col px-1 pb-2 pt-2"> <div className="flex flex-col px-1 pb-2 pt-2">
<div className="flex flex-col space-y-1.5"> <div className="flex flex-col space-y-1.5">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-error">HP</span> <span className="text-xs font-semibold text-error">HP</span>
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div> </div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-info">Speed</span> <span className="text-xs font-semibold text-info">Speed</span>
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div> </div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-base-content/70">Toughness</span> <span className="text-xs font-semibold text-base-content/70">Toughness</span>
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div> </div>
</div> </div>
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center"> <div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5"> <span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
Weakness Weakness
</span> </span>
<div className="flex items-center justify-center gap-1.5 flex-wrap"> <div className="flex items-center justify-center gap-1.5 flex-wrap">
{mapMonster?.[monsterId.toString()]?.StanceWeakList?.map((icon, iconIndex) => ( {mapMonster?.[monsterId.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image <Image
key={iconIndex} key={iconIndex}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`} src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
alt={icon} alt={icon}
width={40} width={40}
height={40} height={40}
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform" className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
/> />
))} ))}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
) )
})} })}
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div> </div>
)} )}
</div> </div>
) )
} }
+340 -340
View File
@@ -1,341 +1,341 @@
"use client" "use client"
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import SelectCustomText from "../select/customSelectText"; import SelectCustomText from "../select/customSelectText";
import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper"; import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import Image from "next/image"; import Image from "next/image";
import { MonsterStore, PFEvent } from "@/types"; import { MonsterStore, PFEvent } from "@/types";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
export default function PfBar() { export default function PfBar() {
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
pf_config, pf_config,
setPfConfig setPfConfig
} = useUserDataStore() } = useUserDataStore()
const { mapMonster, mapPF, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore() const { mapMonster, mapPF, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const challengeSelected = useMemo(() => { const challengeSelected = useMemo(() => {
return mapPF[pf_config.event_id.toString()]?.Level.find((pf) => pf.ID === pf_config.challenge_id) return mapPF[pf_config.event_id.toString()]?.Level.find((pf) => pf.ID === pf_config.challenge_id)
}, [pf_config, mapPF]) }, [pf_config, mapPF])
const eventSelected = useMemo(() => { const eventSelected = useMemo(() => {
return mapPF[pf_config.event_id.toString()] return mapPF[pf_config.event_id.toString()]
}, [pf_config, mapPF]) }, [pf_config, mapPF])
const floorSideList = useMemo(() => { const floorSideList = useMemo(() => {
if (!eventSelected) return []; if (!eventSelected) return [];
const floorList = [ const floorList = [
{ {
id: "firstNode", id: "firstNode",
name: transI18n("firstNode"), name: transI18n("firstNode"),
wave: transI18n("firstNodeEnemies") wave: transI18n("firstNodeEnemies")
}, },
{ {
id: "secondNode", id: "secondNode",
name: transI18n("secondNode"), name: transI18n("secondNode"),
wave: transI18n("secondNodeEnemies") wave: transI18n("secondNodeEnemies")
}, },
] ]
if (eventSelected?.Tierce && eventSelected.Tierce.PreChallenge === pf_config.challenge_id) { if (eventSelected?.Tierce && eventSelected.Tierce.PreChallenge === pf_config.challenge_id) {
floorList.push({ floorList.push({
id: "thirdNode", id: "thirdNode",
name: transI18n("thirdNode"), name: transI18n("thirdNode"),
wave: transI18n("thirdNodeEnemies") wave: transI18n("thirdNodeEnemies")
}) })
} }
return floorList return floorList
}, [pf_config.challenge_id, eventSelected, transI18n]) }, [pf_config.challenge_id, eventSelected, transI18n])
useEffect(() => { useEffect(() => {
if (!challengeSelected || pf_config.event_id === 0 || pf_config.challenge_id === 0) { if (!challengeSelected || pf_config.event_id === 0 || pf_config.challenge_id === 0) {
return return
} }
const newBattleConfig = structuredClone(pf_config) const newBattleConfig = structuredClone(pf_config)
newBattleConfig.cycle_count = challengeSelected.TurnLimit newBattleConfig.cycle_count = challengeSelected.TurnLimit
newBattleConfig.blessings = [] newBattleConfig.blessings = []
if (pf_config.buff_id !== 0) { if (pf_config.buff_id !== 0) {
newBattleConfig.blessings.push({ newBattleConfig.blessings.push({
id: pf_config.buff_id, id: pf_config.buff_id,
level: 1 level: 1
}) })
} }
if (challengeSelected) { if (challengeSelected) {
challengeSelected.MazeBuff.map((item) => { challengeSelected.MazeBuff.map((item) => {
newBattleConfig.blessings.push({ newBattleConfig.blessings.push({
id: item.ID, id: item.ID,
level: 1 level: 1
}) })
}) })
} }
newBattleConfig.monsters = [] newBattleConfig.monsters = []
newBattleConfig.stage_id = 0 newBattleConfig.stage_id = 0
let targetEventList: PFEvent[] = [] let targetEventList: PFEvent[] = []
if (pf_config.floor_side === "firstNode" && challengeSelected.EventList1.length > 0) { if (pf_config.floor_side === "firstNode" && challengeSelected.EventList1.length > 0) {
targetEventList = challengeSelected.EventList1 targetEventList = challengeSelected.EventList1
} else if (pf_config.floor_side === "secondNode" && challengeSelected.EventList2.length > 0) { } else if (pf_config.floor_side === "secondNode" && challengeSelected.EventList2.length > 0) {
targetEventList = challengeSelected.EventList2 targetEventList = challengeSelected.EventList2
} else if (pf_config.floor_side === "thirdNode" && eventSelected?.Tierce && eventSelected.Tierce.EventList.length > 0) { } else if (pf_config.floor_side === "thirdNode" && eventSelected?.Tierce && eventSelected.Tierce.EventList.length > 0) {
targetEventList = eventSelected.Tierce.EventList targetEventList = eventSelected.Tierce.EventList
} }
if (targetEventList.length > 0) { if (targetEventList.length > 0) {
newBattleConfig.stage_id = targetEventList[0].ID newBattleConfig.stage_id = targetEventList[0].ID
for (const wave of targetEventList[0].MonsterList) { for (const wave of targetEventList[0].MonsterList) {
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: value as number, monster_id: value as number,
level: targetEventList[0].Level, level: targetEventList[0].Level,
amount: 1, amount: 1,
}) })
} }
newBattleConfig.monsters.push(newWave) newBattleConfig.monsters.push(newWave)
} }
} }
setPfConfig(newBattleConfig) setPfConfig(newBattleConfig)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
challengeSelected, challengeSelected,
eventSelected, eventSelected,
pf_config.event_id, pf_config.event_id,
pf_config.challenge_id, pf_config.challenge_id,
pf_config.floor_side, pf_config.floor_side,
pf_config.buff_id, pf_config.buff_id,
mapPF, mapPF,
]) ])
if (!mapPF) return null if (!mapPF) return null
return ( return (
<div className="py-8 relative"> <div className="py-8 relative">
{/* Title Card */} {/* Title Card */}
<div className="rounded-xl p-4 mb-2 border border-warning"> <div className="rounded-xl p-4 mb-2 border border-warning">
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={Object.values(mapPF).sort((a, b) => b.ID - a.ID).map((pf) => ({ customSet={Object.values(mapPF).sort((a, b) => b.ID - a.ID).map((pf) => ({
id: pf.ID.toString(), id: pf.ID.toString(),
name: getLocaleName(locale, pf.Name), name: getLocaleName(locale, pf.Name),
time: `${pf.BeginTime} - ${pf.EndTime}`, time: `${pf.BeginTime} - ${pf.EndTime}`,
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={pf_config.event_id.toString()} selectedCustomSet={pf_config.event_id.toString()}
placeholder={transI18n("selectPFEvent")} placeholder={transI18n("selectPFEvent")}
setSelectedCustomSet={(id) => setPfConfig({ setSelectedCustomSet={(id) => setPfConfig({
...pf_config, ...pf_config,
event_id: Number(id), event_id: Number(id),
challenge_id: mapPF[Number(id)]?.Level.slice(-1)[0]?.ID || 0, challenge_id: mapPF[Number(id)]?.Level.slice(-1)[0]?.ID || 0,
buff_id: 0 buff_id: 0
})} })}
/> />
</div> </div>
{/* Settings */} {/* Settings */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-2"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<label className="label"> <label className="label">
<span className="label-text font-bold text-success">{transI18n("floor")}:{" "}</span> <span className="label-text font-bold text-success">{transI18n("floor")}:{" "}</span>
</label> </label>
<select <select
value={pf_config.challenge_id} value={pf_config.challenge_id}
className="select select-success" className="select select-success"
onChange={(e) => setPfConfig({ ...pf_config, challenge_id: Number(e.target.value) })} onChange={(e) => setPfConfig({ ...pf_config, challenge_id: Number(e.target.value) })}
> >
<option value={0} disabled={true}>{transI18n("selectFloor")}</option> <option value={0} disabled={true}>{transI18n("selectFloor")}</option>
{eventSelected?.Level.map((pf) => ( {eventSelected?.Level.map((pf) => (
<option key={pf.ID} value={pf.ID}>{getLocaleName(locale, pf.Name)}</option> <option key={pf.ID} value={pf.ID}>{getLocaleName(locale, pf.Name)}</option>
))} ))}
</select> </select>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<label className="label"> <label className="label">
<span className="label-text font-bold text-success">{transI18n("side")}:{" "}</span> <span className="label-text font-bold text-success">{transI18n("side")}:{" "}</span>
</label> </label>
<select <select
value={pf_config.floor_side} value={pf_config.floor_side}
className="select select-success" className="select select-success"
onChange={(e) => setPfConfig({ ...pf_config, floor_side: e.target.value })} onChange={(e) => setPfConfig({ ...pf_config, floor_side: e.target.value })}
> >
<option value={0} disabled={true}>{transI18n("selectSide")}</option> <option value={0} disabled={true}>{transI18n("selectSide")}</option>
{floorSideList.map((side) => ( {floorSideList.map((side) => (
<option key={side.id} value={side.id}>{side.name}</option> <option key={side.id} value={side.id}>{side.name}</option>
))} ))}
</select> </select>
</div> </div>
</div> </div>
<div className="label-text font-bold text-success mb-2">StageId: {pf_config?.stage_id}</div> <div className="label-text font-bold text-success mb-2">StageId: {pf_config?.stage_id}</div>
{eventSelected && ( {eventSelected && (
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={eventSelected.Option.map((buff) => ({ customSet={eventSelected.Option.map((buff) => ({
id: buff.ID?.toString() || "", id: buff.ID?.toString() || "",
name: getLocaleName(locale, buff?.Name) || "", name: getLocaleName(locale, buff?.Name) || "",
description: replaceByParam(getLocaleName(locale, buff?.Desc) || "", buff?.Param || []), description: replaceByParam(getLocaleName(locale, buff?.Desc) || "", buff?.Param || []),
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={pf_config?.buff_id?.toString()} selectedCustomSet={pf_config?.buff_id?.toString()}
placeholder={transI18n("selectBuff")} placeholder={transI18n("selectBuff")}
setSelectedCustomSet={(id) => setPfConfig({ ...pf_config, buff_id: Number(id) })} setSelectedCustomSet={(id) => setPfConfig({ ...pf_config, buff_id: Number(id) })}
/> />
</div> </div>
)} )}
{/* Turbulence Buff */} {/* Turbulence Buff */}
<div className="bg-base-200/20 rounded-lg p-4 border border-purple-500/20"> <div className="bg-base-200/20 rounded-lg p-4 border border-purple-500/20">
<h2 className="text-2xl font-bold mb-2 text-info">{transI18n("turbulenceBuff")}</h2> <h2 className="text-2xl font-bold mb-2 text-info">{transI18n("turbulenceBuff")}</h2>
{eventSelected && eventSelected.SubOption.length > 0 ? ( {eventSelected && eventSelected.SubOption.length > 0 ? (
eventSelected.SubOption.map((subOption, index) => ( eventSelected.SubOption.map((subOption, index) => (
<div key={index}> <div key={index}>
<label className="label"> <label className="label">
<span className="label-text font-bold text-success">{index + 1}. {getLocaleName(locale, subOption.Name)}</span> <span className="label-text font-bold text-success">{index + 1}. {getLocaleName(locale, subOption.Name)}</span>
</label> </label>
<div <div
className="text-base" className="text-base"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
getLocaleName(locale, subOption.Desc) || "", getLocaleName(locale, subOption.Desc) || "",
subOption.Param || [] subOption.Param || []
) )
}} }}
/> />
</div> </div>
)) ))
) : eventSelected && challengeSelected && eventSelected.SubOption.length === 0 ? ( ) : eventSelected && challengeSelected && eventSelected.SubOption.length === 0 ? (
challengeSelected?.MazeBuff?.map((buff, i) => ( challengeSelected?.MazeBuff?.map((buff, i) => (
<div <div
key={i} key={i}
className="text-base" className="text-base"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
getLocaleName(locale, buff?.Desc) || "", getLocaleName(locale, buff?.Desc) || "",
buff?.Param || [] buff?.Param || []
) )
}} }}
/> />
)) ))
) : ( ) : (
<div className="text-base">{transI18n("noTurbulenceBuff")}</div> <div className="text-base">{transI18n("noTurbulenceBuff")}</div>
)} )}
</div> </div>
</div> </div>
{/* Enemy Waves */} {/* Enemy Waves */}
{(pf_config?.challenge_id ?? 0) !== 0 && ( {(pf_config?.challenge_id ?? 0) !== 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{floorSideList.map((side, i) => { {floorSideList.map((side, i) => {
const eventList = side.id === "firstNode" const eventList = side.id === "firstNode"
? challengeSelected?.EventList1 ? challengeSelected?.EventList1
: side.id === "secondNode" : side.id === "secondNode"
? challengeSelected?.EventList2 ? challengeSelected?.EventList2
: side.id === "thirdNode" : side.id === "thirdNode"
? eventSelected?.Tierce?.EventList ? eventSelected?.Tierce?.EventList
: []; : [];
if (!eventList || eventList.length === 0) return null; if (!eventList || eventList.length === 0) return null;
const targetEvent = eventList[0]; const targetEvent = eventList[0];
return ( return (
<div key={i} className="rounded-xl p-4 mt-2 border border-warning"> <div key={i} className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-2 text-info">{side.wave}</h2> <h2 className="text-2xl font-bold mb-2 text-info">{side.wave}</h2>
{targetEvent && Object.values(targetEvent.Infinite || []).map((waveValue, waveIndex) => ( {targetEvent && Object.values(targetEvent.Infinite || []).map((waveValue, waveIndex) => (
<div key={waveIndex} className="mb-6"> <div key={waveIndex} className="mb-6">
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="flex flex-wrap gap-2 mt-2"> <div className="flex flex-wrap gap-2 mt-2">
{Array.from(new Set(waveValue.MonsterList)).map((monsterId, enemyIndex) => { {Array.from(new Set(waveValue.MonsterList)).map((monsterId, enemyIndex) => {
const monsterStats = calcMonsterStats( const monsterStats = calcMonsterStats(
mapMonster?.[monsterId.toString()], mapMonster?.[monsterId.toString()],
waveValue.EliteGroup, waveValue.EliteGroup,
targetEvent?.HardLevelGroup, targetEvent?.HardLevelGroup,
targetEvent?.Level, targetEvent?.Level,
hardLevelConfig, hardLevelConfig,
eliteConfig eliteConfig
); );
return ( return (
<div <div
key={enemyIndex} key={enemyIndex}
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md" className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
> >
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm"> <div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
Lv. {targetEvent.Level} Lv. {targetEvent.Level}
</div> </div>
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
{mapMonster?.[monsterId.toString()]?.Image?.IconPath && ( {mapMonster?.[monsterId.toString()]?.Image?.IconPath && (
<div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100"> <div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.Image?.IconPath}`} src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.Image?.IconPath}`}
alt="Enemy Icon" alt="Enemy Icon"
width={150} width={150}
height={150} height={150}
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
</div> </div>
)} )}
</div> </div>
<div className="flex flex-col px-1 pb-2 pt-2"> <div className="flex flex-col px-1 pb-2 pt-2">
<div className="flex flex-col space-y-1.5"> <div className="flex flex-col space-y-1.5">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-error">HP</span> <span className="text-xs font-semibold text-error">HP</span>
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div> </div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-info">Speed</span> <span className="text-xs font-semibold text-info">Speed</span>
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div> </div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg"> <div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-base-content/70">Toughness</span> <span className="text-xs font-semibold text-base-content/70">Toughness</span>
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span> <span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div> </div>
</div> </div>
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center"> <div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5"> <span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
Weakness Weakness
</span> </span>
<div className="flex items-center justify-center gap-1.5 flex-wrap"> <div className="flex items-center justify-center gap-1.5 flex-wrap">
{mapMonster?.[monsterId.toString()]?.StanceWeakList?.map((icon, iconIndex) => ( {mapMonster?.[monsterId.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image <Image
key={iconIndex} key={iconIndex}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`} src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
alt={icon} alt={icon}
width={40} width={40}
height={40} height={40}
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform" className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
/> />
))} ))}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
) )
})} })}
</div> </div>
</div> </div>
))} ))}
</div> </div>
) )
})} })}
</div> </div>
)} )}
</div> </div>
) )
} }
+15 -15
View File
@@ -1,16 +1,16 @@
'use client' 'use client'
import { parseRuby } from "@/helper"; import { parseRuby } from "@/helper";
interface TextProps { interface TextProps {
text: string; text: string;
locale: string; locale: string;
className?: string; className?: string;
} }
export default function ParseText({ text, locale, className }: TextProps) { export default function ParseText({ text, locale, className }: TextProps) {
if (locale === "ja") { if (locale === "ja") {
return <div className={className} dangerouslySetInnerHTML={{ __html: parseRuby(text) }} />; return <div className={className} dangerouslySetInnerHTML={{ __html: parseRuby(text) }} />;
} }
return <div className={className}>{text}</div>; return <div className={className}>{text}</div>;
} }
+11 -11
View File
@@ -1,12 +1,12 @@
"use client"; "use client";
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import React from 'react'; import React from 'react';
export default function QueryProviderWrapper({ children }: { children: React.ReactNode }) { export default function QueryProviderWrapper({ children }: { children: React.ReactNode }) {
const [queryClient] = React.useState(() => new QueryClient()); const [queryClient] = React.useState(() => new QueryClient());
return ( return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider> <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
); );
} }
+472 -472
View File
@@ -1,473 +1,473 @@
"use client" "use client"
import NextImage from "next/image" import NextImage from "next/image"
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useMemo } from "react"; import { useMemo } from "react";
import { calcAffixBonus, calcBaseStatRaw, calcBonusStatRaw, calcMainAffixBonus, calcMainAffixBonusRaw, calcPromotion, calcSubAffixBonusRaw, getLocaleName, replaceByParam } from "@/helper"; import { calcAffixBonus, calcBaseStatRaw, calcBonusStatRaw, calcMainAffixBonus, calcMainAffixBonusRaw, calcPromotion, calcSubAffixBonusRaw, getLocaleName, replaceByParam } from "@/helper";
import { mappingStats } from "@/constant/constant"; import { mappingStats } from "@/constant/constant";
import RelicShowcase from "../showcaseCard/relicShowcase"; import RelicShowcase from "../showcaseCard/relicShowcase";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
import useCurrentDataStore from "@/stores/currentDataStore"; import useCurrentDataStore from "@/stores/currentDataStore";
export default function QuickView() { export default function QuickView() {
const { avatars } = useUserDataStore() const { avatars } = useUserDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { avatarSelected, } = useCurrentDataStore() const { avatarSelected, } = useCurrentDataStore()
const { mainAffix, subAffix, mapRelicSet, mapLightCone, mapAvatar } = useDetailDataStore() const { mainAffix, subAffix, mapRelicSet, mapLightCone, mapAvatar } = useDetailDataStore()
const avatarSkillTree = useMemo(() => { const avatarSkillTree = useMemo(() => {
if (!avatarSelected || !avatars[avatarSelected?.ID?.toString()]) return {} if (!avatarSelected || !avatars[avatarSelected?.ID?.toString()]) return {}
if (avatars[avatarSelected?.ID?.toString()].enhanced) { if (avatars[avatarSelected?.ID?.toString()].enhanced) {
return avatarSelected?.Enhanced?.[avatars[avatarSelected?.ID?.toString()].enhanced.toString()].SkillTrees || {} return avatarSelected?.Enhanced?.[avatars[avatarSelected?.ID?.toString()].enhanced.toString()].SkillTrees || {}
} }
return avatarSelected?.SkillTrees || {} return avatarSelected?.SkillTrees || {}
}, [avatarSelected, avatars]) }, [avatarSelected, avatars])
const avatarData = useMemo(() => { const avatarData = useMemo(() => {
if (!avatarSelected) return if (!avatarSelected) return
return avatars[avatarSelected?.ID?.toString()] return avatars[avatarSelected?.ID?.toString()]
}, [avatarSelected, avatars]) }, [avatarSelected, avatars])
const avatarProfile = useMemo(() => { const avatarProfile = useMemo(() => {
if (!avatarSelected || !avatarData) return if (!avatarSelected || !avatarData) return
return avatarData?.profileList?.[avatarData?.profileSelect] return avatarData?.profileList?.[avatarData?.profileSelect]
}, [avatarSelected, avatarData]) }, [avatarSelected, avatarData])
const relicEffects = useMemo(() => { const relicEffects = useMemo(() => {
const avatar = avatars[avatarSelected?.ID?.toString() || ""]; const avatar = avatars[avatarSelected?.ID?.toString() || ""];
const relicCount: { [key: string]: number } = {}; const relicCount: { [key: string]: number } = {};
if (avatar) { if (avatar) {
for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) { for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) {
if (relicCount[relic.relic_set_id]) { if (relicCount[relic.relic_set_id]) {
relicCount[relic.relic_set_id]++; relicCount[relic.relic_set_id]++;
} else { } else {
relicCount[relic.relic_set_id] = 1; relicCount[relic.relic_set_id] = 1;
} }
} }
} }
const listEffects: { key: string, count: number }[] = []; const listEffects: { key: string, count: number }[] = [];
Object.entries(relicCount).forEach(([key, value]) => { Object.entries(relicCount).forEach(([key, value]) => {
if (value >= 2) { if (value >= 2) {
listEffects.push({ key: key, count: value }); listEffects.push({ key: key, count: value });
} }
}); });
return listEffects; return listEffects;
}, [avatars, avatarSelected]); }, [avatars, avatarSelected]);
const relicStats = useMemo(() => { const relicStats = useMemo(() => {
if (!avatarSelected || !avatarProfile?.relics || !mainAffix || !subAffix) return if (!avatarSelected || !avatarProfile?.relics || !mainAffix || !subAffix) return
return Object.entries(avatarProfile?.relics).map(([key, value]) => { return Object.entries(avatarProfile?.relics).map(([key, value]) => {
const mainAffixMap = mainAffix["5" + key] const mainAffixMap = mainAffix["5" + key]
const subAffixMap = subAffix["5"] const subAffixMap = subAffix["5"]
if (!mainAffixMap || !subAffixMap) return if (!mainAffixMap || !subAffixMap) return
return { return {
img: `${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${value.relic_set_id}_${key}.png`, img: `${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${value.relic_set_id}_${key}.png`,
mainAffix: { mainAffix: {
property: mainAffixMap?.[value?.main_affix_id]?.Property, property: mainAffixMap?.[value?.main_affix_id]?.Property,
level: value?.level, level: value?.level,
valueAffix: calcMainAffixBonus(mainAffixMap?.[value?.main_affix_id], value?.level), valueAffix: calcMainAffixBonus(mainAffixMap?.[value?.main_affix_id], value?.level),
detail: mappingStats?.[mainAffixMap?.[value?.main_affix_id]?.Property] detail: mappingStats?.[mainAffixMap?.[value?.main_affix_id]?.Property]
}, },
subAffix: value?.sub_affixes?.map((subValue) => { subAffix: value?.sub_affixes?.map((subValue) => {
return { return {
property: subAffixMap?.[subValue?.sub_affix_id]?.Property, property: subAffixMap?.[subValue?.sub_affix_id]?.Property,
valueAffix: calcAffixBonus(subAffixMap?.[subValue?.sub_affix_id], subValue?.step, subValue?.count), valueAffix: calcAffixBonus(subAffixMap?.[subValue?.sub_affix_id], subValue?.step, subValue?.count),
detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.Property], detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.Property],
step: subValue?.step, step: subValue?.step,
count: subValue?.count count: subValue?.count
} }
}) })
} }
}) })
}, [avatarSelected, avatarProfile, mainAffix, subAffix]) }, [avatarSelected, avatarProfile, mainAffix, subAffix])
const characterStats = useMemo(() => { const characterStats = useMemo(() => {
if (!avatarSelected || !avatarData) return if (!avatarSelected || !avatarData) return
const charPromotion = calcPromotion(avatarData.level) const charPromotion = calcPromotion(avatarData.level)
const statsData: Record<string, { const statsData: Record<string, {
value: number, value: number,
base: number, base: number,
name: string, name: string,
icon: string, icon: string,
unit: string, unit: string,
round: number round: number
}> = { }> = {
HP: { HP: {
value: calcBaseStatRaw( value: calcBaseStatRaw(
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPBase,
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPAdd,
avatarData.level avatarData.level
), ),
base: calcBaseStatRaw( base: calcBaseStatRaw(
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPBase,
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPAdd,
avatarData.level avatarData.level
), ),
name: "HP", name: "HP",
icon: "spriteoutput/ui/avatar/icon/IconMaxHP.png", icon: "spriteoutput/ui/avatar/icon/IconMaxHP.png",
unit: "", unit: "",
round: 0 round: 0
}, },
ATK: { ATK: {
value: calcBaseStatRaw( value: calcBaseStatRaw(
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackBase,
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackAdd,
avatarData.level avatarData.level
), ),
base: calcBaseStatRaw( base: calcBaseStatRaw(
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackBase,
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackAdd,
avatarData.level avatarData.level
), ),
name: "ATK", name: "ATK",
icon: "spriteoutput/ui/avatar/icon/IconAttack.png", icon: "spriteoutput/ui/avatar/icon/IconAttack.png",
unit: "", unit: "",
round: 0 round: 0
}, },
DEF: { DEF: {
value: calcBaseStatRaw( value: calcBaseStatRaw(
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceBase,
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceAdd,
avatarData.level avatarData.level
), ),
base: calcBaseStatRaw( base: calcBaseStatRaw(
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceBase,
mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceAdd,
avatarData.level avatarData.level
), ),
name: "DEF", name: "DEF",
icon: "spriteoutput/ui/avatar/icon/IconDefence.png", icon: "spriteoutput/ui/avatar/icon/IconDefence.png",
unit: "", unit: "",
round: 0 round: 0
}, },
SPD: { SPD: {
value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.SpeedBase || 0, value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.SpeedBase || 0,
base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.SpeedBase || 0, base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.SpeedBase || 0,
name: "SPD", name: "SPD",
icon: "spriteoutput/ui/avatar/icon/IconSpeed.png", icon: "spriteoutput/ui/avatar/icon/IconSpeed.png",
unit: "", unit: "",
round: 1 round: 1
}, },
CRITRate: { CRITRate: {
value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalChance || 0, value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalChance || 0,
base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalChance || 0, base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalChance || 0,
name: "CRIT Rate", name: "CRIT Rate",
icon: "spriteoutput/ui/avatar/icon/IconCriticalChance.png", icon: "spriteoutput/ui/avatar/icon/IconCriticalChance.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
CRITDmg: { CRITDmg: {
value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalDamage || 0, value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalDamage || 0,
base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalDamage || 0, base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalDamage || 0,
name: "CRIT DMG", name: "CRIT DMG",
icon: "spriteoutput/ui/avatar/icon/IconCriticalDamage.png", icon: "spriteoutput/ui/avatar/icon/IconCriticalDamage.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
BreakEffect: { BreakEffect: {
value: 0, value: 0,
base: 0, base: 0,
name: "Break Effect", name: "Break Effect",
icon: "spriteoutput/ui/avatar/icon/IconBreakUp.png", icon: "spriteoutput/ui/avatar/icon/IconBreakUp.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
EffectRES: { EffectRES: {
value: 0, value: 0,
base: 0, base: 0,
name: "Effect RES", name: "Effect RES",
icon: "spriteoutput/ui/avatar/icon/IconStatusResistance.png", icon: "spriteoutput/ui/avatar/icon/IconStatusResistance.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
EnergyRate: { EnergyRate: {
value: 0, value: 0,
base: 0, base: 0,
name: "Energy Rate", name: "Energy Rate",
icon: "spriteoutput/ui/avatar/icon/IconEnergyRecovery.png", icon: "spriteoutput/ui/avatar/icon/IconEnergyRecovery.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
EffectHitRate: { EffectHitRate: {
value: 0, value: 0,
base: 0, base: 0,
name: "Effect Hit Rate", name: "Effect Hit Rate",
icon: "spriteoutput/ui/avatar/icon/IconStatusProbability.png", icon: "spriteoutput/ui/avatar/icon/IconStatusProbability.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
HealBoost: { HealBoost: {
value: 0, value: 0,
base: 0, base: 0,
name: "Healing Boost", name: "Healing Boost",
icon: "spriteoutput/ui/avatar/icon/IconHealRatio.png", icon: "spriteoutput/ui/avatar/icon/IconHealRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
PhysicalAdd: { PhysicalAdd: {
value: 0, value: 0,
base: 0, base: 0,
name: "Physical Boost", name: "Physical Boost",
icon: "spriteoutput/ui/avatar/icon/IconPhysicalAddedRatio.png", icon: "spriteoutput/ui/avatar/icon/IconPhysicalAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
FireAdd: { FireAdd: {
value: 0, value: 0,
base: 0, base: 0,
name: "Fire Boost", name: "Fire Boost",
icon: "spriteoutput/ui/avatar/icon/IconFireAddedRatio.png", icon: "spriteoutput/ui/avatar/icon/IconFireAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
IceAdd: { IceAdd: {
value: 0, value: 0,
base: 0, base: 0,
name: "Ice Boost", name: "Ice Boost",
icon: "spriteoutput/ui/avatar/icon/IconIceAddedRatio.png", icon: "spriteoutput/ui/avatar/icon/IconIceAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
ThunderAdd: { ThunderAdd: {
value: 0, value: 0,
base: 0, base: 0,
name: "Thunder Boost", name: "Thunder Boost",
icon: "spriteoutput/ui/avatar/icon/IconThunderAddedRatio.png", icon: "spriteoutput/ui/avatar/icon/IconThunderAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
WindAdd: { WindAdd: {
value: 0, value: 0,
base: 0, base: 0,
name: "Wind Boost", name: "Wind Boost",
icon: "spriteoutput/ui/avatar/icon/IconWindAddedRatio.png", icon: "spriteoutput/ui/avatar/icon/IconWindAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
QuantumAdd: { QuantumAdd: {
value: 0, value: 0,
base: 0, base: 0,
name: "Quantum Boost", name: "Quantum Boost",
icon: "spriteoutput/ui/avatar/icon/IconQuantumAddedRatio.png", icon: "spriteoutput/ui/avatar/icon/IconQuantumAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
ImaginaryAdd: { ImaginaryAdd: {
value: 0, value: 0,
base: 0, base: 0,
name: "Imaginary Boost", name: "Imaginary Boost",
icon: "spriteoutput/ui/avatar/icon/IconImaginaryAddedRatio.png", icon: "spriteoutput/ui/avatar/icon/IconImaginaryAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
ElationAdd: { ElationAdd: {
value: 0, value: 0,
base: 0, base: 0,
name: "Elation Boost", name: "Elation Boost",
icon: "spriteoutput/ui/avatar/icon/IconJoy.png", icon: "spriteoutput/ui/avatar/icon/IconJoy.png",
unit: "%", unit: "%",
round: 1 round: 1
} }
} }
if (avatarProfile?.lightcone && mapLightCone[avatarProfile?.lightcone?.item_id]) { if (avatarProfile?.lightcone && mapLightCone[avatarProfile?.lightcone?.item_id]) {
const lightconePromotion = calcPromotion(avatarProfile?.lightcone?.level) const lightconePromotion = calcPromotion(avatarProfile?.lightcone?.level)
statsData.HP.value += calcBaseStatRaw( statsData.HP.value += calcBaseStatRaw(
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseHP, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseHP,
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.HP.base += calcBaseStatRaw( statsData.HP.base += calcBaseStatRaw(
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseHP, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseHP,
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseHPAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseHPAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.ATK.value += calcBaseStatRaw( statsData.ATK.value += calcBaseStatRaw(
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseAttack, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseAttack,
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseAttackAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseAttackAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.ATK.base += calcBaseStatRaw( statsData.ATK.base += calcBaseStatRaw(
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseAttack, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseAttack,
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseAttackAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseAttackAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.DEF.value += calcBaseStatRaw( statsData.DEF.value += calcBaseStatRaw(
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseDefence, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseDefence,
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseDefenceAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseDefenceAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.DEF.base += calcBaseStatRaw( statsData.DEF.base += calcBaseStatRaw(
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseDefence, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseDefence,
mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseDefenceAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats?.[lightconePromotion]?.BaseDefenceAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
const bonusData = mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Skills?.Level?.[avatarProfile?.lightcone.rank]?.Bonus const bonusData = mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Skills?.Level?.[avatarProfile?.lightcone.rank]?.Bonus
if (bonusData && bonusData.length > 0) { if (bonusData && bonusData.length > 0) {
const bonusSpd = bonusData.filter((bonus) => bonus.PropertyType === "BaseSpeed") const bonusSpd = bonusData.filter((bonus) => bonus.PropertyType === "BaseSpeed")
const bonusOther = bonusData.filter((bonus) => bonus.PropertyType !== "BaseSpeed") const bonusOther = bonusData.filter((bonus) => bonus.PropertyType !== "BaseSpeed")
bonusSpd.forEach((bonus) => { bonusSpd.forEach((bonus) => {
statsData.SPD.value += bonus.Value statsData.SPD.value += bonus.Value
statsData.SPD.base += bonus.Value statsData.SPD.base += bonus.Value
}) })
bonusOther.forEach((bonus) => { bonusOther.forEach((bonus) => {
const statsBase = mappingStats?.[bonus.PropertyType]?.baseStat const statsBase = mappingStats?.[bonus.PropertyType]?.baseStat
if (statsBase && statsData[statsBase]) { if (statsBase && statsData[statsBase]) {
statsData[statsBase].value += calcBonusStatRaw(bonus.PropertyType, statsData[statsBase].base, bonus.Value) statsData[statsBase].value += calcBonusStatRaw(bonus.PropertyType, statsData[statsBase].base, bonus.Value)
} }
}) })
} }
} }
if (avatarSkillTree) { if (avatarSkillTree) {
Object.values(avatarSkillTree).forEach((value) => { Object.values(avatarSkillTree).forEach((value) => {
if (value?.["1"] if (value?.["1"]
&& value?.["1"]?.PointID && value?.["1"]?.PointID
&& typeof avatarData?.data?.skills?.[value?.["1"]?.PointID] === "number" && typeof avatarData?.data?.skills?.[value?.["1"]?.PointID] === "number"
&& avatarData?.data?.skills?.[value?.["1"]?.PointID] !== 0 && avatarData?.data?.skills?.[value?.["1"]?.PointID] !== 0
&& value?.["1"]?.StatusAddList && value?.["1"]?.StatusAddList
&& value?.["1"].StatusAddList.length > 0) { && value?.["1"].StatusAddList.length > 0) {
value?.["1"]?.StatusAddList.forEach((status) => { value?.["1"]?.StatusAddList.forEach((status) => {
const statsBase = mappingStats?.[status?.PropertyType]?.baseStat const statsBase = mappingStats?.[status?.PropertyType]?.baseStat
if (statsBase && statsData[statsBase]) { if (statsBase && statsData[statsBase]) {
statsData[statsBase].value += calcBonusStatRaw(status?.PropertyType, statsData[statsBase].base, status.Value) statsData[statsBase].value += calcBonusStatRaw(status?.PropertyType, statsData[statsBase].base, status.Value)
} }
}) })
} }
}) })
} }
if (avatarProfile?.relics && mainAffix && subAffix) { if (avatarProfile?.relics && mainAffix && subAffix) {
Object.entries(avatarProfile?.relics).forEach(([key, value]) => { Object.entries(avatarProfile?.relics).forEach(([key, value]) => {
const mainAffixMap = mainAffix["5" + key] const mainAffixMap = mainAffix["5" + key]
const subAffixMap = subAffix["5"] const subAffixMap = subAffix["5"]
if (!mainAffixMap || !subAffixMap) return if (!mainAffixMap || !subAffixMap) return
const mainStats = mappingStats?.[mainAffixMap?.[value.main_affix_id]?.Property]?.baseStat const mainStats = mappingStats?.[mainAffixMap?.[value.main_affix_id]?.Property]?.baseStat
if (mainStats && statsData[mainStats]) { if (mainStats && statsData[mainStats]) {
statsData[mainStats].value += calcMainAffixBonusRaw(mainAffixMap?.[value.main_affix_id], value.level, statsData[mainStats].base) statsData[mainStats].value += calcMainAffixBonusRaw(mainAffixMap?.[value.main_affix_id], value.level, statsData[mainStats].base)
} }
value?.sub_affixes.forEach((subValue) => { value?.sub_affixes.forEach((subValue) => {
const subStats = mappingStats?.[subAffixMap?.[subValue.sub_affix_id]?.Property]?.baseStat const subStats = mappingStats?.[subAffixMap?.[subValue.sub_affix_id]?.Property]?.baseStat
if (subStats && statsData[subStats]) { if (subStats && statsData[subStats]) {
statsData[subStats].value += calcSubAffixBonusRaw(subAffixMap?.[subValue.sub_affix_id], subValue.step, subValue.count, statsData[subStats].base) statsData[subStats].value += calcSubAffixBonusRaw(subAffixMap?.[subValue.sub_affix_id], subValue.step, subValue.count, statsData[subStats].base)
} }
}) })
}) })
} }
if (relicEffects && relicEffects.length > 0) { if (relicEffects && relicEffects.length > 0) {
relicEffects.forEach((relic) => { relicEffects.forEach((relic) => {
const dataBonus = mapRelicSet?.[relic.key]?.Skills const dataBonus = mapRelicSet?.[relic.key]?.Skills
if (!dataBonus || Object.keys(dataBonus).length === 0) return if (!dataBonus || Object.keys(dataBonus).length === 0) return
Object.entries(dataBonus || {}).forEach(([key, value]) => { Object.entries(dataBonus || {}).forEach(([key, value]) => {
if (relic.count < Number(key)) return if (relic.count < Number(key)) return
value.Bonus.forEach((bonus) => { value.Bonus.forEach((bonus) => {
const statsBase = mappingStats?.[bonus.PropertyType]?.baseStat const statsBase = mappingStats?.[bonus.PropertyType]?.baseStat
if (statsBase && statsData[statsBase]) { if (statsBase && statsData[statsBase]) {
statsData[statsBase].value += calcBonusStatRaw(bonus.PropertyType, statsData[statsBase].base, bonus.Value) statsData[statsBase].value += calcBonusStatRaw(bonus.PropertyType, statsData[statsBase].base, bonus.Value)
} }
}) })
}) })
}) })
} }
return statsData return statsData
}, [ }, [
avatarSelected, avatarSelected,
avatarData, avatarData,
mapAvatar, mapAvatar,
avatarProfile?.lightcone, avatarProfile?.lightcone,
avatarProfile?.relics, avatarProfile?.relics,
mapLightCone, mapLightCone,
mainAffix, mainAffix,
subAffix, subAffix,
relicEffects, relicEffects,
mapRelicSet, mapRelicSet,
avatarSkillTree avatarSkillTree
]) ])
return ( return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="col-span-1 md:col-span-2 flex flex-col justify-between py-3"> <div className="col-span-1 md:col-span-2 flex flex-col justify-between py-3">
<div className="flex w-full flex-col justify-between gap-y-0.5 text-base"> <div className="flex w-full flex-col justify-between gap-y-0.5 text-base">
{Object.entries(characterStats || {})?.map(([key, stat], index) => { {Object.entries(characterStats || {})?.map(([key, stat], index) => {
if (!stat || (key.includes("Add") && stat.value === 0)) return null if (!stat || (key.includes("Add") && stat.value === 0)) return null
return ( return (
<div key={index} className="flex flex-row items-center justify-between"> <div key={index} className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<NextImage <NextImage
src={`${process.env.CDN_URL}/${stat?.icon}`} src={`${process.env.CDN_URL}/${stat?.icon}`}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
alt="Stat Icon" alt="Stat Icon"
width={40} width={40}
height={40} height={40}
className="h-10 w-10 p-1 mx-1 bg-black/20 rounded-full" className="h-10 w-10 p-1 mx-1 bg-black/20 rounded-full"
/> />
<div className="font-bold">{stat.name}</div> <div className="font-bold">{stat.name}</div>
</div> </div>
<div className="ml-3 mr-3 grow border rounded opacity-50" /> <div className="ml-3 mr-3 grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">{ <div className="flex cursor-default flex-col text-right font-bold">{
stat.value ? stat.unit === "%" ? (stat.value * 100).toFixed(stat.round) : stat.value.toFixed(stat.round) : 0 stat.value ? stat.unit === "%" ? (stat.value * 100).toFixed(stat.round) : stat.value.toFixed(stat.round) : 0
}{stat.unit}</div> }{stat.unit}</div>
</div> </div>
) )
})} })}
<hr /> <hr />
</div> </div>
<div className="flex flex-col items-center gap-1 w-full my-2"> <div className="flex flex-col items-center gap-1 w-full my-2">
{relicEffects.map((setEffect, index) => { {relicEffects.map((setEffect, index) => {
const relicInfo = mapRelicSet[setEffect.key]; const relicInfo = mapRelicSet[setEffect.key];
if (!relicInfo) return null; if (!relicInfo) return null;
return ( return (
<div key={index} className="flex w-full flex-row justify-between text-left"> <div key={index} className="flex w-full flex-row justify-between text-left">
<div <div
className="font-bold truncate max-w-full mr-1" className="font-bold truncate max-w-full mr-1"
style={{ style={{
fontSize: 'clamp(0.5rem, 2vw, 1rem)' fontSize: 'clamp(0.5rem, 2vw, 1rem)'
}} }}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
getLocaleName(locale, relicInfo.Name), getLocaleName(locale, relicInfo.Name),
[] []
) )
}} }}
/> />
<div> <div>
<span className="black-blur bg-black/20 flex w-5 justify-center rounded px-1.5 py-0.5">{setEffect.count}</span> <span className="black-blur bg-black/20 flex w-5 justify-center rounded px-1.5 py-0.5">{setEffect.count}</span>
</div> </div>
</div> </div>
) )
})} })}
</div> </div>
</div> </div>
<div className="grid grid-cols-1 gap-2 justify-between py-3 text-lg"> <div className="grid grid-cols-1 gap-2 justify-between py-3 text-lg">
{relicStats?.map((relic, index) => { {relicStats?.map((relic, index) => {
if (!relic || !avatarSelected) return null if (!relic || !avatarSelected) return null
return ( return (
<RelicShowcase key={index} relic={relic} avatarInfo={avatarSelected} /> <RelicShowcase key={index} relic={relic} avatarInfo={avatarSelected} />
) )
})} })}
{(!relicStats || !relicStats?.length) && ( {(!relicStats || !relicStats?.length) && (
<div className="flex flex-col items-center justify-center"> <div className="flex flex-col items-center justify-center">
<div className="text-center p-6 rounded-lg bg-black/40 backdrop-blur-sm border border-white/10"> <div className="text-center p-6 rounded-lg bg-black/40 backdrop-blur-sm border border-white/10">
<span className="text-lg text-gray-400">{transI18n("noRelicEquipped")}</span> <span className="text-lg text-gray-400">{transI18n("noRelicEquipped")}</span>
</div> </div>
</div> </div>
)} )}
</div> </div>
</div> </div>
) )
} }
+498 -498
View File
@@ -1,498 +1,498 @@
"use client"; "use client";
import useUserDataStore from '@/stores/userDataStore'; import useUserDataStore from '@/stores/userDataStore';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import SelectCustomImage from '../select/customSelectImage'; import SelectCustomImage from '../select/customSelectImage';
import { calcAffixBonus, calcMainAffixBonus, randomPartition, randomStep, replaceByParam, getLocaleName } from '@/helper'; import { calcAffixBonus, calcMainAffixBonus, randomPartition, randomStep, replaceByParam, getLocaleName } from '@/helper';
import { mappingStats } from '@/constant/constant'; import { mappingStats } from '@/constant/constant';
import useModelStore from '@/stores/modelStore'; import useModelStore from '@/stores/modelStore';
import useRelicMakerStore from '@/stores/relicMakerStore'; import useRelicMakerStore from '@/stores/relicMakerStore';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useTranslations } from 'next-intl' import { useTranslations } from 'next-intl'
import { ChevronDown, ChevronUp } from 'lucide-react'; import { ChevronDown, ChevronUp } from 'lucide-react';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import useDetailDataStore from '@/stores/detailDataStore'; import useDetailDataStore from '@/stores/detailDataStore';
import useCurrentDataStore from '@/stores/currentDataStore'; import useCurrentDataStore from '@/stores/currentDataStore';
import { RelicSetDetail, SubAffixData } from '@/types'; import { RelicSetDetail, SubAffixData } from '@/types';
import useLocaleStore from '@/stores/localeStore'; import useLocaleStore from '@/stores/localeStore';
import { mappingRelicSlot } from "@/constant/constant"; import { mappingRelicSlot } from "@/constant/constant";
export default function RelicMaker() { export default function RelicMaker() {
const { avatars, setAvatars } = useUserDataStore() const { avatars, setAvatars } = useUserDataStore()
const { avatarSelected } = useCurrentDataStore() const { avatarSelected } = useCurrentDataStore()
const { setIsOpenRelic } = useModelStore() const { setIsOpenRelic } = useModelStore()
const { mainAffix, subAffix, mapRelicSet } = useDetailDataStore() const { mainAffix, subAffix, mapRelicSet } = useDetailDataStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { const {
selectedRelicSlot, selectedRelicSlot,
selectedRelicSet, selectedRelicSet,
selectedMainStat, selectedMainStat,
listSelectedSubStats, listSelectedSubStats,
selectedRelicLevel, selectedRelicLevel,
preSelectedSubStats, preSelectedSubStats,
setSelectedRelicSet, setSelectedRelicSet,
setSelectedMainStat, setSelectedMainStat,
setSelectedRelicLevel, setSelectedRelicLevel,
setListSelectedSubStats, setListSelectedSubStats,
resetHistory, resetHistory,
popHistory, popHistory,
addHistory, addHistory,
} = useRelicMakerStore() } = useRelicMakerStore()
const [error, setError] = useState<string>(""); const [error, setError] = useState<string>("");
const relicSets = useMemo(() => { const relicSets = useMemo(() => {
const listSet: Record<string, RelicSetDetail> = {}; const listSet: Record<string, RelicSetDetail> = {};
for (const [key, value] of Object.entries(mapRelicSet || {})) { for (const [key, value] of Object.entries(mapRelicSet || {})) {
let isOk = false; let isOk = false;
for (const key2 of Object.keys(value.Parts)) { for (const key2 of Object.keys(value.Parts)) {
if (key2 == mappingRelicSlot?.[selectedRelicSlot]) { if (key2 == mappingRelicSlot?.[selectedRelicSlot]) {
isOk = true; isOk = true;
break; break;
} }
} }
if (isOk) { if (isOk) {
listSet[key] = value; listSet[key] = value;
} }
} }
return listSet; return listSet;
}, [mapRelicSet , selectedRelicSlot]); }, [mapRelicSet , selectedRelicSlot]);
const subAffixOptions = useMemo(() => { const subAffixOptions = useMemo(() => {
const listSet: Record<string, SubAffixData> = {}; const listSet: Record<string, SubAffixData> = {};
const subAffixMap = subAffix["5"]; const subAffixMap = subAffix["5"];
const mainAffixMap = mainAffix["5" + selectedRelicSlot] const mainAffixMap = mainAffix["5" + selectedRelicSlot]
if (Object.keys(subAffixMap || {}).length === 0 || Object.keys(mainAffixMap || {}).length === 0) return listSet; if (Object.keys(subAffixMap || {}).length === 0 || Object.keys(mainAffixMap || {}).length === 0) return listSet;
for (const [key, value] of Object.entries(subAffixMap)) { for (const [key, value] of Object.entries(subAffixMap)) {
if (value.Property !== mainAffixMap[selectedMainStat]?.Property) { if (value.Property !== mainAffixMap[selectedMainStat]?.Property) {
listSet[key] = value; listSet[key] = value;
} }
} }
return listSet; return listSet;
}, [subAffix, mainAffix, selectedRelicSlot, selectedMainStat]); }, [subAffix, mainAffix, selectedRelicSlot, selectedMainStat]);
useEffect(() => { useEffect(() => {
const subAffixMap = subAffix["5"]; const subAffixMap = subAffix["5"];
const mainAffixMap = mainAffix["5" + selectedRelicSlot]; const mainAffixMap = mainAffix["5" + selectedRelicSlot];
if (!subAffixMap || !mainAffixMap) return; if (!subAffixMap || !mainAffixMap) return;
const mainProp = mainAffixMap[selectedMainStat]?.Property; const mainProp = mainAffixMap[selectedMainStat]?.Property;
if (!mainProp) return; if (!mainProp) return;
const newSubAffixes = structuredClone(listSelectedSubStats); const newSubAffixes = structuredClone(listSelectedSubStats);
let updated = false; let updated = false;
for (let i = 0; i < newSubAffixes.length; i++) { for (let i = 0; i < newSubAffixes.length; i++) {
if (newSubAffixes[i].property === mainProp) { if (newSubAffixes[i].property === mainProp) {
newSubAffixes[i].affixId = ""; newSubAffixes[i].affixId = "";
newSubAffixes[i].property = ""; newSubAffixes[i].property = "";
newSubAffixes[i].rollCount = 0; newSubAffixes[i].rollCount = 0;
newSubAffixes[i].stepCount = 0; newSubAffixes[i].stepCount = 0;
updated = true; updated = true;
} }
} }
if (updated) setListSelectedSubStats(newSubAffixes); if (updated) setListSelectedSubStats(newSubAffixes);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedMainStat, subAffix, mainAffix, selectedRelicSlot]); }, [selectedMainStat, subAffix, mainAffix, selectedRelicSlot]);
const exSubAffixOptions = useMemo(() => { const exSubAffixOptions = useMemo(() => {
const listSet: Record<string, SubAffixData> = {}; const listSet: Record<string, SubAffixData> = {};
const subAffixMap = subAffix["5"]; const subAffixMap = subAffix["5"];
const mainAffixMap = mainAffix["5" + selectedRelicSlot]; const mainAffixMap = mainAffix["5" + selectedRelicSlot];
if (!subAffixMap || !mainAffixMap) return listSet; if (!subAffixMap || !mainAffixMap) return listSet;
for (const [key, value] of Object.entries(subAffixMap)) { for (const [key, value] of Object.entries(subAffixMap)) {
const subAffix = listSelectedSubStats.find((item) => item.property === value.Property); const subAffix = listSelectedSubStats.find((item) => item.property === value.Property);
if (subAffix && value.Property !== mainAffixMap[selectedMainStat]?.Property) { if (subAffix && value.Property !== mainAffixMap[selectedMainStat]?.Property) {
listSet[key] = value; listSet[key] = value;
} }
} }
return listSet; return listSet;
}, [subAffix, listSelectedSubStats, mainAffix, selectedRelicSlot, selectedMainStat]); }, [subAffix, listSelectedSubStats, mainAffix, selectedRelicSlot, selectedMainStat]);
const effectBonus = useMemo(() => { const effectBonus = useMemo(() => {
const affixSet = mainAffix?.["5" + selectedRelicSlot]; const affixSet = mainAffix?.["5" + selectedRelicSlot];
if (!affixSet) return 0; if (!affixSet) return 0;
const data = affixSet[selectedMainStat]; const data = affixSet[selectedMainStat];
if (!data) return 0; if (!data) return 0;
return calcMainAffixBonus(data, selectedRelicLevel); return calcMainAffixBonus(data, selectedRelicLevel);
}, [mainAffix, selectedRelicSlot, selectedMainStat, selectedRelicLevel]); }, [mainAffix, selectedRelicSlot, selectedMainStat, selectedRelicLevel]);
const handleSubStatChange = (key: string, index: number, rollCount: number, stepCount: number) => { const handleSubStatChange = (key: string, index: number, rollCount: number, stepCount: number) => {
setError(""); setError("");
const newSubAffixes = structuredClone(listSelectedSubStats); const newSubAffixes = structuredClone(listSelectedSubStats);
if (!subAffixOptions[key]) { if (!subAffixOptions[key]) {
newSubAffixes[index].affixId = ""; newSubAffixes[index].affixId = "";
newSubAffixes[index].property = ""; newSubAffixes[index].property = "";
newSubAffixes[index].rollCount = rollCount; newSubAffixes[index].rollCount = rollCount;
newSubAffixes[index].stepCount = stepCount; newSubAffixes[index].stepCount = stepCount;
setListSelectedSubStats(newSubAffixes); setListSelectedSubStats(newSubAffixes);
addHistory(index, newSubAffixes[index]); addHistory(index, newSubAffixes[index]);
return; return;
} }
newSubAffixes[index].affixId = key; newSubAffixes[index].affixId = key;
newSubAffixes[index].property = subAffixOptions[key].Property; newSubAffixes[index].property = subAffixOptions[key].Property;
newSubAffixes[index].rollCount = rollCount; newSubAffixes[index].rollCount = rollCount;
newSubAffixes[index].stepCount = stepCount; newSubAffixes[index].stepCount = stepCount;
setListSelectedSubStats(newSubAffixes); setListSelectedSubStats(newSubAffixes);
addHistory(index, newSubAffixes[index]); addHistory(index, newSubAffixes[index]);
}; };
const handlerRollback = (index: number) => { const handlerRollback = (index: number) => {
setError(""); setError("");
if (!preSelectedSubStats[index]) return; if (!preSelectedSubStats[index]) return;
const keys = Object.keys(preSelectedSubStats[index]); const keys = Object.keys(preSelectedSubStats[index]);
if (keys.length <= 1) return; if (keys.length <= 1) return;
const newSubAffixes = structuredClone(listSelectedSubStats); const newSubAffixes = structuredClone(listSelectedSubStats);
const listHistory = structuredClone(preSelectedSubStats[index]); const listHistory = structuredClone(preSelectedSubStats[index]);
const secondLastKey = listHistory.length - 2; const secondLastKey = listHistory.length - 2;
const preSubAffixes = { ...listHistory[secondLastKey] }; const preSubAffixes = { ...listHistory[secondLastKey] };
newSubAffixes[index].rollCount = preSubAffixes.rollCount; newSubAffixes[index].rollCount = preSubAffixes.rollCount;
newSubAffixes[index].stepCount = preSubAffixes.stepCount; newSubAffixes[index].stepCount = preSubAffixes.stepCount;
setListSelectedSubStats(newSubAffixes); setListSelectedSubStats(newSubAffixes);
popHistory(index); popHistory(index);
}; };
const resetSubStat = (index: number) => { const resetSubStat = (index: number) => {
const newSubAffixes = structuredClone(listSelectedSubStats); const newSubAffixes = structuredClone(listSelectedSubStats);
resetHistory(index); resetHistory(index);
newSubAffixes[index].affixId = ""; newSubAffixes[index].affixId = "";
newSubAffixes[index].property = ""; newSubAffixes[index].property = "";
newSubAffixes[index].rollCount = 0; newSubAffixes[index].rollCount = 0;
newSubAffixes[index].stepCount = 0; newSubAffixes[index].stepCount = 0;
setListSelectedSubStats(newSubAffixes); setListSelectedSubStats(newSubAffixes);
}; };
const randomizeStats = () => { const randomizeStats = () => {
const newSubAffixes = structuredClone(listSelectedSubStats); const newSubAffixes = structuredClone(listSelectedSubStats);
const exKeys = Object.keys(exSubAffixOptions); const exKeys = Object.keys(exSubAffixOptions);
for (let i = 0; i < newSubAffixes.length; i++) { for (let i = 0; i < newSubAffixes.length; i++) {
const keys = Object.keys(subAffixOptions).filter((key) => !exKeys.includes(key)); const keys = Object.keys(subAffixOptions).filter((key) => !exKeys.includes(key));
const randomKey = keys[Math.floor(Math.random() * keys.length)]; const randomKey = keys[Math.floor(Math.random() * keys.length)];
exKeys.push(randomKey); exKeys.push(randomKey);
const randomValue = subAffixOptions[randomKey]; const randomValue = subAffixOptions[randomKey];
newSubAffixes[i].affixId = randomKey; newSubAffixes[i].affixId = randomKey;
newSubAffixes[i].property = randomValue.Property; newSubAffixes[i].property = randomValue.Property;
newSubAffixes[i].rollCount = 0; newSubAffixes[i].rollCount = 0;
newSubAffixes[i].stepCount = 0; newSubAffixes[i].stepCount = 0;
} }
for (let i = 0; i < newSubAffixes.length; i++) { for (let i = 0; i < newSubAffixes.length; i++) {
addHistory(i, newSubAffixes[i]); addHistory(i, newSubAffixes[i]);
} }
setListSelectedSubStats(newSubAffixes); setListSelectedSubStats(newSubAffixes);
}; };
const randomizeRolls = () => { const randomizeRolls = () => {
const newSubAffixes = structuredClone(listSelectedSubStats); const newSubAffixes = structuredClone(listSelectedSubStats);
const randomRolls = randomPartition(9, listSelectedSubStats.length); const randomRolls = randomPartition(9, listSelectedSubStats.length);
for (let i = 0; i < listSelectedSubStats.length; i++) { for (let i = 0; i < listSelectedSubStats.length; i++) {
newSubAffixes[i].rollCount = randomRolls[i]; newSubAffixes[i].rollCount = randomRolls[i];
newSubAffixes[i].stepCount = randomStep(randomRolls[i]); newSubAffixes[i].stepCount = randomStep(randomRolls[i]);
} }
setListSelectedSubStats(newSubAffixes); setListSelectedSubStats(newSubAffixes);
for (let i = 0; i < newSubAffixes.length; i++) { for (let i = 0; i < newSubAffixes.length; i++) {
addHistory(i, newSubAffixes[i]); addHistory(i, newSubAffixes[i]);
} }
}; };
const handlerSaveRelic = () => { const handlerSaveRelic = () => {
setError(""); setError("");
const avatar = avatars[avatarSelected?.ID?.toString() || ""]; const avatar = avatars[avatarSelected?.ID?.toString() || ""];
if (!selectedRelicSet || !selectedMainStat || !selectedRelicLevel || !selectedRelicSlot) { if (!selectedRelicSet || !selectedMainStat || !selectedRelicLevel || !selectedRelicSlot) {
setError(transI18n("pleaseSelectAllOptions")); setError(transI18n("pleaseSelectAllOptions"));
return; return;
}; };
if (listSelectedSubStats.find((item) => item.affixId === "")) { if (listSelectedSubStats.find((item) => item.affixId === "")) {
setError(transI18n("pleaseSelectAllSubStats")); setError(transI18n("pleaseSelectAllSubStats"));
return; return;
}; };
if (avatar) { if (avatar) {
avatar.profileList[avatar.profileSelect].relics[selectedRelicSlot] = { avatar.profileList[avatar.profileSelect].relics[selectedRelicSlot] = {
level: selectedRelicLevel, level: selectedRelicLevel,
relic_id: Number(`6${selectedRelicSet}${selectedRelicSlot}`), relic_id: Number(`6${selectedRelicSet}${selectedRelicSlot}`),
relic_set_id: Number(selectedRelicSet), relic_set_id: Number(selectedRelicSet),
main_affix_id: Number(selectedMainStat), main_affix_id: Number(selectedMainStat),
sub_affixes: listSelectedSubStats.map((item) => { sub_affixes: listSelectedSubStats.map((item) => {
return { return {
sub_affix_id: Number(item.affixId), sub_affix_id: Number(item.affixId),
count: item.rollCount, count: item.rollCount,
step: item.stepCount step: item.stepCount
} }
}) })
} }
} }
setAvatars({ ...avatars }); setAvatars({ ...avatars });
setIsOpenRelic(false); setIsOpenRelic(false);
toast.success(transI18n("relicSavedSuccessfully")); toast.success(transI18n("relicSavedSuccessfully"));
} }
return ( return (
<div> <div>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4"> <div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400"> <h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400">
{transI18n("relicMaker")} {transI18n("relicMaker")}
</h3> </h3>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-2"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-2">
{/* Left Panel */} {/* Left Panel */}
<div className="space-y-6"> <div className="space-y-6">
{/* Set Configuration */} {/* Set Configuration */}
<div className="bg-base-100 rounded-xl p-6 border border-slate-700"> <div className="bg-base-100 rounded-xl p-6 border border-slate-700">
<h2 className="text-xl font-bold mb-6 text-warning">{transI18n("mainSettings")}</h2> <h2 className="text-xl font-bold mb-6 text-warning">{transI18n("mainSettings")}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
{/* Main Stat */} {/* Main Stat */}
<div> <div>
<label className="block text-lg font-medium mb-2">{transI18n("mainStat")}</label> <label className="block text-lg font-medium mb-2">{transI18n("mainStat")}</label>
<SelectCustomImage <SelectCustomImage
customSet={Object.entries(mainAffix["5" + selectedRelicSlot] || {}).map(([key, value]) => ({ customSet={Object.entries(mainAffix["5" + selectedRelicSlot] || {}).map(([key, value]) => ({
value: key, value: key,
label: mappingStats[value.Property].name + " " + mappingStats[value.Property].unit, label: mappingStats[value.Property].name + " " + mappingStats[value.Property].unit,
imageUrl: `${process.env.CDN_URL}/${mappingStats[value.Property].icon}` imageUrl: `${process.env.CDN_URL}/${mappingStats[value.Property].icon}`
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={selectedMainStat} selectedCustomSet={selectedMainStat}
placeholder={transI18n("selectAMainStat")} placeholder={transI18n("selectAMainStat")}
setSelectedCustomSet={setSelectedMainStat} setSelectedCustomSet={setSelectedMainStat}
/> />
</div> </div>
{/* Relic Set Selection */} {/* Relic Set Selection */}
<div> <div>
<label className="block text-lg font-medium mb-2">{transI18n("set")}</label> <label className="block text-lg font-medium mb-2">{transI18n("set")}</label>
<SelectCustomImage <SelectCustomImage
customSet={Object.entries(relicSets).map(([key, value]) => ({ customSet={Object.entries(relicSets).map(([key, value]) => ({
value: key, value: key,
label: getLocaleName(locale, value.Name), label: getLocaleName(locale, value.Name),
imageUrl: `${process.env.CDN_URL}/${value.Image.SetIconPath}` imageUrl: `${process.env.CDN_URL}/${value.Image.SetIconPath}`
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={selectedRelicSet} selectedCustomSet={selectedRelicSet}
placeholder={transI18n("selectASet")} placeholder={transI18n("selectASet")}
setSelectedCustomSet={setSelectedRelicSet} setSelectedCustomSet={setSelectedRelicSet}
/> />
</div> </div>
</div> </div>
{/* Set Bonus Display */} {/* Set Bonus Display */}
<div className="mb-6 py-4 bg-base-100 rounded-lg"> <div className="mb-6 py-4 bg-base-100 rounded-lg">
{selectedRelicSet !== "" ? Object.entries(mapRelicSet[selectedRelicSet].Skills).map(([key, value]) => ( {selectedRelicSet !== "" ? Object.entries(mapRelicSet[selectedRelicSet].Skills).map(([key, value]) => (
<div key={key} className="text-blue-300 text-sm mb-1"> <div key={key} className="text-blue-300 text-sm mb-1">
<span className="text-info font-bold">{key}-Pc: <span className="text-info font-bold">{key}-Pc:
<div <div
className="text-warning leading-relaxed font-bold" className="text-warning leading-relaxed font-bold"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
getLocaleName(locale, value.Desc), getLocaleName(locale, value.Desc),
value.Param || [] value.Param || []
) )
}} }}
/> </span> /> </span>
</div> </div>
)) : <p className="text-blue-300 text-sm font-bold mb-1">{transI18n("pleaseSelectASet")}</p>} )) : <p className="text-blue-300 text-sm font-bold mb-1">{transI18n("pleaseSelectASet")}</p>}
</div> </div>
{/* Rarity */} {/* Rarity */}
<div className="grid grid-cols-2 items-center gap-4 mb-6"> <div className="grid grid-cols-2 items-center gap-4 mb-6">
<label className="block text-lg font-medium mb-2">{transI18n("rarity")}: {5} </label> <label className="block text-lg font-medium mb-2">{transI18n("rarity")}: {5} </label>
<label className="block text-lg font-medium mb-2">{transI18n("effectBonus")}: <span className="text-warning font-bold">{effectBonus}</span></label> <label className="block text-lg font-medium mb-2">{transI18n("effectBonus")}: <span className="text-warning font-bold">{effectBonus}</span></label>
</div> </div>
{/* Level */} {/* Level */}
<div className="mb-6"> <div className="mb-6">
<label className="block text-lg font-medium mb-2">{transI18n("level")}</label> <label className="block text-lg font-medium mb-2">{transI18n("level")}</label>
<div className="bg-base-200 rounded-lg p-4"> <div className="bg-base-200 rounded-lg p-4">
<input <input
type="range" type="range"
min="0" min="0"
max="15" max="15"
value={selectedRelicLevel} value={selectedRelicLevel}
onChange={(e) => setSelectedRelicLevel(parseInt(e.target.value))} onChange={(e) => setSelectedRelicLevel(parseInt(e.target.value))}
className="range range-primary w-full" className="range range-primary w-full"
/> />
<div className="text-center text-2xl font-bold mt-2">{selectedRelicLevel}</div> <div className="text-center text-2xl font-bold mt-2">{selectedRelicLevel}</div>
</div> </div>
</div> </div>
<AnimatePresence> <AnimatePresence>
{error && ( {error && (
<motion.p <motion.p
key="error" key="error"
initial={{ x: 0 }} initial={{ x: 0 }}
animate={{ x: [0, -2, 2, -2, 2, 0] }} animate={{ x: [0, -2, 2, -2, 2, 0] }}
transition={{ transition={{
duration: 6, duration: 6,
repeat: Infinity, repeat: Infinity,
ease: "easeInOut", ease: "easeInOut",
repeatDelay: 1.5, repeatDelay: 1.5,
}} }}
className="text-error my-2 text-center font-bold" className="text-error my-2 text-center font-bold"
> >
{error}! {error}!
</motion.p> </motion.p>
)} )}
</AnimatePresence> </AnimatePresence>
{/* Save Button */} {/* Save Button */}
<button onClick={handlerSaveRelic} className="btn btn-success w-full"> <button onClick={handlerSaveRelic} className="btn btn-success w-full">
{transI18n("save")} {transI18n("save")}
</button> </button>
</div> </div>
</div> </div>
{/* Right Panel - Sub Stats */} {/* Right Panel - Sub Stats */}
<div className="space-y-4"> <div className="space-y-4">
{/* Total Roll */} {/* Total Roll */}
<div className="bg-base-100 rounded-xl p-4 border border-slate-700 z-1"> <div className="bg-base-100 rounded-xl p-4 border border-slate-700 z-1">
<h3 className="text-lg font-bold mb-4">{transI18n("totalRoll")} {listSelectedSubStats.reduce((a, b) => a + b.rollCount, 0)}</h3> <h3 className="text-lg font-bold mb-4">{transI18n("totalRoll")} {listSelectedSubStats.reduce((a, b) => a + b.rollCount, 0)}</h3>
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
<button <button
className="btn btn-outline btn-success sm:btn-sm" className="btn btn-outline btn-success sm:btn-sm"
onClick={randomizeStats} onClick={randomizeStats}
> >
{transI18n("randomizeStats")} {transI18n("randomizeStats")}
</button> </button>
<button <button
className="btn btn-outline btn-success sm:btn-sm" className="btn btn-outline btn-success sm:btn-sm"
onClick={randomizeRolls} onClick={randomizeRolls}
> >
{transI18n("randomizeRolls")} {transI18n("randomizeRolls")}
</button> </button>
</div> </div>
</div> </div>
{listSelectedSubStats.map((v, index) => ( {listSelectedSubStats.map((v, index) => (
<div key={index} className={`bg-base-100 rounded-xl p-4 border border-slate-700`}> <div key={index} className={`bg-base-100 rounded-xl p-4 border border-slate-700`}>
<div className="grid grid-cols-12 gap-2 items-center"> <div className="grid grid-cols-12 gap-2 items-center">
{/* Stat Selection */} {/* Stat Selection */}
<div className="col-span-8"> <div className="col-span-8">
<SelectCustomImage <SelectCustomImage
customSet={Object.entries(subAffixOptions).map(([key, value]) => ({ customSet={Object.entries(subAffixOptions).map(([key, value]) => ({
value: key, value: key,
label: mappingStats[value.Property].name + " " + mappingStats[value.Property].unit, label: mappingStats[value.Property].name + " " + mappingStats[value.Property].unit,
imageUrl: `${process.env.CDN_URL}/${mappingStats[value.Property].icon}` imageUrl: `${process.env.CDN_URL}/${mappingStats[value.Property].icon}`
}))} }))}
excludeSet={Object.entries(exSubAffixOptions).map(([key, value]) => ({ excludeSet={Object.entries(exSubAffixOptions).map(([key, value]) => ({
value: key, value: key,
label: mappingStats[value.Property].name + " " + mappingStats[value.Property].unit, label: mappingStats[value.Property].name + " " + mappingStats[value.Property].unit,
imageUrl: `${process.env.CDN_URL}/${mappingStats[value.Property].icon}` imageUrl: `${process.env.CDN_URL}/${mappingStats[value.Property].icon}`
}))} }))}
selectedCustomSet={v.affixId} selectedCustomSet={v.affixId}
placeholder={transI18n("selectASubStat")} placeholder={transI18n("selectASubStat")}
setSelectedCustomSet={(key) => handleSubStatChange(key, index, 0, 0)} setSelectedCustomSet={(key) => handleSubStatChange(key, index, 0, 0)}
/> />
</div> </div>
{/* Current Value */} {/* Current Value */}
<div className="col-span-4 text-center flex items-center justify-center gap-2"> <div className="col-span-4 text-center flex items-center justify-center gap-2">
<span className="text-2xl font-mono">+{ }</span> <span className="text-2xl font-mono">+{ }</span>
<div className="text-xl font-bold text-info">{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount, v.rollCount)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}</div> <div className="text-xl font-bold text-info">{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount, v.rollCount)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}</div>
</div> </div>
{/* Up Roll Values */} {/* Up Roll Values */}
<div className="col-span-12"> <div className="col-span-12">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<ChevronUp className="w-4 h-4 text-success" /> <ChevronUp className="w-4 h-4 text-success" />
<span className="text-sm font-semibold text-success">{transI18n("upRoll")}</span> <span className="text-sm font-semibold text-success">{transI18n("upRoll")}</span>
</div> </div>
<div className="grid grid-cols-3 gap-1"> <div className="grid grid-cols-3 gap-1">
<button <button
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 0)} onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 0)}
className="btn btn-sm btn-info border-0" className="btn btn-sm btn-info border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], 0, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], 0, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
</button> </button>
<button <button
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 1)} onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 1)}
className="btn btn-sm btn-info border-0" className="btn btn-sm btn-info border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 1, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 1, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
</button> </button>
<button <button
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 2)} onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 2)}
className="btn btn-sm btn-info border-0" className="btn btn-sm btn-info border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
</button> </button>
</div> </div>
</div> </div>
{/* Down Roll Values */} {/* Down Roll Values */}
<div className="col-span-12"> <div className="col-span-12">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<ChevronDown className="w-4 h-4 text-error" /> <ChevronDown className="w-4 h-4 text-error" />
<span className="text-sm font-semibold text-error">{transI18n("downRoll")}</span> <span className="text-sm font-semibold text-error">{transI18n("downRoll")}</span>
</div> </div>
<div className="grid grid-cols-3 gap-1"> <div className="grid grid-cols-3 gap-1">
<button <button
onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount, 0))} onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount, 0))}
className="btn btn-sm btn-info border-0" className="btn btn-sm btn-info border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], 0, Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], 0, Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
</button> </button>
<button <button
onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 1, 0))} onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 1, 0))}
className="btn btn-sm btn-info border-0" className="btn btn-sm btn-info border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 1, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 1, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
</button> </button>
<button <button
onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 2, 0))} onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 2, 0))}
className="btn btn-sm btn-info border-0" className="btn btn-sm btn-info border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 2, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 2, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
</button> </button>
</div> </div>
</div> </div>
{/* Reset Button & Roll Info */} {/* Reset Button & Roll Info */}
<div className="col-span-12 text-center w-full"> <div className="col-span-12 text-center w-full">
<div className="grid grid-rows-2 gap-1 items-center justify-items-start w-full"> <div className="grid grid-rows-2 gap-1 items-center justify-items-start w-full">
<div className="grid grid-cols-2 gap-2 items-center w-full"> <div className="grid grid-cols-2 gap-2 items-center w-full">
<button <button
className="btn btn-error btn-sm mb-1" className="btn btn-error btn-sm mb-1"
onClick={() => resetSubStat(index)} onClick={() => resetSubStat(index)}
> >
{transI18n("reset")} {transI18n("reset")}
</button> </button>
<button <button
className="btn btn-warning btn-sm mb-1" className="btn btn-warning btn-sm mb-1"
onClick={() => handlerRollback(index)} onClick={() => handlerRollback(index)}
> >
{transI18n("rollBack")} {transI18n("rollBack")}
</button> </button>
</div> </div>
<div className="grid grid-cols-2 gap-2 items-center w-full"> <div className="grid grid-cols-2 gap-2 items-center w-full">
<span className="font-bold">{transI18n("roll")}: <span className="text-info">{v.rollCount}</span></span> <span className="font-bold">{transI18n("roll")}: <span className="text-info">{v.rollCount}</span></span>
<span className="font-bold">{transI18n("step")}: <span className="text-info">{v.stepCount}</span></span> <span className="font-bold">{transI18n("step")}: <span className="text-info">{v.stepCount}</span></span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div> </div>
</div> </div>
); );
}; };
+366 -366
View File
@@ -1,366 +1,366 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
"use client"; "use client";
import { useCallback, useEffect, useMemo } from "react"; import { useCallback, useEffect, useMemo } from "react";
import RelicMaker from "../relicBar"; import RelicMaker from "../relicBar";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import RelicCard from "../card/relicCard"; import RelicCard from "../card/relicCard";
import useModelStore from '@/stores/modelStore'; import useModelStore from '@/stores/modelStore';
import { replaceByParam } from '@/helper'; import { replaceByParam } from '@/helper';
import useRelicMakerStore from '@/stores/relicMakerStore'; import useRelicMakerStore from '@/stores/relicMakerStore';
import QuickView from "../quickView"; import QuickView from "../quickView";
import { ModalConfig } from "@/types"; import { ModalConfig } from "@/types";
import useCurrentDataStore from "@/stores/currentDataStore"; import useCurrentDataStore from "@/stores/currentDataStore";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
import { getLocaleName } from '@/helper'; import { getLocaleName } from '@/helper';
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
export default function RelicsInfo() { export default function RelicsInfo() {
const { avatars, setAvatars } = useUserDataStore() const { avatars, setAvatars } = useUserDataStore()
const { const {
setSelectedRelicSlot, setSelectedRelicSlot,
selectedRelicSlot, selectedRelicSlot,
setSelectedMainStat, setSelectedMainStat,
setSelectedRelicSet, setSelectedRelicSet,
setSelectedRelicLevel, setSelectedRelicLevel,
setListSelectedSubStats, setListSelectedSubStats,
resetHistory, resetHistory,
resetSubStat, resetSubStat,
listSelectedSubStats, listSelectedSubStats,
} = useRelicMakerStore() } = useRelicMakerStore()
const { const {
isOpenRelic, isOpenRelic,
setIsOpenRelic, setIsOpenRelic,
isOpenQuickView, isOpenQuickView,
setIsOpenQuickView setIsOpenQuickView
} = useModelStore() } = useModelStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { avatarSelected } = useCurrentDataStore() const { avatarSelected } = useCurrentDataStore()
const { mapRelicSet, subAffix } = useDetailDataStore() const { mapRelicSet, subAffix } = useDetailDataStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const handleShow = (modalId: string) => { const handleShow = (modalId: string) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null; const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) { if (modal) {
modal.showModal(); modal.showModal();
} }
}; };
// Close modal handler // Close modal handler
const handleCloseModal = (modalId: string) => { const handleCloseModal = (modalId: string) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null; const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) { if (modal) {
modal.close(); modal.close();
} }
}; };
const getRelic = useCallback((slot: string) => { const getRelic = useCallback((slot: string) => {
const avatar = avatars[avatarSelected?.ID.toString() || ""]; const avatar = avatars[avatarSelected?.ID.toString() || ""];
if (avatar) { if (avatar) {
return avatar.profileList[avatar.profileSelect]?.relics[slot] || null; return avatar.profileList[avatar.profileSelect]?.relics[slot] || null;
} }
return null; return null;
}, [avatars, avatarSelected]); }, [avatars, avatarSelected]);
const handlerDeleteRelic = (slot: string) => { const handlerDeleteRelic = (slot: string) => {
const avatar = avatars[avatarSelected?.ID.toString() || ""]; const avatar = avatars[avatarSelected?.ID.toString() || ""];
if (avatar) { if (avatar) {
delete avatar.profileList[avatar.profileSelect].relics[slot] delete avatar.profileList[avatar.profileSelect].relics[slot]
setAvatars({ ...avatars }); setAvatars({ ...avatars });
} }
} }
const handlerChangeRelic = (slot: string) => { const handlerChangeRelic = (slot: string) => {
const relic = getRelic(slot) const relic = getRelic(slot)
setSelectedRelicSlot(slot) setSelectedRelicSlot(slot)
resetSubStat() resetSubStat()
resetHistory(null) resetHistory(null)
if (relic) { if (relic) {
setSelectedMainStat(relic.main_affix_id.toString()) setSelectedMainStat(relic.main_affix_id.toString())
setSelectedRelicSet(relic.relic_set_id.toString()) setSelectedRelicSet(relic.relic_set_id.toString())
setSelectedRelicLevel(relic.level) setSelectedRelicLevel(relic.level)
const newSubAffixes: { affixId: string, property: string, rollCount: number, stepCount: number }[] = [...listSelectedSubStats]; const newSubAffixes: { affixId: string, property: string, rollCount: number, stepCount: number }[] = [...listSelectedSubStats];
relic.sub_affixes.forEach((item, index) => { relic.sub_affixes.forEach((item, index) => {
newSubAffixes[index].affixId = item.sub_affix_id.toString(); newSubAffixes[index].affixId = item.sub_affix_id.toString();
newSubAffixes[index].property = subAffix["5"][item.sub_affix_id.toString()]?.Property || ""; newSubAffixes[index].property = subAffix["5"][item.sub_affix_id.toString()]?.Property || "";
newSubAffixes[index].rollCount = item.count || 0; newSubAffixes[index].rollCount = item.count || 0;
newSubAffixes[index].stepCount = item.step || 0; newSubAffixes[index].stepCount = item.step || 0;
}) })
setListSelectedSubStats(newSubAffixes) setListSelectedSubStats(newSubAffixes)
} else { } else {
setSelectedMainStat("") setSelectedMainStat("")
setSelectedRelicSet("") setSelectedRelicSet("")
setSelectedRelicLevel(15) setSelectedRelicLevel(15)
const newSubAffixes: { affixId: string, property: string, rollCount: number, stepCount: number }[] = [...listSelectedSubStats]; const newSubAffixes: { affixId: string, property: string, rollCount: number, stepCount: number }[] = [...listSelectedSubStats];
newSubAffixes.forEach((item) => { newSubAffixes.forEach((item) => {
item.affixId = "" item.affixId = ""
item.property = "" item.property = ""
item.rollCount = 0 item.rollCount = 0
item.stepCount = 0 item.stepCount = 0
}) })
setListSelectedSubStats(newSubAffixes) setListSelectedSubStats(newSubAffixes)
} }
setIsOpenRelic(true) setIsOpenRelic(true)
handleShow("action_detail_modal") handleShow("action_detail_modal")
} }
const relicEffects = useMemo(() => { const relicEffects = useMemo(() => {
const avatar = avatars[avatarSelected?.ID.toString() || ""]; const avatar = avatars[avatarSelected?.ID.toString() || ""];
const relicCount: { [key: string]: number } = {}; const relicCount: { [key: string]: number } = {};
if (avatar) { if (avatar) {
for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) { for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) {
if (relicCount[relic.relic_set_id]) { if (relicCount[relic.relic_set_id]) {
relicCount[relic.relic_set_id]++; relicCount[relic.relic_set_id]++;
} else { } else {
relicCount[relic.relic_set_id] = 1; relicCount[relic.relic_set_id] = 1;
} }
} }
} }
const listEffects: { key: string, count: number }[] = []; const listEffects: { key: string, count: number }[] = [];
Object.entries(relicCount).forEach(([key, value]) => { Object.entries(relicCount).forEach(([key, value]) => {
if (value >= 2) { if (value >= 2) {
listEffects.push({ key: key, count: value }); listEffects.push({ key: key, count: value });
} }
}); });
return listEffects; return listEffects;
}, [avatars, avatarSelected]); }, [avatars, avatarSelected]);
const modalConfigs: ModalConfig[] = [ const modalConfigs: ModalConfig[] = [
{ {
id: "action_detail_modal", id: "action_detail_modal",
title: "", title: "",
isOpen: isOpenRelic, isOpen: isOpenRelic,
onClose: () => { onClose: () => {
setIsOpenRelic(false) setIsOpenRelic(false)
handleCloseModal("action_detail_modal") handleCloseModal("action_detail_modal")
}, },
content: <RelicMaker /> content: <RelicMaker />
}, },
{ {
id: "quick_view_modal", id: "quick_view_modal",
title: transI18n("quickView").toUpperCase(), title: transI18n("quickView").toUpperCase(),
isOpen: isOpenQuickView, isOpen: isOpenQuickView,
onClose: () => { onClose: () => {
setIsOpenQuickView(false) setIsOpenQuickView(false)
handleCloseModal("quick_view_modal") handleCloseModal("quick_view_modal")
}, },
content: <QuickView /> content: <QuickView />
} }
] ]
// Handle ESC key to close modal // Handle ESC key to close modal
useEffect(() => { useEffect(() => {
for (const item of modalConfigs) { for (const item of modalConfigs) {
if (!item?.isOpen) { if (!item?.isOpen) {
handleCloseModal(item?.id || "") handleCloseModal(item?.id || "")
} }
} }
const handleEscKey = (event: KeyboardEvent) => { const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape') { if (event.key === 'Escape') {
for (const item of modalConfigs) { for (const item of modalConfigs) {
handleCloseModal(item?.id || "") handleCloseModal(item?.id || "")
} }
} }
}; };
window.addEventListener('keydown', handleEscKey); window.addEventListener('keydown', handleEscKey);
return () => window.removeEventListener('keydown', handleEscKey); return () => window.removeEventListener('keydown', handleEscKey);
}, [isOpenRelic]); }, [isOpenRelic]);
return ( return (
<div className="max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden"> <div className="max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Left Section - Items Grid */} {/* Left Section - Items Grid */}
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<div className="bg-base-100 rounded-xl p-6 shadow-lg"> <div className="bg-base-100 rounded-xl p-6 shadow-lg">
<h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content"> <h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content">
<div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div> <div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div>
{transI18n("relics")} {transI18n("relics")}
</h2> </h2>
<div className="grid grid-cols-2 md:grid-cols-3 gap-6"> <div className="grid grid-cols-2 md:grid-cols-3 gap-6">
{["1", "2", "3", "4", "5", "6"].map((item, index) => ( {["1", "2", "3", "4", "5", "6"].map((item, index) => (
<div key={index} className="relative group"> <div key={index} className="relative group">
<div <div
onClick={() => { onClick={() => {
if (item === selectedRelicSlot) { if (item === selectedRelicSlot) {
setSelectedRelicSlot("") setSelectedRelicSlot("")
} else { } else {
setSelectedRelicSlot(item) setSelectedRelicSlot(item)
} }
handlerChangeRelic(item) handlerChangeRelic(item)
}} }}
className="cursor-pointer" className="cursor-pointer"
> >
<RelicCard <RelicCard
slot={item} slot={item}
avatarId={avatarSelected?.ID.toString() || ""} avatarId={avatarSelected?.ID.toString() || ""}
/> />
</div> </div>
<div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex gap-1"> <div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex gap-1">
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
handlerChangeRelic(item) handlerChangeRelic(item)
}} }}
className="btn btn-info p-1.5 rounded-full shadow-lg transition-colors duration-200" className="btn btn-info p-1.5 rounded-full shadow-lg transition-colors duration-200"
title={transI18n("changeRelic")} title={transI18n("changeRelic")}
> >
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
</svg> </svg>
</button> </button>
{getRelic(item) && ( {getRelic(item) && (
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
if (window.confirm(`${transI18n("deleteRelicConfirm")} ${item}?`)) { if (window.confirm(`${transI18n("deleteRelicConfirm")} ${item}?`)) {
handlerDeleteRelic(item) handlerDeleteRelic(item)
} }
}} }}
className="btn btn-error p-1.5 rounded-full shadow-lg transition-colors duration-200" className="btn btn-error p-1.5 rounded-full shadow-lg transition-colors duration-200"
title={transI18n("deleteRelic")} title={transI18n("deleteRelic")}
> >
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg> </svg>
</button> </button>
)} )}
</div> </div>
</div> </div>
))} ))}
</div> </div>
<div className="grid grid-cols-2 gap-2 mt-10"> <div className="grid grid-cols-2 gap-2 mt-10">
<button <button
disabled={!selectedRelicSlot} disabled={!selectedRelicSlot}
onClick={() => { onClick={() => {
handlerChangeRelic(selectedRelicSlot) handlerChangeRelic(selectedRelicSlot)
}} }}
className="btn btn-info" className="btn btn-info"
> >
{transI18n("changeRelic")} {transI18n("changeRelic")}
</button> </button>
<button <button
disabled={!selectedRelicSlot} disabled={!selectedRelicSlot}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
if (window.confirm(`${transI18n("deleteRelicConfirm")} ${selectedRelicSlot}?`)) { if (window.confirm(`${transI18n("deleteRelicConfirm")} ${selectedRelicSlot}?`)) {
handlerDeleteRelic(selectedRelicSlot) handlerDeleteRelic(selectedRelicSlot)
} }
}} }}
className="btn btn-error" className="btn btn-error"
> >
{transI18n("deleteRelic")} {transI18n("deleteRelic")}
</button> </button>
</div> </div>
<button <button
onClick={() => { onClick={() => {
setIsOpenQuickView(true) setIsOpenQuickView(true)
handleShow("quick_view_modal") handleShow("quick_view_modal")
}} }}
className="btn btn-info w-full mt-2" className="btn btn-info w-full mt-2"
> >
{transI18n("quickView")} {transI18n("quickView")}
</button> </button>
</div> </div>
</div> </div>
{/* Right Section - Stats and Set Effects */} {/* Right Section - Stats and Set Effects */}
<div className="space-y-6"> <div className="space-y-6">
{/* Set Effects Panel */} {/* Set Effects Panel */}
<div className="bg-base-100 rounded-xl p-6 shadow-lg"> <div className="bg-base-100 rounded-xl p-6 shadow-lg">
<h3 className="flex items-center gap-2 text-xl font-bold mb-4 text-base-content"> <h3 className="flex items-center gap-2 text-xl font-bold mb-4 text-base-content">
<div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div> <div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div>
{transI18n("setEffects")} {transI18n("setEffects")}
</h3> </h3>
<div className="space-y-6"> <div className="space-y-6">
{relicEffects.map((setEffect, index) => { {relicEffects.map((setEffect, index) => {
const relicInfo = mapRelicSet[setEffect.key]; const relicInfo = mapRelicSet[setEffect.key];
if (!relicInfo) return null; if (!relicInfo) return null;
return ( return (
<div key={index} className="space-y-3"> <div key={index} className="space-y-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className="font-bold text-warning" className="font-bold text-warning"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
getLocaleName(locale, relicInfo.Name), getLocaleName(locale, relicInfo.Name),
[] []
) )
}} }}
/> />
{setEffect.count && ( {setEffect.count && (
<span className={`text-sm text-info`}> <span className={`text-sm text-info`}>
({setEffect.count}) ({setEffect.count})
</span> </span>
)} )}
</div> </div>
<div className="space-y-2 pl-4"> <div className="space-y-2 pl-4">
{Object.entries(relicInfo.Skills).map(([requireNum, value]) => { {Object.entries(relicInfo.Skills).map(([requireNum, value]) => {
if (Number(requireNum) > Number(setEffect.count)) return null; if (Number(requireNum) > Number(setEffect.count)) return null;
return ( return (
<div key={requireNum} className="space-y-1"> <div key={requireNum} className="space-y-1">
<div className={`font-medium text-success`}> <div className={`font-medium text-success`}>
{requireNum}-PC: {requireNum}-PC:
</div> </div>
<div <div
className="text-sm text-base-content/80 leading-relaxed pl-4" className="text-sm text-base-content/80 leading-relaxed pl-4"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
getLocaleName(locale, value.Desc), getLocaleName(locale, value.Desc),
value.Param || [] value.Param || []
) )
}} }}
/> />
</div> </div>
) )
})} })}
</div> </div>
</div> </div>
) )
})} })}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{modalConfigs.map(({ id, title, onClose, content }) => ( {modalConfigs.map(({ id, title, onClose, content }) => (
<dialog key={id} id={id} className="modal"> <dialog key={id} id={id} className="modal">
<div className="modal-box w-11/12 max-w-[90%] max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="modal-box w-11/12 max-w-[90%] max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10"> <div className="sticky top-0 z-10">
<motion.button <motion.button
whileHover={{ scale: 1.1, rotate: 90 }} whileHover={{ scale: 1.1, rotate: 90 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none" className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
onClick={onClose} onClick={onClose}
> >
</motion.button> </motion.button>
</div> </div>
{title && ( {title && (
<div className="border-b border-purple-500/30 px-6 py-4 mb-4"> <div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400"> <h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400">
{title} {title}
</h3> </h3>
</div> </div>
)} )}
{content} {content}
</div> </div>
</dialog> </dialog>
))} ))}
</div> </div>
); );
} }
+112 -112
View File
@@ -1,112 +1,112 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
'use client' 'use client'
import dynamic from "next/dynamic" import dynamic from "next/dynamic"
import type { SingleValue } from "react-select" import type { SingleValue } from "react-select"
import Image from "next/image" import Image from "next/image"
import useLocaleStore from "@/stores/localeStore" import useLocaleStore from "@/stores/localeStore"
import ParseText from "../parseText" import ParseText from "../parseText"
import { themeColors } from "@/constant/constant" import { themeColors } from "@/constant/constant"
import type { Props as SelectProps } from "react-select" import type { Props as SelectProps } from "react-select"
import { JSX } from "react" import { JSX } from "react"
const Select = dynamic( const Select = dynamic(
() => import("react-select").then(m => m.default), () => import("react-select").then(m => m.default),
{ ssr: false } { ssr: false }
) as <Option, IsMulti extends boolean = false>( ) as <Option, IsMulti extends boolean = false>(
props: SelectProps<Option, IsMulti> props: SelectProps<Option, IsMulti>
) => JSX.Element ) => JSX.Element
export type SelectOption = { export type SelectOption = {
value: string value: string
label: string label: string
imageUrl: string imageUrl: string
} }
type SelectCustomProp = { type SelectCustomProp = {
customSet: SelectOption[] customSet: SelectOption[]
excludeSet: SelectOption[] excludeSet: SelectOption[]
selectedCustomSet: string selectedCustomSet: string
placeholder: string placeholder: string
setSelectedCustomSet: (value: string) => void setSelectedCustomSet: (value: string) => void
} }
export default function SelectCustomImage({ customSet, excludeSet, selectedCustomSet, placeholder, setSelectedCustomSet }: SelectCustomProp) { export default function SelectCustomImage({ customSet, excludeSet, selectedCustomSet, placeholder, setSelectedCustomSet }: SelectCustomProp) {
const options: SelectOption[] = customSet const options: SelectOption[] = customSet
const { locale, theme } = useLocaleStore() const { locale, theme } = useLocaleStore()
const c = themeColors[theme] || themeColors.winter const c = themeColors[theme] || themeColors.winter
const customStyles = { const customStyles = {
option: (p: any, s: any) => ({ option: (p: any, s: any) => ({
...p, ...p,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: '8px', gap: '8px',
padding: '8px 12px', padding: '8px 12px',
backgroundColor: s.isFocused ? c.bgHover : c.bg, backgroundColor: s.isFocused ? c.bgHover : c.bg,
color: c.text, color: c.text,
cursor: 'pointer' cursor: 'pointer'
}), }),
singleValue: (p: any) => ({ singleValue: (p: any) => ({
...p, ...p,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: '8px', gap: '8px',
color: c.text color: c.text
}), }),
control: (p: any) => ({ control: (p: any) => ({
...p, ...p,
backgroundColor: c.bg, backgroundColor: c.bg,
borderColor: c.border, borderColor: c.border,
boxShadow: 'none' boxShadow: 'none'
}), }),
menu: (p: any) => ({ menu: (p: any) => ({
...p, ...p,
backgroundColor: c.bg, backgroundColor: c.bg,
color: c.text, color: c.text,
zIndex: 9999 zIndex: 9999
}), }),
menuPortal: (p: any) => ({ menuPortal: (p: any) => ({
...p, ...p,
zIndex: 9999 zIndex: 9999
}) })
} }
const formatOptionLabel = (option: SelectOption) => ( const formatOptionLabel = (option: SelectOption) => (
<div className="flex items-center gap-1 w-full h-full"> <div className="flex items-center gap-1 w-full h-full">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={option.imageUrl} src={option.imageUrl}
alt="" alt=""
width={125} width={125}
height={125} height={125}
className="w-8 h-8 object-contain bg-warning-content rounded-full" className="w-8 h-8 object-contain bg-warning-content rounded-full"
/> />
<ParseText className='font-bold' text={option.label} locale={locale} /> <ParseText className='font-bold' text={option.label} locale={locale} />
</div> </div>
) )
return ( return (
<Select <Select
options={options.filter(opt => !excludeSet.some(ex => ex.value === opt.value))} options={options.filter(opt => !excludeSet.some(ex => ex.value === opt.value))}
value={options.find(opt => { value={options.find(opt => {
return opt.value === selectedCustomSet return opt.value === selectedCustomSet
}) || null} }) || null}
onChange={(selected: SingleValue<SelectOption>) => { onChange={(selected: SingleValue<SelectOption>) => {
setSelectedCustomSet(selected?.value || '') setSelectedCustomSet(selected?.value || '')
}} }}
formatOptionLabel={formatOptionLabel} formatOptionLabel={formatOptionLabel}
styles={customStyles} styles={customStyles}
placeholder={placeholder} placeholder={placeholder}
className="my-react-select-container" className="my-react-select-container"
classNamePrefix="my-react-select" classNamePrefix="my-react-select"
isSearchable isSearchable
isClearable isClearable
/> />
) )
} }
+118 -118
View File
@@ -1,118 +1,118 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
'use client' 'use client'
import dynamic from "next/dynamic" import dynamic from "next/dynamic"
import type { SingleValue } from "react-select" import type { SingleValue } from "react-select"
import { replaceByParam } from '@/helper' import { replaceByParam } from '@/helper'
import useLocaleStore from '@/stores/localeStore' import useLocaleStore from '@/stores/localeStore'
import { themeColors } from '@/constant/constant' import { themeColors } from '@/constant/constant'
import type { Props as SelectProps } from "react-select" import type { Props as SelectProps } from "react-select"
import { JSX } from "react" import { JSX } from "react"
const Select = dynamic( const Select = dynamic(
() => import("react-select").then(m => m.default), () => import("react-select").then(m => m.default),
{ ssr: false } { ssr: false }
) as <Option, IsMulti extends boolean = false>( ) as <Option, IsMulti extends boolean = false>(
props: SelectProps<Option, IsMulti> props: SelectProps<Option, IsMulti>
) => JSX.Element ) => JSX.Element
export type SelectOption = { export type SelectOption = {
id: string id: string
name: string name: string
time?: string time?: string
description?: string description?: string
} }
type SelectCustomProp = { type SelectCustomProp = {
customSet: SelectOption[] customSet: SelectOption[]
excludeSet: SelectOption[] excludeSet: SelectOption[]
selectedCustomSet: string selectedCustomSet: string
placeholder: string placeholder: string
setSelectedCustomSet: (value: string) => void setSelectedCustomSet: (value: string) => void
} }
export default function SelectCustomText({ customSet, excludeSet, selectedCustomSet, placeholder, setSelectedCustomSet }: SelectCustomProp) { export default function SelectCustomText({ customSet, excludeSet, selectedCustomSet, placeholder, setSelectedCustomSet }: SelectCustomProp) {
const options: SelectOption[] = customSet const options: SelectOption[] = customSet
const { theme } = useLocaleStore() const { theme } = useLocaleStore()
const c = themeColors[theme] || themeColors.winter const c = themeColors[theme] || themeColors.winter
const customStyles = { const customStyles = {
option: (p: any, s: any) => ({ option: (p: any, s: any) => ({
...p, ...p,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: '8px', gap: '8px',
padding: '8px 12px', padding: '8px 12px',
backgroundColor: s.isFocused ? c.bgHover : c.bg, backgroundColor: s.isFocused ? c.bgHover : c.bg,
color: c.text, color: c.text,
cursor: 'pointer' cursor: 'pointer'
}), }),
singleValue: (p: any) => ({ singleValue: (p: any) => ({
...p, ...p,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: '8px', gap: '8px',
color: c.text color: c.text
}), }),
control: (p: any) => ({ control: (p: any) => ({
...p, ...p,
backgroundColor: c.bg, backgroundColor: c.bg,
borderColor: c.border, borderColor: c.border,
boxShadow: 'none' boxShadow: 'none'
}), }),
menu: (p: any) => ({ menu: (p: any) => ({
...p, ...p,
backgroundColor: c.bg, backgroundColor: c.bg,
color: c.text, color: c.text,
zIndex: 9999 zIndex: 9999
}), }),
menuPortal: (p: any) => ({ menuPortal: (p: any) => ({
...p, ...p,
zIndex: 9999 zIndex: 9999
}) })
} }
const formatOptionLabel = (option: SelectOption) => ( const formatOptionLabel = (option: SelectOption) => (
<div className="flex flex-col gap-1 w-full h-full"> <div className="flex flex-col gap-1 w-full h-full">
<div <div
className="font-bold text-lg" className="font-bold text-lg"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
option.name, option.name,
[] []
) )
}} }}
/> />
{option.time && <div className='text-base'>{option.time}</div>} {option.time && <div className='text-base'>{option.time}</div>}
{option.description && <div {option.description && <div
className="text-base" className="text-base"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: option.description __html: option.description
}} }}
/>} />}
</div> </div>
) )
return ( return (
<Select <Select
options={options.filter(opt => !excludeSet.some(ex => ex.id === opt.id))} options={options.filter(opt => !excludeSet.some(ex => ex.id === opt.id))}
value={options.find(opt => { value={options.find(opt => {
return opt.id === selectedCustomSet return opt.id === selectedCustomSet
}) || null} }) || null}
onChange={(selected: SingleValue<SelectOption>) => { onChange={(selected: SingleValue<SelectOption>) => {
setSelectedCustomSet(selected?.id || '') setSelectedCustomSet(selected?.id || '')
}} }}
formatOptionLabel={formatOptionLabel} formatOptionLabel={formatOptionLabel}
styles={customStyles} styles={customStyles}
placeholder={placeholder} placeholder={placeholder}
className="my-react-select-container" className="my-react-select-container"
classNamePrefix="my-react-select" classNamePrefix="my-react-select"
isSearchable isSearchable
isClearable isClearable
/> />
) )
} }
File diff suppressed because it is too large Load Diff
+107 -107
View File
@@ -1,108 +1,108 @@
"use client" "use client"
import NextImage from "next/image" import NextImage from "next/image"
import { AvatarDetail, RelicShowcaseType } from "@/types"; import { AvatarDetail, RelicShowcaseType } from "@/types";
export default function RelicShowcase({ export default function RelicShowcase({
relic, relic,
avatarInfo, avatarInfo,
}: { }: {
relic: RelicShowcaseType; relic: RelicShowcaseType;
avatarInfo: AvatarDetail; avatarInfo: AvatarDetail;
}) { }) {
return ( return (
<> <>
<div <div
className="relative w-full flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600/60 bg-linear-to-r from-yellow-600/20 to-transparent" className="relative w-full flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600/60 bg-linear-to-r from-yellow-600/20 to-transparent"
> >
{/* Subtle glow overlay */} {/* Subtle glow overlay */}
<div className="absolute inset-0 rounded-s-lg pointer-events-none"></div> <div className="absolute inset-0 rounded-s-lg pointer-events-none"></div>
<div className="flex relative"> <div className="flex relative">
<div className="absolute inset-0 rounded-lg blur-lg -z-10"></div> <div className="absolute inset-0 rounded-lg blur-lg -z-10"></div>
<NextImage <NextImage
src={relic?.img || ""} src={relic?.img || ""}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
width={78} width={78}
height={78} height={78}
alt="Relic Icon" alt="Relic Icon"
className="h-19.5 w-19.5 rounded-lg" className="h-19.5 w-19.5 rounded-lg"
/> />
<div <div
className="absolute text-yellow-400 font-bold z-10 drop-shadow-[0_0_6px_rgba(251,191,36,0.8)]" className="absolute text-yellow-400 font-bold z-10 drop-shadow-[0_0_6px_rgba(251,191,36,0.8)]"
style={{ style={{
left: '0.65rem', left: '0.65rem',
bottom: '-0.45rem', bottom: '-0.45rem',
fontSize: '1.05rem', fontSize: '1.05rem',
letterSpacing: '-0.1em', letterSpacing: '-0.1em',
}} }}
> >
</div> </div>
</div> </div>
<div className=" flex w-1/6 flex-col items-center justify-center"> <div className=" flex w-1/6 flex-col items-center justify-center">
<div className="relative"> <div className="relative">
<div className="absolute inset-0 bg-yellow-500/15 rounded-full blur-md -z-10"></div> <div className="absolute inset-0 bg-yellow-500/15 rounded-full blur-md -z-10"></div>
<NextImage <NextImage
src={`${process.env.CDN_URL}/${relic?.mainAffix?.detail?.icon}` || ""} src={`${process.env.CDN_URL}/${relic?.mainAffix?.detail?.icon}` || ""}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
width={35} width={35}
height={35} height={35}
alt="Main Affix Icon" alt="Main Affix Icon"
className="h-8.75 w-8.75" className="h-8.75 w-8.75"
/> />
</div> </div>
<span className="text-base text-yellow-400 font-semibold drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]"> <span className="text-base text-yellow-400 font-semibold drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]">
{relic?.mainAffix?.valueAffix + relic?.mainAffix?.detail?.unit} {relic?.mainAffix?.valueAffix + relic?.mainAffix?.detail?.unit}
</span> </span>
<span className="bg-black/20 backdrop-blur-sm rounded px-1.5 py-0.5 text-xs border border-white/10"> <span className="bg-black/20 backdrop-blur-sm rounded px-1.5 py-0.5 text-xs border border-white/10">
+{relic?.mainAffix?.level} +{relic?.mainAffix?.level}
</span> </span>
</div> </div>
<div style={{ opacity: 0.3, height: '78px', borderLeftWidth: '1px' }}></div> <div style={{ opacity: 0.3, height: '78px', borderLeftWidth: '1px' }}></div>
<div className="grid w-[65%] m-2 grid-cols-2 gap-1"> <div className="grid w-[65%] m-2 grid-cols-2 gap-1">
{relic?.subAffix?.map((subAffix, index) => { {relic?.subAffix?.map((subAffix, index) => {
if (!subAffix) return null if (!subAffix) return null
return ( return (
<div key={index} className="flex flex-col"> <div key={index} className="flex flex-col">
<div className="relative flex flex-row items-center bg-black/20 backdrop-blur-sm rounded-md p-1 border border-white/5 min-w-0"> <div className="relative flex flex-row items-center bg-black/20 backdrop-blur-sm rounded-md p-1 border border-white/5 min-w-0">
{subAffix?.detail?.icon ? ( {subAffix?.detail?.icon ? (
<NextImage <NextImage
src={`${process.env.CDN_URL}/${subAffix?.detail?.icon}` || ""} src={`${process.env.CDN_URL}/${subAffix?.detail?.icon}` || ""}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
width={32} width={32}
height={32} height={32}
alt="Sub Affix Icon" alt="Sub Affix Icon"
className="h-6 w-6 shrink-0" className="h-6 w-6 shrink-0"
/> />
) : ( ) : (
<div className="h-6 w-6 bg-black/60 rounded flex items-center justify-center border border-white/10 shrink-0"> <div className="h-6 w-6 bg-black/60 rounded flex items-center justify-center border border-white/10 shrink-0">
<span className="text-xs text-white/50">?</span> <span className="text-xs text-white/50">?</span>
</div> </div>
)} )}
<span className="text-xs text-gray-200 ml-0.5 truncate flex-1 min-w-0"> <span className="text-xs text-gray-200 ml-0.5 truncate flex-1 min-w-0">
+{subAffix?.valueAffix + subAffix?.detail?.unit} +{subAffix?.valueAffix + subAffix?.detail?.unit}
</span> </span>
{ {
(avatarInfo?.Relics?.SubAffixPropertyList.findIndex((item) => item === subAffix?.property) !== -1) && ( (avatarInfo?.Relics?.SubAffixPropertyList.findIndex((item) => item === subAffix?.property) !== -1) && (
<span className="ml-1 bg-yellow-600/20 text-yellow-400 rounded-full px-1 py-0.5 text-[10px] font-semibold border border-yellow-600/30 shrink-0 leading-none"> <span className="ml-1 bg-yellow-600/20 text-yellow-400 rounded-full px-1 py-0.5 text-[10px] font-semibold border border-yellow-600/30 shrink-0 leading-none">
{subAffix?.count} {subAffix?.count}
</span> </span>
)} )}
</div> </div>
</div> </div>
) )
})} })}
</div> </div>
</div> </div>
</> </>
) )
} }
+384 -384
View File
@@ -1,385 +1,385 @@
"use client" "use client"
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useMemo } from "react"; import { useMemo } from "react";
import { traceButtonsInfo, traceLink } from "@/constant/traceConstant"; import { traceButtonsInfo, traceLink } from "@/constant/traceConstant";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import Image from "next/image"; import Image from "next/image";
import { replaceByParam, getLocaleName } from '@/helper'; import { replaceByParam, getLocaleName } from '@/helper';
import { mappingStats } from "@/constant/constant"; import { mappingStats } from "@/constant/constant";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import useCurrentDataStore from "@/stores/currentDataStore"; import useCurrentDataStore from "@/stores/currentDataStore";
import { StatusAdd } from '@/types/avatarDetail'; import { StatusAdd } from '@/types/avatarDetail';
import { SkillDescription } from "./skillDescription"; import { SkillDescription } from "./skillDescription";
export default function SkillsInfo() { export default function SkillsInfo() {
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { theme } = useLocaleStore() const { theme } = useLocaleStore()
const { avatarSelected, skillIDSelected, setSkillIDSelected } = useCurrentDataStore() const { avatarSelected, skillIDSelected, setSkillIDSelected } = useCurrentDataStore()
const { avatars, setAvatar } = useUserDataStore() const { avatars, setAvatar } = useUserDataStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const traceButtons = useMemo(() => { const traceButtons = useMemo(() => {
if (!avatarSelected) return if (!avatarSelected) return
return traceButtonsInfo[avatarSelected.BaseType] return traceButtonsInfo[avatarSelected.BaseType]
}, [avatarSelected]) }, [avatarSelected])
const avatarData = useMemo(() => { const avatarData = useMemo(() => {
if (!avatarSelected) return if (!avatarSelected) return
return avatars[avatarSelected?.ID?.toString()] return avatars[avatarSelected?.ID?.toString()]
}, [avatarSelected, avatars]) }, [avatarSelected, avatars])
const avatarSkillTree = useMemo(() => { const avatarSkillTree = useMemo(() => {
if (!avatarSelected || !avatars[avatarSelected?.ID?.toString()]) return {} if (!avatarSelected || !avatars[avatarSelected?.ID?.toString()]) return {}
if (avatars[avatarSelected?.ID?.toString()].enhanced) { if (avatars[avatarSelected?.ID?.toString()].enhanced) {
return avatarSelected?.Enhanced?.[avatars[avatarSelected?.ID?.toString()].enhanced.toString()].SkillTrees || {} return avatarSelected?.Enhanced?.[avatars[avatarSelected?.ID?.toString()].enhanced.toString()].SkillTrees || {}
} }
return avatarSelected?.SkillTrees || {} return avatarSelected?.SkillTrees || {}
}, [avatarSelected, avatars]) }, [avatarSelected, avatars])
const skillInfo = useMemo(() => { const skillInfo = useMemo(() => {
if (!avatarSelected || !skillIDSelected) return if (!avatarSelected || !skillIDSelected) return
return avatarSkillTree?.[skillIDSelected || ""]?.["1"] return avatarSkillTree?.[skillIDSelected || ""]?.["1"]
}, [avatarSelected, avatarSkillTree, skillIDSelected]) }, [avatarSelected, avatarSkillTree, skillIDSelected])
const getTraceBuffDisplay = (status: StatusAdd) => { const getTraceBuffDisplay = (status: StatusAdd) => {
const dataDisplay = mappingStats[status.PropertyType] const dataDisplay = mappingStats[status.PropertyType]
if (!dataDisplay) return "" if (!dataDisplay) return ""
if (dataDisplay.unit === "%") { if (dataDisplay.unit === "%") {
return `${(status.Value * 100).toFixed(1)}${dataDisplay.unit}` return `${(status.Value * 100).toFixed(1)}${dataDisplay.unit}`
} }
if (dataDisplay.name === "SPD") { if (dataDisplay.name === "SPD") {
return `${status.Value.toFixed(1)}${dataDisplay.unit}` return `${status.Value.toFixed(1)}${dataDisplay.unit}`
} }
return `${status.Value.toFixed(0)}${dataDisplay.unit}` return `${status.Value.toFixed(0)}${dataDisplay.unit}`
} }
const dataLevelUpSkill = useMemo(() => { const dataLevelUpSkill = useMemo(() => {
const skillIds: number[] = skillInfo?.LevelUpSkillID || [] const skillIds: number[] = skillInfo?.LevelUpSkillID || []
if (!avatarSelected || !avatarData) return undefined if (!avatarSelected || !avatarData) return undefined
let result = Object.values(avatarSelected.Skills || {})?.filter((skill) => skillIds.includes(skill.ID)) let result = Object.values(avatarSelected.Skills || {})?.filter((skill) => skillIds.includes(skill.ID))
if (avatarData.enhanced) { if (avatarData.enhanced) {
result = Object.values(avatarSelected?.Enhanced?.[avatarData.enhanced.toString()]?.Skills || {})?.filter((skill) => skillIds.includes(skill.ID)) result = Object.values(avatarSelected?.Enhanced?.[avatarData.enhanced.toString()]?.Skills || {})?.filter((skill) => skillIds.includes(skill.ID))
} }
if (result && result.length > 0) { if (result && result.length > 0) {
return { return {
isServant: false, isServant: false,
data: result, data: result,
servantData: null, servantData: null,
} }
} }
const resultServant = Object.values(avatarSelected?.Memosprite?.Skills || {}) const resultServant = Object.values(avatarSelected?.Memosprite?.Skills || {})
?.filter((skill) => skillIds.includes(skill.ID)) ?.filter((skill) => skillIds.includes(skill.ID))
if (resultServant && resultServant.length > 0) { if (resultServant && resultServant.length > 0) {
return { return {
isServant: true, isServant: true,
data: resultServant, data: resultServant,
servantData: avatarSelected.Memosprite, servantData: avatarSelected.Memosprite,
} }
} }
return undefined return undefined
}, [skillInfo?.LevelUpSkillID, avatarSelected, avatarData]) }, [skillInfo?.LevelUpSkillID, avatarSelected, avatarData])
const handlerMaxAll = () => { const handlerMaxAll = () => {
if (!avatarData || !avatarSkillTree) { if (!avatarData || !avatarSkillTree) {
toast.error(transI18n("maxAllFailed")) toast.error(transI18n("maxAllFailed"))
return return
} }
const newData = structuredClone(avatarData) const newData = structuredClone(avatarData)
newData.data.skills = Object.values(avatarSkillTree).reduce((acc, dataPointEntry) => { newData.data.skills = Object.values(avatarSkillTree).reduce((acc, dataPointEntry) => {
const firstEntry = Object.values(dataPointEntry)[0]; const firstEntry = Object.values(dataPointEntry)[0];
if (firstEntry) { if (firstEntry) {
acc[firstEntry.PointID] = firstEntry.MaxLevel; acc[firstEntry.PointID] = firstEntry.MaxLevel;
} }
return acc; return acc;
}, {} as Record<string, number>) }, {} as Record<string, number>)
toast.success(transI18n("maxAllSuccess")) toast.success(transI18n("maxAllSuccess"))
setAvatar(newData) setAvatar(newData)
} }
const handlerChangeStatusTrace = (status: boolean) => { const handlerChangeStatusTrace = (status: boolean) => {
if (!avatarData || !skillInfo) return if (!avatarData || !skillInfo) return
const newData = structuredClone(avatarData) const newData = structuredClone(avatarData)
newData.data.skills[skillInfo?.PointID] = status ? 1 : 0 newData.data.skills[skillInfo?.PointID] = status ? 1 : 0
if (!status && traceLink?.[avatarSelected?.BaseType || ""]?.[skillIDSelected || ""]) { if (!status && traceLink?.[avatarSelected?.BaseType || ""]?.[skillIDSelected || ""]) {
traceLink[avatarSelected?.BaseType || ""][skillIDSelected || ""].forEach((pointId) => { traceLink[avatarSelected?.BaseType || ""][skillIDSelected || ""].forEach((pointId) => {
if (avatarSkillTree?.[pointId]?.["1"]) { if (avatarSkillTree?.[pointId]?.["1"]) {
newData.data.skills[avatarSkillTree?.[pointId]?.["1"].PointID] = 0 newData.data.skills[avatarSkillTree?.[pointId]?.["1"].PointID] = 0
} }
}) })
} }
setAvatar(newData) setAvatar(newData)
} }
return ( return (
<div className="w-full"> <div className="w-full">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<div className="rounded-xl p-6 shadow-lg"> <div className="rounded-xl p-6 shadow-lg">
<h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content"> <h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content">
<div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div> <div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div>
{transI18n("skills")} {transI18n("skills")}
</h2> </h2>
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<button className="btn btn-success" onClick={handlerMaxAll}>{transI18n("maxAll")}</button> <button className="btn btn-success" onClick={handlerMaxAll}>{transI18n("maxAll")}</button>
{traceButtons && avatarSelected && ( {traceButtons && avatarSelected && (
<div className="grid col-span-4 relative w-full aspect-square"> <div className="grid col-span-4 relative w-full aspect-square">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/skilltree/${avatarSelected?.BaseType?.toUpperCase()}.webp`} src={`/skilltree/${avatarSelected?.BaseType?.toUpperCase()}.webp`}
alt="" alt=""
width={312} width={312}
priority={true} priority={true}
height={312} height={312}
style={{ style={{
filter: (theme === "winter" || theme === "cupcake") ? "invert(1)" : "none" filter: (theme === "winter" || theme === "cupcake") ? "invert(1)" : "none"
}} }}
className={`w-full h-full object-cover rounded-xl`} className={`w-full h-full object-cover rounded-xl`}
/> />
{traceButtons.map((btn, index) => { {traceButtons.map((btn, index) => {
if (!avatarSelected?.SkillTrees?.[btn.id]) { if (!avatarSelected?.SkillTrees?.[btn.id]) {
return null return null
} }
return ( return (
<div <div
key={`${btn.id} + ${index}`} key={`${btn.id} + ${index}`}
id={btn.id} id={btn.id}
className={` className={`
absolute rounded-full border border-black absolute rounded-full border border-black
bg-no-repeat bg-contain bg-no-repeat bg-contain
cursor-pointer transition-all duration-200 ease-in-out cursor-pointer transition-all duration-200 ease-in-out
shadow-[0_0_5px_white] flex justify-center items-center shadow-[0_0_5px_white] flex justify-center items-center
hover:scale-110z-10 hover:scale-110z-10
${btn.size === "small" ? "w-[6vw] h-[6vw] md:w-[2vw] md:h-[2vw] bg-white" : ""} ${btn.size === "small" ? "w-[6vw] h-[6vw] md:w-[2vw] md:h-[2vw] bg-white" : ""}
${btn.size === "medium" ? "w-[8vw] h-[8vw] md:w-[3vw] md:h-[3vw] bg-white" : ""} ${btn.size === "medium" ? "w-[8vw] h-[8vw] md:w-[3vw] md:h-[3vw] bg-white" : ""}
${btn.size === "big" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""} ${btn.size === "big" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""}
${btn.size === "special" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-white" : ""} ${btn.size === "special" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-white" : ""}
${btn.size === "memory" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""} ${btn.size === "memory" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""}
${btn.size === "elation" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""} ${btn.size === "elation" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""}
${skillIDSelected === btn.id ? "border-4 border-primary" : ""} ${skillIDSelected === btn.id ? "border-4 border-primary" : ""}
${!avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID] ${!avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID]
? "opacity-50 cursor-not-allowed" ? "opacity-50 cursor-not-allowed"
: ""} : ""}
`} `}
onClick={() => { onClick={() => {
setSkillIDSelected(btn.id === skillIDSelected ? null : btn.id) setSkillIDSelected(btn.id === skillIDSelected ? null : btn.id)
}} }}
style={{ style={{
left: btn.left, left: btn.left,
top: btn.top, top: btn.top,
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
}} }}
> >
<Image <Image
src={`${process.env.CDN_URL}/${avatarSelected?.SkillTrees?.[btn.id]?.["1"]?.Icon}`} src={`${process.env.CDN_URL}/${avatarSelected?.SkillTrees?.[btn.id]?.["1"]?.Icon}`}
alt={btn.id.replaceAll("Point", "")} alt={btn.id.replaceAll("Point", "")}
priority={true} priority={true}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
width={124} width={124}
height={124} height={124}
style={{ style={{
objectFit: "contain", objectFit: "contain",
padding: "2px", padding: "2px",
filter: (btn.size !== "big" && btn.size !== "memory" && btn.size !== "elation") ? "brightness(0%)" : "" filter: (btn.size !== "big" && btn.size !== "memory" && btn.size !== "elation") ? "brightness(0%)" : ""
}} }}
/> />
{(btn.size === "big" || btn.size === "memory" || btn.size === "elation") && ( {(btn.size === "big" || btn.size === "memory" || btn.size === "elation") && (
<p className=" <p className="
z-12 text-sm sm:text-xs lg:text-sm xl:text-base 2xl:text-2xl z-12 text-sm sm:text-xs lg:text-sm xl:text-base 2xl:text-2xl
font-bold text-center rounded-full absolute font-bold text-center rounded-full absolute
translate-y-full mt-1 translate-y-full mt-1
bg-base-300 px-1 bg-base-300 px-1
left-1/2 transform -translate-x-1/2 left-1/2 transform -translate-x-1/2
"> ">
{`${avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID] || 0}/${avatarSkillTree?.[btn.id]?.["1"]?.MaxLevel}`} {`${avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID] || 0}/${avatarSkillTree?.[btn.id]?.["1"]?.MaxLevel}`}
</p> </p>
)} )}
{btn.size === "special" && ( {btn.size === "special" && (
<div <div
style={{ style={{
position: "absolute", position: "absolute",
inset: 0, inset: 0,
backgroundColor: "#4a6eff", backgroundColor: "#4a6eff",
mixBlendMode: "screen", mixBlendMode: "screen",
opacity: 0.8, opacity: 0.8,
borderRadius: "50%" borderRadius: "50%"
}} }}
/> />
)} )}
{btn.size === "memory" && ( {btn.size === "memory" && (
<div <div
style={{ style={{
position: "absolute", position: "absolute",
inset: 0, inset: 0,
backgroundColor: "#9a89ff", backgroundColor: "#9a89ff",
mixBlendMode: "screen", mixBlendMode: "screen",
opacity: 0.4, opacity: 0.4,
borderRadius: "50%" borderRadius: "50%"
}} }}
/> />
)} )}
{btn.size === "elation" && ( {btn.size === "elation" && (
<div <div
style={{ style={{
position: "absolute", position: "absolute",
inset: 0, inset: 0,
backgroundColor: "#ff8c00", backgroundColor: "#ff8c00",
mixBlendMode: "screen", mixBlendMode: "screen",
opacity: 0.5, opacity: 0.5,
borderRadius: "50%" borderRadius: "50%"
}} }}
/> />
)} )}
{btn.size === "big" && ( {btn.size === "big" && (
<div <div
style={{ style={{
position: "absolute", position: "absolute",
inset: 0, inset: 0,
backgroundColor: "#f5e4b0", backgroundColor: "#f5e4b0",
mixBlendMode: "screen", mixBlendMode: "screen",
opacity: 0.3, opacity: 0.3,
borderRadius: "50%" borderRadius: "50%"
}} }}
/> />
)} )}
</div> </div>
) )
})} })}
</div> </div>
)} )}
{!traceButtons && avatarSelected && ( {!traceButtons && avatarSelected && (
<div className="flex flex-col relative w-full aspect-square"> <div className="flex flex-col relative w-full aspect-square">
</div> </div>
)} )}
</div> </div>
</div> </div>
<div className="bg-base-100 rounded-xl p-6 shadow-lg"> <div className="bg-base-100 rounded-xl p-6 shadow-lg">
<h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content"> <h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content">
<div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div> <div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div>
{transI18n("details")} {transI18n("details")}
</h2> </h2>
{skillIDSelected && avatarSelected?.SkillTrees && avatarData && ( {skillIDSelected && avatarSelected?.SkillTrees && avatarData && (
<div> <div>
{skillInfo?.MaxLevel && skillInfo?.MaxLevel > 1 ? ( {skillInfo?.MaxLevel && skillInfo?.MaxLevel > 1 ? (
<div> <div>
<div className="font-bold text-success">{transI18n("level")}</div> <div className="font-bold text-success">{transI18n("level")}</div>
<div className="w-full max-w-xs"> <div className="w-full max-w-xs">
<input type="range" <input type="range"
min={1} min={1}
max={skillInfo?.MaxLevel || 1} max={skillInfo?.MaxLevel || 1}
value={avatarData?.data.skills?.[skillInfo?.PointID] || 1} value={avatarData?.data.skills?.[skillInfo?.PointID] || 1}
onChange={(e) => { onChange={(e) => {
const newData = structuredClone(avatarData) const newData = structuredClone(avatarData)
newData.data.skills[skillInfo?.PointID] = parseInt(e.target.value) newData.data.skills[skillInfo?.PointID] = parseInt(e.target.value)
setAvatar(newData) setAvatar(newData)
}} }}
className="range range-success" className="range range-success"
step="1" /> step="1" />
<div className="flex justify-between px-2.5 mt-2 text-xs"> <div className="flex justify-between px-2.5 mt-2 text-xs">
{Array.from({ length: skillInfo?.MaxLevel }, (_, index) => index + 1).map((index) => ( {Array.from({ length: skillInfo?.MaxLevel }, (_, index) => index + 1).map((index) => (
<span key={index}>{index}</span> <span key={index}>{index}</span>
))} ))}
</div> </div>
</div> </div>
</div> </div>
) : skillInfo?.MaxLevel && skillInfo?.MaxLevel === 1 && traceButtons?.find((btn) => btn.id === skillIDSelected)?.size !== "big" ? ( ) : skillInfo?.MaxLevel && skillInfo?.MaxLevel === 1 && traceButtons?.find((btn) => btn.id === skillIDSelected)?.size !== "big" ? (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<input <input
type="checkbox" type="checkbox"
checked={avatarData?.data.skills?.[skillInfo?.PointID] === 1} checked={avatarData?.data.skills?.[skillInfo?.PointID] === 1}
className="toggle toggle-success" className="toggle toggle-success"
onChange={(e) => { onChange={(e) => {
if (traceButtons?.find((btn) => btn.id === skillIDSelected)?.size === "special") { if (traceButtons?.find((btn) => btn.id === skillIDSelected)?.size === "special") {
if (e.target.checked) { if (e.target.checked) {
const newData = structuredClone(avatarData) const newData = structuredClone(avatarData)
newData.data.skills[skillInfo?.PointID] = 1 newData.data.skills[skillInfo?.PointID] = 1
setAvatar(newData) setAvatar(newData)
return return
} }
const newData = structuredClone(avatarData) const newData = structuredClone(avatarData)
delete newData.data.skills[skillInfo?.PointID] delete newData.data.skills[skillInfo?.PointID]
setAvatar(newData) setAvatar(newData)
return return
} }
handlerChangeStatusTrace(e.target.checked) handlerChangeStatusTrace(e.target.checked)
}} }}
/> />
<div className="font-bold text-success"> <div className="font-bold text-success">
{avatarData?.data.skills?.[skillInfo?.PointID] === 1 ? transI18n("active") : transI18n("inactive")} {avatarData?.data.skills?.[skillInfo?.PointID] === 1 ? transI18n("active") : transI18n("inactive")}
</div> </div>
</div> </div>
) : ( ) : (
null null
)} )}
{((skillInfo?.PointName && skillInfo?.PointDesc) || {((skillInfo?.PointName && skillInfo?.PointDesc) ||
(skillInfo?.PointName && skillInfo?.StatusAddList.length > 0)) (skillInfo?.PointName && skillInfo?.StatusAddList.length > 0))
&& ( && (
<div className="text-xl font-bold flex items-center gap-2 mt-2"> <div className="text-xl font-bold flex items-center gap-2 mt-2">
{getLocaleName(locale, skillInfo.PointName)} {getLocaleName(locale, skillInfo.PointName)}
{skillInfo.StatusAddList.length > 0 && ( {skillInfo.StatusAddList.length > 0 && (
<div> <div>
{skillInfo.StatusAddList.map((status, index) => ( {skillInfo.StatusAddList.map((status, index) => (
<div key={index}> <div key={index}>
<div className="text-xl font-bold">{getTraceBuffDisplay(status)}</div> <div className="text-xl font-bold">{getTraceBuffDisplay(status)}</div>
</div> </div>
))} ))}
</div> </div>
)} )}
</div> </div>
)} )}
{skillInfo?.PointDesc && ( {skillInfo?.PointDesc && (
<div <div
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
getLocaleName(locale, skillInfo?.PointDesc) || "", getLocaleName(locale, skillInfo?.PointDesc) || "",
skillInfo?.Param || [] skillInfo?.Param || []
) )
}} }}
/> />
)} )}
{skillInfo?.LevelUpSkillID {skillInfo?.LevelUpSkillID
&& skillInfo?.LevelUpSkillID.length > 0 && skillInfo?.LevelUpSkillID.length > 0
&& dataLevelUpSkill && dataLevelUpSkill
&& ( && (
<div className="mt-2 flex flex-col gap-2"> <div className="mt-2 flex flex-col gap-2">
{dataLevelUpSkill?.data?.map((skill, index) => ( {dataLevelUpSkill?.data?.map((skill, index) => (
<div key={index}> <div key={index}>
<div className="text-xl font-bold text-primary"> <div className="text-xl font-bold text-primary">
{transI18n(dataLevelUpSkill.isServant ? `${skill?.AttackType ? "severaltalent" : "servantskill"}` : `${skill?.AttackType ? skill?.AttackType.toLowerCase() : "talent"}`)} {transI18n(dataLevelUpSkill.isServant ? `${skill?.AttackType ? "severaltalent" : "servantskill"}` : `${skill?.AttackType ? skill?.AttackType.toLowerCase() : "talent"}`)}
{` (${transI18n(skill?.SkillEffect?.toLowerCase())})`} {` (${transI18n(skill?.SkillEffect?.toLowerCase())})`}
</div> </div>
<SkillDescription <SkillDescription
skill={skill} skill={skill}
locale={locale} locale={locale}
avatarData={avatarData} avatarData={avatarData}
skillInfo={skillInfo} skillInfo={skillInfo}
/> />
</div> </div>
))} ))}
</div> </div>
)} )}
</div> </div>
)} )}
</div> </div>
</div> </div>
</div> </div>
); );
} }
+40 -40
View File
@@ -1,41 +1,41 @@
import { getLocaleName, replaceByParam } from "@/helper"; import { getLocaleName, replaceByParam } from "@/helper";
import { AvatarStore, SkillDetail, SkillTreePoint } from "@/types"; import { AvatarStore, SkillDetail, SkillTreePoint } from "@/types";
import ExtraEffectList from "../extraInfo"; import ExtraEffectList from "../extraInfo";
export const SkillDescription = ({ skill, locale, avatarData, skillInfo }: { export const SkillDescription = ({ skill, locale, avatarData, skillInfo }: {
skill: SkillDetail, skill: SkillDetail,
locale: string, locale: string,
avatarData: AvatarStore, avatarData: AvatarStore,
skillInfo: SkillTreePoint skillInfo: SkillTreePoint
}) => { }) => {
const levelKey = avatarData?.data.skills?.[skillInfo?.PointID]?.toString() || ""; const levelKey = avatarData?.data.skills?.[skillInfo?.PointID]?.toString() || "";
const params = skill.Level[levelKey]?.Param || []; const params = skill.Level[levelKey]?.Param || [];
const descHtml = getLocaleName(locale, skill.Desc) || getLocaleName(locale, skill.SimpleDesc); const descHtml = getLocaleName(locale, skill.Desc) || getLocaleName(locale, skill.SimpleDesc);
const extraList = Object.values(skill.Extra).length > 0 const extraList = Object.values(skill.Extra).length > 0
? skill.Extra ? skill.Extra
: skill?.SimpleExtra || {}; : skill?.SimpleExtra || {};
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="space-y-2 pb-2"> <div className="space-y-2 pb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-1 h-5 bg-primary/80 rounded-sm" /> <div className="w-1 h-5 bg-primary/80 rounded-sm" />
<div <div
className="text-lg font-bold tracking-wide text-foreground uppercase" className="text-lg font-bold tracking-wide text-foreground uppercase"
dangerouslySetInnerHTML={{ __html: replaceByParam(getLocaleName(locale, skill.Name), []) }} dangerouslySetInnerHTML={{ __html: replaceByParam(getLocaleName(locale, skill.Name), []) }}
/> />
</div> </div>
<div <div
className="text-[15px] leading-relaxed text-foreground/90 pl-3 border-l border-transparent" className="text-[15px] leading-relaxed text-foreground/90 pl-3 border-l border-transparent"
dangerouslySetInnerHTML={{ __html: replaceByParam(descHtml, params) }} dangerouslySetInnerHTML={{ __html: replaceByParam(descHtml, params) }}
/> />
</div> </div>
{Object.keys(extraList).length > 0 && ( {Object.keys(extraList).length > 0 && (
<ExtraEffectList extras={extraList} locale={locale} /> <ExtraEffectList extras={extraList} locale={locale} />
)} )}
</div> </div>
); );
}; };
@@ -1,8 +1,8 @@
"use client"; "use client";
import { PropsWithChildren, useContext } from "react"; import { PropsWithChildren, useContext } from "react";
import { ThemeContext } from "./themeContext"; import { ThemeContext } from "./themeContext";
export function ClientThemeWrapper({ children }: PropsWithChildren) { export function ClientThemeWrapper({ children }: PropsWithChildren) {
const { theme } = useContext(ThemeContext); const { theme } = useContext(ThemeContext);
return <div data-theme={theme} className="h-full">{children}</div>; return <div data-theme={theme} className="h-full">{children}</div>;
} }
+2 -2
View File
@@ -1,2 +1,2 @@
export * from "./clientThemeWrapper" export * from "./clientThemeWrapper"
export * from "./themeContext" export * from "./themeContext"
+40 -40
View File
@@ -1,41 +1,41 @@
"use client"; "use client";
import { createContext, PropsWithChildren, useEffect } from "react"; import { createContext, PropsWithChildren, useEffect } from "react";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
interface ThemeContextType { interface ThemeContextType {
theme?: string; theme?: string;
changeTheme?: (nextTheme: string | null) => void; changeTheme?: (nextTheme: string | null) => void;
} }
export const ThemeContext = createContext<ThemeContextType>({}); export const ThemeContext = createContext<ThemeContextType>({});
export const ThemeProvider = ({ children }: PropsWithChildren) => { export const ThemeProvider = ({ children }: PropsWithChildren) => {
const { theme, setTheme } = useLocaleStore() const { theme, setTheme } = useLocaleStore()
useEffect(() => { useEffect(() => {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
const storedTheme = localStorage.getItem("theme"); const storedTheme = localStorage.getItem("theme");
if (storedTheme) setTheme(storedTheme); if (storedTheme) setTheme(storedTheme);
} }
}, [setTheme]); }, [setTheme]);
const changeTheme = (nextTheme: string | null) => { const changeTheme = (nextTheme: string | null) => {
if (nextTheme) { if (nextTheme) {
setTheme(nextTheme); setTheme(nextTheme);
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
localStorage.setItem("theme", nextTheme); localStorage.setItem("theme", nextTheme);
} }
} else { } else {
setTheme(theme === "winter" ? "night" : "winter"); setTheme(theme === "winter" ? "night" : "winter");
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
localStorage.setItem("theme", theme); localStorage.setItem("theme", theme);
} }
} }
}; };
return ( return (
<ThemeContext.Provider value={{ theme, changeTheme }}> <ThemeContext.Provider value={{ theme, changeTheme }}>
{children} {children}
</ThemeContext.Provider> </ThemeContext.Provider>
); );
}; };
+220 -220
View File
@@ -1,220 +1,220 @@
export const listCurrentLanguage = { export const listCurrentLanguage = {
en: { label: "English", flag: "🇬🇧" }, en: { label: "English", flag: "🇬🇧" },
vi: { label: "Tiếng Việt", flag: "🇻🇳" }, vi: { label: "Tiếng Việt", flag: "🇻🇳" },
ja: { label: "日本語", flag: "🇯🇵" }, ja: { label: "日本語", flag: "🇯🇵" },
ko: { label: "한국어", flag: "🇰🇷" }, ko: { label: "한국어", flag: "🇰🇷" },
zh: { label: "中文", flag: "🇨🇳" }, zh: { label: "中文", flag: "🇨🇳" },
de: { label: "Deutsch", flag: "🇩🇪" }, de: { label: "Deutsch", flag: "🇩🇪" },
es: { label: "Español", flag: "🇪🇸" }, es: { label: "Español", flag: "🇪🇸" },
fr: { label: "Français", flag: "🇫🇷" }, fr: { label: "Français", flag: "🇫🇷" },
id: { label: "Bahasa Indonesia", flag: "🇮🇩" }, id: { label: "Bahasa Indonesia", flag: "🇮🇩" },
pt: { label: "Português", flag: "🇵🇹" }, pt: { label: "Português", flag: "🇵🇹" },
ru: { label: "Русский", flag: "🇷🇺" }, ru: { label: "Русский", flag: "🇷🇺" },
th: { label: "ไทย", flag: "🇹🇭" } th: { label: "ไทย", flag: "🇹🇭" }
}; };
export const listCurrentLanguageApi : Record<string, string> = { export const listCurrentLanguageApi : Record<string, string> = {
ja: "jp", ja: "jp",
ko: "kr", ko: "kr",
en: "en", en: "en",
vi: "vi", vi: "vi",
zh: "cn", zh: "cn",
de: "de", de: "de",
es: "es", es: "es",
fr: "fr", fr: "fr",
id: "id", id: "id",
pt: "pt", pt: "pt",
ru: "ru", ru: "ru",
th: "th" th: "th"
}; };
export const mappingStats = <Record<string, {name: string, icon: string, unit: string, baseStat: string}> > { export const mappingStats = <Record<string, {name: string, icon: string, unit: string, baseStat: string}> > {
"HPDelta": { "HPDelta": {
name:"HP", name:"HP",
icon:"spriteoutput/ui/avatar/icon/IconMaxHP.png", icon:"spriteoutput/ui/avatar/icon/IconMaxHP.png",
unit: "", unit: "",
baseStat: "HP" baseStat: "HP"
}, },
"AttackDelta": { "AttackDelta": {
name:"ATK", name:"ATK",
icon:"spriteoutput/ui/avatar/icon/IconAttack.png", icon:"spriteoutput/ui/avatar/icon/IconAttack.png",
unit: "", unit: "",
baseStat: "ATK" baseStat: "ATK"
}, },
"HPAddedRatio": { "HPAddedRatio": {
name:"HP", name:"HP",
icon:"spriteoutput/ui/avatar/icon/IconMaxHP.png", icon:"spriteoutput/ui/avatar/icon/IconMaxHP.png",
unit: "%", unit: "%",
baseStat: "HP" baseStat: "HP"
}, },
"AttackAddedRatio": { "AttackAddedRatio": {
name:"ATK", name:"ATK",
icon:"spriteoutput/ui/avatar/icon/IconAttack.png", icon:"spriteoutput/ui/avatar/icon/IconAttack.png",
unit: "%", unit: "%",
baseStat: "ATK" baseStat: "ATK"
}, },
"DefenceDelta": { "DefenceDelta": {
name:"DEF", name:"DEF",
icon:"spriteoutput/ui/avatar/icon/IconDefence.png", icon:"spriteoutput/ui/avatar/icon/IconDefence.png",
unit: "", unit: "",
baseStat: "DEF" baseStat: "DEF"
}, },
"DefenceAddedRatio": { "DefenceAddedRatio": {
name:"DEF", name:"DEF",
icon:"spriteoutput/ui/avatar/icon/IconDefence.png", icon:"spriteoutput/ui/avatar/icon/IconDefence.png",
unit: "%", unit: "%",
baseStat: "DEF" baseStat: "DEF"
}, },
"SpeedAddedRatio": { "SpeedAddedRatio": {
name:"SPD", name:"SPD",
icon:"spriteoutput/ui/avatar/icon/IconSpeed.png", icon:"spriteoutput/ui/avatar/icon/IconSpeed.png",
unit: "%", unit: "%",
baseStat: "SPD" baseStat: "SPD"
}, },
"BaseSpeed": { "BaseSpeed": {
name:"SPD", name:"SPD",
icon:"spriteoutput/ui/avatar/icon/IconSpeed.png", icon:"spriteoutput/ui/avatar/icon/IconSpeed.png",
unit: "", unit: "",
baseStat: "SPD" baseStat: "SPD"
}, },
"CriticalChanceBase": { "CriticalChanceBase": {
name:"CRIT Rate", name:"CRIT Rate",
icon:"spriteoutput/ui/avatar/icon/IconCriticalChance.png", icon:"spriteoutput/ui/avatar/icon/IconCriticalChance.png",
unit: "%", unit: "%",
baseStat: "CRITRate" baseStat: "CRITRate"
}, },
"CriticalDamageBase": { "CriticalDamageBase": {
name:"CRIT DMG", name:"CRIT DMG",
icon:"spriteoutput/ui/avatar/icon/IconCriticalDamage.png", icon:"spriteoutput/ui/avatar/icon/IconCriticalDamage.png",
unit: "%", unit: "%",
baseStat: "CRITDmg" baseStat: "CRITDmg"
}, },
"HealRatioBase": { "HealRatioBase": {
name:"Outgoing Healing Boost", name:"Outgoing Healing Boost",
icon:"spriteoutput/ui/avatar/icon/IconHealRatio.png", icon:"spriteoutput/ui/avatar/icon/IconHealRatio.png",
unit: "%", unit: "%",
baseStat: "HealBoost" baseStat: "HealBoost"
}, },
"StatusProbabilityBase": { "StatusProbabilityBase": {
name:"Effect Hit Rate", name:"Effect Hit Rate",
icon:"spriteoutput/ui/avatar/icon/IconStatusProbability.png", icon:"spriteoutput/ui/avatar/icon/IconStatusProbability.png",
unit: "%", unit: "%",
baseStat: "EffectHitRate" baseStat: "EffectHitRate"
}, },
"StatusResistanceBase": { "StatusResistanceBase": {
name:"Effect RES", name:"Effect RES",
icon:"spriteoutput/ui/avatar/icon/IconStatusResistance.png", icon:"spriteoutput/ui/avatar/icon/IconStatusResistance.png",
unit: "%", unit: "%",
baseStat: "EffectRES" baseStat: "EffectRES"
}, },
"BreakDamageAddedRatioBase": { "BreakDamageAddedRatioBase": {
name:"Break Effect", name:"Break Effect",
icon:"spriteoutput/ui/avatar/icon/IconBreakUp.png", icon:"spriteoutput/ui/avatar/icon/IconBreakUp.png",
unit: "%", unit: "%",
baseStat: "BreakEffect" baseStat: "BreakEffect"
}, },
"SpeedDelta": { "SpeedDelta": {
name:"SPD", name:"SPD",
icon:"spriteoutput/ui/avatar/icon/IconSpeed.png", icon:"spriteoutput/ui/avatar/icon/IconSpeed.png",
unit: "", unit: "",
baseStat: "SPD" baseStat: "SPD"
}, },
"PhysicalAddedRatio": { "PhysicalAddedRatio": {
name:"Physical DMG Boost", name:"Physical DMG Boost",
icon:"spriteoutput/ui/avatar/icon/IconPhysicalAddedRatio.png", icon:"spriteoutput/ui/avatar/icon/IconPhysicalAddedRatio.png",
unit: "%", unit: "%",
baseStat: "PhysicalAdd" baseStat: "PhysicalAdd"
}, },
"FireAddedRatio": { "FireAddedRatio": {
name:"Fire DMG Boost", name:"Fire DMG Boost",
icon:"spriteoutput/ui/avatar/icon/IconFireAddedRatio.png", icon:"spriteoutput/ui/avatar/icon/IconFireAddedRatio.png",
unit: "%", unit: "%",
baseStat: "FireAdd" baseStat: "FireAdd"
}, },
"IceAddedRatio": { "IceAddedRatio": {
name:"Ice DMG Boost", name:"Ice DMG Boost",
icon:"spriteoutput/ui/avatar/icon/IconIceAddedRatio.png", icon:"spriteoutput/ui/avatar/icon/IconIceAddedRatio.png",
unit: "%", unit: "%",
baseStat: "IceAdd" baseStat: "IceAdd"
}, },
"ThunderAddedRatio": { "ThunderAddedRatio": {
name:"Thunder DMG Boost", name:"Thunder DMG Boost",
icon:"spriteoutput/ui/avatar/icon/IconThunderAddedRatio.png", icon:"spriteoutput/ui/avatar/icon/IconThunderAddedRatio.png",
unit: "%", unit: "%",
baseStat: "ThunderAdd" baseStat: "ThunderAdd"
}, },
"WindAddedRatio": { "WindAddedRatio": {
name:"Wind DMG Boost", name:"Wind DMG Boost",
icon:"spriteoutput/ui/avatar/icon/IconWindAddedRatio.png", icon:"spriteoutput/ui/avatar/icon/IconWindAddedRatio.png",
unit: "%", unit: "%",
baseStat: "WindAdd" baseStat: "WindAdd"
}, },
"QuantumAddedRatio": { "QuantumAddedRatio": {
name:"Quantum DMG Boost", name:"Quantum DMG Boost",
icon:"spriteoutput/ui/avatar/icon/IconQuantumAddedRatio.png", icon:"spriteoutput/ui/avatar/icon/IconQuantumAddedRatio.png",
unit: "%", unit: "%",
baseStat: "QuantumAdd" baseStat: "QuantumAdd"
}, },
"ImaginaryAddedRatio": { "ImaginaryAddedRatio": {
name:"Imaginary DMG Boost", name:"Imaginary DMG Boost",
icon:"spriteoutput/ui/avatar/icon/IconImaginaryAddedRatio.png", icon:"spriteoutput/ui/avatar/icon/IconImaginaryAddedRatio.png",
unit: "%", unit: "%",
baseStat: "ImaginaryAdd" baseStat: "ImaginaryAdd"
}, },
"ElationDamageAddedRatioBase": { "ElationDamageAddedRatioBase": {
name:"Elation DMG Boost", name:"Elation DMG Boost",
icon:"spriteoutput/ui/avatar/icon/IconJoy.png", icon:"spriteoutput/ui/avatar/icon/IconJoy.png",
unit: "%", unit: "%",
baseStat: "ElationAdd" baseStat: "ElationAdd"
}, },
"SPRatioBase": { "SPRatioBase": {
name:"Energy Regeneration Rate", name:"Energy Regeneration Rate",
icon:"spriteoutput/ui/avatar/icon/IconEnergyRecovery.png", icon:"spriteoutput/ui/avatar/icon/IconEnergyRecovery.png",
unit: "%", unit: "%",
baseStat: "EnergyRate" baseStat: "EnergyRate"
} }
} }
export const ratioStats = [ export const ratioStats = [
"HPAddedRatio", "HPAddedRatio",
"AttackAddedRatio", "AttackAddedRatio",
"DefenceAddedRatio", "DefenceAddedRatio",
"SpeedAddedRatio", "SpeedAddedRatio",
] ]
export const mappingRelicSlot: Record<string, string> = { export const mappingRelicSlot: Record<string, string> = {
"1": "HEAD", "1": "HEAD",
"2": "HAND", "2": "HAND",
"3": "BODY", "3": "BODY",
"4": "FOOT", "4": "FOOT",
"5": "NECK", "5": "NECK",
"6": "OBJECT", "6": "OBJECT",
} }
export const themeColors: Record<string, { bg: string; bgHover: string; text: string; border: string }> = { export const themeColors: Record<string, { bg: string; bgHover: string; text: string; border: string }> = {
winter: { winter: {
bg: '#ffffff', bg: '#ffffff',
bgHover: '#f1f5f9', bgHover: '#f1f5f9',
text: '#3a4f6b', text: '#3a4f6b',
border: '#cbd5e1' border: '#cbd5e1'
}, },
night: { night: {
bg: '#1d232a', bg: '#1d232a',
bgHover: '#2a323c', bgHover: '#2a323c',
text: '#cbcdd1', text: '#cbcdd1',
border: '#3f3f46' border: '#3f3f46'
}, },
cupcake: { cupcake: {
bg: '#faf7f5', bg: '#faf7f5',
bgHover: '#f3eae6', bgHover: '#f3eae6',
text: '#281333', text: '#281333',
border: '#e5d3cb' border: '#e5d3cb'
}, },
coffee: { coffee: {
bg: '#20161f', bg: '#20161f',
bgHover: '#2a1d29', bgHover: '#2a1d29',
text: '#c4a051', text: '#c4a051',
border: '#3a2a36' border: '#3a2a36'
} }
} }
+1147 -1147
View File
File diff suppressed because it is too large Load Diff
+104 -104
View File
@@ -1,105 +1,105 @@
"use client" "use client"
import { SendDataThroughProxy, SendDataToServer } from "@/lib/api/api" import { SendDataThroughProxy, SendDataToServer } from "@/lib/api/api"
import useConnectStore from "@/stores/connectStore" import useConnectStore from "@/stores/connectStore"
import useUserDataStore from "@/stores/userDataStore" import useUserDataStore from "@/stores/userDataStore"
import { converterToFreeSRJson } from "./converterToFreeSRJson" import { converterToFreeSRJson } from "./converterToFreeSRJson"
import { psResponseSchema } from "@/zod" import { psResponseSchema } from "@/zod"
import useGlobalStore from "@/stores/globalStore" import useGlobalStore from "@/stores/globalStore"
import { ActionResult, ExtraData, ProxyPayload, ProxyResponse, PSConnectType, PSResponse } from "@/types" import { ActionResult, ExtraData, ProxyPayload, ProxyResponse, PSConnectType, PSResponse } from "@/types"
const getUrlQuery = (connectionType: PSConnectType | string, serverUrl: string): string => { const getUrlQuery = (connectionType: PSConnectType | string, serverUrl: string): string => {
if (connectionType === PSConnectType.FireflyGo) return "http://localhost:21000/sync" if (connectionType === PSConnectType.FireflyGo) return "http://localhost:21000/sync"
if (connectionType === PSConnectType.RobinSR) return "http://localhost:21000/srtools" if (connectionType === PSConnectType.RobinSR) return "http://localhost:21000/srtools"
if (!serverUrl.startsWith("http://") && !serverUrl.startsWith("https://")) { if (!serverUrl.startsWith("http://") && !serverUrl.startsWith("https://")) {
return `http://${serverUrl}` return `http://${serverUrl}`
} }
return serverUrl return serverUrl
} }
const handleProxyRequest = async (payload: ProxyPayload): Promise<ActionResult> => { const handleProxyRequest = async (payload: ProxyPayload): Promise<ActionResult> => {
const response = await SendDataThroughProxy({ const response = await SendDataThroughProxy({
data: { ...payload, method: "POST" } data: { ...payload, method: "POST" }
}) as ProxyResponse | Error }) as ProxyResponse | Error
if (response instanceof Error) { if (response instanceof Error) {
return { success: false, message: response.message } return { success: false, message: response.message }
} }
if (response.error) { if (response.error) {
return { success: false, message: response.error } return { success: false, message: response.error }
} }
const parsed = psResponseSchema.safeParse(response.data) const parsed = psResponseSchema.safeParse(response.data)
if (!parsed.success) { if (!parsed.success) {
return { success: false, message: "Invalid response schema" } return { success: false, message: "Invalid response schema" }
} }
return { success: true, message: "" } return { success: true, message: "" }
} }
const handleDirectServerResponse = ( const handleDirectServerResponse = (
response: PSResponse | string, response: PSResponse | string,
setIsConnectPS: (val: boolean) => void, setIsConnectPS: (val: boolean) => void,
onSuccess: (extraData?: ExtraData) => void onSuccess: (extraData?: ExtraData) => void
): ActionResult => { ): ActionResult => {
if (typeof response === "string") { if (typeof response === "string") {
setIsConnectPS(false) setIsConnectPS(false)
return { success: false, message: response } return { success: false, message: response }
} }
if (response.status !== 200) { if (response.status !== 200) {
setIsConnectPS(false) setIsConnectPS(false)
return { success: false, message: response.message } return { success: false, message: response.message }
} }
setIsConnectPS(true) setIsConnectPS(true)
onSuccess(response?.extra_data) onSuccess(response?.extra_data)
return { success: true, message: "" } return { success: true, message: "" }
} }
export const connectToPS = async (): Promise<ActionResult> => { export const connectToPS = async (): Promise<ActionResult> => {
const { connectionType, privateType, serverUrl, username, password } = useConnectStore.getState() const { connectionType, privateType, serverUrl, username, password } = useConnectStore.getState()
const { setExtraData, setIsConnectPS } = useGlobalStore.getState() const { setExtraData, setIsConnectPS } = useGlobalStore.getState()
if (connectionType === "Other" && privateType === "Server") { if (connectionType === "Other" && privateType === "Server") {
return handleProxyRequest({ username, password, serverUrl, data: undefined }) return handleProxyRequest({ username, password, serverUrl, data: undefined })
} }
const urlQuery = getUrlQuery(connectionType, serverUrl) const urlQuery = getUrlQuery(connectionType, serverUrl)
const response = await SendDataToServer(username, password, urlQuery, undefined) const response = await SendDataToServer(username, password, urlQuery, undefined)
return handleDirectServerResponse(response, setIsConnectPS, (extraData) => { return handleDirectServerResponse(response, setIsConnectPS, (extraData) => {
setExtraData(extraData) setExtraData(extraData)
}) })
} }
export const syncDataToPS = async (): Promise<ActionResult> => { export const syncDataToPS = async (): Promise<ActionResult> => {
const { connectionType, privateType, serverUrl, username, password } = useConnectStore.getState() const { connectionType, privateType, serverUrl, username, password } = useConnectStore.getState()
const { extraData, setIsConnectPS, setExtraData, isEnableChangePath, isEnableLua } = useGlobalStore.getState() const { extraData, setIsConnectPS, setExtraData, isEnableChangePath, isEnableLua } = useGlobalStore.getState()
const { avatars, battle_type, moc_config, pf_config, as_config, ce_config, peak_config } = useUserDataStore.getState() const { avatars, battle_type, moc_config, pf_config, as_config, ce_config, peak_config } = useUserDataStore.getState()
const data = converterToFreeSRJson(avatars, battle_type, moc_config, pf_config, as_config, ce_config, peak_config) const data = converterToFreeSRJson(avatars, battle_type, moc_config, pf_config, as_config, ce_config, peak_config)
if (connectionType === "Other" && privateType === "Server") { if (connectionType === "Other" && privateType === "Server") {
return handleProxyRequest({ username, password, serverUrl, data }) return handleProxyRequest({ username, password, serverUrl, data })
} }
const urlQuery = getUrlQuery(connectionType, serverUrl) const urlQuery = getUrlQuery(connectionType, serverUrl)
const payloadExtra: PSResponse['extra_data'] = structuredClone(extraData) const payloadExtra: PSResponse['extra_data'] = structuredClone(extraData)
if (payloadExtra) { if (payloadExtra) {
if (!isEnableChangePath) payloadExtra.multi_path = undefined if (!isEnableChangePath) payloadExtra.multi_path = undefined
if (!isEnableLua) payloadExtra.lua = null if (!isEnableLua) payloadExtra.lua = null
} }
const response = await SendDataToServer(username, password, urlQuery, data, payloadExtra) const response = await SendDataToServer(username, password, urlQuery, data, payloadExtra)
return handleDirectServerResponse(response, setIsConnectPS, (responseExtraData) => { return handleDirectServerResponse(response, setIsConnectPS, (responseExtraData) => {
const newData = structuredClone(responseExtraData) const newData = structuredClone(responseExtraData)
if (newData) { if (newData) {
newData.lua = extraData?.lua || null newData.lua = extraData?.lua || null
} }
setExtraData(newData) setExtraData(newData)
}) })
} }
+14 -14
View File
@@ -1,15 +1,15 @@
export function convertToRoman(num: number): string { export function convertToRoman(num: number): string {
const roman: [number, string][] = [ const roman: [number, string][] = [
[1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'], [1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'],
[100, 'C'], [90, 'XC'], [50, 'L'], [40, 'XL'], [100, 'C'], [90, 'XC'], [50, 'L'], [40, 'XL'],
[10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I'] [10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I']
]; ];
let result = ''; let result = '';
for (const [val, sym] of roman) { for (const [val, sym] of roman) {
while (num >= val) { while (num >= val) {
result += sym; result += sym;
num -= val; num -= val;
} }
} }
return result; return result;
} }
+119 -119
View File
@@ -1,89 +1,89 @@
import useConnectStore from "@/stores/connectStore"; import useConnectStore from "@/stores/connectStore";
import useDetailDataStore from "@/stores/detailDataStore"; import useDetailDataStore from "@/stores/detailDataStore";
import { ASConfigStore, AvatarJson, AvatarStore, BattleConfigJson, CEConfigStore, FreeSRJson, LightconeJson, MOCConfigStore, PEAKConfigStore, PFConfigStore, PSConnectType, RelicJson } from "@/types"; import { ASConfigStore, AvatarJson, AvatarStore, BattleConfigJson, CEConfigStore, FreeSRJson, LightconeJson, MOCConfigStore, PEAKConfigStore, PFConfigStore, PSConnectType, RelicJson } from "@/types";
export function converterToFreeSRJson( export function converterToFreeSRJson(
avatars: Record<string, AvatarStore>, avatars: Record<string, AvatarStore>,
battle_type: string, battle_type: string,
moc_config: MOCConfigStore, moc_config: MOCConfigStore,
pf_config: PFConfigStore, pf_config: PFConfigStore,
as_config: ASConfigStore, as_config: ASConfigStore,
ce_config: CEConfigStore, ce_config: CEConfigStore,
peak_config: PEAKConfigStore, peak_config: PEAKConfigStore,
): FreeSRJson { ): FreeSRJson {
const { skillConfig } = useDetailDataStore.getState() const { skillConfig } = useDetailDataStore.getState()
const { connectionType } = useConnectStore.getState() const { connectionType } = useConnectStore.getState()
const lightcones: LightconeJson[] = [] const lightcones: LightconeJson[] = []
const relics: RelicJson[] = [] const relics: RelicJson[] = []
let battleJson: BattleConfigJson let battleJson: BattleConfigJson
if (battle_type === "MOC") { if (battle_type === "MOC") {
battleJson = { battleJson = {
battle_type: battle_type, battle_type: battle_type,
blessings: moc_config.blessings, blessings: moc_config.blessings,
custom_stats: [], custom_stats: [],
cycle_count: moc_config.cycle_count, cycle_count: moc_config.cycle_count,
stage_id: moc_config.stage_id, stage_id: moc_config.stage_id,
path_resonance_id: 0, path_resonance_id: 0,
monsters: moc_config.monsters, monsters: moc_config.monsters,
} }
} else if (battle_type === "PF") { } else if (battle_type === "PF") {
battleJson = { battleJson = {
battle_type: battle_type, battle_type: battle_type,
blessings: pf_config.blessings, blessings: pf_config.blessings,
custom_stats: [], custom_stats: [],
cycle_count: pf_config.cycle_count, cycle_count: pf_config.cycle_count,
stage_id: pf_config.stage_id, stage_id: pf_config.stage_id,
path_resonance_id: 0, path_resonance_id: 0,
monsters: pf_config.monsters, monsters: pf_config.monsters,
} }
} else if (battle_type === "AS") { } else if (battle_type === "AS") {
battleJson = { battleJson = {
battle_type: battle_type, battle_type: battle_type,
blessings: as_config.blessings, blessings: as_config.blessings,
custom_stats: [], custom_stats: [],
cycle_count: as_config.cycle_count, cycle_count: as_config.cycle_count,
stage_id: as_config.stage_id, stage_id: as_config.stage_id,
path_resonance_id: 0, path_resonance_id: 0,
monsters: as_config.monsters, monsters: as_config.monsters,
} }
} else if (battle_type === "CE") { } else if (battle_type === "CE") {
battleJson = { battleJson = {
battle_type: connectionType === PSConnectType.FireflyGo ? battle_type : "DEFAULT", battle_type: connectionType === PSConnectType.FireflyGo ? battle_type : "DEFAULT",
blessings: ce_config.blessings, blessings: ce_config.blessings,
custom_stats: [], custom_stats: [],
cycle_count: ce_config.cycle_count, cycle_count: ce_config.cycle_count,
stage_id: ce_config.stage_id, stage_id: ce_config.stage_id,
path_resonance_id: 0, path_resonance_id: 0,
monsters: ce_config.monsters, monsters: ce_config.monsters,
} }
} else if (battle_type === "PEAK") { } else if (battle_type === "PEAK") {
battleJson = { battleJson = {
battle_type: connectionType === PSConnectType.FireflyGo ? battle_type : "DEFAULT", battle_type: connectionType === PSConnectType.FireflyGo ? battle_type : "DEFAULT",
blessings: peak_config.blessings, blessings: peak_config.blessings,
custom_stats: [], custom_stats: [],
cycle_count: peak_config.cycle_count, cycle_count: peak_config.cycle_count,
stage_id: peak_config.stage_id, stage_id: peak_config.stage_id,
path_resonance_id: 0, path_resonance_id: 0,
monsters: peak_config.monsters, monsters: peak_config.monsters,
} }
} else { } else {
battleJson = { battleJson = {
battle_type: battle_type, battle_type: battle_type,
blessings: [], blessings: [],
custom_stats: [], custom_stats: [],
cycle_count: 0, cycle_count: 0,
stage_id: 0, stage_id: 0,
path_resonance_id: 0, path_resonance_id: 0,
monsters: [], monsters: [],
} }
} }
const avatarsJson: { [key: string]: AvatarJson } = {} const avatarsJson: { [key: string]: AvatarJson } = {}
let internalUidLightcone = 0 let internalUidLightcone = 0
let internalUidRelic = 0 let internalUidRelic = 0
Object.entries(avatars ?? {}).forEach(([avatarId, avatar]) => { Object.entries(avatars ?? {}).forEach(([avatarId, avatar]) => {
if (!avatar) return if (!avatar) return
@@ -91,10 +91,10 @@ export function converterToFreeSRJson(
for (const [skillId, level] of Object.entries(avatar?.data?.skills || {})) { for (const [skillId, level] of Object.entries(avatar?.data?.skills || {})) {
if (skillConfig?.[skillId]) { if (skillConfig?.[skillId]) {
skillsByAnchorType[skillConfig[skillId].IndexSlot] = level > skillConfig[skillId].MaxLevel ? skillConfig[skillId].MaxLevel : level skillsByAnchorType[skillConfig[skillId].IndexSlot] = level > skillConfig[skillId].MaxLevel ? skillConfig[skillId].MaxLevel : level
} }
} }
avatarsJson[avatarId] = { avatarsJson[avatarId] = {
owner_uid: Number(avatar.owner_uid || 0), owner_uid: Number(avatar.owner_uid || 0),
avatar_id: Number(avatar.avatar_id || 0), avatar_id: Number(avatar.avatar_id || 0),
data: { data: {
rank: Number(avatar.data?.rank || 0), rank: Number(avatar.data?.rank || 0),
@@ -115,21 +115,21 @@ export function converterToFreeSRJson(
const newLightcone: LightconeJson = { const newLightcone: LightconeJson = {
level: Number(currentProfile.lightcone.level || 0), level: Number(currentProfile.lightcone.level || 0),
item_id: Number(currentProfile.lightcone.item_id || 0), item_id: Number(currentProfile.lightcone.item_id || 0),
rank: Number(currentProfile.lightcone.rank || 0), rank: Number(currentProfile.lightcone.rank || 0),
promotion: Number(currentProfile.lightcone.promotion || 0), promotion: Number(currentProfile.lightcone.promotion || 0),
internal_uid: internalUidLightcone, internal_uid: internalUidLightcone,
equip_avatar: Number(avatar.avatar_id || 0), equip_avatar: Number(avatar.avatar_id || 0),
} }
internalUidLightcone++ internalUidLightcone++
lightcones.push(newLightcone) lightcones.push(newLightcone)
} }
if (currentProfile.relics) { if (currentProfile.relics) {
["1", "2", "3", "4", "5", "6"].forEach(slot => { ["1", "2", "3", "4", "5", "6"].forEach(slot => {
const relic = currentProfile.relics[slot] const relic = currentProfile.relics[slot]
if (relic && relic.relic_id !== 0) { if (relic && relic.relic_id !== 0) {
const newRelic: RelicJson = { const newRelic: RelicJson = {
level: Number(relic.level || 0), level: Number(relic.level || 0),
relic_id: Number(relic.relic_id || 0), relic_id: Number(relic.relic_id || 0),
relic_set_id: Number(relic.relic_set_id || 0), relic_set_id: Number(relic.relic_set_id || 0),
main_affix_id: Number(relic.main_affix_id || 0), main_affix_id: Number(relic.main_affix_id || 0),
@@ -137,18 +137,18 @@ export function converterToFreeSRJson(
internal_uid: internalUidRelic, internal_uid: internalUidRelic,
equip_avatar: Number(avatar.avatar_id || 0), equip_avatar: Number(avatar.avatar_id || 0),
} }
internalUidRelic++ internalUidRelic++
relics.push(newRelic) relics.push(newRelic)
} }
}) })
} }
}) })
return { return {
lightcones, lightcones,
relics, relics,
avatars: avatarsJson, avatars: avatarsJson,
battle_config: battleJson, battle_config: battleJson,
} }
} }
+61 -61
View File
@@ -1,62 +1,62 @@
import { listCurrentLanguageApi } from "@/constant/constant"; import { listCurrentLanguageApi } from "@/constant/constant";
import { AvatarDetail } from "@/types"; import { AvatarDetail } from "@/types";
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
type TFunc = ReturnType<typeof useTranslations> type TFunc = ReturnType<typeof useTranslations>
function cleanText(text: string): string { function cleanText(text: string): string {
if (!text) return "" if (!text) return ""
return text.replace(/<unbreak>(.*?)<\/unbreak>/g, "$1") return text.replace(/<unbreak>(.*?)<\/unbreak>/g, "$1")
} }
export function getNameChar( export function getNameChar(
locale: string, locale: string,
t: TFunc, t: TFunc,
data: AvatarDetail | undefined data: AvatarDetail | undefined
): string { ): string {
if (!data) return ""; if (!data) return "";
if (!Object.prototype.hasOwnProperty.call(listCurrentLanguageApi, locale)) { if (!Object.prototype.hasOwnProperty.call(listCurrentLanguageApi, locale)) {
return ""; return "";
} }
const langKey = listCurrentLanguageApi[locale as keyof typeof listCurrentLanguageApi].toLowerCase(); const langKey = listCurrentLanguageApi[locale as keyof typeof listCurrentLanguageApi].toLowerCase();
let text = data.Name[langKey] ?? ""; let text = data.Name[langKey] ?? "";
if (!text) { if (!text) {
text = data.Name["en"] ?? ""; text = data.Name["en"] ?? "";
} }
if (data.ID > 8000) { if (data.ID > 8000) {
text = `${t("trailblazer")}${t(data?.BaseType?.toLowerCase() ?? "")}`; text = `${t("trailblazer")}${t(data?.BaseType?.toLowerCase() ?? "")}`;
} }
return cleanText(text) return cleanText(text)
} }
export function getLocaleName(locale: string, data: Record<string, string> | undefined | null): string { export function getLocaleName(locale: string, data: Record<string, string> | undefined | null): string {
if (!data) { if (!data) {
return "" return ""
} }
if (!Object.prototype.hasOwnProperty.call(listCurrentLanguageApi, locale)) { if (!Object.prototype.hasOwnProperty.call(listCurrentLanguageApi, locale)) {
return "" return ""
} }
const langKey = listCurrentLanguageApi[locale as keyof typeof listCurrentLanguageApi].toLowerCase(); const langKey = listCurrentLanguageApi[locale as keyof typeof listCurrentLanguageApi].toLowerCase();
let text = data[langKey] ?? ""; let text = data[langKey] ?? "";
if (!text) { if (!text) {
text = data["en"] ?? ""; text = data["en"] ?? "";
} }
return cleanText(text) return cleanText(text)
} }
export function parseRuby(text: string): string { export function parseRuby(text: string): string {
const rubyRegex = /\{RUBY_B#(.*?)\}(.*?)\{RUBY_E#\}/gs; const rubyRegex = /\{RUBY_B#(.*?)\}(.*?)\{RUBY_E#\}/gs;
return text.replace(rubyRegex, (_match, furigana, kanji) => { return text.replace(rubyRegex, (_match, furigana, kanji) => {
return `<ruby>${kanji}<rt>${furigana}</rt></ruby>`; return `<ruby>${kanji}<rt>${furigana}</rt></ruby>`;
}); });
} }
+19 -19
View File
@@ -1,23 +1,23 @@
import { AvatarDetail } from "@/types"; import { AvatarDetail } from "@/types";
export function getSkillTree(avatarSelected: AvatarDetail | null, enhanced: string) { export function getSkillTree(avatarSelected: AvatarDetail | null, enhanced: string) {
if (!avatarSelected) return null; if (!avatarSelected) return null;
if (enhanced != "" && !!avatarSelected?.Enhanced?.[enhanced]?.SkillTrees) { if (enhanced != "" && !!avatarSelected?.Enhanced?.[enhanced]?.SkillTrees) {
return Object.values(avatarSelected?.Enhanced?.[enhanced]?.SkillTrees).reduce((acc, dataPointEntry) => { return Object.values(avatarSelected?.Enhanced?.[enhanced]?.SkillTrees).reduce((acc, dataPointEntry) => {
const firstEntry = Object.values(dataPointEntry)[0]; const firstEntry = Object.values(dataPointEntry)[0];
if (firstEntry) { if (firstEntry) {
acc[firstEntry.PointID] = firstEntry.MaxLevel; acc[firstEntry.PointID] = firstEntry.MaxLevel;
} }
return acc; return acc;
}, {} as Record<string, number>) }, {} as Record<string, number>)
} }
return Object.values(avatarSelected?.SkillTrees ?? {}).reduce((acc, dataPointEntry) => { return Object.values(avatarSelected?.SkillTrees ?? {}).reduce((acc, dataPointEntry) => {
const firstEntry = Object.values(dataPointEntry)[0]; const firstEntry = Object.values(dataPointEntry)[0];
if (firstEntry) { if (firstEntry) {
acc[firstEntry.PointID] = firstEntry.MaxLevel; acc[firstEntry.PointID] = firstEntry.MaxLevel;
} }
return acc; return acc;
}, {} as Record<string, number>); }, {} as Record<string, number>);
} }
+7 -7
View File
@@ -1,8 +1,8 @@
export * from "./getName" export * from "./getName"
export * from "./replaceByParam" export * from "./replaceByParam"
export * from "./converterToAvatarStore" export * from "./converterToAvatarStore"
export * from "./calcData" export * from "./calcData"
export * from "./random" export * from "./random"
export * from "./json" export * from "./json"
export * from "./convertData" export * from "./convertData"
export * from "./connect" export * from "./connect"
+23 -23
View File
@@ -8,27 +8,27 @@ export function randomPartition(sum: number, parts: number): number[] {
const raw = Array.from({ length: parts }, () => Math.random()); const raw = Array.from({ length: parts }, () => Math.random());
const total = raw.reduce((a, b) => a + b, 0); const total = raw.reduce((a, b) => a + b, 0);
const result = raw.map(r => Math.floor((r / total) * (sum - parts)) + 1); const result = raw.map(r => Math.floor((r / total) * (sum - parts)) + 1);
let diff = sum - result.reduce((a, b) => a + b, 0); let diff = sum - result.reduce((a, b) => a + b, 0);
while (diff !== 0) { while (diff !== 0) {
for (let i = 0; i < result.length && diff !== 0; i++) { for (let i = 0; i < result.length && diff !== 0; i++) {
if (diff > 0) { if (diff > 0) {
result[i]++; result[i]++;
diff--; diff--;
} else if (result[i] > 1) { } else if (result[i] > 1) {
result[i]--; result[i]--;
diff++; diff++;
} }
} }
} }
return result; return result;
} }
export function randomStep(x: number): number { export function randomStep(x: number): number {
let total = 0; let total = 0;
for (let i = 0; i < x; i++) { for (let i = 0; i < x; i++) {
total += Math.floor(Math.random() * 3); total += Math.floor(Math.random() * 3);
} }
return total; return total;
} }
+34 -34
View File
@@ -1,39 +1,39 @@
const formatValue = (value: number, format: string, floatDigits?: string, hasPercent?: boolean): string => { const formatValue = (value: number, format: string, floatDigits?: string, hasPercent?: boolean): string => {
if (format.startsWith('f')) { if (format.startsWith('f')) {
const digits = parseInt(floatDigits || "1", 10); const digits = parseInt(floatDigits || "1", 10);
const num = hasPercent ? value * 100 : value; const num = hasPercent ? value * 100 : value;
return `${num.toFixed(digits)}${hasPercent ? "%" : ""}`; return `${num.toFixed(digits)}${hasPercent ? "%" : ""}`;
} }
if (format === 'i') { if (format === 'i') {
const num = hasPercent ? value * 100 : value; const num = hasPercent ? value * 100 : value;
return `${Math.round(num)}${hasPercent ? "%" : ""}`; return `${Math.round(num)}${hasPercent ? "%" : ""}`;
} }
return String(value); return String(value);
}; };
export function replaceByParam(desc?: string, params: number[] = []): string { export function replaceByParam(desc?: string, params: number[] = []): string {
if (!desc) return ""; if (!desc) return "";
const PARAM_REGEX = /#(\d+)\[(f(\d+)|i)\](%)?/g; const PARAM_REGEX = /#(\d+)\[(f(\d+)|i)\](%)?/g;
const processor = (_match: string, index: string, format: string, digits?: string, percent?: string): string => { const processor = (_match: string, index: string, format: string, digits?: string, percent?: string): string => {
const i = parseInt(index, 10) - 1; const i = parseInt(index, 10) - 1;
const val = params[i]; const val = params[i];
return val !== undefined ? formatValue(val, format, digits, !!percent) : ""; return val !== undefined ? formatValue(val, format, digits, !!percent) : "";
}; };
let result = desc.replace(/<color=(#[0-9a-fA-F]{8})>(.*?)<\/color>/g, (_, color, inner) => { let result = desc.replace(/<color=(#[0-9a-fA-F]{8})>(.*?)<\/color>/g, (_, color, inner) => {
const processedInner = inner.replace(PARAM_REGEX, processor); const processedInner = inner.replace(PARAM_REGEX, processor);
return `<span style="color: ${color}">${processedInner}</span>`; return `<span style="color: ${color}">${processedInner}</span>`;
}); });
result = result.replace(/<unbreak>(.*?)<\/unbreak>/g, (_, inner) => { result = result.replace(/<unbreak>(.*?)<\/unbreak>/g, (_, inner) => {
return inner.replace(PARAM_REGEX, processor); return inner.replace(PARAM_REGEX, processor);
}); });
result = result.replace(PARAM_REGEX, processor); result = result.replace(PARAM_REGEX, processor);
return result.split("\\n").join("<br/>"); return result.split("\\n").join("<br/>");
} }
+1 -1
View File
@@ -1 +1 @@
export * from "./useChangeTheme"; export * from "./useChangeTheme";
+3 -3
View File
@@ -1,4 +1,4 @@
import { useContext } from "react"; import { useContext } from "react";
import { ThemeContext } from "@/components/themeController"; import { ThemeContext } from "@/components/themeController";
export const useChangeTheme = () => useContext(ThemeContext); export const useChangeTheme = () => useContext(ThemeContext);
+141 -141
View File
@@ -1,142 +1,142 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { ASGroupDetail, ChangelogItemType, AvatarDetail, FreeSRJson, LightConeDetail, MOCGroupDetail, MonsterDetail, PeakGroupDetail, PFGroupDetail, PSResponse, RelicSetDetail } from "@/types"; import { ASGroupDetail, ChangelogItemType, AvatarDetail, FreeSRJson, LightConeDetail, MOCGroupDetail, MonsterDetail, PeakGroupDetail, PFGroupDetail, PSResponse, RelicSetDetail } from "@/types";
import axios from 'axios'; import axios from 'axios';
import { psResponseSchema } from "@/zod"; import { psResponseSchema } from "@/zod";
import { ExtraData, Metadata } from "@/types"; import { ExtraData, Metadata } from "@/types";
export async function getMetadataApi(): Promise<Metadata> { export async function getMetadataApi(): Promise<Metadata> {
try { try {
const res = await axios.get<Metadata>(`/api/data/metadata`); const res = await axios.get<Metadata>(`/api/data/metadata`);
return res.data as Metadata; return res.data as Metadata;
} catch (error: unknown) { } catch (error: unknown) {
console.error('Failed to fetch metadata:', error); console.error('Failed to fetch metadata:', error);
return { return {
BaseType: {}, BaseType: {},
DamageType: {}, DamageType: {},
MainAffix: {}, MainAffix: {},
SubAffix: {}, SubAffix: {},
SkillConfig: {}, SkillConfig: {},
Stage: {}, Stage: {},
HardLevelConfig: {}, HardLevelConfig: {},
EliteConfig: {} EliteConfig: {}
}; };
} }
} }
export async function getAvatarListApi(): Promise<Record<string, AvatarDetail>> { export async function getAvatarListApi(): Promise<Record<string, AvatarDetail>> {
try { try {
const res = await axios.get<Record<string, AvatarDetail>>(`/api/data/avatar`); const res = await axios.get<Record<string, AvatarDetail>>(`/api/data/avatar`);
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch Avatars:', error); console.error('Failed to fetch Avatars:', error);
return {}; return {};
} }
} }
export async function getLightconeListApi(): Promise<Record<string, LightConeDetail>> { export async function getLightconeListApi(): Promise<Record<string, LightConeDetail>> {
try { try {
const res = await axios.get<Record<string, LightConeDetail>>(`/api/data/lightcone`); const res = await axios.get<Record<string, LightConeDetail>>(`/api/data/lightcone`);
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch lightcones:', error); console.error('Failed to fetch lightcones:', error);
return {}; return {};
} }
} }
export async function getRelicSetListApi(): Promise<Record<string, RelicSetDetail>> { export async function getRelicSetListApi(): Promise<Record<string, RelicSetDetail>> {
try { try {
const res = await axios.get<Record<string, RelicSetDetail>>(`/api/data/relic`); const res = await axios.get<Record<string, RelicSetDetail>>(`/api/data/relic`);
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch relics:', error); console.error('Failed to fetch relics:', error);
return {}; return {};
} }
} }
export async function getMonsterListApi(): Promise<Record<string, MonsterDetail>> { export async function getMonsterListApi(): Promise<Record<string, MonsterDetail>> {
try { try {
const res = await axios.get<Record<string, MonsterDetail>>(`/api/data/monster`); const res = await axios.get<Record<string, MonsterDetail>>(`/api/data/monster`);
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch monster:', error); console.error('Failed to fetch monster:', error);
return {}; return {};
} }
} }
export async function getASEventListApi(): Promise<Record<string, ASGroupDetail>> { export async function getASEventListApi(): Promise<Record<string, ASGroupDetail>> {
try { try {
const res = await axios.get<Record<string, ASGroupDetail>>(`/api/data/as`); const res = await axios.get<Record<string, ASGroupDetail>>(`/api/data/as`);
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch AS:', error); console.error('Failed to fetch AS:', error);
return {}; return {};
} }
} }
export async function getPFEventListApi(): Promise<Record<string, PFGroupDetail>> { export async function getPFEventListApi(): Promise<Record<string, PFGroupDetail>> {
try { try {
const res = await axios.get<Record<string, PFGroupDetail>>(`/api/data/pf`); const res = await axios.get<Record<string, PFGroupDetail>>(`/api/data/pf`);
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch PF:', error); console.error('Failed to fetch PF:', error);
return {}; return {};
} }
} }
export async function getMOCEventListApi(): Promise<Record<string, MOCGroupDetail>> { export async function getMOCEventListApi(): Promise<Record<string, MOCGroupDetail>> {
try { try {
const res = await axios.get<Record<string, MOCGroupDetail>>(`/api/data/moc`); const res = await axios.get<Record<string, MOCGroupDetail>>(`/api/data/moc`);
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch MOC:', error); console.error('Failed to fetch MOC:', error);
return {}; return {};
} }
} }
export async function getPeakEventListApi(): Promise<Record<string, PeakGroupDetail>> { export async function getPeakEventListApi(): Promise<Record<string, PeakGroupDetail>> {
try { try {
const res = await axios.get<Record<string, PeakGroupDetail>>(`/api/data/peak`); const res = await axios.get<Record<string, PeakGroupDetail>>(`/api/data/peak`);
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch peak:', error); console.error('Failed to fetch peak:', error);
return {}; return {};
} }
} }
export async function getChangelog(): Promise<ChangelogItemType[]> { export async function getChangelog(): Promise<ChangelogItemType[]> {
try { try {
const res = await axios.get<ChangelogItemType[]>(`/api/data/changelog`); const res = await axios.get<ChangelogItemType[]>(`/api/data/changelog`);
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch monster:', error); console.error('Failed to fetch monster:', error);
return []; return [];
} }
} }
export async function SendDataToServer( export async function SendDataToServer(
username: string, username: string,
password: string, password: string,
serverUrl: string, serverUrl: string,
data?: FreeSRJson, data?: FreeSRJson,
extraData?: ExtraData extraData?: ExtraData
): Promise<PSResponse | string> { ): Promise<PSResponse | string> {
try { try {
const response = await axios.post(`${serverUrl}`, { username, password, data, extra_data: extraData }) const response = await axios.post(`${serverUrl}`, { username, password, data, extra_data: extraData })
const parsed = psResponseSchema.safeParse(response.data) const parsed = psResponseSchema.safeParse(response.data)
if (!parsed.success) { if (!parsed.success) {
return "Invalid response schema"; return "Invalid response schema";
} }
return parsed.data; return parsed.data;
} catch (error: any) { } catch (error: any) {
return error?.message || "Unknown error"; return error?.message || "Unknown error";
} }
} }
export async function SendDataThroughProxy({ data }: { data: any }) { export async function SendDataThroughProxy({ data }: { data: any }) {
try { try {
const response = await axios.post(`/api/proxy`, { ...data }) const response = await axios.post(`/api/proxy`, { ...data })
return response.data; return response.data;
} catch (error: any) { } catch (error: any) {
return error; return error;
} }
} }
+37 -37
View File
@@ -1,38 +1,38 @@
import { readFileSync, readdirSync } from "fs" import { readFileSync, readdirSync } from "fs"
import path from "path" import path from "path"
type CacheItem = { type CacheItem = {
buf: Uint8Array buf: Uint8Array
type: "json" | "br" type: "json" | "br"
} }
const cache = new Map<string, CacheItem>() const cache = new Map<string, CacheItem>()
const dir = path.join(process.cwd(), "data") const dir = path.join(process.cwd(), "data")
for (const f of readdirSync(dir)) { for (const f of readdirSync(dir)) {
const file = path.join(dir, f) const file = path.join(dir, f)
if (f.endsWith(".json.br")) { if (f.endsWith(".json.br")) {
const name = f.replace(".json.br", "") const name = f.replace(".json.br", "")
const buf = new Uint8Array(readFileSync(file)) const buf = new Uint8Array(readFileSync(file))
cache.set(name, { cache.set(name, {
buf, buf,
type: "br" type: "br"
}) })
} }
if (f.endsWith(".json")) { if (f.endsWith(".json")) {
const name = f.replace(".json", "") const name = f.replace(".json", "")
const buf = new Uint8Array(readFileSync(file)) const buf = new Uint8Array(readFileSync(file))
cache.set(name, { cache.set(name, {
buf, buf,
type: "json" type: "json"
}) })
} }
} }
export function getDataCache(name: string) { export function getDataCache(name: string) {
return cache.get(name) return cache.get(name)
} }
+9 -9
View File
@@ -1,10 +1,10 @@
export * from "./useFetchMetaData"; export * from "./useFetchMetaData";
export * from "./useFetchAvatarData"; export * from "./useFetchAvatarData";
export * from "./useFetchLightconeData"; export * from "./useFetchLightconeData";
export * from "./useFetchRelicData"; export * from "./useFetchRelicData";
export * from "./useFetchMonsterData"; export * from "./useFetchMonsterData";
export * from "./useFetchPFData"; export * from "./useFetchPFData";
export * from "./useFetchMOCData"; export * from "./useFetchMOCData";
export * from "./useFetchASData"; export * from "./useFetchASData";
export * from "./useFetchPEAKData"; export * from "./useFetchPEAKData";
export * from "./useFetchChangelog"; export * from "./useFetchChangelog";
+26 -26
View File
@@ -1,26 +1,26 @@
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getASEventListApi } from '@/lib/api' import { getASEventListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useDetailDataStore from '@/stores/detailDataStore' import useDetailDataStore from '@/stores/detailDataStore'
export const useFetchASGroupData = () => { export const useFetchASGroupData = () => {
const { setMapAS } = useDetailDataStore() const { setMapAS } = useDetailDataStore()
const query = useQuery({ const query = useQuery({
queryKey: ['ASGroupData'], queryKey: ['ASGroupData'],
queryFn: getASEventListApi, queryFn: getASEventListApi,
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
useEffect(() => { useEffect(() => {
if (query.data) { if (query.data) {
setMapAS(query.data) setMapAS(query.data)
} else if (query.error) { } else if (query.error) {
toast.error("Failed to load ASGroup data") toast.error("Failed to load ASGroup data")
} }
}, [query.data, query.error, setMapAS]) }, [query.data, query.error, setMapAS])
return query return query
} }
+42 -42
View File
@@ -1,43 +1,43 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getAvatarListApi } from '@/lib/api' import { getAvatarListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useDetailDataStore from '@/stores/detailDataStore' import useDetailDataStore from '@/stores/detailDataStore'
import useCurrentDataStore from '@/stores/currentDataStore' import useCurrentDataStore from '@/stores/currentDataStore'
import useUserDataStore from '@/stores/userDataStore' import useUserDataStore from '@/stores/userDataStore'
import { converterToAvatarStore } from '@/helper' import { converterToAvatarStore } from '@/helper'
export const useFetchAvatarData = () => { export const useFetchAvatarData = () => {
const { setMapAvatar, mapAvatar } = useDetailDataStore() const { setMapAvatar, mapAvatar } = useDetailDataStore()
const { setAvatar, avatars } = useUserDataStore() const { setAvatar, avatars } = useUserDataStore()
const { avatarSelected, setAvatarSelected } = useCurrentDataStore() const { avatarSelected, setAvatarSelected } = useCurrentDataStore()
const query = useQuery({ const query = useQuery({
queryKey: ['AvatarData'], queryKey: ['AvatarData'],
queryFn: getAvatarListApi, queryFn: getAvatarListApi,
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
useEffect(() => { useEffect(() => {
const listAvatarId = Object.keys(avatars) const listAvatarId = Object.keys(avatars)
const listAvatarNotExist = Object.entries(mapAvatar).filter(([avatarId]) => !listAvatarId.includes(avatarId)) const listAvatarNotExist = Object.entries(mapAvatar).filter(([avatarId]) => !listAvatarId.includes(avatarId))
const avatarStore = converterToAvatarStore(Object.fromEntries(listAvatarNotExist)) const avatarStore = converterToAvatarStore(Object.fromEntries(listAvatarNotExist))
if (Object.keys(avatarStore).length === 0) return if (Object.keys(avatarStore).length === 0) return
for (const avatar of Object.values(avatarStore)) { for (const avatar of Object.values(avatarStore)) {
setAvatar(avatar) setAvatar(avatar)
} }
}, [mapAvatar]) }, [mapAvatar])
useEffect(() => { useEffect(() => {
if (query.data) { if (query.data) {
setMapAvatar(query.data) setMapAvatar(query.data)
if (!avatarSelected) { if (!avatarSelected) {
setAvatarSelected(Object.values(query.data)[0]) setAvatarSelected(Object.values(query.data)[0])
} }
} }
if (query.error) toast.error("Failed to load Avatar data") if (query.error) toast.error("Failed to load Avatar data")
}, [query.data, query.error, setMapAvatar, avatarSelected, setAvatarSelected]) }, [query.data, query.error, setMapAvatar, avatarSelected, setAvatarSelected])
return query return query
} }
+32 -32
View File
@@ -1,32 +1,32 @@
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getChangelog } from '@/lib/api' import { getChangelog } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useModelStore from '@/stores/modelStore' import useModelStore from '@/stores/modelStore'
import useLocaleStore from '@/stores/localeStore' import useLocaleStore from '@/stores/localeStore'
export const useFetchChangelog = () => { export const useFetchChangelog = () => {
const { currentVersion, setChangelog, setCurrentVersion } = useLocaleStore() const { currentVersion, setChangelog, setCurrentVersion } = useLocaleStore()
const { setIsChangelog } = useModelStore() const { setIsChangelog } = useModelStore()
const query = useQuery({ const query = useQuery({
queryKey: ['changelog'], queryKey: ['changelog'],
queryFn: getChangelog, queryFn: getChangelog,
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
useEffect(() => { useEffect(() => {
if (query.data && !query.error) { if (query.data && !query.error) {
setChangelog(query.data) setChangelog(query.data)
if (query.data?.[0] && query.data[0].version != currentVersion) { if (query.data?.[0] && query.data[0].version != currentVersion) {
setIsChangelog(true) setIsChangelog(true)
setCurrentVersion(query.data[0].version) setCurrentVersion(query.data[0].version)
} }
} else if (query.error) { } else if (query.error) {
toast.error("Failed to load changelog data") toast.error("Failed to load changelog data")
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [query.data, query.error, setChangelog, setCurrentVersion, setIsChangelog]) }, [query.data, query.error, setChangelog, setCurrentVersion, setIsChangelog])
return query return query
} }
+25 -25
View File
@@ -1,25 +1,25 @@
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getLightconeListApi } from '@/lib/api'; import { getLightconeListApi } from '@/lib/api';
import { useEffect } from 'react' import { useEffect } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useDetailDataStore from '@/stores/detailDataStore'; import useDetailDataStore from '@/stores/detailDataStore';
export const useFetchLightconeData = () => { export const useFetchLightconeData = () => {
const { setMapLightCone } = useDetailDataStore() const { setMapLightCone } = useDetailDataStore()
const query = useQuery({ const query = useQuery({
queryKey: ['lightconeData'], queryKey: ['lightconeData'],
queryFn: getLightconeListApi, queryFn: getLightconeListApi,
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
useEffect(() => { useEffect(() => {
if (query.data && !query.error) { if (query.data && !query.error) {
setMapLightCone(query.data) setMapLightCone(query.data)
} else if (query.error) { } else if (query.error) {
toast.error("Failed to load lightcone data") toast.error("Failed to load lightcone data")
} }
}, [query.data, query.error, setMapLightCone]) }, [query.data, query.error, setMapLightCone])
return query return query
} }
+25 -25
View File
@@ -1,25 +1,25 @@
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getMOCEventListApi } from '@/lib/api' import { getMOCEventListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import useDetailDataStore from '@/stores/detailDataStore' import useDetailDataStore from '@/stores/detailDataStore'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
export const useFetchMOCGroupData = () => { export const useFetchMOCGroupData = () => {
const { setMapMoc } = useDetailDataStore() const { setMapMoc } = useDetailDataStore()
const query = useQuery({ const query = useQuery({
queryKey: ['MOCGroupData'], queryKey: ['MOCGroupData'],
queryFn: getMOCEventListApi, queryFn: getMOCEventListApi,
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
useEffect(() => { useEffect(() => {
if (query.data && !query.error) { if (query.data && !query.error) {
setMapMoc(query.data) setMapMoc(query.data)
} else if (query.error) { } else if (query.error) {
toast.error("Failed to load MOCGroup data") toast.error("Failed to load MOCGroup data")
} }
}, [query.data, query.error, setMapMoc]) }, [query.data, query.error, setMapMoc])
return query return query
} }
+33 -33
View File
@@ -1,33 +1,33 @@
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { useEffect } from 'react' import { useEffect } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { getMetadataApi } from '@/lib/api' import { getMetadataApi } from '@/lib/api'
import useCurrentDataStore from '@/stores/currentDataStore' import useCurrentDataStore from '@/stores/currentDataStore'
import useDetailDataStore from '@/stores/detailDataStore' import useDetailDataStore from '@/stores/detailDataStore'
export const useFetchConfigData = () => { export const useFetchConfigData = () => {
const { setMetaData } = useDetailDataStore() const { setMetaData } = useDetailDataStore()
const { setSettingData } = useCurrentDataStore() const { setSettingData } = useCurrentDataStore()
const query = useQuery({ const query = useQuery({
queryKey: ['initialConfigData'], queryKey: ['initialConfigData'],
queryFn: async () => { queryFn: async () => {
const metaData = await getMetadataApi() const metaData = await getMetadataApi()
return { metaData } return { metaData }
}, },
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
useEffect(() => { useEffect(() => {
if (query.data && !query.error) { if (query.data && !query.error) {
setSettingData(query.data.metaData) setSettingData(query.data.metaData)
setMetaData(query.data.metaData) setMetaData(query.data.metaData)
} }
else if (query.error) { else if (query.error) {
toast.error("Failed to load initial config data") toast.error("Failed to load initial config data")
} }
}, [query.data, query.error, setMetaData, setSettingData]) }, [query.data, query.error, setMetaData, setSettingData])
return query return query
} }
+25 -25
View File
@@ -1,25 +1,25 @@
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getMonsterListApi } from '@/lib/api' import { getMonsterListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import useDetailDataStore from '@/stores/detailDataStore' import useDetailDataStore from '@/stores/detailDataStore'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
export const useFetchMonsterData = () => { export const useFetchMonsterData = () => {
const { setMapMonster } = useDetailDataStore() const { setMapMonster } = useDetailDataStore()
const query = useQuery({ const query = useQuery({
queryKey: ['MonsterData'], queryKey: ['MonsterData'],
queryFn: getMonsterListApi, queryFn: getMonsterListApi,
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
useEffect(() => { useEffect(() => {
if (query.data && !query.error) { if (query.data && !query.error) {
setMapMonster(query.data) setMapMonster(query.data)
} else if (query.error) { } else if (query.error) {
toast.error("Failed to load Monster data") toast.error("Failed to load Monster data")
} }
}, [query.data, query.error, setMapMonster]) }, [query.data, query.error, setMapMonster])
return query return query
} }
+25 -25
View File
@@ -1,25 +1,25 @@
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getPeakEventListApi } from '@/lib/api' import { getPeakEventListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import useDetailDataStore from '@/stores/detailDataStore' import useDetailDataStore from '@/stores/detailDataStore'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
export const useFetchPeakGroupData = () => { export const useFetchPeakGroupData = () => {
const { setMapPeak } = useDetailDataStore() const { setMapPeak } = useDetailDataStore()
const query = useQuery({ const query = useQuery({
queryKey: ['PeakGroupData'], queryKey: ['PeakGroupData'],
queryFn: getPeakEventListApi, queryFn: getPeakEventListApi,
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
useEffect(() => { useEffect(() => {
if (query.data && !query.error) { if (query.data && !query.error) {
setMapPeak(query.data) setMapPeak(query.data)
} else if (query.error) { } else if (query.error) {
toast.error("Failed to load PeakGroup data") toast.error("Failed to load PeakGroup data")
} }
}, [query.data, query.error, setMapPeak]) }, [query.data, query.error, setMapPeak])
return query return query
} }
+25 -25
View File
@@ -1,25 +1,25 @@
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getPFEventListApi } from '@/lib/api' import { getPFEventListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import useDetailDataStore from '@/stores/detailDataStore' import useDetailDataStore from '@/stores/detailDataStore'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
export const useFetchPFGroupData = () => { export const useFetchPFGroupData = () => {
const { setMapPF } = useDetailDataStore() const { setMapPF } = useDetailDataStore()
const query = useQuery({ const query = useQuery({
queryKey: ['PFGroupData'], queryKey: ['PFGroupData'],
queryFn: getPFEventListApi, queryFn: getPFEventListApi,
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
useEffect(() => { useEffect(() => {
if (query.data && !query.error) { if (query.data && !query.error) {
setMapPF(query.data) setMapPF(query.data)
} else if (query.error) { } else if (query.error) {
toast.error("Failed to load PFGroup data") toast.error("Failed to load PFGroup data")
} }
}, [query.data, query.error, setMapPF]) }, [query.data, query.error, setMapPF])
return query return query
} }
+25 -25
View File
@@ -1,25 +1,25 @@
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getRelicSetListApi } from '@/lib/api' import { getRelicSetListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import useDetailDataStore from '@/stores/detailDataStore' import useDetailDataStore from '@/stores/detailDataStore'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
export const useFetchRelicSetData = () => { export const useFetchRelicSetData = () => {
const { setMapRelicSet } = useDetailDataStore() const { setMapRelicSet } = useDetailDataStore()
const query = useQuery({ const query = useQuery({
queryKey: ['RelicSetData'], queryKey: ['RelicSetData'],
queryFn: getRelicSetListApi, queryFn: getRelicSetListApi,
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
useEffect(() => { useEffect(() => {
if (query.data && !query.error) { if (query.data && !query.error) {
setMapRelicSet(query.data) setMapRelicSet(query.data)
} else if (query.error) { } else if (query.error) {
toast.error("Failed to load RelicSet data") toast.error("Failed to load RelicSet data")
} }
}, [query.data, query.error, setMapRelicSet]) }, [query.data, query.error, setMapRelicSet])
return query return query
} }
+24 -24
View File
@@ -2,19 +2,19 @@ import { create } from 'zustand'
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { connectPersistedSchema } from '@/zod'; import { connectPersistedSchema } from '@/zod';
import { createValidatedJSONStorage } from './persistStorage'; import { createValidatedJSONStorage } from './persistStorage';
interface ConnectState { interface ConnectState {
connectionType: string; connectionType: string;
privateType: string; privateType: string;
serverUrl: string; serverUrl: string;
username: string; username: string;
password: string; password: string;
setConnectionType: (newConnectionType: string) => void; setConnectionType: (newConnectionType: string) => void;
setPrivateType: (newPrivateType: string) => void; setPrivateType: (newPrivateType: string) => void;
setServerUrl: (newServerUrl: string) => void; setServerUrl: (newServerUrl: string) => void;
setUsername: (newUsername: string) => void; setUsername: (newUsername: string) => void;
setPassword: (newPassword: string) => void; setPassword: (newPassword: string) => void;
} }
type ConnectPersistedState = Pick<ConnectState, "connectionType" | "privateType" | "serverUrl" | "username" | "password">; type ConnectPersistedState = Pick<ConnectState, "connectionType" | "privateType" | "serverUrl" | "username" | "password">;
@@ -22,17 +22,17 @@ type ConnectPersistedState = Pick<ConnectState, "connectionType" | "privateType"
const useConnectStore = create<ConnectState>()( const useConnectStore = create<ConnectState>()(
persist<ConnectState, [], [], ConnectPersistedState>( persist<ConnectState, [], [], ConnectPersistedState>(
(set) => ({ (set) => ({
connectionType: "FireflyGo", connectionType: "FireflyGo",
privateType: "Local", privateType: "Local",
serverUrl: "http://localhost:21000", serverUrl: "http://localhost:21000",
username: "", username: "",
password: "", password: "",
setConnectionType: (newConnectionType: string) => set({ connectionType: newConnectionType }), setConnectionType: (newConnectionType: string) => set({ connectionType: newConnectionType }),
setPrivateType: (newPrivateType: string) => set({ privateType: newPrivateType }), setPrivateType: (newPrivateType: string) => set({ privateType: newPrivateType }),
setServerUrl: (newServerUrl: string) => set({ serverUrl: newServerUrl }), setServerUrl: (newServerUrl: string) => set({ serverUrl: newServerUrl }),
setUsername: (newUsername: string) => set({ username: newUsername }), setUsername: (newUsername: string) => set({ username: newUsername }),
setPassword: (newPassword: string) => set({ password: newPassword }), setPassword: (newPassword: string) => set({ password: newPassword }),
}), }),
{ {
name: 'connect-storage', name: 'connect-storage',
storage: createValidatedJSONStorage(() => localStorage, connectPersistedSchema), storage: createValidatedJSONStorage(() => localStorage, connectPersistedSchema),
@@ -46,5 +46,5 @@ const useConnectStore = create<ConnectState>()(
} }
) )
); );
export default useConnectStore; export default useConnectStore;
+40 -40
View File
@@ -1,41 +1,41 @@
import { AvatarDetail, AvatarProfileCardType, BaseTypeData, DamageTypeData } from '@/types'; import { AvatarDetail, AvatarProfileCardType, BaseTypeData, DamageTypeData } from '@/types';
import { create } from 'zustand' import { create } from 'zustand'
interface CopyProfileState { interface CopyProfileState {
selectedProfiles: AvatarProfileCardType[]; selectedProfiles: AvatarProfileCardType[];
avatarCopySelected: AvatarDetail | null; avatarCopySelected: AvatarDetail | null;
listElement: Record<string, boolean>; listElement: Record<string, boolean>;
listPath: Record<string, boolean>; listPath: Record<string, boolean>;
listRank: Record<string, boolean>; listRank: Record<string, boolean>;
setListElement: (newListElement: Record<string, boolean>) => void; setListElement: (newListElement: Record<string, boolean>) => void;
setListPath: (newListPath: Record<string, boolean>) => void; setListPath: (newListPath: Record<string, boolean>) => void;
setListRank: (newListRank: Record<string, boolean>) => void; setListRank: (newListRank: Record<string, boolean>) => void;
setSelectedProfiles: (newListAvatar: AvatarProfileCardType[]) => void; setSelectedProfiles: (newListAvatar: AvatarProfileCardType[]) => void;
setAvatarCopySelected: (newAvatarSelected: AvatarDetail | null) => void; setAvatarCopySelected: (newAvatarSelected: AvatarDetail | null) => void;
setResetData: (baseType: Record<string, BaseTypeData>, damageType: Record<string, DamageTypeData>) => void; setResetData: (baseType: Record<string, BaseTypeData>, damageType: Record<string, DamageTypeData>) => void;
} }
const useCopyProfileStore = create<CopyProfileState>((set) => ({ const useCopyProfileStore = create<CopyProfileState>((set) => ({
selectedProfiles: [], selectedProfiles: [],
avatarCopySelected: null, avatarCopySelected: null,
listElement: {}, listElement: {},
listPath: {}, listPath: {},
listRank: { "4": false, "5": false }, listRank: { "4": false, "5": false },
listCopyAvatar: [], listCopyAvatar: [],
listRawCopyAvatar: [], listRawCopyAvatar: [],
setListElement: (newListElement: Record<string, boolean>) => set({ listElement: newListElement }), setListElement: (newListElement: Record<string, boolean>) => set({ listElement: newListElement }),
setListPath: (newListPath: Record<string, boolean>) => set({ listPath: newListPath }), setListPath: (newListPath: Record<string, boolean>) => set({ listPath: newListPath }),
setListRank: (newListRank: Record<string, boolean>) => set({ listRank: newListRank }), setListRank: (newListRank: Record<string, boolean>) => set({ listRank: newListRank }),
setAvatarCopySelected: (newAvatarSelected: AvatarDetail | null) => set({ avatarCopySelected: newAvatarSelected }), setAvatarCopySelected: (newAvatarSelected: AvatarDetail | null) => set({ avatarCopySelected: newAvatarSelected }),
setSelectedProfiles: (newListAvatar: AvatarProfileCardType[]) => set({ selectedProfiles: newListAvatar }), setSelectedProfiles: (newListAvatar: AvatarProfileCardType[]) => set({ selectedProfiles: newListAvatar }),
setResetData: (baseType: Record<string, BaseTypeData>, damageType: Record<string, DamageTypeData>) => { setResetData: (baseType: Record<string, BaseTypeData>, damageType: Record<string, DamageTypeData>) => {
set({ set({
listElement: Object.fromEntries(Object.keys(damageType).map(key => [key, false])) as Record<string, boolean>, listElement: Object.fromEntries(Object.keys(damageType).map(key => [key, false])) as Record<string, boolean>,
listPath: Object.fromEntries(Object.keys(baseType).map(key => [key, false])) as Record<string, boolean>, listPath: Object.fromEntries(Object.keys(baseType).map(key => [key, false])) as Record<string, boolean>,
listRank: { "4": false, "5": false } listRank: { "4": false, "5": false }
}) })
} }
})); }));
export default useCopyProfileStore; export default useCopyProfileStore;

Some files were not shown because too many files have changed in this diff Show More