From 0368f5dbc3560c4328afab495dea08a57d295cc2 Mon Sep 17 00:00:00 2001 From: Abhinav Jha Date: Wed, 2 Sep 2020 11:37:18 +0530 Subject: [PATCH 1/4] Fix: Datasource name not getting updated from the entity explorer (#478) --- app/client/src/actions/datasourceActions.ts | 7 ++----- .../Editor/Explorer/Datasources/DatasourceEntity.tsx | 11 ++++------- app/client/src/sagas/DatasourcesSagas.ts | 12 ++---------- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/app/client/src/actions/datasourceActions.ts b/app/client/src/actions/datasourceActions.ts index 0ea4e64793..50ab33bdb4 100644 --- a/app/client/src/actions/datasourceActions.ts +++ b/app/client/src/actions/datasourceActions.ts @@ -15,13 +15,10 @@ export const createDatasourceFromForm = (payload: CreateDatasourceConfig) => { }; }; -export const updateDatasource = ( - payload: Datasource, - reinitializeForm?: boolean, -) => { +export const updateDatasource = (payload: Datasource) => { return { type: ReduxActionTypes.UPDATE_DATASOURCE_INIT, - payload: { datasource: payload, reinitializeForm: !!reinitializeForm }, + payload, }; }; diff --git a/app/client/src/pages/Editor/Explorer/Datasources/DatasourceEntity.tsx b/app/client/src/pages/Editor/Explorer/Datasources/DatasourceEntity.tsx index 1abe081bb0..2eca7d8ee0 100644 --- a/app/client/src/pages/Editor/Explorer/Datasources/DatasourceEntity.tsx +++ b/app/client/src/pages/Editor/Explorer/Datasources/DatasourceEntity.tsx @@ -7,7 +7,7 @@ import { ExplorerURLParams, getDatasourceIdFromURL } from "../helpers"; import Entity, { EntityClassNames } from "../Entity"; import { DATA_SOURCES_EDITOR_ID_URL } from "constants/routes"; import history from "utils/history"; -import { updateDatasource } from "actions/datasourceActions"; +import { saveDatasourceName } from "actions/datasourceActions"; type ExplorerDatasourceEntityProps = { datasource: Datasource; @@ -32,12 +32,9 @@ export const ExplorerDatasourceEntity = ( const datasourceIdFromURL = getDatasourceIdFromURL(); const active = datasourceIdFromURL === props.datasource.id; - const updateDatasourceName = useCallback( - (id: string, name: string) => { - return updateDatasource({ ...props.datasource, name: name }, active); - }, - [props.datasource, active], - ); + const updateDatasourceName = (id: string, name: string) => + saveDatasourceName({ id, name }); + return ( , -) { +function* updateDatasourceSaga(actionPayload: ReduxAction) { try { - const datasourcePayload = _.omit(actionPayload.payload.datasource, "name"); + const datasourcePayload = _.omit(actionPayload.payload, "name"); const response: GenericApiResponse = yield DatasourcesApi.updateDatasource( datasourcePayload, @@ -184,9 +179,6 @@ function* updateDatasourceSaga( id: response.data.id, }, }); - if (actionPayload.payload.reinitializeForm) { - yield put(initialize(DATASOURCE_DB_FORM, datasourcePayload)); - } } } catch (error) { yield put({ From 83945c81a01e1cceaaebaba4d4b6d93c396c5f62 Mon Sep 17 00:00:00 2001 From: Abhinav Jha Date: Wed, 2 Sep 2020 11:37:34 +0530 Subject: [PATCH 2/4] Fix: Other page widget props are not shown on the entity explorer (#480) --- .../pages/Editor/Explorer/Actions/ActionEntity.tsx | 5 +++++ .../Editor/Explorer/Entity/EntityProperties.tsx | 14 +++++++++++--- .../src/pages/Editor/Explorer/Pages/PageEntity.tsx | 1 - .../pages/Editor/Explorer/Widgets/WidgetEntity.tsx | 11 ++++++----- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/app/client/src/pages/Editor/Explorer/Actions/ActionEntity.tsx b/app/client/src/pages/Editor/Explorer/Actions/ActionEntity.tsx index 61cd23c635..4bab144064 100644 --- a/app/client/src/pages/Editor/Explorer/Actions/ActionEntity.tsx +++ b/app/client/src/pages/Editor/Explorer/Actions/ActionEntity.tsx @@ -5,6 +5,8 @@ import history from "utils/history"; import { saveActionName } from "actions/actionActions"; import EntityProperties from "../Entity/EntityProperties"; import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory"; +import { ExplorerURLParams } from "../helpers"; +import { useParams } from "react-router"; const getUpdateActionNameReduxAction = (id: string, name: string) => { return saveActionName({ id, name }); @@ -21,6 +23,7 @@ type ExplorerActionEntityProps = { }; export const ExplorerActionEntity = memo((props: ExplorerActionEntityProps) => { + const { pageId } = useParams(); const switchToAction = useCallback(() => { props.url && history.push(props.url); }, [props.url]); @@ -51,7 +54,9 @@ export const ExplorerActionEntity = memo((props: ExplorerActionEntityProps) => { ); diff --git a/app/client/src/pages/Editor/Explorer/Entity/EntityProperties.tsx b/app/client/src/pages/Editor/Explorer/Entity/EntityProperties.tsx index 52efb355b2..ff367c25ba 100644 --- a/app/client/src/pages/Editor/Explorer/Entity/EntityProperties.tsx +++ b/app/client/src/pages/Editor/Explorer/Entity/EntityProperties.tsx @@ -14,11 +14,19 @@ import { evaluateDataTreeWithoutFunctions } from "selectors/dataTreeSelectors"; export const EntityProperties = (props: { entityType: ENTITY_TYPE; entityName: string; + isCurrentPage: boolean; step: number; + entity?: any; }) => { + let entity: any; const dataTree: DataTree = useSelector(evaluateDataTreeWithoutFunctions); - const entity: any = dataTree[props.entityName]; - if (!entity) return null; + if (props.isCurrentPage && dataTree[props.entityName]) { + entity = dataTree[props.entityName]; + } else if (props.entity) { + entity = props.entity; + } else { + return null; + } let config: any; let entityProperties: Array = []; @@ -42,7 +50,7 @@ export const EntityProperties = (props: { } return { propertyName: actionProperty, - entityName: entity.name, + entityName: props.entityName, value, step: props.step, }; diff --git a/app/client/src/pages/Editor/Explorer/Pages/PageEntity.tsx b/app/client/src/pages/Editor/Explorer/Pages/PageEntity.tsx index 06f0c5093d..a40a064f1f 100644 --- a/app/client/src/pages/Editor/Explorer/Pages/PageEntity.tsx +++ b/app/client/src/pages/Editor/Explorer/Pages/PageEntity.tsx @@ -49,7 +49,6 @@ export const ExplorerPageEntity = (props: ExplorerPageEntityProps) => { ); const icon = props.page.isDefault ? homePageIcon : pageIcon; - return ( { - const params = useParams(); + const { pageId } = useParams(); + const { navigateToWidget, isWidgetSelected } = useWidget( props.widgetProps.widgetId, props.widgetProps.type, @@ -139,7 +140,9 @@ export const WidgetEntity = memo((props: WidgetEntityProps) => { ); } @@ -162,15 +165,13 @@ export const WidgetEntity = memo((props: WidgetEntityProps) => { active={isWidgetSelected} entityId={props.widgetProps.widgetId} step={props.step} - updateEntityName={ - props.pageId === params?.pageId ? updateWidgetName : noop - } + updateEntityName={props.pageId === pageId ? updateWidgetName : noop} searchKeyword={props.searchKeyword} isDefaultExpanded={ (!!props.searchKeyword && !!props.widgetProps.children) || !!props.isDefaultExpanded } - contextMenu={props.pageId === params?.pageId && contextMenu} + contextMenu={props.pageId === pageId && contextMenu} > {children} From 70fea0e560bae27e54150be91a7935be1df3319e Mon Sep 17 00:00:00 2001 From: Abhinav Jha Date: Wed, 2 Sep 2020 12:14:01 +0530 Subject: [PATCH 3/4] Feature: Add widgets from explorer (#464) * Deprecate Navbar. Sidebar becomes a panelStack. It now has two panels, WidgetSidebar and Explorer Co-authored-by: Nandan --- .../ApiPaneTests/API_Mustache_spec.js | 3 - .../Applications/DuplicateApplication_spec.js | 1 - .../Binding/Bind_DatePicker_Text_spec.js | 3 - .../Binding/Bind_TableTextPagination_spec.js | 15 +-- .../Binding/Bind_tableApi_spec.js | 5 +- .../Smoke_TestSuite/Binding/ChartText.js | 2 - .../Smoke_TestSuite/Binding/TextTable.js | 4 - .../DisplayWidgets/Chart_spec.js | 1 - .../DisplayWidgets/Image_spec.js | 3 - .../DisplayWidgets/Table_spec.js | 1 - .../DisplayWidgets/Text_spec.js | 1 - .../DynamicInput/autocomplete_spec.js | 4 - .../Entity_Explorer_Widgets_spec.js | 1 - .../FormWidgets/Button_spec.js | 1 - .../FormWidgets/CheckBox_spec.js | 5 - .../FormWidgets/DatePicker_spec.js | 1 - .../FormWidgets/Dropdown_spec.js | 3 - .../FormWidgets/FilePicker_spec.js | 1 - .../FormWidgets/FormWidget_spec.js | 2 - .../Smoke_TestSuite/FormWidgets/Input_spec.js | 5 - .../Smoke_TestSuite/FormWidgets/Radio_spec.js | 3 - .../FormWidgets/RichTextEditor_spec.js | 1 - .../Smoke_TestSuite/LayoutWidgets/Tab_spec.js | 3 - app/client/cypress/support/commands.js | 4 +- .../components/editorComponents/Sidebar.tsx | 26 ++-- app/client/src/constants/DefaultTheme.tsx | 2 +- app/client/src/constants/Explorer.ts | 2 +- app/client/src/constants/messages.ts | 4 + app/client/src/constants/routes.ts | 13 -- .../Editor/Explorer/Actions/ActionsGroup.tsx | 2 +- .../Explorer/Datasources/DatasourcesGroup.tsx | 2 +- .../pages/Editor/Explorer/Entity/index.tsx | 8 +- .../pages/Editor/Explorer/ExplorerSearch.tsx | 8 +- .../Editor/Explorer/Pages/PageEntity.tsx | 8 +- .../pages/Editor/Explorer/Pages/PageGroup.tsx | 4 +- .../Editor/Explorer/Widgets/WidgetGroup.tsx | 15 +-- .../src/pages/Editor/Explorer/index.tsx | 19 ++- app/client/src/pages/Editor/MainContainer.tsx | 4 +- app/client/src/pages/Editor/Navbar.tsx | 85 ------------ app/client/src/pages/Editor/WidgetSidebar.tsx | 126 +++++++++++++++--- app/client/src/pages/Editor/index.tsx | 25 ++-- app/client/src/pages/Editor/routes.tsx | 7 +- 42 files changed, 192 insertions(+), 241 deletions(-) delete mode 100644 app/client/src/pages/Editor/Navbar.tsx diff --git a/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_Mustache_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_Mustache_spec.js index be79b8feac..806c3e89f4 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_Mustache_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_Mustache_spec.js @@ -9,12 +9,9 @@ describe("Moustache test Functionality", function() { cy.addDsl(dsl); }); it("Moustache test Functionality", function() { - //cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("textwidget"); cy.widgetText("Api", widgetsPage.textWidget, widgetsPage.textInputval); cy.testCodeMirror("/api/users/2"); - cy.NavigateToEntityExplorer(); - cy.wait(10000); cy.NavigateToAPI_Panel(); cy.log("Navigation to API Panel screen successful"); cy.CreateAPI("TestAPINew"); diff --git a/app/client/cypress/integration/Smoke_TestSuite/Applications/DuplicateApplication_spec.js b/app/client/cypress/integration/Smoke_TestSuite/Applications/DuplicateApplication_spec.js index c4edb6a992..75e4756c8e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Applications/DuplicateApplication_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Applications/DuplicateApplication_spec.js @@ -23,7 +23,6 @@ describe("Duplicate application", function() { .contains("Duplicate") .click({ force: true }); - cy.get(explorerlocators.entityExplorer).should("be.visible"); cy.wait("@getPage").should( "have.nested.property", "response.body.responseMeta.status", diff --git a/app/client/cypress/integration/Smoke_TestSuite/Binding/Bind_DatePicker_Text_spec.js b/app/client/cypress/integration/Smoke_TestSuite/Binding/Bind_DatePicker_Text_spec.js index 8a895531ab..2b763f76f5 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Binding/Bind_DatePicker_Text_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Binding/Bind_DatePicker_Text_spec.js @@ -16,7 +16,6 @@ describe("Binding the Datepicker and Text Widget", function() { /** * Bind DatePicker1 to Text for "selectedDate" */ - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("textwidget"); cy.testJsontext("text", "{{DatePicker1.selectedDate}}"); cy.get(commonlocators.editPropCrossButton).click(); @@ -50,7 +49,6 @@ describe("Binding the Datepicker and Text Widget", function() { }); it("DatePicker1-text: Change the date in DatePicker1 and Validate the same in text widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("textwidget"); /** @@ -108,7 +106,6 @@ describe("Binding the Datepicker and Text Widget", function() { /** * Bind the DatePicker1 and DatePicker2 along with hard coded text to Text widget */ - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("textwidget"); cy.testJsontext( "text", diff --git a/app/client/cypress/integration/Smoke_TestSuite/Binding/Bind_TableTextPagination_spec.js b/app/client/cypress/integration/Smoke_TestSuite/Binding/Bind_TableTextPagination_spec.js index 62fab2131c..9089c61aac 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Binding/Bind_TableTextPagination_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Binding/Bind_TableTextPagination_spec.js @@ -16,15 +16,14 @@ describe("Test Create Api and Bind to Table widget", function() { }); it("Table-Text, Validate Server Side Pagination of Paginate with Table Page No", function() { - cy.get(pages.widgetsEditor).click(); - cy.openPropertyPane("tablewidget"); + cy.SearchEntityandOpen("Table1"); /**Bind Api1 with Table widget */ cy.testJsontext("tabledata", "{{Api1.data.users}}"); cy.CheckWidgetProperties(commonlocators.serverSidePaginationCheckbox); /**Bind Table with Textwidget with selected row */ - cy.get(pages.widgetsEditor).click(); - cy.openPropertyPane("textwidget"); + cy.SearchEntityandOpen("Text1"); cy.testJsontext("text", "{{Table1.selectedRow.url}}"); + cy.SearchEntityandOpen("Table1"); cy.readTabledata("0", "0").then(tabData => { const tableData = tabData; localStorage.setItem("tableDataPage1", tableData); @@ -43,7 +42,6 @@ describe("Test Create Api and Bind to Table widget", function() { cy.get(commonlocators.rightArrowBtn).click({ force: true }); cy.validateToastMessage("done"); cy.ValidatePublishTableData("11"); - cy.get(publishPage.backToEditor).click({ force: true }); }); @@ -59,13 +57,12 @@ describe("Test Create Api and Bind to Table widget", function() { parseSpecialCharSequences: false, }); cy.WaitAutoSave(); - cy.get(pages.widgetsEditor).click(); - cy.openPropertyPane("textwidget"); + cy.SearchEntityandOpen("Text1"); + //cy.openPropertyPane("textwidget"); /** Bind the Table widget with Text widget*/ cy.testJsontext("text", "{{Table1.selectedRow.url}}"); cy.get(commonlocators.editPropCrossButton).click(); - cy.get(pages.widgetsEditor).click(); - cy.openPropertyPane("tablewidget"); + cy.SearchEntityandOpen("Table1"); cy.testJsontext("tabledata", "{{Api2.data.users}}"); cy.callApi("Api2"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/Binding/Bind_tableApi_spec.js b/app/client/cypress/integration/Smoke_TestSuite/Binding/Bind_tableApi_spec.js index f499396bb4..d833b0b01f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Binding/Bind_tableApi_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Binding/Bind_tableApi_spec.js @@ -27,9 +27,8 @@ describe("Test Create Api and Bind to Table widget", function() { }); it("Test_Validate the Api data is updated on Table widget", function() { - //cy.get(pages.pagesIcon).click({ force: true }); - cy.get(pages.widgetsEditor).click(); - cy.openPropertyPane("tablewidget"); + cy.SearchEntityandOpen("Table1"); + //cy.openPropertyPane("tablewidget"); cy.testJsontext("tabledata", "{{Api1.data}}"); cy.get(commonlocators.editPropCrossButton).click(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/Binding/ChartText.js b/app/client/cypress/integration/Smoke_TestSuite/Binding/ChartText.js index aa519de87f..5ebdc10a05 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Binding/ChartText.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Binding/ChartText.js @@ -9,7 +9,6 @@ describe("Text-Chart Binding Functionality", function() { cy.addDsl(dsl); }); it("Text-Chart Binding Functionality View", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("textwidget"); cy.testJsontext("text", JSON.stringify(this.data.chartInputValidate)); cy.get(commonlocators.TextInside).should( @@ -17,7 +16,6 @@ describe("Text-Chart Binding Functionality", function() { JSON.stringify(this.data.chartInputValidate), ); cy.closePropertyPane(); - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("chartwidget"); cy.get(viewWidgetsPage.chartType) .find(commonlocators.dropdownbuttonclick) diff --git a/app/client/cypress/integration/Smoke_TestSuite/Binding/TextTable.js b/app/client/cypress/integration/Smoke_TestSuite/Binding/TextTable.js index 5eff6c04da..96272e2cd9 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Binding/TextTable.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Binding/TextTable.js @@ -14,7 +14,6 @@ describe("Text-Table Binding Functionality", function() { cy.addDsl(dsl); }); it("Text-Table Binding Functionality For Id", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("tablewidget"); /** * @param(Index) Provide index value to select the row. @@ -43,7 +42,6 @@ describe("Text-Table Binding Functionality", function() { cy.get(publish.backToEditor) .first() .click(); - cy.get(pages.widgetsEditor).click(); cy.isSelectRow(2); cy.openPropertyPane("textwidget"); cy.testJsontext("text", "{{Table1.selectedRow.email}}"); @@ -68,7 +66,6 @@ describe("Text-Table Binding Functionality", function() { cy.get(publish.backToEditor) .first() .click(); - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("textwidget"); cy.testJsontext("text", "{{Table1.pageSize}}"); cy.get(commonlocators.TableRow) @@ -95,7 +92,6 @@ describe("Text-Table Binding Functionality", function() { * @param(Index) Provide index value to select the row. */ cy.isSelectRow(1); - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("textwidget"); cy.testJsontext("text", JSON.stringify(this.data.textfun)); /** diff --git a/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Chart_spec.js b/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Chart_spec.js index 0328351635..8f038e5490 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Chart_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Chart_spec.js @@ -10,7 +10,6 @@ describe("Chart Widget Functionality", function() { }); beforeEach(() => { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("chartwidget"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Image_spec.js b/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Image_spec.js index c35bc2f779..432f9b098b 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Image_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Image_spec.js @@ -10,7 +10,6 @@ describe("Image Widget Functionality", function() { }); it("Image Widget Functionality", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("imagewidget"); /** * @param{Text} Random Text @@ -43,7 +42,6 @@ describe("Image Widget Functionality", function() { }); it("Image Widget Functionality To Unchecked Visible Widget", function() { cy.get(publish.backToEditor).click(); - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("imagewidget"); cy.togglebarDisable(commonlocators.visibleCheckbox); cy.PublishtheApp(); @@ -51,7 +49,6 @@ describe("Image Widget Functionality", function() { cy.get(publish.backToEditor).click(); }); it("Image Widget Functionality To Check Visible Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("imagewidget"); cy.togglebar(commonlocators.visibleCheckbox); cy.PublishtheApp(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Table_spec.js b/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Table_spec.js index 216eedd243..9a92763333 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Table_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Table_spec.js @@ -10,7 +10,6 @@ describe("Table Widget Functionality", function() { }); it("Table Widget Functionality", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("tablewidget"); /** diff --git a/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Text_spec.js b/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Text_spec.js index f933a44a04..8abbd02993 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Text_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Text_spec.js @@ -10,7 +10,6 @@ describe("Text Widget Functionality", function() { }); beforeEach(() => { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("textwidget"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/DynamicInput/autocomplete_spec.js b/app/client/cypress/integration/Smoke_TestSuite/DynamicInput/autocomplete_spec.js index 3b6a72834f..aa590e0618 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/DynamicInput/autocomplete_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/DynamicInput/autocomplete_spec.js @@ -10,7 +10,6 @@ describe("Dynamic input autocomplete", () => { cy.addDsl(dsl); }); it("opens autocomplete for bindings", () => { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("buttonwidget"); cy.get(dynamicInputLocators.input) .first() @@ -57,14 +56,11 @@ describe("Dynamic input autocomplete", () => { }); it("opens current value popup", () => { // Test on widgets pane - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("buttonwidget"); cy.get(dynamicInputLocators.input) .first() .focus(); cy.assertEvaluatedValuePopup("string"); - - cy.NavigateToEntityExplorer(); // Test on api pane cy.NavigateToAPI_Panel(); cy.get(apiwidget.createapi).click({ force: true }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_Widgets_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_Widgets_spec.js index 2dd1ba83e5..71dd4c178e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_Widgets_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_Widgets_spec.js @@ -8,7 +8,6 @@ describe("Entity explorer tests related to widgets and validation", function() { }); it("Widget edit/delete/copy to clipboard validation", function() { - cy.NavigateToEntityExplorer(); cy.SearchEntityandOpen("Text1"); cy.get(explorer.collapse) .last() diff --git a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Button_spec.js b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Button_spec.js index 341bbd6cde..0586b1ec96 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Button_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Button_spec.js @@ -12,7 +12,6 @@ describe("Button Widget Functionality", function() { }); beforeEach(() => { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("buttonwidget"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/CheckBox_spec.js b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/CheckBox_spec.js index 1b90e5a8ca..62ac7139cf 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/CheckBox_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/CheckBox_spec.js @@ -10,7 +10,6 @@ describe("Checkbox Widget Functionality", function() { cy.addDsl(dsl); }); it("Checkbox Widget Functionality", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("checkboxwidget"); /** * @param{Text} Random Text @@ -45,7 +44,6 @@ describe("Checkbox Widget Functionality", function() { cy.get(publish.backToEditor).click(); }); it("Checkbox Functionality To Check Disabled Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("checkboxwidget"); cy.togglebar(commonlocators.Disablejs + " " + "input"); cy.PublishtheApp(); @@ -53,7 +51,6 @@ describe("Checkbox Widget Functionality", function() { cy.get(publish.backToEditor).click(); }); it("Checkbox Functionality To Check Enabled Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("checkboxwidget"); cy.togglebarDisable(commonlocators.Disablejs + " " + "input"); cy.PublishtheApp(); @@ -61,7 +58,6 @@ describe("Checkbox Widget Functionality", function() { cy.get(publish.backToEditor).click(); }); it("Checkbox Functionality To Unchecked Visible Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("checkboxwidget"); cy.togglebarDisable(commonlocators.visibleCheckbox); cy.PublishtheApp(); @@ -69,7 +65,6 @@ describe("Checkbox Widget Functionality", function() { cy.get(publish.backToEditor).click(); }); it("Checkbox Functionality To Check Visible Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("checkboxwidget"); cy.togglebar(commonlocators.visibleCheckbox); cy.PublishtheApp(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/DatePicker_spec.js b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/DatePicker_spec.js index 8226d22581..620f79dfbc 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/DatePicker_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/DatePicker_spec.js @@ -10,7 +10,6 @@ describe("DatePicker Widget Functionality", function() { }); beforeEach(() => { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("datepickerwidget"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Dropdown_spec.js b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Dropdown_spec.js index 8ce22e97e3..279bed8d5c 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Dropdown_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Dropdown_spec.js @@ -9,7 +9,6 @@ describe("Dropdown Widget Functionality", function() { cy.addDsl(dsl); }); it("Dropdown Widget Functionality", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("dropdownwidget"); /** * @param{Text} Random Text @@ -48,7 +47,6 @@ describe("Dropdown Widget Functionality", function() { cy.get(publish.backToEditor).click(); }); it("Dropdown Functionality To Unchecked Visible Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("dropdownwidget"); cy.togglebarDisable(commonlocators.visibleCheckbox); cy.PublishtheApp(); @@ -56,7 +54,6 @@ describe("Dropdown Widget Functionality", function() { cy.get(publish.backToEditor).click(); }); it("Dropdown Functionality To Check Visible Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("dropdownwidget"); cy.togglebar(commonlocators.visibleCheckbox); cy.PublishtheApp(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FilePicker_spec.js b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FilePicker_spec.js index a4c836c789..d52e69e302 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FilePicker_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FilePicker_spec.js @@ -7,7 +7,6 @@ describe("FilePicker Widget Functionality", function() { cy.addDsl(dsl); }); it("FilePicker Widget Functionality", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("filepickerwidget"); //Checking the edit props for FilePicker and also the properties of FilePicker widget diff --git a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FormWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FormWidget_spec.js index 404e09e62b..92d6ef913e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FormWidget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FormWidget_spec.js @@ -9,7 +9,6 @@ describe("Form Widget Functionality", function() { cy.addDsl(dsl); }); it("Form Widget Functionality", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("formwidget"); /** * @param{Text} Random Text @@ -45,7 +44,6 @@ describe("Form Widget Functionality", function() { }); it("Form Widget Functionality To Unchecked Visible Widget", function() { cy.get(publish.backToEditor).click(); - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("formwidget"); cy.togglebarDisable(commonlocators.visibleCheckbox); cy.PublishtheApp(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Input_spec.js b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Input_spec.js index c5bb1ca99f..9efd1ddfeb 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Input_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Input_spec.js @@ -9,7 +9,6 @@ describe("Input Widget Functionality", function() { cy.addDsl(dsl); }); it("Input Widget Functionality", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("inputwidget"); /** * @param{Text} Random Text @@ -65,7 +64,6 @@ describe("Input Widget Functionality", function() { cy.get(publish.backToEditor).click({ force: true }); }); it("Input Widget Functionality To Check Disabled Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("inputwidget"); cy.togglebar(commonlocators.Disablejs + " " + "input"); cy.PublishtheApp(); @@ -73,7 +71,6 @@ describe("Input Widget Functionality", function() { cy.get(publish.backToEditor).click({ force: true }); }); it("Input Widget Functionality To Check Enabled Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("inputwidget"); cy.togglebarDisable(commonlocators.Disablejs + " " + "input"); cy.PublishtheApp(); @@ -81,7 +78,6 @@ describe("Input Widget Functionality", function() { cy.get(publish.backToEditor).click({ force: true }); }); it("Input Functionality To Unchecked Visible Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("inputwidget"); cy.togglebarDisable(commonlocators.visibleCheckbox); cy.PublishtheApp(); @@ -89,7 +85,6 @@ describe("Input Widget Functionality", function() { cy.get(publish.backToEditor).click({ force: true }); }); it("Input Functionality To Check Visible Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("inputwidget"); cy.togglebar(commonlocators.visibleCheckbox); cy.PublishtheApp(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Radio_spec.js b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Radio_spec.js index f6b0cde75a..5d5d0a4d31 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Radio_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/Radio_spec.js @@ -9,7 +9,6 @@ describe("Radio Widget Functionality", function() { cy.addDsl(dsl); }); it("Radio Widget Functionality", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("radiogroupwidget"); /** * @param{Text} Random Text @@ -59,7 +58,6 @@ describe("Radio Widget Functionality", function() { }); it("Radio Functionality To Unchecked Visible Widget", function() { cy.get(publish.backToEditor).click(); - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("radiogroupwidget"); cy.togglebarDisable(commonlocators.visibleCheckbox); cy.PublishtheApp(); @@ -67,7 +65,6 @@ describe("Radio Widget Functionality", function() { cy.get(publish.backToEditor).click(); }); it("Radio Functionality To Check Visible Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("radiogroupwidget"); cy.togglebar(commonlocators.visibleCheckbox); cy.PublishtheApp(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/RichTextEditor_spec.js b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/RichTextEditor_spec.js index 8dc4c98a98..46aa8617b8 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/RichTextEditor_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/RichTextEditor_spec.js @@ -10,7 +10,6 @@ describe("RichTextEditor Widget Functionality", function() { }); beforeEach(() => { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("richtexteditorwidget"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/LayoutWidgets/Tab_spec.js b/app/client/cypress/integration/Smoke_TestSuite/LayoutWidgets/Tab_spec.js index 7ee578507e..38e9147674 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/LayoutWidgets/Tab_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/LayoutWidgets/Tab_spec.js @@ -10,7 +10,6 @@ describe("Tab widget test", function() { cy.addDsl(dsl); }); it("Tab Widget Functionality Test", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("tabswidget"); /** * @param{Text} Random Text @@ -60,7 +59,6 @@ describe("Tab widget test", function() { }); it("Tab Widget Functionality To Unchecked Visible Widget", function() { cy.get(publish.backToEditor).click(); - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("tabswidget"); cy.togglebarDisable(commonlocators.visibleCheckbox); cy.PublishtheApp(); @@ -68,7 +66,6 @@ describe("Tab widget test", function() { cy.get(publish.backToEditor).click(); }); it("Tab Widget Functionality To Check Visible Widget", function() { - cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("tabswidget"); cy.togglebar(commonlocators.visibleCheckbox); cy.PublishtheApp(); diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index ccca8faaf1..b3ab5e8192 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -1554,8 +1554,6 @@ Cypress.Commands.add("ValidatePublishTableData", value => { }); Cypress.Commands.add("ValidatePaginateResponseUrlData", runTestCss => { - cy.NavigateToEntityExplorer(); - cy.NavigateToApiEditor(); cy.SearchEntityandOpen("Api2"); cy.NavigateToPaginationTab(); cy.RunAPI(); @@ -1573,7 +1571,7 @@ Cypress.Commands.add("ValidatePaginateResponseUrlData", runTestCss => { const respBody = tabData.match(/"(.*)"/)[0]; localStorage.setItem("respBody", respBody); cy.log(respBody); - cy.get(pages.widgetsEditor).click({ force: true }); + cy.SearchEntityandOpen("Table1"); // cy.openPropertyPane("tablewidget"); // cy.testJsontext("tabledata", "{{Api2.data.results}}"); cy.isSelectRow(0); diff --git a/app/client/src/components/editorComponents/Sidebar.tsx b/app/client/src/components/editorComponents/Sidebar.tsx index ca199e45ba..c88446f159 100644 --- a/app/client/src/components/editorComponents/Sidebar.tsx +++ b/app/client/src/components/editorComponents/Sidebar.tsx @@ -1,28 +1,30 @@ import React, { memo } from "react"; -import { Switch, Route } from "react-router"; import styled from "styled-components"; -import { WIDGETS_URL } from "constants/routes"; -import WidgetSidebar from "pages/Editor/WidgetSidebar"; import ExplorerSidebar from "pages/Editor/Explorer"; +import { PanelStack, Classes } from "@blueprintjs/core"; +import { Colors } from "constants/Colors"; const SidebarWrapper = styled.div` + background-color: ${Colors.MINE_SHAFT}; padding: 0px 0 0 6px; + width: ${props => props.theme.sidebarWidth}; + color: ${props => props.theme.colors.textOnDarkBG}; overflow-y: auto; + & .${Classes.PANEL_STACK} { + height: 100%; + .${Classes.PANEL_STACK_VIEW} { + background: none; + } + } `; +const initialPanel = { component: ExplorerSidebar }; + export const Sidebar = memo(() => { return ( - - - - + ); }); diff --git a/app/client/src/constants/DefaultTheme.tsx b/app/client/src/constants/DefaultTheme.tsx index 5673d5320f..2d110ce332 100644 --- a/app/client/src/constants/DefaultTheme.tsx +++ b/app/client/src/constants/DefaultTheme.tsx @@ -645,7 +645,7 @@ export const theme: Theme = { }, ], sidebarWidth: "320px", - headerHeight: "50px", + headerHeight: "48px", canvasPadding: "20px 0 200px 0", sideNav: { maxWidth: 220, diff --git a/app/client/src/constants/Explorer.ts b/app/client/src/constants/Explorer.ts index 2ad4fb114b..94f482e1a5 100644 --- a/app/client/src/constants/Explorer.ts +++ b/app/client/src/constants/Explorer.ts @@ -1,2 +1,2 @@ export const ENTITY_EXPLORER_SEARCH_ID = "entity-explorer-search"; -export const ENTITY_EXPLORER_SEARCH_LOCATION_HASH = "#search"; +export const WIDGETS_SEARCH_ID = "#widgets-search"; diff --git a/app/client/src/constants/messages.ts b/app/client/src/constants/messages.ts index 8be218f5bd..616a46fc5f 100644 --- a/app/client/src/constants/messages.ts +++ b/app/client/src/constants/messages.ts @@ -157,3 +157,7 @@ export const SHOW_REQUEST = "Show Request"; export const TABLE_FILTER_COLUMN_TYPE_CALLOUT = "Change column datatype to see filter operators"; + +export const WIDGET_SIDEBAR_TITLE = "Widgets"; +export const WIDGET_SIDEBAR_CAPTION = + "To add a widget, please drag and drop a widget on the canvas to the right"; diff --git a/app/client/src/constants/routes.ts b/app/client/src/constants/routes.ts index b0b8394c4a..af0f81ca0f 100644 --- a/app/client/src/constants/routes.ts +++ b/app/client/src/constants/routes.ts @@ -50,19 +50,6 @@ export const BUILDER_PAGE_URL = ( ); }; -export const WIDGETS_URL = ( - applicationId = ":applicationId", - pageId = ":pageId", - params?: Record, -): string => { - if (!pageId) return APPLICATIONS_URL; - const queryParams = convertToQueryParams(params); - return ( - `${BUILDER_BASE_URL(applicationId)}/pages/${pageId}/edit/widgets` + - queryParams - ); -}; - export const API_EDITOR_URL = ( applicationId = ":applicationId", pageId = ":pageId", diff --git a/app/client/src/pages/Editor/Explorer/Actions/ActionsGroup.tsx b/app/client/src/pages/Editor/Explorer/Actions/ActionsGroup.tsx index f2b0bbc027..dab4b62ce3 100644 --- a/app/client/src/pages/Editor/Explorer/Actions/ActionsGroup.tsx +++ b/app/client/src/pages/Editor/Explorer/Actions/ActionsGroup.tsx @@ -71,7 +71,7 @@ export const ExplorerActionsGroup = memo((props: ExplorerActionsGroupProps) => { entityId={props.page.pageId + "_" + props.config?.type} step={props.step} disabled={!!props.searchKeyword && (!childNode || !props.actions.length)} - createFn={switchToCreateActionPage} + onCreate={switchToCreateActionPage} isDefaultExpanded={ props.config?.isGroupExpanded(params, props.page.pageId) || !!props.searchKeyword diff --git a/app/client/src/pages/Editor/Explorer/Datasources/DatasourcesGroup.tsx b/app/client/src/pages/Editor/Explorer/Datasources/DatasourcesGroup.tsx index 767fd128b3..bc3af46367 100644 --- a/app/client/src/pages/Editor/Explorer/Datasources/DatasourcesGroup.tsx +++ b/app/client/src/pages/Editor/Explorer/Datasources/DatasourcesGroup.tsx @@ -66,7 +66,7 @@ export const ExplorerDatasourcesGroup = ( ) > -1 || !!props.searchKeyword } disabled={disableDatasourceGroup} - createFn={() => { + onCreate={() => { history.push( DATA_SOURCES_EDITOR_URL(params.applicationId, params.pageId), ); diff --git a/app/client/src/pages/Editor/Explorer/Entity/index.tsx b/app/client/src/pages/Editor/Explorer/Entity/index.tsx index 5a7dcfe7b8..7d79b8dc32 100644 --- a/app/client/src/pages/Editor/Explorer/Entity/index.tsx +++ b/app/client/src/pages/Editor/Explorer/Entity/index.tsx @@ -82,13 +82,13 @@ export type EntityProps = { action?: () => void; active?: boolean; isDefaultExpanded?: boolean; - createFn?: () => void; + onCreate?: () => void; contextMenu?: ReactNode; searchKeyword?: string; step: number; updateEntityName?: (id: string, name: string) => any; runActionOnExpand?: boolean; - nameTransformFn?: (input: string, limit?: number) => string; + onNameEdit?: (input: string, limit?: number) => string; }; export const Entity = forwardRef( @@ -155,13 +155,13 @@ export const Entity = forwardRef( className={`${EntityClassNames.NAME}`} ref={itemRef} name={props.name} - nameTransformFn={props.nameTransformFn} + nameTransformFn={props.onNameEdit} isEditing={!!props.updateEntityName && isEditing} updateEntityName={updateNameCallback} searchKeyword={props.searchKeyword} /> {props.contextMenu} diff --git a/app/client/src/pages/Editor/Explorer/ExplorerSearch.tsx b/app/client/src/pages/Editor/Explorer/ExplorerSearch.tsx index 91d12b394e..c8dab06ef1 100644 --- a/app/client/src/pages/Editor/Explorer/ExplorerSearch.tsx +++ b/app/client/src/pages/Editor/Explorer/ExplorerSearch.tsx @@ -63,15 +63,19 @@ const Underline = styled.div` `; /*eslint-disable react/display-name */ export const ExplorerSearch = forwardRef( - (props: { clear: () => void }, ref: Ref) => { + ( + props: { clear: () => void; placeholder?: string }, + ref: Ref, + ) => { return ( diff --git a/app/client/src/pages/Editor/Explorer/Pages/PageEntity.tsx b/app/client/src/pages/Editor/Explorer/Pages/PageEntity.tsx index a40a064f1f..8e7aa65992 100644 --- a/app/client/src/pages/Editor/Explorer/Pages/PageEntity.tsx +++ b/app/client/src/pages/Editor/Explorer/Pages/PageEntity.tsx @@ -22,6 +22,7 @@ type ExplorerPageEntityProps = { actions: any[]; step: number; searchKeyword?: string; + showWidgetsSidebar: () => void; }; export const ExplorerPageEntity = (props: ExplorerPageEntityProps) => { const params = useParams(); @@ -49,6 +50,10 @@ export const ExplorerPageEntity = (props: ExplorerPageEntityProps) => { ); const icon = props.page.isDefault ? homePageIcon : pageIcon; + + let addWidgetsFn; + if (isCurrentPage) addWidgetsFn = props.showWidgetsSidebar; + return ( { isDefaultExpanded={isCurrentPage || !!props.searchKeyword} updateEntityName={updatePage} contextMenu={contextMenu} - nameTransformFn={resolveAsSpaceChar} + onNameEdit={resolveAsSpaceChar} > {getActionGroups( diff --git a/app/client/src/pages/Editor/Explorer/Pages/PageGroup.tsx b/app/client/src/pages/Editor/Explorer/Pages/PageGroup.tsx index 8c65fb7b33..d9be1dfc07 100644 --- a/app/client/src/pages/Editor/Explorer/Pages/PageGroup.tsx +++ b/app/client/src/pages/Editor/Explorer/Pages/PageGroup.tsx @@ -17,6 +17,7 @@ type ExplorerPageGroupProps = { step: number; widgets?: Record; actions: Record; + showWidgetsSidebar: () => void; }; export const ExplorerPageGroup = (props: ExplorerPageGroupProps) => { @@ -46,6 +47,7 @@ export const ExplorerPageGroup = (props: ExplorerPageGroupProps) => { actions={pageActions} searchKeyword={props.searchKeyword} page={page} + showWidgetsSidebar={props.showWidgetsSidebar} /> ); }); @@ -61,7 +63,7 @@ export const ExplorerPageGroup = (props: ExplorerPageGroupProps) => { action={noop} entityId="Pages" step={props.step} - createFn={createPageCallback} + onCreate={createPageCallback} > {pageEntities} diff --git a/app/client/src/pages/Editor/Explorer/Widgets/WidgetGroup.tsx b/app/client/src/pages/Editor/Explorer/Widgets/WidgetGroup.tsx index 3a0019ba42..660b1ae607 100644 --- a/app/client/src/pages/Editor/Explorer/Widgets/WidgetGroup.tsx +++ b/app/client/src/pages/Editor/Explorer/Widgets/WidgetGroup.tsx @@ -10,7 +10,7 @@ import { } from "constants/WidgetConstants"; import { useParams } from "react-router"; import { ExplorerURLParams } from "../helpers"; -import { BUILDER_PAGE_URL, WIDGETS_URL } from "constants/routes"; +import { BUILDER_PAGE_URL } from "constants/routes"; import { Link } from "react-router-dom"; import styled from "styled-components"; import { AppState } from "reducers"; @@ -108,6 +108,7 @@ type ExplorerWidgetGroupProps = { step: number; widgets?: WidgetTree; searchKeyword?: string; + addWidgetsFn?: () => void; }; const StyledLink = styled(Link)` @@ -155,13 +156,8 @@ export const ExplorerWidgetGroup = memo((props: ExplorerWidgetGroupProps) => { ) : ( " " )} - click the{" "} - - - Widgets - - {" "} - navigation menu icon on the left to drag and drop widgets + click the + icon on the Widgets group + to drag and drop widgets ); } else if (!childNode && props.searchKeyword) return null; @@ -170,7 +166,7 @@ export const ExplorerWidgetGroup = memo((props: ExplorerWidgetGroupProps) => { { !!props.searchKeyword || (params.pageId === props.pageId && !!selectedWidget) } + onCreate={props.addWidgetsFn} > {childNode} diff --git a/app/client/src/pages/Editor/Explorer/index.tsx b/app/client/src/pages/Editor/Explorer/index.tsx index 6b09ac0d18..9ca0a51f7f 100644 --- a/app/client/src/pages/Editor/Explorer/index.tsx +++ b/app/client/src/pages/Editor/Explorer/index.tsx @@ -1,4 +1,4 @@ -import React, { useRef, MutableRefObject } from "react"; +import React, { useRef, MutableRefObject, useCallback } from "react"; import styled from "styled-components"; import Divider from "components/editorComponents/Divider"; import { @@ -11,8 +11,12 @@ import Search from "./ExplorerSearch"; import ExplorerPageGroup from "./Pages/PageGroup"; import ExplorerDatasourcesGroup from "./Datasources/DatasourcesGroup"; import { scrollbarDark } from "constants/DefaultTheme"; -import { NonIdealState, Classes } from "@blueprintjs/core"; - +import { NonIdealState, Classes, IPanelProps } from "@blueprintjs/core"; +import WidgetSidebar from "../WidgetSidebar"; +import { BUILDER_PAGE_URL } from "constants/routes"; +import history from "utils/history"; +import { useParams } from "react-router"; +import { ExplorerURLParams } from "./helpers"; const Wrapper = styled.div` height: 100%; overflow-y: scroll; @@ -29,7 +33,8 @@ const StyledDivider = styled(Divider)` border-bottom-color: rgba(255, 255, 255, 0.1); `; -const EntityExplorer = () => { +const EntityExplorer = (props: IPanelProps) => { + const { applicationId, pageId } = useParams(); const searchInputRef: MutableRefObject = useRef( null, ); @@ -51,6 +56,11 @@ const EntityExplorer = () => { const noDatasource = !datasources || datasources.length === 0; noResults = noWidgets && noActions && noDatasource; } + const { openPanel } = props; + const showWidgetsSidebar = useCallback(() => { + history.push(BUILDER_PAGE_URL(applicationId, pageId)); + openPanel({ component: WidgetSidebar }); + }, [openPanel, applicationId, pageId]); return ( @@ -60,6 +70,7 @@ const EntityExplorer = () => { step={0} widgets={widgets} actions={actions} + showWidgetsSidebar={showWidgetsSidebar} /> {noResults && ( { return ( - + diff --git a/app/client/src/pages/Editor/Navbar.tsx b/app/client/src/pages/Editor/Navbar.tsx deleted file mode 100644 index b1ed90beb8..0000000000 --- a/app/client/src/pages/Editor/Navbar.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import React from "react"; -import { useParams } from "react-router-dom"; -import styled from "styled-components"; -import SidebarComponent from "components/editorComponents/Sidebar"; -import NavBarItem from "components/editorComponents/NavBarItem"; -import { - BuilderRouteParams, - BUILDER_PAGE_URL, - WIDGETS_URL, -} from "constants/routes"; -import { Colors } from "constants/Colors"; -import { MenuIcons } from "icons/MenuIcons"; - -const Wrapper = styled.div` - display: grid; - grid-template-columns: 1fr 5fr; - width: ${props => props.theme.sidebarWidth}; - box-shadow: 0px 1px 3px ${Colors.MINE_SHAFT}; - background-color: ${Colors.MINE_SHAFT}; - z-index: 3; -`; - -const NavBar = styled.div` - background-color: ${Colors.MINE_SHAFT}; - color: ${props => props.theme.colors.textOnDarkBG}; - display: flex; - flex-direction: column; - box-shadow: 0px 0px 4px ${Colors.CODE_GRAY}; -`; - -const Navbar = () => { - const params = useParams(); - - return ( - - - { - // Currently, the explorer shows on all paths except for the - // WIDGETS_URL path - - // get the applicationId and pageId from the current location pathname - const found = current.match( - /^\/applications\/(?\w+)\/pages\/(?\w+)\//, - ); - // In this case: expected = BUILDER_PAGE_URL(applicationId, pageId) - // If current url begins with expected url AND - // If the current url isn't the WIDGETS_URL THEN - // this is an explorer sidebar path - return ( - current.indexOf(expected) === 0 && - current !== - WIDGETS_URL(found?.groups?.applicationId, found?.groups?.pageId) - ); - }} - /> - expected === current} - /> - - - - ); -}; -Navbar.displayName = "Navbar"; - -Navbar.whyDidYouRender = { - logOnDifferentValues: false, - customName: "MainSidebar", -}; - -export default Navbar; diff --git a/app/client/src/pages/Editor/WidgetSidebar.tsx b/app/client/src/pages/Editor/WidgetSidebar.tsx index e3b5445942..5e94818a78 100644 --- a/app/client/src/pages/Editor/WidgetSidebar.tsx +++ b/app/client/src/pages/Editor/WidgetSidebar.tsx @@ -1,14 +1,16 @@ -import React from "react"; +import React, { useRef, useEffect, useState } from "react"; import { useSelector } from "react-redux"; import WidgetCard from "./WidgetCard"; import styled from "styled-components"; import { WidgetCardProps } from "widgets/BaseWidget"; import { getWidgetCards } from "selectors/editorSelectors"; import { getColorWithOpacity } from "constants/DefaultTheme"; - -type WidgetSidebarProps = { - cards: { [id: string]: WidgetCardProps[] }; -}; +import { IPanelProps, Icon, Classes } from "@blueprintjs/core"; +import { Colors } from "constants/Colors"; +import ExplorerSearch from "./Explorer/ExplorerSearch"; +import { debounce } from "lodash"; +import produce from "immer"; +import { WIDGET_SIDEBAR_CAPTION } from "constants/messages"; const MainWrapper = styled.div` text-transform: capitalize; @@ -43,22 +45,108 @@ const CardsWrapper = styled.div` align-items: stretch; `; -const WidgetSidebar = () => { +const CloseIcon = styled(Icon)` + &&.${Classes.ICON} { + cursor: pointer; + display: flex; + justify-content: center; + opacity: 0.6; + &:hover { + opacity: 1; + } + } +`; + +const Header = styled.div` + display: grid; + grid-template-columns: 7fr 1fr; +`; + +const Info = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: space-around; + text-transform: none; + h4 { + margin-top: 0px; + } + p { + opacity: 0.6; + } +`; + +const WidgetSidebar = (props: IPanelProps) => { const cards = useSelector(getWidgetCards); - const groups = Object.keys(cards); + const [filteredCards, setFilteredCards] = useState(cards); + const searchInputRef = useRef(null); + const clearSearchInput = () => { + if (searchInputRef.current) searchInputRef.current.value = ""; + }; + const search = debounce((e: any) => { + const keyword = e.target.value.toLowerCase(); + let filteredCards = cards; + if (keyword.trim().length > 0) { + filteredCards = produce(cards, draft => { + for (const [key, value] of Object.entries(cards)) { + value.forEach((card, index) => { + if (card.widgetCardName.toLowerCase().indexOf(keyword) === -1) { + delete draft[key][index]; + } + }); + draft[key] = draft[key].filter(Boolean); + if (draft[key].length === 0) { + delete draft[key]; + } + } + }); + } + setFilteredCards(filteredCards); + }, 300); + useEffect(() => { + const el: HTMLInputElement | null = searchInputRef.current; + + el?.addEventListener("keydown", search); + el?.addEventListener("cleared", search); + return () => { + el?.removeEventListener("keydown", search); + el?.removeEventListener("cleared", search); + }; + }, [searchInputRef, search]); + const groups = Object.keys(filteredCards); return ( - - {groups.map((group: string) => ( - -
{group}
- - {cards[group].map((card: WidgetCardProps) => ( - - ))} - -
- ))} -
+ <> + + + +
+ +

{WIDGET_SIDEBAR_CAPTION}

+
+ +
+ {groups.map((group: string) => ( + +
{group}
+ + {filteredCards[group].map((card: WidgetCardProps) => ( + + ))} + +
+ ))} +
+ ); }; diff --git a/app/client/src/pages/Editor/index.tsx b/app/client/src/pages/Editor/index.tsx index fdb5d4c32a..44a9ac159b 100644 --- a/app/client/src/pages/Editor/index.tsx +++ b/app/client/src/pages/Editor/index.tsx @@ -5,7 +5,6 @@ import { withRouter, RouteComponentProps } from "react-router-dom"; import { BuilderRouteParams, getApplicationViewerPageURL, - BUILDER_PAGE_URL, } from "constants/routes"; import { AppState } from "reducers"; import MainContainer from "./MainContainer"; @@ -32,9 +31,8 @@ import { initEditor } from "actions/initActions"; import { editorInitializer } from "utils/EditorUtils"; import { ENTITY_EXPLORER_SEARCH_ID, - ENTITY_EXPLORER_SEARCH_LOCATION_HASH, + WIDGETS_SEARCH_ID, } from "constants/Explorer"; -import history from "utils/history"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; import { getAppsmithConfigs } from "configs"; import { getCurrentUser } from "selectors/usersSelectors"; @@ -66,19 +64,14 @@ class Editor extends Component { combo="meta + f" label="Search entities" onKeyDown={(e: any) => { - //TODO(abhinav): make this id into a constant. - const el = document.getElementById(ENTITY_EXPLORER_SEARCH_ID); - if (!el) { - history.push( - `${BUILDER_PAGE_URL( - this.props.currentApplicationId, - this.props.currentPageId, - )}${ENTITY_EXPLORER_SEARCH_LOCATION_HASH}`, - ); - } else { - el?.focus(); - } - + const entitySearchInput = document.getElementById( + ENTITY_EXPLORER_SEARCH_ID, + ); + const widgetSearchInput = document.getElementById( + WIDGETS_SEARCH_ID, + ); + if (entitySearchInput) entitySearchInput.focus(); + if (widgetSearchInput) widgetSearchInput.focus(); e.preventDefault(); e.stopPropagation(); }} diff --git a/app/client/src/pages/Editor/routes.tsx b/app/client/src/pages/Editor/routes.tsx index c632b60056..de253e8c90 100644 --- a/app/client/src/pages/Editor/routes.tsx +++ b/app/client/src/pages/Editor/routes.tsx @@ -19,7 +19,6 @@ import { getCurlImportPageURL, API_EDITOR_URL_WITH_SELECTED_PAGE_ID, getProviderTemplatesURL, - WIDGETS_URL, } from "constants/routes"; import styled from "styled-components"; import AppRoute from "pages/common/AppRoute"; @@ -63,8 +62,7 @@ class EditorsRouter extends React.Component< this.state = { isVisible: this.props.location.pathname !== - BUILDER_PAGE_URL(applicationId, pageId) && - this.props.location.pathname !== WIDGETS_URL(applicationId, pageId), + BUILDER_PAGE_URL(applicationId, pageId), }; } @@ -74,8 +72,7 @@ class EditorsRouter extends React.Component< this.setState({ isVisible: this.props.location.pathname !== - BUILDER_PAGE_URL(applicationId, pageId) && - this.props.location.pathname !== WIDGETS_URL(applicationId, pageId), + BUILDER_PAGE_URL(applicationId, pageId), }); } } From a7e33a90e2ae3f66a66835921b958e43ee691253 Mon Sep 17 00:00:00 2001 From: devrk96 Date: Wed, 2 Sep 2020 12:16:02 +0530 Subject: [PATCH 4/4] Feature: Dropdown component (#477) * dropdown with text and icon implemented * dropdown with lable implemented and story updated * Icon import and storyWrapper import updated * onclick cleaned --- app/client/src/components/ads/Dropdown.tsx | 179 +++++++++++++++--- app/client/src/components/ads/Icon.tsx | 5 + .../components/stories/Dropdown.stories.tsx | 112 +++++++++++ 3 files changed, 273 insertions(+), 23 deletions(-) create mode 100644 app/client/src/components/stories/Dropdown.stories.tsx diff --git a/app/client/src/components/ads/Dropdown.tsx b/app/client/src/components/ads/Dropdown.tsx index bcb0e6bac0..d7e91c022f 100644 --- a/app/client/src/components/ads/Dropdown.tsx +++ b/app/client/src/components/ads/Dropdown.tsx @@ -1,36 +1,169 @@ -import { ReactNode } from "react"; -import { IconName } from "./Icon"; -import { CommonComponentProps } from "./common"; +import React, { useState, useEffect, useCallback } from "react"; +import Icon, { IconName, IconSize } from "./Icon"; +import { CommonComponentProps, Classes } from "./common"; +import styled from "styled-components"; +import Text, { TextType } from "./Text"; type DropdownOption = { - label: string; + label?: string; value: string; id?: string; - icon: IconName; // Create an icon library + icon?: IconName; onSelect?: (option: DropdownOption) => void; children?: DropdownOption[]; }; -export enum DropdownDisplayType { - TAGS = "TAGS", - CHECKBOXES = "CHECKBOXES", -} - type DropdownProps = CommonComponentProps & { options: DropdownOption[]; - selectHandler: (selectedValue: string) => void; - selected?: DropdownOption; - multiselectDisplayType?: DropdownDisplayType; - checked?: boolean; - multi?: boolean; - autocomplete?: boolean; - addItem?: { - displayText: string; - addItemHandler: (name: string) => void; - }; - toggle?: ReactNode; + selected: DropdownOption; }; -export default function Button(props: DropdownProps) { - return null; +const DropdownContainer = styled.div` + width: 260px; +`; + +const Selected = styled.div<{ isOpen: boolean; disabled?: boolean }>` + padding: ${props => props.theme.spaces[4]}px + ${props => props.theme.spaces[6]}px; + background: ${props => + props.disabled + ? props.theme.colors.blackShades[2] + : props.theme.colors.blackShades[0]}; + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + cursor: pointer; + ${props => + props.isOpen && !props.disabled + ? `border: 1.2px solid ${props.theme.colors.info.main}` + : null}; + ${props => + props.isOpen && !props.disabled ? "box-sizing: border-box" : null}; + ${props => + props.isOpen && !props.disabled + ? "box-shadow: 0px 0px 4px 4px rgba(203, 72, 16, 0.18)" + : null}; + .${Classes.TEXT} { + ${props => + props.disabled + ? `color: ${props.theme.colors.blackShades[6]}` + : `color: ${props.theme.colors.blackShades[7]}`}; + } +`; + +const DropdownWrapper = styled.div` + margin-top: ${props => props.theme.spaces[2] - 1}px; + background: ${props => props.theme.colors.blackShades[3]}; + box-shadow: 0px 12px 28px rgba(0, 0, 0, 0.6); + width: 100%; +`; + +const OptionWrapper = styled.div<{ selected: boolean }>` + padding: ${props => props.theme.spaces[4]}px + ${props => props.theme.spaces[6]}px; + cursor: pointer; + display: flex; + align-items: center; + ${props => + props.selected ? `background: ${props.theme.colors.blackShades[4]}` : null}; + .${Classes.TEXT} { + ${props => + props.selected ? `color: ${props.theme.colors.blackShades[9]}` : null}; + } + .${Classes.ICON} { + margin-right: ${props => props.theme.spaces[5]}px; + svg { + path { + ${props => + props.selected + ? `fill: ${props.theme.colors.blackShades[8]}` + : `fill: ${props.theme.colors.blackShades[6]}`}; + } + } + } + + &:hover { + .${Classes.TEXT} { + color: ${props => props.theme.colors.blackShades[9]}; + } + .${Classes.ICON} { + svg { + path { + fill: ${props => props.theme.colors.blackShades[8]}; + } + } + } + } +`; + +const LabelWrapper = styled.div<{ label?: string }>` + display: flex; + flex-direction: column; + align-item: flex-start; + + ${props => + props.label + ? ` + .${Classes.TEXT}:last-child { + margin-top: ${props.theme.spaces[2] - 1}px; + } + ` + : null} +`; + +export default function Dropdown(props: DropdownProps) { + const [isOpen, setIsOpen] = useState(false); + const [selected, setSelected] = useState(props.selected); + + useEffect(() => { + setSelected(props.selected); + }, [props.selected]); + + const optionClickHandler = useCallback((option: DropdownOption) => { + setSelected(option); + setIsOpen(false); + option.onSelect && option.onSelect(option); + }, []); + + return ( + setIsOpen(false)}> + setIsOpen(!isOpen)} + > + {selected.value} + + + + {isOpen && !props.disabled ? ( + + {props.options.map((option: DropdownOption, index: number) => { + return ( + optionClickHandler(option)} + > + {option.icon ? ( + + ) : null} + + {option.label ? ( + {option.value} + ) : ( + {option.value} + )} + {option.label ? ( + {option.label} + ) : null} + + + ); + })} + + ) : null} + + ); } diff --git a/app/client/src/components/ads/Icon.tsx b/app/client/src/components/ads/Icon.tsx index 7e3b9fd89a..014576198d 100644 --- a/app/client/src/components/ads/Icon.tsx +++ b/app/client/src/components/ads/Icon.tsx @@ -8,6 +8,7 @@ import { ReactComponent as ErrorIcon } from "assets/icons/ads/error.svg"; import { ReactComponent as SuccessIcon } from "assets/icons/ads/success.svg"; import { ReactComponent as SearchIcon } from "assets/icons/ads/search.svg"; import { ReactComponent as CloseIcon } from "assets/icons/ads/close.svg"; +import { ReactComponent as DownArrow } from "assets/icons/ads/down_arrow.svg"; import { ReactComponent as ShareIcon } from "assets/icons/ads/share.svg"; import { ReactComponent as RocketIcon } from "assets/icons/ads/launch.svg"; import { ReactComponent as WorkspaceIcon } from "assets/icons/ads/workspace.svg"; @@ -72,6 +73,7 @@ export const IconCollection = [ "plus", "invite-user", "view-all", + "downArrow", ] as const; export type IconName = typeof IconCollection[number]; @@ -143,6 +145,9 @@ const Icon = (props: IconProps & CommonComponentProps) => { case "close": returnIcon = ; break; + case "downArrow": + returnIcon = ; + break; case "share": returnIcon = ; break; diff --git a/app/client/src/components/stories/Dropdown.stories.tsx b/app/client/src/components/stories/Dropdown.stories.tsx new file mode 100644 index 0000000000..4b23cd0aa2 --- /dev/null +++ b/app/client/src/components/stories/Dropdown.stories.tsx @@ -0,0 +1,112 @@ +import React from "react"; +import { withKnobs, select, boolean, text } from "@storybook/addon-knobs"; +import { withDesign } from "storybook-addon-designs"; +import Dropdown from "components/ads/Dropdown"; +import { action } from "@storybook/addon-actions"; +import { IconCollection } from "components/ads/Icon"; +import { StoryWrapper } from "./Tabs.stories"; + +export default { + title: "Dropdown", + component: Dropdown, + decorators: [withKnobs, withDesign], +}; + +export const Text = () => ( + + + +); + +export const IconAndText = () => ( + + + +); + +export const LabelAndText = () => ( + + + +);