task_manager.coffee 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. class App.TaskManager
  2. _instance = undefined
  3. @init: (params = {}) ->
  4. if params.force
  5. _instance = new _taskManagerSingleton(params)
  6. return
  7. _instance ?= new _taskManagerSingleton(params)
  8. @all: ->
  9. _instance.all()
  10. @allWithMeta: ->
  11. return [] if !_instance
  12. _instance.allWithMeta()
  13. @execute: (params) ->
  14. return if !_instance
  15. _instance.execute(params)
  16. @get: (key) ->
  17. return if !_instance
  18. _instance.get(key)
  19. @update: (key, params) ->
  20. return if !_instance
  21. _instance.update(key, params)
  22. @remove: (key) ->
  23. return if !_instance
  24. _instance.remove(key)
  25. @notify: (key) ->
  26. return if !_instance
  27. _instance.notify(key)
  28. @mute: (key) ->
  29. return if !_instance
  30. _instance.mute(key)
  31. @reorder: (order) ->
  32. return if !_instance
  33. _instance.reorder(order)
  34. @touch: (key) ->
  35. return if !_instance
  36. _instance.touch(key)
  37. @reset: ->
  38. return if !_instance
  39. _instance.reset()
  40. @tasksInitial: ->
  41. if _instance == undefined
  42. _instance ?= new _taskManagerSingleton
  43. _instance.tasksInitial()
  44. @worker: (key) ->
  45. return if !_instance
  46. _instance.worker(key)
  47. @nextTaskUrl: ->
  48. return if !_instance
  49. _instance.nextTaskUrl()
  50. @TaskbarId: ->
  51. return if !_instance
  52. _instance.TaskbarId()
  53. @hideAll: ->
  54. return if !_instance
  55. _instance.showControllerHideOthers()
  56. class _taskManagerSingleton extends App.Controller
  57. @include App.LogInclude
  58. constructor: (params = {}) ->
  59. super
  60. if params.el
  61. @el = params.el
  62. else
  63. @el = $('#app')
  64. @offlineModus = params.offlineModus
  65. @tasksInitial()
  66. init: ->
  67. @domStore = {}
  68. @shownStore = {}
  69. @workers = {}
  70. @allTasksByKey = {}
  71. @tasksToUpdate = {}
  72. @activeTaskHistory = []
  73. @queue = []
  74. @queueRunning = false
  75. all: ->
  76. # sort by prio
  77. byPrios = []
  78. for key, task of @allTasksByKey
  79. byPrios.push task
  80. _.sortBy(byPrios, (task) ->
  81. return task.prio
  82. )
  83. allWithMeta: ->
  84. all = @all()
  85. for task in all
  86. task = @getMeta(task)
  87. all
  88. getMeta: (task) ->
  89. # collect meta data of task for task bar item
  90. meta =
  91. url: '#'
  92. id: false
  93. iconClass: 'loading'
  94. title: App.i18n.translateInline('Loading...')
  95. head: App.i18n.translateInline('Loading...')
  96. worker = App.TaskManager.worker(task.key)
  97. if worker
  98. data = worker.meta()
  99. # apply meta data of controller
  100. if data
  101. for key, value of data
  102. meta[key] = value
  103. task.meta = meta
  104. task
  105. newPrio: ->
  106. prio = 1
  107. for task in @all()
  108. if task.prio && task.prio > prio
  109. prio = task.prio
  110. prio++
  111. prio
  112. # generate dom id for task
  113. domID: (key) ->
  114. "content_permanent_#{key}"
  115. worker: (key) ->
  116. return @workers[ key ] if @workers[ key ]
  117. return
  118. execute: (params) ->
  119. @queue.push params
  120. @run()
  121. run: ->
  122. return if !@queue[0]
  123. return if @queueRunning
  124. @queueRunning = true
  125. loop
  126. param = @queue.shift()
  127. @executeSingel(param)
  128. if !@queue[0]
  129. @queueRunning = false
  130. break
  131. executeSingel: (params) ->
  132. # input validation
  133. params.key = App.Utils.htmlAttributeCleanup(params.key)
  134. # in case an init execute arrives later but is aleady executed, ignore it
  135. if params.init && @workers[params.key]
  136. return
  137. # if we have init task startups, let the controller know this
  138. if params.init
  139. params.params.init = true
  140. # modify shown param for controller
  141. if params.params
  142. if !params.show
  143. delete params.params.shown
  144. else
  145. params.params.shown = true
  146. # remember latest active controller
  147. if params.show
  148. @activeTaskHistory.push _.clone(params)
  149. # check if task already exists in storage / e. g. from last session
  150. task = @get(params.key)
  151. # create new online task if not exists and if not persistent
  152. if !task && !@workers[params.key] && !params.persistent
  153. task = new App.Taskbar
  154. task.load(
  155. key: params.key
  156. params: params.params
  157. callback: params.controller
  158. client_id: 123
  159. prio: @newPrio()
  160. notify: false
  161. active: params.show
  162. )
  163. @allTasksByKey[params.key] = task.attributes()
  164. @touch(params.key)
  165. # save new task and update task collection
  166. ui = @
  167. task.save(
  168. done: ->
  169. ui.allTasksByKey[params.key] = @attributes()
  170. for taskPosition of ui.allTasks
  171. if ui.allTasks[taskPosition] && ui.allTasks[taskPosition]['key'] is @key
  172. task = @attributes()
  173. ui.allTasks[taskPosition] = task
  174. )
  175. # empty static content if task is shown
  176. if params.show
  177. @$('#content').empty()
  178. # set all tasks to active false, only new/selected one to active
  179. if params.show
  180. for key, task of @allTasksByKey
  181. if key isnt params.key
  182. if task.active
  183. task.active = false
  184. @taskUpdate(task)
  185. else
  186. changed = false
  187. if !task.active
  188. changed = true
  189. task.active = true
  190. if task.notify
  191. changed = true
  192. task.notify = false
  193. if changed
  194. @taskUpdate(task)
  195. # start worker for task if not exists
  196. @startController(params)
  197. startController: (params) =>
  198. @log 'debug', 'controller start try...', params
  199. # create clean params
  200. params_app = _.clone(params.params)
  201. domKey = @domID(params.key)
  202. domStoreItem = @domStore[domKey]
  203. if domStoreItem
  204. el = domStoreItem.el
  205. else
  206. el = $("<div id=\"#{domKey}\" class=\"content horizontal flex\"></div>")
  207. @domStore[domKey] = { el: el }
  208. params_app['el'] = el
  209. params_app['task_key'] = params.key
  210. if !params.show
  211. params_app['doNotLog'] = 1
  212. # start controller if not already started
  213. if !@workers[params.key]
  214. @workers[params.key] = new App[params.controller](params_app)
  215. # if controller is started hidden, call hide of controller
  216. if !params.show
  217. @hide(params.key)
  218. # hide all other controller / show current controller
  219. else
  220. @showControllerHideOthers(params.key, params_app)
  221. @tasksAutoCleanupDelay()
  222. showControllerHideOthers: (thisKey, params_app) =>
  223. for key of @workers
  224. if key isnt thisKey
  225. if @shownStore[key] isnt false
  226. @hide(key)
  227. @$('#content').addClass('hide')
  228. for key of @workers
  229. if key is thisKey
  230. @show(key, params_app)
  231. # show task content
  232. show: (key, params_app) =>
  233. controller = @workers[ key ]
  234. @shownStore[key] = true
  235. domKey = @domID(key)
  236. domStoreItem = @domStore[domKey]
  237. if !@$("##{domKey}").get(0) && domStoreItem && domStoreItem.el
  238. @el.append(domStoreItem.el)
  239. @$("##{domKey}").removeClass('hide').addClass('active')
  240. if controller
  241. # set position of view
  242. if domStoreItem.position
  243. controller.setPosition(domStoreItem.position)
  244. else
  245. @$("##{domKey}").removeClass('hide').addClass('active')
  246. if controller
  247. # set controller state to active
  248. if controller.active && _.isFunction(controller.active)
  249. controller.active(true)
  250. # execute controllers show
  251. if controller.show && _.isFunction(controller.show)
  252. controller.show(params_app)
  253. true
  254. # hide task content
  255. hide: (key) =>
  256. controller = @workers[ key ]
  257. @shownStore[key] = false
  258. element = @$("##{@domID(key)}")
  259. if element.get(0)
  260. domKey = @domID(key)
  261. domStoreItem = @domStore[domKey]
  262. if controller && _.isFunction(controller.currentPosition)
  263. position = controller.currentPosition()
  264. domStoreItem.position = position
  265. element.addClass('hide').removeClass('active')
  266. domStoreItem.el = element.detach()
  267. else
  268. element.addClass('hide').removeClass('active')
  269. return false if !controller
  270. # set controller state to active
  271. if controller.active && _.isFunction(controller.active)
  272. controller.active(false)
  273. # execute controllers hide
  274. if controller.hide && _.isFunction(controller.hide)
  275. controller.hide()
  276. @anyPopoversDestroy()
  277. true
  278. # get task
  279. get: (key) =>
  280. @allTasksByKey[key]
  281. # get task
  282. getWithMeta: (key) =>
  283. task = @get(key)
  284. return if !task
  285. @getMeta(task)
  286. # update task
  287. update: (key, params) =>
  288. task = @get(key)
  289. if !task
  290. throw "No such task with '#{key}' to update"
  291. for item, value of params
  292. task[item] = value
  293. # mute rerender on state attribute updates
  294. mute = false
  295. if Object.keys(params).length is 1 && params.state
  296. mute = true
  297. @taskUpdate(task, mute)
  298. # remove task certain task from tasks
  299. remove: (key) =>
  300. task = @allTasksByKey[key]
  301. delete @allTasksByKey[key]
  302. return if !task
  303. # rerender taskbar
  304. App.Event.trigger('taskRemove', [task])
  305. # destroy in backend storage
  306. @taskDestroy(task)
  307. # release task from dom and destroy controller
  308. @release(key)
  309. # set notify of task
  310. notify: (key) =>
  311. task = @get(key)
  312. if !task
  313. throw "No such task with '#{key}' to notify"
  314. return if task.notify
  315. task.notify = true
  316. @taskUpdate(task)
  317. # unset notify of task
  318. mute: (key) =>
  319. task = @get(key)
  320. if !task
  321. throw "No such task with '#{key}' to mute"
  322. return if !task.notify
  323. task.notify = false
  324. @taskUpdate(task)
  325. # set new order of tasks (needed for dnd)
  326. reorder: (order) =>
  327. prio = 0
  328. for key in order
  329. task = @get(key)
  330. if !task
  331. throw "No such task with '#{key}' of order"
  332. prio++
  333. if task.prio isnt prio
  334. task.prio = prio
  335. @taskUpdate(task, true)
  336. App.Event.trigger('taskCollectionOrderSet', order)
  337. # release one task
  338. release: (key) =>
  339. domKey = @domID(key)
  340. localDomStore = @domStore[domKey]
  341. if localDomStore
  342. if localDomStore.el
  343. $('#app').append("<div id=\"#{domKey}_trash\" class=\"hide\"></div>")
  344. $("#app ##{domKey}_trash").append(localDomStore.el).remove()
  345. localDomStore.el = undefined
  346. localDomStore = undefined
  347. delete @domStore[@domID(key)]
  348. worker = @workers[key]
  349. if worker
  350. worker = undefined
  351. delete @workers[key]
  352. try
  353. element = @$("##{@domID(key)}")
  354. element.html('')
  355. element.remove()
  356. catch
  357. @log 'notice', "invalid key '#{key}'"
  358. # reset while tasks
  359. reset: =>
  360. # release touch tasks
  361. for key, task of @allTasksByKey
  362. @release(key)
  363. # release persistent tasks
  364. for key, controller of @workers
  365. @release(key)
  366. # clear instance vars
  367. @init()
  368. # clear in mem tasks
  369. App.Taskbar.deleteAll()
  370. # rerender task bar
  371. App.Event.trigger('taskInit')
  372. nextTaskUrl: =>
  373. # activate latest controller based on history
  374. loop
  375. controllerParams = @activeTaskHistory.pop()
  376. break if !controllerParams
  377. break if !controllerParams.key
  378. controller = @workers[ controllerParams.key ]
  379. if controller && controller.url
  380. return controller.url()
  381. # activate latest controller with highest prio
  382. tasks = @all()
  383. taskNext = tasks[tasks.length-1]
  384. if taskNext
  385. controller = @workers[ taskNext.key ]
  386. if controller && controller.url
  387. return controller.url()
  388. false
  389. TaskbarId: =>
  390. if !@TaskbarIdInt
  391. @TaskbarIdInt = Math.floor( Math.random() * 99999999 )
  392. @TaskbarIdInt
  393. taskUpdate: (task, mute = false) ->
  394. @log 'debug', 'UPDATE task', task, mute
  395. @tasksToUpdate[ task.key ] = 'toUpdate'
  396. @taskUpdateTrigger()
  397. return if mute
  398. @touch(task.key)
  399. touch: (key) ->
  400. delay = =>
  401. task = @getWithMeta(key)
  402. return if !task
  403. # throw "No such task with '#{key}' to touch"
  404. # update title
  405. if task.active && task.meta
  406. @title task.meta.title
  407. App.Event.trigger('taskUpdate', [task])
  408. App.Delay.set(delay, 20, "task-#{key}", undefined)
  409. taskUpdateTrigger: =>
  410. # send updates to server
  411. App.Delay.set(@taskUpdateLoop, 2000, 'check_update_to_server_pending', 'task', true)
  412. taskUpdateLoop: =>
  413. return if @offlineModus
  414. for key of @tasksToUpdate
  415. continue if !key
  416. task = @get(key)
  417. continue if !task
  418. if @tasksToUpdate[ task.key ] is 'toUpdate'
  419. @tasksToUpdate[ task.key ] = 'inProgress'
  420. taskUpdate = new App.Taskbar
  421. taskUpdate.load(task)
  422. if taskUpdate.isOnline()
  423. ui = @
  424. taskUpdate.save(
  425. done: ->
  426. if ui.tasksToUpdate[ @key ] is 'inProgress'
  427. delete ui.tasksToUpdate[ @key ]
  428. fail: ->
  429. ui.log 'error', "can't update task", @
  430. if ui.tasksToUpdate[ @key ] is 'inProgress'
  431. delete ui.tasksToUpdate[ @key ]
  432. )
  433. taskDestroy: (task) ->
  434. # check if update is still in process
  435. if @tasksToUpdate[ task.key ] is 'inProgress'
  436. App.Delay.set(
  437. => @taskDestroy(task)
  438. 800
  439. undefined
  440. 'task'
  441. true
  442. )
  443. return
  444. # destroy task in backend
  445. delete @tasksToUpdate[ task.key ]
  446. # if task isnt already stored on backend
  447. return if !task.id
  448. App.Taskbar.destroy(task.id)
  449. tasksAutoCleanupDelay: =>
  450. delay = =>
  451. @tasksAutoCleanup()
  452. App.Delay.set(delay, 12000, 'task-autocleanup', undefined, true)
  453. tasksAutoCleanup: =>
  454. # auto cleanup of old tasks
  455. currentTaskCount = =>
  456. Object.keys(@allTasksByKey).length
  457. maxTaskCount = 30
  458. if currentTaskCount() > maxTaskCount
  459. for task in App.Taskbar.search(sortBy:'updated_at', order:'ASC')
  460. if currentTaskCount() > maxTaskCount
  461. if !task.active
  462. if _.isEmpty(task.state) || (_.isEmpty(task.state.ticket) && _.isEmpty(task.state.article))
  463. @log 'notice', "More then #{maxTaskCount} tasks open, close oldest untouched task #{task.key}"
  464. @remove(task.key)
  465. tasksInitial: =>
  466. @init()
  467. # set taskbar collection stored in database
  468. tasks = App.Taskbar.all()
  469. for task in tasks
  470. task.active = false
  471. @allTasksByKey[task.key] = task.attributes()
  472. # reopen tasks
  473. App.Event.trigger 'taskbar:init'
  474. # initial load of permanent tasks
  475. user_id = App.Session.get('id')
  476. user = undefined
  477. if user_id
  478. user = App.User.find(user_id)
  479. permanentTask = App.Config.get('permanentTask')
  480. taskCount = 0
  481. if permanentTask
  482. for key, config of permanentTask
  483. if !config.permission || (user && user.permission(config.permission))
  484. taskCount += 1
  485. do (key, config, taskCount) =>
  486. App.Delay.set(
  487. =>
  488. @execute(
  489. key: key
  490. controller: config.controller
  491. params: {}
  492. show: false
  493. persistent: true
  494. init: true
  495. )
  496. taskCount * 350
  497. undefined
  498. 'task'
  499. true
  500. )
  501. # initial load of taskbar collection
  502. for key, task of @allTasksByKey
  503. taskCount += 1
  504. do (task, taskCount) =>
  505. App.Delay.set(
  506. =>
  507. @execute(
  508. key: task.key
  509. controller: task.callback
  510. params: task.params
  511. show: false
  512. persistent: false
  513. init: true
  514. )
  515. taskCount * 350
  516. undefined
  517. 'task'
  518. true
  519. )
  520. App.Event.trigger 'taskbar:ready'