Skip to content

Commit

Permalink
#24040 Create a video's thumbnail component (#24097)
Browse files Browse the repository at this point in the history
* dev: fix vertial videos thumbnail

* dev: Show/hide thumbnails based on the dotmarketing SHOW_VIDEO_THUMBNAIL property

* clean up

* clean up v2

* clean up v2

* remove 'fit'
  • Loading branch information
rjvelazco committed Feb 16, 2023
1 parent 21f12f3 commit e4a3d0e
Show file tree
Hide file tree
Showing 19 changed files with 217 additions and 37 deletions.
Expand Up @@ -2,16 +2,17 @@
#sidebar
[blockScroll]="true"
[visible]="!!blockEditorInput"
position="right"
[dismissible]="false"
[showCloseIcon]="false"
[closeOnEscape]="false"
(onHide)="sidebar.destroyModal(); closeSidebar()"
position="right"
data-testid="sidebar"
>
<div class="container" *ngIf="blockEditorInput">
<dot-block-editor
#blockEditor
[showVideoThumbnail]="showVideoThumbnail"
[lang]="blockEditorInput.language"
[allowedBlocks]="blockEditorInput.fieldVariables.allowedBlocks"
[allowedContentTypes]="blockEditorInput.fieldVariables.allowedContentTypes"
Expand All @@ -21,20 +22,20 @@
</dot-block-editor>
<footer>
<button
pButton
class="p-button-secondary"
type="button"
(click)="closeSidebar()"
[label]="'Cancel' | dm"
(click)="closeSidebar()"
pButton
type="button"
data-testid="cancelBtn"
></button>
<button
[disabled]="saving"
[loading]="saving"
[label]="'Update' | dm"
(click)="saveEditorChanges()"
pButton
type="button"
(click)="saveEditorChanges()"
[label]="'Update' | dm"
data-testid="updateBtn"
></button>
</footer>
Expand Down
Expand Up @@ -15,6 +15,7 @@ import {
DotContentTypeService,
DotEventsService,
DotMessageService,
DotPropertiesService,
DotWorkflowActionsFireService
} from '@dotcms/data-access';
import { CoreWebService } from '@dotcms/dotcms-js';
Expand All @@ -33,6 +34,7 @@ export class MockDotBlockEditorComponent {
@Input() allowedContentTypes = '';
@Input() customStyles = '';
@Input() allowedBlocks = '';
@Input() showVideoThumbnail = false;
@Input() value: { [key: string]: string } | string = '';

editor = {
Expand Down Expand Up @@ -87,6 +89,7 @@ describe('DotBlockEditorSidebarComponent', () => {
let dotAlertConfirmService: DotAlertConfirmService;
let dotContentTypeService: DotContentTypeService;
let de: DebugElement;
let dotPropertiesService: DotPropertiesService;

beforeEach(async () => {
await TestBed.configureTestingModule({
Expand All @@ -102,6 +105,12 @@ describe('DotBlockEditorSidebarComponent', () => {
{ provide: CoreWebService, useClass: CoreWebServiceMock },
{ provide: DotMessageService, useValue: messageServiceMock },
{ provide: DotContentTypeService, useClass: MockDotContentTypeService },
{
provide: DotPropertiesService,
useValue: {
getKey: () => of('true')
}
},
DotWorkflowActionsFireService,
DotEventsService,
DotAlertConfirmService,
Expand All @@ -112,6 +121,7 @@ describe('DotBlockEditorSidebarComponent', () => {
dotWorkflowActionsFireService = TestBed.inject(DotWorkflowActionsFireService);
dotAlertConfirmService = TestBed.inject(DotAlertConfirmService);
dotContentTypeService = TestBed.inject(DotContentTypeService);
dotPropertiesService = TestBed.inject(DotPropertiesService);
});

beforeEach(() => {
Expand All @@ -133,6 +143,7 @@ describe('DotBlockEditorSidebarComponent', () => {

it('should set inputs to the block editor', async () => {
spyOn(dotContentTypeService, 'getContentType').and.callThrough();
spyOn(dotPropertiesService, 'getKey').and.returnValue(of('true'));
dotEventsService.notify('edit-block-editor', clickEvent);

await fixture.whenRenderingDone();
Expand All @@ -144,6 +155,7 @@ describe('DotBlockEditorSidebarComponent', () => {

expect(dotContentTypeService.getContentType).toHaveBeenCalledWith('Blog');
expect(blockEditor.lang).toEqual(clickEvent.dataset.language);
expect(blockEditor.showVideoThumbnail).toBeTruthy();
expect(blockEditor.allowedBlocks).toEqual('heading1');
expect(blockEditor.allowedContentTypes).toEqual('Activity');
expect(blockEditor.value).toEqual(JSON.parse(clickEvent.dataset.blockEditorContent));
Expand Down
@@ -1,4 +1,4 @@
import { Observable, of, Subject } from 'rxjs';
import { combineLatest, Observable, of, Subject } from 'rxjs';

import { HttpErrorResponse } from '@angular/common/http';
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
Expand All @@ -11,9 +11,14 @@ import {
DotContentTypeService,
DotEventsService,
DotMessageService,
DotPropertiesService,
DotWorkflowActionsFireService
} from '@dotcms/data-access';
import { DotCMSContentTypeField, DotCMSContentTypeFieldVariable } from '@dotcms/dotcms-models';
import {
DotCMSContentTypeField,
DotCMSContentTypeFieldVariable,
EDITOR_MARKETING_KEYS
} from '@dotcms/dotcms-models';

export interface BlockEditorInput {
content: { [key: string]: string };
Expand All @@ -36,6 +41,7 @@ export class DotBlockEditorSidebarComponent implements OnInit, OnDestroy {
@ViewChild('blockEditor') blockEditor: DotBlockEditorComponent;

blockEditorInput: BlockEditorInput;
showVideoThumbnail: boolean;
saving = false;
private destroy$: Subject<boolean> = new Subject<boolean>();

Expand All @@ -44,19 +50,24 @@ export class DotBlockEditorSidebarComponent implements OnInit, OnDestroy {
private dotEventsService: DotEventsService,
private dotMessageService: DotMessageService,
private dotAlertConfirmService: DotAlertConfirmService,
private dotContentTypeService: DotContentTypeService
private dotContentTypeService: DotContentTypeService,
private dotPropertiesService: DotPropertiesService
) {}

ngOnInit(): void {
this.dotEventsService
.listen<HTMLDivElement>('edit-block-editor')
.pipe(
takeUntil(this.destroy$),
switchMap((event) => this.extractBlockEditorData(event.data.dataset))
)
.subscribe((eventData: BlockEditorInput) => {
this.blockEditorInput = eventData;
});
const content$ = this.dotEventsService.listen<HTMLDivElement>('edit-block-editor').pipe(
takeUntil(this.destroy$),
switchMap((event) => this.extractBlockEditorData(event.data.dataset))
);

const propery$ = this.dotPropertiesService.getKey(
EDITOR_MARKETING_KEYS.SHOW_VIDEO_THUMBNAIL
);

combineLatest([content$, propery$]).subscribe(([eventData, property = 'true']) => {
this.blockEditorInput = eventData;
this.showVideoThumbnail = property === 'true' || property === 'NOT_FOUND';
});
}

/**
Expand Down
Expand Up @@ -24,7 +24,7 @@ import { TextAlign } from '@tiptap/extension-text-align';
import { Underline } from '@tiptap/extension-underline';
import StarterKit, { StarterKitOptions } from '@tiptap/starter-kit';

import { CustomBlock } from '@dotcms/dotcms-models';
import { CustomBlock, EDITOR_MARKETING_KEYS } from '@dotcms/dotcms-models';

import {
ActionsMenu,
Expand All @@ -42,7 +42,12 @@ import {
ImageUpload
} from '../../extensions';
import { ContentletBlock, ImageNode, VideoNode } from '../../nodes';
import { formatHTML, removeInvalidNodes, SetDocAttrStep } from '../../shared/utils';
import {
formatHTML,
removeInvalidNodes,
SetDocAttrStep,
DotMarketingConfigService
} from '../../shared';

function toTitleCase(str) {
return str.replace(/\p{L}+('\p{L}+)?/gu, function (txt) {
Expand All @@ -63,6 +68,12 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy {
@Input() charLimit: number;
@Input() customBlocks: string;
@Input() content: Content = '';
@Input() set showVideoThumbnail(value) {
this.dotMarketingConfigService.setProperty(
EDITOR_MARKETING_KEYS.SHOW_VIDEO_THUMBNAIL,
value
);
}

@Input() set allowedBlocks(blocks: string) {
const allowedBlocks = blocks ? blocks.replace(/ /g, '').split(',').filter(Boolean) : [];
Expand Down Expand Up @@ -112,7 +123,11 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy {
return Math.ceil(this.characterCount.words() / 265);
}

constructor(private injector: Injector, public viewContainerRef: ViewContainerRef) {}
constructor(
private injector: Injector,
public viewContainerRef: ViewContainerRef,
private dotMarketingConfigService: DotMarketingConfigService
) {}

async loadCustomBlocks(urls: string[]) {
return Promise.all(urls.map(async (url) => import(/* webpackIgnore: true */ url)));
Expand Down
Expand Up @@ -5,6 +5,7 @@
[contentlet]="contentlet"
[cover]="false"
[iconSize]="'72px'"
[showVideoThumbnail]="showVideoThumbnail"
></dot-contentlet-thumbnail>
</div>
</ng-template>
Expand Down
@@ -1,16 +1,28 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';

import { DotCMSContentlet } from '@dotcms/dotcms-models';
import { DotCMSContentlet, EDITOR_MARKETING_KEYS } from '@dotcms/dotcms-models';

import { DotMarketingConfigService } from '../../../../../../shared';

@Component({
selector: 'dot-asset-card',
templateUrl: './dot-asset-card.component.html',
styleUrls: ['./dot-asset-card.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DotAssetCardComponent {
export class DotAssetCardComponent implements OnInit {
showVideoThumbnail = true;

@Input() contentlet: DotCMSContentlet;

constructor(private dotMarketingConfigService: DotMarketingConfigService) {}

ngOnInit() {
this.showVideoThumbnail = this.dotMarketingConfigService.getProperty(
EDITOR_MARKETING_KEYS.SHOW_VIDEO_THUMBNAIL
);
}

/**
* Return the contentlet Thumbanil based in the inode
*
Expand Down
@@ -0,0 +1,36 @@
import { TestBed } from '@angular/core/testing';

import { DotMarketingConfigService, INITIAL_STATE } from './dot-marketing-config.service';

describe('DotEditorMarketingService', () => {
let service: DotMarketingConfigService;

beforeEach(() => {
TestBed.configureTestingModule({ teardown: { destroyAfterEach: false } });
service = TestBed.inject(DotMarketingConfigService);
});

test('should be created', () => {
expect(service).toBeTruthy();
});

test('should has initial state', () => {
expect(service.configObject).toEqual(INITIAL_STATE);
});

test('should set a config property', () => {
const key = 'SHOW_VIDEO_THUMBNAIL';
const value = false;

jest.spyOn(service, 'setProperty');

service.setProperty(key, value);

expect(service.setProperty).toHaveBeenCalledWith(key, value);
expect(service.getProperty(key)).toBe(value);
});

test('should return the config object', () => {
expect(service.configObject).toBeDefined();
});
});
@@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';

import { EDITOR_DOTMARKETING_CONFIG } from '@dotcms/dotcms-models';

export const INITIAL_STATE = {
SHOW_VIDEO_THUMBNAIL: true
};

@Injectable({
providedIn: 'root'
})
export class DotMarketingConfigService {
private config: EDITOR_DOTMARKETING_CONFIG = INITIAL_STATE;

/**
* Get the config object
*
* @return {*} {EDITOR_DOTMARKETING_CONFIG}
* @memberof DotMarketingConfigService
*/
get configObject(): EDITOR_DOTMARKETING_CONFIG {
return this.config;
}

/**
* Set a property in the config object
*
* @param {keyof EDITOR_DOTMARKETING_CONFIG} key
* @param {boolean} value
* @memberof DotMarketingConfigService
*/
setProperty(key: keyof EDITOR_DOTMARKETING_CONFIG, value: boolean): void {
this.config = {
...this.config,
[key]: value
};
}

/**
* Get a property from the config object base on a key
*
* @param {keyof EDITOR_DOTMARKETING_CONFIG} key
* @return {*} {boolean}
* @memberof DotMarketingConfigService
*/
getProperty(key: keyof EDITOR_DOTMARKETING_CONFIG): boolean {
return this.config[key] || false;
}
}
@@ -1,3 +1,4 @@
export * from './dot-language/dot-language.service';
export * from './search/search.service';
export * from './suggestions/suggestions.service';
export * from './dot-marketing-config/dot-marketing-config.service';
8 changes: 8 additions & 0 deletions core-web/libs/dotcms-models/src/lib/dot-block-editor.model.ts
Expand Up @@ -15,3 +15,11 @@ export type Block = {
export type CustomBlock = {
extensions: Block[];
};

export interface EDITOR_DOTMARKETING_CONFIG {
SHOW_VIDEO_THUMBNAIL: boolean;
}

export enum EDITOR_MARKETING_KEYS {
SHOW_VIDEO_THUMBNAIL = 'SHOW_VIDEO_THUMBNAIL'
}
Expand Up @@ -46,6 +46,8 @@ export class DotCardView {
})
value: string;

@Prop() showVideoThumbnail = true;

@Event() selected: EventEmitter;
@Event() cardClick: EventEmitter;

Expand Down Expand Up @@ -135,6 +137,7 @@ export class DotCardView {
this.setValue(originalTarget, item.data);
}}
item={item}
showVideoThumbnail={this.showVideoThumbnail}
/>
))}
</Host>
Expand Down
Expand Up @@ -29,6 +29,8 @@ export class DotCardContentlet {
})
checked: boolean;

@Prop() showVideoThumbnail = false;

@Event() checkboxChange: EventEmitter<DotCardContentletEvent>;
@Event() contextMenuClick: EventEmitter<MouseEvent>;

Expand Down Expand Up @@ -57,6 +59,7 @@ export class DotCardContentlet {
return (
<dot-card>
<dot-contentlet-thumbnail
showVideoThumbnail={this.showVideoThumbnail}
contentlet={contentlet}
width={this.thumbnailSize}
height={this.thumbnailSize}
Expand Down

0 comments on commit e4a3d0e

Please sign in to comment.