userface
Version:
Universal Data-Driven UI Engine with live data, validation, and multi-platform support
364 lines (363 loc) • 12.5 kB
JavaScript
;
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();