From 52ae818c1bf4c0b2bf0e67f408b48a9b8f1593ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Zbytovsk=C3=BD?= Date: Wed, 9 Oct 2024 12:28:46 +0200 Subject: [PATCH] EditDialog: add FeatureTypeSelect based on id Presets --- .../EditDialog/EditContent/ComboSearchBox.tsx | 107 +++++++++++ .../EditDialog/EditContent/EditContent.tsx | 2 +- .../EditContent/FeatureTypeSelect.tsx | 175 ++++++++++-------- .../FeaturePanel/EditDialog/EditContext.tsx | 7 + src/components/SearchBox/options/preset.tsx | 4 +- src/components/utils/Maki.tsx | 8 +- .../tagging/__tests__/idTaggingScheme.test.ts | 4 +- src/services/tagging/data.ts | 21 ++- src/services/tagging/fields.ts | 83 +++++---- src/services/tagging/idTaggingScheme.ts | 27 ++- src/services/tagging/ourPresets.ts | 3 +- src/services/tagging/presets.ts | 4 +- src/services/tagging/types/Fields.ts | 12 +- src/services/tagging/types/Presets.ts | 8 +- src/services/tagging/utils.ts | 1 + 15 files changed, 312 insertions(+), 154 deletions(-) create mode 100644 src/components/FeaturePanel/EditDialog/EditContent/ComboSearchBox.tsx create mode 100644 src/services/tagging/utils.ts diff --git a/src/components/FeaturePanel/EditDialog/EditContent/ComboSearchBox.tsx b/src/components/FeaturePanel/EditDialog/EditContent/ComboSearchBox.tsx new file mode 100644 index 000000000..7c9272124 --- /dev/null +++ b/src/components/FeaturePanel/EditDialog/EditContent/ComboSearchBox.tsx @@ -0,0 +1,107 @@ +import React, { useMemo, useState } from 'react'; +import { InputBase, ListSubheader, MenuItem, Select } from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import styled from '@emotion/styled'; +import Maki from '../../../utils/Maki'; +import { TranslatedPreset } from './FeatureTypeSelect'; +import { Setter } from '../../../../types'; +import { Preset } from '../../../../services/tagging/types/Presets'; + +// https://stackoverflow.com/a/70918883/671880 + +const containsText = (text, searchText) => + text.toLowerCase().indexOf(searchText.toLowerCase()) > -1; + +const StyledListSubheader = styled(ListSubheader)` + display: flex; + align-items: center; + border-bottom: 1px solid rgba(0, 0, 0, 0.12); + padding: 6px 13px; + & :first-child { + margin-right: 11px; + } +`; + +const emptyOptions = [ + 'amenity/cafe', + 'amenity/restaurant', + 'amenity/fast_food', + 'amenity/bar', + 'shop', + 'leisure/park', + 'amenity/place_of_worship', + 'climbing/route_bottom', + 'climbing/route', + 'climbing/crag', + // 'climbing/area', +]; + +const renderOption = (option) => + option && ( + <> + + + {option.name} + + ); + +export const ComboSearchBox = ({ + value, + setValue, + options, +}: { + value: Preset; + setValue: Setter; + options: TranslatedPreset[]; +}) => { + const [searchText, setSearchText] = useState(''); + const displayedOptions = useMemo( + () => + searchText.length + ? options.filter((option) => containsText(option.name, searchText)) + : emptyOptions.map((presetKey) => + options.find((preset) => preset.presetKey === presetKey), + ), + [options, searchText], + ); + + return ( + + ); +}; diff --git a/src/components/FeaturePanel/EditDialog/EditContent/EditContent.tsx b/src/components/FeaturePanel/EditDialog/EditContent/EditContent.tsx index 257b2100a..f9a3dd3b3 100644 --- a/src/components/FeaturePanel/EditDialog/EditContent/EditContent.tsx +++ b/src/components/FeaturePanel/EditDialog/EditContent/EditContent.tsx @@ -13,7 +13,7 @@ export const EditContent = () => ( <>
e.preventDefault()}> - {false && } + diff --git a/src/components/FeaturePanel/EditDialog/EditContent/FeatureTypeSelect.tsx b/src/components/FeaturePanel/EditDialog/EditContent/FeatureTypeSelect.tsx index 7ad316aac..dddfef4ef 100644 --- a/src/components/FeaturePanel/EditDialog/EditContent/FeatureTypeSelect.tsx +++ b/src/components/FeaturePanel/EditDialog/EditContent/FeatureTypeSelect.tsx @@ -1,99 +1,112 @@ import React, { useEffect, useState } from 'react'; -import { Box, MenuItem, TextField, Typography } from '@mui/material'; -import Maki from '../../../utils/Maki'; -import { fetchJson } from '../../../../services/fetch'; -import { intl, t } from '../../../../services/intl'; +import { Box, Typography } from '@mui/material'; +import styled from '@emotion/styled'; import { getPoiClass } from '../../../../services/getPoiClass'; -import { trimText } from '../../../helpers'; +import { allPresets } from '../../../../services/tagging/data'; +import { + fetchSchemaTranslations, + getPresetTermsTranslation, + getPresetTranslation, +} from '../../../../services/tagging/translations'; +import { useFeatureContext } from '../../../utils/FeatureContext'; +import { ComboSearchBox } from './ComboSearchBox'; import { useEditContext } from '../EditContext'; +import { Preset } from '../../../../services/tagging/types/Presets'; -/* -https://taginfo.openstreetmap.org/taginfo/apidoc#api_4_key_values -{ - "value": "parking", - "count": 3897865, - "fraction": 0.208, - "in_wiki": true, - "description": "Parkoviště pro auta", - "desclang": "cs", - "descdir": "ltr" -}, -*/ - -type TagInfoResponse = { - url: string; - data_until: string; - page: number; - rp: number; - total: number; - data: { - value: string; - count: number; - fraction: number; - in_wiki: boolean; - description?: string; - desclang: string; - descdir: string; - }[]; +export type TranslatedPreset = Preset & { + name: string; + icon: string; }; -const getData = async () => { - const body = await fetchJson( - `https://taginfo.openstreetmap.org/api/4/key/values?key=amenity&filter=all&lang=${intl.lang}&sortname=count_all&sortorder=desc&page=1&rp=200&qtype=value`, // &format=json_pretty - ); - const key = 'amenity'; - return body.data - .map((item) => ({ - ...item, - key, - tag: `${key}=${item.value}`, - ...getPoiClass({ [key]: item.value }), - })) - .sort((a, b) => a.subclass.localeCompare(b.subclass)); +type PresetCacheItem = Preset & { name: string; icon: string; terms: string[] }; +type PresetsCache = PresetCacheItem[]; + +let presetsCache: PresetsCache | null = null; +const getPresets = async (): Promise => { + if (presetsCache) { + return presetsCache; + } + + await fetchSchemaTranslations(); + + // resolve symlinks to {landuse...} etc + presetsCache = Object.values(allPresets) + .filter(({ searchable }) => searchable === undefined || searchable) + .filter(({ geometry }) => geometry.includes('point')) + .filter(({ locationSet }) => !locationSet?.include) + .filter(({ tags }) => Object.keys(tags).length > 0) + // .map(({ name, presetKey, tags, terms }) => { + // const tagsAsStrings = Object.entries(tags).map(([k, v]) => `${k}=${v}`); + // return { + // key: presetKey, + // name: getPresetTranslation(presetKey) ?? name ?? 'x', + // tags, + // tagsAsOneString: tagsAsStrings.join(', '), + // texts: [ + // ...(getPresetTermsTranslation(presetKey) ?? terms ?? 'x').split(','), + // ...tagsAsStrings, + // presetKey, + // ], + // icon: getPoiClass(tags).class, + // }; + // }); + .map((preset) => { + return { + ...preset, + name: getPresetTranslation(preset.presetKey) ?? preset.presetKey, + icon: getPoiClass(preset.tags).class, + terms: getPresetTermsTranslation(preset.presetKey) ?? preset.terms, + }; + }); + + return presetsCache; }; -const renderValue = (value) => ( - <> - {value.tag} - -); +const Row = styled(Box)` + display: flex; + align-items: center; + + //first child + & > *:first-child { + min-width: 44px; + margin-right: 1em; + } + // second child + & > *:nth-child(2) { + width: 100%; + } +`; export const FeatureTypeSelect = () => { - const { - tags: { typeTag, setTypeTag }, - } = useEditContext(); + const { preset, setPreset } = useEditContext(); const [options, setOptions] = useState([]); + const { feature } = useFeatureContext(); + useEffect(() => { - getData().then(setOptions); - }, []); + (async () => { + const presets = await getPresets(); + setOptions(presets); + setPreset( + presets.find( + (option: PresetCacheItem) => + option.presetKey === feature.schema?.presetKey, + ), + ); + })(); + }, [feature.schema?.presetKey, setPreset]); - const onChange = (event) => setTypeTag(event.target.value); + if (options.length === 0) { + return null; + } return ( - - - - {options.map((item) => ( - - - - {item.subclass} -
- - {trimText(item.description, 100)} - -
-
- ))} -
-
+ + + Typ: + + + + ); }; diff --git a/src/components/FeaturePanel/EditDialog/EditContext.tsx b/src/components/FeaturePanel/EditDialog/EditContext.tsx index b62ea1764..1ee7bccae 100644 --- a/src/components/FeaturePanel/EditDialog/EditContext.tsx +++ b/src/components/FeaturePanel/EditDialog/EditContext.tsx @@ -1,6 +1,7 @@ import React, { createContext, useContext, useState } from 'react'; import { useToggleState } from '../../helpers'; import { Feature, FeatureTags, SuccessInfo } from '../../../services/types'; +import { Preset } from '../../../services/tagging/types/Presets'; export type TypeTag = { key: string; value: string } | undefined; type EditContextType = { @@ -12,6 +13,8 @@ type EditContextType = { setLocation: (s: string) => void; comment: string; setComment: (s: string) => void; + preset: Preset | undefined; + setPreset: (p: Preset | undefined) => void; tags: { typeTag: TypeTag; setTypeTag: (typeTag: TypeTag) => void; @@ -45,6 +48,8 @@ export const EditContextProvider = ({ feature, children }: Props) => { const [location, setLocation] = useState(''); const [comment, setComment] = useState(''); + const [preset, setPreset] = useState(); + const [typeTag, setTypeTag] = useState(); const [tags, setTag] = useTagsState(feature.tags); const [tmpNewTag, setTmpNewTag] = useState({}); @@ -59,6 +64,8 @@ export const EditContextProvider = ({ feature, children }: Props) => { setLocation, comment, setComment, + preset, + setPreset, tags: { typeTag, setTypeTag, diff --git a/src/components/SearchBox/options/preset.tsx b/src/components/SearchBox/options/preset.tsx index 6ce12985d..3cf8b8762 100644 --- a/src/components/SearchBox/options/preset.tsx +++ b/src/components/SearchBox/options/preset.tsx @@ -11,7 +11,7 @@ import { getPresetTermsTranslation, getPresetTranslation, } from '../../../services/tagging/translations'; -import { presets } from '../../../services/tagging/data'; +import { allPresets } from '../../../services/tagging/data'; import { PresetOption } from '../types'; import { t } from '../../../services/intl'; import { highlightText, IconPart } from '../utils'; @@ -31,7 +31,7 @@ const getPresetsForSearch = async () => { await fetchSchemaTranslations(); // resolve symlinks to {landuse...} etc - presetsForSearch = Object.values(presets) + presetsForSearch = Object.values(allPresets) .filter(({ searchable }) => searchable === undefined || searchable) .filter(({ locationSet }) => !locationSet?.include) .filter(({ tags }) => Object.keys(tags).length > 0) diff --git a/src/components/utils/Maki.tsx b/src/components/utils/Maki.tsx index de77cb71b..005eac2bd 100644 --- a/src/components/utils/Maki.tsx +++ b/src/components/utils/Maki.tsx @@ -1,6 +1,7 @@ import React from 'react'; import styled from '@emotion/styled'; import { icons } from '../../assets/icons'; +import { useUserThemeContext } from '../../helpers/theme'; const MakiImg = styled.img<{ $invert: boolean }>` line-height: 14px; @@ -16,6 +17,7 @@ type MakiProps = { style?: React.CSSProperties | undefined; size?: number; middle?: boolean | undefined; + themed?: boolean; }; const Maki = ({ @@ -25,7 +27,11 @@ const Maki = ({ style = undefined, size = 11, middle = undefined, + themed = false, }: MakiProps) => { + const { currentTheme } = useUserThemeContext(); + const invertFinal = themed ? currentTheme === 'dark' : invert; + const icon = icons.includes(ico) ? ico : 'information'; // console.log(icon, ' was: ',ico) return ( @@ -33,7 +39,7 @@ const Maki = ({ src={`/icons/${icon}_11.svg`} alt={ico} title={title ?? ico} - $invert={invert} + $invert={invertFinal} style={{ ...style, verticalAlign: middle ? 'middle' : undefined }} width={size} height={size} diff --git a/src/services/tagging/__tests__/idTaggingScheme.test.ts b/src/services/tagging/__tests__/idTaggingScheme.test.ts index 1480783e3..189df43b5 100644 --- a/src/services/tagging/__tests__/idTaggingScheme.test.ts +++ b/src/services/tagging/__tests__/idTaggingScheme.test.ts @@ -3,7 +3,7 @@ import { getSchemaForFeature } from '../idTaggingScheme'; import { Feature } from '../../types'; import { mockSchemaTranslations } from '../translations'; import { intl } from '../../intl'; -import { computeAllFieldKeys } from '../fields'; +import { getFieldKeys } from '../fields'; intl.lang = 'en'; @@ -72,7 +72,7 @@ describe('idTaggingScheme', () => { } as unknown as Feature; const schema = getSchemaForFeature(featureWithTemplate); - const computedAllFieldKeys = computeAllFieldKeys(schema.preset); + const computedAllFieldKeys = getFieldKeys(schema.preset); expect(computedAllFieldKeys).toEqual([ 'amenity', diff --git a/src/services/tagging/data.ts b/src/services/tagging/data.ts index e223d26e2..228f4f00b 100644 --- a/src/services/tagging/data.ts +++ b/src/services/tagging/data.ts @@ -1,22 +1,25 @@ import fieldsJson from '@openstreetmap/id-tagging-schema/dist/fields.json'; import presetsJson from '@openstreetmap/id-tagging-schema/dist/presets.json'; -import { Fields } from './types/Fields'; +import { Fields, RawFields } from './types/Fields'; import { Presets } from './types/Presets'; import { publishDbgObject } from '../../utils'; import { ourFields, ourPresets } from './ourPresets'; -export const fields = { ...fieldsJson, ...ourFields } as unknown as Fields; -Object.keys(fields).forEach((fieldKey) => { - fields[fieldKey].fieldKey = fieldKey; +export const allFields = { ...fieldsJson, ...ourFields } as unknown as Fields; +Object.keys(allFields).forEach((fieldKey) => { + allFields[fieldKey].fieldKey = fieldKey; }); -export const presets = { ...presetsJson, ...ourPresets } as unknown as Presets; -Object.keys(presets).forEach((presetKey) => { - presets[presetKey].presetKey = presetKey; +export const allPresets = { + ...presetsJson, + ...ourPresets, +} as unknown as Presets; +Object.keys(allPresets).forEach((presetKey) => { + allPresets[presetKey].presetKey = presetKey; }); -publishDbgObject('presets', presets); -publishDbgObject('fields', fields); +publishDbgObject('allPresets', allPresets); +publishDbgObject('allFields', allFields); // TODO build a key lookup table for fields by osm key ? // const fieldsByOsmKey = {}; diff --git a/src/services/tagging/fields.ts b/src/services/tagging/fields.ts index 8571c3f5b..ad09cf9b2 100644 --- a/src/services/tagging/fields.ts +++ b/src/services/tagging/fields.ts @@ -1,55 +1,50 @@ -// links like {shop}, are recursively resolved to their fields import { Preset } from './types/Presets'; -import { fields, presets } from './data'; +import { allFields, allPresets } from './data'; +import { deduplicate } from './utils'; import { Field } from './types/Fields'; +import { getFieldTranslation } from './translations'; -const getResolvedFields = (fieldKeys: string[]): string[] => - fieldKeys.flatMap((key) => { - if (key.match(/^{.*}$/)) { - const presetKey = key.substr(1, key.length - 2); - return getResolvedFields(presets[presetKey].fields); - } - return key; - }); +type FieldType = 'fields' | 'moreFields'; -const getResolvedMoreFields = (fieldKeys: string[]): string[] => +// links like {shop}, are recursively resolved to their fields +const resolveLinks = (fieldKeys: string[], type: FieldType): string[] => fieldKeys.flatMap((key) => { if (key.match(/^{.*}$/)) { const presetKey = key.substr(1, key.length - 2); - return getResolvedMoreFields(presets[presetKey].moreFields); + const linkedFields = allPresets[presetKey][type]; + return resolveLinks(linkedFields, type); } return key; }); -const getResolvedFieldsWithParents = ( - preset: Preset, - fieldType: 'fields' | 'moreFields', -): string[] => { +const resolveParents = (preset: Preset, type: FieldType): string[] => { const parts = preset.presetKey.split('/'); if (parts.length > 1) { const parentKey = parts.slice(0, parts.length - 1).join('/'); - const parentPreset = presets[parentKey]; + const parentPreset = allPresets[parentKey]; if (parentPreset) { - return [ - ...getResolvedFieldsWithParents(parentPreset, fieldType), - ...(preset[fieldType] ?? []), - ]; + return [...resolveParents(parentPreset, type), ...(preset[type] ?? [])]; } } - return preset[fieldType] ?? []; + return preset[type] ?? []; }; -export const computeAllFieldKeys = (preset: Preset) => { +const resolveFieldKeys = (preset: Preset, fieldType: FieldType) => + resolveLinks(resolveParents(preset, fieldType), fieldType); + +const resolveFields = (preset: Preset, fieldType: FieldType): Field[] => + resolveFieldKeys(preset, fieldType).map((key) => allFields[key]); + +const getUniversalFields = (): Field[] => + Object.values(allFields).filter((f) => f.universal); + +export const getFieldKeys = (preset: Preset): string[] => { const allFieldKeys = [ - ...getResolvedFields(getResolvedFieldsWithParents(preset, 'fields')), - ...getResolvedMoreFields( - getResolvedFieldsWithParents(preset, 'moreFields'), - ), - ...Object.values(fields) - .filter((f) => f.universal) - .map((f) => f.fieldKey), + ...resolveFieldKeys(preset, 'fields'), + ...resolveFieldKeys(preset, 'moreFields'), + ...getUniversalFields().map((f) => f.fieldKey), 'operator', 'architect', 'address', @@ -58,8 +53,32 @@ export const computeAllFieldKeys = (preset: Preset) => { .filter((f) => f !== 'image') // already covered in feature image .filter((f) => f !== 'source' && f !== 'check_date'); // lets leave these to tagsWithFields - // @ts-ignore - return [...new Set(allFieldKeys)]; + return deduplicate(allFieldKeys); +}; + +const translateFields = (fields: Field[]): Field[] => + fields.map((field) => { + const fieldTranslation = getFieldTranslation(field); + return { + ...field, + fieldTranslation: { label: `[${field.fieldKey}]`, ...fieldTranslation }, + }; + }); + +const eatPreset = (preset: Preset, fields: Field[]) => { + return fields.filter((field) => !preset.tags[field.key]); +}; + +export const getFields = (preset: Preset) => { + const fields = resolveFields(preset, 'fields'); + const moreFields = resolveFields(preset, 'moreFields'); + const universalFields = getUniversalFields(); + + return { + fields: eatPreset(preset, translateFields(fields)), + moreFields: translateFields(moreFields), + universalFields: translateFields(universalFields), + }; }; // TODO check - 1) field.options 2) strings.options diff --git a/src/services/tagging/idTaggingScheme.ts b/src/services/tagging/idTaggingScheme.ts index 560b3dfc5..111d4e4ce 100644 --- a/src/services/tagging/idTaggingScheme.ts +++ b/src/services/tagging/idTaggingScheme.ts @@ -1,14 +1,19 @@ import { Feature, FeatureTags } from '../types'; import { getFieldTranslation, getPresetTranslation } from './translations'; import { getPresetForFeature } from './presets'; -import { fields } from './data'; -import { computeAllFieldKeys, getValueForField } from './fields'; +import { allFields } from './data'; +import { + getFieldKeys, + getAllFieldsFeaturePanel, + getValueForField, +} from './fields'; import { Preset, UiField } from './types/Presets'; import { publishDbgObject } from '../../utils'; import { getShortId } from '../helpers'; import { Field } from './types/Fields'; import { DEBUG_ID_SCHEMA } from '../../config.mjs'; import { gradeSystemKeys } from '../../components/FeaturePanel/Climbing/utils/grades/gradeSystem'; +import { deduplicate } from './utils'; const logMoreMatchingFields = (matchingFields: Field[], key: string) => { if (DEBUG_ID_SCHEMA && matchingFields.length > 1) { @@ -21,20 +26,12 @@ const logMoreMatchingFields = (matchingFields: Field[], key: string) => { } }; -const deduplicate = (strings: string[]) => Array.from(new Set(strings)); - const getUiField = ( field: Field, keysTodo: KeysTodo, feature: Feature, key: string, ): UiField => { - // TODO this should be removed now the parsing works ok (+run tests) - if (field.type === 'typeCombo') { - keysTodo.remove(field.key); // ignores eg. railway=tram_stop on public_transport=stop_position - return undefined; - } - const value = feature.tags[key]; const keysInField = deduplicate([ @@ -66,12 +63,12 @@ const matchFieldsFromPreset = ( keysTodo: any, feature: Feature, ): UiField[] => { - const computedAllFieldKeys = computeAllFieldKeys(preset); - publishDbgObject('computedAllFieldKeys', computedAllFieldKeys); + const fieldKeys = getFieldKeys(preset); + publishDbgObject('all fieldKeys', fieldKeys); - return computedAllFieldKeys + return fieldKeys .map((fieldKey: string) => { - const field = fields[fieldKey]; + const field = allFields[fieldKey]; const key = field?.key; const keys = field?.keys; const includeThisField = keysTodo.has(key) || keysTodo.hasAny(keys); @@ -87,7 +84,7 @@ const matchFieldsFromPreset = ( const matchRestToFields = (keysTodo: KeysTodo, feature: Feature): UiField[] => keysTodo.mapOrSkip((key) => { - const matchingFields = Object.values(fields).filter( + const matchingFields = Object.values(allFields).filter( (f) => f.key === key || f.keys?.includes(key), // todo cache this ); logMoreMatchingFields(matchingFields, key); diff --git a/src/services/tagging/ourPresets.ts b/src/services/tagging/ourPresets.ts index fdaaa20bd..7aa577266 100644 --- a/src/services/tagging/ourPresets.ts +++ b/src/services/tagging/ourPresets.ts @@ -1,8 +1,9 @@ import { RawPresets } from './types/Presets'; +import type { RawFields } from './types/Fields'; // until https://github.com/openstreetmap/id-tagging-schema/pull/1113 is merged -export const ourFields = { +export const ourFields: RawFields = { 'climbing/summit_log': { key: 'climbing:summit_log', type: 'check', diff --git a/src/services/tagging/presets.ts b/src/services/tagging/presets.ts index 103268c38..00e2eb5e6 100644 --- a/src/services/tagging/presets.ts +++ b/src/services/tagging/presets.ts @@ -1,4 +1,4 @@ -import { presets } from './data'; +import { allPresets } from './data'; import { Feature } from '../types'; import { Preset } from './types/Presets'; import { DEBUG_ID_SCHEMA } from '../../config.mjs'; @@ -54,7 +54,7 @@ const index = { }; // build an index by geometry type -Object.values(presets).forEach((preset) => { +Object.values(allPresets).forEach((preset) => { const { geometry } = preset; geometry.forEach((geometryType) => { diff --git a/src/services/tagging/types/Fields.ts b/src/services/tagging/types/Fields.ts index 20bc66267..7cc4adeb0 100644 --- a/src/services/tagging/types/Fields.ts +++ b/src/services/tagging/types/Fields.ts @@ -1,5 +1,7 @@ // https://github.com/ideditor/schema-builder/blob/main/schemas/field.json +import type { FieldTranslation } from './Presets'; + type FieldType = | 'access' | 'address' @@ -38,6 +40,7 @@ type FieldType = export type Field = { // added by osmapp (not in schema) fieldKey: string; + fieldTranslation?: FieldTranslation; /** * Tag key whose value is to be displayed @@ -80,10 +83,7 @@ export type Field = { /** * If specified, only show the field for these kinds of geometry */ - geometry?: [ - 'point' | 'vertex' | 'line' | 'area' | 'relation', // minimal one entry - ...('point' | 'vertex' | 'line' | 'area' | 'relation')[], - ]; + geometry?: ('point' | 'vertex' | 'line' | 'area' | 'relation')[]; // minimal one entry /** * The default value for this field */ @@ -223,3 +223,7 @@ export type Field = { export type Fields = { [fieldKey: string]: Field; }; + +export type RawFields = { + [fieldKey: string]: Omit; +}; diff --git a/src/services/tagging/types/Presets.ts b/src/services/tagging/types/Presets.ts index 4d2b488a4..52992cd68 100644 --- a/src/services/tagging/types/Presets.ts +++ b/src/services/tagging/types/Presets.ts @@ -104,12 +104,12 @@ export type RawPresets = { export type FieldTranslation = { label: string; - placeholder: string; - terms: string; - options: { + placeholder?: string; + terms?: string; + options?: { [key: string]: { title: string; description: string }; }; - types: { + types?: { [key: string]: string; }; }; diff --git a/src/services/tagging/utils.ts b/src/services/tagging/utils.ts new file mode 100644 index 000000000..ca5eaa5bb --- /dev/null +++ b/src/services/tagging/utils.ts @@ -0,0 +1 @@ +export const deduplicate = (strings: string[]) => Array.from(new Set(strings));