mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 13:54:20 +03:00
grid: update for new hark timeboxing
This commit is contained in:
parent
1d94d08d60
commit
993b529b9d
@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import { BrowserRouter, Switch, Route, useHistory } from 'react-router-dom';
|
||||
import { BrowserRouter, Switch, Route, useHistory, useLocation } from 'react-router-dom';
|
||||
import { Grid } from './pages/Grid';
|
||||
import useDocketState from './state/docket';
|
||||
import { PermalinkRoutes } from './pages/PermalinkRoutes';
|
||||
@ -9,9 +9,26 @@ import { usePreferencesStore } from './nav/preferences/usePreferencesStore';
|
||||
import useContactState from './state/contact';
|
||||
import api from './state/api';
|
||||
|
||||
const getNoteRedirect = (path: string) => {
|
||||
if (path.startsWith('/desk/')) {
|
||||
const [, , desk] = path.split('/');
|
||||
return `/app/${desk}`;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const AppRoutes = () => {
|
||||
const { push } = useHistory();
|
||||
const theme = usePreferencesStore((s) => s.theme);
|
||||
const { search } = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const query = new URLSearchParams(location.search);
|
||||
if (query.has('grid-note')) {
|
||||
const redir = getNoteRedirect(query.get('grid-note')!);
|
||||
push(redir);
|
||||
}
|
||||
}, [location.search]);
|
||||
|
||||
const updateThemeClass = useCallback(
|
||||
(e: MediaQueryListEvent) => {
|
||||
@ -36,6 +53,8 @@ const AppRoutes = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {}, []);
|
||||
|
||||
useEffect(() => {
|
||||
window.name = 'grid';
|
||||
|
||||
|
32
pkg/grid/src/components/DeskLink.tsx
Normal file
32
pkg/grid/src/components/DeskLink.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useCharge } from '../state/docket';
|
||||
import { getAppHref } from '../state/util';
|
||||
|
||||
interface DeskLinkProps extends React.AnchorHTMLAttributes<any> {
|
||||
desk: string;
|
||||
to?: string;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function DeskLink({ children, className, desk, to = '', ...rest }: DeskLinkProps) {
|
||||
const charge = useCharge(desk);
|
||||
|
||||
if (!charge) {
|
||||
return null;
|
||||
}
|
||||
if (desk === window.desk) {
|
||||
return (
|
||||
<Link to={to} className={className} {...rest}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
const href = `${getAppHref(charge.href)}${to}`;
|
||||
return (
|
||||
<a href={href} target={desk} className={className} {...rest}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, NavLink, Route, Switch } from 'react-router-dom';
|
||||
import { Notification } from '@urbit/api';
|
||||
import { useLeapStore } from './Nav';
|
||||
import { Button } from '../components/Button';
|
||||
@ -11,6 +11,7 @@ import {
|
||||
import { useNotifications } from '../state/notifications';
|
||||
import { useHarkStore } from '../state/hark';
|
||||
import { OnboardingNotification } from './notifications/OnboardingNotification';
|
||||
import { Inbox } from './notifications/Inbox';
|
||||
|
||||
function renderNotification(notification: Notification, key: string, unread = false) {
|
||||
// Special casing
|
||||
@ -36,7 +37,7 @@ const Empty = () => (
|
||||
|
||||
export const Notifications = () => {
|
||||
const select = useLeapStore((s) => s.select);
|
||||
const { unreads, reads, hasAnyNotifications } = useNotifications();
|
||||
const { unseen, seen, hasAnyNotifications } = useNotifications();
|
||||
const markAllAsRead = () => {
|
||||
const { readAll } = useHarkStore.getState();
|
||||
readAll();
|
||||
@ -46,12 +47,37 @@ export const Notifications = () => {
|
||||
select('Notifications');
|
||||
const { getMore } = useHarkStore.getState();
|
||||
getMore();
|
||||
|
||||
function visibilitychange() {
|
||||
useHarkStore.getState().opened();
|
||||
}
|
||||
document.addEventListener('visibilitychange', visibilitychange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', visibilitychange);
|
||||
useHarkStore.getState().opened();
|
||||
};
|
||||
}, []);
|
||||
// const select = useLeapStore((s) => s.select);
|
||||
|
||||
return (
|
||||
<div className="grid grid-rows-[auto,1fr] h-full p-4 md:p-8 overflow-hidden">
|
||||
<header className="space-x-2 mb-8">
|
||||
<NavLink
|
||||
exact
|
||||
activeClassName="text-black"
|
||||
className="text-base font-semibold px-4"
|
||||
to="/leap/notifications"
|
||||
>
|
||||
New
|
||||
</NavLink>
|
||||
<NavLink
|
||||
activeClassName="text-black"
|
||||
className="text-base font-semibold px-4"
|
||||
to="/leap/notifications/archive"
|
||||
>
|
||||
Archive
|
||||
</NavLink>
|
||||
<Button onClick={markAllAsRead} variant="secondary" className="py-1.5 px-6 rounded-full">
|
||||
Mark All as Read
|
||||
</Button>
|
||||
@ -64,17 +90,14 @@ export const Notifications = () => {
|
||||
Notification Settings
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
{!hasAnyNotifications && <Empty />}
|
||||
{hasAnyNotifications && (
|
||||
<section className="text-gray-400 space-y-2 overflow-y-auto">
|
||||
{unreads.map((n, index) => renderNotification(n, index.toString(), true))}
|
||||
{Array.from(reads)
|
||||
.map(([, nots]) => nots)
|
||||
.flat()
|
||||
.map((n, index) => renderNotification(n, `reads-${index}`))}
|
||||
</section>
|
||||
)}
|
||||
<Switch>
|
||||
<Route path="/leap/notifications" exact>
|
||||
<Inbox />
|
||||
</Route>
|
||||
<Route path="/leap/notifications/archive" exact>
|
||||
<Inbox archived />
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { Notification } from '@urbit/api';
|
||||
import { Notification, Timebox } from '@urbit/api';
|
||||
import { Link, LinkProps } from 'react-router-dom';
|
||||
import { Bullet } from '../components/icons/Bullet';
|
||||
import { useNotifications } from '../state/notifications';
|
||||
@ -8,7 +8,8 @@ import { MenuState } from './Nav';
|
||||
|
||||
type NotificationsState = 'empty' | 'unread' | 'attention-needed';
|
||||
|
||||
function getNotificationsState(notifications: Notification[]): NotificationsState {
|
||||
function getNotificationsState(box: Timebox): NotificationsState {
|
||||
const notifications = Object.values(box);
|
||||
if (notifications.filter(({ bin }) => bin.place.desk === window.desk).length > 0) {
|
||||
return 'attention-needed';
|
||||
}
|
||||
@ -27,8 +28,8 @@ type NotificationsLinkProps = Omit<LinkProps<HTMLAnchorElement>, 'to'> & {
|
||||
};
|
||||
|
||||
export const NotificationsLink = ({ navOpen, menu }: NotificationsLinkProps) => {
|
||||
const { unreads } = useNotifications();
|
||||
const state = getNotificationsState(unreads);
|
||||
const { unseen } = useNotifications();
|
||||
const state = getNotificationsState(unseen);
|
||||
|
||||
return (
|
||||
<Link
|
||||
@ -44,7 +45,7 @@ export const NotificationsLink = ({ navOpen, menu }: NotificationsLinkProps) =>
|
||||
)}
|
||||
>
|
||||
{state === 'empty' && <Bullet className="w-6 h-6" />}
|
||||
{state === 'unread' && unreads.length}
|
||||
{state === 'unread' && Object.keys(unseen).length}
|
||||
{state === 'attention-needed' && (
|
||||
<span className="h2">
|
||||
! <span className="sr-only">Attention needed</span>
|
||||
|
@ -1,25 +1,28 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { Notification, harkBinToId, HarkContent } from '@urbit/api';
|
||||
import { Notification, harkBinToId, HarkContent, HarkLid } from '@urbit/api';
|
||||
import { map, take } from 'lodash';
|
||||
import { useCharge } from '../../state/docket';
|
||||
import { Elbow } from '../../components/icons/Elbow';
|
||||
import { ShipName } from '../../components/ShipName';
|
||||
import { getAppHref } from '../../state/util';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { DeskLink } from '../../components/DeskLink';
|
||||
import {useHarkStore} from '../../state/hark';
|
||||
|
||||
interface BasicNotificationProps {
|
||||
notification: Notification;
|
||||
unread?: boolean;
|
||||
lid: HarkLid;
|
||||
}
|
||||
|
||||
const MAX_CONTENTS = 20;
|
||||
const MAX_CONTENTS = 5;
|
||||
|
||||
const NotificationText = ({ contents }: { contents: HarkContent[] }) => {
|
||||
return (
|
||||
<>
|
||||
{contents.map((content, idx) => {
|
||||
if ('ship' in content) {
|
||||
return <ShipName key={idx} name={content.ship} />;
|
||||
return <ShipName className="color-blue" key={idx} name={content.ship} />;
|
||||
}
|
||||
return content.text;
|
||||
})}
|
||||
@ -27,7 +30,7 @@ const NotificationText = ({ contents }: { contents: HarkContent[] }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const BasicNotification = ({ notification, unread = false }: BasicNotificationProps) => {
|
||||
export const BasicNotification = ({ notification, lid }: BasicNotificationProps) => {
|
||||
const { desk } = notification.bin.place;
|
||||
const binId = harkBinToId(notification.bin);
|
||||
const id = `notif-${notification.time}-${binId}`;
|
||||
@ -39,15 +42,19 @@ export const BasicNotification = ({ notification, unread = false }: BasicNotific
|
||||
}
|
||||
const contents = map(notification.body, 'content').filter((c) => c.length > 0);
|
||||
const large = contents.length === 0;
|
||||
const link = `${getAppHref(charge.href)}?grid-note=${encodeURIComponent(first.link)}`;
|
||||
const archive = () => {
|
||||
const { bin } = notification;
|
||||
useHarkStore.getState().archiveNote(notification.bin, lid);
|
||||
};
|
||||
|
||||
return (
|
||||
<a
|
||||
href={link}
|
||||
target={desk}
|
||||
<DeskLink
|
||||
onClick={archive}
|
||||
to={`?grid-note=${encodeURIComponent(first.link)}`}
|
||||
desk={desk}
|
||||
className={cn(
|
||||
'text-black rounded',
|
||||
unread ? 'bg-blue-100' : 'bg-gray-100',
|
||||
'unseen' in lid ? 'bg-blue-100' : 'bg-gray-100',
|
||||
large ? 'note-grid-no-content' : 'note-grid-content'
|
||||
)}
|
||||
aria-labelledby={id}
|
||||
@ -75,6 +82,6 @@ export const BasicNotification = ({ notification, unread = false }: BasicNotific
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</a>
|
||||
</DeskLink>
|
||||
);
|
||||
};
|
||||
|
92
pkg/grid/src/nav/notifications/Inbox.tsx
Normal file
92
pkg/grid/src/nav/notifications/Inbox.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { HarkLid, Notification } from '@urbit/api';
|
||||
import { useLeapStore } from '../Nav';
|
||||
import { Button } from '../../components/Button';
|
||||
import { BasicNotification } from './BasicNotification';
|
||||
import { BaseBlockedNotification, RuntimeLagNotification } from './SystemNotification';
|
||||
import { useNotifications } from '../../state/notifications';
|
||||
import { useHarkStore } from '../../state/hark';
|
||||
import { OnboardingNotification } from './OnboardingNotification';
|
||||
|
||||
function renderNotification(notification: Notification, key: string, lid: HarkLid) {
|
||||
// Special casing
|
||||
if (notification.bin.place.desk === window.desk) {
|
||||
if (notification.bin.place.path === '/lag') {
|
||||
return <RuntimeLagNotification key={key} />;
|
||||
}
|
||||
if (notification.bin.place.path === '/blocked') {
|
||||
return <BaseBlockedNotification key={key} />;
|
||||
}
|
||||
if (notification.bin.place.path === '/onboard') {
|
||||
return <OnboardingNotification key={key} unread={false} />;
|
||||
}
|
||||
}
|
||||
return <BasicNotification key={key} notification={notification} lid={lid} />;
|
||||
}
|
||||
|
||||
const Empty = () => (
|
||||
<section className="flex justify-center items-center min-h-[480px] text-gray-400 space-y-2">
|
||||
<span className="h4">All clear!</span>
|
||||
</section>
|
||||
);
|
||||
|
||||
export const Inbox = ({ archived = false }) => {
|
||||
const select = useLeapStore((s) => s.select);
|
||||
const { unseen, seen, hasAnyNotifications } = useNotifications();
|
||||
const archive = useHarkStore((s) => s.archive);
|
||||
const markAllAsRead = () => {};
|
||||
|
||||
useEffect(() => {
|
||||
useHarkStore.getState().getMore();
|
||||
|
||||
}, [archived]);
|
||||
|
||||
useEffect(() => {
|
||||
select('Notifications');
|
||||
const { getMore } = useHarkStore.getState();
|
||||
getMore();
|
||||
|
||||
function visibilitychange() {
|
||||
setTimeout(() => useHarkStore.getState().opened(), 100);
|
||||
}
|
||||
document.addEventListener('visibilitychange', visibilitychange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', visibilitychange);
|
||||
visibilitychange();
|
||||
};
|
||||
}, []);
|
||||
// const select = useLeapStore((s) => s.select);
|
||||
|
||||
if (false) {
|
||||
return <Empty />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-gray-400 space-y-2 overflow-y-auto">
|
||||
{archived ? (
|
||||
Array.from(archive).map(([key, box]) => {
|
||||
return Object.entries(box)
|
||||
.sort(([, a], [, b]) => b.time - a.time)
|
||||
.map(([binId, n], index) => renderNotification(n, `${key.toString}-${binId}`, { time: key.toString() }));
|
||||
})
|
||||
) : (
|
||||
<>
|
||||
<header>Unseen</header>
|
||||
<section className="space-y-2">
|
||||
{Object.entries(unseen)
|
||||
.sort(([, a], [, b]) => b.time - a.time)
|
||||
.map(([binId, n], index) => renderNotification(n, `unseen-${binId}`, { unseen: null }))}
|
||||
</section>
|
||||
<header>Seen</header>
|
||||
<section className="space-y-2">
|
||||
{Object.entries(seen)
|
||||
.sort(([, a], [, b]) => b.time - a.time)
|
||||
.map(([binId, n], index) => renderNotification(n, `seen-${binId}`, { seen: null }))}
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -47,7 +47,7 @@ const OnboardingCard = ({ title, button, body, color }: OnboardingCardProps) =>
|
||||
style={color ? { backgroundColor: color } : {}}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<h4 className="font-semibold">{title}</h4>
|
||||
<h4 className="font-semibold text-black">{title}</h4>
|
||||
<p>{body}</p>
|
||||
</div>
|
||||
<Button variant="primary" className="bg-gray-500">
|
||||
|
@ -6,7 +6,11 @@ import { usePreferencesStore } from './usePreferencesStore';
|
||||
const selDnd = (s: SettingsState) => s.display.doNotDisturb;
|
||||
async function toggleDnd() {
|
||||
const state = useSettingsState.getState();
|
||||
await state.putEntry('display', 'doNotDisturb', !selDnd(state));
|
||||
const curr = selDnd(state);
|
||||
if(curr) {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
await state.putEntry('display', 'doNotDisturb', !curr);
|
||||
}
|
||||
|
||||
export const NotificationPrefs = () => {
|
||||
|
@ -1,6 +1,20 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import create from 'zustand';
|
||||
import { Notification, harkBinEq, makePatDa, readAll, decToUd, unixToDa } from '@urbit/api';
|
||||
import {
|
||||
Notification as HarkNotification,
|
||||
harkBinEq,
|
||||
makePatDa,
|
||||
readAll,
|
||||
decToUd,
|
||||
unixToDa,
|
||||
Timebox,
|
||||
harkBinToId,
|
||||
opened,
|
||||
HarkBin,
|
||||
HarkLid,
|
||||
archive,
|
||||
HarkContent
|
||||
} from '@urbit/api';
|
||||
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
|
||||
/* eslint-disable-next-line camelcase */
|
||||
import { unstable_batchedUpdates } from 'react-dom';
|
||||
@ -9,37 +23,42 @@ import { map } from 'lodash';
|
||||
import api from './api';
|
||||
import { useMockData } from './util';
|
||||
import { mockNotifications } from './mock-data';
|
||||
import useDocketState from './docket';
|
||||
import { useSettingsState } from './settings';
|
||||
|
||||
interface HarkStore {
|
||||
unreads: Notification[];
|
||||
reads: BigIntOrderedMap<Notification[]>;
|
||||
seen: Timebox;
|
||||
unseen: Timebox;
|
||||
archive: BigIntOrderedMap<Timebox>;
|
||||
set: (f: (s: HarkStore) => void) => void;
|
||||
readAll: () => Promise<void>;
|
||||
opened: () => Promise<void>;
|
||||
archiveAll: () => Promise<void>;
|
||||
archiveNote: (bin: HarkBin, lid: HarkLid) => Promise<void>;
|
||||
getMore: () => Promise<void>;
|
||||
webNotes: {
|
||||
[binId: string]: Notification[];
|
||||
};
|
||||
}
|
||||
|
||||
export const useHarkStore = create<HarkStore>((set, get) => ({
|
||||
unreads: useMockData ? mockNotifications : [],
|
||||
reads: new BigIntOrderedMap<Notification[]>(),
|
||||
seen: {},
|
||||
unseen: {},
|
||||
archive: new BigIntOrderedMap<Timebox>(),
|
||||
webNotes: {},
|
||||
set: (f) => {
|
||||
const newState = produce(get(), f);
|
||||
set(newState);
|
||||
},
|
||||
readAll: async () => {
|
||||
const { set: innerSet } = get();
|
||||
innerSet((state) => {
|
||||
state.unreads.forEach((note) => {
|
||||
const time = makePatDa(note.time as any);
|
||||
const box = state.reads.get(time) || [];
|
||||
state.reads.set(time, [...box, note]);
|
||||
});
|
||||
state.unreads = [];
|
||||
});
|
||||
await api.poke(readAll);
|
||||
archiveAll: async () => {},
|
||||
archiveNote: async (bin, lid) => {
|
||||
await api.poke(archive(bin, lid));
|
||||
},
|
||||
opened: async () => {
|
||||
await api.poke(opened);
|
||||
},
|
||||
getMore: async () => {
|
||||
const { reads } = get();
|
||||
const idx = decToUd((reads.peekSmallest()?.[0] || unixToDa(Date.now() * 1000)).toString());
|
||||
const { archive } = get();
|
||||
const idx = decToUd((archive.peekSmallest()?.[0] || unixToDa(Date.now() * 1000)).toString());
|
||||
const update = await api.scry({
|
||||
app: 'hark-store',
|
||||
path: `/recent/inbox/${idx}/5`
|
||||
@ -60,35 +79,59 @@ function reduceHark(u: any) {
|
||||
} else if ('all-stats' in u) {
|
||||
// TODO: probably ignore?
|
||||
} else if ('added' in u) {
|
||||
set((state) => {
|
||||
state.unreads = state.unreads.filter((unread) => !harkBinEq(unread.bin, u.added.bin));
|
||||
|
||||
state.unreads.unshift(u.added);
|
||||
set((draft) => {
|
||||
const { bin } = u.added;
|
||||
const binId = harkBinToId(bin);
|
||||
draft.unseen[binId] = u.added;
|
||||
});
|
||||
} else if ('timebox' in u) {
|
||||
const { timebox } = u;
|
||||
const notifications = map(timebox.notifications, 'notification');
|
||||
if (timebox.time) {
|
||||
console.log(timebox);
|
||||
const { lid, notifications } = timebox;
|
||||
if ('archive' in lid) {
|
||||
set((draft) => {
|
||||
const time = makePatDa(lid.archive);
|
||||
const timebox = draft.archive.get(time) || {};
|
||||
console.log(timebox);
|
||||
notifications.forEach((note: any) => {
|
||||
console.log(note);
|
||||
const binId = harkBinToId(note.bin);
|
||||
timebox[binId] = note;
|
||||
});
|
||||
console.log(notifications);
|
||||
set((state) => {
|
||||
state.reads = state.reads.set(makePatDa(timebox.time), notifications);
|
||||
draft.archive = draft.archive.set(time, timebox);
|
||||
});
|
||||
} else {
|
||||
set((state) => {
|
||||
state.unreads = [...state.unreads, ...notifications];
|
||||
set((draft) => {
|
||||
const seen = 'seen' in lid ? 'seen' : 'unseen';
|
||||
notifications.forEach((note: any) => {
|
||||
const binId = harkBinToId(note.bin);
|
||||
draft[seen][binId] = note;
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if ('archive' in u) {
|
||||
console.log(u.archive);
|
||||
set((state) => {
|
||||
const time = u.archive?.time;
|
||||
if (time) {
|
||||
let box = state.reads.get(makePatDa(time)) || [];
|
||||
box = box.filter((n) => !harkBinEq(u.archive.bin, n.bin));
|
||||
state.reads = state.reads.set(makePatDa(time), box);
|
||||
} else {
|
||||
state.unreads = state.unreads.filter((n) => !harkBinEq(u.archive.bin, n.bin));
|
||||
}
|
||||
} else if ('archived' in u) {
|
||||
const { lid, notification } = u.archived;
|
||||
console.log(u.archived);
|
||||
set((draft) => {
|
||||
const seen = 'seen' in lid ? 'seen' : 'unseen';
|
||||
const binId = harkBinToId(notification.bin);
|
||||
delete draft[seen][binId];
|
||||
const time = makePatDa(u.archived.time);
|
||||
const timebox = draft.archive.get(time) || {};
|
||||
timebox[binId] = notification;
|
||||
draft.archive = draft.archive.set(time, timebox);
|
||||
});
|
||||
} else if ('opened' in u) {
|
||||
set((draft) => {
|
||||
const bins = Object.keys(draft.unseen);
|
||||
bins.forEach((bin) => {
|
||||
const old = draft.seen[bin];
|
||||
const curr = draft.unseen[bin];
|
||||
curr.body = [...curr.body, ...(old?.body || [])];
|
||||
draft.seen[bin] = curr;
|
||||
delete draft.unseen[bin];
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -103,3 +146,37 @@ api.subscribe({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function harkContentsToPlainText(contents: HarkContent[]) {
|
||||
return contents
|
||||
.map((c) => {
|
||||
if ('ship' in c) {
|
||||
return c.ship;
|
||||
}
|
||||
return c.text;
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
|
||||
api.subscribe({
|
||||
app: 'hark-store',
|
||||
path: '/notes',
|
||||
event: (u: any) => {
|
||||
if ('add-note' in u) {
|
||||
if (useSettingsState.getState().display.doNotDisturb) {
|
||||
//return;
|
||||
}
|
||||
const { bin, body } = u['add-note'];
|
||||
const binId = harkBinToId(bin);
|
||||
const { title, content } = body;
|
||||
const image = useDocketState.getState().charges[bin.desk]?.image;
|
||||
|
||||
const notification = new Notification(harkContentsToPlainText(title), {
|
||||
body: harkContentsToPlainText(content),
|
||||
tag: binId,
|
||||
renotify: true
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
window.hark = useHarkStore.getState;
|
||||
|
@ -2,12 +2,12 @@ import shallow from 'zustand/shallow';
|
||||
import { useHarkStore } from './hark';
|
||||
|
||||
export const useNotifications = () => {
|
||||
const [unreads, reads] = useHarkStore((s) => [s.unreads, s.reads], shallow);
|
||||
const hasAnyNotifications = unreads.length > 0 || reads.size > 0;
|
||||
const [unseen, seen] = useHarkStore((s) => [s.unseen, s.seen], shallow);
|
||||
const hasAnyNotifications = Object.keys(seen).length > 0 || Object.keys(unseen).length > 0;
|
||||
|
||||
return {
|
||||
unreads,
|
||||
reads,
|
||||
unseen,
|
||||
seen,
|
||||
hasAnyNotifications
|
||||
};
|
||||
};
|
||||
|
@ -20,6 +20,7 @@ export default ({ mode }) => {
|
||||
mode === 'mock'
|
||||
? undefined
|
||||
: {
|
||||
https: true,
|
||||
proxy: {
|
||||
'^/apps/grid/desk.js': {
|
||||
target: SHIP_URL
|
||||
|
Loading…
Reference in New Issue
Block a user