Skip to content

Commit

Permalink
fix: improved heatmap dragging ux
Browse files Browse the repository at this point in the history
  • Loading branch information
felixroos committed Aug 9, 2019
1 parent f635cc4 commit f05b148
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 56 deletions.
52 changes: 40 additions & 12 deletions packages/calendar/src/lib/calendar/calendar.component.html
@@ -1,13 +1,22 @@
<svg aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg"
xlink="http://www.w3.org/1999/xlink">
<svg
aria-hidden="true"
style="position: absolute; width: 0; height: 0; overflow: hidden;"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xlink="http://www.w3.org/1999/xlink"
>
<defs>
<symbol id="arrowhead-left" viewBox="0 0 24 24">
<title>arrowhead-left</title>
<path d="M13.829 19c-0.292 0-0.582-0.127-0.78-0.373l-4.828-6c-0.298-0.371-0.294-0.901 0.011-1.267l5-6c0.353-0.424 0.984-0.481 1.409-0.128s0.481 0.984 0.127 1.408l-4.475 5.371 4.315 5.362c0.346 0.43 0.278 1.060-0.153 1.406-0.184 0.149-0.406 0.221-0.626 0.221z"></path>
<path
d="M13.829 19c-0.292 0-0.582-0.127-0.78-0.373l-4.828-6c-0.298-0.371-0.294-0.901 0.011-1.267l5-6c0.353-0.424 0.984-0.481 1.409-0.128s0.481 0.984 0.127 1.408l-4.475 5.371 4.315 5.362c0.346 0.43 0.278 1.060-0.153 1.406-0.184 0.149-0.406 0.221-0.626 0.221z"
></path>
</symbol>
<symbol id="arrowhead-right" viewBox="0 0 24 24">
<title>arrowhead-right</title>
<path d="M9.999 19c-0.226 0-0.453-0.076-0.64-0.232-0.424-0.353-0.481-0.984-0.128-1.408l4.476-5.371-4.315-5.362c-0.346-0.43-0.278-1.060 0.152-1.406 0.431-0.346 1.060-0.278 1.407 0.152l4.828 6c0.298 0.371 0.294 0.901-0.011 1.267l-5 6c-0.198 0.237-0.482 0.36-0.769 0.36z"></path>
<path
d="M9.999 19c-0.226 0-0.453-0.076-0.64-0.232-0.424-0.353-0.481-0.984-0.128-1.408l4.476-5.371-4.315-5.362c-0.346-0.43-0.278-1.060 0.152-1.406 0.431-0.346 1.060-0.278 1.407 0.152l4.828 6c0.298 0.371 0.294 0.901-0.011 1.267l-5 6c-0.198 0.237-0.482 0.36-0.769 0.36z"
></path>
</symbol>
<symbol id="today" viewBox="0 0 24 24">
<title>today</title>
Expand All @@ -27,30 +36,49 @@
</style>
<div class="ec-calendar">
<nav class="ec-calendar-controls">
<a (click)="grid.alter(-1, 'months')" class="btn btn_clear btn_square ec-calendar-controls__prev" [class.is-disabled]="!grid.canAlter(-1,'month')">
<a
(click)="grid.alter(-1, 'months')"
class="btn btn_clear btn_square ec-calendar-controls__prev"
[class.is-disabled]="!grid.canAlter(-1, 'month')"
>
<svg class="ixo">
<use xlink:href="#arrowhead-left"></use>
</svg>
</a>
<div class="ec-calendar-title">{{grid.formatted}}</div>
<div class="ec-calendar-title">{{ grid.formatted }}</div>
<a (click)="grid.setToday()" class="btn btn_clear btn_square ec-calendar-controls__current">
<svg class="ixo">
<use xlink:href="#today"></use>
</svg>
</a>
<a (click)="grid.alter(1, 'months')" class="btn btn_clear btn_square ec-calendar-controls__next" [class.is-disabled]="!grid.canAlter(1,'month')">
<a
(click)="grid.alter(1, 'months')"
class="btn btn_clear btn_square ec-calendar-controls__next"
[class.is-disabled]="!grid.canAlter(1, 'month')"
>
<svg class="ixo">
<use xlink:href="#arrowhead-right"></use>
</svg>
</a>
</nav>
<div class="ec-calendar-month ">
<ul class="ec-calendar-weekdays ">
<li *ngFor="let weekday of weekdays ">
{{weekday}}
<li *ngFor="let weekday of weekdays">
{{ weekday }}
</li>
</ul>
<ec-month [disableDragStart]="disableDragStart" [disableDragEnd]="disableDragEnd" (spanChanged)="spanChanged.emit($event)"
[timespan]="timespan" [disabled]="disabled" [heatmap]="heatmap" [date]="date" (dayClicked)="select($event)" #grid></ec-month>
<ec-month
[disabled]="disabled"
[disableDrag]="disableDrag"
[disableDragAnywhere]="disableDragAnywhere"
[disableDragStart]="disableDragStart"
[disableDragEnd]="disableDragEnd"
(spanChanged)="spanChanged.emit($event)"
[timespan]="timespan"
[heatmap]="heatmap"
[date]="date"
(dayClicked)="select($event)"
#grid
></ec-month>
</div>
</div>
</div>
35 changes: 20 additions & 15 deletions packages/calendar/src/lib/calendar/month.component.html
@@ -1,26 +1,31 @@
<ul class="ec-calendar-days">
<li *ngFor="let day of cells;let i = index"
<li
*ngFor="let day of cells; let i = index"
[class.is-active]="day.active"
[class.is-last]="day.last"
[class.is-first]="day.first"
[class.is-draggable]="day.draggable"
[class.is-inside-timespan]="day.inside && !day.last && !day.first"
[class.is-draggable]="isDraggable(day)"
[class.is-disabled]="day.disabled"
[class.heat-none]="day.heat===0"
[class.heat-low]="day.heat>0&&day.heat<30"
[class.heat-medium]="day.heat>=30&&day.heat<70"
[class.heat-high]="day.heat>=70&&day.heat<90"
[class.heat-extreme]="day.heat>=90"
[draggable]="day.first||day.last"
[class.heat-none]="day.heat === 0"
[class.heat-low]="day.heat > 0 && day.heat < 30"
[class.heat-medium]="day.heat >= 30 && day.heat < 70"
[class.heat-high]="day.heat >= 70 && day.heat < 90"
[class.heat-extreme]="day.heat >= 90"
[draggable]="isDraggable(day)"
(dragstart)="dragStart(day, $event)"
(click)="selectDay(day.date)"
(mouseover)="mouseOver(day, $event)"
(mouseup)="mouseUp(day, $event)">
<div class="ec-calendar-day"
[style.background-color]="day.color"
[class.is-other]="day.type!=='current'"
[class.is-today]="day.today"
[class.is-selected]="isSelected(day.date)">
{{day.class||day.format}}
(mouseup)="mouseUp(day, $event)"
>
<div
class="ec-calendar-day"
[style.background-color]="day.color"
[class.is-other]="day.type !== 'current'"
[class.is-today]="day.today"
[class.is-selected]="isSelected(day.date)"
>
{{ day.class || day.format }}
</div>
</li>
</ul>
91 changes: 69 additions & 22 deletions packages/calendar/src/lib/calendar/month.component.ts
Expand Up @@ -22,8 +22,6 @@ export interface Day {
first: boolean;
/** if the day is the last in the timespan */
last: boolean;
/** determines if the day can be dragged to change the timespan */
draggable: boolean;
/** custom class */
heat?: string;
}
Expand All @@ -35,6 +33,7 @@ export interface Day {
})
export class MonthComponent implements OnInit, OnChanges {
dragged: any;
move: boolean;
/** The current selected date */
@Input() selected: moment.Moment;
/** Color mapping for day cells. E.g. to view a month heatmap */
Expand All @@ -51,6 +50,10 @@ export class MonthComponent implements OnInit, OnChanges {
@Input() disableDragStart = false;
/** If true, the timespan end cannot be dragged */
@Input() disableDragEnd = false;
/** If true, cannot drag anywhere to select a span (can still drag start and end, if not disabled too) */
@Input() disableDragAnywhere = false;
/** If true, no dragging can be done at all (other drag flags will be ignored) */
@Input() disableDrag = false;
/** If true, nothing can be changed */
@Input() disabled;
/** The current month as string */
Expand All @@ -64,58 +67,102 @@ export class MonthComponent implements OnInit, OnChanges {
/** Changed Timespan selection */
@Output() spanChanged: EventEmitter<any> = new EventEmitter();

protected drag: Subject<Day> = new Subject();
protected changeSpan: Subject<moment.Moment[]> = new Subject();

/* public symbol: SymbolService */
constructor(@Inject('moment.format.month') protected defaultMonthFormat) {
/* this.monthFormat = this.symbol.resolve('moment.format.month') || this.monthFormat; */
this.monthFormat = this.defaultMonthFormat || this.monthFormat;
this.drag
.asObservable()
.pipe(debounceTime(100))
.subscribe((day) => this.dropDay(day));
this.changeSpan
.asObservable()
.pipe(debounceTime(800))
.subscribe((timespan) => this.spanChanged.emit(this.timespan));
.pipe(debounceTime(500))
.subscribe((timespan) => {
this.spanChanged.emit(this.timespan);
});
}

isDraggable(day) {
return !this.disabled &&
!this.disableDrag &&
(
!this.disableDragAnywhere || (
(day.first && !this.disableDragStart) || day.last && !this.disableDragEnd)
);
}

dropDay(day: Day) {
if (!this.dragged || ((day.first && this.dragged.first) || (day.last && this.dragged.last))) {
dragDay(day: Day, e?) {
if (!this.dragged) {
return;
}
this.selected = null;

/* if (day.date.isSame(this.dragged.date)) {
return;
} */

let newTimespan;
if (this.move) {
const moved = day.date.diff(this.dragged.date, 'days');
newTimespan = [this.timespan[0].clone().add(moved, 'days'), this.timespan[1].clone().add(moved, 'days')];
this.dragged = day;
} else {
newTimespan = [].concat(this.timespan);
newTimespan[this.dragged.first ? 0 : 1] = day.date.clone();
}


if (newTimespan[0].isSame(this.timespan[0]) && newTimespan[1].isSame(this.timespan[1])) {
// nothing changes => no need to rerender
return;
}
const newTimespan = [].concat(this.timespan);
newTimespan[this.dragged.first ? 0 : 1] = day.date.clone();
if (newTimespan[0].isAfter(newTimespan[1])) {
this.dragged.first = !this.dragged.first;
this.dragged.last = !this.dragged.last;
newTimespan.reverse();
}
this.timespan = newTimespan;
this.changeSpan.next(this.timespan);
// this.changeSpan.next(this.timespan);

this.setDate();

/* if (this.cells[0] === day || this.cells[this.cells.length - 1] === day) {
// change month if dragging to edge
this.setDate(day.date.clone().subtract(1, 'days'));
} else {
this.setDate();
} */
this.setDate();

/* this.cells = this.getMonth(this.date, 'current'); */
}

isInTimeSpan(date) {
return this.timespan && date.isBetween(this.timespan[0], this.timespan[1], 'days', '][');
}

dragStart(day, e) {
if ((this.disableDragStart && day.first) || (this.disableDragEnd && day.last)) {
if (this.disabled || !this.isDraggable(day)) {
return;
}
this.dragged = day;
console.log('drag start', this.isDraggable(day));
e.preventDefault();
this.dragged = day;
this.move = false;
if (!day.first && !day.last) {
if (this.isInTimeSpan(day.date)) {
this.move = true;
return;
}
this.timespan = [day.date, day.date];
this.setDate();
}
}

mouseUp(day, e) {
if (!this.dragged) {
return;
}
delete this.dragged;
this.changeSpan.next(this.timespan);
e.preventDefault();
}

Expand All @@ -124,7 +171,7 @@ export class MonthComponent implements OnInit, OnChanges {
return;
}
e.preventDefault();
this.drag.next(day);
this.dragDay(day, e);
}

getDayColor(_moment: moment.Moment) {
Expand Down Expand Up @@ -188,7 +235,7 @@ export class MonthComponent implements OnInit, OnChanges {
active: this.timespan && date.isBetween(this.timespan[0], this.timespan[1], 'days', '[]'),
first: isStart,
last: isEnd,
draggable: (!this.disableDragStart && isStart) || (!this.disableDragEnd && isEnd),
inside: this.isInTimeSpan(date),
color: this.getDayColor(date),
heat: this.getDayHeat(date),
format: date.format('DD'),
Expand All @@ -202,10 +249,9 @@ export class MonthComponent implements OnInit, OnChanges {

/** Sets the calendars viewed date to the given moment's month. Renders always 42 cells to keep the layout consistent. */
setDate(date: moment.Moment = this.selected || this.date || moment()) {
if (this.disabled) {
return;
if (date && date !== this.date) {
this.date = date.clone();
}
this.date = date.clone();
this.formatted = date.format(this.monthFormat);
this.cells = this.getMonth(date.clone(), 'current');
}
Expand All @@ -215,6 +261,7 @@ export class MonthComponent implements OnInit, OnChanges {
if (this.disabled) {
return;
}
this.timespan = [_moment, _moment];
this.setDate(_moment);
this.selected = _moment;
if (emit) {
Expand Down
15 changes: 12 additions & 3 deletions packages/calendar/src/lib/heatmap/heatmap.component.html
@@ -1,4 +1,13 @@
<div class="ec-heatmap">
<ec-calendar (spanChanged)="spanChanged.emit($event)" [heatmap]="heatmap" [timespan]="timespan" [date]="date" [disableDragStart]="disableDragStart"
[disableDragEnd]="disableDragEnd" [disabled]="disabled"></ec-calendar>
</div>
<ec-calendar
(spanChanged)="spanChanged.emit($event)"
[heatmap]="heatmap"
[timespan]="timespan"
[date]="date"
[disabled]="disabled"
[disableDrag]="disableDrag"
[disableDragAnywhere]="disableDragAnywhere"
[disableDragStart]="disableDragStart"
[disableDragEnd]="disableDragEnd"
></ec-calendar>
</div>
11 changes: 8 additions & 3 deletions packages/style/scss/ec-calendar/_ec-calendar-days.scss
Expand Up @@ -65,9 +65,14 @@
}
}

.is-last,
.is-first {
cursor: move;
.is-draggable {
&.is-last,
&.is-first {
cursor: col-resize;
}
&.is-inside-timespan {
cursor: move;
}
}

.is-first {
Expand Down
2 changes: 1 addition & 1 deletion src/app/form/datetime-demo.component.ts
Expand Up @@ -12,7 +12,7 @@ import moment from 'moment-es6';
<ec-calendar #calendar></ec-calendar>
<h2>Month Heatmap</h2>
<ec-heatmap [disabled]="true" (spanChanged)="changedSpan($event)" [timestamps]="timestamps"></ec-heatmap>
<ec-heatmap [disableDrag]="false" (spanChanged)="changedSpan($event)" [timestamps]="timestamps"></ec-heatmap>
<h2>Month</h2>
<p>Just month grid</p>
Expand Down

0 comments on commit f05b148

Please sign in to comment.