UNPKG

userface

Version:

Universal Data-Driven UI Engine with live data, validation, and multi-platform support

364 lines (363 loc) 12.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.renderReact = exports.RenderReact = exports.UserRenderer = exports.useUserFace = exports.useUserContext = exports.ReactContextProvider = void 0; // Условные импорты для поддержки Node.js let React = null; let createContext = null; let useState = null; let useEffect = null; let useContext = null; let useMemo = null; // Проверяем доступность React if (typeof window !== 'undefined' && window.React) { React = window.React; createContext = React.createContext; useState = React.useState; useEffect = React.useEffect; useContext = React.useContext; useMemo = React.useMemo; } else if (typeof require !== 'undefined') { try { React = require('react'); createContext = React.createContext; useState = React.useState; useEffect = React.useEffect; useContext = React.useContext; useMemo = React.useMemo; } catch (error) { // React недоступен в Node.js - это нормально console.warn('React not available in Node.js environment'); } } const errors_1 = require("./errors"); const logger_1 = require("./logger"); const engine_factory_1 = require("./engine-factory"); // React рендерер class ReactRenderer { createElement(type, props, children) { return React.createElement(type, props, ...children); } render(component, container) { // React рендеринг через ReactDOM if (typeof window !== 'undefined' && window.ReactDOM) { window.ReactDOM.render(component, container); } } unmount(container) { if (typeof window !== 'undefined' && window.ReactDOM) { window.ReactDOM.unmountComponentAtNode(container); } } } const UserContext = createContext ? createContext(null) : null; // React контекст провайдер class ReactContextProvider { constructor() { Object.defineProperty(this, "face", { enumerable: true, configurable: true, writable: true, value: null }); Object.defineProperty(this, "options", { enumerable: true, configurable: true, writable: true, value: {} }); Object.defineProperty(this, "contextValue", { enumerable: true, configurable: true, writable: true, value: null }); } initialize(face, options) { this.face = face; this.options = options || {}; } getData() { return this.contextValue; } updateData(data) { this.contextValue = data; } cleanup() { this.face = null; this.contextValue = null; } getContext() { return UserContext; } createProvider(children) { if (!this.face) { throw new Error('Context not initialized'); } return React.createElement(ReactContextProviderComponent, { face: this.face, children, ...this.options }); } } exports.ReactContextProvider = ReactContextProvider; // React компонент провайдера const ReactContextProviderComponent = ({ face, children, remoteBundles = [], registryKey = 'userRegistry', onReady, onError, fallback }) => { const [isReady, setIsReady] = useState ? useState(false) : [false, () => { }]; const [error, setError] = useState ? useState(null) : [null, () => { }]; const bundlesKey = useMemo(() => remoteBundles.join(','), [remoteBundles]); const reload = () => { setIsReady(false); setError(null); loadRemoteBundles(); }; const loadRemoteBundles = async () => { if (remoteBundles.length === 0) { setIsReady(true); onReady?.(); return; } try { window[registryKey] = {}; const promises = remoteBundles.map(url => { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = `${url}?t=${new Date().getTime()}`; script.async = true; script.onload = () => { console.log(`[RenderReact] Loaded remote bundle: ${url}`); resolve(); }; script.onerror = () => { console.error(`[RenderReact] Failed to load remote bundle: ${url}`); reject(new Error(`Failed to load: ${url}`)); }; document.head.appendChild(script); }); }); await Promise.all(promises); setIsReady(true); onReady?.(); } catch (err) { const errorMsg = err instanceof Error ? err.message : String(err); setError(errorMsg); onError?.(errorMsg); } }; useEffect(() => { loadRemoteBundles(); }, [bundlesKey]); const contextValue = { face, isReady, error, reload }; if (error) { return React.createElement('div', { style: { padding: '20px', border: '1px solid #ff6b6b', backgroundColor: '#ffe6e6', color: '#d63031', borderRadius: '4px' } }, `Error: ${error}`); } if (!isReady) { return fallback ? React.createElement(React.Fragment, {}, fallback) : null; } return React.createElement(UserContext.Provider, { value: contextValue }, children); }; // React хуки const useUserContext = () => { const context = useContext(UserContext); if (!context) { throw new Error('useUserContext must be used within a UserContextProvider'); } return context; }; exports.useUserContext = useUserContext; const useUserFace = () => { const context = (0, exports.useUserContext)(); return context.face; }; exports.useUserFace = useUserFace; // React рендерер компонента const UserRenderer = ({ face, fallback, onError }) => { const [error, setError] = useState(null); const [component, setComponent] = useState(null); useEffect(() => { const loadComponent = async () => { try { // Получаем компонент из registry const registry = window.userRegistry || {}; const Component = registry[face.component]; if (!Component) { throw new errors_1.ComponentNotFoundError(face.component); } setComponent(() => Component); setError(null); } catch (err) { const errorMsg = err instanceof Error ? err.message : String(err); setError(errorMsg); onError?.(errorMsg, face); } }; loadComponent(); }, [face.component, onError]); if (error) { return React.createElement('div', { style: { padding: '10px', border: '1px solid #ff6b6b', backgroundColor: '#ffe6e6', color: '#d63031', borderRadius: '4px' } }, `Error: ${error}`); } if (!component) { return fallback ? React.createElement(fallback) : null; } return React.createElement(component, face); }; exports.UserRenderer = UserRenderer; // React платформа рендерер class RenderReact { constructor() { Object.defineProperty(this, "id", { enumerable: true, configurable: true, writable: true, value: 'react' }); Object.defineProperty(this, "meta", { enumerable: true, configurable: true, writable: true, value: { name: 'React Adapter', version: '1.0.0', platform: 'react' } }); Object.defineProperty(this, "renderer", { enumerable: true, configurable: true, writable: true, value: new ReactRenderer() }); } render(spec) { try { // Валидируем спецификацию if (!this.validateSpec(spec)) { throw new Error('Invalid UserFace specification'); } // Получаем компонент из registry const Component = this.getComponentFromRegistry(spec.component); if (!Component) { throw new errors_1.ComponentNotFoundError(spec.component); } // Адаптируем пропсы const props = this.adaptProps(spec); // Валидируем универсальные типы this.validateUniversalTypes(props); // Рендерим компонент return React.createElement(Component, props); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger_1.logger.error(`React render failed: ${errorMessage}`, 'RenderReact', error instanceof Error ? error : undefined); throw error; } } isCompatible(component) { return React.isValidElement(component) || typeof component === 'function' || component.$$typeof === React.elementType; } getSupportedComponents() { return ['div', 'span', 'button', 'input', 'form', 'img', 'a', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']; } validateSpec(spec) { return spec && typeof spec.component === 'string' && spec.component.length > 0; } getComponentFromRegistry(name) { // Получаем компонент из Engine return engine_factory_1.engine.getComponent(name); } adaptProps(props) { const adaptedProps = {}; for (const [key, value] of Object.entries(props)) { if (key === 'component' || key === 'data') continue; // Адаптируем события if (key.startsWith('on') && typeof value === 'function') { const eventName = this.mapEventName(key); if (eventName) { adaptedProps[eventName] = value; } } else { adaptedProps[key] = value; } } return adaptedProps; } validateUniversalTypes(props) { for (const [key, value] of Object.entries(props)) { if (key === 'children') continue; // Проверяем типы if (value !== null && value !== undefined) { const type = typeof value; if (!['string', 'number', 'boolean', 'function', 'object'].includes(type)) { logger_1.logger.warn(`Unsupported prop type: ${type} for ${key}`, 'RenderReact'); } } } } validateType(value, type) { switch (type) { case 'text': return typeof value === 'string'; case 'number': return typeof value === 'number' && !isNaN(value); case 'boolean': return typeof value === 'boolean'; case 'function': return typeof value === 'function'; case 'object': return typeof value === 'object' && value !== null; case 'array': return Array.isArray(value); default: return true; } } mapEventName(eventName) { const eventMap = { 'onClick': 'onClick', 'onChange': 'onChange', 'onSubmit': 'onSubmit', 'onFocus': 'onFocus', 'onBlur': 'onBlur', 'onKeyDown': 'onKeyDown', 'onKeyUp': 'onKeyUp', 'onMouseEnter': 'onMouseEnter', 'onMouseLeave': 'onMouseLeave' }; return eventMap[eventName] || null; } } exports.RenderReact = RenderReact; // Экспортируем рендерер exports.renderReact = new RenderReact();