Move plugins to "sonar/desktop/plugins"
Summary: Plugins moved from "sonar/desktop/src/plugins" to "sonar/desktop/plugins". Fixed all the paths after moving. New "desktop" folder structure: - `src` - Flipper desktop app JS code executing in Electron Renderer (Chrome) process. - `static` - Flipper desktop app JS code executing in Electron Main (Node.js) process. - `plugins` - Flipper desktop JS plugins. - `pkg` - Flipper packaging lib and CLI tool. - `doctor` - Flipper diagnostics lib and CLI tool. - `scripts` - Build scripts for Flipper desktop app. - `headless` - Headless version of Flipper desktop app. - `headless-tests` - Integration tests running agains Flipper headless version. Reviewed By: mweststrate Differential Revision: D20344186 fbshipit-source-id: d020da970b2ea1e001f9061a8782bfeb54e31ba0
This commit is contained in:
committed by
Facebook GitHub Bot
parent
beb5c85e69
commit
10d990c32c
73
desktop/plugins/navigation/components/AutoCompleteSheet.tsx
Normal file
73
desktop/plugins/navigation/components/AutoCompleteSheet.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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 {Glyph, styled} from 'flipper';
|
||||
import {useItemNavigation} from '../hooks/autoCompleteSheet';
|
||||
import {filterProvidersToLineItems} from '../util/autoCompleteProvider';
|
||||
import {AutoCompleteProvider, AutoCompleteLineItem, URI} from '../types';
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
providers: Array<AutoCompleteProvider>;
|
||||
onHighlighted: (uri: URI) => void;
|
||||
onNavigate: (uri: URI) => void;
|
||||
query: string;
|
||||
};
|
||||
|
||||
const MAX_ITEMS = 5;
|
||||
|
||||
const AutoCompleteSheetContainer = styled.div({
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
top: 'calc(100% - 3px)',
|
||||
backgroundColor: 'white',
|
||||
zIndex: 1,
|
||||
borderBottomRightRadius: 10,
|
||||
borderBottomLeftRadius: 10,
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)',
|
||||
});
|
||||
|
||||
const SheetItem = styled.div({
|
||||
padding: 5,
|
||||
textOverflow: 'ellipsis',
|
||||
overflowX: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
'&.selected': {
|
||||
backgroundColor: 'rgba(155, 155, 155, 0.2)',
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(155, 155, 155, 0.2)',
|
||||
},
|
||||
});
|
||||
|
||||
const SheetItemIcon = styled.span({
|
||||
padding: 8,
|
||||
});
|
||||
|
||||
export default (props: Props) => {
|
||||
const {providers, onHighlighted, onNavigate, query} = props;
|
||||
const lineItems = filterProvidersToLineItems(providers, query, MAX_ITEMS);
|
||||
lineItems.unshift({uri: query, matchPattern: query, icon: 'send'});
|
||||
const selectedItem = useItemNavigation(lineItems, onHighlighted);
|
||||
return (
|
||||
<AutoCompleteSheetContainer>
|
||||
{lineItems.map((lineItem: AutoCompleteLineItem, idx: number) => (
|
||||
<SheetItem
|
||||
className={idx === selectedItem ? 'selected' : ''}
|
||||
key={idx}
|
||||
onMouseDown={() => onNavigate(lineItem.uri)}>
|
||||
<SheetItemIcon>
|
||||
<Glyph name={lineItem.icon} size={16} variant="outline" />
|
||||
</SheetItemIcon>
|
||||
{lineItem.matchPattern}
|
||||
</SheetItem>
|
||||
))}
|
||||
</AutoCompleteSheetContainer>
|
||||
);
|
||||
};
|
||||
126
desktop/plugins/navigation/components/BookmarksSidebar.tsx
Normal file
126
desktop/plugins/navigation/components/BookmarksSidebar.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 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 {
|
||||
DetailSidebar,
|
||||
FlexCenter,
|
||||
styled,
|
||||
colors,
|
||||
FlexRow,
|
||||
FlexColumn,
|
||||
Text,
|
||||
Panel,
|
||||
} from 'flipper';
|
||||
import {Bookmark, URI} from '../types';
|
||||
import {IconButton} from './';
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
bookmarks: Map<string, Bookmark>;
|
||||
onNavigate: (uri: URI) => void;
|
||||
onRemove: (uri: URI) => void;
|
||||
};
|
||||
|
||||
const NoData = styled(FlexCenter)({
|
||||
fontSize: 18,
|
||||
color: colors.macOSTitleBarIcon,
|
||||
});
|
||||
|
||||
const BookmarksList = styled.div({
|
||||
overflowY: 'scroll',
|
||||
overflowX: 'hidden',
|
||||
height: '100%',
|
||||
backgroundColor: colors.white,
|
||||
});
|
||||
|
||||
const BookmarkContainer = styled(FlexRow)({
|
||||
width: '100%',
|
||||
padding: 10,
|
||||
height: 55,
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
borderBottom: `1px ${colors.greyTint} solid`,
|
||||
':last-child': {
|
||||
borderBottom: '0',
|
||||
},
|
||||
':active': {
|
||||
backgroundColor: colors.highlight,
|
||||
color: colors.white,
|
||||
},
|
||||
':active *': {
|
||||
color: colors.white,
|
||||
},
|
||||
});
|
||||
|
||||
const BookmarkTitle = styled(Text)({
|
||||
fontSize: '1.1em',
|
||||
overflowX: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
fontWeight: 500,
|
||||
});
|
||||
|
||||
const BookmarkSubtitle = styled(Text)({
|
||||
overflowX: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
color: colors.greyTint3,
|
||||
marginTop: 4,
|
||||
});
|
||||
|
||||
const TextContainer = styled(FlexColumn)({
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
const alphabetizeBookmarkCompare = (b1: Bookmark, b2: Bookmark) => {
|
||||
return b1.uri < b2.uri ? -1 : b1.uri > b2.uri ? 1 : 0;
|
||||
};
|
||||
|
||||
export default (props: Props) => {
|
||||
const {bookmarks, onNavigate, onRemove} = props;
|
||||
return (
|
||||
<DetailSidebar>
|
||||
<Panel heading="Bookmarks" floating={false} padded={false}>
|
||||
{bookmarks.size === 0 ? (
|
||||
<NoData grow>No Bookmarks</NoData>
|
||||
) : (
|
||||
<BookmarksList>
|
||||
{[...bookmarks.values()]
|
||||
.sort(alphabetizeBookmarkCompare)
|
||||
.map((bookmark, idx) => (
|
||||
<BookmarkContainer
|
||||
key={idx}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
onNavigate(bookmark.uri);
|
||||
}}>
|
||||
<TextContainer grow>
|
||||
<BookmarkTitle>
|
||||
{bookmark.commonName || bookmark.uri}
|
||||
</BookmarkTitle>
|
||||
{!bookmark.commonName && (
|
||||
<BookmarkSubtitle>{bookmark.uri}</BookmarkSubtitle>
|
||||
)}
|
||||
</TextContainer>
|
||||
<IconButton
|
||||
color={colors.macOSTitleBarButtonBackgroundActive}
|
||||
outline={false}
|
||||
icon="cross-circle"
|
||||
size={16}
|
||||
onClick={() => onRemove(bookmark.uri)}
|
||||
/>
|
||||
</BookmarkContainer>
|
||||
))}
|
||||
</BookmarksList>
|
||||
)}
|
||||
</Panel>
|
||||
</DetailSidebar>
|
||||
);
|
||||
};
|
||||
50
desktop/plugins/navigation/components/FavoriteButton.tsx
Normal file
50
desktop/plugins/navigation/components/FavoriteButton.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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 {styled, IconSize, colors} from 'flipper';
|
||||
import {IconButton} from './';
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
onClick?: () => void;
|
||||
highlighted: boolean;
|
||||
size: IconSize;
|
||||
};
|
||||
|
||||
const FavoriteButtonContainer = styled.div({
|
||||
position: 'relative',
|
||||
'>:first-child': {
|
||||
position: 'absolute',
|
||||
},
|
||||
'>:last-child': {
|
||||
position: 'relative',
|
||||
},
|
||||
});
|
||||
|
||||
export default (props: Props) => {
|
||||
const {highlighted, onClick, ...iconButtonProps} = props;
|
||||
return (
|
||||
<FavoriteButtonContainer>
|
||||
{highlighted ? (
|
||||
<IconButton
|
||||
outline={false}
|
||||
color={colors.lemon}
|
||||
icon="star"
|
||||
{...iconButtonProps}
|
||||
/>
|
||||
) : null}
|
||||
<IconButton
|
||||
outline={true}
|
||||
icon="star"
|
||||
onClick={onClick}
|
||||
{...iconButtonProps}
|
||||
/>
|
||||
</FavoriteButtonContainer>
|
||||
);
|
||||
};
|
||||
65
desktop/plugins/navigation/components/IconButton.tsx
Normal file
65
desktop/plugins/navigation/components/IconButton.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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 {Glyph, styled, keyframes, IconSize} from 'flipper';
|
||||
import React from 'react';
|
||||
|
||||
const shrinkAnimation = keyframes({
|
||||
'0%': {
|
||||
transform: 'scale(1);',
|
||||
},
|
||||
'100%': {
|
||||
transform: 'scale(.9)',
|
||||
},
|
||||
});
|
||||
|
||||
type Props = {
|
||||
icon: string;
|
||||
outline?: boolean;
|
||||
onClick?: () => void;
|
||||
color?: string;
|
||||
size: IconSize;
|
||||
};
|
||||
|
||||
const RippleEffect = styled.div({
|
||||
padding: 5,
|
||||
borderRadius: 100,
|
||||
backgroundPosition: 'center',
|
||||
transition: 'background 0.5s',
|
||||
':hover': {
|
||||
background:
|
||||
'rgba(155, 155, 155, 0.2) radial-gradient(circle, transparent 1%, rgba(155, 155, 155, 0.2) 1%) center/15000%',
|
||||
},
|
||||
':active': {
|
||||
backgroundColor: 'rgba(201, 200, 200, 0.5)',
|
||||
backgroundSize: '100%',
|
||||
transition: 'background 0s',
|
||||
},
|
||||
});
|
||||
|
||||
const IconButton = styled.div({
|
||||
':active': {
|
||||
animation: `${shrinkAnimation} .25s ease forwards`,
|
||||
},
|
||||
});
|
||||
|
||||
export default function(props: Props) {
|
||||
return (
|
||||
<RippleEffect>
|
||||
<IconButton className="icon-button" onClick={props.onClick}>
|
||||
<Glyph
|
||||
name={props.icon}
|
||||
size={props.size}
|
||||
color={props.color}
|
||||
variant={props.outline ? 'outline' : 'filled'}
|
||||
/>
|
||||
</IconButton>
|
||||
</RippleEffect>
|
||||
);
|
||||
}
|
||||
240
desktop/plugins/navigation/components/NavigationInfoBox.tsx
Normal file
240
desktop/plugins/navigation/components/NavigationInfoBox.tsx
Normal file
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* 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 {
|
||||
styled,
|
||||
colors,
|
||||
ManagedTable,
|
||||
TableBodyRow,
|
||||
FlexCenter,
|
||||
LoadingIndicator,
|
||||
Button,
|
||||
Glyph,
|
||||
} from 'flipper';
|
||||
import {parseURIParameters, stripQueryParameters} from '../util/uri';
|
||||
import React from 'react';
|
||||
|
||||
const BOX_HEIGHT = 240;
|
||||
|
||||
type Props = {
|
||||
isBookmarked: boolean;
|
||||
uri: string | null;
|
||||
className: string | null;
|
||||
onNavigate: (query: string) => void;
|
||||
onFavorite: (query: string) => void;
|
||||
screenshot: string | null;
|
||||
date: Date | null;
|
||||
};
|
||||
|
||||
const ScreenshotContainer = styled.div({
|
||||
width: 200,
|
||||
minWidth: 200,
|
||||
overflow: 'hidden',
|
||||
borderLeft: `1px ${colors.blueGreyTint90} solid`,
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
borderRadius: 10,
|
||||
img: {
|
||||
width: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
const NoData = styled.div({
|
||||
color: colors.light30,
|
||||
fontSize: 14,
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
const NavigationDataContainer = styled.div({
|
||||
alignItems: 'flex-start',
|
||||
flexGrow: 1,
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
const Footer = styled.div({
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
borderTop: `1px ${colors.blueGreyTint90} solid`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const Seperator = styled.div({
|
||||
flexGrow: 1,
|
||||
});
|
||||
|
||||
const TimeContainer = styled.div({
|
||||
color: colors.light30,
|
||||
fontSize: 14,
|
||||
});
|
||||
|
||||
const NavigationInfoBoxContainer = styled.div({
|
||||
display: 'flex',
|
||||
height: BOX_HEIGHT,
|
||||
borderRadius: 10,
|
||||
flexGrow: 1,
|
||||
position: 'relative',
|
||||
marginBottom: 10,
|
||||
backgroundColor: colors.white,
|
||||
boxShadow: '1px 1px 5px rgba(0,0,0,0.1)',
|
||||
});
|
||||
|
||||
const Header = styled.div({
|
||||
fontSize: 18,
|
||||
fontWeight: 500,
|
||||
userSelect: 'text',
|
||||
cursor: 'text',
|
||||
padding: 10,
|
||||
borderBottom: `1px ${colors.blueGreyTint90} solid`,
|
||||
display: 'flex',
|
||||
});
|
||||
|
||||
const ClassNameContainer = styled.div({
|
||||
color: colors.light30,
|
||||
});
|
||||
|
||||
const ParametersContainer = styled.div({
|
||||
height: 150,
|
||||
'&>*': {
|
||||
height: 150,
|
||||
marginBottom: 20,
|
||||
},
|
||||
});
|
||||
|
||||
const NoParamters = styled(FlexCenter)({
|
||||
fontSize: 18,
|
||||
color: colors.light10,
|
||||
});
|
||||
|
||||
const TimelineCircle = styled.div({
|
||||
width: 18,
|
||||
height: 18,
|
||||
top: 11,
|
||||
left: -33,
|
||||
backgroundColor: colors.light02,
|
||||
border: `4px solid ${colors.highlight}`,
|
||||
borderRadius: '50%',
|
||||
position: 'absolute',
|
||||
});
|
||||
|
||||
const TimelineMiniCircle = styled.div({
|
||||
width: 12,
|
||||
height: 12,
|
||||
top: 1,
|
||||
left: -30,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: colors.highlight,
|
||||
position: 'absolute',
|
||||
});
|
||||
|
||||
const buildParameterTable = (parameters: Map<string, string>) => {
|
||||
const tableRows: Array<TableBodyRow> = [];
|
||||
let idx = 0;
|
||||
parameters.forEach((parameter_value, parameter) => {
|
||||
tableRows.push({
|
||||
key: idx.toString(),
|
||||
columns: {
|
||||
parameter: {
|
||||
value: parameter,
|
||||
},
|
||||
value: {
|
||||
value: parameter_value,
|
||||
},
|
||||
},
|
||||
});
|
||||
idx++;
|
||||
});
|
||||
return (
|
||||
<ManagedTable
|
||||
columns={{parameter: {value: 'Parameter'}, value: {value: 'Value'}}}
|
||||
rows={tableRows}
|
||||
zebra={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default (props: Props) => {
|
||||
const {
|
||||
uri,
|
||||
isBookmarked,
|
||||
className,
|
||||
screenshot,
|
||||
onNavigate,
|
||||
onFavorite,
|
||||
date,
|
||||
} = props;
|
||||
if (uri == null && className == null) {
|
||||
return (
|
||||
<>
|
||||
<NoData>
|
||||
<TimelineMiniCircle />
|
||||
Unknown Navigation Event
|
||||
</NoData>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
const parameters = uri != null ? parseURIParameters(uri) : null;
|
||||
return (
|
||||
<NavigationInfoBoxContainer>
|
||||
<TimelineCircle />
|
||||
<NavigationDataContainer>
|
||||
<Header>
|
||||
{uri != null ? stripQueryParameters(uri) : ''}
|
||||
<Seperator />
|
||||
{className != null ? (
|
||||
<>
|
||||
<Glyph
|
||||
color={colors.light30}
|
||||
size={16}
|
||||
name="paper-fold-text"
|
||||
/>
|
||||
|
||||
<ClassNameContainer>
|
||||
{className != null ? className : ''}
|
||||
</ClassNameContainer>
|
||||
</>
|
||||
) : null}
|
||||
</Header>
|
||||
<ParametersContainer>
|
||||
{parameters != null && parameters.size > 0 ? (
|
||||
buildParameterTable(parameters)
|
||||
) : (
|
||||
<NoParamters grow>No Parameters for this Event</NoParamters>
|
||||
)}
|
||||
</ParametersContainer>
|
||||
<Footer>
|
||||
{uri != null ? (
|
||||
<>
|
||||
<Button onClick={() => onNavigate(uri)}>Open</Button>
|
||||
<Button onClick={() => onFavorite(uri)}>
|
||||
{isBookmarked ? 'Edit Bookmark' : 'Bookmark'}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
<Seperator />
|
||||
<TimeContainer>
|
||||
{date != null ? date.toTimeString() : ''}
|
||||
</TimeContainer>
|
||||
</Footer>
|
||||
</NavigationDataContainer>
|
||||
{uri != null || className != null ? (
|
||||
<ScreenshotContainer>
|
||||
{screenshot != null ? (
|
||||
<img src={screenshot} />
|
||||
) : (
|
||||
<FlexCenter grow>
|
||||
<LoadingIndicator size={32} />
|
||||
</FlexCenter>
|
||||
)}
|
||||
</ScreenshotContainer>
|
||||
) : null}
|
||||
</NavigationInfoBoxContainer>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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 {Button, FlexColumn, Input, Sheet, styled, Glyph, colors} from 'flipper';
|
||||
import {
|
||||
replaceRequiredParametersWithValues,
|
||||
parameterIsNumberType,
|
||||
parameterIsBooleanType,
|
||||
validateParameter,
|
||||
liveEdit,
|
||||
} from '../util/uri';
|
||||
import {useRequiredParameterFormValidator} from '../hooks/requiredParameters';
|
||||
import React from 'react';
|
||||
|
||||
import {URI} from '../types';
|
||||
|
||||
type Props = {
|
||||
uri: string;
|
||||
requiredParameters: Array<string>;
|
||||
shouldShow: boolean;
|
||||
onHide?: () => void;
|
||||
onSubmit: (uri: URI) => void;
|
||||
};
|
||||
|
||||
const Container = styled(FlexColumn)({
|
||||
padding: 10,
|
||||
width: 600,
|
||||
});
|
||||
|
||||
const Title = styled.span({
|
||||
display: 'flex',
|
||||
marginTop: 8,
|
||||
marginLeft: 2,
|
||||
marginBottom: 8,
|
||||
});
|
||||
|
||||
const Text = styled.span({
|
||||
lineHeight: 1.3,
|
||||
});
|
||||
|
||||
const ErrorLabel = styled.span({
|
||||
color: colors.yellow,
|
||||
lineHeight: 1.4,
|
||||
});
|
||||
|
||||
const URIContainer = styled.div({
|
||||
lineHeight: 1.3,
|
||||
marginLeft: 2,
|
||||
marginBottom: 8,
|
||||
marginTop: 10,
|
||||
overflowWrap: 'break-word',
|
||||
});
|
||||
|
||||
const ButtonContainer = styled.div({
|
||||
marginLeft: 'auto',
|
||||
});
|
||||
|
||||
const RequiredParameterInput = styled(Input)({
|
||||
margin: 0,
|
||||
marginTop: 8,
|
||||
height: 30,
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
const WarningIconContainer = styled.span({
|
||||
marginRight: 8,
|
||||
});
|
||||
|
||||
export default (props: Props) => {
|
||||
const {shouldShow, onHide, onSubmit, uri, requiredParameters} = props;
|
||||
const {isValid, values, setValuesArray} = useRequiredParameterFormValidator(
|
||||
requiredParameters,
|
||||
);
|
||||
if (uri == null || !shouldShow) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Sheet onHideSheet={onHide}>
|
||||
{(hide: () => void) => {
|
||||
return (
|
||||
<Container>
|
||||
<Title>
|
||||
<WarningIconContainer>
|
||||
<Glyph
|
||||
name="caution-triangle"
|
||||
size={16}
|
||||
variant="filled"
|
||||
color={colors.yellow}
|
||||
/>
|
||||
</WarningIconContainer>
|
||||
<Text>
|
||||
This uri has required parameters denoted by {'{parameter}'}.
|
||||
</Text>
|
||||
</Title>
|
||||
{requiredParameters.map((paramater, idx) => (
|
||||
<div key={idx}>
|
||||
<RequiredParameterInput
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValuesArray([
|
||||
...values.slice(0, idx),
|
||||
event.target.value,
|
||||
...values.slice(idx + 1),
|
||||
])
|
||||
}
|
||||
name={paramater}
|
||||
placeholder={paramater}
|
||||
/>
|
||||
{values[idx] &&
|
||||
parameterIsNumberType(paramater) &&
|
||||
!validateParameter(values[idx], paramater) ? (
|
||||
<ErrorLabel>Parameter must be a number</ErrorLabel>
|
||||
) : null}
|
||||
{values[idx] &&
|
||||
parameterIsBooleanType(paramater) &&
|
||||
!validateParameter(values[idx], paramater) ? (
|
||||
<ErrorLabel>
|
||||
Parameter must be either 'true' or 'false'
|
||||
</ErrorLabel>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
<URIContainer>{liveEdit(uri, values)}</URIContainer>
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (onHide != null) {
|
||||
onHide();
|
||||
}
|
||||
setValuesArray([]);
|
||||
hide();
|
||||
}}
|
||||
compact
|
||||
padded>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type={isValid ? 'primary' : undefined}
|
||||
onClick={() => {
|
||||
onSubmit(replaceRequiredParametersWithValues(uri, values));
|
||||
if (onHide != null) {
|
||||
onHide();
|
||||
}
|
||||
setValuesArray([]);
|
||||
hide();
|
||||
}}
|
||||
disabled={!isValid}
|
||||
compact
|
||||
padded>
|
||||
Submit
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</Container>
|
||||
);
|
||||
}}
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
};
|
||||
117
desktop/plugins/navigation/components/SaveBookmarkDialog.tsx
Normal file
117
desktop/plugins/navigation/components/SaveBookmarkDialog.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 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 {Button, FlexColumn, Input, Sheet, styled} from 'flipper';
|
||||
import React, {useState} from 'react';
|
||||
import {Bookmark, URI} from '../types';
|
||||
|
||||
type Props = {
|
||||
uri: string | null;
|
||||
edit: boolean;
|
||||
shouldShow: boolean;
|
||||
onHide?: () => void;
|
||||
onRemove: (uri: URI) => void;
|
||||
onSubmit: (bookmark: Bookmark) => void;
|
||||
};
|
||||
|
||||
const Container = styled(FlexColumn)({
|
||||
padding: 10,
|
||||
width: 400,
|
||||
});
|
||||
|
||||
const Title = styled.div({
|
||||
fontWeight: 500,
|
||||
marginTop: 8,
|
||||
marginLeft: 2,
|
||||
marginBottom: 8,
|
||||
});
|
||||
|
||||
const URIContainer = styled.div({
|
||||
marginLeft: 2,
|
||||
marginBottom: 8,
|
||||
overflowWrap: 'break-word',
|
||||
});
|
||||
|
||||
const ButtonContainer = styled.div({
|
||||
marginLeft: 'auto',
|
||||
});
|
||||
|
||||
const NameInput = styled(Input)({
|
||||
margin: 0,
|
||||
marginBottom: 10,
|
||||
height: 30,
|
||||
});
|
||||
|
||||
export default (props: Props) => {
|
||||
const {edit, shouldShow, onHide, onRemove, onSubmit, uri} = props;
|
||||
const [commonName, setCommonName] = useState('');
|
||||
if (uri == null || !shouldShow) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Sheet onHideSheet={onHide}>
|
||||
{(onHide: () => void) => {
|
||||
return (
|
||||
<Container>
|
||||
<Title>
|
||||
{edit ? 'Edit bookmark...' : 'Save to bookmarks...'}
|
||||
</Title>
|
||||
<NameInput
|
||||
placeholder="Name..."
|
||||
value={commonName}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setCommonName(event.target.value)
|
||||
}
|
||||
/>
|
||||
<URIContainer>{uri}</URIContainer>
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onHide();
|
||||
setCommonName('');
|
||||
}}
|
||||
compact
|
||||
padded>
|
||||
Cancel
|
||||
</Button>
|
||||
{edit ? (
|
||||
<Button
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
onHide();
|
||||
onRemove(uri);
|
||||
setCommonName('');
|
||||
}}
|
||||
compact
|
||||
padded>
|
||||
Remove
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
onHide();
|
||||
onSubmit({uri, commonName});
|
||||
// The component state is remembered even after unmounting.
|
||||
// Thus it is necessary to reset the commonName here.
|
||||
setCommonName('');
|
||||
}}
|
||||
compact
|
||||
padded>
|
||||
Save
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</Container>
|
||||
);
|
||||
}}
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
};
|
||||
166
desktop/plugins/navigation/components/SearchBar.tsx
Normal file
166
desktop/plugins/navigation/components/SearchBar.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 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 {styled, SearchBox, SearchInput, Toolbar} from 'flipper';
|
||||
import {AutoCompleteSheet, IconButton, FavoriteButton} from './';
|
||||
import {AutoCompleteProvider, Bookmark, URI} from '../types';
|
||||
import React, {Component} from 'react';
|
||||
|
||||
type Props = {
|
||||
onFavorite: (query: URI) => void;
|
||||
onNavigate: (query: URI) => void;
|
||||
bookmarks: Map<URI, Bookmark>;
|
||||
providers: Array<AutoCompleteProvider>;
|
||||
uriFromAbove: URI;
|
||||
};
|
||||
|
||||
type State = {
|
||||
query: URI;
|
||||
inputFocused: boolean;
|
||||
autoCompleteSheetOpen: boolean;
|
||||
searchInputValue: URI;
|
||||
prevURIFromAbove: URI;
|
||||
};
|
||||
|
||||
const IconContainer = styled.div({
|
||||
display: 'inline-flex',
|
||||
height: '16px',
|
||||
alignItems: 'center',
|
||||
'': {
|
||||
marginLeft: 10,
|
||||
'.icon-button': {
|
||||
height: 16,
|
||||
},
|
||||
'img,div': {
|
||||
verticalAlign: 'top',
|
||||
alignItems: 'none',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const ToolbarContainer = styled.div({
|
||||
'.drop-shadow': {
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)',
|
||||
},
|
||||
});
|
||||
|
||||
const SearchInputContainer = styled.div({
|
||||
width: '100%',
|
||||
marginLeft: 5,
|
||||
marginRight: 9,
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
class SearchBar extends Component<Props, State> {
|
||||
state = {
|
||||
inputFocused: false,
|
||||
autoCompleteSheetOpen: false,
|
||||
query: '',
|
||||
searchInputValue: '',
|
||||
prevURIFromAbove: '',
|
||||
};
|
||||
|
||||
favorite = (searchInputValue: string) => {
|
||||
this.props.onFavorite(searchInputValue);
|
||||
};
|
||||
|
||||
navigateTo = (searchInputValue: string) => {
|
||||
this.setState({query: searchInputValue, searchInputValue});
|
||||
this.props.onNavigate(searchInputValue);
|
||||
};
|
||||
|
||||
queryInputChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.target.value;
|
||||
this.setState({query: value, searchInputValue: value});
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps = (newProps: Props, state: State) => {
|
||||
const {uriFromAbove: newURIFromAbove} = newProps;
|
||||
const {prevURIFromAbove} = state;
|
||||
if (newURIFromAbove !== prevURIFromAbove) {
|
||||
return {
|
||||
searchInputValue: newURIFromAbove,
|
||||
query: newURIFromAbove,
|
||||
prevURIFromAbove: newURIFromAbove,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {bookmarks, providers} = this.props;
|
||||
const {
|
||||
autoCompleteSheetOpen,
|
||||
inputFocused,
|
||||
searchInputValue,
|
||||
query,
|
||||
} = this.state;
|
||||
return (
|
||||
<ToolbarContainer>
|
||||
<Toolbar>
|
||||
<SearchBox className={inputFocused ? 'drop-shadow' : ''}>
|
||||
<SearchInputContainer>
|
||||
<SearchInput
|
||||
value={searchInputValue}
|
||||
onBlur={() =>
|
||||
this.setState({
|
||||
autoCompleteSheetOpen: false,
|
||||
inputFocused: false,
|
||||
})
|
||||
}
|
||||
onFocus={(event: React.FocusEvent<HTMLInputElement>) => {
|
||||
event.target.select();
|
||||
this.setState({
|
||||
autoCompleteSheetOpen: true,
|
||||
inputFocused: true,
|
||||
});
|
||||
}}
|
||||
onChange={this.queryInputChanged}
|
||||
onKeyPress={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.navigateTo(this.state.searchInputValue);
|
||||
(e.target as HTMLInputElement).blur();
|
||||
}
|
||||
}}
|
||||
placeholder="Navigate To..."
|
||||
/>
|
||||
{autoCompleteSheetOpen && query.length > 0 ? (
|
||||
<AutoCompleteSheet
|
||||
providers={providers}
|
||||
onNavigate={this.navigateTo}
|
||||
onHighlighted={(newInputValue: URI) =>
|
||||
this.setState({searchInputValue: newInputValue})
|
||||
}
|
||||
query={query}
|
||||
/>
|
||||
) : null}
|
||||
</SearchInputContainer>
|
||||
</SearchBox>
|
||||
{searchInputValue.length > 0 ? (
|
||||
<IconContainer>
|
||||
<IconButton
|
||||
icon="send"
|
||||
size={16}
|
||||
outline={true}
|
||||
onClick={() => this.navigateTo(searchInputValue)}
|
||||
/>
|
||||
<FavoriteButton
|
||||
size={16}
|
||||
highlighted={bookmarks.has(searchInputValue)}
|
||||
onClick={() => this.favorite(searchInputValue)}
|
||||
/>
|
||||
</IconContainer>
|
||||
) : null}
|
||||
</Toolbar>
|
||||
</ToolbarContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SearchBar;
|
||||
95
desktop/plugins/navigation/components/Timeline.tsx
Normal file
95
desktop/plugins/navigation/components/Timeline.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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 {colors, FlexCenter, styled} from 'flipper';
|
||||
import {NavigationInfoBox} from './';
|
||||
import {Bookmark, NavigationEvent, URI} from '../types';
|
||||
import React, {useRef} from 'react';
|
||||
|
||||
type Props = {
|
||||
bookmarks: Map<string, Bookmark>;
|
||||
events: Array<NavigationEvent>;
|
||||
onNavigate: (uri: URI) => void;
|
||||
onFavorite: (uri: URI) => void;
|
||||
};
|
||||
|
||||
const TimelineLine = styled.div({
|
||||
width: 2,
|
||||
backgroundColor: colors.highlight,
|
||||
position: 'absolute',
|
||||
top: 38,
|
||||
bottom: 0,
|
||||
});
|
||||
|
||||
const TimelineContainer = styled.div({
|
||||
position: 'relative',
|
||||
paddingLeft: 25,
|
||||
overflowY: 'scroll',
|
||||
flexGrow: 1,
|
||||
backgroundColor: colors.light02,
|
||||
scrollBehavior: 'smooth',
|
||||
'&>div': {
|
||||
position: 'relative',
|
||||
minHeight: '100%',
|
||||
'&:last-child': {
|
||||
paddingBottom: 25,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const NavigationEventContainer = styled.div({
|
||||
display: 'flex',
|
||||
paddingTop: 25,
|
||||
paddingLeft: 25,
|
||||
marginRight: 25,
|
||||
});
|
||||
|
||||
const NoData = styled(FlexCenter)({
|
||||
height: '100%',
|
||||
fontSize: 18,
|
||||
backgroundColor: colors.macOSTitleBarBackgroundBlur,
|
||||
color: colors.macOSTitleBarIcon,
|
||||
});
|
||||
|
||||
export default (props: Props) => {
|
||||
const {bookmarks, events, onNavigate, onFavorite} = props;
|
||||
const timelineRef = useRef<HTMLDivElement>(null);
|
||||
return events.length === 0 ? (
|
||||
<NoData>No Navigation Events to Show</NoData>
|
||||
) : (
|
||||
<TimelineContainer ref={timelineRef}>
|
||||
<div>
|
||||
<TimelineLine />
|
||||
{events.map((event: NavigationEvent, idx: number) => {
|
||||
return (
|
||||
<NavigationEventContainer>
|
||||
<NavigationInfoBox
|
||||
key={idx}
|
||||
isBookmarked={
|
||||
event.uri != null ? bookmarks.has(event.uri) : false
|
||||
}
|
||||
className={event.className}
|
||||
uri={event.uri}
|
||||
onNavigate={uri => {
|
||||
if (timelineRef.current != null) {
|
||||
timelineRef.current.scrollTo(0, 0);
|
||||
}
|
||||
onNavigate(uri);
|
||||
}}
|
||||
onFavorite={onFavorite}
|
||||
screenshot={event.screenshot}
|
||||
date={event.date}
|
||||
/>
|
||||
</NavigationEventContainer>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</TimelineContainer>
|
||||
);
|
||||
};
|
||||
18
desktop/plugins/navigation/components/index.tsx
Normal file
18
desktop/plugins/navigation/components/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
export {default as AutoCompleteSheet} from './AutoCompleteSheet';
|
||||
export {default as BookmarksSidebar} from './BookmarksSidebar';
|
||||
export {default as FavoriteButton} from './FavoriteButton';
|
||||
export {default as IconButton} from './IconButton';
|
||||
export {default as NavigationInfoBox} from './NavigationInfoBox';
|
||||
export {default as RequiredParametersDialog} from './RequiredParametersDialog';
|
||||
export {default as SaveBookmarkDialog} from './SaveBookmarkDialog';
|
||||
export {default as SearchBar} from './SearchBar';
|
||||
export {default as Timeline} from './Timeline';
|
||||
Reference in New Issue
Block a user