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