diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index f140135a8..4506527ac 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -102,6 +102,7 @@ "assets.tabHistory": "History", "assets.tabImage": "Image", "assets.tabMetadata": "Metadata", + "assets.tabPreview": "Preview", "assets.updated": "Asset has been updated.", "assets.updateFailed": "Failed to update asset. Please reload.", "assets.updateFolderFailed": "Failed to update asset folder. Please reload.", diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json index 9b4eb8fbf..20dff0ee9 100644 --- a/backend/i18n/frontend_it.json +++ b/backend/i18n/frontend_it.json @@ -102,6 +102,7 @@ "assets.tabHistory": "Cronologia", "assets.tabImage": "Immagine", "assets.tabMetadata": "Metadati", + "assets.tabPreview": "Preview", "assets.updated": "La risorsa è stata aggiornata.", "assets.updateFailed": "Non è stato possibile aggiornare la risorsa. Per favore ricarica.", "assets.updateFolderFailed": "Non è stato possibile aggiornare la cartella delle risorse. Per favore ricarica.", diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json index ec273f709..99ba50991 100644 --- a/backend/i18n/frontend_nl.json +++ b/backend/i18n/frontend_nl.json @@ -102,6 +102,7 @@ "assets.tabHistory": "Geschiedenis", "assets.tabImage": "Afbeelding", "assets.tabMetadata": "Metadata", + "assets.tabPreview": "Preview", "assets.updated": "Asset is bijgewerkt.", "assets.updateFailed": "Bijwerken van item is mislukt. Laad opnieuw.", "assets.updateFolderFailed": "Bijwerken van de map is mislukt. Laad opnieuw.", diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index f140135a8..4506527ac 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -102,6 +102,7 @@ "assets.tabHistory": "History", "assets.tabImage": "Image", "assets.tabMetadata": "Metadata", + "assets.tabPreview": "Preview", "assets.updated": "Asset has been updated.", "assets.updateFailed": "Failed to update asset. Please reload.", "assets.updateFolderFailed": "Failed to update asset folder. Please reload.", diff --git a/frontend/app/framework/angular/video-player.component.html b/frontend/app/framework/angular/video-player.component.html new file mode 100644 index 000000000..50e4739fb --- /dev/null +++ b/frontend/app/framework/angular/video-player.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/app/framework/angular/video-player.component.scss b/frontend/app/framework/angular/video-player.component.scss new file mode 100644 index 000000000..8f1666fa2 --- /dev/null +++ b/frontend/app/framework/angular/video-player.component.scss @@ -0,0 +1,13 @@ +:host ::ng-deep { + .video-js { + &.vjs-fluid { + height: 100%; + } + } +} + +$color-video: #000; + +:host { + background: $color-video; +} \ No newline at end of file diff --git a/frontend/app/framework/angular/video-player.component.ts b/frontend/app/framework/angular/video-player.component.ts new file mode 100644 index 000000000..c2824bc37 --- /dev/null +++ b/frontend/app/framework/angular/video-player.component.ts @@ -0,0 +1,65 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnDestroy, Renderer2, ViewChild } from '@angular/core'; +import { ResourceLoaderService } from '@app/framework/internal'; + +declare var videojs: any; + +@Component({ + selector: 'sqx-video-player', + styleUrls: ['./video-player.component.scss'], + templateUrl: './video-player.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class VideoPlayerComponent implements AfterViewInit, OnDestroy, OnChanges { + private player: any; + + @Input() + public source: string; + + @Input() + public mimeType: string; + + @ViewChild('video', { static: false }) + public video: ElementRef; + + constructor( + private readonly resourceLoader: ResourceLoaderService, + private readonly renderer: Renderer2 + ) { + } + + public ngOnDestroy() { + this.player?.dispose(); + } + + public ngOnChanges() { + if (this.player) { + if (this.source) { + this.player.src({ type: this.mimeType, src: this.source }); + } else { + this.player.src(); + } + } + } + + public ngAfterViewInit(): void { + Promise.all([ + this.resourceLoader.loadScript('https://vjs.zencdn.net/7.10.2/video.min.js'), + this.resourceLoader.loadStyle('https://vjs.zencdn.net/7.10.2/video-js.css') + ]).then(() => { + this.player = videojs(this.video.nativeElement, { + fluid: true + }); + + this.renderer.removeClass(this.video.nativeElement, 'hidden'); + + this.ngOnChanges(); + }); + } +} \ No newline at end of file diff --git a/frontend/app/framework/declarations.ts b/frontend/app/framework/declarations.ts index 16121a039..a8a2b7921 100644 --- a/frontend/app/framework/declarations.ts +++ b/frontend/app/framework/declarations.ts @@ -76,5 +76,6 @@ export * from './angular/sync-width.directive'; export * from './angular/tab-router-link.directive'; export * from './angular/template-wrapper.directive'; export * from './angular/title.component'; +export * from './angular/video-player.component'; export * from './internal'; export * from './state'; \ No newline at end of file diff --git a/frontend/app/framework/module.ts b/frontend/app/framework/module.ts index 2c93a12a1..95d89a9c8 100644 --- a/frontend/app/framework/module.ts +++ b/frontend/app/framework/module.ts @@ -12,7 +12,7 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { ModuleWithProviders, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ColorPickerModule } from 'ngx-color-picker'; -import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterceptor, CanDeactivateGuard, CheckboxGroupComponent, ClipboardService, CodeComponent, CodeEditorComponent, ColorPickerComponent, ConfirmClickDirective, ControlErrorsComponent, CopyDirective, DarkenPipe, DatePipe, DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DialogRendererComponent, DialogService, DisplayNamePipe, DropdownComponent, DurationPipe, EditableTitleComponent, ExternalLinkDirective, FileDropDirective, FileSizePipe, FocusOnInitDirective, FormAlertComponent, FormErrorComponent, FormHintComponent, FromNowPipe, FullDateTimePipe, HighlightPipe, HoverBackgroundDirective, IFrameEditorComponent, ImageSourceDirective, IndeterminateValueDirective, ISODatePipe, JsonEditorComponent, KeysPipe, KNumberPipe, LanguageSelectorComponent, LightenPipe, ListViewComponent, LoadingInterceptor, LoadingService, LocalizedInputComponent, LocalStoreService, MarkdownInlinePipe, MarkdownPipe, MessageBus, ModalDialogComponent, ModalDirective, ModalPlacementDirective, MoneyPipe, MonthPipe, OnboardingService, OnboardingTooltipComponent, PagerComponent, PanelComponent, PanelContainerDirective, ParentLinkDirective, PopupLinkDirective, ProgressBarComponent, ResizedDirective, ResizeService, ResourceLoaderService, RootViewComponent, SafeHtmlPipe, SafeUrlPipe, ScrollActiveDirective, ShortcutComponent, ShortcutService, ShortDatePipe, ShortTimePipe, StarsComponent, StatusIconComponent, StopClickDirective, SyncScollingDirective, SyncWidthDirective, TabRouterlinkDirective, TagEditorComponent, TemplateWrapperDirective, TempService, TitleComponent, TitleService, ToggleComponent, TooltipDirective, TransformInputDirective, TranslatePipe } from './declarations'; +import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterceptor, CanDeactivateGuard, CheckboxGroupComponent, ClipboardService, CodeComponent, CodeEditorComponent, ColorPickerComponent, ConfirmClickDirective, ControlErrorsComponent, CopyDirective, DarkenPipe, DatePipe, DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DialogRendererComponent, DialogService, DisplayNamePipe, DropdownComponent, DurationPipe, EditableTitleComponent, ExternalLinkDirective, FileDropDirective, FileSizePipe, FocusOnInitDirective, FormAlertComponent, FormErrorComponent, FormHintComponent, FromNowPipe, FullDateTimePipe, HighlightPipe, HoverBackgroundDirective, IFrameEditorComponent, ImageSourceDirective, IndeterminateValueDirective, ISODatePipe, JsonEditorComponent, KeysPipe, KNumberPipe, LanguageSelectorComponent, LightenPipe, ListViewComponent, LoadingInterceptor, LoadingService, LocalizedInputComponent, LocalStoreService, MarkdownInlinePipe, MarkdownPipe, MessageBus, ModalDialogComponent, ModalDirective, ModalPlacementDirective, MoneyPipe, MonthPipe, OnboardingService, OnboardingTooltipComponent, PagerComponent, PanelComponent, PanelContainerDirective, ParentLinkDirective, PopupLinkDirective, ProgressBarComponent, ResizedDirective, ResizeService, ResourceLoaderService, RootViewComponent, SafeHtmlPipe, SafeUrlPipe, ScrollActiveDirective, ShortcutComponent, ShortcutService, ShortDatePipe, ShortTimePipe, StarsComponent, StatusIconComponent, StopClickDirective, SyncScollingDirective, SyncWidthDirective, TabRouterlinkDirective, TagEditorComponent, TemplateWrapperDirective, TempService, TitleComponent, TitleService, ToggleComponent, TooltipDirective, TransformInputDirective, TranslatePipe, VideoPlayerComponent } from './declarations'; @NgModule({ imports: [ @@ -97,7 +97,8 @@ import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterc ToggleComponent, TooltipDirective, TransformInputDirective, - TranslatePipe + TranslatePipe, + VideoPlayerComponent ], exports: [ AutocompleteComponent, @@ -178,7 +179,8 @@ import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterc ToggleComponent, TooltipDirective, TransformInputDirective, - TranslatePipe + TranslatePipe, + VideoPlayerComponent ] }) export class SqxFrameworkModule { diff --git a/frontend/app/shared/components/assets/asset-dialog.component.html b/frontend/app/shared/components/assets/asset-dialog.component.html index 0d3f84e84..5a6c14fb3 100644 --- a/frontend/app/shared/components/assets/asset-dialog.component.html +++ b/frontend/app/shared/components/assets/asset-dialog.component.html @@ -18,7 +18,12 @@ + @@ -185,6 +190,15 @@ + + + + + + + + + diff --git a/frontend/app/shared/components/assets/asset-dialog.component.scss b/frontend/app/shared/components/assets/asset-dialog.component.scss index 520f2044b..e41317988 100644 --- a/frontend/app/shared/components/assets/asset-dialog.component.scss +++ b/frontend/app/shared/components/assets/asset-dialog.component.scss @@ -48,4 +48,13 @@ &:focus { box-shadow: none; } +} + +sqx-video-player { + @include absolute(0, 0, 0, 0); + height: auto !important; +} + +ngx-doc-viewer { + @include absolute(0, 0, 0, 0); } \ No newline at end of file diff --git a/frontend/app/shared/components/assets/asset-dialog.component.ts b/frontend/app/shared/components/assets/asset-dialog.component.ts index 903331cc4..8a6bcb004 100644 --- a/frontend/app/shared/components/assets/asset-dialog.component.ts +++ b/frontend/app/shared/components/assets/asset-dialog.component.ts @@ -55,6 +55,10 @@ export class AssetDialogComponent implements OnChanges { return this.asset.type === 'Image'; } + public get isVideo() { + return this.asset.type === 'Video'; + } + constructor( private readonly appsState: AppsState, private readonly assetsState: AssetsState, diff --git a/frontend/app/shared/components/assets/image-cropper.component.ts b/frontend/app/shared/components/assets/image-cropper.component.ts index 3d13f3201..83120036b 100644 --- a/frontend/app/shared/components/assets/image-cropper.component.ts +++ b/frontend/app/shared/components/assets/image-cropper.component.ts @@ -59,7 +59,7 @@ export class ImageCropperComponent implements AfterViewInit, OnDestroy, OnChange public rotate(value: number) { if (this.cropper) { - this.cropper.rotate(-90); + this.cropper.rotate(value); const canvasData = this.cropper.getCanvasData(); const containerData = this.cropper.getContainerData(); diff --git a/frontend/app/shared/components/assets/pipes.ts b/frontend/app/shared/components/assets/pipes.ts index e55fddc15..cc7090617 100644 --- a/frontend/app/shared/components/assets/pipes.ts +++ b/frontend/app/shared/components/assets/pipes.ts @@ -59,7 +59,6 @@ export class AssetPreviewUrlPipe implements PipeTransform { }) export class FileIconPipe implements PipeTransform { public transform(asset: { mimeType: string, fileType: string }): string { - let mimeIcon: string; const mimeParts = asset.mimeType.split('/'); @@ -74,6 +73,16 @@ export class FileIconPipe implements PipeTransform { } } +@Pipe({ + name: 'sqxPreviewable', + pure: true +}) +export class PreviewableType implements PipeTransform { + public transform(asset: { fileSize: number, fileType: string }): boolean { + return PREVIEW_TYPES.indexOf(asset.fileType) >= 0 && asset.fileSize < 25_000_000; + } +} + const KNOWN_TYPES: ReadonlyArray = [ 'doc', 'docx', @@ -83,4 +92,26 @@ const KNOWN_TYPES: ReadonlyArray = [ 'video', 'xls', 'xlsx' +]; + +const PREVIEW_TYPES: ReadonlyArray = [ + 'ai', + 'doc', + 'docx', + 'dxf', + 'eps', + 'pages', + 'pdf', + 'ppt', + 'pptx', + 'ps', + 'psd', + 'rar', + 'svg', + 'tiff', + 'ttf', + 'xls', + 'xlsx', + 'xps', + 'zip' ]; \ No newline at end of file diff --git a/frontend/app/shared/module.ts b/frontend/app/shared/module.ts index ae52d4269..a1ba61c34 100644 --- a/frontend/app/shared/module.ts +++ b/frontend/app/shared/module.ts @@ -13,6 +13,8 @@ import { ModuleWithProviders, NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { SqxFrameworkModule } from '@app/framework'; import { MentionModule } from 'angular-mentions'; +import { NgxDocViewerModule } from 'ngx-doc-viewer'; +import { PreviewableType } from './components/assets/pipes'; import { AppFormComponent, AppLanguagesService, AppMustExistGuard, AppsService, AppsState, AssetComponent, AssetDialogComponent, AssetFolderComponent, AssetFolderDialogComponent, AssetHistoryComponent, AssetPathComponent, AssetPreviewUrlPipe, AssetsDialogState, AssetsListComponent, AssetsSelectorComponent, AssetsService, AssetsState, AssetUploaderComponent, AssetUploaderState, AssetUrlPipe, AuthInterceptor, AuthService, AutoSaveService, BackupsService, BackupsState, ClientsService, ClientsState, CommentComponent, CommentsComponent, CommentsService, ContentMustExistGuard, ContentsService, ContentsState, ContributorsService, ContributorsState, FileIconPipe, FilterComparisonComponent, FilterLogicalComponent, FilterNodeComponent, GeolocationEditorComponent, GraphQlService, HelpComponent, HelpMarkdownPipe, HelpService, HistoryComponent, HistoryListComponent, HistoryMessagePipe, HistoryService, ImageCropperComponent, ImageFocusPointComponent, LanguagesService, LanguagesState, LoadAppsGuard, LoadLanguagesGuard, MarkdownEditorComponent, MustBeAuthenticatedGuard, MustBeNotAuthenticatedGuard, NewsService, NotifoComponent, PatternsService, PatternsState, PlansService, PlansState, QueryComponent, QueryListComponent, QueryPathComponent, ReferencesCheckboxesComponent, ReferencesDropdownComponent, ReferencesTagsComponent, RichEditorComponent, RolesService, RolesState, RuleEventsState, RulesService, RulesState, SavedQueriesComponent, SchemaCategoryComponent, SchemaMustExistGuard, SchemaMustExistPublishedGuard, SchemaMustNotBeSingletonGuard, SchemasService, SchemasState, SchemaTagSource, SearchFormComponent, SortingComponent, StockPhotoService, TableHeaderComponent, TranslationsService, UIService, UIState, UnsetAppGuard, UnsetContentGuard, UsagesService, UserDtoPicture, UserIdPicturePipe, UserNamePipe, UserNameRefPipe, UserPicturePipe, UserPictureRefPipe, UsersProviderService, UsersService, WorkflowsService, WorkflowsState } from './declarations'; import { SearchService } from './services/search.service'; @@ -20,6 +22,7 @@ import { SearchService } from './services/search.service'; imports: [ DragDropModule, MentionModule, + NgxDocViewerModule, RouterModule, SqxFrameworkModule ], @@ -52,6 +55,7 @@ import { SearchService } from './services/search.service'; ImageFocusPointComponent, MarkdownEditorComponent, NotifoComponent, + PreviewableType, QueryComponent, QueryListComponent, QueryPathComponent, @@ -95,6 +99,7 @@ import { SearchService } from './services/search.service'; HistoryMessagePipe, MarkdownEditorComponent, NotifoComponent, + PreviewableType, QueryListComponent, ReferencesCheckboxesComponent, ReferencesDropdownComponent, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2434d186f..04d07c070 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8071,6 +8071,14 @@ "tslib": "^2.0.0" } }, + "ngx-doc-viewer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ngx-doc-viewer/-/ngx-doc-viewer-1.4.0.tgz", + "integrity": "sha512-L3YSAgdNAbDPAVaNV0WQ2XFfFCAp6HoSA+Pl2YAXHWu9FSdNFS1U//pzXLokxopln6DPK8r2Lp43R/0J4QCbfA==", + "requires": { + "tslib": "^2.0.0" + } + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index 5e8227bee..98fb3b694 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -47,6 +47,7 @@ "mersenne-twister": "1.1.0", "mousetrap": "1.6.5", "ngx-color-picker": "10.1.0", + "ngx-doc-viewer": "^1.4.0", "oidc-client": "1.10.1", "pikaday": "1.8.2", "progressbar.js": "1.1.0",