time_table 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascript/time_table/controllers/time_table_controller.js +115 -151
- data/app/assets/javascript/time_table/controllers/time_table_controller_old.js +569 -0
- data/app/assets/javascript/time_table/controllers/time_table_controller_pre_time_keeper.js +350 -0
- data/app/assets/javascript/time_table/helpers/drag_event_state.js +53 -0
- data/app/assets/javascript/time_table/helpers/scroll_frame.js +141 -0
- data/app/assets/javascript/time_table/helpers/time_keeper.js +98 -0
- data/app/assets/javascript/time_table/helpers/time_table_renderer.js +75 -0
- data/app/assets/stylesheets/time_table/time_table.css +16 -2
- data/app/helpers/time_table/time_table_helper.rb +2 -2
- data/app/views/time_table/_time_table.html.erb +2 -2
- data/lib/time_table/version.rb +1 -1
- metadata +8 -2
@@ -0,0 +1,350 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
import ScrollFrame from "../helpers/scroll_frame.js"
|
3
|
+
import TimeTableRenderer from "../helpers/time_table_renderer.js"
|
4
|
+
|
5
|
+
export default class extends Controller {
|
6
|
+
static targets = ["canvas", "eventsList", "event", "eventName", "eventTime", "timestamp"]
|
7
|
+
|
8
|
+
static UNITS = {
|
9
|
+
hours: 3_600_000,
|
10
|
+
minutes: 60_000,
|
11
|
+
seconds: 1_000
|
12
|
+
}
|
13
|
+
|
14
|
+
connect() {
|
15
|
+
this.startTime = new Date(this.element.dataset.timetableStartTime);
|
16
|
+
this.endTime = new Date(this.element.dataset.timetableEndTime);
|
17
|
+
this.extendedStartTime = this.startTime;
|
18
|
+
this.extendedEndTime = this.endTime;
|
19
|
+
this.clipSize = this.element.dataset.timetableClipSize;
|
20
|
+
|
21
|
+
let topPadding = parseInt(window.getComputedStyle(this.element).paddingTop, 10);
|
22
|
+
|
23
|
+
this.scrollHeight = this.element.scrollHeight - topPadding;
|
24
|
+
this.scrollFrame = new ScrollFrame(this.element, { origin: topPadding });
|
25
|
+
|
26
|
+
this.dragging = false;
|
27
|
+
this.dragEvent = {};
|
28
|
+
this.scrollEvent = {};
|
29
|
+
|
30
|
+
this.timeScope = this.constructor.UNITS.hours;
|
31
|
+
this.timeMetric = this.constructor.UNITS.minutes;
|
32
|
+
this.metricConversion = this.timeScope / this.timeMetric;
|
33
|
+
|
34
|
+
this.scale = 60;
|
35
|
+
|
36
|
+
this.eventTargets.forEach((event) => {
|
37
|
+
TimeTableRenderer.setSpecialClasses(event);
|
38
|
+
});
|
39
|
+
}
|
40
|
+
|
41
|
+
dragScreenShot(event) {
|
42
|
+
let relativePosition = this.scrollFrame.relativeCursorPosition(event);
|
43
|
+
|
44
|
+
return {
|
45
|
+
position: relativePosition,
|
46
|
+
time: this.#timeAtPosition(relativePosition.scroll.y),
|
47
|
+
target: this.targetScreenShot(this.dragEvent.target)
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
targetScreenShot(target) {
|
52
|
+
if(!target) {
|
53
|
+
return null;
|
54
|
+
}
|
55
|
+
|
56
|
+
let relativePosition = this.scrollFrame.relativePosition(target.getBoundingClientRect().left, target.getBoundingClientRect().top);
|
57
|
+
|
58
|
+
let height = target.offsetHeight;
|
59
|
+
|
60
|
+
return {
|
61
|
+
height: height,
|
62
|
+
position: relativePosition,
|
63
|
+
startTime: this.#timeAtPosition(relativePosition.scroll.y),
|
64
|
+
endTime: this.#timeAtPosition(relativePosition.scroll.y + height),
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
startDragEvent(event) {
|
69
|
+
event.preventDefault();
|
70
|
+
event.stopPropagation();
|
71
|
+
|
72
|
+
this.dragging = true;
|
73
|
+
|
74
|
+
this.dragEvent.type = event.params.dragEvent;
|
75
|
+
this.dragEvent.target = event.currentTarget.closest(".event");
|
76
|
+
this.dragEvent.origin = this.dragScreenShot(event);
|
77
|
+
this.dragEvent.current = this.dragEvent.origin;
|
78
|
+
|
79
|
+
// this.dragEventObject = new DragState({
|
80
|
+
// type: event.params.dragEvent,
|
81
|
+
// scrollFrame: this.scrollFrame,
|
82
|
+
// target: event.currentTarget.closest(".event"),
|
83
|
+
// event: event
|
84
|
+
// })
|
85
|
+
|
86
|
+
// console.log(this.dragEventObject);
|
87
|
+
|
88
|
+
// this.dragEvent = new DragEvent({
|
89
|
+
// scrollFrame: this.scrollFrame, event: event, target: event.currentTarget.closest(".event")});
|
90
|
+
|
91
|
+
|
92
|
+
|
93
|
+
this.#setDragEventListeners();
|
94
|
+
}
|
95
|
+
|
96
|
+
stopDragEvent(event) {
|
97
|
+
event.preventDefault();
|
98
|
+
|
99
|
+
this.dragging = false;
|
100
|
+
this.dragEvent = {};
|
101
|
+
|
102
|
+
this.stopScroll();
|
103
|
+
this.#removeDragEventListeners();
|
104
|
+
}
|
105
|
+
|
106
|
+
drag(event) {
|
107
|
+
if(this.dragging) {
|
108
|
+
this.dragEvent.previous = this.dragEvent.current;
|
109
|
+
this.dragEvent.current = this.dragScreenShot(event);
|
110
|
+
this.dragEvent.event = event;
|
111
|
+
|
112
|
+
// Calls dragResize, dragMove, dragCreate, etc.
|
113
|
+
this.#invokeDragMethodFor(this.dragEvent.type);
|
114
|
+
|
115
|
+
if(this.dragEvent.type != undefined && this.dragEvent.type != "rescale") {
|
116
|
+
this.checkScroll();
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
//////////////////
|
122
|
+
// Drag Methods //
|
123
|
+
//////////////////
|
124
|
+
|
125
|
+
dragCreate(event) {
|
126
|
+
let timeBoundaries = this.#timeBoundaries(this.dragEvent.current.time.clipped, this.dragEvent.origin.time.clipped);
|
127
|
+
let eventElement = TimeTableRenderer.createEventElement(timeBoundaries);
|
128
|
+
|
129
|
+
this.eventsListTarget.appendChild(eventElement);
|
130
|
+
this.dragEvent.target = eventElement;
|
131
|
+
this.dragEvent.origin.target = this.targetScreenShot(eventElement);
|
132
|
+
this.dragEvent.current.target = this.dragEvent.origin.target;
|
133
|
+
this.dragEvent.type = "resize";
|
134
|
+
}
|
135
|
+
|
136
|
+
dragResize(event) {
|
137
|
+
let timeBoundaries = this.#timeBoundaries(this.dragEvent.current.time.clipped, this.dragEvent.origin.target.startTime.real);
|
138
|
+
TimeTableRenderer.updateEventElement(this.dragEvent.target, timeBoundaries);
|
139
|
+
this.dragEvent.current.target = this.targetScreenShot(this.dragEvent.target);
|
140
|
+
}
|
141
|
+
|
142
|
+
dragMove(event) {
|
143
|
+
let timeChange = (this.dragEvent.current.time.clipped - this.dragEvent.origin.time.clipped);
|
144
|
+
let updatedStartTime = new Date(this.dragEvent.origin.target.startTime.real.getTime() + timeChange)
|
145
|
+
let updatedEndTime = new Date(this.dragEvent.origin.target.endTime.real.getTime() + timeChange);
|
146
|
+
|
147
|
+
let timeBoundaries = this.#timeBoundaries(updatedStartTime, updatedEndTime);
|
148
|
+
|
149
|
+
TimeTableRenderer.updateEventElement(this.dragEvent.target, timeBoundaries);
|
150
|
+
this.dragEvent.current.target = this.targetScreenShot(this.dragEvent.target);
|
151
|
+
}
|
152
|
+
|
153
|
+
dragRescale(event) {
|
154
|
+
let delta = this.dragEvent.previous.position.scroll.y - this.dragEvent.current.position.scroll.y;
|
155
|
+
let newScale = Math.max(30, Math.min(300, this.scale - (delta)));
|
156
|
+
this.element.style.setProperty("--hour-scale", newScale);
|
157
|
+
let scaleChange = newScale / this.scale;
|
158
|
+
|
159
|
+
this.scrollHeight = this.element.scrollHeight - 20;
|
160
|
+
this.scale = newScale;
|
161
|
+
this.setClipSize(newScale);
|
162
|
+
|
163
|
+
this.eventTargets.forEach((event) => {
|
164
|
+
event.style.setProperty("height", `${event.offsetHeight * (scaleChange)}px`);
|
165
|
+
event.style.setProperty("top", `${event.offsetTop * (scaleChange)}px`);
|
166
|
+
TimeTableRenderer.setSpecialClasses(event);
|
167
|
+
});
|
168
|
+
}
|
169
|
+
|
170
|
+
////////////////////
|
171
|
+
// Scroll Methods //
|
172
|
+
////////////////////
|
173
|
+
|
174
|
+
checkScroll() {
|
175
|
+
// TODO - Refactor
|
176
|
+
let currentFrameY = this.dragEvent.current.position.frame.y;
|
177
|
+
if(currentFrameY < 30) {
|
178
|
+
let distance = Math.abs(currentFrameY - 30);
|
179
|
+
let direction = -1;
|
180
|
+
this.setScroll(distance, direction);
|
181
|
+
} else if (currentFrameY > this.scrollFrame.frame.height - 30) {
|
182
|
+
let distance = Math.abs(currentFrameY - (this.scrollFrame.frame.height - 30));
|
183
|
+
let direction = +1;
|
184
|
+
this.setScroll(distance, direction);
|
185
|
+
} else {
|
186
|
+
this.stopScroll();
|
187
|
+
}
|
188
|
+
}
|
189
|
+
|
190
|
+
static SCROLL_SPEEDS = [
|
191
|
+
{ max: 30, speed: 1 },
|
192
|
+
{ max: 50, speed: 2 },
|
193
|
+
{ max: 100, speed: 3 },
|
194
|
+
{ max: 150, speed: 5 },
|
195
|
+
{ max: 250, speed: 10 },
|
196
|
+
{ max: Infinity, speed: 15}
|
197
|
+
]
|
198
|
+
|
199
|
+
setScroll(distance, direction) {
|
200
|
+
this.scrollEvent.step = (this.constructor.SCROLL_SPEEDS.find(range => distance < range.max)?.speed || 10) * direction;
|
201
|
+
|
202
|
+
if(!this.scrollEvent.timeout) {
|
203
|
+
this.scroll();
|
204
|
+
}
|
205
|
+
}
|
206
|
+
|
207
|
+
scroll() {
|
208
|
+
this.scrollEvent.timeout = setTimeout(() => {
|
209
|
+
this.element.scrollTop += this.scrollEvent.step;
|
210
|
+
if(this.element.scrollTop == 0) {
|
211
|
+
this.insertTimestampAbove();
|
212
|
+
}
|
213
|
+
|
214
|
+
if(this.element.scrollTop + this.element.clientHeight >= this.scrollHeight) {
|
215
|
+
this.insertTimestampBelow();
|
216
|
+
}
|
217
|
+
|
218
|
+
this.scroll();
|
219
|
+
this.drag(this.dragEvent.event);
|
220
|
+
}, 1);
|
221
|
+
}
|
222
|
+
|
223
|
+
insertTimestampAbove() {
|
224
|
+
}
|
225
|
+
|
226
|
+
insertTimestampBelow() {
|
227
|
+
// this.dragEvent.inserting = true;
|
228
|
+
// Add 1 hour
|
229
|
+
let time = new Date(this.endTime.getTime());
|
230
|
+
time.setHours(time.getHours() + 1);
|
231
|
+
this.endTime = time;
|
232
|
+
let timestampDiv = TimeTableRenderer.timestamp(time);
|
233
|
+
console.log(timestampDiv);
|
234
|
+
this.element.querySelector(".timestamps").appendChild(timestampDiv);
|
235
|
+
this.scrollHeight = this.element.scrollHeight - 20;
|
236
|
+
}
|
237
|
+
|
238
|
+
stopScroll() {
|
239
|
+
if(this.scrollEvent.timeout) {
|
240
|
+
clearTimeout(this.scrollEvent.timeout);
|
241
|
+
this.scrollEvent = {};
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
/////////////////////
|
246
|
+
// Time & Position //
|
247
|
+
/////////////////////
|
248
|
+
|
249
|
+
#timeBoundaries(timeA, timeB) {
|
250
|
+
let startTime = new Date(Math.min(timeA, timeB));
|
251
|
+
let endTime = new Date(Math.max(timeA, timeB));
|
252
|
+
|
253
|
+
if (endTime - startTime < this.clipSize * 60 * 1000) {
|
254
|
+
endTime = new Date(startTime.getTime() + (this.clipSize * 60 * 1000));
|
255
|
+
}
|
256
|
+
|
257
|
+
return {
|
258
|
+
start: { time: startTime, location: this.#positionOfTime(startTime) },
|
259
|
+
end: { time: endTime, location: this.#positionOfTime(endTime) }
|
260
|
+
};
|
261
|
+
}
|
262
|
+
|
263
|
+
#timeAtPosition(relativeScrollY) {
|
264
|
+
let scopeOrdinal = Math.trunc(relativeScrollY / this.scale); // Eg "Hours from start"
|
265
|
+
let positionInScope = (relativeScrollY % this.scale) / this.scale;
|
266
|
+
let scopeMeasure = Math.trunc(positionInScope * this.metricConversion)
|
267
|
+
let clippedScopeMeasure = Math.floor(scopeMeasure / this.clipSize) * this.clipSize;
|
268
|
+
let millisecondsFromStart = (scopeOrdinal * this.timeScope) + (scopeMeasure * this.timeMetric);
|
269
|
+
let clippedMillisecondsFromStart = (scopeOrdinal * this.timeScope) + (clippedScopeMeasure * this.timeMetric);
|
270
|
+
|
271
|
+
return {
|
272
|
+
real: new Date(this.startTime.getTime() + millisecondsFromStart), // For some reason 1 second is added to real each time you change it.
|
273
|
+
clipped: new Date(this.startTime.getTime() + clippedMillisecondsFromStart),
|
274
|
+
}
|
275
|
+
}
|
276
|
+
|
277
|
+
#positionOfTime(time) {
|
278
|
+
let timeFromStart = time - this.startTime;
|
279
|
+
let scopeFromStart = Math.floor(timeFromStart / this.timeScope);
|
280
|
+
let metricFromScope = Math.floor((timeFromStart % this.timeScope) / this.timeMetric);
|
281
|
+
let pixelsFromStart = (scopeFromStart * this.scale) + (metricFromScope * this.scale / this.metricConversion);
|
282
|
+
return pixelsFromStart;
|
283
|
+
}
|
284
|
+
|
285
|
+
|
286
|
+
////////////////
|
287
|
+
// Invokation //
|
288
|
+
////////////////
|
289
|
+
|
290
|
+
#invokeDragMethodFor(type) {
|
291
|
+
if(!type) {
|
292
|
+
console.error("No drag type provided");
|
293
|
+
return;
|
294
|
+
}
|
295
|
+
let capitalizedType = type.charAt(0).toUpperCase() + type.slice(1);
|
296
|
+
|
297
|
+
if (this[`drag${capitalizedType}`]) {
|
298
|
+
this[`drag${capitalizedType}`]();
|
299
|
+
} else {
|
300
|
+
console.error(`No drag method found for type: ${type}`);
|
301
|
+
}
|
302
|
+
}
|
303
|
+
|
304
|
+
/////////////
|
305
|
+
// Helpers //
|
306
|
+
/////////////
|
307
|
+
|
308
|
+
static CLIP_SCALES = [
|
309
|
+
{ max: 40, clipSize: 60 },
|
310
|
+
{ max: 50, clipSize: 30 },
|
311
|
+
{ max: 150, clipSize: 15 },
|
312
|
+
{ max: 280, clipSize: 5 },
|
313
|
+
{ max: Infinity, clipSize: 1}
|
314
|
+
]
|
315
|
+
|
316
|
+
setClipSize(scale) {
|
317
|
+
this.clipSize = this.constructor.CLIP_SCALES.find(range => scale < range.max)?.clipSize || 15;
|
318
|
+
}
|
319
|
+
|
320
|
+
#setDragEventListeners() {
|
321
|
+
document.addEventListener("mousemove", this.drag.bind(this));
|
322
|
+
document.addEventListener("mouseup", this.stopDragEvent.bind(this));
|
323
|
+
document.addEventListener("mouseleave", this.stopDragEvent.bind(this));
|
324
|
+
}
|
325
|
+
|
326
|
+
#removeDragEventListeners() {
|
327
|
+
document.removeEventListener("mousemove", this.drag.bind(this));
|
328
|
+
document.removeEventListener("mouseup", this.stopDragEvent.bind(this));
|
329
|
+
document.removeEventListener("mouseleave", this.stopDragEvent.bind(this));
|
330
|
+
}
|
331
|
+
}
|
332
|
+
|
333
|
+
// class DragState {
|
334
|
+
// constructor({ type, scrollFrame, target, event }) {
|
335
|
+
// this.type = type;
|
336
|
+
// this.scrollFrame = scrollFrame;
|
337
|
+
// this.target = target;
|
338
|
+
// this.event = event;
|
339
|
+
// this.captureOriginSnapshot();
|
340
|
+
// }
|
341
|
+
|
342
|
+
// captureOriginSnapshot() {
|
343
|
+
// this.current = this.origin;
|
344
|
+
// }
|
345
|
+
|
346
|
+
// captureSnapshot() {
|
347
|
+
// this.previous = this.current;
|
348
|
+
|
349
|
+
// }
|
350
|
+
// }
|
@@ -0,0 +1,53 @@
|
|
1
|
+
export default class DragEventState {
|
2
|
+
constructor({ type, scrollFrame, timeKeeper, target }) {
|
3
|
+
this.type = type;
|
4
|
+
this.scrollFrame = scrollFrame;
|
5
|
+
this.timeKeeper = timeKeeper;
|
6
|
+
this.target = target;
|
7
|
+
}
|
8
|
+
|
9
|
+
captureOriginSnapshot(event) {
|
10
|
+
this.origin = this.snapshot(event);
|
11
|
+
this.current = this.origin;
|
12
|
+
}
|
13
|
+
|
14
|
+
captureSnapshot(event) {
|
15
|
+
this.previous = this.current;
|
16
|
+
this.current = this.snapshot(event);
|
17
|
+
}
|
18
|
+
|
19
|
+
shift({ type, target }) {
|
20
|
+
this.type = type;
|
21
|
+
this.target = target;
|
22
|
+
this.origin.target = this.targetSnapshot(target);
|
23
|
+
this.current.target = this.targetSnapshot(target);
|
24
|
+
}
|
25
|
+
|
26
|
+
snapshot(event) {
|
27
|
+
let relativePosition = this.scrollFrame.relativeCursorPosition(event);
|
28
|
+
return {
|
29
|
+
event: event,
|
30
|
+
position: relativePosition,
|
31
|
+
time: this.timeKeeper.timeAtPosition(relativePosition.scroll.y),
|
32
|
+
target: this.targetSnapshot(this.target)
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
targetSnapshot(target) {
|
37
|
+
if(!target) {
|
38
|
+
return null;
|
39
|
+
}
|
40
|
+
|
41
|
+
let targetRect = target.getBoundingClientRect();
|
42
|
+
let relativePosition = this.scrollFrame.relativePosition(targetRect.left, targetRect.top);
|
43
|
+
let height = target.offsetHeight;
|
44
|
+
|
45
|
+
return {
|
46
|
+
height: height,
|
47
|
+
boundaries: targetRect,
|
48
|
+
position: relativePosition,
|
49
|
+
startTime: this.timeKeeper.timeAtPosition(relativePosition.scroll.y),
|
50
|
+
endTime: this.timeKeeper.timeAtPosition(relativePosition.scroll.y + height),
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
@@ -0,0 +1,141 @@
|
|
1
|
+
// Major Purpose - Calculate position of of a coordinate on screen and convert it to position relative to frame and scroll of element.
|
2
|
+
export default class ScrollFrame {
|
3
|
+
constructor(element, { origin = 0 }) {
|
4
|
+
this.element = element;
|
5
|
+
this.origin = origin;
|
6
|
+
this.scrollEvent = {};
|
7
|
+
// this.endBoundary = this.element.scrollHeight;
|
8
|
+
}
|
9
|
+
|
10
|
+
get frame() {
|
11
|
+
return this.element.getBoundingClientRect();
|
12
|
+
}
|
13
|
+
|
14
|
+
checkForScroll(relativePosition, scrollHandler) {
|
15
|
+
let currentFrameY = relativePosition.frame.y;
|
16
|
+
this.scrollEvent.scrollHandler = scrollHandler;
|
17
|
+
if(currentFrameY < 30) {
|
18
|
+
this.scrollUp(currentFrameY);
|
19
|
+
} else if (currentFrameY > this.frame.height - 30) {
|
20
|
+
this.scrollDown(currentFrameY);
|
21
|
+
} else {
|
22
|
+
this.stopScrolling();
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
scrollUp(currentFrameY) {
|
27
|
+
let distance = Math.abs(currentFrameY - 30);
|
28
|
+
// console.log("scrolling up", distance);
|
29
|
+
this.setScroll(distance, -1);
|
30
|
+
}
|
31
|
+
|
32
|
+
scrollDown(currentFrameY) {
|
33
|
+
let distance = Math.abs(currentFrameY - (this.frame.height - 30));
|
34
|
+
// console.log("scrolling down", distance);
|
35
|
+
this.setScroll(distance, +1);
|
36
|
+
}
|
37
|
+
|
38
|
+
static SCROLL_SPEEDS = [
|
39
|
+
{ max: 30, speed: 1 },
|
40
|
+
{ max: 50, speed: 2 },
|
41
|
+
{ max: 100, speed: 3 },
|
42
|
+
{ max: 150, speed: 5 },
|
43
|
+
{ max: 250, speed: 10 },
|
44
|
+
{ max: Infinity, speed: 15}
|
45
|
+
]
|
46
|
+
|
47
|
+
setScroll(distance, direction) {
|
48
|
+
this.scrollEvent.step = (this.constructor.SCROLL_SPEEDS.find(range => distance < range.max)?.speed || 10) * direction;
|
49
|
+
|
50
|
+
if(!this.scrollEvent.timeout) {
|
51
|
+
this.scroll();
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
scroll() {
|
56
|
+
// TODO - Refactor to use boundaries from ScrollFrame
|
57
|
+
this.scrollEvent.timeout = setTimeout(() => {
|
58
|
+
|
59
|
+
|
60
|
+
var status = "IN";
|
61
|
+
console.log(this.element.scrollTop + this.element.clientHeight + this.scrollEvent.step, this.element.scrollHeight);
|
62
|
+
|
63
|
+
if(this.element.scrollTop + this.scrollEvent.step <= 0) {
|
64
|
+
status = "ABOVE";
|
65
|
+
}
|
66
|
+
|
67
|
+
if(this.element.scrollTop + this.scrollEvent.step >= this.element.scrollHeight) {
|
68
|
+
status = "BELOW";
|
69
|
+
}
|
70
|
+
|
71
|
+
this.element.scrollTop += this.scrollEvent.step;
|
72
|
+
|
73
|
+
this.scrollEvent.scrollHandler(status);
|
74
|
+
|
75
|
+
this.scroll();
|
76
|
+
// this.drag(this.dragEvent.current.event);
|
77
|
+
}, 1);
|
78
|
+
}
|
79
|
+
|
80
|
+
stopScrolling() {
|
81
|
+
if(this.scrollEvent.timeout) {
|
82
|
+
clearTimeout(this.scrollEvent.timeout);
|
83
|
+
this.scrollEvent = {};
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
relativeCursorPosition(event) {
|
88
|
+
return this.relativePosition(event.clientX, event.clientY);
|
89
|
+
}
|
90
|
+
|
91
|
+
relativePosition(x, y) {
|
92
|
+
return {
|
93
|
+
frame: this.relativeFramePosition(x, y),
|
94
|
+
scroll: this.relativeScrollPosition(x, y)
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
relativeScrollPosition(x, y) {
|
99
|
+
return {
|
100
|
+
x: this.relativeXScrollPosition(x),
|
101
|
+
y: this.relativeYScrollPosition(y)
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
relativeFramePosition(x, y) {
|
106
|
+
return {
|
107
|
+
x: this.relativeXFramePosition(x),
|
108
|
+
y: this.relativeYFramePosition(y)
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
relativeYPosition(y) {
|
113
|
+
return {
|
114
|
+
inFrame: this.relativeYFramePosition(y),
|
115
|
+
inScroll: this.relativeYScrollPosition(y)
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
relativeXPosition(x) {
|
120
|
+
return {
|
121
|
+
inFrame: this.relativeXFramePosition(x),
|
122
|
+
inScroll: this.relativeXScrollPosition(x)
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
relativeYFramePosition(y) {
|
127
|
+
return y - this.frame.top;
|
128
|
+
}
|
129
|
+
|
130
|
+
relativeYScrollPosition(y) {
|
131
|
+
return (this.relativeYFramePosition(y) - this.origin) + this.element.scrollTop;
|
132
|
+
}
|
133
|
+
|
134
|
+
relativeXFramePosition(x) {
|
135
|
+
return x - this.frame.left;
|
136
|
+
}
|
137
|
+
|
138
|
+
relativeXScrollPosition(x) {
|
139
|
+
return this.relativeXFramePosition(x) + this.element.scrollLeft;
|
140
|
+
}
|
141
|
+
}
|
@@ -0,0 +1,98 @@
|
|
1
|
+
export default class TimeKeeper {
|
2
|
+
static UNITS = {
|
3
|
+
hours: 3_600_000,
|
4
|
+
minutes: 60_000,
|
5
|
+
seconds: 1_000,
|
6
|
+
milliseconds: 1
|
7
|
+
}
|
8
|
+
|
9
|
+
static CLIP_SCALES = [
|
10
|
+
{ max: 40, clipSize: 60 },
|
11
|
+
{ max: 50, clipSize: 30 },
|
12
|
+
{ max: 150, clipSize: 15 },
|
13
|
+
{ max: 280, clipSize: 5 },
|
14
|
+
{ max: Infinity, clipSize: 1}
|
15
|
+
]
|
16
|
+
|
17
|
+
// static DEFAULT_SCOPES = {
|
18
|
+
// hours: {
|
19
|
+
// unit: TimeKeeper.UNITS.hours,
|
20
|
+
// metric: TimeKeeper.UNITS.minutes,
|
21
|
+
// defaultScale: 60,
|
22
|
+
// defaultClipSize: 15,
|
23
|
+
// defaultClipScales: [
|
24
|
+
// { max: 40, clipSize: 60 },
|
25
|
+
// { max: 50, clipSize: 30 },
|
26
|
+
// { max: 150, clipSize: 15 },
|
27
|
+
// { max: 280, clipSize: 5 },
|
28
|
+
// { max: Infinity, clipSize: 1}
|
29
|
+
// ]
|
30
|
+
// }
|
31
|
+
// }
|
32
|
+
|
33
|
+
constructor({ start, end, scope = "hours", scale = 60, clip = 15}) {
|
34
|
+
this.startTime = start;
|
35
|
+
this.endTime = end;
|
36
|
+
this.scope = this.constructor.UNITS[scope] || this.constructor.UNITS.hours;
|
37
|
+
this.metric = this.metricFor(scope);
|
38
|
+
this.conversion = this.scope / this.metric;
|
39
|
+
this.scale = scale;
|
40
|
+
this.clipSize = clip;
|
41
|
+
// this.extendedStartTime = this.timeKeeper.startTime;
|
42
|
+
// this.extendedEndTime = this.timeKeeper.endTime;
|
43
|
+
}
|
44
|
+
|
45
|
+
metricFor(scope) {
|
46
|
+
switch(scope) {
|
47
|
+
case "hours":
|
48
|
+
return TimeKeeper.UNITS.minutes;
|
49
|
+
case "minutes":
|
50
|
+
return TimeKeeper.UNITS.seconds;
|
51
|
+
case "seconds":
|
52
|
+
return TimeKeeper.UNITS.milliseconds;
|
53
|
+
default:
|
54
|
+
return TimeKeeper.UNITS.minutes;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
rescale(scale) {
|
59
|
+
this.scale = scale;
|
60
|
+
this.clipSize = this.constructor.CLIP_SCALES.find(range => scale < range.max)?.clipSize || 15;
|
61
|
+
}
|
62
|
+
|
63
|
+
timeBoundaries(timeA, timeB) {
|
64
|
+
let startTime = new Date(Math.min(timeA, timeB));
|
65
|
+
let endTime = new Date(Math.max(timeA, timeB));
|
66
|
+
|
67
|
+
if (endTime - startTime < this.clipSize * 60 * 1000) {
|
68
|
+
endTime = new Date(startTime.getTime() + (this.clipSize * 60 * 1000));
|
69
|
+
}
|
70
|
+
|
71
|
+
return {
|
72
|
+
start: { time: startTime, location: this.positionOfTime(startTime) },
|
73
|
+
end: { time: endTime, location: this.positionOfTime(endTime) }
|
74
|
+
};
|
75
|
+
}
|
76
|
+
|
77
|
+
timeAtPosition(relativeScrollY) {
|
78
|
+
let scopeOrdinal = Math.trunc(relativeScrollY / this.scale); // Eg "Hours from start"
|
79
|
+
let positionInScope = (relativeScrollY % this.scale) / this.scale;
|
80
|
+
let scopeMeasure = Math.trunc(positionInScope * this.conversion)
|
81
|
+
let clippedScopeMeasure = Math.floor(scopeMeasure / this.clipSize) * this.clipSize;
|
82
|
+
let millisecondsFromStart = (scopeOrdinal * this.scope) + (scopeMeasure * this.metric);
|
83
|
+
let clippedMillisecondsFromStart = (scopeOrdinal * this.scope) + (clippedScopeMeasure * this.metric);
|
84
|
+
|
85
|
+
return {
|
86
|
+
real: new Date(this.startTime.getTime() + millisecondsFromStart), // For some reason 1 second is added to real each time you change it.
|
87
|
+
clipped: new Date(this.startTime.getTime() + clippedMillisecondsFromStart),
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
positionOfTime(time) {
|
92
|
+
let timeFromStart = time - this.startTime;
|
93
|
+
let scopeFromStart = Math.floor(timeFromStart / this.scope);
|
94
|
+
let metricFromScope = Math.floor((timeFromStart % this.scope) / this.metric);
|
95
|
+
let pixelsFromStart = (scopeFromStart * this.scale) + (metricFromScope * this.scale / this.conversion);
|
96
|
+
return pixelsFromStart;
|
97
|
+
}
|
98
|
+
}
|