feat(project-editor): add sticky save/publish bar with validation summary
Mounts a new ProjectEditorSaveBarComponent in the editor page that shows draft/published status, unsaved-changes indicator, and validation issues, wiring the Task 8 facade save()/publish()/dirty/status/validationIssues signals to an actual UI for the first time.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<div class="project-editor-save-bar">
|
||||
<div class="project-editor-save-bar-status">
|
||||
<span>{{ (status() === 'published' ? 'builder.statusPublished' : 'builder.statusDraft') | translate }}</span>
|
||||
@if (dirty()) {
|
||||
<span class="project-editor-save-bar-dirty">{{ 'builder.unsavedChanges' | translate }}</span>
|
||||
}
|
||||
@if (issues().length > 0) {
|
||||
<ul class="project-editor-save-bar-issues">
|
||||
@for (issue of issues(); track issue.code) {
|
||||
<li>{{ issue.message | translate }}</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
<div class="project-editor-save-bar-actions">
|
||||
<button type="button" (click)="save()">{{ 'builder.save' | translate }}</button>
|
||||
<button type="button" [disabled]="issues().length > 0" (click)="publish()">{{ 'builder.publish' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
.project-editor-save-bar {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: var(--surface, #fff);
|
||||
border-top: 1px solid var(--border, #ddd);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.project-editor-save-bar-dirty {
|
||||
color: var(--warning, #b45309);
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.project-editor-save-bar-issues {
|
||||
margin: 0.25rem 0 0;
|
||||
padding-left: 1.25rem;
|
||||
color: var(--danger, #b91c1c);
|
||||
}
|
||||
|
||||
.project-editor-save-bar-actions button + button {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
||||
import { ProjectEditorFacade } from '../../facade/project-editor.facade';
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-editor-save-bar',
|
||||
standalone: true,
|
||||
imports: [TranslatePipe],
|
||||
templateUrl: './project-editor-save-bar.component.html',
|
||||
styleUrls: ['./project-editor-save-bar.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ProjectEditorSaveBarComponent {
|
||||
private readonly facade = inject(ProjectEditorFacade);
|
||||
readonly dirty = this.facade.dirty;
|
||||
readonly status = this.facade.status;
|
||||
readonly issues = this.facade.validationIssues;
|
||||
|
||||
save(): void {
|
||||
this.facade.save();
|
||||
}
|
||||
|
||||
publish(): void {
|
||||
this.facade.publish();
|
||||
}
|
||||
}
|
||||
@@ -28,5 +28,6 @@
|
||||
@case ('preview') { <app-project-editor-preview-section /> }
|
||||
}
|
||||
</section>
|
||||
<app-project-editor-save-bar />
|
||||
}
|
||||
</main>
|
||||
|
||||
@@ -17,6 +17,7 @@ import { ProjectEditorNavigationSectionComponent } from '../sections/navigation-
|
||||
import { ProjectEditorPreviewSectionComponent } from '../sections/preview-section.component';
|
||||
import { TranslatePipe } from '../../../i18n/translate.pipe';
|
||||
import { StaticPagesEditorComponent } from '../../content-management/components/static-pages-editor.component';
|
||||
import { ProjectEditorSaveBarComponent } from '../components/save-bar/project-editor-save-bar.component';
|
||||
import { ProjectEditorSectionId } from '../models/project-editor.model';
|
||||
|
||||
const KNOWN_SECTIONS: ProjectEditorSectionId[] = [
|
||||
@@ -41,6 +42,7 @@ const KNOWN_SECTIONS: ProjectEditorSectionId[] = [
|
||||
ProjectEditorLanguagesSectionComponent,
|
||||
ProjectEditorNavigationSectionComponent,
|
||||
ProjectEditorPreviewSectionComponent,
|
||||
ProjectEditorSaveBarComponent,
|
||||
],
|
||||
templateUrl: './project-editor-page.component.html',
|
||||
styleUrls: ['./project-editor-page.component.scss'],
|
||||
|
||||
@@ -512,6 +512,11 @@ export const en: Translations = {
|
||||
validationMissingWidget: 'A homepage section has a widget with no type.',
|
||||
validationDuplicateNavLinks: 'Two or more header navigation links are duplicates.',
|
||||
validationInvalidColors: 'One or more theme colors are not valid hex colors.',
|
||||
statusDraft: 'Draft',
|
||||
statusPublished: 'Published',
|
||||
unsavedChanges: 'Unsaved changes',
|
||||
save: 'Save',
|
||||
publish: 'Publish',
|
||||
},
|
||||
staticPages: {
|
||||
notFound: 'Page not found',
|
||||
|
||||
@@ -512,6 +512,11 @@ export const hy: Translations = {
|
||||
validationMissingWidget: 'Գլխավոր էջի սեկցիաներից մեկն ունի վիջեթ առանց տիպի։',
|
||||
validationDuplicateNavLinks: 'Վերնագրի նավիգացիայում կան կրկնվող հղումներ։',
|
||||
validationInvalidColors: 'Թեմայի գույներից մեկը կամ մի քանիսը վավեր hex գույն չեն։',
|
||||
statusDraft: 'Սևագիր',
|
||||
statusPublished: 'Հրապարակված',
|
||||
unsavedChanges: 'Չպահված փոփոխություններ',
|
||||
save: 'Պահպանել',
|
||||
publish: 'Հրապարակել',
|
||||
},
|
||||
staticPages: {
|
||||
notFound: 'Էջը չի գտնվել',
|
||||
|
||||
@@ -512,6 +512,11 @@ export const ru: Translations = {
|
||||
validationMissingWidget: 'В секции главной страницы есть виджет без типа.',
|
||||
validationDuplicateNavLinks: 'Две или более ссылки в навигации шапки дублируются.',
|
||||
validationInvalidColors: 'Один или несколько цветов темы указаны некорректно.',
|
||||
statusDraft: 'Черновик',
|
||||
statusPublished: 'Опубликовано',
|
||||
unsavedChanges: 'Есть несохранённые изменения',
|
||||
save: 'Сохранить',
|
||||
publish: 'Опубликовать',
|
||||
},
|
||||
staticPages: {
|
||||
notFound: 'Страница не найдена',
|
||||
|
||||
@@ -510,6 +510,11 @@ export interface Translations {
|
||||
validationMissingWidget: string;
|
||||
validationDuplicateNavLinks: string;
|
||||
validationInvalidColors: string;
|
||||
statusDraft: string;
|
||||
statusPublished: string;
|
||||
unsavedChanges: string;
|
||||
save: string;
|
||||
publish: string;
|
||||
};
|
||||
staticPages: {
|
||||
notFound: string;
|
||||
|
||||
Reference in New Issue
Block a user