Merge branch 'release' of gitlab.com:theappsmith/internal-tools-client into fix/first-page-load-performance

This commit is contained in:
Abhinav Jha 2020-04-15 17:22:43 +05:30
commit ac4911d896
44 changed files with 2145 additions and 266 deletions

View File

@ -73,6 +73,7 @@
"react-dnd-html5-backend": "^9.3.4",
"react-dnd-touch-backend": "^9.4.0",
"react-dom": "^16.7.0",
"react-google-maps": "^9.4.5",
"react-helmet": "^5.2.1",
"react-infinite-scroller": "^1.2.4",
"react-json-view": "^1.19.1",

View File

@ -101,6 +101,7 @@ export type WidgetAddChild = {
parentRowSpace: number;
parentColumnSpace: number;
newWidgetId: string;
tabId: string;
props?: Record<string, any>;
};

View File

@ -0,0 +1,3 @@
<svg width="12" height="2" viewBox="0 0 12 2" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.25 2H0.75C0.551088 2 0.360322 1.89464 0.21967 1.70711C0.0790178 1.51957 0 1.26522 0 1C0 0.734784 0.0790178 0.48043 0.21967 0.292893C0.360322 0.105357 0.551088 0 0.75 0H11.25C11.4489 0 11.6397 0.105357 11.7803 0.292893C11.921 0.48043 12 0.734784 12 1C12 1.26522 11.921 1.51957 11.7803 1.70711C11.6397 1.89464 11.4489 2 11.25 2Z" fill="#768896"/>
</svg>

After

Width:  |  Height:  |  Size: 459 B

View File

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.2611 9H5.73889C5.33081 9 5 9.42533 5 9.95V10.05C5 10.5747 5.33081 11 5.73889 11H18.2611C18.6692 11 19 10.5747 19 10.05V9.95C19 9.42533 18.6692 9 18.2611 9Z" fill="#A3B3BF"/>
<path d="M18.2611 13H5.73889C5.33081 13 5 13.4253 5 13.95V14.05C5 14.5747 5.33081 15 5.73889 15H18.2611C18.6692 15 19 14.5747 19 14.05V13.95C19 13.4253 18.6692 13 18.2611 13Z" fill="#A3B3BF"/>
</svg>

After

Width:  |  Height:  |  Size: 483 B

View File

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.25 5.25H6.75V0.75C6.75 0.551088 6.67098 0.360322 6.53033 0.21967C6.38968 0.0790178 6.19891 0 6 0C5.80109 0 5.61032 0.0790178 5.46967 0.21967C5.32902 0.360322 5.25 0.551088 5.25 0.75V5.25H0.75C0.551088 5.25 0.360322 5.32902 0.21967 5.46967C0.0790178 5.61032 0 5.80109 0 6C0 6.19891 0.0790178 6.38968 0.21967 6.53033C0.360322 6.67098 0.551088 6.75 0.75 6.75H5.25V11.25C5.25 11.4489 5.32902 11.6397 5.46967 11.7803C5.61032 11.921 5.80109 12 6 12C6.19891 12 6.38968 11.921 6.53033 11.7803C6.67098 11.6397 6.75 11.4489 6.75 11.25V6.75H11.25C11.4489 6.75 11.6397 6.67098 11.7803 6.53033C11.921 6.38968 12 6.19891 12 6C12 5.80109 11.921 5.61032 11.7803 5.46967C11.6397 5.32902 11.4489 5.25 11.25 5.25Z" fill="#768896"/>
</svg>

After

Width:  |  Height:  |  Size: 829 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="crosshairs" class="svg-inline--fa fa-crosshairs fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M500 224h-30.364C455.724 130.325 381.675 56.276 288 42.364V12c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v30.364C130.325 56.276 56.276 130.325 42.364 224H12c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h30.364C56.276 381.675 130.325 455.724 224 469.636V500c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12v-30.364C381.675 455.724 455.724 381.675 469.636 288H500c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12zM288 404.634V364c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40.634C165.826 392.232 119.783 346.243 107.366 288H148c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12h-40.634C119.768 165.826 165.757 119.783 224 107.366V148c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12v-40.634C346.174 119.768 392.217 165.757 404.634 224H364c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40.634C392.232 346.174 346.243 392.217 288 404.634zM288 256c0 17.673-14.327 32-32 32s-32-14.327-32-32c0-17.673 14.327-32 32-32s32 14.327 32 32z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="22" height="29" viewBox="0 0 22 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.9997 0.664001C15.4157 0.664001 18.9997 4.216 18.9997 8.59733C18.9997 14.548 10.9997 23.3307 10.9997 23.3307C10.9997 23.3307 2.99967 14.548 2.99967 8.59733C2.99967 4.216 6.58101 0.664001 10.9997 0.664001ZM10.9997 6C10.2924 6 9.61415 6.28095 9.11406 6.78105C8.61396 7.28115 8.33301 7.95942 8.33301 8.66667C8.33301 9.37391 8.61396 10.0522 9.11406 10.5523C9.61415 11.0524 10.2924 11.3333 10.9997 11.3333C11.7069 11.3333 12.3852 11.0524 12.8853 10.5523C13.3854 10.0522 13.6663 9.37391 13.6663 8.66667C13.6663 7.95942 13.3854 7.28115 12.8853 6.78105C12.3852 6.28095 11.7069 6 10.9997 6ZM21.6663 23.3333C21.6663 26.28 16.8903 28.6667 10.9997 28.6667C5.10901 28.6667 0.333008 26.28 0.333008 23.3333C0.333008 21.6147 1.95967 20.0867 4.48367 19.1107L5.32767 20.316C3.88767 20.92 2.99967 21.7493 2.99967 22.6667C2.99967 24.5067 6.58101 26 10.9997 26C15.4183 26 18.9997 24.5067 18.9997 22.6667C18.9997 21.7493 18.1103 20.92 16.6717 20.316L17.5157 19.1107C20.041 20.0867 21.6663 21.6147 21.6663 23.3333Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,5 +1,5 @@
import React from "react";
import { ChartType, ChartData } from "widgets/ChartWidget";
import { ChartType, ChartData, ChartDataPoint } from "widgets/ChartWidget";
import styled from "styled-components";
import { invisible } from "constants/DefaultTheme";
import _ from "lodash";
@ -18,6 +18,7 @@ export interface ChartComponentProps {
chartName: string;
widgetId: string;
isVisible?: boolean;
allowHorizontalScroll: boolean;
}
const CanvasContainer = styled.div<ChartComponentProps>`
@ -29,29 +30,49 @@ const CanvasContainer = styled.div<ChartComponentProps>`
box-shadow: 0 1px 1px 0 rgba(60,75,100,.14),0 2px 1px -1px rgba(60,75,100,.12),0 1px 3px 0 rgba(60,75,100,.2);
position: relative;
${props => (!props.isVisible ? invisible : "")};
padding: 10px 0 0 0;
}`;
class ChartComponent extends React.Component<ChartComponentProps> {
chartInstance = new FusionCharts();
getChartType = (chartType: ChartType) => {
getChartType = () => {
const { chartType, allowHorizontalScroll, chartData } = this.props;
const isMSChart = chartData.length > 1;
switch (chartType) {
case "LINE_CHART":
return "line";
case "BAR_CHART":
return "bar2d";
case "PIE_CHART":
return "pie2d";
case "LINE_CHART":
return allowHorizontalScroll
? "scrollline2d"
: isMSChart
? "msline"
: "line";
case "BAR_CHART":
return allowHorizontalScroll
? "scrollBar2D"
: isMSChart
? "msbar2d"
: "bar2d";
case "COLUMN_CHART":
return "column2d";
return allowHorizontalScroll
? "scrollColumn2D"
: isMSChart
? "mscolumn2d"
: "column2d";
case "AREA_CHART":
return "area2d";
return allowHorizontalScroll
? "scrollarea2d"
: isMSChart
? "msarea"
: "area2d";
default:
return "column2d";
return allowHorizontalScroll ? "scrollColumn2D" : "mscolumn2d";
}
};
getChartData = (chartData: ChartData[]) => {
return chartData.map(item => {
const data: ChartDataPoint[] = chartData[0].data;
return data.map(item => {
return {
label: item.x,
value: item.y,
@ -59,26 +80,123 @@ class ChartComponent extends React.Component<ChartComponentProps> {
});
};
getChartCategoriesMutliSeries = (chartData: ChartData[]) => {
const categories: string[] = [];
for (let index = 0; index < chartData.length; index++) {
const data: ChartDataPoint[] = chartData[index].data;
for (let dataIndex = 0; dataIndex < data.length; dataIndex++) {
const category = data[dataIndex].x;
if (!categories.includes(category)) {
categories.push(category);
}
}
}
return categories;
};
getChartCategories = (chartData: ChartData[]) => {
const categories: string[] = this.getChartCategoriesMutliSeries(chartData);
return categories.map(item => {
return {
label: item,
};
});
};
getSeriesChartData = (data: ChartDataPoint[], categories: string[]) => {
const dataMap: { [key: string]: string } = {};
for (let index = 0; index < data.length; index++) {
const item: ChartDataPoint = data[index];
dataMap[item.x] = item.y;
}
return categories.map((category: string) => {
return {
value: dataMap[category] ? dataMap[category] : null,
};
});
};
getChartDataset = (chartData: ChartData[]) => {
const categories: string[] = this.getChartCategoriesMutliSeries(chartData);
return chartData.map((item: ChartData) => {
const seriesChartData: object[] = this.getSeriesChartData(
item.data,
categories,
);
return {
seriesName: item.seriesName,
data: seriesChartData,
};
});
};
getChartConfig = () => {
return {
caption: this.props.chartName,
xAxisName: this.props.xAxisName,
yAxisName: this.props.yAxisName,
theme: "fusion",
captionAlignment: "left",
captionHorizontalPadding: 10,
alignCaptionWithCanvas: 0,
};
};
getChartDataSource = () => {
if (
this.props.chartData.length === 1 ||
this.props.chartType === "PIE_CHART"
) {
return {
chart: this.getChartConfig(),
data: this.getChartData(this.props.chartData),
};
} else {
return {
chart: this.getChartConfig(),
categories: [
{
category: this.getChartCategories(this.props.chartData),
},
],
dataset: this.getChartDataset(this.props.chartData),
};
}
};
getScrollChartDataSource = () => {
const chartConfig = this.getChartConfig();
return {
chart: {
...chartConfig,
scrollheight: "10",
showvalues: "1",
numVisiblePlot: "5",
flatScrollBars: "1",
},
categories: [
{
category: this.getChartCategories(this.props.chartData),
},
],
dataset: this.getChartDataset(this.props.chartData),
};
};
createGraph = () => {
const dataSource =
this.props.allowHorizontalScroll && this.props.chartType !== "PIE_CHART"
? this.getScrollChartDataSource()
: this.getChartDataSource();
const chartConfig = {
type: this.getChartType(this.props.chartType),
type: this.getChartType(),
renderAt: this.props.widgetId + "chart-container",
width: "100%",
height: "100%",
dataFormat: "json",
dataSource: {
chart: {
caption: this.props.chartName,
xAxisName: this.props.xAxisName,
yAxisName: this.props.yAxisName,
theme: "fusion",
captionAlignment: "left",
captionHorizontalPadding: 10,
alignCaptionWithCanvas: 0,
},
data: this.getChartData(this.props.chartData),
},
dataSource: dataSource,
};
console.log("chartConfig", chartConfig);
this.chartInstance = new FusionCharts(chartConfig);
};
@ -91,19 +209,15 @@ class ChartComponent extends React.Component<ChartComponentProps> {
componentDidUpdate(prevProps: ChartComponentProps) {
if (!_.isEqual(prevProps, this.props)) {
if (prevProps.chartType !== this.props.chartType) {
const chartType = this.getChartType(this.props.chartType);
this.chartInstance.chartType(chartType);
const chartType = this.getChartType();
this.chartInstance.chartType(chartType);
if (
this.props.allowHorizontalScroll &&
this.props.chartType !== "PIE_CHART"
) {
this.chartInstance.setChartData(this.getScrollChartDataSource());
} else {
this.chartInstance.setChartData({
chart: {
caption: this.props.chartName,
xAxisName: this.props.xAxisName,
yAxisName: this.props.yAxisName,
theme: "fusion",
},
data: this.getChartData(this.props.chartData),
});
this.chartInstance.setChartData(this.getChartDataSource());
}
}
}

View File

@ -0,0 +1,91 @@
import React from "react";
import { ControlWrapper } from "../../propertyControls/StyledControls";
import styled from "constants/DefaultTheme";
import { Droppable, Draggable } from "react-beautiful-dnd";
const StyledListWrapper = styled(ControlWrapper)`
display: flex;
justify-content: flex-start;
padding-right: 16px;
margin: 8px 0 0 0;
`;
type RenderComponentProps = {
index: number;
item: {
label: string;
};
deleteOption: (index: number) => void;
updateOption: (index: number, value: string) => void;
};
type DraggableComponentProps = {
index: number;
draggableId: string;
item: {
label: string;
};
deleteOption: (index: number) => void;
updateOption: (index: number, value: string) => void;
renderComponent: (props: RenderComponentProps) => JSX.Element;
};
export const DraggableComponent = (props: DraggableComponentProps) => {
const {
deleteOption,
updateOption,
item,
index,
draggableId,
renderComponent,
} = props;
return (
<Draggable draggableId={draggableId} key={draggableId} index={index}>
{({ innerRef, draggableProps, dragHandleProps }) => (
<StyledListWrapper
orientation={"HORIZONTAL"}
{...draggableProps}
{...dragHandleProps}
ref={innerRef as React.Ref<HTMLDivElement>}
style={{
...draggableProps.style,
userSelect: "none",
position: "static",
}}
>
{renderComponent({ deleteOption, updateOption, item, index })}
</StyledListWrapper>
)}
</Draggable>
);
};
type DroppableComponentProps = {
items: object[];
renderComponent: (props: RenderComponentProps) => JSX.Element;
deleteOption: (index: number) => void;
updateOption: (index: number, value: string) => void;
};
export const DroppableComponent = (props: DroppableComponentProps) => {
const { items } = props;
return (
<Droppable droppableId="0">
{({ innerRef, droppableProps, placeholder }) => (
<div ref={innerRef as React.Ref<HTMLDivElement>} {...droppableProps}>
{items.map((item: { id: string } & any, index: number) => {
return (
<DraggableComponent
key={index}
index={index}
item={item}
draggableId={item.id}
{...props}
/>
);
})}
</div>
)}
</Droppable>
);
};

View File

@ -0,0 +1,159 @@
import React from "react";
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
} from "react-google-maps";
import SearchBox from "react-google-maps/lib/components/places/SearchBox";
import { MarkerProps } from "widgets/MapWidget";
import { ControlIcons } from "icons/ControlIcons";
import styled, { AnyStyledComponent } from "styled-components";
interface MapComponentProps {
widgetId: string;
isDisabled?: boolean;
isVisible?: boolean;
enableSearch: boolean;
zoomLevel: number;
enablePickLocation: boolean;
allowZoom: boolean;
center: {
lat: number;
lng: number;
};
markers?: Array<MarkerProps>;
enableCreateMarker: boolean;
updateCenter: (lat: number, lng: number) => void;
saveMarker: (lat: number, lng: number) => void;
selectMarker: (lat: number, lng: number, title: string) => void;
}
const StyledInput = styled.input`
box-sizing: border-box;
border: 1px solid transparent;
width: 240px;
height: 32px;
margin-top: 27px;
padding: 0 12px;
border-radius: 3px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
font-size: 14px;
outline: none;
text-overflow: ellipses;
`;
type PickMyLocationProps = {
allowZoom: boolean;
};
const PickMyLocationWrapper = styled.div<PickMyLocationProps>`
position: absolute;
bottom: ${props => (props.allowZoom ? 110 : 20)}px;
right: -95px;
width: 140px;
`;
const StyledPickMyLocationIcon = styled(
ControlIcons.PICK_MY_LOCATION_CONTROL as AnyStyledComponent,
)`
position: relative;
cursor: pointer;
`;
const MyMapComponent = withScriptjs(
withGoogleMap((props: any) => (
<GoogleMap
options={{
zoomControl: props.allowZoom,
fullscreenControl: false,
mapTypeControl: false,
scrollwheel: false,
}}
zoom={props.zoom}
center={props.center}
onClick={e => {
if (props.enableCreateMarker) {
props.saveMarker(e.latLng.lat(), e.latLng.lng());
}
}}
>
{props.enableSearch && (
<SearchBox
controlPosition={2}
onPlacesChanged={props.onPlacesChanged}
ref={props.onSearchBoxMounted}
>
<StyledInput type="text" placeholder="Enter location to search" />
</SearchBox>
)}
{props.markers.map((marker: any, index: number) => (
<Marker
key={index}
title={marker.title}
position={{ lat: marker.lat, lng: marker.lng }}
clickable
onClick={e => {
props.selectMarker(marker.lat, marker.lng, marker.title);
}}
/>
))}
{props.enablePickLocation && (
<PickMyLocationWrapper
onClick={props.getUserLocation}
title="Pick My Location"
allowZoom={props.allowZoom}
>
<StyledPickMyLocationIcon />
</PickMyLocationWrapper>
)}
</GoogleMap>
)),
);
class MapComponent extends React.Component<MapComponentProps> {
private searchBox = React.createRef<SearchBox>();
onSearchBoxMounted = (ref: any) => {
this.searchBox = ref;
};
onPlacesChanged = () => {
const node: any = this.searchBox;
if (node) {
const places: any = node.getPlaces();
const location = places[0].geometry.location;
const lat = location.lat();
const lng = location.lng();
this.props.updateCenter(lat, lng);
}
};
getUserLocation = () => {
if ("geolocation" in navigator) {
return navigator.geolocation.getCurrentPosition(data => {
const {
coords: { latitude: lat, longitude: lng },
} = data;
this.props.saveMarker(lat, lng);
});
}
};
render() {
const zoom = Math.floor(this.props.zoomLevel / 5);
return (
<MyMapComponent
googleMapURL="https://maps.googleapis.com/maps/api/js?key=AIzaSyC2H_twoNbEKMm9Q0nYAh7715Dplg2asCI&v=3.exp&libraries=geometry,drawing,places"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%` }} />}
mapElement={<div style={{ height: `100%` }} />}
{...this.props}
zoom={zoom}
onPlacesChanged={this.onPlacesChanged}
onSearchBoxMounted={this.onSearchBoxMounted}
getUserLocation={this.getUserLocation}
/>
);
}
}
export default MapComponent;

View File

@ -41,7 +41,7 @@ export const RichtextEditorComponent = (
"insertdatetime media table paste code help",
],
toolbar:
"undo redo | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help",
"undo redo | formatselect | bold italic backcolor forecolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help",
}}
onEditorChange={(content: any) => props.onValueChange(content)}
/>

View File

@ -0,0 +1,95 @@
import React from "react";
import { ControlIcons } from "icons/ControlIcons";
import { AnyStyledComponent } from "styled-components";
import styled from "constants/DefaultTheme";
const StyledIncreaseIcon = styled(
ControlIcons.INCREASE_CONTROL as AnyStyledComponent,
)`
display: flex;
justify-content: center;
align-items: center;
position: relative;
cursor: pointer;
width: 40px;
height: 32px;
svg {
path {
fill: ${props => props.theme.colors.paneSectionLabel};
}
}
`;
const StyledDecreaseIcon = styled(
ControlIcons.DECREASE_CONTROL as AnyStyledComponent,
)`
display: flex;
justify-content: center;
align-items: center;
position: relative;
cursor: pointer;
width: 40px;
height: 32px;
svg {
path {
fill: ${props => props.theme.colors.paneSectionLabel};
}
}
`;
const StepWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
background: #121518;
border-radius: 4px;
height: 32px;
line-height: 32px;
`;
const InputWrapper = styled.div`
width: calc(100% - 80px);
height: 32px;
line-height: 32px;
background: #23292e;
font-size: 14px;
color: ${props => props.theme.colors.textOnDarkBG};
text-align: center;
letter-spacing: 1.44px;
`;
interface StepComponentProps {
value: number;
min: number;
max: number;
steps: number;
displayFormat: (value: number) => string;
onChange: (value: number) => void;
}
export const StepComponent = (props: StepComponentProps) => {
function decrease() {
if (props.value < props.min) {
return;
}
const value = props.value - props.steps;
props.onChange(value);
}
function increase() {
if (props.value > props.max) {
return;
}
const value = props.value + props.steps;
props.onChange(value);
}
return (
<StepWrapper>
<StyledDecreaseIcon height={2} width={12} onClick={decrease} />
<InputWrapper>{props.displayFormat(props.value)}</InputWrapper>
<StyledIncreaseIcon height={12} width={12} onClick={increase} />
</StepWrapper>
);
};
export default StepComponent;

View File

@ -0,0 +1,80 @@
import React from "react";
import styled from "styled-components";
interface TabsComponentProps {
isVisible?: boolean;
tabs?: Array<{
id: string;
label: string;
}>;
selectedTabId?: string;
onTabChange: (tabId: string) => void;
}
const TabsContainer = styled.div`
&& {
height: 32px;
width: 100%;
display: flex;
justify-content: flex-start;
align-items: flex-start;
}
`;
type TabProps = {
selected?: boolean;
};
const StyledTab = styled.div`
height: 32px;
border-bottom: 1px solid;
border-color: ${props => props.theme.colors.bodyBG};
width: 100%;
`;
const StyledText = styled.div<TabProps>`
white-space: nowrap;
background: ${props => props.theme.colors.builderBodyBG};
color: ${props => props.theme.colors.menuIconColorInactive};
font-size: ${props => props.theme.fontSizes[3]}px;
line-height: 32px;
height: 32px;
padding: 0 16px;
cursor: pointer;
box-shadow: ${props => (props.selected ? props.theme.shadows[2] : "")};
border-bottom: ${props => (props.selected ? "none" : "1px solid")};
border-color: ${props => props.theme.colors.bodyBG};
&:hover {
background: ${props =>
props.selected
? props.theme.colors.textOnDarkBG
: props.theme.colors.hover};
box-shadow: ${props => (props.selected ? "" : props.theme.shadows[3])};
}
`;
class TabsComponent extends React.Component<TabsComponentProps> {
selectTab = (tab: { id: string; label: string }) => {
this.props.onTabChange(tab.id);
};
render() {
return (
<TabsContainer>
{this.props.tabs &&
this.props.tabs.map((tab, index) => (
<StyledText
onClick={this.selectTab.bind(this, tab)}
selected={this.props.selectedTabId === tab.id}
key={index}
>
{tab.label}
</StyledText>
))}
<StyledTab></StyledTab>
</TabsContainer>
);
}
}
export default TabsComponent;

View File

@ -8,6 +8,7 @@ import moment from "moment-timezone";
import "../../../../node_modules/@blueprintjs/datetime/lib/css/blueprint-datetime.css";
import { DatePickerType } from "widgets/DatePickerWidget";
import { WIDGET_PADDING } from "constants/WidgetConstants";
import { TimePrecision } from "@blueprintjs/datetime";
import { Colors } from "constants/Colors";
const StyledControlGroup = styled(ControlGroup)`
@ -45,19 +46,34 @@ const StyledControlGroup = styled(ControlGroup)`
}
&&& {
input {
border: 1px solid #a1acb3;
border-radius: 4px;
border: 1px solid ${Colors.HIT_GRAY};
border-radius: ${props => props.theme.radii[1]}px;
box-shadow: none;
color: #2e3d49;
font-size: 14px;
color: ${Colors.OXFORD_BLUE};
font-size: ${props => props.theme.fontSizes[3]}px;
}
}
`;
class DatePickerComponent extends React.Component<DatePickerComponentProps> {
render() {
const now = moment();
const year = now.get("year");
const month = now.get("month");
const date = now.get("date");
const minDate = now.clone().set({ month, date: date - 1, year: year - 20 });
const maxDate = now.clone().set({ month, date: date + 1, year: year + 20 });
const selectedDate = new Date(
new Date(this.props.selectedDate || "").getTime() +
this.getOffset(new Date(this.props.selectedDate || "")),
);
return (
<StyledControlGroup fill>
<StyledControlGroup
fill
onClick={(e: any) => {
e.stopPropagation();
}}
>
{this.props.label && (
<Label
className={
@ -77,18 +93,14 @@ class DatePickerComponent extends React.Component<DatePickerComponentProps> {
placeholder={this.props.dateFormat}
disabled={this.props.isDisabled}
showActionsBar={true}
timePickerProps={
this.props.enableTimePicker
? {
useAmPm: true,
value: this.props.selectedDate,
showArrowButtons: true,
}
: undefined
timePrecision={
this.props.enableTimePicker ? TimePrecision.MINUTE : undefined
}
closeOnSelection={true}
closeOnSelection
onChange={this.onDateSelected}
value={this.props.selectedDate}
value={selectedDate}
maxDate={maxDate.toDate()}
minDate={minDate.toDate()}
/>
) : (
<DateRangeInput
@ -106,30 +118,28 @@ class DatePickerComponent extends React.Component<DatePickerComponentProps> {
);
}
getOffset = (date: Date) => {
const timezone = this.props.timezone || moment.tz.guess();
return moment.tz.zone(timezone)!.utcOffset(date.getTime()) * 60 * 1000;
};
formatDate = (date: Date): string => {
let dateFormat = "DD/MM/YYYY";
if (this.props.enableTimePicker) {
dateFormat = "DD/MM/YYYY HH:mm";
}
if (this.props.timezone) {
return moment(date)
.tz(this.props.timezone)
.format(dateFormat);
}
return moment(date).format(dateFormat);
};
parseDate = (dateStr: string): Date => {
if (this.props.timezone) {
return moment(dateStr)
.tz(this.props.timezone)
.toDate();
}
return moment(dateStr).toDate();
};
onDateSelected = (selectedDate: Date) => {
this.props.onDateSelected(selectedDate);
const dateValue = new Date(
selectedDate.getTime() - this.getOffset(selectedDate),
).getTime();
this.props.onDateSelected(new Date(dateValue));
};
}

View File

@ -0,0 +1,210 @@
import React from "react";
import BaseControl, { ControlProps } from "./BaseControl";
import {
ControlWrapper,
StyledInputGroup,
StyledPropertyPaneButton,
} from "./StyledControls";
import styled from "constants/DefaultTheme";
import { FormIcons } from "icons/FormIcons";
import { AnyStyledComponent } from "styled-components";
import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocompleteInput";
const StyledOptionControlWrapper = styled(ControlWrapper)`
display: flex;
justify-content: flex-start;
padding: 0;
width: 100%;
`;
const StyledOptionControlInputGroup = styled(StyledInputGroup)`
margin-right: 2px;
width: 100%;
margin-bottom: 0;
&&& {
input {
border: none;
color: ${props => props.theme.colors.textOnDarkBG};
background: ${props => props.theme.colors.paneInputBG};
&:focus {
border: none;
color: ${props => props.theme.colors.textOnDarkBG};
background: ${props => props.theme.colors.paneInputBG};
}
}
}
`;
const StyledDynamicInput = styled.div`
width: 100%;
&&& {
input {
border: none;
color: ${props => props.theme.colors.textOnDarkBG};
background: ${props => props.theme.colors.paneInputBG};
&:focus {
border: none;
color: ${props => props.theme.colors.textOnDarkBG};
background: ${props => props.theme.colors.paneInputBG};
}
}
}
`;
const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)`
padding: 0;
position: relative;
margin-left: 15px;
cursor: pointer;
`;
type RenderComponentProps = {
index: number;
item: {
seriesName: string;
data: Array<{
x: string;
y: string;
}>;
};
length: number;
deleteOption: Function;
updateOption: Function;
};
function DataControlComponent(props: RenderComponentProps) {
const { deleteOption, updateOption, item, index, length } = props;
return (
<StyledOptionControlWrapper orientation={"VERTICAL"}>
{length > 1 && (
<StyledOptionControlWrapper orientation={"HORIZONTAL"}>
<StyledOptionControlInputGroup
type="text"
placeholder="Series Name"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
updateOption(index, "seriesName", event.target.value);
}}
defaultValue={item.seriesName}
/>
<StyledDeleteIcon
height={20}
width={20}
onClick={() => {
deleteOption(index);
}}
/>
</StyledOptionControlWrapper>
)}
<StyledDynamicInput>
<DynamicAutocompleteInput
input={{
value: item.data,
onChange: (
event: React.ChangeEvent<HTMLTextAreaElement> | string,
) => {
let value = event;
if (typeof event !== "string") {
value = event.target.value;
}
updateOption(index, "data", value);
},
}}
meta={{
error: "",
touched: true,
}}
theme={"DARK"}
singleLine={false}
placeholder=""
/>
</StyledDynamicInput>
</StyledOptionControlWrapper>
);
}
class ChartDataControl extends BaseControl<ControlProps> {
render() {
const chartData: Array<{
seriesName: string;
data: Array<{
x: string;
y: string;
}>;
}> = this.props.propertyValue || [];
const dataLength = chartData.length;
return (
<React.Fragment>
{chartData.map((data, index) => {
return (
<DataControlComponent
key={index}
index={index}
item={data}
length={dataLength}
deleteOption={this.deleteOption}
updateOption={this.updateOption}
/>
);
})}
<StyledPropertyPaneButton
text="Add Data"
icon="plus"
color="#FFFFFF"
minimal
onClick={this.addOption}
/>
</React.Fragment>
);
}
deleteOption = (index: number) => {
const chartData: object[] = this.props.propertyValue.slice();
chartData.splice(index, 1);
this.updateProperty(this.props.propertyName, chartData);
};
updateOption = (
index: number,
propertyName: string,
updatedValue: string,
) => {
const chartData: Array<{
seriesName: string;
data: Array<{
x: string;
y: string;
}>;
}> = this.props.propertyValue;
this.updateProperty(
this.props.propertyName,
chartData.map((item, i) => {
if (index === i) {
if (propertyName === "seriesName") {
item.seriesName = updatedValue;
} else {
item.data = JSON.parse(updatedValue);
}
}
return item;
}),
);
};
addOption = () => {
const chartData: Array<{
seriesName: string;
data: Array<{
x: string;
y: string;
}>;
}> = this.props.propertyValue ? this.props.propertyValue.slice() : [];
chartData.push({ seriesName: "", data: [{ x: "", y: "" }] });
this.updateProperty(this.props.propertyName, chartData);
};
static getControlType() {
return "CHART_DATA";
}
}
export default ChartDataControl;

View File

@ -1,37 +1,184 @@
import React from "react";
import BaseControl, { ControlProps } from "./BaseControl";
import { StyledDatePicker } from "./StyledControls";
import {
StyledDatePicker,
StyledTimeZonePicker,
StyledSwitch,
} from "./StyledControls";
import moment from "moment-timezone";
import "../../../node_modules/@blueprintjs/timezone/lib/css/blueprint-timezone.css";
import styled from "styled-components";
import { TIMEZONE, ENABLE_TIME } from "constants/messages";
import { TimePrecision } from "@blueprintjs/datetime";
const DatePickerControlWrapper = styled.div`
display: flex;
flex-direction: column;
&&& {
input {
background: ${props => props.theme.colors.paneTextBG};
color: ${props => props.theme.colors.textOnDarkBG};
font-size: ${props => props.theme.fontSizes[3]}px;
box-shadow: none;
}
}
`;
const LabelWrapper = styled.div`
color: ${props => props.theme.colors.paneText};
margin-bottom: 4px;
font-size: ${props => props.theme.fontSizes[3]}px;
margin-top: 10px;
`;
type DatePickerContorlState = {
date: Date;
dateValue: number;
isTimeEnabled: boolean;
timezone: string;
};
class DatePickerControl extends BaseControl<
DatePickerControlProps,
DatePickerContorlState
> {
constructor(props: DatePickerControlProps) {
super(props);
const timezone =
this.props.propertyValue && this.props.propertyValue.timezone
? this.props.propertyValue.timezone
: moment.tz.guess();
const isTimeEnabled =
this.props.propertyValue && this.props.propertyValue.isTimeEnabled
? this.props.propertyValue.isTimeEnabled
: false;
const dateValue =
this.props.propertyValue && this.props.propertyValue.dateValue
? this.props.propertyValue.dateValue
: new Date().getTime();
const offset = this.getOffset(timezone, dateValue);
const date = new Date(dateValue + offset);
this.state = {
date,
timezone,
dateValue,
isTimeEnabled,
};
}
componentDidMount() {
const timezone =
this.props.propertyValue && this.props.propertyValue.timezone
? this.props.propertyValue.timezone
: moment.tz.guess();
const isTimeEnabled =
this.props.propertyValue && this.props.propertyValue.isTimeEnabled
? this.props.propertyValue.isTimeEnabled
: false;
const dateValue =
this.props.propertyValue && this.props.propertyValue.dateValue
? this.props.propertyValue.dateValue
: new Date().getTime();
const offset = this.getOffset(timezone, dateValue);
const date = new Date(dateValue + offset);
this.setState({
date,
timezone,
dateValue,
isTimeEnabled,
});
}
getOffset = (timezone: string, timeStamp: number) => {
return moment.tz.zone(timezone)!.utcOffset(timeStamp) * 60 * 1000;
};
class DatePickerControl extends BaseControl<DatePickerControlProps> {
render() {
const now = moment();
const year = now.get("year");
const date = now.get("date");
const month = now.get("month");
const minDate = now.clone().set({ month, date: date - 1, year: year - 20 });
const maxDate = now.clone().set({ month, date: date + 1, year: year + 20 });
return (
<StyledDatePicker
formatDate={this.formatDate}
parseDate={this.parseDate}
placeholder={"DD/MM/YYYY"}
showActionsBar={true}
timePickerProps={{
useAmPm: true,
value: new Date(this.props.propertyValue),
showArrowButtons: true,
}}
onChange={this.onDateSelected}
value={new Date(this.props.propertyValue)}
/>
<DatePickerControlWrapper>
<StyledDatePicker
formatDate={this.formatDate}
parseDate={this.parseDate}
placeholder="DD/MM/YYYY"
showActionsBar
timePrecision={
this.state.isTimeEnabled ? TimePrecision.MINUTE : undefined
}
closeOnSelection
onChange={this.onDateSelected}
maxDate={maxDate.toDate()}
minDate={minDate.toDate()}
value={this.state.date}
/>
<LabelWrapper>{TIMEZONE}</LabelWrapper>
<StyledTimeZonePicker
onChange={this.onTimeZoneSelected}
valueDisplayFormat="composite"
showLocalTimezone
value={this.state.timezone || moment.tz.guess()}
/>
<LabelWrapper>{ENABLE_TIME}</LabelWrapper>
<StyledSwitch
onChange={this.onToggle}
checked={this.state.isTimeEnabled}
large
/>
</DatePickerControlWrapper>
);
}
formatDate = (date: Date): string => {
return moment(date).format("DD/MM/YYYY");
onToggle = () => {
const { isTimeEnabled } = this.state;
this.setState({ isTimeEnabled: !isTimeEnabled }, () => {
this.updatePropertyValue();
});
};
parseDate = (dateStr: string): Date => {
return moment(dateStr).toDate();
onTimeZoneSelected = (timezone: string): void => {
const { dateValue } = this.state;
const offset = this.getOffset(timezone, dateValue);
const dateVisible = new Date(dateValue + offset);
this.setState({ timezone: timezone, date: dateVisible }, () => {
this.updatePropertyValue();
});
};
onDateSelected = (date: Date): void => {
this.updateProperty(this.props.propertyName, date);
const { timezone } = this.state;
const offset = this.getOffset(timezone, date.getTime());
const dateValue = new Date(date.getTime() - offset).getTime();
this.setState({ date: date, dateValue: dateValue }, () => {
this.updatePropertyValue();
});
};
updatePropertyValue = () => {
const { dateValue, isTimeEnabled, timezone } = this.state;
this.updateProperty(this.props.propertyName, {
timezone: timezone,
dateValue: dateValue,
isTimeEnabled: isTimeEnabled,
});
};
formatDate = (date: Date): string => {
let dateFormat = "DD/MM/YYYY";
if (this.state.isTimeEnabled) {
dateFormat = "DD/MM/YYYY HH:mm";
}
return moment(date).format(dateFormat);
};
parseDate = (dateStr: string): Date => {
const date = moment(dateStr).toDate();
return date;
};
static getControlType() {
@ -41,8 +188,11 @@ class DatePickerControl extends BaseControl<DatePickerControlProps> {
export interface DatePickerControlProps extends ControlProps {
placeholderText: string;
propertyValue: Date;
enableTimePicker: boolean;
propertyValue: {
timezone: string;
dateValue: number;
isTimeEnabled: boolean;
};
}
export default DatePickerControl;

View File

@ -0,0 +1,95 @@
import React, { MutableRefObject } from "react";
import BaseControl, { ControlProps } from "./BaseControl";
import styled from "styled-components";
import SearchBox from "react-google-maps/lib/components/places/SearchBox";
import StandaloneSearchBox from "react-google-maps/lib/components/places/StandaloneSearchBox";
const { compose, withProps, lifecycle } = require("recompose");
const { withScriptjs } = require("react-google-maps");
const StyledInput = styled.input`
box-sizing: border-box;
border: 1px solid transparent;
width: 100%;
height: 32px;
padding: 0 5px;
border-radius: 3px;
font-size: 14px;
outline: none;
text-overflow: ellipses;
background: #272821;
color: ${props => props.theme.colors.textOnDarkBG};
`;
interface StandaloneSearchBoxProps {
onSearchBoxMounted: (ref: SearchBoxRef) => void;
onPlacesChanged: () => void;
}
type SearchBoxRef = MutableRefObject<StandaloneSearchBox | null>;
const PlacesWithStandaloneSearchBox = compose(
withProps({
googleMapURL:
"https://maps.googleapis.com/maps/api/js?key=AIzaSyC2H_twoNbEKMm9Q0nYAh7715Dplg2asCI&v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
}),
lifecycle({
componentWillMount() {
let searchBox: SearchBoxRef = React.createRef<SearchBox>();
this.setState({
places: [],
onSearchBoxMounted: (ref: SearchBoxRef) => {
searchBox = ref;
},
onPlacesChanged: () => {
if (searchBox === null) return;
if (searchBox.current === null) return;
if (searchBox.current.getPlaces === null) return;
const places = searchBox.current.getPlaces();
this.setState({
places,
});
this.props.onLocationSelection(places);
},
});
},
}),
withScriptjs,
)((props: any) => (
<div data-standalone-searchbox="">
<StandaloneSearchBox
ref={props.onSearchBoxMounted}
onPlacesChanged={props.onPlacesChanged}
>
<StyledInput type="text" placeholder="Enter location" />
</StandaloneSearchBox>
</div>
));
class LocationSearchControl extends BaseControl<ControlProps> {
onLocationSelection = (
places: Array<{
geometry: { location: { lat: () => number; lng: () => number } };
}>,
) => {
const location = places[0].geometry.location;
const lat = location.lat();
const lng = location.lng();
const value = { lat, lng };
this.updateProperty(this.props.propertyName, value);
};
render() {
return (
<PlacesWithStandaloneSearchBox
onLocationSelection={this.onLocationSelection}
/>
);
}
static getControlType() {
return "LOCATION_SEARCH";
}
}
export default LocationSearchControl;

View File

@ -0,0 +1,55 @@
import React from "react";
import BaseControl, { ControlProps } from "./BaseControl";
import StepComponent from "components/designSystems/appsmith/StepComponent";
class StepControl extends BaseControl<StepControlProps> {
getStepTypeControls = () => {
const { stepType } = this.props;
if (stepType === "ZOOM_PERCENTAGE") {
return {
min: 0,
max: 100,
steps: 5,
displayFormat: (value: number): string => {
return `${value}%`;
},
};
}
return {
min: 0,
max: 100,
steps: 1,
displayFormat: (value: number): string => {
return `${value}`;
},
};
};
render() {
const { min, max, steps, displayFormat } = this.getStepTypeControls();
return (
<StepComponent
min={min}
max={max}
steps={steps}
value={this.props.propertyValue}
onChange={(value: number) => {
this.updateProperty(this.props.propertyName, value);
}}
displayFormat={displayFormat}
/>
);
}
static getControlType() {
return "STEP";
}
}
export type StepType = "ZOOM_PERCENTAGE";
export interface StepControlProps extends ControlProps {
stepType: StepType;
}
export default StepControl;

View File

@ -225,6 +225,7 @@ export const StyledSwitch = styled(Switch)`
`;
export const StyledDynamicInput = styled.div`
width: 100%;
&&& {
input {
border: none;
@ -248,14 +249,11 @@ export const StyledInputGroup = styled(InputGroup)`
`;
export const StyledDatePicker = styled(DateInput)`
& {
input {
placeholderText: ${props => props.placeholder};
color: ${props => props.theme.colors.textOnDarkBG};
background: ${props => props.theme.colors.paneInputBG};
}
> input {
color: ${props => props.theme.colors.textOnDarkBG};
background: ${props => props.theme.colors.paneInputBG};
border: 1px solid green;
}
}
`;
export const StyledTimeZonePicker = styled(TimezonePicker)`

View File

@ -0,0 +1,180 @@
import React from "react";
import BaseControl, { ControlProps } from "./BaseControl";
import { StyledInputGroup, StyledPropertyPaneButton } from "./StyledControls";
import { ControlType } from "constants/PropertyControlConstants";
import styled from "constants/DefaultTheme";
import { FormIcons } from "icons/FormIcons";
import { ControlIcons } from "icons/ControlIcons";
import { AnyStyledComponent } from "styled-components";
import { DragDropContext } from "react-beautiful-dnd";
import { generateReactKey } from "utils/generators";
import { DroppableComponent } from "../designSystems/appsmith/DraggableListComponent";
const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)`
padding: 0;
position: relative;
margin-left: 15px;
cursor: pointer;
`;
const StyledDragIcon = styled(
ControlIcons.DRAGGABLE_CONTROL as AnyStyledComponent,
)`
padding: 0;
position: relative;
margin-left: 15px;
cursor: move;
svg {
path {
fill: ${props => props.theme.colors.paneSectionLabel};
}
}
`;
const StyledPropertyPaneButtonWrapper = styled.div`
display: flex;
width: 100%;
justify-content: flex-end;
margin-top: 10px;
`;
const StyledOptionControlInputGroup = styled(StyledInputGroup)`
margin-right: 2px;
&&& {
input {
border: none;
color: ${props => props.theme.colors.textOnDarkBG};
background: ${props => props.theme.colors.paneInputBG};
&:focus {
border: none;
color: ${props => props.theme.colors.textOnDarkBG};
background: ${props => props.theme.colors.paneInputBG};
}
}
}
`;
type RenderComponentProps = {
index: number;
item: {
label: string;
};
deleteOption: (index: number) => void;
updateOption: (index: number, value: string) => void;
};
function TabControlComponent(props: RenderComponentProps) {
const { deleteOption, updateOption, item, index } = props;
return (
<React.Fragment>
<StyledOptionControlInputGroup
type="text"
placeholder="Tab Title"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
updateOption(index, event.target.value);
}}
defaultValue={item.label}
/>
<StyledDeleteIcon
height={20}
width={20}
onClick={() => {
deleteOption(index);
}}
/>
<StyledDragIcon height={20} width={20} />
</React.Fragment>
);
}
class TabControl extends BaseControl<ControlProps> {
onDragEnd = (result: any) => {
const { destination, source } = result;
if (!destination) {
return;
}
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
) {
return;
}
const tabs: string[] = this.props.propertyValue || [""];
const sourceTab = tabs[source.index];
const destinationTab = tabs[destination.index];
this.updateProperty(
this.props.propertyName,
tabs.map((tab, index) => {
if (index === source.index) {
return destinationTab;
} else if (index === destination.index) {
return sourceTab;
}
return tab;
}),
);
};
render() {
const tabs: Array<{
id: string;
label: string;
}> = this.props.propertyValue || [{ id: "" }];
return (
<DragDropContext onDragEnd={this.onDragEnd}>
<DroppableComponent
items={tabs}
renderComponent={TabControlComponent}
deleteOption={this.deleteOption}
updateOption={this.updateOption}
/>
<StyledPropertyPaneButtonWrapper>
<StyledPropertyPaneButton
text="Add a Tab"
color="#FFFFFF"
minimal
onClick={this.addOption}
/>
</StyledPropertyPaneButtonWrapper>
</DragDropContext>
);
}
deleteOption = (index: number) => {
const tabs: object[] = this.props.propertyValue.slice();
tabs.splice(index, 1);
this.updateProperty(this.props.propertyName, tabs);
};
updateOption = (index: number, updatedLabel: string) => {
const tabs: Array<{
id: string;
label: string;
}> = this.props.propertyValue;
this.updateProperty(
this.props.propertyName,
tabs.map((tab, tabIndex) => {
if (index === tabIndex) {
tab.label = updatedLabel;
}
return tab;
}),
);
};
addOption = () => {
const tabs: Array<{
id: string;
label: string;
}> = this.props.propertyValue ? this.props.propertyValue.slice() : [];
const newTabId = generateReactKey({ prefix: "tab" });
tabs.push({ id: newTabId, label: `Tab ${tabs.length + 1}` });
this.updateProperty(this.props.propertyName, tabs);
};
getControlType(): ControlType {
return "TABS_INPUT";
}
}
export default TabControl;

View File

@ -18,9 +18,11 @@ import MultiSelectControl, {
import DatePickerControl, {
DatePickerControlProps,
} from "components/propertyControls/DatePickerControl";
import TimeZoneControl, {
TimeZoneControlProps,
} from "components/propertyControls/TimezoneControl";
import ChartDataControl from "components/propertyControls/ChartDataControl";
import LocationSearchControl from "components/propertyControls/LocationSearchControl";
import StepControl, {
StepControlProps,
} from "components/propertyControls/StepControl";
import ActionSelectorControl from "components/propertyControls/ActionSelectorControl";
import ColumnActionSelectorControl from "components/propertyControls/ColumnActionSelectorControl";
import MultiSwitchControl, {
@ -35,10 +37,12 @@ export const PropertyControls = {
CodeEditorControl,
MultiSelectControl,
DatePickerControl,
TimeZoneControl,
ActionSelectorControl,
ColumnActionSelectorControl,
MultiSwitchControl,
ChartDataControl,
LocationSearchControl,
StepControl,
};
export type PropertyControlPropsType =
@ -48,8 +52,8 @@ export type PropertyControlPropsType =
| SwitchControlProps
| MultiSelectControlProps
| DatePickerControlProps
| TimeZoneControlProps
| MultiSwitchControlProps;
| MultiSwitchControlProps
| StepControlProps;
export const getPropertyControlTypes = (): { [key: string]: string } => {
const _types: { [key: string]: string } = {};

View File

@ -33,6 +33,8 @@ export enum EventType {
ON_DATE_SELECTED = "ON_DATE_SELECTED",
ON_DATE_RANGE_SELECTED = "ON_DATE_RANGE_SELECTED",
ON_OPTION_CHANGE = "ON_OPTION_CHANGE",
ON_MARKER_CLICK = "ON_MARKER_CLICK",
ON_CREATE_MARKER = "ON_CREATE_MARKER",
}
export type ActionType =

View File

@ -333,6 +333,7 @@ export const theme: Theme = {
paneInputBG: Colors.SHARK,
paneBG: Colors.OUTER_SPACE,
paneText: Colors.GRAY_CHATEAU,
paneTextBG: Colors.DEEP_SPACE,
paneTextUnderline: Colors.LIGHT_GREYISH_BLUE,
paneSectionLabel: Colors.CADET_BLUE,
navBG: Colors.SHARK,
@ -414,6 +415,8 @@ export const theme: Theme = {
shadows: [
"0px 2px 4px rgba(67, 70, 74, 0.14)",
`0px 2px 4px ${Colors.MYSTIC}`,
`inset -1px 0px 0px ${Colors.ATHENS_GRAY}, inset 1px 0px 0px ${Colors.ATHENS_GRAY}, inset 0px 4px 0px ${Colors.GREEN}`,
`inset -1px 0px 0px ${Colors.ATHENS_GRAY}, inset 1px 0px 0px ${Colors.ATHENS_GRAY}, inset 0px 1px 0px ${Colors.ATHENS_GRAY}`,
],
widgets: {
tableWidget: {

View File

@ -11,10 +11,12 @@ export type WidgetType =
| "RADIO_GROUP_WIDGET"
| "FILE_PICKER_WIDGET"
| "INPUT_WIDGET"
| "TABS_WIDGET"
| "CHART_WIDGET"
| "MODAL_WIDGET"
| "FORM_WIDGET"
| "FORM_BUTTON_WIDGET"
| "MAP_WIDGET"
| "CANVAS_WIDGET"
| "ICON_WIDGET";
@ -29,11 +31,13 @@ export const WidgetTypes: { [id: string]: WidgetType } = {
DROP_DOWN_WIDGET: "DROP_DOWN_WIDGET",
CHECKBOX_WIDGET: "CHECKBOX_WIDGET",
RADIO_GROUP_WIDGET: "RADIO_GROUP_WIDGET",
TABS_WIDGET: "TABS_WIDGET",
MODAL_WIDGET: "MODAL_WIDGET",
RICH_TEXT_EDITOR_WIDGET: "RICH_TEXT_EDITOR_WIDGET",
CHART_WIDGET: "CHART_WIDGET",
FORM_WIDGET: "FORM_WIDGET",
FORM_BUTTON_WIDGET: "FORM_BUTTON_WIDGET",
MAP_WIDGET: "MAP_WIDGET",
CANVAS_WIDGET: "CANVAS_WIDGET",
ICON_WIDGET: "ICON_WIDGET",
};

View File

@ -12,6 +12,7 @@ export const VALIDATION_TYPES = {
OPTIONS_DATA: "OPTIONS_DATA",
DATE: "DATE",
CHART_DATA: "CHART_DATA",
MARKERS: "MARKERS",
ACTION_SELECTOR: "ACTION_SELECTOR",
ARRAY_ACTION_SELECTOR: "ARRAY_ACTION_SELECTOR",
};

View File

@ -129,3 +129,6 @@ export const INPUT_WIDGET_DEFAULT_VALIDATION_ERROR = "Invalid input";
export const AUTOFIT_ALL_COLUMNS = "Autofit all columns";
export const AUTOFIT_THIS_COLUMN = "Autofit this column";
export const AUTOFIT_COLUMN = "Autofit column";
export const TIMEZONE = "Timezone";
export const ENABLE_TIME = "Enable Time";

View File

@ -7,7 +7,12 @@ import { ReactComponent as ViewIcon } from "assets/icons/control/view.svg";
import { ReactComponent as MoreVerticalIcon } from "assets/icons/control/more-vertical.svg";
import { ReactComponent as OverflowMenuIcon } from "assets/icons/menu/overflow-menu.svg";
import { ReactComponent as JsToggleIcon } from "assets/icons/control/js-toggle.svg";
import { ReactComponent as IncreaseIcon } from "assets/icons/control/increase.svg";
import { ReactComponent as DecreaseIcon } from "assets/icons/control/decrease.svg";
import { ReactComponent as DraggableIcon } from "assets/icons/control/draggable.svg";
import { ReactComponent as CloseIcon } from "assets/icons/control/close.svg";
import { ReactComponent as PickMyLocationIcon } from "assets/icons/control/pick-my-location.svg";
/* eslint-disable react/display-name */
export const ControlIcons: {
@ -48,11 +53,31 @@ export const ControlIcons: {
<JsToggleIcon />
</IconWrapper>
),
INCREASE_CONTROL: (props: IconProps) => (
<IconWrapper {...props}>
<IncreaseIcon />
</IconWrapper>
),
DECREASE_CONTROL: (props: IconProps) => (
<IconWrapper {...props}>
<DecreaseIcon />
</IconWrapper>
),
DRAGGABLE_CONTROL: (props: IconProps) => (
<IconWrapper {...props}>
<DraggableIcon />
</IconWrapper>
),
CLOSE_CONTROL: (props: IconProps) => (
<IconWrapper {...props}>
<CloseIcon />
</IconWrapper>
),
PICK_MY_LOCATION_CONTROL: (props: IconProps) => (
<IconWrapper {...props}>
<PickMyLocationIcon />
</IconWrapper>
),
};
export type ControlIconName = keyof typeof ControlIcons;

View File

@ -14,9 +14,11 @@ import { ReactComponent as SwitchIcon } from "assets/icons/widget/switch.svg";
import { ReactComponent as TextIcon } from "assets/icons/widget/text.svg";
import { ReactComponent as ImageIcon } from "assets/icons/widget/image.svg";
import { ReactComponent as FilePickerIcon } from "assets/icons/widget/filepicker.svg";
import { ReactComponent as TabsIcon } from "assets/icons/widget/tabs.svg";
import { ReactComponent as RichTextEditorIcon } from "assets/icons/widget/rich-text.svg";
import { ReactComponent as ChartIcon } from "assets/icons/widget/chart.svg";
import { ReactComponent as FormIcon } from "assets/icons/widget/form.svg";
import { ReactComponent as MapIcon } from "assets/icons/widget/map.svg";
/* eslint-disable react/display-name */
@ -98,6 +100,11 @@ export const WidgetIcons: {
<FilePickerIcon />
</IconWrapper>
),
TABS_WIDGET: (props: IconProps) => (
<IconWrapper {...props}>
<TabsIcon />
</IconWrapper>
),
CHART_WIDGET: (props: IconProps) => (
<IconWrapper {...props}>
<ChartIcon />
@ -108,6 +115,11 @@ export const WidgetIcons: {
<FormIcon />
</IconWrapper>
),
MAP_WIDGET: (props: IconProps) => (
<IconWrapper {...props}>
<MapIcon />
</IconWrapper>
),
};
export type WidgetIcon = typeof WidgetIcons[keyof typeof WidgetIcons];

View File

@ -318,6 +318,12 @@ const PropertyPaneConfigResponse = {
errorMessage: "Must be a valid string",
controlType: "INPUT_TEXT",
},
{
id: "16.1.3",
propertyName: "shouldScrollContents",
label: "Scroll Contents",
controlType: "SWITCH",
},
],
},
],
@ -404,47 +410,6 @@ const PropertyPaneConfigResponse = {
],
},
],
MODAL_WIDGET: [
{
sectionName: "General",
id: "18.1",
children: [
{
id: "18.1.1",
propertyName: "canOutsideClickClose",
label: "Quick Dismiss",
helpText: "Allows dismissing the modal when user taps outside",
validationType: "BOOLEAN",
errorMessage: "Must be a valid boolean value",
controlType: "SWITCH",
},
{
id: "18.1.2",
propertyName: "size",
label: "Modal Type",
controlType: "DROP_DOWN",
options: [
{
label: "Form Modal",
value: "MODAL_LARGE",
},
{
label: "Alert Modal",
value: "MODAL_SMALL",
},
],
},
{
id: "18.1.3",
propertyName: "shouldScrollContents",
label: "Scroll Contents",
validationType: "BOOLEAN",
errorMessage: "Must be a valid boolean value",
controlType: "SWITCH",
},
],
},
],
INPUT_WIDGET: [
{
id: "4.1",
@ -758,63 +723,6 @@ const PropertyPaneConfigResponse = {
],
},
],
MAP_WIDGET: [
{
sectionName: "General",
id: "19",
children: [
{
id: "19.1",
propertyName: "mapCenter",
label: "Initial location",
controlType: "LOCATION_SEARCH",
},
{
id: "19.2",
propertyName: "enableSearch",
label: "Enable search location",
controlType: "SWITCH",
},
{
id: "19.3",
propertyName: "zoomToLocation",
label: "Zoom to location",
controlType: "SWITCH",
},
{
id: "19.4",
propertyName: "defaultMarkers",
label: "Default markers",
controlType: "INPUT_TEXT",
inputType: "ARRAY",
},
{
id: "19.5",
propertyName: "location",
label: "Creat new marker",
controlType: "SWITCH",
},
],
},
{
sectionName: "ZOOM SETTINGS",
id: "20",
children: [
{
id: "20.1",
propertyName: "zoomLevel",
label: "Zoom Level",
controlType: "PERCENTAGE_LEVEL",
},
{
id: "20.2",
propertyName: "allowZoom",
label: "Zoomable",
controlType: "SWITCH",
},
],
},
],
BUTTON_WIDGET: [
{
id: "1.1",
@ -1232,6 +1140,113 @@ const PropertyPaneConfigResponse = {
],
},
],
MODAL_WIDGET: [
{
sectionName: "General",
id: "24.1",
children: [
{
id: "24.1.1",
propertyName: "canOutsideClickClose",
label: "Close on blur",
controlType: "SWITCH",
},
{
id: "24.1.3",
propertyName: "canEscapeKeyClose",
label: "Close on ESC",
controlType: "SWITCH",
},
{
id: "24.1.4",
propertyName: "size",
label: "Size",
controlType: "DROP_DOWN",
options: [
{
label: "Large",
value: "MODAL_LARGE",
},
{
label: "Small",
value: "MODAL_SMALL",
},
],
},
],
},
],
MAP_WIDGET: [
{
sectionName: "General",
id: "25",
children: [
{
id: "25.1",
propertyName: "mapCenter",
label: "Initial location",
controlType: "LOCATION_SEARCH",
},
{
id: "25.2",
propertyName: "enableSearch",
label: "Enable search location",
controlType: "SWITCH",
},
{
id: "25.3",
propertyName: "enablePickLocation",
label: "Enable pick location",
controlType: "SWITCH",
},
{
id: "25.4",
propertyName: "defaultMarkers",
label: "Default markers",
controlType: "INPUT_TEXT",
inputType: "ARRAY",
},
{
id: "25.5",
propertyName: "enableCreateMarker",
label: "Create new marker",
controlType: "SWITCH",
},
],
},
{
sectionName: "ZOOM SETTINGS",
id: "26",
children: [
{
id: "26.1",
propertyName: "zoomLevel",
label: "Zoom Level",
controlType: "STEP",
stepType: "ZOOM_PERCENTAGE",
},
{
id: "26.2",
propertyName: "allowZoom",
label: "Zoomable",
controlType: "SWITCH",
},
],
},
{
id: "27",
sectionName: "Actions",
children: [
{
id: "27.1",
propertyName: "onMarkerClick",
label: "onMarkerClick",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
},
],
},
],
ICON_WIDGET: [
{
id: "25.1",

View File

@ -21,12 +21,13 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
widgetName: "Text",
},
RICH_TEXT_EDITOR_WIDGET: {
text: "This is the initial <b>content</b> of the editor",
defaultText: "This is the initial <b>content</b> of the editor",
rows: 5,
columns: 8,
isDisabled: false,
isVisible: true,
widgetName: "RichTextEditor",
isDefaultClickDisabled: true,
},
IMAGE_WIDGET: {
defaultImage:
@ -79,7 +80,6 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
},
},
DATE_PICKER_WIDGET: {
enableTimePicker: true,
isDisabled: false,
datePickerType: "DATE_PICKER",
rows: 1,
@ -87,6 +87,11 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
columns: 8,
label: "Date",
widgetName: "DatePicker",
defaultDate: {
dateValue: new Date().getTime(),
timezone: "",
isTimeEnabled: true,
},
},
TABLE_WIDGET: {
rows: 7,
@ -181,6 +186,45 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
widgetName: "FilePicker",
isDefaultClickDisabled: true,
},
TABS_WIDGET: {
rows: 7,
columns: 8,
shouldScrollContents: false,
widgetName: "Tabs",
tabs: [
{ label: "Tab 1", id: "tab1" },
{ label: "Tab 2", id: "tab2" },
],
selectedTab: "Tab 1",
blueprint: {
view: [
{
type: "CANVAS_WIDGET",
position: { top: 0, left: 0 },
size: { rows: 6, cols: 16 },
props: {
containerStyle: "none",
canExtend: false,
detachFromLayout: true,
children: [],
tabId: "tab1",
},
},
{
type: "CANVAS_WIDGET",
position: { top: 0, left: 0 },
size: { rows: 6, cols: 16 },
props: {
containerStyle: "none",
canExtend: false,
detachFromLayout: true,
children: [],
tabId: "tab2",
},
},
],
},
},
MODAL_WIDGET: {
rows: 456,
columns: 456,
@ -286,34 +330,40 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
widgetName: "Chart",
chartType: "LINE_CHART",
chartName: "Sales on working days",
allowHorizontalScroll: false,
chartData: [
{
x: "Mon",
y: 10000,
},
{
x: "Tue",
y: 12000,
},
{
x: "Wed",
y: 32000,
},
{
x: "Thu",
y: 28000,
},
{
x: "Fri",
y: 14000,
},
{
x: "Sat",
y: 19000,
},
{
x: "Sun",
y: 36000,
seriesName: "",
data: [
{
x: "Mon",
y: 10000,
},
{
x: "Tue",
y: 12000,
},
{
x: "Wed",
y: 32000,
},
{
x: "Thu",
y: 28000,
},
{
x: "Fri",
y: 14000,
},
{
x: "Sat",
y: 19000,
},
{
x: "Sun",
y: 36000,
},
],
},
],
xAxisName: "Last Week",
@ -382,6 +432,20 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
],
},
},
MAP_WIDGET: {
rows: 12,
columns: 8,
isDisabled: false,
isVisible: true,
widgetName: "Map",
enableSearch: true,
enableCreateMarker: true,
zoomLevel: 50,
enablePickLocation: true,
allowZoom: true,
mapCenter: { lat: -34.397, lng: 150.644 },
defaultMarkers: [{ lat: -34.397, lng: 150.644, title: "Test A" }],
},
},
configVersion: 1,
};

View File

@ -106,6 +106,11 @@ const WidgetSidebarResponse: {
widgetCardName: "Chart",
key: generateReactKey(),
},
{
type: "MAP_WIDGET",
widgetCardName: "Map",
key: generateReactKey(),
},
],
["Layout widgets"]: [
{
@ -113,6 +118,11 @@ const WidgetSidebarResponse: {
widgetCardName: "Container",
key: generateReactKey(),
},
{
type: "TABS_WIDGET",
widgetCardName: "Tabs",
key: generateReactKey(),
},
],
};

View File

@ -15,9 +15,14 @@ import { CheckboxWidgetProps } from "../../widgets/CheckboxWidget";
import { RadioGroupWidgetProps } from "../../widgets/RadioGroupWidget";
import { AlertWidgetProps } from "../../widgets/AlertWidget";
import { FilePickerWidgetProps } from "../../widgets/FilepickerWidget";
import {
TabsWidgetProps,
TabContainerWidgetProps,
} from "../../widgets/TabsWidget";
import { ChartWidgetProps } from "../../widgets/ChartWidget";
import { FormWidgetProps } from "widgets/FormWidget";
import { FormButtonWidgetProps } from "widgets/FormButtonWidget";
import { MapWidgetProps } from "widgets/MapWidget";
import { ModalWidgetProps } from "widgets/ModalWidget";
import { IconWidgetProps } from "widgets/IconWidget";
@ -56,10 +61,13 @@ export interface WidgetConfigReducerState {
RADIO_GROUP_WIDGET: Partial<RadioGroupWidgetProps> & WidgetConfigProps;
ALERT_WIDGET: Partial<AlertWidgetProps> & WidgetConfigProps;
FILE_PICKER_WIDGET: Partial<FilePickerWidgetProps> & WidgetConfigProps;
TABS_WIDGET: Partial<TabsWidgetProps<TabContainerWidgetProps>> &
WidgetConfigProps;
MODAL_WIDGET: Partial<ModalWidgetProps> & WidgetConfigProps;
CHART_WIDGET: Partial<ChartWidgetProps> & WidgetConfigProps;
FORM_WIDGET: Partial<FormWidgetProps> & WidgetConfigProps;
FORM_BUTTON_WIDGET: Partial<FormButtonWidgetProps> & WidgetConfigProps;
MAP_WIDGET: Partial<MapWidgetProps> & WidgetConfigProps;
CANVAS_WIDGET: Partial<ContainerWidgetProps<WidgetProps>> &
WidgetConfigProps;
ICON_WIDGET: Partial<IconWidgetProps> & WidgetConfigProps;

View File

@ -44,6 +44,7 @@ export function* createModalSaga(action: ReduxAction<{ modalName: string }>) {
topRow: 0,
columns: 0,
rows: 0,
tabId: "",
};
yield put({
type: ReduxActionTypes.WIDGET_ADD_CHILD,

View File

@ -67,7 +67,6 @@ function* getChildWidgetProps(
}
const widgetProps = { ...defaultConfig, ...props, columns, rows, minHeight };
const widget = generateWidgetProps(
parent,
type,
@ -257,7 +256,6 @@ export function* resizeSaga(resizeAction: ReduxAction<WidgetResize>) {
widget = { ...widget, leftColumn, rightColumn, topRow, bottomRow };
widgets[widgetId] = widget;
yield put(updateAndSaveLayout(widgets));
} catch (error) {
yield put({

View File

@ -211,6 +211,23 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
}
return { isValid, parsed };
},
[VALIDATION_TYPES.MARKERS]: (value: any): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](value);
if (!isValid) {
return {
isValid,
parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Marker Data`,
};
} else if (!_.every(parsed, datum => _.isObject(datum))) {
return {
isValid: false,
parsed: [],
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Marker Data`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.OPTIONS_DATA]: (value: any): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](value);
if (!isValid) {

View File

@ -359,7 +359,6 @@ export const generateWidgetProps = (
...derivedProperties,
parentId: parent.widgetId,
};
delete props.rows;
delete props.columns;
return props;

View File

@ -13,11 +13,16 @@ import ButtonWidget, { ButtonWidgetProps } from "widgets/ButtonWidget";
import DropdownWidget, { DropdownWidgetProps } from "widgets/DropdownWidget";
import ImageWidget, { ImageWidgetProps } from "widgets/ImageWidget";
import TableWidget, { TableWidgetProps } from "widgets/TableWidget";
import TabsWidget, {
TabsWidgetProps,
TabContainerWidgetProps,
} from "widgets/TabsWidget";
import ModalWidget, { ModalWidgetProps } from "widgets/ModalWidget";
import RichTextEditorWidget, {
RichTextEditorWidgetProps,
} from "widgets/RichTextEditorWidget";
import ChartWidget, { ChartWidgetProps } from "widgets/ChartWidget";
import MapWidget, { MapWidgetProps } from "widgets/MapWidget";
import FilePickerWidget, {
FilePickerWidgetProps,
@ -165,6 +170,19 @@ class WidgetBuilderRegistry {
DatePickerWidget.getDerivedPropertiesMap(),
DatePickerWidget.getTriggerPropertyMap(),
);
WidgetFactory.registerWidgetBuilder(
"TABS_WIDGET",
{
buildWidget(
widgetProps: TabsWidgetProps<TabContainerWidgetProps>,
): JSX.Element {
return <TabsWidget {...widgetProps} />;
},
},
TabsWidget.getPropertyValidationMap(),
TabsWidget.getDerivedPropertiesMap(),
TabsWidget.getTriggerPropertyMap(),
);
WidgetFactory.registerWidgetBuilder(
WidgetTypes.MODAL_WIDGET,
{
@ -224,6 +242,18 @@ class WidgetBuilderRegistry {
FormButtonWidget.getTriggerPropertyMap(),
);
WidgetFactory.registerWidgetBuilder(
"MAP_WIDGET",
{
buildWidget(widgetProps: MapWidgetProps): JSX.Element {
return <MapWidget {...widgetProps} />;
},
},
MapWidget.getPropertyValidationMap(),
MapWidget.getDerivedPropertiesMap(),
MapWidget.getTriggerPropertyMap(),
);
WidgetFactory.registerWidgetBuilder(
WidgetTypes.CANVAS_WIDGET,
{

View File

@ -91,6 +91,15 @@ abstract class BaseWidget<
disableDrag && disable !== undefined && disableDrag(disable);
}
updateWidget(
operationName: string,
widgetId: string,
widgetProperties: any,
): void {
const { updateWidget } = this.context;
updateWidget && updateWidget(operationName, widgetId, widgetProperties);
}
updateWidgetProperty(propertyName: string, propertyValue: any): void {
const { updateWidgetProperty } = this.context;
const { widgetId } = this.props;

View File

@ -8,7 +8,6 @@ import { VALIDATION_TYPES } from "constants/WidgetValidation";
class ChartWidget extends BaseWidget<ChartWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
chartData: VALIDATION_TYPES.CHART_DATA,
xAxisName: VALIDATION_TYPES.TEXT,
yAxisName: VALIDATION_TYPES.TEXT,
chartName: VALIDATION_TYPES.TEXT,
@ -27,6 +26,7 @@ class ChartWidget extends BaseWidget<ChartWidgetProps, WidgetState> {
chartName={this.props.chartName}
chartData={this.props.chartData}
widgetId={this.props.widgetId}
allowHorizontalScroll={this.props.allowHorizontalScroll}
/>
);
}
@ -44,11 +44,16 @@ export type ChartType =
| "AREA_CHART"
| "SCATTER_CHART";
export interface ChartData {
export interface ChartDataPoint {
x: any;
y: any;
}
export interface ChartData {
seriesName?: string;
data: ChartDataPoint[];
}
export interface ChartWidgetProps extends WidgetProps {
chartType: ChartType;
chartData: ChartData[];
@ -56,6 +61,7 @@ export interface ChartWidgetProps extends WidgetProps {
yAxisName: string;
chartName: string;
isVisible?: boolean;
allowHorizontalScroll: boolean;
}
export default ChartWidget;

View File

@ -12,12 +12,12 @@ import {
DerivedPropertiesMap,
TriggerPropertiesMap,
} from "utils/WidgetFactory";
import moment from "moment-timezone";
class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
defaultDate: VALIDATION_TYPES.DATE,
timezone: VALIDATION_TYPES.TEXT,
enableTimePicker: VALIDATION_TYPES.BOOLEAN,
dateFormat: VALIDATION_TYPES.TEXT,
@ -43,18 +43,30 @@ class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
};
}
componentDidMount() {
super.componentDidMount();
if (this.props.defaultDate) {
this.updateWidgetMetaProperty(
"selectedDate",
new Date(this.props.defaultDate.dateValue),
);
}
}
componentDidUpdate(prevProps: DatePickerWidgetProps) {
super.componentDidUpdate(prevProps);
if (this.props.defaultDate) {
if (
(this.props.selectedDate !== prevProps.selectedDate &&
this.props.selectedDate === undefined) ||
prevProps.defaultDate === undefined ||
this.props.defaultDate.toDateString() !==
prevProps.defaultDate.toDateString()
) {
this.updateWidgetMetaProperty("selectedDate", this.props.defaultDate);
}
if (
this.props.defaultDate &&
(prevProps.defaultDate === undefined ||
!moment(this.props.defaultDate.dateValue).isSame(
moment(prevProps.defaultDate.dateValue),
"second",
))
) {
this.updateWidgetMetaProperty(
"selectedDate",
new Date(this.props.defaultDate.dateValue),
);
}
}
@ -64,8 +76,16 @@ class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
label={`${this.props.label}${this.props.isRequired ? " *" : ""}`}
dateFormat={this.props.dateFormat}
widgetId={this.props.widgetId}
timezone={this.props.timezone}
enableTimePicker={this.props.enableTimePicker}
timezone={
this.props.defaultDate && this.props.defaultDate.timezone
? this.props.defaultDate.timezone
: ""
}
enableTimePicker={
this.props.defaultDate && this.props.defaultDate.isTimeEnabled
? this.props.defaultDate.isTimeEnabled
: false
}
isDisabled={this.props.isDisabled}
datePickerType={"DATE_PICKER"}
onDateSelected={this.onDateSelected}
@ -95,10 +115,12 @@ class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
export type DatePickerType = "DATE_PICKER" | "DATE_RANGE_PICKER";
export interface DatePickerWidgetProps extends WidgetProps {
defaultDate: Date;
defaultDate: {
dateValue: number;
isTimeEnabled: boolean;
timezone: string;
};
selectedDate: Date;
timezone?: string;
enableTimePicker: boolean;
isDisabled: boolean;
dateFormat: string;
label: string;

View File

@ -0,0 +1,151 @@
import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import MapComponent from "components/designSystems/appsmith/MapComponent";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { EventType } from "constants/ActionConstants";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
class MapWidget extends BaseWidget<MapWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
defaultMarkers: VALIDATION_TYPES.MARKERS,
isDisabled: VALIDATION_TYPES.BOOLEAN,
isVisible: VALIDATION_TYPES.BOOLEAN,
enableSearch: VALIDATION_TYPES.BOOLEAN,
enablePickLocation: VALIDATION_TYPES.BOOLEAN,
allowZoom: VALIDATION_TYPES.BOOLEAN,
enableCreateMarker: VALIDATION_TYPES.BOOLEAN,
zoomLevel: VALIDATION_TYPES.NUMBER,
onMarkerClick: VALIDATION_TYPES.ACTION_SELECTOR,
onCreateMarker: VALIDATION_TYPES.ACTION_SELECTOR,
};
}
static getTriggerPropertyMap(): TriggerPropertiesMap {
return {
onMarkerClick: true,
onCreateMarker: true,
};
}
updateCenter = (lat: number, lng: number) => {
this.updateWidgetMetaProperty("center", { lat, lng });
};
onCreateMarker = (lat: number, lng: number) => {
const markers: Array<MarkerProps> = [...this.props.markers];
markers.push({ lat: lat, lng: lng });
this.updateWidgetMetaProperty("markers", markers);
if (this.props.onCreateMarker) {
super.executeAction({
dynamicString: this.props.onCreateMarker,
event: {
type: EventType.ON_CREATE_MARKER,
},
});
}
};
onMarkerClick = (lat: number, lng: number, title: string) => {
this.updateWidgetMetaProperty("selectedMarker", {
lat: lat,
lng: lng,
title: title,
});
if (this.props.onMarkerClick) {
super.executeAction({
dynamicString: this.props.onMarkerClick,
event: {
type: EventType.ON_MARKER_CLICK,
},
});
}
};
componentDidMount() {
super.componentDidMount();
if (this.props.mapCenter) {
this.updateWidgetMetaProperty("center", this.props.mapCenter);
}
if (this.props.defaultMarkers && !this.props.markers) {
this.updateWidgetMetaProperty("markers", [...this.props.defaultMarkers]);
}
}
componentDidUpdate(prevProps: MapWidgetProps) {
super.componentDidUpdate(prevProps);
if (
this.props.mapCenter &&
prevProps.mapCenter &&
(this.props.mapCenter.lat !== prevProps.mapCenter.lat ||
this.props.mapCenter.lng !== prevProps.mapCenter.lng)
) {
this.updateWidgetMetaProperty("center", this.props.mapCenter);
}
if (
this.props.defaultMarkers &&
!this.props.markers &&
this.props.defaultMarkers.length !== prevProps.defaultMarkers?.length
) {
this.updateWidgetMetaProperty("markers", [...this.props.defaultMarkers]);
}
}
getPageView() {
return (
<MapComponent
widgetId={this.props.widgetId}
isVisible={this.props.isVisible}
zoomLevel={this.props.zoomLevel}
allowZoom={this.props.allowZoom}
center={this.props.center || this.props.mapCenter}
enableCreateMarker={this.props.enableCreateMarker}
updateCenter={this.updateCenter}
isDisabled={this.props.isDisabled}
enableSearch={this.props.enableSearch}
enablePickLocation={this.props.enablePickLocation}
saveMarker={this.onCreateMarker}
selectMarker={this.onMarkerClick}
markers={this.props.markers || []}
/>
);
}
getWidgetType(): WidgetType {
return "MAP_WIDGET";
}
}
export interface MarkerProps {
lat: number;
lng: number;
title?: string;
description?: string;
}
export interface MapWidgetProps extends WidgetProps {
isDisabled?: boolean;
isVisible?: boolean;
enableSearch: boolean;
zoomLevel: number;
allowZoom: boolean;
enablePickLocation: boolean;
enableCreateMarker: boolean;
mapCenter: {
lat: number;
lng: number;
};
center?: {
lat: number;
lng: number;
};
defaultMarkers?: Array<MarkerProps>;
markers?: Array<MarkerProps>;
selectedMarker?: MarkerProps;
onMarkerClick?: string;
onCreateMarker?: string;
}
export default MapWidget;

View File

@ -29,22 +29,22 @@ class RichTextEditorWidget extends BaseWidget<
componentDidMount() {
super.componentDidMount();
if (this.props.text) {
this.updateWidgetMetaProperty("value", this.props.text);
if (this.props.defaultText) {
this.updateWidgetMetaProperty("text", this.props.defaultText);
}
}
componentDidUpdate(prevProps: RichTextEditorWidgetProps) {
super.componentDidUpdate(prevProps);
if (this.props.text) {
if (this.props.text !== prevProps.text) {
this.updateWidgetMetaProperty("value", this.props.text);
if (this.props.defaultText) {
if (this.props.defaultText !== prevProps.defaultText) {
this.updateWidgetMetaProperty("text", this.props.defaultText);
}
}
}
onValueChange = (value: string) => {
this.updateWidgetMetaProperty("value", value);
onValueChange = (text: string) => {
this.updateWidgetMetaProperty("text", text);
if (this.props.onTextChange) {
super.executeAction({
dynamicString: this.props.onTextChange,
@ -59,7 +59,7 @@ class RichTextEditorWidget extends BaseWidget<
return (
<RichtextEditorComponent
onValueChange={this.onValueChange}
defaultValue={this.props.value}
defaultValue={this.props.text}
widgetId={this.props.widgetId}
placeholder={this.props.placeholder}
key={this.props.widgetId}
@ -80,8 +80,8 @@ export interface InputValidator {
}
export interface RichTextEditorWidgetProps extends WidgetProps {
defaultText?: string;
text?: string;
value?: string;
placeholder?: string;
onTextChange?: string;
isDisabled?: boolean;

View File

@ -0,0 +1,196 @@
import React from "react";
import styled from "styled-components";
import TabsComponent from "components/designSystems/appsmith/TabsComponent";
import { WidgetType, WidgetTypes } from "constants/WidgetConstants";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import WidgetFactory from "utils/WidgetFactory";
import { generateReactKey } from "utils/generators";
const TabsContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
`;
const ChildrenWrapper = styled.div`
height: 100%;
width: 100%;
position: relative;
border: 1px solid;
border-top: none;
border-color: ${props => props.theme.colors.bodyBG};
background: ${props => props.theme.colors.builderBodyBG};
`;
const ScrollableCanvasWrapper = styled.div`
overflow-y: auto;
position: absolute;
width: 100%;
height: 100%;
`;
class TabsWidget extends BaseWidget<
TabsWidgetProps<TabContainerWidgetProps>,
WidgetState
> {
onTabChange = (tabId: string) => {
this.updateWidgetMetaProperty("selectedTabId", tabId);
};
getPageView() {
return (
<TabsContainer>
<TabsComponent {...this.props} onTabChange={this.onTabChange} />
<ChildrenWrapper>
<ScrollableCanvasWrapper>
{this.renderComponent()}
</ScrollableCanvasWrapper>
</ChildrenWrapper>
</TabsContainer>
);
}
renderComponent = () => {
const selectedTabId = this.props.selectedTabId;
const children = this.props.children.filter(item => {
return selectedTabId === item.tabId;
})[0];
const childWidgetData: TabContainerWidgetProps = children;
if (!childWidgetData) {
return <div></div>;
}
childWidgetData.shouldScrollContents = false;
childWidgetData.canExtend = this.props.shouldScrollContents;
const { componentWidth, componentHeight } = this.getComponentDimensions();
childWidgetData.rightColumn = componentWidth;
childWidgetData.bottomRow = this.props.shouldScrollContents
? (this.props.bottomRow - this.props.topRow - 1) *
this.props.parentRowSpace
: componentHeight;
return WidgetFactory.createWidget(childWidgetData, this.props.renderMode);
};
getWidgetType(): WidgetType {
return "TABS_WIDGET";
}
addTabContainer = () => {
let tabId = "";
const childrenTabIds: string[] = this.props.children.map(children => {
return children.tabId;
});
for (let index = 0; index < this.props.tabs.length; index++) {
const tab = this.props.tabs[index];
if (!childrenTabIds.includes(tab.id)) {
tabId = tab.id;
}
}
const columns =
(this.props.rightColumn - this.props.leftColumn) *
this.props.parentColumnSpace;
const rows =
(this.props.bottomRow - this.props.topRow - 1) *
this.props.parentRowSpace;
const config = {
type: WidgetTypes.CANVAS_WIDGET,
columns: columns,
rows: rows,
parentRowSpace: 1,
parentColumnSpace: 1,
leftColumn: 0,
topRow: 1,
newWidgetId: generateReactKey(),
widgetId: this.props.widgetId,
props: {
tabId: tabId,
containerStyle: "none",
canExtend: false,
detachFromLayout: true,
children: [],
},
};
this.updateWidget("ADD_CHILD", this.props.widgetId, config);
};
removeTabContainer = () => {
let removedContainerWidgetId = "";
const tabIds: string[] = this.props.tabs.map(tab => {
return tab.id;
});
for (let index = 0; index < this.props.children.length; index++) {
const children = this.props.children[index];
if (!tabIds.includes(children.tabId)) {
removedContainerWidgetId = children.widgetId;
}
}
this.updateWidget("REMOVE_CHILD", removedContainerWidgetId, {
parentId: this.props.widgetId,
});
};
componentDidUpdate(prevProps: TabsWidgetProps<TabContainerWidgetProps>) {
super.componentDidUpdate(prevProps);
if (this.props.tabs) {
if (
this.props.tabs.length !== prevProps.tabs.length &&
this.props.children.length !== this.props.tabs.length
) {
//adding container widget for the new tab
if (this.props.tabs.length > this.props.children.length) {
this.addTabContainer();
}
//removing container widget for the removed tab
else {
this.removeTabContainer();
}
}
}
if (this.props.selectedTab) {
if (this.props.selectedTab !== prevProps.selectedTab) {
let selectedTabId = "";
for (let index = 0; index < this.props.tabs.length; index++) {
if (this.props.tabs[index].label === this.props.selectedTab) {
selectedTabId = this.props.tabs[index].id;
}
}
this.updateWidgetMetaProperty("selectedTabId", selectedTabId);
}
}
}
componentDidMount() {
if (this.props.selectedTab) {
let selectedTabId = "";
for (let index = 0; index < this.props.tabs.length; index++) {
if (this.props.tabs[index].label === this.props.selectedTab) {
selectedTabId = this.props.tabs[index].id;
}
}
this.updateWidgetMetaProperty("selectedTabId", selectedTabId);
}
}
}
export interface TabContainerWidgetProps extends WidgetProps {
tabId: string;
}
export interface TabsWidgetProps<T extends TabContainerWidgetProps>
extends WidgetProps {
isVisible?: boolean;
shouldScrollContents: boolean;
tabs: Array<{
id: string;
label: string;
}>;
children: T[];
snapColumns?: number;
snapRows?: number;
selectedTab: string;
selectedTabId: string;
}
export default TabsWidget;

View File

@ -3301,7 +3301,6 @@ async@^2.1.4, async@^2.6.2:
async@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
asynckit@^0.4.0:
version "0.4.0"
@ -3690,7 +3689,7 @@ babel-preset-react-app@^9.1.1:
babel-plugin-macros "2.8.0"
babel-plugin-transform-react-remove-prop-types "0.4.24"
babel-runtime@^6.26.0:
babel-runtime@^6.11.6, babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies:
@ -4049,7 +4048,6 @@ cache-base@^1.0.1:
cachedir@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8"
integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==
call-me-maybe@^1.0.1:
version "1.0.1"
@ -4188,6 +4186,10 @@ chance@^1.1.3:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.4.tgz#d8743bf8e40bb05e024c305ca1ff441195eb23db"
change-emitter@^0.1.2:
version "0.1.6"
resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515"
character-entities-legacy@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
@ -4502,7 +4504,6 @@ comma-separated-tokens@^1.0.0:
commander@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.0.tgz#545983a0603fe425bc672d66c9e3c89c42121a83"
integrity sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==
commander@^2.11.0, commander@^2.19.0, commander@^2.20.0:
version "2.20.3"
@ -5015,7 +5016,6 @@ cypress-xpath@^1.4.0:
cypress@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.1.0.tgz#295f115d2e8a08fff2760ab49d94d876f5877aee"
integrity sha512-FFV8pS9iuriSX4M9rna6awJUhiqozZD1D5z5BprCUJoho1ctbcgpkEUIUnqxli2OwjQqVz07egO+iqoGL+tw7g==
dependencies:
"@cypress/listr-verbose-renderer" "0.4.1"
"@cypress/xvfb" "1.2.4"
@ -6656,7 +6656,6 @@ get-value@^2.0.3, get-value@^2.0.6:
getos@3.1.4:
version "3.1.4"
resolved "https://registry.yarnpkg.com/getos/-/getos-3.1.4.tgz#29cdf240ed10a70c049add7b6f8cb08c81876faf"
integrity sha512-UORPzguEB/7UG5hqiZai8f0vQ7hzynMQyJLxStoQ8dPGAcmgsfXOPA4iE/fGtweHYkK+z4zc9V0g+CIFRf5HYw==
dependencies:
async "^3.1.0"
@ -6816,6 +6815,10 @@ good-listener@^1.2.2:
dependencies:
delegate "^3.1.2"
google-maps-infobox@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/google-maps-infobox/-/google-maps-infobox-2.0.0.tgz#1ea6de93c0cdf4138c2d586331835c83dcc59dc2"
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2:
version "4.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
@ -7019,6 +7022,10 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^2.3.1:
version "2.5.5"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.2.1, hoist-non-react-statics@^3.3.0:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
@ -7409,7 +7416,7 @@ interweave@^12.1.1:
escape-html "^1.0.3"
prop-types "^15.7.2"
invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4:
invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
dependencies:
@ -8786,7 +8793,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
lodash@4.17.15, "lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.0.1, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@~4.17.10:
lodash@4.17.15, "lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.0.1, lodash@^4.16.2, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@~4.17.10:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
@ -8935,6 +8942,14 @@ markdown-to-jsx@^6.10.3, markdown-to-jsx@^6.9.1, markdown-to-jsx@^6.9.3:
prop-types "^15.6.2"
unquote "^1.1.0"
marker-clusterer-plus@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/marker-clusterer-plus/-/marker-clusterer-plus-2.1.4.tgz#f8eff74d599dab3b7d0e3fed5264ea0e704f5d67"
markerwithlabel@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/markerwithlabel/-/markerwithlabel-2.0.2.tgz#fa6aee4abb0ee553e24e2b708226858f58b8729e"
material-colors@^1.2.1:
version "1.2.6"
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
@ -9873,7 +9888,6 @@ osenv@0:
ospath@1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b"
integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=
p-defer@^1.0.0:
version "1.0.0"
@ -11210,7 +11224,6 @@ raf@^3.4.1:
ramda@0.26.1:
version "0.26.1"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==
ramda@^0.21.0:
version "0.21.0"
@ -11483,6 +11496,22 @@ react-focus-lock@^2.1.0:
use-callback-ref "^1.2.1"
use-sidecar "^1.0.1"
react-google-maps@^9.4.5:
version "9.4.5"
resolved "https://registry.yarnpkg.com/react-google-maps/-/react-google-maps-9.4.5.tgz#920c199bdc925e0ce93880edffb09428d263aafa"
dependencies:
babel-runtime "^6.11.6"
can-use-dom "^0.1.0"
google-maps-infobox "^2.0.0"
invariant "^2.2.1"
lodash "^4.16.2"
marker-clusterer-plus "^2.1.4"
markerwithlabel "^2.0.1"
prop-types "^15.5.8"
recompose "^0.26.0"
scriptjs "^2.5.8"
warning "^3.0.0"
react-helmet-async@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.0.4.tgz#079ef10b7fefcaee6240fefd150711e62463cc97"
@ -11945,6 +11974,15 @@ rechoir@^0.6.2:
dependencies:
resolve "^1.1.6"
recompose@^0.26.0:
version "0.26.0"
resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.26.0.tgz#9babff039cb72ba5bd17366d55d7232fbdfb2d30"
dependencies:
change-emitter "^0.1.2"
fbjs "^0.8.1"
hoist-non-react-statics "^2.3.1"
symbol-observable "^1.0.4"
recursive-readdir@2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f"
@ -12517,6 +12555,10 @@ schema-utils@^2.0.1, schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6
ajv "^6.10.2"
ajv-keywords "^3.4.1"
scriptjs@^2.5.8:
version "2.5.9"
resolved "https://registry.yarnpkg.com/scriptjs/-/scriptjs-2.5.9.tgz#343915cd2ec2ed9bfdde2b9875cd28f59394b35f"
scss-tokenizer@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
@ -13294,7 +13336,6 @@ supports-color@^2.0.0:
supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
@ -13326,7 +13367,7 @@ svgo@^1.0.0, svgo@^1.2.2:
unquote "~1.1.1"
util.promisify "~1.0.0"
symbol-observable@^1.0.2, symbol-observable@^1.1.0, symbol-observable@^1.2.0:
symbol-observable@^1.0.2, symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
@ -13914,7 +13955,6 @@ unset-value@^1.0.0:
untildify@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
upath@^1.1.1:
version "1.2.0"
@ -14142,6 +14182,12 @@ walker@^1.0.7, walker@~1.0.5:
dependencies:
makeerror "1.0.x"
warning@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"
dependencies:
loose-envify "^1.0.0"
warning@^4.0.2, warning@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"