$(() => {
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(); });
});