$(() => { var fullPageMediaDisplayed = false; var selectedThumbnails = []; var lastKeyboardEvent = null; var lastSelection = null; function onItemSelected(mediaItem) { document.getElementById("pch-mediaList").classList.add("selection"); } function onItemDeselected(mediaItem) { if (!selectedThumbnails.length) document.getElementById("pch-mediaList").classList.remove("selection"); } function buildThumbnail(mediaItem) { if (mediaItem.ui) return mediaItem.ui; let checkbox = document.createElement("input"); let editButton = document.createElement("button"); let img = document.createElement("img"); let container = document.createElement("li"); let loadingImg = document.createElement("div"); checkbox.type = "checkbox"; editButton.type = "button"; if (!mediaItem.writeAccess) editButton.classList.add("hidden"); let editButtonSpan = document.createElement("span"); editButtonSpan.className = "bi bi-pen"; editButton.appendChild(editButtonSpan); container.classList.add("pch-image"); container.classList.add("loading"); loadingImg.classList.add("spinner"); loadingImg.innerHTML = ""; container.dataset.md5sum = mediaItem.md5sum; img.loading = "lazy"; let requestSize = mediaItem.resize(450, 450); img.src = `${mediaItem.thumbnail}?w=${requestSize.width}&h=${requestSize.height}&q=4`; img.classList.add("img-fluid"); img.classList.add("img-thumbnail"); img.addEventListener("load", () => { container.classList.remove("loading"); container.classList.remove("spinner-grow"); }); container.style.width = `${requestSize.width}px`; container.appendChild(loadingImg); container.appendChild(img); container.appendChild(checkbox); container.appendChild(editButton); let setSelectionCheckboxValue = function(media, value) { let checked = media.ui.checkbox.checked; let indexInSelection = selectedThumbnails.indexOf(media.md5sum); if (checked && indexInSelection < 0) { selectedThumbnails.push(media.md5sum); onItemSelected(media); return true; } else if (!checked && indexInSelection >= 0) { selectedThumbnails.splice(indexInSelection, 1); onItemDeselected(media); return true; } return false; } let cascadeSetSelectionCheckboxValue = function(value) { if (!setSelectionCheckboxValue(mediaItem, value)) return; if (lastKeyboardEvent?.shiftKey && lastSelection) { let _lastKeyboardEvent = lastKeyboardEvent; lastKeyboardEvent = null; for (let i of MediaStorage.Instance.getMediaBetween(lastSelection, mediaItem)) { if (i === mediaItem) continue; i.ui.checkbox.setAttribute("checked", value); i.ui.checkbox.checked = value; setSelectionCheckboxValue(i, value); } lastKeyboardEvent = _lastKeyboardEvent; } lastSelection = mediaItem; } editButton.addEventListener("click", e => { e.stopPropagation(); if (!checkbox.checked) { checkbox.checked = true; setSelectionCheckboxValue(mediaItem, true); } let sel = selectedThumbnails.map(x => MediaStorage.Instance.getMediaLocal(x)).filter(x => x.writeAccess); if (sel.length === 1 && sel[0].md5sum === mediaItem.md5sum) { checkbox.checked = false; setSelectionCheckboxValue(mediaItem, false); document.location.hash = mediaItem.md5sum; return; } console.log(sel); }); container.addEventListener("click", () => { if (selectedThumbnails.length || lastKeyboardEvent?.ctrlKey) { let value = !checkbox.checked; checkbox.setAttribute("checked", value); checkbox.checked = value; cascadeSetSelectionCheckboxValue(value); return; } document.location.hash = mediaItem.md5sum; }); checkbox.addEventListener("click", evt => { evt.stopPropagation(); }); checkbox.addEventListener("change", evt => { cascadeSetSelectionCheckboxValue(checkbox.checked); }); return mediaItem.ui = { root: container, img: img, checkbox: checkbox }; } function buildYear(date) { let result = document.createElement('h3'); result.textContent = date.getUTCFullYear(); return result; } function buildMonth(date) { let result = document.createElement('h4'); result.textContent = date.toLocaleString('default', { month: 'long' }); return result; } function redraw(container, media) { buildThumbnail(media); if (!media.ui) return; let yearUpdated = !container.dataset.lastItemYear || container.dataset.lastItemYear != media.date.getUTCFullYear(); if (yearUpdated) { container.appendChild(buildYear(media.date)); container.dataset.lastItemYear = media.date.getUTCFullYear(); } if (yearUpdated || container.dataset.lastItemMonth === undefined || container.dataset.lastItemMonth != media.date.getUTCMonth()) { container.appendChild(buildMonth(media.date)); container.dataset.lastItemMonth = media.date.getUTCMonth(); } container.appendChild(media.ui.root); } MediaStorage.Instance.addEventListener("rebuildMedia", (evt) => { let newContainer = document.getElementById('pch-mediaList'); newContainer.textContent = ''; newContainer.dataset.lastItemYear = null; newContainer.dataset.lastItemMonth = null; for (let i of MediaStorage.Instance.medias) if (window.FilterManager.match(i)) redraw(newContainer, i); }); MediaStorage.Instance.addEventListener("newMedia", (evt) => { let container = document.getElementById('pch-mediaList'); if (window.FilterManager.match(evt.detail)) redraw(container, evt.detail); }); 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', () => MediaStorage.Instance.setMetaValue(fullPageMediaDisplayed.md5sum, key, valInput.value)); inputGroup.addEventListener('submit', evt => { evt.preventDefault(); MediaStorage.Instance.setMetaValue(fullPageMediaDisplayed.md5sum, key, valInput.value); }); } li.appendChild(keySpan); li.appendChild(valSpan); return li; } function displayMetas(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()))) { let metaItem = displayMeta(i, metaData[i], isRo || !MediaStorage.Instance.allMetaTypes[i]?.canWrite); metaItem && metaList.appendChild(metaItem); } if (!isRo) for (let i of ['geoCountry']) { if (!metaData[i]) { let metaItem = displayMeta(i, "", false); metaItem && metaList.appendChild(metaItem); } } return metaList; } 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', e => { e.preventDefault(); }); uiItem.appendChild(btItem); } tagListUi.appendChild(uiItem); }; for (let i of fixedTagList) createTagUiItem(i, true); for (let i of tagList) createTagUiItem(i, !writeAccess); return tagListUi; } function _displayMediaFullPage(fileName, imgUrl, metaData, downloadLink, 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 = ""; document.getElementById("pch-fullPageDetail").appendChild(displayMetas(Object.assign({}, metaData || {}), !writeAccess)); document.getElementById("pch-fullPageDetail").appendChild(displayTags(metaData?.fixedTags || [], metaData?.tags || [], writeAccess)); if (downloadLink) { document.getElementById("pch-fullPageDetail-dlButton").classList.remove("hidden"); document.getElementById("pch-fullPageDetail-dlButton").dataset["link"] = downloadLink; } else { document.getElementById("pch-fullPageDetail-dlButton").classList.add("hidden"); } }); } function LoadPreviousMedia() { let media = MediaStorage.Instance.previousMedia(fullPageMediaDisplayed); if (media) window.displayMediaFullPage(media); } function LoadNextMedia() { let media = MediaStorage.Instance.nextMedia(fullPageMediaDisplayed); if (media) window.displayMediaFullPage(media); } function CloseFullpageMedia() { document.getElementById("pch-fullPageMedia").classList.add("hidden"); fullPageMediaDisplayed = false; history.pushState({}, '', '#'); } window.displayMediaFullPage = function(mediaItem) { document.getElementById("pch-fullPageMedia").classList.remove("hidden"); fullPageMediaDisplayed = mediaItem; if (!mediaItem) return _displayMediaFullPage("Error", 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.date } || undefined, filename: mediaItem.filename ? { type: 'string', value: mediaItem.fileName } : undefined, fixedTags: mediaItem.fixedTags, tags: mediaItem.tags }; if (document.location.hash != `#${mediaItem.md5sum}`) history.pushState({}, '', `#${mediaItem.md5sum}`); return _displayMediaFullPage(mediaItem.fileName, `${mediaItem.thumbnail}?w=${requestSize.width}&h=${requestSize.height}&q=6`, meta, mediaItem.original, mediaItem.writeAccess); } 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.getElementById('pch-fullPageDetail-dlButton').addEventListener("click", (evt) => { if (!evt.target?.dataset?.link) return; let link = document.createElement('a'); link.target='_blank'; link.setAttribute("download", ""); link.href = evt.target.dataset.link; link.click(); }); document.addEventListener("keyup", evt => { lastKeyboardEvent = evt; }); document.addEventListener("keydown", evt => { lastKeyboardEvent = evt; if (!fullPageMediaDisplayed) return; if (evt.keyCode === 37 || evt.keyCode === 38) LoadPreviousMedia(); else if (evt.keyCode === 39 || evt.keyCode === 40) LoadNextMedia(); else if (evt.keyCode === 27) CloseFullpageMedia(); else { console.log("Unregistered key event", evt.key, evt.keyCode); return; } evt.preventDefault(); }); });