diff --git a/src/lib/components/common/item_holder_metadata.ts b/src/lib/components/common/item_holder_metadata.ts index d75f79d7..c2c90b9b 100644 --- a/src/lib/components/common/item_holder_metadata.ts +++ b/src/lib/components/common/item_holder_metadata.ts @@ -4,9 +4,12 @@ import {state} from 'lit/decorators.js'; import {rgAsset} from '../../types/steam'; import {gFloatFetcher} from '../../services/float_fetcher'; import {ItemInfo} from '../../bridge/handlers/fetch_inspect_info'; +import {FetchRecommendedPrice} from '../../bridge/handlers/fetch_recommended_price'; import { + isSellableOnCSFloat, formatFloatWithRank, formatSeed, + formatPrice, getFadePercentage, getLowestRank, isBlueSkin, @@ -25,10 +28,21 @@ export abstract class ItemHolderMetadata extends FloatElement { static styles = [ ...FloatElement.styles, css` + .price { + position: absolute; + bottom: 3px; + left: 3px; + right: 48px; + font-size: 12px; + text-align: left; + z-index: 2; + } + .float { position: absolute; bottom: 3px; right: 3px; + left: 50px; font-size: 12px; text-align: right; } @@ -75,6 +89,9 @@ export abstract class ItemHolderMetadata extends FloatElement { @state() private bluegemData: FetchBluegemResponse | undefined; + @state() + private itemPrice: number | undefined; + get assetId(): string | undefined { return $J(this).parent().attr('id')?.split('_')[2]; } @@ -86,6 +103,11 @@ export abstract class ItemHolderMetadata extends FloatElement { abstract get asset(): rgAsset | undefined; abstract get ownerSteamId(): string | undefined; + get isSellable(): boolean { + if (!this.asset) return false; + return isSellableOnCSFloat(this.asset); + } + get inspectLink(): string | undefined { if (!this.asset) return; @@ -105,7 +127,17 @@ export abstract class ItemHolderMetadata extends FloatElement { } protected render(): HTMLTemplateResult { - if (!this.itemInfo || !this.asset) return html``; + if (!this.asset) return html``; + + if (!this.itemInfo) { + return html` + + ${this.itemPrice + ? html`$${formatPrice(this.itemPrice / 100)}` + : nothing} + + ` + } if (isSkin(this.asset)) { const fadeDetails = this.asset && getFadePercentage(this.asset, this.itemInfo); @@ -118,7 +150,10 @@ export abstract class ItemHolderMetadata extends FloatElement { return html` - ${formatFloatWithRank(this.itemInfo, 6)} + ${formatFloatWithRank(this.itemInfo, 3)} + ${this.itemPrice + ? html`$${formatPrice(this.itemPrice / 100)}` + : nothing} ${formatSeed(this.itemInfo)} ${fadeDetails !== undefined @@ -138,9 +173,10 @@ export abstract class ItemHolderMetadata extends FloatElement { } else if (isCharm(this.asset) && !isHighlightCharm(this.asset)) { return html` - #${this.itemInfo.keychains?.length > 0 ? this.itemInfo.keychains[0].pattern : 'NA'} + ${this.itemPrice + ? html`$${formatPrice(this.itemPrice / 100)}` + : nothing} + #${this.itemInfo.keychains?.length > 0 ? this.itemInfo.keychains[0].pattern : 'NA'} `; } else { @@ -151,14 +187,14 @@ export abstract class ItemHolderMetadata extends FloatElement { async connectedCallback() { super.connectedCallback(); - if (this.inspectLink) { + if (this.isSellable) { this.onInit(); } else { // Wait until the asset exists Observe( - () => this.inspectLink, + () => this.isSellable, () => { - if (this.inspectLink) { + if (this.isSellable) { this.onInit(); } }, @@ -169,19 +205,28 @@ export abstract class ItemHolderMetadata extends FloatElement { async onInit() { if (!this.asset) return; + + if ((isSkin(this.asset) || isCharm(this.asset)) && this.inspectLink && this.assetId) { + try { + this.itemInfo = await gFloatFetcher.fetch({ + link: this.inspectLink, + asset_id: this.assetId, + }); + } catch (e: any) { + console.error(`Failed to fetch float for ${this.assetId}: ${e.toString()}`); + } + } - if (!isSkin(this.asset) && !isCharm(this.asset)) return; - - // Commodities won't have inspect links - if (!this.inspectLink || !this.assetId) return; - - try { - this.itemInfo = await gFloatFetcher.fetch({ - link: this.inspectLink, - asset_id: this.assetId, - }); - } catch (e: any) { - console.error(`Failed to fetch float for ${this.assetId}: ${e.toString()}`); + if (isSellableOnCSFloat(this.asset)) { + try { + const result = await ClientSend(FetchRecommendedPrice, { + market_hash_name: this.asset.market_hash_name, + paint_index: this.itemInfo?.paintindex, + }); + this.itemPrice = result.price; + } catch (e: any) { + console.error(`Failed to fetch price for ${this.asset.market_hash_name}: ${e.toString()}`); + } } if (this.itemInfo) { diff --git a/src/lib/utils/skin.ts b/src/lib/utils/skin.ts index c6408969..81ac25c7 100644 --- a/src/lib/utils/skin.ts +++ b/src/lib/utils/skin.ts @@ -41,8 +41,13 @@ export function parseRank(info: ItemInfo): {order: OrderType; rank: number} | un } } +function truncate(num: number, digits: number): string { + const factor = 10 ** digits; + return (Math.trunc(num * factor) / factor).toFixed(digits); + } + export function formatFloatWithRank(info: ItemInfo, precisionDigits = 14): string { - let r = info.floatvalue.toFixed(precisionDigits); + let r = truncate(info.floatvalue, precisionDigits); const ranked = parseRank(info); if (ranked) { @@ -52,6 +57,16 @@ export function formatFloatWithRank(info: ItemInfo, precisionDigits = 14): strin return r; } +export function formatPrice(price: number): string { + + const formattedPrice = + price >= 100 + ? Math.round(price).toString() + : Number(truncate(price, 2)).toString(); + + return formattedPrice; +} + export function formatSeed(info: ItemInfo): string { let r = info.paintseed.toString();