"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HorizontalResizer = void 0; const dayjs_1 = __importDefault(require("dayjs")); const lit_element_1 = require("lit-element"); const style_map_1 = require("lit-html/directives/style-map"); const unsafe_html_1 = require("lit-html/directives/unsafe-html"); const simplebar_1 = __importDefault(require("simplebar")); const SimpleBar_styles_1 = require("./styles/SimpleBar.styles"); const Event_1 = require("./Event"); const Ressource_1 = require("./Ressource"); var horizontal_resizer_1 = require("./components/horizontal-resizer"); Object.defineProperty(exports, "HorizontalResizer", { enumerable: true, get: function () { return horizontal_resizer_1.HorizontalResizer; } }); const syncroScroll_1 = __importDefault(require("./utils/syncroScroll")); const Timeline_style_1 = require("./styles/Timeline.style"); let Timeline = class Timeline extends lit_element_1.LitElement { constructor(options = {}) { super(); this._slotDuration = 30; this._legendSpan = 2; this.rowHeight = 32; this.slotWidth = 20; this.legendUnitFormat = { y: "YYYY", M: "MMMM", d: "D", h: "H[h]", m: "m'", s: "s[s]", }; this._clearSelectionHandler = (_e) => { const exclusionZone = document.querySelectorAll("[jc-timeline-keep-select]"); if (Array.from(exclusionZone).some((e) => e.contains(_e.target))) return; this.clearSelectedItems(); const myEvent = new CustomEvent("item-selected", { detail: { items: [] }, bubbles: true, composed: true, }); this.dispatchEvent(myEvent); window.removeEventListener("click", this._clearSelectionHandler); }; this.rows = []; this.items = []; this._start = dayjs_1.default().startOf("day"); this._end = this._start.endOf("day"); this.rowsTitle = "Ressources"; this.ressourceWidth = 200; this.selectedList = []; this.legend = []; this.defaultBackground = ""; this.customStyle = ""; if (options.ressources) { this.addRessources(options.ressources); } if (options.items) { this.addEvents(options.items); } this.updateLegend(); this.render(); } static get styles() { return [Timeline_style_1.TimelineStyle, SimpleBar_styles_1.SimpleBarStyle]; } get start() { return this._start.toISOString(); } set start(value) { const d = dayjs_1.default(value); if (d.isValid()) { this._start = d < this._end ? d : this._end; this.updateLegend(); } else { console.log("Invalid Date :", value); } } get end() { return this._end.toISOString(); } set end(value) { const d = dayjs_1.default(value); if (d.isValid()) { this._end = this._start < d ? d : this._start; this.updateLegend(); } } get slotDuration() { return this._slotDuration; } set slotDuration(value) { this._slotDuration = value; this.updateLegend(); } get legendSpan() { return this._legendSpan; } set legendSpan(value) { this._legendSpan = value; this.updateLegend(); } set defaultBackground(value) { this.style.setProperty("--default-background", value); } get defaultBackground() { return this.style.getPropertyValue("--default-background"); } setLegendUnitFormatAll(legend) { this.legendUnitFormat = Object.assign(Object.assign({}, this.legendUnitFormat), legend); this.updateLegend(); } setLegendUnitFormat(unit, format) { this.legendUnitFormat[unit] = format; this.updateLegend(); } addRessources(list, pos = Infinity) { return list.map((r, idx) => this.addRessource(r, idx + pos)); 0; } addRessource(ressource, pos = Infinity) { var _a, _b; const existingRessource = this.getRessourceFromId(ressource.id); if (existingRessource) { return existingRessource; } const r = Ressource_1.Ressource.toRessource(ressource); if (r.parent !== undefined) { r.parent = (_a = this.getRessourceFromId(r.parent.id)) !== null && _a !== void 0 ? _a : this.addRessource(r.parent); const idx = this.rows.indexOf(r.parent); const alreadyChild = (_b = r.parent) === null || _b === void 0 ? void 0 : _b.children.some((c) => c.id == r.id); if (idx > -1) { if (pos <= idx) { this.rows.splice(idx + 1, 0, r); if (!alreadyChild) r.parent.children = [r, ...r.parent.children]; } else if (pos <= idx + r.parent.children.length) { if (alreadyChild) { this.rows.splice(idx + r.parent.children.indexOf(r) + 1, 0, r); } else { this.rows.splice(pos, 0, r); r.parent.children.splice(pos - idx, 0, r); } } else { if (alreadyChild) { this.rows.splice(idx + r.parent.children.indexOf(r) + 1, 0, r); } else { this.rows.splice(idx + r.parent.children.length + 1, 0, r); r.parent.children = [...r.parent.children, r]; } } } else { throw new Error("Not able to create ressource parent.\n" + r.id); } } else { if (pos < 0) { this.rows = [r, ...this.rows]; } else { let idx = pos; while (idx < this.rows.length) { if (this.rows[idx].parent == undefined) { this.rows.splice(idx, 0, r); break; } idx++; } if (idx >= this.rows.length) { this.rows = [...this.rows, r]; } } } this.addRessources(r.children); this._updateEventPosition(r); return r; } removeRessourceById(id) { return this._removeRessourceById(id); } _removeRessourceById(id, depth = 0) { const output = { ressources: [], items: [], index: 0 }; for (let i = 0; i < this.rows.length; i) { const ressource = this.rows[i]; if (ressource.id === id) { output.index = i; output.ressources.push(ressource); if (ressource.parent && depth === 0) { ressource.parent.children = ressource.parent.children.filter((o) => o.id !== id); } this.rows.splice(i, 1); } else if (ressource.parentId === id) { const partialOutput = this._removeRessourceById(ressource.id, depth + 1); output.ressources.push(...partialOutput.ressources); output.items.push(...partialOutput.items); } else { i++; } } output.items.push(...this.items.filter((i) => i.ressourceId === id)); this.items = this.items.filter((i) => i.ressourceId !== id); return output; } getRessources() { return this.rows; } getRessourceFromId(id) { const tmp = this.rows.filter((r) => r.id === id); return tmp.length > 0 ? tmp[0] : null; } updateRessource(id, key, value) { const ressource = this.getRessourceFromId(id); if (ressource) { if (key == "parent") { const content = this.removeRessourceById(id); ressource[key] = value; this.addRessource(ressource); this.addRessources(content.ressources); this.addEvents(content.items); } else { ressource[key] = value; this.rows = this.rows.map((r) => (r.id == ressource.id ? ressource : r)); } this._updateEventPosition(ressource); return ressource; } return null; } setRowsTitle(title) { this.rowsTitle = title; } getEventById(id) { return this.items.find((o) => o.id == id); } addEvents(list) { return list.map((e) => this.addEvent(e)); } addEvent(event) { const existingEvent = this.getEventById(event.id); if (existingEvent) { return existingEvent; } const ressource = this.rows.find((r) => r.id === event.ressourceId); if (ressource === undefined) { return undefined; } const timeslot = Event_1.Event.toTimeSlot(event); this.items = [...this.items, timeslot]; timeslot.isDisplayed = timeslot.end > this._start.toDate() || timeslot.start < this._end.toDate(); this._updateEventPosition(ressource); return timeslot; } removeEventById(id) { const output = this.items.filter((o) => o.id === id); this.items = this.items.filter((o) => o.id !== id); return output; } updateEventById(id) { const output = this.removeEventById(id); if (output.length > 0) { this.addEvent(output[0]); return output[0]; } else { return null; } } _updateEventPosition(ressource) { const timeslots = this.items.filter((i) => i.ressourceId === ressource.id); if (timeslots.length === 0) { ressource.height = this.rowHeight + (ressource.collapseChildren ? 5 : 0); return; } const start = this._start.toDate().getTime(); const end = this._end.toDate().getTime(); const points = [start, end]; const populateInterval = (d) => { const t = d.getTime(); if (start < t && t < end && !points.includes(t)) { points.push(t); } }; timeslots.forEach((element) => { populateInterval(element.start); populateInterval(element.end); }); points.sort(); const intervals = []; for (let i = 0; i < points.length - 1; i++) { const startTime = points[i]; const endTime = points[i + 1]; intervals.push({ start: points[i], end: points[i + 1], slots: timeslots.filter((slot) => slot.start.getTime() <= startTime && endTime <= slot.end.getTime()), }); } const lineCount = intervals.reduce((acc, interval) => Math.max(acc, interval.slots.length), 0); ressource.height = this.rowHeight * Math.max(lineCount, 1) + (ressource.collapseChildren ? 5 : 0); const sortTimeslots = (a, b) => { const t = a.start.getTime() - b.start.getTime(); if (t === 0) { const tend = b.end.getTime() - a.end.getTime(); return tend === 0 ? ("" + a.id).localeCompare(b.id) : tend; } return t; }; timeslots.forEach((slot) => (slot.offset = -1)); timeslots.sort(sortTimeslots); timeslots[0].offset = 0; const potentialOffset = []; for (let i = 0; i < lineCount; i++) { potentialOffset.push(i); } intervals.forEach((intervals) => { intervals.slots.sort(sortTimeslots); const usedOffset = intervals.slots.map((o) => o.offset).filter((i) => i > -1); const availableOffset = potentialOffset.filter((i) => !usedOffset.includes(i)); intervals.slots.forEach((slot) => { if (slot.offset === -1) { slot.offset = availableOffset.shift() || 0; } }); }); } getEvents() { return this.items; } updateLegend() { const legend = []; const legendUnitList = ["y", "M", "d", "h", "m", "s"]; const legendMinUnitSpan = this.slotDuration * this.legendSpan; const deltaT = this._end.diff(this._start, "m"); const nCol = Math.floor(deltaT / legendMinUnitSpan) + 1; for (const legendUnit of legendUnitList) { const currentDate = dayjs_1.default(this._start); const format = this.legendUnitFormat[legendUnit]; let nextColumn = currentDate.add(legendMinUnitSpan, "m"); const isLegendPossible = this._end.diff(this._start, legendUnit) > 0 && (nextColumn.format(format) !== currentDate.format(format) || currentDate.add(1, legendUnit).diff(currentDate, "m") >= legendMinUnitSpan); if (isLegendPossible) { let currentHeader = currentDate.format(format); nextColumn = currentDate; const row = []; let i = 0; for (let j = 0; j < nCol; j++) { i += this.legendSpan; nextColumn = nextColumn.add(legendMinUnitSpan, "m"); const nextHeader = nextColumn.format(format); if (currentHeader !== nextHeader) { row.push({ colspan: i, title: currentHeader, }); i = 0; currentHeader = nextHeader; } } if (i > 0) { row.push({ colspan: i, title: currentHeader, }); } legend.push(row); } } this.legend = legend; } _handleResizeX(e) { e.stopPropagation(); this.ressourceWidth += e.detail; if (this.ressourceWidth < 0) { this.ressourceWidth = 0; } } _grabHeader(e) { const root = this.shadowRoot; if (root !== null) { const headerContainer = root.querySelector(".jc-timeline-grid-title-container"); let lastPosX = e.clientX; const scroll = function (e) { const scrollLeft = lastPosX - e.clientX; headerContainer.scrollBy({ left: scrollLeft }); lastPosX = e.clientX; }; const mouseUpListener = function (_e) { window.removeEventListener("mousemove", scroll); window.removeEventListener("mouseup", mouseUpListener); }; window.addEventListener("mousemove", scroll); window.addEventListener("mouseup", mouseUpListener); } } _getEventResizerHandler(slot, direction) { return (evt) => { evt.stopPropagation(); evt.preventDefault(); const startPos = evt.clientX; const localSlot = slot; const localDir = direction; const initialDate = slot[direction]; const resizeListener = (e) => { const newDate = dayjs_1.default(initialDate) .add(Math.round((e.clientX - startPos) / this.slotWidth) * this.slotDuration, "m") .toDate(); if (direction === "start" ? newDate < localSlot.end : localSlot.start < newDate) { if (localSlot[localDir] !== newDate) { localSlot[localDir] = newDate; this.updateEventById(slot.id); } } }; const mouseUpListener = (_e) => { window.removeEventListener("mousemove", resizeListener); window.removeEventListener("mouseup", mouseUpListener); localSlot.moving = false; this.updateEventById(localSlot.id); if (initialDate !== localSlot[localDir]) { const cEvt = new CustomEvent("event-change", { detail: { items: [localSlot] }, }); this.dispatchEvent(cEvt); } }; localSlot.moving = true; window.addEventListener("mousemove", resizeListener); window.addEventListener("mouseup", mouseUpListener); }; } _getEventGrabHandler(slot, editable, ressourceEditable, callback) { return (evt) => { evt.stopPropagation(); evt.preventDefault(); const startPos = evt.clientX; let hasChanged = false; const localSlot = slot; let localSlots = this.selectedList .filter((s) => s instanceof Event_1.Event) .map((s) => s); if (!localSlots.includes(localSlot)) { localSlots = [localSlot]; } const startDates = localSlots.map((slot) => slot.start); const endDates = localSlots.map((slot) => slot.end); const updatePosition = editable ? (e) => { const changeTime = Math.round((e.clientX - startPos) / this.slotWidth) * this.slotDuration; return localSlots .map((slot, index) => { const prevStart = slot.start; slot.start = dayjs_1.default(startDates[index]).add(changeTime, "m").toDate(); slot.end = dayjs_1.default(endDates[index]).add(changeTime, "m").toDate(); return prevStart.getTime() !== slot.start.getTime(); }) .reduce((prev, curr) => prev || curr); } : (_e) => { return false; }; const updateRessource = ressourceEditable ? (e) => { var _a, _b, _c; const ressourceId = (_c = (_b = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.elementsFromPoint(e.clientX, e.clientY).find((e) => e.tagName == "TD")) === null || _b === void 0 ? void 0 : _b.parentElement) === null || _c === void 0 ? void 0 : _c.getAttribute("ressourceid"); if (ressourceId) { if (ressourceId !== localSlot.ressourceId) { const oldRessource = this.getRessourceFromId(localSlot.ressourceId); localSlot.ressourceId = ressourceId; this._updateEventPosition(oldRessource); return true; } } return false; } : (_e) => { return false; }; const moveListener = (e) => { const a = updatePosition(e); if (updateRessource(e) || a) { hasChanged = true; this.updateEventById(localSlot.id); } }; const mouseUpListener = (e) => { window.removeEventListener("mousemove", moveListener); window.removeEventListener("mouseup", mouseUpListener); localSlot.moving = false; this.updateEventById(slot.id); if (hasChanged) { const cEvt = new CustomEvent("event-change", { detail: { items: localSlots }, }); this.dispatchEvent(cEvt); } callback(e, hasChanged); }; localSlot.moving = true; window.addEventListener("mousemove", moveListener); window.addEventListener("mouseup", mouseUpListener); return true; }; } clearSelectedItems() { this.items.forEach((selectable) => { selectable.selected = false; this.updateEventById(selectable.id); }); this.rows.forEach((selectable) => (selectable.selected = false)); this.selectedList = []; } _getEventClickHandler(clickedItem) { const item = clickedItem; return (e, wasModified = false) => { e.stopPropagation(); const idx = this.selectedList.indexOf(item); if (idx > -1) { if (!wasModified) { if (e.ctrlKey) { this.selectedList.splice(idx, 1); item.selected = false; this.updateEventById(item.id); } else { this.clearSelectedItems(); } } } else { if (this.selectedList.length > 0 && !e.ctrlKey) { this.clearSelectedItems(); } item.selected = true; this.selectedList.push(item); this.updateEventById(item.id); } const myEvent = new CustomEvent("item-selected", { detail: { items: this.selectedList }, bubbles: true, composed: true, }); if (this.selectedList.length > 0) { window.addEventListener("click", this._clearSelectionHandler); } this.dispatchEvent(myEvent); }; } firstUpdated() { const root = this.shadowRoot; if (root !== null) { const gridContainer = root.querySelector(".jc-timeline-grid-container"); const simplebar = new simplebar_1.default(gridContainer).getScrollElement(); syncroScroll_1.default([simplebar, root.querySelector(".jc-timeline-grid-title-container")], "h"); syncroScroll_1.default([simplebar, root.querySelector(".jc-timeline-rows")], "v"); } if (this.defaultBackground === "") { this.style.setProperty("--default-background", "SteelBlue"); } } renderTimeslot(evt) { if (!evt.isDisplayed) { return lit_element_1.html ``; } let rowTop = 0; let ressource; let i; for (i = 0; i < this.rows.length && this.rows[i].id !== evt.ressourceId; i++) { ressource = this.rows[i]; if (ressource.show) { rowTop += ressource.height ? ressource.height : this.rowHeight; } } ressource = this.rows[i]; const minute2pixel = this.slotWidth / this.slotDuration; const left = dayjs_1.default(evt.start).diff(this._start, "m") * minute2pixel; const right = -dayjs_1.default(evt.end).diff(this._end, "m") * minute2pixel; const style = { height: this.rowHeight - 4 + "px", top: rowTop + evt.offset * this.rowHeight + "px", left: left + "px", right: right + "px", backgroundColor: "", }; const bgColor = evt.bgColor ? evt.bgColor : ressource.eventBgColor; if (bgColor) { style.backgroundColor = bgColor; } if (!ressource.show) { style.height = ""; style.top = rowTop - 6 + "px"; return lit_element_1.html `
`; } let content = lit_element_1.html `
${evt.title}
${evt.content ? unsafe_html_1.unsafeHTML(evt.content) : ""}`; const resizer = evt.editable === null ? ressource.eventEditable : evt.editable; const editableRessource = evt.ressourceEditable === null ? ressource.eventRessourceEditable : evt.ressourceEditable; if (resizer) { content = lit_element_1.html `
${content}
`; } return lit_element_1.html `
${content}
`; } _getCollapseRessourceHandler(item) { return (_e) => { item.collapseChildren = !item.collapseChildren; this._updateEventPosition(item); this.rows = [...this.rows]; }; } _onRessourceDragStart(item) { return (event) => { var _a; (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.setData("text", item.id); }; } _onRessourceDragEnter(event) { if (event.target instanceof HTMLElement) { const tgt = event.target; tgt.classList.add("target"); } } _onRessourceDragLeave(event) { if (event.target instanceof HTMLElement) { event.target.classList.remove("target"); } } _onRessourceDrop(event) { var _a, _b; event.preventDefault(); if (event.target instanceof HTMLElement) { event.target.classList.remove("target"); const srcId = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData("text"); const destinationId = (_b = event.target.parentElement) === null || _b === void 0 ? void 0 : _b.getAttribute("ressourceId"); if (srcId && destinationId && destinationId !== srcId) { const src = this.getRessourceFromId(srcId); const destination = this.getRessourceFromId(destinationId); if (destination.descendantOf(src)) { return; } const movedContent = this.removeRessourceById(src.id); if (event.target.classList.contains("jc-ressource")) { movedContent.ressources[0].parent = destination; this.addRessource(src); } else { movedContent.ressources[0].parent = destination.parent; let idx = this.rows.findIndex((v) => v.id === destinationId); if (event.target.classList.contains("jc-ressource-below")) { idx += 1; while (idx < this.rows.length && this.rows[idx].descendantOf(destination)) { idx += 1; } } console.log(idx); this.addRessource(src, idx); } const idx = this.rows.findIndex((v) => v.id === srcId); this.addRessources(movedContent.ressources, idx - 1); this.addEvents(movedContent.items); this.dispatchEvent(new CustomEvent("reorder-ressource", { detail: { ressources: this.rows }, })); } } } renderRessource(item) { const depth = item.depth; const style = `--depth:${depth};` + (item.height ? `height:${item.height}px;` : ""); const hasChild = item.children.length > 0; const collapseHandler = this._getCollapseRessourceHandler(item); return lit_element_1.html `
${Array(depth) .fill(0) .map((_i) => lit_element_1.html ``)}${hasChild ? lit_element_1.html `` : lit_element_1.html ``} ${item.title}
`; } renderGridRow(columns, id = "", height = 30) { return lit_element_1.html ` ${columns.map((d, i) => lit_element_1.html `   `)} `; } render() { const nCol = Math.floor(this._end.diff(this._start, "m") / this.slotDuration) + 1; const columns = []; for (let i = 0; i < nCol; i++) { columns.push(this._start.add(this.slotDuration * i, "m")); } const displayedRows = this.rows .map((r, i) => { return { i: i, r: r }; }) .filter((o) => o.r.show); return lit_element_1.html `
${this.rowsTitle}
${columns.map((_) => lit_element_1.html ``)} ${this.legend.map((arr) => lit_element_1.html ` ${arr.map((o) => lit_element_1.html ``)} `)}
${o.title}
${this.rows.length > 0 ? displayedRows.map((o) => this.renderRessource(o.r)) : lit_element_1.html ``}
No ressource
${columns.map((_) => lit_element_1.html ``)} ${this.rows.length > 0 ? displayedRows.map((o) => this.renderGridRow(columns, o.r.id, o.r.height)) : this.renderGridRow(columns)}
${this.items.map((slot) => this.renderTimeslot(slot))}
`; } }; __decorate([ lit_element_1.internalProperty() ], Timeline.prototype, "rows", void 0); __decorate([ lit_element_1.internalProperty() ], Timeline.prototype, "items", void 0); __decorate([ lit_element_1.property({ type: Number }) ], Timeline.prototype, "ressourceWidth", void 0); __decorate([ lit_element_1.internalProperty() ], Timeline.prototype, "_start", void 0); __decorate([ lit_element_1.property({ type: String }) ], Timeline.prototype, "start", null); __decorate([ lit_element_1.internalProperty() ], Timeline.prototype, "_end", void 0); __decorate([ lit_element_1.property({ type: String }) ], Timeline.prototype, "end", null); __decorate([ lit_element_1.property({ type: Number }) ], Timeline.prototype, "slotDuration", null); __decorate([ lit_element_1.property({ type: Number }) ], Timeline.prototype, "legendSpan", null); __decorate([ lit_element_1.property({ type: Number }) ], Timeline.prototype, "rowHeight", void 0); __decorate([ lit_element_1.property({ type: Number }) ], Timeline.prototype, "slotWidth", void 0); __decorate([ lit_element_1.property({ type: String }) ], Timeline.prototype, "rowsTitle", void 0); __decorate([ lit_element_1.property({ type: Array }) ], Timeline.prototype, "legend", void 0); __decorate([ lit_element_1.property({ type: String }) ], Timeline.prototype, "defaultBackground", null); Timeline = __decorate([ lit_element_1.customElement("jc-timeline") ], Timeline); exports.default = Timeline; //# sourceMappingURL=Timeline.js.map