@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
449 lines (393 loc) • 10.6 kB
text/typescript
/**
* 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,
};
}
}