Merge pull request #23 from h3poteto/iss-6

refs #6 Call streaming and display home timeline
This commit is contained in:
AkiraFukushima 2018-03-12 20:05:17 +09:00 committed by GitHub
commit 333a2be131
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 308 additions and 67 deletions

5
package-lock.json generated
View File

@ -9803,6 +9803,11 @@
}
}
},
"moment": {
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz",
"integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ=="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",

View File

@ -62,6 +62,7 @@
"google-fonts-webpack-plugin": "^0.4.4",
"is-empty": "^1.2.0",
"mastodon-api": "^1.3.0",
"moment": "^2.21.0",
"nedb": "^1.8.0",
"vue": "^2.3.3",
"vue-awesome": "^2.3.5",

View File

@ -7,6 +7,7 @@ import empty from 'is-empty'
import Authentication from './auth'
import Account from './account'
import Streaming from './streaming'
/**
* Set `__static` path to static files in production
@ -126,6 +127,36 @@ ipcMain.on('get-local-account', (event, id) => {
})
})
// streaming
let userStreaming = null
ipcMain.on('start-user-streaming', (event, ac) => {
const account = new Account(db)
account.getAccount(ac._id)
.catch((err) => {
event.sender.send('error-start-user-streaming', err)
})
.then((account) => {
userStreaming = new Streaming(account)
userStreaming.startUserStreaming(
(update) => {
event.sender.send('update-start-user-streaming', update)
},
(notification) => {
event.sender.send('notification-start-user-streaming', notification)
},
(err) => {
event.sender.send('error-start-user-streaming', err)
}
)
})
})
ipcMain.on('stop-user-streaming', (event, _) => {
userStreaming.stop()
userStreaming = null
})
/**
* Auto Updater
*

40
src/main/streaming.js Normal file
View File

@ -0,0 +1,40 @@
import Mastodon from 'mastodon-api'
export default class Streaming {
constructor (account) {
this.account = account
this.client = new Mastodon(
{
access_token: account.accessToken,
api_url: account.baseURL + '/api/v1'
}
)
this.listener = null
}
startUserStreaming (updateCallback, notificationCallback, errCallback) {
this.listener = this.client.stream('/streaming/user')
this.listener.on('message', (msg) => {
switch (msg.event) {
case 'update':
updateCallback(msg.data)
break
case 'notification':
notificationCallback(msg.data)
break
default:
console.log(msg)
break
}
})
this.listener.on('error', (err) => {
errCallback(err)
})
}
stop () {
this.listener.stop()
}
}

View File

@ -65,6 +65,10 @@ html, body, #app, #global_header {
margin: 0;
}
p {
margin: 0;
}
#global_header {
.account-menu {
height: 100%;

View File

@ -14,7 +14,21 @@ export default {
name: 'timeline-space',
components: { SideMenu },
created () {
console.log(this.$route.params.id)
this.$store.dispatch('TimelineSpace/fetchAccount', this.$route.params.id)
.then((account) => {
this.$store.dispatch('TimelineSpace/fetchHomeTimeline', account)
this.$store.dispatch('TimelineSpace/startUserStreaming', account)
this.$store.dispatch('TimelineSpace/username', account)
})
.catch(() => {
this.$message({
message: 'Could not find account',
type: 'error'
})
})
},
beforeDestroy () {
this.$store.dispatch('TimelineSpace/stopUserStreaming')
}
}
</script>

View File

@ -1,11 +1,28 @@
<template>
<div id="home">
home
<div class="home-timeline" v-for="message in timeline" v-bind:key="message.id">
<toot :message="message"></toot>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import Toot from './Toot'
export default {
name: 'home'
name: 'home',
components: { Toot },
computed: {
...mapState({
timeline: state => state.TimelineSpace.homeTimeline
})
}
}
</script>
<style lang="scss" scoped>
.home-timeline {
margin-left: 16px;
}
</style>

View File

@ -44,22 +44,10 @@ export default {
name: 'side-menu',
computed: {
...mapState({
account: state => state.TimelineSpace.SideMenu.account,
username: state => state.TimelineSpace.SideMenu.username
account: state => state.TimelineSpace.account,
username: state => state.TimelineSpace.username
})
},
created () {
this.$store.dispatch('TimelineSpace/SideMenu/fetchAccount', this.$route.params.id)
.then((account) => {
this.$store.dispatch('TimelineSpace/SideMenu/username', account)
})
.catch(() => {
this.$message({
message: 'Could not find account',
type: 'error'
})
})
},
methods: {
id () {
return this.$route.params.id

View File

@ -0,0 +1,95 @@
<template>
<div class="toot">
<div class="icon">
<img :src="message.account.avatar" />
</div>
<div class="detail">
<div class="toot-header">
<div class="user">
{{ message.account.display_name }}
</div>
<div class="timestamp">
{{ parseDatetime(message.created_at) }}
</div>
</div>
<div class="content" v-html="message.content"></div>
<div class="tool-box">
<el-button type="text"><icon name="reply" scale="0.9"></icon></el-button>
<el-button type="text"><icon name="retweet" scale="0.9"></icon></el-button>
<el-button type="text"><icon name="star" scale="0.9"></icon></el-button>
</div>
</div>
<div class="clearfix"></div>
<div class="fill-line"></div>
</div>
</template>
<script>
import moment from 'moment'
export default {
name: 'toot',
props: ['message'],
methods: {
parseDatetime (datetime) {
return moment(datetime).format('YYYY-MM-DD HH:mm:ss')
}
}
}
</script>
<style lang="scss" scoped>
.fill-line {
height: 1px;
background-color: #f2f6fc;
margin: 4px 0;
}
.toot {
.icon {
float: left;
img {
width: 36px;
height: 36px;
border-radius: 4px;
}
}
.detail {
margin-left: 42px;
margin-right: 8px;
margin-top: 8px;
.toot-header {
.user {
float: left;
font-weight: 800;
color: #303133;
font-size: 14px;
}
.timestamp {
font-size: 14px;
text-align: right;
width: 100%;
color: #909399;
}
}
.content {
font-size: 14px;
color: #303133;
margin: 4px 0 8px;
}
.tool-box {
button {
margin: 0 8px;
padding: 0;
color: #909399;
}
}
}
}
</style>

View File

@ -19,7 +19,6 @@ const GlobalHeader = {
})
ipcRenderer.once('response-list-accounts', (event, accounts) => {
commit('updateAccounts', accounts)
console.log(accounts)
resolve(accounts)
})
})

View File

@ -1,9 +1,102 @@
import { ipcRenderer } from 'electron'
import Mastodon from 'mastodon-api'
import SideMenu from './TimelineSpace/SideMenu'
const TimelineSpace = {
namespaced: true,
modules: {
SideMenu
},
state: {
account: {
domain: '',
id: ''
},
username: '',
homeTimeline: [],
notification: []
},
mutations: {
updateAccount (state, account) {
state.account = account
},
updateUsername (state, username) {
state.username = username
},
appendHomeTimeline (state, update) {
state.homeTimeline = [update].concat(state.homeTimeline)
},
appendNotification (state, notification) {
state.notification = [notification].concat(state.notification)
},
insertHomeTimeline (state, messages) {
state.homeTimeline = state.homeTimeline.concat(messages)
}
},
actions: {
fetchAccount ({ commit }, id) {
return new Promise((resolve, reject) => {
ipcRenderer.send('get-local-account', id)
ipcRenderer.once('error-get-local-account', (event, err) => {
// TODO: handle error
console.log(err)
reject(err)
})
ipcRenderer.once('response-get-local-account', (event, account) => {
commit('updateAccount', account)
resolve(account)
})
})
},
username ({ commit }, account) {
return new Promise((resolve, reject) => {
const client = new Mastodon(
{
access_token: account.accessToken,
api_url: account.baseURL + '/api/v1'
})
client.get('/accounts/verify_credentials', {})
.then((res) => {
commit('updateUsername', res.data.username)
resolve(res)
})
})
},
startUserStreaming ({ commit }, account) {
ipcRenderer.send('start-user-streaming', account)
// TODO: when get notification, create notify and display badge in sidemenu
ipcRenderer.once('error-start-userstreaming', (event, err) => {
console.log(err)
})
ipcRenderer.on('update-start-user-streaming', (event, update) => {
commit('appendHomeTimeline', update)
})
ipcRenderer.on('notification-start-user-streaming', (event, notification) => {
commit('appendNotification', notification)
})
},
stopUserStreaming ({ commit }) {
ipcRenderer.send('stop-user-streaming')
},
fetchHomeTimeline ({ commit }, account) {
return new Promise((resolve, reject) => {
const client = new Mastodon(
{
access_token: account.accessToken,
api_url: account.baseURL + '/api/v1'
}
)
client.get('/timelines/home', { limit: 40 })
.then((res) => {
commit('insertHomeTimeline', res.data)
resolve()
})
.catch((err) => {
reject(err)
})
})
}
}
}

View File

@ -1,54 +1,8 @@
import { ipcRenderer } from 'electron'
import Mastodon from 'mastodon-api'
const SideMenu = {
namespaced: true,
state: {
account: {
domain: '',
id: ''
},
username: ''
},
mutations: {
updateAccount (state, account) {
state.account = account
},
updateUsername (state, username) {
state.username = username
}
},
actions: {
fetchAccount ({ commit }, id) {
return new Promise((resolve, reject) => {
ipcRenderer.send('get-local-account', id)
ipcRenderer.once('error-get-local-account', (event, err) => {
// TODO: handle error
console.log(err)
reject(err)
})
ipcRenderer.once('response-get-local-account', (event, account) => {
commit('updateAccount', account)
resolve(account)
})
})
},
username ({ commit }, account) {
return new Promise((resolve, reject) => {
const client = new Mastodon(
{
access_token: account.accessToken,
api_url: account.baseURL + '/api/v1'
})
client.get('/accounts/verify_credentials', {})
.then((res) => {
commit('updateUsername', res.data.username)
resolve(res)
})
})
}
}
state: {},
mutations: {},
actions: {}
}
export default SideMenu