aboutsummaryrefslogtreecommitdiff
path: root/static/components
diff options
context:
space:
mode:
Diffstat (limited to 'static/components')
-rw-r--r--static/components/changelog.ts49
-rw-r--r--static/components/date-jump.ts40
-rw-r--r--static/components/date-time-input.ts119
-rw-r--r--static/components/edit-rrule.ts75
-rw-r--r--static/components/input-list.ts120
-rw-r--r--static/components/popup-element.ts201
-rw-r--r--static/components/slider.ts101
-rw-r--r--static/components/tab-group-element.ts184
-rw-r--r--static/components/vevent-block.ts99
-rw-r--r--static/components/vevent-description.ts38
-rw-r--r--static/components/vevent-dl.ts35
-rw-r--r--static/components/vevent-edit.ts179
-rw-r--r--static/components/vevent.ts69
13 files changed, 0 insertions, 1309 deletions
diff --git a/static/components/changelog.ts b/static/components/changelog.ts
deleted file mode 100644
index d08f7cb3..00000000
--- a/static/components/changelog.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { makeElement } from '../lib'
-import { ComponentVEvent } from './vevent'
-import { VEvent } from '../vevent'
-
-export { VEventChangelog }
-
-class VEventChangelog extends ComponentVEvent {
-
- readonly ul: HTMLElement
-
- constructor(uid?: string) {
- super(uid);
-
- this.ul = makeElement('ul');
- }
-
- connectedCallback() {
- this.replaceChildren(this.ul);
- }
-
- redraw(data: VEvent) {
- /* TODO only redraw what is needed */
- let children = []
- for (let [_, el] of data.changelog) {
- let msg = '';
- switch (el.type) {
- case 'property':
- msg += `change ${el.name}: `
- msg += `from "${el.from}" to "${el.to}"`
- break;
- case 'calendar':
- if (el.from === null && el.to === null) {
- msg += '???'
- } else if (el.from === null) {
- msg += `set calendar to "${atob(el.to!)}"`
- } else if (el.to === null) {
- msg += `Remove calendar "${atob(el.from)}"`
- } else {
- msg += `Change calendar from "${atob(el.from)}" to "${atob(el.to)}"`
- }
- break;
- }
-
- children.push(makeElement('li', { textContent: msg }));
- }
-
- this.ul.replaceChildren(...children)
- }
-}
diff --git a/static/components/date-jump.ts b/static/components/date-jump.ts
deleted file mode 100644
index fd3908ae..00000000
--- a/static/components/date-jump.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-export { DateJump }
-
-/* Replace backend-driven [today] link with frontend, with one that
- gets correctly set in the frontend. Similarly, update the go to
- specific date button into a link which updates wheneven the date
- form updates.
-*/
-class DateJump extends HTMLElement {
-
- readonly golink: HTMLAnchorElement;
- readonly input: HTMLInputElement;
-
- constructor() {
- super();
-
- this.golink = document.createElement('a')
- this.golink.classList.add('btn');
- this.golink.textContent = "➔"
- this.input = document.createElement('input')
- this.input.type = 'date';
- }
-
- connectedCallback() {
-
- /* Form is just here so the css works out */
- let form = document.createElement('form');
- form.replaceChildren(this.input, this.golink);
- this.replaceChildren(form);
-
- this.input.onchange = () => {
- let date = this.input.valueAsDate!.format('~Y-~m-~d');
- this.golink.href = `${date}.html`
- }
-
- let now = (new Date).format("~Y-~m-~d")
- this.input.value = now;
- /* onchange isn't triggered by manually setting the value */
- this.golink.href = `${now}.html`
- }
-}
diff --git a/static/components/date-time-input.ts b/static/components/date-time-input.ts
deleted file mode 100644
index 20e9a505..00000000
--- a/static/components/date-time-input.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-export { DateTimeInput }
-
-import { makeElement, parseDate } from '../lib'
-
-
-/* '<date-time-input />' */
-class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
-
- readonly time: HTMLInputElement;
- readonly date: HTMLInputElement;
-
- constructor() {
- super();
-
- this.date = makeElement('input', {
- type: 'date'
- }) as HTMLInputElement
-
- this.time = makeElement('input', {
- type: 'time',
- disabled: this.dateonly
- }) as HTMLInputElement
- }
-
- connectedCallback() {
- /* This can be in the constructor for chromium, but NOT firefox...
- Vivaldi 4.3.2439.63 stable
- Mozilla Firefox 94.0.1
- */
- /*
- https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#boolean_attributes
- https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute
- */
- this.replaceChildren(this.date, this.time)
- }
-
- static get observedAttributes() {
- return ['dateonly']
- }
-
- attributeChangedCallback(name: string, _: string | null, to: string | null): void {
- switch (name) {
- case 'dateonly':
- if (to == null) {
- this.time.disabled = false
- } else {
- if (to == '' || to == name) {
- this.time.disabled = true;
- } else {
- throw new TypeError(`Invalid value for attribute dateonly: ${to}`)
- }
- }
- break;
- }
- }
-
- get dateonly(): boolean {
- return this.hasAttribute('dateonly');
- }
-
- set dateonly(b: boolean) {
- if (b) {
- this.setAttribute('dateonly', "");
- } else {
- this.removeAttribute('dateonly');
- }
- }
-
- set value(date: Date) {
- let [d, t] = date.format("~L~Y-~m-~dT~H:~M").split('T');
- this.date.value = d;
- this.time.value = t;
-
- this.dateonly = date.dateonly;
- }
-
- get value(): Date {
- let dt;
- let date = this.date.value;
- if (this.dateonly) {
- dt = parseDate(date);
- dt.dateonly = true;
- } else {
- let time = this.time.value;
- dt = parseDate(date + 'T' + time)
- dt.dateonly = false;
- }
- return dt;
- }
-
- get stringValue(): string {
- if (this.dateonly) {
- return this.value.format("~Y-~m-~d")
- } else {
- return this.value.format("~Y-~m-~dT~H:~M:~S")
- }
- }
-
- set stringValue(new_value: Date | string) {
- let date, time, dateonly = false;
- if (new_value instanceof Date) {
- date = new_value.format("~L~Y-~m-~d");
- time = new_value.format("~L~H:~M:~S");
- dateonly = new_value.dateonly;
- } else {
- [date, time] = new_value.split('T')
- }
- this.dateonly = dateonly;
- this.date.value = date;
- this.time.value = time;
- }
-
- addEventListener(type: string, proc: ((e: Event) => void)) {
- if (type != 'input') throw "Only input supported";
-
- this.date.addEventListener(type, proc);
- this.time.addEventListener(type, proc);
- }
-}
diff --git a/static/components/edit-rrule.ts b/static/components/edit-rrule.ts
deleted file mode 100644
index a361bdee..00000000
--- a/static/components/edit-rrule.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-export { EditRRule }
-
-import { ComponentVEvent } from './vevent'
-import { VEvent } from '../vevent'
-import { vcal_objects } from '../globals'
-
-import { RecurrenceRule } from '../vevent'
-
-/* <vevent-edit-rrule/>
- Tab for editing the recurrence rule of a component
-*/
-class EditRRule extends ComponentVEvent {
-
- constructor(uid?: string) {
- super(uid);
-
- if (!this.template) {
- throw 'vevent-edit-rrule template required';
- }
-
- let frag = this.template.content.cloneNode(true) as DocumentFragment
- let body = frag.firstElementChild!
- this.replaceChildren(body);
-
- for (let el of this.querySelectorAll('[name]')) {
- el.addEventListener('input', () => {
- // console.log(this);
- let data = vcal_objects.get(this.uid)!;
- let rrule = data.getProperty('rrule')
- if (!rrule) {
- console.warn('RRUle missing from object');
- return;
- }
- rrule = rrule as RecurrenceRule
-
- console.log(el.getAttribute('name'), (el as any).value);
- rrule[el.getAttribute('name')!] = (el as any).value;
- data.setProperty('rrule', rrule);
-
- });
- }
- }
-
- connectedCallback() {
- this.redraw(vcal_objects.get(this.uid)!)
- }
-
- redraw(data: VEvent) {
-
- let rrule = data.getProperty('rrule')
- if (!rrule) return;
- rrule = rrule as RecurrenceRule
-
- for (let el of this.querySelectorAll('[name]')) {
-
- /*
- el ought to be one of the tag types:
- <input/>, <input-list/>, <select/>, and <date-time-input/>
- Which all have `name` and `value` fields, allowing the code
- below to work.
- */
-
- let name = el.getAttribute('name')
- if (!name) {
- console.warn(`Input without name, ${el}`)
- continue
- }
-
- let value: any = rrule[name];
- if (value)
- (el as any).value = value;
- }
- }
-
-}
diff --git a/static/components/input-list.ts b/static/components/input-list.ts
deleted file mode 100644
index 0afd4999..00000000
--- a/static/components/input-list.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-export { InputList }
-
-/*
- TODO allow each item to be a larger unit, possibly containing multiple input
- fields.
-*/
-class InputList extends HTMLElement {
-
- el: HTMLInputElement;
-
- #listeners: [string, (e: Event) => void][] = [];
-
- constructor() {
- super();
- this.el = this.children[0].cloneNode(true) as HTMLInputElement;
- }
-
- connectedCallback() {
- for (let child of this.children) {
- child.remove();
- }
- this.addInstance();
- }
-
- createInstance(): HTMLInputElement {
- let new_el = this.el.cloneNode(true) as HTMLInputElement
- let that = this;
- new_el.addEventListener('input', function() {
- /* TODO .value is empty both if it's actually empty, but also
- for invalid input. Check new_el.validity, and new_el.validationMessage
- */
- if (new_el.value === '') {
- let sibling = (this.previousElementSibling || this.nextElementSibling)
- /* Only remove ourselves if we have siblings
- Otherwise we just linger */
- if (sibling) {
- this.remove();
- (sibling as HTMLInputElement).focus();
- }
- } else {
- if (!this.nextElementSibling) {
- that.addInstance();
- // window.setTimeout(() => this.focus())
- this.focus();
- }
- }
- });
-
- for (let [type, proc] of this.#listeners) {
- new_el.addEventListener(type, proc);
- }
-
- return new_el;
- }
-
- addInstance() {
- let new_el = this.createInstance();
- this.appendChild(new_el);
- }
-
- get value(): any[] {
- let value_list = []
- for (let child of this.children) {
- value_list.push((child as any).value);
- }
- if (value_list[value_list.length - 1] === '') {
- value_list.pop();
- }
- return value_list
- }
-
- set value(new_value: any[]) {
-
- let all_equal = true;
- for (let i = 0; i < this.children.length; i++) {
- let sv = (this.children[i] as any).value
- all_equal
- &&= (sv == new_value[i])
- || (sv === '' && new_value[i] == undefined)
- }
- if (all_equal) return;
-
- /* Copy our current input elements into a dictionary.
- This allows us to only create new elements where needed
- */
- let values = new Map;
- for (let child of this.children) {
- values.set((child as HTMLInputElement).value, child);
- }
-
- let output_list: HTMLInputElement[] = []
- for (let value of new_value) {
- let element;
- /* Only create element if needed */
- if ((element = values.get(value))) {
- output_list.push(element)
- /* clear dictionary */
- values.set(value, false);
- } else {
- let new_el = this.createInstance();
- new_el.value = value;
- output_list.push(new_el);
- }
- }
- /* final, trailing, element */
- output_list.push(this.createInstance());
-
- this.replaceChildren(...output_list);
- }
-
- addEventListener(type: string, proc: ((e: Event) => void)) {
- // if (type != 'input') throw "Only input supported";
-
- this.#listeners.push([type, proc])
-
- for (let child of this.children) {
- child.addEventListener(type, proc);
- }
- }
-}
diff --git a/static/components/popup-element.ts b/static/components/popup-element.ts
deleted file mode 100644
index 458f543c..00000000
--- a/static/components/popup-element.ts
+++ /dev/null
@@ -1,201 +0,0 @@
-export { PopupElement, setup_popup_element }
-
-import { VEvent } from '../vevent'
-import { find_block, vcal_objects } from '../globals'
-
-import { ComponentVEvent } from './vevent'
-
-import { remove_event } from '../server_connect'
-
-/* <popup-element /> */
-class PopupElement extends ComponentVEvent {
-
- /* The popup which is the "selected" popup.
- /* Makes the popup last hovered over the selected popup, moving it to
- * the top, and allowing global keyboard bindings to affect it. */
- static activePopup: PopupElement | null = null;
-
- constructor(uid?: string) {
- super(uid);
-
- /* TODO populate remaining (??) */
-
- let obj = vcal_objects.get(this.uid);
- if (obj && obj.calendar) {
- this.dataset.calendar = obj.calendar;
- }
-
- /* Makes us the active popup */
- this.addEventListener('mouseover', () => {
- if (PopupElement.activePopup) {
- PopupElement.activePopup.removeAttribute('active');
- }
- PopupElement.activePopup = this;
- this.setAttribute('active', 'active');
- })
- }
-
- redraw(data: VEvent) {
- if (data.calendar) {
- /* The CSS has hooks on [data-calendar], meaning that this can
- (and will) change stuff */
- this.dataset.calendar = data.calendar;
- }
-
- }
-
- connectedCallback() {
- 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);
-
- let close_btn = body.querySelector('.popup-control .close-button') as HTMLButtonElement
- close_btn.addEventListener('click', () => this.visible = false);
-
- let maximize_btn = body.querySelector('.popup-control .maximize-button') as HTMLButtonElement
- maximize_btn.addEventListener('click', () => this.maximize());
-
- let remove_btn = body.querySelector('.popup-control .remove-button') as HTMLButtonElement
- remove_btn.addEventListener('click', () => remove_event(uid));
- /* end nav bar */
-
- this.replaceChildren(body);
- }
-
- static get observedAttributes() {
- return ['visible'];
- }
-
- attributeChangedCallback(name: string, _?: string, newValue?: string) {
- switch (name) {
- case 'visible':
- if (newValue !== null)
- /* Only run resize code when showing the popup */
- this.onVisibilityChange()
- break;
- }
- }
-
- get visible(): boolean {
- return this.hasAttribute('visible');
- }
-
- set visible(isVisible: boolean) {
- if (isVisible) {
- this.setAttribute('visible', 'visible');
- } else {
- this.removeAttribute('visible');
- }
- }
-
- private onVisibilityChange() {
- console.log('here');
-
- /* TODO better way to find root */
- let root;
- switch (window.VIEW) {
- case 'week':
- root = document.getElementsByClassName("days")[0];
- break;
- case 'month':
- default:
- root = document.body;
- break;
- }
-
- let element = find_block(this.uid) as HTMLElement | null
- /* start <X, Y> sets offset between top left corner
- of event in calendar and popup. 10, 10 soo old
- event is still visible */
- let offsetX = 10, offsetY = 10;
- while (element !== root && element !== null) {
- offsetX += element.offsetLeft;
- offsetY += element.offsetTop;
- element = element.offsetParent as HTMLElement;
- }
- this.style.left = offsetX + "px";
- this.style.top = offsetY + "px";
-
- /* Reset width and height to initial, to save user if they have resized
- it to something weird */
- let el = this.firstElementChild as HTMLElement;
- el.style.removeProperty('width');
- el.style.removeProperty('height');
- }
-
- maximize() {
- /* TODO this assumes that popups are direct decendant of their parent,
- which they really ought to be */
- let parent = this.parentElement!;
- let el = this.firstElementChild as HTMLElement
- /* TODO offsetParent.scrollLeft places us "fullscreen" according to the currently
- scrolled viewport. But is this the correct way to do it? How does it work for
- month views */
- this.style.left = `${this.offsetParent!.scrollLeft + 10}px`;
- this.style.top = '10px';
- /* 5ex is width of tab labels */
- el.style.width = `calc(${parent.clientWidth - 20}px - 5ex)`
- el.style.height = `${parent.clientHeight - 20}px`
- }
-}
-
-/* Create a new popup element for the given VEvent, and ready it for editing the
- event. Used when creating event (through the frontend).
- The return value can safely be ignored.
-*/
-function setup_popup_element(ev: VEvent): PopupElement {
- let uid = ev.getProperty('uid');
- let popup = new PopupElement(uid);
- ev.register(popup);
- /* TODO propper way to find popup container */
- (document.querySelector('.days') as Element).appendChild(popup);
- let tabBtn = popup.querySelector('[role="tab"][data-originaltitle="Edit"]') as HTMLButtonElement
- tabBtn.click()
- let tab = document.getElementById(tabBtn.getAttribute('aria-controls')!)!
- let input = tab.querySelector('input[name="summary"]') as HTMLInputElement
- popup.visible = true;
- input.select();
- return popup;
-}
-
-/*
- Given the navbar of a popup, make it dragable.
- */
-function bind_popup_control(nav: HTMLElement) {
-
- // if (!nav.closest('popup-element')) {
- // console.log(nav);
- // throw TypeError('not a popup container');
- // }
-
- nav.addEventListener('mousedown', function(e) {
- /* Ignore mousedown on children */
- if (e.target != nav) return;
- nav.style.cursor = "grabbing";
- nav.dataset.grabbed = "true";
- nav.dataset.grabPoint = e.clientX + ";" + e.clientY;
- // let popup = nav.closest(".popup-container");
- let popup = nav.closest("popup-element") as HTMLElement;
- nav.dataset.startPoint = popup.offsetLeft + ";" + popup.offsetTop;
- })
- window.addEventListener('mousemove', function(e) {
- if (nav.dataset.grabbed) {
- let [x, y] = nav.dataset.grabPoint!.split(";").map(Number);
- let [startX, startY] = nav.dataset.startPoint!.split(";").map(Number);
- // let popup = nav.closest(".popup-container");
- let popup = nav.closest("popup-element") as HTMLElement;
-
- popup.style.left = startX + (e.clientX - x) + "px";
- popup.style.top = startY + (e.clientY - y) + "px";
- }
- });
- window.addEventListener('mouseup', function() {
- nav.dataset.grabbed = "";
- nav.style.cursor = "";
- });
-}
diff --git a/static/components/slider.ts b/static/components/slider.ts
deleted file mode 100644
index 48abc91b..00000000
--- a/static/components/slider.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-export { SliderInput }
-
-import { makeElement } from '../lib'
-
-const dflt = {
- min: 0,
- max: 100,
- step: 1,
-}
-
-type Attribute = 'min' | 'max' | 'step'
-
-class SliderInput extends HTMLElement {
-
- /* value a string since javascript kind of expects that */
- #value = "0";
- min = 0;
- max = 100;
- step = 1;
-
- readonly slider: HTMLInputElement;
- readonly textIn: HTMLInputElement;
-
- constructor(min?: number, max?: number, step?: number, value?: number) {
- super();
-
- this.min = min || parseFloat(this.getAttribute('min') || "" + dflt['min']);
- this.max = max || parseFloat(this.getAttribute('max') || "" + dflt['max']);
- this.step = step || parseFloat(this.getAttribute('step') || "" + dflt['step']);
- // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range#value
- const defaultValue
- = (this.max < this.min)
- ? this.min
- : this.min + (this.max - this.min) / 2;
-
- this.slider = makeElement('input', {
- type: 'range',
- min: this.min,
- max: this.max,
- step: this.step,
- value: this.value,
- }) as HTMLInputElement
- this.textIn = makeElement('input', {
- type: 'number',
- min: this.min,
- max: this.max,
- step: this.step,
- value: this.value,
- }) as HTMLInputElement
-
- this.slider.addEventListener('input', e => this.propagate(e));
- this.textIn.addEventListener('input', e => this.propagate(e));
-
- /* MUST be after sub components are bound */
- this.value = "" + (value || this.getAttribute('value') || defaultValue);
- }
-
- connectedCallback() {
- this.replaceChildren(this.slider, this.textIn);
- }
-
-
- static get observedAttributes(): Attribute[] {
- return ['min', 'max', 'step']
- }
-
- attributeChangedCallback(name: Attribute, _?: string, to?: string): void {
- if (to) {
- this.slider.setAttribute(name, to);
- this.textIn.setAttribute(name, to);
- } else {
- this.slider.removeAttribute(name);
- this.textIn.removeAttribute(name);
- }
- this[name] = parseFloat(to || "" + dflt[name])
- }
-
- propagate(e: Event) {
- this.value = (e.target as HTMLInputElement).value;
- if (e instanceof InputEvent && this.oninput) {
- this.oninput(e);
- }
- }
-
- set value(value: string) {
- this.slider.value = value;
- this.textIn.value = value;
- this.#value = value;
- }
-
- get value(): string {
- return this.#value;
- }
-
- /* TODO do we want to implement this?
- * oninput directly on the component already works
- * /
- addEventListener(type: string, proc: ((e: Event) => void)) {
- }
- */
-}
diff --git a/static/components/tab-group-element.ts b/static/components/tab-group-element.ts
deleted file mode 100644
index e90997e9..00000000
--- a/static/components/tab-group-element.ts
+++ /dev/null
@@ -1,184 +0,0 @@
-import { ComponentVEvent } from './vevent'
-import { makeElement, gensym } from '../lib'
-import { EditRRule } from './edit-rrule'
-import { VEvent, isRedrawable } from '../vevent'
-import { vcal_objects } from '../globals'
-
-export { TabGroupElement }
-
-/* Lacks a template, since it's trivial
- The initial children of this element all becomes tabs, each child may have
- the datapropertys 'label' and 'title' set, where label is what is shown in
- the tab bar, and title is the hower text.
-
- All additions and removals of tabs MUST go through addTab and removeTab!
-
- Information about how tabs should work from an accesability standpoint can be
- found here:
- https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Tab_Role
-
- <tab-group/>
-*/
-class TabGroupElement extends ComponentVEvent {
-
- readonly menu: HTMLElement;
-
- tabs: HTMLElement[] = [];
- tabLabels: HTMLElement[] = [];
-
- constructor(uid?: string) {
- super(uid);
-
- this.menu = makeElement('div', {}, {
- role: 'tablist',
- 'aria-label': 'Simple Tabs',
- })
- }
-
- redraw(data: VEvent) {
- /* Update our tabset to match data:s having or not having of rrule,
- but do nothing if we already match */
- let rrule_tab = this.has_rrule_tab()
- if (data.getProperty('rrule')) {
- 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')),
- "↺", "Upprepningar");
- }
- } else {
- if (rrule_tab) this.removeTab(rrule_tab as HTMLElement);
- }
-
- /* TODO is there any case where we want to propagate the draw to any of
- our tabs? or are all our tabs independent? */
- }
-
- connectedCallback() {
- /* All pre-added children should become tabs, but start with removing
- them and storing them for later */
- let originalChildren: HTMLElement[] = [];
- while (this.firstChild) {
- originalChildren.push(this.removeChild(this.firstChild) as HTMLElement);
- }
-
- /* Add our tab label menu */
- this.appendChild(this.menu);
-
- /* Re-add our initial children, but as proper tab elements */
- for (let child of originalChildren) {
- this.addTab(child);
- }
-
- /* redraw might add or remove tabs depending on our data, so call it here */
- this.redraw(vcal_objects.get(this.uid)!);
-
- /* All tabs should now be ready, focus the first one */
- if (this.tabLabels.length > 0) {
- this.tabLabels[0].setAttribute('tabindex', '0');
- this.tabLabels[0].click();
- }
-
- } /* end connectedCallback */
-
- addTab(child: HTMLElement, label?: string, title?: string) {
-
- /* First character of text is a good a guess as any for our label,
- but still defaut to '?' if no text is found */
- label = label || child.dataset.label || (child.textContent + '?')[0];
- title = title || child.dataset.title || '';
- let extra_attributes = {};
- /* Used to target a tab by name */
- if (child.dataset.originaltitle) {
- extra_attributes = { 'data-originaltitle': child.dataset.originaltitle }
- }
-
- let tab_id = gensym('tab_content_');
- let label_id = gensym('tab_label_');
-
- let tabLabel = makeElement('button', {
- textContent: label,
- }, {
- role: 'tab',
- id: label_id,
- tabindex: -1,
- title: title,
- 'aria-selected': false,
- 'aria-controls': tab_id,
- ...extra_attributes,
- })
-
- let tabContainer = makeElement('div', {}, {
- id: tab_id,
- role: 'tabpanel',
- tabindex: 0,
- hidden: 'hidden',
- 'aria-labelledby': label_id,
- })
-
- tabContainer.replaceChildren(child);
- this.tabs.push(tabContainer);
- this.appendChild(tabContainer);
-
- this.tabLabels.push(tabLabel);
- this.menu.appendChild(tabLabel);
-
- tabLabel.addEventListener('click', () => this.tabClickedCallback(tabLabel));
-
- this.style.setProperty('--tabcount', '' + this.tabs.length);
- }
-
- removeTab(tab: HTMLElement) {
- let id = tab.getAttribute('aria-labelledby')!
- let label = document.getElementById(id)
- if (label) {
- if (label.ariaSelected === 'true') {
- this.tabLabels[0].click();
- }
- this.tabLabels = this.tabLabels.filter(el => el !== label)
- label.remove();
- }
- /* remove tab */
- this.tabs = this.tabs.filter(el => el !== tab)
- this.removeChild(tab);
- if (tab.firstChild) {
- let child = tab.firstChild as HTMLElement;
- if (isRedrawable(child)) {
- vcal_objects.get(this.uid)?.unregister(child)
- }
- }
-
- this.style.setProperty('--tabcount', '' + this.tabs.length);
- }
-
- /* TODO replace querySelectors here with our already saved references */
- tabClickedCallback(tab: Element) {
-
- /* hide all tab panels */
- for (let tabcontent of this.querySelectorAll('[role="tabpanel"]')) {
- tabcontent.setAttribute('hidden', 'hidden');
- }
- /* unselect all (selected) tab handles */
- for (let item of this.querySelectorAll('[aria-selected="true"]')) {
- item.setAttribute('aria-selected', 'false');
- }
- /* re-select ourselves */
- tab.setAttribute('aria-selected', 'true');
-
- /* unhide our target tab */
- this.querySelector('#' + tab.getAttribute('aria-controls'))!
- .removeAttribute('hidden')
- }
-
-
- /* returns our rrule tab if we have one */
- has_rrule_tab(): Element | false {
- for (let child of this.children) {
- if (child.firstChild! instanceof EditRRule) {
- return child;
- }
- }
- return false;
- }
-
-}
diff --git a/static/components/vevent-block.ts b/static/components/vevent-block.ts
deleted file mode 100644
index 9bbb8e7e..00000000
--- a/static/components/vevent-block.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-export { ComponentBlock }
-
-import { ComponentVEvent } from './vevent'
-import { VEvent } from '../vevent'
-import { parseDate, to_local } from '../lib'
-
-
-/* <vevent-block />
-
- A grahpical block in the week view.
-*/
-class ComponentBlock extends ComponentVEvent {
- constructor(uid?: string) {
- super(uid);
-
- if (!this.template) {
- throw 'vevent-block template required';
- }
-
- this.addEventListener('click', () => {
- let uid = this.uid
- /* TODO is it better to find the popup through a query selector, or
- by looking through all registered components of a VEvent? */
- let popup = document.querySelector(`popup-element[data-uid="${uid}"]`)
- if (popup === null) throw new Error('no popup for uid ' + uid);
- popup.toggleAttribute('visible');
- });
- }
-
- redraw(data: VEvent) {
- let body = (this.template!.content.cloneNode(true) as DocumentFragment).firstElementChild!;
-
- for (let el of body.querySelectorAll('[data-property]')) {
- if (!(el instanceof HTMLElement)) continue;
- let p = el.dataset.property!;
- let d, fmt;
- if ((d = data.getProperty(p))) {
- if ((fmt = el.dataset.fmt)) {
- el.textContent = d.format(fmt);
- } else {
- el.textContent = d;
- }
- } else switch (p.toLowerCase()) {
- /* We lack that property, but might want to set a default here */
- case 'summary':
- el.textContent = 'Ny händelse'
- break;
- }
- }
-
- this.replaceChildren(body);
-
- /* -------------------------------------------------- */
-
- if (window.VIEW === 'week') {
- let p;
- if ((p = data.getProperty('dtstart'))) {
- let c = this.closest('.event-container') as HTMLElement
- let start = parseDate(c.dataset.start!).getTime()
- let end = parseDate(c.dataset.end!).getTime();
- // console.log(p);
- let pp = to_local(p).getTime()
- let result = 100 * (Math.min(end, Math.max(start, pp)) - start) / (end - start) + "%"
- if (c.classList.contains('longevents')) {
- this.style.left = result
- } else {
- this.style.top = result
- }
- // console.log('dtstart', p);
- }
- if ((p = data.getProperty('dtend'))) {
- // console.log('dtend', p);
- let c = this.closest('.event-container') as HTMLElement
- let start = parseDate(c.dataset.start!).getTime()
- let end = parseDate(c.dataset.end!).getTime();
- let pp = to_local(p).getTime()
- let result = 100 - (100 * (Math.min(end, Math.max(start, pp)) - start) / (end - start)) + "%"
- if (c.classList.contains('longevents')) {
- this.style.width = 'unset';
- this.style.right = result;
- } else {
- this.style.height = 'unset';
- this.style.bottom = result;
- }
- }
- }
-
- if (data.calendar) {
- this.dataset.calendar = data.calendar;
- }
-
- if (data.getProperty('rrule') !== undefined) {
- let rep = this.getElementsByClassName('repeating')
- if (rep.length !== 0) {
- (rep[0] as HTMLElement).innerText = '↺'
- }
- }
- }
-}
diff --git a/static/components/vevent-description.ts b/static/components/vevent-description.ts
deleted file mode 100644
index b44185e7..00000000
--- a/static/components/vevent-description.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-export { ComponentDescription }
-
-import { VEvent } from '../vevent'
-import { ComponentVEvent } from './vevent'
-import { format } from '../formatters'
-
-/*
- <vevent-description />
-*/
-class ComponentDescription extends ComponentVEvent {
-
- constructor(uid?: string) {
- super(uid);
- if (!this.template) {
- throw 'vevent-description template required';
- }
- }
-
- redraw(data: VEvent) {
- // update ourselves from template
-
- let body = (this.template!.content.cloneNode(true) as DocumentFragment).firstElementChild!;
-
- for (let el of body.querySelectorAll('[data-property]')) {
- if (!(el instanceof HTMLElement)) continue;
- format(el, data, el.dataset.property!);
- }
-
- let repeating = body.getElementsByClassName('repeating')[0] as HTMLElement
- if (data.getProperty('rrule')) {
- repeating.classList.remove('hidden');
- } else {
- repeating.classList.add('hidden');
- }
-
- this.replaceChildren(body);
- }
-}
diff --git a/static/components/vevent-dl.ts b/static/components/vevent-dl.ts
deleted file mode 100644
index a792c07f..00000000
--- a/static/components/vevent-dl.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-export { VEventDL }
-
-import { ComponentVEvent } from './vevent'
-import { VEvent } from '../vevent'
-import { makeElement } from '../lib'
-
-import { RecurrenceRule } from '../vevent'
-
-/* <vevent-dl /> */
-class VEventDL extends ComponentVEvent {
- redraw(obj: VEvent) {
- let dl = buildDescriptionList(
- Array.from(obj.boundProperties)
- .map(key => [key, obj.getProperty(key)]))
- this.replaceChildren(dl);
- }
-}
-
-function buildDescriptionList(data: [string, any][]): HTMLElement {
- let dl = document.createElement('dl');
- for (let [key, val] of data) {
- dl.appendChild(makeElement('dt', { textContent: key }))
- let fmtVal: string = val;
- if (val instanceof Date) {
- fmtVal = val.format(
- val.dateonly
- ? '~Y-~m-~d'
- : '~Y-~m-~dT~H:~M:~S');
- } else if (val instanceof RecurrenceRule) {
- fmtVal = JSON.stringify(val.to_jcal())
- }
- dl.appendChild(makeElement('dd', { textContent: fmtVal }))
- }
- return dl;
-}
diff --git a/static/components/vevent-edit.ts b/static/components/vevent-edit.ts
deleted file mode 100644
index e3b5d105..00000000
--- a/static/components/vevent-edit.ts
+++ /dev/null
@@ -1,179 +0,0 @@
-export { ComponentEdit }
-
-import { ComponentVEvent } from './vevent'
-import { InputList } from './input-list'
-import { DateTimeInput } from './date-time-input'
-
-import { vcal_objects } from '../globals'
-import { VEvent, RecurrenceRule } from '../vevent'
-import { create_event } from '../server_connect'
-import { to_boolean, gensym } from '../lib'
-
-/* <vevent-edit />
- Edit form for a given VEvent. Used as the edit tab of popups.
-*/
-class ComponentEdit extends ComponentVEvent {
-
- constructor(uid?: string) {
- super(uid);
-
- if (!this.template) {
- throw 'vevent-edit template required';
- }
-
- let frag = this.template.content.cloneNode(true) as DocumentFragment
- let body = frag.firstElementChild!
- this.replaceChildren(body);
-
- let data = vcal_objects.get(this.uid)
- if (!data) {
- throw `Data missing for uid ${this.dataset.uid}.`
- }
-
- for (let el of this.querySelectorAll('[data-label]')) {
- let label = document.createElement('label');
- let id = el.id || gensym('input');
- el.id = id;
- label.htmlFor = id;
- label.textContent = (el as HTMLElement).dataset.label!;
- el.parentElement!.insertBefore(label, el);
- }
-
- /* Handle calendar dropdown */
- for (let el of this.querySelectorAll('select.calendar-selection')) {
- for (let opt of el.getElementsByTagName('option')) {
- opt.selected = false;
- }
- if (data.calendar) {
- (el as HTMLSelectElement).value = data.calendar;
- }
-
- el.addEventListener('change', e => {
- let v = (e.target as HTMLSelectElement).selectedOptions[0].value
- let obj = vcal_objects.get(this.uid)!
- obj.calendar = v;
- });
- }
-
-
- // for (let el of this.getElementsByClassName("interactive")) {
- for (let el of this.querySelectorAll("[data-property]")) {
- // console.log(el);
- el.addEventListener('input', () => {
- let obj = vcal_objects.get(this.uid)
- if (obj === undefined) {
- throw 'No object with uid ' + this.uid
- }
- if (!(el instanceof HTMLInputElement
- || el instanceof DateTimeInput
- || el instanceof HTMLTextAreaElement
- || el instanceof InputList
- )) {
- console.log(el, 'not an HTMLInputElement');
- return;
- }
- obj.setProperty(
- el.dataset.property!,
- el.value)
- });
- }
-
- let wholeday_ = this.querySelector('[name="wholeday"]')
- if (wholeday_) {
- let wholeday = wholeday_ as HTMLInputElement
-
- if (data.getProperty('dtstart')?.dateonly) {
- wholeday.checked = true;
- }
-
- wholeday.addEventListener('click', () => {
- let chk = wholeday.checked
- let start = data!.getProperty('dtstart')
- let end = data!.getProperty('dtend')
- start.dateonly = chk
- end.dateonly = chk
- data!.setProperty('dtstart', start);
- data!.setProperty('dtend', end);
- });
- }
-
- let has_repeats_ = this.querySelector('[name="has_repeats"]')
- if (has_repeats_) {
- let has_repeats = has_repeats_ as HTMLInputElement;
-
- has_repeats.addEventListener('click', () => {
- /* 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())
- } else {
- /* TODO is this a good way to remove a property ? */
- vcal_objects.get(this.uid)!.setProperty('rrule', undefined)
- }
- })
- }
-
- let submit = this.querySelector('form') as HTMLFormElement
- submit.addEventListener('submit', (e) => {
- console.log(submit, e);
- create_event(vcal_objects.get(this.uid)!);
-
- e.preventDefault();
- return false;
- });
- }
-
- connectedCallback() {
-
- /* 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)
-
- if (!data) {
- throw `Data missing for uid ${this.dataset.uid}.`
- }
-
- this.redraw(data);
-
- // return;
- }
-
- redraw(data: VEvent) {
- /* We only update our fields, instead of reinstansiating
- ourselves from the template, in hope that it's faster */
-
-
- for (let el of this.querySelectorAll("[data-property]")) {
- if (!(el instanceof HTMLElement)) continue;
- let p = el.dataset.property!;
- let d: any;
- if ((d = data.getProperty(p))) {
- /*
- https://stackoverflow.com/questions/57157830/how-can-i-specify-the-sequence-of-running-nested-web-components-constructors
- */
- window.setTimeout(() => {
- /* NOTE Some specific types might require special formatting
- here. But due to my custom components implementing custom
- `.value' procedures, we might not need any special cases
- here */
- /* Technically we just want to cast to HTMLElement with
- value field here, but multiple types implement it
- sepparately, and no common interface exist */
- (el as HTMLInputElement).value = d;
- });
- }
- }
-
- let el = this.querySelector('[name="has_repeats"]')
- if (el) {
- (el as HTMLInputElement).checked = to_boolean(data.getProperty('rrule'))
- }
-
- if (data.calendar) {
- for (let el of this.getElementsByClassName('calendar-selection')) {
- (el as HTMLSelectElement).value = data.calendar;
- }
- }
- }
-}
diff --git a/static/components/vevent.ts b/static/components/vevent.ts
deleted file mode 100644
index 7487cbb6..00000000
--- a/static/components/vevent.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-export { ComponentVEvent }
-
-import { vcal_objects } from '../globals'
-import { VEvent } from '../vevent'
-
-/* Root component for all events which content is closely linked to a
-@code{VEvent} object
-
-Lacks an accompaning tag, and shouldn't be directly instanciated.
-*/
-abstract class ComponentVEvent extends HTMLElement {
-
- template?: HTMLTemplateElement
- uid: string
-
- constructor(uid?: string) {
- super();
- this.template = document.getElementById(this.tagName.toLowerCase()) as HTMLTemplateElement | undefined
-
- let real_uid;
-
- if (uid) {
- // console.log('Got UID directly');
- real_uid = uid;
- } else {
- /* I know that this case is redundant, it's here if we don't want to
- look up the tree later */
- if (this.dataset.uid) {
- // console.log('Had UID as direct attribute');
- real_uid = this.dataset.uid;
- } else {
- let el = this.closest('[data-uid]')
- if (el) {
- // console.log('Found UID higher up in the tree');
- real_uid = (el as HTMLElement).dataset.uid
- } else {
- throw "No parent with [data-uid] set"
- }
- }
- }
-
- if (!real_uid) {
- console.warn(this.outerHTML);
- throw `UID required`
- }
-
- // console.log(real_uid);
- this.uid = real_uid;
- this.dataset.uid = real_uid;
-
- vcal_objects.get(this.uid)?.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.
- Those that need a direct rerendering (such as the edit tabs)
- should take care of that some other way */
- }
-
- connectedCallback() {
- let uid = this.dataset.uid
- if (uid) {
- let v = vcal_objects.get(uid)
- if (v) this.redraw(v);
- }
- }
-
- abstract redraw(data: VEvent): void
-
-}