mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-09-21 07:28:30 +03:00
Merge pull request #4873 from urbit/tbcs/dumb-as-rocks
interface: adding basic notifications
This commit is contained in:
commit
74918eec65
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user