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

View file

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

View file

@ -1,8 +1,10 @@
<script lang="ts"> <script lang="ts">
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { onMount } from 'svelte';
import { viewStore } from '$lib/stores/view.svelte'; import { viewStore } from '$lib/stores/view.svelte';
import { settingsStore } from '$lib/stores/settings.svelte'; import { settingsStore } from '$lib/stores/settings.svelte';
import { getOffsetDate } from '$lib/utils/dateNavigation'; import { getOffsetDate } from '$lib/utils/dateNavigation';
import { HOUR_HEIGHT_PX } from '$lib/utils/calendarConstants';
import WeekView from './WeekView.svelte'; import WeekView from './WeekView.svelte';
import DayView from './DayView.svelte'; import DayView from './DayView.svelte';
import MonthView from './MonthView.svelte'; import MonthView from './MonthView.svelte';
@ -37,6 +39,7 @@
// Container refs // Container refs
let viewportEl: HTMLDivElement; let viewportEl: HTMLDivElement;
let currentPageEl: HTMLDivElement;
let viewportWidth = $state(0); let viewportWidth = $state(0);
// Threshold: 15% of viewport width or high velocity triggers navigation // Threshold: 15% of viewport width or high velocity triggers navigation
@ -284,6 +287,39 @@
// Computed styles // Computed styles
let trackStyle = $derived(`transform: translateX(calc(-33.333% + ${offsetX}px))`); 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> </script>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
@ -331,7 +367,7 @@
</div> </div>
<!-- Current View (main interactive view) --> <!-- Current View (main interactive view) -->
<div class="carousel-page current"> <div class="carousel-page current" bind:this={currentPageEl}>
{#if viewStore.viewType === 'day'} {#if viewStore.viewType === 'day'}
<DayView {onQuickCreate} {onEventClick} /> <DayView {onQuickCreate} {onEventClick} />
{:else if viewStore.viewType === '3day'} {:else if viewStore.viewType === '3day'}

View file

@ -1003,37 +1003,39 @@
/> />
{/each} {/each}
<!-- Scheduled Tasks (Time-Blocking) --> <!-- Scheduled Tasks (Time-Blocking) - only shown if enabled in settings -->
{#each getScheduledTasksForDay(day) as task (task.id)} {#if settingsStore.showTasksInCalendar}
{@const isTaskBeingDragged = isTaskDragging && draggedTask?.id === task.id} {#each getScheduledTasksForDay(day) as task (task.id)}
{@const isTaskBeingResized = isTaskResizing && resizeTask?.id === task.id} {@const isTaskBeingDragged = isTaskDragging && draggedTask?.id === task.id}
{@const isTaskCrossDayDrag = {@const isTaskBeingResized = isTaskResizing && resizeTask?.id === task.id}
isTaskBeingDragged && {@const isTaskCrossDayDrag =
taskDragTargetDay !== null && isTaskBeingDragged &&
!isSameDay(day, taskDragTargetDay)} taskDragTargetDay !== null &&
<TaskBlock !isSameDay(day, taskDragTargetDay)}
{task} <TaskBlock
style={isTaskBeingDragged && !isTaskCrossDayDrag {task}
? `top: ${taskDragPreviewTop}%; height: ${taskDragPreviewHeight}%;` style={isTaskBeingDragged && !isTaskCrossDayDrag
: isTaskBeingResized ? `top: ${taskDragPreviewTop}%; height: ${taskDragPreviewHeight}%;`
? `top: ${taskResizePreviewTop}%; height: ${taskResizePreviewHeight}%;` : isTaskBeingResized
: getTaskStyle(task)} ? `top: ${taskResizePreviewTop}%; height: ${taskResizePreviewHeight}%;`
{onTaskClick} : getTaskStyle(task)}
onDragStart={handleTaskDragStart} {onTaskClick}
onResizeStart={handleTaskResizeStart} onDragStart={handleTaskDragStart}
isDragging={isTaskBeingDragged && !isTaskCrossDayDrag} onResizeStart={handleTaskResizeStart}
isResizing={isTaskBeingResized} isDragging={isTaskBeingDragged && !isTaskCrossDayDrag}
isDraggingSource={isTaskCrossDayDrag} isResizing={isTaskBeingResized}
/> isDraggingSource={isTaskCrossDayDrag}
{/each} />
{/each}
<!-- Task Drag preview (solid) for cross-day dragging - shows where task will be --> <!-- 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)} {#if isTaskDragging && draggedTask && taskDragTargetDay && isSameDay(day, taskDragTargetDay) && !getScheduledTasksForDay(day).some((t) => t.id === draggedTask!.id)}
<TaskBlock <TaskBlock
task={draggedTask} task={draggedTask}
style="top: {taskDragPreviewTop}%; height: {taskDragPreviewHeight}%;" style="top: {taskDragPreviewTop}%; height: {taskDragPreviewHeight}%;"
isDragging={true} isDragging={true}
/> />
{/if}
{/if} {/if}
<!-- Drag preview (solid) for cross-day dragging - shows where event will be --> <!-- 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; showBirthdays: boolean;
showBirthdayAge: boolean; showBirthdayAge: boolean;
// Task settings
showTasksInCalendar: boolean;
// UI settings // UI settings
sidebarCollapsed: boolean; sidebarCollapsed: boolean;
@ -96,6 +99,7 @@ const DEFAULT_SETTINGS: CalendarAppSettings = {
immersiveModeEnabled: false, immersiveModeEnabled: false,
showBirthdays: true, showBirthdays: true,
showBirthdayAge: true, showBirthdayAge: true,
showTasksInCalendar: false,
sidebarCollapsed: false, sidebarCollapsed: false,
quickViewPillViews: ['week', 'month', 'agenda'], quickViewPillViews: ['week', 'month', 'agenda'],
customDayCount: 30, customDayCount: 30,
@ -231,6 +235,9 @@ export const settingsStore = {
get showBirthdayAge() { get showBirthdayAge() {
return baseStore.settings.showBirthdayAge; return baseStore.settings.showBirthdayAge;
}, },
get showTasksInCalendar() {
return baseStore.settings.showTasksInCalendar;
},
get defaultEventDuration() { get defaultEventDuration() {
return baseStore.settings.defaultEventDuration; return baseStore.settings.defaultEventDuration;
}, },
@ -295,6 +302,10 @@ export const settingsStore = {
baseStore.set('selectedTagIds', []); baseStore.set('selectedTagIds', []);
}, },
toggleTasksInCalendar() {
baseStore.set('showTasksInCalendar', !baseStore.settings.showTasksInCalendar);
},
// Time formatting helpers // Time formatting helpers
formatTime(date: Date): string { formatTime(date: Date): string {
if (baseStore.settings.timeFormat === '12h') { if (baseStore.settings.timeFormat === '12h') {