const moment = require("moment") const {postApi} = require("../../../../../rpdata/api/Post") const {logsApi} = require("../../../../../rpdata/api/Log") module.exports = class { onCreate(input) { this.state = { log: input.log, modal: null, removed: false, } } onMount() { this.sub = logsApi.subscribeChanges(this.state.log.id) this.sub.on("data", data => { this.handleChange(data.changes) }) this.sub.connect() } onUnmount() { if (this.sub != null) { this.sub.disconnect() } } open(modal) { this.state.modal = modal } close() { this.state.modal = null } /** * Patch the posts * * @param {{id:string, [x:string]: any}[]} patches */ patch(patches) { const posts = this.state.log.posts.slice() for (const patch of patches) { const index = posts.findIndex(p => p.id === patch.id) if (index == -1) { continue } posts[index] = Object.assign(Object.assign({}, posts[index]), patch) } // Sort posts and look for continuity issues (duplicate or skipped positions) posts.sort((a, b) => a.position - b.position) for (let i = 1; i < posts.length; ++i) { if (posts[i].position !== posts[i - 1].position + 1) { console.warn("Discontinuity detected:", i, posts) } } this.state.log.posts = posts this.state.log = Object.assign({}, this.state.log) } movePost(post, toPosition, relative) { if (relative) { toPosition = post.position + toPosition } postApi.move({id: post.id, toPosition}).then(patches => { this.patch(patches) }).catch(err => { console.warn(err) }) } removePost(post) { postApi.remove({id: post.id}).then(() => { this.postRemoved(post) }).catch(errs => { console.warn("Failed to delete:", errs) this.state.error = "Failed to delete: " + (errs.message || errs[0].message) }) } postEdited(patch) { this.patch([patch]) } logEdited(patch) { this.state.log = Object.assign(Object.assign({}, this.state.log), patch) } postRemoved(post) { if (this.state.log.posts.find(p => p.id === post.id) == null) { return } const posts = this.state.log.posts.filter(p => p.id !== post.id).map(p => { if (p.position > post.position) { return Object.assign({}, p, {position: p.position - 1}) } return p }) this.state.log = Object.assign({}, this.state.log, {posts: posts}) } postAdded(post) { // Stop duplicates in case of race condition. if (this.state.log.posts.find(p => p.id === post.id)) { return } this.state.log.posts = this.state.log.posts.concat([post]) this.state.log = Object.assign({}, this.state.log) } handleChange(change) { switch (`${change.model}.${change.op}`) { case "Post.add": { for (const post of change.objects.filter(o => o.type === "Post")) { this.postAdded(post) } break } case "Post.move": case "Post.edit": { this.patch(change.objects.filter(o => o.type === "Post")) break } case "Post.remove": { for (const post of change.objects.filter(o => o.type === "Post")) { this.postRemoved(post) } } case "Log.edit": { const patch = change.objects.find(o => o.type === "Log") if (patch == null) { break } this.logEdited(patch) break } case "Log.remove": { this.logRemoved() break } } } logRemoved() { this.state.removed = true } get title() { if (this.state.log.title) { return this.state.log.title } return `${this.state.log.channel.name} – ${moment(this.state.log.date).format("MMMM D, YYYY")}` } }