Skip to content

Commit

Permalink
feat(side-bar): ✨ side bar supports date switching
Browse files Browse the repository at this point in the history
  • Loading branch information
haydenull committed Aug 25, 2023
1 parent 83c6510 commit 174778f
Show file tree
Hide file tree
Showing 11 changed files with 462 additions and 181 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"antd": "^5.0.6",
"axios": "^0.26.1",
"classnames": "^2.3.2",
"clsx": "^2.0.0",
"date-fns": "^2.29.3",
"dayjs": "^1.11.7",
"echarts": "^5.4.0",
Expand Down
10 changes: 9 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

116 changes: 104 additions & 12 deletions src/apps/TaskListApp.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,65 @@
import clsx from 'clsx'
import dayjs, { type Dayjs } from 'dayjs'
import { useAtom } from 'jotai'
import React, { useEffect } from 'react'
import React, { useEffect, useState } from 'react'
import { IoIosArrowBack, IoIosArrowForward } from 'react-icons/io'
import { MdAddTask } from 'react-icons/md'
import { TbActivity } from 'react-icons/tb'

import SidebarSubscription from '@/components/SidebarSubscription'
import SidebarTask from '@/components/SidebarTask'
import { renderModalApp } from '@/main'
import { fullEventsAtom, journalEventsAtom, projectEventsAtom, todayTasksAtom } from '@/model/events'
import { todaySubscriptionSchedulesAtom } from '@/model/schedule'
import { getInternalEvents } from '@/util/events'
import { todaySubscriptionSchedulesAtom, subscriptionSchedulesAtom } from '@/model/schedule'
import { getInitialSettings } from '@/util/baseInfo'
import { getInternalEvents, getTasksInTimeRange } from '@/util/events'
import { categorizeSubscriptions, categorizeTasks } from '@/util/schedule'
import { getSubscriptionsInTimeRange } from '@/util/subscription'
import { genWeekDays } from '@/util/util'

const App: React.FC<{
containerId: string
}> = ({ containerId }) => {
const [todaySubscriptions] = useAtom(todaySubscriptionSchedulesAtom)
const { allDaySubscriptions, timeSubscriptions } = categorizeSubscriptions(todaySubscriptions)
const [todayTasks] = useAtom(todayTasksAtom)
const { overdueTasks, allDayTasks, timeTasks } = categorizeTasks(todayTasks)

const [, setFullEvents] = useAtom(fullEventsAtom)
const [, setJournalEvents] = useAtom(journalEventsAtom)
const [, setProjectEvents] = useAtom(projectEventsAtom)

// calendar
const [weekDays, setWeekDays] = useState<Dayjs[]>([])
const [activeDay, setActiveDay] = useState<Dayjs>(dayjs())

// subscriptions
const [fullSubscriptions] = useAtom(subscriptionSchedulesAtom)
const weekSubscriptionsMap = getSubscriptionsInTimeRange(fullSubscriptions, weekDays)
const activeDaySubscriptions = weekSubscriptionsMap.get(activeDay.format('YYYY-MM-DD')) ?? []
const { allDaySubscriptions, timeSubscriptions } = categorizeSubscriptions(activeDaySubscriptions)

// tasks
const [fullTasks, setFullEvents] = useAtom(fullEventsAtom)
const weekTasksMap = getTasksInTimeRange(fullTasks?.tasks?.withTime, weekDays)
const activeDayTasks = weekTasksMap.get(activeDay.format('YYYY-MM-DD')) ?? []
const { overdueTasks, allDayTasks, timeTasks } = categorizeTasks(activeDayTasks)

// weekday with dots
const weekDaysWithDots = Object.fromEntries(
weekDays.map((day) => {
const dayString = day.format('YYYY-MM-DD')
const subscriptions = weekSubscriptionsMap.get(dayString) ?? []
const tasks = weekTasksMap.get(dayString) ?? []
return [dayString, subscriptions.length > 0 || tasks.length > 0]
}),
)

const onClickCalendarNumber = (day: Dayjs) => {
setActiveDay(day)
}
const onClickCalendarArrow = (direction: 'back' | 'forward') => {
const oneDay = direction === 'back' ? weekDays[0]?.add(-1, 'day') : weekDays[weekDays.length - 1].add(1, 'day')
const { weekStartDay } = getInitialSettings()
const days = genWeekDays(weekStartDay, oneDay)
setWeekDays(days)
setActiveDay((_old) => days.find((day) => day.day() === _old.day()) ?? days[0])
}

useEffect(() => {
async function fetchSchedules() {
const res = await getInternalEvents()
Expand All @@ -41,8 +78,15 @@ const App: React.FC<{
})
}, [])

useEffect(() => {
const { weekStartDay } = getInitialSettings()
const days = genWeekDays(weekStartDay)
setWeekDays(days)
}, [])

return (
<main>
{/* ========= toolbar start ========= */}
<div className="flex items-center">
<a
className="button items-center"
Expand All @@ -69,9 +113,57 @@ const App: React.FC<{
<MdAddTask />
</a>
</div>
{overdueTasks?.length === 0 && allDayTasks?.length === 0 && timeTasks?.length === 0 && (
<div>Agenda: No Task Today</div>
)}
{/* ========= toolbar end ========= */}

{/* ========= calendar start ========= */}
<div className="flex items-center justify-between agenda-sidebar-calendar">
<div
className="agenda-sidebar-calendar__number flex items-center justify-center"
onClick={() => onClickCalendarArrow('back')}
>
<IoIosArrowBack />
</div>
{weekDays.map((day) => (
<div
className="flex flex-col items-center"
key={day.format('YYYY-MM-DD')}
onClick={() => onClickCalendarNumber(day)}
>
<span
className={clsx({
'agenda-sidebar-calendar__week-day--today': day.isSame(dayjs(), 'day'),
})}
>
{day.format('dd')?.charAt(0)}
</span>
<div
className={clsx('agenda-sidebar-calendar__number flex items-center justify-center', {
'bg-indigo-600 agenda-sidebar-calendar__number--active': day.isSame(activeDay, 'day'),
'agenda-sidebar-calendar__number--dot': weekDaysWithDots[day.format('YYYY-MM-DD')],
'agenda-sidebar-calendar__number--today': day.isSame(dayjs(), 'day'),
})}
style={{ opacity: day.isSameOrAfter(dayjs(), 'day') || day.isSame(activeDay, 'day') ? 0.9 : 0.4 }}
>
{day.format('DD')}
</div>
</div>
))}
<div
className="agenda-sidebar-calendar__number flex items-center justify-center"
onClick={() => onClickCalendarArrow('forward')}
>
<IoIosArrowForward />
</div>
</div>
{/* ========= calendar end ========= */}

{overdueTasks?.length === 0 &&
allDayTasks?.length === 0 &&
timeTasks?.length === 0 &&
allDaySubscriptions?.length === 0 &&
timeSubscriptions?.length === 0 ? (
<div className="flex justify-center">No tasks found, enjoy your day</div>
) : null}
{/* overdue tasks */}
{overdueTasks.length > 0 && (
<div style={{ margin: '8px 0' }}>
Expand Down
24 changes: 8 additions & 16 deletions src/components/SidebarTask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,33 +83,25 @@ const Task = ({ task, type = 'allDay' }: { task: IEvent; type?: 'overdue' | 'all
<div
style={{ width: '3px', backgroundColor: calendarConfig?.bgColor, borderRadius: '2px', margin: '0 6px' }}
></div>
<div style={{ width: 'calc(100% - 114px)', paddingBottom: '24px', position: 'relative' }}>
<div style={{ color: 'var(--ls-icon-color)', fontSize: '0.8em', opacity: 0.6 }}>{calendarConfig?.id}</div>
<div style={{ width: 'calc(100% - 114px)' }} className="relative">
<div className="truncate" style={{ color: 'var(--ls-icon-color)', fontSize: '0.8em', opacity: 0.6 }}>
{calendarConfig?.id}
</div>
<div
className="agenda-sidebar-task__title"
style={{
marginBottom: '-0.2em',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
position: 'absolute',
bottom: 0,
width: 'calc(100% - 30px)',
}}
className="agenda-sidebar-task__title line-clamp-2"
style={{ width: 'calc(100% - 30px)' }}
title={task.addOns.showTitle}
>
{task.addOns.showTitle}
</div>
{isActive && (
<span
className="ui__button bg-indigo-600"
className="ui__button bg-indigo-600 absolute right-0"
style={{
fontSize: '0.5em',
position: 'absolute',
right: 0,
bottom: 0,
padding: '0 3px',
borderRadius: '3px',
top: 'calc(50% - 7px)',
}}
>
NOW
Expand Down
48 changes: 48 additions & 0 deletions src/constants/style.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,56 @@
export const LOGSEQ_PROVIDE_COMMON_STYLE = `
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.line-clamp-2 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.external-link[href^="#agenda://"]::before {
content: '📅';
margin: 0 4px;
}
.agenda-sidebar-calendar {
font-size: 14px;
cursor: default;
margin-bottom: 14px;
}
.agenda-sidebar-calendar__week-day--today {
color: var(--ls-link-text-color);
font-weight: 600;
}
.agenda-sidebar-calendar__number {
width: 26px;
height: 26px;
border-radius: 100%;
cursor: pointer;
position: relative;
}
.agenda-sidebar-calendar__number--today {
color: var(--ls-link-text-color);
border: 1px solid var(--ls-link-text-color);
box-sizing: border-box;
}
.agenda-sidebar-calendar__number--active {
border: none;
opacity: 0.9;
color: #fff;
}
.agenda-sidebar-calendar__number--dot::after {
content: '';
display: block;
width: 4px;
height: 4px;
border-radius: 100%;
background: var(--ls-link-text-color);
position: absolute;
left: calc(50% - 2px);
top: calc(100% + 2px);
}
.agenda-sidebar-task__add {
display: none;
}
Expand Down
20 changes: 10 additions & 10 deletions src/model/schedule.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import dayjs from 'dayjs'
import { atom } from 'jotai'
import type { ISchedule } from 'tui-calendar'
import { genScheduleWithCalendarMap } from '@/util/schedule'
import dayjs from 'dayjs'
import { getInitialSettings } from '@/util/baseInfo'

export const projectSchedulesAtom = atom<ISchedule[]>([]) // include overdue schedules
export const subscriptionSchedulesAtom = atom<ISchedule[]>([])

export const todaySubscriptionSchedulesAtom = atom<ISchedule[]>(get => {
export const todaySubscriptionSchedulesAtom = atom<ISchedule[]>((get) => {
const subscriptions = get(subscriptionSchedulesAtom)
return subscriptions.filter(schedule => {
const start = dayjs(schedule.start as string)
const end = dayjs(schedule.end as string)
return dayjs().isBetween(start, end, 'day', '[]')
}).sort((a, b) => dayjs(a.start as string).diff(dayjs(b.start as string)))
return subscriptions
.filter((schedule) => {
const start = dayjs(schedule.start as string)
const end = dayjs(schedule.end as string)
return dayjs().isBetween(start, end, 'day', '[]')
})
.sort((a, b) => dayjs(a.start as string).diff(dayjs(b.start as string)))
})

// export const schedulesAtom = atom((get) => get(projectSchedulesAtom).concat(get(subscriptionSchedulesAtom)))
Expand Down Expand Up @@ -60,4 +60,4 @@ export const todaySubscriptionSchedulesAtom = atom<ISchedule[]>(get => {
// const taskDay = dayjs(task.start as string)
// return taskDay.isBetween(start, end, 'day', '(]')
// })
// })
// })
9 changes: 7 additions & 2 deletions src/util/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Language } from '@/constants/language'
/* eslint-disable no-useless-escape */

/* eslint-disable no-misleading-character-class */
import { theme as antdTheme } from 'antd'
import { ISettingsForm } from './type'

import { Language } from '@/constants/language'

import type { ISettingsForm } from './type'

export const SHOW_DATE_FORMAT = 'yyyy-MM-dd'

Expand Down
33 changes: 27 additions & 6 deletions src/util/events.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { IPomodoroInfo } from '@/helper/pomodoro'
import type { BlockEntity, PageEntity } from '@logseq/libs/dist/LSPlugin'
import dayjs from 'dayjs'
import dayjs, { type Dayjs } from 'dayjs'

import type { IPomodoroInfo } from '@/helper/pomodoro'

import { transformBlockToEvent } from './../helper/transform'
import { getInitialSettings } from './baseInfo'
import { pureTaskBlockContent } from './logseq'
import { getProjectTaskTime, getTimeInfo } from './schedule'
import { ICustomCalendar } from './type'
import type { ICustomCalendar } from './type'

export const getEventTimeInfo = (
block: BlockEntity
block: BlockEntity,
): {
start: string
end?: string
Expand Down Expand Up @@ -195,8 +197,8 @@ export const getInternalEvents = async () => {
})
}

let fullEvents: IPageEvent = genDefaultProjectEvents()
let journalEvents: IPageEvent = genDefaultProjectEvents()
const fullEvents: IPageEvent = genDefaultProjectEvents()
const journalEvents: IPageEvent = genDefaultProjectEvents()
const projectEventsMap = new Map<string, IPageEvent>()

const closedProjects = settings?.projectList?.filter((p) => p?.enabled !== true)
Expand Down Expand Up @@ -287,3 +289,22 @@ export const getEventPomodoroLength = (event: IEvent) => {
}, 0) || 0
)
}

/**
* Retrieve tasks within a specified range and combine them into a map based on time.
*/
export const getTasksInTimeRange = (events: IEvent[], range: Dayjs[]) => {
const tasksInTimeRange = new Map<string, IEvent[]>()

range.forEach((day) => {
const eventsInDay = events
.filter((event) => {
if (!event.addOns.start || !event.addOns.end) return false
return day.isBetween(dayjs(event.addOns.start), dayjs(event.addOns.end), 'd', '[]')
})
.sort((a, b) => dayjs(a.addOns.start).diff(dayjs(b.addOns.start)))
tasksInTimeRange.set(day.format('YYYY-MM-DD'), eventsInDay)
})

return tasksInTimeRange
}
Loading

0 comments on commit 174778f

Please sign in to comment.