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 @@
-
+
+ {{ 'assets.tabPreview' | sqxTranslate }}
+
+
+
+
{{ 'assets.tabHistory' | sqxTranslate }}
@@ -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",