Add layouts and more
This commit is contained in:
parent
0c470f318a
commit
22a672788b
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,35 @@
|
|||
<script>
|
||||
import Loadable from './loadable/Loadable.js'
|
||||
import connectedServiceVue from './connectedService.vue'
|
||||
|
||||
export default {
|
||||
extends: connectedServiceVue,
|
||||
data() {
|
||||
return {
|
||||
account: new Loadable()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
connector() {
|
||||
return this.account
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
load() {
|
||||
this.account.load(this.catchEmit(this.getAccount(this.auth)), this.mapAccount)
|
||||
},
|
||||
checkAuth(auth) {
|
||||
return this.getAccount(auth)
|
||||
},
|
||||
getAccount() {
|
||||
this.mustDefine('getAccount(auth) method')
|
||||
},
|
||||
mapAccount(res) {
|
||||
return res.data
|
||||
},
|
||||
mapServiceName(res) {
|
||||
return `${this.serviceName} ${this.mapAccount(res)}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -22,7 +22,38 @@ export default {
|
|||
settingInt: settingIntVue,
|
||||
settingString: settingStringVue
|
||||
},
|
||||
extends: serviceEmiterVue
|
||||
extends: serviceEmiterVue,
|
||||
props: {
|
||||
auth: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newAuth: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
auth() {
|
||||
this.init()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.mustDefine('init() method')
|
||||
},
|
||||
mustDefine(name) {
|
||||
this.emitError('Must define ' + name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const Loadable = _Loadable
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<script>
|
||||
import baseServiceVue from './baseService.vue'
|
||||
import Loadable from './loadable/Loadable'
|
||||
|
||||
export default {
|
||||
extends: baseServiceVue,
|
||||
computed: {
|
||||
isSetup() {
|
||||
this.mustDefine('isSetup computed')
|
||||
return false
|
||||
},
|
||||
connector() {
|
||||
this.mustDefine('connector computed')
|
||||
return new Loadable()
|
||||
},
|
||||
serviceName() {
|
||||
this.mustDefine('serviceName computed')
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
if(this.isSetup) {
|
||||
this.load()
|
||||
} else this.connector.fail('First connection')
|
||||
},
|
||||
makeAuth() {
|
||||
this.catchEmit(this.checkAuth(this.newAuth)).then(res =>
|
||||
this.saveService(this.mapServiceName(res, this.newAuth), this.newAuth))
|
||||
},
|
||||
load() {
|
||||
this.mustDefine('load() method')
|
||||
},
|
||||
checkAuth() {
|
||||
this.mustDefine('checkAuth(auth) method')
|
||||
},
|
||||
mapServiceName() {
|
||||
return this.serviceName
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -16,7 +16,7 @@ export default {
|
|||
mixins: [ timedMixin ],
|
||||
props: {
|
||||
date: {
|
||||
type: Date,
|
||||
type: [Date, Number, String],
|
||||
default: Date.now
|
||||
}
|
||||
},
|
||||
|
|
|
@ -17,7 +17,6 @@ export default class {
|
|||
this.loaded = false
|
||||
this.data = undefined
|
||||
this.error = undefined
|
||||
this.loadingMore = false
|
||||
}
|
||||
success(data) {
|
||||
this.loaded = true
|
||||
|
@ -39,12 +38,4 @@ export default class {
|
|||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
loadMore(promise, then) {
|
||||
this.loadingMore = true
|
||||
promise.then(res => {
|
||||
then(res, this.data, this)
|
||||
this.loadingMore = false
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import Loadable from './Loadable'
|
||||
|
||||
export default class extends Loadable {
|
||||
reset() {
|
||||
super.reset()
|
||||
this.loadingMore = false
|
||||
}
|
||||
|
||||
loadMore(promise, then) {
|
||||
this.loadingMore = true
|
||||
promise.then(res => {
|
||||
then(res, this.data, this)
|
||||
this.loadingMore = false
|
||||
})
|
||||
}
|
||||
}
|
|
@ -11,10 +11,7 @@ export default {
|
|||
this.emit('error', err)
|
||||
},
|
||||
saveOptions(options) {
|
||||
this.emit('saveAll', options)
|
||||
this.$nextTick(function(){
|
||||
this.$forceUpdate()
|
||||
})
|
||||
this.emit('saveOptions', options)
|
||||
},
|
||||
saveOption(key, value) {
|
||||
this.saveOptionCouple({
|
||||
|
@ -22,9 +19,11 @@ export default {
|
|||
})
|
||||
},
|
||||
saveOptionCouple(couple) {
|
||||
this.emit('save', couple)
|
||||
this.$nextTick(function(){
|
||||
this.$forceUpdate()
|
||||
this.emit('saveOption', couple)
|
||||
},
|
||||
saveService(name, auth) {
|
||||
this.emit('saveService', {
|
||||
name: name, auth: auth
|
||||
})
|
||||
},
|
||||
catchEmit(req) {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
@keyup.left.ctrl.exact="onMove('y', -1)" @keyup.right.ctrl.exact="onMove('y', 1)"
|
||||
@keyup.up.alt.exact="onMove('h', -1)" @keyup.down.alt.exact="onMove('h', 1)"
|
||||
@keyup.left.alt.exact="onMove('w', -1)" @keyup.right.alt.exact="onMove('w', 1)"
|
||||
@keyup.delete.ctrl.exact="onRemove" @keyup.delete.alt.exact="onRemoveService"
|
||||
)
|
||||
slot(name="settings")
|
||||
</template>
|
||||
|
@ -27,6 +28,12 @@ export default {
|
|||
onMove(type, direction) {
|
||||
this.emit('move', { type: type, direction: direction })
|
||||
},
|
||||
onRemove() {
|
||||
this.emit('remove', { })
|
||||
},
|
||||
onRemoveService() {
|
||||
this.emit('removeService', { })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
.client(@scroll.passive="onScroll")
|
||||
loadable-block.list(:loadable="guilds")
|
||||
template(#success)
|
||||
guild(v-for="guild in guilds.get()" :key="guild.id" :guild="guild" :showMedia="showMedia")
|
||||
guild(v-for="guild in guilds.get()" :key="guild.id" :guild="guild" :showMedia="options.showMedia")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -22,33 +22,18 @@ export default {
|
|||
extends: serviceEmiterVue,
|
||||
mixins: [ timerMinin ],
|
||||
props: {
|
||||
token: {
|
||||
default: undefined,
|
||||
type: String
|
||||
auth: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
timeout: {
|
||||
default: 5000,
|
||||
type: Number
|
||||
},
|
||||
reconnect: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
buffer: {
|
||||
default: 20,
|
||||
type: Number
|
||||
},
|
||||
showMedia: {
|
||||
default: true,
|
||||
type: Boolean
|
||||
}
|
||||
options: { type: Object, default: () => ({}) }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rest: axios.create({
|
||||
baseURL: 'https://discordapp.com/api/',
|
||||
headers: { Authorization: this.token },
|
||||
timeout: this.timeout
|
||||
headers: { Authorization: this.auth.token },
|
||||
timeout: this.options.timeout
|
||||
}),
|
||||
guilds: new Loadable()
|
||||
}
|
||||
|
@ -62,7 +47,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
get(path, options = {}) {
|
||||
return this.catchEmit(this.rest.get(path, { params: { limit: this.buffer, ...options } }))
|
||||
return this.catchEmit(this.rest.get(path, { params: { limit: this.options.buffer, ...options } }))
|
||||
},
|
||||
onScroll() {
|
||||
/*if(!this.loadingOlder && event.target.scrollHeight - event.target.clientHeight - event.target.scrollTop - 100 < 0) {
|
||||
|
|
|
@ -1,87 +1,53 @@
|
|||
<template lang="pug">
|
||||
.discord
|
||||
service-header(:emit="emit")
|
||||
template(#title) Discord: {{ account.display() }}
|
||||
template(#title) {{ serviceName }}: {{ account.display() }}
|
||||
template(#settings)
|
||||
setting-boolean(:id="'reconnect'" :title="'Reconnect'" :value="reconnect" @change="saveOptionCouple")
|
||||
setting-int(:id="'buffer'" :title="'Buffer size'" :value="buffer" @change="saveOptionCouple")
|
||||
setting-boolean(:id="'showMedia'" :title="'Show medias'" :value="showMedia" @change="saveOptionCouple")
|
||||
setting-boolean(:id="'reconnect'" :title="'Reconnect'" :value="params.reconnect" @change="saveOptionCouple")
|
||||
setting-int(:id="'buffer'" :title="'Buffer size'" :value="params.buffer" @change="saveOptionCouple")
|
||||
setting-boolean(:id="'showMedia'" :title="'Show medias'" :value="params.showMedia" @change="saveOptionCouple")
|
||||
loadable-block.service-content(:loadable="account")
|
||||
template(#success)
|
||||
client(v-bind="$props")
|
||||
client(:auth="auth" :options="params" :emit="emit")
|
||||
template(#error)
|
||||
form(@submit.prevent="makeAuth")
|
||||
p
|
||||
label(for="token") Token:
|
||||
input#token(v-model="newToken" required)
|
||||
input#token(v-model="newAuth.token" required)
|
||||
p
|
||||
input(type="submit" value="Connect")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global axios */
|
||||
import baseServiceVue, { Loadable } from '../core/baseService.vue'
|
||||
import accountServiceVue from '../core/accountService.vue'
|
||||
|
||||
import clientVue from './client.vue'
|
||||
|
||||
export default { //TODO: Use oauth
|
||||
name: 'Discord',
|
||||
components: {
|
||||
client: clientVue
|
||||
},
|
||||
extends: baseServiceVue,
|
||||
props: {
|
||||
token: {
|
||||
default: undefined,
|
||||
type: String
|
||||
components: { client: clientVue },
|
||||
extends: accountServiceVue,
|
||||
computed: {
|
||||
params() {
|
||||
return { timeout: 5000, reconnect: false, buffer: 20, showMedia: true, ...this.options }
|
||||
},
|
||||
timeout: {
|
||||
default: 5000,
|
||||
type: Number
|
||||
isSetup() {
|
||||
return this.auth && this.auth.token
|
||||
},
|
||||
reconnect: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
buffer: {
|
||||
default: 20,
|
||||
type: Number
|
||||
},
|
||||
showMedia: {
|
||||
default: true,
|
||||
type: Boolean
|
||||
serviceName() {
|
||||
return 'Discord'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valid: false,
|
||||
newToken: this.token,
|
||||
account: new Loadable()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
getMe(token) {
|
||||
return this.catchEmit(axios.get('https://discordapp.com/api/users/@me', {
|
||||
getAccount({ token }) {
|
||||
return axios.get('https://discordapp.com/api/users/@me', {
|
||||
headers: { Authorization: token },
|
||||
timeout: this.timeout
|
||||
}))
|
||||
timeout: this.params.timeout
|
||||
})
|
||||
},
|
||||
init() {
|
||||
if(this.token) {
|
||||
this.account.load(
|
||||
this.getMe(this.token),
|
||||
res => res.data.username
|
||||
)
|
||||
} else {
|
||||
this.account.fail('First connection')
|
||||
}
|
||||
},
|
||||
makeAuth() {
|
||||
this.getMe(this.newToken)
|
||||
.then(() => this.saveOption('token', this.newToken))
|
||||
mapAccount(res) {
|
||||
return res.data.username
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
loadable-block.list(:loadable="statues")
|
||||
template(#success)
|
||||
template(v-for="status in statues.get()")
|
||||
status(v-if="showStatus(status)" :key="status.id" :status="status" :now="now" :showMedia="showMedia" @mark="onStatusMark")
|
||||
status(v-if="showStatus(status)" :key="status.id" :status="status" :now="now" :showMedia="options.showMedia" @mark="onStatusMark")
|
||||
.status(v-show="statues.loadingMore")
|
||||
.service-loader
|
||||
.notifications(v-if="hasNotifications")
|
||||
|
@ -14,7 +14,7 @@
|
|||
span.date(@click.stop.prevent="onNotificationsClear") ❌
|
||||
.list
|
||||
notification(v-for="notification in notifications.get()" :key="notification.id" :notification="notification" :now="now"
|
||||
:showMedia="showMedia" @dismiss="onNotificationDismiss" @mark="onStatusMark")
|
||||
:showMedia="options.showMedia" @dismiss="onNotificationDismiss" @mark="onStatusMark")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -28,6 +28,7 @@ import statusVue from './status.vue'
|
|||
import notificationVue from './notification.vue'
|
||||
|
||||
import Loadable from '../core/loadable/Loadable'
|
||||
import ReLoadable from '../core/loadable/ReLoadable'
|
||||
import loadableBlockVue from '../core/loadable/loadableBlock.vue'
|
||||
|
||||
export default {
|
||||
|
@ -39,35 +40,20 @@ export default {
|
|||
extends: serviceEmiterVue,
|
||||
mixins: [ timerMinin ],
|
||||
props: {
|
||||
server: {
|
||||
type: String,
|
||||
default: undefined
|
||||
auth: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
token: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
timeout: {
|
||||
type: Number,
|
||||
default: 5000
|
||||
},
|
||||
reconnect: Boolean,
|
||||
buffer: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
reblog: Boolean,
|
||||
reply: Boolean,
|
||||
showMedia: Boolean
|
||||
options: { type: Object, default: () => ({}) }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rest: axios.create({
|
||||
baseURL: `https://${this.server}/api/v1/`,
|
||||
headers: { Authorization: 'Bearer ' + this.token },
|
||||
timeout: this.timeout
|
||||
baseURL: `https://${this.auth.server}/api/v1/`,
|
||||
headers: { Authorization: 'Bearer ' + this.auth.token },
|
||||
timeout: this.options.timeout
|
||||
}),
|
||||
statues: new Loadable(),
|
||||
statues: new ReLoadable(),
|
||||
notifications: new Loadable()
|
||||
}
|
||||
},
|
||||
|
@ -89,7 +75,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
get(path, options = {}) {
|
||||
return this.catchEmit(this.rest.get(path, { params: { limit: this.buffer, ...options } }))
|
||||
return this.catchEmit(this.rest.get(path, { params: { limit: this.options.buffer, ...options } }))
|
||||
},
|
||||
post(path, options = {}) {
|
||||
return this.catchEmit(this.rest.post(path, options))
|
||||
|
@ -104,14 +90,14 @@ export default {
|
|||
(res, statues) => Lists.pushAll(statues, res.data)
|
||||
)
|
||||
} else if(event.target.scrollTop < 20) {
|
||||
this.statues.get().splice(this.buffer)
|
||||
this.statues.get().splice(this.options.buffer)
|
||||
}
|
||||
},
|
||||
removeById(ls, id) {
|
||||
Lists.removeFirst(ls, e => e.id === id)
|
||||
},
|
||||
showStatus(status) {
|
||||
return (!status.in_reply_to_id || this.reply) && (!status.reblog || this.reblog)
|
||||
return (!status.in_reply_to_id || this.options.reply) && (!status.reblog || this.options.reblog)
|
||||
},
|
||||
onStatusMark(action) {
|
||||
this.post(`/statuses/${action.id}/${action.type}`)
|
||||
|
@ -127,7 +113,7 @@ export default {
|
|||
},
|
||||
setupStream() {
|
||||
const ws = new WebSocket(
|
||||
`wss://${this.server}/api/v1/streaming?access_token=${this.token}&stream=user`
|
||||
`wss://${this.auth.server}/api/v1/streaming?access_token=${this.auth.token}&stream=user`
|
||||
)
|
||||
ws.onmessage = event => {
|
||||
event = JSON.parse(event.data)
|
||||
|
@ -150,9 +136,9 @@ export default {
|
|||
ws.onclose = () => {
|
||||
this.emitError(
|
||||
'Mastodon stream disconnected !' +
|
||||
(this.reconnect ? ' Reconnecting...' : '')
|
||||
(this.options.reconnect ? ' Reconnecting...' : '')
|
||||
)
|
||||
if (this.reconnect) setTimeout(() => this.setupStream(), this.timeout)
|
||||
if (this.options.reconnect) setTimeout(() => this.setupStream(), this.options.timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,113 +2,64 @@
|
|||
.mastodon
|
||||
service-header(:emit="emit")
|
||||
template(#title)
|
||||
| Mastodon:
|
||||
| {{ serviceName }}:
|
||||
loadable-inline(:loadable="account")
|
||||
template(#success)
|
||||
span(v-html="parseEmojis(account.data.display_name, account.data.emojis) + '@' + server")
|
||||
span(v-html="parseEmojis(account.data.display_name, account.data.emojis) + '@' + auth.server")
|
||||
template(#settings)
|
||||
setting-boolean(:id="'reconnect'" :title="'Reconnect'" :value="reconnect" @change="saveOptionCouple")
|
||||
setting-boolean(:id="'reblog'" :title="'Show reblogs'" :value="reblog" @change="saveOptionCouple")
|
||||
setting-boolean(:id="'reply'" :title="'Show replies'" :value="reply" @change="saveOptionCouple")
|
||||
setting-int(:id="'buffer'" :title="'Buffer size'" :value="buffer" @change="saveOptionCouple")
|
||||
setting-boolean(:id="'showMedia'" :title="'Show medias'" :value="showMedia" @change="saveOptionCouple")
|
||||
setting-boolean(:id="'reconnect'" :title="'Reconnect'" :value="params.reconnect" @change="saveOptionCouple")
|
||||
setting-boolean(:id="'reblog'" :title="'Show reblogs'" :value="params.reblog" @change="saveOptionCouple")
|
||||
setting-boolean(:id="'reply'" :title="'Show replies'" :value="params.reply" @change="saveOptionCouple")
|
||||
setting-int(:id="'buffer'" :title="'Buffer size'" :value="params.buffer" @change="saveOptionCouple")
|
||||
setting-boolean(:id="'showMedia'" :title="'Show medias'" :value="params.showMedia" @change="saveOptionCouple")
|
||||
loadable-block.service-content(:loadable="account")
|
||||
template(#success)
|
||||
client(v-bind="$props")
|
||||
client(:auth="auth" :options="params" :emit="emit")
|
||||
template(#error)
|
||||
form(@submit.prevent="makeAuth")
|
||||
p
|
||||
label(for="server") Server:
|
||||
input#server(v-model="newServer" required)
|
||||
input#server(v-model="newAuth.server" required)
|
||||
p
|
||||
label(for="token") Token:
|
||||
input#token(v-model="newToken" required)
|
||||
input#token(v-model="newAuth.token" required)
|
||||
p
|
||||
input(type="submit" value="Connect")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global axios */
|
||||
import baseServiceVue, { Loadable } from '../core/baseService.vue'
|
||||
import accountServiceVue from '../core/accountService.vue'
|
||||
|
||||
import { parseEmojisMixin } from './tools'
|
||||
import clientVue from './client.vue'
|
||||
|
||||
export default { //TODO: Use oauth
|
||||
name: 'Mastodon',
|
||||
components: {
|
||||
client: clientVue
|
||||
},
|
||||
extends: baseServiceVue,
|
||||
components: { client: clientVue },
|
||||
extends: accountServiceVue,
|
||||
mixins: [ parseEmojisMixin ],
|
||||
props: {
|
||||
server: {
|
||||
type: String,
|
||||
default: undefined
|
||||
computed: {
|
||||
params() {
|
||||
return { timeout: 5000, reconnect: false, buffer: 20, reblog: true, reply: false,
|
||||
showMedia: true, ...this.options }
|
||||
},
|
||||
token: {
|
||||
type: String,
|
||||
default: undefined
|
||||
isSetup() {
|
||||
return this.auth && this.auth.server && this.auth.token
|
||||
},
|
||||
timeout: {
|
||||
default: 5000,
|
||||
type: Number
|
||||
},
|
||||
reconnect: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
buffer: {
|
||||
default: 20,
|
||||
type: Number
|
||||
},
|
||||
reblog: {
|
||||
default: true,
|
||||
type: Boolean
|
||||
},
|
||||
reply: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
showMedia: {
|
||||
default: true,
|
||||
type: Boolean
|
||||
serviceName() {
|
||||
return 'Mastodon'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newServer: this.server,
|
||||
newToken: this.token,
|
||||
account: new Loadable()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
getMe(server, token) {
|
||||
return this.catchEmit(axios.get(`https://${server}/api/v1/accounts/verify_credentials`, {
|
||||
getAccount({ server, token }) {
|
||||
return axios.get(`https://${server}/api/v1/accounts/verify_credentials`, {
|
||||
headers: { Authorization: 'Bearer ' + token },
|
||||
timeout: this.timeout
|
||||
}))
|
||||
timeout: this.params.timeout
|
||||
})
|
||||
},
|
||||
init() {
|
||||
if(this.server && this.token) {
|
||||
this.account.load(
|
||||
this.getMe(this.server, this.token),
|
||||
res => res.data
|
||||
)
|
||||
} else {
|
||||
this.account.fail('First connection')
|
||||
}
|
||||
},
|
||||
makeAuth() {
|
||||
this.getMe(this.newServer, this.newToken)
|
||||
.then(() => {
|
||||
this.saveOptions({ ...this.$props,
|
||||
server: this.newServer, token: this.newToken })
|
||||
this.init()
|
||||
})
|
||||
mapServiceName(res, { server }) {
|
||||
return `${this.serviceName} ${this.mapAccount(res).acct}@${server}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<template lang="pug">
|
||||
.nextcloud-news(v-show="showEmpty || hasNews || !isSetup")
|
||||
.nextcloud-news(v-show="showService")
|
||||
service-header(:emit="emit")
|
||||
template(#title)
|
||||
| Nextcloud News
|
||||
| {{ serviceName }}
|
||||
span.note(v-if="hasNews") ({{ news.get().length }})
|
||||
template(#settings)
|
||||
setting-int(:id="'update'" :title="'Update interval'" :value="update" @change="saveOptionCouple")
|
||||
setting-int(:id="'buffer'" :title="'Buffer size'" :value="buffer" @change="saveOptionCouple")
|
||||
setting-boolean(:id="'showEmpty'" :title="'Show empty'" :value="showEmpty" @change="saveOptionCouple")
|
||||
setting-int(:id="'update'" :title="'Update interval'" :value="params.update" @change="saveOptionCouple")
|
||||
setting-int(:id="'buffer'" :title="'Buffer size'" :value="params.buffer" @change="saveOptionCouple")
|
||||
setting-boolean(:id="'showEmpty'" :title="'Show empty'" :value="params.showEmpty" @change="saveOptionCouple")
|
||||
loadable-block.unreaded(:loadable="news")
|
||||
template(#success)
|
||||
.news(v-for="line in news.get()")
|
||||
|
@ -20,20 +20,20 @@
|
|||
form(@submit.prevent="makeAuth")
|
||||
p
|
||||
label(for="server") Server:
|
||||
input#server(v-model="newServer" required)
|
||||
input#server(v-model="newAuth.server" required)
|
||||
p
|
||||
label(for="username") Username:
|
||||
input#username(v-model="newUsername" required)
|
||||
input#username(v-model="newAuth.username" required)
|
||||
p
|
||||
label(for="token") Token:
|
||||
input#token(v-model="newToken" required)
|
||||
input#token(v-model="newAuth.token" required)
|
||||
p
|
||||
input(type="submit" value="Connect")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global axios */
|
||||
import baseServiceVue from '../core/baseService.vue'
|
||||
import connectedServiceVue from '../core/connectedService.vue'
|
||||
import fromNowVue, { timerMinin } from '../core/fromNow.vue'
|
||||
|
||||
import Loadable from '../core/loadable/Loadable'
|
||||
|
@ -44,68 +44,38 @@ export default {
|
|||
components: {
|
||||
fromNow: fromNowVue
|
||||
},
|
||||
extends: baseServiceVue,
|
||||
extends: connectedServiceVue,
|
||||
mixins: [ timerMinin ],
|
||||
props: {
|
||||
server: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
token: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
timeout: {
|
||||
default: 5000,
|
||||
type: Number
|
||||
},
|
||||
buffer : {
|
||||
default: -1,
|
||||
type: Number
|
||||
},
|
||||
update: {
|
||||
default: 5 * 60, //5min
|
||||
type: Number
|
||||
},
|
||||
showEmpty: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rest: axios.create({
|
||||
baseURL: `https://${this.server}/index.php/apps/news/api/v1-2/`,
|
||||
timeout: this.timeout,
|
||||
headers: {
|
||||
Authorization: 'Basic ' + btoa(this.username + ':' + this.token)
|
||||
}
|
||||
}),
|
||||
news: new Loadable(),
|
||||
newServer: this.server,
|
||||
newUsername: this.username,
|
||||
newToken: this.token,
|
||||
rest: undefined, //NOTE: set in this.init()
|
||||
news: new Loadable()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
params() {
|
||||
return { timeout: 5000, buffer: -1, update: 5 * 60, showEmpty: true, ...this.options }
|
||||
},
|
||||
isSetup() {
|
||||
return this.auth && this.auth.server && this.auth.username && this.auth.token
|
||||
},
|
||||
connector() {
|
||||
return this.news
|
||||
},
|
||||
serviceName() {
|
||||
return 'Nextcloud News'
|
||||
},
|
||||
hasNews() {
|
||||
return this.news.isSuccess() && this.news.get().length > 0
|
||||
},
|
||||
isSetup() {
|
||||
return this.server && this.username && this.token
|
||||
showService() {
|
||||
return this.params.showEmpty || this.hasNews || !this.isSetup
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
this.news.load(
|
||||
this.catchEmit(this.rest.get('/items', { params: { batchSize: this.buffer, type: 3, getRead: false } })),
|
||||
this.catchEmit(this.rest.get('/items', { params: { batchSize: this.params.buffer, type: 3, getRead: false } })),
|
||||
res => res.data.items.map(n => {
|
||||
n.open = false
|
||||
return n
|
||||
|
@ -119,22 +89,24 @@ export default {
|
|||
this.catchEmit(this.rest.put(`/items/${id}/read`))
|
||||
.then(() => this.removeNews(id))
|
||||
},
|
||||
init() {
|
||||
if(this.isSetup) {
|
||||
this.loadData()
|
||||
load() {
|
||||
this.rest = axios.create({
|
||||
baseURL: `https://${this.auth.server}/index.php/apps/news/api/v1-2/`,
|
||||
timeout: this.params.timeout,
|
||||
headers: {
|
||||
Authorization: 'Basic ' + btoa(this.auth.username + ':' + this.auth.token)
|
||||
}
|
||||
}) //NOTE: required by this.params
|
||||
|
||||
if(this.update > 0)
|
||||
setInterval(this.loadData, this.update * 1000)
|
||||
}else this.news.fail('First connection')
|
||||
this.loadData()
|
||||
|
||||
if(this.params.update > 0)
|
||||
setInterval(this.loadData, this.params.update * 1000)
|
||||
},
|
||||
makeAuth() {
|
||||
this.catchEmit(axios.get(`https://${this.newServer}/index.php/apps/news/api/v1-2/folders`, {
|
||||
headers: { Authorization: 'Basic ' + btoa(this.newUsername + ':' + this.newToken) },
|
||||
timeout: this.timeout
|
||||
})).then(() => {
|
||||
this.saveOptions({ ...this.$props,
|
||||
server: this.newServer, token: this.newToken, username: this.newUsername })
|
||||
this.init()
|
||||
checkAuth({ server, username, token }){
|
||||
return axios.get(`https://${server}/index.php/apps/news/api/v1-2/folders`, {
|
||||
headers: { Authorization: 'Basic ' + btoa(username + ':' + token) },
|
||||
timeout: this.params.timeout
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template lang="pug">
|
||||
.openweathermap
|
||||
service-header(:emit="emit")
|
||||
template(#title) OpenWeatherMap
|
||||
template(#title) {{ serviceName }}
|
||||
template(#settings)
|
||||
setting-int(:id="'update'" :title="'Update interval'" :value="update" @change="saveOptionCouple")
|
||||
setting-int(:id="'forecastLimit'" :title="'Forecast limit'" :value="forecastLimit" @change="saveOptionCouple")
|
||||
setting-int(:id="'update'" :title="'Update interval'" :value="params.update" @change="saveOptionCouple")
|
||||
setting-int(:id="'forecastLimit'" :title="'Forecast limit'" :value="params.forecastLimit" @change="saveOptionCouple")
|
||||
p.setting
|
||||
button(@click="showAdd = true") Add city
|
||||
loadable-block(:loadable="weathers")
|
||||
|
@ -20,14 +20,14 @@
|
|||
form(@submit.prevent="makeAuth")
|
||||
p
|
||||
label(for="token") Token:
|
||||
input#token(v-model="newToken" required)
|
||||
input#token(v-model="newAuth.token" required)
|
||||
p
|
||||
input(type="submit" value="Connect")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global axios */
|
||||
import baseServiceVue from '../core/baseService.vue'
|
||||
import connectedServiceVue from '../core/connectedService.vue'
|
||||
import Lists from '../core/Lists.js'
|
||||
import Loadable from '../core/loadable/Loadable'
|
||||
|
||||
|
@ -40,52 +40,30 @@ export default {
|
|||
weather: weatherVue,
|
||||
chart: chartVue
|
||||
},
|
||||
extends: baseServiceVue,
|
||||
props: {
|
||||
token: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
cities: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
timeout: {
|
||||
default: 5000,
|
||||
type: Number
|
||||
},
|
||||
update: {
|
||||
default: 10 * 60, //10min
|
||||
type: Number
|
||||
},
|
||||
lang: {
|
||||
default: 'fr',
|
||||
type: String
|
||||
},
|
||||
forecastLimit: {
|
||||
default: 9,
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
extends: connectedServiceVue,
|
||||
data() {
|
||||
return {
|
||||
rest: axios.create({
|
||||
baseURL: 'https://api.openweathermap.org/data/2.5/',
|
||||
params: {
|
||||
appid: this.token, units: 'metric', lang: this.lang
|
||||
},
|
||||
timeout: this.timeout
|
||||
}),
|
||||
newToken: this.token,
|
||||
rest: undefined, //NOTE: set in this.init()
|
||||
weathers: new Loadable(),
|
||||
forecast: new Loadable(),
|
||||
selectedId: 0,
|
||||
showAdd: this.cities.length == 0
|
||||
showAdd: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
params() {
|
||||
return { cities: [], timeout: 5000, update: 10 * 60, lang: 'fr',
|
||||
forecastLimit: 9, ...this.options }
|
||||
},
|
||||
isSetup() {
|
||||
return this.auth && this.auth.token
|
||||
},
|
||||
serviceName() {
|
||||
return 'OpenWeatherMap'
|
||||
},
|
||||
connector() {
|
||||
return this.weathers
|
||||
},
|
||||
forecastChart() { return {
|
||||
datasets: [{
|
||||
type: 'line',
|
||||
|
@ -117,7 +95,7 @@ export default {
|
|||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
this.$watch('options.cities', this.init)
|
||||
},
|
||||
methods: {
|
||||
makeSelect(id) {
|
||||
|
@ -138,7 +116,7 @@ export default {
|
|||
if(this.selected) {
|
||||
this.forecast.load(
|
||||
this.catchEmit(this.rest.get('forecast', { params: {
|
||||
id: this.selected.id, cnt: this.forecastLimit
|
||||
id: this.selected.id, cnt: this.params.forecastLimit
|
||||
}})),
|
||||
res => res.data.list
|
||||
)
|
||||
|
@ -149,34 +127,38 @@ export default {
|
|||
return `${date.toLocaleDateString()} ${date.getHours()}h`
|
||||
},
|
||||
addCity(id) {
|
||||
this.cities.push({ id: id })
|
||||
this.saveOption('cities', this.cities)
|
||||
this.params.cities.push({ id: id })
|
||||
this.saveOption('cities', this.params.cities)
|
||||
},
|
||||
removeCity(key) {
|
||||
Lists.removeAt(this.cities, key)
|
||||
this.saveOption('cities', this.cities)
|
||||
Lists.removeAt(this.params.cities, key)
|
||||
this.saveOption('cities', this.params.cities)
|
||||
},
|
||||
init() {
|
||||
if(this.token) {
|
||||
if(this.cities.length > 0) {
|
||||
axios.all(this.cities.map(city => this.getWeather(city)))
|
||||
.then(axios.spread((...ress) =>
|
||||
this.weathers.success(ress.map(r => r.data))))
|
||||
.then(this.loadForecast)
|
||||
.catch(this.weathers.fail)
|
||||
load() {
|
||||
this.rest = axios.create({
|
||||
baseURL: 'https://api.openweathermap.org/data/2.5/',
|
||||
params: {
|
||||
appid: this.auth.token, units: 'metric', lang: this.params.lang
|
||||
},
|
||||
timeout: this.params.timeout
|
||||
}) //NOTE: required by this.params
|
||||
this.showAdd = this.params.cities.length == 0
|
||||
|
||||
if(this.update > 0)
|
||||
setInterval(this.updateData, this.update * 1000)
|
||||
} else this.weathers.success([])
|
||||
} else this.weathers.fail('First connection')
|
||||
if(this.params.cities.length > 0) {
|
||||
axios.all(this.params.cities.map(city => this.getWeather(city)))
|
||||
.then(axios.spread((...ress) =>
|
||||
this.weathers.success(ress.map(r => r.data))))
|
||||
.then(this.loadForecast)
|
||||
.catch(this.weathers.fail)
|
||||
|
||||
if(this.update > 0)
|
||||
setInterval(this.updateData, this.params.update * 1000)
|
||||
} else this.weathers.success([])
|
||||
},
|
||||
makeAuth() {
|
||||
this.catchEmit(axios.get('https://api.openweathermap.org/data/2.5/weather', {
|
||||
params: { q: 'London', appid: this.newToken },
|
||||
timeout: this.timeout
|
||||
})).then(() => {
|
||||
this.saveOption('token', this.newToken)
|
||||
this.init()
|
||||
checkAuth({ token }) {
|
||||
return axios.get('https://api.openweathermap.org/data/2.5/weather', {
|
||||
params: { q: 'London', appid: token },
|
||||
timeout: this.params.timeout
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
38
index.html
38
index.html
|
@ -21,15 +21,35 @@
|
|||
<div id="errors" v-show="errors">
|
||||
<p class="error" v-for="(error, key) in errors" @click="removeError(key)">{{ error }}</p>
|
||||
</div>
|
||||
<div id="manager">
|
||||
<button @click="showManager = !showManager">+</button>
|
||||
<input v-show="showManager" v-model="newService" @keyup.enter="addService">
|
||||
</div>
|
||||
<div id="services">
|
||||
<component
|
||||
v-for="(service, key) in services" :is="service.type" :emit="makeEmiter(key)"
|
||||
:key="key" v-bind="service.options" :style="gridPos(key, service.position)"
|
||||
/>
|
||||
<div id="content">
|
||||
<div id="services">
|
||||
<div class="tile" v-if="layout" v-for="tile in layoutTiles" :style="tile.grid">
|
||||
<component :is="tile.service.type" :emit="tile.emiter" :auth="tile.service.auth" :options="tile.options" />
|
||||
</div>
|
||||
</div>
|
||||
<button id="showManager" @click="showManager = !showManager">{{ showManager ? '▼' : '▲' }}</button>
|
||||
<div id="manager" v-show="showManager">
|
||||
<div>
|
||||
<input v-model="newService" @keyup.enter="addService" placeholder="service">
|
||||
</div>
|
||||
<div id="layout-select">
|
||||
<div class="layout" v-for="(layout, id) in layouts">
|
||||
<template v-if="layoutId == id">
|
||||
<input :value="layout.name" @keyup.ctrl.delete="removeSelectedLayout()"
|
||||
@keyup.enter="renameSelectedLayout($event.target.value)">
|
||||
</template>
|
||||
<button v-else @click="layoutId = id">{{ layout.name }}</button>
|
||||
</div>
|
||||
<div><button @click="addLayout">+</button></div>
|
||||
</div>
|
||||
<div>
|
||||
<select @change="showService($event.target.value)">
|
||||
<option v-for="(service, key) in services.get()" :value="key">
|
||||
{{ service.name || service.type }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="main.js"></script>
|
||||
|
|
51
main.css
51
main.css
|
@ -56,13 +56,46 @@ input, select, button {
|
|||
padding: 0.3em;
|
||||
}
|
||||
|
||||
#manager {
|
||||
#content {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#showManager {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#manager {
|
||||
background-color: #222;
|
||||
border-radius: 0.3em;
|
||||
padding-left: 1em;
|
||||
height: 1.3em;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: justify;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#layout-select {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#services {
|
||||
height: 100vh;
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: -ms-grid;
|
||||
display: grid;
|
||||
grid-gap: .2em;
|
||||
|
@ -73,8 +106,12 @@ input, select, button {
|
|||
justify-items: stretch;
|
||||
}
|
||||
|
||||
#services > div {
|
||||
#services .tile {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#services .tile > div {
|
||||
height: 100%;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
|
@ -84,25 +121,25 @@ input, select, button {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
#services > div .service-header .title, #services > div .service-header .settings {
|
||||
#services .tile > div .service-header .title, #services .tile > div .service-header .settings {
|
||||
margin: 0.3em;
|
||||
background-color: #222;
|
||||
border-radius: 0.3em;
|
||||
padding: 0.3em;
|
||||
}
|
||||
|
||||
#services > div .service-header .title {
|
||||
#services .tile > div .service-header .title {
|
||||
font-size: large;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#services > div .service-header .settings .position {
|
||||
#services .tile > div .service-header .settings .position {
|
||||
float: right;
|
||||
width: 1.2em;
|
||||
}
|
||||
|
||||
#services > div .service-content {
|
||||
#services .tile > div .service-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
178
main.js
178
main.js
|
@ -1,31 +1,148 @@
|
|||
/* globals Vue */
|
||||
/* exported app */
|
||||
|
||||
const layoutsStorage = 'layouts'
|
||||
const servicesStorage = 'services'
|
||||
|
||||
class WebStorageHandler { //TODO: extends loadable
|
||||
constructor(storage, key, data) {
|
||||
this.storage = storage
|
||||
this.key = key
|
||||
this.data = data || []
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.data
|
||||
}
|
||||
|
||||
load() {
|
||||
if (this.storage.getItem(this.key)) {
|
||||
try {
|
||||
this.data = JSON.parse(this.storage.getItem(this.key))
|
||||
} catch (e) {
|
||||
this.storage.removeItem(this.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save() {
|
||||
this.storage.setItem(this.key, JSON.stringify(this.data))
|
||||
}
|
||||
}
|
||||
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
showManager: false,
|
||||
|
||||
layouts: new WebStorageHandler(window.localStorage, layoutsStorage, [{ name: 'main', tiles: [] }]),
|
||||
layoutId: 0,
|
||||
|
||||
services: new WebStorageHandler(window.localStorage, servicesStorage),
|
||||
newService: '',
|
||||
services: [],
|
||||
|
||||
errors: [],
|
||||
bus: new Vue()
|
||||
},
|
||||
mounted() {
|
||||
if (localStorage.getItem(servicesStorage)) { //TODO: allow external storage
|
||||
try {
|
||||
this.services = JSON.parse(localStorage.getItem(servicesStorage))
|
||||
} catch (e) {
|
||||
localStorage.removeItem(servicesStorage)
|
||||
}
|
||||
}
|
||||
this.layouts.load()
|
||||
this.services.load()
|
||||
|
||||
this.bus.$on('error', this.onError)
|
||||
this.bus.$on('saveAll', this.onSaveAll)
|
||||
this.bus.$on('save', this.onSave)
|
||||
this.bus.$on('saveOptions', this.onSaveOptions)
|
||||
this.bus.$on('saveOption', this.onSaveOption)
|
||||
this.bus.$on('move', this.onMove)
|
||||
this.bus.$on('remove', this.onRemove)
|
||||
this.bus.$on('saveService', this.onSaveService)
|
||||
this.bus.$on('removeService', this.onRemoveService)
|
||||
},
|
||||
computed: {
|
||||
layout() {
|
||||
return this.layouts.get()[this.layoutId]
|
||||
},
|
||||
layoutTiles() {
|
||||
return this.layout.tiles.map((tile, key) => ({
|
||||
...tile, service: this.loadService(key, tile.service),
|
||||
grid: this.gridPos(tile.position), emiter: this.makeEmiter(key)
|
||||
}))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//Layouts
|
||||
addLayout() {
|
||||
this.layouts.get().push({
|
||||
name: 'layout' + this.layouts.get().length, tiles: []
|
||||
})
|
||||
this.layouts.save()
|
||||
},
|
||||
renameSelectedLayout(name) {
|
||||
this.layout.name = name
|
||||
this.layouts.save()
|
||||
},
|
||||
removeSelectedLayout() {
|
||||
this.layouts.get().splice(this.layoutId, 1)
|
||||
this.layoutId = 0
|
||||
this.layouts.save()
|
||||
},
|
||||
//Tiles
|
||||
showService(id) {
|
||||
this.layout.tiles.push({
|
||||
service: id, position: {}, options: {}
|
||||
})
|
||||
this.layouts.save()
|
||||
},
|
||||
tile(id) {
|
||||
return this.layout.tiles[id]
|
||||
},
|
||||
onSaveOption({ key, msg }) {
|
||||
this.$set(this.tile(key).options, msg.key, msg.value)
|
||||
this.layouts.save()
|
||||
},
|
||||
onSaveOptions({ key, msg }) {
|
||||
this.tile(key).options = Object.assign({}, this.tile(key).options, msg)
|
||||
this.layouts.save()
|
||||
},
|
||||
onMove({ key, msg }) {
|
||||
this.$set(this.tile(key).position, msg.type, Math.max(1,
|
||||
(this.tile(key).position[msg.type] || 1) + msg.direction
|
||||
))
|
||||
this.layouts.save()
|
||||
},
|
||||
onRemove({ key }) {
|
||||
this.layout.tiles.splice(key, 1)
|
||||
this.layouts.save()
|
||||
},
|
||||
|
||||
//Services
|
||||
loadService(key, id) {
|
||||
const ser = this.services.get()[id]
|
||||
if (ser)
|
||||
return ser
|
||||
else {
|
||||
this.onRemove({ key })
|
||||
this.addError('Removing missing service')
|
||||
}
|
||||
},
|
||||
addService() {
|
||||
if (this.newService) {
|
||||
this.services.get().push({ type: this.newService, name: this.newService, auth: {} })
|
||||
this.services.save()
|
||||
this.showService(this.services.get().length - 1)
|
||||
this.newService = ''
|
||||
}
|
||||
},
|
||||
onSaveService({ key, msg }) {
|
||||
const service = this.loadService(key, this.tile(key).service)
|
||||
service.name = msg.name
|
||||
service.auth = msg.auth
|
||||
this.services.save()
|
||||
},
|
||||
onRemoveService({ key }) {
|
||||
this.services.get().splice(this.tile(key).service, 1)
|
||||
this.onRemove({ key })
|
||||
this.services.save()
|
||||
},
|
||||
|
||||
//Errors
|
||||
onError(event) {
|
||||
this.addError(event.msg.toString())
|
||||
|
@ -36,47 +153,12 @@ var app = new Vue({
|
|||
removeError(id) {
|
||||
this.errors.splice(id, 1)
|
||||
},
|
||||
//Services
|
||||
addService() {
|
||||
if (!this.newService)
|
||||
return
|
||||
|
||||
this.services.push({
|
||||
type: this.newService,
|
||||
options: {}, position: {}
|
||||
})
|
||||
this.newService = ''
|
||||
this.showManager = false
|
||||
this.saveServices()
|
||||
},
|
||||
onSave({ key, msg }) {
|
||||
this.$set(this.services[key].options, msg.key, msg.value)
|
||||
this.saveServices()
|
||||
},
|
||||
onSaveAll({ key, msg }) {
|
||||
this.$set(this.services, key, {
|
||||
...this.services[key],
|
||||
options: msg
|
||||
})
|
||||
this.saveServices()
|
||||
},
|
||||
onMove({ key, msg }) {
|
||||
this.$set(this.services[key].position, msg.type, Math.max(1,
|
||||
(this.services[key].position[msg.type] || 1) + msg.direction
|
||||
))
|
||||
this.saveServices()
|
||||
},
|
||||
removeService(id) {
|
||||
this.services.splice(id, 1)
|
||||
this.saveServices()
|
||||
},
|
||||
saveServices() {
|
||||
localStorage.setItem(servicesStorage, JSON.stringify(this.services))
|
||||
},
|
||||
gridPos(id, position = {}) {
|
||||
//Helpers
|
||||
gridPos(position = {}) {
|
||||
return {
|
||||
'grid-row': `${position.x || 1} / span ${position.h || 2}`,
|
||||
'grid-column': `${position.y || id*2+1} / span ${position.w || 2}`
|
||||
'grid-column': `${position.y || 1} / span ${position.w || 2}`
|
||||
}
|
||||
},
|
||||
makeEmiter(key) {
|
||||
|
|
53
main.sass
53
main.sass
|
@ -57,33 +57,52 @@ input, select, button
|
|||
.error
|
||||
@include tile
|
||||
|
||||
#manager
|
||||
#content
|
||||
display: flex
|
||||
flex-direction: column
|
||||
height: 100vh
|
||||
|
||||
#showManager
|
||||
position: absolute
|
||||
bottom: 0
|
||||
|
||||
#manager
|
||||
background-color: $tileColor
|
||||
border-radius: $borderRadius
|
||||
padding-left: 1em
|
||||
height: 1.3em
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
|
||||
#layout-select
|
||||
display: flex
|
||||
|
||||
#services
|
||||
height: 100vh
|
||||
flex: 1
|
||||
overflow: hidden
|
||||
display: grid
|
||||
grid-gap: .2em
|
||||
grid-template-columns: repeat(8, minmax(0, 1fr))
|
||||
grid-template-rows: repeat(4, minmax(0, 1fr))
|
||||
justify-items: stretch
|
||||
& > div
|
||||
.tile
|
||||
overflow: auto
|
||||
display: flex
|
||||
flex-direction: column
|
||||
.service-header
|
||||
.title, .settings
|
||||
@include tile
|
||||
.title
|
||||
font-size: large
|
||||
text-align: center
|
||||
font-weight: bold
|
||||
.settings .position
|
||||
float: right
|
||||
width: 1.2em
|
||||
.service-content
|
||||
overflow: hidden
|
||||
& > div
|
||||
height: 100%
|
||||
display: flex
|
||||
flex-direction: column
|
||||
.service-header
|
||||
.title, .settings
|
||||
@include tile
|
||||
.title
|
||||
font-size: large
|
||||
text-align: center
|
||||
font-weight: bold
|
||||
.settings .position
|
||||
float: right
|
||||
width: 1.2em
|
||||
.service-content
|
||||
overflow: hidden
|
||||
.service-loader
|
||||
display: inline-block
|
||||
width: 64px
|
||||
|
|
Loading…
Reference in New Issue