@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
158 lines (157 loc) • 6.17 kB
JavaScript
;
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
* ═══════════════════════════════════════════════════════════════════════════
* SVG LOADER - Centralized SVG Icon Loading Utility
*
* Provides a unified way to load SVG icons that works across all contexts:
* - Web browser (http/https)
* - Electron app (file:// protocol)
* - VS Code extension
*
* Key Features:
* - Automatic protocol detection to choose the right loading strategy
* - SVG content caching to prevent duplicate loads
* - Fallback handling when icons can't be loaded
* - Memory-safe: caches failed loads to prevent retry loops
*
* Usage:
* import SvgLoader from "../core/SvgLoader";
* const svgContent = await SvgLoader.load("/res/icons/myicon.svg");
* ═══════════════════════════════════════════════════════════════════════════
*/
const CreatorToolsHost_1 = __importDefault(require("../app/CreatorToolsHost"));
const Log_1 = __importDefault(require("./Log"));
// Cache for loaded SVG content (keyed by original path for simplicity)
const svgCache = new Map();
// Pending requests to prevent duplicate fetches
const pendingRequests = new Map();
// Fallback SVG for failed loads - a simple placeholder rectangle
const FALLBACK_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect x="2" y="2" width="12" height="12" fill="currentColor" fill-opacity="0.3"/></svg>`;
/**
* Check if fetch() will work for loading local resources.
* In Electron (file:// protocol), fetch() cannot load local files.
*/
function canUseFetch() {
// Use globalThis to safely check for window in any environment (browser, Node, VS Code extension)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const win = typeof globalThis !== "undefined" ? globalThis.window : undefined;
if (!win || !win.location)
return false;
// file:// protocol doesn't support fetch for local resources
return !win.location.protocol.startsWith("file:");
}
/**
* Get the content root URL for loading resources.
* Falls back to inferring from window.location if CreatorToolsHost hasn't initialized yet.
*/
function getContentRoot() {
// If contentWebRoot is set, use it
if (CreatorToolsHost_1.default.contentWebRoot) {
return CreatorToolsHost_1.default.contentWebRoot;
}
// Fallback: infer from current page URL
// Use globalThis to safely check for window in any environment (browser, Node, VS Code extension)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const win = typeof globalThis !== "undefined" ? globalThis.window : undefined;
if (win && win.location) {
const href = win.location.href;
const lastSlash = href.lastIndexOf("/");
if (lastSlash >= 0) {
return href.substring(0, lastSlash + 1);
}
}
return "";
}
/**
* Centralized SVG loading utility.
*/
class SvgLoader {
/**
* Load an SVG file and return its content as a string.
* Results are cached to prevent duplicate loads.
* Failed loads return a fallback SVG and are also cached to prevent retry loops.
*
* @param path - The SVG file path (e.g., "/res/icons/myicon.svg")
* @returns Promise resolving to the SVG content string
*/
static async load(path) {
// Check cache first (using original path as key)
if (svgCache.has(path)) {
return svgCache.get(path);
}
// In Electron/file:// context, fetch() won't work for local files
// Return fallback immediately to avoid errors
if (!canUseFetch()) {
svgCache.set(path, FALLBACK_SVG);
return FALLBACK_SVG;
}
// Check if there's a pending request for this path
if (pendingRequests.has(path)) {
return pendingRequests.get(path);
}
// Build the full URL
const normalizedPath = path.startsWith("/") ? path.substring(1) : path;
const contentRoot = getContentRoot();
const fullUrl = contentRoot + normalizedPath;
// Create new request
const request = fetch(fullUrl)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.text();
})
.then((content) => {
svgCache.set(path, content);
pendingRequests.delete(path);
return content;
})
.catch((error) => {
pendingRequests.delete(path);
Log_1.default.debug(`SvgLoader: Failed to load ${path} - ${error.message || error}`);
// Cache the fallback to prevent retry loops
svgCache.set(path, FALLBACK_SVG);
return FALLBACK_SVG;
});
pendingRequests.set(path, request);
return request;
}
/**
* Check if an SVG is already cached.
*/
static isCached(path) {
return svgCache.has(path);
}
/**
* Get cached SVG content synchronously, or null if not cached.
*/
static getCached(path) {
return svgCache.get(path) ?? null;
}
/**
* Clear the SVG cache. Useful for testing or memory cleanup.
*/
static clearCache() {
svgCache.clear();
pendingRequests.clear();
}
/**
* Get the fallback SVG content.
*/
static get fallbackSvg() {
return FALLBACK_SVG;
}
/**
* Check if fetch-based loading is available in this context.
*/
static get canFetch() {
return canUseFetch();
}
}
exports.default = SvgLoader;