Merge pull request #4873 from urbit/tbcs/dumb-as-rocks

interface: adding basic notifications
This commit is contained in:
matildepark 2021-05-13 13:10:46 -04:00 committed by GitHub
commit 74918eec65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 115 additions and 30 deletions

View File

@ -5,7 +5,7 @@
/- glob
/+ default-agent, verb, dbug
|%
++ hash 0v4.vrvkt.4gcnm.dgg5o.e73d6.kqnaq
++ hash 0v2.rvlfs.f97fq.hjrpe.d3h68.n54sj
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ all-states
$% state-0

View File

@ -24,6 +24,6 @@
<div id="portal-root"></div>
<script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script>
<script src="/~landscape/js/bundle/index.f252a9afb6e952de19c9.js"></script>
<script src="/~landscape/js/bundle/index.a6842e8d167b4e66a4e0.js"></script>
</body>
</html>

View File

@ -1,7 +1,8 @@
import { IndexedNotification, NotificationGraphConfig, Unreads } from '@urbit/api';
import { GraphNotifIndex, GroupNotifIndex, IndexedNotification, NotificationGraphConfig, Post, Unreads } from '@urbit/api';
import bigInt, { BigInteger } from 'big-integer';
import _ from 'lodash';
import f from 'lodash/fp';
import { pluralize } from './util';
export function getLastSeen(
unreads: Unreads,
@ -58,3 +59,54 @@ export function getNotificationKey(time: BigInteger, notification: IndexedNotifi
return `${base}-unknown`;
}
export function notificationReferent(not: IndexedNotification) {
if('graph' in not.index) {
return not.index.graph.graph;
} else {
return not.index.group.group;
}
}
export function describeNotification(notification: IndexedNotification) {
function group(idx: GroupNotifIndex) {
switch (idx.description) {
case 'add-members':
return 'joined';
case 'remove-members':
return 'left';
default:
return idx.description;
}
}
function graph(idx: GraphNotifIndex, plural: boolean, singleAuthor: boolean) {
const isDm = idx.graph.startsWith('dm--');
switch (idx.description) {
case 'post':
return singleAuthor ? 'replied to you' : 'Your post received replies';
case 'link':
return `New link${plural ? 's' : ''} in`;
case 'comment':
return `New comment${plural ? 's' : ''} on`;
case 'note':
return `New Note${plural ? 's' : ''} in`;
case 'edit-note':
return `updated ${pluralize('note', plural)} in`;
case 'mention':
return singleAuthor ? 'mentioned you in' : 'You were mentioned in';
case 'message':
if (isDm) {
return 'messaged you';
}
return `New message${plural ? 's' : ''} in`;
default:
return idx.description;
}
}
if('group' in notification.index) {
return group(notification.index.group);
} else if('graph' in notification.index) {
const contents = notification.notification?.contents?.graph ?? [] as Post[];
return graph(notification.index.graph, contents.length > 1, _.uniq(_.map(contents, 'author')).length === 1)
}
}

View File

@ -1,4 +1,5 @@
/* eslint-disable max-lines */
import { sigil as sigiljs, stringRenderer } from '@tlon/sigil-js';
import { Association, Contact } from '@urbit/api';
import anyAscii from 'any-ascii';
import bigInt, { BigInteger } from 'big-integer';
@ -6,7 +7,9 @@ import { enableMapSet } from 'immer';
import _ from 'lodash';
import f from 'lodash/fp';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { foregroundFromBackground } from '~/logic/lib/sigil';
import { IconRef } from '~/types';
import useContactState from '../state/contact';
import useSettingsState from '../state/settings';
enableMapSet();
@ -442,3 +445,22 @@ export function getItemTitle(association: Association): string {
return association.metadata.title ?? association.resource ?? '';
}
export const svgDataURL = (svg) => 'data:image/svg+xml;base64,' + btoa(svg);
export const svgBlobURL = (svg) => URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }));
export const favicon = () => {
let background = '#ffffff';
const contacts = useContactState.getState().contacts;
if (contacts.hasOwnProperty(`~${window.ship}`)) {
background = `#${uxToHex(contacts[`~${window.ship}`].color)}`;
}
const foreground = foregroundFromBackground(background);
const svg = sigiljs({
patp: window.ship,
renderer: stringRenderer,
size: 16,
colors: [background, foreground]
});
return svg;
}

View File

@ -7,8 +7,10 @@ import { BigInteger } from 'big-integer';
import _ from 'lodash';
import { compose } from 'lodash/fp';
import { makePatDa } from '~/logic/lib/util';
import { describeNotification } from '../lib/hark';
import { reduceState } from '../state/base';
import useHarkState, { HarkState } from '../state/hark';
import useMetadataState from '../state/metadata';
export const HarkReducer = (json: any) => {
const data = _.get(json, 'harkUpdate', false);
@ -195,7 +197,7 @@ function readSince(json: any, state: HarkState): HarkState {
function unreadSince(json: any, state: HarkState): HarkState {
const data = _.get(json, 'unread-count');
if(data) {
if (data) {
updateUnreadCount(state, data.index, u => u + 1);
}
return state;
@ -313,7 +315,7 @@ function removeNotificationFromUnread(state: HarkState, index: NotifIndex, time:
}
}
function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'unreads' | 'last', f: (x: number) => number) {
function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'unreads' | 'last', f: (x: number) => number, notify = false) {
if('graph' in index) {
const curr: any = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, statField], 0);
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, statField], f(curr));
@ -328,6 +330,20 @@ function added(json: any, state: HarkState): HarkState {
if (data) {
const { index, notification } = data;
const time = makePatDa(data.time);
if (!useHarkState.getState().doNotDisturb) {
const description = describeNotification(data);
const meta = useMetadataState.getState();
const referent = 'graph' in data.index ? meta.associations.graph[data.index.graph.graph]?.metadata?.title ?? data.index.graph : meta.associations.groups[data.index.group.group]?.metadata?.title ?? data.index.group;
new Notification(`${description} ${referent}`, {
tag: 'landscape',
image: '/img/favicon.png',
icon: '/img/favicon.png',
badge: '/img/favicon.png',
renotify: true
});
}
const timebox = state.notifications.get(time) || [];
addNotificationToUnread(state, index, time);

View File

@ -1,6 +1,5 @@
import dark from '@tlon/indigo-dark';
import light from '@tlon/indigo-light';
import { sigil as sigiljs, stringRenderer } from '@tlon/sigil-js';
import Mousetrap from 'mousetrap';
import 'mousetrap-global-bind';
import * as React from 'react';
@ -11,8 +10,7 @@ import { BrowserRouter as Router, withRouter } from 'react-router-dom';
import styled, { ThemeProvider } from 'styled-components';
import GlobalApi from '~/logic/api/global';
import gcpManager from '~/logic/lib/gcpManager';
import { foregroundFromBackground } from '~/logic/lib/sigil';
import { uxToHex } from '~/logic/lib/util';
import { favicon, svgDataURL } from '~/logic/lib/util';
import withState from '~/logic/lib/withState';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
@ -86,7 +84,6 @@ class App extends React.Component {
this.updateTheme = this.updateTheme.bind(this);
this.updateMobile = this.updateMobile.bind(this);
this.faviconString = this.faviconString.bind(this);
}
componentDidMount() {
@ -130,22 +127,6 @@ class App extends React.Component {
});
}
faviconString() {
let background = '#ffffff';
if (this.props.contacts.hasOwnProperty(`~${window.ship}`)) {
background = `#${uxToHex(this.props.contacts[`~${window.ship}`].color)}`;
}
const foreground = foregroundFromBackground(background);
const svg = sigiljs({
patp: window.ship,
renderer: stringRenderer,
size: 16,
colors: [background, foreground]
});
const dataurl = 'data:image/svg+xml;base64,' + btoa(svg);
return dataurl;
}
getTheme() {
const { props } = this;
return ((props.dark && props?.display?.theme == 'auto') ||
@ -162,7 +143,7 @@ class App extends React.Component {
<ThemeProvider theme={theme}>
<Helmet>
{window.ship.length < 14
? <link rel="icon" type="image/svg+xml" href={this.faviconString()} />
? <link rel="icon" type="image/svg+xml" href={svgDataURL(favicon())} />
: null}
</Helmet>
<Root>

View File

@ -1,11 +1,15 @@
import {
Col,
Button,
Col,
ManagedToggleSwitchField as Toggle, Text
ManagedToggleSwitchField as Toggle, Text
} from '@tlon/indigo-react';
import { Form, Formik, FormikHelpers } from 'formik';
import _ from 'lodash';
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import GlobalApi from '~/logic/api/global';
import { isWatching } from '~/logic/lib/hark';
import useHarkState from '~/logic/state/hark';
@ -70,6 +74,8 @@ export function NotificationPreferences(props: {
}
}, [api, graphConfig, dnd]);
const [notificationsAllowed, setNotificationsAllowed] = useState(Notification.permission !== 'default');
return (
<>
<BackButton />
@ -85,7 +91,15 @@ export function NotificationPreferences(props: {
</Col>
<FormikOnBlur initialValues={initialValues} onSubmit={onSubmit}>
<Form>
<Col gapY={4}>
<Col gapY="4">
{notificationsAllowed
? null
: <Button alignSelf='flex-start' onClick={() => {
Notification.requestPermission().then(() => {
setNotificationsAllowed(Notification.permission !== 'default');
});
}}>Allow Browser Notifications</Button>
}
<Toggle
label="Do not disturb"
id="dnd"