From 6c0c8832989ef6416c4ddd822bd11442cbad8b00 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sat, 8 Jul 2023 16:55:16 +0200 Subject: [PATCH] Creating svg picker for svg icon config #587 #667 --- src/common/config/public/ClientConfig.ts | 28 +++--- .../app/ui/gallery/map/MarkerFactory.ts | 6 +- .../lightbox.map.gallery.component.ts | 2 +- .../settings-entry.component.html | 92 ++++++++++++++++--- .../settings-entry.component.ts | 62 ++++++++++++- 5 files changed, 155 insertions(+), 35 deletions(-) diff --git a/src/common/config/public/ClientConfig.ts b/src/common/config/public/ClientConfig.ts index 842a7446..dde0e85d 100644 --- a/src/common/config/public/ClientConfig.ts +++ b/src/common/config/public/ClientConfig.ts @@ -41,7 +41,7 @@ export type TAGS = { experimental?: boolean, //is it a beta feature unit?: string, // Unit info to display on UI uiIcon?: string, - uiType?: 'SearchQuery' | 'ThemeSelector' | 'SelectedThemeSettings', // Hint for the UI about the type + uiType?: 'SearchQuery' | 'ThemeSelector' | 'SelectedThemeSettings' | 'SVGIconConfig', // Hint for the UI about the type uiOptions?: (string | number)[], //Hint for the UI about the recommended options uiAllowSpaces?: boolean uiOptional?: boolean; //makes the tag not "required" @@ -318,27 +318,23 @@ export class MapLayers { darkLayer: boolean = false; } -export enum MapPathGroupTypes { - Transportation = 1, Sport, Custom -} - @SubConfigClass({tags: {client: true}, softReadonly: true}) export class SVGIconConfig { - constructor(viewBoxWidth: number = 512, path: string = '') { - this.viewBoxWidth = viewBoxWidth; + constructor(viewBox: string = '0 0 512 512', path: string = '') { + this.viewBox = viewBox; this.path = path; } @ConfigProperty({ tags: { - name: $localize`SBG icon viewBox with`, + name: $localize`SVG icon viewBox`, priority: ConfigPriority.advanced }, - description: $localize`You need the with from the SVG viewBox (assuming height is 512). See: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox`, + description: $localize`SVG path viewBox. See: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox`, }) - viewBoxWidth: number = 512; + viewBox: string = '0 0 512 512'; @ConfigProperty({ tags: { @@ -372,7 +368,8 @@ export class PathThemeConfig { @ConfigProperty({ tags: { name: $localize`Dash pattern`, - priority: ConfigPriority.advanced + priority: ConfigPriority.advanced, + uiOptions: ['', '4', '4 1', '4 8', '4 1 2', '0 4 0', '4 1 2 3'], }, description: $localize`Dash pattern of the path. Represents the spacing and length of the dash. Read more about dash array at: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray.`, }) @@ -383,8 +380,9 @@ export class PathThemeConfig { type: SVGIconConfig, tags: { name: $localize`Svg Icon`, + uiType: 'SVGIconConfig', priority: ConfigPriority.advanced - }, + } as TAGS, description: $localize`Set the icon of the map marker pin.`, }) svgIcon: SVGIconConfig = new SVGIconConfig(); @@ -406,7 +404,7 @@ export class MapPathGroupThemeConfig { name: $localize`Matchers`, priority: ConfigPriority.advanced }, - description: $localize`List of regex string to match the name of the path. Case insensitive.`, + description: $localize`List of regex string to match the name of the path. Case insensitive. Empty list matches everything.`, }) matchers: string[] = []; @@ -527,7 +525,7 @@ export class ClientMapConfig { ['flight', 'flying', 'drive', 'driving'], new PathThemeConfig('var(--bs-orange)', '4 8', - new SVGIconConfig(567, 'M482.3 192c34.2 0 93.7 29 93.7 64c0 36-59.5 64-93.7 64l-116.6 0L265.2 495.9c-5.7 10-16.3 16.1-27.8 16.1l-56.2 0c-10.6 0-18.3-10.2-15.4-20.4l49-171.6L112 320 68.8 377.6c-3 4-7.8 6.4-12.8 6.4l-42 0c-7.8 0-14-6.3-14-14c0-1.3 .2-2.6 .5-3.9L32 256 .5 145.9c-.4-1.3-.5-2.6-.5-3.9c0-7.8 6.3-14 14-14l42 0c5 0 9.8 2.4 12.8 6.4L112 192l102.9 0-49-171.6C162.9 10.2 170.6 0 181.2 0l56.2 0c11.5 0 22.1 6.2 27.8 16.1L365.7 192l116.6 0z') + new SVGIconConfig('0 0 567 512', 'M482.3 192c34.2 0 93.7 29 93.7 64c0 36-59.5 64-93.7 64l-116.6 0L265.2 495.9c-5.7 10-16.3 16.1-27.8 16.1l-56.2 0c-10.6 0-18.3-10.2-15.4-20.4l49-171.6L112 320 68.8 377.6c-3 4-7.8 6.4-12.8 6.4l-42 0c-7.8 0-14-6.3-14-14c0-1.3 .2-2.6 .5-3.9L32 256 .5 145.9c-.4-1.3-.5-2.6-.5-3.9c0-7.8 6.3-14 14-14l42 0c5 0 9.8 2.4 12.8 6.4L112 192l102.9 0-49-171.6C162.9 10.2 170.6 0 181.2 0l56.2 0c11.5 0 22.1 6.2 27.8 16.1L365.7 192l116.6 0z') ) )]), new MapPathGroupConfig('Sport', @@ -535,7 +533,7 @@ export class ClientMapConfig { ['run', 'walk', 'hike', 'hiking', 'bike', 'biking', 'cycling', 'skiing'], new PathThemeConfig('var(--bs-primary)', '', - new SVGIconConfig(417, 'M320 48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM125.7 175.5c9.9-9.9 23.4-15.5 37.5-15.5c1.9 0 3.8 .1 5.6 .3L137.6 254c-9.3 28 1.7 58.8 26.8 74.5l86.2 53.9-25.4 88.8c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l28.7-100.4c5.9-20.6-2.6-42.6-20.7-53.9L238 299l30.9-82.4 5.1 12.3C289 264.7 323.9 288 362.7 288H384c17.7 0 32-14.3 32-32s-14.3-32-32-32H362.7c-12.9 0-24.6-7.8-29.5-19.7l-6.3-15c-14.6-35.1-44.1-61.9-80.5-73.1l-48.7-15c-11.1-3.4-22.7-5.2-34.4-5.2c-31 0-60.8 12.3-82.7 34.3L57.4 153.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l23.1-23.1zM91.2 352H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h69.6c19 0 36.2-11.2 43.9-28.5L157 361.6l-9.5-6c-17.5-10.9-30.5-26.8-37.9-44.9L91.2 352z') + new SVGIconConfig('0 0 417 512', 'M320 48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM125.7 175.5c9.9-9.9 23.4-15.5 37.5-15.5c1.9 0 3.8 .1 5.6 .3L137.6 254c-9.3 28 1.7 58.8 26.8 74.5l86.2 53.9-25.4 88.8c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l28.7-100.4c5.9-20.6-2.6-42.6-20.7-53.9L238 299l30.9-82.4 5.1 12.3C289 264.7 323.9 288 362.7 288H384c17.7 0 32-14.3 32-32s-14.3-32-32-32H362.7c-12.9 0-24.6-7.8-29.5-19.7l-6.3-15c-14.6-35.1-44.1-61.9-80.5-73.1l-48.7-15c-11.1-3.4-22.7-5.2-34.4-5.2c-31 0-60.8 12.3-82.7 34.3L57.4 153.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l23.1-23.1zM91.2 352H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h69.6c19 0 36.2-11.2 43.9-28.5L157 361.6l-9.5-6c-17.5-10.9-30.5-26.8-37.9-44.9L91.2 352z') ) )]), new MapPathGroupConfig('Other paths', diff --git a/src/frontend/app/ui/gallery/map/MarkerFactory.ts b/src/frontend/app/ui/gallery/map/MarkerFactory.ts index 94747747..abec7a38 100644 --- a/src/frontend/app/ui/gallery/map/MarkerFactory.ts +++ b/src/frontend/app/ui/gallery/map/MarkerFactory.ts @@ -3,7 +3,7 @@ import {DivIcon, setOptions} from 'leaflet'; export interface SvgIconOptions { color?: string; svgPath?: string; - width?: number; + viewBox?: string; small?: boolean; } @@ -11,8 +11,8 @@ const SvgIcon: { new(options?: SvgIconOptions): DivIcon } = DivIcon.extend({ initialize: function(options: SvgIconOptions = {}) { options.color = options.color || 'var(--bs-primary)'; options.svgPath = options.svgPath || 'M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512z'; - options.width = options.width || 512; - const svg = ''; + options.viewBox = options.viewBox || '0 0 512 512'; + const svg = ''; setOptions(this, { iconSize: options.small ? [15, 15] : [30, 30], iconAnchor: options.small ? [15, 28] : [15, 35], diff --git a/src/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.ts b/src/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.ts index c2ef93b3..26121428 100644 --- a/src/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.ts +++ b/src/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.ts @@ -189,7 +189,7 @@ export class GalleryMapLightboxComponent implements OnChanges, OnDestroy { icon: MarkerFactory.getSvgIcon({ color: ths.theme.color, svgPath: ths.theme.svgIcon?.path, - width: ths.theme.svgIcon?.viewBoxWidth + viewBox: ths.theme.svgIcon?.viewBox }) }; }) diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html index 7cd29c19..6a3bec2a 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html @@ -151,6 +151,79 @@ (ngModelChange)="onChange($event)"> + + + + + + + + + + + +
@@ -227,17 +300,11 @@
-
-
- + [ngModel]="val.theme.__state.svgIcon" + [name]="'svgIcon_'+idName+i" + [id]="'svgIcon_'+idName+i" + (ngModelChange)="onChange($event)"> +
@@ -279,7 +346,8 @@ [ngModel]="arr.__state.matchers" [name]="'arr_m_'+idName+i" [id]="'arr_m_'+idName+i" - (change)="onChange($event)"> + (ngModelChange)="onChange($event)"> + diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts index 4d980f75..f767496e 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.ts @@ -3,7 +3,15 @@ import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors import {Utils} from '../../../../../../common/Utils'; import {propertyTypes} from 'typeconfig/common'; import {SearchQueryParserService} from '../../../gallery/search/search-query-parser.service'; -import {MapLayers, MapPathGroupConfig, MapPathGroupThemeConfig, NavigationLinkConfig, NavigationLinkTypes, TAGS, ThemeConfig} from '../../../../../../common/config/public/ClientConfig'; +import { + MapLayers, + MapPathGroupConfig, + MapPathGroupThemeConfig, + NavigationLinkConfig, + NavigationLinkTypes, + TAGS, + ThemeConfig +} from '../../../../../../common/config/public/ClientConfig'; import {SettingsService} from '../../settings.service'; import {WebConfig} from '../../../../../../common/config/private/WebConfig'; import {JobScheduleConfig, UserConfig} from '../../../../../../common/config/private/PrivateConfig'; @@ -69,6 +77,7 @@ export class SettingsEntryComponent public arrayType: string; public uiType: string; newThemeModalRef: any; + iconModal: { ref?: any, error?: string }; constructor(private searchQueryParserService: SearchQueryParserService, @@ -217,11 +226,16 @@ export class SettingsEntryComponent } else { this.arrayType = this.state.arrayType; } + + if (this.state.tags?.uiOptions) { + this.state.isEnumType = true; + } this.uiType = this.arrayType; if (!this.state.isEnumType && !this.state.isEnumArrayType && this.type !== 'boolean' && this.type !== 'SearchQuery' && + this.type !== 'SVGIconConfig' && this.arrayType !== 'MapLayers' && this.arrayType !== 'NavigationLinkConfig' && this.arrayType !== 'MapPathGroupConfig' && @@ -242,9 +256,6 @@ export class SettingsEntryComponent this.placeholder = this.state.tags?.hint || this.state.default; - if (this.state.tags?.uiOptions) { - this.state.isEnumType = true; - } this.title = ''; if (this.state.readonly) { this.title = $localize`readonly` + ', '; @@ -404,11 +415,54 @@ export class SettingsEntryComponent } + showIconModal(template: TemplateRef): void { + this.iconModal = {}; + this.iconModal.ref = this.modalService.show(template, { + class: 'modal-lg', + }); + document.body.style.paddingRight = '0px'; + } + + public hideNewThemeModal(): void { this.newThemeModalRef.hide(); this.newThemeModalRef = null; } + hideIconModal(): void { + if (!this.iconModal) { + return; + } + this.iconModal.ref.hide(); + delete this.iconModal; + } + + newSvgFile(event: Event): void { + + const file: File = (event.target as HTMLInputElement).files[0]; + + const reader = new FileReader(); + reader.onload = () => { + console.log(reader.result); + const parser = new DOMParser(); + const doc = parser.parseFromString(reader.result as string, 'image/svg+xml'); + try { + const wb = doc.documentElement.getAttribute('viewBox'); + const path = doc.documentElement.getElementsByTagName('path')[0].getAttribute('d'); + this.state.value.path = path; + this.state.value.viewBox = wb; + } catch (e) { + console.error(e); + if (this.iconModal) { + this.iconModal.error = 'Can\'t parse SVG file: ' + e.toState; + } + } + this.onChange(null); + }; + reader.readAsText(file); + + } + }