1
0

medias.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. class Media {
  2. constructor(data) {
  3. this.date = new Date(data.date);
  4. this.md5sum = data.md5sum;
  5. this.fixedSum = data.fixedSum;
  6. this.path = data.path;
  7. this.fileName = data.fileName;
  8. this.meta = data.meta || {};
  9. this.fixedTags = [];
  10. this.tags = [];
  11. this.writeAccess = data.accessType === 2;
  12. this.thumbnail = `/api/media/thumbnail/${data.fixedSum}.jpg`;
  13. this.original = `/api/media/original/${data.fixedSum}`;
  14. this.ui = null;
  15. this.setTags(data.fixedTags || [], data.tags || []);
  16. for (let i in this.meta) {
  17. if (this.meta[i].type === 'date')
  18. this.meta[i].value = new Date(parseInt(this.meta[i].value));
  19. else if (this.meta[i].type === 'number' || this.meta[i].type === 'octet')
  20. this.meta[i].value = parseInt(this.meta[i].value);
  21. else if (this.meta[i].type === 'string')
  22. this.meta[i].value = '' + this.meta[i].value;
  23. }
  24. }
  25. resize(maxWidth, maxHeight) {
  26. let ratio = Math.min(1, Math.max(
  27. maxWidth / (this.meta?.width?.value || maxWidth),
  28. maxHeight / (this.meta?.height?.value || maxHeight)));
  29. let result = {
  30. width: Math.floor(this.meta.width?.value *ratio),
  31. height: Math.floor(this.meta.height?.value *ratio),
  32. };
  33. if (isNaN(result.width) || isNaN(result.height) || !result.height || !result.width) {
  34. console.error("Failed to resize image ", this);
  35. return null;
  36. }
  37. return result;
  38. }
  39. setTags(fixedTags, tags) {
  40. this.tags = tags.reduce((acc, tag) => { acc.add(tag.replaceAll(/\/\/+/gi, '/')); return acc; }, new Set());
  41. this.fixedTags = fixedTags.reduce((acc, tag) => { acc.add(tag.replaceAll(/\/\/+/gi, '/')); return acc; }, new Set());
  42. }
  43. allTags() {
  44. return Array.from(new Set([...this.fixedTags, ...this.tags])).sort();
  45. }
  46. }
  47. function tryLoadMedia(md5sum) {
  48. return new Promise((ok, ko) => {
  49. $.get("/api/media/" +md5sum, data => {
  50. let item = new Media(data);
  51. MediaStorage.Instance.pushAll([item], true);
  52. ok(item);
  53. }).fail(err => {
  54. console.error("Trying to get media with md5sum " +md5sum +" failed:", err.responseText);
  55. ok(null);
  56. });
  57. });
  58. }
  59. class MediaStorage extends EventTarget
  60. {
  61. constructor() {
  62. super();
  63. this.#reset();
  64. }
  65. #reset() {
  66. this.allMeta = {};
  67. this.allMetaTypes = {};
  68. this.allTags = new Set();
  69. this.medias = [];
  70. this.oldest = null;
  71. this.newest = null;
  72. }
  73. rebuildMetaList() {
  74. this.#reset();
  75. this.dispatchEvent(new CustomEvent("rebuildMedia"));
  76. window.chronology.reset();
  77. this.downloadMetaList();
  78. }
  79. downloadMetaList() {
  80. if (this.isLoading())
  81. return;
  82. this.#isLoading = true;
  83. document.getElementById("pch-infiniteScrollLoading").classList.remove("hidden");
  84. LoadingTasks.push(() => {
  85. return new Promise(ok => {
  86. let chronology = window.chronology.isInitialized() ? "" : "&chronology"
  87. let oldest = (this.oldest?.date?.getTime() || 0);
  88. let oldestArg = oldest ? `&from=${oldest}` : "";
  89. let requestCount = 300;
  90. $.get(`/api/media/list?count=${requestCount}${chronology}${oldestArg}`, data => {
  91. this.pushAll(data.data.map(i => new Media(i)));
  92. if (data.first || data.last)
  93. window.chronology.rebuildRange(data.first, data.last);
  94. this.#isLoading = false;
  95. if ((data.data?.length || 0) < requestCount) {
  96. document.getElementById("pch-infiniteScrollLoading").classList.add("hidden");
  97. window.ReloadFilters(MediaStorage.Instance);
  98. }
  99. else
  100. setTimeout(this.downloadMetaList.bind(this), 25);
  101. ok();
  102. });
  103. });
  104. });
  105. }
  106. #pushMeta(metaKey, metaVal) {
  107. if (metaKey === 'dateTime')
  108. return;
  109. if (!this.allMeta[metaKey])
  110. this.allMeta[metaKey] = new Set();
  111. this.allMeta[metaKey].add(metaVal.value);
  112. if (!this.allMetaTypes[metaKey])
  113. this.allMetaTypes[metaKey] = { type: metaVal.type, canBeEmpty: !!this.medias.length, canWrite: metaVal.canWrite };
  114. }
  115. #pushTag(tag, first) {
  116. while (tag.length && tag.endsWith('/'))
  117. tag = tag.substr(0, tag.length -1);
  118. this.allTags.add(tag);
  119. let index = tag.lastIndexOf('/');
  120. if (index >= 0)
  121. this.#pushTag(tag.substr(0, index));
  122. }
  123. #pushUnique(media) {
  124. for (let i of media.tags)
  125. this.#pushTag(i, true);
  126. for (let i of media.fixedTags)
  127. this.#pushTag(i, true);
  128. for (let key in media.meta)
  129. this.#pushMeta(key, media.meta[key]);
  130. for (let key in this.allMetaTypes)
  131. if (!media.meta[key])
  132. this.allMetaTypes[key].canBeEmpty = true;
  133. this.medias.push(media);
  134. }
  135. #isLoading = false;
  136. isLoading() { return this.#isLoading; }
  137. pushAll(arr, partialLoad) {
  138. let result = [];
  139. let reorder = false;
  140. for (let i of arr) {
  141. if (partialLoad !== true) {
  142. this.oldest = !this.oldest || this.oldest.date.getTime() > i.date.getTime() ? i : this.oldest;
  143. this.newest = !this.newest || this.newest.date.getTime() < i.date.getTime() ? i : this.newest;
  144. }
  145. if (this.medias.length && this.medias[this.medias.length -1].date.getTime() < i.date.getTime())
  146. reorder = true;
  147. if (this.medias.find(x => x.fixedSum === i.fixedSum))
  148. continue;
  149. this.#pushUnique(i);
  150. result.push(i);
  151. }
  152. for (let i of result)
  153. this.dispatchEvent(new CustomEvent("newMedia", { detail: i }));
  154. if (reorder) {
  155. this.medias.sort((a, b) => b.date.getTime() - a.date.getTime());
  156. this.dispatchEvent(new CustomEvent("rebuildMedia"));
  157. }
  158. }
  159. onFilterUpdated() {
  160. this.dispatchEvent(new CustomEvent("rebuildMedia"));
  161. }
  162. getMediaIndex(media) {
  163. return this.medias.indexOf(media);
  164. }
  165. nextMedia(current) {
  166. return this.medias[this.getMediaIndex(current) +1];
  167. }
  168. previousMedia(current) {
  169. return this.medias[this.getMediaIndex(current) -1];
  170. }
  171. getMediaBetweenIndexes(a, b) {
  172. if (a > b)
  173. return this.getMediaBetweenIndexes(b, a);
  174. return this.medias.slice(a, b +1);
  175. }
  176. getMediaBetween(a, b) {
  177. let aIndex = this.medias.indexOf(a);
  178. let bIndex = this.medias.indexOf(b);
  179. if (aIndex < 0 || bIndex < 0 || aIndex === bIndex)
  180. return [];
  181. return this.getMediaBetweenIndexes(aIndex, bIndex);
  182. }
  183. getMediaLocal(md5sum) {
  184. return this.medias.find(x => x.fixedSum === md5sum);
  185. }
  186. async getMedia(md5sum) {
  187. let media = this.medias.find(x => x.fixedSum === md5sum);
  188. if (media)
  189. return media;
  190. return await tryLoadMedia(md5sum);
  191. }
  192. setMetaValue(md5sum, key, value) {
  193. // FIXME md5sum may be array
  194. return LoadingTasks.push(() => {
  195. return new Promise(ok => {
  196. let media = this.medias.find(x => x.fixedSum === md5sum);
  197. if (!media || !media.writeAccess)
  198. return ok(false);
  199. $.ajax({
  200. url: `/api/media/${encodeURIComponent(md5sum)}/meta/${encodeURIComponent(key)}`,
  201. type: "PATCH",
  202. data: { value },
  203. success: (queryResult) => {
  204. let meta = queryResult.meta[key] || { type: 'string', value: value, canWrite: true };
  205. meta.value = value;
  206. this.#pushMeta(key, meta);
  207. media.meta[key] = meta;
  208. window.ReloadFilters(this);
  209. ok(true);
  210. },
  211. error: err => ok(false),
  212. });
  213. });
  214. });
  215. }
  216. removeTag(md5sum, tagName) {
  217. let md5arr = undefined;
  218. if (Array.isArray(md5sum)) {
  219. md5arr = md5sum;
  220. md5sum = "list";
  221. }
  222. return LoadingTasks.push(() => {
  223. return new Promise(ok => {
  224. let mediaCount = (md5arr || [ md5sum ]).map(checksum => this.medias.find(x => x.fixedSum === checksum)).filter(x => x.writeAccess).length;
  225. if (mediaCount != (md5arr || [ md5sum ]).length)
  226. return ok(false);
  227. $.ajax({
  228. url: `/api/media/${encodeURIComponent(md5sum)}/tag/del/${encodeURIComponent(tagName)}`,
  229. type: "POST",
  230. data: { list: md5arr || [0] },
  231. success: allData => {
  232. allData.forEach(data => {
  233. let media = this.medias.find(x => x.fixedSum === data.fixedSum);
  234. media.setTags(data.fixedTags, data.tags);
  235. for (let i of data.tags)
  236. this.#pushTag(i, true);
  237. for (let i of data.fixedTags)
  238. this.#pushTag(i, true);
  239. });
  240. ok(true);
  241. },
  242. error: err => ok(false),
  243. });
  244. });
  245. });
  246. }
  247. addTag(md5sum, tagName) {
  248. let md5arr = undefined;
  249. if (Array.isArray(md5sum)) {
  250. md5arr = md5sum;
  251. md5sum = "list";
  252. }
  253. return LoadingTasks.push(() => {
  254. return new Promise(ok => {
  255. let mediaCount = (md5arr || [ md5sum ]).map(checksum => this.medias.find(x => x.fixedSum === checksum)).filter(x => x.writeAccess).length;
  256. if (mediaCount != (md5arr || [ md5sum ]).length)
  257. return ok(false);
  258. $.ajax({
  259. url: `/api/media/${encodeURIComponent(md5sum)}/tag`,
  260. type: "PUT",
  261. data: { tag: tagName, list: md5arr },
  262. success: allData => {
  263. allData.forEach(data => {
  264. let media = this.medias.find(x => x.fixedSum === data.fixedSum);
  265. media.setTags(data.fixedTags, data.tags);
  266. for (let i of data.tags)
  267. this.#pushTag(i, true);
  268. for (let i of data.fixedTags)
  269. this.#pushTag(i, true);
  270. });
  271. ok(true);
  272. },
  273. error: err => ok(false),
  274. });
  275. });
  276. });
  277. }
  278. }
  279. MediaStorage.Instance = new MediaStorage();