@schukai/monster 3.112.2 → 3.112.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/package.json +1 -1
- package/source/components/form/style/context-help.pcss +1 -1
- package/source/components/form/stylesheet/context-help.mjs +1 -1
- package/source/components/layout/popper.mjs +1 -1
- package/source/components/layout/stylesheet/panel.mjs +13 -6
- package/source/components/navigation/stylesheet/table-of-content.mjs +13 -6
- package/source/components/navigation/table-of-content.mjs +4 -5
- package/source/components/time/day.mjs +407 -0
- package/source/components/time/month-calendar.mjs +258 -54
- package/source/components/time/style/day.pcss +159 -0
- package/source/components/time/style/month-calendar.pcss +24 -14
- package/source/components/time/stylesheet/day.mjs +31 -0
- package/source/components/time/stylesheet/month-calendar.mjs +1 -1
- package/source/components/time/timeline/appointment.mjs +147 -0
- package/source/components/time/timeline/segment.mjs +7 -15
- package/source/components/time/timeline/style/appointment.pcss +25 -0
- package/source/components/time/timeline/style/segment.pcss +10 -1
- package/source/components/time/timeline/stylesheet/appointment.mjs +38 -0
- package/source/components/time/timeline/stylesheet/segment.mjs +1 -1
- package/source/data/pipe.mjs +1 -1
- package/source/dom/locale.mjs +3 -2
- package/source/i18n/internal.mjs +4 -0
- package/source/monster.mjs +1 -1
- package/source/types/version.mjs +1 -1
- package/test/cases/monster.mjs +1 -1
- package/test/web/test.html +2 -2
- package/test/web/tests.js +5 -5
- package/source/components/time/timeline/collection.mjs +0 -218
- package/source/components/time/timeline/item.mjs +0 -192
@@ -1,218 +0,0 @@
|
|
1
|
-
// Constants for possible appointment types
|
2
|
-
import { ID } from "../../../types/id.mjs";
|
3
|
-
import { isArray } from "../../../types/is.mjs";
|
4
|
-
|
5
|
-
/**
|
6
|
-
* Helper function: Check if two dates are on the same day (ignoring time)
|
7
|
-
* @private
|
8
|
-
* @param {string|Date} date1 - First date
|
9
|
-
* @param {string|Date} date2 - Second date
|
10
|
-
* @returns {boolean} True if the dates are on the same day, false otherwise
|
11
|
-
*/
|
12
|
-
function isSameDay(date1, date2) {
|
13
|
-
const d1 = new Date(date1);
|
14
|
-
const d2 = new Date(date2);
|
15
|
-
return (
|
16
|
-
d1.getFullYear() === d2.getFullYear() &&
|
17
|
-
d1.getMonth() === d2.getMonth() &&
|
18
|
-
d1.getDate() === d2.getDate()
|
19
|
-
);
|
20
|
-
}
|
21
|
-
|
22
|
-
class AppointmentCollection {
|
23
|
-
constructor() {
|
24
|
-
this.appointments = [];
|
25
|
-
}
|
26
|
-
|
27
|
-
// Add an appointment to the collection
|
28
|
-
addAppointment(appointment) {
|
29
|
-
if (!(appointment instanceof TimelineItem)) {
|
30
|
-
throw new Error("Only instances of Appointment can be added");
|
31
|
-
}
|
32
|
-
this.appointments.push(appointment);
|
33
|
-
}
|
34
|
-
|
35
|
-
// Get all appointments that overlap with the given date range
|
36
|
-
getAppointmentsInRange(rangeStart, rangeEnd) {
|
37
|
-
const start = new Date(rangeStart);
|
38
|
-
const end = new Date(rangeEnd);
|
39
|
-
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
|
40
|
-
throw new Error("Invalid date range");
|
41
|
-
}
|
42
|
-
return this.appointments.filter(
|
43
|
-
(app) => app.endDate >= start && app.startDate <= end,
|
44
|
-
);
|
45
|
-
}
|
46
|
-
|
47
|
-
// Get appointments that start on a specific day
|
48
|
-
getAppointmentsStartingOn(date) {
|
49
|
-
return this.appointments.filter((app) => isSameDay(app.startDate, date));
|
50
|
-
}
|
51
|
-
|
52
|
-
// Get appointments that end on a specific day
|
53
|
-
getAppointmentsEndingOn(date) {
|
54
|
-
return this.appointments.filter((app) => isSameDay(app.endDate, date));
|
55
|
-
}
|
56
|
-
|
57
|
-
// Get appointments active on a specific day (the day falls between startDate and endDate, inclusive)
|
58
|
-
getAppointmentsForDay(date) {
|
59
|
-
const d = new Date(date);
|
60
|
-
return this.appointments.filter(
|
61
|
-
(app) => app.startDate <= d && app.endDate >= d,
|
62
|
-
);
|
63
|
-
}
|
64
|
-
|
65
|
-
// Get appointments where the specified date is strictly between the start and end dates
|
66
|
-
getAppointmentsWithDateAsMiddleDay(date) {
|
67
|
-
const d = new Date(date);
|
68
|
-
return this.appointments.filter(
|
69
|
-
(app) =>
|
70
|
-
app.startDate < d &&
|
71
|
-
app.endDate > d &&
|
72
|
-
!isSameDay(app.startDate, d) &&
|
73
|
-
!isSameDay(app.endDate, d),
|
74
|
-
);
|
75
|
-
}
|
76
|
-
|
77
|
-
// Get appointments filtered by type (e.g., all milestones)
|
78
|
-
getAppointmentsByType(type) {
|
79
|
-
return this.appointments.filter((app) => app.type === type);
|
80
|
-
}
|
81
|
-
|
82
|
-
// Export the entire collection as JSON
|
83
|
-
toJSON() {
|
84
|
-
return this.appointments.map((app) => app.toJSON());
|
85
|
-
}
|
86
|
-
|
87
|
-
/**
|
88
|
-
* Splits an appointment into slices of a specified duration (in days).
|
89
|
-
* If the appointment is provided as an ID, it will be looked up in the collection.
|
90
|
-
* Each slice is returned as an object with the original appointment's properties,
|
91
|
-
* plus sliceIndex and the new startDate/endDate for the slice.
|
92
|
-
*
|
93
|
-
* @param {TimelineItem|string} appointmentOrId - The appointment instance or its ID.
|
94
|
-
* @param {number} sliceDurationDays - Duration of each slice in days.
|
95
|
-
* @returns {Array} Array of slice objects.
|
96
|
-
*/
|
97
|
-
splitAppointment(appointmentOrId, sliceDurationDays) {
|
98
|
-
if (typeof sliceDurationDays !== "number" || sliceDurationDays <= 0) {
|
99
|
-
throw new Error("Slice duration must be a positive number");
|
100
|
-
}
|
101
|
-
|
102
|
-
let appointment;
|
103
|
-
if (appointmentOrId instanceof TimelineItem) {
|
104
|
-
appointment = appointmentOrId;
|
105
|
-
} else {
|
106
|
-
appointment = this.appointments.find((app) => app.id === appointmentOrId);
|
107
|
-
if (!appointment) {
|
108
|
-
throw new Error("Appointment not found");
|
109
|
-
}
|
110
|
-
}
|
111
|
-
|
112
|
-
const slices = [];
|
113
|
-
let currentStart = new Date(appointment.startDate);
|
114
|
-
let sliceIndex = 0;
|
115
|
-
|
116
|
-
while (currentStart < appointment.endDate) {
|
117
|
-
const currentEnd = new Date(currentStart);
|
118
|
-
currentEnd.setDate(currentEnd.getDate() + sliceDurationDays);
|
119
|
-
if (currentEnd > appointment.endDate) {
|
120
|
-
currentEnd.setTime(appointment.endDate.getTime());
|
121
|
-
}
|
122
|
-
|
123
|
-
const slice = {
|
124
|
-
parentId: appointment.id,
|
125
|
-
sliceIndex: sliceIndex,
|
126
|
-
title: appointment.title,
|
127
|
-
type: appointment.type,
|
128
|
-
description: appointment.description,
|
129
|
-
userIds: appointment.userIds,
|
130
|
-
startDate: currentStart.toISOString(),
|
131
|
-
endDate: currentEnd.toISOString(),
|
132
|
-
};
|
133
|
-
slices.push(slice);
|
134
|
-
|
135
|
-
// Prepare for next slice
|
136
|
-
currentStart = currentEnd;
|
137
|
-
sliceIndex++;
|
138
|
-
}
|
139
|
-
|
140
|
-
return slices;
|
141
|
-
}
|
142
|
-
}
|
143
|
-
|
144
|
-
// Example usage
|
145
|
-
try {
|
146
|
-
// Create some appointments with user associations
|
147
|
-
const appointment1 = new TimelineItem({
|
148
|
-
title: "Team Meeting",
|
149
|
-
type: AppointmentType.TASK,
|
150
|
-
startDate: "2025-03-05T09:00:00",
|
151
|
-
endDate: "2025-03-05T10:00:00",
|
152
|
-
description: "Weekly team meeting",
|
153
|
-
userIds: ["user1", "user2"],
|
154
|
-
});
|
155
|
-
|
156
|
-
const appointment2 = new TimelineItem({
|
157
|
-
title: "Project Kickoff",
|
158
|
-
type: AppointmentType.MILESTONE,
|
159
|
-
startDate: "2025-03-06T08:00:00",
|
160
|
-
endDate: "2025-03-06T12:00:00",
|
161
|
-
description: "Kickoff for the new project",
|
162
|
-
userIds: "user3", // single user id; will be converted to an array
|
163
|
-
});
|
164
|
-
|
165
|
-
// An appointment spanning over 30 days
|
166
|
-
const appointment3 = new TimelineItem({
|
167
|
-
title: "Long Term Project",
|
168
|
-
type: AppointmentType.TASK,
|
169
|
-
startDate: "2025-04-01T09:00:00",
|
170
|
-
endDate: "2025-05-15T17:00:00",
|
171
|
-
description: "Project spanning multiple weeks",
|
172
|
-
userIds: ["user4"],
|
173
|
-
});
|
174
|
-
|
175
|
-
// Create an appointment collection and add appointments
|
176
|
-
const collection = new AppointmentCollection();
|
177
|
-
collection.addAppointment(appointment1);
|
178
|
-
collection.addAppointment(appointment2);
|
179
|
-
collection.addAppointment(appointment3);
|
180
|
-
|
181
|
-
console.log("Appointments on 2025-03-05:");
|
182
|
-
collection
|
183
|
-
.getAppointmentsForDay("2025-03-05")
|
184
|
-
.forEach((app) => console.log(app.toString()));
|
185
|
-
|
186
|
-
console.log("\nAppointments in range 2025-03-05 to 2025-03-06:");
|
187
|
-
collection
|
188
|
-
.getAppointmentsInRange("2025-03-05T00:00:00", "2025-03-06T23:59:59")
|
189
|
-
.forEach((app) => console.log(app.toString()));
|
190
|
-
|
191
|
-
console.log("\nAll milestones:");
|
192
|
-
collection
|
193
|
-
.getAppointmentsByType(AppointmentType.MILESTONE)
|
194
|
-
.forEach((app) => console.log(app.toString()));
|
195
|
-
|
196
|
-
// Split appointment3 into weekly slices (7-day slices)
|
197
|
-
console.log("\nWeekly slices for Long Term Project:");
|
198
|
-
const weeklySlices = collection.splitAppointment(appointment3, 7);
|
199
|
-
weeklySlices.forEach((slice) => {
|
200
|
-
console.log(
|
201
|
-
`Slice ${slice.sliceIndex}: ${slice.startDate} to ${slice.endDate}`,
|
202
|
-
);
|
203
|
-
});
|
204
|
-
|
205
|
-
// Split appointment3 into 14-day slices
|
206
|
-
console.log("\n14-day slices for Long Term Project:");
|
207
|
-
const biweeklySlices = collection.splitAppointment(appointment3, 14);
|
208
|
-
biweeklySlices.forEach((slice) => {
|
209
|
-
console.log(
|
210
|
-
`Slice ${slice.sliceIndex}: ${slice.startDate} to ${slice.endDate}`,
|
211
|
-
);
|
212
|
-
});
|
213
|
-
|
214
|
-
console.log("\nJSON Export of the collection:");
|
215
|
-
console.log(JSON.stringify(collection.toJSON(), null, 2));
|
216
|
-
} catch (error) {
|
217
|
-
console.error("Error:", error.message);
|
218
|
-
}
|
@@ -1,192 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
|
3
|
-
* Node module: @schukai/monster
|
4
|
-
*
|
5
|
-
* This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
|
6
|
-
* The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
|
7
|
-
*
|
8
|
-
* For those who do not wish to adhere to the AGPLv3, a commercial license is available.
|
9
|
-
* Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
|
10
|
-
* For more information about purchasing a commercial license, please contact schukai GmbH.
|
11
|
-
*/
|
12
|
-
|
13
|
-
import { ID } from "../../../types/id.mjs";
|
14
|
-
import { isArray } from "../../../types/is.mjs";
|
15
|
-
import { BaseWithOptions } from "../../../types/basewithoptions.mjs";
|
16
|
-
|
17
|
-
const AppointmentType = {
|
18
|
-
TODO: "todo",
|
19
|
-
TASK: "task",
|
20
|
-
MILESTONE: "milestone",
|
21
|
-
EVENT: "event",
|
22
|
-
REMINDER: "reminder",
|
23
|
-
MEETING: "meeting",
|
24
|
-
CALL: "call",
|
25
|
-
APPOINTMENT: "appointment",
|
26
|
-
DEADLINE: "deadline",
|
27
|
-
BIRTHDAY: "birthday",
|
28
|
-
ANNIVERSARY: "anniversary",
|
29
|
-
HOLIDAY: "holiday",
|
30
|
-
VACATION: "vacation",
|
31
|
-
SICKDAY: "sickday",
|
32
|
-
OOO: "ooo",
|
33
|
-
CUSTOM: "custom",
|
34
|
-
};
|
35
|
-
|
36
|
-
class Item extends BaseWithOptions {
|
37
|
-
/**
|
38
|
-
* Creates a new Item.
|
39
|
-
*
|
40
|
-
* @param {Object} params - Parameters for creating the item.
|
41
|
-
* @param {string} [params.id] - Optional ID. If not provided, one will be generated.
|
42
|
-
* @param {string} params.title - Title of the item.
|
43
|
-
* @param {string} params.type - Type of the item (must be one of AppointmentType values).
|
44
|
-
* @param {string|Date} params.startDate - Start date/time.
|
45
|
-
* @param {string|Date} params.endDate - End date/time.
|
46
|
-
* @param {string} [params.description=""] - Optional description.
|
47
|
-
* @param {string|string[]} [params.userIds=[]] - One or more user IDs associated with this item.
|
48
|
-
* @throws {Error} If type is invalid or dates are not valid or startDate is after endDate.
|
49
|
-
*/
|
50
|
-
constructor({
|
51
|
-
id,
|
52
|
-
title,
|
53
|
-
type,
|
54
|
-
startDate,
|
55
|
-
endDate,
|
56
|
-
description = "",
|
57
|
-
userIds = [],
|
58
|
-
}) {
|
59
|
-
// Validate item type
|
60
|
-
if (!Object.values(AppointmentType).includes(type)) {
|
61
|
-
throw new Error(`Invalid appointment type: ${type}`);
|
62
|
-
}
|
63
|
-
|
64
|
-
// Convert startDate and endDate to Date objects
|
65
|
-
this.startDate = new Date(startDate);
|
66
|
-
this.endDate = new Date(endDate);
|
67
|
-
if (isNaN(this.startDate.getTime()) || isNaN(this.endDate.getTime())) {
|
68
|
-
throw new Error("Invalid start or end date");
|
69
|
-
}
|
70
|
-
// Ensure startDate is not after endDate
|
71
|
-
if (this.startDate > this.endDate) {
|
72
|
-
throw new Error("Start date cannot be after end date");
|
73
|
-
}
|
74
|
-
|
75
|
-
// Initialize fields
|
76
|
-
this.id = id || new ID().toString();
|
77
|
-
this.title = title || "new appointment";
|
78
|
-
this.type = type || AppointmentType.CUSTOM;
|
79
|
-
this.description = description || "";
|
80
|
-
|
81
|
-
// Ensure userIds is stored as an array
|
82
|
-
this.userIds = isArray(userIds) ? userIds : [userIds];
|
83
|
-
}
|
84
|
-
|
85
|
-
/**
|
86
|
-
* Calculates the duration of the item in days.
|
87
|
-
*
|
88
|
-
* @returns {number} Duration in days.
|
89
|
-
*/
|
90
|
-
getDurationInDays() {
|
91
|
-
const msPerDay = 1000 * 60 * 60 * 24;
|
92
|
-
return Math.ceil((this.endDate - this.startDate) / msPerDay);
|
93
|
-
}
|
94
|
-
|
95
|
-
/**
|
96
|
-
* Calculates the duration of the item in hours.
|
97
|
-
*
|
98
|
-
* @returns {number} Duration in hours.
|
99
|
-
*/
|
100
|
-
getDurationInHours() {
|
101
|
-
const msPerHour = 1000 * 60 * 60;
|
102
|
-
return Math.ceil((this.endDate - this.startDate) / msPerHour);
|
103
|
-
}
|
104
|
-
|
105
|
-
/**
|
106
|
-
* Calculates the duration of the item in minutes.
|
107
|
-
*
|
108
|
-
* @returns {number} Duration in minutes.
|
109
|
-
*/
|
110
|
-
getDurationInMinutes() {
|
111
|
-
const msPerMinute = 1000 * 60;
|
112
|
-
return Math.ceil((this.endDate - this.startDate) / msPerMinute);
|
113
|
-
}
|
114
|
-
|
115
|
-
/**
|
116
|
-
* Calculates the duration of the item in seconds.
|
117
|
-
*
|
118
|
-
* @returns {number} Duration in seconds.
|
119
|
-
*/
|
120
|
-
getDurationInSeconds() {
|
121
|
-
const msPerSecond = 1000;
|
122
|
-
return Math.ceil((this.endDate - this.startDate) / msPerSecond);
|
123
|
-
}
|
124
|
-
|
125
|
-
/**
|
126
|
-
* Checks if the item is active on the specified date.
|
127
|
-
*
|
128
|
-
* @param {string|Date} date - The date to check.
|
129
|
-
* @returns {boolean} True if the item is active on the given date.
|
130
|
-
* @throws {Error} If the provided date is invalid.
|
131
|
-
*/
|
132
|
-
isActiveOn(date) {
|
133
|
-
const d = new Date(date);
|
134
|
-
if (isNaN(d.getTime())) {
|
135
|
-
throw new Error("Invalid date");
|
136
|
-
}
|
137
|
-
return this.startDate <= d && this.endDate >= d;
|
138
|
-
}
|
139
|
-
|
140
|
-
/**
|
141
|
-
* Returns a JSON-compatible object representing the item.
|
142
|
-
*
|
143
|
-
* @returns {Object} JSON representation of the item.
|
144
|
-
*/
|
145
|
-
toJSON() {
|
146
|
-
return {
|
147
|
-
id: this.id,
|
148
|
-
title: this.title,
|
149
|
-
type: this.type,
|
150
|
-
description: this.description,
|
151
|
-
userIds: this.userIds,
|
152
|
-
startDate: this.startDate.toISOString(),
|
153
|
-
endDate: this.endDate.toISOString(),
|
154
|
-
};
|
155
|
-
}
|
156
|
-
|
157
|
-
/**
|
158
|
-
* Creates a new Item instance from a JSON object.
|
159
|
-
*
|
160
|
-
* @param {Object} json - The JSON object.
|
161
|
-
* @param {string} json.id - The ID of the item.
|
162
|
-
* @param {string} json.title - The title of the item.
|
163
|
-
* @param {string} json.type - The type of the item.
|
164
|
-
* @param {string} json.startDate - The start date in ISO format.
|
165
|
-
* @param {string} json.endDate - The end date in ISO format.
|
166
|
-
* @param {string} [json.description=""] - The description of the item.
|
167
|
-
* @param {string|string[]} [json.userIds=[]] - One or more user IDs.
|
168
|
-
* @returns {Item} A new Item instance.
|
169
|
-
*/
|
170
|
-
static fromJson(json) {
|
171
|
-
return new Item({
|
172
|
-
id: json.id,
|
173
|
-
title: json.title,
|
174
|
-
type: json.type,
|
175
|
-
startDate: json.startDate,
|
176
|
-
endDate: json.endDate,
|
177
|
-
description: json.description,
|
178
|
-
userIds: json.userIds,
|
179
|
-
});
|
180
|
-
}
|
181
|
-
|
182
|
-
/**
|
183
|
-
* Returns a readable string representation of the item.
|
184
|
-
*
|
185
|
-
* @returns {string} String representation of the item.
|
186
|
-
*/
|
187
|
-
toString() {
|
188
|
-
return `[${this.type}] ${this.title} (${this.startDate.toLocaleString()} - ${this.endDate.toLocaleString()})`;
|
189
|
-
}
|
190
|
-
}
|
191
|
-
|
192
|
-
export { Item, AppointmentType };
|