Skip to content

Commit

Permalink
EditDialog: add FeatureTypeSelect based on id Presets
Browse files Browse the repository at this point in the history
  • Loading branch information
zbycz committed Oct 10, 2024
1 parent 70b0108 commit 52ae818
Show file tree
Hide file tree
Showing 15 changed files with 312 additions and 154 deletions.
107 changes: 107 additions & 0 deletions src/components/FeaturePanel/EditDialog/EditContent/ComboSearchBox.tsx
Original file line number Diff line number Diff line change
@@ -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 && (
<>
<Maki ico={option.icon} size={16} middle themed />
<span style={{ paddingLeft: 5 }} />
{option.name}
</>
);

export const ComboSearchBox = ({
value,
setValue,
options,
}: {
value: Preset;
setValue: Setter<Preset>;
options: TranslatedPreset[];
}) => {
const [searchText, setSearchText] = useState('');
const displayedOptions = useMemo<TranslatedPreset[]>(
() =>
searchText.length
? options.filter((option) => containsText(option.name, searchText))
: emptyOptions.map((presetKey) =>
options.find((preset) => preset.presetKey === presetKey),
),
[options, searchText],
);

return (
<Select
MenuProps={{ autoFocus: false }}
value={value}
onChange={(e) => {
// @ts-ignore https://github.com/mui/material-ui/issues/14286
setValue(e.target.value);
}}
onClose={() => setSearchText('')}
renderValue={() => renderOption(value)}
size="small"
>
<StyledListSubheader>
<SearchIcon fontSize="small" />
<InputBase
size="small"
autoFocus
placeholder="Type to search..."
fullWidth
onChange={(e) => setSearchText(e.target.value)}
onKeyDown={(e) => {
if (e.key !== 'Escape') {
e.stopPropagation();
}
}}
/>
</StyledListSubheader>
{displayedOptions.map((option) => (
<MenuItem
key={option.presetKey}
component="li"
// @ts-ignore https://github.com/mui/material-ui/issues/14286
value={option}
>
{renderOption(option)}
</MenuItem>
))}
</Select>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const EditContent = () => (
<>
<DialogContent dividers>
<form autoComplete="off" onSubmit={(e) => e.preventDefault()}>
{false && <FeatureTypeSelect />}
<FeatureTypeSelect />
<MajorKeysEditor />
<OptionsEditor />
<ContributionInfoBox />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TagInfoResponse>(
`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<PresetsCache> => {
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) => (
<>
<Maki ico={value.class} size={16} middle /> {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 (
<Box mb={3}>
<TextField
variant="outlined"
select
fullWidth
value={typeTag}
SelectProps={{ renderValue, onChange }}
label={t('editdialog.feature_type_select')}
>
<MenuItem value=""></MenuItem>
{options.map((item) => (
<MenuItem key={item.tag} value={item}>
<Box>
<Maki ico={item.class} />
{item.subclass}
<br />
<Typography variant="caption">
{trimText(item.description, 100)}
</Typography>
</Box>
</MenuItem>
))}
</TextField>
</Box>
<Row mb={3}>
<Typography variant="body1" component="span" color="textSecondary">
Typ:
</Typography>

<ComboSearchBox value={preset} setValue={setPreset} options={options} />
</Row>
);
};
7 changes: 7 additions & 0 deletions src/components/FeaturePanel/EditDialog/EditContext.tsx
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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;
Expand Down Expand Up @@ -45,6 +48,8 @@ export const EditContextProvider = ({ feature, children }: Props) => {
const [location, setLocation] = useState('');
const [comment, setComment] = useState('');

const [preset, setPreset] = useState<undefined | Preset>();

const [typeTag, setTypeTag] = useState<TypeTag>();
const [tags, setTag] = useTagsState(feature.tags);
const [tmpNewTag, setTmpNewTag] = useState({});
Expand All @@ -59,6 +64,8 @@ export const EditContextProvider = ({ feature, children }: Props) => {
setLocation,
comment,
setComment,
preset,
setPreset,
tags: {
typeTag,
setTypeTag,
Expand Down
4 changes: 2 additions & 2 deletions src/components/SearchBox/options/preset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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)
Expand Down
Loading

0 comments on commit 52ae818

Please sign in to comment.