Skip to content

Commit e1b2802

Browse files
committed
feat(importing): show all writable calendars in picker
Mark unsupported calendars as non-selectable. Previously they were fully hidden. Show a message about why some calendars are not selectable. Also show calendars that do not support events (and only support tasks and/or journal entries). Previously, calendars that did not support events were hidden altogether. Resolves: #2572 Signed-off-by: Oleksandr Dzhychko <hey@oleks.dev>
1 parent 26fa154 commit e1b2802

3 files changed

Lines changed: 100 additions & 37 deletions

File tree

css/import.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121

2222
.import-modal-file-item {
2323
display: flex;
24-
padding-top: 10px;
24+
margin-top: 10px;
25+
margin-bottom: 10px;
2526

2627
&--header {
2728
font-weight: bold;
@@ -34,6 +35,10 @@
3435
&__calendar-select {
3536
flex: 1 1 0;
3637
}
38+
39+
&__calendar-disabled-hint {
40+
color: var(--color-text-maxcontrast);
41+
}
3742
}
3843
}
3944
}

src/components/AppNavigation/Settings/ImportScreenRow.vue

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,28 @@
66
<template>
77
<li class="import-modal-file-item">
88
<div class="import-modal-file-item__filename">
9-
{{ file.name }}
9+
<div>{{ file.name }}</div>
10+
<div
11+
v-if="isSomeCalendarDisabledBecauseItDoesNotSupportEvents"
12+
class="import-modal-file-item__calendar-disabled-hint">
13+
{{ $t('calendar', 'Some calendars are disabled because this file contains events.') }}
14+
</div>
15+
<div
16+
v-if="isSomeCalendarDisabledBecauseItDoesNotSupportTasks"
17+
class="import-modal-file-item__calendar-disabled-hint">
18+
{{ $t('calendar', 'Some calendars are disabled because this file contains tasks.') }}
19+
</div>
20+
<div
21+
v-if="isSomeCalendarDisabledBecauseItDoesNotSupportJournals"
22+
class="import-modal-file-item__calendar-disabled-hint">
23+
{{ $t('calendar', 'Some calendars are disabled because this file contains journal entries.') }}
24+
</div>
1025
</div>
1126
<CalendarPicker
1227
class="import-modal-file-item__calendar-select"
1328
:value="calendar"
1429
:calendars="calendars"
30+
:is-calendar-selectable="isCalendarSelectable"
1531
@select-calendar="selectCalendar" />
1632
</li>
1733
</template>
@@ -39,59 +55,76 @@ export default {
3955
4056
computed: {
4157
...mapStores(usePrincipalsStore, useImportFilesStore, useCalendarsStore),
42-
calendar() {
43-
let calendarId = this.importFilesStore.importCalendarRelation[this.file.id]
44-
if (!calendarId) {
45-
this.setDefaultCalendarId()
46-
calendarId = this.importFilesStore.importCalendarRelation[this.file.id]
58+
newCalendar() {
59+
return {
60+
id: 'new',
61+
displayName: this.$t('calendar', 'New calendar'),
62+
isSharedWithMe: false,
63+
color: uidToHexColor(this.$t('calendar', 'New calendar')),
64+
owner: this.principalsStore.getCurrentUserPrincipal.url,
4765
}
66+
},
4867
49-
if (calendarId === 'new') {
50-
return {
51-
id: 'new',
52-
displayName: this.$t('calendar', 'New calendar'),
53-
isSharedWithMe: false,
54-
color: uidToHexColor(this.$t('calendar', 'New calendar')),
55-
owner: this.principalsStore.getCurrentUserPrincipal.url,
56-
}
68+
calendar() {
69+
const calendarId = this.importFilesStore.importCalendarRelation[this.file.id]
70+
if (calendarId === this.newCalendar.id) {
71+
return this.newCalendar
5772
}
58-
5973
return this.calendarsStore.getCalendarById(calendarId)
6074
},
6175
6276
calendars() {
63-
// TODO: remove once the false positive is fixed upstream
77+
// `this.calendarsStore.sortedCalendars` cannot be used because it already filters out calendars that do not support events.
78+
const calendars = [...this.calendarsStore.calendars]
79+
.filter((calendar) => !calendar.readOnly)
80+
.sort((a, b) => a.order - b.order)
81+
calendars.push(this.newCalendar)
82+
return calendars
83+
},
6484
65-
const calendars = this.calendarsStore.sortedCalendarFilteredByComponents(
66-
this.file.parser.containsVEvents(),
67-
this.file.parser.containsVJournals(),
68-
this.file.parser.containsVTodos(),
69-
)
85+
isSomeCalendarDisabledBecauseItDoesNotSupportEvents() {
86+
return this.file.parser.containsVEvents() && this.calendars.some((calendar) => !calendar.supportsEvents)
87+
},
7088
71-
calendars.push({
72-
id: 'new',
73-
displayName: this.$t('calendar', 'New calendar'),
74-
isSharedWithMe: false,
75-
color: uidToHexColor(this.$t('calendar', 'New calendar')),
76-
owner: this.principalsStore.getCurrentUserPrincipal.url,
77-
})
89+
isSomeCalendarDisabledBecauseItDoesNotSupportTasks() {
90+
return this.file.parser.containsVTodos() && this.calendars.some((calendar) => !calendar.supportsTasks)
91+
},
7892
79-
return calendars
93+
isSomeCalendarDisabledBecauseItDoesNotSupportJournals() {
94+
return this.file.parser.containsVJournals() && this.calendars.some((calendar) => !calendar.supportsJournals)
8095
},
8196
},
8297
98+
created() {
99+
const preselectedCalendar = this.calendars.find((calendar) => this.isCalendarSelectable(calendar))
100+
if (!preselectedCalendar) {
101+
// If no other calendar is selectable, at least `this.newCalendar` should be selectable and be preselected.
102+
throw new Error('Encountered illegal state. At least one calendar that can be selected should exist.')
103+
}
104+
this.selectCalendar(preselectedCalendar)
105+
},
106+
83107
methods: {
84-
selectCalendar(newCalendar) {
85-
this.importFilesStore.setCalendarForFileId({
86-
fileId: this.file.id,
87-
calendarId: newCalendar.id,
88-
})
108+
isCalendarSelectable(calendar) {
109+
if (calendar.id === this.newCalendar.id) {
110+
return true
111+
}
112+
if (this.file.parser.containsVEvents() && !calendar.supportsEvents) {
113+
return false
114+
}
115+
if (this.file.parser.containsVTodos() && !calendar.supportsTasks) {
116+
return false
117+
}
118+
if (this.file.parser.containsVJournals() && !calendar.supportsJournals) {
119+
return false
120+
}
121+
return true
89122
},
90123
91-
setDefaultCalendarId() {
124+
selectCalendar(newCalendar) {
92125
this.importFilesStore.setCalendarForFileId({
93126
fileId: this.file.id,
94-
calendarId: this.calendars[0].id,
127+
calendarId: newCalendar.id,
95128
})
96129
},
97130
},

src/components/Shared/CalendarPicker.vue

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
:filter-by="selectFilterBy"
1515
:input-label="inputLabel"
1616
:label-outside="inputLabel === ''"
17+
:selectable="selectable"
1718
@option:selected="change"
1819
@option:deselected="remove">
1920
<template #option="{ id }">
@@ -87,6 +88,19 @@ export default {
8788
type: String,
8889
default: '',
8990
},
91+
92+
/**
93+
* Decides whether a calendar is selectable or not.
94+
* Non-selectable calendars are displayed but cannot be selected.
95+
*
96+
* @type {Function}
97+
* @param {object} calendar
98+
* @return {boolean}
99+
*/
100+
isCalendarSelectable: {
101+
type: Function,
102+
default: (calendar) => true,
103+
},
90104
},
91105
92106
computed: {
@@ -162,6 +176,17 @@ export default {
162176
selectFilterBy(option, label, search) {
163177
return option.displayName.toLowerCase().indexOf(search) !== -1
164178
},
179+
180+
/**
181+
* Decide whether the given option can be selected
182+
*
183+
* @param {object} option The calendar option
184+
* @return {boolean} True if the option can be selected
185+
*/
186+
selectable(option) {
187+
const calendar = this.getCalendarById(option.id)
188+
return this.isCalendarSelectable(calendar)
189+
},
165190
},
166191
}
167192
</script>

0 commit comments

Comments
 (0)