Codex commited on
Commit ·
cc14448
1
Parent(s): 8c3a0fd
Fix planner conflicts and compact layout
Browse files- app.py +53 -0
- static/planner.js +51 -9
- static/v020.css +47 -10
app.py
CHANGED
|
@@ -59,6 +59,7 @@ MSG_INVALID_WEEKDAY = "星期设置不正确"
|
|
| 59 |
MSG_INVALID_WEEK_RANGE = "开始周数不能晚于结束周数"
|
| 60 |
MSG_INVALID_WEEK_PATTERN = "单双周设置不正确"
|
| 61 |
MSG_INVALID_DURATION = "默认任务时长需在 30 到 240 分钟之间"
|
|
|
|
| 62 |
WEEKDAYS = [
|
| 63 |
"星期一",
|
| 64 |
"星期二",
|
|
@@ -284,6 +285,49 @@ def course_occurs_on(course: dict, current_week: int, selected_date: date) -> bo
|
|
| 284 |
return True
|
| 285 |
|
| 286 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
def build_planner_payload(selected_date: date) -> dict:
|
| 288 |
settings = store.get_schedule_settings()
|
| 289 |
semester_start = date.fromisoformat(settings["semester_start"])
|
|
@@ -612,6 +656,15 @@ def update_task_schedule(task_id: str):
|
|
| 612 |
except ValueError as exc:
|
| 613 |
return jsonify({"ok": False, "error": str(exc)}), 400
|
| 614 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 615 |
try:
|
| 616 |
task = store.schedule_task(task_id, schedule)
|
| 617 |
except KeyError:
|
|
|
|
| 59 |
MSG_INVALID_WEEK_RANGE = "开始周数不能晚于结束周数"
|
| 60 |
MSG_INVALID_WEEK_PATTERN = "单双周设置不正确"
|
| 61 |
MSG_INVALID_DURATION = "默认任务时长需在 30 到 240 分钟之间"
|
| 62 |
+
MSG_SCHEDULE_CONFLICT = "该时间段已有课程或任务,请换一个时间"
|
| 63 |
WEEKDAYS = [
|
| 64 |
"星期一",
|
| 65 |
"星期二",
|
|
|
|
| 285 |
return True
|
| 286 |
|
| 287 |
|
| 288 |
+
def schedules_overlap(
|
| 289 |
+
left_start_minutes: int,
|
| 290 |
+
left_end_minutes: int,
|
| 291 |
+
right_start_minutes: int,
|
| 292 |
+
right_end_minutes: int,
|
| 293 |
+
) -> bool:
|
| 294 |
+
return left_start_minutes < right_end_minutes and right_start_minutes < left_end_minutes
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def find_schedule_conflict(task_id: str, schedule: dict | None) -> str | None:
|
| 298 |
+
if not schedule:
|
| 299 |
+
return None
|
| 300 |
+
|
| 301 |
+
schedule_date = date.fromisoformat(schedule["date"])
|
| 302 |
+
start_minutes = parse_time_to_minutes(schedule["start_time"])
|
| 303 |
+
end_minutes = parse_time_to_minutes(schedule["end_time"])
|
| 304 |
+
|
| 305 |
+
settings = store.get_schedule_settings()
|
| 306 |
+
semester_start = date.fromisoformat(settings["semester_start"])
|
| 307 |
+
current_week = get_academic_week(schedule_date, semester_start)
|
| 308 |
+
|
| 309 |
+
for task in store.list_tasks():
|
| 310 |
+
if task["id"] == task_id:
|
| 311 |
+
continue
|
| 312 |
+
existing_schedule = task.get("schedule")
|
| 313 |
+
if not existing_schedule or existing_schedule.get("date") != schedule["date"]:
|
| 314 |
+
continue
|
| 315 |
+
existing_start = parse_time_to_minutes(existing_schedule["start_time"])
|
| 316 |
+
existing_end = parse_time_to_minutes(existing_schedule["end_time"])
|
| 317 |
+
if schedules_overlap(start_minutes, end_minutes, existing_start, existing_end):
|
| 318 |
+
return task["title"]
|
| 319 |
+
|
| 320 |
+
for course in store.list_courses():
|
| 321 |
+
if not course_occurs_on(course, current_week, schedule_date):
|
| 322 |
+
continue
|
| 323 |
+
course_start = parse_time_to_minutes(course["start_time"])
|
| 324 |
+
course_end = parse_time_to_minutes(course["end_time"])
|
| 325 |
+
if schedules_overlap(start_minutes, end_minutes, course_start, course_end):
|
| 326 |
+
return course["title"]
|
| 327 |
+
|
| 328 |
+
return None
|
| 329 |
+
|
| 330 |
+
|
| 331 |
def build_planner_payload(selected_date: date) -> dict:
|
| 332 |
settings = store.get_schedule_settings()
|
| 333 |
semester_start = date.fromisoformat(settings["semester_start"])
|
|
|
|
| 656 |
except ValueError as exc:
|
| 657 |
return jsonify({"ok": False, "error": str(exc)}), 400
|
| 658 |
|
| 659 |
+
conflict_title = find_schedule_conflict(task_id, schedule)
|
| 660 |
+
if conflict_title:
|
| 661 |
+
return jsonify(
|
| 662 |
+
{
|
| 663 |
+
"ok": False,
|
| 664 |
+
"error": f"该时间段与“{conflict_title}”冲突,请换一个时间",
|
| 665 |
+
}
|
| 666 |
+
), 400
|
| 667 |
+
|
| 668 |
try:
|
| 669 |
task = store.schedule_task(task_id, schedule)
|
| 670 |
except KeyError:
|
static/planner.js
CHANGED
|
@@ -239,6 +239,28 @@
|
|
| 239 |
return Math.max(MIN_DURATION, Number((state.planner.settings && state.planner.settings.default_task_duration_minutes) || 45));
|
| 240 |
}
|
| 241 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
function minutesToPixels(minutes) {
|
| 243 |
return Math.round(minutes * getPixelsPerMinute());
|
| 244 |
}
|
|
@@ -684,10 +706,8 @@
|
|
| 684 |
block.style.setProperty("--event-accent", item.color);
|
| 685 |
}
|
| 686 |
const courseWeekText = getCourseWeekText(item);
|
| 687 |
-
const coursePeriodText = getCoursePeriodRange(item.start_time, item.end_time);
|
| 688 |
const courseLines = [
|
| 689 |
courseWeekText,
|
| 690 |
-
coursePeriodText,
|
| 691 |
item.location || "",
|
| 692 |
`${item.start_time} - ${item.end_time}`,
|
| 693 |
].filter(Boolean);
|
|
@@ -697,7 +717,6 @@
|
|
| 697 |
<strong class="planner-course-title">${escapeHtml(item.title)}</strong>
|
| 698 |
<div class="planner-course-details">
|
| 699 |
<span class="planner-course-line">${escapeHtml(courseWeekText)}</span>
|
| 700 |
-
<span class="planner-course-line">${escapeHtml(coursePeriodText)}</span>
|
| 701 |
<span class="planner-course-line">${escapeHtml(item.location || "")}</span>
|
| 702 |
<span class="planner-course-line planner-event-time">${escapeHtml(item.start_time)} - ${escapeHtml(item.end_time)}</span>
|
| 703 |
</div>
|
|
@@ -793,6 +812,7 @@
|
|
| 793 |
}
|
| 794 |
event.preventDefault();
|
| 795 |
preview.style.display = "none";
|
|
|
|
| 796 |
if (!requireAuth()) {
|
| 797 |
state.dragTaskId = null;
|
| 798 |
return;
|
|
@@ -976,12 +996,10 @@
|
|
| 976 |
block.style.setProperty("--event-accent", item.color);
|
| 977 |
}
|
| 978 |
const courseWeekText = getCourseWeekText(item);
|
| 979 |
-
const coursePeriodText = getCoursePeriodRange(item.start_time, item.end_time);
|
| 980 |
const courseLines = [
|
| 981 |
courseWeekText,
|
| 982 |
-
coursePeriodText,
|
| 983 |
-
`${item.start_time} - ${item.end_time}`,
|
| 984 |
item.location || "",
|
|
|
|
| 985 |
].filter(Boolean);
|
| 986 |
block.title = [item.title, ...courseLines].join("\n");
|
| 987 |
block.innerHTML = `
|
|
@@ -989,9 +1007,8 @@
|
|
| 989 |
<strong class="planner-course-title">${escapeHtml(item.title)}</strong>
|
| 990 |
<div class="planner-course-details">
|
| 991 |
<span class="planner-course-line">${escapeHtml(courseWeekText)}</span>
|
| 992 |
-
<span class="planner-course-line">${escapeHtml(coursePeriodText)}</span>
|
| 993 |
-
<span class="planner-course-line planner-event-time">${escapeHtml(item.start_time)} - ${escapeHtml(item.end_time)}</span>
|
| 994 |
<span class="planner-course-line">${escapeHtml(item.location || "")}</span>
|
|
|
|
| 995 |
</div>
|
| 996 |
</div>
|
| 997 |
`;
|
|
@@ -1056,6 +1073,7 @@
|
|
| 1056 |
endMinutes: item.endMinutes,
|
| 1057 |
duration: item.endMinutes - item.startMinutes,
|
| 1058 |
pointerOffsetMinutes: point ? point.minutes - item.startMinutes : 0,
|
|
|
|
| 1059 |
};
|
| 1060 |
|
| 1061 |
if (typeof block.setPointerCapture === "function") {
|
|
@@ -1099,18 +1117,21 @@
|
|
| 1099 |
const duration = getTaskDuration(task);
|
| 1100 |
const startMinutes = clamp(point.minutes, dayStart, dayEnd - duration);
|
| 1101 |
const dayMeta = getWeekDayMeta(point.date);
|
|
|
|
| 1102 |
preview.style.display = "grid";
|
|
|
|
| 1103 |
setBlockBounds(preview, startMinutes, startMinutes + duration, dayStart);
|
| 1104 |
setBlockHorizontalBounds(preview, point.date, 0, 1);
|
| 1105 |
preview.innerHTML = `
|
| 1106 |
<strong>${escapeHtml(dayMeta ? dayMeta.short_label : "")}</strong>
|
| 1107 |
-
<span>${minutesToTime(startMinutes)} - ${minutesToTime(startMinutes + duration)}</span>
|
| 1108 |
`;
|
| 1109 |
});
|
| 1110 |
|
| 1111 |
canvasLayer.addEventListener("dragleave", (event) => {
|
| 1112 |
if (!canvasLayer.contains(event.relatedTarget)) {
|
| 1113 |
preview.style.display = "none";
|
|
|
|
| 1114 |
}
|
| 1115 |
});
|
| 1116 |
|
|
@@ -1135,6 +1156,11 @@
|
|
| 1135 |
try {
|
| 1136 |
const duration = getTaskDuration(task);
|
| 1137 |
const startMinutes = clamp(point.minutes, dayStart, dayEnd - duration);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1138 |
await requestJSON(`/api/tasks/${task.id}/schedule`, {
|
| 1139 |
method: "PATCH",
|
| 1140 |
body: JSON.stringify({
|
|
@@ -1253,6 +1279,7 @@
|
|
| 1253 |
const preview = document.getElementById("timelineDropPreview");
|
| 1254 |
if (preview) {
|
| 1255 |
preview.style.display = "none";
|
|
|
|
| 1256 |
}
|
| 1257 |
});
|
| 1258 |
|
|
@@ -1300,6 +1327,14 @@
|
|
| 1300 |
state.interaction.duration = state.interaction.endMinutes - state.interaction.startMinutes;
|
| 1301 |
}
|
| 1302 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1303 |
updateEventLayout(state.interaction.block, {
|
| 1304 |
date: state.interaction.currentDate,
|
| 1305 |
startMinutes: state.interaction.startMinutes,
|
|
@@ -1363,6 +1398,7 @@
|
|
| 1363 |
|
| 1364 |
const current = state.interaction;
|
| 1365 |
current.block.classList.remove("is-dragging");
|
|
|
|
| 1366 |
state.interaction = null;
|
| 1367 |
releaseInteractionPointer(current);
|
| 1368 |
finishPlannerInteraction();
|
|
@@ -1376,6 +1412,12 @@
|
|
| 1376 |
return;
|
| 1377 |
}
|
| 1378 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1379 |
persistWeekInteraction(current);
|
| 1380 |
}
|
| 1381 |
|
|
|
|
| 239 |
return Math.max(MIN_DURATION, Number((state.planner.settings && state.planner.settings.default_task_duration_minutes) || 45));
|
| 240 |
}
|
| 241 |
|
| 242 |
+
function rangesOverlap(leftStart, leftEnd, rightStart, rightEnd) {
|
| 243 |
+
return leftStart < rightEnd && rightStart < leftEnd;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
function hasScheduleConflict(dateIso, startMinutes, endMinutes, ignoreTaskId = null) {
|
| 247 |
+
return (state.planner.scheduled_items || []).some((item) => {
|
| 248 |
+
const itemDate = item.date || state.selectedDate;
|
| 249 |
+
if (itemDate !== dateIso) {
|
| 250 |
+
return false;
|
| 251 |
+
}
|
| 252 |
+
if (ignoreTaskId && item.kind === "task" && item.task_id === ignoreTaskId) {
|
| 253 |
+
return false;
|
| 254 |
+
}
|
| 255 |
+
return rangesOverlap(
|
| 256 |
+
startMinutes,
|
| 257 |
+
endMinutes,
|
| 258 |
+
toMinutes(item.start_time),
|
| 259 |
+
toMinutes(item.end_time)
|
| 260 |
+
);
|
| 261 |
+
});
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
function minutesToPixels(minutes) {
|
| 265 |
return Math.round(minutes * getPixelsPerMinute());
|
| 266 |
}
|
|
|
|
| 706 |
block.style.setProperty("--event-accent", item.color);
|
| 707 |
}
|
| 708 |
const courseWeekText = getCourseWeekText(item);
|
|
|
|
| 709 |
const courseLines = [
|
| 710 |
courseWeekText,
|
|
|
|
| 711 |
item.location || "",
|
| 712 |
`${item.start_time} - ${item.end_time}`,
|
| 713 |
].filter(Boolean);
|
|
|
|
| 717 |
<strong class="planner-course-title">${escapeHtml(item.title)}</strong>
|
| 718 |
<div class="planner-course-details">
|
| 719 |
<span class="planner-course-line">${escapeHtml(courseWeekText)}</span>
|
|
|
|
| 720 |
<span class="planner-course-line">${escapeHtml(item.location || "")}</span>
|
| 721 |
<span class="planner-course-line planner-event-time">${escapeHtml(item.start_time)} - ${escapeHtml(item.end_time)}</span>
|
| 722 |
</div>
|
|
|
|
| 812 |
}
|
| 813 |
event.preventDefault();
|
| 814 |
preview.style.display = "none";
|
| 815 |
+
preview.classList.remove("is-conflict");
|
| 816 |
if (!requireAuth()) {
|
| 817 |
state.dragTaskId = null;
|
| 818 |
return;
|
|
|
|
| 996 |
block.style.setProperty("--event-accent", item.color);
|
| 997 |
}
|
| 998 |
const courseWeekText = getCourseWeekText(item);
|
|
|
|
| 999 |
const courseLines = [
|
| 1000 |
courseWeekText,
|
|
|
|
|
|
|
| 1001 |
item.location || "",
|
| 1002 |
+
`${item.start_time} - ${item.end_time}`,
|
| 1003 |
].filter(Boolean);
|
| 1004 |
block.title = [item.title, ...courseLines].join("\n");
|
| 1005 |
block.innerHTML = `
|
|
|
|
| 1007 |
<strong class="planner-course-title">${escapeHtml(item.title)}</strong>
|
| 1008 |
<div class="planner-course-details">
|
| 1009 |
<span class="planner-course-line">${escapeHtml(courseWeekText)}</span>
|
|
|
|
|
|
|
| 1010 |
<span class="planner-course-line">${escapeHtml(item.location || "")}</span>
|
| 1011 |
+
<span class="planner-course-line planner-event-time">${escapeHtml(item.start_time)} - ${escapeHtml(item.end_time)}</span>
|
| 1012 |
</div>
|
| 1013 |
</div>
|
| 1014 |
`;
|
|
|
|
| 1073 |
endMinutes: item.endMinutes,
|
| 1074 |
duration: item.endMinutes - item.startMinutes,
|
| 1075 |
pointerOffsetMinutes: point ? point.minutes - item.startMinutes : 0,
|
| 1076 |
+
hasConflict: false,
|
| 1077 |
};
|
| 1078 |
|
| 1079 |
if (typeof block.setPointerCapture === "function") {
|
|
|
|
| 1117 |
const duration = getTaskDuration(task);
|
| 1118 |
const startMinutes = clamp(point.minutes, dayStart, dayEnd - duration);
|
| 1119 |
const dayMeta = getWeekDayMeta(point.date);
|
| 1120 |
+
const hasConflict = hasScheduleConflict(point.date, startMinutes, startMinutes + duration, task.id);
|
| 1121 |
preview.style.display = "grid";
|
| 1122 |
+
preview.classList.toggle("is-conflict", hasConflict);
|
| 1123 |
setBlockBounds(preview, startMinutes, startMinutes + duration, dayStart);
|
| 1124 |
setBlockHorizontalBounds(preview, point.date, 0, 1);
|
| 1125 |
preview.innerHTML = `
|
| 1126 |
<strong>${escapeHtml(dayMeta ? dayMeta.short_label : "")}</strong>
|
| 1127 |
+
<span>${hasConflict ? "时间冲突" : `${minutesToTime(startMinutes)} - ${minutesToTime(startMinutes + duration)}`}</span>
|
| 1128 |
`;
|
| 1129 |
});
|
| 1130 |
|
| 1131 |
canvasLayer.addEventListener("dragleave", (event) => {
|
| 1132 |
if (!canvasLayer.contains(event.relatedTarget)) {
|
| 1133 |
preview.style.display = "none";
|
| 1134 |
+
preview.classList.remove("is-conflict");
|
| 1135 |
}
|
| 1136 |
});
|
| 1137 |
|
|
|
|
| 1156 |
try {
|
| 1157 |
const duration = getTaskDuration(task);
|
| 1158 |
const startMinutes = clamp(point.minutes, dayStart, dayEnd - duration);
|
| 1159 |
+
if (hasScheduleConflict(point.date, startMinutes, startMinutes + duration, task.id)) {
|
| 1160 |
+
showToast("该时间段已有课程或任务,请换一个时间", "error");
|
| 1161 |
+
state.dragTaskId = null;
|
| 1162 |
+
return;
|
| 1163 |
+
}
|
| 1164 |
await requestJSON(`/api/tasks/${task.id}/schedule`, {
|
| 1165 |
method: "PATCH",
|
| 1166 |
body: JSON.stringify({
|
|
|
|
| 1279 |
const preview = document.getElementById("timelineDropPreview");
|
| 1280 |
if (preview) {
|
| 1281 |
preview.style.display = "none";
|
| 1282 |
+
preview.classList.remove("is-conflict");
|
| 1283 |
}
|
| 1284 |
});
|
| 1285 |
|
|
|
|
| 1327 |
state.interaction.duration = state.interaction.endMinutes - state.interaction.startMinutes;
|
| 1328 |
}
|
| 1329 |
|
| 1330 |
+
state.interaction.hasConflict = hasScheduleConflict(
|
| 1331 |
+
state.interaction.currentDate,
|
| 1332 |
+
state.interaction.startMinutes,
|
| 1333 |
+
state.interaction.endMinutes,
|
| 1334 |
+
state.interaction.taskId
|
| 1335 |
+
);
|
| 1336 |
+
state.interaction.block.classList.toggle("is-conflict", !!state.interaction.hasConflict);
|
| 1337 |
+
|
| 1338 |
updateEventLayout(state.interaction.block, {
|
| 1339 |
date: state.interaction.currentDate,
|
| 1340 |
startMinutes: state.interaction.startMinutes,
|
|
|
|
| 1398 |
|
| 1399 |
const current = state.interaction;
|
| 1400 |
current.block.classList.remove("is-dragging");
|
| 1401 |
+
current.block.classList.remove("is-conflict");
|
| 1402 |
state.interaction = null;
|
| 1403 |
releaseInteractionPointer(current);
|
| 1404 |
finishPlannerInteraction();
|
|
|
|
| 1412 |
return;
|
| 1413 |
}
|
| 1414 |
|
| 1415 |
+
if (current.hasConflict) {
|
| 1416 |
+
loadPlanner(current.currentDate || state.selectedDate, true);
|
| 1417 |
+
showToast("该时间段已有课程或任务,请换一个时间", "error");
|
| 1418 |
+
return;
|
| 1419 |
+
}
|
| 1420 |
+
|
| 1421 |
persistWeekInteraction(current);
|
| 1422 |
}
|
| 1423 |
|
static/v020.css
CHANGED
|
@@ -160,15 +160,19 @@ body.planner-interacting .page-planner {
|
|
| 160 |
display: grid;
|
| 161 |
gap: 12px;
|
| 162 |
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
|
|
| 163 |
align-items: stretch;
|
| 164 |
align-content: start;
|
| 165 |
min-height: 0;
|
| 166 |
-
height:
|
|
|
|
| 167 |
}
|
| 168 |
|
| 169 |
.todo-column {
|
| 170 |
padding: 16px;
|
| 171 |
min-height: 0;
|
|
|
|
|
|
|
| 172 |
display: grid;
|
| 173 |
grid-template-rows: auto minmax(0, 1fr);
|
| 174 |
}
|
|
@@ -185,13 +189,17 @@ body.planner-interacting .page-planner {
|
|
| 185 |
gap: 10px;
|
| 186 |
margin-top: 12px;
|
| 187 |
min-height: 0;
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
| 189 |
padding-right: 4px;
|
| 190 |
}
|
| 191 |
|
| 192 |
.task-card {
|
| 193 |
padding: 14px;
|
| 194 |
border-radius: 18px;
|
|
|
|
| 195 |
}
|
| 196 |
|
| 197 |
.task-copy h4 {
|
|
@@ -219,7 +227,7 @@ body.planner-interacting .page-planner {
|
|
| 219 |
height: 100%;
|
| 220 |
display: grid;
|
| 221 |
grid-template-rows: auto auto minmax(0, 1fr);
|
| 222 |
-
gap:
|
| 223 |
}
|
| 224 |
|
| 225 |
.planner-head h2 {
|
|
@@ -295,12 +303,27 @@ body.planner-interacting .page-planner {
|
|
| 295 |
|
| 296 |
.planner-layout {
|
| 297 |
display: grid;
|
| 298 |
-
grid-template-columns: minmax(0,
|
| 299 |
gap: 12px;
|
| 300 |
min-height: 0;
|
| 301 |
overflow: hidden;
|
| 302 |
}
|
| 303 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
.timeline-surface,
|
| 305 |
.planner-sidebar {
|
| 306 |
border-radius: 26px;
|
|
@@ -591,8 +614,8 @@ body.planner-interacting .page-planner {
|
|
| 591 |
.planner-course-title {
|
| 592 |
display: block;
|
| 593 |
margin: 0;
|
| 594 |
-
font-size: 0.
|
| 595 |
-
line-height: 1.
|
| 596 |
letter-spacing: 0.01em;
|
| 597 |
word-break: break-word;
|
| 598 |
overflow: hidden;
|
|
@@ -605,8 +628,8 @@ body.planner-interacting .page-planner {
|
|
| 605 |
display: grid;
|
| 606 |
gap: 1px;
|
| 607 |
color: rgba(238, 244, 251, 0.82);
|
| 608 |
-
font-size: 0.
|
| 609 |
-
line-height: 1.
|
| 610 |
}
|
| 611 |
|
| 612 |
.planner-course-line {
|
|
@@ -668,6 +691,17 @@ body.planner-interacting .page-planner {
|
|
| 668 |
z-index: 8;
|
| 669 |
}
|
| 670 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 671 |
.course-event {
|
| 672 |
padding: 7px 8px 7px 10px;
|
| 673 |
}
|
|
@@ -749,7 +783,7 @@ body.planner-interacting .page-planner {
|
|
| 749 |
gap: 12px;
|
| 750 |
align-content: start;
|
| 751 |
min-height: 0;
|
| 752 |
-
grid-template-rows: auto
|
| 753 |
}
|
| 754 |
|
| 755 |
.planner-sidebar-note {
|
|
@@ -763,7 +797,10 @@ body.planner-interacting .page-planner {
|
|
| 763 |
display: grid;
|
| 764 |
gap: 10px;
|
| 765 |
min-height: 0;
|
| 766 |
-
|
|
|
|
|
|
|
|
|
|
| 767 |
padding-right: 4px;
|
| 768 |
}
|
| 769 |
|
|
|
|
| 160 |
display: grid;
|
| 161 |
gap: 12px;
|
| 162 |
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
| 163 |
+
grid-auto-rows: minmax(0, 1fr);
|
| 164 |
align-items: stretch;
|
| 165 |
align-content: start;
|
| 166 |
min-height: 0;
|
| 167 |
+
height: 100%;
|
| 168 |
+
overflow: hidden;
|
| 169 |
}
|
| 170 |
|
| 171 |
.todo-column {
|
| 172 |
padding: 16px;
|
| 173 |
min-height: 0;
|
| 174 |
+
height: 100%;
|
| 175 |
+
max-height: 100%;
|
| 176 |
display: grid;
|
| 177 |
grid-template-rows: auto minmax(0, 1fr);
|
| 178 |
}
|
|
|
|
| 189 |
gap: 10px;
|
| 190 |
margin-top: 12px;
|
| 191 |
min-height: 0;
|
| 192 |
+
height: 100%;
|
| 193 |
+
max-height: 100%;
|
| 194 |
+
overflow-y: auto;
|
| 195 |
+
overscroll-behavior: contain;
|
| 196 |
padding-right: 4px;
|
| 197 |
}
|
| 198 |
|
| 199 |
.task-card {
|
| 200 |
padding: 14px;
|
| 201 |
border-radius: 18px;
|
| 202 |
+
flex: none;
|
| 203 |
}
|
| 204 |
|
| 205 |
.task-copy h4 {
|
|
|
|
| 227 |
height: 100%;
|
| 228 |
display: grid;
|
| 229 |
grid-template-rows: auto auto minmax(0, 1fr);
|
| 230 |
+
gap: 8px;
|
| 231 |
}
|
| 232 |
|
| 233 |
.planner-head h2 {
|
|
|
|
| 303 |
|
| 304 |
.planner-layout {
|
| 305 |
display: grid;
|
| 306 |
+
grid-template-columns: minmax(0, 8.2fr) minmax(220px, 1.8fr);
|
| 307 |
gap: 12px;
|
| 308 |
min-height: 0;
|
| 309 |
overflow: hidden;
|
| 310 |
}
|
| 311 |
|
| 312 |
+
.page-planner .planner-head h2,
|
| 313 |
+
.page-planner #plannerHeadlineNote,
|
| 314 |
+
.page-planner .planner-sidebar-note,
|
| 315 |
+
.page-planner .planner-sidebar-head .column-label {
|
| 316 |
+
display: none;
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
.page-planner .planner-head {
|
| 320 |
+
align-items: center;
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
.page-planner .planner-head .section-kicker {
|
| 324 |
+
margin: 0;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
.timeline-surface,
|
| 328 |
.planner-sidebar {
|
| 329 |
border-radius: 26px;
|
|
|
|
| 614 |
.planner-course-title {
|
| 615 |
display: block;
|
| 616 |
margin: 0;
|
| 617 |
+
font-size: 0.74rem;
|
| 618 |
+
line-height: 1.12;
|
| 619 |
letter-spacing: 0.01em;
|
| 620 |
word-break: break-word;
|
| 621 |
overflow: hidden;
|
|
|
|
| 628 |
display: grid;
|
| 629 |
gap: 1px;
|
| 630 |
color: rgba(238, 244, 251, 0.82);
|
| 631 |
+
font-size: 0.58rem;
|
| 632 |
+
line-height: 1.12;
|
| 633 |
}
|
| 634 |
|
| 635 |
.planner-course-line {
|
|
|
|
| 691 |
z-index: 8;
|
| 692 |
}
|
| 693 |
|
| 694 |
+
.planner-event.is-conflict,
|
| 695 |
+
.timeline-drop-preview.is-conflict {
|
| 696 |
+
border-color: rgba(255, 107, 92, 0.72);
|
| 697 |
+
box-shadow: 0 0 0 1px rgba(255, 107, 92, 0.22), 0 16px 28px rgba(0, 0, 0, 0.24);
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
.planner-event.is-conflict::before,
|
| 701 |
+
.timeline-drop-preview.is-conflict::before {
|
| 702 |
+
background: #ff6b5c;
|
| 703 |
+
}
|
| 704 |
+
|
| 705 |
.course-event {
|
| 706 |
padding: 7px 8px 7px 10px;
|
| 707 |
}
|
|
|
|
| 783 |
gap: 12px;
|
| 784 |
align-content: start;
|
| 785 |
min-height: 0;
|
| 786 |
+
grid-template-rows: auto minmax(0, 1fr);
|
| 787 |
}
|
| 788 |
|
| 789 |
.planner-sidebar-note {
|
|
|
|
| 797 |
display: grid;
|
| 798 |
gap: 10px;
|
| 799 |
min-height: 0;
|
| 800 |
+
height: 100%;
|
| 801 |
+
max-height: 100%;
|
| 802 |
+
overflow-y: auto;
|
| 803 |
+
overscroll-behavior: contain;
|
| 804 |
padding-right: 4px;
|
| 805 |
}
|
| 806 |
|