$(() => { let fullPageMediaDisplayed = false; let fullPageMediaList = false; 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) { 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); 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 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) { 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) { 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); 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); metaItem && metaList.appendChild(metaItem); } 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(MediaStorage.Instance.getMediaLocal(fullPageMediaDisplayed.fixedSum)); } function displayTags(fixedTagList, tagList, writeAccess) { 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()); 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 _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; document.getElementById("pch-fullPagePreview").onceLoaded = ok; document.getElementById("pch-fullPagePreview").src = imgUrl ?? ""; 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)); document.getElementById("pch-fullPageDetail").appendChild(displayMetas(media, Object.assign({}, metaData || {}), !writeAccess)); }); } 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(); } 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 containerSize = document.getElementById("pch-fullPageMedia").getBoundingClientRect(); let requestSize = mediaItem.resize(containerSize.width, containerSize.height); document.getElementById("pch-fullPagePreview").parentNode.style.maxWidth = "100%"; document.getElementById("pch-fullPagePreview").parentNode.style.maxHeight = "100%"; 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}`); const requestSizeQuery = requestSize ? `&w=${requestSize.width}&h=${requestSize.height}` : ""; return _displayMediaFullPage(mediaItem, mediaItem.fileName, `${mediaItem.thumbnail}?q=6${requestSizeQuery}`, 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"); document.getElementById("pch-fullPagePreview").parentNode.style.maxWidth = "100%"; document.getElementById("pch-fullPagePreview").parentNode.style.maxHeight = "100%"; 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.getElementById("pch-fullPagePreview").addEventListener("load", () => { document.getElementById("pch-fullPagePreviewContainer").classList.remove("loading"); let domItem = document.getElementById("pch-fullPagePreview"); domItem.onceLoaded && domItem.onceLoaded(); domItem.onceLoaded = null; }); 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(); }); });