Skip to content
Snippets Groups Projects
Commit 363f79d4 authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Merge branch '946-make-calendar-overview-page-non-scrollable' into 'master'

Resolve "Make calendar overview page non-scrollable"

Closes #946, #945, #940, #950, #919, and #951

See merge request !1350
parents 5a06f6e5 77fb15d6
No related branches found
No related tags found
1 merge request!1350Resolve "Make calendar overview page non-scrollable"
Pipeline #161025 failed
......@@ -144,7 +144,7 @@ export default {
},
methods: {
comparator(array, value) {
return array && array.includes(value);
return Array.isArray(array) && array.includes(value);
},
},
};
......
<template>
<div>
<!-- Calendar title with current calendar time range -->
<v-sheet height="600">
<v-sheet :height="height">
<v-expand-transition>
<v-progress-linear
v-if="$apollo.queries.calendar.loading"
......@@ -24,6 +24,13 @@
@click:event="viewEvent"
@change="setCalendarRange"
>
<template #day-body="{ date, week }">
<div
class="v-current-time"
:class="{ first: date === week[0].date }"
:style="{ top: nowY }"
></div>
</template>
<template #event="{ event, eventParsed, timed }">
<component
:is="eventBarComponentForFeed(event.calendarFeedName)"
......@@ -76,6 +83,11 @@ export default {
required: false,
default: () => false,
},
height: {
type: String,
required: false,
default: "600",
},
},
data() {
return {
......@@ -101,6 +113,8 @@ export default {
},
firstTime: 1,
ready: false,
};
},
emits: ["changeCalendarType", "changeCalendarFocus", "selectEvent"],
......@@ -194,6 +208,12 @@ export default {
return mergedRanges;
},
cal() {
return this.ready ? this.$refs.calendar : null;
},
nowY() {
return this.cal ? this.cal.timeToY(this.cal.times.now) + "px" : "-10px";
},
},
watch: {
params(newParams) {
......@@ -427,9 +447,49 @@ export default {
this.selectedEvent = null;
}
},
getCurrentTime() {
return this.cal
? this.cal.times.now.hour * 60 + this.cal.times.now.minute
: 0;
},
scrollToTime() {
const time = this.getCurrentTime();
const first = Math.max(0, time - (time % 30) - 30);
this.cal.scrollToTime(first);
},
updateTime() {
// TODO: is this destroyed when unloading?
setInterval(() => this.cal.updateTimes(), 60 * 1000);
},
},
mounted() {
this.ready = true;
this.$refs.calendar.move(0);
this.scrollToTime();
this.updateTime();
},
};
</script>
<style lang="scss">
.v-current-time {
height: 2px;
background-color: #ea4335;
position: absolute;
left: -1px;
right: 0;
pointer-events: none;
&.first::before {
content: "";
position: absolute;
background-color: #ea4335;
width: 12px;
height: 12px;
border-radius: 50%;
margin-top: -5px;
margin-left: -6.5px;
}
}
</style>
......@@ -2,18 +2,25 @@
export default {
name: "CalendarControlBar",
emits: ["prev", "next", "today"],
props: {
small: {
type: Boolean,
default: false,
required: false,
},
},
};
</script>
<template>
<div class="d-flex justify-center mx-2">
<v-btn icon @click="$emit('prev')">
<v-btn icon @click="$emit('prev')" :small="small">
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-btn outlined text class="mx-1" @click="$emit('today')">
<v-btn outlined text class="mx-1" @click="$emit('today')" :small="small">
{{ $t("calendar.today") }}
</v-btn>
<v-btn icon @click="$emit('next')">
<v-btn icon @click="$emit('next')" :small="small">
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</div>
......
<template>
<div class="mt-4 mb-4">
<h1
class="mb-4 mx-4"
v-if="$vuetify.breakpoint.mdAndDown && $refs.calendar"
>
{{ $refs.calendar.title }}
</h1>
<v-row align="stretch">
<!-- Control bar with prev, next and today buttons -->
<v-col cols="12" sm="4" lg="3" xl="2" align-self="center">
<v-sheet class="mb-10">
<!-- Create personal event button (fab) -->
<personal-event-dialog @save="$refs.calendar.refresh()" />
<v-row align="stretch" class="page-height flex-nowrap">
<v-navigation-drawer
:clipped="$vuetify.breakpoint.lgAndUp"
hide-overlay
disable-route-watcher
v-model="sidebar"
lg="3"
xl="2"
:floating="$vuetify.breakpoint.lgAndUp"
class="pt-6"
:temporary="$vuetify.breakpoint.mdAndDown"
:app="$vuetify.breakpoint.mdAndDown"
>
<calendar-control-bar
@prev="$refs.calendar.prev()"
@next="$refs.calendar.next()"
@today="calendarFocus = ''"
/>
</v-col>
<!-- Calendar title with current calendar time range -->
<v-col v-if="$vuetify.breakpoint.lgAndUp" align-self="center">
<h1 class="mx-2" v-if="$refs.calendar">{{ $refs.calendar.title }}</h1>
</v-col>
<!-- Button menu for selecting currently active calendars (only tablets/mobile) -->
<v-col
v-if="$vuetify.breakpoint.mdAndDown"
cols="12"
sm="4"
align-self="center"
class="d-flex justify-center"
>
<button-menu
icon="mdi-calendar-check-outline"
text-translation-key="calendar.select"
>
<calendar-select
v-model="selectedCalendarFeedNames"
:calendar-feeds="calendar.calendarFeeds"
@input="storeActivatedCalendars"
/>
</button-menu>
</v-col>
<v-spacer v-if="$vuetify.breakpoint.lgAndUp" />
<!-- Calendar type select (month, week, day) -->
<v-col
cols="12"
sm="4"
lg="3"
align-self="center"
:align="$vuetify.breakpoint.smAndUp ? 'right' : 'center'"
>
<calendar-type-select v-model="calendarType" />
</v-col>
<!-- Create personal event button -->
<v-col
cols="12"
sm="3"
lg="2"
align-self="center"
:align="$vuetify.breakpoint.smAndUp ? 'right' : 'center'"
>
<personal-event-dialog @save="$refs.calendar.refresh()" />
</v-col>
</v-row>
<v-row>
<v-col v-if="$vuetify.breakpoint.lgAndUp" lg="3" xl="2">
<!-- Mini date picker -->
<v-date-picker
@wheel.native.prevent="handleWheel"
no-title
v-model="calendarFocus"
:style="{ margin: '0px -8px' }"
:first-day-of-week="1"
full-width
></v-date-picker>
<!-- Calendar select (only desktop) -->
......@@ -81,33 +36,71 @@
{{ $t("calendar.my_calendars") }}
</v-subheader>
<calendar-select
class="mb-4"
class="mb-4 overflow-auto"
v-model="selectedCalendarFeedNames"
:calendar-feeds="calendar.calendarFeeds"
@input="storeActivatedCalendars"
/>
<calendar-download-all-button
v-if="calendar?.allFeedsUrl"
:url="calendar.allFeedsUrl"
/>
</v-list>
</v-col>
<template #append>
<div class="pa-4 d-flex justify-center align-center">
<v-spacer />
<calendar-download-all-button
v-if="calendar?.allFeedsUrl"
:url="calendar.allFeedsUrl"
/>
<v-spacer />
</div>
</template>
</v-navigation-drawer>
<v-col lg="9" xl="10" class="d-flex flex-column fill-height">
<div class="d-flex justify-space-between flex-wrap mb-2 align-center">
<!-- Calendar title with current calendar time range -->
<h2 v-if="$refs.calendar">
<v-btn
icon
@click="sidebar = true"
small
v-if="$vuetify.breakpoint.mdAndDown"
>
<v-icon>mdi-menu</v-icon>
</v-btn>
{{ $refs.calendar.title }}
</h2>
<!-- Actual calendar -->
<v-col lg="9" xl="10">
<calendar
:calendar-feeds="selectedFeedsForCalendar"
@changeCalendarFocus="setCalendarFocus"
@changeCalendarType="setCalendarType"
ref="calendar"
/>
<!-- Control bar with prev, next and today buttons -->
<calendar-control-bar
v-if="$vuetify.breakpoint.mdAndDown"
@prev="$refs.calendar.prev()"
@next="$refs.calendar.next()"
@today="calendarFocus = ''"
small
/>
<!-- Calendar type select (month, week, day) -->
<calendar-type-select v-model="calendarType" class="mt-1 ma-md-0" />
</div>
<v-row class="overflow-auto calendar-height">
<!-- Actual calendar -->
<v-col class="fill-height">
<calendar
:calendar-feeds="selectedFeedsForCalendar"
@changeCalendarFocus="setCalendarFocus"
@changeCalendarType="setCalendarType"
ref="calendar"
height="100%"
class="fill-height"
/>
</v-col>
</v-row>
</v-col>
</v-row>
</div>
</v-sheet>
</template>
<script>
import ButtonMenu from "../generic/ButtonMenu.vue";
import { DateTime } from "luxon";
import CalendarSelect from "./CalendarSelect.vue";
import PersonalEventDialog from "./personal_event/PersonalEventDialog.vue";
......@@ -126,10 +119,91 @@ export default {
Calendar,
CalendarTypeSelect,
CalendarControlBar,
ButtonMenu,
CalendarSelect,
CalendarDownloadAllButton,
PersonalEventDialog,
},
methods: {
handleWheel(event) {
if (event.wheelDelta < 0) {
this.$refs.calendar.next();
} else {
this.$refs.calendar.prev();
}
},
},
computed: {
sidebar: {
get() {
return this.internalSidebar || this.$vuetify.breakpoint.lgAndUp;
},
set(value) {
this.internalSidebar = value;
},
},
},
data() {
return {
internalSidebar: false,
};
},
mounted() {
if (this.$route.name === "core.calendar_overview") {
this.setCalendarFocus(DateTime.now().toISODate());
this.setCalendarType(this.$vuetify.breakpoint.mdAndDown ? "day" : "week");
} else {
// If we are here, we have a date supplied via the route params
this.setCalendarFocus(
[
this.$route.params.year,
this.$route.params.month,
this.$route.params.day,
].join("-"),
);
this.setCalendarType(this.$route.params.view);
}
},
watch: {
calendarFocus(newValue, oldValue) {
// Do not redirect on first page load
if (oldValue === "") return;
const [year, month, day] = newValue.split("-");
this.$router.push({
name: "core.calendar_overview_with_params",
params: {
view: this.calendarType,
year,
month,
day,
},
});
},
calendarType(newValue) {
const [year, month, day] = this.calendarFocus.split("-");
this.$router.push({
name: "core.calendar_overview_with_params",
params: {
view: newValue,
year,
month,
day,
},
});
},
},
};
</script>
<style scoped>
.page-height {
/* not all browsers support dvh so we use vh as fallback */
height: calc(98vh - 12rem);
height: calc(100dvh - 12rem);
overflow: auto;
}
.calendar-height {
min-height: 400px;
}
</style>
......@@ -28,11 +28,18 @@ import RecurrenceField from "../../generic/forms/RecurrenceField.vue";
color="secondary"
@click="requestDialog"
:disabled="dialogOpen"
i18n-key="personal_events.create_button"
/>
fab
large
bottom
fixed
right
>
<v-icon>$plus</v-icon>
</create-button>
<edit-button
v-else
color="secondary"
outlined
@click="requestDialog"
:disabled="dialogOpen"
/>
......@@ -127,6 +134,7 @@ export default {
.toISO({ suppressSeconds: true }),
datetimeEnd: DateTime.now()
.startOf("minute")
.plus({ hours: 1 })
.toISO({ suppressSeconds: true }),
recurrences: "",
persons: [],
......
......@@ -102,6 +102,8 @@
v-if="selectedEvent.meta.can_delete"
@click="deleteDialog = true"
class="ml-2"
color="error"
text
/>
<delete-dialog
v-if="selectedEvent.meta.can_delete"
......
......@@ -3,7 +3,7 @@
<template #activator="{ on, attrs }">
<slot name="activator" v-bind="{ on, attrs }">
<v-btn outlined text v-bind="attrs" v-on="on">
<v-icon center>
<v-icon left>
{{ icon }}
</v-icon>
<span v-if="textTranslationKey">{{ $t(textTranslationKey) }}</span>
......
......@@ -8,7 +8,7 @@ export default {
iconText: {
type: String,
required: false,
default: "$delete",
default: "$deleteContent",
},
i18nKey: {
type: String,
......
......@@ -1024,7 +1024,7 @@ const routes = [
name: "invitations.accept_invite",
},
{
path: "/calendar/",
path: "/calendar",
component: () => import("./components/calendar/CalendarOverview.vue"),
name: "core.calendar_overview",
meta: {
......@@ -1035,6 +1035,18 @@ const routes = [
toolbarTitle: "calendar.menu_title",
permission: "core.view_calendar_feed_rule",
},
children: [
{
path: ":view(month|week|day)/:year(\\d\\d\\d\\d)/:month(\\d\\d)/:day(\\d\\d)/",
component: () => import("./components/calendar/CalendarOverview.vue"),
name: "core.calendar_overview_with_params",
meta: {
titleKey: "calendar.menu_title",
toolbarTitle: "calendar.menu_title",
permission: "core.view_calendar_feed_rule",
},
},
],
},
];
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment