145 lines
4.2 KiB
JavaScript
145 lines
4.2 KiB
JavaScript
"use client";
|
|
|
|
import React, { useEffect, useRef } from "react";
|
|
|
|
const DEFAULT_REMOTE_URL = "https://uikit.d.aiengines.ir/AppHeader.js";
|
|
const APP_HEADER_SLOT = Symbol.for("uikit.remote.app-header.slot");
|
|
const remotePromises = new Map();
|
|
|
|
function loadRemote(componentName, remoteFileUrl) {
|
|
const cacheKey = `${componentName}:${remoteFileUrl}`;
|
|
if (!remotePromises.has(cacheKey)) {
|
|
remotePromises.set(
|
|
cacheKey,
|
|
import(
|
|
/* webpackIgnore: true */
|
|
/* @vite-ignore */
|
|
remoteFileUrl
|
|
).catch((error) => {
|
|
remotePromises.delete(cacheKey);
|
|
throw error;
|
|
}),
|
|
);
|
|
}
|
|
return remotePromises.get(cacheKey);
|
|
}
|
|
|
|
function createRemoteElementComponent({ componentName, tagName, defaultRemoteUrl, normalizeProps }) {
|
|
return function RemoteElementComponent(props) {
|
|
const containerRef = useRef(null);
|
|
const elementRef = useRef(null);
|
|
const latestPropsRef = useRef(null);
|
|
latestPropsRef.current = normalizeProps ? normalizeProps(props) : props;
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
loadRemote(componentName, defaultRemoteUrl)
|
|
.then(() => {
|
|
if (!active || !containerRef.current) return;
|
|
const element = document.createElement(tagName);
|
|
element.componentProps = latestPropsRef.current;
|
|
containerRef.current.appendChild(element);
|
|
elementRef.current = element;
|
|
})
|
|
.catch((error) => {
|
|
console.error(`[uikit/remote/ui] Failed to load ${componentName}:`, error);
|
|
});
|
|
|
|
return () => {
|
|
active = false;
|
|
if (elementRef.current) {
|
|
elementRef.current.remove();
|
|
elementRef.current = null;
|
|
}
|
|
};
|
|
}, [componentName, defaultRemoteUrl, tagName]);
|
|
|
|
useEffect(() => {
|
|
if (elementRef.current) {
|
|
elementRef.current.componentProps = latestPropsRef.current;
|
|
}
|
|
});
|
|
|
|
return React.createElement("span", {
|
|
ref: containerRef,
|
|
style: { display: "contents" },
|
|
});
|
|
};
|
|
}
|
|
|
|
function createAppHeaderSlot(name) {
|
|
function AppHeaderSlot() {
|
|
return null;
|
|
}
|
|
AppHeaderSlot[APP_HEADER_SLOT] = name;
|
|
return AppHeaderSlot;
|
|
}
|
|
|
|
const BrandSection = createAppHeaderSlot("brandSection");
|
|
const Logo = createAppHeaderSlot("logo");
|
|
const Brand = createAppHeaderSlot("brand");
|
|
const Title = createAppHeaderSlot("title");
|
|
const Description = createAppHeaderSlot("description");
|
|
const Bi = createAppHeaderSlot("bi");
|
|
|
|
function toPlainText(value) {
|
|
if (value === null || value === undefined || typeof value === "boolean") return undefined;
|
|
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
if (Array.isArray(value)) return value.map(toPlainText).filter(Boolean).join("");
|
|
if (React.isValidElement(value)) return toPlainText(value.props?.children);
|
|
return undefined;
|
|
}
|
|
|
|
function extractCompoundAppHeader(children) {
|
|
const result = { showBi: false };
|
|
|
|
function visit(node) {
|
|
React.Children.forEach(node, (child) => {
|
|
if (!React.isValidElement(child)) return;
|
|
const slot = child.type?.[APP_HEADER_SLOT];
|
|
|
|
if (slot === "logo") {
|
|
result.logoSrc = child.props.src;
|
|
result.logoAlt = child.props.alt;
|
|
} else if (slot === "title") {
|
|
result.title = toPlainText(child.props.children);
|
|
} else if (slot === "description") {
|
|
result.description = toPlainText(child.props.children);
|
|
} else if (slot === "bi") {
|
|
result.showBi = true;
|
|
result.biLogoSrc = child.props.logoSrc || child.props.src;
|
|
result.biLogoAlt = child.props.alt;
|
|
result.biText = child.props.text;
|
|
}
|
|
|
|
if (child.props?.children) visit(child.props.children);
|
|
});
|
|
}
|
|
|
|
visit(children);
|
|
return result;
|
|
}
|
|
|
|
const RemoteAppHeaderElement = createRemoteElementComponent({
|
|
componentName: "AppHeader",
|
|
tagName: "uikit-app-header",
|
|
defaultRemoteUrl: DEFAULT_REMOTE_URL,
|
|
normalizeProps: ({ children, ...props }) =>
|
|
children === undefined || children === null
|
|
? props
|
|
: { ...props, ...extractCompoundAppHeader(children) },
|
|
});
|
|
|
|
function AppHeaderRoot(props) {
|
|
return React.createElement(RemoteAppHeaderElement, props);
|
|
}
|
|
|
|
export const AppHeader = Object.assign(AppHeaderRoot, {
|
|
BrandSection,
|
|
Logo,
|
|
Brand,
|
|
Title,
|
|
Description,
|
|
Bi,
|
|
});
|