Add client-side spell column sorting and improve inventory search

- Implement client-side sorting for all columns including spell_names
- Add computed_spell_names CTE for server-side sort fallback
- Add resizable columns with localStorage persistence
- Add Cloak slot detection by name pattern
- Increase items limit to 50000 for full inventory loading
- Increase proxy timeout to 60s for large queries
- Remove pagination (all items loaded at once for sorting)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
erik 2026-01-28 15:32:54 +00:00
parent 25980edf99
commit 8cae94d87d
6 changed files with 294 additions and 111 deletions

View file

@ -2188,7 +2188,7 @@
"dictionaries": {
"AttributeSetInfo": {
"name": "AttributeSetInfo",
"description": "Equipment set names",
"description": "Equipment set names (complete from Dictionaries.cs)",
"values": {
"2": "Test",
"4": "Carraida's Benediction",
@ -2210,7 +2210,7 @@
"20": "Dexterous Set",
"21": "Wise Set",
"22": "Swift Set",
"23": "Hardenend Set",
"23": "Hardened Set",
"24": "Reinforced Set",
"25": "Interlocking Set",
"26": "Flame Proof Set",
@ -2232,15 +2232,102 @@
"42": "Olthoi Armor D Red",
"43": "Olthoi Armor C Rat",
"44": "Olthoi Armor C Red",
"45": "Olthoi Armor F Red",
"46": "Olthoi Armor K Red",
"47": "Olthoi Armor M Red",
"48": "Olthoi Armor B Red",
"49": "Olthoi Armor B Rat",
"50": "Olthoi Armor K Rat",
"51": "Olthoi Armor M Rat",
"52": "Olthoi Armor F Rat",
"53": "Olthoi Armor D Rat"
"45": "Olthoi Armor D Rat",
"46": "Upgraded Relic Alduressa Set",
"47": "Upgraded Ancient Relic Set",
"48": "Upgraded Noble Relic Set",
"49": "Weave of Alchemy",
"50": "Weave of Arcane Lore",
"51": "Weave of Armor Tinkering",
"52": "Weave of Assess Person",
"53": "Weave of Light Weapons",
"54": "Weave of Missile Weapons",
"55": "Weave of Cooking",
"56": "Weave of Creature Enchantment",
"57": "Weave of Missile Weapons",
"58": "Weave of Finesse",
"59": "Weave of Deception",
"60": "Weave of Fletching",
"61": "Weave of Healing",
"62": "Weave of Item Enchantment",
"63": "Weave of Item Tinkering",
"64": "Weave of Leadership",
"65": "Weave of Life Magic",
"66": "Weave of Loyalty",
"67": "Weave of Light Weapons",
"68": "Weave of Magic Defense",
"69": "Weave of Magic Item Tinkering",
"70": "Weave of Mana Conversion",
"71": "Weave of Melee Defense",
"72": "Weave of Missile Defense",
"73": "Weave of Salvaging",
"74": "Weave of Light Weapons",
"75": "Weave of Light Weapons",
"76": "Weave of Heavy Weapons",
"77": "Weave of Missile Weapons",
"78": "Weave of Two Handed Combat",
"79": "Weave of Light Weapons",
"80": "Weave of Void Magic",
"81": "Weave of War Magic",
"82": "Weave of Weapon Tinkering",
"83": "Weave of Assess Creature",
"84": "Weave of Dirty Fighting",
"85": "Weave of Dual Wield",
"86": "Weave of Recklessness",
"87": "Weave of Shield",
"88": "Weave of Sneak Attack",
"89": "Ninja_New",
"90": "Weave of Summoning",
"91": "Shrouded Soul",
"92": "Darkened Mind",
"93": "Clouded Spirit",
"94": "Minor Stinging Shrouded Soul",
"95": "Minor Sparking Shrouded Soul",
"96": "Minor Smoldering Shrouded Soul",
"97": "Minor Shivering Shrouded Soul",
"98": "Minor Stinging Darkened Mind",
"99": "Minor Sparking Darkened Mind",
"100": "Minor Smoldering Darkened Mind",
"101": "Minor Shivering Darkened Mind",
"102": "Minor Stinging Clouded Spirit",
"103": "Minor Sparking Clouded Spirit",
"104": "Minor Smoldering Clouded Spirit",
"105": "Minor Shivering Clouded Spirit",
"106": "Major Stinging Shrouded Soul",
"107": "Major Sparking Shrouded Soul",
"108": "Major Smoldering Shrouded Soul",
"109": "Major Shivering Shrouded Soul",
"110": "Major Stinging Darkened Mind",
"111": "Major Sparking Darkened Mind",
"112": "Major Smoldering Darkened Mind",
"113": "Major Shivering Darkened Mind",
"114": "Major Stinging Clouded Spirit",
"115": "Major Sparking Clouded Spirit",
"116": "Major Smoldering Clouded Spirit",
"117": "Major Shivering Clouded Spirit",
"118": "Blackfire Stinging Shrouded Soul",
"119": "Blackfire Sparking Shrouded Soul",
"120": "Blackfire Smoldering Shrouded Soul",
"121": "Blackfire Shivering Shrouded Soul",
"122": "Blackfire Stinging Darkened Mind",
"123": "Blackfire Sparking Darkened Mind",
"124": "Blackfire Smoldering Darkened Mind",
"125": "Blackfire Shivering Darkened Mind",
"126": "Blackfire Stinging Clouded Spirit",
"127": "Blackfire Sparking Clouded Spirit",
"128": "Blackfire Smoldering Clouded Spirit",
"129": "Blackfire Shivering Clouded Spirit",
"130": "Shimmering Shadows",
"131": "Brown Society Locket",
"132": "Yellow Society Locket",
"133": "Red Society Band",
"134": "Green Society Band",
"135": "Purple Society Band",
"136": "Blue Society Band",
"137": "Gauntlet Garb",
"138": "Paragon Missile Weapons",
"139": "Paragon Casters",
"140": "Paragon Melee Weapons"
}
}
},

View file

@ -232,7 +232,7 @@ class ItemRatings(Base):
class ItemSpells(Base):
"""Spell information for items."""
__tablename__ = 'item_spells'
item_id = Column(Integer, ForeignKey('items.id'), primary_key=True)
spell_id = Column(Integer, primary_key=True)
is_active = Column(Boolean, default=False)

View file

@ -2085,7 +2085,7 @@ async def search_items(
sort_by: str = Query("name", description="Sort field: name, value, damage, armor, workmanship, damage_rating, crit_damage_rating, level"),
sort_dir: str = Query("asc", description="Sort direction: asc or desc"),
page: int = Query(1, ge=1, description="Page number"),
limit: int = Query(200, ge=1, le=10000, description="Items per page")
limit: int = Query(200, ge=1, le=50000, description="Items per page")
):
"""
Search items across characters with comprehensive filtering options.
@ -2232,14 +2232,24 @@ async def search_items(
WHEN i.object_class = 6 THEN 'Melee Weapon'
WHEN i.object_class = 7 THEN 'Missile Weapon'
WHEN i.object_class = 8 THEN 'Held'
-- Check wielded location for two-handed weapons
WHEN i.current_wielded_location = 67108864 THEN 'Two-Handed'
-- CLOAKS: Identify by name pattern
WHEN i.name ILIKE '%cloak%' THEN 'Cloak'
-- DEFAULT
ELSE '-'
END as computed_slot_name
END as computed_slot_name,
-- Compute spell_names (spell IDs for client-side name lookup)
COALESCE(
(SELECT STRING_AGG(CAST(sp_inner.spell_id AS VARCHAR), ',' ORDER BY sp_inner.spell_id)
FROM item_spells sp_inner WHERE sp_inner.item_id = i.id),
''
) as computed_spell_names
FROM items i
LEFT JOIN item_combat_stats cs ON i.id = cs.item_id
LEFT JOIN item_requirements req ON i.id = req.item_id
@ -2495,7 +2505,11 @@ async def search_items(
slot_approaches.append("(object_class = 11 AND name ILIKE '%trinket%')")
# 4. Jewelry fallback: items that don't match other jewelry patterns
slot_approaches.append("(object_class = 4 AND name NOT ILIKE '%ring%' AND name NOT ILIKE '%bracelet%' AND name NOT ILIKE '%amulet%' AND name NOT ILIKE '%necklace%' AND name NOT ILIKE '%gorget%')")
elif slot_name.lower() == 'cloak':
# For cloaks: identify by name pattern
slot_approaches.append("(name ILIKE '%cloak%')")
slot_approaches.append("(computed_slot_name = 'Cloak')")
# Combine approaches with OR (any approach can match)
if slot_approaches:
slot_conditions.append(f"({' OR '.join(slot_approaches)})")
@ -2663,9 +2677,11 @@ async def search_items(
# Add ORDER BY
sort_mapping = {
"name": "name",
"character_name": "character_name",
"value": "value",
"damage": "max_damage",
"armor": "armor_level",
"armor": "armor_level",
"armor_level": "armor_level",
"workmanship": "workmanship",
"level": "wield_level",
"damage_rating": "damage_rating",
@ -2673,11 +2689,20 @@ async def search_items(
"heal_boost_rating": "heal_boost_rating",
"vitality_rating": "vitality_rating",
"damage_resist_rating": "damage_resist_rating",
"crit_damage_resist_rating": "crit_damage_resist_rating"
"crit_damage_resist_rating": "crit_damage_resist_rating",
"item_set": "item_set",
"slot_name": "computed_slot_name",
"coverage": "coverage_mask",
"item_type_name": "object_class",
"last_updated": "timestamp",
"spell_names": "computed_spell_names"
}
sort_field = sort_mapping.get(sort_by, "name")
sort_direction = "DESC" if sort_dir.lower() == "desc" else "ASC"
query_parts.append(f"ORDER BY {sort_field} {sort_direction}")
# Handle NULLS for optional fields
nulls_clause = "NULLS LAST" if sort_direction == "ASC" else "NULLS FIRST"
query_parts.append(f"ORDER BY {sort_field} {sort_direction} {nulls_clause}")
# Add pagination
offset = (page - 1) * limit