Add Notification View
Summary: This stack tries to migrate notification system to Sandy. This diff contains on the view; other functionalities aren't working. Also, some of the views will be implemented as functionalities are added. Reviewed By: mweststrate Differential Revision: D24390370 fbshipit-source-id: 8e11a01d9462934ff1fadf411e7e8f57ca7ef078
This commit is contained in:
committed by
Facebook GitHub Bot
parent
2c88ca3d18
commit
467a6b16fb
@@ -118,7 +118,10 @@ export function LeftRail({
|
||||
}}
|
||||
/>
|
||||
<LeftRailButton icon={<AppstoreOutlined />} title="Plugin Manager" />
|
||||
<LeftRailButton icon={<BellOutlined />} title="Notifications" />
|
||||
<NotificationButton
|
||||
toplevelSelection={toplevelSelection}
|
||||
setToplevelSelection={setToplevelSelection}
|
||||
/>
|
||||
<LeftRailDivider />
|
||||
<DebugLogsButton
|
||||
toplevelSelection={toplevelSelection}
|
||||
@@ -185,6 +188,24 @@ function RightSidebarToggleButton() {
|
||||
);
|
||||
}
|
||||
|
||||
function NotificationButton({
|
||||
toplevelSelection,
|
||||
setToplevelSelection,
|
||||
}: ToplevelProps) {
|
||||
const notificationCount = useStore(
|
||||
(state) => state.notifications.activeNotifications.length,
|
||||
);
|
||||
return (
|
||||
<LeftRailButton
|
||||
icon={<BellOutlined />}
|
||||
title="Notifications"
|
||||
selected={toplevelSelection === 'notification'}
|
||||
count={notificationCount}
|
||||
onClick={() => setToplevelSelection('notification')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DebugLogsButton({
|
||||
toplevelSelection,
|
||||
setToplevelSelection,
|
||||
|
||||
@@ -24,8 +24,13 @@ import {toggleLeftSidebarVisible} from '../reducers/application';
|
||||
import {AppInspect} from './appinspect/AppInspect';
|
||||
import PluginContainer from '../PluginContainer';
|
||||
import {ContentContainer} from './ContentContainer';
|
||||
import {Notification} from './notification/Notification';
|
||||
|
||||
export type ToplevelNavItem = 'appinspect' | 'flipperlogs' | undefined;
|
||||
export type ToplevelNavItem =
|
||||
| 'appinspect'
|
||||
| 'flipperlogs'
|
||||
| 'notification'
|
||||
| undefined;
|
||||
export type ToplevelProps = {
|
||||
toplevelSelection: ToplevelNavItem;
|
||||
setToplevelSelection: (_newSelection: ToplevelNavItem) => void;
|
||||
@@ -51,7 +56,8 @@ export function SandyApp({logger}: {logger: Logger}) {
|
||||
const setToplevelSelection = useCallback(
|
||||
(newSelection: ToplevelNavItem) => {
|
||||
// toggle sidebar visibility if needed
|
||||
const hasLeftSidebar = newSelection === 'appinspect';
|
||||
const hasLeftSidebar =
|
||||
newSelection === 'appinspect' || newSelection === 'notification';
|
||||
if (hasLeftSidebar) {
|
||||
if (newSelection === toplevelSelection) {
|
||||
dispatch(toggleLeftSidebarVisible());
|
||||
@@ -76,10 +82,12 @@ export function SandyApp({logger}: {logger: Logger}) {
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const leftMenuContent =
|
||||
leftSidebarVisible && toplevelSelection === 'appinspect' ? (
|
||||
<AppInspect />
|
||||
) : null;
|
||||
const leftMenuContent = !leftSidebarVisible ? null : toplevelSelection ===
|
||||
'appinspect' ? (
|
||||
<AppInspect />
|
||||
) : toplevelSelection === 'notification' ? (
|
||||
<Notification />
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<SandyContext.Provider value={true}>
|
||||
|
||||
188
desktop/app/src/sandy-chrome/notification/Notification.tsx
Normal file
188
desktop/app/src/sandy-chrome/notification/Notification.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {Layout, styled} from '../../ui';
|
||||
import {Input, Typography, Button, Collapse} from 'antd';
|
||||
import {
|
||||
DownOutlined,
|
||||
UpOutlined,
|
||||
SearchOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
SettingOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {LeftSidebar, SidebarTitle} from '../LeftSidebar';
|
||||
import {PluginNotification} from '../../reducers/notifications';
|
||||
import {theme} from '../theme';
|
||||
|
||||
const {Title, Text, Paragraph} = Typography;
|
||||
|
||||
// NOTE: remove after the component link to state
|
||||
const notificationExample: Array<PluginNotification> = [
|
||||
{
|
||||
notification: {
|
||||
id: 'testid_0',
|
||||
title: `
|
||||
CRASH: FATAL EXCEPTION:
|
||||
mainReason: java.lang.RuntimeException: Artificially triggered crash from Flipper sample app
|
||||
`,
|
||||
message:
|
||||
'very very very very very very very very very very very very very very very very very very very very very long',
|
||||
severity: 'error',
|
||||
},
|
||||
pluginId: 'testPluginId',
|
||||
client: 'iPortaldroid',
|
||||
},
|
||||
{
|
||||
notification: {
|
||||
id: 'testid_1',
|
||||
title: `CRASH: FATAL EXCEPTION:
|
||||
mainReason: java.lang.RuntimeException: Artificially triggered crash from Flipper sample app
|
||||
`,
|
||||
message: `FATAL EXCEPTION: main`,
|
||||
severity: 'error',
|
||||
},
|
||||
pluginId: 'testPluginId',
|
||||
client: 'iPortaldroid',
|
||||
},
|
||||
{
|
||||
notification: {
|
||||
id: 'testid_2',
|
||||
action: '1',
|
||||
title: `CRASH: FATAL EXCEPTION: mainReason: java.lang.RuntimeException: Artificially triggered`,
|
||||
message: `Callstack: FATAL EXCEPTION: main Process: com.facebook.flipper.sample, PID: 1646 java.lang.RuntimeException: Artificially triggered crash from Flipper sample app at com.facebook.flipper.sample.RootComponentSpec`,
|
||||
severity: 'error',
|
||||
category:
|
||||
'java.lang.RuntimeException: Artificially triggered crash from Flipper sample app',
|
||||
},
|
||||
pluginId: 'CrashReporter',
|
||||
client: 'emulator-5554',
|
||||
},
|
||||
];
|
||||
|
||||
const CollapseContainer = styled.div({
|
||||
'.ant-collapse-ghost .ant-collapse-item': {
|
||||
'& > .ant-collapse-header': {
|
||||
paddingLeft: '16px',
|
||||
},
|
||||
'& > .ant-collapse-content > .ant-collapse-content-box': {
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function DetailCollapse({detail}: {detail: string | React.ReactNode}) {
|
||||
const detailView =
|
||||
typeof detail === 'string' ? (
|
||||
<Paragraph
|
||||
type="secondary"
|
||||
style={{
|
||||
fontSize: theme.fontSize.smallBody,
|
||||
marginBottom: 0,
|
||||
}}
|
||||
ellipsis={{rows: 3}}>
|
||||
{detail}
|
||||
</Paragraph>
|
||||
) : (
|
||||
detail
|
||||
);
|
||||
return (
|
||||
<CollapseContainer>
|
||||
<Collapse
|
||||
ghost
|
||||
expandIcon={({isActive}) =>
|
||||
isActive ? (
|
||||
<UpOutlined style={{fontSize: 8, left: 0}} />
|
||||
) : (
|
||||
<DownOutlined style={{fontSize: 8, left: 0}} />
|
||||
)
|
||||
}>
|
||||
<Collapse.Panel
|
||||
key="detail"
|
||||
header={
|
||||
<Text type="secondary" style={{fontSize: theme.fontSize.smallBody}}>
|
||||
View detail
|
||||
</Text>
|
||||
}>
|
||||
{detailView}
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
</CollapseContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function NotificationEntry({notification}: {notification: PluginNotification}) {
|
||||
const {notification: content, pluginId, client} = notification;
|
||||
// TODO: figure out how to transform app name to icon
|
||||
const icon = React.createElement(ExclamationCircleOutlined, {
|
||||
style: {color: theme.primaryColor},
|
||||
});
|
||||
return (
|
||||
<Layout.Vertical gap="small" pad="medium">
|
||||
<Layout.Horizontal gap="tiny" center>
|
||||
{icon}
|
||||
<Text>{pluginId}</Text>
|
||||
</Layout.Horizontal>
|
||||
<Title level={4} ellipsis={{rows: 2}}>
|
||||
{content.title}
|
||||
</Title>
|
||||
<Text type="secondary" style={{fontSize: theme.fontSize.smallBody}}>
|
||||
{client}
|
||||
</Text>
|
||||
<Button style={{width: 'fit-content'}} size="small">
|
||||
Open
|
||||
</Button>
|
||||
<DetailCollapse detail={content.message} />
|
||||
</Layout.Vertical>
|
||||
);
|
||||
}
|
||||
|
||||
function NotificationList({
|
||||
notifications,
|
||||
}: {
|
||||
notifications: Array<PluginNotification>;
|
||||
}) {
|
||||
return (
|
||||
<Layout.ScrollContainer vertical>
|
||||
<Layout.Vertical>
|
||||
{notifications.map((notification) => (
|
||||
<NotificationEntry
|
||||
key={notification.notification.id}
|
||||
notification={notification}
|
||||
/>
|
||||
))}
|
||||
</Layout.Vertical>
|
||||
</Layout.ScrollContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export function Notification() {
|
||||
const actions = (
|
||||
<div>
|
||||
<Layout.Horizontal gap="medium">
|
||||
<SettingOutlined style={{fontSize: theme.space.large}} />
|
||||
<DeleteOutlined style={{fontSize: theme.space.large}} />
|
||||
</Layout.Horizontal>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<LeftSidebar>
|
||||
<Layout.Top>
|
||||
<Layout.Vertical gap="tiny" padv="tiny" borderBottom>
|
||||
<SidebarTitle actions={actions}>notifications</SidebarTitle>
|
||||
<Layout.Container padh="medium" padv="small">
|
||||
<Input placeholder="Search..." prefix={<SearchOutlined />} />
|
||||
</Layout.Container>
|
||||
</Layout.Vertical>
|
||||
<NotificationList notifications={notificationExample} />
|
||||
</Layout.Top>
|
||||
</LeftSidebar>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user