add orphaned item indicators
shows warning badge + dimmed styling when collection items get deleted party banner warns if any items are orphaned
This commit is contained in:
parent
d0592935be
commit
daa104cd99
5 changed files with 148 additions and 2 deletions
|
|
@ -316,6 +316,14 @@
|
||||||
// Check if syncing is in progress
|
// Check if syncing is in progress
|
||||||
const isSyncingAll = $derived(syncAllItems.isPending)
|
const isSyncingAll = $derived(syncAllItems.isPending)
|
||||||
|
|
||||||
|
// Check if any items in the party are orphaned (linked collection item was deleted)
|
||||||
|
const hasOrphanedItems = $derived.by(() => {
|
||||||
|
const hasOrphanedWeapons = (party?.weapons ?? []).some((w) => w?.orphaned)
|
||||||
|
const hasOrphanedCharacters = (party?.characters ?? []).some((c) => c?.orphaned)
|
||||||
|
const hasOrphanedSummons = (party?.summons ?? []).some((s) => s?.orphaned)
|
||||||
|
return hasOrphanedWeapons || hasOrphanedCharacters || hasOrphanedSummons
|
||||||
|
})
|
||||||
|
|
||||||
function handleTabChange(tab: GridType) {
|
function handleTabChange(tab: GridType) {
|
||||||
activeTab = tab // Instant UI update
|
activeTab = tab // Instant UI update
|
||||||
|
|
||||||
|
|
@ -941,6 +949,13 @@
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</PartyInfoGrid>
|
</PartyInfoGrid>
|
||||||
|
|
||||||
|
{#if hasOrphanedItems}
|
||||||
|
<div class="orphan-warning" role="alert">
|
||||||
|
<Icon name="alertTriangle" size={16} />
|
||||||
|
<span>Some items in this party are no longer in your collection and may have outdated data.</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<PartySegmentedControl
|
<PartySegmentedControl
|
||||||
selectedTab={activeTab}
|
selectedTab={activeTab}
|
||||||
onTabChange={handleTabChange}
|
onTabChange={handleTabChange}
|
||||||
|
|
@ -1149,4 +1164,21 @@
|
||||||
background: color.adjust($error, $lightness: -10%);
|
background: color.adjust($error, $lightness: -10%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.orphan-warning {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $unit;
|
||||||
|
padding: $unit $unit-2x;
|
||||||
|
background: rgba(209, 137, 58, 0.15);
|
||||||
|
border: 1px solid rgba(209, 137, 58, 0.4);
|
||||||
|
border-radius: $unit-half;
|
||||||
|
color: #c47a1a;
|
||||||
|
font-size: $font-small;
|
||||||
|
|
||||||
|
:global(svg) {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #c47a1a;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="unit {elementClass}" class:empty={!item} class:is-active={isActive}>
|
<div class="unit {elementClass}" class:empty={!item} class:is-active={isActive} class:orphaned={item?.orphaned}>
|
||||||
{#if item}
|
{#if item}
|
||||||
<UnitMenuContainer showGearButton={true}>
|
<UnitMenuContainer showGearButton={true}>
|
||||||
{#snippet trigger()}
|
{#snippet trigger()}
|
||||||
|
|
@ -218,6 +218,11 @@
|
||||||
title="Perpetuity Ring"
|
title="Perpetuity Ring"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if item?.orphaned}
|
||||||
|
<div class="orphaned-badge" title="This item is no longer in your collection">
|
||||||
|
<Icon name="alertTriangle" size={16} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#if imageUrl}
|
{#if imageUrl}
|
||||||
<img
|
<img
|
||||||
class="image {elementClass}"
|
class="image {elementClass}"
|
||||||
|
|
@ -617,4 +622,34 @@
|
||||||
color: colors.$grey-40;
|
color: colors.$grey-40;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.orphaned-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: #d13a3a;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: help;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Orphaned state
|
||||||
|
.unit.orphaned {
|
||||||
|
.frame {
|
||||||
|
opacity: 0.7;
|
||||||
|
border: 2px solid #d13a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: #d13a3a;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="unit {elementClass}" class:empty={!item} class:is-active={isActive}>
|
<div class="unit {elementClass}" class:empty={!item} class:is-active={isActive} class:orphaned={item?.orphaned}>
|
||||||
{#if item}
|
{#if item}
|
||||||
<UnitMenuContainer showGearButton={true}>
|
<UnitMenuContainer showGearButton={true}>
|
||||||
{#snippet trigger()}
|
{#snippet trigger()}
|
||||||
|
|
@ -129,6 +129,11 @@
|
||||||
class:is-active={isActive}
|
class:is-active={isActive}
|
||||||
onclick={() => viewDetails()}
|
onclick={() => viewDetails()}
|
||||||
>
|
>
|
||||||
|
{#if item?.orphaned}
|
||||||
|
<div class="orphaned-badge" title="This item is no longer in your collection">
|
||||||
|
<Icon name="alertTriangle" size={16} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<img
|
<img
|
||||||
class="image {elementClass}"
|
class="image {elementClass}"
|
||||||
class:placeholder={!item?.summon?.granblueId}
|
class:placeholder={!item?.summon?.granblueId}
|
||||||
|
|
@ -346,6 +351,36 @@
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.orphaned-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: #d13a3a;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: help;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Orphaned state
|
||||||
|
.unit.orphaned {
|
||||||
|
.frame {
|
||||||
|
opacity: 0.7;
|
||||||
|
border: 2px solid #d13a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: #d13a3a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Pulsing focus ring animation
|
// Pulsing focus ring animation
|
||||||
@keyframes pulse-focus-ring {
|
@keyframes pulse-focus-ring {
|
||||||
0%, 100% {
|
0%, 100% {
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,7 @@
|
||||||
class:empty={!item}
|
class:empty={!item}
|
||||||
class:extra={position >= 9}
|
class:extra={position >= 9}
|
||||||
class:is-active={isActive}
|
class:is-active={isActive}
|
||||||
|
class:orphaned={item?.orphaned}
|
||||||
>
|
>
|
||||||
{#if item}
|
{#if item}
|
||||||
<UnitMenuContainer showGearButton={true}>
|
<UnitMenuContainer showGearButton={true}>
|
||||||
|
|
@ -173,6 +174,11 @@
|
||||||
onclick={() => viewDetails()}
|
onclick={() => viewDetails()}
|
||||||
>
|
>
|
||||||
<div class="modifiers">
|
<div class="modifiers">
|
||||||
|
{#if item?.orphaned}
|
||||||
|
<div class="orphaned-badge" title="This item is no longer in your collection">
|
||||||
|
<Icon name="alertTriangle" size={16} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#if awakeningImage}
|
{#if awakeningImage}
|
||||||
<img
|
<img
|
||||||
class="awakening"
|
class="awakening"
|
||||||
|
|
@ -448,6 +454,24 @@
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
|
.orphaned-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: #d13a3a;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: help;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
.awakening {
|
.awakening {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
|
|
@ -467,6 +491,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Orphaned state
|
||||||
|
.unit.orphaned {
|
||||||
|
.frame {
|
||||||
|
opacity: 0.7;
|
||||||
|
border: 2px solid #d13a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: #d13a3a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Position modifiers for grid weapons
|
// Position modifiers for grid weapons
|
||||||
.frame.weapon.cell {
|
.frame.weapon.cell {
|
||||||
.awakening {
|
.awakening {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ export interface GridWeapon {
|
||||||
collectionWeaponId?: string
|
collectionWeaponId?: string
|
||||||
/** Whether the grid item is out of sync with its collection source */
|
/** Whether the grid item is out of sync with its collection source */
|
||||||
outOfSync?: boolean
|
outOfSync?: boolean
|
||||||
|
/** Whether the linked collection item has been deleted (item is orphaned) */
|
||||||
|
orphaned?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// GridCharacter from GridCharacterBlueprint
|
// GridCharacter from GridCharacterBlueprint
|
||||||
|
|
@ -60,6 +62,8 @@ export interface GridCharacter {
|
||||||
collectionCharacterId?: string
|
collectionCharacterId?: string
|
||||||
/** Whether the grid item is out of sync with its collection source */
|
/** Whether the grid item is out of sync with its collection source */
|
||||||
outOfSync?: boolean
|
outOfSync?: boolean
|
||||||
|
/** Whether the linked collection item has been deleted (item is orphaned) */
|
||||||
|
orphaned?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// GridSummon from GridSummonBlueprint
|
// GridSummon from GridSummonBlueprint
|
||||||
|
|
@ -76,6 +80,8 @@ export interface GridSummon {
|
||||||
collectionSummonId?: string
|
collectionSummonId?: string
|
||||||
/** Whether the grid item is out of sync with its collection source */
|
/** Whether the grid item is out of sync with its collection source */
|
||||||
outOfSync?: boolean
|
outOfSync?: boolean
|
||||||
|
/** Whether the linked collection item has been deleted (item is orphaned) */
|
||||||
|
orphaned?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// JobSkillList for party job skills
|
// JobSkillList for party job skills
|
||||||
|
|
@ -115,6 +121,8 @@ export interface Party {
|
||||||
extra?: boolean
|
extra?: boolean
|
||||||
remix?: boolean
|
remix?: boolean
|
||||||
editKey?: string
|
editKey?: string
|
||||||
|
/** Whether the party contains any orphaned grid items */
|
||||||
|
hasOrphanedItems?: boolean
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
weapons: GridWeapon[]
|
weapons: GridWeapon[]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue