uiMedia.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. $(() => {
  2. var fullPageMediaDisplayed = false;
  3. var selectedThumbnails = [];
  4. var lastKeyboardEvent = null;
  5. var lastSelection = null;
  6. function onItemSelected(mediaItem) {
  7. document.getElementById("pch-mediaList").classList.add("selection");
  8. }
  9. function onItemDeselected(mediaItem) {
  10. if (!selectedThumbnails.length)
  11. document.getElementById("pch-mediaList").classList.remove("selection");
  12. }
  13. function buildThumbnail(mediaItem) {
  14. if (mediaItem.ui)
  15. return mediaItem.ui;
  16. let checkbox = document.createElement("input");
  17. let img = document.createElement("img");
  18. let container = document.createElement("li");
  19. let loadingImg = document.createElement("div");
  20. checkbox.type = "checkbox";
  21. container.classList.add("pch-image");
  22. container.classList.add("loading");
  23. loadingImg.classList.add("spinner");
  24. loadingImg.innerHTML = "<span class='spinner-grow'></span>";
  25. container.dataset.md5sum = mediaItem.md5sum;
  26. img.loading = "lazy";
  27. let requestSize = mediaItem.resize(450, 450);
  28. img.src = `${mediaItem.thumbnail}?w=${requestSize.width}&h=${requestSize.height}&q=4`;
  29. img.classList.add("img-fluid");
  30. img.classList.add("img-thumbnail");
  31. img.addEventListener("load", () => {
  32. container.classList.remove("loading");
  33. container.classList.remove("spinner-grow");
  34. });
  35. container.style.width = `${requestSize.width}px`;
  36. container.appendChild(loadingImg);
  37. container.appendChild(img);
  38. container.appendChild(checkbox);
  39. let setSelectionCheckboxValue = function(media, value) {
  40. let checked = media.ui.checkbox.checked;
  41. let indexInSelection = selectedThumbnails.indexOf(media.md5sum);
  42. if (checked && indexInSelection < 0) {
  43. selectedThumbnails.push(media.md5sum);
  44. onItemSelected(media);
  45. return true;
  46. } else if (!checked && indexInSelection >= 0) {
  47. selectedThumbnails.splice(indexInSelection, 1);
  48. onItemDeselected(media);
  49. return true;
  50. }
  51. return false;
  52. }
  53. let cascadeSetSelectionCheckboxValue = function(value) {
  54. if (!setSelectionCheckboxValue(mediaItem, value))
  55. return;
  56. if (lastKeyboardEvent?.shiftKey && lastSelection) {
  57. let _lastKeyboardEvent = lastKeyboardEvent;
  58. lastKeyboardEvent = null;
  59. for (let i of MediaStorage.Instance.getMediaBetween(lastSelection, mediaItem)) {
  60. if (i === mediaItem)
  61. continue;
  62. i.ui.checkbox.setAttribute("checked", value);
  63. i.ui.checkbox.checked = value;
  64. setSelectionCheckboxValue(i, value);
  65. }
  66. lastKeyboardEvent = _lastKeyboardEvent;
  67. }
  68. lastSelection = mediaItem;
  69. }
  70. container.addEventListener("click", () => {
  71. if (selectedThumbnails.length || lastKeyboardEvent?.ctrlKey) {
  72. let value = !checkbox.checked;
  73. checkbox.setAttribute("checked", value);
  74. checkbox.checked = value;
  75. cascadeSetSelectionCheckboxValue(value);
  76. return;
  77. }
  78. document.location.hash = mediaItem.md5sum;
  79. });
  80. checkbox.addEventListener("click", evt => {
  81. evt.stopPropagation();
  82. });
  83. checkbox.addEventListener("change", evt => {
  84. cascadeSetSelectionCheckboxValue(checkbox.checked);
  85. });
  86. return mediaItem.ui = {
  87. root: container,
  88. img: img,
  89. checkbox: checkbox
  90. };
  91. }
  92. function buildYear(date) {
  93. let result = document.createElement('h3');
  94. result.textContent = date.getUTCFullYear();
  95. return result;
  96. }
  97. function buildMonth(date) {
  98. let result = document.createElement('h4');
  99. result.textContent = date.toLocaleString('default', { month: 'long' });
  100. return result;
  101. }
  102. function redraw(container, media) {
  103. buildThumbnail(media);
  104. if (!media.ui)
  105. return;
  106. let yearUpdated = !container.dataset.lastItemYear || container.dataset.lastItemYear != media.date.getUTCFullYear();
  107. if (yearUpdated) {
  108. container.appendChild(buildYear(media.date));
  109. container.dataset.lastItemYear = media.date.getUTCFullYear();
  110. }
  111. if (yearUpdated || container.dataset.lastItemMonth === undefined || container.dataset.lastItemMonth != media.date.getUTCMonth()) {
  112. container.appendChild(buildMonth(media.date));
  113. container.dataset.lastItemMonth = media.date.getUTCMonth();
  114. }
  115. container.appendChild(media.ui.root);
  116. }
  117. MediaStorage.Instance.addEventListener("rebuildMedia", (evt) => {
  118. let newContainer = document.getElementById('pch-mediaList');
  119. newContainer.textContent = '';
  120. newContainer.dataset.lastItemYear = null;
  121. newContainer.dataset.lastItemMonth = null;
  122. for (let i of MediaStorage.Instance.medias)
  123. if (window.FilterManager.match(i))
  124. redraw(newContainer, i);
  125. });
  126. MediaStorage.Instance.addEventListener("newMedia", (evt) => {
  127. let container = document.getElementById('pch-mediaList');
  128. if (window.FilterManager.match(evt.detail))
  129. redraw(container, evt.detail);
  130. });
  131. function serializeFileSize(size) {
  132. let units = [ 'o', 'Ko', 'Mo', 'Go', 'To' ];
  133. let idx = 0;
  134. while (size >= 800 && idx < units.length) {
  135. ++idx;
  136. size /= 1024;
  137. }
  138. size = Math.floor(size * 100) / 100;
  139. return `${size} ${units[idx]}`;
  140. }
  141. function displayMeta(key, value) {
  142. let li = document.createElement("li");
  143. let type = value?.type || null;
  144. let val = (value?.value ? value.value : value) || null;
  145. if (!val)
  146. return null;
  147. if (type === 'date')
  148. val = val.toLocaleString();
  149. if (["dimension", "height", "width"].indexOf(key) >= 0)
  150. val += ' px';
  151. if (["fileSize"].indexOf(key) >= 0)
  152. val = serializeFileSize(val);
  153. if (key == 'fNumber')
  154. val = `f/ ${val}`;
  155. let keyTranslate = {
  156. fileSize: "File Size",
  157. photochamberImport: "Photochamber Imported",
  158. lensModel: "Lens Model",
  159. exposureTimeStr: "Exposure Time"
  160. };
  161. let keySpan = document.createElement('span');
  162. let valSpan = document.createElement('span');
  163. keySpan.classList.add("metaKey");
  164. valSpan.classList.add("metaVal");
  165. keySpan.innerText = (keyTranslate[key] || (key[0].toUpperCase()+key.substr(1))) + ':';
  166. valSpan.innerText = val;
  167. li.appendChild(keySpan);
  168. li.appendChild(valSpan);
  169. return li;
  170. }
  171. function displayMetas(metaData) {
  172. let metaList = document.createElement("ul");
  173. metaData.libraryPath = null;
  174. metaData.tags = metaData.fixedTags = null;
  175. if (metaData.exposureTime && metaData.exposureTimeStr)
  176. metaData.exposureTime = null;
  177. if (metaData.date && metaData.dateTime)
  178. metaData.dateTime = null;
  179. if (metaData.height && metaData.width) {
  180. metaData.dimension = { type: "string", value: `${metaData.width.value} x ${metaData.height.value}` }
  181. metaData.height = metaData.width = null;
  182. }
  183. for (let i of [ "date", "dimension", "height", "width", "fileSize" ]) {
  184. let metaItem = displayMeta(i, metaData[i]);
  185. metaItem && metaList.appendChild(metaItem);
  186. metaData[i] = null;
  187. }
  188. // FIXME sort and filter
  189. for (let i in metaData) {
  190. let metaItem = displayMeta(i, metaData[i]);
  191. metaItem && metaList.appendChild(metaItem);
  192. }
  193. return metaList;
  194. }
  195. function displayTags(fixedTagList, tagList, writeAccess) {
  196. let tagListUi = document.createElement("ul");
  197. tagListUi.className = "taglist";
  198. let createTagUiItem = (i, roTag) => {
  199. let uiItem = document.createElement("li");
  200. uiItem.className = "badge text-bg-light";
  201. let textItem = document.createElement("span");
  202. textItem.textContent = i;
  203. uiItem.appendChild(textItem);
  204. if (!roTag) {
  205. let btItem = document.createElement("a");
  206. btItem.className = "border-start bi bi-x removeBt";
  207. btItem.href = '#';
  208. btItem.addEventListener('click', e => {
  209. e.preventDefault();
  210. });
  211. uiItem.appendChild(btItem);
  212. }
  213. tagListUi.appendChild(uiItem);
  214. };
  215. for (let i of fixedTagList)
  216. createTagUiItem(i, true);
  217. for (let i of tagList)
  218. createTagUiItem(i, !writeAccess);
  219. return tagListUi;
  220. }
  221. function _displayMediaFullPage(fileName, imgUrl, metaData, downloadLink, writeAccess) {
  222. return new Promise(ok => {
  223. document.getElementById("pch-fullPagePreviewContainer").classList.add("loading");
  224. document.getElementById("pch-fullPageMedia-title").innerText = fileName;
  225. document.getElementById("pch-fullPagePreview").onceLoaded = ok;
  226. document.getElementById("pch-fullPagePreview").src = imgUrl;
  227. document.getElementById("pch-fullPageDetail").innerText = "";
  228. document.getElementById("pch-fullPageDetail").appendChild(displayMetas(Object.create(metaData || {})));
  229. document.getElementById("pch-fullPageDetail").appendChild(displayTags(metaData?.fixedTags || [], metaData?.tags || [], writeAccess));
  230. if (downloadLink) {
  231. document.getElementById("pch-fullPageDetail-dlButton").classList.remove("hidden");
  232. document.getElementById("pch-fullPageDetail-dlButton").dataset["link"] = downloadLink;
  233. } else {
  234. document.getElementById("pch-fullPageDetail-dlButton").classList.add("hidden");
  235. }
  236. });
  237. }
  238. function LoadPreviousMedia() {
  239. let media = MediaStorage.Instance.previousMedia(fullPageMediaDisplayed);
  240. if (media)
  241. window.displayMediaFullPage(media);
  242. }
  243. function LoadNextMedia() {
  244. let media = MediaStorage.Instance.nextMedia(fullPageMediaDisplayed);
  245. if (media)
  246. window.displayMediaFullPage(media);
  247. }
  248. function CloseFullpageMedia() {
  249. document.getElementById("pch-fullPageMedia").classList.add("hidden");
  250. fullPageMediaDisplayed = false;
  251. history.pushState({}, '', '#');
  252. }
  253. window.displayMediaFullPage = function(mediaItem) {
  254. document.getElementById("pch-fullPageMedia").classList.remove("hidden");
  255. fullPageMediaDisplayed = mediaItem;
  256. if (!mediaItem)
  257. return _displayMediaFullPage("Error", null, {}, [], null, false);
  258. let containerSize = document.getElementById("pch-fullPageMedia").getBoundingClientRect();
  259. let requestSize = mediaItem.resize(containerSize.width, containerSize.height);
  260. document.getElementById("pch-fullPagePreview").parentNode.style.maxWidth = "100%";
  261. document.getElementById("pch-fullPagePreview").parentNode.style.maxHeight = "100%";
  262. let meta = {
  263. ...mediaItem.meta,
  264. date: { type: 'date', value: mediaItem.date } || undefined,
  265. filename: mediaItem.filename ? { type: 'string', value: mediaItem.fileName } : undefined,
  266. fixedTags: mediaItem.fixedTags,
  267. tags: mediaItem.tags
  268. };
  269. if (document.location.hash != `#${mediaItem.md5sum}`)
  270. history.pushState({}, '', `#${mediaItem.md5sum}`);
  271. return _displayMediaFullPage(mediaItem.fileName, `${mediaItem.thumbnail}?w=${requestSize.width}&h=${requestSize.height}&q=6`, meta, mediaItem.original, mediaItem.writeAccess);
  272. }
  273. document.getElementById("pch-fullPageMedia-closeBt")
  274. .addEventListener("click", () => CloseFullpageMedia());
  275. document.getElementById("pch-fullPagePreview").addEventListener("load", () => {
  276. document.getElementById("pch-fullPagePreviewContainer").classList.remove("loading");
  277. let domItem = document.getElementById("pch-fullPagePreview");
  278. domItem.onceLoaded && domItem.onceLoaded();
  279. domItem.onceLoaded = null;
  280. });
  281. document.getElementById('pch-fullPageDetail-dlButton').addEventListener("click", (evt) => {
  282. if (!evt.target?.dataset?.link)
  283. return;
  284. let link = document.createElement('a');
  285. link.target='_blank';
  286. link.setAttribute("download", "");
  287. link.href = evt.target.dataset.link;
  288. link.click();
  289. });
  290. document.addEventListener("keyup", evt => {
  291. lastKeyboardEvent = evt;
  292. });
  293. document.addEventListener("keydown", evt => {
  294. lastKeyboardEvent = evt;
  295. if (!fullPageMediaDisplayed)
  296. return;
  297. if (evt.keyCode === 37 || evt.keyCode === 38)
  298. LoadPreviousMedia();
  299. else if (evt.keyCode === 39 || evt.keyCode === 40)
  300. LoadNextMedia();
  301. else if (evt.keyCode === 27)
  302. CloseFullpageMedia();
  303. else {
  304. console.log("Unregistered key event", evt.key, evt.keyCode);
  305. return;
  306. }
  307. evt.preventDefault();
  308. });
  309. });