Geomap: Custom markers using ResourcePicker with standard marker fallbacks (#39919)
* add custom icons
* use resourcePicker for marker icon
* use regular shapes for specific icons
* update type
* update svgs and remove marker shape selection
* update types and resourcePicker
* add migration and update markers
Co-authored-by: Ryan McKinley <ryantxu@users.noreply.github.com>
* quick cleanup
* update marker path and remove any
* update migration test
* use inline snapshot
* remove unused
* Docs: Add documentation for library elements API (#39829)
* LibraryElements: Adds api documentation
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/http_api/library_element.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Refactor: changes after PR comments
* Apply suggestions from code review
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Chore: updates after review
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* CodeEditor: making sure we trigger the latest onSave callback provided to the component (#39835)
* Fix: prevent queryDisplyText in QueryRowHeader from overflowing (#40094)
* Revert "Fix Query Editor Row horizontal overflow (#39419)"
This reverts commit 42b1fa0f62
.
* fix: prevent queryDisplyText in QueryRowHeader from overflowing
* Search: Fix local storage key (#40127)
* Default to 'General' if no folder title is present
* Add bottom padding
* Live: remote write sampling (#40079)
* Stat: recompute shared y range during streaming updates (#39485)
* NavBar: Order App plugins alphabetically (#40078)
* NavBar: Order App plugins alphabetically
* Update pkg/api/index.go
Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
* Schema: use the generated graph.gen.ts (#40090)
* actually generate graph.gen.ts
* getting closer
* keep file where it is
* manual fixes
* Update packages/grafana-schema/src/schema/graph.gen.ts
Co-authored-by: sam boyer <sam.boyer@grafana.com>
* Docs: Whats new in 8.2 (#39945)
* Added time range controls updates
* Added plugins catalog update
* Added enterprise images
* Added community contributions highlights for 8.2
* accessibility statement
* Update docs/sources/whatsnew/whats-new-in-v8-2.md
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
* Update docs/sources/whatsnew/whats-new-in-v8-2.md
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
* Update docs/sources/whatsnew/whats-new-in-v8-2.md
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
* Update docs/sources/whatsnew/whats-new-in-v8-2.md
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
* Update docs/sources/whatsnew/whats-new-in-v8-2.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/whatsnew/whats-new-in-v8-2.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/whatsnew/whats-new-in-v8-2.md
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
* Update docs/sources/whatsnew/whats-new-in-v8-2.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Update docs/sources/whatsnew/whats-new-in-v8-2.md
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Apply suggestions from code review
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Live: array for Processor, Outputter and Subscriber in channel rule top level (#39677)
* ReleaseNotes: Updated changelog and release notes for 8.1.7 (#40081)
* more
* more
Co-authored-by: sam boyer <sam.boyer@grafana.com>
Co-authored-by: Petros Kolyvas <code@petros.io>
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
Co-authored-by: Alexander Emelin <frvzmb@gmail.com>
Co-authored-by: Grot (@grafanabot) <43478413+grafanabot@users.noreply.github.com>
* Access Control: Add scope type prefix (#40076)
* prefix runtime scopes with key type
* Bump mocha from 7.0.1 to 9.1.2 (#39979)
* Bump mocha from 7.0.1 to 9.1.2
Bumps [mocha](https://github.com/mochajs/mocha) from 7.0.1 to 9.1.2.
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v7.0.1...v9.1.2)
---
updated-dependencies:
- dependency-name: mocha
dependency-type: direct:development
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <support@github.com>
* kick drone
* Remove mocha as it's not used by anything
* kick drone
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
* ReleaseNotes: Updated changelog and release notes for 8.2.0 (#40141)
* ReleaseNotes: Updated changelog and release notes for 8.2.0
* Add link & remove empty line in CHANGELOG
* remove empty line
Co-authored-by: Elfo404 <me@giordanoricci.com>
* Create search filters by interface (#39843)
* Extract search users to a new service
* Fix wire provider
* Fix common_test and remove RouteRegister
* Remove old endpoints
* Fix test
* Create search filters using interfaces
* Move Enterprise filter, rename filter for filters and allow use filters with params
* Each filter has unique key
* Back activeLast30Days filter to OSS
* Fix tests
* Delete unusued param
* Move filters to searchusers service and small refactor
* Fix tests
* Encryption: Refactor securejsondata.SecureJsonData to stop relying on global functions (#38865)
* Encryption: Add support to encrypt/decrypt sjd
* Add datasources.Service as a proxy to datasources db operations
* Encrypt ds.SecureJsonData before calling SQLStore
* Move ds cache code into ds service
* Fix tlsmanager tests
* Fix pluginproxy tests
* Remove some securejsondata.GetEncryptedJsonData usages
* Add pluginsettings.Service as a proxy for plugin settings db operations
* Add AlertNotificationService as a proxy for alert notification db operations
* Remove some securejsondata.GetEncryptedJsonData usages
* Remove more securejsondata.GetEncryptedJsonData usages
* Fix lint errors
* Minor fixes
* Remove encryption global functions usages from ngalert
* Fix lint errors
* Minor fixes
* Minor fixes
* Remove securejsondata.DecryptedValue usage
* Refactor the refactor
* Remove securejsondata.DecryptedValue usage
* Move securejsondata to migrations package
* Move securejsondata to migrations package
* Minor fix
* Fix integration test
* Fix integration tests
* Undo undesired changes
* Fix tests
* Add context.Context into encryption methods
* Fix tests
* Fix tests
* Fix tests
* Trigger CI
* Fix test
* Add names to params of encryption service interface
* Remove bus from CacheServiceImpl
* Add logging
* Add keys to logger
Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
* Add missing key to logger
Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
* Undo changes in markdown files
* Fix formatting
* Add context to secrets service
* Rename decryptSecureJsonData to decryptSecureJsonDataFn
* Name args in GetDecryptedValueFn
* Add template back to NewAlertmanagerNotifier
* Copy GetDecryptedValueFn to ngalert
* Add logging to pluginsettings
* Fix pluginsettings test
Co-authored-by: Tania B <yalyna.ts@gmail.com>
Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
* Admin: Enable extending filters (#39825)
* Setup extensible filters
* Fix test
* Handle filter as array
* Add className
* Abstract getFilters
* Make docs link external
* Use underline for links in tooltips instead of link color
Co-authored-by: Selene <selenepinillos@gmail.com>
* Chore: update latest.json to 8.2 (#40153)
* Doc: Fixed issue 40017 (#40152)
* Added content as suggested by Will
* removed a few extra words.
* Update docs/sources/administration/configuration.md
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
* Update docs/sources/administration/configuration.md
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
* Update docs/sources/administration/configuration.md
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
* docs: Add keepCokkies cofiguration option in datasources (#39890)
Signed-off-by: Vinayak Kadam <kadamvinayak03@gmail.com>
* Grammar issues (#40168)
* Packaging: document systemd net bind capability rpm and deb installations (#40165)
* add systemd net bind capability docs for rpm and deb
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
* Alerting: declare constants for __dashboardUid__ and __panelId__ literals (#39976)
* Babel: Remove unused plugin (#40172)
* removed unused babel plugin
* Update lock file
* Chore(dependencies): Remove puppeteer since we don't use it anywhere (#40137)
* Folders: Prevents deletion of General folder (#40192)
* Datasources: Fix deletion of datasource if plugin cannot be found (#40095)
* fix(pluginsettings): reject with error so datasource plugin loading failures still render ui
* feat(pluginpage): handle plugin loading error
* refactor(datasources): separate out datasource and meta loading so store has info for deletion
* fix(datasourcesettings): introduce loading flag to wait for datasource and meta loading
* test(datasourcesettings): fix failing test
* test(datasources): assert loading status of datasource settings
* test(datasources): update action tests for latest changes
* Replace SAML library with fork (#40149)
* Update saml library to latest
* Use fork of crewjam/saml with fix for certificate chain bug
* CloudMonitoring: Migrate to use backend plugin SDK contracts (#38650)
* Use SDK contracts for cloudmonitoring
* Get build running, tests passing and do some refactoring (#38754)
* fix build+tests and refactor
* remove alerting stuff
* remove unused field
* fix plugin fetch
* end to end
* resp rename
* tidy annotations
* reformatting
* update refID
* reformat imports
* fix styling
* clean up unmarshalling
* uncomment + fix tests
* appease linter
* remove spaces
* remove old cruft
* add check for empty queries
* update tests
* remove pm as dep
* adjust proxy route contract
* fix service loading
* use UNIX val
* fix endpoint + resp
* h@ckz for frontend
* fix resp
* fix interval
* always set custom meta
* remove unused param
* fix labels fetch
* fix linter
* fix test + remove unused field
* apply pr feedback
* fix grafana-auto intervals
* fix tests
* resolve conflicts
* fix bad merge
* fix conflicts
* remove bad logger import
Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
Co-authored-by: Will Browne <will.browne@grafana.com>
* Do codegen and check no-diff of all (non-blacklisted) CUE->TS codegen during CI (#39922)
* Add file blacklist to `grafana-cli cue gen-ts` cmd
* Add CI step checking all cuetsification is done
* Add dummy command to make the next one fail
* Generate drone bits
* Check diff output failure
* Echo list of untracked files, for failure locality
* Move git cleanness checking into script
* Blacklist of cue files is complete and correct
* Remove news panel plugin from cuetsify blacklist
* Dummy commit, check that untracked gen still fail
* Tie off remaining errors
* Re-add barchart to blacklist
* Remove file left around by earlier pipeline
* Commit generated news models.gen.ts
* Include eslint as part of cuetsified output gen
* Update pkg/cmd/grafana-cli/commands/cuetsify_command.go
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
* Update scripts/drone/steps/lib.star
Co-authored-by: Maria Alexandra <239999+axelavargas@users.noreply.github.com>
* Update drone.yml
* Last fix on .drone.yml
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
Co-authored-by: Maria Alexandra <239999+axelavargas@users.noreply.github.com>
* Alerting: add organziation ID to the ngAlert webhook payload (#40189)
* Alerting: add organziation ID to the ngAlert webhook payload
* remove systemcallfilters sections from systemd unit files (#40176)
* Add Headers to http client Options (#40214)
* Docs: Add required library for the image renderer (#40201)
* update permissions scopes and description for role scopes (#40206)
* Chore: Migrate yarn from v1 to v2 (#39082)
* Chore: Migrate yarn from v1 to v2
Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com>
* ReleaseNotes: Updated changelog and release notes for 8.2.0 (#40233)
Co-authored-by: Ryan McKinley <ryantxu@users.noreply.github.com>
Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com>
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
Co-authored-by: Giordano Ricci <me@giordanoricci.com>
Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
Co-authored-by: Alexander Emelin <frvzmb@gmail.com>
Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
Co-authored-by: sam boyer <sam.boyer@grafana.com>
Co-authored-by: Petros Kolyvas <code@petros.io>
Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
Co-authored-by: Grot (@grafanabot) <43478413+grafanabot@users.noreply.github.com>
Co-authored-by: Karl Persson <kalle.persson@grafana.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Selene <selenepinillos@gmail.com>
Co-authored-by: Joan López de la Franca Beltran <5459617+joanlopez@users.noreply.github.com>
Co-authored-by: Tania B <yalyna.ts@gmail.com>
Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
Co-authored-by: Vinayak <vinayak03@users.noreply.github.com>
Co-authored-by: Anne E. Ulrich <aeulrich1997@gmail.com>
Co-authored-by: Kevin Minehart <kmineh0151@gmail.com>
Co-authored-by: Yuriy Tseretyan <yuriy.tseretyan@grafana.com>
Co-authored-by: Torkel Ödegaard <torkel@grafana.org>
Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com>
Co-authored-by: idafurjes <36131195+idafurjes@users.noreply.github.com>
Co-authored-by: Will Browne <will.browne@grafana.com>
Co-authored-by: Maria Alexandra <239999+axelavargas@users.noreply.github.com>
Co-authored-by: Jean-Philippe Quéméner <JohnnyQQQQ@users.noreply.github.com>
Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com>
Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>
Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com>
@ -4,6 +4,7 @@ import { ResourceDimensionConfig, ResourceDimensionMode, ResourceDimensionOption
|
||||
import { InlineField, InlineFieldRow, RadioButtonGroup, Button, Modal, Input } from '@grafana/ui';
|
||||
import { FieldNamePicker } from '../../../../../packages/grafana-ui/src/components/MatchersUI/FieldNamePicker';
|
||||
import { ResourcePicker } from './ResourcePicker';
|
||||
import { ResourceFolderName } from '..';
|
||||
|
||||
const resourceOptions = [
|
||||
{ label: 'Fixed', value: ResourceDimensionMode.Fixed, description: 'Fixed value' },
|
||||
@ -58,21 +59,24 @@ export const ResourceDimensionEditor: FC<
|
||||
}, []);
|
||||
|
||||
const mode = value?.mode ?? ResourceDimensionMode.Fixed;
|
||||
const showSourceRadio = item.settings?.showSourceRadio ?? true;
|
||||
const mediaType = item.settings?.resourceType ?? 'icon';
|
||||
const folderName = item.settings?.folderName ?? ResourceFolderName.Icon;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isOpen && (
|
||||
<Modal isOpen={isOpen} title={`Select ${mediaType}`} onDismiss={() => setOpen(false)} closeOnEscape>
|
||||
<ResourcePicker onChange={onFixedChange} value={value?.fixed} mediaType={mediaType} />
|
||||
<ResourcePicker onChange={onFixedChange} value={value?.fixed} mediaType={mediaType} folderName={folderName} />
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Source" labelWidth={labelWidth} grow={true}>
|
||||
<RadioButtonGroup value={mode} options={resourceOptions} onChange={onModeChange} fullWidth />
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
{showSourceRadio && (
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Source" labelWidth={labelWidth} grow={true}>
|
||||
<RadioButtonGroup value={mode} options={resourceOptions} onChange={onModeChange} fullWidth />
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
)}
|
||||
{mode !== ResourceDimensionMode.Fixed && (
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Field" labelWidth={labelWidth} grow={true}>
|
||||
|
@ -18,11 +18,13 @@ import { css } from '@emotion/css';
|
||||
import { getPublicOrAbsoluteUrl } from '../resource';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { FileElement, GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource';
|
||||
import { ResourceFolderName } from '..';
|
||||
|
||||
interface Props {
|
||||
value?: string; //img/icons/unicons/0-plus.svg
|
||||
onChange: (value?: string) => void;
|
||||
mediaType: 'icon' | 'image';
|
||||
folderName: ResourceFolderName;
|
||||
}
|
||||
|
||||
interface ResourceItem {
|
||||
@ -33,12 +35,13 @@ interface ResourceItem {
|
||||
}
|
||||
|
||||
export function ResourcePicker(props: Props) {
|
||||
const { value, onChange, mediaType } = props;
|
||||
const folders = (mediaType === 'icon' ? ['img/icons/unicons', 'img/icons/iot'] : ['img/bg']).map((v) => ({
|
||||
const { value, onChange, mediaType, folderName } = props;
|
||||
const folders = getFolders(mediaType).map((v) => ({
|
||||
label: v,
|
||||
value: v,
|
||||
}));
|
||||
const folderOfCurrentValue = value ? folders.filter((folder) => value.indexOf(folder.value) > -1)[0] : folders[0];
|
||||
|
||||
const folderOfCurrentValue = value || folderName ? folderIfExists(folders, value ?? folderName) : folders[0];
|
||||
const [currentFolder, setCurrentFolder] = useState<SelectableValue<string>>(folderOfCurrentValue);
|
||||
const [tabs, setTabs] = useState([
|
||||
{ label: 'Select', active: true },
|
||||
@ -169,3 +172,15 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => {
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
const getFolders = (mediaType: 'icon' | 'image') => {
|
||||
if (mediaType === 'icon') {
|
||||
return [ResourceFolderName.Icon, ResourceFolderName.IOT, ResourceFolderName.Marker];
|
||||
} else {
|
||||
return [ResourceFolderName.BG];
|
||||
}
|
||||
};
|
||||
|
||||
const folderIfExists = (folders: Array<{ label: string; value: string }>, path: string) => {
|
||||
return folders.filter((folder) => path.indexOf(folder.value) > -1)[0] ?? folders[0];
|
||||
};
|
||||
|
@ -71,6 +71,8 @@ export interface ColorDimensionConfig extends BaseDimensionConfig<string> {}
|
||||
/** Places that use the value */
|
||||
export interface ResourceDimensionOptions {
|
||||
resourceType: 'icon' | 'image';
|
||||
folderName?: ResourceFolderName;
|
||||
showSourceRadio?: boolean;
|
||||
}
|
||||
|
||||
export enum ResourceDimensionMode {
|
||||
@ -84,3 +86,10 @@ export enum ResourceDimensionMode {
|
||||
export interface ResourceDimensionConfig extends BaseDimensionConfig<string> {
|
||||
mode: ResourceDimensionMode;
|
||||
}
|
||||
|
||||
export enum ResourceFolderName {
|
||||
Icon = 'img/icons/unicons',
|
||||
IOT = 'img/icons/iot',
|
||||
Marker = 'img/icons/marker',
|
||||
BG = 'img/bg',
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import Feature from 'ol/Feature';
|
||||
import { Point } from 'ol/geom';
|
||||
import * as layer from 'ol/layer';
|
||||
import * as source from 'ol/source';
|
||||
import * as style from 'ol/style';
|
||||
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { dataFrameToPoints, getLocationMatchers } from '../../utils/location';
|
||||
@ -19,11 +20,15 @@ import {
|
||||
ScaleDimensionConfig,
|
||||
getScaledDimension,
|
||||
getColorDimension,
|
||||
ResourceDimensionConfig,
|
||||
ResourceDimensionMode,
|
||||
ResourceFolderName,
|
||||
getPublicOrAbsoluteUrl,
|
||||
} from 'app/features/dimensions';
|
||||
import { ScaleDimensionEditor, ColorDimensionEditor } from 'app/features/dimensions/editors';
|
||||
import { ScaleDimensionEditor, ColorDimensionEditor, ResourceDimensionEditor } from 'app/features/dimensions/editors';
|
||||
import { ObservablePropsWrapper } from '../../components/ObservablePropsWrapper';
|
||||
import { MarkersLegend, MarkersLegendProps } from './MarkersLegend';
|
||||
import { circleMarker, markerMakers } from '../../utils/regularShapes';
|
||||
import { StyleMaker, getMarkerFromPath, MarkerShapePath } from '../../utils/regularShapes';
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
|
||||
// Configuration options for Circle overlays
|
||||
@ -31,13 +36,15 @@ export interface MarkersConfig {
|
||||
size: ScaleDimensionConfig;
|
||||
color: ColorDimensionConfig;
|
||||
fillOpacity: number;
|
||||
shape?: string;
|
||||
showLegend?: boolean;
|
||||
markerSymbol: ResourceDimensionConfig;
|
||||
}
|
||||
|
||||
const DEFAULT_SIZE = 5;
|
||||
|
||||
const defaultOptions: MarkersConfig = {
|
||||
size: {
|
||||
fixed: 5,
|
||||
fixed: DEFAULT_SIZE,
|
||||
min: 2,
|
||||
max: 15,
|
||||
},
|
||||
@ -45,8 +52,11 @@ const defaultOptions: MarkersConfig = {
|
||||
fixed: 'dark-green', // picked from theme
|
||||
},
|
||||
fillOpacity: 0.4,
|
||||
shape: 'circle',
|
||||
showLegend: true,
|
||||
markerSymbol: {
|
||||
mode: ResourceDimensionMode.Fixed,
|
||||
fixed: MarkerShapePath.Circle,
|
||||
},
|
||||
};
|
||||
|
||||
export const MARKERS_LAYER_ID = 'markers';
|
||||
@ -88,7 +98,6 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
|
||||
if (config.showLegend) {
|
||||
legend = <ObservablePropsWrapper watch={legendProps} initialSubProps={{}} child={MarkersLegend} />;
|
||||
}
|
||||
const shape = markerMakers.getIfExists(config.shape) ?? circleMarker;
|
||||
|
||||
return {
|
||||
init: () => vectorLayer,
|
||||
@ -98,6 +107,24 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
|
||||
return; // ignore empty
|
||||
}
|
||||
|
||||
const markerPath =
|
||||
getPublicOrAbsoluteUrl(config.markerSymbol?.fixed) ?? getPublicOrAbsoluteUrl(MarkerShapePath.Circle);
|
||||
|
||||
const marker = getMarkerFromPath(config.markerSymbol?.fixed);
|
||||
|
||||
const makeIconStyle = (color: string, fillColor: string, radius: number) => {
|
||||
return new style.Style({
|
||||
image: new style.Icon({
|
||||
src: markerPath,
|
||||
color,
|
||||
// opacity,
|
||||
scale: (DEFAULT_SIZE + radius) / 100,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
const shape: StyleMaker = marker?.make ?? makeIconStyle;
|
||||
|
||||
const features: Feature<Point>[] = [];
|
||||
|
||||
for (const frame of data.series) {
|
||||
@ -126,8 +153,7 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
|
||||
frame,
|
||||
rowIndex: i,
|
||||
});
|
||||
|
||||
dot.setStyle(shape!.make(color, fillColor, radius));
|
||||
dot.setStyle(shape(color, fillColor, radius));
|
||||
features.push(dot);
|
||||
}
|
||||
|
||||
@ -150,17 +176,6 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
|
||||
// Marker overlay options
|
||||
registerOptionsUI: (builder) => {
|
||||
builder
|
||||
.addCustomEditor({
|
||||
id: 'config.color',
|
||||
path: 'config.color',
|
||||
name: 'Marker Color',
|
||||
editor: ColorDimensionEditor,
|
||||
settings: {},
|
||||
defaultValue: {
|
||||
// Configured values
|
||||
fixed: 'grey',
|
||||
},
|
||||
})
|
||||
.addCustomEditor({
|
||||
id: 'config.size',
|
||||
path: 'config.size',
|
||||
@ -172,18 +187,33 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
|
||||
},
|
||||
defaultValue: {
|
||||
// Configured values
|
||||
fixed: 5,
|
||||
fixed: DEFAULT_SIZE,
|
||||
min: 1,
|
||||
max: 20,
|
||||
},
|
||||
})
|
||||
.addSelect({
|
||||
path: 'config.shape',
|
||||
name: 'Marker Shape',
|
||||
.addCustomEditor({
|
||||
id: 'config.markerSymbol',
|
||||
path: 'config.markerSymbol',
|
||||
name: 'Marker Symbol',
|
||||
editor: ResourceDimensionEditor,
|
||||
defaultValue: defaultOptions.markerSymbol,
|
||||
settings: {
|
||||
options: markerMakers.selectOptions().options,
|
||||
resourceType: 'icon',
|
||||
showSourceRadio: false,
|
||||
folderName: ResourceFolderName.Marker,
|
||||
},
|
||||
})
|
||||
.addCustomEditor({
|
||||
id: 'config.color',
|
||||
path: 'config.color',
|
||||
name: 'Marker Color',
|
||||
editor: ColorDimensionEditor,
|
||||
settings: {},
|
||||
defaultValue: {
|
||||
// Configured values
|
||||
fixed: 'grey',
|
||||
},
|
||||
defaultValue: 'circle',
|
||||
})
|
||||
.addSliderInput({
|
||||
path: 'config.fillOpacity',
|
||||
@ -194,7 +224,6 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
|
||||
max: 1,
|
||||
step: 0.1,
|
||||
},
|
||||
showIf: (cfg) => markerMakers.getIfExists((cfg as any).config?.shape)?.hasFill,
|
||||
})
|
||||
.addBooleanSwitch({
|
||||
path: 'config.showLegend',
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { PanelModel, FieldConfigSource } from '@grafana/data';
|
||||
import { mapPanelChangedHandler } from './migrations';
|
||||
|
||||
import { mapMigrationHandler, mapPanelChangedHandler } from './migrations';
|
||||
describe('Worldmap Migrations', () => {
|
||||
let prevFieldConfig: FieldConfigSource;
|
||||
|
||||
@ -106,3 +105,169 @@ const simpleWorldmapConfig = {
|
||||
valueName: 'total',
|
||||
datasource: null,
|
||||
};
|
||||
|
||||
describe('geomap migrations', () => {
|
||||
it('updates marker', () => {
|
||||
const panel = {
|
||||
id: 2,
|
||||
gridPos: {
|
||||
h: 9,
|
||||
w: 12,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
type: 'geomap',
|
||||
title: 'Panel Title',
|
||||
fieldConfig: {
|
||||
defaults: {
|
||||
thresholds: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 80,
|
||||
},
|
||||
],
|
||||
},
|
||||
mappings: [],
|
||||
color: {
|
||||
mode: 'thresholds',
|
||||
},
|
||||
},
|
||||
overrides: [],
|
||||
},
|
||||
options: {
|
||||
view: {
|
||||
id: 'zero',
|
||||
lat: 0,
|
||||
lon: 0,
|
||||
zoom: 1,
|
||||
},
|
||||
basemap: {
|
||||
type: 'default',
|
||||
config: {},
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
config: {
|
||||
color: {
|
||||
fixed: 'dark-green',
|
||||
},
|
||||
fillOpacity: 0.4,
|
||||
markerSymbol: {
|
||||
fixed: '',
|
||||
mode: 'fixed',
|
||||
},
|
||||
shape: 'circle',
|
||||
showLegend: true,
|
||||
size: {
|
||||
fixed: 5,
|
||||
max: 15,
|
||||
min: 2,
|
||||
},
|
||||
},
|
||||
location: {
|
||||
mode: 'auto',
|
||||
},
|
||||
type: 'markers',
|
||||
},
|
||||
],
|
||||
controls: {
|
||||
showZoom: true,
|
||||
mouseWheelZoom: true,
|
||||
showAttribution: true,
|
||||
showScale: false,
|
||||
showDebug: false,
|
||||
},
|
||||
},
|
||||
pluginVersion: '8.3.0-pre',
|
||||
datasource: null,
|
||||
} as PanelModel;
|
||||
panel.options = mapMigrationHandler(panel);
|
||||
|
||||
expect(panel).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"datasource": null,
|
||||
"fieldConfig": Object {
|
||||
"defaults": Object {
|
||||
"color": Object {
|
||||
"mode": "thresholds",
|
||||
},
|
||||
"mappings": Array [],
|
||||
"thresholds": Object {
|
||||
"mode": "absolute",
|
||||
"steps": Array [
|
||||
Object {
|
||||
"color": "green",
|
||||
"value": null,
|
||||
},
|
||||
Object {
|
||||
"color": "red",
|
||||
"value": 80,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"overrides": Array [],
|
||||
},
|
||||
"gridPos": Object {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
"id": 2,
|
||||
"options": Object {
|
||||
"basemap": Object {
|
||||
"config": Object {},
|
||||
"type": "default",
|
||||
},
|
||||
"controls": Object {
|
||||
"mouseWheelZoom": true,
|
||||
"showAttribution": true,
|
||||
"showDebug": false,
|
||||
"showScale": false,
|
||||
"showZoom": true,
|
||||
},
|
||||
"layers": Array [
|
||||
Object {
|
||||
"config": Object {
|
||||
"color": Object {
|
||||
"fixed": "dark-green",
|
||||
},
|
||||
"fillOpacity": 0.4,
|
||||
"markerSymbol": Object {
|
||||
"fixed": "img/icons/marker/circle.svg",
|
||||
"mode": "fixed",
|
||||
},
|
||||
"showLegend": true,
|
||||
"size": Object {
|
||||
"fixed": 5,
|
||||
"max": 15,
|
||||
"min": 2,
|
||||
},
|
||||
},
|
||||
"location": Object {
|
||||
"mode": "auto",
|
||||
},
|
||||
"type": "markers",
|
||||
},
|
||||
],
|
||||
"view": Object {
|
||||
"id": "zero",
|
||||
"lat": 0,
|
||||
"lon": 0,
|
||||
"zoom": 1,
|
||||
},
|
||||
},
|
||||
"pluginVersion": "8.3.0-pre",
|
||||
"title": "Panel Title",
|
||||
"type": "geomap",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { FieldConfigSource, PanelTypeChangedHandler, Threshold, ThresholdsMode } from '@grafana/data';
|
||||
import { FieldConfigSource, PanelModel, PanelTypeChangedHandler, Threshold, ThresholdsMode } from '@grafana/data';
|
||||
import { GeomapPanelOptions } from './types';
|
||||
import { markerMakers } from './utils/regularShapes';
|
||||
import { MapCenterID } from './view';
|
||||
|
||||
/**
|
||||
@ -97,3 +98,27 @@ function asNumber(v: any): number | undefined {
|
||||
const num = +v;
|
||||
return isNaN(num) ? undefined : num;
|
||||
}
|
||||
|
||||
export const mapMigrationHandler = (panel: PanelModel): Partial<GeomapPanelOptions> => {
|
||||
const pluginVersion = panel?.pluginVersion;
|
||||
if (pluginVersion?.startsWith('8.1') || pluginVersion?.startsWith('8.2') || pluginVersion?.startsWith('8.3')) {
|
||||
if (panel.options?.layers?.length > 0) {
|
||||
const layer = panel.options.layers[0];
|
||||
if (layer?.type === 'markers') {
|
||||
const shape = layer?.config?.shape;
|
||||
if (shape) {
|
||||
const marker = markerMakers.getIfExists(shape);
|
||||
if (marker?.aliasIds && marker.aliasIds?.length > 0) {
|
||||
layer.config.markerSymbol = {
|
||||
fixed: marker.aliasIds[0],
|
||||
mode: 'fixed',
|
||||
};
|
||||
delete layer.config.shape;
|
||||
}
|
||||
return { ...panel.options, layers: Object.assign([], ...panel.options.layers, { 0: layer }) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return panel.options;
|
||||
};
|
||||
|
@ -3,13 +3,14 @@ import { PanelPlugin } from '@grafana/data';
|
||||
import { GeomapPanel } from './GeomapPanel';
|
||||
import { MapViewEditor } from './editor/MapViewEditor';
|
||||
import { defaultView, GeomapPanelOptions } from './types';
|
||||
import { mapPanelChangedHandler } from './migrations';
|
||||
import { mapPanelChangedHandler, mapMigrationHandler } from './migrations';
|
||||
import { getLayerEditor } from './editor/layerEditor';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
export const plugin = new PanelPlugin<GeomapPanelOptions>(GeomapPanel)
|
||||
.setNoPadding()
|
||||
.setPanelChangeHandler(mapPanelChangedHandler)
|
||||
.setMigrationHandler(mapMigrationHandler)
|
||||
.useFieldConfig()
|
||||
.setPanelOptions((builder, context) => {
|
||||
let category = ['Map view'];
|
||||
|
@ -1,15 +1,38 @@
|
||||
import { Fill, RegularShape, Stroke, Style, Circle } from 'ol/style';
|
||||
import { Registry, RegistryItem } from '@grafana/data';
|
||||
|
||||
interface MarkerMaker extends RegistryItem {
|
||||
make: (color: string, fillColor: string, radius: number) => Style;
|
||||
export type StyleMaker = (color: string, fillColor: string, radius: number, markerPath?: string) => Style;
|
||||
|
||||
export interface MarkerMaker extends RegistryItem {
|
||||
// path to icon that will be shown (but then replaced)
|
||||
aliasIds: string[];
|
||||
make: StyleMaker;
|
||||
hasFill: boolean;
|
||||
}
|
||||
|
||||
export enum RegularShapeId {
|
||||
Circle = 'circle',
|
||||
Square = 'square',
|
||||
Triangle = 'triangle',
|
||||
Star = 'star',
|
||||
Cross = 'cross',
|
||||
X = 'x',
|
||||
}
|
||||
|
||||
export enum MarkerShapePath {
|
||||
Circle = 'img/icons/marker/circle.svg',
|
||||
Square = 'img/icons/marker/square.svg',
|
||||
Triangle = 'img/icons/marker/triangle.svg',
|
||||
Star = 'img/icons/marker/star.svg',
|
||||
Cross = 'img/icons/marker/cross.svg',
|
||||
X = 'img/icons/marker/x-mark.svg',
|
||||
}
|
||||
|
||||
export const circleMarker: MarkerMaker = {
|
||||
id: 'circle',
|
||||
id: RegularShapeId.Circle,
|
||||
name: 'Circle',
|
||||
hasFill: true,
|
||||
aliasIds: [MarkerShapePath.Circle],
|
||||
make: (color: string, fillColor: string, radius: number) => {
|
||||
return new Style({
|
||||
image: new Circle({
|
||||
@ -21,12 +44,13 @@ export const circleMarker: MarkerMaker = {
|
||||
},
|
||||
};
|
||||
|
||||
export const markerMakers = new Registry<MarkerMaker>(() => [
|
||||
const makers: MarkerMaker[] = [
|
||||
circleMarker,
|
||||
{
|
||||
id: 'square',
|
||||
id: RegularShapeId.Square,
|
||||
name: 'Square',
|
||||
hasFill: true,
|
||||
aliasIds: [MarkerShapePath.Square],
|
||||
make: (color: string, fillColor: string, radius: number) => {
|
||||
return new Style({
|
||||
image: new RegularShape({
|
||||
@ -40,9 +64,10 @@ export const markerMakers = new Registry<MarkerMaker>(() => [
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'triangle',
|
||||
id: RegularShapeId.Triangle,
|
||||
name: 'Triangle',
|
||||
hasFill: true,
|
||||
aliasIds: [MarkerShapePath.Triangle],
|
||||
make: (color: string, fillColor: string, radius: number) => {
|
||||
return new Style({
|
||||
image: new RegularShape({
|
||||
@ -57,9 +82,10 @@ export const markerMakers = new Registry<MarkerMaker>(() => [
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'star',
|
||||
id: RegularShapeId.Star,
|
||||
name: 'Star',
|
||||
hasFill: true,
|
||||
aliasIds: [MarkerShapePath.Star],
|
||||
make: (color: string, fillColor: string, radius: number) => {
|
||||
return new Style({
|
||||
image: new RegularShape({
|
||||
@ -74,9 +100,10 @@ export const markerMakers = new Registry<MarkerMaker>(() => [
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cross',
|
||||
id: RegularShapeId.Cross,
|
||||
name: 'Cross',
|
||||
hasFill: false,
|
||||
aliasIds: [MarkerShapePath.Cross],
|
||||
make: (color: string, fillColor: string, radius: number) => {
|
||||
return new Style({
|
||||
image: new RegularShape({
|
||||
@ -91,9 +118,10 @@ export const markerMakers = new Registry<MarkerMaker>(() => [
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'x',
|
||||
id: RegularShapeId.X,
|
||||
name: 'X',
|
||||
hasFill: false,
|
||||
aliasIds: [MarkerShapePath.X],
|
||||
make: (color: string, fillColor: string, radius: number) => {
|
||||
return new Style({
|
||||
image: new RegularShape({
|
||||
@ -107,4 +135,15 @@ export const markerMakers = new Registry<MarkerMaker>(() => [
|
||||
});
|
||||
},
|
||||
},
|
||||
]);
|
||||
];
|
||||
|
||||
export const markerMakers = new Registry<MarkerMaker>(() => makers);
|
||||
|
||||
export const getMarkerFromPath = (svgPath: string): MarkerMaker | undefined => {
|
||||
for (const [key, val] of Object.entries(MarkerShapePath)) {
|
||||
if (val === svgPath) {
|
||||
return markerMakers.getIfExists(key);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
1
public/img/icons/marker/circle.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/></svg>
|
After Width: | Height: | Size: 98 B |
1
public/img/icons/marker/cross.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19,11H13V5a1,1,0,0,0-2,0v6H5a1,1,0,0,0,0,2h6v6a1,1,0,0,0,2,0V13h6a1,1,0,0,0,0-2Z"/></svg>
|
After Width: | Height: | Size: 159 B |
1
public/img/icons/marker/plane.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#fff" d="M21.75,12a1,1,0,0,0-.55-.89L15.08,8.05v-4a3.08,3.08,0,1,0-6.16,0v4L2.8,11.11a1,1,0,0,0-.55.89v3.33a1,1,0,0,0,.43.83,1,1,0,0,0,.92.11l5.32-2V18l-1.82.6a1,1,0,0,0-.68.95V22a1,1,0,0,0,.3.71,1,1,0,0,0,.7.29h9.17a1,1,0,0,0,1-1V19.5a1,1,0,0,0-.68-.95L15.08,18V14.28l5.32,2a1,1,0,0,0,.92-.11,1,1,0,0,0,.43-.83Zm-7.31-.1a1,1,0,0,0-.93.11,1,1,0,0,0-.43.82v5.84a1,1,0,0,0,.69.95l1.81.6V21H8.41v-.78l1.81-.6a1,1,0,0,0,.69-.95V12.83a1,1,0,0,0-.43-.82,1,1,0,0,0-.93-.11l-5.31,2V12.62l6.11-3.06a1,1,0,0,0,.56-.89V4.08a1.08,1.08,0,1,1,2.16,0V8.67a1,1,0,0,0,.56.89l6.11,3.06v1.27Z"/></svg>
|
After Width: | Height: | Size: 654 B |
1
public/img/icons/marker/square.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect width="85%" height="85%" x="2" y="2" rx="5"/></svg>
|
After Width: | Height: | Size: 117 B |
1
public/img/icons/marker/star.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M22,9.67A1,1,0,0,0,21.14,9l-5.69-.83L12.9,3a1,1,0,0,0-1.8,0L8.55,8.16,2.86,9a1,1,0,0,0-.81.68,1,1,0,0,0,.25,1l4.13,4-1,5.68a1,1,0,0,0,.4,1,1,1,0,0,0,1.05.07L12,18.76l5.1,2.68a.93.93,0,0,0,.46.12,1,1,0,0,0,.59-.19,1,1,0,0,0,.4-1l-1-5.68,4.13-4A1,1,0,0,0,22,9.67Zm-6.15,4a1,1,0,0,0-.29.89l.72,4.19-3.76-2a1,1,0,0,0-.94,0l-3.76,2,.72-4.19a1,1,0,0,0-.29-.89l-3-3,4.21-.61a1,1,0,0,0,.76-.55L12,5.7l1.88,3.82a1,1,0,0,0,.76.55l4.21.61Z"/></svg>
|
After Width: | Height: | Size: 506 B |
1
public/img/icons/marker/triangle.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M21.87,19.29l-9-15.58a1,1,0,0,0-1.74,0l-9,15.58a1,1,0,0,0,0,1,1,1,0,0,0,.87.5H21a1,1,0,0,0,.87-.5A1,1,0,0,0,21.87,19.29Zm-17.14-.5L12,6.21l7.27,12.58Z"/></svg>
|
After Width: | Height: | Size: 228 B |
1
public/img/icons/marker/x-mark.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13.41,12l6.3-6.29a1,1,0,1,0-1.42-1.42L12,10.59,5.71,4.29A1,1,0,0,0,4.29,5.71L10.59,12l-6.3,6.29a1,1,0,0,0,0,1.42,1,1,0,0,0,1.42,0L12,13.41l6.29,6.3a1,1,0,0,0,1.42,0,1,1,0,0,0,0-1.42Z"/></svg>
|
After Width: | Height: | Size: 261 B |