Skip to content

Commit

Permalink
feat(module:upload): support with non-image format file preview (#2709)
Browse files Browse the repository at this point in the history
* feat(module:upload): Compatible with non-image format file preview

- support download html attribute via linkProps

* chore: support hide preview button when is non-image url
  • Loading branch information
cipchk authored and vthinkxie committed Jan 17, 2019
1 parent 48d5333 commit 4c41715
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 84 deletions.
25 changes: 15 additions & 10 deletions components/upload/demo/avatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Observable, Observer } from 'rxjs';
[nzBeforeUpload]="beforeUpload"
(nzChange)="handleChange($event)">
<ng-container *ngIf="!avatarUrl">
<i nz-icon type="plus"></i>
<i nz-icon [type]="loading ? 'loading' : 'plus'"></i>
<div class="ant-upload-text">Upload</div>
</ng-container>
<img *ngIf="avatarUrl" [src]="avatarUrl" class="avatar">
Expand Down Expand Up @@ -90,16 +90,21 @@ export class NzDemoUploadAvatarComponent {
}

handleChange(info: { file: UploadFile }): void {
if (info.file.status === 'uploading') {
this.loading = true;
return;
}
if (info.file.status === 'done') {
// Get this url from response in real world.
this.getBase64(info.file.originFileObj, (img: string) => {
switch (info.file.status) {
case 'uploading':
this.loading = true;
break;
case 'done':
// Get this url from response in real world.
this.getBase64(info.file.originFileObj, (img: string) => {
this.loading = false;
this.avatarUrl = img;
});
break;
case 'error':
this.msg.error('Network error');
this.loading = false;
this.avatarUrl = img;
});
break;
}
}
}
3 changes: 2 additions & 1 deletion components/upload/demo/manually.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class NzDemoUploadManuallyComponent {
constructor(private http: HttpClient, private msg: NzMessageService) {}

beforeUpload = (file: UploadFile): boolean => {
this.fileList.push(file);
this.fileList = this.fileList.concat(file);
return false;
}

Expand All @@ -46,6 +46,7 @@ export class NzDemoUploadManuallyComponent {
.subscribe(
(event: {}) => {
this.uploading = false;
this.fileList = [];
this.msg.success('upload successfully.');
},
err => {
Expand Down
6 changes: 6 additions & 0 deletions components/upload/demo/picture-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { NzMessageService, UploadFile } from 'ng-zorro-antd';
nzListType="picture-card"
[(nzFileList)]="fileList"
[nzShowButton]="fileList.length < 3"
[nzShowUploadList]="showUploadList"
[nzPreview]="handlePreview">
<i nz-icon type="plus"></i>
<div class="ant-upload-text">Upload</div>
Expand All @@ -35,6 +36,11 @@ import { NzMessageService, UploadFile } from 'ng-zorro-antd';
]
})
export class NzDemoUploadPictureCardComponent {
showUploadList = {
showPreviewIcon: true,
showRemoveIcon : true,
hidePreviewIconInNonImage: true
};
fileList = [
{
uid: -1,
Expand Down
3 changes: 2 additions & 1 deletion components/upload/doc/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ When uploading state change, it returns:
uid: 'uid', // unique identifier
name: 'xx.png' // file name
status: 'done', // options:uploading, done, error, removed
response: '{"status": "success"}' // response from server
response: '{"status": "success"}', // response from server
linkProps: '{"download": "image"}', // additional html props of file link
}
```

Expand Down
3 changes: 2 additions & 1 deletion components/upload/doc/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ title: Upload
uid: 'uid', // 文件唯一标识
name: 'xx.png' // 文件名
status: 'done', // 状态有:uploading done error removed
response: '{"status": "success"}' // 服务端响应内容
response: '{"status": "success"}', // 服务端响应内容
linkProps: '{"download": "image"}', // 下载链接额外的 HTML 属性
}
```

Expand Down
3 changes: 2 additions & 1 deletion components/upload/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface UploadFile {
thumbUrl?: string;
response?: any;
error?: any;
linkProps?: any;
linkProps?: { download: string };
type: string;

[ key: string ]: any;
Expand All @@ -42,6 +42,7 @@ export interface UploadChangeParam {
export interface ShowUploadListInterface {
showRemoveIcon?: boolean;
showPreviewIcon?: boolean;
hidePreviewIconInNonImage?: boolean;
}

export interface ZipButtonOptions {
Expand Down
56 changes: 27 additions & 29 deletions components/upload/nz-upload-list.component.html
Original file line number Diff line number Diff line change
@@ -1,54 +1,52 @@
<div *ngFor="let file of items" class="ant-upload-list-item ant-upload-list-item-{{file.status}}" @itemState>
<ng-template #icon>
<ng-container *ngIf="listType === 'picture' || listType === 'picture-card'; else defIcon">
<ng-container *ngIf="file.status === 'uploading' || (!file.thumbUrl && !file.url); else thumbIcon">
<div *ngIf="listType === 'picture-card'" class="ant-upload-list-item-uploading-text">{{ locale.uploading }}</div>
<i *ngIf="listType !== 'picture-card'" class="anticon anticon-picture ant-upload-list-item-thumbnail"></i>
</ng-container>
<ng-container *ngIf="showPic; else noPicTpl">
<div *ngIf="listType === 'picture-card' && file.status === 'uploading'; else thumbUrlCheck" class="ant-upload-list-item-uploading-text">{{ locale.uploading }}</div>
</ng-container>
<ng-template #defIcon>
<i nz-icon [type]="file.status === 'uploading' ? 'loading' : 'paper-clip'"></i>
<ng-template #thumbUrlCheck>
<i *ngIf="!file.thumbUrl && !file.url; else thumbTpl"
class="ant-upload-list-item-thumbnail" nz-icon type="picture" theme="twotone"></i>
</ng-template>
<ng-template #thumbIcon>
<ng-template #thumbTpl>
<a class="ant-upload-list-item-thumbnail" target="_blank" rel="noopener noreferrer"
[href]="file.thumbUrl || file.url"
(click)="handlePreview(file, $event)">
<img [src]="file.thumbUrl || file.url" [attr.alt]="file.name" />
<img *ngIf="isImageUrl(file); else noThumbTpl" [src]="file.thumbUrl || file.url" [attr.alt]="file.name" />
</a>
</ng-template>
<ng-template #noThumbTpl><i class="ant-upload-list-item-icon" nz-icon type="file" theme="twotone"></i></ng-template>
<ng-template #noPicTpl><i nz-icon [type]="file.status === 'uploading' ? 'loading' : 'paper-clip'"></i></ng-template>
</ng-template>
<ng-template #preview>
<ng-container *ngIf="file.url; else prevText">
<a [href]="file.thumbUrl || file.url" target="_blank" rel="noopener noreferrer"
<a [href]="file.thumbUrl || file.url" target="_blank" rel="noopener noreferrer" [attr.download]="file.linkProps && file.linkProps.download"
(click)="handlePreview(file, $event)" class="ant-upload-list-item-name" title="{{ file.name }}">{{ file.name }}</a>
</ng-container>
<ng-template #prevText>
<span (click)="handlePreview(file, $event)" class="ant-upload-list-item-name" title="{{ file.name }}">{{ file.name }}</span>
</ng-template>
</ng-template>
<div class="ant-upload-list-item-info">
<nz-tooltip *ngIf="file.status === 'error'" [nzTitle]="file.message">
<span nz-tooltip>
<ng-template [ngTemplateOutlet]="icon"></ng-template>
<ng-template [ngTemplateOutlet]="preview"></ng-template>
</span>
</nz-tooltip>
<span *ngIf="file.status === 'error'" nz-tooltip [nzTitle]="file.message">
<ng-template [ngTemplateOutlet]="icon"></ng-template>
<ng-template [ngTemplateOutlet]="preview"></ng-template>
</span>
<span *ngIf="file.status !== 'error'">
<ng-template [ngTemplateOutlet]="icon"></ng-template>
<ng-template [ngTemplateOutlet]="preview"></ng-template>
</span>
<ng-template [ngTemplateOutlet]="icon"></ng-template>
<ng-template [ngTemplateOutlet]="preview"></ng-template>
</span>
</div>
<ng-container *ngIf="listType === 'picture-card' && file.status !== 'uploading'; else close">
<span class="ant-upload-list-item-actions">
<a *ngIf="icons.showPreviewIcon" [href]="file.thumbUrl || file.url"
target="_blank" rel="noopener noreferrer"
title="{{ locale.previewFile }}"
[ngStyle]="!(file.url || file.thumbUrl) && {'opacity': .5, 'pointer-events': 'none'}"
(click)="handlePreview(file, $event)">
<i nz-icon type="eye-o"></i>
</a>
<i *ngIf="icons.showRemoveIcon" (click)="handleRemove(file, $event)" class="anticon anticon-delete" title="{{ locale.removeFile }}"></i>
</span>
<span class="ant-upload-list-item-actions">
<a *ngIf="showPreview(file)" [href]="file.thumbUrl || file.url"
target="_blank" rel="noopener noreferrer"
title="{{ locale.previewFile }}"
[ngStyle]="!(file.url || file.thumbUrl) && {'opacity': .5, 'pointer-events': 'none'}"
(click)="handlePreview(file, $event)">
<i nz-icon type="eye-o"></i>
</a>
<i *ngIf="icons.showRemoveIcon" (click)="handleRemove(file, $event)" class="anticon anticon-delete" title="{{ locale.removeFile }}"></i>
</span>
</ng-container>
<ng-template #close>
<i *ngIf="icons.showRemoveIcon" (click)="handleRemove(file, $event)" nz-icon type="close" title="{{ locale.removeFile }}"></i>
Expand Down
99 changes: 95 additions & 4 deletions components/upload/nz-upload-list.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { animate, style, transition, trigger } from '@angular/animations';
import { Component, ElementRef, Input, OnChanges, ViewEncapsulation } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnChanges, ViewEncapsulation } from '@angular/core';

import { NzUpdateHostClassService } from '../core/services/update-host-class.service';

Expand All @@ -21,15 +21,32 @@ import { ShowUploadListInterface, UploadFile, UploadListType } from './interface
])
],
preserveWhitespaces: false,
encapsulation : ViewEncapsulation.None
encapsulation : ViewEncapsulation.None,
changeDetection : ChangeDetectionStrategy.OnPush
})
export class NzUploadListComponent implements OnChanges {
private imageTypes = ['image', 'webp', 'png', 'svg', 'gif', 'jpg', 'jpeg', 'bmp'];
private _items: UploadFile[];

get showPic(): boolean {
return this.listType === 'picture' || this.listType === 'picture-card';
}

// #region fields

// tslint:disable-next-line:no-any
@Input() locale: any = {};
@Input() listType: UploadListType;
@Input() items: UploadFile[];
@Input()
set items(list: UploadFile[]) {
list.forEach(file => {
file.linkProps = typeof file.linkProps === 'string' ? JSON.parse(file.linkProps) : file.linkProps;
});
this._items = list;
}
get items(): UploadFile[] {
return this._items;
}
@Input() icons: ShowUploadListInterface;
@Input() onPreview: (file: UploadFile) => void;
@Input() onRemove: (file: UploadFile) => void;
Expand All @@ -52,6 +69,75 @@ export class NzUploadListComponent implements OnChanges {

// #region render

private extname(url: string): string {
const temp = url.split('/');
const filename = temp[temp.length - 1];
const filenameWithoutSuffix = filename.split(/#|\?/)[0];
return (/\.[^./\\]*$/.exec(filenameWithoutSuffix) || [''])[0];
}

isImageUrl(file: UploadFile): boolean {
if (~this.imageTypes.indexOf(file.type)) {
return true;
}
const url: string = (file.thumbUrl || file.url || '') as string;
if (!url) {
return false;
}
const extension = this.extname(url);
if (/^data:image\//.test(url) || /(webp|svg|png|gif|jpg|jpeg|bmp)$/i.test(extension)) {
return true;
} else if (/^data:/.test(url)) {
// other file types of base64
return false;
} else if (extension) {
// other file types which have extension
return false;
}
return true;
}

private previewFile(file: File | Blob, callback: (dataUrl: string) => void): void {
if (file.type && this.imageTypes.indexOf(file.type) === -1) {
callback('');
}
const reader = new FileReader();
// https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
reader.onloadend = () => callback(reader.result as string);
reader.readAsDataURL(file);
}

private genThumb(): void {
// tslint:disable-next-line:no-any
const win = window as any;
if (
!this.showPic ||
typeof document === 'undefined' ||
typeof win === 'undefined' ||
!win.FileReader ||
!win.File
) {
return ;
}
this.items
.filter(file => file.originFileObj instanceof File && file.thumbUrl === undefined)
.forEach(file => {
file.thumbUrl = '';
this.previewFile(file.originFileObj, (previewDataUrl: string) => {
file.thumbUrl = previewDataUrl;
this.detectChanges();
});
});
}

showPreview(file: UploadFile): boolean {
const { showPreviewIcon, hidePreviewIconInNonImage } = this.icons;
if (!showPreviewIcon) {
return false;
}
return this.isImageUrl(file) ? true : !hidePreviewIconInNonImage;
}

handlePreview(file: UploadFile, e: Event): void {
if (!this.onPreview) {
return;
Expand All @@ -71,10 +157,15 @@ export class NzUploadListComponent implements OnChanges {

// #endregion

constructor(private el: ElementRef, private updateHostClassService: NzUpdateHostClassService) {
constructor(private el: ElementRef, private cdr: ChangeDetectorRef, private updateHostClassService: NzUpdateHostClassService) {
}

detectChanges(): void {
this.cdr.detectChanges();
}

ngOnChanges(): void {
this.setClassMap();
this.genThumb();
}
}
6 changes: 3 additions & 3 deletions components/upload/nz-upload.component.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<ng-template #list>
<nz-upload-list *ngIf="nzShowUploadList"
<nz-upload-list #listComp [style.display]="nzShowUploadList ? '' : 'none'"
[locale]="locale"
[listType]="nzListType"
[items]="nzFileList"
[items]="nzFileList || []"
[icons]="nzShowUploadList"
[onPreview]="nzPreview"
[onRemove]="onRemove"></nz-upload-list>
</ng-template>
<ng-template #con><ng-content></ng-content></ng-template>
<ng-template #btn>
<div [ngClass]="classList" [style.display]="nzShowButton ? '' : 'none'">
<div nz-upload-btn #upload [options]="_btnOptions">
<div nz-upload-btn #uploadComp [options]="_btnOptions">
<ng-template [ngTemplateOutlet]="con"></ng-template>
</div>
</div>
Expand Down
Loading

0 comments on commit 4c41715

Please sign in to comment.