Browse Source

UI: New map widgets - introduce preserveAspectRatio option for shape image fill settings.

pull/13167/head
Igor Kulikov 1 year ago
parent
commit
7d4ccad788
  1. 34
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts
  2. 10
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts
  3. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings-panel.component.html
  4. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings-panel.component.ts
  5. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-stripe-settings-panel.component.ts
  6. 6
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-stripe-settings.component.ts
  7. 4
      ui-ngx/src/app/shared/models/widget/maps/map.models.ts
  8. 2
      ui-ngx/src/assets/help/en_US/widget/lib/map/shape_fill_image_fn.md
  9. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  10. 9
      ui-ngx/src/typings/leaflet-extend-tb.d.ts

34
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts

@ -49,9 +49,10 @@ interface ShapePatternInfo {
image: string;
width: number;
height: number;
opacity?: number;
angle?: number;
scale?: number;
preserveAspectRatio: boolean;
opacity: number;
angle: number;
scale: number;
};
fillStripe?: {
weight: number;
@ -122,23 +123,35 @@ abstract class ShapePatternProcessor<S = any> {
fillOpacity: 1, stroke: false, fill: true, fillColor: patternInfo.fillColor});
pattern.addElement(fillRect);
} else if (patternInfo.type === ShapeFillType.image) {
pattern = new L.TB.Pattern({
const patternOptions: L.TB.PatternOptions = {
width: 1,
height: 1,
patternUnits: 'objectBoundingBox',
patternContentUnits: 'objectBoundingBox',
preserveAspectRatioAlign: 'xMidYMid',
preserveAspectRatioMeetOrSlice: 'slice',
viewBox: [0,0,patternInfo.fillImage.width,patternInfo.fillImage.height]
});
const imagePatternShape = new L.TB.PatternImage({
};
if (patternInfo.fillImage.preserveAspectRatio) {
patternOptions.preserveAspectRatioAlign = 'xMidYMid';
patternOptions.preserveAspectRatioMeetOrSlice = 'slice';
} else {
patternOptions.preserveAspectRatioAlign = 'none';
}
pattern = new L.TB.Pattern(patternOptions);
const imagePatternOptions: L.TB.PatternImageOptions = {
imageUrl: patternInfo.fillImage.image,
width: patternInfo.fillImage.width,
height: patternInfo.fillImage.height,
opacity: patternInfo.fillImage.opacity,
angle: patternInfo.fillImage.angle,
scale: patternInfo.fillImage.scale
});
};
if (patternInfo.fillImage.preserveAspectRatio) {
imagePatternOptions.preserveAspectRatioAlign = 'xMidYMid';
imagePatternOptions.preserveAspectRatioMeetOrSlice = 'slice';
} else {
imagePatternOptions.preserveAspectRatioAlign = 'none';
}
const imagePatternShape = new L.TB.PatternImage(imagePatternOptions);
pattern.addElement(imagePatternShape);
} else if (patternInfo.type === ShapeFillType.stripe) {
const stripeInfo = patternInfo.fillStripe;
@ -221,6 +234,7 @@ class ShapeImagePatternProcessor extends ShapePatternProcessor<ShapeFillImageSet
if (!currentImage?.url) {
currentImage = {
url: this.settings.image,
preserveAspectRatio: this.settings.preserveAspectRatio,
opacity: this.settings.opacity,
angle: this.settings.angle,
scale: this.settings.scale
@ -231,6 +245,7 @@ class ShapeImagePatternProcessor extends ShapePatternProcessor<ShapeFillImageSet
private loadPatternInfoFromImage(image: ShapeFillImageInfo): Observable<ShapePatternInfo> {
const imageUrl = image?.url || '/assets/widget-preview-empty.svg';
const preserveAspectRatio = isDefinedAndNotNull(image?.preserveAspectRatio) ? image.preserveAspectRatio : true;
const opacity = isDefinedAndNotNull(image?.opacity) ? image.opacity : 1;
const imagePipe = this.dataLayer.getCtx().$injector.get(ImagePipe);
return loadImageWithAspect(imagePipe, imageUrl).pipe(
@ -241,6 +256,7 @@ class ShapeImagePatternProcessor extends ShapePatternProcessor<ShapeFillImageSet
image: res.url,
width: res.width,
height: res.height,
preserveAspectRatio,
opacity,
angle: image?.angle,
scale: image?.scale

10
ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts

@ -974,7 +974,15 @@ class PatternImage extends PatternElement<L.TB.PatternImageOptions> implements L
this._dom.setAttribute('y', '0');
this._dom.setAttribute('width', `${options.width}`);
this._dom.setAttribute('height', `${options.height}`);
this._dom.setAttribute('preserveAspectRatio', 'xMidYMid slice');
if (options.preserveAspectRatioAlign) {
let preserveAspectRatioValue = options.preserveAspectRatioAlign;
if (preserveAspectRatioValue !== 'none' && options.preserveAspectRatioMeetOrSlice) {
preserveAspectRatioValue += (' ' + options.preserveAspectRatioMeetOrSlice);
}
dom.setAttribute('preserveAspectRatio', preserveAspectRatioValue);
} else {
dom.removeAttribute('preserveAspectRatio');
}
const transforms: string[] = [];
if (options.angle) {
transforms.push(`rotate(${options.angle})`);

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings-panel.component.html

@ -30,6 +30,11 @@
<div class="tb-shape-fill-image-settings-panel-body" [class.!hidden]="shapeFillImageSettingsFormGroup.get('type').value !== ShapeFillImageType.image">
<div class="tb-form-panel no-padding no-border">
<tb-gallery-image-input required formControlName="image"></tb-gallery-image-input>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="preserveAspectRatio">
{{ 'widgets.maps.data-layer.shape.preserve-aspect-ratio' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.maps.data-layer.shape.opacity</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings-panel.component.ts

@ -53,6 +53,7 @@ export class ShapeFillImageSettingsPanelComponent implements OnInit {
{
type: [this.shapeFillImageSettings?.type || ShapeFillImageType.image, []],
image: [this.shapeFillImageSettings?.image, [Validators.required]],
preserveAspectRatio: [this.shapeFillImageSettings?.preserveAspectRatio, []],
opacity: [this.shapeFillImageSettings?.opacity, [Validators.min(0), Validators.max(1)]],
angle: [this.shapeFillImageSettings?.angle, [Validators.min(0), Validators.max(360)]],
scale: [this.shapeFillImageSettings?.scale, [Validators.min(0)]],
@ -83,6 +84,7 @@ export class ShapeFillImageSettingsPanelComponent implements OnInit {
const type: ShapeFillImageType = this.shapeFillImageSettingsFormGroup.get('type').value;
if (type === ShapeFillImageType.image) {
this.shapeFillImageSettingsFormGroup.get('image').enable({emitEvent: false});
this.shapeFillImageSettingsFormGroup.get('preserveAspectRatio').enable({emitEvent: false});
this.shapeFillImageSettingsFormGroup.get('opacity').enable({emitEvent: false});
this.shapeFillImageSettingsFormGroup.get('angle').enable({emitEvent: false});
this.shapeFillImageSettingsFormGroup.get('scale').enable({emitEvent: false});
@ -90,6 +92,7 @@ export class ShapeFillImageSettingsPanelComponent implements OnInit {
this.shapeFillImageSettingsFormGroup.get('images').disable({emitEvent: false});
} else {
this.shapeFillImageSettingsFormGroup.get('image').disable({emitEvent: false});
this.shapeFillImageSettingsFormGroup.get('preserveAspectRatio').disable({emitEvent: false});
this.shapeFillImageSettingsFormGroup.get('opacity').disable({emitEvent: false});
this.shapeFillImageSettingsFormGroup.get('angle').disable({emitEvent: false});
this.shapeFillImageSettingsFormGroup.get('scale').disable({emitEvent: false});

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-stripe-settings-panel.component.ts

@ -97,7 +97,7 @@ export class ShapeFillStripeSettingsPanelComponent implements OnInit {
private updatePreview() {
const shapeFillStripeSettings: ShapeFillStripeSettings = this.shapeFillStripeSettingsFormGroup.value;
const previewUrl = generateStripePreviewUrl(shapeFillStripeSettings);
const previewUrl = generateStripePreviewUrl(shapeFillStripeSettings, 136, 118);
this.stripePreviewStyle = {
background: this.sanitizer.bypassSecurityTrustStyle(`url(${previewUrl}) no-repeat 50% 50% / cover`)
};

6
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-stripe-settings.component.ts

@ -126,15 +126,15 @@ export class ShapeFillStripeSettingsComponent implements ControlValueAccessor {
}
}
export const generateStripePreviewUrl = (settings: ShapeFillStripeSettings): string => {
export const generateStripePreviewUrl = (settings: ShapeFillStripeSettings, previewWidth = 48, previewHeight = 48): string => {
const weight = isDefinedAndNotNull(settings?.weight) ? settings.weight : 3;
const spaceWeight = isDefinedAndNotNull(settings?.spaceWeight) ? settings.spaceWeight : 9;
const angle = isDefinedAndNotNull(settings?.angle) ? settings.angle : 45;
const height = weight + spaceWeight;
const color = settings?.color?.color || '#8f8f8f';
const spaceColor = settings?.spaceColor?.color || 'rgba(143,143,143,0)';
const svgStr = `<svg x="0" y="0" width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="48" height="48" fill="url(#stripePattern)" fill-opacity="1"></rect>
const svgStr = `<svg x="0" y="0" width="${previewWidth}" height="${previewHeight}" viewBox="0 0 ${previewWidth} ${previewHeight}" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="${previewWidth}" height="${previewHeight}" fill="url(#stripePattern)" fill-opacity="1"></rect>
<defs>
<pattern id="stripePattern" x="0" y="0" width="8" height="${height}" patternUnits="userSpaceOnUse"
patternContentUnits="userSpaceOnUse" patternTransform="rotate(${angle})">

4
ui-ngx/src/app/shared/models/widget/maps/map.models.ts

@ -501,6 +501,7 @@ export enum ShapeFillImageType {
export interface ShapeFillImageSettings {
type: ShapeFillImageType;
image?: string;
preserveAspectRatio?: boolean;
opacity?: number; // (0-1)
angle?: number; // (0-360)
scale?: number; // (0-...)
@ -550,6 +551,7 @@ export const defaultBasePolygonsDataLayerSettings = (mapType: MapType): Partial<
fillImage: {
type: ShapeFillImageType.image,
image: '/assets/widget-preview-empty.svg',
preserveAspectRatio: true,
opacity: 1,
angle: 0,
scale: 1
@ -600,6 +602,7 @@ export const defaultBaseCirclesDataLayerSettings = (mapType: MapType): Partial<C
fillImage: {
type: ShapeFillImageType.image,
image: '/assets/widget-preview-empty.svg',
preserveAspectRatio: true,
opacity: 1,
angle: 0,
scale: 1
@ -1186,6 +1189,7 @@ export interface MarkerIconInfo {
export interface ShapeFillImageInfo {
url: string;
preserveAspectRatio?: boolean;
opacity?: number;
angle?: number;
scale?: number;

2
ui-ngx/src/assets/help/en_US/widget/lib/map/shape_fill_image_fn.md

@ -20,6 +20,7 @@ Should return shape fill image data having the following structure:
```typescript
{
url: string;
preserveAspectRatio?: boolean;
opacity?: number;
angle?: number;
scale?: number;
@ -27,6 +28,7 @@ Should return shape fill image data having the following structure:
```
- *url* - fill image url;
- *preserveAspectRatio* - optional property indicating whether to preserve image aspect ratio (`true` if not specified);
- *opacity* - optional image opacity, number value from 0 to 1;
- *angle* - optional image rotation angle, number value from 0 to 360;
- *scale* - optional image scale, number value (1 - original size, smaller value - scale down, bigger value - scale up);

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -8134,6 +8134,7 @@
"fill-image": "Fill image",
"fill-image-type-image": "Image",
"fill-image-type-function": "Function",
"preserve-aspect-ratio": "Preserve aspect ratio",
"opacity": "Opacity",
"angle": "Rotation angle",
"scale": "Scale",

9
ui-ngx/src/typings/leaflet-extend-tb.d.ts

@ -168,6 +168,9 @@ declare module 'leaflet' {
fillPattern?: Pattern | undefined;
}
type aspectRatioAlign = "none" | "xMinYMin" | "xMidYMin" | "xMaxYMin" | "xMinYMid" | "xMidYMid" | "xMaxYMid" | "xMinYMax" | "xMidYMax" | "xMaxYMax";
type aspectRatioMeetOrSlice = "meet" | "slice";
interface PatternOptions {
x?: number | undefined;
y?: number | undefined;
@ -176,8 +179,8 @@ declare module 'leaflet' {
patternUnits?: "userSpaceOnUse" | "objectBoundingBox" | undefined;
patternContentUnits?: "userSpaceOnUse" | "objectBoundingBox" | undefined;
patternTransform?: string | null | undefined;
preserveAspectRatioAlign?: "none" | "xMinYMin" | "xMidYMin" | "xMaxYMin" | "xMinYMid" | "xMidYMid" | "xMaxYMid" | "xMinYMax" | "xMidYMax" | "xMaxYMax" | undefined;
preserveAspectRatioMeetOrSlice?: "meet" | "slice" | undefined;
preserveAspectRatioAlign?: aspectRatioAlign | undefined;
preserveAspectRatioMeetOrSlice?: aspectRatioMeetOrSlice | undefined;
viewBox?: [number, number, number, number] | undefined;
angle?: number | null | undefined;
className?: string | undefined;
@ -222,6 +225,8 @@ declare module 'leaflet' {
imageUrl: string;
width: number;
height: number;
preserveAspectRatioAlign?: aspectRatioAlign | undefined;
preserveAspectRatioMeetOrSlice?: aspectRatioMeetOrSlice | undefined;
opacity?: number;
angle?: number;
scale?: number;

Loading…
Cancel
Save