TrackChangesManager.coffee 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. define [
  2. "track-changes/models/ChangeList"
  3. "track-changes/models/Diff"
  4. "track-changes/ChangeListView"
  5. "track-changes/DiffView"
  6. "account/AccountManager"
  7. "utils/Modal"
  8. "models/Doc"
  9. "moment"
  10. ], (ChangeList, Diff, ChangeListView, DiffView, AccountManager, Modal, Doc, moment) ->
  11. class TrackChangesManager
  12. template: $("#trackChangesPanelTemplate").html()
  13. constructor: (@ide) ->
  14. @project_id = window.userSettings.project_id
  15. @$el = $(@template)
  16. @ide.mainAreaManager.addArea
  17. identifier: "trackChanges"
  18. element: @$el
  19. @ide.tabManager.addTab
  20. id: "history"
  21. name: "History"
  22. show: "code"
  23. after: "code"
  24. contract: true
  25. onShown: () => @show()
  26. onHidden: () => @hide()
  27. @ide.editor.on "resize", () =>
  28. @diffView?.resize()
  29. @$el.find(".track-changes-close").on "click", (e) =>
  30. e.preventDefault
  31. @hide()
  32. @bindToFileTreeEvents()
  33. @disable()
  34. bindToFileTreeEvents: () ->
  35. @ide.fileTreeManager.on "open:doc", (doc_id) =>
  36. @doc_id = doc_id
  37. if @enabled
  38. @updateDiff()
  39. AB_BUCKETS: ["control", "one-week", "pop-up"]
  40. show: () ->
  41. @changes = new ChangeList([], project_id: @project_id, ide: @ide)
  42. if @changeListView?
  43. @changeListView.remove()
  44. @changeListView = new ChangeListView(
  45. el: @$el.find(".change-list-area")
  46. collection: @changes
  47. )
  48. @changeListView.render()
  49. @changeListView.loadUntilFull (error) =>
  50. @autoSelectDiff()
  51. @changeListView.on "change_diff", (fromIndex, toIndex) =>
  52. @findDocsInChange(fromIndex, toIndex)
  53. @updateLabels()
  54. @updateDiff()
  55. @showUpgradeView()
  56. if @diffView?
  57. @diffView.remove()
  58. @ide.mainAreaManager.change "trackChanges"
  59. @ide.editor.disable()
  60. @ide.fileViewManager.disable()
  61. @ide.fileTreeManager.makeReadOnly()
  62. @ide.fileTreeManager.showDeletedDocs()
  63. @enable()
  64. showUpgradeView: () ->
  65. @$el.find("button.start-free-trial").off "click.track-changes"
  66. @$el.find("button.start-free-trial").on "click.track-changes", () => @gotoFreeTrial()
  67. if !@ide.project.get("features").versioning
  68. ga('send', 'event', 'subscription-funnel', 'askToUgrade', "trackchanges")
  69. @$el.find(".track-changes-upgrade-popup").show()
  70. if @ide.project.get("owner") == @ide.user
  71. @$el.find(".show-when-not-owner").hide()
  72. else
  73. @$el.find(".show-when-owner").hide()
  74. hide: () ->
  75. @ide.editor.enable()
  76. @ide.fileViewManager.enable()
  77. @disable()
  78. doc = @ide.fileTreeManager.getEntity(@doc_id, include_deleted: true)
  79. if doc? and doc.get("deleted")
  80. @ide.fileTreeManager.openDoc(@ide.project.get("rootDoc_id"))
  81. else
  82. @ide.fileTreeManager.openDoc(@doc_id)
  83. @ide.tabManager.show "code"
  84. @resetLabels()
  85. @ide.fileTreeManager.makeReadWriteIfAllowed()
  86. @ide.fileTreeManager.hideDeletedDocs()
  87. autoSelectDiff: () ->
  88. if @changes.models.length == 0
  89. return
  90. # Find all change until the last one we made
  91. fromIndex = null
  92. for change, i in @changes.models
  93. if ide.user in change.get("users")
  94. if i > 0
  95. fromIndex = i - 1
  96. else
  97. fromIndex = 0
  98. break
  99. fromIndex = 0 if !fromIndex
  100. toChange = @changes.models[0]
  101. fromChange = @changes.models[fromIndex]
  102. @changeListView.setSelectionRange(fromIndex, 0)
  103. @updateDiff()
  104. findDocsInChange: (fromIndex, toIndex) ->
  105. @changed_doc_ids = []
  106. for change in @changes.models.slice(toIndex, fromIndex + 1)
  107. for doc in change.get("docs") or []
  108. @changed_doc_ids.push doc.id if doc.id not in @changed_doc_ids
  109. if !@doc_id? or @doc_id not in @changed_doc_ids
  110. @doc_id = @changed_doc_ids[0]
  111. @updateDiff()
  112. updateLabels: () ->
  113. labels = {}
  114. for doc_id in @changed_doc_ids
  115. labels[doc_id] = true
  116. @ide.fileTreeManager.setLabels(labels)
  117. resetLabels: () ->
  118. @ide.fileTreeManager.setLabels({})
  119. updateDiff: () ->
  120. fromIndex = @changeListView.selectedFromIndex
  121. toIndex = @changeListView.selectedToIndex
  122. if !toIndex? or !fromIndex?
  123. console.log "No selection - what should we do!?"
  124. return
  125. {from, to, start_ts, end_ts} = @_findDocVersionsRangeInSelection(@doc_id, fromIndex, toIndex)
  126. @diff = new Diff({
  127. project_id: @project_id
  128. doc_id: @doc_id
  129. from: from
  130. to: to
  131. start_ts: start_ts
  132. end_ts: end_ts
  133. }, {
  134. ide: @ide
  135. })
  136. if @diffView?
  137. @diffView.remove()
  138. if !@diff.get("doc")?
  139. console.log "This document does not exist. What should we do?"
  140. return
  141. @diffView = new DiffView(
  142. model: @diff
  143. el: @$el.find(".track-changes-diff")
  144. )
  145. @diffView.on "restore", () =>
  146. @restoreDiff(@diff)
  147. @diffView.on "restore-deleted", () =>
  148. @restoreDeletedDoc @diff.get("doc"), (error, doc_id) =>
  149. return if error? or !doc_id?
  150. setTimeout () =>
  151. # Give doc a chance to appear in file tree via socket.io
  152. @hide()
  153. @ide.fileTreeManager.openDoc(doc_id)
  154. , 1000
  155. @ide.fileTreeManager.selectEntity(@doc_id)
  156. _findDocVersionsRangeInSelection: (doc_id, fromIndex, toIndex) ->
  157. from = to = start_ts = end_ts = null
  158. for change in @changes.models.slice(toIndex, fromIndex + 1)
  159. for doc in change.get("docs")
  160. if doc.id == doc_id
  161. if from? and to?
  162. from = Math.min(from, doc.fromV)
  163. to = Math.max(to, doc.toV)
  164. start_ts = Math.min(start_ts, change.get("start_ts"))
  165. end_ts = Math.max(end_ts, change.get("end_ts"))
  166. else
  167. from = doc.fromV
  168. to = doc.toV
  169. start_ts = change.get("start_ts")
  170. end_ts = change.get("end_ts")
  171. break
  172. return {from, to, start_ts, end_ts}
  173. restoreDiff: (diff) ->
  174. name = diff.get("doc")?.get("name")
  175. date = moment(diff.get("start_ts")).format("Do MMM YYYY, h:mm:ss a")
  176. modal = new Modal({
  177. title: "Restore document"
  178. message: "Are you sure you want to restore <strong>#{name}</strong> to before the changes on #{date}?"
  179. buttons: [{
  180. text: "Cancel"
  181. }, {
  182. text: "Restore"
  183. class: "btn-success"
  184. close: false
  185. callback: ($button) =>
  186. $button.text("Restoring...")
  187. $button.prop("disabled", true)
  188. diff.restore (error) =>
  189. modal.remove()
  190. @hide()
  191. }]
  192. })
  193. restoreDeletedDoc: (doc, callback) ->
  194. $.ajax {
  195. url: "/project/#{@project_id}/doc/#{doc.get("id")}/restore"
  196. type: "POST"
  197. dataType: "json"
  198. data:
  199. name: doc.get("name")
  200. headers:
  201. "X-CSRF-Token": window.csrfToken
  202. success: (body, status, response) ->
  203. callback(null, body?.doc_id)
  204. error: (error) ->
  205. callback(error)
  206. }
  207. enable: () ->
  208. @enabled = true
  209. disable: () ->
  210. @enabled = false
  211. gotoFreeTrial: () ->
  212. AccountManager.gotoSubscriptionsPage()
  213. ga('send', 'event', 'subscription-funnel', 'upgraded-free-trial', "trackchanges")
  214. return TrackChangesManager