Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iD Tagging scheme exploration #131

Merged
merged 32 commits into from
Jun 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f726704
iD Tagging scheme exploration
zbycz Feb 2, 2023
ac4a5e4
fix build
zbycz Feb 2, 2023
4303f91
fix build2
zbycz Feb 2, 2023
c256952
show only unique fields
zbycz Feb 4, 2023
d2879df
add typescript types
zbycz Feb 4, 2023
6d6c93e
y lintfix
zbycz Feb 4, 2023
904a6f8
find Field for rest of tags
zbycz Feb 6, 2023
8753c1f
move schema to Feature + add UI
zbycz Feb 9, 2023
9dde0c5
translate values in semiCombo
zbycz Feb 11, 2023
c23dd38
titles
zbycz Feb 11, 2023
657714f
hack address:* in
zbycz Feb 11, 2023
d022a75
remove tags which are already covered by Preset name
zbycz Feb 18, 2023
8d8421d
experiment - clear sessionstorage
zbycz Feb 19, 2023
fd84d97
typeCombo is usually cleared by preset.tags
zbycz Feb 19, 2023
5a6837b
We need typeCombo after all, eg node/7002486683
zbycz Feb 19, 2023
e4a4d18
move units from label to value
zbycz Feb 21, 2023
fc584ef
finalize first prototype
zbycz Feb 26, 2023
093f867
allow language switching
zbycz Feb 26, 2023
4ff8899
fix buildAddress a little
zbycz Mar 4, 2023
f5aefe4
Merge remote-tracking branch 'origin/master' into tagging
zbycz Mar 25, 2023
c826aeb
extract PoiDescription.tsx
zbycz Mar 25, 2023
4c08c4d
extract field helpers to fields.ts
zbycz Mar 26, 2023
112bb41
refactoring restKeys->keysTodo, added publishDbgObject(), added test
zbycz Apr 10, 2023
afc696c
fix access key - covers multiple tags
zbycz Apr 10, 2023
d962b8a
Use `@openstreetmap/id-tagging-schema` pkg
zbycz Apr 27, 2023
7c05661
fix keysTodo (never commit 30k ft above ground)
zbycz Jun 17, 2023
d67ca25
Merge branch 'master' into tagging
zbycz Jun 17, 2023
6e55aa8
y lintfix
zbycz Jun 17, 2023
a99ecc7
fix [object object] in `subject:wikidata`
zbycz Jun 17, 2023
9fb9c1f
update osmApi.test.ts
zbycz Jun 17, 2023
e601d7f
hide under advanced mode
zbycz Jun 17, 2023
fe28dd7
add Details above TagsTable
zbycz Jun 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@material-ui/core": "^4.11.4",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "4.0.0-alpha.58",
"@openstreetmap/id-tagging-schema": "^6.1.0",
"@sentry/browser": "^6.5.1",
"@sentry/node": "^6.5.1",
"@types/maplibre-gl": "^1.13.1",
Expand Down
45 changes: 33 additions & 12 deletions src/components/FeaturePanel/FeaturePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useState } from 'react';
import { Typography } from '@material-ui/core';
import { FeatureHeading } from './FeatureHeading';
import Coordinates from './Coordinates';
import { useToggleState } from '../helpers';
import { TagsTable } from './TagsTable';
import { getFullOsmappLink, getUrlOsmId } from '../../services/helpers';
import { EditDialog } from './EditDialog/EditDialog';
import {
Expand All @@ -21,6 +21,8 @@ import { EditButton } from './EditButton';
import { FeaturedTags } from './FeaturedTags';
import { getLabel } from '../../helpers/featureLabel';
import { ImageSection } from './ImageSection/ImageSection';
import { IdSchemeFields } from './IdSchemeFields';
import { TagsTable } from './TagsTable';

const featuredKeys = [
'website',
Expand Down Expand Up @@ -69,17 +71,36 @@ const FeaturePanel = () => {
setDialogOpenedWith={setDialogOpenedWith}
/>

<TagsTable
tags={tags}
center={feature.center}
except={
advanced || deleted ? [] : ['name', 'layer', ...featuredKeys]
}
onEdit={setDialogOpenedWith}
key={
getUrlOsmId(osmMeta) // we need to refresh inner state
}
/>
{advanced && (
<IdSchemeFields
featuredTags={deleted ? [] : featuredTags}
feature={feature}
/>
)}
{!advanced && (
<>
{featuredTags.length && (
<Typography
variant="overline"
display="block"
color="textSecondary"
>
{t('featurepanel.other_info_heading')}
</Typography>
)}
<TagsTable
tags={tags}
center={feature.center}
except={
advanced || deleted ? [] : ['name', 'layer', ...featuredKeys]
}
onEdit={setDialogOpenedWith}
key={
getUrlOsmId(osmMeta) // we need to refresh inner state
}
/>
</>
)}

{advanced && <Members />}

Expand Down
6 changes: 0 additions & 6 deletions src/components/FeaturePanel/FeaturedTags.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import Typography from '@material-ui/core/Typography';
import React from 'react';
import styled from 'styled-components';
import { t } from '../../services/intl';
import { FeaturedTag } from './FeaturedTag';

const Spacer = styled.div`
Expand All @@ -17,10 +15,6 @@ export const FeaturedTags = ({ featuredTags, setDialogOpenedWith }) => {
<FeaturedTag key={k} k={k} v={v} onEdit={setDialogOpenedWith} />
))}
<Spacer />

<Typography variant="overline" display="block" color="textSecondary">
{t('featurepanel.other_info_heading')}
</Typography>
</>
);
};
136 changes: 136 additions & 0 deletions src/components/FeaturePanel/IdSchemeFields.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, { ReactNode } from 'react';
import styled from 'styled-components';
import Typography from '@material-ui/core/Typography';
import { Field } from '../../services/tagging/types/Fields';
import { getUrlForTag } from './helpers/getUrlForTag';
import { slashToOptionalBr } from '../helpers';
import { buildAddress } from '../../services/helpers';
import { Feature } from '../../services/types';
import { t } from '../../services/intl';

// taken from src/components/FeaturePanel/TagsTable.tsx
const Table = styled.table`
font-size: 1rem;
width: 100%;

th,
td {
padding: 0.1em;
overflow: hidden;

&:hover .show-on-hover {
display: block !important;
}
}

th {
width: 140px;
max-width: 140px;
color: rgba(0, 0, 0, 0.54);
text-align: left;
font-weight: normal;
vertical-align: baseline;
padding-left: 0;
}

table {
padding-left: 1em;
padding-bottom: 1em;
}
`;

// TODO move to helpers
const getEllipsisHumanUrl = (humanUrl) => {
const MAX_LENGTH = 40;
return humanUrl.replace(/^([^/]+.{0,5})(.*)$/, (full, hostname, rest) => {
const charsLeft = MAX_LENGTH - 10 - hostname.length;
return (
hostname +
(full.length > MAX_LENGTH
? `…${rest.substring(rest.length - charsLeft)}`
: rest)
);
});
};

// taken from src/components/FeaturePanel/TagsTable.tsx
const renderValue = (k, v): string | ReactNode => {
const url = getUrlForTag(k, v);
if (url) {
let humanUrl = v.replace(/^https?:\/\//, '').replace(/^([^/]+)\/$/, '$1');
if (k === 'image') {
humanUrl = getEllipsisHumanUrl(humanUrl);
}
return <a href={url}>{slashToOptionalBr(humanUrl)}</a>;
}
return v;
};

const render = (field: Field, feature: Feature, k, v): string | ReactNode => {
if (field.type === 'address') {
return buildAddress(feature.tags, feature.center);
}
return renderValue(k, v);
};

const getTitle = (type: string, field: Field) =>
`${type}: ${JSON.stringify(field, null, 2)}`;

// TODO some fields eg. oneway/bicycle doesnt have units in brackets
const unitRegExp = / \((.+)\)$/i;
const removeUnits = (label) => label.replace(unitRegExp, '');
const addUnits = (label, value: string | ReactNode) => {
if (typeof value !== 'string') return value;
const unit = label.match(unitRegExp);
return `${value}${unit ? ` (${unit[1]})` : ''}`;
};

export const IdSchemeFields = ({ feature, featuredTags }) => {
const { schema } = feature;
if (!schema) return null;
if (!Object.keys(schema).length) return null;

return (
<>
{featuredTags.length &&
(schema.matchedFields.length ||
schema.tagsWithFields.length ||
schema.restKeys.length) ? (
<Typography variant="overline" display="block" color="textSecondary">
{t('featurepanel.other_info_heading')}
</Typography>
) : null}

<Table>
<tbody>
{schema.matchedFields.map(({ key, value, label, field }) => (
<tr key={key}>
<th title={getTitle('from preset', field)}>
{removeUnits(label)}
</th>
<td>{addUnits(label, render(field, feature, key, value))}</td>
</tr>
))}
</tbody>
<tbody>
{schema.tagsWithFields.map(({ key, value, label, field }) => (
<tr key={key}>
<th title={getTitle('standalone field', field)}>
{removeUnits(label)}
</th>
<td>{render(field, feature, key, addUnits(label, value))}</td>
</tr>
))}
</tbody>
<tbody>
{schema.keysTodo.map((key) => (
<tr key={key}>
<th>{key}</th>
<td>{renderValue(key, feature.tags[key])}</td>
</tr>
))}
</tbody>
</Table>
</>
);
};
33 changes: 2 additions & 31 deletions src/components/FeaturePanel/ImageSection/ImageSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ import React from 'react';
import styled from 'styled-components';
import { useFeatureContext } from '../../utils/FeatureContext';
import { FeatureImage } from './FeatureImage';
import Maki from '../../utils/Maki';
import { hasName } from '../../../helpers/featureLabel';
import { t } from '../../../services/intl';
import { SHOW_PROTOTYPE_UI } from '../../../config';
import { Feature } from '../../../services/types';
import { PoiDescription } from './PoiDescription';

const StyledIconButton = styled(IconButton)`
svg {
Expand All @@ -20,40 +18,13 @@ const StyledIconButton = styled(IconButton)`
}
`;

const PoiType = styled.div`
color: #fff;
margin: 0 auto 0 15px;
font-size: 13px;
position: relative;
width: 100%;
svg {
vertical-align: bottom;
}
span {
position: absolute;
left: 20px;
}
`;

const getSubclass = ({ layer, osmMeta, properties }: Feature) =>
properties.subclass?.replace(/_/g, ' ') ||
(layer && layer.id) || // layer.id specified only when maplibre-gl skeleton displayed
osmMeta.type;

export const ImageSection = () => {
const { feature } = useFeatureContext();
const { properties } = feature;

const poiType = hasName(feature)
? getSubclass(feature)
: t('featurepanel.no_name');

return (
<FeatureImage feature={feature} ico={properties.class}>
<PoiType>
<Maki ico={properties.class} invert middle />
<span>{poiType}</span>
</PoiType>
<PoiDescription />

{SHOW_PROTOTYPE_UI && (
<>
Expand Down
46 changes: 46 additions & 0 deletions src/components/FeaturePanel/ImageSection/PoiDescription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import styled from 'styled-components';
import { hasName } from '../../../helpers/featureLabel';
import { useFeatureContext } from '../../utils/FeatureContext';
import { t } from '../../../services/intl';
import Maki from '../../utils/Maki';
import { Feature } from '../../../services/types';

const PoiType = styled.div`
color: #fff;
margin: 0 auto 0 15px;
font-size: 13px;
position: relative;
width: 100%;

svg {
vertical-align: bottom;
}

span {
position: absolute;
left: 20px;
}
`;

const getSubclass = ({ layer, osmMeta, properties, schema }: Feature) =>
schema?.label ||
properties.subclass?.replace(/_/g, ' ') ||
(layer && layer.id) || // layer.id specified only when maplibre-gl skeleton displayed
osmMeta.type;

export const PoiDescription = () => {
const { feature } = useFeatureContext();
const { properties } = feature;

const poiType = hasName(feature)
? getSubclass(feature)
: t('featurepanel.no_name');

return (
<PoiType>
<Maki ico={properties.class} invert middle />
<span>{poiType}</span>
</PoiType>
);
};
3 changes: 3 additions & 0 deletions src/components/utils/FeatureContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Router from 'next/router';
import Cookies from 'js-cookie';
import { Feature } from '../../services/types';
import { useBoolState } from '../helpers';
import { publishDbgObject } from '../../utils';

export interface FeatureContextType {
feature: Feature | null;
Expand Down Expand Up @@ -43,6 +44,8 @@ export const FeatureProvider = ({
useEffect(() => {
// set feature on next.js router transition
setFeature(featureFromRouter);
publishDbgObject('feature', featureFromRouter);
publishDbgObject('schema', featureFromRouter?.schema);
}, [featureFromRouter]);

const [homepageShown, showHomepage, hideHomepage] = useBoolState(
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/featureLabel.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Feature } from '../services/types';
import { roundedToDeg } from '../utils';

const getSubclass = ({ properties, osmMeta }: Feature) =>
properties.subclass?.replace(/_/g, ' ') || osmMeta.type; // TODO translate ? maybe use iD editor logic (already with translations)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const getSubclass = ({ properties, osmMeta, schema }: Feature) =>
schema?.label || properties.subclass?.replace(/_/g, ' ') || osmMeta.type;

const getRef = (feature: Feature) =>
feature.tags.ref ? `${getSubclass(feature)} ${feature.tags.ref}` : '';
Expand Down
14 changes: 14 additions & 0 deletions src/services/__tests__/osmApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,29 @@ import {
way,
wayFeature,
} from './osmApi.fixture';
import { intl } from '../intl';
import * as tagging from '../tagging/translations';
import * as idTaggingScheme from '../tagging/idTaggingScheme';

const osm = (item) => ({ elements: [item] });
const overpass = {
elements: [{ center: { lat: 50, lon: 14 } }],
};

// fetchFeature() fetches the translations for getSchemaForFeature()
// TODO maybe refactor it without need for intl?
intl.lang = 'en';
jest.mock('next/config', () => () => ({
publicRuntimeConfig: { languages: ['en'] },
}));

describe('fetchFeature', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(tagging, 'fetchSchemaTranslations').mockResolvedValue(true);
jest
.spyOn(idTaggingScheme, 'getSchemaForFeature')
.mockReturnValue(undefined); // this is covered in idTaggingScheme.test.ts
});

const isServer = jest.spyOn(helpers, 'isServer').mockReturnValue(true);
Expand Down
Loading