Abandon
This commit is contained in:
parent
74935ced6c
commit
ab895a7d02
174
src/App.vue
174
src/App.vue
|
@ -31,75 +31,35 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from 'vue-property-decorator'
|
import { Component, Vue } from 'vue-property-decorator'
|
||||||
import { Selectable } from './helpers/lists/Selectable'
|
import { Selectable } from './helpers/lists/Selectable'
|
||||||
import LocalStorageHandler from './helpers/storage/LocalStorageHandler'
|
|
||||||
import Discord from './services/discord/Discord.vue'
|
import Discord from './services/discord/Discord.vue'
|
||||||
import Mastodon from './services/mastodon/Mastodon.vue'
|
import Mastodon from './services/mastodon/Mastodon.vue'
|
||||||
import NextcloudNews from './services/nextcloud/NextcloudNews.vue'
|
import NextcloudNews from './services/nextcloud/NextcloudNews.vue'
|
||||||
import OpenWeatherMap from './services/openweathermap/OpenWeatherMap.vue'
|
import OpenWeatherMap from './services/openweathermap/OpenWeatherMap.vue'
|
||||||
|
import { ErrorsModule, ServicesModule, LayoutsModule } from './store'
|
||||||
import { Auth, Layout, Rect, Service, serviceKey, tileKey } from './types/App'
|
import { Auth, Layout, Rect, Service, serviceKey, tileKey } from './types/App'
|
||||||
import * as Events from './types/Events'
|
|
||||||
|
|
||||||
const layoutsStorage = 'layouts'
|
|
||||||
const servicesStorage = 'services'
|
|
||||||
|
|
||||||
function saveAuth(auth: Auth) {
|
|
||||||
const res: any = {}
|
|
||||||
for (const entry of auth.entries()) {
|
|
||||||
res[entry[0]] = entry[1]
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({ components: { Mastodon, NextcloudNews, openweathermap: OpenWeatherMap, Discord } })
|
@Component({ components: { Mastodon, NextcloudNews, openweathermap: OpenWeatherMap, Discord } })
|
||||||
export default class App extends Vue {
|
export default class App extends Vue {
|
||||||
showManager = false
|
showManager = false
|
||||||
|
|
||||||
layouts = new LocalStorageHandler(layoutsStorage,
|
|
||||||
new Selectable<Layout>([{ name: 'main', tiles: [] }]),
|
|
||||||
data => new Selectable<Layout>(data), l => l.data)
|
|
||||||
|
|
||||||
services = new LocalStorageHandler<Service[]>(servicesStorage, [],
|
|
||||||
(data: any[]) => data.map(s => ({ ...s, auth: new Auth(Object.entries(s.auth)) })),
|
|
||||||
data => data.map(s => ({ ...s, auth: saveAuth(s.auth) })))
|
|
||||||
newService = ''
|
newService = ''
|
||||||
|
|
||||||
errors: string[] = []
|
|
||||||
bus = new Vue()
|
|
||||||
|
|
||||||
get managerButton() {
|
get managerButton() {
|
||||||
return this.showManager ? '▼' : '▲'
|
return this.showManager ? '▼' : '▲'
|
||||||
}
|
}
|
||||||
|
|
||||||
get tiles() {
|
get tiles() {
|
||||||
const layout = this.layouts.get().selected
|
return LayoutsModule.tiles.map((tile, key: tileKey) => {
|
||||||
if(layout) {
|
const service = ServicesModule.get(tile.service)
|
||||||
return layout.tiles.map((tile, key: tileKey) => {
|
if(service) {
|
||||||
const service = this.loadService(key, tile.service)
|
return {
|
||||||
if(service) {
|
...tile, service, serviceId: tile.service,
|
||||||
return {
|
grid: this.gridPos(tile.position)
|
||||||
...tile, service, serviceId: tile.service,
|
|
||||||
grid: this.gridPos(tile.position), emiter: this.makeEmiter(key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
} else {
|
LayoutsModule.removeTile(key)
|
||||||
return []
|
}
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.layouts.load()
|
|
||||||
this.services.load()
|
|
||||||
|
|
||||||
new Map<string, (event: Events.Message) => void>([
|
|
||||||
[ Events.ErrorEvent, this.onError ],
|
|
||||||
[ Events.SaveOptionsEvent, this.onSaveOptions ],
|
|
||||||
[ Events.SaveOptionEvent, this.onSaveOption ],
|
|
||||||
[ Events.MoveTileEvent, this.onMoveTile ],
|
|
||||||
[ Events.RemoveTileEvent, this.onRemoveTile ],
|
|
||||||
[ Events.SaveServiceEvent, this.onSaveService ],
|
|
||||||
[ Events.RemoveServiceEvent, this.onRemoveService ]
|
|
||||||
]).forEach((handler, name) => this.bus.$on(name, handler))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
|
@ -107,117 +67,44 @@ export default class App extends Vue {
|
||||||
this.showManager = !this.showManager
|
this.showManager = !this.showManager
|
||||||
}
|
}
|
||||||
selectLayout(id: number) {
|
selectLayout(id: number) {
|
||||||
this.layouts.get().select(id)
|
LayoutsModule.select(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layouts
|
// Layouts
|
||||||
addLayout() {
|
addLayout() {
|
||||||
this.layouts.edit(l => {
|
LayoutsModule.add({ name: 'new layout', tiles: [] })
|
||||||
l.data.push({ name: 'layout' + l.data.length, tiles: [] })
|
|
||||||
return l
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
renameSelectedLayout(name: string) {
|
renameSelectedLayout(name: string) {
|
||||||
this.layouts.edit(data => {
|
LayoutsModule.setName(name)
|
||||||
if(data.selected) {
|
|
||||||
data.selected.name = name
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
removeSelectedLayout() {
|
removeSelectedLayout() {
|
||||||
this.layouts.edit(data => data.remove())
|
LayoutsModule.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tiles
|
// Tiles
|
||||||
showService(id: serviceKey) {
|
showService(id: serviceKey) {
|
||||||
this.layouts.edit(data => {
|
LayoutsModule.addTile({
|
||||||
if(data.selected) {
|
service: id, position: {}, options: {}
|
||||||
data.selected.tiles.push({
|
|
||||||
service: id, position: {}, options: {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onSaveOption({ key, msg }: Events.SaveOptionMessage) {
|
|
||||||
this.layouts.edit(data => {
|
|
||||||
if(data.selected) {
|
|
||||||
this.$set(data.selected.tiles[key].options, msg.key, msg.value)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onSaveOptions({ key, msg }: Events.SaveOptionsMessage) {
|
|
||||||
this.layouts.edit(data => {
|
|
||||||
if(data.selected) {
|
|
||||||
let options = data.selected.tiles[key].options
|
|
||||||
options = Object.assign({}, options, msg)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onMoveTile({ key, msg }: Events.MoveTileMessage) {
|
|
||||||
this.layouts.edit(data => {
|
|
||||||
if(data.selected){
|
|
||||||
const position = data.selected.tiles[key].position
|
|
||||||
this.$set(position, msg.type, Math.max(1,
|
|
||||||
(position[msg.type] || 1) + msg.direction
|
|
||||||
))
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onRemoveTile({ key }: Events.RemoveTileMessage) {
|
|
||||||
this.layouts.edit(data => {
|
|
||||||
if(data.selected) {
|
|
||||||
data.selected.tiles.splice(key, 1)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
getServiceId(key: number) {
|
|
||||||
const tile = this.tiles[key]
|
|
||||||
if(tile) {
|
|
||||||
return tile.serviceId
|
|
||||||
} else {
|
|
||||||
throw new Error('tile not found')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addService() {
|
addService() {
|
||||||
if (this.newService) {
|
if (this.newService) {
|
||||||
this.services.edit(data => {
|
const id = ServicesModule.add({
|
||||||
data.push({ type: this.newService, name: this.newService, auth: new Auth() })
|
type: this.newService, name: this.newService, auth: new Auth()
|
||||||
return data
|
|
||||||
})
|
})
|
||||||
this.showService(this.services.get().length - 1)
|
this.showService(id)
|
||||||
this.newService = ''
|
this.newService = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onSaveService({ key, msg }: Events.SaveServiceMessage) {
|
|
||||||
const service = this.loadService(key, this.getServiceId(key))
|
|
||||||
if(service){
|
|
||||||
service.name = msg.name
|
|
||||||
service.auth = msg.auth
|
|
||||||
this.services.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onRemoveService({ key }: Events.RemoveServiceMessage) {
|
|
||||||
this.services.edit(data => {
|
|
||||||
data.splice(this.getServiceId(key), 1)
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
this.onRemoveTile({ key, msg: undefined })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
onError({ msg }: Events.ErrorMessage) {
|
get errors() {
|
||||||
this.errors.push(msg.toString())
|
return ErrorsModule.errors
|
||||||
}
|
}
|
||||||
removeError(id: number) {
|
removeError(id: number) {
|
||||||
this.errors.splice(id, 1)
|
ErrorsModule.remove(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
@ -227,21 +114,6 @@ export default class App extends Vue {
|
||||||
'grid-column': `${position.y || 1} / span ${position.w || 2}`
|
'grid-column': `${position.y || 1} / span ${position.w || 2}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private makeEmiter(key: tileKey) {
|
|
||||||
const bus = this.bus
|
|
||||||
return (name: string, msg: any) => {
|
|
||||||
bus.$emit(name, { key, msg })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private loadService(key: tileKey, id: serviceKey) {
|
|
||||||
const ser = this.services.get()[id]
|
|
||||||
if (ser){
|
|
||||||
return ser
|
|
||||||
} else {
|
|
||||||
this.onRemoveTile({ key, msg: undefined })
|
|
||||||
this.errors.push('Removing missing service')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,37 +5,37 @@ import LoadableBlock from './loadable/LoadableBlock.vue'
|
||||||
import LoadableInline from './loadable/LoadableInline.vue'
|
import LoadableInline from './loadable/LoadableInline.vue'
|
||||||
import SuccessLoadable from './loadable/SuccessLoadableBlock.vue'
|
import SuccessLoadable from './loadable/SuccessLoadableBlock.vue'
|
||||||
|
|
||||||
import { ServiceData } from '@/types/App'
|
import { ErrorsModule, LayoutsModule, ServicesModule } from '@/store'
|
||||||
import * as Events from '@/types/Events'
|
import { Options, ServiceData } from '@/types/App'
|
||||||
|
|
||||||
@Component({ components: { LoadableBlock, LoadableInline, SuccessLoadable } })
|
@Component({ components: { LoadableBlock, LoadableInline, SuccessLoadable } })
|
||||||
export default class ServiceEmiter extends Vue {
|
export default class ServiceEmiter extends Vue {
|
||||||
@Prop(Function)
|
@Prop(Number)
|
||||||
readonly emit!: (name: string, msg: any) => void
|
readonly tileKey!: number
|
||||||
|
|
||||||
emitError(err: string) {
|
addError(error: any) {
|
||||||
this.emit(Events.ErrorEvent, err)
|
ErrorsModule.add(error.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
saveOptions(options: object) {
|
saveOptions(options: Options) {
|
||||||
this.emit(Events.SaveOptionsEvent, options)
|
LayoutsModule.setTileOptions(this.tileKey, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveOption(key: string, value: any) {
|
saveOption(key: string, value: any) {
|
||||||
this.saveOptionCouple({ key, value })
|
LayoutsModule.setTileOption(this.tileKey, key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveOptionCouple(couple: Events.Option) {
|
saveOptionCouple({ key, value }: { key: string, value: any }) {
|
||||||
this.emit(Events.SaveOptionEvent, couple)
|
this.saveOption(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveService(service: ServiceData) {
|
saveService({ name, auth }: ServiceData) {
|
||||||
this.emit(Events.SaveServiceEvent, service)
|
ServicesModule.set(LayoutsModule.getTile(this.tileKey).service, name, auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
catchEmit(req: AxiosPromise) {
|
catchError(req: AxiosPromise) {
|
||||||
return req.catch(err => {
|
return req.catch(err => {
|
||||||
this.emitError(err)
|
this.addError(err)
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,24 +16,25 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Mixins } from 'vue-property-decorator'
|
import { Component, Mixins } from 'vue-property-decorator'
|
||||||
|
|
||||||
import * as Events from '../types/Events'
|
|
||||||
import ServiceEmiter from './ServiceEmiter'
|
import ServiceEmiter from './ServiceEmiter'
|
||||||
|
import { MoveType, MoveDirection } from '@/types/App';
|
||||||
|
import { LayoutsModule, ServicesModule } from '../store';
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class ServiceHeader extends Mixins(ServiceEmiter) {
|
export default class ServiceHeader extends Mixins(ServiceEmiter) {
|
||||||
|
|
||||||
showSettings = false
|
showSettings = false
|
||||||
|
|
||||||
onMove(type: Events.MoveType, direction: Events.MoveDirection) {
|
onMove(type: MoveType, direction: MoveDirection) {
|
||||||
this.emit(Events.MoveTileEvent, { type, direction })
|
LayoutsModule.moveTile(this.tileKey, type, direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemove() {
|
onRemove() {
|
||||||
this.emit(Events.RemoveTileEvent, {})
|
LayoutsModule.removeTile(this.tileKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemoveService() {
|
onRemoveService() {
|
||||||
this.emit(Events.RemoveServiceEvent, {})
|
ServicesModule.remove(LayoutsModule.getTile(this.tileKey).service)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { AxiosPromise, AxiosResponse } from 'axios'
|
||||||
import { Component } from 'vue-property-decorator'
|
import { Component } from 'vue-property-decorator'
|
||||||
|
|
||||||
import AxiosLoadable from '@/helpers/loadable/AxiosLoadable'
|
import AxiosLoadable from '@/helpers/loadable/AxiosLoadable'
|
||||||
import { unsafeAxiosMapper } from '@/helpers/unsafeAxiosMapper'
|
import { unsafeAxiosMapper } from '@/helpers/unsafeMapper'
|
||||||
import { Auth } from '@/types/App'
|
import { Auth } from '@/types/App'
|
||||||
import ConnectedService from './ConnectedService'
|
import ConnectedService from './ConnectedService'
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ export default class AccountService<T, E, U = T> extends ConnectedService<T, E>
|
||||||
}
|
}
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
this.account.load(this.catchEmit(this.getAccount(this.auth)), this.mapAccount)
|
this.account.load(this.catchError(this.getAccount(this.auth)), this.mapAccount)
|
||||||
}
|
}
|
||||||
mapAccount(res: AxiosResponse<U>) {
|
mapAccount(res: AxiosResponse<U>) {
|
||||||
return unsafeAxiosMapper<T, U>(res)
|
return unsafeAxiosMapper<T, U>(res)
|
||||||
|
|
|
@ -30,7 +30,7 @@ export default class ConnectedService<T, E> extends BaseService {
|
||||||
|
|
||||||
makeAuth() {
|
makeAuth() {
|
||||||
const auth = new Auth(Object.entries(this.newAuth))
|
const auth = new Auth(Object.entries(this.newAuth))
|
||||||
this.catchEmit(this.checkAuth(auth)).then(res =>
|
this.catchError(this.checkAuth(auth)).then(res =>
|
||||||
this.saveService({ name: this.mapServiceName(res, auth), auth }))
|
this.saveService({ name: this.mapServiceName(res, auth), auth }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { AxiosPromise, AxiosResponse } from 'axios'
|
import { AxiosPromise, AxiosResponse } from 'axios'
|
||||||
import { unsafeAxiosMapper } from '../unsafeAxiosMapper'
|
import { unsafeAxiosMapper } from '../unsafeMapper'
|
||||||
import ErrorLoadable from './ErrorLoadable'
|
import ErrorLoadable from './ErrorLoadable'
|
||||||
|
|
||||||
export default class AxiosLoadable<T, E> extends ErrorLoadable<T, E> {
|
export default class AxiosLoadable<T, E> extends ErrorLoadable<T, E> {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Loadable from './Loadable'
|
import Loadable from './Loadable'
|
||||||
|
import { unsafeMapper } from '../unsafeMapper';
|
||||||
|
|
||||||
export default class ErrorLoadable<T, E> extends Loadable<T> {
|
export default class ErrorLoadable<T, E> extends Loadable<T> {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { unsafeMapper } from '../unsafeMapper'
|
||||||
|
import ErrorLoadable from './ErrorLoadable'
|
||||||
|
|
||||||
|
export default class PromiseLoadable<T, E> extends ErrorLoadable<T, E> {
|
||||||
|
|
||||||
|
load<U>(promise: Promise<U>, then: (res: U) => T = res => unsafeMapper<T, U>(res), reset = true) {
|
||||||
|
if (reset) {
|
||||||
|
this.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise
|
||||||
|
.then(res => this.success(then(res)))
|
||||||
|
.catch(err => {
|
||||||
|
this.fail(err)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
import WebStorageHandler from './WebStorageHandler'
|
|
||||||
|
|
||||||
export default class LocalStorageHandler<T> extends WebStorageHandler<T> {
|
|
||||||
constructor(key: string, data: T, loader?: (value: any) => T, saver?: (me: T) => any) {
|
|
||||||
super(window.localStorage, key, data, loader, saver)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
export default class StorageHandler<T> implements IStorageHandler<T> {
|
||||||
|
|
||||||
|
constructor(protected storage: Storage, protected key: string, protected fallback: T) { }
|
||||||
|
|
||||||
|
loadSync(): T {
|
||||||
|
const data = this.storage.getItem(this.key)
|
||||||
|
if (data) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(data)
|
||||||
|
} catch (e) {
|
||||||
|
this.storage.removeItem(this.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
return this.loadSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
save(data: T) {
|
||||||
|
return this.storage.setItem(this.key, JSON.stringify(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStorageHandler<T> {
|
||||||
|
load(): Promise<T>
|
||||||
|
loadSync(): T
|
||||||
|
save(data: T): void
|
||||||
|
}
|
|
@ -1,33 +0,0 @@
|
||||||
export default class WebStorageHandler<T> { // TODO: extends loadable
|
|
||||||
constructor(protected storage: Storage, protected key: string, protected data: T,
|
|
||||||
protected loader: (value: any) => T = (v => v), protected saver: (me: T) => any = (v => v)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
get() {
|
|
||||||
return this.data
|
|
||||||
}
|
|
||||||
|
|
||||||
set(value: T) {
|
|
||||||
this.data = value
|
|
||||||
this.save()
|
|
||||||
}
|
|
||||||
|
|
||||||
edit(mapper: (data: T) => T) {
|
|
||||||
this.set(mapper(this.get()))
|
|
||||||
}
|
|
||||||
|
|
||||||
load() {
|
|
||||||
const data = this.storage.getItem(this.key)
|
|
||||||
if (data) {
|
|
||||||
try {
|
|
||||||
this.data = this.loader(JSON.parse(data))
|
|
||||||
} catch (e) {
|
|
||||||
this.storage.removeItem(this.key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
save() {
|
|
||||||
this.storage.setItem(this.key, JSON.stringify(this.saver(this.data)))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,8 @@
|
||||||
import { AxiosResponse } from 'axios'
|
import { AxiosResponse } from 'axios'
|
||||||
|
|
||||||
|
export function unsafeMapper<T, U = T>(res: U) {
|
||||||
|
return res as unknown as T
|
||||||
|
}
|
||||||
export function unsafeAxiosMapper<T, U = T>(res: AxiosResponse<U>) {
|
export function unsafeAxiosMapper<T, U = T>(res: AxiosResponse<U>) {
|
||||||
return res.data as unknown as T
|
return res.data as unknown as T
|
||||||
}
|
}
|
|
@ -83,7 +83,7 @@ export default class Client extends Mixins<ServiceClient<Options>>(ServiceClient
|
||||||
}
|
}
|
||||||
|
|
||||||
get(path: string, options = {}) {
|
get(path: string, options = {}) {
|
||||||
return this.catchEmit(this.rest.get(path, { params: { limit: this.options.buffer, ...options } }))
|
return this.catchError(this.rest.get(path, { params: { limit: this.options.buffer, ...options } }))
|
||||||
}
|
}
|
||||||
|
|
||||||
getMessages(channel: string, options = {}) {
|
getMessages(channel: string, options = {}) {
|
||||||
|
|
|
@ -57,11 +57,11 @@ export default class Client extends Mixins<ServiceClient<Options>>(ServiceClient
|
||||||
}
|
}
|
||||||
|
|
||||||
get(path: string, options = {}) {
|
get(path: string, options = {}) {
|
||||||
return this.catchEmit(this.rest.get(path, { params: { limit: this.options.buffer, ...options } }))
|
return this.catchError(this.rest.get(path, { params: { limit: this.options.buffer, ...options } }))
|
||||||
}
|
}
|
||||||
|
|
||||||
post(path: string, options = {}) {
|
post(path: string, options = {}) {
|
||||||
return this.catchEmit(this.rest.post(path, options))
|
return this.catchError(this.rest.post(path, options))
|
||||||
}
|
}
|
||||||
|
|
||||||
getTimeline(options = {}) {
|
getTimeline(options = {}) {
|
||||||
|
@ -118,9 +118,9 @@ export default class Client extends Mixins<ServiceClient<Options>>(ServiceClient
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ws.onerror = ev => this.emitError(ev.type)
|
ws.onerror = ev => this.addError(ev.type)
|
||||||
ws.onclose = () => {
|
ws.onclose = () => {
|
||||||
this.emitError(
|
this.addError(
|
||||||
'Mastodon stream disconnected !' +
|
'Mastodon stream disconnected !' +
|
||||||
(this.options.reconnect ? ' Reconnecting...' : '')
|
(this.options.reconnect ? ' Reconnecting...' : '')
|
||||||
)
|
)
|
||||||
|
|
|
@ -94,7 +94,7 @@ export default class NextcloudNews extends Mixins<ConnectedService<object, objec
|
||||||
|
|
||||||
loadData() {
|
loadData() {
|
||||||
this.news.load<{ items: News[] }>(
|
this.news.load<{ items: News[] }>(
|
||||||
this.catchEmit(this.rest.get<News[]>('/items', { params: { batchSize: this.params.buffer, type: 3, getRead: false } })),
|
this.catchError(this.rest.get<News[]>('/items', { params: { batchSize: this.params.buffer, type: 3, getRead: false } })),
|
||||||
res => res.data.items.map(n => {
|
res => res.data.items.map(n => {
|
||||||
n.open = false
|
n.open = false
|
||||||
return n
|
return n
|
||||||
|
@ -107,7 +107,7 @@ export default class NextcloudNews extends Mixins<ConnectedService<object, objec
|
||||||
}
|
}
|
||||||
|
|
||||||
makeRead(id: number) {
|
makeRead(id: number) {
|
||||||
this.catchEmit(this.rest.put(`/items/${id}/read`))
|
this.catchError(this.rest.put(`/items/${id}/read`))
|
||||||
.then(() => this.removeNews(id))
|
.then(() => this.removeNews(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import axios, { AxiosInstance, AxiosResponse } from 'axios'
|
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||||
import { Component } from 'vue-property-decorator'
|
import { Component } from 'vue-property-decorator'
|
||||||
|
|
||||||
import ConnectedService from '@/components/service/ConnectedService'
|
import ConnectedService from '@/components/service/ConnectedService'
|
||||||
|
@ -129,17 +129,21 @@ export default class OpenWeatherMap extends ConnectedService<object, object> {
|
||||||
this.loadForecast()
|
this.loadForecast()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get(path: string, params: AxiosRequestConfig) {
|
||||||
|
return this.catchError(this.rest.get(path, params))
|
||||||
|
}
|
||||||
|
|
||||||
getWeather(params: { id: number }) {
|
getWeather(params: { id: number }) {
|
||||||
return this.catchEmit(this.rest.get('weather', { params }))
|
return this.get('weather', { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
loadForecast() {
|
loadForecast() {
|
||||||
const selected = this.weathers.map(w => w.selected, undefined)
|
const selected = this.weathers.map(w => w.selected, undefined)
|
||||||
if(selected) {
|
if(selected) {
|
||||||
this.forecast.load<{ list: Forecast[] }>(
|
this.forecast.load<{ list: Forecast[] }>(
|
||||||
this.catchEmit(this.rest.get('forecast', { params: {
|
this.get('forecast', { params: {
|
||||||
id: selected.id, cnt: this.params.forecastLimit
|
id: selected.id, cnt: this.params.forecastLimit
|
||||||
}})),
|
}}),
|
||||||
res => res.data.list
|
res => res.data.list
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { Selectable } from '@/helpers/lists/Selectable'
|
||||||
|
import ErrorLoadable from '@/helpers/loadable/ErrorLoadable'
|
||||||
|
import { Layout, Service } from '@/types/App'
|
||||||
|
import { Action, Module, Mutation, VuexModule } from 'vuex-module-decorators'
|
||||||
|
|
||||||
|
export interface AppState {
|
||||||
|
layouts: ErrorLoadable<Selectable<Layout>, string>
|
||||||
|
services: ErrorLoadable<Service[], string>
|
||||||
|
errors: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Module({ name: 'app', namespaced: true })
|
||||||
|
export default class App extends VuexModule implements AppState {
|
||||||
|
layouts = new ErrorLoadable<Selectable<Layout>, string>()
|
||||||
|
services = new ErrorLoadable<Service[], string>()
|
||||||
|
errors: string[] = []
|
||||||
|
|
||||||
|
addError(error: string) {
|
||||||
|
this.errors.push(error)
|
||||||
|
}
|
||||||
|
removeError(id: number) {
|
||||||
|
this.errors.splice(id, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { Module, Mutation, VuexModule } from 'vuex-module-decorators'
|
||||||
|
|
||||||
|
export interface ErrorsState {
|
||||||
|
errors: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Module({ name: 'errors', namespaced: true })
|
||||||
|
export default class Errors extends VuexModule implements ErrorsState {
|
||||||
|
errors: string[] = []
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
add(error: string) {
|
||||||
|
this.errors.push(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
remove(id: number) {
|
||||||
|
this.errors.splice(id, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
import { Selectable } from '@/helpers/lists/Selectable'
|
||||||
|
import PromiseLoadable from '@/helpers/loadable/PromiseLoadable'
|
||||||
|
import StorageHandler from '@/helpers/storage/StorageHandler'
|
||||||
|
import { Layout, Tile, Options, MoveType, MoveDirection } from '@/types/App'
|
||||||
|
import { Module, Mutation, VuexModule } from 'vuex-module-decorators'
|
||||||
|
|
||||||
|
export interface LayoutsState {
|
||||||
|
layouts: PromiseLoadable<Selectable<Layout>, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Module({ name: 'services', namespaced: true })
|
||||||
|
export default class Layouts extends VuexModule implements LayoutsState {
|
||||||
|
layouts = new PromiseLoadable<Selectable<Layout>, string>() // TODO: create increment id storage
|
||||||
|
private storage = new StorageHandler<Layout[]>(window.localStorage, 'services', [{ name: 'main', tiles: [] }])
|
||||||
|
|
||||||
|
get data() {
|
||||||
|
return this.layouts.get()
|
||||||
|
}
|
||||||
|
get current() {
|
||||||
|
return this.layouts.map(l => l.selected, undefined)
|
||||||
|
}
|
||||||
|
get tiles() {
|
||||||
|
return this.current ? this.current.tiles : []
|
||||||
|
}
|
||||||
|
|
||||||
|
getTile(id: number) {
|
||||||
|
return this.tiles[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
load() {
|
||||||
|
this.layouts.load(this.storage.load(),
|
||||||
|
data => new Selectable<Layout>(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
this.storage.save(this.layouts.map(l => l.data, []))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
select(id: number) {
|
||||||
|
this.layouts.with(l => l.select(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
remove() {
|
||||||
|
this.layouts.with(l => l.remove())
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
removeTile(id: number) {
|
||||||
|
const layout = this.current
|
||||||
|
if (layout) {
|
||||||
|
layout.tiles.splice(id, 1)
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
add(layout: Layout) {
|
||||||
|
this.layouts.with(l => l.data.push(layout))
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
addTile(tile: Tile) {
|
||||||
|
const layout = this.current
|
||||||
|
if (layout) {
|
||||||
|
layout.tiles.push(tile)
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
setName(name: string) {
|
||||||
|
const layout = this.current
|
||||||
|
if (layout) {
|
||||||
|
layout.name = name
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
setTileOption(id: number, key: string, value: any) {
|
||||||
|
const tile = this.getTile(id)
|
||||||
|
if (tile) {
|
||||||
|
tile.options[key] = value
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
setTileOptions(id: number, options: Options) {
|
||||||
|
const tile = this.getTile(id)
|
||||||
|
if (tile) {
|
||||||
|
tile.options = options
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
moveTile(id: number, type: MoveType, direction: MoveDirection) {
|
||||||
|
const tile = this.getTile(id)
|
||||||
|
if (tile) {
|
||||||
|
tile.position[type] = Math.max(1,
|
||||||
|
(tile.position[type] || 1) + direction
|
||||||
|
)
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
import PromiseLoadable from '@/helpers/loadable/PromiseLoadable'
|
||||||
|
import StorageHandler from '@/helpers/storage/StorageHandler'
|
||||||
|
import { Auth, Service } from '@/types/App'
|
||||||
|
import { Module, Mutation, VuexModule } from 'vuex-module-decorators'
|
||||||
|
|
||||||
|
export interface ServicesState {
|
||||||
|
services: PromiseLoadable<Service[], string>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Module({ name: 'services', namespaced: true })
|
||||||
|
export default class Services extends VuexModule implements ServicesState {
|
||||||
|
services = new PromiseLoadable<Service[], string>() // TODO: create increment id storage
|
||||||
|
private storage = new StorageHandler<Service[]>(window.localStorage, 'services', [])
|
||||||
|
|
||||||
|
get data() {
|
||||||
|
return this.services.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
get(id: number) {
|
||||||
|
return this.services.map(data => data[id], undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
load() {
|
||||||
|
this.services.load(this.storage.load(),
|
||||||
|
data => data.map(s => ({ ...s, auth: new Auth(Object.entries(s.auth)) }))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
function saveAuth(auth: Auth) {
|
||||||
|
const res: any = {}
|
||||||
|
for (const entry of auth.entries()) {
|
||||||
|
res[entry[0]] = entry[1]
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
this.storage.save(this.services.map(data => data.map(
|
||||||
|
(s: Service) => ({ ...s, auth: saveAuth(s.auth) })
|
||||||
|
), []))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
remove(id: number) {
|
||||||
|
this.services.with(data => data.splice(id, 1))
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
add(service: Service): number {
|
||||||
|
this.services.with(data => data.push(service))
|
||||||
|
this.save()
|
||||||
|
return this.services.map(data => data.length - 1, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
set(id: number, name: string, auth: Auth) {
|
||||||
|
const service = this.get(id)
|
||||||
|
if (service) {
|
||||||
|
service.name = name
|
||||||
|
service.auth = auth
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,14 +9,14 @@ export default class Time extends VuexModule implements TimeState {
|
||||||
now = new Date
|
now = new Date
|
||||||
|
|
||||||
@Mutation
|
@Mutation
|
||||||
updateTime() {
|
update() {
|
||||||
this.now = new Date
|
this.now = new Date
|
||||||
}
|
}
|
||||||
|
|
||||||
@Action
|
@Action
|
||||||
start() {
|
start() {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.updateTime()
|
this.update()
|
||||||
}, 15 * 1000)
|
}, 15 * 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,39 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import { getModule } from 'vuex-module-decorators'
|
import { getModule } from 'vuex-module-decorators'
|
||||||
|
|
||||||
|
import Errors from './Errors'
|
||||||
|
import Layouts from './Layouts'
|
||||||
|
import Services from './Services'
|
||||||
import Time from './Time'
|
import Time from './Time'
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
|
errors: Errors
|
||||||
|
layouts: Layouts
|
||||||
|
services: Services
|
||||||
time: Time
|
time: Time
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = new Vuex.Store<State>({
|
const store = new Vuex.Store<State>({
|
||||||
strict: process.env.NODE_ENV !== 'production',
|
strict: process.env.NODE_ENV !== 'production',
|
||||||
modules: {
|
modules: {
|
||||||
|
errors: Errors,
|
||||||
|
layouts: Layouts,
|
||||||
|
services: Services,
|
||||||
time: Time
|
time: Time
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const ErrorsModule = getModule(Errors, store)
|
||||||
|
|
||||||
|
export const LayoutsModule = getModule(Layouts, store)
|
||||||
|
LayoutsModule.load()
|
||||||
|
|
||||||
|
export const ServicesModule = getModule(Services, store)
|
||||||
|
ServicesModule.load()
|
||||||
|
|
||||||
export const TimeModule = getModule(Time, store)
|
export const TimeModule = getModule(Time, store)
|
||||||
TimeModule.start()
|
TimeModule.start()
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,16 @@ export interface Layout {
|
||||||
tiles: Tile[]
|
tiles: Tile[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MoveType = 'x' | 'y' | 'h' | 'w'
|
||||||
|
export type MoveDirection = 1 | -1
|
||||||
export interface Rect {
|
export interface Rect {
|
||||||
x?: number, y?: number
|
x?: number, y?: number
|
||||||
h?: number, w?: number
|
h?: number, w?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Options { [index: string]: { option: any } }
|
||||||
export interface Tile {
|
export interface Tile {
|
||||||
service: serviceKey
|
service: serviceKey
|
||||||
position: Rect
|
position: Rect
|
||||||
options: object
|
options: Options
|
||||||
}
|
}
|
|
@ -1,49 +0,0 @@
|
||||||
import { ServiceData, tileKey } from './App';
|
|
||||||
|
|
||||||
export interface Message {
|
|
||||||
key: tileKey,
|
|
||||||
msg: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SaveOptionEvent = 'saveOption'
|
|
||||||
export interface Option {
|
|
||||||
key: string
|
|
||||||
value: any
|
|
||||||
}
|
|
||||||
export interface SaveOptionMessage extends Message {
|
|
||||||
msg: Option
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SaveOptionsEvent = 'saveOptions'
|
|
||||||
export interface SaveOptionsMessage extends Message {
|
|
||||||
msg: object
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MoveTileEvent = 'move'
|
|
||||||
export type MoveType = 'x' | 'y' | 'h' | 'w'
|
|
||||||
export type MoveDirection = 1 | -1
|
|
||||||
export interface Move {
|
|
||||||
type: MoveType
|
|
||||||
direction: MoveDirection
|
|
||||||
}
|
|
||||||
export interface MoveTileMessage extends Message {
|
|
||||||
msg: Move
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RemoveTileEvent = 'remove'
|
|
||||||
export interface RemoveTileMessage extends Message {
|
|
||||||
msg: undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SaveServiceEvent = 'saveService'
|
|
||||||
export interface SaveServiceMessage extends Message {
|
|
||||||
msg: ServiceData
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RemoveServiceEvent = 'removeService'
|
|
||||||
export interface RemoveServiceMessage extends Message {
|
|
||||||
msg: undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ErrorEvent = 'error'
|
|
||||||
export interface ErrorMessage extends Message { }
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/* ... other settings */
|
transpileDependencies: [
|
||||||
transpileDependencies: ["vuex-module-decorators"]
|
'vuex-module-decorators',
|
||||||
|
'vuex-persist'
|
||||||
|
]
|
||||||
}
|
}
|
Loading…
Reference in New Issue