add context menu to job components
This commit is contained in:
parent
0ab2782697
commit
a88411eb46
3 changed files with 123 additions and 70 deletions
|
|
@ -1,6 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Job } from '$lib/types/api/entities'
|
import type { Job } from '$lib/types/api/entities'
|
||||||
import { getJobIconUrl, formatJobProficiency } from '$lib/utils/jobUtils'
|
import {
|
||||||
|
getJobIconUrl,
|
||||||
|
getJobWideImageUrl,
|
||||||
|
formatJobProficiency,
|
||||||
|
Gender
|
||||||
|
} from '$lib/utils/jobUtils'
|
||||||
import ProficiencyLabel from '../labels/ProficiencyLabel.svelte'
|
import ProficiencyLabel from '../labels/ProficiencyLabel.svelte'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -21,26 +26,34 @@
|
||||||
aria-pressed={selected}
|
aria-pressed={selected}
|
||||||
aria-label="{job.name.en} - {selected ? 'Currently selected' : 'Click to select'}"
|
aria-label="{job.name.en} - {selected ? 'Currently selected' : 'Click to select'}"
|
||||||
>
|
>
|
||||||
<img src={getJobIconUrl(job.granblueId)} alt={job.name.en} class="job-icon" loading="lazy" />
|
<div class="job-image-container">
|
||||||
|
<img
|
||||||
|
src={getJobWideImageUrl(job, Gender.Gran)}
|
||||||
|
alt={job.name.en}
|
||||||
|
class="job-wide"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="job-info">
|
<div class="job-info">
|
||||||
<span class="job-name">{job.name.en}</span>
|
<span class="job-name">{job.name.en}</span>
|
||||||
|
|
||||||
<div class="job-details">
|
{#if proficiencies.length > 0}
|
||||||
{#if job.ultimateMastery}
|
<div class="proficiencies">
|
||||||
<span class="badge ultimate">UM</span>
|
{#each job.proficiency as prof}
|
||||||
{/if}
|
{#if prof > 0}
|
||||||
|
<ProficiencyLabel proficiency={prof} size="small" />
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if proficiencies.length > 0}
|
<div class="job-right">
|
||||||
<div class="proficiencies">
|
{#if job.ultimateMastery}
|
||||||
{#each job.proficiency as prof}
|
<span class="badge ultimate">UM</span>
|
||||||
{#if prof > 0}
|
{/if}
|
||||||
<ProficiencyLabel proficiency={prof} size="small" />
|
<img src={getJobIconUrl(job.granblueId)} alt="" class="job-icon" loading="lazy" />
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
@ -53,7 +66,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: spacing.$unit;
|
gap: spacing.$unit;
|
||||||
padding: spacing.$unit-2x spacing.$unit;
|
padding: spacing.$unit;
|
||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
border-radius: layout.$card-corner;
|
border-radius: layout.$card-corner;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -83,24 +96,26 @@
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.job-icon {
|
.job-image-container {
|
||||||
// Display at native size (job icons are typically 48x48px)
|
position: relative;
|
||||||
width: auto;
|
width: 120px;
|
||||||
height: 24px;
|
border-radius: layout.$item-corner;
|
||||||
max-width: 48px;
|
overflow: hidden;
|
||||||
max-height: 48px;
|
|
||||||
border-radius: 4px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
object-fit: contain;
|
|
||||||
|
.job-wide {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.job-info {
|
.job-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
gap: spacing.$unit-half;
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
.job-name {
|
.job-name {
|
||||||
|
|
@ -112,39 +127,46 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.job-details {
|
.proficiencies {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: spacing.$unit-half;
|
gap: spacing.$unit-half;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.badge {
|
.job-right {
|
||||||
display: inline-block;
|
display: flex;
|
||||||
padding: 2px 6px;
|
align-items: center;
|
||||||
border-radius: 8px;
|
gap: spacing.$unit;
|
||||||
font-size: typography.$font-small;
|
flex-shrink: 0;
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&.master {
|
.badge {
|
||||||
background: var(--badge-master-bg, #ffd700);
|
display: inline-block;
|
||||||
color: var(--badge-master-text, #000);
|
padding: 2px 6px;
|
||||||
}
|
border-radius: 8px;
|
||||||
|
font-size: typography.$font-small;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
|
||||||
&.ultimate {
|
&.master {
|
||||||
background: var(--badge-ultimate-bg, #9b59b6);
|
background: var(--badge-master-bg, #ffd700);
|
||||||
color: var(--badge-ultimate-text, #fff);
|
color: var(--badge-master-text, #000);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.proficiencies {
|
&.ultimate {
|
||||||
display: flex;
|
background: var(--badge-ultimate-bg, #9b59b6);
|
||||||
gap: spacing.$unit-half;
|
color: var(--badge-ultimate-text, #fff);
|
||||||
align-items: center;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.job-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: layout.$item-corner;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -86,23 +86,36 @@
|
||||||
<div class="job-header">
|
<div class="job-header">
|
||||||
{#if canEdit}
|
{#if canEdit}
|
||||||
<button class="job-name clickable" on:click={onSelectJob}>
|
<button class="job-name clickable" on:click={onSelectJob}>
|
||||||
<img src={jobIconUrl} alt="{job.name.en} icon" class="job-icon" />
|
<div class="job-name-row">
|
||||||
<h3>{job.name.en}</h3>
|
<img src={jobIconUrl} alt="{job.name.en} icon" class="job-icon" />
|
||||||
|
<h3>{job.name.en}</h3>
|
||||||
|
</div>
|
||||||
|
{#if job.masterLevel || job.ultimateMastery}
|
||||||
|
<div class="job-badges">
|
||||||
|
{#if job.masterLevel}
|
||||||
|
<span class="badge master">ML{job.masterLevel}</span>
|
||||||
|
{/if}
|
||||||
|
{#if job.ultimateMastery}
|
||||||
|
<span class="badge ultimate">UM</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="job-name">
|
<div class="job-name">
|
||||||
<img src={jobIconUrl} alt="{job.name.en} icon" class="job-icon" />
|
<div class="job-name-row">
|
||||||
<h3>{job.name.en}</h3>
|
<img src={jobIconUrl} alt="{job.name.en} icon" class="job-icon" />
|
||||||
</div>
|
<h3>{job.name.en}</h3>
|
||||||
{/if}
|
</div>
|
||||||
|
{#if job.masterLevel || job.ultimateMastery}
|
||||||
{#if job.masterLevel || job.ultimateMastery}
|
<div class="job-badges">
|
||||||
<div class="job-badges">
|
{#if job.masterLevel}
|
||||||
{#if job.masterLevel}
|
<span class="badge master">ML{job.masterLevel}</span>
|
||||||
<span class="badge master">Master Lv.{job.masterLevel}</span>
|
{/if}
|
||||||
{/if}
|
{#if job.ultimateMastery}
|
||||||
{#if job.ultimateMastery}
|
<span class="badge ultimate">UM</span>
|
||||||
<span class="badge ultimate">Ultimate</span>
|
{/if}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -297,6 +310,7 @@
|
||||||
.job-name {
|
.job-name {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
gap: spacing.$unit;
|
gap: spacing.$unit;
|
||||||
padding: spacing.$unit;
|
padding: spacing.$unit;
|
||||||
border-radius: layout.$card-corner;
|
border-radius: layout.$card-corner;
|
||||||
|
|
@ -316,6 +330,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.job-name-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: spacing.$unit-half;
|
||||||
|
}
|
||||||
|
|
||||||
.job-icon {
|
.job-icon {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,9 @@ export function getJobFullImageUrl(job: Job | undefined, gender: Gender = Gender
|
||||||
return '/images/placeholders/placeholder-weapon-grid.png'
|
return '/images/placeholders/placeholder-weapon-grid.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert job name to slug format (lowercase, spaces to hyphens)
|
|
||||||
const slug = job.name.en.toLowerCase().replace(/\s+/g, '-')
|
|
||||||
const genderSuffix = gender === Gender.Djeeta ? 'b' : 'a'
|
const genderSuffix = gender === Gender.Djeeta ? 'b' : 'a'
|
||||||
|
|
||||||
return `/images/jobs/${slug}_${genderSuffix}.png`
|
return `/images/job-zoom/${job.granblueId}_${genderSuffix}.png`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -61,6 +59,19 @@ export function getJobIconUrl(granblueId: string | undefined): string {
|
||||||
return `/images/job-icons/${granblueId}.png`
|
return `/images/job-icons/${granblueId}.png`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate job wide banner image URL for JobItem component
|
||||||
|
* These are wider banner-style images stored in /static/images/job-wide/
|
||||||
|
*/
|
||||||
|
export function getJobWideImageUrl(job: Job | undefined, gender: Gender = Gender.Gran): string {
|
||||||
|
if (!job) {
|
||||||
|
return '/images/placeholders/placeholder-weapon-grid.png'
|
||||||
|
}
|
||||||
|
|
||||||
|
const genderSuffix = gender === Gender.Djeeta ? 'b' : 'a'
|
||||||
|
return `/images/job-wide/${job.granblueId}_${genderSuffix}.jpg`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get job tier display name
|
* Get job tier display name
|
||||||
* Converts internal row codes to user-friendly names
|
* Converts internal row codes to user-friendly names
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue