Skip to content

Commit

Permalink
Merge pull request #187 from OpenHistoricalMap/1ec5-edtf-fields-645
Browse files Browse the repository at this point in the history
Add EDTF start and end date fields
  • Loading branch information
erictheise authored Dec 20, 2023
2 parents ac14034 + 41e0fb3 commit 39de18f
Show file tree
Hide file tree
Showing 10 changed files with 579 additions and 12 deletions.
25 changes: 17 additions & 8 deletions css/80_app.css
Original file line number Diff line number Diff line change
Expand Up @@ -1996,18 +1996,20 @@ a.hide-toggle {
}


/* Field - Localized Name
/* Field - Localized Name, Date
------------------------------------------------------- */
.form-field-input-localized > input.localized-main {
border-radius: 0 0 0 4px;
}
.ideditor[dir='rtl'] .form-field-input-localized > input.localized-main {
border-radius: 0 0 4px 0;
}
.form-field-input-localized > button.localized-add {
.form-field-input-localized > button.localized-add,
.form-field-input-date > button.date-add {
border-radius: 0 0 4px 0;
}
.ideditor[dir='rtl'] .form-field-input-localized > button.localized-add {
.ideditor[dir='rtl'] .form-field-input-localized > button.localized-add,
.ideditor[dir='rtl'] .form-field-input-date > button.date-add {
border-radius: 0 0 0 4px;
}

Expand All @@ -2020,18 +2022,21 @@ a.hide-toggle {
cursor: not-allowed;
}

/* nested subfields for name in different languages */
.localized-multilingual {
/* nested subfields for name in different languages or date in different formats */
.localized-multilingual,
.date-edtf {
padding: 0 10px;
flex-basis: 100%;
}
.localized-multilingual .entry {
.localized-multilingual .entry,
.date-edtf .entry {
position: relative;
overflow: hidden;
}

/* draws a little line connecting the multilingual field up to the name field */
.localized-multilingual .entry::before {
.localized-multilingual .entry::before,
.date-edtf .entry::before {
content: "";
display: block;
position: absolute;
Expand All @@ -2049,7 +2054,8 @@ a.hide-toggle {
border-top-width: 0;
width: 100%;
}
.localized-multilingual .entry .localized-value {
.localized-multilingual .entry .localized-value,
.date-edtf .entry .date-value {
border-top-width: 0;
border-radius: 0 0 4px 4px;
width: 100%;
Expand All @@ -2062,6 +2068,9 @@ a.hide-toggle {
.ideditor .form-field-input-date > .combobox-caret + input.date-main {
border-left: 0;
}
.ideditor .form-field-input-date > input.date-main:last-of-type {
border-radius: 0;
}


/* Field - Address
Expand Down
14 changes: 14 additions & 0 deletions data/core.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,9 @@ en:
month: Month
# placeholder for day of the month
day: Day
edtf: Add EDTF date
edtf_label: Extended Date/Time Format
edtf_placeholder: 1849~, 1804?, 189X, 1906/1908, 1814-23, 1960-05-01T13:00...
max_length_reached: "This string is longer than the maximum length of {maxChars} characters. Anything exceeding that length will be truncated."
background:
title: Background
Expand Down Expand Up @@ -1842,10 +1845,21 @@ en:
message_start: '{feature} has an invalid start date'
message_end: '{feature} has an invalid end date'
reference: 'A date must be formatted as YYYY-MM-DD, YYYY-MM, or YYYY.'
edtf:
message_start: '{feature} has an invalid EDTF start date'
message_end: '{feature} has an invalid EDTF end date'
reference: 'There is an unexpected "{token}" at character {position}.'
line_as_area:
message: '{feature} should be a line, not an area'
line_as_point:
message: '{feature} should be a line, not a point'
mismatched_dates:
title: Mismatched Dates
tip: Find dates that are contradictory or anachronistic
edtf:
message_start: '{feature} has a start date that falls outside of the EDTF start date'
message_end: '{feature} has an end date that falls outside of the EDTF end date'
reference: 'The basic date value should overlap with the range of possible dates described by the EDTF date.'
mismatched_geometry:
title: Mismatched Geometry
tip: "Find features with conflicting tags and geometry"
Expand Down
197 changes: 194 additions & 3 deletions modules/ui/fields/date.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import * as countryCoder from '@ideditor/country-coder';

import { svgIcon } from '../../svg';
import { uiTooltip } from '../tooltip';
import { uiCombobox } from '../combobox';
import { t, localizer } from '../../core/localizer';
import { utilGetSetValue, utilNoAuto, utilRebind, utilTotalExtent } from '../../util';
import { utilGetSetValue, utilNoAuto, utilRebind, utilUniqueDomId } from '../../util';


export function uiFieldDate(field, context) {
Expand All @@ -13,8 +14,13 @@ export function uiFieldDate(field, context) {
let eraInput = d3_select(null);
let monthInput = d3_select(null);
let dayInput = d3_select(null);
let edtfInput = d3_select(null);
let _entityIDs = [];
let _tags;
let _selection = d3_select(null);
let _edtfValue;

let edtfKey = field.key + ':edtf';

let dateTimeFormat = new Intl.DateTimeFormat(localizer.languageCode(), {
year: 'numeric',
Expand Down Expand Up @@ -100,9 +106,27 @@ export function uiFieldDate(field, context) {
};
}));

let buttonTip = uiTooltip()
.title(() => t.append('inspector.date.edtf'))
.placement('left');


// update _edtfValue
function calcEDTFValue(tags) {
if (_edtfValue && !tags[edtfKey]) {
// Don't unset the variable based on deleted tags, since this makes the UI
// disappear unexpectedly when clearing values - #8164
_edtfValue = '';
} else {
_edtfValue = tags[edtfKey];
}
}


function date(selection) {
_selection = selection;

var wrap = selection.selectAll('.form-field-input-wrap')
let wrap = selection.selectAll('.form-field-input-wrap')
.data([0]);

wrap = wrap.enter()
Expand Down Expand Up @@ -169,6 +193,47 @@ export function uiFieldDate(field, context) {
dayInput
.on('change', change)
.on('blur', change);

if (_tags && _edtfValue === undefined) {
calcEDTFValue(_tags);
}

let edtfButton = wrap.selectAll('.date-add')
.data([0]);

edtfButton = edtfButton.enter()
.append('button')
.attr('class', 'date-add form-field-button')
.attr('aria-label', t('icons.plus'))
.call(svgIcon('#iD-icon-plus'))
.merge(edtfButton);

edtfButton
.classed('disabled', typeof _edtfValue === 'string' || Array.isArray(_edtfValue))
.call(buttonTip)
.on('click', addEDTF);

edtfInput = selection.selectAll('.date-edtf')
.data([0]);

edtfInput = edtfInput.enter()
.append('div')
.attr('class', 'date-edtf')
.merge(edtfInput);

edtfInput
.call(renderEDTF);
}


function addEDTF(d3_event) {
d3_event.preventDefault();

if (typeof _edtfValue !== 'string' && !Array.isArray(_edtfValue)) {
_edtfValue = '';

edtfInput.call(renderEDTF);
}
}


Expand Down Expand Up @@ -210,6 +275,125 @@ export function uiFieldDate(field, context) {
}


function changeEDTFValue(d3_event, d) {
let value = context.cleanTagValue(utilGetSetValue(d3_select(this))) || undefined;

// don't override multiple values with blank string
if (!value && Array.isArray(d.value)) return;

let t = {};
t[edtfKey] = value;
d.value = value;
dispatch.call('change', this, t);
}


function renderEDTF(selection) {
let entries = selection.selectAll('div.entry')
.data((typeof _edtfValue === 'string' || Array.isArray(_edtfValue)) ? [_edtfValue] : []);

entries.exit()
.style('top', '0')
.style('max-height', '240px')
.transition()
.duration(200)
.style('opacity', '0')
.style('max-height', '0px')
.remove();

let entriesEnter = entries.enter()
.append('div')
.attr('class', 'entry')
.each(function() {
var wrap = d3_select(this);

let domId = utilUniqueDomId('edtf');
let label = wrap
.append('label')
.attr('class', 'field-label')
.attr('for', domId);

let text = label
.append('span')
.attr('class', 'label-text');

text
.append('span')
.attr('class', 'label-textvalue')
.call(t.append('inspector.date.edtf_label'));

text
.append('span')
.attr('class', 'label-textannotation');

label
.append('button')
.attr('class', 'remove-icon-edtf')
.attr('title', t('icons.remove'))
.on('click', function(d3_event) {
d3_event.preventDefault();

// remove the UI item manually
_edtfValue = undefined;

if (edtfKey && edtfKey in _tags) {
delete _tags[edtfKey];
// remove from entity tags
let t = {};
t[edtfKey] = undefined;
dispatch.call('change', this, t);
return;
}

renderEDTF(selection);
})
.call(svgIcon('#iD-operation-delete'));

wrap
.append('input')
.attr('type', 'text')
.attr('class', 'date-value')
.on('blur', changeEDTFValue)
.on('change', changeEDTFValue);
});

entriesEnter
.style('margin-top', '0px')
.style('max-height', '0px')
.style('opacity', '0')
.transition()
.duration(200)
.style('margin-top', '10px')
.style('max-height', '240px')
.style('opacity', '1')
.on('end', function() {
d3_select(this)
.style('max-height', '')
.style('overflow', 'visible');
});

entries = entries.merge(entriesEnter);

entries.order();

// allow removing the entry UIs even if there isn't a tag to remove
entries.classed('present', true);

utilGetSetValue(entries.select('.date-value'), function(d) {
return typeof d === 'string' ? d : '';
})
.attr('title', function(d) {
return Array.isArray(d) ? d.filter(Boolean).join('\n') : null;
})
.attr('placeholder', function(d) {
return Array.isArray(d) ? t('inspector.multiple_values') : t('inspector.date.edtf_placeholder');
})
.classed('mixed', function(d) {
return Array.isArray(d);
});
}


date.tags = function(tags) {
_tags = tags;

Expand Down Expand Up @@ -250,6 +434,10 @@ export function uiFieldDate(field, context) {
.attr('placeholder', t('inspector.date.month'));
utilGetSetValue(dayInput, typeof dayValue === 'number' ? dayValue : '')
.attr('placeholder', t('inspector.date.day'));

calcEDTFValue(tags);

_selection.call(date);
};


Expand All @@ -260,7 +448,10 @@ export function uiFieldDate(field, context) {


date.entityIDs = function(val) {
if (!arguments.length) return _entityIDs;
_entityIDs = val;
_edtfValue = undefined;
return date;
};


Expand Down
1 change: 1 addition & 0 deletions modules/validations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { validationHelpRequest } from './help_request';
export { validationImpossibleOneway } from './impossible_oneway';
export { validationIncompatibleSource } from './incompatible_source';
export { validationMaprules } from './maprules';
export { validationMismatchedDates } from './mismatched_dates';
export { validationMismatchedGeometry } from './mismatched_geometry';
export { validationMissingRole } from './missing_role';
export { validationMissingTag } from './missing_tag';
Expand Down
Loading

0 comments on commit 39de18f

Please sign in to comment.