aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@lysator.liu.se>2022-06-23 01:39:08 +0200
committerHugo Hörnquist <hugo@lysator.liu.se>2022-06-23 01:39:08 +0200
commit327b322b9583f760cd02ddad7a2a8890df26cc8b (patch)
treed0a73d5df0011233f3eaa5d9a54d1317fad50bbc
parentMinor cleanup in recurrence generate. (diff)
downloadcalp-uid-stuff-2.tar.gz
calp-uid-stuff-2.tar.xz
-rw-r--r--module/calp/html/vcomponent.scm36
-rw-r--r--module/calp/html/view/calendar.scm128
-rw-r--r--module/calp/html/view/calendar/month.scm7
-rw-r--r--module/calp/html/view/calendar/week.scm7
-rw-r--r--module/calp/server/routes.scm67
-rw-r--r--module/vcomponent/util/instance/methods.scm110
-rw-r--r--static/components/edit-rrule.ts12
-rw-r--r--static/components/popup-element.ts13
-rw-r--r--static/components/tab-group-element.ts11
-rw-r--r--static/components/vevent-block.ts4
-rw-r--r--static/components/vevent-description.ts4
-rw-r--r--static/components/vevent-edit.ts25
-rw-r--r--static/components/vevent.ts58
-rw-r--r--static/event-creator.ts2
-rw-r--r--static/script.ts28
-rw-r--r--static/server_connect.ts5
-rw-r--r--static/vevent.ts29
17 files changed, 369 insertions, 177 deletions
diff --git a/module/calp/html/vcomponent.scm b/module/calp/html/vcomponent.scm
index 1cee47a5..472a8c2b 100644
--- a/module/calp/html/vcomponent.scm
+++ b/module/calp/html/vcomponent.scm
@@ -112,7 +112,9 @@
(class ,(when (and (prop ev 'PARTSTAT)
(eq? 'TENTATIVE (prop ev 'PARTSTAT)))
" tentative "))
- (data-uid ,(output-uid ev)))))
+ ,@(when (repeating? ev)
+ `((data-instance ,(datetime->string (as-datetime (prop ev 'DTSTART))))))
+ (data-uid ,(prop ev 'UID)))))
(div (@ (class "vevent eventtext summary-tab"))
(h3 ,(fmt-header
(when (prop ev 'RRULE)
@@ -293,7 +295,10 @@
extra-attributes
`((id ,(html-id ev) "-block")
(data-calendar ,(base64encode (or (prop (parent ev) 'NAME) "unknown")))
- (data-uid ,(output-uid ev))
+ (data-uid ,(prop ev 'UID))
+ ;; TODO write helper for this (re-occuring) case
+ ,@(when (repeating? ev)
+ `((data-instance ,(datetime->string (as-datetime (prop ev 'DTSTART))))))
(class "vevent event"
,(when (and (prop ev 'PARTSTAT)
@@ -352,29 +357,6 @@
(prop event 'RRULE)))))
-;; Return a unique identifier for a specific instance of an event.
-;; Allows us to reference each instance of a repeating event separately
-;; from any other
-(define-public (output-uid event)
- (string-concatenate
- (cons
- (prop event 'UID)
- (when (repeating? event)
- ;; TODO this will break if a UID already looks like this...
- ;; Just using a pre-generated unique string would solve it,
- ;; until someone wants to break us. Therefore, we just give
- ;; up for now, until a proper solution can be devised.
- (list "---"
- ;; TODO Will this give us a unique identifier?
- ;; Or can two events share UID along with start time
- (datetime->string
- (as-datetime (or
- ;; TODO What happens if the parameter RANGE=THISANDFUTURE is set?
- (prop event 'RECURRENCE-ID)
- (prop event 'DTSTART)))
- "~Y-~m-~dT~H:~M:~S"))))))
-
-
(define (week-day-select args)
`(select (@ ,@args)
(option "-")
@@ -476,7 +458,9 @@
;; (hr)
- (input (@ (type "submit")))
+ (input (@ (type "submit") (data-key "this") (value "This")))
+ (input (@ (type "submit") (data-key "this_future") (value "This & Future")))
+ (input (@ (type "submit") (data-key "all") (value "All")))
))))
;; description in sidebar / tab of popup
diff --git a/module/calp/html/view/calendar.scm b/module/calp/html/view/calendar.scm
index 8b7d8075..9e2992a4 100644
--- a/module/calp/html/view/calendar.scm
+++ b/module/calp/html/view/calendar.scm
@@ -2,7 +2,7 @@
:use-module (hnh util)
:use-module (vcomponent)
:use-module ((vcomponent datetime)
- :select (events-between))
+ :select (ev-time<? events-between))
:use-module (datetime)
:use-module (calp html components)
:use-module ((calp html vcomponent)
@@ -10,7 +10,6 @@
fmt-day
make-block
fmt-single-event
- output-uid
))
:use-module (calp html config)
:use-module (calp html util)
@@ -29,8 +28,14 @@
:select (group-stream get-groups-between))
:use-module ((base64) :select (base64encode))
+ :use-module ((vcomponent util instance) :select (global-event-object))
+ :use-module ((vcomponent util instance methods) :select (fixed-events-in-range
+ repeating-events-in-range))
+
:use-module (ice-9 format)
:use-module (calp translation)
+ :use-module ((vcomponent formats xcal output)
+ :select (vcomponent->sxcal))
)
@@ -46,6 +51,35 @@
(define repo-url (make-parameter "https://git.hornquist.se/calp"))
+;; Mapping showing which events belongs to which calendar,
+;; on the form
+;; (calendar (@ (key ,(base64-encode calendar-name)))
+;; (li ,event-uid) ...)
+(define (calendar-event-mapping events)
+ `(div (@ (style "display:none !important;")
+ (id "calendar-event-mapping"))
+
+ ;; ,(for (calendar entries ...) in (group-by parent events)
+ ;; `(calendar (@ (key ,(base64encode (prop calendar 'NAME))))
+ ;; ,@(map (lambda (uid) `(li ,uid))
+ ;; (map (extract 'UID) entries))))
+
+ ,(let ((ht (make-hash-table)))
+ (for-each (lambda (event)
+ (define name (prop (parent event) 'NAME))
+ (hash-set! ht name
+ (cons (prop event 'UID)
+ (hash-ref ht name '()))))
+ events)
+
+ (hash-map->list
+ (lambda (key values)
+ `(calendar (@ (key ,(base64encode key)))
+ ,@(map (lambda (uid) `(li ,uid))
+ values)))
+ ht))))
+
+
;; TODO document what @var{render-calendar} is supposed to take and return.
;; Can at least note that @var{render-calendar} is strongly encouraged to include
;; (script "const VIEW='??';"), where ?? is replaced by the name of the view.
@@ -362,57 +396,39 @@ window.default_calendar='~a';"
SEQUENCE REQUEST-STATUS
)))
- ,@(let* (
- (flat-events
- ;; A simple filter-sorted-stream on event-overlaps? here fails.
- ;; See tests/annoying-events.scm
- (stream->list
- (stream-filter
- (lambda (ev)
- ((@ (vcomponent datetime) event-overlaps?)
- ev pre-start
- (date+ post-end (date day: 1))))
- (stream-take-while (lambda (ev) (date<
- (as-date (prop ev 'DTSTART))
- (date+ post-end (date day: 1))))
- events))))
- (repeating% regular (partition repeating? flat-events))
- (repeating
- (for ev in repeating%
- (define instance (copy-vcomponent ev))
-
- (set! (prop instance 'UID) (output-uid instance))
- (delete-parameter! (prop* instance 'DTSTART) '-X-HNH-ORIGINAL)
- (delete-parameter! (prop* instance 'DTEND) '-X-HNH-ORIGINAL)
-
- instance)))
-
- `(
- ;; Mapping showing which events belongs to which calendar,
- ;; on the form
- ;; (calendar (@ (key ,(base64-encode calendar-name)))
- ;; (li ,event-uid) ...)
- (div (@ (style "display:none !important;")
- (id "calendar-event-mapping"))
- ,(let ((ht (make-hash-table)))
- (for-each (lambda (event)
- (define name (prop (parent event) 'NAME))
- (hash-set! ht name
- (cons (prop event 'UID)
- (hash-ref ht name '()))))
- (append regular repeating))
-
- (hash-map->list
- (lambda (key values)
- `(calendar (@ (key ,(base64encode key)))
- ,@(map (lambda (uid) `(li ,uid))
- values)))
- ht)))
-
- ;; Calendar data for all events in current interval,
- ;; rendered as xcal.
- (div (@ (style "display:none !important;")
- (id "xcal-data"))
- ,((@ (vcomponent formats xcal output) ns-wrap)
- (map (@ (vcomponent formats xcal output) vcomponent->sxcal)
- (append regular repeating)))))))))
+ ,(let ((regular (fixed-events-in-range global-event-object start-date end-date))
+ (repeating (repeating-events-in-range global-event-object start-date end-date)))
+
+ `(div (@ (style "display:none !important"))
+ ,(calendar-event-mapping (append regular repeating))
+
+ ;; Calendar data for all events in current interval,
+ ;; rendered as xcal.
+ (div (@ (id "xcal-data"))
+ ,((@ (vcomponent formats xcal output) ns-wrap)
+ (map vcomponent->sxcal regular)))
+
+ (div (@ (id "xcal-data-repeating"))
+ ,((@ (vcomponent formats xcal output) ns-wrap)
+ (map vcomponent->sxcal
+ ;; TODO possibly create generate-reccurrence-set-in-interval
+ (map (lambda (event)
+ (delete-parameter! (prop* event 'DTSTART) '-X-HNH-ORIGINAL)
+ (delete-parameter! (prop* event 'DTEND) '-X-HNH-ORIGINAL)
+ event)
+ (stream->list
+ (events-between
+ start-date end-date
+ (interleave-streams
+ ev-time<?
+ (map generate-recurrence-set repeating)))))))
+
+ )
+ )
+
+ #;
+ (map (lambda (ev)
+ (set! (prop ev 'X-HNH-INSTANCE-ID)
+ (datetime->string (as-datetime (prop ev 'DTSTART))))
+ (vcomponent->sxcal ev))
+ repeating)))))
diff --git a/module/calp/html/view/calendar/month.scm b/module/calp/html/view/calendar/month.scm
index 1c162aaa..5678db0f 100644
--- a/module/calp/html/view/calendar/month.scm
+++ b/module/calp/html/view/calendar/month.scm
@@ -12,9 +12,10 @@
:select (really-long-event?
events-between))
:use-module ((calp html vcomponent)
- :select (make-block output-uid))
+ :select (make-block))
:use-module ((vcomponent util group)
:select (group-stream get-groups-between))
+ :use-module ((vcomponent recurrence) :select (repeating?))
)
;; (stream event-group) -> sxml
@@ -84,7 +85,9 @@
(events-between pre-start post-end events))
`(popup-element
(@ (class "vevent")
- (data-uid ,(output-uid event)))))
+ (data-uid ,(prop event 'UID))
+ ,@(when (repeating? event)
+ `((data-instance ,(datetime->string (as-datetime (prop event 'DTSTART)))))))))
(template
(@ (id "vevent-block"))
diff --git a/module/calp/html/view/calendar/week.scm b/module/calp/html/view/calendar/week.scm
index 78abcfbf..1303f134 100644
--- a/module/calp/html/view/calendar/week.scm
+++ b/module/calp/html/view/calendar/week.scm
@@ -15,13 +15,14 @@
event-zero-length?
events-between))
:use-module ((calp html vcomponent)
- :select (make-block output-uid) )
+ :select (make-block) )
;; :use-module ((calp html components)
;; :select ())
:use-module (calp translation)
:use-module ((vcomponent util group)
:select (group-stream get-groups-between))
:use-module (ice-9 format)
+ :use-module ((vcomponent recurrence) :select (repeating?))
)
@@ -62,7 +63,9 @@
(events-between start-date end-date events))
`(popup-element
(@ (class "vevent")
- (data-uid ,(output-uid event)))))))
+ ,@(when (repeating? event)
+ `((data-instance ,(datetime->string (as-datetime (prop event 'DTSTART))))))
+ (data-uid ,(prop event 'UID)))))))
;; This template is here, instead of in (calp html calendar) since it only
diff --git a/module/calp/server/routes.scm b/module/calp/server/routes.scm
index 3d90cc04..37111c71 100644
--- a/module/calp/server/routes.scm
+++ b/module/calp/server/routes.scm
@@ -32,6 +32,12 @@
:autoload (vcomponent util instance) (global-event-object)
+ :use-module (vcomponent recurrence)
+ :use-module ((vcomponent recurrence internal) :select (until))
+ :use-module (srfi srfi-41)
+ :use-module (srfi srfi-41 util)
+ :use-module (hnh util uuid)
+
:use-module (calp util config)
:use-module (calp html view calendar)
:use-module ((calp html view search) :select (search-result-page))
@@ -207,7 +213,7 @@
;; TODO this fails when dtstart is <date>.
;; @var{cal} should be the name of the calendar encoded in base64.
- (POST "/insert" (cal data)
+ (POST "/insert" (cal data submit_type)
(unless (and cal data)
(return (build-response code: 400)
@@ -269,8 +275,65 @@
(parameterize ((warnings-are-errors #t))
(catch*
- (lambda () (add-and-save-event global-event-object
+ (lambda ()
+ (if (repeating? event)
+ (cond
+ ((not (string? submit_type))
+ (scm-error 'wrong-type-arg #f
+ "Expected string, got ~s" (list submit_type) #f)
+ )
+ ((string=? submit_type "this")
+ ;; Change this instance only
+ ;; set EXDATE
+ (string->datetime (prop event 'X-HNH-INSTANCE-ID))
+ )
+ ;; change this and future instances
+ ((string=? submit_type "this_future")
+ ;; mark original event's RRULE with until this event
+ ;; create new event, which should be really similar
+ (let* ((og-event (get-event-by-uid global-event-object (prop event 'UID)))
+ (occurences (generate-recurrence-set og-event))
+ (pairs (stream-zip occurences (stream-cdr occurences)))
+ (dt (string->datetime (prop event 'X-HNH-INSTANCE-ID))))
+ ;; find recurrence instance before this one
+ (let ((last-of-old-instances
+ (car
+ (stream-find (lambda (p) (datetime= dt (cadr (prop p 'DTSTART))))
+ pairs))))
+ (let ((event-a event)
+ (event-b (copy-vcomponent event)))
+ (set!
+ (prop event-a 'RRULE)
+ (set-> (prop og-event 'RRULE)
+ (until (prop last-of-old-instances 'DTSTART)))
+ (prop event-a 'DTSTART) (prop og-event 'DTSTART)
+ (prop event-a 'DTEND) (prop og-event 'DTEND)
+ (prop event-b 'UID) (uuid)
+ )
+ )
+ ))
+ ;; use its date as UNTIL in RRULE
+ )
+ ((string=? submit_type "all")
+ ;; change all instances
+ (let ((og-event (get-event-by-uid global-event-object (prop event 'UID))))
+ (set!
+ (prop event 'DTSTART)
+ (datetime date: (as-date (prop og-event 'DTSTART))
+ time: (as-time (prop event 'DTSTART)))
+ (prop event 'DTEND)
+ (datetime date: (as-date (prop og-event 'DTEND))
+ time: (as-time (prop event 'DTEND))))
+ (add-and-save-event global-event-object
+ calendar event)))
+ (else
+ (scm-error 'misc-error #f
+ "Invalid submit_type: ~s"
+ (list submit_type) #f)))
+ ;; else (if not repeating)
+ (add-and-save-event global-event-object
calendar event))
+ )
(warning
(lambda (err fmt args)
(define str (format #f "~?" fmt args))
diff --git a/module/vcomponent/util/instance/methods.scm b/module/vcomponent/util/instance/methods.scm
index 7a1d2fc8..6bafb274 100644
--- a/module/vcomponent/util/instance/methods.scm
+++ b/module/vcomponent/util/instance/methods.scm
@@ -10,10 +10,12 @@
;; :use-module (vcomponent parse)
:use-module ((vcomponent util parse-cal-path) :select (parse-cal-path))
:use-module ((vcomponent recurrence) :select (generate-recurrence-set repeating?))
- :use-module ((vcomponent datetime) :select (ev-time<?))
+ :use-module ((vcomponent datetime) :select (ev-time<? event-overlaps?))
:use-module (oop goops)
:use-module (calp translation)
+ :use-module ((vcomponent recurrence internal) :select (until count)
+ :renamer (symbol-prefix-proc (symbol #\r #\r #\:)))
:export (add-event
remove-event
@@ -22,6 +24,7 @@
get-event-by-uid
fixed-events-in-range
+ repeating-events-in-range
get-calendar-by-name
@@ -60,6 +63,7 @@
(define (make-instance calendar-files)
(make <events> calendar-files: calendar-files))
+
(define-method (get-event-by-uid (this <events>) uid)
(hash-ref (slot-ref this 'uid-map) uid))
@@ -70,10 +74,24 @@
(define-method (fixed-events-in-range (this <events>) start end)
+ ;; TODO why not use events-between here?
(filter-sorted (lambda (ev) ((in-date-range? start end)
(as-date (prop ev 'DTSTART))))
(slot-ref this 'fixed-events)))
+(define-method (repeating-events-in-range (this <events>) start end)
+ (filter (lambda (ev)
+ (cond ((date/-time< end (prop ev 'DTSTART)) #f)
+ ((and=> (prop ev 'RRULE) rr:until) => (lambda (u) (date/-time< u start)))
+ ((prop ev 'RRULE)
+ => (lambda (rr)
+ (or (not (or (rr:until rr) (rr:count rr)))
+ ;; else if recurrence set overlaps interval
+ ;; TODO check this with tests/annoying-events.scm
+ (stream-find (lambda (instance) (event-overlaps? instance start end))
+ (generate-recurrence-set ev)))))))
+ (slot-ref this 'repeating-events)))
+
(define-method (initialize (this <events>) args)
(next-method)
@@ -85,6 +103,7 @@
(let ((calendars (load-calendars (slot-ref this 'calendar-files))))
(apply add-calendars this calendars)))
+
(define-method (add-calendars (this <events>) . calendars)
(slot-set! this 'calendars (append calendars (slot-ref this 'calendars)))
@@ -166,61 +185,66 @@
(prop event 'UID)))
(slot-ref this 'event-set)))
- (hash-set! (slot-ref this 'uid-map) (prop event 'UID)
- #f))
+ (remove-child! (parent event) event)
+ (hash-remove! (slot-ref this 'uid-map) (prop event 'UID)))
-(define-method (add-and-save-event (this <events>) calendar event)
- (cond
- [(get-event-by-uid this (prop event 'UID))
- => (lambda (old-event)
+(define-method (update-and-save-event (this <events>) calendar old-event)
+ ;; remove old instance of event from runtime
+ (remove-event this old-event)
- ;; remove old instance of event from runtime
- (remove-event this old-event)
- (remove-child! (parent old-event) old-event)
+ ;; Add new event to runtime,
+ ;; MUST be done after since the two events SHOULD share UID.
+ ;; NOTE that this can emit warnings
+ (add-event this calendar old-event)
+ (remove-child! (parent old-event) old-event)
- ;; Add new event to runtime,
- ;; MUST be done after since the two events SHOULD share UID.
- ;; NOTE that this can emit warnings
- (add-event this calendar event)
+ (set! (prop old-event 'LAST-MODIFIED)
+ (current-datetime))
- (set! (prop event 'LAST-MODIFIED)
- (current-datetime))
+ ;; NOTE Posibly defer save to a later point.
+ ;; That would allow better asyncronous preformance.
- ;; NOTE Posibly defer save to a later point.
- ;; That would allow better asyncronous preformance.
+ ;; save-event sets -X-HNH-FILENAME from the UID. This is fine
+ ;; since the two events are guaranteed to have the same UID.
+ (unless ((@ (vcomponent formats vdir save-delete) save-event) old-event)
+ (throw 'misc-error (_ "Saving event to disk failed.")))
- ;; save-event sets -X-HNH-FILENAME from the UID. This is fine
- ;; since the two events are guaranteed to have the same UID.
- (unless ((@ (vcomponent formats vdir save-delete) save-event) event)
- (throw 'misc-error (_ "Saving event to disk failed.")))
+ (unless (eq? calendar (parent old-event))
+ ;; change to a new calendar
+ (format (current-error-port)
+ (_ "Unlinking old event from ~a~%")
+ (prop old-event '-X-HNH-FILENAME))
+ ;; NOTE that this may fail, leading to a duplicate event being
+ ;; created (since we save beforehand). This is just a minor problem
+ ;; which either a better atomic model, or a propper error
+ ;; recovery log would solve.
+ ((@ (vcomponent formats vdir save-delete) remove-event) old-event))
- (unless (eq? calendar (parent old-event))
- ;; change to a new calendar
- (format (current-error-port)
- (_ "Unlinking old event from ~a~%")
- (prop old-event '-X-HNH-FILENAME))
- ;; NOTE that this may fail, leading to a duplicate event being
- ;; created (since we save beforehand). This is just a minor problem
- ;; which either a better atomic model, or a propper error
- ;; recovery log would solve.
- ((@ (vcomponent formats vdir save-delete) remove-event) old-event))
+ (format (current-error-port)
+ (_ "Event updated ~a~%") (prop old-event 'UID)))
- (format (current-error-port)
- (_ "Event updated ~a~%") (prop event 'UID)))]
- [else
- (add-event this calendar event)
+;; Add a new event, which isn't already in the event object
+(define-method (add-new-event (this <events>) calendar event)
+ (add-event this calendar event)
- (set! (prop event 'LAST-MODIFIED) (current-datetime))
+ (set! (prop event 'LAST-MODIFIED) (current-datetime))
- ;; NOTE Posibly defer save to a later point.
- ;; That would allow better asyncronous preformance.
- (unless ((@ (vcomponent formats vdir save-delete) save-event) event)
- (throw 'misc-error (_ "Saving event to disk failed.")))
+ ;; NOTE Posibly defer save to a later point.
+ ;; That would allow better asyncronous preformance.
+ (unless ((@ (vcomponent formats vdir save-delete) save-event) event)
+ (throw 'misc-error (_ "Saving event to disk failed.")))
- (format (current-error-port)
- (_ "Event inserted ~a~%") (prop event 'UID))]))
+ (format (current-error-port)
+ (_ "Event inserted ~a~%") (prop event 'UID))
+ )
+
+(define-method (add-and-save-event (this <events>) calendar event)
+ (cond
+ [(get-event-by-uid this (prop event 'UID))
+ => (lambda (old-event) (update-and-save-event this calendar old-event))]
+ [else (add-new-event this calendar event)]))
diff --git a/static/components/edit-rrule.ts b/static/components/edit-rrule.ts
index a361bdee..8575d9bc 100644
--- a/static/components/edit-rrule.ts
+++ b/static/components/edit-rrule.ts
@@ -11,8 +11,8 @@ import { RecurrenceRule } from '../vevent'
*/
class EditRRule extends ComponentVEvent {
- constructor(uid?: string) {
- super(uid);
+ constructor(uid?: string, instance?: string) {
+ super(uid, instance);
if (!this.template) {
throw 'vevent-edit-rrule template required';
@@ -25,7 +25,7 @@ class EditRRule extends ComponentVEvent {
for (let el of this.querySelectorAll('[name]')) {
el.addEventListener('input', () => {
// console.log(this);
- let data = vcal_objects.get(this.uid)!;
+ let data = this.getData()!;
let rrule = data.getProperty('rrule')
if (!rrule) {
console.warn('RRUle missing from object');
@@ -41,9 +41,9 @@ class EditRRule extends ComponentVEvent {
}
}
- connectedCallback() {
- this.redraw(vcal_objects.get(this.uid)!)
- }
+ // connectedCallback() {
+ // this.redraw(this.getData()!)
+ // }
redraw(data: VEvent) {
diff --git a/static/components/popup-element.ts b/static/components/popup-element.ts
index 3300f885..5dc627d0 100644
--- a/static/components/popup-element.ts
+++ b/static/components/popup-element.ts
@@ -15,12 +15,12 @@ class PopupElement extends ComponentVEvent {
* the top, and allowing global keyboard bindings to affect it. */
static activePopup: PopupElement | null = null;
- constructor(uid?: string) {
- super(uid);
+ constructor(uid?: string, instance?: string) {
+ super(uid, instance);
/* TODO populate remaining (??) */
- let obj = vcal_objects.get(this.uid);
+ let obj = this.getData();
if (obj && obj.calendar) {
this.dataset.calendar = obj.calendar;
}
@@ -48,8 +48,6 @@ class PopupElement extends ComponentVEvent {
let template = document.getElementById('popup-template') as HTMLTemplateElement
let body = (template.content.cloneNode(true) as DocumentFragment).firstElementChild!;
- let uid = this.uid;
-
/* nav bar */
let nav = body.getElementsByClassName("popup-control")[0] as HTMLElement;
bind_popup_control(nav);
@@ -61,7 +59,8 @@ class PopupElement extends ComponentVEvent {
maximize_btn.addEventListener('click', () => this.maximize());
let remove_btn = body.querySelector('.popup-control .remove-button') as HTMLButtonElement
- remove_btn.addEventListener('click', () => remove_event(uid));
+ /* TODO repeating removal */
+ remove_btn.addEventListener('click', () => remove_event(this.uid));
/* end nav bar */
this.replaceChildren(body);
@@ -147,7 +146,7 @@ class PopupElement extends ComponentVEvent {
*/
function setup_popup_element(ev: VEvent): PopupElement {
let uid = ev.getProperty('uid');
- let popup = new PopupElement(uid);
+ let popup = new PopupElement(uid, ev.getProperty('dtstart')?.format('~Y-~m-~dT~H:~M:~S'));
ev.register(popup);
/* TODO propper way to find popup container */
(document.querySelector('.days') as Element).appendChild(popup);
diff --git a/static/components/tab-group-element.ts b/static/components/tab-group-element.ts
index e90997e9..11949870 100644
--- a/static/components/tab-group-element.ts
+++ b/static/components/tab-group-element.ts
@@ -26,8 +26,8 @@ class TabGroupElement extends ComponentVEvent {
tabs: HTMLElement[] = [];
tabLabels: HTMLElement[] = [];
- constructor(uid?: string) {
- super(uid);
+ constructor(uid?: string, instance?: string) {
+ super(uid!, instance!);
this.menu = makeElement('div', {}, {
role: 'tablist',
@@ -43,7 +43,8 @@ class TabGroupElement extends ComponentVEvent {
if (!this.has_rrule_tab()) {
/* Note that EditRRule register itself to be updated on changes
to the event */
- this.addTab(new EditRRule(data.getProperty('uid')),
+ this.addTab(new EditRRule(data.getProperty('uid'),
+ data.getProperty('dtstart')?.format('~Y-~m-~dT~H:~M:~S')),
"↺", "Upprepningar");
}
} else {
@@ -71,7 +72,7 @@ class TabGroupElement extends ComponentVEvent {
}
/* redraw might add or remove tabs depending on our data, so call it here */
- this.redraw(vcal_objects.get(this.uid)!);
+ this.redraw(this.getData()!);
/* All tabs should now be ready, focus the first one */
if (this.tabLabels.length > 0) {
@@ -144,7 +145,7 @@ class TabGroupElement extends ComponentVEvent {
if (tab.firstChild) {
let child = tab.firstChild as HTMLElement;
if (isRedrawable(child)) {
- vcal_objects.get(this.uid)?.unregister(child)
+ this.getData()?.unregister(child)
}
}
diff --git a/static/components/vevent-block.ts b/static/components/vevent-block.ts
index 8cf61d30..f97a0e54 100644
--- a/static/components/vevent-block.ts
+++ b/static/components/vevent-block.ts
@@ -10,8 +10,8 @@ import { parseDate, to_local } from '../lib'
A grahpical block in the week view.
*/
class ComponentBlock extends ComponentVEvent {
- constructor(uid?: string) {
- super(uid);
+ constructor(uid?: string, instance?: string) {
+ super(uid!, instance!);
if (!this.template) {
throw 'vevent-block template required';
diff --git a/static/components/vevent-description.ts b/static/components/vevent-description.ts
index f0d224be..efa619c4 100644
--- a/static/components/vevent-description.ts
+++ b/static/components/vevent-description.ts
@@ -9,8 +9,8 @@ import { formatters } from '../formatters'
*/
class ComponentDescription extends ComponentVEvent {
- constructor(uid?: string) {
- super(uid);
+ constructor(uid?: string, instance?: string) {
+ super(uid!, instance!);
if (!this.template) {
throw 'vevent-description template required';
}
diff --git a/static/components/vevent-edit.ts b/static/components/vevent-edit.ts
index bf72678c..108146a3 100644
--- a/static/components/vevent-edit.ts
+++ b/static/components/vevent-edit.ts
@@ -14,8 +14,8 @@ import { to_boolean, gensym } from '../lib'
*/
class ComponentEdit extends ComponentVEvent {
- constructor(uid?: string) {
- super(uid);
+ constructor(uid?: string, instance?: string) {
+ super(uid, instance);
if (!this.template) {
throw 'vevent-edit template required';
@@ -39,10 +39,10 @@ class ComponentEdit extends ComponentVEvent {
/* Edit tab is rendered here. It's left blank server-side, since
it only makes sense to have something here if we have javascript */
- let data = vcal_objects.get(this.uid)
+ let data = this.getData()
if (!data) {
- throw `Data missing for uid ${this.dataset.uid}.`
+ throw `Data missing for uid ${this.getKey()}.`
}
@@ -59,7 +59,7 @@ class ComponentEdit extends ComponentVEvent {
el.addEventListener('change', (e) => {
let v = (e.target as HTMLSelectElement).selectedOptions[0].value
- let obj = vcal_objects.get(this.uid)!
+ let obj = this.getData()!
obj.calendar = v;
});
}
@@ -70,7 +70,7 @@ class ComponentEdit extends ComponentVEvent {
for (let el of this.querySelectorAll("[data-property]")) {
// console.log(el);
el.addEventListener('input', (e) => {
- let obj = vcal_objects.get(this.uid)
+ let obj = this.getData()
// console.log(el, e);
if (obj === undefined) {
throw 'No object with uid ' + this.uid
@@ -117,18 +117,25 @@ class ComponentEdit extends ComponentVEvent {
/* TODO unselecting and reselecting this checkbox deletes all entered data.
Cache it somewhere */
if (has_repeats.checked) {
- vcal_objects.get(this.uid)!.setProperty('rrule', new RecurrenceRule())
+ this.getData()!.setProperty('rrule', new RecurrenceRule())
} else {
/* TODO is this a good way to remove a property ? */
- vcal_objects.get(this.uid)!.setProperty('rrule', undefined)
+ this.getData()!.setProperty('rrule', undefined)
}
})
}
let submit = this.querySelector('form') as HTMLFormElement
+ /* TODO If start or end DATE is changed, only allow THIS */
+ /* if only time component was changed, allow all */
submit.addEventListener('submit', (e) => {
console.log(submit, e);
- create_event(vcal_objects.get(this.uid)!);
+ // submit button pressed (e.submitter);
+ let submit_type = 'all';
+ if (e.submitter) {
+ submit_type = e.submitter.dataset.key!;
+ }
+ create_event(submit_type, this.getData()!);
e.preventDefault();
return false;
diff --git a/static/components/vevent.ts b/static/components/vevent.ts
index 5852a2ff..abf0f4c0 100644
--- a/static/components/vevent.ts
+++ b/static/components/vevent.ts
@@ -12,12 +12,13 @@ abstract class ComponentVEvent extends HTMLElement {
template: HTMLTemplateElement | null
uid: string
+ instance: string | null
- constructor(uid?: string) {
+ constructor(uid?: string, instance?: string) {
super();
this.template = document.getElementById(this.tagName.toLowerCase()) as HTMLTemplateElement | null
- let real_uid;
+ let real_uid, real_instance;
if (uid) {
// console.log('Got UID directly');
@@ -29,6 +30,7 @@ abstract class ComponentVEvent extends HTMLElement {
// console.log('Had UID as direct attribute');
real_uid = this.dataset.uid;
} else {
+ /* TODO when is this case relevant? */
let el = this.closest('[data-uid]')
if (el) {
// console.log('Found UID higher up in the tree');
@@ -48,7 +50,32 @@ abstract class ComponentVEvent extends HTMLElement {
this.uid = real_uid;
this.dataset.uid = real_uid;
- vcal_objects.get(this.uid)?.register(this);
+ if (instance) {
+ real_instance = instance
+ } else {
+ if (this.dataset.instance) {
+ real_instance = this.dataset.instance
+ } else {
+ let el = this.closest('[data-instance]')
+ if (el) {
+ real_instance = (el as HTMLElement).dataset.instance
+ if (real_instance === undefined) {
+ real_instance = null;
+ }
+ } else {
+ real_instance = null
+ }
+ }
+ }
+ this.instance = real_instance;
+ if (real_instance) {
+ this.dataset.instance = real_instance;
+ }
+
+ /* TODO */
+ /* Here, choose different rules depending on if we are repeating or not */
+
+ vcal_objects.get(this.getKey())?.register(this);
/* We DON'T have a redraw here in the general case, since the
HTML rendered server-side should be fine enough for us.
@@ -56,12 +83,27 @@ abstract class ComponentVEvent extends HTMLElement {
should take care of that some other way */
}
- connectedCallback() {
+ getKey(): string {
let uid = this.dataset.uid
- if (uid) {
- let v = vcal_objects.get(uid)
- if (v) this.redraw(v);
- }
+ // let instance_id = ev.getProperty('dtstart')!.format('~Y-~m-~dT~H:~M:~S')
+ let instance = this.dataset.instance
+ if (!uid) throw new Error("UID missing");
+ let key = uid;
+ /* NOTE proper composite keys would be nice, since this can collide with
+ a regular key. However, javascript's Map doesn't support lists (or
+ object) as keys by default. */
+ if (instance) key += '---' + instance;
+ return key;
+ }
+
+ /* This return different object for different instances */
+ getData(): VEvent | undefined {
+ return vcal_objects.get(this.getKey())
+ }
+
+ connectedCallback() {
+ let v = this.getData();
+ if (v) this.redraw(v);
}
abstract redraw(data: VEvent): void
diff --git a/static/event-creator.ts b/static/event-creator.ts
index 0f2c42b4..6ce04d78 100644
--- a/static/event-creator.ts
+++ b/static/event-creator.ts
@@ -69,7 +69,7 @@ class EventCreator {
that.ev.calendar = window.default_calendar;
// let ev_block = document.createElement('vevent-block') as ComponentBlock;
- let ev_block = new ComponentBlock(that.ev.getProperty('uid'));
+ let ev_block = new ComponentBlock(that.ev.getProperty('uid'), that.ev.getProperty('dtstart')?.format('~Y-~m-~dT~H:~M:~S'));
ev_block.classList.add('generated');
that.event = ev_block;
that.ev.register(ev_block);
diff --git a/static/script.ts b/static/script.ts
index ec771773..79365e7d 100644
--- a/static/script.ts
+++ b/static/script.ts
@@ -20,12 +20,29 @@ window.addEventListener('load', function() {
*/
// let json_objects_el = document.getElementById('json-objects');
- let div = document.getElementById('xcal-data')!;
- let vevents = div.firstElementChild!.children;
- for (let vevent of vevents) {
- let ev = xml_to_vcal(vevent);
- vcal_objects.set(ev.getProperty('uid'), ev)
+ {/* Load "regular" (non-repeating) events */
+ let div = document.getElementById('xcal-data')!;
+ let vevents = div.firstElementChild!.children;
+
+ for (let vevent of vevents) {
+ let ev = xml_to_vcal(vevent);
+ vcal_objects.set(ev.getProperty('uid'), ev)
+ }
+ }
+
+ {/* Load repeating events */
+ let div = document.getElementById('xcal-data-repeating')!;
+ let vevents = div.firstElementChild!.children;
+ for (let vevent of vevents) {
+ let ev = xml_to_vcal(vevent);
+ /* NOTE manuall calculation of key, since getKey is method of vevent
+ component, not the abstract vevent */
+ let instance_id = ev.getProperty('dtstart')!
+ .format('~Y-~m-~dT~H:~M:~S')
+ let key = ev.getProperty('uid') + '---' + instance_id
+ vcal_objects.set(key, ev);
+ }
}
@@ -38,6 +55,7 @@ window.addEventListener('load', function() {
throw "UID required"
}
event_calendar_mapping.set(uid, calendar_name);
+ /* TODO this fails for repeating events */
let obj = vcal_objects.get(uid);
if (obj) obj.calendar = calendar_name
}
diff --git a/static/server_connect.ts b/static/server_connect.ts
index d1a544eb..d2617bf9 100644
--- a/static/server_connect.ts
+++ b/static/server_connect.ts
@@ -55,7 +55,7 @@ async function remove_event(uid: uid) {
// ];
// }
-async function create_event(event: VEvent) {
+async function create_event(submit_type: string, event: VEvent) {
// let xml = event.getElementsByTagName("icalendar")[0].outerHTML
let calendar = event.calendar;
@@ -67,6 +67,9 @@ async function create_event(event: VEvent) {
console.log('calendar=', calendar/*, xml*/);
let data = new URLSearchParams();
+
+ data.append('submit_type', submit_type);
+
data.append("cal", calendar);
// data.append("data", xml);
diff --git a/static/vevent.ts b/static/vevent.ts
index 6a2c6f0f..a7f67a7f 100644
--- a/static/vevent.ts
+++ b/static/vevent.ts
@@ -555,3 +555,32 @@ function xml_to_vcal(xml: Element): VEvent {
return new VEvent(property_map, component_list)
}
+
+
+class MultiInstanceVEvent extends VEvent {
+
+ private base_event: VEvent | null = null;
+ private overlayProperties: Map<string, VEventValue | VEventValue[]>;
+
+ constructor(parent: VEvent) {
+ super();
+
+ this.base_event = parent;
+
+ this.overlayProperties = new Map
+
+ /* Add this to alternative set of parent */
+ }
+
+ setProperty(key: string, value: any, type?: ical_type) {
+ /* TODO type resolution a setPropertyInternal */
+ this.overlayProperties.set(key.toUpperCase(), value);
+ }
+
+ getProperty(key: string): any | any[] | undefined {
+ let a = this.overlayProperties.get(key.toUpperCase())
+ if (a !== undefined) return a
+ return super.getProperty(key)
+ }
+
+}