Add: WIP: Timeline (rend/main)

This commit is contained in:
Cutls 2019-04-28 22:47:11 +09:00
parent b6042896e6
commit da6ceefe34
7 changed files with 172 additions and 48 deletions

41
package-lock.json generated
View File

@ -5431,8 +5431,7 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -5453,14 +5452,12 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -5475,20 +5472,17 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -5605,8 +5599,7 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -5618,7 +5611,6 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -5633,7 +5625,6 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -5641,14 +5632,12 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.3.5", "version": "2.3.5",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -5667,7 +5656,6 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -5748,8 +5736,7 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -5761,7 +5748,6 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -5847,8 +5833,7 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -5884,7 +5869,6 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -5904,7 +5888,6 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -5948,14 +5931,12 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
} }
} }
}, },

View File

@ -14,7 +14,6 @@
<div id="timelines"> <div id="timelines">
<div v-for="(value, key, index) in pubTL" :key="index" class="tl"> <div v-for="(value, key, index) in pubTL" :key="index" class="tl">
{{value.name}} {{value.name}}
<!--とりあえずここに書かせて-->
<TimelineToot <TimelineToot
v-for="[id,status] in value.statuses" v-for="[id,status] in value.statuses"
:key="id" :key="id"

View File

@ -1,5 +1,6 @@
<template> <template>
<div> <div>
<div>
<form @submit.prevent="addTL"> <form @submit.prevent="addTL">
<label <label
v-for="(types,name) in userTimelineTypes" v-for="(types,name) in userTimelineTypes"
@ -15,31 +16,131 @@
style="--font-size:.8em;margin-top:1em;" style="--font-size:.8em;margin-top:1em;"
>Add Column</BaseButton> >Add Column</BaseButton>
</form> </form>
</div>
<div id="timelines">
<div v-for="(value, key, index) in pubTL" :key="index" class="tl">
{{value.name}}/{{value.type}}
<TimelineToot
v-for="[id,status] in value.statuses"
:key="id"
:status="status"
:pref-static="pref.static"
/>
</div>
</div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { ipcRenderer } from "electron"
import { Status } from "megalodon"
import { Component, Prop, Vue } from "vue-property-decorator" import { Component, Prop, Vue } from "vue-property-decorator"
import TimelineToot from "@/components/Timeline/Toot.vue"
type Timeline = {
name: string
type: string
statuses: Map<number, Status>
error?: Error
}
type Timelines = Timeline[]
type DeleteListener = (e: Event, id: number) => void
type UpdateListener = (e: Event, status: Status) => void
@Component @Component({
components: {
TimelineToot
}
})
export default class UserTimeline extends Vue { export default class UserTimeline extends Vue {
@Prop() public username!: string @Prop() public username!: string
public deleteListeners: [string, DeleteListener][] = []
public timelineType: string = 'home' public updateListeners: [string, UpdateListener][] = []
public timelineType: string = "home"
public userTimelineTypes: { public userTimelineTypes: {
[key: string]: string [key: string]: string
} = { } = {
home: 'Home Timeline', home: "Home Timeline",
notify: 'Notifications', notify: "Notifications",
dm: 'Direct Messages', dm: "Direct Messages",
local: 'Local Timeline', local: "Local Timeline",
fediverse: 'Fediverse Timeline', fediverse: "Fediverse Timeline",
integrated: 'Integrated Timeline (Home + Local)', integrated: "Integrated Timeline (Home + Local)",
localPlus: 'Integrated Timeline (Local + Boost + Reply)', localPlus: "Integrated Timeline (Local + Boost + Reply)"
} }
public TL: Timelines = []
beforeDestroy() {
this.updateListeners.forEach(([name, listener]) => {
ipcRenderer.removeListener(name, listener)
})
this.deleteListeners.forEach(([name, listener]) => {
ipcRenderer.removeListener(name, listener)
})
}
public sortedStatus(statuses: Map<number, Status>): Map<number, Status> {
return statuses.sortByValue(
(s1, s2): number => {
return s1.created_at > s2.created_at ? -1 : 1
}
)
}
public addTL() { public addTL() {
console.log(this.timelineType) let timeline: Timeline = {
name: "",
type: this.timelineType,
statuses: new Map()
}
this.TL.push(timeline)
// TL
ipcRenderer.once(
`timeline-${timeline.name}-${timeline.type}`,
(e: Event, statuses: Status[], error?: Error) => {
timeline.error = error
if (error === undefined) {
this.loadTL(timeline, statuses)
}
this.$forceUpdate()
}
)
ipcRenderer.send("timeline", timeline.type, timeline.name)
}
public loadTL(timeline: Timeline, statuses: Status[]) {
timeline.statuses = new Map(
statuses.map((status): [number, Status] => [status.id, status])
)
// streaming
this.subscribeStreaming(timeline)
}
public async subscribeStreaming(timeline: Timeline) {
// update
let updateListener = (_: Event, status: Status) => {
timeline.statuses.set(status.id, status)
timeline.statuses = this.sortedStatus(timeline.statuses)
this.$forceUpdate()
}
ipcRenderer.on(`update-${timeline.name}-${timeline.type}`, updateListener)
this.updateListeners.push([
`update-${timeline.name}-${timeline.type}`,
updateListener
])
// delete
let deleteListener = (_: Event, id: number) => {
timeline.statuses.delete(id)
this.$forceUpdate()
}
ipcRenderer.on(`delete-${timeline.name}-${timeline.type}`, deleteListener)
this.deleteListeners.push([
`delete-${timeline.name}-${timeline.type}`,
deleteListener
])
ipcRenderer.send("open-streaming", timeline.name, timeline.type)
}
public showAccount(id: number) {
console.log("Account dialog:" + id)
} }
} }
</script> </script>
@ -62,4 +163,12 @@ label {
display: none; display: none;
} }
} }
#timelines {
display: flex;
width: 100%;
}
.tl {
height: 100%;
flex-grow: 4;
}
</style> </style>

View File

@ -30,7 +30,6 @@ export default class Application {
private constructor() { private constructor() {
this.isDarkMode = systemPreferences.isDarkMode() this.isDarkMode = systemPreferences.isDarkMode()
app.on('window-all-closed', () => this.onWindowAllClosed()) app.on('window-all-closed', () => this.onWindowAllClosed())
app.on('ready', () => this.onReady()) app.on('ready', () => this.onReady())
app.on('activate', () => this.onActivated()) app.on('activate', () => this.onActivated())

View File

@ -9,6 +9,7 @@ interface AccountDoc {
_id?: string _id?: string
domain: string domain: string
acct: string acct: string
full: string
avatar: string avatar: string
avatarStatic: string avatarStatic: string
accessToken: string accessToken: string
@ -101,6 +102,7 @@ export default class Auth {
let docs: AccountDoc = { let docs: AccountDoc = {
domain: instance, domain: instance,
acct: you.acct, acct: you.acct,
full: you.acct+"@"+instance,
avatar: you.avatar, avatar: you.avatar,
avatarStatic: you.avatar_static, avatarStatic: you.avatar_static,
accessToken: tokenData.accessToken, accessToken: tokenData.accessToken,

View File

@ -1,6 +1,7 @@
import { app } from "electron"
import Mastodon from 'megalodon' import Mastodon from 'megalodon'
import Datastore from "nedb"
import { join } from "path"
type Protocol = 'http' | 'websocket' type Protocol = 'http' | 'websocket'
export default class Clients { export default class Clients {
@ -17,7 +18,17 @@ export default class Clients {
if (!clients.has(username)) { if (!clients.has(username)) {
// usernameからドメインをとトークンをデータベースから取得してクライアントを作る // usernameからドメインをとトークンをデータベースから取得してクライアントを作る
//this.setAuthClient(protocol, username, this.createAuthClient(protocol, domain, accessToken)) let db = new Datastore({
filename: join(app.getPath("userData"), "account.db"),
autoload: true
})
db.find({ full: username }, function(err: any, docs: { domain: string; accessToken: string; }){
if (err) {
console.log(err)
} else {
Clients.setAuthClient(protocol, username, Clients.createAuthClient(protocol, docs.domain, docs.accessToken))
}
});
} }
return clients.get(username)! return clients.get(username)!

View File

@ -17,5 +17,28 @@ export default class Timeline {
event.sender.send(`timeline-${name}-no-auth`, [], error) event.sender.send(`timeline-${name}-no-auth`, [], error)
} }
}) })
ipcMain.on('timeline', async (event: Event, name: string, type: string) => {
const client = Client.getAuthClient(name)
try {
let url: string = ""
//home/notify/dm/local/fediverse/integrated/localPlus
//integratedはまだ。dmはAPI構造が違う。notifyはmax_idとかのためにヘッダー取らないといけない。
if(type=="home"){
url="/timelines/home"
}else if(type=="notify"){
url="/timelines/notifications"
}else if(type=="dm"){
url="/conversations"
}else if(type=="local"){
url="/timelines/public?local=true"
}else if(type=="fediverse"){
url="/timelines/public"
}
let res: Response<[Status]> = await client.get<[Status]>(url)
event.sender.send(`timeline-${name}-no-auth`, res.data)
} catch (error) {
event.sender.send(`timeline-${name}-no-auth`, [], error)
}
})
} }
} }