lightning menu moved to dynamic auto complete

This commit is contained in:
vicky_primathon.in 2020-05-20 21:14:52 +05:30
parent acd18bafd5
commit 69e2de1d38
8 changed files with 190 additions and 167 deletions

View File

@ -22,6 +22,7 @@ const buttonStyles = css<{
filled?: string; filled?: string;
fluid?: boolean; fluid?: boolean;
themeType?: string; themeType?: string;
iconAlignment?: Direction;
}>` }>`
${BlueprintButtonIntentsCSS} ${BlueprintButtonIntentsCSS}
&&&& { &&&& {
@ -34,18 +35,24 @@ const buttonStyles = css<{
props.filled || props.outline ? "inherit" : "transparent"}; props.filled || props.outline ? "inherit" : "transparent"};
width: ${props => (props.fluid ? "100%" : "auto")}; width: ${props => (props.fluid ? "100%" : "auto")};
color: ${props =>
props.themeType === "dark"
? props.theme.colors.textOnDarkBG
: props.theme.colors.textDefault};
} }
&&&&&& { &&&&&& {
&.bp3-button span { &.bp3-button span {
font-weight: ${props => (props.themeType === "dark" ? 400 : 700)}; font-weight: ${props => (props.themeType ? 400 : 700)};
} }
.bp3-icon svg { .bp3-icon svg {
width: ${props => (props.themeType === "dark" ? 14 : 16)}px; width: ${props => (props.themeType ? 14 : 16)}px;
height: ${props => (props.themeType === "dark" ? 14 : 16)}px; height: ${props => (props.themeType ? 14 : 16)}px;
}
&.bp3-button {
${props =>
props.themeType !== undefined
? props.iconAlignment === "right"
? "display: flex; justify-content: space-between;width: 100%;"
: props.iconAlignment === "left"
? "display: flex; justify-content: flex-start;width: 100%;"
: ""
: ""};
} }
} }
${props => (props.outline ? outline : "")} ${props => (props.outline ? outline : "")}
@ -55,6 +62,7 @@ const StyledButton = styled(BlueprintButton)<{
intent?: Intent; intent?: Intent;
filled?: string; filled?: string;
themeType?: string; themeType?: string;
iconAlignment?: Direction;
}>` }>`
${buttonStyles} ${buttonStyles}
`; `;
@ -63,6 +71,7 @@ const StyledAnchorButton = styled(BlueprintAnchorButton)<{
intent?: Intent; intent?: Intent;
filled?: string; filled?: string;
themeType?: string; themeType?: string;
iconAlignment?: Direction;
}>` }>`
${buttonStyles} ${buttonStyles}
`; `;
@ -111,6 +120,7 @@ export const Button = (props: ButtonProps) => {
className: props.className, className: props.className,
fluid: props.fluid ? props.fluid.toString() : undefined, fluid: props.fluid ? props.fluid.toString() : undefined,
themeType: props.themeType ? props.themeType : undefined, themeType: props.themeType ? props.themeType : undefined,
iconAlignment: props.iconAlignment ? props.iconAlignment : undefined,
}; };
if (props.href) { if (props.href) {
return ( return (

View File

@ -1,4 +1,4 @@
import React, { Component } from "react"; import React, { Component, lazy, Suspense } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { AppState } from "reducers"; import { AppState } from "reducers";
import styled, { createGlobalStyle } from "styled-components"; import styled, { createGlobalStyle } from "styled-components";
@ -21,6 +21,9 @@ import { parseDynamicString } from "utils/DynamicBindingUtils";
import { DataTree } from "entities/DataTree/dataTreeFactory"; import { DataTree } from "entities/DataTree/dataTreeFactory";
import { Theme } from "constants/DefaultTheme"; import { Theme } from "constants/DefaultTheme";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
const LightningMenu = lazy(() =>
import("components/editorComponents/LightningMenu"),
);
require("codemirror/mode/javascript/javascript"); require("codemirror/mode/javascript/javascript");
require("codemirror/mode/sql/sql"); require("codemirror/mode/sql/sql");
require("codemirror/addon/hint/sql-hint"); require("codemirror/addon/hint/sql-hint");
@ -233,6 +236,18 @@ const IconContainer = styled.div`
} }
`; `;
const DynamicAutocompleteInputWrapper = styled.div`
width: 100%;
position: relative;
& > span:first-of-type {
position: absolute;
right: 0;
top: 2px;
width: 14px;
z-index: 10;
}
`;
const THEMES = { const THEMES = {
LIGHT: "LIGHT", LIGHT: "LIGHT",
DARK: "DARK", DARK: "DARK",
@ -264,7 +279,7 @@ export type DynamicAutocompleteInputProps = {
link?: string; link?: string;
baseMode?: string | object; baseMode?: string | object;
setMaxHeight?: boolean; setMaxHeight?: boolean;
defaultValue?: string; showLightningMenu?: boolean;
}; };
type Props = ReduxStateProps & type Props = ReduxStateProps &
@ -292,6 +307,7 @@ class DynamicAutocompleteInput extends Component<Props, State> {
componentDidMount(): void { componentDidMount(): void {
if (this.textArea.current) { if (this.textArea.current) {
const options: EditorConfiguration = {}; const options: EditorConfiguration = {};
//use this for lightning menu theme
if (this.props.theme === "DARK") options.theme = "monokai"; if (this.props.theme === "DARK") options.theme = "monokai";
if (!this.props.input.onChange || this.props.disabled) { if (!this.props.input.onChange || this.props.disabled) {
options.readOnly = true; options.readOnly = true;
@ -366,19 +382,6 @@ class DynamicAutocompleteInput extends Component<Props, State> {
} }
} }
} }
if (prevProps.defaultValue !== this.props.defaultValue) {
const cursorPosition = this.props.defaultValue
? this.props.defaultValue.length + 1
: 1;
this.editor.setValue(
this.props.defaultValue ? this.props.defaultValue : "",
);
this.editor.focus();
this.editor.setCursor({
line: 1,
ch: cursorPosition,
});
}
} }
handleEditorFocus = () => { handleEditorFocus = () => {
@ -479,6 +482,15 @@ class DynamicAutocompleteInput extends Component<Props, State> {
} }
}; };
updatePropertyValue = (value: string, cursor: number) => {
this.editor.setValue(value);
this.editor.focus();
this.editor.setCursor({
line: 1,
ch: cursor,
});
};
render() { render() {
const { const {
input, input,
@ -488,6 +500,7 @@ class DynamicAutocompleteInput extends Component<Props, State> {
disabled, disabled,
className, className,
setMaxHeight, setMaxHeight,
showLightningMenu,
} = this.props; } = this.props;
const hasError = !!(meta && meta.error); const hasError = !!(meta && meta.error);
let showError = false; let showError = false;
@ -495,57 +508,65 @@ class DynamicAutocompleteInput extends Component<Props, State> {
showError = showError =
hasError && this.state.isFocused && !this.state.autoCompleteVisible; hasError && this.state.isFocused && !this.state.autoCompleteVisible;
} }
console.log(className); const themeType = this.props.theme === "DARK" ? "dark" : "light";
return ( return (
<ErrorTooltip message={meta ? meta.error : ""} isOpen={showError}> <DynamicAutocompleteInputWrapper>
<Wrapper {(showLightningMenu === undefined || showLightningMenu === true) && (
editorTheme={theme} <LightningMenu
hasError={hasError} themeType={themeType}
singleLine={singleLine} updatePropertyValue={this.updatePropertyValue}
isFocused={this.state.isFocused}
disabled={disabled}
className={className}
setMaxHeight={setMaxHeight}
>
<HintStyles />
<IconContainer>
{this.props.leftIcon && <this.props.leftIcon />}
</IconContainer>
{this.props.leftImage && (
<img
src={this.props.leftImage}
alt="img"
className="leftImageStyles"
/>
)}
<textarea
ref={this.textArea}
{..._.omit(this.props.input, ["onChange", "value"])}
defaultValue={input.value}
placeholder={this.props.placeholder}
/> />
{this.props.link && ( )}
<React.Fragment> <ErrorTooltip message={meta ? meta.error : ""} isOpen={showError}>
<a <Wrapper
href={this.props.link} editorTheme={theme}
target="_blank" hasError={hasError}
className="linkStyles" singleLine={singleLine}
rel="noopener noreferrer" isFocused={this.state.isFocused}
> disabled={disabled}
API documentation className={className}
</a> setMaxHeight={setMaxHeight}
</React.Fragment> >
)} <HintStyles />
{this.props.rightIcon && ( <IconContainer>
<HelperTooltip {this.props.leftIcon && <this.props.leftIcon />}
description={this.props.description} </IconContainer>
rightIcon={this.props.rightIcon}
{this.props.leftImage && (
<img
src={this.props.leftImage}
alt="img"
className="leftImageStyles"
/>
)}
<textarea
ref={this.textArea}
{..._.omit(this.props.input, ["onChange", "value"])}
defaultValue={input.value}
placeholder={this.props.placeholder}
/> />
)} {this.props.link && (
</Wrapper> <React.Fragment>
</ErrorTooltip> <a
href={this.props.link}
target="_blank"
className="linkStyles"
rel="noopener noreferrer"
>
API documentation
</a>
</React.Fragment>
)}
{this.props.rightIcon && (
<HelperTooltip
description={this.props.description}
rightIcon={this.props.rightIcon}
/>
)}
</Wrapper>
</ErrorTooltip>
</DynamicAutocompleteInputWrapper>
); );
} }
} }

View File

@ -10,7 +10,11 @@ import { RestAction } from "api/ActionAPI";
import { WidgetProps } from "widgets/BaseWidget"; import { WidgetProps } from "widgets/BaseWidget";
import { noop } from "lodash"; import { noop } from "lodash";
const getApiOptions = (apis: RestAction[]) => ({ const getApiOptions = (
themeType: string,
apis: RestAction[],
updatePropertyValue: (value: string, cursor: number) => void,
) => ({
sections: [ sections: [
{ {
isSticky: true, isSticky: true,
@ -21,7 +25,7 @@ const getApiOptions = (apis: RestAction[]) => ({
text="Create new API" text="Create new API"
icon="plus" icon="plus"
iconAlignment="left" iconAlignment="left"
themeType="dark" themeType={themeType}
/> />
), ),
}, },
@ -30,7 +34,10 @@ const getApiOptions = (apis: RestAction[]) => ({
{ {
options: apis.map(api => ({ options: apis.map(api => ({
content: api.name, content: api.name,
onSelect: noop, onSelect: () => {
const value = `{{${api.name}.data}}`;
updatePropertyValue(value, value.length + 1);
},
})), })),
}, },
], ],
@ -39,10 +46,14 @@ const getApiOptions = (apis: RestAction[]) => ({
}, },
openDirection: Directions.RIGHT, openDirection: Directions.RIGHT,
openOnHover: false, openOnHover: false,
themeType: "dark", themeType: themeType,
}); });
const getQueryOptions = (queries: RestAction[]) => ({ const getQueryOptions = (
themeType: string,
queries: RestAction[],
updatePropertyValue: (value: string, cursor: number) => void,
) => ({
sections: [ sections: [
{ {
isSticky: true, isSticky: true,
@ -53,7 +64,7 @@ const getQueryOptions = (queries: RestAction[]) => ({
text="Create new Query" text="Create new Query"
icon="plus" icon="plus"
iconAlignment="left" iconAlignment="left"
themeType="dark" themeType={themeType}
/> />
), ),
}, },
@ -62,7 +73,10 @@ const getQueryOptions = (queries: RestAction[]) => ({
{ {
options: queries.map(query => ({ options: queries.map(query => ({
content: query.name, content: query.name,
onSelect: noop, onSelect: () => {
const value = `{{${query.name}.data}}`;
updatePropertyValue(value, value.length + 1);
},
})), })),
}, },
], ],
@ -71,33 +85,21 @@ const getQueryOptions = (queries: RestAction[]) => ({
}, },
openDirection: Directions.RIGHT, openDirection: Directions.RIGHT,
openOnHover: false, openOnHover: false,
themeType: "dark", themeType: themeType,
}); });
const getWidgetOptions = ( const getWidgetOptions = (
themeType: string,
widgets: WidgetProps[], widgets: WidgetProps[],
updatePropertyValue: (value: string) => void, updatePropertyValue: (value: string, cursor: number) => void,
) => ({ ) => ({
sections: [ sections: [
// {
// isSticky: true,
// options: [
// {
// content: (
// <Button
// text="Create new widget"
// icon="plus"
// iconAlignment="left"
// themeType="dark"
// />
// ),
// },
// ],
// },
{ {
options: widgets.map(widget => ({ options: widgets.map(widget => ({
content: ( content: (
<CustomizedDropdown {...getWidgetData(widget, updatePropertyValue)} /> <CustomizedDropdown
{...getWidgetData(themeType, widget, updatePropertyValue)}
/>
), ),
disabled: false, disabled: false,
shouldCloseDropdown: false, shouldCloseDropdown: false,
@ -109,19 +111,21 @@ const getWidgetOptions = (
}, },
openDirection: Directions.RIGHT, openDirection: Directions.RIGHT,
openOnHover: false, openOnHover: false,
themeType: "dark", themeType: themeType,
}); });
const getWidgetData = ( const getWidgetData = (
themeType: string,
widget: WidgetProps, widget: WidgetProps,
updatePropertyValue: (value: string) => void, updatePropertyValue: (value: string, cursor: number) => void,
) => ({ ) => ({
sections: [ sections: [
{ {
options: Object.keys(widget).map(widgetProp => ({ options: Object.keys(widget).map(widgetProp => ({
content: widgetProp, content: widgetProp,
onSelect: () => { onSelect: () => {
updatePropertyValue(`{{${widget.widgetName}.${widgetProp}}}`); const value = `{{${widget.widgetName}.${widgetProp}}}`;
updatePropertyValue(value, value.length + 1);
}, },
})), })),
}, },
@ -131,14 +135,15 @@ const getWidgetData = (
}, },
openDirection: Directions.RIGHT, openDirection: Directions.RIGHT,
openOnHover: false, openOnHover: false,
themeType: "dark", themeType: themeType,
}); });
const lightningMenuOptions = ( const lightningMenuOptions = (
themeType: string,
apis: RestAction[], apis: RestAction[],
queries: RestAction[], queries: RestAction[],
widgets: WidgetProps[], widgets: WidgetProps[],
updatePropertyValue: (value: string) => void, updatePropertyValue: (value: string, cursor: number) => void,
): CustomizedDropdownProps => ({ ): CustomizedDropdownProps => ({
sections: [ sections: [
{ {
@ -148,23 +153,31 @@ const lightningMenuOptions = (
disabled: false, disabled: false,
shouldCloseDropdown: true, shouldCloseDropdown: true,
onSelect: () => { onSelect: () => {
updatePropertyValue("Plain Text"); updatePropertyValue("", 1);
}, },
}, },
{ {
content: <CustomizedDropdown {...getApiOptions(apis)} />, content: (
disabled: false, <CustomizedDropdown
shouldCloseDropdown: false, {...getApiOptions(themeType, apis, updatePropertyValue)}
}, />
{ ),
content: <CustomizedDropdown {...getQueryOptions(queries)} />,
disabled: false, disabled: false,
shouldCloseDropdown: false, shouldCloseDropdown: false,
}, },
{ {
content: ( content: (
<CustomizedDropdown <CustomizedDropdown
{...getWidgetOptions(widgets, updatePropertyValue)} {...getQueryOptions(themeType, queries, updatePropertyValue)}
/>
),
disabled: false,
shouldCloseDropdown: false,
},
{
content: (
<CustomizedDropdown
{...getWidgetOptions(themeType, widgets, updatePropertyValue)}
/> />
), ),
disabled: false, disabled: false,
@ -175,7 +188,7 @@ const lightningMenuOptions = (
disabled: false, disabled: false,
shouldCloseDropdown: true, shouldCloseDropdown: true,
onSelect: () => { onSelect: () => {
updatePropertyValue("{{}}"); updatePropertyValue("{{}}", 3);
}, },
}, },
{ {
@ -183,7 +196,7 @@ const lightningMenuOptions = (
disabled: false, disabled: false,
shouldCloseDropdown: true, shouldCloseDropdown: true,
onSelect: () => { onSelect: () => {
updatePropertyValue("<p></p>"); updatePropertyValue("<p></p>", 4);
}, },
}, },
], ],
@ -194,12 +207,13 @@ const lightningMenuOptions = (
trigger: { trigger: {
text: "", text: "",
}, },
themeType: "dark", themeType: themeType,
}); });
type LightningMenuProps = { type LightningMenuProps = {
onSelect?: (value: string) => void; onSelect?: (value: string) => void;
updatePropertyValue: (value: string) => void; updatePropertyValue: (value: string, cursor: number) => void;
themeType: string;
}; };
export const LightningMenu = (props: LightningMenuProps) => { export const LightningMenu = (props: LightningMenuProps) => {
@ -213,16 +227,14 @@ export const LightningMenu = (props: LightningMenuProps) => {
const canvasWidgets = []; const canvasWidgets = [];
for (const i in state.entities.canvasWidgets) { for (const i in state.entities.canvasWidgets) {
if ( if (
!["CONTAINER_WIDGET", "CANVAS_WIDGET"].includes( !state.entities.canvasWidgets[i].children ||
state.entities.canvasWidgets[i].type, state.entities.canvasWidgets[i].children?.length === 0
)
) { ) {
canvasWidgets.push(state.entities.canvasWidgets[i]); canvasWidgets.push(state.entities.canvasWidgets[i]);
} }
} }
return canvasWidgets; return canvasWidgets;
}); });
console.log("widgets", widgets);
const apis = actions const apis = actions
.filter(action => action.config.pluginType === "API") .filter(action => action.config.pluginType === "API")
.map(action => action.config); .map(action => action.config);
@ -233,6 +245,7 @@ export const LightningMenu = (props: LightningMenuProps) => {
return ( return (
<CustomizedDropdown <CustomizedDropdown
{...lightningMenuOptions( {...lightningMenuOptions(
props.themeType,
apis, apis,
queries, queries,
widgets, widgets,

View File

@ -42,6 +42,7 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
placeholder="Key" placeholder="Key"
singleLine singleLine
setMaxHeight setMaxHeight
showLightningMenu={false}
/> />
{!props.actionConfig && ( {!props.actionConfig && (
<DynamicTextField <DynamicTextField

View File

@ -1,24 +1,8 @@
import React, { lazy, Suspense } from "react"; import React from "react";
import styled from "styled-components";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import { StyledDynamicInput } from "./StyledControls"; import { StyledDynamicInput } from "./StyledControls";
import { InputType } from "widgets/InputWidget"; import { InputType } from "widgets/InputWidget";
import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocompleteInput"; import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocompleteInput";
import CodeMirror from "codemirror";
const LightningMenu = lazy(() =>
import("components/editorComponents/LightningMenu"),
);
const InputControlWrapper = styled.div`
width: 100%;
position: relative;
& > span:first-of-type {
position: absolute;
right: 0;
top: 2px;
width: 14px;
z-index: 10;
}
`;
export function InputText(props: { export function InputText(props: {
label: string; label: string;
@ -27,16 +11,8 @@ export function InputText(props: {
isValid: boolean; isValid: boolean;
validationMessage?: string; validationMessage?: string;
placeholder?: string; placeholder?: string;
defaultValue?: string;
}) { }) {
const { const { validationMessage, value, isValid, onChange, placeholder } = props;
validationMessage,
value,
isValid,
onChange,
placeholder,
defaultValue,
} = props;
return ( return (
<StyledDynamicInput> <StyledDynamicInput>
<DynamicAutocompleteInput <DynamicAutocompleteInput
@ -51,20 +27,12 @@ export function InputText(props: {
theme={"DARK"} theme={"DARK"}
singleLine={false} singleLine={false}
placeholder={placeholder} placeholder={placeholder}
defaultValue={defaultValue}
/> />
</StyledDynamicInput> </StyledDynamicInput>
); );
} }
class InputTextControl extends BaseControl<InputControlProps> { class InputTextControl extends BaseControl<InputControlProps> {
state = {
defaultValue: "",
};
updatePropertyValue = (value: string) => {
this.setState({ defaultValue: value });
};
render() { render() {
const { const {
validationMessage, validationMessage,
@ -74,20 +42,14 @@ class InputTextControl extends BaseControl<InputControlProps> {
placeholderText, placeholderText,
} = this.props; } = this.props;
return ( return (
<InputControlWrapper> <InputText
<Suspense fallback={<div />}> label={label}
<LightningMenu updatePropertyValue={this.updatePropertyValue} /> value={propertyValue}
</Suspense> onChange={this.onTextChange}
<InputText isValid={isValid}
label={label} validationMessage={validationMessage}
value={propertyValue} placeholder={placeholderText}
onChange={this.onTextChange} />
isValid={isValid}
validationMessage={validationMessage}
placeholder={placeholderText}
defaultValue={this.state.defaultValue}
/>
</InputControlWrapper>
); );
} }

View File

@ -80,6 +80,7 @@ export const EditorHeader = (props: EditorHeaderProps) => {
text="Manage Pages" text="Manage Pages"
icon="page-layout" icon="page-layout"
iconAlignment="left" iconAlignment="left"
themeType="light"
/> />
), ),
onSelect: () => onSelect: () =>

View File

@ -1,7 +1,7 @@
import styled, { css } from "styled-components"; import styled, { css } from "styled-components";
import { Intent, IntentColors } from "constants/DefaultTheme"; import { Intent, IntentColors } from "constants/DefaultTheme";
export const DropdownTrigger = styled.div` export const DropdownTrigger = styled.div<{ themeType: string }>`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
@ -17,8 +17,13 @@ export const DropdownTrigger = styled.div`
justify-content: space-between; justify-content: space-between;
outline: 0; outline: 0;
span { span {
color: inherit;
font-weight: 400; font-weight: 400;
color: ${props =>
props.themeType === "dark"
? props.theme.colors.textOnDarkBG
: props.themeType === "light"
? props.theme.colors.defaultText
: "initial"};
} }
&:hover { &:hover {
background: inherit; background: inherit;
@ -96,4 +101,14 @@ export const Option = styled.div<{
${props => (!props.disabled ? highlightOption : ``)}; ${props => (!props.disabled ? highlightOption : ``)};
} }
${props => (props.active && !props.disabled ? highlightOption : ``)}; ${props => (props.active && !props.disabled ? highlightOption : ``)};
&&& button {
span {
color: ${props =>
props.themeType === "dark"
? props.theme.colors.textOnDarkBG
: props.themeType === "light"
? props.theme.colors.defaultText
: "initial"};
}
}
`; `;

View File

@ -145,7 +145,7 @@ export const CustomizedDropdown = (
minimal minimal
enforceFocus={false} enforceFocus={false}
> >
<DropdownTrigger>{trigger}</DropdownTrigger> <DropdownTrigger themeType={themeType}>{trigger}</DropdownTrigger>
<DropdownContent themeType={themeType}>{content}</DropdownContent> <DropdownContent themeType={themeType}>{content}</DropdownContent>
</Popover> </Popover>
); );