Skip to content

Commit

Permalink
feat: add new event datetime was clicked (#184)
Browse files Browse the repository at this point in the history
* feat: add styles for dark mode

* test: add visual regression tests for dark mode

* feat: create function for calculating time based on clickOffset and weekHeight

* test: add test cases for getTimeFromClick

* refactor: implement time.doubleDigit

* test: add tests to Qalendar.test.ts

* test: add more tests to Qalendar.test.ts

* feat: make datetime-was-clicked compatible with flexible day boundaries

* feat: deprecate day-was-clicked and add date-was-clicked for month view

* docs: add documentation for datetime-was-clicked and date-was-clicked

* test: add unit tests for Month.test.ts

* test: add more tests to Month.test.ts

* test: add unit tests for language function

* test: add unit tests for language function

* fix: implicit type any

* test: add unit tests for Errors.ts

* test: add unit tests for EventFlyoutPosition.ts

* test: add unit tests for month/Day.vue

* test: add unit tests for Week.vue and DatePicker.vue

* test: add unit tests for DatePicker.vue

* refactor: remove unnecessary watcher in DatePicker.vue
  • Loading branch information
tomosterlund authored Jun 29, 2023
1 parent cf62c8a commit 125fe3d
Show file tree
Hide file tree
Showing 28 changed files with 1,141 additions and 209 deletions.
8 changes: 5 additions & 3 deletions development/QalendarView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
@edit-event="editEvent"
@delete-event="deleteEvent"
@day-was-clicked="reactToEvent"
@date-was-clicked="reactToEvent"
@datetime-was-clicked="reactToEvent"
@event-was-dragged="handleEventWasDragged"
@interval-was-clicked="handleIntervalWasClicked"
>
Expand Down Expand Up @@ -147,16 +149,16 @@ export default defineComponent({
},
},
},
defaultMode: 'month',
// defaultMode: 'day',
showCurrentTime: true,
isSilent: true,
dayIntervals: {
height: 50,
length: 30,
},
dayBoundaries: {
start: 4,
end: 4,
start: 5,
end: 5,
},
eventDialog: {
isDisabled: false,
Expand Down
2 changes: 1 addition & 1 deletion development/data/seeded-events.ts

Large diffs are not rendered by default.

26 changes: 15 additions & 11 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ prevent this by wrapping it in an element with the inline style `style="color-sc
Qalendar takes a `config` prop, which contains all the most crucial options for configuring its
behavior. `config` is passed as an object, which could look like this:

## Basic configuration

```js
data()
{
Expand Down Expand Up @@ -186,17 +188,19 @@ Please note, however, that you cannot mix these two types of time formats for an

Qalendar emits the following events that can be listened to:

| Event name | Purpose |
|:----------------------:|:--------------------------------------------------------------------------:|
| `event-was-clicked` | |
| `event-was-dragged` | emits the updated event, after an event was dragged |
| `event-was-resized` | emits the updated event, after an event was resized |
| `interval-was-clicked` | [see section on intervals](#intervals) |
| `day-was-clicked` | Emits a the date that a user clicked, e.g. `2022-11-16` |
| `updated-period` | emits the value with the new period selected in the date picker |
| `updated-mode` | emits the new selected mode and the period, when the user changes the mode |
| `edit-event` | is triggered, when a user clicks the edit-icon of an event |
| `delete-event` | is triggered, when a user clicks the delete-icon of an event |
| Event name | Purpose |
|:----------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------:|
| `event-was-clicked` | |
| `event-was-dragged` | emits the updated event, after an event was dragged |
| `event-was-resized` | emits the updated event, after an event was resized |
| `interval-was-clicked` | [see section on intervals](#intervals) |
| `updated-period` | emits the value with the new period selected in the date picker |
| `updated-mode` | emits the new selected mode and the period, when the user changes the mode |
| `edit-event` | is triggered, when a user clicks the edit-icon of an event |
| `delete-event` | is triggered, when a user clicks the delete-icon of an event |
| `datetime-was-clicked` | Emits a the datetime string from where the clicks in week & day modes, e.g. `2022-11-16 00:10` |
| `date-was-clicked` | In month mode, this emits a the date that the user clicked, e.g. `2022-11-16` |
| ~~day-was-clicked~~ | Emits a the date that a user clicked, e.g. `2022-11-16`. **Deprecated**. Will be removed with v4. Use `datetime-was-clicked` and `date-was-clicked` instead |

## Drag and drop

Expand Down
1 change: 0 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Qalendar</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
Expand Down
Binary file removed public/favicon.ico
Binary file not shown.
3 changes: 2 additions & 1 deletion renovate.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
"config:base",
":disableDependencyDashboard"
],
"packageRules": [
{
Expand Down
12 changes: 10 additions & 2 deletions src/Qalendar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
@delete-event="$emit('delete-event', $event)"
@interval-was-clicked="$emit('interval-was-clicked', $event)"
@day-was-clicked="$emit('day-was-clicked', $event)"
@datetime-was-clicked="$emit('datetime-was-clicked', $event)"
>
<template #weekDayEvent="p">
<slot
Expand Down Expand Up @@ -72,7 +73,7 @@
:config="enhancedConfig"
:period="period"
@event-was-clicked="$emit('event-was-clicked', $event)"
@day-was-clicked="$emit('day-was-clicked', $event)"
@date-was-clicked="handleDateWasClicked"
@event-was-dragged="handleEventWasUpdated($event, 'dragged')"
@updated-period="handleUpdatedPeriod($event, true)"
@edit-event="$emit('edit-event', $event)"
Expand Down Expand Up @@ -152,7 +153,9 @@ export default defineComponent({
'edit-event',
'delete-event',
'interval-was-clicked',
'day-was-clicked',
'day-was-clicked', // TODO: remove with v4. day-was-clicked is deprecated
'date-was-clicked',
'datetime-was-clicked',
],
data() {
Expand Down Expand Up @@ -328,6 +331,11 @@ export default defineComponent({
setTimePointsFromDayBoundary(boundary: number) {
return Time.getTimePointsFromHour(boundary);
},
handleDateWasClicked(payload: string) {
this.$emit('day-was-clicked', payload); // TODO: remove with v4. day-was-clicked is deprecated
this.$emit('date-was-clicked', payload);
}
},
});
</script>
Expand Down
26 changes: 7 additions & 19 deletions src/components/header/DatePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -219,18 +219,6 @@ export default defineComponent({
},
},
watch: {
period: {
deep: true,
handler() {
if (this.selectedDate.getTime() === this.period.selectedDate.getTime())
return;
this.hydrateDatePicker();
},
},
},
mounted() {
this.hydrateDatePicker(true);
},
Expand Down Expand Up @@ -355,6 +343,7 @@ export default defineComponent({
},
toggleDatePickerMode() {
// toggle to year
if (this.datePickerMode === 'month') {
this.monthPickerDates = this.time.getCalendarYearMonths(
this.datePickerCurrentDate.getFullYear()
Expand All @@ -363,14 +352,13 @@ export default defineComponent({
return (this.datePickerMode = 'year');
}
if (this.datePickerMode === 'year') {
this.weekPickerDates = this.time.getCalendarMonthSplitInWeeks(
this.datePickerCurrentDate.getFullYear(),
this.datePickerCurrentDate.getMonth()
);
// toggle to month
this.weekPickerDates = this.time.getCalendarMonthSplitInWeeks(
this.datePickerCurrentDate.getFullYear(),
this.datePickerCurrentDate.getMonth()
);
return (this.datePickerMode = 'month');
}
this.datePickerMode = 'month';
},
getLocale() {
Expand Down
4 changes: 2 additions & 2 deletions src/components/month/Day.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export default defineComponent({
'event-was-clicked',
'event-was-dragged',
'updated-period',
'day-was-clicked',
'date-was-clicked',
'day-was-selected',
],
Expand Down Expand Up @@ -190,7 +190,7 @@ export default defineComponent({
},
emitDayWasClicked() {
this.$emit('day-was-clicked', this.time.dateStringFrom(this.day.dateTimeString));
this.$emit('date-was-clicked', this.time.dateStringFrom(this.day.dateTimeString));
if (this.config.isSmall) this.$emit('day-was-selected', this.day);
},
},
Expand Down
8 changes: 2 additions & 6 deletions src/components/month/Month.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
:is-selected="selectedDay?.dateTimeString === day.dateTimeString"
@event-was-clicked="handleClickOnEvent"
@event-was-dragged="handleEventWasDragged"
@day-was-clicked="onDayWasClicked"
@date-was-clicked="$emit('date-was-clicked', $event)"
@day-was-selected="selectedDay = $event"
@updated-period="$emit('updated-period', $event)"
>
Expand Down Expand Up @@ -135,7 +135,7 @@ export default defineComponent({
'updated-period',
'event-was-clicked',
'event-was-dragged',
'day-was-clicked',
'date-was-clicked',
],
data() {
Expand All @@ -156,10 +156,6 @@ export default defineComponent({
},
methods: {
onDayWasClicked(day: dayInterface) {
this.$emit('day-was-clicked', day)
},
initScrollbar(elapsedMs = 0) {
const el = document.querySelector('.calendar-month');
if (elapsedMs > 3000) return;
Expand Down
32 changes: 31 additions & 1 deletion src/components/week/Day.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div
class="calendar-week__day"
@click.self="$emit('day-was-clicked', time.dateStringFrom(day.dateTimeString))"
@click.self="handleClickOnDay"
>
<DayEvent
v-for="(event, eventIndex) in events"
Expand Down Expand Up @@ -88,6 +88,10 @@ export default defineComponent({
type: Object as PropType<dayIntervalsType>,
required: true,
},
weekHeight: {
type: Number,
required: true,
},
},
emits: [
Expand All @@ -96,6 +100,7 @@ export default defineComponent({
'event-was-dragged',
'interval-was-clicked',
'day-was-clicked',
'datetime-was-clicked',
'drag-start',
'drag-end',
],
Expand Down Expand Up @@ -150,6 +155,31 @@ export default defineComponent({
this.time.HOURS_PER_DAY,
).getIntervals()
},
handleClickOnDay(event: MouseEvent) {
const timeClicked = this.time.getTimeFromClick(event.offsetY, this.weekHeight);
let dateString = this.time.dateStringFrom(this.day.dateTimeString);
const isFlexibleDay = this.time.DAY_END <= this.time.DAY_START;
if (isFlexibleDay) dateString = this.getDateStringForFlexibleDayBoundaries(dateString, timeClicked);
const dateTimeString = `${dateString} ${timeClicked}`;
this.$emit('day-was-clicked', dateString);
this.$emit('datetime-was-clicked', dateTimeString);
},
getDateStringForFlexibleDayBoundaries(dateString: string, timeClickedHHMM: string) {
const hourTwoDigits = this.time.doubleDigit(this.time.DAY_START / 100);
const dayStartTimeHHMM = `${hourTwoDigits}:00`
const isClickOnNextDay = timeClickedHHMM < dayStartTimeHHMM;
if (isClickOnNextDay) {
dateString = this.time.dateStringFrom(
this.time.addDaysToDateTimeString(1, dateString)
)
}
return dateString;
}
},
});
</script>
Expand Down
3 changes: 3 additions & 0 deletions src/components/week/Week.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@
:day-info="{ daysTotalN: days.length, thisDayIndex: dayIndex, dateTimeString: day.dateTimeString }"
:mode="mode"
:day-intervals="dayIntervals"
:week-height="+weekHeight.replace('px', '')"
@event-was-clicked="handleClickOnEvent"
@event-was-resized="$emit('event-was-resized', $event)"
@event-was-dragged="handleEventWasDragged"
@interval-was-clicked="$emit('interval-was-clicked', $event)"
@day-was-clicked="$emit('day-was-clicked', $event)"
@drag-start="destroyScrollbarAndHideOverflow"
@drag-end="initScrollbar"
@datetime-was-clicked="$emit('datetime-was-clicked', $event)"
>
<template #weekDayEvent="p">
<slot
Expand Down Expand Up @@ -146,6 +148,7 @@ export default defineComponent({
'delete-event',
'interval-was-clicked',
'day-was-clicked',
'datetime-was-clicked',
],
data() {
Expand Down
66 changes: 26 additions & 40 deletions src/helpers/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,46 @@ import { type eventInterface } from "../typings/interfaces/event.interface";
import {DATE_TIME_STRING_FULL_DAY_PATTERN, DATE_TIME_STRING_PATTERN} from "../constants";
import { type configInterface } from "../typings/config.interface";

type RecursivePartial<T> = {
[P in keyof T]?: RecursivePartial<T[P]>;
};

export default class Errors {
public static PREFIX = "[Qalendar warning]";
// public static SUFFIX = 'This is a development warning, which will never be displayed in production environments'
public static SUFFIX = "";

static checkEventProperties(event: eventInterface) {
public static readonly MISSING_ID_WARNING = `${this.PREFIX} required event property 'id' is missing \n${this.SUFFIX}`;

public static readonly MISSING_TITLE_WARNING = `${this.PREFIX} required event property 'title' is missing \n${this.SUFFIX}`;

public static readonly MISSING_TIME_WARNING = `${this.PREFIX} required event property 'time' is missing \n${this.SUFFIX}`;

public static readonly MISSING_TIME_START_WARNING = `${this.PREFIX} required event property 'time.start' is missing \n${this.SUFFIX}`;

public static readonly MISSING_TIME_END_WARNING = `${this.PREFIX} required event property 'time.end' is missing \n${this.SUFFIX}`;

static checkEventProperties(event: RecursivePartial<eventInterface>) {
// Warn if required property is missing
if (!event.id)
console.warn(
`${this.PREFIX} required event property 'id' is missing \n${this.SUFFIX}`
);
if (!event.title)
console.warn(
`${this.PREFIX} required event property 'title' is missing \n${this.SUFFIX}`
);
if (!event.time)
console.warn(
`${this.PREFIX} required event property 'time' is missing \n${this.SUFFIX}`
);
if (!event.time.start)
console.warn(
`${this.PREFIX} required event property 'time.start' is missing \n${this.SUFFIX}`
);
if (!event.time.end)
console.warn(
`${this.PREFIX} required event property 'time.end' is missing \n${this.SUFFIX}`
);
if (!event.id) console.warn(this.MISSING_ID_WARNING);
if (!event.title) console.warn(this.MISSING_TITLE_WARNING);
if (!event.time) console.warn(this.MISSING_TIME_WARNING);
if (!event?.time?.start) console.warn(this.MISSING_TIME_START_WARNING);
if (!event?.time?.end) console.warn(this.MISSING_TIME_END_WARNING);

// Warn if property type is faulty
if (!["number", "string"].includes(typeof event.id))
console.warn(
`${
this.PREFIX
} event property 'id' expects a string or a number, received ${typeof event.id} \n${
this.SUFFIX
}`
);
if (typeof event.title !== 'string')
console.warn(
`${
this.PREFIX
} event property 'title' expects a string, received ${typeof event.title} \n${
this.SUFFIX
}`
);
if (
!DATE_TIME_STRING_PATTERN.test(event.time.start)
event.time?.start
&& event.time?.end
&& !DATE_TIME_STRING_PATTERN.test(event.time.start)
&& !DATE_TIME_STRING_FULL_DAY_PATTERN.test(event.time.start)
)
console.warn(
`${this.PREFIX} event property 'time.start' expects a string formatted like 'YYYY-MM-DD hh:mm', or 'YYYY-MM-DD', received ${event.time.start} \n${this.SUFFIX}`
);
if (
!DATE_TIME_STRING_PATTERN.test(event.time.end)
event.time?.start
&& event.time?.end
&& !DATE_TIME_STRING_PATTERN.test(event.time.end)
&& !DATE_TIME_STRING_FULL_DAY_PATTERN.test(event.time.end)
)
console.warn(
Expand Down
Loading

0 comments on commit 125fe3d

Please sign in to comment.