Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/app/notifications.tsx
1496 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { blue as ANTD_BLUE } from "@ant-design/colors";
7
import { Badge } from "antd";
8
import {
9
CSS,
10
React,
11
redux,
12
useActions,
13
useTypedRedux,
14
} from "@cocalc/frontend/app-framework";
15
import { Icon } from "@cocalc/frontend/components";
16
import { unreachable } from "@cocalc/util/misc";
17
import { COLORS } from "@cocalc/util/theme";
18
import track from "@cocalc/frontend/user-tracking";
19
import { PageStyle, TOP_BAR_ELEMENT_CLASS } from "./top-nav-consts";
20
import { blur_active_element } from "./util";
21
import { useEffect, useMemo } from "react";
22
import { set_window_title } from "@cocalc/frontend/browser";
23
24
interface Props {
25
type: "bell" | "notifications";
26
active: boolean;
27
pageStyle: PageStyle;
28
}
29
30
export const Notification: React.FC<Props> = React.memo((props: Props) => {
31
const { active, type, pageStyle } = props;
32
const { topPaddingIcons, sidePaddingIcons, fontSizeIcons } = pageStyle;
33
const newsBadgeOffset = `-${fontSizeIcons}`;
34
const page_actions = useActions("page");
35
36
const mentions_store = redux.getStore("mentions");
37
const mentions = useTypedRedux("mentions", "mentions");
38
const notify_count = useTypedRedux("file_use", "notify_count");
39
const news_unread = useTypedRedux("news", "unread");
40
const unread_message_count =
41
useTypedRedux("account", "unread_message_count") ?? 0;
42
43
const count = useMemo(() => {
44
switch (type) {
45
case "bell":
46
return notify_count ?? 0;
47
case "notifications":
48
return mentions_store.getUnreadSize() ?? 0;
49
default:
50
unreachable(type);
51
return 0;
52
}
53
}, [type, notify_count, mentions]);
54
55
useEffect(() => {
56
set_window_title();
57
}, [count, news_unread]);
58
59
const outer_style: CSS = {
60
padding: `${topPaddingIcons} ${sidePaddingIcons}`,
61
height: `${pageStyle.height}px`,
62
...(active ? { backgroundColor: COLORS.TOP_BAR.ACTIVE } : {}),
63
};
64
65
const inner_style: CSS = {
66
cursor: "pointer",
67
position: "relative",
68
...(type === "notifications"
69
? { top: Math.floor(pageStyle.height / 10) + 1 } // bit offset to make room for the badge
70
: { top: 1 }),
71
};
72
73
function onClick(e) {
74
e.preventDefault();
75
e.stopPropagation();
76
77
switch (type) {
78
case "bell":
79
page_actions.toggle_show_file_use();
80
blur_active_element();
81
if (!active) {
82
track("top_nav", { name: "file_use" });
83
}
84
break;
85
86
case "notifications":
87
page_actions.set_active_tab("notifications");
88
if (!active) {
89
track("top_nav", { name: "mentions" });
90
}
91
break;
92
93
default:
94
unreachable(type);
95
}
96
}
97
98
function renderBadge() {
99
switch (type) {
100
case "bell":
101
return (
102
<Badge
103
showZero
104
color={count == 0 ? COLORS.GRAY : undefined}
105
count={count}
106
className={count > 0 ? "smc-bell-notification" : ""}
107
/>
108
);
109
110
case "notifications":
111
// only wiggle, if there are unread news – because they clear out automatically.
112
// mentions can be more long term, i.e. keep them unread until you mark them done.
113
const wiggle = news_unread > 0 || unread_message_count > 0;
114
return (
115
<Badge
116
color={unread_message_count == 0 ? COLORS.GRAY : "green"}
117
count={unread_message_count}
118
size="small"
119
showZero={false}
120
offset={[0, `${fontSizeIcons}`]}
121
>
122
<Badge
123
color={count == 0 ? COLORS.GRAY : undefined}
124
count={count}
125
size="small"
126
>
127
<Badge
128
color={news_unread == 0 ? COLORS.GRAY : ANTD_BLUE.primary}
129
count={news_unread}
130
showZero={false}
131
size="small"
132
offset={[newsBadgeOffset, 0]}
133
>
134
<Icon
135
style={{ fontSize: fontSizeIcons }}
136
className={wiggle ? "smc-bell-notification" : ""}
137
name="mail"
138
/>{" "}
139
</Badge>
140
</Badge>
141
</Badge>
142
);
143
144
default:
145
unreachable(type);
146
}
147
}
148
149
const className = TOP_BAR_ELEMENT_CLASS + (active ? " active" : "");
150
151
return (
152
<div style={outer_style} onClick={onClick} className={className}>
153
<div style={inner_style}>{renderBadge()}</div>
154
</div>
155
);
156
});
157
158