Create sub components for editor, allow to save this or all future

Signed-off-by: Georg Ehrke <developer@georgehrke.com>
This commit is contained in:
Georg Ehrke 2019-09-01 03:44:11 +02:00
parent 2f5e99f66c
commit 3a58916e0b
No known key found for this signature in database
GPG Key ID: 9D98FD9380A1CB43
21 changed files with 866 additions and 176 deletions

View File

@ -347,3 +347,10 @@ button.delete:focus {
max-width: 0 !important;
overflow: hidden !important;
}
#app-sidebar .app-sidebar-header__action {
max-height: none !important;
}

View File

@ -19,8 +19,10 @@
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
@include icon-black-white('briefcase', 'calendar', 2);
@include icon-black-white('color-picker', 'calendar', 1);
@include icon-black-white('embed', 'calendar', 1);
@include icon-black-white('eye', 'calendar', 3);
@include icon-black-white('leftarrow', 'calendar', 1);
@include icon-black-white('random', 'calendar', 1);
@include icon-black-white('reminder', 'calendar', 1);

View File

@ -1,5 +1,15 @@
# Licenses
## briefcase.svg
- Created by: [Oriza Creative](https://thenounproject.com/orizacreativa)
- License: CC-BY
- Link: https://thenounproject.com/search/?q=briefcase&i=2834945
## eye.svg
- Created by: [David](https://thenounproject.com/kaxgyatso)
- License: CC-BY
- Link: https://thenounproject.com/search/?q=eye&i=428971
## repeat.svg
- Created by: [Brandy Bora](https://thenounproject.com/brandy.bora/)
- License: CC-BY

1
img/briefcase.svg Normal file
View File

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:serif="http://www.serif.com/" viewBox="0 0 65 65" version="1.1" xml:space="preserve" style="" x="0px" y="0px" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><rect serif:id="carrer suitcase sales" x="0.749" y="0.952" width="64" height="64" style="" fill="none"></rect><path d="M51.737,36.95c-4.999,0.038 -9.999,0.06 -14.998,0.068l0,4.887c-0.006,0.193 -0.021,0.225 -0.049,0.309c-0.13,0.401 -0.434,0.674 -0.95,0.691l-5.998,0c-0.205,-0.007 -0.261,-0.028 -0.368,-0.071c-0.372,-0.147 -0.616,-0.431 -0.631,-0.929l0,-4.887c-5,-0.008 -9.999,-0.03 -14.998,-0.068l0,19.975c12.661,0.105 25.323,0.105 37.984,0l0.008,-19.975Zm-44.007,-0.159c-0.018,5.716 -0.037,11.432 0.018,17.148c0.024,1.547 1.361,2.934 2.959,2.959c0.346,0.004 0.693,0.007 1.039,0.01l0,-19.974c-1.021,-0.008 -2.042,-0.018 -3.063,-0.027c-0.323,-0.005 -0.642,-0.045 -0.953,-0.116Zm49.996,0.007c-0.303,0.066 -0.615,0.104 -0.935,0.109c-1.018,0.009 -2.037,0.018 -3.055,0.027l-0.008,19.974c0.346,-0.003 0.692,-0.006 1.039,-0.01c1.546,-0.024 2.944,-1.355 2.959,-2.979l0,-17.121Zm-26.984,-3.889l0,7.996l3.998,0l0,-7.996l-3.998,0Zm28.983,-17.993l-53.977,0c0,5.678 -0.053,11.355 0.001,17.032c0.024,1.554 1.36,2.934 2.959,2.96c1.276,0.012 2.552,0.023 3.828,0.033c0.096,-0.023 0.197,-0.034 0.302,-0.029c0.052,0.007 0.103,0.018 0.152,0.033c5.251,0.041 10.502,0.066 15.753,0.074l0,-3.11c0.006,-0.193 0.021,-0.225 0.049,-0.309c0.13,-0.401 0.434,-0.674 0.95,-0.69l5.998,0c0.021,0 0.041,0.001 0.062,0.002c0.193,0.018 0.223,0.035 0.306,0.068c0.372,0.147 0.616,0.431 0.631,0.929l0,3.11c5.257,-0.008 10.513,-0.033 15.769,-0.074c0.101,-0.026 0.209,-0.038 0.321,-0.033c0.047,0.007 0.093,0.016 0.138,0.029c1.266,-0.01 2.532,-0.021 3.799,-0.033c1.547,-0.025 2.944,-1.355 2.959,-2.979c0,0 0,-17.013 0,-17.013Z" style="" fill-rule="nonzero"></path><path d="M23.741,12.917l0,-3.002c0.01,-1.553 1.343,-2.964 2.96,-2.995c4.024,-0.025 8.048,-0.025 12.072,0c1.553,0.03 2.95,1.367 2.96,2.995l0,3.002l-17.992,0Zm15.993,0c0,-1.008 0.019,-2.017 0,-3.025c-0.017,-0.509 -0.462,-0.956 -0.973,-0.973c-4.006,-0.076 -8.012,0 -12.018,0c-0.531,0.003 -1,0.457 -1.003,1.003l0,2.995l13.994,0Z" style="" fill-rule="nonzero"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

1
img/eye.svg Normal file
View File

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><circle cx="50" cy="50" r="12.272"></circle><path d="M50,21.363C25.454,21.363,5,47.954,5,50c0,2.044,20.454,28.637,45,28.637c24.545,0,45-26.591,45-28.637 C95,47.953,74.546,21.363,50,21.363z M50,70.454c-11.3,0-20.454-9.156-20.454-20.454c0-11.301,9.154-20.454,20.454-20.454 c11.299,0,20.454,9.153,20.454,20.454C70.454,61.298,61.299,70.454,50,70.454z"></path></svg>

After

Width:  |  Height:  |  Size: 579 B

15
package-lock.json generated
View File

@ -2658,6 +2658,11 @@
}
}
},
"autosize": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/autosize/-/autosize-4.0.2.tgz",
"integrity": "sha512-jnSyH2d+qdfPGpWlcuhGiHmqBJ6g3X+8T+iRwFrHPLVcdoGJE/x6Qicm6aDHfTsbgZKxyV8UU/YB2p4cjKDRRA=="
},
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
@ -4189,7 +4194,7 @@
}
},
"calendar-js": {
"version": "git+https://github.com/georgehrke/calendar-js.git#69d5d23d8bce3f7be13865c4a98419474e99c806",
"version": "git+https://github.com/georgehrke/calendar-js.git#8dfe9b41f7160656518d1e242cf7333cf486d9f0",
"from": "git+https://github.com/georgehrke/calendar-js.git",
"requires": {
"ical.js": "^1.3.0",
@ -15144,6 +15149,14 @@
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
},
"v-autosize": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/v-autosize/-/v-autosize-1.0.3.tgz",
"integrity": "sha512-hglWoI7tTNFi7GDvHotsID5zl15vyzU6Gzfs91WB9lowy7/N6nfZ3wQCDlf1DGxUwdRxvfOhr+Vnf6evr8QFDQ==",
"requires": {
"autosize": "^4.0.2"
}
},
"v-tooltip": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/v-tooltip/-/v-tooltip-2.0.2.tgz",

View File

@ -55,6 +55,7 @@
"p-limit": "^2.2.1",
"p-queue": "^6.1.1",
"uuid": "^3.3.3",
"v-autosize": "^1.0.3",
"v-tooltip": "^2.0.2",
"vue": "^2.6.10",
"vue-click-outside": "^1.0.7",

View File

@ -1,4 +1,28 @@
<template />
<!--
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
-
- @author Georg Ehrke <oc.list@georgehrke.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/>.
-
-->
<template>
</template>
<script>
export default {

View File

@ -1,4 +1,28 @@
<template />
<!--
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
-
- @author Georg Ehrke <oc.list@georgehrke.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/>.
-
-->
<template>
</template>
<script>
export default {

View File

@ -0,0 +1,25 @@
<!--
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
-
- @author Georg Ehrke <oc.list@georgehrke.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/>.
-
-->
<template>
</template>

View File

@ -0,0 +1,118 @@
<!--
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
-
- @author Georg Ehrke <oc.list@georgehrke.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/>.
-
-->
<template>
<div v-if="eventComponentLoaded" class="property-wrapper">
<div class="property-icon" :class="icon" :title="readableName" />
<div class="property-input">
<multiselect v-model="value" :options="options" :searchable="false"
:allow-empty="false" :title="readableName" track-by="value"
label="label" @select="changeValue"
/>
</div>
<div v-if="hasInfo" v-tooltip="info" class="property-info icon-details" />
</div>
</template>
<script>
import PropertyMixin from '../../../mixins/PropertyMixin'
export default {
name: 'PropertyText',
mixins: [
PropertyMixin
],
data() {
return {
value: null
}
},
computed: {
options() {
return this.propModel.options
}
},
watch: {
eventComponent() {
this.initValue()
}
},
created() {
this.initValue()
},
methods: {
changeValue(selectedOption) {
if (!selectedOption) {
return
}
this.eventComponent[this.propModel.name] = selectedOption.value
},
initValue() {
if (!this.eventComponent) {
return
}
const value = this.eventComponent[this.propModel.name] || this.propModel.defaultValue
this.value = this.options.find((option) => option.value === value)
}
},
}
</script>
<style scoped>
.property-wrapper {
display: flex;
width: 100%;
align-items: flex-start;
min-height: 46px;
}
.property-icon,
.property-info {
height: 34px;
width: 34px;
margin-top: 3px;
}
.property-icon {
margin-left: -5px;
margin-right: 5px;
}
.property-info {
opacity: .5;
}
.property-info:hover {
opacity: 1
}
.property-input {
flex-grow: 2;
}
.multiselect {
width: 100%;
margin: 3px 3px 3px 0;
}
</style>

View File

@ -1,11 +1,109 @@
<template />
<!--
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
-
- @author Georg Ehrke <oc.list@georgehrke.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/>.
-
-->
<template>
<div v-if="eventComponentLoaded" class="property-wrapper">
<div class="property-icon" :class="icon" :title="readableName" />
<div class="property-input">
<textarea v-if="!isReadOnly" v-autosize :value="value"
:placeholder="placeholder" :title="readableName" rows="1"
@input="changeValue"
/>
<div v-if="isReadOnly">
{{ value }}
</div>
</div>
<div v-if="hasInfo" v-tooltip="info" class="property-info icon-details" />
</div>
</template>
<script>
import autosize from 'v-autosize'
import PropertyMixin from '../../../mixins/PropertyMixin'
export default {
name: 'PropertyTextVue'
name: 'PropertyText',
directives: {
autosize
},
mixins: [
PropertyMixin
],
data() {
return {
value: null
}
},
watch: {
eventComponent() {
this.initValue()
}
},
created() {
this.initValue()
},
methods: {
changeValue(event) {
if (!this.eventComponentLoaded) {
return
}
this.eventComponent[this.propModel.name] = event.target.value
},
initValue() {
if (!this.eventComponentLoaded) {
return
}
this.value = this.eventComponent[this.propModel.name]
}
}
}
</script>
<style scoped>
.property-wrapper {
display: flex;
width: 100%;
align-items: flex-start;
}
.property-icon,
.property-info {
height: 34px;
width: 34px;
margin-top: 3px;
}
.property-icon {
margin-left: -5px;
margin-right: 5px;
}
.property-input {
flex-grow: 2;
}
textarea {
width: 100%
}
</style>

View File

@ -1,11 +1,91 @@
<template />
<!--
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
-
- @author Georg Ehrke <oc.list@georgehrke.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/>.
-
-->
<template>
<div v-if="eventComponentLoaded" class="property-wrapper">
<div class="property-input">
<textarea v-if="!isReadOnly" v-autosize :value="value"
:placeholder="placeholder" :title="readableName" rows="1"
@input="changeValue"
/>
<div v-if="isReadOnly">
{{ value }}
</div>
</div>
</div>
</template>
<script>
import autosize from 'v-autosize'
import PropertyMixin from '../../../mixins/PropertyMixin'
export default {
name: 'PropertyTitle'
name: 'PropertyTitle',
directives: {
autosize
},
mixins: [
PropertyMixin
],
data() {
return {
value: null
}
},
watch: {
eventComponent() {
this.initValue()
}
},
created() {
this.initValue()
},
methods: {
changeValue(event) {
if (!this.eventComponentLoaded) {
return
}
this.eventComponent[this.propModel.name] = event.target.value
},
initValue() {
if (!this.eventComponentLoaded) {
return
}
this.value = this.eventComponent[this.propModel.name]
}
}
}
</script>
<style scoped>
.property-wrapper,
.property-input,
textarea {
width: 100%;
}
textarea {
font-size: 20px
}
</style>

View File

@ -18,7 +18,7 @@ export default {
props: {
eventComponent: {
type: Object,
default: false
default: () => {}
}
},
data() {
@ -29,18 +29,17 @@ export default {
},
computed: {
timeFormat() {
// if (this.eventComponent.isAllDay) {
if (true) {
return 'YYYY-MM-DD'
}
// if (this.eventComponent.isAllDay()) {
// return 'YYYY-MM-DD'
// }
return 'YYYY-MM-DD HH:mm'
},
timeType() {
// if (this.eventComponent.isAllDay) {
if (true) {
return 'date'
}
// if (true) {
// return 'date'
// }
return 'datetime'
}

View File

@ -30,23 +30,23 @@ export default {
props: {
eventComponent: {
type: Object,
default: false
default: () => {}
}
},
computed: {
timeFormat() {
// if (this.eventComponent.isAllDay) {
if (true) {
return 'YYYY-MM-DD'
}
// if (true) {
// return 'YYYY-MM-DD'
// }
return 'YYYY-MM-DD HH:mm'
},
timeType() {
// if (this.eventComponent.isAllDay) {
if (true) {
return 'date'
}
// if (true) {
// return 'date'
// }
return 'datetime'
}

View File

@ -0,0 +1,60 @@
/**
* @copyright Copyright (c) 2018 Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.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 EventComponent from 'calendar-js/src/components/root/eventComponent'
export default {
props: {
// This is coming from rfcProps
propModel: {
type: Object,
required: true
},
isReadOnly: {
type: Boolean,
required: true
},
eventComponent: {
validator: prop => prop instanceof EventComponent || prop === null,
required: true
}
},
computed: {
icon() {
return this.propModel.icon || ''
},
placeholder() {
return this.propModel.placeholder || ''
},
info() {
return this.propModel.info || ''
},
readableName() {
return this.propModel.readableName || ''
},
hasInfo() {
return this.propModel.info !== undefined
},
eventComponentLoaded() {
return this.eventComponent !== null
}
}
}

View File

@ -25,6 +25,156 @@ import { getParserManager } from 'calendar-js'
import DateTimeValue from 'calendar-js/src/values/dateTimeValue'
import CalendarComponent from 'calendar-js/src/components/calendarComponent'
// export default function CalendarObject(calendarData, calendarId, dav = null) {
// const context = {
// dav,
// updateQueue: new PQueue({ concurrency: 1 }),
// vcalendar: null
// }
//
// const iface = {
// calendarId,
// conflict: false,
// }
//
// Object.defineProperties(iface, {
// id: {
// get() {
// if (context.dav) {
// return btoa(context.dav.url)
// }
//
// return 'new'
// }
// },
// uid: {
// get() {
// const iterator = context.vcalendar.getVObjectIterator()
// const firstVObject = iterator.next().value
// if (firstVObject) {
// return firstVObject.uid
// }
//
// return null
// }
// },
// objectType: {
// get() {
// const iterator = context.vcalendar.getVObjectIterator()
// const firstVObject = iterator.next().value
// if (firstVObject) {
// return firstVObject.name
// }
//
// return null
// }
// },
// dav: {
// get() {
// return context.dav
// }
// },
// vcalendar: {
// get() {
// return context.vcalendar
// }
// }
// })
//
// /**
// * Whether or not this calendar-object is an event
// *
// * @returns {boolean}
// */
// iface.isEvent = () => {
// return iface.objectType === 'vevent'
// }
//
// /**
// * Whether or not this calendar-object is a task
// *
// * @returns {boolean}
// */
// iface.isTodo = () => {
// return iface.objectType === 'vtodo'
// }
//
// /**
// * Get all recurrence-items in given range
// *
// * @param {Date} start Begin of time-range
// * @param {Date} end End of time-range
// * @returns {Array}
// */
// iface.getAllObjectsInTimeRange = (start, end) => {
// const iterator = context.vcalendar.getVObjectIterator()
// const firstVObject = iterator.next().value
// if (!firstVObject) {
// return []
// }
//
// const s = DateTimeValue.fromJSDate(start, true)
// const e = DateTimeValue.fromJSDate(end, true)
// return firstVObject.recurrenceManager.getAllOccurrencesBetween(s, e)
// }
//
// /**
// * Get recurrence-item at exactly a given recurrence-Id
// *
// * @param {Date} recurrenceId RecurrenceId to retrieve
// * @returns {AbstractRecurringComponent|null}
// */
// iface.getObjectAtRecurrenceId = (recurrenceId) => {
// const iterator = context.vcalendar.getVObjectIterator()
// const firstVObject = iterator.next().value
// if (!firstVObject) {
// return null
// }
//
// const d = DateTimeValue.fromJSDate(recurrenceId, true)
// return firstVObject.recurrenceManager.getOccurrenceAtExactly(d)
// }
//
// /**
// * resets the inter vcalendar to the dav data
// *
// * @param {CalendarComponent|String} data Data to reset to
// */
// iface.resetToDav = (data = null) => {
// console.debug('RESET TO DAV CALLED ' + context.dav.url)
// if (data instanceof CalendarComponent) {
// context.vcalendar = data
// return
// }
//
// if (data === null && context.dav === null) {
// return
// }
//
// const parserManager = getParserManager()
// const parser = parserManager.getParserForFileType('text/calendar')
//
// const calendarData = data || context.dav.data
// parser.parse(calendarData)
//
// const itemIterator = parser.getItemIterator()
// const firstVCalendar = itemIterator.next().value
// if (firstVCalendar) {
// context.vcalendar = firstVCalendar
// }
// }
//
// iface.existsOnServer = () => {
// return !!context.dav
// }
//
// iface.resetToDav()
//
// Object.freeze(iface)
//
// return iface
// }
export default class CalendarObject {
/**
@ -197,4 +347,13 @@ export default class CalendarObject {
}
}
/**
* Whether or not this objects exists on the server
*
* @returns {boolean}
*/
existsOnServer() {
return !!this.dav
}
}

View File

@ -20,63 +20,85 @@
*
*/
export const properties = {
export default {
// RFC 5545
class: {
name: 'accessClass',
readableName: t('calendar', 'When shared show'),
icon: 'icon-share',
options: {
},
icon: 'icon-eye',
options: [
{ value: 'PUBLIC', label: t('calendar', 'When shared show full event') },
{ value: 'CONFIDENTIAL', label: t('calendar', 'When shared show only busy') },
{ value: 'PRIVATE', label: t('calendar', 'When shared hide this event') },
],
multiple: false,
default: true,
info: t('calendar', '')
info: t('calendar', 'The visibility of this event in shared calendars.'),
defaultValue: 'PUBLIC'
},
summary: {
name: 'title',
readableName: t('calendar', 'Title'),
placeholder: t('calendar', 'Enter a title for this event')
},
location: {
name: 'location',
readableName: t('calendar', 'Location'),
placeholder: t('calendar', 'Add or search for location'),
icon: 'icon-address'
},
description: {
name: 'description',
readableName: t('calendar', 'Description'),
icon: 'icon-text',
multiple: false,
default: true,
info: t('calendar', '')
placeholder: t('calendar', 'Add a description for yourself and your attendees'),
icon: 'icon-menu',
},
geo: {
name: 'geo',
readableName: t('calendar', 'Geographic Position'),
icon: 'icon-timezone',
multiple: false,
default: false,
info: t('calendar', '')
info: t('calendar', 'The geographical position this events take place at.')
},
priority: {
readableName: t('calendar', 'Priority'),
icon: '',
multiple: false,
default: false,
info: t('calendar', ''),
info: t('calendar', 'Priority of this event.'),
options: [
{ value: 7, label: t('calendar', 'low') },
{ value: 5, label: t('calendar', 'medium') },
{ value: 3, label: t('calendar', 'high') },
{ value: 7, label: t('calendar', 'Low') },
{ value: 5, label: t('calendar', 'Medium') },
{ value: 3, label: t('calendar', 'High') },
]
},
status: {
name: 'status',
readableName: t('calendar', 'Status'),
icon: '',
multiple: false,
default: false,
info: t('calendar', '')
},
transp: {
readableName: t('calendar', 'Show as'),
icon: '',
multiple: false,
default: false,
info: t('calendar', ''),
icon: 'icon-checkmark',
options: [
{ value: 'TRANSPARENT', label: t('calendar', 'free') },
{ value: 'OPAQUE', label: t('calendar', 'busy') },
]
{ value: 'CONFIRMED', label: t('calendar', 'Confirmed') },
{ value: 'TENTATIVE', label: t('calendar', 'Tentative') },
{ value: 'CANCELLED', label: t('calendar', 'Cancelled') },
],
multiple: false,
default: true,
info: t('calendar', 'Confirmation about the overall status of the event.'),
defaultValue: 'CONFIRMED'
},
timeTransparency: {
name: 'timeTransparency',
readableName: t('calendar', 'Show as'),
icon: 'icon-briefcase',
multiple: false,
default: true,
info: t('calendar', 'Take this event into account when calculating free-busy information'),
options: [
{ value: 'TRANSPARENT', label: t('calendar', 'Free') },
{ value: 'OPAQUE', label: t('calendar', 'Busy') },
],
defaultValue: 'TRANSPARENT'
},
// url: {
// readableName: t('calendar', 'URL'),
@ -113,7 +135,7 @@ export const properties = {
icon: '',
multiple: false,
default: false,
info: t('calendar', '')
info: t('calendar', 'Special color of this event. Overrides the calendar-color.')
},
// To be implemented later:
// conference: {

View File

@ -46,7 +46,7 @@ export function getFCEventFromEventComponent(calendarObjects, start, end, timezo
const fcEvent = {
id: [calendarObject.id, object.id].join('###'),
title: object.title,
title: object.title || t('calendar', 'Untitled event'),
allDay: object.isAllDay(),
start: object.startDate.getInTimezone(timezone).jsDate,
end: object.endDate.getInTimezone(timezone).jsDate,

View File

@ -23,7 +23,7 @@
*/
import Vue from 'vue'
import CalendarObject from '../models/calendarObject'
import logger from '../services/loggerService'
// import logger from '../services/loggerService'
import DateTimeValue from 'calendar-js/src/values/dateTimeValue'
import { createEvent, getTimezoneManager } from 'calendar-js'
@ -41,11 +41,13 @@ const mutations = {
*/
appendCalendarObjects(state, calendarObjects = []) {
for (const calendarObject of calendarObjects) {
if (calendarObject instanceof CalendarObject) {
if (!state.calendarObjects[calendarObject.id]) {
Vue.set(state.calendarObjects, calendarObject.id, calendarObject)
} else {
logger.error('Invalid calendarObject object')
}
// if (calendarObject instanceof CalendarObject) {
// } else {
// logger.error('Invalid calendarObject object')
// }
}
},
@ -56,11 +58,13 @@ const mutations = {
* @param {Object} calendarObject Calendar-object to add
*/
appendCalendarObject(state, calendarObject) {
if (calendarObject instanceof CalendarObject) {
if (!state.calendarObjects[calendarObject.id]) {
Vue.set(state.calendarObjects, calendarObject.id, calendarObject)
} else {
logger.error('Invalid calendarObject object')
}
// if (calendarObject instanceof CalendarObject) {
// } else {
// logger.error('Invalid calendarObject object')
// }
},
/**
@ -148,7 +152,7 @@ const actions = {
* @returns {Promise<void>}
*/
async updateCalendarObject(context, { calendarObject }) {
if (calendarObject.dav) {
if (calendarObject.existsOnServer()) {
calendarObject.dav.data = calendarObject.vcalendar.toICS()
return calendarObject.dav.update()
@ -180,7 +184,7 @@ const actions = {
async deleteCalendarObject(context, { calendarObject }) {
// If this calendar-object was not created on the server yet,
// no need to send requests to the server
if (calendarObject.dav) {
if (calendarObject.existsOnServer()) {
await calendarObject.dav.delete()
}

View File

@ -1,35 +1,60 @@
<!--
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
- @copyright Copyright (c) 2019 Jakob Röhrl <jakob.roehrl@web.de>
-
- @author Georg Ehrke <oc.list@georgehrke.com>
- @author Jakob Röhrl <jakob.roehrl@web.de>
-
- @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/>.
-
-->
<template>
<AppSidebar :title="title" :subtitle="subtitle" :compact="false"
@close="cancel"
>
<template v-slot:primary-actions>
<calendar-picker />
<AppSidebar title="" :compact="false" @close="cancel">
<template v-slot:primary-actions style="max-height: none !important">
<div style="width: 100%">
<property-title :event-component="eventComponent" :prop-model="rfcProps.summary" :is-read-only="false" />
<calendar-picker :calendars="calendars" :calendar="selectedCalendar" />
<!-- <property-timepicker-title :event-component="eventComponent" :is-read-only="false" />-->
<!-- <title-timepicker :event-component="eventComponent" />-->
<!-- <timezonepicker :event-component="eventComponent" />-->
</div>
</template>
<template v-slot:secondary-actions>
<ActionLink v-if="hasDownloadURL" icon="icon-download" title="Download"
:href="downloadURL"
/>
<ActionButton v-if="canDelete && !canCreateRecurrenceException" icon="icon-delete" @click="alert('Delete')">
Delete
</ActionButton>
<ActionButton v-if="canDelete && canCreateRecurrenceException" icon="icon-delete" @click="alert('Delete')">
Delete this occurrence
</ActionButton>
<ActionButton v-if="canDelete && canCreateRecurrenceException" icon="icon-delete" @click="alert('Delete')">
Delete this and all future
</ActionButton>
</template>
<AppSidebarTab name="Details" icon="icon-details" :order="0">
<title-timepicker :event-component="eventComponent" />
<timezonepicker :event-component="eventComponent" />
<div class="section-wrapper">
<input v-model="location" type="text" placeholder="t('calendar', 'location')">
</div>
<div class="section-wrapper">
<textarea v-model="description" placeholder="t('calendar', 'description')" />
</div>
<div class="section-wrapper">
<Multiselect v-model="status" :options="statusOptions" />
</div>
<div class="section-wrapper">
<Multiselect v-model="accessClass" :options="accessOptions" />
</div>
<div class="section-wrapper">
<Multiselect v-model="timeTranspararency" :options="timeTranspararencyOptions" />
</div>
<property-text :event-component="eventComponent" :prop-model="rfcProps.location" :is-read-only="false" />
<property-text :event-component="eventComponent" :prop-model="rfcProps.description" :is-read-only="false" />
<property-select :event-component="eventComponent" :prop-model="rfcProps.status" :is-read-only="false" />
<property-select :event-component="eventComponent" :prop-model="rfcProps.class" :is-read-only="false" />
<property-select :event-component="eventComponent" :prop-model="rfcProps.timeTransparency" :is-read-only="false" />
</AppSidebarTab>
<AppSidebarTab name="Attendees" icon="icon-group" :order="1">
This is the attendees tab
@ -46,6 +71,18 @@
<!-- <AppSidebarTab name="Projects" icon="icon-projects" :order="5">-->
<!-- This is the projects tab-->
<!-- </AppSidebarTab>-->
<div class="app-sidebar-button-area-bottom">
<button v-if="!canCreateRecurrenceException" class="primary one-option">
{{ updateLabel }}
</button>
<button v-if="canCreateRecurrenceException" class="primary two-options">
Update this occurrence
</button>
<button v-if="canCreateRecurrenceException" class="two-options">
Update this and all future
</button>
</div>
</AppSidebar>
</template>
<script>
@ -54,13 +91,18 @@ import {
AppSidebar,
AppSidebarTab,
ActionLink,
Multiselect
ActionButton
} from 'nextcloud-vue'
import CalendarPicker from '../components/Editor/CalendarPicker'
import TitleTimepicker from '../components/Editor/TitleTimepicker'
import Timezonepicker from '../components/Editor/Timezonepicker'
// import TitleTimepicker from '../components/Editor/TitleTimepicker'
// import Timezonepicker from '../components/Editor/Timezonepicker'
import detectTimezone from '../services/timezoneDetectionService'
import PropertyText from '../components/Editor/Properties/PropertyText'
import PropertySelect from '../components/Editor/Properties/PropertySelect'
import rfcProps from '../models/rfcProps'
import PropertyTitle from '../components/Editor/Properties/PropertyTitle'
// import { loadNewEventIntoEditor } from '../services/routerHelper'
// import TitleTimepicker from '../components/Editor/TitleTimepicker'
@ -72,13 +114,16 @@ import detectTimezone from '../services/timezoneDetectionService'
export default {
name: 'EditSidebar',
components: {
TitleTimepicker,
Timezonepicker,
PropertyTitle,
PropertySelect,
PropertyText,
// TitleTimepicker,
// Timezonepicker,
AppSidebar,
AppSidebarTab,
ActionLink,
ActionButton,
CalendarPicker,
Multiselect
// TitleTimepicker,
// DetailsTab,
// InviteesTab,
@ -89,84 +134,11 @@ export default {
return {
calendarObject: null,
eventComponent: null,
isLoading: false,
isLoading: true,
error: false,
statusOptions: ['CONFIRMED', 'Tentative', 'Cancelled'],
accessOptions: [
'When shared show full event',
'When shared show only busy',
'When shared hide this event'
],
timeTranspararencyOptions: ['OPAQUE', 'TRANSPARENT']
}
},
computed: {
title: {
get() {
return this.isLoading
? 'LOADING'
: this.eventComponent ? this.eventComponent.title : ''
},
set(newValue) {
this.eventComponent.title = newValue
}
},
subtitle: {
get() {
return this.isLoading ? 'LOADING' : 'LOADED'
},
set(newValue) {
}
},
location: {
get() {
return this.isLoading
? 'LOADING'
: this.eventComponent ? this.eventComponent.location : ''
},
set(newValue) {
this.eventComponent.location = newValue
}
},
description: {
get() {
return this.isLoading
? 'LOADING'
: this.eventComponent ? this.eventComponent.description : ''
},
set(newValue) {
this.eventComponent.description = newValue
}
},
status: {
get() {
return this.isLoading
? 'LOADING'
: this.eventComponent ? this.eventComponent.status : ''
},
set(newValue) {
}
},
accessClass: {
get() {
return this.isLoading
? 'LOADING'
: this.eventComponent ? this.eventComponent.accessClass : ''
}
},
timeTranspararency: {
get() {
return this.isLoading
? 'LOADING'
: this.eventComponent ? this.eventComponent.timeTranspararency : ''
}
},
reminderIcon() {
// Todo: show different icon based on alarm.
// If no alarm is set: Show reminder icon without dot
@ -190,6 +162,63 @@ export default {
}
return this.calendarObject.dav.url + '?export'
},
displayDetails() {
console.debug('display details?', (!this.isLoading && !this.error))
return !this.isLoading && !this.error
},
rfcProps() {
return rfcProps
},
canDelete() {
if (!this.calendarObject) {
return false
}
return !!this.calendarObject.dav
},
canCreateRecurrenceException() {
if (!this.eventComponent) {
return false
}
return this.eventComponent.canCreateRecurrenceExceptions()
},
updateLabel() {
if (!this.calendarObject) {
return ''
}
if (!this.calendarObject.dav) {
return t('calendar', 'Save')
}
return t('calendar', 'Update')
},
updateOnlyThis() {
return t('calendar', 'Update this occurrence')
},
updateThisAllFuture() {
return t('calendar', 'Update this and all future')
},
isReadOnly() {
return false
},
calendars() {
if (this.isReadOnly) {
return [
this.$store.getters.getCalendarById(this.calendarObject.calendarId)
]
}
return this.$store.getters.sortedCalendars
},
selectedCalendar() {
if (!this.calendarObject) {
return {}
}
return this.$store.getters.getCalendarById(this.calendarObject.calendarId)
}
},
methods: {
@ -216,7 +245,9 @@ export default {
return
}
console.debug('Can create a recurrence exception?', this.eventComponent.canCreateRecurrenceExceptions())
if (this.eventComponent.canCreateRecurrenceExceptions() && this.calendarObject.id !== 'new') {
console.debug('Creating a recurrence-exception')
this.eventComponent.createRecurrenceException(thisAndAllFuture)
}
@ -242,7 +273,7 @@ export default {
},
selectCalendar(selectedCalendar) {
this.event = selectedCalendar
}
},
},
beforeRouteEnter(to, from, next) {
if (to.name === 'NewSidebarView') {
@ -259,11 +290,8 @@ export default {
vm.$store.dispatch('createNewEvent', { start, end, isAllDay, timezoneId })
.then((calendarObject) => {
vm.calendarObject = calendarObject
vm.eventComponent = vm.calendarObject.getObjectAtRecurrenceId(new Date(start * 1000))
vm.eventComponent = calendarObject.getObjectAtRecurrenceId(new Date(start * 1000))
vm.isLoading = true
console.debug(vm.calendarObject)
console.debug(vm.eventComponent)
})
})
} else {
@ -278,6 +306,7 @@ export default {
.then(() => {
vm.calendarObject = vm.$store.getters.getCalendarObjectById(objectId)
vm.eventComponent = vm.calendarObject.getObjectAtRecurrenceId(new Date(recurrenceId * 1000))
vm.isLoading = false
OCP.Toast.info('Loaded event ...')
})
@ -304,6 +333,7 @@ export default {
.then(() => {
this.calendarObject = this.$store.getters.getCalendarObjectById(objectId)
this.eventComponent = this.calendarObject.getObjectAtRecurrenceId(new Date(recurrenceId * 1000))
// this.getEventComponent = () => eventComponent
this.isLoading = false
OCP.Toast.info('Loaded event ...')
@ -329,11 +359,23 @@ export default {
</script>
<style>
.app-sidebar-button-area-bottom {
position: absolute;
margin-top: -60px;
display: flex !important;
width: 100%;
padding: 10px;
}
.section-wrapper {
display: flex;
max-width: 100%;
margin-top: 10px;
}
button.one-option {
width: 100%
}
button.two-options {
width: 50%
}
.multiselect {
width: 100%;
}
</style>