🚸 feat(calendar): hide tasks by default and scroll to midday on load

- Add showTasksInCalendar setting (default: false) to hide task blocks
- Auto-scroll time-grid views to 12:00 on initial load for better UX
- Tasks can be re-enabled via settings toggle
This commit is contained in:
Till-JS 2026-02-02 18:54:05 +01:00
parent 5c19500748
commit bd9bd556f4
5 changed files with 132 additions and 79 deletions

View file

@ -813,24 +813,26 @@
/>
{/each}
<!-- Scheduled Tasks (Time-Blocking) -->
{#each getScheduledTasks() as task (task.id)}
{@const isTaskBeingDragged = isTaskDragging && draggedTask?.id === task.id}
{@const isTaskBeingResized = isTaskResizing && resizeTask?.id === task.id}
<TaskBlock
{task}
style={isTaskBeingDragged
? `top: ${taskDragPreviewTop}%; height: ${taskDragPreviewHeight}%;`
: isTaskBeingResized
? `top: ${taskResizePreviewTop}%; height: ${taskResizePreviewHeight}%;`
: getTaskStyle(task)}
{onTaskClick}
onDragStart={handleTaskDragStart}
onResizeStart={handleTaskResizeStart}
isDragging={isTaskBeingDragged}
isResizing={isTaskBeingResized}
/>
{/each}
<!-- Scheduled Tasks (Time-Blocking) - only shown if enabled in settings -->
{#if settingsStore.showTasksInCalendar}
{#each getScheduledTasks() as task (task.id)}
{@const isTaskBeingDragged = isTaskDragging && draggedTask?.id === task.id}
{@const isTaskBeingResized = isTaskResizing && resizeTask?.id === task.id}
<TaskBlock
{task}
style={isTaskBeingDragged
? `top: ${taskDragPreviewTop}%; height: ${taskDragPreviewHeight}%;`
: isTaskBeingResized
? `top: ${taskResizePreviewTop}%; height: ${taskResizePreviewHeight}%;`
: getTaskStyle(task)}
{onTaskClick}
onDragStart={handleTaskDragStart}
onResizeStart={handleTaskResizeStart}
isDragging={isTaskBeingDragged}
isResizing={isTaskBeingResized}
/>
{/each}
{/if}
<!-- Overflow indicators for events outside visible time range -->
{#if overflowEvents.before.length > 0}

View file

@ -976,37 +976,39 @@
</div>
{/each}
<!-- Scheduled Tasks (Time-Blocking) -->
{#each getScheduledTasksForDay(day) as task (task.id)}
{@const isTaskBeingDragged = isTaskDragging && draggedTask?.id === task.id}
{@const isTaskBeingResized = isTaskResizing && resizeTask?.id === task.id}
{@const isTaskCrossDayDrag =
isTaskBeingDragged &&
taskDragTargetDay !== null &&
!isSameDay(day, taskDragTargetDay)}
<TaskBlock
{task}
style={isTaskBeingDragged && !isTaskCrossDayDrag
? `top: ${taskDragPreviewTop}%; height: ${taskDragPreviewHeight}%;`
: isTaskBeingResized
? `top: ${taskResizePreviewTop}%; height: ${taskResizePreviewHeight}%;`
: getTaskStyle(task)}
{onTaskClick}
onDragStart={handleTaskDragStart}
onResizeStart={handleTaskResizeStart}
isDragging={isTaskBeingDragged && !isTaskCrossDayDrag}
isResizing={isTaskBeingResized}
isDraggingSource={isTaskCrossDayDrag}
/>
{/each}
<!-- Scheduled Tasks (Time-Blocking) - only shown if enabled in settings -->
{#if settingsStore.showTasksInCalendar}
{#each getScheduledTasksForDay(day) as task (task.id)}
{@const isTaskBeingDragged = isTaskDragging && draggedTask?.id === task.id}
{@const isTaskBeingResized = isTaskResizing && resizeTask?.id === task.id}
{@const isTaskCrossDayDrag =
isTaskBeingDragged &&
taskDragTargetDay !== null &&
!isSameDay(day, taskDragTargetDay)}
<TaskBlock
{task}
style={isTaskBeingDragged && !isTaskCrossDayDrag
? `top: ${taskDragPreviewTop}%; height: ${taskDragPreviewHeight}%;`
: isTaskBeingResized
? `top: ${taskResizePreviewTop}%; height: ${taskResizePreviewHeight}%;`
: getTaskStyle(task)}
{onTaskClick}
onDragStart={handleTaskDragStart}
onResizeStart={handleTaskResizeStart}
isDragging={isTaskBeingDragged && !isTaskCrossDayDrag}
isResizing={isTaskBeingResized}
isDraggingSource={isTaskCrossDayDrag}
/>
{/each}
<!-- Task Drag preview (solid) for cross-day dragging - shows where task will be -->
{#if isTaskDragging && draggedTask && taskDragTargetDay && isSameDay(day, taskDragTargetDay) && !getScheduledTasksForDay(day).some((t) => t.id === draggedTask!.id)}
<TaskBlock
task={draggedTask}
style="top: {taskDragPreviewTop}%; height: {taskDragPreviewHeight}%;"
isDragging={true}
/>
<!-- Task Drag preview (solid) for cross-day dragging - shows where task will be -->
{#if isTaskDragging && draggedTask && taskDragTargetDay && isSameDay(day, taskDragTargetDay) && !getScheduledTasksForDay(day).some((t) => t.id === draggedTask!.id)}
<TaskBlock
task={draggedTask}
style="top: {taskDragPreviewTop}%; height: {taskDragPreviewHeight}%;"
isDragging={true}
/>
{/if}
{/if}
<!-- Drag preview (solid) for cross-day dragging - shows where event will be -->

View file

@ -1,8 +1,10 @@
<script lang="ts">
import { browser } from '$app/environment';
import { onMount } from 'svelte';
import { viewStore } from '$lib/stores/view.svelte';
import { settingsStore } from '$lib/stores/settings.svelte';
import { getOffsetDate } from '$lib/utils/dateNavigation';
import { HOUR_HEIGHT_PX } from '$lib/utils/calendarConstants';
import WeekView from './WeekView.svelte';
import DayView from './DayView.svelte';
import MonthView from './MonthView.svelte';
@ -37,6 +39,7 @@
// Container refs
let viewportEl: HTMLDivElement;
let currentPageEl: HTMLDivElement;
let viewportWidth = $state(0);
// Threshold: 15% of viewport width or high velocity triggers navigation
@ -284,6 +287,39 @@
// Computed styles
let trackStyle = $derived(`transform: translateX(calc(-33.333% + ${offsetX}px))`);
// Scroll to center of day (around 12:00) on initial mount
// Only for time-grid views (day, week, multi-day)
onMount(() => {
if (!browser) return;
// Small delay to ensure views are rendered
setTimeout(() => {
if (!currentPageEl) return;
// Only scroll for time-grid views (not month, year, agenda)
const timeGridViews = [
'day',
'3day',
'5day',
'week',
'10day',
'14day',
'30day',
'60day',
'90day',
'365day',
'custom',
];
if (!timeGridViews.includes(viewStore.viewType)) return;
// Calculate scroll position to center around 12:00 (noon)
const targetHour = 12;
const targetScrollTop = targetHour * HOUR_HEIGHT_PX - currentPageEl.clientHeight / 2;
currentPageEl.scrollTop = Math.max(0, targetScrollTop);
}, 150);
});
</script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
@ -331,7 +367,7 @@
</div>
<!-- Current View (main interactive view) -->
<div class="carousel-page current">
<div class="carousel-page current" bind:this={currentPageEl}>
{#if viewStore.viewType === 'day'}
<DayView {onQuickCreate} {onEventClick} />
{:else if viewStore.viewType === '3day'}

View file

@ -1003,37 +1003,39 @@
/>
{/each}
<!-- Scheduled Tasks (Time-Blocking) -->
{#each getScheduledTasksForDay(day) as task (task.id)}
{@const isTaskBeingDragged = isTaskDragging && draggedTask?.id === task.id}
{@const isTaskBeingResized = isTaskResizing && resizeTask?.id === task.id}
{@const isTaskCrossDayDrag =
isTaskBeingDragged &&
taskDragTargetDay !== null &&
!isSameDay(day, taskDragTargetDay)}
<TaskBlock
{task}
style={isTaskBeingDragged && !isTaskCrossDayDrag
? `top: ${taskDragPreviewTop}%; height: ${taskDragPreviewHeight}%;`
: isTaskBeingResized
? `top: ${taskResizePreviewTop}%; height: ${taskResizePreviewHeight}%;`
: getTaskStyle(task)}
{onTaskClick}
onDragStart={handleTaskDragStart}
onResizeStart={handleTaskResizeStart}
isDragging={isTaskBeingDragged && !isTaskCrossDayDrag}
isResizing={isTaskBeingResized}
isDraggingSource={isTaskCrossDayDrag}
/>
{/each}
<!-- Scheduled Tasks (Time-Blocking) - only shown if enabled in settings -->
{#if settingsStore.showTasksInCalendar}
{#each getScheduledTasksForDay(day) as task (task.id)}
{@const isTaskBeingDragged = isTaskDragging && draggedTask?.id === task.id}
{@const isTaskBeingResized = isTaskResizing && resizeTask?.id === task.id}
{@const isTaskCrossDayDrag =
isTaskBeingDragged &&
taskDragTargetDay !== null &&
!isSameDay(day, taskDragTargetDay)}
<TaskBlock
{task}
style={isTaskBeingDragged && !isTaskCrossDayDrag
? `top: ${taskDragPreviewTop}%; height: ${taskDragPreviewHeight}%;`
: isTaskBeingResized
? `top: ${taskResizePreviewTop}%; height: ${taskResizePreviewHeight}%;`
: getTaskStyle(task)}
{onTaskClick}
onDragStart={handleTaskDragStart}
onResizeStart={handleTaskResizeStart}
isDragging={isTaskBeingDragged && !isTaskCrossDayDrag}
isResizing={isTaskBeingResized}
isDraggingSource={isTaskCrossDayDrag}
/>
{/each}
<!-- Task Drag preview (solid) for cross-day dragging - shows where task will be -->
{#if isTaskDragging && draggedTask && taskDragTargetDay && isSameDay(day, taskDragTargetDay) && !getScheduledTasksForDay(day).some((t) => t.id === draggedTask!.id)}
<TaskBlock
task={draggedTask}
style="top: {taskDragPreviewTop}%; height: {taskDragPreviewHeight}%;"
isDragging={true}
/>
<!-- Task Drag preview (solid) for cross-day dragging - shows where task will be -->
{#if isTaskDragging && draggedTask && taskDragTargetDay && isSameDay(day, taskDragTargetDay) && !getScheduledTasksForDay(day).some((t) => t.id === draggedTask!.id)}
<TaskBlock
task={draggedTask}
style="top: {taskDragPreviewTop}%; height: {taskDragPreviewHeight}%;"
isDragging={true}
/>
{/if}
{/if}
<!-- Drag preview (solid) for cross-day dragging - shows where event will be -->

View file

@ -54,6 +54,9 @@ export interface CalendarAppSettings extends Record<string, unknown> {
showBirthdays: boolean;
showBirthdayAge: boolean;
// Task settings
showTasksInCalendar: boolean;
// UI settings
sidebarCollapsed: boolean;
@ -96,6 +99,7 @@ const DEFAULT_SETTINGS: CalendarAppSettings = {
immersiveModeEnabled: false,
showBirthdays: true,
showBirthdayAge: true,
showTasksInCalendar: false,
sidebarCollapsed: false,
quickViewPillViews: ['week', 'month', 'agenda'],
customDayCount: 30,
@ -231,6 +235,9 @@ export const settingsStore = {
get showBirthdayAge() {
return baseStore.settings.showBirthdayAge;
},
get showTasksInCalendar() {
return baseStore.settings.showTasksInCalendar;
},
get defaultEventDuration() {
return baseStore.settings.defaultEventDuration;
},
@ -295,6 +302,10 @@ export const settingsStore = {
baseStore.set('selectedTagIds', []);
},
toggleTasksInCalendar() {
baseStore.set('showTasksInCalendar', !baseStore.settings.showTasksInCalendar);
},
// Time formatting helpers
formatTime(date: Date): string {
if (baseStore.settings.timeFormat === '12h') {