components: update party and character components
This commit is contained in:
parent
c3ed9b2885
commit
af659b9760
5 changed files with 62 additions and 216 deletions
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { GridCharacter } from '$lib/types/api/party'
|
import type { GridCharacter } from '$lib/types/api/party'
|
||||||
import type { Job } from '$lib/types/api/entities'
|
|
||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import type { PartyContext } from '$lib/types/party-context'
|
import type { PartyContext } from '$lib/types/party-context'
|
||||||
import type { DragDropContext } from '$lib/composables/drag-drop.svelte'
|
import type { DragDropContext } from '$lib/composables/drag-drop.svelte'
|
||||||
|
|
@ -13,7 +12,6 @@
|
||||||
characters?: GridCharacter[] | undefined
|
characters?: GridCharacter[] | undefined
|
||||||
mainWeaponElement?: number | null | undefined
|
mainWeaponElement?: number | null | undefined
|
||||||
partyElement?: number | null | undefined
|
partyElement?: number | null | undefined
|
||||||
job?: Job | undefined
|
|
||||||
container?: string | undefined
|
container?: string | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,7 +19,6 @@
|
||||||
characters = [],
|
characters = [],
|
||||||
mainWeaponElement = undefined,
|
mainWeaponElement = undefined,
|
||||||
partyElement = undefined,
|
partyElement = undefined,
|
||||||
job = undefined,
|
|
||||||
container = 'main-characters'
|
container = 'main-characters'
|
||||||
}: Props = $props()
|
}: Props = $props()
|
||||||
|
|
||||||
|
|
@ -50,7 +47,6 @@
|
||||||
{#each characterSlots as character, i}
|
{#each characterSlots as character, i}
|
||||||
<li
|
<li
|
||||||
aria-label={`Character slot ${i}`}
|
aria-label={`Character slot ${i}`}
|
||||||
class:main-character={i === 0}
|
|
||||||
class:Empty={!character}
|
class:Empty={!character}
|
||||||
>
|
>
|
||||||
{#if dragContext}
|
{#if dragContext}
|
||||||
|
|
@ -73,7 +69,6 @@
|
||||||
position={i}
|
position={i}
|
||||||
{mainWeaponElement}
|
{mainWeaponElement}
|
||||||
{partyElement}
|
{partyElement}
|
||||||
job={i === 0 ? job : undefined}
|
|
||||||
/>
|
/>
|
||||||
</DraggableItem>
|
</DraggableItem>
|
||||||
</DropZone>
|
</DropZone>
|
||||||
|
|
@ -83,7 +78,6 @@
|
||||||
position={i}
|
position={i}
|
||||||
{mainWeaponElement}
|
{mainWeaponElement}
|
||||||
{partyElement}
|
{partyElement}
|
||||||
job={i === 0 ? job : undefined}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, getContext, setContext } from 'svelte'
|
import { onMount, getContext, setContext, onDestroy } from 'svelte'
|
||||||
import type { Party, GridCharacter, GridWeapon, GridSummon } from '$lib/types/api/party'
|
import type { Party, GridCharacter, GridWeapon, GridSummon } from '$lib/types/api/party'
|
||||||
|
import { partyStore } from '$lib/stores/partyStore.svelte'
|
||||||
|
|
||||||
// TanStack Query mutations - Grid
|
// TanStack Query mutations - Grid
|
||||||
import {
|
import {
|
||||||
|
|
@ -84,6 +85,16 @@
|
||||||
initial?.id && initial?.id !== 'new' && Array.isArray(initial?.weapons) ? initial : defaultParty
|
initial?.id && initial?.id !== 'new' && Array.isArray(initial?.weapons) ? initial : defaultParty
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Sync party to global store for components outside the party context (like DetailsSidebar)
|
||||||
|
$effect(() => {
|
||||||
|
partyStore.setParty(party)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Clear store on unmount
|
||||||
|
onDestroy(() => {
|
||||||
|
partyStore.clear()
|
||||||
|
})
|
||||||
|
|
||||||
let activeTab = $state<GridType>(GridType.Weapon)
|
let activeTab = $state<GridType>(GridType.Weapon)
|
||||||
let loading = $state(false)
|
let loading = $state(false)
|
||||||
let error = $state<string | null>(null)
|
let error = $state<string | null>(null)
|
||||||
|
|
@ -249,14 +260,12 @@
|
||||||
function handleTabChange(tab: GridType) {
|
function handleTabChange(tab: GridType) {
|
||||||
activeTab = tab
|
activeTab = tab
|
||||||
// Update selectedSlot to the first valid empty slot for this tab
|
// Update selectedSlot to the first valid empty slot for this tab
|
||||||
// Characters tab: position 0 is protagonist (not selectable), so start at 1
|
|
||||||
// Weapons/Summons: start at first empty slot
|
|
||||||
const nextEmpty = findNextEmptySlot(party, tab)
|
const nextEmpty = findNextEmptySlot(party, tab)
|
||||||
if (nextEmpty !== SLOT_NOT_FOUND) {
|
if (nextEmpty !== SLOT_NOT_FOUND) {
|
||||||
selectedSlot = nextEmpty
|
selectedSlot = nextEmpty
|
||||||
} else {
|
} else {
|
||||||
// Fallback: Characters start at 1 (skip protagonist), others at 0
|
// Fallback: all grid types start at 0
|
||||||
selectedSlot = tab === GridType.Character ? 1 : 0
|
selectedSlot = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -902,7 +911,6 @@
|
||||||
characters={party.characters}
|
characters={party.characters}
|
||||||
{mainWeaponElement}
|
{mainWeaponElement}
|
||||||
{partyElement}
|
{partyElement}
|
||||||
job={party.job}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
<svelte:options runes={true} />
|
<svelte:options runes={true} />
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from 'svelte'
|
|
||||||
import type { Party, GridCharacter, GridWeapon, GridSummon } from '$lib/types/api/party'
|
import type { Party, GridCharacter, GridWeapon, GridSummon } from '$lib/types/api/party'
|
||||||
import { GridType } from '$lib/types/enums'
|
import { GridType } from '$lib/types/enums'
|
||||||
import SegmentedControl from '$lib/components/ui/segmented-control/SegmentedControl.svelte'
|
import SegmentedControl from '$lib/components/ui/segmented-control/SegmentedControl.svelte'
|
||||||
|
|
@ -26,8 +25,6 @@
|
||||||
const weapons = $derived(party.weapons)
|
const weapons = $derived(party.weapons)
|
||||||
const summons = $derived(party.summons)
|
const summons = $derived(party.summons)
|
||||||
const characters = $derived(party.characters)
|
const characters = $derived(party.characters)
|
||||||
const job = $derived(party.job)
|
|
||||||
const element = $derived(party.element)
|
|
||||||
|
|
||||||
// Handle value changes
|
// Handle value changes
|
||||||
let value = $state(selectedTab)
|
let value = $state(selectedTab)
|
||||||
|
|
@ -41,10 +38,6 @@
|
||||||
onTabChange?.(newValue as GridType)
|
onTabChange?.(newValue as GridType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user gender from context if available
|
|
||||||
// This would typically come from auth/account state
|
|
||||||
const accountContext = getContext<any>('account')
|
|
||||||
const userGender = $derived(accountContext?.user?.gender ?? 0)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class={className}>
|
<nav class={className}>
|
||||||
|
|
@ -54,12 +47,7 @@
|
||||||
label={m.party_segmented_control_characters()}
|
label={m.party_segmented_control_characters()}
|
||||||
selected={value === GridType.Character}
|
selected={value === GridType.Character}
|
||||||
>
|
>
|
||||||
<CharacterRep
|
<CharacterRep characters={characters} />
|
||||||
job={job}
|
|
||||||
element={element}
|
|
||||||
gender={userGender}
|
|
||||||
characters={characters}
|
|
||||||
/>
|
|
||||||
</RepSegment>
|
</RepSegment>
|
||||||
|
|
||||||
<RepSegment
|
<RepSegment
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,21 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Party, GridWeapon, GridCharacter } from '$lib/types/api/party'
|
import type { Party, GridWeapon, GridCharacter } from '$lib/types/api/party'
|
||||||
import type { Job } from '$lib/types/api/entities'
|
|
||||||
import { getElementClass } from '$lib/utils/element'
|
|
||||||
import { getCharacterImageWithPose } from '$lib/utils/images'
|
import { getCharacterImageWithPose } from '$lib/utils/images'
|
||||||
import { getJobPortraitUrl, Gender } from '$lib/utils/jobUtils'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
party?: Party
|
party?: Party
|
||||||
characters?: GridCharacter[]
|
characters?: GridCharacter[]
|
||||||
job?: Job
|
|
||||||
jobId?: string
|
|
||||||
element?: number
|
|
||||||
gender?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let { party, characters: directCharacters, job, jobId, element, gender }: Props = $props()
|
let { party, characters: directCharacters }: Props = $props()
|
||||||
|
|
||||||
// Use direct characters if provided, otherwise get from party
|
// Use direct characters if provided, otherwise get from party
|
||||||
const characters = $derived(directCharacters || party?.characters || [])
|
const characters = $derived(directCharacters || party?.characters || [])
|
||||||
const grid = $derived(Array.from({ length: 3 }, (_, i) =>
|
// Show 5 characters at positions 0-4
|
||||||
|
const grid = $derived(Array.from({ length: 5 }, (_, i) =>
|
||||||
characters.find((c: GridCharacter) => c?.position === i)
|
characters.find((c: GridCharacter) => c?.position === i)
|
||||||
))
|
))
|
||||||
|
|
||||||
// Get job from party if not directly provided
|
|
||||||
const currentJob = $derived(job || party?.job)
|
|
||||||
const genderValue = $derived(gender !== undefined ? gender : 0) // Default to Gran if not specified
|
|
||||||
|
|
||||||
const protagonistClass = $derived(
|
|
||||||
// If element is directly provided, use it
|
|
||||||
element ? getElementClass(element) :
|
|
||||||
// Otherwise try to get from party's mainhand weapon
|
|
||||||
party ? (() => {
|
|
||||||
const main: GridWeapon | undefined = (party.weapons || []).find(
|
|
||||||
(w: GridWeapon) => w?.mainhand || w?.position === -1
|
|
||||||
)
|
|
||||||
const el = main?.element ?? main?.weapon?.element
|
|
||||||
return getElementClass(el) || ''
|
|
||||||
})() : ''
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get job portrait URL if job is available
|
|
||||||
const jobPortraitUrl = $derived(
|
|
||||||
currentJob ? getJobPortraitUrl(currentJob, genderValue as Gender) : ''
|
|
||||||
)
|
|
||||||
|
|
||||||
function characterImageUrl(c?: GridCharacter): string {
|
function characterImageUrl(c?: GridCharacter): string {
|
||||||
const id = c?.character?.granblueId
|
const id = c?.character?.granblueId
|
||||||
if (!id) return ''
|
if (!id) return ''
|
||||||
|
|
@ -70,16 +42,6 @@
|
||||||
|
|
||||||
<div class="rep">
|
<div class="rep">
|
||||||
<ul class="characters">
|
<ul class="characters">
|
||||||
<li class={`protagonist ${protagonistClass}`} class:empty={!currentJob}>
|
|
||||||
{#if currentJob && jobPortraitUrl}
|
|
||||||
<img
|
|
||||||
alt="{currentJob.name.en} job"
|
|
||||||
src={jobPortraitUrl}
|
|
||||||
loading="lazy"
|
|
||||||
decoding="async"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</li>
|
|
||||||
{#each grid as c, i}
|
{#each grid as c, i}
|
||||||
<li class="character" class:empty={!c}>
|
<li class="character" class:empty={!c}>
|
||||||
{#if c}<img
|
{#if c}<img
|
||||||
|
|
@ -96,7 +58,6 @@
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use '$src/themes/layout' as *;
|
@use '$src/themes/layout' as *;
|
||||||
@use '$src/themes/spacing' as *;
|
@use '$src/themes/spacing' as *;
|
||||||
@use '$src/themes/rep' as rep;
|
|
||||||
|
|
||||||
.rep {
|
.rep {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -106,14 +67,13 @@
|
||||||
|
|
||||||
.characters {
|
.characters {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(5, 1fr);
|
||||||
gap: $unit-half;
|
gap: $unit-half;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
||||||
.character,
|
.character {
|
||||||
.protagonist {
|
|
||||||
aspect-ratio: 16/33;
|
aspect-ratio: 16/33;
|
||||||
background: var(--placeholder-bg);
|
background: var(--placeholder-bg);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
@ -125,62 +85,13 @@
|
||||||
background: var(--placeholder-bg);
|
background: var(--placeholder-bg);
|
||||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.character img {
|
img {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.protagonist {
|
|
||||||
border-color: transparent;
|
|
||||||
border-width: 1px;
|
|
||||||
border-style: solid;
|
|
||||||
@include rep.aspect(32, 66);
|
|
||||||
|
|
||||||
img {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.wind {
|
|
||||||
background: var(--wind-portrait-bg);
|
|
||||||
border-color: var(--wind-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.fire {
|
|
||||||
background: var(--fire-portrait-bg);
|
|
||||||
border-color: var(--fire-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.water {
|
|
||||||
background: var(--water-portrait-bg);
|
|
||||||
border-color: var(--water-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.earth {
|
|
||||||
background: var(--earth-portrait-bg);
|
|
||||||
border-color: var(--earth-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.light {
|
|
||||||
background: var(--light-portrait-bg);
|
|
||||||
border-color: var(--light-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.dark {
|
|
||||||
background: var(--dark-portrait-bg);
|
|
||||||
border-color: var(--dark-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.empty {
|
|
||||||
background: var(--placeholder-bg);
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { GridCharacter } from '$lib/types/api/party'
|
import type { GridCharacter } from '$lib/types/api/party'
|
||||||
import type { Party } from '$lib/types/api/party'
|
import type { Party } from '$lib/types/api/party'
|
||||||
import type { Job } from '$lib/types/api/entities'
|
|
||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import Icon from '$lib/components/Icon.svelte'
|
import Icon from '$lib/components/Icon.svelte'
|
||||||
import UnitMenuContainer from '$lib/components/ui/menu/UnitMenuContainer.svelte'
|
import UnitMenuContainer from '$lib/components/ui/menu/UnitMenuContainer.svelte'
|
||||||
|
|
@ -9,7 +8,6 @@
|
||||||
import UncapIndicator from '$lib/components/uncap/UncapIndicator.svelte'
|
import UncapIndicator from '$lib/components/uncap/UncapIndicator.svelte'
|
||||||
import { getCharacterImageWithPose } from '$lib/utils/images'
|
import { getCharacterImageWithPose } from '$lib/utils/images'
|
||||||
import { openDetailsSidebar } from '$lib/features/details/openDetailsSidebar.svelte'
|
import { openDetailsSidebar } from '$lib/features/details/openDetailsSidebar.svelte'
|
||||||
import { getJobPortraitUrl, Gender } from '$lib/utils/jobUtils'
|
|
||||||
import { sidebar } from '$lib/stores/sidebar.svelte'
|
import { sidebar } from '$lib/stores/sidebar.svelte'
|
||||||
import { GridType } from '$lib/types/enums'
|
import { GridType } from '$lib/types/enums'
|
||||||
import perpetuityFilled from '$src/assets/icons/perpetuity/filled.svg'
|
import perpetuityFilled from '$src/assets/icons/perpetuity/filled.svg'
|
||||||
|
|
@ -21,10 +19,9 @@
|
||||||
position: number
|
position: number
|
||||||
mainWeaponElement?: number | null | undefined
|
mainWeaponElement?: number | null | undefined
|
||||||
partyElement?: number | null | undefined
|
partyElement?: number | null | undefined
|
||||||
job?: Job | undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let { item, position, mainWeaponElement, partyElement, job }: Props = $props()
|
let { item, position, mainWeaponElement, partyElement }: Props = $props()
|
||||||
|
|
||||||
type PartyCtx = {
|
type PartyCtx = {
|
||||||
getParty: () => Party
|
getParty: () => Party
|
||||||
|
|
@ -51,16 +48,6 @@
|
||||||
}
|
}
|
||||||
// Use $derived to ensure consistent computation between server and client
|
// Use $derived to ensure consistent computation between server and client
|
||||||
let imageUrl = $derived.by(() => {
|
let imageUrl = $derived.by(() => {
|
||||||
// For protagonist slot (position 0) with a job, show job portrait
|
|
||||||
if (position === 0 && job) {
|
|
||||||
return getJobPortraitUrl(job, Gender.Gran) // TODO: Get gender from user preferences
|
|
||||||
}
|
|
||||||
|
|
||||||
// For protagonist slot without a job, show nothing (relief.png background shows through)
|
|
||||||
if (position === 0 && !job) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no item or no character with granblueId, return placeholder
|
// If no item or no character with granblueId, return placeholder
|
||||||
if (!item || !item.character?.granblueId) {
|
if (!item || !item.character?.granblueId) {
|
||||||
return getCharacterImageWithPose(null, 'main', 0, 0)
|
return getCharacterImageWithPose(null, 'main', 0, 0)
|
||||||
|
|
@ -76,16 +63,12 @@
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check if this is the protagonist slot
|
|
||||||
const isProtagonist = $derived(position === 0)
|
|
||||||
|
|
||||||
// Check if this item is currently active in the sidebar
|
// Check if this item is currently active in the sidebar
|
||||||
let isActive = $derived(item?.id && sidebar.activeItemId === String(item.id))
|
let isActive = $derived(item?.id && sidebar.activeItemId === String(item.id))
|
||||||
|
|
||||||
// Check if this empty slot is currently selected for adding an item
|
// Check if this empty slot is currently selected for adding an item
|
||||||
let isEmptySelected = $derived(
|
let isEmptySelected = $derived(
|
||||||
!item &&
|
!item &&
|
||||||
!isProtagonist &&
|
|
||||||
ctx?.getSelectedSlot?.() === position &&
|
ctx?.getSelectedSlot?.() === position &&
|
||||||
ctx?.getActiveTab?.() === GridType.Character
|
ctx?.getActiveTab?.() === GridType.Character
|
||||||
)
|
)
|
||||||
|
|
@ -194,48 +177,44 @@
|
||||||
{#key item?.id ?? position}
|
{#key item?.id ?? position}
|
||||||
<div
|
<div
|
||||||
class="frame character cell {elementClass}"
|
class="frame character cell {elementClass}"
|
||||||
class:protagonist={position === 0}
|
|
||||||
class:editable={ctx?.canEdit()}
|
class:editable={ctx?.canEdit()}
|
||||||
class:is-active={isActive}
|
class:is-active={isActive}
|
||||||
onclick={() => viewDetails()}
|
onclick={() => viewDetails()}
|
||||||
>
|
>
|
||||||
{#if position !== 0}
|
{#if ctx?.canEdit()}
|
||||||
{#if ctx?.canEdit()}
|
<button
|
||||||
<button
|
class="perpetuity"
|
||||||
class="perpetuity"
|
class:active={item.perpetuity}
|
||||||
class:active={item.perpetuity}
|
onclick={togglePerpetuity}
|
||||||
onclick={togglePerpetuity}
|
title={item.perpetuity ? 'Remove Perpetuity Ring' : 'Add Perpetuity Ring'}
|
||||||
title={item.perpetuity ? 'Remove Perpetuity Ring' : 'Add Perpetuity Ring'}
|
>
|
||||||
>
|
|
||||||
<img
|
|
||||||
class="perpetuity-icon filled"
|
|
||||||
src={perpetuityFilled}
|
|
||||||
alt="Perpetuity Ring"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
class="perpetuity-icon empty"
|
|
||||||
src={perpetuityEmpty}
|
|
||||||
alt="Add Perpetuity Ring"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
{:else if item.perpetuity}
|
|
||||||
<img
|
<img
|
||||||
class="perpetuity static"
|
class="perpetuity-icon filled"
|
||||||
src={perpetuityFilled}
|
src={perpetuityFilled}
|
||||||
alt="Perpetuity Ring"
|
alt="Perpetuity Ring"
|
||||||
title="Perpetuity Ring"
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
<img
|
||||||
|
class="perpetuity-icon empty"
|
||||||
|
src={perpetuityEmpty}
|
||||||
|
alt="Add Perpetuity Ring"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
{:else if item.perpetuity}
|
||||||
|
<img
|
||||||
|
class="perpetuity static"
|
||||||
|
src={perpetuityFilled}
|
||||||
|
alt="Perpetuity Ring"
|
||||||
|
title="Perpetuity Ring"
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if imageUrl}
|
{#if imageUrl}
|
||||||
<img
|
<img
|
||||||
class="image {elementClass}"
|
class="image {elementClass}"
|
||||||
class:placeholder={!item?.character?.granblueId && !isProtagonist}
|
class:placeholder={!item?.character?.granblueId}
|
||||||
class:protagonist={isProtagonist}
|
alt={displayName(item?.character)}
|
||||||
alt={isProtagonist && job ? job.name.en : displayName(item?.character)}
|
src={imageUrl}
|
||||||
src={imageUrl}
|
/>
|
||||||
/>
|
{/if}
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -271,30 +250,19 @@
|
||||||
{#key `empty-${position}`}
|
{#key `empty-${position}`}
|
||||||
<div
|
<div
|
||||||
class="frame character cell"
|
class="frame character cell"
|
||||||
class:editable={ctx?.canEdit() && !isProtagonist}
|
class:editable={ctx?.canEdit()}
|
||||||
class:protagonist={isProtagonist}
|
|
||||||
class:empty-protagonist={isProtagonist && !job}
|
|
||||||
class:is-selected={isEmptySelected}
|
class:is-selected={isEmptySelected}
|
||||||
onclick={() =>
|
onclick={() =>
|
||||||
!isProtagonist &&
|
|
||||||
ctx?.canEdit() &&
|
ctx?.canEdit() &&
|
||||||
ctx?.openPicker &&
|
ctx?.openPicker &&
|
||||||
ctx.openPicker({ type: 'character', position, item })}
|
ctx.openPicker({ type: 'character', position, item })}
|
||||||
>
|
>
|
||||||
{#if !isProtagonist}
|
<img
|
||||||
<img
|
class="image placeholder"
|
||||||
class="image placeholder"
|
alt=""
|
||||||
alt=""
|
src="/images/placeholders/placeholder-weapon-grid.png"
|
||||||
src="/images/placeholders/placeholder-weapon-grid.png"
|
/>
|
||||||
/>
|
{#if ctx?.canEdit()}
|
||||||
{:else if job && imageUrl}
|
|
||||||
<img
|
|
||||||
class="image protagonist"
|
|
||||||
alt={job.name.en}
|
|
||||||
src={imageUrl}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if ctx?.canEdit() && !isProtagonist}
|
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<Icon name="plus" size={24} />
|
<Icon name="plus" size={24} />
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -428,17 +396,6 @@
|
||||||
|
|
||||||
.frame.character.cell {
|
.frame.character.cell {
|
||||||
@include rep.aspect(rep.$char-cell-w, rep.$char-cell-h);
|
@include rep.aspect(rep.$char-cell-w, rep.$char-cell-h);
|
||||||
|
|
||||||
&.protagonist {
|
|
||||||
background-image: url('/images/relief.png'), linear-gradient(to right, #000, #484440, #000);
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center -20px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
|
|
||||||
&.empty-protagonist {
|
|
||||||
background-position: center 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
|
|
@ -453,18 +410,6 @@
|
||||||
&.placeholder {
|
&.placeholder {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.protagonist {
|
|
||||||
object-fit: cover;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.frame.protagonist {
|
|
||||||
// Protagonist slot may have different aspect ratio for job portraits
|
|
||||||
.image {
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue