$(() => { let fullPageMediaDisplayed = false; let fullPageMediaList = false; let metaListMap = {}; function refreshOneMetaList(key, domNode) { domNode.textContent = ""; let data = key === "Tags" ? Array.from(MediaStorage.Instance.allTags) : Array.from(MediaStorage.Instance.allMeta[key] || []); for (let i of data.sort()) { let childDom = document.createElement("option"); childDom.value = childDom.textContent = i; domNode.appendChild(childDom); } } function buildMetaDatalistes(key, datalistes) { let uuid = "METALIST_"+crypto.randomUUID(); let list = document.createElement("datalist"); list.id = uuid; datalistes.push(list); refreshOneMetaList(key, list); metaListMap[key] = uuid; return uuid; } function refreshMetaDatalistes() { for (let i of Object.keys(metaListMap)) { refreshOneMetaList(i, document.getElementById(metaListMap[i])); } } function serializeFileSize(size) { let units = [ 'o', 'Ko', 'Mo', 'Go', 'To' ]; let idx = 0; while (size >= 800 && idx < units.length) { ++idx; size /= 1024; } size = Math.floor(size * 100) / 100; return `${size} ${units[idx]}`; } function displayMeta(key, value, isRo, datalistes) { let li = document.createElement("li"); let type = value?.type || null; let val = (value?.value !== undefined ? value.value : value); if (!val && val !== '') return null; if (type === 'date') val = val.toLocaleString(); if (["dimension", "height", "width"].indexOf(key) >= 0) val += ' px'; if (["fileSize"].indexOf(key) >= 0) val = serializeFileSize(val); if (key == 'fNumber') val = `f/ ${val}`; let keyTranslate = { fileSize: "File Size", photochamberImport: "Photochamber Imported", lensModel: "Lens Model", exposureTimeStr: "Exposure Time" }; let keySpan = document.createElement('span'); let valSpan = document.createElement('div'); li.classList.add("row"); keySpan.className = "metaKey col-xl-12 col-4"; valSpan.className = "metaVal col-xl-12 col-8"; let inputGroup = document.createElement('form'); valSpan.appendChild(inputGroup); inputGroup.classList.add('input-group'); keySpan.innerText = (keyTranslate[key] || (key[0].toUpperCase()+key.substr(1))) + ':'; let valInput = document.createElement("input"); valInput.classList.add("form-control"); valInput.value = val; valInput.disabled = isRo; valInput.addEventListener('keyup', evt => evt.stopPropagation()); valInput.addEventListener('keydown', evt => evt.stopPropagation()); inputGroup.appendChild(valInput); if (!isRo) { let bt = document.createElement('button'); bt.className = 'btn btn-outline-secondary'; bt.type = 'button'; bt.innerHTML = ''; inputGroup.appendChild(bt); valInput.setAttribute("list", buildMetaDatalistes(key, datalistes)); bt.addEventListener('click', async () => { await MediaStorage.Instance.setMetaValue(fullPageMediaDisplayed?.fixedSum || fullPageMediaList, key, valInput.value) reloadCurrentMedia(); }); inputGroup.addEventListener('submit', async evt => { evt.preventDefault(); await MediaStorage.Instance.setMetaValue(fullPageMediaDisplayed?.fixedSum || fullPageMediaList, key, valInput.value); reloadCurrentMedia(); }); } li.appendChild(keySpan); li.appendChild(valSpan); return li; } 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 searchMarker; let searchPopup; 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 = geo && JSON.parse(geo); } catch(err) { return null; } let outerHTML = document.createElement("li"); outerHTML.className = "row"; let container = document.createElement("div"); container.className = "leaflet-container container"; outerHTML.appendChild(container); let innerHTML = document.createElement("div"); container.appendChild(innerHTML); let map = L.map(innerHTML, { scrollWheelZoom: false }); L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); if (!isRo) { L.Control.geocoder({defaultMarkGeocode: false}).on('markgeocode', e => { let pos = e?.geocode?.center; if (!pos) return; if (!searchMarker) searchMarker = L.marker(pos).addTo(map); else searchMarker.setLatLng(pos); searchMarker.bindPopup("
" +(e.geocode.html || e.geocode.name) + "
").openPopup(); searchMarker._popup._container.querySelector("button").addEventListener('click', () => { setLatLngWithDelay(media, pos.lat, pos.lng); }); map.setView(pos, 13); }).addTo(map); 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); 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(media, metaData, isRo, datalistes) { let metaList = document.createElement("ul"); metaData.libraryPath = null; metaData.tags = metaData.fixedTags = null; if (metaData.exposureTime && metaData.exposureTimeStr) metaData.exposureTime = null; if (metaData.date && metaData.dateTime) metaData.dateTime = null; if (metaData.height && metaData.width) { metaData.dimension = { type: "string", value: `${metaData.width.value} x ${metaData.height.value}` } metaData.height = metaData.width = null; } for (let i of [ "date", "dimension", "height", "width", "fileSize" ]) { let metaItem = displayMeta(i, metaData[i], true, datalistes); metaItem && metaList.appendChild(metaItem); metaData[i] = null; } for (let i of Object.keys(metaData).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))) { if (i === 'gpsLocation') { let dom = displayMap(Array.isArray(media) ? media : [media], metaData[i].value, isRo); dom && metaList.appendChild(dom); continue; } let metaItem = displayMeta(i, metaData[i], isRo || !MediaStorage.Instance.allMetaTypes[i]?.canWrite, datalistes); metaItem && metaList.appendChild(metaItem); } if (!isRo && media) { for (let i of ['geoCountry', 'geoCity', 'geoAdmin']) { if (!metaData[i]) { let metaItem = displayMeta(i, "", false, datalistes); 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(MediaStorage.Instance.getMediaLocal(fullPageMediaDisplayed.fixedSum)); } function displayTags(fixedTagList, tagList, writeAccess, datalistes) { let tagListUi = document.createElement("ul"); tagListUi.className = "taglist"; let createTagUiItem = (i, roTag) => { let uiItem = document.createElement("li"); uiItem.className = "badge text-bg-light"; let textItem = document.createElement("span"); textItem.textContent = i; uiItem.appendChild(textItem); if (!roTag) { let btItem = document.createElement("a"); btItem.className = "border-start bi bi-x removeBt"; btItem.href = '#'; btItem.addEventListener('click', async e => { e.preventDefault(); await MediaStorage.Instance.removeTag(fullPageMediaDisplayed?.fixedSum || fullPageMediaList, i); reloadCurrentMedia(); }); uiItem.appendChild(btItem); } tagListUi.appendChild(uiItem); }; for (let i of fixedTagList) createTagUiItem(i, true); for (let i of tagList) createTagUiItem(i, !writeAccess); if (writeAccess) { let inputGroup = document.createElement('form'); tagListUi.appendChild(inputGroup); inputGroup.classList.add('input-group'); let valInput = document.createElement("input"); valInput.classList.add("form-control"); valInput.addEventListener('keyup', evt => evt.stopPropagation()); valInput.addEventListener('keydown', evt => evt.stopPropagation()); valInput.setAttribute("list", buildMetaDatalistes("Tags", datalistes)); inputGroup.appendChild(valInput); let bt = document.createElement('button'); bt.className = 'btn btn-outline-secondary'; bt.type = 'button'; bt.innerHTML = ''; inputGroup.appendChild(bt); bt.addEventListener('click', async () => { await MediaStorage.Instance.addTag(fullPageMediaDisplayed?.fixedSum || fullPageMediaList, valInput.value); reloadCurrentMedia(); }); inputGroup.addEventListener('submit', async evt => { evt.preventDefault(); await MediaStorage.Instance.addTag(fullPageMediaDisplayed?.fixedSum || fullPageMediaList, valInput.value); reloadCurrentMedia(); }); } return tagListUi; } function displayDownloadBt(downloadLink) { let bt = document.createElement("button"); bt.type = "button"; bt.className = "btn btn-primary"; bt.textContent = "Download"; bt.addEventListener("click", (evt) => { let link = document.createElement('a'); link.target='_blank'; link.setAttribute("download", ""); link.href = downloadLink; link.click(); }); return bt; } function displayRemoveButton(ids) { let bt = document.createElement("button"); bt.type = "button"; bt.className = "btn btn-danger"; bt.textContent = ids.length > 1 ? "Remove files" : "Remove file"; bt.addEventListener("click", async evt => { if (window.confirm("File will be removed from the server. Are you sure ?")) { await MediaStorage.Instance.remoteRemove(ids); CloseFullpageMedia(); } }); return bt; } function displayImg(medias) { if (!Array.isArray(medias)) medias = [ medias ]; if (medias.length > 1) { $("#pch-fullPagePreview > a").removeClass("hidden"); $("#pch-fullPagePreview > .carousel-indicators").removeClass("hidden"); } else { $("#pch-fullPagePreview > a").addClass("hidden"); $("#pch-fullPagePreview > .carousel-indicators").addClass("hidden"); } const containerSize = document.getElementById("pch-fullPageMedia").getBoundingClientRect(); let container = document.querySelector("#pch-fullPagePreview .carousel-inner"); container.textContent = ""; let carouselIndicators = document.querySelector("#pch-fullPagePreview .carousel-indicators"); carouselIndicators.textContent = ""; return Promise.allSettled(medias.map((media, index) => new Promise(ok => { let item = document.createElement("div"); item.className = "carousel-item"; let img = document.createElement("img"); let requestSize = media.resize(containerSize.width, containerSize.height); const requestSizeQuery = requestSize ? `&w=${requestSize.width}&h=${requestSize.height}` : ""; img.src = `${media.thumbnail}?q=6${requestSizeQuery}`; img.addEventListener("load", () => ok()); item.appendChild(img); container.appendChild(item); let carouselIndicator = document.createElement("button"); carouselIndicator.type = "button"; carouselIndicator.dataset.bsTarget = "#pch-fullPagePreview"; carouselIndicator.dataset.bsSlideTo = index; if (!index) { item.classList.add("active"); carouselIndicator.classList.add("active"); } carouselIndicators.appendChild(carouselIndicator); ++index; }))).finally(() => { document.getElementById("pch-fullPagePreviewContainer").classList.remove("loading"); if (medias.length > 1) $(container.parentElement).carousel(); }); } function _displayMediaFullPage(media, fileName, imgUrl, metaData, downloadLink, fileIds, writeAccess) { document.getElementById("pch-fullPagePreviewContainer").classList.add("loading"); document.getElementById("pch-fullPageMedia-title").innerText = fileName; let datalistes = []; metaListMap = {}; document.getElementById("pch-fullPageDetail").innerText = ""; if (downloadLink) document.getElementById("pch-fullPageDetail").appendChild(displayDownloadBt(downloadLink)); if (fileIds && writeAccess) document.getElementById("pch-fullPageDetail").appendChild(displayRemoveButton(fileIds)); document.getElementById("pch-fullPageDetail").appendChild(displayTags(metaData?.fixedTags || [], metaData?.tags || [], writeAccess, datalistes)); document.getElementById("pch-fullPageDetail").appendChild(displayMetas(media, Object.assign({}, metaData || {}), !writeAccess, datalistes)); document.querySelector("#pch-fullPagePreview .carousel-inner").textContent = ""; $("#pch-fullPagePreview.carousel").carousel("dispose"); { let datalistParent = document.getElementById("datalistes"); datalistParent.textContent = ""; for (let i of datalistes) datalistParent.appendChild(i); } return displayImg(media, imgUrl); } function LoadPreviousMedia() { let i = fullPageMediaDisplayed; if (!i) return; while (i = MediaStorage.Instance.previousMedia(i)) { if (window.FilterManager.match(i)) { window.displayMediaFullPage(i); break; } } } function LoadNextMedia() { let i = fullPageMediaDisplayed; if (!i) return; while (i = MediaStorage.Instance.nextMedia(i)) { if (window.FilterManager.match(i)) { window.displayMediaFullPage(i); break; } } } function CloseFullpageMedia() { if (fullPageMediaDisplayed !== false || fullPageMediaList) document.body.classList.remove("overlay-visible"); document.getElementById("pch-fullPageMedia").classList.add("hidden"); fullPageMediaDisplayed = false; history.pushState({}, '', '#'); document.Title.pop(); metaListMap = {}; document.getElementById("datalistes").textContent = ""; } window.displayMediaFullPage = function(mediaItem) { document.getElementById("pch-fullPageMedia").classList.remove("hidden"); document.getElementById("pch-fullPageMedia").classList.remove("multiple"); if (fullPageMediaDisplayed) document.Title.replaceTitle(mediaItem.fileName); else document.Title.pushTitle(mediaItem.fileName); fullPageMediaDisplayed = mediaItem ?? null; document.body.classList.add("overlay-visible"); if (!mediaItem) return _displayMediaFullPage(null, "Error", null, {}, null, null, false); let meta = { ...mediaItem.meta, date: { type: 'date', value: mediaItem.getDate() } || undefined, filename: mediaItem.filename ? { type: 'string', value: mediaItem.fileName } : undefined, fixedTags: mediaItem.fixedTags, tags: mediaItem.tags }; if (document.location.hash != `#${mediaItem.fixedSum}`) history.pushState({}, '', `#${mediaItem.fixedSum}`); return _displayMediaFullPage(mediaItem, mediaItem.fileName, "", meta, `${mediaItem.original}?trim`, [mediaItem.fixedSum], mediaItem.writeAccess); } function aggregateMetas(medias) { let meta = medias.reduce((acc, x) => { for (let key in x.meta) { acc[key] = acc[key] || x.meta[key]; acc[key] = acc[key].value != x.meta[key].value ? "(multiple)" : x.meta[key]; } return acc; }, {}); delete meta.dateTime; if (!Number.isInteger(meta.height) || !Number.isInteger(meta.width)) { delete meta.height; delete meta.width; } return meta; } window.displayMultipleMediaFullPage = function(medias) { const title = "Multiple edit"; // FIXME lang ? fullPageMediaList = Array.from(new Set(medias.map(x => x.fixedSum))); document.getElementById("pch-fullPageMedia").classList.remove("hidden"); document.getElementById("pch-fullPageMedia").classList.add("multiple"); document.Title.pushTitle(title); document.body.classList.add("overlay-visible"); let meta = { ...aggregateMetas(medias), 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(medias, title, "", meta, null, medias.map(x => x.fixedSum), true); } document.getElementById("pch-fullPageMedia-closeBt") .addEventListener("click", () => CloseFullpageMedia()); document.addEventListener("keydown", evt => { if (!fullPageMediaDisplayed) return; if (evt.keyCode === 37 || evt.keyCode === 38) LoadPreviousMedia(); else if (evt.keyCode === 39 || evt.keyCode === 40) LoadNextMedia(); }); document.onClosePopinRequested(() => { CloseFullpageMedia(); }); document.UiFullPageRefreshMetaDatalistes = refreshMetaDatalistes; });