Remove old scrap
This commit is contained in:
parent
cf1326ca59
commit
ddb4890c15
|
@ -1,2 +1,3 @@
|
||||||
*node_modules
|
*node_modules
|
||||||
*vscode
|
*vscode
|
||||||
|
config.json
|
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:vue/recommended"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"indent": [
|
|
||||||
"error",
|
|
||||||
2,
|
|
||||||
{
|
|
||||||
"SwitchCase": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"linebreak-style": [
|
|
||||||
"error",
|
|
||||||
"windows"
|
|
||||||
],
|
|
||||||
"quotes": [
|
|
||||||
"error",
|
|
||||||
"single"
|
|
||||||
],
|
|
||||||
"semi": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
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
|
@ -1,6 +0,0 @@
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// BROWSER COMPONENT ENTRY FILE
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
import Component from './src/__FILE__';
|
|
||||||
Vue.component(Component.name, Component);
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,74 +0,0 @@
|
||||||
{
|
|
||||||
"name": "vue-browser-component",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "A VueJS CLI template for compiling standalone components from .vue files",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"build": "webpack --env.file",
|
|
||||||
"serve": "webpack -d --watch --env.file",
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/RonnieSan/browser-components.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"vue",
|
|
||||||
"vuejs",
|
|
||||||
"browser",
|
|
||||||
"component"
|
|
||||||
],
|
|
||||||
"author": "RonnieSan",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/RonnieSan/browser-components/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/RonnieSan/browser-components#readme",
|
|
||||||
"devDependencies": {
|
|
||||||
"@babel/cli": "^7.2.0",
|
|
||||||
"@babel/core": "^7.2.2",
|
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
|
||||||
"@babel/preset-env": "^7.2.0",
|
|
||||||
"autoprefixer": "^8.3.0",
|
|
||||||
"babel-loader": "^8.0.5",
|
|
||||||
"chart.js": "^2.8.0",
|
|
||||||
"core-js": "^2.6.5",
|
|
||||||
"css-loader": "^0.28.11",
|
|
||||||
"eslint": "^5.16.0",
|
|
||||||
"eslint-plugin-vue": "^5.2.2",
|
|
||||||
"file-loader": "^1.1.11",
|
|
||||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
|
||||||
"less": "^3.0.2",
|
|
||||||
"less-loader": "^4.1.0",
|
|
||||||
"node-sass": "^4.11.0",
|
|
||||||
"optimize-css-assets-webpack-plugin": "^4.0.0",
|
|
||||||
"pug": "^2.0.3",
|
|
||||||
"pug-plain-loader": "^1.0.0",
|
|
||||||
"sass-loader": "^7.0.1",
|
|
||||||
"string-replace-webpack-plugin": "^0.1.3",
|
|
||||||
"url-loader": "^1.0.1",
|
|
||||||
"vue-chartjs": "^3.4.2",
|
|
||||||
"vue-loader": "^15.4.2",
|
|
||||||
"vue-style-loader": "^4.1.2",
|
|
||||||
"vue-template-compiler": "^2.6.10",
|
|
||||||
"webpack": "^4.6.0",
|
|
||||||
"webpack-cli": "^3.2.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"vue": "^2.6.10"
|
|
||||||
},
|
|
||||||
"babel": {
|
|
||||||
"presets": [
|
|
||||||
[
|
|
||||||
"@babel/preset-env",
|
|
||||||
{
|
|
||||||
"useBuiltIns": "usage"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"browserslist": [
|
|
||||||
"last 2 versions",
|
|
||||||
"ie >= 11"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
plugins: [
|
|
||||||
require('autoprefixer')
|
|
||||||
]
|
|
||||||
};
|
|
|
@ -1,27 +0,0 @@
|
||||||
export default {
|
|
||||||
last(list) {
|
|
||||||
return list[list.length - 1]
|
|
||||||
},
|
|
||||||
getIndex(list, where) {
|
|
||||||
for(let i = list.length - 1; i >= 0; i--)
|
|
||||||
if(where(list[i])) return i
|
|
||||||
|
|
||||||
return list.length
|
|
||||||
},
|
|
||||||
removeAt(list, id) {
|
|
||||||
list.splice(id, 1)
|
|
||||||
},
|
|
||||||
removeFirst(list, where) {
|
|
||||||
this.removeAt(list, this.getIndex(list, where))
|
|
||||||
},
|
|
||||||
pushAll(list, elems) {
|
|
||||||
list.push.apply(list, elems)
|
|
||||||
},
|
|
||||||
clear(list) {
|
|
||||||
list.splice(0, list.length)
|
|
||||||
},
|
|
||||||
for(list, action) {
|
|
||||||
for(let i = 0; i < list.length; i++)
|
|
||||||
action(list[i], i, list)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,60 +0,0 @@
|
||||||
<script>
|
|
||||||
import serviceEmiterVue from './serviceEmiter.vue'
|
|
||||||
|
|
||||||
import _Loadable from './loadable/Loadable.js'
|
|
||||||
import loadableBlockVue from './loadable/loadableBlock.vue'
|
|
||||||
import loadableInlineVue from './loadable/loadableInline.vue'
|
|
||||||
|
|
||||||
import serviceHeaderVue from './serviceHeader.vue'
|
|
||||||
|
|
||||||
import settingBooleanVue from './input/settingBoolean.vue'
|
|
||||||
import settingIntVue from './input/settingInt.vue'
|
|
||||||
import settingStringVue from './input/settingString.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
loadableBlock: loadableBlockVue,
|
|
||||||
loadableInline: loadableInlineVue,
|
|
||||||
|
|
||||||
serviceHeader: serviceHeaderVue,
|
|
||||||
|
|
||||||
settingBoolean: settingBooleanVue,
|
|
||||||
settingInt: settingIntVue,
|
|
||||||
settingString: settingStringVue
|
|
||||||
},
|
|
||||||
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
|
|
||||||
</script>
|
|
|
@ -1,42 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,66 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
span {{ timeSince }}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export const timedMixin = {
|
|
||||||
props: {
|
|
||||||
now: {
|
|
||||||
type: Number | Date,
|
|
||||||
default: Date.now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mixins: [ timedMixin ],
|
|
||||||
props: {
|
|
||||||
date: {
|
|
||||||
type: [Date, Number, String],
|
|
||||||
default: Date.now
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
timeSince() {
|
|
||||||
var seconds = Math.floor((this.now - new Date(this.date)) / 1000)
|
|
||||||
|
|
||||||
var interval = Math.floor(seconds / 31536000)
|
|
||||||
|
|
||||||
if (interval > 1) {
|
|
||||||
return interval + ' years'
|
|
||||||
}
|
|
||||||
interval = Math.floor(seconds / 2592000)
|
|
||||||
if (interval > 1) {
|
|
||||||
return interval + ' months'
|
|
||||||
}
|
|
||||||
interval = Math.floor(seconds / 86400)
|
|
||||||
if (interval > 1) {
|
|
||||||
return interval + ' days'
|
|
||||||
}
|
|
||||||
interval = Math.floor(seconds / 3600)
|
|
||||||
if (interval > 1) {
|
|
||||||
return interval + ' hours'
|
|
||||||
}
|
|
||||||
interval = Math.floor(seconds / 60)
|
|
||||||
if (interval > 1) {
|
|
||||||
return interval + ' minutes'
|
|
||||||
}
|
|
||||||
return Math.floor(seconds) + ' seconds'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const timerMinin = {
|
|
||||||
created() {
|
|
||||||
let self = this
|
|
||||||
setInterval(() => {
|
|
||||||
self.now = Date.now()
|
|
||||||
}, 30 * 1000)
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
now: Date.now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
sendChange(value) {
|
|
||||||
this.$emit('change', { key: this.id, value: value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,3 +0,0 @@
|
||||||
p.setting
|
|
||||||
label(:for="id") {{ title }}:
|
|
||||||
block input
|
|
|
@ -1,14 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
extends model
|
|
||||||
|
|
||||||
block input
|
|
||||||
input(:id="id" type="checkbox" :checked="value" @change.stop="sendChange($event.target.checked)")
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import baseSettingVue from './baseSetting.vue'
|
|
||||||
export default {
|
|
||||||
extends: baseSettingVue,
|
|
||||||
props: { value: Boolean }
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
extends model
|
|
||||||
|
|
||||||
block input
|
|
||||||
input(:id="id" type="number" step="1" :value="value" @keyup.enter="sendChange(parseInt($event.target.value))")
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import baseSettingVue from './baseSetting.vue'
|
|
||||||
export default {
|
|
||||||
extends: baseSettingVue,
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
extends model
|
|
||||||
|
|
||||||
block input
|
|
||||||
input(:id="id" type="text" :value="value" @keyup.enter="sendChange($event.target.value)")
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import baseSettingVue from './baseSetting.vue'
|
|
||||||
export default {
|
|
||||||
extends: baseSettingVue,
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,41 +0,0 @@
|
||||||
export default class {
|
|
||||||
constructor() {
|
|
||||||
this.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
get() {
|
|
||||||
return this.data
|
|
||||||
}
|
|
||||||
isSuccess() {
|
|
||||||
return this.loaded && this.error == undefined
|
|
||||||
}
|
|
||||||
display() {
|
|
||||||
return this.loaded ? (this.error || this.data) : 'Loading...'
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.loaded = false
|
|
||||||
this.data = undefined
|
|
||||||
this.error = undefined
|
|
||||||
}
|
|
||||||
success(data) {
|
|
||||||
this.loaded = true
|
|
||||||
this.data = data || {}
|
|
||||||
}
|
|
||||||
fail(error) {
|
|
||||||
this.loaded = true
|
|
||||||
this.error = error || 'Failed'
|
|
||||||
}
|
|
||||||
|
|
||||||
load(promise, then, reset = true) {
|
|
||||||
if(reset)
|
|
||||||
this.reset()
|
|
||||||
|
|
||||||
promise
|
|
||||||
.then(res => this.success(then(res)))
|
|
||||||
.catch(err => {
|
|
||||||
this.fail(err)
|
|
||||||
throw err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
div.loadable-block
|
|
||||||
slot(name="success" v-if="loadable.isSuccess()") {{ get }}
|
|
||||||
slot(name="error" v-else-if="loadable.error") {{ loadable.error }}
|
|
||||||
slot(name="loading" v-else)
|
|
||||||
.service-loader
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Loadable from './Loadable.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
loadable: Loadable
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
get() {
|
|
||||||
return this.loadable.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
span.loadable-inline
|
|
||||||
slot(name="success" v-if="loadable.isSuccess()") {{ get }}
|
|
||||||
slot(name="error" v-else-if="loadable.error") {{ loadable.error }}
|
|
||||||
slot(name="loading" v-else) Loading...
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Loadable from './Loadable.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
loadable: Loadable
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
get() {
|
|
||||||
return this.loadable.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,37 +0,0 @@
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
emit: {
|
|
||||||
type: Function,
|
|
||||||
default: undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods:{
|
|
||||||
emitError(err) {
|
|
||||||
this.emit('error', err)
|
|
||||||
},
|
|
||||||
saveOptions(options) {
|
|
||||||
this.emit('saveOptions', options)
|
|
||||||
},
|
|
||||||
saveOption(key, value) {
|
|
||||||
this.saveOptionCouple({
|
|
||||||
key: key, value: value
|
|
||||||
})
|
|
||||||
},
|
|
||||||
saveOptionCouple(couple) {
|
|
||||||
this.emit('saveOption', couple)
|
|
||||||
},
|
|
||||||
saveService(name, auth) {
|
|
||||||
this.emit('saveService', {
|
|
||||||
name: name, auth: auth
|
|
||||||
})
|
|
||||||
},
|
|
||||||
catchEmit(req) {
|
|
||||||
return req.catch(err => {
|
|
||||||
this.emitError(err)
|
|
||||||
throw err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,39 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
.service-header
|
|
||||||
.title(@click="showSettings = !showSettings")
|
|
||||||
slot(name="title")
|
|
||||||
.settings(v-show="showSettings")
|
|
||||||
input.position(
|
|
||||||
@keyup.up.ctrl.exact="onMove('x', -1)" @keyup.down.ctrl.exact="onMove('x', 1)"
|
|
||||||
@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>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
emit: {
|
|
||||||
type: Function,
|
|
||||||
default: undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() { return {
|
|
||||||
showSettings: false
|
|
||||||
} },
|
|
||||||
methods: {
|
|
||||||
onMove(type, direction) {
|
|
||||||
this.emit('move', { type: type, direction: direction })
|
|
||||||
},
|
|
||||||
onRemove() {
|
|
||||||
this.emit('remove', { })
|
|
||||||
},
|
|
||||||
onRemoveService() {
|
|
||||||
this.emit('removeService', { })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,97 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
.client(@scroll.passive="onScroll")
|
|
||||||
loadable-block.list(:loadable="guilds")
|
|
||||||
template(#success)
|
|
||||||
guild(v-for="guild in guilds.get()" :key="guild.id" :guild="guild" :showMedia="options.showMedia")
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/* global axios */
|
|
||||||
import { timerMinin } from '../core/fromNow.vue'
|
|
||||||
import serviceEmiterVue from '../core/serviceEmiter.vue'
|
|
||||||
|
|
||||||
import guildVue from './guild.vue'
|
|
||||||
import Loadable from '../core/loadable/Loadable'
|
|
||||||
import loadableBlockVue from '../core/loadable/loadableBlock.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
guild: guildVue,
|
|
||||||
loadableBlock: loadableBlockVue
|
|
||||||
},
|
|
||||||
extends: serviceEmiterVue,
|
|
||||||
mixins: [ timerMinin ],
|
|
||||||
props: {
|
|
||||||
auth: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
options: { type: Object, default: () => ({}) }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
rest: axios.create({
|
|
||||||
baseURL: 'https://discordapp.com/api/',
|
|
||||||
headers: { Authorization: this.auth.token },
|
|
||||||
timeout: this.options.timeout
|
|
||||||
}),
|
|
||||||
guilds: new Loadable()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.guilds.load(
|
|
||||||
this.get('/users/@me/guilds'),
|
|
||||||
res => res.data)
|
|
||||||
|
|
||||||
this.setupStream()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
get(path, 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) {
|
|
||||||
this.loadingOlder = true
|
|
||||||
this.catchEmit(this.rest.get("/timelines/home", { params: { limit: this.buffer,
|
|
||||||
max_id: this.statues[this.statues.length - 1].id
|
|
||||||
} })).then(res => {
|
|
||||||
this.statues.push.apply(this.statues, res.data)
|
|
||||||
this.loadingOlder = false
|
|
||||||
})
|
|
||||||
} else if(event.target.scrollTop < 20) {
|
|
||||||
this.statues.splice(this.buffer)
|
|
||||||
}*/
|
|
||||||
},
|
|
||||||
setupStream() {
|
|
||||||
/*const ws = new WebSocket(
|
|
||||||
`wss://${this.server}/api/v1/streaming?access_token=${this.token}&stream=user`
|
|
||||||
)
|
|
||||||
ws.onmessage = event => {
|
|
||||||
event = JSON.parse(event.data)
|
|
||||||
const payload = JSON.parse(event.payload)
|
|
||||||
switch (event.event) {
|
|
||||||
case "update":
|
|
||||||
this.statues.unshift(payload)
|
|
||||||
break
|
|
||||||
|
|
||||||
case "notification":
|
|
||||||
this.notifications.unshift(payload)
|
|
||||||
break
|
|
||||||
|
|
||||||
case "delete":
|
|
||||||
this.removeStatus(payload)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ws.onerror = this.emitError
|
|
||||||
ws.onclose = () => {
|
|
||||||
this.emitError(
|
|
||||||
"Mastodon stream disconnected !" +
|
|
||||||
(this.reconnect ? " Reconnecting..." : "")
|
|
||||||
)
|
|
||||||
if (this.reconnect) setTimeout(() => this.setupStream(), this.timeout)
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,27 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
.guild
|
|
||||||
| {{ guild.name }}
|
|
||||||
img(v-if="showMedia && guild.icon" :src="`${CDN}/icons/${guild.id}/${guild.icon}.png?size=16`")
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { CDN } from './tools.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
guild: {
|
|
||||||
type: Object,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
showMedia: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
CDN() {
|
|
||||||
return CDN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,54 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
.discord
|
|
||||||
service-header(:emit="emit")
|
|
||||||
template(#title) {{ serviceName }}: {{ account.display() }}
|
|
||||||
template(#settings)
|
|
||||||
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(:auth="auth" :options="params" :emit="emit")
|
|
||||||
template(#error)
|
|
||||||
form(@submit.prevent="makeAuth")
|
|
||||||
p
|
|
||||||
label(for="token") Token:
|
|
||||||
input#token(v-model="newAuth.token" required)
|
|
||||||
p
|
|
||||||
input(type="submit" value="Connect")
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/* global axios */
|
|
||||||
import accountServiceVue from '../core/accountService.vue'
|
|
||||||
|
|
||||||
import clientVue from './client.vue'
|
|
||||||
|
|
||||||
export default { //TODO: Use oauth
|
|
||||||
name: 'Discord',
|
|
||||||
components: { client: clientVue },
|
|
||||||
extends: accountServiceVue,
|
|
||||||
computed: {
|
|
||||||
params() {
|
|
||||||
return { timeout: 5000, reconnect: false, buffer: 20, showMedia: true, ...this.options }
|
|
||||||
},
|
|
||||||
isSetup() {
|
|
||||||
return this.auth && this.auth.token
|
|
||||||
},
|
|
||||||
serviceName() {
|
|
||||||
return 'Discord'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getAccount({ token }) {
|
|
||||||
return axios.get('https://discordapp.com/api/users/@me', {
|
|
||||||
headers: { Authorization: token },
|
|
||||||
timeout: this.params.timeout
|
|
||||||
})
|
|
||||||
},
|
|
||||||
mapAccount(res) {
|
|
||||||
return res.data.username
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1 +0,0 @@
|
||||||
export const CDN = 'https://cdn.discordapp.com'
|
|
|
@ -1,31 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
a.account(target="_blank" :href="account.url")
|
|
||||||
.avatar(v-if="showMedia" :style="avatarStyle(account.avatar_static)")
|
|
||||||
.name(v-html="parseEmojis(account.display_name, account.emojis)")
|
|
||||||
.acct @{{ account.acct }}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { parseEmojisMixin } from './tools'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mixins: [ parseEmojisMixin ],
|
|
||||||
props: {
|
|
||||||
account: {
|
|
||||||
type: Object,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
showMedia: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
avatarStyle(avatar) {
|
|
||||||
return {
|
|
||||||
'background-image': `url(${avatar})`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,146 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
.client(@scroll.passive="onScroll")
|
|
||||||
.statues
|
|
||||||
.header(v-if="hasNotifications") Accueil
|
|
||||||
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="options.showMedia" @mark="onStatusMark")
|
|
||||||
.status(v-show="statues.loadingMore")
|
|
||||||
.service-loader
|
|
||||||
.notifications(v-if="hasNotifications")
|
|
||||||
.header
|
|
||||||
| Notifications
|
|
||||||
span.date(@click.stop.prevent="onNotificationsClear") ❌
|
|
||||||
.list
|
|
||||||
notification(v-for="notification in notifications.get()" :key="notification.id" :notification="notification" :now="now"
|
|
||||||
:showMedia="options.showMedia" @dismiss="onNotificationDismiss" @mark="onStatusMark")
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/* global axios */
|
|
||||||
import { timerMinin } from '../core/fromNow.vue'
|
|
||||||
import serviceEmiterVue from '../core/serviceEmiter.vue'
|
|
||||||
|
|
||||||
import Lists from '../core/Lists'
|
|
||||||
|
|
||||||
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 {
|
|
||||||
components: {
|
|
||||||
status: statusVue,
|
|
||||||
notification: notificationVue,
|
|
||||||
loadableBlock: loadableBlockVue
|
|
||||||
},
|
|
||||||
extends: serviceEmiterVue,
|
|
||||||
mixins: [ timerMinin ],
|
|
||||||
props: {
|
|
||||||
auth: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
options: { type: Object, default: () => ({}) }
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
rest: axios.create({
|
|
||||||
baseURL: `https://${this.auth.server}/api/v1/`,
|
|
||||||
headers: { Authorization: 'Bearer ' + this.auth.token },
|
|
||||||
timeout: this.options.timeout
|
|
||||||
}),
|
|
||||||
statues: new ReLoadable(),
|
|
||||||
notifications: new Loadable()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
hasNotifications() {
|
|
||||||
return this.notifications.isSuccess() && this.notifications.get().length > 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.statues.load(
|
|
||||||
this.getTimeline({}),
|
|
||||||
res => res.data)
|
|
||||||
|
|
||||||
this.notifications.load(
|
|
||||||
this.get('/notifications'),
|
|
||||||
res => res.data)
|
|
||||||
|
|
||||||
this.setupStream()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
get(path, 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))
|
|
||||||
},
|
|
||||||
getTimeline(options) {
|
|
||||||
return this.get('/timelines/home', options)
|
|
||||||
},
|
|
||||||
onScroll(event) {
|
|
||||||
if(!this.statues.loadingMore && event.target.scrollHeight - event.target.clientHeight - event.target.scrollTop - 100 < 0) {
|
|
||||||
this.statues.loadMore(
|
|
||||||
this.getTimeline({ max_id: Lists.last(this.statues.get()).id }),
|
|
||||||
(res, statues) => Lists.pushAll(statues, res.data)
|
|
||||||
)
|
|
||||||
} else if(event.target.scrollTop < 20) {
|
|
||||||
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.options.reply) && (!status.reblog || this.options.reblog)
|
|
||||||
},
|
|
||||||
onStatusMark(action) {
|
|
||||||
this.post(`/statuses/${action.id}/${action.type}`)
|
|
||||||
.then(action.callback)
|
|
||||||
},
|
|
||||||
onNotificationDismiss(id) {
|
|
||||||
this.post('/notifications/dismiss', { id: id })
|
|
||||||
.then(() => this.removeById(this.notifications.get(), id))
|
|
||||||
},
|
|
||||||
onNotificationsClear() {
|
|
||||||
this.post('/notifications/clear')
|
|
||||||
.then(() => Lists.clear(this.notifications.get()))
|
|
||||||
},
|
|
||||||
setupStream() {
|
|
||||||
const ws = new WebSocket(
|
|
||||||
`wss://${this.auth.server}/api/v1/streaming?access_token=${this.auth.token}&stream=user`
|
|
||||||
)
|
|
||||||
ws.onmessage = event => {
|
|
||||||
event = JSON.parse(event.data)
|
|
||||||
const payload = JSON.parse(event.payload)
|
|
||||||
switch (event.event) {
|
|
||||||
case 'update':
|
|
||||||
this.statues.get().unshift(payload)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'notification':
|
|
||||||
this.notifications.get().unshift(payload)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'delete':
|
|
||||||
this.removeById(this.statues.get(), payload.id)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ws.onerror = this.emitError
|
|
||||||
ws.onclose = () => {
|
|
||||||
this.emitError(
|
|
||||||
'Mastodon stream disconnected !' +
|
|
||||||
(this.options.reconnect ? ' Reconnecting...' : '')
|
|
||||||
)
|
|
||||||
if (this.options.reconnect) setTimeout(() => this.setupStream(), this.options.timeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,66 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
.mastodon
|
|
||||||
service-header(:emit="emit")
|
|
||||||
template(#title)
|
|
||||||
| {{ serviceName }}:
|
|
||||||
loadable-inline(:loadable="account")
|
|
||||||
template(#success)
|
|
||||||
span(v-html="parseEmojis(account.data.display_name, account.data.emojis) + '@' + auth.server")
|
|
||||||
template(#settings)
|
|
||||||
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(:auth="auth" :options="params" :emit="emit")
|
|
||||||
template(#error)
|
|
||||||
form(@submit.prevent="makeAuth")
|
|
||||||
p
|
|
||||||
label(for="server") Server:
|
|
||||||
input#server(v-model="newAuth.server" required)
|
|
||||||
p
|
|
||||||
label(for="token") Token:
|
|
||||||
input#token(v-model="newAuth.token" required)
|
|
||||||
p
|
|
||||||
input(type="submit" value="Connect")
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/* global axios */
|
|
||||||
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: accountServiceVue,
|
|
||||||
mixins: [ parseEmojisMixin ],
|
|
||||||
computed: {
|
|
||||||
params() {
|
|
||||||
return { timeout: 5000, reconnect: false, buffer: 20, reblog: true, reply: false,
|
|
||||||
showMedia: true, ...this.options }
|
|
||||||
},
|
|
||||||
isSetup() {
|
|
||||||
return this.auth && this.auth.server && this.auth.token
|
|
||||||
},
|
|
||||||
serviceName() {
|
|
||||||
return 'Mastodon'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getAccount({ server, token }) {
|
|
||||||
return axios.get(`https://${server}/api/v1/accounts/verify_credentials`, {
|
|
||||||
headers: { Authorization: 'Bearer ' + token },
|
|
||||||
timeout: this.params.timeout
|
|
||||||
})
|
|
||||||
},
|
|
||||||
mapServiceName(res, { server }) {
|
|
||||||
return `${this.serviceName} ${this.mapAccount(res).acct}@${server}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,50 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
.notification
|
|
||||||
account(:account="notification.account" :showMedia="showMedia")
|
|
||||||
|
|
||||||
span.colored.text-icon.letter(v-if="notification.type == 'mention'") ✉
|
|
||||||
span.colored.text-icon.letter(v-if="notification.type == 'reblog'") ⟳
|
|
||||||
span.colored.text-icon.letter(v-if="notification.type == 'favourite'") ⚝
|
|
||||||
|
|
||||||
from-now.date(:date="notification.created_at" :now="now")
|
|
||||||
|
|
||||||
.content
|
|
||||||
template(v-if="notification.type == 'follow'") Vous suit
|
|
||||||
status.reblog(v-else-if="notification.status" :status="notification.status" :now="now"
|
|
||||||
:showMedia="showMedia" :withAccount="notification.type != 'mention'" @mark="passMark")
|
|
||||||
|
|
||||||
a.date(@click.stop.prevent="makeDismiss" style="margin-top: -1em") ❌
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import fromNowVue, { timedMixin } from '../core/fromNow.vue'
|
|
||||||
import accountVue from './account.vue'
|
|
||||||
import statusVue from './status.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
fromNow: fromNowVue,
|
|
||||||
account: accountVue,
|
|
||||||
status: statusVue
|
|
||||||
},
|
|
||||||
mixins: [ timedMixin ],
|
|
||||||
props: {
|
|
||||||
notification: {
|
|
||||||
type: Object,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
showMedia: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
makeDismiss() {
|
|
||||||
this.$emit('dismiss', this.notification.id)
|
|
||||||
},
|
|
||||||
passMark(action) {
|
|
||||||
this.$emit('mark', action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,91 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
.status
|
|
||||||
account(v-if="withAccount" :account="status.account" :showMedia="showMedia")
|
|
||||||
|
|
||||||
span.text-icon.letter(v-if="status.reblog") ⟳
|
|
||||||
|
|
||||||
a.date(target="_blank" :href="status.uri")
|
|
||||||
from-now(:date="status.created_at" :now="now")
|
|
||||||
|
|
||||||
.content(:class="{ avatared: showMedia }")
|
|
||||||
template(v-if="!status.reblog")
|
|
||||||
.spoiler(v-if="status.spoiler_text" @click.stop.prevent="status.sensitive = !status.sensitive").
|
|
||||||
{{ status.spoiler_text || 'Spoiler' }} {{ status.sensitive ? '→' : '↓' }}
|
|
||||||
div(v-if="!status.spoiler_text || !status.sensitive")
|
|
||||||
.text(v-html="parseEmojis(status.content, status.emojis)")
|
|
||||||
a.media(v-for="media in status.media_attachments" :href="media.url" target="_blank")
|
|
||||||
template(v-if="showMedia")
|
|
||||||
img(v-if="media.type == 'image' || media.type == 'gifv'" :src="media.preview_url" :alt="media.description" :title="media.description")
|
|
||||||
.gif(v-if="media.type == 'gifv'") GIF
|
|
||||||
template(v-else) Hidden media
|
|
||||||
status.reblog(v-else :status="status.reblog" :now="now" :showMedia="showMedia")
|
|
||||||
|
|
||||||
.meta(v-if="!status.reblog")
|
|
||||||
a.replies(@click.stop.prevent="makeReply(status)")
|
|
||||||
span.text-icon ✉
|
|
||||||
| {{ status.replies_count }}
|
|
||||||
a.reblogs(:class="{ colored: status.reblogged }" @click.stop.prevent="makeReblog(status)")
|
|
||||||
span.text-icon ⟳
|
|
||||||
| {{ status.reblogs_count }}
|
|
||||||
a.favourites(:class="{ colored: status.favourited }" @click.stop.prevent="makeFav(status)")
|
|
||||||
span.text-icon ⚝
|
|
||||||
| {{ status.favourites_count }}
|
|
||||||
a.fil(v-if="status.in_reply_to_id" @click.stop.prevent="showReply(status.in_reply_to_id)")
|
|
||||||
| Voir le fil
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import fromNowVue, { timedMixin } from '../core/fromNow.vue'
|
|
||||||
import { parseEmojisMixin } from './tools'
|
|
||||||
import accountVue from './account.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Status',
|
|
||||||
components: {
|
|
||||||
account: accountVue,
|
|
||||||
fromNow: fromNowVue
|
|
||||||
},
|
|
||||||
mixins: [ timedMixin, parseEmojisMixin ],
|
|
||||||
props: {
|
|
||||||
status: {
|
|
||||||
type: Object,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
withAccount: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
showMedia: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
showReply(statusId) {
|
|
||||||
console.error(statusId) //TODO:
|
|
||||||
},
|
|
||||||
makeReply(status) {
|
|
||||||
console.error(status.id) //TODO:
|
|
||||||
},
|
|
||||||
emitMark(status, action, callback, undo = false) {
|
|
||||||
this.$emit('mark', {
|
|
||||||
id: status.id,
|
|
||||||
type: (undo ? 'un' : '') + action,
|
|
||||||
callback: callback
|
|
||||||
})
|
|
||||||
},
|
|
||||||
makeReblog(status) {
|
|
||||||
this.emitMark(status, 'reblog', () => {
|
|
||||||
status.reblogs_count += (status.reblogged ? -1 : 1)
|
|
||||||
status.reblogged = !status.reblogged
|
|
||||||
}, status.reblogged)
|
|
||||||
},
|
|
||||||
makeFav(status) {
|
|
||||||
this.emitMark(status, 'favourite', () => {
|
|
||||||
status.favourites_count += (status.favourited ? -1 : 1)
|
|
||||||
status.favourited = !status.favourited
|
|
||||||
}, status.favourited)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,12 +0,0 @@
|
||||||
export const parseEmojisMixin = {
|
|
||||||
methods: {
|
|
||||||
parseEmojis(text, emojis) {
|
|
||||||
for (const emoji of emojis) {
|
|
||||||
text = text.split(`:${emoji.shortcode}:`).join(
|
|
||||||
`<img draggable="false" class="icon" alt="${emoji.shortcode}" title="${emoji.shortcode}" src="${emoji.static_url}">`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
.nextcloud-news(v-show="showService")
|
|
||||||
service-header(:emit="emit")
|
|
||||||
template(#title)
|
|
||||||
| {{ serviceName }}
|
|
||||||
span.note(v-if="hasNews") ({{ news.get().length }})
|
|
||||||
template(#settings)
|
|
||||||
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()")
|
|
||||||
a(:href="line.url" target="_blank")
|
|
||||||
from-now.date(:date="line.pubDate * 1000" :now="now")
|
|
||||||
span.read(@click.stop="makeRead(line.id)") 👁
|
|
||||||
span.title(@click.stop="line.open = !line.open") {{ line.author }} ─ {{ line.title }}
|
|
||||||
.content(v-if="line.open && line.body") {{ line.body }}
|
|
||||||
template(#error)
|
|
||||||
form(@submit.prevent="makeAuth")
|
|
||||||
p
|
|
||||||
label(for="server") Server:
|
|
||||||
input#server(v-model="newAuth.server" required)
|
|
||||||
p
|
|
||||||
label(for="username") Username:
|
|
||||||
input#username(v-model="newAuth.username" required)
|
|
||||||
p
|
|
||||||
label(for="token") Token:
|
|
||||||
input#token(v-model="newAuth.token" required)
|
|
||||||
p
|
|
||||||
input(type="submit" value="Connect")
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/* global axios */
|
|
||||||
import connectedServiceVue from '../core/connectedService.vue'
|
|
||||||
import fromNowVue, { timerMinin } from '../core/fromNow.vue'
|
|
||||||
|
|
||||||
import Loadable from '../core/loadable/Loadable'
|
|
||||||
import Lists from '../core/Lists'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'NextcloudNews',
|
|
||||||
components: {
|
|
||||||
fromNow: fromNowVue
|
|
||||||
},
|
|
||||||
extends: connectedServiceVue,
|
|
||||||
mixins: [ timerMinin ],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
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
|
|
||||||
},
|
|
||||||
showService() {
|
|
||||||
return this.params.showEmpty || this.hasNews || !this.isSetup
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadData() {
|
|
||||||
this.news.load(
|
|
||||||
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
|
|
||||||
})
|
|
||||||
)
|
|
||||||
},
|
|
||||||
removeNews(id) {
|
|
||||||
Lists.removeFirst(this.news.get(), n => n.id === id)
|
|
||||||
},
|
|
||||||
makeRead(id) {
|
|
||||||
this.catchEmit(this.rest.put(`/items/${id}/read`))
|
|
||||||
.then(() => this.removeNews(id))
|
|
||||||
},
|
|
||||||
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
|
|
||||||
|
|
||||||
this.loadData()
|
|
||||||
|
|
||||||
if(this.params.update > 0)
|
|
||||||
setInterval(this.loadData, this.params.update * 1000)
|
|
||||||
},
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,72 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Bar, mixins } from 'vue-chartjs'
|
|
||||||
import moment from 'moment'
|
|
||||||
const { reactiveProp } = mixins
|
|
||||||
|
|
||||||
export default {
|
|
||||||
extends: Bar,
|
|
||||||
mixins: [ reactiveProp ],
|
|
||||||
mounted () {
|
|
||||||
this.renderChart(this.chartData, {
|
|
||||||
responsive: true, maintainAspectRatio: false,
|
|
||||||
legend: {
|
|
||||||
labels: {
|
|
||||||
fontColor: 'white'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
xAxes: [{
|
|
||||||
type: 'time',
|
|
||||||
distribution: 'series',
|
|
||||||
ticks: {
|
|
||||||
fontColor: 'white',
|
|
||||||
source: 'data',
|
|
||||||
autoSkip: true,
|
|
||||||
maxRotation: 0,
|
|
||||||
autoSkipPadding: 5
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
displayFormats: {
|
|
||||||
hour: 'HH[h]'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
yAxes: [{
|
|
||||||
id: 'y-axis-temp',
|
|
||||||
display: true,
|
|
||||||
position: 'left',
|
|
||||||
ticks: {
|
|
||||||
fontColor: 'white'
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
id: 'y-axis-rain',
|
|
||||||
display: true,
|
|
||||||
position: 'right',
|
|
||||||
ticks: {
|
|
||||||
fontColor: 'white',
|
|
||||||
beginAtZero: true,
|
|
||||||
suggestedMax: 1
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
intersect: false,
|
|
||||||
callbacks: {
|
|
||||||
title: function(tooltipItem, myData) {
|
|
||||||
var item = myData.datasets[tooltipItem[0].datasetIndex].data[tooltipItem[0].index]
|
|
||||||
return moment(item.x || item.t).format('HH[h]')
|
|
||||||
},
|
|
||||||
label: function(tooltipItem, myData) {
|
|
||||||
var label = myData.datasets[tooltipItem.datasetIndex].label || ''
|
|
||||||
if (label) {
|
|
||||||
label += ': '
|
|
||||||
}
|
|
||||||
label += tooltipItem.value
|
|
||||||
return label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,166 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
.openweathermap
|
|
||||||
service-header(:emit="emit")
|
|
||||||
template(#title) {{ serviceName }}
|
|
||||||
template(#settings)
|
|
||||||
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")
|
|
||||||
template(#success)
|
|
||||||
.list
|
|
||||||
weather(v-for="(city, id) in weathers.get()" :key="id" :selected="selectedId == id"
|
|
||||||
:city="city" @select="makeSelect(id)" @remove="removeCity(id)")
|
|
||||||
input.weather(v-show="showAdd" placeholder="city id" @keyup.enter="addCity(parseInt($event.target.value))")
|
|
||||||
loadable-block(:loadable="forecast").forecast
|
|
||||||
template(#success)
|
|
||||||
chart.chart(:chartData="forecastChart")
|
|
||||||
template(#error)
|
|
||||||
form(@submit.prevent="makeAuth")
|
|
||||||
p
|
|
||||||
label(for="token") Token:
|
|
||||||
input#token(v-model="newAuth.token" required)
|
|
||||||
p
|
|
||||||
input(type="submit" value="Connect")
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/* global axios */
|
|
||||||
import connectedServiceVue from '../core/connectedService.vue'
|
|
||||||
import Lists from '../core/Lists.js'
|
|
||||||
import Loadable from '../core/loadable/Loadable'
|
|
||||||
|
|
||||||
import chartVue from './chart.vue'
|
|
||||||
import weatherVue from './weather.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Openweathermap',
|
|
||||||
components: {
|
|
||||||
weather: weatherVue,
|
|
||||||
chart: chartVue
|
|
||||||
},
|
|
||||||
extends: connectedServiceVue,
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
rest: undefined, //NOTE: set in this.init()
|
|
||||||
weathers: new Loadable(),
|
|
||||||
forecast: new Loadable(),
|
|
||||||
selectedId: 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',
|
|
||||||
label: 'Temperature',
|
|
||||||
yAxisID: 'y-axis-temp',
|
|
||||||
borderColor: 'white',
|
|
||||||
borderWidth: 1,
|
|
||||||
fill: false,
|
|
||||||
data: this.forecast.get().map(function (line) { return {
|
|
||||||
x: line.dt * 1000, y: line.main.temp
|
|
||||||
} })
|
|
||||||
},{
|
|
||||||
type: 'bar',
|
|
||||||
label: 'Percipitation',
|
|
||||||
yAxisID: 'y-axis-rain',
|
|
||||||
borderColor: '#DDDDDD',
|
|
||||||
backgroundColor: '#DDDDDD33',
|
|
||||||
borderWidth: 1,
|
|
||||||
data: this.forecast.get().filter(f => 'rain' in f && '3h' in f.rain).map(function (line) { return {
|
|
||||||
x: line.dt * 1000, y: line.rain['3h']
|
|
||||||
} })
|
|
||||||
}]
|
|
||||||
} },
|
|
||||||
selected() {
|
|
||||||
return this.weathers.isSuccess() ? this.weathers.get()[this.selectedId] : null
|
|
||||||
},
|
|
||||||
hasWeathers() {
|
|
||||||
return this.weathers.isSuccess() && this.weathers.get().length > 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.$watch('options.cities', this.init)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
makeSelect(id) {
|
|
||||||
this.selectedId = id
|
|
||||||
this.loadForecast()
|
|
||||||
},
|
|
||||||
updateData() {
|
|
||||||
Lists.for(this.weathers.get(),
|
|
||||||
(weather, i) => this.getWeather({ id: weather.id })
|
|
||||||
.then(res => this.$set(this.weathers.get(), i, res.data))
|
|
||||||
)
|
|
||||||
this.loadForecast()
|
|
||||||
},
|
|
||||||
getWeather(params) {
|
|
||||||
return this.catchEmit(this.rest.get('weather', { params: params }))
|
|
||||||
},
|
|
||||||
loadForecast() {
|
|
||||||
if(this.selected) {
|
|
||||||
this.forecast.load(
|
|
||||||
this.catchEmit(this.rest.get('forecast', { params: {
|
|
||||||
id: this.selected.id, cnt: this.params.forecastLimit
|
|
||||||
}})),
|
|
||||||
res => res.data.list
|
|
||||||
)
|
|
||||||
} else this.forecast.fail('Any selection')
|
|
||||||
},
|
|
||||||
formatDate(dt) {
|
|
||||||
const date = new Date(dt * 1000)
|
|
||||||
return `${date.toLocaleDateString()} ${date.getHours()}h`
|
|
||||||
},
|
|
||||||
addCity(id) {
|
|
||||||
this.params.cities.push({ id: id })
|
|
||||||
this.saveOption('cities', this.params.cities)
|
|
||||||
},
|
|
||||||
removeCity(key) {
|
|
||||||
Lists.removeAt(this.params.cities, key)
|
|
||||||
this.saveOption('cities', this.params.cities)
|
|
||||||
},
|
|
||||||
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.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([])
|
|
||||||
},
|
|
||||||
checkAuth({ token }) {
|
|
||||||
return axios.get('https://api.openweathermap.org/data/2.5/weather', {
|
|
||||||
params: { q: 'London', appid: token },
|
|
||||||
timeout: this.params.timeout
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,25 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
.weather(:class="{ selected: selected }" @click.stop.prevent="$emit('select')")
|
|
||||||
.main(v-for="main in city.weather")
|
|
||||||
p {{ main.description }}
|
|
||||||
.ic
|
|
||||||
img(:src="`https://openweathermap.org/img/w/${main.icon}.png`" :alt="main.main")
|
|
||||||
span.remove(@click.stop.prevent="$emit('remove')") ❌
|
|
||||||
.header
|
|
||||||
| {{ city.name }}
|
|
||||||
img.icon(:src="`https://openweathermap.org/images/flags/${city.sys.country.toLowerCase()}.png`" :alt="city.sys.country" :title="city.sys.country")
|
|
||||||
.data
|
|
||||||
| {{ city.main.temp }}°C ─ {{ city.main.humidity }}%
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
city: {
|
|
||||||
type: Object,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
selected: Boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,127 +0,0 @@
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// WEBPACK CONFIGURATION
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// INSTRUCTIONS
|
|
||||||
// webpack --env.file="./path/to/file" --relative to the src folder
|
|
||||||
|
|
||||||
// Import dependencies
|
|
||||||
const path = require('path');
|
|
||||||
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin');
|
|
||||||
const StringReplacePlugin = require('string-replace-webpack-plugin');
|
|
||||||
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
|
||||||
|
|
||||||
function resolve(dir) {
|
|
||||||
return path.resolve(__dirname, dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = (env) => {
|
|
||||||
// Get the basename from the filepath
|
|
||||||
const filename = path.basename(env.file, '.vue');
|
|
||||||
const filepath = path.dirname(env.file);
|
|
||||||
|
|
||||||
return {
|
|
||||||
mode : 'production',
|
|
||||||
entry : {
|
|
||||||
[filename] : './entry.js'
|
|
||||||
},
|
|
||||||
performance: {
|
|
||||||
maxEntrypointSize: 512000,
|
|
||||||
maxAssetSize: 512000
|
|
||||||
},
|
|
||||||
output : {
|
|
||||||
filename : '[name].js',
|
|
||||||
path : path.resolve(__dirname, 'dist', filepath)
|
|
||||||
},
|
|
||||||
resolve : {
|
|
||||||
extensions : ['.vue', '.js'],
|
|
||||||
alias : {
|
|
||||||
'vue$' : resolve('node_modules/vue/dist/vue.min.js'),
|
|
||||||
'@' : resolve('src')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
externals : {
|
|
||||||
vue : 'Vue',
|
|
||||||
lodash : 'lodash'
|
|
||||||
},
|
|
||||||
module : {
|
|
||||||
rules : [
|
|
||||||
{
|
|
||||||
test : /entry\.js$/,
|
|
||||||
loader : StringReplacePlugin.replace({
|
|
||||||
replacements: [
|
|
||||||
{
|
|
||||||
pattern: /__FILE__/ig,
|
|
||||||
replacement: function (match, p1, offset, string) {
|
|
||||||
return env.file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test : /\.vue$/,
|
|
||||||
loader : 'vue-loader'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test : /\.js$/,
|
|
||||||
loader : 'babel-loader',
|
|
||||||
include : [
|
|
||||||
resolve('src')
|
|
||||||
],
|
|
||||||
exclude: file => (
|
|
||||||
/node_modules/.test(file) &&
|
|
||||||
!/\.vue\.js/.test(file)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test : /\.css$/,
|
|
||||||
use : [
|
|
||||||
'vue-style-loader',
|
|
||||||
'css-loader'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test : /\.less$/,
|
|
||||||
use : [
|
|
||||||
'vue-style-loader',
|
|
||||||
'css-loader',
|
|
||||||
'less-loader'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test : /\.scss$/,
|
|
||||||
use : [
|
|
||||||
'vue-style-loader',
|
|
||||||
'css-loader',
|
|
||||||
'sass-loader'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test : /\.sass$/,
|
|
||||||
use : [
|
|
||||||
'vue-style-loader',
|
|
||||||
'css-loader',
|
|
||||||
{
|
|
||||||
loader : 'sass-loader',
|
|
||||||
options : {
|
|
||||||
indentedSyntax : true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.pug$/,
|
|
||||||
loader: 'pug-plain-loader'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins : [
|
|
||||||
new VueLoaderPlugin(),
|
|
||||||
new OptimizeCSSPlugin({
|
|
||||||
cssProcessorOptions: {
|
|
||||||
safe: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]
|
|
||||||
};
|
|
||||||
};
|
|
57
index.html
57
index.html
|
@ -1,57 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<title>Mixit</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="main.css">
|
|
||||||
|
|
||||||
<script src="lib/axios.js"></script>
|
|
||||||
<script src="lib/vue.full.js"></script>
|
|
||||||
|
|
||||||
<script src="compiler/dist/mastodon/main.js"></script>
|
|
||||||
<script src="compiler/dist/openweathermap/main.js"></script>
|
|
||||||
<script src="compiler/dist/nextcloud-news/main.js"></script>
|
|
||||||
<script src="compiler/dist/discord/main.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>Mixit needs javascript enabled to work correctly. Sorry</noscript>
|
|
||||||
<div id="app">
|
|
||||||
<div id="errors" v-show="errors">
|
|
||||||
<p class="error" v-for="(error, key) in errors" @click="removeError(key)">{{ error }}</p>
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
1602
lib/axios.js
1602
lib/axios.js
File diff suppressed because it is too large
Load Diff
10947
lib/vue.full.js
10947
lib/vue.full.js
File diff suppressed because it is too large
Load Diff
426
main.css
426
main.css
|
@ -1,426 +0,0 @@
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: Verdana, Geneva, sans-serif;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: #333;
|
|
||||||
color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
input, select, button {
|
|
||||||
background-color: #333;
|
|
||||||
color: #eee;
|
|
||||||
border: 1px solid #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-icon {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note {
|
|
||||||
font-size: .7em;
|
|
||||||
vertical-align: text-top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.letter {
|
|
||||||
margin: 0 .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.colored {
|
|
||||||
color: orange;
|
|
||||||
}
|
|
||||||
|
|
||||||
#errors {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
#errors .error {
|
|
||||||
margin: 0.3em;
|
|
||||||
background-color: #222;
|
|
||||||
border-radius: 0.3em;
|
|
||||||
padding: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#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 {
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-ms-flex: 1;
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
display: -ms-grid;
|
|
||||||
display: grid;
|
|
||||||
grid-gap: .2em;
|
|
||||||
-ms-grid-columns: (minmax(0, 1fr))[8];
|
|
||||||
grid-template-columns: repeat(8, minmax(0, 1fr));
|
|
||||||
-ms-grid-rows: (minmax(0, 1fr))[4];
|
|
||||||
grid-template-rows: repeat(4, minmax(0, 1fr));
|
|
||||||
justify-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
#services .tile {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#services .tile > div {
|
|
||||||
height: 100%;
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: flex;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-webkit-box-direction: normal;
|
|
||||||
-ms-flex-direction: column;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#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 .tile > div .service-header .title {
|
|
||||||
font-size: large;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#services .tile > div .service-header .settings .position {
|
|
||||||
float: right;
|
|
||||||
width: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#services .tile > div .service-content {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#services .service-loader {
|
|
||||||
display: inline-block;
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#services .service-loader:after {
|
|
||||||
content: " ";
|
|
||||||
display: block;
|
|
||||||
width: 46px;
|
|
||||||
height: 46px;
|
|
||||||
margin: 1px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 5px solid #aaa;
|
|
||||||
border-color: #aaa transparent #aaa transparent;
|
|
||||||
-webkit-animation: service-loader 1.2s linear infinite;
|
|
||||||
animation: service-loader 1.2s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes service-loader {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: rotate(0deg);
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: rotate(360deg);
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes service-loader {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: rotate(0deg);
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: rotate(360deg);
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client {
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .header {
|
|
||||||
margin: 0.3em;
|
|
||||||
background-color: #222;
|
|
||||||
border-radius: 0.3em;
|
|
||||||
padding: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .list > div {
|
|
||||||
margin: 0.3em;
|
|
||||||
background-color: #222;
|
|
||||||
border-radius: 0.3em;
|
|
||||||
padding: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .statues {
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-ms-flex: 1;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .notifications {
|
|
||||||
max-width: 33%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .account .name {
|
|
||||||
margin: 0 0.3em;
|
|
||||||
color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .account .avatar {
|
|
||||||
float: left;
|
|
||||||
border-radius: 0.3em;
|
|
||||||
width: 3em;
|
|
||||||
height: 3em;
|
|
||||||
background-size: 3em 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .account div {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .status, .mastodon .client .notification {
|
|
||||||
min-height: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .status .content, .mastodon .client .notification .content {
|
|
||||||
margin: .5em .5em .5em 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .status .content.avatared, .mastodon .client .notification .content.avatared {
|
|
||||||
margin-left: 3.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .status .content .reblog, .mastodon .client .notification .content .reblog {
|
|
||||||
font-size: .8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .status .content .spoiler, .mastodon .client .notification .content .spoiler {
|
|
||||||
margin-bottom: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .status .content .media, .mastodon .client .notification .content .media {
|
|
||||||
margin: .5em;
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .status .content .media > *, .mastodon .client .notification .content .media > * {
|
|
||||||
max-height: 10em;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .status .content .media .gif, .mastodon .client .notification .content .media .gif {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #00000044;
|
|
||||||
color: white;
|
|
||||||
padding: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .status .meta, .mastodon .client .notification .meta {
|
|
||||||
margin-left: 4em;
|
|
||||||
font-size: .8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .status .meta a, .mastodon .client .notification .meta a {
|
|
||||||
margin: 0 .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mastodon .client .date, .mastodon .client .dismiss {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .loadable-block {
|
|
||||||
overflow: hidden;
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: flex;
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-ms-flex: 1;
|
|
||||||
flex: 1;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-webkit-box-direction: normal;
|
|
||||||
-ms-flex-direction: column;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .list {
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: flex;
|
|
||||||
-ms-flex-wrap: wrap;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .weather, .openweathermap .forecast {
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-ms-flex: 1;
|
|
||||||
flex: 1;
|
|
||||||
margin: 0.3em;
|
|
||||||
background-color: #222;
|
|
||||||
border-radius: 0.3em;
|
|
||||||
padding: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .forecast {
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-ms-flex: 1;
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .forecast .chart {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .weather {
|
|
||||||
min-width: 17em;
|
|
||||||
border: 1px solid #222;
|
|
||||||
display: -ms-grid;
|
|
||||||
display: grid;
|
|
||||||
-ms-grid-columns: auto auto;
|
|
||||||
grid-template-columns: auto auto;
|
|
||||||
-ms-grid-rows: 1.2em auto;
|
|
||||||
grid-template-rows: 1.2em auto;
|
|
||||||
grid-template-areas: "header main" "data remove";
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .weather.selected {
|
|
||||||
border-color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .weather .header {
|
|
||||||
-ms-grid-row: 1;
|
|
||||||
-ms-grid-column: 1;
|
|
||||||
grid-area: header;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .weather .data {
|
|
||||||
-ms-grid-row: 2;
|
|
||||||
-ms-grid-column: 1;
|
|
||||||
grid-area: data;
|
|
||||||
margin-top: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .weather .main {
|
|
||||||
-ms-grid-row: 1;
|
|
||||||
-ms-grid-column: 2;
|
|
||||||
grid-area: main;
|
|
||||||
-ms-grid-column-align: right;
|
|
||||||
justify-self: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .weather .main p {
|
|
||||||
margin: 0.3em;
|
|
||||||
display: inline;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .weather .remove {
|
|
||||||
-ms-grid-row: 2;
|
|
||||||
-ms-grid-column: 2;
|
|
||||||
grid-area: remove;
|
|
||||||
-ms-grid-column-align: right;
|
|
||||||
justify-self: right;
|
|
||||||
-ms-flex-item-align: end;
|
|
||||||
-ms-grid-row-align: end;
|
|
||||||
align-self: end;
|
|
||||||
font-size: .8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .ic {
|
|
||||||
overflow: hidden;
|
|
||||||
height: 30px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openweathermap .ic img {
|
|
||||||
margin-top: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nextcloud-news .unreaded {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nextcloud-news .news {
|
|
||||||
margin: 0.3em;
|
|
||||||
background-color: #222;
|
|
||||||
border-radius: 0.3em;
|
|
||||||
padding: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nextcloud-news .news .date {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nextcloud-news .news .read {
|
|
||||||
margin-right: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nextcloud-news .news .content {
|
|
||||||
padding: 0.3em;
|
|
||||||
}
|
|
||||||
/*# sourceMappingURL=main.css.map */
|
|
File diff suppressed because one or more lines are too long
171
main.js
171
main.js
|
@ -1,171 +0,0 @@
|
||||||
/* 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: '',
|
|
||||||
|
|
||||||
errors: [],
|
|
||||||
bus: new Vue()
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.layouts.load()
|
|
||||||
this.services.load()
|
|
||||||
|
|
||||||
this.bus.$on('error', this.onError)
|
|
||||||
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())
|
|
||||||
},
|
|
||||||
addError(err) {
|
|
||||||
this.errors.push(err)
|
|
||||||
},
|
|
||||||
removeError(id) {
|
|
||||||
this.errors.splice(id, 1)
|
|
||||||
},
|
|
||||||
|
|
||||||
//Helpers
|
|
||||||
gridPos(position = {}) {
|
|
||||||
return {
|
|
||||||
'grid-row': `${position.x || 1} / span ${position.h || 2}`,
|
|
||||||
'grid-column': `${position.y || 1} / span ${position.w || 2}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
makeEmiter(key) {
|
|
||||||
const self = this
|
|
||||||
return function(name, msg) {
|
|
||||||
self.bus.$emit(name, { msg: msg, key: key })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
254
main.sass
254
main.sass
|
@ -1,254 +0,0 @@
|
||||||
$avatarSize: 3em
|
|
||||||
$borderRadius: .3em
|
|
||||||
|
|
||||||
$backColor: #333
|
|
||||||
$tileColor: #222
|
|
||||||
$darkColor: #111
|
|
||||||
$halfColor: #999
|
|
||||||
$noneColor: #aaa
|
|
||||||
$foreColor: #eee
|
|
||||||
|
|
||||||
@mixin tile
|
|
||||||
margin: $borderRadius
|
|
||||||
background-color: $tileColor
|
|
||||||
border-radius: $borderRadius
|
|
||||||
padding: $borderRadius
|
|
||||||
|
|
||||||
*
|
|
||||||
margin: 0
|
|
||||||
padding: 0
|
|
||||||
font-family: Verdana, Geneva, sans-serif
|
|
||||||
scrollbar-width: thin
|
|
||||||
|
|
||||||
body
|
|
||||||
background-color: $backColor
|
|
||||||
color: $foreColor
|
|
||||||
|
|
||||||
a
|
|
||||||
text-decoration: none
|
|
||||||
color: $noneColor
|
|
||||||
|
|
||||||
input, select, button
|
|
||||||
background-color: $backColor
|
|
||||||
color: $foreColor
|
|
||||||
border: 1px solid $halfColor
|
|
||||||
|
|
||||||
.icon
|
|
||||||
width: 1em
|
|
||||||
height: 1em
|
|
||||||
vertical-align: middle
|
|
||||||
|
|
||||||
.text-icon
|
|
||||||
font-weight: bold
|
|
||||||
font-size: 1.2em
|
|
||||||
|
|
||||||
.note
|
|
||||||
font-size: .7em
|
|
||||||
vertical-align: text-top
|
|
||||||
|
|
||||||
.letter
|
|
||||||
margin: 0 .5em
|
|
||||||
|
|
||||||
.colored
|
|
||||||
color: orange
|
|
||||||
|
|
||||||
#errors
|
|
||||||
position: absolute
|
|
||||||
.error
|
|
||||||
@include tile
|
|
||||||
|
|
||||||
#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
|
|
||||||
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
|
|
||||||
.tile
|
|
||||||
overflow: auto
|
|
||||||
& > 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
|
|
||||||
height: 64px
|
|
||||||
&:after
|
|
||||||
content: " "
|
|
||||||
display: block
|
|
||||||
width: 46px
|
|
||||||
height: 46px
|
|
||||||
margin: 1px
|
|
||||||
border-radius: 50%
|
|
||||||
border: 5px solid $noneColor
|
|
||||||
border-color: $noneColor transparent $noneColor transparent
|
|
||||||
animation: service-loader 1.2s linear infinite
|
|
||||||
@keyframes service-loader
|
|
||||||
0%
|
|
||||||
transform: rotate(0deg)
|
|
||||||
100%
|
|
||||||
transform: rotate(360deg)
|
|
||||||
|
|
||||||
.mastodon
|
|
||||||
.client
|
|
||||||
display: flex
|
|
||||||
height: 100%
|
|
||||||
overflow-y: auto
|
|
||||||
.header
|
|
||||||
@include tile
|
|
||||||
.list
|
|
||||||
& > div
|
|
||||||
@include tile
|
|
||||||
.statues
|
|
||||||
flex: 1
|
|
||||||
.notifications
|
|
||||||
max-width: 33%
|
|
||||||
|
|
||||||
.account
|
|
||||||
.name
|
|
||||||
margin: 0 $borderRadius
|
|
||||||
color: $foreColor
|
|
||||||
.avatar
|
|
||||||
float: left
|
|
||||||
border-radius: $borderRadius
|
|
||||||
width: $avatarSize
|
|
||||||
height: $avatarSize
|
|
||||||
background-size: $avatarSize $avatarSize
|
|
||||||
div
|
|
||||||
display: inline-block
|
|
||||||
|
|
||||||
.status, .notification
|
|
||||||
min-height: $avatarSize
|
|
||||||
.content
|
|
||||||
margin: .5em .5em .5em 1em
|
|
||||||
&.avatared
|
|
||||||
margin-left: .5em + $avatarSize
|
|
||||||
.reblog
|
|
||||||
font-size: .8em
|
|
||||||
.spoiler
|
|
||||||
margin-bottom: .5em
|
|
||||||
.media
|
|
||||||
margin: .5em
|
|
||||||
position: relative
|
|
||||||
display: inline-block
|
|
||||||
& > *
|
|
||||||
max-height: 10em
|
|
||||||
max-width: 100%
|
|
||||||
.gif
|
|
||||||
position: absolute
|
|
||||||
top: 0
|
|
||||||
bottom: 0
|
|
||||||
left: 0
|
|
||||||
right: 0
|
|
||||||
height: 100%
|
|
||||||
width: 100%
|
|
||||||
background-color: #00000044
|
|
||||||
color: white
|
|
||||||
padding: .5em
|
|
||||||
.meta
|
|
||||||
margin-left: 1em + $avatarSize
|
|
||||||
font-size: .8em
|
|
||||||
a
|
|
||||||
margin: 0 .5em
|
|
||||||
|
|
||||||
.date, .dismiss
|
|
||||||
float: right
|
|
||||||
|
|
||||||
.openweathermap
|
|
||||||
.loadable-block
|
|
||||||
overflow: hidden
|
|
||||||
display: flex
|
|
||||||
flex: 1
|
|
||||||
flex-direction: column
|
|
||||||
.list
|
|
||||||
display: flex
|
|
||||||
flex-wrap: wrap
|
|
||||||
.weather, .forecast
|
|
||||||
flex: 1
|
|
||||||
@include tile
|
|
||||||
.forecast
|
|
||||||
flex: 1
|
|
||||||
overflow: hidden
|
|
||||||
height: 100%
|
|
||||||
.chart
|
|
||||||
position: relative
|
|
||||||
height: 100%
|
|
||||||
.weather
|
|
||||||
min-width: 17em
|
|
||||||
border: 1px solid $tileColor
|
|
||||||
display: grid
|
|
||||||
grid-template-columns: auto auto
|
|
||||||
grid-template-rows: 1.2em auto
|
|
||||||
grid-template-areas: "header main" "data remove"
|
|
||||||
&.selected
|
|
||||||
border-color: $halfColor
|
|
||||||
.header
|
|
||||||
grid-area: header
|
|
||||||
font-size: 1.2em
|
|
||||||
.data
|
|
||||||
grid-area: data
|
|
||||||
margin-top: .5em
|
|
||||||
.main
|
|
||||||
grid-area: main
|
|
||||||
justify-self: right
|
|
||||||
p
|
|
||||||
margin: $borderRadius
|
|
||||||
display: inline
|
|
||||||
vertical-align: top
|
|
||||||
.remove
|
|
||||||
grid-area: remove
|
|
||||||
justify-self: right
|
|
||||||
align-self: end
|
|
||||||
font-size: .8em
|
|
||||||
.ic
|
|
||||||
overflow: hidden
|
|
||||||
height: 30px
|
|
||||||
display: inline-block
|
|
||||||
img
|
|
||||||
margin-top: -10px
|
|
||||||
|
|
||||||
.nextcloud-news
|
|
||||||
.unreaded
|
|
||||||
overflow-y: auto
|
|
||||||
.news
|
|
||||||
@include tile
|
|
||||||
.date
|
|
||||||
float: right
|
|
||||||
.read
|
|
||||||
margin-right: .5em
|
|
||||||
.content
|
|
||||||
padding: $borderRadius
|
|
|
@ -4680,12 +4680,14 @@
|
||||||
"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"
|
||||||
|
@ -4700,17 +4702,20 @@
|
||||||
"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",
|
||||||
|
@ -4827,7 +4832,8 @@
|
||||||
"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",
|
||||||
|
@ -4839,6 +4845,7 @@
|
||||||
"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"
|
||||||
}
|
}
|
||||||
|
@ -4853,6 +4860,7 @@
|
||||||
"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"
|
||||||
}
|
}
|
||||||
|
@ -4860,12 +4868,14 @@
|
||||||
"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"
|
||||||
|
@ -4884,6 +4894,7 @@
|
||||||
"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"
|
||||||
}
|
}
|
||||||
|
@ -4964,7 +4975,8 @@
|
||||||
"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",
|
||||||
|
@ -4976,6 +4988,7 @@
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
|
@ -5097,6 +5110,7 @@
|
||||||
"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",
|
||||||
|
|
Loading…
Reference in New Issue