Install plugin from package file
Summary: Adding a way to install plugins directly from package files. This is required for testing after packaging format changes. Stage 2: Added new component for file selection and implemented UI required for plugin installation from package file Reviewed By: priteshrnandgaonkar Differential Revision: D19743998 fbshipit-source-id: 1112d5afca9a649df11e33eb6ac15c0e06747d47
This commit is contained in:
committed by
Facebook Github Bot
parent
15ed856388
commit
984cdbfb67
127
src/ui/components/FileSelector.tsx
Normal file
127
src/ui/components/FileSelector.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 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, {useState} from 'react';
|
||||
import FlexRow from './FlexRow';
|
||||
import FlexColumn from './FlexColumn';
|
||||
import Glyph from './Glyph';
|
||||
import Input from './Input';
|
||||
import electron from 'electron';
|
||||
import styled from '@emotion/styled';
|
||||
import {colors} from './colors';
|
||||
import Electron from 'electron';
|
||||
import fs from 'fs';
|
||||
|
||||
const CenteredGlyph = styled(Glyph)({
|
||||
flexGrow: 0,
|
||||
margin: 'auto',
|
||||
marginLeft: 10,
|
||||
});
|
||||
|
||||
const Container = styled(FlexRow)({
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
const FileInputBox = styled(Input)<{isValid: boolean}>(({isValid}) => ({
|
||||
flexGrow: 1,
|
||||
color: isValid ? undefined : colors.red,
|
||||
'&::-webkit-input-placeholder': {
|
||||
color: colors.placeholder,
|
||||
fontWeight: 300,
|
||||
},
|
||||
}));
|
||||
|
||||
function strToArr<T extends string>(item: T): T[] {
|
||||
return [item];
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
onPathChanged: (evtArgs: {path: string; isValid: boolean}) => void;
|
||||
placeholderText: string;
|
||||
defaultPath: string;
|
||||
showHiddenFiles: boolean;
|
||||
}
|
||||
|
||||
const defaultProps: Props = {
|
||||
onPathChanged: () => {},
|
||||
placeholderText: '',
|
||||
defaultPath: '/',
|
||||
showHiddenFiles: false,
|
||||
};
|
||||
|
||||
export default function FileSelector({
|
||||
onPathChanged,
|
||||
placeholderText,
|
||||
|
||||
defaultPath,
|
||||
showHiddenFiles,
|
||||
}: Props) {
|
||||
const [value, setValue] = useState('');
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
const options: Electron.OpenDialogOptions = {
|
||||
properties: [
|
||||
'openFile',
|
||||
...(showHiddenFiles ? strToArr('showHiddenFiles') : []),
|
||||
],
|
||||
defaultPath,
|
||||
};
|
||||
const onChange = (path: string) => {
|
||||
setValue(path);
|
||||
let isNewPathValid = false;
|
||||
try {
|
||||
isNewPathValid = fs.statSync(path).isFile();
|
||||
} catch {
|
||||
isNewPathValid = false;
|
||||
}
|
||||
setIsValid(isNewPathValid);
|
||||
onPathChanged({path, isValid: isNewPathValid});
|
||||
};
|
||||
return (
|
||||
<Container>
|
||||
<FileInputBox
|
||||
placeholder={placeholderText}
|
||||
value={value}
|
||||
isValid={true}
|
||||
onDrop={e => {
|
||||
if (e.dataTransfer.files.length) {
|
||||
onChange(e.dataTransfer.files[0].path);
|
||||
}
|
||||
}}
|
||||
onChange={e => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<FlexColumn
|
||||
onClick={() =>
|
||||
electron.remote.dialog
|
||||
.showOpenDialog(options)
|
||||
.then((result: electron.OpenDialogReturnValue) => {
|
||||
if (result && !result.canceled && result.filePaths.length) {
|
||||
onChange(result.filePaths[0]);
|
||||
}
|
||||
})
|
||||
}>
|
||||
<CenteredGlyph
|
||||
name="dots-3-circle"
|
||||
variant="outline"
|
||||
title="Open file selection dialog"
|
||||
/>
|
||||
</FlexColumn>
|
||||
{isValid ? null : (
|
||||
<CenteredGlyph
|
||||
name="caution-triangle"
|
||||
color={colors.yellow}
|
||||
title="Path is invalid"
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
FileSelector.defaultProps = defaultProps;
|
||||
@@ -48,12 +48,20 @@ function ColoredIcon(
|
||||
className?: string;
|
||||
color?: string;
|
||||
style?: React.CSSProperties;
|
||||
title?: string;
|
||||
},
|
||||
context: {
|
||||
glyphColor?: string;
|
||||
},
|
||||
) {
|
||||
const {color = context.glyphColor, name, size = 16, src, style} = props;
|
||||
const {
|
||||
color = context.glyphColor,
|
||||
name,
|
||||
size = 16,
|
||||
src,
|
||||
style,
|
||||
title,
|
||||
} = props;
|
||||
|
||||
const isBlack =
|
||||
color == null ||
|
||||
@@ -69,6 +77,7 @@ function ColoredIcon(
|
||||
size={size}
|
||||
className={props.className}
|
||||
style={style}
|
||||
title={title}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
@@ -79,6 +88,7 @@ function ColoredIcon(
|
||||
src={src}
|
||||
className={props.className}
|
||||
style={style}
|
||||
title={title}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -98,7 +108,15 @@ export default class Glyph extends React.PureComponent<{
|
||||
title?: string;
|
||||
}> {
|
||||
render() {
|
||||
const {name, size = 16, variant, color, className, style} = this.props;
|
||||
const {
|
||||
name,
|
||||
size = 16,
|
||||
variant,
|
||||
color,
|
||||
className,
|
||||
style,
|
||||
title,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ColoredIcon
|
||||
@@ -106,6 +124,7 @@ export default class Glyph extends React.PureComponent<{
|
||||
className={className}
|
||||
color={color}
|
||||
size={size}
|
||||
title={title}
|
||||
src={getIconURL(
|
||||
variant === 'outline' ? `${name}-outline` : name,
|
||||
size,
|
||||
|
||||
Reference in New Issue
Block a user