+
${time}
-
-
diff --git a/src/components/timeline/c-timeline-block/c-timeline-block.ts b/src/components/timeline/c-timeline-block/c-timeline-block.ts
index 104daef5..4a09d95f 100644
--- a/src/components/timeline/c-timeline-block/c-timeline-block.ts
+++ b/src/components/timeline/c-timeline-block/c-timeline-block.ts
@@ -4,66 +4,12 @@ Licensed under the terms of the MIT license. See the LICENSE file in the project
*/
import {bindable} from 'aurelia-framework';
-import * as moment from 'moment';
-import {authState} from '../../../decorators/auth-state';
import * as styles from './c-timeline-block.css.json';
-@authState
export class CTimelineBlock {
@bindable
public time: string;
- @bindable
- public isoTime: string;
- @bindable
- public addEntryOffset: ({isoTime, mouseOffset}: {isoTime: string; mouseOffset: number}) => [string, number];
- @bindable
- public newEntryViewModel: string;
public styles = styles;
-
- private newItem: any = null;
- private placeholderEntry;
- @bindable
- public preventCreate: ({isoTime}?: {isoTime: string}) => boolean = _isoTime => false
-
- public togglePopoverFunction($event) {
- if (!this.isoTime || this.preventCreate({isoTime: this.isoTime}) || !_.isFunction(this.addEntryOffset)) {
- return;
- }
-
- if (!this.newItem) {
- const mouseOffset = $event && $event.layerY ? $event.layerY : 0;
- let top;
- let isoTime;
-
- [isoTime, top] = this.addEntryOffset({isoTime: this.isoTime, mouseOffset});
-
- if (!_.isNumber(top)) {
- top = mouseOffset;
- }
-
- if (!moment(isoTime).isValid()) {
- isoTime = this.isoTime;
- }
-
- this.newItem = {
- isoTime,
- top,
- blockIsoTime: this.isoTime,
- color: 'secondary',
- height: 50,
- placeholder: true,
- title: `${moment(isoTime).format('HH:mm')} (New Item)`,
- };
-
- if (this.newEntryViewModel) {
- this.newItem.contentViewModel = this.newEntryViewModel;
- }
- }
-
- _.defer(() => {
- this.placeholderEntry.openPopover();
- });
- }
}
diff --git a/src/components/timeline/c-timeline-container/c-timeline-container.ts b/src/components/timeline/c-timeline-container/c-timeline-container.ts
index ac4e4879..caa5cc5b 100644
--- a/src/components/timeline/c-timeline-container/c-timeline-container.ts
+++ b/src/components/timeline/c-timeline-container/c-timeline-container.ts
@@ -3,13 +3,12 @@ Copyright 2020, Verizon Media
Licensed under the terms of the MIT license. See the LICENSE file in the project root for license terms.
*/
-import {bindable, containerless} from 'aurelia-framework';
+import {bindable} from 'aurelia-framework';
import {generateRandom} from '../../../helpers/generate-random';
import * as styles from './c-timeline-container.css.json';
-@containerless
export class CTimelineContainer {
@bindable
public currentTimeTop = -1;
diff --git a/src/components/timeline/c-timeline/c-timeline-interfaces.ts b/src/components/timeline/c-timeline/c-timeline-interfaces.ts
index b43c1663..c334d300 100644
--- a/src/components/timeline/c-timeline/c-timeline-interfaces.ts
+++ b/src/components/timeline/c-timeline/c-timeline-interfaces.ts
@@ -27,6 +27,8 @@ export interface ITimeEntry extends ITimeEntryBasic {
contentViewModel?: string;
endTime: string;
height: number;
+ isoTime?: string;
+ placeholder?: boolean;
rightCalc?: number;
shiftIcons?: boolean;
startTime: string;
@@ -35,11 +37,9 @@ export interface ITimeEntry extends ITimeEntryBasic {
}
export interface ITimeBlock {
- addNewMiddle?: boolean;
- addNewTop?: boolean;
isoTime?: string;
- showTime?: boolean;
time: string;
+ newItem?: any;
}
export interface ITimeDay {
@@ -47,6 +47,8 @@ export interface ITimeDay {
blocks: ITimeBlock[];
date: string;
entries: ITimeEntry[];
+ newItem?: any;
parsedDate?: string;
+ placeholderEntry?: any;
today?: boolean;
}
diff --git a/src/components/timeline/c-timeline/c-timeline.html b/src/components/timeline/c-timeline/c-timeline.html
index bdb97ffb..a1b0271d 100644
--- a/src/components/timeline/c-timeline/c-timeline.html
+++ b/src/components/timeline/c-timeline/c-timeline.html
@@ -13,14 +13,11 @@
current-time-top.bind="currentTimeLine"
loading-top.bind="isLoadingTop"
loading-bottom.bind="isLoadingBottom"
+ click.trigger="togglePopover($event)"
>
+
+
-
+
-
-
-
+
+
+
+
+
diff --git a/src/components/timeline/c-timeline/c-timeline.ts b/src/components/timeline/c-timeline/c-timeline.ts
index aa7817c1..722e8b74 100644
--- a/src/components/timeline/c-timeline/c-timeline.ts
+++ b/src/components/timeline/c-timeline/c-timeline.ts
@@ -13,6 +13,8 @@ import {authState} from '../../../decorators/auth-state';
import {generateRandom} from '../../../helpers/generate-random';
import {CToastsService} from '../../toasts/c-toasts/c-toasts-service';
+import * as CTimelineWeekContainerStyles from '../c-timeline-week-container/c-timeline-week-container.css.json';
+
type Moment = moment.Moment;
/**
@@ -45,11 +47,12 @@ export const ZOOM_LEVELS: any = [
},
];
+export const BLOCK_HEIGHT = 50;
+
const DEFAULT_ZOOM = 2;
const MINUTES_IN_DAY = 1440;
const HOURS_IN_DAY = 24;
const TIME_REGEX = /^[0-9]{1,4}$/;
-const BLOCK_HEIGHT = 50;
const SECONDS_IN_MINUTE = 60;
/**
@@ -158,6 +161,8 @@ export class CTimeline {
public id = generateRandom();
public currentScroll: number = 0;
public preventScrollCheck: boolean = true;
+ public placeholderEntry: any;
+ public newItem: any = null;
/**
* Build out the timeline. Put in a throttle so it doesn't bind up
@@ -256,6 +261,120 @@ export class CTimeline {
});
}, 100);
+ public togglePopover = _.debounce(
+ ($event, day?: ITimeDay) => {
+ if (
+ // @ts-ignore
+ this._state === 'disabled' ||
+ (_.isBoolean(this.preventCreate) && this.preventCreate)
+ ) {
+ return;
+ }
+
+ let isoTime;
+ const dayWeek = day;
+
+ const dayDate = day ? dayWeek.date : null;
+ const [zoomLevelData, pxPerMinute] = this.getZoomLevelData();
+ const [blockStart] = this.getDayStartEndTimes(dayDate);
+
+ const relativeY = $event.pageY - this.parentScrollElem.offset().top;
+ // On week view, get the height of the date names so we can offset
+ const elemDiff = day ? $(`.${CTimelineWeekContainerStyles.dates}`).outerHeight() : 0;
+ const pxDown = relativeY + this.parentScrollElem.scrollTop() - elemDiff;
+ const blockIndex = Math.floor(pxDown / BLOCK_HEIGHT);
+
+ if (dayWeek) {
+ dayWeek.newItem = null;
+ isoTime = dayWeek.blocks[blockIndex].isoTime;
+
+ _.forEach(this.displayDays, weekDay => (weekDay.newItem = null));
+ } else {
+ this.newItem = null;
+ isoTime = this.blocks[blockIndex].isoTime;
+ }
+
+ const minutesFromTop = pxDown / pxPerMinute;
+
+ const clickedTime = moment(blockStart)
+ .add(minutesFromTop, 'minutes')
+ .startOf(this.zoomLevel < 5 ? 'minute' : 'second');
+ const startTime: Moment = moment(clickedTime).subtract(zoomLevelData.minutes / 2, 'minutes');
+ const endTime: Moment = moment(clickedTime).add(zoomLevelData.minutes / 2, 'minutes');
+
+ const matchingEntries: ITimeEntry[] = [];
+
+ const entries = dayWeek ? dayWeek.entries : this.transformedEntries;
+
+ if (this.snapAdd) {
+ (matchingEntries as any[]) = _.filter(entries, entry =>
+ moment(entry.end).isBetween(startTime, endTime, null, '[)'),
+ );
+ }
+
+ let top = blockIndex * BLOCK_HEIGHT;
+ let startIso = null;
+
+ if (!matchingEntries.length) {
+ const isoTimeMoment = moment(isoTime);
+ const halfBlock = BLOCK_HEIGHT / 2;
+
+ if ($event.layerY >= halfBlock) {
+ top += halfBlock;
+ isoTimeMoment.add((halfBlock / pxPerMinute) * SECONDS_IN_MINUTE, 'seconds');
+ }
+
+ startIso = isoTimeMoment.toISOString();
+ } else {
+ const sortedEntries = _.sortBy(matchingEntries, entry =>
+ Math.abs(moment(entry.end).diff(clickedTime, 'seconds')),
+ );
+ const firstEntry = _.first(sortedEntries);
+ const diff = Math.ceil(moment(isoTime).diff(firstEntry.end, 'seconds')) * -1;
+
+ top += Math.floor((diff / SECONDS_IN_MINUTE) * pxPerMinute);
+ startIso = firstEntry.end;
+ }
+
+ if (_.isFunction(this.preventCreate) && this.preventCreate({isoTime: startIso})) {
+ return;
+ }
+
+ const newItem: any = {
+ top,
+ color: 'secondary',
+ height: 50,
+ isoTime: startIso,
+ placeholder: true,
+ title: `${moment(startIso).format(this.zoomLevel < 5 ? 'HH:mm' : 'HH:mm:ss')} (New Item)`,
+ };
+
+ if (this.newEntryViewModel) {
+ newItem.contentViewModel = this.newEntryViewModel;
+ }
+
+ if (!dayWeek) {
+ this.newItem = _.cloneDeep(newItem);
+
+ _.defer(() => {
+ if (this.placeholderEntry) {
+ this.placeholderEntry.openPopover();
+ }
+ });
+ } else {
+ dayWeek.newItem = _.cloneDeep(newItem);
+
+ _.defer(() => {
+ if (dayWeek.placeholderEntry) {
+ dayWeek.placeholderEntry.openPopover();
+ }
+ });
+ }
+ },
+ 500,
+ {leading: true, trailing: false},
+ );
+
private calculateCurrentTimeLine = _.throttle(
() => {
if (this.timeView === 'month') {
@@ -404,81 +523,6 @@ export class CTimeline {
this.scrollToSpot(this.scrollTime);
}
- /**
- * Passed into `c-time-block` elements. Allows you to set spot for placeholder
- * new time entry while popover is showing.
- *
- * @param isoTime ISO Date string
- * @param mouseOffset How many pixels down the mouse was clicked
- */
- public calculatePlaceholder(isoTime: string, mouseOffset: number): [string, number] {
- const zoomLevelData = ZOOM_LEVELS[this.zoomLevel];
- const pxPerMinute = BLOCK_HEIGHT / zoomLevelData.minutes;
- const offsetMinutes = mouseOffset / pxPerMinute;
-
- // Buffer around clicked time to snap
- const clickedTime = moment(isoTime)
- .add(offsetMinutes, 'minutes')
- .startOf(this.zoomLevel < 5 ? 'minute' : 'second');
- const startTime = moment(clickedTime).subtract(zoomLevelData.minutes, 'minutes');
- const endTime = moment(clickedTime).add(zoomLevelData.minutes, 'minutes');
-
- let matchingEntries: any[] = [];
-
- if (this.snapAdd) {
- if (this.timeView === 'day') {
- matchingEntries = _.filter(this.transformedEntries, entry => {
- return moment(entry.end).isBetween(startTime, endTime, null, '[)');
- });
- } else {
- _.forEach(this.displayDays, day => {
- const dayMatches = _.filter(day.entries, entry => {
- return moment(entry.end).isBetween(startTime, endTime, null, '[)');
- });
-
- matchingEntries = [...matchingEntries, ...dayMatches];
- });
- }
- }
-
- let offset = 0;
-
- if (!matchingEntries.length) {
- const isoTimeMoment = moment(isoTime);
- const halfBlock = BLOCK_HEIGHT / 2;
-
- if (mouseOffset >= halfBlock) {
- offset = halfBlock;
- isoTimeMoment.add((halfBlock / pxPerMinute) * SECONDS_IN_MINUTE, 'seconds');
- }
-
- return [isoTimeMoment.toISOString(), offset];
- }
-
- const sortedEntries = _.sortBy(matchingEntries, entry =>
- Math.abs(moment(entry.end).diff(clickedTime, 'seconds')),
- );
- const firstEntry = _.first(sortedEntries);
- const diff = Math.ceil(moment(isoTime).diff(firstEntry.end, 'seconds')) * -1;
- offset = Math.floor((diff / SECONDS_IN_MINUTE) * pxPerMinute);
-
- return [firstEntry.end, offset];
- }
-
- /**
- * Passed into `c-time-block` elements. Allows you to check if clicking in a block
- * will allow you to create a new entry.
- *
- * @param isoTime ISO Date string
- */
- public checkPreventAdd(isoTime) {
- if (!_.isFunction(this.preventCreate)) {
- return false;
- }
-
- return this.preventCreate({isoTime});
- }
-
/**
* Changes week view to day view and navigates to that date
*
@@ -848,7 +892,7 @@ export class CTimeline {
/**
* Calculate the times for when the day starts
*/
- private getDayStartEndTimes(): [Moment, Moment] {
+ private getDayStartEndTimes(date?): [Moment, Moment] {
const zoomLevelData = ZOOM_LEVELS[this.zoomLevel];
let startTime;
@@ -884,6 +928,10 @@ export class CTimeline {
}
}
+ if (date) {
+ startTime = moment(date).startOf('day');
+ }
+
const blockTime = this.blocks[0].time;
const numOfBlocks = this.blocks.length;
addHours = moment(blockTime, 'HH:mm').format('H');
@@ -892,7 +940,7 @@ export class CTimeline {
startTime.add(addHours, 'hours').add(addMinutes, 'minutes');
endTime = moment(startTime).add(numOfBlocks * zoomLevelData.minutes, 'minutes');
- return [startTime, endTime];
+ return [moment(startTime), moment(endTime)];
}
private getZoomLevelData() {
diff --git a/src/index.ts b/src/index.ts
index dd41a248..b3ff2b59 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -22,6 +22,7 @@ import {CToastsService} from './components/toasts/c-toasts/c-toasts-service';
import {dirtyCheckPrompt} from './decorators/dirty-check-prompt/index';
// Value Converters
+import {AsyncBindingBehavior} from './value-converters/async-binding';
import {BooleanYesNoValueConverter} from './value-converters/boolean-yes-no';
import {CapitalizeValueConverter} from './value-converters/capitalize';
import {CountValueConverter} from './value-converters/count';
@@ -122,6 +123,7 @@ export function configure(config: FrameworkConfiguration) {
PLATFORM.moduleName('./value-converters/string-to-number'),
PLATFORM.moduleName('./value-converters/th-class-for'),
PLATFORM.moduleName('./value-converters/vsort'),
+ PLATFORM.moduleName('./value-converters/async-binding'),
// Components
PLATFORM.moduleName('./components/copy/c-copy/c-copy'),
@@ -266,6 +268,7 @@ export {
StringToNumberValueConverter,
ThClassForValueConverter,
CsortValueConverter,
+ AsyncBindingBehavior,
};
// Interfaces
diff --git a/src/value-converters/async-binding.ts b/src/value-converters/async-binding.ts
new file mode 100644
index 00000000..6c4ba94f
--- /dev/null
+++ b/src/value-converters/async-binding.ts
@@ -0,0 +1,20 @@
+/**
+ * This will allow you to pipe an promise to a value converter
+ *
+ * ex. `value | value-converter & async`
+ */
+export class AsyncBindingBehavior {
+ public bind(binding) {
+ binding.originalupdateTarget = binding.updateTarget;
+
+ binding.updateTarget = async a => {
+ const d = await a;
+ binding.originalupdateTarget(d);
+ };
+ }
+
+ public unbind(binding) {
+ binding.updateTarget = binding.originalupdateTarget;
+ binding.originalupdateTarget = null;
+ }
+}