Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(VCalendar): weekdays and day views conflict (#11025) #11129

Merged
merged 2 commits into from
Apr 20, 2020

Conversation

ClickerMonkey
Copy link
Contributor

@ClickerMonkey ClickerMonkey commented Apr 16, 2020

fixes #11025

Description

When day or 4day type are used, ignore weekday settings - they don't make sense in these views.

Motivation and Context

#11025

How Has This Been Tested?

unit & visually

Markup:

<template>
  <v-sheet class="v-fullscreen">
    <v-sheet
        tile
        height="54"
        color="grey lighten-3"
        class="d-flex"
      >
        <v-btn
          icon
          class="ma-2"
          @click="$refs.calendar.prev()"
        >
          <v-icon>mdi-chevron-left</v-icon>
        </v-btn>
        <v-select
          v-model="type"
          :items="types"
          dense
          outlined
          hide-details
          class="ma-2"
          label="type"
        ></v-select>
        <v-select
          v-model="mode"
          :items="modes"
          dense
          outlined
          hide-details
          label="event-overlap-mode"
          class="ma-2"
        ></v-select>
        <v-select
          v-model="weekday"
          :items="weekdays"
          dense
          outlined
          hide-details
          label="weekdays"
          class="ma-2"
        ></v-select>
        <v-spacer></v-spacer>
        <v-btn
          icon
          class="ma-2"
          @click="$refs.calendar.next()"
        >
          <v-icon>mdi-chevron-right</v-icon>
        </v-btn>
      </v-sheet>
      <v-sheet height="600">
        <v-calendar
          ref="calendar"
          v-model="value"
          :weekdays="weekday"
          :type="type"
          :events="events"
          :event-overlap-mode="mode"
          :event-overlap-threshold="60"
          :event-color="getEventColor"
          :event-ripple="false"
          @change="getEvents"          
          @mousedown:event="startDrag"
          @mousedown:time="startTime"
          @mousemove:time="mouseMove"
          @mouseup:time="endDrag"
          @mouseleave.native="cancelDrag"
        >
          <template #event="{ event, timed }">
            <div class="pl-1" 
              v-html="getEventHTML(event, timed)"
            ></div>
            <div v-if="timed" 
              class="v-event-drag-bottom"
              @mousedown.stop="extendBottom(event)"
            ></div>
          </template>
          <template #day-body="{ date, week }">
            <div class="v-current-time"
               :class="{ first: date === week[0].date }"
               :style="{ top: nowY }">
              <div class="v-current-time-time">
                {{ nowTime }}
              </div>
            </div>
          </template>
          &nbsp;
        </v-calendar>
<!--         {{ lastEvent }} -->
      </v-sheet>
  </v-sheet>
</template>

<script>
export default {
  data: () => ({
    type: 'day',
    types: ['month', 'week', 'day', '4day'],
    mode: 'stack',
    modes: ['stack', 'column'],
    weekday: [0, 1, 2, 3, 4, 5, 6],
    weekdays: [
      { text: 'Sun - Sat', value: [0, 1, 2, 3, 4, 5, 6] },
      { text: 'Mon - Sun', value: [1, 2, 3, 4, 5, 6, 0] },
      { text: 'Mon - Fri', value: [1, 2, 3, 4, 5] },
      { text: 'Mon, Wed, Fri', value: [1, 3, 5] },
    ],
    value: '',
    events: [],
    colors: ['#2196F3', '#3F51B5', '#673AB7', '#00BCD4', '#4CAF50', '#FF9800', '#757575'],
    names: ['Meeting', 'Holiday', 'PTO', 'Travel', 'Event', 'Birthday', 'Conference', 'Party'],
    dragEvent: null,
    dragStart: null,
    lastEvent: '',
    createEvent: null,
    createStart: null,
    extendOriginal: null,
    isMounted: false,
  }),
  computed: {
    nowY() {
      const cal = this.$refs.calendar;
      if (!cal && !this.isMounted) {
        return -1;
      }

      return cal.timeToY(cal.times.now) + 'px';
    },
    nowTime() {
      const cal = this.$refs.calendar;
      if (!cal && !this.isMounted) {
        return -1;
      }

      return cal.formatTime(cal.times.now);
    },
  },
  mounted() {
    const cal = this.$refs.calendar;

    window.Vuetify = Vuetify;
    window.app = this;
    window.cal = cal;

    this.isMounted = true;

    // scroll to the current time
    const minutes = cal.times.now.hour * 60 + cal.times.now.minute;
    const firstTime = Math.max(0, minutes - (minutes % 30) - 30);
    cal.scrollToTime(firstTime);

    // every minute update the current time bar
    setInterval(() => cal.updateTimes(), 60 * 1000);
  },
  methods: {
    getEvents ({ start, end }) {
      const events = []

      const min = new Date(`${start.date}T00:00:00`)
      const max = new Date(`${end.date}T23:59:59`)
      const days = (max.getTime() - min.getTime()) / 86400000
      const eventCount = this.rnd(days, days + 20)

      for (let i = 0; i < eventCount; i++) {
        const allDay = this.rnd(0, 3) === 0
        const firstTimestamp = this.rnd(min.getTime(), max.getTime())
        const first = new Date(firstTimestamp - (firstTimestamp % 900000))
        const secondTimestamp = this.rnd(2, allDay ? 288 : 8) * 900000
        const second = new Date(first.getTime() + secondTimestamp)

        events.push({
          name: this.names[this.rnd(0, this.names.length - 1)],
          start: this.formatDate(first, !allDay),
          end: this.formatDate(second, !allDay),
          color: this.colors[this.rnd(0, this.colors.length - 1)],
        })
      }

      this.events = events
    },
    getEventColor (event) {
      const rgb = parseInt(event.color.substring(1), 16);
      const r = (rgb >> 16) & 0xFF;
      const g = (rgb >>  8) & 0xFF;
      const b = (rgb >>  0) & 0xFF;

      return event === this.dragEvent
        ? `rgba(${r}, ${g}, ${b}, 0.5)`
        : event === this.createEvent
          ? `rgba(${r}, ${g}, ${b}, 0.5)`
          : event.color;
    },
    getEventHTML(event, timed) {
      const cal = this.$refs.calendar;
      let name = event.name;
      if (event.start.hasTime) {
        if (timed) {
          const showStart = event.start.hour < 12 && event.end.hour >= 12;
          const start = cal.formatTime(event.start, showStart);
          const end = cal.formatTime(event.end, true);
          const singline = diffMinutes(event.start, event.end) <= this.parsedEventOverlapThreshold
          const separator = singline ? ', ' : '<br>'
          return `<strong>${name}</strong>${separator}${start} - ${end}`
        } else {
          const time = this.formatTime(event.start, true)
          return `<strong>${time}</strong> ${name}`
        }
      }
      return name;
    },
    rnd (a, b) {
      return Math.floor((b - a + 1) * Math.random()) + a
    },
    formatDate (a, withTime) {
      return withTime
        ? `${a.getFullYear()}-${a.getMonth() + 1}-${a.getDate()} ${a.getHours()}:${a.getMinutes()}`
        : `${a.getFullYear()}-${a.getMonth() + 1}-${a.getDate()}`
    },
    eventMove(e) {
      console.log('eventMove', e);
    },
    startDrag(e) {
      if (e.event && e.timed) {
        this.dragEvent = e.event;
        this.dragTime = null;
        this.extendOriginal = null;
      }

      this.lastEvent = 'startDrag';
    },
    startTime(e) {
      const mouse = this.toDate(e);

      if (this.dragEvent && this.dragTime === null) {
        const start = this.toDate(this.dragEvent.start);

        this.dragTime = mouse.getTime() - start.getTime();

      } else {
        this.createStart = this.roundTime(mouse.getTime());
        this.createEvent = {
          name: '(no title)',
          start: this.toTimestamp(new Date(this.createStart)),
          end: this.toTimestamp(new Date(this.createStart)),
          color: this.colors[this.rnd(0, this.colors.length - 1)],
        };
        this.events.push(this.createEvent);
      }
      this.lastEvent = 'startTime';
    },
    extendBottom(event) {
      this.createEvent = event;
      this.createStart = this.toDate(event.start).getTime();
      this.extendOriginal = event.end;
    },
    mouseMoveEvent(e) {
      console.log('mouseMoveEvent', e);
    },
    mouseMove(e) {
      if (this.dragEvent && this.dragTime !== null) {
        const start = this.toDate(this.dragEvent.start);
        const end = this.toDate(this.dragEvent.end);
        const duration = end.getTime() - start.getTime();
        const mouse = this.toDate(e);

        const newStartTime = mouse.getTime() - this.dragTime;
        const newStart = new Date(this.roundTime(newStartTime));
        const newEnd = new Date(newStart.getTime() + duration);

        this.dragEvent.start = this.toTimestamp(newStart);
        this.dragEvent.end = this.toTimestamp(newEnd);
      }
      else if (this.createEvent && this.createStart !== null) {
        const mouse = this.toDate(e).getTime();
        const mouseRounded = this.roundTime(mouse, false);
        const min = Math.min(mouseRounded, this.createStart);
        const max = Math.max(mouseRounded, this.createStart);

        this.createEvent.start = this.toTimestamp(new Date(min));
        this.createEvent.end = this.toTimestamp(new Date(max));
      }
    },
    endDrag(e) {
      this.dragTime = null;
      this.dragEvent = null;
      this.createEvent = null;
      this.createStart = null;
      this.extendOriginal = null;

      this.lastEvent = 'endDrag';
    },
    cancelDrag(e) {
      if (this.createEvent) {
        if (this.extendOriginal) {
          this.createEvent.end = this.extendOriginal;
        } else {
          const i = this.events.indexOf(this.createEvent);
          if (i !== -1) {
            this.events.splice(i, 1);
          }
        }
      }

      this.createEvent = null;
      this.createStart = null;
      this.dragTime = null;
      this.dragEvent = null;

      this.lastEvent = 'cancelDrag';
    },
    roundTime(time, down = true) {
      const roundDownTime = 15 * 60 * 1000; // 15 minutes

      return down
        ? time - time % roundDownTime
        : time + (roundDownTime - (time % roundDownTime));
    },
    toDate(tms) {
      return typeof tms === 'string'
        ? new Date(tms)
        : new Date(tms.year, tms.month - 1, tms.day, tms.hour, tms.minute);
    },
    toTimestamp(date) {
      return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`;
    },
  },
}
</script>

<style scoped>
  .v-fullscreen {
    position: absolute;
    padding-top: 1px;
    top: 0px;
    left: 0px;
    right: 0px;
    bottom: 0px;
  }

  .v-calendar {
    user-select: none;
    -webkit-user-select: none;
  }
  .v-event-drag-bottom {
    position: absolute; 
    left: 0; 
    right: 0;
    bottom: 4px; 
    height: 4px; 
    cursor: ns-resize;
  }
  .v-event-drag-bottom::after {
    display: none;
    position: absolute; 
    left: 50%; 
    height: 4px; 
    border-top: 1px solid white; 
    border-bottom: 1px solid white; 
    width: 20px; 
    margin-left: -10px;
    opacity: 0.8;
    content: '';
  }
  .v-event-timed:hover .v-event-drag-bottom::after {
    display: block;
  }
  .v-current-time {
    height: 1px;
    background-color: red;
    position: absolute;
    left: -1px;
    right: 0;
    pointer-events: none;
  }
    
  .v-current-time.first::after {
    content: '';
    position: absolute;
    background-color: red;
    width: 7px;
    height: 7px;
    border-radius: 3.5px;
    margin-top: -3px;
    margin-left: -3px;
  }
    
  .v-current-time .v-current-time-time {
    display: none;
  }
    
  .v-current-time.first .v-current-time-time {
    display: block;
    position: absolute;
    left: -60px;
    color: red;
    font-size: 10px;
    padding: 1px;
    background-color: white;
    width: 50px;
    height: 20px;
    text-align: right;
    top: -9px;
  }
</style>

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Improvement/refactoring (non-breaking change that doesn't add any features but makes things better)

Checklist:

  • The PR title is no longer than 64 characters.
  • The PR is submitted to the correct branch (master for bug fixes and documentation updates, dev for new features and backwards compatible changes and next for non-backwards compatible changes).
  • My code follows the code style of this project.
  • I've added relevant changes to the documentation (applies to new features and breaking changes in core library)

@ClickerMonkey
Copy link
Contributor Author

I added another fix to this PR (#10123) because it depended on code fixes in this PR

@jacekkarczmarczyk jacekkarczmarczyk added C: VCalendar VCalendar T: bug Functionality that does not work as intended/expected labels Apr 16, 2020
@johnleider johnleider added this to the v2.2.x milestone Apr 20, 2020
@johnleider johnleider merged commit c4eb5da into master Apr 20, 2020
@johnleider johnleider deleted the fix/calendar-day-type-weekdays branch April 20, 2020 19:05
@lock lock bot locked as resolved and limited conversation to collaborators May 20, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
C: VCalendar VCalendar T: bug Functionality that does not work as intended/expected
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug Report] no day view on Saturday if weekdays are only m-f
3 participants