mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-25 01:24:05 +02:00
Merge branch 'notes'
This commit is contained in:
@@ -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
@@ -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..
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -513,7 +513,6 @@ export function modeSelect(context, selectedIDs) {
|
||||
showMenu();
|
||||
}
|
||||
}, 270); /* after any centerEase completes */
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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';
|
||||
|
||||
@@ -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)},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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'));
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user