time_table 0.1.5 → 0.1.6

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.
@@ -5,9 +5,19 @@ import DragEventState from "../helpers/drag_event_state.js"
5
5
  import TimeTableRenderer from "../helpers/time_table_renderer.js"
6
6
 
7
7
  export default class extends Controller {
8
- static targets = ["canvas", "eventsList", "event", "eventName", "eventTime", "timestamp"]
8
+ // static targets = ["canvas", "eventsList", "event", "eventName", "eventTime", "timestamp", "template"]
9
+
10
+ static targets = ["event", "template", "eventsList", "eventActionMenu"]
9
11
 
10
12
  connect() {
13
+ this.renderer = new TimeTableRenderer(this.templateTarget);
14
+ this.eventTargets.forEach((event) => {
15
+ this.renderer.setSpecialClasses(event);
16
+ });
17
+
18
+ // A little hacky
19
+ this.element.classList.remove("loading");
20
+
11
21
  this.timeKeeper = new TimeKeeper({
12
22
  start: new Date(this.element.dataset.timetableStartTime),
13
23
  end: new Date(this.element.dataset.timetableEndTime),
@@ -21,10 +31,16 @@ export default class extends Controller {
21
31
  this.scrollFrame = new ScrollFrame(this.element, { origin: topPadding });
22
32
 
23
33
  this.dragEvent = null;
34
+ }
24
35
 
25
- this.eventTargets.forEach((event) => {
26
- TimeTableRenderer.setSpecialClasses(event);
27
- });
36
+ changeColumn(event) {
37
+ if(this.dragEvent && this.dragEvent.type == "move") {
38
+ let currentEvent = this.dragEvent.target;
39
+ this.dragEvent.column = event.currentTarget.closest(".column");
40
+ this.dragEvent.date = event.params.column
41
+ // TODO: Change query method
42
+ this.dragEvent.column.querySelector(".timetable-events").appendChild(currentEvent);
43
+ }
28
44
  }
29
45
 
30
46
  startDragEvent(event) {
@@ -36,10 +52,12 @@ export default class extends Controller {
36
52
  }
37
53
 
38
54
  this.dragEvent = new DragEventState({
55
+ date: event.params.column || event.target.closest(".column").dataset.timetableColumnParam,
39
56
  type: event.params.dragEvent,
40
57
  scrollFrame: this.scrollFrame,
41
58
  timeKeeper: this.timeKeeper,
42
- target: event.currentTarget.closest(".event")
59
+ target: event.currentTarget.closest(".event"),
60
+ column: event.currentTarget.closest(".column")
43
61
  });
44
62
 
45
63
  this.dragEvent.captureOriginSnapshot(event);
@@ -50,8 +68,21 @@ export default class extends Controller {
50
68
  drag(event) {
51
69
  if(this.dragEvent) {
52
70
 
71
+ if(this.dragEvent.prevent) {
72
+ this.dragEvent = null;
73
+ return;
74
+ }
75
+
53
76
  this.dragEvent.captureSnapshot(event);
77
+ if(this.dragEvent.current.realTime - this.dragEvent.origin.realTime < 100) {
78
+ return;
79
+ }
54
80
 
81
+ // HACKY
82
+ this.dragEvent.dragging = true;
83
+ if(this.dragEvent.type != "create" && this.dragEvent.target) {
84
+ this.#dispatchDragClick(this.dragEvent.target);
85
+ }
55
86
  // Calls dragResize, dragMove, dragCreate, etc.
56
87
  this.#invokeDragMethodFor(this.dragEvent.type);
57
88
 
@@ -63,6 +94,27 @@ export default class extends Controller {
63
94
 
64
95
  stopDragEvent(event) {
65
96
  event.preventDefault();
97
+ event.stopPropagation();
98
+
99
+ // HACK - Should come up with system of determining form target to submit (ADD CREATE CHECK TO DELETE UNSET EVENT HERE)
100
+ if(this.dragEvent && (this.dragEvent.type == "move" || this.dragEvent.type == "resize") && this.dragEvent.origin.type != "create") {
101
+ if(Math.abs(this.dragEvent.origin.time.real - this.dragEvent.current.time.real) > 100 || this.dragEvent.origin.column != this.dragEvent.current.column) {
102
+ console.log("Submitting form");
103
+ let form = this.dragEvent.target.querySelector(".calendar-event-form");
104
+ form.requestSubmit();
105
+ }
106
+ }
107
+
108
+ if(this.dragEvent && !this.dragEvent.dragging && this.dragEvent.target) {
109
+ this.#dispatchStillClick(this.dragEvent.target)
110
+ }
111
+
112
+ if(this.dragEvent && this.dragEvent.origin.type == "create" && this.dragEvent.previous) {
113
+ let target = this.dragEvent.target;
114
+ setTimeout(() => {
115
+ this.#dispatchStillClick(target);
116
+ }, 100);
117
+ }
66
118
 
67
119
  this.dragEvent = null;
68
120
 
@@ -70,6 +122,12 @@ export default class extends Controller {
70
122
  this.#removeDragEventListeners();
71
123
  }
72
124
 
125
+ preventDragEvent(event) {
126
+ event.stopPropagation();
127
+ if(this.dragEvent) {
128
+ this.dragEvent.prevent = true;
129
+ }
130
+ }
73
131
 
74
132
  //////////////////
75
133
  // Drag Methods //
@@ -77,17 +135,23 @@ export default class extends Controller {
77
135
 
78
136
  dragCreate(event) {
79
137
  let timeBoundaries = this.timeKeeper.timeBoundaries(this.dragEvent.current.time.clipped, this.dragEvent.origin.time.clipped);
80
- let eventElement = TimeTableRenderer.createEventElement(timeBoundaries);
138
+
139
+ let eventElement = this.renderer.createEventElement(this.dragEvent.date, timeBoundaries);
81
140
 
82
141
  this.eventsListTarget.appendChild(eventElement);
83
142
 
143
+ // Come up with better way to select events list, maybe remove child div all together
144
+ this.dragEvent.column.querySelector(".timetable-events").appendChild(eventElement);
145
+ // this.eventsListTarget
146
+
84
147
  this.dragEvent.shift({ type: "resize", target: eventElement });
85
148
  this.handleOverlap(eventElement);
86
149
  }
87
150
 
88
151
  dragResize(event) {
89
152
  let timeBoundaries = this.timeKeeper.timeBoundaries(this.dragEvent.current.time.clipped, this.dragEvent.origin.target.startTime.real);
90
- TimeTableRenderer.updateEventElement(this.dragEvent.target, timeBoundaries);
153
+
154
+ this.renderer.updateEventElement(this.dragEvent.target, this.dragEvent.date, timeBoundaries);
91
155
  this.handleOverlap(this.dragEvent.target);
92
156
  }
93
157
 
@@ -98,7 +162,7 @@ export default class extends Controller {
98
162
 
99
163
  let timeBoundaries = this.timeKeeper.timeBoundaries(updatedStartTime, updatedEndTime);
100
164
 
101
- TimeTableRenderer.updateEventElement(this.dragEvent.target, timeBoundaries);
165
+ this.renderer.updateEventElement(this.dragEvent.target, this.dragEvent.date, timeBoundaries);
102
166
  this.handleOverlap(this.dragEvent.target);
103
167
  }
104
168
 
@@ -113,31 +177,31 @@ export default class extends Controller {
113
177
  this.eventTargets.forEach((event) => {
114
178
  event.style.setProperty("height", `${event.offsetHeight * (scaleChange)}px`);
115
179
  event.style.setProperty("top", `${event.offsetTop * (scaleChange)}px`);
116
- TimeTableRenderer.setSpecialClasses(event);
180
+ this.renderer.setSpecialClasses(event);
117
181
  });
118
182
  }
119
183
 
120
184
  handleOverlap(element) {
121
185
  let eventTop = element.offsetTop;
122
186
  let eventBottom = eventTop + element.offsetHeight;
123
-
124
- this.eventTargets.forEach((event) => {
125
- if(event != element) {
126
- let top = event.offsetTop;
127
- let bottom = top + event.offsetHeight;
128
-
129
- if(eventTop >= top && eventBottom <= bottom) {
130
- element.style.setProperty("left", "50px");
131
- event.style.setProperty("right", "20px");
132
- } else if(eventTop < bottom && eventBottom > top) {
133
- element.style.setProperty("left", "50%");
134
- event.style.setProperty("right", "50%");
135
- } else {
136
- element.style.setProperty("left", "50px");
137
- event.style.setProperty("right", "20px");
138
- }
139
- }
140
- });
187
+ // TODO: Change query method
188
+ // this.dragEvent.column.querySelector(".timetable-events").querySelectorAll(".event").forEach((event) => {
189
+ // if(event != element) {
190
+ // let top = event.offsetTop;
191
+ // let bottom = top + event.offsetHeight;
192
+
193
+ // if(eventTop >= top && eventBottom <= bottom) {
194
+ // element.style.setProperty("left", "50px");
195
+ // event.style.setProperty("right", "20px");
196
+ // } else if(eventTop < bottom && eventBottom > top) {
197
+ // element.style.setProperty("left", "50%");
198
+ // event.style.setProperty("right", "50%");
199
+ // } else {
200
+ // element.style.setProperty("left", "50px");
201
+ // event.style.setProperty("right", "20px");
202
+ // }
203
+ // }
204
+ // });
141
205
  }
142
206
 
143
207
 
@@ -147,6 +211,7 @@ export default class extends Controller {
147
211
  ////////////////////
148
212
 
149
213
  handleScroll(status) {
214
+ console.log("SHOULD BE SCROLLING");
150
215
  if(status == "ABOVE") {
151
216
  console.log("ABOVE");
152
217
  this.insertTimestampAbove();
@@ -168,7 +233,7 @@ export default class extends Controller {
168
233
  let time = new Date(this.timeKeeper.endTime.getTime());
169
234
  time.setHours(time.getHours() + 1);
170
235
  this.timeKeeper.endTime = time;
171
- let timestampDiv = TimeTableRenderer.timestamp(time);
236
+ let timestampDiv = this.renderer.timestamp(time);
172
237
  this.element.querySelector(".timestamps").appendChild(timestampDiv);
173
238
  }
174
239
 
@@ -185,7 +250,7 @@ export default class extends Controller {
185
250
 
186
251
  if (this[`drag${capitalizedType}`]) {
187
252
  this[`drag${capitalizedType}`]();
188
- } else {
253
+ } else {
189
254
  console.error(`No drag method found for type: ${type}`);
190
255
  }
191
256
  }
@@ -194,6 +259,30 @@ export default class extends Controller {
194
259
  // Helpers //
195
260
  /////////////
196
261
 
262
+ #dispatchStillClick(target) {
263
+ target.dispatchEvent(this.#stillClickEvent());
264
+ }
265
+
266
+ #stillClickEvent() {
267
+ return new CustomEvent("still:click", {
268
+ detail: {
269
+ dragEvent: this.dragEvent
270
+ }
271
+ })
272
+ }
273
+
274
+ #dispatchDragClick(target) {
275
+ target.dispatchEvent(this.#dragClickEvent());
276
+ }
277
+
278
+ #dragClickEvent() {
279
+ return new CustomEvent("drag:click", {
280
+ detail: {
281
+ dragEvent: this.dragEvent
282
+ }
283
+ })
284
+ }
285
+
197
286
  #setDragEventListeners() {
198
287
  document.addEventListener("mousemove", this.drag.bind(this));
199
288
  document.addEventListener("mouseup", this.stopDragEvent.bind(this));
@@ -1,9 +1,13 @@
1
1
  export default class DragEventState {
2
- constructor({ type, scrollFrame, timeKeeper, target }) {
2
+ constructor({ type, scrollFrame, timeKeeper, target, column, prevent = false, date }) {
3
3
  this.type = type;
4
4
  this.scrollFrame = scrollFrame;
5
5
  this.timeKeeper = timeKeeper;
6
6
  this.target = target;
7
+ // TODO: Reconsider this
8
+ this.column = column;
9
+ this.date = date;
10
+ this.prevent = prevent; // Kinda hacky
7
11
  }
8
12
 
9
13
  captureOriginSnapshot(event) {
@@ -27,12 +31,17 @@ export default class DragEventState {
27
31
  let relativePosition = this.scrollFrame.relativeCursorPosition(event);
28
32
  return {
29
33
  event: event,
34
+ type: this.type,
35
+ date: this.date,
30
36
  position: relativePosition,
31
37
  time: this.timeKeeper.timeAtPosition(relativePosition.scroll.y),
32
- target: this.targetSnapshot(this.target)
38
+ column: this.column,
39
+ target: this.targetSnapshot(this.target),
40
+ realTime: new Date(),
33
41
  }
34
42
  }
35
43
 
44
+ // Probably want to add column here
36
45
  targetSnapshot(target) {
37
46
  if(!target) {
38
47
  return null;
@@ -1,28 +1,29 @@
1
- class TimeTableRenderer {
2
- createEventElement({start, end}) {
3
- const element = document.createElement("div");
4
-
5
- element.classList.add("event");
6
- element.setAttribute("data-time-table-target", "event");
7
- element.setAttribute("data-action", "mousedown->time-table#startDragEvent");
8
- element.setAttribute("data-time-table-drag-event-param", "move");
9
-
10
- element.innerHTML = `
11
- <div class="event-name"><span style="font-weight: 400; color: gray;">(No Title)</span></div>
12
- <div class="event-time" data-action="mousedown->time-table#startDragEvent" data-time-table-drag-event-param="resize">
13
- ${ this.#durationString(start.time, end.time) }
14
- </div>
15
- `;
16
-
17
- this.updateEventElement(element, {start, end});
1
+ export default class TimeTableRenderer {
2
+ constructor(template) {
3
+ this.template = template;
4
+ }
18
5
 
6
+ createEventElement(date, {start, end}) {
7
+ const element = this.template.content.cloneNode(true).firstElementChild;
8
+ this.updateEventElement(element, date, {start, end});
19
9
  return element;
20
10
  }
21
11
 
22
- updateEventElement(element, {start, end}) {
12
+ // TODO: Move all app specific logic to rails app
13
+ // Should move updater to a delegate controller in rails app, maybe creater to
14
+ updateEventElement(element, date, {start, end}) {
23
15
  element.style.setProperty("top", `${start.location}px`);
24
16
  element.style.setProperty("height", `${end.location - start.location}px`);
25
17
 
18
+ let form = element.querySelector(".calendar-event-form");
19
+ // This shouldn't happen here
20
+ if(form.querySelector("#event_date_string")) {
21
+ form.querySelector("#event_date_string").setAttribute("value", date);
22
+ form.querySelector("#event_date_string").dispatchEvent(new CustomEvent("value:change", { bubbles: true, detail: { value: date }}));
23
+ }
24
+ form.querySelector("#event_start_time").value = this.getMilitaryTime(start.time);
25
+ form.querySelector("#event_end_time").value = this.getMilitaryTime(end.time);
26
+
26
27
  this.setSpecialClasses(element);
27
28
 
28
29
  element.querySelector(".event-time").innerHTML = this.#durationString(start.time, end.time);
@@ -30,16 +31,24 @@ class TimeTableRenderer {
30
31
  return element;
31
32
  }
32
33
 
34
+ getMilitaryTime(date) {
35
+ let hours = date.getUTCHours();
36
+ let minutes = date.getUTCMinutes();
37
+ minutes = minutes < 10 ? '0' + minutes : minutes; // Ensure two-digit minutes
38
+ hours = hours < 10 ? '0' + hours : hours; // Ensure two-digit hours
39
+ return `${hours}:${minutes}`;
40
+ }
41
+
33
42
  timestamp(time) {
34
43
  let timestampeElement = document.createElement("div");
35
44
  timestampeElement.classList.add("timestamp");
36
45
  timestampeElement.classList.add("out-of-bounds");
37
- timestampeElement.setAttribute("data-time-table-target", "timestamp");
38
- timestampeElement.setAttribute("data-action", "mousedown->time-table#startDragEvent");
39
- timestampeElement.setAttribute("data-time-table-drag-event-param", "rescale");
46
+ timestampeElement.setAttribute("data-timetable-target", "timestamp");
47
+ timestampeElement.setAttribute("data-action", "mousedown->timetable#startDragEvent");
48
+ timestampeElement.setAttribute("data-timetable-drag-event-param", "rescale");
40
49
 
41
50
  timestampeElement.innerHTML = `
42
- <div class="label" data-action="mousedown->time-table#startDragEvent" data-time-table-drag-event-param="rescale">
51
+ <div class="label" data-action="mousedown->timetable#startDragEvent" data-timetable-drag-event-param="rescale">
43
52
  ${this.#timeString(time, "short")}
44
53
  </div>
45
54
  `;
@@ -48,8 +57,10 @@ class TimeTableRenderer {
48
57
  }
49
58
 
50
59
  setSpecialClasses(element) {
51
- element.classList.toggle("tiny", element.offsetHeight < 15);
52
- element.classList.toggle("small", element.offsetHeight < 45);
60
+ element.classList.toggle("no-content", element.offsetHeight < 15);
61
+ element.classList.toggle("tiny", element.offsetHeight < 30);
62
+ element.classList.toggle("mini", element.offsetHeight < 45);
63
+ element.classList.toggle("small", element.offsetHeight < 75);
53
64
  }
54
65
 
55
66
  #timeString(date, format = "long") {
@@ -72,4 +83,4 @@ class TimeTableRenderer {
72
83
  }
73
84
  }
74
85
 
75
- export default new TimeTableRenderer();
86
+ // export default new TimeTableRenderer();
@@ -0,0 +1,143 @@
1
+ .timetable {
2
+ position: relative;
3
+ height: fit-content;
4
+ overflow: visible;
5
+ display: grid;
6
+ }
7
+
8
+ .timetable-calendar .timetable {
9
+ height: 100%;
10
+ }
11
+
12
+ .timetable > * {
13
+ grid-column: 1;
14
+ grid-row: 1;
15
+ }
16
+
17
+ .timetable-canvas {
18
+ height: calc(100%);
19
+ flex-grow: 1;
20
+ position: relative;
21
+ z-index: 1;
22
+ }
23
+
24
+ .timetable-column {
25
+ border-left: 1px solid var(--backdrop-5);
26
+ height: 100%;
27
+ }
28
+
29
+ .timetable-events {
30
+ width: 100%;
31
+ position: relative;
32
+ }
33
+
34
+ /**************/
35
+ /* Timestamps */
36
+ /**************/
37
+
38
+ .timetable .timestamps {
39
+ z-index: 0;
40
+ }
41
+
42
+ .timetable .timestamp {
43
+ font-size: 0.8em;
44
+ width: 100%;
45
+ position: relative;
46
+ height: calc(var(--hour-scale) * 1px);
47
+ overflow: visible;
48
+ }
49
+
50
+ .timetable .timestamp.out-of-bounds {
51
+ color: rgb(168, 167, 167);
52
+ }
53
+
54
+ .timetable .timestamp .label {
55
+ margin-top: -0.5em;
56
+ height: 100%;
57
+ width: fit-content;
58
+ z-index: 5;
59
+ width: 45px !important;
60
+ text-align: right;
61
+ padding-right: 5px;
62
+ }
63
+
64
+ .timetable .timestamp .label:hover {
65
+ cursor: ns-resize;
66
+ }
67
+
68
+ .timetable .timestamp::before {
69
+ content: "";
70
+ position: absolute;
71
+ right: 0;
72
+ left: 45px;
73
+ height: 1px;
74
+ background-color: var(--backdrop-7);
75
+ opacity: 0.5;
76
+ }
77
+
78
+ /************/
79
+ /* Calendar */
80
+ /************/
81
+
82
+ .timetable-calendar .timetable-headers,
83
+ .timetable-calendar .timetable-canvas {
84
+ margin-left: 45px;
85
+ display: flex;
86
+ flex-direction: row;
87
+ }
88
+
89
+ .timetable-calendar .timetable-header,
90
+ .timetable-calendar .timetable-column {
91
+ flex: 1 1 0 !important;
92
+ /* overflow: hidden; */
93
+ }
94
+
95
+ .timetable-calendar .timetable-header {
96
+ display: flex;
97
+ flex-direction: column;
98
+ justify-content: center;
99
+ }
100
+
101
+ .timetable-calendar .timetable-header:only-child {
102
+ align-items: flex-start;
103
+ }
104
+
105
+ /*********/
106
+ /* SKINS */
107
+ /*********/
108
+
109
+ .timetable-column {
110
+ display: grid;
111
+ }
112
+
113
+ .timetable-column > * {
114
+ grid-area: 1 / -1 / 1 / -1; /* Span the single column */
115
+ width: 100%; /* Ensure full width */
116
+ }
117
+
118
+ .timetable-skins {
119
+ width: 100%;
120
+ position: relative;
121
+ }
122
+
123
+ .timetable-skin {
124
+ width: 100%;
125
+ background-color: var(--backdrop-3);
126
+ position: absolute;
127
+ right: 0;
128
+ left: 0;
129
+ opacity: 0.5;
130
+ }
131
+
132
+ /* .timetable-skin:hover { */
133
+ /* background-color: var(--backdrop-5); */
134
+ /* opacity: 0.5; */
135
+ /* } */
136
+
137
+ .timetable-events {
138
+ pointer-events: none;
139
+ }
140
+
141
+ .timetable-events > * {
142
+ pointer-events: auto;
143
+ }
@@ -1,18 +1,18 @@
1
1
  module TimeTable
2
- module TimeTableHelper
2
+ module TimeTableOldHelper
3
3
  def time_table(date = nil, frozen: false, scale: 60, events: [], clip: 15, &block)
4
4
  # @time_table = TimeTable.new
5
5
  # @time_table.scale = scale
6
- timetable = Timetable.new(date, scale: scale, clip_size: clip, frozen: frozen)
7
- render "time_table/time_table", timetable: timetable, events: events, &block
6
+ timetable = TimeTable.new(date, scale: scale, clip_size: clip, frozen: frozen, events: events, view: self,)
7
+ render "time_table/time_table", timetable: timetable, &block
8
8
  end
9
9
 
10
10
  def hours_in_day
11
11
  end
12
12
 
13
13
 
14
- class Timetable
15
- attr_accessor :start_time, :end_time, :segment_size, :segment_scale, :clip_size, :clock_type, :frozen
14
+ class TimeTable
15
+ attr_accessor :start_time, :end_time, :segment_size, :segment_scale, :clip_size, :clock_type, :frozen, :events
16
16
 
17
17
  def initialize(date = nil, **options)
18
18
  @start_time = options[:start_time] || 0
@@ -24,6 +24,8 @@ module TimeTable
24
24
  @clip_size = options[:clip_size] || 15
25
25
  @clock_type = options[:clock_type] || :meridian
26
26
  @frozen = options[:frozen] || false
27
+ @events = options[:events] || []
28
+ @view = options[:view] || nil
27
29
  end
28
30
 
29
31
  def segments
@@ -36,6 +38,16 @@ module TimeTable
36
38
  end
37
39
  end
38
40
 
41
+ def render_events
42
+ @view.content_tag :div, class: "timetable-canvas", data: { time_table_target: "canvas" } do
43
+ @view.content_tag :div, class: "timetable-events", data: { time_table_target: "eventsList" } do
44
+ events.each do |event|
45
+ yield event
46
+ end
47
+ end
48
+ end
49
+ end
50
+
39
51
  def time_in_meridian(time)
40
52
  hour = time / 60
41
53
  minute = time % 60