Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions apps/desktop2/src/components/calendar.tsx

This file was deleted.

6 changes: 5 additions & 1 deletion apps/desktop2/src/components/main/left-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ function TimelineView() {
return (
<div className="flex flex-col">
{allSessionIds?.map((sessionId) => (
<SessionItem key={sessionId} sessionId={sessionId} active={currentTab?.id === sessionId} />
<SessionItem
key={sessionId}
sessionId={sessionId}
active={currentTab?.type === "sessions" && currentTab?.id === sessionId}
/>
))}
</div>
);
Expand Down
224 changes: 192 additions & 32 deletions apps/desktop2/src/components/main/main-area.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { useNavigate, useSearch } from "@tanstack/react-router";
import { clsx } from "clsx";
import { addMonths, eachDayOfInterval, endOfMonth, format, getDay, startOfMonth } from "date-fns";
import {
Building2Icon,
CalendarIcon,
ChevronLeftIcon,
ChevronRightIcon,
CogIcon,
PanelLeftOpenIcon,
PencilIcon,
StickyNoteIcon,
UserIcon,
} from "lucide-react";

import { commands as windowsCommands } from "@hypr/plugin-windows";
import NoteEditor from "@hypr/tiptap/editor";
import { ChatPanelButton } from "@hypr/ui/components/block/chat-panel-button";
import TitleInput from "@hypr/ui/components/block/title-input";
import { Button } from "@hypr/ui/components/ui/button";
import { ScrollArea, ScrollBar } from "@hypr/ui/components/ui/scroll-area";
import { useLeftSidebar, useRightPanel } from "@hypr/utils/contexts";
import { useTabs } from "../../hooks/useTabs";
import * as persisted from "../../tinybase/store/persisted";
import { Tab } from "../../types";
import { rowIdfromTab, Tab, uniqueIdfromTab } from "../../types";

export function MainContent({ tabs }: { tabs: Tab[] }) {
const activeTab = tabs.find((t) => t.active)!;
Expand All @@ -34,6 +36,8 @@ export function MainContent({ tabs }: { tabs: Tab[] }) {
export function MainHeader() {
const search = useSearch({ strict: false });
const navigate = useNavigate();

const { openNew } = useTabs();
const { isExpanded: isRightPanelExpanded, togglePanel: toggleRightPanel } = useRightPanel();
const { isExpanded: isLeftPanelExpanded, togglePanel: toggleLeftPanel } = useLeftSidebar();

Expand Down Expand Up @@ -77,6 +81,10 @@ export function MainHeader() {
onClick={handleClickNewNote}
className="cursor-pointer h-5 w-5 text-muted-foreground hover:text-foreground"
/>
<CalendarIcon
onClick={() => openNew({ type: "calendars", month: new Date(), active: true })}
className="cursor-pointer h-5 w-5 text-muted-foreground hover:text-foreground"
/>
</div>

<ChatPanelButton
Expand All @@ -93,31 +101,74 @@ function TabsHeader({ tabs }: { tabs: Tab[] }) {
return (
<ScrollArea className="w-full border-b whitespace-nowrap">
<div className="flex w-max gap-1">
{tabs.map((tab) => (
<TabItem
key={tab.id}
tab={tab}
active={tab.active}
handleSelect={select}
handleClose={close}
/>
))}
{tabs.map((tab) => <TabItem key={uniqueIdfromTab(tab)} tab={tab} handleClose={close} handleSelect={select} />)}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
);
}

function TabItem(
{ tab, active, handleSelect, handleClose }: {
{ tab, handleClose, handleSelect }: { tab: Tab; handleClose: (tab: Tab) => void; handleSelect: (tab: Tab) => void },
) {
if (tab.type === "sessions") {
return <TabItemNote tab={tab} handleClose={handleClose} handleSelect={handleSelect} />;
}

if (tab.type === "calendars") {
return <TabItemCalendar tab={tab} handleClose={handleClose} handleSelect={handleSelect} />;
}

return null;
}

function TabItemNote(
{ tab, handleClose, handleSelect }: {
tab: Tab;
active: boolean;
handleClose: (tab: Tab) => void;
handleSelect: (tab: Tab) => void;
},
) {
const title = persisted.UI.useCell("sessions", rowIdfromTab(tab), "title", persisted.STORE_ID);

return (
<TabItemBase
icon={<StickyNoteIcon className="w-4 h-4" />}
title={title ?? ""}
active={tab.active}
handleClose={() => handleClose(tab)}
handleSelect={() => handleSelect(tab)}
/>
);
}

function TabItemCalendar(
{ tab, handleClose, handleSelect }: {
tab: Tab;
handleClose: (tab: Tab) => void;
handleSelect: (tab: Tab) => void;
},
) {
const title = persisted.UI.useCell("sessions", tab.id, "title", persisted.STORE_ID);
return (
<TabItemBase
icon={<CalendarIcon className="w-4 h-4" />}
title={"Calendar"}
active={tab.active}
handleClose={() => handleClose(tab)}
handleSelect={() => handleSelect(tab)}
/>
);
}

function TabItemBase(
{ icon, title, active, handleClose, handleSelect }: {
icon: React.ReactNode;
title: string;
active: boolean;
handleClose: () => void;
handleSelect: () => void;
},
) {
return (
<div
className={clsx([
Expand All @@ -129,27 +180,17 @@ function TabItem(
])}
>
<button
onClick={() => handleSelect(tab)}
onClick={() => handleSelect()}
className="flex flex-row items-center gap-1 text-sm max-w-[140px]"
>
<span className="flex-shrink-0">
{tab.type === "sessions"
? <StickyNoteIcon className="w-4 h-4" />
: tab.type === "calendars"
? <CalendarIcon className="w-4 h-4" />
: tab.type === "humans"
? <UserIcon className="w-4 h-4" />
: tab.type === "events"
? <CalendarIcon className="w-4 h-4" />
: tab.type === "organizations"
? <Building2Icon className="w-4 h-4" />
: <></>}
{icon}
</span>
<span className="truncate">{title}</span>
</button>
{active && (
<button
onClick={() => handleClose(tab)}
onClick={() => handleClose()}
className="text-muted-foreground hover:text-foreground text-xs"
>
Expand All @@ -160,19 +201,32 @@ function TabItem(
}

function TabContent({ tab }: { tab: Tab }) {
const row = persisted.UI.useRow("sessions", tab.id, persisted.STORE_ID);
if (tab.type === "sessions") {
return <TabContentNote tab={tab} />;
}

if (tab.type === "calendars") {
return <TabContentCalendar tab={tab} />;
}

return null;
}

function TabContentNote({ tab }: { tab: Tab }) {
const id = rowIdfromTab(tab);
const row = persisted.UI.useRow("sessions", id, persisted.STORE_ID);

const handleEditTitle = persisted.UI.useSetRowCallback(
"sessions",
tab.id,
id,
(input: string, _store) => ({ ...row, title: input }),
[row],
persisted.STORE_ID,
);

const handleEditRawMd = persisted.UI.useSetRowCallback(
"sessions",
tab.id,
id,
(input: string, _store) => ({ ...row, raw_md: input }),
[row],
persisted.STORE_ID,
Expand All @@ -181,8 +235,8 @@ function TabContent({ tab }: { tab: Tab }) {
return (
<div className="flex flex-col gap-2 px-2 pt-2">
<TitleInput
value={row.title ?? ""}
editable={true}
value={row.title ?? ""}
onChange={(e) => handleEditTitle(e.target.value)}
/>
<NoteEditor
Expand All @@ -198,3 +252,109 @@ function TabContent({ tab }: { tab: Tab }) {
</div>
);
}

function TabContentCalendar({ tab }: { tab: Tab }) {
if (tab.type !== "calendars") {
return null;
}

const { openCurrent } = useTabs();
const monthStart = startOfMonth(tab.month);
const monthEnd = endOfMonth(tab.month);
const days = eachDayOfInterval({ start: monthStart, end: monthEnd }).map((day) => format(day, "yyyy-MM-dd"));
const startDayOfWeek = getDay(monthStart); // 0 = Sunday, 6 = Saturday
const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

const handlePreviousMonth = () => {
openCurrent({ ...tab, month: addMonths(tab.month, -1) });
};

const handleNextMonth = () => {
openCurrent({ ...tab, month: addMonths(tab.month, 1) });
};

const handleToday = () => {
openCurrent({ ...tab, month: new Date() });
};

return (
<div className="flex flex-col h-full p-4">
<div className="mb-4 flex items-center justify-between">
<div className="text-xl font-semibold">
{format(tab.month, "MMMM yyyy")}
</div>
<div className="flex h-fit rounded-md overflow-clip border border-neutral-200">
<Button
variant="outline"
className="p-0.5 rounded-none border-none"
onClick={handlePreviousMonth}
>
<ChevronLeftIcon size={16} />
</Button>

<Button
variant="outline"
className="text-sm px-1 py-0.5 rounded-none border-none"
onClick={handleToday}
>
Today
</Button>

<Button
variant="outline"
className="p-0.5 rounded-none border-none"
onClick={handleNextMonth}
>
<ChevronRightIcon size={16} />
</Button>
</div>
</div>
<div className="grid grid-cols-7 gap-2">
{weekDays.map((day) => (
<div key={day} className="text-center text-sm font-medium text-muted-foreground p-2">
{day}
</div>
))}
{Array.from({ length: startDayOfWeek }).map((_, i) => <div key={`empty-${i}`} />)}
{days.map((day) => <TabContentCalendarDay key={day} day={day} />)}
</div>
</div>
);
}

function TabContentCalendarDay({ day }: { day: string }) {
const eventIds = persisted.UI.useSliceRowIds(
persisted.INDEXES.eventsByDate,
day,
persisted.STORE_ID,
);

const dayNumber = format(new Date(day), "d");
const isToday = format(new Date(), "yyyy-MM-dd") === day;

return (
<div
className={clsx([
"h-32 max-h-32 p-2 border rounded-md flex flex-col overflow-hidden",
isToday ? "bg-blue-50 border-blue-300" : "bg-background border-border",
])}
>
<div
className={clsx([
"text-sm font-medium mb-1 flex-shrink-0",
isToday && "text-blue-600",
])}
>
{dayNumber}
</div>
<div className="flex flex-col gap-1 overflow-y-auto">
{eventIds?.map((rowId) => <TabContentCalendarDayEvent key={rowId} rowId={rowId} />)}
</div>
</div>
);
}

function TabContentCalendarDayEvent({ rowId }: { rowId: string }) {
const event = persisted.UI.useRow("events", rowId, persisted.STORE_ID);
return <div className="text-xs bg-blue-100 px-1.5 py-0.5 rounded truncate">{event.title}</div>;
}
Loading
Loading