Files
Wand-Enhancer/web-panel/bridge/scripts/default/installed-apps-sync/artwork.js
T
kitbyte 13759b1db6 feat: ship 1.0.8.0 release automation and runtime overhaul
- reduce ASAR IO overhead with streamed archive reads, buffered copies, faster relative-path handling and placeholder integrity records
- fix in-place app.asar.unpacked packing/extraction self-copy cases that caused locked-file failures
- tighten JS patch discovery with candidate bundle filters and search hints
- require prebuilt remote-panel dist artifacts and clean up embedded bridge/script packaging
- add unified build entrypoints for PowerShell, cmd and bash and move native CMake output under .tmp
- add release metadata validation, changelog section extraction, pre-commit hook and GitHub Actions validation/release pipelines
- make CHANGELOG the source of truth for release notes and document the tag-driven release flow
- add updater release notes UI with latest/full changelog loading and localize the new update strings
- modularize bridge renderer scripts, add installed apps and game status sync, and support remote launch/stop commands
- centralize bridge protocol, IPC and WebSocket constants and improve LAN IP selection for QR pairing
- refactor remote panel controls/state enums, persist accent color, polish library/session UI and refresh assets
2026-05-06 13:07:09 +03:00

256 lines
6.2 KiB
JavaScript

import {
DATA_IMAGE_URL_PREFIX,
IMAGE_FIELD_NAMES,
MAX_IMAGE_URL_SEARCH_DEPTH,
PROTOCOL_RELATIVE_IMAGE_URL_PATTERN,
REMOTE_IMAGE_URL_PATTERN,
SIDEBAR_GAME_ROW_CONTAINER_SELECTOR,
SIDEBAR_GAME_ROW_IMAGE_SELECTOR,
SIDEBAR_GAME_ROW_MORE_SELECTOR,
SIDEBAR_GAME_ROW_TITLE_SELECTOR,
SIDEBAR_GAME_ROW_TOOLTIP_ID_PATTERN,
STEAM_APP_ID_FIELD_NAMES,
STEAM_APP_ID_PATTERN,
STEAM_CONTAINER_FIELD_PATTERN,
STEAM_CONTAINER_ID_FIELD_NAMES,
STEAM_PLATFORM,
WEMOD_STEAM_COMMUNITY_CDN_BASE_URL,
} from "./constants.js"
import { isRecord, safeString, toStringId } from "./runtime.js"
export function pickImageUrl(...values) {
for (const value of values) {
const imageUrl = normalizeImageUrl(value)
if (imageUrl) {
return imageUrl
}
}
return null
}
export function normalizeImageUrl(value, depth = 0) {
if (typeof value === "string") {
const trimmed = value.trim()
if (
REMOTE_IMAGE_URL_PATTERN.test(trimmed) ||
trimmed.startsWith(DATA_IMAGE_URL_PREFIX)
) {
return trimmed
}
if (PROTOCOL_RELATIVE_IMAGE_URL_PATTERN.test(trimmed)) {
return `https:${trimmed}`
}
return null
}
if (depth > MAX_IMAGE_URL_SEARCH_DEPTH || !value) {
return null
}
if (Array.isArray(value)) {
for (const entry of value) {
const imageUrl = normalizeImageUrl(entry, depth + 1)
if (imageUrl) {
return imageUrl
}
}
return null
}
if (!isRecord(value)) {
return null
}
for (const key of IMAGE_FIELD_NAMES) {
const imageUrl = normalizeImageUrl(value[key], depth + 1)
if (imageUrl) {
return imageUrl
}
}
return null
}
export function getSteamClientIconUrl(...values) {
for (const value of values) {
const steamAppId = toStringId(value)
if (steamAppId && STEAM_APP_ID_PATTERN.test(steamAppId)) {
return `${WEMOD_STEAM_COMMUNITY_CDN_BASE_URL}/${steamAppId}/client_icon/96.webp`
}
}
return null
}
export function getSidebarGameRowClientIcons() {
const byTitleId = new Map()
const byTitleName = new Map()
if (typeof document === "undefined") {
return { byTitleId, byTitleName }
}
const containers = document.querySelectorAll(
SIDEBAR_GAME_ROW_CONTAINER_SELECTOR
)
for (const container of containers) {
const imageElement = container.querySelector(
SIDEBAR_GAME_ROW_IMAGE_SELECTOR
)
const titleElement = container.querySelector(
SIDEBAR_GAME_ROW_TITLE_SELECTOR
)
const moreButton = container.querySelector(SIDEBAR_GAME_ROW_MORE_SELECTOR)
if (!imageElement || !titleElement) {
continue
}
const backgroundImageUrl = getCssBackgroundImageUrl(
safeString(imageElement.style?.backgroundImage) ||
getComputedStyle(imageElement).backgroundImage
)
if (!backgroundImageUrl) {
continue
}
const tooltipId = safeString(
moreButton?.getAttribute?.("data-tooltip-trigger-for")
)
const titleId =
tooltipId.match(SIDEBAR_GAME_ROW_TOOLTIP_ID_PATTERN)?.[1] ?? null
const titleName = normalizeTitleMatchKey(titleElement.textContent)
if (titleId) {
byTitleId.set(titleId, backgroundImageUrl)
}
if (titleName) {
byTitleName.set(titleName, backgroundImageUrl)
}
}
return { byTitleId, byTitleName }
}
export function getSidebarGameRowClientIconUrl(
sidebarIcons,
titleId,
...names
) {
const normalizedTitleId = toStringId(titleId)
if (normalizedTitleId && sidebarIcons.byTitleId.has(normalizedTitleId)) {
return sidebarIcons.byTitleId.get(normalizedTitleId) ?? null
}
for (const name of names) {
const normalizedName = normalizeTitleMatchKey(name)
if (normalizedName && sidebarIcons.byTitleName.has(normalizedName)) {
return sidebarIcons.byTitleName.get(normalizedName) ?? null
}
}
return null
}
export function findSteamAppId(...roots) {
const seen = new Set()
const queue = roots.map((value) => ({ value, depth: 0, steamContext: false }))
while (queue.length > 0) {
const current = queue.shift()
const value = current?.value
const depth = current?.depth ?? 0
const steamContext = current?.steamContext ?? false
if (!value || depth > MAX_IMAGE_URL_SEARCH_DEPTH || seen.has(value)) {
continue
}
if (typeof value === "string" || typeof value === "number") {
const steamAppId = steamContext ? toStringId(value) : null
if (steamAppId && STEAM_APP_ID_PATTERN.test(steamAppId)) {
return steamAppId
}
continue
}
seen.add(value)
if (Array.isArray(value)) {
for (const entry of value) {
queue.push({ value: entry, depth: depth + 1, steamContext })
}
continue
}
if (!isRecord(value)) {
continue
}
for (const [key, entry] of Object.entries(value)) {
const steamAppId = getSteamAppIdFromEntry(key, entry, steamContext)
if (steamAppId) {
return steamAppId
}
if (isRecord(entry) || Array.isArray(entry)) {
queue.push({
value: entry,
depth: depth + 1,
steamContext: steamContext || STEAM_CONTAINER_FIELD_PATTERN.test(key),
})
}
}
}
return null
}
export function getInstalledAppSteamAppId(platform, sku) {
if (safeString(platform).toLowerCase() !== STEAM_PLATFORM) {
return null
}
return sku
}
function normalizeTitleMatchKey(value) {
const normalized = safeString(value).trim().toLowerCase().replace(/\s+/g, " ")
return normalized || null
}
function getCssBackgroundImageUrl(value) {
const backgroundImage = safeString(value)
if (!backgroundImage || backgroundImage === "none") {
return null
}
const match = backgroundImage.match(/url\((['"]?)(.*?)\1\)/i)
if (!match?.[2]) {
return null
}
return normalizeImageUrl(match[2])
}
function getSteamAppIdFromEntry(key, entry, steamContext) {
if (STEAM_APP_ID_FIELD_NAMES.has(key)) {
const steamAppId = toStringId(entry)
if (steamAppId && STEAM_APP_ID_PATTERN.test(steamAppId)) {
return steamAppId
}
}
if (steamContext && STEAM_CONTAINER_ID_FIELD_NAMES.has(key)) {
const steamAppId = toStringId(entry)
if (steamAppId && STEAM_APP_ID_PATTERN.test(steamAppId)) {
return steamAppId
}
}
return null
}