tangled
alpha
login
or
join now
bad-example.com
/
spacedust-utils
demos for spacedust
6
fork
atom
overview
issues
pulls
pipelines
buttons for selecting notification sources
bad-example.com
7 months ago
f2c68d57
ddbc813d
+119
-6
5 changed files
expand all
collapse all
unified
split
atproto-notifications
src
components
Buttons.css
Buttons.tsx
Feed.tsx
db.ts
index.css
+49
atproto-notifications/src/components/Buttons.css
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
button.bg:focus-visible {
2
+
outline: 4px auto -webkit-focus-ring-color;
3
+
}
4
+
5
+
button.bg {
6
+
font: inherit;
7
+
cursor: pointer;
8
+
padding: 0 0.5rem;
9
+
}
10
+
button.bg:not(.bg-subtle) {
11
+
background: #111;
12
+
border: 1px solid hsla(0, 0%, 50%, 0.5);
13
+
border-radius: 0.5rem;
14
+
}
15
+
16
+
button.bg.bg-subtle {
17
+
background: transparent;
18
+
border: 0;
19
+
}
20
+
button.bg.bg-subtle:hover {
21
+
color: inherit;
22
+
background: hsla(0, 0%, 0%, 0.3);
23
+
}
24
+
25
+
button.bg.big {
26
+
padding: 0.25rem 0.75rem;
27
+
}
28
+
29
+
.button-group.vertical {
30
+
display: flex;
31
+
flex-direction: column;
32
+
}
33
+
34
+
.button-group > button.bg {
35
+
color: #888;
36
+
}
37
+
.button-group > button.bg.current {
38
+
font-weight: bold;
39
+
color: #99bb77;
40
+
}
41
+
.button-group > button.bg.current.bg-subtle {
42
+
background: #111; /*hsla(0, 0%, 0%, 0.2);*/
43
+
}
44
+
45
+
@media screen and (max-width: 600px) {
46
+
button.bg.bg-subtle {
47
+
font-size: 0.85rem;
48
+
}
49
+
}
+17
atproto-notifications/src/components/Buttons.tsx
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
import './Buttons.css';
2
+
3
+
export function ButtonGroup({ options, current, onChange, subtle, big, vertical }) {
4
+
return (
5
+
<div className={`button-group ${vertical ? 'vertical' : ''}`}>
6
+
{options.map(({val, label}) => (
7
+
<button
8
+
key={val}
9
+
className={`bg ${subtle ? 'bg-subtle' : ''} ${big ? 'big' : ''} ${val === current ? 'current' : ''}`}
10
+
onClick={() => onChange(val)}
11
+
>
12
+
{label ?? val}
13
+
</button>
14
+
))}
15
+
</div>
16
+
);
17
+
}
+50
-5
atproto-notifications/src/components/Feed.tsx
···
1
import { useEffect, useState } from 'react';
2
import { getNotifications, getSecondary } from '../db';
0
0
0
3
4
function Asdf({ inc, secondary }) {
5
const [secondaries, setSecondaries] = useState([]);
0
0
6
useEffect(() => {
7
(async () => {
8
const secondaries = await getSecondary(secondary);
9
secondaries.sort((a, b) => b.unread - a.unread);
10
setSecondaries(secondaries);
0
11
})();
12
}, [inc, secondary]);
13
14
return (
15
<div>
16
-
<p>secondaries: ({secondaries.length})</p>
17
-
{secondaries.map(a => (
18
-
<p key={a.k}>asdf {a.k} ({a.unread}/{a.total})</p>
19
-
))}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
20
</div>
21
);
22
}
23
24
export function Feed() {
0
25
26
// for now, we just increment a counter when a new notif comes in, which forces a re-render
27
const [inc, setInc] = useState(0);
···
44
}
45
return (
46
<div className="feed">
47
-
<Asdf inc={inc} secondary='source' />
0
0
0
0
0
0
0
0
0
0
0
48
{feed.map(([k, n]) => (
49
<p key={k}>{k}: {n.source} ({n.source_record}) <code>{JSON.stringify(n)}</code></p>
50
))}
···
1
import { useEffect, useState } from 'react';
2
import { getNotifications, getSecondary } from '../db';
3
+
import { ButtonGroup } from './Buttons';
4
+
import psl from 'psl';
5
+
import lexicons from 'lexicons';
6
7
function Asdf({ inc, secondary }) {
8
const [secondaries, setSecondaries] = useState([]);
9
+
const [selected, setSelected] = useState(null);
10
+
11
useEffect(() => {
12
(async () => {
13
const secondaries = await getSecondary(secondary);
14
secondaries.sort((a, b) => b.unread - a.unread);
15
setSecondaries(secondaries);
16
+
// setSelected(secondaries[0]?.k); // TODO
17
})();
18
}, [inc, secondary]);
19
20
return (
21
<div>
22
+
<ButtonGroup
23
+
options={secondaries.map(({ k, unread, total }) => {
24
+
25
+
26
+
let title = k;
27
+
if (secondary === 'source') {
28
+
// TODO: clean up / move this to lexicons package?
29
+
let app;
30
+
let appPrefix;
31
+
try {
32
+
const [nsid, ...rp] = k.split(':');
33
+
const parts = nsid.split('.');
34
+
const unreversed = parts.toReversed().join('.');
35
+
app = psl.parse(unreversed)?.domain ?? 'unknown';
36
+
appPrefix = app.split('.').toReversed().join('.');
37
+
} catch (e) {
38
+
console.error('getting top app failed', e);
39
+
}
40
+
const lex = lexicons[appPrefix];
41
+
const icon = lex?.clients[0]?.icon;
42
+
title = lex?.known_sources[k.slice(app.length + 1)] ?? k;
43
+
}
44
+
45
+
return { val: k, label: `${title} (${total})` };
46
+
})}
47
+
current={selected}
48
+
onChange={setSelected}
49
+
subtle
50
+
/>
51
+
52
+
53
</div>
54
);
55
}
56
57
export function Feed() {
58
+
const [secondary, setSecondary] = useState('all');
59
60
// for now, we just increment a counter when a new notif comes in, which forces a re-render
61
const [inc, setInc] = useState(0);
···
78
}
79
return (
80
<div className="feed">
81
+
<ButtonGroup
82
+
options={[
83
+
{val: 'all', label: 'All'},
84
+
{val: 'app', label: 'App'},
85
+
{val: 'group', label: 'Lexicon group'},
86
+
{val: 'source', label: 'Every source'},
87
+
]}
88
+
current={secondary}
89
+
onChange={setSecondary}
90
+
subtle
91
+
/>
92
+
<Asdf inc={inc} secondary={secondary} />
93
{feed.map(([k, n]) => (
94
<p key={k}>{k}: {n.source} ({n.source_record}) <code>{JSON.stringify(n)}</code></p>
95
))}
+2
-1
atproto-notifications/src/db.ts
···
1
const NOTIFICATIONS = 'notifications';
2
-
const SECONDARIES = ['all', 'source', 'group', 'app'];
0
3
4
export const getDB = ((upgrade, v) => {
5
let instance;
···
1
const NOTIFICATIONS = 'notifications';
2
+
3
+
export const SECONDARIES = ['all', 'source', 'group', 'app'];
4
5
export const getDB = ((upgrade, v) => {
6
let instance;
+1
atproto-notifications/src/index.css
···
61
margin: 0;
62
padding: 0;
63
font: inherit;
0
64
}
65
button.bad {
66
color: tomato;
···
61
margin: 0;
62
padding: 0;
63
font: inherit;
64
+
box-shadow: none;
65
}
66
button.bad {
67
color: tomato;