feat(editors): redesign editors

Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
This commit is contained in:
Richard Steinmetz 2024-01-09 21:17:23 +01:00
parent 497ab38dde
commit 3369fc8ea9
No known key found for this signature in database
GPG Key ID: 27137D9E7D273FB2
108 changed files with 715 additions and 1280 deletions

View File

@ -37,11 +37,14 @@
}
}
/** Hide the submit button for the title, because it does not trigger a save */
.app-sidebar-header__mainname-form {
button {
display: none;
}
// We use our custom header layout for the sidebar editor
.app-sidebar-header__info {
display: none !important;
}
.app-sidebar-header__description {
// Close button should be aligned with calendar picker (header)
padding-top: 5px;
}
.editor-invitee-list-empty-message,
@ -228,6 +231,18 @@
.property-title-time-picker {
width: 100%;
&--readonly {
display: flex;
align-items: center;
}
&__icon {
width: 34px;
height: 34px;
margin-left: -5px;
margin-right: 5px;
}
&__time-pickers,
&__all-day {
display: flex;
@ -235,10 +250,12 @@
}
&__time-pickers {
flex-wrap: wrap;
justify-content: space-between;
gap: 5px;
.mx-datepicker {
width: 49%;
flex: 1 auto;
.mx-input-append {
background-color: transparent !important;
@ -246,16 +263,24 @@
}
&--readonly {
justify-content: start;
.property-title-time-picker-read-only-wrapper {
display: flex;
align-items: center;
width: 50%;
margin: 3px 3px 3px 0;
padding: 8px 7px;
background-color: var(--color-main-background);
color: var(--color-main-text);
outline: none;
&--start-date {
padding-right: 0;
}
&--end-date {
padding-left: 0;
}
&__icon {
margin-left: 8px;
height: 16px;
@ -275,22 +300,14 @@
}
}
@media screen and (max-width: 1500px) {
&__time-pickers {
display: block;
}
.mx-datepicker {
width: 100%;
}
.property-title-time-picker-read-only-wrapper {
width: 100%;
}
}
&__all-day {
justify-content: flex-start;
padding-left: 3px;
margin-top: 5px;
// Reduce the height just a little bit (from 44px) to save some space
.checkbox-radio-switch__label {
min-height: 32px;
}
}
.datetime-picker-inline-icon {
@ -421,7 +438,6 @@
&__summary {
display: flex;
align-items: center;
margin-bottom: 5px;
&__icon {
width: 34px;
@ -432,7 +448,7 @@
&__content {
flex: 1 auto;
padding: 0 7px;
padding: 8px 7px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@ -519,7 +535,6 @@
display: flex;
width: 100%;
align-items: flex-start;
margin-bottom: 5px;
&__icon,
&__info {
@ -536,6 +551,7 @@
&__info {
display: flex;
justify-content: center;
flex-shrink: 0;
opacity: .5;
}
@ -568,7 +584,6 @@
div {
width: calc(100% - 8px); /* for typical (thin) scrollbar size */
white-space: pre-line;
margin: 3px 3px 3px 0;
padding: 8px 7px;
background-color: var(--color-main-background);
color: var(--color-main-text);
@ -577,26 +592,31 @@
word-break: break-word; /* allows breaking on long URLs */
max-height: 30vh;
}
a.linkified {
text-decoration: underline;
&::after {
content: '';
}
}
}
&--readonly-calendar-picker {
div.calendar-picker-option {
margin: 3px 3px 3px 0;
padding: 8px 7px;
}
}
}
}
.property-text,
.property-select,
.property-color,
.property-select-multiple,
.property-title,
.property-repeat,
.resource-capacity,
.resource-room-type {
margin-bottom: 5px;
&--readonly {
margin-bottom: 0;
}
}
.property-select,
.property-select-multiple {
align-items: center;
@ -611,21 +631,46 @@
}
.property-color {
&__input {
display: flex;
gap: 5px;
margin-bottom: 5px;
&--readonly {
// Align with other (text based) fields
margin: 3px 0 3px 7px;
}
}
&__color-preview {
border-radius: var(--border-radius);
height: 34px !important;
width: 34px !important;
margin: 0;
$size: 44px;
width: $size !important;
height: $size !important;
border-radius: $size;
}
}
.property-text {
&__icon {
// Prevent icon misalignment on vertically growing inputs
height: unset;
align-self: flex-start;
padding-top: 12px;
}
&--readonly {
.property-text__icon {
padding-top: 10px;
}
}
&__input {
&--readonly {
// Reduce line height but still keep first row aligned to the icon
line-height: 1;
padding-top: calc(var(--default-line-height) / 2 - 0.5lh);
}
textarea {
resize: none;
}
@ -655,12 +700,29 @@
}
}
}
// Fix weird height
&__input {
max-height: 44px;
}
}
.property-title {
&__input,
&__input input {
font-size: 20px;
&__input, input {
font-weight: bold;
}
&__input--readonly {
font-size: 18px;
}
}
// Normalize gaps between all properties. We use outer margins between each row so a padding
// around inputs (from core) is not required.
.property-title,
.property-title-time-picker {
input {
margin: 0;
}
}
@ -668,16 +730,19 @@
margin-bottom: 5px;
}
.illustration-header {
max-height: 150px;
height: 150px;
width: 100%;
}
.event-popover .event-popover__inner {
.event-popover__response-buttons {
margin-top: 8px;
margin-bottom: 0;
}
.illustration-header svg {
width: 100%;
height: 150px;
padding: 8px 8px 0 8px;
.property-text,
.property-title-time-picker {
&__icon {
margin: 0 !important;
}
}
}
@ -705,27 +770,21 @@
text-align: left;
max-width: 480px;
width: 480px;
padding: 5px 8px;
padding: 5px 10px 10px 10px;
.empty-content {
margin-top: 0 !important;
padding: 50px 0;
}
.illustration-header {
height: 100px;
overflow: hidden;
margin-bottom: 5px;
background-color: var(--color-background-dark);
// There is probably a more elegant solution for this
margin: -5px 0 5px -8px;
width: 496px;
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
.property-title-time-picker:not(.property-title-time-picker--readonly) {
margin-bottom: 12px;
}
.property-title-time-picker {
margin-bottom: 12px;
.event-popover__invitees {
.avatar-participation-status__text {
bottom: 22px;
}
}
.event-popover__buttons {
@ -824,7 +883,11 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
// Only apply the margin if at least one button is being rendered
&:not(:empty) {
margin-top: 20px;
}
}
.resource-search-list-item,

View File

@ -8,7 +8,6 @@
cursor: text;
width: 100% !important;
box-sizing: border-box;
margin-top: 1px !important;
padding: 12px;
white-space: pre-line;
overflow: auto;
@ -19,7 +18,14 @@
max-height: 16em;
max-height: calc(100vh - 500px);
a.linkified::after {
content: '';
a.linkified {
text-decoration: underline;
// Prevent misalignment when a linkified line starts with a link, e.g. in the location field
margin: 0;
&::after {
content: '';
}
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 90 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 72 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 68 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 79 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 46 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 40 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 57 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -8,15 +8,15 @@
<div class="attachments-summary">
<div class="attachments-summary-inner">
<Paperclip :size="20" />
<div v-if="attachments.length > 0">
<div v-if="attachments.length > 0" class="attachments-summary-inner-label">
{{ n('calendar', '{count} attachment', '{count} attachments', attachments.length, { count: attachments.length }) }}
</div>
<div v-else>
<div v-else class="attachments-summary-inner-label">
{{ t('calendar', 'No attachments') }}
</div>
</div>
<NcActions>
<NcActions v-if="!isReadOnly">
<template #icon>
<Plus :size="20" />
</template>
@ -35,9 +35,10 @@
</NcActions>
</div>
<div v-if="attachments.length > 0">
<ul class="attachments-list-item">
<ul class="attachments-list">
<NcListItem v-for="attachment in attachments"
:key="attachment.path"
class="attachments-list-item"
:force-display-actions="true"
:name="getBaseName(attachment.fileName)"
@click="openFile(attachment.uri)">
@ -45,7 +46,8 @@
<img :src="getPreview(attachment)" class="attachment-icon">
</template>
<template #actions>
<NcActionButton @click="deleteAttachmentFromEvent(attachment)">
<NcActionButton v-if="!isReadOnly"
@click="deleteAttachmentFromEvent(attachment)">
<template #icon>
<Close :size="20" />
</template>
@ -217,13 +219,37 @@ export default {
width: 34px;
height: 34px;
margin-left: -10px;
margin-right: 10px;
margin-right: 5px;
}
.attachments-summary-inner-label {
padding: 0 7px;
font-weight: bold;
}
}
}
.attachments-list-item {
.attachments-list {
margin: 0 -8px;
.attachments-list-item {
// Reduce height to 44px
:deep(.list-item) {
padding: 0 8px;
}
:deep(.list-item-content__wrapper) {
height: 44px;
}
:deep(.list-item-content) {
// Align text with other properties
padding-left: 18px;
}
:deep(.line-one__title) {
font-weight: unset;
}
}
}
#attachments .empty-content {
@ -240,8 +266,8 @@ export default {
}
}
.attachment-icon {
width: 40px;
height: auto;
width: 24px;
height: 24px;
border-radius: var(--border-radius);
}
</style>

View File

@ -0,0 +1,167 @@
<!--
- @copyright Copyright (c) 2024 Richard Steinmetz <richard@steinmetz.cloud>
-
- @author Richard Steinmetz <richard@steinmetz.cloud>
-
- @license AGPL-3.0-or-later
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU 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 General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div class="calendar-picker-header"
:class="{ 'calendar-picker-header--readonly': isReadOnly }">
<NcActions type="tertiary"
class="calendar-picker-header__picker"
:class="{ 'calendar-picker-header__picker--fix-width': isReadOnly && value && value.isSharedWithMe }"
:menu-name="value.displayName"
:force-name="true"
:disabled="isDisabled">
<template #icon>
<div class="calendar-picker-header__icon">
<div class="calendar-picker-header__icon__dot"
:style="{ 'background-color': value.color }" />
</div>
</template>
<NcActionButton v-for="calendar in calendars"
:key="calendar.id"
:close-after-click="true"
@click="$emit('update:value', calendar)">
<template #icon>
<div class="calendar-picker-header__icon">
<div class="calendar-picker-header__icon__dot"
:style="{ 'background-color': calendar.color }" />
</div>
</template>
{{ calendar.displayName }}
</NcActionButton>
</NcActions>
</div>
</template>
<script>
import { NcActions, NcActionButton } from '@nextcloud/vue'
export default {
name: 'CalendarPickerHeader',
components: {
NcActions,
NcActionButton,
},
props: {
value: {
type: Object,
required: true,
},
calendars: {
type: Array,
required: true,
},
isReadOnly: {
type: Boolean,
required: true,
},
},
computed: {
/**
* @return {boolean}
*/
isDisabled() {
return this.isReadOnly || this.calendars.length < 2
},
},
}
</script>
<style lang="scss">
.event-popover {
.calendar-picker-header {
width: calc(100% - 79px);
button {
margin-left: -9px;
.button-vue__text {
margin-left: 0;
}
}
}
.calendar-picker-header--readonly button .button-vue__text {
margin-left: 2px;
}
}
.app-sidebar {
.calendar-picker-header {
width: calc(100% - 30px);
button {
margin-left: -14px;
.button-vue__text {
margin-left: 0;
}
}
}
.calendar-picker-header--readonly button .button-vue__text {
margin-left: 6px;
}
}
</style>
<style lang="scss" scoped>
.calendar-picker-header {
align-self: flex-start;
margin-bottom: 5px;
&__picker {
width: 100%;
// For some reason the NcActions component behaves weirdly when a calendar is shared
// read-only with the user. This is an ugly workaround to fix the width of the button.
&--fix-width {
width: unset;
max-width: 100%;
}
:deep(button.button-vue) {
max-width: 100%;
}
// Keep full opacity for disabled buttons
&:disabled, :deep(:disabled) {
opacity: 1 !important;
filter: unset !important;
}
}
&__icon {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
&__dot {
$dot-size: 16px;
width: $dot-size;
height: $dot-size;
border-radius: $dot-size;
}
}
}
</style>

View File

@ -21,7 +21,8 @@
-->
<template>
<div class="invitation-response-buttons">
<div class="invitation-response-buttons"
:class="{ 'invitation-response-buttons--grow': growHorizontally }">
<NcButton v-if="!isAccepted"
type="primary"
class="invitation-response-buttons__button"
@ -87,6 +88,10 @@ export default {
type: Boolean,
default: false,
},
growHorizontally: {
type: Boolean,
default: false,
},
},
data() {
return {
@ -165,11 +170,16 @@ export default {
<style lang="scss" scoped>
.invitation-response-buttons {
display: flex;
width: 100%;
justify-content: flex-end;
gap: 5px;
margin-bottom: 8px;
&__button {
flex: 1 auto;
&--grow {
width: 100%;
.invitation-response-buttons__button {
flex: 1 auto;
}
}
}
</style>

View File

@ -24,7 +24,13 @@
-->
<template>
<div>
<div v-if="!hideIfEmpty || !isListEmpty" class="invitees-list">
<div v-if="showHeader" class="invitees-list__header">
<AccountMultipleIcon :size="20" />
<b>{{ t('calendar', 'Attendees') }}</b>
{{ statusHeader }}
</div>
<InviteesListSearch v-if="!isReadOnly && !isSharedWithMe && hasUserEmailAddress"
:already-invited-emails="alreadyInvitedEmails"
:organizer="calendarObjectInstance.organizer"
@ -32,22 +38,24 @@
<OrganizerListItem v-if="hasOrganizer"
:is-read-only="isReadOnly || isSharedWithMe"
:organizer="calendarObjectInstance.organizer" />
<InviteesListItem v-for="invitee in inviteesWithoutOrganizer"
<InviteesListItem v-for="invitee in limitedInviteesWithoutOrganizer"
:key="invitee.email"
:attendee="invitee"
:is-read-only="isReadOnly || isSharedWithMe"
:organizer-display-name="organizerDisplayName"
:members="invitee.members"
@remove-attendee="removeAttendee" />
<NoAttendeesView v-if="isReadOnly && isListEmpty"
:message="noInviteesMessage" />
<NoAttendeesView v-if="!isReadOnly && isListEmpty && hasUserEmailAddress"
:message="noInviteesMessage" />
<NoAttendeesView v-if="isSharedWithMe"
<div v-if="limit > 0 && inviteesWithoutOrganizer.length > limit"
class="invitees-list__more">
{{ n('calendar', '%n more guest', '%n more guests', inviteesWithoutOrganizer.length - limit) }}
</div>
<NoAttendeesView v-if="isReadOnly && isSharedWithMe && !hideErrors"
:message="noOwnerMessage" />
<OrganizerNoEmailError v-if="!isReadOnly && isListEmpty && !hasUserEmailAddress" />
<NoAttendeesView v-else-if="isReadOnly && isListEmpty && hasUserEmailAddress"
:message="noInviteesMessage" />
<OrganizerNoEmailError v-else-if="!isReadOnly && isListEmpty && !hasUserEmailAddress && !hideErrors" />
<div class="invitees-list-button-group">
<div v-if="!hideButtons" class="invitees-list-button-group">
<NcButton v-if="isCreateTalkRoomButtonVisible"
class="invitees-list-button-group__button"
:disabled="isCreateTalkRoomButtonDisabled"
@ -86,6 +94,7 @@ import {
showError,
} from '@nextcloud/dialogs'
import { organizerDisplayName, removeMailtoPrefix } from '../../../utils/attendee.js'
import AccountMultipleIcon from 'vue-material-design-icons/AccountMultiple.vue'
export default {
name: 'InviteesList',
@ -97,6 +106,7 @@ export default {
InviteesListItem,
InviteesListSearch,
OrganizerListItem,
AccountMultipleIcon,
},
props: {
isReadOnly: {
@ -111,11 +121,32 @@ export default {
type: Boolean,
required: true,
},
showHeader: {
type: Boolean,
required: true,
},
hideIfEmpty: {
type: Boolean,
default: false,
},
hideButtons: {
type: Boolean,
default: false,
},
hideErrors: {
type: Boolean,
default: false,
},
limit: {
type: Number,
default: 0,
},
},
data() {
return {
creatingTalkRoom: false,
showFreeBusyModel: false,
recentAttendees: [],
}
},
computed: {
@ -146,8 +177,12 @@ export default {
return false
})
},
/**
* All invitees except the organizer.
*
* @return {object[]}
*/
inviteesWithoutOrganizer() {
if (!this.calendarObjectInstance.organizer) {
return this.invitees
}
@ -171,6 +206,25 @@ export default {
return attendee.uri !== this.calendarObjectInstance.organizer.uri
})
},
/**
* All invitees except the organizer limited by the limit prop.
* If the limit prop is 0 all invitees except the organizer are returned.
*
* @return {object[]}
*/
limitedInviteesWithoutOrganizer() {
const filteredInvitees = this.inviteesWithoutOrganizer
if (this.limit) {
const limit = this.hasOrganizer ? this.limit - 1 : this.limit
return filteredInvitees
// Push newly added attendees to the top of the list
.toSorted((a, b) => this.recentAttendees.indexOf(b.uri) - this.recentAttendees.indexOf(a.uri))
.slice(0, limit)
}
return filteredInvitees
},
isOrganizer() {
return this.calendarObjectInstance.organizer !== null
&& this.$store.getters.getCurrentUserPrincipal !== null
@ -221,6 +275,18 @@ export default {
return false
},
statusHeader() {
if (!this.isReadOnly) {
return ''
}
return this.t('calendar', '{invitedCount} invited, {confirmedCount} confirmed', {
invitedCount: this.inviteesWithoutOrganizer.length,
confirmedCount: this.inviteesWithoutOrganizer
.filter((attendee) => attendee.participationStatus === 'ACCEPTED')
.length,
})
},
},
methods: {
addAttendee({ commonName, email, calendarUserType, language, timezoneId, member }) {
@ -237,6 +303,7 @@ export default {
organizer: this.$store.getters.getCurrentUserPrincipal,
member,
})
this.recentAttendees.push(email)
},
removeAttendee(attendee) {
// Remove attendee from participating group
@ -256,6 +323,7 @@ export default {
calendarObjectInstance: this.calendarObjectInstance,
attendee,
})
this.recentAttendees = this.recentAttendees.filter((a) => a.uri !== attendee.email)
},
openFreeBusy() {
this.showFreeBusyModel = true
@ -304,19 +372,35 @@ export default {
</script>
<style lang="scss" scoped>
.invitees-list-button-group {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.invitees-list {
margin-top: 12px;
.invitees-list-button-group__button {
flex: 1 0 200px;
&__header {
display: flex;
gap: 5px;
padding: 5px 5px 5px 6px;
}
::v-deep .button-vue__text {
white-space: unset !important;
overflow: unset !important;
text-overflow: unset !important;
&__more {
padding: 15px 0 0 46px;
font-weight: bold;
opacity: 0.75;
}
.invitees-list-button-group {
display: flex;
flex-wrap: wrap;
gap: 5px;
&__button {
flex: 1 0 200px;
:deep(.button-vue__text) {
white-space: unset !important;
overflow: unset !important;
text-overflow: unset !important;
}
}
}
}
</style>

View File

@ -21,7 +21,9 @@
-->
<template>
<div v-if="display" class="property-select">
<div v-if="display"
class="property-select"
:class="{ 'property-select--readonly': isReadOnly }">
<div class="property-select__input"
:class="{ 'property-select__input--readonly-calendar-picker': isReadOnly }">
<CalendarPicker v-if="!isReadOnly"

View File

@ -22,7 +22,7 @@
-->
<template>
<div class="property-color">
<div class="property-color" :class="{ 'property-color--readonly': isReadOnly }">
<component :is="icon"
:size="20"
:name="readableName"

View File

@ -22,7 +22,9 @@
-->
<template>
<div v-if="display" class="property-select">
<div v-if="display"
class="property-select"
:class="{ 'property-select--readonly': isReadOnly }">
<component :is="icon"
:size="20"
:name="readableName"

View File

@ -22,7 +22,9 @@
-->
<template>
<div v-if="display" class="property-text">
<div v-if="display"
class="property-text"
:class="{ 'property-text--readonly': isReadOnly }">
<component :is="icon"
:size="20"
:name="readableName"

View File

@ -21,7 +21,7 @@
-->
<template>
<div class="property-title">
<div class="property-title" :class="{ 'property-title--readonly': isReadOnly }">
<div class="property-title__input"
:class="{ 'property-title__input--readonly': isReadOnly }">
<input v-if="!isReadOnly"

View File

@ -4,6 +4,7 @@
-
- @author Georg Ehrke <oc.list@georgehrke.com>
- @author Jakob Röhrl <jakob.roehrl@web.de>
- @author Richard Steinmetz <richard@steinmetz.cloud>
-
- @license AGPL-3.0-or-later
-
@ -23,7 +24,12 @@
-->
<template>
<div class="property-title-time-picker">
<div class="property-title-time-picker"
:class="{ 'property-title-time-picker--readonly': isReadOnly }">
<CalendarIcon v-if="isReadOnly"
class="property-title-time-picker__icon"
:size="20" />
<div v-if="!isReadOnly"
class="property-title-time-picker__time-pickers">
<DatePicker :date="startDate"
@ -46,7 +52,7 @@
</div>
<div v-if="isReadOnly"
class="property-title-time-picker__time-pickers property-title-time-picker__time-pickers--readonly">
<div class="property-title-time-picker-read-only-wrapper">
<div class="property-title-time-picker-read-only-wrapper property-title-time-picker-read-only-wrapper--start-date">
<div class="property-title-time-picker-read-only-wrapper__label">
{{ formattedStart }}
</div>
@ -55,28 +61,26 @@
:class="{ 'highlighted-timezone-icon': highlightStartTimezone }"
:size="20" />
</div>
<div class="property-title-time-picker-read-only-wrapper">
<div class="property-title-time-picker-read-only-wrapper__label">
{{ formattedEnd }}
<template v-if="!isAllDayOneDayEvent">
<div>-</div>
<div class="property-title-time-picker-read-only-wrapper property-title-time-picker-read-only-wrapper--end-date">
<div class="property-title-time-picker-read-only-wrapper__label">
{{ formattedEnd }}
</div>
<IconTimezone v-if="!isAllDay"
:title="endTimezone"
:class="{ 'highlighted-timezone-icon': highlightStartTimezone }"
:size="20" />
</div>
<IconTimezone v-if="!isAllDay"
:name="endTimezone"
:class="{ 'highlighted-timezone-icon': highlightStartTimezone }"
:size="20" />
</div>
</template>
</div>
<div v-if="!isReadOnly" class="property-title-time-picker__all-day">
<input id="allDay"
:checked="isAllDay"
type="checkbox"
class="checkbox"
<NcCheckboxRadioSwitch :checked="isAllDay"
:disabled="!canModifyAllDay"
@change="toggleAllDay">
<label v-tooltip="allDayTooltip"
for="allDay">
@update:checked="toggleAllDay">
{{ $t('calendar', 'All day') }}
</label>
</NcCheckboxRadioSwitch>
</div>
</div>
</template>
@ -85,13 +89,17 @@
import moment from '@nextcloud/moment'
import DatePicker from '../../Shared/DatePicker.vue'
import IconTimezone from 'vue-material-design-icons/Web.vue'
import CalendarIcon from 'vue-material-design-icons/Calendar.vue'
import { mapState } from 'vuex'
import { NcCheckboxRadioSwitch } from '@nextcloud/vue'
export default {
name: 'PropertyTitleTimePicker',
components: {
DatePicker,
IconTimezone,
CalendarIcon,
NcCheckboxRadioSwitch,
},
props: {
/**
@ -195,16 +203,10 @@ export default {
*/
formattedStart() {
if (this.isAllDay) {
return this.$t('calendar', 'from {startDate}', {
startDate: moment(this.startDate).locale(this.locale).format('L'),
endDate: moment(this.endDate).locale(this.locale).format('L'),
})
return moment(this.startDate).locale(this.locale).format('ll')
}
return this.$t('calendar', 'from {startDate} at {startTime}', {
startDate: moment(this.startDate).locale(this.locale).format('L'),
startTime: moment(this.startDate).locale(this.locale).format('LT'),
})
return moment(this.startDate).locale(this.locale).format('lll')
},
/**
*
@ -212,15 +214,10 @@ export default {
*/
formattedEnd() {
if (this.isAllDay) {
return this.$t('calendar', 'to {endDate}', {
endDate: moment(this.endDate).locale(this.locale).format('L'),
})
return moment(this.endDate).locale(this.locale).format('ll')
}
return this.$t('calendar', 'to {endDate} at {endTime}', {
endDate: moment(this.endDate).locale(this.locale).format('L'),
endTime: moment(this.endDate).locale(this.locale).format('LT'),
})
return moment(this.endDate).locale(this.locale).format('lll')
},
/**
* @return {boolean}
@ -234,6 +231,17 @@ export default {
highlightEndTimezone() {
return this.endTimezone !== this.userTimezone
},
/**
* True if the event is an all day event, starts and ends on the same date
*
* @return {boolean}
*/
isAllDayOneDayEvent() {
return this.isAllDay
&& this.startDate.getDate() === this.endDate.getDate()
&& this.startDate.getMonth() === this.endDate.getMonth()
&& this.startDate.getFullYear() === this.endDate.getFullYear()
},
},
methods: {
/**

View File

@ -22,7 +22,7 @@
-->
<template>
<div class="property-repeat">
<div class="property-repeat" :class="{ 'property-repeat--readonly': isReadOnly }">
<div class="property-repeat__summary">
<RepeatIcon class="property-repeat__summary__icon"
:name="$t('calendar', 'Repeat')"

View File

@ -26,7 +26,7 @@
{{ recurrenceRule | formatRecurrenceRule(locale) }}
</span>
<span v-else>
{{ $t('calendar', 'No recurrence') }}
{{ $t('calendar', 'Does not repeat') }}
</span>
</template>

Some files were not shown because too many files have changed in this diff Show More