1import React, { CSSProperties } from 'react'; 2 3import { CanvasElementItem, CanvasElementProps } from '../element'; 4import { 5 ColorDimensionConfig, 6 ResourceDimensionConfig, 7 ResourceDimensionMode, 8 getPublicOrAbsoluteUrl, 9} from 'app/features/dimensions'; 10import { ColorDimensionEditor, ResourceDimensionEditor } from 'app/features/dimensions/editors'; 11import SVG from 'react-inlinesvg'; 12import { css } from '@emotion/css'; 13import { isString } from 'lodash'; 14import { LineConfig } from '../types'; 15import { DimensionContext } from 'app/features/dimensions/context'; 16 17export interface IconConfig { 18 path?: ResourceDimensionConfig; 19 fill?: ColorDimensionConfig; 20 stroke?: LineConfig; 21} 22 23interface IconData { 24 path: string; 25 fill: string; 26 strokeColor?: string; 27 stroke?: number; 28} 29 30// When a stoke is defined, we want the path to be in page units 31const svgStrokePathClass = css` 32 path { 33 vector-effect: non-scaling-stroke; 34 } 35`; 36 37export function IconDisplay(props: CanvasElementProps) { 38 const { width, height, data } = props; 39 if (!data?.path) { 40 return null; 41 } 42 43 const svgStyle: CSSProperties = { 44 fill: data?.fill, 45 stroke: data?.strokeColor, 46 strokeWidth: data?.stroke, 47 }; 48 49 return ( 50 <SVG 51 src={data.path} 52 width={width} 53 height={height} 54 style={svgStyle} 55 className={svgStyle.strokeWidth ? svgStrokePathClass : undefined} 56 /> 57 ); 58} 59 60export const iconItem: CanvasElementItem<IconConfig, IconData> = { 61 id: 'icon', 62 name: 'Icon', 63 description: 'SVG Icon display', 64 65 display: IconDisplay, 66 67 getNewOptions: (options) => ({ 68 placement: { 69 width: 50, 70 height: 50, 71 }, 72 ...options, 73 config: { 74 path: { 75 mode: ResourceDimensionMode.Fixed, 76 fixed: 'img/icons/unicons/question-circle.svg', 77 }, 78 fill: { fixed: '#FFF899' }, 79 }, 80 }), 81 82 // Called when data changes 83 prepareData: (ctx: DimensionContext, cfg: IconConfig) => { 84 let path: string | undefined = undefined; 85 if (cfg.path) { 86 path = ctx.getResource(cfg.path).value(); 87 } 88 if (!path || !isString(path)) { 89 path = getPublicOrAbsoluteUrl('img/icons/unicons/question-circle.svg'); 90 } 91 92 const data: IconData = { 93 path, 94 fill: cfg.fill ? ctx.getColor(cfg.fill).value() : '#CCC', 95 }; 96 97 if (cfg.stroke?.width && cfg.stroke.color) { 98 if (cfg.stroke.width > 0) { 99 data.stroke = cfg.stroke?.width; 100 data.strokeColor = ctx.getColor(cfg.stroke.color).value(); 101 } 102 } 103 return data; 104 }, 105 106 // Heatmap overlay options 107 registerOptionsUI: (builder) => { 108 const category = ['Icon']; 109 builder 110 .addCustomEditor({ 111 category, 112 id: 'iconSelector', 113 path: 'config.path', 114 name: 'SVG Path', 115 editor: ResourceDimensionEditor, 116 settings: { 117 resourceType: 'icon', 118 }, 119 }) 120 .addCustomEditor({ 121 category, 122 id: 'config.fill', 123 path: 'config.fill', 124 name: 'Fill color', 125 editor: ColorDimensionEditor, 126 settings: {}, 127 defaultValue: { 128 // Configured values 129 fixed: 'grey', 130 }, 131 }) 132 .addSliderInput({ 133 category, 134 path: 'config.stroke.width', 135 name: 'Stroke', 136 defaultValue: 0, 137 settings: { 138 min: 0, 139 max: 10, 140 }, 141 }) 142 .addCustomEditor({ 143 category, 144 id: 'config.stroke.color', 145 path: 'config.stroke.color', 146 name: 'Stroke color', 147 editor: ColorDimensionEditor, 148 settings: {}, 149 defaultValue: { 150 // Configured values 151 fixed: 'grey', 152 }, 153 showIf: (cfg) => Boolean(cfg?.config?.stroke?.width), 154 }); 155 }, 156}; 157