Add "new" badge and title
parent
3a76e4733c
commit
1d2f3f72e4
|
@ -2,7 +2,14 @@ import db from "./db";
|
||||||
|
|
||||||
class SubscriptionManager {
|
class SubscriptionManager {
|
||||||
async all() {
|
async all() {
|
||||||
return db.subscriptions.toArray();
|
// All subscriptions, including "new count"; this is a JOIN, see https://dexie.org/docs/API-Reference#joining
|
||||||
|
const subscriptions = await db.subscriptions.toArray();
|
||||||
|
await Promise.all(subscriptions.map(async s => {
|
||||||
|
s.new = await db.notifications
|
||||||
|
.where({ subscriptionId: s.id, new: 1 })
|
||||||
|
.count();
|
||||||
|
}));
|
||||||
|
return subscriptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(subscriptionId) {
|
async get(subscriptionId) {
|
||||||
|
@ -14,7 +21,6 @@ class SubscriptionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateState(subscriptionId, state) {
|
async updateState(subscriptionId, state) {
|
||||||
console.log(`Update state: ${subscriptionId} ${state}`)
|
|
||||||
db.subscriptions.update(subscriptionId, { state: state });
|
db.subscriptions.update(subscriptionId, { state: state });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,10 +47,15 @@ class SubscriptionManager {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
notification.new = 1; // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
|
||||||
await db.notifications.add({ ...notification, subscriptionId }); // FIXME consider put() for double tab
|
await db.notifications.add({ ...notification, subscriptionId }); // FIXME consider put() for double tab
|
||||||
await db.subscriptions.update(subscriptionId, {
|
await db.subscriptions.update(subscriptionId, {
|
||||||
last: notification.id
|
last: notification.id
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[SubscriptionManager] Error adding notification`, e);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +80,12 @@ class SubscriptionManager {
|
||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async markNotificationsRead(subscriptionId) {
|
||||||
|
await db.notifications
|
||||||
|
.where({subscriptionId: subscriptionId, new: 1})
|
||||||
|
.modify({new: 0});
|
||||||
|
}
|
||||||
|
|
||||||
async pruneNotifications(thresholdTimestamp) {
|
async pruneNotifications(thresholdTimestamp) {
|
||||||
await db.notifications
|
await db.notifications
|
||||||
.where("time").below(thresholdTimestamp)
|
.where("time").below(thresholdTimestamp)
|
||||||
|
|
|
@ -10,7 +10,7 @@ const db = new Dexie('ntfy');
|
||||||
|
|
||||||
db.version(1).stores({
|
db.version(1).stores({
|
||||||
subscriptions: '&id,baseUrl',
|
subscriptions: '&id,baseUrl',
|
||||||
notifications: '&id,subscriptionId,time',
|
notifications: '&id,subscriptionId,time,new,[subscriptionId+new]', // compound key for query performance
|
||||||
users: '&baseUrl,username',
|
users: '&baseUrl,username',
|
||||||
prefs: '&key'
|
prefs: '&key'
|
||||||
});
|
});
|
||||||
|
|
|
@ -46,6 +46,7 @@ const Root = () => {
|
||||||
const users = useLiveQuery(() => userManager.all());
|
const users = useLiveQuery(() => userManager.all());
|
||||||
const subscriptions = useLiveQuery(() => subscriptionManager.all());
|
const subscriptions = useLiveQuery(() => subscriptionManager.all());
|
||||||
const selectedSubscription = findSelected(location, subscriptions);
|
const selectedSubscription = findSelected(location, subscriptions);
|
||||||
|
const newNotificationsCount = subscriptions?.reduce((prev, cur) => prev + cur.new, 0) || 0;
|
||||||
|
|
||||||
useWorkers();
|
useWorkers();
|
||||||
useConnectionListeners();
|
useConnectionListeners();
|
||||||
|
@ -54,6 +55,11 @@ const Root = () => {
|
||||||
connectionManager.refresh(subscriptions, users);
|
connectionManager.refresh(subscriptions, users);
|
||||||
}, [subscriptions, users]); // Dangle!
|
}, [subscriptions, users]); // Dangle!
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(`hello ${newNotificationsCount}`)
|
||||||
|
document.title = (newNotificationsCount > 0) ? `(${newNotificationsCount}) ntfy web` : "ntfy web";
|
||||||
|
}, [newNotificationsCount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{display: 'flex'}}>
|
<Box sx={{display: 'flex'}}>
|
||||||
<CssBaseline/>
|
<CssBaseline/>
|
||||||
|
|
|
@ -12,12 +12,13 @@ import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
import HomeIcon from '@mui/icons-material/Home';
|
import HomeIcon from '@mui/icons-material/Home';
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import SubscribeDialog from "./SubscribeDialog";
|
import SubscribeDialog from "./SubscribeDialog";
|
||||||
import {Alert, AlertTitle, CircularProgress, ListSubheader} from "@mui/material";
|
import {Alert, AlertTitle, Badge, CircularProgress, ListSubheader} from "@mui/material";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import {subscriptionRoute, topicShortUrl, topicUrl} from "../app/utils";
|
import {subscriptionRoute, topicShortUrl, topicUrl} from "../app/utils";
|
||||||
import {ConnectionState} from "../app/Connection";
|
import {ConnectionState} from "../app/Connection";
|
||||||
import {useLocation, useNavigate} from "react-router-dom";
|
import {useLocation, useNavigate} from "react-router-dom";
|
||||||
|
import subscriptionManager from "../app/SubscriptionManager";
|
||||||
|
|
||||||
const navWidth = 240;
|
const navWidth = 240;
|
||||||
|
|
||||||
|
@ -134,12 +135,16 @@ const SubscriptionItem = (props) => {
|
||||||
const subscription = props.subscription;
|
const subscription = props.subscription;
|
||||||
const icon = (subscription.state === ConnectionState.Connecting)
|
const icon = (subscription.state === ConnectionState.Connecting)
|
||||||
? <CircularProgress size="24px"/>
|
? <CircularProgress size="24px"/>
|
||||||
: <ChatBubbleOutlineIcon/>;
|
: <Badge badgeContent={subscription.new} invisible={subscription.new === 0} color="primary"><ChatBubbleOutlineIcon/></Badge>;
|
||||||
const label = (subscription.baseUrl === window.location.origin)
|
const label = (subscription.baseUrl === window.location.origin)
|
||||||
? subscription.topic
|
? subscription.topic
|
||||||
: topicShortUrl(subscription.baseUrl, subscription.topic);
|
: topicShortUrl(subscription.baseUrl, subscription.topic);
|
||||||
|
const handleClick = async () => {
|
||||||
|
navigate(subscriptionRoute(subscription));
|
||||||
|
await subscriptionManager.markNotificationsRead(subscription.id);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<ListItemButton onClick={() => navigate(subscriptionRoute(subscription))} selected={props.selected}>
|
<ListItemButton onClick={handleClick} selected={props.selected}>
|
||||||
<ListItemIcon>{icon}</ListItemIcon>
|
<ListItemIcon>{icon}</ListItemIcon>
|
||||||
<ListItemText primary={label}/>
|
<ListItemText primary={label}/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
|
|
|
@ -80,6 +80,10 @@ const NotificationItem = (props) => {
|
||||||
alt={`Priority ${notification.priority}`}
|
alt={`Priority ${notification.priority}`}
|
||||||
style={{ verticalAlign: 'bottom' }}
|
style={{ verticalAlign: 'bottom' }}
|
||||||
/>}
|
/>}
|
||||||
|
{notification.new === 1 &&
|
||||||
|
<svg style={{ width: '8px', height: '8px', marginLeft: '4px' }} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="50" cy="50" r="50" fill="#338574"/>
|
||||||
|
</svg>}
|
||||||
</Typography>
|
</Typography>
|
||||||
{notification.title && <Typography variant="h5" component="div">{formatTitle(notification)}</Typography>}
|
{notification.title && <Typography variant="h5" component="div">{formatTitle(notification)}</Typography>}
|
||||||
<Typography variant="body1" sx={{ whiteSpace: 'pre-line' }}>{formatMessage(notification)}</Typography>
|
<Typography variant="body1" sx={{ whiteSpace: 'pre-line' }}>{formatMessage(notification)}</Typography>
|
||||||
|
|
Loading…
Reference in New Issue