components: use centralized image URL helpers

This commit is contained in:
Justin Edmund 2025-12-01 02:26:29 -08:00
parent e9463ae5ba
commit 43c291327c
9 changed files with 48 additions and 28 deletions

View file

@ -1,6 +1,7 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { Party } from '$lib/types/api/party'
import { getGuidebookImage } from '$lib/utils/images'
export let item: any | undefined
export let position: number // 1..3
@ -22,9 +23,7 @@
}
function guidebookImageUrl(g?: any): string {
const id = g?.granblueId
if (!id) return '/images/placeholders/placeholder-weapon-grid.png'
return `/images/guidebooks/book_${id}.png`
return getGuidebookImage(g?.granblueId)
}
async function remove() {

View file

@ -10,6 +10,7 @@
isSkillSlotAvailable,
isSkillSlotLocked
} from '$lib/utils/jobUtils'
import { getAccessoryImage } from '$lib/utils/images'
import Icon from '$lib/components/Icon.svelte'
interface Props {
@ -155,7 +156,7 @@
>
{#if accessory}
<img
src="/images/accessory-square/{accessory.granblueId}.jpg"
src={getAccessoryImage(accessory.granblueId)}
alt={accessory.name.en}
class="accessory-icon"
/>

View file

@ -1,6 +1,7 @@
<script lang="ts">
import type { JobSkill } from '$lib/types/api/entities'
import { getSkillCategoryName } from '$lib/utils/jobUtils'
import { getJobSkillIcon } from '$lib/utils/images'
interface Props {
skill: JobSkill
@ -13,11 +14,7 @@
let { skill, onClick, disabled = false, variant = 'default', onRemove }: Props = $props()
function getSkillIcon(skill: JobSkill): string {
if (skill.slug) {
return `/images/job-skills/${skill.slug}.png`
}
// Fallback if no slug
return '/images/job-skills/default.png'
return getJobSkillIcon(skill.slug)
}
function getSkillColorClass(skill: JobSkill): string {

View file

@ -1,6 +1,7 @@
<script lang="ts">
import type { JobSkill } from '$lib/types/api/entities'
import { getSkillCategoryColor } from '$lib/utils/jobUtils'
import { getJobSkillIcon } from '$lib/utils/images'
import Icon from '$lib/components/Icon.svelte'
import Tooltip from '$lib/components/ui/Tooltip.svelte'
import Button from '$lib/components/ui/Button.svelte'
@ -26,7 +27,7 @@
}: Props = $props()
const categoryColor = $derived(skill ? getSkillCategoryColor(skill) : '')
const skillIconUrl = $derived(skill?.slug ? `/images/job-skills/${skill.slug}.png` : '')
const skillIconUrl = $derived(skill?.slug ? getJobSkillIcon(skill.slug) : '')
const isEditable = $derived(editable && !locked && available)
const isUnavailable = $derived(!available)

View file

@ -3,6 +3,7 @@
<script lang="ts">
import { onMount } from 'svelte'
import { searchAdapter, type SearchResult } from '$lib/api/adapters'
import { getCharacterImage, getWeaponImage, getSummonImage, getPlaceholderImage } from '$lib/utils/images'
interface Props {
open?: boolean
@ -182,10 +183,19 @@
}
function getImageUrl(item: SearchResult): string {
if (!item.granblueId) return '/images/placeholders/placeholder-' + type + '.png'
const id = item.granblueId
if (!id) return getPlaceholderImage(type, 'grid')
const folder = type === 'weapon' ? 'weapon-grid' : type
return `/images/${folder}/${item.granblueId}.jpg`
switch (type) {
case 'character':
return getCharacterImage(id, 'grid', '01')
case 'weapon':
return getWeaponImage(id, 'grid')
case 'summon':
return getSummonImage(id, 'grid')
default:
return getPlaceholderImage(type, 'grid')
}
}
function getItemName(item: SearchResult): string {

View file

@ -9,6 +9,7 @@
import AwakeningSelect from './edit/AwakeningSelect.svelte'
import AxSkillSelect from './edit/AxSkillSelect.svelte'
import Button from '$lib/components/ui/Button.svelte'
import { getElementIcon } from '$lib/utils/images'
interface Props {
weapon: GridWeapon
@ -78,12 +79,12 @@
// Element options
const elementOptions = [
{ value: 1, label: 'Wind', image: '/images/elements/element-wind.png' },
{ value: 2, label: 'Fire', image: '/images/elements/element-fire.png' },
{ value: 3, label: 'Water', image: '/images/elements/element-water.png' },
{ value: 4, label: 'Earth', image: '/images/elements/element-earth.png' },
{ value: 5, label: 'Dark', image: '/images/elements/element-dark.png' },
{ value: 6, label: 'Light', image: '/images/elements/element-light.png' }
{ value: 1, label: 'Wind', image: getElementIcon(1) },
{ value: 2, label: 'Fire', image: getElementIcon(2) },
{ value: 3, label: 'Water', image: getElementIcon(3) },
{ value: 4, label: 'Earth', image: getElementIcon(4) },
{ value: 5, label: 'Dark', image: getElementIcon(5) },
{ value: 6, label: 'Light', image: getElementIcon(6) }
]
function displayName(input: any): string {

View file

@ -2,6 +2,7 @@
import type { GridCharacter } from '$lib/types/api/party'
import { formatRingStat, formatEarringStat } from '$lib/utils/modificationFormatters'
import { getRingStat, getElementalizedEarringStat } from '$lib/utils/masteryUtils'
import { getMasteryImage } from '$lib/utils/images'
import { getLocale } from '$lib/paraglide/runtime.js'
interface Props {
@ -29,7 +30,7 @@
if (!stat || !stat.slug) return null
return `/images/mastery/${stat.slug}.png`
return getMasteryImage(stat.slug)
}
</script>

View file

@ -7,6 +7,16 @@
import type { Job, JobSkill } from '$lib/types/api/entities'
import type { JobSkillList } from '$lib/types/api/party'
import { getImageBaseUrl } from '$lib/api/adapters/config'
/**
* Gets the base path for images
* Returns AWS S3/CDN URL if configured, otherwise local /images path
*/
function getBasePath(): string {
const remoteUrl = getImageBaseUrl()
return remoteUrl || '/images'
}
/**
* Gender options for job portraits
@ -29,7 +39,7 @@ export function getJobPortraitUrl(job: Job | undefined, gender: Gender = Gender.
const slug = job.name.en.toLowerCase().replace(/\s+/g, '-')
const genderSuffix = gender === Gender.Djeeta ? 'b' : 'a'
return `/images/job-portraits/${slug}_${genderSuffix}.png`
return `${getBasePath()}/job-portraits/${slug}_${genderSuffix}.png`
}
/**
@ -43,20 +53,19 @@ export function getJobFullImageUrl(job: Job | undefined, gender: Gender = Gender
const genderSuffix = gender === Gender.Djeeta ? 'b' : 'a'
return `/images/job-zoom/${job.granblueId}_${genderSuffix}.png`
return `${getBasePath()}/job-zoom/${job.granblueId}_${genderSuffix}.png`
}
/**
* Generate job icon URL
* Job icons are small square icons representing the job
* Images are stored locally in /static/images/job-icons/
*/
export function getJobIconUrl(granblueId: string | undefined): string {
if (!granblueId) {
return '/images/placeholders/placeholder-weapon-grid.png'
}
return `/images/job-icons/${granblueId}.png`
return `${getBasePath()}/job-icons/${granblueId}.png`
}
/**
@ -69,7 +78,7 @@ export function getJobWideImageUrl(job: Job | undefined, gender: Gender = Gender
}
const genderSuffix = gender === Gender.Djeeta ? 'b' : 'a'
return `/images/job-wide/${job.granblueId}_${genderSuffix}.jpg`
return `${getBasePath()}/job-wide/${job.granblueId}_${genderSuffix}.jpg`
}
/**

View file

@ -4,6 +4,7 @@
import type { Awakening, WeaponKey } from '$lib/types/api/entities'
import type { SimpleAxSkill } from '$lib/types/SimpleAxSkill'
import { getBasePath } from '$lib/utils/images'
/**
* Get the image URL for an awakening type
@ -20,7 +21,7 @@ export function getAwakeningImage(awakening?: { type?: Awakening; level?: number
const isCharacterAwakening = slug.startsWith('character-')
const extension = isCharacterAwakening ? 'jpg' : 'png'
return `/images/awakening/${slug}.${extension}`
return `${getBasePath()}/awakening/${slug}.${extension}`
}
/**
@ -35,7 +36,7 @@ export function getWeaponKeyImage(
): string {
if (!key.slug) return ''
const baseUrl = '/images/weapon-keys/'
const baseUrl = `${getBasePath()}/weapon-keys/`
let filename = key.slug
// Handle element-specific telumas (Draconic weapons)
@ -97,7 +98,7 @@ export function getWeaponKeyImages(
*/
export function getAxSkillImage(axSkill?: { slug?: string }): string | null {
if (!axSkill?.slug) return null
return `/images/ax/${axSkill.slug}.png`
return `${getBasePath()}/ax/${axSkill.slug}.png`
}
/**