The frontend/UI server, written in JS using the MarkoJS library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
3.8 KiB

  1. const moment = require("moment")
  2. const {postApi} = require("../../../../../rpdata/api/Post")
  3. const {logsApi} = require("../../../../../rpdata/api/Log")
  4. module.exports = class {
  5. onCreate(input) {
  6. this.state = {
  7. log: input.log,
  8. modal: null,
  9. removed: false,
  10. }
  11. }
  12. onMount() {
  13. this.sub = logsApi.subscribeChanges(this.state.log.id)
  14. this.sub.on("data", data => {
  15. this.handleChange(data.changes)
  16. })
  17. this.sub.connect()
  18. }
  19. onUnmount() {
  20. if (this.sub != null) {
  21. this.sub.disconnect()
  22. }
  23. }
  24. open(modal) {
  25. this.state.modal = modal
  26. }
  27. close() {
  28. this.state.modal = null
  29. }
  30. /**
  31. * Patch the posts
  32. *
  33. * @param {{id:string, [x:string]: any}[]} patches
  34. */
  35. patch(patches) {
  36. const posts = this.state.log.posts.slice()
  37. for (const patch of patches) {
  38. const index = posts.findIndex(p => p.id === patch.id)
  39. if (index == -1) {
  40. continue
  41. }
  42. posts[index] = Object.assign(Object.assign({}, posts[index]), patch)
  43. }
  44. // Sort posts and look for continuity issues (duplicate or skipped positions)
  45. posts.sort((a, b) => a.position - b.position)
  46. for (let i = 1; i < posts.length; ++i) {
  47. if (posts[i].position !== posts[i - 1].position + 1) {
  48. console.warn("Discontinuity detected:", i, posts)
  49. }
  50. }
  51. this.state.log.posts = posts
  52. this.state.log = Object.assign({}, this.state.log)
  53. }
  54. movePost(post, toPosition, relative) {
  55. if (relative) {
  56. toPosition = post.position + toPosition
  57. }
  58. postApi.move({id: post.id, toPosition}).then(patches => {
  59. this.patch(patches)
  60. }).catch(err => {
  61. console.warn(err)
  62. })
  63. }
  64. removePost(post) {
  65. postApi.remove({id: post.id}).then(() => {
  66. this.postRemoved(post)
  67. }).catch(errs => {
  68. console.warn("Failed to delete:", errs)
  69. this.state.error = "Failed to delete: " + (errs.message || errs[0].message)
  70. })
  71. }
  72. postEdited(patch) {
  73. this.patch([patch])
  74. }
  75. logEdited(patch) {
  76. this.state.log = Object.assign(Object.assign({}, this.state.log), patch)
  77. }
  78. postRemoved(post) {
  79. if (this.state.log.posts.find(p => p.id === post.id) == null) {
  80. return
  81. }
  82. const posts = this.state.log.posts.filter(p => p.id !== post.id).map(p => {
  83. if (p.position > post.position) {
  84. return Object.assign({}, p, {position: p.position - 1})
  85. }
  86. return p
  87. })
  88. this.state.log = Object.assign({}, this.state.log, {posts: posts})
  89. }
  90. postAdded(post) {
  91. // Stop duplicates in case of race condition.
  92. if (this.state.log.posts.find(p => p.id === post.id)) {
  93. return
  94. }
  95. this.state.log.posts = this.state.log.posts.concat([post])
  96. this.state.log = Object.assign({}, this.state.log)
  97. }
  98. handleChange(change) {
  99. switch (`${change.model}.${change.op}`) {
  100. case "Post.add": {
  101. for (const post of change.objects.filter(o => o.type === "Post")) {
  102. this.postAdded(post)
  103. }
  104. break
  105. }
  106. case "Post.move":
  107. case "Post.edit": {
  108. this.patch(change.objects.filter(o => o.type === "Post"))
  109. break
  110. }
  111. case "Post.remove": {
  112. for (const post of change.objects.filter(o => o.type === "Post")) {
  113. this.postRemoved(post)
  114. }
  115. }
  116. case "Log.edit": {
  117. const patch = change.objects.find(o => o.type === "Log")
  118. if (patch == null) {
  119. break
  120. }
  121. this.logEdited(patch)
  122. break
  123. }
  124. case "Log.remove": {
  125. this.logRemoved()
  126. break
  127. }
  128. }
  129. }
  130. logRemoved() {
  131. this.state.removed = true
  132. }
  133. get title() {
  134. if (this.state.log.title) {
  135. return this.state.log.title
  136. }
  137. return `${this.state.log.channel.name}${moment(this.state.log.date).format("MMMM D, YYYY")}`
  138. }
  139. }