Merge branch 'release' of https://github.com/appsmithorg/appsmith into release

This commit is contained in:
Automated Github Action 2020-09-26 13:06:06 +00:00
commit 902fd2dcc7
17 changed files with 405 additions and 71 deletions

View File

@ -0,0 +1,3 @@
<svg width="17" height="14" viewBox="0 0 17 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.8682 7.35302L6.52617 9.1177V5.23541L10.8682 7.35302ZM14.4211 2.41192H2.57929L2.57891 11.5883H14.4211V2.41192ZM2.97404 1.00016C1.78945 0.987885 0.999229 1.68148 1 2.75257V11.2353C0.999615 12.2941 1.78945 13 2.97363 13H14.0264C15.2105 13 16 12.2579 16 11.2353L15.9996 2.76485C15.9996 1.70604 15.2102 1.00016 14.026 1.00016H2.97404Z" fill="#C4C4C4"/>
</svg>

After

Width:  |  Height:  |  Size: 504 B

View File

@ -0,0 +1,64 @@
import React from "react";
import {
Popover,
PopoverInteractionKind,
PopoverPosition,
} from "@blueprintjs/core";
import { Colors } from "constants/Colors";
import VideoComponent, { VideoComponentProps } from "./VideoComponent";
import styled, { AnyStyledComponent } from "styled-components";
import { ControlIcons } from "icons/ControlIcons";
const PlayIcon = styled(ControlIcons.PLAY_VIDEO as AnyStyledComponent)`
position: relative;
top: 10px;
cursor: pointer;
&:hover {
svg {
path {
fill: ${Colors.POMEGRANATE};
}
}
}
`;
const PlayerWrapper = styled.div` import React, { Ref } from "react";
width: 600px;
height: 400px;
`;
const PopoverVideo = (props: VideoComponentProps) => {
return (
<div onClick={e => e.stopPropagation()}>
<Popover
position={PopoverPosition.AUTO}
interactionKind={PopoverInteractionKind.CLICK}
minimal
usePortal
enforceFocus={false}
lazy={true}
modifiers={{
flip: {
behavior: ["right", "left", "bottom", "top"],
},
keepTogether: {
enabled: false,
},
arrow: {
enabled: false,
},
preventOverflow: {
enabled: true,
boundariesElement: "viewport",
},
}}
>
<PlayIcon></PlayIcon>
<PlayerWrapper>
<VideoComponent url={props.url} />
</PlayerWrapper>
</Popover>
</div>
);
};
export default PopoverVideo;

View File

@ -15,7 +15,7 @@ import {
Condition, Condition,
} from "widgets/TableWidget"; } from "widgets/TableWidget";
import { isString } from "lodash"; import { isString } from "lodash";
import VideoComponent from "components/designSystems/appsmith/VideoComponent"; import PopoverVideo from "components/designSystems/appsmith/PopoverVideo";
import Button from "components/editorComponents/Button"; import Button from "components/editorComponents/Button";
import AutoToolTipComponent from "components/designSystems/appsmith/AutoToolTipComponent"; import AutoToolTipComponent from "components/designSystems/appsmith/AutoToolTipComponent";
import TableColumnMenuPopup from "./TableColumnMenu"; import TableColumnMenuPopup from "./TableColumnMenu";
@ -505,7 +505,7 @@ export const renderCell = (
} else if (isString(value) && youtubeRegex.test(value)) { } else if (isString(value) && youtubeRegex.test(value)) {
return ( return (
<CellWrapper isHidden={isHidden} className="video-cell"> <CellWrapper isHidden={isHidden} className="video-cell">
<VideoComponent url={value} /> <PopoverVideo url={value} />
</CellWrapper> </CellWrapper>
); );
} else { } else {

View File

@ -1,73 +1,72 @@
import React from "react";
import ReactPlayer from "react-player"; import ReactPlayer from "react-player";
import { import React, { Ref } from "react";
Popover, import styled from "styled-components";
PopoverInteractionKind, import { ENTER_VIDEO_URL } from "constants/messages";
PopoverPosition, export interface VideoComponentProps {
} from "@blueprintjs/core"; url?: string;
import { ControlIcons } from "icons/ControlIcons"; autoplay?: boolean;
import styled, { AnyStyledComponent } from "styled-components"; controls?: boolean;
import { Colors } from "constants/Colors"; onStart?: () => void;
onPlay?: () => void;
const PlayerWrapper = styled.div` onPause?: () => void;
width: 600px; onEnded?: () => void;
height: 400px; onReady?: () => void;
`; onProgress?: () => void;
onSeek?: () => void;
const PlayIcon = styled(ControlIcons.PLAY_VIDEO as AnyStyledComponent)` onError?: () => void;
position: relative; player?: Ref<ReactPlayer>;
top: 10px;
&:hover {
svg {
path {
fill: ${Colors.POMEGRANATE};
}
}
}
`;
interface VideoComponentProps {
url: string;
} }
const VideoComponent = (props: VideoComponentProps) => { const ErrorContainer = styled.div`
return ( display: flex;
<div onClick={e => e.stopPropagation()}> align-items: center;
<Popover justify-content: center;
position={PopoverPosition.AUTO} width: 100%;
interactionKind={PopoverInteractionKind.CLICK} height: 100%;
minimal `;
usePortal
enforceFocus={false}
lazy={true}
modifiers={{
flip: {
behavior: ["right", "left", "bottom", "top"],
},
keepTogether: {
enabled: false,
},
arrow: {
enabled: false,
},
preventOverflow: {
enabled: true,
boundariesElement: "viewport",
},
}}
>
<PlayIcon width="80" height="52" color="black" />
<PlayerWrapper>
<ReactPlayer
playing={true}
url={props.url}
width="100%"
height="100%"
/>
</PlayerWrapper>
</Popover>
</div>
);
};
export default VideoComponent; const Error = styled.span``;
export default function VideoComponent(props: VideoComponentProps) {
const {
url,
autoplay,
controls,
onStart,
onPlay,
onPause,
onEnded,
onReady,
onProgress,
onSeek,
onError,
player,
} = props;
return (
<>
{url ? (
<ReactPlayer
url={url}
ref={player}
playing={autoplay}
controls={controls || true}
onStart={onStart}
onPlay={onPlay}
onPause={onPause}
onEnded={onEnded}
onReady={onReady}
onProgress={onProgress}
onSeek={onSeek}
onError={onError}
width="100%"
height="100%"
pip={false}
/>
) : (
<ErrorContainer>
<Error>{ENTER_VIDEO_URL}</Error>
</ErrorContainer>
)}
</>
);
}

View File

@ -37,6 +37,10 @@ export enum EventType {
ON_MARKER_CLICK = "ON_MARKER_CLICK", ON_MARKER_CLICK = "ON_MARKER_CLICK",
ON_CREATE_MARKER = "ON_CREATE_MARKER", ON_CREATE_MARKER = "ON_CREATE_MARKER",
ON_TAB_CHANGE = "ON_TAB_CHANGE", ON_TAB_CHANGE = "ON_TAB_CHANGE",
ON_VIDEO_START = "ON_VIDEO_START",
ON_VIDEO_END = "ON_VIDEO_END",
ON_VIDEO_PLAY = "ON_VIDEO_PLAY",
ON_VIDEO_PAUSE = "ON_VIDEO_PAUSE",
} }
export type ActionType = export type ActionType =

View File

@ -33,6 +33,11 @@ const FIELD_VALUES: Record<
// onRowSelected: "Function Call", // onRowSelected: "Function Call",
// onPageChange: "Function Call", // onPageChange: "Function Call",
}, },
VIDEO_WIDGET: {
url: "string",
autoPlay: "boolean",
isVisible: "boolean",
},
IMAGE_WIDGET: { IMAGE_WIDGET: {
image: "string", image: "string",
defaultImage: "string", defaultImage: "string",

View File

@ -31,6 +31,10 @@ export const HelpMap = {
path: "/widget-reference/table", path: "/widget-reference/table",
searchKey: "Table", searchKey: "Table",
}, },
VIDEO_WIDGET: {
path: "/widget-reference/video",
searchKey: "Video",
},
DROP_DOWN_WIDGET: { DROP_DOWN_WIDGET: {
path: "/widget-reference/dropdown", path: "/widget-reference/dropdown",
searchKey: "Dropdown", searchKey: "Dropdown",

View File

@ -19,6 +19,7 @@ export enum WidgetTypes {
CANVAS_WIDGET = "CANVAS_WIDGET", CANVAS_WIDGET = "CANVAS_WIDGET",
ICON_WIDGET = "ICON_WIDGET", ICON_WIDGET = "ICON_WIDGET",
FILE_PICKER_WIDGET = "FILE_PICKER_WIDGET", FILE_PICKER_WIDGET = "FILE_PICKER_WIDGET",
VIDEO_WIDGET = "VIDEO_WIDGET",
} }
export type WidgetType = keyof typeof WidgetTypes; export type WidgetType = keyof typeof WidgetTypes;

View File

@ -13,6 +13,8 @@ export const NAME_SPACE_ERROR = "Name must not have spaces";
export const FORM_VALIDATION_EMPTY_EMAIL = "Please enter an email"; export const FORM_VALIDATION_EMPTY_EMAIL = "Please enter an email";
export const FORM_VALIDATION_INVALID_EMAIL = export const FORM_VALIDATION_INVALID_EMAIL =
"Please provide a valid email address"; "Please provide a valid email address";
export const ENTER_VIDEO_URL = "Please provide a valid url";
export const FORM_VALIDATION_EMPTY_PASSWORD = "Please enter the password"; export const FORM_VALIDATION_EMPTY_PASSWORD = "Please enter the password";
export const FORM_VALIDATION_PASSWORD_RULE = export const FORM_VALIDATION_PASSWORD_RULE =
"Please provide a password with a minimum of 6 characters"; "Please provide a password with a minimum of 6 characters";

View File

@ -6,6 +6,7 @@ import { ReactComponent as CollapseIcon } from "assets/icons/widget/collapse.svg
import { ReactComponent as ContainerIcon } from "assets/icons/widget/container.svg"; import { ReactComponent as ContainerIcon } from "assets/icons/widget/container.svg";
import { ReactComponent as DatePickerIcon } from "assets/icons/widget/datepicker.svg"; import { ReactComponent as DatePickerIcon } from "assets/icons/widget/datepicker.svg";
import { ReactComponent as TableIcon } from "assets/icons/widget/table.svg"; import { ReactComponent as TableIcon } from "assets/icons/widget/table.svg";
import { ReactComponent as VideoIcon } from "assets/icons/widget/video.svg";
import { ReactComponent as DropDownIcon } from "assets/icons/widget/dropdown.svg"; import { ReactComponent as DropDownIcon } from "assets/icons/widget/dropdown.svg";
import { ReactComponent as CheckboxIcon } from "assets/icons/widget/checkbox.svg"; import { ReactComponent as CheckboxIcon } from "assets/icons/widget/checkbox.svg";
import { ReactComponent as RadioGroupIcon } from "assets/icons/widget/radio.svg"; import { ReactComponent as RadioGroupIcon } from "assets/icons/widget/radio.svg";
@ -60,6 +61,11 @@ export const WidgetIcons: {
<TableIcon /> <TableIcon />
</IconWrapper> </IconWrapper>
), ),
VIDEO_WIDGET: (props: IconProps) => (
<IconWrapper {...props}>
<VideoIcon />
</IconWrapper>
),
DROP_DOWN_WIDGET: (props: IconProps) => ( DROP_DOWN_WIDGET: (props: IconProps) => (
<IconWrapper {...props}> <IconWrapper {...props}>
<DropDownIcon /> <DropDownIcon />

View File

@ -119,6 +119,76 @@ const PropertyPaneConfigResponse: PropertyPaneConfigsResponse["data"] = {
], ],
}, },
], ],
VIDEO_WIDGET: [
{
id: "17.1",
sectionName: "General",
children: [
{
id: "17.1.1",
propertyName: "url",
label: "Url",
controlType: "INPUT_TEXT",
placeholderText: "Enter url",
inputType: "TEXT",
},
{
id: "17.1.1",
propertyName: "autoPlay",
label: "autoPlay",
helpText: "Video will be automatically played",
controlType: "SWITCH",
isJSConvertible: true,
},
{
id: "17.1.2",
helpText: "Controls the visibility of the widget",
propertyName: "isVisible",
label: "Visible",
controlType: "SWITCH",
isJSConvertible: true,
},
],
},
{
id: "17.2",
sectionName: "Actions",
children: [
{
id: "17.2.1",
helpText: "Triggers an action when the video starts playing",
propertyName: "onStart",
label: "onStart",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
},
{
id: "17.2.2",
helpText: "Triggers an action when the video ends",
propertyName: "onEnd",
label: "onEnd",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
},
{
id: "17.2.3",
helpText: "Triggers an action when the video is played",
propertyName: "onPlay",
label: "onPlay",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
},
{
id: "17.2.4",
helpText: "Triggers an action when the video is paused",
propertyName: "onPause",
label: "onPause",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
},
],
},
],
TABLE_WIDGET: [ TABLE_WIDGET: [
{ {
id: "7.1", id: "7.1",

View File

@ -92,6 +92,14 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
widgetName: "DatePicker", widgetName: "DatePicker",
defaultDate: moment().format("DD/MM/YYYY HH:mm"), defaultDate: moment().format("DD/MM/YYYY HH:mm"),
}, },
VIDEO_WIDGET: {
rows: 7,
columns: 7,
widgetName: "Video",
url: "https://www.youtube.com/watch?v=mzqK0QIZRLs",
autoPlay: false,
},
TABLE_WIDGET: { TABLE_WIDGET: {
rows: 7, rows: 7,
columns: 8, columns: 8,

View File

@ -74,6 +74,11 @@ const WidgetSidebarResponse: {
widgetCardName: "Table", widgetCardName: "Table",
key: generateReactKey(), key: generateReactKey(),
}, },
{
type: "VIDEO_WIDGET",
widgetCardName: "Video",
key: generateReactKey(),
},
{ {
type: "MAP_WIDGET", type: "MAP_WIDGET",
widgetCardName: "Map", widgetCardName: "Map",

View File

@ -25,6 +25,7 @@ import { FormButtonWidgetProps } from "widgets/FormButtonWidget";
import { MapWidgetProps } from "widgets/MapWidget"; import { MapWidgetProps } from "widgets/MapWidget";
import { ModalWidgetProps } from "widgets/ModalWidget"; import { ModalWidgetProps } from "widgets/ModalWidget";
import { IconWidgetProps } from "widgets/IconWidget"; import { IconWidgetProps } from "widgets/IconWidget";
import { VideoWidgetProps } from "widgets/VideoWidget";
const initialState: WidgetConfigReducerState = WidgetConfigResponse; const initialState: WidgetConfigReducerState = WidgetConfigResponse;
@ -57,6 +58,7 @@ export interface WidgetConfigReducerState {
WidgetConfigProps; WidgetConfigProps;
DATE_PICKER_WIDGET: Partial<DatePickerWidgetProps> & WidgetConfigProps; DATE_PICKER_WIDGET: Partial<DatePickerWidgetProps> & WidgetConfigProps;
TABLE_WIDGET: Partial<TableWidgetProps> & WidgetConfigProps; TABLE_WIDGET: Partial<TableWidgetProps> & WidgetConfigProps;
VIDEO_WIDGET: Partial<VideoWidgetProps> & WidgetConfigProps;
DROP_DOWN_WIDGET: Partial<DropdownWidgetProps> & WidgetConfigProps; DROP_DOWN_WIDGET: Partial<DropdownWidgetProps> & WidgetConfigProps;
CHECKBOX_WIDGET: Partial<CheckboxWidgetProps> & WidgetConfigProps; CHECKBOX_WIDGET: Partial<CheckboxWidgetProps> & WidgetConfigProps;
RADIO_GROUP_WIDGET: Partial<RadioGroupWidgetProps> & WidgetConfigProps; RADIO_GROUP_WIDGET: Partial<RadioGroupWidgetProps> & WidgetConfigProps;

View File

@ -38,6 +38,10 @@ import TableWidget, {
TableWidgetProps, TableWidgetProps,
ProfiledTableWidget, ProfiledTableWidget,
} from "widgets/TableWidget"; } from "widgets/TableWidget";
import VideoWidget, {
VideoWidgetProps,
ProfiledVideoWidget,
} from "widgets/VideoWidget";
import TabsWidget, { import TabsWidget, {
TabsWidgetProps, TabsWidgetProps,
TabContainerWidgetProps, TabContainerWidgetProps,
@ -204,6 +208,21 @@ export default class WidgetBuilderRegistry {
TableWidget.getDefaultPropertiesMap(), TableWidget.getDefaultPropertiesMap(),
TableWidget.getMetaPropertiesMap(), TableWidget.getMetaPropertiesMap(),
); );
WidgetFactory.registerWidgetBuilder(
"VIDEO_WIDGET",
{
buildWidget(widgetData: VideoWidgetProps): JSX.Element {
return <ProfiledVideoWidget {...widgetData} />;
},
},
VideoWidget.getPropertyValidationMap(),
VideoWidget.getDerivedPropertiesMap(),
VideoWidget.getTriggerPropertyMap(),
VideoWidget.getDefaultPropertiesMap(),
VideoWidget.getMetaPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder( WidgetFactory.registerWidgetBuilder(
"FILE_PICKER_WIDGET", "FILE_PICKER_WIDGET",
{ {

View File

@ -67,6 +67,13 @@ export const entityDefinitions = {
isVisible: isVisible, isVisible: isVisible,
searchText: "string", searchText: "string",
}), }),
VIDEO_WIDGET: (widget: any) => ({
"!doc":
"Video widget can be used for playing a variety of URLs, including file paths, YouTube, Facebook, Twitch, SoundCloud, Streamable, Vimeo, Wistia, Mixcloud, and DailyMotion.",
"!url": "https://docs.appsmith.com/widget-reference/video",
playState: "number",
autoPlay: "bool",
}),
DROP_DOWN_WIDGET: { DROP_DOWN_WIDGET: {
"!doc": "!doc":
"Dropdown is used to capture user input/s from a specified list of permitted inputs. A Dropdown can capture a single choice as well as multiple choices", "Dropdown is used to capture user input/s from a specified list of permitted inputs. A Dropdown can capture a single choice as well as multiple choices",

View File

@ -0,0 +1,135 @@
import React, { Suspense, lazy } from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/ActionConstants";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
import Skeleton from "components/utils/Skeleton";
import * as Sentry from "@sentry/react";
import { retryPromise } from "utils/AppsmithUtils";
import ReactPlayer from "react-player";
const VideoComponent = lazy(() =>
retryPromise(() =>
import("components/designSystems/appsmith/VideoComponent"),
),
);
export enum PlayState {
NOT_STARTED = "NOT_STARTED",
PAUSED = "PAUSED",
ENDED = "ENDED",
PLAYING = "PLAYING",
}
class VideoWidget extends BaseWidget<VideoWidgetProps, WidgetState> {
private _player = React.createRef<ReactPlayer>();
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
url: VALIDATION_TYPES.TEXT,
};
}
static getMetaPropertiesMap(): Record<string, any> {
return {
playState: PlayState.NOT_STARTED,
};
}
static getDefaultPropertiesMap(): Record<string, string> {
return {};
}
static getTriggerPropertyMap(): TriggerPropertiesMap {
return {
onStart: true,
onEnd: true,
onPlay: true,
onPause: true,
};
}
shouldComponentUpdate(nextProps: VideoWidgetProps) {
return nextProps.url !== this.props.url;
}
getPageView() {
const { url, autoPlay, onStart, onEnd, onPause, onPlay } = this.props;
return (
<Suspense fallback={<Skeleton />}>
<VideoComponent
player={this._player}
url={url}
autoplay={autoPlay}
controls={true}
onStart={() => {
this.updateWidgetMetaProperty("playState", PlayState.PLAYING);
if (onStart) {
super.executeAction({
dynamicString: onStart,
event: {
type: EventType.ON_VIDEO_START,
},
});
}
}}
onPlay={() => {
this.updateWidgetMetaProperty("playState", PlayState.PLAYING);
if (onPlay) {
super.executeAction({
dynamicString: onPlay,
event: {
type: EventType.ON_VIDEO_PLAY,
},
});
}
}}
onPause={() => {
//TODO: We do not want the pause event for onSeek or onEnd.
this.updateWidgetMetaProperty("playState", PlayState.PAUSED);
if (onPause) {
super.executeAction({
dynamicString: onPause,
event: {
type: EventType.ON_VIDEO_PAUSE,
},
});
}
}}
onEnded={() => {
this.updateWidgetMetaProperty("playState", PlayState.ENDED);
if (onEnd) {
super.executeAction({
dynamicString: onEnd,
event: {
type: EventType.ON_VIDEO_END,
},
});
}
}}
/>
</Suspense>
);
}
getWidgetType(): WidgetType {
return "VIDEO_WIDGET";
}
}
export interface VideoWidgetProps extends WidgetProps {
url: string;
autoPlay: boolean;
onStart?: string;
onPause?: string;
onPlay?: string;
onEnd?: string;
}
export default VideoWidget;
export const ProfiledVideoWidget = Sentry.withProfiler(VideoWidget);