UNPKG

reactdesk-core

Version:

A powerful React-based desktop environment library for creating Windows 11-like desktop interfaces with window management, taskbar, themes, and more

1,129 lines (1,116 loc) 38.2 kB
import { jsx, jsxs } from "react/jsx-runtime"; import { useRef, useState, useCallback, useEffect, memo, useMemo, useLayoutEffect } from "react"; import styled, { useTheme } from "styled-components"; import { S as StyledTaskbarEntry } from "./index-BA88Gpwx.js"; import { Y as m, ac as WIN_TASKBAR_HEIGHT, av as PEEK_MAX_WIDTH, Z as TRANSITIONS_IN_SECONDS, $ as useContext, aw as isCanvasDrawn, ax as MILLISECONDS_IN_SECOND, _ as useContext$1, al as FOCUSABLE_ELEMENT, ay as HIGH_PRIORITY_ELEMENT, ao as Button, a5 as label, am as haltEvent, aa as viewWidth } from "./index-CGa3ZYCK.js"; import { C as CloseIcon } from "./WindowActionIcons-DaxmM-cO.js"; import { b as useWindowActions } from "./useTitlebarContextMenu-DjUJ9T4Z.js"; const StyledPeekWindow = styled(m.div)` backdrop-filter: ${({ theme }) => `blur(${theme.sizes.taskbar.blur})`}; background-color: ${({ theme }) => theme.colors.taskbar.background}; border: ${({ theme }) => `1px solid ${theme.colors.taskbar.peekBorder}`}; border-bottom: 0; border-radius: 6px; bottom: ${WIN_TASKBAR_HEIGHT}px; display: flex; overflow: hidden; place-content: center; place-items: flex-start; position: fixed; z-index: 1000; transform: ${({ $offsetX }) => $offsetX ? `translateX(${$offsetX}px)` : void 0}; ${StyledTaskbarEntry}:hover & { background-color: hsla(0, 0%, 25%, 85%); &:active { background-color: ${({ theme }) => theme.colors.taskbar.activeForeground}; } } img { height: ${({ theme }) => theme.sizes.taskbar.entry.peekImage.height}px; margin: ${({ theme }) => theme.sizes.taskbar.entry.peekImage.margin}px; max-height: ${PEEK_MAX_WIDTH}px; max-width: ${PEEK_MAX_WIDTH}px; min-height: 80px; min-width: 80px; object-fit: contain; } button.close { background-color: rgba(40, 40, 40, 0.1); height: 24px; position: absolute; right: 0; top: 0; width: 24px; svg { fill: rgb(252, 246, 247); width: 12px; } &:active { background-color: rgb(139, 10, 20) !important; } &:hover { background-color: rgb(194, 22, 36); } } .controls { display: flex; place-content: center; position: absolute; top: ${({ theme }) => theme.sizes.taskbar.entry.peekImage.height + theme.sizes.taskbar.entry.peekImage.margin * 2}px; width: 100%; button { background-color: rgb(70, 70, 70); border: 1px solid rgb(46, 46, 46); display: flex; height: 27px; place-content: center; place-items: center; width: 27px; &:active { background-color: rgb(61, 96, 153) !important; border: 1px solid rgb(49, 77, 122) !important; } &:hover { background-color: rgb(54, 101, 179); border: 1px solid rgb(43, 81, 143); } svg { fill: #fff; height: 16px; margin-left: 1px; pointer-events: none; user-select: none; width: 16px; } } } `; const usePeekTransition = (showControls = false) => { const { sizes: { taskbar } } = useTheme(); let peekContainerHeight = taskbar.entry.peekImage.height + taskbar.entry.peekImage.margin * 2; if (showControls) peekContainerHeight += taskbar.entry.peekControlsHeight; return { animate: "active", exit: "initial", initial: "initial", transition: { duration: TRANSITIONS_IN_SECONDS.WINDOW / 3, // Reduced duration ease: [0.42, 0, 0.58, 1] // Custom cubic-bezier for quicker start and smooth end }, variants: { active: { height: peekContainerHeight, opacity: 1 }, initial: { height: 0, opacity: 0 } } }; }; function resolveUrl(url, baseUrl) { if (url.match(/^[a-z]+:\/\//i)) { return url; } if (url.match(/^\/\//)) { return window.location.protocol + url; } if (url.match(/^[a-z]+:/i)) { return url; } const doc = document.implementation.createHTMLDocument(); const base = doc.createElement("base"); const a = doc.createElement("a"); doc.head.appendChild(base); doc.body.appendChild(a); if (baseUrl) { base.href = baseUrl; } a.href = url; return a.href; } const uuid = /* @__PURE__ */ (() => { let counter = 0; const random = () => ( // eslint-disable-next-line no-bitwise `0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4) ); return () => { counter += 1; return `u${random()}${counter}`; }; })(); function toArray(arrayLike) { const arr = []; for (let i = 0, l = arrayLike.length; i < l; i++) { arr.push(arrayLike[i]); } return arr; } let styleProps = null; function getStyleProperties(options = {}) { if (styleProps) { return styleProps; } if (options.includeStyleProperties) { styleProps = options.includeStyleProperties; return styleProps; } styleProps = toArray(window.getComputedStyle(document.documentElement)); return styleProps; } function px(node, styleProperty) { const win = node.ownerDocument.defaultView || window; const val = win.getComputedStyle(node).getPropertyValue(styleProperty); return val ? parseFloat(val.replace("px", "")) : 0; } function getNodeWidth(node) { const leftBorder = px(node, "border-left-width"); const rightBorder = px(node, "border-right-width"); return node.clientWidth + leftBorder + rightBorder; } function getNodeHeight(node) { const topBorder = px(node, "border-top-width"); const bottomBorder = px(node, "border-bottom-width"); return node.clientHeight + topBorder + bottomBorder; } function getImageSize(targetNode, options = {}) { const width = options.width || getNodeWidth(targetNode); const height = options.height || getNodeHeight(targetNode); return { width, height }; } function getPixelRatio() { let ratio; let FINAL_PROCESS; try { FINAL_PROCESS = process; } catch (e) { } const val = FINAL_PROCESS && FINAL_PROCESS.env ? FINAL_PROCESS.env.devicePixelRatio : null; if (val) { ratio = parseInt(val, 10); if (Number.isNaN(ratio)) { ratio = 1; } } return ratio || window.devicePixelRatio || 1; } const canvasDimensionLimit = 16384; function checkCanvasDimensions(canvas) { if (canvas.width > canvasDimensionLimit || canvas.height > canvasDimensionLimit) { if (canvas.width > canvasDimensionLimit && canvas.height > canvasDimensionLimit) { if (canvas.width > canvas.height) { canvas.height *= canvasDimensionLimit / canvas.width; canvas.width = canvasDimensionLimit; } else { canvas.width *= canvasDimensionLimit / canvas.height; canvas.height = canvasDimensionLimit; } } else if (canvas.width > canvasDimensionLimit) { canvas.height *= canvasDimensionLimit / canvas.width; canvas.width = canvasDimensionLimit; } else { canvas.width *= canvasDimensionLimit / canvas.height; canvas.height = canvasDimensionLimit; } } } function createImage(url) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { img.decode().then(() => { requestAnimationFrame(() => resolve(img)); }); }; img.onerror = reject; img.crossOrigin = "anonymous"; img.decoding = "async"; img.src = url; }); } async function svgToDataURL(svg) { return Promise.resolve().then(() => new XMLSerializer().serializeToString(svg)).then(encodeURIComponent).then((html) => `data:image/svg+xml;charset=utf-8,${html}`); } async function nodeToDataURL(node, width, height) { const xmlns = "http://www.w3.org/2000/svg"; const svg = document.createElementNS(xmlns, "svg"); const foreignObject = document.createElementNS(xmlns, "foreignObject"); svg.setAttribute("width", `${width}`); svg.setAttribute("height", `${height}`); svg.setAttribute("viewBox", `0 0 ${width} ${height}`); foreignObject.setAttribute("width", "100%"); foreignObject.setAttribute("height", "100%"); foreignObject.setAttribute("x", "0"); foreignObject.setAttribute("y", "0"); foreignObject.setAttribute("externalResourcesRequired", "true"); svg.appendChild(foreignObject); foreignObject.appendChild(node); return svgToDataURL(svg); } const isInstanceOfElement = (node, instance) => { if (node instanceof instance) return true; const nodePrototype = Object.getPrototypeOf(node); if (nodePrototype === null) return false; return nodePrototype.constructor.name === instance.name || isInstanceOfElement(nodePrototype, instance); }; function formatCSSText(style) { const content = style.getPropertyValue("content"); return `${style.cssText} content: '${content.replace(/'|"/g, "")}';`; } function formatCSSProperties(style, options) { return getStyleProperties(options).map((name) => { const value = style.getPropertyValue(name); const priority = style.getPropertyPriority(name); return `${name}: ${value}${priority ? " !important" : ""};`; }).join(" "); } function getPseudoElementStyle(className, pseudo, style, options) { const selector = `.${className}:${pseudo}`; const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style, options); return document.createTextNode(`${selector}{${cssText}}`); } function clonePseudoElement(nativeNode, clonedNode, pseudo, options) { const style = window.getComputedStyle(nativeNode, pseudo); const content = style.getPropertyValue("content"); if (content === "" || content === "none") { return; } const className = uuid(); try { clonedNode.className = `${clonedNode.className} ${className}`; } catch (err) { return; } const styleElement = document.createElement("style"); styleElement.appendChild(getPseudoElementStyle(className, pseudo, style, options)); clonedNode.appendChild(styleElement); } function clonePseudoElements(nativeNode, clonedNode, options) { clonePseudoElement(nativeNode, clonedNode, ":before", options); clonePseudoElement(nativeNode, clonedNode, ":after", options); } const WOFF = "application/font-woff"; const JPEG = "image/jpeg"; const mimes = { woff: WOFF, woff2: WOFF, ttf: "application/font-truetype", eot: "application/vnd.ms-fontobject", png: "image/png", jpg: JPEG, jpeg: JPEG, gif: "image/gif", tiff: "image/tiff", svg: "image/svg+xml", webp: "image/webp" }; function getExtension(url) { const match = /\.([^./]*?)$/g.exec(url); return match ? match[1] : ""; } function getMimeType(url) { const extension = getExtension(url).toLowerCase(); return mimes[extension] || ""; } function getContentFromDataUrl(dataURL) { return dataURL.split(/,/)[1]; } function isDataUrl(url) { return url.search(/^(data:)/) !== -1; } function makeDataUrl(content, mimeType) { return `data:${mimeType};base64,${content}`; } async function fetchAsDataURL(url, init, process2) { const res = await fetch(url, init); if (res.status === 404) { throw new Error(`Resource "${res.url}" not found`); } const blob = await res.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = reject; reader.onloadend = () => { try { resolve(process2({ res, result: reader.result })); } catch (error) { reject(error); } }; reader.readAsDataURL(blob); }); } const cache$1 = {}; function getCacheKey(url, contentType, includeQueryParams) { let key = url.replace(/\?.*/, ""); if (includeQueryParams) { key = url; } if (/ttf|otf|eot|woff2?/i.test(key)) { key = key.replace(/.*\//, ""); } return contentType ? `[${contentType}]${key}` : key; } async function resourceToDataURL(resourceUrl, contentType, options) { const cacheKey = getCacheKey(resourceUrl, contentType, options.includeQueryParams); if (cache$1[cacheKey] != null) { return cache$1[cacheKey]; } if (options.cacheBust) { resourceUrl += (/\?/.test(resourceUrl) ? "&" : "?") + (/* @__PURE__ */ new Date()).getTime(); } let dataURL; try { const content = await fetchAsDataURL(resourceUrl, options.fetchRequestInit, ({ res, result }) => { if (!contentType) { contentType = res.headers.get("Content-Type") || ""; } return getContentFromDataUrl(result); }); dataURL = makeDataUrl(content, contentType); } catch (error) { dataURL = options.imagePlaceholder || ""; let msg = `Failed to fetch resource: ${resourceUrl}`; if (error) { msg = typeof error === "string" ? error : error.message; } if (msg) { console.warn(msg); } } cache$1[cacheKey] = dataURL; return dataURL; } async function cloneCanvasElement(canvas) { const dataURL = canvas.toDataURL(); if (dataURL === "data:,") { return canvas.cloneNode(false); } return createImage(dataURL); } async function cloneVideoElement(video, options) { if (video.currentSrc) { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = video.clientWidth; canvas.height = video.clientHeight; ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const dataURL2 = canvas.toDataURL(); return createImage(dataURL2); } const poster = video.poster; const contentType = getMimeType(poster); const dataURL = await resourceToDataURL(poster, contentType, options); return createImage(dataURL); } async function cloneIFrameElement(iframe, options) { var _a; try { if ((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body) { return await cloneNode(iframe.contentDocument.body, options, true); } } catch (_b) { } return iframe.cloneNode(false); } async function cloneSingleNode(node, options) { if (isInstanceOfElement(node, HTMLCanvasElement)) { return cloneCanvasElement(node); } if (isInstanceOfElement(node, HTMLVideoElement)) { return cloneVideoElement(node, options); } if (isInstanceOfElement(node, HTMLIFrameElement)) { return cloneIFrameElement(node, options); } return node.cloneNode(isSVGElement(node)); } const isSlotElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SLOT"; const isSVGElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SVG"; async function cloneChildren(nativeNode, clonedNode, options) { var _a, _b; if (isSVGElement(clonedNode)) { return clonedNode; } let children = []; if (isSlotElement(nativeNode) && nativeNode.assignedNodes) { children = toArray(nativeNode.assignedNodes()); } else if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && ((_a = nativeNode.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) { children = toArray(nativeNode.contentDocument.body.childNodes); } else { children = toArray(((_b = nativeNode.shadowRoot) !== null && _b !== void 0 ? _b : nativeNode).childNodes); } if (children.length === 0 || isInstanceOfElement(nativeNode, HTMLVideoElement)) { return clonedNode; } await children.reduce((deferred, child) => deferred.then(() => cloneNode(child, options)).then((clonedChild) => { if (clonedChild) { clonedNode.appendChild(clonedChild); } }), Promise.resolve()); return clonedNode; } function cloneCSSStyle(nativeNode, clonedNode, options) { const targetStyle = clonedNode.style; if (!targetStyle) { return; } const sourceStyle = window.getComputedStyle(nativeNode); if (sourceStyle.cssText) { targetStyle.cssText = sourceStyle.cssText; targetStyle.transformOrigin = sourceStyle.transformOrigin; } else { getStyleProperties(options).forEach((name) => { let value = sourceStyle.getPropertyValue(name); if (name === "font-size" && value.endsWith("px")) { const reducedFont = Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1; value = `${reducedFont}px`; } if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && name === "display" && value === "inline") { value = "block"; } if (name === "d" && clonedNode.getAttribute("d")) { value = `path(${clonedNode.getAttribute("d")})`; } targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name)); }); } } function cloneInputValue(nativeNode, clonedNode) { if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) { clonedNode.innerHTML = nativeNode.value; } if (isInstanceOfElement(nativeNode, HTMLInputElement)) { clonedNode.setAttribute("value", nativeNode.value); } } function cloneSelectValue(nativeNode, clonedNode) { if (isInstanceOfElement(nativeNode, HTMLSelectElement)) { const clonedSelect = clonedNode; const selectedOption = Array.from(clonedSelect.children).find((child) => nativeNode.value === child.getAttribute("value")); if (selectedOption) { selectedOption.setAttribute("selected", ""); } } } function decorate(nativeNode, clonedNode, options) { if (isInstanceOfElement(clonedNode, Element)) { cloneCSSStyle(nativeNode, clonedNode, options); clonePseudoElements(nativeNode, clonedNode, options); cloneInputValue(nativeNode, clonedNode); cloneSelectValue(nativeNode, clonedNode); } return clonedNode; } async function ensureSVGSymbols(clone, options) { const uses = clone.querySelectorAll ? clone.querySelectorAll("use") : []; if (uses.length === 0) { return clone; } const processedDefs = {}; for (let i = 0; i < uses.length; i++) { const use = uses[i]; const id = use.getAttribute("xlink:href"); if (id) { const exist = clone.querySelector(id); const definition = document.querySelector(id); if (!exist && definition && !processedDefs[id]) { processedDefs[id] = await cloneNode(definition, options, true); } } } const nodes = Object.values(processedDefs); if (nodes.length) { const ns = "http://www.w3.org/1999/xhtml"; const svg = document.createElementNS(ns, "svg"); svg.setAttribute("xmlns", ns); svg.style.position = "absolute"; svg.style.width = "0"; svg.style.height = "0"; svg.style.overflow = "hidden"; svg.style.display = "none"; const defs = document.createElementNS(ns, "defs"); svg.appendChild(defs); for (let i = 0; i < nodes.length; i++) { defs.appendChild(nodes[i]); } clone.appendChild(svg); } return clone; } async function cloneNode(node, options, isRoot) { if (!isRoot && options.filter && !options.filter(node)) { return null; } return Promise.resolve(node).then((clonedNode) => cloneSingleNode(clonedNode, options)).then((clonedNode) => cloneChildren(node, clonedNode, options)).then((clonedNode) => decorate(node, clonedNode, options)).then((clonedNode) => ensureSVGSymbols(clonedNode, options)); } const URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g; const URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g; const FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g; function toRegex(url) { const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1"); return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, "g"); } function parseURLs(cssText) { const urls = []; cssText.replace(URL_REGEX, (raw, quotation, url) => { urls.push(url); return raw; }); return urls.filter((url) => !isDataUrl(url)); } async function embed(cssText, resourceURL, baseURL, options, getContentFromUrl) { try { const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL; const contentType = getMimeType(resourceURL); let dataURL; if (getContentFromUrl) ; else { dataURL = await resourceToDataURL(resolvedURL, contentType, options); } return cssText.replace(toRegex(resourceURL), `$1${dataURL}$3`); } catch (error) { } return cssText; } function filterPreferredFontFormat(str, { preferredFontFormat }) { return !preferredFontFormat ? str : str.replace(FONT_SRC_REGEX, (match) => { while (true) { const [src, , format] = URL_WITH_FORMAT_REGEX.exec(match) || []; if (!format) { return ""; } if (format === preferredFontFormat) { return `src: ${src};`; } } }); } function shouldEmbed(url) { return url.search(URL_REGEX) !== -1; } async function embedResources(cssText, baseUrl, options) { if (!shouldEmbed(cssText)) { return cssText; } const filteredCSSText = filterPreferredFontFormat(cssText, options); const urls = parseURLs(filteredCSSText); return urls.reduce((deferred, url) => deferred.then((css) => embed(css, url, baseUrl, options)), Promise.resolve(filteredCSSText)); } async function embedProp(propName, node, options) { var _a; const propValue = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propName); if (propValue) { const cssString = await embedResources(propValue, null, options); node.style.setProperty(propName, cssString, node.style.getPropertyPriority(propName)); return true; } return false; } async function embedBackground(clonedNode, options) { await embedProp("background", clonedNode, options) || await embedProp("background-image", clonedNode, options); await embedProp("mask", clonedNode, options) || await embedProp("-webkit-mask", clonedNode, options) || await embedProp("mask-image", clonedNode, options) || await embedProp("-webkit-mask-image", clonedNode, options); } async function embedImageNode(clonedNode, options) { const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement); if (!(isImageElement && !isDataUrl(clonedNode.src)) && !(isInstanceOfElement(clonedNode, SVGImageElement) && !isDataUrl(clonedNode.href.baseVal))) { return; } const url = isImageElement ? clonedNode.src : clonedNode.href.baseVal; const dataURL = await resourceToDataURL(url, getMimeType(url), options); await new Promise((resolve, reject) => { clonedNode.onload = resolve; clonedNode.onerror = options.onImageErrorHandler ? (...attributes) => { try { resolve(options.onImageErrorHandler(...attributes)); } catch (error) { reject(error); } } : reject; const image = clonedNode; if (image.decode) { image.decode = resolve; } if (image.loading === "lazy") { image.loading = "eager"; } if (isImageElement) { clonedNode.srcset = ""; clonedNode.src = dataURL; } else { clonedNode.href.baseVal = dataURL; } }); } async function embedChildren(clonedNode, options) { const children = toArray(clonedNode.childNodes); const deferreds = children.map((child) => embedImages(child, options)); await Promise.all(deferreds).then(() => clonedNode); } async function embedImages(clonedNode, options) { if (isInstanceOfElement(clonedNode, Element)) { await embedBackground(clonedNode, options); await embedImageNode(clonedNode, options); await embedChildren(clonedNode, options); } } function applyStyle(node, options) { const { style } = node; if (options.backgroundColor) { style.backgroundColor = options.backgroundColor; } if (options.width) { style.width = `${options.width}px`; } if (options.height) { style.height = `${options.height}px`; } const manual = options.style; if (manual != null) { Object.keys(manual).forEach((key) => { style[key] = manual[key]; }); } return node; } const cssFetchCache = {}; async function fetchCSS(url) { let cache2 = cssFetchCache[url]; if (cache2 != null) { return cache2; } const res = await fetch(url); const cssText = await res.text(); cache2 = { url, cssText }; cssFetchCache[url] = cache2; return cache2; } async function embedFonts(data, options) { let cssText = data.cssText; const regexUrl = /url\(["']?([^"')]+)["']?\)/g; const fontLocs = cssText.match(/url\([^)]+\)/g) || []; const loadFonts = fontLocs.map(async (loc) => { let url = loc.replace(regexUrl, "$1"); if (!url.startsWith("https://")) { url = new URL(url, data.url).href; } return fetchAsDataURL(url, options.fetchRequestInit, ({ result }) => { cssText = cssText.replace(loc, `url(${result})`); return [loc, result]; }); }); return Promise.all(loadFonts).then(() => cssText); } function parseCSS(source) { if (source == null) { return []; } const result = []; const commentsRegex = /(\/\*[\s\S]*?\*\/)/gi; let cssText = source.replace(commentsRegex, ""); const keyframesRegex = new RegExp("((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})", "gi"); while (true) { const matches = keyframesRegex.exec(cssText); if (matches === null) { break; } result.push(matches[0]); } cssText = cssText.replace(keyframesRegex, ""); const importRegex = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi; const combinedCSSRegex = "((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})"; const unifiedRegex = new RegExp(combinedCSSRegex, "gi"); while (true) { let matches = importRegex.exec(cssText); if (matches === null) { matches = unifiedRegex.exec(cssText); if (matches === null) { break; } else { importRegex.lastIndex = unifiedRegex.lastIndex; } } else { unifiedRegex.lastIndex = importRegex.lastIndex; } result.push(matches[0]); } return result; } async function getCSSRules(styleSheets, options) { const ret = []; const deferreds = []; styleSheets.forEach((sheet) => { if ("cssRules" in sheet) { try { toArray(sheet.cssRules || []).forEach((item, index) => { if (item.type === CSSRule.IMPORT_RULE) { let importIndex = index + 1; const url = item.href; const deferred = fetchCSS(url).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => { try { sheet.insertRule(rule, rule.startsWith("@import") ? importIndex += 1 : sheet.cssRules.length); } catch (error) { console.error("Error inserting rule from remote css", { rule, error }); } })).catch((e) => { console.error("Error loading remote css", e.toString()); }); deferreds.push(deferred); } }); } catch (e) { const inline = styleSheets.find((a) => a.href == null) || document.styleSheets[0]; if (sheet.href != null) { deferreds.push(fetchCSS(sheet.href).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => { inline.insertRule(rule, inline.cssRules.length); })).catch((err) => { console.error("Error loading remote stylesheet", err); })); } console.error("Error inlining remote css file", e); } } }); return Promise.all(deferreds).then(() => { styleSheets.forEach((sheet) => { if ("cssRules" in sheet) { try { toArray(sheet.cssRules || []).forEach((item) => { ret.push(item); }); } catch (e) { console.error(`Error while reading CSS rules from ${sheet.href}`, e); } } }); return ret; }); } function getWebFontRules(cssRules) { return cssRules.filter((rule) => rule.type === CSSRule.FONT_FACE_RULE).filter((rule) => shouldEmbed(rule.style.getPropertyValue("src"))); } async function parseWebFontRules(node, options) { if (node.ownerDocument == null) { throw new Error("Provided element is not within a Document"); } const styleSheets = toArray(node.ownerDocument.styleSheets); const cssRules = await getCSSRules(styleSheets, options); return getWebFontRules(cssRules); } function normalizeFontFamily(font) { return font.trim().replace(/["']/g, ""); } function getUsedFonts(node) { const fonts = /* @__PURE__ */ new Set(); function traverse(node2) { const fontFamily = node2.style.fontFamily || getComputedStyle(node2).fontFamily; fontFamily.split(",").forEach((font) => { fonts.add(normalizeFontFamily(font)); }); Array.from(node2.children).forEach((child) => { if (child instanceof HTMLElement) { traverse(child); } }); } traverse(node); return fonts; } async function getWebFontCSS(node, options) { const rules = await parseWebFontRules(node, options); const usedFonts = getUsedFonts(node); const cssTexts = await Promise.all(rules.filter((rule) => usedFonts.has(normalizeFontFamily(rule.style.fontFamily))).map((rule) => { const baseUrl = rule.parentStyleSheet ? rule.parentStyleSheet.href : null; return embedResources(rule.cssText, baseUrl, options); })); return cssTexts.join("\n"); } async function embedWebFonts(clonedNode, options) { const cssText = options.fontEmbedCSS != null ? options.fontEmbedCSS : options.skipFonts ? null : await getWebFontCSS(clonedNode, options); if (cssText) { const styleNode = document.createElement("style"); const sytleContent = document.createTextNode(cssText); styleNode.appendChild(sytleContent); if (clonedNode.firstChild) { clonedNode.insertBefore(styleNode, clonedNode.firstChild); } else { clonedNode.appendChild(styleNode); } } } async function toSvg(node, options = {}) { const { width, height } = getImageSize(node, options); const clonedNode = await cloneNode(node, options, true); await embedWebFonts(clonedNode, options); await embedImages(clonedNode, options); applyStyle(clonedNode, options); const datauri = await nodeToDataURL(clonedNode, width, height); return datauri; } async function toCanvas(node, options = {}) { const { width, height } = getImageSize(node, options); const svg = await toSvg(node, options); const img = await createImage(svg); const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const ratio = options.pixelRatio || getPixelRatio(); const canvasWidth = options.canvasWidth || width; const canvasHeight = options.canvasHeight || height; canvas.width = canvasWidth * ratio; canvas.height = canvasHeight * ratio; if (!options.skipAutoScale) { checkCanvasDimensions(canvas); } canvas.style.width = `${canvasWidth}`; canvas.style.height = `${canvasHeight}`; if (options.backgroundColor) { context.fillStyle = options.backgroundColor; context.fillRect(0, 0, canvas.width, canvas.height); } context.drawImage(img, 0, 0, canvas.width, canvas.height); return canvas; } const FPS = 10; const FRAME_INTERVAL = MILLISECONDS_IN_SECOND / FPS; const CACHE_DELAY = 2500; const cache = /* @__PURE__ */ new Map(); const getCanvasFromElement = async (element) => { const spacing = element.tagName === "VIDEO" ? { margin: "0", padding: "0" } : {}; return toCanvas(element, { ...element.clientWidth > PEEK_MAX_WIDTH && { canvasHeight: Math.round( PEEK_MAX_WIDTH / element.clientWidth * element.clientHeight ), canvasWidth: PEEK_MAX_WIDTH }, filter: (el) => !(el instanceof HTMLSourceElement), skipAutoScale: true, style: { inset: "0", ...spacing } }); }; const renderFrame = async (previewElement, animate, callback, latestDataUrl, id) => { if (!animate.current) return; const nextFrame = () => { setTimeout(() => { window.requestAnimationFrame(() => { renderFrame(previewElement, animate, callback, latestDataUrl, id); }); }, FRAME_INTERVAL); }; let dataCanvas; try { dataCanvas = await getCanvasFromElement(previewElement); } catch { } if (dataCanvas && dataCanvas.width > 0 && dataCanvas.height > 0) { if (isCanvasDrawn(dataCanvas)) { dataCanvas.toBlob( (blob) => { if (!blob) return; const objectUrl = URL.createObjectURL(blob); latestDataUrl.current = objectUrl; cache.set(id, objectUrl); callback(objectUrl); window.setTimeout(nextFrame, MILLISECONDS_IN_SECOND / FPS); }, "image/webp" // Using WebP format for smaller size ); } else { window.setTimeout(nextFrame, MILLISECONDS_IN_SECOND / FPS); } } else { nextFrame(); } }; const useWindowPeek = (id) => { const { windows: { [id]: currentWindow } } = useContext(); const { peekElement, componentWindow } = currentWindow || {}; const previewTimer = useRef(); const [imageSrc, setImageSrc] = useState(""); const animate = useRef(true); const latestDataUrl = useRef(""); const startRendering = useCallback( (element) => { window.requestAnimationFrame( () => renderFrame(element, animate, setImageSrc, latestDataUrl, id) ); animate.current = true; }, [id] ); const captureInitialFrame = useCallback( async (element) => { try { const dataCanvas = await getCanvasFromElement(element); if (dataCanvas && dataCanvas.width > 0 && dataCanvas.height > 0 && isCanvasDrawn(dataCanvas)) { dataCanvas.toBlob( (blob) => { if (!blob) return; const objectUrl = URL.createObjectURL(blob); latestDataUrl.current = objectUrl; cache.set(id, objectUrl); setImageSrc(objectUrl); }, "image/webp" // Using WebP format for smaller size ); } } catch { } }, [id] ); useEffect(() => { const previewElement = peekElement || componentWindow; if (cache.has(id)) { setImageSrc(cache.get(id)); const cacheTimeout = window.setTimeout(() => { if (!previewTimer.current && previewElement) { previewTimer.current = window.setTimeout( () => startRendering(previewElement), document.querySelector(".peekWindow") ? 0 : MILLISECONDS_IN_SECOND / 2 ); } }, CACHE_DELAY); return () => { clearTimeout(cacheTimeout); if (latestDataUrl.current) { cache.set(id, latestDataUrl.current); } }; } else if (!previewTimer.current && previewElement) { captureInitialFrame(previewElement); previewTimer.current = window.setTimeout( () => startRendering(previewElement), document.querySelector(".peekWindow") ? 0 : MILLISECONDS_IN_SECOND / 2 ); } return () => { if (previewTimer.current) { clearTimeout(previewTimer.current); previewTimer.current = void 0; } animate.current = false; if (latestDataUrl.current) { cache.set(id, latestDataUrl.current); } }; }, [componentWindow, peekElement, id, startRendering, captureInitialFrame]); return imageSrc; }; const Pause = memo(() => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 32 32", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx("path", { d: "M8 29.328V2.672h2.672v26.656H8zM21.328 2.672H24v26.656h-2.672V2.672z" }) })); const Play = memo(() => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 32 32", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx("path", { d: "M28 16 8 30V2z" }) })); const PeekWindow = ({ id }) => { const { minimize, windows: { [id]: currentWindow } } = useContext(); const { pause, paused, play, minimized = false, title = id } = currentWindow || {}; const { setForegroundId, setPreviewID } = useContext$1(); const { onClose } = useWindowActions(id); const [offsetX, setOffsetX] = useState(0); const image = useWindowPeek(id); const showControls = useMemo( () => Boolean(play && pause && paused), [pause, paused, play] ); const peekTransition = usePeekTransition(showControls); const peekRef = useRef(null); const onClick = () => { if (minimized) minimize(id); setPreviewID(false); setForegroundId(id); }; const [isPaused, setIsPaused] = useState(false); const monitoringPaused = useRef(false); useEffect(() => { if (showControls && paused && !monitoringPaused.current) { monitoringPaused.current = true; setIsPaused(paused(setIsPaused)); } }, [paused, showControls]); useLayoutEffect(() => { var _a; if (image) { const { left = 0, right = 0 } = ((_a = peekRef.current) == null ? void 0 : _a.getBoundingClientRect()) || {}; const vw = viewWidth(); if (left < 0) { setOffsetX(Math.abs(left)); } else if (right > vw) { setOffsetX(vw - right); } } }, [image]); return image ? /* @__PURE__ */ jsxs( StyledPeekWindow, { ref: peekRef, $offsetX: offsetX, className: "peekWindow", onClick, ...peekTransition, ...FOCUSABLE_ELEMENT, children: [ /* @__PURE__ */ jsx( "img", { alt: title, decoding: "async", loading: "eager", src: image, ...HIGH_PRIORITY_ELEMENT } ), /* @__PURE__ */ jsx(Button, { className: "close", onClick: onClose, ...label("Close"), children: /* @__PURE__ */ jsx(CloseIcon, {}) }), showControls && /* @__PURE__ */ jsxs("div", { className: "controls", children: [ isPaused && /* @__PURE__ */ jsx( Button, { onClick: (event) => { haltEvent(event); play == null ? void 0 : play(); }, ...label("Play"), ...FOCUSABLE_ELEMENT, children: /* @__PURE__ */ jsx(Play, {}) } ), !isPaused && /* @__PURE__ */ jsx( Button, { onClick: (event) => { haltEvent(event); pause == null ? void 0 : pause(); }, ...label("Pause"), ...FOCUSABLE_ELEMENT, children: /* @__PURE__ */ jsx(Pause, {}) } ) ] }) ] } ) : null; }; const PeekWindow$1 = memo(PeekWindow); export { PeekWindow$1 as default }; //# sourceMappingURL=PeekWindow-xF6uH7jm.js.map