feat: Page Editor UX Phase 1 - live preview, hover mapping, visual layout picker

- PreviewHighlightService + appHighlightSource directive: shared hover/focus bridge between editor fields and the schematic live preview.
- BuilderLivePreviewComponent: in-page schematic homepage render (header/hero/blocks/footer) reading the same bootstrap the sections mutate, highlighting the area matching the active field.
- VisualLayoutPickerComponent: card-based layout picker (ControlValueAccessor, same shape as app-select) replacing the raw layout <select> in homepage-section.
- app-form-field gains optional usedBy/usedByLabel inputs for the "Where is this used?" helper text, wired into aria-describedby.
- Wired homepage/branding/theme sections with highlight sources + usedBy hints; live preview panel shown in project-editor-page for those three sections.
- All new copy added as translation keys (en/ru/hy).
This commit is contained in:
sdarbinyan
2026-07-27 10:00:42 +04:00
parent 96c1527d1b
commit 42f11dd8c0
24 changed files with 735 additions and 45 deletions

View File

@@ -13,6 +13,9 @@ import { SectionConfig } from '../../../shared/models/config';
import { IconComponent } from '../../../shared/ui/icon/icon.component';
import { AppIconName } from '../../../shared/ui/icon/icon-registry';
import { ConfirmDialogComponent } from '../../../shared/ui/confirm-dialog/confirm-dialog.component';
import { FormFieldComponent } from '../../../shared/ui/form-field/form-field.component';
import { VisualLayoutPickerComponent, VisualLayoutOption } from '../../../shared/ui/visual-layout-picker/visual-layout-picker.component';
import { HighlightSourceDirective } from '../../../shared/ui/highlight-source/highlight-source.directive';
export interface LayoutStrategyOption {
value: 'stack' | 'grid' | 'hero' | 'carousel' | 'split';
@@ -45,7 +48,7 @@ const BLOCK_BY_TYPE = new Map(BLOCK_CATALOG.map(entry => [entry.type, entry]));
@Component({
selector: 'app-project-editor-homepage-section',
standalone: true,
imports: [DragDropModule, FormsModule, TranslatePipe, InputComponent, SectionCardComponent, ToggleComponent, EmptyStateComponent, HomepageOverviewComponent, IconComponent, ConfirmDialogComponent],
imports: [DragDropModule, FormsModule, TranslatePipe, InputComponent, SectionCardComponent, ToggleComponent, EmptyStateComponent, HomepageOverviewComponent, IconComponent, ConfirmDialogComponent, FormFieldComponent, VisualLayoutPickerComponent, HighlightSourceDirective],
templateUrl: './homepage-section.component.html',
styleUrls: ['./section.shared.scss', './homepage-section.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@@ -70,9 +73,21 @@ export class ProjectEditorHomepageSectionComponent {
{ value: 'split', labelKey: 'builder.layoutStrategySplit', descKey: 'builder.layoutStrategySplitDesc' },
];
layoutStrategyDescKey(strategy: string | undefined): string {
return this.layoutStrategyOptions.find(option => option.value === strategy)?.descKey ?? '';
}
/** Card icons for the visual layout picker - purely decorative shorthand for each strategy's shape. */
private readonly layoutStrategyIcons: Record<LayoutStrategyOption['value'], AppIconName> = {
stack: 'list',
grid: 'layoutGrid',
hero: 'image',
carousel: 'images',
split: 'table',
};
readonly layoutStrategyPickerOptions: VisualLayoutOption[] = this.layoutStrategyOptions.map(option => ({
value: option.value,
labelKey: option.labelKey,
descKey: option.descKey,
icon: this.layoutStrategyIcons[option.value],
}));
blockLabel(type: string): string {
const entry = BLOCK_BY_TYPE.get(type);