photo.js 15 KB

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