-
Notifications
You must be signed in to change notification settings - Fork 283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Drag and Drop JSON File to Tile Layer to Reconstruct Saved Map #1345
Changes from 13 commits
09c3b32
da8f401
d2abe87
3173526
ab8d053
7f1908e
dbe4623
8922aeb
63f1f67
eacd8d1
fd84324
1d29f8d
19e42a2
ed8c56b
a19e37e
29804dd
e2469b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ import {Paginator} from './modules/paginator.js'; | |
let map; | ||
const welcomeModal = document.getElementById('welcomeModal'); | ||
const tileMap = document.getElementById('map'); | ||
const beginBtn = document.getElementById('beginBtn'); | ||
const restoreWelcomeModal = document.getElementById('restoreWelcomeModalBtn'); | ||
const sidebar = document.getElementById('offcanvasRight'); | ||
const form = document.getElementById('form'); | ||
|
@@ -15,10 +16,11 @@ let fetchedFrom; | |
let fetchedImages; | ||
let currPagination; // currPagination is used to initiate the Paginator Class | ||
let sidebarOpen = false; | ||
let mapReconstructionMode = false; | ||
let mapReconstructionMode = false; // map is reconstructed from json URL in this mode | ||
|
||
const setupMap = () => { | ||
map = L.map('map').setView([51.505, -0.09], 13); | ||
window.map = map; // make map global for debugging | ||
|
||
map.attributionControl.setPosition('bottomleft'); | ||
|
||
|
@@ -160,9 +162,10 @@ function showImages(getUrl) { | |
}); | ||
} | ||
|
||
welcomeModal.addEventListener('hidden.bs.modal', (event) => { | ||
new bootstrap.Offcanvas(sidebar).show(); | ||
sidebarOpen = true; | ||
beginBtn.addEventListener('click', (event) => { | ||
bootstrap.Modal.getInstance(welcomeModal).hide(); | ||
new bootstrap.Offcanvas(sidebar).show(); | ||
sidebarOpen = true; | ||
}); | ||
|
||
restoreWelcomeModal.addEventListener('click', (event) => { | ||
|
@@ -213,29 +216,28 @@ function isJsonDetected(url) { | |
function placeImage (imageURL, options, newImage = false) { | ||
let image; | ||
|
||
if (newImage) { | ||
if (newImage) { // Construct new map | ||
image = L.distortableImageOverlay( | ||
imageURL, | ||
{tooltipText: options.tooltipText} | ||
); | ||
} else { | ||
} else { // Reconstruct map to previous saved state | ||
image = L.distortableImageOverlay( | ||
imageURL, | ||
{ | ||
height: options.height, | ||
tooltipText: options.tooltipText, | ||
// corners: options.corners, <== uncomment this to see the effect of the corners | ||
corners: options.corners, // <== uncomment this to see the effect of the corners | ||
} | ||
); | ||
} | ||
|
||
map.imgGroup.addLayer(image); | ||
}; | ||
|
||
// Reconstruct Map from JSON | ||
document.addEventListener('DOMContentLoaded', async (event) => { | ||
if (mapReconstructionMode) { | ||
const url = location.href; | ||
// expected url format http://localhost:8081/examples/archive.html?json=https://archive.org/download/mkl-1/mkl-1.json | ||
const url = location.href; | ||
|
||
if (isJsonDetected(url)) { | ||
const jsonDownloadURL = extractJsonFromUrlParams(url); | ||
|
@@ -249,6 +251,9 @@ document.addEventListener('DOMContentLoaded', async (event) => { | |
let imageURL; | ||
let options; | ||
|
||
// let cornerBounds = .... // collect all corners into a big array of [lat, lon] | ||
// map.fitBounds(cornerBounds); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here's where we can collect up all the corner lat/lon pairs, and make an array. Then on the second line, we fit the window to contain them all before we start placing images! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay... many thanks! |
||
|
||
imageCollectionObj.imgCollectionProps.forEach((imageObj) => { | ||
imageURL = imageObj.src; | ||
options = { | ||
|
@@ -290,21 +295,115 @@ document.addEventListener('click', (event) => { | |
|
||
// download JSON | ||
saveMap.addEventListener('click', () => { | ||
const jsonImages = map.imgGroup.generateExportJson(true).images; | ||
const jsonImages = map.imgGroup.generateExportJson(true); | ||
// a check to prevent download of empty file | ||
if (jsonImages.length) { | ||
const encodedFile = 'text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(jsonImages)); | ||
if (jsonImages.images.length) { | ||
const modifiedJsonImages = {}; | ||
const tempCollection = []; | ||
|
||
// restructure jsonImages | ||
modifiedJsonImages.avg_cm_per_pixel = jsonImages.avg_cm_per_pixel; | ||
jsonImages.images.map((image) => { | ||
tempCollection.push({ | ||
id: image.id, | ||
src: image.src, | ||
width: image.width, | ||
height: image.height, | ||
tooltipText: image.tooltipText, | ||
image_file_name: image.image_file_name, | ||
nodes: image.nodes, | ||
cm_per_pixel: image.cm_per_pixel, | ||
}); | ||
}); | ||
modifiedJsonImages.collection = tempCollection; | ||
|
||
const encodedFile = 'text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(modifiedJsonImages)); | ||
const a = document.createElement('a'); | ||
a.href = 'data:' + encodedFile; | ||
const fileName = prompt('Use this file to recover your map’s saved state. Enter filename:'); | ||
a.download = fileName ? fileName + '.json' : 'MapknitterLite.json'; | ||
a.click(); | ||
} | ||
}) | ||
}); | ||
|
||
// share map modal | ||
const shareModal = document.getElementById('shareModal') | ||
const modality = new bootstrap.Modal(shareModal) | ||
shareMapBtn.addEventListener('click', () => { | ||
bootstrap.Modal.getInstance(shareModal).show() | ||
}) | ||
}); | ||
|
||
function extractFileName(name) { | ||
const startIndex = name.lastIndexOf('.'); | ||
const fileName = name.substring(0, startIndex); | ||
|
||
return fileName; | ||
} | ||
|
||
function handleDrop (e) { | ||
const files = e.dataTransfer.files; | ||
const reader = new FileReader(); | ||
|
||
// confirm file being dragged has json format | ||
if (files.length === 1 && files[0].type === 'application/json') { | ||
reader.addEventListener('load', () => { | ||
let imgUrl; | ||
let options; | ||
const imgObj = JSON.parse(reader.result); | ||
// for json file with multiple image property sets | ||
if (imgObj.collection.length > 1) { | ||
imgObj.collection.forEach((imgObj) => { | ||
imgUrl = imgObj.src; | ||
options = { | ||
height: imgObj.height, | ||
tooltipText: imgObj.tooltipText, | ||
corners: imgObj.nodes, // uncomment to view the effect of corners | ||
}; | ||
placeImage(imgUrl, options); | ||
}); | ||
return; | ||
} | ||
// for json file with only one image property set | ||
imgUrl = imgObj.collection[0].src; | ||
options = { | ||
height: imgObj.collection[0].height, | ||
tooltipText: imgObj.collection[0].tooltipText, | ||
corners: imgObj.nodes, // uncomment to view the effect of corners | ||
}; | ||
placeImage(imgUrl, options); | ||
}); | ||
reader.readAsText(files[0]); | ||
} else { | ||
// non-json (i.e., images) files make it to this point | ||
for (let i = 0; i < files.length; i++) { | ||
reader.addEventListener('load', () => { | ||
const options = {tooltipText: extractFileName(files[i].name)}; | ||
placeImage(reader.result, options, true); | ||
}); | ||
reader.readAsDataURL(files[i]); | ||
} | ||
} | ||
}; | ||
|
||
function uploadFiles() { | ||
const dropZone = document.getElementById('dropZone'); | ||
const active = () => dropZone.classList.add('overlay'); | ||
const inactive = () => dropZone.classList.remove('overlay'); | ||
const prevents = e => e.preventDefault(); | ||
|
||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach((e) => { | ||
dropZone.addEventListener(e, prevents); | ||
}); | ||
|
||
['dragenter', 'dragover'].forEach((e) => { | ||
dropZone.addEventListener(e, active); | ||
}); | ||
|
||
['dragleave', 'drop'].forEach((e) => { | ||
dropZone.addEventListener(e, inactive); | ||
}); | ||
|
||
dropZone.addEventListener('drop', handleDrop); | ||
}; | ||
|
||
document.addEventListener('DOMContentLoaded', uploadFiles); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,13 +33,25 @@ const uploadFiles = () => { | |
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, i just still see some changes to this file so I asked, maybe this was unintentional? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's intentional, do you want me to remove the changes since focus has since shifted to archive.js? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the purpose of the changes, does it do the same thing for the local.html demo? That makes sense if so! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The changes expand existing drag and drop functionality in local.html/local.js to include support for JSON file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, great, let's keep it! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, many thanks! |
||
document.addEventListener('DOMContentLoaded', uploadFiles); | ||
|
||
// display uploaded image | ||
const loadMap = (image) => { | ||
// <-Ignore --- delete this comment | ||
// this function is a candidate from modularization. It's used in many other .js files | ||
const placeImage = (imageUrl, options = {}) => { | ||
map.whenReady(function() { | ||
img = L.distortableImageOverlay(image, { | ||
selected: true, | ||
fullResolutionSrc: 'large.jpg', | ||
}).addTo(map); | ||
// constructs new map | ||
const noOptions = Object.keys(options).length === 0 && options.constructor === Object | ||
if (noOptions) { | ||
img = L.distortableImageOverlay(imageUrl, { | ||
selected: true, | ||
fullResolutionSrc: 'large.jpg', | ||
}).addTo(map); | ||
} else { | ||
// reconstructs map into previous state | ||
image = L.distortableImageOverlay(imageUrl, { | ||
height: options.height, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll have to make the same changes to local.js There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure... okay. |
||
tooltipText: options.tooltipText, | ||
// corners: options.corners, <== uncomment this to see the effect of the corners | ||
}).addTo(map); | ||
} | ||
}); | ||
}; | ||
|
||
|
@@ -50,30 +62,59 @@ const handleDrop = (e) => { | |
form.append('scale', prompt('Choose a scale to download image or use the default (cm per pixel):', 100) || mergedOpts.scale); | ||
|
||
const files = e.dataTransfer.files; | ||
// eslint-disable-next-line no-unused-vars | ||
for (let i = 0, f; (f = files[i]); i++) { | ||
const reader = new FileReader(); | ||
// save file to local storage | ||
const reader = new FileReader(); | ||
|
||
// confirm file being dragged has json format | ||
if (files.length === 1 && files[0].type === 'application/json') { | ||
reader.addEventListener('load', () => { | ||
loadMap(reader.result); | ||
}); | ||
reader.readAsDataURL(files[i]); | ||
// Read the File objects in this FileList. | ||
imgObj = JSON.parse(reader.result); | ||
// for json file with multiple image property sets | ||
if (imgObj.collection.length > 1) { | ||
imgObj.collection.forEach((imgObj) => { | ||
imgUrl = imgObj.src; | ||
options = { | ||
height: imgObj.height, | ||
tooltipText: imgObj.tooltipText, | ||
// corners: imgObj.nodes, // uncomment to view the effect of corners | ||
}; | ||
placeImage(imgUrl, options); | ||
}); | ||
return; | ||
} | ||
// for json file with only one image property set | ||
imgUrl = imgObj.collection[0].src; | ||
options = { | ||
height: imgObj.collection[0].height, | ||
tooltipText: imgObj.collection[0].tooltipText, | ||
// corners: imgObj.nodes, // uncomment to view the effect of corners | ||
}; | ||
placeImage(imgUrl, options); | ||
}); | ||
reader.readAsText(files[0]); | ||
} else { | ||
// non-json (i.e., images) files make it to this point | ||
// eslint-disable-next-line no-unused-vars | ||
for (let i = 0, f; (f = files[i]); i++) { | ||
reader.addEventListener('load', () => { | ||
placeImage(reader.result); | ||
}); | ||
reader.readAsDataURL(files[i]); | ||
// Read the File objects in this FileList. | ||
} | ||
} | ||
}; | ||
|
||
// <-Ignore --- delete this comment | ||
// notify user that they will lose all changes | ||
window.addEventListener('beforeunload', (e) => { | ||
e.preventDefault(); | ||
e.returnValue = ''; | ||
}); | ||
|
||
|
||
let map; | ||
(function() { | ||
map = L.map('map').setView([51.505, -0.09], 13); | ||
map.addGoogleMutant(); | ||
})(); | ||
|
||
|
||
L.Control.geocoder().addTo(map); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed height here because it'd be auto-detected if we do the fitBounds thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting... okay.