time_table 0.1.4 → 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,21 +31,33 @@ 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) {
31
47
  event.preventDefault();
32
48
  event.stopPropagation();
33
49
 
50
+ if(event.params.dragEvent != "rescale" && this.element.dataset.timetableFrozenParam == "true") {
51
+ return;
52
+ }
53
+
34
54
  this.dragEvent = new DragEventState({
55
+ date: event.params.column || event.target.closest(".column").dataset.timetableColumnParam,
35
56
  type: event.params.dragEvent,
36
57
  scrollFrame: this.scrollFrame,
37
58
  timeKeeper: this.timeKeeper,
38
- target: event.currentTarget.closest(".event")
59
+ target: event.currentTarget.closest(".event"),
60
+ column: event.currentTarget.closest(".column")
39
61
  });
40
62
 
41
63
  this.dragEvent.captureOriginSnapshot(event);
@@ -46,8 +68,21 @@ export default class extends Controller {
46
68
  drag(event) {
47
69
  if(this.dragEvent) {
48
70
 
71
+ if(this.dragEvent.prevent) {
72
+ this.dragEvent = null;
73
+ return;
74
+ }
75
+
49
76
  this.dragEvent.captureSnapshot(event);
77
+ if(this.dragEvent.current.realTime - this.dragEvent.origin.realTime < 100) {
78
+ return;
79
+ }
50
80
 
81
+ // HACKY
82
+ this.dragEvent.dragging = true;
83
+ if(this.dragEvent.type != "create" && this.dragEvent.target) {
84
+ this.#dispatchDragClick(this.dragEvent.target);
85
+ }
51
86
  // Calls dragResize, dragMove, dragCreate, etc.
52
87
  this.#invokeDragMethodFor(this.dragEvent.type);
53
88
 
@@ -59,6 +94,27 @@ export default class extends Controller {
59
94
 
60
95
  stopDragEvent(event) {
61
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
+ }
62
118
 
63
119
  this.dragEvent = null;
64
120
 
@@ -66,6 +122,12 @@ export default class extends Controller {
66
122
  this.#removeDragEventListeners();
67
123
  }
68
124
 
125
+ preventDragEvent(event) {
126
+ event.stopPropagation();
127
+ if(this.dragEvent) {
128
+ this.dragEvent.prevent = true;
129
+ }
130
+ }
69
131
 
70
132
  //////////////////
71
133
  // Drag Methods //
@@ -73,17 +135,23 @@ export default class extends Controller {
73
135
 
74
136
  dragCreate(event) {
75
137
  let timeBoundaries = this.timeKeeper.timeBoundaries(this.dragEvent.current.time.clipped, this.dragEvent.origin.time.clipped);
76
- let eventElement = TimeTableRenderer.createEventElement(timeBoundaries);
138
+
139
+ let eventElement = this.renderer.createEventElement(this.dragEvent.date, timeBoundaries);
77
140
 
78
141
  this.eventsListTarget.appendChild(eventElement);
79
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
+
80
147
  this.dragEvent.shift({ type: "resize", target: eventElement });
81
148
  this.handleOverlap(eventElement);
82
149
  }
83
150
 
84
151
  dragResize(event) {
85
152
  let timeBoundaries = this.timeKeeper.timeBoundaries(this.dragEvent.current.time.clipped, this.dragEvent.origin.target.startTime.real);
86
- TimeTableRenderer.updateEventElement(this.dragEvent.target, timeBoundaries);
153
+
154
+ this.renderer.updateEventElement(this.dragEvent.target, this.dragEvent.date, timeBoundaries);
87
155
  this.handleOverlap(this.dragEvent.target);
88
156
  }
89
157
 
@@ -94,7 +162,7 @@ export default class extends Controller {
94
162
 
95
163
  let timeBoundaries = this.timeKeeper.timeBoundaries(updatedStartTime, updatedEndTime);
96
164
 
97
- TimeTableRenderer.updateEventElement(this.dragEvent.target, timeBoundaries);
165
+ this.renderer.updateEventElement(this.dragEvent.target, this.dragEvent.date, timeBoundaries);
98
166
  this.handleOverlap(this.dragEvent.target);
99
167
  }
100
168
 
@@ -109,31 +177,31 @@ export default class extends Controller {
109
177
  this.eventTargets.forEach((event) => {
110
178
  event.style.setProperty("height", `${event.offsetHeight * (scaleChange)}px`);
111
179
  event.style.setProperty("top", `${event.offsetTop * (scaleChange)}px`);
112
- TimeTableRenderer.setSpecialClasses(event);
180
+ this.renderer.setSpecialClasses(event);
113
181
  });
114
182
  }
115
183
 
116
184
  handleOverlap(element) {
117
185
  let eventTop = element.offsetTop;
118
186
  let eventBottom = eventTop + element.offsetHeight;
119
-
120
- this.eventTargets.forEach((event) => {
121
- if(event != element) {
122
- let top = event.offsetTop;
123
- let bottom = top + event.offsetHeight;
124
-
125
- if(eventTop >= top && eventBottom <= bottom) {
126
- element.style.setProperty("left", "50px");
127
- event.style.setProperty("right", "20px");
128
- } else if(eventTop < bottom && eventBottom > top) {
129
- element.style.setProperty("left", "50%");
130
- event.style.setProperty("right", "50%");
131
- } else {
132
- element.style.setProperty("left", "50px");
133
- event.style.setProperty("right", "20px");
134
- }
135
- }
136
- });
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
+ // });
137
205
  }
138
206
 
139
207
 
@@ -143,6 +211,7 @@ export default class extends Controller {
143
211
  ////////////////////
144
212
 
145
213
  handleScroll(status) {
214
+ console.log("SHOULD BE SCROLLING");
146
215
  if(status == "ABOVE") {
147
216
  console.log("ABOVE");
148
217
  this.insertTimestampAbove();
@@ -164,7 +233,7 @@ export default class extends Controller {
164
233
  let time = new Date(this.timeKeeper.endTime.getTime());
165
234
  time.setHours(time.getHours() + 1);
166
235
  this.timeKeeper.endTime = time;
167
- let timestampDiv = TimeTableRenderer.timestamp(time);
236
+ let timestampDiv = this.renderer.timestamp(time);
168
237
  this.element.querySelector(".timestamps").appendChild(timestampDiv);
169
238
  }
170
239
 
@@ -181,7 +250,7 @@ export default class extends Controller {
181
250
 
182
251
  if (this[`drag${capitalizedType}`]) {
183
252
  this[`drag${capitalizedType}`]();
184
- } else {
253
+ } else {
185
254
  console.error(`No drag method found for type: ${type}`);
186
255
  }
187
256
  }
@@ -190,6 +259,30 @@ export default class extends Controller {
190
259
  // Helpers //
191
260
  /////////////
192
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
+
193
286
  #setDragEventListeners() {
194
287
  document.addEventListener("mousemove", this.drag.bind(this));
195
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
3
- def time_table(date = nil, scale: 60, events: [], clip: 15, &block)
2
+ module TimeTableOldHelper
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)
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
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
@@ -23,6 +23,9 @@ module TimeTable
23
23
  @segment_scale = options[:scale] || 100
24
24
  @clip_size = options[:clip_size] || 15
25
25
  @clock_type = options[:clock_type] || :meridian
26
+ @frozen = options[:frozen] || false
27
+ @events = options[:events] || []
28
+ @view = options[:view] || nil
26
29
  end
27
30
 
28
31
  def segments
@@ -35,6 +38,16 @@ module TimeTable
35
38
  end
36
39
  end
37
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
+
38
51
  def time_in_meridian(time)
39
52
  hour = time / 60
40
53
  minute = time % 60