Browse Source

client, logs-content: Added change subscription setup and hooked it up to the logs-content page.

1.2
Gisle Aune 6 years ago
parent
commit
013c50d4f4
  1. 14
      marko/page/logs-content/components/edit-log-modal/component.js
  2. 90
      marko/page/logs-content/components/page/component.js
  3. 2
      marko/page/logs/components/page/component.js
  4. 1130
      package-lock.json
  5. 4
      package.json
  6. 2
      routes/graphql.js
  7. 45
      rpdata/api/Log.js
  8. 87
      rpdata/client.js
  9. 1
      server.js

14
marko/page/logs-content/components/edit-log-modal/component.js

@ -19,15 +19,11 @@ module.exports = class {
} }
onInput(input) { onInput(input) {
if (input.log && !this.first) {
this.state.values = {
title: input.log.title,
event: input.log.eventName,
description: input.log.description,
open: input.log.open,
}
this.first = true
this.state.values = {
title: input.log.title,
event: input.log.eventName,
description: input.log.description,
open: input.log.open,
} }
} }

90
marko/page/logs-content/components/page/component.js

@ -1,6 +1,7 @@
const moment = require("moment") const moment = require("moment")
const {postApi} = require("../../../../../rpdata/api/Post") const {postApi} = require("../../../../../rpdata/api/Post")
const {logsApi} = require("../../../../../rpdata/api/Log")
module.exports = class { module.exports = class {
onCreate(input) { onCreate(input) {
@ -11,6 +12,22 @@ module.exports = class {
} }
} }
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) { open(modal) {
this.state.modal = modal this.state.modal = modal
} }
@ -30,8 +47,6 @@ module.exports = class {
for (const patch of patches) { for (const patch of patches) {
const index = posts.findIndex(p => p.id === patch.id) const index = posts.findIndex(p => p.id === patch.id)
if (index == -1) { if (index == -1) {
console.warn("Post not found for patch:", patch)
// TODO: Force full refresh
continue continue
} }
@ -42,8 +57,7 @@ module.exports = class {
posts.sort((a, b) => a.position - b.position) posts.sort((a, b) => a.position - b.position)
for (let i = 1; i < posts.length; ++i) { for (let i = 1; i < posts.length; ++i) {
if (posts[i].position !== posts[i - 1].position + 1) { if (posts[i].position !== posts[i - 1].position + 1) {
console.warn("Discontinuity detected!")
// TODO: Force full refresh
console.warn("Discontinuity detected:", i, posts)
} }
} }
@ -65,15 +79,7 @@ module.exports = class {
removePost(post) { removePost(post) {
postApi.remove({id: post.id}).then(() => { postApi.remove({id: post.id}).then(() => {
this.state.log.posts = this.state.log.posts.filter(p => p.id !== post.id)
for (const p of this.state.log.posts) {
if (p.position > post.position) {
p.position--
}
}
this.state.log = Object.assign({}, this.state.log)
this.postRemoved(post)
}).catch(errs => { }).catch(errs => {
console.warn("Failed to delete:", errs) console.warn("Failed to delete:", errs)
this.state.error = "Failed to delete: " + (errs.message || errs[0].message) this.state.error = "Failed to delete: " + (errs.message || errs[0].message)
@ -88,11 +94,69 @@ module.exports = class {
this.state.log = Object.assign(Object.assign({}, this.state.log), 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) { postAdded(post) {
this.state.log.posts = this.state.log.posts.concat([post]) this.state.log.posts = this.state.log.posts.concat([post])
this.state.log = Object.assign({}, this.state.log) 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() { logRemoved() {
this.state.removed = true this.state.removed = true
} }

2
marko/page/logs/components/page/component.js

@ -1,4 +1,4 @@
const {logHeaderApi} = require("../../../../../rpdata/api/LogHeader")
const { logHeaderApi } = require("../../../../../rpdata/api/LogHeader")
module.exports = class { module.exports = class {
onCreate(input) { onCreate(input) {

1130
package-lock.json
File diff suppressed because it is too large
View File

4
package.json

@ -25,6 +25,7 @@
"express-http-proxy": "^1.2.0", "express-http-proxy": "^1.2.0",
"express-session": "^1.15.6", "express-session": "^1.15.6",
"graphql-query-compress": "^1.1.0", "graphql-query-compress": "^1.1.0",
"http-proxy-middleware": "^0.19.1",
"isomorphic-fetch": "^2.2.1", "isomorphic-fetch": "^2.2.1",
"jsonwebtoken": "^8.3.0", "jsonwebtoken": "^8.3.0",
"lasso": "^3.2.6", "lasso": "^3.2.6",
@ -41,5 +42,8 @@
"passport": "^0.4.0", "passport": "^0.4.0",
"passport-auth0": "^1.0.0", "passport-auth0": "^1.0.0",
"pluralize": "^7.0.0" "pluralize": "^7.0.0"
},
"devDependencies": {
"@types/http-proxy-middleware": "^0.19.0"
} }
} }

2
routes/graphql.js

@ -1,3 +1,4 @@
const proxy = require("http-proxy-middleware")
const express = require("express") const express = require("express")
const jwt = require("jsonwebtoken") const jwt = require("jsonwebtoken")
@ -44,6 +45,7 @@ router.post("/", (req, res) => {
}) })
}) })
router.use("/", proxy(config.graphqlEndpoint, {ws: true}))
/** /**
* @param {string} user * @param {string} user

45
rpdata/api/Log.js

@ -1,4 +1,4 @@
const {query} = require("../client")
const {query, Subscription} = require("../client")
const {Character} = require("./Character") const {Character} = require("./Character")
const {Channel} = require("./Channel") const {Channel} = require("./Channel")
@ -193,7 +193,6 @@ class LogAPI {
}) })
} }
/** /**
* Call `removeLog(input)` mutation, returns the id * Call `removeLog(input)` mutation, returns the id
* *
@ -211,6 +210,48 @@ class LogAPI {
return editLog return editLog
}) })
} }
/**
* Subscribe to the changes.
*
* @param {string} logId The long logId, the short one won't do.
*/
subscribeChanges(logId) {
return new Subscription(`
subscription Changes($keys: [ChangeKeyInput!]!) {
changes(keys: $keys) {
id
model
op
author
listed
keys {
model
id
}
date
objects {
type: __typename
...on Log {
id
title
eventName
description
open
}
...on Post {
id
time
kind
nick
text
position
}
}
}
}
`, {keys: [{model: "Log", id: logId}]})
}
} }
module.exports = { Log, logsApi: new LogAPI } module.exports = { Log, logsApi: new LogAPI }

87
rpdata/client.js

@ -1,3 +1,4 @@
const EventEmitter = require("events")
const fetch = require("isomorphic-fetch") const fetch = require("isomorphic-fetch")
const config = require("../config") const config = require("../config")
const compressQuery = require("graphql-query-compress") const compressQuery = require("graphql-query-compress")
@ -30,4 +31,88 @@ function query(query, variables = {}, options = {}) {
}) })
} }
module.exports = { query }
class Subscription extends EventEmitter {
constructor(query, variables) {
super()
this.query = query
this.variables = Object.assign({}, variables)
this.websocket = null
this.verboseLogging = false
}
connect() {
const baseUrl = (window.location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.host
let id = 1
this.websocket = new WebSocket(baseUrl + config.graphqlEndpoint, "graphql-ws")
this.websocket.onopen = () => {
this.websocket.send(JSON.stringify({type: "connection_init", payload: {}}));
this.websocket.send(JSON.stringify({id: (id++).toFixed(0), type: "start", payload: {
query: this.query,
variables: this.variables,
extensions: {},
}}));
console.log("WS Open")
}
this.websocket.onmessage = (ev) => {
let data = {}
try {
data = JSON.parse(ev.data)
} catch (err) {
console.warn("Error", err, ev)
return
}
if (this.verboseLogging) {
console.log("WS Data:", data)
}
switch (data.type) {
case "connection_ack": {
this.emit("connect")
break;
}
case "connection_error": {
console.warn("WS Connection Error", data.payload.message)
this.websocket.close()
this.emit("error", new Error(data.payload.message))
break;
}
case "data": {
if (data.payload.errors != null && data.payload.errors.length > 0) {
this.emit("error", data.payload.errors)
} else {
this.emit("data", data.payload.data)
}
break;
}
}
}
this.websocket.onerror = (err) => {
console.warn("WS Error", err)
}
this.websocket.onclose = () => {
this.emit("disconnect")
}
}
disconnect() {
if (this.websocket != null) {
this.websocket.close();
}
}
}
module.exports = { query, Subscription }

1
server.js

@ -8,6 +8,7 @@ const config = require("./config")
// Express depedencies // Express depedencies
const express = require("express") const express = require("express")
const proxy = require("express-http-proxy") const proxy = require("express-http-proxy")
const proxy2 = require("http-proxy-middleware")
const lasso = require("lasso") const lasso = require("lasso")
const lassoMiddleware = require("lasso/middleware") const lassoMiddleware = require("lasso/middleware")
const markoExpress = require("marko/express") const markoExpress = require("marko/express")

Loading…
Cancel
Save