Browse Source

Asset preview.

pull/614/head
Sebastian 5 years ago
parent
commit
70fc489c40
  1. 1
      backend/i18n/frontend_en.json
  2. 1
      backend/i18n/frontend_it.json
  3. 1
      backend/i18n/frontend_nl.json
  4. 1
      backend/i18n/source/frontend_en.json
  5. 1
      frontend/app/framework/angular/video-player.component.html
  6. 13
      frontend/app/framework/angular/video-player.component.scss
  7. 65
      frontend/app/framework/angular/video-player.component.ts
  8. 1
      frontend/app/framework/declarations.ts
  9. 8
      frontend/app/framework/module.ts
  10. 16
      frontend/app/shared/components/assets/asset-dialog.component.html
  11. 9
      frontend/app/shared/components/assets/asset-dialog.component.scss
  12. 4
      frontend/app/shared/components/assets/asset-dialog.component.ts
  13. 2
      frontend/app/shared/components/assets/image-cropper.component.ts
  14. 33
      frontend/app/shared/components/assets/pipes.ts
  15. 5
      frontend/app/shared/module.ts
  16. 8
      frontend/package-lock.json
  17. 1
      frontend/package.json

1
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.",

1
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.",

1
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.",

1
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.",

1
frontend/app/framework/angular/video-player.component.html

@ -0,0 +1 @@
<video class="video-js vjs-big-play-centered hidden" controls muted playsinline preload="none" #video></video>

13
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;
}

65
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<HTMLVideoElement>;
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();
});
}
}

1
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';

8
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 {

16
frontend/app/shared/components/assets/asset-dialog.component.html

@ -18,7 +18,12 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link" (click)="selectTab(3)" [class.active]="selectedTab === 3">
<a class="nav-link" (click)="selectTab(3)" [class.active]="selectedTab === 3" *ngIf="isVideo || (asset | sqxPreviewable)">
{{ 'assets.tabPreview' | sqxTranslate }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" (click)="selectTab(4)" [class.active]="selectedTab === 4">
{{ 'assets.tabHistory' | sqxTranslate }}
</a>
</li>
@ -185,6 +190,15 @@
</div>
</ng-container>
<ng-container *ngSwitchCase="3">
<ng-container *ngIf="asset | sqxPreviewable; else video">
<ngx-doc-viewer [url]="asset | sqxAssetPreviewUrl" [style]="{}" viewer="google" style="width:100%;height:50vh;"></ngx-doc-viewer>
</ng-container>
<ng-template #video>
<sqx-video-player [source]="asset | sqxAssetPreviewUrl" [mimeType]="asset.mimeType"></sqx-video-player>
</ng-template>
</ng-container>
<ng-container *ngSwitchCase="4">
<sqx-asset-history [asset]="asset"></sqx-asset-history>
</ng-container>
</ng-container>

9
frontend/app/shared/components/assets/asset-dialog.component.scss

@ -49,3 +49,12 @@
box-shadow: none;
}
}
sqx-video-player {
@include absolute(0, 0, 0, 0);
height: auto !important;
}
ngx-doc-viewer {
@include absolute(0, 0, 0, 0);
}

4
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,

2
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();

33
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<string> = [
'doc',
'docx',
@ -84,3 +93,25 @@ const KNOWN_TYPES: ReadonlyArray<string> = [
'xls',
'xlsx'
];
const PREVIEW_TYPES: ReadonlyArray<string> = [
'ai',
'doc',
'docx',
'dxf',
'eps',
'pages',
'pdf',
'ppt',
'pptx',
'ps',
'psd',
'rar',
'svg',
'tiff',
'ttf',
'xls',
'xlsx',
'xps',
'zip'
];

5
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,

8
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",

1
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",

Loading…
Cancel
Save