Add basic components, fc, router & store

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2018-10-18 19:22:14 +02:00 committed by Georg Ehrke
parent 8fa7f4aa8a
commit c4559d047c
No known key found for this signature in database
GPG Key ID: 9D98FD9380A1CB43
15 changed files with 709 additions and 15 deletions

View File

@ -58,3 +58,11 @@
#fullcalendar td.fc-day.fc-sun {
background-color: var(--color-background-darker);
}
@import 'calendarlist.scss';
@import 'confirmation.scss';
@import 'datepicker.scss';
@import 'eventdialog.scss';
@import 'globals.scss';
@import 'print.scss';
@import 'settings.scss';

11
package-lock.json generated
View File

@ -2591,13 +2591,8 @@
"requires": {
"@babel/polyfill": "^7.0.0",
"compare-urls": "^2.0.0",
"davclient.js": "git+https://github.com/owncloud/davclient.js.git#0e5ef1af5d174d9ec10dbe889a415b8f481d5094",
"normalize-url": "^3.3.0"
},
"dependencies": {
"davclient.js": {
"version": "git+https://github.com/owncloud/davclient.js.git#0e5ef1af5d174d9ec10dbe889a415b8f481d5094",
"from": "git+https://github.com/owncloud/davclient.js.git#0e5ef1af5d174d9ec10dbe889a415b8f481d5094"
}
}
},
"chalk": {
@ -3561,6 +3556,10 @@
"integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
"dev": true
},
"davclient.js": {
"version": "git+https://github.com/owncloud/davclient.js.git#0e5ef1af5d174d9ec10dbe889a415b8f481d5094",
"from": "git+https://github.com/owncloud/davclient.js.git"
},
"de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",

View File

@ -21,11 +21,83 @@
-->
<template>
<router-view />
<div class="app">
<app-navigation menu="menu">
<settings-section v-if="!loading" slot="settings-content" />
</app-navigation>
<router-view />
</div>
</template>
<script>
import { AppNavigation } from 'nextcloud-vue'
import client from './services/cdav.js'
export default {
name: 'App'
name: 'App',
components: {
AppNavigation,
},
data() {
return {
}
},
computed: {
// store getters
calendars() {
return this.$store.getters.getCalendars
},
},
beforeMount() {
// get calendars then get events
client.connect({ enableCalDAV: true }).then(() => {
console.debug('Connected to dav!', client)
this.$store.dispatch('getCalendars')
.then((calendars) => {
// No calendars? Create a new one!
if (calendars.length === 0) {
this.$store.dispatch('appendCalendar', { displayName: t('calendars', 'Calendars') })
.then(() => {
this.fetchEvents()
})
// else, let's get those events!
} else {
this.fetchEvents()
}
})
// check local storage for orderKey
// if (localStorage.getItem('orderKey')) {
// // run setOrder mutation with local storage key
// this.$store.commit('setOrder', localStorage.getItem('orderKey'))
// }
})
},
methods: {
menu() {
return {
menu: {
id: 'navigation',
items: [
]
}
}
},
/**
* Fetch the events of each calendar
*/
fetchEvents() {
// wait for all calendars to have fetch their events
Promise.all(this.calendars.map(calendar => this.$store.dispatch('getEventsFromCalendar', { calendar })))
.then(results => {
this.loading = false
// eslint-disable-next-line
console.log(results)
})
// no need for a catch, the action does not throw
// and the error is handled there
},
}
}
</script>

20
src/components/Edit.vue Normal file
View File

@ -0,0 +1,20 @@
<template>
<div class="editor">
<!-- title -->
<!-- timepicker -->
<!-- details -->
<!-- invitees-list -->
<!-- invitees-list-new -->
<!-- invitees-list-item -->
<!-- alarm-list -->
<!-- alarm-list-new -->
<!-- alarm-list-item -->
<!-- repeat -->
<!-- eventual other apps modules -->
</div>
</template>
<script>
export default {
name: 'Edit',
}
</script>

View File

@ -0,0 +1,23 @@
<template>
<div class="sidebar">
<!-- Datepicker -->
<!-- View buttons -->
<!-- Today button -->
<!-- Calendar list -->
<!-- calendar-list-new -->
<!-- calendar-list-item -->
<!-- colorpicker -->
<!-- subscription-list -->
<!-- subscription-list-new -->
<!-- subscription-list-item -->
<!-- colorpicker -->
<!-- Settings -->
<!-- import-progressbar (similar to contacts) -->
</div>
</template>
<script>
export default {
name: 'Sidebar',
}
</script>

35
src/components/View.vue Normal file
View File

@ -0,0 +1,35 @@
<template>
<div class="view">
<!-- Full calendar -->
<div ref="fullcalendar" class="fullcalendar-wrapper" />
<!-- Edit modal -->
<router-view />
</div>
</template>
<script>
import { Calendar } from 'fullcalendar'
import '../../node_modules/fullcalendar/dist/fullcalendar.css'
export default {
name: 'View',
// props: {
// view: {
// type: String,
// required: true,
// },
// firstday: {
// type: String,
// required: true,
// }
// },
data() {
return {
calendar: null
}
},
mounted() {
this.calendar = new Calendar(this.$refs.fullcalendar)
this.calendar.render()
},
}
</script>

View File

@ -23,8 +23,8 @@ import '@babel/polyfill'
import Vue from 'vue'
import App from './App'
// import router from './router'
// import store from './store'
import router from './router'
import store from './store'
// import { sync } from 'vuex-router-sync'
// CSP config for webpack dynamic chunk loading
@ -47,7 +47,7 @@ Vue.prototype.OCA = OCA
export default new Vue({
el: '#content',
// router,
// store,
router,
store,
render: h => h(App)
})

2
src/models/event.js Normal file
View File

@ -0,0 +1,2 @@
export default class {
}

51
src/router.js Normal file
View File

@ -0,0 +1,51 @@
import Vue from 'vue'
import Router from 'vue-router'
import View from './components/View'
import Edit from './components/Edit'
Vue.use(Router)
const router = new Router({
mode: 'history',
base: OC.generateUrl('/apps/calendar'),
routes: [
{
path: '/',
name: 'Root',
component: View,
// redirect: {
// name: 'View',
// params: {
// view: 'now',
// firstday: 'now'
// },
// }
},
{
path: '/{view}/{firstday}',
props: true,
children: [
{
path: '/',
name: 'View',
component: View,
props: true
},
{
path: '/edit/{mode}/{object}/{recurrence}',
name: 'Edit',
component: Edit,
props: true
},
{
path: '/new/{mode}/{recurrence}',
name: 'Edit',
component: Edit,
props: true,
},
],
},
],
})
export default router

47
src/services/cdav.js Normal file
View File

@ -0,0 +1,47 @@
/**
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import DavClient from 'cdav-library'
function xhrProvider() {
var headers = {
'X-Requested-With': 'XMLHttpRequest',
'requesttoken': OC.requestToken
}
var xhr = new XMLHttpRequest()
var oldOpen = xhr.open
// override open() method to add headers
xhr.open = function() {
var result = oldOpen.apply(this, arguments)
for (let name in headers) {
xhr.setRequestHeader(name, headers[name])
}
return result
}
OC.registerXHRForErrorProcessing(xhr)
return xhr
}
export default new DavClient({
rootUrl: OC.linkToRemote('dav')
}, xhrProvider)

3
src/services/parseIcs.js Normal file
View File

@ -0,0 +1,3 @@
export default function(ics, calendar) {
}

420
src/store/calendars.js Normal file
View File

@ -0,0 +1,420 @@
import Vue from 'vue'
import ICAL from 'ical.js'
import parseIcs from '../services/parseIcs'
import client from '../services/cdav'
import Event from '../models/event'
import pLimit from 'p-limit'
const calendarModel = {
id: '',
displayName: '',
enabled: true,
owner: '',
shares: [],
events: {},
url: '',
readOnly: false,
dav: false
}
const state = {
calendars: []
}
/**
* map a dav collection to our calendar object model
*
* @param {Object} calendar the calendar object from the cdav library
* @returns {Object}
*/
export function mapDavCollectionToCalendar(calendar) {
return {
// get last part of url
id: calendar.url.split('/').slice(-2, -1)[0],
displayName: calendar.displayname,
enabled: calendar.enabled !== false,
owner: calendar.owner,
readOnly: calendar.readOnly !== false,
url: calendar.url,
dav: calendar
}
}
const mutations = {
/**
* Add calendar into state
*
* @param {Object} state the store data
* @param {Object} calendar the calendar to add
*/
addCalendar(state, calendar) {
// extend the calendar to the default model
state.calendars.push(Object.assign({}, calendarModel, calendar))
},
/**
* Delete calendar
*
* @param {Object} state the store data
* @param {Object} calendar the calendar to delete
*/
deleteCalendar(state, calendar) {
state.calendars.splice(state.calendars.indexOf(calendar), 1)
},
/**
* Toggle whether a calendar is Enabled
* @param {Object} context the store mutations
* @param {Object} calendar the calendar to toggle
*/
toggleCalendarEnabled(context, calendar) {
calendar = state.calendars.find(search => search.id === calendar.id)
calendar.enabled = !calendar.enabled
},
/**
* Rename a calendar
* @param {Object} context the store mutations
* @param {Object} data destructuring object
* @param {Object} data.calendar the calendar to rename
* @param {String} data.newName the new name of the calendar
*/
renameCalendar(context, { calendar, newName }) {
calendar = state.calendars.find(search => search.id === calendar.id)
calendar.displayName = newName
},
/**
* Append a list of events to an calendar
* and remove duplicates
*
* @param {Object} state the store data
* @param {Object} data destructuring object
* @param {Object} data.calendar the calendar to add the event to
* @param {Event[]} data.events array of events to append
*/
appendEventsToCalendar(state, { calendar, events }) {
calendar = state.calendars.find(search => search === calendar)
// convert list into an array and remove duplicate
calendar.events = events.reduce((list, event) => {
if (list[event.uid]) {
console.debug('Duplicate event overrided', list[event.uid], event)
}
Vue.set(list, event.uid, event)
return list
}, calendar.events)
},
/**
* Add an event to an calendar and overwrite if duplicate uid
*
* @param {Object} state the store data
* @param {Event} event the event to add
*/
addEventToCalendar(state, event) {
let calendar = state.calendars.find(search => search.id === event.calendar.id)
Vue.set(calendar.events, event.uid, event)
},
/**
* Delete an event in a specified calendar
*
* @param {Object} state the store data
* @param {Event} event the event to delete
*/
deleteEventFromCalendar(state, event) {
let calendar = state.calendars.find(search => search.id === event.calendar.id)
Vue.delete(calendar, event.uid)
},
/**
* Share calendar with a user or group
*
* @param {Object} state the store data
* @param {Object} data destructuring object
* @param {Object} data.calendar the calendar
* @param {string} data.sharee the sharee
* @param {string} data.id id
* @param {Boolean} data.group group
*/
shareCalendar(state, { calendar, sharee, id, group }) {
calendar = state.calendars.find(search => search.id === calendar.id)
let newSharee = {
displayname: sharee,
id,
writeable: false,
group
}
calendar.shares.push(newSharee)
},
/**
* Remove Sharee from calendar shares list
*
* @param {Object} state the store data
* @param {Object} sharee the sharee
*/
removeSharee(state, sharee) {
let calendar = state.calendars.find(search => {
for (let i in search.shares) {
if (search.shares[i] === sharee) {
return true
}
}
})
calendar.shares.splice(calendar.shares.indexOf(sharee), 1)
},
/**
* Toggle sharee's writable permission
*
* @param {Object} state the store data
* @param {Object} sharee the sharee
*/
updateShareeWritable(state, sharee) {
let calendar = state.calendars.find(search => {
for (let i in search.shares) {
if (search.shares[i] === sharee) {
return true
}
}
})
sharee = calendar.shares.find(search => search === sharee)
sharee.writeable = !sharee.writeable
}
}
const getters = {
getCalendars: state => state.calendar
}
const actions = {
/**
* Retrieve and commit calendars
*
* @param {Object} context the store mutations
* @returns {Promise<Array>} the calendars
*/
async getCalendars(context) {
let calendars = await client.calendarHomes[0].findAllCalendars()
.then(calendars => {
return calendars.map(calendar => {
return mapDavCollectionToCalendar(calendar)
})
})
calendars.forEach(calendar => {
context.commit('addCalendar', calendar)
})
return calendars
},
/**
* Append a new calendar to array of existing calendars
*
* @param {Object} context the store mutations
* @param {Object} calendar The calendar to append
* @returns {Promise}
*/
async appendCalendar(context, calendar) {
return client.calendarHomes[0].createCalendarCollection(calendar.displayName)
.then((response) => {
calendar = mapDavCollectionToCalendar(response)
context.commit('addCalendar', calendar)
})
.catch((error) => { throw error })
},
/**
* Delete calendar
* @param {Object} context the store mutations Current context
* @param {Object} calendar the calendar to delete
* @returns {Promise}
*/
async deleteCalendar(context, calendar) {
return calendar.dav.delete()
.then((response) => {
// delete all the events from the store that belong to this calendar
Object.values(calendar.events)
.forEach(event => context.commit('deleteEvent', event))
// then delete the calendar
context.commit('deleteCalendar', calendar)
})
.catch((error) => { throw error })
},
/**
* Toggle whether a calendar is Enabled
* @param {Object} context the store mutations Current context
* @param {Object} calendar the calendar to toggle
* @returns {Promise}
*/
async toggleCalendarEnabled(context, calendar) {
calendar.dav.enabled = !calendar.dav.enabled
return calendar.dav.update()
.then((response) => context.commit('toggleCalendarEnabled', calendar))
.catch((error) => { throw error })
},
/**
* Rename a calendar
* @param {Object} context the store mutations Current context
* @param {Object} data.calendar the calendar to rename
* @param {String} data.newName the new name of the calendar
* @returns {Promise}
*/
async renameCalendar(context, { calendar, newName }) {
calendar.dav.displayname = newName
return calendar.dav.update()
.then((response) => context.commit('renameCalendar', { calendar, newName }))
.catch((error) => { throw error })
},
/**
* Retrieve the events of the specified calendar
* and commit the results
*
* @param {Object} context the store mutations
* @param {Object} importDetails = { ics, calendar }
* @returns {Promise}
*/
async getEventsFromCalendar(context, { calendar }) {
return calendar.dav.findByType('VEVENT')
.then((response) => {
// We don't want to lose the url information
// so we need to parse one by one
const events = response.map(item => {
let event = new Event(item.data, calendar)
Vue.set(event, 'dav', item)
return event
})
context.commit('appendEventsToCalendar', { calendar, events })
context.commit('appendEvents', events)
return events
})
.catch((error) => {
// unrecoverable error, if no events were loaded,
// remove the calendar
// TODO: create a failed calendar state and show that there was an issue?
context.commit('deleteCalendar', calendar)
console.error(error)
})
},
/**
*
* @param {Object} context the store mutations
* @param {Object} importDetails = { ics, calendar }
*/
async importEventsIntoCalendar(context, { ics, calendar }) {
const events = parseIcs(ics, calendar)
context.commit('changeStage', 'importing')
// max simultaneous requests
const limit = pLimit(3)
const requests = []
// create the array of requests to send
events.map(async event => {
// Get vcard string
try {
let vData = ICAL.stringify(event.vCard.jCal)
// push event to server and use limit
requests.push(limit(() => event.calendar.dav.createVCard(vData)
.then((response) => {
// setting the event dav property
Vue.set(event, 'dav', response)
// success, update store
context.commit('addEvent', event)
context.commit('addEventToCalendar', event)
context.commit('incrementAccepted')
})
.catch((error) => {
// error
context.commit('incrementDenied')
console.error(error)
})
))
} catch (e) {
context.commit('incrementDenied')
}
})
Promise.all(requests).then(() => {
context.commit('changeStage', 'default')
})
},
/**
* Remove sharee from calendar
* @param {Object} context the store mutations Current context
* @param {Object} sharee calendar sharee object
*/
removeSharee(context, sharee) {
context.commit('removeSharee', sharee)
},
/**
* Toggle permissions of calendar Sharees writeable rights
* @param {Object} context the store mutations Current context
* @param {Object} sharee calendar sharee object
*/
toggleShareeWritable(context, sharee) {
context.commit('updateShareeWritable', sharee)
},
/**
* Share calendar with User or Group
* @param {Object} context the store mutations Current context
* @param {Object} data.calendar the calendar
* @param {String} data.sharee the sharee
* @param {Boolean} data.id id
* @param {Boolean} data.group group
*/
shareCalendar(context, { calendar, sharee, id, group }) {
// Share calendar with entered group or user
context.commit('shareCalendar', { calendar, sharee, id, group })
},
/**
* Move an event to the provided calendar
*
* @param {Object} context the store mutations
* @param {Object} data destructuring object
* @param {Event} data.event the event to move
* @param {Object} data.calendar the calendar to move the event to
*/
async moveEventToCalendar(context, { event, calendar }) {
// only local move if the event doesn't exists on the server
if (event.dav) {
// TODO: implement proper move
// await events.dav.move(calendar.dav)
// .catch((error) => {
// console.error(error)
// OC.Notification.showTemporary(t('calendars', 'An error occurred'))
// })
let vData = ICAL.stringify(event.vCard.jCal)
let newDav
await calendar.dav.createVCard(vData)
.then((response) => { newDav = response })
.catch((error) => { throw error })
await event.dav.delete()
.catch((error) => {
console.error(error)
OC.Notification.showTemporary(t('calendars', 'An error occurred'))
})
await Vue.set(event, 'dav', newDav)
}
await context.commit('deleteEventFromCalendar', event)
await context.commit('updateEventCalendar', { event, calendar })
await context.commit('addEventToCalendar', event)
}
}
export default { state, mutations, getters, actions }

14
src/store/index.js Normal file
View File

@ -0,0 +1,14 @@
import Vue from 'vue'
import Vuex from 'vuex'
import calendars from './calendars'
Vue.use(Vuex)
const mutations = {}
export default new Vuex.Store({
modules: {
calendars,
},
mutations
})

View File

@ -1,4 +1,4 @@
<?php
script('contacts', 'contacts');
style('contacts', 'contacts');
script('calendar', 'calendar');
style('calendar', 'app/calendar');
?>

View File

@ -46,7 +46,7 @@ module.exports = {
},
plugins: [
new VueLoaderPlugin(),
new StyleLintPlugin(),
// new StyleLintPlugin(),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
],
resolve: {