File

src/lib/fs-editing-overlay/fs-editing-overlay.component.ts

Description

This overlay component marks empty slots in that content can be added.

Implements

OnDestroy

Metadata

Index

Properties
Methods

Constructor

constructor(componentData: CmsComponentData<FsEditingOverlay>, cmsService: CmsService, tppWrapperService: TppWrapperService, previewService: PreviewService, routingService: RoutingService, changeDetectorRef: ChangeDetectorRef)
Parameters :
Name Type Optional
componentData CmsComponentData<FsEditingOverlay> No
cmsService CmsService No
tppWrapperService TppWrapperService No
previewService PreviewService No
routingService RoutingService No
changeDetectorRef ChangeDetectorRef No

Methods

Async addContent
addContent()
Returns : Promise<void>
Private Async createPage
createPage(page: Page, routerState: RouterState)
Parameters :
Name Type Optional
page Page No
routerState RouterState No
Returns : Promise<CreatePageResult | void>
Private Async createSection
createSection(previewElement: string, componentData: FsEditingOverlay)
Parameters :
Name Type Optional
previewElement string No
componentData FsEditingOverlay No
Returns : Promise<void>
Private getComponentDataWithFlexType
getComponentDataWithFlexType(componentUid: string)
Parameters :
Name Type Optional
componentUid string No
Returns : any
Private getLatestPagePreviewData
getLatestPagePreviewData()
Returns : Observable<>
ngOnDestroy
ngOnDestroy()
Returns : void

Properties

components$
Type : Observable<any[]>
isButtonDisabled
Default value : false
Private subs$
Default value : new Subscription()
Static Readonly TYPE_CODE
Type : string
Default value : 'FsEditingOverlay'
import { PreviewTranslationKey as TranslationKey } from '../fs/cms/page/preview/preview-translation.service';
import { Component, ChangeDetectorRef, OnDestroy } from '@angular/core';
import { CmsComponent, CmsService, RoutingService, Page, RouterState, PageType } from '@spartacus/core';
import { CmsComponentData } from '@spartacus/storefront';
import { combineLatest, Observable, from, of, Subscription } from 'rxjs';
import { map, switchMap, take, withLatestFrom } from 'rxjs/operators';
import { TppWrapperService } from '../fs/cms/page/tpp-wrapper-service';
import { nullSafe, errorToString } from 'fs-spartacus-common';
import { PreviewService } from '../fs/cms/page/preview/preview.service';
import { extractPageUniqueId } from '../fs/util/occ-cms-pages';
import { CreatePageResult } from '../fs/cms/page/fs-tpp-api.data';

/**
 * This overlay component marks empty slots in that content can be added.
 */
@Component({
    selector: 'fs-fs-editing-overlay',
    templateUrl: './fs-editing-overlay.component.html',
    styleUrls: ['./fs-editing-overlay.component.css'],
    standalone: false
})
export class FsEditingOverlayComponent implements OnDestroy {
  static readonly TYPE_CODE = 'FsEditingOverlay';
  isButtonDisabled = false;
  private subs$ = new Subscription();
  components$: Observable<any[]>;

  constructor(
    private componentData: CmsComponentData<FsEditingOverlay>,
    private cmsService: CmsService,
    private tppWrapperService: TppWrapperService,
    private previewService: PreviewService,
    private routingService: RoutingService,
    private changeDetectorRef: ChangeDetectorRef
  ) {
    // components$ and getComponentDataWithFlexType were copied from 'tab-paragraph-container.component.ts'
    // in the Spartacus storefrontlib.
    this.components$ = this.componentData.data$.pipe(
      switchMap((data) =>
        combineLatest([
          data != null
            ? data.components
                .split(',')
                .map((component) => component.trim())
                .filter((component) => '' !== component)
                .map((component) => this.getComponentDataWithFlexType(component))
            : of([]),
        ])
      )
    );
  }

  ngOnDestroy(): void {
    if (this.subs$) {
      this.subs$.unsubscribe();
    }
  }

  private getComponentDataWithFlexType(componentUid: string) {
    return this.cmsService.getComponentData<any>(componentUid).pipe(
      map((componentData) => {
        if (!componentData.flexType) {
          componentData = {
            ...componentData,
            flexType: componentData.typeCode,
          };
        }
        return {
          ...componentData,
        };
      })
    );
  }

  private async createSection(previewElement: string, componentData: FsEditingOverlay): Promise<void> {
    return this.previewService.createSection(previewElement, componentData);
  }

  private async createPage(page: Page, routerState: RouterState): Promise<CreatePageResult | void> {
    if (page != null && routerState != null && routerState.state != null && routerState.state.context != null) {
      const pageData = extractPageUniqueId(routerState.state.context);
      if (pageData != null) {
        return this.previewService.createPage(pageData.pageId, page.template, routerState.state.context.type).catch((createPageError) => {
          this.previewService.showDetailedErrorDialog(TranslationKey.CREATE_PAGE_UNEXPECTED_ERROR, {
            errorMessage: errorToString(createPageError),
          });
        });
      } else {
        this.previewService.showErrorDialog(TranslationKey.MISSING_ROUTING_DATA);
      }
    } else {
      this.previewService.showErrorDialog(TranslationKey.MISSING_PAGE_DATA);
    }
  }

  private getLatestPagePreviewData(): Observable<[string, [FsEditingOverlay, Page, RouterState]]> {
    // In theory, this.tppWrapperService.getPreviewElement() and this.componentData.data$ could emit more than one value.
    // But of course we only want to create a section once.
    // Therefore we get the latest value from each Observable, but only subscribe to one combination of the results.
    const previewElement$ = from(this.tppWrapperService.getPreviewElement());
    const currentPage$ = this.cmsService.getCurrentPage();
    return previewElement$.pipe(
      withLatestFrom(combineLatest([this.componentData.data$, currentPage$, this.routingService.getRouterState()])),
      take(1)
    );
  }

  async addContent(): Promise<void> {
    if (!this.isButtonDisabled) {
      this.isButtonDisabled = true;
      this.subs$.add(
        this.getLatestPagePreviewData().subscribe(async (pagePreviewData) => {
          const [previewElement, componentDataAndPage] = nullSafe(pagePreviewData, []);
          const [componentData, page, routerState] = nullSafe(componentDataAndPage, []);
          if (await this.previewService.isFirstSpiritManagedPage(previewElement)) {
            await this.createSection(previewElement, componentData);
          } else {
            const createPageResult = (await this.createPage(page, routerState)) as CreatePageResult;
            if (createPageResult != null && createPageResult.identifier != null) {
              const { uid, identifier, displayname, name } = createPageResult;
              console.log(
                `Successfully created the page '${displayname || name}' (template: ${
                  page.template
                }, uid: ${uid}, identifier: ${identifier})`
              );
              const hybrisPageId = `${routerState.state.context.type || PageType.CONTENT_PAGE}:${routerState.state.context.id}`;
              await this.tppWrapperService.setHybrisPageId(uid, hybrisPageId);
              await this.tppWrapperService.setPreviewElement(identifier);
              await this.tppWrapperService.triggerRerenderView();
              await this.createSection(identifier, componentData);
            } else {
              console.log('The creation of the page was cancelled.');
            }
          }
          this.isButtonDisabled = false;
          this.changeDetectorRef.detectChanges();
        })
      );
    }
  }
}

export interface FsEditingOverlay extends CmsComponent {
  components?: string;
  slotName: string;
}
<ng-container *ngIf="components$ | async">
  <div
    #fsContainer
    class="fs-content-connect-slot
              fs-content-connect-editing-container
              fs-editing

              {{ (components$ | async).length <= 0 ? 'fs-content-connect-highlight-content-area' : '' }}"
    data-fs-content-editing
  >
    <div class="fs-container-overlay"></div>
    <div class="fs-content-connect-button-container">
      <button class="fs-content-connect-editing-button" [disabled]="isButtonDisabled" (click)="addContent()">
        <span class="fs-content-connect-icon fs-content-connect-icon-add"></span>
        <span class="fs-content-connect-button-label">{{ (components$ | async).length > 0 ? 'override content' : 'add content' }}</span>
      </button>
    </div>
    <div class="fs-content-connect-content-container">
      <ng-container *ngFor="let component of components$ | async">
        <ng-template [cxOutlet]="component.flexType" [cxOutletContext]="{}" [cxComponentWrapper]="component"> </ng-template>
      </ng-container>
    </div>
  </div>
</ng-container>

./fs-editing-overlay.component.css

.fs-content-connect-slot {
  position: relative;
}

.fs-content-connect-editing-container {
  min-height: 6rem;
}

.fs-editing {
  height: 100%;
}

.fs-content-connect-editing-container .fs-content-connect-button-container {
  width: 100%;
  position: absolute;
  top: 50%;
  z-index: 1000;
  -webkit-transform: translateY(-50%);
  transform: translateY(-50%);
}

.fs-content-connect-content-container {
  display: flex;
  flex-wrap: wrap;
  height: 100%;
}

.fs-content-connect-editing-container .fs-content-connect-content-container {
  filter: contrast(50%);
}
.fs-content-connect-editing-container .fs-content-connect-content-container::after {
  display: block;
  clear: both;
  content: '';
}
.fs-content-connect-editing-container:hover .fs-content-connect-content-container {
  filter: contrast(70%);
  transition: all 0.2s ease-in-out;
}
@media screen and (prefers-reduced-motion: reduce) {
  .fs-content-connect-editing-container:hover .fs-content-connect-content-container {
    transition: none;
  }
}
.fs-content-connect-editing-container.fs-content-connect-highlight-content-area {
  border: 0.15rem transparent solid;
}
.fs-content-connect-editing-container.fs-content-connect-highlight-content-area:hover {
  background-color: rgba(108, 117, 125, 0.3);
}
.fs-content-connect-editing-container.fs-content-connect-highlight-content-area .fs-content-connect-button-container {
  position: relative;
  top: 0;
  transform: translateY(0%);
}
.fs-content-connect-editing-container .fs-content-connect-editing-button {
  display: block;
  width: auto;
  min-height: 3.9rem;
  min-width: 3.9rem;
  margin: 1rem auto;
  position: relative;
  top: 50%;
  color: #fff;
  font-size: 2.25rem;
  font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
  font-weight: 300;
  background-color: #3288c3;
  border-radius: 3.65rem;
  border: 0.15rem #3288c3 solid;
  -webkit-user-select: none;
  /* Safari */
  -moz-user-select: none;
  /* Firefox */
  -ms-user-select: none;
  /* IE10+/Edge */
  user-select: none;
  /* Standard */
}
.fs-content-connect-editing-container .fs-content-connect-editing-button .fs-content-connect-icon {
  color: #fff;
  margin-top: -0.3rem;
  box-sizing: border-box;
  display: inline-block;
  height: 1em;
  width: 1em;
  position: relative;
  font-size: inherit;
  font-style: normal;
  text-indent: -9999px;
  vertical-align: middle;
}
.fs-content-connect-editing-container .fs-content-connect-editing-button .fs-content-connect-icon::before,
.fs-content-connect-editing-container .fs-content-connect-editing-button .fs-content-connect-icon::after {
  display: block;
  left: 50%;
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
}
.fs-content-connect-editing-container .fs-content-connect-editing-button .fs-content-connect-icon.fs-content-connect-icon-add::before {
  content: '';
  background: currentColor;
  height: 0.2rem;
  width: 100%;
  transform: translate(-50%, -50%);
}
.fs-content-connect-editing-container .fs-content-connect-editing-button .fs-content-connect-icon.fs-content-connect-icon-add::after {
  content: '';
  background: currentColor;
  height: 100%;
  width: 0.2rem;
  transform: translate(-50%, -50%);
}
.fs-content-connect-editing-container .fs-content-connect-editing-button .fs-content-connect-button-label {
  display: inline-block;
  max-width: 0;
  opacity: 0;
  white-space: nowrap;
  transition: 1s, opacity 0.2s;
}
@media screen and (prefers-reduced-motion: reduce) {
  .fs-content-connect-editing-container .fs-content-connect-editing-button .fs-content-connect-button-label {
    transition: none;
  }
}
.fs-content-connect-editing-container .fs-content-connect-editing-button:hover {
  background-color: #266895;
  border-color: #266895;
}
.fs-content-connect-editing-container
  .fs-content-connect-button-container:hover
  .fs-content-connect-editing-button
  .fs-content-connect-button-label {
  opacity: 1;
  padding: 0 0.8rem;
  max-width: 50rem;
  transition: max-width 2s ease-out 0.1s, opacity 1s ease-out 0.5s;
}
@media screen and (prefers-reduced-motion: reduce) {
  .fs-content-connect-editing-container .fs-content-connect-editing-button .fs-content-connect-button-label:hover {
    transition: none;
  }
}

.fs-container-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgb(108, 117, 125);
  opacity: 0.45;
}

.fs-content-connect-slot:hover .fs-container-overlay {
  opacity: 0.3;
  transition: opacity 0.2s ease-in-out;
}

@media screen and (prefers-reduced-motion: reduce) {
  .fs-content-connect-slot:hover .fs-container-overlay {
    transition: none;
  }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""