1632 lines
208 KiB
JavaScript
1632 lines
208 KiB
JavaScript
|
|
var __create = Object.create;
|
||
|
|
var __defProp = Object.defineProperty;
|
||
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||
|
|
var __getProtoOf = Object.getPrototypeOf;
|
||
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||
|
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
||
|
|
var __commonJS = (cb, mod) => function __require() {
|
||
|
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||
|
|
};
|
||
|
|
var __copyProps = (to, from, except, desc) => {
|
||
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
||
|
|
for (let key of __getOwnPropNames(from))
|
||
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
||
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||
|
|
}
|
||
|
|
return to;
|
||
|
|
};
|
||
|
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||
|
|
// If the importer is in node compatibility mode or this is not an ESM
|
||
|
|
// file that has been converted to a CommonJS file using a Babel-
|
||
|
|
// compatible transform (i.e. "__esModule" has not been set), then set
|
||
|
|
// "default" to the CommonJS "module.exports" for node compatibility.
|
||
|
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||
|
|
mod
|
||
|
|
));
|
||
|
|
|
||
|
|
// node_modules/dayjs/dayjs.min.js
|
||
|
|
var require_dayjs_min = __commonJS({
|
||
|
|
"node_modules/dayjs/dayjs.min.js"(exports, module) {
|
||
|
|
!function(t, e) {
|
||
|
|
"object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs = e();
|
||
|
|
}(exports, function() {
|
||
|
|
"use strict";
|
||
|
|
var t = 1e3, e = 6e4, n = 36e5, r = "millisecond", i = "second", s = "minute", u = "hour", a = "day", o = "week", c = "month", f = "quarter", h = "year", d = "date", l = "Invalid Date", $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(t2) {
|
||
|
|
var e2 = ["th", "st", "nd", "rd"], n2 = t2 % 100;
|
||
|
|
return "[" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + "]";
|
||
|
|
} }, m = /* @__PURE__ */ __name(function(t2, e2, n2) {
|
||
|
|
var r2 = String(t2);
|
||
|
|
return !r2 || r2.length >= e2 ? t2 : "" + Array(e2 + 1 - r2.length).join(n2) + t2;
|
||
|
|
}, "m"), v = { s: m, z: function(t2) {
|
||
|
|
var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60;
|
||
|
|
return (e2 <= 0 ? "+" : "-") + m(r2, 2, "0") + ":" + m(i2, 2, "0");
|
||
|
|
}, m: /* @__PURE__ */ __name(function t2(e2, n2) {
|
||
|
|
if (e2.date() < n2.date())
|
||
|
|
return -t2(n2, e2);
|
||
|
|
var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, c), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), c);
|
||
|
|
return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0);
|
||
|
|
}, "t"), a: function(t2) {
|
||
|
|
return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2);
|
||
|
|
}, p: function(t2) {
|
||
|
|
return { M: c, y: h, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: f }[t2] || String(t2 || "").toLowerCase().replace(/s$/, "");
|
||
|
|
}, u: function(t2) {
|
||
|
|
return void 0 === t2;
|
||
|
|
} }, g = "en", D = {};
|
||
|
|
D[g] = M;
|
||
|
|
var p = "$isDayjsObject", S = /* @__PURE__ */ __name(function(t2) {
|
||
|
|
return t2 instanceof _ || !(!t2 || !t2[p]);
|
||
|
|
}, "S"), w = /* @__PURE__ */ __name(function t2(e2, n2, r2) {
|
||
|
|
var i2;
|
||
|
|
if (!e2)
|
||
|
|
return g;
|
||
|
|
if ("string" == typeof e2) {
|
||
|
|
var s2 = e2.toLowerCase();
|
||
|
|
D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2);
|
||
|
|
var u2 = e2.split("-");
|
||
|
|
if (!i2 && u2.length > 1)
|
||
|
|
return t2(u2[0]);
|
||
|
|
} else {
|
||
|
|
var a2 = e2.name;
|
||
|
|
D[a2] = e2, i2 = a2;
|
||
|
|
}
|
||
|
|
return !r2 && i2 && (g = i2), i2 || !r2 && g;
|
||
|
|
}, "t"), O = /* @__PURE__ */ __name(function(t2, e2) {
|
||
|
|
if (S(t2))
|
||
|
|
return t2.clone();
|
||
|
|
var n2 = "object" == typeof e2 ? e2 : {};
|
||
|
|
return n2.date = t2, n2.args = arguments, new _(n2);
|
||
|
|
}, "O"), b = v;
|
||
|
|
b.l = w, b.i = S, b.w = function(t2, e2) {
|
||
|
|
return O(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset });
|
||
|
|
};
|
||
|
|
var _ = function() {
|
||
|
|
function M2(t2) {
|
||
|
|
this.$L = w(t2.locale, null, true), this.parse(t2), this.$x = this.$x || t2.x || {}, this[p] = true;
|
||
|
|
}
|
||
|
|
__name(M2, "M");
|
||
|
|
var m2 = M2.prototype;
|
||
|
|
return m2.parse = function(t2) {
|
||
|
|
this.$d = function(t3) {
|
||
|
|
var e2 = t3.date, n2 = t3.utc;
|
||
|
|
if (null === e2)
|
||
|
|
return /* @__PURE__ */ new Date(NaN);
|
||
|
|
if (b.u(e2))
|
||
|
|
return /* @__PURE__ */ new Date();
|
||
|
|
if (e2 instanceof Date)
|
||
|
|
return new Date(e2);
|
||
|
|
if ("string" == typeof e2 && !/Z$/i.test(e2)) {
|
||
|
|
var r2 = e2.match($);
|
||
|
|
if (r2) {
|
||
|
|
var i2 = r2[2] - 1 || 0, s2 = (r2[7] || "0").substring(0, 3);
|
||
|
|
return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return new Date(e2);
|
||
|
|
}(t2), this.init();
|
||
|
|
}, m2.init = function() {
|
||
|
|
var t2 = this.$d;
|
||
|
|
this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds();
|
||
|
|
}, m2.$utils = function() {
|
||
|
|
return b;
|
||
|
|
}, m2.isValid = function() {
|
||
|
|
return !(this.$d.toString() === l);
|
||
|
|
}, m2.isSame = function(t2, e2) {
|
||
|
|
var n2 = O(t2);
|
||
|
|
return this.startOf(e2) <= n2 && n2 <= this.endOf(e2);
|
||
|
|
}, m2.isAfter = function(t2, e2) {
|
||
|
|
return O(t2) < this.startOf(e2);
|
||
|
|
}, m2.isBefore = function(t2, e2) {
|
||
|
|
return this.endOf(e2) < O(t2);
|
||
|
|
}, m2.$g = function(t2, e2, n2) {
|
||
|
|
return b.u(t2) ? this[e2] : this.set(n2, t2);
|
||
|
|
}, m2.unix = function() {
|
||
|
|
return Math.floor(this.valueOf() / 1e3);
|
||
|
|
}, m2.valueOf = function() {
|
||
|
|
return this.$d.getTime();
|
||
|
|
}, m2.startOf = function(t2, e2) {
|
||
|
|
var n2 = this, r2 = !!b.u(e2) || e2, f2 = b.p(t2), l2 = /* @__PURE__ */ __name(function(t3, e3) {
|
||
|
|
var i2 = b.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2);
|
||
|
|
return r2 ? i2 : i2.endOf(a);
|
||
|
|
}, "l"), $2 = /* @__PURE__ */ __name(function(t3, e3) {
|
||
|
|
return b.w(n2.toDate()[t3].apply(n2.toDate("s"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2);
|
||
|
|
}, "$"), y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = "set" + (this.$u ? "UTC" : "");
|
||
|
|
switch (f2) {
|
||
|
|
case h:
|
||
|
|
return r2 ? l2(1, 0) : l2(31, 11);
|
||
|
|
case c:
|
||
|
|
return r2 ? l2(1, M3) : l2(0, M3 + 1);
|
||
|
|
case o:
|
||
|
|
var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2;
|
||
|
|
return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3);
|
||
|
|
case a:
|
||
|
|
case d:
|
||
|
|
return $2(v2 + "Hours", 0);
|
||
|
|
case u:
|
||
|
|
return $2(v2 + "Minutes", 1);
|
||
|
|
case s:
|
||
|
|
return $2(v2 + "Seconds", 2);
|
||
|
|
case i:
|
||
|
|
return $2(v2 + "Milliseconds", 3);
|
||
|
|
default:
|
||
|
|
return this.clone();
|
||
|
|
}
|
||
|
|
}, m2.endOf = function(t2) {
|
||
|
|
return this.startOf(t2, false);
|
||
|
|
}, m2.$set = function(t2, e2) {
|
||
|
|
var n2, o2 = b.p(t2), f2 = "set" + (this.$u ? "UTC" : ""), l2 = (n2 = {}, n2[a] = f2 + "Date", n2[d] = f2 + "Date", n2[c] = f2 + "Month", n2[h] = f2 + "FullYear", n2[u] = f2 + "Hours", n2[s] = f2 + "Minutes", n2[i] = f2 + "Seconds", n2[r] = f2 + "Milliseconds", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2;
|
||
|
|
if (o2 === c || o2 === h) {
|
||
|
|
var y2 = this.clone().set(d, 1);
|
||
|
|
y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d;
|
||
|
|
} else
|
||
|
|
l2 && this.$d[l2]($2);
|
||
|
|
return this.init(), this;
|
||
|
|
}, m2.set = function(t2, e2) {
|
||
|
|
return this.clone().$set(t2, e2);
|
||
|
|
}, m2.get = function(t2) {
|
||
|
|
return this[b.p(t2)]();
|
||
|
|
}, m2.add = function(r2, f2) {
|
||
|
|
var d2, l2 = this;
|
||
|
|
r2 = Number(r2);
|
||
|
|
var $2 = b.p(f2), y2 = /* @__PURE__ */ __name(function(t2) {
|
||
|
|
var e2 = O(l2);
|
||
|
|
return b.w(e2.date(e2.date() + Math.round(t2 * r2)), l2);
|
||
|
|
}, "y");
|
||
|
|
if ($2 === c)
|
||
|
|
return this.set(c, this.$M + r2);
|
||
|
|
if ($2 === h)
|
||
|
|
return this.set(h, this.$y + r2);
|
||
|
|
if ($2 === a)
|
||
|
|
return y2(1);
|
||
|
|
if ($2 === o)
|
||
|
|
return y2(7);
|
||
|
|
var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3;
|
||
|
|
return b.w(m3, this);
|
||
|
|
}, m2.subtract = function(t2, e2) {
|
||
|
|
return this.add(-1 * t2, e2);
|
||
|
|
}, m2.format = function(t2) {
|
||
|
|
var e2 = this, n2 = this.$locale();
|
||
|
|
if (!this.isValid())
|
||
|
|
return n2.invalidDate || l;
|
||
|
|
var r2 = t2 || "YYYY-MM-DDTHH:mm:ssZ", i2 = b.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, c2 = n2.months, f2 = n2.meridiem, h2 = /* @__PURE__ */ __name(function(t3, n3, i3, s3) {
|
||
|
|
return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3);
|
||
|
|
}, "h"), d2 = /* @__PURE__ */ __name(function(t3) {
|
||
|
|
return b.s(s2 % 12 || 12, t3, "0");
|
||
|
|
}, "d"), $2 = f2 || function(t3, e3, n3) {
|
||
|
|
var r3 = t3 < 12 ? "AM" : "PM";
|
||
|
|
return n3 ? r3.toLowerCase() : r3;
|
||
|
|
};
|
||
|
|
return r2.replace(y, function(t3, r3) {
|
||
|
|
return r3 || function(t4) {
|
||
|
|
switch (t4) {
|
||
|
|
case "YY":
|
||
|
|
return String(e2.$y).slice(-2);
|
||
|
|
case "YYYY":
|
||
|
|
return b.s(e2.$y, 4, "0");
|
||
|
|
case "M":
|
||
|
|
return a2 + 1;
|
||
|
|
case "MM":
|
||
|
|
return b.s(a2 + 1, 2, "0");
|
||
|
|
case "MMM":
|
||
|
|
return h2(n2.monthsShort, a2, c2, 3);
|
||
|
|
case "MMMM":
|
||
|
|
return h2(c2, a2);
|
||
|
|
case "D":
|
||
|
|
return e2.$D;
|
||
|
|
case "DD":
|
||
|
|
return b.s(e2.$D, 2, "0");
|
||
|
|
case "d":
|
||
|
|
return String(e2.$W);
|
||
|
|
case "dd":
|
||
|
|
return h2(n2.weekdaysMin, e2.$W, o2, 2);
|
||
|
|
case "ddd":
|
||
|
|
return h2(n2.weekdaysShort, e2.$W, o2, 3);
|
||
|
|
case "dddd":
|
||
|
|
return o2[e2.$W];
|
||
|
|
case "H":
|
||
|
|
return String(s2);
|
||
|
|
case "HH":
|
||
|
|
return b.s(s2, 2, "0");
|
||
|
|
case "h":
|
||
|
|
return d2(1);
|
||
|
|
case "hh":
|
||
|
|
return d2(2);
|
||
|
|
case "a":
|
||
|
|
return $2(s2, u2, true);
|
||
|
|
case "A":
|
||
|
|
return $2(s2, u2, false);
|
||
|
|
case "m":
|
||
|
|
return String(u2);
|
||
|
|
case "mm":
|
||
|
|
return b.s(u2, 2, "0");
|
||
|
|
case "s":
|
||
|
|
return String(e2.$s);
|
||
|
|
case "ss":
|
||
|
|
return b.s(e2.$s, 2, "0");
|
||
|
|
case "SSS":
|
||
|
|
return b.s(e2.$ms, 3, "0");
|
||
|
|
case "Z":
|
||
|
|
return i2;
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}(t3) || i2.replace(":", "");
|
||
|
|
});
|
||
|
|
}, m2.utcOffset = function() {
|
||
|
|
return 15 * -Math.round(this.$d.getTimezoneOffset() / 15);
|
||
|
|
}, m2.diff = function(r2, d2, l2) {
|
||
|
|
var $2, y2 = this, M3 = b.p(d2), m3 = O(r2), v2 = (m3.utcOffset() - this.utcOffset()) * e, g2 = this - m3, D2 = /* @__PURE__ */ __name(function() {
|
||
|
|
return b.m(y2, m3);
|
||
|
|
}, "D");
|
||
|
|
switch (M3) {
|
||
|
|
case h:
|
||
|
|
$2 = D2() / 12;
|
||
|
|
break;
|
||
|
|
case c:
|
||
|
|
$2 = D2();
|
||
|
|
break;
|
||
|
|
case f:
|
||
|
|
$2 = D2() / 3;
|
||
|
|
break;
|
||
|
|
case o:
|
||
|
|
$2 = (g2 - v2) / 6048e5;
|
||
|
|
break;
|
||
|
|
case a:
|
||
|
|
$2 = (g2 - v2) / 864e5;
|
||
|
|
break;
|
||
|
|
case u:
|
||
|
|
$2 = g2 / n;
|
||
|
|
break;
|
||
|
|
case s:
|
||
|
|
$2 = g2 / e;
|
||
|
|
break;
|
||
|
|
case i:
|
||
|
|
$2 = g2 / t;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
$2 = g2;
|
||
|
|
}
|
||
|
|
return l2 ? $2 : b.a($2);
|
||
|
|
}, m2.daysInMonth = function() {
|
||
|
|
return this.endOf(c).$D;
|
||
|
|
}, m2.$locale = function() {
|
||
|
|
return D[this.$L];
|
||
|
|
}, m2.locale = function(t2, e2) {
|
||
|
|
if (!t2)
|
||
|
|
return this.$L;
|
||
|
|
var n2 = this.clone(), r2 = w(t2, e2, true);
|
||
|
|
return r2 && (n2.$L = r2), n2;
|
||
|
|
}, m2.clone = function() {
|
||
|
|
return b.w(this.$d, this);
|
||
|
|
}, m2.toDate = function() {
|
||
|
|
return new Date(this.valueOf());
|
||
|
|
}, m2.toJSON = function() {
|
||
|
|
return this.isValid() ? this.toISOString() : null;
|
||
|
|
}, m2.toISOString = function() {
|
||
|
|
return this.$d.toISOString();
|
||
|
|
}, m2.toString = function() {
|
||
|
|
return this.$d.toUTCString();
|
||
|
|
}, M2;
|
||
|
|
}(), k = _.prototype;
|
||
|
|
return O.prototype = k, [["$ms", r], ["$s", i], ["$m", s], ["$H", u], ["$W", a], ["$M", c], ["$y", h], ["$D", d]].forEach(function(t2) {
|
||
|
|
k[t2[1]] = function(e2) {
|
||
|
|
return this.$g(e2, t2[0], t2[1]);
|
||
|
|
};
|
||
|
|
}), O.extend = function(t2, e2) {
|
||
|
|
return t2.$i || (t2(e2, _, O), t2.$i = true), O;
|
||
|
|
}, O.locale = w, O.isDayjs = S, O.unix = function(t2) {
|
||
|
|
return O(1e3 * t2);
|
||
|
|
}, O.en = D[g], O.Ls = D, O.p = {}, O;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// node_modules/dayjs/plugin/utc.js
|
||
|
|
var require_utc = __commonJS({
|
||
|
|
"node_modules/dayjs/plugin/utc.js"(exports, module) {
|
||
|
|
!function(t, i) {
|
||
|
|
"object" == typeof exports && "undefined" != typeof module ? module.exports = i() : "function" == typeof define && define.amd ? define(i) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_utc = i();
|
||
|
|
}(exports, function() {
|
||
|
|
"use strict";
|
||
|
|
var t = "minute", i = /[+-]\d\d(?::?\d\d)?/g, e = /([+-]|\d\d)/g;
|
||
|
|
return function(s, f, n) {
|
||
|
|
var u = f.prototype;
|
||
|
|
n.utc = function(t2) {
|
||
|
|
var i2 = { date: t2, utc: true, args: arguments };
|
||
|
|
return new f(i2);
|
||
|
|
}, u.utc = function(i2) {
|
||
|
|
var e2 = n(this.toDate(), { locale: this.$L, utc: true });
|
||
|
|
return i2 ? e2.add(this.utcOffset(), t) : e2;
|
||
|
|
}, u.local = function() {
|
||
|
|
return n(this.toDate(), { locale: this.$L, utc: false });
|
||
|
|
};
|
||
|
|
var r = u.parse;
|
||
|
|
u.parse = function(t2) {
|
||
|
|
t2.utc && (this.$u = true), this.$utils().u(t2.$offset) || (this.$offset = t2.$offset), r.call(this, t2);
|
||
|
|
};
|
||
|
|
var o = u.init;
|
||
|
|
u.init = function() {
|
||
|
|
if (this.$u) {
|
||
|
|
var t2 = this.$d;
|
||
|
|
this.$y = t2.getUTCFullYear(), this.$M = t2.getUTCMonth(), this.$D = t2.getUTCDate(), this.$W = t2.getUTCDay(), this.$H = t2.getUTCHours(), this.$m = t2.getUTCMinutes(), this.$s = t2.getUTCSeconds(), this.$ms = t2.getUTCMilliseconds();
|
||
|
|
} else
|
||
|
|
o.call(this);
|
||
|
|
};
|
||
|
|
var a = u.utcOffset;
|
||
|
|
u.utcOffset = function(s2, f2) {
|
||
|
|
var n2 = this.$utils().u;
|
||
|
|
if (n2(s2))
|
||
|
|
return this.$u ? 0 : n2(this.$offset) ? a.call(this) : this.$offset;
|
||
|
|
if ("string" == typeof s2 && (s2 = function(t2) {
|
||
|
|
void 0 === t2 && (t2 = "");
|
||
|
|
var s3 = t2.match(i);
|
||
|
|
if (!s3)
|
||
|
|
return null;
|
||
|
|
var f3 = ("" + s3[0]).match(e) || ["-", 0, 0], n3 = f3[0], u3 = 60 * +f3[1] + +f3[2];
|
||
|
|
return 0 === u3 ? 0 : "+" === n3 ? u3 : -u3;
|
||
|
|
}(s2), null === s2))
|
||
|
|
return this;
|
||
|
|
var u2 = Math.abs(s2) <= 16 ? 60 * s2 : s2;
|
||
|
|
if (0 === u2)
|
||
|
|
return this.utc(f2);
|
||
|
|
var r2 = this.clone();
|
||
|
|
if (f2)
|
||
|
|
return r2.$offset = u2, r2.$u = false, r2;
|
||
|
|
var o2 = this.$u ? this.toDate().getTimezoneOffset() : -1 * this.utcOffset();
|
||
|
|
return (r2 = this.local().add(u2 + o2, t)).$offset = u2, r2.$x.$localOffset = o2, r2;
|
||
|
|
};
|
||
|
|
var h = u.format;
|
||
|
|
u.format = function(t2) {
|
||
|
|
var i2 = t2 || (this.$u ? "YYYY-MM-DDTHH:mm:ss[Z]" : "");
|
||
|
|
return h.call(this, i2);
|
||
|
|
}, u.valueOf = function() {
|
||
|
|
var t2 = this.$utils().u(this.$offset) ? 0 : this.$offset + (this.$x.$localOffset || this.$d.getTimezoneOffset());
|
||
|
|
return this.$d.valueOf() - 6e4 * t2;
|
||
|
|
}, u.isUTC = function() {
|
||
|
|
return !!this.$u;
|
||
|
|
}, u.toISOString = function() {
|
||
|
|
return this.toDate().toISOString();
|
||
|
|
}, u.toString = function() {
|
||
|
|
return this.toDate().toUTCString();
|
||
|
|
};
|
||
|
|
var l = u.toDate;
|
||
|
|
u.toDate = function(t2) {
|
||
|
|
return "s" === t2 && this.$offset ? n(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate() : l.call(this);
|
||
|
|
};
|
||
|
|
var c = u.diff;
|
||
|
|
u.diff = function(t2, i2, e2) {
|
||
|
|
if (t2 && this.$u === t2.$u)
|
||
|
|
return c.call(this, t2, i2, e2);
|
||
|
|
var s2 = this.local(), f2 = n(t2).local();
|
||
|
|
return c.call(s2, f2, i2, e2);
|
||
|
|
};
|
||
|
|
};
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// node_modules/dayjs/plugin/timezone.js
|
||
|
|
var require_timezone = __commonJS({
|
||
|
|
"node_modules/dayjs/plugin/timezone.js"(exports, module) {
|
||
|
|
!function(t, e) {
|
||
|
|
"object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_timezone = e();
|
||
|
|
}(exports, function() {
|
||
|
|
"use strict";
|
||
|
|
var t = { year: 0, month: 1, day: 2, hour: 3, minute: 4, second: 5 }, e = {};
|
||
|
|
return function(n, i, o) {
|
||
|
|
var r, a = /* @__PURE__ */ __name(function(t2, n2, i2) {
|
||
|
|
void 0 === i2 && (i2 = {});
|
||
|
|
var o2 = new Date(t2), r2 = function(t3, n3) {
|
||
|
|
void 0 === n3 && (n3 = {});
|
||
|
|
var i3 = n3.timeZoneName || "short", o3 = t3 + "|" + i3, r3 = e[o3];
|
||
|
|
return r3 || (r3 = new Intl.DateTimeFormat("en-US", { hour12: false, timeZone: t3, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", timeZoneName: i3 }), e[o3] = r3), r3;
|
||
|
|
}(n2, i2);
|
||
|
|
return r2.formatToParts(o2);
|
||
|
|
}, "a"), u = /* @__PURE__ */ __name(function(e2, n2) {
|
||
|
|
for (var i2 = a(e2, n2), r2 = [], u2 = 0; u2 < i2.length; u2 += 1) {
|
||
|
|
var f2 = i2[u2], s2 = f2.type, m = f2.value, c = t[s2];
|
||
|
|
c >= 0 && (r2[c] = parseInt(m, 10));
|
||
|
|
}
|
||
|
|
var d = r2[3], l = 24 === d ? 0 : d, h = r2[0] + "-" + r2[1] + "-" + r2[2] + " " + l + ":" + r2[4] + ":" + r2[5] + ":000", v = +e2;
|
||
|
|
return (o.utc(h).valueOf() - (v -= v % 1e3)) / 6e4;
|
||
|
|
}, "u"), f = i.prototype;
|
||
|
|
f.tz = function(t2, e2) {
|
||
|
|
void 0 === t2 && (t2 = r);
|
||
|
|
var n2, i2 = this.utcOffset(), a2 = this.toDate(), u2 = a2.toLocaleString("en-US", { timeZone: t2 }), f2 = Math.round((a2 - new Date(u2)) / 1e3 / 60), s2 = 15 * -Math.round(a2.getTimezoneOffset() / 15) - f2;
|
||
|
|
if (!Number(s2))
|
||
|
|
n2 = this.utcOffset(0, e2);
|
||
|
|
else if (n2 = o(u2, { locale: this.$L }).$set("millisecond", this.$ms).utcOffset(s2, true), e2) {
|
||
|
|
var m = n2.utcOffset();
|
||
|
|
n2 = n2.add(i2 - m, "minute");
|
||
|
|
}
|
||
|
|
return n2.$x.$timezone = t2, n2;
|
||
|
|
}, f.offsetName = function(t2) {
|
||
|
|
var e2 = this.$x.$timezone || o.tz.guess(), n2 = a(this.valueOf(), e2, { timeZoneName: t2 }).find(function(t3) {
|
||
|
|
return "timezonename" === t3.type.toLowerCase();
|
||
|
|
});
|
||
|
|
return n2 && n2.value;
|
||
|
|
};
|
||
|
|
var s = f.startOf;
|
||
|
|
f.startOf = function(t2, e2) {
|
||
|
|
if (!this.$x || !this.$x.$timezone)
|
||
|
|
return s.call(this, t2, e2);
|
||
|
|
var n2 = o(this.format("YYYY-MM-DD HH:mm:ss:SSS"), { locale: this.$L });
|
||
|
|
return s.call(n2, t2, e2).tz(this.$x.$timezone, true);
|
||
|
|
}, o.tz = function(t2, e2, n2) {
|
||
|
|
var i2 = n2 && e2, a2 = n2 || e2 || r, f2 = u(+o(), a2);
|
||
|
|
if ("string" != typeof t2)
|
||
|
|
return o(t2).tz(a2);
|
||
|
|
var s2 = function(t3, e3, n3) {
|
||
|
|
var i3 = t3 - 60 * e3 * 1e3, o2 = u(i3, n3);
|
||
|
|
if (e3 === o2)
|
||
|
|
return [i3, e3];
|
||
|
|
var r2 = u(i3 -= 60 * (o2 - e3) * 1e3, n3);
|
||
|
|
return o2 === r2 ? [i3, o2] : [t3 - 60 * Math.min(o2, r2) * 1e3, Math.max(o2, r2)];
|
||
|
|
}(o.utc(t2, i2).valueOf(), f2, a2), m = s2[0], c = s2[1], d = o(m).utcOffset(c);
|
||
|
|
return d.$x.$timezone = a2, d;
|
||
|
|
}, o.tz.guess = function() {
|
||
|
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||
|
|
}, o.tz.setDefault = function(t2) {
|
||
|
|
r = t2;
|
||
|
|
};
|
||
|
|
};
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// node_modules/dayjs/plugin/isoWeek.js
|
||
|
|
var require_isoWeek = __commonJS({
|
||
|
|
"node_modules/dayjs/plugin/isoWeek.js"(exports, module) {
|
||
|
|
!function(e, t) {
|
||
|
|
"object" == typeof exports && "undefined" != typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define(t) : (e = "undefined" != typeof globalThis ? globalThis : e || self).dayjs_plugin_isoWeek = t();
|
||
|
|
}(exports, function() {
|
||
|
|
"use strict";
|
||
|
|
var e = "day";
|
||
|
|
return function(t, i, s) {
|
||
|
|
var a = /* @__PURE__ */ __name(function(t2) {
|
||
|
|
return t2.add(4 - t2.isoWeekday(), e);
|
||
|
|
}, "a"), d = i.prototype;
|
||
|
|
d.isoWeekYear = function() {
|
||
|
|
return a(this).year();
|
||
|
|
}, d.isoWeek = function(t2) {
|
||
|
|
if (!this.$utils().u(t2))
|
||
|
|
return this.add(7 * (t2 - this.isoWeek()), e);
|
||
|
|
var i2, d2, n2, o, r = a(this), u = (i2 = this.isoWeekYear(), d2 = this.$u, n2 = (d2 ? s.utc : s)().year(i2).startOf("year"), o = 4 - n2.isoWeekday(), n2.isoWeekday() > 4 && (o += 7), n2.add(o, e));
|
||
|
|
return r.diff(u, "week") + 1;
|
||
|
|
}, d.isoWeekday = function(e2) {
|
||
|
|
return this.$utils().u(e2) ? this.day() || 7 : this.day(this.day() % 7 ? e2 : e2 - 7);
|
||
|
|
};
|
||
|
|
var n = d.startOf;
|
||
|
|
d.startOf = function(e2, t2) {
|
||
|
|
var i2 = this.$utils(), s2 = !!i2.u(t2) || t2;
|
||
|
|
return "isoweek" === i2.p(e2) ? s2 ? this.date(this.date() - (this.isoWeekday() - 1)).startOf("day") : this.date(this.date() - 1 - (this.isoWeekday() - 1) + 7).endOf("day") : n.bind(this)(e2, t2);
|
||
|
|
};
|
||
|
|
};
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// src/v2/core/RenderBuilder.ts
|
||
|
|
function buildPipeline(renderers) {
|
||
|
|
return {
|
||
|
|
async run(context) {
|
||
|
|
for (const renderer of renderers) {
|
||
|
|
await renderer.render(context);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
__name(buildPipeline, "buildPipeline");
|
||
|
|
|
||
|
|
// src/v2/core/FilterTemplate.ts
|
||
|
|
var _FilterTemplate = class _FilterTemplate {
|
||
|
|
constructor(dateService, entityResolver) {
|
||
|
|
this.dateService = dateService;
|
||
|
|
this.entityResolver = entityResolver;
|
||
|
|
this.fields = [];
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Tilføj felt til template
|
||
|
|
* @param idProperty - Property-navn (bruges på både event og column.dataset)
|
||
|
|
* @param derivedFrom - Hvis feltet udledes fra anden property (f.eks. date fra start)
|
||
|
|
*/
|
||
|
|
addField(idProperty, derivedFrom) {
|
||
|
|
this.fields.push({ idProperty, derivedFrom });
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Parse dot-notation string into components
|
||
|
|
* @example 'resource.teamId' → { entityType: 'resource', property: 'teamId', foreignKey: 'resourceId' }
|
||
|
|
*/
|
||
|
|
parseDotNotation(idProperty) {
|
||
|
|
if (!idProperty.includes("."))
|
||
|
|
return null;
|
||
|
|
const [entityType, property] = idProperty.split(".");
|
||
|
|
return {
|
||
|
|
entityType,
|
||
|
|
property,
|
||
|
|
foreignKey: entityType + "Id"
|
||
|
|
// Convention: resource → resourceId
|
||
|
|
};
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get dataset key for column lookup
|
||
|
|
* For dot-notation 'resource.teamId', we look for 'teamId' in dataset
|
||
|
|
*/
|
||
|
|
getDatasetKey(idProperty) {
|
||
|
|
const dotNotation = this.parseDotNotation(idProperty);
|
||
|
|
if (dotNotation) {
|
||
|
|
return dotNotation.property;
|
||
|
|
}
|
||
|
|
return idProperty;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Byg nøgle fra kolonne
|
||
|
|
* Læser værdier fra column.dataset[idProperty]
|
||
|
|
* For dot-notation, uses the property part (resource.teamId → teamId)
|
||
|
|
*/
|
||
|
|
buildKeyFromColumn(column) {
|
||
|
|
return this.fields.map((f) => {
|
||
|
|
const key = this.getDatasetKey(f.idProperty);
|
||
|
|
return column.dataset[key] || "";
|
||
|
|
}).join(":");
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Byg nøgle fra event
|
||
|
|
* Læser værdier fra event[idProperty] eller udleder fra derivedFrom
|
||
|
|
* For dot-notation, resolves via EntityResolver
|
||
|
|
*/
|
||
|
|
buildKeyFromEvent(event) {
|
||
|
|
const eventRecord = event;
|
||
|
|
return this.fields.map((f) => {
|
||
|
|
const dotNotation = this.parseDotNotation(f.idProperty);
|
||
|
|
if (dotNotation) {
|
||
|
|
return this.resolveDotNotation(eventRecord, dotNotation);
|
||
|
|
}
|
||
|
|
if (f.derivedFrom) {
|
||
|
|
const sourceValue = eventRecord[f.derivedFrom];
|
||
|
|
if (sourceValue instanceof Date) {
|
||
|
|
return this.dateService.getDateKey(sourceValue);
|
||
|
|
}
|
||
|
|
return String(sourceValue || "");
|
||
|
|
}
|
||
|
|
return String(eventRecord[f.idProperty] || "");
|
||
|
|
}).join(":");
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Resolve dot-notation reference via EntityResolver
|
||
|
|
*/
|
||
|
|
resolveDotNotation(eventRecord, dotNotation) {
|
||
|
|
if (!this.entityResolver) {
|
||
|
|
console.warn(`FilterTemplate: EntityResolver required for dot-notation '${dotNotation.entityType}.${dotNotation.property}'`);
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
const foreignId = eventRecord[dotNotation.foreignKey];
|
||
|
|
if (!foreignId)
|
||
|
|
return "";
|
||
|
|
const entity = this.entityResolver.resolve(dotNotation.entityType, String(foreignId));
|
||
|
|
if (!entity)
|
||
|
|
return "";
|
||
|
|
return String(entity[dotNotation.property] || "");
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Match event mod kolonne
|
||
|
|
*/
|
||
|
|
matches(event, column) {
|
||
|
|
return this.buildKeyFromEvent(event) === this.buildKeyFromColumn(column);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
__name(_FilterTemplate, "FilterTemplate");
|
||
|
|
var FilterTemplate = _FilterTemplate;
|
||
|
|
|
||
|
|
// src/v2/core/CalendarOrchestrator.ts
|
||
|
|
var _CalendarOrchestrator = class _CalendarOrchestrator {
|
||
|
|
constructor(allRenderers, eventRenderer, scheduleRenderer, headerDrawerRenderer, dateService, entityServices) {
|
||
|
|
this.allRenderers = allRenderers;
|
||
|
|
this.eventRenderer = eventRenderer;
|
||
|
|
this.scheduleRenderer = scheduleRenderer;
|
||
|
|
this.headerDrawerRenderer = headerDrawerRenderer;
|
||
|
|
this.dateService = dateService;
|
||
|
|
this.entityServices = entityServices;
|
||
|
|
}
|
||
|
|
async render(viewConfig, container) {
|
||
|
|
const headerContainer = container.querySelector("swp-calendar-header");
|
||
|
|
const columnContainer = container.querySelector("swp-day-columns");
|
||
|
|
if (!headerContainer || !columnContainer) {
|
||
|
|
throw new Error("Missing swp-calendar-header or swp-day-columns");
|
||
|
|
}
|
||
|
|
const filter = {};
|
||
|
|
for (const grouping of viewConfig.groupings) {
|
||
|
|
filter[grouping.type] = grouping.values;
|
||
|
|
}
|
||
|
|
const filterTemplate = new FilterTemplate(this.dateService);
|
||
|
|
for (const grouping of viewConfig.groupings) {
|
||
|
|
if (grouping.idProperty) {
|
||
|
|
filterTemplate.addField(grouping.idProperty, grouping.derivedFrom);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const { parentChildMap, childType } = await this.resolveBelongsTo(viewConfig.groupings, filter);
|
||
|
|
const context = { headerContainer, columnContainer, filter, groupings: viewConfig.groupings, parentChildMap, childType };
|
||
|
|
headerContainer.innerHTML = "";
|
||
|
|
columnContainer.innerHTML = "";
|
||
|
|
const levels = viewConfig.groupings.map((g) => g.type).join(" ");
|
||
|
|
headerContainer.dataset.levels = levels;
|
||
|
|
const activeRenderers = this.selectRenderers(viewConfig);
|
||
|
|
const pipeline = buildPipeline(activeRenderers);
|
||
|
|
await pipeline.run(context);
|
||
|
|
await this.scheduleRenderer.render(container, filter);
|
||
|
|
await this.eventRenderer.render(container, filter, filterTemplate);
|
||
|
|
await this.headerDrawerRenderer.render(container, filter, filterTemplate);
|
||
|
|
}
|
||
|
|
selectRenderers(viewConfig) {
|
||
|
|
const types = viewConfig.groupings.map((g) => g.type);
|
||
|
|
return types.map((type) => this.allRenderers.find((r) => r.type === type)).filter((r) => r !== void 0);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Resolve belongsTo relations to build parent-child map
|
||
|
|
* e.g., belongsTo: 'team.resourceIds' → { team1: ['EMP001', 'EMP002'], team2: [...] }
|
||
|
|
* Also returns the childType (the grouping type that has belongsTo)
|
||
|
|
*/
|
||
|
|
async resolveBelongsTo(groupings, filter) {
|
||
|
|
const childGrouping = groupings.find((g) => g.belongsTo);
|
||
|
|
if (!childGrouping?.belongsTo)
|
||
|
|
return {};
|
||
|
|
const [entityType, property] = childGrouping.belongsTo.split(".");
|
||
|
|
if (!entityType || !property)
|
||
|
|
return {};
|
||
|
|
const parentIds = filter[entityType] || [];
|
||
|
|
if (parentIds.length === 0)
|
||
|
|
return {};
|
||
|
|
const service = this.entityServices.find(
|
||
|
|
(s) => s.entityType.toLowerCase() === entityType
|
||
|
|
);
|
||
|
|
if (!service)
|
||
|
|
return {};
|
||
|
|
const allEntities = await service.getAll();
|
||
|
|
const entities = allEntities.filter(
|
||
|
|
(e) => parentIds.includes(e.id)
|
||
|
|
);
|
||
|
|
const map = {};
|
||
|
|
for (const entity of entities) {
|
||
|
|
const entityRecord = entity;
|
||
|
|
const children = entityRecord[property] || [];
|
||
|
|
map[entityRecord.id] = children;
|
||
|
|
}
|
||
|
|
return { parentChildMap: map, childType: childGrouping.type };
|
||
|
|
}
|
||
|
|
};
|
||
|
|
__name(_CalendarOrchestrator, "CalendarOrchestrator");
|
||
|
|
var CalendarOrchestrator = _CalendarOrchestrator;
|
||
|
|
|
||
|
|
// src/v2/core/NavigationAnimator.ts
|
||
|
|
var _NavigationAnimator = class _NavigationAnimator {
|
||
|
|
constructor(headerTrack, contentTrack) {
|
||
|
|
this.headerTrack = headerTrack;
|
||
|
|
this.contentTrack = contentTrack;
|
||
|
|
}
|
||
|
|
async slide(direction, renderFn) {
|
||
|
|
const out = direction === "left" ? "-100%" : "100%";
|
||
|
|
const into = direction === "left" ? "100%" : "-100%";
|
||
|
|
await this.animateOut(out);
|
||
|
|
await renderFn();
|
||
|
|
await this.animateIn(into);
|
||
|
|
}
|
||
|
|
async animateOut(translate) {
|
||
|
|
await Promise.all([
|
||
|
|
this.headerTrack.animate(
|
||
|
|
[{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }],
|
||
|
|
{ duration: 200, easing: "ease-in" }
|
||
|
|
).finished,
|
||
|
|
this.contentTrack.animate(
|
||
|
|
[{ transform: "translateX(0)" }, { transform: `translateX(${translate})` }],
|
||
|
|
{ duration: 200, easing: "ease-in" }
|
||
|
|
).finished
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
async animateIn(translate) {
|
||
|
|
await Promise.all([
|
||
|
|
this.headerTrack.animate(
|
||
|
|
[{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }],
|
||
|
|
{ duration: 200, easing: "ease-out" }
|
||
|
|
).finished,
|
||
|
|
this.contentTrack.animate(
|
||
|
|
[{ transform: `translateX(${translate})` }, { transform: "translateX(0)" }],
|
||
|
|
{ duration: 200, easing: "ease-out" }
|
||
|
|
).finished
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
__name(_NavigationAnimator, "NavigationAnimator");
|
||
|
|
var NavigationAnimator = _NavigationAnimator;
|
||
|
|
|
||
|
|
// src/v2/features/date/DateRenderer.ts
|
||
|
|
var _DateRenderer = class _DateRenderer {
|
||
|
|
constructor(dateService) {
|
||
|
|
this.dateService = dateService;
|
||
|
|
this.type = "date";
|
||
|
|
}
|
||
|
|
render(context) {
|
||
|
|
const dates = context.filter["date"] || [];
|
||
|
|
const resourceIds = context.filter["resource"] || [];
|
||
|
|
const dateGrouping = context.groupings?.find((g) => g.type === "date");
|
||
|
|
const hideHeader = dateGrouping?.hideHeader === true;
|
||
|
|
const iterations = resourceIds.length || 1;
|
||
|
|
let columnCount = 0;
|
||
|
|
for (let r = 0; r < iterations; r++) {
|
||
|
|
const resourceId = resourceIds[r];
|
||
|
|
for (const dateStr of dates) {
|
||
|
|
const date = this.dateService.parseISO(dateStr);
|
||
|
|
const segments = { date: dateStr };
|
||
|
|
if (resourceId)
|
||
|
|
segments.resource = resourceId;
|
||
|
|
const columnKey = this.dateService.buildColumnKey(segments);
|
||
|
|
const header = document.createElement("swp-day-header");
|
||
|
|
header.dataset.date = dateStr;
|
||
|
|
header.dataset.columnKey = columnKey;
|
||
|
|
if (resourceId) {
|
||
|
|
header.dataset.resourceId = resourceId;
|
||
|
|
}
|
||
|
|
if (hideHeader) {
|
||
|
|
header.dataset.hidden = "true";
|
||
|
|
}
|
||
|
|
header.innerHTML = `
|
||
|
|
<swp-day-name>${this.dateService.getDayName(date, "short")}</swp-day-name>
|
||
|
|
<swp-day-date>${date.getDate()}</swp-day-date>
|
||
|
|
`;
|
||
|
|
context.headerContainer.appendChild(header);
|
||
|
|
const column = document.createElement("swp-day-column");
|
||
|
|
column.dataset.date = dateStr;
|
||
|
|
column.dataset.columnKey = columnKey;
|
||
|
|
if (resourceId) {
|
||
|
|
column.dataset.resourceId = resourceId;
|
||
|
|
}
|
||
|
|
column.innerHTML = "<swp-events-layer></swp-events-layer>";
|
||
|
|
context.columnContainer.appendChild(column);
|
||
|
|
columnCount++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const container = context.columnContainer.closest("swp-calendar-container");
|
||
|
|
if (container) {
|
||
|
|
container.style.setProperty("--grid-columns", String(columnCount));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
__name(_DateRenderer, "DateRenderer");
|
||
|
|
var DateRenderer = _DateRenderer;
|
||
|
|
|
||
|
|
// src/v2/core/DateService.ts
|
||
|
|
var import_dayjs = __toESM(require_dayjs_min(), 1);
|
||
|
|
var import_utc = __toESM(require_utc(), 1);
|
||
|
|
var import_timezone = __toESM(require_timezone(), 1);
|
||
|
|
var import_isoWeek = __toESM(require_isoWeek(), 1);
|
||
|
|
import_dayjs.default.extend(import_utc.default);
|
||
|
|
import_dayjs.default.extend(import_timezone.default);
|
||
|
|
import_dayjs.default.extend(import_isoWeek.default);
|
||
|
|
var _DateService = class _DateService {
|
||
|
|
constructor(config, baseDate) {
|
||
|
|
this.config = config;
|
||
|
|
this.timezone = config.timezone;
|
||
|
|
this.baseDate = baseDate ? (0, import_dayjs.default)(baseDate) : (0, import_dayjs.default)();
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Set a fixed base date (useful for demos with static mock data)
|
||
|
|
*/
|
||
|
|
setBaseDate(date) {
|
||
|
|
this.baseDate = (0, import_dayjs.default)(date);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get the current base date (either fixed or today)
|
||
|
|
*/
|
||
|
|
getBaseDate() {
|
||
|
|
return this.baseDate.toDate();
|
||
|
|
}
|
||
|
|
parseISO(isoString) {
|
||
|
|
return (0, import_dayjs.default)(isoString).toDate();
|
||
|
|
}
|
||
|
|
getDayName(date, format = "short") {
|
||
|
|
return new Intl.DateTimeFormat(this.config.locale, { weekday: format }).format(date);
|
||
|
|
}
|
||
|
|
getWeekDates(offset = 0, days = 7) {
|
||
|
|
const monday = this.baseDate.startOf("week").add(1, "day").add(offset, "week");
|
||
|
|
return Array.from(
|
||
|
|
{ length: days },
|
||
|
|
(_, i) => monday.add(i, "day").format("YYYY-MM-DD")
|
||
|
|
);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get dates for specific weekdays within a week
|
||
|
|
* @param offset - Week offset from base date (0 = current week)
|
||
|
|
* @param workDays - Array of ISO weekday numbers (1=Monday, 7=Sunday)
|
||
|
|
* @returns Array of date strings in YYYY-MM-DD format
|
||
|
|
*/
|
||
|
|
getWorkWeekDates(offset, workDays) {
|
||
|
|
const monday = this.baseDate.startOf("week").add(1, "day").add(offset, "week");
|
||
|
|
return workDays.map((isoDay) => {
|
||
|
|
const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1;
|
||
|
|
return monday.add(daysFromMonday, "day").format("YYYY-MM-DD");
|
||
|
|
});
|
||
|
|
}
|
||
|
|
// ============================================
|
||
|
|
// FORMATTING
|
||
|
|
// ============================================
|
||
|
|
formatTime(date, showSeconds = false) {
|
||
|
|
const pattern = showSeconds ? "HH:mm:ss" : "HH:mm";
|
||
|
|
return (0, import_dayjs.default)(date).format(pattern);
|
||
|
|
}
|
||
|
|
formatTimeRange(start, end) {
|
||
|
|
return `${this.formatTime(start)} - ${this.formatTime(end)}`;
|
||
|
|
}
|
||
|
|
formatDate(date) {
|
||
|
|
return (0, import_dayjs.default)(date).format("YYYY-MM-DD");
|
||
|
|
}
|
||
|
|
getDateKey(date) {
|
||
|
|
return this.formatDate(date);
|
||
|
|
}
|
||
|
|
// ============================================
|
||
|
|
// COLUMN KEY
|
||
|
|
// ============================================
|
||
|
|
/**
|
||
|
|
* Build a uniform columnKey from grouping segments
|
||
|
|
* Handles any combination of date, resource, team, etc.
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
* buildColumnKey({ date: '2025-12-09' }) → "2025-12-09"
|
||
|
|
* buildColumnKey({ date: '2025-12-09', resource: 'EMP001' }) → "2025-12-09:EMP001"
|
||
|
|
*/
|
||
|
|
buildColumnKey(segments) {
|
||
|
|
const date = segments.date;
|
||
|
|
const others = Object.entries(segments).filter(([k]) => k !== "date").sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v);
|
||
|
|
return date ? [date, ...others].join(":") : others.join(":");
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Parse a columnKey back into segments
|
||
|
|
* Assumes format: "date:resource:..." or just "date"
|
||
|
|
*/
|
||
|
|
parseColumnKey(columnKey) {
|
||
|
|
const parts = columnKey.split(":");
|
||
|
|
return {
|
||
|
|
date: parts[0],
|
||
|
|
resource: parts[1]
|
||
|
|
};
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Extract dateKey from columnKey (first segment)
|
||
|
|
*/
|
||
|
|
getDateFromColumnKey(columnKey) {
|
||
|
|
return columnKey.split(":")[0];
|
||
|
|
}
|
||
|
|
// ============================================
|
||
|
|
// TIME CALCULATIONS
|
||
|
|
// ============================================
|
||
|
|
timeToMinutes(timeString) {
|
||
|
|
const parts = timeString.split(":").map(Number);
|
||
|
|
const hours = parts[0] || 0;
|
||
|
|
const minutes = parts[1] || 0;
|
||
|
|
return hours * 60 + minutes;
|
||
|
|
}
|
||
|
|
minutesToTime(totalMinutes) {
|
||
|
|
const hours = Math.floor(totalMinutes / 60);
|
||
|
|
const minutes = totalMinutes % 60;
|
||
|
|
return (0, import_dayjs.default)().hour(hours).minute(minutes).format("HH:mm");
|
||
|
|
}
|
||
|
|
getMinutesSinceMidnight(date) {
|
||
|
|
const d = (0, import_dayjs.default)(date);
|
||
|
|
return d.hour() * 60 + d.minute();
|
||
|
|
}
|
||
|
|
// ============================================
|
||
|
|
// UTC CONVERSIONS
|
||
|
|
// ============================================
|
||
|
|
toUTC(localDate) {
|
||
|
|
return import_dayjs.default.tz(localDate, this.timezone).utc().toISOString();
|
||
|
|
}
|
||
|
|
fromUTC(utcString) {
|
||
|
|
return import_dayjs.default.utc(utcString).tz(this.timezone).toDate();
|
||
|
|
}
|
||
|
|
// ============================================
|
||
|
|
// DATE CREATION
|
||
|
|
// ============================================
|
||
|
|
createDateAtTime(baseDate, timeString) {
|
||
|
|
const totalMinutes = this.timeToMinutes(timeString);
|
||
|
|
const hours = Math.floor(totalMinutes / 60);
|
||
|
|
const minutes = totalMinutes % 60;
|
||
|
|
return (0, import_dayjs.default)(baseDate).startOf("day").hour(hours).minute(minutes).toDate();
|
||
|
|
}
|
||
|
|
getISOWeekDay(date) {
|
||
|
|
return (0, import_dayjs.default)(date).isoWeekday();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
__name(_DateService, "DateService");
|
||
|
|
var DateService = _DateService;
|
||
|
|
|
||
|
|
// src/v2/utils/PositionUtils.ts
|
||
|
|
function calculateEventPosition(start, end, config) {
|
||
|
|
const startMinutes = start.getHours() * 60 + start.getMinutes();
|
||
|
|
const endMinutes = end.getHours() * 60 + end.getMinutes();
|
||
|
|
const dayStartMinutes = config.dayStartHour * 60;
|
||
|
|
const minuteHeight = config.hourHeight / 60;
|
||
|
|
const top = (startMinutes - dayStartMinutes) * minuteHeight;
|
||
|
|
const height = (endMinutes - startMinutes) * minuteHeight;
|
||
|
|
return { top, height };
|
||
|
|
}
|
||
|
|
__name(calculateEventPosition, "calculateEventPosition");
|
||
|
|
function minutesToPixels(minutes, config) {
|
||
|
|
return minutes / 60 * config.hourHeight;
|
||
|
|
}
|
||
|
|
__name(minutesToPixels, "minutesToPixels");
|
||
|
|
function pixelsToMinutes(pixels, config) {
|
||
|
|
return pixels / config.hourHeight * 60;
|
||
|
|
}
|
||
|
|
__name(pixelsToMinutes, "pixelsToMinutes");
|
||
|
|
function snapToGrid(pixels, config) {
|
||
|
|
const snapPixels = minutesToPixels(config.snapInterval, config);
|
||
|
|
return Math.round(pixels / snapPixels) * snapPixels;
|
||
|
|
}
|
||
|
|
__name(snapToGrid, "snapToGrid");
|
||
|
|
|
||
|
|
// src/v2/constants/CoreEvents.ts
|
||
|
|
var CoreEvents = {
|
||
|
|
// Lifecycle events
|
||
|
|
INITIALIZED: "core:initialized",
|
||
|
|
READY: "core:ready",
|
||
|
|
DESTROYED: "core:destroyed",
|
||
|
|
// View events
|
||
|
|
VIEW_CHANGED: "view:changed",
|
||
|
|
VIEW_RENDERED: "view:rendered",
|
||
|
|
// Navigation events
|
||
|
|
DATE_CHANGED: "nav:date-changed",
|
||
|
|
NAVIGATION_COMPLETED: "nav:navigation-completed",
|
||
|
|
// Data events
|
||
|
|
DATA_LOADING: "data:loading",
|
||
|
|
DATA_LOADED: "data:loaded",
|
||
|
|
DATA_ERROR: "data:error",
|
||
|
|
// Grid events
|
||
|
|
GRID_RENDERED: "grid:rendered",
|
||
|
|
GRID_CLICKED: "grid:clicked",
|
||
|
|
// Event management
|
||
|
|
EVENT_CREATED: "event:created",
|
||
|
|
EVENT_UPDATED: "event:updated",
|
||
|
|
EVENT_DELETED: "event:deleted",
|
||
|
|
EVENT_SELECTED: "event:selected",
|
||
|
|
// Event drag-drop
|
||
|
|
EVENT_DRAG_START: "event:drag-start",
|
||
|
|
EVENT_DRAG_MOVE: "event:drag-move",
|
||
|
|
EVENT_DRAG_END: "event:drag-end",
|
||
|
|
EVENT_DRAG_CANCEL: "event:drag-cancel",
|
||
|
|
EVENT_DRAG_COLUMN_CHANGE: "event:drag-column-change",
|
||
|
|
// Header drag (timed → header conversion)
|
||
|
|
EVENT_DRAG_ENTER_HEADER: "event:drag-enter-header",
|
||
|
|
EVENT_DRAG_MOVE_HEADER: "event:drag-move-header",
|
||
|
|
EVENT_DRAG_LEAVE_HEADER: "event:drag-leave-header",
|
||
|
|
// Event resize
|
||
|
|
EVENT_RESIZE_START: "event:resize-start",
|
||
|
|
EVENT_RESIZE_END: "event:resize-end",
|
||
|
|
// Edge scroll
|
||
|
|
EDGE_SCROLL_TICK: "edge-scroll:tick",
|
||
|
|
EDGE_SCROLL_STARTED: "edge-scroll:started",
|
||
|
|
EDGE_SCROLL_STOPPED: "edge-scroll:stopped",
|
||
|
|
// System events
|
||
|
|
ERROR: "system:error",
|
||
|
|
// Sync events
|
||
|
|
SYNC_STARTED: "sync:started",
|
||
|
|
SYNC_COMPLETED: "sync:completed",
|
||
|
|
SYNC_FAILED: "sync:failed",
|
||
|
|
// Entity events - for audit and sync
|
||
|
|
ENTITY_SAVED: "entity:saved",
|
||
|
|
ENTITY_DELETED: "entity:deleted",
|
||
|
|
// Audit events
|
||
|
|
AUDIT_LOGGED: "audit:logged",
|
||
|
|
// Rendering events
|
||
|
|
EVENTS_RENDERED: "events:rendered"
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/v2/features/event/EventLayoutEngine.ts
|
||
|
|
function eventsOverlap(a, b) {
|
||
|
|
return a.start < b.end && a.end > b.start;
|
||
|
|
}
|
||
|
|
__name(eventsOverlap, "eventsOverlap");
|
||
|
|
function eventsWithinThreshold(a, b, thresholdMinutes) {
|
||
|
|
const thresholdMs = thresholdMinutes * 60 * 1e3;
|
||
|
|
const startToStartDiff = Math.abs(a.start.getTime() - b.start.getTime());
|
||
|
|
if (startToStartDiff <= thresholdMs)
|
||
|
|
return true;
|
||
|
|
const bStartsBeforeAEnds = a.end.getTime() - b.start.getTime();
|
||
|
|
if (bStartsBeforeAEnds > 0 && bStartsBeforeAEnds <= thresholdMs)
|
||
|
|
return true;
|
||
|
|
const aStartsBeforeBEnds = b.end.getTime() - a.start.getTime();
|
||
|
|
if (aStartsBeforeBEnds > 0 && aStartsBeforeBEnds <= thresholdMs)
|
||
|
|
return true;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
__name(eventsWithinThreshold, "eventsWithinThreshold");
|
||
|
|
function findOverlapGroups(events) {
|
||
|
|
if (events.length === 0)
|
||
|
|
return [];
|
||
|
|
const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
|
||
|
|
const used = /* @__PURE__ */ new Set();
|
||
|
|
const groups = [];
|
||
|
|
for (const event of sorted) {
|
||
|
|
if (used.has(event.id))
|
||
|
|
continue;
|
||
|
|
const group = [event];
|
||
|
|
used.add(event.id);
|
||
|
|
let expanded = true;
|
||
|
|
while (expanded) {
|
||
|
|
expanded = false;
|
||
|
|
for (const candidate of sorted) {
|
||
|
|
if (used.has(candidate.id))
|
||
|
|
continue;
|
||
|
|
const connects = group.some((member) => eventsOverlap(member, candidate));
|
||
|
|
if (connects) {
|
||
|
|
group.push(candidate);
|
||
|
|
used.add(candidate.id);
|
||
|
|
expanded = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
groups.push(group);
|
||
|
|
}
|
||
|
|
return groups;
|
||
|
|
}
|
||
|
|
__name(findOverlapGroups, "findOverlapGroups");
|
||
|
|
function findGridCandidates(events, thresholdMinutes) {
|
||
|
|
if (events.length === 0)
|
||
|
|
return [];
|
||
|
|
const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
|
||
|
|
const used = /* @__PURE__ */ new Set();
|
||
|
|
const groups = [];
|
||
|
|
for (const event of sorted) {
|
||
|
|
if (used.has(event.id))
|
||
|
|
continue;
|
||
|
|
const group = [event];
|
||
|
|
used.add(event.id);
|
||
|
|
let expanded = true;
|
||
|
|
while (expanded) {
|
||
|
|
expanded = false;
|
||
|
|
for (const candidate of sorted) {
|
||
|
|
if (used.has(candidate.id))
|
||
|
|
continue;
|
||
|
|
const connects = group.some(
|
||
|
|
(member) => eventsWithinThreshold(member, candidate, thresholdMinutes)
|
||
|
|
);
|
||
|
|
if (connects) {
|
||
|
|
group.push(candidate);
|
||
|
|
used.add(candidate.id);
|
||
|
|
expanded = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
groups.push(group);
|
||
|
|
}
|
||
|
|
return groups;
|
||
|
|
}
|
||
|
|
__name(findGridCandidates, "findGridCandidates");
|
||
|
|
function calculateStackLevels(events) {
|
||
|
|
const levels = /* @__PURE__ */ new Map();
|
||
|
|
const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
|
||
|
|
for (const event of sorted) {
|
||
|
|
let maxOverlappingLevel = -1;
|
||
|
|
for (const [id, level] of levels) {
|
||
|
|
const other = events.find((e) => e.id === id);
|
||
|
|
if (other && eventsOverlap(event, other)) {
|
||
|
|
maxOverlappingLevel = Math.max(maxOverlappingLevel, level);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
levels.set(event.id, maxOverlappingLevel + 1);
|
||
|
|
}
|
||
|
|
return levels;
|
||
|
|
}
|
||
|
|
__name(calculateStackLevels, "calculateStackLevels");
|
||
|
|
function allocateColumns(events) {
|
||
|
|
const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
|
||
|
|
const columns = [];
|
||
|
|
for (const event of sorted) {
|
||
|
|
let placed = false;
|
||
|
|
for (const column of columns) {
|
||
|
|
const canFit = !column.some((e) => eventsOverlap(event, e));
|
||
|
|
if (canFit) {
|
||
|
|
column.push(event);
|
||
|
|
placed = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!placed) {
|
||
|
|
columns.push([event]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return columns;
|
||
|
|
}
|
||
|
|
__name(allocateColumns, "allocateColumns");
|
||
|
|
function calculateColumnLayout(events, config) {
|
||
|
|
const thresholdMinutes = config.gridStartThresholdMinutes ?? 10;
|
||
|
|
const result = {
|
||
|
|
grids: [],
|
||
|
|
stacked: []
|
||
|
|
};
|
||
|
|
if (events.length === 0)
|
||
|
|
return result;
|
||
|
|
const overlapGroups = findOverlapGroups(events);
|
||
|
|
for (const overlapGroup of overlapGroups) {
|
||
|
|
if (overlapGroup.length === 1) {
|
||
|
|
result.stacked.push({
|
||
|
|
event: overlapGroup[0],
|
||
|
|
stackLevel: 0
|
||
|
|
});
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const gridSubgroups = findGridCandidates(overlapGroup, thresholdMinutes);
|
||
|
|
const largestGridCandidate = gridSubgroups.reduce((max, g) => g.length > max.length ? g : max, gridSubgroups[0]);
|
||
|
|
if (largestGridCandidate.length === overlapGroup.length) {
|
||
|
|
const columns = allocateColumns(overlapGroup);
|
||
|
|
const earliest = overlapGroup.reduce((min, e) => e.start < min.start ? e : min, overlapGroup[0]);
|
||
|
|
const position = calculateEventPosition(earliest.start, earliest.end, config);
|
||
|
|
result.grids.push({
|
||
|
|
events: overlapGroup,
|
||
|
|
columns,
|
||
|
|
stackLevel: 0,
|
||
|
|
position: { top: position.top }
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
const levels = calculateStackLevels(overlapGroup);
|
||
|
|
for (const event of overlapGroup) {
|
||
|
|
result.stacked.push({
|
||
|
|
event,
|
||
|
|
stackLevel: levels.get(event.id) ?? 0
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
__name(calculateColumnLayout, "calculateColumnLayout");
|
||
|
|
|
||
|
|
// src/v2/features/event/EventRenderer.ts
|
||
|
|
var _EventRenderer = class _EventRenderer {
|
||
|
|
constructor(eventService, dateService, gridConfig, eventBus) {
|
||
|
|
this.eventService = eventService;
|
||
|
|
this.dateService = dateService;
|
||
|
|
this.gridConfig = gridConfig;
|
||
|
|
this.eventBus = eventBus;
|
||
|
|
this.container = null;
|
||
|
|
this.setupListeners();
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Setup listeners for drag-drop and update events
|
||
|
|
*/
|
||
|
|
setupListeners() {
|
||
|
|
this.eventBus.on(CoreEvents.EVENT_DRAG_COLUMN_CHANGE, (e) => {
|
||
|
|
const payload = e.detail;
|
||
|
|
this.handleColumnChange(payload);
|
||
|
|
});
|
||
|
|
this.eventBus.on(CoreEvents.EVENT_DRAG_MOVE, (e) => {
|
||
|
|
const payload = e.detail;
|
||
|
|
this.updateDragTimestamp(payload);
|
||
|
|
});
|
||
|
|
this.eventBus.on(CoreEvents.EVENT_UPDATED, (e) => {
|
||
|
|
const payload = e.detail;
|
||
|
|
this.handleEventUpdated(payload);
|
||
|
|
});
|
||
|
|
this.eventBus.on(CoreEvents.EVENT_DRAG_END, (e) => {
|
||
|
|
const payload = e.detail;
|
||
|
|
this.handleDragEnd(payload);
|
||
|
|
});
|
||
|
|
this.eventBus.on(CoreEvents.EVENT_DRAG_LEAVE_HEADER, (e) => {
|
||
|
|
const payload = e.detail;
|
||
|
|
this.handleDragLeaveHeader(payload);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Handle EVENT_DRAG_END - remove element if dropped in header
|
||
|
|
*/
|
||
|
|
handleDragEnd(payload) {
|
||
|
|
if (payload.target === "header") {
|
||
|
|
const element = this.container?.querySelector(`swp-content-viewport swp-event[data-event-id="${payload.swpEvent.eventId}"]`);
|
||
|
|
element?.remove();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Handle header item leaving header - create swp-event in grid
|
||
|
|
*/
|
||
|
|
handleDragLeaveHeader(payload) {
|
||
|
|
if (payload.source !== "header")
|
||
|
|
return;
|
||
|
|
if (!payload.targetColumn || !payload.start || !payload.end)
|
||
|
|
return;
|
||
|
|
if (payload.element) {
|
||
|
|
payload.element.classList.add("drag-ghost");
|
||
|
|
payload.element.style.opacity = "0.3";
|
||
|
|
payload.element.style.pointerEvents = "none";
|
||
|
|
}
|
||
|
|
const event = {
|
||
|
|
id: payload.eventId,
|
||
|
|
title: payload.title || "",
|
||
|
|
description: "",
|
||
|
|
start: payload.start,
|
||
|
|
end: payload.end,
|
||
|
|
type: "customer",
|
||
|
|
allDay: false,
|
||
|
|
syncStatus: "pending"
|
||
|
|
};
|
||
|
|
const element = this.createEventElement(event);
|
||
|
|
let eventsLayer = payload.targetColumn.querySelector("swp-events-layer");
|
||
|
|
if (!eventsLayer) {
|
||
|
|
eventsLayer = document.createElement("swp-events-layer");
|
||
|
|
payload.targetColumn.appendChild(eventsLayer);
|
||
|
|
}
|
||
|
|
eventsLayer.appendChild(element);
|
||
|
|
element.classList.add("dragging");
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Handle EVENT_UPDATED - re-render affected columns
|
||
|
|
*/
|
||
|
|
async handleEventUpdated(payload) {
|
||
|
|
if (payload.sourceColumnKey !== payload.targetColumnKey) {
|
||
|
|
await this.rerenderColumn(payload.sourceColumnKey);
|
||
|
|
}
|
||
|
|
await this.rerenderColumn(payload.targetColumnKey);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Re-render a single column with fresh data from IndexedDB
|
||
|
|
*/
|
||
|
|
async rerenderColumn(columnKey) {
|
||
|
|
const column = this.findColumn(columnKey);
|
||
|
|
if (!column)
|
||
|
|
return;
|
||
|
|
const date = column.dataset.date;
|
||
|
|
const resourceId = column.dataset.resourceId;
|
||
|
|
if (!date)
|
||
|
|
return;
|
||
|
|
const startDate = new Date(date);
|
||
|
|
const endDate = new Date(date);
|
||
|
|
endDate.setHours(23, 59, 59, 999);
|
||
|
|
const events = resourceId ? await this.eventService.getByResourceAndDateRange(resourceId, startDate, endDate) : await this.eventService.getByDateRange(startDate, endDate);
|
||
|
|
const timedEvents = events.filter(
|
||
|
|
(event) => !event.allDay && this.dateService.getDateKey(event.start) === date
|
||
|
|
);
|
||
|
|
let eventsLayer = column.querySelector("swp-events-layer");
|
||
|
|
if (!eventsLayer) {
|
||
|
|
eventsLayer = document.createElement("swp-events-layer");
|
||
|
|
column.appendChild(eventsLayer);
|
||
|
|
}
|
||
|
|
eventsLayer.innerHTML = "";
|
||
|
|
const layout = calculateColumnLayout(timedEvents, this.gridConfig);
|
||
|
|
layout.grids.forEach((grid) => {
|
||
|
|
const groupEl = this.renderGridGroup(grid);
|
||
|
|
eventsLayer.appendChild(groupEl);
|
||
|
|
});
|
||
|
|
layout.stacked.forEach((item) => {
|
||
|
|
const eventEl = this.renderStackedEvent(item.event, item.stackLevel);
|
||
|
|
eventsLayer.appendChild(eventEl);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Find a column element by columnKey
|
||
|
|
*/
|
||
|
|
findColumn(columnKey) {
|
||
|
|
if (!this.container)
|
||
|
|
return null;
|
||
|
|
return this.container.querySelector(`swp-day-column[data-column-key="${columnKey}"]`);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Handle event moving to a new column during drag
|
||
|
|
*/
|
||
|
|
handleColumnChange(payload) {
|
||
|
|
const eventsLayer = payload.newColumn.querySelector("swp-events-layer");
|
||
|
|
if (!eventsLayer)
|
||
|
|
return;
|
||
|
|
eventsLayer.appendChild(payload.element);
|
||
|
|
payload.element.style.top = `${payload.currentY}px`;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Update timestamp display during drag (snapped to grid)
|
||
|
|
*/
|
||
|
|
updateDragTimestamp(payload) {
|
||
|
|
const timeEl = payload.element.querySelector("swp-event-time");
|
||
|
|
if (!timeEl)
|
||
|
|
return;
|
||
|
|
const snappedY = snapToGrid(payload.currentY, this.gridConfig);
|
||
|
|
const minutesFromGridStart = pixelsToMinutes(snappedY, this.gridConfig);
|
||
|
|
const startMinutes = this.gridConfig.dayStartHour * 60 + minutesFromGridStart;
|
||
|
|
const height = parseFloat(payload.element.style.height) || this.gridConfig.hourHeight;
|
||
|
|
const durationMinutes = pixelsToMinutes(height, this.gridConfig);
|
||
|
|
const start = this.minutesToDate(startMinutes);
|
||
|
|
const end = this.minutesToDate(startMinutes + durationMinutes);
|
||
|
|
timeEl.textContent = this.dateService.formatTimeRange(start, end);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Convert minutes since midnight to a Date object (today)
|
||
|
|
*/
|
||
|
|
minutesToDate(minutes) {
|
||
|
|
const date = /* @__PURE__ */ new Date();
|
||
|
|
date.setHours(Math.floor(minutes / 60) % 24, minutes % 60, 0, 0);
|
||
|
|
return date;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Render events for visible dates into day columns
|
||
|
|
* @param container - Calendar container element
|
||
|
|
* @param filter - Filter with 'date' and optionally 'resource' arrays
|
||
|
|
* @param filterTemplate - Template for matching events to columns
|
||
|
|
*/
|
||
|
|
async render(container, filter, filterTemplate) {
|
||
|
|
this.container = container;
|
||
|
|
const visibleDates = filter["date"] || [];
|
||
|
|
if (visibleDates.length === 0)
|
||
|
|
return;
|
||
|
|
const startDate = new Date(visibleDates[0]);
|
||
|
|
const endDate = new Date(visibleDates[visibleDates.length - 1]);
|
||
|
|
endDate.setHours(23, 59, 59, 999);
|
||
|
|
const events = await this.eventService.getByDateRange(startDate, endDate);
|
||
|
|
const dayColumns = container.querySelector("swp-day-columns");
|
||
|
|
if (!dayColumns)
|
||
|
|
return;
|
||
|
|
const columns = dayColumns.querySelectorAll("swp-day-column");
|
||
|
|
columns.forEach((column) => {
|
||
|
|
const columnEl = column;
|
||
|
|
const columnEvents = events.filter((event) => filterTemplate.matches(event, columnEl));
|
||
|
|
let eventsLayer = column.querySelector("swp-events-layer");
|
||
|
|
if (!eventsLayer) {
|
||
|
|
eventsLayer = document.createElement("swp-events-layer");
|
||
|
|
column.appendChild(eventsLayer);
|
||
|
|
}
|
||
|
|
eventsLayer.innerHTML = "";
|
||
|
|
const timedEvents = columnEvents.filter((event) => !event.allDay);
|
||
|
|
const layout = calculateColumnLayout(timedEvents, this.gridConfig);
|
||
|
|
layout.grids.forEach((grid) => {
|
||
|
|
const groupEl = this.renderGridGroup(grid);
|
||
|
|
eventsLayer.appendChild(groupEl);
|
||
|
|
});
|
||
|
|
layout.stacked.forEach((item) => {
|
||
|
|
const eventEl = this.renderStackedEvent(item.event, item.stackLevel);
|
||
|
|
eventsLayer.appendChild(eventEl);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Create a single event element
|
||
|
|
*
|
||
|
|
* CLEAN approach:
|
||
|
|
* - Only data-id for lookup
|
||
|
|
* - Visible content in innerHTML only
|
||
|
|
*/
|
||
|
|
createEventElement(event) {
|
||
|
|
const element = document.createElement("swp-event");
|
||
|
|
element.dataset.eventId = event.id;
|
||
|
|
if (event.resourceId) {
|
||
|
|
element.dataset.resourceId = event.resourceId;
|
||
|
|
}
|
||
|
|
const position = calculateEventPosition(event.start, event.end, this.gridConfig);
|
||
|
|
element.style.top = `${position.top}px`;
|
||
|
|
element.style.height = `${position.height}px`;
|
||
|
|
const colorClass = this.getColorClass(event);
|
||
|
|
if (colorClass) {
|
||
|
|
element.classList.add(colorClass);
|
||
|
|
}
|
||
|
|
element.innerHTML = `
|
||
|
|
<swp-event-time>${this.dateService.formatTimeRange(event.start, event.end)}</swp-event-time>
|
||
|
|
<swp-event-title>${this.escapeHtml(event.title)}</swp-event-title>
|
||
|
|
${event.description ? `<swp-event-description>${this.escapeHtml(event.description)}</swp-event-description>` : ""}
|
||
|
|
`;
|
||
|
|
return element;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get color class based on metadata.color or event type
|
||
|
|
*/
|
||
|
|
getColorClass(event) {
|
||
|
|
if (event.metadata?.color) {
|
||
|
|
return `is-${event.metadata.color}`;
|
||
|
|
}
|
||
|
|
const typeColors = {
|
||
|
|
"customer": "is-blue",
|
||
|
|
"vacation": "is-green",
|
||
|
|
"break": "is-amber",
|
||
|
|
"meeting": "is-purple",
|
||
|
|
"blocked": "is-red"
|
||
|
|
};
|
||
|
|
return typeColors[event.type] || "is-blue";
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Escape HTML to prevent XSS
|
||
|
|
*/
|
||
|
|
escapeHtml(text) {
|
||
|
|
const div = document.createElement("div");
|
||
|
|
div.textContent = text;
|
||
|
|
return div.innerHTML;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Render a GRID group with side-by-side columns
|
||
|
|
* Used when multiple events start at the same time
|
||
|
|
*/
|
||
|
|
renderGridGroup(layout) {
|
||
|
|
const group = document.createElement("swp-event-group");
|
||
|
|
group.classList.add(`cols-${layout.columns.length}`);
|
||
|
|
group.style.top = `${layout.position.top}px`;
|
||
|
|
if (layout.stackLevel > 0) {
|
||
|
|
group.style.marginLeft = `${layout.stackLevel * 15}px`;
|
||
|
|
group.style.zIndex = `${100 + layout.stackLevel}`;
|
||
|
|
}
|
||
|
|
let maxBottom = 0;
|
||
|
|
for (const event of layout.events) {
|
||
|
|
const pos = calculateEventPosition(event.start, event.end, this.gridConfig);
|
||
|
|
const eventBottom = pos.top + pos.height;
|
||
|
|
if (eventBottom > maxBottom)
|
||
|
|
maxBottom = eventBottom;
|
||
|
|
}
|
||
|
|
const groupHeight = maxBottom - layout.position.top;
|
||
|
|
group.style.height = `${groupHeight}px`;
|
||
|
|
layout.columns.forEach((columnEvents) => {
|
||
|
|
const wrapper = document.createElement("div");
|
||
|
|
wrapper.style.position = "relative";
|
||
|
|
columnEvents.forEach((event) => {
|
||
|
|
const eventEl = this.createEventElement(event);
|
||
|
|
const pos = calculateEventPosition(event.start, event.end, this.gridConfig);
|
||
|
|
eventEl.style.top = `${pos.top - layout.position.top}px`;
|
||
|
|
eventEl.style.position = "absolute";
|
||
|
|
eventEl.style.left = "0";
|
||
|
|
eventEl.style.right = "0";
|
||
|
|
wrapper.appendChild(eventEl);
|
||
|
|
});
|
||
|
|
group.appendChild(wrapper);
|
||
|
|
});
|
||
|
|
return group;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Render a STACKED event with margin-left offset
|
||
|
|
* Used for overlapping events that don't start at the same time
|
||
|
|
*/
|
||
|
|
renderStackedEvent(event, stackLevel) {
|
||
|
|
const element = this.createEventElement(event);
|
||
|
|
element.dataset.stackLink = JSON.stringify({ stackLevel });
|
||
|
|
if (stackLevel > 0) {
|
||
|
|
element.style.marginLeft = `${stackLevel * 15}px`;
|
||
|
|
element.style.zIndex = `${100 + stackLevel}`;
|
||
|
|
}
|
||
|
|
return element;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
__name(_EventRenderer, "EventRenderer");
|
||
|
|
var EventRenderer = _EventRenderer;
|
||
|
|
|
||
|
|
// src/v2/core/BaseGroupingRenderer.ts
|
||
|
|
var _BaseGroupingRenderer = class _BaseGroupingRenderer {
|
||
|
|
/**
|
||
|
|
* Main render method - handles common logic
|
||
|
|
*/
|
||
|
|
async render(context) {
|
||
|
|
const allowedIds = context.filter[this.type] || [];
|
||
|
|
if (allowedIds.length === 0)
|
||
|
|
return;
|
||
|
|
const entities = await this.getEntities(allowedIds);
|
||
|
|
const dateCount = context.filter["date"]?.length || 1;
|
||
|
|
const childIds = context.childType ? context.filter[context.childType] || [] : [];
|
||
|
|
for (const entity of entities) {
|
||
|
|
const entityChildIds = context.parentChildMap?.[entity.id] || [];
|
||
|
|
const childCount = entityChildIds.filter((id) => childIds.includes(id)).length;
|
||
|
|
const colspan = childCount * dateCount;
|
||
|
|
const header = document.createElement(this.config.elementTag);
|
||
|
|
header.dataset[this.config.idAttribute] = entity.id;
|
||
|
|
header.style.setProperty(this.config.colspanVar, String(colspan));
|
||
|
|
this.renderHeader(entity, header, context);
|
||
|
|
context.headerContainer.appendChild(header);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Override this method for custom header rendering
|
||
|
|
* Default: just sets textContent to display name
|
||
|
|
*/
|
||
|
|
renderHeader(entity, header, _context) {
|
||
|
|
header.textContent = this.getDisplayName(entity);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Helper to render a single entity header.
|
||
|
|
* Can be used by subclasses that override render() but want consistent header creation.
|
||
|
|
*/
|
||
|
|
createHeader(entity, context) {
|
||
|
|
const header = document.createElement(this.config.elementTag);
|
||
|
|
header.dataset[this.config.idAttribute] = entity.id;
|
||
|
|
this.renderHeader(entity, header, context);
|
||
|
|
return header;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
__name(_BaseGroupingRenderer, "BaseGroupingRenderer");
|
||
|
|
var BaseGroupingRenderer = _BaseGroupingRenderer;
|
||
|
|
|
||
|
|
// src/v2/features/resource/ResourceRenderer.ts
|
||
|
|
var _ResourceRenderer = class _ResourceRenderer extends BaseGroupingRenderer {
|
||
|
|
constructor(resourceService) {
|
||
|
|
super();
|
||
|
|
this.resourceService = resourceService;
|
||
|
|
this.type = "resource";
|
||
|
|
this.config = {
|
||
|
|
elementTag: "swp-resource-header",
|
||
|
|
idAttribute: "resourceId",
|
||
|
|
colspanVar: "--resource-cols"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
getEntities(ids) {
|
||
|
|
return this.resourceService.getByIds(ids);
|
||
|
|
}
|
||
|
|
getDisplayName(entity) {
|
||
|
|
return entity.displayName;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Override render to handle:
|
||
|
|
* 1. Special ordering when parentChildMap exists (resources grouped by parent)
|
||
|
|
* 2. Different colspan calculation (just dateCount, not childCount * dateCount)
|
||
|
|
*/
|
||
|
|
async render(context) {
|
||
|
|
const resourceIds = context.filter["resource"] || [];
|
||
|
|
const dateCount = context.filter["date"]?.length || 1;
|
||
|
|
let orderedResourceIds;
|
||
|
|
if (context.parentChildMap) {
|
||
|
|
orderedResourceIds = [];
|
||
|
|
for (const childIds of Object.values(context.parentChildMap)) {
|
||
|
|
for (const childId of childIds) {
|
||
|
|
if (resourceIds.includes(childId)) {
|
||
|
|
orderedResourceIds.push(childId);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
orderedResourceIds = resourceIds;
|
||
|
|
}
|
||
|
|
const resources = await this.getEntities(orderedResourceIds);
|
||
|
|
const resourceMap = new Map(resources.map((r) => [r.id, r]));
|
||
|
|
for (const resourceId of orderedResourceIds) {
|
||
|
|
const resource = resourceMap.get(resourceId);
|
||
|
|
if (!resource)
|
||
|
|
continue;
|
||
|
|
const header = this.createHeader(resource, context);
|
||
|
|
header.style.gridColumn = `span ${dateCount}`;
|
||
|
|
context.headerContainer.appendChild(header);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
__name(_ResourceRenderer, "ResourceRenderer");
|
||
|
|
var ResourceRenderer = _ResourceRenderer;
|
||
|
|
|
||
|
|
// src/v2/features/team/TeamRenderer.ts
|
||
|
|
var _TeamRenderer = class _TeamRenderer extends BaseGroupingRenderer {
|
||
|
|
constructor(teamService) {
|
||
|
|
super();
|
||
|
|
this.teamService = teamService;
|
||
|
|
this.type = "team";
|
||
|
|
this.config = {
|
||
|
|
elementTag: "swp-team-header",
|
||
|
|
idAttribute: "teamId",
|
||
|
|
colspanVar: "--team-cols"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
getEntities(ids) {
|
||
|
|
return this.teamService.getByIds(ids);
|
||
|
|
}
|
||
|
|
getDisplayName(entity) {
|
||
|
|
return entity.name;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
__name(_TeamRenderer, "TeamRenderer");
|
||
|
|
var TeamRenderer = _TeamRenderer;
|
||
|
|
|
||
|
|
// src/v2/features/timeaxis/TimeAxisRenderer.ts
|
||
|
|
var _TimeAxisRenderer = class _TimeAxisRenderer {
|
||
|
|
render(container, startHour = 6, endHour = 20) {
|
||
|
|
container.innerHTML = "";
|
||
|
|
for (let hour = startHour; hour <= endHour; hour++) {
|
||
|
|
const marker = document.createElement("swp-hour-marker");
|
||
|
|
marker.textContent = `${hour.toString().padStart(2, "0")}:00`;
|
||
|
|
container.appendChild(marker);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
__name(_TimeAxisRenderer, "TimeAxisRenderer");
|
||
|
|
var TimeAxisRenderer = _TimeAxisRenderer;
|
||
|
|
export {
|
||
|
|
CalendarOrchestrator,
|
||
|
|
DateRenderer,
|
||
|
|
DateService,
|
||
|
|
EventRenderer,
|
||
|
|
NavigationAnimator,
|
||
|
|
ResourceRenderer,
|
||
|
|
TeamRenderer,
|
||
|
|
TimeAxisRenderer,
|
||
|
|
buildPipeline
|
||
|
|
};
|
||
|
|
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vbm9kZV9tb2R1bGVzL2RheWpzL2RheWpzLm1pbi5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvZGF5anMvcGx1Z2luL3V0Yy5qcyIsICIuLi8uLi9ub2RlX21vZHVsZXMvZGF5anMvcGx1Z2luL3RpbWV6b25lLmpzIiwgIi4uLy4uL25vZGVfbW9kdWxlcy9kYXlqcy9wbHVnaW4vaXNvV2Vlay5qcyIsICIuLi8uLi9zcmMvdjIvY29yZS9SZW5kZXJCdWlsZGVyLnRzIiwgIi4uLy4uL3NyYy92Mi9jb3JlL0ZpbHRlclRlbXBsYXRlLnRzIiwgIi4uLy4uL3NyYy92Mi9jb3JlL0NhbGVuZGFyT3JjaGVzdHJhdG9yLnRzIiwgIi4uLy4uL3NyYy92Mi9jb3JlL05hdmlnYXRpb25BbmltYXRvci50cyIsICIuLi8uLi9zcmMvdjIvZmVhdHVyZXMvZGF0ZS9EYXRlUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL3YyL2NvcmUvRGF0ZVNlcnZpY2UudHMiLCAiLi4vLi4vc3JjL3YyL3V0aWxzL1Bvc2l0aW9uVXRpbHMudHMiLCAiLi4vLi4vc3JjL3YyL2NvbnN0YW50cy9Db3JlRXZlbnRzLnRzIiwgIi4uLy4uL3NyYy92Mi9mZWF0dXJlcy9ldmVudC9FdmVudExheW91dEVuZ2luZS50cyIsICIuLi8uLi9zcmMvdjIvZmVhdHVyZXMvZXZlbnQvRXZlbnRSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvdjIvY29yZS9CYXNlR3JvdXBpbmdSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvdjIvZmVhdHVyZXMvcmVzb3VyY2UvUmVzb3VyY2VSZW5kZXJlci50cyIsICIuLi8uLi9zcmMvdjIvZmVhdHVyZXMvdGVhbS9UZWFtUmVuZGVyZXIudHMiLCAiLi4vLi4vc3JjL3YyL2ZlYXR1cmVzL3RpbWVheGlzL1RpbWVBeGlzUmVuZGVyZXIudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIiFmdW5jdGlvbih0LGUpe1wib2JqZWN0XCI9PXR5cGVvZiBleHBvcnRzJiZcInVuZGVmaW5lZFwiIT10eXBlb2YgbW9kdWxlP21vZHVsZS5leHBvcnRzPWUoKTpcImZ1bmN0aW9uXCI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKGUpOih0PVwidW5kZWZpbmVkXCIhPXR5cGVvZiBnbG9iYWxUaGlzP2dsb2JhbFRoaXM6dHx8c2VsZikuZGF5anM9ZSgpfSh0aGlzLChmdW5jdGlvbigpe1widXNlIHN0cmljdFwiO3ZhciB0PTFlMyxlPTZlNCxuPTM2ZTUscj1cIm1pbGxpc2Vjb25kXCIsaT1cInNlY29uZFwiLHM9XCJtaW51dGVcIix1PVwiaG91clwiLGE9XCJkYXlcIixvPVwid2Vla1wiLGM9XCJtb250aFwiLGY9XCJxdWFydGVyXCIsaD1cInllYXJcIixkPVwiZGF0ZVwiLGw9XCJJbnZhbGlkIERhdGVcIiwkPS9eKFxcZHs0fSlbLS9dPyhcXGR7MSwyfSk/Wy0vXT8oXFxkezAsMn0pW1R0XFxzXSooXFxkezEsMn0pPzo/KFxcZHsxLDJ9KT86PyhcXGR7MSwyfSk/Wy46XT8oXFxkKyk/JC8seT0vXFxbKFteXFxdXSspXXxZezEsNH18TXsxLDR9fER7MSwyfXxkezEsNH18SHsxLDJ9fGh7MSwyfXxhfEF8bXsxLDJ9fHN7MSwyfXxaezEsMn18U1NTL2csTT17bmFtZTpcImVuXCIsd2Vla2RheXM6XCJTdW5kYXlfTW9uZGF5X1R1ZXNkYXlfV2VkbmVzZGF5X1RodXJzZGF5X0ZyaWRheV9TYXR1cmRheVwiLnNwbGl0KFwiX1wiKSxtb250aHM6XCJKYW51YXJ5X0ZlYnJ1YXJ5X01hcmNoX0FwcmlsX01heV9KdW5lX0p1bHlfQXVndXN0X1NlcHRlbWJlcl9PY3RvYmVyX05vdmVtYmVyX0RlY2VtYmVyXCIuc3BsaXQoXCJfXCIpLG9yZGluYWw6ZnVuY3Rpb24odCl7dmFyIGU9W1widGhcIixcInN0XCIsXCJuZFwiLFwicmRcIl0sbj10JTEwMDtyZXR1cm5cIltcIit0KyhlWyhuLTIwKSUxMF18fGVbbl18fGVbMF0pK1wiXVwifX0sbT1mdW5jdGlvbih0LGUsbil7dmFyIHI9U3RyaW5nKHQpO3JldHVybiFyfHxyLmxlbmd0aD49ZT90OlwiXCIrQXJyYXkoZSsxLXIubGVuZ3RoKS5qb2luKG4pK3R9LHY9e3M6bSx6OmZ1bmN0aW9uKHQpe3ZhciBlPS10LnV0Y09mZnNldCgpLG49TWF0aC5hYnMoZSkscj1NYXRoLmZsb29yKG4vNjApLGk9biU2MDtyZXR1cm4oZTw9MD9cIitcIjpcIi1cIikrbShyLDIsXCIwXCIpK1wiOlwiK20oaSwyLFwiMFwiKX0sbTpmdW5jdGlvbiB0KGUsbil7aWYoZS5kYXRlKCk8bi5kYXRlKCkpcmV0dXJuLXQobixlKTt2YXIgcj0xMioobi55ZWFyKCktZS55ZWFyKCkpKyhuLm1vbnRoKCktZS5tb250aCgpKSxpPWUuY2xvbmUoKS5hZGQocixjKSxzPW4taTwwLHU9ZS5jbG9uZSgpLmFkZChyKyhzPy0xOjEpLGMpO3JldHVybisoLShyKyhuLWkpLyhzP2ktdTp1LWkpKXx8MCl9LGE6ZnVuY3Rpb24odCl7cmV0dXJuIHQ8MD9NYXRoLmNlaWwodCl8fDA6TWF0aC5mbG9vcih0KX0scDpmdW5jdGlvbih0KXtyZXR1cm57TTpjLHk6aCx3Om8sZDphLEQ6ZCxoOnUsbTpzLHM6aSxtczpyLFE6Zn1bdF18fFN0cmluZyh0fHxcIlwiKS50b0xvd2VyQ2FzZSgpLnJlcGxhY2UoL3MkLyxcIlwiKX0sdTpmdW5jdGlvbih0KXtyZXR1cm4gdm9pZCAwPT09dH19LGc9XCJlblwiLEQ9e307RFtnXT1NO3ZhciBwPVwiJGlzRGF5anNPYmplY3RcIixTPWZ1bmN0aW9uKHQpe3JldHVybiB0IGluc3RhbmNlb2YgX3x8ISghdHx8IXRbcF0pfSx3PWZ1bmN0aW9uIHQoZSxuLHIpe3ZhciBpO2lmKCFlKXJldHVybiBnO2lmKFwic3RyaW5nXCI9PXR5cGVvZiBlKXt2YXIgcz1lLnRvTG93ZXJDYXNlKCk7RFtzXSYmKGk9cyksbiYmKERbc109bixpPXMpO3ZhciB1PWUuc3BsaXQoXCItXCIpO2lmKCFpJiZ1Lmxlbmd0aD4xKXJldHVybiB0KHVbMF0pfWVsc2V7dmFyIGE9ZS5uYW1lO0RbYV09ZSxpPWF9cmV0dXJuIXImJmkmJihnPWkpLGl8fCFyJiZnfSxPPWZ1bmN0aW9uKHQsZSl7aWYoUyh0KSlyZXR1cm4gdC5jbG9uZSgpO3ZhciBuPVwib2JqZWN0XCI9PXR5cGVvZiBlP2U6e307cmV0dXJuIG4uZGF0ZT10LG4uYXJncz1hcmd1bWVudHMsbmV3IF8obil9LGI9djtiLmw9dyxiLmk9UyxiLnc9ZnVuY3Rpb24odCxlKXtyZXR1cm4gTyh0LHtsb2NhbGU6ZS4kTCx1dGM6ZS4kdSx4OmUuJHgsJG9mZnNldDplLiRvZmZzZXR9KX07dmFyIF89ZnVuY3Rpb24oKXtmdW5jdGlvbiBNKHQpe3RoaXMuJEw9dyh0LmxvY2FsZSxudWxsLCEwKSx0aGlzLnBhcnNlKHQpLHRoaXMuJHg9d
|