Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 7 additions & 34 deletions src/lib/components/common/item_holder_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ import {
isCharm,
isHighlightCharm,
} from '../../utils/skin';
import {isSkin, floor} from '../../utils/skin';
import {isSkin} from '../../utils/skin';
import {getRankColour} from '../../utils/ranks';
import {Observe} from '../../utils/observers';
import {FetchBluegem, FetchBluegemResponse} from '../../bridge/handlers/fetch_bluegem';
import {ClientSend} from '../../bridge/client';
import {renderBluegemPercentage, renderFadePercentage, patternDetailStyles} from './pattern_details';

// Generic annotator of item holder metadata (float, seed, etc...)
// Must be extended to use as a component
export abstract class ItemHolderMetadata extends FloatElement {
static styles = [
...FloatElement.styles,
patternDetailStyles,
css`
.float {
position: absolute;
Expand All @@ -40,32 +42,10 @@ export abstract class ItemHolderMetadata extends FloatElement {
font-size: 12px;
}

.fade-base {
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}

.fade {
background-image: -webkit-linear-gradient(0deg, #d9bba5 0%, #e5903b 33%, #db5977 66%, #6775e1 100%);
}

.amber-fade {
background-image: -webkit-linear-gradient(0deg, #627d66 0%, #896944 50%, #7e4201 100%);
}

.acid-fade {
background-image: -webkit-linear-gradient(0deg, #2b441b 0%, #3e6b2f 11%, #82a64a 66%, #c1a16c 100%);
}

.csfloat-shine-fade-text {
font-weight: 1000;
-webkit-text-stroke: 1px black;
}

.bluegem {
color: deepskyblue;
}
`,
];

Expand Down Expand Up @@ -108,7 +88,7 @@ export abstract class ItemHolderMetadata extends FloatElement {
if (!this.itemInfo || !this.asset) return html``;

if (isSkin(this.asset)) {
const fadeDetails = this.asset && getFadePercentage(this.asset, this.itemInfo);
const fadeDetails = this.asset && getFadePercentage(this.asset.market_hash_name, this.itemInfo);

if (fadeDetails?.percentage === 100) {
$J(this).parent().addClass('full-fade-border');
Expand All @@ -121,17 +101,10 @@ export abstract class ItemHolderMetadata extends FloatElement {
<span class="float">${formatFloatWithRank(this.itemInfo, 6)}</span>
<span class="seed">
${formatSeed(this.itemInfo)}
${fadeDetails !== undefined
? html`<span
class="fade-base ${fadeDetails.className} ${rank && rank <= 5
? 'csfloat-shine-fade-text'
: ''}"
>(${floor(fadeDetails.percentage, 1)}%)</span
>`
: nothing}
${this.bluegemData
? html`<span class="bluegem">(${floor(this.bluegemData.playside_blue, 1)}%)</span>`
${fadeDetails
? renderFadePercentage(fadeDetails, 1, rank && rank <= 5 ? 'csfloat-shine-fade-text' : '')
: nothing}
${this.bluegemData ? renderBluegemPercentage(this.bluegemData) : nothing}
</span>
</span>
`;
Expand Down
57 changes: 57 additions & 0 deletions src/lib/components/common/pattern_details.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {css, html, TemplateResult} from 'lit';

import {floor} from '../../utils/skin';
import {FetchBluegemResponse} from '../../bridge/handlers/fetch_bluegem';

export const patternDetailStyles = css`
.fade-base {
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}

.fade {
background-image: -webkit-linear-gradient(0deg, #d9bba5 0%, #e5903b 33%, #db5977 66%, #6775e1 100%);
}

.amber-fade {
background-image: -webkit-linear-gradient(0deg, #627d66 0%, #896944 50%, #7e4201 100%);
}

.acid-fade {
background-image: -webkit-linear-gradient(0deg, #2b441b 0%, #3e6b2f 11%, #82a64a 66%, #c1a16c 100%);
}

.bluegem {
color: deepskyblue;
}
`;

/**
* Renders HTML for the fade percentage.
* Requires the component to include {@link patternDetailStyles} in its static styles.
*/
export function renderFadePercentage(
fade: {percentage: number; className: string},
precision = 1,
extraClasses = ''
): TemplateResult<1> {
return html`<span class="fade-base ${fade.className} ${extraClasses}"
>(${floor(fade.percentage, precision)}%)</span
>`;
}

/**
* Renders HTML for the blue gem percentage.
* Requires the component to include {@link patternDetailStyles} in its static styles.
*/
export function renderBluegemPercentage(bluegemData: FetchBluegemResponse, showBackside = false): TemplateResult<1> {
// Some skins got only one blue value
if (showBackside && bluegemData.backside_blue !== undefined) {
return html`<span class="bluegem"
>(${floor(bluegemData.playside_blue, 1)}% / ${floor(bluegemData.backside_blue, 1)}%)</span
>`;
}

return html`<span class="bluegem">(${floor(bluegemData.playside_blue, 1)}%)</span>`;
}
5 changes: 4 additions & 1 deletion src/lib/components/inventory/selected_item_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,10 @@ export class SelectedItemInfo extends FloatElement {
containerChildren.push(html`<div>Paint Seed: ${formatSeed(this.itemInfo)}</div>`);

// Fade skins
const fadePercentage = getFadePercentage(this.asset.description, this.itemInfo)?.percentage;
const fadePercentage = getFadePercentage(
this.asset.description.market_hash_name,
this.itemInfo
)?.percentage;
if (fadePercentage !== undefined) {
containerChildren.push(html`<div>Fade: ${floor(fadePercentage, 5)}%</div>`);
}
Expand Down
3 changes: 2 additions & 1 deletion src/lib/components/market/item_row_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ export class ItemRowWrapper extends FloatElement {
}

if (this.itemInfo && isSkin(this.asset)) {
const fadePercentage = this.asset && getFadePercentage(this.asset, this.itemInfo)?.percentage;
const fadePercentage =
this.asset && getFadePercentage(this.asset.market_hash_name, this.itemInfo)?.percentage;

return html`
<div class="float-row-wrapper">
Expand Down
29 changes: 28 additions & 1 deletion src/lib/components/market/react/placement.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import type {ScopedInjectionArgs} from '../../injectors';
import {parseRank} from '../../../utils/skin';
import {getFadePercentage, isBlueSkin, parseRank} from '../../../utils/skin';
import {hasDopplerPhase} from '../../../utils/dopplers';
import type {ReactListingContext} from './listing';

function hasSeedDetail(context: ReactListingContext): boolean {
return (
getFadePercentage(context.listing.description.market_hash_name, context.itemInfo) !== undefined ||
isBlueSkin(context.itemInfo) ||
hasDopplerPhase(context.itemInfo.paintindex)
);
}

export function findWearSpan({
scope,
context,
Expand All @@ -19,3 +28,21 @@ export function findWearSpan({

return undefined;
}

export function findSeedSpan({
scope,
context,
}: ScopedInjectionArgs<ReactListingContext>): HTMLSpanElement | null | undefined {
if (!hasSeedDetail(context)) return null;

const spans = scope.querySelectorAll<HTMLSpanElement>('span[style*="pre-wrap"]');
for (const span of spans) {
const text = span.textContent?.trim();
if (!text) continue;

const value = parseInt(text, 10);
if (!Number.isNaN(value) && String(value) === text && value === context.itemInfo.paintseed) return span;
}

return undefined;
}
89 changes: 89 additions & 0 deletions src/lib/components/market/react/seed_info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {css, html, nothing} from 'lit';
import {property, state} from 'lit/decorators.js';

import {CustomElement, InjectIntoScope, InjectionPosition} from '../../injectors';
import {FloatElement} from '../../custom';
import {getFadePercentage, isBlueSkin} from '../../../utils/skin';
import {getDopplerPhase, hasDopplerPhase} from '../../../utils/dopplers';
import {ClientSend} from '../../../bridge/client';
import {FetchBluegem, FetchBluegemResponse} from '../../../bridge/handlers/fetch_bluegem';
import {renderBluegemPercentage, renderFadePercentage, patternDetailStyles} from '../../common/pattern_details';
import {ReactMarketListingScope, type ReactListingContext} from './listing';
import {findSeedSpan} from './placement';
import type {ItemInfo} from '../../../bridge/handlers/fetch_inspect_info';

/**
* Renders the fade percentage and blue-gem percentage next to the paint seed in the Steam Market beta,
* mirroring {@link ReactListingRank}. Self-contained: fetches its own blue-gem data.
*/
@CustomElement()
@InjectIntoScope(ReactMarketListingScope, {
anchor: findSeedSpan,
position: InjectionPosition.After,
})
export class ReactListingSeedInfo extends FloatElement {
@property({attribute: false}) injectionContext?: ReactListingContext;

@state() private bluegemData: FetchBluegemResponse | undefined;

static styles = [
patternDetailStyles,
css`
:host {
margin-left: 4px;
}
`,
];

async connectedCallback(): Promise<void> {
super.connectedCallback();

const itemInfo = this.injectionContext?.itemInfo;
if (itemInfo) {
this.bluegemData = await this.fetchBluegem(itemInfo);
}
}

private async fetchBluegem(itemInfo: ItemInfo): Promise<FetchBluegemResponse | undefined> {
if (!isBlueSkin(itemInfo)) return undefined;

try {
return await ClientSend(FetchBluegem, {iteminfo: itemInfo});
} catch (e) {
return undefined;
}
}

private get fadeDetails(): {percentage: number; className: string} | undefined {
const context = this.injectionContext;
if (!context) return undefined;

return getFadePercentage(context.listing.description.market_hash_name, context.itemInfo);
}

private get dopplerPhase(): string | undefined {
const itemInfo = this.injectionContext?.itemInfo;
if (!itemInfo || !hasDopplerPhase(itemInfo.paintindex)) return undefined;

return getDopplerPhase(itemInfo.paintindex);
}

protected render() {
if (!this.injectionContext) return nothing;

if (this.fadeDetails) {
return renderFadePercentage(this.fadeDetails, 2);
}

if (this.bluegemData) {
return renderBluegemPercentage(this.bluegemData, true);
}

const phase = this.dopplerPhase;
if (phase) {
return html`<span>(${phase})</span>`;
}

return nothing;
}
}
4 changes: 2 additions & 2 deletions src/lib/components/market/sort_listings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class SortListings extends FloatElement {

const asset = g_rgAssets[AppId.CSGO][ContextId.PRIMARY][listingInfo.asset.id];

return getFadeParams(asset) !== undefined;
return getFadeParams(asset.market_hash_name) !== undefined;
}

connectedCallback() {
Expand Down Expand Up @@ -130,7 +130,7 @@ export class SortListings extends FloatElement {
info,
listingId: listingId!,
converted_price: listingInfo?.converted_price || 0,
fadePercentage: (asset && getFadePercentage(asset, info)?.percentage) || 0,
fadePercentage: (asset && getFadePercentage(asset.market_hash_name, info)?.percentage) || 0,
};
} catch (error) {
console.error(`CSFloat: Failed to fetch float for listing ${listingId}:`, error);
Expand Down
1 change: 1 addition & 0 deletions src/lib/page_scripts/market_listing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {init} from './utils';
import '../components/market/item_row_wrapper';
import '../components/market/utility_belt';
import '../components/market/react/rank';
import '../components/market/react/seed_info';

init('src/lib/page_scripts/market_listing.js', main);

Expand Down
8 changes: 4 additions & 4 deletions src/lib/utils/skin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export function isBlueSkin(itemInfo: ItemInfo): boolean {
);
}

export function getFadeParams(asset: rgAsset):
export function getFadeParams(marketHashName: string):
| {
calculator: typeof FadeCalculator | typeof AcidFadeCalculator | typeof AmberFadeCalculator;
weaponName: string;
Expand All @@ -198,7 +198,7 @@ export function getFadeParams(asset: rgAsset):

for (const [fadeType, calculator] of Object.entries(FADE_TYPE_TO_CALCULATOR)) {
for (const supportedWeapon of calculator.getSupportedWeapons()) {
if (asset.market_hash_name.includes(`${supportedWeapon} | ${fadeType}`)) {
if (marketHashName.includes(`${supportedWeapon} | ${fadeType}`)) {
return {
calculator,
weaponName: supportedWeapon.toString(),
Expand All @@ -210,10 +210,10 @@ export function getFadeParams(asset: rgAsset):
}

export function getFadePercentage(
asset: rgAsset,
marketHashName: string,
itemInfo: ItemInfo
): {percentage: number; className: string} | undefined {
const fadeInfo = getFadeParams(asset);
const fadeInfo = getFadeParams(marketHashName);

if (fadeInfo !== undefined) {
const {calculator, weaponName, className} = fadeInfo;
Expand Down