photo.js 14 KB

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