PageSourceSearch

https://caldariprimeponyclub.com/src/skinr/SkinrCardElement.mjs

js caldariprimeponyclub.com collected 2026-10-07 19:38:10 UTC 6,522 bytes, 220 lines download raw bytes

1import { EveTypeRenderURL } from "../api/EveImageServer.mjs";
2import { LoadImage } from "../ui/ImageRepository.mjs";
3import { QueueImage } from "../ui/ImageQueue.mjs";
4import { Ownership } from "../api/Ownership.mjs";
5
6const RENDER_SIZE = 512;
7
8const PROBES_BEFORE_GIVING_UP = 3;
9
10let paintedProbe = null;
11let probesFailed = 0;
12
13function PaintedAvailable(url)
14{
15    if (probesFailed >= PROBES_BEFORE_GIVING_UP) return Promise.resolve(false);
16
17    if (!paintedProbe)
18    {
19        paintedProbe = LoadImage(url).then(loaded =>
20        {
21            if (!loaded)
22            {
23                probesFailed++;
24                paintedProbe = null;
25            }
26            return loaded;
27        });
28    }
29
30    return paintedProbe;
31}
32
33export function SkinrRenderURL(skinrId, version = renderVersion)
34{
35    const stamp = version ? `?v=${encodeURIComponent(version)}` : "";
36
37    return `/media/previews/skinr/${skinrId}_${RENDER_SIZE}x${RENDER_SIZE}.jpg${stamp}`;
38}
39
40let renderVersion = null;
41let versionAsked = null;
42
43function RenderVersion()
44{
45    versionAsked ??= fetch("/media/previews/skinr/version")
46        .then(response => (response.ok ? response.json() : null))
47        .then(doc =>
48        {
49            renderVersion = doc?.version ?? null;
50
51            return renderVersion;
52        })
53        .catch(() => null);
54
55    return versionAsked;
56}
57
58function CornerOf(entry)
59{
60
61    if (!entry.harvested) return { kind: "sof", text: "?", title: "Listing seen; its design is not harvested yet" };
62    if (entry.state && entry.state !== "listed")
63    {
64        return {
65            kind: "unpublished",
66            text: entry.tierLevel ? String(entry.tierLevel) : "-",
67            title: `Tier ${entry.tierLevel ?? "unknown"} — ${entry.state.toUpperCase()}`
68        };
69    }
70
71    return {
72        kind: "skinr",
73        text: entry.tierLevel ? String(entry.tierLevel) : "-",
74        title: entry.tierLevel ? `Tier ${entry.tierLevel}` : "No tier"
75    };
76}
77
78export class SkinrCardElement
79{
80
81    constructor(entry, { hullName = null, size = 256, tag = true, name = true, price = true, hull = true } = {})
82    {
83        this.entry = entry;
84        this.hullName = hullName;
85        this.size = size;
86        this.showHull = hull;
87
88        const el = this.element = document.createElement("div");
89
90        el.className = "sof-element";
91
92        if (Ownership.HasDesign(entry.skinrId)) el.classList.add("owned");
93
94        el.dataset.listingId = entry.listingId;
95        if (entry.skinrId) el.dataset.skinrId = entry.skinrId;
96        if (entry.typeID) el.dataset.shipTypeId = String(entry.typeID);
97
98        const frame = document.createElement("div");
99        const corner = CornerOf(entry);
100
101        frame.className = "sof-element-preview";
102
103        this.artEl = document.createElement("div");
104        this.artEl.className = "sof-preview pending";
105        this.imgEl = document.createElement("img");
106        this.imgEl.alt = "";
107        this.artEl.appendChild(this.imgEl);
108
109        const tagEl = document.createElement("span");
110
111        tagEl.className = `skinr-tier ${corner.kind}`;
112        tagEl.textContent = corner.text;
113        tagEl.title = corner.title;
114
115        if (entry.origin && entry.origin !== "skinr")
116        {
117            const originEl = document.createElement("span");
118
119            originEl.className = `sof-badge origin ${entry.origin}`;
120            originEl.textContent = String(entry.origin).toUpperCase();
121            originEl.title = "Made on this site. Not available in the SKINR store.";
122
123            frame.appendChild(originEl);
124        }
125
126        const priceEl = document.createElement("span");
127
128        priceEl.className = "sof-badge";
129
130        if (entry.priceLines?.length)
131        {
132            for (const line of entry.priceLines)
133            {
134                const row = document.createElement("span");
135
136                row.className = `sof-price ${line.kind}${line.estimate ? " estimate" : ""}`;
137
138                row.appendChild(document.createTextNode(line.text));
139
140                if (line.icon)
141                {
142                    const icon = document.createElement("img");
143
144                    icon.src = line.icon;
145                    icon.alt = "";
146                    icon.setAttribute("aria-hidden", "true");
147                    row.appendChild(icon);
148                }
149
150                if (line.tip)
151                {
152                    const tip = document.createElement("span");
153
154                    tip.className = "hub-tip sof-price-tip";
155                    tip.textContent = line.tip;
156                    row.appendChild(tip);
157                }
158
159                priceEl.appendChild(row);
160            }
161        }
162        else
163        {
164            priceEl.textContent = entry.priceText ?? "";
165        }
166
167        frame.append(this.artEl);
168        if (tag) frame.append(tagEl);
169        if (price) frame.append(priceEl);
170
171        this.nameEl = document.createElement("span");
172        this.nameEl.className = "sof-element-name";
173        this.nameEl.textContent = entry.name;
174        this.nameEl.title = entry.harvested ? entry.name : "This listing's design has not been harvested yet";
175
176        this.classEl = document.createElement("span");
177        this.classEl.className = "sof-element-class";
178        this.#Classification();
179
180        el.append(frame);
181        if (name) el.append(this.nameEl);
182        if (hull) el.append(this.classEl);
183    }
184
185    async Resolve()
186    {
187        const hullURL = this.entry.typeID ? EveTypeRenderURL(this.entry.typeID, { size: this.size }) : null;
188
189        const direct = this.entry.iconURL ?? null;
190
191        if (!direct && this.entry.skinrId) await RenderVersion();
192        const wanted = !direct && this.entry.skinrId ? SkinrRenderURL(this.entry.skinrId) : null;
193        const paintedURL = wanted && await PaintedAvailable(wanted) ? wanted : null;
194        this.artEl.classList.add("pending");
195        QueueImage(this.imgEl, direct ? [ direct ] : [ paintedURL, hullURL ], {
196            onLoad: () => this.artEl.classList.remove("pending")
197        });
198
199        if (!this.hullName || !this.entry.typeID) return;
200
201        const named = await this.hullName(this.entry.typeID);
202
203        if (!named) return;
204
205        this.entry.hull = named.name;
206        this.entry.hullGroup = named.group ?? null;
207
208        this.entry.category = named.category ?? null;
209        this.#Classification();
210    }
211
212    #Classification()
213    {
214        const hull = this.entry.hull ?? "";
215
216        this.classEl.textContent = hull;
217        this.classEl.title = hull;
218    }
219
220}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.