refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages ## Package Structure Reorganization - Reorganized 55 packages into categorized subdirectories: - packages/framework/ - Generic framework (Laya/Cocos compatible) - packages/engine/ - ESEngine core modules - packages/rendering/ - Rendering modules (WASM dependent) - packages/physics/ - Physics modules - packages/streaming/ - World streaming - packages/network-ext/ - Network extensions - packages/editor/ - Editor framework and plugins - packages/rust/ - Rust WASM engine - packages/tools/ - Build tools and SDK ## Framework Package Decoupling - Decoupled behavior-tree and blueprint packages from ESEngine dependencies - Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent) - ESEngine-specific code moved to esengine/ subpath exports - Framework packages now usable with Cocos/Laya without ESEngine ## CI Configuration - Updated CI to only type-check and lint framework packages - Added type-check:framework and lint:framework scripts ## Breaking Changes - Package import paths changed due to directory reorganization - ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine') * fix: update es-engine file path after directory reorganization * docs: update README to focus on framework over engine * ci: only build framework packages, remove Rust/WASM dependencies * fix: remove esengine subpath from behavior-tree and blueprint builds ESEngine integration code will only be available in full engine builds. Framework packages are now purely engine-agnostic. * fix: move network-protocols to framework, build both in CI * fix: update workflow paths from packages/core to packages/framework/core * fix: exclude esengine folder from type-check in behavior-tree and blueprint * fix: update network tsconfig references to new paths * fix: add test:ci:framework to only test framework packages in CI * fix: only build core and math npm packages in CI * fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ChevronDown, ChevronRight, Settings } from 'lucide-react';
|
||||
import { IPropertyRenderer, PropertyContext, PropertyRendererRegistry } from '@esengine/editor-core';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
|
||||
interface ComponentData {
|
||||
typeName: string;
|
||||
properties: Record<string, any>;
|
||||
}
|
||||
|
||||
export class ComponentRenderer implements IPropertyRenderer<ComponentData> {
|
||||
readonly id = 'app.component';
|
||||
readonly name = 'Component Renderer';
|
||||
readonly priority = 75;
|
||||
|
||||
canHandle(value: any, _context: PropertyContext): value is ComponentData {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
typeof value.typeName === 'string' &&
|
||||
typeof value.properties === 'object' &&
|
||||
value.properties !== null
|
||||
);
|
||||
}
|
||||
|
||||
render(value: ComponentData, context: PropertyContext): React.ReactElement {
|
||||
const [isExpanded, setIsExpanded] = useState(context.expandByDefault ?? false);
|
||||
const depth = context.depth ?? 0;
|
||||
|
||||
return (
|
||||
<div style={{ marginLeft: depth > 0 ? '12px' : 0 }}>
|
||||
<div
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '6px 8px',
|
||||
backgroundColor: '#3a3a3a',
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '2px'
|
||||
}}
|
||||
>
|
||||
{isExpanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
||||
<Settings size={14} style={{ marginLeft: '4px', color: '#888' }} />
|
||||
<span
|
||||
style={{
|
||||
marginLeft: '6px',
|
||||
fontSize: '12px',
|
||||
fontWeight: 500,
|
||||
color: '#e0e0e0'
|
||||
}}
|
||||
>
|
||||
{value.typeName}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div style={{ marginLeft: '8px', borderLeft: '1px solid #444', paddingLeft: '8px' }}>
|
||||
{Object.entries(value.properties).map(([key, propValue]) => {
|
||||
const registry = Core.services.resolve(PropertyRendererRegistry);
|
||||
const propContext: PropertyContext = {
|
||||
...context,
|
||||
name: key,
|
||||
depth: depth + 1,
|
||||
path: [...(context.path || []), key]
|
||||
};
|
||||
|
||||
const rendered = registry.render(propValue, propContext);
|
||||
if (rendered) {
|
||||
return <div key={key}>{rendered}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={key} className="property-field">
|
||||
<label className="property-label">{key}</label>
|
||||
<span className="property-value-text" style={{ color: '#666', fontStyle: 'italic' }}>
|
||||
[No Renderer]
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import React from 'react';
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { IPropertyRenderer, PropertyContext } from '@esengine/editor-core';
|
||||
|
||||
export class FallbackRenderer implements IPropertyRenderer<any> {
|
||||
readonly id = 'app.fallback';
|
||||
readonly name = 'Fallback Renderer';
|
||||
readonly priority = -1000;
|
||||
|
||||
canHandle(_value: any, _context: PropertyContext): _value is any {
|
||||
return true;
|
||||
}
|
||||
|
||||
render(value: any, context: PropertyContext): React.ReactElement {
|
||||
const typeInfo = this.getTypeInfo(value);
|
||||
|
||||
return (
|
||||
<div className="property-field" style={{ opacity: 0.6 }}>
|
||||
<label className="property-label">{context.name}</label>
|
||||
<span
|
||||
className="property-value-text"
|
||||
style={{ color: '#888', fontStyle: 'italic', fontSize: '0.9em' }}
|
||||
title="No renderer registered for this type"
|
||||
>
|
||||
{typeInfo}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getTypeInfo(value: any): string {
|
||||
if (value === null) return 'null';
|
||||
if (value === undefined) return 'undefined';
|
||||
|
||||
const type = typeof value;
|
||||
|
||||
if (type === 'object') {
|
||||
if (Array.isArray(value)) {
|
||||
return `Array(${value.length})`;
|
||||
}
|
||||
|
||||
const constructor = value.constructor?.name;
|
||||
if (constructor && constructor !== 'Object') {
|
||||
return `[${constructor}]`;
|
||||
}
|
||||
|
||||
const keys = Object.keys(value);
|
||||
if (keys.length === 0) return '{}';
|
||||
if (keys.length <= 3) {
|
||||
return `{${keys.join(', ')}}`;
|
||||
}
|
||||
return `{${keys.slice(0, 3).join(', ')}...}`;
|
||||
}
|
||||
|
||||
return `[${type}]`;
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrayRenderer implements IPropertyRenderer<any[]> {
|
||||
readonly id = 'app.array';
|
||||
readonly name = 'Array Renderer';
|
||||
readonly priority = 50;
|
||||
|
||||
canHandle(value: any, _context: PropertyContext): value is any[] {
|
||||
return Array.isArray(value);
|
||||
}
|
||||
|
||||
render(value: any[], context: PropertyContext): React.ReactElement {
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
const depth = context.depth ?? 0;
|
||||
|
||||
if (value.length === 0) {
|
||||
return (
|
||||
<div className="property-field">
|
||||
<label className="property-label">{context.name}</label>
|
||||
<span className="property-value-text" style={{ color: '#666' }}>
|
||||
[]
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isStringArray = value.every((item) => typeof item === 'string');
|
||||
if (isStringArray && value.length <= 5) {
|
||||
return (
|
||||
<div className="property-field">
|
||||
<label className="property-label">{context.name}</label>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px', marginTop: '4px' }}>
|
||||
{(value as string[]).map((item, index) => (
|
||||
<span
|
||||
key={index}
|
||||
style={{
|
||||
padding: '2px 8px',
|
||||
backgroundColor: '#2d4a3e',
|
||||
color: '#8fbc8f',
|
||||
borderRadius: '4px',
|
||||
fontSize: '11px',
|
||||
fontFamily: 'monospace'
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginLeft: depth > 0 ? '12px' : 0 }}>
|
||||
<div
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '3px 0',
|
||||
fontSize: '11px',
|
||||
borderBottom: '1px solid #333',
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none'
|
||||
}}
|
||||
>
|
||||
{isExpanded ? <ChevronDown size={10} /> : <ChevronRight size={10} />}
|
||||
<span style={{ color: '#9cdcfe', marginLeft: '4px' }}>{context.name}</span>
|
||||
<span
|
||||
style={{
|
||||
color: '#666',
|
||||
fontFamily: 'monospace',
|
||||
marginLeft: '8px',
|
||||
fontSize: '10px'
|
||||
}}
|
||||
>
|
||||
Array({value.length})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import { IPropertyRenderer, PropertyContext } from '@esengine/editor-core';
|
||||
import { formatNumber } from '../../components/inspectors/utils';
|
||||
|
||||
export class StringRenderer implements IPropertyRenderer<string> {
|
||||
readonly id = 'app.string';
|
||||
readonly name = 'String Renderer';
|
||||
readonly priority = 100;
|
||||
|
||||
canHandle(value: any, _context: PropertyContext): value is string {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
|
||||
render(value: string, context: PropertyContext): React.ReactElement {
|
||||
const displayValue = value.length > 50 ? `${value.substring(0, 50)}...` : value;
|
||||
return (
|
||||
<div className="property-field">
|
||||
<label className="property-label">{context.name}</label>
|
||||
<span className="property-value-text" title={value}>
|
||||
{displayValue}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class NumberRenderer implements IPropertyRenderer<number> {
|
||||
readonly id = 'app.number';
|
||||
readonly name = 'Number Renderer';
|
||||
readonly priority = 100;
|
||||
|
||||
canHandle(value: any, _context: PropertyContext): value is number {
|
||||
return typeof value === 'number';
|
||||
}
|
||||
|
||||
render(value: number, context: PropertyContext): React.ReactElement {
|
||||
const decimalPlaces = context.decimalPlaces ?? 4;
|
||||
const displayValue = formatNumber(value, decimalPlaces);
|
||||
|
||||
return (
|
||||
<div className="property-field">
|
||||
<label className="property-label">{context.name}</label>
|
||||
<span className="property-value-text" style={{ color: '#b5cea8' }}>
|
||||
{displayValue}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class BooleanRenderer implements IPropertyRenderer<boolean> {
|
||||
readonly id = 'app.boolean';
|
||||
readonly name = 'Boolean Renderer';
|
||||
readonly priority = 100;
|
||||
|
||||
canHandle(value: any, _context: PropertyContext): value is boolean {
|
||||
return typeof value === 'boolean';
|
||||
}
|
||||
|
||||
render(value: boolean, context: PropertyContext): React.ReactElement {
|
||||
return (
|
||||
<div className="property-field">
|
||||
<label className="property-label">{context.name}</label>
|
||||
<span
|
||||
className="property-value-text"
|
||||
style={{ color: value ? '#4ade80' : '#f87171' }}
|
||||
>
|
||||
{value ? 'true' : 'false'}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class NullRenderer implements IPropertyRenderer<null> {
|
||||
readonly id = 'app.null';
|
||||
readonly name = 'Null Renderer';
|
||||
readonly priority = 100;
|
||||
|
||||
canHandle(value: any, _context: PropertyContext): value is null {
|
||||
return value === null || value === undefined;
|
||||
}
|
||||
|
||||
render(_value: null, context: PropertyContext): React.ReactElement {
|
||||
return (
|
||||
<div className="property-field">
|
||||
<label className="property-label">{context.name}</label>
|
||||
<span className="property-value-text" style={{ color: '#666' }}>
|
||||
null
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import React from 'react';
|
||||
import { IPropertyRenderer, PropertyContext } from '@esengine/editor-core';
|
||||
import { formatNumber } from '../../components/inspectors/utils';
|
||||
|
||||
interface Vector2 {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface Vector3 extends Vector2 {
|
||||
z: number;
|
||||
}
|
||||
|
||||
interface Vector4 extends Vector3 {
|
||||
w: number;
|
||||
}
|
||||
|
||||
interface Color {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
a: number;
|
||||
}
|
||||
|
||||
const VectorValue: React.FC<{
|
||||
label: string;
|
||||
value: number;
|
||||
axis: 'x' | 'y' | 'z' | 'w';
|
||||
decimals: number;
|
||||
}> = ({ label, value, axis, decimals }) => (
|
||||
<div className="property-vector-axis-compact">
|
||||
<span className={`property-vector-axis-label property-vector-axis-${axis}`}>{label}</span>
|
||||
<span className="property-input property-input-number property-input-number-compact" style={{ cursor: 'default' }}>
|
||||
{formatNumber(value, decimals)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export class Vector2Renderer implements IPropertyRenderer<Vector2> {
|
||||
readonly id = 'app.vector2';
|
||||
readonly name = 'Vector2 Renderer';
|
||||
readonly priority = 80;
|
||||
|
||||
canHandle(value: any, _context: PropertyContext): value is Vector2 {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
typeof value.x === 'number' &&
|
||||
typeof value.y === 'number' &&
|
||||
!('z' in value) &&
|
||||
Object.keys(value).length === 2
|
||||
);
|
||||
}
|
||||
|
||||
render(value: Vector2, context: PropertyContext): React.ReactElement {
|
||||
const decimals = context.decimalPlaces ?? 2;
|
||||
return (
|
||||
<div className="property-field">
|
||||
<label className="property-label">{context.name}</label>
|
||||
<div className="property-vector-compact">
|
||||
<VectorValue label="X" value={value.x} axis="x" decimals={decimals} />
|
||||
<VectorValue label="Y" value={value.y} axis="y" decimals={decimals} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Vector3Renderer implements IPropertyRenderer<Vector3> {
|
||||
readonly id = 'app.vector3';
|
||||
readonly name = 'Vector3 Renderer';
|
||||
readonly priority = 80;
|
||||
|
||||
canHandle(value: any, _context: PropertyContext): value is Vector3 {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
typeof value.x === 'number' &&
|
||||
typeof value.y === 'number' &&
|
||||
typeof value.z === 'number' &&
|
||||
!('w' in value) &&
|
||||
Object.keys(value).length === 3
|
||||
);
|
||||
}
|
||||
|
||||
render(value: Vector3, context: PropertyContext): React.ReactElement {
|
||||
const decimals = context.decimalPlaces ?? 2;
|
||||
return (
|
||||
<div className="property-field">
|
||||
<label className="property-label">{context.name}</label>
|
||||
<div className="property-vector-compact">
|
||||
<VectorValue label="X" value={value.x} axis="x" decimals={decimals} />
|
||||
<VectorValue label="Y" value={value.y} axis="y" decimals={decimals} />
|
||||
<VectorValue label="Z" value={value.z} axis="z" decimals={decimals} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ColorRenderer implements IPropertyRenderer<Color> {
|
||||
readonly id = 'app.color';
|
||||
readonly name = 'Color Renderer';
|
||||
readonly priority = 85;
|
||||
|
||||
canHandle(value: any, _context: PropertyContext): value is Color {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
typeof value.r === 'number' &&
|
||||
typeof value.g === 'number' &&
|
||||
typeof value.b === 'number' &&
|
||||
typeof value.a === 'number' &&
|
||||
Object.keys(value).length === 4
|
||||
);
|
||||
}
|
||||
|
||||
render(value: Color, context: PropertyContext): React.ReactElement {
|
||||
const r = Math.round(value.r * 255);
|
||||
const g = Math.round(value.g * 255);
|
||||
const b = Math.round(value.b * 255);
|
||||
const colorHex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
||||
|
||||
return (
|
||||
<div className="property-field">
|
||||
<label className="property-label">{context.name}</label>
|
||||
<div className="property-color-wrapper">
|
||||
<div
|
||||
className="property-color-preview"
|
||||
style={{ backgroundColor: colorHex }}
|
||||
/>
|
||||
<span className="property-input property-input-color-text" style={{ cursor: 'default' }}>
|
||||
{colorHex.toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './PrimitiveRenderers';
|
||||
export * from './VectorRenderers';
|
||||
export * from './ComponentRenderer';
|
||||
export * from './FallbackRenderer';
|
||||
Reference in New Issue
Block a user