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.