diff --git a/.env.example b/.env.example index 2a2b1e90ac..068552f107 100644 --- a/.env.example +++ b/.env.example @@ -68,3 +68,6 @@ APPSMITH_RECAPTCHA_ENABLED= # Intercom APPSMITH_DISABLE_INTERCOM= + +# baidu map +BMAP_AK= diff --git a/.gitignore b/.gitignore index eb79f65bc9..44970bbf83 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ app/client/yalc.lock .idea .fleet/* app/client/.fleet/* +*debug.log diff --git a/README.md b/README.md index 052279c818..d6285ba946 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # 前端低代码工具(base on AppSmith-v1.7.3) ## 🎈 启动项目(windows) -[非windows](/contributions/ClientSetup.md) + +[非 windows](/contributions/ClientSetup.md) + ``` // 配置 host 127.0.0.1 dev.appsmith.com @@ -14,11 +16,12 @@ cd app/client yarn start-proxy // 启动前端服务 -yarn +yarn yarn start-win ( yarn start-cloudos ) ``` [服务端指南](/contributions/ServerSetup.md) + ``` // .env 环境变量,开发测试 mongo、redis 地址 APPSMITH_MONGODB_URI="mongodb://10.10.13.50:27017/appsmith" @@ -36,6 +39,7 @@ bash ./scripts/start-dev-server.sh ``` ## 💕 合并 GitHub 更新 + ``` // 外网代理 export https_proxy=http://127.0.0.1:3083 @@ -50,6 +54,7 @@ git push origin master ``` ## 📦 打包发布 + ``` // 前端打包 cd app/client @@ -77,6 +82,7 @@ docker-registry-idc01-sz.cloudtogo.cn/pageplug-server:cloudtogo ``` ## 🌱 系统安装脚本 + ``` install.sh @@ -86,6 +92,7 @@ sed -i 's/index\.docker\.io\/appsmith\/appsmith-server/harbor\.cloud2go\.cn\/clo ``` ## 🌼 小程序环境变量 + ``` // 小程序 ID、密钥,用于获取小程序码 CLOUDOS_WECHAT_APPID="wx414ad0dbeda1a70b" @@ -93,6 +100,7 @@ CLOUDOS_WECHAT_SECRET="d5289fd08b1fb31290f66ea2ce5ec7dc" ``` ## 🔔 StarOS 版本注意 + ``` // StarOS 版本环境变量 // 同步春景 API 列表 @@ -104,7 +112,7 @@ CLOUDOS_IN_CLOUDOS=true // nginx.conf 配置前端环境变量,替换 index.html 中的配置 sub_filter __PAGEPLUG_CLOUDOS_LOGIN_URL__ 'http://factory.dev.staros.local/user/login'; - +sub_filter __BMAP_AK__ 'nWCpSjRnXLfGuBc3iLZ9kYv8Y6wYaxf8'; 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 StarOS 版本使用固定用户 在开启 inCloudOS 前需要 diff --git a/app/client/public/index.html b/app/client/public/index.html index 73e2f13111..351ad24ec2 100755 --- a/app/client/public/index.html +++ b/app/client/public/index.html @@ -19,12 +19,14 @@ transition: all ease-in 0.3s; } + + - + diff --git a/app/client/src/api/ApplicationApi.tsx b/app/client/src/api/ApplicationApi.tsx index 1f1bb468b3..3526dfb82c 100644 --- a/app/client/src/api/ApplicationApi.tsx +++ b/app/client/src/api/ApplicationApi.tsx @@ -114,6 +114,7 @@ export type UpdateApplicationPayload = { appLayout?: AppLayoutConfig; applicationVersion?: number; viewerLayout?: string; + chartTheme?: string; }; export type UpdateApplicationRequest = UpdateApplicationPayload & { @@ -129,6 +130,7 @@ export interface ApplicationObject { workspaceId: string; pages: ApplicationPagePayload[]; userPermissions: string[]; + chartTheme?: string; } export interface PermissionGroup { diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index b92ed9f60e..bd2d9c59c9 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -481,6 +481,8 @@ export const ReduxActionTypes = { UPDATE_CANVAS_STRUCTURE: "UPDATE_CANVAS_STRUCTURE", SET_SELECTED_WIDGET_ANCESTORY: "SET_SELECTED_WIDGET_ANCESTORY", START_EVALUATION: "START_EVALUATION", + CURRENT_APPLICATION_CHART_THEME_UPDATE: + "CURRENT_APPLICATION_CHART_THEME_UPDATE", CURRENT_APPLICATION_NAME_UPDATE: "CURRENT_APPLICATION_NAME_UPDATE", CURRENT_APPLICATION_LAYOUT_UPDATE: "CURRENT_APPLICATION_LAYOUT_UPDATE", CURRENT_APPLICATION_VIEWER_LAYOUT_UPDATE: @@ -1024,6 +1026,7 @@ export interface ApplicationPayload { viewerLayout?: string; isAutoUpdate?: boolean; isManualUpdate?: boolean; + chartTheme?: string; } export type WorkspaceDetails = { diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index 396af60cb4..bc39e61409 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -988,6 +988,10 @@ export const APP_THEME_BETA_CARD_HEADING = () => `🎨 修改应用风格`; export const APP_THEME_BETA_CARD_CONTENT = () => `自定义全局样式风格,后面对所有组件进行支持`; +// echat theme +export const CHANGE_ECHART_THEME = (name: string) => + `Echart主题已切换为 ${name} 风格主题`; + export const UPGRADE_TO_EE = (authLabel: string) => `你好,我想升级并且开始使用 ${authLabel} 鉴权`; export const UPGRADE_TO_EE_GENERIC = () => `你好,我想升级`; diff --git a/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx b/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx index c6206bda79..c67b6b48c0 100644 --- a/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx +++ b/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx @@ -133,6 +133,16 @@ export const handlers = { currentApplication: action.payload, isFetchingApplication: false, }), + [ReduxActionTypes.CURRENT_APPLICATION_CHART_THEME_UPDATE]: ( + state: ApplicationsReduxState, + action: ReduxAction<{ chartTheme: string }>, + ) => ({ + ...state, + currentApplication: { + ...state.currentApplication, + chartTheme: action.payload.chartTheme, + }, + }), [ReduxActionTypes.CURRENT_APPLICATION_NAME_UPDATE]: ( state: ApplicationsReduxState, action: ReduxAction<{ name: string; slug: string }>, diff --git a/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx b/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx index ba774765f4..3e38fb1b52 100644 --- a/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx +++ b/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx @@ -86,6 +86,12 @@ export const WIDGET_DATA_FIELD_MAP: Record = { widgetName: "Chart", image: "https://assets.appsmith.com/widgetSuggestion/chart.svg", }, + ECHART_WIDGET: { + label: "chart-series-data-control", + propertyName: "chartData", + widgetName: "EChart", + image: "https://assets.appsmith.com/widgetSuggestion/chart.svg", + }, SELECT_WIDGET: { label: "选项", propertyName: "options", @@ -147,6 +153,20 @@ function getWidgetProps( dynamicBindingPathList: [{ key: `chartData.${reactKey}.data` }], }, }; + case "ECHART_WIDGET": + const ereactKey = generateReactKey(); + return { + type: suggestedWidget.type, + props: { + [fieldName]: { + [ereactKey]: { + seriesName: "Sales", + data: `{{${actionName}.${suggestedWidget.bindingQuery}}}`, + }, + }, + dynamicBindingPathList: [{ key: `echartData.${ereactKey}.data` }], + }, + }; case "SELECT_WIDGET": return { type: suggestedWidget.type, diff --git a/app/client/src/components/editorComponents/CodeEditor/index.tsx b/app/client/src/components/editorComponents/CodeEditor/index.tsx index 35b6abeffd..404b917a43 100644 --- a/app/client/src/components/editorComponents/CodeEditor/index.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/index.tsx @@ -677,6 +677,7 @@ class CodeEditor extends Component { }); } const inputValue = this.props.input.value || ""; + // console.log(inputValue, "handleChange"); if ( this.props.input.onChange && ((value !== inputValue && this.state.isFocused) || diff --git a/app/client/src/components/propertyControls/EchartDataControl.tsx b/app/client/src/components/propertyControls/EchartDataControl.tsx new file mode 100644 index 0000000000..da10e90ab4 --- /dev/null +++ b/app/client/src/components/propertyControls/EchartDataControl.tsx @@ -0,0 +1,328 @@ +import React from "react"; +import { get, isString } from "lodash"; +import BaseControl, { ControlProps } from "./BaseControl"; +import { ControlWrapper, StyledPropertyPaneButton } from "./StyledControls"; +import styled from "constants/DefaultTheme"; +import { FormIcons } from "icons/FormIcons"; +import { AnyStyledComponent } from "styled-components"; +import { CodeEditorExpected } from "components/editorComponents/CodeEditor"; +import { + EditorModes, + EditorSize, + EditorTheme, + TabBehaviour, +} from "components/editorComponents/CodeEditor/EditorConfig"; +import { Size, Category } from "design-system"; +import { AllChartData, ChartData } from "widgets/ChartWidget/constants"; +import { generateReactKey } from "utils/generators"; +import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import CodeEditor from "components/editorComponents/LazyCodeEditorWrapper"; + +const Wrapper = styled.div` + background-color: ${(props) => + props.theme.colors.propertyPane.dropdownSelectBg}; + padding: 0 8px; +`; + +const StyledOptionControlWrapper = styled(ControlWrapper)` + display: flex; + justify-content: flex-start; + padding: 0; + width: 100%; +`; + +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; + + &&& svg { + path { + fill: ${(props) => props.theme.colors.propertyPane.jsIconBg}; + } + } +`; + +const ActionHolder = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +`; + +const StyledLabel = styled.label` + margin: 8px auto 8px 0; + + && { + color: ${(props) => props.theme.colors.propertyPane.label}; + } +`; + +const Box = styled.div` + height: 16px; +`; + +type labelType = { + titleLabel: string; + areaLabel: string; + btnLabel: string; +}; + +type RenderComponentProps = { + index: string; + item: ChartData; + length: number; + dataTreePath: string; + deleteOption: (index: string) => void; + updateOption: (index: string, key: string, value: string) => void; + evaluated: { + seriesName: string; + data: Array<{ name: string; value: string }> | any; + }; + theme: EditorTheme; + noTitle?: boolean; + label?: labelType; +}; + +const expectedSeriesName: CodeEditorExpected = { + type: "string", + example: "series1", + autocompleteDataType: AutocompleteDataType.STRING, +}; +const expectedSeriesData: CodeEditorExpected = { + type: "Array<{ name: string, value: number Required }>", + example: [ + { + name: "Mon", + value: 10000, + }, + ], + autocompleteDataType: AutocompleteDataType.ARRAY, +}; + +function DataControlComponent(props: RenderComponentProps) { + const { + dataTreePath, + deleteOption, + evaluated, + index, + item, + length, + updateOption, + noTitle, + label, + } = props; + return ( + + {!noTitle ? ( + <> + + {label?.titleLabel} + {length > 1 && ( + { + deleteOption(index); + }} + width={20} + /> + )} + + + | string, + ) => { + let value: string = event as string; + if (typeof event !== "string") { + value = event.target.value; + } + updateOption(index, "seriesName", value); + }, + }} + mode={EditorModes.JAVASCRIPT} + placeholder={label?.titleLabel} + size={EditorSize.EXTENDED} + tabBehaviour={TabBehaviour.INPUT} + theme={props.theme} + /> + + + ) : null} + {label?.areaLabel} + + | string, + ) => { + let value: string = event as string; + if (typeof event !== "string") { + value = event.target.value; + } + updateOption(index, "data", value); + }, + }} + mode={EditorModes.JSON_WITH_BINDING} + placeholder={label?.areaLabel} + size={EditorSize.EXTENDED} + tabBehaviour={TabBehaviour.INPUT} + theme={props.theme} + /> + + + + ); +} + +const Name_Data_Array = ["xAxis", "yAxis"]; + +const ControlLabel = (propertyName: string) => { + if (Name_Data_Array.includes(propertyName)) { + return { + titleLabel: "标签", + areaLabel: "数据", + hasAddBtn: false, + btnLabel: "ADD AXIS", + }; + } + return { + titleLabel: "Series Title", + areaLabel: "Series Data", + hasAddBtn: true, + btnLabel: "ADD SERIES", + }; +}; + +class EchartDataControl extends BaseControl { + render() { + const chartData: AllChartData = + isString(this.props.propertyValue) || !this.props.propertyValue + ? {} + : this.props.propertyValue; + const dataLength = Object.keys(chartData).length; + const isDataSet = this.props.propertyName === "dataSet"; + const evaluatedValue = this.props.evaluatedValue; + const firstKey = Object.keys(chartData)[0] as string; + if (this.props.widgetProperties.chartType === "PIE_CHART") { + const data = dataLength + ? get(chartData, `${firstKey}`) + : { + seriesName: "", + data: [], + }; + return ( + + ); + } + + return ( + <> + + {Object.keys(chartData).map((key: string) => { + const data = get(chartData, `${key}`); + return ( + + ); + })} + + + {/* 新增按钮 */} + {ControlLabel(this.props.propertyName).hasAddBtn ? ( + + ) : null} + + ); + } + + deleteOption = (index: string) => { + this.deleteProperties([`${this.props.propertyName}.${index}`]); + }; + + updateOption = ( + index: string, + propertyName: string, + updatedValue: string, + ) => { + this.updateProperty( + `${this.props.propertyName}.${index}.${propertyName}`, + updatedValue, + ); + }; + + /** + * it adds new series data object in the chartData + */ + addOption = () => { + const randomString = generateReactKey(); + this.updateProperty(`${this.props.propertyName}.${randomString}`, { + seriesName: "", + data: JSON.stringify([{ name: "label", value: 51 }]), + }); + }; + + static getControlType() { + return "ECHART_DATA"; + } +} + +export default EchartDataControl; diff --git a/app/client/src/components/propertyControls/index.ts b/app/client/src/components/propertyControls/index.ts index a7c201c0bf..e600f49a70 100644 --- a/app/client/src/components/propertyControls/index.ts +++ b/app/client/src/components/propertyControls/index.ts @@ -16,6 +16,7 @@ import DatePickerControl, { DatePickerControlProps, } from "components/propertyControls/DatePickerControl"; import ChartDataControl from "components/propertyControls/ChartDataControl"; +import EchartDataControl from "components/propertyControls/EchartDataControl"; import LocationSearchControl from "components/propertyControls/LocationSearchControl"; import StepControl, { StepControlProps, @@ -90,6 +91,7 @@ export const PropertyControls = { ColumnActionSelectorControl, MultiSwitchControl, ChartDataControl, + EchartDataControl, LocationSearchControl, StepControl, TabControl, diff --git a/app/client/src/constants/EchartContants.ts b/app/client/src/constants/EchartContants.ts new file mode 100644 index 0000000000..cc31a050e8 --- /dev/null +++ b/app/client/src/constants/EchartContants.ts @@ -0,0 +1,64 @@ +import { EchartTheme } from "entities/AppTheming"; + +import macarons from "theme/echart/macarons.json"; +import chalk from "theme/echart/chalk.json"; +import walden from "theme/echart/walden.json"; +import westeros from "theme/echart/westeros.json"; + +type echartsMap = { + [key: string]: EchartTheme; +}; + +export const echartThemes: any = { + macarons: { + themeKey: "macarons", + ...macarons, + }, + chalk: { + themeKey: "chalk", + ...chalk, + }, + walden: { + themeKey: "walden", + ...walden, + }, + westeros: { + themeKey: "westeros", + ...westeros, + }, + dark: { + themeKey: "dark", + backgroundColor: "#100c2a", + color: [ + "#5470c6", + "#91cc75", + "#fac858", + "#ee6666", + "#73c0de", + "#3ba272", + "#fc8452", + "#9a60b4", + "#ea7ccc", + ], + }, + default: { + themeKey: "default", + backgroundColor: "#ffffff", + color: [ + "#5470c6", + "#91cc75", + "#fac858", + "#ee6666", + "#73c0de", + "#3ba272", + "#fc8452", + "#9a60b4", + "#ea7ccc", + ], + }, + transparent: { + themeKey: "transparent", + backgroundColor: "transparent", + color: [], + }, +}; diff --git a/app/client/src/constants/HelpConstants.ts b/app/client/src/constants/HelpConstants.ts index dd1b00a122..fd71b6956a 100644 --- a/app/client/src/constants/HelpConstants.ts +++ b/app/client/src/constants/HelpConstants.ts @@ -275,6 +275,10 @@ export const HelpMap: Record = { path: "/widget-reference/map-chart", searchKey: "Map Chart", }, + ECHART_WIDGET: { + path: "/widget-reference/chart", + searchKey: "Echart", + }, }; export const HelpBaseURL = "https://docs.appsmith.com"; diff --git a/app/client/src/entities/AppTheming/index.ts b/app/client/src/entities/AppTheming/index.ts index 10b477656d..43d578ba62 100644 --- a/app/client/src/entities/AppTheming/index.ts +++ b/app/client/src/entities/AppTheming/index.ts @@ -70,3 +70,10 @@ export type AppTheme = { }; }; }; + +export type EchartTheme = { + themeKey: string; + backgroundColor?: string; + color?: string[]; + [key: string]: any; +}; diff --git a/app/client/src/entities/DataTree/actionTriggers.ts b/app/client/src/entities/DataTree/actionTriggers.ts index ba0090642c..6b2235acfb 100644 --- a/app/client/src/entities/DataTree/actionTriggers.ts +++ b/app/client/src/entities/DataTree/actionTriggers.ts @@ -21,6 +21,7 @@ export enum ActionTriggerType { STOP_WATCHING_CURRENT_LOCATION = "STOP_WATCHING_CURRENT_LOCATION", CONFIRMATION_MODAL = "CONFIRMATION_MODAL", POST_MESSAGE = "POST_MESSAGE", + CALL_FUNC = "CALL_FUNC", } export const ActionTriggerFunctionNames: Record = { @@ -35,6 +36,7 @@ export const ActionTriggerFunctionNames: Record = { [ActionTriggerType.SET_INTERVAL]: "setInterval", [ActionTriggerType.SHOW_ALERT]: "showAlert", [ActionTriggerType.SHOW_MODAL_BY_NAME]: "showModal", + [ActionTriggerType.CALL_FUNC]: "callFunc", [ActionTriggerType.STORE_VALUE]: "storeValue", [ActionTriggerType.REMOVE_VALUE]: "removeValue", [ActionTriggerType.CLEAR_STORE]: "clearStore", @@ -193,12 +195,30 @@ export type PostMessageDescription = { }; }; +// type optionProps = { +// widgetName: string; +// funcName: string; +// params: any; +// }; + +type EchartActionPayload = { + options?: any; + widgetName: string; + funcName: string; +}; + +export type EchartAction = { + type: ActionTriggerType.CALL_FUNC; + payload: EchartActionPayload; +}; + export type ActionDescription = | RunPluginActionDescription | ClearPluginActionDescription | NavigateActionDescription | ShowAlertActionDescription | ShowModalActionDescription + | EchartAction | CloseModalActionDescription | StoreValueActionDescription | RemoveValueActionDescription diff --git a/app/client/src/pages/Editor/CanvasPropertyPane/index.tsx b/app/client/src/pages/Editor/CanvasPropertyPane/index.tsx index 359b58c336..f734b40993 100644 --- a/app/client/src/pages/Editor/CanvasPropertyPane/index.tsx +++ b/app/client/src/pages/Editor/CanvasPropertyPane/index.tsx @@ -15,6 +15,7 @@ const Title = styled.p` type Props = { skipThemeEditor?: boolean; }; +import EchartThemeEditor from "../ThemePropertyPane/Echart/EchartThemeEditor"; export function CanvasPropertyPane(props: Props) { const isMobile = useSelector(isMobileLayout); @@ -31,6 +32,7 @@ export function CanvasPropertyPane(props: Props) { )} {!props.skipThemeEditor && } + ); diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/testUtils.ts b/app/client/src/pages/Editor/FirstTimeUserOnboarding/testUtils.ts index 26ac4513d8..2b62b2dfae 100644 --- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/testUtils.ts +++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/testUtils.ts @@ -32,6 +32,7 @@ export const initialState: any = { currentApplication: { lastDeployedAt: null, slug: "applicationSlug", + chartTheme: "default", }, }, onBoarding: { diff --git a/app/client/src/pages/Editor/ThemePropertyPane/Echart/EchartThemeEditor.tsx b/app/client/src/pages/Editor/ThemePropertyPane/Echart/EchartThemeEditor.tsx new file mode 100644 index 0000000000..83624a5e8b --- /dev/null +++ b/app/client/src/pages/Editor/ThemePropertyPane/Echart/EchartThemeEditor.tsx @@ -0,0 +1,88 @@ +// import { createGlobalStyle } from "styled-components"; +// import { get, startCase } from "lodash"; +// import MoreIcon from "remixicon-react/MoreFillIcon"; +import { useDispatch, useSelector } from "react-redux"; +import React, { useCallback, useState } from "react"; + +import ThemeCard from "./ThemeCard"; +import { + AppThemingMode, + getAppThemingStack, + getSelectedEchartTheme, +} from "selectors/appThemingSelectors"; +import { + // resetThemeAction, + setAppThemingModeStackAction, + // updateSelectedAppThemeAction, +} from "actions/appThemingActions"; +// import SettingSection from "../SettingSection"; +import SaveThemeModal from "../SaveThemeModal"; +// import { AppTheme } from "entities/AppTheming"; +import AnalyticsUtil from "utils/AnalyticsUtil"; +import { Button, Category, Size } from "design-system"; +// import { getCurrentApplicationId } from "selectors/editorSelectors"; +import BetaCard from "components/editorComponents/BetaCard"; + +// const THEMING_BETA_CARD_POPOVER_CLASSNAME = `theming-beta-card-popover`; + +function EchartThemeEditor() { + const dispatch = useDispatch(); + // const applicationId = useSelector(getCurrentApplicationId); + // const selectedTheme = useSelector(getSelectedAppTheme); + const selectedEchartTheme = useSelector(getSelectedEchartTheme); + const themingStack = useSelector(getAppThemingStack); + const [isSaveModalOpen, setSaveModalOpen] = useState(false); + + /** + * sets the mode to THEME_EDIT + */ + const onClickChangeThemeButton = useCallback(() => { + AnalyticsUtil.logEvent("APP_THEMING_CHOOSE_THEME"); + + dispatch( + setAppThemingModeStackAction([ + ...themingStack, + AppThemingMode.ECHART_THEME_SELECTION, + ]), + ); + }, [setAppThemingModeStackAction]); + + /** + * on close save modal + */ + const onCloseSaveModal = useCallback(() => { + setSaveModalOpen(false); + }, [setSaveModalOpen]); + + return ( + <> +
+
+
+
+

Echart主题配置

+ +
+
+ + + + +
+
+ + + ); +} + +export default EchartThemeEditor; diff --git a/app/client/src/pages/Editor/ThemePropertyPane/Echart/EchartThemeSelector.tsx b/app/client/src/pages/Editor/ThemePropertyPane/Echart/EchartThemeSelector.tsx new file mode 100644 index 0000000000..b9baa9c732 --- /dev/null +++ b/app/client/src/pages/Editor/ThemePropertyPane/Echart/EchartThemeSelector.tsx @@ -0,0 +1,82 @@ +import React from "react"; +import { useDispatch, useSelector } from "react-redux"; + +import { + // getAppThemes, + getAppThemingStack, + // getSelectedAppTheme, + getSelectedEchartTheme, +} from "selectors/appThemingSelectors"; +import * as _ from "lodash"; +import { echartThemes as ECHART_THEMES } from "./constants"; +import { ThemeCard } from "./ThemeCard"; +import { SettingSection } from "../SettingSection"; +import ArrowLeft from "remixicon-react/ArrowLeftSLineIcon"; +import { setAppThemingModeStackAction } from "actions/appThemingActions"; + +function EchartThemeSelector() { + const dispatch = useDispatch(); + // const themes = useSelector(getAppThemes); + const themingStack = useSelector(getAppThemingStack); + const selectedEchartTheme = useSelector(getSelectedEchartTheme); + + /** + * goes to previous screen in the pane + */ + const onClickBack = () => { + dispatch(setAppThemingModeStackAction(themingStack.slice(0, -1))); + }; + + /** + * stores user saved themes + */ + // const userSavedThemes = themes.filter( + // (theme) => theme.isSystemTheme === false, + // ); + + const userSavedThemes = {}; + + /** + * stores default system themes + */ + const systemThemes = ECHART_THEMES; + const currentTheme = + ECHART_THEMES[selectedEchartTheme?.themeKey] || selectedEchartTheme; + return ( +
+
+ + + + +
+ {_.size(userSavedThemes) > 0 && ( +
+

你的Echart主题

+ {_.map(userSavedThemes, (theme) => ( + + ))} +
+ )} +
+

特色Echart主题

+ {_.map(systemThemes, (theme: any) => ( + + ))} +
+
+ ); +} + +export default EchartThemeSelector; diff --git a/app/client/src/pages/Editor/ThemePropertyPane/Echart/ThemeCard.tsx b/app/client/src/pages/Editor/ThemePropertyPane/Echart/ThemeCard.tsx new file mode 100644 index 0000000000..c52de409ee --- /dev/null +++ b/app/client/src/pages/Editor/ThemePropertyPane/Echart/ThemeCard.tsx @@ -0,0 +1,177 @@ +import { last } from "lodash"; +import classNames from "classnames"; +import styled from "styled-components"; +import * as _ from "lodash"; +import * as Sentry from "@sentry/react"; +import React, { useState } from "react"; +import { message } from "antd"; +import { useDispatch, useSelector } from "react-redux"; +// import DeleteIcon from "remixicon-react/DeleteBinLineIcon"; + +import { updateApplication } from "actions/applicationActions"; + +import { + AppThemingMode, + getAppThemingStack, + // getAppThemes, + // getSelectedEchartTheme, +} from "selectors/appThemingSelectors"; +import { AppTheme, EchartTheme } from "entities/AppTheming"; +// import AnalyticsUtil from "utils/AnalyticsUtil"; +// import DeleteThemeModal from "../DeleteThemeModal"; +import { getComplementaryGrayscaleColor } from "widgets/WidgetUtils"; +import { getCurrentApplicationId } from "selectors/editorSelectors"; + +/** + * ---------------------------------------------------------------------------- + * TYPES + *----------------------------------------------------------------------------- + */ +type ThemeCard = React.PropsWithChildren<{ + theme: EchartTheme; + isSelected?: boolean; + className?: string; + selectable?: boolean; + deletable?: boolean; +}>; + +const MainContainer = styled.main<{ backgroundColor: string }>` + background: ${({ backgroundColor }) => backgroundColor}; +`; + +const HeaderContainer = styled.main<{ primaryColor: string }>` + background-color: ${({ primaryColor }) => primaryColor}; + color: ${({ primaryColor }) => getComplementaryGrayscaleColor(primaryColor)}; +`; + +const MainText = styled.main<{ backgroundColor: string }>` + color: ${({ backgroundColor }) => + getComplementaryGrayscaleColor(backgroundColor)}; +`; + +const MainTextTransparentBg = styled.main<{ backgroundColor: string }>` + background: #fff; + background-image: linear-gradient( + 45deg, + #e7e7e7 25%, + transparent 0, + transparent 75%, + #e7e7e7 0 + ), + linear-gradient( + 45deg, + #e7e7e7 25%, + transparent 0, + transparent 75%, + #e7e7e7 0 + ); + background-position: 0 0, 15px 15px; + background-size: 30px 30px; +`; + +const ThemeColorCircle = styled.main<{ backgroundColor: string }>` + background-color: ${({ backgroundColor }) => backgroundColor}; +`; + +/** + * ---------------------------------------------------------------------------- + * COMPONENT + *----------------------------------------------------------------------------- + */ +export function ThemeCard(props: ThemeCard) { + const { selectable, theme } = props; + const dispatch = useDispatch(); + // const selectedEchart = useSelector(getSelectedEchartTheme); + const themingStack = useSelector(getAppThemingStack); + const themingMode = last(themingStack); + const applicationId = useSelector(getCurrentApplicationId); + const isThemeSelectionMode = + themingMode === AppThemingMode.ECHART_THEME_SELECTION; + + // colors + const userDefinedColors = _.slice(theme.color, 0, 6); + const backgroundColor = theme.backgroundColor; + + /** + * fires action for changing theme + * + * NOTE: since we are same card in theme edit and theme selection, + * we don't need to fire the action in theme edit mode on click on the card + */ + + const changeSelectedTheme = () => { + const data = { + id: applicationId, + chartTheme: theme.themeKey, + }; + if (isThemeSelectionMode && selectable) { + dispatch(updateApplication(applicationId, data)); + message.success(`Echart主题已切换为 ${theme.themeKey} 风格主题`); + } + }; + + const TRANSPARENT_BG = ["transparent", "rgba(0,0,0,0)"]; + + const MainWrap: any = + backgroundColor === "transparent" ? MainTextTransparentBg : MainContainer; + return ( +
+ {selectable && ( +
+

+ {props.theme.displayName} +

+
+ )} +
+ +
+ + {theme.themeKey} + +
+ {userDefinedColors.map((colorKey: any, index: number) => ( + + ))} +
+
+
+
+
+
+ + {props.children} +
+
+ ); +} + +ThemeCard.displayName = "ThemeCard"; + +export default Sentry.withProfiler(ThemeCard); diff --git a/app/client/src/pages/Editor/ThemePropertyPane/Echart/constants.ts b/app/client/src/pages/Editor/ThemePropertyPane/Echart/constants.ts new file mode 100644 index 0000000000..cc31a050e8 --- /dev/null +++ b/app/client/src/pages/Editor/ThemePropertyPane/Echart/constants.ts @@ -0,0 +1,64 @@ +import { EchartTheme } from "entities/AppTheming"; + +import macarons from "theme/echart/macarons.json"; +import chalk from "theme/echart/chalk.json"; +import walden from "theme/echart/walden.json"; +import westeros from "theme/echart/westeros.json"; + +type echartsMap = { + [key: string]: EchartTheme; +}; + +export const echartThemes: any = { + macarons: { + themeKey: "macarons", + ...macarons, + }, + chalk: { + themeKey: "chalk", + ...chalk, + }, + walden: { + themeKey: "walden", + ...walden, + }, + westeros: { + themeKey: "westeros", + ...westeros, + }, + dark: { + themeKey: "dark", + backgroundColor: "#100c2a", + color: [ + "#5470c6", + "#91cc75", + "#fac858", + "#ee6666", + "#73c0de", + "#3ba272", + "#fc8452", + "#9a60b4", + "#ea7ccc", + ], + }, + default: { + themeKey: "default", + backgroundColor: "#ffffff", + color: [ + "#5470c6", + "#91cc75", + "#fac858", + "#ee6666", + "#73c0de", + "#3ba272", + "#fc8452", + "#9a60b4", + "#ea7ccc", + ], + }, + transparent: { + themeKey: "transparent", + backgroundColor: "transparent", + color: [], + }, +}; diff --git a/app/client/src/pages/Editor/ThemePropertyPane/index.tsx b/app/client/src/pages/Editor/ThemePropertyPane/index.tsx index 791759613d..6fafeae1e1 100644 --- a/app/client/src/pages/Editor/ThemePropertyPane/index.tsx +++ b/app/client/src/pages/Editor/ThemePropertyPane/index.tsx @@ -4,6 +4,7 @@ import { last } from "lodash"; import ThemeEditor from "./ThemeEditor"; import ThemeSelector from "./ThemeSelector"; +import EchartThemeSelector from "./Echart/EchartThemeSelector"; import { AppThemingMode, getAppThemingStack, @@ -26,6 +27,8 @@ export function ThemePropertyPane() { return ; case themingMode === AppThemingMode.APP_THEME_SELECTION: return ; + case themingMode === AppThemingMode.ECHART_THEME_SELECTION: + return ; default: return ; } diff --git a/app/client/src/reducers/entityReducers/appReducer.ts b/app/client/src/reducers/entityReducers/appReducer.ts index d429076ef1..88c7a76219 100644 --- a/app/client/src/reducers/entityReducers/appReducer.ts +++ b/app/client/src/reducers/entityReducers/appReducer.ts @@ -40,6 +40,7 @@ export type AppDataState = { canBeRequested: boolean; currentPosition?: Partial; }; + echartInstance: any; }; const initialState: AppDataState = { @@ -67,6 +68,7 @@ const initialState: AppDataState = { canBeRequested: "geolocation" in navigator, currentPosition: {}, }, + echartInstance: {}, }; const appReducer = createReducer(initialState, { diff --git a/app/client/src/sagas/ActionExecution/ActionExecutionSagas.ts b/app/client/src/sagas/ActionExecution/ActionExecutionSagas.ts index b519a6592a..f88edcd88a 100644 --- a/app/client/src/sagas/ActionExecution/ActionExecutionSagas.ts +++ b/app/client/src/sagas/ActionExecution/ActionExecutionSagas.ts @@ -46,6 +46,7 @@ import { setIntervalSaga, } from "sagas/ActionExecution/SetIntervalSaga"; import { + getEchartSaga, getCurrentLocationSaga, stopWatchCurrentLocation, watchCurrentLocation, @@ -128,7 +129,14 @@ export function* executeActionTriggers( triggerMeta, ); break; - + case ActionTriggerType.CALL_FUNC: + response = yield call( + getEchartSaga, + trigger.payload, + eventType, + triggerMeta, + ); + break; case ActionTriggerType.WATCH_CURRENT_LOCATION: response = yield call( watchCurrentLocation, diff --git a/app/client/src/sagas/ActionExecution/GetCurrentLocationSaga.ts b/app/client/src/sagas/ActionExecution/GetCurrentLocationSaga.ts index 9cd8d6fc0e..d88cfeb25b 100644 --- a/app/client/src/sagas/ActionExecution/GetCurrentLocationSaga.ts +++ b/app/client/src/sagas/ActionExecution/GetCurrentLocationSaga.ts @@ -1,5 +1,6 @@ import { GetCurrentLocationDescription, + EchartAction, WatchCurrentLocationDescription, } from "entities/DataTree/actionTriggers"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; @@ -7,13 +8,19 @@ import { executeAppAction, TriggerMeta, } from "sagas/ActionExecution/ActionExecutionSagas"; -import { call, put, spawn, take } from "redux-saga/effects"; +import _find from "lodash/find"; +import { call, put, spawn, take, select } from "redux-saga/effects"; import { logActionExecutionError, TriggerFailureError, } from "sagas/ActionExecution/errorUtils"; import { setUserCurrentGeoLocation } from "actions/browserRequestActions"; import { Channel, channel } from "redux-saga"; +import { getEchartWidget } from "selectors/widgetSelectors"; +import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; +import { ActionValidationError } from "sagas/ActionExecution/errorUtils"; +import { ActionTriggerType } from "entities/DataTree/actionTriggers"; +import { getType, Types } from "utils/TypeHelpers"; // Making the getCurrentPosition call in a promise fashion const getUserLocation = (options?: PositionOptions) => @@ -128,6 +135,56 @@ export function* getCurrentLocationSaga( } } +export function* getEchartSaga( + actionPayload: EchartAction["payload"], + eventType: EventType, + triggerMeta: TriggerMeta, +) { + const { widgetName, funcName, options } = actionPayload; + const echartWidgets: { + [widgetId: string]: FlattenedWidgetProps; + } = yield select(getEchartWidget); + const _target = _find(echartWidgets, (it: any) => it.name === widgetName); + const _instance = _target?.instance; + if (typeof widgetName !== "string") { + throw new ActionValidationError( + ActionTriggerType.CALL_FUNC, + "widgetName", + Types.STRING, + getType(widgetName), + ); + } + if (typeof funcName !== "string") { + throw new ActionValidationError( + ActionTriggerType.CALL_FUNC, + "funcName", + Types.STRING, + getType(funcName), + ); + } + + try { + if (_instance) { + const res = _instance[funcName](options); + if (funcName === "getDom") { + logActionExecutionError( + "postmessage could not be cloned", + triggerMeta.source, + triggerMeta.triggerPropertyName, + ); + } + return [res]; + } + } catch (error) { + logActionExecutionError( + (error as Error).message, + triggerMeta.source, + triggerMeta.triggerPropertyName, + ); + return [error]; + } +} + let watchId: number | undefined; export function* watchCurrentLocation( actionPayload: WatchCurrentLocationDescription["payload"], diff --git a/app/client/src/sagas/ApplicationSagas.tsx b/app/client/src/sagas/ApplicationSagas.tsx index 082578a391..45a2588a42 100644 --- a/app/client/src/sagas/ApplicationSagas.tsx +++ b/app/client/src/sagas/ApplicationSagas.tsx @@ -360,6 +360,10 @@ export function* updateApplicationSaga( type: ReduxActionTypes.UPDATE_APPLICATION_SUCCESS, payload: response.data, }); + yield put({ + type: ReduxActionTypes.CURRENT_APPLICATION_CHART_THEME_UPDATE, + payload: response.data, + }); } if (request.currentApp) { yield put({ diff --git a/app/client/src/selectors/appThemingSelectors.tsx b/app/client/src/selectors/appThemingSelectors.tsx index c51e8aa9fa..e37f21c1d0 100644 --- a/app/client/src/selectors/appThemingSelectors.tsx +++ b/app/client/src/selectors/appThemingSelectors.tsx @@ -1,8 +1,10 @@ import { AppState } from "@appsmith/reducers"; +import { echartThemes } from "constants/EchartContants"; export enum AppThemingMode { APP_THEME_EDIT = "APP_THEME_EDIT", APP_THEME_SELECTION = "APP_THEME_SELECTION", + ECHART_THEME_SELECTION = "ECHART_THEME_SELECTION", } /** @@ -45,6 +47,19 @@ export const getSelectedAppThemeStylesheet = (state: AppState) => { return state.ui.appTheming.selectedTheme.stylesheet; }; +/** + * get the selected echart theme + * + * @param state + * @returns + */ +export const getSelectedEchartTheme = (state: AppState) => { + if (state.ui.applications.currentApplication?.chartTheme) { + return echartThemes[state.ui.applications.currentApplication?.chartTheme]; + } + return echartThemes["default"]; +}; + /** * get the preview theme or selected theme * diff --git a/app/client/src/selectors/widgetSelectors.ts b/app/client/src/selectors/widgetSelectors.ts index 9f631f825a..758c09551f 100644 --- a/app/client/src/selectors/widgetSelectors.ts +++ b/app/client/src/selectors/widgetSelectors.ts @@ -1,5 +1,6 @@ import { createSelector } from "reselect"; import { AppState } from "@appsmith/reducers"; +import * as echarts from "echarts"; import { CanvasWidgetsReduxState, FlattenedWidgetProps, @@ -43,6 +44,24 @@ export const getModalDropdownList = createSelector( }, ); +export const getEchartWidget = createSelector(getCanvasWidgets, (widgets) => { + const echartWidgets = Object.values(widgets).filter( + (widget: FlattenedWidgetProps) => widget.type === "ECHART_WIDGET", + ); + if (echartWidgets.length === 0) return undefined; + return echartWidgets.map((widget: FlattenedWidgetProps) => { + const chartId = `${widget.widgetId}-echart-container`; + const c_dom: HTMLElement | null = document + ? document.getElementById(`${chartId}`) + : null; + return { + id: widget.widgetId, + name: widget.widgetName, + instance: c_dom ? echarts.getInstanceByDom(c_dom) : null, + }; + }); +}); + export const getNextModalName = createSelector( getExistingWidgetNames, (names) => { diff --git a/app/client/src/theme/echart/chalk.json b/app/client/src/theme/echart/chalk.json new file mode 100644 index 0000000000..37b651f751 --- /dev/null +++ b/app/client/src/theme/echart/chalk.json @@ -0,0 +1,396 @@ + +{ + "color": [ + "#fc97af", + "#87f7cf", + "#f7f494", + "#72ccff", + "#f7c5a0", + "#d4a4eb", + "#d2f5a6", + "#76f2f2" + ], + "backgroundColor": "rgba(41,52,65,1)", + "textStyle": {}, + "title": { + "textStyle": { + "color": "#ffffff" + }, + "subtextStyle": { + "color": "#dddddd" + } + }, + "line": { + "itemStyle": { + "borderWidth": "4" + }, + "lineStyle": { + "width": "3" + }, + "symbolSize": "0", + "symbol": "circle", + "smooth": true + }, + "radar": { + "itemStyle": { + "borderWidth": "4" + }, + "lineStyle": { + "width": "3" + }, + "symbolSize": "0", + "symbol": "circle", + "smooth": true + }, + "bar": { + "itemStyle": { + "barBorderWidth": 0, + "barBorderColor": "#ccc" + } + }, + "pie": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "scatter": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "boxplot": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "parallel": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "sankey": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "funnel": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "gauge": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "candlestick": { + "itemStyle": { + "color": "#fc97af", + "color0": "transparent", + "borderColor": "#fc97af", + "borderColor0": "#87f7cf", + "borderWidth": "2" + } + }, + "graph": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + }, + "lineStyle": { + "width": "1", + "color": "#ffffff" + }, + "symbolSize": "0", + "symbol": "circle", + "smooth": true, + "color": [ + "#fc97af", + "#87f7cf", + "#f7f494", + "#72ccff", + "#f7c5a0", + "#d4a4eb", + "#d2f5a6", + "#76f2f2" + ], + "label": { + "color": "#293441" + } + }, + "map": { + "itemStyle": { + "areaColor": "#f3f3f3", + "borderColor": "#999999", + "borderWidth": 0.5 + }, + "label": { + "color": "#893448" + }, + "emphasis": { + "itemStyle": { + "areaColor": "rgba(255,178,72,1)", + "borderColor": "#eb8146", + "borderWidth": 1 + }, + "label": { + "color": "rgb(137,52,72)" + } + } + }, + "geo": { + "itemStyle": { + "areaColor": "#f3f3f3", + "borderColor": "#999999", + "borderWidth": 0.5 + }, + "label": { + "color": "#893448" + }, + "emphasis": { + "itemStyle": { + "areaColor": "rgba(255,178,72,1)", + "borderColor": "#eb8146", + "borderWidth": 1 + }, + "label": { + "color": "rgb(137,52,72)" + } + } + }, + "categoryAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#666666" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#aaaaaa" + }, + "splitLine": { + "show": false, + "lineStyle": { + "color": [ + "#e6e6e6" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.05)", + "rgba(200,200,200,0.02)" + ] + } + } + }, + "valueAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#666666" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#aaaaaa" + }, + "splitLine": { + "show": false, + "lineStyle": { + "color": [ + "#e6e6e6" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.05)", + "rgba(200,200,200,0.02)" + ] + } + } + }, + "logAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#666666" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#aaaaaa" + }, + "splitLine": { + "show": false, + "lineStyle": { + "color": [ + "#e6e6e6" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.05)", + "rgba(200,200,200,0.02)" + ] + } + } + }, + "timeAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#666666" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#aaaaaa" + }, + "splitLine": { + "show": false, + "lineStyle": { + "color": [ + "#e6e6e6" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.05)", + "rgba(200,200,200,0.02)" + ] + } + } + }, + "toolbox": { + "iconStyle": { + "borderColor": "#999999" + }, + "emphasis": { + "iconStyle": { + "borderColor": "#666666" + } + } + }, + "legend": { + "textStyle": { + "color": "#999999" + } + }, + "tooltip": { + "axisPointer": { + "lineStyle": { + "color": "#cccccc", + "width": 1 + }, + "crossStyle": { + "color": "#cccccc", + "width": 1 + } + } + }, + "timeline": { + "lineStyle": { + "color": "#87f7cf", + "width": 1 + }, + "itemStyle": { + "color": "#87f7cf", + "borderWidth": 1 + }, + "controlStyle": { + "color": "#87f7cf", + "borderColor": "#87f7cf", + "borderWidth": 0.5 + }, + "checkpointStyle": { + "color": "#fc97af", + "borderColor": "#fc97af" + }, + "label": { + "color": "#87f7cf" + }, + "emphasis": { + "itemStyle": { + "color": "#f7f494" + }, + "controlStyle": { + "color": "#87f7cf", + "borderColor": "#87f7cf", + "borderWidth": 0.5 + }, + "label": { + "color": "#87f7cf" + } + } + }, + "visualMap": { + "color": [ + "#fc97af", + "#87f7cf" + ] + }, + "dataZoom": { + "backgroundColor": "rgba(255,255,255,0)", + "dataBackgroundColor": "rgba(114,204,255,1)", + "fillerColor": "rgba(114,204,255,0.2)", + "handleColor": "#72ccff", + "handleSize": "100%", + "textStyle": { + "color": "#333333" + } + }, + "markPoint": { + "label": { + "color": "#293441" + }, + "emphasis": { + "label": { + "color": "#293441" + } + } + } +} diff --git a/app/client/src/theme/echart/macarons.json b/app/client/src/theme/echart/macarons.json new file mode 100644 index 0000000000..a851e5cc53 --- /dev/null +++ b/app/client/src/theme/echart/macarons.json @@ -0,0 +1,420 @@ + +{ + "color": [ + "#2ec7c9", + "#b6a2de", + "#5ab1ef", + "#ffb980", + "#d87a80", + "#8d98b3", + "#e5cf0d", + "#97b552", + "#95706d", + "#dc69aa", + "#07a2a4", + "#9a7fd1", + "#588dd5", + "#f5994e", + "#c05050", + "#59678c", + "#c9ab00", + "#7eb00a", + "#6f5553", + "#c14089" + ], + "backgroundColor": "rgba(0,0,0,0)", + "textStyle": {}, + "title": { + "textStyle": { + "color": "#008acd" + }, + "subtextStyle": { + "color": "#aaaaaa" + } + }, + "line": { + "itemStyle": { + "borderWidth": 1 + }, + "lineStyle": { + "width": 2 + }, + "symbolSize": 3, + "symbol": "emptyCircle", + "smooth": true + }, + "radar": { + "itemStyle": { + "borderWidth": 1 + }, + "lineStyle": { + "width": 2 + }, + "symbolSize": 3, + "symbol": "emptyCircle", + "smooth": true + }, + "bar": { + "itemStyle": { + "barBorderWidth": 0, + "barBorderColor": "#ccc" + } + }, + "pie": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "scatter": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "boxplot": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "parallel": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "sankey": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "funnel": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "gauge": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "candlestick": { + "itemStyle": { + "color": "#d87a80", + "color0": "#2ec7c9", + "borderColor": "#d87a80", + "borderColor0": "#2ec7c9", + "borderWidth": 1 + } + }, + "graph": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + }, + "lineStyle": { + "width": 1, + "color": "#aaaaaa" + }, + "symbolSize": 3, + "symbol": "emptyCircle", + "smooth": true, + "color": [ + "#2ec7c9", + "#b6a2de", + "#5ab1ef", + "#ffb980", + "#d87a80", + "#8d98b3", + "#e5cf0d", + "#97b552", + "#95706d", + "#dc69aa", + "#07a2a4", + "#9a7fd1", + "#588dd5", + "#f5994e", + "#c05050", + "#59678c", + "#c9ab00", + "#7eb00a", + "#6f5553", + "#c14089" + ], + "label": { + "color": "#eeeeee" + } + }, + "map": { + "itemStyle": { + "areaColor": "#dddddd", + "borderColor": "#eeeeee", + "borderWidth": 0.5 + }, + "label": { + "color": "#d87a80" + }, + "emphasis": { + "itemStyle": { + "areaColor": "rgba(254,153,78,1)", + "borderColor": "#444", + "borderWidth": 1 + }, + "label": { + "color": "rgb(100,0,0)" + } + } + }, + "geo": { + "itemStyle": { + "areaColor": "#dddddd", + "borderColor": "#eeeeee", + "borderWidth": 0.5 + }, + "label": { + "color": "#d87a80" + }, + "emphasis": { + "itemStyle": { + "areaColor": "rgba(254,153,78,1)", + "borderColor": "#444", + "borderWidth": 1 + }, + "label": { + "color": "rgb(100,0,0)" + } + } + }, + "categoryAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#008acd" + } + }, + "axisTick": { + "show": true, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#333" + }, + "splitLine": { + "show": false, + "lineStyle": { + "color": [ + "#eee" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.3)", + "rgba(200,200,200,0.3)" + ] + } + } + }, + "valueAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#008acd" + } + }, + "axisTick": { + "show": true, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#333" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": [ + "#eee" + ] + } + }, + "splitArea": { + "show": true, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.3)", + "rgba(200,200,200,0.3)" + ] + } + } + }, + "logAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#008acd" + } + }, + "axisTick": { + "show": true, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#333" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": [ + "#eee" + ] + } + }, + "splitArea": { + "show": true, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.3)", + "rgba(200,200,200,0.3)" + ] + } + } + }, + "timeAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#008acd" + } + }, + "axisTick": { + "show": true, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#333" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": [ + "#eee" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.3)", + "rgba(200,200,200,0.3)" + ] + } + } + }, + "toolbox": { + "iconStyle": { + "borderColor": "#2ec7c9" + }, + "emphasis": { + "iconStyle": { + "borderColor": "#18a4a6" + } + } + }, + "legend": { + "textStyle": { + "color": "#333333" + } + }, + "tooltip": { + "axisPointer": { + "lineStyle": { + "color": "#008acd", + "width": "1" + }, + "crossStyle": { + "color": "#008acd", + "width": "1" + } + } + }, + "timeline": { + "lineStyle": { + "color": "#008acd", + "width": 1 + }, + "itemStyle": { + "color": "#008acd", + "borderWidth": 1 + }, + "controlStyle": { + "color": "#008acd", + "borderColor": "#008acd", + "borderWidth": 0.5 + }, + "checkpointStyle": { + "color": "#2ec7c9", + "borderColor": "#2ec7c9" + }, + "label": { + "color": "#008acd" + }, + "emphasis": { + "itemStyle": { + "color": "#a9334c" + }, + "controlStyle": { + "color": "#008acd", + "borderColor": "#008acd", + "borderWidth": 0.5 + }, + "label": { + "color": "#008acd" + } + } + }, + "visualMap": { + "color": [ + "#5ab1ef", + "#e0ffff" + ] + }, + "dataZoom": { + "backgroundColor": "rgba(47,69,84,0)", + "dataBackgroundColor": "#efefff", + "fillerColor": "rgba(182,162,222,0.2)", + "handleColor": "#008acd", + "handleSize": "100%", + "textStyle": { + "color": "#333333" + } + }, + "markPoint": { + "label": { + "color": "#eeeeee" + }, + "emphasis": { + "label": { + "color": "#eeeeee" + } + } + } +} diff --git a/app/client/src/theme/echart/walden.json b/app/client/src/theme/echart/walden.json new file mode 100644 index 0000000000..2549c910b9 --- /dev/null +++ b/app/client/src/theme/echart/walden.json @@ -0,0 +1,392 @@ + +{ + "color": [ + "#3fb1e3", + "#6be6c1", + "#626c91", + "#a0a7e6", + "#c4ebad", + "#96dee8" + ], + "backgroundColor": "rgba(252,252,252,0)", + "textStyle": {}, + "title": { + "textStyle": { + "color": "#666666" + }, + "subtextStyle": { + "color": "#999999" + } + }, + "line": { + "itemStyle": { + "borderWidth": "2" + }, + "lineStyle": { + "width": "3" + }, + "symbolSize": "8", + "symbol": "emptyCircle", + "smooth": false + }, + "radar": { + "itemStyle": { + "borderWidth": "2" + }, + "lineStyle": { + "width": "3" + }, + "symbolSize": "8", + "symbol": "emptyCircle", + "smooth": false + }, + "bar": { + "itemStyle": { + "barBorderWidth": 0, + "barBorderColor": "#ccc" + } + }, + "pie": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "scatter": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "boxplot": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "parallel": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "sankey": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "funnel": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "gauge": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "candlestick": { + "itemStyle": { + "color": "#e6a0d2", + "color0": "transparent", + "borderColor": "#e6a0d2", + "borderColor0": "#3fb1e3", + "borderWidth": "2" + } + }, + "graph": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + }, + "lineStyle": { + "width": "1", + "color": "#cccccc" + }, + "symbolSize": "8", + "symbol": "emptyCircle", + "smooth": false, + "color": [ + "#3fb1e3", + "#6be6c1", + "#626c91", + "#a0a7e6", + "#c4ebad", + "#96dee8" + ], + "label": { + "color": "#ffffff" + } + }, + "map": { + "itemStyle": { + "areaColor": "#eeeeee", + "borderColor": "#aaaaaa", + "borderWidth": 0.5 + }, + "label": { + "color": "#ffffff" + }, + "emphasis": { + "itemStyle": { + "areaColor": "rgba(63,177,227,0.25)", + "borderColor": "#3fb1e3", + "borderWidth": 1 + }, + "label": { + "color": "#3fb1e3" + } + } + }, + "geo": { + "itemStyle": { + "areaColor": "#eeeeee", + "borderColor": "#aaaaaa", + "borderWidth": 0.5 + }, + "label": { + "color": "#ffffff" + }, + "emphasis": { + "itemStyle": { + "areaColor": "rgba(63,177,227,0.25)", + "borderColor": "#3fb1e3", + "borderWidth": 1 + }, + "label": { + "color": "#3fb1e3" + } + } + }, + "categoryAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#999999" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": [ + "#eeeeee" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.05)", + "rgba(200,200,200,0.02)" + ] + } + } + }, + "valueAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#999999" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": [ + "#eeeeee" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.05)", + "rgba(200,200,200,0.02)" + ] + } + } + }, + "logAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#999999" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": [ + "#eeeeee" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.05)", + "rgba(200,200,200,0.02)" + ] + } + } + }, + "timeAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#999999" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": [ + "#eeeeee" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.05)", + "rgba(200,200,200,0.02)" + ] + } + } + }, + "toolbox": { + "iconStyle": { + "borderColor": "#999999" + }, + "emphasis": { + "iconStyle": { + "borderColor": "#666666" + } + } + }, + "legend": { + "textStyle": { + "color": "#999999" + } + }, + "tooltip": { + "axisPointer": { + "lineStyle": { + "color": "#cccccc", + "width": 1 + }, + "crossStyle": { + "color": "#cccccc", + "width": 1 + } + } + }, + "timeline": { + "lineStyle": { + "color": "#626c91", + "width": 1 + }, + "itemStyle": { + "color": "#626c91", + "borderWidth": 1 + }, + "controlStyle": { + "color": "#626c91", + "borderColor": "#626c91", + "borderWidth": 0.5 + }, + "checkpointStyle": { + "color": "#3fb1e3", + "borderColor": "#3fb1e3" + }, + "label": { + "color": "#626c91" + }, + "emphasis": { + "itemStyle": { + "color": "#626c91" + }, + "controlStyle": { + "color": "#626c91", + "borderColor": "#626c91", + "borderWidth": 0.5 + }, + "label": { + "color": "#626c91" + } + } + }, + "visualMap": { + "color": [ + "#2a99c9", + "#afe8ff" + ] + }, + "dataZoom": { + "backgroundColor": "rgba(255,255,255,0)", + "dataBackgroundColor": "rgba(222,222,222,1)", + "fillerColor": "rgba(114,230,212,0.25)", + "handleColor": "#cccccc", + "handleSize": "100%", + "textStyle": { + "color": "#999999" + } + }, + "markPoint": { + "label": { + "color": "#ffffff" + }, + "emphasis": { + "label": { + "color": "#ffffff" + } + } + } +} diff --git a/app/client/src/theme/echart/westeros.json b/app/client/src/theme/echart/westeros.json new file mode 100644 index 0000000000..310990e57f --- /dev/null +++ b/app/client/src/theme/echart/westeros.json @@ -0,0 +1,393 @@ + +{ + "color": [ + "#516b91", + "#59c4e6", + "#edafda", + "#93b7e3", + "#a5e7f0", + "#cbb0e3" + ], + "backgroundColor": "rgba(0,0,0,0)", + "textStyle": {}, + "title": { + "textStyle": { + "color": "#516b91" + }, + "subtextStyle": { + "color": "#93b7e3" + } + }, + "line": { + "itemStyle": { + "borderWidth": "2" + }, + "lineStyle": { + "width": "2" + }, + "symbolSize": "6", + "symbol": "emptyCircle", + "smooth": true + }, + "radar": { + "itemStyle": { + "borderWidth": "2" + }, + "lineStyle": { + "width": "2" + }, + "symbolSize": "6", + "symbol": "emptyCircle", + "smooth": true + }, + "bar": { + "itemStyle": { + "barBorderWidth": 0, + "barBorderColor": "#ccc" + } + }, + "pie": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "scatter": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "boxplot": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "parallel": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "sankey": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "funnel": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "gauge": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "candlestick": { + "itemStyle": { + "color": "#edafda", + "color0": "transparent", + "borderColor": "#d680bc", + "borderColor0": "#8fd3e8", + "borderWidth": "2" + } + }, + "graph": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + }, + "lineStyle": { + "width": 1, + "color": "#aaaaaa" + }, + "symbolSize": "6", + "symbol": "emptyCircle", + "smooth": true, + "color": [ + "#516b91", + "#59c4e6", + "#edafda", + "#93b7e3", + "#a5e7f0", + "#cbb0e3" + ], + "label": { + "color": "#eeeeee" + } + }, + "map": { + "itemStyle": { + "areaColor": "#f3f3f3", + "borderColor": "#516b91", + "borderWidth": 0.5 + }, + "label": { + "color": "#000" + }, + "emphasis": { + "itemStyle": { + "areaColor": "#a5e7f0", + "borderColor": "#516b91", + "borderWidth": 1 + }, + "label": { + "color": "#516b91" + } + } + }, + "geo": { + "itemStyle": { + "areaColor": "#f3f3f3", + "borderColor": "#516b91", + "borderWidth": 0.5 + }, + "label": { + "color": "#000" + }, + "emphasis": { + "itemStyle": { + "areaColor": "#a5e7f0", + "borderColor": "#516b91", + "borderWidth": 1 + }, + "label": { + "color": "#516b91" + } + } + }, + "categoryAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#999999" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": [ + "#eeeeee" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.05)", + "rgba(200,200,200,0.02)" + ] + } + } + }, + "valueAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#999999" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": [ + "#eeeeee" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.05)", + "rgba(200,200,200,0.02)" + ] + } + } + }, + "logAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#999999" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": [ + "#eeeeee" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.05)", + "rgba(200,200,200,0.02)" + ] + } + } + }, + "timeAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#999999" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": [ + "#eeeeee" + ] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": [ + "rgba(250,250,250,0.05)", + "rgba(200,200,200,0.02)" + ] + } + } + }, + "toolbox": { + "iconStyle": { + "borderColor": "#999999" + }, + "emphasis": { + "iconStyle": { + "borderColor": "#666666" + } + } + }, + "legend": { + "textStyle": { + "color": "#999999" + } + }, + "tooltip": { + "axisPointer": { + "lineStyle": { + "color": "#cccccc", + "width": 1 + }, + "crossStyle": { + "color": "#cccccc", + "width": 1 + } + } + }, + "timeline": { + "lineStyle": { + "color": "#8fd3e8", + "width": 1 + }, + "itemStyle": { + "color": "#8fd3e8", + "borderWidth": 1 + }, + "controlStyle": { + "color": "#8fd3e8", + "borderColor": "#8fd3e8", + "borderWidth": 0.5 + }, + "checkpointStyle": { + "color": "#8fd3e8", + "borderColor": "#8a7ca8" + }, + "label": { + "color": "#8fd3e8" + }, + "emphasis": { + "itemStyle": { + "color": "#8fd3e8" + }, + "controlStyle": { + "color": "#8fd3e8", + "borderColor": "#8fd3e8", + "borderWidth": 0.5 + }, + "label": { + "color": "#8fd3e8" + } + } + }, + "visualMap": { + "color": [ + "#516b91", + "#59c4e6", + "#a5e7f0" + ] + }, + "dataZoom": { + "backgroundColor": "rgba(0,0,0,0)", + "dataBackgroundColor": "rgba(255,255,255,0.3)", + "fillerColor": "rgba(167,183,204,0.4)", + "handleColor": "#a7b7cc", + "handleSize": "100%", + "textStyle": { + "color": "#333" + } + }, + "markPoint": { + "label": { + "color": "#eeeeee" + }, + "emphasis": { + "label": { + "color": "#eeeeee" + } + } + } +} diff --git a/app/client/src/utils/AppUtils.ts b/app/client/src/utils/AppUtils.ts index afb15a0fbf..1c9d28e43f 100644 --- a/app/client/src/utils/AppUtils.ts +++ b/app/client/src/utils/AppUtils.ts @@ -26,3 +26,21 @@ const getEnvLogLevel = (configLevel: LogLevelDesc): LogLevelDesc => { } return logLevel; }; + +/** + * read json data from json url + * @param blobUrl + * @returns + */ +export async function readBlob(blobUrl: string): Promise { + const file = await fetch(blobUrl, { + referrer: "", + }).then((r) => r.blob()); + return new Promise((resolve) => { + const reader = new FileReader(); + reader.readAsText(file); + reader.onloadend = () => { + resolve(reader.result); + }; + }); +} diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts index ef1e6fae84..9295822641 100644 --- a/app/client/src/utils/DynamicBindingUtils.ts +++ b/app/client/src/utils/DynamicBindingUtils.ts @@ -2,6 +2,7 @@ import _, { get, isString, VERSION as lodashVersion } from "lodash"; import { DATA_BIND_REGEX } from "constants/BindingsConstants"; import { Action } from "entities/Action"; import moment from "moment-timezone"; +import * as echarts from "echarts"; import { WidgetProps } from "widgets/BaseWidget"; import parser from "fast-xml-parser"; @@ -187,6 +188,13 @@ export const extraLibraries: ExtraLibrary[] = [ docsURL: "https://github.com/digitalbazaar/forge", displayName: "forge", }, + { + accessor: "echarts", + lib: echarts, + version: "5.4.0", + docsURL: `https://echarts.apache.org/handbook/zh/how-to/animation/transition/`, + displayName: "echarts", + }, ]; /** * creates dynamic list of constants based on diff --git a/app/client/src/utils/WidgetRegistry.tsx b/app/client/src/utils/WidgetRegistry.tsx index 4e3aa44e09..12d7088fa1 100644 --- a/app/client/src/utils/WidgetRegistry.tsx +++ b/app/client/src/utils/WidgetRegistry.tsx @@ -23,6 +23,9 @@ import CanvasWidget, { import ChartWidget, { CONFIG as CHART_WIDGET_CONFIG, } from "widgets/ChartWidget"; +import EChartWidget, { + CONFIG as ECHART_WIDGET_CONFIG, +} from "widgets/EchartWidget"; import CheckboxGroupWidget, { CONFIG as CHECKBOX_GROUP_WIDGET_CONFIG, } from "widgets/CheckboxGroupWidget"; @@ -240,6 +243,7 @@ export const ALL_WIDGETS_AND_CONFIG = [ [TabsWidget, TABS_WIDGET_CONFIG], [ModalWidget, MODAL_WIDGET_CONFIG], [ChartWidget, CHART_WIDGET_CONFIG], + [EChartWidget, ECHART_WIDGET_CONFIG], [MapWidget, MAP_WIDGET_CONFIG], [RichTextEditorWidget, RICH_TEXT_EDITOR_WIDGET_CONFIG], [DatePickerWidget2, DATE_PICKER_WIDGET_2_CONFIG], diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts index 7c5971eac4..9ef4af94fd 100644 --- a/app/client/src/utils/autocomplete/EntityDefinitions.ts +++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts @@ -39,6 +39,10 @@ export const entityDefinitions = { watchPosition: "fn(options: object) -> void", clearWatch: "fn() -> void", }, + echartInstance: { + callFunc: + "fn(widgetName: string, funcName: string, options?: any) -> +Promise[:t=[!0..:t]]", + }, }; } return generatedTypeDef; @@ -350,6 +354,20 @@ export const entityDefinitions = { yAxisName: "string", selectedDataPoint: "$__chartDataPoint__$", }, + ECHART_WIDGET: { + "!doc": + "EChart widget is used to view the graphical representation of your data. Echart is the go-to widget for your data visualisation needs.", + "!url": "https://docs.appsmith.com/widget-reference/chart", + isVisible: true, + chartData: { + seriesName: "string", + // data: "[$__chartDataPoint__$]", + data: "any", + }, + xAxisName: "string", + yAxisName: "string", + selectedDataItem: "any", + }, FORM_WIDGET: (widget: any, extraDefsToDefine?: ExtraDef) => ({ "!doc": "Form is used to capture a set of data inputs from a user. Forms are used specifically because they reset the data inputs when a form is submitted and disable submission for invalid data inputs", diff --git a/app/client/src/utils/migrations/ChartWidgetReskinningMigrations.ts b/app/client/src/utils/migrations/ChartWidgetReskinningMigrations.ts index e9d0eeb9d8..fe3290d355 100644 --- a/app/client/src/utils/migrations/ChartWidgetReskinningMigrations.ts +++ b/app/client/src/utils/migrations/ChartWidgetReskinningMigrations.ts @@ -3,7 +3,7 @@ import { DSLWidget } from "widgets/constants"; export const migrateChartWidgetReskinningData = (currentDSL: DSLWidget) => { currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { - if (child.type === "CHART_WIDGET") { + if (child.type === "CHART_WIDGET" || child.type === "ECHART_WIDGET") { if ( !( child.hasOwnProperty("accentColor") && diff --git a/app/client/src/widgets/EchartWidget/component/index.tsx b/app/client/src/widgets/EchartWidget/component/index.tsx new file mode 100644 index 0000000000..4061ebbde4 --- /dev/null +++ b/app/client/src/widgets/EchartWidget/component/index.tsx @@ -0,0 +1,303 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import React, { useEffect, useRef, useState } from "react"; +import * as echarts from "echarts"; +import "echarts-gl"; +import * as _ from "lodash"; +import "echarts/extension/bmap/bmap"; +import macarons from "theme/echart/macarons.json"; +import chalk from "theme/echart/chalk.json"; +import walden from "theme/echart/walden.json"; +import westeros from "theme/echart/westeros.json"; +import { useSelector } from "react-redux"; +import { EchartTheme } from "entities/AppTheming"; +import { getSelectedEchartTheme } from "selectors/appThemingSelectors"; +import { LabelOrientation, AllChartData } from "../constants"; +import { NO_AXIS, ECHART_BASIC_OPTION, ECHART_TYPE_MAP } from "../constants"; +import { convertStringFunciton } from "../widget/helper"; +import { readBlob } from "utils/AppUtils"; +import { message } from "antd"; + +interface pointType { + name: string | number; + value: number; +} + +function customizer(objValue: any, srcValue: any) { + if (_.isArray(objValue)) { + return objValue.concat(srcValue); + } +} +const DARK_THEMES = ["dark", "chalk", "transparent"]; + +function EchartComponent(props: EchartComponentProps) { + const echartRef = useRef(); + const echartInstance = useRef(); + const selectedEchartTheme = useSelector(getSelectedEchartTheme); + const objectRef = useRef(); + const [preChartTheme, setCurrentChart] = useState(); + + const chartContainerId = props.widgetId + "-echart-container"; + + const { + onDataPointClick, + chartType, + chartName, + xAxisName, + yAxisName, + chartData, + customEchartConfig, + borderRadius, + boxShadow, + backgroundColor, + registerMapJsonUrl, + registerMapName, + } = props; + + const handleEvent = () => { + const _echartInstance = echartInstance.current; + const { onDataPointClick } = props; + if (onDataPointClick && _echartInstance) { + _echartInstance.on("click", (evt: any) => { + onDataPointClick(evt); + }); + } + }; + + const registerEchartTheme = (theme: string | undefined) => { + switch (theme) { + case "macarons": + echarts.registerTheme("macarons", macarons); + break; + case "chalk": + echarts.registerTheme("chalk", chalk); + break; + case "westeros": + echarts.registerTheme("westeros", westeros); + break; + case "walden": + echarts.registerTheme("walden", walden); + break; + default: + break; + } + }; + + const registerMap = () => { + if (registerMapName && registerMapJsonUrl && echartInstance.current) { + if (echarts.getMap(registerMapName)) { + // already register + } else { + readBlob(registerMapJsonUrl) + .then((resData) => { + echarts.registerMap(registerMapName, resData); + }) + .catch(() => { + message.warn("地图注册失败"); + }); + } + } + }; + + useEffect(() => { + const { onInstance } = props; + onInstance && echartInstance.current && onInstance(echartInstance.current); + }, [echartInstance.current]); + + // props更新调用 + useEffect(() => { + if (!chartData) { + return; + } + if (!echartInstance.current && echartRef.current) { + echartInstance.current = echarts.init(echartRef?.current, "default"); + } + /*register theme*/ + const registerTheme: EchartTheme | undefined = selectedEchartTheme; + if (preChartTheme !== registerTheme && registerTheme) { + echartInstance.current.dispose(); + registerEchartTheme(registerTheme.themeKey); + echartInstance.current = echarts.init( + echartRef?.current, + registerTheme.themeKey, + ); + } + + if (echartInstance.current) { + // 清空一下实例 + echartInstance.current.clear(); + const _seriesD = getSeriesAndXaxisData(); + let newOption: any = { + title: { + text: chartName, + }, + xAxis: { + name: xAxisName, + data: _seriesD.xAxis, + }, + yAxis: { + name: yAxisName, + }, + darkMode: true, + series: _seriesD.series, + }; + if (backgroundColor) { + _.set(newOption, "backgroundColor", backgroundColor); + } + if ( + selectedEchartTheme && + DARK_THEMES.includes(selectedEchartTheme.themeKey) + ) { + _.set(newOption, "darkMode", true); + } + + if (chartType === "CUSTOM_CHART" && customEchartConfig) { + newOption = customEchartConfig; + } + + _.mergeWith(newOption, ECHART_BASIC_OPTION, customizer); + + /** + * 如果在opts.replaceMerge里指定组件类型,这类组件会进行替换合并。 + * 否则,会进行普通合并 + */ + const replaceMerge: string[] = []; + + if (NO_AXIS[chartType]) { + replaceMerge.push("xAxis", "yAxis"); + _.set(newOption, "yAxis.show", false); + _.set(newOption, "xAxis.show", false); + } else { + _.set(newOption, "yAxis.show", true); + _.set(newOption, "xAxis.show", true); + } + const newOptions: any = convertStringFunciton(newOption); + echartInstance.current.setOption(newOptions, { replaceMerge }); + setCurrentChart(selectedEchartTheme); + } + }, [ + chartType, + chartName, + xAxisName, + yAxisName, + chartData, + customEchartConfig, + backgroundColor, + selectedEchartTheme, + registerMapName, + registerMapJsonUrl, + ]); + + useEffect(() => { + registerMap(); + }, [registerMapJsonUrl, registerMapName]); + + // 点击事件handler + useEffect(() => { + return () => { + echartInstance.current && + echartInstance.current.off("click", onDataPointClick); + }; + }, [onDataPointClick]); + + // bind event + useEffect(() => { + if (echartRef.current) { + handleEvent(); + } + registerMap(); + }, []); + + const getSeriesAndXaxisData = () => { + const series: any[] = []; + const xAxis: any[] = []; + _.each(chartData, (c_data, c_key) => { + if (chartType === "PIE_CHART") { + series.push({ + ...c_data, + id: c_key, + name: c_data.seriesName, + type: ECHART_TYPE_MAP[chartType], + data: c_data.data.map((it: pointType) => { + return { + name: it.name, + value: it.value, + }; + }), + }); + } else { + series.push({ + ...c_data, + id: c_key, + name: c_data.seriesName, + type: ECHART_TYPE_MAP[chartType], + data: c_data.data.map((it: pointType) => { + !xAxis.includes(it.name) && xAxis.push(it.name); + return it.value; + }), + }); + } + }); + return { xAxis, series }; + }; + + useEffect(() => { + objectRef.current.contentDocument.defaultView.addEventListener( + "resize", + () => { + echartInstance.current.resize(); + }, + ); + }, [objectRef.current]); + + const style = { + borderRadius, + boxShadow, + }; + return ( +
+
+ {/* 监听组件大小 */} +