photo.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  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. oldTitle = oldTitle.replace(/'/g, '&apos;')
  194. }
  195. const action = function(data) {
  196. basicModal.close()
  197. let newTitle = data.title
  198. // Remove html from input
  199. newTitle = lychee.removeHTML(newTitle)
  200. if (visible.photo()) {
  201. photo.json.title = (newTitle==='' ? 'Untitled' : newTitle)
  202. view.photo.title()
  203. }
  204. photoIDs.forEach(function(id, index, array) {
  205. album.json.content[id].title = newTitle
  206. view.album.content.title(id)
  207. })
  208. let params = {
  209. photoIDs: photoIDs.join(),
  210. title: newTitle
  211. }
  212. api.post('Photo::setTitle', params, function(data) {
  213. if (data!==true) lychee.error(null, params, data)
  214. })
  215. }
  216. let input = `<input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='${ oldTitle }'>`
  217. if (photoIDs.length===1) msg = `<p>Enter a new title for this photo: ${ input }</p>`
  218. else msg = `<p>Enter a title for all ${ photoIDs.length } selected photos: ${ input }</p>`
  219. basicModal.show({
  220. body: msg,
  221. buttons: {
  222. action: {
  223. title: 'Set title',
  224. fn: action
  225. },
  226. cancel: {
  227. title: 'Cancel',
  228. fn: basicModal.close
  229. }
  230. }
  231. })
  232. }
  233. photo.setAlbum = function(photoIDs, albumID) {
  234. let nextPhoto,
  235. previousPhoto
  236. if (!photoIDs) return false
  237. if (photoIDs instanceof Array===false) photoIDs = [photoIDs]
  238. if (visible.photo) lychee.goto(album.getID())
  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. album.json.content[id] = null
  248. view.album.content.delete(id)
  249. })
  250. albums.refresh()
  251. let params = {
  252. photoIDs: photoIDs.join(),
  253. albumID
  254. }
  255. api.post('Photo::setAlbum', params, function(data) {
  256. if (data!==true) lychee.error(null, params, data)
  257. })
  258. }
  259. photo.setStar = function(photoIDs) {
  260. if (!photoIDs) return false
  261. if (visible.photo()) {
  262. photo.json.star = (photo.json.star==='0' ? '1' : '0')
  263. view.photo.star()
  264. }
  265. photoIDs.forEach(function(id, index, array) {
  266. album.json.content[id].star = (album.json.content[id].star==='0' ? '1' : '0')
  267. view.album.content.star(id)
  268. })
  269. albums.refresh()
  270. let params = {
  271. photoIDs: photoIDs.join()
  272. }
  273. api.post('Photo::setStar', params, function(data) {
  274. if (data!==true) lychee.error(null, params, data)
  275. })
  276. }
  277. photo.setPublic = function(photoID, e) {
  278. if (photo.json.public==='2') {
  279. const action = function() {
  280. basicModal.close()
  281. lychee.goto(photo.json.original_album)
  282. }
  283. basicModal.show({
  284. 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>",
  285. buttons: {
  286. action: {
  287. title: 'Show Album',
  288. fn: action
  289. },
  290. cancel: {
  291. title: 'Cancel',
  292. fn: basicModal.close
  293. }
  294. }
  295. })
  296. return false
  297. }
  298. if (visible.photo()) {
  299. photo.json.public = (photo.json.public==='0' ? '1' : '0')
  300. view.photo.public()
  301. if (photo.json.public==='1') contextMenu.sharePhoto(photoID, e)
  302. }
  303. album.json.content[photoID].public = (album.json.content[photoID].public==='0' ? '1' : '0')
  304. view.album.content.public(photoID)
  305. albums.refresh()
  306. api.post('Photo::setPublic', { photoID }, function(data) {
  307. if (data!==true) lychee.error(null, params, data)
  308. })
  309. }
  310. photo.setDescription = function(photoID) {
  311. let oldDescription = photo.json.description.replace(/'/g, '&apos;')
  312. const action = function(data) {
  313. basicModal.close()
  314. let description = data.description
  315. // Remove html from input
  316. description = lychee.removeHTML(description)
  317. if (visible.photo()) {
  318. photo.json.description = description
  319. view.photo.description()
  320. }
  321. let params = {
  322. photoID,
  323. description
  324. }
  325. api.post('Photo::setDescription', params, function(data) {
  326. if (data!==true) lychee.error(null, params, data)
  327. })
  328. }
  329. basicModal.show({
  330. body: `<p>Enter a description for this photo: <input class='text' name='description' type='text' maxlength='800' placeholder='Description' value='${ oldDescription }'></p>`,
  331. buttons: {
  332. action: {
  333. title: 'Set Description',
  334. fn: action
  335. },
  336. cancel: {
  337. title: 'Cancel',
  338. fn: basicModal.close
  339. }
  340. }
  341. })
  342. }
  343. photo.editTags = function(photoIDs) {
  344. let oldTags = '',
  345. msg = ''
  346. if (!photoIDs) return false
  347. if (photoIDs instanceof Array===false) photoIDs = [photoIDs]
  348. // Get tags
  349. if (visible.photo()) oldTags = photo.json.tags
  350. else if (visible.album() && photoIDs.length===1) oldTags = album.json.content[photoIDs].tags
  351. else if (visible.search() && photoIDs.length===1) oldTags = album.json.content[photoIDs].tags
  352. else if (visible.album() && photoIDs.length>1) {
  353. let same = true
  354. photoIDs.forEach(function(id, index, array) {
  355. if (album.json.content[id].tags===album.json.content[photoIDs[0]].tags && same===true) same = true
  356. else same = false
  357. })
  358. if (same===true) oldTags = album.json.content[photoIDs[0]].tags
  359. }
  360. // Improve tags
  361. oldTags = oldTags.replace(/,/g, ', ')
  362. const action = function(data) {
  363. basicModal.close()
  364. photo.setTags(photoIDs, data.tags)
  365. }
  366. let input = `<input class='text' name='tags' type='text' maxlength='800' placeholder='Tags' value='${ oldTags }'>`
  367. 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>`
  368. 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>`
  369. basicModal.show({
  370. body: msg,
  371. buttons: {
  372. action: {
  373. title: 'Set Tags',
  374. fn: action
  375. },
  376. cancel: {
  377. title: 'Cancel',
  378. fn: basicModal.close
  379. }
  380. }
  381. })
  382. }
  383. photo.setTags = function(photoIDs, tags) {
  384. if (!photoIDs) return false
  385. if (photoIDs instanceof Array===false) photoIDs = [photoIDs]
  386. // Parse tags
  387. tags = tags.replace(/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/g, ',')
  388. tags = tags.replace(/,$|^,|(\ ){0,}$/g, '')
  389. // Remove html from input
  390. tags = lychee.removeHTML(tags)
  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 link = '',
  417. url = photo.getViewLink(photoID)
  418. switch (service) {
  419. case 'twitter':
  420. link = `https://twitter.com/share?url=${ encodeURI(url) }`
  421. break
  422. case 'facebook':
  423. link = `http://www.facebook.com/sharer.php?u=${ encodeURI(url) }&t=${ encodeURI(photo.json.title) }`
  424. break
  425. case 'mail':
  426. link = `mailto:?subject=${ encodeURI(photo.json.title) }&body=${ encodeURI(url) }`
  427. break
  428. case 'dropbox':
  429. lychee.loadDropbox(function() {
  430. let filename = photo.json.title + '.' + photo.getDirectLink().split('.').pop()
  431. Dropbox.save(photo.getDirectLink(), filename)
  432. })
  433. break
  434. default:
  435. link = ''
  436. break
  437. }
  438. if (link.length!=='') location.href = link
  439. }
  440. photo.getSize = function() {
  441. // Size can be 'big', 'medium' or 'small'
  442. // Default is big
  443. // Small is centered in the middle of the screen
  444. let size = 'big',
  445. scaled = false,
  446. hasMedium = photo.json.medium!=='',
  447. pixelRatio = window.devicePixelRatio,
  448. view = {
  449. width : $(window).width() - 60,
  450. height : $(window).height() - 100
  451. }
  452. // Detect if the photo will be shown scaled,
  453. // because the screen size is smaller than the photo
  454. if (photo.json.width>view.width || photo.json.height>view.height) scaled = true
  455. // Calculate pixel ratio of screen
  456. if (pixelRatio!==undefined && pixelRatio>1) {
  457. view.width = view.width * pixelRatio
  458. view.height = view.height * pixelRatio
  459. }
  460. // Medium available and
  461. // Medium still bigger than screen
  462. if (hasMedium===true && (1920>view.width && 1080>view.height)) size = 'medium'
  463. // Photo not scaled
  464. // Photo smaller then screen
  465. if (scaled===false && (photo.json.width<view.width&& photo.json.width<view.height)) size = 'small'
  466. return size
  467. }
  468. photo.getArchive = function(photoID) {
  469. let link,
  470. url = `${ api.path }?function=Photo::getArchive&photoID=${ photoID }`
  471. if (location.href.indexOf('index.html')>0) link = location.href.replace(location.hash, '').replace('index.html', url)
  472. else link = location.href.replace(location.hash, '') + url
  473. if (lychee.publicMode===true) link += '&password=' + password.value
  474. location.href = link
  475. }
  476. photo.getDirectLink = function() {
  477. let url = ''
  478. if (photo.json && photo.json.url && photo.json.url!=='') url = photo.json.url
  479. return url
  480. }
  481. photo.getViewLink = function(photoID) {
  482. let url = 'view.php?p=' + photoID
  483. if (location.href.indexOf('index.html')>0) return location.href.replace('index.html' + location.hash, url)
  484. else return location.href.replace(location.hash, url)
  485. }