photo.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. /**
  2. * @description Takes care of every action a photo can handle and execute.
  3. * @copyright 2015 by Tobias Reich
  4. */
  5. photo = {
  6. json : null,
  7. cache : null
  8. }
  9. photo.getID = function() {
  10. let id = null
  11. if (photo.json) id = photo.json.id
  12. else id = $('.photo:hover, .photo.active').attr('data-id')
  13. if ($.isNumeric(id)===true) return id
  14. else return false
  15. }
  16. photo.load = function(photoID, albumID) {
  17. const checkContent = function() {
  18. if (album.json!=null) photo.load(photoID, albumID)
  19. else setTimeout(checkContent, 100)
  20. }
  21. const checkPasswd = function() {
  22. if (password.value!=='') photo.load(photoID, albumID)
  23. else setTimeout(checkPasswd, 200)
  24. }
  25. if (album.json==null) {
  26. checkContent()
  27. return false
  28. }
  29. let params = {
  30. photoID,
  31. albumID,
  32. password: password.value
  33. }
  34. api.post('Photo::get', params, function(data) {
  35. if (data==='Warning: Photo private!') {
  36. lychee.content.show()
  37. lychee.goto()
  38. return false
  39. }
  40. if (data==='Warning: Wrong password!') {
  41. checkPasswd()
  42. return false
  43. }
  44. photo.json = data
  45. if (!visible.photo()) view.photo.show()
  46. view.photo.init()
  47. lychee.imageview.show()
  48. setTimeout(() => {
  49. lychee.content.show()
  50. photo.preloadNext(photoID)
  51. }, 300)
  52. })
  53. }
  54. // Preload the next photo for better response time
  55. photo.preloadNext = function(photoID) {
  56. if (album.json &&
  57. album.json.content &&
  58. album.json.content[photoID] &&
  59. album.json.content[photoID].nextPhoto!='') {
  60. let nextPhoto = album.json.content[photoID].nextPhoto
  61. let url = album.json.content[nextPhoto].url
  62. let medium = album.json.content[nextPhoto].medium
  63. let href = (medium!=null && medium!=='' ? medium : url)
  64. $('head [data-prefetch]').remove()
  65. $('head').append(`<link data-prefetch rel="prefetch" href="${ href }">`)
  66. }
  67. }
  68. photo.parse = function() {
  69. if (!photo.json.title) photo.json.title = 'Untitled'
  70. }
  71. photo.previous = function(animate) {
  72. if (photo.getID()!==false &&
  73. album.json &&
  74. album.json.content[photo.getID()] &&
  75. album.json.content[photo.getID()].previousPhoto!=='') {
  76. let delay = 0
  77. if (animate===true) {
  78. delay = 200
  79. $('#imageview #image').css({
  80. WebkitTransform : 'translateX(100%)',
  81. MozTransform : 'translateX(100%)',
  82. transform : 'translateX(100%)',
  83. opacity : 0
  84. })
  85. }
  86. setTimeout(() => {
  87. if (photo.getID()===false) return false
  88. lychee.goto(album.getID() + '/' + album.json.content[photo.getID()].previousPhoto)
  89. }, delay)
  90. }
  91. }
  92. photo.next = function(animate) {
  93. if (photo.getID()!==false &&
  94. album.json &&
  95. album.json.content[photo.getID()] &&
  96. album.json.content[photo.getID()].nextPhoto!=='') {
  97. let delay = 0
  98. if (animate===true) {
  99. delay = 200
  100. $('#imageview #image').css({
  101. WebkitTransform : 'translateX(-100%)',
  102. MozTransform : 'translateX(-100%)',
  103. transform : 'translateX(-100%)',
  104. opacity : 0
  105. })
  106. }
  107. setTimeout(() => {
  108. if (photo.getID()===false) return false
  109. lychee.goto(album.getID() + '/' + album.json.content[photo.getID()].nextPhoto)
  110. }, delay)
  111. }
  112. }
  113. photo.duplicate = function(photoIDs) {
  114. if (!photoIDs) return false
  115. if (photoIDs instanceof Array===false) photoIDs = [ photoIDs ]
  116. albums.refresh()
  117. let params = {
  118. photoIDs: photoIDs.join()
  119. }
  120. api.post('Photo::duplicate', params, function(data) {
  121. if (data!==true) lychee.error(null, params, data)
  122. else album.load(album.getID())
  123. })
  124. }
  125. photo.delete = function(photoIDs) {
  126. let action = {}
  127. let cancel = {}
  128. let msg = ''
  129. let photoTitle = ''
  130. if (!photoIDs) return false
  131. if (photoIDs instanceof Array===false) photoIDs = [ photoIDs ]
  132. if (photoIDs.length===1) {
  133. // Get title if only one photo is selected
  134. if (visible.photo()) photoTitle = photo.json.title
  135. else photoTitle = album.json.content[photoIDs].title
  136. // Fallback for photos without a title
  137. if (photoTitle==='') photoTitle = 'Untitled'
  138. }
  139. action.fn = function() {
  140. let nextPhoto = null
  141. let previousPhoto = null
  142. basicModal.close()
  143. photoIDs.forEach(function(id, index, array) {
  144. // Change reference for the next and previous photo
  145. if (album.json.content[id].nextPhoto!=='' || album.json.content[id].previousPhoto!=='') {
  146. nextPhoto = album.json.content[id].nextPhoto
  147. previousPhoto = album.json.content[id].previousPhoto
  148. album.json.content[previousPhoto].nextPhoto = nextPhoto
  149. album.json.content[nextPhoto].previousPhoto = previousPhoto
  150. }
  151. delete album.json.content[id]
  152. view.album.content.delete(id)
  153. })
  154. albums.refresh()
  155. // Go to next photo if there is a next photo and
  156. // next photo is not the current one. Show album otherwise.
  157. if (visible.photo() && nextPhoto!=null && nextPhoto!==photo.getID()) lychee.goto(album.getID() + '/' + nextPhoto)
  158. else if (!visible.albums()) lychee.goto(album.getID())
  159. let params = {
  160. photoIDs: photoIDs.join()
  161. }
  162. api.post('Photo::delete', params, function(data) {
  163. if (data!==true) lychee.error(null, params, data)
  164. })
  165. }
  166. if (photoIDs.length===1) {
  167. action.title = 'Delete Photo'
  168. cancel.title = 'Keep Photo'
  169. msg = lychee.html`<p>Are you sure you want to delete the photo '$${ photoTitle }'? This action can't be undone!</p>`
  170. } else {
  171. action.title = 'Delete Photo'
  172. cancel.title = 'Keep Photo'
  173. msg = lychee.html`<p>Are you sure you want to delete all $${ photoIDs.length } selected photo? This action can't be undone!</p>`
  174. }
  175. basicModal.show({
  176. body: msg,
  177. buttons: {
  178. action: {
  179. title: action.title,
  180. fn: action.fn,
  181. class: 'red'
  182. },
  183. cancel: {
  184. title: cancel.title,
  185. fn: basicModal.close
  186. }
  187. }
  188. })
  189. }
  190. photo.setTitle = function(photoIDs) {
  191. let oldTitle = ''
  192. let msg = ''
  193. if (!photoIDs) return false
  194. if (photoIDs instanceof Array===false) photoIDs = [ photoIDs ]
  195. if (photoIDs.length===1) {
  196. // Get old title if only one photo is selected
  197. if (photo.json) oldTitle = photo.json.title
  198. else if (album.json) oldTitle = album.json.content[photoIDs].title
  199. }
  200. const action = function(data) {
  201. basicModal.close()
  202. let newTitle = data.title
  203. if (visible.photo()) {
  204. photo.json.title = (newTitle==='' ? 'Untitled' : newTitle)
  205. view.photo.title()
  206. }
  207. photoIDs.forEach(function(id, index, array) {
  208. album.json.content[id].title = newTitle
  209. view.album.content.title(id)
  210. })
  211. let params = {
  212. photoIDs : photoIDs.join(),
  213. title : newTitle
  214. }
  215. api.post('Photo::setTitle', params, function(data) {
  216. if (data!==true) lychee.error(null, params, data)
  217. })
  218. }
  219. let input = lychee.html`<input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='$${ oldTitle }'>`
  220. if (photoIDs.length===1) msg = lychee.html`<p>Enter a new title for this photo: ${ input }</p>`
  221. else msg = lychee.html`<p>Enter a title for all $${ photoIDs.length } selected photos: ${ input }</p>`
  222. basicModal.show({
  223. body: msg,
  224. buttons: {
  225. action: {
  226. title: 'Set title',
  227. fn: action
  228. },
  229. cancel: {
  230. title: 'Cancel',
  231. fn: basicModal.close
  232. }
  233. }
  234. })
  235. }
  236. photo.setAlbum = function(photoIDs, albumID) {
  237. let nextPhoto = null
  238. let previousPhoto = null
  239. if (!photoIDs) return false
  240. if (photoIDs instanceof Array===false) photoIDs = [ photoIDs ]
  241. photoIDs.forEach(function(id, index, array) {
  242. // Change reference for the next and previous photo
  243. if (album.json.content[id].nextPhoto!==''||album.json.content[id].previousPhoto!=='') {
  244. nextPhoto = album.json.content[id].nextPhoto
  245. previousPhoto = album.json.content[id].previousPhoto
  246. album.json.content[previousPhoto].nextPhoto = nextPhoto
  247. album.json.content[nextPhoto].previousPhoto = previousPhoto
  248. }
  249. delete album.json.content[id]
  250. view.album.content.delete(id)
  251. })
  252. albums.refresh()
  253. // Go to next photo if there is a next photo and
  254. // next photo is not the current one. Show album otherwise.
  255. if (visible.photo() && nextPhoto!=null && nextPhoto!==photo.getID()) lychee.goto(album.getID() + '/' + nextPhoto)
  256. else if (!visible.albums()) lychee.goto(album.getID())
  257. let params = {
  258. photoIDs: photoIDs.join(),
  259. albumID
  260. }
  261. api.post('Photo::setAlbum', params, function(data) {
  262. if (data!==true) lychee.error(null, params, data)
  263. })
  264. }
  265. photo.setStar = function(photoIDs) {
  266. if (!photoIDs) return false
  267. if (visible.photo()) {
  268. photo.json.star = (photo.json.star==='0' ? '1' : '0')
  269. view.photo.star()
  270. }
  271. photoIDs.forEach(function(id, index, array) {
  272. album.json.content[id].star = (album.json.content[id].star==='0' ? '1' : '0')
  273. view.album.content.star(id)
  274. })
  275. albums.refresh()
  276. let params = {
  277. photoIDs: photoIDs.join()
  278. }
  279. api.post('Photo::setStar', params, function(data) {
  280. if (data!==true) lychee.error(null, params, data)
  281. })
  282. }
  283. photo.setPublic = function(photoID, e) {
  284. if (photo.json.public==='2') {
  285. const action = function() {
  286. basicModal.close()
  287. lychee.goto(photo.json.original_album)
  288. }
  289. basicModal.show({
  290. body: '<p>This photo is located in a public album. To make this photo private or public, edit the visibility of the associated album.</p>',
  291. buttons: {
  292. action: {
  293. title: 'Show Album',
  294. fn: action
  295. },
  296. cancel: {
  297. title: 'Cancel',
  298. fn: basicModal.close
  299. }
  300. }
  301. })
  302. return false
  303. }
  304. if (visible.photo()) {
  305. photo.json.public = (photo.json.public==='0' ? '1' : '0')
  306. view.photo.public()
  307. if (photo.json.public==='1') contextMenu.sharePhoto(photoID, e)
  308. }
  309. album.json.content[photoID].public = (album.json.content[photoID].public==='0' ? '1' : '0')
  310. view.album.content.public(photoID)
  311. albums.refresh()
  312. api.post('Photo::setPublic', { photoID }, function(data) {
  313. if (data!==true) lychee.error(null, params, data)
  314. })
  315. }
  316. photo.setDescription = function(photoID) {
  317. let oldDescription = photo.json.description
  318. const action = function(data) {
  319. basicModal.close()
  320. let description = data.description
  321. if (visible.photo()) {
  322. photo.json.description = description
  323. view.photo.description()
  324. }
  325. let params = {
  326. photoID,
  327. description
  328. }
  329. api.post('Photo::setDescription', params, function(data) {
  330. if (data!==true) lychee.error(null, params, data)
  331. })
  332. }
  333. basicModal.show({
  334. body: lychee.html`<p>Enter a description for this photo: <input class='text' name='description' type='text' maxlength='800' placeholder='Description' value='$${ oldDescription }'></p>`,
  335. buttons: {
  336. action: {
  337. title: 'Set Description',
  338. fn: action
  339. },
  340. cancel: {
  341. title: 'Cancel',
  342. fn: basicModal.close
  343. }
  344. }
  345. })
  346. }
  347. photo.editTags = function(photoIDs) {
  348. let oldTags = ''
  349. let msg = ''
  350. if (!photoIDs) return false
  351. if (photoIDs instanceof Array===false) photoIDs = [ photoIDs ]
  352. // Get tags
  353. if (visible.photo()) oldTags = photo.json.tags
  354. else if (visible.album() && photoIDs.length===1) oldTags = album.json.content[photoIDs].tags
  355. else if (visible.search() && photoIDs.length===1) oldTags = album.json.content[photoIDs].tags
  356. else if (visible.album() && photoIDs.length>1) {
  357. let same = true
  358. photoIDs.forEach(function(id, index, array) {
  359. if (album.json.content[id].tags===album.json.content[photoIDs[0]].tags && same===true) same = true
  360. else same = false
  361. })
  362. if (same===true) oldTags = album.json.content[photoIDs[0]].tags
  363. }
  364. // Improve tags
  365. oldTags = oldTags.replace(/,/g, ', ')
  366. const action = function(data) {
  367. basicModal.close()
  368. photo.setTags(photoIDs, data.tags)
  369. }
  370. let input = lychee.html`<input class='text' name='tags' type='text' maxlength='800' placeholder='Tags' value='$${ oldTags }'>`
  371. if (photoIDs.length===1) msg = lychee.html`<p>Enter your tags for this photo. You can add multiple tags by separating them with a comma: ${ input }</p>`
  372. else msg = lychee.html`<p>Enter your tags for all $${ photoIDs.length } selected photos. Existing tags will be overwritten. You can add multiple tags by separating them with a comma: ${ input }</p>`
  373. basicModal.show({
  374. body: msg,
  375. buttons: {
  376. action: {
  377. title: 'Set Tags',
  378. fn: action
  379. },
  380. cancel: {
  381. title: 'Cancel',
  382. fn: basicModal.close
  383. }
  384. }
  385. })
  386. }
  387. photo.setTags = function(photoIDs, tags) {
  388. if (!photoIDs) return false
  389. if (photoIDs instanceof Array===false) photoIDs = [ photoIDs ]
  390. // Parse tags
  391. tags = tags.replace(/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/g, ',')
  392. tags = tags.replace(/,$|^,|(\ ){0,}$/g, '')
  393. if (visible.photo()) {
  394. photo.json.tags = tags
  395. view.photo.tags()
  396. }
  397. photoIDs.forEach(function(id, index, array) {
  398. album.json.content[id].tags = tags
  399. })
  400. let params = {
  401. photoIDs: photoIDs.join(),
  402. tags
  403. }
  404. api.post('Photo::setTags', params, function(data) {
  405. if (data!==true) lychee.error(null, params, data)
  406. })
  407. }
  408. photo.deleteTag = function(photoID, index) {
  409. let tags
  410. // Remove
  411. tags = photo.json.tags.split(',')
  412. tags.splice(index, 1)
  413. // Save
  414. photo.json.tags = tags.toString()
  415. photo.setTags([ photoID ], photo.json.tags)
  416. }
  417. photo.share = function(photoID, service) {
  418. let url = photo.getViewLink(photoID)
  419. switch (service) {
  420. case 'twitter':
  421. window.open(`https://twitter.com/share?url=${ encodeURI(url) }`)
  422. break
  423. case 'facebook':
  424. window.open(`http://www.facebook.com/sharer.php?u=${ encodeURI(url) }&t=${ encodeURI(photo.json.title) }`)
  425. break
  426. case 'mail':
  427. location.href = `mailto:?subject=${ encodeURI(photo.json.title) }&body=${ encodeURI(url) }`
  428. break
  429. case 'dropbox':
  430. lychee.loadDropbox(function() {
  431. let filename = photo.json.title + '.' + photo.getDirectLink().split('.').pop()
  432. Dropbox.save(photo.getDirectLink(), filename)
  433. })
  434. break
  435. }
  436. }
  437. photo.getArchive = function(photoID) {
  438. let link
  439. let url = `${ api.path }?function=Photo::getArchive&photoID=${ photoID }`
  440. if (location.href.indexOf('index.html')>0) link = location.href.replace(location.hash, '').replace('index.html', url)
  441. else link = location.href.replace(location.hash, '') + url
  442. if (lychee.publicMode===true) link += `&password=${ encodeURIComponent(password.value) }`
  443. location.href = link
  444. }
  445. photo.getDirectLink = function() {
  446. let url = ''
  447. if (photo.json && photo.json.url && photo.json.url!=='') url = photo.json.url
  448. return url
  449. }
  450. photo.getViewLink = function(photoID) {
  451. let url = 'view.php?p=' + photoID
  452. if (location.href.indexOf('index.html')>0) return location.href.replace('index.html' + location.hash, url)
  453. else return location.href.replace(location.hash, url)
  454. }