CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/app/notifications.tsx
Views: 687
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
9
import {
10
CSS,
11
React,
12
redux,
13
useActions,
14
useMemo,
15
useTypedRedux,
16
} from "@cocalc/frontend/app-framework";
17
import { Icon } from "@cocalc/frontend/components";
18
import { unreachable } from "@cocalc/util/misc";
19
import { COLORS } from "@cocalc/util/theme";
20
import track from "@cocalc/frontend/user-tracking";
21
import { PageStyle, TOP_BAR_ELEMENT_CLASS } from "./top-nav-consts";
22
import { blur_active_element } from "./util";
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
41
const count = useMemo(() => {
42
switch (type) {
43
case "bell":
44
return notify_count ?? 0;
45
case "notifications":
46
return mentions_store.get_unseen_size(mentions) ?? 0;
47
default:
48
unreachable(type);
49
return 0;
50
}
51
}, [type, notify_count, mentions]);
52
53
const outer_style: CSS = {
54
padding: `${topPaddingIcons} ${sidePaddingIcons}`,
55
height: `${pageStyle.height}px`,
56
...(active ? { backgroundColor: COLORS.TOP_BAR.ACTIVE } : {}),
57
};
58
59
const inner_style: CSS = {
60
cursor: "pointer",
61
position: "relative",
62
...(type === "notifications"
63
? { top: Math.floor(pageStyle.height / 10) + 1 } // bit offset to make room for the badge
64
: { top: 1 }),
65
};
66
67
function onClick(e) {
68
e.preventDefault();
69
e.stopPropagation();
70
71
switch (type) {
72
case "bell":
73
page_actions.toggle_show_file_use();
74
blur_active_element();
75
if (!active) {
76
track("top_nav", { name: "file_use" });
77
}
78
break;
79
80
case "notifications":
81
page_actions.set_active_tab("notifications");
82
83
// the idea of the following is to make sure the user sees immediately the most important notifications
84
if (count > 0) {
85
// mentions are more important, and this makes them shown to the user
86
redux.getActions("mentions").set_filter("unread");
87
} else if (news_unread > 0) {
88
// similar to the above, guide user towards seeing the news (if there are no mentions)
89
redux.getActions("mentions").set_filter("allNews");
90
}
91
92
if (!active) {
93
track("top_nav", { name: "mentions" });
94
}
95
break;
96
97
default:
98
unreachable(type);
99
}
100
}
101
102
function renderBadge() {
103
switch (type) {
104
case "bell":
105
return (
106
<Badge
107
showZero
108
color={count == 0 ? COLORS.GRAY : undefined}
109
count={count}
110
className={count > 0 ? "smc-bell-notification" : ""}
111
/>
112
);
113
114
case "notifications":
115
// only wiggle, if there are unread news – because they clear out automatically.
116
// mentions can be more long term, i.e. keep them unread until you mark them done.
117
const wiggle = news_unread > 0;
118
return (
119
<Badge
120
color={count == 0 ? COLORS.GRAY : undefined}
121
count={count}
122
size="small"
123
>
124
<Badge
125
color={news_unread == 0 ? COLORS.GRAY : ANTD_BLUE.primary}
126
count={news_unread}
127
showZero={false}
128
size="small"
129
offset={[newsBadgeOffset, 0]}
130
>
131
<Icon
132
style={{ fontSize: fontSizeIcons }}
133
className={wiggle ? "smc-bell-notification" : ""}
134
name="mail"
135
/>{" "}
136
</Badge>
137
</Badge>
138
);
139
140
default:
141
unreachable(type);
142
}
143
}
144
145
const className = TOP_BAR_ELEMENT_CLASS + (active ? " active" : "");
146
147
return (
148
<div style={outer_style} onClick={onClick} className={className}>
149
<div style={inner_style}>{renderBadge()}</div>
150
</div>
151
);
152
});
153
154