added integration for calendar to have twake top bar (#576)

This commit is contained in:
Camille Moussu
2026-02-25 11:01:09 +01:00
committed by GitHub
parent 9cc6625e84
commit 8c3fa4bc1b
6 changed files with 84 additions and 43 deletions
+16
View File
@@ -23,6 +23,7 @@
"@mui/material": "^7.1.2", "@mui/material": "^7.1.2",
"@mui/x-date-pickers": "^8.14.0", "@mui/x-date-pickers": "^8.14.0",
"@reduxjs/toolkit": "^2.8.2", "@reduxjs/toolkit": "^2.8.2",
"cozy-external-bridge": "^1.2.1",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"i18next": "^23.7.9", "i18next": "^23.7.9",
"ical.js": "^2.2.0", "ical.js": "^2.2.0",
@@ -6387,6 +6388,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/comlink": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.1.tgz",
"integrity": "sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==",
"license": "Apache-2.0"
},
"node_modules/commander": { "node_modules/commander": {
"version": "9.5.0", "version": "9.5.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
@@ -6547,6 +6554,15 @@
"js-yaml": "bin/js-yaml.js" "js-yaml": "bin/js-yaml.js"
} }
}, },
"node_modules/cozy-external-bridge": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/cozy-external-bridge/-/cozy-external-bridge-1.2.1.tgz",
"integrity": "sha512-Vbf3ugIuYWGHHD4fiWdUIvljadpHiV/GF1YaETMmlmOlxagiwbK3NESlAiXoGgg5aHdLjXliGJy+j9ccMdVm/g==",
"license": "MIT",
"dependencies": {
"comlink": "4.4.1"
}
},
"node_modules/create-require": { "node_modules/create-require": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+1
View File
@@ -18,6 +18,7 @@
"@mui/material": "^7.1.2", "@mui/material": "^7.1.2",
"@mui/x-date-pickers": "^8.14.0", "@mui/x-date-pickers": "^8.14.0",
"@reduxjs/toolkit": "^2.8.2", "@reduxjs/toolkit": "^2.8.2",
"cozy-external-bridge": "^1.2.1",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"i18next": "^23.7.9", "i18next": "^23.7.9",
"ical.js": "^2.2.0", "ical.js": "^2.2.0",
+4
View File
@@ -33,6 +33,7 @@ import { User } from "../Attendees/PeopleSearch";
import { EventErrorSnackbar } from "../Error/ErrorSnackbar"; import { EventErrorSnackbar } from "../Error/ErrorSnackbar";
import { EventErrorHandler } from "../Error/EventErrorHandler"; import { EventErrorHandler } from "../Error/EventErrorHandler";
import { EditModeDialog } from "../Event/EditModeDialog"; import { EditModeDialog } from "../Event/EditModeDialog";
import { Menubar, MenubarProps } from "../Menubar/Menubar";
import "./Calendar.styl"; import "./Calendar.styl";
import CalendarSelection from "./CalendarSelection"; import CalendarSelection from "./CalendarSelection";
import "./CustomCalendar.styl"; import "./CustomCalendar.styl";
@@ -59,12 +60,14 @@ interface CalendarAppProps {
calendarRef: MutableRefObject<CalendarApi | null>; calendarRef: MutableRefObject<CalendarApi | null>;
onDateChange?: (date: Date) => void; onDateChange?: (date: Date) => void;
onViewChange?: (view: string) => void; onViewChange?: (view: string) => void;
menubarProps?: MenubarProps;
} }
export default function CalendarApp({ export default function CalendarApp({
calendarRef, calendarRef,
onDateChange, onDateChange,
onViewChange, onViewChange,
menubarProps,
}: CalendarAppProps) { }: CalendarAppProps) {
const [selectedDate, setSelectedDate] = useState(new Date()); const [selectedDate, setSelectedDate] = useState(new Date());
const [selectedMiniDate, setSelectedMiniDate] = useState(new Date()); const [selectedMiniDate, setSelectedMiniDate] = useState(new Date());
@@ -661,6 +664,7 @@ export default function CalendarApp({
</Box> </Box>
<div className="calendar"> <div className="calendar">
<ImportAlert /> <ImportAlert />
{menubarProps?.isIframe && <Menubar {...menubarProps} />}
{view === "calendar" && ( {view === "calendar" && (
<FullCalendar <FullCalendar
ref={(ref) => { ref={(ref) => {
+6 -2
View File
@@ -2,7 +2,8 @@ import { useAppDispatch, useAppSelector } from "@/app/hooks";
import SettingsPage from "@/features/Settings/SettingsPage"; import SettingsPage from "@/features/Settings/SettingsPage";
import { getCalendarRange } from "@/utils/dateUtils"; import { getCalendarRange } from "@/utils/dateUtils";
import type { CalendarApi } from "@fullcalendar/core"; import type { CalendarApi } from "@fullcalendar/core";
import { useEffect, useRef, useState } from "react"; import CozyBridge from "cozy-external-bridge";
import { useEffect, useMemo, useRef, useState } from "react";
import { ErrorSnackbar } from "../Error/ErrorSnackbar"; import { ErrorSnackbar } from "../Error/ErrorSnackbar";
import { refreshCalendars } from "../Event/utils/eventUtils"; import { refreshCalendars } from "../Event/utils/eventUtils";
import { Menubar, MenubarProps } from "../Menubar/Menubar"; import { Menubar, MenubarProps } from "../Menubar/Menubar";
@@ -17,6 +18,7 @@ export default function CalendarLayout() {
const view = useAppSelector((state) => state.settings.view); const view = useAppSelector((state) => state.settings.view);
const [currentDate, setCurrentDate] = useState<Date>(new Date()); const [currentDate, setCurrentDate] = useState<Date>(new Date());
const [currentView, setCurrentView] = useState<string>("timeGridWeek"); const [currentView, setCurrentView] = useState<string>("timeGridWeek");
const isInIframe = useMemo(() => new CozyBridge().isInIframe(), []);
const handleRefresh = async () => { const handleRefresh = async () => {
// Get current calendar range // Get current calendar range
@@ -70,16 +72,18 @@ export default function CalendarLayout() {
onDateChange: handleDateChange, onDateChange: handleDateChange,
currentView, currentView,
onViewChange: handleViewChange, onViewChange: handleViewChange,
isIframe: isInIframe,
}; };
return ( return (
<div className="App"> <div className="App">
<Menubar {...menubarProps} /> {!isInIframe && <Menubar {...menubarProps} />}
{(view === "calendar" || view === "search") && ( {(view === "calendar" || view === "search") && (
<CalendarApp <CalendarApp
calendarRef={calendarRef} calendarRef={calendarRef}
onDateChange={handleDateChange} onDateChange={handleDateChange}
onViewChange={handleViewChange} onViewChange={handleViewChange}
menubarProps={menubarProps}
/> />
)} )}
{view === "settings" && <SettingsPage />} {view === "settings" && <SettingsPage />}
+57 -41
View File
@@ -46,6 +46,7 @@ export type MenubarProps = {
onDateChange?: (date: Date) => void; onDateChange?: (date: Date) => void;
currentView: string; currentView: string;
onViewChange?: (view: string) => void; onViewChange?: (view: string) => void;
isIframe?: boolean;
}; };
export function Menubar({ export function Menubar({
@@ -55,6 +56,7 @@ export function Menubar({
onDateChange, onDateChange,
currentView, currentView,
onViewChange, onViewChange,
isIframe,
}: MenubarProps) { }: MenubarProps) {
const { t } = useI18n(); // deliberately NOT using f() const { t } = useI18n(); // deliberately NOT using f()
const user = useAppSelector((state) => state.user.userData); const user = useAppSelector((state) => state.user.userData);
@@ -155,14 +157,16 @@ export function Menubar({
<> <>
<header className="menubar"> <header className="menubar">
<div className="left-menu"> <div className="left-menu">
<div className="menu-items"> {!isIframe && (
<MainTitle <div className="menu-items">
calendarRef={calendarRef} <MainTitle
currentView={currentView} calendarRef={calendarRef}
onViewChange={onViewChange} currentView={currentView}
onDateChange={onDateChange} onViewChange={onViewChange}
/> onDateChange={onDateChange}
</div> />
</div>
)}
<div className="menu-items"> <div className="menu-items">
<div className="navigation-controls"> <div className="navigation-controls">
<ButtonGroup <ButtonGroup
@@ -245,41 +249,53 @@ export function Menubar({
</Select> </Select>
</FormControl> </FormControl>
</div> </div>
<div className="menu-items"> {!isIframe && (
<IconButton <>
component="a" <div className="menu-items">
href="https://twake.app/support/" <IconButton
target="_blank" component="a"
rel="noopener noreferrer" href="https://twake.app/support/"
style={{ marginRight: 8 }} target="_blank"
aria-label={t("menubar.help")} rel="noopener noreferrer"
title={t("menubar.help")} style={{ marginRight: 8 }}
> aria-label={t("menubar.help")}
<HelpOutlineIcon /> title={t("menubar.help")}
</IconButton> >
</div> <HelpOutlineIcon />
<div className="menu-items"> </IconButton>
{applist.length > 0 && ( </div>
<IconButton
onClick={handleOpen} <div className="menu-items">
style={{ marginRight: 8 }} {applist.length > 0 && (
aria-label={t("menubar.apps")} <IconButton
title={t("menubar.apps")} onClick={handleOpen}
> style={{ marginRight: 8 }}
<WidgetsOutlinedIcon /> aria-label={t("menubar.apps")}
</IconButton> title={t("menubar.apps")}
)} >
</div> <WidgetsOutlinedIcon />
</IconButton>
)}
</div>
</>
)}
<div className="menu-items"> <div className="menu-items">
<IconButton onClick={handleUserMenuClick}> <IconButton
<Avatar onClick={!isIframe ? handleUserMenuClick : handleSettingsClick}
color={stringToGradient(getUserDisplayName(user))} aria-label={isIframe ? t("menubar.settings") : undefined}
size="m" >
aria-label={t("menubar.userProfile")} {!isIframe ? (
> <Avatar
{getInitials(getUserDisplayName(user))} color={stringToGradient(getUserDisplayName(user))}
</Avatar> size="m"
aria-label={t("menubar.userProfile")}
>
{getInitials(getUserDisplayName(user))}
</Avatar>
) : (
<SettingsOutlinedIcon />
)}
</IconButton> </IconButton>
</div> </div>
</div> </div>