mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-25 01:24:05 +02:00
add list of loaded local photos
This commit is contained in:
+37
-19
@@ -1,7 +1,7 @@
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
import exifr from 'exifr';
|
||||
|
||||
import { utilDetect } from '../util/detect';
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
import { geoExtent } from '../geo';
|
||||
import { isArray, isNumber } from 'lodash-es';
|
||||
|
||||
@@ -12,7 +12,8 @@ export function svgLocalPhotos(projection, context, dispatch) {
|
||||
var detected = utilDetect();
|
||||
let layer = d3_select(null);
|
||||
var _fileList;
|
||||
var _imageList = [];
|
||||
var _photos = [];
|
||||
var _idAutoinc = 0;
|
||||
|
||||
function init() {
|
||||
if (_initialized) return; // run once
|
||||
@@ -45,7 +46,7 @@ export function svgLocalPhotos(projection, context, dispatch) {
|
||||
}
|
||||
|
||||
// opens the image at bottom left
|
||||
function click(d3_event, image) {
|
||||
function click(d3_event, image, zoomTo) {
|
||||
// removes old div(s), if any
|
||||
closePhotoViewer();
|
||||
|
||||
@@ -71,7 +72,9 @@ export function svgLocalPhotos(projection, context, dispatch) {
|
||||
|
||||
|
||||
// centers the map with image location
|
||||
context.map().centerEase(image.loc);
|
||||
if (zoomTo) {
|
||||
context.map().centerEase(image.loc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +130,7 @@ export function svgLocalPhotos(projection, context, dispatch) {
|
||||
|
||||
function drawPhotos(selection) {
|
||||
layer = selection.selectAll('.layer-local-photos')
|
||||
.data(_fileList ? [0] : []);
|
||||
.data(_photos ? [0] : []);
|
||||
|
||||
layer.exit()
|
||||
.remove();
|
||||
@@ -143,20 +146,18 @@ export function svgLocalPhotos(projection, context, dispatch) {
|
||||
layer = layerEnter
|
||||
.merge(layer);
|
||||
|
||||
// if (_imageList.length !== 0) {
|
||||
// if (_fileList && _fileList.length !== 0) {
|
||||
if (_imageList && _imageList.length !== 0) {
|
||||
display_markers(_imageList);
|
||||
if (_photos && _photos.length !== 0) {
|
||||
display_markers(_photos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads and parses files
|
||||
* @param {Array<object>} arrayFiles - Holds array of file - [file_1, file_2, ...]
|
||||
* @param {Array<object>} files - Holds array of file - [file_1, file_2, ...]
|
||||
*/
|
||||
async function readmultifiles(arrayFiles) {
|
||||
const filePromises = arrayFiles.map((file, i) => {
|
||||
async function readmultifiles(files) {
|
||||
const filePromises = files.map(file => {
|
||||
// Return a promise per file
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
@@ -167,8 +168,12 @@ export function svgLocalPhotos(projection, context, dispatch) {
|
||||
try {
|
||||
const response = await exifr.parse(file)
|
||||
.then(output => {
|
||||
_imageList.push({
|
||||
id: i,
|
||||
if (_photos.find(i => i.name === file.name && i.src === reader.result)) {
|
||||
// skip if already loaded photos
|
||||
return;
|
||||
}
|
||||
_photos.push({
|
||||
id: _idAutoinc++,
|
||||
name: file.name,
|
||||
src: reader.result,
|
||||
loc: [output.longitude, output.latitude]
|
||||
@@ -177,10 +182,12 @@ export function svgLocalPhotos(projection, context, dispatch) {
|
||||
// Resolve the promise with the response value
|
||||
resolve(response);
|
||||
} catch (err) {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
reject(err);
|
||||
}
|
||||
};
|
||||
reader.onerror = (error) => {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
reject(error);
|
||||
};
|
||||
|
||||
@@ -188,15 +195,14 @@ export function svgLocalPhotos(projection, context, dispatch) {
|
||||
});
|
||||
|
||||
// Wait for all promises to be resolved
|
||||
await Promise.all(filePromises);
|
||||
await Promise.allSettled(filePromises);
|
||||
_photos = _photos.sort((a, b) => a.id - b.id);
|
||||
dispatch.call('change');
|
||||
}
|
||||
|
||||
drawPhotos.setFile = function(fileList) {
|
||||
// read and parse asynchronously
|
||||
readmultifiles(Array.from(fileList));
|
||||
|
||||
dispatch.call('change');
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -219,8 +225,20 @@ export function svgLocalPhotos(projection, context, dispatch) {
|
||||
return this;
|
||||
};
|
||||
|
||||
drawPhotos.getPhotos = function() {
|
||||
return _photos;
|
||||
};
|
||||
|
||||
drawPhotos.removePhoto = function(id) {
|
||||
_photos = _photos.filter(i => i.id !== id);
|
||||
dispatch.call('change');
|
||||
return _photos;
|
||||
};
|
||||
|
||||
drawPhotos.openPhoto = click;
|
||||
|
||||
drawPhotos.fitZoom = function() {
|
||||
let extent = _imageList
|
||||
let extent = _photos
|
||||
.map(image => image.loc)
|
||||
.filter(l => isArray(l) && isNumber(l[0]) && isNumber(l[1]))
|
||||
.map(l => geoExtent(l, l))
|
||||
@@ -268,7 +286,7 @@ export function svgLocalPhotos(projection, context, dispatch) {
|
||||
};
|
||||
|
||||
drawPhotos.hasData = function() {
|
||||
return isArray(_imageList) && _imageList.length > 0;
|
||||
return isArray(_photos) && _photos.length > 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -390,7 +390,7 @@ export function uiSectionPhotoOverlays(context) {
|
||||
.append('button')
|
||||
.attr('class', 'zoom-to-data')
|
||||
.call(uiTooltip()
|
||||
.title(() => t.append('map_data.layers.custom.zoom'))
|
||||
.title(() => t.append('local_photos.zoom'))
|
||||
.placement((localizer.textDirection() === 'rtl') ? 'right' : 'left')
|
||||
)
|
||||
.on('click', function(d3_event) {
|
||||
@@ -427,9 +427,7 @@ export function uiSectionPhotoOverlays(context) {
|
||||
function localPhotosChanged(d) {
|
||||
var localPhotosLayer = layers.layer('local-photos');
|
||||
|
||||
if (d && d.fileList) {
|
||||
localPhotosLayer.fileList(d.fileList);
|
||||
}
|
||||
localPhotosLayer.fileList(d);
|
||||
}
|
||||
|
||||
context.layers().on('change.uiSectionPhotoOverlays', section.reRender);
|
||||
|
||||
@@ -292,7 +292,11 @@ export function uiSectionRawTagEditor(id, context) {
|
||||
});
|
||||
|
||||
items.selectAll('button.remove')
|
||||
.on(('PointerEvent' in window ? 'pointer' : 'mouse') + 'down', removeTag); // 'click' fires too late - #5878
|
||||
.on(('PointerEvent' in window ? 'pointer' : 'mouse') + 'down', // 'click' fires too late - #5878
|
||||
(d3_event, d) => {
|
||||
if (d3_event.button !== 0) return;
|
||||
removeTag(d3_event, d);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,87 +1,139 @@
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { t } from '../../core/localizer';
|
||||
import { uiConfirm } from '../confirm';
|
||||
import { utilRebind } from '../../util';
|
||||
import { isArray, isNumber } from 'lodash-es';
|
||||
import { uiTooltip } from '../tooltip';
|
||||
import { svgIcon } from '../../svg';
|
||||
|
||||
|
||||
export function uiSettingsLocalPhotos(context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var photoLayer = context.layers().layer('local-photos');
|
||||
var modal;
|
||||
|
||||
function render(selection) {
|
||||
var dataLayer = context.layers().layer('local-photos');
|
||||
|
||||
var _currSettings = {
|
||||
fileList: (dataLayer && dataLayer.fileList()) || null
|
||||
};
|
||||
|
||||
// var example = 'https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png';
|
||||
var modal = uiConfirm(selection).okButton();
|
||||
modal = uiConfirm(selection).okButton();
|
||||
|
||||
modal
|
||||
.classed('settings-modal settings-custom-data', true);
|
||||
.classed('settings-modal settings-local-photos', true);
|
||||
|
||||
modal.select('.modal-section.header')
|
||||
.append('h3')
|
||||
.call(t.append('local_photos.header'));
|
||||
|
||||
modal.select('.modal-section.message-text')
|
||||
.append('div')
|
||||
.classed('local-photos', true);
|
||||
|
||||
var textSection = modal.select('.modal-section.message-text');
|
||||
var instructionsSection = modal.select('.modal-section.message-text .local-photos')
|
||||
.append('div')
|
||||
.classed('instructions', true);
|
||||
|
||||
textSection
|
||||
.append('pre')
|
||||
.attr('class', 'instructions-local-photos')
|
||||
instructionsSection
|
||||
.append('p')
|
||||
.classed('instructions-local-photos', true)
|
||||
.call(t.append('local_photos.file.instructions'));
|
||||
|
||||
textSection
|
||||
instructionsSection
|
||||
.append('input')
|
||||
.attr('class', 'field-file')
|
||||
.classed('field-file', true)
|
||||
.attr('type', 'file')
|
||||
.attr('multiple', 'multiple')
|
||||
// .attr('accept', '.gpx,.kml,.geojson,.json,application/gpx+xml,application/vnd.google-earth.kml+xml,application/geo+json,application/json')
|
||||
.property('files', _currSettings.fileList)
|
||||
.attr('accept', '.jpg,.jpeg,.png,image/png,image/jpeg')
|
||||
.style('visibility', 'hidden')
|
||||
.attr('id', 'local-photo-files')
|
||||
.on('change', function(d3_event) {
|
||||
var files = d3_event.target.files;
|
||||
if (files && files.length) {
|
||||
_currSettings.fileList = files;
|
||||
} else {
|
||||
_currSettings.fileList = null;
|
||||
previews
|
||||
.select('ul')
|
||||
.append('li')
|
||||
.classed('placeholder', true)
|
||||
.append('div');
|
||||
dispatch.call('change', this, files);
|
||||
}
|
||||
d3_event.target.value = null;
|
||||
});
|
||||
instructionsSection
|
||||
.append('label')
|
||||
.attr('for', 'local-photo-files')
|
||||
.classed('button', true)
|
||||
.call(t.append('local_photos.file.label'));
|
||||
|
||||
const previews = modal.select('.modal-section.message-text .local-photos')
|
||||
.append('div')
|
||||
.append('div')
|
||||
.classed('preview-local-photos', true)
|
||||
|
||||
// insert a cancel button
|
||||
var buttonSection = modal.select('.modal-section.buttons');
|
||||
previews
|
||||
.append('ul');
|
||||
|
||||
buttonSection
|
||||
.insert('button', '.ok-button')
|
||||
.attr('class', 'button cancel-button secondary-action')
|
||||
.call(t.append('confirm.cancel'));
|
||||
updatePreviews(previews.select('ul'));
|
||||
|
||||
context.layers().on('change', () => updatePreviews(previews.select('ul')));
|
||||
}
|
||||
|
||||
buttonSection.select('.cancel-button')
|
||||
.on('click.cancel', clickCancel);
|
||||
|
||||
buttonSection.select('.ok-button')
|
||||
.attr('disabled', isSaveDisabled)
|
||||
.on('click.save', clickSave);
|
||||
|
||||
|
||||
function isSaveDisabled() {
|
||||
return null;
|
||||
function updatePreviews(container) {
|
||||
function locationUnavailable(d) {
|
||||
return !(isArray(d.loc) && isNumber(d.loc[0]) && isNumber(d.loc[1]));
|
||||
}
|
||||
|
||||
container.selectAll('li.placeholder').remove();
|
||||
|
||||
function clickCancel() {
|
||||
this.blur();
|
||||
modal.close();
|
||||
}
|
||||
let selection = container.selectAll('li')
|
||||
.data(photoLayer.getPhotos() ?? [], d => d.id);
|
||||
selection.exit()
|
||||
.remove();
|
||||
|
||||
function clickSave() {
|
||||
this.blur();
|
||||
modal.close();
|
||||
dispatch.call('change', this, _currSettings);
|
||||
}
|
||||
const selectionEnter = selection.enter()
|
||||
.append('li');
|
||||
|
||||
selectionEnter
|
||||
.append('span')
|
||||
.classed('filename', true);
|
||||
selectionEnter
|
||||
.append('button')
|
||||
.classed('form-field-button zoom-to-data', true)
|
||||
.attr('title', t('local_photos.zoom_single'))
|
||||
.call(svgIcon('#iD-icon-framed-dot'));
|
||||
selectionEnter
|
||||
.append('button')
|
||||
.classed('form-field-button no-geolocation', true)
|
||||
.call(svgIcon('#iD-icon-alert'))
|
||||
.call(uiTooltip()
|
||||
.title(() => t.append('local_photos.no_geolocation.tooltip'))
|
||||
.placement('left')
|
||||
);
|
||||
selectionEnter
|
||||
.append('button')
|
||||
.classed('form-field-button remove', true)
|
||||
.attr('title', t('icons.remove'))
|
||||
.call(svgIcon('#iD-operation-delete'));
|
||||
|
||||
selection = selection.merge(selectionEnter);
|
||||
|
||||
selection
|
||||
.classed('invalid', locationUnavailable);
|
||||
selection.select('span.filename')
|
||||
.text(d => d.name)
|
||||
.attr('title', d => d.name);
|
||||
selection.select('span.filename')
|
||||
.on('click', (d3_event, d) => {
|
||||
photoLayer.openPhoto(d3_event, d, false);
|
||||
});
|
||||
selection.select('button.zoom-to-data')
|
||||
.on('click', (d3_event, d) => {
|
||||
photoLayer.openPhoto(d3_event, d, true);
|
||||
});
|
||||
selection.select('button.remove')
|
||||
.on('click', (d3_event, d) => {
|
||||
photoLayer.removePhoto(d.id);
|
||||
updatePreviews(container);
|
||||
});
|
||||
}
|
||||
|
||||
return utilRebind(render, dispatch, 'on');
|
||||
|
||||
Reference in New Issue
Block a user