add utility functions for party component
- extractErrorMessage: handle nested api error structures - transformSkillsToArray: convert job skills to api format - findNextEmptySlot: find available positions in grids
This commit is contained in:
parent
f9bb43f214
commit
112e8c39a9
3 changed files with 229 additions and 0 deletions
64
src/lib/utils/errors.ts
Normal file
64
src/lib/utils/errors.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* Error message extraction utilities
|
||||
* Handles complex nested error structures from API responses
|
||||
*/
|
||||
|
||||
export interface NestedErrorDetails {
|
||||
details?: NestedErrorDetails
|
||||
errors?: { message?: string; [key: string]: any }
|
||||
message?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts user-friendly error message from nested API error structures
|
||||
* Handles the pattern: error.details.details.errors.message
|
||||
*
|
||||
* @param error - The error object to extract from
|
||||
* @param fallbackMessage - Message to return if extraction fails
|
||||
* @returns Extracted error message or fallback
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* try {
|
||||
* await api.updateParty(...)
|
||||
* } catch (e) {
|
||||
* error = extractErrorMessage(e, 'Failed to update party')
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function extractErrorMessage(
|
||||
error: any,
|
||||
fallbackMessage: string = 'An error occurred'
|
||||
): string {
|
||||
if (!error) return fallbackMessage
|
||||
|
||||
// Navigate through nested details structure
|
||||
let errorDetails: NestedErrorDetails | undefined = error?.details
|
||||
while (errorDetails?.details) {
|
||||
errorDetails = errorDetails.details
|
||||
}
|
||||
|
||||
// Try to extract message from various formats
|
||||
if (errorDetails?.errors) {
|
||||
// Simple message format
|
||||
if (errorDetails.errors.message) {
|
||||
return errorDetails.errors.message
|
||||
}
|
||||
|
||||
// Field-based errors - combine all messages
|
||||
const errorMessages = Object.entries(errorDetails.errors)
|
||||
.map(([field, messages]) => {
|
||||
if (Array.isArray(messages)) {
|
||||
return messages.join(', ')
|
||||
}
|
||||
return String(messages)
|
||||
})
|
||||
.filter((msg) => msg && msg !== 'undefined')
|
||||
.join('; ')
|
||||
|
||||
if (errorMessages) return errorMessages
|
||||
}
|
||||
|
||||
// Fallback to error.message
|
||||
return error?.message || fallbackMessage
|
||||
}
|
||||
99
src/lib/utils/gridHelpers.ts
Normal file
99
src/lib/utils/gridHelpers.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* Grid slot finding and helper utilities
|
||||
* Handles finding available positions in weapon, summon, and character grids
|
||||
*/
|
||||
|
||||
import type { Party } from '$lib/types/api/party'
|
||||
import { GridType } from '$lib/types/enums'
|
||||
|
||||
/** Sentinel value indicating no empty slot was found */
|
||||
export const SLOT_NOT_FOUND = -999
|
||||
|
||||
export interface SlotRange {
|
||||
start: number
|
||||
end: number
|
||||
specialSlots?: number[] // e.g., mainhand (-1), friend summon (6)
|
||||
}
|
||||
|
||||
/** Grid slot configuration for each grid type */
|
||||
const GRID_CONFIGS: Record<GridType, SlotRange> = {
|
||||
[GridType.Weapon]: { start: 0, end: 8, specialSlots: [-1] }, // mainhand + 9 grid slots
|
||||
[GridType.Summon]: { start: 0, end: 5, specialSlots: [-1, 6] }, // main + 6 grid + friend
|
||||
[GridType.Character]: { start: 0, end: 4, specialSlots: [] } // 5 slots (0-4)
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the next empty slot in a grid
|
||||
*
|
||||
* @param party - Current party state
|
||||
* @param gridType - Type of grid to search (weapon, summon, or character)
|
||||
* @returns Position number of next empty slot, or SLOT_NOT_FOUND if grid is full
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const nextSlot = findNextEmptySlot(party, GridType.Weapon)
|
||||
* if (nextSlot !== SLOT_NOT_FOUND) {
|
||||
* selectedSlot = nextSlot
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function findNextEmptySlot(party: Party, gridType: GridType): number {
|
||||
const config = GRID_CONFIGS[gridType]
|
||||
const collection = getCollectionForType(party, gridType)
|
||||
|
||||
// Check special slots first (e.g., mainhand, main summon)
|
||||
for (const specialSlot of config.specialSlots || []) {
|
||||
if (!isSlotOccupied(collection, specialSlot, gridType)) {
|
||||
return specialSlot
|
||||
}
|
||||
}
|
||||
|
||||
// Check regular grid slots
|
||||
for (let i = config.start; i <= config.end; i++) {
|
||||
if (!isSlotOccupied(collection, i, gridType)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return SLOT_NOT_FOUND
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate collection array for a grid type
|
||||
*/
|
||||
function getCollectionForType(party: Party, gridType: GridType) {
|
||||
switch (gridType) {
|
||||
case GridType.Weapon:
|
||||
return party.weapons
|
||||
case GridType.Summon:
|
||||
return party.summons
|
||||
case GridType.Character:
|
||||
return party.characters
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific slot position is occupied
|
||||
* Handles special cases for mainhand weapons, main/friend summons
|
||||
*/
|
||||
function isSlotOccupied(collection: any[], position: number, gridType: GridType): boolean {
|
||||
// For weapons, check both position and mainhand flag
|
||||
if (gridType === GridType.Weapon) {
|
||||
return collection.some(
|
||||
(item) => item.position === position || (position === -1 && item.mainhand)
|
||||
)
|
||||
}
|
||||
|
||||
// For summons, check position, main, and friend flags
|
||||
if (gridType === GridType.Summon) {
|
||||
return collection.some(
|
||||
(item) =>
|
||||
item.position === position ||
|
||||
(position === -1 && item.main) ||
|
||||
(position === 6 && item.friend)
|
||||
)
|
||||
}
|
||||
|
||||
// For characters, simple position check
|
||||
return collection.some((item) => item.position === position)
|
||||
}
|
||||
66
src/lib/utils/jobSkills.ts
Normal file
66
src/lib/utils/jobSkills.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* Job skills transformation utilities
|
||||
* Handles converting between object and array formats for job skills
|
||||
*/
|
||||
|
||||
import type { JobSkill } from '$lib/types/api/entities'
|
||||
|
||||
export interface JobSkillsMap {
|
||||
[slot: string]: JobSkill | null | undefined
|
||||
}
|
||||
|
||||
export interface JobSkillPayload {
|
||||
id: string
|
||||
slot: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts job skills object to API array format
|
||||
* Filters out null/undefined values and adds slot numbers
|
||||
*
|
||||
* @param skillsMap - Object mapping slot numbers to job skills
|
||||
* @returns Array of job skill payloads ready for API submission
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const skillsMap = { '0': skill1, '1': skill2, '2': null }
|
||||
* const payload = transformSkillsToArray(skillsMap)
|
||||
* // Returns: [{ id: 'skill1-id', slot: 0 }, { id: 'skill2-id', slot: 1 }]
|
||||
* ```
|
||||
*/
|
||||
export function transformSkillsToArray(skillsMap: JobSkillsMap): JobSkillPayload[] {
|
||||
return Object.entries(skillsMap)
|
||||
.filter(([_, skill]) => skill !== null && skill !== undefined)
|
||||
.map(([slotKey, skill]) => ({
|
||||
id: skill!.id,
|
||||
slot: parseInt(slotKey)
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a skill in a specific slot (returns new object, immutable)
|
||||
*
|
||||
* @param currentSkills - Current job skills map
|
||||
* @param slot - Slot number to update
|
||||
* @param skill - Job skill to set, or null to remove
|
||||
* @returns New skills map with the update applied
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const updated = updateSkillInSlot(currentSkills, 0, newSkill)
|
||||
* const removed = updateSkillInSlot(currentSkills, 1, null)
|
||||
* ```
|
||||
*/
|
||||
export function updateSkillInSlot(
|
||||
currentSkills: JobSkillsMap,
|
||||
slot: number,
|
||||
skill: JobSkill | null
|
||||
): JobSkillsMap {
|
||||
const updated = { ...currentSkills }
|
||||
if (skill === null) {
|
||||
delete updated[String(slot)]
|
||||
} else {
|
||||
updated[String(slot)] = skill
|
||||
}
|
||||
return updated
|
||||
}
|
||||
Loading…
Reference in a new issue