UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

449 lines (393 loc) 10.6 kB
/** * Base Template Class * 모든 서비스 티어 템플릿이 상속받을 베이스 클래스 */ import { BaseTemplate, ServiceTier, BaseComponent, ComponentType, TemplateContext, ComponentRenderConfig, TemplateRenderConfig, } from "../../types/template-system"; import { componentRegistry } from "./component-registry"; export abstract class TemplateBase { protected template: BaseTemplate; constructor(template: BaseTemplate) { this.template = template; } /** * 템플릿 ID 반환 */ getId(): string { return this.template.id; } /** * 템플릿 이름 반환 */ getName(): string { return this.template.name; } /** * 서비스 티어 반환 */ getTier(): ServiceTier { return this.template.tier; } /** * 템플릿 슬러그 반환 */ getSlug(): string { return this.template.slug; } /** * 템플릿 설명 반환 */ getDescription(): string { return this.template.description; } /** * 전체 템플릿 데이터 반환 */ getTemplate(): BaseTemplate { return { ...this.template }; } /** * 컴포넌트 목록 반환 */ getComponents(): BaseComponent[] { return [...this.template.components]; } /** * 특정 타입의 컴포넌트 조회 */ getComponent(type: ComponentType): BaseComponent | null { return this.template.components.find((c) => c.type === type) || null; } /** * 활성화된 컴포넌트만 반환 */ getActiveComponents(): BaseComponent[] { return this.template.components.filter( (c) => c.settings.enabled && c.settings.visible ); } /** * 컴포넌트 추가 */ addComponent(component: BaseComponent): boolean { // 해당 티어에서 컴포넌트가 사용 가능한지 확인 if (!componentRegistry.validate(component, this.template.tier)) { return false; } // 이미 같은 타입의 컴포넌트가 있는지 확인 const existingComponent = this.getComponent(component.type); if (existingComponent) { return false; } this.template.components.push(component); this.sortComponentsByOrder(); return true; } /** * 컴포넌트 제거 */ removeComponent(componentId: string): boolean { const index = this.template.components.findIndex( (c) => c.id === componentId ); if (index === -1) { return false; } const component = this.template.components[index]; // 필수 컴포넌트는 제거할 수 없음 if (component.isRequired) { return false; } this.template.components.splice(index, 1); return true; } /** * 컴포넌트 업데이트 */ updateComponent( componentId: string, updates: Partial<BaseComponent> ): boolean { const component = this.template.components.find( (c) => c.id === componentId ); if (!component) { return false; } // 타입은 변경할 수 없음 if (updates.type && updates.type !== component.type) { return false; } Object.assign(component, updates); // 순서가 변경된 경우 정렬 if (updates.order !== undefined) { this.sortComponentsByOrder(); } return true; } /** * 컴포넌트 순서 변경 */ reorderComponents(componentIds: string[]): boolean { // 모든 컴포넌트가 존재하는지 확인 const allComponents = this.template.components; if (componentIds.length !== allComponents.length) { return false; } const reorderedComponents: BaseComponent[] = []; componentIds.forEach((id, index) => { const component = allComponents.find((c) => c.id === id); if (component) { component.order = index; reorderedComponents.push(component); } }); if (reorderedComponents.length === allComponents.length) { this.template.components = reorderedComponents; return true; } return false; } /** * 컴포넌트를 순서대로 정렬 */ private sortComponentsByOrder(): void { this.template.components.sort((a, b) => a.order - b.order); } /** * 컴포넌트 활성화/비활성화 */ toggleComponent(componentId: string, enabled: boolean): boolean { const component = this.template.components.find( (c) => c.id === componentId ); if (!component) { return false; } // 필수 컴포넌트는 비활성화할 수 없음 if (!enabled && component.isRequired) { return false; } component.settings.enabled = enabled; return true; } /** * 컴포넌트 표시/숨김 */ toggleComponentVisibility(componentId: string, visible: boolean): boolean { const component = this.template.components.find( (c) => c.id === componentId ); if (!component) { return false; } // 필수 컴포넌트는 숨길 수 없음 if (!visible && component.isRequired) { return false; } component.settings.visible = visible; return true; } /** * 글로벌 설정 업데이트 */ updateGlobalSettings( updates: Partial<typeof this.template.globalSettings> ): void { Object.assign(this.template.globalSettings, updates); } /** * 테마 색상 업데이트 */ updateThemeColors(colors: { primaryColor?: string; secondaryColor?: string; accentColor?: string; }): void { Object.assign(this.template.globalSettings.theme, colors); } /** * 레이아웃 설정 업데이트 */ updateLayoutSettings( layout: Partial<typeof this.template.globalSettings.layout> ): void { Object.assign(this.template.globalSettings.layout, layout); } /** * 커스터마이제이션 옵션 확인 */ canCustomize(feature: string): boolean { const options = this.template.customizationOptions; switch (feature) { case "colors": return options.colors.allowCustomColors; case "fonts": return options.typography.allowFontChange; case "layout": return options.layout.allowLayoutChange; case "components": return options.components.allowComponentToggle; case "css": return options.advanced.allowCustomCSS; case "javascript": return options.advanced.allowCustomJS; default: return false; } } /** * 렌더링 설정 생성 */ createRenderConfig(context: TemplateContext): TemplateRenderConfig { const components = this.getActiveComponents().map((component) => this.createComponentRenderConfig(component, context) ); return { template: this.template, components, globalProps: this.createGlobalProps(context), context, }; } /** * 컴포넌트 렌더링 설정 생성 */ protected createComponentRenderConfig( component: BaseComponent, context: TemplateContext ): ComponentRenderConfig { return { component, props: this.createComponentProps(component, context), conditionalRender: this.createConditionalRender(component, context), }; } /** * 컴포넌트 props 생성 (하위 클래스에서 구현) */ protected abstract createComponentProps( component: BaseComponent, context: TemplateContext ): Record<string, unknown>; /** * 조건부 렌더링 설정 생성 */ protected createConditionalRender( component: BaseComponent, _context: TemplateContext ): ComponentRenderConfig["conditionalRender"] { // 기본적으로는 조건부 렌더링 없음 return component.settings.enabled && component.settings.visible ? undefined : { condition: "false", fallback: undefined, }; } /** * 글로벌 props 생성 */ protected createGlobalProps( context: TemplateContext ): Record<string, unknown> { return { theme: this.template.globalSettings.theme, layout: this.template.globalSettings.layout, seo: this.template.globalSettings.seo, siteData: context.siteData, tier: this.template.tier, }; } /** * 템플릿 유효성 검사 */ validate(): { isValid: boolean; errors: string[] } { const errors: string[] = []; // 필수 컴포넌트 확인 const requiredTypes = [ComponentType.HERO, ComponentType.FOOTER]; for (const type of requiredTypes) { const component = this.getComponent(type); if (!component || !component.settings.enabled) { errors.push(`필수 컴포넌트 ${type}가 누락되었거나 비활성화되었습니다.`); } } // 티어 제한사항 확인 const tierRestrictions = componentRegistry.getTierRestrictions( this.template.tier ); for (const component of this.template.components) { if (!tierRestrictions.allowedComponents.includes(component.type)) { errors.push( `컴포넌트 ${component.type}는 ${this.template.tier} 티어에서 허용되지 않습니다.` ); } } return { isValid: errors.length === 0, errors, }; } /** * 템플릿 복제 */ clone(): TemplateBase { const clonedTemplate = JSON.parse(JSON.stringify(this.template)); return new (this.constructor as new ( template: BaseTemplate ) => TemplateBase)(clonedTemplate); } /** * 템플릿 상태 스냅샷 생성 */ createSnapshot(): string { return JSON.stringify(this.template); } /** * 스냅샷에서 템플릿 복원 */ restoreFromSnapshot(snapshot: string): boolean { try { const restoredTemplate = JSON.parse(snapshot); this.template = restoredTemplate; return true; } catch { return false; } } } /** * 템플릿 생성 헬퍼 함수 */ export function createTemplateInstance(template: BaseTemplate): TemplateBase { // 실제 구현에서는 티어에 따라 적절한 템플릿 클래스를 반환 // 현재는 기본 구현체 반환 return new DefaultTemplate(template); } /** * 기본 템플릿 구현체 */ class DefaultTemplate extends TemplateBase { protected createComponentProps( component: BaseComponent, context: TemplateContext ): Record<string, unknown> { return { id: component.id, type: component.type, title: component.title, description: component.description, settings: component.settings, content: context.content[component.type] || {}, theme: this.template.globalSettings.theme, tier: this.template.tier, }; } }