• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atinc / ngx-tethys / f1a286ce-ff11-484c-ae71-950dd547bf85

20 May 2025 06:07AM UTC coverage: 90.216% (-0.007%) from 90.223%
f1a286ce-ff11-484c-ae71-950dd547bf85

Pull #3449

circleci

why520crazy
refactor: update @Output to output
Pull Request #3449: refactor(upload): migrate inputs to signal #TINFR-1787

5568 of 6832 branches covered (81.5%)

Branch coverage included in aggregate %.

35 of 36 new or added lines in 3 files covered. (97.22%)

4 existing lines in 3 files now uncovered.

13592 of 14406 relevant lines covered (94.35%)

907.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.67
/src/upload/file-drop.directive.ts
1
import { isEmpty, isString } from 'ngx-tethys/util';
2
import { fromEvent, Subject } from 'rxjs';
3
import { filter, takeUntil, tap } from 'rxjs/operators';
4

5
import {
6
    DestroyRef,
7
    Directive,
8
    ElementRef,
9
    EventEmitter,
10
    HostBinding,
11
    Inject,
12
    Input,
13
    NgZone,
1✔
14
    OnInit,
15
    Output,
8✔
16
    Renderer2,
8✔
17
    inject,
8✔
18
    input,
8✔
19
    output,
8✔
20
    signal
8✔
21
} from '@angular/core';
8✔
22
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
8✔
23

8✔
24
import { FileSelectBaseDirective } from './file-select-base';
8✔
25
import { THY_UPLOAD_DEFAULT_OPTIONS, ThyUploadConfig } from './upload.config';
26
import { ThyFileSelectEvent } from './types';
27

8✔
28
/**
8✔
29
 * @name thyFileDrop
30
 */
7✔
31
@Directive({
7✔
32
    selector: '[thyFileDrop]',
33
    host: {
7!
34
        '[class.thy-drop-over]': 'isDragOver()',
7✔
35
        '[class.drop-over]': 'isDragOver()'
7✔
36
    }
4✔
37
})
4✔
38
export class ThyFileDropDirective extends FileSelectBaseDirective implements OnInit {
4✔
39
    protected isDragOver = signal(false);
40

41
    readonly thyFileDropClassName = input<string>();
42

43
    readonly thyOnDrop = output<ThyFileSelectEvent>();
8✔
44

45
    /**
46
     * 当拖拽的文件中有不符合 thyAcceptType 中定义的类型时触发
1✔
47
     * @description.en-us It is triggered when there are files in the dragged files that do not conform to the types defined in thyAcceptType.
48
     */
8✔
49
    thyFilesReject = output<File[]>();
50

51
    private destroyRef = inject(DestroyRef);
2✔
52

2!
53
    constructor(
2✔
54
        public elementRef: ElementRef,
2✔
55
        public renderer: Renderer2,
56
        public ngZone: NgZone,
57
        @Inject(THY_UPLOAD_DEFAULT_OPTIONS) public defaultConfig: ThyUploadConfig
58
    ) {
8✔
59
        super(elementRef, defaultConfig);
60
    }
7✔
61

62
    public ngOnInit(): void {
63
        this.ngZone.runOutsideAngular(() => {
7✔
64
            fromEvent(this.elementRef.nativeElement, 'dragenter')
7!
65
                .pipe(
7✔
66
                    takeUntilDestroyed(this.destroyRef),
7✔
67
                    tap((event: DragEvent) => {
7✔
68
                        event.preventDefault();
4✔
69
                    }),
4✔
70
                    filter(event => event.dataTransfer.items && event.dataTransfer.items.length > 0)
71
                )
7✔
72
                .subscribe((event: DragEvent) => {
2✔
73
                    if (this.checkRejectFolderAndHtmlElement(event)) {
74
                        const files = this.filterFilesOrItems(Array.from(event.dataTransfer.items));
75
                        if (!isEmpty(files)) {
7✔
76
                            this.ngZone.run(() => {
7✔
77
                                this.isDragOver.set(true);
78
                                this.toggleDropOverClassName();
79
                            });
80
                        }
81
                    }
82
                });
83

14✔
84
            fromEvent(this.elementRef.nativeElement, 'dragover')
14✔
85
                .pipe(takeUntilDestroyed(this.destroyRef))
14✔
86
                .subscribe((event: any) => {
13✔
87
                    event.preventDefault();
13✔
88
                });
13!
UNCOV
89

×
90
            fromEvent(this.elementRef.nativeElement, 'dragleave')
91
                .pipe(takeUntilDestroyed(this.destroyRef))
92
                .subscribe((event: any) => {
93
                    this.ngZone.run(() => {
14✔
94
                        if (!this.elementRef.nativeElement.contains(event.fromElement)) {
95
                            this.resetDragOver();
96
                            this.toggleDropOverClassName();
97
                        }
13!
98
                    });
UNCOV
99
                });
×
100

101
            fromEvent(this.elementRef.nativeElement, 'drop')
13!
102
                .pipe(
13✔
103
                    takeUntilDestroyed(this.destroyRef),
104
                    tap((event: DragEvent) => {
13✔
105
                        event.preventDefault();
106
                    })
107
                )
14✔
108
                .subscribe((event: DragEvent) => {
14✔
109
                    this.ngZone.run(() => {
8✔
110
                        if (this.checkRejectFolderAndHtmlElement(event)) {
8✔
111
                            const originFiles = event.dataTransfer ? Array.from(event.dataTransfer.files) : [];
8✔
112
                            const files = this.filterFilesOrItems(originFiles) as File[];
113

114
                            if (files.length !== originFiles.length) {
115
                                const differentFiles = originFiles.filter(item => !files.includes(item));
6✔
116
                                this.thyFilesReject.emit(differentFiles);
117
                            }
118
                            if (!isEmpty(files)) {
119
                                this.selectFiles(event, files, this.thyOnDrop);
13✔
120
                            }
13✔
121
                        }
2✔
122
                        this.resetDragOver();
1✔
123
                        this.toggleDropOverClassName();
124
                    });
125
                });
1✔
126
        });
127
    }
128

129
    private checkRejectFolderAndHtmlElement(event: DragEvent) {
130
        // 排除文件夹和HTML元素拖拽
9✔
131
        const items: DataTransferItemList | DataTransferItem[] = event.dataTransfer ? event.dataTransfer.items : [];
132
        let res = true;
1✔
133
        for (let index = 0; index < items.length; index++) {
134
            const item = items[index];
135
            const entry = this.getAsEntry(item);
136
            if (item.kind !== 'file' || (entry && !entry.isFile)) {
137
                res = false;
138
                // console.error(`file extensions not support drag upload, kind: ${item.kind}, type: ${item.type}`);
1✔
139
            }
140
        }
141
        return res;
142
    }
143

144
    private getAsEntry(item: DataTransferItem): FileSystemEntry {
1✔
145
        let entry: FileSystemEntry;
146
        if ((item as unknown as { getAsEntry: () => FileSystemEntry })['getAsEntry']) {
147
            // https://wiki.whatwg.org/wiki/DragAndDropEntries
148
            entry = (item as unknown as { getAsEntry: () => FileSystemEntry })['getAsEntry']();
149
        } else if (item.webkitGetAsEntry) {
150
            entry = item.webkitGetAsEntry();
151
        }
152
        return entry;
153
    }
154

155
    private filterFilesOrItems(items: Array<DataTransferItem | File>): Array<DataTransferItem | File> {
156
        const acceptType = this.thyAcceptType();
157
        if (acceptType && acceptType != '*/*') {
158
            return items.filter(item => {
159
                const isValidType = isString(item.type) && item.type.length > 0;
160
                return isValidType && acceptType.includes(item.type);
161
            });
162
        } else {
163
            return Array.from(items);
164
        }
165
    }
166

167
    private toggleDropOverClassName() {
168
        const dragOverCustomClass = this.thyFileDropClassName();
169
        if (dragOverCustomClass) {
170
            if (this.isDragOver()) {
171
                this.renderer.addClass(this.elementRef.nativeElement, dragOverCustomClass);
172
            } else {
173
                this.renderer.removeClass(this.elementRef.nativeElement, dragOverCustomClass);
174
            }
175
        }
176
    }
177
    private resetDragOver() {
178
        this.isDragOver.set(false);
179
    }
180
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc