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

atinc / ngx-tethys / d9ae709b-3c27-4b69-b125-b8b80b54f90b

pending completion
d9ae709b-3c27-4b69-b125-b8b80b54f90b

Pull #2757

circleci

mengshuicmq
fix: fix code review
Pull Request #2757: feat(color-picker): color-picker support disabled (#INFR-8645)

98 of 6315 branches covered (1.55%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

2392 of 13661 relevant lines covered (17.51%)

83.12 hits per line

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

4.96
/src/watermark/watermark.directive.ts
1
import { Directive, Input, ElementRef, OnDestroy, OnInit, SimpleChanges, OnChanges } from '@angular/core';
2
import { InputBoolean, Constructor, MixinBase, mixinUnsubscribe, ThyUnsubscribe } from 'ngx-tethys/core';
3
import { Subject, Observable } from 'rxjs';
4
import { takeUntil } from 'rxjs/operators';
5
import { DEFAULT_WATERMARK_CONFIG, DEFAULT_CANVAS_CONFIG } from './config';
6
import { MutationObserverFactory } from '@angular/cdk/observers';
7

8
const _MixinBase: Constructor<ThyUnsubscribe> & typeof MixinBase = mixinUnsubscribe(MixinBase);
1✔
9

10
export interface ThyCanvasConfigType {
11
    degree?: number;
12
    color?: string;
13
    fontSize?: number | string;
1✔
14
    textLineHeight?: number;
15
    gutter?: number[];
×
16
}
×
17

18
/**
19
 * 水印指令
×
20
 * @name thyWatermark
×
21
 */
×
22
@Directive({
×
23
    selector: '[thyWatermark]',
24
    standalone: true
25
})
×
26
export class ThyWatermarkDirective extends _MixinBase implements OnInit, OnDestroy, OnChanges {
×
27
    /**
×
28
     * 是否禁用,默认为 false
29
     */
30
    @Input()
31
    @InputBoolean()
×
32
    thyDisabled: boolean = false;
33

34
    content: string;
35
    /**
×
36
     * 水印内容
×
37
     */
×
38
    @Input()
×
39
    set thyWatermark(value: string) {
×
40
        value = value?.replace(/^\"|\"$/g, '');
×
41
        this.content = !!value ? value : '';
42
    }
43

×
44
    /**
×
45
     * canvas样式配置
×
46
     */
×
47
    @Input() thyCanvasConfig: ThyCanvasConfigType;
48

×
49
    private createWatermark$ = new Subject<string>();
×
50

51
    private observer: MutationObserver;
52

×
53
    private canvas: HTMLCanvasElement;
×
54

55
    private wmDiv: HTMLElement;
56

×
57
    constructor(private el: ElementRef) {
×
58
        super();
×
59
    }
60

61
    ngOnInit() {
62
        if (!this.thyDisabled) {
×
63
            this.createWatermark$.pipe(takeUntil(this.ngUnsubscribe$)).subscribe(() => {
64
                this.observeAttributes()
×
65
                    .pipe(takeUntil(this.ngUnsubscribe$))
66
                    .subscribe(() => {});
×
67
            });
×
68
            this.createWatermark();
×
69
        }
×
70
    }
×
71

×
72
    ngOnChanges(changes: SimpleChanges): void {
73
        const { thyWatermark, thyDisabled } = changes;
74
        const thyWatermarkChange = () => {
75
            if (thyWatermark.firstChange) return;
76
            if (thyWatermark.currentValue) {
77
                this.refreshWatermark();
78
            }
79
        };
80
        const thyDisabledChange = () => {
81
            if (thyDisabled.firstChange) return;
×
82
            thyDisabled?.currentValue ? this.removeWatermark() : this.refreshWatermark();
×
83
        };
×
84
        thyWatermark && thyWatermarkChange();
×
85
        thyDisabled && thyDisabledChange();
×
86
    }
×
87

×
88
    private refreshWatermark() {
89
        this.removeWatermark();
×
90
        this.createWatermark();
×
91
    }
×
92

×
93
    private removeWatermark() {
×
94
        if (this.wmDiv) {
×
95
            this.wmDiv.remove();
×
96
            this.wmDiv = null;
×
97
        }
×
98
    }
×
99

×
100
    createCanvas() {
×
101
        let { gutter, fontSize, color, degree, textLineHeight } = {
×
102
            ...DEFAULT_CANVAS_CONFIG,
×
103
            ...(this.thyCanvasConfig || {})
×
104
        };
×
105

106
        const [xGutter, yGutter] = gutter;
×
107
        const canvas = document.createElement('canvas');
×
108
        const ctx = canvas.getContext('2d');
109

×
110
        const getFakeSize = () => {
×
111
            const fakeBox = document.createElement('div');
×
112
            const fakeBoxStyle = {
×
113
                position: 'absolute',
114
                top: 0,
115
                left: 0,
116
                display: 'inline-block',
×
117
                'font-size': `${parseFloat('' + fontSize)}px`,
×
118
                'word-wrap': 'break-word',
×
119
                'font-family': 'inherit',
×
120
                'white-space': 'pre-line'
×
121
            };
×
122
            const styleStr = Object.keys(fakeBoxStyle).reduce((pre, next) => ((pre += `${next}:${fakeBoxStyle[next]};`), pre), '');
×
123
            fakeBox.setAttribute('style', styleStr);
124

×
125
            fakeBox.innerHTML = this.content.replace(/(\\n)/gm, '</br>');
126
            document.querySelector('body').insertBefore(fakeBox, document.querySelector('body').firstChild);
127
            const { width, height } = fakeBox.getBoundingClientRect();
×
128
            fakeBox.remove();
×
129
            return { width, height };
×
130
        };
×
131
        const { width: fakeBoxWidth, height: fakeBoxHeight } = getFakeSize();
×
132

×
133
        const angle = (degree * Math.PI) / 180;
134
        const contentArr = this.content.split('\\n');
135
        const canvasHeight = Math.sin(angle) * fakeBoxWidth + fakeBoxHeight;
136

×
137
        let start = Math.ceil(Math.sin(angle) * fakeBoxWidth * Math.sin(angle));
×
138
        const canvasWidth = start + fakeBoxWidth;
×
139
        canvas.setAttribute('width', '' + (canvasWidth + xGutter));
×
140
        canvas.setAttribute('height', '' + (canvasHeight + yGutter));
141

142
        ctx.font = `${parseFloat('' + fontSize)}px microsoft yahei`;
×
143
        ctx.textAlign = 'center';
×
144
        ctx.textBaseline = 'top';
×
145
        ctx.fillStyle = color;
146
        ctx.rotate(0 - (degree * Math.PI) / 180);
147
        contentArr.map((k, i) => {
148
            ctx.fillText(k, -start + Math.ceil(canvasWidth / 2), Math.sin(angle) * canvasWidth + textLineHeight * i);
149
            start += Math.sin(angle) * textLineHeight;
×
150
        });
151
        this.canvas = canvas;
1✔
152
        return canvas;
153
    }
154

1✔
155
    private createWatermark(isRefresh = true) {
156
        const watermarkDiv = this.wmDiv || document.createElement('div');
157

158
        const background = !isRefresh ? this.canvas.toDataURL() : this.createCanvas().toDataURL();
159
        const watermarkStyle = {
160
            ...DEFAULT_WATERMARK_CONFIG,
1✔
161
            'background-image': `url(${background})`
162
        };
163

164
        const styleStr = Object.keys(watermarkStyle).reduce((pre, next) => ((pre += `${next}:${watermarkStyle[next]};`), pre), '');
1✔
165
        watermarkDiv.setAttribute('style', styleStr);
166

167
        if (!this.wmDiv) {
168
            const parentNode = this.el.nativeElement;
169
            watermarkDiv.classList.add(`_vm`);
170
            this.wmDiv = watermarkDiv;
171
            parentNode.insertBefore(watermarkDiv, parentNode.firstChild);
172
        }
173
        this.createWatermark$.next('');
174
    }
175

176
    private observeAttributes() {
177
        this.observer?.disconnect();
178
        return new Observable(observe => {
179
            const stream = new Subject<MutationRecord[]>();
180
            this.observer = new MutationObserverFactory().create(mutations => stream.next(mutations));
181
            if (this.observer) {
182
                this.observer.observe(this.wmDiv, {
183
                    attributes: true
184
                });
185
            }
186
            stream.pipe(takeUntil(this.ngUnsubscribe$)).subscribe(() => {
187
                if (this.wmDiv) {
188
                    this?.observer?.disconnect();
189
                    this.createWatermark(false);
190
                }
191
            });
192
            observe.next(stream);
193
            return () => {
194
                this.observer?.disconnect();
195
            };
196
        });
197
    }
198

199
    ngOnDestroy(): void {
200
        super.ngOnDestroy();
201
    }
202
}
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