mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-14 17:41:33 +03:00
Merge pull request #3915 from urbit/lf/hark-qa-fixes
hark: lazier notifications loading, final bugfixes
This commit is contained in:
commit
118e7d2c8b
@ -48,7 +48,7 @@
|
|||||||
(on-watch:def path)
|
(on-watch:def path)
|
||||||
:_ this
|
:_ this
|
||||||
=; =cage
|
=; =cage
|
||||||
[%give %fact ~[/updates] cage]~
|
[%give %fact ~ cage]~
|
||||||
:- %hark-group-hook-update
|
:- %hark-group-hook-update
|
||||||
!> ^- update:hook
|
!> ^- update:hook
|
||||||
[%initial watching]
|
[%initial watching]
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
=notifications:store
|
=notifications:store
|
||||||
archive=notifications:store
|
archive=notifications:store
|
||||||
last-seen=@da
|
last-seen=@da
|
||||||
dnd=?
|
dnd=_|
|
||||||
==
|
==
|
||||||
+$ inflated-state
|
+$ inflated-state
|
||||||
$: state-0
|
$: state-0
|
||||||
@ -25,6 +25,7 @@
|
|||||||
:: albeit expensively
|
:: albeit expensively
|
||||||
+$ cache
|
+$ cache
|
||||||
$: unread-count=@ud
|
$: unread-count=@ud
|
||||||
|
graph-unreads=(map resource @ud)
|
||||||
~
|
~
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
@ -71,14 +72,17 @@
|
|||||||
^- update:store
|
^- update:store
|
||||||
:- %more
|
:- %more
|
||||||
^- (list update:store)
|
^- (list update:store)
|
||||||
|
:- [%graph-unreads graph-unreads]
|
||||||
:+ [%set-dnd dnd]
|
:+ [%set-dnd dnd]
|
||||||
[%count unread-count]
|
[%count unread-count]
|
||||||
%+ weld
|
%+ weld
|
||||||
%+ turn
|
%+ turn
|
||||||
(tap-nonempty archive)
|
%+ scag 5
|
||||||
|
(tap-nonempty:ha archive)
|
||||||
(timebox-update &)
|
(timebox-update &)
|
||||||
%+ turn
|
%+ turn
|
||||||
(tap-nonempty notifications)
|
%+ scag 5
|
||||||
|
(tap-nonempty:ha notifications)
|
||||||
(timebox-update |)
|
(timebox-update |)
|
||||||
::
|
::
|
||||||
++ timebox-update
|
++ timebox-update
|
||||||
@ -86,12 +90,6 @@
|
|||||||
|= [time=@da =timebox:store]
|
|= [time=@da =timebox:store]
|
||||||
^- update:store
|
^- update:store
|
||||||
[%timebox time archived ~(tap by timebox)]
|
[%timebox time archived ~(tap by timebox)]
|
||||||
::
|
|
||||||
++ tap-nonempty
|
|
||||||
|= =notifications:store
|
|
||||||
^- (list [@da timebox:store])
|
|
||||||
%+ skip (tap:orm notifications)
|
|
||||||
|=([@da =timebox:store] =(0 ~(wyt by timebox)))
|
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
++ on-peek
|
++ on-peek
|
||||||
@ -104,11 +102,13 @@
|
|||||||
(slav %ud i.t.t.path)
|
(slav %ud i.t.t.path)
|
||||||
=/ length=@ud
|
=/ length=@ud
|
||||||
(slav %ud i.t.t.t.path)
|
(slav %ud i.t.t.t.path)
|
||||||
:^ ~ ~ %noun
|
:^ ~ ~ %hark-update
|
||||||
!> ^- update:store
|
!> ^- update:store
|
||||||
:- %more
|
:- %more
|
||||||
%+ turn
|
%+ turn
|
||||||
(scag length (slag offset (tap:orm notifications)))
|
%+ scag length
|
||||||
|
%+ slag offset
|
||||||
|
(tap-nonempty:ha notifications)
|
||||||
|= [time=@da =timebox:store]
|
|= [time=@da =timebox:store]
|
||||||
^- update:store
|
^- update:store
|
||||||
:^ %timebox time %.n
|
:^ %timebox time %.n
|
||||||
@ -154,6 +154,7 @@
|
|||||||
(~(put by timebox) index new)
|
(~(put by timebox) index new)
|
||||||
:- (give:ha [/updates]~ %added last-seen index new)
|
:- (give:ha [/updates]~ %added last-seen index new)
|
||||||
%_ state
|
%_ state
|
||||||
|
+ ?~(existing-notif (upd-unreads:ha index %.n) +.state)
|
||||||
notifications (put:orm notifications last-seen new-timebox)
|
notifications (put:orm notifications last-seen new-timebox)
|
||||||
unread-count ?~(existing-notif +(unread-count) unread-count)
|
unread-count ?~(existing-notif +(unread-count) unread-count)
|
||||||
==
|
==
|
||||||
@ -169,7 +170,7 @@
|
|||||||
(~(del by timebox) index)
|
(~(del by timebox) index)
|
||||||
:- (give:ha [/updates]~ %archive time index)
|
:- (give:ha [/updates]~ %archive time index)
|
||||||
%_ state
|
%_ state
|
||||||
unread-count ?.(read.notification (dec unread-count) unread-count)
|
+ ?.(read.notification (upd-unreads:ha index %.y) +.state)
|
||||||
::
|
::
|
||||||
notifications
|
notifications
|
||||||
(put:orm notifications time new-timebox)
|
(put:orm notifications time new-timebox)
|
||||||
@ -186,6 +187,7 @@
|
|||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
:- (give:ha [/updates]~ %read time index)
|
:- (give:ha [/updates]~ %read time index)
|
||||||
%_ state
|
%_ state
|
||||||
|
+ (upd-unreads:ha index %.y)
|
||||||
unread-count (dec unread-count)
|
unread-count (dec unread-count)
|
||||||
notifications (change-read-status:ha time index %.y)
|
notifications (change-read-status:ha time index %.y)
|
||||||
==
|
==
|
||||||
@ -195,6 +197,7 @@
|
|||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
:- (give:ha [/updates]~ %unread time index)
|
:- (give:ha [/updates]~ %unread time index)
|
||||||
%_ state
|
%_ state
|
||||||
|
+ (upd-unreads:ha index %.n)
|
||||||
unread-count +(unread-count)
|
unread-count +(unread-count)
|
||||||
notifications (change-read-status:ha time index %.n)
|
notifications (change-read-status:ha time index %.n)
|
||||||
==
|
==
|
||||||
@ -231,6 +234,12 @@
|
|||||||
|_ =bowl:gall
|
|_ =bowl:gall
|
||||||
+* met ~(. metadata bowl)
|
+* met ~(. metadata bowl)
|
||||||
::
|
::
|
||||||
|
++ tap-nonempty
|
||||||
|
|= =notifications:store
|
||||||
|
^- (list [@da timebox:store])
|
||||||
|
%+ skip (tap:orm notifications)
|
||||||
|
|=([@da =timebox:store] =(0 ~(wyt by timebox)))
|
||||||
|
::
|
||||||
++ merge-notification
|
++ merge-notification
|
||||||
|= [existing=notification:store new=notification:store]
|
|= [existing=notification:store new=notification:store]
|
||||||
^- notification:store
|
^- notification:store
|
||||||
@ -292,22 +301,37 @@
|
|||||||
^- (list card)
|
^- (list card)
|
||||||
[%give %fact paths [%hark-update !>(update)]]~
|
[%give %fact paths [%hark-update !>(update)]]~
|
||||||
::
|
::
|
||||||
|
++ upd-unreads
|
||||||
|
|= [=index:store read=?]
|
||||||
|
^+ +.state
|
||||||
|
=/ f=$-(@ @)
|
||||||
|
?: read
|
||||||
|
dec
|
||||||
|
|=(a=@ +(a))
|
||||||
|
=. unread-count (f unread-count)
|
||||||
|
?. ?=(%graph -.index)
|
||||||
|
+.state
|
||||||
|
=/ curr-unread=@ud
|
||||||
|
(~(gut by graph-unreads) graph.index 0)
|
||||||
|
+.state(graph-unreads (~(put by graph-unreads) graph.index (f curr-unread)))
|
||||||
|
::
|
||||||
++ inflate-cache
|
++ inflate-cache
|
||||||
|= state-0
|
|= state-0
|
||||||
^- cache
|
^+ +.state
|
||||||
:_ ~
|
=/ nots=(list [p=@da =timebox:store])
|
||||||
%+ roll
|
|
||||||
(tap:orm notifications)
|
(tap:orm notifications)
|
||||||
|= [[time=@da =timebox:store] out=@ud]
|
|- =* outer $
|
||||||
=/ unreads ~(tap by timebox)
|
?~ nots
|
||||||
|-
|
+.state
|
||||||
?~ unreads out
|
=/ unreads ~(tap by timebox.i.nots)
|
||||||
|
|- =* inner $
|
||||||
|
?~ unreads
|
||||||
|
outer(nots t.nots)
|
||||||
=* notification q.i.unreads
|
=* notification q.i.unreads
|
||||||
|
=* index p.i.unreads
|
||||||
?: read.notification
|
?: read.notification
|
||||||
out
|
inner(unreads t.unreads)
|
||||||
%_ $
|
=. +.state
|
||||||
unreads t.unreads
|
(upd-unreads index read.notification)
|
||||||
::
|
inner(unreads t.unreads)
|
||||||
out +(out)
|
|
||||||
==
|
|
||||||
--
|
--
|
||||||
|
@ -78,12 +78,22 @@
|
|||||||
%timebox (timebox +.upd)
|
%timebox (timebox +.upd)
|
||||||
%set-dnd b+dnd.upd
|
%set-dnd b+dnd.upd
|
||||||
%count (numb count.upd)
|
%count (numb count.upd)
|
||||||
|
%graph-unreads (graph-unreads map.upd)
|
||||||
%more (more +.upd)
|
%more (more +.upd)
|
||||||
::
|
::
|
||||||
?(%archive %read %unread)
|
?(%archive %read %unread)
|
||||||
(notif-ref +.upd)
|
(notif-ref +.upd)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
|
++ graph-unreads
|
||||||
|
|= =(map resource @ud)
|
||||||
|
^- json
|
||||||
|
%- pairs
|
||||||
|
%+ turn
|
||||||
|
~(tap by map)
|
||||||
|
|= [rid=resource unread=@ud]
|
||||||
|
(enjs-path:resource rid)^(numb unread)
|
||||||
|
::
|
||||||
++ added
|
++ added
|
||||||
|= [tim=@da idx=^index not=^notification]
|
|= [tim=@da idx=^index not=^notification]
|
||||||
^- json
|
^- json
|
||||||
|
@ -46,5 +46,6 @@
|
|||||||
[%added time=@da =index =notification]
|
[%added time=@da =index =notification]
|
||||||
[%timebox time=@da archived=? =(list [index notification])]
|
[%timebox time=@da archived=? =(list [index notification])]
|
||||||
[%count count=@ud]
|
[%count count=@ud]
|
||||||
|
[%graph-unreads =(map resource @ud)]
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
|
@ -149,10 +149,21 @@ export class HarkApi extends BaseApi<StoreState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMore() {
|
||||||
|
const offset = this.store.state.notifications.size;
|
||||||
|
const count = 10;
|
||||||
|
return this.getSubset(offset,count);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSubset(offset:number, count:number) {
|
||||||
|
const data = await this.scry("hark-store", `/recent/${offset}/${count}`);
|
||||||
|
this.store.handleEvent({ data });
|
||||||
|
}
|
||||||
|
|
||||||
async getTimeSubset(start?: Date, end?: Date) {
|
async getTimeSubset(start?: Date, end?: Date) {
|
||||||
const s = start ? dateToDa(start) : "-";
|
const s = start ? dateToDa(start) : "-";
|
||||||
const e = end ? dateToDa(end) : "-";
|
const e = end ? dateToDa(end) : "-";
|
||||||
const result = await this.scry("hark-hook", `/time-subset/${s}/${e}`);
|
const result = await this.scry("hark-hook", `/recent/${s}/${e}`);
|
||||||
this.store.handleEvent({
|
this.store.handleEvent({
|
||||||
data: result,
|
data: result,
|
||||||
});
|
});
|
||||||
|
@ -23,7 +23,6 @@ export const HarkReducer = (json: any, state: HarkState) => {
|
|||||||
}
|
}
|
||||||
const graphHookData = _.get(json, "hark-graph-hook-update", false);
|
const graphHookData = _.get(json, "hark-graph-hook-update", false);
|
||||||
if (graphHookData) {
|
if (graphHookData) {
|
||||||
console.log(graphHookData);
|
|
||||||
graphInitial(graphHookData, state);
|
graphInitial(graphHookData, state);
|
||||||
graphIgnore(graphHookData, state);
|
graphIgnore(graphHookData, state);
|
||||||
graphListen(graphHookData, state);
|
graphListen(graphHookData, state);
|
||||||
@ -133,6 +132,7 @@ function graphWatchSelf(json: any, state: HarkState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function reduce(data: any, state: HarkState) {
|
function reduce(data: any, state: HarkState) {
|
||||||
|
console.log(data);
|
||||||
unread(data, state);
|
unread(data, state);
|
||||||
read(data, state);
|
read(data, state);
|
||||||
archive(data, state);
|
archive(data, state);
|
||||||
@ -141,6 +141,14 @@ function reduce(data: any, state: HarkState) {
|
|||||||
dnd(data, state);
|
dnd(data, state);
|
||||||
count(data, state);
|
count(data, state);
|
||||||
added(data, state);
|
added(data, state);
|
||||||
|
graphUnreads(data, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function graphUnreads(json: any, state: HarkState) {
|
||||||
|
const data = _.get(json, 'graph-unreads');
|
||||||
|
if(data) {
|
||||||
|
state.graphUnreads = data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function added(json: any, state: HarkState) {
|
function added(json: any, state: HarkState) {
|
||||||
@ -158,6 +166,10 @@ function added(json: any, state: HarkState) {
|
|||||||
} else {
|
} else {
|
||||||
state.notifications.set(time, [...timebox, { index, notification }]);
|
state.notifications.set(time, [...timebox, { index, notification }]);
|
||||||
state.notificationsCount++;
|
state.notificationsCount++;
|
||||||
|
if('graph' in index) {
|
||||||
|
const curr = state.graphUnreads[index.graph.graph] || 0;
|
||||||
|
state.graphUnreads[index.graph.graph] = curr+1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,6 +255,11 @@ function read(json: any, state: HarkState) {
|
|||||||
if (data) {
|
if (data) {
|
||||||
const { time, index } = data;
|
const { time, index } = data;
|
||||||
state.notificationsCount--;
|
state.notificationsCount--;
|
||||||
|
if('graph' in index) {
|
||||||
|
const curr = state.graphUnreads[index.graph.graph] || 0;
|
||||||
|
state.graphUnreads[index.graph.graph] = curr-1;
|
||||||
|
}
|
||||||
|
|
||||||
setRead(time, index, true, state);
|
setRead(time, index, true, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -252,6 +269,10 @@ function unread(json: any, state: HarkState) {
|
|||||||
if (data) {
|
if (data) {
|
||||||
const { time, index } = data;
|
const { time, index } = data;
|
||||||
state.notificationsCount++;
|
state.notificationsCount++;
|
||||||
|
if('graph' in index) {
|
||||||
|
const curr = state.graphUnreads[index.graph.graph] || 0;
|
||||||
|
state.graphUnreads[index.graph.graph] = curr+1;
|
||||||
|
}
|
||||||
setRead(time, index, false, state);
|
setRead(time, index, false, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,8 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
|||||||
mentions: false,
|
mentions: false,
|
||||||
watching: [],
|
watching: [],
|
||||||
},
|
},
|
||||||
notificationsCount: 0
|
notificationsCount: 0,
|
||||||
|
graphUnreads: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,4 +65,5 @@ export interface StoreState {
|
|||||||
notificationsChatConfig: string[];
|
notificationsChatConfig: string[];
|
||||||
notificationsCount: number,
|
notificationsCount: number,
|
||||||
doNotDisturb: boolean;
|
doNotDisturb: boolean;
|
||||||
|
graphUnreads: Record<string, number>;
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,8 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
unreadMarkerRef,
|
unreadMarkerRef,
|
||||||
history,
|
history,
|
||||||
api,
|
api,
|
||||||
highlighted
|
highlighted,
|
||||||
|
fontSize
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const renderSigil = Boolean((nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1);
|
const renderSigil = Boolean((nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1);
|
||||||
@ -118,7 +119,8 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
history,
|
history,
|
||||||
api,
|
api,
|
||||||
scrollWindow,
|
scrollWindow,
|
||||||
highlighted
|
highlighted,
|
||||||
|
fontSize
|
||||||
};
|
};
|
||||||
|
|
||||||
const unreadContainerStyle = {
|
const unreadContainerStyle = {
|
||||||
@ -131,7 +133,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
width='100%'
|
width='100%'
|
||||||
display='flex'
|
display='flex'
|
||||||
flexWrap='wrap'
|
flexWrap='wrap'
|
||||||
pt={renderSigil ? 3 : 0}
|
pt={this.props.pt ? this.props.pt : renderSigil ? 3 : 0}
|
||||||
pr={3}
|
pr={3}
|
||||||
pb={isLastMessage ? 3 : 0}
|
pb={isLastMessage ? 3 : 0}
|
||||||
ref={this.divRef}
|
ref={this.divRef}
|
||||||
@ -170,7 +172,7 @@ interface MessageProps {
|
|||||||
|
|
||||||
export class MessageWithSigil extends PureComponent<MessageProps> {
|
export class MessageWithSigil extends PureComponent<MessageProps> {
|
||||||
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
msg,
|
msg,
|
||||||
@ -184,14 +186,15 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
|||||||
measure,
|
measure,
|
||||||
api,
|
api,
|
||||||
history,
|
history,
|
||||||
scrollWindow
|
scrollWindow,
|
||||||
|
fontSize
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const datestamp = moment.unix(msg.when / 1000).format(DATESTAMP_FORMAT);
|
const datestamp = moment.unix(msg.when / 1000).format(DATESTAMP_FORMAT);
|
||||||
const contact = msg.author in contacts ? contacts[msg.author] : false;
|
const contact = msg.author in contacts ? contacts[msg.author] : false;
|
||||||
const showNickname = !hideNicknames && contact && contact.nickname;
|
const showNickname = !hideNicknames && contact && contact.nickname;
|
||||||
const name = showNickname ? contact.nickname : cite(msg.author);
|
const name = showNickname ? contact.nickname : cite(msg.author);
|
||||||
const color = contact ? `#${uxToHex(contact.color)}` : this.isDark ? '#000000' :'#FFFFFF'
|
const color = contact ? `#${uxToHex(contact.color)}` : this.isDark ? '#000000' :'#FFFFFF'
|
||||||
const sigilClass = contact ? '' : this.isDark ? 'mix-blend-diff' : 'mix-blend-darken';
|
const sigilClass = contact ? '' : this.isDark ? 'mix-blend-diff' : 'mix-blend-darken';
|
||||||
|
|
||||||
let nameSpan = null;
|
let nameSpan = null;
|
||||||
@ -245,7 +248,7 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
|||||||
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
|
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
|
||||||
<Text gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
|
<Text gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box fontSize='14px'><MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure} /></Box>
|
<Box fontSize={fontSize ? fontSize : '14px'}><MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure} fontSize={fontSize} /></Box>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -261,12 +264,12 @@ export const MessageWithoutSigil = ({ timestamp, msg, remoteContentPolicy, measu
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const MessageContent = ({ content, remoteContentPolicy, measure }) => {
|
export const MessageContent = ({ content, remoteContentPolicy, measure, fontSize }) => {
|
||||||
if ('code' in content) {
|
if ('code' in content) {
|
||||||
return <CodeContent content={content} />;
|
return <CodeContent content={content} />;
|
||||||
} else if ('url' in content) {
|
} else if ('url' in content) {
|
||||||
return (
|
return (
|
||||||
<Text fontSize='14px' lineHeight="tall" color='black'>
|
<Text fontSize={fontSize ? fontSize : '14px'} lineHeight="tall" color='black'>
|
||||||
<RemoteContent
|
<RemoteContent
|
||||||
url={content.url}
|
url={content.url}
|
||||||
remoteContentPolicy={remoteContentPolicy}
|
remoteContentPolicy={remoteContentPolicy}
|
||||||
@ -282,13 +285,13 @@ export const MessageContent = ({ content, remoteContentPolicy, measure }) => {
|
|||||||
);
|
);
|
||||||
} else if ('me' in content) {
|
} else if ('me' in content) {
|
||||||
return (
|
return (
|
||||||
<Text fontStyle='italic' fontSize='14px' lineHeight='tall' color='black'>
|
<Text fontStyle='italic' fontSize={fontSize ? fontSize : '14px'} lineHeight='tall' color='black'>
|
||||||
{content.me}
|
{content.me}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if ('text' in content) {
|
else if ('text' in content) {
|
||||||
return <TextContent content={content} />;
|
return <TextContent fontSize={fontSize} content={content} />;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ export default class CodeContent extends Component {
|
|||||||
p='1'
|
p='1'
|
||||||
my='0'
|
my='0'
|
||||||
borderRadius='1'
|
borderRadius='1'
|
||||||
fontSize='14px'
|
|
||||||
overflow='auto'
|
overflow='auto'
|
||||||
maxHeight='10em'
|
maxHeight='10em'
|
||||||
maxWidth='100%'
|
maxWidth='100%'
|
||||||
@ -35,7 +34,6 @@ export default class CodeContent extends Component {
|
|||||||
my='0'
|
my='0'
|
||||||
p='1'
|
p='1'
|
||||||
borderRadius='1'
|
borderRadius='1'
|
||||||
fontSize='14px'
|
|
||||||
overflow='auto'
|
overflow='auto'
|
||||||
maxHeight='10em'
|
maxHeight='10em'
|
||||||
maxWidth='100%'
|
maxWidth='100%'
|
||||||
|
@ -26,13 +26,12 @@ const DISABLED_INLINE_TOKENS = [
|
|||||||
|
|
||||||
const renderers = {
|
const renderers = {
|
||||||
inlineCode: ({language, value}) => {
|
inlineCode: ({language, value}) => {
|
||||||
return <Text mono fontSize='14px' backgroundColor='washedGray' style={{ whiteSpace: 'preWrap'}}>{value}</Text>
|
return <Text mono backgroundColor='washedGray' style={{ whiteSpace: 'preWrap'}}>{value}</Text>
|
||||||
},
|
},
|
||||||
code: ({language, value}) => {
|
code: ({language, value}) => {
|
||||||
return <Text
|
return <Text
|
||||||
p='1'
|
p='1'
|
||||||
className='clamp-message'
|
className='clamp-message'
|
||||||
fontSize='14px'
|
|
||||||
display='block'
|
display='block'
|
||||||
borderRadius='1'
|
borderRadius='1'
|
||||||
mono
|
mono
|
||||||
@ -84,7 +83,7 @@ export default class TextContent extends Component {
|
|||||||
&& (urbitOb.isValidPatp(group[2]) // valid patp?
|
&& (urbitOb.isValidPatp(group[2]) // valid patp?
|
||||||
&& (group[0] === content.text))) { // entire message is room name?
|
&& (group[0] === content.text))) { // entire message is room name?
|
||||||
return (
|
return (
|
||||||
<Text fontSize='14px' color='black' lineHeight="tall">
|
<Text fontSize={props.fontSize ? props.fontSize : '14px'} color='black' lineHeight="tall">
|
||||||
<Link
|
<Link
|
||||||
className="bb b--black b--white-d mono"
|
className="bb b--black b--white-d mono"
|
||||||
to={'/~landscape/join/' + group.input}>
|
to={'/~landscape/join/' + group.input}>
|
||||||
@ -94,7 +93,7 @@ export default class TextContent extends Component {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Text color='black' fontSize='14px' lineHeight="tall" style={{ overflowWrap: 'break-word' }}>
|
<Text color='black' fontSize={props.fontSize ? props.fontSize : '14px'} lineHeight="tall" style={{ overflowWrap: 'break-word' }}>
|
||||||
<MessageMarkdown source={content.text} />
|
<MessageMarkdown source={content.text} />
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
@ -83,6 +83,8 @@ export function ChatNotification(props: {
|
|||||||
isLastRead={false}
|
isLastRead={false}
|
||||||
group={group}
|
group={group}
|
||||||
contacts={groupContacts}
|
contacts={groupContacts}
|
||||||
|
fontSize='0'
|
||||||
|
pt='2'
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
@ -51,8 +51,8 @@ function describeNotification(description: string, plural: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GraphUrl = ({ url, title }) => (
|
const GraphUrl = ({ url, title }) => (
|
||||||
<Box borderRadius="1" p="2" bg="washedGray">
|
<Box borderRadius="2" p="2" bg="scales.black05">
|
||||||
<Anchor target="_blank" color="gray" href={url}>
|
<Anchor underline={false} target="_blank" color="black" href={url}>
|
||||||
<Icon verticalAlign="bottom" mr="2" icon="ArrowExternal" />
|
<Icon verticalAlign="bottom" mr="2" icon="ArrowExternal" />
|
||||||
{title}
|
{title}
|
||||||
</Anchor>
|
</Anchor>
|
||||||
@ -140,7 +140,7 @@ const GraphNode = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={nodeUrl}>
|
<Link to={nodeUrl}>
|
||||||
<Row gapX="2" py="2">
|
<Row gapX="2" pt="2">
|
||||||
<Col>{img}</Col>
|
<Col>{img}</Col>
|
||||||
<Col alignItems="flex-start">
|
<Col alignItems="flex-start">
|
||||||
<Row
|
<Row
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect, useCallback } from "react";
|
||||||
import f from "lodash/fp";
|
import f from "lodash/fp";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { Icon, Col, Row, Box, Text, Anchor } from "@tlon/indigo-react";
|
import { Icon, Col, Row, Box, Text, Anchor } from "@tlon/indigo-react";
|
||||||
@ -69,13 +69,20 @@ export default function Inbox(props: {
|
|||||||
f.values
|
f.values
|
||||||
)(notifications);
|
)(notifications);
|
||||||
|
|
||||||
|
const onScroll = useCallback((e) => {
|
||||||
|
let container = e.target;
|
||||||
|
if(!props.showArchive && (container.scrollHeight - container.scrollTop === container.clientHeight)) {
|
||||||
|
api.hark.getMore();
|
||||||
|
}
|
||||||
|
}, [api]);
|
||||||
|
|
||||||
const incomingGroups = Object.values(invites?.['contacts'] || {});
|
const incomingGroups = Object.values(invites?.['contacts'] || {});
|
||||||
|
|
||||||
const getKeyByValue = (object, value) => {
|
const getKeyByValue = (object, value) => {
|
||||||
return Object.keys(object).find(key => object[key] === value);
|
return Object.keys(object).find(key => object[key] === value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const acceptInvite = (invite) => {
|
const acceptInvite = (invite) => {
|
||||||
const resource = {
|
const resource = {
|
||||||
ship: `~${invite.resource.ship}`,
|
ship: `~${invite.resource.ship}`,
|
||||||
name: invite.resource.name
|
name: invite.resource.name
|
||||||
@ -86,11 +93,9 @@ const acceptInvite = (invite) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col overflowY="auto" flexGrow="1">
|
<Col onScroll={onScroll} overflowY="auto" flexGrow="1" minHeight='0' flexShrink='0'>
|
||||||
{incomingGroups.map((invite) => (
|
{incomingGroups.map((invite) => (
|
||||||
<Box
|
<Box
|
||||||
height='100%'
|
|
||||||
width='100%'
|
|
||||||
bg='white'
|
bg='white'
|
||||||
p='3'
|
p='3'
|
||||||
fontSize='0'>
|
fontSize='0'>
|
||||||
|
@ -87,9 +87,9 @@ function NotificationWrapper(props: {
|
|||||||
|
|
||||||
const changeMuteDesc = isMuted ? "Unmute" : "Mute";
|
const changeMuteDesc = isMuted ? "Unmute" : "Mute";
|
||||||
return (
|
return (
|
||||||
<Row alignItems="center" justifyContent="space-between">
|
<Row alignItems="top" justifyContent="space-between">
|
||||||
{children}
|
{children}
|
||||||
<Row gapX="2" p="2" alignItems="center">
|
<Row gapX="2" p="2" pt='3' alignItems="top">
|
||||||
<StatelessAsyncAction name={changeMuteDesc} onClick={onChangeMute} backgroundColor="transparent">
|
<StatelessAsyncAction name={changeMuteDesc} onClick={onChangeMute} backgroundColor="transparent">
|
||||||
{changeMuteDesc}
|
{changeMuteDesc}
|
||||||
</StatelessAsyncAction>
|
</StatelessAsyncAction>
|
||||||
|
@ -53,7 +53,7 @@ export default function NotificationsScreen(props: any) {
|
|||||||
const { view } = routeProps.match.params;
|
const { view } = routeProps.match.params;
|
||||||
return (
|
return (
|
||||||
<Body>
|
<Body>
|
||||||
<Col height="100%">
|
<Col height="100%" minHeight='0' overflowY='scroll'>
|
||||||
<Row
|
<Row
|
||||||
p="3"
|
p="3"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
|
36
pkg/interface/src/views/components/StatelessAsyncToggle.tsx
Normal file
36
pkg/interface/src/views/components/StatelessAsyncToggle.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import React, { ReactNode, useState, useEffect, useCallback } from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
StatelessToggleSwitchField as Toggle,
|
||||||
|
LoadingSpinner,
|
||||||
|
Text
|
||||||
|
} from "@tlon/indigo-react";
|
||||||
|
import { useFormikContext } from "formik";
|
||||||
|
|
||||||
|
import { useStatelessAsyncClickable } from "~/logic/lib/useStatelessAsyncClickable";
|
||||||
|
|
||||||
|
interface AsyncToggleProps {
|
||||||
|
name?: string;
|
||||||
|
onClick: (e: React.MouseEvent) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatelessAsyncToggle({
|
||||||
|
onClick,
|
||||||
|
name = "",
|
||||||
|
...rest
|
||||||
|
}: AsyncToggleProps & Parameters<typeof Toggle>[0]) {
|
||||||
|
const {
|
||||||
|
onClick: handleClick,
|
||||||
|
buttonState: state,
|
||||||
|
} = useStatelessAsyncClickable(onClick, name);
|
||||||
|
|
||||||
|
return state === "error" ? (
|
||||||
|
<Text mr="2">Error</Text>
|
||||||
|
) : state === "loading" ? (
|
||||||
|
<LoadingSpinner mr="2" foreground={"white"} background="gray" />
|
||||||
|
) : state === "success" ? (
|
||||||
|
<Text mr="2">Done</Text>
|
||||||
|
) : (
|
||||||
|
<Toggle onClick={handleClick} {...rest} />
|
||||||
|
);
|
||||||
|
}
|
@ -26,7 +26,7 @@ const StatusBar = (props) => {
|
|||||||
|
|
||||||
<StatusBarItem mr={2} onClick={() => props.api.local.setOmnibox()}>
|
<StatusBarItem mr={2} onClick={() => props.api.local.setOmnibox()}>
|
||||||
{ !props.doNotDisturb && (props.notificationsCount > 0 || invites.length > 0) &&
|
{ !props.doNotDisturb && (props.notificationsCount > 0 || invites.length > 0) &&
|
||||||
(<Box display="block" right="-5px" top="-5px" position="absolute" >
|
(<Box display="block" right="-8px" top="-8px" position="absolute" >
|
||||||
<Icon color="blue" icon="Bullet" />
|
<Icon color="blue" icon="Bullet" />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
@ -20,6 +20,7 @@ export function StatusBarItem({
|
|||||||
color="washedGray"
|
color="washedGray"
|
||||||
bg="white"
|
bg="white"
|
||||||
px={2}
|
px={2}
|
||||||
|
overflow='visible'
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -9,9 +9,9 @@ import {
|
|||||||
Col,
|
Col,
|
||||||
Label,
|
Label,
|
||||||
Button,
|
Button,
|
||||||
|
LoadingSpinner,
|
||||||
|
BaseLabel
|
||||||
} from "@tlon/indigo-react";
|
} from "@tlon/indigo-react";
|
||||||
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
|
|
||||||
import { FormError } from "~/views/components/FormError";
|
|
||||||
import { Group, GroupPolicy } from "~/types/group-update";
|
import { Group, GroupPolicy } from "~/types/group-update";
|
||||||
import { Enc } from "~/types/noun";
|
import { Enc } from "~/types/noun";
|
||||||
import { Association } from "~/types/metadata-update";
|
import { Association } from "~/types/metadata-update";
|
||||||
@ -24,6 +24,7 @@ import { useHistory } from "react-router-dom";
|
|||||||
import { uxToHex } from "~/logic/lib/util";
|
import { uxToHex } from "~/logic/lib/util";
|
||||||
import { FormikOnBlur } from "~/views/components/FormikOnBlur";
|
import { FormikOnBlur } from "~/views/components/FormikOnBlur";
|
||||||
import {GroupNotificationsConfig} from "~/types";
|
import {GroupNotificationsConfig} from "~/types";
|
||||||
|
import {StatelessAsyncToggle} from "~/views/components/StatelessAsyncToggle";
|
||||||
|
|
||||||
function DeleteGroup(props: {
|
function DeleteGroup(props: {
|
||||||
owner: boolean;
|
owner: boolean;
|
||||||
@ -42,7 +43,7 @@ function DeleteGroup(props: {
|
|||||||
const action = props.owner ? "Delete" : "Leave";
|
const action = props.owner ? "Delete" : "Leave";
|
||||||
const description = props.owner
|
const description = props.owner
|
||||||
? "Permanently delete this group. (All current members will no longer see this group.)"
|
? "Permanently delete this group. (All current members will no longer see this group.)"
|
||||||
: "Leave this group. You can rejoin if it is an open group, or if you are reinvited";
|
: "You can rejoin if it is an open group, or if you are reinvited";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col>
|
<Col>
|
||||||
@ -50,7 +51,7 @@ function DeleteGroup(props: {
|
|||||||
<Label gray mt="2">
|
<Label gray mt="2">
|
||||||
{description}
|
{description}
|
||||||
</Label>
|
</Label>
|
||||||
<StatelessAsyncButton onClick={onDelete} mt={2} destructive>
|
<StatelessAsyncButton onClick={onDelete} mt={2} destructive={props.owner}>
|
||||||
{action} this group
|
{action} this group
|
||||||
</StatelessAsyncButton>
|
</StatelessAsyncButton>
|
||||||
</Col>
|
</Col>
|
||||||
@ -71,27 +72,27 @@ export function GroupPersonalSettings(props: {
|
|||||||
|
|
||||||
const watching = props.notificationsGroupConfig.findIndex(g => g === groupPath) !== -1;
|
const watching = props.notificationsGroupConfig.findIndex(g => g === groupPath) !== -1;
|
||||||
|
|
||||||
const initialValues: FormSchema = {
|
const onClick = async () => {
|
||||||
watching
|
const func = !watching ? 'listenGroup' : 'ignoreGroup';
|
||||||
};
|
|
||||||
const onSubmit = async (values: FormSchema) => {
|
|
||||||
if(values.watching === watching) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const func = values.watching ? 'listenGroup' : 'ignoreGroup';
|
|
||||||
await props.api.hark[func](groupPath);
|
await props.api.hark[func](groupPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const owner = (props.group?.tags?.role?.admin.has(window.ship) || false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col gapY="4">
|
<Col gapY="4">
|
||||||
<FormikOnBlur initialValues={initialValues} onSubmit={onSubmit}>
|
<BaseLabel
|
||||||
<Toggle
|
htmlFor="asyncToggle"
|
||||||
id="watching"
|
display="flex"
|
||||||
label="Notify me on group activity"
|
cursor="pointer"
|
||||||
caption="Send me notifications when this group changes"
|
>
|
||||||
/>
|
<StatelessAsyncToggle selected={watching} onClick={onClick} />
|
||||||
</FormikOnBlur>
|
<Col>
|
||||||
<DeleteGroup association={props.association} owner api={props.api} />
|
<Label>Notify me on group activity</Label>
|
||||||
|
<Label mt="2" gray>Send me notifications when this group changes</Label>
|
||||||
|
</Col>
|
||||||
|
</BaseLabel>
|
||||||
|
<DeleteGroup association={props.association} owner={owner} api={props.api} />
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,13 @@ export function useChat(
|
|||||||
export function useGraphModule(
|
export function useGraphModule(
|
||||||
graphKeys: Set<string>,
|
graphKeys: Set<string>,
|
||||||
graphs: Graphs,
|
graphs: Graphs,
|
||||||
|
graphUnreads: Record<string, number>
|
||||||
): SidebarAppConfig {
|
): SidebarAppConfig {
|
||||||
const getStatus = useCallback(
|
const getStatus = useCallback(
|
||||||
(s: string) => {
|
(s: string) => {
|
||||||
|
if((graphUnreads[s] || 0) > 0) {
|
||||||
|
return 'unread';
|
||||||
|
}
|
||||||
const [, , host, name] = s.split("/");
|
const [, , host, name] = s.split("/");
|
||||||
const graphKey = `${host.slice(1)}/${name}`;
|
const graphKey = `${host.slice(1)}/${name}`;
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ interface SkeletonProps {
|
|||||||
|
|
||||||
export function Skeleton(props: SkeletonProps) {
|
export function Skeleton(props: SkeletonProps) {
|
||||||
const chatConfig = useChat(props.inbox, props.chatSynced);
|
const chatConfig = useChat(props.inbox, props.chatSynced);
|
||||||
const graphConfig = useGraphModule(props.graphKeys, props.graphs);
|
const graphConfig = useGraphModule(props.graphKeys, props.graphs, props.graphUnreads);
|
||||||
const config = useMemo(
|
const config = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
graph: graphConfig,
|
graph: graphConfig,
|
||||||
|
Loading…
Reference in New Issue
Block a user