Merge branch 'notes'

This commit is contained in:
Bryan Housel
2018-07-16 16:59:35 -04:00
35 changed files with 1934 additions and 332 deletions
+11 -4
View File
@@ -6,7 +6,10 @@ import {
} from 'd3-selection';
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
import { osmEntity } from '../osm';
import {
osmEntity,
osmNote
} from '../osm';
import { utilRebind } from '../util/rebind';
@@ -108,7 +111,7 @@ export function behaviorHover(context) {
.classed('hover-suppressed', false);
var entity;
if (datum instanceof osmEntity) {
if (datum instanceof osmNote || datum instanceof osmEntity) {
entity = datum;
} else {
entity = datum && datum.properties && datum.properties.entity;
@@ -122,7 +125,7 @@ export function behaviorHover(context) {
return;
}
var selector = '.' + entity.id;
var selector = (datum instanceof osmNote) ? 'note-' + entity.id : '.' + entity.id;
if (entity.type === 'relation') {
entity.members.forEach(function(member) {
@@ -135,7 +138,11 @@ export function behaviorHover(context) {
_selection.selectAll(selector)
.classed(suppressed ? 'hover-suppressed' : 'hover', true);
dispatch.call('hover', this, !suppressed && entity.id);
if (datum instanceof osmNote) {
dispatch.call('hover', this, !suppressed && entity);
} else {
dispatch.call('hover', this, !suppressed && entity.id);
}
} else {
dispatch.call('hover', this, null);
+20 -10
View File
@@ -10,10 +10,14 @@ import { geoVecLength } from '../geo';
import {
modeBrowse,
modeSelect
modeSelect,
modeSelectNote
} from '../modes';
import { osmEntity } from '../osm';
import {
osmEntity,
osmNote
} from '../osm';
export function behaviorSelect(context) {
@@ -122,15 +126,9 @@ export function behaviorSelect(context) {
datum = datum.parents[0];
}
if (!(datum instanceof osmEntity)) {
// clicked nothing..
if (!isMultiselect && mode.id !== 'browse') {
context.enter(modeBrowse(context));
}
} else {
// clicked an entity..
if (datum instanceof osmEntity) { // clicked an entity..
var selectedIDs = context.selectedIDs();
context.selectedNoteID(null);
if (!isMultiselect) {
if (selectedIDs.length > 1 && (!suppressMenu && !isShowAlways)) {
@@ -158,6 +156,18 @@ export function behaviorSelect(context) {
context.enter(modeSelect(context, selectedIDs).suppressMenu(suppressMenu));
}
}
} else if (datum instanceof osmNote && !isMultiselect) { // clicked a Note..
context
.selectedNoteID(datum.id)
.enter(modeSelectNote(context, datum.id));
} else { // clicked nothing..
context.selectedNoteID(null);
if (!isMultiselect && mode.id !== 'browse') {
context.enter(modeBrowse(context));
}
}
// reset for next time..
+8
View File
@@ -255,10 +255,18 @@ export function coreContext() {
return [];
}
};
context.activeID = function() {
return mode && mode.activeID && mode.activeID();
};
var _selectedNoteID;
context.selectedNoteID = function(noteID) {
if (!arguments.length) return _selectedNoteID;
_selectedNoteID = noteID;
return context;
};
/* Behaviors */
context.install = function(behavior) {
+1
View File
@@ -9,3 +9,4 @@ export { modeMove } from './move';
export { modeRotate } from './rotate';
export { modeSave } from './save';
export { modeSelect } from './select';
export { modeSelectNote } from './select_note';
-1
View File
@@ -513,7 +513,6 @@ export function modeSelect(context, selectedIDs) {
showMenu();
}
}, 270); /* after any centerEase completes */
};
+123
View File
@@ -0,0 +1,123 @@
import {
event as d3_event,
select as d3_select
} from 'd3-selection';
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
import {
behaviorHover,
behaviorLasso,
behaviorSelect
} from '../behavior';
import { services } from '../services';
import { modeBrowse } from './browse';
import { uiNoteEditor } from '../ui';
export function modeSelectNote(context, selectedNoteID) {
var mode = {
id: 'select_note',
button: 'browse'
};
var osm = services.osm;
var keybinding = d3_keybinding('select-note');
var noteEditor = uiNoteEditor(context)
.on('change', function() {
context.map().pan([0,0]); // trigger a redraw
var note = checkSelectedID();
if (!note) return;
context.ui().sidebar
.show(noteEditor.note(note));
});
var behaviors = [
behaviorHover(context),
behaviorSelect(context),
behaviorLasso(context),
];
function checkSelectedID() {
if (!osm) return;
var note = osm.getNote(selectedNoteID);
if (!note) {
context.enter(modeBrowse(context));
}
return note;
}
mode.enter = function() {
// class the note as selected, or return to browse mode if the note is gone
function selectNote(drawn) {
if (!checkSelectedID()) return;
var selection = context.surface()
.selectAll('.note-' + selectedNoteID);
if (selection.empty()) {
// Return to browse mode if selected DOM elements have
// disappeared because the user moved them out of view..
var source = d3_event && d3_event.type === 'zoom' && d3_event.sourceEvent;
if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) {
context.enter(modeBrowse(context));
}
} else {
selection
.classed('selected', true);
}
}
function esc() {
context.enter(modeBrowse(context));
}
var note = checkSelectedID();
if (!note) return;
behaviors.forEach(function(behavior) {
context.install(behavior);
});
keybinding
.on('⎋', esc, true);
d3_select(document)
.call(keybinding);
context.ui().sidebar
.show(noteEditor.note(note));
context.map()
.on('drawn.select', selectNote);
selectNote();
};
mode.exit = function() {
behaviors.forEach(function(behavior) {
context.uninstall(behavior);
});
keybinding.off();
context.surface()
.selectAll('.note.selected')
.classed('selected hovered', false);
context.map()
.on('drawn.select', null);
context.ui().sidebar
.hide();
};
return mode;
}
+1
View File
@@ -1,6 +1,7 @@
export { osmChangeset } from './changeset';
export { osmEntity } from './entity';
export { osmNode } from './node';
export { osmNote } from './note';
export { osmRelation } from './relation';
export { osmWay } from './way';
+60
View File
@@ -0,0 +1,60 @@
import _extend from 'lodash-es/extend';
import { geoExtent } from '../geo';
export function osmNote() {
if (!(this instanceof osmNote)) {
return (new osmNote()).initialize(arguments);
} else if (arguments.length) {
this.initialize(arguments);
}
}
osmNote.id = function() {
return osmNote.id.next--;
};
osmNote.id.next = -1;
_extend(osmNote.prototype, {
type: 'note',
initialize: function(sources) {
for (var i = 0; i < sources.length; ++i) {
var source = sources[i];
for (var prop in source) {
if (Object.prototype.hasOwnProperty.call(source, prop)) {
if (source[prop] === undefined) {
delete this[prop];
} else {
this[prop] = source[prop];
}
}
}
}
if (!this.id) {
this.id = osmNote.id();
}
return this;
},
extent: function() {
return new geoExtent(this.loc);
},
update: function(attrs) {
return osmNote(this, attrs, {v: 1 + (this.v || 0)});
},
isNew: function() {
return this.id < 0;
}
});
+1 -1
View File
@@ -350,7 +350,7 @@ export function rendererMap(context) {
surface.selectAll('.layer-osm *').remove();
var mode = context.mode();
if (mode && mode.id !== 'save') {
if (mode && mode.id !== 'save' && mode.id !== 'select_note') {
context.enter(modeBrowse(context));
}
+589 -227
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -10,6 +10,7 @@ export { svgLines } from './lines.js';
export { svgMapillaryImages } from './mapillary_images.js';
export { svgMapillarySigns } from './mapillary_signs.js';
export { svgMidpoints } from './midpoints.js';
export { svgNotes } from './notes.js';
export { svgOneWaySegments } from './helpers.js';
export { svgOpenstreetcamImages } from './openstreetcam_images.js';
export { svgOsm } from './osm.js';
+2
View File
@@ -15,6 +15,7 @@ import { svgMapillaryImages } from './mapillary_images';
import { svgMapillarySigns } from './mapillary_signs';
import { svgOpenstreetcamImages } from './openstreetcam_images';
import { svgOsm } from './osm';
import { svgNotes } from './notes';
import { utilRebind } from '../util/rebind';
import { utilGetDimensions, utilSetDimensions } from '../util/dimensions';
@@ -24,6 +25,7 @@ export function svgLayers(projection, context) {
var svg = d3_select(null);
var layers = [
{ id: 'osm', layer: svgOsm(projection, context, dispatch) },
{ id: 'notes', layer: svgNotes(projection, context, dispatch) },
{ id: 'gpx', layer: svgGpx(projection, context, dispatch) },
{ id: 'mvt', layer: svgMvt(projection, context, dispatch) },
{ id: 'streetside', layer: svgStreetside(projection, context, dispatch)},
+173
View File
@@ -0,0 +1,173 @@
import _throttle from 'lodash-es/throttle';
import { select as d3_select } from 'd3-selection';
import { svgPointTransform } from './index';
import { services } from '../services';
export function svgNotes(projection, context, dispatch) {
var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
var minZoom = 12;
var layer = d3_select(null);
var _notes;
function init() {
if (svgNotes.initialized) return; // run once
svgNotes.enabled = false;
svgNotes.initialized = true;
}
function editOn() {
layer.style('display', 'block');
}
function editOff() {
layer.selectAll('.note').remove();
layer.style('display', 'none');
}
function getService() {
if (services.osm && !_notes) {
_notes = services.osm;
_notes.on('loadedNotes', throttledRedraw);
} else if (!services.osm && _notes) {
_notes = null;
}
return _notes;
}
function showLayer() {
editOn();
layer
.style('opacity', 0)
.transition()
.duration(250)
.style('opacity', 1)
.on('end', function () { dispatch.call('change'); });
}
function hideLayer() {
throttledRedraw.cancel();
layer
.transition()
.duration(250)
.style('opacity', 0)
.on('end', editOff);
}
function update() {
var service = getService();
var selectedID = context.selectedNoteID();
var data = (service ? service.notes(projection) : []);
var transform = svgPointTransform(projection);
var notes = layer.selectAll('.note')
.data(data, function(d) { return d.status + d.id; });
// exit
notes.exit()
.remove();
// enter
var notesEnter = notes.enter()
.append('g')
.attr('class', function(d) { return 'note note-' + d.id + ' ' + d.status; });
// notesEnter
// .append('use')
// .attr('class', 'note-shadow')
// .attr('width', '24px')
// .attr('height', '24px')
// .attr('x', '-12px')
// .attr('y', '-24px')
// .attr('xlink:href', '#iD-icon-note');
notesEnter
.append('use')
.attr('class', 'note-fill')
.attr('width', '20px')
.attr('height', '20px')
.attr('x', '-10px')
.attr('y', '-22px')
.attr('xlink:href', '#iD-icon-note');
// add dots if there's a comment thread
notesEnter.selectAll('.note-annotation')
.data(function(d) { return d.comments.length > 1 ? [0] : []; })
.enter()
.append('use')
.attr('class', 'note-annotation thread')
.attr('width', '14px')
.attr('height', '14px')
.attr('x', '-7px')
.attr('y', '-20px')
.attr('xlink:href', '#iD-icon-more');
// update
notes
.merge(notesEnter)
.sort(function(a, b) {
return (a.id === selectedID) ? 1
: (b.id === selectedID) ? -1
: b.loc[1] - a.loc[1]; // sort Y
})
.classed('selected', function(d) { return d.id === selectedID; })
.attr('transform', transform);
}
function drawNotes(selection) {
var enabled = svgNotes.enabled;
var service = getService();
layer = selection.selectAll('.layer-notes')
.data(service ? [0] : []);
layer.exit()
.remove();
layer.enter()
.append('g')
.attr('class', 'layer-notes')
.style('display', enabled ? 'block' : 'none')
.merge(layer);
function dimensions() {
return [window.innerWidth, window.innerHeight];
}
if (enabled) {
if (service && ~~context.map().zoom() >= minZoom) {
editOn();
service.loadNotes(projection, dimensions());
update();
} else {
editOff();
}
}
}
drawNotes.enabled = function(_) {
if (!arguments.length) return svgNotes.enabled;
svgNotes.enabled = _;
if (svgNotes.enabled) {
showLayer();
} else {
hideLayer();
}
dispatch.call('change');
return this;
};
init();
return drawNotes;
}
+2 -2
View File
@@ -220,14 +220,14 @@ export function uiCommit(context) {
buttonEnter
.append('button')
.attr('class', 'secondary-action col5 button cancel-button')
.attr('class', 'secondary-action button cancel-button')
.append('span')
.attr('class', 'label')
.text(t('commit.cancel'));
buttonEnter
.append('button')
.attr('class', 'action col5 button save-button')
.attr('class', 'action button save-button')
.append('span')
.attr('class', 'label')
.text(t('commit.save'));
+4
View File
@@ -34,6 +34,10 @@ export { uiMapInMap } from './map_in_map';
export { uiModal } from './modal';
export { uiModes } from './modes';
export { uiNotice } from './notice';
export { uiNoteComments } from './note_comments';
export { uiNoteEditor } from './note_editor';
export { uiNoteHeader } from './note_header';
export { uiNoteReport } from './note_report';
export { uiPresetEditor } from './preset_editor';
export { uiPresetIcon } from './preset_icon';
export { uiPresetList } from './preset_list';
+9 -6
View File
@@ -44,11 +44,12 @@ export function uiInspector(context) {
var presetPane = wrap.selectAll('.preset-list-pane');
var editorPane = wrap.selectAll('.entity-editor-pane');
var graph = context.graph(),
entity = context.entity(_entityID),
showEditor = _state === 'hover' ||
entity.isUsed(graph) ||
entity.isHighwayIntersection(graph);
var graph = context.graph();
var entity = context.entity(_entityID);
var showEditor = _state === 'hover' ||
entity.isUsed(graph) ||
entity.isHighwayIntersection(graph);
if (showEditor) {
wrap.style('right', '0%');
@@ -67,7 +68,9 @@ export function uiInspector(context) {
.merge(footer);
footer
.call(uiViewOnOSM(context).entityID(_entityID));
.call(uiViewOnOSM(context)
.what(context.hasEntity(_entityID))
);
function showList(preset) {
+2 -2
View File
@@ -71,7 +71,7 @@ export function uiIntro(context) {
var background = context.background().baseLayerSource();
var overlays = context.background().overlayLayerSources();
var opacity = d3_selectAll('#map .layer-background').style('opacity');
var loadedTiles = osm && osm.loadedTiles();
var caches = osm && osm.caches();
var baseEntities = context.history().graph().base().entities;
var countryCode = services.geocoder.countryCode;
@@ -147,7 +147,7 @@ export function uiIntro(context) {
curtain.remove();
navwrap.remove();
d3_selectAll('#map .layer-background').style('opacity', opacity);
if (osm) { osm.toggle(true).reset().loadedTiles(loadedTiles); }
if (osm) { osm.toggle(true).reset().caches(caches); }
context.history().reset().merge(_values(baseEntities));
context.background().baseLayerSource(background);
overlays.forEach(function (d) { context.background().toggleOverlayLayer(d); });
+35 -29
View File
@@ -86,7 +86,7 @@ export function uiMapData(context) {
function drawPhotoItems(selection) {
var photoKeys = ['streetside','mapillary-images', 'mapillary-signs', 'openstreetcam-images'];
var photoKeys = ['streetside', 'mapillary-images', 'mapillary-signs', 'openstreetcam-images'];
var photoLayers = layers.all().filter(function(obj) { return photoKeys.indexOf(obj.id) !== -1; });
var data = photoLayers.filter(function(obj) { return obj.layer.supported(); });
@@ -147,58 +147,64 @@ export function uiMapData(context) {
}
function drawOsmItem(selection) {
var osm = layers.layer('osm'),
showsOsm = osm.enabled();
function drawOsmItems(selection) {
var osmKeys = ['osm', 'notes'];
var osmLayers = layers.all().filter(function(obj) { return osmKeys.indexOf(obj.id) !== -1; });
var ul = selection
.selectAll('.layer-list-osm')
.data(osm ? [0] : []);
.data([0]);
// Exit
ul.exit()
ul = ul.enter()
.append('ul')
.attr('class', 'layer-list layer-list-osm')
.merge(ul);
var li = ul.selectAll('.list-item')
.data(osmLayers);
li.exit()
.remove();
// Enter
var ulEnter = ul.enter()
.append('ul')
.attr('class', 'layer-list layer-list-osm');
var liEnter = ulEnter
var liEnter = li.enter()
.append('li')
.attr('class', 'list-item-osm');
.attr('class', function(d) { return 'list-item list-item-' + d.id; });
var labelEnter = liEnter
.append('label')
.call(tooltip()
.title(t('map_data.layers.osm.tooltip'))
.placement('bottom')
);
.each(function(d) {
d3_select(this)
.call(tooltip()
.title(t('map_data.layers.' + d.id + '.tooltip'))
.placement('bottom')
);
});
labelEnter
.append('input')
.attr('type', 'checkbox')
.on('change', function() { toggleLayer('osm'); });
.on('change', function(d) { toggleLayer(d.id); });
labelEnter
.append('span')
.text(t('map_data.layers.osm.title'));
.text(function(d) { return t('map_data.layers.' + d.id + '.title'); });
// Update
ul = ul
.merge(ulEnter);
li = li
.merge(liEnter);
ul.selectAll('.list-item-osm')
.classed('active', showsOsm)
li
.classed('active', function (d) { return d.layer.enabled(); })
.selectAll('input')
.property('checked', showsOsm);
.property('checked', function (d) { return d.layer.enabled(); });
}
function drawGpxItem(selection) {
var gpx = layers.layer('gpx'),
hasGpx = gpx && gpx.hasGpx(),
showsGpx = hasGpx && gpx.enabled();
var gpx = layers.layer('gpx');
var hasGpx = gpx && gpx.hasGpx();
var showsGpx = hasGpx && gpx.enabled();
var ul = selection
.selectAll('.layer-list-gpx')
@@ -448,7 +454,7 @@ export function uiMapData(context) {
function update() {
_dataLayerContainer
.call(drawOsmItem)
.call(drawOsmItems)
.call(drawPhotoItems)
.call(drawGpxItem);
// .call(drawMvtItem);
+116
View File
@@ -0,0 +1,116 @@
import { select as d3_select } from 'd3-selection';
import { t } from '../util/locale';
import { svgIcon } from '../svg';
import { services } from '../services';
import { utilDetect } from '../util/detect';
export function uiNoteComments() {
var _note;
function noteComments(selection) {
var comments = selection.selectAll('.comments-container')
.data([0]);
comments = comments.enter()
.append('div')
.attr('class', 'comments-container')
.merge(comments);
var commentEnter = comments.selectAll('.comment')
.data(_note.comments)
.enter()
.append('div')
.attr('class', 'comment');
commentEnter
.append('div')
.attr('class', function(d) { return 'comment-avatar user-' + d.uid; })
.call(svgIcon('#iD-icon-avatar', 'comment-avatar-icon'));
var mainEnter = commentEnter
.append('div')
.attr('class', 'comment-main');
var metadataEnter = mainEnter
.append('div')
.attr('class', 'comment-metadata');
metadataEnter
.append('div')
.attr('class', 'comment-author')
.each(function(d) {
var selection = d3_select(this);
var osm = services.osm;
if (osm && d.user) {
selection = selection
.append('a')
.attr('class', 'comment-author-link')
.attr('href', osm.userURL(d.user))
.attr('tabindex', -1)
.attr('target', '_blank');
}
selection
.text(function(d) { return d.user || t('note.anonymous'); });
});
metadataEnter
.append('div')
.attr('class', 'comment-date')
.text(function(d) { return d.action + ' ' + localeDateString(d.date); });
mainEnter
.append('div')
.attr('class', 'comment-text')
.text(function(d) { return d.text; });
comments
.call(replaceAvatars);
}
function replaceAvatars(selection) {
var osm = services.osm;
if (!osm) return;
var uids = {}; // gather uids in the comment thread
_note.comments.forEach(function(d) {
if (d.uid) uids[d.uid] = true;
});
Object.keys(uids).forEach(function(uid) {
osm.loadUser(uid, function(err, user) {
if (!user || !user.image_url) return;
selection.selectAll('.comment-avatar.user-' + uid)
.html('')
.append('img')
.attr('class', 'icon comment-avatar-icon')
.attr('src', user.image_url)
.attr('alt', user.display_name);
});
});
}
function localeDateString(s) {
if (!s) return null;
var detected = utilDetect();
var options = { day: 'numeric', month: 'short', year: 'numeric' };
var d = new Date(s);
if (isNaN(d.getTime())) return null;
return d.toLocaleDateString(detected.locale, options);
}
noteComments.note = function(_) {
if (!arguments.length) return _note;
_note = _;
return noteComments;
};
return noteComments;
}
+201
View File
@@ -0,0 +1,201 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import { t } from '../util/locale';
import { services } from '../services';
import { modeBrowse } from '../modes';
import { svgIcon } from '../svg';
import {
uiNoteComments,
uiNoteHeader,
uiNoteReport,
uiViewOnOSM,
} from './index';
import {
utilNoAuto,
utilRebind
} from '../util';
export function uiNoteEditor(context) {
var dispatch = d3_dispatch('change');
var noteComments = uiNoteComments();
var noteHeader = uiNoteHeader();
var _note;
function noteEditor(selection) {
var header = selection.selectAll('.header')
.data([0]);
var headerEnter = header.enter()
.append('div')
.attr('class', 'header fillL');
headerEnter
.append('button')
.attr('class', 'fr note-editor-close')
.on('click', function() { context.enter(modeBrowse(context)); })
.call(svgIcon('#iD-icon-close'));
headerEnter
.append('h3')
.text(t('note.title'));
var body = selection.selectAll('.body')
.data([0]);
body = body.enter()
.append('div')
.attr('class', 'body')
.merge(body);
body.selectAll('.note-editor')
.data([0])
.enter()
.append('div')
.attr('class', 'modal-section note-editor')
.call(noteHeader.note(_note))
.call(noteComments.note(_note))
.call(noteSave);
selection.selectAll('.footer')
.data([0])
.enter()
.append('div')
.attr('class', 'footer')
.call(uiViewOnOSM(context).what(_note))
.call(uiNoteReport(context).note(_note));
}
function noteSave(selection) {
var isSelected = (_note && _note.id === context.selectedNoteID());
var noteSave = selection.selectAll('.note-save-section')
.data((isSelected ? [_note] : []), function(d) { return d.status + d.id; });
// exit
noteSave.exit()
.remove();
// enter
var noteSaveEnter = noteSave.enter()
.append('div')
.attr('class', 'note-save-section save-section cf');
noteSaveEnter
.append('h4')
.attr('class', '.note-save-header')
.text(t('note.newComment'));
noteSaveEnter
.append('textarea')
.attr('id', 'new-comment-input')
.attr('placeholder', t('note.inputPlaceholder'))
.attr('maxlength', 1000)
.property('value', function(d) { return d.newComment; })
.call(utilNoAuto)
.on('input', change)
.on('blur', change);
// update
noteSave = noteSaveEnter
.merge(noteSave)
.call(noteSaveButtons);
function change() {
var input = d3_select(this);
var val = input.property('value').trim() || undefined;
// store the unsaved comment with the note itself
_note = _note.update({ newComment: val });
var osm = services.osm;
if (osm) {
osm.replaceNote(_note); // update note cache
}
noteSave
.call(noteSaveButtons);
}
}
function noteSaveButtons(selection) {
var isSelected = (_note && _note.id === context.selectedNoteID());
var buttonSection = selection.selectAll('.buttons')
.data((isSelected ? [_note] : []), function(d) { return d.status + d.id; });
// exit
buttonSection.exit()
.remove();
// enter
var buttonEnter = buttonSection.enter()
.append('div')
.attr('class', 'buttons');
buttonEnter
.append('button')
.attr('class', 'button status-button action')
.append('span')
.attr('class', 'label');
buttonEnter
.append('button')
.attr('class', 'button comment-button action')
.append('span')
.attr('class', 'label')
.text(t('note.comment'));
// update
buttonSection = buttonSection
.merge(buttonEnter);
buttonSection.select('.status-button') // select and propagate data
.text(function(d) {
var action = (d.status === 'open' ? 'close' : 'open');
var andComment = (d.newComment ? '_comment' : '');
return t('note.' + action + andComment);
})
.on('click.status', function(d) {
this.blur(); // avoid keeping focus on the button - #4641
var osm = services.osm;
if (osm) {
var setStatus = (d.status === 'open' ? 'closed' : 'open');
osm.postNoteUpdate(d, setStatus, function(err, note) {
dispatch.call('change', note);
});
}
});
buttonSection.select('.comment-button') // select and propagate data
.attr('disabled', function(d) {
return (d.status === 'open' && d.newComment) ? null : true;
})
.on('click.save', function(d) {
this.blur(); // avoid keeping focus on the button - #4641
var osm = services.osm;
if (osm) {
osm.postNoteUpdate(d, d.status, function(err, note) {
dispatch.call('change', note);
});
}
});
}
noteEditor.note = function(_) {
if (!arguments.length) return _note;
_note = _;
return noteEditor;
};
return utilRebind(noteEditor, dispatch, 'on');
}
+59
View File
@@ -0,0 +1,59 @@
import { t } from '../util/locale';
import { svgIcon } from '../svg';
export function uiNoteHeader() {
var _note;
function noteHeader(selection) {
var header = selection.selectAll('.note-header')
.data(
(_note ? [_note] : []),
function(d) { return d.status + d.id; }
);
header.exit()
.remove();
var headerEnter = header.enter()
.append('div')
.attr('class', 'note-header');
var iconEnter = headerEnter
.append('div')
.attr('class', function(d) { return 'note-header-icon ' + d.status; });
iconEnter
.append('div')
.attr('class', 'preset-icon-28')
.call(svgIcon('#iD-icon-note', 'note-fill'));
iconEnter.each(function(d) {
if (d.comments.length > 1) {
iconEnter
.append('div')
.attr('class', 'note-icon-annotation')
.call(svgIcon('#iD-icon-more', 'note-annotation'));
}
});
headerEnter
.append('div')
.attr('class', 'note-header-label')
.text(function(d) {
return t('note.note') + ' ' + d.id + ' ' +
(d.status === 'closed' ? t('note.closed') : '');
});
}
noteHeader.note = function(_) {
if (!arguments.length) return _note;
_note = _;
return noteHeader;
};
return noteHeader;
}
+47
View File
@@ -0,0 +1,47 @@
import { t } from '../util/locale';
import { svgIcon } from '../svg';
import {
osmNote
} from '../osm';
export function uiNoteReport() {
var _note;
var url = 'https://www.openstreetmap.org/reports/new?reportable_id=';
function noteReport(selection) {
if (!(_note instanceof osmNote)) return;
url += _note.id + '&reportable_type=Note';
var data = ((!_note || _note.isNew()) ? [] : [_note]);
var link = selection.selectAll('.note-report')
.data(data, function(d) { return d.id; });
// exit
link.exit()
.remove();
// enter
var linkEnter = link.enter()
.append('a')
.attr('class', 'note-report')
.attr('target', '_blank')
.attr('href', url)
.call(svgIcon('#iD-icon-out-link', 'inline'));
linkEnter
.append('span')
.text(t('note.report'));
}
noteReport.note = function(_) {
if (!arguments.length) return _note;
_note = _;
return noteReport;
};
return noteReport;
}
+40 -15
View File
@@ -1,11 +1,19 @@
import _throttle from 'lodash-es/throttle';
import { selectAll as d3_selectAll } from 'd3-selection';
import { osmNote } from '../osm';
import { uiFeatureList } from './feature_list';
import { uiInspector } from './inspector';
import { uiNoteEditor } from './note_editor';
export function uiSidebar(context) {
var inspector = uiInspector(context),
current;
var inspector = uiInspector(context);
var noteEditor = uiNoteEditor(context);
var _current;
var _wasNote = false;
// var layer = d3_select(null);
function sidebar(selection) {
@@ -20,8 +28,19 @@ export function uiSidebar(context) {
.attr('class', 'inspector-hidden inspector-wrap fr');
function hover(id) {
if (!current && context.hasEntity(id)) {
function hover(what) {
if ((what instanceof osmNote)) {
_wasNote = true;
var notes = d3_selectAll('.note');
notes
.classed('hovered', function(d) { return d === what; });
sidebar.show(noteEditor.note(what));
selection.selectAll('.sidebar-component')
.classed('inspector-hover', true);
} else if (!_current && context.hasEntity(what)) {
featureListWrap
.classed('inspector-hidden', true);
@@ -29,22 +48,28 @@ export function uiSidebar(context) {
.classed('inspector-hidden', false)
.classed('inspector-hover', true);
if (inspector.entityID() !== id || inspector.state() !== 'hover') {
if (inspector.entityID() !== what || inspector.state() !== 'hover') {
inspector
.state('hover')
.entityID(id);
.entityID(what);
inspectorWrap
.call(inspector);
}
} else if (!current) {
} else if (!_current) {
featureListWrap
.classed('inspector-hidden', false);
inspectorWrap
.classed('inspector-hidden', true);
inspector
.state('hide');
} else if (_wasNote) {
_wasNote = false;
d3_selectAll('.note')
.classed('hovered', false);
sidebar.hide();
}
}
@@ -53,7 +78,7 @@ export function uiSidebar(context) {
sidebar.select = function(id, newFeature) {
if (!current && id) {
if (!_current && id) {
featureListWrap
.classed('inspector-hidden', true);
@@ -71,7 +96,7 @@ export function uiSidebar(context) {
.call(inspector);
}
} else if (!current) {
} else if (!_current) {
featureListWrap
.classed('inspector-hidden', false);
inspectorWrap
@@ -82,17 +107,17 @@ export function uiSidebar(context) {
};
sidebar.show = function(component) {
sidebar.show = function(component, element) {
featureListWrap
.classed('inspector-hidden', true);
inspectorWrap
.classed('inspector-hidden', true);
if (current) current.remove();
current = selection
if (_current) _current.remove();
_current = selection
.append('div')
.attr('class', 'sidebar-component')
.call(component);
.call(component, element);
};
@@ -102,8 +127,8 @@ export function uiSidebar(context) {
inspectorWrap
.classed('inspector-hidden', true);
if (current) current.remove();
current = null;
if (_current) _current.remove();
_current = null;
};
}
+25 -14
View File
@@ -1,37 +1,48 @@
import { t } from '../util/locale';
import { svgIcon } from '../svg';
import {
osmEntity,
osmNote
} from '../osm';
export function uiViewOnOSM(context) {
var id;
var _what; // an osmEntity or osmNote
function viewOnOSM(selection) {
var entity = context.entity(id);
selection.style('display', entity.isNew() ? 'none' : null);
var url;
if (_what instanceof osmEntity) {
url = context.connection().entityURL(_what);
} else if (_what instanceof osmNote) {
url = context.connection().noteURL(_what);
}
var data = ((!_what || _what.isNew()) ? [] : [_what]);
var link = selection.selectAll('.view-on-osm')
.data([0]);
.data(data, function(d) { return d.id; });
var enter = link.enter()
// exit
link.exit()
.remove();
// enter
var linkEnter = link.enter()
.append('a')
.attr('class', 'view-on-osm')
.attr('target', '_blank')
.attr('href', url)
.call(svgIcon('#iD-icon-out-link', 'inline'));
enter
linkEnter
.append('span')
.text(t('inspector.view_on_osm'));
link
.merge(enter)
.attr('href', context.connection().entityURL(entity));
}
viewOnOSM.entityID = function(_) {
if (!arguments.length) return id;
id = _;
viewOnOSM.what = function(_) {
if (!arguments.length) return _what;
_what = _;
return viewOnOSM;
};