time_table 0.1.5 → 0.1.7

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" && !this.dragEvent.locked) {
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) {
@@ -35,11 +51,16 @@ export default class extends Controller {
35
51
  return;
36
52
  }
37
53
 
54
+ console.log(event.params)
55
+
38
56
  this.dragEvent = new DragEventState({
57
+ date: event.params.column || event.target.closest(".column").dataset.timetableColumnParam,
39
58
  type: event.params.dragEvent,
40
59
  scrollFrame: this.scrollFrame,
41
60
  timeKeeper: this.timeKeeper,
42
- target: event.currentTarget.closest(".event")
61
+ locked: event.params.locked,
62
+ target: event.currentTarget.closest(".event"),
63
+ column: event.currentTarget.closest(".column")
43
64
  });
44
65
 
45
66
  this.dragEvent.captureOriginSnapshot(event);
@@ -50,8 +71,26 @@ export default class extends Controller {
50
71
  drag(event) {
51
72
  if(this.dragEvent) {
52
73
 
74
+ if(this.dragEvent.prevent) {
75
+ this.dragEvent = null;
76
+ return;
77
+ }
78
+
79
+ console.log(this.dragEvent.locked);
80
+ if(this.dragEvent.locked) {
81
+ return;
82
+ }
83
+
53
84
  this.dragEvent.captureSnapshot(event);
85
+ if(this.dragEvent.current.realTime - this.dragEvent.origin.realTime < 100) {
86
+ return;
87
+ }
54
88
 
89
+ // HACKY
90
+ this.dragEvent.dragging = true;
91
+ if(this.dragEvent.type != "create" && this.dragEvent.target) {
92
+ this.#dispatchDragClick(this.dragEvent.target);
93
+ }
55
94
  // Calls dragResize, dragMove, dragCreate, etc.
56
95
  this.#invokeDragMethodFor(this.dragEvent.type);
57
96
 
@@ -63,6 +102,27 @@ export default class extends Controller {
63
102
 
64
103
  stopDragEvent(event) {
65
104
  event.preventDefault();
105
+ event.stopPropagation();
106
+
107
+ // HACK - Should come up with system of determining form target to submit (ADD CREATE CHECK TO DELETE UNSET EVENT HERE)
108
+ if(this.dragEvent && (this.dragEvent.type == "move" || this.dragEvent.type == "resize") && this.dragEvent.origin.type != "create") {
109
+ if(Math.abs(this.dragEvent.origin.time.real - this.dragEvent.current.time.real) > 100 || this.dragEvent.origin.column != this.dragEvent.current.column) {
110
+ console.log("Submitting form");
111
+ let form = this.dragEvent.target.querySelector(".calendar-event-form");
112
+ form.requestSubmit();
113
+ }
114
+ }
115
+
116
+ if(this.dragEvent && !this.dragEvent.dragging && this.dragEvent.target) {
117
+ this.#dispatchStillClick(this.dragEvent.target)
118
+ }
119
+
120
+ if(this.dragEvent && this.dragEvent.origin.type == "create" && this.dragEvent.previous) {
121
+ let target = this.dragEvent.target;
122
+ setTimeout(() => {
123
+ this.#dispatchStillClick(target);
124
+ }, 100);
125
+ }
66
126
 
67
127
  this.dragEvent = null;
68
128
 
@@ -70,6 +130,12 @@ export default class extends Controller {
70
130
  this.#removeDragEventListeners();
71
131
  }
72
132
 
133
+ preventDragEvent(event) {
134
+ event.stopPropagation();
135
+ if(this.dragEvent) {
136
+ this.dragEvent.prevent = true;
137
+ }
138
+ }
73
139
 
74
140
  //////////////////
75
141
  // Drag Methods //
@@ -77,17 +143,23 @@ export default class extends Controller {
77
143
 
78
144
  dragCreate(event) {
79
145
  let timeBoundaries = this.timeKeeper.timeBoundaries(this.dragEvent.current.time.clipped, this.dragEvent.origin.time.clipped);
80
- let eventElement = TimeTableRenderer.createEventElement(timeBoundaries);
146
+
147
+ let eventElement = this.renderer.createEventElement(this.dragEvent.date, timeBoundaries);
81
148
 
82
149
  this.eventsListTarget.appendChild(eventElement);
83
150
 
151
+ // Come up with better way to select events list, maybe remove child div all together
152
+ this.dragEvent.column.querySelector(".timetable-events").appendChild(eventElement);
153
+ // this.eventsListTarget
154
+
84
155
  this.dragEvent.shift({ type: "resize", target: eventElement });
85
156
  this.handleOverlap(eventElement);
86
157
  }
87
158
 
88
159
  dragResize(event) {
89
160
  let timeBoundaries = this.timeKeeper.timeBoundaries(this.dragEvent.current.time.clipped, this.dragEvent.origin.target.startTime.real);
90
- TimeTableRenderer.updateEventElement(this.dragEvent.target, timeBoundaries);
161
+
162
+ this.renderer.updateEventElement(this.dragEvent.target, this.dragEvent.date, timeBoundaries);
91
163
  this.handleOverlap(this.dragEvent.target);
92
164
  }
93
165
 
@@ -98,7 +170,7 @@ export default class extends Controller {
98
170
 
99
171
  let timeBoundaries = this.timeKeeper.timeBoundaries(updatedStartTime, updatedEndTime);
100
172
 
101
- TimeTableRenderer.updateEventElement(this.dragEvent.target, timeBoundaries);
173
+ this.renderer.updateEventElement(this.dragEvent.target, this.dragEvent.date, timeBoundaries);
102
174
  this.handleOverlap(this.dragEvent.target);
103
175
  }
104
176
 
@@ -113,31 +185,31 @@ export default class extends Controller {
113
185
  this.eventTargets.forEach((event) => {
114
186
  event.style.setProperty("height", `${event.offsetHeight * (scaleChange)}px`);
115
187
  event.style.setProperty("top", `${event.offsetTop * (scaleChange)}px`);
116
- TimeTableRenderer.setSpecialClasses(event);
188
+ this.renderer.setSpecialClasses(event);
117
189
  });
118
190
  }
119
191
 
120
192
  handleOverlap(element) {
121
193
  let eventTop = element.offsetTop;
122
194
  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
- });
195
+ // TODO: Change query method
196
+ // this.dragEvent.column.querySelector(".timetable-events").querySelectorAll(".event").forEach((event) => {
197
+ // if(event != element) {
198
+ // let top = event.offsetTop;
199
+ // let bottom = top + event.offsetHeight;
200
+
201
+ // if(eventTop >= top && eventBottom <= bottom) {
202
+ // element.style.setProperty("left", "50px");
203
+ // event.style.setProperty("right", "20px");
204
+ // } else if(eventTop < bottom && eventBottom > top) {
205
+ // element.style.setProperty("left", "50%");
206
+ // event.style.setProperty("right", "50%");
207
+ // } else {
208
+ // element.style.setProperty("left", "50px");
209
+ // event.style.setProperty("right", "20px");
210
+ // }
211
+ // }
212
+ // });
141
213
  }
142
214
 
143
215
 
@@ -147,6 +219,7 @@ export default class extends Controller {
147
219
  ////////////////////
148
220
 
149
221
  handleScroll(status) {
222
+ console.log("SHOULD BE SCROLLING");
150
223
  if(status == "ABOVE") {
151
224
  console.log("ABOVE");
152
225
  this.insertTimestampAbove();
@@ -168,7 +241,7 @@ export default class extends Controller {
168
241
  let time = new Date(this.timeKeeper.endTime.getTime());
169
242
  time.setHours(time.getHours() + 1);
170
243
  this.timeKeeper.endTime = time;
171
- let timestampDiv = TimeTableRenderer.timestamp(time);
244
+ let timestampDiv = this.renderer.timestamp(time);
172
245
  this.element.querySelector(".timestamps").appendChild(timestampDiv);
173
246
  }
174
247
 
@@ -185,7 +258,7 @@ export default class extends Controller {
185
258
 
186
259
  if (this[`drag${capitalizedType}`]) {
187
260
  this[`drag${capitalizedType}`]();
188
- } else {
261
+ } else {
189
262
  console.error(`No drag method found for type: ${type}`);
190
263
  }
191
264
  }
@@ -194,6 +267,30 @@ export default class extends Controller {
194
267
  // Helpers //
195
268
  /////////////
196
269
 
270
+ #dispatchStillClick(target) {
271
+ target.dispatchEvent(this.#stillClickEvent());
272
+ }
273
+
274
+ #stillClickEvent() {
275
+ return new CustomEvent("still:click", {
276
+ detail: {
277
+ dragEvent: this.dragEvent
278
+ }
279
+ })
280
+ }
281
+
282
+ #dispatchDragClick(target) {
283
+ target.dispatchEvent(this.#dragClickEvent());
284
+ }
285
+
286
+ #dragClickEvent() {
287
+ return new CustomEvent("drag:click", {
288
+ detail: {
289
+ dragEvent: this.dragEvent
290
+ }
291
+ })
292
+ }
293
+
197
294
  #setDragEventListeners() {
198
295
  document.addEventListener("mousemove", this.drag.bind(this));
199
296
  document.addEventListener("mouseup", this.stopDragEvent.bind(this));
@@ -1,9 +1,14 @@
1
1
  export default class DragEventState {
2
- constructor({ type, scrollFrame, timeKeeper, target }) {
2
+ constructor({ type, scrollFrame, timeKeeper, target, column, locked = false, 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
11
+ this.locked = locked; // Kinda hacky
7
12
  }
8
13
 
9
14
  captureOriginSnapshot(event) {
@@ -27,12 +32,17 @@ export default class DragEventState {
27
32
  let relativePosition = this.scrollFrame.relativeCursorPosition(event);
28
33
  return {
29
34
  event: event,
35
+ type: this.type,
36
+ date: this.date,
30
37
  position: relativePosition,
31
38
  time: this.timeKeeper.timeAtPosition(relativePosition.scroll.y),
32
- target: this.targetSnapshot(this.target)
39
+ column: this.column,
40
+ target: this.targetSnapshot(this.target),
41
+ realTime: new Date(),
33
42
  }
34
43
  }
35
44
 
45
+ // Probably want to add column here
36
46
  targetSnapshot(target) {
37
47
  if(!target) {
38
48
  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