Browse Source

Fixes #25 Edit geo place

isundil 1 year ago
parent
commit
06da2dabc5
5 changed files with 176 additions and 85 deletions
  1. 38 0
      model/mediaService.js
  2. 3 15
      router/api.js
  3. 60 44
      src/filetype/metaStruct.js
  4. 4 5
      static/public/js/medias.js
  5. 71 21
      static/public/js/uiMediaFullpage.js

+ 38 - 0
model/mediaService.js

@@ -1,6 +1,7 @@
 
 const path = require('path');
 const fs = require('fs');
+const MetaStruct = require('../src/filetype/metaStruct.js').MetaStruct;
 const MediaFileModel = require('./mediaItem.js').MediaFileModel;
 const { MediaFileTagModel } = require('./mediaItemTag.js');
 const { MediaFileMetaModel } = require('./mediaItemMeta.js');
@@ -216,6 +217,43 @@ function fetchMediasBuildSubQuery(startTs, count, access, maxVersion) {
     };
 }
 
+async function removeMeta(app, mediaFixedSums, key) {
+    await app.databaseHelper.remove(MediaFileMetaModel, { md5sum: mediaFixedSums, key: key, fromFile: 0 });
+}
+
+async function updateMeta(app, mediaFixedSums, key, newValue) {
+    await removeMeta(app, mediaFixedSums, key);
+    if (newValue) {
+        try {
+            await app.databaseHelper.insertMultipleSameTable(mediaFixedSums.map(x => new MediaFileMetaModel(x, key, newValue, false)));
+        }
+        catch (err) {
+            console.error(err);
+            return false;
+        }
+    }
+    if (key === 'gpsLocation') {
+        try {
+            let metaStruct = new MetaStruct();
+            let geoLatLng = JSON.parse(newValue);
+            await metaStruct.setGPSInfoFromLatLng(geoLatLng[0], geoLatLng[1]);
+            await updateMeta(app, mediaFixedSums, 'geoHash', metaStruct.geoHash);
+            await updateMeta(app, mediaFixedSums, 'geoCountry', metaStruct.geoCountry);
+            await updateMeta(app, mediaFixedSums, 'geoCity', metaStruct.geoCity);
+            await updateMeta(app, mediaFixedSums, 'geoAdmin', metaStruct.geoAdmin);
+        } catch (err) {
+            console.error(err);
+            return false;
+        }
+    }
+    return true;
+}
+
+module.exports.updateMeta = async function(app, mediaFixedSums, key, newValue) {
+    await Promise.all(mediaFixedSums.map(x => module.exports.updateVersionInDb(app, x)));
+    return await updateMeta(app, mediaFixedSums, key, newValue);
+}
+
 module.exports.removeMedia = async function(app, media) {
     await app.databaseHelper.remove(MediaFileTagModel, { md5sum: media.fixedSum });
     await app.databaseHelper.remove(MediaFileMetaModel, { md5sum: media.fixedSum });

+ 3 - 15
router/api.js

@@ -209,22 +209,10 @@ module.exports = { register: app => {
         data = Object.keys(data)
             .map(x => data[x])
             .filter(x => x.ACCESS_TYPE != ACCESS_GRANT.write);
-        await Promise.all(data.map(x => MediaService.updateVersionInDb(app, x.fixedSum)));
 
-        if (!req.body.value) {
-            await app.databaseHelper.remove(MediaFileMetaModel, { md5sum: data.map(x => x.fixedSum), key: req.params.key, fromFile: 0 });
-        } else {
-            let newMediaItemMedia = data.map(x => new MediaFileMetaModel(x.fixedSum, req.params.key, req.body.value, false));
-            await app.databaseHelper.remove(MediaFileMetaModel, { md5sum: data.map(x => x.fixedSum), key: req.params.key, fromFile: 0 });
-            try {
-                await app.databaseHelper.insertMultipleSameTable(newMediaItemMedia);
-            }
-            catch (err) {
-                console.error(err);
-                return app.routerUtils.onBadRequest(res);
-            }
-        }
-        const allMedias = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList);
+        if (!(await MediaService.updateMeta(app, data.map(x => x.fixedSum), req.params.key, req.body.value)))
+            return app.routerUtils.onBadRequest(res);
+        const allMedias = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList, 0);
         app.routerUtils.jsonResponse(res, Object.keys(allMedias).map(x => allMedias[x]).map(x => MediaToJson(x)));
     });
     app.router.get("/api/media/list", async (req, res) => {

+ 60 - 44
src/filetype/metaStruct.js

@@ -2,42 +2,6 @@
 const geokit = require('geokit');
 const geocoder = require('offline-geocoder')({ database: 'static/db.sqlite'});
 
-function MetaStruct() {
-    this.artist = undefined;
-    this.exposureProgram = undefined;
-    this.exposureTime = undefined;
-    this.exposureTimeStr = undefined;
-    this.dateTime = undefined;
-    this.fNumber = undefined;
-    this.focal = undefined;
-    this.lensModel = undefined;
-    this.camera = undefined;
-    this.software = undefined;
-    this.iso = undefined;
-    this.width = undefined;
-    this.height = undefined;
-    this.compression = undefined;
-    this.gpsLocation = undefined;
-    this.geoHash = undefined;
-    this.geoCountry = undefined;
-    this.geoAdmin = undefined;
-    this.geoCity = undefined;
-    this.orientation = undefined;
-    this.tags = undefined;
-
-    // Internal
-    this.imException = undefined;
-    this.exifParsed = false;
-}
-
-MetaStruct.prototype.setExposureProgram = function(progId) {
-    if (isNaN(progId) || !progId)
-        this.exposureProgram = undefined;
-    else
-        this.exposureProgram = [ "Manual", "Normal program", "Aperture priority", "Shutter priority",
-            "Creative program", "Action program", "Portrait mode", "Landscape mode"][progId];
-}
-
 function ExifGps(lat, latRef, lon, lonRef) {
     this.lat = null;
     this.lon = null;
@@ -54,6 +18,13 @@ function ExifGps(lat, latRef, lon, lonRef) {
         this.lat = this.lon = null;
 }
 
+ExifGps.fromLatLng = (lat, lng) => {
+    let result = new ExifGps();
+    result.lat = lat;
+    result.lon = lng;
+    return result;
+}
+
 ExifGps.prototype.toGps = function() {
     if (this.lat === null)
         return undefined;
@@ -79,14 +50,59 @@ ExifGps.prototype.toAddress = function() {
     });
 }
 
-MetaStruct.prototype.setGPSInfo = async function(latitude, latitudeRef, longitude, longitudeRef) {
-    let gpsData = new ExifGps(latitude, latitudeRef, longitude, longitudeRef);
-    this.gpsLocation = gpsData.toGps();
-    this.geoHash = gpsData.toGeoHash();
-    const address = await gpsData.toAddress();
-    this.geoCountry = address?.country;
-    this.geoAdmin = address?.admin;
-    this.geoCity = address?.city;
+class MetaStruct {
+    artist = undefined;
+    exposureProgram = undefined;
+    exposureTime = undefined;
+    exposureTimeStr = undefined;
+    dateTime = undefined;
+    fNumber = undefined;
+    focal = undefined;
+    lensModel = undefined;
+    camera = undefined;
+    software = undefined;
+    iso = undefined;
+    width = undefined;
+    height = undefined;
+    compression = undefined;
+    gpsLocation = undefined;
+    geoHash = undefined;
+    geoCountry = undefined;
+    geoAdmin = undefined;
+    geoCity = undefined;
+    orientation = undefined;
+    tags = undefined;
+
+    // Internal
+    imException = undefined;
+    exifParsed = false;
+
+    setExposureProgram(progId) {
+        if (isNaN(progId) || !progId)
+            this.exposureProgram = undefined;
+        else
+            this.exposureProgram = [ "Manual", "Normal program", "Aperture priority", "Shutter priority",
+                "Creative program", "Action program", "Portrait mode", "Landscape mode"][progId];
+    }
+
+    async #updateGPS(gpsData) {
+        this.gpsLocation = gpsData.toGps();
+        this.geoHash = gpsData.toGeoHash();
+        const address = await gpsData.toAddress();
+        this.geoCountry = address?.country;
+        this.geoAdmin = address?.admin;
+        this.geoCity = address?.city;
+    }
+
+    setGPSInfo(latitude, latitudeRef, longitude, longitudeRef) {
+        const gpsData = new ExifGps(latitude, latitudeRef, longitude, longitudeRef);
+        return this.#updateGPS(gpsData);
+    }
+
+    setGPSInfoFromLatLng(latitude, longitude) {
+        const gpsData = ExifGps.fromLatLng(latitude, longitude);
+        return this.#updateGPS(gpsData);
+    }
 }
 
 module.exports.MetaStruct = MetaStruct;

+ 4 - 5
static/public/js/medias.js

@@ -324,6 +324,8 @@ class MediaStorage extends EventTarget
 
     setMetaValue(md5sum, key, value) {
         let md5arr = undefined;
+        if (Array.isArray(md5sum) && md5sum.length === 1)
+            md5sum = md5sum[0];
         if (Array.isArray(md5sum)) {
             md5arr = md5sum;
             md5sum = "list";
@@ -339,11 +341,8 @@ class MediaStorage extends EventTarget
                     data: { value: value, list: md5arr },
                     success: allData => {
                         allData.forEach(data => {
-                            let media = this.medias.find(x => x.fixedSum === data.fixedSum);
-                            let meta = data.meta[key] || { type: 'string', value: value, canWrite: true };
-                            meta.value = value;
-                            this.#pushMeta(key, meta);
-                            media.meta[key] = meta;
+                            this.medias = this.medias.filter(x => x.fixedSum !== data.fixedSum);
+                            this.#pushUnique(new Media(data));
                         });
                         window.ReloadFilters(this);
                         ok(true);

+ 71 - 21
static/public/js/uiMediaFullpage.js

@@ -56,10 +56,14 @@ $(() => {
             bt.type = 'button';
             bt.innerHTML = '<i class="bi bi-pen"></i>';
             inputGroup.appendChild(bt);
-            bt.addEventListener('click', () => MediaStorage.Instance.setMetaValue(fullPageMediaDisplayed?.fixedSum || fullPageMediaList, key, valInput.value));
-            inputGroup.addEventListener('submit', evt => {
+            bt.addEventListener('click', async () => {
+                await MediaStorage.Instance.setMetaValue(fullPageMediaDisplayed?.fixedSum || fullPageMediaList, key, valInput.value)
+                reloadCurrentMedia();
+            });
+            inputGroup.addEventListener('submit', async evt => {
                 evt.preventDefault();
-                MediaStorage.Instance.setMetaValue(fullPageMediaDisplayed?.fixedSum || fullPageMediaList, key, valInput.value);
+                await MediaStorage.Instance.setMetaValue(fullPageMediaDisplayed?.fixedSum || fullPageMediaList, key, valInput.value);
+                reloadCurrentMedia();
             });
         }
         li.appendChild(keySpan);
@@ -67,10 +71,35 @@ $(() => {
         return li;
     }
 
-    function displayMap(geo) {
+    let latLngTimeo = null;
+    function setLatLngWithDelay(medias, lat, lng) {
+        if (latLngTimeo)
+            clearTimeout(latLngTimeo);
+        latLngTimeo = setTimeout(async () => {
+            await MediaStorage.Instance.setMetaValue(medias.map(x => x.fixedSum), 'gpsLocation', JSON.stringify([lat, lng]));
+            latLngTimeo = null;
+            reloadCurrentMedia();
+        }, 500);
+    }
+
+    function displayMap(media, geo, isRo) {
         let jsonGeo = geo;
+        let marker;
+        let createMarker = (latLng) => {
+            if (marker)
+                return marker;
+            marker = L.marker(latLng, { draggable: true, autoPan: true });
+            if (!isRo)
+                marker.addEventListener('dragend', e => {
+                    let latLng = e.target?.getLatLng();
+                    if (!latLng || !latLng.lat || !latLng.lng)
+                        return;
+                    setLatLngWithDelay(media, latLng.lat, latLng.lng);
+                });
+            return marker;
+        };
         try {
-            geo = JSON.parse(geo);
+            geo = geo && JSON.parse(geo);
         }
         catch(err) { return null; }
         let outerHTML = document.createElement("li");
@@ -80,23 +109,39 @@ $(() => {
         outerHTML.appendChild(container);
         let innerHTML = document.createElement("div");
         container.appendChild(innerHTML);
-        let map = L.map(innerHTML, { scrollWheelZoom: false }).setView(geo, 13);
+        let map = L.map(innerHTML, { scrollWheelZoom: false });
         L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
             attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
         }).addTo(map);
-        L.marker(geo).addTo(map);
+        if (!isRo) {
+            map.addEventListener("contextmenu", evt => {
+                if (!marker)
+                    createMarker(evt.latlng).addTo(map);
+                else
+                    marker.setLatLng(evt.latlng);
+                setLatLngWithDelay(media, evt.latlng.lat, evt.latlng.lng);
+            });
+        }
+        if (geo) {
+            map.setView(geo, 13);
+            createMarker(geo).addTo(map);
+        }
+        else
+            map.setView(L.latLng(45, 2), 5);
         setTimeout(function () {
             map.invalidateSize();
         }, 0);
-        let a = document.createElement("a");
-        a.href = `https://www.openstreetmap.org/?mlat=${geo[0]}&mlon=${geo[1]}`;
-        a.target = "_blank";
-        a.innerHTML = jsonGeo;
-        container.appendChild(a);
+        if (jsonGeo) {
+            let a = document.createElement("a");
+            a.href = `https://www.openstreetmap.org/?mlat=${geo[0]}&mlon=${geo[1]}`;
+            a.target = "_blank";
+            a.innerHTML = jsonGeo;
+            container.appendChild(a);
+        }
         return outerHTML;
     }
 
-    function displayMetas(metaData, isRo) {
+    function displayMetas(media, metaData, isRo) {
         let metaList = document.createElement("ul");
 
         metaData.libraryPath = null;
@@ -122,7 +167,7 @@ $(() => {
 
         for (let i of Object.keys(metaData).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))) {
             if (i === 'gpsLocation') {
-                let dom = displayMap(metaData[i].value);
+                let dom = displayMap(Array.isArray(media) ? media : [media], metaData[i].value, isRo);
                 dom && metaList.appendChild(dom);
                 continue;
             }
@@ -130,18 +175,23 @@ $(() => {
             metaItem && metaList.appendChild(metaItem);
         }
 
-        if (!isRo)
+        if (!isRo && media) {
             for (let i of ['geoCountry', 'geoCity', 'geoAdmin']) {
                 if (!metaData[i]) {
                     let metaItem = displayMeta(i, "", false);
                     metaItem && metaList.appendChild(metaItem);
                 }
             }
+            if (!metaData['gpsLocation']) {
+                let dom = displayMap(Array.isArray(media) ? media : [media], null, isRo);
+                dom && metaList.appendChild(dom);
+            }
+        }
         return metaList;
     }
 
     function reloadCurrentMedia() {
-        fullPageMediaDisplayed && window.displayMediaFullPage(fullPageMediaDisplayed);
+        fullPageMediaDisplayed && window.displayMediaFullPage(MediaStorage.Instance.getMediaLocal(fullPageMediaDisplayed.fixedSum));
     }
 
     function displayTags(fixedTagList, tagList, writeAccess) {
@@ -223,7 +273,7 @@ $(() => {
         return bt;
     }
 
-    function _displayMediaFullPage(fileName, imgUrl, metaData, downloadLink, fileIds, writeAccess) {
+    function _displayMediaFullPage(media, fileName, imgUrl, metaData, downloadLink, fileIds, writeAccess) {
         return new Promise(ok => {
             document.getElementById("pch-fullPagePreviewContainer").classList.add("loading");
             document.getElementById("pch-fullPageMedia-title").innerText = fileName;
@@ -236,7 +286,7 @@ $(() => {
             if (fileIds && writeAccess)
                 document.getElementById("pch-fullPageDetail").appendChild(displayRemoveButton(fileIds));
             document.getElementById("pch-fullPageDetail").appendChild(displayTags(metaData?.fixedTags || [], metaData?.tags || [], writeAccess));
-            document.getElementById("pch-fullPageDetail").appendChild(displayMetas(Object.assign({}, metaData || {}), !writeAccess));
+            document.getElementById("pch-fullPageDetail").appendChild(displayMetas(media, Object.assign({}, metaData || {}), !writeAccess));
         });
     }
 
@@ -281,7 +331,7 @@ $(() => {
         fullPageMediaDisplayed = mediaItem ?? null;
         document.body.classList.add("overlay-visible");
         if (!mediaItem)
-            return _displayMediaFullPage("Error", null, {}, null, null, false);
+            return _displayMediaFullPage(null, "Error", null, {}, null, null, false);
         let containerSize = document.getElementById("pch-fullPageMedia").getBoundingClientRect();
         let requestSize = mediaItem.resize(containerSize.width, containerSize.height);
         document.getElementById("pch-fullPagePreview").parentNode.style.maxWidth = "100%";
@@ -296,7 +346,7 @@ $(() => {
         if (document.location.hash != `#${mediaItem.fixedSum}`)
             history.pushState({}, '', `#${mediaItem.fixedSum}`);
         const requestSizeQuery = requestSize ? `&w=${requestSize.width}&h=${requestSize.height}` : "";
-        return _displayMediaFullPage(mediaItem.fileName, `${mediaItem.thumbnail}?q=6${requestSizeQuery}`, meta, `${mediaItem.original}?trim`, [mediaItem.fixedSum], mediaItem.writeAccess);
+        return _displayMediaFullPage(mediaItem, mediaItem.fileName, `${mediaItem.thumbnail}?q=6${requestSizeQuery}`, meta, `${mediaItem.original}?trim`, [mediaItem.fixedSum], mediaItem.writeAccess);
     }
 
     function aggregateMetas(medias) {
@@ -329,7 +379,7 @@ $(() => {
             fixedTags: medias.reduce((acc, x) => { x.fixedTags.forEach(tag => acc.add(tag)) ; return acc; }, new Set()),
             tags: medias.reduce((acc, x) => { x.tags.forEach(tag => acc.add(tag)) ; return acc; }, new Set()),
         };
-        return _displayMediaFullPage(title, "", meta, null, medias.map(x => x.fixedSum), true);
+        return _displayMediaFullPage(medias, title, "", meta, null, medias.map(x => x.fixedSum), true);
     }
 
     document.getElementById("pch-fullPageMedia-closeBt")